/**
* 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 = '
';
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 = '';
}
} catch (e) {
genPreview.innerHTML = ``;
} 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 = `
`;
} else if (type === "video") {
html = ``;
} else if (["voice", "music", "sfx"].includes(type)) {
html = ``;
} else {
html = ``;
}
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();