151 lines
5.1 KiB
Svelte
151 lines
5.1 KiB
Svelte
<script lang="ts">
|
|
import { m } from '$lib/paraglide/messages.js';
|
|
import type { TrainingRun } from '$lib/types/training.js';
|
|
|
|
interface Props {
|
|
runs: TrainingRun[];
|
|
personNames?: Record<string, string>;
|
|
}
|
|
|
|
let { runs, personNames }: Props = $props();
|
|
|
|
const COLLAPSED_COUNT = 3;
|
|
let expanded = $state(false);
|
|
|
|
const visibleRuns = $derived(expanded ? runs : runs.slice(0, COLLAPSED_COUNT));
|
|
const hasMore = $derived(runs.length > COLLAPSED_COUNT);
|
|
|
|
const dateFormatter = new Intl.DateTimeFormat('de-DE', {
|
|
day: 'numeric',
|
|
month: 'short',
|
|
year: 'numeric'
|
|
});
|
|
|
|
function formatDate(iso: string): string {
|
|
return dateFormatter.format(new Date(iso));
|
|
}
|
|
|
|
function formatCer(cer: number | undefined | null): string {
|
|
if (cer == null) return '—';
|
|
return (cer * 100).toFixed(1) + ' %';
|
|
}
|
|
</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">{m.training_history_col_date()}</th>
|
|
<th class="pb-2 text-left">{m.training_history_col_status()}</th>
|
|
<th class="hidden pb-2 text-left md:table-cell">{m.training_col_type()}</th>
|
|
<th class="hidden pb-2 text-left md:table-cell">{m.training_col_person()}</th>
|
|
<th class="pb-2 text-right">{m.training_history_col_blocks()}</th>
|
|
<th class="hidden pb-2 text-right md:table-cell">{m.training_history_col_docs()}</th>
|
|
<th class="hidden pb-2 text-right md:table-cell">{m.training_history_col_cer()}</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="training-history-rows">
|
|
{#if runs.length === 0}
|
|
<tr>
|
|
<td colspan="7" class="py-4 text-center text-sm text-ink-2">
|
|
{m.training_history_empty()}
|
|
</td>
|
|
</tr>
|
|
{:else}
|
|
{#each visibleRuns 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 === 'QUEUED'}
|
|
<span
|
|
class="inline-flex items-center gap-1 rounded-sm bg-amber-100 px-1.5 py-0.5 text-xs font-medium text-amber-700"
|
|
>
|
|
<span aria-hidden="true" class="h-1.5 w-1.5 rounded-full bg-amber-500"></span>
|
|
{m.training_status_queued()}
|
|
</span>
|
|
{:else 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"
|
|
>
|
|
<svg
|
|
aria-hidden="true"
|
|
class="h-3 w-3 shrink-0"
|
|
viewBox="0 0 20 20"
|
|
fill="currentColor"
|
|
>
|
|
<path
|
|
fill-rule="evenodd"
|
|
d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z"
|
|
clip-rule="evenodd"
|
|
/>
|
|
</svg>
|
|
{m.training_status_done()}
|
|
</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"
|
|
>
|
|
<svg
|
|
aria-hidden="true"
|
|
class="h-3 w-3 shrink-0"
|
|
viewBox="0 0 20 20"
|
|
fill="currentColor"
|
|
>
|
|
<path
|
|
fill-rule="evenodd"
|
|
d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z"
|
|
clip-rule="evenodd"
|
|
/>
|
|
</svg>
|
|
{m.training_status_failed()}
|
|
</span>
|
|
{#if run.errorMessage}
|
|
<details class="mt-0.5">
|
|
<summary class="cursor-pointer text-xs text-red-700 underline">
|
|
{m.training_error_detail_label()}
|
|
</summary>
|
|
<p class="mt-1 text-xs text-red-600">{run.errorMessage}</p>
|
|
</details>
|
|
{/if}
|
|
{: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
|
|
aria-hidden="true"
|
|
class="h-1.5 w-1.5 rounded-full bg-yellow-500 motion-safe:animate-pulse"
|
|
></span>
|
|
{m.training_status_running()}
|
|
</span>
|
|
{/if}
|
|
</td>
|
|
<td class="hidden py-2 text-left text-ink-2 md:table-cell">
|
|
{run.personId ? m.training_type_personalized() : m.training_type_base()}
|
|
</td>
|
|
<td class="hidden py-2 text-left text-ink-2 md:table-cell">
|
|
{run.personId && personNames?.[run.personId] ? personNames[run.personId] : '—'}
|
|
</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>
|
|
<td class="hidden py-2 text-right md:table-cell"
|
|
>{run.status === 'DONE' && run.cer != null ? formatCer(run.cer) : '—'}</td
|
|
>
|
|
</tr>
|
|
{/each}
|
|
{/if}
|
|
</tbody>
|
|
</table>
|
|
|
|
{#if hasMore}
|
|
<div class="mt-2 text-center">
|
|
<button
|
|
type="button"
|
|
aria-expanded={expanded}
|
|
aria-controls="training-history-rows"
|
|
class="text-xs font-medium text-ink-3 transition-colors hover:text-ink"
|
|
onclick={() => (expanded = !expanded)}
|
|
>
|
|
{expanded ? m.comp_expandable_show_less() : m.comp_expandable_show_more()}
|
|
</button>
|
|
</div>
|
|
{/if}
|