SSA Agent channel improvements (12 code files, +931/-203 lines): - Solution B: left/right separation of concerns (gaze guiding + state mutex + time-travel) - JWT token refresh mechanism (ensureFreshToken) to fix HTTP 401 during pipeline - Code truncation fix: LLM maxTokens 4000->8000 + CSS max-height 60vh - Retry streaming code generation with generateCodeStream() - R Docker structured errors: 20+ pattern matching + format_agent_error + line extraction - Prompt iron rules: strict output format in CoderAgent System Prompt - parseCode robustness: XML/Markdown/inference 3-tier matching + length validation - consoleOutput type defense: handle both array and scalar from R Docker unboxedJSON - Agent progress bar sync: derive phase from agentExecution.status - Export report / view code buttons restored for Agent mode - ExecutingProgress component: real-time timer + dynamic tips + step pulse animation Architecture design (3 review reports): - Plan-and-Execute step-by-step execution architecture approved - Code accumulation strategy (R Docker stays stateless) - 5 engineering guardrails: XML tags, AST pre-check, defensive prompts, high-fidelity schema, error classification circuit breaker Docs: update SSA module status v4.1, system status v6.7, deployment changelist Made-with: Cursor
137 lines
4.4 KiB
TypeScript
137 lines
4.4 KiB
TypeScript
/**
|
|
* 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';
|
|
|
|
export const SSACodeModal: React.FC = () => {
|
|
const {
|
|
codeModalVisible,
|
|
setCodeModalVisible,
|
|
addToast,
|
|
currentRecordId,
|
|
analysisHistory,
|
|
executionMode,
|
|
agentExecution,
|
|
} = useSSAStore();
|
|
|
|
const [code, setCode] = useState<string>('');
|
|
const [isLoading, setIsLoading] = useState(false);
|
|
|
|
const record = currentRecordId
|
|
? analysisHistory.find((r) => r.id === currentRecordId) ?? null
|
|
: null;
|
|
|
|
useEffect(() => {
|
|
if (!codeModalVisible) return;
|
|
setIsLoading(true);
|
|
try {
|
|
if (executionMode === 'agent' && agentExecution?.generatedCode) {
|
|
const header = `# ========================================\n# Agent 生成的 R 代码\n# 分析任务: ${agentExecution.query || '统计分析'}\n# ========================================\n`;
|
|
setCode(header + agentExecution.generatedCode);
|
|
} else {
|
|
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 || '# 该步骤暂无可用代码');
|
|
})
|
|
.join('\n\n');
|
|
setCode(allCode);
|
|
} else {
|
|
setCode('# 暂无可用代码\n# 请先执行分析');
|
|
}
|
|
}
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
}, [codeModalVisible, record, executionMode, agentExecution]);
|
|
|
|
if (!codeModalVisible) return null;
|
|
|
|
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 rawTitle = executionMode === 'agent'
|
|
? (agentExecution?.query || 'agent_analysis')
|
|
: (record?.plan?.title || 'analysis');
|
|
const title = rawTitle
|
|
.replace(/[^a-zA-Z0-9\u4e00-\u9fa5_-]/g, '_')
|
|
.slice(0, 30);
|
|
return `SSA_${title}_${ts}.R`;
|
|
};
|
|
|
|
const handleDownload = () => {
|
|
try {
|
|
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');
|
|
}
|
|
};
|
|
|
|
const handleCopy = () => {
|
|
navigator.clipboard.writeText(code);
|
|
addToast('代码已复制', 'success');
|
|
};
|
|
|
|
return (
|
|
<div className="code-modal-overlay" onClick={handleClose}>
|
|
<div className="code-modal pop-in" onClick={(e) => e.stopPropagation()}>
|
|
<header className="code-modal-header">
|
|
<h3 className="code-modal-title">
|
|
<span className="r-icon">R</span>
|
|
R 源代码交付
|
|
</h3>
|
|
<button className="code-modal-close" onClick={handleClose}>
|
|
<X size={16} />
|
|
</button>
|
|
</header>
|
|
|
|
<div className="code-modal-body">
|
|
{isLoading ? (
|
|
<div className="code-loading">
|
|
<Loader2 size={24} className="spin" />
|
|
<span>加载代码中...</span>
|
|
</div>
|
|
) : (
|
|
<pre className="code-block">
|
|
<code>{code}</code>
|
|
</pre>
|
|
)}
|
|
</div>
|
|
|
|
<footer className="code-modal-footer">
|
|
<button className="copy-btn" onClick={handleCopy} disabled={isLoading}>
|
|
复制代码
|
|
</button>
|
|
<button className="download-btn" onClick={handleDownload} disabled={isLoading}>
|
|
<Download size={14} />
|
|
下载 .R 文件
|
|
</button>
|
|
</footer>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default SSACodeModal;
|