@alexkroman1/aai-ui 6.10.1 → 7.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 +104 -3
- package/dist/_run-controls.d.ts +33 -0
- package/dist/{chat-view-ByQFf94G.js → chat-view-BKsFfFZJ.js} +57 -11
- package/dist/client-config-B4nznRvH.js +134 -0
- package/dist/client-config.d.ts +45 -2
- package/dist/client-dir.d.ts +3 -1
- package/dist/client-dir.js +3 -1
- package/dist/components/auto-scroll.d.ts +24 -13
- package/dist/components/button.d.ts +8 -4
- package/dist/components/button.js +4 -4
- package/dist/components/chat-view.d.ts +4 -3
- package/dist/components/chat-view.js +1 -1
- package/dist/components/console-shell.d.ts +71 -10
- package/dist/components/controls.d.ts +15 -4
- package/dist/components/controls.js +48 -2
- package/dist/components/form-fields.d.ts +142 -0
- package/dist/components/form-types.d.ts +6 -0
- package/dist/components/form.d.ts +38 -80
- package/dist/components/markdown.d.ts +31 -5
- package/dist/components/message-list.d.ts +21 -4
- package/dist/components/message-list.js +1 -1
- package/dist/components/sidebar-layout.d.ts +11 -0
- package/dist/components/sidebar-layout.js +2 -0
- package/dist/components/start-screen.d.ts +8 -0
- package/dist/components/start-screen.js +2 -0
- package/dist/components/tool-call-block.js +1 -1
- package/dist/components/tool-call-row.d.ts +24 -0
- package/dist/components/upload-progress.d.ts +17 -7
- package/dist/components/workflow-fields.d.ts +10 -22
- package/dist/components/workflow-progress.d.ts +29 -10
- package/dist/context.d.ts +36 -0
- package/dist/context.js +105 -14
- package/dist/default-client/assets/index-S5fkKi6B.css +2 -0
- package/dist/default-client/assets/index-fEkrcZgo.js +293 -0
- package/dist/default-client/index.html +2 -2
- package/dist/define-client.d.ts +67 -62
- package/dist/define-client.js +56 -22
- package/dist/hooks.d.ts +88 -3
- package/dist/index.d.ts +12 -11
- package/dist/index.js +504 -248
- package/dist/internal.d.ts +40 -0
- package/dist/internal.js +6 -0
- package/dist/{message-list-CpPV7dGx.js → message-list-DHddO4QC.js} +217 -72
- package/dist/{session-core-CAfYmUbg.js → session-core-C2JtLArh.js} +267 -165
- package/dist/session-core-audio-setup.d.ts +3 -0
- package/dist/session-core-messages.d.ts +3 -0
- package/dist/session-core-state.d.ts +146 -0
- package/dist/session-core-types.d.ts +77 -32
- package/dist/session-core.d.ts +3 -2
- package/dist/session-core.js +1 -1
- package/dist/{tool-call-block-D6pTEPrT.js → tool-call-block-DoF-cSIZ.js} +27 -19
- package/dist/tool-config-context-DzAofqi_.js +19 -0
- package/dist/types.d.ts +31 -5
- package/dist/types.js +5 -4
- package/dist/{controls-CjG91QJ4.js → url-chips-DpM7Oocj.js} +3 -46
- package/dist/use-conversation.d.ts +122 -0
- package/dist/use-download-url.d.ts +83 -0
- package/dist/use-workflow-form.d.ts +59 -3
- package/dist/use-workflow-run.d.ts +35 -0
- package/dist/use-workflow-stream.d.ts +36 -62
- package/dist/workflow-client.d.ts +36 -11
- package/dist/workflow-status-labels.d.ts +35 -0
- package/package.json +10 -6
- package/styles.css +14 -0
- package/dist/_sse.d.ts +0 -56
- package/dist/default-client/assets/index-DTLrhtTF.css +0 -2
- package/dist/default-client/assets/index-DXODx_9r.js +0 -293
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @alexkroman1/aai-ui
|
|
2
2
|
|
|
3
|
-
The browser client for aai
|
|
3
|
+
The browser client for aai agents: React 19 components, hooks, and a
|
|
4
4
|
framework-agnostic session core (WebSocket + microphone + playback).
|
|
5
5
|
|
|
6
6
|
```sh
|
|
@@ -11,7 +11,13 @@ Every agent gets this UI for free — `aai dev` and deployed agents serve a
|
|
|
11
11
|
default client built from this package. Install it directly when the agent
|
|
12
12
|
has its own `client.tsx`.
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
**Two front doors, two mounts.** A voice agent's page calls `client()` and
|
|
15
|
+
talks to a live session. A workflow app's page (`workflowApp()`, or
|
|
16
|
+
`agent({ page: "static" })`) calls `page()` and talks to the workflow HTTP
|
|
17
|
+
API — no session, no socket, no microphone. Both are still `client.tsx`,
|
|
18
|
+
still React, still the same theme tokens.
|
|
19
|
+
|
|
20
|
+
## A custom voice client
|
|
15
21
|
|
|
16
22
|
`client()` mounts the default chat shell with your sidebar, or replaces the
|
|
17
23
|
whole UI with a custom component:
|
|
@@ -41,11 +47,106 @@ function OrderSidebar() {
|
|
|
41
47
|
client({ sidebar: OrderSidebar });
|
|
42
48
|
```
|
|
43
49
|
|
|
50
|
+
## A workflow app
|
|
51
|
+
|
|
52
|
+
`page()` mounts a form over the agent's workflows. `<WorkflowFields>` renders
|
|
53
|
+
one control per scalar property of the workflow's own input schema, so adding
|
|
54
|
+
a field to the schema adds it to the page:
|
|
55
|
+
|
|
56
|
+
```tsx
|
|
57
|
+
import "@alexkroman1/aai-ui/styles.css";
|
|
58
|
+
import {
|
|
59
|
+
Form,
|
|
60
|
+
page,
|
|
61
|
+
SubmitButton,
|
|
62
|
+
UploadProgressBar,
|
|
63
|
+
useWorkflowSubmit,
|
|
64
|
+
WorkflowFields,
|
|
65
|
+
WorkflowProgress,
|
|
66
|
+
} from "@alexkroman1/aai-ui";
|
|
67
|
+
|
|
68
|
+
function App() {
|
|
69
|
+
const { submit, run, pending, upload, error } = useWorkflowSubmit("digest");
|
|
70
|
+
return (
|
|
71
|
+
<main className="mx-auto flex max-w-2xl flex-col gap-6 p-8">
|
|
72
|
+
<Form onSubmit={submit} error={error}>
|
|
73
|
+
<WorkflowFields workflow="digest" />
|
|
74
|
+
<SubmitButton pending={pending}>Summarize</SubmitButton>
|
|
75
|
+
</Form>
|
|
76
|
+
{/* The upload is its own wait: the run does not exist until the bytes
|
|
77
|
+
are in, so nothing else on the page can describe it. */}
|
|
78
|
+
<UploadProgressBar upload={upload} />
|
|
79
|
+
{/* What the run has SAID, from `report()` in its steps. */}
|
|
80
|
+
<WorkflowProgress runId={run?.runId} />
|
|
81
|
+
{run?.status === "completed" && <pre>{JSON.stringify(run.output)}</pre>}
|
|
82
|
+
</main>
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
page({ name: "Digest", component: App });
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
`WorkflowOutputOf<typeof myWorkflow>` is what makes
|
|
90
|
+
`run.status === "completed"` narrow to a typed `run.output` — a type-only
|
|
91
|
+
import of `agent.ts` is erased, so it pulls no server code into the bundle.
|
|
92
|
+
|
|
93
|
+
## What is in the package
|
|
94
|
+
|
|
95
|
+
The reference below groups by TypeScript kind. This is the same surface
|
|
96
|
+
grouped by what it is for.
|
|
97
|
+
|
|
98
|
+
**Mounts** — `client()`, `page()`, the config each takes and the handle each
|
|
99
|
+
returns (`ClientConfig`, `ClientHandle`, `PageConfig`, `PageHandle`).
|
|
100
|
+
`fetchClientConfig()` reads the agent's declared `name`/`greeting` on a page,
|
|
101
|
+
which `client()` does for itself.
|
|
102
|
+
|
|
103
|
+
**The live call** — `useSession()` for the whole snapshot,
|
|
104
|
+
`useSessionSelector()` for one slice, `useUserTranscript()` for the caller's
|
|
105
|
+
in-progress turn. `createSessionCore()` is the same session as a plain store,
|
|
106
|
+
for a non-React client. `SessionSnapshot`, `AgentState`, `ChatMessage`,
|
|
107
|
+
`ToolCallInfo`, `SessionError` and `SessionErrorCode` come with it.
|
|
108
|
+
|
|
109
|
+
**What the agent pushes** — `useAgentState()` (the `syncState` projection),
|
|
110
|
+
`useToolResult()` / `useToolCallStart()` (tool calls as they run), and
|
|
111
|
+
`useEvent()` (custom events from `ctx.send`).
|
|
112
|
+
|
|
113
|
+
**Chat chrome** — `ChatView` is the whole surface; `MessageList`, `Controls`,
|
|
114
|
+
`Markdown`, `ToolCallRow`, `StartScreen`, `SidebarLayout`, `AutoScroll` and
|
|
115
|
+
`Button` are its parts, exported so a custom chrome reuses them rather than
|
|
116
|
+
approximating them. `AutoScroll` is the one to reach for directly: it is the
|
|
117
|
+
only scroll-pinning implementation here, and it needs a **bounded height**.
|
|
118
|
+
|
|
119
|
+
**Forms** — `Form` collects typed values off the DOM on submit. `Field`,
|
|
120
|
+
`TextField`, `NumberField`, `TextAreaField`, `SelectField`, `CheckboxField`,
|
|
121
|
+
`FileField` and `SubmitButton` are the controls; `WorkflowFields` generates
|
|
122
|
+
them from a schema. A plain `<input name="x">` works identically.
|
|
123
|
+
|
|
124
|
+
**Workflows** — `createWorkflowApi()` is the client;
|
|
125
|
+
`useWorkflowSubmit()` / `useWorkflowStream()` (start one and watch it),
|
|
126
|
+
`useWorkflowRun()` (watch one by id), `useWorkflowRuns()` (the history),
|
|
127
|
+
`useWorkflowProgress()` (what a run has written), `useWorkflows()` (the
|
|
128
|
+
listing). `<WorkflowProgress>` and `<UploadProgressBar>` are the rendered
|
|
129
|
+
halves. `WorkflowRun`, `WorkflowSummary`, `WorkflowOutputOf` and
|
|
130
|
+
`isTerminal()` are the vocabulary.
|
|
131
|
+
|
|
132
|
+
**Theme** — `useTheme()` and `ClientTheme`.
|
|
133
|
+
|
|
134
|
+
## Other subpaths
|
|
135
|
+
|
|
136
|
+
The root export is the whole client API. Two subpaths sit beside it, neither
|
|
137
|
+
of them something a `client.tsx` reaches for:
|
|
138
|
+
|
|
139
|
+
| Subpath | Reach for it when |
|
|
140
|
+
| --- | --- |
|
|
141
|
+
| `/client-dir` | serving the prebuilt default client from Node — `defaultClientDir()`, the filesystem path `createServer({ clientDir })` wants |
|
|
142
|
+
| `/internal` | never, from application code: the plumbing `client()` installs for itself (the session and theme providers, the default shell's URL chips, the tool-config context, the pre-connection lookup). Not a public API and not covered by semver |
|
|
143
|
+
|
|
44
144
|
## Hooks
|
|
45
145
|
|
|
46
146
|
Inside components rendered by `client()`:
|
|
47
147
|
|
|
48
|
-
- `useSession()` — connection state, transcript,
|
|
148
|
+
- `useSession()` — connection state, transcript, and the call controls:
|
|
149
|
+
`start`, `toggle`, `cancel`, `reset`, `resetState`, `disconnect`, `end`.
|
|
49
150
|
- `useAgentState<T>()` — the agent's `syncState` projection, live.
|
|
50
151
|
- `useToolResult(name, cb)` / `useToolCallStart(name, cb)` — observe tool
|
|
51
152
|
calls as they run (e.g. to render a card per result).
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The two things a page does TO a run it started, bound to the run it has.
|
|
3
|
+
*
|
|
4
|
+
* `useWorkflowSubmit` and `useWorkflowStream` both hold a run id and neither
|
|
5
|
+
* handed it back, so a page that wanted "send it now" or "stop" had to hold an
|
|
6
|
+
* `api` of its own purely to write `api.wake(runId)` — which is the whole reason
|
|
7
|
+
* the two raw-primitive template pages keep a client at module scope. That is a
|
|
8
|
+
* page carrying the transport to make up for a hook withholding its own state.
|
|
9
|
+
*
|
|
10
|
+
* Both calls answer rather than fail when there is nothing to act on — `0`
|
|
11
|
+
* sleeps ended, `false` this call did not end it — which is the SDK's own
|
|
12
|
+
* contract for them (two tabs pressing Stop is ordinary), and it is what lets
|
|
13
|
+
* the no-run case be the same answer rather than a special one a caller has to
|
|
14
|
+
* branch on.
|
|
15
|
+
*/
|
|
16
|
+
import type { WorkflowApi } from "./workflow-client.ts";
|
|
17
|
+
/** What {@link useRunControls} returns — see {@link WorkflowSubmission}. */
|
|
18
|
+
export type RunControls = {
|
|
19
|
+
/** End the run's `sleep()` early; resolves how many sleeps it interrupted. */
|
|
20
|
+
wake: () => Promise<number>;
|
|
21
|
+
/** Stop the run; resolves whether this call is what ended it. */
|
|
22
|
+
cancel: () => Promise<boolean>;
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Bind `wake` and `cancel` to whatever run the hook is currently following.
|
|
26
|
+
*
|
|
27
|
+
* @param runId - The live run, or `undefined` before one exists.
|
|
28
|
+
* @param getClient - The stable getter from `useWorkflowApiRef`.
|
|
29
|
+
* @returns Two callbacks, stable while `runId` is.
|
|
30
|
+
*
|
|
31
|
+
* @internal
|
|
32
|
+
*/
|
|
33
|
+
export declare function useRunControls(runId: string | undefined, getClient: () => WorkflowApi): RunControls;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { t as MessageList } from "./message-list-
|
|
1
|
+
import { t as MessageList } from "./message-list-DHddO4QC.js";
|
|
2
2
|
import { useSessionSelector, useTheme } from "./context.js";
|
|
3
3
|
import { n as THINKING_COLOR, r as inkTint, t as ERROR_COLOR } from "./_colors-CcAi2FOU.js";
|
|
4
4
|
import { t as AaiLogo } from "./aai-logo-9xRBGVFl.js";
|
|
5
5
|
import { t as Eyebrow } from "./eyebrow-C6ZFuiz6.js";
|
|
6
|
-
import {
|
|
6
|
+
import { Controls } from "./components/controls.js";
|
|
7
7
|
import clsx from "clsx";
|
|
8
8
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
9
9
|
//#region components/console-shell.tsx
|
|
@@ -28,13 +28,61 @@ function stateColor(state, primary, idle) {
|
|
|
28
28
|
}
|
|
29
29
|
}
|
|
30
30
|
/**
|
|
31
|
-
* The design-system "console" chrome
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
* on a raised white card, and a footer row beneath it.
|
|
31
|
+
* The design-system "console" chrome: a 760px column on the themed page with a
|
|
32
|
+
* header (icon + live-status eyebrow), an announced error banner, the main
|
|
33
|
+
* content on a raised card, and a footer row beneath it.
|
|
35
34
|
*
|
|
35
|
+
* {@link ChatView} is this shell with `<MessageList>` inside it and
|
|
36
|
+
* `<Controls>` under it, and until now that was the only way to get it — the
|
|
37
|
+
* shell itself was internal, so a client wanting its own conversation markup
|
|
38
|
+
* had to rebuild the chrome as well. Each one that did re-derived the error
|
|
39
|
+
* banner WITHOUT `role="alert"`, which is the one part of this component a
|
|
40
|
+
* reviewer cannot see is missing: per the `fatalError` latch in
|
|
41
|
+
* `session-core.ts`, the banner is the only remaining signal once the state
|
|
42
|
+
* eyebrow goes back to reading like a live session, and a screen reader is
|
|
43
|
+
* never told an unannounced one appeared.
|
|
36
44
|
*
|
|
37
|
-
*
|
|
45
|
+
* Reach for it when the conversation is yours and the frame is not. Reach for
|
|
46
|
+
* `<ChatView>` when both are ours.
|
|
47
|
+
*
|
|
48
|
+
* Must be rendered inside the providers `client()` installs.
|
|
49
|
+
*
|
|
50
|
+
* @example A custom conversation in the stock chrome
|
|
51
|
+
* ```tsx
|
|
52
|
+
* import {
|
|
53
|
+
* ConsoleShell,
|
|
54
|
+
* Controls,
|
|
55
|
+
* useConversation,
|
|
56
|
+
* useSessionSelector,
|
|
57
|
+
* } from "@alexkroman1/aai-ui";
|
|
58
|
+
*
|
|
59
|
+
* function Console() {
|
|
60
|
+
* const state = useSessionSelector((s) => s.state);
|
|
61
|
+
* const error = useSessionSelector((s) => s.error);
|
|
62
|
+
* const { items } = useConversation();
|
|
63
|
+
* return (
|
|
64
|
+
* <ConsoleShell
|
|
65
|
+
* title="Dispatch"
|
|
66
|
+
* state={state}
|
|
67
|
+
* pulsing={state === "listening"}
|
|
68
|
+
* error={error?.message}
|
|
69
|
+
* footer={<Controls />}
|
|
70
|
+
* >
|
|
71
|
+
* <ul>
|
|
72
|
+
* {items.map((item) => (
|
|
73
|
+
* <li key={item.kind === "message" ? item.message.id : item.toolCall.callId}>
|
|
74
|
+
* {item.kind === "message" ? item.message.content : item.toolCall.name}
|
|
75
|
+
* </li>
|
|
76
|
+
* ))}
|
|
77
|
+
* </ul>
|
|
78
|
+
* </ConsoleShell>
|
|
79
|
+
* );
|
|
80
|
+
* }
|
|
81
|
+
* ```
|
|
82
|
+
*
|
|
83
|
+
* @param props - See {@link ConsoleShellProps}.
|
|
84
|
+
*
|
|
85
|
+
* @public
|
|
38
86
|
*/
|
|
39
87
|
function ConsoleShell({ icon, title, state, pulsing, error, children, footer, className }) {
|
|
40
88
|
const theme = useTheme();
|
|
@@ -113,9 +161,7 @@ const PULSING_STATES = /* @__PURE__ */ new Set(["listening", "speaking"]);
|
|
|
113
161
|
* }
|
|
114
162
|
* ```
|
|
115
163
|
*
|
|
116
|
-
* @param
|
|
117
|
-
* @param title - Optional title string for the header.
|
|
118
|
-
* @param className - Additional CSS class names applied to the root element.
|
|
164
|
+
* @param props - Chat surface props.
|
|
119
165
|
*
|
|
120
166
|
* @public
|
|
121
167
|
*/
|
|
@@ -134,4 +180,4 @@ function ChatView({ icon, title, className }) {
|
|
|
134
180
|
});
|
|
135
181
|
}
|
|
136
182
|
//#endregion
|
|
137
|
-
export { ChatView as t };
|
|
183
|
+
export { ConsoleShell as n, ChatView as t };
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { CLIENT_CONFIG_PATH, ClientConfigResponseSchema } from "@alexkroman1/aai/protocol";
|
|
2
|
+
//#region client-config.ts
|
|
3
|
+
/**
|
|
4
|
+
* Pre-connection client-config lookup.
|
|
5
|
+
*
|
|
6
|
+
* `GET client-config` (relative to the agent's base URL — see
|
|
7
|
+
* `sdk/client-config.ts` in `@alexkroman1/aai`) gives the default client the
|
|
8
|
+
* agent's display name and greeting before any connection exists. For that
|
|
9
|
+
* use every failure path — network error, 404 from an older server,
|
|
10
|
+
* malformed body — degrades to the empty default (`fetchClientConfig`), so
|
|
11
|
+
* the lookup can never break an existing agent.
|
|
12
|
+
*
|
|
13
|
+
* The session's broker decision needs the opposite: `loadClientConfig`
|
|
14
|
+
* keeps "the lookup failed" (`null`) distinct from "the server answered and
|
|
15
|
+
* named no sessionUrl" (`{}`). See its doc comment.
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* Resolve a relative endpoint path against the agent's base URL.
|
|
19
|
+
*
|
|
20
|
+
* @internal
|
|
21
|
+
*/
|
|
22
|
+
function buildAgentUrl(platformUrl, endpointPath) {
|
|
23
|
+
return new URL(endpointPath, platformUrl.endsWith("/") ? platformUrl : `${platformUrl}/`);
|
|
24
|
+
}
|
|
25
|
+
const AGENT_DEFAULT = {};
|
|
26
|
+
/**
|
|
27
|
+
* Per-attempt deadline for the `client-config` lookup.
|
|
28
|
+
*
|
|
29
|
+
* A request issued while the platform is restarting or saturated can HANG
|
|
30
|
+
* rather than fail — the proxy holds the socket open — and a browser fetch
|
|
31
|
+
* has no timeout of its own. Every other failure here is already handled
|
|
32
|
+
* (`null`, then the same-origin fallback), but a hang is not a failure: the
|
|
33
|
+
* promise simply never settles.
|
|
34
|
+
*
|
|
35
|
+
* That is unrecoverable rather than merely slow, because this lookup runs
|
|
36
|
+
* inside the session's WebSocket URL *provider*. partysocket awaits the
|
|
37
|
+
* provider under `_connectLock` and arms its own `connectionTimeout` only
|
|
38
|
+
* AFTER the URL resolves, so a hung lookup means no socket is ever
|
|
39
|
+
* constructed, no `error`/`close` ever fires, and none of the 10 reconnect
|
|
40
|
+
* attempts ever happen — the session sits on "connecting" forever, and stays
|
|
41
|
+
* there long after the server is back. Reproduced: zero sockets opened.
|
|
42
|
+
*
|
|
43
|
+
* A timed-out attempt therefore degrades exactly like any other failed one —
|
|
44
|
+
* `null`, so `serverIsBroker` stays unlatched and the attempt falls through
|
|
45
|
+
* to the same-origin `websocket` path, whose failure re-enters the normal
|
|
46
|
+
* backoff and re-fetches this on the next attempt.
|
|
47
|
+
*
|
|
48
|
+
* Sized well above the real work (one same-origin JSON GET that reads the
|
|
49
|
+
* agent's row) and well under a user's patience — the same 10s the studio's
|
|
50
|
+
* gating reads use for the identical hazard.
|
|
51
|
+
*
|
|
52
|
+
* @internal
|
|
53
|
+
*/
|
|
54
|
+
const CLIENT_CONFIG_ATTEMPT_TIMEOUT_MS = 1e4;
|
|
55
|
+
/**
|
|
56
|
+
* Fetch the agent's client config, reporting `null` when the lookup did not
|
|
57
|
+
* produce an answer (network error, non-2xx, unparsable body).
|
|
58
|
+
*
|
|
59
|
+
* The distinction from `fetchClientConfig` matters for exactly one caller:
|
|
60
|
+
* the session's per-attempt broker decision. A config that ARRIVED and named
|
|
61
|
+
* no `sessionUrl` means "this server is not a broker" (`aai dev`, an older
|
|
62
|
+
* server) — a durable fact worth latching. A lookup that FAILED means
|
|
63
|
+
* nothing about the server, and treating the two alike is how a single 503
|
|
64
|
+
* (a sandbox mid-boot, or one that failed to start) pinned a session to the
|
|
65
|
+
* platform's `/:slug/websocket` — a WebSocket redirect browsers don't
|
|
66
|
+
* follow, so every retry failed with no re-brokering even after the agent
|
|
67
|
+
* recovered.
|
|
68
|
+
*
|
|
69
|
+
* @internal
|
|
70
|
+
*/
|
|
71
|
+
async function loadClientConfig(platformUrl, fetchFn) {
|
|
72
|
+
const doFetch = fetchFn ?? ((input, init) => globalThis.fetch(input, init));
|
|
73
|
+
try {
|
|
74
|
+
const resp = await doFetch(buildAgentUrl(platformUrl, CLIENT_CONFIG_PATH).href, { signal: AbortSignal.timeout(CLIENT_CONFIG_ATTEMPT_TIMEOUT_MS) });
|
|
75
|
+
if (!resp.ok) return null;
|
|
76
|
+
const parsed = ClientConfigResponseSchema.safeParse(await resp.json());
|
|
77
|
+
return parsed.success ? parsed.data : null;
|
|
78
|
+
} catch {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Fetch the agent's declared `name`, `greeting` and front door; any failure
|
|
84
|
+
* yields the agent default (`{}`).
|
|
85
|
+
*
|
|
86
|
+
* **This is what a workflow app calls instead of receiving the config.**
|
|
87
|
+
* `client()` fetches `GET client-config` for itself before it renders the
|
|
88
|
+
* default chat shell, so a voice client never has to. `page()` mounts no
|
|
89
|
+
* session and makes no such request — deliberately, since a page has no shell
|
|
90
|
+
* to put a name in — so a page that wants the agent's own `name` or `greeting`
|
|
91
|
+
* asks for them here.
|
|
92
|
+
*
|
|
93
|
+
* Every failure path degrades to the empty default rather than throwing: a
|
|
94
|
+
* network error, a 404 from a server older than the endpoint, a malformed
|
|
95
|
+
* body, and a lookup that hangs past
|
|
96
|
+
* `CLIENT_CONFIG_ATTEMPT_TIMEOUT_MS` all read as "the agent declared nothing".
|
|
97
|
+
* So a page may render straight from the result and never needs a `catch` —
|
|
98
|
+
* treat every field as optional, because an agent that declared none is a
|
|
99
|
+
* normal agent.
|
|
100
|
+
*
|
|
101
|
+
* @param platformUrl - The agent's base URL. On a deployed page that is the
|
|
102
|
+
* page's own origin and path (`location.origin + location.pathname`); the
|
|
103
|
+
* endpoint is resolved relative to it.
|
|
104
|
+
* @param fetchFn - Fetch implementation, for tests and for a caller that
|
|
105
|
+
* supplies its own credentials. Defaults to the global `fetch`.
|
|
106
|
+
* @returns The agent's config, or `{}` when the lookup produced no answer.
|
|
107
|
+
*
|
|
108
|
+
* @example
|
|
109
|
+
* ```tsx
|
|
110
|
+
* import { fetchClientConfig, page } from "@alexkroman1/aai-ui";
|
|
111
|
+
*
|
|
112
|
+
* const { name, greeting } = await fetchClientConfig(
|
|
113
|
+
* location.origin + location.pathname,
|
|
114
|
+
* );
|
|
115
|
+
*
|
|
116
|
+
* function App() {
|
|
117
|
+
* return (
|
|
118
|
+
* <main>
|
|
119
|
+
* <h1>{name ?? "Workflows"}</h1>
|
|
120
|
+
* {greeting ? <p>{greeting}</p> : null}
|
|
121
|
+
* </main>
|
|
122
|
+
* );
|
|
123
|
+
* }
|
|
124
|
+
*
|
|
125
|
+
* page({ name: name ?? "Workflows", component: App });
|
|
126
|
+
* ```
|
|
127
|
+
*
|
|
128
|
+
* @public
|
|
129
|
+
*/
|
|
130
|
+
async function fetchClientConfig(platformUrl, fetchFn) {
|
|
131
|
+
return await loadClientConfig(platformUrl, fetchFn) ?? AGENT_DEFAULT;
|
|
132
|
+
}
|
|
133
|
+
//#endregion
|
|
134
|
+
export { fetchClientConfig as n, loadClientConfig as r, buildAgentUrl as t };
|
package/dist/client-config.d.ts
CHANGED
|
@@ -68,8 +68,51 @@ export declare const CLIENT_CONFIG_ATTEMPT_TIMEOUT_MS = 10000;
|
|
|
68
68
|
*/
|
|
69
69
|
export declare function loadClientConfig(platformUrl: string, fetchFn?: typeof globalThis.fetch): Promise<ClientConfigResponse | null>;
|
|
70
70
|
/**
|
|
71
|
-
* Fetch the agent's
|
|
71
|
+
* Fetch the agent's declared `name`, `greeting` and front door; any failure
|
|
72
|
+
* yields the agent default (`{}`).
|
|
72
73
|
*
|
|
73
|
-
*
|
|
74
|
+
* **This is what a workflow app calls instead of receiving the config.**
|
|
75
|
+
* `client()` fetches `GET client-config` for itself before it renders the
|
|
76
|
+
* default chat shell, so a voice client never has to. `page()` mounts no
|
|
77
|
+
* session and makes no such request — deliberately, since a page has no shell
|
|
78
|
+
* to put a name in — so a page that wants the agent's own `name` or `greeting`
|
|
79
|
+
* asks for them here.
|
|
80
|
+
*
|
|
81
|
+
* Every failure path degrades to the empty default rather than throwing: a
|
|
82
|
+
* network error, a 404 from a server older than the endpoint, a malformed
|
|
83
|
+
* body, and a lookup that hangs past
|
|
84
|
+
* `CLIENT_CONFIG_ATTEMPT_TIMEOUT_MS` all read as "the agent declared nothing".
|
|
85
|
+
* So a page may render straight from the result and never needs a `catch` —
|
|
86
|
+
* treat every field as optional, because an agent that declared none is a
|
|
87
|
+
* normal agent.
|
|
88
|
+
*
|
|
89
|
+
* @param platformUrl - The agent's base URL. On a deployed page that is the
|
|
90
|
+
* page's own origin and path (`location.origin + location.pathname`); the
|
|
91
|
+
* endpoint is resolved relative to it.
|
|
92
|
+
* @param fetchFn - Fetch implementation, for tests and for a caller that
|
|
93
|
+
* supplies its own credentials. Defaults to the global `fetch`.
|
|
94
|
+
* @returns The agent's config, or `{}` when the lookup produced no answer.
|
|
95
|
+
*
|
|
96
|
+
* @example
|
|
97
|
+
* ```tsx
|
|
98
|
+
* import { fetchClientConfig, page } from "@alexkroman1/aai-ui";
|
|
99
|
+
*
|
|
100
|
+
* const { name, greeting } = await fetchClientConfig(
|
|
101
|
+
* location.origin + location.pathname,
|
|
102
|
+
* );
|
|
103
|
+
*
|
|
104
|
+
* function App() {
|
|
105
|
+
* return (
|
|
106
|
+
* <main>
|
|
107
|
+
* <h1>{name ?? "Workflows"}</h1>
|
|
108
|
+
* {greeting ? <p>{greeting}</p> : null}
|
|
109
|
+
* </main>
|
|
110
|
+
* );
|
|
111
|
+
* }
|
|
112
|
+
*
|
|
113
|
+
* page({ name: name ?? "Workflows", component: App });
|
|
114
|
+
* ```
|
|
115
|
+
*
|
|
116
|
+
* @public
|
|
74
117
|
*/
|
|
75
118
|
export declare function fetchClientConfig(platformUrl: string, fetchFn?: typeof globalThis.fetch): Promise<ClientConfigResponse>;
|
package/dist/client-dir.d.ts
CHANGED
|
@@ -10,6 +10,8 @@
|
|
|
10
10
|
* serving the default UI has to write them — `aai-cli`'s dev server had its own
|
|
11
11
|
* copy, as did every self-hosted example. Three places that would all silently
|
|
12
12
|
* serve nothing if the build output moved.
|
|
13
|
+
*
|
|
14
|
+
* @module client-dir
|
|
13
15
|
*/
|
|
14
16
|
/**
|
|
15
17
|
* Absolute path to the prebuilt browser client's static files — pass it to
|
|
@@ -27,7 +29,7 @@
|
|
|
27
29
|
* @example
|
|
28
30
|
* ```ts
|
|
29
31
|
* import { agent } from "@alexkroman1/aai";
|
|
30
|
-
* import { createAgentServer } from "@alexkroman1/aai
|
|
32
|
+
* import { createAgentServer } from "@alexkroman1/aai-runtime";
|
|
31
33
|
* import { defaultClientDir } from "@alexkroman1/aai-ui/client-dir";
|
|
32
34
|
*
|
|
33
35
|
* const server = createAgentServer({
|
package/dist/client-dir.js
CHANGED
|
@@ -13,6 +13,8 @@ import path from "node:path";
|
|
|
13
13
|
* serving the default UI has to write them — `aai-cli`'s dev server had its own
|
|
14
14
|
* copy, as did every self-hosted example. Three places that would all silently
|
|
15
15
|
* serve nothing if the build output moved.
|
|
16
|
+
*
|
|
17
|
+
* @module client-dir
|
|
16
18
|
*/
|
|
17
19
|
const require = createRequire(import.meta.url);
|
|
18
20
|
/**
|
|
@@ -31,7 +33,7 @@ const require = createRequire(import.meta.url);
|
|
|
31
33
|
* @example
|
|
32
34
|
* ```ts
|
|
33
35
|
* import { agent } from "@alexkroman1/aai";
|
|
34
|
-
* import { createAgentServer } from "@alexkroman1/aai
|
|
36
|
+
* import { createAgentServer } from "@alexkroman1/aai-runtime";
|
|
35
37
|
* import { defaultClientDir } from "@alexkroman1/aai-ui/client-dir";
|
|
36
38
|
*
|
|
37
39
|
* const server = createAgentServer({
|
|
@@ -36,28 +36,39 @@ import type { CSSProperties, ReactNode } from "react";
|
|
|
36
36
|
* }
|
|
37
37
|
* ```
|
|
38
38
|
*
|
|
39
|
-
* @param
|
|
40
|
-
* @param className - Classes for the outer container. It must be given a
|
|
41
|
-
* bounded height (`flex-1 min-h-0`, `h-full`, a fixed height) — an unbounded
|
|
42
|
-
* one grows with its content and never scrolls, so nothing pins.
|
|
43
|
-
* @param contentClassName - Classes for the inner content element, where
|
|
44
|
-
* padding and the children's own layout belong.
|
|
45
|
-
* @param scrollClassName - Classes for the scrolling element itself. Defaults
|
|
46
|
-
* to hiding the scrollbar; pass `"overflow-y-auto"` to show a native one.
|
|
47
|
-
* @param style - Inline styles for the outer container.
|
|
48
|
-
* @param initial - Scroll behavior on mount. Defaults to `"instant"` (start at
|
|
49
|
-
* the latest content without animating a scroll the reader did not ask for).
|
|
50
|
-
* @param resize - Scroll behavior when pinned content grows. Defaults to
|
|
51
|
-
* `"smooth"`.
|
|
39
|
+
* @param props - Scroll container props.
|
|
52
40
|
*
|
|
53
41
|
* @public
|
|
54
42
|
*/
|
|
55
43
|
export declare function AutoScroll({ children, className, contentClassName, scrollClassName, style, initial, resize, }: {
|
|
44
|
+
/** The scrollable content. */
|
|
56
45
|
children: ReactNode;
|
|
46
|
+
/**
|
|
47
|
+
* Classes for the outer container, appended to its own.
|
|
48
|
+
*
|
|
49
|
+
* **The container must end up with a bounded height** (`flex-1 min-h-0`,
|
|
50
|
+
* `h-full`, a fixed height). This is the one constraint callers get wrong:
|
|
51
|
+
* an unbounded container grows with its content and never scrolls, so
|
|
52
|
+
* nothing pins and the component silently does nothing.
|
|
53
|
+
*/
|
|
57
54
|
className?: string | undefined;
|
|
55
|
+
/**
|
|
56
|
+
* Classes for the inner content element, where padding and the children's
|
|
57
|
+
* own layout belong.
|
|
58
|
+
*/
|
|
58
59
|
contentClassName?: string | undefined;
|
|
60
|
+
/**
|
|
61
|
+
* Classes for the scrolling element itself. Defaults to hiding the
|
|
62
|
+
* scrollbar; pass `"overflow-y-auto"` to show a native one.
|
|
63
|
+
*/
|
|
59
64
|
scrollClassName?: string | undefined;
|
|
65
|
+
/** Inline styles for the outer container. */
|
|
60
66
|
style?: CSSProperties | undefined;
|
|
67
|
+
/**
|
|
68
|
+
* Scroll behavior on mount. Defaults to `"instant"` — start at the latest
|
|
69
|
+
* content without animating a scroll the reader did not ask for.
|
|
70
|
+
*/
|
|
61
71
|
initial?: "instant" | "smooth" | undefined;
|
|
72
|
+
/** Scroll behavior when pinned content grows. Defaults to `"smooth"`. */
|
|
62
73
|
resize?: "instant" | "smooth" | undefined;
|
|
63
74
|
}): ReactNode;
|
|
@@ -39,16 +39,20 @@ export type ButtonSize = "default" | "lg";
|
|
|
39
39
|
* }
|
|
40
40
|
* ```
|
|
41
41
|
*
|
|
42
|
-
* @param
|
|
43
|
-
* @
|
|
44
|
-
* @
|
|
45
|
-
*
|
|
42
|
+
* @param props - Button props: `variant` (visual style — see
|
|
43
|
+
* {@link ButtonVariant}, defaults to `"default"`), `size` (see
|
|
44
|
+
* {@link ButtonSize}, defaults to `"default"`), `className` (appended to the
|
|
45
|
+
* button's own classes), `children` (the label), and any `<button>` attribute.
|
|
46
46
|
*
|
|
47
47
|
* @public
|
|
48
48
|
*/
|
|
49
49
|
export declare function Button({ variant, size, className, children, style, ...rest }: {
|
|
50
|
+
/** Visual style. Defaults to `"default"`. */
|
|
50
51
|
variant?: ButtonVariant;
|
|
52
|
+
/** Size preset. Defaults to `"default"`. */
|
|
51
53
|
size?: ButtonSize;
|
|
54
|
+
/** Additional CSS class names, appended to the button's own. */
|
|
52
55
|
className?: string;
|
|
56
|
+
/** Button label / content. */
|
|
53
57
|
children?: ReactNode;
|
|
54
58
|
} & Omit<ButtonHTMLAttributes<HTMLButtonElement>, "className">): import("react").JSX.Element;
|
|
@@ -53,10 +53,10 @@ function variantColors(variant, theme) {
|
|
|
53
53
|
* }
|
|
54
54
|
* ```
|
|
55
55
|
*
|
|
56
|
-
* @param
|
|
57
|
-
* @
|
|
58
|
-
* @
|
|
59
|
-
*
|
|
56
|
+
* @param props - Button props: `variant` (visual style — see
|
|
57
|
+
* {@link ButtonVariant}, defaults to `"default"`), `size` (see
|
|
58
|
+
* {@link ButtonSize}, defaults to `"default"`), `className` (appended to the
|
|
59
|
+
* button's own classes), `children` (the label), and any `<button>` attribute.
|
|
60
60
|
*
|
|
61
61
|
* @public
|
|
62
62
|
*/
|
|
@@ -21,14 +21,15 @@ import type { ReactNode } from "react";
|
|
|
21
21
|
* }
|
|
22
22
|
* ```
|
|
23
23
|
*
|
|
24
|
-
* @param
|
|
25
|
-
* @param title - Optional title string for the header.
|
|
26
|
-
* @param className - Additional CSS class names applied to the root element.
|
|
24
|
+
* @param props - Chat surface props.
|
|
27
25
|
*
|
|
28
26
|
* @public
|
|
29
27
|
*/
|
|
30
28
|
export declare function ChatView({ icon, title, className, }: {
|
|
29
|
+
/** Element rendered in place of the logo in the header. */
|
|
31
30
|
icon?: ReactNode | undefined;
|
|
31
|
+
/** Title string for the header. Defaults to the agent's declared name. */
|
|
32
32
|
title?: string | undefined;
|
|
33
|
+
/** Additional CSS class names for the root element, appended to its own. */
|
|
33
34
|
className?: string | undefined;
|
|
34
35
|
}): ReactNode;
|