fix(ssa): Fix Agent mode chat leaking plan/result details + navigation reset
Backend (ChatHandlerService): - Replace LLM-generated hints with fixed-text hints for plan/code/result steps to prevent SystemPrompt intent instruction conflict causing verbose chat output - Add sendFixedHint() helper for deterministic SSE text delivery (no LLM call) - Cancel old plan_pending/code_pending executions when regenerating plan - Eliminates 3 unnecessary LLM calls per analysis cycle (faster response) Frontend (SSAModule): - Use location.key as useEffect dependency to ensure store reset on every navigation (fixes stale session when re-entering from other modules) - TopNavigation uses replace:true for active module re-click to avoid browser history clutter Tested: Agent mode plan/code/result hints now show brief fixed text in chat, detailed content exclusively in right workspace panel Made-with: Cursor
This commit is contained in:
@@ -478,6 +478,31 @@ export class ChatHandlerService {
|
||||
return 'other';
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送固定文本引导语(不经过 LLM,避免指令冲突导致 LLM 输出冗长内容)
|
||||
*/
|
||||
private async sendFixedHint(
|
||||
writer: StreamWriter,
|
||||
placeholderMessageId: string,
|
||||
text: string,
|
||||
): Promise<void> {
|
||||
const sseData = JSON.stringify({
|
||||
id: `chatcmpl-ssa-${Date.now()}`,
|
||||
object: 'chat.completion.chunk',
|
||||
choices: [{ delta: { content: text }, finish_reason: null }],
|
||||
});
|
||||
writer.write(`data: ${sseData}\n\n`);
|
||||
|
||||
const doneData = JSON.stringify({
|
||||
id: `chatcmpl-ssa-${Date.now()}`,
|
||||
object: 'chat.completion.chunk',
|
||||
choices: [{ delta: {}, finish_reason: 'stop' }],
|
||||
});
|
||||
writer.write(`data: ${doneData}\n\n`);
|
||||
|
||||
await conversationService.finalizeAssistantMessage(placeholderMessageId, text);
|
||||
}
|
||||
|
||||
// ── Agent Step 1: 生成分析计划 → 等用户确认 ──
|
||||
|
||||
private async agentGeneratePlan(
|
||||
@@ -491,6 +516,12 @@ export class ChatHandlerService {
|
||||
writer.write(`data: ${JSON.stringify({ type, ...data })}\n\n`);
|
||||
};
|
||||
|
||||
// 取消该 session 下所有未完成的 pending 执行(防止旧计划干扰)
|
||||
await (prisma as any).ssaAgentExecution.updateMany({
|
||||
where: { sessionId, status: { in: ['plan_pending', 'code_pending'] } },
|
||||
data: { status: 'error', errorMessage: '用户发起了新的分析请求' },
|
||||
});
|
||||
|
||||
const execution = await (prisma as any).ssaAgentExecution.create({
|
||||
data: { sessionId, query: userContent, status: 'planning' },
|
||||
});
|
||||
@@ -516,17 +547,9 @@ export class ChatHandlerService {
|
||||
planText: plan.rawText,
|
||||
});
|
||||
|
||||
// 简短视线牵引语(方案 B:左侧只输出简短提示,详细内容在右侧工作区)
|
||||
const hint = [
|
||||
`[系统指令——严格遵守] 分析计划已生成(${plan.steps.length} 步),标题「${plan.title}」。`,
|
||||
'你只需回复 1-2 句话,引导用户去右侧工作区查看。',
|
||||
'示例回复:「我已为您拟定了详细的分析计划,请在右侧工作区核对并点击确认。」',
|
||||
'【铁律】禁止在对话中重复计划内容、禁止编造任何数值。回复不超过 2 句话。',
|
||||
].join('\n');
|
||||
|
||||
const msgs = await conversationService.buildContext(sessionId, conversationId, 'analyze', hint);
|
||||
const sr = await conversationService.streamToSSE(msgs, writer, { temperature: 0.3, maxTokens: 150 });
|
||||
await conversationService.finalizeAssistantMessage(placeholderMessageId, sr.content, sr.thinking, sr.tokens);
|
||||
// 固定文本引导语(不经过 LLM,彻底避免 SystemPrompt 的 intent 指令冲突)
|
||||
const hintText = `我已为您拟定了分析计划(${plan.steps.length} 步),👉 请在右侧工作区核对并点击确认。`;
|
||||
await this.sendFixedHint(writer, placeholderMessageId, hintText);
|
||||
|
||||
return { messageId: placeholderMessageId, intent: 'analyze', success: true };
|
||||
}
|
||||
@@ -571,17 +594,9 @@ export class ChatHandlerService {
|
||||
explanation: generated.explanation,
|
||||
});
|
||||
|
||||
// 简短视线牵引语
|
||||
const hint = [
|
||||
`[系统指令——严格遵守] R 代码已生成(${generated.code.split('\n').length} 行),使用 ${generated.requiredPackages.join(', ') || '基础包'}。`,
|
||||
'你只需回复 1-2 句话,引导用户去右侧工作区核对代码并点击执行。',
|
||||
'示例回复:「R 代码已生成,请在右侧工作区核对代码并点击 "确认并执行"。」',
|
||||
'【铁律】禁止在对话中贴代码、禁止编造结果。回复不超过 2 句话。',
|
||||
].join('\n');
|
||||
|
||||
const msgs = await conversationService.buildContext(sessionId, conversationId, 'analyze', hint);
|
||||
const sr = await conversationService.streamToSSE(msgs, writer, { temperature: 0.3, maxTokens: 150 });
|
||||
await conversationService.finalizeAssistantMessage(placeholderMessageId, sr.content, sr.thinking, sr.tokens);
|
||||
// 固定文本引导语
|
||||
const hintText = `R 代码已生成(${generated.code.split('\n').length} 行),👉 请在右侧工作区核对代码并点击「确认并执行」。`;
|
||||
await this.sendFixedHint(writer, placeholderMessageId, hintText);
|
||||
|
||||
return { messageId: placeholderMessageId, intent: 'analyze', success: true };
|
||||
}
|
||||
@@ -639,16 +654,11 @@ export class ChatHandlerService {
|
||||
durationMs,
|
||||
});
|
||||
|
||||
// 流式总结结果
|
||||
const hint = [
|
||||
`[系统指令] R 代码执行完成(${durationMs}ms),生成 ${(execResult.reportBlocks || []).length} 个报告模块。`,
|
||||
'请简要解释结果的统计学意义。',
|
||||
'【禁止】不要编造数值(工作区已展示完整结果)。',
|
||||
].join('\n');
|
||||
|
||||
const msgs = await conversationService.buildContext(sessionId, conversationId, 'analyze', hint);
|
||||
const sr = await conversationService.streamToSSE(msgs, writer, { temperature: 0.5, maxTokens: 800 });
|
||||
await conversationService.finalizeAssistantMessage(placeholderMessageId, sr.content, sr.thinking, sr.tokens);
|
||||
// 固定文本引导语(结果解读应在右侧工作区,不在对话区)
|
||||
const blockCount = (execResult.reportBlocks || []).length;
|
||||
const seconds = (durationMs / 1000).toFixed(1);
|
||||
const hintText = `✅ 分析完成(${seconds}s),共生成 ${blockCount} 个结果模块。👉 请在右侧工作区查看完整结果和图表。`;
|
||||
await this.sendFixedHint(writer, placeholderMessageId, hintText);
|
||||
|
||||
return { messageId: placeholderMessageId, intent: 'analyze', success: true };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user