(() => { const actions = document.querySelector("[data-ai-runtime-actions]"); const state = document.querySelector("[data-runtime-state]"); const downloadStatus = document.querySelector("[data-download-status]"); const testForm = document.querySelector("[data-ai-test-form]"); const testOutput = document.querySelector("[data-ai-test-output]"); const gpuControl = document.querySelector("[data-gpu-control]"); const accessForm = document.querySelector("[data-ai-access-form]"); if (actions) { actions.addEventListener("click", async (event) => { const button = event.target.closest("[data-runtime-action]"); if (!button) return; button.disabled = true; try { const response = await fetch(`/plugins/lumi_ai/runtime/${button.dataset.runtimeAction}`, { method: "POST" }); const data = await response.json(); if (!response.ok) throw new Error(data.error || "Runtime action failed."); if (data.state) state.textContent = data.state; if (["self-test", "verify-runtime", "verify-model", "verify-gate-model"].includes(button.dataset.runtimeAction)) { const labels = { "self-test": "Runtime self-test passed.", "verify-runtime": "Runtime installation verified.", "verify-model": "Model verification passed.", "verify-gate-model": "Gate model verification passed." }; window.alert(labels[button.dataset.runtimeAction]); } } catch (error) { window.alert(error.message); } finally { button.disabled = false; } }); } const pollDownloads = async () => { if (!downloadStatus) return; try { const response = await fetch("/plugins/lumi_ai/api/downloads"); if (!response.ok) return; const jobs = Object.values(await response.json()); const active = jobs.filter((job) => !["complete", "error"].includes(job.state)); if (!jobs.length) return; downloadStatus.hidden = false; downloadStatus.textContent = jobs.map((job) => { const percent = job.total ? Math.floor(job.downloaded / job.total * 100) : 0; return `${job.id}: ${job.state}${job.total ? ` ${percent}%` : ""}${job.error ? ` - ${job.error}` : ""}`; }).join(" | "); if (active.length) window.setTimeout(pollDownloads, 1000); } catch {} }; if (testForm && testOutput) { testForm.addEventListener("submit", async (event) => { event.preventDefault(); const form = new FormData(testForm); testOutput.hidden = false; testOutput.textContent = "Running..."; try { const response = await fetch("/plugins/lumi_ai/assistant/test", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ role: form.get("role"), message: form.get("message"), show_raw_prompt: form.get("show_raw_prompt") === "on", show_raw_output: form.get("show_raw_output") === "on" }) }); const data = await response.json(); if (!response.ok) throw new Error(data.error || "Test failed."); if (form.get("show_raw_output") !== "on") delete data.raw_response; testOutput.textContent = JSON.stringify(data, null, 2); } catch (error) { testOutput.textContent = error.message; } }); } if (gpuControl) { const model = document.querySelector("[data-gpu-model]"); const context = document.querySelector("[data-gpu-context]"); const workload = gpuControl.querySelector("[data-gpu-workload]"); const slider = gpuControl.querySelector("[data-gpu-slider]"); const value = gpuControl.querySelector("[data-gpu-value]"); const intentLabel = gpuControl.querySelector("[data-gpu-intent]"); const actualLabel = gpuControl.querySelector("[data-gpu-actual]"); const limit = gpuControl.querySelector("[data-gpu-limit]"); const backend = gpuControl.querySelector("[data-gpu-backend]"); const memory = gpuControl.querySelector("[data-gpu-memory]"); const totalVram = gpuControl.querySelector("[data-gpu-total-vram]"); const freeVram = gpuControl.querySelector("[data-gpu-free-vram]"); const externalVram = gpuControl.querySelector("[data-gpu-external-vram]"); const warning = gpuControl.querySelector("[data-gpu-warning]"); let maximum = Number.parseInt(limit.textContent.match(/\d+/)?.[0], 10) || 0; let capacityTimer = null; const formatBytes = (megabytes) => { if (!megabytes) return "0 B"; return megabytes >= 1024 ? `${(megabytes / 1024).toFixed(1)} GB` : `${Math.round(megabytes)} MB`; }; const clampNewIntent = () => { const next = Math.max(0, Math.min(maximum, Number(workload.value) || 0)); workload.value = String(next); value.textContent = `${next}% intent`; intentLabel.textContent = `${next}%`; return next; }; const applyCapacity = (data) => { maximum = Math.max(0, Math.min(100, Number(data.gpu_allocation_max_safe_percent) || 0)); const intent = Math.max(0, Math.min(100, Number(data.gpu_allocation_intent_percent) || 0)); const actual = Math.max(0, Math.min(maximum, Number(data.gpu_allocation_actual_percent) || 0)); slider.style.setProperty("--gpu-max", `${maximum}%`); slider.style.setProperty("--gpu-actual", `${actual}%`); slider.title = `Intended ${intent}%, actual ${actual}%, maximum safe ${maximum}%`; limit.textContent = `Maximum safe: ${maximum}%`; workload.value = String(intent); value.textContent = `${intent}% intent`; intentLabel.textContent = `${intent}%`; actualLabel.textContent = `${actual}%`; backend.textContent = String(data.backend || "cpu").toUpperCase(); memory.dataset.fullOffloadMb = String(Number(data.estimated_full_offload_mb) || 0); memory.textContent = formatBytes(data.managed_model_vram_mb); totalVram.textContent = formatBytes(data.total_vram_mb); freeVram.textContent = formatBytes(data.free_vram_mb); externalVram.textContent = formatBytes(data.external_vram_estimate_mb); warning.hidden = !data.warning; warning.textContent = data.warning || ""; }; const refreshCapacity = async () => { const query = new URLSearchParams({ model_id: model.value, context_size: context.value, intent_percent: workload.value }); try { const response = await fetch(`${gpuControl.dataset.endpoint}?${query}`, { cache: "no-store" }); const data = await response.json(); if (!response.ok) throw new Error(data.error || "Capacity check failed."); applyCapacity(data); } catch (error) { applyCapacity({ max_percent: 0, backend: "cpu", warning: error.message }); } }; const scheduleCapacity = () => { window.clearTimeout(capacityTimer); capacityTimer = window.setTimeout(refreshCapacity, 250); }; workload.addEventListener("input", () => { const selected = clampNewIntent(); const actual = Math.min(selected, maximum); slider.style.setProperty("--gpu-actual", `${actual}%`); actualLabel.textContent = `${actual}%`; memory.textContent = formatBytes( (Number(memory.dataset.fullOffloadMb) || 0) * actual / 100 ); }); workload.addEventListener("change", refreshCapacity); model.addEventListener("change", refreshCapacity); context.addEventListener("input", scheduleCapacity); refreshCapacity(); } if (accessForm) { const search = accessForm.querySelector("[data-user-search]"); const userId = accessForm.querySelector("[data-user-id]"); const results = accessForm.querySelector("[data-user-results]"); const preview = accessForm.querySelector("[data-user-preview]"); const action = accessForm.querySelector("[data-access-action]"); const timeoutField = accessForm.querySelector("[data-timeout-field]"); let searchTimer = null; const updateTimeoutVisibility = () => { const visible = action.value === "timeout"; timeoutField.hidden = !visible; const input = timeoutField.querySelector("input"); input.required = visible; if (!visible) input.value = ""; }; const selectUser = (user) => { userId.value = user.id; search.value = user.username; const identities = user.identities.map((identity) => `${identity.provider}: ${identity.display_name || identity.provider_user_id}`).join(" | "); preview.textContent = `${user.username} | ${user.id}${identities ? ` | ${identities}` : ""}`; preview.hidden = false; results.hidden = true; }; const renderUsers = (users) => { results.replaceChildren(); for (const user of users) { const button = document.createElement("button"); button.type = "button"; button.className = "ai-user-result"; const identity = user.identities[0]; button.textContent = identity ? `${user.username} | ${identity.display_name || identity.provider_user_id} (${identity.provider})` : `${user.username} | ${user.id}`; button.addEventListener("click", () => selectUser(user)); results.append(button); } results.hidden = !users.length; }; search.addEventListener("input", () => { userId.value = ""; preview.hidden = true; window.clearTimeout(searchTimer); const query = search.value.trim(); if (query.length < 2) { results.hidden = true; return; } searchTimer = window.setTimeout(async () => { try { const response = await fetch(`/plugins/lumi_ai/api/users/search?q=${encodeURIComponent(query)}`, { cache: "no-store" }); const data = await response.json(); renderUsers(response.ok ? data.users || [] : []); } catch { renderUsers([]); } }, 180); }); accessForm.addEventListener("submit", (event) => { if (!userId.value) { event.preventDefault(); search.setCustomValidity("Select a known Lumi user."); search.reportValidity(); } }); search.addEventListener("input", () => search.setCustomValidity("")); action.addEventListener("change", updateTimeoutVisibility); updateTimeoutVisibility(); } const assistantDiagnostics = document.querySelector("[data-assistant-diagnostics]"); if (assistantDiagnostics) { const status = assistantDiagnostics.querySelector("[data-assistant-status]"); const reason = assistantDiagnostics.querySelector("[data-assistant-reason]"); const conditions = assistantDiagnostics.querySelector("[data-assistant-conditions]"); const endpointStatus = assistantDiagnostics.querySelector("[data-panel-endpoint-status]"); const htmlLength = assistantDiagnostics.querySelector("[data-panel-html-length]"); const htmlError = assistantDiagnostics.querySelector("[data-panel-html-error]"); const mountError = assistantDiagnostics.querySelector("[data-panel-mount-error]"); const refreshDiagnostics = () => fetch(assistantDiagnostics.dataset.endpoint, { cache: "no-store" }) .then(async (response) => { const data = await response.json(); if (!response.ok) throw new Error(data.error || "Diagnostic unavailable."); status.textContent = data.conditions?.every((condition) => condition.passed) ? "Mounted" : data.available ? "Backend ready" : "Hidden"; reason.textContent = data.reason; for (const condition of data.conditions || []) { const row = conditions?.querySelector(`[data-condition="${condition.key}"] strong`); if (!row) continue; row.textContent = condition.passed ? "Pass" : "Fail"; row.className = condition.passed ? "pass" : "fail"; } if (endpointStatus) endpointStatus.textContent = data.panel_endpoint_status || "Not requested"; if (htmlLength) htmlLength.textContent = data.panel_html_length || 0; if (htmlError) htmlError.textContent = data.panel_html_error || "None"; if (mountError) mountError.textContent = data.mount_error || "None"; }) .catch((error) => { status.textContent = "Unknown"; reason.textContent = error.message; }); refreshDiagnostics(); window.setInterval(refreshDiagnostics, 5000); } const logContent = document.querySelector("[data-log-content]"); const logFilter = document.querySelector("[data-log-filter]"); const logCopy = document.querySelector("[data-log-copy]"); if (logContent && logFilter) { const originalLines = logContent.textContent.split(/\r?\n/); logFilter.addEventListener("input", () => { const term = logFilter.value.trim().toLowerCase(); logContent.textContent = term ? originalLines.filter((line) => line.toLowerCase().includes(term)).join("\n") : originalLines.join("\n"); }); logCopy?.addEventListener("click", async () => { await navigator.clipboard.writeText(logContent.textContent); const original = logCopy.textContent; logCopy.textContent = "Copied"; window.setTimeout(() => { logCopy.textContent = original; }, 1200); }); } pollDownloads(); })();