fauna-api 0.0.0-stage → 1.0.676

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (171) hide show
  1. package/dist/brands.d.ts +2 -0
  2. package/dist/brands.js +2 -0
  3. package/dist/index.d.ts +64 -0
  4. package/dist/index.js +121 -0
  5. package/dist/src/ant/chat.d.ts +130 -0
  6. package/dist/src/ant/chat.js +467 -0
  7. package/dist/src/ant/format.d.ts +40 -0
  8. package/dist/src/ant/format.js +143 -0
  9. package/dist/src/ant/graph.d.ts +130 -0
  10. package/dist/src/ant/graph.js +449 -0
  11. package/dist/src/ant/index.d.ts +16 -0
  12. package/dist/src/ant/index.js +61 -0
  13. package/dist/src/ant/persona.d.ts +151 -0
  14. package/dist/src/ant/persona.js +136 -0
  15. package/dist/src/ant/timeline.d.ts +283 -0
  16. package/dist/src/ant/timeline.js +1025 -0
  17. package/dist/src/authentification.d.ts +45 -0
  18. package/dist/src/authentification.js +194 -0
  19. package/dist/src/billing.d.ts +135 -0
  20. package/dist/src/billing.js +128 -0
  21. package/dist/src/calendar.d.ts +83 -0
  22. package/dist/src/calendar.js +133 -0
  23. package/dist/src/chats.d.ts +113 -0
  24. package/dist/src/chats.js +100 -0
  25. package/dist/src/cicada/contacts.d.ts +31 -0
  26. package/dist/src/cicada/contacts.js +28 -0
  27. package/dist/src/cicada/conversations.d.ts +73 -0
  28. package/dist/src/cicada/conversations.js +236 -0
  29. package/dist/src/cicada/inboxes.d.ts +48 -0
  30. package/dist/src/cicada/inboxes.js +82 -0
  31. package/dist/src/cicada/index.d.ts +16 -0
  32. package/dist/src/cicada/index.js +61 -0
  33. package/dist/src/cicada/phone.d.ts +48 -0
  34. package/dist/src/cicada/phone.js +306 -0
  35. package/dist/src/drive.d.ts +80 -0
  36. package/dist/src/drive.js +472 -0
  37. package/dist/src/extensions/index.d.ts +4 -0
  38. package/dist/src/extensions/index.js +5 -0
  39. package/dist/src/extensions/web/index.d.ts +6 -0
  40. package/dist/src/extensions/web/index.js +7 -0
  41. package/dist/src/extensions/web/session.d.ts +106 -0
  42. package/dist/src/extensions/web/session.js +469 -0
  43. package/dist/src/extensions/web/tools.d.ts +13 -0
  44. package/dist/src/extensions/web/tools.js +72 -0
  45. package/dist/src/friends.d.ts +39 -0
  46. package/dist/src/friends.js +119 -0
  47. package/dist/src/groups.d.ts +82 -0
  48. package/dist/src/groups.js +269 -0
  49. package/dist/src/inbox.d.ts +11 -0
  50. package/dist/src/inbox.js +35 -0
  51. package/dist/src/integrations.d.ts +52 -0
  52. package/dist/src/integrations.js +205 -0
  53. package/dist/src/magpie/explorer.d.ts +14 -0
  54. package/dist/src/magpie/explorer.js +36 -0
  55. package/dist/src/magpie/index.d.ts +16 -0
  56. package/dist/src/magpie/index.js +61 -0
  57. package/dist/src/magpie/portfolio.d.ts +52 -0
  58. package/dist/src/magpie/portfolio.js +179 -0
  59. package/dist/src/magpie/quotes.d.ts +35 -0
  60. package/dist/src/magpie/quotes.js +47 -0
  61. package/dist/src/magpie/room.d.ts +61 -0
  62. package/dist/src/magpie/room.js +238 -0
  63. package/dist/src/maps.d.ts +159 -0
  64. package/dist/src/maps.js +171 -0
  65. package/dist/src/marmot/browser.d.ts +139 -0
  66. package/dist/src/marmot/browser.js +389 -0
  67. package/dist/src/marmot/index.d.ts +20 -0
  68. package/dist/src/marmot/index.js +65 -0
  69. package/dist/src/marmot/media.d.ts +56 -0
  70. package/dist/src/marmot/media.js +265 -0
  71. package/dist/src/marmot/research.d.ts +337 -0
  72. package/dist/src/marmot/research.js +1138 -0
  73. package/dist/src/marmot/scraper.d.ts +59 -0
  74. package/dist/src/marmot/scraper.js +94 -0
  75. package/dist/src/marmot/web.d.ts +402 -0
  76. package/dist/src/marmot/web.js +2586 -0
  77. package/dist/src/marmot/workspace.d.ts +81 -0
  78. package/dist/src/marmot/workspace.js +263 -0
  79. package/dist/src/parrot/agent.d.ts +53 -0
  80. package/dist/src/parrot/agent.js +93 -0
  81. package/dist/src/parrot/chat.d.ts +84 -0
  82. package/dist/src/parrot/chat.js +237 -0
  83. package/dist/src/parrot/format.d.ts +128 -0
  84. package/dist/src/parrot/format.js +639 -0
  85. package/dist/src/parrot/generative.d.ts +261 -0
  86. package/dist/src/parrot/generative.js +185 -0
  87. package/dist/src/parrot/index.d.ts +22 -0
  88. package/dist/src/parrot/index.js +67 -0
  89. package/dist/src/parrot/room.d.ts +225 -0
  90. package/dist/src/parrot/room.js +466 -0
  91. package/dist/src/parrot/routing.d.ts +90 -0
  92. package/dist/src/parrot/routing.js +78 -0
  93. package/dist/src/parrot/share.d.ts +126 -0
  94. package/dist/src/parrot/share.js +261 -0
  95. package/dist/src/parrot/socket.d.ts +41 -0
  96. package/dist/src/parrot/socket.js +478 -0
  97. package/dist/src/payments.d.ts +73 -0
  98. package/dist/src/payments.js +107 -0
  99. package/dist/src/private/cards.css +75 -0
  100. package/dist/src/rooms.d.ts +76 -0
  101. package/dist/src/rooms.js +168 -0
  102. package/dist/src/shared/schema.d.ts +24 -0
  103. package/dist/src/shared/schema.js +1 -0
  104. package/dist/src/shared/server.d.ts +65 -0
  105. package/dist/src/shared/server.js +302 -0
  106. package/dist/src/termite/environment.d.ts +110 -0
  107. package/dist/src/termite/environment.js +263 -0
  108. package/dist/src/termite/index.d.ts +14 -0
  109. package/dist/src/termite/index.js +59 -0
  110. package/dist/src/termite/os.d.ts +326 -0
  111. package/dist/src/termite/os.js +572 -0
  112. package/dist/src/termite/versioning.d.ts +94 -0
  113. package/dist/src/termite/versioning.js +217 -0
  114. package/dist/src/tickets.d.ts +43 -0
  115. package/dist/src/tickets.js +138 -0
  116. package/dist/src/travel.d.ts +85 -0
  117. package/dist/src/travel.js +44 -0
  118. package/dist/src/ui/authentification.d.ts +24 -0
  119. package/dist/src/ui/authentification.js +177 -0
  120. package/dist/src/ui/brands.css +11 -0
  121. package/dist/src/ui/brands.d.ts +7 -0
  122. package/dist/src/ui/brands.js +14 -0
  123. package/dist/src/ui/browser.d.ts +74 -0
  124. package/dist/src/ui/browser.js +470 -0
  125. package/dist/src/ui/chat.d.ts +183 -0
  126. package/dist/src/ui/chat.js +2379 -0
  127. package/dist/src/ui/chatPanel.d.ts +109 -0
  128. package/dist/src/ui/chatPanel.js +768 -0
  129. package/dist/src/ui/chatSequence.d.ts +36 -0
  130. package/dist/src/ui/chatSequence.js +42 -0
  131. package/dist/src/ui/crown.d.ts +19 -0
  132. package/dist/src/ui/crown.js +199 -0
  133. package/dist/src/ui/cursor.d.ts +73 -0
  134. package/dist/src/ui/cursor.js +175 -0
  135. package/dist/src/ui/device.d.ts +33 -0
  136. package/dist/src/ui/device.js +49 -0
  137. package/dist/src/ui/drive.d.ts +69 -0
  138. package/dist/src/ui/drive.js +1243 -0
  139. package/dist/src/ui/generative.d.ts +39 -0
  140. package/dist/src/ui/generative.js +313 -0
  141. package/dist/src/ui/hover.d.ts +6 -0
  142. package/dist/src/ui/hover.js +210 -0
  143. package/dist/src/ui/index.d.ts +42 -0
  144. package/dist/src/ui/index.js +87 -0
  145. package/dist/src/ui/integrations.d.ts +13 -0
  146. package/dist/src/ui/integrations.js +175 -0
  147. package/dist/src/ui/maps.d.ts +87 -0
  148. package/dist/src/ui/maps.js +547 -0
  149. package/dist/src/ui/roster.d.ts +32 -0
  150. package/dist/src/ui/roster.js +163 -0
  151. package/dist/src/ui/talk.d.ts +12 -0
  152. package/dist/src/ui/talk.js +560 -0
  153. package/dist/src/ui/tickets.d.ts +13 -0
  154. package/dist/src/ui/tickets.js +966 -0
  155. package/dist/src/ui/toast.d.ts +7 -0
  156. package/dist/src/ui/toast.js +258 -0
  157. package/dist/src/ui/tokenText.d.ts +18 -0
  158. package/dist/src/ui/tokenText.js +96 -0
  159. package/dist/src/ui/tokenized.d.ts +42 -0
  160. package/dist/src/ui/tokenized.js +622 -0
  161. package/dist/src/ui/ui.css +4763 -0
  162. package/dist/src/ui/view.d.ts +23 -0
  163. package/dist/src/ui/view.js +584 -0
  164. package/dist/src/view.d.ts +113 -0
  165. package/dist/src/view.js +386 -0
  166. package/dist/src/webhooks.d.ts +66 -0
  167. package/dist/src/webhooks.js +78 -0
  168. package/dist/ui.d.ts +1 -0
  169. package/dist/ui.js +1 -0
  170. package/package.json +89 -6
  171. package/README.md +0 -3
@@ -0,0 +1,470 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { createRoot } from "react-dom/client";
3
+ import { gsap } from "gsap";
4
+ import { Cursor } from "./cursor.js";
5
+ import { DomReplayer } from "../marmot/web.js";
6
+ const DRAG_SLOP = 4;
7
+ const SCROLL_FLUSH_MS = 100;
8
+ /** PointerEvent.button to the click tool's button. */
9
+ const BUTTONS = { 0: "left", 1: "middle", 2: "right" };
10
+ const TYPE_FLUSH_MS = 60;
11
+ const MODIFIERS = new Set(["Shift", "Control", "Alt", "Meta", "CapsLock", "Dead", "Unidentified"]);
12
+ /** A press released at `up`: a click where it went down, or a drag once it moved past the slop. */
13
+ export function pressTool(down, up) {
14
+ if (Math.hypot(up.x - down.x, up.y - down.y) < DRAG_SLOP)
15
+ return { kind: "click", x: down.x, y: down.y };
16
+ return { kind: "drag", from: down, to: up };
17
+ }
18
+ /** Plain printable keys become text to batch, everything else a chord in the key tool's names. */
19
+ export function keyInput(event) {
20
+ if (event.isComposing || MODIFIERS.has(event.key))
21
+ return null;
22
+ const chord = event.ctrlKey || event.altKey || event.metaKey;
23
+ if (event.key.length === 1 && !chord)
24
+ return { text: event.key };
25
+ const key = event.key === " " ? "space" : event.key.length === 1 ? event.key.toLowerCase() : event.key;
26
+ return {
27
+ keys: [
28
+ ...(event.ctrlKey ? ["ctrl"] : []),
29
+ ...(event.altKey ? ["alt"] : []),
30
+ ...(event.metaKey ? ["meta"] : []),
31
+ ...(event.shiftKey ? ["shift"] : []),
32
+ key,
33
+ ],
34
+ };
35
+ }
36
+ /**
37
+ * Forward a person's pointer, wheel, keyboard, and paste on `layer` to the room.
38
+ * Tools go out as soon as they're made, without waiting for replies; the host runs
39
+ * them in the order they arrive, so a click lands before the text typed after it.
40
+ * Bind it only while you control the room; the server refuses anyone else.
41
+ */
42
+ export function bindInput(room, layer, opts) {
43
+ const { toPage } = opts;
44
+ const invoke = (tool) => room.invokeTool(tool);
45
+ const send = (tool) => {
46
+ invoke(tool).catch(() => undefined);
47
+ };
48
+ let text = "";
49
+ let textTimer = 0;
50
+ const flushText = () => {
51
+ window.clearTimeout(textTimer);
52
+ textTimer = 0;
53
+ if (text)
54
+ send({ kind: "type", text });
55
+ text = "";
56
+ };
57
+ let wheel = { x: 0, y: 0 };
58
+ let wheelAt = null;
59
+ let wheelTimer = 0;
60
+ const flushWheel = () => {
61
+ wheelTimer = 0;
62
+ if (!wheelAt)
63
+ return;
64
+ send({ kind: "move", x: wheelAt.x, y: wheelAt.y });
65
+ send({ kind: "scroll", deltaX: Math.round(wheel.x), deltaY: Math.round(wheel.y) });
66
+ wheel = { x: 0, y: 0 };
67
+ wheelAt = null;
68
+ };
69
+ let down = null;
70
+ let downButton = "left";
71
+ // Left presses click or drag; right and middle presses click with that button. A quick
72
+ // second click goes out like any other: the host counts it toward a double click.
73
+ const onPointerDown = (event) => {
74
+ const button = BUTTONS[event.button];
75
+ if (!button)
76
+ return;
77
+ const point = toPage(event.clientX, event.clientY);
78
+ if (!point)
79
+ return;
80
+ event.preventDefault();
81
+ layer.focus({ preventScroll: true });
82
+ layer.setPointerCapture(event.pointerId);
83
+ down = point;
84
+ downButton = button;
85
+ };
86
+ const onPointerUp = (event) => {
87
+ if (!down)
88
+ return;
89
+ const start = down;
90
+ down = null;
91
+ flushText();
92
+ if (downButton !== "left") {
93
+ send({ kind: "click", x: start.x, y: start.y, button: downButton });
94
+ return;
95
+ }
96
+ const end = toPage(event.clientX, event.clientY) ?? start;
97
+ const tool = pressTool(start, end);
98
+ if (tool.kind === "click" && opts.claimClick?.(start))
99
+ return;
100
+ send(tool);
101
+ };
102
+ // The page's own menu shows through the right click instead.
103
+ const onContextMenu = (event) => event.preventDefault();
104
+ const onPointerMove = (event) => {
105
+ if (!down)
106
+ opts.onHover?.(toPage(event.clientX, event.clientY));
107
+ };
108
+ const onPointerLeave = () => opts.onHover?.(null);
109
+ const onWheel = (event) => {
110
+ const point = toPage(event.clientX, event.clientY);
111
+ if (!point)
112
+ return;
113
+ event.preventDefault();
114
+ const unit = event.deltaMode === 1 ? 16 : event.deltaMode === 2 ? layer.clientHeight : 1;
115
+ wheel = { x: wheel.x + event.deltaX * unit, y: wheel.y + event.deltaY * unit };
116
+ wheelAt = point;
117
+ if (!wheelTimer)
118
+ wheelTimer = window.setTimeout(flushWheel, SCROLL_FLUSH_MS);
119
+ };
120
+ const onKeyDown = (event) => {
121
+ // Leave paste to the paste event, which carries the clipboard.
122
+ if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === "v")
123
+ return;
124
+ const input = keyInput(event);
125
+ if (!input)
126
+ return;
127
+ if ("keys" in input && !opts.keys) {
128
+ // Enter and Tab still reach a page as typed text.
129
+ const typed = input.keys.length === 1 ? { Enter: "\n", Tab: "\t" }[input.keys[0]] : undefined;
130
+ if (!typed)
131
+ return;
132
+ event.preventDefault();
133
+ text += typed;
134
+ flushText();
135
+ return;
136
+ }
137
+ event.preventDefault();
138
+ if ("text" in input) {
139
+ text += input.text;
140
+ window.clearTimeout(textTimer);
141
+ textTimer = window.setTimeout(flushText, TYPE_FLUSH_MS);
142
+ return;
143
+ }
144
+ flushText();
145
+ send({ kind: "key", keys: input.keys });
146
+ };
147
+ const onPaste = (event) => {
148
+ const pasted = event.clipboardData?.getData("text") ?? "";
149
+ if (!pasted)
150
+ return;
151
+ event.preventDefault();
152
+ flushText();
153
+ send({ kind: "type", text: pasted });
154
+ };
155
+ layer.addEventListener("pointerdown", onPointerDown);
156
+ layer.addEventListener("pointerup", onPointerUp);
157
+ layer.addEventListener("pointermove", onPointerMove);
158
+ layer.addEventListener("pointerleave", onPointerLeave);
159
+ layer.addEventListener("contextmenu", onContextMenu);
160
+ layer.addEventListener("wheel", onWheel, { passive: false });
161
+ layer.addEventListener("keydown", onKeyDown);
162
+ layer.addEventListener("paste", onPaste);
163
+ return () => {
164
+ flushText();
165
+ window.clearTimeout(wheelTimer);
166
+ layer.removeEventListener("pointerdown", onPointerDown);
167
+ layer.removeEventListener("pointerup", onPointerUp);
168
+ layer.removeEventListener("pointermove", onPointerMove);
169
+ layer.removeEventListener("pointerleave", onPointerLeave);
170
+ layer.removeEventListener("contextmenu", onContextMenu);
171
+ layer.removeEventListener("wheel", onWheel);
172
+ layer.removeEventListener("keydown", onKeyDown);
173
+ layer.removeEventListener("paste", onPaste);
174
+ };
175
+ }
176
+ export function cursorSeed(options) {
177
+ return options.seed || options.room?.id || "browser";
178
+ }
179
+ export function toolBadge(tool) {
180
+ switch (tool.kind) {
181
+ case "move": return { label: "Move", tone: "neutral" };
182
+ case "click": return { label: "Click", tone: "neutral" };
183
+ case "drag": return { label: "Drag", tone: "neutral" };
184
+ case "scroll": return { label: "Scroll", tone: "neutral" };
185
+ case "type": return { label: "Typing", tone: "neutral" };
186
+ case "executeJavascript": return { label: "Running JS", tone: "neutral" };
187
+ case "search": return { label: "Search", tone: "neutral" };
188
+ case "exit": return tool.answer?.trim()
189
+ ? { label: "Found it", tone: "good" }
190
+ : { label: "Stopped", tone: "bad" };
191
+ }
192
+ }
193
+ function landed(current, x, y, slack = 8) {
194
+ return Math.hypot(current.x - x, current.y - y) < slack;
195
+ }
196
+ export function playBrowserTool(opts) {
197
+ const { tool, current, toScreen } = opts;
198
+ if (opts.badge) {
199
+ const next = toolBadge(tool);
200
+ opts.badge.textContent = next.label;
201
+ opts.badge.dataset.tone = next.tone;
202
+ opts.badge.style.display = "";
203
+ }
204
+ const tl = gsap.timeline();
205
+ if (tool.kind === "click") {
206
+ const x = tool.x ?? current.x;
207
+ const y = tool.y ?? current.y;
208
+ const screen = toScreen(x, y);
209
+ if (opts.ripple) {
210
+ gsap.set(opts.ripple, { left: screen.x, top: screen.y, display: "block" });
211
+ opts.ripple.classList.remove("is-firing");
212
+ void opts.ripple.offsetWidth;
213
+ opts.ripple.classList.add("is-firing");
214
+ }
215
+ tl.to(opts.cursor, { scale: 0.86, duration: 0.08, ease: "power2.out" })
216
+ .to(opts.cursor, { scale: 1, duration: 0.16, ease: "power2.out" });
217
+ if (!landed(current, x, y)) {
218
+ tl.to(opts.cursor, { x: screen.x, y: screen.y, duration: 0.18, ease: "power2.out" }, 0);
219
+ }
220
+ }
221
+ else if (tool.kind === "move" && !landed(current, tool.x, tool.y)) {
222
+ const screen = toScreen(tool.x, tool.y);
223
+ tl.to(opts.cursor, { x: screen.x, y: screen.y, duration: 0.2, ease: "power2.out" });
224
+ }
225
+ else if (tool.kind === "drag") {
226
+ const dest = tool.to;
227
+ const start = tool.from ?? { x: current.x, y: current.y };
228
+ if (!landed(current, dest.x, dest.y)) {
229
+ const from = toScreen(start.x, start.y);
230
+ const to = toScreen(dest.x, dest.y);
231
+ if (!landed({ ...current, x: start.x, y: start.y }, dest.x, dest.y)) {
232
+ tl.to(opts.cursor, { x: from.x, y: from.y, duration: 0.12, ease: "power2.out" });
233
+ }
234
+ tl.to(opts.cursor, { x: to.x, y: to.y, duration: 0.28, ease: "power2.inOut" });
235
+ }
236
+ }
237
+ return tl;
238
+ }
239
+ /** Styles a field lays its text out with, copied onto the mirror that measures it. */
240
+ const MIRRORED = [
241
+ "box-sizing", "border-top-width", "border-right-width", "border-bottom-width", "border-left-width", "border-style",
242
+ "padding-top", "padding-right", "padding-bottom", "padding-left", "font-style", "font-variant", "font-weight",
243
+ "font-stretch", "font-size", "font-family", "line-height", "letter-spacing", "word-spacing", "text-align",
244
+ "text-transform", "text-indent", "tab-size", "direction",
245
+ ];
246
+ const SELECTION = "rgba(51, 144, 255, 0.38)";
247
+ const BLINK_MS = 1060;
248
+ /**
249
+ * The host's caret, or its selection, drawn over the focused text field of a replay. The
250
+ * copy is never focused, so it has neither; the recorder streams them as
251
+ * `data-fauna-caret="start,end,scrollLeft,scrollTop"`. The marks sit in a layer at the end
252
+ * of the replay's document, outside every subtree the page mutates.
253
+ */
254
+ function caretPainter(getDoc) {
255
+ let layer = null;
256
+ let painted = "";
257
+ let frame = 0;
258
+ const clear = () => {
259
+ layer?.replaceChildren();
260
+ painted = "";
261
+ };
262
+ const paint = () => {
263
+ frame = 0;
264
+ const doc = getDoc();
265
+ if (!doc?.documentElement || !doc.defaultView)
266
+ return;
267
+ if (layer?.ownerDocument !== doc || !layer.isConnected) {
268
+ layer = doc.createElement("div");
269
+ layer.setAttribute("aria-hidden", "true");
270
+ layer.style.cssText = "all:initial;position:fixed;inset:0;pointer-events:none;overflow:hidden;z-index:2147483647";
271
+ doc.documentElement.appendChild(layer);
272
+ painted = "";
273
+ }
274
+ const field = doc.querySelector("[data-fauna-caret]");
275
+ const caret = field?.getAttribute("data-fauna-caret") ?? "";
276
+ const rect = field?.getBoundingClientRect();
277
+ if (!field || !caret || !rect?.width || !rect.height)
278
+ return clear();
279
+ const [start, end, scrollLeft, scrollTop] = caret.split(",").map((n) => Number(n) || 0);
280
+ const style = doc.defaultView.getComputedStyle(field);
281
+ const textarea = field.tagName === "TEXTAREA";
282
+ const value = field.value;
283
+ const from = Math.max(0, Math.min(start, value.length));
284
+ const to = Math.max(from, Math.min(end, value.length));
285
+ const mirror = doc.createElement("div");
286
+ mirror.style.cssText = "all:initial;position:fixed;pointer-events:none;visibility:hidden;overflow:hidden";
287
+ for (const name of MIRRORED)
288
+ mirror.style.setProperty(name, style.getPropertyValue(name));
289
+ const px = (name) => parseFloat(style.getPropertyValue(name)) || 0;
290
+ const inner = {
291
+ left: rect.left + px("border-left-width"),
292
+ top: rect.top + px("border-top-width"),
293
+ right: rect.right - px("border-right-width"),
294
+ bottom: rect.bottom - px("border-bottom-width"),
295
+ };
296
+ mirror.style.boxSizing = "border-box";
297
+ mirror.style.left = `${rect.left}px`;
298
+ mirror.style.top = `${rect.top}px`;
299
+ mirror.style.width = `${rect.width}px`;
300
+ mirror.style.height = `${rect.height}px`;
301
+ mirror.style.whiteSpace = textarea ? "pre-wrap" : "pre";
302
+ mirror.style.overflowWrap = textarea ? "break-word" : "normal";
303
+ // An input centers its one line in the box.
304
+ if (!textarea)
305
+ mirror.style.lineHeight = `${inner.bottom - inner.top - px("padding-top") - px("padding-bottom")}px`;
306
+ const mark = doc.createElement("span");
307
+ mark.textContent = from === to ? "\u200b" : value.slice(from, to);
308
+ mirror.append(value.slice(0, from), mark, value.slice(to) + (textarea ? "\u200b" : ""));
309
+ layer.appendChild(mirror);
310
+ mirror.scrollLeft = scrollLeft;
311
+ mirror.scrollTop = scrollTop;
312
+ const boxes = Array.from(mark.getClientRects());
313
+ mirror.remove();
314
+ const fontSize = px("font-size") || 16;
315
+ const color = from === to ? (style.caretColor && style.caretColor !== "auto" ? style.caretColor : style.color) : SELECTION;
316
+ const shapes = [];
317
+ for (const box of boxes) {
318
+ const height = Math.min(box.height, fontSize * 1.25);
319
+ const left = from === to ? box.left : Math.max(box.left, inner.left);
320
+ const right = from === to ? box.left + Math.max(1, Math.round(fontSize / 16)) : Math.min(box.right, inner.right);
321
+ const top = Math.max(box.top + (box.height - height) / 2, inner.top);
322
+ const bottom = Math.min(box.top + (box.height + height) / 2, inner.bottom);
323
+ if (right <= left || bottom <= top || left < inner.left - 1 || left > inner.right)
324
+ continue;
325
+ // A window over the field covers its caret too.
326
+ const under = doc.elementFromPoint(Math.min(left + 1, right), (top + bottom) / 2);
327
+ if (under && under !== field && !field.contains(under))
328
+ continue;
329
+ shapes.push({ left, top, width: right - left, height: bottom - top });
330
+ }
331
+ // Unchanged marks stay, so the caret keeps blinking through unrelated changes.
332
+ const key = `${color}|${shapes.map((s) => `${s.left},${s.top},${s.width},${s.height}`).join(";")}`;
333
+ if (key === painted)
334
+ return;
335
+ painted = key;
336
+ const marks = shapes.map((s) => {
337
+ const el = doc.createElement("div");
338
+ el.style.cssText = `all:initial;position:fixed;pointer-events:none;left:${s.left}px;top:${s.top}px;width:${s.width}px;height:${s.height}px`;
339
+ el.style.background = color;
340
+ return el;
341
+ });
342
+ layer.replaceChildren(...marks);
343
+ if (from === to && marks[0]) {
344
+ marks[0].animate([{ opacity: 1 }, { opacity: 1, offset: 0.5 }, { opacity: 0, offset: 0.5 }, { opacity: 0 }], {
345
+ duration: BLINK_MS,
346
+ iterations: Infinity,
347
+ });
348
+ }
349
+ };
350
+ return {
351
+ schedule() {
352
+ if (!frame)
353
+ frame = requestAnimationFrame(paint);
354
+ },
355
+ destroy() {
356
+ cancelAnimationFrame(frame);
357
+ layer?.remove();
358
+ layer = null;
359
+ },
360
+ };
361
+ }
362
+ export function createBrowser(container, options) {
363
+ const room = options.room;
364
+ const seed = cursorSeed(options);
365
+ const root = document.createElement("div");
366
+ root.className = "fauna-browser";
367
+ if (options.url)
368
+ root.dataset.url = options.url;
369
+ const frame = document.createElement("div");
370
+ frame.className = "fauna-browser-frame";
371
+ root.appendChild(frame);
372
+ const cursorHost = document.createElement("div");
373
+ cursorHost.className = "fauna-browser-cursor";
374
+ cursorHost.style.display = "none";
375
+ root.appendChild(cursorHost);
376
+ const ripple = document.createElement("div");
377
+ ripple.className = "fauna-browser-ripple";
378
+ ripple.style.display = "none";
379
+ root.appendChild(ripple);
380
+ const badge = document.createElement("div");
381
+ badge.className = "fauna-browser-badge";
382
+ badge.style.display = "none";
383
+ root.appendChild(badge);
384
+ container.appendChild(root);
385
+ const reactRoot = createRoot(cursorHost);
386
+ let shape;
387
+ reactRoot.render(_jsx(Cursor, { seed: seed, label: options.label }));
388
+ gsap.set(cursorHost, { x: 0, y: 0, scale: 1 });
389
+ const replayer = new DomReplayer(frame);
390
+ let toolTl = null;
391
+ // Only a full document replays in its own frame; a fragment shares this page's document.
392
+ const caret = caretPainter(() => {
393
+ const doc = replayer.getDocument();
394
+ return doc === document ? null : doc;
395
+ });
396
+ const resized = new ResizeObserver(() => caret.schedule());
397
+ resized.observe(frame);
398
+ function viewport() {
399
+ return replayer.getViewport() ?? { width: 1280, height: 800 };
400
+ }
401
+ function toScreen(x, y) {
402
+ if (options.pagePixels)
403
+ return { x, y };
404
+ const vp = viewport();
405
+ const w = frame.clientWidth || vp.width;
406
+ const scale = w / vp.width;
407
+ return { x: x * scale, y: y * scale };
408
+ }
409
+ function applyCursor(pointer) {
410
+ if (pointer.shape !== shape) {
411
+ shape = pointer.shape;
412
+ reactRoot.render(_jsx(Cursor, { seed: seed, label: options.label, shape: shape }));
413
+ }
414
+ const { x, y } = toScreen(pointer.x, pointer.y);
415
+ cursorHost.style.display = pointer.visible === false ? "none" : "";
416
+ gsap.to(cursorHost, { x, y, duration: 0.12, ease: "power2.out", overwrite: "auto" });
417
+ options.onCursor?.(pointer);
418
+ }
419
+ function applyTool(tool) {
420
+ toolTl?.kill();
421
+ toolTl = playBrowserTool({
422
+ cursor: cursorHost,
423
+ ripple,
424
+ badge,
425
+ tool,
426
+ toScreen,
427
+ current: room.cursor,
428
+ });
429
+ }
430
+ async function applyBootstrap(html, vp) {
431
+ if (!html)
432
+ return;
433
+ await replayer.applyHtml(html, vp);
434
+ applyCursor(room.cursor);
435
+ options.onFrame?.(replayer.getDocument());
436
+ caret.schedule();
437
+ }
438
+ const boot = room.web.getBootstrap();
439
+ void applyBootstrap(boot?.html, boot?.viewport);
440
+ applyCursor(room.cursor);
441
+ room.watch({
442
+ onCursor: (_r, pointer) => applyCursor(pointer),
443
+ onTool: (_r, tool) => applyTool(tool),
444
+ onClosed: () => { badge.style.display = "none"; },
445
+ web: {
446
+ onBootstrap: (_web, meta) => { void applyBootstrap(meta.html, meta.viewport); },
447
+ onMutation: (_web, mutation) => {
448
+ replayer.mutate(mutation);
449
+ caret.schedule();
450
+ },
451
+ },
452
+ });
453
+ return {
454
+ toPage(clientX, clientY) {
455
+ const rect = frame.getBoundingClientRect();
456
+ const vp = options.pagePixels ? { width: frame.clientWidth, height: frame.clientHeight } : viewport();
457
+ const scale = (rect.width || vp.width) / vp.width;
458
+ const x = (clientX - rect.left) / scale;
459
+ const y = (clientY - rect.top) / scale;
460
+ return x >= 0 && y >= 0 && x <= vp.width && y <= vp.height ? { x, y } : null;
461
+ },
462
+ destroy() {
463
+ toolTl?.kill();
464
+ resized.disconnect();
465
+ caret.destroy();
466
+ reactRoot.unmount();
467
+ root.remove();
468
+ },
469
+ };
470
+ }
@@ -0,0 +1,183 @@
1
+ import React from "react";
2
+ import { Fauna } from "fauna-api";
3
+ import type { ChatRoom, RoomMessage } from "../parrot/room.js";
4
+ import type { Presence } from "../shared/server.js";
5
+ import { type Part, type Trigger, type Token } from "./tokenized.js";
6
+ import { type ChatFile, type ChatMessageBlock, type ToolSession } from "./chatSequence.js";
7
+ import { type RepositoryHolder } from "./chatPanel.js";
8
+ export { RepositoryControl, askForComputer, pickedRepository, type RepositoryHolder } from "./chatPanel.js";
9
+ export { toPlainText } from "./tokenized.js";
10
+ export type Author = {
11
+ id: string;
12
+ profile?: Fauna.Authentification.Profile;
13
+ };
14
+ export type ChatMode = 'assistant' | 'thread';
15
+ export type ChatMessage = {
16
+ role: 'user' | 'model';
17
+ text: string;
18
+ createdAt: Date;
19
+ parts?: Part[];
20
+ files?: ChatFile[];
21
+ toolCalls?: {
22
+ id?: string;
23
+ name: string;
24
+ args: Record<string, unknown>;
25
+ status?: 'running' | 'success' | 'error';
26
+ result?: string;
27
+ progress?: unknown;
28
+ session?: ToolSession;
29
+ }[];
30
+ /** Room hydrate only: TEXT / INVOCATION / FILE in persist order. */
31
+ sequence?: ChatMessageBlock[];
32
+ author?: Author;
33
+ /** LLM id that produced a MODEL turn — drives the left avatar icon. */
34
+ model?: string;
35
+ };
36
+ export type { ChatMessageBlock };
37
+ export type StreamEvent = {
38
+ event: 'text';
39
+ delta: string;
40
+ } | {
41
+ event: 'tool_call';
42
+ id: string;
43
+ name: string;
44
+ args: Record<string, unknown>;
45
+ server?: boolean;
46
+ } | {
47
+ event: 'done';
48
+ usage?: {
49
+ promptTokens: number;
50
+ completionTokens: number;
51
+ };
52
+ } | {
53
+ event: 'error';
54
+ message: string;
55
+ code?: string;
56
+ };
57
+ export type ToolCallView = {
58
+ id?: string;
59
+ name: string;
60
+ args: Record<string, unknown>;
61
+ status?: 'running' | 'success' | 'error';
62
+ result?: string;
63
+ progress?: unknown;
64
+ /** The live room the tool opened, for a Watch button. */
65
+ session?: ToolSession;
66
+ };
67
+ export type RenderToolCall = (call: ToolCallView) => HTMLElement | null;
68
+ export declare function toolChip(call: ToolCallView, icon: string, label: string): HTMLElement;
69
+ export type ChatInstance = {
70
+ sendMessage: (text: string, files?: {
71
+ mime: string;
72
+ name: string;
73
+ data: Uint8Array;
74
+ }[]) => void;
75
+ appendMessage: (role: 'user' | 'model', text: string) => void;
76
+ removeQueuedMessage: (index: number) => void;
77
+ getQueue: () => {
78
+ text: string;
79
+ files?: {
80
+ mime: string;
81
+ name: string;
82
+ }[];
83
+ }[];
84
+ clearMessages: () => void;
85
+ getHistory: () => ChatMessage[];
86
+ getModel: () => Fauna.Parrot.Generative.Text;
87
+ setModel: (model: Fauna.Parrot.Generative.Text) => void;
88
+ getReasoning: () => string | undefined;
89
+ setReasoning: (reasoning: string | undefined) => void;
90
+ setMode: (mode: ChatMode) => void;
91
+ setModels: (models?: Fauna.Parrot.Generative.Text | Fauna.Parrot.Generative.Text[]) => void;
92
+ insertToken: (token: Token) => void;
93
+ getParts: () => Part[];
94
+ watchInput: (handler: (parts: Part[]) => void) => () => void;
95
+ clearInput: () => void;
96
+ focusInput: () => void;
97
+ getElement: () => HTMLElement;
98
+ destroy: () => void;
99
+ };
100
+ export type LogInstance = {
101
+ appendMessage: (role: 'user' | 'model', text: string) => void;
102
+ streamMessage: (iterable: AsyncIterable<StreamEvent>) => Promise<void>;
103
+ clearMessages: () => void;
104
+ getHistory: () => ChatMessage[];
105
+ getElement: () => HTMLElement;
106
+ destroy: () => void;
107
+ };
108
+ export type ChatOptions = Partial<{
109
+ generate: (model: Fauna.Parrot.Generative.Text, history: ChatMessage[], attachments: Fauna.Drive.File[], signal?: AbortSignal, reasoning?: string) => AsyncIterable<StreamEvent>;
110
+ /** One model locks the picker. A list limits it. Omit for the full catalog. */
111
+ models: Fauna.Parrot.Generative.Text | Fauna.Parrot.Generative.Text[];
112
+ /** The model the picker starts on, when `models` allows it. Defaults to Auto. */
113
+ model: Fauna.Parrot.Generative.Text;
114
+ /** The reasoning level the picker starts on. */
115
+ reasoning: string;
116
+ authorization: Fauna.Authentification.Credentials | Fauna.Authentification.ApiKey;
117
+ onModelChange: (model: Fauna.Parrot.Generative.Text) => void;
118
+ onReasoningChange: (reasoning: string | undefined) => void;
119
+ acceptedTypes: Record<string, string>;
120
+ placeholder: string;
121
+ triggers: Trigger[];
122
+ initialHistory: ChatMessage[];
123
+ callbacks: Partial<{
124
+ onMessage: (message: ChatMessage) => void;
125
+ onStreamComplete: (message: ChatMessage) => void;
126
+ onError: (error: string) => void;
127
+ onQueueChange: (length: number) => void;
128
+ }>;
129
+ room: {
130
+ room: ChatRoom;
131
+ self: Presence;
132
+ /** Called after the widget renders each room message (any member's send or
133
+ * the server's model reply). Lets the hosting app run its own side-effects
134
+ * (e.g. auto-name, memory extraction) off the shared transcript. */
135
+ onRoomMessage?: (message: RoomMessage) => void;
136
+ /** Fires when the room starts or finishes a model reply. */
137
+ onThinking?: (thinking: boolean) => void;
138
+ /** Client tools for this originator (schemas on send + local evaluate). */
139
+ tools?: Fauna.Parrot.Generative.Tool[] | Fauna.Parrot.Generative.Tool;
140
+ /** Fires after an older transcript page is prepended into the widget. */
141
+ onOlderLoaded?: () => void;
142
+ };
143
+ /**
144
+ * Optional custom DOM for a finished tool call (history + live stream).
145
+ * Return null to keep the default chip. Running calls always use the chip.
146
+ */
147
+ renderToolCall?: RenderToolCall;
148
+ /** `assistant` (default): every human on the right. `thread`: only this seat on the right. */
149
+ mode?: ChatMode;
150
+ onTokenClick?: (token: Token) => void;
151
+ /** Show the side panel: the chat's attachments, and a tab for each file opened, each a live file room. */
152
+ attachments?: boolean;
153
+ /**
154
+ * Show the repository button: attach a new or existing repository, or detach it.
155
+ * `true` binds it to the chat room's own repository; a holder binds it to someone
156
+ * else's (an app's), and the computer the model asks for is made there.
157
+ */
158
+ repositories?: boolean | RepositoryHolder;
159
+ /** Who asks for a computer in the popup, like "Parrot wants a computer for this". Defaults to Parrot. */
160
+ computerAsker?: string;
161
+ /** Opens a repository in Termite, from the repository chip. Without it the chip has no Open in Termite. */
162
+ onOpenRepository?: (repository: number) => void;
163
+ /** The chat's repository changed, from this seat or anyone else's. */
164
+ onRepositoryChange?: (repository: number | null) => void;
165
+ /** The app's own buttons at the chat's top right, beside the side panel's, in the same style. */
166
+ actions?: ChatAction[];
167
+ }>;
168
+ /** A button at the chat's top right: a Material Symbols icon, its label, and what it does. */
169
+ export type ChatAction = {
170
+ icon: string;
171
+ label: string;
172
+ onClick: () => void;
173
+ };
174
+ export declare const Chat: React.ForwardRefExoticComponent<{
175
+ options?: ChatOptions;
176
+ } & React.RefAttributes<ChatInstance>>;
177
+ export declare function createChat(container: HTMLElement, options?: ChatOptions): ChatInstance;
178
+ export declare function createLog(container: HTMLElement, options?: Partial<{
179
+ initialHistory: ChatMessage[];
180
+ callbacks: Partial<{
181
+ onMessage: (message: ChatMessage) => void;
182
+ }>;
183
+ }>): LogInstance;