diff --git a/frontend/src/lib/components/ProcessingFlowBoard.svelte b/frontend/src/lib/components/ProcessingFlowBoard.svelte index f2e80bf..f66bf77 100644 --- a/frontend/src/lib/components/ProcessingFlowBoard.svelte +++ b/frontend/src/lib/components/ProcessingFlowBoard.svelte @@ -246,8 +246,8 @@ {/each} - -
+ +
{#each mainNodes as n, i (n.def.key)} {#if i > 0} diff --git a/frontend/src/lib/utils/queueDisplay.ts b/frontend/src/lib/utils/queueDisplay.ts index 709d43f..a3f3eb0 100644 --- a/frontend/src/lib/utils/queueDisplay.ts +++ b/frontend/src/lib/utils/queueDisplay.ts @@ -37,10 +37,14 @@ export function etaPhrase(minutes: number): string { return `약 ${text}시간 후 소진 예상`; } -/** ETA 분 → 칩용 짧은 표기 ("약 4.6시간" / "약 12분") */ +/** ETA 분 → 칩용 짧은 표기 ("약 12분" / "약 4.6시간" / 48h+ = "약 5.5일") */ export function etaShort(minutes: number): string { if (minutes < 60) return `약 ${Math.max(1, Math.round(minutes))}분`; const hours = minutes / 60; + if (hours >= 48) { + const days = hours / 24; + return `약 ${days >= 10 ? Math.round(days) : Math.round(days * 10) / 10}일`; + } const text = hours >= 10 ? String(Math.round(hours)) : String(Math.round(hours * 10) / 10); return `약 ${text}시간`; } diff --git a/frontend/src/routes/+page.svelte b/frontend/src/routes/+page.svelte index 451bdae..e67eef8 100644 --- a/frontend/src/routes/+page.svelte +++ b/frontend/src/routes/+page.svelte @@ -19,7 +19,7 @@ import EmptyState from '$lib/components/ui/EmptyState.svelte'; import Skeleton from '$lib/components/ui/Skeleton.svelte'; import { - Scale, FileText, Pin, ChevronRight, GraduationCap, Upload, Newspaper, + Scale, FileText, Pin, GraduationCap, Upload, Newspaper, } from 'lucide-svelte'; import { addToast } from '$lib/stores/toast'; @@ -133,17 +133,6 @@ // 백엔드 미배포/실패 시 store=null → 보드 자체가 조용히 생략 (silent 비차단). let queue = $derived($queueOverview); - // 머신 담당 단계 라벨 — STAGE_LABEL 재사용 + overview 전용 단계 보강 - // (backend services/queue_overview.py _STAGE_ORDER 와 동기), 미지 키는 raw - const QUEUE_STAGE_LABEL: Record = { - ...STAGE_LABEL, - summarize: '요약', chunk: '청크', markdown: '마크다운', - fulltext: '전문', deep_summary: '심층분석', - }; - function queueStageLabel(stage: string): string { - return QUEUE_STAGE_LABEL[stage] ?? stage; - } - onMount(() => { void refreshQueueOverview(); const handle = setInterval(() => void refreshQueueOverview(), 30_000); @@ -191,35 +180,10 @@ let pipelineRows = $derived( summary ? buildPipelineRows(summary.pipeline_status, summary.queue_lag ?? []) : [] ); - let pipelineMax = $derived(Math.max(1, ...pipelineRows.map((r) => r.total))); let totalFailed = $derived(summary?.failed_count ?? 0); let totalPending = $derived(pipelineRows.reduce((s, r) => s + r.pending, 0)); let totalProcessing = $derived(pipelineRows.reduce((s, r) => s + r.processing, 0)); - let pipelineManualClosed = $state(false); - let pipelineOpen = $derived( - pipelineManualClosed ? false : (queue?.totals.failed ?? totalFailed) > 0 - ); - - // 단계별 현황 (2026-06-11 피드백 재설계: 완료가 보여야 한다 — overview.stages 단일 소스) - // active = 오늘 움직임이 있는 단계만, idle = 전부 0 인 단계는 한 줄로 숨김. - let stageRows = $derived(queue?.stages ?? []); - let activeStageRows = $derived( - stageRows.filter((r) => r.pending + r.processing + r.failed + r.done_today > 0) - ); - let idleStageRows = $derived( - stageRows.filter((r) => r.pending + r.processing + r.failed + r.done_today === 0) - ); - let stageDoneToday = $derived(stageRows.reduce((s, r) => s + r.done_today, 0)); - - function formatAge(sec: number | null): string { - if (sec == null || sec <= 0) return ''; - if (sec < 60) return `${sec}초 전`; - if (sec < 3600) return `${Math.floor(sec / 60)}분 전`; - if (sec < 86400) return `${Math.floor(sec / 3600)}시간 전`; - return `${Math.floor(sec / 86400)}일 전`; - } - function formatTime(dateStr: string) { const d = new Date(dateStr); if (isNaN(d.getTime())) return ''; // 빈 문자열/유효하지 않은 created_at → 'Invalid Date' 회피 @@ -463,80 +427,6 @@ {/if} - -
{ if (!e.currentTarget.open) pipelineManualClosed = true; }} - > - - - - 단계별 현황 - - - {#if queue} - {#if stageDoneToday > 0}오늘 {stageDoneToday.toLocaleString()} 완료{/if} - {#if queue.totals.failed > 0}실패 {queue.totals.failed}{/if} - {#if queue.totals.pending > 0}대기 {queue.totals.pending.toLocaleString()}{/if} - {#if stageDoneToday === 0 && queue.totals.failed === 0 && queue.totals.pending === 0}모든 단계 한가함{/if} - {:else} - {#if totalFailed > 0}실패 {totalFailed}{/if} - {#if totalPending > 0}대기 {totalPending}{/if} - {/if} - - - -
- {#if queue} - {#if activeStageRows.length > 0} -
- {#each activeStageRows as row (row.stage)} - {@const total = row.done_today + row.pending + row.processing} - {@const donePct = total > 0 ? (row.done_today / total) * 100 : 0} - {@const procPct = total > 0 ? (row.processing / total) * 100 : 0} -
-
- - {queueStageLabel(row.stage)} - {#if row.processing > 0} - - 처리 중 {row.processing} - {/if} - - - {#if row.done_today > 0}오늘 {row.done_today.toLocaleString()} 완료{/if} - {#if row.pending > 0}대기 {row.pending.toLocaleString()}{/if} - {#if row.failed > 0}실패 {row.failed}{/if} - -
- -
- {#if donePct > 0}
{/if} - {#if procPct > 0}
{/if} -
- {#if row.pending > 0 && row.oldest_pending_age_sec && row.oldest_pending_age_sec > 600} -

- 가장 오래 기다린 항목 {formatAge(row.oldest_pending_age_sec)} -

- {/if} -
- {/each} -
- {:else} -

대기·처리·실패 없음 — 모든 단계가 한가합니다

- {/if} - {#if idleStageRows.length > 0} -

- 비어 있음: {idleStageRows.map((r) => queueStageLabel(r.stage)).join(' · ')} -

- {/if} - {:else} -

현황을 불러오지 못했습니다

- {/if} -
-
- {/if}
@@ -548,7 +438,3 @@ {/snippet} -