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:
@@ -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>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user