@classic-homes/agent-chat 0.11.0 → 0.13.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.
- package/README.md +67 -2
- package/dist/components/AgentChat.svelte +60 -25
- package/dist/components/AgentChat.svelte.d.ts +11 -1
- package/dist/components/AgentChat.svelte.d.ts.map +1 -1
- package/dist/components/ArtifactPanel.svelte +4 -2
- package/dist/components/AssistantMessage.svelte +58 -16
- package/dist/components/AssistantMessage.svelte.d.ts +2 -0
- package/dist/components/AssistantMessage.svelte.d.ts.map +1 -1
- package/dist/components/ChatHeader.svelte +4 -1
- package/dist/components/ChatHeader.svelte.d.ts +2 -0
- package/dist/components/ChatHeader.svelte.d.ts.map +1 -1
- package/dist/components/ChatLauncher.svelte +175 -0
- package/dist/components/ChatLauncher.svelte.d.ts +26 -0
- package/dist/components/ChatLauncher.svelte.d.ts.map +1 -0
- package/dist/components/Composer.svelte +7 -3
- package/dist/components/Composer.svelte.d.ts.map +1 -1
- package/dist/components/FloatingWindow.svelte +280 -0
- package/dist/components/FloatingWindow.svelte.d.ts +35 -0
- package/dist/components/FloatingWindow.svelte.d.ts.map +1 -0
- package/dist/components/Markdown.svelte +4 -0
- package/dist/components/Markdown.svelte.d.ts.map +1 -1
- package/dist/components/MessageFeedback.svelte +11 -4
- package/dist/components/MessageFeedback.svelte.d.ts.map +1 -1
- package/dist/components/MessageFiles.svelte +133 -40
- package/dist/components/MessageFiles.svelte.d.ts +6 -0
- package/dist/components/MessageFiles.svelte.d.ts.map +1 -1
- package/dist/components/MessageItem.svelte +2 -0
- package/dist/components/MessageItem.svelte.d.ts +2 -0
- package/dist/components/MessageItem.svelte.d.ts.map +1 -1
- package/dist/components/MessageList.svelte +40 -7
- package/dist/components/MessageList.svelte.d.ts +3 -1
- package/dist/components/MessageList.svelte.d.ts.map +1 -1
- package/dist/components/ShortcutsDialog.svelte +10 -3
- package/dist/components/ShortcutsDialog.svelte.d.ts +2 -0
- package/dist/components/ShortcutsDialog.svelte.d.ts.map +1 -1
- package/dist/components/UiFrame.svelte +12 -4
- package/dist/components/UiFrame.svelte.d.ts.map +1 -1
- package/dist/components/UserMessage.svelte +10 -1
- package/dist/components/UserMessage.svelte.d.ts.map +1 -1
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -0
- package/dist/markdown.d.ts.map +1 -1
- package/dist/markdown.js +94 -61
- package/dist/reducer.d.ts +11 -0
- package/dist/reducer.d.ts.map +1 -1
- package/dist/reducer.js +18 -2
- package/dist/session.svelte.d.ts +40 -0
- package/dist/session.svelte.d.ts.map +1 -1
- package/dist/session.svelte.js +178 -23
- package/dist/shortcuts.d.ts +7 -0
- package/dist/shortcuts.d.ts.map +1 -1
- package/dist/shortcuts.js +9 -0
- package/dist/surface-geometry.d.ts +60 -0
- package/dist/surface-geometry.d.ts.map +1 -0
- package/dist/surface-geometry.js +112 -0
- package/dist/surface.svelte.d.ts +79 -0
- package/dist/surface.svelte.d.ts.map +1 -0
- package/dist/surface.svelte.js +178 -0
- package/dist/thinking.d.ts +10 -0
- package/dist/thinking.d.ts.map +1 -0
- package/dist/thinking.js +61 -0
- package/dist/ui-frame-protocol.d.ts +6 -0
- package/dist/ui-frame-protocol.d.ts.map +1 -1
- package/dist/ui-frame-protocol.js +11 -0
- package/package.json +8 -7
- package/dist/artifact-pages.test.d.ts +0 -2
- package/dist/artifact-pages.test.d.ts.map +0 -1
- package/dist/artifact-pages.test.js +0 -102
- package/dist/components/a11y.svelte.test.d.ts +0 -2
- package/dist/components/a11y.svelte.test.d.ts.map +0 -1
- package/dist/components/a11y.svelte.test.js +0 -183
- package/dist/components/agent-picker.svelte.test.d.ts +0 -2
- package/dist/components/agent-picker.svelte.test.d.ts.map +0 -1
- package/dist/components/agent-picker.svelte.test.js +0 -84
- package/dist/components/composer.svelte.test.d.ts +0 -2
- package/dist/components/composer.svelte.test.d.ts.map +0 -1
- package/dist/components/composer.svelte.test.js +0 -162
- package/dist/components/continue.svelte.test.d.ts +0 -2
- package/dist/components/continue.svelte.test.d.ts.map +0 -1
- package/dist/components/continue.svelte.test.js +0 -109
- package/dist/components/conversation-chrome.svelte.test.d.ts +0 -2
- package/dist/components/conversation-chrome.svelte.test.d.ts.map +0 -1
- package/dist/components/conversation-chrome.svelte.test.js +0 -147
- package/dist/components/feedback.svelte.test.d.ts +0 -2
- package/dist/components/feedback.svelte.test.d.ts.map +0 -1
- package/dist/components/feedback.svelte.test.js +0 -99
- package/dist/components/live-fixes.svelte.test.d.ts +0 -2
- package/dist/components/live-fixes.svelte.test.d.ts.map +0 -1
- package/dist/components/live-fixes.svelte.test.js +0 -127
- package/dist/components/markdown-prose.svelte.test.d.ts +0 -2
- package/dist/components/markdown-prose.svelte.test.d.ts.map +0 -1
- package/dist/components/markdown-prose.svelte.test.js +0 -26
- package/dist/components/messages.svelte.test.d.ts +0 -2
- package/dist/components/messages.svelte.test.d.ts.map +0 -1
- package/dist/components/messages.svelte.test.js +0 -215
- package/dist/components/regenerate.svelte.test.d.ts +0 -2
- package/dist/components/regenerate.svelte.test.d.ts.map +0 -1
- package/dist/components/regenerate.svelte.test.js +0 -108
- package/dist/components/welcome.svelte.test.d.ts +0 -2
- package/dist/components/welcome.svelte.test.d.ts.map +0 -1
- package/dist/components/welcome.svelte.test.js +0 -119
- package/dist/composer-keys.test.d.ts +0 -2
- package/dist/composer-keys.test.d.ts.map +0 -1
- package/dist/composer-keys.test.js +0 -16
- package/dist/markdown.test.d.ts +0 -2
- package/dist/markdown.test.d.ts.map +0 -1
- package/dist/markdown.test.js +0 -35
- package/dist/reducer.test.d.ts +0 -2
- package/dist/reducer.test.d.ts.map +0 -1
- package/dist/reducer.test.js +0 -153
- package/dist/ui-frame-protocol.test.d.ts +0 -2
- package/dist/ui-frame-protocol.test.d.ts.map +0 -1
- package/dist/ui-frame-protocol.test.js +0 -48
- package/dist/ui-frame-relay.test.d.ts +0 -2
- package/dist/ui-frame-relay.test.d.ts.map +0 -1
- package/dist/ui-frame-relay.test.js +0 -36
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@ artifacts and MCP-UI sandbox frames. The Classic App's `/agent` page and agent p
|
|
|
5
5
|
|
|
6
6
|
## Requirements
|
|
7
7
|
|
|
8
|
-
- `svelte` 5 and `@classic-homes/theme-svelte` 1.
|
|
8
|
+
- `svelte` `^5.31.0` and `@classic-homes/theme-svelte` `^1.42.2` (peer dependencies).
|
|
9
9
|
- Tailwind with `@classic-homes/theme-tailwind-preset`. The components are styled with Tailwind
|
|
10
10
|
utility classes and theme tokens, so the consumer's Tailwind build has to scan this package:
|
|
11
11
|
|
|
@@ -38,13 +38,75 @@ artifacts and MCP-UI sandbox frames. The Classic App's `/agent` page and agent p
|
|
|
38
38
|
`client` is an `AgentApiClient` from `@classic-homes/agent-client`. `sandboxOrigin` is the
|
|
39
39
|
agent-api origin that serves `/frame`; artifacts and MCP-UI resources only ever render there.
|
|
40
40
|
|
|
41
|
+
### `conversationId`
|
|
42
|
+
|
|
43
|
+
- `null` (or absent): a new chat. When the first message starts a conversation,
|
|
44
|
+
`onconversationstarted(id)` reports its id; the host may pass that id back as
|
|
45
|
+
`conversationId` (the chat stays mounted and does not reload) or keep passing `null` (the
|
|
46
|
+
chat carries on in that conversation).
|
|
47
|
+
- Another id: the chat loads that transcript, re-attaching to a turn still running there. A
|
|
48
|
+
stream of the conversation shown before is dropped (not cancelled on the server; it finishes
|
|
49
|
+
there) and does not fire `onturnend`. A load that fails shows Try again; it is not retried
|
|
50
|
+
automatically for the same id.
|
|
51
|
+
- From an id back to `null` (the host's "New chat"): the transcript and the conversation are
|
|
52
|
+
cleared, so the next message starts a new conversation.
|
|
53
|
+
|
|
54
|
+
`ChatSession` (used by `AgentChat`, exported for other layouts) has the matching methods:
|
|
55
|
+
`load(id)`, `reset()` (forget the conversation and drop a running stream without a server
|
|
56
|
+
cancel), `send`, `cancel` (asks the runtime to stop and keeps reading until it confirms, at
|
|
57
|
+
most `cancelGraceMs`, default 3 s, so the text up to the stop is kept), `retry`, `regenerate`,
|
|
58
|
+
`edit` and `continue(requestId)`.
|
|
59
|
+
|
|
60
|
+
### Continue
|
|
61
|
+
|
|
62
|
+
An answer cut short by the output limit (`finishReason: 'length'`) or stopped by the user
|
|
63
|
+
(`'cancelled'`) shows Continue: `ChatSession.continue` asks the runtime to carry on and streams
|
|
64
|
+
the rest into the same message. A connection that drops mid-answer is re-attached
|
|
65
|
+
automatically (twice); if that fails the answer shows a retryable error (Try again), not
|
|
66
|
+
Continue. When a continuation itself fails, the text so far stays and Continue is offered again.
|
|
67
|
+
Continue needs a runtime that supports `ChatRequest.continues`; an older one ignores it.
|
|
68
|
+
|
|
69
|
+
### Files
|
|
70
|
+
|
|
71
|
+
The file endpoint needs the bearer, which `<img src>` and download links cannot send. `AgentChat`
|
|
72
|
+
loads produced files with `client.fetchFile` (blob URLs, revoked when the message goes away);
|
|
73
|
+
`MessageFiles` takes a `loadFile(fileId): Promise<Blob>` prop and links to `fileUrl` without one
|
|
74
|
+
(only for cookie-authenticated hosts).
|
|
75
|
+
|
|
76
|
+
### Detached chat (launcher, floating window, dock)
|
|
77
|
+
|
|
78
|
+
A host can show the chat over its pages instead of on a route of its own:
|
|
79
|
+
|
|
80
|
+
- `createChatSurface({ storageKey })` holds which presentation is showing (`launcher`, `float`
|
|
81
|
+
or `dock`), where the launcher sits (`corner`, or `launcher` once the user has placed it), the
|
|
82
|
+
window's geometry and an `unread` flag; it persists to `localStorage` under `storageKey` and
|
|
83
|
+
keeps both inside the viewport. The launcher's place and the window's are separate.
|
|
84
|
+
- `ChatLauncher` is the floating button. The user can drag it anywhere; pass `position` and save
|
|
85
|
+
the drop from `onmove` (`surface.setLauncher`). While answering an arc orbits it; it also shows
|
|
86
|
+
an unseen answer, an error and a MOCK marker.
|
|
87
|
+
- `FloatingWindow` is a non-modal window around any content: one header (mark, `title`, optional
|
|
88
|
+
`subtitle` such as the conversation name, your `actions`, Dock, Minimize), drag by the header,
|
|
89
|
+
resize from the edges, drop on the right edge to dock, arrow keys on the move handle, Escape to
|
|
90
|
+
minimize. It takes the keyboard when it appears (the first text field, else the window;
|
|
91
|
+
`autofocus={false}` to opt out). The dock itself is the host's (e.g. a theme `DataPanel`).
|
|
92
|
+
For history, render `ConversationSidebar` in place of the chat (`class="w-full border-r-0"`);
|
|
93
|
+
with a host-owned session nothing is lost while it shows.
|
|
94
|
+
- Pass `AgentChat` a host-owned `session` (`new ChatSession({...})`) and `variant="float"` or
|
|
95
|
+
`"panel"`. The session then survives the view moving between presentations, including a
|
|
96
|
+
running answer and the unsent composer text (`session.draft`). With `session` set, configure `mode`, `agent`, `clientKind`, `getContext` and
|
|
97
|
+
the turn callbacks on the session; those `AgentChat` props are ignored. Call
|
|
98
|
+
`surface.notifyTurnEnd()` from the session's `onTurnEnd` to flag an answer that finished while
|
|
99
|
+
collapsed.
|
|
100
|
+
|
|
41
101
|
## Components
|
|
42
102
|
|
|
43
103
|
`AgentChat` composes the rest; each is also exported for other layouts:
|
|
44
104
|
|
|
45
105
|
- `ChatHeader`, `ConversationSidebar`: title and conversation actions; the history rail.
|
|
46
106
|
`ChatHeader`'s menu opens `ShortcutsDialog` (the chat's `CHAT_SHORTCUTS` plus the host's
|
|
47
|
-
`shortcuts`, e.g. a panel toggle). Hosts implement their own app-level shortcuts.
|
|
107
|
+
`shortcuts`, e.g. a panel toggle). Hosts implement their own app-level shortcuts. Pass
|
|
108
|
+
`savedPrompts={false}` to `ChatHeader` when the chat has no `prompts`, so the `/` entry is
|
|
109
|
+
left out.
|
|
48
110
|
- `MessageList`: transcript with day separators and a jump-to-latest button.
|
|
49
111
|
- `MessageItem` → `UserMessage` / `AssistantMessage`, with `MessageActions` (copy, time,
|
|
50
112
|
`MessageFeedback` thumbs up/down with an optional note, Regenerate and Edit on the latest turn),
|
|
@@ -63,4 +125,7 @@ agent-api origin that serves `/frame`; artifacts and MCP-UI resources only ever
|
|
|
63
125
|
an error with Try again when that load fails.
|
|
64
126
|
- `ArtifactPanel`, `UiFrame`, `ModeBanner`.
|
|
65
127
|
|
|
128
|
+
While an answer has nothing to show yet, the assistant message cycles through
|
|
129
|
+
`THINKING_PHRASES` (homebuilding verbs, as in the Classic Chat widget) beside a stepped loader.
|
|
130
|
+
|
|
66
131
|
Publishing: `docs/operations/package-publishing.md`.
|
|
@@ -32,6 +32,14 @@
|
|
|
32
32
|
|
|
33
33
|
interface Props {
|
|
34
34
|
client: AgentApiClient;
|
|
35
|
+
/**
|
|
36
|
+
* A session the host owns, so it survives this component being unmounted and mounted
|
|
37
|
+
* elsewhere (launcher, floating window, dock) without dropping a running answer. The host
|
|
38
|
+
* then configures it itself: `mode`, `agent`, `clientKind`, `getContext`,
|
|
39
|
+
* `onconversationstarted`, `ontoolresult` and `onturnend` here are ignored. Without it the
|
|
40
|
+
* component creates its own, as before.
|
|
41
|
+
*/
|
|
42
|
+
session?: ChatSession;
|
|
35
43
|
/** Sandbox origin for artifacts and MCP-UI (agent-api `/frame`). */
|
|
36
44
|
sandboxOrigin: string;
|
|
37
45
|
conversationId?: string | null;
|
|
@@ -47,7 +55,8 @@
|
|
|
47
55
|
/** The agent of this conversation changed (picked, or learned from a loaded conversation). */
|
|
48
56
|
onagentchange?: (agent: AgentKey) => void;
|
|
49
57
|
clientKind?: ClientKind;
|
|
50
|
-
|
|
58
|
+
/** `float` is the detached window: laid out like the panel. */
|
|
59
|
+
variant?: 'page' | 'panel' | 'float';
|
|
51
60
|
getContext?: () => DocumentContext | null | undefined;
|
|
52
61
|
onconversationstarted?: (conversationId: string) => void;
|
|
53
62
|
ontoolresult?: (name: string, status: string) => void;
|
|
@@ -83,6 +92,7 @@
|
|
|
83
92
|
}
|
|
84
93
|
let {
|
|
85
94
|
client,
|
|
95
|
+
session: hostSession,
|
|
86
96
|
sandboxOrigin,
|
|
87
97
|
conversationId = null,
|
|
88
98
|
mode,
|
|
@@ -111,29 +121,54 @@
|
|
|
111
121
|
class: className,
|
|
112
122
|
}: Props = $props();
|
|
113
123
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
124
|
+
// One session for the component's lifetime: the host's, or our own.
|
|
125
|
+
// svelte-ignore state_referenced_locally
|
|
126
|
+
const session =
|
|
127
|
+
hostSession ??
|
|
128
|
+
new ChatSession({
|
|
129
|
+
client,
|
|
130
|
+
conversationId,
|
|
131
|
+
mode,
|
|
132
|
+
agent,
|
|
133
|
+
clientKind,
|
|
134
|
+
getContext,
|
|
135
|
+
onConversationStarted: (id) => onconversationstarted?.(id),
|
|
136
|
+
onToolResult: (name, status) => ontoolresult?.(name, status),
|
|
137
|
+
onTurnEnd: (info) => onturnend?.(info),
|
|
138
|
+
});
|
|
139
|
+
// Open the pane when the runtime starts an artifact, not by watching state later: a fast
|
|
140
|
+
// stream can open and close it before any effect sees `open`. Only beside the chat: a sheet
|
|
141
|
+
// (narrow screens, the panel, the floating window) would cover the answer mid-stream, so the
|
|
142
|
+
// chip opens it there.
|
|
143
|
+
$effect(() =>
|
|
144
|
+
session.subscribe((event) => {
|
|
145
|
+
if (event.type === 'artifact' && event.op === 'open' && sidePane) {
|
|
128
146
|
selectedArtifact = event.artifactId;
|
|
129
147
|
artifactsOpen = true;
|
|
130
148
|
}
|
|
131
|
-
},
|
|
132
|
-
|
|
149
|
+
}),
|
|
150
|
+
);
|
|
151
|
+
const compact = $derived(variant !== 'page');
|
|
133
152
|
let attemptedId: string | null = null;
|
|
153
|
+
/** The prop as last seen, to tell the host's "New chat" (id → null) from a chat started here. */
|
|
154
|
+
let previousId: string | null = null;
|
|
134
155
|
// The constructor already knows `conversationId`; what decides a fetch is whether the
|
|
135
|
-
// transcript for it has been loaded (or started) in this session.
|
|
156
|
+
// transcript for it has been loaded (or started) in this session. `load` stops a stream of
|
|
157
|
+
// the conversation shown before.
|
|
136
158
|
$effect(() => {
|
|
159
|
+
const before = previousId;
|
|
160
|
+
previousId = conversationId;
|
|
161
|
+
if (!conversationId) {
|
|
162
|
+
// The host left a conversation for a new chat: forget it, or the next message would go
|
|
163
|
+
// to it. A chat started here while the prop stayed null keeps going (null → null).
|
|
164
|
+
if (before && session.conversationId) {
|
|
165
|
+
session.reset();
|
|
166
|
+
attemptedId = null;
|
|
167
|
+
selectedArtifact = null;
|
|
168
|
+
artifactsOpen = false;
|
|
169
|
+
}
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
137
172
|
if (
|
|
138
173
|
conversationId &&
|
|
139
174
|
conversationId !== session.loadedConversationId &&
|
|
@@ -146,11 +181,10 @@
|
|
|
146
181
|
}
|
|
147
182
|
});
|
|
148
183
|
$effect(() => {
|
|
149
|
-
if (mode && !session.mode) session.mode = mode;
|
|
184
|
+
if (mode && !hostSession && !session.mode) session.mode = mode;
|
|
150
185
|
});
|
|
151
186
|
|
|
152
187
|
let composer: Composer | undefined = $state();
|
|
153
|
-
let draft = $state('');
|
|
154
188
|
let files = $state<FileRef[]>([]);
|
|
155
189
|
let selectedArtifact = $state<string | null>(null);
|
|
156
190
|
let artifactsOpen = $state(false);
|
|
@@ -174,7 +208,7 @@
|
|
|
174
208
|
}
|
|
175
209
|
function retry(): void {
|
|
176
210
|
const last = session.messages[session.messages.length - 1];
|
|
177
|
-
if (last?.role === 'assistant')
|
|
211
|
+
if (last?.role === 'assistant') thenFocus(session.retry(last.requestId));
|
|
178
212
|
}
|
|
179
213
|
const agentName = $derived(agents?.find((a) => a.key === session.agent)?.name);
|
|
180
214
|
const pickAgent = $derived(
|
|
@@ -213,7 +247,7 @@
|
|
|
213
247
|
if (conversationId) void session.load(conversationId);
|
|
214
248
|
}
|
|
215
249
|
function prompt(text: string): void {
|
|
216
|
-
draft = text;
|
|
250
|
+
session.draft = text;
|
|
217
251
|
composer?.focus();
|
|
218
252
|
}
|
|
219
253
|
</script>
|
|
@@ -259,7 +293,7 @@
|
|
|
259
293
|
title={welcomeTitle}
|
|
260
294
|
description={welcomeDescription}
|
|
261
295
|
{suggestions}
|
|
262
|
-
compact
|
|
296
|
+
{compact}
|
|
263
297
|
disabled={session.status !== 'idle'}
|
|
264
298
|
onselect={(text) => thenFocus(send(text))}
|
|
265
299
|
/>
|
|
@@ -292,6 +326,7 @@
|
|
|
292
326
|
streaming={session.streaming}
|
|
293
327
|
{sandboxOrigin}
|
|
294
328
|
fileUrl={(id) => client.fileUrl(id)}
|
|
329
|
+
loadFile={client.fetchFile ? (id) => client.fetchFile!(id) : undefined}
|
|
295
330
|
{toolLabels}
|
|
296
331
|
{onnavigate}
|
|
297
332
|
onopenartifact={(id) => {
|
|
@@ -312,13 +347,13 @@
|
|
|
312
347
|
: undefined}
|
|
313
348
|
oncontinue={() => {
|
|
314
349
|
const answer = session.current;
|
|
315
|
-
if (answer)
|
|
350
|
+
if (answer) thenFocus(session.continue(answer.requestId));
|
|
316
351
|
}}
|
|
317
352
|
empty={emptyState}
|
|
318
353
|
/>
|
|
319
354
|
<Composer
|
|
320
355
|
bind:this={composer}
|
|
321
|
-
bind:value={draft}
|
|
356
|
+
bind:value={session.draft}
|
|
322
357
|
bind:files
|
|
323
358
|
disabled={session.status === 'loading'}
|
|
324
359
|
streaming={session.streaming}
|
|
@@ -2,9 +2,18 @@ import type { AgentApiClient } from '@classic-homes/agent-client';
|
|
|
2
2
|
import type { AgentKey, AgentSummary, ClientKind, DocumentContext } from '@classic-homes/agent-contracts';
|
|
3
3
|
import type { BackendMode } from '@classic-homes/agent-contracts';
|
|
4
4
|
import type { ComposerPrompt } from '../composer-prompts.js';
|
|
5
|
+
import { ChatSession } from '../session.svelte.js';
|
|
5
6
|
import type { WelcomeSuggestion } from '../welcome.js';
|
|
6
7
|
interface Props {
|
|
7
8
|
client: AgentApiClient;
|
|
9
|
+
/**
|
|
10
|
+
* A session the host owns, so it survives this component being unmounted and mounted
|
|
11
|
+
* elsewhere (launcher, floating window, dock) without dropping a running answer. The host
|
|
12
|
+
* then configures it itself: `mode`, `agent`, `clientKind`, `getContext`,
|
|
13
|
+
* `onconversationstarted`, `ontoolresult` and `onturnend` here are ignored. Without it the
|
|
14
|
+
* component creates its own, as before.
|
|
15
|
+
*/
|
|
16
|
+
session?: ChatSession;
|
|
8
17
|
/** Sandbox origin for artifacts and MCP-UI (agent-api `/frame`). */
|
|
9
18
|
sandboxOrigin: string;
|
|
10
19
|
conversationId?: string | null;
|
|
@@ -20,7 +29,8 @@ interface Props {
|
|
|
20
29
|
/** The agent of this conversation changed (picked, or learned from a loaded conversation). */
|
|
21
30
|
onagentchange?: (agent: AgentKey) => void;
|
|
22
31
|
clientKind?: ClientKind;
|
|
23
|
-
|
|
32
|
+
/** `float` is the detached window: laid out like the panel. */
|
|
33
|
+
variant?: 'page' | 'panel' | 'float';
|
|
24
34
|
getContext?: () => DocumentContext | null | undefined;
|
|
25
35
|
onconversationstarted?: (conversationId: string) => void;
|
|
26
36
|
ontoolresult?: (name: string, status: string) => void;
|
|
@@ -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,YAAY,EACZ,UAAU,EACV,eAAe,EAEhB,MAAM,gCAAgC,CAAC;AAC1C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gCAAgC,CAAC;AAclE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"AgentChat.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/AgentChat.svelte.ts"],"names":[],"mappings":"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"}
|
|
@@ -115,8 +115,10 @@
|
|
|
115
115
|
<div class="min-h-0 flex-1 overflow-auto p-3">
|
|
116
116
|
{#if selected.mediaType === 'text/markdown'}
|
|
117
117
|
<Markdown html={markdownHtml} />
|
|
118
|
-
{:else if selected.mediaType === 'application/vnd.ant.code' ||
|
|
119
|
-
<!--
|
|
118
|
+
{:else if selected.mediaType === 'application/vnd.ant.code' || selected.open}
|
|
119
|
+
<!-- Framed artifacts (html, svg, react, mermaid) render once complete: the source shows
|
|
120
|
+
while it streams, so the sandbox is not reloaded on every delta (and a partial
|
|
121
|
+
diagram is a parse error). -->
|
|
120
122
|
<pre class="overflow-auto whitespace-pre font-mono text-[13px] leading-relaxed"><code
|
|
121
123
|
>{selected.text}</code
|
|
122
124
|
></pre>
|
|
@@ -7,8 +7,9 @@
|
|
|
7
7
|
import { Alert, Button, Icon } from '@classic-homes/theme-svelte';
|
|
8
8
|
import type { TurnRating } from '@classic-homes/agent-contracts';
|
|
9
9
|
import type { AssistantMessage } from '../reducer.js';
|
|
10
|
-
import { errorTitle } from '../reducer.js';
|
|
10
|
+
import { errorTitle, isContinuable } from '../reducer.js';
|
|
11
11
|
import { parseMarkdown } from '../markdown.js';
|
|
12
|
+
import { THINKING_PHRASE_MS, pickThinkingPhrase } from '../thinking.js';
|
|
12
13
|
import ArtifactChip from './ArtifactChip.svelte';
|
|
13
14
|
import Markdown from './Markdown.svelte';
|
|
14
15
|
import MessageActions from './MessageActions.svelte';
|
|
@@ -24,6 +25,8 @@
|
|
|
24
25
|
last?: boolean;
|
|
25
26
|
sandboxOrigin: string;
|
|
26
27
|
fileUrl: (fileId: string) => string;
|
|
28
|
+
/** Fetch a file with the host's credentials (blob URLs); without it files link to `fileUrl`. */
|
|
29
|
+
loadFile?: (fileId: string) => Promise<Blob>;
|
|
27
30
|
toolLabels?: Record<string, string>;
|
|
28
31
|
onnavigate?: (href: string) => void;
|
|
29
32
|
onopenartifact?: (artifactId: string) => void;
|
|
@@ -44,6 +47,7 @@
|
|
|
44
47
|
last = false,
|
|
45
48
|
sandboxOrigin,
|
|
46
49
|
fileUrl,
|
|
50
|
+
loadFile,
|
|
47
51
|
toolLabels,
|
|
48
52
|
onnavigate,
|
|
49
53
|
onopenartifact,
|
|
@@ -98,8 +102,16 @@
|
|
|
98
102
|
!message.tools.some((t) => !t.status) &&
|
|
99
103
|
!message.artifacts.some((a) => a.open),
|
|
100
104
|
);
|
|
101
|
-
//
|
|
102
|
-
|
|
105
|
+
// A homebuilding verb while there is nothing to show yet, changed every few seconds.
|
|
106
|
+
let phrase = $state(pickThinkingPhrase());
|
|
107
|
+
$effect(() => {
|
|
108
|
+
if (!thinking) return;
|
|
109
|
+
const timer = setInterval(() => (phrase = pickThinkingPhrase(phrase)), THINKING_PHRASE_MS);
|
|
110
|
+
return () => clearInterval(timer);
|
|
111
|
+
});
|
|
112
|
+
// Continue carries on the text so far of an answer cut short or stopped; a reply stopped before
|
|
113
|
+
// any text, or one that failed or lost its connection, is retried instead.
|
|
114
|
+
const canContinue = $derived(last && !!oncontinue && isContinuable(message));
|
|
103
115
|
</script>
|
|
104
116
|
|
|
105
117
|
<div class="group/message flex gap-3 px-4 py-2" data-role="assistant" data-status={message.status}>
|
|
@@ -120,17 +132,15 @@
|
|
|
120
132
|
{/if}
|
|
121
133
|
{#if html}<Markdown {html} streaming={streaming && !!message.text} onclick={handleClick} />{/if}
|
|
122
134
|
{#if thinking}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
</span>
|
|
133
|
-
Thinking
|
|
135
|
+
<!-- aria-live off: the phrase changes for the eye; announcing each one would be noise. -->
|
|
136
|
+
<div
|
|
137
|
+
class="flex items-center gap-2 text-sm italic text-muted-foreground"
|
|
138
|
+
role="status"
|
|
139
|
+
aria-live="off"
|
|
140
|
+
data-agent-thinking
|
|
141
|
+
>
|
|
142
|
+
<span>{phrase}</span>
|
|
143
|
+
<span class="thinking-loader" aria-hidden="true"></span>
|
|
134
144
|
</div>
|
|
135
145
|
{/if}
|
|
136
146
|
{#if message.artifacts.length}
|
|
@@ -143,7 +153,7 @@
|
|
|
143
153
|
{#each message.ui as resource (resource.callId + resource.uri)}
|
|
144
154
|
<UiFrame {resource} {sandboxOrigin} {onprompt} {ontool} {onnotify} />
|
|
145
155
|
{/each}
|
|
146
|
-
{#if message.files.length}<MessageFiles files={message.files} {fileUrl} />{/if}
|
|
156
|
+
{#if message.files.length}<MessageFiles files={message.files} {fileUrl} {loadFile} />{/if}
|
|
147
157
|
{#if message.proposals.length}
|
|
148
158
|
<div class="flex flex-col gap-1.5">
|
|
149
159
|
{#each message.proposals as operation (operation.operationId)}
|
|
@@ -158,7 +168,10 @@
|
|
|
158
168
|
<strong class="font-semibold">{errorTitle(message.error.code)}.</strong>
|
|
159
169
|
{message.error.message}
|
|
160
170
|
</p>
|
|
161
|
-
{#if
|
|
171
|
+
{#if canContinue}
|
|
172
|
+
<!-- A continuation failed: the text so far is kept and can still be continued. -->
|
|
173
|
+
<Button variant="outline" size="sm" onclick={oncontinue}>Continue</Button>
|
|
174
|
+
{:else if message.error.retryable && onretry}
|
|
162
175
|
<Button variant="outline" size="sm" onclick={onretry}>
|
|
163
176
|
<Icon name="refresh" size={14} /> Try again
|
|
164
177
|
</Button>
|
|
@@ -170,6 +183,8 @@
|
|
|
170
183
|
<Icon name="stop" size={14} /> Stopped
|
|
171
184
|
{#if canContinue}
|
|
172
185
|
<Button variant="link" size="sm" class="h-auto p-0" onclick={oncontinue}>Continue</Button>
|
|
186
|
+
{:else if last && !message.text && onretry}
|
|
187
|
+
<Button variant="link" size="sm" class="h-auto p-0" onclick={onretry}>Try again</Button>
|
|
173
188
|
{/if}
|
|
174
189
|
</div>
|
|
175
190
|
{:else if message.status === 'incomplete'}
|
|
@@ -195,3 +210,30 @@
|
|
|
195
210
|
{/if}
|
|
196
211
|
</div>
|
|
197
212
|
</div>
|
|
213
|
+
|
|
214
|
+
<style>
|
|
215
|
+
/* Six blocks with one lit, stepping along (the Classic Chat widget's loader). Only custom
|
|
216
|
+
properties and an animation here: nothing a host's base styles compete with. */
|
|
217
|
+
.thinking-loader {
|
|
218
|
+
display: inline-block;
|
|
219
|
+
width: 60px;
|
|
220
|
+
height: 10px;
|
|
221
|
+
flex-shrink: 0;
|
|
222
|
+
-webkit-mask: linear-gradient(90deg, #000 70%, #0000 0) left / 20% 100%;
|
|
223
|
+
mask: linear-gradient(90deg, #000 70%, #0000 0) left / 20% 100%;
|
|
224
|
+
background: linear-gradient(hsl(var(--primary)) 0 0) left -25% top 0 / 20% 100% no-repeat
|
|
225
|
+
hsl(var(--muted-foreground) / 0.25);
|
|
226
|
+
animation: thinking-slide 1s infinite steps(6);
|
|
227
|
+
}
|
|
228
|
+
@keyframes thinking-slide {
|
|
229
|
+
100% {
|
|
230
|
+
background-position: right -25% top 0;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
@media (prefers-reduced-motion: reduce) {
|
|
234
|
+
.thinking-loader {
|
|
235
|
+
animation: none;
|
|
236
|
+
background-position: left 37.5% top 0;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
</style>
|
|
@@ -7,6 +7,8 @@ interface Props {
|
|
|
7
7
|
last?: boolean;
|
|
8
8
|
sandboxOrigin: string;
|
|
9
9
|
fileUrl: (fileId: string) => string;
|
|
10
|
+
/** Fetch a file with the host's credentials (blob URLs); without it files link to `fileUrl`. */
|
|
11
|
+
loadFile?: (fileId: string) => Promise<Blob>;
|
|
10
12
|
toolLabels?: Record<string, string>;
|
|
11
13
|
onnavigate?: (href: string) => void;
|
|
12
14
|
onopenartifact?: (artifactId: string) => void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AssistantMessage.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/AssistantMessage.svelte.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gCAAgC,CAAC;AACjE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"AssistantMessage.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/AssistantMessage.svelte.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gCAAgC,CAAC;AACjE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAapD,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,gGAAgG;IAChG,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7C,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,8EAA8E;IAC9E,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;IAC1B,iDAAiD;IACjD,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI,EAAE,OAAO,CAAC,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/E,8DAA8D;IAC9D,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;CACzB;AA0KH;;;GAGG;AACH,QAAA,MAAM,gBAAgB,2CAAwC,CAAC;AAC/D,KAAK,gBAAgB,GAAG,UAAU,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAC5D,eAAe,gBAAgB,CAAC"}
|
|
@@ -34,6 +34,8 @@
|
|
|
34
34
|
onerror?: (message: string) => void;
|
|
35
35
|
/** Host shortcuts for the "Keyboard shortcuts" dialog, e.g. opening the panel. */
|
|
36
36
|
shortcuts?: ChatShortcut[];
|
|
37
|
+
/** Whether the chat offers saved prompts (`AgentChat`'s `prompts`); false hides the `/` shortcut. */
|
|
38
|
+
savedPrompts?: boolean;
|
|
37
39
|
/** Extra controls before the menu. */
|
|
38
40
|
actions?: Snippet;
|
|
39
41
|
class?: string;
|
|
@@ -49,6 +51,7 @@
|
|
|
49
51
|
ondelete,
|
|
50
52
|
onerror,
|
|
51
53
|
shortcuts,
|
|
54
|
+
savedPrompts = true,
|
|
52
55
|
actions,
|
|
53
56
|
class: className,
|
|
54
57
|
}: Props = $props();
|
|
@@ -131,4 +134,4 @@
|
|
|
131
134
|
</header>
|
|
132
135
|
|
|
133
136
|
<ConversationDialogs bind:renaming bind:deleting {onrename} {ondelete} {onerror} {returnFocus} />
|
|
134
|
-
<ShortcutsDialog bind:open={showingShortcuts} {shortcuts} {returnFocus} />
|
|
137
|
+
<ShortcutsDialog bind:open={showingShortcuts} {shortcuts} {returnFocus} {savedPrompts} />
|
|
@@ -15,6 +15,8 @@ interface Props {
|
|
|
15
15
|
onerror?: (message: string) => void;
|
|
16
16
|
/** Host shortcuts for the "Keyboard shortcuts" dialog, e.g. opening the panel. */
|
|
17
17
|
shortcuts?: ChatShortcut[];
|
|
18
|
+
/** Whether the chat offers saved prompts (`AgentChat`'s `prompts`); false hides the `/` shortcut. */
|
|
19
|
+
savedPrompts?: boolean;
|
|
18
20
|
/** Extra controls before the menu. */
|
|
19
21
|
actions?: Snippet;
|
|
20
22
|
class?: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ChatHeader.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/ChatHeader.svelte.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAKlD,UAAU,KAAK;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,yDAAyD;IACzD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,qEAAqE;IACrE,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;IACxB,KAAK,CAAC,EAAE,MAAM,IAAI,CAAC;IACnB,8DAA8D;IAC9D,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB,QAAQ,CAAC,EAAE,CAAC,cAAc,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3E,QAAQ,CAAC,EAAE,CAAC,cAAc,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5D,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,kFAAkF;IAClF,SAAS,CAAC,EAAE,YAAY,EAAE,CAAC;IAC3B,sCAAsC;IACtC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;
|
|
1
|
+
{"version":3,"file":"ChatHeader.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/ChatHeader.svelte.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAKlD,UAAU,KAAK;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,yDAAyD;IACzD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,qEAAqE;IACrE,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;IACxB,KAAK,CAAC,EAAE,MAAM,IAAI,CAAC;IACnB,8DAA8D;IAC9D,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB,QAAQ,CAAC,EAAE,CAAC,cAAc,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3E,QAAQ,CAAC,EAAE,CAAC,cAAc,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5D,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,kFAAkF;IAClF,SAAS,CAAC,EAAE,YAAY,EAAE,CAAC;IAC3B,qGAAqG;IACrG,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,sCAAsC;IACtC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAwFH;;;;;GAKG;AACH,QAAA,MAAM,UAAU,2CAAwC,CAAC;AACzD,KAAK,UAAU,GAAG,UAAU,CAAC,OAAO,UAAU,CAAC,CAAC;AAChD,eAAe,UAAU,CAAC"}
|