diff --git a/backend/app/api/matching.py b/backend/app/api/matching.py index c1e7a4d..0a0738d 100644 --- a/backend/app/api/matching.py +++ b/backend/app/api/matching.py @@ -88,46 +88,63 @@ async def _background_parse(project_id: int, filename: str, text: str, metadata: async def upload_client_document( project_id: int, background_tasks: BackgroundTasks, - file: UploadFile = File(...), + files: list[UploadFile] = File(...), mode: str = "normal", db: AsyncSession = Depends(get_db), ): - """Upload a client document and extract assets using AI.""" + """Upload one or more client documents and extract assets using AI.""" project = await _get_project(project_id, db) - # Stage 1: Read file and save to data dir import os from app.config import settings - content = await file.read() - save_path = os.path.join(settings.data_dir, file.filename) - with open(save_path, "wb") as f: - f.write(content) - project.source_filename = file.filename + + filenames = [] + all_text_parts = [] + total_chars = 0 + total_sheets = 0 + project.status = ProjectStatus.PARSING - project.parse_stage = f"Uploading {file.filename}..." + project.parse_stage = f"Uploading {len(files)} file(s)..." await db.commit() - # Stage 2: Extract text (fast, synchronous) - project.parse_stage = "Extracting text from document..." - await db.commit() + # Stage 1+2: Read and extract text from each file + for file in files: + content = await file.read() + save_path = os.path.join(settings.data_dir, file.filename) + with open(save_path, "wb") as f: + f.write(content) + filenames.append(file.filename) - try: - text, metadata = extract_text_from_file(content, file.filename) - except Exception as e: + project.parse_stage = f"Extracting text from {file.filename}..." + await db.commit() + + try: + text, metadata = extract_text_from_file(content, file.filename) + all_text_parts.append(f"\n{'='*60}\nFILE: {file.filename}\n{'='*60}\n{text}") + total_chars += metadata["char_count"] + total_sheets += metadata.get("sheet_count", 0) + except Exception as e: + logger.warning(f"Failed to extract text from {file.filename}: {e}") + continue + + if not all_text_parts: project.status = ProjectStatus.DRAFT project.parse_stage = None await db.commit() - raise HTTPException(status_code=400, detail=f"Failed to extract text: {str(e)}") + raise HTTPException(status_code=400, detail="Failed to extract text from any uploaded file.") - sheets_info = f" ({metadata['sheet_count']} sheets)" if metadata['sheet_count'] else "" - project.parse_stage = f"Extracted {metadata['char_count']:,} characters{sheets_info}. Sending to AI..." + combined_text = "\n".join(all_text_parts) + project.source_filename = ", ".join(filenames) + + sheets_info = f" ({total_sheets} sheets)" if total_sheets else "" + project.parse_stage = f"Extracted {total_chars:,} characters from {len(filenames)} file(s){sheets_info}. Sending to AI..." await db.commit() # Stage 3+4: AI parsing runs in background — return 202 immediately - background_tasks.add_task(_background_parse, project_id, file.filename, text, metadata, mode) + background_tasks.add_task(_background_parse, project_id, ", ".join(filenames), combined_text, {"char_count": total_chars, "sheet_count": total_sheets}, mode) return { - "message": f"Document received. AI parsing started for {file.filename}.", + "message": f"{len(filenames)} file(s) received. AI parsing started.", "status": "parsing", } diff --git a/frontend/src/pages/ProjectView.tsx b/frontend/src/pages/ProjectView.tsx index e0eafdc..fbae811 100644 --- a/frontend/src/pages/ProjectView.tsx +++ b/frontend/src/pages/ProjectView.tsx @@ -144,14 +144,17 @@ export default function ProjectView() { }, []); async function handleUpload(e: React.ChangeEvent) { - const file = e.target.files?.[0]; - if (!file) return; + const files = e.target.files; + if (!files || files.length === 0) return; setUploading(true); - setUploadStage(`Uploading ${file.name}...`); + const names = Array.from(files).map(f => f.name).join(', '); + setUploadStage(`Uploading ${files.length} file(s): ${names}...`); try { const form = new FormData(); - form.append('file', file); + for (let i = 0; i < files.length; i++) { + form.append('files', files[i]); + } await api.post(`/projects/${id}/upload?mode=${extractionMode}`, form); } catch (err: any) { alert(`Upload failed: ${err.response?.data?.detail || err.message}`); @@ -524,7 +527,7 @@ export default function ProjectView() { {project.source_filename && (

Current: {project.source_filename}