@assistant-ui/mcp-docs-server 0.1.38 → 0.2.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/.docs/organized/code-examples/waterfall.md +11 -12
- package/.docs/organized/code-examples/with-a2a.md +18 -13
- package/.docs/organized/code-examples/with-ag-ui.md +19 -14
- package/.docs/organized/code-examples/{with-ai-sdk-v6.md → with-ai-sdk-v7.md} +34 -23
- package/.docs/organized/code-examples/with-artifacts.md +473 -141
- package/.docs/organized/code-examples/with-assistant-transport.md +17 -10
- package/.docs/organized/code-examples/with-browser-extension.md +17 -10
- package/.docs/organized/code-examples/with-chain-of-thought.md +21 -14
- package/.docs/organized/code-examples/with-cloud-standalone.md +12 -11
- package/.docs/organized/code-examples/with-cloud.md +20 -15
- package/.docs/organized/code-examples/with-custom-thread-list.md +19 -12
- package/.docs/organized/code-examples/with-elevenlabs-conversational.md +22 -15
- package/.docs/organized/code-examples/with-elevenlabs-scribe.md +22 -15
- package/.docs/organized/code-examples/with-eve.md +18 -11
- package/.docs/organized/code-examples/with-expo.md +35 -52
- package/.docs/organized/code-examples/with-external-store.md +18 -13
- package/.docs/organized/code-examples/with-ffmpeg.md +20 -15
- package/.docs/organized/code-examples/with-generative-ui.md +22 -17
- package/.docs/organized/code-examples/with-google-adk.md +18 -11
- package/.docs/organized/code-examples/with-heat-graph.md +10 -11
- package/.docs/organized/code-examples/with-image-generation.md +19 -12
- package/.docs/organized/code-examples/with-interactables.md +21 -17
- package/.docs/organized/code-examples/with-langchain.md +19 -12
- package/.docs/organized/code-examples/with-langgraph.md +19 -12
- package/.docs/organized/code-examples/with-livekit.md +23 -16
- package/.docs/organized/code-examples/with-mcp.md +46 -28
- package/.docs/organized/code-examples/with-opencode.md +25 -23
- package/.docs/organized/code-examples/with-pi.md +54 -19
- package/.docs/organized/code-examples/with-react-hook-form.md +21 -16
- package/.docs/organized/code-examples/with-react-ink-web.md +9 -9
- package/.docs/organized/code-examples/with-react-ink.md +4 -4
- package/.docs/organized/code-examples/with-react-router.md +22 -17
- package/.docs/organized/code-examples/with-resumable-stream.md +21 -14
- package/.docs/organized/code-examples/with-store.md +10 -11
- package/.docs/organized/code-examples/with-tanstack.md +19 -13
- package/.docs/organized/code-examples/with-tap-runtime.md +18 -13
- package/.docs/organized/code-examples/with-virtualized-thread.md +19 -14
- package/.docs/raw/docs/(docs)/base-ui.mdx +39 -0
- package/.docs/raw/docs/(docs)/cli.mdx +19 -1
- package/.docs/raw/docs/(docs)/devtools.mdx +7 -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/a2ui.mdx +40 -0
- 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 +22 -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/hooks/primitives.mdx +19 -420
- package/.docs/raw/docs/(reference)/api-reference/hooks/state.mdx +4 -1
- package/.docs/raw/docs/(reference)/api-reference/integrations/index.mdx +4 -1
- package/.docs/raw/docs/(reference)/api-reference/integrations/react-ai-sdk.mdx +25 -2
- package/.docs/raw/docs/(reference)/api-reference/integrations/react-data-stream.mdx +37 -0
- package/.docs/raw/docs/(reference)/api-reference/model-context/context.mdx +1 -9
- package/.docs/raw/docs/(reference)/api-reference/overview.mdx +1 -0
- package/.docs/raw/docs/(reference)/api-reference/primitives/assistant-if.mdx +14 -31
- 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/copilots/model-context.mdx +1 -1
- package/.docs/raw/docs/copilots/motivation.mdx +1 -1
- package/.docs/raw/docs/guides/attachments.mdx +3 -3
- package/.docs/raw/docs/guides/branching.mdx +2 -2
- package/.docs/raw/docs/guides/chatgpt-subscription.mdx +108 -0
- package/.docs/raw/docs/guides/context-api.mdx +89 -111
- package/.docs/raw/docs/guides/dictation.mdx +185 -257
- package/.docs/raw/docs/guides/editing.mdx +5 -5
- package/.docs/raw/docs/guides/electron.mdx +369 -0
- package/.docs/raw/docs/guides/index.mdx +20 -0
- package/.docs/raw/docs/guides/mentions.mdx +31 -3
- package/.docs/raw/docs/guides/quoting.mdx +3 -3
- package/.docs/raw/docs/guides/resumable-stream-deployment.mdx +2 -2
- 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/hooks.mdx +3 -3
- package/.docs/raw/docs/ink/primitives.mdx +49 -8
- package/.docs/raw/docs/integrations/auth/better-auth.mdx +2 -2
- package/.docs/raw/docs/integrations/auth/clerk.mdx +2 -2
- package/.docs/raw/docs/integrations/auth/next-auth.mdx +3 -3
- package/.docs/raw/docs/integrations/frameworks/ai-sdk.mdx +10 -4
- 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/observability/helicone.mdx +1 -1
- package/.docs/raw/docs/integrations/observability/langfuse.mdx +1 -1
- package/.docs/raw/docs/integrations/observability/langsmith.mdx +2 -2
- package/.docs/raw/docs/integrations/persistence/custom-adapter.mdx +36 -9
- package/.docs/raw/docs/migrations/index.mdx +50 -0
- package/.docs/raw/docs/migrations/toolkit-tools.mdx +4 -2
- package/.docs/raw/docs/migrations/v0-15.mdx +156 -0
- package/.docs/raw/docs/primitives/chain-of-thought.mdx +6 -1
- package/.docs/raw/docs/primitives/composer.mdx +17 -1
- package/.docs/raw/docs/primitives/selection-toolbar.mdx +25 -0
- package/.docs/raw/docs/primitives/thread-list.mdx +2 -2
- package/.docs/raw/docs/react-native/hooks.mdx +3 -3
- package/.docs/raw/docs/react-native/index.mdx +2 -2
- package/.docs/raw/docs/react-native/primitives.mdx +62 -5
- package/.docs/raw/docs/runtimes/ag-ui/agent-state.mdx +124 -0
- package/.docs/raw/docs/runtimes/ag-ui/runtime-options.mdx +10 -1
- package/.docs/raw/docs/runtimes/ai-sdk/overview.mdx +14 -5
- package/.docs/raw/docs/runtimes/ai-sdk/v4-legacy.mdx +15 -15
- package/.docs/raw/docs/runtimes/ai-sdk/v5-legacy.mdx +8 -8
- package/.docs/raw/docs/runtimes/ai-sdk/{v6.mdx → v6-legacy.mdx} +11 -9
- 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/concepts/threads.mdx +10 -10
- package/.docs/raw/docs/runtimes/custom/assistant-transport.mdx +2 -2
- package/.docs/raw/docs/runtimes/custom/data-stream.mdx +10 -19
- package/.docs/raw/docs/runtimes/custom/external-store.mdx +2 -2
- package/.docs/raw/docs/runtimes/custom/local-runtime.mdx +5 -3
- package/.docs/raw/docs/runtimes/eve/overview.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/langgraph/overview.mdx +1 -1
- package/.docs/raw/docs/runtimes/opencode/hooks.mdx +3 -1
- package/.docs/raw/docs/tools/a2ui.mdx +107 -0
- package/.docs/raw/docs/tools/backend.mdx +6 -3
- package/.docs/raw/docs/tools/defining-tools.mdx +7 -1
- package/.docs/raw/docs/tools/generative-ui.mdx +60 -2
- package/.docs/raw/docs/tools/interactables-legacy.mdx +4 -4
- package/.docs/raw/docs/tools/interactables.mdx +3 -3
- package/.docs/raw/docs/tools/mcp-apps.mdx +90 -13
- package/.docs/raw/docs/tools/mcp.mdx +100 -3
- package/.docs/raw/docs/tools/tool-ui.mdx +6 -4
- package/.docs/raw/docs/tools/user-managed-mcp.mdx +77 -9
- 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 +4 -2
- package/.docs/raw/docs/ui/model-selector.mdx +33 -3
- package/.docs/raw/docs/ui/part-grouping.mdx +0 -4
- package/.docs/raw/docs/ui/reasoning.mdx +1 -1
- 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/dist/constants.d.ts.map +1 -1
- package/dist/index.d.ts +1 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +41 -2
- package/dist/index.js.map +1 -1
- package/dist/prepare-docs/code-examples.d.ts.map +1 -1
- package/dist/prepare-docs/code-examples.js.map +1 -1
- package/dist/prepare-docs/copy-raw.d.ts.map +1 -1
- package/dist/prepare-docs/prepare.d.ts +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/stdio.d.ts +1 -1
- package/dist/tools/docs.d.ts +8 -14
- package/dist/tools/docs.d.ts.map +1 -1
- package/dist/tools/docs.js +26 -10
- package/dist/tools/docs.js.map +1 -1
- package/dist/tools/examples.d.ts +6 -12
- package/dist/tools/examples.d.ts.map +1 -1
- package/dist/tools/examples.js +11 -8
- package/dist/tools/examples.js.map +1 -1
- package/dist/tools/resources.d.ts +1 -2
- package/dist/tools/resources.d.ts.map +1 -1
- package/dist/tools/resources.js +1 -1
- package/dist/tools/resources.js.map +1 -1
- package/dist/tools/search.d.ts +6 -15
- package/dist/tools/search.d.ts.map +1 -1
- package/dist/tools/search.js +2 -2
- package/dist/tools/search.js.map +1 -1
- package/dist/tools/tests/mcp-test-client.d.ts +15 -0
- package/dist/tools/tests/mcp-test-client.d.ts.map +1 -0
- package/dist/tools/tests/mcp-test-client.js +68 -0
- package/dist/tools/tests/mcp-test-client.js.map +1 -0
- package/dist/tools/tests/test-setup.d.ts.map +1 -1
- package/dist/tools/tests/test-setup.js +5 -1
- package/dist/tools/tests/test-setup.js.map +1 -1
- package/dist/tools/xulux-templates.d.ts +58 -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.map +1 -1
- package/dist/utils/paths.d.ts +1 -1
- package/dist/utils/paths.d.ts.map +1 -1
- package/dist/utils/paths.js +3 -1
- package/dist/utils/paths.js.map +1 -1
- package/dist/utils/search.d.ts.map +1 -1
- package/dist/utils/security.d.ts.map +1 -1
- package/dist/utils/security.js.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 +7 -6
- package/src/index.ts +55 -2
- package/src/prompts/xulux-playground.ts +36 -0
- package/src/tools/docs.ts +27 -5
- package/src/tools/examples.ts +17 -12
- package/src/tools/resources.ts +1 -4
- package/src/tools/search.ts +2 -2
- package/src/tools/tests/completions.test.ts +40 -26
- package/src/tools/tests/docs.test.ts +20 -0
- package/src/tools/tests/examples.test.ts +5 -5
- package/src/tools/tests/integration.test.ts +3 -4
- package/src/tools/tests/listings-cache.test.ts +19 -0
- package/src/tools/tests/mcp-protocol.test.ts +173 -108
- package/src/tools/tests/mcp-test-client.ts +111 -0
- package/src/tools/tests/resources.test.ts +97 -66
- package/src/tools/tests/test-setup.ts +8 -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/paths.ts +4 -1
- 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/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.
|
|
@@ -66,9 +66,9 @@ const AssistantMessage = () => {
|
|
|
66
66
|
};
|
|
67
67
|
```
|
|
68
68
|
|
|
69
|
-
`ActionBarPrimitive.Edit` calls `aui.composer
|
|
69
|
+
`ActionBarPrimitive.Edit` calls `aui.composer.beginEdit()` under the hood and is disabled when the composer is already in edit mode.
|
|
70
70
|
|
|
71
|
-
`ComposerPrimitive.Cancel` calls `aui.composer
|
|
71
|
+
`ComposerPrimitive.Cancel` calls `aui.composer.cancel()`, which exits edit mode and restores the original message content. See [Composer primitives](/docs/primitives/composer) for the full composer API.
|
|
72
72
|
|
|
73
73
|
## Detecting Edit Mode
|
|
74
74
|
|
|
@@ -76,7 +76,7 @@ The `isEditing` flag is available on both `ThreadComposerState` and `EditCompose
|
|
|
76
76
|
|
|
77
77
|
## Imperative API
|
|
78
78
|
|
|
79
|
-
`aui.composer
|
|
79
|
+
`aui.composer.beginEdit()` is the programmatic entry point for entering edit mode on a message. Use it for headless or keyboard-shortcut-driven flows where `ActionBarPrimitive.Edit` is not rendered:
|
|
80
80
|
|
|
81
81
|
```tsx
|
|
82
82
|
import { useAui } from "@assistant-ui/react";
|
|
@@ -84,12 +84,12 @@ import { useAui } from "@assistant-ui/react";
|
|
|
84
84
|
const EditButton = () => {
|
|
85
85
|
const aui = useAui();
|
|
86
86
|
return (
|
|
87
|
-
<button onClick={() => aui.composer
|
|
87
|
+
<button onClick={() => aui.composer.beginEdit()}>Edit</button>
|
|
88
88
|
);
|
|
89
89
|
};
|
|
90
90
|
```
|
|
91
91
|
|
|
92
|
-
`aui.composer
|
|
92
|
+
`aui.composer.cancel()` exits edit mode without re-submitting.
|
|
93
93
|
|
|
94
94
|
## Editing While Streaming
|
|
95
95
|
|