const state = { jobId: null, draft: null, }; const $ = (id) => document.getElementById(id); function pretty(obj) { return JSON.stringify(obj, null, 2); } async function refreshHealth() { const el = $("health"); try { const res = await fetch("/api/health"); const data = await res.json(); const z = data.zhipu_configured ? `智谱 OK (${data.zhipu_model})` : "智谱未配置"; const g = data.geometry?.available ? "几何后端可用" : "几何后端未装(占位 mesh)"; el.textContent = `${z} · ${g}`; el.className = "pill " + (data.zhipu_configured ? "ok" : "warn"); } catch (e) { el.textContent = "后端未连接"; el.className = "pill warn"; } } $("btnParse").addEventListener("click", async () => { const file = $("file").files?.[0]; if (!file) { $("parseOut").textContent = "请先选择 STEP 文件"; $("parseOut").classList.add("error"); return; } $("parseOut").classList.remove("error"); $("parseOut").textContent = "解析中…"; const fd = new FormData(); fd.append("file", file); try { const res = await fetch("/api/parse", { method: "POST", body: fd }); if (!res.ok) throw new Error(await res.text()); const data = await res.json(); state.jobId = data.job_id; $("btnPropose").disabled = false; $("parseOut").textContent = pretty({ job_id: data.job_id, root_name: data.root_name, product_count: data.product_names?.length, product_names: data.product_names, stats: data.stats, }); } catch (e) { $("parseOut").textContent = String(e); $("parseOut").classList.add("error"); } }); $("btnPropose").addEventListener("click", async () => { if (!state.jobId) return; $("proposeOut").classList.remove("error"); $("proposeOut").textContent = "调用智谱中(可能需要几十秒)…"; try { const res = await fetch("/api/propose", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ job_id: state.jobId, profile: $("profile").value, robot_name: $("robotName").value || "A7", extra_hint: $("hint").value || "", }), }); if (!res.ok) throw new Error(await res.text()); const draft = await res.json(); state.draft = draft; $("draftEditor").value = pretty(draft); $("btnExport").disabled = false; $("proposeOut").textContent = `links=${draft.links?.length || 0}, joints=${draft.joints?.length || 0}\n` + (draft.notes || []).map((n) => `- ${n}`).join("\n"); } catch (e) { $("proposeOut").textContent = String(e); $("proposeOut").classList.add("error"); } }); $("btnExport").addEventListener("click", async () => { if (!state.jobId) return; $("exportOut").classList.remove("error"); try { const draft = JSON.parse($("draftEditor").value); state.draft = draft; const res = await fetch("/api/export", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ job_id: state.jobId, draft, include_meshes: true, }), }); if (!res.ok) throw new Error(await res.text()); const meta = await res.json(); $("exportOut").textContent = pretty(meta); const name = draft.name || "robot"; const a = $("dlLink"); a.hidden = false; a.href = `/api/jobs/${state.jobId}/download/${encodeURIComponent(name)}`; a.textContent = `下载 ${name}.urdf`; } catch (e) { $("exportOut").textContent = String(e); $("exportOut").classList.add("error"); } }); refreshHealth();