(function () {
const ASSET_BASE = window.EPIC_ASSET_BASE || "/assets/";
const API_BASE = window.EPIC_API_BASE || "";
const root = document.getElementById("diy-app");
const themeToToken = {
classroom: "storybook",
questbook: "atelier",
comic: "comic",
};
const themes = {
classroom: { label: "Classroom" },
questbook: { label: "Questbook" },
comic: { label: "Comic" },
};
const icons = {
back: '',
wand: '',
};
const params = new URLSearchParams(window.location.search);
let state = {
workflow_draft: {
ordinary_goal: params.get("goal") || "Clean up my room before dinner",
selected_theme_id: normalizedTheme(params.get("theme") || "questbook"),
selected_generation_reference_ids: params.getAll("ref").length
? params.getAll("ref")
: ["parent-photo-demo", "child-photo-demo", "custom-image-reference-demo"],
},
preview: null,
trace_json: {},
save_to_app_status: "coming_soon",
};
function asset(path) {
return `${ASSET_BASE}${path}`;
}
function escapeHtml(value) {
return String(value)
.replace(/&/g, "&")
.replace(//g, ">")
.replace(/"/g, """)
.replace(/'/g, "'");
}
function normalizedTheme(value) {
return themes[value] ? value : "questbook";
}
async function postJson(path, payload) {
const response = await fetch(`${API_BASE}${path}`, {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify(payload),
});
if (!response.ok) throw new Error(`Request failed: ${response.status}`);
return response.json();
}
async function updatePreview() {
try {
state = await postJson("/api/diy-preview", {
ordinary_goal: state.workflow_draft.ordinary_goal,
selected_theme_id: state.workflow_draft.selected_theme_id,
selected_generation_reference_ids: state.workflow_draft.selected_generation_reference_ids,
});
} catch (error) {
state.trace_json = {
error: "local_diy_preview_api_unavailable",
message: String(error.message || error),
save_to_app_status: "coming_soon",
};
}
render();
}
function stepHtml(label, step) {
return `
${escapeHtml(label)}
${escapeHtml(step.model || step.result_asset_ref || step.overlay_text || "Ready")}
${escapeHtml(step.runtime || step.output_size_px || step.fallback_used || "")}
`;
}
function nativeWorkflowHtml() {
return `
The full Gradio workflow canvas is beta and opens separately; this surface is the V2 mobile mirror used for local UAT.
`;
}
function render() {
if (!root) return;
const draft = state.workflow_draft;
const preview = state.preview;
const trace = state.trace_json || {};
document.documentElement.dataset.theme = themeToToken[normalizedTheme(draft.selected_theme_id)];
root.innerHTML = `
${nativeWorkflowHtml()}
V2 Preview Inputs
${escapeHtml(themes[normalizedTheme(draft.selected_theme_id)].label)}
${Object.entries(themes)
.map(([id, item]) => `
`)
.join("")}
${draft.selected_generation_reference_ids.map((ref) => `${escapeHtml(ref)}`).join("")}
Pipeline
Quality
${stepHtml("Text / Quest JSON", trace.text_step || {})}
${stepHtml("Image Request / Result", trace.image_step || {})}
${stepHtml("Audio Request / Result", trace.audio_step || {})}
${stepHtml("Composed Card", trace.composed_card_step || {})}
${preview ? `
${escapeHtml(preview.generated_title)}
${escapeHtml(preview.generated_narration)}
${escapeHtml(preview.generated_reward_label)}
` : ""}
Trace
Model details
${escapeHtml(JSON.stringify(trace.quality_mode || {}, null, 2))}
${escapeHtml(JSON.stringify(trace, null, 2))}
`;
}
document.addEventListener("click", (event) => {
const button = event.target.closest("[data-action]");
if (!button || button.disabled) return;
if (button.dataset.action === "diy-theme") {
state.workflow_draft.selected_theme_id = normalizedTheme(button.dataset.theme);
updatePreview();
}
if (button.dataset.action === "update-diy-preview") updatePreview();
});
document.addEventListener("input", (event) => {
const field = event.target.closest("[data-field]");
if (!field) return;
if (field.dataset.field === "diy-goal") state.workflow_draft.ordinary_goal = field.value;
});
render();
updatePreview();
})();