import React, { useState, useEffect } from 'react'; import { TableProperties, Plus, FilePlus, CheckCircle2, Download, Clock, RefreshCcw, Sparkles, Zap, FileCheck, FileSpreadsheet, Trash2, ChevronDown, ChevronUp, BarChart3, FileText, TrendingUp, Info, AlertCircle, Loader2 } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { Card, CardContent, CardHeader, CardTitle, CardDescription, CardFooter } from '@/components/ui/card'; import { Badge } from '@/components/ui/badge'; import { useAuth } from '@/context/AuthContext'; import { templateApi, documentApi, taskApi } from '@/db/api'; import { backendApi, aiApi } from '@/db/backend-api'; import { supabase } from '@/db/supabase'; import { format } from 'date-fns'; import { toast } from 'sonner'; import { cn } from '@/lib/utils'; import { Skeleton } from '@/components/ui/skeleton'; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger, DialogFooter, DialogDescription } from '@/components/ui/dialog'; import { Checkbox } from '@/components/ui/checkbox'; import { ScrollArea } from '@/components/ui/scroll-area'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Textarea } from '@/components/ui/textarea'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; import { useDropzone } from 'react-dropzone'; import { Markdown } from '@/components/ui/markdown'; type Template = any; type Document = any; type FillTask = any; const FormFill: React.FC = () => { const { profile } = useAuth(); const [templates, setTemplates] = useState([]); const [documents, setDocuments] = useState([]); const [tasks, setTasks] = useState([]); const [loading, setLoading] = useState(true); // Selection state const [selectedTemplate, setSelectedTemplate] = useState(null); const [selectedDocs, setSelectedDocs] = useState([]); const [creating, setCreating] = useState(false); const [openTaskDialog, setOpenTaskDialog] = useState(false); const [viewingTask, setViewingTask] = useState(null); // Excel upload state const [excelFile, setExcelFile] = useState(null); const [excelParseResult, setExcelParseResult] = useState(null); const [excelAnalysis, setExcelAnalysis] = useState(null); const [excelAnalyzing, setExcelAnalyzing] = useState(false); const [expandedSheet, setExpandedSheet] = useState(null); const [aiOptions, setAiOptions] = useState({ userPrompt: '请分析这些数据,并提取关键信息用于填表,包括数值、分类、摘要等。', analysisType: 'general' as 'general' | 'summary' | 'statistics' | 'insights' }); const loadData = async () => { if (!profile) return; try { const [t, d, ts] = await Promise.all([ templateApi.listTemplates((profile as any).id), documentApi.listDocuments((profile as any).id), taskApi.listTasks((profile as any).id) ]); setTemplates(t); setDocuments(d); setTasks(ts); } catch (err: any) { toast.error('数据加载失败'); } finally { setLoading(false); } }; useEffect(() => { loadData(); }, [profile]); // Excel upload handlers const onExcelDrop = async (acceptedFiles: File[]) => { const file = acceptedFiles[0]; if (!file) return; if (!file.name.match(/\.(xlsx|xls)$/i)) { toast.error('仅支持 .xlsx 和 .xls 格式的 Excel 文件'); return; } setExcelFile(file); setExcelParseResult(null); setExcelAnalysis(null); setExpandedSheet(null); try { const result = await backendApi.uploadExcel(file); if (result.success) { toast.success(`Excel 解析成功: ${file.name}`); setExcelParseResult(result); } else { toast.error(result.error || '解析失败'); } } catch (error: any) { toast.error(error.message || '上传失败'); } }; const { getRootProps, getInputProps, isDragActive } = useDropzone({ onDrop: onExcelDrop, accept: { 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': ['.xlsx'], 'application/vnd.ms-excel': ['.xls'] }, maxFiles: 1 }); const handleAnalyzeExcel = async () => { if (!excelFile || !excelParseResult?.success) { toast.error('请先上传并解析 Excel 文件'); return; } setExcelAnalyzing(true); setExcelAnalysis(null); try { const result = await aiApi.analyzeExcel(excelFile, { userPrompt: aiOptions.userPrompt, analysisType: aiOptions.analysisType }); if (result.success) { toast.success('AI 分析完成'); setExcelAnalysis(result); } else { toast.error(result.error || 'AI 分析失败'); } } catch (error: any) { toast.error(error.message || 'AI 分析失败'); } finally { setExcelAnalyzing(false); } }; const handleUseExcelData = () => { if (!excelParseResult?.success) { toast.error('请先解析 Excel 文件'); return; } // 将 Excel 解析的数据标记为"文档",添加到选择列表 toast.success('Excel 数据已添加到数据源,请在任务对话框中选择'); // 这里可以添加逻辑来将 Excel 数据传递给后端创建任务 }; const handleDeleteExcel = () => { setExcelFile(null); setExcelParseResult(null); setExcelAnalysis(null); setExpandedSheet(null); toast.success('Excel 文件已清除'); }; const handleUploadTemplate = async (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (!file || !profile) return; try { toast.loading('正在上传模板...'); await templateApi.uploadTemplate(file, (profile as any).id); toast.dismiss(); toast.success('模板上传成功'); loadData(); } catch (err) { toast.dismiss(); toast.error('上传模板失败'); } }; const handleCreateTask = async () => { if (!profile || !selectedTemplate || selectedDocs.length === 0) { toast.error('请先选择模板和数据源文档'); return; } setCreating(true); try { const task = await taskApi.createTask((profile as any).id, selectedTemplate, selectedDocs); if (task) { toast.success('任务已创建,正在进行智能填表...'); setOpenTaskDialog(false); // Invoke edge function supabase.functions.invoke('fill-template', { body: { taskId: task.id } }).then(({ error }) => { if (error) toast.error('填表任务执行失败'); else { toast.success('表格填写完成!'); loadData(); } }); loadData(); } } catch (err: any) { toast.error('创建任务失败'); } finally { setCreating(false); } }; const getStatusColor = (status: string) => { switch (status) { case 'completed': return 'bg-emerald-500 text-white'; case 'failed': return 'bg-destructive text-white'; default: return 'bg-amber-500 text-white'; } }; 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]}`; }; return (

智能填表

根据您的表格模板,自动聚合多源文档信息进行精准填充,告别重复劳动。

开启智能填表之旅 选择一个表格模板及若干个数据源文档,AI 将自动为您分析并填写。
{/* Step 1: Select Template */}

1 选择表格模板

{templates.length > 0 ? (
{templates.map(t => (
setSelectedTemplate(t.id)} >

{t.name}

{t.type}

{selectedTemplate === t.id && (
)}
))}
) : (
暂无模板,请先点击右上角上传。
)}
{/* Step 2: Upload & Analyze Excel */}

1.5 Excel 数据源

{!excelFile ? (

{isDragActive ? '释放以开始上传' : '点击或拖拽 Excel 文件'}

支持 .xlsx 和 .xls 格式

) : (

{excelFile.name}

{formatFileSize(excelFile.size)}

{/* AI Analysis Options */} {excelParseResult?.success && (