Spaces:
Sleeping
Sleeping
Download dashboard/features/clips/controller.js from JuanCS-Dev/twitch-byte-bot: direct link, hf CLI and curl.
- Browser
- Download file 4.39 kB
-
https://huggingface.co/spaces/JuanCS-Dev/twitch-byte-bot/resolve/main/dashboard/features/clips/controller.js
- Command line
-
hf download hf://spaces/JuanCS-Dev/twitch-byte-bot/dashboard/features/clips/controller.js
-
curl -L -o controller.js https://huggingface.co/spaces/JuanCS-Dev/twitch-byte-bot/resolve/main/dashboard/features/clips/controller.js
4.39 kB
| import { fetchClipJobs, fetchVisionStatus, postVisionIngest } from "./api.js"; | |
| import { renderClipCard, renderVisionStatus } from "./view.js"; | |
| // Adaptive polling based on Page Visibility API. | |
| class AdaptivePoller { | |
| constructor(fn, intervalMs = 2000) { | |
| this.fn = fn; | |
| this.baseInterval = intervalMs; | |
| this.timer = null; | |
| this.isRunning = false; | |
| document.addEventListener("visibilitychange", () => { | |
| if (this.isRunning) { | |
| this.restart(); // Restart with interval adjusted by visibility | |
| } | |
| }); | |
| } | |
| start() { | |
| if (this.isRunning) return; | |
| this.isRunning = true; | |
| this.tick(); | |
| } | |
| stop() { | |
| this.isRunning = false; | |
| if (this.timer) clearTimeout(this.timer); | |
| } | |
| restart() { | |
| if (this.timer) clearTimeout(this.timer); | |
| this.tick(); | |
| } | |
| async tick() { | |
| if (!this.isRunning) return; | |
| await this.fn(); | |
| const isHidden = document.hidden; | |
| const nextInterval = isHidden ? this.baseInterval * 5 : this.baseInterval; | |
| this.timer = setTimeout(() => this.tick(), nextInterval); | |
| } | |
| } | |
| export function createClipsController({ els }) { | |
| const render = (jobs) => { | |
| if (!els.container) return; | |
| if (jobs.length === 0) { | |
| els.container.innerHTML = '<div class="empty-state">No recent clip jobs.</div>'; | |
| return; | |
| } | |
| const html = jobs.map(renderClipCard).join(""); | |
| // Simple DOM diffing could be added here for optimization, | |
| // but innerHTML is fast enough for < 50 items. | |
| // We check if content changed to avoid text selection loss if user is selecting text. | |
| if (els.container.innerHTML !== html) { | |
| els.container.innerHTML = html; | |
| } | |
| // Hide skeleton if visible | |
| if (els.loadingSkeleton) { | |
| els.loadingSkeleton.style.display = "none"; | |
| } | |
| }; | |
| const fetchAndRender = async () => { | |
| try { | |
| const [clipData, visionData] = await Promise.all([ | |
| fetchClipJobs().catch(() => null), | |
| fetchVisionStatus().catch(() => null) | |
| ]); | |
| if (clipData?.ok && Array.isArray(clipData.items)) { | |
| render(clipData.items); | |
| } | |
| if (visionData?.ok) { | |
| renderVisionStatus(visionData, els); | |
| } | |
| } catch (error) { | |
| console.error("Clip polling error:", error); | |
| } | |
| }; | |
| const handleVisionIngest = async () => { | |
| if (!els.visionIngestInput || !els.visionIngestInput.files.length) { | |
| if (els.visionFeedback) els.visionFeedback.textContent = "Select an image first."; | |
| return; | |
| } | |
| const file = els.visionIngestInput.files[0]; | |
| if (els.visionIngestBtn) els.visionIngestBtn.disabled = true; | |
| if (els.visionFeedback) els.visionFeedback.textContent = "Uploading frame..."; | |
| try { | |
| const result = await postVisionIngest(file); | |
| if (result && result.ok) { | |
| els.visionFeedback.textContent = "Frame analyzed successfully."; | |
| els.visionFeedback.className = "panel-hint event-level-info"; | |
| els.visionIngestInput.value = ""; // clear | |
| await fetchAndRender(); | |
| } else { | |
| els.visionFeedback.textContent = `Error: ${result?.reason || 'Upload failed'}`; | |
| els.visionFeedback.className = "panel-hint event-level-warn"; | |
| } | |
| } catch (error) { | |
| els.visionFeedback.textContent = "Error communicating with server."; | |
| els.visionFeedback.className = "panel-hint event-level-error"; | |
| } finally { | |
| if (els.visionIngestBtn) els.visionIngestBtn.disabled = false; | |
| } | |
| }; | |
| if (els.visionIngestBtn) { | |
| els.visionIngestBtn.addEventListener("click", handleVisionIngest); | |
| } | |
| const poller = new AdaptivePoller(fetchAndRender, 2000); | |
| return { | |
| startPolling: () => poller.start(), | |
| stopPolling: () => poller.stop(), | |
| manualRefresh: fetchAndRender, | |
| // O6/reliability: render from the SSE snapshot instead of polling. | |
| renderFromSnapshot: (clips) => { | |
| if (clips && Array.isArray(clips.items)) render(clips.items); | |
| } | |
| }; | |
| } | |