From 6893ea132da1812783616cfaad334fdbf575d1c0 Mon Sep 17 00:00:00 2001 From: Hyungi Ahn Date: Fri, 3 Apr 2026 12:31:57 +0900 Subject: [PATCH] =?UTF-8?q?refactor:=20preview=20=EB=B3=91=EB=A0=AC=20?= =?UTF-8?q?=ED=8A=B8=EB=A6=AC=EA=B1=B0=20+=20=ED=8C=8C=EC=9D=BC=20?= =?UTF-8?q?=EC=9D=B4=EB=8F=99=20=EC=A0=9C=EA=B1=B0=20+=20domain=20?= =?UTF-8?q?=EC=83=89=EC=83=81=20=EB=B0=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - queue_consumer: extract 완료 시 classify + preview 동시 등록 - classify_worker: _move_to_knowledge() 제거, 파일 원본 위치 유지 - DocumentCard: 좌측 domain별 색상 바 (4px) 추가 Co-Authored-By: Claude Opus 4.6 (1M context) --- app/workers/classify_worker.py | 36 ++----------------- app/workers/queue_consumer.py | 33 ++++++++--------- .../src/lib/components/DocumentCard.svelte | 20 ++++++++++- 3 files changed, 38 insertions(+), 51 deletions(-) diff --git a/app/workers/classify_worker.py b/app/workers/classify_worker.py index ae16441..cd2aeb4 100644 --- a/app/workers/classify_worker.py +++ b/app/workers/classify_worker.py @@ -1,6 +1,5 @@ """AI 분류 워커 — Qwen3.5로 도메인/태그/요약 생성 + Inbox→Knowledge 이동""" -import shutil from datetime import datetime, timezone from pathlib import Path @@ -70,9 +69,7 @@ async def process(document_id: int, session: AsyncSession) -> None: doc.ai_model_version = "qwen3.5-35b-a3b" doc.ai_processed_at = datetime.now(timezone.utc) - # ─── Inbox → Knowledge 폴더 이동 ─── - if doc.file_path.startswith("PKM/Inbox/") and domain: - _move_to_knowledge(doc, domain) + # 파일은 원본 위치 유지 (물리 이동 없음, DB 메타데이터만 관리) logger.info( f"[분류] document_id={document_id}: " @@ -83,33 +80,4 @@ async def process(document_id: int, session: AsyncSession) -> None: await client.close() -def _move_to_knowledge(doc: Document, domain: str): - """분류 완료 후 Inbox에서 Knowledge 폴더로 파일 이동""" - nas_root = Path(settings.nas_mount_path) - src = nas_root / doc.file_path - - if not src.exists(): - logger.warning(f"[이동] 원본 파일 없음: {src}") - return - - # 대상 경로: PKM/{domain}/{파일명} - sub_group = doc.ai_sub_group - if sub_group: - new_rel = f"PKM/{domain}/{sub_group}/{src.name}" - else: - new_rel = f"PKM/{domain}/{src.name}" - - dst = nas_root / new_rel - dst.parent.mkdir(parents=True, exist_ok=True) - - # 중복 파일명 처리 - counter = 1 - stem, suffix = dst.stem, dst.suffix - while dst.exists(): - dst = dst.parent / f"{stem}_{counter}{suffix}" - new_rel = str(dst.relative_to(nas_root)) - counter += 1 - - shutil.move(str(src), str(dst)) - doc.file_path = new_rel - logger.info(f"[이동] {doc.file_path} → {new_rel}") + # _move_to_knowledge 제거됨 — 파일은 원본 위치 유지, 분류는 DB 메타데이터만 diff --git a/app/workers/queue_consumer.py b/app/workers/queue_consumer.py index 589349e..e31951a 100644 --- a/app/workers/queue_consumer.py +++ b/app/workers/queue_consumer.py @@ -34,27 +34,28 @@ 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", "embed": "preview"} - next_stage = next_stages.get(current_stage) - if not next_stage: + next_stages = {"extract": ["classify", "preview"], "classify": ["embed"]} + stages = next_stages.get(current_stage, []) + if not stages: return async with async_session() as session: - existing = await session.execute( - select(ProcessingQueue).where( - ProcessingQueue.document_id == document_id, - ProcessingQueue.stage == next_stage, - ProcessingQueue.status.in_(["pending", "processing"]), + for next_stage in stages: + existing = await session.execute( + select(ProcessingQueue).where( + ProcessingQueue.document_id == document_id, + ProcessingQueue.stage == next_stage, + ProcessingQueue.status.in_(["pending", "processing"]), + ) ) - ) - if existing.scalar_one_or_none(): - return + if existing.scalar_one_or_none(): + continue - session.add(ProcessingQueue( - document_id=document_id, - stage=next_stage, - status="pending", - )) + session.add(ProcessingQueue( + document_id=document_id, + stage=next_stage, + status="pending", + )) await session.commit() diff --git a/frontend/src/lib/components/DocumentCard.svelte b/frontend/src/lib/components/DocumentCard.svelte index 2530d17..2b4f95c 100644 --- a/frontend/src/lib/components/DocumentCard.svelte +++ b/frontend/src/lib/components/DocumentCard.svelte @@ -23,6 +23,18 @@ return `${(bytes / 1048576).toFixed(1)}MB`; } + const DOMAIN_COLORS = { + 'Knowledge/Philosophy': 'var(--domain-philosophy)', + 'Knowledge/Language': 'var(--domain-language)', + 'Knowledge/Engineering': 'var(--domain-engineering)', + 'Knowledge/Industrial_Safety': 'var(--domain-safety)', + 'Knowledge/Programming': 'var(--domain-programming)', + 'Knowledge/General': 'var(--domain-general)', + 'Reference': 'var(--domain-reference)', + }; + + let domainColor = $derived(DOMAIN_COLORS[doc.ai_domain] || 'var(--border)'); + function handleClick() { // 모바일에서는 항상 detail 페이지로 이동 if (window.innerWidth < 1024) { @@ -39,9 +51,14 @@