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 分析 + + + +
+ + +
+
+ +