@classic-homes/agent-chat 0.2.1 → 0.4.0

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 (58) hide show
  1. package/README.md +18 -2
  2. package/dist/components/AgentChat.svelte +10 -0
  3. package/dist/components/AgentChat.svelte.d.ts +5 -0
  4. package/dist/components/AgentChat.svelte.d.ts.map +1 -1
  5. package/dist/components/ArtifactChip.svelte +45 -0
  6. package/dist/components/ArtifactChip.svelte.d.ts +14 -0
  7. package/dist/components/ArtifactChip.svelte.d.ts.map +1 -0
  8. package/dist/components/AssistantMessage.svelte +186 -0
  9. package/dist/components/AssistantMessage.svelte.d.ts +26 -0
  10. package/dist/components/AssistantMessage.svelte.d.ts.map +1 -0
  11. package/dist/components/Composer.svelte +221 -30
  12. package/dist/components/Composer.svelte.d.ts +8 -0
  13. package/dist/components/Composer.svelte.d.ts.map +1 -1
  14. package/dist/components/Markdown.svelte +87 -5
  15. package/dist/components/Markdown.svelte.d.ts +9 -1
  16. package/dist/components/Markdown.svelte.d.ts.map +1 -1
  17. package/dist/components/MessageActions.svelte +56 -0
  18. package/dist/components/MessageActions.svelte.d.ts +17 -0
  19. package/dist/components/MessageActions.svelte.d.ts.map +1 -0
  20. package/dist/components/MessageFiles.svelte +83 -0
  21. package/dist/components/MessageFiles.svelte.d.ts +14 -0
  22. package/dist/components/MessageFiles.svelte.d.ts.map +1 -0
  23. package/dist/components/MessageItem.svelte +12 -144
  24. package/dist/components/MessageItem.svelte.d.ts +4 -0
  25. package/dist/components/MessageItem.svelte.d.ts.map +1 -1
  26. package/dist/components/MessageList.svelte +102 -30
  27. package/dist/components/MessageList.svelte.d.ts +2 -0
  28. package/dist/components/MessageList.svelte.d.ts.map +1 -1
  29. package/dist/components/ProposalCard.svelte +39 -0
  30. package/dist/components/ProposalCard.svelte.d.ts +13 -0
  31. package/dist/components/ProposalCard.svelte.d.ts.map +1 -0
  32. package/dist/components/ToolTimeline.svelte +138 -54
  33. package/dist/components/ToolTimeline.svelte.d.ts +11 -3
  34. package/dist/components/ToolTimeline.svelte.d.ts.map +1 -1
  35. package/dist/components/UserMessage.svelte +46 -0
  36. package/dist/components/UserMessage.svelte.d.ts +10 -0
  37. package/dist/components/UserMessage.svelte.d.ts.map +1 -0
  38. package/dist/components/composer.svelte.test.d.ts +2 -0
  39. package/dist/components/composer.svelte.test.d.ts.map +1 -0
  40. package/dist/components/composer.svelte.test.js +162 -0
  41. package/dist/components/messages.svelte.test.d.ts +2 -0
  42. package/dist/components/messages.svelte.test.d.ts.map +1 -0
  43. package/dist/components/messages.svelte.test.js +202 -0
  44. package/dist/composer-keys.d.ts +3 -0
  45. package/dist/composer-keys.d.ts.map +1 -1
  46. package/dist/composer-keys.js +18 -0
  47. package/dist/composer-prompts.d.ts +16 -0
  48. package/dist/composer-prompts.d.ts.map +1 -0
  49. package/dist/composer-prompts.js +27 -0
  50. package/dist/index.d.ts +8 -0
  51. package/dist/index.d.ts.map +1 -1
  52. package/dist/index.js +8 -0
  53. package/dist/markdown.d.ts.map +1 -1
  54. package/dist/markdown.js +12 -4
  55. package/dist/message-format.d.ts +58 -0
  56. package/dist/message-format.d.ts.map +1 -0
  57. package/dist/message-format.js +149 -0
  58. package/package.json +3 -3
package/README.md CHANGED
@@ -21,8 +21,9 @@ artifacts and MCP-UI sandbox frames. The Classic App's `/agent` page and agent p
21
21
  };
22
22
  ```
23
23
 
24
- Without that entry the chat renders unstyled. Markdown prose (rendered from model text) is the
25
- one part styled with plain CSS, shipped inside the `Markdown` component.
24
+ Without that entry the chat renders unstyled. Markdown prose (rendered from model text) uses
25
+ arbitrary-variant utilities on its container (`[&_ul]:list-disc`), not a stylesheet: the
26
+ Classic App's cascade-layers polyfill outranks plain component CSS.
26
27
 
27
28
  ## Usage
28
29
 
@@ -37,4 +38,19 @@ artifacts and MCP-UI sandbox frames. The Classic App's `/agent` page and agent p
37
38
  `client` is an `AgentApiClient` from `@classic-homes/agent-client`. `sandboxOrigin` is the
38
39
  agent-api origin that serves `/frame`; artifacts and MCP-UI resources only ever render there.
39
40
 
41
+ ## Components
42
+
43
+ `AgentChat` composes the rest; each is also exported for other layouts:
44
+
45
+ - `ChatHeader`, `ConversationSidebar`: title and conversation actions; the history rail.
46
+ - `MessageList`: transcript with day separators and a jump-to-latest button.
47
+ - `MessageItem` → `UserMessage` / `AssistantMessage`, with `MessageActions` (copy, time),
48
+ `ArtifactChip`, `MessageFiles` (image lightbox, downloads) and `ProposalCard` (read-only
49
+ document edits; applying stays in the Office add-in).
50
+ - `Composer`: attach by button, drop or paste; `prompts` turns on the `/` saved-prompt menu
51
+ (`ComposerPrompt.text` may be a loader, e.g. fetching the template).
52
+ - `ToolTimeline`: tool calls with status and duration, delegations nested; `toolLabels` maps
53
+ tool names to readable ones (`AgentChat` passes both through).
54
+ - `ArtifactPanel`, `UiFrame`, `ModeBanner`.
55
+
40
56
  Publishing: `docs/operations/package-publishing.md`.
@@ -10,6 +10,7 @@
10
10
  import { cn, ResizablePanes, Sheet } from '@classic-homes/theme-svelte';
11
11
  import { MediaQuery } from 'svelte/reactivity';
12
12
  import { releaseStuckBodyLock } from '../body-lock.js';
13
+ import type { ComposerPrompt } from '../composer-prompts.js';
13
14
  import { ChatSession } from '../session.svelte.js';
14
15
  import ArtifactPanel from './ArtifactPanel.svelte';
15
16
  import Composer from './Composer.svelte';
@@ -40,6 +41,10 @@
40
41
  /** A UI resource asked to run a tool; the host confirms with the user and may send a message. */
41
42
  ontoolrequest?: (toolName: string, params?: Record<string, unknown>) => void;
42
43
  placeholder?: string;
44
+ /** Saved prompts offered by the composer's `/` menu. */
45
+ prompts?: ComposerPrompt[];
46
+ /** Readable tool names for the timeline, by tool name (`server/name` or `name`). */
47
+ toolLabels?: Record<string, string>;
43
48
  /** Rendered above the transcript, e.g. a `ChatHeader`. */
44
49
  header?: import('svelte').Snippet;
45
50
  context?: import('svelte').Snippet;
@@ -62,6 +67,8 @@
62
67
  onnotify,
63
68
  ontoolrequest,
64
69
  placeholder,
70
+ prompts,
71
+ toolLabels,
65
72
  header,
66
73
  context,
67
74
  empty,
@@ -146,6 +153,7 @@
146
153
  streaming={session.streaming}
147
154
  {sandboxOrigin}
148
155
  fileUrl={(id) => client.fileUrl(id)}
156
+ {toolLabels}
149
157
  {onnavigate}
150
158
  onopenartifact={(id) => {
151
159
  selectedArtifact = id;
@@ -155,6 +163,7 @@
155
163
  ontool={(name, params) => ontoolrequest?.(name, params)}
156
164
  {onnotify}
157
165
  onretry={retry}
166
+ oncontinue={() => void session.send('Continue.')}
158
167
  {empty}
159
168
  />
160
169
  <Composer
@@ -164,6 +173,7 @@
164
173
  disabled={session.status === 'loading'}
165
174
  streaming={session.streaming}
166
175
  {placeholder}
176
+ {prompts}
167
177
  upload={(file) => client.uploadFile(file, file.name)}
168
178
  onsend={send}
169
179
  oncancel={() => session.cancel()}
@@ -1,6 +1,7 @@
1
1
  import type { AgentApiClient } from '@classic-homes/agent-client';
2
2
  import type { AgentKey, ClientKind, DocumentContext } from '@classic-homes/agent-contracts';
3
3
  import type { BackendMode } from '@classic-homes/agent-contracts';
4
+ import type { ComposerPrompt } from '../composer-prompts.js';
4
5
  interface Props {
5
6
  client: AgentApiClient;
6
7
  /** Sandbox origin for artifacts and MCP-UI (agent-api `/frame`). */
@@ -25,6 +26,10 @@ interface Props {
25
26
  /** A UI resource asked to run a tool; the host confirms with the user and may send a message. */
26
27
  ontoolrequest?: (toolName: string, params?: Record<string, unknown>) => void;
27
28
  placeholder?: string;
29
+ /** Saved prompts offered by the composer's `/` menu. */
30
+ prompts?: ComposerPrompt[];
31
+ /** Readable tool names for the timeline, by tool name (`server/name` or `name`). */
32
+ toolLabels?: Record<string, string>;
28
33
  /** Rendered above the transcript, e.g. a `ChatHeader`. */
29
34
  header?: import('svelte').Snippet;
30
35
  context?: import('svelte').Snippet;
@@ -1 +1 @@
1
- {"version":3,"file":"AgentChat.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/AgentChat.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAClE,OAAO,KAAK,EACR,QAAQ,EACR,UAAU,EACV,eAAe,EAEhB,MAAM,gCAAgC,CAAC;AAC1C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gCAAgC,CAAC;AAWhE,UAAU,KAAK;IACb,MAAM,EAAE,cAAc,CAAC;IACvB,oEAAoE;IACpE,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,+EAA+E;IAC/E,IAAI,CAAC,EAAE,WAAW,CAAC;IACnB,KAAK,CAAC,EAAE,QAAQ,CAAC;IACjB,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IAC3B,UAAU,CAAC,EAAE,MAAM,eAAe,GAAG,IAAI,GAAG,SAAS,CAAC;IACtD,qBAAqB,CAAC,EAAE,CAAC,cAAc,EAAE,MAAM,KAAK,IAAI,CAAC;IACzD,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACtD,gFAAgF;IAChF,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE;QACjB,SAAS,EAAE,MAAM,CAAC;QAClB,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;QAC9B,MAAM,EAAE,MAAM,CAAC;KAChB,KAAK,IAAI,CAAC;IACX,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACrC,iGAAiG;IACjG,aAAa,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;IAC7E,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,0DAA0D;IAC1D,MAAM,CAAC,EAAE,OAAO,QAAQ,EAAE,OAAO,CAAC;IAClC,OAAO,CAAC,EAAE,OAAO,QAAQ,EAAE,OAAO,CAAC;IACnC,KAAK,CAAC,EAAE,OAAO,QAAQ,EAAE,OAAO,CAAC;IACjC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AA6IH,QAAA,MAAM,SAAS,2CAAwC,CAAC;AACxD,KAAK,SAAS,GAAG,UAAU,CAAC,OAAO,SAAS,CAAC,CAAC;AAC9C,eAAe,SAAS,CAAC"}
1
+ {"version":3,"file":"AgentChat.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/AgentChat.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAClE,OAAO,KAAK,EACR,QAAQ,EACR,UAAU,EACV,eAAe,EAEhB,MAAM,gCAAgC,CAAC;AAC1C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gCAAgC,CAAC;AAIlE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAQ3D,UAAU,KAAK;IACb,MAAM,EAAE,cAAc,CAAC;IACvB,oEAAoE;IACpE,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,+EAA+E;IAC/E,IAAI,CAAC,EAAE,WAAW,CAAC;IACnB,KAAK,CAAC,EAAE,QAAQ,CAAC;IACjB,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IAC3B,UAAU,CAAC,EAAE,MAAM,eAAe,GAAG,IAAI,GAAG,SAAS,CAAC;IACtD,qBAAqB,CAAC,EAAE,CAAC,cAAc,EAAE,MAAM,KAAK,IAAI,CAAC;IACzD,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACtD,gFAAgF;IAChF,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE;QACjB,SAAS,EAAE,MAAM,CAAC;QAClB,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;QAC9B,MAAM,EAAE,MAAM,CAAC;KAChB,KAAK,IAAI,CAAC;IACX,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACrC,iGAAiG;IACjG,aAAa,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;IAC7E,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,wDAAwD;IACxD,OAAO,CAAC,EAAE,cAAc,EAAE,CAAC;IAC3B,oFAAoF;IACpF,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACpC,0DAA0D;IAC1D,MAAM,CAAC,EAAE,OAAO,QAAQ,EAAE,OAAO,CAAC;IAClC,OAAO,CAAC,EAAE,OAAO,QAAQ,EAAE,OAAO,CAAC;IACnC,KAAK,CAAC,EAAE,OAAO,QAAQ,EAAE,OAAO,CAAC;IACjC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAgJH,QAAA,MAAM,SAAS,2CAAwC,CAAC;AACxD,KAAK,SAAS,GAAG,UAAU,CAAC,OAAO,SAAS,CAAC,CAAC;AAC9C,eAAe,SAAS,CAAC"}
@@ -0,0 +1,45 @@
1
+ <!--
2
+ @component
3
+ A card-like button for one artifact in a message: type icon, title and kind, and a spinner while
4
+ the artifact is still being written. Opens it in the artifact pane.
5
+ -->
6
+ <script lang="ts">
7
+ import { cn, Icon, Spinner } from '@classic-homes/theme-svelte';
8
+ import type { Artifact } from '../reducer.js';
9
+ import { artifactIcon, artifactKind } from '../message-format.js';
10
+
11
+ interface Props {
12
+ artifact: Artifact;
13
+ onopen?: (artifactId: string) => void;
14
+ class?: string;
15
+ }
16
+ let { artifact, onopen, class: className }: Props = $props();
17
+ const title = $derived(artifact.title ?? 'Untitled artifact');
18
+ </script>
19
+
20
+ <button
21
+ type="button"
22
+ class={cn(
23
+ 'flex w-full max-w-sm items-center gap-3 rounded-lg border border-border bg-background px-3 py-2 text-left shadow-sm transition hover:border-primary/40 hover:shadow focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring',
24
+ className,
25
+ )}
26
+ aria-label={artifact.open ? `${title} (writing)` : `Open ${title}`}
27
+ onclick={() => onopen?.(artifact.artifactId)}
28
+ >
29
+ <span
30
+ class="flex h-9 w-9 shrink-0 items-center justify-center rounded-md bg-muted text-muted-foreground"
31
+ >
32
+ <Icon name={artifactIcon(artifact.mediaType)} size={18} />
33
+ </span>
34
+ <span class="min-w-0 flex-1">
35
+ <span class="block truncate text-sm font-medium text-foreground">{title}</span>
36
+ <span class="block text-xs text-muted-foreground">
37
+ {artifact.open ? 'Writing…' : artifactKind(artifact.mediaType)}
38
+ </span>
39
+ </span>
40
+ {#if artifact.open}
41
+ <Spinner size="sm" label="Writing" />
42
+ {:else}
43
+ <Icon name="chevron-right" size={16} class="text-muted-foreground" />
44
+ {/if}
45
+ </button>
@@ -0,0 +1,14 @@
1
+ import type { Artifact } from '../reducer.js';
2
+ interface Props {
3
+ artifact: Artifact;
4
+ onopen?: (artifactId: string) => void;
5
+ class?: string;
6
+ }
7
+ /**
8
+ * A card-like button for one artifact in a message: type icon, title and kind, and a spinner while
9
+ * the artifact is still being written. Opens it in the artifact pane.
10
+ */
11
+ declare const ArtifactChip: import("svelte").Component<Props, {}, "">;
12
+ type ArtifactChip = ReturnType<typeof ArtifactChip>;
13
+ export default ArtifactChip;
14
+ //# sourceMappingURL=ArtifactChip.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ArtifactChip.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/ArtifactChip.svelte.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAI5C,UAAU,KAAK;IACb,QAAQ,EAAE,QAAQ,CAAC;IACnB,MAAM,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;IACtC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAiCH;;;GAGG;AACH,QAAA,MAAM,YAAY,2CAAwC,CAAC;AAC3D,KAAK,YAAY,GAAG,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC;AACpD,eAAe,YAAY,CAAC"}
@@ -0,0 +1,186 @@
1
+ <!--
2
+ @component
3
+ The agent's turn: identity mark, tool timeline, the answer (throttled markdown with a caret
4
+ while streaming), artifacts, UI frames, files, proposed edits, the turn's outcome and actions.
5
+ -->
6
+ <script lang="ts">
7
+ import { Alert, Button, Icon } from '@classic-homes/theme-svelte';
8
+ import type { AssistantMessage } from '../reducer.js';
9
+ import { errorTitle } from '../reducer.js';
10
+ import { parseMarkdown } from '../markdown.js';
11
+ import ArtifactChip from './ArtifactChip.svelte';
12
+ import Markdown from './Markdown.svelte';
13
+ import MessageActions from './MessageActions.svelte';
14
+ import MessageFiles from './MessageFiles.svelte';
15
+ import ProposalCard from './ProposalCard.svelte';
16
+ import ToolTimeline from './ToolTimeline.svelte';
17
+ import UiFrame from './UiFrame.svelte';
18
+
19
+ interface Props {
20
+ message: AssistantMessage;
21
+ streaming?: boolean;
22
+ /** The last message: actions stay visible and the turn can be retried or continued. */
23
+ last?: boolean;
24
+ sandboxOrigin: string;
25
+ fileUrl: (fileId: string) => string;
26
+ toolLabels?: Record<string, string>;
27
+ onnavigate?: (href: string) => void;
28
+ onopenartifact?: (artifactId: string) => void;
29
+ onprompt?: (text: string) => void;
30
+ ontool?: (toolName: string, params?: Record<string, unknown>) => void;
31
+ onnotify?: (message: string) => void;
32
+ onretry?: () => void;
33
+ /** Ask the agent to pick up a stopped or cut-short answer. */
34
+ oncontinue?: () => void;
35
+ }
36
+ let {
37
+ message,
38
+ streaming = false,
39
+ last = false,
40
+ sandboxOrigin,
41
+ fileUrl,
42
+ toolLabels,
43
+ onnavigate,
44
+ onopenartifact,
45
+ onprompt,
46
+ ontool,
47
+ onnotify,
48
+ onretry,
49
+ oncontinue,
50
+ }: Props = $props();
51
+
52
+ // Re-parsing markdown on every token is the dominant streaming cost; throttle it.
53
+ const PARSE_THROTTLE_MS = 120;
54
+ let html = $state('');
55
+ let lastParse = 0;
56
+ let timer: ReturnType<typeof setTimeout> | null = null;
57
+ $effect(() => {
58
+ const text = message.text;
59
+ const now = Date.now();
60
+ if (!streaming || now - lastParse >= PARSE_THROTTLE_MS) {
61
+ if (timer) clearTimeout(timer);
62
+ timer = null;
63
+ lastParse = now;
64
+ html = parseMarkdown(text);
65
+ } else if (!timer) {
66
+ timer = setTimeout(
67
+ () => {
68
+ timer = null;
69
+ lastParse = Date.now();
70
+ html = parseMarkdown(message.text);
71
+ },
72
+ PARSE_THROTTLE_MS - (now - lastParse),
73
+ );
74
+ }
75
+ });
76
+ $effect(() => () => {
77
+ if (timer) clearTimeout(timer);
78
+ });
79
+
80
+ function handleClick(event: MouseEvent): void {
81
+ if (!onnavigate) return;
82
+ const anchor = (event.target as HTMLElement | null)?.closest?.('a.agent-internal-link');
83
+ if (!(anchor instanceof HTMLAnchorElement)) return;
84
+ event.preventDefault();
85
+ onnavigate(anchor.getAttribute('href') ?? anchor.href);
86
+ }
87
+
88
+ const thinking = $derived(
89
+ streaming &&
90
+ !message.text &&
91
+ !message.tools.some((t) => !t.status) &&
92
+ !message.artifacts.some((a) => a.open),
93
+ );
94
+ const canContinue = $derived(last && !!oncontinue);
95
+ </script>
96
+
97
+ <div class="group/message flex gap-3 px-4 py-2" data-role="assistant" data-status={message.status}>
98
+ <span
99
+ class="mt-0.5 flex h-7 w-7 shrink-0 items-center justify-center rounded-full bg-primary/10 text-primary"
100
+ aria-hidden="true"
101
+ >
102
+ <Icon name="sparkles" size={14} />
103
+ </span>
104
+ <div class="flex min-w-0 flex-1 flex-col gap-2">
105
+ {#if message.tools.length || message.delegations.length}
106
+ <ToolTimeline
107
+ tools={message.tools}
108
+ delegations={message.delegations}
109
+ {streaming}
110
+ {toolLabels}
111
+ />
112
+ {/if}
113
+ {#if html}<Markdown {html} streaming={streaming && !!message.text} onclick={handleClick} />{/if}
114
+ {#if thinking}
115
+ <div class="flex items-center gap-2 text-sm text-muted-foreground" role="status">
116
+ <span class="flex gap-1" aria-hidden="true">
117
+ <span class="h-1.5 w-1.5 rounded-full bg-current motion-safe:animate-bounce"></span>
118
+ <span
119
+ class="h-1.5 w-1.5 rounded-full bg-current motion-safe:animate-bounce [animation-delay:150ms]"
120
+ ></span>
121
+ <span
122
+ class="h-1.5 w-1.5 rounded-full bg-current motion-safe:animate-bounce [animation-delay:300ms]"
123
+ ></span>
124
+ </span>
125
+ Thinking
126
+ </div>
127
+ {/if}
128
+ {#if message.artifacts.length}
129
+ <div class="flex flex-col gap-1.5">
130
+ {#each message.artifacts as artifact (artifact.artifactId)}
131
+ <ArtifactChip {artifact} onopen={onopenartifact} />
132
+ {/each}
133
+ </div>
134
+ {/if}
135
+ {#each message.ui as resource (resource.callId + resource.uri)}
136
+ <UiFrame {resource} {sandboxOrigin} {onprompt} {ontool} {onnotify} />
137
+ {/each}
138
+ {#if message.files.length}<MessageFiles files={message.files} {fileUrl} />{/if}
139
+ {#if message.proposals.length}
140
+ <div class="flex flex-col gap-1.5">
141
+ {#each message.proposals as operation (operation.operationId)}
142
+ <ProposalCard {operation} />
143
+ {/each}
144
+ </div>
145
+ {/if}
146
+ {#if message.error}
147
+ <Alert variant="error">
148
+ <div class="flex flex-wrap items-center gap-x-3 gap-y-1">
149
+ <p class="min-w-0 flex-1">
150
+ <strong class="font-semibold">{errorTitle(message.error.code)}.</strong>
151
+ {message.error.message}
152
+ </p>
153
+ {#if message.error.retryable && onretry}
154
+ <Button variant="outline" size="sm" onclick={onretry}>
155
+ <Icon name="refresh" size={14} /> Try again
156
+ </Button>
157
+ {/if}
158
+ </div>
159
+ </Alert>
160
+ {:else if message.status === 'cancelled'}
161
+ <div class="flex items-center gap-2 text-sm text-muted-foreground">
162
+ <Icon name="stop" size={14} /> Stopped
163
+ {#if canContinue}
164
+ <Button variant="link" size="sm" class="h-auto p-0" onclick={oncontinue}>Continue</Button>
165
+ {/if}
166
+ </div>
167
+ {:else if message.status === 'incomplete'}
168
+ <Alert variant="warning">
169
+ <div class="flex flex-wrap items-center gap-x-3 gap-y-1">
170
+ <p class="min-w-0 flex-1">The answer was cut short.</p>
171
+ {#if canContinue}
172
+ <Button variant="outline" size="sm" onclick={oncontinue}>Continue</Button>
173
+ {/if}
174
+ </div>
175
+ </Alert>
176
+ {/if}
177
+ {#if !streaming && message.status !== 'streaming'}
178
+ <MessageActions
179
+ text={message.text}
180
+ createdAt={message.createdAt}
181
+ visible={last}
182
+ class="-ml-1.5"
183
+ />
184
+ {/if}
185
+ </div>
186
+ </div>
@@ -0,0 +1,26 @@
1
+ import type { AssistantMessage } from '../reducer.js';
2
+ interface Props {
3
+ message: AssistantMessage;
4
+ streaming?: boolean;
5
+ /** The last message: actions stay visible and the turn can be retried or continued. */
6
+ last?: boolean;
7
+ sandboxOrigin: string;
8
+ fileUrl: (fileId: string) => string;
9
+ toolLabels?: Record<string, string>;
10
+ onnavigate?: (href: string) => void;
11
+ onopenartifact?: (artifactId: string) => void;
12
+ onprompt?: (text: string) => void;
13
+ ontool?: (toolName: string, params?: Record<string, unknown>) => void;
14
+ onnotify?: (message: string) => void;
15
+ onretry?: () => void;
16
+ /** Ask the agent to pick up a stopped or cut-short answer. */
17
+ oncontinue?: () => void;
18
+ }
19
+ /**
20
+ * The agent's turn: identity mark, tool timeline, the answer (throttled markdown with a caret
21
+ * while streaming), artifacts, UI frames, files, proposed edits, the turn's outcome and actions.
22
+ */
23
+ declare const AssistantMessage: import("svelte").Component<Props, {}, "">;
24
+ type AssistantMessage = ReturnType<typeof AssistantMessage>;
25
+ export default AssistantMessage;
26
+ //# sourceMappingURL=AssistantMessage.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AssistantMessage.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/AssistantMessage.svelte.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAYpD,UAAU,KAAK;IACb,OAAO,EAAE,gBAAgB,CAAC;IAC1B,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,uFAAuF;IACvF,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,8DAA8D;IAC9D,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;CACzB;AAwJH;;;GAGG;AACH,QAAA,MAAM,gBAAgB,2CAAwC,CAAC;AAC/D,KAAK,gBAAgB,GAAG,UAAU,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAC5D,eAAe,gBAAgB,CAAC"}