@artooi/ag-ui-web-component 0.18.0 → 0.19.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 +68 -1
- package/README.md +27 -2
- package/dist/ag-ui-web-component.bundle.js +107 -42
- package/dist/ag-ui-web-component.bundle.js.map +3 -3
- package/dist/core/ag_ui_chat.d.ts +5 -1
- package/dist/core/ag_ui_chat.d.ts.map +1 -1
- package/dist/index.js +179 -58
- package/dist/index.js.map +2 -2
- package/dist/ui/confirmation_card.d.ts +8 -2
- package/dist/ui/confirmation_card.d.ts.map +1 -1
- package/dist/ui/styles.d.ts +1 -1
- package/dist/ui/styles.d.ts.map +1 -1
- package/dist/ui/tool_call_card.d.ts +26 -14
- package/dist/ui/tool_call_card.d.ts.map +1 -1
- package/dist/ui/ui_strings.d.ts +10 -4
- package/dist/ui/ui_strings.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/core/ag_ui_chat.ts +27 -5
- package/src/ui/confirmation_card.ts +15 -5
- package/src/ui/styles.ts +71 -4
- package/src/ui/tool_call_card.ts +116 -58
- package/src/ui/ui_strings.ts +13 -4
- package/src/version.ts +1 -1
package/src/ui/tool_call_card.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { TOOL_CALL_STATUS, TOOL_DISPLAY } from "../constants.js";
|
|
1
|
+
import { TOOL_CALL_STATUS, type TOOL_DISPLAY } from "../constants.js";
|
|
2
2
|
import { DEFAULT_UI_STRINGS, type UiStrings } from "./ui_strings.js";
|
|
3
3
|
|
|
4
4
|
/** Any state a tool-call card can be in. */
|
|
@@ -20,7 +20,7 @@ function statusLabels(strings: UiStrings): Record<ToolCallStatus, string> {
|
|
|
20
20
|
};
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
/**
|
|
23
|
+
/** Section-heading text for each settled outcome's result region. */
|
|
24
24
|
function resultLabels(strings: UiStrings): Record<SettledStatus, string> {
|
|
25
25
|
return {
|
|
26
26
|
[TOOL_CALL_STATUS.DONE]: strings.resultLabel,
|
|
@@ -29,19 +29,32 @@ function resultLabels(strings: UiStrings): Record<SettledStatus, string> {
|
|
|
29
29
|
};
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
/** Pretty-print a JSON payload; fall back to the raw text if it isn't JSON. */
|
|
33
|
+
function formatPayload(text: string): string {
|
|
34
|
+
try {
|
|
35
|
+
return JSON.stringify(JSON.parse(text), null, 2);
|
|
36
|
+
} catch {
|
|
37
|
+
return text;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
32
41
|
/**
|
|
33
42
|
* A live tool-call card for the chat transcript.
|
|
34
43
|
*
|
|
35
|
-
* Construction renders a status icon, the tool name,
|
|
36
|
-
*
|
|
37
|
-
* {@link settle}
|
|
38
|
-
*
|
|
44
|
+
* Construction renders a status icon, the tool name, a `running…` pill, and the
|
|
45
|
+
* card's body: an **arguments** region and a **result** region, each with its own
|
|
46
|
+
* heading and its own `part`. {@link settle} fills in the result and flips the
|
|
47
|
+
* pill. Both payloads are pretty-printed, and the two are never concatenated —
|
|
48
|
+
* the previous compact layout ran `args: {...}` and the result together in one
|
|
49
|
+
* `<pre>`, leaving no way to see where the call ended and the answer began.
|
|
39
50
|
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
51
|
+
* **The card renders one DOM shape in every display mode, and CSS decides what
|
|
52
|
+
* shows.** That is what makes `data-tool-display` behave like `data-answer-well`
|
|
53
|
+
* — flip it on the host and every card already on screen re-reads it. Building a
|
|
54
|
+
* different structure per mode meant only cards created *after* the change
|
|
55
|
+
* picked it up, so the setting appeared not to work until the next conversation.
|
|
56
|
+
* Visibility is selected from the host attribute rather than a value copied onto
|
|
57
|
+
* the card at construction, for the same reason.
|
|
45
58
|
*
|
|
46
59
|
* The leading icon carries no text of its own: its glyph/spinner is drawn by
|
|
47
60
|
* the shadow CSS keyed off the card's `data-status`, so a host themes it via
|
|
@@ -57,20 +70,20 @@ export class ToolCallCard {
|
|
|
57
70
|
readonly element: HTMLDivElement;
|
|
58
71
|
|
|
59
72
|
readonly #status: HTMLSpanElement;
|
|
60
|
-
readonly #
|
|
61
|
-
readonly #
|
|
73
|
+
readonly #decision: HTMLSpanElement;
|
|
74
|
+
readonly #toggle: HTMLButtonElement;
|
|
75
|
+
readonly #resultSection: HTMLDivElement;
|
|
76
|
+
readonly #resultLabel: HTMLSpanElement;
|
|
77
|
+
readonly #resultBody: HTMLPreElement;
|
|
62
78
|
readonly #strings: UiStrings;
|
|
63
79
|
#settled = false;
|
|
64
80
|
|
|
65
81
|
constructor(
|
|
66
82
|
name: string,
|
|
67
83
|
args: Record<string, unknown>,
|
|
68
|
-
mode: ToolDisplayMode = TOOL_DISPLAY.FULL,
|
|
69
84
|
summary?: string,
|
|
70
85
|
strings: UiStrings = DEFAULT_UI_STRINGS,
|
|
71
86
|
) {
|
|
72
|
-
this.#mode = mode;
|
|
73
|
-
this.#args = args;
|
|
74
87
|
this.#strings = strings;
|
|
75
88
|
|
|
76
89
|
this.element = document.createElement("div");
|
|
@@ -78,7 +91,7 @@ export class ToolCallCard {
|
|
|
78
91
|
this.element.setAttribute("part", "tool-card");
|
|
79
92
|
this.element.setAttribute("data-tool-name", name);
|
|
80
93
|
this.element.setAttribute("data-status", TOOL_CALL_STATUS.PENDING);
|
|
81
|
-
this.element.setAttribute("data-
|
|
94
|
+
this.element.setAttribute("data-expanded", "false");
|
|
82
95
|
|
|
83
96
|
const head = document.createElement("div");
|
|
84
97
|
head.className = "tool-call-head";
|
|
@@ -103,16 +116,56 @@ export class ToolCallCard {
|
|
|
103
116
|
this.#status.setAttribute("part", "tool-card-status");
|
|
104
117
|
this.#status.textContent = statusLabels(strings)[TOOL_CALL_STATUS.PENDING];
|
|
105
118
|
|
|
106
|
-
|
|
107
|
-
this.
|
|
119
|
+
this.#decision = document.createElement("span");
|
|
120
|
+
this.#decision.className = "tool-call-decision";
|
|
121
|
+
this.#decision.setAttribute("part", "tool-card-decision");
|
|
122
|
+
this.#decision.hidden = true;
|
|
123
|
+
|
|
124
|
+
head.append(icon, label, this.#status, this.#decision);
|
|
125
|
+
|
|
126
|
+
const argsSection = this.#section("args", strings.argumentsLabel);
|
|
127
|
+
argsSection.body.textContent = JSON.stringify(args, null, 2);
|
|
128
|
+
// A call with no arguments renders an empty object in a box of its own,
|
|
129
|
+
// which is a frame around nothing. Drop the region instead.
|
|
130
|
+
argsSection.root.hidden = Object.keys(args).length === 0;
|
|
131
|
+
|
|
132
|
+
const resultSection = this.#section("result", strings.resultLabel);
|
|
133
|
+
this.#resultSection = resultSection.root;
|
|
134
|
+
this.#resultLabel = resultSection.label;
|
|
135
|
+
this.#resultBody = resultSection.body;
|
|
136
|
+
// Nothing to show until `settle` supplies it; a pending card would
|
|
137
|
+
// otherwise expand onto an empty region.
|
|
138
|
+
resultSection.root.hidden = true;
|
|
139
|
+
|
|
140
|
+
this.#toggle = document.createElement("button");
|
|
141
|
+
this.#toggle.type = "button";
|
|
142
|
+
this.#toggle.className = "tool-call-toggle";
|
|
143
|
+
this.#toggle.setAttribute("part", "tool-card-toggle");
|
|
144
|
+
this.#toggle.setAttribute("aria-expanded", "false");
|
|
145
|
+
this.#toggle.textContent = strings.details;
|
|
146
|
+
this.#toggle.addEventListener("click", () => this.#setExpanded(!this.#expanded()));
|
|
147
|
+
|
|
148
|
+
const body = document.createElement("div");
|
|
149
|
+
body.className = "tool-call-body";
|
|
150
|
+
body.setAttribute("part", "tool-card-body");
|
|
151
|
+
body.append(argsSection.root, resultSection.root);
|
|
152
|
+
|
|
153
|
+
this.element.append(head, this.#toggle, body);
|
|
154
|
+
}
|
|
108
155
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
156
|
+
/**
|
|
157
|
+
* Record that a human approved or declined this call, as a line in the card.
|
|
158
|
+
*
|
|
159
|
+
* Approval used to leave no trace at all: a declined call became a tool
|
|
160
|
+
* result saying so, while an approved one simply ran, making the transcript
|
|
161
|
+
* of a gated call byte-identical to one that was never gated. The prompt is
|
|
162
|
+
* gone once answered, so this is where the decision lives.
|
|
163
|
+
*/
|
|
164
|
+
recordDecision(kind: "approved" | "declined"): void {
|
|
165
|
+
this.element.setAttribute("data-decision", kind);
|
|
166
|
+
this.#decision.textContent =
|
|
167
|
+
kind === "approved" ? this.#strings.decisionApproved : this.#strings.decisionDeclined;
|
|
168
|
+
this.#decision.hidden = false;
|
|
116
169
|
}
|
|
117
170
|
|
|
118
171
|
/** Whether {@link settle} has already run (so a terminal sweep can skip it). */
|
|
@@ -121,50 +174,55 @@ export class ToolCallCard {
|
|
|
121
174
|
}
|
|
122
175
|
|
|
123
176
|
/**
|
|
124
|
-
* Flip the status pill to
|
|
125
|
-
*
|
|
126
|
-
* `inline`), or the args + result together (`compact`).
|
|
177
|
+
* Flip the status pill to `status` and fill in the result region, whose
|
|
178
|
+
* heading names the outcome (result / error / declined).
|
|
127
179
|
*/
|
|
128
180
|
settle(status: SettledStatus, text: string): void {
|
|
129
181
|
// Idempotent: a duplicate `TOOL_CALL_RESULT`, or a replayed tool message
|
|
130
|
-
// for an already-settled card, must not
|
|
131
|
-
// first settle wins; later calls are ignored.
|
|
182
|
+
// for an already-settled card, must not overwrite the first outcome.
|
|
132
183
|
if (this.#settled) {
|
|
133
184
|
return;
|
|
134
185
|
}
|
|
135
186
|
this.#settled = true;
|
|
136
187
|
this.element.setAttribute("data-status", status);
|
|
137
188
|
this.#status.textContent = statusLabels(this.#strings)[status];
|
|
189
|
+
this.#resultLabel.textContent = resultLabels(this.#strings)[status];
|
|
190
|
+
this.#resultBody.textContent = formatPayload(text);
|
|
191
|
+
this.#resultSection.hidden = false;
|
|
192
|
+
}
|
|
138
193
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
194
|
+
/** Build one labelled region of the body: a heading plus a payload block. */
|
|
195
|
+
#section(
|
|
196
|
+
kind: string,
|
|
197
|
+
labelText: string,
|
|
198
|
+
): {
|
|
199
|
+
root: HTMLDivElement;
|
|
200
|
+
label: HTMLSpanElement;
|
|
201
|
+
body: HTMLPreElement;
|
|
202
|
+
} {
|
|
203
|
+
const root = document.createElement("div");
|
|
204
|
+
root.className = `tool-call-section tool-call-section--${kind}`;
|
|
205
|
+
root.setAttribute("part", `tool-card-section tool-card-${kind}-section`);
|
|
142
206
|
|
|
143
|
-
const
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
toggle.setAttribute("aria-expanded", "false");
|
|
148
|
-
|
|
149
|
-
const output = document.createElement("pre");
|
|
150
|
-
output.className = "tool-call-result";
|
|
151
|
-
output.setAttribute("part", "tool-card-result");
|
|
152
|
-
output.hidden = true;
|
|
153
|
-
|
|
154
|
-
if (this.#mode === TOOL_DISPLAY.COMPACT) {
|
|
155
|
-
toggle.textContent = this.#strings.details;
|
|
156
|
-
output.textContent = `args: ${JSON.stringify(this.#args)}\n\n${text}`;
|
|
157
|
-
} else {
|
|
158
|
-
toggle.textContent = resultLabels(this.#strings)[status];
|
|
159
|
-
output.textContent = text;
|
|
160
|
-
}
|
|
207
|
+
const label = document.createElement("span");
|
|
208
|
+
label.className = "tool-call-section-label";
|
|
209
|
+
label.setAttribute("part", `tool-card-section-label tool-card-${kind}-label`);
|
|
210
|
+
label.textContent = labelText;
|
|
161
211
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
212
|
+
const body = document.createElement("pre");
|
|
213
|
+
body.className = `tool-call-${kind}`;
|
|
214
|
+
body.setAttribute("part", `tool-card-${kind}`);
|
|
215
|
+
|
|
216
|
+
root.append(label, body);
|
|
217
|
+
return { root, label, body };
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
#expanded(): boolean {
|
|
221
|
+
return this.element.getAttribute("data-expanded") === "true";
|
|
222
|
+
}
|
|
167
223
|
|
|
168
|
-
|
|
224
|
+
#setExpanded(expand: boolean): void {
|
|
225
|
+
this.element.setAttribute("data-expanded", String(expand));
|
|
226
|
+
this.#toggle.setAttribute("aria-expanded", String(expand));
|
|
169
227
|
}
|
|
170
228
|
}
|
package/src/ui/ui_strings.ts
CHANGED
|
@@ -89,13 +89,19 @@ export interface UiStrings {
|
|
|
89
89
|
toolError: string;
|
|
90
90
|
/** Status pill on a declined call. */
|
|
91
91
|
toolDeclined: string;
|
|
92
|
-
/**
|
|
92
|
+
/** Note on a tool card whose call a human approved. */
|
|
93
|
+
decisionApproved: string;
|
|
94
|
+
/** Note on a tool card whose call a human declined. */
|
|
95
|
+
decisionDeclined: string;
|
|
96
|
+
/** Heading over a tool card's arguments region. */
|
|
97
|
+
argumentsLabel: string;
|
|
98
|
+
/** Heading over a tool card's result region when the call succeeded. */
|
|
93
99
|
resultLabel: string;
|
|
94
|
-
/**
|
|
100
|
+
/** Heading over a tool card's result region when the call failed. */
|
|
95
101
|
errorLabel: string;
|
|
96
|
-
/**
|
|
102
|
+
/** Heading over a tool card's result region when the call was declined. */
|
|
97
103
|
declinedLabel: string;
|
|
98
|
-
/**
|
|
104
|
+
/** Label on the toggle that expands a tool card's body. */
|
|
99
105
|
details: string;
|
|
100
106
|
|
|
101
107
|
// ── Confirmation card ───────────────────────────────────────────────────────
|
|
@@ -237,6 +243,9 @@ export const DEFAULT_UI_STRINGS: UiStrings = {
|
|
|
237
243
|
toolDone: "✓ done",
|
|
238
244
|
toolError: "⚠ error",
|
|
239
245
|
toolDeclined: "⊘ declined",
|
|
246
|
+
decisionApproved: "approved by you",
|
|
247
|
+
decisionDeclined: "declined by you",
|
|
248
|
+
argumentsLabel: "Arguments",
|
|
240
249
|
resultLabel: "Result",
|
|
241
250
|
errorLabel: "Error",
|
|
242
251
|
declinedLabel: "Declined",
|
package/src/version.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const VERSION: string = "0.
|
|
1
|
+
export const VERSION: string = "0.19.0";
|