| |
| |
|
|
| from fastapi import FastAPI, UploadFile, File, HTTPException, BackgroundTasks |
| from fastapi.responses import JSONResponse |
| from pathlib import Path |
| import shutil |
| import uuid |
| import logging |
| from tool_chandra import async_process_document |
| from config_chandra import * |
| from typing import Dict |
| import time |
| import aiofiles |
|
|
|
|
| logging.basicConfig( |
| level=logging.INFO, |
| format='%(asctime)s | %(levelname)s | %(message)s', |
| datefmt='%Y-%m-%d %H:%M:%S' |
| ) |
| logger = logging.getLogger(__name__) |
|
|
| app = FastAPI( |
| title="SiLIX Document Intelligence API", |
| description="๋ฌธ์ ๋ณตํธํ โ ๋ ์ด์์ ๋ถ์ โ ๋งํฌ๋ค์ด/JSON ์ถ์ถ ์์ง (Chandra OCR 2)", |
| version="2.0.0" |
| ) |
|
|
| BASE_DIR = Path(__file__).parent |
| UPLOAD_DIR = BASE_DIR / "uploads" |
| UPLOAD_DIR.mkdir(exist_ok=True) |
|
|
|
|
| def cleanup_path(path: str): |
| p = Path(path) |
| try: |
| if p.is_file(): |
| p.unlink() |
| elif p.is_dir(): |
| shutil.rmtree(p, ignore_errors=True) |
| except Exception as e: |
| logger.warning(f"์ ๋ฆฌ ์คํจ: {path}, ์ค๋ฅ: {e}") |
|
|
|
|
| @app.post("/process-file/") |
| async def process_file( |
| file: UploadFile = File(...), |
| background_tasks: BackgroundTasks = None, |
| highqual: bool = False, |
| use_large_model: bool = False |
| ): |
| """ |
| ์
๋ก๋๋ ํ์ผ์ ๋ณตํธํํ๊ณ , Chandra OCR 2๋ก ๋ ์ด์์ ๋ถ์์ ์ํํ์ฌ |
| ๋งํฌ๋ค์ด, JSON, ์๊ฐํ ์ด๋ฏธ์ง ๋ฑ์ ํฌํจํ ๊ฒฐ๊ณผ๋ฅผ ๋ฐํํฉ๋๋ค. |
| |
| Args: |
| file: ์
๋ก๋๋ ํ์ผ (PDF, Office, ์ด๋ฏธ์ง ๋ฑ) |
| background_tasks: ๋น๋๊ธฐ ์ ๋ฆฌ ์์
|
| highqual: ๊ณ ํด์๋ ์ฒ๋ฆฌ ์ฌ๋ถ |
| use_large_model: (ํธํ์ฉ, Chandra์์๋ ๋ฌด์๋จ) |
| """ |
| content_type = file.content_type |
| filename = file.filename |
| job_id = str(uuid.uuid4()) |
|
|
| |
| if content_type not in ALLOWED_TYPES: |
| raise HTTPException(status_code=400, detail=f"์ง์ํ์ง ์๋ ํ์: {content_type}") |
|
|
| content = await file.read() |
| if len(content) > MAX_FILE_SIZE: |
| raise HTTPException(status_code=413, detail="ํ์ผ ํฌ๊ธฐ ์ด๊ณผ (10MB ์ ํ)") |
|
|
| upload_path = UPLOAD_DIR / f"{job_id}_{filename}" |
|
|
| try: |
| |
| async with aiofiles.open(upload_path, "wb") as f: |
| await f.write(content) |
| logger.info(f"[{job_id}] ์
๋ก๋ ์๋ฃ: {filename}") |
|
|
| |
| result = await async_process_document( |
| file_path=str(upload_path), |
| job_id=job_id, |
| highqual=highqual, |
| use_large_model=use_large_model |
| ) |
|
|
| if result["status"] == "error": |
| logger.error(f"[{job_id}] ๋ฌธ์ ์ฒ๋ฆฌ ์คํจ: {result['message']}") |
| raise HTTPException(status_code=500, detail=result["message"]) |
|
|
| |
| temp_dir = result.get("temp_dir") |
| if temp_dir and background_tasks: |
| background_tasks.add_task(cleanup_path, temp_dir) |
|
|
| |
| response = { |
| "job_id": job_id, |
| "filename": filename, |
| "status": "processed", |
| "message": result["message"], |
| "original_path": str(upload_path), |
| "decrypted_path": result["decrypted_path"], |
| "decrypted_filename": result["decrypted_filename"], |
| "temp_dir": None, |
| "full_markdown": result["full_markdown"], |
| "filtered_markdown": result["filtered_markdown"], |
| "page_markdowns": result["page_markdowns"], |
| "page_processed_images": result["page_processed_images"], |
| "json": result["json"], |
| "total_pages": result["total_pages"] |
| } |
|
|
| logger.info(f"[{job_id}] ์ฒ๋ฆฌ ์๋ฃ (Chandra OCR 2)") |
| return JSONResponse(response) |
|
|
| except HTTPException: |
| raise |
| except Exception as e: |
| logger.error(f"[{job_id}] ๋ด๋ถ ์ค๋ฅ: {e}", exc_info=True) |
| raise HTTPException(status_code=500, detail="๋ด๋ถ ์๋ฒ ์ค๋ฅ") |
| finally: |
| if background_tasks: |
| background_tasks.add_task(cleanup_path, str(upload_path)) |
|
|
|
|
| @app.get("/") |
| async def root(): |
| return { |
| "message": "SiLIX Document Intelligence API ์ด์ ์ค", |
| "version": "2.0.0", |
| "engine": "Chandra OCR 2", |
| "endpoints": { |
| "POST /process-file/": "๋ฌธ์ ๋ณตํธํ ๋ฐ ๋ ์ด์์ ๋ถ์ (์ด๋ฏธ์ง ํฌํจ)", |
| "Query Parameters": { |
| "highqual": "bool, ๊ณ ํด์๋ ์ฒ๋ฆฌ ์ฌ๋ถ (๊ธฐ๋ณธ: False)", |
| "use_large_model": "bool, (ํธํ์ฉ, Chandra์์๋ ๋ฌด์๋จ)" |
| } |
| }, |
| "response_fields": { |
| "full_markdown": "Base64 ์ธ์ฝ๋ฉ๋ ์ด๋ฏธ์ง ํฌํจ ๋งํฌ๋ค์ด", |
| "filtered_markdown": "์ด๋ฏธ์ง ์๋ฆฌ์ '[์ด๋ฏธ์ง]'๋ง ํ์๋ ๋งํฌ๋ค์ด", |
| "page_markdowns": "๊ฐ ํ์ด์ง์ ์์ ํ
์คํธ ๊ธฐ๋ฐ ๋งํฌ๋ค์ด ๋ฆฌ์คํธ", |
| "page_processed_images": "๊ฐ ํ์ด์ง์ ๋ ์ด์์ ๋ฐ์ค๊ฐ ์ค๋ฒ๋ ์ด๋ ์ด๋ฏธ์ง (Base64, PNG)" |
| } |
| } |
|
|