feat(library): Phase 2B 문서 상세 facet 편집 + 업로드 facet 전달
FileInfoView에 회사/주제/연도/문서유형 select 4개 추가. facet 옵션은 /api/library/facets에서 로드, 세션 캐시. 업로드 엔드포인트에 facet Form 파라미터 4개 추가. 업로드 시 현재 선택 facet 자동 전달 + 미리보기 텍스트. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -422,6 +422,10 @@ async def upload_document(
|
||||
session: Annotated[AsyncSession, Depends(get_session)],
|
||||
doc_purpose: str | None = Form(None, description="business | knowledge"),
|
||||
library_path: str | None = Form(None, description="자료실 경로 (자동 @library/ 태깅)"),
|
||||
facet_company: str | None = Form(None),
|
||||
facet_topic: str | None = Form(None),
|
||||
facet_year: int | None = Form(None),
|
||||
facet_doctype: str | None = Form(None),
|
||||
):
|
||||
"""파일 업로드 → Inbox 저장 + DB 등록 + 처리 큐 등록"""
|
||||
from core.library import DEFAULT_LIBRARY_PATH, LIBRARY_PREFIX, normalize_library_path
|
||||
@@ -490,6 +494,10 @@ async def upload_document(
|
||||
source_channel="manual",
|
||||
doc_purpose=doc_purpose,
|
||||
user_tags=[library_tag] if library_tag else [],
|
||||
facet_company=facet_company or None,
|
||||
facet_topic=facet_topic or None,
|
||||
facet_year=facet_year,
|
||||
facet_doctype=facet_doctype or None,
|
||||
)
|
||||
session.add(doc)
|
||||
await session.flush()
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
<script>
|
||||
// Phase E.1 — 파일 메타 정보 read-only 표시.
|
||||
// 파일 메타 정보 + facet 편집
|
||||
import { onMount } from 'svelte';
|
||||
import { api } from '$lib/api';
|
||||
import { addToast } from '$lib/stores/toast';
|
||||
|
||||
let { doc } = $props();
|
||||
|
||||
// facet 옵션 (세션 내 1회 로드)
|
||||
let facetOptions = $state({ company: [], topic: [], doctype: [], year: [] });
|
||||
|
||||
function formatDate(dateStr) {
|
||||
if (!dateStr) return '-';
|
||||
return new Date(dateStr).toLocaleDateString('ko-KR', {
|
||||
@@ -21,6 +25,12 @@
|
||||
return `${(bytes / 1048576).toFixed(1)}MB`;
|
||||
}
|
||||
|
||||
onMount(async () => {
|
||||
try {
|
||||
facetOptions = await api('/library/facets');
|
||||
} catch { /* 옵션 로딩 실패해도 수동 입력 가능 */ }
|
||||
});
|
||||
|
||||
async function updatePurpose(e) {
|
||||
const val = e.target.value || null;
|
||||
const prev = doc.doc_purpose;
|
||||
@@ -36,6 +46,21 @@
|
||||
addToast('error', '용도 변경 실패');
|
||||
}
|
||||
}
|
||||
|
||||
async function updateFacet(field, value) {
|
||||
const prev = doc[field];
|
||||
doc[field] = value;
|
||||
try {
|
||||
await api(`/documents/${doc.id}`, {
|
||||
method: 'PATCH',
|
||||
body: JSON.stringify({ [field]: value }),
|
||||
});
|
||||
addToast('success', '변경됨');
|
||||
} catch {
|
||||
doc[field] = prev;
|
||||
addToast('error', '변경 실패');
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div>
|
||||
@@ -82,4 +107,69 @@
|
||||
<dd class="text-text">{formatDate(doc.created_at)}</dd>
|
||||
</div>
|
||||
</dl>
|
||||
|
||||
<!-- Facet 메타데이터 -->
|
||||
<h4 class="text-xs font-semibold text-dim uppercase mt-3 mb-1.5">탐색 축</h4>
|
||||
<dl class="space-y-1.5 text-xs">
|
||||
<div class="flex justify-between items-center">
|
||||
<dt class="text-dim">회사</dt>
|
||||
<dd>
|
||||
<select
|
||||
value={doc.facet_company || ''}
|
||||
onchange={(e) => updateFacet('facet_company', e.target.value || null)}
|
||||
class="bg-bg border border-default rounded px-1 py-0.5 text-xs text-text outline-none focus:border-accent"
|
||||
>
|
||||
<option value="">-</option>
|
||||
{#each facetOptions.company || [] as v}
|
||||
<option value={v}>{v}</option>
|
||||
{/each}
|
||||
</select>
|
||||
</dd>
|
||||
</div>
|
||||
<div class="flex justify-between items-center">
|
||||
<dt class="text-dim">주제</dt>
|
||||
<dd>
|
||||
<select
|
||||
value={doc.facet_topic || ''}
|
||||
onchange={(e) => updateFacet('facet_topic', e.target.value || null)}
|
||||
class="bg-bg border border-default rounded px-1 py-0.5 text-xs text-text outline-none focus:border-accent"
|
||||
>
|
||||
<option value="">-</option>
|
||||
{#each facetOptions.topic || [] as v}
|
||||
<option value={v}>{v}</option>
|
||||
{/each}
|
||||
</select>
|
||||
</dd>
|
||||
</div>
|
||||
<div class="flex justify-between items-center">
|
||||
<dt class="text-dim">연도</dt>
|
||||
<dd>
|
||||
<select
|
||||
value={doc.facet_year ?? ''}
|
||||
onchange={(e) => updateFacet('facet_year', e.target.value ? Number(e.target.value) : null)}
|
||||
class="bg-bg border border-default rounded px-1 py-0.5 text-xs text-text outline-none focus:border-accent"
|
||||
>
|
||||
<option value="">-</option>
|
||||
{#each facetOptions.year || [] as v}
|
||||
<option value={v}>{v}</option>
|
||||
{/each}
|
||||
</select>
|
||||
</dd>
|
||||
</div>
|
||||
<div class="flex justify-between items-center">
|
||||
<dt class="text-dim">문서유형</dt>
|
||||
<dd>
|
||||
<select
|
||||
value={doc.facet_doctype || ''}
|
||||
onchange={(e) => updateFacet('facet_doctype', e.target.value || null)}
|
||||
class="bg-bg border border-default rounded px-1 py-0.5 text-xs text-text outline-none focus:border-accent"
|
||||
>
|
||||
<option value="">-</option>
|
||||
{#each facetOptions.doctype || [] as v}
|
||||
<option value={v}>{v}</option>
|
||||
{/each}
|
||||
</select>
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
|
||||
@@ -287,6 +287,10 @@
|
||||
formData.append('file', file);
|
||||
formData.append('doc_purpose', 'business');
|
||||
formData.append('library_path', activePath || DEFAULT_LIBRARY_PATH);
|
||||
if (activeFacetCompany) formData.append('facet_company', activeFacetCompany);
|
||||
if (activeFacetTopic) formData.append('facet_topic', activeFacetTopic);
|
||||
if (activeFacetYear) formData.append('facet_year', activeFacetYear);
|
||||
if (activeFacetDoctype) formData.append('facet_doctype', activeFacetDoctype);
|
||||
try {
|
||||
await api('/documents/', { method: 'POST', body: formData });
|
||||
success++;
|
||||
@@ -606,6 +610,18 @@
|
||||
{uploadingCount > 0 ? `업로드 중 (${uploadingCount})` : '업로드'}
|
||||
</Button>
|
||||
|
||||
<!-- 업로드 시 적용될 facet 미리보기 -->
|
||||
{#if hasAnyFacet}
|
||||
<span class="text-[10px] text-faint">
|
||||
적용: {[
|
||||
activeFacetCompany && `회사:${activeFacetCompany}`,
|
||||
activeFacetTopic && `주제:${activeFacetTopic}`,
|
||||
activeFacetYear && `연도:${activeFacetYear}`,
|
||||
activeFacetDoctype && `유형:${activeFacetDoctype}`,
|
||||
].filter(Boolean).join(' / ')}
|
||||
</span>
|
||||
{/if}
|
||||
|
||||
<!-- 정렬 -->
|
||||
<select
|
||||
value={activeSort}
|
||||
|
||||
Reference in New Issue
Block a user