@artooi/ag-ui-web-component 0.38.0 → 0.40.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 +515 -1
- package/README.md +232 -56
- package/dist/ag-ui-web-component.bundle.js +370 -139
- package/dist/ag-ui-web-component.bundle.js.map +4 -4
- package/dist/constants.d.ts +17 -14
- package/dist/constants.d.ts.map +1 -1
- package/dist/core/activity_registry.d.ts +46 -0
- package/dist/core/activity_registry.d.ts.map +1 -0
- package/dist/core/ag_ui_chat.d.ts +23 -10
- package/dist/core/ag_ui_chat.d.ts.map +1 -1
- package/dist/core/agui_client.d.ts +28 -0
- package/dist/core/agui_client.d.ts.map +1 -1
- package/dist/core/client_seed.d.ts +27 -0
- package/dist/core/client_seed.d.ts.map +1 -0
- package/dist/core/read_max_tool_rounds.d.ts +9 -0
- package/dist/core/read_max_tool_rounds.d.ts.map +1 -0
- package/dist/core/run_handlers.d.ts +80 -0
- package/dist/core/run_handlers.d.ts.map +1 -0
- package/dist/core/storage_scope.d.ts +110 -0
- package/dist/core/storage_scope.d.ts.map +1 -0
- package/dist/core/tool_outcome.d.ts +2 -3
- package/dist/core/tool_outcome.d.ts.map +1 -1
- package/dist/core/utils.d.ts +46 -0
- package/dist/core/utils.d.ts.map +1 -1
- package/dist/index.js +16268 -14747
- package/dist/index.js.map +4 -4
- package/dist/skills/skill_catalog.d.ts +67 -0
- package/dist/skills/skill_catalog.d.ts.map +1 -0
- package/dist/skills/skill_name_from.d.ts +12 -0
- package/dist/skills/skill_name_from.d.ts.map +1 -0
- package/dist/tools/tool_catalog.d.ts +79 -0
- package/dist/tools/tool_catalog.d.ts.map +1 -0
- package/dist/tools/tool_dispatch.d.ts +104 -0
- package/dist/tools/tool_dispatch.d.ts.map +1 -0
- package/dist/ui/composer/attachment_tray.d.ts.map +1 -1
- package/dist/ui/composer/auto_grow.d.ts +10 -0
- package/dist/ui/composer/auto_grow.d.ts.map +1 -0
- package/dist/ui/composer/composer_attachments.d.ts +57 -0
- package/dist/ui/composer/composer_attachments.d.ts.map +1 -0
- package/dist/ui/composer/composer_voice.d.ts +48 -0
- package/dist/ui/composer/composer_voice.d.ts.map +1 -0
- package/dist/ui/composer/voice_input.d.ts.map +1 -1
- package/dist/ui/excerpts/transcript_quote_offer.d.ts +57 -0
- package/dist/ui/excerpts/transcript_quote_offer.d.ts.map +1 -0
- package/dist/ui/fill_ui_string.d.ts +32 -0
- package/dist/ui/fill_ui_string.d.ts.map +1 -0
- package/dist/ui/history/conversation_history.d.ts +203 -0
- package/dist/ui/history/conversation_history.d.ts.map +1 -0
- package/dist/ui/history/relative_time.d.ts.map +1 -1
- package/dist/ui/history/thread_drawer.d.ts +0 -14
- package/dist/ui/history/thread_drawer.d.ts.map +1 -1
- package/dist/ui/interrupts/confirmation_card.d.ts.map +1 -1
- package/dist/ui/interrupts/pending_decision.d.ts +23 -0
- package/dist/ui/interrupts/pending_decision.d.ts.map +1 -0
- package/dist/ui/placement/is_collapsible_placement.d.ts +18 -0
- package/dist/ui/placement/is_collapsible_placement.d.ts.map +1 -0
- package/dist/ui/placement/is_draggable_placement.d.ts +9 -0
- package/dist/ui/placement/is_draggable_placement.d.ts.map +1 -0
- package/dist/ui/placement/launcher_drag.d.ts +6 -0
- package/dist/ui/placement/launcher_drag.d.ts.map +1 -1
- package/dist/ui/placement/panel_placement.d.ts +119 -0
- package/dist/ui/placement/panel_placement.d.ts.map +1 -0
- package/dist/ui/progress/run_announcer.d.ts +41 -0
- package/dist/ui/progress/run_announcer.d.ts.map +1 -0
- package/dist/ui/progress/subagent_progress.d.ts +58 -0
- package/dist/ui/progress/subagent_progress.d.ts.map +1 -0
- package/dist/ui/progress/tool_call_card.d.ts.map +1 -1
- package/dist/ui/shell/adopt_styles.d.ts +23 -0
- package/dist/ui/shell/adopt_styles.d.ts.map +1 -0
- package/dist/ui/shell/glyph_slot.d.ts +7 -0
- package/dist/ui/shell/glyph_slot.d.ts.map +1 -0
- package/dist/ui/shell/header_button.d.ts +10 -0
- package/dist/ui/shell/header_button.d.ts.map +1 -0
- package/dist/ui/shell/icon_element.d.ts +12 -0
- package/dist/ui/shell/icon_element.d.ts.map +1 -0
- package/dist/ui/shell/is_unread_badge_enabled.d.ts +10 -0
- package/dist/ui/shell/is_unread_badge_enabled.d.ts.map +1 -0
- package/dist/ui/shell/read_launcher_icon_url.d.ts +8 -0
- package/dist/ui/shell/read_launcher_icon_url.d.ts.map +1 -0
- package/dist/ui/styles.d.ts +1 -1
- package/dist/ui/styles.d.ts.map +1 -1
- package/dist/ui/transcript/answer_actions.d.ts +39 -0
- package/dist/ui/transcript/answer_actions.d.ts.map +1 -0
- package/dist/ui/transcript/answer_stream.d.ts +56 -0
- package/dist/ui/transcript/answer_stream.d.ts.map +1 -0
- package/dist/ui/transcript/render_or_warn.d.ts +16 -0
- package/dist/ui/transcript/render_or_warn.d.ts.map +1 -0
- package/dist/ui/transcript/starter_chips.d.ts +21 -0
- package/dist/ui/transcript/starter_chips.d.ts.map +1 -0
- package/dist/ui/transcript/transcript.d.ts +171 -0
- package/dist/ui/transcript/transcript.d.ts.map +1 -0
- package/dist/ui/ui_strings.d.ts +29 -1
- package/dist/ui/ui_strings.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/constants.ts +17 -15
- package/src/core/activity_registry.ts +133 -0
- package/src/core/ag_ui_chat.ts +945 -4419
- package/src/core/agui_client.ts +172 -16
- package/src/core/client_seed.ts +27 -0
- package/src/core/read_max_tool_rounds.ts +13 -0
- package/src/core/remote_conversation_store.ts +1 -1
- package/src/core/run_handlers.ts +501 -0
- package/src/core/storage_scope.ts +279 -0
- package/src/core/tool_outcome.ts +5 -3
- package/src/core/utils.ts +109 -0
- package/src/skills/skill_catalog.ts +166 -0
- package/src/skills/skill_name_from.ts +19 -0
- package/src/tools/tool_catalog.ts +354 -0
- package/src/tools/tool_dispatch.ts +521 -0
- package/src/ui/composer/attachment_tray.ts +6 -5
- package/src/ui/composer/auto_grow.ts +12 -0
- package/src/ui/composer/composer_attachments.ts +324 -0
- package/src/ui/composer/composer_voice.ts +93 -0
- package/src/ui/composer/voice_input.ts +4 -4
- package/src/ui/excerpts/transcript_quote_offer.ts +198 -0
- package/src/ui/fill_ui_string.ts +41 -0
- package/src/ui/history/conversation_history.ts +699 -0
- package/src/ui/history/relative_time.ts +5 -4
- package/src/ui/history/thread_drawer.ts +3 -50
- package/src/ui/interrupts/confirmation_card.ts +4 -2
- package/src/ui/interrupts/pending_decision.ts +34 -0
- package/src/ui/placement/is_collapsible_placement.ts +19 -0
- package/src/ui/placement/is_draggable_placement.ts +23 -0
- package/src/ui/placement/launcher_drag.ts +104 -89
- package/src/ui/placement/panel_placement.ts +1130 -0
- package/src/ui/progress/run_announcer.ts +67 -0
- package/src/ui/progress/subagent_progress.ts +201 -0
- package/src/ui/progress/tool_call_card.ts +2 -0
- package/src/ui/shell/adopt_styles.ts +28 -0
- package/src/ui/shell/glyph_slot.ts +12 -0
- package/src/ui/shell/header_button.ts +21 -0
- package/src/ui/shell/icon_element.ts +33 -0
- package/src/ui/shell/is_unread_badge_enabled.ts +11 -0
- package/src/ui/shell/read_launcher_icon_url.ts +9 -0
- package/src/ui/styles.ts +301 -70
- package/src/ui/transcript/answer_actions.ts +139 -0
- package/src/ui/transcript/answer_stream.ts +123 -0
- package/src/ui/transcript/render_or_warn.ts +22 -0
- package/src/ui/transcript/starter_chips.ts +42 -0
- package/src/ui/transcript/transcript.ts +429 -0
- package/src/ui/ui_strings.ts +38 -1
- package/src/version.ts +1 -1
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { FEEDBACK_EVENT, ICON_RETRY, MESSAGE_ACTIONS } from "../../constants.js";
|
|
2
|
+
import type { FeedbackDetail } from "../../core/events/feedback_detail.js";
|
|
3
|
+
import { commaTokens } from "../../core/utils.js";
|
|
4
|
+
import { copyPayload } from "../excerpts/copy_payload.js";
|
|
5
|
+
import type { UiStrings } from "../ui_strings.js";
|
|
6
|
+
import { attachMessageActions, messageActionBar, messageActionButton } from "./message_actions.js";
|
|
7
|
+
|
|
8
|
+
/** What the answer actions need from the element that owns them. */
|
|
9
|
+
export interface AnswerActionsHost {
|
|
10
|
+
/** The custom element: its `data-message-actions`, and where feedback is dispatched. */
|
|
11
|
+
readonly element: HTMLElement;
|
|
12
|
+
/** The resolved string table. */
|
|
13
|
+
readonly strings: () => UiStrings;
|
|
14
|
+
/** Retry the last turn, as the element's public `retryLastTurn` does. */
|
|
15
|
+
readonly retry: () => void;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* The action row under a finished answer -- copy, retry, feedback -- and the
|
|
20
|
+
* one row currently holding Retry.
|
|
21
|
+
*
|
|
22
|
+
* Owned one-to-one by an `<ag-ui-chat>`, and holding no state outside the
|
|
23
|
+
* instance.
|
|
24
|
+
*/
|
|
25
|
+
export class AnswerActions {
|
|
26
|
+
readonly #host: AnswerActionsHost;
|
|
27
|
+
/**
|
|
28
|
+
* The one action row currently carrying Retry, if any.
|
|
29
|
+
*
|
|
30
|
+
* Retry belongs to the **last** turn only: re-running an older one is
|
|
31
|
+
* branching, and for a page-driving agent editing a past turn is not neutral
|
|
32
|
+
* -- those turns clicked buttons, and re-running turn 3 does not un-save what
|
|
33
|
+
* turn 5 saved. Holding a single owner is what keeps exactly one offer on
|
|
34
|
+
* screen without per-bubble bookkeeping.
|
|
35
|
+
*/
|
|
36
|
+
#retryOwner: HTMLElement | null = null;
|
|
37
|
+
|
|
38
|
+
constructor(host: AnswerActionsHost) {
|
|
39
|
+
this.#host = host;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Forget the row holding Retry, with the transcript it was in. */
|
|
43
|
+
forget(): void {
|
|
44
|
+
this.#retryOwner = null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Give a finished assistant bubble its action row, and hand it Retry.
|
|
49
|
+
*
|
|
50
|
+
* Every finished bubble gets copy and feedback -- both are safe on a message
|
|
51
|
+
* of any age. Retry moves to the newest, because it is the only one where
|
|
52
|
+
* re-running answers the same question rather than rewriting history.
|
|
53
|
+
*
|
|
54
|
+
* `data-message-actions` subtracts from that. The row is built only when
|
|
55
|
+
* something survives to go in it: an empty row still takes its margin, still
|
|
56
|
+
* answers to the `message-actions` part, and still reads to a screen reader
|
|
57
|
+
* as a group of actions with none in it.
|
|
58
|
+
*/
|
|
59
|
+
attach(bubble: HTMLDivElement, options: { rateable?: boolean } = {}): void {
|
|
60
|
+
const enabled = this.#enabled();
|
|
61
|
+
const copyable = enabled.has(MESSAGE_ACTIONS.COPY);
|
|
62
|
+
// A failed run is copyable -- error text is what people paste into a bug
|
|
63
|
+
// report -- but not rateable: a rating is a statement about an *answer*,
|
|
64
|
+
// and mixing "the connection dropped" into that signal makes the host's
|
|
65
|
+
// feedback data say less than it did before.
|
|
66
|
+
const rateable = options.rateable !== false && enabled.has(MESSAGE_ACTIONS.FEEDBACK);
|
|
67
|
+
if (copyable || rateable) {
|
|
68
|
+
attachMessageActions(bubble, {
|
|
69
|
+
strings: this.#host.strings(),
|
|
70
|
+
// Read at click time, not captured: a bubble rendered from markdown
|
|
71
|
+
// holds its text in the DOM, and that is what the user sees and means
|
|
72
|
+
// to copy. Serialised rather than read off `textContent`, which welds
|
|
73
|
+
// a table into one run of digits and picks up the code blocks' own
|
|
74
|
+
// copy buttons on the way past.
|
|
75
|
+
...(copyable
|
|
76
|
+
? {
|
|
77
|
+
text: () => copyPayload(bubble).text,
|
|
78
|
+
html: () => copyPayload(bubble).html,
|
|
79
|
+
}
|
|
80
|
+
: {}),
|
|
81
|
+
...(rateable
|
|
82
|
+
? {
|
|
83
|
+
onFeedback: (rating: "up" | "down") => {
|
|
84
|
+
this.#host.element.dispatchEvent(
|
|
85
|
+
new CustomEvent<FeedbackDetail>(FEEDBACK_EVENT, {
|
|
86
|
+
detail: { content: copyPayload(bubble).text, rating },
|
|
87
|
+
bubbles: true,
|
|
88
|
+
composed: true,
|
|
89
|
+
}),
|
|
90
|
+
);
|
|
91
|
+
},
|
|
92
|
+
}
|
|
93
|
+
: {}),
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
if (enabled.has(MESSAGE_ACTIONS.RETRY)) {
|
|
97
|
+
this.#moveRetryTo(messageActionBar(bubble, this.#host.strings()));
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** Move the Retry button onto `bar`, taking it off whoever held it. */
|
|
102
|
+
#moveRetryTo(bar: HTMLElement): void {
|
|
103
|
+
this.#retryOwner?.querySelector(".message-action--retry")?.remove();
|
|
104
|
+
const retry = messageActionButton("retry", this.#host.strings().retryMessage, ICON_RETRY);
|
|
105
|
+
retry.addEventListener("click", () => {
|
|
106
|
+
this.#host.retry();
|
|
107
|
+
});
|
|
108
|
+
// First in the row: it is the action a reader reaches for when the answer
|
|
109
|
+
// was wrong, which is when they are least inclined to hunt for a control.
|
|
110
|
+
bar.prepend(retry);
|
|
111
|
+
this.#retryOwner = bar;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Which message actions a finished bubble offers, from
|
|
116
|
+
* `data-message-actions`.
|
|
117
|
+
*
|
|
118
|
+
* **Absent means copy and retry, not all three.** Those two work with nothing
|
|
119
|
+
* wired: copy reads the DOM, retry drives this element. The rating pair does
|
|
120
|
+
* not -- it fires `ag-ui-feedback` and stores nothing by design, because a
|
|
121
|
+
* rating belongs to whatever the host already uses for product signal. With no
|
|
122
|
+
* listener the buttons still latch `aria-pressed`, so a reader is told their
|
|
123
|
+
* rating was taken and a screen reader announces it, while nothing recorded
|
|
124
|
+
* anything. This README has always said two buttons that lead nowhere are
|
|
125
|
+
* worse than none; shipping them by default was that sentence being false.
|
|
126
|
+
*
|
|
127
|
+
* A host with a listener asks for them: `data-message-actions="copy,retry,feedback"`.
|
|
128
|
+
* A value names the survivors, which makes `data-message-actions="false"` --
|
|
129
|
+
* the spelling its sibling `data-quote-selection` uses -- an empty set by
|
|
130
|
+
* falling out of the same rule rather than by a case of its own.
|
|
131
|
+
*/
|
|
132
|
+
#enabled(): ReadonlySet<string> {
|
|
133
|
+
const attr = this.#host.element.getAttribute("data-message-actions");
|
|
134
|
+
if (attr === null) {
|
|
135
|
+
return new Set([MESSAGE_ACTIONS.COPY, MESSAGE_ACTIONS.RETRY]);
|
|
136
|
+
}
|
|
137
|
+
return new Set(commaTokens(attr));
|
|
138
|
+
}
|
|
139
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { renderMarkdown } from "./render_markdown.js";
|
|
2
|
+
|
|
3
|
+
/** What the streamed answer needs from the element that owns it. */
|
|
4
|
+
export interface AnswerStreamHost {
|
|
5
|
+
/** Append an empty assistant bubble to the transcript, and return it. */
|
|
6
|
+
readonly openBubble: () => HTMLDivElement;
|
|
7
|
+
/** The element's `allowImages`, read per render. */
|
|
8
|
+
readonly allowImages: () => boolean;
|
|
9
|
+
/** Keep the transcript at its foot, if the reader is there. */
|
|
10
|
+
readonly follow: () => void;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* The assistant answer currently streaming: the bubble it streams into, the
|
|
15
|
+
* text the next frame will draw, and how many deltas it arrived in.
|
|
16
|
+
*
|
|
17
|
+
* Owned one-to-one by an `<ag-ui-chat>`, and holding no state outside the
|
|
18
|
+
* instance.
|
|
19
|
+
*/
|
|
20
|
+
export class AnswerStream {
|
|
21
|
+
readonly #host: AnswerStreamHost;
|
|
22
|
+
#streamingBubble: HTMLDivElement | null = null;
|
|
23
|
+
// Text deltas applied to the current streaming bubble. >1 ⇒ the message
|
|
24
|
+
// revealed progressively as it streamed, so the word reveal must not re-animate
|
|
25
|
+
// it; ≤1 ⇒ it arrived at once and the word reveal is appropriate.
|
|
26
|
+
#streamDeltas = 0;
|
|
27
|
+
// The accumulated answer the next render will draw. Deltas overwrite it
|
|
28
|
+
// (each one carries the whole answer), so a frame always draws the latest.
|
|
29
|
+
#streamBuffer = "";
|
|
30
|
+
// The frame that render is queued on, or `null` when nothing is queued —
|
|
31
|
+
// also the flag saying a delta is still undrawn.
|
|
32
|
+
#streamFrame: number | null = null;
|
|
33
|
+
|
|
34
|
+
constructor(host: AnswerStreamHost) {
|
|
35
|
+
this.#host = host;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Text deltas the current answer has arrived in so far. */
|
|
39
|
+
get deltas(): number {
|
|
40
|
+
return this.#streamDeltas;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Count one delta received. Counted per delta received, not per render: the
|
|
45
|
+
* word reveal asks whether the answer *arrived* progressively, which
|
|
46
|
+
* coalescing renders must not change the answer to.
|
|
47
|
+
*/
|
|
48
|
+
countDelta(): void {
|
|
49
|
+
this.#streamDeltas += 1;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Queue a render of the answer so far, at most one per frame.
|
|
54
|
+
*
|
|
55
|
+
* Each `TEXT_MESSAGE_CONTENT` event carries the *whole* accumulated answer,
|
|
56
|
+
* and drawing it means marked + DOMPurify over the entire document and a
|
|
57
|
+
* wholesale replacement of the bubble's subtree. Once per token that is
|
|
58
|
+
* quadratic in the answer's length — a long answer is agent-controlled, so
|
|
59
|
+
* an ordinary run becomes a progressively stalling tab — and every rebuild
|
|
60
|
+
* takes any selection or focus inside the bubble with it.
|
|
61
|
+
*
|
|
62
|
+
* A frame is the right grain: it is the fastest anything on screen can
|
|
63
|
+
* change anyway, so a burst of tokens costs one parse and the text still
|
|
64
|
+
* appears to flow rather than in visible chunks.
|
|
65
|
+
*/
|
|
66
|
+
queue(buffer: string): void {
|
|
67
|
+
this.#streamBuffer = buffer;
|
|
68
|
+
this.#open();
|
|
69
|
+
if (this.#streamFrame !== null) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
this.#streamFrame = requestAnimationFrame(() => {
|
|
73
|
+
this.#streamFrame = null;
|
|
74
|
+
this.into(this.#streamBuffer);
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** Render `buffer` into the streaming bubble now, dropping any queued frame. */
|
|
79
|
+
into(buffer: string): HTMLDivElement {
|
|
80
|
+
// A frame still queued would otherwise fire after this and repaint the
|
|
81
|
+
// bubble with whatever the last delta held — behind the buffer just drawn.
|
|
82
|
+
if (this.#streamFrame !== null) {
|
|
83
|
+
cancelAnimationFrame(this.#streamFrame);
|
|
84
|
+
this.#streamFrame = null;
|
|
85
|
+
}
|
|
86
|
+
this.#streamBuffer = buffer;
|
|
87
|
+
const bubble = this.#open();
|
|
88
|
+
bubble.innerHTML = renderMarkdown(buffer, { allowImages: this.#host.allowImages() });
|
|
89
|
+
this.#host.follow();
|
|
90
|
+
return bubble;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Close the current answer's streaming bubble.
|
|
95
|
+
*
|
|
96
|
+
* Draws a queued render first. A run that ends without a text end — a
|
|
97
|
+
* cancel, an error, a round boundary — leaves the last delta sitting in the
|
|
98
|
+
* queue, and simply dropping the bubble here would strand it: the partial
|
|
99
|
+
* answer the user stopped mid-sentence would lose its final tokens, or be an
|
|
100
|
+
* empty bubble above the stopped note.
|
|
101
|
+
*/
|
|
102
|
+
end(): void {
|
|
103
|
+
if (this.#streamFrame !== null) {
|
|
104
|
+
this.into(this.#streamBuffer);
|
|
105
|
+
}
|
|
106
|
+
this.#streamingBubble = null;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* The bubble the current answer streams into, opening it on first sight.
|
|
111
|
+
*
|
|
112
|
+
* Opened the moment a token arrives rather than on the frame that draws it,
|
|
113
|
+
* so the answer's container replaces the pending dots straight away and the
|
|
114
|
+
* turn never shows a gap while the first render waits for a frame.
|
|
115
|
+
*/
|
|
116
|
+
#open(): HTMLDivElement {
|
|
117
|
+
if (this.#streamingBubble === null) {
|
|
118
|
+
this.#streamingBubble = this.#host.openBubble();
|
|
119
|
+
this.#streamDeltas = 0;
|
|
120
|
+
}
|
|
121
|
+
return this.#streamingBubble;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Call a consumer's renderer, turning a throw into "nothing drawn".
|
|
3
|
+
*
|
|
4
|
+
* `render` is consumer code -- a host's activity renderer, a tool's `render` --
|
|
5
|
+
* and both call sites run inside the history replay, where a throw abandons the
|
|
6
|
+
* loop and takes every later turn of the transcript with it -- silently, and
|
|
7
|
+
* again on every reload. One thing that fails to draw is worth losing; the rest
|
|
8
|
+
* of the conversation is not. Reported so the failure is findable rather than
|
|
9
|
+
* merely survived.
|
|
10
|
+
*
|
|
11
|
+
* @param render - The consumer's renderer, already bound to its input.
|
|
12
|
+
* @param subject - What was being drawn, for the warning: `tool <name>` or
|
|
13
|
+
* `activity <type>`.
|
|
14
|
+
*/
|
|
15
|
+
export function renderOrWarn(render: () => Node | null, subject: string): Node | null {
|
|
16
|
+
try {
|
|
17
|
+
return render();
|
|
18
|
+
} catch (error) {
|
|
19
|
+
console.warn(`ag-ui-chat: render failed for ${subject}`, error);
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { UiStrings } from "../ui_strings.js";
|
|
2
|
+
import { renderSuggestionChips } from "./suggestion_chips.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The prompts offered on an empty transcript, from `element`'s `data-starters`.
|
|
6
|
+
*
|
|
7
|
+
* Different from the suggestion chips a run pushes, which are follow-ups to
|
|
8
|
+
* something already said. These answer the blank-page question instead, and
|
|
9
|
+
* they are the host's rather than the model's -- only the host knows what its
|
|
10
|
+
* page is for. Shares the renderer, the count and the length limit, because
|
|
11
|
+
* two rows of prompt chips that behaved differently would be the harder
|
|
12
|
+
* thing to explain.
|
|
13
|
+
*
|
|
14
|
+
* Read once at connect: it is content for a state the widget is in before
|
|
15
|
+
* anything happens, and a host that wants it to change has `slot="empty"`.
|
|
16
|
+
*
|
|
17
|
+
* Parses the attribute itself rather than through the element's JSON attribute
|
|
18
|
+
* reader, because the warning is its own: it says no starters are shown and
|
|
19
|
+
* gives an example of the shape, where the shared one says a built-in default
|
|
20
|
+
* is in use, which for this attribute there is not.
|
|
21
|
+
*/
|
|
22
|
+
export function renderStarterChips(
|
|
23
|
+
element: Element,
|
|
24
|
+
strings: UiStrings,
|
|
25
|
+
onPick: (prompt: string) => void,
|
|
26
|
+
): HTMLElement | null {
|
|
27
|
+
const raw = element.getAttribute("data-starters");
|
|
28
|
+
if (raw === null) {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
let parsed: unknown;
|
|
32
|
+
try {
|
|
33
|
+
parsed = JSON.parse(raw);
|
|
34
|
+
} catch {
|
|
35
|
+
console.warn(
|
|
36
|
+
"<ag-ui-chat>: data-starters is not valid JSON, so no starters are shown. " +
|
|
37
|
+
"It takes an array of strings, e.g. data-starters='[\"Summarise this page\"]'.",
|
|
38
|
+
);
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
return renderSuggestionChips({ prompts: parsed }, strings, onPick);
|
|
42
|
+
}
|