Ask for changes in plain words. The agent edits the scene with the same tools as you, and Undo takes any of its steps back.
' + '$1").replace(/\*\*([^*]+)\*\*/g, "$1").replace(/(^|[\s(])\*([^*\s][^*]*)\*/g, "$1$2");
}
src.replace(/\r/g, "").split("\n").forEach(function (line) {
if (/^\s*```/.test(line)) {
if (code === null) { close(); code = []; } else { html += "" + esc(code.join("\n")) + ""; code = null; }
return;
}
if (code !== null) { code.push(line); return; }
var m = /^\s*(?:([-*•])|(\d+)[.)])\s+(.*)$/.exec(line);
if (m) {
var kind = m[1] ? "ul" : "ol";
if (list !== kind) { close(); html += "<" + kind + ">"; list = kind; }
html += "" + inline(h[1]) + "
" : "" + inline(line) + "
"; }); close(); if (code !== null) html += "" + esc(code.join("\n")) + "";
return html;
}
/** `ids` of objects in the scene become links that select them. */
function linkIds(el) {
var s = st();
Array.prototype.forEach.call(el.querySelectorAll("code"), function (c) {
var id = c.textContent.trim();
if (s.obj(id)) { c.classList.add("ag-obj"); c.setAttribute("data-obj", id); c.title = "Select and zoom to it"; }
});
}
function botView() {
var d = addMsg("ag-msg ag-bot ag-live", '' + esc(id) + "", sp = d.querySelector("summary span");
// "id: x, y" -> the id itself becomes the link; else it follows the summary
if (sp.textContent.indexOf(id) === 0) { sp.textContent = sp.textContent.slice(id.length); sp.insertAdjacentHTML("beforebegin", link); }
else em.insertAdjacentHTML("beforebegin", link);
}
}
};
}
function busy(on) {
A.busy = on;
var b = $("agsend");
b.textContent = on ? "Stop" : "Send";
b.classList.toggle("ag-stop", on);
b.title = on ? "Stop the agent" : "Send (Enter)";
$("agwin").classList.toggle("ag-busy", on);
}
// ------------------------------------------------------------ settings form
function who() {
var b = $("agwho");
if (!A.cfg) { b.innerHTML = "…"; return; }
var ok = !A.modelsErr && A.models.length;
b.innerHTML = '' + esc((model() || "no model") + " · " + host());
b.title = A.modelsErr ? "Not connected: " + A.modelsErr : "Model and endpoint (⚙ to change)";
}
function fillSettings() {
var c = A.cfg, f = $("agsetf");
$("agurl").value = c.url;
$("agurl").disabled = c.url_from === "env";
$("agurl").title = c.url_from === "env" ? "Set by ISOKIT_LLM_URL" : "OpenAI-compatible base URL, usually ending in /v1";
$("agkey").value = "";
$("agkey").disabled = c.key_from === "env";
$("agkey").placeholder = c.key_from === "env" ? "set by ISOKIT_LLM_KEY" : c.has_key ? "saved (type to replace)" : "none (most hosted endpoints need one)";
$("agforget").hidden = !c.has_key || c.key_from === "env";
var sel = $("agmodel"), names = A.models.slice();
if (c.model && names.indexOf(c.model) < 0) names.unshift(c.model);
sel.innerHTML = '" +
names.map(function (m) { return ""; }).join("");
$("agthink").checked = c.think !== false;
$("agsetnote").innerHTML = web()
? "Kept in this browser only." + (relay() ? " " + esc(relay().url) + " is this site's relay to " + esc(relay().upstream) + " (entering that URL uses the relay too)." : "")
: "Kept by the Studio server" + (c.file ? " in " + esc(c.file) + "" : "") + "; the key never reaches this page.";
setMsg(A.modelsErr ? A.modelsErr : A.models.length ? "Connected: " + A.models.length + " model" + (A.models.length === 1 ? "" : "s") : "", !!A.modelsErr);
f.hidden = false;
$("agset").setAttribute("aria-pressed", "true");
}
function setMsg(t, bad) { var m = $("agsetmsg"); m.textContent = t; m.classList.toggle("ag-badtext", !!bad); m.title = t; }
function hideSettings() { $("agsetf").hidden = true; $("agset").setAttribute("aria-pressed", "false"); }
function saveSettings(e) {
e.preventDefault();
var v = { model: $("agmodel").value, think: $("agthink").checked };
if (!$("agurl").disabled) v.url = $("agurl").value.trim();
if ($("agkey").value.trim()) v.key = $("agkey").value.trim();
setMsg("Saving…");
saveCfg(v).then(connect).then(function () { fillSettings(); if (!A.modelsErr) setTimeout(hideSettings, 700); }, function (err) { setMsg(String(err && err.message || err), true); });
}
function connect() {
return loadModels().catch(function () { /* shown in the form and the dot */ }).then(who);
}
// ------------------------------------------------------------ window
function geom() { var g = A.ui.geom || {}; return { r: g.r === undefined ? 12 : g.r, b: g.b === undefined ? 12 : g.b, w: g.w || 390, h: g.h || 480 }; }
function place() {
var win = $("agwin");
if (win.hidden) return;
var c = win.parentNode, foot = c.querySelector(".st-status"), fh = foot ? foot.offsetHeight : 0;
var cw = c.clientWidth, ch = c.clientHeight - fh, g = geom();
var w = Math.max(280, Math.min(g.w, cw - 8)), h = Math.max(220, Math.min(g.h, ch - 8));
var r = Math.max(4, Math.min(g.r, cw - w - 4)), b = Math.max(4, Math.min(g.b, ch - h - 4));
win.style.width = w + "px"; win.style.height = h + "px";
win.style.right = r + "px"; win.style.bottom = (b + fh) + "px";
}
function dragger(el, fn) {
el.addEventListener("pointerdown", function (e) {
if (e.button !== 0 || e.target.closest("button, input, select, a, code")) return;
e.preventDefault();
var g0 = geom(), x0 = e.clientX, y0 = e.clientY;
el.setPointerCapture(e.pointerId);
function mv(ev) { A.ui.geom = fn(g0, ev.clientX - x0, ev.clientY - y0); place(); }
function up() {
el.removeEventListener("pointermove", mv); el.removeEventListener("pointerup", up); el.removeEventListener("pointercancel", up);
var win = $("agwin"), foot = win.parentNode.querySelector(".st-status");
A.ui.geom = { w: win.offsetWidth, h: win.offsetHeight, r: parseFloat(win.style.right), b: parseFloat(win.style.bottom) - (foot ? foot.offsetHeight : 0) };
saveUi();
}
el.addEventListener("pointermove", mv); el.addEventListener("pointerup", up); el.addEventListener("pointercancel", up);
});
}
function open(on) {
var win = $("agwin");
win.hidden = !on;
$("agent").setAttribute("aria-pressed", String(on));
A.ui.open = on; saveUi();
if (!on) return;
place();
$("agtext").focus();
if (!A.started) {
A.started = true;
ready().then(function () { if (A.modelsErr && !A.msgs.length) fillSettings(); })
.catch(function (e) { error("The agent could not start: " + (e && e.message || e)); });
}
}
function buildUI() {
var btn = document.createElement("button");
btn.type = "button"; btn.id = "agent"; btn.className = "ag-open";
btn.title = "Chat with an AI agent that edits this scene";
btn.setAttribute("aria-pressed", "false");
btn.textContent = "✦ Agent";
$("preview").parentNode.insertBefore(btn, $("preview"));
var win = document.createElement("section");
win.className = "ag-win"; win.id = "agwin"; win.hidden = true;
win.setAttribute("aria-label", "Agent");
win.innerHTML =
'