feat: Markdown 편집기 + PDF 변환 파이프라인 + 뷰어 포맷 분기

- Markdown split editor: textarea + marked preview, Ctrl+S 저장
- PUT /api/documents/{id}/content: 원본 파일 저장 + extracted_text 갱신
- GET /api/documents/{id}/preview: PDF 미리보기 캐시 서빙
- preview_worker: LibreOffice headless → PDF 변환 (timeout 60s, retry 1회)
- queue_consumer: preview stage 추가 (embed 후 자동 트리거)
- DocumentViewer: 포맷별 분기 (markdown/pdf/preview-pdf/image/text/cad)
- 오피스/CAD 문서: 새 탭 편집 버튼
- Dockerfile: LibreOffice headless 설치
- migration 005: preview_status, preview_hash, preview_at 컬럼

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Hyungi Ahn
2026-04-03 10:10:03 +09:00
parent 3546c8cefb
commit 4bea408bbd
7 changed files with 354 additions and 45 deletions

View File

@@ -11,7 +11,7 @@ from models.queue import ProcessingQueue
logger = setup_logger("queue_consumer")
# stage별 배치 크기
BATCH_SIZE = {"extract": 5, "classify": 3, "embed": 1}
BATCH_SIZE = {"extract": 5, "classify": 3, "embed": 1, "preview": 2}
STALE_THRESHOLD_MINUTES = 10
@@ -34,7 +34,7 @@ async def reset_stale_items():
async def enqueue_next_stage(document_id: int, current_stage: str):
"""현재 stage 완료 후 다음 stage를 pending으로 등록"""
next_stages = {"extract": "classify", "classify": "embed"}
next_stages = {"extract": "classify", "classify": "embed", "embed": "preview"}
next_stage = next_stages.get(current_stage)
if not next_stage:
return
@@ -63,11 +63,13 @@ async def consume_queue():
from workers.classify_worker import process as classify_process
from workers.embed_worker import process as embed_process
from workers.extract_worker import process as extract_process
from workers.preview_worker import process as preview_process
workers = {
"extract": extract_process,
"classify": classify_process,
"embed": embed_process,
"preview": preview_process,
}
await reset_stale_items()