Spaces:
Sleeping
Sleeping
Download dashboard/features/action-queue/view.js from JuanCS-Dev/twitch-byte-bot: direct link, hf CLI and curl.
- Browser
- Download file 15.2 kB
-
https://huggingface.co/spaces/JuanCS-Dev/twitch-byte-bot/resolve/main/dashboard/features/action-queue/view.js
- Command line
-
hf download hf://spaces/JuanCS-Dev/twitch-byte-bot/dashboard/features/action-queue/view.js
-
curl -L -o view.js https://huggingface.co/spaces/JuanCS-Dev/twitch-byte-bot/resolve/main/dashboard/features/action-queue/view.js
15.2 kB
| import { asArray, formatNumber, setText } from "../shared/dom.js"; | |
| function chipClassByStatus(status) { | |
| const safeStatus = String(status || "") | |
| .trim() | |
| .toLowerCase(); | |
| if (safeStatus === "approved") return "ok"; | |
| if (safeStatus === "rejected") return "error"; | |
| if (safeStatus === "ignored") return "pending"; | |
| return "warn"; | |
| } | |
| function chipClassByRisk(risk) { | |
| const safeRisk = String(risk || "") | |
| .trim() | |
| .toLowerCase(); | |
| if (safeRisk === "moderation_action") return "error"; | |
| if (safeRisk === "auto_chat") return "pending"; | |
| return "warn"; | |
| } | |
| function chipClassByPlaybookState(state) { | |
| const safeState = String(state || "") | |
| .trim() | |
| .toLowerCase(); | |
| if (safeState === "idle") return "ok"; | |
| if (safeState === "cooldown") return "pending"; | |
| if (safeState === "awaiting_decision") return "warn"; | |
| return "warn"; | |
| } | |
| function chipClassByPlaybookOutcome(outcome) { | |
| const safeOutcome = String(outcome || "") | |
| .trim() | |
| .toLowerCase(); | |
| if (safeOutcome === "completed") return "ok"; | |
| if (safeOutcome === "aborted") return "error"; | |
| return "pending"; | |
| } | |
| function formatPlaybookState(state) { | |
| const safeState = String(state || "") | |
| .trim() | |
| .toLowerCase(); | |
| if (safeState === "awaiting_decision") return "awaiting decision"; | |
| if (safeState === "cooldown") return "cooldown"; | |
| if (safeState === "idle") return "idle"; | |
| return safeState || "-"; | |
| } | |
| function formatPlaybookOutcome(outcome) { | |
| const safeOutcome = String(outcome || "") | |
| .trim() | |
| .toLowerCase(); | |
| if (safeOutcome === "never_run") return "never run"; | |
| if (safeOutcome === "completed") return "completed"; | |
| if (safeOutcome === "aborted") return "aborted"; | |
| return safeOutcome || "-"; | |
| } | |
| function buildChip(text, tone) { | |
| const chip = document.createElement("span"); | |
| chip.className = `chip ${tone}`; | |
| chip.textContent = String(text || "-"); | |
| return chip; | |
| } | |
| function buildPendingActions(item, onDecision) { | |
| const controls = document.createElement("div"); | |
| controls.style.display = "flex"; | |
| controls.style.flexDirection = "column"; | |
| controls.style.gap = "var(--spacing-2)"; | |
| controls.style.marginTop = "var(--spacing-3)"; | |
| const noteInput = document.createElement("input"); | |
| noteInput.type = "text"; | |
| noteInput.className = "form-control"; | |
| noteInput.placeholder = "Optional audit note (e.g. approved for test)."; | |
| controls.appendChild(noteInput); | |
| const buttonRow = document.createElement("div"); | |
| buttonRow.style.display = "flex"; | |
| buttonRow.style.gap = "var(--spacing-2)"; | |
| const approveBtn = document.createElement("button"); | |
| approveBtn.type = "button"; | |
| approveBtn.className = "btn btn-primary"; | |
| approveBtn.dataset.byteReact = "approve"; | |
| approveBtn.textContent = "Approve"; | |
| approveBtn.addEventListener("click", () => | |
| onDecision(item.id, "approve", noteInput.value), | |
| ); | |
| buttonRow.appendChild(approveBtn); | |
| const rejectBtn = document.createElement("button"); | |
| rejectBtn.type = "button"; | |
| rejectBtn.className = "btn btn-danger"; | |
| rejectBtn.textContent = "Reject"; | |
| rejectBtn.addEventListener("click", () => | |
| onDecision(item.id, "reject", noteInput.value), | |
| ); | |
| buttonRow.appendChild(rejectBtn); | |
| controls.appendChild(buttonRow); | |
| return controls; | |
| } | |
| function buildActionItem(item, onDecision) { | |
| const card = document.createElement("li"); | |
| card.style.display = "flex"; | |
| card.style.flexDirection = "column"; | |
| card.style.gap = "var(--spacing-2)"; | |
| const topRow = document.createElement("div"); | |
| topRow.style.display = "flex"; | |
| topRow.style.flexWrap = "wrap"; | |
| topRow.style.gap = "var(--spacing-2)"; | |
| topRow.style.alignItems = "center"; | |
| const title = document.createElement("strong"); | |
| title.textContent = String(item.title || "Untitled action"); | |
| topRow.appendChild(title); | |
| topRow.appendChild( | |
| buildChip(String(item.risk || "unknown"), chipClassByRisk(item.risk)), | |
| ); | |
| topRow.appendChild( | |
| buildChip(String(item.status || "pending"), chipClassByStatus(item.status)), | |
| ); | |
| card.appendChild(topRow); | |
| const body = document.createElement("div"); | |
| body.textContent = String(item.body || "-"); | |
| card.appendChild(body); | |
| const meta = document.createElement("div"); | |
| meta.className = "event-meta"; | |
| meta.textContent = `id=${item.id || "-"} | created=${item.created_at || "-"} | updated=${item.updated_at || "-"}`; | |
| card.appendChild(meta); | |
| if ( | |
| String(item.status || "") | |
| .trim() | |
| .toLowerCase() === "pending" | |
| ) { | |
| card.appendChild(buildPendingActions(item, onDecision)); | |
| } else if (item.decision_note) { | |
| const note = document.createElement("div"); | |
| note.className = "panel-hint"; | |
| note.textContent = `Note: ${item.decision_note}`; | |
| card.appendChild(note); | |
| } | |
| return card; | |
| } | |
| function buildPlaybookItem(item) { | |
| const card = document.createElement("li"); | |
| card.style.display = "flex"; | |
| card.style.flexDirection = "column"; | |
| card.style.gap = "var(--spacing-2)"; | |
| const topRow = document.createElement("div"); | |
| topRow.style.display = "flex"; | |
| topRow.style.flexWrap = "wrap"; | |
| topRow.style.gap = "var(--spacing-2)"; | |
| topRow.style.alignItems = "center"; | |
| const title = document.createElement("strong"); | |
| title.textContent = String(item?.name || item?.id || "Playbook"); | |
| topRow.appendChild(title); | |
| topRow.appendChild( | |
| buildChip( | |
| formatPlaybookState(item?.state), | |
| chipClassByPlaybookState(item?.state), | |
| ), | |
| ); | |
| topRow.appendChild( | |
| buildChip( | |
| formatPlaybookOutcome(item?.last_outcome), | |
| chipClassByPlaybookOutcome(item?.last_outcome), | |
| ), | |
| ); | |
| card.appendChild(topRow); | |
| const description = document.createElement("div"); | |
| description.textContent = String(item?.description || "-"); | |
| card.appendChild(description); | |
| const step = Number(item?.current_step_number || 0); | |
| const totalSteps = Number(item?.total_steps || 0); | |
| const waitingActionId = String(item?.waiting_action_id || "").trim(); | |
| const progress = document.createElement("div"); | |
| progress.className = "panel-hint"; | |
| progress.textContent = | |
| step > 0 && totalSteps > 0 | |
| ? `Step ${step}/${totalSteps}: ${item?.current_step_title || "-"}` | |
| : `Total steps: ${formatNumber(totalSteps)}`; | |
| card.appendChild(progress); | |
| const meta = document.createElement("div"); | |
| meta.className = "event-meta"; | |
| meta.textContent = `id=${item?.id || "-"} | run=${item?.last_run_id || "-"} | updated=${item?.updated_at || "-"}`; | |
| card.appendChild(meta); | |
| const detail = document.createElement("div"); | |
| detail.className = "panel-hint"; | |
| if (waitingActionId) { | |
| detail.textContent = `Awaiting queue decision for action_id=${waitingActionId}.`; | |
| } else if ( | |
| String(item?.state || "") | |
| .trim() | |
| .toLowerCase() === "cooldown" | |
| ) { | |
| detail.textContent = `Cooldown until ${item?.cooldown_until || "-"}.`; | |
| } else { | |
| detail.textContent = `Last reason: ${item?.last_outcome_reason || "n/a"}.`; | |
| } | |
| card.appendChild(detail); | |
| return card; | |
| } | |
| export function getActionQueueElements() { | |
| return { | |
| panel: document.getElementById("aqPanel"), | |
| summaryQueuePendingCount: document.getElementById( | |
| "summaryQueuePendingCount", | |
| ), | |
| summaryQueuePendingChip: document.getElementById("summaryQueuePendingChip"), | |
| statusFilter: document.getElementById("aqStatusFilter"), | |
| limitInput: document.getElementById("aqLimitInput"), | |
| refreshBtn: document.getElementById("aqRefreshBtn"), | |
| feedback: document.getElementById("aqFeedbackMsg"), | |
| pendingCount: document.getElementById("aqPendingCount"), | |
| approvedCount: document.getElementById("aqApprovedCount"), | |
| rejectedCount: document.getElementById("aqRejectedCount"), | |
| ignoredCount: document.getElementById("aqIgnoredCount"), | |
| totalCount: document.getElementById("aqTotalCount"), | |
| list: document.getElementById("aqList"), | |
| opsUpdatedAt: document.getElementById("aqOpsUpdatedAt"), | |
| opsIdleCount: document.getElementById("aqOpsIdleCount"), | |
| opsAwaitingCount: document.getElementById("aqOpsAwaitingCount"), | |
| opsCooldownCount: document.getElementById("aqOpsCooldownCount"), | |
| opsCompletedCount: document.getElementById("aqOpsCompletedCount"), | |
| opsAbortedCount: document.getElementById("aqOpsAbortedCount"), | |
| opsPlaybookSelect: document.getElementById("aqOpsPlaybookSelect"), | |
| opsReasonInput: document.getElementById("aqOpsReasonInput"), | |
| opsForceToggle: document.getElementById("aqOpsForceToggle"), | |
| opsRefreshBtn: document.getElementById("aqOpsRefreshBtn"), | |
| opsTriggerBtn: document.getElementById("aqOpsTriggerBtn"), | |
| opsFeedback: document.getElementById("aqOpsFeedbackMsg"), | |
| opsList: document.getElementById("aqOpsList"), | |
| }; | |
| } | |
| export function setActionQueueBusy(els, busy) { | |
| const disabled = Boolean(busy); | |
| if (els?.statusFilter) els.statusFilter.disabled = disabled; | |
| if (els?.limitInput) els.limitInput.disabled = disabled; | |
| if (els?.refreshBtn) els.refreshBtn.disabled = disabled; | |
| if (els?.list) { | |
| const actionButtons = els.list.querySelectorAll("button"); | |
| actionButtons.forEach((button) => { | |
| button.disabled = disabled; | |
| }); | |
| els.list.style.opacity = disabled ? "0.7" : "1"; | |
| } | |
| if (els?.opsPlaybookSelect) els.opsPlaybookSelect.disabled = disabled; | |
| if (els?.opsReasonInput) els.opsReasonInput.disabled = disabled; | |
| if (els?.opsForceToggle) els.opsForceToggle.disabled = disabled; | |
| if (els?.opsRefreshBtn) els.opsRefreshBtn.disabled = disabled; | |
| if (els?.opsTriggerBtn) els.opsTriggerBtn.disabled = disabled; | |
| if (els?.opsList) { | |
| els.opsList.style.opacity = disabled ? "0.7" : "1"; | |
| } | |
| } | |
| export function showActionQueueFeedback(els, message, type = "info") { | |
| if (!els?.feedback) return; | |
| setText(els.feedback, message); | |
| els.feedback.className = `panel-hint event-level-${type === "ok" ? "info" : type}`; | |
| } | |
| export function showOpsPlaybooksFeedback(els, message, type = "info") { | |
| if (!els?.opsFeedback) return; | |
| setText(els.opsFeedback, message); | |
| els.opsFeedback.className = `panel-hint event-level-${type === "ok" ? "info" : type}`; | |
| } | |
| export function getActionQueueQuery(els) { | |
| const status = String(els?.statusFilter?.value || "") | |
| .trim() | |
| .toLowerCase(); | |
| const limitRaw = Number.parseInt(String(els?.limitInput?.value || "80"), 10); | |
| const limit = Number.isFinite(limitRaw) | |
| ? Math.max(1, Math.min(300, limitRaw)) | |
| : 80; | |
| if (els?.limitInput) { | |
| els.limitInput.value = String(limit); | |
| } | |
| return { status, limit }; | |
| } | |
| function syncOpsPlaybookSelect(playbooks, selectEl) { | |
| if (!selectEl) return; | |
| const items = asArray(playbooks).filter((item) => item && item.id); | |
| const previousValue = String(selectEl.value || "") | |
| .trim() | |
| .toLowerCase(); | |
| selectEl.innerHTML = ""; | |
| if (!items.length) { | |
| const option = document.createElement("option"); | |
| option.value = ""; | |
| option.textContent = "No playbook"; | |
| selectEl.appendChild(option); | |
| selectEl.value = ""; | |
| return; | |
| } | |
| items.forEach((item) => { | |
| const option = document.createElement("option"); | |
| option.value = String(item.id || "") | |
| .trim() | |
| .toLowerCase(); | |
| option.textContent = String(item.name || item.id || "Playbook"); | |
| selectEl.appendChild(option); | |
| }); | |
| const hasPrevious = items.some( | |
| (item) => | |
| String(item.id || "") | |
| .trim() | |
| .toLowerCase() === previousValue, | |
| ); | |
| selectEl.value = hasPrevious | |
| ? previousValue | |
| : String(items[0].id || "") | |
| .trim() | |
| .toLowerCase(); | |
| } | |
| function renderOpsPlaybookRows(playbooks, targetList) { | |
| if (!targetList) return; | |
| targetList.innerHTML = ""; | |
| const items = asArray(playbooks); | |
| if (!items.length) { | |
| const emptyItem = document.createElement("li"); | |
| emptyItem.style.fontStyle = "italic"; | |
| emptyItem.style.color = "var(--text-muted)"; | |
| emptyItem.textContent = "No operational playbooks registered."; | |
| targetList.appendChild(emptyItem); | |
| return; | |
| } | |
| items.forEach((playbook) => { | |
| targetList.appendChild(buildPlaybookItem(playbook)); | |
| }); | |
| } | |
| export function getOpsPlaybookTriggerPayload(els, channelId = "default") { | |
| const playbookId = String(els?.opsPlaybookSelect?.value || "") | |
| .trim() | |
| .toLowerCase(); | |
| const reason = | |
| String(els?.opsReasonInput?.value || "").trim() || "manual_dashboard"; | |
| const force = Boolean(els?.opsForceToggle?.checked); | |
| return { | |
| playbookId, | |
| channelId: | |
| String(channelId || "") | |
| .trim() | |
| .toLowerCase() || "default", | |
| reason, | |
| force, | |
| }; | |
| } | |
| function renderSummaryQueueState(els, pendingCount) { | |
| const safePending = Number.isFinite(Number(pendingCount)) | |
| ? Number(pendingCount) | |
| : 0; | |
| setText(els?.summaryQueuePendingCount, formatNumber(safePending)); | |
| if (!els?.summaryQueuePendingChip) return; | |
| els.summaryQueuePendingChip.classList.remove( | |
| "ok", | |
| "warn", | |
| "error", | |
| "pending", | |
| ); | |
| if (safePending <= 0) { | |
| setText(els.summaryQueuePendingChip, "CLEAR"); | |
| els.summaryQueuePendingChip.classList.add("ok"); | |
| return; | |
| } | |
| if (safePending >= 5) { | |
| setText(els.summaryQueuePendingChip, "HIGH LOAD"); | |
| els.summaryQueuePendingChip.classList.add("error"); | |
| return; | |
| } | |
| setText(els.summaryQueuePendingChip, "REVIEW"); | |
| els.summaryQueuePendingChip.classList.add("warn"); | |
| } | |
| export function renderActionQueuePayload(payload, els, onDecision) { | |
| const safePayload = payload && typeof payload === "object" ? payload : {}; | |
| const summary = safePayload.summary || {}; | |
| const items = asArray(safePayload.items); | |
| setText(els?.pendingCount, formatNumber(summary.pending)); | |
| setText(els?.approvedCount, formatNumber(summary.approved)); | |
| setText(els?.rejectedCount, formatNumber(summary.rejected)); | |
| setText(els?.ignoredCount, formatNumber(summary.ignored)); | |
| setText(els?.totalCount, formatNumber(summary.total)); | |
| renderSummaryQueueState(els, summary.pending); | |
| if (els?.panel) { | |
| if (summary.pending > 0) { | |
| els.panel.classList.add("attention-required"); | |
| } else { | |
| els.panel.classList.remove("attention-required"); | |
| } | |
| } | |
| if (!els?.list) return; | |
| els.list.innerHTML = ""; | |
| if (!items.length) { | |
| const emptyItem = document.createElement("li"); | |
| emptyItem.style.fontStyle = "italic"; | |
| emptyItem.style.color = "var(--text-muted)"; | |
| emptyItem.textContent = "Queue has no items for the current filter."; | |
| els.list.appendChild(emptyItem); | |
| return; | |
| } | |
| items.forEach((item) => { | |
| els.list.appendChild(buildActionItem(item, onDecision)); | |
| }); | |
| } | |
| export function renderOpsPlaybooksPayload(payload, els) { | |
| const safePayload = payload && typeof payload === "object" ? payload : {}; | |
| const summary = safePayload.summary || {}; | |
| const playbooks = asArray(safePayload.playbooks); | |
| setText(els?.opsUpdatedAt, safePayload.updated_at || "-"); | |
| setText(els?.opsIdleCount, formatNumber(summary.idle)); | |
| setText(els?.opsAwaitingCount, formatNumber(summary.awaiting_decision)); | |
| setText(els?.opsCooldownCount, formatNumber(summary.cooldown)); | |
| setText(els?.opsCompletedCount, formatNumber(summary.completed)); | |
| setText(els?.opsAbortedCount, formatNumber(summary.aborted)); | |
| syncOpsPlaybookSelect(playbooks, els?.opsPlaybookSelect); | |
| renderOpsPlaybookRows(playbooks, els?.opsList); | |
| } | |