From 4781bbc15695cfb94cb48e1d9978365af3f814df Mon Sep 17 00:00:00 2001 From: john Date: Sun, 5 Jul 2026 07:30:37 +0800 Subject: [PATCH] fix: stop chat hang after misrouted themed article requests Route themed page/article prompts to agent orchestration, reject them from portal direct chat, and finalize regular sessions to idle when a direct chat turn already completed. Co-authored-by: Cursor --- chat-intent-router.mjs | 2 ++ chat-intent-router.test.mjs | 13 ++++++++++++ direct-chat-service.mjs | 2 ++ direct-chat-service.test.mjs | 24 ++++++++++++++++++++++ src/hooks/useTKMindChat.ts | 39 +++++++++++++++++++++++++++++++++++- 5 files changed, 79 insertions(+), 1 deletion(-) diff --git a/chat-intent-router.mjs b/chat-intent-router.mjs index f607dd2..3c76d09 100644 --- a/chat-intent-router.mjs +++ b/chat-intent-router.mjs @@ -30,6 +30,8 @@ const OBVIOUS_AGENT_PATTERNS = [ /MindSpace\/[^/\s]+\/public\/[^\s"'<>]+\.html/i, /(?:生成|创建|制作|做|写|设计|发布).{0,24}(?:H5|h5|HTML|html|网页|页面|活动页|宣传页|落地页|分享页)/u, /(?:H5|h5|HTML|html|网页|页面).{0,24}(?:生成|创建|制作|做|写|设计|发布)/u, + /(?:写|做|生成|制作|设计).{0,16}主题(?:页面|文章)/u, + /主题(?:页面|文章).{0,16}(?:写|做|生成|制作|设计)/u, ]; const OBVIOUS_DIRECT_PATTERNS = [ diff --git a/chat-intent-router.test.mjs b/chat-intent-router.test.mjs index b922269..922de1d 100644 --- a/chat-intent-router.test.mjs +++ b/chat-intent-router.test.mjs @@ -36,6 +36,19 @@ test('classifyWithRules routes page generation to agent orchestration', () => { assert.equal(result.suggestedSkill, 'static-page-publish'); }); +test('classifyWithRules routes themed article requests to agent orchestration', () => { + const result = classifyWithRules({ + text: '帮我写个主题文章', + userMessage: { + role: 'user', + content: [{ type: 'text', text: '帮我写个主题文章' }], + metadata: { displayText: '帮我写个主题文章' }, + }, + }); + assert.equal(result.route, CHAT_INTENT_ROUTE.AGENT); + assert.equal(result.suggestedSkill, 'static-page-publish'); +}); + test('classifyWithRules honors forceDeepReasoning bypass', () => { const result = classifyWithRules({ text: '你好', diff --git a/direct-chat-service.mjs b/direct-chat-service.mjs index 0472d7a..e46bcd3 100644 --- a/direct-chat-service.mjs +++ b/direct-chat-service.mjs @@ -20,6 +20,8 @@ const TASK_EXECUTION_REQUIRED_PATTERNS = [ /\b(file|page|html|docx|word|asset)\b.{0,40}\b(write|create|generate|publish|download|export|save|edit)\b/i, /(?:生成|创建|制作|做|写|设计|发布|导出|下载|保存|修改|编辑).{0,24}(?:页面|网页|HTML|html|H5|h5|文件|文档|Word|word|docx|下载页|公开页|分享页|落地页|活动页)/u, /(?:页面|网页|HTML|html|H5|h5|文件|文档|Word|word|docx|下载页|公开页|分享页|落地页|活动页).{0,24}(?:生成|创建|制作|做|写|设计|发布|导出|下载|保存|修改|编辑)/u, + /(?:写|做|生成|制作|设计).{0,16}主题(?:页面|文章)/u, + /主题(?:页面|文章).{0,16}(?:写|做|生成|制作|设计)/u, /MindSpace\/[^/\s]+\/public\/[^\s"'<>]+\.html/i, ]; diff --git a/direct-chat-service.test.mjs b/direct-chat-service.test.mjs index 382afa2..b507519 100644 --- a/direct-chat-service.test.mjs +++ b/direct-chat-service.test.mjs @@ -205,6 +205,30 @@ test('direct chat rejects page generation tasks so the execution backend can han }), false); }); +test('direct chat rejects themed article page tasks without explicit html path', () => { + const service = createDirectChatService({ + enabled: true, + userAuth: {}, + llmProviderService: {}, + sessionSnapshotService: {}, + }); + + assert.equal(service.canHandle({ + toolMode: 'chat', + userMessage: { + role: 'user', + content: [{ type: 'text', text: '帮我写个主题文章' }], + }, + }), false); + assert.equal(service.explainCanHandle({ + toolMode: 'chat', + userMessage: { + role: 'user', + content: [{ type: 'text', text: '帮我写个主题文章' }], + }, + }).reason, 'task_execution_required'); +}); + test('direct chat allows llm-routed replies on regular agent sessions', () => { const service = createDirectChatService({ enabled: true, diff --git a/src/hooks/useTKMindChat.ts b/src/hooks/useTKMindChat.ts index 67e6cc7..680556b 100644 --- a/src/hooks/useTKMindChat.ts +++ b/src/hooks/useTKMindChat.ts @@ -82,6 +82,18 @@ function isDirectChatSessionId(sessionId?: string | null) { return typeof sessionId === 'string' && sessionId.startsWith(DIRECT_CHAT_SESSION_PREFIX); } +function sessionFinishedViaPortalDirectChat(messages: Message[], userMessage: Message) { + const lastAssistant = [...messages].reverse().find((message) => message.role === 'assistant'); + if (lastAssistant?.metadata?.source !== 'portal-direct-chat') return false; + const userText = getDisplayText(userMessage).trim(); + const userId = userMessage.id; + return messages.some( + (message) => + message.role === 'user' && + (message.id === userId || getDisplayText(message).trim() === userText), + ); +} + async function waitForAgentRun(runId: string): Promise { return await waitForAgentRunWithDirectChatPreview(runId, {}); } @@ -1344,8 +1356,33 @@ export function useTKMindChat( activeRequestId.current = null; setChatState('idle'); } else { + let finishedViaPortalDirectChat = false; + try { + const detail = await loadSessionDetail(activeSessionId); + if (submitToken === connectTokenRef.current) { + messagesRef.current = mergeConversationSnapshot( + messagesRef.current, + detail.messages, + ); + messageHistoryLoadedCountRef.current = messagesRef.current.length; + setMessages(messagesRef.current); + setSession(detail.session); + finishedViaPortalDirectChat = sessionFinishedViaPortalDirectChat( + detail.messages, + userMessage, + ); + } + } catch { + // Fall through to agent streaming when the snapshot is not ready yet. + } subscribeToSession(activeSessionId); - setChatState('streaming'); + if (finishedViaPortalDirectChat) { + clearActiveRequestMissingTimer(); + activeRequestId.current = null; + setChatState('idle'); + } else { + setChatState('streaming'); + } } } catch (err) { if (activeSessionId && isAmbiguousReplySubmitError(err)) {