feat(ssa): Complete T-test end-to-end testing with 9 bug fixes - Phase 1 core 85% complete. R service: missing value auto-filter. Backend: error handling, variable matching, dynamic filename. Frontend: module activation, session isolation, error propagation. Full flow verified.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-02-19 20:57:00 +08:00
parent 8137e3cde2
commit 49b5c37cb1
86 changed files with 21207 additions and 252 deletions

View File

@@ -1,34 +1,296 @@
import React from 'react'
import Placeholder from '../../shared/components/Placeholder'
/**
* 智能统计分析模块
* Java团队开发前端仅做导航集成
* SSA 智能统计分析模块主入口
*
* 遵循规范:
* - 使用 AIStreamChat通用 Chat 组件)
* - 使用 apiClient带认证的 axios
*/
import React, { useState, useCallback, useEffect } from 'react';
import {
Card,
Upload,
Input,
Button,
Space,
Typography,
message,
Empty,
} from 'antd';
import {
UploadOutlined,
SendOutlined,
CloudUploadOutlined,
} from '@ant-design/icons';
import type { UploadProps } from 'antd';
import { useSSAStore } from './stores/ssaStore';
import { useAnalysis } from './hooks/useAnalysis';
import {
ModeSwitch,
PlanCard,
ExecutionTrace,
ResultCard,
ExecutionProgress,
SAPDownloadButton,
} from './components';
// 使用通用 Chat 组件(遵循通用能力层清单)
import { AIStreamChat } from '@/shared/components/Chat';
import './styles/ssa.css';
const { Title, Text, Paragraph } = Typography;
const { TextArea } = Input;
const SSAModule: React.FC = () => {
const {
mode,
currentSession,
messages,
currentPlan,
executionResult,
traceSteps,
isLoading,
isExecuting,
setCurrentSession,
reset,
} = useSSAStore();
// 组件挂载时重置 store确保不同用户看到独立的状态
useEffect(() => {
reset();
}, [reset]);
const {
uploadData,
generatePlan,
executePlan,
downloadCode,
isUploading,
uploadProgress,
} = useAnalysis();
const [query, setQuery] = useState('');
const handleUpload: UploadProps['customRequest'] = async (options) => {
const { file, onSuccess, onError } = options;
try {
const result = await uploadData(file as File);
setCurrentSession({
id: result.sessionId,
title: (file as File).name,
mode: mode,
status: 'active',
dataSchema: {
columns: result.schema.columns.map((c) => ({
name: c.name,
type: c.type as 'numeric' | 'categorical' | 'datetime' | 'text',
uniqueValues: c.uniqueValues,
nullCount: c.nullCount,
})),
rowCount: result.schema.rowCount,
preview: [],
},
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
});
message.success('数据上传成功');
onSuccess?.({});
} catch (error) {
message.error('上传失败');
onError?.(error as Error);
}
};
const handleGeneratePlan = async () => {
if (!query.trim()) {
message.warning('请输入分析需求');
return;
}
try {
await generatePlan(query);
setQuery('');
} catch (error) {
message.error('生成计划失败');
}
};
const handleExecute = async () => {
if (!currentPlan) return;
try {
await executePlan(currentPlan.id);
} catch (error: any) {
message.error(error.message || '执行失败');
}
};
const handleDownloadCode = async () => {
try {
const { blob, filename } = await downloadCode();
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
window.URL.revokeObjectURL(url);
message.success('代码下载成功');
} catch (error) {
message.error('下载失败');
}
};
// 咨询模式使用 AIStreamChat无需自定义消息处理
return (
<Placeholder
moduleName="智能统计分析"
message="由Java团队开发中前端集成规划中"
/>
)
}
export default SSAModule
<div className="ssa-page">
<div className="ssa-page-header">
<Title level={3}></Title>
<Paragraph type="secondary">
AI
</Paragraph>
<ModeSwitch disabled={isUploading || isExecuting} />
</div>
<div className="ssa-workspace">
<div className="ssa-main-panel">
{!currentSession ? (
<Card>
<div className="ssa-upload-area">
<Upload.Dragger
name="file"
accept=".csv,.xlsx,.xls"
customRequest={handleUpload}
showUploadList={false}
disabled={isUploading}
>
<p className="ssa-upload-icon">
<CloudUploadOutlined />
</p>
<p className="ant-upload-text"></p>
<p className="ant-upload-hint">
CSVExcel 50MB
</p>
</Upload.Dragger>
</div>
</Card>
) : mode === 'analysis' ? (
<>
<Card style={{ marginBottom: 16 }}>
<div className="ssa-query-input">
<TextArea
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="例如:比较 A 组和 B 组的身高差异是否有统计学意义"
autoSize={{ minRows: 2, maxRows: 4 }}
disabled={isLoading}
/>
<Button
type="primary"
icon={<SendOutlined />}
onClick={handleGeneratePlan}
loading={isLoading}
style={{ marginTop: 12 }}
>
</Button>
</div>
</Card>
{currentPlan && (
<PlanCard
plan={currentPlan}
canExecute={true}
onExecute={handleExecute}
isExecuting={isExecuting}
/>
)}
{isExecuting && traceSteps.length > 0 && (
<>
<ExecutionProgress steps={traceSteps} />
<ExecutionTrace steps={traceSteps} />
</>
)}
{executionResult && (
<ResultCard
result={executionResult}
onDownloadCode={handleDownloadCode}
/>
)}
{!currentPlan && !executionResult && (
<Card>
<Empty description="请输入分析需求AI 将为您生成统计分析计划" />
</Card>
)}
</>
) : (
<Card className="ssa-consult-card">
{/* 使用通用 AIStreamChat 组件(遵循通用能力层清单) */}
<AIStreamChat
apiEndpoint={`/api/v1/ssa/consult/${currentSession?.id}/chat/stream`}
conversationId={currentSession?.id}
agentId="SSA_CONSULT"
enableDeepThinking={true}
welcome={{
title: '统计分析咨询',
description: '请描述您的研究设计和分析需求我将为您生成统计分析计划SAP',
}}
/>
{currentSession && (
<div style={{ marginTop: 16, textAlign: 'right' }}>
<SAPDownloadButton
sessionId={currentSession.id}
disabled={messages.length < 2}
/>
</div>
)}
</Card>
)}
</div>
<div className="ssa-side-panel">
<Card title="数据信息" size="small">
{currentSession?.dataSchema ? (
<>
<Paragraph>
<Text strong>: </Text>
{currentSession.title}
</Paragraph>
<Paragraph>
<Text strong>: </Text>
{currentSession.dataSchema.rowCount}
</Paragraph>
<Paragraph>
<Text strong>: </Text>
{currentSession.dataSchema.columns.length}
</Paragraph>
<div style={{ marginTop: 12 }}>
<Text strong>:</Text>
<ul style={{ paddingLeft: 20, marginTop: 8 }}>
{currentSession.dataSchema.columns.map((col) => (
<li key={col.name}>
<Text code>{col.name}</Text>
<Text type="secondary" style={{ marginLeft: 8 }}>
({col.type})
</Text>
</li>
))}
</ul>
</div>
</>
) : (
<Empty
description="暂无数据"
image={Empty.PRESENTED_IMAGE_SIMPLE}
/>
)}
</Card>
</div>
</div>
</div>
);
};
export default SSAModule;