@alexkroman1/aai-ui 13.3.0 → 14.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +159 -69
- package/dist/{_colors-CZ6OlPbL.js → _colors-CpZO-88A.js} +24 -1
- package/dist/_recover-run.d.ts +2 -2
- package/dist/_submission-state.d.ts +92 -0
- package/dist/_upload-files.d.ts +2 -2
- package/dist/_upload-report.d.ts +26 -0
- package/dist/{_utils-CyzjK0gW.js → _utils-DnQDM9Uy.js} +3 -3
- package/dist/_utils.d.ts +3 -3
- package/dist/_web-storage.d.ts +43 -0
- package/dist/_workflow-files.d.ts +1 -1
- package/dist/agent-state-labels.d.ts +60 -0
- package/dist/audio.js +20 -14
- package/dist/{chat-view-Bv5VFJIE.js → chat-view-C_3T7Ln8.js} +96 -29
- package/dist/{client-config-DD820zHn.js → client-config-DJQHnYjm.js} +4 -4
- package/dist/client-config.d.ts +4 -4
- package/dist/client-dir.d.ts +1 -1
- package/dist/client-dir.js +1 -1
- package/dist/components/_colors.d.ts +23 -0
- package/dist/components/_form-readiness.d.ts +1 -1
- package/dist/components/bullet-list.d.ts +74 -0
- package/dist/components/button.js +3 -3
- package/dist/components/chat-view.js +1 -1
- package/dist/components/console-shell.d.ts +16 -20
- package/dist/components/controls.js +3 -3
- package/dist/components/facts.d.ts +81 -0
- package/dist/components/form-fields.d.ts +6 -6
- package/dist/components/form-types.d.ts +1 -1
- package/dist/components/form.d.ts +1 -1
- package/dist/components/message-list.js +1 -1
- package/dist/components/session-error-banner.d.ts +69 -0
- package/dist/components/start-screen.js +1 -1
- package/dist/components/tool-call-block.js +1 -1
- package/dist/components/tool-config-context.d.ts +1 -1
- package/dist/components/workflow-progress.d.ts +11 -4
- package/dist/context.d.ts +142 -19
- package/dist/context.js +155 -17
- package/dist/default-client/assets/{audio-BuDICbPf.js → audio-9zQsNc1w.js} +1 -1
- package/dist/default-client/assets/index-BTv30Z4F.css +2 -0
- package/dist/default-client/assets/index-RAZ-29Sz.js +284 -0
- package/dist/default-client/index.html +2 -2
- package/dist/define-client.d.ts +19 -19
- package/dist/define-client.js +19 -19
- package/dist/hooks.d.ts +44 -8
- package/dist/hooks.js +19 -13
- package/dist/index.d.ts +11 -7
- package/dist/index.js +418 -185
- package/dist/internal.d.ts +2 -2
- package/dist/internal.js +5 -5
- package/dist/{message-list-C0pL7x41.js → message-list-CdOnSh5m.js} +19 -12
- package/dist/page.d.ts +11 -11
- package/dist/session-core-audio-setup.d.ts +1 -1
- package/dist/session-core-dial.d.ts +0 -2
- package/dist/{session-core-C9elBIdu.js → session-core-gwePM95B.js} +125 -52
- package/dist/session-core-messages.d.ts +2 -2
- package/dist/session-core-types.d.ts +58 -1
- package/dist/session-core.d.ts +6 -6
- package/dist/session-core.js +2 -2
- package/dist/session-resume-store.d.ts +3 -3
- package/dist/{tool-call-block-Bunc6rCw.js → tool-call-block-C2t_5fpp.js} +27 -9
- package/dist/{tool-config-context-Bh8p3DtG.js → tool-config-context-Es4YUzV2.js} +1 -1
- package/dist/types.d.ts +19 -4
- package/dist/types.js +2 -2
- package/dist/{url-chips-C2u7QPv8.js → url-chips-BxhzZgk2.js} +4 -4
- package/dist/use-conversation.d.ts +1 -1
- package/dist/{use-user-transcript-DFTSEuZN.js → use-user-transcript-uyHhzy4d.js} +3 -2
- package/dist/use-workflow-form.d.ts +34 -2
- package/dist/{use-workflow-run-CXGEcM0l.js → use-workflow-run-CP2ekKPV.js} +3 -6
- package/dist/use-workflow-stream.d.ts +1 -1
- package/dist/workflow-client.d.ts +1 -1
- package/package.json +2 -2
- package/styles.css +78 -0
- package/dist/default-client/assets/index-B1_ROnTJ.js +0 -284
- package/dist/default-client/assets/index-S5fkKi6B.css +0 -2
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { InputHTMLAttributes, ReactNode, SelectHTMLAttributes, TextareaHTMLAttributes } from "react";
|
|
2
|
-
import type { FieldShell,
|
|
2
|
+
import type { FieldShell, FileReadMode } from "./form-types.ts";
|
|
3
3
|
/**
|
|
4
4
|
* Label + control + hint, in the layout every field here uses.
|
|
5
5
|
*
|
|
@@ -47,7 +47,7 @@ export declare function Field({ label, hint, htmlFor, className, children, }: {
|
|
|
47
47
|
*
|
|
48
48
|
* @public
|
|
49
49
|
*/
|
|
50
|
-
export declare function TextField(
|
|
50
|
+
export declare function TextField(props: FieldShell & Omit<InputHTMLAttributes<HTMLInputElement>, "name" | "className">): import("react").JSX.Element;
|
|
51
51
|
/**
|
|
52
52
|
* A number input. Contributes a NUMBER to {@link FormValues}, or nothing when
|
|
53
53
|
* left empty.
|
|
@@ -60,7 +60,7 @@ export declare function TextField({ name, label, hint, className, ...rest }: Fie
|
|
|
60
60
|
*
|
|
61
61
|
* @public
|
|
62
62
|
*/
|
|
63
|
-
export declare function NumberField(
|
|
63
|
+
export declare function NumberField(props: FieldShell & Omit<InputHTMLAttributes<HTMLInputElement>, "name" | "className" | "type">): import("react").JSX.Element;
|
|
64
64
|
/**
|
|
65
65
|
* A multi-line text input.
|
|
66
66
|
*
|
|
@@ -116,14 +116,14 @@ export declare function CheckboxField({ name, label, hint, className, ...rest }:
|
|
|
116
116
|
* travel in it. With `upload` the field contributes the `File` itself,
|
|
117
117
|
* `useWorkflowSubmit` stores it through `POST /workflows/uploads` before
|
|
118
118
|
* starting the run, and the input carries the upload id — which a step reads
|
|
119
|
-
* windows of with `
|
|
119
|
+
* windows of with `stepReadUpload`. Declaring the property in the workflow's
|
|
120
120
|
* `uploads` list makes `<WorkflowFields>` render exactly this, so a declared
|
|
121
121
|
* form needs no file markup at all.
|
|
122
122
|
*
|
|
123
123
|
* **Without it the field describes the file and does not read it.** `read`
|
|
124
124
|
* exists for the cases where the bytes really are small and really are the
|
|
125
125
|
* input — a CSV of ids, a config — and the size is the author's to check. See
|
|
126
|
-
* {@link
|
|
126
|
+
* {@link FileReadMode} for the four values; `upload` is shorthand for
|
|
127
127
|
* `read="upload"`.
|
|
128
128
|
*
|
|
129
129
|
* Otherwise accepts every `<input>` attribute except `name`, `className` and
|
|
@@ -136,7 +136,7 @@ export declare function CheckboxField({ name, label, hint, className, ...rest }:
|
|
|
136
136
|
* @public
|
|
137
137
|
*/
|
|
138
138
|
export declare function FileField({ name, label, hint, className, read, upload, ...rest }: FieldShell & {
|
|
139
|
-
read?:
|
|
139
|
+
read?: FileReadMode;
|
|
140
140
|
/** Shorthand for `read="upload"` — see above. */
|
|
141
141
|
upload?: boolean;
|
|
142
142
|
} & Omit<InputHTMLAttributes<HTMLInputElement>, "name" | "className" | "type">): import("react").JSX.Element;
|
|
@@ -2,7 +2,7 @@ import type { ButtonHTMLAttributes, FormHTMLAttributes, ReactNode } from "react"
|
|
|
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,
|
|
5
|
+
export type { FieldShell, FileReadMode, FileValue, FormValues } from "./form-types.ts";
|
|
6
6
|
/** Props of {@link Form}. */
|
|
7
7
|
export type FormProps = {
|
|
8
8
|
/**
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
/**
|
|
3
|
+
* Props of {@link SessionErrorBanner}.
|
|
4
|
+
*
|
|
5
|
+
* Every field is optional, so `<SessionErrorBanner />` is the whole call. It is
|
|
6
|
+
* a NAMED type all the same, for the reason `ControlsProps` and
|
|
7
|
+
* `ConsoleShellProps` are: an inline object literal in the signature leaves a
|
|
8
|
+
* `createElement(SessionErrorBanner, { className })` caller unable to infer the
|
|
9
|
+
* props at all, and gives the reference page nothing to link to.
|
|
10
|
+
*
|
|
11
|
+
* @public
|
|
12
|
+
*/
|
|
13
|
+
export type SessionErrorBannerProps = {
|
|
14
|
+
/** Additional CSS class names for the banner, appended to its own. */
|
|
15
|
+
className?: string | undefined;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* The announced banner for a failed session: the error's message and code, or
|
|
19
|
+
* nothing at all when the session is fine.
|
|
20
|
+
*
|
|
21
|
+
* **This used to be four lines inside `ConsoleShell`, and that is why it is its
|
|
22
|
+
* own component.** The banner was the reason `ConsoleShell` was published —
|
|
23
|
+
* `role="alert"` is the one part of that component a reviewer cannot see is
|
|
24
|
+
* missing, since per the `fatalError` latch in `session-core.ts` the banner is
|
|
25
|
+
* the ONLY remaining signal a session died (the state eyebrow beside it goes
|
|
26
|
+
* back to reading like a live session), and a screen reader is never told an
|
|
27
|
+
* unannounced one appeared. But `ConsoleShell` is a whole FRAME: a centred
|
|
28
|
+
* `max-w-190` column with its own header and footer. Every full-bleed chrome —
|
|
29
|
+
* a two-pane board, a CRT — therefore could not adopt it, rebuilt the banner
|
|
30
|
+
* instead, and the three that did had ALREADY drifted: one rendered
|
|
31
|
+
* `ERROR: {message}` and dropped the code entirely, one `ERROR: {message}
|
|
32
|
+
* ({code})`, one `{message} ({code})`. Splitting the banner out is what lets a
|
|
33
|
+
* chrome take the announced-error decision without taking the layout, and
|
|
34
|
+
* `ConsoleShell` composes this rather than keeping a second copy, so the two
|
|
35
|
+
* cannot drift again.
|
|
36
|
+
*
|
|
37
|
+
* **It reads the session itself.** There is no `error` prop: a banner that
|
|
38
|
+
* takes its text from the caller is a banner a caller can forget to wire, which
|
|
39
|
+
* is exactly the failure above with an extra step. It subscribes narrowly via
|
|
40
|
+
* {@link useSessionError}, so a page that renders it does not re-render with
|
|
41
|
+
* the transcript.
|
|
42
|
+
*
|
|
43
|
+
* **The code is shown, always.** `SessionError.code` is the eight-member wire
|
|
44
|
+
* union — it is what a user pastes into a bug report and the only part of the
|
|
45
|
+
* error that is stable across wordings — and the chrome that dropped it left
|
|
46
|
+
* its readers with a sentence and no way to say which failure it was.
|
|
47
|
+
*
|
|
48
|
+
* Must be rendered inside the providers `client()` installs.
|
|
49
|
+
*
|
|
50
|
+
* @example A full-bleed chrome that wants the banner and not the frame
|
|
51
|
+
* ```tsx
|
|
52
|
+
* import { SessionErrorBanner } from "@alexkroman1/aai-ui";
|
|
53
|
+
*
|
|
54
|
+
* function Board() {
|
|
55
|
+
* return (
|
|
56
|
+
* <div className="grid grid-cols-[1fr_320px] h-screen">
|
|
57
|
+
* <main>…</main>
|
|
58
|
+
* <aside>…</aside>
|
|
59
|
+
* <SessionErrorBanner className="col-span-2" />
|
|
60
|
+
* </div>
|
|
61
|
+
* );
|
|
62
|
+
* }
|
|
63
|
+
* ```
|
|
64
|
+
*
|
|
65
|
+
* @param props - See {@link SessionErrorBannerProps}.
|
|
66
|
+
*
|
|
67
|
+
* @public
|
|
68
|
+
*/
|
|
69
|
+
export declare function SessionErrorBanner({ className }: SessionErrorBannerProps): ReactNode;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useSessionCore, useSessionSelector, useTheme } from "../context.js";
|
|
2
|
-
import {
|
|
2
|
+
import { a as inkTint } from "../_colors-CpZO-88A.js";
|
|
3
3
|
import { Button } from "./button.js";
|
|
4
4
|
import { t as AaiLogo } from "../aai-logo-CFlomZlS.js";
|
|
5
5
|
import { t as Eyebrow } from "../eyebrow-UfmSz9yy.js";
|
|
@@ -8,7 +8,7 @@ export type ToolDisplayConfig = Record<string, {
|
|
|
8
8
|
label?: string;
|
|
9
9
|
}>;
|
|
10
10
|
/**
|
|
11
|
-
* Context for tool display configuration. Installed by `
|
|
11
|
+
* Context for tool display configuration. Installed by `mountClient()` from
|
|
12
12
|
* `ClientConfig.tools`; the built-in components read it via `useToolConfig`.
|
|
13
13
|
*
|
|
14
14
|
* @internal
|
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
2
|
import type { WorkflowApi } from "../workflow-client.ts";
|
|
3
3
|
/**
|
|
4
4
|
* What a run has said so far, rendered.
|
|
5
5
|
*
|
|
6
6
|
* The complement of a status line, and the reason both exist: a run is
|
|
7
7
|
* `running` for its whole life, so a one-round job and a ten-round one look
|
|
8
|
-
* identical while they happen. These lines come from the run itself (`
|
|
8
|
+
* identical while they happen. These lines come from the run itself (`stepReport()`
|
|
9
9
|
* in a `"use step"` body), which is the only channel a workflow has before it
|
|
10
10
|
* produces an output.
|
|
11
11
|
*
|
|
12
|
-
*
|
|
12
|
+
* Four rules are baked in, and they are why this is a component rather than
|
|
13
13
|
* three lines each page writes for itself — the two templates that had written
|
|
14
|
-
* it had written
|
|
14
|
+
* it had written three of them, comments included:
|
|
15
15
|
*
|
|
16
16
|
* - **It renders nothing until there is something to render.** `supported` is
|
|
17
17
|
* what keeps this from being an empty box forever on an agent deployed before
|
|
@@ -25,6 +25,13 @@ import type { WorkflowApi } from "../workflow-client.ts";
|
|
|
25
25
|
* or opening a finished run tomorrow — shows how it got there rather than an
|
|
26
26
|
* empty box. That is `useWorkflowProgress`'s doing; this is what makes it
|
|
27
27
|
* visible.
|
|
28
|
+
* - **They are ANNOUNCED**, for the reason the first paragraph gives: this is
|
|
29
|
+
* the only channel a run has before it produces an output, and a `<pre>` that
|
|
30
|
+
* grows is a silent one. A screen-reader user pressing "Digest" got nothing
|
|
31
|
+
* between the click and a terminal state minutes later — no "fetching", no
|
|
32
|
+
* "summarising", no evidence the button did anything. See `role="log"` below.
|
|
33
|
+
* The six pages that render this pass only `className`, so no template could
|
|
34
|
+
* have fixed it locally; that is what makes it this component's job.
|
|
28
35
|
*
|
|
29
36
|
* @example
|
|
30
37
|
* ```tsx
|
package/dist/context.d.ts
CHANGED
|
@@ -1,34 +1,45 @@
|
|
|
1
1
|
import { type ReactNode } from "react";
|
|
2
|
-
import type {
|
|
3
|
-
import type { ClientTheme } from "./types.ts";
|
|
2
|
+
import type { BrowserSession, SessionSnapshot } from "./session-core-types.ts";
|
|
3
|
+
import type { AgentState, ClientTheme, SessionError } from "./types.ts";
|
|
4
4
|
/**
|
|
5
|
-
* Provides the {@link
|
|
5
|
+
* Provides the {@link BrowserSession} the session hooks read. `mountClient()`
|
|
6
6
|
* installs it automatically; a custom tree only needs it when bypassing
|
|
7
|
-
* `
|
|
7
|
+
* `mountClient()` and mounting React itself.
|
|
8
8
|
*
|
|
9
9
|
* @internal
|
|
10
10
|
*/
|
|
11
|
-
export declare function SessionProvider({ value, children }: {
|
|
12
|
-
value:
|
|
11
|
+
export declare function SessionProvider({ value, children, }: {
|
|
12
|
+
value: BrowserSession;
|
|
13
13
|
children?: ReactNode;
|
|
14
|
-
}): import("react").FunctionComponentElement<import("react").ProviderProps<
|
|
14
|
+
}): import("react").FunctionComponentElement<import("react").ProviderProps<BrowserSession | null>>;
|
|
15
|
+
/**
|
|
16
|
+
* The session's control methods, and nothing else — what a `client.tsx` may
|
|
17
|
+
* legitimately CALL on a session, as against what it may read.
|
|
18
|
+
*
|
|
19
|
+
* Declared once and merged into {@link Session} rather than written out at both
|
|
20
|
+
* places: the two lists have to be the same list, and a member added to one and
|
|
21
|
+
* not the other is a hook that cannot do what `useSession()` can.
|
|
22
|
+
*
|
|
23
|
+
* Method signatures come from {@link BrowserSession} — one source of truth.
|
|
24
|
+
*
|
|
25
|
+
* @public
|
|
26
|
+
*/
|
|
27
|
+
export type SessionActions = Pick<BrowserSession, "start" | "cancel" | "resetState" | "reset" | "restart" | "disconnect" | "toggle" | "end">;
|
|
15
28
|
/**
|
|
16
29
|
* What {@link useSession} returns: the live {@link SessionSnapshot} fields
|
|
17
30
|
* (`state`, `messages`, `toolCalls`, `agentState`, live transcripts, `error`,
|
|
18
31
|
* `apiUrl`, `started`/`running`/`recording`, …) merged with the session's
|
|
19
|
-
* control methods (`start`, `toggle`, `reset`, `
|
|
20
|
-
* `cancel`, `end`).
|
|
32
|
+
* control methods (`start`, `toggle`, `reset`, `restart`, `resetState`,
|
|
33
|
+
* `disconnect`, `cancel`, `end`).
|
|
21
34
|
*
|
|
22
35
|
* Note there is no text-send method — sessions are voice-only; the only
|
|
23
36
|
* client→server inputs are audio and the control methods above.
|
|
24
37
|
*
|
|
25
|
-
* Method signatures come from {@link SessionCore} — one source of truth.
|
|
26
|
-
*
|
|
27
38
|
* @public
|
|
28
39
|
*/
|
|
29
|
-
export type Session = SessionSnapshot &
|
|
40
|
+
export type Session = SessionSnapshot & SessionActions;
|
|
30
41
|
/**
|
|
31
|
-
* Return the raw {@link
|
|
42
|
+
* Return the raw {@link BrowserSession} from context without subscribing to
|
|
32
43
|
* snapshot changes. Useful for accessing stable methods (`start`, `toggle`,
|
|
33
44
|
* `reset`, …) from components that select narrow state via
|
|
34
45
|
* {@link useSessionSelector}.
|
|
@@ -36,15 +47,73 @@ export type Session = SessionSnapshot & Pick<SessionCore, "start" | "cancel" | "
|
|
|
36
47
|
* Not part of the package's public export surface — internal to aai-ui
|
|
37
48
|
* components.
|
|
38
49
|
*/
|
|
39
|
-
export declare function useSessionCore():
|
|
50
|
+
export declare function useSessionCore(): BrowserSession;
|
|
51
|
+
/**
|
|
52
|
+
* The session's control methods — `start`, `cancel`, `resetState`, `reset`,
|
|
53
|
+
* `restart`, `disconnect`, `toggle`, `end` — with **no snapshot
|
|
54
|
+
* subscription**.
|
|
55
|
+
*
|
|
56
|
+
* This is the narrow half of {@link useSession}, and it is the half a custom
|
|
57
|
+
* chrome could not reach. `<Controls>` and `<StartScreen>` in this package pair
|
|
58
|
+
* a one-field `useSessionSelector` with this package's own `useSessionCore`
|
|
59
|
+
* (`context.ts`, unpublished); a `client.tsx`
|
|
60
|
+
* could not, because that hook is not published — so a footer needing `start`
|
|
61
|
+
* and `toggle` held a WHOLE-SNAPSHOT `useSession()`, and `session-core.ts`
|
|
62
|
+
* rebuilds the snapshot object on every change. Measured consequence: four
|
|
63
|
+
* components across three templates re-rendered on every STT partial and every
|
|
64
|
+
* streaming delta, in files whose every other component is narrowly subscribed
|
|
65
|
+
* on purpose. One of them (`infocom-adventure`'s `TitleScreen`) reads nothing
|
|
66
|
+
* from the snapshot at all and subscribes to all of it for `session.start`.
|
|
67
|
+
*
|
|
68
|
+
* **Why publishing this does not reopen what `/internal` closed.**
|
|
69
|
+
* `useSessionCore` hands back the STORE — `subscribe`, `getSnapshot`,
|
|
70
|
+
* `connect`, `Symbol.dispose` — which is the framework's own plumbing, the same
|
|
71
|
+
* category as the providers and `buildAgentUrl` that live on
|
|
72
|
+
* `@alexkroman1/aai-ui/internal`. A client that holds it can subscribe out of
|
|
73
|
+
* band of React, dial a socket the mount did not, and dispose the session under
|
|
74
|
+
* the tree that is rendering it. What comes back from here is the SAME eight
|
|
75
|
+
* methods `useSession()` already publishes on its result, built into a fresh
|
|
76
|
+
* object rather than passed through, so the store is not reachable from it.
|
|
77
|
+
* There is no new capability here — only the existing one without the
|
|
78
|
+
* subscription tax.
|
|
79
|
+
*
|
|
80
|
+
* Identity-stable per core, so it is safe in a dependency array and in a
|
|
81
|
+
* `memo()` child's props: the methods are closures created once by
|
|
82
|
+
* `createBrowserSession`, and the object wrapping them is memoized on the core.
|
|
83
|
+
*
|
|
84
|
+
* Throws outside the provider `mountClient()` installs, like every session hook.
|
|
85
|
+
*
|
|
86
|
+
* @example A footer that acts on the session without re-rendering with it
|
|
87
|
+
* ```tsx
|
|
88
|
+
* import { useSessionActions, useSessionSelector } from "@alexkroman1/aai-ui";
|
|
89
|
+
*
|
|
90
|
+
* function Footer() {
|
|
91
|
+
* // Two narrow subscriptions and no snapshot read: this row re-renders when
|
|
92
|
+
* // `running` flips, and not on every transcript delta.
|
|
93
|
+
* const running = useSessionSelector((s) => s.running);
|
|
94
|
+
* const { toggle, end } = useSessionActions();
|
|
95
|
+
* return (
|
|
96
|
+
* <>
|
|
97
|
+
* <button onClick={toggle}>{running ? "Pause" : "Resume"}</button>
|
|
98
|
+
* <button onClick={end}>Hang up</button>
|
|
99
|
+
* </>
|
|
100
|
+
* );
|
|
101
|
+
* }
|
|
102
|
+
* ```
|
|
103
|
+
*
|
|
104
|
+
* @returns The eight control methods — see {@link SessionActions}.
|
|
105
|
+
*
|
|
106
|
+
* @public
|
|
107
|
+
*/
|
|
108
|
+
export declare function useSessionActions(): SessionActions;
|
|
40
109
|
/**
|
|
41
110
|
* Return the live {@link Session}: the current snapshot fields plus the
|
|
42
111
|
* control methods (`start`, `toggle`, `reset`, `resetState`, `disconnect`,
|
|
43
112
|
* `cancel`, `end`).
|
|
44
113
|
*
|
|
45
|
-
* Throws if used outside the provider `
|
|
114
|
+
* Throws if used outside the provider `mountClient()` installs (the error names
|
|
46
115
|
* `<SessionProvider>` — you only mount that yourself when bypassing
|
|
47
|
-
* `
|
|
116
|
+
* `mountClient()`). Re-renders the component on *every* snapshot change; for a
|
|
48
117
|
* component that reads one field, prefer {@link useSessionSelector} for a
|
|
49
118
|
* targeted subscription.
|
|
50
119
|
*
|
|
@@ -94,9 +163,63 @@ export declare function useSession(): Session;
|
|
|
94
163
|
*/
|
|
95
164
|
export declare function useSessionSelector<T>(selector: (snapshot: SessionSnapshot) => T, isEqual?: (a: T, b: T) => boolean): T;
|
|
96
165
|
/**
|
|
97
|
-
*
|
|
166
|
+
* The agent's live {@link AgentState} — `disconnected`, `connecting`, `ready`,
|
|
167
|
+
* `listening`, `thinking`, `speaking`, `error` — on its own narrow
|
|
168
|
+
* subscription.
|
|
169
|
+
*
|
|
170
|
+
* `useSessionSelector((s) => s.state)` spelled once. It is one of exactly two
|
|
171
|
+
* snapshot fields that more than one custom chrome ever selects (the other is
|
|
172
|
+
* {@link useSessionError}), and it had been written inline at eight sites —
|
|
173
|
+
* including inside this package and, worse, in `ConsoleShell`'s own `@example`,
|
|
174
|
+
* which taught the inline form to everyone who read it.
|
|
175
|
+
*
|
|
176
|
+
* **Named `useSessionStatus`, not `useSessionState`.** `useAgentState` is the
|
|
177
|
+
* SLOT hook — the agent's own synced application state, whatever a
|
|
178
|
+
* `sessionSlot()` projects — and `AgentState` here is the phase of the CALL.
|
|
179
|
+
* Two different concepts one letter apart, so the shorter-sounding name is the
|
|
180
|
+
* one deliberately not taken.
|
|
181
|
+
*
|
|
182
|
+
* Pair it with {@link AGENT_STATE_LABELS} for a rendered word; the raw member
|
|
183
|
+
* is a wire value, not a label.
|
|
184
|
+
*
|
|
185
|
+
* @example
|
|
186
|
+
* ```tsx
|
|
187
|
+
* import { AGENT_STATE_LABELS, useSessionStatus } from "@alexkroman1/aai-ui";
|
|
188
|
+
*
|
|
189
|
+
* function StatusDot() {
|
|
190
|
+
* const status = useSessionStatus();
|
|
191
|
+
* return <span data-state={status}>{AGENT_STATE_LABELS[status]}</span>;
|
|
192
|
+
* }
|
|
193
|
+
* ```
|
|
194
|
+
*
|
|
195
|
+
* @returns The current agent state.
|
|
196
|
+
*
|
|
197
|
+
* @public
|
|
198
|
+
*/
|
|
199
|
+
export declare function useSessionStatus(): AgentState;
|
|
200
|
+
/**
|
|
201
|
+
* The session's current {@link SessionError}, or `null` when there is none, on
|
|
202
|
+
* its own narrow subscription.
|
|
203
|
+
*
|
|
204
|
+
* The other half of {@link useSessionStatus} — the second of the two fields a
|
|
205
|
+
* custom chrome reads over and over, and the one whose absence is invisible:
|
|
206
|
+
* per the `fatalError` latch in `session-core.ts` the error is the ONLY
|
|
207
|
+
* remaining signal that a session died, since the state beside it goes back to
|
|
208
|
+
* reading like a live one.
|
|
209
|
+
*
|
|
210
|
+
* A chrome rendering it owes `role="alert"` — which is what
|
|
211
|
+
* {@link SessionErrorBanner} is for, and why reaching for that beats reaching
|
|
212
|
+
* for this.
|
|
213
|
+
*
|
|
214
|
+
* @returns The current error, or `null`.
|
|
215
|
+
*
|
|
216
|
+
* @public
|
|
217
|
+
*/
|
|
218
|
+
export declare function useSessionError(): SessionError | null;
|
|
219
|
+
/**
|
|
220
|
+
* Provides the theme the components read via `useTheme`. `mountClient()` installs
|
|
98
221
|
* it automatically (from `ClientConfig.theme`); a custom tree only needs it
|
|
99
|
-
* when bypassing `
|
|
222
|
+
* when bypassing `mountClient()` and mounting React itself.
|
|
100
223
|
*
|
|
101
224
|
* @internal
|
|
102
225
|
*/
|
|
@@ -110,7 +233,7 @@ export declare function ThemeProvider({ value, children, }: {
|
|
|
110
233
|
* provider is present, so components can call it unconditionally.
|
|
111
234
|
*
|
|
112
235
|
* This is how a custom component stays on the agent's palette: a
|
|
113
|
-
* `
|
|
236
|
+
* `mountClient({ theme })` override reaches it here, where a hardcoded colour or a
|
|
114
237
|
* Tailwind class cannot see it.
|
|
115
238
|
*
|
|
116
239
|
* @example
|
package/dist/context.js
CHANGED
|
@@ -10,9 +10,9 @@ const DEFAULT_THEME = {
|
|
|
10
10
|
};
|
|
11
11
|
const SessionCtx = createContext(null);
|
|
12
12
|
/**
|
|
13
|
-
* Provides the {@link
|
|
13
|
+
* Provides the {@link BrowserSession} the session hooks read. `mountClient()`
|
|
14
14
|
* installs it automatically; a custom tree only needs it when bypassing
|
|
15
|
-
* `
|
|
15
|
+
* `mountClient()` and mounting React itself.
|
|
16
16
|
*
|
|
17
17
|
* @internal
|
|
18
18
|
*/
|
|
@@ -20,7 +20,7 @@ function SessionProvider({ value, children }) {
|
|
|
20
20
|
return createElement(SessionCtx.Provider, { value }, children);
|
|
21
21
|
}
|
|
22
22
|
/**
|
|
23
|
-
* Return the raw {@link
|
|
23
|
+
* Return the raw {@link BrowserSession} from context without subscribing to
|
|
24
24
|
* snapshot changes. Useful for accessing stable methods (`start`, `toggle`,
|
|
25
25
|
* `reset`, …) from components that select narrow state via
|
|
26
26
|
* {@link useSessionSelector}.
|
|
@@ -34,13 +34,83 @@ function useSessionCore() {
|
|
|
34
34
|
return core;
|
|
35
35
|
}
|
|
36
36
|
/**
|
|
37
|
+
* The session's control methods — `start`, `cancel`, `resetState`, `reset`,
|
|
38
|
+
* `restart`, `disconnect`, `toggle`, `end` — with **no snapshot
|
|
39
|
+
* subscription**.
|
|
40
|
+
*
|
|
41
|
+
* This is the narrow half of {@link useSession}, and it is the half a custom
|
|
42
|
+
* chrome could not reach. `<Controls>` and `<StartScreen>` in this package pair
|
|
43
|
+
* a one-field `useSessionSelector` with this package's own `useSessionCore`
|
|
44
|
+
* (`context.ts`, unpublished); a `client.tsx`
|
|
45
|
+
* could not, because that hook is not published — so a footer needing `start`
|
|
46
|
+
* and `toggle` held a WHOLE-SNAPSHOT `useSession()`, and `session-core.ts`
|
|
47
|
+
* rebuilds the snapshot object on every change. Measured consequence: four
|
|
48
|
+
* components across three templates re-rendered on every STT partial and every
|
|
49
|
+
* streaming delta, in files whose every other component is narrowly subscribed
|
|
50
|
+
* on purpose. One of them (`infocom-adventure`'s `TitleScreen`) reads nothing
|
|
51
|
+
* from the snapshot at all and subscribes to all of it for `session.start`.
|
|
52
|
+
*
|
|
53
|
+
* **Why publishing this does not reopen what `/internal` closed.**
|
|
54
|
+
* `useSessionCore` hands back the STORE — `subscribe`, `getSnapshot`,
|
|
55
|
+
* `connect`, `Symbol.dispose` — which is the framework's own plumbing, the same
|
|
56
|
+
* category as the providers and `buildAgentUrl` that live on
|
|
57
|
+
* `@alexkroman1/aai-ui/internal`. A client that holds it can subscribe out of
|
|
58
|
+
* band of React, dial a socket the mount did not, and dispose the session under
|
|
59
|
+
* the tree that is rendering it. What comes back from here is the SAME eight
|
|
60
|
+
* methods `useSession()` already publishes on its result, built into a fresh
|
|
61
|
+
* object rather than passed through, so the store is not reachable from it.
|
|
62
|
+
* There is no new capability here — only the existing one without the
|
|
63
|
+
* subscription tax.
|
|
64
|
+
*
|
|
65
|
+
* Identity-stable per core, so it is safe in a dependency array and in a
|
|
66
|
+
* `memo()` child's props: the methods are closures created once by
|
|
67
|
+
* `createBrowserSession`, and the object wrapping them is memoized on the core.
|
|
68
|
+
*
|
|
69
|
+
* Throws outside the provider `mountClient()` installs, like every session hook.
|
|
70
|
+
*
|
|
71
|
+
* @example A footer that acts on the session without re-rendering with it
|
|
72
|
+
* ```tsx
|
|
73
|
+
* import { useSessionActions, useSessionSelector } from "@alexkroman1/aai-ui";
|
|
74
|
+
*
|
|
75
|
+
* function Footer() {
|
|
76
|
+
* // Two narrow subscriptions and no snapshot read: this row re-renders when
|
|
77
|
+
* // `running` flips, and not on every transcript delta.
|
|
78
|
+
* const running = useSessionSelector((s) => s.running);
|
|
79
|
+
* const { toggle, end } = useSessionActions();
|
|
80
|
+
* return (
|
|
81
|
+
* <>
|
|
82
|
+
* <button onClick={toggle}>{running ? "Pause" : "Resume"}</button>
|
|
83
|
+
* <button onClick={end}>Hang up</button>
|
|
84
|
+
* </>
|
|
85
|
+
* );
|
|
86
|
+
* }
|
|
87
|
+
* ```
|
|
88
|
+
*
|
|
89
|
+
* @returns The eight control methods — see {@link SessionActions}.
|
|
90
|
+
*
|
|
91
|
+
* @public
|
|
92
|
+
*/
|
|
93
|
+
function useSessionActions() {
|
|
94
|
+
const core = useSessionCore();
|
|
95
|
+
return useMemo(() => ({
|
|
96
|
+
start: core.start,
|
|
97
|
+
cancel: core.cancel,
|
|
98
|
+
resetState: core.resetState,
|
|
99
|
+
reset: core.reset,
|
|
100
|
+
restart: core.restart,
|
|
101
|
+
disconnect: core.disconnect,
|
|
102
|
+
toggle: core.toggle,
|
|
103
|
+
end: core.end
|
|
104
|
+
}), [core]);
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
37
107
|
* Return the live {@link Session}: the current snapshot fields plus the
|
|
38
108
|
* control methods (`start`, `toggle`, `reset`, `resetState`, `disconnect`,
|
|
39
109
|
* `cancel`, `end`).
|
|
40
110
|
*
|
|
41
|
-
* Throws if used outside the provider `
|
|
111
|
+
* Throws if used outside the provider `mountClient()` installs (the error names
|
|
42
112
|
* `<SessionProvider>` — you only mount that yourself when bypassing
|
|
43
|
-
* `
|
|
113
|
+
* `mountClient()`). Re-renders the component on *every* snapshot change; for a
|
|
44
114
|
* component that reads one field, prefer {@link useSessionSelector} for a
|
|
45
115
|
* targeted subscription.
|
|
46
116
|
*
|
|
@@ -60,16 +130,11 @@ function useSessionCore() {
|
|
|
60
130
|
function useSession() {
|
|
61
131
|
const core = useSessionCore();
|
|
62
132
|
const snapshot = useSyncExternalStore(core.subscribe, core.getSnapshot);
|
|
133
|
+
const actions = useSessionActions();
|
|
63
134
|
return useMemo(() => ({
|
|
64
135
|
...snapshot,
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
resetState: core.resetState,
|
|
68
|
-
reset: core.reset,
|
|
69
|
-
disconnect: core.disconnect,
|
|
70
|
-
toggle: core.toggle,
|
|
71
|
-
end: core.end
|
|
72
|
-
}), [snapshot, core]);
|
|
136
|
+
...actions
|
|
137
|
+
}), [snapshot, actions]);
|
|
73
138
|
}
|
|
74
139
|
/**
|
|
75
140
|
* Subscribe to a narrow slice of the session snapshot.
|
|
@@ -105,11 +170,84 @@ function useSessionSelector(selector, isEqual = Object.is) {
|
|
|
105
170
|
const core = useSessionCore();
|
|
106
171
|
return useSyncExternalStoreWithSelector(core.subscribe, core.getSnapshot, core.getSnapshot, selector, isEqual);
|
|
107
172
|
}
|
|
173
|
+
/**
|
|
174
|
+
* The two selectors below are MODULE-SCOPE functions, and that is load-bearing
|
|
175
|
+
* rather than tidy.
|
|
176
|
+
*
|
|
177
|
+
* `useSyncExternalStoreWithSelector` caches its selection keyed on the selector
|
|
178
|
+
* it was handed: a fresh arrow per render invalidates that memo every render,
|
|
179
|
+
* so the selector re-runs and the `isEqual` short-circuit protects only the
|
|
180
|
+
* re-RENDER, never the work. Hoisting them means the two fields more than one
|
|
181
|
+
* chrome ever reads are selected by one stable function for the life of the
|
|
182
|
+
* program — which is also the thing a caller writing the arrow inline cannot
|
|
183
|
+
* do for themselves, and the reason these two are hooks at all rather than a
|
|
184
|
+
* documented one-liner.
|
|
185
|
+
*/
|
|
186
|
+
const selectState = (s) => s.state;
|
|
187
|
+
const selectError = (s) => s.error;
|
|
188
|
+
/**
|
|
189
|
+
* The agent's live {@link AgentState} — `disconnected`, `connecting`, `ready`,
|
|
190
|
+
* `listening`, `thinking`, `speaking`, `error` — on its own narrow
|
|
191
|
+
* subscription.
|
|
192
|
+
*
|
|
193
|
+
* `useSessionSelector((s) => s.state)` spelled once. It is one of exactly two
|
|
194
|
+
* snapshot fields that more than one custom chrome ever selects (the other is
|
|
195
|
+
* {@link useSessionError}), and it had been written inline at eight sites —
|
|
196
|
+
* including inside this package and, worse, in `ConsoleShell`'s own `@example`,
|
|
197
|
+
* which taught the inline form to everyone who read it.
|
|
198
|
+
*
|
|
199
|
+
* **Named `useSessionStatus`, not `useSessionState`.** `useAgentState` is the
|
|
200
|
+
* SLOT hook — the agent's own synced application state, whatever a
|
|
201
|
+
* `sessionSlot()` projects — and `AgentState` here is the phase of the CALL.
|
|
202
|
+
* Two different concepts one letter apart, so the shorter-sounding name is the
|
|
203
|
+
* one deliberately not taken.
|
|
204
|
+
*
|
|
205
|
+
* Pair it with {@link AGENT_STATE_LABELS} for a rendered word; the raw member
|
|
206
|
+
* is a wire value, not a label.
|
|
207
|
+
*
|
|
208
|
+
* @example
|
|
209
|
+
* ```tsx
|
|
210
|
+
* import { AGENT_STATE_LABELS, useSessionStatus } from "@alexkroman1/aai-ui";
|
|
211
|
+
*
|
|
212
|
+
* function StatusDot() {
|
|
213
|
+
* const status = useSessionStatus();
|
|
214
|
+
* return <span data-state={status}>{AGENT_STATE_LABELS[status]}</span>;
|
|
215
|
+
* }
|
|
216
|
+
* ```
|
|
217
|
+
*
|
|
218
|
+
* @returns The current agent state.
|
|
219
|
+
*
|
|
220
|
+
* @public
|
|
221
|
+
*/
|
|
222
|
+
function useSessionStatus() {
|
|
223
|
+
return useSessionSelector(selectState);
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* The session's current {@link SessionError}, or `null` when there is none, on
|
|
227
|
+
* its own narrow subscription.
|
|
228
|
+
*
|
|
229
|
+
* The other half of {@link useSessionStatus} — the second of the two fields a
|
|
230
|
+
* custom chrome reads over and over, and the one whose absence is invisible:
|
|
231
|
+
* per the `fatalError` latch in `session-core.ts` the error is the ONLY
|
|
232
|
+
* remaining signal that a session died, since the state beside it goes back to
|
|
233
|
+
* reading like a live one.
|
|
234
|
+
*
|
|
235
|
+
* A chrome rendering it owes `role="alert"` — which is what
|
|
236
|
+
* {@link SessionErrorBanner} is for, and why reaching for that beats reaching
|
|
237
|
+
* for this.
|
|
238
|
+
*
|
|
239
|
+
* @returns The current error, or `null`.
|
|
240
|
+
*
|
|
241
|
+
* @public
|
|
242
|
+
*/
|
|
243
|
+
function useSessionError() {
|
|
244
|
+
return useSessionSelector(selectError);
|
|
245
|
+
}
|
|
108
246
|
const ThemeCtx = createContext(DEFAULT_THEME);
|
|
109
247
|
/**
|
|
110
|
-
* Provides the theme the components read via `useTheme`. `
|
|
248
|
+
* Provides the theme the components read via `useTheme`. `mountClient()` installs
|
|
111
249
|
* it automatically (from `ClientConfig.theme`); a custom tree only needs it
|
|
112
|
-
* when bypassing `
|
|
250
|
+
* when bypassing `mountClient()` and mounting React itself.
|
|
113
251
|
*
|
|
114
252
|
* @internal
|
|
115
253
|
*/
|
|
@@ -208,7 +346,7 @@ function useThemeStyles(theme) {
|
|
|
208
346
|
* provider is present, so components can call it unconditionally.
|
|
209
347
|
*
|
|
210
348
|
* This is how a custom component stays on the agent's palette: a
|
|
211
|
-
* `
|
|
349
|
+
* `mountClient({ theme })` override reaches it here, where a hardcoded colour or a
|
|
212
350
|
* Tailwind class cannot see it.
|
|
213
351
|
*
|
|
214
352
|
* @example
|
|
@@ -233,4 +371,4 @@ function useTheme() {
|
|
|
233
371
|
return useContext(ThemeCtx);
|
|
234
372
|
}
|
|
235
373
|
//#endregion
|
|
236
|
-
export { SessionProvider, ThemeProvider, useSession, useSessionCore, useSessionSelector, useTheme };
|
|
374
|
+
export { SessionProvider, ThemeProvider, useSession, useSessionActions, useSessionCore, useSessionError, useSessionSelector, useSessionStatus, useTheme };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{a as e,o as t}from"./client-audio-constants-CP13UQZt.js";var n={echoCancellation:!0,noiseSuppression:!1,autoGainControl:!1,voiceIsolation:!1};function r(e,t,n){if(e!==t)throw Error(`Browser refused the ${n} sample rate: asked for ${t} Hz, got ${e} Hz`)}function i(e){let t=Error(`Audio ${e} worklet crashed`);return console.error(`[aai-ui]`,t.message),t}function a(e){e.then(e=>{for(let t of e.getTracks())t.stop()}).catch(()=>{})}function o(e,t,n){let r=new AudioWorkletNode(e,`capture-processor`,{channelCount:1,channelCountMode:`explicit`}),i=null;return r.port.onmessage=e=>{let r=e.data;r.event===`chunk`&&r.buffer?t(r.buffer):r.event===`silent`?n?.():r.event===`stopped`&&(i?.(),i=null)},{node:r,start(){r.port.postMessage({event:`start`})},stop(){let{promise:e,resolve:t}=Promise.withResolvers(),n=setTimeout(t,250);return i=()=>{clearTimeout(n),t()},r.port.postMessage({event:`stop`}),e}}}async function s(s){let{sttSampleRate:c,ttsSampleRate:l,captureWorkletSrc:u,playbackWorkletSrc:d,onMicData:f,onError:p,onPlaybackStats:m,onPlaybackProgress:h,onMicSilent:g}=s,_=new AudioContext({sampleRate:l,latencyHint:`playback`}),v=c===l,y=v?_:new AudioContext({sampleRate:c,latencyHint:`interactive`});async function b(){let e=v?[_]:[_,y];await Promise.all(e.map(e=>e.close().catch(e=>{console.warn(`AudioContext close failed:`,e)})))}let x=navigator.mediaDevices.getUserMedia({audio:{deviceId:{ideal:`default`},...n}}),S;try{[S]=await Promise.all([x,_.resume(),y.resume(),y.audioWorklet.addModule(u),_.audioWorklet.addModule(d)]),r(y.sampleRate,c,`capture`),r(_.sampleRate,l,`playback`)}catch(e){throw a(x),await b(),e}let C=y.createMediaStreamSource(S),w=o(y,f,g);C.connect(w.node),w.node.onprocessorerror=()=>p?.(i(`capture`)),w.start();let T=null,E=
|
|
1
|
+
import{a as e,o as t}from"./client-audio-constants-CP13UQZt.js";var n={echoCancellation:!0,noiseSuppression:!1,autoGainControl:!1,voiceIsolation:!1};function r(e,t,n){if(e!==t)throw Error(`Browser refused the ${n} sample rate: asked for ${t} Hz, got ${e} Hz`)}function i(e){let t=Error(`Audio ${e} worklet crashed`);return console.error(`[aai-ui]`,t.message),t}function a(e){e.then(e=>{for(let t of e.getTracks())t.stop()}).catch(()=>{})}function o(e,t,n){let r=new AudioWorkletNode(e,`capture-processor`,{channelCount:1,channelCountMode:`explicit`}),i=null;return r.port.onmessage=e=>{let r=e.data;r.event===`chunk`&&r.buffer?t(r.buffer):r.event===`silent`?n?.():r.event===`stopped`&&(i?.(),i=null)},{node:r,start(){r.port.postMessage({event:`start`})},stop(){let{promise:e,resolve:t}=Promise.withResolvers(),n=setTimeout(t,250);return i=()=>{clearTimeout(n),t()},r.port.postMessage({event:`stop`}),e}}}async function s(s){let{sttSampleRate:c,ttsSampleRate:l,captureWorkletSrc:u,playbackWorkletSrc:d,onMicData:f,onError:p,onPlaybackStats:m,onPlaybackProgress:h,onMicSilent:g}=s,_=new AudioContext({sampleRate:l,latencyHint:`playback`}),v=c===l,y=v?_:new AudioContext({sampleRate:c,latencyHint:`interactive`});async function b(){let e=v?[_]:[_,y];await Promise.all(e.map(e=>e.close().catch(e=>{console.warn(`AudioContext close failed:`,e)})))}let x=navigator.mediaDevices.getUserMedia({audio:{deviceId:{ideal:`default`},...n}}),S;try{[S]=await Promise.all([x,_.resume(),y.resume(),y.audioWorklet.addModule(u),_.audioWorklet.addModule(d)]),r(y.sampleRate,c,`capture`),r(_.sampleRate,l,`playback`)}catch(e){throw a(x),await b(),e}let C=y.createMediaStreamSource(S),w=o(y,f,g);C.connect(w.node),w.node.onprocessorerror=()=>p?.(i(`capture`)),w.start();let T=null,E=0,D=null,O=new AbortController;function k(){let e=D;D=null,e?.settle()}function A(e){e.stats&&e.stats.concealedSamples>0&&m?.(e.stats),e.reason!==`interrupt`&&(D===null||e.turn===D.turn)&&k()}function j(){if(T)return T;let e=new AudioWorkletNode(_,`playback-processor`);return e.connect(_.destination),e.port.onmessage=e=>{e.data.event===`stop`?A(e.data):e.data.event===`progress`&&h?.(e.data.bufferedMs)},e.onprocessorerror=()=>{let e=i(`playback`);k(),p?.(e)},T=e,e}let M={enqueue(e){O.signal.aborted||e.byteLength!==0&&j().port.postMessage({event:`write`,buffer:new Uint8Array(e)},[e])},done(){if(!T)return Promise.resolve();let n=++E;if(T.port.postMessage({event:`done`,turn:n}),_.state!==`running`)return k(),Promise.resolve();let{promise:r,resolve:i}=Promise.withResolvers();k();let a=()=>{clearInterval(o),clearTimeout(s),D?.settle===a&&(D=null),i()},o=setInterval(()=>{_.state!==`running`&&a()},t),s=setTimeout(a,e);return D={turn:n,settle:a},r},flush(){T&&(k(),T.port.postMessage({event:`interrupt`}))},async close(){if(!O.signal.aborted){O.abort(),await w.stop(),C.disconnect(),w.node.disconnect(),T&&T.disconnect();for(let e of S.getTracks())e.stop();await b()}},async[Symbol.asyncDispose](){await M.close()}};return M}export{s as createVoiceIO};
|