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

@@ -35,6 +35,8 @@ import { useWorkflow } from '../hooks/useWorkflow';
import { useSSAChat } from '../hooks/useSSAChat';
import type { ChatMessage, ChatIntentType } from '../hooks/useSSAChat';
import type { SSAMessage } from '../types';
import ReactMarkdown from 'react-markdown';
import remarkGfm from 'remark-gfm';
import { TypeWriter } from './TypeWriter';
import { DataProfileCard } from './DataProfileCard';
import { ClarificationCard } from './ClarificationCard';
@@ -65,13 +67,12 @@ export const SSAChatPane: React.FC = () => {
} = useSSAStore();
const { uploadData, generatePlan, isUploading, uploadProgress } = useAnalysis();
const { generateDataProfile, handleClarify, executeWorkflow, isProfileLoading, isPlanLoading } = useWorkflow();
const { generateDataProfile, handleClarify, isProfileLoading, isPlanLoading } = useWorkflow();
const {
chatMessages,
isGenerating,
currentIntent,
pendingQuestion,
pendingPlanConfirm,
sendChatMessage,
respondToQuestion,
skipQuestion,
@@ -91,15 +92,6 @@ export const SSAChatPane: React.FC = () => {
const chatEndRef = useRef<HTMLDivElement>(null);
const messagesContainerRef = useRef<HTMLDivElement>(null);
// Phase IV: plan_confirmed → 自动触发 executeWorkflow
useEffect(() => {
if (pendingPlanConfirm?.workflowId && currentSession?.id) {
executeWorkflow(currentSession.id, pendingPlanConfirm.workflowId).catch((err: any) => {
addToast(err?.message || '执行失败', 'error');
});
}
}, [pendingPlanConfirm, currentSession?.id, executeWorkflow, addToast]);
// Phase II: session 切换时加载对话历史
useEffect(() => {
if (currentSession?.id) {
@@ -417,7 +409,11 @@ export const SSAChatPane: React.FC = () => {
<span>{msg.content}</span>
</div>
) : (
<div className="chat-msg-content">{msg.content}</div>
<div className="chat-msg-content">
<ReactMarkdown remarkPlugins={[remarkGfm]}>
{msg.content}
</ReactMarkdown>
</div>
)}
</div>
</div>