@assistant-ui/mcp-docs-server 0.1.36 → 0.1.39
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/.docs/organized/code-examples/waterfall.md +8 -9
- package/.docs/organized/code-examples/with-a2a.md +16 -11
- package/.docs/organized/code-examples/with-ag-ui.md +16 -11
- package/.docs/organized/code-examples/{with-ai-sdk-v6.md → with-ai-sdk-v7.md} +32 -21
- package/.docs/organized/code-examples/with-artifacts.md +17 -10
- package/.docs/organized/code-examples/with-assistant-transport.md +15 -8
- package/.docs/organized/code-examples/with-browser-extension.md +15 -8
- package/.docs/organized/code-examples/with-chain-of-thought.md +17 -10
- package/.docs/organized/code-examples/with-cloud-standalone.md +10 -9
- package/.docs/organized/code-examples/with-cloud.md +18 -13
- package/.docs/organized/code-examples/with-custom-thread-list.md +17 -10
- package/.docs/organized/code-examples/with-elevenlabs-conversational.md +20 -13
- package/.docs/organized/code-examples/with-elevenlabs-scribe.md +20 -13
- package/.docs/organized/code-examples/with-eve.md +16 -9
- package/.docs/organized/code-examples/with-expo.md +26 -23
- package/.docs/organized/code-examples/with-external-store.md +16 -11
- package/.docs/organized/code-examples/with-ffmpeg.md +18 -13
- package/.docs/organized/code-examples/with-generative-ui.md +20 -15
- package/.docs/organized/code-examples/with-google-adk.md +16 -9
- package/.docs/organized/code-examples/with-heat-graph.md +8 -9
- package/.docs/organized/code-examples/with-image-generation.md +17 -10
- package/.docs/organized/code-examples/with-interactables.md +19 -15
- package/.docs/organized/code-examples/with-langchain.md +17 -10
- package/.docs/organized/code-examples/with-langgraph.md +17 -10
- package/.docs/organized/code-examples/with-livekit.md +21 -14
- package/.docs/organized/code-examples/with-mcp.md +40 -18
- package/.docs/organized/code-examples/with-opencode.md +22 -17
- package/.docs/organized/code-examples/with-pi.md +47 -12
- package/.docs/organized/code-examples/with-react-hook-form.md +19 -14
- package/.docs/organized/code-examples/with-react-ink-web.md +6 -6
- package/.docs/organized/code-examples/with-react-ink.md +2 -2
- package/.docs/organized/code-examples/with-react-router.md +20 -15
- package/.docs/organized/code-examples/with-resumable-stream.md +19 -12
- package/.docs/organized/code-examples/with-store.md +8 -9
- package/.docs/organized/code-examples/with-tanstack.md +17 -11
- package/.docs/organized/code-examples/with-tap-runtime.md +16 -11
- package/.docs/organized/code-examples/with-virtualized-thread.md +17 -12
- package/.docs/raw/docs/(docs)/base-ui.mdx +39 -0
- package/.docs/raw/docs/(docs)/cli.mdx +18 -2
- package/.docs/raw/docs/(docs)/installation.mdx +15 -1
- package/.docs/raw/docs/(docs)/rtl.mdx +2 -4
- package/.docs/raw/docs/(reference)/api-reference/generative-ui/actions.mdx +56 -0
- package/.docs/raw/docs/(reference)/api-reference/generative-ui/components.mdx +86 -0
- package/.docs/raw/docs/(reference)/api-reference/generative-ui/index.mdx +19 -1
- package/.docs/raw/docs/(reference)/api-reference/generative-ui/json-generative-ui.mdx +42 -0
- package/.docs/raw/docs/(reference)/api-reference/generative-ui/rendering.mdx +53 -2
- package/.docs/raw/docs/(reference)/api-reference/generative-ui/slack.mdx +81 -0
- package/.docs/raw/docs/(reference)/api-reference/generative-ui/teams.mdx +86 -0
- package/.docs/raw/docs/(reference)/api-reference/generative-ui/tokens.mdx +62 -0
- package/.docs/raw/docs/(reference)/api-reference/integrations/index.mdx +4 -1
- package/.docs/raw/docs/(reference)/api-reference/integrations/react-ai-sdk.mdx +4 -2
- package/.docs/raw/docs/(reference)/api-reference/integrations/react-data-stream.mdx +37 -0
- package/.docs/raw/docs/(reference)/api-reference/overview.mdx +1 -0
- package/.docs/raw/docs/(reference)/api-reference/primitives/assistant-if.mdx +29 -29
- package/.docs/raw/docs/(reference)/api-reference/primitives/composition.mdx +1 -0
- package/.docs/raw/docs/(reference)/api-reference/primitives/index.mdx +3 -0
- package/.docs/raw/docs/(reference)/api-reference/primitives/selection-toolbar.mdx +2 -0
- package/.docs/raw/docs/(reference)/api-reference/tools/status.mdx +0 -2
- package/.docs/raw/docs/(reference)/api-reference/tools/toolkits.mdx +12 -9
- package/.docs/raw/docs/(reference)/api-reference/utilities/miscellaneous.mdx +4 -0
- package/.docs/raw/docs/cloud/ai-sdk-assistant-ui.mdx +7 -1
- package/.docs/raw/docs/cloud/langgraph.mdx +4 -2
- package/.docs/raw/docs/guides/chatgpt-subscription.mdx +108 -0
- package/.docs/raw/docs/guides/dictation.mdx +185 -257
- package/.docs/raw/docs/guides/index.mdx +10 -0
- package/.docs/raw/docs/guides/mentions.mdx +31 -3
- package/.docs/raw/docs/guides/resumable-streams.mdx +12 -1
- package/.docs/raw/docs/guides/speech.mdx +47 -29
- package/.docs/raw/docs/guides/suggestions.mdx +70 -1
- package/.docs/raw/docs/guides/voice.mdx +197 -267
- package/.docs/raw/docs/ink/primitives.mdx +35 -1
- package/.docs/raw/docs/integrations/frameworks/ai-sdk.mdx +13 -7
- package/.docs/raw/docs/integrations/frameworks/cloudflare-agents/overview.mdx +3 -3
- package/.docs/raw/docs/integrations/frameworks/mastra/full-stack.mdx +1 -1
- package/.docs/raw/docs/integrations/frameworks/mastra/overview.mdx +3 -3
- package/.docs/raw/docs/integrations/frameworks/mastra/separate-server.mdx +1 -1
- package/.docs/raw/docs/integrations/gateways/index.mdx +2 -2
- package/.docs/raw/docs/integrations/index.mdx +1 -1
- package/.docs/raw/docs/integrations/observability/helicone.mdx +1 -1
- package/.docs/raw/docs/integrations/observability/langfuse.mdx +1 -1
- package/.docs/raw/docs/integrations/observability/langsmith.mdx +3 -3
- package/.docs/raw/docs/migrations/index.mdx +50 -0
- package/.docs/raw/docs/migrations/react-langgraph-v0-7.mdx +2 -2
- package/.docs/raw/docs/migrations/toolkit-tools.mdx +4 -2
- package/.docs/raw/docs/primitives/chain-of-thought.mdx +6 -1
- package/.docs/raw/docs/primitives/composer.mdx +16 -0
- package/.docs/raw/docs/primitives/selection-toolbar.mdx +25 -0
- package/.docs/raw/docs/primitives/thread-list.mdx +30 -6
- package/.docs/raw/docs/react-native/primitives.mdx +23 -0
- package/.docs/raw/docs/runtimes/a2a/quickstart.mdx +1 -1
- package/.docs/raw/docs/runtimes/ag-ui/agent-state.mdx +124 -0
- package/.docs/raw/docs/runtimes/ag-ui/quickstart.mdx +1 -1
- package/.docs/raw/docs/runtimes/ag-ui/runtime-options.mdx +13 -2
- package/.docs/raw/docs/runtimes/ai-sdk/overview.mdx +13 -4
- package/.docs/raw/docs/runtimes/ai-sdk/v4-legacy.mdx +11 -11
- package/.docs/raw/docs/runtimes/ai-sdk/v5-legacy.mdx +5 -5
- package/.docs/raw/docs/runtimes/ai-sdk/{v6.mdx → v6-legacy.mdx} +8 -6
- package/.docs/raw/docs/runtimes/ai-sdk/v7.mdx +717 -0
- package/.docs/raw/docs/runtimes/concepts/adapters.mdx +1 -1
- package/.docs/raw/docs/runtimes/concepts/architecture.mdx +1 -1
- package/.docs/raw/docs/runtimes/custom/data-stream.mdx +10 -19
- package/.docs/raw/docs/runtimes/custom/external-store.mdx +1 -1
- package/.docs/raw/docs/runtimes/custom/local-runtime.mdx +2 -0
- package/.docs/raw/docs/runtimes/eve/overview.mdx +1 -1
- package/.docs/raw/docs/runtimes/google-adk/quickstart.mdx +1 -1
- package/.docs/raw/docs/runtimes/langchain.mdx +2 -2
- package/.docs/raw/docs/runtimes/langgraph/agent-state.mdx +181 -0
- package/.docs/raw/docs/runtimes/opencode/hooks.mdx +1 -1
- package/.docs/raw/docs/tools/backend.mdx +6 -3
- package/.docs/raw/docs/tools/defining-tools.mdx +12 -1
- package/.docs/raw/docs/tools/generative-ui.mdx +95 -0
- package/.docs/raw/docs/tools/mcp-apps.mdx +37 -10
- package/.docs/raw/docs/tools/mcp.mdx +105 -3
- package/.docs/raw/docs/tools/tool-ui.mdx +6 -4
- package/.docs/raw/docs/tools/user-managed-mcp.mdx +41 -4
- package/.docs/raw/docs/ui/accordion.mdx +16 -10
- package/.docs/raw/docs/ui/assistant-modal.mdx +8 -4
- package/.docs/raw/docs/ui/attachment.mdx +5 -1
- package/.docs/raw/docs/ui/badge.mdx +23 -12
- package/.docs/raw/docs/ui/follow-up-suggestions.mdx +80 -0
- package/.docs/raw/docs/ui/model-selector.mdx +69 -6
- package/.docs/raw/docs/ui/select.mdx +22 -14
- package/.docs/raw/docs/ui/sources.mdx +1 -1
- package/.docs/raw/docs/ui/tabs.mdx +25 -14
- package/.docs/raw/docs/utilities/heat-graph.mdx +2 -2
- package/README.md +1 -1
- package/dist/constants.d.ts +2 -1
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +2 -1
- package/dist/constants.js.map +1 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +51 -0
- package/dist/index.js.map +1 -1
- package/dist/prepare-docs/code-examples.d.ts.map +1 -1
- package/dist/prepare-docs/copy-raw.d.ts.map +1 -1
- package/dist/prepare-docs/copy-raw.js +0 -4
- package/dist/prepare-docs/copy-raw.js.map +1 -1
- package/dist/prompts/xulux-playground.d.ts +12 -0
- package/dist/prompts/xulux-playground.d.ts.map +1 -0
- package/dist/prompts/xulux-playground.js +33 -0
- package/dist/prompts/xulux-playground.js.map +1 -0
- package/dist/tools/docs.d.ts +2 -4
- package/dist/tools/docs.d.ts.map +1 -1
- package/dist/tools/docs.js +38 -10
- package/dist/tools/docs.js.map +1 -1
- package/dist/tools/examples.d.ts +5 -5
- package/dist/tools/examples.d.ts.map +1 -1
- package/dist/tools/examples.js +10 -7
- package/dist/tools/examples.js.map +1 -1
- package/dist/tools/resources.d.ts +6 -0
- package/dist/tools/resources.d.ts.map +1 -0
- package/dist/tools/resources.js +74 -0
- package/dist/tools/resources.js.map +1 -0
- package/dist/tools/search.d.ts +30 -0
- package/dist/tools/search.d.ts.map +1 -0
- package/dist/tools/search.js +39 -0
- package/dist/tools/search.js.map +1 -0
- package/dist/tools/tests/test-setup.d.ts.map +1 -1
- package/dist/tools/tests/test-setup.js +7 -1
- package/dist/tools/tests/test-setup.js.map +1 -1
- package/dist/tools/xulux-templates.d.ts +72 -0
- package/dist/tools/xulux-templates.d.ts.map +1 -0
- package/dist/tools/xulux-templates.js +82 -0
- package/dist/tools/xulux-templates.js.map +1 -0
- package/dist/utils/cache.d.ts +5 -0
- package/dist/utils/cache.d.ts.map +1 -0
- package/dist/utils/cache.js +18 -0
- package/dist/utils/cache.js.map +1 -0
- package/dist/utils/logger.d.ts.map +1 -1
- package/dist/utils/mcp-format.d.ts +1 -0
- package/dist/utils/mcp-format.d.ts.map +1 -1
- package/dist/utils/mcp-format.js +7 -4
- package/dist/utils/mcp-format.js.map +1 -1
- package/dist/utils/mdx.d.ts +2 -1
- package/dist/utils/mdx.d.ts.map +1 -1
- package/dist/utils/mdx.js +19 -2
- package/dist/utils/mdx.js.map +1 -1
- package/dist/utils/paths.d.ts +2 -1
- package/dist/utils/paths.d.ts.map +1 -1
- package/dist/utils/paths.js +20 -1
- package/dist/utils/paths.js.map +1 -1
- package/dist/utils/search.d.ts +10 -0
- package/dist/utils/search.d.ts.map +1 -0
- package/dist/utils/search.js +97 -0
- package/dist/utils/search.js.map +1 -0
- package/dist/utils/security.d.ts.map +1 -1
- package/dist/xulux/catalog-client.d.ts +14 -0
- package/dist/xulux/catalog-client.d.ts.map +1 -0
- package/dist/xulux/catalog-client.js +67 -0
- package/dist/xulux/catalog-client.js.map +1 -0
- package/dist/xulux/fallback-catalog.d.ts +7 -0
- package/dist/xulux/fallback-catalog.d.ts.map +1 -0
- package/dist/xulux/fallback-catalog.js +47 -0
- package/dist/xulux/fallback-catalog.js.map +1 -0
- package/dist/xulux/fetch-sandbox.d.ts +5 -0
- package/dist/xulux/fetch-sandbox.d.ts.map +1 -0
- package/dist/xulux/fetch-sandbox.js +40 -0
- package/dist/xulux/fetch-sandbox.js.map +1 -0
- package/dist/xulux/template-service.d.ts +84 -0
- package/dist/xulux/template-service.d.ts.map +1 -0
- package/dist/xulux/template-service.js +223 -0
- package/dist/xulux/template-service.js.map +1 -0
- package/dist/xulux/types.d.ts +55 -0
- package/dist/xulux/types.d.ts.map +1 -0
- package/dist/xulux/types.js +6 -0
- package/dist/xulux/types.js.map +1 -0
- package/package.json +5 -5
- package/src/constants.ts +2 -0
- package/src/index.ts +67 -0
- package/src/prepare-docs/copy-raw.ts +0 -5
- package/src/prompts/xulux-playground.ts +36 -0
- package/src/tools/docs.ts +52 -4
- package/src/tools/examples.ts +18 -11
- package/src/tools/resources.ts +114 -0
- package/src/tools/search.ts +46 -0
- package/src/tools/tests/completions.test.ts +46 -0
- package/src/tools/tests/directory-size-cap.test.ts +50 -0
- package/src/tools/tests/docs.test.ts +20 -0
- package/src/tools/tests/examples.test.ts +5 -5
- package/src/tools/tests/listings-cache.test.ts +19 -0
- package/src/tools/tests/mcp-protocol.test.ts +92 -1
- package/src/tools/tests/resources.test.ts +102 -0
- package/src/tools/tests/search.test.ts +37 -0
- package/src/tools/tests/test-setup.ts +10 -0
- package/src/tools/tests/xulux-templates.test.ts +262 -0
- package/src/tools/xulux-templates.ts +141 -0
- package/src/utils/cache.ts +20 -0
- package/src/utils/mcp-format.ts +8 -6
- package/src/utils/mdx.ts +21 -1
- package/src/utils/paths.ts +25 -0
- package/src/utils/search.ts +131 -0
- package/src/utils/tests/cache.test.ts +51 -0
- package/src/utils/tests/mcp-format.test.ts +22 -0
- package/src/utils/tests/security.test.ts +1 -1
- package/src/xulux/catalog-client.ts +105 -0
- package/src/xulux/fallback-catalog.ts +63 -0
- package/src/xulux/fetch-sandbox.ts +56 -0
- package/src/xulux/template-service.ts +406 -0
- package/src/xulux/types.ts +60 -0
- package/.docs/raw/blog/2024-07-29-hello/index.mdx +0 -64
- package/.docs/raw/blog/2024-09-11/index.mdx +0 -10
- package/.docs/raw/blog/2024-12-15/index.mdx +0 -10
- package/.docs/raw/blog/2025-01-31-changelog/index.mdx +0 -127
- package/.docs/raw/blog/2026-03-launch-week/index.mdx +0 -258
- package/.docs/raw/docs/(reference)/api-reference/hooks/utilities.mdx +0 -464
|
@@ -6,19 +6,15 @@ platforms: ["react"]
|
|
|
6
6
|
|
|
7
7
|
import { DictationSample } from "@/components/docs/samples/dictation";
|
|
8
8
|
|
|
9
|
-
assistant-ui supports speech-to-text (dictation) via the `DictationAdapter` interface.
|
|
9
|
+
assistant-ui supports speech-to-text (dictation) via the `DictationAdapter` interface. Users speak into the microphone and transcribed text lands in the composer, either as interim preview or as committed final text.
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
## DictationAdapter
|
|
11
|
+
Dictation is the push-to-talk input mode. For a live duplex conversation, see [Realtime Voice](/docs/guides/voice). For reading messages aloud, see [Speech](/docs/guides/speech).
|
|
14
12
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
- `WebSpeechDictationAdapter`: Uses the browser's `Web Speech API` (SpeechRecognition)
|
|
13
|
+
<DictationSample />
|
|
18
14
|
|
|
19
|
-
|
|
15
|
+
## WebSpeechDictationAdapter
|
|
20
16
|
|
|
21
|
-
|
|
17
|
+
The zero-config built-in adapter uses the browser's Web Speech API (`SpeechRecognition` / `webkitSpeechRecognition`). It works in Chrome, Edge, and Safari. Check [browser compatibility](https://developer.mozilla.org/en-US/docs/Web/API/SpeechRecognition#browser_compatibility) for details.
|
|
22
18
|
|
|
23
19
|
```tsx
|
|
24
20
|
import { WebSpeechDictationAdapter } from "@assistant-ui/react";
|
|
@@ -26,49 +22,15 @@ import { WebSpeechDictationAdapter } from "@assistant-ui/react";
|
|
|
26
22
|
const runtime = useChatRuntime({
|
|
27
23
|
adapters: {
|
|
28
24
|
dictation: new WebSpeechDictationAdapter({
|
|
29
|
-
//
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
interimResults: true, // Return interim results (default: true)
|
|
25
|
+
language: "en-US", // default: browser language
|
|
26
|
+
continuous: true, // keep recording after pauses (default: true)
|
|
27
|
+
interimResults: true, // emit interim transcripts (default: true)
|
|
33
28
|
}),
|
|
34
29
|
},
|
|
35
30
|
});
|
|
36
31
|
```
|
|
37
32
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
The dictation feature uses `ComposerPrimitive.Dictate` and `ComposerPrimitive.StopDictation` components.
|
|
41
|
-
|
|
42
|
-
```tsx
|
|
43
|
-
import { AuiIf, ComposerPrimitive } from "@assistant-ui/react";
|
|
44
|
-
import { MicIcon, SquareIcon } from "lucide-react";
|
|
45
|
-
|
|
46
|
-
const ComposerWithDictation = () => (
|
|
47
|
-
<ComposerPrimitive.Root>
|
|
48
|
-
<ComposerPrimitive.Input />
|
|
49
|
-
|
|
50
|
-
{/* Show Dictate button when not dictating */}
|
|
51
|
-
<AuiIf condition={(s) => s.composer.dictation == null}>
|
|
52
|
-
<ComposerPrimitive.Dictate>
|
|
53
|
-
<MicIcon />
|
|
54
|
-
</ComposerPrimitive.Dictate>
|
|
55
|
-
</AuiIf>
|
|
56
|
-
|
|
57
|
-
{/* Show Stop button when dictating */}
|
|
58
|
-
<AuiIf condition={(s) => s.composer.dictation != null}>
|
|
59
|
-
<ComposerPrimitive.StopDictation>
|
|
60
|
-
<SquareIcon className="animate-pulse" />
|
|
61
|
-
</ComposerPrimitive.StopDictation>
|
|
62
|
-
</AuiIf>
|
|
63
|
-
|
|
64
|
-
<ComposerPrimitive.Send />
|
|
65
|
-
</ComposerPrimitive.Root>
|
|
66
|
-
);
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
## Browser Compatibility Check
|
|
70
|
-
|
|
71
|
-
You can check if the browser supports dictation:
|
|
33
|
+
You can gate the feature on browser support:
|
|
72
34
|
|
|
73
35
|
```tsx
|
|
74
36
|
import { WebSpeechDictationAdapter } from "@assistant-ui/react";
|
|
@@ -78,293 +40,259 @@ if (WebSpeechDictationAdapter.isSupported()) {
|
|
|
78
40
|
}
|
|
79
41
|
```
|
|
80
42
|
|
|
81
|
-
##
|
|
43
|
+
## DictationAdapter interface
|
|
82
44
|
|
|
83
|
-
|
|
45
|
+
Custom providers implement the same contract. The interface is defined in `@assistant-ui/core` and re-exported from `@assistant-ui/react`:
|
|
84
46
|
|
|
85
47
|
```tsx
|
|
86
48
|
import type { DictationAdapter } from "@assistant-ui/react";
|
|
87
49
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
disableInputDuringDictation
|
|
50
|
+
type DictationAdapter = {
|
|
51
|
+
listen: () => DictationAdapter.Session;
|
|
52
|
+
disableInputDuringDictation?: boolean;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
namespace DictationAdapter {
|
|
56
|
+
type Status =
|
|
57
|
+
| { type: "starting" | "running" }
|
|
58
|
+
| {
|
|
59
|
+
type: "ended";
|
|
60
|
+
reason: "stopped" | "cancelled" | "error";
|
|
61
|
+
};
|
|
91
62
|
|
|
92
|
-
|
|
63
|
+
type Result = {
|
|
64
|
+
transcript: string;
|
|
65
|
+
isFinal?: boolean;
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
type Session = {
|
|
69
|
+
status: Status;
|
|
70
|
+
stop: () => Promise<void>;
|
|
71
|
+
cancel: () => void;
|
|
72
|
+
onSpeechStart: (callback: () => void) => Unsubscribe;
|
|
73
|
+
onSpeechEnd: (callback: (result: Result) => void) => Unsubscribe;
|
|
74
|
+
onSpeech: (callback: (result: Result) => void) => Unsubscribe;
|
|
75
|
+
};
|
|
93
76
|
}
|
|
94
77
|
```
|
|
95
78
|
|
|
96
|
-
|
|
97
|
-
When a message is sent during an active dictation session, the session is automatically stopped.
|
|
98
|
-
</Callout>
|
|
99
|
-
|
|
100
|
-
## Custom Adapters
|
|
79
|
+
`listen()` starts a session. The session reports status, accepts `stop` / `cancel`, and emits speech events through the three `on*` methods (each returns an unsubscribe function).
|
|
101
80
|
|
|
102
|
-
|
|
81
|
+
### Interim vs final results
|
|
103
82
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
```tsx
|
|
107
|
-
import type { DictationAdapter } from "@assistant-ui/react";
|
|
83
|
+
The `onSpeech` callback receives results with an optional `isFinal` flag:
|
|
108
84
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
disableInputDuringDictation?: boolean;
|
|
85
|
+
- `isFinal: true` (or omitted): text is committed into the composer input.
|
|
86
|
+
- `isFinal: false`: text is shown as a preview that later interim results replace until a final result commits it.
|
|
112
87
|
|
|
113
|
-
|
|
114
|
-
// Return a session object that manages the dictation
|
|
115
|
-
return {
|
|
116
|
-
status: { type: "starting" },
|
|
88
|
+
Both interim and final results render in the input field, similar to native dictation on mobile.
|
|
117
89
|
|
|
118
|
-
|
|
119
|
-
// Stop recognition and finalize results
|
|
120
|
-
},
|
|
90
|
+
### Disabling input during dictation
|
|
121
91
|
|
|
122
|
-
|
|
123
|
-
// Cancel recognition without finalizing
|
|
124
|
-
},
|
|
92
|
+
Some services return cumulative transcripts that conflict with simultaneous typing. Set `disableInputDuringDictation` to block the text input while a session is active:
|
|
125
93
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
return () => {}; // Return unsubscribe function
|
|
129
|
-
},
|
|
94
|
+
```tsx
|
|
95
|
+
import type { DictationAdapter } from "@assistant-ui/react";
|
|
130
96
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
return () => {};
|
|
134
|
-
},
|
|
97
|
+
class MyAdapter implements DictationAdapter {
|
|
98
|
+
disableInputDuringDictation = true;
|
|
135
99
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
// callback({ transcript: "text", isFinal: true })
|
|
139
|
-
//
|
|
140
|
-
// isFinal: true → Append to composer input (default)
|
|
141
|
-
// isFinal: false → Show as preview only
|
|
142
|
-
return () => {};
|
|
143
|
-
},
|
|
144
|
-
};
|
|
100
|
+
listen() {
|
|
101
|
+
// ...
|
|
145
102
|
}
|
|
146
103
|
}
|
|
147
104
|
```
|
|
148
105
|
|
|
149
|
-
|
|
106
|
+
<Callout type="info">
|
|
107
|
+
When a message is sent during an active dictation session, the session is automatically stopped.
|
|
108
|
+
</Callout>
|
|
150
109
|
|
|
151
|
-
|
|
110
|
+
## UI: ComposerPrimitive.Dictate
|
|
111
|
+
|
|
112
|
+
The dictation trigger is `ComposerPrimitive.Dictate`. Pair it with `ComposerPrimitive.StopDictation` to end the session. Both live under `ComposerPrimitive` in `@assistant-ui/react`.
|
|
152
113
|
|
|
153
114
|
```tsx
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
115
|
+
import { AuiIf, ComposerPrimitive } from "@assistant-ui/react";
|
|
116
|
+
import { MicIcon, SquareIcon } from "lucide-react";
|
|
117
|
+
|
|
118
|
+
const ComposerWithDictation = () => (
|
|
119
|
+
<ComposerPrimitive.Root>
|
|
120
|
+
<ComposerPrimitive.Input />
|
|
121
|
+
|
|
122
|
+
<AuiIf condition={(s) => s.composer.dictation == null}>
|
|
123
|
+
<ComposerPrimitive.Dictate>
|
|
124
|
+
<MicIcon />
|
|
125
|
+
</ComposerPrimitive.Dictate>
|
|
126
|
+
</AuiIf>
|
|
127
|
+
|
|
128
|
+
<AuiIf condition={(s) => s.composer.dictation != null}>
|
|
129
|
+
<ComposerPrimitive.StopDictation>
|
|
130
|
+
<SquareIcon className="animate-pulse" />
|
|
131
|
+
</ComposerPrimitive.StopDictation>
|
|
132
|
+
</AuiIf>
|
|
133
|
+
|
|
134
|
+
<ComposerPrimitive.Send />
|
|
135
|
+
</ComposerPrimitive.Root>
|
|
136
|
+
);
|
|
160
137
|
```
|
|
161
138
|
|
|
162
|
-
|
|
139
|
+
`ComposerPrimitive.Dictate` is disabled when no dictation adapter is configured. For a separate interim transcript display, `ComposerPrimitive.DictationTranscript` and `composer.dictation?.transcript` are available.
|
|
163
140
|
|
|
164
|
-
|
|
141
|
+
## Server-side transcription
|
|
165
142
|
|
|
166
|
-
|
|
143
|
+
Browser speech recognition is convenient, but production apps often need higher accuracy, more languages, or a consistent model across devices. Implement a custom `DictationAdapter` that records audio with `MediaRecorder`, then POST the blob to a Next.js route that calls the AI SDK's `transcribe` API.
|
|
167
144
|
|
|
168
|
-
|
|
145
|
+
### API route
|
|
169
146
|
|
|
170
|
-
```
|
|
171
|
-
|
|
172
|
-
|
|
147
|
+
```ts title="app/api/transcribe/route.ts"
|
|
148
|
+
import { transcribe } from "ai";
|
|
149
|
+
import { openai } from "@ai-sdk/openai";
|
|
173
150
|
|
|
174
|
-
|
|
151
|
+
export async function POST(req: Request) {
|
|
152
|
+
const formData = await req.formData();
|
|
153
|
+
const audio = formData.get("audio");
|
|
175
154
|
|
|
176
|
-
|
|
155
|
+
if (!(audio instanceof Blob)) {
|
|
156
|
+
return Response.json({ error: "audio required" }, { status: 400 });
|
|
157
|
+
}
|
|
177
158
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
{
|
|
183
|
-
method: "POST",
|
|
184
|
-
headers: {
|
|
185
|
-
"xi-api-key": process.env.ELEVENLABS_API_KEY!,
|
|
186
|
-
},
|
|
187
|
-
}
|
|
188
|
-
);
|
|
159
|
+
const result = await transcribe({
|
|
160
|
+
model: openai.transcription("whisper-1"),
|
|
161
|
+
audio: new Uint8Array(await audio.arrayBuffer()),
|
|
162
|
+
});
|
|
189
163
|
|
|
190
|
-
|
|
191
|
-
return Response.json({ token: data.token });
|
|
164
|
+
return Response.json({ text: result.text });
|
|
192
165
|
}
|
|
193
166
|
```
|
|
194
167
|
|
|
195
|
-
|
|
168
|
+
`transcribe` is the stable export on the `ai` package. It accepts a transcription model and audio as `DataContent` (for example a `Uint8Array`) or a `URL`, and returns a `TranscriptionResult` whose `text` field is the full transcript.
|
|
196
169
|
|
|
197
|
-
|
|
170
|
+
### Adapter
|
|
171
|
+
|
|
172
|
+
```tsx title="lib/server-dictation-adapter.ts"
|
|
198
173
|
import type { DictationAdapter } from "@assistant-ui/react";
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
private tokenEndpoint: string;
|
|
203
|
-
private languageCode: string;
|
|
204
|
-
|
|
205
|
-
// ElevenLabs returns cumulative transcripts, so we disable typing during dictation
|
|
206
|
-
public disableInputDuringDictation: boolean;
|
|
207
|
-
|
|
208
|
-
constructor(options: {
|
|
209
|
-
tokenEndpoint: string;
|
|
210
|
-
languageCode?: string;
|
|
211
|
-
disableInputDuringDictation?: boolean;
|
|
212
|
-
}) {
|
|
213
|
-
this.tokenEndpoint = options.tokenEndpoint;
|
|
214
|
-
this.languageCode = options.languageCode ?? "en";
|
|
215
|
-
this.disableInputDuringDictation = options.disableInputDuringDictation ?? true;
|
|
216
|
-
}
|
|
174
|
+
|
|
175
|
+
export class ServerDictationAdapter implements DictationAdapter {
|
|
176
|
+
constructor(private endpoint = "/api/transcribe") {}
|
|
217
177
|
|
|
218
178
|
listen(): DictationAdapter.Session {
|
|
219
|
-
const
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
speech: new Set<(r: DictationAdapter.Result) => void>(),
|
|
223
|
-
};
|
|
179
|
+
const speechStart = new Set<() => void>();
|
|
180
|
+
const speechEnd = new Set<(r: DictationAdapter.Result) => void>();
|
|
181
|
+
const speech = new Set<(r: DictationAdapter.Result) => void>();
|
|
224
182
|
|
|
225
|
-
let
|
|
226
|
-
let
|
|
183
|
+
let mediaRecorder: MediaRecorder | null = null;
|
|
184
|
+
let stream: MediaStream | null = null;
|
|
185
|
+
let cancelled = false;
|
|
186
|
+
const chunks: BlobPart[] = [];
|
|
227
187
|
|
|
228
188
|
const session: DictationAdapter.Session = {
|
|
229
189
|
status: { type: "starting" },
|
|
230
190
|
|
|
231
191
|
stop: async () => {
|
|
232
|
-
|
|
233
|
-
connection.commit();
|
|
234
|
-
await new Promise((r) => setTimeout(r, 500));
|
|
235
|
-
connection.close();
|
|
236
|
-
}
|
|
237
|
-
if (fullTranscript) {
|
|
238
|
-
for (const cb of callbacks.end) cb({ transcript: fullTranscript });
|
|
239
|
-
}
|
|
192
|
+
mediaRecorder?.stop();
|
|
240
193
|
},
|
|
241
194
|
|
|
242
195
|
cancel: () => {
|
|
243
|
-
|
|
196
|
+
cancelled = true;
|
|
197
|
+
mediaRecorder?.stop();
|
|
198
|
+
stream?.getTracks().forEach((t) => t.stop());
|
|
199
|
+
session.status = { type: "ended", reason: "cancelled" };
|
|
244
200
|
},
|
|
245
201
|
|
|
246
202
|
onSpeechStart: (cb) => {
|
|
247
|
-
|
|
248
|
-
return () =>
|
|
203
|
+
speechStart.add(cb);
|
|
204
|
+
return () => speechStart.delete(cb);
|
|
249
205
|
},
|
|
250
|
-
|
|
251
206
|
onSpeechEnd: (cb) => {
|
|
252
|
-
|
|
253
|
-
return () =>
|
|
207
|
+
speechEnd.add(cb);
|
|
208
|
+
return () => speechEnd.delete(cb);
|
|
254
209
|
},
|
|
255
|
-
|
|
256
210
|
onSpeech: (cb) => {
|
|
257
|
-
|
|
258
|
-
return () =>
|
|
211
|
+
speech.add(cb);
|
|
212
|
+
return () => speech.delete(cb);
|
|
259
213
|
},
|
|
260
214
|
};
|
|
261
215
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
return session;
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
private async connect(
|
|
272
|
-
session: DictationAdapter.Session,
|
|
273
|
-
callbacks: {
|
|
274
|
-
start: Set<() => void>;
|
|
275
|
-
end: Set<(r: DictationAdapter.Result) => void>;
|
|
276
|
-
speech: Set<(r: DictationAdapter.Result) => void>;
|
|
277
|
-
},
|
|
278
|
-
refs: {
|
|
279
|
-
setConnection: (c: ReturnType<typeof Scribe.connect>) => void;
|
|
280
|
-
getFullTranscript: () => string;
|
|
281
|
-
setFullTranscript: (t: string) => void;
|
|
282
|
-
}
|
|
283
|
-
) {
|
|
284
|
-
try {
|
|
285
|
-
// 1. Get token from backend
|
|
286
|
-
const tokenRes = await fetch(this.tokenEndpoint, { method: "POST" });
|
|
287
|
-
const { token } = await tokenRes.json();
|
|
288
|
-
|
|
289
|
-
// 2. Connect to Scribe with microphone
|
|
290
|
-
const connection = Scribe.connect({
|
|
291
|
-
token,
|
|
292
|
-
modelId: "scribe_v2_realtime",
|
|
293
|
-
languageCode: this.languageCode,
|
|
294
|
-
microphone: {
|
|
295
|
-
echoCancellation: true,
|
|
296
|
-
noiseSuppression: true,
|
|
297
|
-
},
|
|
298
|
-
});
|
|
299
|
-
refs.setConnection(connection);
|
|
300
|
-
|
|
301
|
-
// 3. Handle events
|
|
302
|
-
connection.on(RealtimeEvents.SESSION_STARTED, () => {
|
|
303
|
-
(session as { status: DictationAdapter.Status }).status = {
|
|
304
|
-
type: "running",
|
|
305
|
-
};
|
|
306
|
-
for (const cb of callbacks.start) cb();
|
|
307
|
-
});
|
|
308
|
-
|
|
309
|
-
// Partial transcripts → preview (isFinal: false)
|
|
310
|
-
connection.on(RealtimeEvents.PARTIAL_TRANSCRIPT, (data) => {
|
|
311
|
-
if (data.text) {
|
|
312
|
-
for (const cb of callbacks.speech)
|
|
313
|
-
cb({ transcript: data.text, isFinal: false });
|
|
314
|
-
}
|
|
315
|
-
});
|
|
316
|
-
|
|
317
|
-
// Committed transcripts → append to input (isFinal: true)
|
|
318
|
-
connection.on(RealtimeEvents.COMMITTED_TRANSCRIPT, (data) => {
|
|
319
|
-
if (data.text?.trim()) {
|
|
320
|
-
refs.setFullTranscript(refs.getFullTranscript() + data.text + " ");
|
|
321
|
-
for (const cb of callbacks.speech)
|
|
322
|
-
cb({ transcript: data.text, isFinal: true });
|
|
216
|
+
void (async () => {
|
|
217
|
+
try {
|
|
218
|
+
stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
|
219
|
+
if (cancelled) {
|
|
220
|
+
stream.getTracks().forEach((t) => t.stop());
|
|
221
|
+
return;
|
|
323
222
|
}
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
console.error("Scribe error:", error);
|
|
328
|
-
(session as { status: DictationAdapter.Status }).status = {
|
|
329
|
-
type: "ended",
|
|
330
|
-
reason: "error",
|
|
223
|
+
mediaRecorder = new MediaRecorder(stream);
|
|
224
|
+
mediaRecorder.ondataavailable = (e) => {
|
|
225
|
+
if (e.data.size > 0) chunks.push(e.data);
|
|
331
226
|
};
|
|
332
|
-
|
|
227
|
+
mediaRecorder.onstart = () => {
|
|
228
|
+
session.status = { type: "running" };
|
|
229
|
+
for (const cb of speechStart) cb();
|
|
230
|
+
};
|
|
231
|
+
mediaRecorder.onstop = async () => {
|
|
232
|
+
stream?.getTracks().forEach((t) => t.stop());
|
|
233
|
+
if (cancelled) return;
|
|
234
|
+
try {
|
|
235
|
+
const blob = new Blob(chunks, {
|
|
236
|
+
type: mediaRecorder?.mimeType || "audio/webm",
|
|
237
|
+
});
|
|
238
|
+
const body = new FormData();
|
|
239
|
+
body.append("audio", blob, "dictation.webm");
|
|
240
|
+
|
|
241
|
+
const res = await fetch(this.endpoint, {
|
|
242
|
+
method: "POST",
|
|
243
|
+
body,
|
|
244
|
+
});
|
|
245
|
+
if (!res.ok) throw new Error("transcription failed");
|
|
246
|
+
|
|
247
|
+
const { text } = (await res.json()) as { text: string };
|
|
248
|
+
const result: DictationAdapter.Result = {
|
|
249
|
+
transcript: text,
|
|
250
|
+
isFinal: true,
|
|
251
|
+
};
|
|
252
|
+
for (const cb of speech) cb(result);
|
|
253
|
+
for (const cb of speechEnd) cb(result);
|
|
254
|
+
session.status = { type: "ended", reason: "stopped" };
|
|
255
|
+
} catch {
|
|
256
|
+
session.status = { type: "ended", reason: "error" };
|
|
257
|
+
}
|
|
258
|
+
};
|
|
259
|
+
mediaRecorder.start();
|
|
260
|
+
} catch {
|
|
261
|
+
session.status = { type: "ended", reason: "error" };
|
|
262
|
+
}
|
|
263
|
+
})();
|
|
333
264
|
|
|
334
|
-
|
|
335
|
-
console.error("ElevenLabs Scribe connection failed:", error);
|
|
336
|
-
(session as { status: DictationAdapter.Status }).status = {
|
|
337
|
-
type: "ended",
|
|
338
|
-
reason: "error",
|
|
339
|
-
};
|
|
340
|
-
}
|
|
265
|
+
return session;
|
|
341
266
|
}
|
|
342
267
|
}
|
|
343
268
|
```
|
|
344
269
|
|
|
345
|
-
|
|
270
|
+
Wire it like any other adapter:
|
|
346
271
|
|
|
347
272
|
```tsx
|
|
273
|
+
import { ServerDictationAdapter } from "@/lib/server-dictation-adapter";
|
|
274
|
+
|
|
348
275
|
const runtime = useChatRuntime({
|
|
349
276
|
adapters: {
|
|
350
|
-
dictation: new
|
|
351
|
-
tokenEndpoint: "/api/scribe-token",
|
|
352
|
-
languageCode: "en", // Optional: supports 90+ languages
|
|
353
|
-
disableInputDuringDictation: true, // Default: true (recommended for ElevenLabs)
|
|
354
|
-
}),
|
|
277
|
+
dictation: new ServerDictationAdapter("/api/transcribe"),
|
|
355
278
|
},
|
|
356
279
|
});
|
|
357
280
|
```
|
|
358
281
|
|
|
359
|
-
|
|
282
|
+
This pattern records until the user stops dictation, then commits a single final transcript. For streaming partials, keep the same `DictationAdapter` surface and emit `isFinal: false` results as your provider produces them.
|
|
360
283
|
|
|
361
|
-
|
|
284
|
+
## Custom realtime providers
|
|
362
285
|
|
|
363
|
-
|
|
364
|
-
For advanced customization, `composer.dictation?.transcript` contains the current interim transcript, and `ComposerPrimitive.DictationTranscript` can display it separately if desired.
|
|
365
|
-
</Callout>
|
|
286
|
+
You can implement `DictationAdapter` against any streaming STT service. [ElevenLabs Scribe](https://elevenlabs.io/docs/capabilities/speech-to-text) is one option for low-latency WebSocket transcription; scaffold a full example with:
|
|
366
287
|
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
288
|
+
```bash
|
|
289
|
+
npx assistant-ui create my-app -e with-elevenlabs-scribe
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
Cumulative transcript services should set `disableInputDuringDictation: true` so interim text does not fight the user's keyboard.
|
|
293
|
+
|
|
294
|
+
## Related guides
|
|
370
295
|
|
|
296
|
+
- [Realtime Voice](/docs/guides/voice): duplex voice sessions (`RealtimeVoiceAdapter`, `createVoiceSession`, voice UI component).
|
|
297
|
+
- [Speech](/docs/guides/speech): text-to-speech for assistant messages.
|
|
298
|
+
- [Speech and Dictation API reference](/docs/api-reference/voice/speech-dictation): generated type docs.
|
|
@@ -95,3 +95,13 @@ Read and drive runtime state from your own code.
|
|
|
95
95
|
Access threads, messages, composer, and tool state via `useAui` and the runtime scope tree.
|
|
96
96
|
</Card>
|
|
97
97
|
</Cards>
|
|
98
|
+
|
|
99
|
+
## Providers
|
|
100
|
+
|
|
101
|
+
Alternative ways to connect a model.
|
|
102
|
+
|
|
103
|
+
<Cards>
|
|
104
|
+
<Card title="ChatGPT Subscription" href="/docs/guides/chatgpt-subscription">
|
|
105
|
+
Run your app locally on a ChatGPT Plus or Pro plan via Codex OAuth, no API key required.
|
|
106
|
+
</Card>
|
|
107
|
+
</Cards>
|
|
@@ -25,9 +25,7 @@ Under the hood, mentions are one kind of [trigger popover](/docs/guides/slash-co
|
|
|
25
25
|
|
|
26
26
|
The fastest path is the pre-built [Mention UI components](/docs/ui/composer-trigger-popover), which wire everything together with two shadcn components — the popover picker and the message-side chip renderer:
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
npx shadcn@latest add "https://r.assistant-ui.com/composer-trigger-popover" "https://r.assistant-ui.com/directive-text"
|
|
30
|
-
```
|
|
28
|
+
<InstallCommand shadcn={["composer-trigger-popover", "directive-text"]} />
|
|
31
29
|
|
|
32
30
|
See the [Composer Trigger Popover](/docs/ui/composer-trigger-popover) and [Directive Text](/docs/ui/directive-text) guides for setup steps.
|
|
33
31
|
|
|
@@ -414,6 +412,36 @@ import { LexicalComposerInput } from "@assistant-ui/react-lexical";
|
|
|
414
412
|
|
|
415
413
|
`LexicalComposerInput` automatically discovers every `Directive` trigger registered under `TriggerPopoverRoot` and renders their selections as inline chips.
|
|
416
414
|
|
|
415
|
+
### Custom Lexical Plugins
|
|
416
|
+
|
|
417
|
+
Children of `LexicalComposerInput` render inside the `LexicalComposer` context after the built-in plugins, so standard Lexical plugin components built on `useLexicalComposerContext` work for editor concerns the mention system does not cover, such as paste normalization or length limits. Custom plugins import Lexical APIs directly, so install `lexical` and `@lexical/react` as direct dependencies of your app. The example below registers an update listener that flags messages over a maximum length.
|
|
418
|
+
|
|
419
|
+
```tsx
|
|
420
|
+
import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
|
|
421
|
+
import { useEffect } from "react";
|
|
422
|
+
import { $getRoot } from "lexical";
|
|
423
|
+
|
|
424
|
+
function MaxLengthPlugin({ maxLength }: { maxLength: number }) {
|
|
425
|
+
const [editor] = useLexicalComposerContext();
|
|
426
|
+
|
|
427
|
+
useEffect(() => {
|
|
428
|
+
return editor.registerUpdateListener(({ editorState }) => {
|
|
429
|
+
editorState.read(() => {
|
|
430
|
+
if ($getRoot().getTextContent().length > maxLength) {
|
|
431
|
+
console.warn(`Message exceeds ${maxLength} characters`);
|
|
432
|
+
}
|
|
433
|
+
});
|
|
434
|
+
});
|
|
435
|
+
}, [editor, maxLength]);
|
|
436
|
+
|
|
437
|
+
return null;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
<LexicalComposerInput placeholder="Ask anything...">
|
|
441
|
+
<MaxLengthPlugin maxLength={2000} />
|
|
442
|
+
</LexicalComposerInput>
|
|
443
|
+
```
|
|
444
|
+
|
|
417
445
|
## Rendering Mentions in Messages
|
|
418
446
|
|
|
419
447
|
Use `DirectiveText` as the `Text` component for user messages so directives render as inline chips instead of raw syntax. See the [Directive Text](/docs/ui/directive-text) guide for setup and customization.
|
|
@@ -112,7 +112,12 @@ export default function Page() {
|
|
|
112
112
|
}),
|
|
113
113
|
[],
|
|
114
114
|
);
|
|
115
|
-
const runtime = useChatRuntime({
|
|
115
|
+
const runtime = useChatRuntime({
|
|
116
|
+
transport,
|
|
117
|
+
onResumeError: (error) => {
|
|
118
|
+
console.error("Could not resume the previous response", error);
|
|
119
|
+
},
|
|
120
|
+
});
|
|
116
121
|
|
|
117
122
|
return (
|
|
118
123
|
<AssistantRuntimeProvider runtime={runtime}>
|
|
@@ -122,6 +127,8 @@ export default function Page() {
|
|
|
122
127
|
}
|
|
123
128
|
```
|
|
124
129
|
|
|
130
|
+
`onResumeError` runs when the client finds a stored stream id but the reconnect attempt fails. Use it to show a toast, report telemetry, or mark the thread as needing retry; assistant-ui still clears the stale stream id after the callback runs.
|
|
131
|
+
|
|
125
132
|
`createResumableSessionStorage` returns a `ResumableClientStorage` backed by `window.sessionStorage`. Pass `{ key }` to namespace per route or per chat surface, or supply your own implementation of the three methods (`getStreamId`, `setStreamId`, `clear`). If you are running on a transport that already wraps `fetch` or `prepareReconnectToStreamRequest`, the `resumable` option composes with your existing handlers.
|
|
126
133
|
|
|
127
134
|
The default finish detector scans the SSE body for the AI SDK `"type":"finish"` marker. Override `isFinishEvent` on the `resumable` option when you ship a custom encoder.
|
|
@@ -208,3 +215,7 @@ Both helpers default to the data-stream encoder; pass `encoder: () => new Assist
|
|
|
208
215
|
```sh
|
|
209
216
|
npx assistant-ui create my-app -e with-resumable-stream
|
|
210
217
|
```
|
|
218
|
+
|
|
219
|
+
To drop the resumable chat and resume routes into an existing project (in-memory store only; upgrade via [Storage choices](#storage-choices)):
|
|
220
|
+
|
|
221
|
+
<InstallCommand shadcn={["ai-sdk-backend-resumable"]} />
|