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