refactor(chat): remove typing indicator

This commit is contained in:
2026-06-10 21:33:33 +08:00
parent a6ea97142a
commit 8e07c5342a
3 changed files with 9 additions and 74 deletions
@@ -34,10 +34,6 @@ jest.mock("framer-motion", () => ({
}, },
})); }));
jest.mock("./GlobalChatbox.parts", () => ({
TypingIndicator: () => <div>typing</div>,
}));
jest.mock("./AgentTurn", () => ({ jest.mock("./AgentTurn", () => ({
AgentTurn: ({ message, isStreaming }: { message: Message; isStreaming: boolean }) => { AgentTurn: ({ message, isStreaming }: { message: Message; isStreaming: boolean }) => {
React.useEffect(() => { React.useEffect(() => {
+9 -40
View File
@@ -10,7 +10,6 @@ import TroubleshootRounded from "@mui/icons-material/TroubleshootRounded";
import MapRounded from "@mui/icons-material/MapRounded"; import MapRounded from "@mui/icons-material/MapRounded";
import { AgentTurn } from "./AgentTurn"; import { AgentTurn } from "./AgentTurn";
import { TypingIndicator } from "./GlobalChatbox.parts";
import type { PermissionReply } from "@/lib/chatStream"; import type { PermissionReply } from "@/lib/chatStream";
import type { import type {
Message, Message,
@@ -39,7 +38,7 @@ type AgentWorkspaceProps = {
type TurnListProps = { type TurnListProps = {
messages: Message[]; messages: Message[];
isStreaming: boolean; isAssistantStreaming: boolean;
streamingMessageId: string | null; streamingMessageId: string | null;
speakingMessageId: string | null; speakingMessageId: string | null;
speechState: SpeechState; speechState: SpeechState;
@@ -65,7 +64,7 @@ const TurnItem = React.memo(AgentTurn);
const TurnListInner = ({ const TurnListInner = ({
messages, messages,
isStreaming, isAssistantStreaming,
streamingMessageId, streamingMessageId,
speakingMessageId, speakingMessageId,
speechState, speechState,
@@ -85,7 +84,7 @@ const TurnListInner = ({
<TurnItem <TurnItem
key={message.id} key={message.id}
message={message} message={message}
isStreaming={isStreaming && message.id === streamingMessageId} isStreaming={isAssistantStreaming && message.id === streamingMessageId}
messageSpeechState={speakingMessageId === message.id ? speechState : "idle"} messageSpeechState={speakingMessageId === message.id ? speechState : "idle"}
onSpeak={onSpeak} onSpeak={onSpeak}
onPause={onPauseSpeech} onPause={onPauseSpeech}
@@ -106,7 +105,7 @@ const TurnList = React.memo(
TurnListInner, TurnListInner,
(prevProps, nextProps) => (prevProps, nextProps) =>
sameMessages(prevProps.messages, nextProps.messages) && sameMessages(prevProps.messages, nextProps.messages) &&
prevProps.isStreaming === nextProps.isStreaming && prevProps.isAssistantStreaming === nextProps.isAssistantStreaming &&
prevProps.streamingMessageId === nextProps.streamingMessageId && prevProps.streamingMessageId === nextProps.streamingMessageId &&
prevProps.speakingMessageId === nextProps.speakingMessageId && prevProps.speakingMessageId === nextProps.speakingMessageId &&
prevProps.speechState === nextProps.speechState && prevProps.speechState === nextProps.speechState &&
@@ -318,19 +317,10 @@ export const AgentWorkspace = ({
onReplyQuestion, onReplyQuestion,
onRejectQuestion, onRejectQuestion,
}: AgentWorkspaceProps) => { }: AgentWorkspaceProps) => {
const theme = useTheme(); const streamingMessageId =
const latestAssistant = [...messages]
.reverse()
.find((message) => message.role === "assistant");
const showTypingIndicator =
isStreaming &&
(!latestAssistant ||
(latestAssistant.content.trim().length === 0 &&
!(latestAssistant.artifacts?.length)));
const streamingMessage =
isStreaming && messages.at(-1)?.role === "assistant" isStreaming && messages.at(-1)?.role === "assistant"
? messages.at(-1) ? messages.at(-1)?.id ?? null
: undefined; : null;
const handleScroll = React.useCallback( const handleScroll = React.useCallback(
(event: React.UIEvent<HTMLDivElement>) => { (event: React.UIEvent<HTMLDivElement>) => {
if (!onScrollStateChange) return; if (!onScrollStateChange) return;
@@ -372,8 +362,8 @@ export const AgentWorkspace = ({
<Box sx={{ display: "flex", flexDirection: "column", gap: 2 }}> <Box sx={{ display: "flex", flexDirection: "column", gap: 2 }}>
<TurnList <TurnList
messages={messages} messages={messages}
isStreaming={isStreaming} isAssistantStreaming={isStreaming}
streamingMessageId={streamingMessage?.id ?? null} streamingMessageId={streamingMessageId}
speakingMessageId={speakingMessageId} speakingMessageId={speakingMessageId}
speechState={speechState} speechState={speechState}
onSpeak={onSpeak} onSpeak={onSpeak}
@@ -391,27 +381,6 @@ export const AgentWorkspace = ({
</> </>
)} )}
{!isLoadingSession && showTypingIndicator ? (
<motion.div
initial={{ opacity: 0, y: 10, scale: 0.94 }}
animate={{ opacity: 1, y: 0, scale: 1 }}
transition={{ type: "spring", stiffness: 300 }}
style={{ alignSelf: "flex-start", display: "flex", gap: 12, marginTop: 4, marginLeft: 44 }}
>
<Paper
elevation={0}
sx={{
p: 1.3,
borderRadius: 4,
bgcolor: alpha("#fff", 0.82),
boxShadow: `0 4px 12px ${alpha(theme.palette.common.black, 0.05)}`,
}}
>
<TypingIndicator />
</Paper>
</motion.div>
) : null}
<div <div
ref={bottomRef} ref={bottomRef}
style={{ style={{
@@ -2,36 +2,6 @@
import React from "react"; import React from "react";
import { motion } from "framer-motion"; import { motion } from "framer-motion";
import { Box, Stack } from "@mui/material";
export const TypingIndicator = () => {
return (
<Stack direction="row" spacing={0.5} alignItems="center" sx={{ p: 1 }}>
{[0, 1, 2].map((i) => (
<motion.div
key={i}
initial={{ y: 0 }}
animate={{ y: [-4, 4, -4] }}
transition={{
duration: 0.6,
repeat: Infinity,
delay: i * 0.15,
ease: "easeInOut",
}}
>
<Box
sx={{
width: 8,
height: 8,
borderRadius: "50%",
background: "linear-gradient(135deg, #FF6B6B 0%, #FF8E53 100%)",
}}
/>
</motion.div>
))}
</Stack>
);
};
export const Blob = ({ export const Blob = ({
color, color,