@alexkroman1/aai-ui 13.2.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-j8XMToi9.js → _colors-CpZO-88A.js} +25 -2
- package/dist/{_module-url-C_4gRVL0.js → _module-url-C13kAJ87.js} +1 -1
- package/dist/_recover-run.d.ts +13 -7
- 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-B6498_bm.js → _utils-DnQDM9Uy.js} +4 -4
- 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/{aai-logo-CXZGPSIY.js → aai-logo-CFlomZlS.js} +1 -1
- package/dist/agent-state-labels.d.ts +60 -0
- package/dist/audio.js +21 -15
- package/dist/{chat-view-DDTtrh7N.js → chat-view-C_3T7Ln8.js} +100 -33
- package/dist/{client-config-BT_kWID5.js → client-config-DJQHnYjm.js} +5 -5
- package/dist/client-config.d.ts +4 -4
- package/dist/client-dir.d.ts +1 -1
- package/dist/client-dir.js +2 -2
- 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 +4 -4
- package/dist/components/chat-view.js +1 -1
- package/dist/components/console-shell.d.ts +16 -20
- package/dist/components/controls.js +4 -4
- 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/sidebar-layout.js +1 -1
- package/dist/components/start-screen.js +4 -4
- 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 +156 -18
- 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/default-client.d.ts +1 -1
- package/dist/define-client.d.ts +19 -19
- package/dist/define-client.js +20 -20
- package/dist/{eyebrow-C6ZFuiz6.js → eyebrow-UfmSz9yy.js} +1 -1
- package/dist/hooks.d.ts +44 -8
- package/dist/hooks.js +20 -14
- package/dist/index.d.ts +13 -8
- package/dist/index.js +1447 -1160
- package/dist/internal.d.ts +2 -2
- package/dist/internal.js +5 -5
- package/dist/{message-list-BJYyuIcR.js → message-list-CdOnSh5m.js} +23 -16
- 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-DxBYsfHA.js → session-core-gwePM95B.js} +135 -62
- 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-tcPQAkcP.js → tool-call-block-C2t_5fpp.js} +30 -12
- package/dist/{tool-config-context-DzAofqi_.js → tool-config-context-Es4YUzV2.js} +2 -2
- package/dist/types.d.ts +19 -4
- package/dist/types.js +3 -3
- package/dist/{url-chips-YqhCjWfQ.js → url-chips-BxhzZgk2.js} +5 -5
- package/dist/use-conversation.d.ts +1 -1
- package/dist/use-run-key.d.ts +44 -10
- package/dist/{use-user-transcript-C14qWFu2.js → use-user-transcript-uyHhzy4d.js} +4 -3
- package/dist/use-workflow-form.d.ts +64 -91
- package/dist/{use-workflow-progress-Cu0SxMyg.js → use-workflow-run-CP2ekKPV.js} +254 -257
- package/dist/use-workflow-stream.d.ts +5 -2
- package/dist/use-workflows.d.ts +77 -0
- package/dist/workflow-client.d.ts +1 -1
- package/dist/worklets/capture-processor.js +2 -2
- package/dist/worklets/playback-processor.js +2 -2
- package/package.json +6 -6
- 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/tsdown.config.d.ts +0 -2
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One word for where the CALL has got to, for each {@link AgentState} there is.
|
|
3
|
+
*
|
|
4
|
+
* The sibling of `WORKFLOW_STATUS_LABELS` next door, for the same reason and
|
|
5
|
+
* with the same shape. Three custom chromes carried three renderings of this
|
|
6
|
+
* one seven-member union, all written as a chain of ternaries over the raw
|
|
7
|
+
* member: one mapped `speaking` to "Narrating" and fell through to "Idle" for
|
|
8
|
+
* everything it had not listed, one shouted (`thinking` → "PROCESSING"), and
|
|
9
|
+
* the third rendered the enum member itself — so a caller of that agent read a
|
|
10
|
+
* lowercase `disconnected` in the header of a page nobody had decided to say
|
|
11
|
+
* that on.
|
|
12
|
+
*
|
|
13
|
+
* The exhaustiveness argument is the one that makes this worth exporting rather
|
|
14
|
+
* than documenting. A `Record` over {@link AgentState} means a state ADDED to
|
|
15
|
+
* the union is a compile error HERE, in one place every page inherits, instead
|
|
16
|
+
* of falling through each page's own `: "Idle"` tail into whichever word that
|
|
17
|
+
* page happened to end on — which is a silent wrong label, not a missing one. A
|
|
18
|
+
* page overriding one member keeps that: spreading a complete record cannot
|
|
19
|
+
* drop a key.
|
|
20
|
+
*
|
|
21
|
+
* There is deliberately no `StatusDot` component beside it. The dot markup and
|
|
22
|
+
* its palette are what each chrome exists to look like — a CRT's glow, a
|
|
23
|
+
* dispatch board's alert colours — and a component would take the one part
|
|
24
|
+
* that is genuinely shared (the words) hostage to the part that is not.
|
|
25
|
+
*/
|
|
26
|
+
import type { AgentState } from "./types.ts";
|
|
27
|
+
/**
|
|
28
|
+
* The default label per {@link AgentState}.
|
|
29
|
+
*
|
|
30
|
+
* Override the ones your page has a better word for and keep the rest:
|
|
31
|
+
*
|
|
32
|
+
* ```ts
|
|
33
|
+
* import type { AgentState } from "@alexkroman1/aai-ui";
|
|
34
|
+
* import { AGENT_STATE_LABELS } from "@alexkroman1/aai-ui";
|
|
35
|
+
*
|
|
36
|
+
* // A dispatch board that shouts, and renames one state.
|
|
37
|
+
* const STATE_LABEL = { ...AGENT_STATE_LABELS, thinking: "Processing" };
|
|
38
|
+
* const shout = (s: AgentState) => STATE_LABEL[s].toUpperCase();
|
|
39
|
+
* ```
|
|
40
|
+
*
|
|
41
|
+
* **Sentence case, deliberately.** A template that wants caps applies its own
|
|
42
|
+
* `.toUpperCase()`, and a template that wants Title Case is already there;
|
|
43
|
+
* shipping the shouted form instead would leave the two chromes that do not
|
|
44
|
+
* shout with a string they have to un-shout, which no case transform does
|
|
45
|
+
* correctly.
|
|
46
|
+
*
|
|
47
|
+
* Two wordings are decisions rather than transliterations of the member name:
|
|
48
|
+
*
|
|
49
|
+
* - `disconnected` is **"Idle"**. It is the state a session is in BEFORE it has
|
|
50
|
+
* ever started as well as after it ends, so it is the first word most callers
|
|
51
|
+
* see; "Disconnected" reads as a fault on a page where nothing has gone
|
|
52
|
+
* wrong yet. Both chromes that mapped this state by hand chose "Idle" too.
|
|
53
|
+
* - `connecting` and `thinking` carry an ellipsis, `listening` and `speaking`
|
|
54
|
+
* do not. The first two are waits with nothing for the caller to do; the
|
|
55
|
+
* other two describe someone actually talking. Same distinction
|
|
56
|
+
* `WORKFLOW_STATUS_LABELS` draws with its one "Working…".
|
|
57
|
+
*
|
|
58
|
+
* @public
|
|
59
|
+
*/
|
|
60
|
+
export declare const AGENT_STATE_LABELS: Readonly<Record<AgentState, string>>;
|
package/dist/audio.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { CAPTURE_STOP_ACK_TIMEOUT_MS, PLAYBACK_DONE_MAX_WAIT_MS, PLAYBACK_DONE_POLL_MS, VOICE_CAPTURE_CONSTRAINTS } from "./types.js";
|
|
2
|
-
//#region audio.ts
|
|
2
|
+
//#region src/audio.ts
|
|
3
3
|
/**
|
|
4
4
|
* Throw unless the browser honored a requested context sample rate. The
|
|
5
5
|
* requested rates are never advisory: captured audio is tagged with the
|
|
@@ -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,12 +1,86 @@
|
|
|
1
|
-
import { t as MessageList } from "./message-list-
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { t as AaiLogo } from "./aai-logo-
|
|
5
|
-
import { t as Eyebrow } from "./eyebrow-
|
|
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
|
+
import { t as AaiLogo } from "./aai-logo-CFlomZlS.js";
|
|
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 components/
|
|
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
|
|
83
|
+
//#region src/components/console-shell.tsx
|
|
10
84
|
/** @jsxImportSource react */
|
|
11
85
|
/**
|
|
12
86
|
* Indicator dot color per state.
|
|
@@ -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: {
|
|
@@ -138,7 +207,7 @@ function ConsoleShell({ icon, title, state, pulsing, error, children, footer, cl
|
|
|
138
207
|
});
|
|
139
208
|
}
|
|
140
209
|
//#endregion
|
|
141
|
-
//#region components/chat-view.tsx
|
|
210
|
+
//#region src/components/chat-view.tsx
|
|
142
211
|
const PULSING_STATES = /* @__PURE__ */ new Set(["listening", "speaking"]);
|
|
143
212
|
/**
|
|
144
213
|
* The main chat interface for a voice agent session — the design-system
|
|
@@ -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 };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { CLIENT_CONFIG_PATH, ClientConfigResponseSchema } from "@alexkroman1/aai/protocol";
|
|
2
|
-
//#region client-config.ts
|
|
2
|
+
//#region src/client-config.ts
|
|
3
3
|
/**
|
|
4
4
|
* Pre-connection client-config lookup.
|
|
5
5
|
*
|
|
@@ -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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createRequire } from "node:module";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
//#region client-dir.ts
|
|
3
|
+
//#region src/client-dir.ts
|
|
4
4
|
/**
|
|
5
5
|
* Filesystem location of the prebuilt default client — **Node only**.
|
|
6
6
|
*
|
|
@@ -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,8 +1,8 @@
|
|
|
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
|
-
//#region components/button.tsx
|
|
5
|
+
//#region src/components/button.tsx
|
|
6
6
|
/** @jsxImportSource react */
|
|
7
7
|
/** One variant's rest and hover colors, derived from the theme. */
|
|
8
8
|
function variantColors(variant, theme) {
|
|
@@ -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;
|