@alexkroman1/aai-ui 0.16.0 → 0.18.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 +6 -7
- package/dist/_submission-timing.d.ts +40 -0
- package/dist/_submission.d.ts +56 -0
- package/dist/_theme.d.ts +17 -0
- package/dist/_theme.test-d.d.ts +14 -0
- package/dist/{_utils-Mi-SCFA1.js → _utils-BlZBJPdj.js} +4 -5
- package/dist/_utils.d.ts +4 -5
- package/dist/agent-state-labels.d.ts +8 -3
- package/dist/agent-state.d.ts +4 -2
- package/dist/append-dictation.d.ts +23 -0
- package/dist/audio/dictation.d.ts +36 -0
- package/dist/audio/index.d.ts +4 -2
- package/dist/audio/index.js +44 -1
- package/dist/{chat-view-DIwPoAEG.js → chat-view-BwKpF0MN.js} +65 -45
- package/dist/{client-config-C7fQrioM.js → client-config-DcMCWmkO.js} +8 -13
- package/dist/client-config.d.ts +29 -10
- package/dist/components/_form-readiness.d.ts +2 -1
- package/dist/components/audio-result.d.ts +2 -0
- package/dist/components/bullet-list.d.ts +1 -0
- package/dist/components/button.d.ts +2 -2
- package/dist/components/button.js +1 -1
- package/dist/components/chat-view.js +1 -2
- package/dist/components/console-shell.d.ts +8 -5
- package/dist/components/controls.d.ts +1 -0
- package/dist/components/controls.js +2 -2
- package/dist/components/conversation-view.d.ts +1 -0
- package/dist/components/facts.d.ts +1 -0
- package/dist/components/form-fields.d.ts +31 -12
- package/dist/components/form-types.d.ts +48 -2
- package/dist/components/form.d.ts +10 -5
- package/dist/components/markdown.d.ts +1 -0
- package/dist/components/message-list.d.ts +1 -0
- package/dist/components/message-list.js +1 -2
- package/dist/components/session-controls.d.ts +7 -2
- package/dist/components/session-error-banner.d.ts +1 -0
- package/dist/components/session-state-dot.d.ts +45 -20
- package/dist/components/sidebar-layout.d.ts +2 -2
- package/dist/components/sidebar-layout.js +1 -1
- package/dist/components/start-screen.js +2 -2
- package/dist/components/tool-call-block.d.ts +3 -3
- package/dist/components/tool-call-block.js +1 -2
- package/dist/components/tool-call-row.d.ts +1 -0
- package/dist/components/tool-config-context.d.ts +23 -5
- package/dist/components/url-chips.d.ts +4 -3
- package/dist/components/workflow-fields.d.ts +36 -11
- package/dist/components/workflow-pending-note.d.ts +31 -12
- package/dist/components/workflow-run-error.d.ts +1 -0
- package/dist/components/workflow-run-panel.d.ts +1 -0
- package/dist/context-BF0KsI9X.js +473 -0
- package/dist/context.d.ts +8 -7
- package/dist/context.js +1 -381
- package/dist/default-client/assets/audio-CtBvSS3L.js +1 -0
- package/dist/default-client/assets/{capture-processor-CXCWxCO8.js → capture-processor-BZXisGjZ.js} +1 -1
- package/dist/default-client/assets/index-BkjCv4MX.css +2 -0
- package/dist/default-client/assets/index-DUQoaxdq.js +127 -0
- package/dist/default-client/assets/{playback-processor-DXHfyQD9.js → playback-processor-BHk-iXEU.js} +1 -1
- package/dist/default-client/index.html +2 -2
- package/dist/default-client.d.ts +1 -3
- package/dist/define-client.d.ts +32 -28
- package/dist/define-client.js +85 -44
- package/dist/dictation-transcriber.d.ts +49 -0
- package/dist/{eyebrow-UfmSz9yy.js → eyebrow-Bp4y1rZ9.js} +1 -1
- package/dist/{hooks-DN4gHYsj.js → hooks-1CoQY_Fc.js} +2 -2
- package/dist/hooks.js +1 -2
- package/dist/inbox-protocol.d.ts +14 -4
- package/dist/inbox.d.ts +1 -0
- package/dist/index.d.ts +12 -9
- package/dist/index.js +905 -277
- package/dist/internal.d.ts +1 -1
- package/dist/internal.js +6 -6
- package/dist/linked-client.d.ts +2 -0
- package/dist/{message-list-DHZcLhRA.js → message-list-BBHLK05X.js} +24 -13
- package/dist/page.d.ts +2 -0
- package/dist/phone.d.ts +1 -0
- package/dist/route-fetch.d.ts +8 -1
- package/dist/session/audio-setup.d.ts +1 -1
- package/dist/session/audio-state.d.ts +6 -6
- package/dist/session/index.js +1 -1
- package/dist/session/messages.d.ts +1 -9
- package/dist/session/preconnect.d.ts +1 -1
- package/dist/session/types.d.ts +4 -0
- package/dist/{session-CYdjRvbl.js → session-Rud8mmU-.js} +26 -15
- package/dist/stored-value.d.ts +2 -0
- package/dist/{tool-call-block-uXtMOUWN.js → tool-call-block-C8s6AhjS.js} +4 -4
- package/dist/tool-config-context-Bd-7xrfP.js +28 -0
- package/dist/types.d.ts +95 -6
- package/dist/{url-chips-oyy_Eiqh.js → url-chips-C_SHU6fq.js} +2 -2
- package/dist/use-client-runs.d.ts +2 -0
- package/dist/use-conversation-log.d.ts +2 -0
- package/dist/use-conversation.d.ts +24 -9
- package/dist/use-copy.d.ts +1 -0
- package/dist/use-dictation.d.ts +145 -0
- package/dist/use-download-url.d.ts +6 -1
- package/dist/use-flash.d.ts +1 -0
- package/dist/use-inbox.d.ts +2 -0
- package/dist/use-push-to-talk.d.ts +6 -1
- package/dist/use-route-mutation.d.ts +3 -0
- package/dist/use-route.d.ts +51 -7
- package/dist/use-run-key.d.ts +6 -1
- package/dist/use-session-controls.d.ts +1 -0
- package/dist/use-tap-to-talk.d.ts +5 -2
- package/dist/{use-user-transcript-uyHhzy4d.js → use-user-transcript-lOu_uMnf.js} +1 -1
- package/dist/use-user-transcript.d.ts +5 -1
- package/dist/use-workflow-form.d.ts +76 -167
- package/dist/use-workflow-progress.d.ts +5 -0
- package/dist/{use-workflow-run-DdhWmbx-.js → use-workflow-run-BTEemod0.js} +2 -2
- package/dist/use-workflow-run.d.ts +5 -0
- package/dist/use-workflow-runs.d.ts +18 -4
- package/dist/use-workflow-stream.d.ts +33 -7
- package/dist/use-workflows.d.ts +10 -2
- package/dist/workflow-client.d.ts +5 -0
- package/dist/workflow-def-types.d.ts +18 -2
- package/dist/workflow-field-kind.d.ts +66 -10
- package/dist/workflow-submission.d.ts +265 -0
- package/dist/worklets/capture-processor.d.ts +3 -3
- package/dist/worklets/capture-processor.js +2 -2
- package/dist/worklets/playback-processor.d.ts +3 -3
- package/dist/worklets/playback-processor.js +2 -2
- package/package.json +2 -2
- package/dist/default-client/assets/audio-DzwGxdv4.js +0 -1
- package/dist/default-client/assets/index-BOTLwsNO.js +0 -126
- package/dist/default-client/assets/index-_-x5JLLa.css +0 -2
- package/dist/tool-config-context-Es4YUzV2.js +0 -19
package/README.md
CHANGED
|
@@ -28,21 +28,20 @@ still React, still the same theme tokens.
|
|
|
28
28
|
|
|
29
29
|
### Agent `agent.ts`
|
|
30
30
|
|
|
31
|
-
The session's state lives in a slot, and `
|
|
32
|
-
pushed after every tool call, so the client never rebuilds it
|
|
31
|
+
The session's state lives in a slot, and a slot declared `sync: true` is what
|
|
32
|
+
the browser sees — pushed after every tool call, so the client never rebuilds it
|
|
33
|
+
from events:
|
|
33
34
|
|
|
34
35
|
```ts
|
|
35
36
|
import { agent, sessionSlot } from "@alexkroman1/aai";
|
|
36
37
|
|
|
37
38
|
const desk = sessionSlot("desk", () => ({ symbols: [] as string[] }), {
|
|
38
39
|
view: (state) => ({ symbols: state.symbols }),
|
|
40
|
+
sync: true,
|
|
39
41
|
});
|
|
40
42
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
systemPrompt: "You look up quotes and keep the caller's watchlist.",
|
|
44
|
-
syncState: desk.projected,
|
|
45
|
-
});
|
|
43
|
+
// The `sync: true` slot is collected; the prompt is the system-prompt.md beside this file.
|
|
44
|
+
export default agent({ name: "Market Desk" });
|
|
46
45
|
```
|
|
47
46
|
|
|
48
47
|
### Tool `tools/get_quote.ts`
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The press-to-settle stopwatch both submit hooks report as
|
|
3
|
+
* `WorkflowSubmission.timing`: the mark `submit()` was called at, and the mark
|
|
4
|
+
* the submission settled at, read off the one flag that spans both waits.
|
|
5
|
+
*
|
|
6
|
+
* It is the half of `transcription-workflow`'s `useTotalLatency` that is a
|
|
7
|
+
* fact about the SUBMISSION rather than about the page: that template kept
|
|
8
|
+
* `startedAt`/`frozenMs` in its own state, armed by its own submit handler
|
|
9
|
+
* and cleared by its own Clear button — shadow state for two instants only
|
|
10
|
+
* the hook can place exactly. The rendering half (the interval that re-draws
|
|
11
|
+
* a running clock, the "before the run / inside it" split) stays on the page,
|
|
12
|
+
* because it is the page that decides how to print a duration.
|
|
13
|
+
*
|
|
14
|
+
* Two details it would be easy to get wrong, both kept from the template:
|
|
15
|
+
*
|
|
16
|
+
* - **The settle is measured in the effect that sees `pending` fall**, so the
|
|
17
|
+
* mark is the moment the run settled rather than whenever the page next
|
|
18
|
+
* drew. And only after `pending` has been seen TRUE since the press — a
|
|
19
|
+
* press that lands one render before the submission reports itself in
|
|
20
|
+
* flight would otherwise freeze the clock at zero.
|
|
21
|
+
* - **`performance.now()`, not `Date.now()`.** Monotonic; see
|
|
22
|
+
* `WorkflowSubmissionTiming`.
|
|
23
|
+
*/
|
|
24
|
+
import type { WorkflowSubmissionTiming } from "./workflow-submission.ts";
|
|
25
|
+
/** What the stopwatch hands a submit hook. */
|
|
26
|
+
export type SubmissionTiming = {
|
|
27
|
+
/** The marks, for `WorkflowSubmission.timing`. A new object only when a mark changes. */
|
|
28
|
+
timing: WorkflowSubmissionTiming;
|
|
29
|
+
/** Call from `submit()`: sets `submittedAt`, clears `settledAt`. */
|
|
30
|
+
markSubmitted: () => void;
|
|
31
|
+
/** Call from `reset()`: drops both marks. */
|
|
32
|
+
clear: () => void;
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* The stopwatch over `pending` — see the module doc.
|
|
36
|
+
*
|
|
37
|
+
* @param pending - The submission's own `pending`: true from the press until
|
|
38
|
+
* the run settles, which is the whole of what the clock covers.
|
|
39
|
+
*/
|
|
40
|
+
export declare function useSubmissionTiming(pending: boolean): SubmissionTiming;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Everything a submit hook is, bar its BODY.
|
|
3
|
+
*
|
|
4
|
+
* `useWorkflowSubmit` (`use-workflow-form.ts`) and `useWorkflowStream`
|
|
5
|
+
* (`use-workflow-stream.ts`) differ in one thing — when the run is created
|
|
6
|
+
* relative to the bytes — and that one thing is the submission body each hands
|
|
7
|
+
* to the form's statechart. Everything after the body was written out twice,
|
|
8
|
+
* identically: the watch over the run id (`useWorkflowRun`), the two controls
|
|
9
|
+
* bound to it (`_run-controls.ts`), `pending` as "the machine is busy or the
|
|
10
|
+
* watch is still polling", the press-to-settle stopwatch
|
|
11
|
+
* (`_submission-timing.ts`), a `reset` that clears both, and the fourteen-field
|
|
12
|
+
* `WorkflowSubmission` assembled from all of it. Two copies of a return object
|
|
13
|
+
* that have to agree field for field are two copies that can stop agreeing —
|
|
14
|
+
* the stream hook's type is an ALIAS of the form hook's for the same reason —
|
|
15
|
+
* so the tail is one function, and each hook keeps only what makes it itself.
|
|
16
|
+
*/
|
|
17
|
+
import type { RecoverRunOptions } from "./_recover-run.ts";
|
|
18
|
+
import type { SubmissionWork } from "./_workflow-form-state.ts";
|
|
19
|
+
import type { UploadGate } from "./upload/index.ts";
|
|
20
|
+
import type { WorkflowApi } from "./workflow-client.ts";
|
|
21
|
+
import type { WorkflowSubmission, WorkflowSubmissionScope } from "./workflow-submission.ts";
|
|
22
|
+
/**
|
|
23
|
+
* What one `submit(input)` does, from the hook that owns it: the gate the
|
|
24
|
+
* bytes pause through, and the work that runs as THE submission (see
|
|
25
|
+
* `SubmissionActions.submit`). Called per press with the client read at that
|
|
26
|
+
* moment, after the stopwatch has marked it.
|
|
27
|
+
*
|
|
28
|
+
* It is a `useCallback` dependency of `submit`, so a hook hands over a stable
|
|
29
|
+
* one — rebuilt only when what it closes over changes — or `submit`, and with
|
|
30
|
+
* it the `onSubmit` a page gives `<Form>`, is rebuilt every render.
|
|
31
|
+
*/
|
|
32
|
+
export type BeginSubmission = (input: unknown, client: WorkflowApi) => {
|
|
33
|
+
gate: UploadGate;
|
|
34
|
+
work: SubmissionWork;
|
|
35
|
+
};
|
|
36
|
+
/** What {@link useSubmission} needs from the hook. */
|
|
37
|
+
export type SubmissionOptions = {
|
|
38
|
+
/** The stable getter from `useWorkflowApiRef` — the hook's, since the convention keys on the hook file. */
|
|
39
|
+
getClient: () => WorkflowApi;
|
|
40
|
+
/** The caller's client, for the watch; `undefined` is the page's own agent. */
|
|
41
|
+
api: WorkflowApi | undefined;
|
|
42
|
+
/** How often the fallback poll re-reads a live run; `undefined` is the default. */
|
|
43
|
+
intervalMs: number | undefined;
|
|
44
|
+
/** The mount-time lookup, or nothing for a hook that refuses it (`useWorkflowStream`). */
|
|
45
|
+
recover?: RecoverRunOptions;
|
|
46
|
+
/** Who finds the run again — reported back as `WorkflowSubmission.scope`. */
|
|
47
|
+
scope: WorkflowSubmissionScope;
|
|
48
|
+
/** The hook's own half — see {@link BeginSubmission}. */
|
|
49
|
+
begin: BeginSubmission;
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* The shared tail of a submit hook: the statechart, the watch, the controls,
|
|
53
|
+
* the stopwatch, and the `WorkflowSubmission` built from them. See the module
|
|
54
|
+
* doc.
|
|
55
|
+
*/
|
|
56
|
+
export declare function useSubmission(options: SubmissionOptions): WorkflowSubmission<unknown, never>;
|
package/dist/_theme.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One `ClientTheme` — either form — to the five resolved tokens.
|
|
3
|
+
*
|
|
4
|
+
* Its own module rather than a branch in `context.ts` (which is at the cap)
|
|
5
|
+
* because the derivation is a decision worth a test of its own: the dark
|
|
6
|
+
* neutrals are chosen to keep the components' derived tints (`_colors.ts`'s
|
|
7
|
+
* `inkTint`, which walks from `text` toward the ground) above WCAG AA on a
|
|
8
|
+
* dark surface, exactly as the light defaults do on a light one.
|
|
9
|
+
*/
|
|
10
|
+
import type { ClientTheme, ResolvedClientTheme } from "./types.ts";
|
|
11
|
+
export declare const DEFAULT_THEME: ResolvedClientTheme;
|
|
12
|
+
/**
|
|
13
|
+
* Every token filled in: the preset's neutrals under its accent, or the given
|
|
14
|
+
* tokens over the defaults. `undefined` is the default theme itself. A token
|
|
15
|
+
* given as `""` is ignored with a warning ({@link withoutBlankTokens}).
|
|
16
|
+
*/
|
|
17
|
+
export declare function resolveClientTheme(theme: ClientTheme | undefined): ResolvedClientTheme;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The two forms of `ClientTheme` are EXCLUSIVE at the type level.
|
|
3
|
+
*
|
|
4
|
+
* A mixed `{ primary, mode: "dark", bg }` used to compile: an object literal is
|
|
5
|
+
* checked against the union as a whole, so a key from either member passed
|
|
6
|
+
* excess-property checking, and at runtime `isPreset` won and `bg` was dropped
|
|
7
|
+
* without a word. The `?: undefined` guards on each member are what make the
|
|
8
|
+
* mixed literal a compile error, and this file is what keeps them there.
|
|
9
|
+
*
|
|
10
|
+
* Probed with a conditional rather than a suppression directive: an assignability
|
|
11
|
+
* question answered as `true`/`false` names the shape that moved, where a
|
|
12
|
+
* suppressed error passes on ANY error.
|
|
13
|
+
*/
|
|
14
|
+
export {};
|
|
@@ -21,11 +21,10 @@ function pageBaseUrl() {
|
|
|
21
21
|
/**
|
|
22
22
|
* Set the document title, when there is one to set and a document to set it on.
|
|
23
23
|
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
* the
|
|
28
|
-
* same, so the two mounts must not be able to disagree about it.
|
|
24
|
+
* `mountPage()` does this for its `name`, and may not clobber a title the
|
|
25
|
+
* page's own HTML declared — there is no shell to show the name in otherwise.
|
|
26
|
+
* `mountClient()` sets none: the agent's name reaches its default shell from
|
|
27
|
+
* the agent's own `client-config`, and a custom component owns the page.
|
|
29
28
|
*/
|
|
30
29
|
function setPageTitle(name) {
|
|
31
30
|
if (name && typeof document !== "undefined") document.title = name;
|
package/dist/_utils.d.ts
CHANGED
|
@@ -11,11 +11,10 @@ export declare function pageBaseUrl(): string;
|
|
|
11
11
|
/**
|
|
12
12
|
* Set the document title, when there is one to set and a document to set it on.
|
|
13
13
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* the
|
|
18
|
-
* same, so the two mounts must not be able to disagree about it.
|
|
14
|
+
* `mountPage()` does this for its `name`, and may not clobber a title the
|
|
15
|
+
* page's own HTML declared — there is no shell to show the name in otherwise.
|
|
16
|
+
* `mountClient()` sets none: the agent's name reaches its default shell from
|
|
17
|
+
* the agent's own `client-config`, and a custom component owns the page.
|
|
19
18
|
*/
|
|
20
19
|
export declare function setPageTitle(name: string | undefined): void;
|
|
21
20
|
/**
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
* chrome exists to look like — a CRT's glow, a dispatch board's alert colours —
|
|
24
24
|
* and a component would take the one part that is genuinely shared (the words)
|
|
25
25
|
* hostage to the part that is not. It takes the palette as a PROP, so it does
|
|
26
|
-
* not: what it shares is the
|
|
26
|
+
* not: what it shares is the colour lookup, this record as the label
|
|
27
27
|
* fallback, and the pulse rule, and the three chromes that had each written
|
|
28
28
|
* those fourteen lines around their own `satisfies Record<AgentState, string>`
|
|
29
29
|
* keep the record and lose the lines.
|
|
@@ -32,6 +32,8 @@ import type { AgentState } from "./types.ts";
|
|
|
32
32
|
/**
|
|
33
33
|
* The default label per {@link AgentState}.
|
|
34
34
|
*
|
|
35
|
+
* {@link AgentState} is open, so a lookup by a live state is `string |
|
|
36
|
+
* undefined`: fall back to the raw state for one this version has no word for.
|
|
35
37
|
* Override the ones your page has a better word for and keep the rest:
|
|
36
38
|
*
|
|
37
39
|
* ```ts
|
|
@@ -39,8 +41,11 @@ import type { AgentState } from "./types.ts";
|
|
|
39
41
|
* import { AGENT_STATE_LABELS } from "@alexkroman1/aai-ui";
|
|
40
42
|
*
|
|
41
43
|
* // A dispatch board that shouts, and renames one state.
|
|
42
|
-
* const STATE_LABEL
|
|
43
|
-
*
|
|
44
|
+
* const STATE_LABEL: Readonly<Record<AgentState, string>> = {
|
|
45
|
+
* ...AGENT_STATE_LABELS,
|
|
46
|
+
* thinking: "Processing",
|
|
47
|
+
* };
|
|
48
|
+
* const shout = (s: AgentState) => (STATE_LABEL[s] ?? s).toUpperCase();
|
|
44
49
|
* ```
|
|
45
50
|
*
|
|
46
51
|
* **Sentence case, deliberately.** A template that wants caps applies its own
|
package/dist/agent-state.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Reading what the agent projects —
|
|
2
|
+
* Reading what the agent projects — the slots declared `sync: true`, collected
|
|
3
|
+
* into its `syncState` — in the browser.
|
|
3
4
|
*
|
|
4
5
|
* The frame is KEYED by slot name (`{ [slot]: view }`), so every reader here
|
|
5
6
|
* selects ONE slot by the name the agent keyed it under. That is what lets a
|
|
@@ -52,9 +53,10 @@ export declare function useAgentState(): AgentStateFrame | null;
|
|
|
52
53
|
* import { useAgentState } from "@alexkroman1/aai-ui";
|
|
53
54
|
*
|
|
54
55
|
* // In a real project the slot is declared beside the agent and imported here;
|
|
55
|
-
* // the agent
|
|
56
|
+
* // `sync: true` is what puts it in the agent's `syncState`.
|
|
56
57
|
* const cartSlot = sessionSlot("cart", () => ({ items: [] as string[] }), {
|
|
57
58
|
* view: (cart) => ({ count: cart.items.length }),
|
|
59
|
+
* sync: true,
|
|
58
60
|
* });
|
|
59
61
|
*
|
|
60
62
|
* function CartBadge() {
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `appendDictation(draft, text)` — where dictated words land in a text box
|
|
3
|
+
* that may already hold some.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* `draft` with `text` added after it, joined by exactly one space: a draft
|
|
7
|
+
* ending in whitespace or a newline does not grow a double space, and an empty
|
|
8
|
+
* or blank draft is replaced rather than given a leading one.
|
|
9
|
+
*
|
|
10
|
+
* Never REPLACES what was typed — the words someone said are an addition to
|
|
11
|
+
* the box, which is what makes speaking into a half-written prompt safe.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```ts
|
|
15
|
+
* import { appendDictation } from "@alexkroman1/aai-ui";
|
|
16
|
+
*
|
|
17
|
+
* appendDictation("Build me an agent", "that books tables."); // "Build me an agent that books tables."
|
|
18
|
+
* appendDictation("", "Hello"); // "Hello"
|
|
19
|
+
* ```
|
|
20
|
+
*
|
|
21
|
+
* @public
|
|
22
|
+
*/
|
|
23
|
+
export declare function appendDictation(draft: string, text: string): string;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The dictation recorder: the microphone captured into one clip, handed back
|
|
3
|
+
* as a WAV file for a transcription endpoint to read whole.
|
|
4
|
+
*
|
|
5
|
+
* A clip, not a stream — the dictation API takes the whole utterance in one
|
|
6
|
+
* request — so this is the pre-connect capture (`audio/preconnect.ts`) used
|
|
7
|
+
* for what it already does: buffer the mic at a known rate. Stopping is its
|
|
8
|
+
* `detach` (the worklet's tail flush lands in the buffer, so the last word is
|
|
9
|
+
* kept) and then `close`, which turns the browser's recording indicator off.
|
|
10
|
+
*
|
|
11
|
+
* @module
|
|
12
|
+
*/
|
|
13
|
+
/** One recording in progress. Every method is safe to call after another. */
|
|
14
|
+
export type DictationRecorder = {
|
|
15
|
+
/** The rate the clip is recorded at. */
|
|
16
|
+
readonly sampleRate: number;
|
|
17
|
+
/**
|
|
18
|
+
* Stop, release the microphone, and answer the clip as a mono 16-bit WAV.
|
|
19
|
+
* An empty clip (stopped before the first batch) is a 44-byte header.
|
|
20
|
+
*/
|
|
21
|
+
stop(): Promise<Uint8Array<ArrayBuffer>>;
|
|
22
|
+
/** Stop and release the microphone, keeping nothing. Never rejects. */
|
|
23
|
+
cancel(): Promise<void>;
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Ask for the microphone and start recording.
|
|
27
|
+
*
|
|
28
|
+
* Rejects like the session's own capture does — a denied prompt, a rate the
|
|
29
|
+
* browser refused — with the microphone released.
|
|
30
|
+
*/
|
|
31
|
+
export declare function openDictationRecorder(opts: {
|
|
32
|
+
sampleRate: number;
|
|
33
|
+
/** The capture buffer's bound; the caller stops the clip by then. */
|
|
34
|
+
maxSeconds: number;
|
|
35
|
+
captureWorkletSrc: string;
|
|
36
|
+
}): Promise<DictationRecorder>;
|
package/dist/audio/index.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The browser audio layer's import surface for the rest of the package: the
|
|
3
|
-
* {@link VoiceIO} a live call plays and captures through,
|
|
4
|
-
* capture that buffers the mic before the server names its rates
|
|
3
|
+
* {@link VoiceIO} a live call plays and captures through, the pre-connect
|
|
4
|
+
* capture that buffers the mic before the server names its rates, and the
|
|
5
|
+
* dictation recorder `useDictation` turns into one clip.
|
|
5
6
|
*
|
|
6
7
|
* `capture.ts` (the grant checks and the capture node both halves share) is
|
|
7
8
|
* private to this directory — guard-invariants rule 37 fails an import from
|
|
@@ -14,5 +15,6 @@
|
|
|
14
15
|
*
|
|
15
16
|
* @module
|
|
16
17
|
*/
|
|
18
|
+
export { type DictationRecorder, openDictationRecorder } from "./dictation.ts";
|
|
17
19
|
export { openPreConnectCapture, type PreConnectCapture } from "./preconnect.ts";
|
|
18
20
|
export { createVoiceIO, type VoiceIO, type VoiceIOOptions } from "./voice-io.ts";
|
package/dist/audio/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { CAPTURE_STOP_ACK_TIMEOUT_MS, MIC_BUFFER_SECONDS, PLAYBACK_DONE_MAX_WAIT_MS, PLAYBACK_DONE_POLL_MS, VOICE_CAPTURE_CONSTRAINTS } from "../types.js";
|
|
2
|
+
import { encodeWav } from "@alexkroman1/aai/step";
|
|
2
3
|
//#region src/audio/capture.ts
|
|
3
4
|
/**
|
|
4
5
|
* The capture half's primitives, shared by the audio path (`audio/voice-io.ts`) and
|
|
@@ -250,6 +251,48 @@ async function startCapture(ctx, stream, opts) {
|
|
|
250
251
|
};
|
|
251
252
|
}
|
|
252
253
|
//#endregion
|
|
254
|
+
//#region src/audio/dictation.ts
|
|
255
|
+
/**
|
|
256
|
+
* The dictation recorder: the microphone captured into one clip, handed back
|
|
257
|
+
* as a WAV file for a transcription endpoint to read whole.
|
|
258
|
+
*
|
|
259
|
+
* A clip, not a stream — the dictation API takes the whole utterance in one
|
|
260
|
+
* request — so this is the pre-connect capture (`audio/preconnect.ts`) used
|
|
261
|
+
* for what it already does: buffer the mic at a known rate. Stopping is its
|
|
262
|
+
* `detach` (the worklet's tail flush lands in the buffer, so the last word is
|
|
263
|
+
* kept) and then `close`, which turns the browser's recording indicator off.
|
|
264
|
+
*
|
|
265
|
+
* @module
|
|
266
|
+
*/
|
|
267
|
+
/**
|
|
268
|
+
* Ask for the microphone and start recording.
|
|
269
|
+
*
|
|
270
|
+
* Rejects like the session's own capture does — a denied prompt, a rate the
|
|
271
|
+
* browser refused — with the microphone released.
|
|
272
|
+
*/
|
|
273
|
+
async function openDictationRecorder(opts) {
|
|
274
|
+
const capture = await openPreConnectCapture(opts);
|
|
275
|
+
let done = false;
|
|
276
|
+
return {
|
|
277
|
+
sampleRate: capture.sampleRate,
|
|
278
|
+
async stop() {
|
|
279
|
+
if (done) return encodeWav([], { sampleRate: capture.sampleRate });
|
|
280
|
+
done = true;
|
|
281
|
+
try {
|
|
282
|
+
const { chunks, sampleRate } = await capture.detach();
|
|
283
|
+
return encodeWav(chunks.map((c) => new Uint8Array(c)), { sampleRate });
|
|
284
|
+
} finally {
|
|
285
|
+
await capture.close();
|
|
286
|
+
}
|
|
287
|
+
},
|
|
288
|
+
async cancel() {
|
|
289
|
+
if (done) return;
|
|
290
|
+
done = true;
|
|
291
|
+
await capture.close().catch(() => {});
|
|
292
|
+
}
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
//#endregion
|
|
253
296
|
//#region src/audio/voice-io.ts
|
|
254
297
|
/**
|
|
255
298
|
* Create a {@link VoiceIO} instance that captures microphone audio and
|
|
@@ -428,4 +471,4 @@ async function createVoiceIO(opts) {
|
|
|
428
471
|
return io;
|
|
429
472
|
}
|
|
430
473
|
//#endregion
|
|
431
|
-
export { createVoiceIO, openPreConnectCapture };
|
|
474
|
+
export { createVoiceIO, openDictationRecorder, openPreConnectCapture };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { t as MessageList } from "./message-list-
|
|
1
|
+
import { l as useSessionStatus, s as useSessionError, u as useTheme } from "./context-BF0KsI9X.js";
|
|
2
|
+
import { t as MessageList } from "./message-list-BBHLK05X.js";
|
|
3
3
|
import { a as inkTint, r as THINKING_COLOR, t as ERROR_COLOR } from "./_colors-CpZO-88A.js";
|
|
4
|
-
import { t as Eyebrow } from "./eyebrow-
|
|
4
|
+
import { t as Eyebrow } from "./eyebrow-Bp4y1rZ9.js";
|
|
5
5
|
import { Controls } from "./components/controls.js";
|
|
6
6
|
import clsx from "clsx";
|
|
7
7
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
@@ -9,6 +9,8 @@ import { jsx, jsxs } from "react/jsx-runtime";
|
|
|
9
9
|
/**
|
|
10
10
|
* The default label per {@link AgentState}.
|
|
11
11
|
*
|
|
12
|
+
* {@link AgentState} is open, so a lookup by a live state is `string |
|
|
13
|
+
* undefined`: fall back to the raw state for one this version has no word for.
|
|
12
14
|
* Override the ones your page has a better word for and keep the rest:
|
|
13
15
|
*
|
|
14
16
|
* ```ts
|
|
@@ -16,8 +18,11 @@ import { jsx, jsxs } from "react/jsx-runtime";
|
|
|
16
18
|
* import { AGENT_STATE_LABELS } from "@alexkroman1/aai-ui";
|
|
17
19
|
*
|
|
18
20
|
* // A dispatch board that shouts, and renames one state.
|
|
19
|
-
* const STATE_LABEL
|
|
20
|
-
*
|
|
21
|
+
* const STATE_LABEL: Readonly<Record<AgentState, string>> = {
|
|
22
|
+
* ...AGENT_STATE_LABELS,
|
|
23
|
+
* thinking: "Processing",
|
|
24
|
+
* };
|
|
25
|
+
* const shout = (s: AgentState) => (STATE_LABEL[s] ?? s).toUpperCase();
|
|
21
26
|
* ```
|
|
22
27
|
*
|
|
23
28
|
* **Sentence case, deliberately.** A template that wants caps applies its own
|
|
@@ -126,6 +131,26 @@ function SessionErrorBanner({ className }) {
|
|
|
126
131
|
//#region src/components/session-state-dot.tsx
|
|
127
132
|
/** @jsxImportSource react */
|
|
128
133
|
/**
|
|
134
|
+
* Indicator dot colour per state, from the theme — `ConsoleShell`'s dot, and
|
|
135
|
+
* {@link SessionStateDot}'s for every state its `colors` does not name.
|
|
136
|
+
*
|
|
137
|
+
* `idle` is the theme's own faint ink step rather than a fixed warm grey, so
|
|
138
|
+
* the dot stays visible on a dark ground; the thinking amber and error red
|
|
139
|
+
* are semantic and stay fixed. A state this version does not know is `idle`.
|
|
140
|
+
*
|
|
141
|
+
* @internal
|
|
142
|
+
*/
|
|
143
|
+
function stateColor(state, primary, idle) {
|
|
144
|
+
switch (state) {
|
|
145
|
+
case "listening":
|
|
146
|
+
case "speaking":
|
|
147
|
+
case "ready": return primary;
|
|
148
|
+
case "thinking": return THINKING_COLOR;
|
|
149
|
+
case "error": return ERROR_COLOR;
|
|
150
|
+
default: return idle;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
129
154
|
* One `aai-pulse` cycle per pulsing state, in milliseconds. The two states
|
|
130
155
|
* that pulse are the two in which the agent is DOING something the reader
|
|
131
156
|
* cannot otherwise see — the microphone is open, or a reply is being worked
|
|
@@ -161,6 +186,23 @@ function StateDot({ color, pulseMs, className, title }) {
|
|
|
161
186
|
title
|
|
162
187
|
});
|
|
163
188
|
}
|
|
189
|
+
/** Which coarse group a state belongs to — `error` deliberately none. */
|
|
190
|
+
function groupOf(state) {
|
|
191
|
+
switch (state) {
|
|
192
|
+
case "ready":
|
|
193
|
+
case "listening":
|
|
194
|
+
case "speaking": return "live";
|
|
195
|
+
case "thinking": return "busy";
|
|
196
|
+
case "error": return;
|
|
197
|
+
default: return "idle";
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* The idle dot's ink step: the theme's text at half strength over its ground.
|
|
202
|
+
* Lighter than `INK_FAINT_PCT` (the text steps) on purpose — a dot is a
|
|
203
|
+
* 8px disc, not a word, and at 65% it read as "on" beside a live one.
|
|
204
|
+
*/
|
|
205
|
+
const INK_IDLE_PCT = 50;
|
|
164
206
|
/**
|
|
165
207
|
* The live session state as a coloured dot and a word, on its own narrow
|
|
166
208
|
* subscription.
|
|
@@ -173,31 +215,20 @@ function StateDot({ color, pulseMs, className, title }) {
|
|
|
173
215
|
* against a dot component on the grounds that it would take the shared part
|
|
174
216
|
* (the words) hostage to the part that is not (the palette); this takes the
|
|
175
217
|
* palette as a prop precisely so it does not. What is shared is the structure
|
|
176
|
-
* — the
|
|
218
|
+
* — the colour lookup and its theme fallback, the label fallback, the pulse rule, the
|
|
177
219
|
* `useSessionStatus()` subscription that keeps the rest of the header from
|
|
178
220
|
* re-rendering at STT-partial rate.
|
|
179
221
|
*
|
|
180
222
|
* Must be rendered inside the providers `mountClient()` installs.
|
|
181
223
|
*
|
|
182
|
-
* @example A dispatch board's readout:
|
|
224
|
+
* @example A dispatch board's readout: coarse colours, three of its own words
|
|
183
225
|
* ```tsx
|
|
184
|
-
* import type { AgentState } from "@alexkroman1/aai-ui";
|
|
185
226
|
* import { SessionStateDot } from "@alexkroman1/aai-ui";
|
|
186
227
|
*
|
|
187
|
-
* const STATE_COLORS = {
|
|
188
|
-
* disconnected: "#6b7280",
|
|
189
|
-
* connecting: "#6b7280",
|
|
190
|
-
* ready: "#22c55e",
|
|
191
|
-
* listening: "#22c55e",
|
|
192
|
-
* thinking: "#eab308",
|
|
193
|
-
* speaking: "#3b82f6",
|
|
194
|
-
* error: "#6b7280",
|
|
195
|
-
* } satisfies Record<AgentState, string>;
|
|
196
|
-
*
|
|
197
228
|
* function StatusReadout() {
|
|
198
229
|
* return (
|
|
199
230
|
* <SessionStateDot
|
|
200
|
-
* colors={
|
|
231
|
+
* colors={{ idle: "#6b7280", live: "#22c55e", busy: "#eab308", speaking: "#3b82f6" }}
|
|
201
232
|
* labels={{ listening: "LISTENING", thinking: "PROCESSING", speaking: "TRANSMITTING" }}
|
|
202
233
|
* labelClassName="text-[11px] uppercase"
|
|
203
234
|
* />
|
|
@@ -205,24 +236,30 @@ function StateDot({ color, pulseMs, className, title }) {
|
|
|
205
236
|
* }
|
|
206
237
|
* ```
|
|
207
238
|
*
|
|
239
|
+
* With no `colors` at all the dot follows the mounted theme — a chrome that
|
|
240
|
+
* sets `mountClient({ theme })` gets a matching dot for free.
|
|
241
|
+
*
|
|
208
242
|
* @param props - See {@link SessionStateDotProps}.
|
|
209
243
|
*
|
|
210
244
|
* @public
|
|
211
245
|
*/
|
|
212
246
|
function SessionStateDot({ colors, labels, pulse = true, className, dotClassName, labelClassName }) {
|
|
213
247
|
const state = useSessionStatus();
|
|
248
|
+
const theme = useTheme();
|
|
214
249
|
const pulseMs = pulse ? PULSE_MS[state] ?? null : null;
|
|
250
|
+
const group = groupOf(state);
|
|
251
|
+
const color = colors?.[state] ?? (group === void 0 ? void 0 : colors?.[group]) ?? stateColor(state, theme.primary, inkTint(theme.text, theme.bg, INK_IDLE_PCT));
|
|
215
252
|
return /* @__PURE__ */ jsxs("span", {
|
|
216
253
|
className: clsx("inline-flex items-center gap-2", className),
|
|
217
254
|
"data-state": state,
|
|
218
255
|
children: [/* @__PURE__ */ jsx(StateDot, {
|
|
219
|
-
color
|
|
256
|
+
color,
|
|
220
257
|
pulseMs,
|
|
221
258
|
className: dotClassName ?? "w-2 h-2",
|
|
222
259
|
title: state
|
|
223
260
|
}), /* @__PURE__ */ jsx("span", {
|
|
224
261
|
className: labelClassName,
|
|
225
|
-
children: labels?.[state] ?? AGENT_STATE_LABELS[state]
|
|
262
|
+
children: labels?.[state] ?? AGENT_STATE_LABELS[state] ?? state
|
|
226
263
|
})]
|
|
227
264
|
});
|
|
228
265
|
}
|
|
@@ -230,25 +267,6 @@ function SessionStateDot({ colors, labels, pulse = true, className, dotClassName
|
|
|
230
267
|
//#region src/components/console-shell.tsx
|
|
231
268
|
/** @jsxImportSource react */
|
|
232
269
|
/**
|
|
233
|
-
* Indicator dot color per state.
|
|
234
|
-
*
|
|
235
|
-
* `idle` is the theme's own faint ink step rather than a fixed warm grey, so
|
|
236
|
-
* the dot stays visible on a dark ground; the thinking amber and error red
|
|
237
|
-
* are semantic and stay fixed.
|
|
238
|
-
*
|
|
239
|
-
* @internal
|
|
240
|
-
*/
|
|
241
|
-
function stateColor(state, primary, idle) {
|
|
242
|
-
switch (state) {
|
|
243
|
-
case "listening":
|
|
244
|
-
case "speaking":
|
|
245
|
-
case "ready": return primary;
|
|
246
|
-
case "thinking": return THINKING_COLOR;
|
|
247
|
-
case "error": return ERROR_COLOR;
|
|
248
|
-
default: return idle;
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
/**
|
|
252
270
|
* The design-system "console" chrome: a 760px column on the themed page with a
|
|
253
271
|
* header (icon + live-status eyebrow), an announced error banner, the main
|
|
254
272
|
* content on a raised card, and a footer row beneath it.
|
|
@@ -294,11 +312,13 @@ function stateColor(state, primary, idle) {
|
|
|
294
312
|
* footer={<Controls />}
|
|
295
313
|
* >
|
|
296
314
|
* <ul>
|
|
297
|
-
* {items.map((item) =>
|
|
298
|
-
*
|
|
299
|
-
* {item.
|
|
300
|
-
*
|
|
301
|
-
*
|
|
315
|
+
* {items.map((item) =>
|
|
316
|
+
* item.kind === "message" ? (
|
|
317
|
+
* <li key={item.message.id}>{item.message.content}</li>
|
|
318
|
+
* ) : item.kind === "tool" ? (
|
|
319
|
+
* <li key={item.toolCall.callId}>{item.toolCall.name}</li>
|
|
320
|
+
* ) : null,
|
|
321
|
+
* )}
|
|
302
322
|
* </ul>
|
|
303
323
|
* </ConsoleShell>
|
|
304
324
|
* );
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as pageBaseUrl } from "./_utils-
|
|
1
|
+
import { n as pageBaseUrl } from "./_utils-BlZBJPdj.js";
|
|
2
2
|
import { CLIENT_CONFIG_PATH, ClientConfigResponseSchema, SESSION_TICKET_HEADER } from "@alexkroman1/aai/protocol";
|
|
3
3
|
//#region src/client-config.ts
|
|
4
4
|
/**
|
|
@@ -69,11 +69,12 @@ const CLIENT_CONFIG_ATTEMPT_TIMEOUT_MS = 1e4;
|
|
|
69
69
|
*
|
|
70
70
|
* @internal
|
|
71
71
|
*/
|
|
72
|
-
async function loadClientConfig(platformUrl, fetchFn, presentedTicket) {
|
|
72
|
+
async function loadClientConfig(platformUrl, fetchFn, presentedTicket, signal) {
|
|
73
73
|
const doFetch = fetchFn ?? ((input, init) => globalThis.fetch(input, init));
|
|
74
|
+
const deadline = AbortSignal.timeout(CLIENT_CONFIG_ATTEMPT_TIMEOUT_MS);
|
|
74
75
|
try {
|
|
75
76
|
const resp = await doFetch(buildAgentUrl(platformUrl, CLIENT_CONFIG_PATH).href, {
|
|
76
|
-
signal: AbortSignal.
|
|
77
|
+
signal: signal === void 0 ? deadline : AbortSignal.any([deadline, signal]),
|
|
77
78
|
...presentedTicket === void 0 ? {} : { headers: { [SESSION_TICKET_HEADER]: presentedTicket } }
|
|
78
79
|
});
|
|
79
80
|
if (!resp.ok) return null;
|
|
@@ -102,14 +103,7 @@ async function loadClientConfig(platformUrl, fetchFn, presentedTicket) {
|
|
|
102
103
|
* treat every field as optional, because an agent that declared none is a
|
|
103
104
|
* normal agent.
|
|
104
105
|
*
|
|
105
|
-
* @param
|
|
106
|
-
* origin and path** (`pageBaseUrl()`), which is the agent that served the page
|
|
107
|
-
* and the only case a browser has — the argument was required while the default
|
|
108
|
-
* sat one module away in this same package, so every caller wrote
|
|
109
|
-
* `location.origin + location.pathname` for itself. Pass one for a page reading
|
|
110
|
-
* a DIFFERENT agent. The endpoint is resolved relative to it.
|
|
111
|
-
* @param fetchFn - Fetch implementation, for tests and for a caller that
|
|
112
|
-
* supplies its own credentials. Defaults to the global `fetch`.
|
|
106
|
+
* @param options - Where to look and how; see {@link FetchClientConfigOptions}.
|
|
113
107
|
* @returns The agent's config, or `{}` when the lookup produced no answer.
|
|
114
108
|
*
|
|
115
109
|
* @example
|
|
@@ -132,8 +126,9 @@ async function loadClientConfig(platformUrl, fetchFn, presentedTicket) {
|
|
|
132
126
|
*
|
|
133
127
|
* @public
|
|
134
128
|
*/
|
|
135
|
-
async function fetchClientConfig(
|
|
136
|
-
|
|
129
|
+
async function fetchClientConfig(options = {}) {
|
|
130
|
+
const { platformUrl = pageBaseUrl(), fetch: fetchFn, signal } = options;
|
|
131
|
+
return await loadClientConfig(platformUrl, fetchFn, void 0, signal) ?? AGENT_DEFAULT;
|
|
137
132
|
}
|
|
138
133
|
//#endregion
|
|
139
134
|
export { fetchClientConfig as n, loadClientConfig as r, buildAgentUrl as t };
|