refactor: simplify chat fork flow
Build Push and Deploy / docker-image (push) Successful in 1m29s
Build Push and Deploy / deploy-fallback-log (push) Has been skipped

This commit is contained in:
2026-06-08 16:07:39 +08:00
parent 34fd5bfb1a
commit 2691f42581
10 changed files with 274 additions and 656 deletions
+26 -98
View File
@@ -13,17 +13,12 @@ import { AgentTurn } from "./AgentTurn";
import { TypingIndicator } from "./GlobalChatbox.parts";
import type { PermissionReply } from "@/lib/chatStream";
import type {
BranchGroup,
BranchState,
BranchTransition,
Message,
SpeechState,
} from "./GlobalChatbox.types";
type AgentWorkspaceProps = {
messages: Message[];
branchGroups: BranchGroup[];
branchTransition: BranchTransition | null;
isStreaming: boolean;
bottomRef: React.RefObject<HTMLDivElement | null>;
speakingMessageId: string | null;
@@ -33,15 +28,13 @@ type AgentWorkspaceProps = {
onResumeSpeech: () => void;
onStopSpeech: () => void;
isTtsSupported: boolean;
onRegenerate: () => void;
onEditResubmit: (messageId: string, newContent: string) => void;
onCycleBranch: (rootMessageId: string, direction: -1 | 1) => void;
onRegenerate: (messageId: string) => void;
onCreateBranch: (messageId: string) => void;
onReplyPermission: (requestId: string, reply: PermissionReply) => void;
};
type TurnListProps = {
messages: Message[];
branchGroups: BranchGroup[];
speakingMessageId: string | null;
speechState: SpeechState;
onSpeak: (messageId: string, text: string) => void;
@@ -49,9 +42,8 @@ type TurnListProps = {
onResumeSpeech: () => void;
onStopSpeech: () => void;
isTtsSupported: boolean;
onRegenerate: () => void;
onEditResubmit: (messageId: string, newContent: string) => void;
onCycleBranch: (rootMessageId: string, direction: -1 | 1) => void;
onRegenerate: (messageId: string) => void;
onCreateBranch: (messageId: string) => void;
onReplyPermission: (requestId: string, reply: PermissionReply) => void;
};
@@ -61,7 +53,6 @@ const sameMessages = (left: Message[], right: Message[]) =>
const TurnListInner = ({
messages,
branchGroups,
speakingMessageId,
speechState,
onSpeak,
@@ -70,45 +61,26 @@ const TurnListInner = ({
onStopSpeech,
isTtsSupported,
onRegenerate,
onEditResubmit,
onCycleBranch,
onCreateBranch,
onReplyPermission,
}: TurnListProps) => {
const branchStateByRootId = React.useMemo(() => {
const next = new Map<string, BranchState>();
branchGroups.forEach((group) => {
if (group.branches.length > 1) {
next.set(group.rootMessageId, {
activeIndex: group.activeIndex,
total: group.branches.length,
});
}
});
return next;
}, [branchGroups]);
return (
<>
{messages.map((message) => {
const rootMessageId = message.branchRootId ?? message.id;
return (
<AgentTurn
key={rootMessageId}
message={message}
branchState={branchStateByRootId.get(rootMessageId)}
messageSpeechState={speakingMessageId === message.id ? speechState : "idle"}
onSpeak={onSpeak}
onPause={onPauseSpeech}
onResume={onResumeSpeech}
onStopSpeech={onStopSpeech}
isTtsSupported={isTtsSupported}
onRegenerate={onRegenerate}
onEditResubmit={onEditResubmit}
onCycleBranch={onCycleBranch}
onReplyPermission={onReplyPermission}
/>
);
})}
{messages.map((message) => (
<AgentTurn
key={message.id}
message={message}
messageSpeechState={speakingMessageId === message.id ? speechState : "idle"}
onSpeak={onSpeak}
onPause={onPauseSpeech}
onResume={onResumeSpeech}
onStopSpeech={onStopSpeech}
isTtsSupported={isTtsSupported}
onRegenerate={onRegenerate}
onCreateBranch={onCreateBranch}
onReplyPermission={onReplyPermission}
/>
))}
</>
);
};
@@ -117,7 +89,6 @@ const TurnList = React.memo(
TurnListInner,
(prevProps, nextProps) =>
sameMessages(prevProps.messages, nextProps.messages) &&
prevProps.branchGroups === nextProps.branchGroups &&
prevProps.speakingMessageId === nextProps.speakingMessageId &&
prevProps.speechState === nextProps.speechState &&
prevProps.onSpeak === nextProps.onSpeak &&
@@ -126,8 +97,7 @@ const TurnList = React.memo(
prevProps.onStopSpeech === nextProps.onStopSpeech &&
prevProps.isTtsSupported === nextProps.isTtsSupported &&
prevProps.onRegenerate === nextProps.onRegenerate &&
prevProps.onEditResubmit === nextProps.onEditResubmit &&
prevProps.onCycleBranch === nextProps.onCycleBranch &&
prevProps.onCreateBranch === nextProps.onCreateBranch &&
prevProps.onReplyPermission === nextProps.onReplyPermission,
);
@@ -249,8 +219,6 @@ const EmptyState = () => {
export const AgentWorkspace = ({
messages,
branchGroups,
branchTransition,
isStreaming,
bottomRef,
speakingMessageId,
@@ -261,8 +229,7 @@ export const AgentWorkspace = ({
onStopSpeech,
isTtsSupported,
onRegenerate,
onEditResubmit,
onCycleBranch,
onCreateBranch,
onReplyPermission,
}: AgentWorkspaceProps) => {
const theme = useTheme();
@@ -274,18 +241,12 @@ export const AgentWorkspace = ({
(!latestAssistant ||
(latestAssistant.content.trim().length === 0 &&
!(latestAssistant.artifacts?.length)));
const stableMessages = branchTransition
? messages.slice(0, branchTransition.parentCount)
: messages;
const transitionMessages = branchTransition
? messages.slice(branchTransition.parentCount)
: [];
const streamingMessage =
!branchTransition && isStreaming && messages.at(-1)?.role === "assistant"
isStreaming && messages.at(-1)?.role === "assistant"
? messages.at(-1)
: undefined;
const historyMessages =
streamingMessage !== undefined ? messages.slice(0, -1) : stableMessages;
streamingMessage !== undefined ? messages.slice(0, -1) : messages;
return (
<Box
@@ -307,7 +268,6 @@ export const AgentWorkspace = ({
<Box sx={{ display: "flex", flexDirection: "column", gap: 2 }}>
<TurnList
messages={historyMessages}
branchGroups={branchGroups}
speakingMessageId={speakingMessageId}
speechState={speechState}
onSpeak={onSpeak}
@@ -316,15 +276,13 @@ export const AgentWorkspace = ({
onStopSpeech={onStopSpeech}
isTtsSupported={isTtsSupported}
onRegenerate={onRegenerate}
onEditResubmit={onEditResubmit}
onCycleBranch={onCycleBranch}
onCreateBranch={onCreateBranch}
onReplyPermission={onReplyPermission}
/>
{streamingMessage ? (
<TurnList
messages={[streamingMessage]}
branchGroups={branchGroups}
speakingMessageId={speakingMessageId}
speechState={speechState}
onSpeak={onSpeak}
@@ -333,40 +291,10 @@ export const AgentWorkspace = ({
onStopSpeech={onStopSpeech}
isTtsSupported={isTtsSupported}
onRegenerate={onRegenerate}
onEditResubmit={onEditResubmit}
onCycleBranch={onCycleBranch}
onCreateBranch={onCreateBranch}
onReplyPermission={onReplyPermission}
/>
) : null}
{branchTransition ? (
<AnimatePresence initial={false} mode="wait">
<motion.div
key={`${branchTransition.rootMessageId}:${branchTransition.activeBranchId}:${branchTransition.nonce}`}
initial={{ opacity: 0, y: 8 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -8 }}
transition={{ duration: 0.18, ease: "easeOut" }}
style={{ display: "flex", flexDirection: "column", gap: 16 }}
>
<TurnList
messages={transitionMessages}
branchGroups={branchGroups}
speakingMessageId={speakingMessageId}
speechState={speechState}
onSpeak={onSpeak}
onPauseSpeech={onPauseSpeech}
onResumeSpeech={onResumeSpeech}
onStopSpeech={onStopSpeech}
isTtsSupported={isTtsSupported}
onRegenerate={onRegenerate}
onEditResubmit={onEditResubmit}
onCycleBranch={onCycleBranch}
onReplyPermission={onReplyPermission}
/>
</motion.div>
</AnimatePresence>
) : null}
</Box>
) : null}