@classic-homes/agent-chat 0.3.0 → 0.4.1

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 (43) hide show
  1. package/README.md +5 -1
  2. package/dist/artifact-pages.d.ts +18 -0
  3. package/dist/artifact-pages.d.ts.map +1 -0
  4. package/dist/artifact-pages.js +89 -0
  5. package/dist/artifact-pages.test.d.ts +2 -0
  6. package/dist/artifact-pages.test.d.ts.map +1 -0
  7. package/dist/artifact-pages.test.js +102 -0
  8. package/dist/components/AgentChat.svelte +9 -0
  9. package/dist/components/AgentChat.svelte.d.ts +5 -0
  10. package/dist/components/AgentChat.svelte.d.ts.map +1 -1
  11. package/dist/components/ArtifactPanel.svelte +3 -6
  12. package/dist/components/ArtifactPanel.svelte.d.ts.map +1 -1
  13. package/dist/components/AssistantMessage.svelte +8 -1
  14. package/dist/components/AssistantMessage.svelte.d.ts +1 -0
  15. package/dist/components/AssistantMessage.svelte.d.ts.map +1 -1
  16. package/dist/components/Composer.svelte +221 -30
  17. package/dist/components/Composer.svelte.d.ts +8 -0
  18. package/dist/components/Composer.svelte.d.ts.map +1 -1
  19. package/dist/components/MessageItem.svelte +1 -0
  20. package/dist/components/MessageItem.svelte.d.ts +1 -0
  21. package/dist/components/MessageItem.svelte.d.ts.map +1 -1
  22. package/dist/components/MessageList.svelte +3 -0
  23. package/dist/components/MessageList.svelte.d.ts +1 -0
  24. package/dist/components/MessageList.svelte.d.ts.map +1 -1
  25. package/dist/components/ToolTimeline.svelte +138 -54
  26. package/dist/components/ToolTimeline.svelte.d.ts +11 -3
  27. package/dist/components/ToolTimeline.svelte.d.ts.map +1 -1
  28. package/dist/components/composer.svelte.test.d.ts +2 -0
  29. package/dist/components/composer.svelte.test.d.ts.map +1 -0
  30. package/dist/components/composer.svelte.test.js +162 -0
  31. package/dist/composer-keys.d.ts +3 -0
  32. package/dist/composer-keys.d.ts.map +1 -1
  33. package/dist/composer-keys.js +18 -0
  34. package/dist/composer-prompts.d.ts +16 -0
  35. package/dist/composer-prompts.d.ts.map +1 -0
  36. package/dist/composer-prompts.js +27 -0
  37. package/dist/index.d.ts +1 -0
  38. package/dist/index.d.ts.map +1 -1
  39. package/dist/index.js +1 -0
  40. package/dist/message-format.d.ts +28 -0
  41. package/dist/message-format.d.ts.map +1 -1
  42. package/dist/message-format.js +31 -0
  43. package/package.json +3 -3
@@ -1,8 +1,16 @@
1
+ <!--
2
+ @component
3
+ The message box: an autosizing textarea in a card with attach, the host's context chip and
4
+ Send/Stop. Files can also be dropped on it or pasted. Typing `/` at the start opens the saved
5
+ prompt menu when the host passes `prompts`.
6
+ -->
1
7
  <script lang="ts">
2
- import { Button, Icon, Textarea } from '@classic-homes/theme-svelte';
8
+ import { cn, Button, Icon, Kbd, Popover, Spinner, Textarea } from '@classic-homes/theme-svelte';
3
9
  import type { FileRef } from '@classic-homes/agent-contracts';
4
10
  import { LIMITS } from '@classic-homes/agent-contracts';
5
- import { composerKeyAction } from '../composer-keys.js';
11
+ import { composerKeyAction, promptMenuKeyAction } from '../composer-keys.js';
12
+ import { filterPrompts, promptQuery, type ComposerPrompt } from '../composer-prompts.js';
13
+ import { fileIcon, formatBytes } from '../message-format.js';
6
14
 
7
15
  interface Props {
8
16
  disabled?: boolean;
@@ -12,6 +20,8 @@
12
20
  placeholder?: string;
13
21
  /** Upload a file; the composer shows it as pending until the message is sent. */
14
22
  upload?: (file: File) => Promise<FileRef>;
23
+ /** Saved prompts for the `/` menu; the menu is off without them. */
24
+ prompts?: ComposerPrompt[];
15
25
  onsend?: (text: string) => void;
16
26
  oncancel?: () => void;
17
27
  onremovefile?: (fileId: string) => void;
@@ -25,28 +35,76 @@
25
35
  files = $bindable([]),
26
36
  placeholder = 'Ask Classic Agent…',
27
37
  upload,
38
+ prompts,
28
39
  onsend,
29
40
  oncancel,
30
41
  onremovefile,
31
42
  context,
32
43
  }: Props = $props();
33
44
 
45
+ const uid = $props.id();
34
46
  let textarea = $state<HTMLTextAreaElement | null>(null);
47
+ let card: HTMLDivElement | undefined = $state();
35
48
  let fileInput: HTMLInputElement | undefined = $state();
36
- let uploading = $state(0);
49
+ /** Files being uploaded right now, shown as chips with a spinner. */
50
+ let pending = $state<{ key: string; name: string; bytes: number; mimeType: string }[]>([]);
37
51
  let uploadError = $state<string | undefined>();
52
+ let dragging = $state(false);
53
+ let dragDepth = 0;
38
54
 
39
55
  export function focus(): void {
40
56
  textarea?.focus({ preventScroll: true });
41
57
  }
42
58
 
59
+ // `/` prompt menu
60
+ const query = $derived(prompts?.length ? promptQuery(value) : null);
61
+ const matches = $derived(query === null ? [] : filterPrompts(prompts ?? [], query));
62
+ let dismissedFor = $state<string | null>(null);
63
+ const menuOpen = $derived(query !== null && matches.length > 0 && dismissedFor !== value);
64
+ let active = $state(0);
65
+ let loadingPrompt = $state(false);
66
+ $effect(() => {
67
+ void query;
68
+ active = 0;
69
+ });
70
+ async function choose(prompt: ComposerPrompt | undefined): Promise<void> {
71
+ if (!prompt) return;
72
+ if (typeof prompt.text === 'string') value = prompt.text;
73
+ else {
74
+ loadingPrompt = true;
75
+ try {
76
+ value = await prompt.text();
77
+ } catch {
78
+ uploadError = `Could not load “${prompt.label}”`;
79
+ } finally {
80
+ loadingPrompt = false;
81
+ }
82
+ }
83
+ focus();
84
+ }
85
+
86
+ const chars = $derived(value.length);
87
+ const showCount = $derived(chars > LIMITS.messageChars * 0.8);
88
+ const busy = $derived(pending.length > 0 || loadingPrompt);
89
+
43
90
  function submit(): void {
44
91
  const text = value.trim();
45
- if (!text || disabled || streaming || uploading > 0) return;
92
+ if (!text || disabled || streaming || busy) return;
46
93
  value = '';
47
94
  onsend?.(text);
48
95
  }
49
96
  function onkeydown(event: KeyboardEvent): void {
97
+ if (menuOpen) {
98
+ const menu = promptMenuKeyAction(event);
99
+ if (menu) {
100
+ event.preventDefault();
101
+ if (menu === 'next') active = (active + 1) % matches.length;
102
+ else if (menu === 'previous') active = (active - 1 + matches.length) % matches.length;
103
+ else if (menu === 'choose') void choose(matches[active]);
104
+ else dismissedFor = value;
105
+ return;
106
+ }
107
+ }
50
108
  const action = composerKeyAction(event, streaming);
51
109
  if (action === 'send') {
52
110
  event.preventDefault();
@@ -56,31 +114,70 @@
56
114
  oncancel?.();
57
115
  }
58
116
  }
59
- async function onfiles(event: Event): Promise<void> {
60
- const input = event.currentTarget as HTMLInputElement;
61
- const chosen = [...(input.files ?? [])];
62
- input.value = '';
117
+
118
+ async function addFiles(chosen: File[]): Promise<void> {
63
119
  if (!upload || chosen.length === 0) return;
64
120
  uploadError = undefined;
65
121
  for (const file of chosen) {
122
+ if (files.length + pending.length >= LIMITS.requestFiles) {
123
+ uploadError = `Attach up to ${LIMITS.requestFiles} files per message`;
124
+ break;
125
+ }
66
126
  if (file.size > LIMITS.fileBytes) {
67
127
  uploadError = `${file.name} is larger than ${Math.round(LIMITS.fileBytes / 1048576)} MB`;
68
128
  continue;
69
129
  }
70
- uploading += 1;
130
+ const key = `${file.name}-${file.size}-${Math.random().toString(36).slice(2)}`;
131
+ pending = [...pending, { key, name: file.name, bytes: file.size, mimeType: file.type }];
71
132
  try {
72
133
  files = [...files, await upload(file)];
73
134
  } catch (err) {
74
135
  uploadError = err instanceof Error ? err.message : `Could not upload ${file.name}`;
75
136
  } finally {
76
- uploading -= 1;
137
+ pending = pending.filter((p) => p.key !== key);
77
138
  }
78
139
  }
79
140
  }
141
+ function onfiles(event: Event): void {
142
+ const input = event.currentTarget as HTMLInputElement;
143
+ const chosen = [...(input.files ?? [])];
144
+ input.value = '';
145
+ void addFiles(chosen);
146
+ }
147
+ function onpaste(event: ClipboardEvent): void {
148
+ const pasted = [...(event.clipboardData?.files ?? [])];
149
+ if (!upload || pasted.length === 0) return;
150
+ // Pasting a screenshot or a copied file attaches it; plain text pastes as usual.
151
+ event.preventDefault();
152
+ void addFiles(pasted);
153
+ }
154
+ const hasFiles = (event: DragEvent) => event.dataTransfer?.types.includes('Files') ?? false;
155
+ function ondragenter(event: DragEvent): void {
156
+ if (!upload || disabled || !hasFiles(event)) return;
157
+ event.preventDefault();
158
+ dragDepth += 1;
159
+ dragging = true;
160
+ }
161
+ function ondragover(event: DragEvent): void {
162
+ if (!upload || disabled || !hasFiles(event)) return;
163
+ event.preventDefault();
164
+ if (event.dataTransfer) event.dataTransfer.dropEffect = 'copy';
165
+ }
166
+ function ondragleave(): void {
167
+ dragDepth = Math.max(0, dragDepth - 1);
168
+ if (dragDepth === 0) dragging = false;
169
+ }
170
+ function ondrop(event: DragEvent): void {
171
+ if (!upload || disabled || !hasFiles(event)) return;
172
+ event.preventDefault();
173
+ dragDepth = 0;
174
+ dragging = false;
175
+ void addFiles([...(event.dataTransfer?.files ?? [])]);
176
+ }
80
177
  </script>
81
178
 
82
179
  <form
83
- class="bg-background px-4 pb-[max(0.75rem,env(safe-area-inset-bottom))] pt-2"
180
+ class="group/composer bg-background px-4 pb-[max(0.75rem,env(safe-area-inset-bottom))] pt-2"
84
181
  onsubmit={(e) => {
85
182
  e.preventDefault();
86
183
  submit();
@@ -88,29 +185,58 @@
88
185
  aria-label="Send a message"
89
186
  >
90
187
  <div
91
- class="mx-auto w-full max-w-3xl rounded-xl border border-input bg-background shadow-sm transition-shadow focus-within:border-ring focus-within:ring-2 focus-within:ring-ring/20"
188
+ bind:this={card}
189
+ class={cn(
190
+ 'relative mx-auto w-full max-w-3xl rounded-xl border border-input bg-background shadow-sm transition-shadow focus-within:border-ring focus-within:ring-2 focus-within:ring-ring/20',
191
+ dragging && 'border-primary ring-2 ring-primary/30',
192
+ )}
193
+ role="group"
194
+ aria-label="Message composer"
195
+ {ondragenter}
196
+ {ondragover}
197
+ {ondragleave}
198
+ {ondrop}
92
199
  >
93
- {#if files.length || uploadError}
94
- <div class="flex flex-wrap items-center gap-1.5 px-3 pt-3 text-xs">
200
+ {#if dragging}
201
+ <div
202
+ class="pointer-events-none absolute inset-0 z-10 flex items-center justify-center gap-2 rounded-xl bg-background/90 text-sm font-medium text-primary"
203
+ >
204
+ <Icon name="paperclip" size={16} /> Drop files to attach
205
+ </div>
206
+ {/if}
207
+ {#if files.length || pending.length || uploadError}
208
+ <ul class="flex flex-wrap items-center gap-1.5 px-3 pt-3 text-xs" aria-label="Attachments">
95
209
  {#each files as file (file.fileId)}
96
- <span
97
- class="inline-flex max-w-full items-center gap-1 rounded-full bg-muted py-0.5 pl-2 pr-0.5"
210
+ <li
211
+ class="inline-flex max-w-60 items-center gap-1.5 rounded-md border border-border bg-background py-1 pl-2 pr-0.5"
98
212
  >
99
- <Icon name="file" size={12} class="shrink-0 text-muted-foreground" />
213
+ <Icon name={fileIcon(file.mimeType)} size={14} class="shrink-0 text-muted-foreground" />
100
214
  <span class="truncate">{file.name}</span>
101
- <button
215
+ <span class="shrink-0 text-muted-foreground">{formatBytes(file.bytes)}</span>
216
+ <Button
102
217
  type="button"
103
- class="inline-flex size-5 shrink-0 items-center justify-center rounded-full text-muted-foreground hover:bg-background hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
218
+ variant="ghost"
219
+ size="icon"
220
+ class="size-6 shrink-0 text-muted-foreground"
104
221
  aria-label={`Remove ${file.name}`}
105
222
  onclick={() => {
106
223
  files = files.filter((f) => f.fileId !== file.fileId);
107
224
  onremovefile?.(file.fileId);
108
- }}><Icon name="close" size={12} /></button
225
+ }}><Icon name="close" size={12} /></Button
109
226
  >
110
- </span>
227
+ </li>
111
228
  {/each}
112
- {#if uploadError}<span class="text-destructive" role="alert">{uploadError}</span>{/if}
113
- </div>
229
+ {#each pending as file (file.key)}
230
+ <li
231
+ class="inline-flex max-w-60 items-center gap-1.5 rounded-md border border-dashed border-border py-1 pl-2 pr-2 text-muted-foreground"
232
+ >
233
+ <Spinner size="sm" label={`Uploading ${file.name}`} class="shrink-0" />
234
+ <span class="truncate">{file.name}</span>
235
+ <span class="shrink-0">{formatBytes(file.bytes)}</span>
236
+ </li>
237
+ {/each}
238
+ {#if uploadError}<li class="text-destructive" role="alert">{uploadError}</li>{/if}
239
+ </ul>
114
240
  {/if}
115
241
  <Textarea
116
242
  bind:ref={textarea}
@@ -122,8 +248,14 @@
122
248
  {placeholder}
123
249
  aria-label="Message"
124
250
  maxlength={LIMITS.messageChars}
251
+ role={prompts?.length ? 'combobox' : undefined}
252
+ aria-autocomplete={prompts?.length ? 'list' : undefined}
253
+ aria-expanded={prompts?.length ? menuOpen : undefined}
254
+ aria-controls={menuOpen ? `${uid}-prompts` : undefined}
255
+ aria-activedescendant={menuOpen ? `${uid}-prompt-${active}` : undefined}
125
256
  {disabled}
126
257
  {onkeydown}
258
+ {onpaste}
127
259
  />
128
260
  <div class="flex items-center gap-1 px-2 pb-2">
129
261
  {#if upload}
@@ -134,22 +266,31 @@
134
266
  size="icon"
135
267
  class="size-8 text-muted-foreground"
136
268
  aria-label="Attach files"
137
- disabled={disabled || uploading > 0}
138
- loading={uploading > 0}
269
+ disabled={disabled || files.length >= LIMITS.requestFiles}
139
270
  onclick={() => fileInput?.click()}
140
271
  >
141
- {#if uploading === 0}<Icon name="paperclip" size={18} />{/if}
272
+ <Icon name="paperclip" size={18} />
142
273
  </Button>
143
274
  {/if}
144
275
  {#if context}<div class="min-w-0">{@render context()}</div>{/if}
145
- <div class="ml-auto">
276
+ <div class="ml-auto flex items-center gap-2">
277
+ {#if showCount}
278
+ <span
279
+ class={cn(
280
+ 'text-xs tabular-nums text-muted-foreground',
281
+ chars >= LIMITS.messageChars && 'text-destructive',
282
+ )}
283
+ aria-live="polite"
284
+ >{chars.toLocaleString()} / {LIMITS.messageChars.toLocaleString()}</span
285
+ >
286
+ {/if}
146
287
  {#if streaming}
147
288
  <Button
148
289
  type="button"
149
290
  variant="outline"
150
291
  size="icon"
151
292
  class="size-8"
152
- aria-label="Stop"
293
+ aria-label="Stop (Esc)"
153
294
  onclick={() => oncancel?.()}><Icon name="stop" size={14} /></Button
154
295
  >
155
296
  {:else}
@@ -158,11 +299,61 @@
158
299
  size="icon"
159
300
  class="size-8"
160
301
  aria-label="Send"
161
- disabled={disabled || !value.trim() || uploading > 0}
162
- ><Icon name="send" size={16} /></Button
302
+ disabled={disabled || !value.trim() || busy}><Icon name="send" size={16} /></Button
163
303
  >
164
304
  {/if}
165
305
  </div>
166
306
  </div>
167
307
  </div>
308
+ <p
309
+ class="mx-auto mt-1.5 hidden max-w-3xl items-center gap-1.5 px-1 text-[11px] text-muted-foreground opacity-0 transition-opacity group-focus-within/composer:opacity-100 sm:flex"
310
+ aria-hidden="true"
311
+ >
312
+ {#if streaming}
313
+ <Kbd size="sm">Esc</Kbd> to stop
314
+ {:else}
315
+ <Kbd size="sm">Enter</Kbd> to send · <Kbd keys={['Shift', 'Enter']} size="sm" /> for a new line
316
+ {#if prompts?.length}· <Kbd size="sm">/</Kbd> for saved prompts{/if}
317
+ {/if}
318
+ </p>
168
319
  </form>
320
+
321
+ {#if prompts?.length}
322
+ <Popover
323
+ open={menuOpen}
324
+ onOpenChange={(next) => {
325
+ if (!next) dismissedFor = value;
326
+ }}
327
+ anchor={card ?? null}
328
+ side="top"
329
+ align="start"
330
+ sideOffset={6}
331
+ autoFocus={false}
332
+ aria-label="Saved prompts"
333
+ class="w-[min(28rem,calc(100vw-2rem))] p-1"
334
+ >
335
+ <ul id={`${uid}-prompts`} role="listbox" aria-label="Saved prompts" class="grid gap-0.5">
336
+ {#each matches as prompt, i (prompt.id)}
337
+ <!-- Keyboard choice happens in the textarea (combobox + aria-activedescendant). -->
338
+ <!-- svelte-ignore a11y_click_events_have_key_events -->
339
+ <li
340
+ id={`${uid}-prompt-${i}`}
341
+ role="option"
342
+ aria-selected={i === active}
343
+ class={cn(
344
+ 'cursor-pointer rounded-md px-2.5 py-1.5 text-sm',
345
+ i === active ? 'bg-muted text-foreground' : 'text-foreground hover:bg-muted/60',
346
+ )}
347
+ onpointerdown={(e) => e.preventDefault()}
348
+ onclick={() => void choose(prompt)}
349
+ onpointermove={() => (active = i)}
350
+ >
351
+ <span class="block truncate font-medium">{prompt.label}</span>
352
+ {#if prompt.description}
353
+ <span class="block truncate text-xs text-muted-foreground">{prompt.description}</span>
354
+ {/if}
355
+ </li>
356
+ {/each}
357
+ </ul>
358
+ </Popover>
359
+ {/if}
@@ -1,4 +1,5 @@
1
1
  import type { FileRef } from '@classic-homes/agent-contracts';
2
+ import { type ComposerPrompt } from '../composer-prompts.js';
2
3
  interface Props {
3
4
  disabled?: boolean;
4
5
  streaming?: boolean;
@@ -7,12 +8,19 @@ interface Props {
7
8
  placeholder?: string;
8
9
  /** Upload a file; the composer shows it as pending until the message is sent. */
9
10
  upload?: (file: File) => Promise<FileRef>;
11
+ /** Saved prompts for the `/` menu; the menu is off without them. */
12
+ prompts?: ComposerPrompt[];
10
13
  onsend?: (text: string) => void;
11
14
  oncancel?: () => void;
12
15
  onremovefile?: (fileId: string) => void;
13
16
  /** Slot for a host-provided context chip (page context toggle). */
14
17
  context?: import('svelte').Snippet;
15
18
  }
19
+ /**
20
+ * The message box: an autosizing textarea in a card with attach, the host's context chip and
21
+ * Send/Stop. Files can also be dropped on it or pasted. Typing `/` at the start opens the saved
22
+ * prompt menu when the host passes `prompts`.
23
+ */
16
24
  declare const Composer: import("svelte").Component<Props, {
17
25
  focus: () => void;
18
26
  }, "value" | "files">;
@@ -1 +1 @@
1
- {"version":3,"file":"Composer.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/Composer.svelte.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,gCAAgC,CAAC;AAK5D,UAAU,KAAK;IACb,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,OAAO,EAAE,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,iFAAiF;IACjF,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAC1C,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAChC,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,mEAAmE;IACnE,OAAO,CAAC,EAAE,OAAO,QAAQ,EAAE,OAAO,CAAC;CACpC;AA8GH,QAAA,MAAM,QAAQ;iBArFO,IAAI;qBAqF6B,CAAC;AACvD,KAAK,QAAQ,GAAG,UAAU,CAAC,OAAO,QAAQ,CAAC,CAAC;AAC5C,eAAe,QAAQ,CAAC"}
1
+ {"version":3,"file":"Composer.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/Composer.svelte.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,gCAAgC,CAAC;AAG9D,OAAO,EAA8B,KAAK,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAIvF,UAAU,KAAK;IACb,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,OAAO,EAAE,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,iFAAiF;IACjF,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAC1C,oEAAoE;IACpE,OAAO,CAAC,EAAE,cAAc,EAAE,CAAC;IAC3B,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAChC,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,mEAAmE;IACnE,OAAO,CAAC,EAAE,OAAO,QAAQ,EAAE,OAAO,CAAC;CACpC;AA2PH;;;;GAIG;AACH,QAAA,MAAM,QAAQ;iBA/NO,IAAI;qBA+N6B,CAAC;AACvD,KAAK,QAAQ,GAAG,UAAU,CAAC,OAAO,QAAQ,CAAC,CAAC;AAC5C,eAAe,QAAQ,CAAC"}
@@ -13,6 +13,7 @@
13
13
  last?: boolean;
14
14
  sandboxOrigin: string;
15
15
  fileUrl: (fileId: string) => string;
16
+ toolLabels?: Record<string, string>;
16
17
  onnavigate?: (href: string) => void;
17
18
  onopenartifact?: (artifactId: string) => void;
18
19
  onprompt?: (text: string) => void;
@@ -5,6 +5,7 @@ interface Props {
5
5
  last?: boolean;
6
6
  sandboxOrigin: string;
7
7
  fileUrl: (fileId: string) => string;
8
+ toolLabels?: Record<string, string>;
8
9
  onnavigate?: (href: string) => void;
9
10
  onopenartifact?: (artifactId: string) => void;
10
11
  onprompt?: (text: string) => void;
@@ -1 +1 @@
1
- {"version":3,"file":"MessageItem.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/MessageItem.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAK/C,UAAU,KAAK;IACb,OAAO,EAAE,WAAW,CAAC;IACrB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,aAAa,EAAE,MAAM,CAAC;IACtB,OAAO,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,MAAM,CAAC;IACpC,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,cAAc,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;IAC9C,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,MAAM,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;IACtE,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACrC,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;CACzB;AAkBH,sEAAsE;AACtE,QAAA,MAAM,WAAW,2CAAwC,CAAC;AAC1D,KAAK,WAAW,GAAG,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC;AAClD,eAAe,WAAW,CAAC"}
1
+ {"version":3,"file":"MessageItem.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/MessageItem.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAK/C,UAAU,KAAK;IACb,OAAO,EAAE,WAAW,CAAC;IACrB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,aAAa,EAAE,MAAM,CAAC;IACtB,OAAO,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,MAAM,CAAC;IACpC,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACpC,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,cAAc,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;IAC9C,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,MAAM,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;IACtE,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACrC,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;CACzB;AAkBH,sEAAsE;AACtE,QAAA,MAAM,WAAW,2CAAwC,CAAC;AAC1D,KAAK,WAAW,GAAG,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC;AAClD,eAAe,WAAW,CAAC"}
@@ -10,6 +10,7 @@
10
10
  streaming: boolean;
11
11
  sandboxOrigin: string;
12
12
  fileUrl: (fileId: string) => string;
13
+ toolLabels?: Record<string, string>;
13
14
  onnavigate?: (href: string) => void;
14
15
  onopenartifact?: (artifactId: string) => void;
15
16
  onprompt?: (text: string) => void;
@@ -24,6 +25,7 @@
24
25
  streaming,
25
26
  sandboxOrigin,
26
27
  fileUrl,
28
+ toolLabels,
27
29
  onnavigate,
28
30
  onopenartifact,
29
31
  onprompt,
@@ -133,6 +135,7 @@
133
135
  streaming={streaming && i === messages.length - 1 && message.role === 'assistant'}
134
136
  {sandboxOrigin}
135
137
  {fileUrl}
138
+ {toolLabels}
136
139
  {onnavigate}
137
140
  {onopenartifact}
138
141
  {onprompt}
@@ -4,6 +4,7 @@ interface Props {
4
4
  streaming: boolean;
5
5
  sandboxOrigin: string;
6
6
  fileUrl: (fileId: string) => string;
7
+ toolLabels?: Record<string, string>;
7
8
  onnavigate?: (href: string) => void;
8
9
  onopenartifact?: (artifactId: string) => void;
9
10
  onprompt?: (text: string) => void;
@@ -1 +1 @@
1
- {"version":3,"file":"MessageList.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/MessageList.svelte.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAK/C,UAAU,KAAK;IACb,QAAQ,EAAE,WAAW,EAAE,CAAC;IACxB,SAAS,EAAE,OAAO,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,OAAO,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,MAAM,CAAC;IACpC,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,cAAc,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;IAC9C,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,MAAM,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;IACtE,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACrC,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;IACxB,KAAK,CAAC,EAAE,OAAO,QAAQ,EAAE,OAAO,CAAC;CAClC;AA2HH,QAAA,MAAM,WAAW,2CAAwC,CAAC;AAC1D,KAAK,WAAW,GAAG,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC;AAClD,eAAe,WAAW,CAAC"}
1
+ {"version":3,"file":"MessageList.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/MessageList.svelte.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAK/C,UAAU,KAAK;IACb,QAAQ,EAAE,WAAW,EAAE,CAAC;IACxB,SAAS,EAAE,OAAO,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,OAAO,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,MAAM,CAAC;IACpC,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACpC,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,cAAc,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;IAC9C,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,MAAM,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;IACtE,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACrC,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;IACxB,KAAK,CAAC,EAAE,OAAO,QAAQ,EAAE,OAAO,CAAC;CAClC;AA4HH,QAAA,MAAM,WAAW,2CAAwC,CAAC;AAC1D,KAAK,WAAW,GAAG,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC;AAClD,eAAe,WAAW,CAAC"}