feat(admin): add OCR training card to admin/system page
- TrainingHistory.svelte: responsive table with status badges (green/red/animated pulse), keyed iteration, empty-state row - OcrTrainingCard.svelte: shows available blocks/docs, disabled states (< 5 blocks, service down), in-flight "…" state, 5s success message, embeds TrainingHistory - Wired into admin/system/+page.svelte via fetchTrainingInfo() in $effect - Regenerated api.ts with OcrTrainingRun + TrainingInfoResponse types - TRAINING_ALREADY_RUNNING error code in errors.ts + de/en/es translations - 7 OcrTrainingCard Vitest tests Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
76
frontend/src/lib/components/TrainingHistory.svelte
Normal file
76
frontend/src/lib/components/TrainingHistory.svelte
Normal file
@@ -0,0 +1,76 @@
|
||||
<script lang="ts">
|
||||
interface Run {
|
||||
id: string;
|
||||
status: 'RUNNING' | 'DONE' | 'FAILED';
|
||||
blockCount: number;
|
||||
documentCount: number;
|
||||
modelName: string;
|
||||
errorMessage?: string;
|
||||
triggeredBy?: string;
|
||||
createdAt: string;
|
||||
completedAt?: string;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
runs: Run[];
|
||||
}
|
||||
|
||||
let { runs }: Props = $props();
|
||||
|
||||
const dateFormatter = new Intl.DateTimeFormat('de-DE', {
|
||||
day: 'numeric',
|
||||
month: 'short',
|
||||
year: 'numeric'
|
||||
});
|
||||
|
||||
function formatDate(iso: string): string {
|
||||
return dateFormatter.format(new Date(iso));
|
||||
}
|
||||
</script>
|
||||
|
||||
<table class="w-full text-sm">
|
||||
<thead>
|
||||
<tr class="border-b border-line text-xs font-bold tracking-widest text-ink-3 uppercase">
|
||||
<th class="pb-2 text-left">Datum</th>
|
||||
<th class="pb-2 text-left">Status</th>
|
||||
<th class="pb-2 text-right">Blöcke</th>
|
||||
<th class="hidden pb-2 text-right md:table-cell">Dokumente</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#if runs.length === 0}
|
||||
<tr>
|
||||
<td colspan="4" class="py-4 text-center text-sm text-ink-2">
|
||||
Noch keine Trainings-Läufe.
|
||||
</td>
|
||||
</tr>
|
||||
{:else}
|
||||
{#each runs as run (run.id)}
|
||||
<tr class="border-b border-line/50 last:border-0">
|
||||
<td class="py-2 text-ink-2">{formatDate(run.createdAt)}</td>
|
||||
<td class="py-2">
|
||||
{#if run.status === 'DONE'}
|
||||
<span
|
||||
class="inline-flex items-center gap-1 rounded-sm bg-green-100 px-1.5 py-0.5 text-xs font-medium text-green-700"
|
||||
>✓ Fertig</span
|
||||
>
|
||||
{:else if run.status === 'FAILED'}
|
||||
<span
|
||||
class="inline-flex items-center gap-1 rounded-sm bg-red-100 px-1.5 py-0.5 text-xs font-medium text-red-700"
|
||||
title={run.errorMessage}>✗ Fehler</span
|
||||
>
|
||||
{:else}
|
||||
<span
|
||||
class="inline-flex items-center gap-1 rounded-sm bg-yellow-100 px-1.5 py-0.5 text-xs font-medium text-yellow-700"
|
||||
><span class="h-1.5 w-1.5 animate-pulse rounded-full bg-yellow-500"
|
||||
></span>Läuft…</span
|
||||
>
|
||||
{/if}
|
||||
</td>
|
||||
<td class="py-2 text-right text-ink-2">{run.blockCount}</td>
|
||||
<td class="hidden py-2 text-right text-ink-2 md:table-cell">{run.documentCount}</td>
|
||||
</tr>
|
||||
{/each}
|
||||
{/if}
|
||||
</tbody>
|
||||
</table>
|
||||
Reference in New Issue
Block a user