feat(ssa): Complete V11 UI development and frontend-backend integration - Pixel-perfect V11 UI, multi-task support, Word export, input overlay fix, code cleanup. MVP Phase 1 core 95% complete.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,296 +1,25 @@
|
||||
/**
|
||||
* SSA 智能统计分析模块主入口
|
||||
*
|
||||
* 遵循规范:
|
||||
* - 使用 AIStreamChat(通用 Chat 组件)
|
||||
* - 使用 apiClient(带认证的 axios)
|
||||
* V11 版本 - 100% 还原原型图设计
|
||||
* - 左侧抽屉栏(可展开/收起)
|
||||
* - 中间对话区(居中布局 max-w-3xl)
|
||||
* - 右侧工作区(动态 60% 宽度)
|
||||
*/
|
||||
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 React, { useEffect } from 'react';
|
||||
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;
|
||||
import SSAWorkspace from './SSAWorkspace';
|
||||
import './styles/ssa-workspace.css';
|
||||
|
||||
const SSAModule: React.FC = () => {
|
||||
const {
|
||||
mode,
|
||||
currentSession,
|
||||
messages,
|
||||
currentPlan,
|
||||
executionResult,
|
||||
traceSteps,
|
||||
isLoading,
|
||||
isExecuting,
|
||||
setCurrentSession,
|
||||
reset,
|
||||
} = useSSAStore();
|
||||
const { 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 (
|
||||
<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">
|
||||
支持 CSV、Excel 格式,单文件最大 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>
|
||||
);
|
||||
return <SSAWorkspace />;
|
||||
};
|
||||
|
||||
export default SSAModule;
|
||||
|
||||
Reference in New Issue
Block a user