@assistant-ui/mcp-docs-server 0.1.38 → 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.
Files changed (207) hide show
  1. package/.docs/organized/code-examples/waterfall.md +8 -9
  2. package/.docs/organized/code-examples/with-a2a.md +16 -11
  3. package/.docs/organized/code-examples/with-ag-ui.md +16 -11
  4. package/.docs/organized/code-examples/{with-ai-sdk-v6.md → with-ai-sdk-v7.md} +32 -21
  5. package/.docs/organized/code-examples/with-artifacts.md +17 -10
  6. package/.docs/organized/code-examples/with-assistant-transport.md +15 -8
  7. package/.docs/organized/code-examples/with-browser-extension.md +15 -8
  8. package/.docs/organized/code-examples/with-chain-of-thought.md +17 -10
  9. package/.docs/organized/code-examples/with-cloud-standalone.md +10 -9
  10. package/.docs/organized/code-examples/with-cloud.md +18 -13
  11. package/.docs/organized/code-examples/with-custom-thread-list.md +17 -10
  12. package/.docs/organized/code-examples/with-elevenlabs-conversational.md +20 -13
  13. package/.docs/organized/code-examples/with-elevenlabs-scribe.md +20 -13
  14. package/.docs/organized/code-examples/with-eve.md +16 -9
  15. package/.docs/organized/code-examples/with-expo.md +27 -24
  16. package/.docs/organized/code-examples/with-external-store.md +16 -11
  17. package/.docs/organized/code-examples/with-ffmpeg.md +18 -13
  18. package/.docs/organized/code-examples/with-generative-ui.md +20 -15
  19. package/.docs/organized/code-examples/with-google-adk.md +15 -8
  20. package/.docs/organized/code-examples/with-heat-graph.md +8 -9
  21. package/.docs/organized/code-examples/with-image-generation.md +17 -10
  22. package/.docs/organized/code-examples/with-interactables.md +19 -15
  23. package/.docs/organized/code-examples/with-langchain.md +17 -10
  24. package/.docs/organized/code-examples/with-langgraph.md +17 -10
  25. package/.docs/organized/code-examples/with-livekit.md +21 -14
  26. package/.docs/organized/code-examples/with-mcp.md +25 -12
  27. package/.docs/organized/code-examples/with-opencode.md +22 -17
  28. package/.docs/organized/code-examples/with-pi.md +47 -12
  29. package/.docs/organized/code-examples/with-react-hook-form.md +19 -14
  30. package/.docs/organized/code-examples/with-react-ink-web.md +6 -6
  31. package/.docs/organized/code-examples/with-react-ink.md +2 -2
  32. package/.docs/organized/code-examples/with-react-router.md +20 -15
  33. package/.docs/organized/code-examples/with-resumable-stream.md +19 -12
  34. package/.docs/organized/code-examples/with-store.md +8 -9
  35. package/.docs/organized/code-examples/with-tanstack.md +16 -10
  36. package/.docs/organized/code-examples/with-tap-runtime.md +16 -11
  37. package/.docs/organized/code-examples/with-virtualized-thread.md +17 -12
  38. package/.docs/raw/docs/(docs)/base-ui.mdx +39 -0
  39. package/.docs/raw/docs/(docs)/cli.mdx +17 -1
  40. package/.docs/raw/docs/(docs)/installation.mdx +15 -1
  41. package/.docs/raw/docs/(docs)/rtl.mdx +2 -4
  42. package/.docs/raw/docs/(reference)/api-reference/generative-ui/actions.mdx +56 -0
  43. package/.docs/raw/docs/(reference)/api-reference/generative-ui/components.mdx +86 -0
  44. package/.docs/raw/docs/(reference)/api-reference/generative-ui/index.mdx +19 -1
  45. package/.docs/raw/docs/(reference)/api-reference/generative-ui/json-generative-ui.mdx +42 -0
  46. package/.docs/raw/docs/(reference)/api-reference/generative-ui/rendering.mdx +53 -2
  47. package/.docs/raw/docs/(reference)/api-reference/generative-ui/slack.mdx +81 -0
  48. package/.docs/raw/docs/(reference)/api-reference/generative-ui/teams.mdx +86 -0
  49. package/.docs/raw/docs/(reference)/api-reference/generative-ui/tokens.mdx +62 -0
  50. package/.docs/raw/docs/(reference)/api-reference/integrations/index.mdx +4 -1
  51. package/.docs/raw/docs/(reference)/api-reference/integrations/react-ai-sdk.mdx +4 -2
  52. package/.docs/raw/docs/(reference)/api-reference/integrations/react-data-stream.mdx +37 -0
  53. package/.docs/raw/docs/(reference)/api-reference/overview.mdx +1 -0
  54. package/.docs/raw/docs/(reference)/api-reference/primitives/assistant-if.mdx +29 -29
  55. package/.docs/raw/docs/(reference)/api-reference/primitives/composition.mdx +1 -0
  56. package/.docs/raw/docs/(reference)/api-reference/primitives/index.mdx +3 -0
  57. package/.docs/raw/docs/(reference)/api-reference/primitives/selection-toolbar.mdx +2 -0
  58. package/.docs/raw/docs/(reference)/api-reference/tools/status.mdx +0 -2
  59. package/.docs/raw/docs/(reference)/api-reference/tools/toolkits.mdx +12 -9
  60. package/.docs/raw/docs/(reference)/api-reference/utilities/miscellaneous.mdx +4 -0
  61. package/.docs/raw/docs/cloud/ai-sdk-assistant-ui.mdx +7 -1
  62. package/.docs/raw/docs/cloud/langgraph.mdx +3 -1
  63. package/.docs/raw/docs/guides/chatgpt-subscription.mdx +108 -0
  64. package/.docs/raw/docs/guides/dictation.mdx +185 -257
  65. package/.docs/raw/docs/guides/index.mdx +10 -0
  66. package/.docs/raw/docs/guides/mentions.mdx +31 -3
  67. package/.docs/raw/docs/guides/resumable-streams.mdx +12 -1
  68. package/.docs/raw/docs/guides/speech.mdx +47 -29
  69. package/.docs/raw/docs/guides/suggestions.mdx +70 -1
  70. package/.docs/raw/docs/guides/voice.mdx +197 -267
  71. package/.docs/raw/docs/ink/primitives.mdx +35 -1
  72. package/.docs/raw/docs/integrations/frameworks/ai-sdk.mdx +10 -4
  73. package/.docs/raw/docs/integrations/frameworks/cloudflare-agents/overview.mdx +3 -3
  74. package/.docs/raw/docs/integrations/frameworks/mastra/full-stack.mdx +1 -1
  75. package/.docs/raw/docs/integrations/frameworks/mastra/overview.mdx +3 -3
  76. package/.docs/raw/docs/integrations/frameworks/mastra/separate-server.mdx +1 -1
  77. package/.docs/raw/docs/integrations/gateways/index.mdx +2 -2
  78. package/.docs/raw/docs/integrations/observability/helicone.mdx +1 -1
  79. package/.docs/raw/docs/integrations/observability/langfuse.mdx +1 -1
  80. package/.docs/raw/docs/integrations/observability/langsmith.mdx +1 -1
  81. package/.docs/raw/docs/migrations/index.mdx +50 -0
  82. package/.docs/raw/docs/migrations/toolkit-tools.mdx +4 -2
  83. package/.docs/raw/docs/primitives/chain-of-thought.mdx +6 -1
  84. package/.docs/raw/docs/primitives/composer.mdx +16 -0
  85. package/.docs/raw/docs/primitives/selection-toolbar.mdx +25 -0
  86. package/.docs/raw/docs/react-native/primitives.mdx +23 -0
  87. package/.docs/raw/docs/runtimes/ag-ui/agent-state.mdx +124 -0
  88. package/.docs/raw/docs/runtimes/ag-ui/runtime-options.mdx +10 -1
  89. package/.docs/raw/docs/runtimes/ai-sdk/overview.mdx +13 -4
  90. package/.docs/raw/docs/runtimes/ai-sdk/v4-legacy.mdx +11 -11
  91. package/.docs/raw/docs/runtimes/ai-sdk/v5-legacy.mdx +5 -5
  92. package/.docs/raw/docs/runtimes/ai-sdk/{v6.mdx → v6-legacy.mdx} +8 -6
  93. package/.docs/raw/docs/runtimes/ai-sdk/v7.mdx +717 -0
  94. package/.docs/raw/docs/runtimes/concepts/adapters.mdx +1 -1
  95. package/.docs/raw/docs/runtimes/concepts/architecture.mdx +1 -1
  96. package/.docs/raw/docs/runtimes/custom/data-stream.mdx +10 -19
  97. package/.docs/raw/docs/runtimes/custom/external-store.mdx +1 -1
  98. package/.docs/raw/docs/runtimes/custom/local-runtime.mdx +2 -0
  99. package/.docs/raw/docs/runtimes/eve/overview.mdx +1 -1
  100. package/.docs/raw/docs/runtimes/langgraph/agent-state.mdx +181 -0
  101. package/.docs/raw/docs/tools/backend.mdx +6 -3
  102. package/.docs/raw/docs/tools/defining-tools.mdx +7 -1
  103. package/.docs/raw/docs/tools/generative-ui.mdx +60 -2
  104. package/.docs/raw/docs/tools/mcp-apps.mdx +29 -11
  105. package/.docs/raw/docs/tools/mcp.mdx +100 -3
  106. package/.docs/raw/docs/tools/tool-ui.mdx +6 -4
  107. package/.docs/raw/docs/tools/user-managed-mcp.mdx +26 -3
  108. package/.docs/raw/docs/ui/accordion.mdx +16 -10
  109. package/.docs/raw/docs/ui/assistant-modal.mdx +8 -4
  110. package/.docs/raw/docs/ui/attachment.mdx +5 -1
  111. package/.docs/raw/docs/ui/badge.mdx +23 -12
  112. package/.docs/raw/docs/ui/follow-up-suggestions.mdx +2 -2
  113. package/.docs/raw/docs/ui/model-selector.mdx +32 -2
  114. package/.docs/raw/docs/ui/select.mdx +22 -14
  115. package/.docs/raw/docs/ui/sources.mdx +1 -1
  116. package/.docs/raw/docs/ui/tabs.mdx +25 -14
  117. package/.docs/raw/docs/utilities/heat-graph.mdx +2 -2
  118. package/dist/constants.d.ts.map +1 -1
  119. package/dist/index.d.ts +0 -1
  120. package/dist/index.d.ts.map +1 -1
  121. package/dist/index.js +39 -0
  122. package/dist/index.js.map +1 -1
  123. package/dist/prepare-docs/code-examples.d.ts.map +1 -1
  124. package/dist/prepare-docs/copy-raw.d.ts.map +1 -1
  125. package/dist/prompts/xulux-playground.d.ts +12 -0
  126. package/dist/prompts/xulux-playground.d.ts.map +1 -0
  127. package/dist/prompts/xulux-playground.js +33 -0
  128. package/dist/prompts/xulux-playground.js.map +1 -0
  129. package/dist/tools/docs.d.ts +2 -4
  130. package/dist/tools/docs.d.ts.map +1 -1
  131. package/dist/tools/docs.js +24 -8
  132. package/dist/tools/docs.js.map +1 -1
  133. package/dist/tools/examples.d.ts +2 -4
  134. package/dist/tools/examples.d.ts.map +1 -1
  135. package/dist/tools/examples.js +9 -6
  136. package/dist/tools/examples.js.map +1 -1
  137. package/dist/tools/resources.d.ts +0 -1
  138. package/dist/tools/resources.d.ts.map +1 -1
  139. package/dist/tools/search.d.ts +2 -5
  140. package/dist/tools/search.d.ts.map +1 -1
  141. package/dist/tools/tests/test-setup.d.ts.map +1 -1
  142. package/dist/tools/tests/test-setup.js +5 -1
  143. package/dist/tools/tests/test-setup.js.map +1 -1
  144. package/dist/tools/xulux-templates.d.ts +72 -0
  145. package/dist/tools/xulux-templates.d.ts.map +1 -0
  146. package/dist/tools/xulux-templates.js +82 -0
  147. package/dist/tools/xulux-templates.js.map +1 -0
  148. package/dist/utils/cache.d.ts +5 -0
  149. package/dist/utils/cache.d.ts.map +1 -0
  150. package/dist/utils/cache.js +18 -0
  151. package/dist/utils/cache.js.map +1 -0
  152. package/dist/utils/logger.d.ts.map +1 -1
  153. package/dist/utils/mcp-format.d.ts +1 -0
  154. package/dist/utils/mcp-format.d.ts.map +1 -1
  155. package/dist/utils/mcp-format.js +7 -4
  156. package/dist/utils/mcp-format.js.map +1 -1
  157. package/dist/utils/mdx.d.ts.map +1 -1
  158. package/dist/utils/paths.d.ts +1 -1
  159. package/dist/utils/paths.d.ts.map +1 -1
  160. package/dist/utils/paths.js +3 -1
  161. package/dist/utils/paths.js.map +1 -1
  162. package/dist/utils/search.d.ts.map +1 -1
  163. package/dist/utils/security.d.ts.map +1 -1
  164. package/dist/xulux/catalog-client.d.ts +14 -0
  165. package/dist/xulux/catalog-client.d.ts.map +1 -0
  166. package/dist/xulux/catalog-client.js +67 -0
  167. package/dist/xulux/catalog-client.js.map +1 -0
  168. package/dist/xulux/fallback-catalog.d.ts +7 -0
  169. package/dist/xulux/fallback-catalog.d.ts.map +1 -0
  170. package/dist/xulux/fallback-catalog.js +47 -0
  171. package/dist/xulux/fallback-catalog.js.map +1 -0
  172. package/dist/xulux/fetch-sandbox.d.ts +5 -0
  173. package/dist/xulux/fetch-sandbox.d.ts.map +1 -0
  174. package/dist/xulux/fetch-sandbox.js +40 -0
  175. package/dist/xulux/fetch-sandbox.js.map +1 -0
  176. package/dist/xulux/template-service.d.ts +84 -0
  177. package/dist/xulux/template-service.d.ts.map +1 -0
  178. package/dist/xulux/template-service.js +223 -0
  179. package/dist/xulux/template-service.js.map +1 -0
  180. package/dist/xulux/types.d.ts +55 -0
  181. package/dist/xulux/types.d.ts.map +1 -0
  182. package/dist/xulux/types.js +6 -0
  183. package/dist/xulux/types.js.map +1 -0
  184. package/package.json +5 -5
  185. package/src/index.ts +53 -0
  186. package/src/prompts/xulux-playground.ts +36 -0
  187. package/src/tools/docs.ts +25 -3
  188. package/src/tools/examples.ts +15 -10
  189. package/src/tools/tests/docs.test.ts +20 -0
  190. package/src/tools/tests/examples.test.ts +5 -5
  191. package/src/tools/tests/listings-cache.test.ts +19 -0
  192. package/src/tools/tests/mcp-protocol.test.ts +81 -1
  193. package/src/tools/tests/test-setup.ts +8 -0
  194. package/src/tools/tests/xulux-templates.test.ts +262 -0
  195. package/src/tools/xulux-templates.ts +141 -0
  196. package/src/utils/cache.ts +20 -0
  197. package/src/utils/mcp-format.ts +8 -6
  198. package/src/utils/paths.ts +4 -1
  199. package/src/utils/tests/cache.test.ts +51 -0
  200. package/src/utils/tests/mcp-format.test.ts +22 -0
  201. package/src/utils/tests/security.test.ts +1 -1
  202. package/src/xulux/catalog-client.ts +105 -0
  203. package/src/xulux/fallback-catalog.ts +63 -0
  204. package/src/xulux/fetch-sandbox.ts +56 -0
  205. package/src/xulux/template-service.ts +406 -0
  206. package/src/xulux/types.ts +60 -0
  207. 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. This allows users to input messages using their voice.
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
- <DictationSample />
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
- Currently, the following dictation adapters are supported:
16
-
17
- - `WebSpeechDictationAdapter`: Uses the browser's `Web Speech API` (SpeechRecognition)
13
+ <DictationSample />
18
14
 
19
- The `WebSpeechDictationAdapter` is supported in Chrome, Edge, and Safari. Check [browser compatibility](https://developer.mozilla.org/en-US/docs/Web/API/SpeechRecognition#browser_compatibility) for details.
15
+ ## WebSpeechDictationAdapter
20
16
 
21
- ## Configuration
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
- // Optional configuration
30
- language: "en-US", // Language for recognition (default: browser language)
31
- continuous: true, // Keep recording after user stops (default: true)
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
- ## UI
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
- ## Disabling Input During Dictation
43
+ ## DictationAdapter interface
82
44
 
83
- Some dictation services (like ElevenLabs Scribe) return cumulative transcripts that conflict with simultaneous typing. You can disable the text input during dictation:
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
- class MyAdapter implements DictationAdapter {
89
- // Set to true to disable typing while dictating
90
- disableInputDuringDictation = true;
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
- listen() { /* ... */ }
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
- <Callout type="info">
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
- You can create custom adapters to integrate with any dictation service by implementing the `DictationAdapter` interface.
81
+ ### Interim vs final results
103
82
 
104
- ### DictationAdapter Interface
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
- class MyCustomDictationAdapter implements DictationAdapter {
110
- // Optional: disable text input while dictating (default: false)
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
- listen(): DictationAdapter.Session {
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
- stop: async () => {
119
- // Stop recognition and finalize results
120
- },
90
+ ### Disabling input during dictation
121
91
 
122
- cancel: () => {
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
- onSpeechStart: (callback) => {
127
- // Called when speech is detected
128
- return () => {}; // Return unsubscribe function
129
- },
94
+ ```tsx
95
+ import type { DictationAdapter } from "@assistant-ui/react";
130
96
 
131
- onSpeechEnd: (callback) => {
132
- // Called when recognition ends with final result
133
- return () => {};
134
- },
97
+ class MyAdapter implements DictationAdapter {
98
+ disableInputDuringDictation = true;
135
99
 
136
- onSpeech: (callback) => {
137
- // Called with transcription results
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
- ### Interim vs Final Results
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
- The `onSpeech` callback receives results with an optional `isFinal` flag:
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
- onSpeech: (callback) => {
155
- // callback({ transcript: "text", isFinal: true })
156
- // - isFinal: true → Text is committed to the input
157
- // - isFinal: false → Text is shown as preview in the input
158
- return () => {};
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
- **Both interim and final results are displayed directly in the input field**, just like native dictation on iOS/Android. Interim results replace each other until a final result commits the text. This provides seamless real-time feedback while the user speaks.
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
- ### Example: ElevenLabs Scribe v2 Realtime
141
+ ## Server-side transcription
165
142
 
166
- [ElevenLabs Scribe](https://elevenlabs.io/docs/capabilities/speech-to-text) provides ultra-low latency (~150ms) real-time transcription via WebSocket.
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
- #### Install Dependencies
145
+ ### API route
169
146
 
170
- ```bash
171
- npm install @elevenlabs/client
172
- ```
147
+ ```ts title="app/api/transcribe/route.ts"
148
+ import { transcribe } from "ai";
149
+ import { openai } from "@ai-sdk/openai";
173
150
 
174
- #### Backend API Route
151
+ export async function POST(req: Request) {
152
+ const formData = await req.formData();
153
+ const audio = formData.get("audio");
175
154
 
176
- Create an API route to generate single-use tokens:
155
+ if (!(audio instanceof Blob)) {
156
+ return Response.json({ error: "audio required" }, { status: 400 });
157
+ }
177
158
 
178
- ```ts title="app/api/scribe-token/route.ts"
179
- export async function POST() {
180
- const response = await fetch(
181
- "https://api.elevenlabs.io/v1/single-use-token/realtime_scribe",
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
- const data = await response.json();
191
- return Response.json({ token: data.token });
164
+ return Response.json({ text: result.text });
192
165
  }
193
166
  ```
194
167
 
195
- #### Frontend Adapter
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
- ```tsx title="lib/elevenlabs-scribe-adapter.ts"
170
+ ### Adapter
171
+
172
+ ```tsx title="lib/server-dictation-adapter.ts"
198
173
  import type { DictationAdapter } from "@assistant-ui/react";
199
- import { Scribe, RealtimeEvents } from "@elevenlabs/client";
200
-
201
- export class ElevenLabsScribeAdapter implements DictationAdapter {
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 callbacks = {
220
- start: new Set<() => void>(),
221
- end: new Set<(r: DictationAdapter.Result) => void>(),
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 connection: ReturnType<typeof Scribe.connect> | null = null;
226
- let fullTranscript = "";
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
- if (connection) {
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
- connection?.close();
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
- callbacks.start.add(cb);
248
- return () => callbacks.start.delete(cb);
203
+ speechStart.add(cb);
204
+ return () => speechStart.delete(cb);
249
205
  },
250
-
251
206
  onSpeechEnd: (cb) => {
252
- callbacks.end.add(cb);
253
- return () => callbacks.end.delete(cb);
207
+ speechEnd.add(cb);
208
+ return () => speechEnd.delete(cb);
254
209
  },
255
-
256
210
  onSpeech: (cb) => {
257
- callbacks.speech.add(cb);
258
- return () => callbacks.speech.delete(cb);
211
+ speech.add(cb);
212
+ return () => speech.delete(cb);
259
213
  },
260
214
  };
261
215
 
262
- this.connect(session, callbacks, {
263
- setConnection: (c) => { connection = c; },
264
- getFullTranscript: () => fullTranscript,
265
- setFullTranscript: (t) => { fullTranscript = t; },
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
- connection.on(RealtimeEvents.ERROR, (error) => {
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
- } catch (error) {
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
- #### Usage
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 ElevenLabsScribeAdapter({
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
- #### Real-time Preview
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
- The transcription is displayed directly in the input field as the user speaks — just like native dictation. No additional UI components are needed for basic use cases.
284
+ ## Custom realtime providers
362
285
 
363
- <Callout type="info">
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
- <Callout type="info">
368
- For more details, see the [ElevenLabs Scribe documentation](https://elevenlabs.io/docs/capabilities/speech-to-text).
369
- </Callout>
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
- ```bash
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({ transport });
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"]} />