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

@@ -1,85 +1,79 @@
/**
* SSACodeModal - V11 R代码模态框
*
* 100% 还原 V11 原型图
* 调用后端 API 获取真实执行代码
* SSACodeModal - R 代码模态框 (Unified Record Architecture)
*
* 从 currentRecord.steps 聚合所有步骤的可复现代码。
*/
import React, { useEffect, useState } from 'react';
import { X, Download, Loader2 } from 'lucide-react';
import { useSSAStore } from '../stores/ssaStore';
import { useAnalysis } from '../hooks/useAnalysis';
export const SSACodeModal: React.FC = () => {
const { codeModalVisible, setCodeModalVisible, executionResult, addToast, isWorkflowMode, workflowSteps } = useSSAStore();
const { downloadCode } = useAnalysis();
const {
codeModalVisible,
setCodeModalVisible,
addToast,
currentRecordId,
analysisHistory,
} = useSSAStore();
const [code, setCode] = useState<string>('');
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
if (codeModalVisible) {
loadCode();
}
}, [codeModalVisible]);
const record = currentRecordId
? analysisHistory.find((r) => r.id === currentRecordId) ?? null
: null;
const loadCode = async () => {
useEffect(() => {
if (!codeModalVisible) return;
setIsLoading(true);
try {
if (isWorkflowMode && workflowSteps.length > 0) {
const allCode = workflowSteps
.filter(s => s.status === 'success' && s.result)
.map(s => {
const steps = record?.steps ?? [];
const successSteps = steps.filter(
(s) => (s.status === 'success' || s.status === 'warning') && s.result
);
if (successSteps.length > 0) {
const allCode = successSteps
.map((s) => {
const stepCode = (s.result as any)?.reproducible_code;
const header = `# ========================================\n# 步骤 ${s.step_number}: ${s.tool_name}\n# ========================================\n`;
return header + (stepCode || `# 该步骤暂无可用代码`);
return header + (stepCode || '# 该步骤暂无可用代码');
})
.join('\n\n');
setCode(allCode || '# 暂无可用代码\n# 请先执行分析');
} else {
const result = await downloadCode();
const text = await result.blob.text();
setCode(text);
}
} catch (error) {
if (executionResult?.reproducibleCode) {
setCode(executionResult.reproducibleCode);
setCode(allCode);
} else {
setCode('# 暂无可用代码\n# 请先执行分析');
}
} finally {
setIsLoading(false);
}
};
}, [codeModalVisible, record]);
if (!codeModalVisible) return null;
const handleClose = () => {
setCodeModalVisible(false);
const handleClose = () => setCodeModalVisible(false);
const generateFilename = () => {
const now = new Date();
const pad = (n: number) => n.toString().padStart(2, '0');
const ts = `${now.getFullYear()}${pad(now.getMonth() + 1)}${pad(now.getDate())}_${pad(now.getHours())}${pad(now.getMinutes())}${pad(now.getSeconds())}`;
const title = (record?.plan?.title || 'analysis')
.replace(/[^a-zA-Z0-9\u4e00-\u9fa5_-]/g, '_')
.slice(0, 30);
return `SSA_${title}_${ts}.R`;
};
const handleDownload = async () => {
const handleDownload = () => {
try {
if (isWorkflowMode && code) {
const blob = new Blob([code], { type: 'text/plain' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'workflow_analysis.R';
a.click();
URL.revokeObjectURL(url);
addToast('R 脚本已下载', 'success');
handleClose();
} else {
const result = await downloadCode();
const url = URL.createObjectURL(result.blob);
const a = document.createElement('a');
a.href = url;
a.download = result.filename;
a.click();
URL.revokeObjectURL(url);
addToast('R 脚本已下载', 'success');
handleClose();
}
} catch (error) {
const blob = new Blob([code], { type: 'text/plain' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = generateFilename();
a.click();
URL.revokeObjectURL(url);
addToast('R 脚本已下载', 'success');
handleClose();
} catch {
addToast('下载失败', 'error');
}
};
@@ -101,7 +95,7 @@ export const SSACodeModal: React.FC = () => {
<X size={16} />
</button>
</header>
<div className="code-modal-body">
{isLoading ? (
<div className="code-loading">