63 lines
1.8 KiB
Svelte
63 lines
1.8 KiB
Svelte
<script lang="ts">
|
|
import type { IndexingJob } from '$lib/types';
|
|
|
|
let { jobId, oncomplete }: { jobId: string; oncomplete?: () => void } = $props();
|
|
|
|
let job = $state<IndexingJob | null>(null);
|
|
|
|
$effect(() => {
|
|
job = null;
|
|
const es = new EventSource(`/api/v1/jobs/${jobId}/stream`);
|
|
|
|
es.addEventListener('job-progress', (event) => {
|
|
const data = JSON.parse(event.data);
|
|
job = { ...job, ...data } as IndexingJob;
|
|
});
|
|
|
|
es.addEventListener('job-done', () => {
|
|
void fetch(`/api/v1/jobs/${jobId}`)
|
|
.then(r => r.json())
|
|
.then(d => { job = d.job; oncomplete?.(); });
|
|
es.close();
|
|
});
|
|
|
|
es.addEventListener('job-failed', (event) => {
|
|
const data = JSON.parse(event.data);
|
|
if (job) job = { ...job, status: 'failed', error: data.error ?? 'Unknown error' } as IndexingJob;
|
|
oncomplete?.();
|
|
es.close();
|
|
});
|
|
|
|
es.onerror = () => {
|
|
es.close();
|
|
void fetch(`/api/v1/jobs/${jobId}`).then(r => r.json()).then(d => { job = d.job; });
|
|
};
|
|
|
|
return () => es.close();
|
|
});
|
|
|
|
const progress = $derived(job?.progress ?? 0);
|
|
const processedFiles = $derived(job?.processedFiles ?? 0);
|
|
const totalFiles = $derived(job?.totalFiles ?? 0);
|
|
</script>
|
|
|
|
{#if job}
|
|
<div class="mt-2">
|
|
<div class="flex justify-between text-xs text-gray-500">
|
|
<span>{processedFiles.toLocaleString()} / {totalFiles.toLocaleString()} files</span>
|
|
<span>{progress}%</span>
|
|
</div>
|
|
<div class="mt-1 h-1.5 w-full rounded-full bg-gray-200">
|
|
<div
|
|
class="h-1.5 rounded-full bg-blue-600 transition-all duration-300"
|
|
style="width: {progress}%"
|
|
></div>
|
|
</div>
|
|
{#if job.status === 'done'}
|
|
<p class="mt-1 text-xs text-green-600">Indexing complete.</p>
|
|
{:else if job.status === 'failed'}
|
|
<p class="mt-1 text-xs text-red-600">{job.error ?? 'Indexing failed.'}</p>
|
|
{/if}
|
|
</div>
|
|
{/if}
|