@hanzo/ui 8.27.3 → 8.27.4

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 (59) hide show
  1. package/dist/backdrop/Backdrop.cjs +420 -0
  2. package/dist/backdrop/Backdrop.d.ts +53 -0
  3. package/dist/backdrop/Backdrop.d.ts.map +1 -0
  4. package/dist/backdrop/Backdrop.js +418 -0
  5. package/dist/backdrop/Backdrop.js.map +1 -0
  6. package/dist/backdrop/index.cjs +50 -0
  7. package/dist/backdrop/index.d.ts +29 -0
  8. package/dist/backdrop/index.d.ts.map +1 -0
  9. package/dist/backdrop/index.js +29 -0
  10. package/dist/backdrop/index.js.map +1 -0
  11. package/dist/backdrop/scene.cjs +439 -0
  12. package/dist/backdrop/scene.d.ts +235 -0
  13. package/dist/backdrop/scene.d.ts.map +1 -0
  14. package/dist/backdrop/scene.js +423 -0
  15. package/dist/backdrop/scene.js.map +1 -0
  16. package/dist/chat/Chat.cjs +23 -1
  17. package/dist/chat/Chat.d.ts +6 -3
  18. package/dist/chat/Chat.d.ts.map +1 -1
  19. package/dist/chat/Chat.js +23 -1
  20. package/dist/chat/Chat.js.map +1 -1
  21. package/dist/chat/Inspector.cjs +51 -0
  22. package/dist/chat/Inspector.d.ts +49 -0
  23. package/dist/chat/Inspector.d.ts.map +1 -0
  24. package/dist/chat/Inspector.js +49 -0
  25. package/dist/chat/Inspector.js.map +1 -0
  26. package/dist/chat/Message.cjs +3 -1
  27. package/dist/chat/Message.d.ts +3 -1
  28. package/dist/chat/Message.d.ts.map +1 -1
  29. package/dist/chat/Message.js +3 -1
  30. package/dist/chat/Message.js.map +1 -1
  31. package/dist/chat/Parts.cjs +159 -0
  32. package/dist/chat/Parts.d.ts +186 -0
  33. package/dist/chat/Parts.d.ts.map +1 -0
  34. package/dist/chat/Parts.js +153 -0
  35. package/dist/chat/Parts.js.map +1 -0
  36. package/dist/chat/Persona.cjs +34 -0
  37. package/dist/chat/Persona.d.ts +37 -0
  38. package/dist/chat/Persona.d.ts.map +1 -0
  39. package/dist/chat/Persona.js +30 -0
  40. package/dist/chat/Persona.js.map +1 -0
  41. package/dist/chat/Preview.cjs +42 -0
  42. package/dist/chat/Preview.d.ts +36 -0
  43. package/dist/chat/Preview.d.ts.map +1 -0
  44. package/dist/chat/Preview.js +40 -0
  45. package/dist/chat/Preview.js.map +1 -0
  46. package/dist/chat/directive.cjs +272 -0
  47. package/dist/chat/directive.d.ts +167 -0
  48. package/dist/chat/directive.d.ts.map +1 -0
  49. package/dist/chat/directive.js +266 -0
  50. package/dist/chat/directive.js.map +1 -0
  51. package/dist/chat/index.cjs +59 -4
  52. package/dist/chat/index.d.ts +31 -3
  53. package/dist/chat/index.d.ts.map +1 -1
  54. package/dist/chat/index.js +31 -3
  55. package/dist/chat/index.js.map +1 -1
  56. package/dist/gallery.html +4 -4
  57. package/dist/styles.css +3 -3
  58. package/dist/theme.css +3 -3
  59. package/package.json +7 -1
@@ -0,0 +1,423 @@
1
+ /**
2
+ * What paints behind a conversation, and the pure rules that read it.
3
+ *
4
+ * No React and no imports: every rule about what can play, what a link means and
5
+ * what a typed command does is decidable from data alone, so it is decidable in
6
+ * a test without rendering anything — and a surface can read a stored value
7
+ * before it has mounted anything.
8
+ *
9
+ * ONE switch. `mode: 'off'` is the only way to say "no backdrop"; there is
10
+ * deliberately no enable flag beside it, because a pair of them makes two states
11
+ * (off + video, on + off) that paint the same nothing, and that ambiguity is
12
+ * where a bug grows.
13
+ *
14
+ * WHAT CANNOT PLAY, AND WHY: only YouTube and Twitch are embeddable. Every DRM
15
+ * service — Netflix and its neighbours — publishes no embed, wraps playback in
16
+ * Widevine and forbids framing in its terms. There is no key and no clever
17
+ * iframe that changes that, so such a link is classed `other` and NEVER becomes
18
+ * a frame. A menu can offer it as a link that opens in a tab; anything else
19
+ * would be pretending.
20
+ *
21
+ * No provider password is ever asked for. A members-only or age-gated stream
22
+ * plays if — and only if — the viewer is already signed in to that provider in
23
+ * this browser, because the embed rides their own cookies. Collecting those
24
+ * credentials is not a feature this is missing; it is one it refuses.
25
+ */
26
+ /** A blank canvas — the value a surface starts from and folds changes onto.
27
+ * `off`, because a component library must not begin streaming video on its
28
+ * own; a surface that wants footage says so. */
29
+ export const BLANK = {
30
+ mode: 'off',
31
+ photo: '',
32
+ video: '',
33
+ playlist: [],
34
+ clips: [],
35
+ loop: true,
36
+ sound: false,
37
+ };
38
+ /** How long a live stream holds the canvas before the list moves on. A Twitch
39
+ * channel never ends, so unlike a video it cannot announce its turn is up. */
40
+ export const DWELL = 5 * 60 * 1000;
41
+ /**
42
+ * The longest URL that will be kept. Browsers stopped being the limit long ago;
43
+ * the limit that matters is whatever storage a surface writes this to, one quota
44
+ * shared by every preference it holds. A URL is a string a visitor can paste, so
45
+ * without a ceiling one pasted megabyte fills that quota and the NEXT write of
46
+ * any unrelated setting is the one that throws. 2048 is what every server and
47
+ * proxy has accepted since forever, and far past any real link.
48
+ */
49
+ const LONGEST = 2048;
50
+ /** The longest list. Same quota, same reasoning: appending is a loop a visitor
51
+ * can run, so it needs an end. Well past any list anyone watches. */
52
+ const MOST = 64;
53
+ /**
54
+ * A short list to choose from without knowing a link — offered, never a default
55
+ * (`BLANK` is the default, and it is off). Short by intent: the set someone can
56
+ * read in one glance, with the whole rest of the web one paste away.
57
+ */
58
+ export const PRESETS = [
59
+ { id: 'cetaceans', label: 'Cetaceans', url: 'https://www.youtube.com/watch?v=UY5YH6B4A9o' },
60
+ { id: 'reef', label: 'Reef', url: 'https://www.youtube.com/watch?v=6lZ3CookYNg' },
61
+ { id: 'deep', label: 'Deep water', url: 'https://www.youtube.com/watch?v=7Exxd2ievAw' },
62
+ ];
63
+ /**
64
+ * The media hosts a file may be loaded from besides the page's own origin.
65
+ *
66
+ * The Hanzo store is the default because it is where our surfaces keep footage.
67
+ * A brand serving from somewhere else passes its own list — every function here
68
+ * that reads a URL takes one — and whatever it passes must also be what its
69
+ * `img-src` / `media-src` policy allows, or the element is refused by the
70
+ * browser and paints nothing with no error anywhere.
71
+ */
72
+ export const HOSTS = ['s3.hanzo.ai', 's3-api.hanzo.ai'];
73
+ /**
74
+ * The URL if it is one worth loading, else ''. Anything that is not http(s) —
75
+ * `javascript:`, `data:`, `blob:` — is refused here, at the boundary, so no
76
+ * caller downstream has to remember to check.
77
+ */
78
+ export function web(value) {
79
+ if (typeof value !== 'string' || value.trim() === '' || value.length > LONGEST)
80
+ return '';
81
+ try {
82
+ const url = new URL(value.trim());
83
+ return url.protocol === 'http:' || url.protocol === 'https:' ? url.href : '';
84
+ }
85
+ catch {
86
+ return '';
87
+ }
88
+ }
89
+ const named = (url) => {
90
+ try {
91
+ return new URL(url).hostname.replace(/^www\./, '').toLowerCase();
92
+ }
93
+ catch {
94
+ return '';
95
+ }
96
+ };
97
+ /** Any absolute URL resolves against this, so a relative path can be told from
98
+ * one that only looks relative. Never fetched; it is a ruler, not a place. */
99
+ const HERE = 'https://backdrop.invalid';
100
+ /**
101
+ * A media file that may be painted or played, else ''.
102
+ *
103
+ * Narrower than `web`, and it is the one rule here that is not about what plays.
104
+ * An `<img>` or a `<video src>` fires a GET the moment it renders, with no click
105
+ * and no script, and a STORED one refires on every load afterwards — reporting
106
+ * the viewer's address to whoever owns the host, forever. So a file is ours: a
107
+ * path on this origin, kept RELATIVE because a surface is served under several
108
+ * brands' domains and a stored absolute origin would be wrong on all but one, or
109
+ * a file on a host the caller allows.
110
+ *
111
+ * A URL the browser's own policy would refuse is dropped HERE instead, where it
112
+ * can be answered honestly: a blocked element never fires `load`, so allowing it
113
+ * would mean a blank canvas and nothing anywhere saying why.
114
+ *
115
+ * Backslashes are why this parses rather than matches: `/\evil.example/x.png`
116
+ * looks like a path and resolves to a different host.
117
+ *
118
+ * A photo and a clip are the same question asked about two elements, so they are
119
+ * one function. Two copies would be two places to get the backslash wrong.
120
+ */
121
+ export function media(value, hosts = HOSTS) {
122
+ if (typeof value !== 'string' || value.trim() === '' || value.length > LONGEST)
123
+ return '';
124
+ try {
125
+ const url = new URL(value.trim(), HERE);
126
+ if (url.protocol !== 'http:' && url.protocol !== 'https:')
127
+ return '';
128
+ if (url.origin === HERE)
129
+ return url.pathname + url.search;
130
+ return hosts.includes(named(url.href)) ? url.href : '';
131
+ }
132
+ catch {
133
+ return '';
134
+ }
135
+ }
136
+ /**
137
+ * A list of files from an array, or from a comma- or space-separated string —
138
+ * whichever a configuration value supplies. Every entry passes through `media`,
139
+ * so an unusable one is dropped rather than trusted, and the whole is capped for
140
+ * the same quota reason a playlist is.
141
+ */
142
+ export function files(value, hosts = HOSTS) {
143
+ const raw = Array.isArray(value)
144
+ ? value.map((v) => (typeof v === 'string' ? v : ''))
145
+ : typeof value === 'string'
146
+ ? value.split(/[\s,]+/)
147
+ : [];
148
+ const out = [];
149
+ for (const entry of raw) {
150
+ const ok = media(entry, hosts);
151
+ if (ok && !out.includes(ok))
152
+ out.push(ok);
153
+ if (out.length >= MOST)
154
+ break;
155
+ }
156
+ return out;
157
+ }
158
+ /** Who serves this link, decided by host alone. */
159
+ export function provider(url) {
160
+ const name = named(url);
161
+ if (name === 'youtube.com' || name === 'm.youtube.com' || name === 'music.youtube.com') {
162
+ return 'youtube';
163
+ }
164
+ if (name === 'youtu.be')
165
+ return 'youtube';
166
+ if (name === 'twitch.tv' || name === 'm.twitch.tv' || name === 'player.twitch.tv') {
167
+ return 'twitch';
168
+ }
169
+ return 'other';
170
+ }
171
+ /** A YouTube video id is exactly 11 of these. */
172
+ const ID = /^[\w-]{11}$/;
173
+ /**
174
+ * The video id inside a YouTube URL, or '' if there is not one. Accepts the
175
+ * forms people actually paste — watch links, share links, shorts, embeds — and a
176
+ * bare id, which is what the address bar leaves you with often enough.
177
+ */
178
+ export function videoId(value) {
179
+ const raw = value.trim();
180
+ if (ID.test(raw))
181
+ return raw;
182
+ const url = web(raw);
183
+ if (!url || provider(url) !== 'youtube')
184
+ return '';
185
+ const { pathname, searchParams } = new URL(url);
186
+ const query = searchParams.get('v') ?? '';
187
+ if (ID.test(query))
188
+ return query;
189
+ // youtu.be/ID, /embed/ID, /shorts/ID, /live/ID — the id is the last segment.
190
+ const last = pathname.split('/').filter(Boolean).pop() ?? '';
191
+ return ID.test(last) ? last : '';
192
+ }
193
+ /**
194
+ * What to ask the Twitch player for: a live `channel`, or a recorded `video`.
195
+ * Empty `name` means the URL names neither.
196
+ *
197
+ * A CLIP names neither. `twitch.tv/<channel>/clip/<slug>` is a few seconds
198
+ * somebody cut out, and the player this builds cannot ask for one — that embed
199
+ * is a different host with a different parameter. Falling back to the channel
200
+ * would put a LIVE STREAM on the canvas in place of the clip that was asked for:
201
+ * not the thing, and not obviously not the thing. So it is unplayable, and a
202
+ * menu says so with a link out. Refusing what we cannot serve beats serving
203
+ * something else.
204
+ */
205
+ export function channel(value) {
206
+ const none = { key: 'channel', name: '' };
207
+ const url = web(value);
208
+ if (!url || provider(url) !== 'twitch')
209
+ return none;
210
+ const parsed = new URL(url);
211
+ const parts = parsed.pathname.split('/').filter(Boolean);
212
+ const direct = parsed.searchParams.get('channel') ?? parsed.searchParams.get('video') ?? '';
213
+ if (direct)
214
+ return { key: parsed.searchParams.get('video') ? 'video' : 'channel', name: direct };
215
+ if (parts[0] === 'videos' && parts[1])
216
+ return { key: 'video', name: parts[1] };
217
+ if (parts.includes('clip'))
218
+ return none;
219
+ return { key: 'channel', name: parts[0] ?? '' };
220
+ }
221
+ /** A playlist entry from a pasted URL, or null if it is not a URL at all. */
222
+ export function link(value) {
223
+ const url = web(value);
224
+ return url ? { url, provider: provider(url) } : null;
225
+ }
226
+ /** Whether this can actually go on the canvas. `other` never can. */
227
+ export function playable(entry) {
228
+ if (entry.provider === 'youtube')
229
+ return videoId(entry.url) !== '';
230
+ if (entry.provider === 'twitch')
231
+ return channel(entry.url).name !== '';
232
+ return false;
233
+ }
234
+ /** Every YouTube player param that keeps the chrome out of frame. */
235
+ const PLAYER = [
236
+ 'autoplay=1',
237
+ 'mute=1',
238
+ 'controls=0',
239
+ 'rel=0',
240
+ 'playsinline=1',
241
+ 'disablekb=1',
242
+ 'fs=0',
243
+ 'iv_load_policy=3',
244
+ 'vq=hd1080',
245
+ // The listening handshake this enables is what lets the canvas wait for a real
246
+ // "playing" report before revealing anything.
247
+ 'enablejsapi=1',
248
+ ].join('&');
249
+ /**
250
+ * The embed URL for one or more YouTube videos.
251
+ *
252
+ * www.youtube.com, not youtube-nocookie: the nocookie host answers embeds with
253
+ * "video player configuration error" (153) in current Chrome.
254
+ *
255
+ * `loop` needs `playlist` even for a single video — that is YouTube's rule, not
256
+ * ours: a lone video with `loop=1` and no list plays once and stops.
257
+ */
258
+ export function youtube(ids, loop, origin, start) {
259
+ const [first, ...rest] = ids;
260
+ const params = [PLAYER, `origin=${encodeURIComponent(origin)}`];
261
+ if (loop) {
262
+ params.push('loop=1', `playlist=${(rest.length ? rest : [first]).join(',')}`);
263
+ }
264
+ else if (rest.length) {
265
+ params.push(`playlist=${rest.join(',')}`);
266
+ }
267
+ // Where the video opens, in seconds. Native `start=` on the embed, so it is
268
+ // honoured before the first frame rather than seeked to after.
269
+ if (start && start > 0)
270
+ params.push(`start=${Math.floor(start)}`);
271
+ return `https://www.youtube.com/embed/${first}?${params.join('&')}`;
272
+ }
273
+ /**
274
+ * Optional knobs a brand carries in the SAME video URL, so one configuration
275
+ * value tunes all of them with no rebuild:
276
+ * `start` / `t` seconds to open on
277
+ * `rate` playback speed (YouTube quantises to its allowed steps —
278
+ * 0.25/0.5/0.75/1/…, so 0.42 plays at the nearest, 0.5)
279
+ * `zoom` cover-crop scale, to push a corner watermark off-frame
280
+ * Absent keys return undefined and the caller keeps its own default.
281
+ */
282
+ export function knobs(value) {
283
+ const url = web(value);
284
+ if (!url)
285
+ return {};
286
+ const q = new URL(url).searchParams;
287
+ const num = (k) => {
288
+ const v = q.get(k);
289
+ if (v == null)
290
+ return undefined;
291
+ const n = Number(v);
292
+ return Number.isFinite(n) ? n : undefined;
293
+ };
294
+ return { start: num('start') ?? num('t'), rate: num('rate'), zoom: num('zoom') };
295
+ }
296
+ /**
297
+ * The embed URL for a Twitch channel or VOD. `parent` is mandatory — Twitch
298
+ * refuses to frame for a host it was not told about — and must be the bare
299
+ * hostname, no scheme, no port.
300
+ */
301
+ export function twitch(value, parent) {
302
+ const { key, name } = channel(value);
303
+ return (`https://player.twitch.tv/?${key}=${encodeURIComponent(name)}` +
304
+ `&parent=${encodeURIComponent(parent)}&muted=true&autoplay=true&controls=false`);
305
+ }
306
+ const MODES = ['off', 'photo', 'video', 'playlist', 'clips'];
307
+ /**
308
+ * Fold an untrusted description of a backdrop onto the current one.
309
+ *
310
+ * This is the ONE gate, and it is deliberately total: anything it cannot make
311
+ * sense of is dropped and the current value kept, so no caller can push a scene
312
+ * into a shape the canvas has to defend against later. Everything a surface
313
+ * takes in comes through here — a settings panel, a card a model can propose,
314
+ * and the read back out of storage on the next load, which matters because bytes
315
+ * in storage are not ours and a rule enforced only on the way in lasts exactly
316
+ * until the page is refreshed.
317
+ */
318
+ export function merge(current, change, hosts = HOSTS) {
319
+ if (typeof change !== 'object' || change === null)
320
+ return current;
321
+ const next = { ...current };
322
+ const given = change;
323
+ if (MODES.includes(given.mode))
324
+ next.mode = given.mode;
325
+ if (typeof given.loop === 'boolean')
326
+ next.loop = given.loop;
327
+ if (typeof given.sound === 'boolean')
328
+ next.sound = given.sound;
329
+ const photo = media(given.photo, hosts);
330
+ if (photo)
331
+ next.photo = photo;
332
+ if (given.video !== undefined && videoId(String(given.video))) {
333
+ next.video = String(given.video).trim();
334
+ }
335
+ if (Array.isArray(given.playlist)) {
336
+ // The provider is recomputed from the URL every time rather than believed: a
337
+ // caller claiming `provider: 'youtube'` for a netflix.com link must not be
338
+ // able to talk the canvas into framing it.
339
+ next.playlist = given.playlist
340
+ .map((entry) => link(typeof entry === 'string' ? entry : String(entry?.url ?? '')))
341
+ .filter((entry) => entry !== null)
342
+ .slice(0, MOST);
343
+ }
344
+ // Re-validated on the way in AND on the way out, the same as the playlist, so
345
+ // a file the media policy would refuse cannot reach a `<video src>` by having
346
+ // been written to storage under an older rule.
347
+ if (given.clips !== undefined)
348
+ next.clips = files(given.clips, hosts);
349
+ return next;
350
+ }
351
+ /**
352
+ * What a brand's single configuration string names.
353
+ *
354
+ * A list of direct video files — comma- or space-separated, or any value naming
355
+ * a media file the surface serves — opens on `clips`; anything else stays the
356
+ * YouTube video it has always been, so an existing single-URL value does not
357
+ * change meaning.
358
+ *
359
+ * Pure, and here rather than in a store, because "what does this value mean" is
360
+ * a rule about data, decidable in a test with no browser.
361
+ */
362
+ export function scene(value, hosts = HOSTS) {
363
+ const list = files(value, hosts);
364
+ if (list.length > 0)
365
+ return { mode: 'clips', clips: list, video: '' };
366
+ return { mode: 'video', clips: [], video: value };
367
+ }
368
+ /**
369
+ * `/background` or `/bg`, and then a space or the end of the line — not merely a
370
+ * word boundary, which `/background-image …` also satisfies. A command is one of
371
+ * exactly two words; anything else is a message and is sent as one.
372
+ */
373
+ const VERB = /^\/(?:background|bg)(?:\s|$)/i;
374
+ /**
375
+ * Read a `/background` (or `/bg`) line typed into a composer.
376
+ *
377
+ * Answers the scene it asks for, or null when the line is not one of these
378
+ * commands or names something unusable — null meaning "not handled", so the
379
+ * caller sends it on as an ordinary message and the viewer can see for
380
+ * themselves that nothing was applied.
381
+ *
382
+ * /bg off stop painting anything
383
+ * /bg photo <url> a still image
384
+ * /bg video <url> one YouTube video
385
+ * /bg add <url> append to the playlist and switch to it
386
+ * /bg loop on|off repeat at the end
387
+ *
388
+ * There is no `/bg <url>` that guesses: guessing is a second way to say what
389
+ * `photo` and `video` already say exactly, and the guess is wrong precisely when
390
+ * the URL is unusual — which is when being wrong costs the most.
391
+ */
392
+ export function command(input, current, hosts = HOSTS) {
393
+ const line = input.trim();
394
+ if (!VERB.test(line))
395
+ return null;
396
+ const [, verb = '', ...rest] = line.split(/\s+/);
397
+ const argument = rest.join(' ').trim();
398
+ switch (verb.toLowerCase()) {
399
+ case 'off':
400
+ return { ...current, mode: 'off' };
401
+ case 'photo': {
402
+ const url = media(argument, hosts);
403
+ return url ? { ...current, mode: 'photo', photo: url } : null;
404
+ }
405
+ case 'video':
406
+ return videoId(argument) ? { ...current, mode: 'video', video: argument.trim() } : null;
407
+ case 'add': {
408
+ // Through `merge` so the list has ONE keeper of its length, rather than a
409
+ // second append here that forgets what the first one enforces.
410
+ const entry = link(argument);
411
+ return entry && playable(entry)
412
+ ? merge(current, { mode: 'playlist', playlist: [...current.playlist, entry] }, hosts)
413
+ : null;
414
+ }
415
+ case 'loop': {
416
+ const on = argument.toLowerCase();
417
+ return on === 'on' || on === 'off' ? { ...current, loop: on === 'on' } : null;
418
+ }
419
+ default:
420
+ return null;
421
+ }
422
+ }
423
+ //# sourceMappingURL=scene.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scene.js","sourceRoot":"","sources":["../../src/backdrop/scene.ts"],"names":[],"mappings":"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"}
@@ -28,6 +28,10 @@ const jsx_runtime_1 = require("react/jsx-runtime");
28
28
  * component reads it, and lifting it would put a keystroke through the surface's
29
29
  * state on every letter.
30
30
  *
31
+ * That same call now draws a turn whose content is a LIST, through `Parts` — so
32
+ * an image, a tool call, a citation and an artifact reach the screen with
33
+ * nothing added at the call site. They used to be dropped there.
34
+ *
31
35
  * No transport, no store, no routing — `@hanzo/ui` stays fetch-free, so nothing
32
36
  * here imports `@hanzo/ai`. `messages` is read structurally, which is what lets
33
37
  * that hold while the two packages still compose exactly.
@@ -38,8 +42,26 @@ const ink_1 = require("../backends/gui/ink.cjs");
38
42
  const slot_1 = require("../backends/gui/slot.cjs");
39
43
  const Composer_1 = require("./Composer.cjs");
40
44
  const Message_1 = require("./Message.cjs");
45
+ const Parts_1 = require("./Parts.cjs");
41
46
  const Thread_1 = require("./Thread.cjs");
42
47
  const words_1 = require("./words.cjs");
48
+ /**
49
+ * A turn's content, drawn by what it is.
50
+ *
51
+ * A string is prose and always was. A LIST goes to `Parts`, which is the change:
52
+ * it used to go to `words`, which keeps the text and drops the image, the tool
53
+ * call and the citation — so a surface passing the wire's own turns saw only the
54
+ * prose in them, and had to write its own switch to see the rest.
55
+ *
56
+ * Nothing regresses on the way through. `Parts` joins adjacent prose before it
57
+ * draws (the wire splits a sentence mid-word), and a part whose type it does not
58
+ * know contributes its text, so an all-text turn renders the identical string
59
+ * `words` produced.
60
+ *
61
+ * The cast is the one place the open wire meets the closed union, and it is safe
62
+ * in exactly that direction: `Parts` reads `type` before it reads anything else.
63
+ */
64
+ const draw = (said) => typeof said === 'string' || said == null ? ((0, ink_1.ink)((0, words_1.words)(said))) : ((0, jsx_runtime_1.jsx)(Parts_1.Parts, { parts: said }));
43
65
  function Chat({ messages, streaming = false, send: onSend, stop: onStop, body, empty, composer, ...thread }) {
44
66
  const [draft, setDraft] = (0, react_1.useState)('');
45
67
  // Clearing here rather than in an effect on `messages` is what keeps a send
@@ -63,5 +85,5 @@ function Chat({ messages, streaming = false, send: onSend, stop: onStop, body, e
63
85
  // from the composer lands on a `display: contents` wrapper or on the page,
64
86
  // and then reports a component that is fine as broken, or one that is broken
65
87
  // as fine.
66
- return ((0, jsx_runtime_1.jsxs)(gui_1.YStack, { flex: 1, width: "100%", ...(0, slot_1.slot)('chat'), children: [messages.length === 0 && empty ? (empty) : ((0, jsx_runtime_1.jsx)(Thread_1.Thread, { flex: 1, ...thread, children: messages.map((turn, i) => ((0, jsx_runtime_1.jsx)(Message_1.Message, { role: turn.role, busy: streaming && i === last && turn.role === 'assistant', children: body ? body(turn) : (0, ink_1.ink)((0, words_1.words)(turn.content)) }, turn.id))) })), (0, jsx_runtime_1.jsx)(Composer_1.Composer, { value: draft, onChange: setDraft, onSend: send, busy: streaming, ...(onStop ? { onStop } : {}), ...composer })] }));
88
+ return ((0, jsx_runtime_1.jsxs)(gui_1.YStack, { flex: 1, width: "100%", ...(0, slot_1.slot)('chat'), children: [messages.length === 0 && empty ? (empty) : ((0, jsx_runtime_1.jsx)(Thread_1.Thread, { flex: 1, ...thread, children: messages.map((turn, i) => ((0, jsx_runtime_1.jsx)(Message_1.Message, { role: turn.role, busy: streaming && i === last && turn.role === 'assistant', children: body ? body(turn) : draw(turn.content) }, turn.id))) })), (0, jsx_runtime_1.jsx)(Composer_1.Composer, { value: draft, onChange: setDraft, onSend: send, busy: streaming, ...(onStop ? { onStop } : {}), ...composer })] }));
67
89
  }
@@ -21,7 +21,7 @@ export interface Turn {
21
21
  * over: a completion's content is `string | ContentPart[]` (text beside an
22
22
  * image), so a turn holding an attachment could not be handed to this at all.
23
23
  * Narrowing the wire in a component fed BY the wire just relocates the
24
- * mismatch to every caller. `words` is the flattener.
24
+ * mismatch to every caller. `words` flattens it to prose; `Parts` draws it.
25
25
  */
26
26
  content?: Said;
27
27
  }
@@ -45,12 +45,15 @@ export interface ChatProps extends Omit<ThreadProps, 'children' | 'ref'> {
45
45
  /** End the turn in flight. Absent, the composer offers no stop. */
46
46
  stop?: () => void;
47
47
  /**
48
- * Render a turn's body. Defaults to its text.
48
+ * Render a turn's body. Defaults to `Parts` for a list and the words for a
49
+ * string.
49
50
  *
50
51
  * A surface with a markdown pipeline passes it here — the plugin set is
51
52
  * per-surface (nine plugins, one plugin, or a regex under a bundle ceiling
52
53
  * that cannot afford a parser), so this package ships none and renders the
53
- * words plainly until someone says otherwise.
54
+ * words plainly until someone says otherwise. The usual form hands the
55
+ * pipeline to `Parts` rather than replacing it:
56
+ * `body={(t) => <Parts parts={t.content as MessagePart[]} prose={md} />}`.
54
57
  */
55
58
  body?: (turn: Turn) => ReactNode;
56
59
  /** Shown instead of the thread while nothing has been said. */
@@ -1 +1 @@
1
- {"version":3,"file":"Chat.d.ts","sourceRoot":"","sources":["../../src/chat/Chat.tsx"],"names":[],"mappings":"AAgCA,OAAO,EAAyB,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AAI7D,OAAO,EAAY,KAAK,aAAa,EAAE,MAAM,YAAY,CAAA;AACzD,OAAO,EAAW,KAAK,IAAI,EAAE,MAAM,WAAW,CAAA;AAC9C,OAAO,EAAU,KAAK,WAAW,EAAE,MAAM,UAAU,CAAA;AACnD,OAAO,EAAS,KAAK,IAAI,EAAE,MAAM,SAAS,CAAA;AAE1C;;;;;;;GAOG;AACH,MAAM,WAAW,IAAI;IACnB,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,IAAI,CAAA;IACV;;;;;;;;OAQG;IACH,OAAO,CAAC,EAAE,IAAI,CAAA;CACf;AAED,MAAM,WAAW,SAAU,SAAQ,IAAI,CAAC,WAAW,EAAE,UAAU,GAAG,KAAK,CAAC;IACtE,sCAAsC;IACtC,QAAQ,EAAE,IAAI,EAAE,CAAA;IAChB,iFAAiF;IACjF,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB;;;;;;;;;;OAUG;IACH,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;IAC5B,mEAAmE;IACnE,IAAI,CAAC,EAAE,MAAM,IAAI,CAAA;IACjB;;;;;;;OAOG;IACH,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,SAAS,CAAA;IAChC,+DAA+D;IAC/D,KAAK,CAAC,EAAE,SAAS,CAAA;IACjB,iFAAiF;IACjF,QAAQ,CAAC,EAAE,OAAO,CAAC,aAAa,CAAC,CAAA;CAClC;AAED,wBAAgB,IAAI,CAAC,EACnB,QAAQ,EACR,SAAiB,EACjB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,IAAI,EACJ,KAAK,EACL,QAAQ,EACR,GAAG,MAAM,EACV,EAAE,SAAS,+BAoDX"}
1
+ {"version":3,"file":"Chat.d.ts","sourceRoot":"","sources":["../../src/chat/Chat.tsx"],"names":[],"mappings":"AAoCA,OAAO,EAAyB,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AAI7D,OAAO,EAAY,KAAK,aAAa,EAAE,MAAM,YAAY,CAAA;AACzD,OAAO,EAAW,KAAK,IAAI,EAAE,MAAM,WAAW,CAAA;AAE9C,OAAO,EAAU,KAAK,WAAW,EAAE,MAAM,UAAU,CAAA;AACnD,OAAO,EAAS,KAAK,IAAI,EAAE,MAAM,SAAS,CAAA;AAE1C;;;;;;;GAOG;AACH,MAAM,WAAW,IAAI;IACnB,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,IAAI,CAAA;IACV;;;;;;;;OAQG;IACH,OAAO,CAAC,EAAE,IAAI,CAAA;CACf;AAED,MAAM,WAAW,SAAU,SAAQ,IAAI,CAAC,WAAW,EAAE,UAAU,GAAG,KAAK,CAAC;IACtE,sCAAsC;IACtC,QAAQ,EAAE,IAAI,EAAE,CAAA;IAChB,iFAAiF;IACjF,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB;;;;;;;;;;OAUG;IACH,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;IAC5B,mEAAmE;IACnE,IAAI,CAAC,EAAE,MAAM,IAAI,CAAA;IACjB;;;;;;;;;;OAUG;IACH,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,SAAS,CAAA;IAChC,+DAA+D;IAC/D,KAAK,CAAC,EAAE,SAAS,CAAA;IACjB,iFAAiF;IACjF,QAAQ,CAAC,EAAE,OAAO,CAAC,aAAa,CAAC,CAAA;CAClC;AAyBD,wBAAgB,IAAI,CAAC,EACnB,QAAQ,EACR,SAAiB,EACjB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,IAAI,EACJ,KAAK,EACL,QAAQ,EACR,GAAG,MAAM,EACV,EAAE,SAAS,+BAoDX"}
package/dist/chat/Chat.js CHANGED
@@ -25,6 +25,10 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
25
25
  * component reads it, and lifting it would put a keystroke through the surface's
26
26
  * state on every letter.
27
27
  *
28
+ * That same call now draws a turn whose content is a LIST, through `Parts` — so
29
+ * an image, a tool call, a citation and an artifact reach the screen with
30
+ * nothing added at the call site. They used to be dropped there.
31
+ *
28
32
  * No transport, no store, no routing — `@hanzo/ui` stays fetch-free, so nothing
29
33
  * here imports `@hanzo/ai`. `messages` is read structurally, which is what lets
30
34
  * that hold while the two packages still compose exactly.
@@ -35,8 +39,26 @@ import { ink } from '../backends/gui/ink.js';
35
39
  import { slot } from '../backends/gui/slot.js';
36
40
  import { Composer } from './Composer.js';
37
41
  import { Message } from './Message.js';
42
+ import { Parts } from './Parts.js';
38
43
  import { Thread } from './Thread.js';
39
44
  import { words } from './words.js';
45
+ /**
46
+ * A turn's content, drawn by what it is.
47
+ *
48
+ * A string is prose and always was. A LIST goes to `Parts`, which is the change:
49
+ * it used to go to `words`, which keeps the text and drops the image, the tool
50
+ * call and the citation — so a surface passing the wire's own turns saw only the
51
+ * prose in them, and had to write its own switch to see the rest.
52
+ *
53
+ * Nothing regresses on the way through. `Parts` joins adjacent prose before it
54
+ * draws (the wire splits a sentence mid-word), and a part whose type it does not
55
+ * know contributes its text, so an all-text turn renders the identical string
56
+ * `words` produced.
57
+ *
58
+ * The cast is the one place the open wire meets the closed union, and it is safe
59
+ * in exactly that direction: `Parts` reads `type` before it reads anything else.
60
+ */
61
+ const draw = (said) => typeof said === 'string' || said == null ? (ink(words(said))) : (_jsx(Parts, { parts: said }));
40
62
  export function Chat({ messages, streaming = false, send: onSend, stop: onStop, body, empty, composer, ...thread }) {
41
63
  const [draft, setDraft] = useState('');
42
64
  // Clearing here rather than in an effect on `messages` is what keeps a send
@@ -60,6 +82,6 @@ export function Chat({ messages, streaming = false, send: onSend, stop: onStop,
60
82
  // from the composer lands on a `display: contents` wrapper or on the page,
61
83
  // and then reports a component that is fine as broken, or one that is broken
62
84
  // as fine.
63
- return (_jsxs(YStack, { flex: 1, width: "100%", ...slot('chat'), children: [messages.length === 0 && empty ? (empty) : (_jsx(Thread, { flex: 1, ...thread, children: messages.map((turn, i) => (_jsx(Message, { role: turn.role, busy: streaming && i === last && turn.role === 'assistant', children: body ? body(turn) : ink(words(turn.content)) }, turn.id))) })), _jsx(Composer, { value: draft, onChange: setDraft, onSend: send, busy: streaming, ...(onStop ? { onStop } : {}), ...composer })] }));
85
+ return (_jsxs(YStack, { flex: 1, width: "100%", ...slot('chat'), children: [messages.length === 0 && empty ? (empty) : (_jsx(Thread, { flex: 1, ...thread, children: messages.map((turn, i) => (_jsx(Message, { role: turn.role, busy: streaming && i === last && turn.role === 'assistant', children: body ? body(turn) : draw(turn.content) }, turn.id))) })), _jsx(Composer, { value: draft, onChange: setDraft, onSend: send, busy: streaming, ...(onStop ? { onStop } : {}), ...composer })] }));
64
86
  }
65
87
  //# sourceMappingURL=Chat.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"Chat.js","sourceRoot":"","sources":["../../src/chat/Chat.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAA;;AAEZ;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,OAAO,EAAE,MAAM,EAAE,MAAM,YAAY,CAAA;AACnC,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAkB,MAAM,OAAO,CAAA;AAE7D,OAAO,EAAE,GAAG,EAAE,MAAM,qBAAqB,CAAA;AACzC,OAAO,EAAE,IAAI,EAAE,MAAM,sBAAsB,CAAA;AAC3C,OAAO,EAAE,QAAQ,EAAsB,MAAM,YAAY,CAAA;AACzD,OAAO,EAAE,OAAO,EAAa,MAAM,WAAW,CAAA;AAC9C,OAAO,EAAE,MAAM,EAAoB,MAAM,UAAU,CAAA;AACnD,OAAO,EAAE,KAAK,EAAa,MAAM,SAAS,CAAA;AA2D1C,MAAM,UAAU,IAAI,CAAC,EACnB,QAAQ,EACR,SAAS,GAAG,KAAK,EACjB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,IAAI,EACJ,KAAK,EACL,QAAQ,EACR,GAAG,MAAM,EACC;IACV,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAC,EAAE,CAAC,CAAA;IAEtC,4EAA4E;IAC5E,yEAAyE;IACzE,iEAAiE;IACjE,MAAM,IAAI,GAAG,WAAW,CAAC,GAAG,EAAE;QAC5B,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,EAAE,CAAA;QACzB,IAAI,CAAC,IAAI;YAAE,OAAM;QACjB,QAAQ,CAAC,EAAE,CAAC,CAAA;QACZ,MAAM,CAAC,IAAI,CAAC,CAAA;IACd,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,CAAA;IAEnB,MAAM,IAAI,GAAG,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAA;IAEhC,4EAA4E;IAC5E,0EAA0E;IAC1E,2EAA2E;IAC3E,yEAAyE;IACzE,8EAA8E;IAC9E,gFAAgF;IAChF,4EAA4E;IAC5E,2EAA2E;IAC3E,6EAA6E;IAC7E,WAAW;IACX,OAAO,CACL,MAAC,MAAM,IAAC,IAAI,EAAE,CAAC,EAAE,KAAK,EAAC,MAAM,KAAK,IAAI,CAAC,MAAM,CAAC,aAC3C,QAAQ,CAAC,MAAM,KAAK,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,CAChC,KAAK,CACN,CAAC,CAAC,CAAC,CACF,KAAC,MAAM,IAAC,IAAI,EAAE,CAAC,KAAM,MAAM,YACxB,QAAQ,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,EAAE,CAAC,CACzB,KAAC,OAAO,IAEN,IAAI,EAAE,IAAI,CAAC,IAAI,EACf,IAAI,EAAE,SAAS,IAAI,CAAC,KAAK,IAAI,IAAI,IAAI,CAAC,IAAI,KAAK,WAAW,YAEzD,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,IAJxC,IAAI,CAAC,EAAE,CAKJ,CACX,CAAC,GACK,CACV,EACD,KAAC,QAAQ,IACP,KAAK,EAAE,KAAK,EACZ,QAAQ,EAAE,QAAQ,EAClB,MAAM,EAAE,IAAI,EACZ,IAAI,EAAE,SAAS,KACX,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,KAC1B,QAAQ,GACZ,IACK,CACV,CAAA;AACH,CAAC"}
1
+ {"version":3,"file":"Chat.js","sourceRoot":"","sources":["../../src/chat/Chat.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,51 @@
1
+ 'use client';"use strict";
2
+ 'use client';
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.Inspector = Inspector;
5
+ const jsx_runtime_1 = require("react/jsx-runtime");
6
+ /**
7
+ * Inspector — what the agent did, beside what it said.
8
+ *
9
+ * The thread carries the answer. This carries the evidence: a summary at the
10
+ * top, then the run's own regions underneath — what it was given, what it
11
+ * produced, what ran on the way. It is the pane a person reads to decide
12
+ * whether to believe the answer, which is why the summary is first and the
13
+ * detail is under it rather than the other way round.
14
+ *
15
+ * It is the CONTENTS of the right rail, not the rail. `Aside` is the column —
16
+ * the border, the width, the fact that it is a column at all — and this goes
17
+ * inside it. Two components because a surface hides the rail at narrow widths
18
+ * by not rendering it, and that decision has nothing to do with what is in it.
19
+ *
20
+ * ONE inspector, whatever the run was. A section carries `facts` (name to
21
+ * value) or `parts` (drawn by `Parts`), and `MessagePart` already covers every
22
+ * shape a run produces: tool calls and their results for a chat turn, files and
23
+ * console output for a coding run, `citation` for a research run's sources, an
24
+ * `artifact` for anything it made. A second panel per kind of run would be four
25
+ * copies of this file disagreeing about spacing.
26
+ *
27
+ * A section with nothing in it does not render, so a caller passes the same
28
+ * list every time and does not write the emptiness checks.
29
+ */
30
+ const gui_1 = require("@hanzo/gui");
31
+ const slot_1 = require("../backends/gui/slot.cjs");
32
+ const Code_1 = require("./Code.cjs");
33
+ const Parts_1 = require("./Parts.cjs");
34
+ const filled = (section) => (section.facts != null && Object.keys(section.facts).length > 0) ||
35
+ (section.parts != null && section.parts.length > 0);
36
+ function Inspector({ title = 'Run', summary, sections = [], placeholder, prose, onRetry, onOpen, onSource, ...props }) {
37
+ const shown = sections.filter(filled);
38
+ const bare = summary == null && shown.length === 0;
39
+ return ((0, jsx_runtime_1.jsxs)(gui_1.YStack, { ...(0, slot_1.slot)('inspector'), flex: 1, minH: 0, width: "100%", ...props, children: [(0, jsx_runtime_1.jsx)(gui_1.XStack, { items: "center", px: "$1", pb: "$2", shrink: 0, children: (0, jsx_runtime_1.jsx)(gui_1.SizableText, { size: "$1", color: "$soft", fontWeight: "600", children: title }) }), (0, jsx_runtime_1.jsx)(gui_1.ScrollView, { flex: 1, showsVerticalScrollIndicator: false, children: (0, jsx_runtime_1.jsxs)(gui_1.YStack, { gap: "$4", pb: "$4", children: [summary ? ((0, jsx_runtime_1.jsx)(gui_1.YStack, { ...(0, slot_1.slot)('inspector-summary'), p: "$3", gap: "$2", rounded: "$4", borderWidth: 1, borderColor: "$borderColor", bg: "$panel", children: (0, jsx_runtime_1.jsx)(gui_1.SizableText, { size: "$2", color: "$ink", children: summary }) })) : null, shown.map((section) => ((0, jsx_runtime_1.jsxs)(gui_1.YStack, { ...(0, slot_1.slot)('inspector-section'), gap: "$2", children: [(0, jsx_runtime_1.jsx)(gui_1.SizableText, { size: "$1", color: "$quiet", fontWeight: "600", children: section.name }), section.facts ? (0, jsx_runtime_1.jsx)(Table, { facts: section.facts }) : null, section.parts?.length ? ((0, jsx_runtime_1.jsx)(Parts_1.Parts, { parts: section.parts, prose: prose, onRetry: onRetry, onOpen: onOpen, onSource: onSource })) : null] }, section.name))), bare && placeholder != null ? ((0, jsx_runtime_1.jsx)(gui_1.SizableText, { size: "$2", color: "$soft", children: placeholder })) : null] }) })] }));
40
+ }
41
+ /**
42
+ * A record, as rows.
43
+ *
44
+ * A string is its own presentation; anything else goes through `Code`, because
45
+ * a nested object flattened into a line is unreadable and `show` already
46
+ * serialises one safely. That is the only rule here, and it is why this is a
47
+ * dozen lines rather than a formatting library.
48
+ */
49
+ function Table({ facts }) {
50
+ return ((0, jsx_runtime_1.jsx)(gui_1.YStack, { ...(0, slot_1.slot)('inspector-facts'), rounded: "$4", borderWidth: 1, borderColor: "$borderColor", overflow: "hidden", children: Object.entries(facts).map(([name, value], i) => ((0, jsx_runtime_1.jsxs)(gui_1.YStack, { gap: "$1.5", px: "$3", py: "$2", borderTopWidth: i === 0 ? 0 : 1, borderColor: "$borderColor", children: [(0, jsx_runtime_1.jsx)(gui_1.SizableText, { size: "$1", color: "$quiet", children: name }), typeof value === 'string' ? ((0, jsx_runtime_1.jsx)(gui_1.SizableText, { size: "$2", color: "$ink", children: value })) : ((0, jsx_runtime_1.jsx)(Code_1.Code, { language: "json", children: (0, Parts_1.show)(value) }))] }, name))) }));
51
+ }