const $ = (id)=>document.getElementById(id); async function api(path, opts){ const res = await fetch(path, opts); if(!res.ok){ const t = await res.text(); throw new Error(`${res.status} ${res.statusText}: ${t}`); } return res; } async function refresh(){ const res = await api('/api/templates'); const items = await res.json(); const list = $('list'); list.innerHTML = ''; for(const t of items){ const li = document.createElement('li'); const name = document.createElement('span'); name.textContent = `${t.id} — ${t.name ?? ''}`; const loadBtn = document.createElement('button'); loadBtn.textContent = 'Load'; loadBtn.onclick = ()=>loadTemplate(t.id); li.appendChild(name); li.appendChild(loadBtn); list.appendChild(li); } } async function loadTemplate(id){ const res = await api(`/api/templates/${encodeURIComponent(id)}`); const tpl = await res.json(); $('tplId').value = tpl.id; $('json').value = JSON.stringify(tpl, null, 2); } function newTemplate(){ const id = `tpl_${Math.random().toString(16).slice(2,8)}`; $('tplId').value = id; $('json').value = JSON.stringify({ id, name: "My Template", width: 1280, height: 720, fps: 30, duration_sec: 5, bg_color: "#111827", text: { value: "{name}", x: 80, y: 600, fontsize: 48 } }, null, 2); } async function saveTemplate(){ const id = $('tplId').value.trim(); if(!id) return alert('Template id required'); let body; try{ body = JSON.parse($('json').value); } catch(e){ return alert('Invalid JSON'); } body.id = id; await api(`/api/templates/${encodeURIComponent(id)}`, { method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify(body) }); await refresh(); } async function render(){ const tplId = $('tplId').value.trim(); if(!tplId) return alert('Load or create a template first'); const name = $('name').value || 'Friend'; $('renderStatus').textContent = 'Queued…'; const res = await api('/api/render', { method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify({ template_id: tplId, variables: { name } }) }); const job = await res.json(); poll(job.job_id); } async function poll(jobId){ while(true){ const res = await api(`/api/jobs/${encodeURIComponent(jobId)}`); const job = await res.json(); $('renderStatus').textContent = `${job.status}${job.detail?` — ${job.detail}`:''}`; if(job.status === 'done'){ const url = `/api/jobs/${encodeURIComponent(jobId)}/download`; $('player').src = url; break; } if(job.status === 'error') break; await new Promise(r=>setTimeout(r, 1200)); } } $('refresh').onclick = refresh; $('new').onclick = ()=>{ newTemplate(); }; $('save').onclick = saveTemplate; $('render').onclick = render; refresh().catch(e=>$('renderStatus').textContent = e.message);