@artooi/ag-ui-web-component 0.23.1 → 0.25.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 +194 -1
- package/README.md +188 -38
- package/dist/ag-ui-web-component.bundle.js +189 -49
- package/dist/ag-ui-web-component.bundle.js.map +3 -3
- package/dist/constants.d.ts +25 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/core/ag_ui_chat.d.ts +24 -0
- package/dist/core/ag_ui_chat.d.ts.map +1 -1
- package/dist/core/conversation_store.d.ts +18 -0
- package/dist/core/conversation_store.d.ts.map +1 -1
- package/dist/core/remote_conversation_store.d.ts +2 -0
- package/dist/core/remote_conversation_store.d.ts.map +1 -1
- package/dist/core/run_index.d.ts +10 -0
- package/dist/core/run_index.d.ts.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +335 -44
- package/dist/index.js.map +2 -2
- package/dist/ui/checkpoint_menu.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 -2
- package/dist/ui/tool_call_card.d.ts.map +1 -1
- package/dist/ui/ui_strings.d.ts +2 -0
- package/dist/ui/ui_strings.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/constants.ts +26 -0
- package/src/core/ag_ui_chat.ts +186 -40
- package/src/core/conversation_store.ts +30 -0
- package/src/core/remote_conversation_store.ts +14 -0
- package/src/core/run_index.ts +10 -0
- package/src/index.ts +3 -0
- package/src/ui/checkpoint_menu.ts +32 -7
- package/src/ui/styles.ts +153 -13
- package/src/ui/tool_call_card.ts +41 -3
- package/src/ui/ui_strings.ts +3 -0
- package/src/version.ts +1 -1
package/src/ui/styles.ts
CHANGED
|
@@ -923,18 +923,28 @@ export const STYLES = `
|
|
|
923
923
|
color: var(--_tool-fg);
|
|
924
924
|
}
|
|
925
925
|
|
|
926
|
+
/* Wraps, because the name is the only flexible child and every badge the row
|
|
927
|
+
gains is taken out of it. An approved call adds a third fixed badge, which in
|
|
928
|
+
a sidebar-width panel left the name 37px and broke it mid-word. Badges drop to
|
|
929
|
+
their own row instead. */
|
|
926
930
|
.tool-call-head {
|
|
927
931
|
display: flex;
|
|
932
|
+
flex-wrap: wrap;
|
|
928
933
|
align-items: center;
|
|
929
934
|
justify-content: space-between;
|
|
930
935
|
gap: 8px;
|
|
931
936
|
}
|
|
932
937
|
|
|
933
938
|
.tool-call-name {
|
|
934
|
-
|
|
935
|
-
|
|
939
|
+
/* An auto basis, and a min-width floor rather than zero: the name may shrink,
|
|
940
|
+
but not below something readable, so wrapping moves a badge instead of
|
|
941
|
+
shredding a word. Breaking anywhere still applies to a name that cannot fit
|
|
942
|
+
on a line of its own, which is what keeps a long unbroken tool name inside
|
|
943
|
+
the card. */
|
|
944
|
+
flex: 1 1 auto;
|
|
945
|
+
min-width: 6ch;
|
|
936
946
|
font-weight: 600;
|
|
937
|
-
|
|
947
|
+
overflow-wrap: anywhere;
|
|
938
948
|
}
|
|
939
949
|
|
|
940
950
|
/* Leading status icon. Empty in the DOM — the glyph/spinner is drawn
|
|
@@ -963,6 +973,13 @@ export const STYLES = `
|
|
|
963
973
|
to { transform: rotate(360deg); }
|
|
964
974
|
}
|
|
965
975
|
|
|
976
|
+
/* Deferred: no spinner, because nothing is spinning. A steady accent dot, since
|
|
977
|
+
the state is waiting-on-you rather than an outcome. */
|
|
978
|
+
.tool-call[data-status="deferred"] .tool-call-icon {
|
|
979
|
+
border-radius: 50%;
|
|
980
|
+
background: var(--_accent);
|
|
981
|
+
}
|
|
982
|
+
|
|
966
983
|
/* Settled: a themeable glyph coloured by outcome. */
|
|
967
984
|
.tool-call[data-status="done"] .tool-call-icon::before {
|
|
968
985
|
content: var(--_tool-icon-done);
|
|
@@ -1005,6 +1022,10 @@ export const STYLES = `
|
|
|
1005
1022
|
color: var(--_muted);
|
|
1006
1023
|
}
|
|
1007
1024
|
|
|
1025
|
+
.tool-call[data-status="deferred"] .tool-call-status {
|
|
1026
|
+
color: var(--_accent);
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1008
1029
|
.tool-call[data-status="done"] .tool-call-status {
|
|
1009
1030
|
color: var(--_success);
|
|
1010
1031
|
}
|
|
@@ -1098,12 +1119,41 @@ export const STYLES = `
|
|
|
1098
1119
|
|
|
1099
1120
|
/* A pending card has no result yet, and in the modes where the arguments are
|
|
1100
1121
|
hidden too there is nothing behind the toggle. Hide the control rather than
|
|
1101
|
-
offer one that expands onto nothing.
|
|
1122
|
+
offer one that expands onto nothing. A deferred card is the same, and its
|
|
1123
|
+
arguments are shown unconditionally by the rules below. */
|
|
1102
1124
|
.tool-call[data-status="pending"] .tool-call-toggle,
|
|
1125
|
+
.tool-call[data-status="deferred"] .tool-call-toggle,
|
|
1103
1126
|
:host([data-tool-display="inline"]) .tool-call[data-status="pending"] .tool-call-toggle {
|
|
1104
1127
|
display: none;
|
|
1105
1128
|
}
|
|
1106
1129
|
|
|
1130
|
+
/* The approval prompt for a gated call, rendered inside that call's own card.
|
|
1131
|
+
Empty on every card nobody is being asked about, so it collapses instead of
|
|
1132
|
+
adding a gap to each one. */
|
|
1133
|
+
.tool-call-approval:empty {
|
|
1134
|
+
display: none;
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
.tool-call-approval {
|
|
1138
|
+
margin-top: 8px;
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
/* A card that is asking a question shows what it is asking about, in every
|
|
1142
|
+
display mode. Three gated calls of one tool ask the same words, so the
|
|
1143
|
+
arguments are the only thing telling them apart, and a density setting must
|
|
1144
|
+
not be able to hide the answer to "which one is this". */
|
|
1145
|
+
:host([data-tool-display="minimal"]) .tool-call[data-status="deferred"] .tool-call-body {
|
|
1146
|
+
display: flex;
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
/* The arguments region only, never every section: the result region carries the
|
|
1150
|
+
hidden attribute until a result exists, and a display value here overrides it,
|
|
1151
|
+
framing an empty RESULT heading under the question. */
|
|
1152
|
+
:host([data-tool-display="compact"]) .tool-call[data-status="deferred"] .tool-call-section--args,
|
|
1153
|
+
:host([data-tool-display="inline"]) .tool-call[data-status="deferred"] .tool-call-section--args {
|
|
1154
|
+
display: flex;
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1107
1157
|
.tool-call-toggle {
|
|
1108
1158
|
align-self: flex-start;
|
|
1109
1159
|
border: none;
|
|
@@ -1323,6 +1373,20 @@ export const STYLES = `
|
|
|
1323
1373
|
opacity: 0.6;
|
|
1324
1374
|
}
|
|
1325
1375
|
|
|
1376
|
+
/* The same trap the attachment tray carries a note about, two rules along: an
|
|
1377
|
+
author display beats the UA stylesheet's rule for the hidden property, so a
|
|
1378
|
+
button the element has explicitly hidden keeps laying out and painting. The
|
|
1379
|
+
clip is hidden until a host supplies an upload handler or an attachments URL,
|
|
1380
|
+
and without this it is a visible control that cannot do anything.
|
|
1381
|
+
|
|
1382
|
+
The mic needs no such rule, and the asymmetry is worth knowing before adding
|
|
1383
|
+
one: it is not hidden when unconfigured, it is never built. The voice wiring
|
|
1384
|
+
returns before constructing the button, leaving only an empty voice slot that
|
|
1385
|
+
is display: contents. A hidden-state rule for the mic would match nothing. */
|
|
1386
|
+
.attach-btn[hidden] {
|
|
1387
|
+
display: none;
|
|
1388
|
+
}
|
|
1389
|
+
|
|
1326
1390
|
/* Send closes the row on the right: a circle, the only filled control in the
|
|
1327
1391
|
composer, so "the thing that acts" reads at a glance. */
|
|
1328
1392
|
.send {
|
|
@@ -1932,47 +1996,123 @@ export const STYLES = `
|
|
|
1932
1996
|
opacity: 0.7;
|
|
1933
1997
|
}
|
|
1934
1998
|
|
|
1999
|
+
/* A row is a label and two buttons, and nothing about the row itself is
|
|
2000
|
+
pressable. It used to light up on hover, which is the affordance of something
|
|
2001
|
+
clickable and made the buttons look like decoration on a clickable strip. The
|
|
2002
|
+
resting surface groups the row instead, so hover can mean what it says: only
|
|
2003
|
+
the buttons respond to it. */
|
|
2004
|
+
/* A row is a label and two buttons, and nothing about the row itself is
|
|
2005
|
+
pressable. It used to light up on hover, which is the affordance of something
|
|
2006
|
+
clickable and made the buttons look like decoration on a clickable strip. The
|
|
2007
|
+
resting surface groups the row instead, so hover can mean what it says: only
|
|
2008
|
+
the buttons respond to it.
|
|
2009
|
+
|
|
2010
|
+
It wraps for the same reason the tool-call head does. Every child but the label
|
|
2011
|
+
is fixed-width, so in a narrow panel the label is the only thing that can give
|
|
2012
|
+
-- and a flex-basis of zero lets it give everything. Adding the run id was
|
|
2013
|
+
enough to crush "just now" to zero pixels: present, correct, and invisible.
|
|
2014
|
+
Wrapping puts the buttons on their own line instead. */
|
|
1935
2015
|
.checkpoint-row {
|
|
1936
2016
|
display: flex;
|
|
2017
|
+
flex-wrap: wrap;
|
|
1937
2018
|
align-items: center;
|
|
1938
2019
|
gap: 0.5rem;
|
|
1939
|
-
padding: 0.
|
|
2020
|
+
padding: 0.3125rem 0.4375rem;
|
|
1940
2021
|
border-radius: 0.375rem;
|
|
1941
|
-
}
|
|
1942
|
-
|
|
1943
|
-
.checkpoint-row:hover {
|
|
1944
2022
|
background: var(--_hover);
|
|
1945
2023
|
}
|
|
1946
2024
|
|
|
2025
|
+
/* Grows into spare room, and refuses to shrink past the shortest thing it ever
|
|
2026
|
+
says. A time is short and bounded, so there is no case for eliding it. */
|
|
1947
2027
|
.checkpoint-label {
|
|
1948
|
-
flex: 1;
|
|
2028
|
+
flex: 1 1 auto;
|
|
2029
|
+
min-width: 7ch;
|
|
1949
2030
|
font-size: 0.8125rem;
|
|
1950
2031
|
white-space: nowrap;
|
|
1951
2032
|
overflow: hidden;
|
|
1952
2033
|
text-overflow: ellipsis;
|
|
1953
2034
|
}
|
|
1954
2035
|
|
|
2036
|
+
/* When the label holds the run's first message, the time moves here: still worth
|
|
2037
|
+
showing, no longer what identifies the row. Muted and unshrinkable, so it does
|
|
2038
|
+
not compete with the words beside it. */
|
|
2039
|
+
.checkpoint-time {
|
|
2040
|
+
flex: 0 0 auto;
|
|
2041
|
+
font-size: 0.6875rem;
|
|
2042
|
+
opacity: 0.7;
|
|
2043
|
+
white-space: nowrap;
|
|
2044
|
+
}
|
|
2045
|
+
|
|
2046
|
+
/* Enough of the run id to tell two runs apart when both say "just now". Muted
|
|
2047
|
+
and monospaced: it is a reference, not a name. */
|
|
2048
|
+
.checkpoint-id {
|
|
2049
|
+
flex: 0 0 auto;
|
|
2050
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
2051
|
+
font-size: 0.6875rem;
|
|
2052
|
+
opacity: 0.55;
|
|
2053
|
+
}
|
|
2054
|
+
|
|
2055
|
+
/* On the panel's own surface, not the row's: the row now paints the hover token
|
|
2056
|
+
itself, and a badge the same colour as what it sits on is not a badge. */
|
|
1955
2057
|
.checkpoint-branch {
|
|
1956
2058
|
font-size: 0.6875rem;
|
|
1957
2059
|
padding: 0 0.375rem;
|
|
1958
2060
|
border-radius: 999px;
|
|
1959
|
-
background: var(--
|
|
2061
|
+
background: var(--_assistant-bg);
|
|
1960
2062
|
opacity: 0.8;
|
|
1961
2063
|
}
|
|
1962
2064
|
|
|
2065
|
+
/* The two things in the row that actually do something, so they are the two
|
|
2066
|
+
things that look like it: a filled surface at rest rather than a transparent
|
|
2067
|
+
outline, which on top of the old row highlight was nearly invisible. */
|
|
1963
2068
|
.checkpoint-action {
|
|
1964
2069
|
font: inherit;
|
|
1965
2070
|
font-size: 0.75rem;
|
|
2071
|
+
line-height: 1.4;
|
|
1966
2072
|
cursor: pointer;
|
|
1967
|
-
padding: 0.
|
|
2073
|
+
padding: 0.1875rem 0.5625rem;
|
|
1968
2074
|
border: 1px solid var(--_border);
|
|
1969
2075
|
border-radius: 0.375rem;
|
|
1970
|
-
background:
|
|
2076
|
+
background: var(--_bg);
|
|
1971
2077
|
color: inherit;
|
|
2078
|
+
transition:
|
|
2079
|
+
background var(--_motion) var(--_ease),
|
|
2080
|
+
border-color var(--_motion) var(--_ease),
|
|
2081
|
+
transform var(--_motion) var(--_ease);
|
|
1972
2082
|
}
|
|
1973
2083
|
|
|
1974
|
-
|
|
2084
|
+
/* Resume is what a reader wants nine times in ten; fork is the deliberate choice
|
|
2085
|
+
beside it. Filled and outlined, the same pair the confirmation and approval
|
|
2086
|
+
cards already use for their primary and secondary action. */
|
|
2087
|
+
.checkpoint-resume {
|
|
2088
|
+
font-weight: 600;
|
|
2089
|
+
border-color: var(--_accent);
|
|
2090
|
+
background: var(--_accent);
|
|
2091
|
+
color: #ffffff;
|
|
2092
|
+
}
|
|
2093
|
+
|
|
2094
|
+
.checkpoint-fork:hover {
|
|
1975
2095
|
background: var(--_hover);
|
|
2096
|
+
border-color: var(--_accent);
|
|
2097
|
+
}
|
|
2098
|
+
|
|
2099
|
+
/* The filled one cannot go lighter on hover without losing its contrast with the
|
|
2100
|
+
white label, so it dims instead. */
|
|
2101
|
+
.checkpoint-resume:hover {
|
|
2102
|
+
opacity: 0.88;
|
|
2103
|
+
}
|
|
2104
|
+
|
|
2105
|
+
/* Pressed: a pixel down, so the click is felt as well as seen. */
|
|
2106
|
+
.checkpoint-action:active {
|
|
2107
|
+
transform: translateY(1px);
|
|
2108
|
+
}
|
|
2109
|
+
|
|
2110
|
+
/* Keyboard focus was invisible here, in a panel that traps focus and is reached
|
|
2111
|
+
by Tab -- so the one navigation path guaranteed to land on these buttons was
|
|
2112
|
+
the one with nothing to show for it. */
|
|
2113
|
+
.checkpoint-action:focus-visible {
|
|
2114
|
+
outline: 2px solid var(--_accent);
|
|
2115
|
+
outline-offset: 2px;
|
|
1976
2116
|
}
|
|
1977
2117
|
|
|
1978
2118
|
.drawer-backdrop {
|
package/src/ui/tool_call_card.ts
CHANGED
|
@@ -7,13 +7,17 @@ export type ToolCallStatus = (typeof TOOL_CALL_STATUS)[keyof typeof TOOL_CALL_ST
|
|
|
7
7
|
/** How much detail a card renders. */
|
|
8
8
|
export type ToolDisplayMode = (typeof TOOL_DISPLAY)[keyof typeof TOOL_DISPLAY];
|
|
9
9
|
|
|
10
|
-
/** The
|
|
11
|
-
export type
|
|
10
|
+
/** The two states a card can sit in before an outcome exists. */
|
|
11
|
+
export type UnsettledStatus = typeof TOOL_CALL_STATUS.PENDING | typeof TOOL_CALL_STATUS.DEFERRED;
|
|
12
|
+
|
|
13
|
+
/** The terminal states a card settles into (everything unsettled excluded). */
|
|
14
|
+
export type SettledStatus = Exclude<ToolCallStatus, UnsettledStatus>;
|
|
12
15
|
|
|
13
16
|
/** Short pill text shown for each status, drawn from the string table. */
|
|
14
17
|
function statusLabels(strings: UiStrings): Record<ToolCallStatus, string> {
|
|
15
18
|
return {
|
|
16
19
|
[TOOL_CALL_STATUS.PENDING]: strings.toolRunning,
|
|
20
|
+
[TOOL_CALL_STATUS.DEFERRED]: strings.toolDeferred,
|
|
17
21
|
[TOOL_CALL_STATUS.DONE]: strings.toolDone,
|
|
18
22
|
[TOOL_CALL_STATUS.ERROR]: strings.toolError,
|
|
19
23
|
[TOOL_CALL_STATUS.DECLINED]: strings.toolDeclined,
|
|
@@ -64,6 +68,21 @@ export class ToolCallCard {
|
|
|
64
68
|
/** The card's root element; append this into the message list. */
|
|
65
69
|
readonly element: HTMLDivElement;
|
|
66
70
|
|
|
71
|
+
/**
|
|
72
|
+
* Where a question about *this* call renders — the approval prompt for a
|
|
73
|
+
* server-side tool the run deferred.
|
|
74
|
+
*
|
|
75
|
+
* It belongs to the card rather than to the transcript because a run can defer
|
|
76
|
+
* several calls at once, and a prompt written per *tool* ("Add this event to
|
|
77
|
+
* the board?") is identical for every one of them. Rendered into the answer
|
|
78
|
+
* group they were three anonymous copies of one question, below the three
|
|
79
|
+
* cards they gated; rendered here, position identifies them and the arguments
|
|
80
|
+
* are already on screen above the question.
|
|
81
|
+
*
|
|
82
|
+
* Empty until used, and hidden while empty by the shadow CSS.
|
|
83
|
+
*/
|
|
84
|
+
readonly approvalSlot: HTMLDivElement;
|
|
85
|
+
|
|
67
86
|
readonly #status: HTMLSpanElement;
|
|
68
87
|
readonly #decision: HTMLSpanElement;
|
|
69
88
|
readonly #toggle: HTMLButtonElement;
|
|
@@ -143,7 +162,26 @@ export class ToolCallCard {
|
|
|
143
162
|
body.setAttribute("part", "tool-card-body");
|
|
144
163
|
body.append(argsSection.root, resultSection.root);
|
|
145
164
|
|
|
146
|
-
this.
|
|
165
|
+
this.approvalSlot = document.createElement("div");
|
|
166
|
+
this.approvalSlot.className = "tool-call-approval";
|
|
167
|
+
this.approvalSlot.setAttribute("part", "tool-card-approval");
|
|
168
|
+
|
|
169
|
+
this.element.append(head, this.#toggle, body, this.approvalSlot);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Move between the two states that are not an outcome — `pending` (running)
|
|
174
|
+
* and `deferred` (gated, waiting on a person).
|
|
175
|
+
*
|
|
176
|
+
* Ignored once {@link settle} has run: a card that was declined must not be
|
|
177
|
+
* talked back into looking live by a late event.
|
|
178
|
+
*/
|
|
179
|
+
mark(status: UnsettledStatus): void {
|
|
180
|
+
if (this.#settled) {
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
this.element.setAttribute("data-status", status);
|
|
184
|
+
this.#status.textContent = statusLabels(this.#strings)[status];
|
|
147
185
|
}
|
|
148
186
|
|
|
149
187
|
/**
|
package/src/ui/ui_strings.ts
CHANGED
|
@@ -84,6 +84,8 @@ export interface UiStrings {
|
|
|
84
84
|
// ── Tool-call card ──────────────────────────────────────────────────────────
|
|
85
85
|
/** Status pill while the call runs. */
|
|
86
86
|
toolRunning: string;
|
|
87
|
+
/** Status pill on a gated call the run deferred, waiting on a person. */
|
|
88
|
+
toolDeferred: string;
|
|
87
89
|
/** Status pill on success. */
|
|
88
90
|
toolDone: string;
|
|
89
91
|
/** Status pill on error. */
|
|
@@ -244,6 +246,7 @@ export const DEFAULT_UI_STRINGS: UiStrings = {
|
|
|
244
246
|
transcriptionFailed: "Transcription failed",
|
|
245
247
|
|
|
246
248
|
toolRunning: "running…",
|
|
249
|
+
toolDeferred: "waiting for you",
|
|
247
250
|
toolDone: "✓ done",
|
|
248
251
|
toolError: "⚠ error",
|
|
249
252
|
toolDeclined: "⊘ declined",
|
package/src/version.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const VERSION: string = "0.
|
|
1
|
+
export const VERSION: string = "0.25.0";
|