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:
2026-03-09 08:38:02 +08:00
parent b4c293788d
commit 740ef8b526
3 changed files with 50 additions and 35 deletions

View File

@@ -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 };
}