diff --git a/frontend/src/context/TemplateFillContext.tsx b/frontend/src/context/TemplateFillContext.tsx index 76ba073..61ef55d 100644 --- a/frontend/src/context/TemplateFillContext.tsx +++ b/frontend/src/context/TemplateFillContext.tsx @@ -21,6 +21,7 @@ interface TemplateFillState { templateFields: TemplateField[]; sourceFiles: SourceFile[]; sourceFilePaths: string[]; + sourceDocIds: string[]; templateId: string; filledResult: any; setStep: (step: Step) => void; @@ -30,6 +31,9 @@ interface TemplateFillState { addSourceFiles: (files: SourceFile[]) => void; removeSourceFile: (index: number) => void; setSourceFilePaths: (paths: string[]) => void; + setSourceDocIds: (ids: string[]) => void; + addSourceDocId: (id: string) => void; + removeSourceDocId: (id: string) => void; setTemplateId: (id: string) => void; setFilledResult: (result: any) => void; reset: () => void; @@ -41,6 +45,7 @@ const initialState = { templateFields: [], sourceFiles: [], sourceFilePaths: [], + sourceDocIds: [], templateId: '', filledResult: null, setStep: () => {}, @@ -50,6 +55,9 @@ const initialState = { addSourceFiles: () => {}, removeSourceFile: () => {}, setSourceFilePaths: () => {}, + setSourceDocIds: () => {}, + addSourceDocId: () => {}, + removeSourceDocId: () => {}, setTemplateId: () => {}, setFilledResult: () => {}, reset: () => {}, @@ -63,6 +71,7 @@ export const TemplateFillProvider: React.FC<{ children: ReactNode }> = ({ childr const [templateFields, setTemplateFields] = useState([]); const [sourceFiles, setSourceFiles] = useState([]); const [sourceFilePaths, setSourceFilePaths] = useState([]); + const [sourceDocIds, setSourceDocIds] = useState([]); const [templateId, setTemplateId] = useState(''); const [filledResult, setFilledResult] = useState(null); @@ -74,12 +83,21 @@ export const TemplateFillProvider: React.FC<{ children: ReactNode }> = ({ childr setSourceFiles(prev => prev.filter((_, i) => i !== index)); }; + const addSourceDocId = (id: string) => { + setSourceDocIds(prev => prev.includes(id) ? prev : [...prev, id]); + }; + + const removeSourceDocId = (id: string) => { + setSourceDocIds(prev => prev.filter(docId => docId !== id)); + }; + const reset = () => { setStep('upload'); setTemplateFile(null); setTemplateFields([]); setSourceFiles([]); setSourceFilePaths([]); + setSourceDocIds([]); setTemplateId(''); setFilledResult(null); }; @@ -92,6 +110,7 @@ export const TemplateFillProvider: React.FC<{ children: ReactNode }> = ({ childr templateFields, sourceFiles, sourceFilePaths, + sourceDocIds, templateId, filledResult, setStep, @@ -101,6 +120,9 @@ export const TemplateFillProvider: React.FC<{ children: ReactNode }> = ({ childr addSourceFiles, removeSourceFile, setSourceFilePaths, + setSourceDocIds, + addSourceDocId, + removeSourceDocId, setTemplateId, setFilledResult, reset, diff --git a/frontend/src/pages/TemplateFill.tsx b/frontend/src/pages/TemplateFill.tsx index d3e57c9..6b96bbd 100644 --- a/frontend/src/pages/TemplateFill.tsx +++ b/frontend/src/pages/TemplateFill.tsx @@ -60,6 +60,7 @@ const TemplateFill: React.FC = () => { templateFields, setTemplateFields, sourceFiles, setSourceFiles, addSourceFiles, removeSourceFile, sourceFilePaths, setSourceFilePaths, + sourceDocIds, setSourceDocIds, addSourceDocId, removeSourceDocId, templateId, setTemplateId, filledResult, setFilledResult, reset @@ -68,6 +69,9 @@ const TemplateFill: React.FC = () => { const [loading, setLoading] = useState(false); const [previewDoc, setPreviewDoc] = useState<{ name: string; content: string } | null>(null); const [previewOpen, setPreviewOpen] = useState(false); + const [sourceMode, setSourceMode] = useState<'upload' | 'select'>('upload'); + const [uploadedDocuments, setUploadedDocuments] = useState([]); + const [docsLoading, setDocsLoading] = useState(false); // 模板拖拽 const onTemplateDrop = useCallback((acceptedFiles: File[]) => { @@ -109,40 +113,118 @@ const TemplateFill: React.FC = () => { multiple: true }); + // 加载已上传文档 + const loadUploadedDocuments = useCallback(async () => { + setDocsLoading(true); + try { + const result = await backendApi.getDocuments(undefined, 100); + if (result.success) { + // 过滤可作为数据源的文档类型 + const docs = (result.documents || []).filter((d: DocumentItem) => + ['docx', 'md', 'txt', 'xlsx', 'xls'].includes(d.doc_type) + ); + setUploadedDocuments(docs); + } + } catch (err: any) { + console.error('加载文档失败:', err); + } finally { + setDocsLoading(false); + } + }, []); + + // 删除文档 + const handleDeleteDocument = async (docId: string, e: React.MouseEvent) => { + e.stopPropagation(); + if (!confirm('确定要删除该文档吗?')) return; + try { + const result = await backendApi.deleteDocument(docId); + if (result.success) { + setUploadedDocuments(prev => prev.filter(d => d.doc_id !== docId)); + removeSourceDocId(docId); + toast.success('文档已删除'); + } else { + toast.error(result.message || '删除失败'); + } + } catch (err: any) { + toast.error('删除失败: ' + (err.message || '未知错误')); + } + }; + + useEffect(() => { + if (sourceMode === 'select') { + loadUploadedDocuments(); + } + }, [sourceMode, loadUploadedDocuments]); + const handleJointUploadAndFill = async () => { if (!templateFile) { toast.error('请先上传模板文件'); return; } + // 检查是否选择了数据源 + if (sourceMode === 'upload' && sourceFiles.length === 0) { + toast.error('请上传源文档或从已上传文档中选择'); + return; + } + if (sourceMode === 'select' && sourceDocIds.length === 0) { + toast.error('请选择源文档'); + return; + } + setLoading(true); try { - // 使用联合上传API - const result = await backendApi.uploadTemplateAndSources( - templateFile, - sourceFiles.map(sf => sf.file) - ); + if (sourceMode === 'select') { + // 使用已上传文档作为数据源 + const result = await backendApi.uploadTemplate(templateFile); - if (result.success) { - setTemplateFields(result.fields || []); - setTemplateId(result.template_id); - setSourceFilePaths(result.source_file_paths || []); - toast.success('文档上传成功,开始智能填表'); - setStep('filling'); + if (result.success) { + setTemplateFields(result.fields || []); + setTemplateId(result.template_id || 'temp'); + toast.success('开始智能填表'); + setStep('filling'); - // 自动开始填表 - const fillResult = await backendApi.fillTemplate( - result.template_id, - result.fields || [], - [], // 使用 source_file_paths 而非 source_doc_ids - result.source_file_paths || [], - '请从以下文档中提取相关信息填写表格' + // 使用 source_doc_ids 进行填表 + const fillResult = await backendApi.fillTemplate( + result.template_id || 'temp', + result.fields || [], + sourceDocIds, + [], + '请从以下文档中提取相关信息填写表格' + ); + + setFilledResult(fillResult); + setStep('preview'); + toast.success('表格填写完成'); + } + } else { + // 使用联合上传API + const result = await backendApi.uploadTemplateAndSources( + templateFile, + sourceFiles.map(sf => sf.file) ); - setFilledResult(fillResult); - setStep('preview'); - toast.success('表格填写完成'); + if (result.success) { + setTemplateFields(result.fields || []); + setTemplateId(result.template_id); + setSourceFilePaths(result.source_file_paths || []); + toast.success('文档上传成功,开始智能填表'); + setStep('filling'); + + // 自动开始填表 + const fillResult = await backendApi.fillTemplate( + result.template_id, + result.fields || [], + [], + result.source_file_paths || [], + '请从以下文档中提取相关信息填写表格' + ); + + setFilledResult(fillResult); + setStep('preview'); + toast.success('表格填写完成'); + } } } catch (err: any) { toast.error('处理失败: ' + (err.message || '未知错误')); @@ -264,47 +346,131 @@ const TemplateFill: React.FC = () => { 源文档 - 上传包含数据的源文档(支持多选),可同时上传多个文件 + 选择包含数据的源文档作为填表依据 + {/* Source Mode Tabs */} +
+ + +
-
- -
- {loading ? : } -
-

- {isSourceDragActive ? '释放以上传' : '点击或拖拽上传源文档'} -

-

- 支持 .xlsx .xls .docx .md .txt -

-
- - {/* Selected Source Files */} - {sourceFiles.length > 0 && ( -
- {sourceFiles.map((sf, idx) => ( -
- {getFileIcon(sf.file.name)} -
-

{sf.file.name}

-

- {(sf.file.size / 1024).toFixed(1)} KB -

-
- + {sourceMode === 'upload' ? ( + <> +
+ +
+ {loading ? : }
- ))} -
+

+ {isSourceDragActive ? '释放以上传' : '点击或拖拽上传源文档'} +

+

+ 支持 .xlsx .xls .docx .md .txt +

+
+ + {/* Selected Source Files */} + {sourceFiles.length > 0 && ( +
+ {sourceFiles.map((sf, idx) => ( +
+ {getFileIcon(sf.file.name)} +
+

{sf.file.name}

+

+ {(sf.file.size / 1024).toFixed(1)} KB +

+
+ +
+ ))} +
+ )} + + ) : ( + <> + {/* Uploaded Documents Selection */} + {docsLoading ? ( +
+ {[1, 2, 3].map(i => ( + + ))} +
+ ) : uploadedDocuments.length > 0 ? ( +
+ {uploadedDocuments.map((doc) => ( +
{ + if (sourceDocIds.includes(doc.doc_id)) { + removeSourceDocId(doc.doc_id); + } else { + addSourceDocId(doc.doc_id); + } + }} + > +
+ {sourceDocIds.includes(doc.doc_id) && } +
+ {getFileIcon(doc.original_filename)} +
+

{doc.original_filename}

+

+ {doc.doc_type.toUpperCase()} • {format(new Date(doc.created_at), 'yyyy-MM-dd')} +

+
+ +
+ ))} +
+ ) : ( +
+ +

暂无可用的已上传文档

+
+ )} + )}