const state = { view: location.hash.slice(1) || "overview", apiKey: sessionStorage.getItem("maester_api_key") || "", workspaceId: "", campaigns: [], variants: [], jobs: [], assets: [], calendar: [], workspaces: [], accounts: [], integrations: [], experiments: [], rules: [], audit: [], overview: {}, selectedVariantId: null, }; const $ = (query, root = document) => root.querySelector(query); const $$ = (query, root = document) => [...root.querySelectorAll(query)]; const escapeHtml = (value = "") => String(value).replace(/[&<>'"]/g, char => ({"&":"&","<":"<",">":">","'":"'",'"':"""}[char])); const badge = value => `${escapeHtml(String(value).replaceAll("_", " "))}`; const date = value => value ? new Date(value).toLocaleString() : "-"; const tags = values => `
${(values || []).map(value => `${escapeHtml(String(value).replaceAll("_", " "))}`).join("")}
`; const jsonPost = body => ({method: "POST", body: JSON.stringify(body)}); function toast(message, error = false) { const node = document.createElement("div"); node.className = `toast${error ? " error" : ""}`; node.textContent = message; $("#toast-region").append(node); setTimeout(() => node.remove(), 4000); } async function api(path, options = {}) { const headers = new Headers(options.headers || {}); if (state.apiKey) headers.set("X-API-Key", state.apiKey); if (options.body && !(options.body instanceof FormData)) headers.set("Content-Type", "application/json"); const response = await fetch(path, {...options, headers, credentials: "same-origin"}); if (response.status === 401) { $("#settings-dialog").showModal(); throw new Error("API key required"); } if (!response.ok) { const body = await response.json().catch(() => ({detail: response.statusText})); throw new Error(typeof body.detail === "string" ? body.detail : JSON.stringify(body.detail)); } return response.status === 204 ? null : response.json(); } async function loadData() { const scope = state.workspaceId ? `?workspace_id=${encodeURIComponent(state.workspaceId)}` : ""; const values = await Promise.all([ api(`/automation/overview${scope}`), api("/automation/campaigns"), api("/automation/variants"), api("/automation/jobs"), api("/automation/assets"), api("/automation/calendar"), api("/automation/workspaces"), api("/automation/accounts"), api("/automation/integrations"), api("/automation/experiments"), api("/automation/rules"), api("/automation/audit?limit=100"), ]); [state.overview, state.campaigns, state.variants, state.jobs, state.assets, state.calendar, state.workspaces, state.accounts, state.integrations, state.experiments, state.rules, state.audit] = values; $(".status-dot").classList.add("online"); $("#connection-label").textContent = "Gateway online"; fillSelectors(); } function fillSelectors() { $("#workspace-filter").innerHTML = `${state.workspaces.map(item => ``).join("")}`; $("#workspace-filter").value = state.workspaceId; const campaignOptions = state.campaigns.map(item => ``).join(""); const editorValue = $("#editor-campaign-filter").value; $("#editor-campaign-filter").innerHTML = `${campaignOptions}`; $("#editor-campaign-filter").value = editorValue; $("#calendar-campaign").innerHTML = campaignOptions; const variantOptions = state.variants.map(item => ``).join(""); $("#analytics-variant").innerHTML = variantOptions; $("#review-variants").innerHTML = variantOptions; } function metric(label, value, detail = "") { return `
${label}
${value}
${escapeHtml(detail)}
`; } function renderOverview() { const data = state.overview; $("#overview-metrics").innerHTML = metric("Campaigns", data.campaigns || 0, "Production briefs") + metric("Variants", data.variants || 0, "Platform cuts") + metric("Queued", data.queue?.states?.queued || 0, `${data.queue?.total || 0} jobs`) + metric("Published", data.approvals?.published || 0, "Live variants"); const queue = data.queue?.states || {}; $("#overview-queue").innerHTML = Object.keys(queue).length ? Object.entries(queue).map(([name, count]) => `
${escapeHtml(name)}
${count}
`).join("") : `
Queue is clear
`; const scores = data.analytics?.platform_average_scores || {}; $("#overview-performance").innerHTML = Object.keys(scores).length ? Object.entries(scores).map(([name, score]) => `
${escapeHtml(name)}
${score}
`).join("") : `
No performance data
`; $("#overview-approvals").innerHTML = `
${Object.entries(data.approvals || {}).map(([name, count]) => metric(name.replaceAll("_", " "), count)).join("")}
`; } function renderCampaigns() { const counts = state.variants.reduce((result, item) => ({...result, [item.campaign_id]: (result[item.campaign_id] || 0) + 1}), {}); $("#campaign-table").innerHTML = state.campaigns.map(item => `${escapeHtml(item.name)}
${date(item.updated_at)}
${escapeHtml(item.topic)}${tags(item.platforms)}${counts[item.id] || 0}${badge(item.status)}
`).join("") || `
No campaigns
`; } function renderEditor() { const campaignId = $("#editor-campaign-filter").value; const variants = state.variants.filter(item => !campaignId || item.campaign_id === campaignId); $("#variant-list").innerHTML = variants.map(item => ``).join("") || `
No variants
`; const item = state.variants.find(variant => variant.id === state.selectedVariantId); if (!item) return; $("#variant-editor").innerHTML = `

${escapeHtml(item.title)}

${badge(item.approval_state)}
`; $("#variant-form").addEventListener("submit", saveVariant); renderInspector(item); } async function renderInspector(item) { try { const quality = await api(`/automation/quality/variants/${item.id}`); $("#variant-inspector").innerHTML = `

Inspector

${badge(item.job_state)}
${quality.hook.score}Hook
${quality.script.score}Script
${quality.caption.score}Caption
${Math.round(quality.retention.predicted_completion_rate * 100)}%Completion

Render payload

${escapeHtml(JSON.stringify(item.render_payload, null, 2))}
`; } catch (error) { $("#variant-inspector").innerHTML = `
${escapeHtml(error.message)}
`; } } function renderCalendar() { const variants = Object.fromEntries(state.variants.map(item => [item.id, item])); $("#calendar-table").innerHTML = state.calendar.sort((a, b) => a.scheduled_at.localeCompare(b.scheduled_at)).map(item => `${date(item.scheduled_at)}${tags([item.platform])}${escapeHtml(variants[item.variant_id]?.title || item.variant_id)}${badge(item.status)}`).join("") || `
No scheduled content
`; } function renderJobs() { const filter = $("#job-filter").value; $("#job-table").innerHTML = state.jobs.filter(item => !filter || item.state === filter).map(item => `${escapeHtml(item.id)}
${escapeHtml(item.idempotency_key || "")}
${escapeHtml(item.kind)}${badge(item.state)}${item.attempts}/${item.max_attempts}${date(item.updated_at)}
${["failed", "cancelled"].includes(item.state) ? `` : ""}${!["published", "ready", "failed", "cancelled"].includes(item.state) ? `` : ""}
`).join("") || `
No matching jobs
`; } function renderAnalytics() { const data = state.overview.analytics || {}, winners = data.winners || []; $("#analytics-metrics").innerHTML = metric("Winners", winners.length, "Viral score ≥ 20") + metric("Platforms", Object.keys(data.platform_average_scores || {}).length, "Reporting sources") + metric("Experiments", state.experiments.length, "Controlled tests") + metric("Top score", winners[0]?.score || 0, winners[0]?.platform || "No data"); $("#winner-board").innerHTML = winners.length ? `
${winners.map(item => `
${escapeHtml(item.title)}${escapeHtml(item.platform)}
${badge(String(item.score))}
`).join("")}
` : `
No winners yet
`; $("#experiment-list").innerHTML = state.experiments.length ? `
${state.experiments.map(item => `
${escapeHtml(item.name)}${escapeHtml(item.variable)} · ${item.variant_ids.length} variants
${badge(item.status)}
`).join("")}
` : `
No experiments
`; } function renderAssets() { $("#asset-table").innerHTML = state.assets.map(item => `${escapeHtml(item.filename)}
${escapeHtml(item.checksum.slice(0, 12))}
${badge(item.kind)}${Math.ceil(item.size_bytes / 1024)} KB${escapeHtml(item.license)}
${escapeHtml(item.attribution)}
${tags(item.tags)}`).join("") || `
No assets
`; } function renderReviews() { const pending = state.variants.filter(item => ["draft", "needs_review"].includes(item.approval_state)); $("#pending-reviews").innerHTML = pending.length ? `
${pending.map(item => `
${escapeHtml(item.title)}${escapeHtml(item.platform)}
${badge(item.approval_state)}
`).join("")}
` : `
Nothing awaiting review
`; } function renderIntegrations() { $("#account-list").innerHTML = state.accounts.length ? `
${state.accounts.map(item => `
${escapeHtml(item.display_name || item.handle)}${escapeHtml(item.platform)} · ${escapeHtml(item.auth_status)}
`).join("")}
` : `
No connected accounts
`; $("#integration-list").innerHTML = state.integrations.length ? `
${state.integrations.map(item => `
${escapeHtml(item.name)}${escapeHtml(item.kind)}
${badge(item.active ? "ready" : "disabled")}
`).join("")}
` : `
No delivery integrations
`; $("#rule-list").innerHTML = state.rules.length ? `
${state.rules.map(item => `
${escapeHtml(item.name)}${escapeHtml(item.event)} · ${item.actions.length} actions
${badge(item.active ? "ready" : "disabled")}
`).join("")}
` : `
No automation rules
`; } async function renderOperations() { const [providers, services] = await Promise.all([api("/automation/providers"), api("/services")]); $("#ops-metrics").innerHTML = metric("Workspaces", state.workspaces.length, "Tenant boundaries") + metric("Accounts", state.accounts.length, "Publishing targets") + metric("Assets", state.assets.length, "Managed media") + metric("Audit events", state.audit.length, "Recent operations"); $("#provider-status").innerHTML = `
${Object.entries(providers.providers).map(([name, item]) => `
${escapeHtml(name)}${escapeHtml(item.model)}
${badge(item.configured ? "ready" : "not_configured")}
`).join("")}
`; $("#service-status").innerHTML = `
${services.services.map(item => `
${escapeHtml(item.title)}${escapeHtml(item.path)}
${badge(item.mounted ? "ready" : item.enabled ? "failed" : "disabled")}
`).join("")}
`; $("#audit-table").innerHTML = state.audit.map(item => `${date(item.created_at)}${escapeHtml(item.actor)}${escapeHtml(item.action)}${escapeHtml(item.resource_type)} · ${escapeHtml(item.resource_id)}`).join("") || `
No audit events
`; } const renderers = {overview: renderOverview, campaigns: renderCampaigns, editor: renderEditor, calendar: renderCalendar, jobs: renderJobs, analytics: renderAnalytics, assets: renderAssets, reviews: renderReviews, integrations: renderIntegrations, operations: renderOperations}; async function refresh() { try { await loadData(); await renderers[state.view]?.(); } catch (error) { $(".status-dot").classList.remove("online"); $("#connection-label").textContent = "Gateway unavailable"; toast(error.message, true); } } function switchView(view) { state.view = view; location.hash = view; $$(".view").forEach(item => item.classList.toggle("active", item.id === `view-${view}`)); $$(".nav-item").forEach(item => item.classList.toggle("active", item.dataset.view === view)); $("#view-title").textContent = view[0].toUpperCase() + view.slice(1); $("#sidebar").classList.remove("open"); renderers[view]?.(); } async function saveVariant(event) { event.preventDefault(); const data = Object.fromEntries(new FormData(event.currentTarget)); data.hashtags = data.hashtags.split(",").map(item => item.trim()).filter(Boolean); data.duration_seconds = Number(data.duration_seconds); try { await api(`/automation/variants/${state.selectedVariantId}`, {method: "PATCH", body: JSON.stringify(data)}); toast("Variant saved"); await refresh(); } catch (error) { toast(error.message, true); } } async function action(name, id) { try { if (name === "process-queue") { const result = await api("/automation/queue/process", {method: "POST"}); toast(`${result.count} job processed`); } if (name === "select-variant") { state.selectedVariantId = id; renderEditor(); return; } if (name === "open-campaign") { $("#editor-campaign-filter").value = id; state.selectedVariantId = state.variants.find(item => item.campaign_id === id)?.id; switchView("editor"); return; } if (name === "generate-ai") await api(`/automation/campaigns/${id}/generate-ai`, jsonPost({})); if (name === "schedule-campaign") await api(`/automation/schedule/apply?campaign_id=${id}`, {method: "POST"}); if (name === "apply-schedule") { const start = $("#calendar-start").value; await api(`/automation/schedule/apply?campaign_id=${$("#calendar-campaign").value}${start ? `&start_at=${encodeURIComponent(new Date(start).toISOString())}` : ""}`, {method: "POST"}); } if (name === "approve-variant") await api(`/automation/variants/${id}/approval`, jsonPost({state: "approved", reviewer: "Studio operator", notes: "Approved in Content Studio"})); if (name === "render-variant") await api(`/automation/variants/${id}/render-job`, {method: "POST"}); if (name === "publish-variant") await api("/automation/publish", jsonPost({variant_id: id, draft: true})); if (name === "rewrite-variant") await api(`/automation/variants/${id}/rewrite`, {method: "POST"}); if (name === "retry-job") await api(`/automation/jobs/${id}/retry`, {method: "POST"}); if (name === "cancel-job") await api(`/automation/jobs/${id}/cancel`, {method: "POST"}); if (name === "delete-asset") await api(`/automation/assets/${id}`, {method: "DELETE"}); if (name === "sync-account") await api(`/automation/accounts/${id}/analytics-sync`, {method: "POST"}); if (name === "evaluate-experiment") await api(`/automation/experiments/${id}/evaluate`, {method: "POST"}); if (name === "new-experiment") { const campaign = state.campaigns[0]; if (!campaign) throw new Error("Create a campaign first"); await api("/automation/experiments", jsonPost({campaign_id: campaign.id, name: `${campaign.name} hook test`, variable: "hook", variant_ids: state.variants.filter(item => item.campaign_id === campaign.id).map(item => item.id)})); } if (name === "new-rule") await api("/automation/rules", jsonPost({name: "Render approved variants", event: "variant_approved", actions: [{type: "render"}], active: true})); toast("Operation completed"); await refresh(); } catch (error) { toast(error.message, true); } } document.addEventListener("click", event => { const nav = event.target.closest("[data-view]"); if (nav) switchView(nav.dataset.view); const dialog = event.target.closest("[data-dialog]"); if (dialog) $(`#${dialog.dataset.dialog}`).showModal(); const button = event.target.closest("[data-action]"); if (button) action(button.dataset.action, button.dataset.id); }); $("#menu-button").addEventListener("click", () => $("#sidebar").classList.toggle("open")); $("#refresh-button").addEventListener("click", refresh); $("#settings-button").addEventListener("click", () => $("#settings-dialog").showModal()); $("#settings-form").addEventListener("submit", event => { event.preventDefault(); state.apiKey = $("#api-key").value.trim(); sessionStorage.setItem("maester_api_key", state.apiKey); $("#settings-dialog").close(); refresh(); }); $("#workspace-filter").addEventListener("change", event => { state.workspaceId = event.target.value; refresh(); }); $("#editor-campaign-filter").addEventListener("change", renderEditor); $("#job-filter").addEventListener("change", renderJobs); $("#campaign-form").addEventListener("submit", async event => { event.preventDefault(); const form = new FormData(event.currentTarget); const data = Object.fromEntries(form); data.platforms = form.getAll("platforms"); data.quantity = Number(data.quantity); data.workspace_id = state.workspaceId || null; data.brand_kit_id ||= null; try { await api("/automation/campaigns/generate", jsonPost(data)); event.currentTarget.reset(); $("#campaign-dialog").close(); await refresh(); } catch (error) { toast(error.message, true); } }); $("#repurpose-form").addEventListener("submit", async event => { event.preventDefault(); const data = Object.fromEntries(new FormData(event.currentTarget)); data.quantity = Number(data.quantity); data.workspace_id = state.workspaceId || null; data.platforms = ["tiktok", "instagram_reels", "youtube_shorts"]; try { await api("/automation/repurpose", jsonPost(data)); event.currentTarget.reset(); $("#repurpose-dialog").close(); await refresh(); } catch (error) { toast(error.message, true); } }); $("#analytics-form").addEventListener("submit", async event => { event.preventDefault(); const data = Object.fromEntries(new FormData(event.currentTarget)); ["views", "likes", "comments", "shares"].forEach(key => data[key] = Number(data[key])); data.completion_rate = Number(data.completion_rate); try { await api("/automation/analytics/events", jsonPost(data)); await refresh(); } catch (error) { toast(error.message, true); } }); $("#asset-form").addEventListener("submit", async event => { event.preventDefault(); const data = new FormData(event.currentTarget); if (state.workspaceId) data.set("workspace_id", state.workspaceId); try { await api("/automation/assets", {method: "POST", body: data}); event.currentTarget.reset(); await refresh(); } catch (error) { toast(error.message, true); } }); $("#review-form").addEventListener("submit", async event => { event.preventDefault(); const form = new FormData(event.currentTarget); const data = {variant_ids: form.getAll("variant_ids"), expires_in_days: Number(form.get("expires_in_days")), created_by: form.get("created_by"), workspace_id: state.workspaceId || null}; try { const result = await api("/automation/reviews", jsonPost(data)); const url = new URL(result.review_url, location.origin).href; $("#review-result").innerHTML = ``; await refresh(); } catch (error) { toast(error.message, true); } }); $("#account-form").addEventListener("submit", async event => { event.preventDefault(); const data = Object.fromEntries(new FormData(event.currentTarget)); data.workspace_id = state.workspaceId || null; data.metadata = {token_env: data.token_env, publish_endpoint: data.publish_endpoint, analytics_endpoint: data.analytics_endpoint}; delete data.token_env; delete data.publish_endpoint; delete data.analytics_endpoint; try { await api("/automation/accounts", jsonPost(data)); $("#account-dialog").close(); await refresh(); } catch (error) { toast(error.message, true); } }); $("#integration-form").addEventListener("submit", async event => { event.preventDefault(); const data = Object.fromEntries(new FormData(event.currentTarget)); data.workspace_id = state.workspaceId || null; try { await api("/automation/integrations", jsonPost(data)); $("#integration-dialog").close(); await refresh(); } catch (error) { toast(error.message, true); } }); $("#api-key").value = state.apiKey; switchView(state.view in renderers ? state.view : "overview"); refresh();