@alexkroman1/aai-ui 0.12.3 → 0.14.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 +253 -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 +34 -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-mpgipo2Q.js +1 -0
- package/dist/default-client/assets/capture-processor-D0q7Qrdd.js +90 -0
- package/dist/default-client/assets/index-8ihb1Urm.css +2 -0
- package/dist/default-client/assets/index-DPfmSFmc.js +126 -0
- package/dist/default-client/assets/playback-processor-CjDbc9iH.js +314 -0
- package/dist/default-client/favicon.ico +0 -0
- package/dist/default-client/index.html +15 -0
- package/dist/default-client-entry.d.ts +6 -0
- package/dist/default-client.d.ts +19 -0
- package/dist/define-client.d.ts +169 -26
- package/dist/define-client.js +209 -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 +37 -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 +39 -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-D9jM5-TF.js +2140 -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 +59 -32
- package/styles.css +152 -30
- package/dist/_jsdom-setup.d.ts +0 -0
- 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
package/README.md
ADDED
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
# @alexkroman1/aai-ui
|
|
2
|
+
|
|
3
|
+
The browser client for AssemblyAI Agent SDK agents: React 19 hooks and
|
|
4
|
+
components over a framework-agnostic session core (WebSocket + microphone +
|
|
5
|
+
playback).
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
npm i @alexkroman1/aai-ui react react-dom
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Every agent gets this UI for free — `aai dev` and deployed agents serve a
|
|
12
|
+
default client built from this package. Install it directly when the agent has
|
|
13
|
+
its own `client.tsx`.
|
|
14
|
+
|
|
15
|
+
## UI integration
|
|
16
|
+
|
|
17
|
+
The hooks here are what a page reads an agent with: the live call, the state the
|
|
18
|
+
agent projects, the tool calls as they run, and the durable runs it started.
|
|
19
|
+
**There is no route to write.** The agent server already serves the session
|
|
20
|
+
socket and the workflow HTTP API, so a component talks to a live agent with no
|
|
21
|
+
glue file in between — a client is one `client.tsx` calling one mount.
|
|
22
|
+
|
|
23
|
+
**Two front doors, two mounts.** A voice agent's page calls `mountClient()` and
|
|
24
|
+
talks to a live session. A workflow app's page (`workflowApp()`, or
|
|
25
|
+
`agent({ mode: "workflow-app" })`) calls `mountPage()` and talks to the workflow
|
|
26
|
+
HTTP API — no session, no socket, no microphone. Both are still `client.tsx`,
|
|
27
|
+
still React, still the same theme tokens.
|
|
28
|
+
|
|
29
|
+
### Agent `agent.ts`
|
|
30
|
+
|
|
31
|
+
The session's state lives in a slot, and `syncState` is what the browser sees —
|
|
32
|
+
pushed after every tool call, so the client never rebuilds it from events:
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
import { agent, sessionSlot } from "@alexkroman1/aai";
|
|
36
|
+
|
|
37
|
+
const desk = sessionSlot("desk", () => ({ symbols: [] as string[] }), {
|
|
38
|
+
view: (state) => ({ symbols: state.symbols }),
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
export default agent({
|
|
42
|
+
name: "Market Desk",
|
|
43
|
+
systemPrompt: "You look up quotes and keep the caller's watchlist.",
|
|
44
|
+
syncState: desk.projected,
|
|
45
|
+
});
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Tool `tools/get_quote.ts`
|
|
49
|
+
|
|
50
|
+
A tool is a **file**, named by its own filename. Nothing registers it, and
|
|
51
|
+
`agent()` takes no `tools` field:
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
import { tool } from "@alexkroman1/aai";
|
|
55
|
+
import { z } from "zod";
|
|
56
|
+
|
|
57
|
+
export default tool({
|
|
58
|
+
description: "Look up the latest price for one ticker symbol",
|
|
59
|
+
inputSchema: z.object({ symbol: z.string() }),
|
|
60
|
+
execute: async ({ symbol }, ctx) => {
|
|
61
|
+
const res = await fetch(`https://api.example.com/quote/${symbol}`, {
|
|
62
|
+
headers: { authorization: `Bearer ${ctx.env.QUOTES_API_KEY}` },
|
|
63
|
+
signal: ctx.signal,
|
|
64
|
+
});
|
|
65
|
+
return (await res.json()) as { symbol: string; price: number };
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### UI component for a tool `quote-card.tsx`
|
|
71
|
+
|
|
72
|
+
`useToolResult` fires once per settled call of one named tool, with the result
|
|
73
|
+
already parsed. A component that mounts late still receives the results of calls
|
|
74
|
+
that finished before it — a result is a value the UI is driven from, not a
|
|
75
|
+
moment:
|
|
76
|
+
|
|
77
|
+
```tsx
|
|
78
|
+
import { useState } from "react";
|
|
79
|
+
import { useToolResult } from "@alexkroman1/aai-ui";
|
|
80
|
+
|
|
81
|
+
type Quote = { symbol: string; price: number };
|
|
82
|
+
|
|
83
|
+
export default function QuoteCard() {
|
|
84
|
+
const [quote, setQuote] = useState<Quote>();
|
|
85
|
+
useToolResult<Quote>("get_quote", (result) => setQuote(result));
|
|
86
|
+
|
|
87
|
+
if (!quote) return null;
|
|
88
|
+
return (
|
|
89
|
+
<p>
|
|
90
|
+
{quote.symbol} — {quote.price.toFixed(2)}
|
|
91
|
+
</p>
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
`useToolCallStart` is the same hook for the other end of the call — the pending
|
|
97
|
+
invocation and its arguments, for a spinner rather than a value. Either one
|
|
98
|
+
takes the tool's own shape as a type argument, derived from the tool module by a
|
|
99
|
+
**type-only** import that is erased and so pulls no server code into the bundle:
|
|
100
|
+
`useToolResult<InferToolOutput<typeof getQuote>>(…)` for the result,
|
|
101
|
+
`useToolCallStart<InferToolInput<typeof getQuote>>(…)` for the arguments.
|
|
102
|
+
|
|
103
|
+
### Client `client.tsx`
|
|
104
|
+
|
|
105
|
+
`mountClient()` mounts the default chat shell — start screen, transcript,
|
|
106
|
+
controls — with your components in it. `sidebar` takes the COMPONENT, not an
|
|
107
|
+
element:
|
|
108
|
+
|
|
109
|
+
```tsx no-check
|
|
110
|
+
import "@alexkroman1/aai-ui/styles.css";
|
|
111
|
+
import { mountClient, useAgentState } from "@alexkroman1/aai-ui";
|
|
112
|
+
import QuoteCard from "./quote-card.tsx";
|
|
113
|
+
|
|
114
|
+
function Watchlist() {
|
|
115
|
+
// The `desk` slot `syncState` projected, live. Null until the first push,
|
|
116
|
+
// which is a moment the UI has to render.
|
|
117
|
+
const desk = useAgentState<{ symbols: string[] }>("desk");
|
|
118
|
+
return (
|
|
119
|
+
<div>
|
|
120
|
+
<QuoteCard />
|
|
121
|
+
{desk?.symbols.map((symbol) => <div key={symbol}>{symbol}</div>)}
|
|
122
|
+
</div>
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
mountClient({
|
|
127
|
+
name: "Market Desk",
|
|
128
|
+
sidebar: Watchlist,
|
|
129
|
+
// Icon and label per tool, for the transcript's tool rows.
|
|
130
|
+
tools: { get_quote: { icon: "📈", label: "Fetching quote" } },
|
|
131
|
+
theme: { primary: "#2f9e44" },
|
|
132
|
+
});
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
The type argument above restates a shape the agent already knows. Move the slot
|
|
136
|
+
and its projection into a `shared.ts` both ends import, pass the projection
|
|
137
|
+
itself — `useAgentState(deskProjection)` — and the state is typed from the
|
|
138
|
+
projection and the pre-first-push frame derived from it, so there is nothing to
|
|
139
|
+
restate and no branch for the first render.
|
|
140
|
+
|
|
141
|
+
Pass `component` instead of `sidebar` to replace the whole shell. It renders
|
|
142
|
+
inside the same providers, so every hook here works in it unchanged.
|
|
143
|
+
|
|
144
|
+
## A workflow app
|
|
145
|
+
|
|
146
|
+
`mountPage()` mounts a form over the agent's workflows and installs no session.
|
|
147
|
+
`<WorkflowFields>` renders one control per scalar property of the workflow's own
|
|
148
|
+
input schema, so adding a field to the schema adds it to the page:
|
|
149
|
+
|
|
150
|
+
```tsx
|
|
151
|
+
import "@alexkroman1/aai-ui/styles.css";
|
|
152
|
+
import {
|
|
153
|
+
Form,
|
|
154
|
+
mountPage,
|
|
155
|
+
SubmitButton,
|
|
156
|
+
UploadProgressBar,
|
|
157
|
+
useWorkflowSubmit,
|
|
158
|
+
WorkflowFields,
|
|
159
|
+
WorkflowProgress,
|
|
160
|
+
} from "@alexkroman1/aai-ui";
|
|
161
|
+
|
|
162
|
+
function App() {
|
|
163
|
+
const { submitForm, run, pending, upload, error } =
|
|
164
|
+
useWorkflowSubmit("digest");
|
|
165
|
+
return (
|
|
166
|
+
<main className="mx-auto flex max-w-2xl flex-col gap-6 p-8">
|
|
167
|
+
<Form onSubmit={submitForm} error={error}>
|
|
168
|
+
<WorkflowFields workflow="digest" />
|
|
169
|
+
<SubmitButton pending={pending}>Summarize</SubmitButton>
|
|
170
|
+
</Form>
|
|
171
|
+
{/* The upload is its own wait: the run does not exist until the bytes
|
|
172
|
+
are in, so nothing else on the page can describe it. */}
|
|
173
|
+
<UploadProgressBar upload={upload} />
|
|
174
|
+
{/* What the run has SAID, from `stepReport()` in its steps. */}
|
|
175
|
+
<WorkflowProgress runId={run?.runId} />
|
|
176
|
+
{run?.status === "completed" && <pre>{JSON.stringify(run.output)}</pre>}
|
|
177
|
+
</main>
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
mountPage({ name: "Digest", component: App });
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
Naming the workflow's def — `useWorkflowSubmit<typeof digest>("digest")`, off a
|
|
185
|
+
**type-only** import of `agent.ts` — is what makes `submit()` take the declared
|
|
186
|
+
input and `run.status === "completed"` narrow to a typed `run.output`. The
|
|
187
|
+
import is erased, so it pulls no server code into the bundle.
|
|
188
|
+
|
|
189
|
+
## What is in the package
|
|
190
|
+
|
|
191
|
+
The [API reference](https://alexkroman.github.io/agent/) groups by TypeScript
|
|
192
|
+
kind. This is the same surface grouped by what it is for.
|
|
193
|
+
|
|
194
|
+
**Mounts** — `mountClient()`, `mountPage()`, the config each takes and the
|
|
195
|
+
handle each returns (`ClientConfig`, `ClientHandle`, `PageConfig`,
|
|
196
|
+
`PageHandle`). `fetchClientConfig()` reads the agent's declared
|
|
197
|
+
`name`/`greeting` on a page, which `mountClient()` does for itself.
|
|
198
|
+
|
|
199
|
+
**The live call** — `useSession()` for the whole snapshot plus the controls
|
|
200
|
+
(`start`, `toggle`, `cancel`, `reset`, `resetState`, `restart`, `disconnect`,
|
|
201
|
+
`end`); `useSessionSelector()` for one slice, with `useSessionStatus()`,
|
|
202
|
+
`useSessionError()` and `useSessionActions()` as the narrow reads a custom
|
|
203
|
+
chrome repeats; `useUserTranscript()` for the caller's in-progress turn, which
|
|
204
|
+
keeps `null` (silent) and `""` (speech, no words yet) apart; `useConversation()`
|
|
205
|
+
for the interleaved transcript with nothing rendered. `createBrowserSession()`
|
|
206
|
+
is the same session as a plain store with an immutable snapshot per change, for
|
|
207
|
+
a non-React client. `SessionSnapshot`, `AgentState`, `ChatMessage`,
|
|
208
|
+
`ToolCallInfo`, `SessionError` and `SessionErrorCode` come with it.
|
|
209
|
+
|
|
210
|
+
**What the agent pushes** — `useAgentState(slot.projected)` (that slot's entry
|
|
211
|
+
in the agent's `syncState`; `selectAgentState(slot)` for `useSessionSelector`),
|
|
212
|
+
`useToolResult()` / `useToolCallStart()` (tool calls as they run), and
|
|
213
|
+
`useEvent()` (custom events from `ctx.send`, and the one to prefer in new code
|
|
214
|
+
over reading tool results).
|
|
215
|
+
|
|
216
|
+
**Chat chrome** — `ChatView` is the whole surface; `ConsoleShell`,
|
|
217
|
+
`MessageList`, `Controls`, `Markdown`, `ToolCallRow`, `SessionErrorBanner`,
|
|
218
|
+
`StartScreen`, `SidebarLayout`, `AutoScroll`, `BulletList`, `Facts` and `Button`
|
|
219
|
+
are its parts, exported so a custom chrome reuses them rather than approximating
|
|
220
|
+
them. `AutoScroll` is the one to reach for directly: it is the only
|
|
221
|
+
scroll-pinning implementation here, and it needs a **bounded height**.
|
|
222
|
+
`AGENT_STATE_LABELS` and `WORKFLOW_STATUS_LABELS` are the default state words,
|
|
223
|
+
so a chrome overrides the one term it has a better word for.
|
|
224
|
+
|
|
225
|
+
**Forms** — `Form` collects typed values off the DOM on submit. `Field`,
|
|
226
|
+
`TextField`, `NumberField`, `TextAreaField`, `SelectField`, `CheckboxField`,
|
|
227
|
+
`FileField` and `SubmitButton` are the controls; `WorkflowFields` generates them
|
|
228
|
+
from a schema. A plain `<input name="x">` works identically.
|
|
229
|
+
|
|
230
|
+
**Workflows** — `createWorkflowApi()` is the client; `useWorkflowSubmit()` /
|
|
231
|
+
`useWorkflowStream()` (start one and watch it), `useWorkflowRun()` (watch one by
|
|
232
|
+
id), `useWorkflowRuns()` (the history), `useWorkflowProgress()` (what a run has
|
|
233
|
+
written), `useWorkflows()` (the listing), `useDownloadUrl()` (an upload a run
|
|
234
|
+
produced, as a URL a DOM element accepts), `useRunKey()` (the storage-backed key
|
|
235
|
+
a submission is recovered by). `<WorkflowProgress>` and `<UploadProgressBar>`
|
|
236
|
+
are the rendered halves. `WorkflowRun`, `WorkflowSummary`, `WorkflowInputOf`,
|
|
237
|
+
`WorkflowOutputOf` and `isTerminal()` are the vocabulary.
|
|
238
|
+
|
|
239
|
+
**Theme** — `useTheme()` and `ClientTheme`.
|
|
240
|
+
|
|
241
|
+
## Other subpaths
|
|
242
|
+
|
|
243
|
+
The root export is the whole client API. Two subpaths sit beside it, neither of
|
|
244
|
+
them something a `client.tsx` reaches for:
|
|
245
|
+
|
|
246
|
+
| Subpath | Reach for it when |
|
|
247
|
+
| ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
248
|
+
| `/client-dir` | serving the prebuilt default client from Node — `defaultClientDir()`, the filesystem path `createServerForRuntime({ clientDir })` wants |
|
|
249
|
+
| `/internal` | never, from application code: the plumbing `mountClient()` installs for itself (the session and theme providers, the default shell's URL chips, the tool-config context, the pre-connection lookup). Not a public API and not covered by semver |
|
|
250
|
+
|
|
251
|
+
## Documentation
|
|
252
|
+
|
|
253
|
+
Full API reference: <https://alexkroman.github.io/agent/>
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
//#region src/components/_colors.ts
|
|
2
|
+
/**
|
|
3
|
+
* Shared tints used by the default components (AssemblyAI design system,
|
|
4
|
+
* "website refresh": warm neutrals over the cream/white surfaces).
|
|
5
|
+
*
|
|
6
|
+
* These sit on top of the {@link ClientTheme} colors, which own the opaque
|
|
7
|
+
* palette. The neutral text steps are **derived from the theme** rather than
|
|
8
|
+
* hardcoded: `ClientTheme` documents `bg`/`surface`/`text` as free-form
|
|
9
|
+
* colors, so a client is free to hand the components a dark ground — and a
|
|
10
|
+
* fixed warm-grey scale tuned for cream leaves the AGENT/TOOL labels, every
|
|
11
|
+
* tool-call args preview, the chevron and both URL chips below WCAG AA while
|
|
12
|
+
* the prose and bubbles around them follow the theme correctly. Measured on a
|
|
13
|
+
* plausible dark theme before this was derived: 14 text nodes under 4.5:1
|
|
14
|
+
* (the faint step at 3.2–3.5:1, the muted step at 2.5:1), against zero on the
|
|
15
|
+
* default light theme.
|
|
16
|
+
*
|
|
17
|
+
* `ERROR_COLOR` and `THINKING_COLOR` stay fixed: they are *semantic* colors
|
|
18
|
+
* whose hue carries the meaning, so they cannot be re-derived from a theme
|
|
19
|
+
* that knows nothing about severity.
|
|
20
|
+
*/
|
|
21
|
+
/**
|
|
22
|
+
* Blend `text` into `surface` by `pct`, giving a neutral step that stays on
|
|
23
|
+
* the theme's own ink/ground axis. On a light theme this walks *down* from
|
|
24
|
+
* the surface toward the ink; on a dark one it walks *up* from the ground
|
|
25
|
+
* toward the light ink — the same call reads correctly either way, which is
|
|
26
|
+
* the whole reason to derive rather than hardcode.
|
|
27
|
+
*/
|
|
28
|
+
function inkTint(text, surface, pct) {
|
|
29
|
+
return mixInto(text, surface, pct);
|
|
30
|
+
}
|
|
31
|
+
/** Error red tuned for warm light surfaces. */
|
|
32
|
+
const ERROR_COLOR = "#B3261E";
|
|
33
|
+
/** Amber used by the "thinking" state indicator. */
|
|
34
|
+
const THINKING_COLOR = "#B98900";
|
|
35
|
+
/**
|
|
36
|
+
* Derive the user-bubble fill/edge from the theme primary (the mock's
|
|
37
|
+
* indigo-50 background with an indigo-100 border), so custom themes keep a
|
|
38
|
+
* coherent tinted bubble instead of a hardcoded indigo.
|
|
39
|
+
*/
|
|
40
|
+
function primaryTint(primary, surface, pct) {
|
|
41
|
+
return mixInto(primary, surface, pct);
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* The blend itself — `pct` of `color` over `ground`, in sRGB.
|
|
45
|
+
*
|
|
46
|
+
* One definition rather than one per axis: {@link inkTint} and
|
|
47
|
+
* {@link primaryTint} differ in WHICH theme color they walk from, which is what
|
|
48
|
+
* their names carry, and not in how the walk is spelled. Two copies of the
|
|
49
|
+
* `color-mix()` string are two places a change of color space has to land.
|
|
50
|
+
*/
|
|
51
|
+
function mixInto(color, ground, pct) {
|
|
52
|
+
return `color-mix(in srgb, ${color} ${pct}%, ${ground})`;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* The focus ring, as the class half of a pair.
|
|
56
|
+
*
|
|
57
|
+
* The ring is the only thing a keyboard user gets, and it has regressed to
|
|
58
|
+
* invisible twice — `outline-none` shipped here with no replacement, on the
|
|
59
|
+
* button and then on the URL chips, which is WCAG 2.4.7 on every control in the
|
|
60
|
+
* default client. Written as a SHORTHAND so it cannot be undone by Tailwind's
|
|
61
|
+
* shared `--tw-outline-style` variable.
|
|
62
|
+
*
|
|
63
|
+
* It is inert on its own: an outline with no colour draws nothing, so a control
|
|
64
|
+
* using this must also spread {@link focusRingStyle}. Naming the two together
|
|
65
|
+
* is what stops the next control shipping one without the other.
|
|
66
|
+
*/
|
|
67
|
+
const FOCUS_RING = "outline-none focus-visible:[outline:2px_solid] focus-visible:[outline-offset:2px]";
|
|
68
|
+
/**
|
|
69
|
+
* The style half of {@link FOCUS_RING} — the colour the outline draws in.
|
|
70
|
+
*
|
|
71
|
+
* A real CSS property rather than a variant class, so the `focus-visible` rule
|
|
72
|
+
* only has to turn the outline on.
|
|
73
|
+
*/
|
|
74
|
+
function focusRingStyle(primary) {
|
|
75
|
+
return { outlineColor: primary };
|
|
76
|
+
}
|
|
77
|
+
//#endregion
|
|
78
|
+
export { inkTint as a, focusRingStyle as i, FOCUS_RING as n, primaryTint as o, THINKING_COLOR as r, ERROR_COLOR as t };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
//#region src/worklets/_module-url.ts
|
|
2
|
+
/**
|
|
3
|
+
* Blob-URL module for an inlined AudioWorklet processor source.
|
|
4
|
+
*
|
|
5
|
+
* A blob URL rather than a data URI because the agent page's CSP allows
|
|
6
|
+
* `script-src blob:` but not `data:` — a data-URI module fails `addModule`
|
|
7
|
+
* with the opaque "Unable to load a worklet's module". Every inline worklet
|
|
8
|
+
* in this package must load through this helper so that constraint lives in
|
|
9
|
+
* one place.
|
|
10
|
+
*/
|
|
11
|
+
function workletModuleUrl(source) {
|
|
12
|
+
return URL.createObjectURL(new Blob([source], { type: "application/javascript" }));
|
|
13
|
+
}
|
|
14
|
+
//#endregion
|
|
15
|
+
export { workletModuleUrl as t };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
/**
|
|
3
|
+
* The default page: the agent, its workflows, and one form.
|
|
4
|
+
*
|
|
5
|
+
* A picker appears only when there is more than one workflow — an agent with a
|
|
6
|
+
* single workflow is the common case and a select with one option is chrome
|
|
7
|
+
* about nothing.
|
|
8
|
+
*/
|
|
9
|
+
export declare function DefaultPageShell({ name }: {
|
|
10
|
+
name?: string | undefined;
|
|
11
|
+
}): ReactNode;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Finding a run again when the page has lost its id.
|
|
3
|
+
*
|
|
4
|
+
* A run is durable and a page is not — which `useWorkflowRun`'s doc says, and
|
|
5
|
+
* which was only half true of the hooks above it: the run id lived in plain
|
|
6
|
+
* `useState`, so a refresh (or a same-tab navigation, or a crashed tab) left a
|
|
7
|
+
* live run with nothing anywhere able to name it. The run really did continue;
|
|
8
|
+
* the person really could not get back to it.
|
|
9
|
+
*
|
|
10
|
+
* `StartOptions.key` is the handle that survives that, and it always was — a
|
|
11
|
+
* caller's own name for a run, indexed by the agent, read back with
|
|
12
|
+
* `find(workflow, key)`. What was missing is the two lines that ASK. This is
|
|
13
|
+
* them, plus the four decisions they turn out to carry.
|
|
14
|
+
*
|
|
15
|
+
* ## It is a MOUNT-time act, not "whenever there is no run"
|
|
16
|
+
*
|
|
17
|
+
* The tempting spelling is "if we hold no run id, look one up", and it breaks
|
|
18
|
+
* `reset()`: a form put back to its initial state holds no run id, so the next
|
|
19
|
+
* pass would re-adopt the very run the person had just dismissed — a Clear
|
|
20
|
+
* button that clears nothing. So the lookup runs once per mount (and again only
|
|
21
|
+
* if the KEY changes, which is a different person's run), and every later
|
|
22
|
+
* absence of a run id is taken at face value.
|
|
23
|
+
*
|
|
24
|
+
* ## The lookup NEVER wins a race against a submit
|
|
25
|
+
*
|
|
26
|
+
* A person who reloads and immediately submits has started the run they want,
|
|
27
|
+
* and an answer that was already in flight names an older one. The caller
|
|
28
|
+
* therefore adopts through `current ?? found`: the recovered id fills an empty
|
|
29
|
+
* slot and never replaces a full one.
|
|
30
|
+
*
|
|
31
|
+
* ## A failed lookup is REPORTED
|
|
32
|
+
*
|
|
33
|
+
* The alternative is a page that quietly shows an empty form to somebody whose
|
|
34
|
+
* run is live, who then starts a second one — the duplicated work the key
|
|
35
|
+
* exists to prevent, and on a workflow app that is real money. A person who has
|
|
36
|
+
* never run anything pays a banner they can ignore. Same trade as
|
|
37
|
+
* `useWorkflows`, for the same reason: an empty answer here is a confident
|
|
38
|
+
* false statement.
|
|
39
|
+
*
|
|
40
|
+
* ## It is ON by default, and it used to be opt-in
|
|
41
|
+
*
|
|
42
|
+
* The argument for opt-in was that a `key` on its own means only "record this
|
|
43
|
+
* with the run" — which is what a voice agent's `ctx.workflows.start({ key })`
|
|
44
|
+
* means, there being no page to put a run back on. A FORM is the other case: it
|
|
45
|
+
* is the page, and losing the run is the thing it cannot recover from. Six of
|
|
46
|
+
* six page templates wrote `useRunKey()` and `recover: true` together, which is
|
|
47
|
+
* the same shape `session/resume-store.ts` names on the voice side — a default
|
|
48
|
+
* in the wrong place — so `useWorkflowSubmit` now mints the key and asks.
|
|
49
|
+
*
|
|
50
|
+
* `enabled` remains, because `recover: false` remains: a page whose form must
|
|
51
|
+
* always open empty says so, and then nothing here runs.
|
|
52
|
+
*/
|
|
53
|
+
import type { WorkflowApi } from "./workflow-client.ts";
|
|
54
|
+
/** What {@link useRecoveredRun} needs. */
|
|
55
|
+
export type RecoverRunOptions = {
|
|
56
|
+
/** The workflow whose runs are indexed under `key`. */
|
|
57
|
+
workflow: string;
|
|
58
|
+
/** The caller's handle on its own run. `useDefaultRunKey` always supplies one. */
|
|
59
|
+
key: string;
|
|
60
|
+
/** Whether the caller asked for this at all — `recover` at the call site. */
|
|
61
|
+
enabled: boolean;
|
|
62
|
+
/** The stable getter from `useWorkflowApiRef`. */
|
|
63
|
+
getClient: () => WorkflowApi;
|
|
64
|
+
/** Adopt this run. Called at most once, and never with an empty answer. */
|
|
65
|
+
onFound: (runId: string) => void;
|
|
66
|
+
/** The lookup failed, and the page has to say so. */
|
|
67
|
+
onError: (message: string) => void;
|
|
68
|
+
};
|
|
69
|
+
/**
|
|
70
|
+
* Look up the newest run for a key, once, as the component mounts.
|
|
71
|
+
*
|
|
72
|
+
* @param opts - See {@link RecoverRunOptions}.
|
|
73
|
+
* @returns Whether the lookup is still out. A caller folds it into its own
|
|
74
|
+
* `pending`, because a form offering Submit while a live run is arriving is a
|
|
75
|
+
* form inviting a second one.
|
|
76
|
+
*
|
|
77
|
+
* @internal
|
|
78
|
+
*/
|
|
79
|
+
export declare function useRecoveredRun(opts: RecoverRunOptions): boolean;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A bounded read, re-armed from the SETTLED read — the loop both workflow
|
|
3
|
+
* watchers are built out of.
|
|
4
|
+
*
|
|
5
|
+
* `pollUntilTerminal` (`use-workflow-run.ts`) and `readProgressUntilComplete`
|
|
6
|
+
* (`use-workflow-progress.ts`) each open a request, decide from its answer
|
|
7
|
+
* whether to come back, and stop when told to. What they had in common was not
|
|
8
|
+
* the decision — one reads a snapshot, the other drains an SSE body — but the
|
|
9
|
+
* scaffold around it, and that scaffold is where the two rules live:
|
|
10
|
+
*
|
|
11
|
+
* - **Re-armed from the settled read, never on an interval.** A slow response
|
|
12
|
+
* would otherwise stack overlapping requests on an agent that is already
|
|
13
|
+
* struggling, and on the platform every one of them BROKERS.
|
|
14
|
+
* - **Cancellation is a signal, not a flag.** The teardown has to reach the
|
|
15
|
+
* in-flight request too — an abandoned progress read otherwise keeps pulling
|
|
16
|
+
* chunks out of a run for a page that has navigated away — so `step` is
|
|
17
|
+
* handed the signal rather than being trusted to check a boolean.
|
|
18
|
+
*/
|
|
19
|
+
/**
|
|
20
|
+
* Call `step` until it reports it is finished, or until the returned stop
|
|
21
|
+
* function is called.
|
|
22
|
+
*
|
|
23
|
+
* `step` resolves `true` when there is nothing left to come back for. It is
|
|
24
|
+
* responsible for its own failures: a rejection would leave the loop stopped
|
|
25
|
+
* with nobody told, so each caller decides whether its failure is terminal or
|
|
26
|
+
* just another reason to try again.
|
|
27
|
+
*
|
|
28
|
+
* @internal
|
|
29
|
+
*/
|
|
30
|
+
export declare function repeatUntil(intervalMs: number, step: (signal: AbortSignal) => Promise<boolean>): () => void;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The two things a page does TO a run it started, bound to the run it has.
|
|
3
|
+
*
|
|
4
|
+
* `useWorkflowSubmit` and `useWorkflowStream` both hold a run id and neither
|
|
5
|
+
* handed it back, so a page that wanted "send it now" or "stop" had to hold an
|
|
6
|
+
* `api` of its own purely to write `api.wake(runId)` — which is the whole reason
|
|
7
|
+
* the two raw-primitive template pages keep a client at module scope. That is a
|
|
8
|
+
* page carrying the transport to make up for a hook withholding its own state.
|
|
9
|
+
*
|
|
10
|
+
* Both calls answer rather than fail when there is nothing to act on — `0`
|
|
11
|
+
* sleeps ended, `false` this call did not end it — which is the SDK's own
|
|
12
|
+
* contract for them (two tabs pressing Stop is ordinary), and it is what lets
|
|
13
|
+
* the no-run case be the same answer rather than a special one a caller has to
|
|
14
|
+
* branch on.
|
|
15
|
+
*/
|
|
16
|
+
import type { WorkflowApi } from "./workflow-client.ts";
|
|
17
|
+
/** What {@link useRunControls} returns — see {@link WorkflowSubmission}. */
|
|
18
|
+
export type RunControls = {
|
|
19
|
+
/** End the run's `sleep()` early; resolves how many sleeps it interrupted. */
|
|
20
|
+
wake: () => Promise<number>;
|
|
21
|
+
/** Stop the run; resolves whether this call is what ended it. */
|
|
22
|
+
cancel: () => Promise<boolean>;
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Bind `wake` and `cancel` to whatever run the hook is currently following.
|
|
26
|
+
*
|
|
27
|
+
* @param runId - The live run, or `undefined` before one exists.
|
|
28
|
+
* @param getClient - The stable getter from `useWorkflowApiRef`.
|
|
29
|
+
* @returns Two callbacks, stable while `runId` is.
|
|
30
|
+
*
|
|
31
|
+
* @internal
|
|
32
|
+
*/
|
|
33
|
+
export declare function useRunControls(runId: string | undefined, getClient: () => WorkflowApi): RunControls;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The state a form submission carries, for both hooks that make one.
|
|
3
|
+
*
|
|
4
|
+
* `useWorkflowSubmit` and `useWorkflowStream` stay separate hooks — they differ
|
|
5
|
+
* in WHEN the run is created relative to the bytes, which is the whole reason
|
|
6
|
+
* the streaming one exists — but everything around that ordering was written
|
|
7
|
+
* twice: the same four `useState`s, the same ref holding the live submission,
|
|
8
|
+
* the same prologue, the same superseded-submission guard, the same `reset`,
|
|
9
|
+
* and the same pause pair. Roughly forty-five lines and thirty of comment.
|
|
10
|
+
*
|
|
11
|
+
* The copy had already diverged in ways nobody chose, which is the argument for
|
|
12
|
+
* this module rather than for tidiness. What is here is only the scaffold; each
|
|
13
|
+
* hook still owns its own `submit` body.
|
|
14
|
+
*
|
|
15
|
+
* ## The superseded-submission rule
|
|
16
|
+
*
|
|
17
|
+
* A submission that has been replaced owns none of this state any more. Its
|
|
18
|
+
* walk unwinds AFTER the next one has already set `starting`, so clearing
|
|
19
|
+
* unconditionally in a `finally` would report the live submission as finished
|
|
20
|
+
* and drop the bar it is drawing. {@link SubmissionState.end} is that check,
|
|
21
|
+
* made once here instead of at each hook's own `finally`.
|
|
22
|
+
*/
|
|
23
|
+
import type { Dispatch, SetStateAction } from "react";
|
|
24
|
+
import type { UploadGate } from "./upload/index.ts";
|
|
25
|
+
import type { UploadStatus } from "./use-workflow-form.ts";
|
|
26
|
+
/**
|
|
27
|
+
* What a hook installs as its live submission.
|
|
28
|
+
*
|
|
29
|
+
* Generic over the token because the two hooks hold different things —
|
|
30
|
+
* `useWorkflowSubmit` an `UploadSession` (the walk needs its id maps),
|
|
31
|
+
* `useWorkflowStream` a bare gate in a wrapper — and all this module needs from
|
|
32
|
+
* either is the gate, which is what a supersede and a `reset` cancel.
|
|
33
|
+
*/
|
|
34
|
+
export type SubmissionToken = {
|
|
35
|
+
gate: UploadGate;
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* The stable half: everything a `submit` body calls.
|
|
39
|
+
*
|
|
40
|
+
* Split from the values deliberately. A `submit` is a `useCallback` and this is
|
|
41
|
+
* one of its dependencies, so a bag that changed identity whenever `upload`
|
|
42
|
+
* changed would rebuild `submit` — and with it the `onSubmit` handed to
|
|
43
|
+
* `<Form>` — on every progress report. Every member here is a `useState`
|
|
44
|
+
* setter or a `useCallback` with no changing dependency, so the bag is stable
|
|
45
|
+
* for the component's life.
|
|
46
|
+
*/
|
|
47
|
+
export type SubmissionActions<S extends SubmissionToken> = {
|
|
48
|
+
/**
|
|
49
|
+
* The full dispatch, not a plain setter: `useWorkflowSubmit`'s recovery
|
|
50
|
+
* lookup writes `(current) => current ?? found`, so that a run the person has
|
|
51
|
+
* already started this session is never displaced by one read back from the
|
|
52
|
+
* key index.
|
|
53
|
+
*/
|
|
54
|
+
setRunId: Dispatch<SetStateAction<string | undefined>>;
|
|
55
|
+
setStartError: Dispatch<SetStateAction<string | undefined>>;
|
|
56
|
+
setUpload: Dispatch<SetStateAction<UploadStatus | undefined>>;
|
|
57
|
+
/**
|
|
58
|
+
* Open a submission: supersede whatever was in flight and install `token`.
|
|
59
|
+
*
|
|
60
|
+
* Cancels the previous gate first, so a stale submission cannot park the new
|
|
61
|
+
* one, and clears the previous run id BEFORE the request rather than when it
|
|
62
|
+
* returns — a finished result sitting under a form that is already submitting
|
|
63
|
+
* again is the one wrong answer this can give, and it looks like a right one.
|
|
64
|
+
*/
|
|
65
|
+
begin: (token: S) => void;
|
|
66
|
+
/**
|
|
67
|
+
* Close `token`, if it is still the live one. See "The superseded-submission
|
|
68
|
+
* rule" above.
|
|
69
|
+
*/
|
|
70
|
+
end: (token: S) => void;
|
|
71
|
+
/** Put the form back: abandon the bytes and drop the result. */
|
|
72
|
+
reset: () => void;
|
|
73
|
+
/** Park the bytes and say so on the bar. */
|
|
74
|
+
pauseUpload: () => void;
|
|
75
|
+
/** Send the rest. */
|
|
76
|
+
resumeUpload: () => void;
|
|
77
|
+
};
|
|
78
|
+
/** What {@link useSubmissionState} hands back. */
|
|
79
|
+
export type SubmissionState<S extends SubmissionToken> = {
|
|
80
|
+
/** The run this submission started, once it has one. */
|
|
81
|
+
runId: string | undefined;
|
|
82
|
+
/** Whether a submission is in flight — the POST and the bytes, not the run. */
|
|
83
|
+
starting: boolean;
|
|
84
|
+
/** The submission's own failure, as against the run's. */
|
|
85
|
+
startError: string | undefined;
|
|
86
|
+
/** The bar's state, or nothing when there is no upload to describe. */
|
|
87
|
+
upload: UploadStatus | undefined;
|
|
88
|
+
/** See {@link SubmissionActions} for why these are their own bag. */
|
|
89
|
+
actions: SubmissionActions<S>;
|
|
90
|
+
};
|
|
91
|
+
/** The shared submission scaffold. See the module doc. */
|
|
92
|
+
export declare function useSubmissionState<S extends SubmissionToken>(): SubmissionState<S>;
|