@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/dist/session.svelte.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { ApiRequestError } from '@classic-homes/agent-client';
|
|
2
|
-
import { applyEvent, newAssistantMessage, newUserMessage, latestTurn, turnForRetry, } from './reducer.js';
|
|
2
|
+
import { applyEvent, isContinuable, newAssistantMessage, newUserMessage, latestTurn, turnForRetry, } from './reducer.js';
|
|
3
|
+
/** Re-attach attempts after a stream ends without `done` (a dropped connection). */
|
|
4
|
+
const RESUME_ATTEMPTS = 2;
|
|
5
|
+
const DEFAULT_CANCEL_GRACE_MS = 3000;
|
|
3
6
|
/**
|
|
4
7
|
* Reactive conversation state (Svelte 5 runes class). Owns the stream lifecycle: one turn at a
|
|
5
8
|
* time, cancel, resume after reload, history load. Rendering is left to the components.
|
|
@@ -15,16 +18,35 @@ export class ChatSession {
|
|
|
15
18
|
agent = $state('classic-agent');
|
|
16
19
|
loadError = $state(undefined);
|
|
17
20
|
pendingFiles = $state([]);
|
|
21
|
+
/** The unsent composer text, kept here so it survives the view being remounted. */
|
|
22
|
+
draft = $state('');
|
|
18
23
|
controller;
|
|
19
24
|
/** The request being streamed; differs from the shown answer's id while continuing it. */
|
|
20
25
|
streamRequestId;
|
|
26
|
+
/** Set once Stop was pressed for the running stream (the server cancel is on its way). */
|
|
27
|
+
cancelling = false;
|
|
28
|
+
cancelTimer;
|
|
29
|
+
/** Bumped by each load and reset, so a superseded load drops its result. */
|
|
30
|
+
loadToken = 0;
|
|
21
31
|
options;
|
|
32
|
+
listeners = new Set();
|
|
22
33
|
constructor(options) {
|
|
23
34
|
this.options = options;
|
|
24
35
|
this.conversationId = options.conversationId ?? null;
|
|
25
36
|
this.mode = options.mode;
|
|
26
37
|
this.agent = options.agent ?? 'classic-agent';
|
|
27
38
|
}
|
|
39
|
+
/**
|
|
40
|
+
* Observe this session's stream events for as long as a view needs them. A session can
|
|
41
|
+
* outlive the component showing it (a host moving the chat between surfaces), so views
|
|
42
|
+
* subscribe instead of owning the `onEvent` option. Returns the unsubscribe.
|
|
43
|
+
*/
|
|
44
|
+
subscribe(listener) {
|
|
45
|
+
this.listeners.add(listener);
|
|
46
|
+
return () => {
|
|
47
|
+
this.listeners.delete(listener);
|
|
48
|
+
};
|
|
49
|
+
}
|
|
28
50
|
get streaming() {
|
|
29
51
|
return this.status === 'streaming';
|
|
30
52
|
}
|
|
@@ -32,12 +54,43 @@ export class ChatSession {
|
|
|
32
54
|
const last = this.messages[this.messages.length - 1];
|
|
33
55
|
return last?.role === 'assistant' ? last : undefined;
|
|
34
56
|
}
|
|
57
|
+
/**
|
|
58
|
+
* Forget the current conversation (the host started a new chat): stops reading a running
|
|
59
|
+
* stream without cancelling it on the server (the turn finishes there and stays in its
|
|
60
|
+
* conversation) and clears the transcript.
|
|
61
|
+
*/
|
|
62
|
+
reset() {
|
|
63
|
+
this.loadToken += 1;
|
|
64
|
+
this.detach();
|
|
65
|
+
this.status = 'idle';
|
|
66
|
+
this.messages = [];
|
|
67
|
+
this.conversationId = null;
|
|
68
|
+
this.loadedConversationId = null;
|
|
69
|
+
this.loadError = undefined;
|
|
70
|
+
this.pendingFiles = [];
|
|
71
|
+
}
|
|
72
|
+
/** Stop reading the running stream, if any; its `run` then leaves the session alone. */
|
|
73
|
+
detach() {
|
|
74
|
+
const controller = this.controller;
|
|
75
|
+
this.controller = undefined;
|
|
76
|
+
this.streamRequestId = undefined;
|
|
77
|
+
this.cancelling = false;
|
|
78
|
+
clearTimeout(this.cancelTimer);
|
|
79
|
+
this.cancelTimer = undefined;
|
|
80
|
+
controller?.abort();
|
|
81
|
+
}
|
|
35
82
|
/** Load an existing conversation's transcript and re-attach to a running turn if any. */
|
|
36
83
|
async load(conversationId) {
|
|
84
|
+
// Another conversation's stream must not keep writing into (or finishing) this one.
|
|
85
|
+
this.detach();
|
|
86
|
+
const token = ++this.loadToken;
|
|
37
87
|
this.status = 'loading';
|
|
38
88
|
this.loadError = undefined;
|
|
39
89
|
try {
|
|
40
90
|
const detail = await this.options.client.getConversation(conversationId);
|
|
91
|
+
// A newer load or a reset took over while this one waited.
|
|
92
|
+
if (token !== this.loadToken)
|
|
93
|
+
return;
|
|
41
94
|
this.conversationId = conversationId;
|
|
42
95
|
this.loadedConversationId = conversationId;
|
|
43
96
|
this.messages = detail.messages.map((m) => m.role === 'user'
|
|
@@ -51,13 +104,9 @@ export class ChatSession {
|
|
|
51
104
|
: {
|
|
52
105
|
...newAssistantMessage(m.id.replace(/^a-/, ''), m.createdAt),
|
|
53
106
|
text: m.content.map((c) => c.text).join('\n'),
|
|
54
|
-
// An answer that ended early keeps its Continue after
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
: m.finish === 'cancelled'
|
|
58
|
-
? 'cancelled'
|
|
59
|
-
: 'done',
|
|
60
|
-
...(m.finish ? { finishReason: m.finish } : {}),
|
|
107
|
+
// An answer that ended early keeps its Continue (or, after an error, its Try
|
|
108
|
+
// again) after a reload. A finish this client does not know reads as done.
|
|
109
|
+
...loadedFinish(m.finish),
|
|
61
110
|
...(detail.feedback?.[m.id.replace(/^a-/, '')]
|
|
62
111
|
? { feedback: detail.feedback[m.id.replace(/^a-/, '')] }
|
|
63
112
|
: {}),
|
|
@@ -69,6 +118,8 @@ export class ChatSession {
|
|
|
69
118
|
await this.resume(active);
|
|
70
119
|
}
|
|
71
120
|
catch (err) {
|
|
121
|
+
if (token !== this.loadToken)
|
|
122
|
+
return;
|
|
72
123
|
this.status = 'idle';
|
|
73
124
|
this.loadError = err instanceof Error ? err.message : 'Could not load the conversation';
|
|
74
125
|
}
|
|
@@ -95,10 +146,15 @@ export class ChatSession {
|
|
|
95
146
|
...(this.options.clientKind ? { client: this.options.clientKind } : {}),
|
|
96
147
|
...(options.replaces ? { replaces: options.replaces } : {}),
|
|
97
148
|
};
|
|
149
|
+
const answer = newAssistantMessage(requestId);
|
|
150
|
+
// Until the runtime accepts the turn (`start`), remember which stored turn it replaces, so
|
|
151
|
+
// retrying a turn that never started still replaces the original (see `replacesFor`).
|
|
152
|
+
if (options.replaces)
|
|
153
|
+
answer.replaces = options.replaces;
|
|
98
154
|
this.messages = [
|
|
99
155
|
...this.messages,
|
|
100
156
|
newUserMessage(requestId, message, { attachments: files, context }),
|
|
101
|
-
|
|
157
|
+
answer,
|
|
102
158
|
];
|
|
103
159
|
await this.run(requestId, (signal, onEvent) => this.options.client.streamChat(request, { signal, onEvent }));
|
|
104
160
|
}
|
|
@@ -112,8 +168,17 @@ export class ChatSession {
|
|
|
112
168
|
const turn = turnForRetry(this.messages, requestId);
|
|
113
169
|
if (!turn)
|
|
114
170
|
return;
|
|
171
|
+
const replaces = this.replacesFor(requestId);
|
|
115
172
|
this.messages = turn.rest;
|
|
116
|
-
await this.send(turn.user.text, { files: turn.user.attachments, replaces
|
|
173
|
+
await this.send(turn.user.text, { files: turn.user.attachments, replaces });
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* The stored turn a resend of `requestId` replaces: the request itself once the runtime
|
|
177
|
+
* accepted it, else the turn that request was meant to replace (the runtime never stored it).
|
|
178
|
+
*/
|
|
179
|
+
replacesFor(requestId) {
|
|
180
|
+
const answer = this.messages.find((m) => m.role === 'assistant' && m.requestId === requestId);
|
|
181
|
+
return answer?.replaces ?? requestId;
|
|
117
182
|
}
|
|
118
183
|
/** Ask again for a new answer to the latest question, replacing the current one. */
|
|
119
184
|
async regenerate(requestId) {
|
|
@@ -122,8 +187,9 @@ export class ChatSession {
|
|
|
122
187
|
const turn = latestTurn(this.messages, requestId);
|
|
123
188
|
if (!turn)
|
|
124
189
|
return;
|
|
190
|
+
const replaces = this.replacesFor(requestId);
|
|
125
191
|
this.messages = turn.rest;
|
|
126
|
-
await this.send(turn.user.text, { files: turn.user.attachments, replaces
|
|
192
|
+
await this.send(turn.user.text, { files: turn.user.attachments, replaces });
|
|
127
193
|
}
|
|
128
194
|
/** Change the latest question and answer it again, replacing that turn. */
|
|
129
195
|
async edit(requestId, text) {
|
|
@@ -132,8 +198,9 @@ export class ChatSession {
|
|
|
132
198
|
const turn = latestTurn(this.messages, requestId);
|
|
133
199
|
if (!turn)
|
|
134
200
|
return;
|
|
201
|
+
const replaces = this.replacesFor(requestId);
|
|
135
202
|
this.messages = turn.rest;
|
|
136
|
-
await this.send(text, { files: turn.user.attachments, replaces
|
|
203
|
+
await this.send(text, { files: turn.user.attachments, replaces });
|
|
137
204
|
}
|
|
138
205
|
/**
|
|
139
206
|
* Carry on the latest answer where it was cut short or stopped. The runtime asks the agent to
|
|
@@ -145,10 +212,9 @@ export class ChatSession {
|
|
|
145
212
|
if (!conversationId || this.status !== 'idle')
|
|
146
213
|
return;
|
|
147
214
|
const last = this.current;
|
|
148
|
-
if (last?.requestId !== requestId ||
|
|
149
|
-
(last.status !== 'incomplete' && last.status !== 'cancelled') ||
|
|
150
|
-
!last.text)
|
|
215
|
+
if (last?.requestId !== requestId || !isContinuable(last))
|
|
151
216
|
return;
|
|
217
|
+
const previous = { status: last.status, finishReason: last.finishReason };
|
|
152
218
|
const request = {
|
|
153
219
|
requestId: crypto.randomUUID(),
|
|
154
220
|
conversationId,
|
|
@@ -163,7 +229,7 @@ export class ChatSession {
|
|
|
163
229
|
delete next.error;
|
|
164
230
|
return next;
|
|
165
231
|
});
|
|
166
|
-
await this.run(requestId, (signal, onEvent) => this.options.client.streamChat(request, { signal, onEvent }), request.requestId);
|
|
232
|
+
await this.run(requestId, (signal, onEvent) => this.options.client.streamChat(request, { signal, onEvent }), request.requestId, previous);
|
|
167
233
|
}
|
|
168
234
|
/** Re-attach to a turn after reload: replays events after the last seq we hold (none here). */
|
|
169
235
|
async resume(requestId) {
|
|
@@ -207,20 +273,40 @@ export class ChatSession {
|
|
|
207
273
|
throw err;
|
|
208
274
|
}
|
|
209
275
|
}
|
|
276
|
+
/**
|
|
277
|
+
* Stop the running answer. The runtime is asked to cancel and the stream is read on until its
|
|
278
|
+
* `done`, so text produced before the cancel landed still shows (and a later Continue appends
|
|
279
|
+
* after it); after `cancelGraceMs` the stream is dropped locally. The answer shows as stopped
|
|
280
|
+
* at once.
|
|
281
|
+
*/
|
|
210
282
|
cancel() {
|
|
211
283
|
const current = this.current;
|
|
212
|
-
|
|
284
|
+
const controller = this.controller;
|
|
285
|
+
if (!controller || !current || this.cancelling)
|
|
213
286
|
return;
|
|
214
|
-
this.
|
|
215
|
-
|
|
287
|
+
this.cancelling = true;
|
|
288
|
+
const answerId = current.requestId;
|
|
289
|
+
this.update(answerId, (m) => m.status === 'streaming' ? { ...m, status: 'cancelled', finishReason: 'cancelled' } : m);
|
|
290
|
+
void this.options.client.cancel(this.streamRequestId ?? answerId);
|
|
291
|
+
this.cancelTimer = setTimeout(() => controller.abort(), this.options.cancelGraceMs ?? DEFAULT_CANCEL_GRACE_MS);
|
|
216
292
|
}
|
|
217
293
|
async run(requestId, start,
|
|
218
294
|
/** The request streamed, when it is not `requestId` (a continuation of that answer). */
|
|
219
|
-
streamRequestId = requestId
|
|
295
|
+
streamRequestId = requestId,
|
|
296
|
+
/** A continuation: the answer's state before it, restored when the continuation fails. */
|
|
297
|
+
previous) {
|
|
298
|
+
this.detach();
|
|
220
299
|
this.status = 'streaming';
|
|
221
|
-
|
|
300
|
+
const controller = new AbortController();
|
|
301
|
+
this.controller = controller;
|
|
222
302
|
this.streamRequestId = streamRequestId;
|
|
303
|
+
// A reset, a load or a newer turn replaced this stream: leave the session alone.
|
|
304
|
+
const stale = () => this.controller !== controller;
|
|
305
|
+
let lastSeq = -1;
|
|
223
306
|
const onEvent = (event) => {
|
|
307
|
+
if (stale())
|
|
308
|
+
return;
|
|
309
|
+
lastSeq = event.seq;
|
|
224
310
|
if (event.type === 'start') {
|
|
225
311
|
this.mode = event.mode;
|
|
226
312
|
if (this.conversationId !== event.conversationId) {
|
|
@@ -233,15 +319,48 @@ export class ChatSession {
|
|
|
233
319
|
this.options.onToolResult?.(this.toolName(requestId, event.callId), event.status);
|
|
234
320
|
this.update(requestId, (m) => applyEvent(m, event));
|
|
235
321
|
this.options.onEvent?.(event);
|
|
322
|
+
for (const listener of [...this.listeners])
|
|
323
|
+
listener(event);
|
|
236
324
|
};
|
|
237
325
|
try {
|
|
238
|
-
|
|
326
|
+
let outcome = await start(controller.signal, onEvent);
|
|
327
|
+
// The connection dropped before `done`: the turn may still be running (or have just
|
|
328
|
+
// finished) on the server, so re-attach after the last event seen. Continue is not the
|
|
329
|
+
// answer here; the runtime refuses it while the turn runs.
|
|
330
|
+
for (let attempt = 0; outcome.kind === 'ended' && attempt < RESUME_ATTEMPTS && !stale() && !this.cancelling; attempt += 1) {
|
|
331
|
+
const conversationId = this.conversationId;
|
|
332
|
+
if (!conversationId)
|
|
333
|
+
break;
|
|
334
|
+
try {
|
|
335
|
+
outcome = await this.options.client.resumeTurn(conversationId, streamRequestId, lastSeq, {
|
|
336
|
+
signal: controller.signal,
|
|
337
|
+
onEvent,
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
catch {
|
|
341
|
+
// Counts as an attempt; the outcome stays `ended`.
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
if (stale())
|
|
345
|
+
return;
|
|
239
346
|
if (outcome.kind === 'aborted')
|
|
240
347
|
this.update(requestId, (m) => m.status === 'streaming' ? { ...m, status: 'cancelled', finishReason: 'cancelled' } : m);
|
|
241
348
|
else if (outcome.kind === 'ended')
|
|
242
|
-
this.update(requestId, (m) => m.status === 'streaming'
|
|
349
|
+
this.update(requestId, (m) => m.status === 'streaming'
|
|
350
|
+
? {
|
|
351
|
+
...m,
|
|
352
|
+
status: 'error',
|
|
353
|
+
error: {
|
|
354
|
+
code: 'network',
|
|
355
|
+
message: 'The connection was lost before the answer finished.',
|
|
356
|
+
retryable: true,
|
|
357
|
+
},
|
|
358
|
+
}
|
|
359
|
+
: m);
|
|
243
360
|
}
|
|
244
361
|
catch (err) {
|
|
362
|
+
if (stale())
|
|
363
|
+
return;
|
|
245
364
|
const code = err instanceof ApiRequestError ? err.code : 'network';
|
|
246
365
|
const text = err instanceof Error ? err.message : 'The request failed';
|
|
247
366
|
this.update(requestId, (m) => ({
|
|
@@ -255,6 +374,28 @@ export class ChatSession {
|
|
|
255
374
|
}));
|
|
256
375
|
}
|
|
257
376
|
finally {
|
|
377
|
+
if (!stale())
|
|
378
|
+
this.finish(requestId, previous);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
/** The turn's stream is over: restore a failed continuation, go idle, tell the host. */
|
|
382
|
+
finish(requestId, previous) {
|
|
383
|
+
// A failed continuation keeps the text so far continuable; the error shows inline.
|
|
384
|
+
if (previous)
|
|
385
|
+
this.update(requestId, (m) => {
|
|
386
|
+
if (m.status !== 'error')
|
|
387
|
+
return m;
|
|
388
|
+
const next = { ...m, status: previous.status };
|
|
389
|
+
if (previous.finishReason)
|
|
390
|
+
next.finishReason = previous.finishReason;
|
|
391
|
+
else
|
|
392
|
+
delete next.finishReason;
|
|
393
|
+
return next;
|
|
394
|
+
});
|
|
395
|
+
{
|
|
396
|
+
clearTimeout(this.cancelTimer);
|
|
397
|
+
this.cancelTimer = undefined;
|
|
398
|
+
this.cancelling = false;
|
|
258
399
|
this.controller = undefined;
|
|
259
400
|
this.streamRequestId = undefined;
|
|
260
401
|
this.status = 'idle';
|
|
@@ -274,6 +415,20 @@ export class ChatSession {
|
|
|
274
415
|
this.messages = this.messages.map((m) => m.role === 'assistant' && m.requestId === requestId ? fn(m) : m);
|
|
275
416
|
}
|
|
276
417
|
}
|
|
418
|
+
/** Status for a stored answer's `finish` (absent: it finished normally). */
|
|
419
|
+
function loadedFinish(finish) {
|
|
420
|
+
if (finish === 'length')
|
|
421
|
+
return { status: 'incomplete', finishReason: 'length' };
|
|
422
|
+
if (finish === 'cancelled')
|
|
423
|
+
return { status: 'cancelled', finishReason: 'cancelled' };
|
|
424
|
+
if (finish === 'error')
|
|
425
|
+
return {
|
|
426
|
+
status: 'error',
|
|
427
|
+
finishReason: 'error',
|
|
428
|
+
error: { code: 'upstream_error', message: 'The answer did not finish.', retryable: true },
|
|
429
|
+
};
|
|
430
|
+
return { status: 'done' };
|
|
431
|
+
}
|
|
277
432
|
export function createChatSession(options) {
|
|
278
433
|
return new ChatSession(options);
|
|
279
434
|
}
|
package/dist/shortcuts.d.ts
CHANGED
|
@@ -8,4 +8,11 @@ export interface ChatShortcut {
|
|
|
8
8
|
}
|
|
9
9
|
/** What the chat itself handles, in the composer. Hosts add their own (e.g. a panel toggle). */
|
|
10
10
|
export declare const CHAT_SHORTCUTS: readonly ChatShortcut[];
|
|
11
|
+
/**
|
|
12
|
+
* The chat's shortcuts for the dialog: without saved prompts (`AgentChat`'s `prompts`) the `/`
|
|
13
|
+
* menu is off, so its entry is left out.
|
|
14
|
+
*/
|
|
15
|
+
export declare function chatShortcuts(options?: {
|
|
16
|
+
savedPrompts?: boolean;
|
|
17
|
+
}): ChatShortcut[];
|
|
11
18
|
//# sourceMappingURL=shortcuts.d.ts.map
|
package/dist/shortcuts.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"shortcuts.d.ts","sourceRoot":"","sources":["../src/lib/shortcuts.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;CACf;AAED,gGAAgG;AAChG,eAAO,MAAM,cAAc,EAAE,SAAS,YAAY,EAMjD,CAAC"}
|
|
1
|
+
{"version":3,"file":"shortcuts.d.ts","sourceRoot":"","sources":["../src/lib/shortcuts.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;CACf;AAED,gGAAgG;AAChG,eAAO,MAAM,cAAc,EAAE,SAAS,YAAY,EAMjD,CAAC;AAEF;;;GAGG;AACH,wBAAgB,aAAa,CAAC,OAAO,GAAE;IAAE,YAAY,CAAC,EAAE,OAAO,CAAA;CAAO,GAAG,YAAY,EAAE,CAItF"}
|
package/dist/shortcuts.js
CHANGED
|
@@ -6,3 +6,12 @@ export const CHAT_SHORTCUTS = [
|
|
|
6
6
|
{ keys: ['/'], label: 'Insert a saved prompt' },
|
|
7
7
|
{ keys: ['Esc'], label: 'Cancel editing a message' },
|
|
8
8
|
];
|
|
9
|
+
/**
|
|
10
|
+
* The chat's shortcuts for the dialog: without saved prompts (`AgentChat`'s `prompts`) the `/`
|
|
11
|
+
* menu is off, so its entry is left out.
|
|
12
|
+
*/
|
|
13
|
+
export function chatShortcuts(options = {}) {
|
|
14
|
+
return options.savedPrompts === false
|
|
15
|
+
? CHAT_SHORTCUTS.filter((s) => s.label !== 'Insert a saved prompt')
|
|
16
|
+
: [...CHAT_SHORTCUTS];
|
|
17
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Geometry for the detached chat: a floating launcher button that snaps to a viewport corner and
|
|
3
|
+
* a floating window the user can move and resize. Pure functions in viewport (CSS px,
|
|
4
|
+
* `position: fixed`) coordinates so they can be tested without layout.
|
|
5
|
+
*
|
|
6
|
+
* The drag threshold and the separate launcher/window positions follow the customer chat widget
|
|
7
|
+
* (Classic-Homes/chat, `packages/widget`), rewritten for this package.
|
|
8
|
+
*/
|
|
9
|
+
export interface Point {
|
|
10
|
+
x: number;
|
|
11
|
+
y: number;
|
|
12
|
+
}
|
|
13
|
+
export interface Size {
|
|
14
|
+
width: number;
|
|
15
|
+
height: number;
|
|
16
|
+
}
|
|
17
|
+
export interface Rect extends Point, Size {
|
|
18
|
+
}
|
|
19
|
+
export type Corner = 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left';
|
|
20
|
+
export declare const CORNERS: readonly Corner[];
|
|
21
|
+
/** Compass edges a resize handle moves. */
|
|
22
|
+
export type ResizeDirection = 'n' | 's' | 'e' | 'w' | 'ne' | 'nw' | 'se' | 'sw';
|
|
23
|
+
export declare const RESIZE_DIRECTIONS: readonly ResizeDirection[];
|
|
24
|
+
export declare const FLOAT_LIMITS: {
|
|
25
|
+
readonly minWidth: 340;
|
|
26
|
+
readonly minHeight: 420;
|
|
27
|
+
readonly maxWidth: 900;
|
|
28
|
+
readonly maxHeight: 1000;
|
|
29
|
+
/** Gap kept between the window and the viewport edge. */
|
|
30
|
+
readonly margin: 12;
|
|
31
|
+
};
|
|
32
|
+
export declare const DEFAULT_FLOAT_SIZE: Size;
|
|
33
|
+
export declare const LAUNCHER_SIZE = 56;
|
|
34
|
+
export declare const LAUNCHER_MARGIN = 20;
|
|
35
|
+
/** Pointer travel (px) before a press on the launcher or the window header counts as a drag. */
|
|
36
|
+
export declare const DRAG_THRESHOLD = 3;
|
|
37
|
+
/** Releasing a dragged window with the pointer this close to the right edge docks it. */
|
|
38
|
+
export declare const DOCK_THRESHOLD = 24;
|
|
39
|
+
/** Arrow-key step for moving and resizing the window; Alt makes it fine. */
|
|
40
|
+
export declare const KEY_STEP = 24;
|
|
41
|
+
/** Size within the limits and never larger than the viewport allows. */
|
|
42
|
+
export declare function clampSize(size: Size, viewport: Size): Size;
|
|
43
|
+
/** The rect resized to the limits and moved fully inside the viewport. */
|
|
44
|
+
export declare function clampRect(rect: Rect, viewport: Size): Rect;
|
|
45
|
+
/** Where a new window opens: beside the launcher's corner, clear of the launcher itself. */
|
|
46
|
+
export declare function defaultRect(viewport: Size, corner: Corner, size?: Size): Rect;
|
|
47
|
+
/** Top-left point of the launcher button when it sits in `corner`. */
|
|
48
|
+
export declare function launcherPoint(corner: Corner, viewport: Size): Point;
|
|
49
|
+
/** Keep a freely placed launcher button fully inside the viewport. */
|
|
50
|
+
export declare function clampLauncher(point: Point, viewport: Size): Point;
|
|
51
|
+
/** The corner a dropped launcher snaps to. */
|
|
52
|
+
export declare function nearestCorner(point: Point, viewport: Size): Corner;
|
|
53
|
+
/**
|
|
54
|
+
* The rect after dragging a resize handle by `(dx, dy)`. The opposite edge stays put, also when
|
|
55
|
+
* a limit stops the dragged edge.
|
|
56
|
+
*/
|
|
57
|
+
export declare function resizeRect(rect: Rect, direction: ResizeDirection, dx: number, dy: number, viewport: Size): Rect;
|
|
58
|
+
/** Was a dragged window released against the right edge (the dock side)? */
|
|
59
|
+
export declare function isDockDrop(pointer: Point, viewport: Size): boolean;
|
|
60
|
+
//# sourceMappingURL=surface-geometry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"surface-geometry.d.ts","sourceRoot":"","sources":["../src/lib/surface-geometry.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,MAAM,WAAW,KAAK;IACpB,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;CACX;AACD,MAAM,WAAW,IAAI;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AACD,MAAM,WAAW,IAAK,SAAQ,KAAK,EAAE,IAAI;CAAG;AAE5C,MAAM,MAAM,MAAM,GAAG,cAAc,GAAG,aAAa,GAAG,WAAW,GAAG,UAAU,CAAC;AAC/E,eAAO,MAAM,OAAO,EAAE,SAAS,MAAM,EAA6D,CAAC;AAEnG,2CAA2C;AAC3C,MAAM,MAAM,eAAe,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AAChF,eAAO,MAAM,iBAAiB,EAAE,SAAS,eAAe,EASvD,CAAC;AAEF,eAAO,MAAM,YAAY;;;;;IAKvB,yDAAyD;;CAEjD,CAAC;AACX,eAAO,MAAM,kBAAkB,EAAE,IAAkC,CAAC;AACpE,eAAO,MAAM,aAAa,KAAK,CAAC;AAChC,eAAO,MAAM,eAAe,KAAK,CAAC;AAClC,gGAAgG;AAChG,eAAO,MAAM,cAAc,IAAI,CAAC;AAChC,yFAAyF;AACzF,eAAO,MAAM,cAAc,KAAK,CAAC;AACjC,4EAA4E;AAC5E,eAAO,MAAM,QAAQ,KAAK,CAAC;AAK3B,wEAAwE;AACxE,wBAAgB,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,GAAG,IAAI,CAS1D;AAED,0EAA0E;AAC1E,wBAAgB,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,GAAG,IAAI,CAQ1D;AAED,4FAA4F;AAC5F,wBAAgB,WAAW,CAAC,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,GAAE,IAAyB,GAAG,IAAI,CAmBjG;AAED,sEAAsE;AACtE,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,GAAG,KAAK,CAMnE;AAED,sEAAsE;AACtE,wBAAgB,aAAa,CAAC,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,IAAI,GAAG,KAAK,CAIjE;AAED,8CAA8C;AAC9C,wBAAgB,aAAa,CAAC,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,IAAI,GAAG,MAAM,CAIlE;AAED;;;GAGG;AACH,wBAAgB,UAAU,CACxB,IAAI,EAAE,IAAI,EACV,SAAS,EAAE,eAAe,EAC1B,EAAE,EAAE,MAAM,EACV,EAAE,EAAE,MAAM,EACV,QAAQ,EAAE,IAAI,GACb,IAAI,CAaN;AAED,4EAA4E;AAC5E,wBAAgB,UAAU,CAAC,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,IAAI,GAAG,OAAO,CAElE"}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
export const CORNERS = ['bottom-right', 'bottom-left', 'top-right', 'top-left'];
|
|
2
|
+
export const RESIZE_DIRECTIONS = [
|
|
3
|
+
'n',
|
|
4
|
+
's',
|
|
5
|
+
'e',
|
|
6
|
+
'w',
|
|
7
|
+
'ne',
|
|
8
|
+
'nw',
|
|
9
|
+
'se',
|
|
10
|
+
'sw',
|
|
11
|
+
];
|
|
12
|
+
export const FLOAT_LIMITS = {
|
|
13
|
+
minWidth: 340,
|
|
14
|
+
minHeight: 420,
|
|
15
|
+
maxWidth: 900,
|
|
16
|
+
maxHeight: 1000,
|
|
17
|
+
/** Gap kept between the window and the viewport edge. */
|
|
18
|
+
margin: 12,
|
|
19
|
+
};
|
|
20
|
+
export const DEFAULT_FLOAT_SIZE = { width: 420, height: 640 };
|
|
21
|
+
export const LAUNCHER_SIZE = 56;
|
|
22
|
+
export const LAUNCHER_MARGIN = 20;
|
|
23
|
+
/** Pointer travel (px) before a press on the launcher or the window header counts as a drag. */
|
|
24
|
+
export const DRAG_THRESHOLD = 3;
|
|
25
|
+
/** Releasing a dragged window with the pointer this close to the right edge docks it. */
|
|
26
|
+
export const DOCK_THRESHOLD = 24;
|
|
27
|
+
/** Arrow-key step for moving and resizing the window; Alt makes it fine. */
|
|
28
|
+
export const KEY_STEP = 24;
|
|
29
|
+
const between = (value, min, max) => Math.min(Math.max(value, min), Math.max(min, max));
|
|
30
|
+
/** Size within the limits and never larger than the viewport allows. */
|
|
31
|
+
export function clampSize(size, viewport) {
|
|
32
|
+
const { minWidth, minHeight, maxWidth, maxHeight, margin } = FLOAT_LIMITS;
|
|
33
|
+
const roomX = viewport.width - margin * 2;
|
|
34
|
+
const roomY = viewport.height - margin * 2;
|
|
35
|
+
return {
|
|
36
|
+
// A viewport smaller than the minimum wins: the window must stay fully visible.
|
|
37
|
+
width: Math.round(Math.min(between(size.width, minWidth, maxWidth), Math.max(roomX, 0))),
|
|
38
|
+
height: Math.round(Math.min(between(size.height, minHeight, maxHeight), Math.max(roomY, 0))),
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
/** The rect resized to the limits and moved fully inside the viewport. */
|
|
42
|
+
export function clampRect(rect, viewport) {
|
|
43
|
+
const size = clampSize(rect, viewport);
|
|
44
|
+
const { margin } = FLOAT_LIMITS;
|
|
45
|
+
return {
|
|
46
|
+
...size,
|
|
47
|
+
x: Math.round(between(rect.x, margin, viewport.width - size.width - margin)),
|
|
48
|
+
y: Math.round(between(rect.y, margin, viewport.height - size.height - margin)),
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
/** Where a new window opens: beside the launcher's corner, clear of the launcher itself. */
|
|
52
|
+
export function defaultRect(viewport, corner, size = DEFAULT_FLOAT_SIZE) {
|
|
53
|
+
const clear = LAUNCHER_MARGIN + LAUNCHER_SIZE + FLOAT_LIMITS.margin;
|
|
54
|
+
const fitted = clampSize({
|
|
55
|
+
width: size.width,
|
|
56
|
+
height: Math.min(size.height, viewport.height - clear - FLOAT_LIMITS.margin),
|
|
57
|
+
}, viewport);
|
|
58
|
+
return clampRect({
|
|
59
|
+
...fitted,
|
|
60
|
+
x: corner.endsWith('left')
|
|
61
|
+
? LAUNCHER_MARGIN
|
|
62
|
+
: viewport.width - fitted.width - LAUNCHER_MARGIN,
|
|
63
|
+
y: corner.startsWith('top') ? clear : viewport.height - fitted.height - clear,
|
|
64
|
+
}, viewport);
|
|
65
|
+
}
|
|
66
|
+
/** Top-left point of the launcher button when it sits in `corner`. */
|
|
67
|
+
export function launcherPoint(corner, viewport) {
|
|
68
|
+
const far = (extent) => Math.max(0, extent - LAUNCHER_SIZE - LAUNCHER_MARGIN);
|
|
69
|
+
return {
|
|
70
|
+
x: corner.endsWith('left') ? LAUNCHER_MARGIN : far(viewport.width),
|
|
71
|
+
y: corner.startsWith('top') ? LAUNCHER_MARGIN : far(viewport.height),
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
/** Keep a freely placed launcher button fully inside the viewport. */
|
|
75
|
+
export function clampLauncher(point, viewport) {
|
|
76
|
+
const within = (value, extent) => Math.min(Math.max(0, value), Math.max(0, extent - LAUNCHER_SIZE));
|
|
77
|
+
return { x: within(point.x, viewport.width), y: within(point.y, viewport.height) };
|
|
78
|
+
}
|
|
79
|
+
/** The corner a dropped launcher snaps to. */
|
|
80
|
+
export function nearestCorner(point, viewport) {
|
|
81
|
+
const vertical = point.y < viewport.height / 2 ? 'top' : 'bottom';
|
|
82
|
+
const horizontal = point.x < viewport.width / 2 ? 'left' : 'right';
|
|
83
|
+
return `${vertical}-${horizontal}`;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* The rect after dragging a resize handle by `(dx, dy)`. The opposite edge stays put, also when
|
|
87
|
+
* a limit stops the dragged edge.
|
|
88
|
+
*/
|
|
89
|
+
export function resizeRect(rect, direction, dx, dy, viewport) {
|
|
90
|
+
const { margin } = FLOAT_LIMITS;
|
|
91
|
+
let { x, y, width, height } = rect;
|
|
92
|
+
const right = x + width;
|
|
93
|
+
const bottom = y + height;
|
|
94
|
+
if (direction.includes('e'))
|
|
95
|
+
width = Math.min(width + dx, viewport.width - margin - x);
|
|
96
|
+
if (direction.includes('s'))
|
|
97
|
+
height = Math.min(height + dy, viewport.height - margin - y);
|
|
98
|
+
if (direction.includes('w'))
|
|
99
|
+
width = Math.min(width - dx, right - margin);
|
|
100
|
+
if (direction.includes('n'))
|
|
101
|
+
height = Math.min(height - dy, bottom - margin);
|
|
102
|
+
({ width, height } = clampSize({ width, height }, viewport));
|
|
103
|
+
if (direction.includes('w'))
|
|
104
|
+
x = right - width;
|
|
105
|
+
if (direction.includes('n'))
|
|
106
|
+
y = bottom - height;
|
|
107
|
+
return clampRect({ x, y, width, height }, viewport);
|
|
108
|
+
}
|
|
109
|
+
/** Was a dragged window released against the right edge (the dock side)? */
|
|
110
|
+
export function isDockDrop(pointer, viewport) {
|
|
111
|
+
return pointer.x >= viewport.width - DOCK_THRESHOLD;
|
|
112
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { type Corner, type Point, type Rect, type ResizeDirection, type Size } from './surface-geometry.js';
|
|
2
|
+
/**
|
|
3
|
+
* How the chat is presented over a host page: collapsed to the launcher button, a floating
|
|
4
|
+
* window, or docked to the side. The full page is a host route, not a mode here; a host on that
|
|
5
|
+
* route simply renders none of these.
|
|
6
|
+
*/
|
|
7
|
+
export type SurfaceMode = 'launcher' | 'float' | 'dock';
|
|
8
|
+
export type OpenSurfaceMode = Exclude<SurfaceMode, 'launcher'>;
|
|
9
|
+
/** What the collapsed launcher shows about the chat behind it. */
|
|
10
|
+
export type LauncherStatus = 'idle' | 'streaming' | 'unread' | 'error';
|
|
11
|
+
export interface ChatSurfaceOptions {
|
|
12
|
+
/** Where layout is remembered, e.g. `classic-agent:surface:<userId>`. Not persisted without it. */
|
|
13
|
+
storageKey?: string;
|
|
14
|
+
/** Defaults to `localStorage` when available. */
|
|
15
|
+
storage?: Pick<Storage, 'getItem' | 'setItem'>;
|
|
16
|
+
/** Defaults to the window's inner size. */
|
|
17
|
+
viewport?: () => Size;
|
|
18
|
+
/** What the launcher opens the first time. */
|
|
19
|
+
defaultOpen?: OpenSurfaceMode;
|
|
20
|
+
corner?: Corner;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Reactive state of the detached chat (Svelte 5 runes class): which presentation is showing,
|
|
24
|
+
* where the launcher sits, the floating window's geometry and whether an answer arrived unseen.
|
|
25
|
+
* Layout survives reloads through `storageKey`; a saved window that no longer fits the viewport
|
|
26
|
+
* is pulled back inside.
|
|
27
|
+
*/
|
|
28
|
+
export declare class ChatSurface {
|
|
29
|
+
mode: SurfaceMode;
|
|
30
|
+
/** The open presentation the launcher returns to. */
|
|
31
|
+
lastOpen: OpenSurfaceMode;
|
|
32
|
+
corner: Corner;
|
|
33
|
+
/**
|
|
34
|
+
* Where the user dropped the launcher (its top-left), or null while it sits in `corner`.
|
|
35
|
+
* Separate from the window's place: moving one never moves the other.
|
|
36
|
+
*/
|
|
37
|
+
launcher: Point | null;
|
|
38
|
+
/** An answer finished while the chat was collapsed. Cleared on open. */
|
|
39
|
+
unread: boolean;
|
|
40
|
+
/** True while the user drags or resizes the window. */
|
|
41
|
+
moving: boolean;
|
|
42
|
+
private saved;
|
|
43
|
+
private readonly options;
|
|
44
|
+
constructor(options?: ChatSurfaceOptions);
|
|
45
|
+
get isOpen(): boolean;
|
|
46
|
+
/** The floating window's rect; a default beside the launcher until the user moves it. */
|
|
47
|
+
get rect(): Rect;
|
|
48
|
+
open(mode?: OpenSurfaceMode): void;
|
|
49
|
+
minimize(): void;
|
|
50
|
+
toggle(): void;
|
|
51
|
+
dock(): void;
|
|
52
|
+
float(): void;
|
|
53
|
+
/** A turn ended; flag it when the user cannot see the answer. */
|
|
54
|
+
notifyTurnEnd(): void;
|
|
55
|
+
setCorner(corner: Corner): void;
|
|
56
|
+
/**
|
|
57
|
+
* The user dropped the launcher at `point` (top-left). It stays there; `corner` follows the
|
|
58
|
+
* nearest one, which is where a window that was never moved opens.
|
|
59
|
+
*/
|
|
60
|
+
setLauncher(point: Point): void;
|
|
61
|
+
/** Move the window by a pointer or key delta. Call `commit` when the gesture ends. */
|
|
62
|
+
moveBy(dx: number, dy: number): void;
|
|
63
|
+
resizeBy(direction: ResizeDirection, dx: number, dy: number): void;
|
|
64
|
+
/** End of a move or resize: remember the result. */
|
|
65
|
+
commit(): void;
|
|
66
|
+
/** Forget the user's geometry and return the window to its default place. */
|
|
67
|
+
resetRect(): void;
|
|
68
|
+
/**
|
|
69
|
+
* Keep the window inside the viewport as it changes size. Returns the cleanup; call from an
|
|
70
|
+
* effect while the floating window is mounted.
|
|
71
|
+
*/
|
|
72
|
+
attach(target?: Pick<Window, 'addEventListener' | 'removeEventListener'>): () => void;
|
|
73
|
+
private viewport;
|
|
74
|
+
private storage;
|
|
75
|
+
private restore;
|
|
76
|
+
private persist;
|
|
77
|
+
}
|
|
78
|
+
export declare function createChatSurface(options?: ChatSurfaceOptions): ChatSurface;
|
|
79
|
+
//# sourceMappingURL=surface.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"surface.svelte.d.ts","sourceRoot":"","sources":["../src/lib/surface.svelte.ts"],"names":[],"mappings":"AAAA,OAAO,EAQL,KAAK,MAAM,EACX,KAAK,KAAK,EACV,KAAK,IAAI,EACT,KAAK,eAAe,EACpB,KAAK,IAAI,EACV,MAAM,uBAAuB,CAAC;AAE/B;;;;GAIG;AACH,MAAM,MAAM,WAAW,GAAG,UAAU,GAAG,OAAO,GAAG,MAAM,CAAC;AACxD,MAAM,MAAM,eAAe,GAAG,OAAO,CAAC,WAAW,EAAE,UAAU,CAAC,CAAC;AAE/D,kEAAkE;AAClE,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,WAAW,GAAG,QAAQ,GAAG,OAAO,CAAC;AAEvE,MAAM,WAAW,kBAAkB;IACjC,mGAAmG;IACnG,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,iDAAiD;IACjD,OAAO,CAAC,EAAE,IAAI,CAAC,OAAO,EAAE,SAAS,GAAG,SAAS,CAAC,CAAC;IAC/C,2CAA2C;IAC3C,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB,8CAA8C;IAC9C,WAAW,CAAC,EAAE,eAAe,CAAC;IAC9B,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAuBD;;;;;GAKG;AACH,qBAAa,WAAW;IACtB,IAAI,cAAmC;IACvC,qDAAqD;IACrD,QAAQ,kBAAoC;IAC5C,MAAM,SAAkC;IACxC;;;OAGG;IACH,QAAQ,eAA8B;IACtC,wEAAwE;IACxE,MAAM,UAAiB;IACvB,uDAAuD;IACvD,MAAM,UAAiB;IACvB,OAAO,CAAC,KAAK,CAA6B;IAC1C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAqB;gBAEjC,OAAO,GAAE,kBAAuB;IAO5C,IAAI,MAAM,IAAI,OAAO,CAEpB;IAED,yFAAyF;IACzF,IAAI,IAAI,IAAI,IAAI,CAGf;IAED,IAAI,CAAC,IAAI,GAAE,eAA+B,GAAG,IAAI;IAMjD,QAAQ,IAAI,IAAI;IAIhB,MAAM,IAAI,IAAI;IAId,IAAI,IAAI,IAAI;IAGZ,KAAK,IAAI,IAAI;IAIb,iEAAiE;IACjE,aAAa,IAAI,IAAI;IAIrB,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAK/B;;;OAGG;IACH,WAAW,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI;IAU/B,sFAAsF;IACtF,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,IAAI;IAIpC,QAAQ,CAAC,SAAS,EAAE,eAAe,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,IAAI;IAGlE,oDAAoD;IACpD,MAAM,IAAI,IAAI;IAId,6EAA6E;IAC7E,SAAS,IAAI,IAAI;IAKjB;;;OAGG;IACH,MAAM,CAAC,MAAM,GAAE,IAAI,CAAC,MAAM,EAAE,kBAAkB,GAAG,qBAAqB,CAAU,GAAG,MAAM,IAAI;IAS7F,OAAO,CAAC,QAAQ;IAMhB,OAAO,CAAC,OAAO;IAUf,OAAO,CAAC,OAAO;IAkBf,OAAO,CAAC,OAAO;CAehB;AAED,wBAAgB,iBAAiB,CAAC,OAAO,GAAE,kBAAuB,GAAG,WAAW,CAE/E"}
|