"use client"; import { useEffect, useState } from "react"; import { useTranslation } from "../../lib/i18n/context"; import { StatusBadge, Badge, ProgressBar } from "./ui"; interface ProgressEvent { job_id: string; status: string; current_file: string | null; progress_percent: number | null; processed_files: number | null; total_files: number | null; stats_json: { scanned_files: number; indexed_files: number; removed_files: number; errors: number; } | null; } interface JobProgressProps { jobId: string; onComplete?: () => void; } export function JobProgress({ jobId, onComplete }: JobProgressProps) { const { t } = useTranslation(); const [progress, setProgress] = useState(null); const [error, setError] = useState(null); const [isComplete, setIsComplete] = useState(false); useEffect(() => { const eventSource = new EventSource(`/api/jobs/${jobId}/stream`); eventSource.onmessage = (event) => { try { const data = JSON.parse(event.data); const progressData: ProgressEvent = { job_id: data.id, status: data.status, current_file: data.current_file, progress_percent: data.progress_percent, processed_files: data.processed_files, total_files: data.total_files, stats_json: data.stats_json, }; setProgress(progressData); if (data.status === "success" || data.status === "failed" || data.status === "cancelled") { setIsComplete(true); eventSource.close(); onComplete?.(); } } catch (err) { setError(t("jobProgress.sseError")); } }; eventSource.onerror = (err) => { console.error("SSE error:", err); eventSource.close(); setError(t("jobProgress.connectionLost")); }; return () => { eventSource.close(); }; }, [jobId, onComplete, t]); if (error) { return (
{t("jobProgress.error", { message: error })}
); } if (!progress) { return (
{t("jobProgress.loadingProgress")}
); } const percent = progress.progress_percent ?? 0; const processed = progress.processed_files ?? 0; const total = progress.total_files ?? 0; const isPhase2 = progress.status === "extracting_pages" || progress.status === "generating_thumbnails"; const unitLabel = progress.status === "extracting_pages" ? t("jobProgress.pages") : progress.status === "generating_thumbnails" ? t("jobProgress.thumbnails") : t("jobProgress.filesUnit"); return (
{isComplete && ( {t("jobProgress.done")} )}
{processed} / {total} {unitLabel} {progress.current_file && ( {t("jobProgress.currentFile", { file: progress.current_file.length > 40 ? progress.current_file.substring(0, 40) + "..." : progress.current_file })} )}
{progress.stats_json && !isPhase2 && (
{t("jobProgress.scanned", { count: progress.stats_json.scanned_files })} {t("jobProgress.indexed", { count: progress.stats_json.indexed_files })} {t("jobProgress.removed", { count: progress.stats_json.removed_files })} {progress.stats_json.errors > 0 && ( {t("jobProgress.errors", { count: progress.stats_json.errors })} )}
)}
); }