@alexkroman1/aai-ui 0.12.3 → 0.13.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 +257 -0
- package/dist/_colors-CpZO-88A.js +78 -0
- package/dist/_module-url-C13kAJ87.js +15 -0
- package/dist/_page-shell.d.ts +11 -0
- package/dist/_recover-run.d.ts +79 -0
- package/dist/_repeat-until.d.ts +30 -0
- package/dist/_run-controls.d.ts +33 -0
- package/dist/_submission-state.d.ts +92 -0
- package/dist/_tap-to-talk-state.d.ts +88 -0
- package/dist/_utils-Mi-SCFA1.js +47 -0
- package/dist/_utils.d.ts +26 -0
- package/dist/_web-storage.d.ts +43 -0
- package/dist/_workflow-api-ref.d.ts +37 -0
- package/dist/_workflow-files.d.ts +34 -0
- package/dist/_workflow-test-defs.d.ts +38 -0
- package/dist/aai-logo-D8pMVn9_.js +85 -0
- package/dist/agent-state-labels.d.ts +65 -0
- package/dist/agent-state.d.ts +108 -0
- package/dist/audio/capture.d.ts +44 -0
- package/dist/audio/index.d.ts +18 -0
- package/dist/audio/index.js +431 -0
- package/dist/audio/preconnect.d.ts +87 -0
- package/dist/audio/voice-io.d.ts +112 -0
- package/dist/build-default-client.d.ts +1 -0
- package/dist/chat-view-BCbI7gD4.js +396 -0
- package/dist/client-config-C7fQrioM.js +139 -0
- package/dist/client-config.d.ts +119 -0
- package/dist/client-dir.d.ts +44 -0
- package/dist/client-dir.js +58 -0
- package/dist/client-identity.d.ts +131 -0
- package/dist/client-tool.d.ts +51 -0
- package/dist/components/_colors.d.ts +73 -0
- package/dist/components/_form-readiness.d.ts +56 -0
- package/dist/components/_form-values.d.ts +19 -0
- package/dist/components/aai-logo.d.ts +12 -0
- package/dist/components/audio-result.d.ts +94 -0
- package/dist/components/auto-scroll.d.ts +74 -0
- package/dist/components/bullet-list.d.ts +74 -0
- package/dist/components/button.d.ts +29 -21
- package/dist/components/button.js +62 -23
- package/dist/components/chat-view.d.ts +24 -18
- package/dist/components/chat-view.js +2 -60
- package/dist/components/console-shell.d.ts +85 -0
- package/dist/components/controls.d.ts +21 -6
- package/dist/components/controls.js +31 -19
- package/dist/components/conversation-view.d.ts +130 -0
- package/dist/components/eyebrow.d.ts +12 -0
- package/dist/components/facts.d.ts +81 -0
- package/dist/components/form-fields.d.ts +142 -0
- package/dist/components/form-types.d.ts +73 -0
- package/dist/components/form.d.ts +96 -0
- package/dist/components/markdown.d.ts +57 -0
- package/dist/components/message-list.d.ts +34 -7
- package/dist/components/message-list.js +2 -75
- package/dist/components/session-controls.d.ts +125 -0
- package/dist/components/session-error-banner.d.ts +69 -0
- package/dist/components/session-state-dot.d.ts +111 -0
- package/dist/components/sidebar-layout.d.ts +31 -10
- package/dist/components/sidebar-layout.js +32 -12
- package/dist/components/start-screen.d.ts +17 -6
- package/dist/components/start-screen.js +34 -14
- package/dist/components/tool-call-block.d.ts +15 -14
- package/dist/components/tool-call-block.js +2 -117
- package/dist/components/tool-call-row.d.ts +75 -0
- package/dist/components/tool-config-context.d.ts +22 -0
- package/dist/components/upload-progress.d.ts +76 -0
- package/dist/components/url-chips.d.ts +29 -0
- package/dist/components/workflow-fields.d.ts +46 -0
- package/dist/components/workflow-pending-note.d.ts +81 -0
- package/dist/components/workflow-progress.d.ts +81 -0
- package/dist/components/workflow-run-error.d.ts +48 -0
- package/dist/components/workflow-run-panel.d.ts +79 -0
- package/dist/context.d.ts +288 -0
- package/dist/context.js +382 -0
- package/dist/conversation-log.d.ts +88 -0
- package/dist/default-client/assets/_module-url-BX0RuRU2.js +1 -0
- package/dist/default-client/assets/audio-cGFGID-2.js +1 -0
- package/dist/default-client/assets/capture-processor-BUABoo3_.js +90 -0
- package/dist/default-client/assets/index-8ihb1Urm.css +2 -0
- package/dist/default-client/assets/index-BQURVARl.js +126 -0
- package/dist/default-client/assets/playback-processor-ABOa11j0.js +314 -0
- package/dist/default-client/favicon.ico +0 -0
- package/dist/default-client/index.html +15 -0
- package/dist/default-client.d.ts +23 -0
- package/dist/define-client.d.ts +162 -26
- package/dist/define-client.js +201 -55
- package/dist/eyebrow-UfmSz9yy.js +27 -0
- package/dist/hooks-DN4gHYsj.js +182 -0
- package/dist/hooks.d.ts +172 -0
- package/dist/hooks.js +3 -0
- package/dist/hooks.test-d.d.ts +14 -0
- package/dist/inbox-protocol.d.ts +128 -0
- package/dist/inbox.d.ts +132 -0
- package/dist/index.d.ts +126 -29
- package/dist/index.js +5500 -15
- package/dist/internal.d.ts +43 -0
- package/dist/internal.js +8 -0
- package/dist/linked-client.d.ts +70 -0
- package/dist/message-list-DHZcLhRA.js +682 -0
- package/dist/notice-player.d.ts +70 -0
- package/dist/page.d.ts +114 -0
- package/dist/phone.d.ts +50 -0
- package/dist/route-fetch.d.ts +79 -0
- package/dist/session/audio-effects.d.ts +31 -0
- package/dist/session/audio-setup.d.ts +57 -0
- package/dist/session/audio-state.d.ts +157 -0
- package/dist/session/browser-session.d.ts +30 -0
- package/dist/session/close.d.ts +26 -0
- package/dist/session/dial.d.ts +65 -0
- package/dist/session/handshake.d.ts +60 -0
- package/dist/session/index.d.ts +17 -0
- package/dist/session/index.js +2 -0
- package/dist/session/messages.d.ts +63 -0
- package/dist/session/mic.d.ts +64 -0
- package/dist/session/preconnect.d.ts +48 -0
- package/dist/session/reconnect.d.ts +25 -0
- package/dist/session/resume-store.d.ts +57 -0
- package/dist/session/state.d.ts +146 -0
- package/dist/session/ticket.d.ts +44 -0
- package/dist/session/types.d.ts +476 -0
- package/dist/session/url.d.ts +17 -0
- package/dist/session/user-turn.d.ts +55 -0
- package/dist/session-CE0Ipsb3.js +2129 -0
- package/dist/stored-value.d.ts +103 -0
- package/dist/tool-call-block-uXtMOUWN.js +204 -0
- package/dist/tool-config-context-Es4YUzV2.js +19 -0
- package/dist/types.d.ts +272 -34
- package/dist/types.js +35 -4
- package/dist/upload/files.d.ts +83 -0
- package/dist/upload/index.d.ts +15 -0
- package/dist/upload/pause.d.ts +31 -0
- package/dist/upload/recall.d.ts +67 -0
- package/dist/upload/report.d.ts +26 -0
- package/dist/upload/session.d.ts +99 -0
- package/dist/url-chips-oyy_Eiqh.js +239 -0
- package/dist/use-client-runs.d.ts +87 -0
- package/dist/use-conversation-log.d.ts +86 -0
- package/dist/use-conversation.d.ts +131 -0
- package/dist/use-copy.d.ts +74 -0
- package/dist/use-download-url.d.ts +84 -0
- package/dist/use-flash.d.ts +61 -0
- package/dist/use-inbox.d.ts +101 -0
- package/dist/use-push-to-talk.d.ts +105 -0
- package/dist/use-route-mutation.d.ts +113 -0
- package/dist/use-route.d.ts +82 -0
- package/dist/use-run-key.d.ts +133 -0
- package/dist/use-session-controls.d.ts +69 -0
- package/dist/use-session-id.d.ts +57 -0
- package/dist/use-tap-to-talk.d.ts +138 -0
- package/dist/use-user-transcript-uyHhzy4d.js +73 -0
- package/dist/use-user-transcript.d.ts +75 -0
- package/dist/use-workflow-form.d.ts +285 -0
- package/dist/use-workflow-progress.d.ts +137 -0
- package/dist/use-workflow-run-DdhWmbx-.js +697 -0
- package/dist/use-workflow-run.d.ts +100 -0
- package/dist/use-workflow-runs.d.ts +71 -0
- package/dist/use-workflow-stream.d.ts +154 -0
- package/dist/use-workflows.d.ts +77 -0
- package/dist/workflow-client.d.ts +136 -0
- package/dist/workflow-def-types.d.ts +31 -0
- package/dist/workflow-events.d.ts +39 -0
- package/dist/workflow-field-kind.d.ts +95 -0
- package/dist/workflow-status-labels.d.ts +35 -0
- package/dist/worklets/_module-url.d.ts +10 -0
- package/dist/worklets/_playback-bench-harness.d.ts +181 -0
- package/dist/worklets/_playback-bench-host.d.ts +63 -0
- package/dist/worklets/_playback-bench-page.d.ts +65 -0
- package/dist/worklets/_tts-trace-harness.d.ts +142 -0
- package/dist/worklets/_worklet-test-utils.d.ts +27 -0
- package/dist/worklets/capture-processor.d.ts +4 -2
- package/dist/worklets/capture-processor.js +73 -33
- package/dist/worklets/playback-processor.d.ts +4 -2
- package/dist/worklets/playback-processor.js +258 -42
- package/package.json +57 -32
- package/styles.css +152 -30
- package/dist/audio.d.ts +0 -45
- package/dist/audio.js +0 -121
- package/dist/client-context.d.ts +0 -33
- package/dist/client-context.js +0 -15
- package/dist/client-handler.d.ts +0 -43
- package/dist/components/app.d.ts +0 -23
- package/dist/components/app.js +0 -41
- package/dist/components/error-banner.d.ts +0 -21
- package/dist/components/error-banner.js +0 -27
- package/dist/components/message-bubble.d.ts +0 -23
- package/dist/components/message-bubble.js +0 -35
- package/dist/components/state-indicator.d.ts +0 -29
- package/dist/components/state-indicator.js +0 -37
- package/dist/components/thinking-indicator.d.ts +0 -16
- package/dist/components/thinking-indicator.js +0 -34
- package/dist/components/tool-icons.d.ts +0 -17
- package/dist/components/tool-icons.js +0 -128
- package/dist/components/transcript.d.ts +0 -25
- package/dist/components/transcript.js +0 -35
- package/dist/session-CWB7vmqz.js +0 -429
- package/dist/session.d.ts +0 -69
- package/dist/session.js +0 -2
- package/dist/signals.d.ts +0 -120
- package/dist/signals.js +0 -161
- package/dist/tsdown.config.d.ts +0 -2
- package/dist/types.test-d.d.ts +0 -7
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `useClientRuns` — the page half of `clientRunsRoutes()` on
|
|
3
|
+
* `@alexkroman1/aai`: the runs going on for this client, polled, with a
|
|
4
|
+
* cancel.
|
|
5
|
+
*
|
|
6
|
+
* `useRoute` over the list route plus `useRouteMutation` over the cancel one,
|
|
7
|
+
* so it inherits both halves' rules — the stale-read drop, data kept through a
|
|
8
|
+
* failed poll, the session's client as `?client=`, no state after unmount —
|
|
9
|
+
* and adds only what every "Running" panel re-wrote: the row type, the cancel
|
|
10
|
+
* URL (`DELETE <path>/<runId>`, the id encoded) and the re-read after it.
|
|
11
|
+
*
|
|
12
|
+
* @module
|
|
13
|
+
*/
|
|
14
|
+
import type { ClientRun } from "@alexkroman1/aai";
|
|
15
|
+
/**
|
|
16
|
+
* Options for {@link useClientRuns}.
|
|
17
|
+
*
|
|
18
|
+
* @public
|
|
19
|
+
*/
|
|
20
|
+
export type UseClientRunsOptions = {
|
|
21
|
+
/** Read the list again every this many ms, while mounted. Default 5000; `0` never. */
|
|
22
|
+
pollMs?: number | undefined;
|
|
23
|
+
/**
|
|
24
|
+
* The `?client=` to send. Default: the session's client inside
|
|
25
|
+
* `mountClient()`, none on a page with no session.
|
|
26
|
+
*/
|
|
27
|
+
client?: string | undefined;
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* What {@link useClientRuns} returns.
|
|
31
|
+
*
|
|
32
|
+
* @public
|
|
33
|
+
*/
|
|
34
|
+
export type UseClientRunsResult = {
|
|
35
|
+
/** The client's runs, oldest first — `undefined` until the first read lands. */
|
|
36
|
+
runs: ClientRun[] | undefined;
|
|
37
|
+
/** Why the last read or cancel failed — the route's `{ error }` when it said — or `undefined`. */
|
|
38
|
+
error: string | undefined;
|
|
39
|
+
/** Read the list again now. */
|
|
40
|
+
reload: () => void;
|
|
41
|
+
/**
|
|
42
|
+
* Cancel one run, then re-read the list. Resolves `true` when this call
|
|
43
|
+
* ended it, `false` when it had already finished or the cancel failed (the
|
|
44
|
+
* reason is in `error`). Never rejects.
|
|
45
|
+
*/
|
|
46
|
+
cancel: (runId: string) => Promise<boolean>;
|
|
47
|
+
/** The run id whose cancel is in flight, or `undefined` — to disable its button. */
|
|
48
|
+
cancelling: string | undefined;
|
|
49
|
+
};
|
|
50
|
+
/**
|
|
51
|
+
* The runs going on for this client, from a `clientRunsRoutes()` pair — see
|
|
52
|
+
* this module's doc.
|
|
53
|
+
*
|
|
54
|
+
* @example A "Running" panel with a Cancel on each live reminder
|
|
55
|
+
* ```tsx
|
|
56
|
+
* import { useClientRuns } from "@alexkroman1/aai-ui";
|
|
57
|
+
*
|
|
58
|
+
* function Running() {
|
|
59
|
+
* const { runs, error, cancel, cancelling } = useClientRuns();
|
|
60
|
+
* if (!runs) return error ? <p role="alert">{error}</p> : null;
|
|
61
|
+
* return (
|
|
62
|
+
* <ul>
|
|
63
|
+
* {runs.map((r) => (
|
|
64
|
+
* <li key={r.runId}>
|
|
65
|
+
* {r.title} — {r.status}
|
|
66
|
+
* {r.detail && <small>{r.detail}</small>}
|
|
67
|
+
* {r.status === "running" && (
|
|
68
|
+
* <button type="button" disabled={cancelling === r.runId} onClick={() => void cancel(r.runId)}>
|
|
69
|
+
* Cancel
|
|
70
|
+
* </button>
|
|
71
|
+
* )}
|
|
72
|
+
* </li>
|
|
73
|
+
* ))}
|
|
74
|
+
* </ul>
|
|
75
|
+
* );
|
|
76
|
+
* }
|
|
77
|
+
* ```
|
|
78
|
+
*
|
|
79
|
+
* @param path - The `path` the routes were declared with, without `/api`.
|
|
80
|
+
* Default `"/tasks"`, `clientRunsRoutes()`' own default.
|
|
81
|
+
* @param options - `pollMs` and `client`; see {@link UseClientRunsOptions}.
|
|
82
|
+
* @returns The rows, the last error, `reload`, `cancel` and the id being
|
|
83
|
+
* cancelled; see {@link UseClientRunsResult}.
|
|
84
|
+
*
|
|
85
|
+
* @public
|
|
86
|
+
*/
|
|
87
|
+
export declare function useClientRuns(path?: string, options?: UseClientRunsOptions): UseClientRunsResult;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `useConversationLog` — everything this page has said and heard, across
|
|
3
|
+
* sessions, kept in `localStorage`.
|
|
4
|
+
*
|
|
5
|
+
* `useConversation()` is the CURRENT session; a page beside a device holds a
|
|
6
|
+
* new resumable session every few turns, and wants the whole history on screen
|
|
7
|
+
* after a reload. This hook writes the live conversation into a persisted log
|
|
8
|
+
* as it happens — handling the replay a resumed session brings back, so no turn
|
|
9
|
+
* is logged twice (the rules are `conversation-log.ts`'s module doc) — plus the
|
|
10
|
+
* page's own notes, what the device said on its own, and, through `mirror`, the
|
|
11
|
+
* live conversation of the client's OTHER sessions (a linked speaker's).
|
|
12
|
+
*
|
|
13
|
+
* **It opens no inbox socket.** The inbox keeps one socket per (client, holder)
|
|
14
|
+
* and a second from the same tab REPLACES the first, so `mirror` is a handler
|
|
15
|
+
* to hand the page's one `useInbox({ onEvent })`.
|
|
16
|
+
*
|
|
17
|
+
* `<ConversationView log={entries}>` renders it with the kit's own rows.
|
|
18
|
+
*
|
|
19
|
+
* @module
|
|
20
|
+
*/
|
|
21
|
+
import { type ConversationLogEntry } from "./conversation-log.ts";
|
|
22
|
+
import type { InboxEvent } from "./inbox-protocol.ts";
|
|
23
|
+
/**
|
|
24
|
+
* Options for {@link useConversationLog}.
|
|
25
|
+
*
|
|
26
|
+
* @public
|
|
27
|
+
*/
|
|
28
|
+
export type UseConversationLogOptions = {
|
|
29
|
+
/**
|
|
30
|
+
* The `localStorage` key, read once on mount. Default: one per agent URL, so
|
|
31
|
+
* two agents on one origin keep separate logs.
|
|
32
|
+
*/
|
|
33
|
+
storageKey?: string | undefined;
|
|
34
|
+
/** Entries kept; the oldest go first. Default 300 — `localStorage` holds a few MB per origin. */
|
|
35
|
+
max?: number | undefined;
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* What {@link useConversationLog} returns.
|
|
39
|
+
*
|
|
40
|
+
* @public
|
|
41
|
+
*/
|
|
42
|
+
export type UseConversationLogResult = {
|
|
43
|
+
/** The log, oldest first; the live session is its newest session entry. */
|
|
44
|
+
readonly entries: readonly ConversationLogEntry[];
|
|
45
|
+
/** Log a line of the page's own: "New session", "Continuing an earlier conversation". */
|
|
46
|
+
addNote(text: string): void;
|
|
47
|
+
/** Log what the agent said outside a session — a reminder read aloud — as an assistant turn. */
|
|
48
|
+
addSpoken(text: string): void;
|
|
49
|
+
/**
|
|
50
|
+
* Mirror a frame of the client's live conversation into the log — pass it as
|
|
51
|
+
* `useInbox({ onEvent: log.mirror })`. The page's own session is skipped (it
|
|
52
|
+
* logs itself); every other session of the client is logged as it happens.
|
|
53
|
+
*/
|
|
54
|
+
mirror(event: InboxEvent): void;
|
|
55
|
+
/** Forget the whole log, stored copy included. */
|
|
56
|
+
clear(): void;
|
|
57
|
+
};
|
|
58
|
+
/**
|
|
59
|
+
* A transcript that outlives the session — see this module's doc.
|
|
60
|
+
*
|
|
61
|
+
* Must be used inside the provider `mountClient()` installs; call it once per
|
|
62
|
+
* page, since two logs over one key would overwrite each other.
|
|
63
|
+
*
|
|
64
|
+
* @example The whole history, with a note per new session and a linked speaker mirrored
|
|
65
|
+
* ```tsx
|
|
66
|
+
* import { ConversationView, useConversationLog, useInbox } from "@alexkroman1/aai-ui";
|
|
67
|
+
*
|
|
68
|
+
* function History() {
|
|
69
|
+
* const log = useConversationLog();
|
|
70
|
+
* useInbox({ onEvent: log.mirror, onNotice: (n) => log.addNote(n.event) });
|
|
71
|
+
* return (
|
|
72
|
+
* <ConversationView
|
|
73
|
+
* log={log.entries}
|
|
74
|
+
* className="flex-1 min-h-0"
|
|
75
|
+
* renderMessage={(m) => <p data-role={m.role}>{m.content}</p>}
|
|
76
|
+
* />
|
|
77
|
+
* );
|
|
78
|
+
* }
|
|
79
|
+
* ```
|
|
80
|
+
*
|
|
81
|
+
* @param options - Where it is stored and how much; see {@link UseConversationLogOptions}.
|
|
82
|
+
* @returns The entries and the ways to add to them; see {@link UseConversationLogResult}.
|
|
83
|
+
*
|
|
84
|
+
* @public
|
|
85
|
+
*/
|
|
86
|
+
export declare function useConversationLog(options?: UseConversationLogOptions): UseConversationLogResult;
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `useConversation` — the exchange, already assembled, with nothing rendered.
|
|
3
|
+
*
|
|
4
|
+
* `<MessageList>` owns four decisions that are not obvious and are not
|
|
5
|
+
* derivable from the snapshot by eye: the message/tool-call INTERLEAVE, the
|
|
6
|
+
* streaming agent utterance, the live user transcript with its `null`-vs-`""`
|
|
7
|
+
* protocol distinction, and the thinking indicator's suppression rule. Its only
|
|
8
|
+
* prop is `className`, so the moment a client wants its own bubble markup it
|
|
9
|
+
* drops all four at once — and the three hand-rolled chromes in the template
|
|
10
|
+
* tree each shipped a strictly worse conversation for exactly that reason. One
|
|
11
|
+
* of them runs fifteen tools and its operator sees none of them, because the
|
|
12
|
+
* tool calls live in a second array nothing in that page ever reads.
|
|
13
|
+
*
|
|
14
|
+
* This is the headless half. `<MessageList>` is now a thin consumer of it —
|
|
15
|
+
* which is the only way to know the hook is complete, since a hook that the
|
|
16
|
+
* package's own list cannot be built from is a hook a custom chrome will find a
|
|
17
|
+
* hole in.
|
|
18
|
+
*
|
|
19
|
+
* ## It subscribes NARROWLY, and that is half the value
|
|
20
|
+
*
|
|
21
|
+
* The three custom chromes also call whole-page `useSession()`, which re-renders
|
|
22
|
+
* on *every* snapshot change — so a dispatch board re-renders at STT-partial
|
|
23
|
+
* rate. `<ChatView>` deliberately avoids that with per-field
|
|
24
|
+
* {@link useSessionSelector} calls, and so does this: a component that reads the
|
|
25
|
+
* conversation gets the conversation's own update rate, not the session's.
|
|
26
|
+
*/
|
|
27
|
+
import type { ChatMessage, ToolCallInfo } from "./types.ts";
|
|
28
|
+
import { type UseUserTranscriptResult } from "./use-user-transcript.ts";
|
|
29
|
+
/**
|
|
30
|
+
* One row of the conversation: a finalized message, or a tool invocation.
|
|
31
|
+
*
|
|
32
|
+
* A discriminated union rather than two arrays, because the ORDER between them
|
|
33
|
+
* is the thing this hook computes — handing back two lists would hand back the
|
|
34
|
+
* problem. `kind` is what a `switch` in a custom renderer narrows on.
|
|
35
|
+
*
|
|
36
|
+
* @public
|
|
37
|
+
*/
|
|
38
|
+
export type ConversationItem = {
|
|
39
|
+
kind: "message";
|
|
40
|
+
message: ChatMessage;
|
|
41
|
+
} | {
|
|
42
|
+
kind: "tool";
|
|
43
|
+
toolCall: ToolCallInfo;
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* What {@link useConversation} returns.
|
|
47
|
+
*
|
|
48
|
+
* @public
|
|
49
|
+
*/
|
|
50
|
+
export type UseConversationResult = {
|
|
51
|
+
/**
|
|
52
|
+
* Messages and tool calls in one list, in the order they happened.
|
|
53
|
+
*
|
|
54
|
+
* Referentially stable while neither array changes, so a consumer may map it
|
|
55
|
+
* inside a `useMemo` keyed on it, or hand rows to `memo()`ed components,
|
|
56
|
+
* without rebuilding the list on unrelated snapshot updates.
|
|
57
|
+
*/
|
|
58
|
+
items: readonly ConversationItem[];
|
|
59
|
+
/**
|
|
60
|
+
* The agent's utterance as it arrives, or `null` between turns.
|
|
61
|
+
*
|
|
62
|
+
* Not yet a member of `items`: it has no id and it is replaced wholesale on
|
|
63
|
+
* every delta, so it is rendered as its own trailing row and disappears when
|
|
64
|
+
* the finalized message takes its place.
|
|
65
|
+
*/
|
|
66
|
+
streaming: string | null;
|
|
67
|
+
/**
|
|
68
|
+
* The caller's in-progress turn — {@link useUserTranscript}'s result,
|
|
69
|
+
* forwarded rather than re-derived, so the `null`-vs-`""` distinction is made
|
|
70
|
+
* in exactly one place.
|
|
71
|
+
*/
|
|
72
|
+
transcript: UseUserTranscriptResult;
|
|
73
|
+
/**
|
|
74
|
+
* Whether to show a thinking indicator.
|
|
75
|
+
*
|
|
76
|
+
* The suppression rule, and it is why this is a field rather than
|
|
77
|
+
* `state === "thinking"`: the agent is `thinking` for a stretch during which
|
|
78
|
+
* something ELSE is already saying so. A pending tool call draws its own
|
|
79
|
+
* spinner, and a trailing agent message means the reply has begun landing —
|
|
80
|
+
* in both cases a second indicator underneath reads as a second thing
|
|
81
|
+
* happening. So it is on only while `thinking` with no pending tool call, and
|
|
82
|
+
* either no messages yet, a trailing USER message, or a settled tool call
|
|
83
|
+
* after it.
|
|
84
|
+
*/
|
|
85
|
+
thinking: boolean;
|
|
86
|
+
};
|
|
87
|
+
/**
|
|
88
|
+
* Interleave messages and tool calls, ordered by insertion time.
|
|
89
|
+
*
|
|
90
|
+
* Each tool call belongs immediately after its anchor message
|
|
91
|
+
* (`afterMessageId`); tool calls whose anchor slid out of the retained window —
|
|
92
|
+
* or that were inserted before any message existed — come first, since there is
|
|
93
|
+
* nothing left for them to follow.
|
|
94
|
+
*/
|
|
95
|
+
export declare function interleave(messages: readonly ChatMessage[], toolCalls: readonly ToolCallInfo[]): ConversationItem[];
|
|
96
|
+
/**
|
|
97
|
+
* Subscribe to the conversation: the interleaved exchange, the streaming
|
|
98
|
+
* utterance, the live transcript and the thinking rule — with no markup.
|
|
99
|
+
*
|
|
100
|
+
* Must be used inside the provider `mountClient()` installs.
|
|
101
|
+
*
|
|
102
|
+
* @example A custom bubble, keeping every rule `<MessageList>` knows
|
|
103
|
+
* ```tsx
|
|
104
|
+
* import { useConversation } from "@alexkroman1/aai-ui";
|
|
105
|
+
*
|
|
106
|
+
* function Transcript() {
|
|
107
|
+
* const { items, streaming, transcript, thinking } = useConversation();
|
|
108
|
+
* return (
|
|
109
|
+
* <div>
|
|
110
|
+
* {items.map((item) =>
|
|
111
|
+
* item.kind === "message" ? (
|
|
112
|
+
* <p key={item.message.id} data-role={item.message.role}>
|
|
113
|
+
* {item.message.content}
|
|
114
|
+
* </p>
|
|
115
|
+
* ) : (
|
|
116
|
+
* <code key={item.toolCall.callId}>{item.toolCall.name}</code>
|
|
117
|
+
* ),
|
|
118
|
+
* )}
|
|
119
|
+
* {streaming !== null && <p data-role="assistant">{streaming}</p>}
|
|
120
|
+
* {transcript.speaking && <p data-role="user">{transcript.text}</p>}
|
|
121
|
+
* {thinking && <p>…</p>}
|
|
122
|
+
* </div>
|
|
123
|
+
* );
|
|
124
|
+
* }
|
|
125
|
+
* ```
|
|
126
|
+
*
|
|
127
|
+
* @returns See {@link UseConversationResult}.
|
|
128
|
+
*
|
|
129
|
+
* @public
|
|
130
|
+
*/
|
|
131
|
+
export declare function useConversation(): UseConversationResult;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copy-to-clipboard with a per-item flash on the button that triggered it.
|
|
3
|
+
*
|
|
4
|
+
* Anywhere a UI hands the reader a string to paste somewhere else — a session
|
|
5
|
+
* URL, a webhook, a `curl` line — the button wants the same three things, and
|
|
6
|
+
* two of them are easy to get wrong:
|
|
7
|
+
*
|
|
8
|
+
* - The flash is keyed by the copied TEXT, so one row's "Copied" does not light
|
|
9
|
+
* up every button beside it.
|
|
10
|
+
* - A FAILED write is reported rather than swallowed. There is no clipboard in
|
|
11
|
+
* an insecure context, and a browser may refuse the permission; a `catch`
|
|
12
|
+
* that does nothing leaves a button that visibly does nothing when clicked,
|
|
13
|
+
* which reads as a broken page rather than as a blocked capability.
|
|
14
|
+
* - The single-timer / clear-on-unmount half is {@link useFlash}'s.
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* What {@link useCopy} hands back — the click handler and the two readings a
|
|
18
|
+
* button needs off one shared flash.
|
|
19
|
+
*
|
|
20
|
+
* @public
|
|
21
|
+
*/
|
|
22
|
+
export type UseCopyResult = {
|
|
23
|
+
/** Copy `text`, flashing the button that owns it. */
|
|
24
|
+
copy: (text: string) => void;
|
|
25
|
+
/**
|
|
26
|
+
* The button label for `text` — `idle` until it is clicked, then `"Copied"`
|
|
27
|
+
* or `"Failed"` for the length of the flash.
|
|
28
|
+
*
|
|
29
|
+
* Only the idle word is the caller's: it is the button's NAME (`"Copy"`,
|
|
30
|
+
* `"UI"`, `"Webhook URL"`), where the other two are the OUTCOME and are the
|
|
31
|
+
* one bit of state a reader can see. A caller wanting other words for those
|
|
32
|
+
* reads {@link UseCopyResult.didCopy} and writes its own.
|
|
33
|
+
*/
|
|
34
|
+
label: (text: string, idle?: string) => string;
|
|
35
|
+
/** True when `text` was the last thing copied, successfully. */
|
|
36
|
+
didCopy: (text: string) => boolean;
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* One copier for a group of copy buttons.
|
|
40
|
+
*
|
|
41
|
+
* @remarks
|
|
42
|
+
* Call it ONCE per group and pass the {@link UseCopyResult} down, rather than once per
|
|
43
|
+
* button: the flash is shared, so clicking a second row clears the first row's
|
|
44
|
+
* "Copied" — which is what makes a list of URLs readable, since two rows
|
|
45
|
+
* claiming to be on the clipboard is a lie about one of them.
|
|
46
|
+
*
|
|
47
|
+
* A chip whose idle text is its own name rather than the word "Copy" passes
|
|
48
|
+
* that name to {@link UseCopyResult.label}; the two outcome words are fixed, for the
|
|
49
|
+
* reason that member's own doc gives.
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* ```tsx
|
|
53
|
+
* import { useCopy } from "@alexkroman1/aai-ui";
|
|
54
|
+
*
|
|
55
|
+
* function UrlList({ urls }: { urls: readonly string[] }) {
|
|
56
|
+
* const copier = useCopy();
|
|
57
|
+
* return (
|
|
58
|
+
* <ul>
|
|
59
|
+
* {urls.map((url) => (
|
|
60
|
+
* <li key={url}>
|
|
61
|
+
* <code>{url}</code>
|
|
62
|
+
* <button type="button" onClick={() => copier.copy(url)}>
|
|
63
|
+
* {copier.label(url)}
|
|
64
|
+
* </button>
|
|
65
|
+
* </li>
|
|
66
|
+
* ))}
|
|
67
|
+
* </ul>
|
|
68
|
+
* );
|
|
69
|
+
* }
|
|
70
|
+
* ```
|
|
71
|
+
*
|
|
72
|
+
* @public
|
|
73
|
+
*/
|
|
74
|
+
export declare function useCopy(): UseCopyResult;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `useDownloadUrl` — an upload id a run produced, as something `<audio>`,
|
|
3
|
+
* `<img>` or `<a download>` will accept.
|
|
4
|
+
*
|
|
5
|
+
* `api.download(id)` resolves a `Blob`, and it has to: the byte route takes the
|
|
6
|
+
* same bearer every workflow route does, and neither `<audio src>` nor
|
|
7
|
+
* `<a href>` can send one. So every page that plays back what a run WROTE ends
|
|
8
|
+
* up at the same four lines — `download` → `createObjectURL` → state — and the
|
|
9
|
+
* two that are really the point are the two the four lines are wrapped in:
|
|
10
|
+
*
|
|
11
|
+
* - **`URL.revokeObjectURL` on cleanup.** An object URL pins its blob for the
|
|
12
|
+
* life of the DOCUMENT. Miss it and every completed run's audio stays resident
|
|
13
|
+
* until the tab closes, which on a page people run all day is a leak measured
|
|
14
|
+
* in the size of the files.
|
|
15
|
+
* - **A `cancelled` flag.** A second run settling while the first download is
|
|
16
|
+
* still in flight otherwise sets state from the stale one, and the page plays
|
|
17
|
+
* the previous run's audio under the current run's transcript — a wrong answer
|
|
18
|
+
* that looks like a right one.
|
|
19
|
+
*
|
|
20
|
+
* Two templates had written this hook, identically, doc paragraph included, and
|
|
21
|
+
* `aai-ui` exported no download helper at all. Both also faked `pending` by
|
|
22
|
+
* checking `url === undefined && error === undefined`, which reads "idle" and
|
|
23
|
+
* "downloading" as the same thing — so this reports it.
|
|
24
|
+
*/
|
|
25
|
+
import type { WorkflowApi } from "./workflow-client.ts";
|
|
26
|
+
/** Options for {@link useDownloadUrl}. */
|
|
27
|
+
export type UseDownloadUrlOptions = {
|
|
28
|
+
/** The client to read the bytes with. Defaults to one for the page's own agent. */
|
|
29
|
+
api?: WorkflowApi;
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* What {@link useDownloadUrl} reports.
|
|
33
|
+
*
|
|
34
|
+
* @public
|
|
35
|
+
*/
|
|
36
|
+
export type UseDownloadUrlResult = {
|
|
37
|
+
/**
|
|
38
|
+
* An object URL for the stored bytes, once they are here. Valid until the id
|
|
39
|
+
* changes or the component unmounts — do not stash it anywhere that outlives
|
|
40
|
+
* the render that read it.
|
|
41
|
+
*/
|
|
42
|
+
url?: string;
|
|
43
|
+
/** The read's failure, as the agent's own sentence where it gave one. */
|
|
44
|
+
error?: string;
|
|
45
|
+
/**
|
|
46
|
+
* True while the bytes are on their way.
|
|
47
|
+
*
|
|
48
|
+
* Its own field rather than "neither `url` nor `error`", which cannot tell a
|
|
49
|
+
* download in flight from no id to download — the two states a page most
|
|
50
|
+
* wants to render differently (a spinner, and nothing at all).
|
|
51
|
+
*/
|
|
52
|
+
pending: boolean;
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* Read an upload's bytes and hand back a URL a DOM element can use.
|
|
56
|
+
*
|
|
57
|
+
* @param uploadId - The id a completed run reported, or `undefined` before one
|
|
58
|
+
* exists — which is what a page passes straight through while it waits, and
|
|
59
|
+
* reports as idle rather than pending.
|
|
60
|
+
* @param options - See {@link UseDownloadUrlOptions}.
|
|
61
|
+
* @returns See {@link UseDownloadUrlResult}.
|
|
62
|
+
*
|
|
63
|
+
* @example
|
|
64
|
+
* ```tsx no-check
|
|
65
|
+
* import { useDownloadUrl, useWorkflowSubmit } from "@alexkroman1/aai-ui";
|
|
66
|
+
* import type { spokenSummary } from "./agent.ts";
|
|
67
|
+
*
|
|
68
|
+
* function Playback() {
|
|
69
|
+
* const { run } = useWorkflowSubmit<typeof spokenSummary>("spokenSummary");
|
|
70
|
+
* const output = run?.status === "completed" ? run.output : undefined;
|
|
71
|
+
* const audio = useDownloadUrl(output?.audio);
|
|
72
|
+
* if (audio.pending) return <p>Fetching audio…</p>;
|
|
73
|
+
* if (audio.error !== undefined) return <p role="alert">{audio.error}</p>;
|
|
74
|
+
* return audio.url === undefined ? null : (
|
|
75
|
+
* <a href={audio.url} download="summary.mp3">
|
|
76
|
+
* Download
|
|
77
|
+
* </a>
|
|
78
|
+
* );
|
|
79
|
+
* }
|
|
80
|
+
* ```
|
|
81
|
+
*
|
|
82
|
+
* @public
|
|
83
|
+
*/
|
|
84
|
+
export declare function useDownloadUrl(uploadId: string | undefined, options?: UseDownloadUrlOptions): UseDownloadUrlResult;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A value that shows itself for a moment and then goes away.
|
|
3
|
+
*
|
|
4
|
+
* The "Copied" on a copy button, the "Saved" under an editor, the "Sent" on a
|
|
5
|
+
* submit. There were three hand-rolled copies of this — two in the studio
|
|
6
|
+
* front-end and one inside this package's own URL chips — each with the same
|
|
7
|
+
* 1500ms and the same comment explaining the same two things the naive version
|
|
8
|
+
* gets wrong. It is one primitive now, and it is PUBLIC because a custom chrome
|
|
9
|
+
* is exactly the caller that needs it: the moment a client renders its own copy
|
|
10
|
+
* button or its own save note, it is writing the timer again.
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* What {@link useFlash} hands back.
|
|
14
|
+
*
|
|
15
|
+
* @typeParam T - What is being flashed. A `string` for a label; a record for a
|
|
16
|
+
* flash that has to say WHICH thing it belongs to, as `useCopy` does.
|
|
17
|
+
*
|
|
18
|
+
* @public
|
|
19
|
+
*/
|
|
20
|
+
export type UseFlashResult<T> = {
|
|
21
|
+
/** What is being shown right now, or `null` between flashes. */
|
|
22
|
+
readonly value: T | null;
|
|
23
|
+
/** Show `value` for the hook's duration, replacing any flash already up. */
|
|
24
|
+
readonly flash: (value: T) => void;
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* A transient value: set it, and it clears itself after `ms`.
|
|
28
|
+
*
|
|
29
|
+
* @remarks
|
|
30
|
+
* The two things this holds that a `useState` plus a `setTimeout` at the call
|
|
31
|
+
* site reliably gets wrong:
|
|
32
|
+
*
|
|
33
|
+
* - **At most ONE live timer.** A second flash while the first is still up
|
|
34
|
+
* re-arms rather than stacking, so the new value gets its full window
|
|
35
|
+
* instead of being cleared early by the previous click's timeout.
|
|
36
|
+
* - **Nothing fires after unmount.** The timer is cleared on teardown, so a
|
|
37
|
+
* chip clicked and then scrolled out of the tree does not `setState` on a
|
|
38
|
+
* component React has already thrown away.
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* ```tsx
|
|
42
|
+
* import { useFlash } from "@alexkroman1/aai-ui";
|
|
43
|
+
*
|
|
44
|
+
* function SaveNote({ onSave }: { onSave: () => Promise<void> }) {
|
|
45
|
+
* const { value: note, flash } = useFlash<string>();
|
|
46
|
+
* return (
|
|
47
|
+
* <button type="button" onClick={() => void onSave().then(() => flash("Saved"))}>
|
|
48
|
+
* {note ?? "Save"}
|
|
49
|
+
* </button>
|
|
50
|
+
* );
|
|
51
|
+
* }
|
|
52
|
+
* ```
|
|
53
|
+
*
|
|
54
|
+
* @typeParam T - What is being flashed.
|
|
55
|
+
* @param ms - How long the value stays up. Defaults to 1500ms, which is what
|
|
56
|
+
* every caller wanted: long enough to read a word, short enough that the
|
|
57
|
+
* control is back to its real label before the reader looks again.
|
|
58
|
+
*
|
|
59
|
+
* @public
|
|
60
|
+
*/
|
|
61
|
+
export declare function useFlash<T>(ms?: number): UseFlashResult<T>;
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `useInbox()` — the session's client, reachable after the session has ended.
|
|
3
|
+
*
|
|
4
|
+
* A tool that starts a durable run (a reminder, a research job) hands the run
|
|
5
|
+
* `sessionClientId(ctx)`, and the run's `stepNotifyClient` delivers to whoever
|
|
6
|
+
* holds that client's `WS /inbox` socket when it is done — usually long after
|
|
7
|
+
* the voice session closed. This hook holds that socket for the page, under the
|
|
8
|
+
* SESSION's own client id (`mountClient({ client: "auto" })` is the one-word
|
|
9
|
+
* way to have one) and this tab's holder id, and plays each notice aloud.
|
|
10
|
+
*
|
|
11
|
+
* Three decisions a hand-written copy got wrong or had to rediscover:
|
|
12
|
+
*
|
|
13
|
+
* - **Busy by default while the session is running**, so a reminder never talks
|
|
14
|
+
* over a reply: the page answers `busy`, the step's retry brings the notice
|
|
15
|
+
* back, and it plays once the call is over. Override with `busy`.
|
|
16
|
+
* - **Callbacks are read at call time**, not captured at mount: a new `onNotice`
|
|
17
|
+
* every render does not reconnect the socket. Only `events` does, because it
|
|
18
|
+
* is part of the socket's URL.
|
|
19
|
+
* - **Playback is unlocked by the page's first gesture** (`notice-player.ts`):
|
|
20
|
+
* the autoplay policy silences a context created without one, and a notice
|
|
21
|
+
* arrives hours after any click.
|
|
22
|
+
* - **One tab plays: the one last touched.** Every tab is a holder of its own,
|
|
23
|
+
* and the inbox sends a notice to each, so nine open tabs played it nine
|
|
24
|
+
* times a few milliseconds apart — heard as one choppy, phasing voice. A
|
|
25
|
+
* gesture claims the client's player in `localStorage`, which every tab of
|
|
26
|
+
* the origin shares synchronously; the others still ack and still call
|
|
27
|
+
* `onNotice`, they just stay quiet. A claimant that closes gives the claim
|
|
28
|
+
* back, and with no claim every unlocked tab plays, as before.
|
|
29
|
+
*
|
|
30
|
+
* @module
|
|
31
|
+
*/
|
|
32
|
+
import type { InboxEvent, InboxNotice } from "./inbox-protocol.ts";
|
|
33
|
+
/**
|
|
34
|
+
* Options for {@link useInbox}.
|
|
35
|
+
*
|
|
36
|
+
* @public
|
|
37
|
+
*/
|
|
38
|
+
export type UseInboxOptions = {
|
|
39
|
+
/**
|
|
40
|
+
* A notice arrived whole — once per delivery, after playback has started
|
|
41
|
+
* (when `play` is on). Log it, show it, or play it yourself with `play: false`.
|
|
42
|
+
*/
|
|
43
|
+
onNotice?: ((notice: InboxNotice) => void) | undefined;
|
|
44
|
+
/**
|
|
45
|
+
* A frame of the client's live conversation — every session of this client,
|
|
46
|
+
* this page's own included (compare `sessionId` with `useSessionId()` to skip
|
|
47
|
+
* it). Giving one asks for the feed, unless `events` says otherwise.
|
|
48
|
+
*/
|
|
49
|
+
onEvent?: ((event: InboxEvent) => void) | undefined;
|
|
50
|
+
/**
|
|
51
|
+
* Whether to refuse a notice for now (`busy`; it comes back later). Default:
|
|
52
|
+
* the session is running — connecting, listening, thinking or speaking.
|
|
53
|
+
*/
|
|
54
|
+
busy?: (() => boolean) | undefined;
|
|
55
|
+
/**
|
|
56
|
+
* Play each notice's audio (PCM16LE mono, 16 kHz) through built-in playback.
|
|
57
|
+
* Default `true`.
|
|
58
|
+
*/
|
|
59
|
+
play?: boolean | undefined;
|
|
60
|
+
/** Ask for the client's live conversation (`?events=1`). Default: whether `onEvent` is set. */
|
|
61
|
+
events?: boolean | undefined;
|
|
62
|
+
};
|
|
63
|
+
/**
|
|
64
|
+
* What {@link useInbox} returns.
|
|
65
|
+
*
|
|
66
|
+
* @public
|
|
67
|
+
*/
|
|
68
|
+
export type UseInboxResult = {
|
|
69
|
+
/** Whether the inbox socket is open now. */
|
|
70
|
+
readonly connected: boolean;
|
|
71
|
+
/** Silence a notice that is playing — e.g. when the user starts talking. */
|
|
72
|
+
stopPlayback(): void;
|
|
73
|
+
};
|
|
74
|
+
/**
|
|
75
|
+
* Hold this session's `WS /inbox` socket open for the life of the component,
|
|
76
|
+
* and play what arrives — see the module doc.
|
|
77
|
+
*
|
|
78
|
+
* The session needs a client id: `mountClient({ client: "auto" })`, or a
|
|
79
|
+
* `client` of your own. With none, no socket is opened (`connected` stays
|
|
80
|
+
* `false`) until the session's `client` getter answers one.
|
|
81
|
+
*
|
|
82
|
+
* @example
|
|
83
|
+
* ```tsx
|
|
84
|
+
* import { mountClient, useInbox } from "@alexkroman1/aai-ui";
|
|
85
|
+
*
|
|
86
|
+
* function App() {
|
|
87
|
+
* const { connected } = useInbox({
|
|
88
|
+
* onNotice: (notice) => console.log(notice.event, notice.data),
|
|
89
|
+
* });
|
|
90
|
+
* return <p>{connected ? "Reminders on" : "Reminders offline"}</p>;
|
|
91
|
+
* }
|
|
92
|
+
*
|
|
93
|
+
* mountClient({ client: "auto", component: App });
|
|
94
|
+
* ```
|
|
95
|
+
*
|
|
96
|
+
* @param options - What to do with a notice or a live event; see {@link UseInboxOptions}.
|
|
97
|
+
* @returns Whether the socket is open, and a way to stop playback.
|
|
98
|
+
*
|
|
99
|
+
* @public
|
|
100
|
+
*/
|
|
101
|
+
export declare function useInbox(options?: UseInboxOptions): UseInboxResult;
|