From 87110ffdff66ec1bfc0e1ad74d2612d72450a1ee Mon Sep 17 00:00:00 2001 From: iven Date: Thu, 23 Apr 2026 17:57:17 +0800 Subject: [PATCH] =?UTF-8?q?feat(suggest):=20=E6=94=B9=E9=80=A0=20createCom?= =?UTF-8?q?pleteHandler=20=E5=B9=B6=E8=A1=8C=E5=8C=96=20+=20generateLLMSug?= =?UTF-8?q?gestions=20=E5=A2=9E=E5=BC=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - createCompleteHandler: 记忆提取+上下文拉取 Promise.all 并行 - generateLLMSuggestions: 新增 SuggestionContext 参数,构建增强 user message - llmSuggestViaSaaS: 删除 2s 人为延迟(并行化后不再需要) - 变量重命名 context→conversationContext 避免与 SuggestionContext 冲突 --- desktop/src/store/chat/streamStore.ts | 83 +++++++++++++++++---------- 1 file changed, 53 insertions(+), 30 deletions(-) diff --git a/desktop/src/store/chat/streamStore.ts b/desktop/src/store/chat/streamStore.ts index 725e4ac..d70751c 100644 --- a/desktop/src/store/chat/streamStore.ts +++ b/desktop/src/store/chat/streamStore.ts @@ -36,6 +36,7 @@ import { useMessageStore } from './messageStore'; import { useArtifactStore } from './artifactStore'; import { llmSuggest } from '../../lib/llm-service'; import { detectNameSuggestion, detectAgentNameSuggestion } from '../../lib/cold-start-mapper'; +import { fetchSuggestionContext, type SuggestionContext } from '../../lib/suggestion-context'; const log = createLogger('StreamStore'); @@ -399,35 +400,43 @@ function createCompleteHandler( } } - // Async memory extraction (independent — failures don't block name detection) + // Parallel: memory extraction + intelligence context fetch const filtered = msgs .filter(m => m.role === 'user' || m.role === 'assistant') .map(m => ({ role: m.role, content: m.content })); const convId = useConversationStore.getState().currentConversationId; - getMemoryExtractor().extractFromConversation(filtered, agentId, convId ?? undefined) - .catch(err => log.warn('Memory extraction failed:', err)); + const lastUserContent = typeof lastContent === 'string' ? lastContent : ''; - intelligenceClient.reflection.recordConversation().catch(err => { - log.warn('Recording conversation failed:', err); - }); - intelligenceClient.reflection.shouldReflect().then(shouldReflect => { - if (shouldReflect) { - intelligenceClient.reflection.reflect(agentId, []).catch(err => { - log.warn('Reflection failed:', err); - }); - } - }); + const suggestionContextPromise = fetchSuggestionContext(agentId, lastUserContent); - // Follow-up suggestions (async LLM call with keyword fallback) - const latestMsgs = chat.getMessages() || []; - const conversationMessages = latestMsgs - .filter(m => m.role === 'user' || m.role === 'assistant') - .filter(m => !m.streaming) - .map(m => ({ role: m.role, content: m.content })); + // Fire-and-forget background tasks + Promise.all([ + getMemoryExtractor().extractFromConversation(filtered, agentId, convId ?? undefined) + .catch(err => log.warn('Memory extraction failed:', err)), + intelligenceClient.reflection.recordConversation() + .catch(err => log.warn('Recording conversation failed:', err)), + suggestionContextPromise, + ]).then(([, , context]) => { + // Conditional reflection (after context is ready) + intelligenceClient.reflection.shouldReflect().then(shouldReflect => { + if (shouldReflect) { + intelligenceClient.reflection.reflect(agentId, []).catch(err => { + log.warn('Reflection failed:', err); + }); + } + }); - generateLLMSuggestions(conversationMessages, set).catch(err => { - log.warn('Suggestion generation error:', err); - set({ suggestionsLoading: false }); + // Follow-up suggestions with enriched context + const latestMsgs = chat.getMessages() || []; + const conversationMessages = latestMsgs + .filter(m => m.role === 'user' || m.role === 'assistant') + .filter(m => !m.streaming) + .map(m => ({ role: m.role, content: m.content })); + + generateLLMSuggestions(conversationMessages, set, context).catch(err => { + log.warn('Suggestion generation error:', err); + set({ suggestionsLoading: false }); + }); }); }; } @@ -559,15 +568,32 @@ function parseSuggestionResponse(raw: string): string[] { async function generateLLMSuggestions( messages: Array<{ role: string; content: string }>, set: (partial: Partial) => void, + context?: SuggestionContext, ): Promise { set({ suggestionsLoading: true }); try { const recentMessages = messages.slice(-6); - const context = recentMessages + const conversationContext = recentMessages .map(m => `${m.role === 'user' ? '用户' : '助手'}: ${m.content}`) .join('\n\n'); + // Build dynamic user message with intelligence context + const ctx = context ?? { userProfile: '', painPoints: '', experiences: '', skillMatch: '' }; + const hasContext = ctx.userProfile || ctx.painPoints || ctx.experiences || ctx.skillMatch; + let userMessage: string; + if (hasContext) { + const sections: string[] = ['以下是用户的背景信息,请在生成建议时参考:\n']; + if (ctx.userProfile) sections.push(`## 用户画像\n${ctx.userProfile}`); + if (ctx.painPoints) sections.push(`## 活跃痛点\n${ctx.painPoints}`); + if (ctx.experiences) sections.push(`## 相关经验\n${ctx.experiences}`); + if (ctx.skillMatch) sections.push(`## 可用技能\n${ctx.skillMatch}`); + sections.push(`\n最近对话:\n${conversationContext}`); + userMessage = sections.join('\n\n'); + } else { + userMessage = `以下是对话中最近的消息:\n\n${conversationContext}\n\n请生成 3 个后续问题。`; + } + const connectionMode = typeof localStorage !== 'undefined' ? localStorage.getItem('zclaw-connection-mode') : null; @@ -575,9 +601,9 @@ async function generateLLMSuggestions( let raw: string; if (connectionMode === 'saas') { - raw = await llmSuggestViaSaaS(context); + raw = await llmSuggestViaSaaS(userMessage); } else { - raw = await llmSuggest(context); + raw = await llmSuggest(userMessage); } const suggestions = parseSuggestionResponse(raw); @@ -601,7 +627,7 @@ async function generateLLMSuggestions( * with non-streaming requests. Collects the full response from SSE deltas, * then parses the suggestion JSON from the accumulated text. */ -async function llmSuggestViaSaaS(context: string): Promise { +async function llmSuggestViaSaaS(userMessage: string): Promise { const { saasClient } = await import('../../lib/saas-client'); const { useConversationStore } = await import('./conversationStore'); const { useSaaSStore } = await import('../saasStore'); @@ -611,9 +637,6 @@ async function llmSuggestViaSaaS(context: string): Promise { const model = currentModel || (availableModels.length > 0 ? availableModels[0]?.id : undefined); if (!model) throw new Error('No model available for suggestions'); - // Delay to avoid concurrent relay requests with memory extraction - await new Promise(r => setTimeout(r, 2000)); - const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), 60000); @@ -623,7 +646,7 @@ async function llmSuggestViaSaaS(context: string): Promise { model, messages: [ { role: 'system', content: LLM_PROMPTS_SYSTEM }, - { role: 'user', content: `以下是对话中最近的消息:\n\n${context}\n\n请生成 3 个后续问题。` }, + { role: 'user', content: userMessage }, ], max_tokens: 500, temperature: 0.7,