fix(ssa): Fix 7 integration bugs and refactor frontend unified state management

Bug fixes:
- Fix garbled error messages in chat (TypeWriter rendering issue)
- Fix R engine NA crash in descriptive.R (defensive isTRUE/is.na checks)
- Fix intent misclassification for statistical significance queries
- Fix step 2 results not displayed (accept warning status alongside success)
- Fix incomplete R code download (only step 1 included)
- Fix multi-task state confusion (clicking old card shows new results)
- Add R engine and backend parameter logging for debugging

Refactor - Unified Record Architecture:
- Replace 12 global singleton fields with AnalysisRecord as single source of truth
- Remove isWorkflowMode branching across all components
- One Analysis = One Record = N Steps paradigm
- selectRecord only sets currentRecordId, all rendering derives from currentRecord
- Fix cross-hook-instance issue: executeWorkflow fallback to store currentRecordId

Updated files: ssaStore, useWorkflow, useAnalysis, SSAChatPane, SSAWorkspacePane,
SSACodeModal, WorkflowTimeline, QueryService, WorkflowExecutorService, descriptive.R

Tested: Manual integration test passed - multi-task switching, R code completeness
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-02-21 22:58:59 +08:00
parent 371e1c069c
commit 11676f2840
17 changed files with 1573 additions and 1829 deletions

View File

@@ -24,6 +24,7 @@ import {
} from 'lucide-react';
import { useNavigate } from 'react-router-dom';
import { useSSAStore } from '../stores/ssaStore';
import type { AnalysisRecord } from '../stores/ssaStore';
import { useAnalysis } from '../hooks/useAnalysis';
import { useWorkflow } from '../hooks/useWorkflow';
import type { SSAMessage } from '../types';
@@ -40,16 +41,15 @@ export const SSAChatPane: React.FC = () => {
mountedFile,
setMountedFile,
setCurrentSession,
setActivePane,
setWorkspaceOpen,
currentPlan,
isLoading,
isExecuting,
error,
setError,
addToast,
addMessage,
selectAnalysisRecord,
selectRecord,
analysisHistory,
dataProfile,
dataProfileLoading,
} = useSSAStore();
@@ -78,10 +78,9 @@ export const SSAChatPane: React.FC = () => {
}, []);
useEffect(() => {
// 延迟滚动,确保 DOM 更新完成
const timer = setTimeout(scrollToBottom, 100);
return () => clearTimeout(timer);
}, [messages, currentPlan, scrollToBottom]);
}, [messages, scrollToBottom]);
const handleBack = () => {
navigate(-1);
@@ -154,18 +153,20 @@ export const SSAChatPane: React.FC = () => {
const query = inputValue;
setInputValue('');
// Immediately show user message in chat
addMessage({
id: crypto.randomUUID(),
role: 'user',
content: query,
createdAt: new Date().toISOString(),
});
try {
if (currentSession?.id) {
// Phase Q: 先做意图解析,低置信度时追问
const intentResp = await parseIntent(currentSession.id, query);
if (intentResp.needsClarification && intentResp.clarificationCards?.length > 0) {
addMessage({
id: crypto.randomUUID(),
role: 'user',
content: query,
createdAt: new Date().toISOString(),
});
addMessage({
id: crypto.randomUUID(),
role: 'assistant',
@@ -180,7 +181,7 @@ export const SSAChatPane: React.FC = () => {
return;
}
// 置信度足够 → 直接生成工作流计划
// 置信度足够 → 直接生成工作流计划(不再重复添加用户消息)
await generateWorkflowPlan(currentSession.id, query);
} else {
await generatePlan(query);
@@ -235,15 +236,13 @@ export const SSAChatPane: React.FC = () => {
}
};
// 打开工作区,可选择特定的分析记录
const handleOpenWorkspace = useCallback((recordId?: string) => {
if (recordId) {
selectAnalysisRecord(recordId);
selectRecord(recordId);
} else {
setWorkspaceOpen(true);
setActivePane('sap');
}
}, [selectAnalysisRecord, setWorkspaceOpen, setActivePane]);
}, [selectRecord, setWorkspaceOpen]);
const formatFileSize = (bytes: number) => {
if (bytes < 1024) return `${bytes}B`;
@@ -321,21 +320,31 @@ export const SSAChatPane: React.FC = () => {
msg.content
)}
{/* SAP 卡片 - 只有消息中明确标记为 sap 类型时才显示 */}
{msg.artifactType === 'sap' && msg.recordId && (
<button className="sap-card" onClick={() => handleOpenWorkspace(msg.recordId)}>
<div className="sap-card-left">
<div className="sap-card-icon">
<FileSignature size={16} />
{/* SAP / Result 卡片 - 统一行为selectRecord + 打开工作区 */}
{(msg.artifactType === 'sap' || msg.artifactType === 'result') && msg.recordId && (() => {
const rec = analysisHistory.find((r: AnalysisRecord) => r.id === msg.recordId);
const isCompleted = rec?.status === 'completed';
const isSap = msg.artifactType === 'sap';
return (
<button className="sap-card" onClick={() => handleOpenWorkspace(msg.recordId)}>
<div className="sap-card-left">
<div className="sap-card-icon">
{isSap ? <FileSignature size={16} /> : <BarChart2 size={16} />}
</div>
<div className="sap-card-content">
<div className="sap-card-title">
{isSap ? '查看分析计划' : '查看分析结果'}
{isCompleted && <span className="sap-card-badge"></span>}
</div>
<div className="sap-card-hint">
{rec?.plan?.title || '点击打开工作区查看详情'}
</div>
</div>
</div>
<div className="sap-card-content">
<div className="sap-card-title"> (SAP)</div>
<div className="sap-card-hint"></div>
</div>
</div>
<ArrowRight size={16} className="sap-card-arrow" />
</button>
)}
<ArrowRight size={16} className="sap-card-arrow" />
</button>
);
})()}
</div>
</div>
);