From 382b5afdb98b2328c79e37d0c9f144cef72d9776 Mon Sep 17 00:00:00 2001 From: ssdfasd <2156608475@qq.com> Date: Sat, 14 Mar 2026 19:40:55 +0800 Subject: [PATCH] Refactor loop detection logic in ChatInput - Use message finish state instead of session status for loop detection - Add lastProcessedMessageId to prevent duplicate loop triggers - Add XCOpenCodeWeb.exe to .gitignore Note: Single-file exe now builds to ~150MB (down from ~300MB with Electron) --- .gitignore | 1 + ui/src/components/chat/ChatInput.tsx | 62 ++++++++++++++++------------ 2 files changed, 36 insertions(+), 27 deletions(-) diff --git a/.gitignore b/.gitignore index acbcf0f..cf16a04 100644 --- a/.gitignore +++ b/.gitignore @@ -54,3 +54,4 @@ packages/intellij/ # OS Thumbs.db +web/XCOpenCodeWeb.exe diff --git a/ui/src/components/chat/ChatInput.tsx b/ui/src/components/chat/ChatInput.tsx index 31ea32c..240f7b7 100644 --- a/ui/src/components/chat/ChatInput.tsx +++ b/ui/src/components/chat/ChatInput.tsx @@ -167,11 +167,9 @@ export const ChatInput: React.FC = ({ onOpenSettings, scrollToBo current: number; total: number; prompt: string; + lastProcessedMessageId: string | null; } | null>(null); - // Get session status from store for loop detection - const sessionStatus = useSessionStore((state) => state.sessionStatus); - const isDesktopExpanded = isExpandedInput && !isMobile; const sendableAttachedFiles = React.useMemo( @@ -856,6 +854,7 @@ export const ChatInput: React.FC = ({ onOpenSettings, scrollToBo current: 1, total: loopNum, prompt: loopArgs, + lastProcessedMessageId: null, }); toast.info(`Loop started: ${loopNum} iterations`); // Clear input @@ -952,37 +951,46 @@ export const ChatInput: React.FC = ({ onOpenSettings, scrollToBo // Update ref with latest handleSubmit on every render handleSubmitRef.current = handleSubmit; - // Handle loop: detect when AI finishes and send next iteration + // Handle loop: detect when AI finishes (finish === 'stop') and send next iteration React.useEffect(() => { if (!loopState?.active || !currentSessionId) { return; } - const status = sessionStatus?.get(currentSessionId); - if (status?.type === 'idle') { - // AI finished, check if we should continue looping - if (loopState.current <= loopState.total) { - if (loopState.current < loopState.total) { - const next = loopState.current + 1; - setLoopState({ ...loopState, current: next }); - useSessionStore.getState().sendMessage( - loopState.prompt, - currentProviderId, - currentModelId, - currentAgentName, - [], - undefined, - undefined, - currentVariant, - inputMode - ); - } else { - // Loop complete - all iterations sent - setLoopState(null); - } + const messages = useMessageStore.getState().messages.get(currentSessionId); + if (!messages) return; + + const assistantMessages = messages.filter((m) => m.info.role === 'assistant'); + const lastAssistant = assistantMessages[assistantMessages.length - 1]; + if (!lastAssistant) return; + + const messageId = lastAssistant.info.id; + const finish = lastAssistant.info.finish; + + if (finish === 'stop' && messageId !== loopState.lastProcessedMessageId) { + if (loopState.current < loopState.total) { + const next = loopState.current + 1; + setLoopState({ + ...loopState, + current: next, + lastProcessedMessageId: messageId, + }); + useSessionStore.getState().sendMessage( + loopState.prompt, + currentProviderId, + currentModelId, + currentAgentName, + [], + undefined, + undefined, + currentVariant, + inputMode + ); + } else { + setLoopState(null); } } - }, [loopState, sessionStatus, currentSessionId, currentProviderId, currentModelId, currentAgentName, currentVariant, inputMode]); + }, [loopState, sessionMessages, currentSessionId, currentProviderId, currentModelId, currentAgentName, currentVariant, inputMode]); // Primary action for send button - respects queue mode setting const handlePrimaryAction = React.useCallback(() => {