export function initProgressPanel(ctx) { const sseUrl = '/api/telemetry/stream'; let eventSource = null; function connect() { if (eventSource) return; eventSource = new EventSource(sseUrl); eventSource.onmessage = (event) => { try { const payload = JSON.parse(event.data); const { event: eventName, data } = payload; if (eventName === 'ingest_progress') { handleIngestProgress(data); } else if (eventName === 'sae_progress') { handleSaeProgress(data); } } catch (err) { console.error("Error parsing telemetry SSE event:", err); } }; eventSource.onerror = (err) => { console.warn("SSE connection error, closing EventSource:", err); if (eventSource) { eventSource.close(); eventSource = null; } // Auto reconnect after 3 seconds setTimeout(connect, 3000); }; } function handleIngestProgress(data) { const container = document.getElementById('relief-ingest-progress'); const phaseLabel = document.getElementById('relief-ingest-phase'); const fillBar = document.getElementById('relief-ingest-fill'); const subLabel = document.getElementById('relief-ingest-sublabel'); const pctLabel = document.getElementById('relief-ingest-pct'); const resultLabel = document.getElementById('relief-ingest-result'); const shield = document.getElementById('relief-ingest-shield'); if (!container) return; if (data.status === 'processing') { container.style.display = 'flex'; container.classList.add('active'); if (shield) shield.style.display = 'block'; if (fillBar) fillBar.style.width = `${data.progress}%`; if (pctLabel) pctLabel.textContent = `${data.progress}%`; let stageMsg = "Processing..."; if (data.stage === 'extract_text') { stageMsg = "Extracting text from PDF..."; if (subLabel && data.current && data.total) { subLabel.textContent = `Page ${data.current} / ${data.total}`; } } else if (data.stage === 'cleaning') { stageMsg = "Normalizing text..."; if (subLabel) subLabel.textContent = ""; } else if (data.stage === 'chunking') { stageMsg = "Splitting text into chunks..."; if (subLabel) subLabel.textContent = ""; } else if (data.stage === 'embeddings') { stageMsg = "Generating semantic embeddings..."; if (subLabel && data.current && data.total) { subLabel.textContent = `Chunk ${data.current} / ${data.total}`; } } else if (data.stage === 'storing') { stageMsg = "Storing in database..."; if (subLabel) subLabel.textContent = ""; } if (phaseLabel) phaseLabel.textContent = stageMsg; if (resultLabel) resultLabel.textContent = ""; } else if (data.status === 'complete') { if (fillBar) fillBar.style.width = '100%'; if (pctLabel) pctLabel.textContent = '100%'; if (phaseLabel) phaseLabel.textContent = "Ingestion complete!"; if (subLabel) subLabel.textContent = `${data.chunks} chunks generated`; if (resultLabel) { resultLabel.style.color = 'var(--color-semantic-success)'; resultLabel.textContent = `✅ Successfully indexed ${data.chunks} chunks.`; } setTimeout(() => { if (shield) shield.style.display = 'none'; container.style.display = 'none'; container.classList.remove('active'); if (resultLabel) resultLabel.textContent = ""; }, 3000); // Reload file selector list! if (ctx && ctx.refresh && typeof ctx.refresh.reliefFiles === 'function') { ctx.refresh.reliefFiles(data.filename); } } else if (data.status === 'error') { if (phaseLabel) phaseLabel.textContent = "Error"; if (resultLabel) { resultLabel.style.color = 'var(--color-semantic-error)'; resultLabel.textContent = `❌ ${data.message || 'Unknown error'}`; } if (shield) shield.style.display = 'none'; setTimeout(() => { container.style.display = 'none'; container.classList.remove('active'); }, 6000); } } function handleSaeProgress(data) { const progressContainer = document.getElementById('sae-training-progress'); const fill = document.getElementById('sae-training-fill'); const label = document.getElementById('sae-training-label'); const saeStatusHint = document.getElementById('sae-status-hint'); const saeToggle = document.getElementById('sae-space-toggle'); const btnTrainSae = document.getElementById('btn-train-sae'); if (!progressContainer) return; if (data.status === 'loading_vectors') { progressContainer.style.display = 'flex'; if (fill) fill.style.width = '0%'; if (label) label.textContent = "Loading vectors from database..."; if (saeStatusHint) saeStatusHint.textContent = "Preparing data..."; if (saeToggle) saeToggle.setAttribute('disabled', 'true'); if (btnTrainSae) { btnTrainSae.setAttribute('disabled', 'true'); btnTrainSae.textContent = '⏳ Preparing...'; } } else if (data.status === 'training') { progressContainer.style.display = 'flex'; if (fill) fill.style.width = `${data.progress}%`; if (label) { label.textContent = `Epoch ${data.current_epoch}/${data.total_epochs} (Loss: ${data.loss.toFixed(6)})`; } if (saeStatusHint) { saeStatusHint.textContent = "⏳ Training SAE on server..."; saeStatusHint.style.color = 'var(--color-brand-secondary)'; } if (btnTrainSae) { btnTrainSae.textContent = `⏳ Training (${data.progress}%)`; btnTrainSae.setAttribute('disabled', 'true'); } } else if (data.status === 'success') { if (fill) fill.style.width = '100%'; if (label) label.textContent = "Training complete."; setTimeout(() => { progressContainer.style.display = 'none'; }, 3000); // Trigger saeStatus UI refresh if (ctx && ctx.refresh && typeof ctx.refresh.saeStatus === 'function') { ctx.refresh.saeStatus(); } } else if (data.status === 'failed') { if (label) label.textContent = `Failed: ${data.error_message}`; if (saeStatusHint) { saeStatusHint.textContent = `❌ Failed: ${data.error_message}`; saeStatusHint.style.color = 'var(--color-semantic-error)'; } setTimeout(() => { progressContainer.style.display = 'none'; }, 5000); if (ctx && ctx.refresh && typeof ctx.refresh.saeStatus === 'function') { ctx.refresh.saeStatus(); } } } connect(); }