diff --git a/static/bte.js b/static/bte.js index 33b0c2a..4695512 100644 --- a/static/bte.js +++ b/static/bte.js @@ -48,6 +48,10 @@ selected: null, // asset id pollTimer: null, log: [], + // SKU options surfaced in the toolbar dropdown. Defaults to the bundled + // placeholders until _fetchSkuOptions replaces them with real top-selling + // offerings from Adwright's TopRecipes RPC. + skus: PLACEHOLDER_SKUS.slice(), }; // ── Public namespace ───────────────────────────────────────────────────── @@ -91,6 +95,37 @@ } overlay.hidden = false; _refreshGrid(); + _fetchSkuOptions(); + } + + // Pull top-selling offerings from Adwright (TopRecipes) so the SKU + // dropdown reflects what's actually shipping, not the bundled PLACEHOLDER + // list. Best-effort — falls through to placeholders on any failure. + function _fetchSkuOptions() { + fetch("/api/adwright/last-panel-update?session_id=&since=0&tool=adwright_list_recipes") + .then((r) => r.json()) + .then((r) => { + const recipes = r && r.update && r.update.payload && r.update.payload.recipes; + if (!Array.isArray(recipes) || !recipes.length) return; + state.skus = recipes.map((rec) => ({ + id: String(rec.product_id || rec.id || ""), + name: rec.name || "", + })).filter((s) => s.id && s.name); + _refreshSkuOptions(); + }) + .catch(() => { /* fallback: keep placeholders */ }); + } + + function _refreshSkuOptions() { + const sel = document.getElementById("svrntyBteSku"); + if (!sel) return; + const current = sel.value; + let html = ''; + state.skus.forEach((s) => { + html += '"; + }); + sel.innerHTML = html; + if (current) sel.value = current; } function _closeOverlay() { @@ -148,9 +183,10 @@ state.family = v; _updateToolbarPressed(); _updateGenerateEnabled(); _refreshGrid(); })); - // SKU dropdown + // SKU dropdown — populated from state.skus (placeholder at boot, + // replaced by real top-selling offerings via _fetchSkuOptions). const skuOpts = [{ value: "", label: "— pick SKU —" }].concat( - PLACEHOLDER_SKUS.map((s) => ({ value: s.id, label: s.name }))); + state.skus.map((s) => ({ value: s.id, label: s.name }))); bar.appendChild(_labeled("SKU", _select("svrntyBteSku", skuOpts, state.skuId || "", (v) => { state.skuId = v || null; _updateGenerateEnabled(); }))); @@ -315,7 +351,7 @@ // ── Generate flow ──────────────────────────────────────────────────────── function _onGenerate() { if (!state.family || !state.skuId) return; - const sku = PLACEHOLDER_SKUS.find((s) => s.id === state.skuId); + const sku = state.skus.find((s) => s.id === state.skuId); const recipeLabel = `${state.family}__${state.mode}__${state.media}`; const body = { brandId: state.brand, @@ -496,13 +532,35 @@ // ── CMO chat handoff ───────────────────────────────────────────────────── function _openCmoChat() { - // Honest minimum: surface the WebUI chat panel (whichever profile is - // active). Full CMO-scoped iframe / component reuse is COMMAND-CENTER-PRD - // §6.5 Phase E — not v1 of this panel. + // Pre-fill the chat textarea with BTE state context so the operator + // doesn't have to retype "I'm in BTE, brand Plan B, mode Polished, …". + // Don't auto-send — user reviews + edits + hits send. Full CMO-scoped + // iframe is COMMAND-CENTER-PRD §6.5 Phase E (not v1). + const modeL = MODES.find((m) => m.slug === state.mode); + const familyL = FAMILIES.find((f) => f.slug === state.family); + const sku = state.skus.find((s) => s.id === state.skuId); + const lines = ["I'm working in BTE Command Center. Help me iterate."]; + lines.push("- Brand: " + state.brand); + if (modeL) lines.push("- Mode: " + modeL.label); + lines.push("- Media: " + state.media); + if (familyL) lines.push("- Recipe family: " + familyL.label); + if (sku) lines.push("- SKU: " + sku.name); + lines.push("- Variants: " + state.variants); + if (state.selected) lines.push("- Selected asset: " + state.selected); + const prompt = lines.join("\n") + "\n\nWhat should I change next?"; + _closeOverlay(); if (typeof window.switchPanel === "function") { try { window.switchPanel("chat", { fromRailClick: true }); } catch (_) { /* ignore */ } } + // Defer pre-fill so the chat panel + #msg textarea mount first. + setTimeout(() => { + const msg = document.getElementById("msg"); + if (!msg) return; + msg.value = prompt; + try { msg.dispatchEvent(new Event("input", { bubbles: true })); } catch (_) {} + try { msg.focus(); } catch (_) {} + }, 200); } // ── Proxy helpers ────────────────────────────────────────────────────────