const runBtn = document.getElementById("run");
const resultsDiv = document.getElementById("results");
const promptEl = document.getElementById("prompt");
const charCount = document.getElementById("char-count");
const resultsMeta = document.getElementById("results-meta");
// Palette cycling for cards
const CARD_ACCENTS = [
{ gradient: "linear-gradient(90deg, #7b74c7, #a09ace)", dot: "#7b74c7" },
{ gradient: "linear-gradient(90deg, #3aaa8a, #5bbfa6)", dot: "#3aaa8a" },
{ gradient: "linear-gradient(90deg, #c49a3a, #d4b46a)", dot: "#c49a3a" },
{ gradient: "linear-gradient(90deg, #b85c8a, #cc85a8)", dot: "#b85c8a" },
{ gradient: "linear-gradient(90deg, #4a7ec7, #7aa3d4)", dot: "#4a7ec7" },
];
// Live character count
promptEl.addEventListener("input", () => {
const n = promptEl.value.length;
charCount.textContent = `${n} character${n !== 1 ? "s" : ""}`;
});
// Allow Ctrl+Enter to submit
promptEl.addEventListener("keydown", (e) => {
if (e.key === "Enter" && (e.ctrlKey || e.metaKey)) runBtn.click();
});
function makeSkeletonCard(modelName, idx) {
const accent = CARD_ACCENTS[idx % CARD_ACCENTS.length];
const card = document.createElement("div");
card.className = "card";
card.dataset.model = modelName;
card.style.setProperty("--card-accent", accent.gradient);
card.style.setProperty("--dot-color", accent.dot);
card.style.animationDelay = `${idx * 60}ms`;
card.innerHTML = `
<div class="card-header">
<span class="model-name">
<span class="model-dot"></span>
${modelName}
</span>
<span class="status-badge loading">Thinking…</span>
</div>
<div class="card-output">
<div class="skeleton skeleton-line"></div>
<div class="skeleton skeleton-line"></div>
<div class="skeleton skeleton-line"></div>
<div class="skeleton skeleton-line"></div>
</div>
`;
return card;
}
function updateCard(card, result) {
const badge = card.querySelector(".status-badge");
const output = card.querySelector(".card-output");
if (result.error) {
badge.className = "status-badge err";
badge.textContent = "Error";
output.className = "card-output error-text";
output.textContent = result.error;
} else {
badge.className = "status-badge ok";
badge.textContent = "Done";
output.className = "card-output";
output.textContent = result.output || "(no output)";
}
}
runBtn.addEventListener("click", async () => {
const prompt = promptEl.value.trim();
if (!prompt) {
promptEl.focus();
promptEl.style.outline = "2px solid rgba(248,113,113,0.6)";
setTimeout(() => { promptEl.style.outline = ""; }, 1200);
return;
}
// UI → loading state
runBtn.disabled = true;
runBtn.classList.add("loading");
runBtn.querySelector("svg").innerHTML =
'<path d="M12 2v4M12 18v4M4.93 4.93l2.83 2.83M16.24 16.24l2.83 2.83M2 12h4M18 12h4M4.93 19.07l2.83-2.83M16.24 7.76l2.83-2.83" stroke-linecap="round"/>';
resultsMeta.classList.remove("visible");
// ── Fetch config to know which models to expect ──
let modelNames = [];
try {
const cfg = await fetch("/prompts/motion-description.txt"); // just to warm up; we don't need it here
// We'll infer model names from the API response instead
} catch (_) {}
// Show placeholder skeleton cards (3 by default; replaced on response)
resultsDiv.innerHTML = "";
const placeholderCount = 3;
const placeholders = [];
for (let i = 0; i < placeholderCount; i++) {
const card = makeSkeletonCard(`model-${i + 1}`, i);
resultsDiv.appendChild(card);
placeholders.push(card);
}
const t0 = Date.now();
try {
const resp = await fetch("/api/run-all", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ prompt }),
});
if (!resp.ok) throw new Error(`Server error: ${resp.status}`);
const results = await resp.json();
const elapsed = ((Date.now() - t0) / 1000).toFixed(1);
// Clear placeholders and render real cards
resultsDiv.innerHTML = "";
results.forEach((r, idx) => {
const accent = CARD_ACCENTS[idx % CARD_ACCENTS.length];
const card = document.createElement("div");
card.className = "card";
card.style.setProperty("--card-accent", accent.gradient);
card.style.setProperty("--dot-color", accent.dot);
card.style.animationDelay = `${idx * 80}ms`;
const isError = !!r.error;
card.innerHTML = `
<div class="card-header">
<span class="model-name">
<span class="model-dot"></span>
${r.model}
</span>
<span class="status-badge ${isError ? "err" : "ok"}">${isError ? "Error" : "Done"}</span>
</div>
<div class="card-output ${isError ? "error-text" : ""}">
${isError ? r.error : (r.output || "(no output)")}
</div>
`;
resultsDiv.appendChild(card);
});
resultsMeta.textContent = `${results.length} model${results.length !== 1 ? "s" : ""} · ${elapsed}s`;
resultsMeta.classList.add("visible");
} catch (err) {
resultsDiv.innerHTML = `
<div class="card" style="--card-accent: linear-gradient(90deg,#f87171,#ef4444); --dot-color:#f87171; grid-column: 1/-1;">
<div class="card-header">
<span class="model-name"><span class="model-dot"></span>Request Failed</span>
<span class="status-badge err">Error</span>
</div>
<div class="card-output error-text">${err.message}</div>
</div>
`;
} finally {
runBtn.disabled = false;
runBtn.classList.remove("loading");
runBtn.querySelector("svg").innerHTML =
'<polygon points="5 3 19 12 5 21 5 3"/>';
}
});