diff --git a/backend/app/api/__pycache__/__init__.cpython-312.pyc b/backend/app/api/__pycache__/__init__.cpython-312.pyc
deleted file mode 100644
index 1a3fce8..0000000
Binary files a/backend/app/api/__pycache__/__init__.cpython-312.pyc and /dev/null differ
diff --git a/backend/app/api/endpoints/__pycache__/ai_analyze.cpython-312.pyc b/backend/app/api/endpoints/__pycache__/ai_analyze.cpython-312.pyc
deleted file mode 100644
index 7ee04aa..0000000
Binary files a/backend/app/api/endpoints/__pycache__/ai_analyze.cpython-312.pyc and /dev/null differ
diff --git a/backend/app/api/endpoints/__pycache__/analysis_charts.cpython-312.pyc b/backend/app/api/endpoints/__pycache__/analysis_charts.cpython-312.pyc
deleted file mode 100644
index a662411..0000000
Binary files a/backend/app/api/endpoints/__pycache__/analysis_charts.cpython-312.pyc and /dev/null differ
diff --git a/backend/app/api/endpoints/__pycache__/documents.cpython-312.pyc b/backend/app/api/endpoints/__pycache__/documents.cpython-312.pyc
deleted file mode 100644
index ec1367a..0000000
Binary files a/backend/app/api/endpoints/__pycache__/documents.cpython-312.pyc and /dev/null differ
diff --git a/backend/app/api/endpoints/__pycache__/health.cpython-312.pyc b/backend/app/api/endpoints/__pycache__/health.cpython-312.pyc
deleted file mode 100644
index 21a536b..0000000
Binary files a/backend/app/api/endpoints/__pycache__/health.cpython-312.pyc and /dev/null differ
diff --git a/backend/app/api/endpoints/__pycache__/library.cpython-312.pyc b/backend/app/api/endpoints/__pycache__/library.cpython-312.pyc
deleted file mode 100644
index a30bc17..0000000
Binary files a/backend/app/api/endpoints/__pycache__/library.cpython-312.pyc and /dev/null differ
diff --git a/backend/app/api/endpoints/__pycache__/rag.cpython-312.pyc b/backend/app/api/endpoints/__pycache__/rag.cpython-312.pyc
deleted file mode 100644
index 0b45d0c..0000000
Binary files a/backend/app/api/endpoints/__pycache__/rag.cpython-312.pyc and /dev/null differ
diff --git a/backend/app/api/endpoints/__pycache__/tasks.cpython-312.pyc b/backend/app/api/endpoints/__pycache__/tasks.cpython-312.pyc
deleted file mode 100644
index 2c6df59..0000000
Binary files a/backend/app/api/endpoints/__pycache__/tasks.cpython-312.pyc and /dev/null differ
diff --git a/backend/app/api/endpoints/__pycache__/templates.cpython-312.pyc b/backend/app/api/endpoints/__pycache__/templates.cpython-312.pyc
deleted file mode 100644
index c426ada..0000000
Binary files a/backend/app/api/endpoints/__pycache__/templates.cpython-312.pyc and /dev/null differ
diff --git a/backend/app/api/endpoints/__pycache__/upload.cpython-312.pyc b/backend/app/api/endpoints/__pycache__/upload.cpython-312.pyc
deleted file mode 100644
index ee10e3d..0000000
Binary files a/backend/app/api/endpoints/__pycache__/upload.cpython-312.pyc and /dev/null differ
diff --git a/backend/app/api/endpoints/__pycache__/upload.cpython-313.pyc b/backend/app/api/endpoints/__pycache__/upload.cpython-313.pyc
deleted file mode 100644
index eecbcee..0000000
Binary files a/backend/app/api/endpoints/__pycache__/upload.cpython-313.pyc and /dev/null differ
diff --git a/backend/app/api/endpoints/__pycache__/visualization.cpython-312.pyc b/backend/app/api/endpoints/__pycache__/visualization.cpython-312.pyc
deleted file mode 100644
index 652ab53..0000000
Binary files a/backend/app/api/endpoints/__pycache__/visualization.cpython-312.pyc and /dev/null differ
diff --git a/backend/app/core/database/__pycache__/__init__.cpython-312.pyc b/backend/app/core/database/__pycache__/__init__.cpython-312.pyc
deleted file mode 100644
index d0c9f86..0000000
Binary files a/backend/app/core/database/__pycache__/__init__.cpython-312.pyc and /dev/null differ
diff --git a/backend/app/core/database/__pycache__/mongodb.cpython-312.pyc b/backend/app/core/database/__pycache__/mongodb.cpython-312.pyc
deleted file mode 100644
index 1d102ac..0000000
Binary files a/backend/app/core/database/__pycache__/mongodb.cpython-312.pyc and /dev/null differ
diff --git a/backend/app/core/database/__pycache__/mysql.cpython-312.pyc b/backend/app/core/database/__pycache__/mysql.cpython-312.pyc
deleted file mode 100644
index 0347b2c..0000000
Binary files a/backend/app/core/database/__pycache__/mysql.cpython-312.pyc and /dev/null differ
diff --git a/backend/app/core/database/__pycache__/redis_db.cpython-312.pyc b/backend/app/core/database/__pycache__/redis_db.cpython-312.pyc
deleted file mode 100644
index 2a8d672..0000000
Binary files a/backend/app/core/database/__pycache__/redis_db.cpython-312.pyc and /dev/null differ
diff --git a/backend/app/core/database/redis_db.py b/backend/app/core/database/redis_db.py
index 83b21e9..ffba53e 100644
--- a/backend/app/core/database/redis_db.py
+++ b/backend/app/core/database/redis_db.py
@@ -144,12 +144,19 @@ class RedisDB:
Returns:
是否成功
"""
- key = f"task:{task_id}"
- data = {
- "status": status,
- "meta": meta or {},
- }
- return await self.set_json(key, data, expire)
+ if not self._connected or not self.client:
+ logger.warning(f"Redis未连接,跳过任务状态更新: {task_id}")
+ return False
+ try:
+ key = f"task:{task_id}"
+ data = {
+ "status": status,
+ "meta": meta or {},
+ }
+ return await self.set_json(key, data, expire)
+ except Exception as e:
+ logger.warning(f"设置任务状态失败: {task_id}, error: {e}")
+ return False
async def get_task_status(self, task_id: str) -> Optional[Dict[str, Any]]:
"""
@@ -161,8 +168,15 @@ class RedisDB:
Returns:
状态信息
"""
- key = f"task:{task_id}"
- return await self.get_json(key)
+ if not self._connected or not self.client:
+ logger.warning(f"Redis未连接,无法获取任务状态: {task_id}")
+ return None
+ try:
+ key = f"task:{task_id}"
+ return await self.get_json(key)
+ except Exception as e:
+ logger.warning(f"获取任务状态失败: {task_id}, error: {e}")
+ return None
async def update_task_progress(
self,
@@ -181,14 +195,21 @@ class RedisDB:
Returns:
是否成功
"""
- data = await self.get_task_status(task_id)
- if data:
- data["meta"]["progress"] = progress
- if message:
- data["meta"]["message"] = message
- key = f"task:{task_id}"
- return await self.set_json(key, data, expire=86400)
- return False
+ if not self._connected or not self.client:
+ logger.warning(f"Redis未连接,跳过任务进度更新: {task_id}")
+ return False
+ try:
+ data = await self.get_task_status(task_id)
+ if data:
+ data["meta"]["progress"] = progress
+ if message:
+ data["meta"]["message"] = message
+ key = f"task:{task_id}"
+ return await self.set_json(key, data, expire=86400)
+ return False
+ except Exception as e:
+ logger.warning(f"更新任务进度失败: {task_id}, error: {e}")
+ return False
# ==================== 缓存操作 ====================
diff --git a/backend/app/core/document_parser/__pycache__/__init__.cpython-312.pyc b/backend/app/core/document_parser/__pycache__/__init__.cpython-312.pyc
deleted file mode 100644
index 25edcb6..0000000
Binary files a/backend/app/core/document_parser/__pycache__/__init__.cpython-312.pyc and /dev/null differ
diff --git a/backend/app/core/document_parser/__pycache__/base.cpython-312.pyc b/backend/app/core/document_parser/__pycache__/base.cpython-312.pyc
deleted file mode 100644
index f282ea1..0000000
Binary files a/backend/app/core/document_parser/__pycache__/base.cpython-312.pyc and /dev/null differ
diff --git a/backend/app/core/document_parser/__pycache__/docx_parser.cpython-312.pyc b/backend/app/core/document_parser/__pycache__/docx_parser.cpython-312.pyc
deleted file mode 100644
index 5c1d8b0..0000000
Binary files a/backend/app/core/document_parser/__pycache__/docx_parser.cpython-312.pyc and /dev/null differ
diff --git a/backend/app/core/document_parser/__pycache__/md_parser.cpython-312.pyc b/backend/app/core/document_parser/__pycache__/md_parser.cpython-312.pyc
deleted file mode 100644
index 5793eb6..0000000
Binary files a/backend/app/core/document_parser/__pycache__/md_parser.cpython-312.pyc and /dev/null differ
diff --git a/backend/app/core/document_parser/__pycache__/txt_parser.cpython-312.pyc b/backend/app/core/document_parser/__pycache__/txt_parser.cpython-312.pyc
deleted file mode 100644
index 6f5c39b..0000000
Binary files a/backend/app/core/document_parser/__pycache__/txt_parser.cpython-312.pyc and /dev/null differ
diff --git a/backend/app/core/document_parser/__pycache__/xlsx_parser.cpython-312.pyc b/backend/app/core/document_parser/__pycache__/xlsx_parser.cpython-312.pyc
deleted file mode 100644
index 2b8606e..0000000
Binary files a/backend/app/core/document_parser/__pycache__/xlsx_parser.cpython-312.pyc and /dev/null differ
diff --git a/backend/app/services/__pycache__/chart_generator_service.cpython-312.pyc b/backend/app/services/__pycache__/chart_generator_service.cpython-312.pyc
deleted file mode 100644
index db6294d..0000000
Binary files a/backend/app/services/__pycache__/chart_generator_service.cpython-312.pyc and /dev/null differ
diff --git a/backend/app/services/__pycache__/chart_generator_service.cpython-313.pyc b/backend/app/services/__pycache__/chart_generator_service.cpython-313.pyc
deleted file mode 100644
index f873a56..0000000
Binary files a/backend/app/services/__pycache__/chart_generator_service.cpython-313.pyc and /dev/null differ
diff --git a/backend/app/services/__pycache__/excel_ai_service.cpython-312.pyc b/backend/app/services/__pycache__/excel_ai_service.cpython-312.pyc
deleted file mode 100644
index 13dfbef..0000000
Binary files a/backend/app/services/__pycache__/excel_ai_service.cpython-312.pyc and /dev/null differ
diff --git a/backend/app/services/__pycache__/excel_storage_service.cpython-312.pyc b/backend/app/services/__pycache__/excel_storage_service.cpython-312.pyc
deleted file mode 100644
index 91bd263..0000000
Binary files a/backend/app/services/__pycache__/excel_storage_service.cpython-312.pyc and /dev/null differ
diff --git a/backend/app/services/__pycache__/file_service.cpython-312.pyc b/backend/app/services/__pycache__/file_service.cpython-312.pyc
deleted file mode 100644
index 666b4d1..0000000
Binary files a/backend/app/services/__pycache__/file_service.cpython-312.pyc and /dev/null differ
diff --git a/backend/app/services/__pycache__/font_helper.cpython-312.pyc b/backend/app/services/__pycache__/font_helper.cpython-312.pyc
deleted file mode 100644
index 02abb07..0000000
Binary files a/backend/app/services/__pycache__/font_helper.cpython-312.pyc and /dev/null differ
diff --git a/backend/app/services/__pycache__/font_helper.cpython-313.pyc b/backend/app/services/__pycache__/font_helper.cpython-313.pyc
deleted file mode 100644
index e3fa6a9..0000000
Binary files a/backend/app/services/__pycache__/font_helper.cpython-313.pyc and /dev/null differ
diff --git a/backend/app/services/__pycache__/llm_service.cpython-312.pyc b/backend/app/services/__pycache__/llm_service.cpython-312.pyc
deleted file mode 100644
index 1fb90da..0000000
Binary files a/backend/app/services/__pycache__/llm_service.cpython-312.pyc and /dev/null differ
diff --git a/backend/app/services/__pycache__/llm_service.cpython-313.pyc b/backend/app/services/__pycache__/llm_service.cpython-313.pyc
deleted file mode 100644
index d802f0f..0000000
Binary files a/backend/app/services/__pycache__/llm_service.cpython-313.pyc and /dev/null differ
diff --git a/backend/app/services/__pycache__/rag_service.cpython-312.pyc b/backend/app/services/__pycache__/rag_service.cpython-312.pyc
deleted file mode 100644
index 856ba0a..0000000
Binary files a/backend/app/services/__pycache__/rag_service.cpython-312.pyc and /dev/null differ
diff --git a/backend/app/services/__pycache__/table_rag_service.cpython-312.pyc b/backend/app/services/__pycache__/table_rag_service.cpython-312.pyc
deleted file mode 100644
index 2239298..0000000
Binary files a/backend/app/services/__pycache__/table_rag_service.cpython-312.pyc and /dev/null differ
diff --git a/backend/app/services/__pycache__/template_fill_service.cpython-312.pyc b/backend/app/services/__pycache__/template_fill_service.cpython-312.pyc
deleted file mode 100644
index ea3101e..0000000
Binary files a/backend/app/services/__pycache__/template_fill_service.cpython-312.pyc and /dev/null differ
diff --git a/backend/app/services/__pycache__/text_analysis_service.cpython-312.pyc b/backend/app/services/__pycache__/text_analysis_service.cpython-312.pyc
deleted file mode 100644
index a9f434c..0000000
Binary files a/backend/app/services/__pycache__/text_analysis_service.cpython-312.pyc and /dev/null differ
diff --git a/backend/app/services/__pycache__/text_analysis_service.cpython-313.pyc b/backend/app/services/__pycache__/text_analysis_service.cpython-313.pyc
deleted file mode 100644
index 8bd2878..0000000
Binary files a/backend/app/services/__pycache__/text_analysis_service.cpython-313.pyc and /dev/null differ
diff --git a/backend/app/services/__pycache__/visualization_service.cpython-312.pyc b/backend/app/services/__pycache__/visualization_service.cpython-312.pyc
deleted file mode 100644
index 4a0c83c..0000000
Binary files a/backend/app/services/__pycache__/visualization_service.cpython-312.pyc and /dev/null differ
diff --git a/backend/app/services/__pycache__/visualization_service.cpython-313.pyc b/backend/app/services/__pycache__/visualization_service.cpython-313.pyc
deleted file mode 100644
index 8227cf1..0000000
Binary files a/backend/app/services/__pycache__/visualization_service.cpython-313.pyc and /dev/null differ
diff --git a/frontend/src/components/layouts/MainLayout.tsx b/frontend/src/components/layouts/MainLayout.tsx
index 37124f5..3f0009d 100644
--- a/frontend/src/components/layouts/MainLayout.tsx
+++ b/frontend/src/components/layouts/MainLayout.tsx
@@ -1,39 +1,29 @@
import React from 'react';
-import { Link, useLocation, Outlet, useNavigate } from 'react-router-dom';
+import { Link, useLocation, Outlet } from 'react-router-dom';
import {
LayoutDashboard,
FileText,
TableProperties,
MessageSquareCode,
- LogOut,
Menu,
- X,
ChevronRight,
- User,
- Sparkles
+ Sparkles,
+ Clock
} from 'lucide-react';
import { Button } from '@/components/ui/button';
-import { useAuth } from '@/context/AuthContext';
import { cn } from '@/lib/utils';
import { Sheet, SheetContent, SheetTrigger } from '@/components/ui/sheet';
const navItems = [
{ name: '控制台', path: '/', icon: LayoutDashboard },
{ name: '文档中心', path: '/documents', icon: FileText },
- { name: 'Excel 解析', path: '/excel-parse', icon: Sparkles },
{ name: '智能填表', path: '/form-fill', icon: TableProperties },
{ name: '智能助手', path: '/assistant', icon: MessageSquareCode },
+ { name: '任务历史', path: '/task-history', icon: Clock },
];
const MainLayout: React.FC = () => {
- const { user, profile, signOut } = useAuth();
const location = useLocation();
- const navigate = useNavigate();
-
- const handleSignOut = async () => {
- await signOut();
- navigate('/login');
- };
const SidebarContent = () => (
@@ -70,25 +60,17 @@ const MainLayout: React.FC = () => {
-
+
-
+
- {((profile as any)?.email) || '用户'}
- {((profile as any)?.role) || 'User'}
+ 智联文档
+ 多源数据融合
-
);
diff --git a/frontend/src/pages/Documents.tsx b/frontend/src/pages/Documents.tsx
index 283b39c..b81e564 100644
--- a/frontend/src/pages/Documents.tsx
+++ b/frontend/src/pages/Documents.tsx
@@ -9,19 +9,43 @@ import {
Clock,
ChevronDown,
ChevronUp,
- Database,
FileSpreadsheet,
- File
+ File,
+ Table,
+ CheckCircle,
+ AlertCircle,
+ Loader2,
+ Sparkles,
+ TrendingUp,
+ Download,
+ Brain,
+ Settings2
} from 'lucide-react';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
-import { Card } from '@/components/ui/card';
+import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card';
import { Badge } from '@/components/ui/badge';
-import { backendApi } from '@/db/backend-api';
-import { format } from 'date-fns';
+import { Label } from '@/components/ui/label';
+import { Switch } from '@/components/ui/switch';
+import { Textarea } from '@/components/ui/textarea';
+import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
+import { Checkbox } from '@/components/ui/checkbox';
import { toast } from 'sonner';
import { cn } from '@/lib/utils';
import { Skeleton } from '@/components/ui/skeleton';
+import { backendApi, type ExcelParseResult, aiApi } from '@/db/backend-api';
+import {
+ Table as TableComponent,
+ TableBody,
+ TableCell,
+ TableHead,
+ TableHeader,
+ TableRow,
+} from '@/components/ui/table';
+import { Markdown } from '@/components/ui/markdown';
+import { AIChartDisplay } from '@/components/ui/ai-chart-display';
+import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from '@/components/ui/dialog';
+import { format } from 'date-fns';
type DocumentItem = {
doc_id: string;
@@ -41,8 +65,55 @@ const Documents: React.FC = () => {
const [documents, setDocuments] = useState
([]);
const [loading, setLoading] = useState(true);
const [search, setSearch] = useState('');
- const [expandedDoc, setExpandedDoc] = useState(null);
+ // 上传相关状态
const [uploading, setUploading] = useState(false);
+ const [uploadedFile, setUploadedFile] = useState(null);
+ const [parseResult, setParseResult] = useState(null);
+ const [expandedSheet, setExpandedSheet] = useState(null);
+
+ // AI 分析相关状态
+ const [analyzing, setAnalyzing] = useState(false);
+ const [analyzingForCharts, setAnalyzingForCharts] = useState(false);
+ const [aiAnalysis, setAiAnalysis] = useState(null);
+ const [analysisCharts, setAnalysisCharts] = useState(null);
+ const [analysisTypes, setAnalysisTypes] = useState>([]);
+
+ // 解析选项
+ const [parseOptions, setParseOptions] = useState({
+ parseAllSheets: false,
+ headerRow: 0
+ });
+
+ // AI 分析选项
+ const [aiOptions, setAiOptions] = useState({
+ userPrompt: '',
+ analysisType: 'general' as 'general' | 'summary' | 'statistics' | 'insights',
+ parseAllSheetsForAI: false
+ });
+
+ // 导出相关状态
+ const [exportDialogOpen, setExportDialogOpen] = useState(false);
+ const [selectedSheet, setSelectedSheet] = useState('');
+ const [selectedColumns, setSelectedColumns] = useState>(new Set());
+ const [selectAll, setSelectAll] = useState(false);
+ const [exporting, setExporting] = useState(false);
+
+ // 上传面板展开状态
+ const [uploadPanelOpen, setUploadPanelOpen] = useState(true);
+
+ // 获取分析类型
+ useEffect(() => {
+ aiApi.getAnalysisTypes()
+ .then(data => setAnalysisTypes(data.types))
+ .catch(() => {
+ setAnalysisTypes([
+ { value: 'general', label: '综合分析', description: '提供数据概览、关键发现,质量评估和建议' },
+ { value: 'summary', label: '数据摘要', description: '快速了解数据的结构、范围和主要内容' },
+ { value: 'statistics', label: '统计分析', description: '数值型列的统计信息和分类列的分布' },
+ { value: 'insights', label: '深度洞察', description: '深入挖掘数据,提供异常值和业务建议' }
+ ]);
+ });
+ }, []);
const loadDocuments = useCallback(async () => {
setLoading(true);
@@ -62,49 +133,68 @@ const Documents: React.FC = () => {
loadDocuments();
}, [loadDocuments]);
+ // 文件上传处理
const onDrop = async (acceptedFiles: File[]) => {
+ const file = acceptedFiles[0];
+ if (!file) return;
+
+ setUploadedFile(file);
setUploading(true);
- const uploaded: string[] = [];
+ setParseResult(null);
+ setAiAnalysis(null);
+ setAnalysisCharts(null);
+ setExpandedSheet(null);
+
+ const ext = file.name.split('.').pop()?.toLowerCase();
try {
- for (const file of acceptedFiles) {
- try {
- const result = await backendApi.uploadDocument(file);
- if (result.task_id) {
- uploaded.push(file.name);
-
- // 轮询任务状态
- const checkStatus = async () => {
- let attempts = 0;
- while (attempts < 30) {
- try {
- const status = await backendApi.getTaskStatus(result.task_id);
- if (status.status === 'success') {
- toast.success(`文件 ${file.name} 处理完成`);
- loadDocuments();
- return;
- } else if (status.status === 'failure') {
- toast.error(`文件 ${file.name} 处理失败: ${status.error}`);
- return;
- }
- } catch (e) {
- console.error('检查状态失败', e);
- }
- await new Promise(resolve => setTimeout(resolve, 2000));
- attempts++;
- }
- toast.error(`文件 ${file.name} 处理超时`);
- };
- checkStatus();
+ // Excel 文件使用专门的上传接口
+ if (ext === 'xlsx' || ext === 'xls') {
+ const result = await backendApi.uploadExcel(file, {
+ parseAllSheets: parseOptions.parseAllSheets,
+ headerRow: parseOptions.headerRow
+ });
+ if (result.success) {
+ toast.success(`解析成功: ${file.name}`);
+ setParseResult(result);
+ if (result.metadata?.sheet_count === 1) {
+ setExpandedSheet(Object.keys(result.data?.sheets || {})[0] || null);
}
- } catch (err: any) {
- toast.error(`上传失败: ${file.name} - ${err.message}`);
+ } else {
+ toast.error(result.error || '解析失败');
+ }
+ } else {
+ // 其他文档使用通用上传接口
+ const result = await backendApi.uploadDocument(file);
+ if (result.task_id) {
+ toast.success(`文件 ${file.name} 已提交处理`);
+ // 轮询任务状态
+ let attempts = 0;
+ const checkStatus = async () => {
+ while (attempts < 30) {
+ try {
+ const status = await backendApi.getTaskStatus(result.task_id);
+ if (status.status === 'success') {
+ toast.success(`文件 ${file.name} 处理完成`);
+ loadDocuments();
+ return;
+ } else if (status.status === 'failure') {
+ toast.error(`文件 ${file.name} 处理失败`);
+ return;
+ }
+ } catch (e) {
+ console.error('检查状态失败', e);
+ }
+ await new Promise(resolve => setTimeout(resolve, 2000));
+ attempts++;
+ }
+ toast.error(`文件 ${file.name} 处理超时`);
+ };
+ checkStatus();
}
}
-
- if (uploaded.length > 0) {
- toast.success(`已提交 ${uploaded.length} 个文件进行处理`);
- }
+ } catch (error: any) {
+ toast.error(error.message || '上传失败');
} finally {
setUploading(false);
}
@@ -114,11 +204,176 @@ const Documents: React.FC = () => {
onDrop,
accept: {
'application/vnd.openxmlformats-officedocument.wordprocessingml.document': ['.docx'],
+ 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': ['.xlsx'],
+ 'application/vnd.ms-excel': ['.xls'],
'text/markdown': ['.md'],
'text/plain': ['.txt']
- }
+ },
+ maxFiles: 1
});
+ // AI 分析处理
+ const handleAnalyze = async () => {
+ if (!uploadedFile || !parseResult?.success) {
+ toast.error('请先上传 Excel 文件');
+ return;
+ }
+
+ setAnalyzing(true);
+ setAiAnalysis(null);
+ setAnalysisCharts(null);
+
+ try {
+ const result = await aiApi.analyzeExcel(uploadedFile, {
+ userPrompt: aiOptions.userPrompt,
+ analysisType: aiOptions.analysisType,
+ parseAllSheets: aiOptions.parseAllSheetsForAI
+ });
+
+ if (result.success) {
+ toast.success('AI 分析完成');
+ setAiAnalysis(result);
+ } else {
+ toast.error(result.error || 'AI 分析失败');
+ }
+ } catch (error: any) {
+ toast.error(error.message || 'AI 分析失败');
+ } finally {
+ setAnalyzing(false);
+ }
+ };
+
+ // 基于 AI 分析生成图表
+ const handleGenerateCharts = async () => {
+ if (!aiAnalysis || !aiAnalysis.success) {
+ toast.error('请先进行 AI 分析');
+ return;
+ }
+
+ let analysisText = '';
+ if (aiAnalysis.analysis?.analysis) {
+ analysisText = aiAnalysis.analysis.analysis;
+ } else if (aiAnalysis.analysis?.sheets) {
+ const sheets = aiAnalysis.analysis.sheets;
+ if (sheets && Object.keys(sheets).length > 0) {
+ const firstSheet = Object.keys(sheets)[0];
+ analysisText = sheets[firstSheet]?.analysis || '';
+ }
+ }
+
+ if (!analysisText?.trim()) {
+ toast.error('无法获取 AI 分析结果');
+ return;
+ }
+
+ setAnalyzingForCharts(true);
+ setAnalysisCharts(null);
+
+ try {
+ const result = await aiApi.extractAndGenerateCharts({
+ analysis_text: analysisText,
+ original_filename: uploadedFile?.name || 'unknown',
+ file_type: 'excel'
+ });
+
+ if (result.success) {
+ toast.success('图表生成完成');
+ setAnalysisCharts(result);
+ } else {
+ toast.error(result.error || '图表生成失败');
+ }
+ } catch (error: any) {
+ toast.error(error.message || '图表生成失败');
+ } finally {
+ setAnalyzingForCharts(false);
+ }
+ };
+
+ // 获取工作表数据
+ const getSheetData = (sheetName: string) => {
+ if (!parseResult?.success || !parseResult.data) return null;
+ const data = parseResult.data;
+ if (data.sheets && data.sheets[sheetName]) {
+ return data.sheets[sheetName];
+ }
+ if (!data.sheets && data.columns && data.rows) {
+ return data;
+ }
+ return null;
+ };
+
+ // 打开导出对话框
+ const openExportDialog = () => {
+ if (!parseResult?.success || !parseResult.data) {
+ toast.error('请先上传并解析 Excel 文件');
+ return;
+ }
+
+ const data = parseResult.data;
+ let sheets: string[] = [];
+ if (data.sheets) {
+ sheets = Object.keys(data.sheets);
+ } else {
+ sheets = ['默认工作表'];
+ }
+
+ setSelectedSheet(sheets[0]);
+ const sheetColumns = getSheetData(sheets[0])?.columns || [];
+ setSelectedColumns(new Set(sheetColumns));
+ setSelectAll(true);
+ setExportDialogOpen(true);
+ };
+
+ // 导出处理
+ const handleExport = async () => {
+ if (selectedColumns.size === 0) {
+ toast.error('请至少选择一列');
+ return;
+ }
+
+ if (!parseResult?.metadata?.saved_path) {
+ toast.error('无法获取文件路径');
+ return;
+ }
+
+ setExporting(true);
+
+ try {
+ const blob = await backendApi.exportExcel(
+ parseResult.metadata.saved_path,
+ {
+ columns: Array.from(selectedColumns),
+ sheetName: selectedSheet === '默认工作表' ? undefined : selectedSheet
+ }
+ );
+
+ const url = URL.createObjectURL(blob);
+ const link = document.createElement('a');
+ link.href = url;
+ link.download = `export_${selectedSheet}_${uploadedFile?.name || 'data.xlsx'}`;
+ document.body.appendChild(link);
+ link.click();
+ document.body.removeChild(link);
+ URL.revokeObjectURL(url);
+
+ toast.success('导出成功');
+ setExportDialogOpen(false);
+ } catch (error: any) {
+ toast.error(error.message || '导出失败');
+ } finally {
+ setExporting(false);
+ }
+ };
+
+ const handleDeleteFile = () => {
+ setUploadedFile(null);
+ setParseResult(null);
+ setAiAnalysis(null);
+ setAnalysisCharts(null);
+ setExpandedSheet(null);
+ toast.success('文件已清除');
+ };
+
const handleDelete = async (docId: string) => {
try {
const result = await backendApi.deleteDocument(docId);
@@ -148,12 +403,35 @@ const Documents: React.FC = () => {
}
};
+ const formatFileSize = (bytes: number): string => {
+ if (bytes === 0) return '0 B';
+ const k = 1024;
+ const sizes = ['B', 'KB', 'MB', 'GB'];
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
+ return `${(bytes / Math.pow(k, i)).toFixed(2)} ${sizes[i]}`;
+ };
+
+ const getAnalysisIcon = (type: string) => {
+ switch (type) {
+ case 'general': return ;
+ case 'summary': return ;
+ case 'statistics': return ;
+ case 'insights': return ;
+ default: return ;
+ }
+ };
+
+ const isExcelFile = (filename: string) => {
+ const ext = filename.split('.').pop()?.toLowerCase();
+ return ext === 'xlsx' || ext === 'xls';
+ };
+
return (
文档中心
-
上传并管理您的非结构化文档(docx/md/txt),系统将自动进行深度理解与信息提取。
+
上传文档,自动解析并使用 AI 进行深度分析
- {/* Upload Zone - For non-Excel documents */}
-
-
-
- {uploading ? : }
-
-
-
- {isDragActive ? '释放以开始上传' : '点击或拖拽文件到这里'}
-
-
- 支持 docx, md, txt 格式文档,系统将自动识别关键信息并存入 MongoDB
-
-
-
-
- Word 文档
-
-
- Markdown
-
-
- 文本文件
-
-
-
-
- {/* Filter & Search */}
-
-
-
- setSearch(e.target.value)}
- />
-
-
-
- {documents.length} 个文档
-
-
-
- {/* Document List */}
-
- {loading ? (
- Array.from({ length: 3 }).map((_, i) => (
-
- ))
- ) : filteredDocs.length > 0 ? (
- filteredDocs.map((doc) => (
-
-
-
-
- {getDocIcon(doc.doc_type)}
-
-
-
-
{doc.original_filename}
-
- {doc.doc_type}
+
+ {/* 左侧:上传和配置区域 */}
+
+ {/* 上传卡片 */}
+
+
+
+
+
+ 文件上传
+
+
+
+ 拖拽或点击上传文档文件
+
+ {uploadPanelOpen && (
+
+ {!uploadedFile ? (
+
+
+
+ {uploading ? : }
+
+
+ {isDragActive ? '释放以开始上传' : '点击或拖拽文件到这里'}
+
+
+
+ Word
+
+
+ Excel
+
+
+ Markdown
+
+
+ 文本
- {doc.metadata?.columns && (
-
- {doc.metadata.columns.length} 列
-
- )}
-
-
-
-
- {format(new Date(doc.created_at), 'yyyy-MM-dd HH:mm')}
-
- {(doc.file_size / 1024).toFixed(1)} KB
-
-
-
-
-
-
- {/* Expanded Details */}
- {expandedDoc === doc.doc_id && (
-
-
-
-
文件信息
-
-
- 文件名
- {doc.original_filename}
-
-
- 文件大小
- {(doc.file_size / 1024).toFixed(2)} KB
-
-
- 文档类型
- {doc.doc_type.toUpperCase()}
-
-
- 创建时间
- {format(new Date(doc.created_at), 'yyyy-MM-dd HH:mm:ss')}
-
-
+ ) : (
+
+
+
+ {isExcelFile(uploadedFile.name) ? : }
-
- {doc.metadata?.columns && doc.metadata.columns.length > 0 && (
-
-
表格结构
-
- {doc.metadata.columns.map((col: string, idx: number) => (
-
- {col}
-
- ))}
-
- {doc.metadata.row_count && (
-
- 共 {doc.metadata.row_count} 行数据
-
- )}
-
- )}
-
-
-
存储位置
-
-
-
- {doc.doc_type === 'xlsx' ? 'MySQL 数据库(结构化数据)' : 'MongoDB 数据库(非结构化文档)'}
-
-
+
+
{uploadedFile.name}
+
{formatFileSize(uploadedFile.size)}
+
+
+ {isExcelFile(uploadedFile.name) && (
+
+ )}
)}
-
+
+ )}
+
+
+ {/* Excel 解析选项 */}
+ {uploadedFile && isExcelFile(uploadedFile.name) && (
+
+
+
+
+ 解析选项
+
+
+
+
+
+ setParseOptions({ ...parseOptions, parseAllSheets: checked })}
+ />
+
+
+
+ setParseOptions({ ...parseOptions, headerRow: parseInt(e.target.value) || 0 })}
+ className="bg-background"
+ />
+
+
- ))
- ) : (
-
-
-
+ )}
+
+ {/* AI 分析选项 */}
+ {uploadedFile && isExcelFile(uploadedFile.name) && parseResult?.success && (
+
+
+
+
+ AI 分析
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )}
+
+ {/* 数据操作 */}
+ {parseResult?.success && (
+
+
+
+
+ 数据操作
+
+
+
+
+
+
+
+ )}
+
+
+ {/* 右侧:结果显示区域 */}
+
+ {/* AI 分析结果 */}
+ {aiAnalysis && (
+
+
+
+
+
+
+ AI 分析结果
+
+ {aiAnalysis.analysis?.model && (
+ 使用模型: {aiAnalysis.analysis.model}
+ )}
+
+
+
+
+ {aiAnalysis.analysis?.sheets ? (
+
+ {Object.entries(aiAnalysis.analysis.sheets).map(([sheetName, result]: [string, any]) => (
+
+
+
+
{sheetName}
+ {result.success ?
:
}
+
+ {result.success && result.analysis &&
}
+ {!result.success &&
{result.error || '分析失败'}
}
+
+ ))}
+
+ ) : (
+ aiAnalysis.analysis?.analysis &&
+ )}
+
+
+ )}
+
+ {/* 图表显示 */}
+ {analysisCharts && (
+
+
+
+
+ AI 分析结果图表
+
+
+
+
+
+
+ )}
+
+ {/* Excel 数据预览 */}
+ {parseResult?.success && parseResult.data && (
+
+
+
+
+
+
+ 数据预览
+
+
{parseResult.data.sheets ? '所有工作表数据' : '工作表数据'}
+
+
+
+
+
+ {parseResult.data.sheets ? (
+
+ {Object.entries(parseResult.data.sheets).map(([sheetName, sheetData]: [string, any]) => (
+
+
+ {expandedSheet === sheetName && (
+
+
+
+ )}
+
+ ))}
+
+ ) : (
+
+ )}
+
+
+ )}
+
+ {/* 文档列表 */}
+
+
+
+
+ 已上传文档
+
+ 共 {documents.length} 个文档
+
+
+ {/* 搜索 */}
+
+
+ setSearch(e.target.value)}
+ />
+
+
+ {/* 文档列表 */}
+ {loading ? (
+ {[1, 2, 3].map(i => )}
+ ) : filteredDocs.length > 0 ? (
+
+ {filteredDocs.map(doc => (
+
+
+ {getDocIcon(doc.doc_type)}
+
+
+
{doc.original_filename}
+
+ {doc.doc_type.toUpperCase()} • {format(new Date(doc.created_at), 'yyyy-MM-dd HH:mm')}
+
+
+
+
+ ))}
+
+ ) : (
+
+ )}
+
+
+
+ {/* 初始状态 */}
+ {!loading && !uploadedFile && !parseResult && !aiAnalysis && (
+
+
+
+ 上传文档开始分析
+ 上传 Excel 文件可获得 AI 分析和图表生成功能
+
+
+ )}
+
+
+
+ {/* 导出对话框 */}
+
+
+ );
+};
+
+// 数据表格组件
+const DataTable: React.FC<{ columns: string[]; rows: Record
[] }> = ({ columns, rows }) => {
+ if (!columns.length || !rows.length) {
+ return 暂无数据
;
+ }
+
+ return (
+
+
+
+
+ #
+ {columns.map((col, idx) => (
+ {col || `<列${idx + 1}>`}
+ ))}
+
+
+
+ {rows.slice(0, 100).map((row, rowIdx) => (
+
+ {rowIdx + 1}
+ {columns.map((col, colIdx) => (
+
+ {row[col] !== null && row[col] !== undefined ? String(row[col]) : '-'}
+
+ ))}
+
+ ))}
+
+
+ {rows.length > 100 && (
+
+ 仅显示前 100 行数据
+
+ )}
);
};
diff --git a/frontend/src/routes.tsx b/frontend/src/routes.tsx
index 0b405c7..2b26b87 100644
--- a/frontend/src/routes.tsx
+++ b/frontend/src/routes.tsx
@@ -1,17 +1,12 @@
import { createBrowserRouter, Navigate } from 'react-router-dom';
-import Login from '@/pages/Login';
import Dashboard from '@/pages/Dashboard';
import Documents from '@/pages/Documents';
-import FormFill from '@/pages/FormFill';
-import Assistant from '@/pages/Assistant';
-import ExcelParse from '@/pages/ExcelParse';
+import TemplateFill from '@/pages/TemplateFill';
+import InstructionChat from '@/pages/InstructionChat';
+import TaskHistory from '@/pages/TaskHistory';
import MainLayout from '@/components/layouts/MainLayout';
export const routes = [
- {
- path: '/login',
- element: ,
- },
{
path: '/',
element: ,
@@ -26,15 +21,15 @@ export const routes = [
},
{
path: '/form-fill',
- element: ,
+ element: ,
},
{
path: '/assistant',
- element: ,
+ element: ,
},
{
- path: '/excel-parse',
- element: ,
+ path: '/task-history',
+ element: ,
},
],
},