@artooi/ag-ui-web-component 0.46.0 → 0.47.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/CHANGELOG.md +87 -1
- package/README.md +130 -19
- package/dist/ag-ui-web-component.bundle.js +99 -94
- package/dist/ag-ui-web-component.bundle.js.map +4 -4
- package/dist/constants.d.ts +10 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/core/ag_ui_chat.d.ts +38 -0
- package/dist/core/ag_ui_chat.d.ts.map +1 -1
- package/dist/core/chat_registry.d.ts +23 -0
- package/dist/core/chat_registry.d.ts.map +1 -0
- package/dist/core/events/run_finished_detail.d.ts +6 -0
- package/dist/core/events/run_finished_detail.d.ts.map +1 -1
- package/dist/core/events/thread_detail.d.ts +27 -0
- package/dist/core/events/thread_detail.d.ts.map +1 -0
- package/dist/core/run_handlers.d.ts +6 -1
- package/dist/core/run_handlers.d.ts.map +1 -1
- package/dist/dom/animations.d.ts +21 -6
- package/dist/dom/animations.d.ts.map +1 -1
- package/dist/dom/highlight_overlay.d.ts +7 -1
- package/dist/dom/highlight_overlay.d.ts.map +1 -1
- package/dist/dom/ring_color.d.ts +14 -0
- package/dist/dom/ring_color.d.ts.map +1 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +249 -54
- package/dist/index.js.map +4 -4
- package/dist/ui/history/conversation_history.d.ts +10 -1
- package/dist/ui/history/conversation_history.d.ts.map +1 -1
- package/dist/ui/placement/panel_placement.d.ts +6 -0
- package/dist/ui/placement/panel_placement.d.ts.map +1 -1
- package/dist/ui/placement/small_viewport_styles.d.ts.map +1 -1
- package/dist/ui/progress/tool_call_card.d.ts.map +1 -1
- package/dist/ui/progress/tool_step_group.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/constants.ts +11 -0
- package/src/core/ag_ui_chat.ts +108 -5
- package/src/core/chat_registry.ts +53 -0
- package/src/core/events/run_finished_detail.ts +6 -0
- package/src/core/events/thread_detail.ts +26 -0
- package/src/core/run_handlers.ts +9 -4
- package/src/dom/animations.ts +30 -31
- package/src/dom/highlight_overlay.ts +9 -5
- package/src/dom/ring_color.ts +85 -0
- package/src/index.ts +3 -0
- package/src/ui/history/conversation_history.ts +28 -4
- package/src/ui/placement/embedded_styles.ts +2 -2
- package/src/ui/placement/panel_placement.ts +34 -9
- package/src/ui/placement/preset_styles.ts +1 -1
- package/src/ui/placement/sidebar_styles.ts +1 -1
- package/src/ui/placement/small_viewport_styles.ts +5 -0
- package/src/ui/progress/tool_call_card.ts +18 -8
- package/src/ui/progress/tool_step_group.ts +3 -0
- package/src/ui/shell/token_styles.ts +2 -2
- package/src/version.ts +1 -1
package/src/dom/animations.ts
CHANGED
|
@@ -8,17 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
import { showHighlightOverlay } from "./highlight_overlay.js";
|
|
10
10
|
import { setNativeChecked, setNativeValue } from "./native_setter.js";
|
|
11
|
-
|
|
12
|
-
/** Ring colour when the host page has not themed `--ag-ui-accent`. */
|
|
13
|
-
const ACCENT = "#4f46e5";
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Fallback for the box-shadow rings, which read as a soft halo rather than a
|
|
17
|
-
* line and so carry their own alpha.
|
|
18
|
-
*/
|
|
19
|
-
const ACCENT_HALO = "rgba(79, 70, 229, 0.4)";
|
|
20
|
-
|
|
21
|
-
const ACCENT_PROPERTY = "--ag-ui-accent";
|
|
11
|
+
import { ringColor } from "./ring_color.js";
|
|
22
12
|
|
|
23
13
|
function delay(ms: number): Promise<void> {
|
|
24
14
|
return new Promise<void>((resolve) => {
|
|
@@ -48,19 +38,23 @@ function motionDelay(ms: number): Promise<void> {
|
|
|
48
38
|
}
|
|
49
39
|
|
|
50
40
|
/**
|
|
51
|
-
*
|
|
41
|
+
* Which `<ag-ui-chat>` a ring speaks for, so it is drawn in that chat's colour.
|
|
52
42
|
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
43
|
+
* The ring is drawn in the host's page, which a token set on the chat never
|
|
44
|
+
* reaches, so the chat is read directly. Unset, it is the most recently
|
|
45
|
+
* connected chat; a page with two widgets names the one acting.
|
|
56
46
|
*/
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
47
|
+
export interface RingOwnerOptions {
|
|
48
|
+
/** The chat whose ring colour and accent to use. Default: the latest connected. */
|
|
49
|
+
chat?: HTMLElement;
|
|
60
50
|
}
|
|
61
51
|
|
|
62
|
-
|
|
63
|
-
|
|
52
|
+
/**
|
|
53
|
+
* The soft halo the press and toggle rings draw: the ring colour at 40%.
|
|
54
|
+
* Mixed rather than given its own alpha, so it follows whichever colour won.
|
|
55
|
+
*/
|
|
56
|
+
function ringShadow(el: HTMLElement, chat: HTMLElement | undefined): string {
|
|
57
|
+
return `0 0 0 3px color-mix(in srgb, ${ringColor(el, chat)} 40%, transparent)`;
|
|
64
58
|
}
|
|
65
59
|
|
|
66
60
|
/** An element whose `value` can be typed into. */
|
|
@@ -93,7 +87,7 @@ export async function typeInto(
|
|
|
93
87
|
el.dispatchEvent(new Event("change", { bubbles: true }));
|
|
94
88
|
}
|
|
95
89
|
|
|
96
|
-
export interface HighlightClickOptions {
|
|
90
|
+
export interface HighlightClickOptions extends RingOwnerOptions {
|
|
97
91
|
/** Milliseconds to hold the highlight before clicking. Default 280. */
|
|
98
92
|
highlightMs?: number;
|
|
99
93
|
}
|
|
@@ -106,7 +100,7 @@ export async function highlightThenClick(
|
|
|
106
100
|
const highlightMs = options.highlightMs ?? 280;
|
|
107
101
|
const previousOutline = el.style.outline;
|
|
108
102
|
const previousOffset = el.style.outlineOffset;
|
|
109
|
-
el.style.outline = `2px solid ${
|
|
103
|
+
el.style.outline = `2px solid ${ringColor(el, options.chat)}`;
|
|
110
104
|
el.style.outlineOffset = "2px";
|
|
111
105
|
await delay(highlightMs);
|
|
112
106
|
el.style.outline = previousOutline;
|
|
@@ -176,10 +170,14 @@ const FLASH_MS = 1200;
|
|
|
176
170
|
/** Share of {@link FLASH_MS} spent fading out rather than holding. */
|
|
177
171
|
const FLASH_FADE_RATIO = 1 / 3;
|
|
178
172
|
|
|
179
|
-
export interface FlashOptions {
|
|
173
|
+
export interface FlashOptions extends RingOwnerOptions {
|
|
180
174
|
/** Total milliseconds the ring is on screen, hold plus fade. Default 1200. */
|
|
181
175
|
flashMs?: number;
|
|
182
|
-
/**
|
|
176
|
+
/**
|
|
177
|
+
* Ring colour. Defaults to `--ag-ui-highlight-ring-color` (on the target,
|
|
178
|
+
* then on the chat), then the target's `--ag-ui-accent`, then the chat's
|
|
179
|
+
* accent.
|
|
180
|
+
*/
|
|
183
181
|
color?: string;
|
|
184
182
|
/**
|
|
185
183
|
* Dim everything except the target.
|
|
@@ -226,6 +224,7 @@ async function flashRing(
|
|
|
226
224
|
gradient: options.gradient === true,
|
|
227
225
|
...(options.color === undefined ? {} : { color: options.color }),
|
|
228
226
|
...(options.ringPadding === undefined ? {} : { padding: options.ringPadding }),
|
|
227
|
+
...(options.chat === undefined ? {} : { chat: options.chat }),
|
|
229
228
|
});
|
|
230
229
|
await delay(flashMs);
|
|
231
230
|
dismiss();
|
|
@@ -234,7 +233,7 @@ async function flashRing(
|
|
|
234
233
|
const previousOutline = el.style.outline;
|
|
235
234
|
const previousOffset = el.style.outlineOffset;
|
|
236
235
|
const previousTransition = el.style.transition;
|
|
237
|
-
const color = options.color ??
|
|
236
|
+
const color = options.color ?? ringColor(el, options.chat);
|
|
238
237
|
// outline, not box-shadow: a shadow paints outside the border box, so any
|
|
239
238
|
// overflow:hidden ancestor sharing the target's box (a card, a table cell)
|
|
240
239
|
// clips the whole ring away while the helper still reports success.
|
|
@@ -273,7 +272,7 @@ export function focusWithFlash(el: HTMLElement, options: FlashOptions = {}): Pro
|
|
|
273
272
|
return flashRing(el, options, true);
|
|
274
273
|
}
|
|
275
274
|
|
|
276
|
-
export interface PressOptions {
|
|
275
|
+
export interface PressOptions extends RingOwnerOptions {
|
|
277
276
|
/** Milliseconds to hold the pressed state before clicking. Default 140. */
|
|
278
277
|
pressMs?: number;
|
|
279
278
|
}
|
|
@@ -290,7 +289,7 @@ export async function pressThenClick(el: HTMLElement, options: PressOptions = {}
|
|
|
290
289
|
const previousShadow = el.style.boxShadow;
|
|
291
290
|
el.style.transition = "transform 80ms ease";
|
|
292
291
|
el.style.transform = "scale(0.96)";
|
|
293
|
-
el.style.boxShadow = ringShadow(el);
|
|
292
|
+
el.style.boxShadow = ringShadow(el, options.chat);
|
|
294
293
|
await motionDelay(pressMs);
|
|
295
294
|
el.style.transform = previousTransform;
|
|
296
295
|
el.style.transition = previousTransition;
|
|
@@ -298,7 +297,7 @@ export async function pressThenClick(el: HTMLElement, options: PressOptions = {}
|
|
|
298
297
|
el.click();
|
|
299
298
|
}
|
|
300
299
|
|
|
301
|
-
export interface SelectOptions {
|
|
300
|
+
export interface SelectOptions extends RingOwnerOptions {
|
|
302
301
|
/** Milliseconds to hold the highlight before committing. Default 220. */
|
|
303
302
|
highlightMs?: number;
|
|
304
303
|
}
|
|
@@ -330,7 +329,7 @@ export async function selectOption(
|
|
|
330
329
|
const highlightMs = options.highlightMs ?? 220;
|
|
331
330
|
const previousOutline = el.style.outline;
|
|
332
331
|
const previousOffset = el.style.outlineOffset;
|
|
333
|
-
el.style.outline = `2px solid ${
|
|
332
|
+
el.style.outline = `2px solid ${ringColor(el, options.chat)}`;
|
|
334
333
|
el.style.outlineOffset = "2px";
|
|
335
334
|
await motionDelay(highlightMs);
|
|
336
335
|
setNativeValue(el, option.value);
|
|
@@ -340,7 +339,7 @@ export async function selectOption(
|
|
|
340
339
|
el.style.outlineOffset = previousOffset;
|
|
341
340
|
}
|
|
342
341
|
|
|
343
|
-
export interface ToggleOptions {
|
|
342
|
+
export interface ToggleOptions extends RingOwnerOptions {
|
|
344
343
|
/** Milliseconds to hold the flash. Default 200. */
|
|
345
344
|
flashMs?: number;
|
|
346
345
|
}
|
|
@@ -356,7 +355,7 @@ export async function toggleControl(
|
|
|
356
355
|
): Promise<void> {
|
|
357
356
|
const flashMs = options.flashMs ?? 200;
|
|
358
357
|
const previousShadow = el.style.boxShadow;
|
|
359
|
-
el.style.boxShadow = ringShadow(el);
|
|
358
|
+
el.style.boxShadow = ringShadow(el, options.chat);
|
|
360
359
|
await motionDelay(flashMs);
|
|
361
360
|
setNativeChecked(el, checked);
|
|
362
361
|
el.dispatchEvent(new Event("input", { bubbles: true }));
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import { HIGHLIGHT_OVERLAY_Z_INDEX } from "../constants.js";
|
|
2
|
-
|
|
3
|
-
/** The package accent, used when the host has themed nothing. */
|
|
4
|
-
const ACCENT = "#4f46e5";
|
|
2
|
+
import { ringColor } from "./ring_color.js";
|
|
5
3
|
|
|
6
4
|
/** How the overlay is drawn around the element being pointed at. */
|
|
7
5
|
export interface HighlightOverlayOptions {
|
|
@@ -9,8 +7,14 @@ export interface HighlightOverlayOptions {
|
|
|
9
7
|
readonly scrim?: boolean;
|
|
10
8
|
/** Draw the ring as a moving gradient rather than a flat colour. Default false. */
|
|
11
9
|
readonly gradient?: boolean;
|
|
12
|
-
/**
|
|
10
|
+
/**
|
|
11
|
+
* Ring colour, and the flat fallback for the gradient. Default:
|
|
12
|
+
* `--ag-ui-highlight-ring-color` on the target, then on the chat, then the
|
|
13
|
+
* target's `--ag-ui-accent`, then the chat's accent.
|
|
14
|
+
*/
|
|
13
15
|
readonly color?: string;
|
|
16
|
+
/** The chat whose ring colour and accent to use. Default: the latest connected. */
|
|
17
|
+
readonly chat?: HTMLElement;
|
|
14
18
|
/** Gap between the target's box and the ring. Default 4. */
|
|
15
19
|
readonly padding?: number;
|
|
16
20
|
/** Corner radius of the cut-out and the ring. Default: the target's own. */
|
|
@@ -205,7 +209,7 @@ function safeColor(value: string | undefined): string | null {
|
|
|
205
209
|
* the padding box, leaving the frame.
|
|
206
210
|
*/
|
|
207
211
|
function ringPaint(target: HTMLElement, options: HighlightOverlayOptions): string {
|
|
208
|
-
const color = safeColor(options.color) ??
|
|
212
|
+
const color = safeColor(options.color) ?? ringColor(target, options.chat);
|
|
209
213
|
if (options.gradient !== true) {
|
|
210
214
|
return `border-color: ${color}`;
|
|
211
215
|
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { ChatRegistration } from "../core/chat_registry.js";
|
|
2
|
+
|
|
3
|
+
/** The public ring token, read from the target and then from the chat. */
|
|
4
|
+
const RING_TOKEN = "--ag-ui-highlight-ring-color";
|
|
5
|
+
|
|
6
|
+
/** The accent a host themes its page with, read from the target. */
|
|
7
|
+
const ACCENT_TOKEN = "--ag-ui-accent";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* The accent the chat actually resolved, its theme's default included. Read
|
|
11
|
+
* through the private alias because the public token is unset on a page that
|
|
12
|
+
* themed nothing, which is exactly the page this exists for.
|
|
13
|
+
*/
|
|
14
|
+
const CHAT_ACCENT = "--_accent";
|
|
15
|
+
|
|
16
|
+
/** The package indigo, for a page with no chat and no theming at all. */
|
|
17
|
+
const PACKAGE_ACCENT = "#4f46e5";
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* The colour a highlight ring is drawn in when the caller passed none.
|
|
21
|
+
*
|
|
22
|
+
* In order: `--ag-ui-highlight-ring-color` on the target, the same token on
|
|
23
|
+
* the chat, the target's `--ag-ui-accent`, the chat's resolved accent, the
|
|
24
|
+
* package indigo. The target's accent stays above the chat's own default so a
|
|
25
|
+
* host that themes `--ag-ui-accent` on its page keeps the rings it has; the
|
|
26
|
+
* ring token sits above both so a host can keep the ring apart from the accent.
|
|
27
|
+
*
|
|
28
|
+
* `chat` defaults to the most recently connected `<ag-ui-chat>`, the common
|
|
29
|
+
* page with one widget. A page with two passes the one the ring speaks for.
|
|
30
|
+
*/
|
|
31
|
+
export function ringColor(target: HTMLElement, chat?: HTMLElement): string {
|
|
32
|
+
const onTarget = readToken(target, RING_TOKEN);
|
|
33
|
+
if (onTarget !== "") {
|
|
34
|
+
return onTarget;
|
|
35
|
+
}
|
|
36
|
+
const owner = chat ?? ChatRegistration.latest();
|
|
37
|
+
const onChat = owner === null ? "" : resolveOn(owner, RING_TOKEN);
|
|
38
|
+
if (onChat !== "") {
|
|
39
|
+
return onChat;
|
|
40
|
+
}
|
|
41
|
+
const accent = readToken(target, ACCENT_TOKEN);
|
|
42
|
+
if (accent !== "") {
|
|
43
|
+
return accent;
|
|
44
|
+
}
|
|
45
|
+
const chatAccent = owner === null ? "" : resolveOn(owner, CHAT_ACCENT);
|
|
46
|
+
return chatAccent === "" ? PACKAGE_ACCENT : chatAccent;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function readToken(el: HTMLElement, name: string): string {
|
|
50
|
+
return window.getComputedStyle(el).getPropertyValue(name).trim();
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* The custom property `name` on `chat`, as the colour it computes to there, or
|
|
55
|
+
* `""` when it is unset or not a colour.
|
|
56
|
+
*
|
|
57
|
+
* Resolved inside the chat rather than handed to the target, because the
|
|
58
|
+
* chat's colours are `light-dark()` pairs and a custom property keeps them
|
|
59
|
+
* unresolved: written onto an element in the page, the pair would pick its
|
|
60
|
+
* half by the page's colour scheme, so a dark widget on a light page would ring
|
|
61
|
+
* in its light-mode accent. A probe inside the shadow tree inherits the chat's
|
|
62
|
+
* scheme, and its computed `color` is the resolved value.
|
|
63
|
+
*
|
|
64
|
+
* An element with no open shadow root (a host passing something other than a
|
|
65
|
+
* chat) is read as it is, unresolved; that is the best it offers.
|
|
66
|
+
*/
|
|
67
|
+
function resolveOn(chat: HTMLElement, name: string): string {
|
|
68
|
+
const value = readToken(chat, name);
|
|
69
|
+
const root = chat.shadowRoot;
|
|
70
|
+
if (value === "" || root === null) {
|
|
71
|
+
return value;
|
|
72
|
+
}
|
|
73
|
+
const probe = document.createElement("span");
|
|
74
|
+
probe.hidden = true;
|
|
75
|
+
probe.style.color = value;
|
|
76
|
+
// An unparseable value leaves the declaration empty, and the probe would
|
|
77
|
+
// then report the colour it inherits instead of saying it has none.
|
|
78
|
+
if (probe.style.color === "") {
|
|
79
|
+
return "";
|
|
80
|
+
}
|
|
81
|
+
root.appendChild(probe);
|
|
82
|
+
const resolved = window.getComputedStyle(probe).color;
|
|
83
|
+
probe.remove();
|
|
84
|
+
return resolved;
|
|
85
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -17,6 +17,7 @@ export {
|
|
|
17
17
|
STATE_EVENT,
|
|
18
18
|
SUBMIT_EVENT,
|
|
19
19
|
SUGGESTIONS_ACTIVITY_TYPE,
|
|
20
|
+
THREAD_EVENT,
|
|
20
21
|
TOGGLE_EVENT,
|
|
21
22
|
TOOL_CALL_STATUS,
|
|
22
23
|
TOOL_DISPLAY,
|
|
@@ -63,6 +64,7 @@ export type { InvalidateDetail } from "./core/events/invalidate_detail.js";
|
|
|
63
64
|
export type { RunFinishedDetail } from "./core/events/run_finished_detail.js";
|
|
64
65
|
export type { StateDetail } from "./core/events/state_detail.js";
|
|
65
66
|
export type { SubmitDetail } from "./core/events/submit_detail.js";
|
|
67
|
+
export type { ThreadDetail } from "./core/events/thread_detail.js";
|
|
66
68
|
export type { ToggleDetail } from "./core/events/toggle_detail.js";
|
|
67
69
|
export type { ToolRun } from "./core/events/tool_run.js";
|
|
68
70
|
export type { UnreadDetail } from "./core/events/unread_detail.js";
|
|
@@ -89,6 +91,7 @@ export {
|
|
|
89
91
|
type PressOptions,
|
|
90
92
|
prefersReducedMotion,
|
|
91
93
|
pressThenClick,
|
|
94
|
+
type RingOwnerOptions,
|
|
92
95
|
type ScrollOptions,
|
|
93
96
|
type SelectOptions,
|
|
94
97
|
scrollIntoCenterView,
|
|
@@ -9,6 +9,7 @@ import type {
|
|
|
9
9
|
ClientConversationStore,
|
|
10
10
|
NavigationCheckpoint,
|
|
11
11
|
} from "../../core/conversation_store.js";
|
|
12
|
+
import type { ThreadDetail } from "../../core/events/thread_detail.js";
|
|
12
13
|
import type { MessageRole } from "../../core/message_role.js";
|
|
13
14
|
import { RunIndex } from "../../core/run_index.js";
|
|
14
15
|
import { toolStatusFromOutcome } from "../../core/tool_outcome.js";
|
|
@@ -119,6 +120,11 @@ export interface ConversationHistoryHost {
|
|
|
119
120
|
readonly resetState: () => void;
|
|
120
121
|
/** Swap the composer between Send and Stop, which the composer owns. */
|
|
121
122
|
readonly setRunning: (running: boolean) => void;
|
|
123
|
+
/**
|
|
124
|
+
* Say the conversation on screen changed, once its transcript is replaced.
|
|
125
|
+
* The element fills in the thread it replaced, which it last reported.
|
|
126
|
+
*/
|
|
127
|
+
readonly threadChanged: (threadId: string, reason: ThreadDetail["reason"]) => void;
|
|
122
128
|
}
|
|
123
129
|
|
|
124
130
|
/**
|
|
@@ -335,7 +341,7 @@ export class ConversationHistory {
|
|
|
335
341
|
this.#host.conversationStore().setActiveThread(threadId);
|
|
336
342
|
this.#threadId = threadId;
|
|
337
343
|
this.#host.setRunning(false);
|
|
338
|
-
await this.rehydrate();
|
|
344
|
+
await this.rehydrate("switch");
|
|
339
345
|
}
|
|
340
346
|
|
|
341
347
|
/** Rename a thread, then reload the list that shows its title. */
|
|
@@ -355,6 +361,8 @@ export class ConversationHistory {
|
|
|
355
361
|
this.#host.resetState();
|
|
356
362
|
this.adoptActiveThread();
|
|
357
363
|
this.#host.setRunning(false);
|
|
364
|
+
// No replay to wait for: the thread that took its place is a fresh one.
|
|
365
|
+
this.#host.threadChanged(this.#threadId, "delete");
|
|
358
366
|
}
|
|
359
367
|
void this.refreshDrawer();
|
|
360
368
|
}
|
|
@@ -628,8 +636,11 @@ export class ConversationHistory {
|
|
|
628
636
|
* for the run it would have resumed -- unless what replaced it is a restore
|
|
629
637
|
* of the same conversation, still current, which answers for that run
|
|
630
638
|
* instead; see `#forgetStopped`.
|
|
639
|
+
*
|
|
640
|
+
* `reason` is what the thread event this restore ends with reports as why
|
|
641
|
+
* the conversation changed. A restore that is stopped reports nothing.
|
|
631
642
|
*/
|
|
632
|
-
async rehydrate(): Promise<void> {
|
|
643
|
+
async rehydrate(reason: ThreadDetail["reason"]): Promise<void> {
|
|
633
644
|
// Claimed before awaiting, and checked by `#restoring` wherever the restore
|
|
634
645
|
// has waited or run host code. With a slow remote store, picking thread B
|
|
635
646
|
// then C interleaved both replays into one transcript, and New chat drew
|
|
@@ -727,11 +738,24 @@ export class ConversationHistory {
|
|
|
727
738
|
}
|
|
728
739
|
}
|
|
729
740
|
}
|
|
741
|
+
if (checkpoint === null) {
|
|
742
|
+
this.#noticeIfRunUnfinished(messages);
|
|
743
|
+
}
|
|
744
|
+
// Said once the transcript is the restored one, and only by a restore
|
|
745
|
+
// still current: one that was stopped returned above, so two quick
|
|
746
|
+
// switches report the second alone. Before a resume, because the resume
|
|
747
|
+
// is a run, and waiting for it reported the change a whole answer late.
|
|
748
|
+
this.#host.threadChanged(threadId, reason);
|
|
730
749
|
if (checkpoint !== null) {
|
|
750
|
+
// A listener is host code, and can stop the restore as a replayed
|
|
751
|
+
// renderer can. Resuming then answered the call into the conversation
|
|
752
|
+
// that replaced it, and left the checkpoint for a later visit to resume.
|
|
753
|
+
if (!this.#restoring(generation)) {
|
|
754
|
+
this.#forgetStopped(store, threadId, checkpoint);
|
|
755
|
+
return;
|
|
756
|
+
}
|
|
731
757
|
await this.#resumeFrom(checkpoint, generation);
|
|
732
|
-
return;
|
|
733
758
|
}
|
|
734
|
-
this.#noticeIfRunUnfinished(messages);
|
|
735
759
|
} finally {
|
|
736
760
|
// The resumed run starting has let go of the hold already, as a send's
|
|
737
761
|
// does, and a request that then fails still started one: the client
|
|
@@ -4,8 +4,8 @@ export const EMBEDDED_STYLES: string = `
|
|
|
4
4
|
the widget lives in the host's own layout (fixes overlay/z-index clashes). */
|
|
5
5
|
:host([placement="embedded"]) {
|
|
6
6
|
--_position: var(--ag-ui-position, static);
|
|
7
|
-
--_width: var(--ag-ui-width, 100%);
|
|
8
|
-
--_height: var(--ag-ui-height, 100%);
|
|
7
|
+
--_width: var(--_drag-width, var(--ag-ui-width, 100%));
|
|
8
|
+
--_height: var(--_drag-height, var(--ag-ui-height, 100%));
|
|
9
9
|
--_max-width: var(--ag-ui-max-width, 100%);
|
|
10
10
|
--_max-height: var(--ag-ui-max-height, 100%);
|
|
11
11
|
--_shadow: var(--ag-ui-shadow, none);
|
|
@@ -27,6 +27,17 @@ import { surfaceFor } from "./surface_for.js";
|
|
|
27
27
|
/** Per-tab persistence key for a dragged panel size. */
|
|
28
28
|
const SIZE_KEY = "ag-ui-chat:size";
|
|
29
29
|
|
|
30
|
+
/**
|
|
31
|
+
* The private properties a dragged size lives in. They are not the public
|
|
32
|
+
* `--ag-ui-width` / `--ag-ui-height` tokens, which belong to the host: an
|
|
33
|
+
* inline write there outranked every host rule and was read back by the
|
|
34
|
+
* small-viewport block, so a size dragged in a wide window leaked into the
|
|
35
|
+
* phone shape. The placement rules read these before the host token; the
|
|
36
|
+
* small-viewport block does not read them at all.
|
|
37
|
+
*/
|
|
38
|
+
const DRAG_WIDTH = "--_drag-width";
|
|
39
|
+
const DRAG_HEIGHT = "--_drag-height";
|
|
40
|
+
|
|
30
41
|
/** Per-tab persistence key for a dragged launcher position. */
|
|
31
42
|
const LAUNCHER_KEY = "ag-ui-chat:launcher";
|
|
32
43
|
|
|
@@ -189,6 +200,20 @@ export class PanelPlacement {
|
|
|
189
200
|
this.#applySize(this.#readSize());
|
|
190
201
|
}
|
|
191
202
|
|
|
203
|
+
/**
|
|
204
|
+
* Drop the dragged size and its stored copy, so the placement's own size and
|
|
205
|
+
* the host's tokens apply again. For a host entering a sized state of its
|
|
206
|
+
* own, which a user's drag would otherwise outrank.
|
|
207
|
+
*/
|
|
208
|
+
resetSize(): void {
|
|
209
|
+
this.#host.element.style.removeProperty(DRAG_WIDTH);
|
|
210
|
+
this.#host.element.style.removeProperty(DRAG_HEIGHT);
|
|
211
|
+
this.#host.clearPreference(SIZE_KEY);
|
|
212
|
+
// The anchor was measured against the dragged box; the resting one may
|
|
213
|
+
// pin different edges.
|
|
214
|
+
this.syncResizeAnchor();
|
|
215
|
+
}
|
|
216
|
+
|
|
192
217
|
/**
|
|
193
218
|
* A zero-sized box carrying the host's viewport insets as padding, so they
|
|
194
219
|
* can be read back as used pixel lengths. See the `.viewport-probe` rule for
|
|
@@ -414,8 +439,8 @@ export class PanelPlacement {
|
|
|
414
439
|
*/
|
|
415
440
|
#measureAnchor(): ResizeAnchor {
|
|
416
441
|
const before = this.#host.element.getBoundingClientRect();
|
|
417
|
-
const width = this.#host.element.style.getPropertyValue(
|
|
418
|
-
const height = this.#host.element.style.getPropertyValue(
|
|
442
|
+
const width = this.#host.element.style.getPropertyValue(DRAG_WIDTH);
|
|
443
|
+
const height = this.#host.element.style.getPropertyValue(DRAG_HEIGHT);
|
|
419
444
|
// Shrink first. Growing is the obvious probe and cannot answer the question
|
|
420
445
|
// at a size already resting against max-width or max-height: the box does
|
|
421
446
|
// not change, no edge moves, and every clamped axis then reads as pinned on
|
|
@@ -431,8 +456,8 @@ export class PanelPlacement {
|
|
|
431
456
|
const grown = shrunk.x === null || shrunk.y === null ? this.#probeAnchor(before, 1) : shrunk;
|
|
432
457
|
// Restore exactly what was there, including "nothing" — leaving a probe
|
|
433
458
|
// value behind would pin a panel that had been sizing itself.
|
|
434
|
-
this.#restoreProperty(
|
|
435
|
-
this.#restoreProperty(
|
|
459
|
+
this.#restoreProperty(DRAG_WIDTH, width);
|
|
460
|
+
this.#restoreProperty(DRAG_HEIGHT, height);
|
|
436
461
|
return {
|
|
437
462
|
// Neither direction moved it: the axis cannot be resized at all, so the
|
|
438
463
|
// floating default is the best answer available and is the one the
|
|
@@ -497,7 +522,7 @@ export class PanelPlacement {
|
|
|
497
522
|
/**
|
|
498
523
|
* Write a dragged size onto the host, on the axes this placement leaves free.
|
|
499
524
|
*
|
|
500
|
-
* Writing
|
|
525
|
+
* Writing a custom property rather than inline `width` / `height` does not
|
|
501
526
|
* by itself leave placement in charge: an inline custom property still
|
|
502
527
|
* outranks a `:host([placement=…])` rule setting the same property, so a
|
|
503
528
|
* height dragged while floating would cap a docked sidebar asking for
|
|
@@ -518,10 +543,10 @@ export class PanelPlacement {
|
|
|
518
543
|
// symmetry nobody has asked for. The limit that matters, staying inside
|
|
519
544
|
// what the host left free, is enforced above.
|
|
520
545
|
if (size.width !== undefined) {
|
|
521
|
-
this.#host.element.style.setProperty(
|
|
546
|
+
this.#host.element.style.setProperty(DRAG_WIDTH, `${size.width}px`);
|
|
522
547
|
}
|
|
523
548
|
if (size.height !== undefined && axis === "both") {
|
|
524
|
-
this.#host.element.style.setProperty(
|
|
549
|
+
this.#host.element.style.setProperty(DRAG_HEIGHT, `${size.height}px`);
|
|
525
550
|
}
|
|
526
551
|
}
|
|
527
552
|
|
|
@@ -535,10 +560,10 @@ export class PanelPlacement {
|
|
|
535
560
|
releaseOwnedAxes(): void {
|
|
536
561
|
const axis = this.#resizeAxis();
|
|
537
562
|
if (axis !== "both") {
|
|
538
|
-
this.#host.element.style.removeProperty(
|
|
563
|
+
this.#host.element.style.removeProperty(DRAG_HEIGHT);
|
|
539
564
|
}
|
|
540
565
|
if (axis === "none") {
|
|
541
|
-
this.#host.element.style.removeProperty(
|
|
566
|
+
this.#host.element.style.removeProperty(DRAG_WIDTH);
|
|
542
567
|
}
|
|
543
568
|
}
|
|
544
569
|
|
|
@@ -12,7 +12,7 @@ export const PRESET_STYLES: string = `
|
|
|
12
12
|
|
|
13
13
|
:host([placement="side"]) {
|
|
14
14
|
--_inset: var(--ag-ui-inset, calc(var(--_viewport-inset-top) + var(--_keyboard-inset-top)) var(--_viewport-inset-right) var(--_viewport-inset-bottom) auto);
|
|
15
|
-
--_width: var(--ag-ui-width, 420px);
|
|
15
|
+
--_width: var(--_drag-width, var(--ag-ui-width, 420px));
|
|
16
16
|
--_height: var(--ag-ui-height, var(--_viewport-height));
|
|
17
17
|
--_max-height: var(--ag-ui-max-height, var(--_viewport-height));
|
|
18
18
|
--_panel-radius: var(--ag-ui-panel-radius, 0);
|
|
@@ -7,7 +7,7 @@ export const SIDEBAR_STYLES: string = `
|
|
|
7
7
|
host-managed push instead. */
|
|
8
8
|
:host([placement="sidebar"]) {
|
|
9
9
|
--_inset: var(--ag-ui-inset, calc(var(--_viewport-inset-top) + var(--_keyboard-inset-top)) var(--_viewport-inset-right) var(--_viewport-inset-bottom) auto);
|
|
10
|
-
--_width: var(--ag-ui-width, 420px);
|
|
10
|
+
--_width: var(--_drag-width, var(--ag-ui-width, 420px));
|
|
11
11
|
--_height: var(--ag-ui-height, var(--_viewport-height));
|
|
12
12
|
--_max-height: var(--ag-ui-max-height, var(--_viewport-height));
|
|
13
13
|
--_panel-radius: var(--ag-ui-panel-radius, 0);
|
|
@@ -17,6 +17,11 @@ export const SMALL_VIEWPORT_STYLES: string = `
|
|
|
17
17
|
fine-pointered and small, and conflating the two gets both wrong. What the
|
|
18
18
|
pointer decides is which controls make sense, further down.
|
|
19
19
|
|
|
20
|
+
The size the user dragged is deliberately not read here. The desktop
|
|
21
|
+
placements read it before the host token, and this block restates the sizes
|
|
22
|
+
without it, so a persisted or freshly dragged size cannot change the shape
|
|
23
|
+
of a phone, and a window narrowed after a drag goes full-bleed.
|
|
24
|
+
|
|
20
25
|
Two placements are left alone. "page" is already this shape. "embedded" sits
|
|
21
26
|
in a box the host sized and placed, and taking that over would break the app
|
|
22
27
|
shell it was embedded into -- the host is the only party that knows whether
|
|
@@ -197,9 +197,7 @@ export class ToolCallCard {
|
|
|
197
197
|
|
|
198
198
|
this.element = document.createElement("div");
|
|
199
199
|
this.element.className = "tool-call";
|
|
200
|
-
this.element.setAttribute("part", "tool-card");
|
|
201
200
|
this.element.setAttribute("data-tool-name", name);
|
|
202
|
-
this.element.setAttribute("data-status", TOOL_CALL_STATUS.PENDING);
|
|
203
201
|
this.element.setAttribute("data-expanded", "false");
|
|
204
202
|
|
|
205
203
|
const head = document.createElement("div");
|
|
@@ -221,8 +219,7 @@ export class ToolCallCard {
|
|
|
221
219
|
|
|
222
220
|
this.#status = document.createElement("span");
|
|
223
221
|
this.#status.className = "tool-call-status";
|
|
224
|
-
this.#
|
|
225
|
-
this.#status.textContent = statusLabels(strings)[TOOL_CALL_STATUS.PENDING];
|
|
222
|
+
this.#setStatus(TOOL_CALL_STATUS.PENDING);
|
|
226
223
|
|
|
227
224
|
this.#decision = document.createElement("span");
|
|
228
225
|
this.#decision.className = "tool-call-decision";
|
|
@@ -283,8 +280,7 @@ export class ToolCallCard {
|
|
|
283
280
|
if (this.#settled) {
|
|
284
281
|
return;
|
|
285
282
|
}
|
|
286
|
-
this
|
|
287
|
-
this.#status.textContent = statusLabels(this.#strings)[status];
|
|
283
|
+
this.#setStatus(status);
|
|
288
284
|
}
|
|
289
285
|
|
|
290
286
|
/**
|
|
@@ -326,8 +322,7 @@ export class ToolCallCard {
|
|
|
326
322
|
return;
|
|
327
323
|
}
|
|
328
324
|
this.#settled = true;
|
|
329
|
-
this
|
|
330
|
-
this.#status.textContent = statusLabels(this.#strings)[status];
|
|
325
|
+
this.#setStatus(status);
|
|
331
326
|
this.#resultLabel.textContent = resultLabels(this.#strings)[status];
|
|
332
327
|
this.#renderPayload(
|
|
333
328
|
this.#resultBody,
|
|
@@ -337,6 +332,21 @@ export class ToolCallCard {
|
|
|
337
332
|
this.#resultSection.hidden = false;
|
|
338
333
|
}
|
|
339
334
|
|
|
335
|
+
/**
|
|
336
|
+
* Write the status everywhere it is read from, in one place so they cannot
|
|
337
|
+
* disagree: `data-status` for the shadow CSS, the pill's text, and the part
|
|
338
|
+
* lists. `::part()` takes no attribute selector, so the card and its pill also
|
|
339
|
+
* name the status as a part (`tool-card-error`, `tool-card-status-error`), the
|
|
340
|
+
* modifier convention a run notice uses, and a host styles failures alone.
|
|
341
|
+
* Setting the whole list replaces the previous modifier.
|
|
342
|
+
*/
|
|
343
|
+
#setStatus(status: ToolCallStatus): void {
|
|
344
|
+
this.element.setAttribute("data-status", status);
|
|
345
|
+
this.element.setAttribute("part", `tool-card tool-card-${status}`);
|
|
346
|
+
this.#status.setAttribute("part", `tool-card-status tool-card-status-${status}`);
|
|
347
|
+
this.#status.textContent = statusLabels(this.#strings)[status];
|
|
348
|
+
}
|
|
349
|
+
|
|
340
350
|
/**
|
|
341
351
|
* Fill one body region, offering it to the host formatter first.
|
|
342
352
|
*
|
|
@@ -225,6 +225,9 @@ export class ToolStepGroup {
|
|
|
225
225
|
state = "error";
|
|
226
226
|
}
|
|
227
227
|
this.element.setAttribute("data-state", state);
|
|
228
|
+
// The state is also a part, because `::part()` takes no attribute selector;
|
|
229
|
+
// the whole list is rewritten so the old modifier goes.
|
|
230
|
+
this.element.setAttribute("part", `tool-group tool-group-${state}`);
|
|
228
231
|
this.element.setAttribute("data-count", String(total));
|
|
229
232
|
|
|
230
233
|
const only = total === 1 ? steps[0] : undefined;
|
|
@@ -300,8 +300,8 @@ export const TOKEN_STYLES: string = `
|
|
|
300
300
|
grid/flex layout) to embed it in the page flow instead of floating. */
|
|
301
301
|
--_position: var(--ag-ui-position, fixed);
|
|
302
302
|
--_z-index: var(--ag-ui-z-index, 2147483000);
|
|
303
|
-
--_width: var(--ag-ui-width, 380px);
|
|
304
|
-
--_height: var(--ag-ui-height, 560px);
|
|
303
|
+
--_width: var(--_drag-width, var(--ag-ui-width, 380px));
|
|
304
|
+
--_height: var(--_drag-height, var(--ag-ui-height, 560px));
|
|
305
305
|
/* The gutter a resting floating panel keeps between itself and the edge of
|
|
306
306
|
the box the host left free. One number, because the inset below spends it
|
|
307
307
|
and the cap beneath has to know it was spent. */
|
package/src/version.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const VERSION: string = "0.
|
|
1
|
+
export const VERSION: string = "0.47.0";
|