/** * GameForge Frontend * Custom HTML/CSS/JS app powered by gradio.Server + ZeroGPU. */ import { Client } from "https://cdn.jsdelivr.net/npm/@gradio/client/dist/index.min.js"; // ============================================================================ // State // ============================================================================ let client = null; let registryData = []; let pipelines = []; // ============================================================================ // Gradio Client Connection // ============================================================================ async function connect() { try { client = await Client.connect(window.location.origin); toast("Connected to GameForge", "success"); await loadDashboard(); } catch (e) { toast("Failed to connect: " + e.message, "error"); } } // ============================================================================ // API Helpers // ============================================================================ async function api(endpoint, params = {}) { if (!client) await connect(); try { const result = await client.predict(endpoint, params); return result.data; } catch (e) { toast(`API error (${endpoint}): ${e.message}`, "error"); throw e; } } // ============================================================================ // Navigation // ============================================================================ document.querySelectorAll(".nav-item").forEach(item => { item.addEventListener("click", () => { document.querySelectorAll(".nav-item").forEach(i => i.classList.remove("active")); document.querySelectorAll(".tab-content").forEach(t => t.classList.remove("active")); item.classList.add("active"); const tab = document.getElementById("tab-" + item.dataset.tab); if (tab) tab.classList.add("active"); // Load data for tab if (item.dataset.tab === "models") loadModels(); if (item.dataset.tab === "pipelines") loadPipelines(); if (item.dataset.tab === "assets") loadAssets(); if (item.dataset.tab === "batch") loadBatchPipelines(); }); }); // ============================================================================ // Dashboard // ============================================================================ async function loadDashboard() { try { const [reg, pipes, assets] = await Promise.all([ api("/registry_info"), api("/list_pipelines"), api("/list_assets"), ]); registryData = reg.models || []; pipelines = pipes || []; document.getElementById("stat-models").textContent = registryData.length; document.getElementById("stat-free").textContent = registryData.filter(m => m.free).length; document.getElementById("stat-pipelines").textContent = pipelines.length; document.getElementById("stat-assets").textContent = assets.length; // Quick generate buttons document.querySelectorAll(".quick-gen-card").forEach(card => { card.addEventListener("click", () => { const typeMap = { character: "image", prop: "image", environment: "image", npc_voice: "voice", audio: "music", }; const type = typeMap[card.dataset.pipeline] || "image"; document.getElementById("gen-type").value = type; document.getElementById("gen-prompt").value = card.dataset.prompt; switchTab("generate"); document.getElementById("gen-btn").click(); }); }); } catch (e) { console.error("Dashboard load failed:", e); } } function switchTab(name) { document.querySelector(`.nav-item[data-tab="${name}"]`)?.click(); } // ============================================================================ // Generate // ============================================================================ const genType = document.getElementById("gen-type"); const genBtn = document.getElementById("gen-btn"); const genPrompt = document.getElementById("gen-prompt"); const genPreview = document.getElementById("gen-preview-box"); const genInfo = document.getElementById("gen-result-info"); genType.addEventListener("change", () => { const is3d = genType.value === "3d"; document.getElementById("ref-image-group").style.display = is3d ? "block" : "none"; document.getElementById("neg-prompt-group").style.display = (genType.value === "image") ? "block" : "none"; }); genBtn.addEventListener("click", async () => { const prompt = genPrompt.value.trim(); if (!prompt) { toast("Enter a prompt", "error"); return; } const type = genType.value; const btnText = genBtn.querySelector(".btn-text"); const btnLoad = genBtn.querySelector(".btn-loading"); btnText.style.display = "none"; btnLoad.style.display = "inline"; genBtn.disabled = true; genPreview.innerHTML = '

Generating...

'; try { const endpointMap = { image: "/generate_image", voice: "/generate_voice", music: "/generate_music", video: "/generate_video", sfx: "/generate_sfx", "3d": "/generate_3d", }; const endpoint = endpointMap[type]; let result; if (type === "image") { const neg = document.getElementById("gen-negative").value; result = await client.predict(endpoint, { prompt, negative_prompt: neg, steps: 4 }); } else { result = await client.predict(endpoint, { prompt, text: prompt }); } const fileData = result.data[0]; if (fileData && fileData.url) { showResult(fileData, type); toast("Generated!", "success"); } else { genPreview.innerHTML = '

Generation failed

'; } } catch (e) { genPreview.innerHTML = `

${e.message}

`; } finally { btnText.style.display = "inline"; btnLoad.style.display = "none"; genBtn.disabled = false; } }); function showResult(fileData, type) { const url = fileData.url; let html = ""; if (type === "image") { html = `Generated`; } else if (type === "video") { html = ``; } else if (["voice", "music", "sfx"].includes(type)) { html = ``; } else { html = `
📦

3D Model Generated

`; } genPreview.innerHTML = html; genInfo.style.display = "flex"; document.getElementById("gen-download").onclick = () => { const a = document.createElement("a"); a.href = url; a.download = `gameforge_${type}_${Date.now()}`; a.click(); }; } // ============================================================================ // Models // ============================================================================ async function loadModels() { if (!registryData.length) { const reg = await api("/registry_info"); registryData = reg.models || []; } const typeFilter = document.getElementById("model-filter-type"); const freeOnly = document.getElementById("model-filter-free"); const safeOnly = document.getElementById("model-filter-safe"); // Populate type filter const types = [...new Set(registryData.map(m => m.asset_type))]; if (typeFilter.options.length <= 1) { types.forEach(t => { const opt = document.createElement("option"); opt.value = t; opt.textContent = t; typeFilter.appendChild(opt); }); } function render() { let models = registryData; if (typeFilter.value) models = models.filter(m => m.asset_type === typeFilter.value); if (freeOnly.checked) models = models.filter(m => m.free); if (safeOnly.checked) models = models.filter(m => m.commercial_safe); const tbody = document.getElementById("models-tbody"); tbody.innerHTML = models.map(m => ` ${m.asset_type} ${m.variant} ${m.model} ${m.type} ${m.license} ${m.hardware} ${m.free ? 'FREE' : 'PAID'} ${m.commercial_safe ? 'SAFE' : 'CHECK'} `).join(""); } typeFilter.onchange = freeOnly.onchange = safeOnly.onchange = render; render(); } // ============================================================================ // Pipelines // ============================================================================ async function loadPipelines() { if (!pipelines.length) { pipelines = await api("/list_pipelines"); } const grid = document.getElementById("pipeline-grid"); grid.innerHTML = pipelines.map(p => `

${p.name}

${p.description}

📋 ${p.steps} steps v${p.version}
`).join(""); } // ============================================================================ // Assets // ============================================================================ async function loadAssets() { const assets = await api("/list_assets"); const grid = document.getElementById("assets-grid"); if (!assets.length) { grid.innerHTML = '

No assets generated yet. Go to Generate to create some!

'; return; } grid.innerHTML = assets.map(a => `
${a.name}
${a.format} · ${(a.size / 1024).toFixed(1)} KB
`).join(""); } document.getElementById("asset-refresh")?.addEventListener("click", loadAssets); // ============================================================================ // Batch // ============================================================================ async function loadBatchPipelines() { if (!pipelines.length) { pipelines = await api("/list_pipelines"); } const select = document.getElementById("batch-pipeline"); select.innerHTML = pipelines.map(p => `` ).join(""); } document.getElementById("batch-count")?.addEventListener("input", (e) => { document.getElementById("batch-count-val").textContent = e.target.value; }); document.getElementById("batch-btn")?.addEventListener("click", async () => { const pipeline = document.getElementById("batch-pipeline").value; const prompt = document.getElementById("batch-prompt").value; const count = parseInt(document.getElementById("batch-count").value); if (!prompt) { toast("Enter a prompt", "error"); return; } toast(`Generating ${count} variants...`, "info"); // Run variants sequentially const results = document.getElementById("batch-results"); results.innerHTML = ""; for (let i = 0; i < count; i++) { const card = document.createElement("div"); card.className = "asset-card"; card.innerHTML = `
Variant ${i + 1}
Generating...
`; results.appendChild(card); try { // Each variant gets a slightly different prompt const variantPrompt = `${prompt}, style variation ${i + 1}`; // This would call the pipeline endpoint card.querySelector(".meta").textContent = "Queued"; } catch (e) { card.querySelector(".meta").textContent = "Failed: " + e.message; } } }); // ============================================================================ // Toast Notifications // ============================================================================ function toast(message, type = "info") { const container = document.getElementById("toast-container"); const el = document.createElement("div"); el.className = `toast toast-${type}`; el.textContent = message; container.appendChild(el); setTimeout(() => el.remove(), 4000); } // ============================================================================ // Init // ============================================================================ connect();