@alexkroman1/aai-ui 0.12.3 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +257 -0
- package/dist/_colors-CpZO-88A.js +78 -0
- package/dist/_module-url-C13kAJ87.js +15 -0
- package/dist/_page-shell.d.ts +11 -0
- package/dist/_recover-run.d.ts +79 -0
- package/dist/_repeat-until.d.ts +30 -0
- package/dist/_run-controls.d.ts +33 -0
- package/dist/_submission-state.d.ts +92 -0
- package/dist/_tap-to-talk-state.d.ts +88 -0
- package/dist/_utils-Mi-SCFA1.js +47 -0
- package/dist/_utils.d.ts +26 -0
- package/dist/_web-storage.d.ts +43 -0
- package/dist/_workflow-api-ref.d.ts +37 -0
- package/dist/_workflow-files.d.ts +34 -0
- package/dist/_workflow-test-defs.d.ts +38 -0
- package/dist/aai-logo-D8pMVn9_.js +85 -0
- package/dist/agent-state-labels.d.ts +65 -0
- package/dist/agent-state.d.ts +108 -0
- package/dist/audio/capture.d.ts +44 -0
- package/dist/audio/index.d.ts +18 -0
- package/dist/audio/index.js +431 -0
- package/dist/audio/preconnect.d.ts +87 -0
- package/dist/audio/voice-io.d.ts +112 -0
- package/dist/build-default-client.d.ts +1 -0
- package/dist/chat-view-BCbI7gD4.js +396 -0
- package/dist/client-config-C7fQrioM.js +139 -0
- package/dist/client-config.d.ts +119 -0
- package/dist/client-dir.d.ts +44 -0
- package/dist/client-dir.js +58 -0
- package/dist/client-identity.d.ts +131 -0
- package/dist/client-tool.d.ts +51 -0
- package/dist/components/_colors.d.ts +73 -0
- package/dist/components/_form-readiness.d.ts +56 -0
- package/dist/components/_form-values.d.ts +19 -0
- package/dist/components/aai-logo.d.ts +12 -0
- package/dist/components/audio-result.d.ts +94 -0
- package/dist/components/auto-scroll.d.ts +74 -0
- package/dist/components/bullet-list.d.ts +74 -0
- package/dist/components/button.d.ts +29 -21
- package/dist/components/button.js +62 -23
- package/dist/components/chat-view.d.ts +24 -18
- package/dist/components/chat-view.js +2 -60
- package/dist/components/console-shell.d.ts +85 -0
- package/dist/components/controls.d.ts +21 -6
- package/dist/components/controls.js +31 -19
- package/dist/components/conversation-view.d.ts +130 -0
- package/dist/components/eyebrow.d.ts +12 -0
- package/dist/components/facts.d.ts +81 -0
- package/dist/components/form-fields.d.ts +142 -0
- package/dist/components/form-types.d.ts +73 -0
- package/dist/components/form.d.ts +96 -0
- package/dist/components/markdown.d.ts +57 -0
- package/dist/components/message-list.d.ts +34 -7
- package/dist/components/message-list.js +2 -75
- package/dist/components/session-controls.d.ts +125 -0
- package/dist/components/session-error-banner.d.ts +69 -0
- package/dist/components/session-state-dot.d.ts +111 -0
- package/dist/components/sidebar-layout.d.ts +31 -10
- package/dist/components/sidebar-layout.js +32 -12
- package/dist/components/start-screen.d.ts +17 -6
- package/dist/components/start-screen.js +34 -14
- package/dist/components/tool-call-block.d.ts +15 -14
- package/dist/components/tool-call-block.js +2 -117
- package/dist/components/tool-call-row.d.ts +75 -0
- package/dist/components/tool-config-context.d.ts +22 -0
- package/dist/components/upload-progress.d.ts +76 -0
- package/dist/components/url-chips.d.ts +29 -0
- package/dist/components/workflow-fields.d.ts +46 -0
- package/dist/components/workflow-pending-note.d.ts +81 -0
- package/dist/components/workflow-progress.d.ts +81 -0
- package/dist/components/workflow-run-error.d.ts +48 -0
- package/dist/components/workflow-run-panel.d.ts +79 -0
- package/dist/context.d.ts +288 -0
- package/dist/context.js +382 -0
- package/dist/conversation-log.d.ts +88 -0
- package/dist/default-client/assets/_module-url-BX0RuRU2.js +1 -0
- package/dist/default-client/assets/audio-cGFGID-2.js +1 -0
- package/dist/default-client/assets/capture-processor-BUABoo3_.js +90 -0
- package/dist/default-client/assets/index-8ihb1Urm.css +2 -0
- package/dist/default-client/assets/index-BQURVARl.js +126 -0
- package/dist/default-client/assets/playback-processor-ABOa11j0.js +314 -0
- package/dist/default-client/favicon.ico +0 -0
- package/dist/default-client/index.html +15 -0
- package/dist/default-client.d.ts +23 -0
- package/dist/define-client.d.ts +162 -26
- package/dist/define-client.js +201 -55
- package/dist/eyebrow-UfmSz9yy.js +27 -0
- package/dist/hooks-DN4gHYsj.js +182 -0
- package/dist/hooks.d.ts +172 -0
- package/dist/hooks.js +3 -0
- package/dist/hooks.test-d.d.ts +14 -0
- package/dist/inbox-protocol.d.ts +128 -0
- package/dist/inbox.d.ts +132 -0
- package/dist/index.d.ts +126 -29
- package/dist/index.js +5500 -15
- package/dist/internal.d.ts +43 -0
- package/dist/internal.js +8 -0
- package/dist/linked-client.d.ts +70 -0
- package/dist/message-list-DHZcLhRA.js +682 -0
- package/dist/notice-player.d.ts +70 -0
- package/dist/page.d.ts +114 -0
- package/dist/phone.d.ts +50 -0
- package/dist/route-fetch.d.ts +79 -0
- package/dist/session/audio-effects.d.ts +31 -0
- package/dist/session/audio-setup.d.ts +57 -0
- package/dist/session/audio-state.d.ts +157 -0
- package/dist/session/browser-session.d.ts +30 -0
- package/dist/session/close.d.ts +26 -0
- package/dist/session/dial.d.ts +65 -0
- package/dist/session/handshake.d.ts +60 -0
- package/dist/session/index.d.ts +17 -0
- package/dist/session/index.js +2 -0
- package/dist/session/messages.d.ts +63 -0
- package/dist/session/mic.d.ts +64 -0
- package/dist/session/preconnect.d.ts +48 -0
- package/dist/session/reconnect.d.ts +25 -0
- package/dist/session/resume-store.d.ts +57 -0
- package/dist/session/state.d.ts +146 -0
- package/dist/session/ticket.d.ts +44 -0
- package/dist/session/types.d.ts +476 -0
- package/dist/session/url.d.ts +17 -0
- package/dist/session/user-turn.d.ts +55 -0
- package/dist/session-CE0Ipsb3.js +2129 -0
- package/dist/stored-value.d.ts +103 -0
- package/dist/tool-call-block-uXtMOUWN.js +204 -0
- package/dist/tool-config-context-Es4YUzV2.js +19 -0
- package/dist/types.d.ts +272 -34
- package/dist/types.js +35 -4
- package/dist/upload/files.d.ts +83 -0
- package/dist/upload/index.d.ts +15 -0
- package/dist/upload/pause.d.ts +31 -0
- package/dist/upload/recall.d.ts +67 -0
- package/dist/upload/report.d.ts +26 -0
- package/dist/upload/session.d.ts +99 -0
- package/dist/url-chips-oyy_Eiqh.js +239 -0
- package/dist/use-client-runs.d.ts +87 -0
- package/dist/use-conversation-log.d.ts +86 -0
- package/dist/use-conversation.d.ts +131 -0
- package/dist/use-copy.d.ts +74 -0
- package/dist/use-download-url.d.ts +84 -0
- package/dist/use-flash.d.ts +61 -0
- package/dist/use-inbox.d.ts +101 -0
- package/dist/use-push-to-talk.d.ts +105 -0
- package/dist/use-route-mutation.d.ts +113 -0
- package/dist/use-route.d.ts +82 -0
- package/dist/use-run-key.d.ts +133 -0
- package/dist/use-session-controls.d.ts +69 -0
- package/dist/use-session-id.d.ts +57 -0
- package/dist/use-tap-to-talk.d.ts +138 -0
- package/dist/use-user-transcript-uyHhzy4d.js +73 -0
- package/dist/use-user-transcript.d.ts +75 -0
- package/dist/use-workflow-form.d.ts +285 -0
- package/dist/use-workflow-progress.d.ts +137 -0
- package/dist/use-workflow-run-DdhWmbx-.js +697 -0
- package/dist/use-workflow-run.d.ts +100 -0
- package/dist/use-workflow-runs.d.ts +71 -0
- package/dist/use-workflow-stream.d.ts +154 -0
- package/dist/use-workflows.d.ts +77 -0
- package/dist/workflow-client.d.ts +136 -0
- package/dist/workflow-def-types.d.ts +31 -0
- package/dist/workflow-events.d.ts +39 -0
- package/dist/workflow-field-kind.d.ts +95 -0
- package/dist/workflow-status-labels.d.ts +35 -0
- package/dist/worklets/_module-url.d.ts +10 -0
- package/dist/worklets/_playback-bench-harness.d.ts +181 -0
- package/dist/worklets/_playback-bench-host.d.ts +63 -0
- package/dist/worklets/_playback-bench-page.d.ts +65 -0
- package/dist/worklets/_tts-trace-harness.d.ts +142 -0
- package/dist/worklets/_worklet-test-utils.d.ts +27 -0
- package/dist/worklets/capture-processor.d.ts +4 -2
- package/dist/worklets/capture-processor.js +73 -33
- package/dist/worklets/playback-processor.d.ts +4 -2
- package/dist/worklets/playback-processor.js +258 -42
- package/package.json +57 -32
- package/styles.css +152 -30
- package/dist/audio.d.ts +0 -45
- package/dist/audio.js +0 -121
- package/dist/client-context.d.ts +0 -33
- package/dist/client-context.js +0 -15
- package/dist/client-handler.d.ts +0 -43
- package/dist/components/app.d.ts +0 -23
- package/dist/components/app.js +0 -41
- package/dist/components/error-banner.d.ts +0 -21
- package/dist/components/error-banner.js +0 -27
- package/dist/components/message-bubble.d.ts +0 -23
- package/dist/components/message-bubble.js +0 -35
- package/dist/components/state-indicator.d.ts +0 -29
- package/dist/components/state-indicator.js +0 -37
- package/dist/components/thinking-indicator.d.ts +0 -16
- package/dist/components/thinking-indicator.js +0 -34
- package/dist/components/tool-icons.d.ts +0 -17
- package/dist/components/tool-icons.js +0 -128
- package/dist/components/transcript.d.ts +0 -25
- package/dist/components/transcript.js +0 -35
- package/dist/session-CWB7vmqz.js +0 -429
- package/dist/session.d.ts +0 -69
- package/dist/session.js +0 -2
- package/dist/signals.d.ts +0 -120
- package/dist/signals.js +0 -161
- package/dist/tsdown.config.d.ts +0 -2
- package/dist/types.test-d.d.ts +0 -7
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `useTapToTalk` — one tap goes live, the next hangs up: the TOGGLE counterpart
|
|
3
|
+
* of `usePushToTalk`, for an agent with automatic turn detection (the default).
|
|
4
|
+
* It is how a smart speaker's button works, and a page that is the speaker's
|
|
5
|
+
* twin wants the same thing.
|
|
6
|
+
*
|
|
7
|
+
* Tapping is the easy half. What each hand-written copy had to work out:
|
|
8
|
+
*
|
|
9
|
+
* - **Hanging up is `disconnect()`, never `end()`**, so the next tap RESUMES
|
|
10
|
+
* the session — the agent still knows what was said a minute ago.
|
|
11
|
+
* - **The mic is muted unless live.** A typed turn opens the session too, and
|
|
12
|
+
* must not open the microphone with it; the mute is set before the connect,
|
|
13
|
+
* so the call opens already in the right state.
|
|
14
|
+
* - **A typed turn while idle opens the session and waits for it**
|
|
15
|
+
* (`session.sendText(text, { connect: true })`) instead of being dropped.
|
|
16
|
+
* - **A call that is not live hangs itself up** after `idleHangupMs` of quiet
|
|
17
|
+
* (the device's follow-up window) or `thinkingHangupMs` of thinking — every
|
|
18
|
+
* session activity restarts the clock, and a speaking agent is never cut
|
|
19
|
+
* off. A LIVE call ends only when the caller taps.
|
|
20
|
+
* - **A connection that never comes is a failure**, after `connectTimeoutMs`.
|
|
21
|
+
* - **The session going down on its own** (an error, the server closing it)
|
|
22
|
+
* leaves live mode — otherwise the next tap would read as "hang up".
|
|
23
|
+
* - The talk key's auto-repeat is not a tap, it is ignored in a text field, and
|
|
24
|
+
* the button's own Space activation is suppressed so it does not count twice.
|
|
25
|
+
*
|
|
26
|
+
* The decisions are a statechart (`_tap-to-talk-state.ts`); this hook feeds it
|
|
27
|
+
* the session and runs its effects.
|
|
28
|
+
*
|
|
29
|
+
* @module
|
|
30
|
+
*/
|
|
31
|
+
/**
|
|
32
|
+
* Options for {@link useTapToTalk}.
|
|
33
|
+
*
|
|
34
|
+
* @public
|
|
35
|
+
*/
|
|
36
|
+
export type UseTapToTalkOptions = {
|
|
37
|
+
/**
|
|
38
|
+
* The keyboard key that taps, as a `KeyboardEvent.code` — `"Space"` by
|
|
39
|
+
* default. `false` turns the page-wide key off; the button itself still
|
|
40
|
+
* answers a click, Space and Enter.
|
|
41
|
+
*/
|
|
42
|
+
key?: string | false | undefined;
|
|
43
|
+
/**
|
|
44
|
+
* Hang up a call that is NOT live after this long with nothing happening —
|
|
45
|
+
* the device's follow-up window. Default 3000 ms.
|
|
46
|
+
*/
|
|
47
|
+
idleHangupMs?: number | undefined;
|
|
48
|
+
/**
|
|
49
|
+
* Hang up a call that is not live when the agent has been thinking this
|
|
50
|
+
* long. Default 60,000 ms — long enough for a slow tool.
|
|
51
|
+
*/
|
|
52
|
+
thinkingHangupMs?: number | undefined;
|
|
53
|
+
/** Give up on a connection attempt after this long, and report `failed`. Default 8000 ms. */
|
|
54
|
+
connectTimeoutMs?: number | undefined;
|
|
55
|
+
};
|
|
56
|
+
/**
|
|
57
|
+
* What {@link useTapToTalk} returns.
|
|
58
|
+
*
|
|
59
|
+
* @public
|
|
60
|
+
*/
|
|
61
|
+
export type UseTapToTalkResult = {
|
|
62
|
+
/**
|
|
63
|
+
* The call: `"idle"` (not meant to be running), `"connecting"`, or
|
|
64
|
+
* `"active"` — up, whatever the agent is doing.
|
|
65
|
+
*/
|
|
66
|
+
phase: "idle" | "connecting" | "active";
|
|
67
|
+
/** Whether the caller is live — mic open, in a realtime conversation. The button is ON. */
|
|
68
|
+
live: boolean;
|
|
69
|
+
/**
|
|
70
|
+
* Whether the last attempt failed: the connection timed out or the session
|
|
71
|
+
* reported an error. Cleared by the next tap or typed turn.
|
|
72
|
+
*/
|
|
73
|
+
failed: boolean;
|
|
74
|
+
/** Tap: go live (connecting first if idle), or hang up if live. */
|
|
75
|
+
toggle: () => void;
|
|
76
|
+
/**
|
|
77
|
+
* A typed turn. Sent at once while the call is up; while idle it opens the
|
|
78
|
+
* session WITHOUT the mic and is sent once connected. Blank text is ignored.
|
|
79
|
+
*/
|
|
80
|
+
send: (text: string) => void;
|
|
81
|
+
/** Hang up now — resumably (`disconnect()`), live or not. */
|
|
82
|
+
hangUp: () => void;
|
|
83
|
+
/**
|
|
84
|
+
* Spread onto a `<button>`: the click, the keyboard guard against a double
|
|
85
|
+
* tap, and `aria-pressed`. Style it however you like.
|
|
86
|
+
*/
|
|
87
|
+
buttonProps: {
|
|
88
|
+
onClick: () => void;
|
|
89
|
+
onKeyDown: (event: {
|
|
90
|
+
code: string;
|
|
91
|
+
preventDefault(): void;
|
|
92
|
+
}) => void;
|
|
93
|
+
onKeyUp: (event: {
|
|
94
|
+
code: string;
|
|
95
|
+
preventDefault(): void;
|
|
96
|
+
}) => void;
|
|
97
|
+
"aria-pressed": boolean;
|
|
98
|
+
};
|
|
99
|
+
};
|
|
100
|
+
/**
|
|
101
|
+
* Tap to go live, tap to hang up, over the session — see this module's doc
|
|
102
|
+
* for what it handles beyond the tap.
|
|
103
|
+
*
|
|
104
|
+
* Must be used inside the provider `mountClient()` installs. Call it ONCE per
|
|
105
|
+
* page: it owns the session's mute and hang-up clock.
|
|
106
|
+
*
|
|
107
|
+
* @example A talk button and a composer
|
|
108
|
+
* ```tsx
|
|
109
|
+
* import { useTapToTalk } from "@alexkroman1/aai-ui";
|
|
110
|
+
*
|
|
111
|
+
* function Speaker() {
|
|
112
|
+
* const { live, phase, send, buttonProps } = useTapToTalk();
|
|
113
|
+
* return (
|
|
114
|
+
* <>
|
|
115
|
+
* <button type="button" {...buttonProps}>
|
|
116
|
+
* {phase === "connecting" ? "Connecting…" : live ? "Tap to hang up" : "Tap to talk"}
|
|
117
|
+
* </button>
|
|
118
|
+
* <form
|
|
119
|
+
* onSubmit={(e) => {
|
|
120
|
+
* e.preventDefault();
|
|
121
|
+
* const input = e.currentTarget.elements.namedItem("say") as HTMLInputElement;
|
|
122
|
+
* send(input.value);
|
|
123
|
+
* input.value = "";
|
|
124
|
+
* }}
|
|
125
|
+
* >
|
|
126
|
+
* <input name="say" placeholder="…or type" />
|
|
127
|
+
* </form>
|
|
128
|
+
* </>
|
|
129
|
+
* );
|
|
130
|
+
* }
|
|
131
|
+
* ```
|
|
132
|
+
*
|
|
133
|
+
* @param options - The talk key and the three clocks; see {@link UseTapToTalkOptions}.
|
|
134
|
+
* @returns The phase, the live flag and the controls; see {@link UseTapToTalkResult}.
|
|
135
|
+
*
|
|
136
|
+
* @public
|
|
137
|
+
*/
|
|
138
|
+
export declare function useTapToTalk(options?: UseTapToTalkOptions): UseTapToTalkResult;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { useSessionSelector } from "./context.js";
|
|
2
|
+
import { useMemo } from "react";
|
|
3
|
+
//#region src/use-user-transcript.ts
|
|
4
|
+
/**
|
|
5
|
+
* `useUserTranscript` — what the caller is saying RIGHT NOW, read correctly.
|
|
6
|
+
*
|
|
7
|
+
* `SessionSnapshot.userTranscript` is `string | null`, and the two falsy values
|
|
8
|
+
* mean different things:
|
|
9
|
+
*
|
|
10
|
+
* - `null` — nobody is speaking. There is no partial turn.
|
|
11
|
+
* - `""` — speech HAS been detected and no words have come back yet. A live
|
|
12
|
+
* session sits here for a few hundred milliseconds at the start of every turn.
|
|
13
|
+
*
|
|
14
|
+
* Read as one falsy check, those collapse and the indicator never appears at the
|
|
15
|
+
* start of a turn — which is the moment it is for. So every custom chrome writes
|
|
16
|
+
* `transcript !== null && (transcript === "" ? "…" : transcript)`, three
|
|
17
|
+
* templates did exactly that, and each one re-derived a protocol distinction
|
|
18
|
+
* from the type rather than from anything that told them.
|
|
19
|
+
*
|
|
20
|
+
* This is the same distinction as two named booleans, so a component can say
|
|
21
|
+
* what it means: render on `speaking`, show `text`, and use the SDK's own
|
|
22
|
+
* placeholder when there is nothing to show yet.
|
|
23
|
+
*/
|
|
24
|
+
/**
|
|
25
|
+
* Placeholder for "listening, no words yet" — the `""` case above.
|
|
26
|
+
*
|
|
27
|
+
* A one-character ellipsis rather than three dots, because it is read by a
|
|
28
|
+
* screen reader as an ellipsis and it does not reflow the row when the first
|
|
29
|
+
* real word replaces it.
|
|
30
|
+
*
|
|
31
|
+
* On `@alexkroman1/aai-ui/internal`, not the root: no public signature names
|
|
32
|
+
* it, and a `client.tsx` rendering its own transcript row compares against the
|
|
33
|
+
* character. `UseUserTranscriptResult.text` therefore spells the value out
|
|
34
|
+
* rather than linking here.
|
|
35
|
+
*
|
|
36
|
+
* @internal
|
|
37
|
+
*/
|
|
38
|
+
const TRANSCRIBING_PLACEHOLDER = "…";
|
|
39
|
+
/**
|
|
40
|
+
* Subscribe to the caller's in-progress turn.
|
|
41
|
+
*
|
|
42
|
+
* Narrowly subscribed — a component using this re-renders at STT-partial rate,
|
|
43
|
+
* which is exactly what it is for and exactly what a whole-page `useSession()`
|
|
44
|
+
* should not do.
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* ```tsx
|
|
48
|
+
* import { useUserTranscript } from "@alexkroman1/aai-ui";
|
|
49
|
+
*
|
|
50
|
+
* function LiveTranscript() {
|
|
51
|
+
* const { speaking, text } = useUserTranscript();
|
|
52
|
+
* if (!speaking) return null;
|
|
53
|
+
* return <div className="italic opacity-60">{text}</div>;
|
|
54
|
+
* }
|
|
55
|
+
* ```
|
|
56
|
+
*
|
|
57
|
+
* @public
|
|
58
|
+
*/
|
|
59
|
+
function useUserTranscript() {
|
|
60
|
+
const partial = useSessionSelector((snapshot) => snapshot.userTranscript);
|
|
61
|
+
return useMemo(() => ({
|
|
62
|
+
speaking: partial !== null,
|
|
63
|
+
text: displayText(partial),
|
|
64
|
+
partial
|
|
65
|
+
}), [partial]);
|
|
66
|
+
}
|
|
67
|
+
/** The three cases, spelled out: silent, detected-but-wordless, and words. */
|
|
68
|
+
function displayText(partial) {
|
|
69
|
+
if (partial === null) return "";
|
|
70
|
+
return partial === "" ? "…" : partial;
|
|
71
|
+
}
|
|
72
|
+
//#endregion
|
|
73
|
+
export { useUserTranscript as n, TRANSCRIBING_PLACEHOLDER as t };
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `useUserTranscript` — what the caller is saying RIGHT NOW, read correctly.
|
|
3
|
+
*
|
|
4
|
+
* `SessionSnapshot.userTranscript` is `string | null`, and the two falsy values
|
|
5
|
+
* mean different things:
|
|
6
|
+
*
|
|
7
|
+
* - `null` — nobody is speaking. There is no partial turn.
|
|
8
|
+
* - `""` — speech HAS been detected and no words have come back yet. A live
|
|
9
|
+
* session sits here for a few hundred milliseconds at the start of every turn.
|
|
10
|
+
*
|
|
11
|
+
* Read as one falsy check, those collapse and the indicator never appears at the
|
|
12
|
+
* start of a turn — which is the moment it is for. So every custom chrome writes
|
|
13
|
+
* `transcript !== null && (transcript === "" ? "…" : transcript)`, three
|
|
14
|
+
* templates did exactly that, and each one re-derived a protocol distinction
|
|
15
|
+
* from the type rather than from anything that told them.
|
|
16
|
+
*
|
|
17
|
+
* This is the same distinction as two named booleans, so a component can say
|
|
18
|
+
* what it means: render on `speaking`, show `text`, and use the SDK's own
|
|
19
|
+
* placeholder when there is nothing to show yet.
|
|
20
|
+
*/
|
|
21
|
+
/**
|
|
22
|
+
* Placeholder for "listening, no words yet" — the `""` case above.
|
|
23
|
+
*
|
|
24
|
+
* A one-character ellipsis rather than three dots, because it is read by a
|
|
25
|
+
* screen reader as an ellipsis and it does not reflow the row when the first
|
|
26
|
+
* real word replaces it.
|
|
27
|
+
*
|
|
28
|
+
* On `@alexkroman1/aai-ui/internal`, not the root: no public signature names
|
|
29
|
+
* it, and a `client.tsx` rendering its own transcript row compares against the
|
|
30
|
+
* character. `UseUserTranscriptResult.text` therefore spells the value out
|
|
31
|
+
* rather than linking here.
|
|
32
|
+
*
|
|
33
|
+
* @internal
|
|
34
|
+
*/
|
|
35
|
+
export declare const TRANSCRIBING_PLACEHOLDER = "\u2026";
|
|
36
|
+
/** What {@link useUserTranscript} returns. */
|
|
37
|
+
export interface UseUserTranscriptResult {
|
|
38
|
+
/**
|
|
39
|
+
* True while the caller holds the turn — from speech detection to the final
|
|
40
|
+
* transcript. This is the flag a live-transcript row renders on.
|
|
41
|
+
*/
|
|
42
|
+
speaking: boolean;
|
|
43
|
+
/**
|
|
44
|
+
* The words so far, or a one-character ellipsis (`…`) while there are none.
|
|
45
|
+
* Empty string when nobody is speaking.
|
|
46
|
+
*/
|
|
47
|
+
text: string;
|
|
48
|
+
/**
|
|
49
|
+
* The raw partial: the words so far, `""` while there are none, and `null`
|
|
50
|
+
* when nobody is speaking. For a chrome that wants to render its own
|
|
51
|
+
* placeholder (or none).
|
|
52
|
+
*/
|
|
53
|
+
partial: string | null;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Subscribe to the caller's in-progress turn.
|
|
57
|
+
*
|
|
58
|
+
* Narrowly subscribed — a component using this re-renders at STT-partial rate,
|
|
59
|
+
* which is exactly what it is for and exactly what a whole-page `useSession()`
|
|
60
|
+
* should not do.
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* ```tsx
|
|
64
|
+
* import { useUserTranscript } from "@alexkroman1/aai-ui";
|
|
65
|
+
*
|
|
66
|
+
* function LiveTranscript() {
|
|
67
|
+
* const { speaking, text } = useUserTranscript();
|
|
68
|
+
* if (!speaking) return null;
|
|
69
|
+
* return <div className="italic opacity-60">{text}</div>;
|
|
70
|
+
* }
|
|
71
|
+
* ```
|
|
72
|
+
*
|
|
73
|
+
* @public
|
|
74
|
+
*/
|
|
75
|
+
export declare function useUserTranscript(): UseUserTranscriptResult;
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The hook a FORM needs, as against the one a status view does.
|
|
3
|
+
*
|
|
4
|
+
* `useWorkflowRun` (`workflow-client.ts`) watches a run you already have.
|
|
5
|
+
* This is what comes before it: `useWorkflowSubmit` starts a run and hands the
|
|
6
|
+
* id straight to `useWorkflowRun`. Its sibling `useWorkflows` — the listing
|
|
7
|
+
* `<WorkflowFields>` renders a form from — is `use-workflows.ts`.
|
|
8
|
+
*
|
|
9
|
+
* ## `useWorkflowSubmit` — a form's two halves in one hook
|
|
10
|
+
*
|
|
11
|
+
* A page that submits a workflow always needs the same four pieces of state:
|
|
12
|
+
* the run id, whether a submit is in flight, whether the RUN is still going, and
|
|
13
|
+
* whichever of the two failed. `link-digest-workflow` writes them out by hand, which is
|
|
14
|
+
* the right shape for a template teaching the primitives and the wrong shape to
|
|
15
|
+
* write a third time — and it is easy to get subtly wrong: dropping the previous
|
|
16
|
+
* run id before the new `POST` returns is what stops a finished result sitting
|
|
17
|
+
* under a form that is already submitting again.
|
|
18
|
+
*
|
|
19
|
+
* So this is `api.start` plus {@link useWorkflowRun}, with the state between
|
|
20
|
+
* them. It adds no transport of its own and holds no run state of its own; the
|
|
21
|
+
* watching (stream first, poll as its fallback, terminal stops) is entirely
|
|
22
|
+
* `useWorkflowRun`'s, and `run` here IS its run.
|
|
23
|
+
*
|
|
24
|
+
* ## Why it starts ASYNCHRONOUSLY even though a synchronous call exists
|
|
25
|
+
*
|
|
26
|
+
* `api.startAndWait` would collapse this to one request, and it is the wrong
|
|
27
|
+
* default for a page: it holds a socket open for up to a minute, answers nothing
|
|
28
|
+
* until it settles, and a page has `useWorkflowRun` — which survives a reload,
|
|
29
|
+
* shows progress, and costs one stream. The synchronous call is for callers with
|
|
30
|
+
* nowhere to put a watch (a script, a cron, a form POST from a server). Pass
|
|
31
|
+
* `wait` here when the page really does want one request, and the run is
|
|
32
|
+
* followed from the same id either way.
|
|
33
|
+
*/
|
|
34
|
+
import type { AnyWorkflowDef, UploadParallelOption, UploadProgress, WorkflowOutputOf } from "@alexkroman1/aai/workflow-api";
|
|
35
|
+
import type { FormValues } from "./components/form-types.ts";
|
|
36
|
+
import type { WorkflowApi, WorkflowRun } from "./workflow-client.ts";
|
|
37
|
+
import type { SubmitInputOf } from "./workflow-def-types.ts";
|
|
38
|
+
/**
|
|
39
|
+
* What {@link WorkflowSubmission.upload} reports while the bytes are going.
|
|
40
|
+
*
|
|
41
|
+
* The SDK's per-request {@link UploadProgress} plus WHICH file it describes,
|
|
42
|
+
* because a form is allowed more than one and a bar over "the upload" would
|
|
43
|
+
* restart at zero partway through with nothing to say why.
|
|
44
|
+
*
|
|
45
|
+
* @public
|
|
46
|
+
*/
|
|
47
|
+
export type UploadStatus = UploadProgress & {
|
|
48
|
+
/** The file being sent, by the name the picker gave it. */
|
|
49
|
+
name: string;
|
|
50
|
+
/** Which file of the submission this is, counting from 1. */
|
|
51
|
+
index: number;
|
|
52
|
+
/** How many files this submission sends in total. */
|
|
53
|
+
count: number;
|
|
54
|
+
/**
|
|
55
|
+
* Whether the person has parked this upload.
|
|
56
|
+
*
|
|
57
|
+
* A paused upload is not a stopped one: the windows already stored stay stored,
|
|
58
|
+
* `loaded` holds where it got to, and resuming sends what is missing rather than
|
|
59
|
+
* the file. So a bar rendering this reads "Paused at 62%", never "62% and
|
|
60
|
+
* frozen" — which is what a page could otherwise only guess from a number that
|
|
61
|
+
* stopped moving, the same ambiguity `complete` exists to remove on the run side.
|
|
62
|
+
*/
|
|
63
|
+
paused: boolean;
|
|
64
|
+
};
|
|
65
|
+
/**
|
|
66
|
+
* What {@link useWorkflowSubmit} returns.
|
|
67
|
+
*
|
|
68
|
+
* @see {@link WorkflowStreamSubmission} — an ALIAS of this type, returned by
|
|
69
|
+
* `useWorkflowStream`, which is a drop-in sibling. Exactly two fields MEAN
|
|
70
|
+
* something different there, and both differences follow from WHEN the run is
|
|
71
|
+
* created: there, `submit()` resolves when the UPLOAD finishes rather than when
|
|
72
|
+
* the run is accepted, and `run` is non-`undefined` from before the bytes are
|
|
73
|
+
* in, so a page can render `<WorkflowProgress>` beside the upload bar instead
|
|
74
|
+
* of after it. Here the run does not exist until the last byte lands.
|
|
75
|
+
*/
|
|
76
|
+
export type WorkflowSubmission<R = unknown, I = unknown> = {
|
|
77
|
+
/**
|
|
78
|
+
* Start a run with this input. Resolves once the run EXISTS — progress
|
|
79
|
+
* arrives through `run` — so a `<Form>`'s handler can await it to know the
|
|
80
|
+
* submission was accepted.
|
|
81
|
+
*/
|
|
82
|
+
submit: (input: I) => Promise<void>;
|
|
83
|
+
/**
|
|
84
|
+
* Start a run from a `<Form>`'s values, which are UNVALIDATED.
|
|
85
|
+
*
|
|
86
|
+
* The same function as {@link WorkflowSubmission.submit}, with the type the
|
|
87
|
+
* form path can honestly offer. `FormValues` is `Record<string, unknown>`
|
|
88
|
+
* scraped off the DOM at submit time — a `<TextField name="limit">`
|
|
89
|
+
* contributes a string whatever the schema says — so the shape is not known
|
|
90
|
+
* here and the SERVER is what checks it against the workflow's schema.
|
|
91
|
+
*
|
|
92
|
+
* Two doors rather than one loose one: `submit` takes the workflow's own
|
|
93
|
+
* input type, so a hand-built object is checked at compile time and
|
|
94
|
+
* `submit({ ur1: 42 })` is an error; widening it to accept `FormValues` would
|
|
95
|
+
* have made every object satisfy it and given the typing back. Reaching for
|
|
96
|
+
* this one is the author saying "these came from a form", which is a fact
|
|
97
|
+
* about the values and not a cast.
|
|
98
|
+
*/
|
|
99
|
+
submitForm: (values: FormValues) => Promise<void>;
|
|
100
|
+
/** Clear the run and any error, putting the form back to its initial state. */
|
|
101
|
+
reset: () => void;
|
|
102
|
+
/**
|
|
103
|
+
* End the current run's `sleep()` early — "file it now" — resolving how many
|
|
104
|
+
* pending sleeps were interrupted.
|
|
105
|
+
*
|
|
106
|
+
* Bound to the run this submission is following, which is the point: it is
|
|
107
|
+
* the only reason a page holding one of these hooks needed an `api` of its
|
|
108
|
+
* own. `0` is an answer rather than a failure (the run had already moved past
|
|
109
|
+
* its wait, or there is no run yet), so nothing here has to be guarded.
|
|
110
|
+
*/
|
|
111
|
+
wake: () => Promise<number>;
|
|
112
|
+
/**
|
|
113
|
+
* Stop the current run, resolving whether this call is what ended it.
|
|
114
|
+
*
|
|
115
|
+
* `false` for a run that had already finished, and for no run at all — the
|
|
116
|
+
* SDK's contract, because two tabs pressing Stop is ordinary. Distinct from
|
|
117
|
+
* `reset()`, which puts the FORM back and leaves the run running.
|
|
118
|
+
*/
|
|
119
|
+
cancel: () => Promise<boolean>;
|
|
120
|
+
/** The run, once started, followed to completion. */
|
|
121
|
+
run: WorkflowRun<R> | undefined;
|
|
122
|
+
/**
|
|
123
|
+
* True from `submit()` on this mount until `reset()` — did THIS page start
|
|
124
|
+
* the run it is showing?
|
|
125
|
+
*
|
|
126
|
+
* A page needs it to say the right sentence and cannot derive it: a run
|
|
127
|
+
* ADOPTED by the mount-time lookup after a reload looks exactly like one this
|
|
128
|
+
* page started. Six templates kept a `useState(false)` next to this hook, set
|
|
129
|
+
* it in their `onSubmit` and mirrored it in their `onClear` — shadow state
|
|
130
|
+
* for a fact only this hook can know, since it is the thing that decides
|
|
131
|
+
* between `submit()` and the recovery lookup. One of the six grew a fourth
|
|
132
|
+
* branch and had to move the whole note into its own module with its own
|
|
133
|
+
* spec, which is what a seam missing one layer down looks like.
|
|
134
|
+
*
|
|
135
|
+
* The RAW fact rather than a derived "recovered", deliberately: with `run`
|
|
136
|
+
* these are three states, not two, and the third is the one a page most needs
|
|
137
|
+
* to explain. `startedHere` is "you pressed the button"; `!startedHere &&
|
|
138
|
+
* !run` is the mount-time lookup still going; `!startedHere && run` is a run
|
|
139
|
+
* this browser started earlier, now in front of somebody who did not press
|
|
140
|
+
* anything. A boolean meaning only the last of those cannot express the
|
|
141
|
+
* middle one.
|
|
142
|
+
*
|
|
143
|
+
* @example
|
|
144
|
+
* ```ts
|
|
145
|
+
* declare const submission: import("@alexkroman1/aai-ui").WorkflowSubmission;
|
|
146
|
+
* const note = submission.startedHere
|
|
147
|
+
* ? "You can close this tab or reload it — this page will find the run again."
|
|
148
|
+
* : submission.run === undefined
|
|
149
|
+
* ? "Looking for a run this tab started earlier…"
|
|
150
|
+
* : "Still working on the run this tab started earlier. Reloading is safe.";
|
|
151
|
+
* ```
|
|
152
|
+
*/
|
|
153
|
+
startedHere: boolean;
|
|
154
|
+
/**
|
|
155
|
+
* True from `submit()` until the run reaches a terminal status.
|
|
156
|
+
*
|
|
157
|
+
* The WORK, not the request: a run outlives its `POST`, and a submit button
|
|
158
|
+
* that re-enabled on the response would invite a second submission of work
|
|
159
|
+
* already in flight.
|
|
160
|
+
*/
|
|
161
|
+
pending: boolean;
|
|
162
|
+
/**
|
|
163
|
+
* How far the submission's files have got, while any are still going.
|
|
164
|
+
*
|
|
165
|
+
* Undefined before the first byte and again from the moment the last one
|
|
166
|
+
* lands, so a page can render `{upload && <UploadProgressBar upload={upload} />}`
|
|
167
|
+
* and the bar exists exactly for as long as there is an upload to describe. A
|
|
168
|
+
* form with no files never sets it at all.
|
|
169
|
+
*
|
|
170
|
+
* The wait it covers is the one `run` cannot: a run does not EXIST until its
|
|
171
|
+
* input is stored, so `pending` is true and there is nothing to poll — which
|
|
172
|
+
* for a 200 MB recording is minutes of a page that looks stuck.
|
|
173
|
+
*/
|
|
174
|
+
upload: UploadStatus | undefined;
|
|
175
|
+
/**
|
|
176
|
+
* Park the upload where it is, stopping the bytes in flight.
|
|
177
|
+
*
|
|
178
|
+
* The windows already stored stay stored, so `resumeUpload()` sends what is
|
|
179
|
+
* missing rather than the file — which is the difference between a pause a
|
|
180
|
+
* person will actually use on a 200 MB recording and a cancel dressed up as one.
|
|
181
|
+
*
|
|
182
|
+
* `submit()`'s promise stays unresolved across a pause, because the submission
|
|
183
|
+
* genuinely has not finished: the run does not exist until the last byte lands,
|
|
184
|
+
* so resolving here would tell a `<Form>` the work was accepted when nothing has
|
|
185
|
+
* been started. A no-op when there is no upload in flight.
|
|
186
|
+
*/
|
|
187
|
+
pauseUpload: () => void;
|
|
188
|
+
/** Continue a paused upload, sending only the windows the store does not have. */
|
|
189
|
+
resumeUpload: () => void;
|
|
190
|
+
/** The submit's own failure (a rejected input), or the watch's. */
|
|
191
|
+
error: string | undefined;
|
|
192
|
+
};
|
|
193
|
+
/** Options for {@link useWorkflowSubmit}. */
|
|
194
|
+
export type UseWorkflowSubmitOptions = {
|
|
195
|
+
/** The client to start runs with. Defaults to one for the page's own agent. */
|
|
196
|
+
api?: WorkflowApi;
|
|
197
|
+
/**
|
|
198
|
+
* Correlation key recorded with the run, for finding it again without the id.
|
|
199
|
+
*
|
|
200
|
+
* **Defaulted**, to an opaque per-page key in `sessionStorage` that the next
|
|
201
|
+
* load produces again — `useRunKey()`'s, minted by the hook. Pass one to
|
|
202
|
+
* scope runs to something the page knows better: an ACCOUNT's own id, which
|
|
203
|
+
* is what makes a run follow the person to a new device, or
|
|
204
|
+
* `useRunKey({ storage: "local" })` for a run that outlives the tab by
|
|
205
|
+
* design. The key is a lookup CAPABILITY (there is no per-user filtering
|
|
206
|
+
* behind `find`), it must fit the route's 256-character bound, and anything
|
|
207
|
+
* derived from a person's own input both collides and carries what they
|
|
208
|
+
* typed — `use-run-key.ts` argues every alternative.
|
|
209
|
+
*/
|
|
210
|
+
key?: string;
|
|
211
|
+
/**
|
|
212
|
+
* On mount, adopt the newest run this `key` already has.
|
|
213
|
+
*
|
|
214
|
+
* **This is what makes a reload survivable, and it is ON.** The run id is
|
|
215
|
+
* this hook's own state, so a refresh loses it while the run carries on — and
|
|
216
|
+
* a page that cannot name a run cannot show it, cancel it or wake it. The
|
|
217
|
+
* hook asks `find(workflow, key)` once as it mounts and follows whatever
|
|
218
|
+
* comes back, so the answer, the progress and the controls are all there
|
|
219
|
+
* again.
|
|
220
|
+
*
|
|
221
|
+
* It used to be opt-in, on the argument that a `key` alone means only "record
|
|
222
|
+
* this with the run" — true of `ctx.workflows.start({ key })`, where there is
|
|
223
|
+
* no page to put a run back on, and not of a form: six of six page templates
|
|
224
|
+
* passed `useRunKey()` and `recover: true` together, which is a default in
|
|
225
|
+
* the wrong place. `false` is the opt-out, and what it buys is a form that
|
|
226
|
+
* always opens empty — no lookup on mount, and a live run reachable only by
|
|
227
|
+
* an id the page has already lost.
|
|
228
|
+
*/
|
|
229
|
+
recover?: boolean;
|
|
230
|
+
/**
|
|
231
|
+
* Hold the `POST` open until the run settles, up to this many ms — the
|
|
232
|
+
* synchronous mode. Omitted (the default) returns as soon as the run exists.
|
|
233
|
+
*/
|
|
234
|
+
wait?: number;
|
|
235
|
+
/** How often the fallback poll re-reads a live run. */
|
|
236
|
+
intervalMs?: number;
|
|
237
|
+
/**
|
|
238
|
+
* Send each chosen file as concurrent parts instead of in one request.
|
|
239
|
+
*
|
|
240
|
+
* **On by default.** `false` opts out, `{ partBytes, concurrency }` tunes it.
|
|
241
|
+
* This is the wait a form with a recording in it actually spends: the run does
|
|
242
|
+
* not exist until its input is stored, so until the last byte lands there is no
|
|
243
|
+
* run to watch and nothing for `<WorkflowProgress>` to say. Splitting the file
|
|
244
|
+
* across connections is what makes that stretch shorter, and it degrades to the
|
|
245
|
+
* single request wherever it would not help — a small file, an older agent — so
|
|
246
|
+
* the default costs nothing where it would not have paid. See
|
|
247
|
+
* `UploadOptions.parallel`.
|
|
248
|
+
*/
|
|
249
|
+
parallel?: UploadParallelOption;
|
|
250
|
+
};
|
|
251
|
+
/**
|
|
252
|
+
* Start a workflow from a form, and follow the run it creates.
|
|
253
|
+
*
|
|
254
|
+
* @typeParam D - The workflow DEFINITION, which types both halves of the
|
|
255
|
+
* submission: `submit(input)` takes what the workflow's schema parses to, and
|
|
256
|
+
* `run.status === "completed"` narrows to a typed `run.output`.
|
|
257
|
+
*
|
|
258
|
+
* It used to be the OUTPUT type alone, and the asymmetry was the bug: a page
|
|
259
|
+
* already wrote `WorkflowOutputOf<typeof digest>` to get the output, while
|
|
260
|
+
* `submit` took `unknown`, so `submit({ ur1: 42 })` compiled and arrived as a
|
|
261
|
+
* 400 in the browser. Naming the def instead types the input from the same
|
|
262
|
+
* declaration — and `import type` is ERASED, so it costs the bundle nothing.
|
|
263
|
+
* Passing an output type where a def belongs is now a compile error rather
|
|
264
|
+
* than a silent loss of typing, which is the point.
|
|
265
|
+
*
|
|
266
|
+
* @example
|
|
267
|
+
* ```tsx no-check
|
|
268
|
+
* import { Form, SubmitButton, TextField, useWorkflowSubmit } from "@alexkroman1/aai-ui";
|
|
269
|
+
* import type { digest } from "./agent.ts";
|
|
270
|
+
*
|
|
271
|
+
* function DigestForm() {
|
|
272
|
+
* const { submit, run, pending, error } = useWorkflowSubmit<typeof digest>("digest");
|
|
273
|
+
* return (
|
|
274
|
+
* <Form onSubmit={(values) => submit(values)} error={error}>
|
|
275
|
+
* <TextField name="url" label="Link" type="url" required />
|
|
276
|
+
* <SubmitButton pending={pending}>Digest</SubmitButton>
|
|
277
|
+
* {run?.status === "completed" && <p>{run.output.title}</p>}
|
|
278
|
+
* </Form>
|
|
279
|
+
* );
|
|
280
|
+
* }
|
|
281
|
+
* ```
|
|
282
|
+
*
|
|
283
|
+
* @public
|
|
284
|
+
*/
|
|
285
|
+
export declare function useWorkflowSubmit<D extends AnyWorkflowDef>(workflow: string, options?: UseWorkflowSubmitOptions): WorkflowSubmission<WorkflowOutputOf<D>, SubmitInputOf<D>>;
|