comparison infinite_canvas/web_surface_web.c @ 280:49e9e591c9bb

Add persistent dictation, prewarmed WebRTC speech input, Copilot SDK routing, animated conversation lifecycle controls, parking, and architecture coverage.
author MrJuneJune <me@mrjunejune.com>
date Tue, 18 Aug 2026 19:14:53 -0700
parents 8d560f50ed4c
children
comparison
equal deleted inserted replaced
279:b3b547563ec7 280:49e9e591c9bb
5 5
6 EM_JS(void, Canvas_Web_Begin_Frame, (), { 6 EM_JS(void, Canvas_Web_Begin_Frame, (), {
7 document.querySelectorAll("[data-canvas-web-surface]").forEach((surface) => { 7 document.querySelectorAll("[data-canvas-web-surface]").forEach((surface) => {
8 surface.dataset.canvasActive = "false"; 8 surface.dataset.canvasActive = "false";
9 }); 9 });
10 });
11
12 EM_JS(void, Canvas_Web_Dictation_Init, (), {
13 if (Module.canvasDictationInitialized) return;
14 Module.canvasDictationInitialized = true;
15 Module.canvasDictationQueue = [];
16 window.addEventListener("message", event => {
17 if (event.data?.type !== "zenbu.dictation.event") return;
18 const frame = document.getElementById("canvas-dictation-transport");
19 if (!frame || frame.contentWindow !== event.source) return;
20 Module.canvasDictationQueue.push({
21 kind: String(event.data.kind || ""),
22 text: String(event.data.text || ""),
23 });
24 });
25 });
26
27 EM_JS(void, Canvas_Web_Dictation_Set_Active, (int active), {
28 let frame = document.getElementById("canvas-dictation-transport");
29 if (!frame) {
30 frame = document.createElement("iframe");
31 frame.id = "canvas-dictation-transport";
32 frame.title = "Canvas dictation transport";
33 frame.allow = "microphone";
34 frame.sandbox =
35 "allow-same-origin allow-scripts";
36 frame.style.position = "fixed";
37 frame.style.left = "-2px";
38 frame.style.top = "-2px";
39 frame.style.width = "1px";
40 frame.style.height = "1px";
41 frame.style.opacity = "0";
42 frame.style.pointerEvents = "none";
43 frame.src = "http://127.0.0.1:8090/?canvas=1";
44 document.body.appendChild(frame);
45 }
46 const send = () => frame.contentWindow.postMessage(
47 {
48 type: "zenbu.dictation.set-active",
49 active: active !== 0,
50 },
51 "*");
52 frame.addEventListener("load", send, {once: true});
53 send();
54 });
55
56 EM_JS(void, Canvas_Web_Dictation_Commit, (), {
57 const frame = document.getElementById("canvas-dictation-transport");
58 frame?.contentWindow?.postMessage(
59 {type: "zenbu.dictation.commit"},
60 "*");
61 });
62
63 EM_JS(int, Canvas_Web_Dictation_Consume, (
64 char *p_text,
65 int text_capacity), {
66 const event = Module.canvasDictationQueue?.shift();
67 if (!event) return 0;
68 stringToUTF8(event.text, p_text, text_capacity);
69 if (event.kind === "status") return 1;
70 if (event.kind === "partial") return 2;
71 if (event.kind === "final") return 3;
72 return 0;
10 }); 73 });
11 74
12 EM_JS(void, Canvas_Web_Create_Or_Update, ( 75 EM_JS(void, Canvas_Web_Create_Or_Update, (
13 uint32 entity_id, 76 uint32 entity_id,
14 const char *p_url, 77 const char *p_url,
168 if (content && content.dataset.blobUrl) { 231 if (content && content.dataset.blobUrl) {
169 URL.revokeObjectURL(content.dataset.blobUrl); 232 URL.revokeObjectURL(content.dataset.blobUrl);
170 } 233 }
171 surface.remove(); 234 surface.remove();
172 }); 235 });
236 document.getElementById("canvas-dictation-transport")?.remove();
173 }); 237 });
174 238
175 static Rectangle Canvas_Web_Content_Bounds( 239 static Rectangle Canvas_Web_Content_Bounds(
176 const Canvas_Camera *p_camera, 240 const Canvas_Camera *p_camera,
177 const Canvas_Scene *p_scene, 241 const Canvas_Scene *p_scene,
234 { 298 {
235 memset(p_surface, 0, sizeof(*p_surface)); 299 memset(p_surface, 0, sizeof(*p_surface));
236 p_surface->p_arena = p_arena; 300 p_surface->p_arena = p_arena;
237 p_surface->dark_mode = dark_mode; 301 p_surface->dark_mode = dark_mode;
238 p_surface->initialized = TRUE; 302 p_surface->initialized = TRUE;
303 Canvas_Web_Dictation_Init();
239 return TRUE; 304 return TRUE;
240 } 305 }
241 306
242 void Canvas_Web_Surface_Set_Dark_Mode( 307 void Canvas_Web_Surface_Set_Dark_Mode(
243 Canvas_Web_Surface *p_surface, 308 Canvas_Web_Surface *p_surface,
367 { 432 {
368 (void)p_surface; 433 (void)p_surface;
369 Canvas_Web_History_Go(entity_id, 1); 434 Canvas_Web_History_Go(entity_id, 1);
370 } 435 }
371 436
437 void Canvas_Web_Surface_Set_Dictation_Active(
438 Canvas_Web_Surface *p_surface,
439 boolean active)
440 {
441 (void)p_surface;
442 Canvas_Web_Dictation_Set_Active(active);
443 }
444
445 void Canvas_Web_Surface_Commit_Dictation(
446 Canvas_Web_Surface *p_surface)
447 {
448 (void)p_surface;
449 Canvas_Web_Dictation_Commit();
450 }
451
452 Canvas_Dictation_Event Canvas_Web_Surface_Consume_Dictation(
453 Canvas_Web_Surface *p_surface,
454 char *p_text,
455 size_t text_capacity)
456 {
457 (void)p_surface;
458 return (Canvas_Dictation_Event)Canvas_Web_Dictation_Consume(
459 p_text,
460 (int)text_capacity);
461 }
462
372 boolean Canvas_Web_Surface_Is_Focused(const Canvas_Web_Surface *p_surface) 463 boolean Canvas_Web_Surface_Is_Focused(const Canvas_Web_Surface *p_surface)
373 { 464 {
374 (void)p_surface; 465 (void)p_surface;
375 return FALSE; 466 return FALSE;
376 } 467 }