Files
familienarchiv/frontend/src/lib/components/OcrProgress.svelte
Marcel 8c07779a91 fix(ocr): fix SSE retry to actually reconnect EventSource
The retry button set status='running' but didn't re-trigger the $effect
because jobId hadn't changed. Added retryCount state so the effect
re-runs and creates a fresh EventSource on retry. Also added aria-label
to the progress bar for accessibility.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 12:28:40 +02:00

92 lines
2.3 KiB
Svelte

<script lang="ts">
import { m } from '$lib/paraglide/messages.js';
interface Props {
jobId: string;
onDone: () => void;
}
let { jobId, onDone }: Props = $props();
let status: 'running' | 'done' | 'error' = $state('running');
let processed: number = $state(0);
let total: number = $state(0);
let currentPage: number = $state(0);
let totalPages: number = $state(0);
let retryCount: number = $state(0);
let progressPercent = $derived(total > 0 ? Math.round((processed / total) * 100) : 0);
$effect(() => {
void retryCount; // track dependency to re-create EventSource on retry
const source = new EventSource(`/api/ocr/jobs/${jobId}/progress`);
source.addEventListener('document', (e) => {
const data = JSON.parse(e.data);
processed = data.processed;
total = data.total;
});
source.addEventListener('page', (e) => {
const data = JSON.parse(e.data);
currentPage = data.page;
totalPages = data.totalPages;
});
source.addEventListener('done', () => {
status = 'done';
source.close();
onDone();
});
source.addEventListener('error', () => {
status = 'error';
source.close();
});
source.onerror = () => {
status = 'error';
source.close();
};
return () => {
source.close();
};
});
</script>
{#if status === 'running'}
<div class="border-brand-sand rounded-sm border bg-white p-4">
<h3 class="mb-3 text-xs font-bold tracking-widest text-gray-400 uppercase">
{m.ocr_progress_heading()}
</h3>
<div class="bg-brand-sand h-2 w-full overflow-hidden rounded-full">
<div
class="h-full bg-brand-mint transition-all duration-300"
style="width: {progressPercent}%"
role="progressbar"
aria-label={m.ocr_progress_heading()}
aria-valuenow={progressPercent}
aria-valuemin={0}
aria-valuemax={100}
></div>
</div>
<p class="mt-2 text-right text-sm text-gray-500">
{m.ocr_progress_page({ current: String(currentPage), total: String(totalPages) })}
</p>
</div>
{:else if status === 'error'}
<div class="border-brand-sand rounded-sm border border-l-4 border-l-red-500 bg-white p-4">
<h3 class="mb-2 text-sm font-semibold text-red-700">
{m.ocr_error_heading()}
</h3>
<button
type="button"
onclick={() => { retryCount++; status = 'running'; }}
class="text-sm font-medium text-brand-navy transition-colors hover:text-brand-navy/80"
>
{m.ocr_error_retry()}
</button>
</div>
{/if}