@alexkroman1/aai-ui 0.17.0 → 0.18.1
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.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-W6KcdjJY.js → chat-view-BwKpF0MN.js} +27 -17
- package/dist/{client-config-CzvWmw4X.js → client-config-DcMCWmkO.js} +1 -1
- package/dist/components/button.js +1 -1
- package/dist/components/chat-view.js +1 -2
- package/dist/components/controls.js +2 -2
- package/dist/components/form-fields.d.ts +24 -5
- package/dist/components/form-types.d.ts +32 -0
- package/dist/components/form.d.ts +1 -1
- package/dist/components/message-list.js +1 -2
- package/dist/components/session-state-dot.d.ts +30 -18
- package/dist/components/sidebar-layout.js +1 -1
- package/dist/components/start-screen.js +2 -2
- package/dist/components/tool-call-block.js +1 -2
- package/dist/components/tool-config-context.d.ts +21 -4
- package/dist/components/workflow-fields.d.ts +36 -12
- package/dist/components/workflow-pending-note.d.ts +30 -12
- package/dist/context-BF0KsI9X.js +473 -0
- package/dist/context.js +1 -381
- package/dist/default-client/assets/audio-CtBvSS3L.js +1 -0
- package/dist/default-client/assets/{capture-processor-Cx1AdMHL.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-CCwTknSu.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 +30 -28
- package/dist/define-client.js +84 -43
- 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/index.d.ts +11 -8
- package/dist/index.js +849 -263
- package/dist/internal.d.ts +1 -1
- package/dist/internal.js +6 -6
- package/dist/{message-list-BsaBkBgo.js → message-list-BBHLK05X.js} +3 -3
- package/dist/route-fetch.d.ts +7 -1
- package/dist/session/index.js +1 -1
- package/dist/{session-3lgPYNNO.js → session-Rud8mmU-.js} +2 -2
- 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 +47 -2
- package/dist/{url-chips-oyy_Eiqh.js → url-chips-C_SHU6fq.js} +2 -2
- package/dist/use-dictation.d.ts +145 -0
- package/dist/use-route.d.ts +49 -7
- package/dist/use-run-key.d.ts +6 -1
- package/dist/{use-user-transcript-uyHhzy4d.js → use-user-transcript-lOu_uMnf.js} +1 -1
- package/dist/use-workflow-form.d.ts +74 -172
- package/dist/{use-workflow-run-DdhWmbx-.js → use-workflow-run-BTEemod0.js} +2 -2
- package/dist/use-workflow-runs.d.ts +8 -2
- package/dist/use-workflow-stream.d.ts +9 -5
- package/dist/workflow-def-types.d.ts +18 -2
- package/dist/workflow-field-kind.d.ts +59 -7
- package/dist/workflow-submission.d.ts +265 -0
- package/package.json +2 -2
- package/dist/default-client/assets/audio-Ct7JpSNT.js +0 -1
- package/dist/default-client/assets/index-BlUYCTB5.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
|
/**
|
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";
|
|
@@ -186,6 +186,23 @@ function StateDot({ color, pulseMs, className, title }) {
|
|
|
186
186
|
title
|
|
187
187
|
});
|
|
188
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;
|
|
189
206
|
/**
|
|
190
207
|
* The live session state as a coloured dot and a word, on its own narrow
|
|
191
208
|
* subscription.
|
|
@@ -204,25 +221,14 @@ function StateDot({ color, pulseMs, className, title }) {
|
|
|
204
221
|
*
|
|
205
222
|
* Must be rendered inside the providers `mountClient()` installs.
|
|
206
223
|
*
|
|
207
|
-
* @example A dispatch board's readout:
|
|
224
|
+
* @example A dispatch board's readout: coarse colours, three of its own words
|
|
208
225
|
* ```tsx
|
|
209
|
-
* import type { AgentState } from "@alexkroman1/aai-ui";
|
|
210
226
|
* import { SessionStateDot } from "@alexkroman1/aai-ui";
|
|
211
227
|
*
|
|
212
|
-
* const STATE_COLORS = {
|
|
213
|
-
* disconnected: "#6b7280",
|
|
214
|
-
* connecting: "#6b7280",
|
|
215
|
-
* ready: "#22c55e",
|
|
216
|
-
* listening: "#22c55e",
|
|
217
|
-
* thinking: "#eab308",
|
|
218
|
-
* speaking: "#3b82f6",
|
|
219
|
-
* error: "#6b7280",
|
|
220
|
-
* } satisfies Record<AgentState, string>;
|
|
221
|
-
*
|
|
222
228
|
* function StatusReadout() {
|
|
223
229
|
* return (
|
|
224
230
|
* <SessionStateDot
|
|
225
|
-
* colors={
|
|
231
|
+
* colors={{ idle: "#6b7280", live: "#22c55e", busy: "#eab308", speaking: "#3b82f6" }}
|
|
226
232
|
* labels={{ listening: "LISTENING", thinking: "PROCESSING", speaking: "TRANSMITTING" }}
|
|
227
233
|
* labelClassName="text-[11px] uppercase"
|
|
228
234
|
* />
|
|
@@ -230,6 +236,9 @@ function StateDot({ color, pulseMs, className, title }) {
|
|
|
230
236
|
* }
|
|
231
237
|
* ```
|
|
232
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
|
+
*
|
|
233
242
|
* @param props - See {@link SessionStateDotProps}.
|
|
234
243
|
*
|
|
235
244
|
* @public
|
|
@@ -238,7 +247,8 @@ function SessionStateDot({ colors, labels, pulse = true, className, dotClassName
|
|
|
238
247
|
const state = useSessionStatus();
|
|
239
248
|
const theme = useTheme();
|
|
240
249
|
const pulseMs = pulse ? PULSE_MS[state] ?? null : null;
|
|
241
|
-
const
|
|
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));
|
|
242
252
|
return /* @__PURE__ */ jsxs("span", {
|
|
243
253
|
className: clsx("inline-flex items-center gap-2", className),
|
|
244
254
|
"data-state": state,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useTheme } from "../context.js";
|
|
1
|
+
import { u as useTheme } from "../context-BF0KsI9X.js";
|
|
2
2
|
import { a as inkTint, i as focusRingStyle, n as FOCUS_RING, o as primaryTint } from "../_colors-CpZO-88A.js";
|
|
3
3
|
import clsx from "clsx";
|
|
4
4
|
import { jsx } from "react/jsx-runtime";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { c as useSessionSelector, o as useSessionCore } from "../context-BF0KsI9X.js";
|
|
2
2
|
import { Button } from "./button.js";
|
|
3
|
-
import { n as SessionUrlChips } from "../url-chips-
|
|
3
|
+
import { n as SessionUrlChips } from "../url-chips-C_SHU6fq.js";
|
|
4
4
|
import { memo } from "react";
|
|
5
5
|
import clsx from "clsx";
|
|
6
6
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { InputHTMLAttributes, JSX, ReactNode, SelectHTMLAttributes, TextareaHTMLAttributes } from "react";
|
|
2
|
-
import type { FieldShell, FileReadMode } from "./form-types.ts";
|
|
2
|
+
import type { FieldShell, FileReadMode, FileValueShape, TextSplitMode } from "./form-types.ts";
|
|
3
3
|
/**
|
|
4
4
|
* Label + control + hint, in the layout every field here uses.
|
|
5
5
|
*
|
|
@@ -67,11 +67,21 @@ export declare function NumberField(props: FieldShell & Omit<InputHTMLAttributes
|
|
|
67
67
|
* Accepts every `<textarea>` attribute except `name` and `className`, plus the
|
|
68
68
|
* shared {@link FieldShell} props. `rows` defaults to 4.
|
|
69
69
|
*
|
|
70
|
-
*
|
|
70
|
+
* **`split="lines"` contributes a `string[]`** — one entry per non-blank line,
|
|
71
|
+
* trimmed — which is the control for a schema's `z.array(z.string())`: "one
|
|
72
|
+
* point per line". `document-redline-workflow` reshaped the textarea's string
|
|
73
|
+
* by hand on submit; the field does it now, so `<WorkflowFields>` can render
|
|
74
|
+
* it for a `string[]` property and a hand-written one can match. See
|
|
75
|
+
* {@link TextSplitMode}.
|
|
76
|
+
*
|
|
77
|
+
* @param props - {@link FieldShell} props, `split`, and `<textarea>` attributes.
|
|
71
78
|
*
|
|
72
79
|
* @public
|
|
73
80
|
*/
|
|
74
|
-
export declare function TextAreaField({ name, label, hint, className, rows, ...rest }: FieldShell &
|
|
81
|
+
export declare function TextAreaField({ name, label, hint, className, rows, split, ...rest }: FieldShell & {
|
|
82
|
+
/** How the text is contributed — see above. Default `"none"`, the string. */
|
|
83
|
+
split?: TextSplitMode | undefined;
|
|
84
|
+
} & Omit<TextareaHTMLAttributes<HTMLTextAreaElement>, "name" | "className">): JSX.Element;
|
|
75
85
|
/**
|
|
76
86
|
* A dropdown.
|
|
77
87
|
*
|
|
@@ -126,17 +136,26 @@ export declare function CheckboxField({ name, label, hint, className, ...rest }:
|
|
|
126
136
|
* {@link FileReadMode} for the four values; `upload` is shorthand for
|
|
127
137
|
* `read="upload"`.
|
|
128
138
|
*
|
|
139
|
+
* **`as="namedText"` contributes `{ name, text }`** ({@link NamedTextValue})
|
|
140
|
+
* instead of the metadata-bearing {@link FileValue}, and implies
|
|
141
|
+
* `read="text"`: the shape a schema declares for a draft to redline or a
|
|
142
|
+
* config to apply, and the one `<WorkflowFields>` renders for an object
|
|
143
|
+
* property of exactly that shape. `document-redline-workflow` reshaped the
|
|
144
|
+
* `FileValue` by hand on submit; the field does it now.
|
|
145
|
+
*
|
|
129
146
|
* Otherwise accepts every `<input>` attribute except `name`, `className` and
|
|
130
147
|
* `type`, plus the shared {@link FieldShell} props — so `accept` and
|
|
131
148
|
* `multiple` are passed straight through.
|
|
132
149
|
*
|
|
133
|
-
* @param props - {@link FieldShell} props, `read`/`upload`, and `<input>`
|
|
150
|
+
* @param props - {@link FieldShell} props, `read`/`upload`/`as`, and `<input>`
|
|
134
151
|
* attributes.
|
|
135
152
|
*
|
|
136
153
|
* @public
|
|
137
154
|
*/
|
|
138
|
-
export declare function FileField({ name, label, hint, className, read, upload, ...rest }: FieldShell & {
|
|
155
|
+
export declare function FileField({ name, label, hint, className, read, upload, as, ...rest }: FieldShell & {
|
|
139
156
|
read?: FileReadMode;
|
|
140
157
|
/** Shorthand for `read="upload"` — see above. */
|
|
141
158
|
upload?: boolean;
|
|
159
|
+
/** The shape of a READ file's value — see above and {@link FileValueShape}. */
|
|
160
|
+
as?: FileValueShape | undefined;
|
|
142
161
|
} & Omit<InputHTMLAttributes<HTMLInputElement>, "name" | "className" | "type">): JSX.Element;
|
|
@@ -48,6 +48,21 @@ export type FileValue = {
|
|
|
48
48
|
* @public
|
|
49
49
|
*/
|
|
50
50
|
export declare function isFileValue(value: unknown): value is FileValue;
|
|
51
|
+
/**
|
|
52
|
+
* What a `<FileField read="text" as="namedText">` contributes: the file's name
|
|
53
|
+
* and its text, and nothing else — the shape a workflow schema declares as
|
|
54
|
+
* `z.object({ name: z.string(), text: z.string() })` for a draft, a config, a
|
|
55
|
+
* CSV of ids that belongs in the run's input rather than in an upload. A
|
|
56
|
+
* {@link FileValue} carries the browser's metadata too, which a schema then
|
|
57
|
+
* has to strip or refuse; this is the value a schema can take as it is.
|
|
58
|
+
*
|
|
59
|
+
* @sealed
|
|
60
|
+
* @public
|
|
61
|
+
*/
|
|
62
|
+
export type NamedTextValue = {
|
|
63
|
+
name: string;
|
|
64
|
+
text: string;
|
|
65
|
+
};
|
|
51
66
|
/**
|
|
52
67
|
* How much of a chosen file a {@link FileField} reads.
|
|
53
68
|
*
|
|
@@ -60,6 +75,23 @@ export declare function isFileValue(value: unknown): value is FileValue;
|
|
|
60
75
|
* @public
|
|
61
76
|
*/
|
|
62
77
|
export type FileReadMode = "none" | "text" | "dataUrl" | "upload";
|
|
78
|
+
/**
|
|
79
|
+
* The SHAPE a {@link FileField} contributes a read file as. `"file"` (the
|
|
80
|
+
* default) is a {@link FileValue}; `"namedText"` is a {@link NamedTextValue},
|
|
81
|
+
* which implies `read="text"`. Only `"namedText"` is a reshape today; the
|
|
82
|
+
* option exists so a workflow's object property has a declared control.
|
|
83
|
+
*
|
|
84
|
+
* @public
|
|
85
|
+
*/
|
|
86
|
+
export type FileValueShape = "file" | "namedText";
|
|
87
|
+
/**
|
|
88
|
+
* How a {@link TextAreaField} contributes its text. `"none"` (the default) is
|
|
89
|
+
* the string; `"lines"` is a `string[]` — one entry per non-blank line,
|
|
90
|
+
* trimmed — the control for a schema's `z.array(z.string())`.
|
|
91
|
+
*
|
|
92
|
+
* @public
|
|
93
|
+
*/
|
|
94
|
+
export type TextSplitMode = "none" | "lines";
|
|
63
95
|
/**
|
|
64
96
|
* The props every field in `form.tsx` shares.
|
|
65
97
|
*
|
|
@@ -2,7 +2,7 @@ import type { ButtonHTMLAttributes, FormHTMLAttributes, JSX, ReactNode } from "r
|
|
|
2
2
|
import { type ButtonSize, type ButtonVariant } from "./button.tsx";
|
|
3
3
|
import type { FormValues } from "./form-types.ts";
|
|
4
4
|
export { CheckboxField, Field, FileField, NumberField, SelectField, TextAreaField, TextField, } from "./form-fields.tsx";
|
|
5
|
-
export type { FieldShell, FileReadMode, FileValue, FormValues } from "./form-types.ts";
|
|
5
|
+
export type { FieldShell, FileReadMode, FileValue, FileValueShape, FormValues, NamedTextValue, TextSplitMode, } from "./form-types.ts";
|
|
6
6
|
export { isFileValue } from "./form-types.ts";
|
|
7
7
|
/**
|
|
8
8
|
* Props of {@link Form}.
|