Files
Nodecast/public/js/services/liveAnalysisPresentation.js
blomios b3dbb3cfba feat: run full refresh with EPG rematching
Run full live catalog refreshes through live source sync, complete channel analysis, EPG source refresh, and persisted EPG rematching while keeping check-new incremental.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-08 00:05:00 +02:00

98 lines
4.5 KiB
JavaScript

(function (root, factory) {
const service = factory();
if (typeof module === 'object' && module.exports) module.exports = service;
if (root) root.LiveAnalysisPresentation = service;
})(typeof globalThis !== 'undefined' ? globalThis : this, function () {
const BUSY_STATUSES = new Set(['queued', 'building', 'running']);
const FAILED_STATUSES = new Set(['error', 'failed']);
const INDETERMINATE_PHASES = new Set([
'writing', 'refreshing_live_sources', 'refreshing_epg_sources', 'writing_epg_matches'
]);
function progressValues(status, phase) {
if (phase === 'laya_identity') {
return {
processed: status.layaProcessed ?? status.phaseProcessed ?? status.batchProcessed ?? status.processedBatches,
total: status.layaTotal ?? status.phaseTotal ?? status.batchTotal ?? status.totalBatches,
prefix: 'AI batch'
};
}
if (phase === 'laya_language') {
return {
processed: status.languageProcessed,
total: status.languageTotal,
prefix: 'Language batch'
};
}
return { processed: status.processed, total: status.total, prefix: '', suffix: ' channels' };
}
function present(status = {}) {
const state = String(status.status || 'idle').toLowerCase();
const busy = BUSY_STATUSES.has(state);
const failed = FAILED_STATUSES.has(state);
const complete = state === 'success';
const phase = String(status.phase || '').toLowerCase();
const progress = progressValues(status, phase);
const processed = Number(progress.processed);
const total = Number(progress.total);
const determined = busy && !INDETERMINATE_PHASES.has(phase) &&
progress.processed !== null && progress.processed !== undefined &&
progress.total !== null && progress.total !== undefined &&
Number.isFinite(processed) && Number.isFinite(total) && total > 0;
const progressPercent = determined
? Math.max(0, Math.min(100, Math.round(processed / total * 100)))
: null;
const previouslyCompleted = Boolean(status.completedAt) || Number(status.projectedCount) > 0;
const statusLabel = busy
? phase === 'refreshing_live_sources'
? 'Refreshing IPTV sources…'
: phase === 'refreshing_epg_sources'
? 'Refreshing EPG data…'
: ['matching_epg', 'laya_epg_matching'].includes(phase)
? 'Matching channels to EPG data…'
: phase === 'writing_epg_matches'
? 'Finalizing…'
: phase === 'laya_identity'
? 'Analyzing with local AI…'
: phase === 'laya_language'
? 'Detecting stream languages…'
: phase === 'projecting'
? 'Building channel catalog…'
: phase === 'writing'
? 'Saving channel catalog…'
: 'Analyzing channels…'
: complete ? 'Analysis complete' : failed ? 'Analysis failed' : 'Ready to analyze';
const progressText = ['writing', 'writing_epg_matches'].includes(phase) && busy
? 'Finalizing…'
: progressPercent === null
? (busy ? 'In progress…' : '')
: progress.prefix
? `${progress.prefix} ${processed} of ${total} · ${progressPercent}%`
: `${processed} of ${total}${progress.suffix || ''} · ${progressPercent}%`;
return {
state,
phase,
busy,
complete,
failed,
statusLabel,
buttonLabel: failed ? 'Try again' : complete || previouslyCompleted ? 'Analyze again' : 'Analyze channels',
progressIndeterminate: busy && progressPercent === null,
progressPercent,
progressText,
errorDetail: failed && status.error ? String(status.error) : ''
};
}
function layaFallbackMessage(mode, layaStatus = {}) {
if (!['laya', 'hybrid'].includes(mode)) return '';
return layaStatus.modelPresent === false || layaStatus.runtimeAvailable === false
? 'Local AI is unavailable. Standard channel matching will be used for this analysis.'
: '';
}
return { layaFallbackMessage, present };
});