feat(admin): OCR admin pages — overview & model detail #265

Merged
marcel merged 53 commits from feat/issue-264-ocr-admin-pages into main 2026-04-18 12:38:42 +02:00
3 changed files with 88 additions and 72 deletions
Showing only changes of commit 8128769feb - Show all commits

View File

@@ -9,39 +9,41 @@ let { data }: { data: PageData } = $props();
const { trainingInfo } = $derived(data);
</script>
<div class="flex flex-col gap-6 p-6">
<!-- Page title + health bar -->
<div class="flex items-center justify-between">
<h1 class="font-sans text-lg font-bold tracking-widest text-brand-navy uppercase">
{m.admin_tab_ocr()}
</h1>
<OcrHealthBar ocrServiceAvailable={trainingInfo.ocrServiceAvailable ?? false} />
</div>
<!-- Stats -->
<OcrStatCards
availableBlocks={trainingInfo.availableBlocks ?? 0}
totalOcrBlocks={trainingInfo.totalOcrBlocks ?? 0}
availableDocuments={trainingInfo.availableDocuments ?? 0}
availableSegBlocks={trainingInfo.availableSegBlocks ?? 0}
/>
<!-- Sender models -->
<div>
<div class="mb-3 flex items-center justify-between">
<h2 class="text-xs font-bold tracking-widest text-gray-400 uppercase">
{m.ocr_sender_models_heading()}
</h2>
<a
href="/admin/ocr/global"
class="text-xs font-medium text-brand-navy/60 transition-colors hover:text-brand-navy focus-visible:rounded-sm focus-visible:ring-2 focus-visible:ring-brand-navy focus-visible:outline-none"
>
{m.ocr_global_history_link()}
</a>
<div class="flex-1 overflow-y-auto p-6">
<div class="mx-auto flex max-w-4xl flex-col gap-6">
<!-- Page title + health bar -->
<div class="flex items-center justify-between">
<h1 class="font-sans text-lg font-bold tracking-widest text-brand-navy uppercase">
{m.admin_tab_ocr()}
</h1>
<OcrHealthBar ocrServiceAvailable={trainingInfo.ocrServiceAvailable ?? false} />
</div>
<OcrModelsTable
senderModels={trainingInfo.senderModels ?? []}
personNames={trainingInfo.personNames ?? {}}
<!-- Stats -->
<OcrStatCards
availableBlocks={trainingInfo.availableBlocks ?? 0}
totalOcrBlocks={trainingInfo.totalOcrBlocks ?? 0}
availableDocuments={trainingInfo.availableDocuments ?? 0}
availableSegBlocks={trainingInfo.availableSegBlocks ?? 0}
/>
<!-- Sender models -->
<div>
<div class="mb-3 flex items-center justify-between">
<h2 class="text-xs font-bold tracking-widest text-gray-400 uppercase">
{m.ocr_sender_models_heading()}
</h2>
<a
href="/admin/ocr/global"
class="text-xs font-medium text-brand-navy/60 transition-colors hover:text-brand-navy focus-visible:rounded-sm focus-visible:ring-2 focus-visible:ring-brand-navy focus-visible:outline-none"
>
{m.ocr_global_history_link()}
</a>
</div>
<OcrModelsTable
senderModels={trainingInfo.senderModels ?? []}
personNames={trainingInfo.personNames ?? {}}
/>
</div>
</div>
</div>

View File

@@ -7,26 +7,33 @@ let { data }: { data: PageData } = $props();
const personName = $derived(data.history.personNames?.[data.personId] ?? 'Unknown');
</script>
<div class="flex flex-col gap-6 p-6">
<div class="flex items-center gap-4">
<a
href="/admin/ocr"
class="group inline-flex items-center text-xs font-bold tracking-widest text-gray-500 uppercase transition-colors hover:text-brand-navy focus-visible:rounded-sm focus-visible:ring-2 focus-visible:ring-brand-navy focus-visible:outline-none"
>
<svg
class="mr-2 h-4 w-4 transform transition-transform group-hover:-translate-x-1"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
stroke-width="2"
><path stroke-linecap="round" stroke-linejoin="round" d="M15 19l-7-7 7-7" /></svg
<div class="flex-1 overflow-y-auto p-6">
<div class="mx-auto flex max-w-4xl flex-col gap-6">
<div class="flex items-center gap-4">
<a
href="/admin/ocr"
class="group inline-flex items-center text-xs font-bold tracking-widest text-gray-500 uppercase transition-colors hover:text-brand-navy focus-visible:rounded-sm focus-visible:ring-2 focus-visible:ring-brand-navy focus-visible:outline-none"
>
{m.admin_tab_ocr()}
</a>
<h1 class="font-sans text-lg font-bold tracking-widest text-brand-navy uppercase">
{personName}
</h1>
</div>
<svg
class="mr-2 h-4 w-4 transform transition-transform group-hover:-translate-x-1"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
stroke-width="2"
><path stroke-linecap="round" stroke-linejoin="round" d="M15 19l-7-7 7-7" /></svg
>
{m.admin_tab_ocr()}
</a>
<h1 class="font-sans text-lg font-bold tracking-widest text-brand-navy uppercase">
{personName}
</h1>
</div>
<TrainingHistory runs={data.history.runs ?? []} personNames={data.history.personNames ?? {}} />
<div class="border-brand-sand rounded-sm border bg-white p-6 shadow-sm">
<TrainingHistory
runs={data.history.runs ?? []}
personNames={data.history.personNames ?? {}}
/>
</div>
</div>
</div>

View File

@@ -6,26 +6,33 @@ import * as m from '$lib/paraglide/messages.js';
let { data }: { data: PageData } = $props();
</script>
<div class="flex flex-col gap-6 p-6">
<div class="flex items-center gap-4">
<a
href="/admin/ocr"
class="group inline-flex items-center text-xs font-bold tracking-widest text-gray-500 uppercase transition-colors hover:text-brand-navy focus-visible:rounded-sm focus-visible:ring-2 focus-visible:ring-brand-navy focus-visible:outline-none"
>
<svg
class="mr-2 h-4 w-4 transform transition-transform group-hover:-translate-x-1"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
stroke-width="2"
><path stroke-linecap="round" stroke-linejoin="round" d="M15 19l-7-7 7-7" /></svg
<div class="flex-1 overflow-y-auto p-6">
<div class="mx-auto flex max-w-4xl flex-col gap-6">
<div class="flex items-center gap-4">
<a
href="/admin/ocr"
class="group inline-flex items-center text-xs font-bold tracking-widest text-gray-500 uppercase transition-colors hover:text-brand-navy focus-visible:rounded-sm focus-visible:ring-2 focus-visible:ring-brand-navy focus-visible:outline-none"
>
{m.admin_tab_ocr()}
</a>
<h1 class="font-sans text-lg font-bold tracking-widest text-brand-navy uppercase">
{m.ocr_global_history_heading()}
</h1>
</div>
<svg
class="mr-2 h-4 w-4 transform transition-transform group-hover:-translate-x-1"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
stroke-width="2"
><path stroke-linecap="round" stroke-linejoin="round" d="M15 19l-7-7 7-7" /></svg
>
{m.admin_tab_ocr()}
</a>
<h1 class="font-sans text-lg font-bold tracking-widest text-brand-navy uppercase">
{m.ocr_global_history_heading()}
</h1>
</div>
<TrainingHistory runs={data.history.runs ?? []} personNames={data.history.personNames ?? {}} />
<div class="border-brand-sand rounded-sm border bg-white p-6 shadow-sm">
<TrainingHistory
runs={data.history.runs ?? []}
personNames={data.history.personNames ?? {}}
/>
</div>
</div>
</div>