diff --git a/frontend-web/src/pages/AgentChatPage.tsx b/frontend-web/src/pages/AgentChatPage.tsx index 19008977..fb2726f2 100644 --- a/frontend-web/src/pages/AgentChatPage.tsx +++ b/frontend-web/src/pages/AgentChatPage.tsx @@ -85,8 +85,11 @@ export default function AgentChatPage({ taskBind && !taskGate && taskContext && taskContext.agentId === agent_id ? (taskContext.openingText?.trim() ?? "") : ""; - // 纯绑定每次进入新建对话都自动发一次开场信息;ref 防止同一新建会话内重复发出。 - const taskAutoSentRef = useRef(false); + // 纯绑定每次进入「新建对话」自动发一次开场信息。用「武装」标志去重,而不是绑定到 threadId—— + // 新建态首帧 useThreadChat 会把 threadId 从 useState 初值换成新 uuid(抖动),若用 threadId 做 + // 去重,会被误判成「又一个新会话」而重复发送(表现为「发了两次、开始回答后又变回一个」)。 + // 「武装」只在会话真正创建完成(isNewThread 落回 false)后才重置,故对 threadId 抖动免疫。 + const autoSentArmedRef = useRef(true); // If ChatPage stashed an opening message for this agent, skip the landing // screen on first render so the user doesn't see a flash of the welcome UI @@ -123,12 +126,16 @@ export default function AgentChatPage({ useEffect(() => { if (isNewThread) { setHasStartedConversation(hasPendingForThisAgent() || !!taskAutoSendText); - // 进入新建对话:允许纯绑定再次自动发出开场信息。 - taskAutoSentRef.current = false; } // eslint-disable-next-line react-hooks/exhaustive-deps }, [isNewThread, threadId, agent_id]); + // 会话真正创建完成(isNewThread 落回 false)后重新「武装」,让下一次「新建对话」能再自动发一次。 + // 不依赖 threadId,故新建态首帧的 threadId 抖动不会误触发重复发送。 + useEffect(() => { + if (!isNewThread) autoSentArmedRef.current = true; + }, [isNewThread]); + const { showNotification } = useNotification(); const [thread, sendMessage, , streamThreadId] = useThreadStream({ threadId: isNewThread ? undefined : threadId, @@ -227,19 +234,20 @@ export default function AgentChatPage({ handleSubmit({ text: pending.text, files: [] }); }, [agent_id, threadId, isNewThread, handleSubmit]); - // 纯绑定任务深链(3qfx/xdfx,无闸门):进入新建对话即自动发出任务开场信息。 - // 每次新建对话都带(与 rwfx 闸门「开始问答」发出等价);ref 防止同一会话内重复发出。 + // 纯绑定任务深链(3qfx/xdfx,无闸门):进入新建对话即自动发出任务开场信息(每个新建会话恰好一次)。 // - // 必须等 `streamThreadId == null`(底层 stream 已切到全新线程)才发:从已有会话点「新建对话」时, - // `isNewThread` 先翻 true,但 useThreadStream 的 onStreamThreadId 要慢一帧才重置为 null。 - // 若在那一帧就 submit,hook 仍绑定上一个会话,submit 的 threadId 选项被忽略 → 开场信息发进 - // 上一个对话(表现为「一直在同一个聊天框里发消息」)。等它归零再发即落到新建线程。 + // 两道守卫缺一不可: + // 1) `streamThreadId == null`:等底层 stream 真正切到全新线程再发。从已有会话点「新建对话」时 + // isNewThread 先翻 true,但 onStreamThreadId 慢一帧才归零;若那一帧就 submit,hook 仍绑定上一个 + // 会话(submit 的 threadId 选项仅在 hook 线程为空时生效)→ 开场信息发进旧对话。 + // 2) `autoSentArmedRef`:本轮新建会话是否还「待发」。只在会话创建完成(isNewThread→false)后重置, + // 不受 threadId 抖动影响,避免同一个新建会话里重复发出。 useEffect(() => { if (!taskAutoSendText) return; if (!isNewThread) return; if (streamThreadId != null) return; - if (taskAutoSentRef.current) return; - taskAutoSentRef.current = true; + if (!autoSentArmedRef.current) return; + autoSentArmedRef.current = false; handleSubmit({ text: taskAutoSendText, files: [] }); }, [taskAutoSendText, isNewThread, streamThreadId, handleSubmit]);