@classic-homes/agent-chat 0.12.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 +17 -7
- package/dist/components/AgentChat.svelte +2 -3
- package/dist/components/AgentChat.svelte.d.ts.map +1 -1
- package/dist/components/AssistantMessage.svelte +44 -11
- package/dist/components/AssistantMessage.svelte.d.ts.map +1 -1
- package/dist/components/ChatLauncher.svelte +33 -15
- package/dist/components/ChatLauncher.svelte.d.ts +6 -3
- package/dist/components/ChatLauncher.svelte.d.ts.map +1 -1
- package/dist/components/FloatingWindow.svelte +46 -5
- package/dist/components/FloatingWindow.svelte.d.ts +9 -0
- package/dist/components/FloatingWindow.svelte.d.ts.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/session.svelte.d.ts +2 -0
- package/dist/session.svelte.d.ts.map +1 -1
- package/dist/session.svelte.js +2 -0
- package/dist/surface-geometry.d.ts +4 -0
- package/dist/surface-geometry.d.ts.map +1 -1
- package/dist/surface-geometry.js +13 -0
- package/dist/surface.svelte.d.ts +12 -2
- package/dist/surface.svelte.d.ts.map +1 -1
- package/dist/surface.svelte.js +24 -2
- package/dist/thinking.d.ts +10 -0
- package/dist/thinking.d.ts.map +1 -0
- package/dist/thinking.js +61 -0
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -78,15 +78,22 @@ loads produced files with `client.fetchFile` (blob URLs, revoked when the messag
|
|
|
78
78
|
A host can show the chat over its pages instead of on a route of its own:
|
|
79
79
|
|
|
80
80
|
- `createChatSurface({ storageKey })` holds which presentation is showing (`launcher`, `float`
|
|
81
|
-
or `dock`), the launcher
|
|
82
|
-
`localStorage` under `storageKey` and
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
the
|
|
86
|
-
|
|
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.
|
|
87
94
|
- Pass `AgentChat` a host-owned `session` (`new ChatSession({...})`) and `variant="float"` or
|
|
88
95
|
`"panel"`. The session then survives the view moving between presentations, including a
|
|
89
|
-
running answer. With `session` set, configure `mode`, `agent`, `clientKind`, `getContext` and
|
|
96
|
+
running answer and the unsent composer text (`session.draft`). With `session` set, configure `mode`, `agent`, `clientKind`, `getContext` and
|
|
90
97
|
the turn callbacks on the session; those `AgentChat` props are ignored. Call
|
|
91
98
|
`surface.notifyTurnEnd()` from the session's `onTurnEnd` to flag an answer that finished while
|
|
92
99
|
collapsed.
|
|
@@ -118,4 +125,7 @@ A host can show the chat over its pages instead of on a route of its own:
|
|
|
118
125
|
an error with Try again when that load fails.
|
|
119
126
|
- `ArtifactPanel`, `UiFrame`, `ModeBanner`.
|
|
120
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
|
+
|
|
121
131
|
Publishing: `docs/operations/package-publishing.md`.
|
|
@@ -185,7 +185,6 @@
|
|
|
185
185
|
});
|
|
186
186
|
|
|
187
187
|
let composer: Composer | undefined = $state();
|
|
188
|
-
let draft = $state('');
|
|
189
188
|
let files = $state<FileRef[]>([]);
|
|
190
189
|
let selectedArtifact = $state<string | null>(null);
|
|
191
190
|
let artifactsOpen = $state(false);
|
|
@@ -248,7 +247,7 @@
|
|
|
248
247
|
if (conversationId) void session.load(conversationId);
|
|
249
248
|
}
|
|
250
249
|
function prompt(text: string): void {
|
|
251
|
-
draft = text;
|
|
250
|
+
session.draft = text;
|
|
252
251
|
composer?.focus();
|
|
253
252
|
}
|
|
254
253
|
</script>
|
|
@@ -354,7 +353,7 @@
|
|
|
354
353
|
/>
|
|
355
354
|
<Composer
|
|
356
355
|
bind:this={composer}
|
|
357
|
-
bind:value={draft}
|
|
356
|
+
bind:value={session.draft}
|
|
358
357
|
bind:files
|
|
359
358
|
disabled={session.status === 'loading'}
|
|
360
359
|
streaming={session.streaming}
|
|
@@ -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;AAC7D,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACnD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAQrD,UAAU,KAAK;IACb,MAAM,EAAE,cAAc,CAAC;IACvB;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,WAAW,CAAC;IACtB,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,yEAAyE;IACzE,KAAK,CAAC,EAAE,QAAQ,CAAC;IACjB;;;OAGG;IACH,MAAM,CAAC,EAAE,YAAY,EAAE,CAAC;IACxB,8FAA8F;IAC9F,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,QAAQ,KAAK,IAAI,CAAC;IAC1C,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,+DAA+D;IAC/D,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,OAAO,CAAC;IACrC,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,6EAA6E;IAC7E,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,6EAA6E;IAC7E,WAAW,CAAC,EAAE,iBAAiB,EAAE,CAAC;IAClC,mDAAmD;IACnD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,8CAA8C;IAC9C,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,6FAA6F;IAC7F,MAAM,CAAC,EAAE,OAAO,QAAQ,EAAE,OAAO,CAAC,CAAC;QAAE,KAAK,EAAE,QAAQ,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC,CAAC;IAC7E,OAAO,CAAC,EAAE,OAAO,QAAQ,EAAE,OAAO,CAAC;IACnC,+DAA+D;IAC/D,KAAK,CAAC,EAAE,OAAO,QAAQ,EAAE,OAAO,CAAC;IACjC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;
|
|
1
|
+
{"version":3,"file":"AgentChat.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/AgentChat.svelte.ts"],"names":[],"mappings":"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"}
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
import type { AssistantMessage } from '../reducer.js';
|
|
10
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';
|
|
@@ -101,6 +102,13 @@
|
|
|
101
102
|
!message.tools.some((t) => !t.status) &&
|
|
102
103
|
!message.artifacts.some((a) => a.open),
|
|
103
104
|
);
|
|
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
|
+
});
|
|
104
112
|
// Continue carries on the text so far of an answer cut short or stopped; a reply stopped before
|
|
105
113
|
// any text, or one that failed or lost its connection, is retried instead.
|
|
106
114
|
const canContinue = $derived(last && !!oncontinue && isContinuable(message));
|
|
@@ -124,17 +132,15 @@
|
|
|
124
132
|
{/if}
|
|
125
133
|
{#if html}<Markdown {html} streaming={streaming && !!message.text} onclick={handleClick} />{/if}
|
|
126
134
|
{#if thinking}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
</span>
|
|
137
|
-
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>
|
|
138
144
|
</div>
|
|
139
145
|
{/if}
|
|
140
146
|
{#if message.artifacts.length}
|
|
@@ -204,3 +210,30 @@
|
|
|
204
210
|
{/if}
|
|
205
211
|
</div>
|
|
206
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>
|
|
@@ -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"}
|
|
@@ -1,16 +1,20 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
/**
|
|
3
|
-
* The floating launcher: a round button
|
|
4
|
-
* user can drag it
|
|
3
|
+
* The floating launcher: a round button that opens the chat. It starts in a viewport corner;
|
|
4
|
+
* the user can drag it anywhere and it stays where it is dropped (kept inside the viewport),
|
|
5
|
+
* independent of where the chat window sits. It also reports what the chat
|
|
5
6
|
* is doing while collapsed (answering, an unseen answer, an error) and keeps the mock backend
|
|
6
7
|
* visible (AGENTS.md: mock must always be visible).
|
|
7
8
|
*/
|
|
8
9
|
import { cn, Icon } from '@classic-homes/theme-svelte';
|
|
9
10
|
import type { LauncherStatus } from '../surface.svelte.js';
|
|
10
|
-
import { DRAG_THRESHOLD,
|
|
11
|
+
import { DRAG_THRESHOLD, clampLauncher, type Corner, type Point } from '../surface-geometry.js';
|
|
11
12
|
|
|
12
13
|
interface Props {
|
|
14
|
+
/** Where it sits until the user places it. */
|
|
13
15
|
corner?: Corner;
|
|
16
|
+
/** Where the user placed it (top-left, viewport px); null or absent uses `corner`. */
|
|
17
|
+
position?: Point | null;
|
|
14
18
|
status?: LauncherStatus;
|
|
15
19
|
/** The backend is the mock: show the marker on the button. */
|
|
16
20
|
mock?: boolean;
|
|
@@ -20,12 +24,13 @@
|
|
|
20
24
|
expanded?: boolean;
|
|
21
25
|
draggable?: boolean;
|
|
22
26
|
onclick?: () => void;
|
|
23
|
-
/** The user dropped the button
|
|
24
|
-
|
|
27
|
+
/** The user dropped the button here (top-left, already inside the viewport). */
|
|
28
|
+
onmove?: (position: Point) => void;
|
|
25
29
|
class?: string;
|
|
26
30
|
}
|
|
27
31
|
let {
|
|
28
32
|
corner = 'bottom-right',
|
|
33
|
+
position = null,
|
|
29
34
|
status = 'idle',
|
|
30
35
|
mock = false,
|
|
31
36
|
label = 'Classic Agent',
|
|
@@ -33,7 +38,7 @@
|
|
|
33
38
|
expanded = false,
|
|
34
39
|
draggable = true,
|
|
35
40
|
onclick,
|
|
36
|
-
|
|
41
|
+
onmove,
|
|
37
42
|
class: className,
|
|
38
43
|
}: Props = $props();
|
|
39
44
|
|
|
@@ -59,6 +64,11 @@
|
|
|
59
64
|
'top-left': 'top-5 left-5',
|
|
60
65
|
};
|
|
61
66
|
|
|
67
|
+
const viewport = () => ({ width: window.innerWidth, height: window.innerHeight });
|
|
68
|
+
// A placed button has to stay reachable when the window shrinks.
|
|
69
|
+
let size = $state(typeof window === 'undefined' ? { width: 1280, height: 800 } : viewport());
|
|
70
|
+
const placed = $derived(position ? clampLauncher(position, size) : null);
|
|
71
|
+
|
|
62
72
|
let start: Point | null = null;
|
|
63
73
|
let offset = $state<Point>({ x: 0, y: 0 });
|
|
64
74
|
let dragging = $state(false);
|
|
@@ -85,12 +95,10 @@
|
|
|
85
95
|
if (!dragging) return;
|
|
86
96
|
dragging = false;
|
|
87
97
|
suppressClick = true;
|
|
98
|
+
// Where the button is now, drag offset included.
|
|
99
|
+
const box = button?.getBoundingClientRect();
|
|
88
100
|
offset = { x: 0, y: 0 };
|
|
89
|
-
|
|
90
|
-
{ x: event.clientX, y: event.clientY },
|
|
91
|
-
{ width: window.innerWidth, height: window.innerHeight },
|
|
92
|
-
);
|
|
93
|
-
if (next !== corner) oncornerchange?.(next);
|
|
101
|
+
if (box) onmove?.(clampLauncher({ x: box.left, y: box.top }, viewport()));
|
|
94
102
|
}
|
|
95
103
|
function onpointercancel(): void {
|
|
96
104
|
start = null;
|
|
@@ -106,15 +114,19 @@
|
|
|
106
114
|
}
|
|
107
115
|
</script>
|
|
108
116
|
|
|
117
|
+
<svelte:window onresize={() => (size = viewport())} />
|
|
118
|
+
|
|
109
119
|
<button
|
|
110
120
|
bind:this={button}
|
|
111
121
|
type="button"
|
|
112
122
|
class={cn(
|
|
113
123
|
'fixed z-40 flex size-14 touch-none select-none items-center justify-center rounded-full bg-primary text-primary-foreground shadow-lg outline-none ring-offset-background transition-shadow hover:shadow-xl focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',
|
|
114
|
-
CORNER_CLASS[corner],
|
|
115
|
-
dragging ? 'cursor-grabbing opacity-
|
|
124
|
+
!placed && CORNER_CLASS[corner],
|
|
125
|
+
dragging ? 'scale-105 cursor-grabbing opacity-85' : 'cursor-pointer',
|
|
116
126
|
className,
|
|
117
127
|
)}
|
|
128
|
+
style:left={placed ? `${placed.x}px` : undefined}
|
|
129
|
+
style:top={placed ? `${placed.y}px` : undefined}
|
|
118
130
|
style:transform={dragging ? `translate(${offset.x}px, ${offset.y}px)` : undefined}
|
|
119
131
|
aria-label={name}
|
|
120
132
|
aria-expanded={expanded}
|
|
@@ -129,12 +141,18 @@
|
|
|
129
141
|
{onpointercancel}
|
|
130
142
|
>
|
|
131
143
|
{#if status === 'streaming'}
|
|
144
|
+
<!-- Answering: an arc orbits just outside the button and the icon breathes. With reduced motion
|
|
145
|
+
the rim stays as a still ring. -->
|
|
132
146
|
<span
|
|
133
|
-
class="pointer-events-none absolute inset-
|
|
147
|
+
class="pointer-events-none absolute -inset-[7px] rounded-full animate-spin [animation-duration:1.1s] motion-reduce:animate-none"
|
|
148
|
+
style="background: conic-gradient(from 0deg, transparent 0 35%, hsl(var(--primary)) 100%); -webkit-mask: radial-gradient(farthest-side, transparent calc(100% - 3px), #000 calc(100% - 2.5px)); mask: radial-gradient(farthest-side, transparent calc(100% - 3px), #000 calc(100% - 2.5px));"
|
|
134
149
|
aria-hidden="true"
|
|
150
|
+
data-testid="launcher-ring"
|
|
135
151
|
></span>
|
|
136
152
|
{/if}
|
|
137
|
-
<
|
|
153
|
+
<span class={cn('flex', status === 'streaming' && 'motion-safe:animate-pulse')}>
|
|
154
|
+
<Icon name="sparkles" size={24} />
|
|
155
|
+
</span>
|
|
138
156
|
{#if status === 'unread' || status === 'error'}
|
|
139
157
|
<span
|
|
140
158
|
class={cn(
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import type { LauncherStatus } from '../surface.svelte.js';
|
|
2
|
-
import { type Corner } from '../surface-geometry.js';
|
|
2
|
+
import { type Corner, type Point } from '../surface-geometry.js';
|
|
3
3
|
interface Props {
|
|
4
|
+
/** Where it sits until the user places it. */
|
|
4
5
|
corner?: Corner;
|
|
6
|
+
/** Where the user placed it (top-left, viewport px); null or absent uses `corner`. */
|
|
7
|
+
position?: Point | null;
|
|
5
8
|
status?: LauncherStatus;
|
|
6
9
|
/** The backend is the mock: show the marker on the button. */
|
|
7
10
|
mock?: boolean;
|
|
@@ -11,8 +14,8 @@ interface Props {
|
|
|
11
14
|
expanded?: boolean;
|
|
12
15
|
draggable?: boolean;
|
|
13
16
|
onclick?: () => void;
|
|
14
|
-
/** The user dropped the button
|
|
15
|
-
|
|
17
|
+
/** The user dropped the button here (top-left, already inside the viewport). */
|
|
18
|
+
onmove?: (position: Point) => void;
|
|
16
19
|
class?: string;
|
|
17
20
|
}
|
|
18
21
|
declare const ChatLauncher: import("svelte").Component<Props, {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ChatLauncher.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/ChatLauncher.svelte.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ChatLauncher.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/ChatLauncher.svelte.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,EAAiC,KAAK,MAAM,EAAE,KAAK,KAAK,EAAE,MAAM,wBAAwB,CAAC;AAG9F,UAAU,KAAK;IACb,8CAA8C;IAC9C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,sFAAsF;IACtF,QAAQ,CAAC,EAAE,KAAK,GAAG,IAAI,CAAC;IACxB,MAAM,CAAC,EAAE,cAAc,CAAC;IACxB,8DAA8D;IAC9D,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,gEAAgE;IAChE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,gFAAgF;IAChF,MAAM,CAAC,EAAE,CAAC,QAAQ,EAAE,KAAK,KAAK,IAAI,CAAC;IACnC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AA2HH,QAAA,MAAM,YAAY;iBArGG,IAAI;MAqGiC,CAAC;AAC3D,KAAK,YAAY,GAAG,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC;AACpD,eAAe,YAAY,CAAC"}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* it is given. Keyboard: the move handle takes arrow keys (Shift resizes, Alt for small
|
|
6
6
|
* steps); Escape inside the window minimizes it unless something inside used the key.
|
|
7
7
|
*/
|
|
8
|
-
import { untrack, type Snippet } from 'svelte';
|
|
8
|
+
import { onMount, untrack, type Snippet } from 'svelte';
|
|
9
9
|
import { Button, cn, Icon } from '@classic-homes/theme-svelte';
|
|
10
10
|
import type { ChatSurface } from '../surface.svelte.js';
|
|
11
11
|
import {
|
|
@@ -20,6 +20,10 @@
|
|
|
20
20
|
interface Props {
|
|
21
21
|
surface: ChatSurface;
|
|
22
22
|
title?: string;
|
|
23
|
+
/** A second, quieter line under the title, e.g. the conversation's name. */
|
|
24
|
+
subtitle?: string;
|
|
25
|
+
/** Replaces the mark before the title. */
|
|
26
|
+
icon?: Snippet;
|
|
23
27
|
id?: string;
|
|
24
28
|
/** Header buttons before Dock and Minimize, e.g. New chat. */
|
|
25
29
|
actions?: Snippet;
|
|
@@ -29,17 +33,25 @@
|
|
|
29
33
|
resizable?: boolean;
|
|
30
34
|
/** Called after the window minimizes, e.g. to move focus to the launcher. */
|
|
31
35
|
onminimize?: () => void;
|
|
36
|
+
/**
|
|
37
|
+
* Take the keyboard when the window appears and nothing inside has it yet: the first text
|
|
38
|
+
* field, or the window itself. Without it Escape and Tab start from the page underneath.
|
|
39
|
+
*/
|
|
40
|
+
autofocus?: boolean;
|
|
32
41
|
class?: string;
|
|
33
42
|
}
|
|
34
43
|
let {
|
|
35
44
|
surface,
|
|
36
45
|
title = 'Classic Agent',
|
|
46
|
+
subtitle,
|
|
47
|
+
icon,
|
|
37
48
|
id = 'agent-chat-window',
|
|
38
49
|
actions,
|
|
39
50
|
children,
|
|
40
51
|
dockable = true,
|
|
41
52
|
resizable = true,
|
|
42
53
|
onminimize,
|
|
54
|
+
autofocus = true,
|
|
43
55
|
class: className,
|
|
44
56
|
}: Props = $props();
|
|
45
57
|
|
|
@@ -47,6 +59,16 @@
|
|
|
47
59
|
// Untracked: attaching clamps the saved rect, which must not re-run this effect.
|
|
48
60
|
$effect(() => untrack(() => surface.attach()));
|
|
49
61
|
|
|
62
|
+
let root: HTMLElement | undefined = $state();
|
|
63
|
+
// After the content has mounted (and had its own chance to focus something).
|
|
64
|
+
onMount(() => {
|
|
65
|
+
if (!autofocus || !root || root.contains(document.activeElement)) return;
|
|
66
|
+
const field = root.querySelector<HTMLElement>(
|
|
67
|
+
'textarea:not([disabled]), input:not([disabled])',
|
|
68
|
+
);
|
|
69
|
+
(field ?? root).focus({ preventScroll: true });
|
|
70
|
+
});
|
|
71
|
+
|
|
50
72
|
function minimize(): void {
|
|
51
73
|
surface.minimize();
|
|
52
74
|
onminimize?.();
|
|
@@ -161,14 +183,16 @@
|
|
|
161
183
|
}
|
|
162
184
|
</script>
|
|
163
185
|
|
|
164
|
-
<!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
|
|
186
|
+
<!-- svelte-ignore a11y_no_noninteractive_element_interactions, a11y_no_noninteractive_element_to_interactive_role -->
|
|
165
187
|
<section
|
|
188
|
+
bind:this={root}
|
|
166
189
|
{id}
|
|
190
|
+
tabindex="-1"
|
|
167
191
|
role="dialog"
|
|
168
192
|
aria-modal="false"
|
|
169
193
|
aria-labelledby="{id}-title"
|
|
170
194
|
class={cn(
|
|
171
|
-
'fixed z-40 flex flex-col overflow-hidden rounded-xl border border-border bg-background text-foreground shadow-2xl',
|
|
195
|
+
'fixed z-40 flex flex-col overflow-hidden rounded-xl border border-border bg-background text-foreground shadow-2xl outline-none',
|
|
172
196
|
surface.moving && 'select-none',
|
|
173
197
|
dockHint && 'ring-2 ring-primary',
|
|
174
198
|
className,
|
|
@@ -184,7 +208,7 @@
|
|
|
184
208
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
185
209
|
<div
|
|
186
210
|
class={cn(
|
|
187
|
-
'flex h-
|
|
211
|
+
'flex h-12 shrink-0 touch-none items-center gap-1 border-b border-border bg-muted/40 pl-1 pr-2',
|
|
188
212
|
surface.moving ? 'cursor-grabbing' : 'cursor-grab',
|
|
189
213
|
)}
|
|
190
214
|
data-testid="window-header"
|
|
@@ -204,7 +228,24 @@
|
|
|
204
228
|
>
|
|
205
229
|
<Icon name="grip" size={14} />
|
|
206
230
|
</button>
|
|
207
|
-
|
|
231
|
+
{#if icon}
|
|
232
|
+
{@render icon()}
|
|
233
|
+
{:else}
|
|
234
|
+
<span
|
|
235
|
+
class="flex size-7 shrink-0 items-center justify-center rounded-full bg-primary/10 text-primary"
|
|
236
|
+
aria-hidden="true"
|
|
237
|
+
>
|
|
238
|
+
<Icon name="sparkles" size={14} />
|
|
239
|
+
</span>
|
|
240
|
+
{/if}
|
|
241
|
+
<div class="ml-1 flex min-w-0 flex-1 flex-col leading-tight">
|
|
242
|
+
<h2 id="{id}-title" class="truncate text-sm font-semibold">{title}</h2>
|
|
243
|
+
{#if subtitle}
|
|
244
|
+
<span class="truncate text-[11px] text-muted-foreground" data-testid="window-subtitle"
|
|
245
|
+
>{subtitle}</span
|
|
246
|
+
>
|
|
247
|
+
{/if}
|
|
248
|
+
</div>
|
|
208
249
|
{@render actions?.()}
|
|
209
250
|
{#if dockable}
|
|
210
251
|
<Button
|
|
@@ -9,6 +9,10 @@ import type { ChatSurface } from '../surface.svelte.js';
|
|
|
9
9
|
interface Props {
|
|
10
10
|
surface: ChatSurface;
|
|
11
11
|
title?: string;
|
|
12
|
+
/** A second, quieter line under the title, e.g. the conversation's name. */
|
|
13
|
+
subtitle?: string;
|
|
14
|
+
/** Replaces the mark before the title. */
|
|
15
|
+
icon?: Snippet;
|
|
12
16
|
id?: string;
|
|
13
17
|
/** Header buttons before Dock and Minimize, e.g. New chat. */
|
|
14
18
|
actions?: Snippet;
|
|
@@ -18,6 +22,11 @@ interface Props {
|
|
|
18
22
|
resizable?: boolean;
|
|
19
23
|
/** Called after the window minimizes, e.g. to move focus to the launcher. */
|
|
20
24
|
onminimize?: () => void;
|
|
25
|
+
/**
|
|
26
|
+
* Take the keyboard when the window appears and nothing inside has it yet: the first text
|
|
27
|
+
* field, or the window itself. Without it Escape and Tab start from the page underneath.
|
|
28
|
+
*/
|
|
29
|
+
autofocus?: boolean;
|
|
21
30
|
class?: string;
|
|
22
31
|
}
|
|
23
32
|
declare const FloatingWindow: import("svelte").Component<Props, {}, "">;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FloatingWindow.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/FloatingWindow.svelte.ts"],"names":[],"mappings":"AAGA;;;;;KAKK;AACL,OAAO,
|
|
1
|
+
{"version":3,"file":"FloatingWindow.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/FloatingWindow.svelte.ts"],"names":[],"mappings":"AAGA;;;;;KAKK;AACL,OAAO,EAAoB,KAAK,OAAO,EAAE,MAAM,QAAQ,CAAC;AAExD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAWtD,UAAU,KAAK;IACb,OAAO,EAAE,WAAW,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,4EAA4E;IAC5E,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,0CAA0C;IAC1C,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,8DAA8D;IAC9D,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,EAAE,OAAO,CAAC;IAClB,yEAAyE;IACzE,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,6EAA6E;IAC7E,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;IACxB;;;OAGG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AA2MH,QAAA,MAAM,cAAc,2CAAwC,CAAC;AAC7D,KAAK,cAAc,GAAG,UAAU,CAAC,OAAO,cAAc,CAAC,CAAC;AACxD,eAAe,cAAc,CAAC"}
|
package/dist/index.d.ts
CHANGED
|
@@ -8,6 +8,7 @@ export * from './session.svelte.js';
|
|
|
8
8
|
export * from './shortcuts.js';
|
|
9
9
|
export * from './surface-geometry.js';
|
|
10
10
|
export * from './surface.svelte.js';
|
|
11
|
+
export * from './thinking.js';
|
|
11
12
|
export * from './ui-frame-protocol.js';
|
|
12
13
|
export * from './welcome.js';
|
|
13
14
|
export { default as AgentChat } from './components/AgentChat.svelte';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/lib/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC;AAC/B,cAAc,oBAAoB,CAAC;AACnC,cAAc,uBAAuB,CAAC;AACtC,cAAc,eAAe,CAAC;AAC9B,cAAc,qBAAqB,CAAC;AACpC,cAAc,cAAc,CAAC;AAC7B,cAAc,qBAAqB,CAAC;AACpC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,uBAAuB,CAAC;AACtC,cAAc,qBAAqB,CAAC;AACpC,cAAc,wBAAwB,CAAC;AACvC,cAAc,cAAc,CAAC;AAC7B,OAAO,EAAE,OAAO,IAAI,SAAS,EAAE,MAAM,+BAA+B,CAAC;AACrE,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,mCAAmC,CAAC;AAC7E,OAAO,EAAE,OAAO,IAAI,QAAQ,EAAE,MAAM,8BAA8B,CAAC;AACnE,OAAO,EAAE,OAAO,IAAI,mBAAmB,EAAE,MAAM,yCAAyC,CAAC;AACzF,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,iCAAiC,CAAC;AACzE,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,iCAAiC,CAAC;AACzE,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,gCAAgC,CAAC;AACvE,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,kCAAkC,CAAC;AAC3E,OAAO,EAAE,OAAO,IAAI,OAAO,EAAE,MAAM,6BAA6B,CAAC;AACjE,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,gCAAgC,CAAC;AACvE,OAAO,EAAE,OAAO,IAAI,gBAAgB,EAAE,MAAM,sCAAsC,CAAC;AACnF,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,iCAAiC,CAAC;AACzE,OAAO,EAAE,OAAO,IAAI,cAAc,EAAE,MAAM,oCAAoC,CAAC;AAC/E,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,kCAAkC,CAAC;AAC3E,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,kCAAkC,CAAC;AAC3E,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,kCAAkC,CAAC;AAC3E,OAAO,EAAE,OAAO,IAAI,OAAO,EAAE,MAAM,6BAA6B,CAAC;AACjE,OAAO,EAAE,OAAO,IAAI,eAAe,EAAE,MAAM,qCAAqC,CAAC;AACjF,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,kCAAkC,CAAC;AAC3E,OAAO,EAAE,OAAO,IAAI,cAAc,EAAE,MAAM,oCAAoC,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/lib/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC;AAC/B,cAAc,oBAAoB,CAAC;AACnC,cAAc,uBAAuB,CAAC;AACtC,cAAc,eAAe,CAAC;AAC9B,cAAc,qBAAqB,CAAC;AACpC,cAAc,cAAc,CAAC;AAC7B,cAAc,qBAAqB,CAAC;AACpC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,uBAAuB,CAAC;AACtC,cAAc,qBAAqB,CAAC;AACpC,cAAc,eAAe,CAAC;AAC9B,cAAc,wBAAwB,CAAC;AACvC,cAAc,cAAc,CAAC;AAC7B,OAAO,EAAE,OAAO,IAAI,SAAS,EAAE,MAAM,+BAA+B,CAAC;AACrE,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,mCAAmC,CAAC;AAC7E,OAAO,EAAE,OAAO,IAAI,QAAQ,EAAE,MAAM,8BAA8B,CAAC;AACnE,OAAO,EAAE,OAAO,IAAI,mBAAmB,EAAE,MAAM,yCAAyC,CAAC;AACzF,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,iCAAiC,CAAC;AACzE,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,iCAAiC,CAAC;AACzE,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,gCAAgC,CAAC;AACvE,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,kCAAkC,CAAC;AAC3E,OAAO,EAAE,OAAO,IAAI,OAAO,EAAE,MAAM,6BAA6B,CAAC;AACjE,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,gCAAgC,CAAC;AACvE,OAAO,EAAE,OAAO,IAAI,gBAAgB,EAAE,MAAM,sCAAsC,CAAC;AACnF,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,iCAAiC,CAAC;AACzE,OAAO,EAAE,OAAO,IAAI,cAAc,EAAE,MAAM,oCAAoC,CAAC;AAC/E,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,kCAAkC,CAAC;AAC3E,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,kCAAkC,CAAC;AAC3E,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,kCAAkC,CAAC;AAC3E,OAAO,EAAE,OAAO,IAAI,OAAO,EAAE,MAAM,6BAA6B,CAAC;AACjE,OAAO,EAAE,OAAO,IAAI,eAAe,EAAE,MAAM,qCAAqC,CAAC;AACjF,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,kCAAkC,CAAC;AAC3E,OAAO,EAAE,OAAO,IAAI,cAAc,EAAE,MAAM,oCAAoC,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -8,6 +8,7 @@ export * from './session.svelte.js';
|
|
|
8
8
|
export * from './shortcuts.js';
|
|
9
9
|
export * from './surface-geometry.js';
|
|
10
10
|
export * from './surface.svelte.js';
|
|
11
|
+
export * from './thinking.js';
|
|
11
12
|
export * from './ui-frame-protocol.js';
|
|
12
13
|
export * from './welcome.js';
|
|
13
14
|
export { default as AgentChat } from './components/AgentChat.svelte';
|
package/dist/session.svelte.d.ts
CHANGED
|
@@ -50,6 +50,8 @@ export declare class ChatSession {
|
|
|
50
50
|
bytes: number;
|
|
51
51
|
status: "uploaded" | "indexed" | "failed";
|
|
52
52
|
}[];
|
|
53
|
+
/** The unsent composer text, kept here so it survives the view being remounted. */
|
|
54
|
+
draft: string;
|
|
53
55
|
private controller;
|
|
54
56
|
/** The request being streamed; differs from the shown answer's id while continuing it. */
|
|
55
57
|
private streamRequestId;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session.svelte.d.ts","sourceRoot":"","sources":["../src/lib/session.svelte.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAElE,OAAO,KAAK,EACV,QAAQ,EACR,WAAW,EACX,SAAS,EAET,UAAU,EACV,eAAe,EACf,OAAO,EACP,UAAU,EACX,MAAM,gCAAgC,CAAC;AACxC,OAAO,EAOL,KAAK,gBAAgB,EACrB,KAAK,WAAW,EACjB,MAAM,cAAc,CAAC;AAEtB,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,cAAc,CAAC;IACvB,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,mGAAmG;IACnG,IAAI,CAAC,EAAE,WAAW,CAAC;IACnB,KAAK,CAAC,EAAE,QAAQ,CAAC;IACjB,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,kEAAkE;IAClE,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,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,SAAS,KAAK,IAAI,CAAC;IACrC;;;OAGG;IACH,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;IACjG;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAQD,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,SAAS,GAAG,WAAW,CAAC;AAE7D;;;GAGG;AACH,qBAAa,WAAW;IACtB,QAAQ,gBAA6B;IACrC,MAAM,gBAAiC;IACvC,cAAc,gBAA+B;IAC7C,gGAAgG;IAChG,oBAAoB,gBAA+B;IACnD,IAAI,8BAA8C;IAClD,4FAA4F;IAC5F,KAAK,SAAqC;IAC1C,SAAS,qBAAyC;IAClD,YAAY;;;;;;QAAyB;
|
|
1
|
+
{"version":3,"file":"session.svelte.d.ts","sourceRoot":"","sources":["../src/lib/session.svelte.ts"],"names":[],"mappings":"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"}
|
package/dist/session.svelte.js
CHANGED
|
@@ -18,6 +18,8 @@ export class ChatSession {
|
|
|
18
18
|
agent = $state('classic-agent');
|
|
19
19
|
loadError = $state(undefined);
|
|
20
20
|
pendingFiles = $state([]);
|
|
21
|
+
/** The unsent composer text, kept here so it survives the view being remounted. */
|
|
22
|
+
draft = $state('');
|
|
21
23
|
controller;
|
|
22
24
|
/** The request being streamed; differs from the shown answer's id while continuing it. */
|
|
23
25
|
streamRequestId;
|
|
@@ -44,6 +44,10 @@ export declare function clampSize(size: Size, viewport: Size): Size;
|
|
|
44
44
|
export declare function clampRect(rect: Rect, viewport: Size): Rect;
|
|
45
45
|
/** Where a new window opens: beside the launcher's corner, clear of the launcher itself. */
|
|
46
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;
|
|
47
51
|
/** The corner a dropped launcher snaps to. */
|
|
48
52
|
export declare function nearestCorner(point: Point, viewport: Size): Corner;
|
|
49
53
|
/**
|
|
@@ -1 +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,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"}
|
|
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"}
|
package/dist/surface-geometry.js
CHANGED
|
@@ -63,6 +63,19 @@ export function defaultRect(viewport, corner, size = DEFAULT_FLOAT_SIZE) {
|
|
|
63
63
|
y: corner.startsWith('top') ? clear : viewport.height - fitted.height - clear,
|
|
64
64
|
}, viewport);
|
|
65
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
|
+
}
|
|
66
79
|
/** The corner a dropped launcher snaps to. */
|
|
67
80
|
export function nearestCorner(point, viewport) {
|
|
68
81
|
const vertical = point.y < viewport.height / 2 ? 'top' : 'bottom';
|
package/dist/surface.svelte.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type Corner, type Rect, type ResizeDirection, type Size } from './surface-geometry.js';
|
|
1
|
+
import { type Corner, type Point, type Rect, type ResizeDirection, type Size } from './surface-geometry.js';
|
|
2
2
|
/**
|
|
3
3
|
* How the chat is presented over a host page: collapsed to the launcher button, a floating
|
|
4
4
|
* window, or docked to the side. The full page is a host route, not a mode here; a host on that
|
|
@@ -21,7 +21,7 @@ export interface ChatSurfaceOptions {
|
|
|
21
21
|
}
|
|
22
22
|
/**
|
|
23
23
|
* Reactive state of the detached chat (Svelte 5 runes class): which presentation is showing,
|
|
24
|
-
* the launcher
|
|
24
|
+
* where the launcher sits, the floating window's geometry and whether an answer arrived unseen.
|
|
25
25
|
* Layout survives reloads through `storageKey`; a saved window that no longer fits the viewport
|
|
26
26
|
* is pulled back inside.
|
|
27
27
|
*/
|
|
@@ -30,6 +30,11 @@ export declare class ChatSurface {
|
|
|
30
30
|
/** The open presentation the launcher returns to. */
|
|
31
31
|
lastOpen: OpenSurfaceMode;
|
|
32
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;
|
|
33
38
|
/** An answer finished while the chat was collapsed. Cleared on open. */
|
|
34
39
|
unread: boolean;
|
|
35
40
|
/** True while the user drags or resizes the window. */
|
|
@@ -48,6 +53,11 @@ export declare class ChatSurface {
|
|
|
48
53
|
/** A turn ended; flag it when the user cannot see the answer. */
|
|
49
54
|
notifyTurnEnd(): void;
|
|
50
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;
|
|
51
61
|
/** Move the window by a pointer or key delta. Call `commit` when the gesture ends. */
|
|
52
62
|
moveBy(dx: number, dy: number): void;
|
|
53
63
|
resizeBy(direction: ResizeDirection, dx: number, dy: number): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"surface.svelte.d.ts","sourceRoot":"","sources":["../src/lib/surface.svelte.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
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"}
|
package/dist/surface.svelte.js
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
|
-
import { CORNERS, clampRect, defaultRect, resizeRect, } from './surface-geometry.js';
|
|
1
|
+
import { CORNERS, LAUNCHER_SIZE, clampLauncher, clampRect, defaultRect, nearestCorner, resizeRect, } from './surface-geometry.js';
|
|
2
|
+
const isPoint = (value) => typeof value === 'object' &&
|
|
3
|
+
value !== null &&
|
|
4
|
+
['x', 'y'].every((key) => Number.isFinite(value[key]));
|
|
2
5
|
const isRect = (value) => typeof value === 'object' &&
|
|
3
6
|
value !== null &&
|
|
4
7
|
['x', 'y', 'width', 'height'].every((key) => Number.isFinite(value[key]));
|
|
5
8
|
/**
|
|
6
9
|
* Reactive state of the detached chat (Svelte 5 runes class): which presentation is showing,
|
|
7
|
-
* the launcher
|
|
10
|
+
* where the launcher sits, the floating window's geometry and whether an answer arrived unseen.
|
|
8
11
|
* Layout survives reloads through `storageKey`; a saved window that no longer fits the viewport
|
|
9
12
|
* is pulled back inside.
|
|
10
13
|
*/
|
|
@@ -13,6 +16,11 @@ export class ChatSurface {
|
|
|
13
16
|
/** The open presentation the launcher returns to. */
|
|
14
17
|
lastOpen = $state('float');
|
|
15
18
|
corner = $state('bottom-right');
|
|
19
|
+
/**
|
|
20
|
+
* Where the user dropped the launcher (its top-left), or null while it sits in `corner`.
|
|
21
|
+
* Separate from the window's place: moving one never moves the other.
|
|
22
|
+
*/
|
|
23
|
+
launcher = $state(null);
|
|
16
24
|
/** An answer finished while the chat was collapsed. Cleared on open. */
|
|
17
25
|
unread = $state(false);
|
|
18
26
|
/** True while the user drags or resizes the window. */
|
|
@@ -62,6 +70,17 @@ export class ChatSurface {
|
|
|
62
70
|
}
|
|
63
71
|
setCorner(corner) {
|
|
64
72
|
this.corner = corner;
|
|
73
|
+
this.launcher = null;
|
|
74
|
+
this.persist();
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* The user dropped the launcher at `point` (top-left). It stays there; `corner` follows the
|
|
78
|
+
* nearest one, which is where a window that was never moved opens.
|
|
79
|
+
*/
|
|
80
|
+
setLauncher(point) {
|
|
81
|
+
const viewport = this.viewport();
|
|
82
|
+
this.launcher = clampLauncher(point, viewport);
|
|
83
|
+
this.corner = nearestCorner({ x: this.launcher.x + LAUNCHER_SIZE / 2, y: this.launcher.y + LAUNCHER_SIZE / 2 }, viewport);
|
|
65
84
|
this.persist();
|
|
66
85
|
}
|
|
67
86
|
/** Move the window by a pointer or key delta. Call `commit` when the gesture ends. */
|
|
@@ -130,6 +149,8 @@ export class ChatSurface {
|
|
|
130
149
|
this.corner = data.corner;
|
|
131
150
|
if (isRect(data.rect))
|
|
132
151
|
this.saved = clampRect(data.rect, this.viewport());
|
|
152
|
+
if (isPoint(data.launcher))
|
|
153
|
+
this.launcher = clampLauncher({ x: data.launcher.x, y: data.launcher.y }, this.viewport());
|
|
133
154
|
}
|
|
134
155
|
catch {
|
|
135
156
|
// Unreadable or blocked storage: start from the defaults.
|
|
@@ -143,6 +164,7 @@ export class ChatSurface {
|
|
|
143
164
|
lastOpen: this.lastOpen,
|
|
144
165
|
corner: this.corner,
|
|
145
166
|
rect: this.saved,
|
|
167
|
+
launcher: this.launcher,
|
|
146
168
|
};
|
|
147
169
|
this.storage()?.setItem(this.options.storageKey, JSON.stringify(data));
|
|
148
170
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What the assistant shows while it works and has nothing to say yet: homebuilding verbs in
|
|
3
|
+
* place of a bare "Thinking" (the wording the Classic Chat widget uses).
|
|
4
|
+
*/
|
|
5
|
+
export declare const THINKING_PHRASES: readonly string[];
|
|
6
|
+
/** How long one phrase shows before the next. */
|
|
7
|
+
export declare const THINKING_PHRASE_MS = 4000;
|
|
8
|
+
/** A random phrase, never `previous` twice in a row. */
|
|
9
|
+
export declare function pickThinkingPhrase(previous?: string, random?: () => number): string;
|
|
10
|
+
//# sourceMappingURL=thinking.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"thinking.d.ts","sourceRoot":"","sources":["../src/lib/thinking.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,eAAO,MAAM,gBAAgB,EAAE,SAAS,MAAM,EAiD7C,CAAC;AAEF,iDAAiD;AACjD,eAAO,MAAM,kBAAkB,OAAO,CAAC;AAEvC,wDAAwD;AACxD,wBAAgB,kBAAkB,CAAC,QAAQ,CAAC,EAAE,MAAM,EAAE,MAAM,GAAE,MAAM,MAAoB,GAAG,MAAM,CAGhG"}
|
package/dist/thinking.js
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What the assistant shows while it works and has nothing to say yet: homebuilding verbs in
|
|
3
|
+
* place of a bare "Thinking" (the wording the Classic Chat widget uses).
|
|
4
|
+
*/
|
|
5
|
+
export const THINKING_PHRASES = [
|
|
6
|
+
'Framing',
|
|
7
|
+
'Drafting',
|
|
8
|
+
'Surveying',
|
|
9
|
+
'Grading',
|
|
10
|
+
'Excavating',
|
|
11
|
+
'Leveling',
|
|
12
|
+
'Measuring',
|
|
13
|
+
'Squaring',
|
|
14
|
+
'Pouring footings',
|
|
15
|
+
'Pouring slab',
|
|
16
|
+
'Laying foundation',
|
|
17
|
+
'Raising walls',
|
|
18
|
+
'Setting trusses',
|
|
19
|
+
'Sheathing',
|
|
20
|
+
'Roofing',
|
|
21
|
+
'Nailing',
|
|
22
|
+
'Checking plans',
|
|
23
|
+
'Reading blueprints',
|
|
24
|
+
'Pulling permits',
|
|
25
|
+
'Scheduling inspections',
|
|
26
|
+
'Sketching layouts',
|
|
27
|
+
'Mapping lots',
|
|
28
|
+
'Staking corners',
|
|
29
|
+
'Wiring',
|
|
30
|
+
'Plumbing',
|
|
31
|
+
'Ducting',
|
|
32
|
+
'Insulating',
|
|
33
|
+
'Hanging drywall',
|
|
34
|
+
'Mudding',
|
|
35
|
+
'Sanding',
|
|
36
|
+
'Priming',
|
|
37
|
+
'Painting',
|
|
38
|
+
'Laying tile',
|
|
39
|
+
'Installing cabinets',
|
|
40
|
+
'Setting countertops',
|
|
41
|
+
'Hanging doors',
|
|
42
|
+
'Trimming',
|
|
43
|
+
'Flooring',
|
|
44
|
+
'Picking finishes',
|
|
45
|
+
'Matching paint',
|
|
46
|
+
'Choosing fixtures',
|
|
47
|
+
'Landscaping',
|
|
48
|
+
'Touring models',
|
|
49
|
+
'Comparing plans',
|
|
50
|
+
'Building',
|
|
51
|
+
'Punch list',
|
|
52
|
+
'Final walkthrough',
|
|
53
|
+
'Handing keys',
|
|
54
|
+
];
|
|
55
|
+
/** How long one phrase shows before the next. */
|
|
56
|
+
export const THINKING_PHRASE_MS = 4000;
|
|
57
|
+
/** A random phrase, never `previous` twice in a row. */
|
|
58
|
+
export function pickThinkingPhrase(previous, random = Math.random) {
|
|
59
|
+
const pool = previous ? THINKING_PHRASES.filter((p) => p !== previous) : THINKING_PHRASES;
|
|
60
|
+
return pool[Math.min(pool.length - 1, Math.floor(random() * pool.length))];
|
|
61
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@classic-homes/agent-chat",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "Svelte 5 chat UI for Classic Agent: session state, message list, composer, tool timeline, artifacts and MCP-UI sandbox frames. Consumed by the Classic App.",
|
|
@@ -18,8 +18,8 @@
|
|
|
18
18
|
"!dist/**/*.test.*"
|
|
19
19
|
],
|
|
20
20
|
"dependencies": {
|
|
21
|
-
"@classic-homes/agent-client": "0.
|
|
22
|
-
"@classic-homes/agent-contracts": "0.
|
|
21
|
+
"@classic-homes/agent-client": "0.13.0",
|
|
22
|
+
"@classic-homes/agent-contracts": "0.13.0",
|
|
23
23
|
"dompurify": "3.4.17"
|
|
24
24
|
},
|
|
25
25
|
"peerDependencies": {
|