feat(ssa): Complete Phase V-A editable analysis plan variables

Features:
- Add editable variable selection in workflow plan (SingleVarSelect + MultiVarTags)
- Implement 3-layer flexible interception (warning bar + icon + blocking dialog)
- Add tool_param_constraints.json for 12 statistical tools parameter validation
- Add PATCH /workflow/:id/params API with Zod structural validation
- Implement synchronous parameter sync before execution (Promise chaining)
- Fix LLM hallucination by strict system prompt constraints
- Fix DynamicReport object-based rows compatibility (R baseline_table)
- Fix Word export row.map error with same normalization logic
- Restore inferGroupingVar for smart default variable selection
- Add ReactMarkdown rendering in SSAChatPane
- Update SSA module status document to v3.5

Modified files:
- backend: workflow.routes, ChatHandlerService, SystemPromptService, FlowTemplateService
- frontend: WorkflowTimeline, SSAWorkspacePane, DynamicReport, SSAChatPane, ssaStore, ssa.css
- config: tool_param_constraints.json (new)
- docs: SSA status doc, team review reports

Tested: Cohort study end-to-end execution + report export verified
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-02-24 13:08:29 +08:00
parent dc6b292308
commit 85fda830c2
27 changed files with 2732 additions and 154 deletions

View File

@@ -239,7 +239,8 @@ export class ChatHandlerService {
const toolOutputs = [
guardToolOutput,
planSummary,
'[系统提示] 你刚刚为用户制定了上述分析方案。请用自然语言向用户解释这个方案:包括为什么选这些方法、分析步骤的逻辑。不要重复列步骤编号和工具代码,要用用户能理解的语言说明。最后提示用户确认方案后即可执行。',
'[系统指令] 你刚刚为用户制定了上述分析方案。请用自然语言向用户解释这个方案:包括为什么选这些方法、分析步骤的逻辑。不要重复列步骤编号和工具代码,要用用户能理解的语言说明。最后提示用户确认方案后即可执行。',
'【禁止事项】不要预测、模拟或编造任何分析结果、数值或表格。方案只是计划R 引擎尚未执行,你不知道结果是什么。',
].filter(Boolean).join('\n\n');
const messages = await conversationService.buildContext(
@@ -397,7 +398,9 @@ export class ChatHandlerService {
writer: StreamWriter,
placeholderMessageId: string,
): Promise<HandleResult> {
// 清除 pending 状态
// 先读取 pending 元数据(含 workflowId再清除
const pending = await askUserService.getPending(sessionId);
const pendingMeta = pending?.metadata || {};
await askUserService.clearPending(sessionId);
if (response.action === 'skip') {
@@ -423,15 +426,21 @@ export class ChatHandlerService {
const selectedValue = response.selectedValues?.[0];
if (selectedValue === 'confirm_plan') {
// Phase IV: 确认分析方案 → 前端将触发 executeWorkflow
const workflowId = response.metadata?.workflowId || '';
// Phase IV: 确认分析方案 → 前端打开工作区,用户手动点击执行
const workflowId = pendingMeta.workflowId || response.metadata?.workflowId || '';
const messages = await conversationService.buildContext(
sessionId, conversationId, 'analyze',
`[系统提示] 用户已确认分析方案workflow: ${workflowId})。请简要确认:"好的,方案已确认,正在准备执行分析..."。`,
[
`[系统指令——严格遵守] 用户已确认分析方案workflow: ${workflowId})。`,
'你只需回复一句简短的确认消息,例如:"好的,方案已确认。请在右侧工作区点击「开始执行分析」启动 R 引擎。"',
'【铁律】禁止在此回复中生成任何分析结果、表格、P值、统计量、数值。',
'你不是计算引擎,所有数值结果将由 R 统计引擎独立计算后返回。',
'你的回复不得超过 2 句话。',
].join('\n'),
);
const result = await conversationService.streamToSSE(messages, writer, {
temperature: 0.3, maxTokens: 300,
temperature: 0.1, maxTokens: 150,
});
await conversationService.finalizeAssistantMessage(
@@ -451,12 +460,12 @@ export class ChatHandlerService {
// Phase III: 确认使用推荐方法 → 提示可以开始分析
const messages = await conversationService.buildContext(
sessionId, conversationId, 'analyze',
'[系统提示] 用户已确认使用推荐的统计方法。请简要确认方案,告知用户可以在对话中说"开始分析"或在右侧面板触发执行。',
'[系统指令] 用户已确认使用推荐的统计方法。请简要确认方案,告知用户可以在对话中说"开始分析"或在右侧面板触发执行。禁止生成任何数值或假设的分析结果。',
);
const result = await conversationService.streamToSSE(messages, writer, {
temperature: 0.5,
maxTokens: 800,
temperature: 0.3,
maxTokens: 500,
});
await conversationService.finalizeAssistantMessage(