← Commit history

2.0.79: web-control demo gains the AI-thread card (named thread + staged prompt + measured bar + evidence)

John Lauer ·7a73a5d783 ·1mo ago ·parent e53a0bd
1 file changed +78
web-control-demo.html+78
@@ -181,6 +181,19 @@   <div class="grp">Fusion surfaces - the SAME selected part (.lbr + .step from its page), one first-class verb per content type (fusion_show_*, v1.9.10+). Component surfaces stage the library automatically; design surfaces additionally need the part project staged (fusion-bridge #30).</div>   <div class="cards" id="grp-fusion"></div> +  <div class="grp">AI threads - hand a written prompt to the AI running in Hydrogen. The wiki composes the prompt; Adom Bridge opens a NAMED thread and stages it. It is left UNSENT by default: you read it and press enter. Evidence is a screenshot of the thread itself.</div>+  <div class="bar" id="ai-bar">+    <span>Thread name:</span>+    <input id="ai-name" type="text" value="wiki thread" style="width:170px" />+    <span>Prompt:</span>+    <input id="ai-prompt" type="text" style="flex:1;min-width:260px"+           value="Open the KiCad bridge page on the wiki and tell me what it does." />+    <label style="font-size:12px;display:flex;align-items:center;gap:4px">+      <input id="ai-submit" type="checkbox" /> send it (skip the human read)+    </label>+  </div>+  <div class="cards" id="grp-ai"></div>+   <div class="grp">Multi-machine - fan a verb across every connected computer</div>   <div class="cards" id="grp-multi"></div> @@ -1918,6 +1931,71 @@ for (const w of mine) {       });     };   });+  // ── AI thread card ────────────────────────────────────────────────────────+  // The whole point of the demo: a wiki page composes a prompt and hands it to+  // the AI on the user's machine. Same shape as the KiCad/Fusion cards - a slow+  // verb, a bar polled ALONGSIDE it, and a screenshot as the receipt.+  var cAi = card("grp-ai", "verb", "Open a named AI thread with this prompt");+  cAi.btn.onclick = function () {+    var name = (document.getElementById("ai-name").value || "").trim();+    var prompt = (document.getElementById("ai-prompt").value || "").trim();+    var submit = !!document.getElementById("ai-submit").checked;+    if (!prompt) { cAi.res.className = "res bad"; cAi.res.textContent = "type a prompt first"; return; }++    cAi.btn.disabled = true;+    cAi.res.className = "res run";+    cAi.res.textContent = submit ? "opening a thread and SENDING..." : "opening a thread, staging the prompt...";+    cardLog(cAi.card).textContent = "";+    shotsBox(cAi.card).innerHTML = "";+    clog(cAi, "opening a thread named " + JSON.stringify(name || "Untitled"));+    if (!submit) clog(cAi, "submit is OFF - the prompt waits in the composer for you");++    // Poll the fast companion WHILE the slow verb runs. Awaiting the slow call+    // first would collapse the bar to a single 100% frame.+    startSurfaceProgress(cAi, "ai_thread");++    postVerb("ai_thread", {+      prompt: prompt,+      name: name,+      submit: submit,+      tags: ["source:web-control"],+      reason: "A wiki page asked to open an AI thread with a written prompt"+    }).then(function (res) {+      var j = res.j || {};+      finishSurfaceBar(cAi);+      cAi.btn.disabled = false;+      if (!j.ok && !j.success) {+        cAi.res.className = "res bad";+        cAi.res.textContent = (j.errorCode || "failed") + ": " + String(j.error || "").slice(0, 160);+        if (j._hint) clog(cAi, "hint: " + j._hint);+        return;+      }+      cAi.res.className = "res ok";+      cAi.res.textContent = (j.submitted ? "sent to " : "staged in ") + (j.name || "thread")+        + " (" + (j.seconds || "?") + "s)";+      clog(cAi, "thread " + (j.tid || "?") + (j.lateAttach ? " (attached late)" : ""));+      // Say which surface the evidence came from. A whole-window grab instead of+      // the panel is still evidence, but the viewer deserves to know which they+      // are looking at rather than inferring it from the pixels.+      if (j.evidenceKind) clog(cAi, "evidence: " + j.evidenceKind);+      if (j.evidenceNote) clog(cAi, "note: " + j.evidenceNote);+      // The verb returns a PATH on the user's machine; read it back as base64 so+      // the page can actually show it (screenshot_base64 re-reads a prior shot).+      if (j.evidence) {+        postVerb("screenshot_base64", { path: j.evidence, maxWidth: 900 }).then(function (r2) {+          var b64 = (r2.j && (r2.j.image || (r2.j.data && r2.j.data.image))) || null;+          if (b64) addShot(cAi.card, b64, "AI thread (" + (j.evidenceKind || "capture") + ")");+          else clog(cAi, "evidence saved on the PC: " + j.evidence);+        }).catch(function () { clog(cAi, "evidence saved on the PC: " + j.evidence); });+      }+    }).catch(function (e) {+      finishSurfaceBar(cAi);+      cAi.btn.disabled = false;+      cAi.res.className = "res bad";+      cAi.res.textContent = "failed: " + e;+    });+  };+   var cKState = card("grp-kicad", "read", "Show KiCad state (every window)");   cKState.btn.onclick = function () { showSurface(cKState, { showCmd: "kicad_state", showArgs: { maxWidth: 900 }, stateCmd: "kicad_state", note: "captures every KiCad window as a screenshot" }); };