@alexkroman1/aai-ui 13.3.0 → 14.0.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 +159 -69
- package/dist/{_colors-CZ6OlPbL.js → _colors-CpZO-88A.js} +24 -1
- package/dist/_recover-run.d.ts +2 -2
- package/dist/_submission-state.d.ts +92 -0
- package/dist/_upload-files.d.ts +2 -2
- package/dist/_upload-report.d.ts +26 -0
- package/dist/{_utils-CyzjK0gW.js → _utils-DnQDM9Uy.js} +3 -3
- package/dist/_utils.d.ts +3 -3
- package/dist/_web-storage.d.ts +43 -0
- package/dist/_workflow-files.d.ts +1 -1
- package/dist/agent-state-labels.d.ts +60 -0
- package/dist/audio.js +20 -14
- package/dist/{chat-view-Bv5VFJIE.js → chat-view-C_3T7Ln8.js} +96 -29
- package/dist/{client-config-DD820zHn.js → client-config-DJQHnYjm.js} +4 -4
- package/dist/client-config.d.ts +4 -4
- package/dist/client-dir.d.ts +1 -1
- package/dist/client-dir.js +1 -1
- package/dist/components/_colors.d.ts +23 -0
- package/dist/components/_form-readiness.d.ts +1 -1
- package/dist/components/bullet-list.d.ts +74 -0
- package/dist/components/button.js +3 -3
- package/dist/components/chat-view.js +1 -1
- package/dist/components/console-shell.d.ts +16 -20
- package/dist/components/controls.js +3 -3
- package/dist/components/facts.d.ts +81 -0
- package/dist/components/form-fields.d.ts +6 -6
- package/dist/components/form-types.d.ts +1 -1
- package/dist/components/form.d.ts +1 -1
- package/dist/components/message-list.js +1 -1
- package/dist/components/session-error-banner.d.ts +69 -0
- package/dist/components/start-screen.js +1 -1
- package/dist/components/tool-call-block.js +1 -1
- package/dist/components/tool-config-context.d.ts +1 -1
- package/dist/components/workflow-progress.d.ts +11 -4
- package/dist/context.d.ts +142 -19
- package/dist/context.js +155 -17
- package/dist/default-client/assets/{audio-BuDICbPf.js → audio-9zQsNc1w.js} +1 -1
- package/dist/default-client/assets/index-BTv30Z4F.css +2 -0
- package/dist/default-client/assets/index-RAZ-29Sz.js +284 -0
- package/dist/default-client/index.html +2 -2
- package/dist/define-client.d.ts +19 -19
- package/dist/define-client.js +19 -19
- package/dist/hooks.d.ts +44 -8
- package/dist/hooks.js +19 -13
- package/dist/index.d.ts +11 -7
- package/dist/index.js +418 -185
- package/dist/internal.d.ts +2 -2
- package/dist/internal.js +5 -5
- package/dist/{message-list-C0pL7x41.js → message-list-CdOnSh5m.js} +19 -12
- package/dist/page.d.ts +11 -11
- package/dist/session-core-audio-setup.d.ts +1 -1
- package/dist/session-core-dial.d.ts +0 -2
- package/dist/{session-core-C9elBIdu.js → session-core-gwePM95B.js} +125 -52
- package/dist/session-core-messages.d.ts +2 -2
- package/dist/session-core-types.d.ts +58 -1
- package/dist/session-core.d.ts +6 -6
- package/dist/session-core.js +2 -2
- package/dist/session-resume-store.d.ts +3 -3
- package/dist/{tool-call-block-Bunc6rCw.js → tool-call-block-C2t_5fpp.js} +27 -9
- package/dist/{tool-config-context-Bh8p3DtG.js → tool-config-context-Es4YUzV2.js} +1 -1
- package/dist/types.d.ts +19 -4
- package/dist/types.js +2 -2
- package/dist/{url-chips-C2u7QPv8.js → url-chips-BxhzZgk2.js} +4 -4
- package/dist/use-conversation.d.ts +1 -1
- package/dist/{use-user-transcript-DFTSEuZN.js → use-user-transcript-uyHhzy4d.js} +3 -2
- package/dist/use-workflow-form.d.ts +34 -2
- package/dist/{use-workflow-run-CXGEcM0l.js → use-workflow-run-CP2ekKPV.js} +3 -6
- package/dist/use-workflow-stream.d.ts +1 -1
- package/dist/workflow-client.d.ts +1 -1
- package/package.json +2 -2
- package/styles.css +78 -0
- package/dist/default-client/assets/index-B1_ROnTJ.js +0 -284
- package/dist/default-client/assets/index-S5fkKi6B.css +0 -2
package/dist/audio.js
CHANGED
|
@@ -132,7 +132,6 @@ async function createVoiceIO(opts) {
|
|
|
132
132
|
capture.node.onprocessorerror = () => onError?.(workletCrash("capture"));
|
|
133
133
|
capture.start();
|
|
134
134
|
let playNode = null;
|
|
135
|
-
let onPlaybackStop = null;
|
|
136
135
|
/**
|
|
137
136
|
* Turn ids for the drain handshake. Every `done()` posts a fresh id and the
|
|
138
137
|
* worklet echoes it on the matching 'stop', so a stop the worklet posted for
|
|
@@ -143,18 +142,26 @@ async function createVoiceIO(opts) {
|
|
|
143
142
|
* reply finished while it is still speaking.
|
|
144
143
|
*/
|
|
145
144
|
let turnSeq = 0;
|
|
146
|
-
|
|
145
|
+
/**
|
|
146
|
+
* The drain wait in flight, if any.
|
|
147
|
+
*
|
|
148
|
+
* ONE field, because the resolver and the turn it belongs to are one fact:
|
|
149
|
+
* as two independently-nulled variables they could disagree, and did — the
|
|
150
|
+
* suspended-context branch of `done()` cleared the turn and left the resolver
|
|
151
|
+
* set. A single nullable makes that unrepresentable.
|
|
152
|
+
*/
|
|
153
|
+
let pendingStop = null;
|
|
147
154
|
const lifecycle = new AbortController();
|
|
148
155
|
/** Settle whatever drain wait is pending and forget the turn it belonged to. */
|
|
149
156
|
function settlePendingStop() {
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
157
|
+
const pending = pendingStop;
|
|
158
|
+
pendingStop = null;
|
|
159
|
+
pending?.settle();
|
|
153
160
|
}
|
|
154
161
|
function onWorkletStop(msg) {
|
|
155
162
|
if (msg.stats && msg.stats.concealedSamples > 0) onPlaybackStats?.(msg.stats);
|
|
156
163
|
if (msg.reason === "interrupt") return;
|
|
157
|
-
if (
|
|
164
|
+
if (pendingStop !== null && msg.turn !== pendingStop.turn) return;
|
|
158
165
|
settlePendingStop();
|
|
159
166
|
}
|
|
160
167
|
function ensurePlayNode() {
|
|
@@ -190,26 +197,25 @@ async function createVoiceIO(opts) {
|
|
|
190
197
|
turn
|
|
191
198
|
});
|
|
192
199
|
if (ctx.state !== "running") {
|
|
193
|
-
|
|
200
|
+
settlePendingStop();
|
|
194
201
|
return Promise.resolve();
|
|
195
202
|
}
|
|
196
203
|
const { promise, resolve } = Promise.withResolvers();
|
|
197
|
-
|
|
204
|
+
settlePendingStop();
|
|
198
205
|
const settle = () => {
|
|
199
206
|
clearInterval(poll);
|
|
200
207
|
clearTimeout(cap);
|
|
201
|
-
if (
|
|
202
|
-
onPlaybackStop = null;
|
|
203
|
-
pendingStopTurn = null;
|
|
204
|
-
}
|
|
208
|
+
if (pendingStop?.settle === settle) pendingStop = null;
|
|
205
209
|
resolve();
|
|
206
210
|
};
|
|
207
211
|
const poll = setInterval(() => {
|
|
208
212
|
if (ctx.state !== "running") settle();
|
|
209
213
|
}, PLAYBACK_DONE_POLL_MS);
|
|
210
214
|
const cap = setTimeout(settle, PLAYBACK_DONE_MAX_WAIT_MS);
|
|
211
|
-
|
|
212
|
-
|
|
215
|
+
pendingStop = {
|
|
216
|
+
turn,
|
|
217
|
+
settle
|
|
218
|
+
};
|
|
213
219
|
return promise;
|
|
214
220
|
},
|
|
215
221
|
flush() {
|
|
@@ -1,11 +1,85 @@
|
|
|
1
|
-
import { t as MessageList } from "./message-list-
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { t as MessageList } from "./message-list-CdOnSh5m.js";
|
|
2
|
+
import { useSessionError, useSessionStatus, useTheme } from "./context.js";
|
|
3
|
+
import { a as inkTint, r as THINKING_COLOR, t as ERROR_COLOR } from "./_colors-CpZO-88A.js";
|
|
4
4
|
import { t as AaiLogo } from "./aai-logo-CFlomZlS.js";
|
|
5
5
|
import { t as Eyebrow } from "./eyebrow-UfmSz9yy.js";
|
|
6
6
|
import { Controls } from "./components/controls.js";
|
|
7
7
|
import clsx from "clsx";
|
|
8
8
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
9
|
+
//#region src/components/session-error-banner.tsx
|
|
10
|
+
/** @jsxImportSource react */
|
|
11
|
+
/**
|
|
12
|
+
* The announced banner for a failed session: the error's message and code, or
|
|
13
|
+
* nothing at all when the session is fine.
|
|
14
|
+
*
|
|
15
|
+
* **This used to be four lines inside `ConsoleShell`, and that is why it is its
|
|
16
|
+
* own component.** The banner was the reason `ConsoleShell` was published —
|
|
17
|
+
* `role="alert"` is the one part of that component a reviewer cannot see is
|
|
18
|
+
* missing, since per the `fatalError` latch in `session-core.ts` the banner is
|
|
19
|
+
* the ONLY remaining signal a session died (the state eyebrow beside it goes
|
|
20
|
+
* back to reading like a live session), and a screen reader is never told an
|
|
21
|
+
* unannounced one appeared. But `ConsoleShell` is a whole FRAME: a centred
|
|
22
|
+
* `max-w-190` column with its own header and footer. Every full-bleed chrome —
|
|
23
|
+
* a two-pane board, a CRT — therefore could not adopt it, rebuilt the banner
|
|
24
|
+
* instead, and the three that did had ALREADY drifted: one rendered
|
|
25
|
+
* `ERROR: {message}` and dropped the code entirely, one `ERROR: {message}
|
|
26
|
+
* ({code})`, one `{message} ({code})`. Splitting the banner out is what lets a
|
|
27
|
+
* chrome take the announced-error decision without taking the layout, and
|
|
28
|
+
* `ConsoleShell` composes this rather than keeping a second copy, so the two
|
|
29
|
+
* cannot drift again.
|
|
30
|
+
*
|
|
31
|
+
* **It reads the session itself.** There is no `error` prop: a banner that
|
|
32
|
+
* takes its text from the caller is a banner a caller can forget to wire, which
|
|
33
|
+
* is exactly the failure above with an extra step. It subscribes narrowly via
|
|
34
|
+
* {@link useSessionError}, so a page that renders it does not re-render with
|
|
35
|
+
* the transcript.
|
|
36
|
+
*
|
|
37
|
+
* **The code is shown, always.** `SessionError.code` is the eight-member wire
|
|
38
|
+
* union — it is what a user pastes into a bug report and the only part of the
|
|
39
|
+
* error that is stable across wordings — and the chrome that dropped it left
|
|
40
|
+
* its readers with a sentence and no way to say which failure it was.
|
|
41
|
+
*
|
|
42
|
+
* Must be rendered inside the providers `client()` installs.
|
|
43
|
+
*
|
|
44
|
+
* @example A full-bleed chrome that wants the banner and not the frame
|
|
45
|
+
* ```tsx
|
|
46
|
+
* import { SessionErrorBanner } from "@alexkroman1/aai-ui";
|
|
47
|
+
*
|
|
48
|
+
* function Board() {
|
|
49
|
+
* return (
|
|
50
|
+
* <div className="grid grid-cols-[1fr_320px] h-screen">
|
|
51
|
+
* <main>…</main>
|
|
52
|
+
* <aside>…</aside>
|
|
53
|
+
* <SessionErrorBanner className="col-span-2" />
|
|
54
|
+
* </div>
|
|
55
|
+
* );
|
|
56
|
+
* }
|
|
57
|
+
* ```
|
|
58
|
+
*
|
|
59
|
+
* @param props - See {@link SessionErrorBannerProps}.
|
|
60
|
+
*
|
|
61
|
+
* @public
|
|
62
|
+
*/
|
|
63
|
+
function SessionErrorBanner({ className }) {
|
|
64
|
+
const error = useSessionError();
|
|
65
|
+
if (!error) return null;
|
|
66
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
67
|
+
role: "alert",
|
|
68
|
+
className: clsx("px-3.5 py-2.5 rounded-aai border text-[13px] leading-[130%] shrink-0", className),
|
|
69
|
+
style: {
|
|
70
|
+
borderColor: "rgba(179,38,30,0.35)",
|
|
71
|
+
background: "rgba(179,38,30,0.06)",
|
|
72
|
+
color: ERROR_COLOR
|
|
73
|
+
},
|
|
74
|
+
children: [
|
|
75
|
+
error.message,
|
|
76
|
+
" (",
|
|
77
|
+
error.code,
|
|
78
|
+
")"
|
|
79
|
+
]
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
//#endregion
|
|
9
83
|
//#region src/components/console-shell.tsx
|
|
10
84
|
/** @jsxImportSource react */
|
|
11
85
|
/**
|
|
@@ -36,16 +110,22 @@ function stateColor(state, primary, idle) {
|
|
|
36
110
|
* `<Controls>` under it, and until now that was the only way to get it — the
|
|
37
111
|
* shell itself was internal, so a client wanting its own conversation markup
|
|
38
112
|
* had to rebuild the chrome as well. Each one that did re-derived the error
|
|
39
|
-
* banner WITHOUT `role="alert"
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
113
|
+
* banner WITHOUT `role="alert"`.
|
|
114
|
+
*
|
|
115
|
+
* **The banner is {@link SessionErrorBanner} now, composed here rather than
|
|
116
|
+
* spelled out.** It was four lines of this file, and this file is a whole
|
|
117
|
+
* FRAME — a centred `max-w-190` column — so the full-bleed chromes that needed
|
|
118
|
+
* the announced banner could not take it without taking a layout that would
|
|
119
|
+
* replace the design they exist to demonstrate. Composing means there is one
|
|
120
|
+
* banner, and it means this component no longer takes an `error` prop: the
|
|
121
|
+
* banner reads the session itself, which is one fewer thing a caller can wire
|
|
122
|
+
* up wrong.
|
|
44
123
|
*
|
|
45
124
|
* Reach for it when the conversation is yours and the frame is not. Reach for
|
|
46
|
-
* `<ChatView>` when both are ours.
|
|
125
|
+
* `<ChatView>` when both are ours. Reach for `<SessionErrorBanner>` alone when
|
|
126
|
+
* neither is.
|
|
47
127
|
*
|
|
48
|
-
* Must be rendered inside the providers `
|
|
128
|
+
* Must be rendered inside the providers `mountClient()` installs.
|
|
49
129
|
*
|
|
50
130
|
* @example A custom conversation in the stock chrome
|
|
51
131
|
* ```tsx
|
|
@@ -53,19 +133,17 @@ function stateColor(state, primary, idle) {
|
|
|
53
133
|
* ConsoleShell,
|
|
54
134
|
* Controls,
|
|
55
135
|
* useConversation,
|
|
56
|
-
*
|
|
136
|
+
* useSessionStatus,
|
|
57
137
|
* } from "@alexkroman1/aai-ui";
|
|
58
138
|
*
|
|
59
139
|
* function Console() {
|
|
60
|
-
* const state =
|
|
61
|
-
* const error = useSessionSelector((s) => s.error);
|
|
140
|
+
* const state = useSessionStatus();
|
|
62
141
|
* const { items } = useConversation();
|
|
63
142
|
* return (
|
|
64
143
|
* <ConsoleShell
|
|
65
144
|
* title="Dispatch"
|
|
66
145
|
* state={state}
|
|
67
146
|
* pulsing={state === "listening"}
|
|
68
|
-
* error={error?.message}
|
|
69
147
|
* footer={<Controls />}
|
|
70
148
|
* >
|
|
71
149
|
* <ul>
|
|
@@ -84,7 +162,7 @@ function stateColor(state, primary, idle) {
|
|
|
84
162
|
*
|
|
85
163
|
* @public
|
|
86
164
|
*/
|
|
87
|
-
function ConsoleShell({ icon, title, state, pulsing,
|
|
165
|
+
function ConsoleShell({ icon, title, state, pulsing, children, footer, className }) {
|
|
88
166
|
const theme = useTheme();
|
|
89
167
|
return /* @__PURE__ */ jsxs("div", {
|
|
90
168
|
className: clsx("flex flex-col h-screen w-full max-w-190 mx-auto box-border px-6 py-8 gap-5 font-aai text-sm", className),
|
|
@@ -114,16 +192,7 @@ function ConsoleShell({ icon, title, state, pulsing, error, children, footer, cl
|
|
|
114
192
|
}), state]
|
|
115
193
|
})]
|
|
116
194
|
}),
|
|
117
|
-
|
|
118
|
-
role: "alert",
|
|
119
|
-
className: "px-3.5 py-2.5 rounded-aai border text-[13px] leading-[130%] shrink-0",
|
|
120
|
-
style: {
|
|
121
|
-
borderColor: "rgba(179,38,30,0.35)",
|
|
122
|
-
background: "rgba(179,38,30,0.06)",
|
|
123
|
-
color: "#B3261E"
|
|
124
|
-
},
|
|
125
|
-
children: error
|
|
126
|
-
}),
|
|
195
|
+
/* @__PURE__ */ jsx(SessionErrorBanner, {}),
|
|
127
196
|
/* @__PURE__ */ jsx("div", {
|
|
128
197
|
className: "flex flex-col flex-1 min-h-0 border rounded-lg overflow-hidden",
|
|
129
198
|
style: {
|
|
@@ -166,18 +235,16 @@ const PULSING_STATES = /* @__PURE__ */ new Set(["listening", "speaking"]);
|
|
|
166
235
|
* @public
|
|
167
236
|
*/
|
|
168
237
|
function ChatView({ icon, title, className }) {
|
|
169
|
-
const state =
|
|
170
|
-
const error = useSessionSelector((s) => s.error);
|
|
238
|
+
const state = useSessionStatus();
|
|
171
239
|
return /* @__PURE__ */ jsx(ConsoleShell, {
|
|
172
240
|
icon,
|
|
173
241
|
title,
|
|
174
242
|
state,
|
|
175
243
|
pulsing: PULSING_STATES.has(state),
|
|
176
|
-
error: error?.message,
|
|
177
244
|
className,
|
|
178
245
|
footer: /* @__PURE__ */ jsx(Controls, {}),
|
|
179
246
|
children: /* @__PURE__ */ jsx(MessageList, {})
|
|
180
247
|
});
|
|
181
248
|
}
|
|
182
249
|
//#endregion
|
|
183
|
-
export { ConsoleShell as n, ChatView as t };
|
|
250
|
+
export { ConsoleShell as n, SessionErrorBanner as r, ChatView as t };
|
|
@@ -84,8 +84,8 @@ async function loadClientConfig(platformUrl, fetchFn) {
|
|
|
84
84
|
* yields the agent default (`{}`).
|
|
85
85
|
*
|
|
86
86
|
* **This is what a workflow app calls instead of receiving the config.**
|
|
87
|
-
* `
|
|
88
|
-
* default chat shell, so a voice client never has to. `
|
|
87
|
+
* `mountClient()` fetches `GET client-config` for itself before it renders the
|
|
88
|
+
* default chat shell, so a voice client never has to. `mountPage()` mounts no
|
|
89
89
|
* session and makes no such request — deliberately, since a page has no shell
|
|
90
90
|
* to put a name in — so a page that wants the agent's own `name` or `greeting`
|
|
91
91
|
* asks for them here.
|
|
@@ -107,7 +107,7 @@ async function loadClientConfig(platformUrl, fetchFn) {
|
|
|
107
107
|
*
|
|
108
108
|
* @example
|
|
109
109
|
* ```tsx
|
|
110
|
-
* import { fetchClientConfig,
|
|
110
|
+
* import { fetchClientConfig, mountPage } from "@alexkroman1/aai-ui";
|
|
111
111
|
*
|
|
112
112
|
* const { name, greeting } = await fetchClientConfig(
|
|
113
113
|
* location.origin + location.pathname,
|
|
@@ -122,7 +122,7 @@ async function loadClientConfig(platformUrl, fetchFn) {
|
|
|
122
122
|
* );
|
|
123
123
|
* }
|
|
124
124
|
*
|
|
125
|
-
*
|
|
125
|
+
* mountPage({ name: name ?? "Workflows", component: App });
|
|
126
126
|
* ```
|
|
127
127
|
*
|
|
128
128
|
* @public
|
package/dist/client-config.d.ts
CHANGED
|
@@ -72,8 +72,8 @@ export declare function loadClientConfig(platformUrl: string, fetchFn?: typeof g
|
|
|
72
72
|
* yields the agent default (`{}`).
|
|
73
73
|
*
|
|
74
74
|
* **This is what a workflow app calls instead of receiving the config.**
|
|
75
|
-
* `
|
|
76
|
-
* default chat shell, so a voice client never has to. `
|
|
75
|
+
* `mountClient()` fetches `GET client-config` for itself before it renders the
|
|
76
|
+
* default chat shell, so a voice client never has to. `mountPage()` mounts no
|
|
77
77
|
* session and makes no such request — deliberately, since a page has no shell
|
|
78
78
|
* to put a name in — so a page that wants the agent's own `name` or `greeting`
|
|
79
79
|
* asks for them here.
|
|
@@ -95,7 +95,7 @@ export declare function loadClientConfig(platformUrl: string, fetchFn?: typeof g
|
|
|
95
95
|
*
|
|
96
96
|
* @example
|
|
97
97
|
* ```tsx
|
|
98
|
-
* import { fetchClientConfig,
|
|
98
|
+
* import { fetchClientConfig, mountPage } from "@alexkroman1/aai-ui";
|
|
99
99
|
*
|
|
100
100
|
* const { name, greeting } = await fetchClientConfig(
|
|
101
101
|
* location.origin + location.pathname,
|
|
@@ -110,7 +110,7 @@ export declare function loadClientConfig(platformUrl: string, fetchFn?: typeof g
|
|
|
110
110
|
* );
|
|
111
111
|
* }
|
|
112
112
|
*
|
|
113
|
-
*
|
|
113
|
+
* mountPage({ name: name ?? "Workflows", component: App });
|
|
114
114
|
* ```
|
|
115
115
|
*
|
|
116
116
|
* @public
|
package/dist/client-dir.d.ts
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
*/
|
|
16
16
|
/**
|
|
17
17
|
* Absolute path to the prebuilt browser client's static files — pass it to
|
|
18
|
-
* `
|
|
18
|
+
* `createRuntimeServer`/`createAgentServer` as `clientDir`.
|
|
19
19
|
*
|
|
20
20
|
* A function, not a constant: resolution touches the module graph and throws
|
|
21
21
|
* when the package is missing, and a module-level constant would move that
|
package/dist/client-dir.js
CHANGED
|
@@ -19,7 +19,7 @@ import path from "node:path";
|
|
|
19
19
|
const require = createRequire(import.meta.url);
|
|
20
20
|
/**
|
|
21
21
|
* Absolute path to the prebuilt browser client's static files — pass it to
|
|
22
|
-
* `
|
|
22
|
+
* `createRuntimeServer`/`createAgentServer` as `clientDir`.
|
|
23
23
|
*
|
|
24
24
|
* A function, not a constant: resolution touches the module graph and throws
|
|
25
25
|
* when the package is missing, and a module-level constant would move that
|
|
@@ -48,3 +48,26 @@ export declare const THINKING_COLOR = "#B98900";
|
|
|
48
48
|
* coherent tinted bubble instead of a hardcoded indigo.
|
|
49
49
|
*/
|
|
50
50
|
export declare function primaryTint(primary: string, surface: string, pct: number): string;
|
|
51
|
+
/**
|
|
52
|
+
* The focus ring, as the class half of a pair.
|
|
53
|
+
*
|
|
54
|
+
* The ring is the only thing a keyboard user gets, and it has regressed to
|
|
55
|
+
* invisible twice — `outline-none` shipped here with no replacement, on the
|
|
56
|
+
* button and then on the URL chips, which is WCAG 2.4.7 on every control in the
|
|
57
|
+
* default client. Written as a SHORTHAND so it cannot be undone by Tailwind's
|
|
58
|
+
* shared `--tw-outline-style` variable.
|
|
59
|
+
*
|
|
60
|
+
* It is inert on its own: an outline with no colour draws nothing, so a control
|
|
61
|
+
* using this must also spread {@link focusRingStyle}. Naming the two together
|
|
62
|
+
* is what stops the next control shipping one without the other.
|
|
63
|
+
*/
|
|
64
|
+
export declare const FOCUS_RING = "outline-none focus-visible:[outline:2px_solid] focus-visible:[outline-offset:2px]";
|
|
65
|
+
/**
|
|
66
|
+
* The style half of {@link FOCUS_RING} — the colour the outline draws in.
|
|
67
|
+
*
|
|
68
|
+
* A real CSS property rather than a variant class, so the `focus-visible` rule
|
|
69
|
+
* only has to turn the outline on.
|
|
70
|
+
*/
|
|
71
|
+
export declare function focusRingStyle(primary: string): {
|
|
72
|
+
outlineColor: string;
|
|
73
|
+
};
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
* one describes an element that EXISTS. So this is a context rather than an
|
|
25
25
|
* attribute, and it carries the one fact a DOM read cannot recover.
|
|
26
26
|
*
|
|
27
|
-
* A form with no such children is ready by definition — `
|
|
27
|
+
* A form with no such children is ready by definition — `useFormReadiness`
|
|
28
28
|
* outside a provider reports nothing pending, so every hand-written form is
|
|
29
29
|
* unaffected and `Form` keeps working outside this package.
|
|
30
30
|
*/
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
/**
|
|
3
|
+
* Props for {@link BulletList}.
|
|
4
|
+
*
|
|
5
|
+
* @public
|
|
6
|
+
*/
|
|
7
|
+
export type BulletListProps = {
|
|
8
|
+
/**
|
|
9
|
+
* The bullets, in the order they should read.
|
|
10
|
+
*
|
|
11
|
+
* TEXT rather than `ReactNode`, deliberately: every list this replaced was a
|
|
12
|
+
* string array straight off a run's output, and taking strings is what lets
|
|
13
|
+
* this component key them (see the component doc). A page that needs a link
|
|
14
|
+
* inside a bullet wants its own `<ul>`, not a prop here.
|
|
15
|
+
*/
|
|
16
|
+
items: readonly string[];
|
|
17
|
+
/**
|
|
18
|
+
* Rendered as a heading above the list, inside a wrapping `<section>`.
|
|
19
|
+
*
|
|
20
|
+
* Omitted (or `null`/`false`, so `title={cond && "Risks"}` means what it
|
|
21
|
+
* looks like) renders the bare `<ul>` with no wrapper — which is what four of
|
|
22
|
+
* the five lists this replaced were.
|
|
23
|
+
*/
|
|
24
|
+
title?: ReactNode | undefined;
|
|
25
|
+
/**
|
|
26
|
+
* `"sm"` adds `text-sm`, which two of the five copies carried and three did
|
|
27
|
+
* not. `"base"` is the default and adds nothing.
|
|
28
|
+
*/
|
|
29
|
+
size?: "sm" | "base" | undefined;
|
|
30
|
+
/**
|
|
31
|
+
* ADDED to the list's own classes rather than replacing them. There is no
|
|
32
|
+
* `tailwind-merge` in this package, so a class that CONFLICTS with a base one
|
|
33
|
+
* is not reliably the winner — use this for additions, not overrides.
|
|
34
|
+
*/
|
|
35
|
+
className?: string | undefined;
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* A disc-bulleted list of short strings — a run's key points, findings, risks.
|
|
39
|
+
*
|
|
40
|
+
* Five pages had written this, byte-identical apart from a `text-sm` suffix on
|
|
41
|
+
* two of them, and all five had the same two defects. Both are the reason this
|
|
42
|
+
* is a component rather than four lines a page repeats:
|
|
43
|
+
*
|
|
44
|
+
* - **All five keyed by the string itself, and these lists are MODEL OUTPUT.**
|
|
45
|
+
* Two identical bullets are entirely plausible — a summariser that repeats
|
|
46
|
+
* itself is a bad summary, not a bad program — and a repeated string is then
|
|
47
|
+
* a duplicate `key`: React warns, and the two `<li>`s contend for one slot in
|
|
48
|
+
* the reconciliation. What is keyed here instead is the content PLUS how many
|
|
49
|
+
* times that content has already appeared in this list, which is unique by
|
|
50
|
+
* construction and unchanged by a re-render that did not change the text.
|
|
51
|
+
* (Position alone would also be sound — these lists are replaced wholesale by
|
|
52
|
+
* each new output and never reordered — but it is what `noArrayIndexKey`
|
|
53
|
+
* exists to talk you out of, and a unique key is one `Map` away, so there is
|
|
54
|
+
* no reason to spend a lint suppression on it.)
|
|
55
|
+
* - **Three of the five rendered an empty `<ul>` under a heading.** Two had
|
|
56
|
+
* hand-rolled `if (items.length === 0) return null` and three had not, so the
|
|
57
|
+
* same absent field was "nothing" on two pages and a stray heading with a
|
|
58
|
+
* void under it on three. Emptiness renders NOTHING here, `title` included:
|
|
59
|
+
* a heading over no bullets is a claim the run did not make.
|
|
60
|
+
*
|
|
61
|
+
* @example
|
|
62
|
+
* ```tsx
|
|
63
|
+
* import { BulletList } from "@alexkroman1/aai-ui";
|
|
64
|
+
*
|
|
65
|
+
* function Findings({ risks }: { risks: string[] }) {
|
|
66
|
+
* return <BulletList title="Risks" items={risks} size="sm" />;
|
|
67
|
+
* }
|
|
68
|
+
* ```
|
|
69
|
+
*
|
|
70
|
+
* @param props - Bullet-list props.
|
|
71
|
+
*
|
|
72
|
+
* @public
|
|
73
|
+
*/
|
|
74
|
+
export declare function BulletList({ items, title, size, className }: BulletListProps): ReactNode;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useTheme } from "../context.js";
|
|
2
|
-
import { i as
|
|
2
|
+
import { a as inkTint, i as focusRingStyle, n as FOCUS_RING, o as primaryTint } from "../_colors-CpZO-88A.js";
|
|
3
3
|
import clsx from "clsx";
|
|
4
4
|
import { jsx } from "react/jsx-runtime";
|
|
5
5
|
//#region src/components/button.tsx
|
|
@@ -70,13 +70,13 @@ function Button({ variant = "default", size = "default", className, children, st
|
|
|
70
70
|
"--aai-btn-bg-hover": colors.hoverBg,
|
|
71
71
|
"--aai-btn-fg-hover": colors.hoverFg,
|
|
72
72
|
"--aai-btn-bd-hover": colors.hoverBorder,
|
|
73
|
-
|
|
73
|
+
...focusRingStyle(theme.primary),
|
|
74
74
|
...style
|
|
75
75
|
};
|
|
76
76
|
return /* @__PURE__ */ jsx("button", {
|
|
77
77
|
type: "button",
|
|
78
78
|
style: vars,
|
|
79
|
-
className: clsx("inline-flex items-center justify-center appearance-none m-0 w-fit whitespace-nowrap", size === "lg" ? "h-11 px-7 text-xs" : "h-9 px-5 text-[11px]", "rounded-aai font-aai font-medium tracking-[1.4px] uppercase leading-none", "cursor-pointer border transition-colors duration-150", "bg-(--aai-btn-bg) text-(--aai-btn-fg) border-(--aai-btn-bd)", "enabled:hover:bg-(--aai-btn-bg-hover) enabled:hover:text-(--aai-btn-fg-hover)", "enabled:hover:border-(--aai-btn-bd-hover)",
|
|
79
|
+
className: clsx("inline-flex items-center justify-center appearance-none m-0 w-fit whitespace-nowrap", size === "lg" ? "h-11 px-7 text-xs" : "h-9 px-5 text-[11px]", "rounded-aai font-aai font-medium tracking-[1.4px] uppercase leading-none", "cursor-pointer border transition-colors duration-150", "bg-(--aai-btn-bg) text-(--aai-btn-fg) border-(--aai-btn-bd)", "enabled:hover:bg-(--aai-btn-bg-hover) enabled:hover:text-(--aai-btn-fg-hover)", "enabled:hover:border-(--aai-btn-bd-hover)", FOCUS_RING, "disabled:cursor-not-allowed disabled:opacity-50", className),
|
|
80
80
|
...rest,
|
|
81
81
|
children
|
|
82
82
|
});
|
|
@@ -14,14 +14,6 @@ export type ConsoleShellProps = {
|
|
|
14
14
|
state: AgentState;
|
|
15
15
|
/** Whether the status dot pulses. */
|
|
16
16
|
pulsing: boolean;
|
|
17
|
-
/**
|
|
18
|
-
* Error banner text; `null`/`undefined` hides the banner.
|
|
19
|
-
*
|
|
20
|
-
* Pass `session.error?.message` — the banner is announced, which a
|
|
21
|
-
* hand-rolled `<div>` in a custom chrome is not. See the `role="alert"`
|
|
22
|
-
* comment below for why that matters more here than it looks.
|
|
23
|
-
*/
|
|
24
|
-
error?: string | null | undefined;
|
|
25
17
|
/** Card content — normally a {@link MessageList}. */
|
|
26
18
|
children: ReactNode;
|
|
27
19
|
/** Row rendered beneath the card (controls). */
|
|
@@ -38,16 +30,22 @@ export type ConsoleShellProps = {
|
|
|
38
30
|
* `<Controls>` under it, and until now that was the only way to get it — the
|
|
39
31
|
* shell itself was internal, so a client wanting its own conversation markup
|
|
40
32
|
* had to rebuild the chrome as well. Each one that did re-derived the error
|
|
41
|
-
* banner WITHOUT `role="alert"
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
33
|
+
* banner WITHOUT `role="alert"`.
|
|
34
|
+
*
|
|
35
|
+
* **The banner is {@link SessionErrorBanner} now, composed here rather than
|
|
36
|
+
* spelled out.** It was four lines of this file, and this file is a whole
|
|
37
|
+
* FRAME — a centred `max-w-190` column — so the full-bleed chromes that needed
|
|
38
|
+
* the announced banner could not take it without taking a layout that would
|
|
39
|
+
* replace the design they exist to demonstrate. Composing means there is one
|
|
40
|
+
* banner, and it means this component no longer takes an `error` prop: the
|
|
41
|
+
* banner reads the session itself, which is one fewer thing a caller can wire
|
|
42
|
+
* up wrong.
|
|
46
43
|
*
|
|
47
44
|
* Reach for it when the conversation is yours and the frame is not. Reach for
|
|
48
|
-
* `<ChatView>` when both are ours.
|
|
45
|
+
* `<ChatView>` when both are ours. Reach for `<SessionErrorBanner>` alone when
|
|
46
|
+
* neither is.
|
|
49
47
|
*
|
|
50
|
-
* Must be rendered inside the providers `
|
|
48
|
+
* Must be rendered inside the providers `mountClient()` installs.
|
|
51
49
|
*
|
|
52
50
|
* @example A custom conversation in the stock chrome
|
|
53
51
|
* ```tsx
|
|
@@ -55,19 +53,17 @@ export type ConsoleShellProps = {
|
|
|
55
53
|
* ConsoleShell,
|
|
56
54
|
* Controls,
|
|
57
55
|
* useConversation,
|
|
58
|
-
*
|
|
56
|
+
* useSessionStatus,
|
|
59
57
|
* } from "@alexkroman1/aai-ui";
|
|
60
58
|
*
|
|
61
59
|
* function Console() {
|
|
62
|
-
* const state =
|
|
63
|
-
* const error = useSessionSelector((s) => s.error);
|
|
60
|
+
* const state = useSessionStatus();
|
|
64
61
|
* const { items } = useConversation();
|
|
65
62
|
* return (
|
|
66
63
|
* <ConsoleShell
|
|
67
64
|
* title="Dispatch"
|
|
68
65
|
* state={state}
|
|
69
66
|
* pulsing={state === "listening"}
|
|
70
|
-
* error={error?.message}
|
|
71
67
|
* footer={<Controls />}
|
|
72
68
|
* >
|
|
73
69
|
* <ul>
|
|
@@ -86,4 +82,4 @@ export type ConsoleShellProps = {
|
|
|
86
82
|
*
|
|
87
83
|
* @public
|
|
88
84
|
*/
|
|
89
|
-
export declare function ConsoleShell({ icon, title, state, pulsing,
|
|
85
|
+
export declare function ConsoleShell({ icon, title, state, pulsing, children, footer, className, }: ConsoleShellProps): ReactNode;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useSessionCore, useSessionSelector } from "../context.js";
|
|
2
2
|
import { Button } from "./button.js";
|
|
3
|
-
import { n as SessionUrlChips } from "../url-chips-
|
|
3
|
+
import { n as SessionUrlChips } from "../url-chips-BxhzZgk2.js";
|
|
4
4
|
import clsx from "clsx";
|
|
5
5
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
6
6
|
import { memo } from "react";
|
|
@@ -27,7 +27,7 @@ import { memo } from "react";
|
|
|
27
27
|
*/
|
|
28
28
|
const Controls = memo(function Controls({ className }) {
|
|
29
29
|
const running = useSessionSelector((s) => s.running);
|
|
30
|
-
const { toggle,
|
|
30
|
+
const { toggle, restart } = useSessionCore();
|
|
31
31
|
return /* @__PURE__ */ jsxs("div", {
|
|
32
32
|
className: clsx("flex flex-wrap items-center gap-3 shrink-0", className),
|
|
33
33
|
children: [
|
|
@@ -38,7 +38,7 @@ const Controls = memo(function Controls({ className }) {
|
|
|
38
38
|
}),
|
|
39
39
|
/* @__PURE__ */ jsx(Button, {
|
|
40
40
|
variant: "ghost",
|
|
41
|
-
onClick:
|
|
41
|
+
onClick: restart,
|
|
42
42
|
children: "New Conversation"
|
|
43
43
|
}),
|
|
44
44
|
/* @__PURE__ */ jsx(SessionUrlChips, { className: "basis-full sm:basis-auto sm:ml-auto sm:max-w-[60%]" })
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
/**
|
|
3
|
+
* Props for {@link Facts}.
|
|
4
|
+
*
|
|
5
|
+
* @public
|
|
6
|
+
*/
|
|
7
|
+
export type FactsProps = {
|
|
8
|
+
/**
|
|
9
|
+
* The facts, in reading order. Anything `false`, `null`, `undefined` or the
|
|
10
|
+
* empty string is DROPPED, so a page writes `cond && \`${n} skipped\`` inline
|
|
11
|
+
* instead of splicing a separator into a conditional string.
|
|
12
|
+
*
|
|
13
|
+
* `0` is NOT dropped — it is a fact ("0 words"), and treating it as absent is
|
|
14
|
+
* the bug a plain truthiness filter would ship.
|
|
15
|
+
*
|
|
16
|
+
* They are TEXT rather than `ReactNode`: every one of the nine lines this
|
|
17
|
+
* replaced was a string, and taking strings is what lets this JOIN them (see
|
|
18
|
+
* the component doc) instead of interleaving keyed separator elements.
|
|
19
|
+
*/
|
|
20
|
+
items: readonly (string | number | false | null | undefined)[];
|
|
21
|
+
/**
|
|
22
|
+
* Which of the two muted typographies the pages use. `"sm"` is
|
|
23
|
+
* `text-sm opacity-70`, `"xs"` is `text-xs opacity-60` — the size and the
|
|
24
|
+
* muting move together, because that is the pair every site had.
|
|
25
|
+
*/
|
|
26
|
+
size?: "sm" | "xs" | undefined;
|
|
27
|
+
/**
|
|
28
|
+
* The element to render. `"p"` by default; `"span"` for a line that sits
|
|
29
|
+
* inside phrasing content, where a `<p>` is invalid nesting the browser will
|
|
30
|
+
* reparent out from under React.
|
|
31
|
+
*/
|
|
32
|
+
as?: "p" | "span" | undefined;
|
|
33
|
+
/**
|
|
34
|
+
* ADDED to the base classes rather than replacing them — `tabular-nums`,
|
|
35
|
+
* `uppercase tracking-[1.2px]`. There is no `tailwind-merge` in this package,
|
|
36
|
+
* so a class that CONFLICTS with a base one is not reliably the winner.
|
|
37
|
+
*/
|
|
38
|
+
className?: string | undefined;
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* A muted line of run facts, joined by `·` — "6 segments · 12:04 of audio ·
|
|
42
|
+
* 1,840 words".
|
|
43
|
+
*
|
|
44
|
+
* Nine pages had written this by hand under four different typographies for
|
|
45
|
+
* one role, two of them (`call-audit` and `spoken-summary`) byte-identical down
|
|
46
|
+
* to the payload. Three things it takes off the caller:
|
|
47
|
+
*
|
|
48
|
+
* - **The separator cannot be forgotten, and neither can the space around it.**
|
|
49
|
+
* Four of the nine carried a literal `{" "}` at the end of a line, because
|
|
50
|
+
* Prettier's wrap ate the space that made `· ` read as a separator rather
|
|
51
|
+
* than as punctuation glued to the next word. A line that is correct only
|
|
52
|
+
* because somebody remembered an invisible JSX expression is exactly the
|
|
53
|
+
* thing a component should own.
|
|
54
|
+
* - **A fact worth omitting is omitted, and by the caller's own condition.**
|
|
55
|
+
* The hand-written shape for a conditional fact was to splice the separator
|
|
56
|
+
* into the string — `{x ? \` · budget exhausted\` : ""}` — which puts the
|
|
57
|
+
* punctuation in two places and gets the leading separator wrong the moment
|
|
58
|
+
* the fact before it also disappears. Passing the condition and letting this
|
|
59
|
+
* drop it keeps the separator in one place.
|
|
60
|
+
* - **A line with nothing left to say renders NOTHING.** With every fact
|
|
61
|
+
* conditional, the alternative is a muted empty row, or a bare `·`.
|
|
62
|
+
*
|
|
63
|
+
* The facts are JOINED into one string rather than interleaved as elements, and
|
|
64
|
+
* that is why the prop is text: joined, there is no per-fact `key` to invent —
|
|
65
|
+
* the same reasoning `WorkflowProgress` gives for its log lines. A line that
|
|
66
|
+
* genuinely needs an element in it (a link) wants its own markup.
|
|
67
|
+
*
|
|
68
|
+
* @example
|
|
69
|
+
* ```tsx
|
|
70
|
+
* import { Facts } from "@alexkroman1/aai-ui";
|
|
71
|
+
*
|
|
72
|
+
* function RunFacts({ words, cut }: { words: number; cut: number }) {
|
|
73
|
+
* return <Facts size="xs" items={[`${words} words`, cut > 0 && `${cut} blind cuts`]} />;
|
|
74
|
+
* }
|
|
75
|
+
* ```
|
|
76
|
+
*
|
|
77
|
+
* @param props - Facts-line props.
|
|
78
|
+
*
|
|
79
|
+
* @public
|
|
80
|
+
*/
|
|
81
|
+
export declare function Facts({ items, size, as, className }: FactsProps): ReactNode;
|