@assistant-ui/mcp-docs-server 0.1.29 → 0.1.31
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 +15 -7
- package/.docs/organized/code-examples/with-a2a.md +9 -21
- package/.docs/organized/code-examples/with-ag-ui.md +11 -8
- package/.docs/organized/code-examples/with-ai-sdk-v6.md +10 -10
- package/.docs/organized/code-examples/with-artifacts.md +12 -10
- package/.docs/organized/code-examples/with-assistant-transport.md +11 -12
- package/.docs/organized/code-examples/with-chain-of-thought.md +83 -54
- package/.docs/organized/code-examples/with-cloud-standalone.md +14 -11
- package/.docs/organized/code-examples/with-cloud.md +9 -10
- package/.docs/organized/code-examples/with-custom-thread-list.md +61 -16
- package/.docs/organized/code-examples/with-elevenlabs-conversational.md +17 -12
- package/.docs/organized/code-examples/with-elevenlabs-scribe.md +13 -13
- package/.docs/organized/code-examples/with-expo.md +25 -21
- package/.docs/organized/code-examples/with-external-store.md +8 -8
- package/.docs/organized/code-examples/with-ffmpeg.md +17 -12
- package/.docs/organized/code-examples/with-generative-ui.md +9 -9
- package/.docs/organized/code-examples/with-google-adk.md +8 -8
- package/.docs/organized/code-examples/with-heat-graph.md +5 -5
- package/.docs/organized/code-examples/with-interactables.md +10 -25
- package/.docs/organized/code-examples/with-langchain.md +437 -0
- package/.docs/organized/code-examples/with-langgraph.md +16 -16
- package/.docs/organized/code-examples/with-livekit.md +18 -13
- package/.docs/organized/code-examples/with-opencode.md +105 -62
- package/.docs/organized/code-examples/with-parent-id-grouping.md +10 -10
- package/.docs/organized/code-examples/with-react-hook-form.md +220 -148
- package/.docs/organized/code-examples/with-react-ink.md +2 -2
- package/.docs/organized/code-examples/with-react-router.md +12 -12
- package/.docs/organized/code-examples/with-store.md +8 -5
- package/.docs/organized/code-examples/with-tanstack.md +10 -10
- package/.docs/organized/code-examples/with-tap-runtime.md +10 -6
- package/.docs/raw/docs/(docs)/cli.mdx +2 -1
- package/.docs/raw/docs/(docs)/copilots/assistant-frame.mdx +1 -0
- package/.docs/raw/docs/(docs)/copilots/make-assistant-tool-ui.mdx +10 -3
- package/.docs/raw/docs/(docs)/copilots/make-assistant-tool.mdx +8 -3
- package/.docs/raw/docs/(docs)/copilots/make-assistant-visible.mdx +1 -0
- package/.docs/raw/docs/(docs)/copilots/model-context.mdx +1 -0
- package/.docs/raw/docs/(docs)/copilots/motivation.mdx +1 -0
- package/.docs/raw/docs/(docs)/copilots/use-assistant-instructions.mdx +1 -0
- package/.docs/raw/docs/(docs)/devtools.mdx +1 -0
- package/.docs/raw/docs/(docs)/index.mdx +1 -0
- package/.docs/raw/docs/(docs)/installation.mdx +1 -0
- package/.docs/raw/docs/(docs)/rtl.mdx +80 -0
- package/.docs/raw/docs/(reference)/api-reference/adapters/attachments.mdx +34 -0
- package/.docs/raw/docs/(reference)/api-reference/adapters/feedback-speech.mdx +41 -0
- package/.docs/raw/docs/(reference)/api-reference/adapters/index.mdx +26 -0
- package/.docs/raw/docs/(reference)/api-reference/adapters/persistence.mdx +34 -0
- package/.docs/raw/docs/(reference)/api-reference/adapters/runtime.mdx +31 -0
- package/.docs/raw/docs/(reference)/api-reference/context-providers/index.mdx +20 -0
- package/.docs/raw/docs/(reference)/api-reference/hooks/index.mdx +26 -0
- package/.docs/raw/docs/(reference)/api-reference/hooks/model-context.mdx +72 -0
- package/.docs/raw/docs/(reference)/api-reference/hooks/runtimes.mdx +41 -0
- package/.docs/raw/docs/(reference)/api-reference/hooks/state.mdx +48 -0
- package/.docs/raw/docs/(reference)/api-reference/hooks/utilities.mdx +30 -0
- package/.docs/raw/docs/(reference)/api-reference/integrations/index.mdx +23 -0
- package/.docs/raw/docs/(reference)/api-reference/overview.mdx +21 -0
- package/.docs/raw/docs/(reference)/api-reference/primitives/assistant-if.mdx +7 -0
- package/.docs/raw/docs/(reference)/api-reference/primitives/composer.mdx +149 -40
- package/.docs/raw/docs/(reference)/api-reference/primitives/index.mdx +65 -0
- package/.docs/raw/docs/(reference)/api-reference/primitives/message-part.mdx +2 -0
- package/.docs/raw/docs/(reference)/api-reference/primitives/message.mdx +50 -6
- package/.docs/raw/docs/(reference)/api-reference/primitives/thread-list.mdx +15 -0
- package/.docs/raw/docs/(reference)/api-reference/primitives/thread.mdx +36 -1
- package/.docs/raw/docs/(reference)/api-reference/runtimes/index.mdx +38 -0
- package/.docs/raw/docs/(reference)/api-reference/runtimes/thread-list-runtime.mdx +9 -0
- package/.docs/raw/docs/(reference)/migrations/v0-14.mdx +144 -6
- package/.docs/raw/docs/cloud/ai-sdk-assistant-ui.mdx +231 -3
- package/.docs/raw/docs/cloud/ai-sdk.mdx +221 -3
- package/.docs/raw/docs/cloud/langgraph.mdx +274 -2
- package/.docs/raw/docs/{(docs)/guides → guides}/attachments.mdx +41 -36
- package/.docs/raw/docs/guides/branching.mdx +76 -0
- package/.docs/raw/docs/guides/chain-of-thought.mdx +166 -0
- package/.docs/raw/docs/{(docs)/guides → guides}/context-api.mdx +50 -22
- package/.docs/raw/docs/{(docs)/guides → guides}/dictation.mdx +2 -0
- package/.docs/raw/docs/guides/editing.mdx +102 -0
- package/.docs/raw/docs/guides/index.mdx +103 -0
- package/.docs/raw/docs/{(docs)/guides → guides}/interactables.mdx +49 -0
- package/.docs/raw/docs/{(docs)/guides → guides}/latex.mdx +51 -8
- package/.docs/raw/docs/guides/mentions.mdx +520 -0
- package/.docs/raw/docs/{(docs)/guides → guides}/message-timing.mdx +8 -2
- package/.docs/raw/docs/{(docs)/guides → guides}/multi-agent.mdx +64 -4
- package/.docs/raw/docs/{(docs)/guides → guides}/quoting.mdx +10 -17
- package/.docs/raw/docs/guides/slash-commands.mdx +361 -0
- package/.docs/raw/docs/guides/speech.mdx +156 -0
- package/.docs/raw/docs/{(docs)/guides → guides}/suggestions.mdx +21 -83
- package/.docs/raw/docs/{(docs)/guides → guides}/tool-ui.mdx +108 -36
- package/.docs/raw/docs/{(docs)/guides → guides}/tools.mdx +131 -35
- package/.docs/raw/docs/{(docs)/guides → guides}/voice.mdx +39 -0
- package/.docs/raw/docs/ink/index.mdx +1 -3
- package/.docs/raw/docs/ink/migration.mdx +1 -3
- package/.docs/raw/docs/ink/primitives.mdx +37 -1
- package/.docs/raw/docs/integrations/attachments/custom-adapter.mdx +520 -0
- package/.docs/raw/docs/integrations/auth/better-auth.mdx +191 -0
- package/.docs/raw/docs/integrations/auth/clerk.mdx +172 -0
- package/.docs/raw/docs/integrations/auth/next-auth.mdx +196 -0
- package/.docs/raw/docs/integrations/frameworks/ai-sdk.mdx +79 -0
- package/.docs/raw/docs/integrations/frameworks/mastra/full-stack.mdx +188 -0
- package/.docs/raw/docs/integrations/frameworks/mastra/overview.mdx +57 -0
- package/.docs/raw/docs/integrations/frameworks/mastra/separate-server.mdx +201 -0
- package/.docs/raw/docs/integrations/gateways/index.mdx +157 -0
- package/.docs/raw/docs/integrations/index.mdx +173 -0
- package/.docs/raw/docs/integrations/observability/helicone.mdx +130 -0
- package/.docs/raw/docs/integrations/observability/langfuse.mdx +156 -0
- package/.docs/raw/docs/integrations/observability/langsmith.mdx +146 -0
- package/.docs/raw/docs/integrations/persistence/custom-adapter.mdx +712 -0
- package/.docs/raw/docs/integrations/tools/mcp.mdx +267 -0
- package/.docs/raw/docs/primitives/action-bar.mdx +1 -0
- package/.docs/raw/docs/primitives/assistant-modal.mdx +1 -0
- package/.docs/raw/docs/primitives/attachment.mdx +1 -0
- package/.docs/raw/docs/primitives/branch-picker.mdx +1 -0
- package/.docs/raw/docs/primitives/chain-of-thought.mdx +90 -85
- package/.docs/raw/docs/primitives/composer.mdx +96 -63
- package/.docs/raw/docs/primitives/error.mdx +1 -0
- package/.docs/raw/docs/primitives/index.mdx +2 -1
- package/.docs/raw/docs/primitives/message.mdx +68 -5
- package/.docs/raw/docs/primitives/selection-toolbar.mdx +1 -0
- package/.docs/raw/docs/primitives/suggestion.mdx +1 -0
- package/.docs/raw/docs/primitives/thread-list.mdx +39 -0
- package/.docs/raw/docs/primitives/thread.mdx +16 -13
- package/.docs/raw/docs/react-native/index.mdx +1 -3
- package/.docs/raw/docs/react-native/migration.mdx +1 -3
- package/.docs/raw/docs/runtimes/a2a/client-and-hooks.mdx +396 -0
- package/.docs/raw/docs/runtimes/a2a/overview.mdx +60 -0
- package/.docs/raw/docs/runtimes/a2a/quickstart.mdx +216 -0
- package/.docs/raw/docs/runtimes/ag-ui/overview.mdx +70 -0
- package/.docs/raw/docs/runtimes/ag-ui/quickstart.mdx +243 -0
- package/.docs/raw/docs/runtimes/ag-ui/runtime-options.mdx +123 -0
- package/.docs/raw/docs/runtimes/ai-sdk/overview.mdx +52 -0
- package/.docs/raw/docs/runtimes/ai-sdk/v4-legacy.mdx +71 -131
- package/.docs/raw/docs/runtimes/ai-sdk/v5-legacy.mdx +69 -63
- package/.docs/raw/docs/runtimes/ai-sdk/v6.mdx +365 -101
- package/.docs/raw/docs/runtimes/concepts/adapters.mdx +265 -0
- package/.docs/raw/docs/runtimes/concepts/architecture.mdx +125 -0
- package/.docs/raw/docs/runtimes/concepts/stability.mdx +67 -0
- package/.docs/raw/docs/runtimes/concepts/threads.mdx +428 -0
- package/.docs/raw/docs/runtimes/custom/assistant-transport.mdx +703 -0
- package/.docs/raw/docs/runtimes/custom/data-stream.mdx +323 -0
- package/.docs/raw/docs/runtimes/custom/external-store.mdx +253 -1236
- package/.docs/raw/docs/runtimes/custom/local-runtime.mdx +746 -0
- package/.docs/raw/docs/runtimes/custom/overview.mdx +71 -0
- package/.docs/raw/docs/runtimes/google-adk/api.mdx +256 -0
- package/.docs/raw/docs/runtimes/google-adk/hooks.mdx +717 -0
- package/.docs/raw/docs/runtimes/google-adk/overview.mdx +69 -0
- package/.docs/raw/docs/runtimes/google-adk/quickstart.mdx +229 -0
- package/.docs/raw/docs/runtimes/langchain.mdx +533 -0
- package/.docs/raw/docs/runtimes/langgraph/generative-ui.mdx +305 -0
- package/.docs/raw/docs/runtimes/langgraph/interrupts.mdx +104 -0
- package/.docs/raw/docs/runtimes/langgraph/overview.mdx +84 -0
- package/.docs/raw/docs/runtimes/langgraph/quickstart.mdx +496 -0
- package/.docs/raw/docs/runtimes/langgraph/streaming.mdx +127 -0
- package/.docs/raw/docs/runtimes/langgraph/threads.mdx +113 -0
- package/.docs/raw/docs/runtimes/langgraph/tutorial/introduction.mdx +3 -3
- package/.docs/raw/docs/runtimes/langgraph/tutorial/part-1.mdx +0 -23
- package/.docs/raw/docs/runtimes/langgraph/tutorial/part-2.mdx +1 -1
- package/.docs/raw/docs/runtimes/opencode/hooks.mdx +191 -0
- package/.docs/raw/docs/runtimes/opencode/overview.mdx +48 -0
- package/.docs/raw/docs/runtimes/opencode/quickstart.mdx +119 -0
- package/.docs/raw/docs/runtimes/pick-a-runtime.mdx +74 -198
- package/.docs/raw/docs/ui/accordion.mdx +1 -0
- package/.docs/raw/docs/ui/assistant-modal.mdx +1 -0
- package/.docs/raw/docs/ui/assistant-sidebar.mdx +1 -0
- package/.docs/raw/docs/ui/attachment.mdx +1 -0
- package/.docs/raw/docs/ui/badge.mdx +1 -0
- package/.docs/raw/docs/ui/composer-trigger-popover.mdx +200 -0
- package/.docs/raw/docs/ui/context-display.mdx +1 -0
- package/.docs/raw/docs/ui/diff-viewer.mdx +1 -0
- package/.docs/raw/docs/ui/directive-text.mdx +114 -0
- package/.docs/raw/docs/ui/file.mdx +1 -0
- package/.docs/raw/docs/ui/image.mdx +1 -0
- package/.docs/raw/docs/ui/markdown.mdx +2 -14
- package/.docs/raw/docs/ui/mermaid.mdx +1 -0
- package/.docs/raw/docs/ui/message-timing.mdx +3 -2
- package/.docs/raw/docs/ui/model-selector.mdx +1 -0
- package/.docs/raw/docs/ui/part-grouping.mdx +325 -313
- package/.docs/raw/docs/ui/quote.mdx +1 -0
- package/.docs/raw/docs/ui/reasoning.mdx +69 -32
- package/.docs/raw/docs/ui/scrollbar.mdx +1 -0
- package/.docs/raw/docs/ui/select.mdx +1 -0
- package/.docs/raw/docs/ui/sources.mdx +1 -0
- package/.docs/raw/docs/ui/streamdown.mdx +1 -0
- package/.docs/raw/docs/ui/syntax-highlighting.mdx +1 -0
- package/.docs/raw/docs/ui/tabs.mdx +1 -0
- package/.docs/raw/docs/ui/thread-list.mdx +17 -0
- package/.docs/raw/docs/ui/thread.mdx +56 -1
- package/.docs/raw/docs/ui/tool-fallback.mdx +1 -0
- package/.docs/raw/docs/ui/tool-group.mdx +39 -11
- package/.docs/raw/docs/ui/voice.mdx +1 -0
- package/.docs/raw/docs/utilities/heat-graph.mdx +1 -0
- package/.docs/raw/docs/utilities/react-o11y.mdx +278 -0
- package/.docs/raw/docs/utilities/tw-shimmer.mdx +1 -0
- package/dist/utils/logger.js +1 -1
- package/dist/utils/logger.js.map +1 -1
- package/package.json +4 -4
- package/src/tools/tests/path-traversal.test.ts +1 -1
- package/src/utils/logger.ts +1 -1
- package/.docs/raw/docs/(docs)/guides/branching.mdx +0 -65
- package/.docs/raw/docs/(docs)/guides/chain-of-thought.mdx +0 -164
- package/.docs/raw/docs/(docs)/guides/editing.mdx +0 -66
- package/.docs/raw/docs/(docs)/guides/mentions.mdx +0 -406
- package/.docs/raw/docs/(docs)/guides/slash-commands.mdx +0 -275
- package/.docs/raw/docs/(docs)/guides/speech.mdx +0 -38
- package/.docs/raw/docs/runtimes/a2a/index.mdx +0 -298
- package/.docs/raw/docs/runtimes/assistant-transport.mdx +0 -1033
- package/.docs/raw/docs/runtimes/custom/custom-thread-list.mdx +0 -268
- package/.docs/raw/docs/runtimes/custom/local.mdx +0 -1464
- package/.docs/raw/docs/runtimes/data-stream.mdx +0 -422
- package/.docs/raw/docs/runtimes/google-adk/index.mdx +0 -686
- package/.docs/raw/docs/runtimes/helicone.mdx +0 -61
- package/.docs/raw/docs/runtimes/langgraph/index.mdx +0 -607
- package/.docs/raw/docs/runtimes/langgraph/tutorial/index.mdx +0 -12
- package/.docs/raw/docs/runtimes/langserve.mdx +0 -116
- package/.docs/raw/docs/runtimes/mastra/full-stack-integration.mdx +0 -218
- package/.docs/raw/docs/runtimes/mastra/overview.mdx +0 -18
- package/.docs/raw/docs/runtimes/mastra/separate-server-integration.mdx +0 -217
- package/.docs/raw/docs/ui/mention.mdx +0 -168
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: Quote Selected Text
|
|
3
3
|
description: Let users select and quote text from messages. Full guide including backend handling and programmatic API.
|
|
4
|
+
platforms: ["react"]
|
|
4
5
|
---
|
|
5
6
|
|
|
6
7
|
import { QuoteComposerSample } from "@/components/docs/samples/quote-composer";
|
|
@@ -9,17 +10,11 @@ import { QuoteComposerSample } from "@/components/docs/samples/quote-composer";
|
|
|
9
10
|
|
|
10
11
|
## Get Started
|
|
11
12
|
|
|
12
|
-
|
|
13
|
+
Install and wire up the `quote` registry component by following the [Quote component page](/docs/ui/quote). It covers the install command, component placement, and the `injectQuoteContext` helper for forwarding quote data to the LLM.
|
|
13
14
|
|
|
14
|
-
<
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
- **QuoteBlock** renders quoted text inline in user messages
|
|
19
|
-
- **SelectionToolbar** is a floating toolbar that appears on text selection
|
|
20
|
-
- **ComposerQuotePreview** shows the pending quote inside the composer
|
|
21
|
-
|
|
22
|
-
See the [Quote component page](/docs/ui/quote) for full setup steps and API reference.
|
|
15
|
+
<Callout type="warn">
|
|
16
|
+
**Limitations:** only one quote can be active at a time. `setQuote` replaces the previous quote instead of appending. The floating toolbar only appears when the selection is entirely within a single message part; cross-message and cross-part selections are ignored.
|
|
17
|
+
</Callout>
|
|
23
18
|
|
|
24
19
|
---
|
|
25
20
|
|
|
@@ -132,7 +127,7 @@ function CustomQuoteDisplay() {
|
|
|
132
127
|
|
|
133
128
|
## Programmatic API
|
|
134
129
|
|
|
135
|
-
Set or clear quotes via
|
|
130
|
+
Set or clear quotes via `useAui` from `@assistant-ui/react`. Call `aui.thread().composer().setQuote()` when your component is rendered outside of a specific thread context, or `aui.composer().setQuote()` when it is rendered inside a thread:
|
|
136
131
|
|
|
137
132
|
```tsx
|
|
138
133
|
import { useAui } from "@assistant-ui/react";
|
|
@@ -162,12 +157,10 @@ function MyComponent() {
|
|
|
162
157
|
|
|
163
158
|
## Design Notes
|
|
164
159
|
|
|
165
|
-
- **
|
|
166
|
-
- **
|
|
167
|
-
-
|
|
168
|
-
- **
|
|
169
|
-
- **`isEmpty` unchanged:** A quote by itself does not make the composer non-empty. The user still needs to type a reply.
|
|
170
|
-
- **Scroll hides toolbar:** The toolbar hides on scroll because its position would otherwise become stale.
|
|
160
|
+
- **snapshot text:** the selected text is captured when the quote is created and is not linked to the source message afterward.
|
|
161
|
+
- **streaming messages:** the toolbar still works while a message is streaming because it relies on the captured selection rather than message status.
|
|
162
|
+
- **`isEmpty` unchanged:** a quote by itself does not make the composer non-empty; the user still needs to type a reply.
|
|
163
|
+
- **scroll hides toolbar:** the toolbar hides on scroll because its position would otherwise become stale.
|
|
171
164
|
|
|
172
165
|
## Related
|
|
173
166
|
|
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Slash Commands
|
|
3
|
+
description: Let users type / in the composer to trigger predefined actions from a popover picker.
|
|
4
|
+
platforms: ["react"]
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
Slash commands let users type `/` in the composer to open a popover, browse available commands, and execute one. Unlike [mentions](/docs/guides/mentions) (which only insert a directive into the message), slash commands additionally fire an **action callback** at the moment of selection.
|
|
8
|
+
|
|
9
|
+
## How It Works
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
User types "/" → Trigger detected → Adapter provides commands
|
|
13
|
+
↓
|
|
14
|
+
Callback fired ← User selects command from popover
|
|
15
|
+
↓
|
|
16
|
+
Directive chip left in composer (or removed if removeOnExecute)
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
The slash command system is built on the same [trigger popover architecture](#trigger-popover-architecture) as mentions. A slash command declares its behavior with a `<TriggerPopover.Action>` sub-primitive whose `onExecute` callback fires when an item is chosen.
|
|
20
|
+
|
|
21
|
+
By default `Action` leaves a directive chip in the composer — giving the user (and the LLM) an audit trail of which commands were invoked. Pass `removeOnExecute` to strip the `/command` text entirely.
|
|
22
|
+
|
|
23
|
+
## Quick Start
|
|
24
|
+
|
|
25
|
+
### 1. Define Commands with `unstable_useSlashCommandAdapter`
|
|
26
|
+
|
|
27
|
+
Declare commands (data + `execute` bundled together, like `useAssistantTool`). The hook returns `{ adapter, action }` — wire both into a single `<TriggerPopover>`:
|
|
28
|
+
|
|
29
|
+
```tsx
|
|
30
|
+
import {
|
|
31
|
+
ComposerPrimitive,
|
|
32
|
+
unstable_useSlashCommandAdapter,
|
|
33
|
+
type Unstable_SlashCommand,
|
|
34
|
+
} from "@assistant-ui/react";
|
|
35
|
+
|
|
36
|
+
const SLASH_COMMANDS: readonly Unstable_SlashCommand[] = [
|
|
37
|
+
{
|
|
38
|
+
id: "summarize",
|
|
39
|
+
description: "Summarize the conversation",
|
|
40
|
+
execute: () => console.log("Summarize!"),
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
id: "translate",
|
|
44
|
+
description: "Translate text to another language",
|
|
45
|
+
execute: () => console.log("Translate!"),
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
id: "help",
|
|
49
|
+
description: "List all available commands",
|
|
50
|
+
execute: () => console.log("Help!"),
|
|
51
|
+
},
|
|
52
|
+
];
|
|
53
|
+
|
|
54
|
+
function MyComposer() {
|
|
55
|
+
const slash = unstable_useSlashCommandAdapter({ commands: SLASH_COMMANDS });
|
|
56
|
+
|
|
57
|
+
return (
|
|
58
|
+
<ComposerPrimitive.Unstable_TriggerPopoverRoot>
|
|
59
|
+
<ComposerPrimitive.Root>
|
|
60
|
+
<ComposerPrimitive.Input placeholder="Type / for commands..." />
|
|
61
|
+
<ComposerPrimitive.Send>Send</ComposerPrimitive.Send>
|
|
62
|
+
|
|
63
|
+
<ComposerPrimitive.Unstable_TriggerPopover
|
|
64
|
+
char="/"
|
|
65
|
+
adapter={slash.adapter}
|
|
66
|
+
className="popover"
|
|
67
|
+
>
|
|
68
|
+
<ComposerPrimitive.Unstable_TriggerPopover.Action {...slash.action} />
|
|
69
|
+
<ComposerPrimitive.Unstable_TriggerPopoverItems>
|
|
70
|
+
{(items) =>
|
|
71
|
+
items.map((item, index) => (
|
|
72
|
+
<ComposerPrimitive.Unstable_TriggerPopoverItem
|
|
73
|
+
key={item.id}
|
|
74
|
+
item={item}
|
|
75
|
+
index={index}
|
|
76
|
+
className="popover-item"
|
|
77
|
+
>
|
|
78
|
+
<strong>{item.label}</strong>
|
|
79
|
+
{item.description && <span>{item.description}</span>}
|
|
80
|
+
</ComposerPrimitive.Unstable_TriggerPopoverItem>
|
|
81
|
+
))
|
|
82
|
+
}
|
|
83
|
+
</ComposerPrimitive.Unstable_TriggerPopoverItems>
|
|
84
|
+
</ComposerPrimitive.Unstable_TriggerPopover>
|
|
85
|
+
</ComposerPrimitive.Root>
|
|
86
|
+
</ComposerPrimitive.Unstable_TriggerPopoverRoot>
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
The label defaults to `/${id}`; override via `label` on the command. Icons are strings that your `iconMap` on the picker UI resolves to components (see [ComposerTriggerPopover](/docs/ui/composer-trigger-popover)).
|
|
92
|
+
|
|
93
|
+
### `unstable_useSlashCommandAdapter` options
|
|
94
|
+
|
|
95
|
+
| Option | Type | Default | Description |
|
|
96
|
+
| --- | --- | --- | --- |
|
|
97
|
+
| `commands` | `Unstable_SlashCommand[]` | — | Command definitions — each has `id`, optional `label`, `description`, `icon`, and an `execute` callback (required) |
|
|
98
|
+
| `removeOnExecute` | `boolean` | `false` | When `true`, strips the trigger text from the composer after executing instead of leaving a directive chip |
|
|
99
|
+
| `iconMap` | `Record<string, IconComponent>` | — | Maps `metadata.icon` / category `id` strings to React icon components; forwarded to `ComposerTriggerPopover` |
|
|
100
|
+
| `fallbackIcon` | `IconComponent` | — | Fallback when no `iconMap` entry matches; forwarded to `ComposerTriggerPopover` |
|
|
101
|
+
|
|
102
|
+
The hook returns `{ adapter, action, iconMap?, fallbackIcon? }` — spread directly into `<ComposerTriggerPopover char="/" {...slash} />` for one-line wiring.
|
|
103
|
+
|
|
104
|
+
### 2. Controlling the Chip
|
|
105
|
+
|
|
106
|
+
By default, a selected `/summarize` is converted into a directive chip (`:command[/summarize]{name=summarize}`) in the composer text and the command's `execute` fires. This keeps an audit trail of which commands were invoked.
|
|
107
|
+
|
|
108
|
+
To strip the trigger text entirely — useful for purely transient commands — pass `removeOnExecute` on the hook options:
|
|
109
|
+
|
|
110
|
+
```tsx
|
|
111
|
+
const slash = unstable_useSlashCommandAdapter({
|
|
112
|
+
commands: SLASH_COMMANDS,
|
|
113
|
+
removeOnExecute: true,
|
|
114
|
+
});
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### 3. Custom Dispatch
|
|
118
|
+
|
|
119
|
+
For side effects on top of `execute` (logging, analytics, intercept), wrap the hook's `onExecute`:
|
|
120
|
+
|
|
121
|
+
```tsx
|
|
122
|
+
<ComposerPrimitive.Unstable_TriggerPopover.Action
|
|
123
|
+
onExecute={(item) => {
|
|
124
|
+
logCommandUsed(item.id);
|
|
125
|
+
slash.action.onExecute(item);
|
|
126
|
+
}}
|
|
127
|
+
/>
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## Categorized Commands
|
|
131
|
+
|
|
132
|
+
For **categorized navigation** (drill-down into groups), return categories from `categories()` and items from `categoryItems()`. The popover shows categories first, then items within the selected category:
|
|
133
|
+
|
|
134
|
+
```ts
|
|
135
|
+
import type { Unstable_TriggerAdapter } from "@assistant-ui/core";
|
|
136
|
+
|
|
137
|
+
const adapter: Unstable_TriggerAdapter = {
|
|
138
|
+
categories() {
|
|
139
|
+
return [
|
|
140
|
+
{ id: "actions", label: "Actions" },
|
|
141
|
+
{ id: "export", label: "Export" },
|
|
142
|
+
];
|
|
143
|
+
},
|
|
144
|
+
|
|
145
|
+
categoryItems(categoryId) {
|
|
146
|
+
if (categoryId === "actions") {
|
|
147
|
+
return [
|
|
148
|
+
{ id: "summarize", type: "command", label: "/summarize", description: "Summarize the conversation" },
|
|
149
|
+
{ id: "translate", type: "command", label: "/translate", description: "Translate text" },
|
|
150
|
+
];
|
|
151
|
+
}
|
|
152
|
+
if (categoryId === "export") {
|
|
153
|
+
return [
|
|
154
|
+
{ id: "pdf", type: "command", label: "/export pdf", description: "Export as PDF" },
|
|
155
|
+
{ id: "markdown", type: "command", label: "/export md", description: "Export as Markdown" },
|
|
156
|
+
];
|
|
157
|
+
}
|
|
158
|
+
return [];
|
|
159
|
+
},
|
|
160
|
+
|
|
161
|
+
// Optional — enables search across all categories
|
|
162
|
+
search(query) {
|
|
163
|
+
const lower = query.toLowerCase();
|
|
164
|
+
const all = [...this.categoryItems("actions"), ...this.categoryItems("export")];
|
|
165
|
+
return all.filter(
|
|
166
|
+
(item) => item.label.toLowerCase().includes(lower) || item.description?.toLowerCase().includes(lower),
|
|
167
|
+
);
|
|
168
|
+
},
|
|
169
|
+
};
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
When using a categorized adapter, add `TriggerPopoverCategories` to your popover UI:
|
|
173
|
+
|
|
174
|
+
```tsx
|
|
175
|
+
const commandHandlers: Record<string, () => void> = {
|
|
176
|
+
summarize: () => {/* ... */},
|
|
177
|
+
pdf: () => {/* ... */},
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
<ComposerPrimitive.Unstable_TriggerPopover
|
|
181
|
+
char="/"
|
|
182
|
+
adapter={adapter}
|
|
183
|
+
>
|
|
184
|
+
<ComposerPrimitive.Unstable_TriggerPopover.Action
|
|
185
|
+
formatter={unstable_defaultDirectiveFormatter}
|
|
186
|
+
onExecute={(item) => commandHandlers[item.id]?.()}
|
|
187
|
+
/>
|
|
188
|
+
<ComposerPrimitive.Unstable_TriggerPopoverBack>← Back</ComposerPrimitive.Unstable_TriggerPopoverBack>
|
|
189
|
+
<ComposerPrimitive.Unstable_TriggerPopoverCategories>
|
|
190
|
+
{(categories) => categories.map((cat) => (
|
|
191
|
+
<ComposerPrimitive.Unstable_TriggerPopoverCategoryItem key={cat.id} categoryId={cat.id}>
|
|
192
|
+
{cat.label}
|
|
193
|
+
</ComposerPrimitive.Unstable_TriggerPopoverCategoryItem>
|
|
194
|
+
))}
|
|
195
|
+
</ComposerPrimitive.Unstable_TriggerPopoverCategories>
|
|
196
|
+
<ComposerPrimitive.Unstable_TriggerPopoverItems>
|
|
197
|
+
{(items) => items.map((item, index) => (
|
|
198
|
+
<ComposerPrimitive.Unstable_TriggerPopoverItem key={item.id} item={item} index={index}>
|
|
199
|
+
{item.label}
|
|
200
|
+
</ComposerPrimitive.Unstable_TriggerPopoverItem>
|
|
201
|
+
))}
|
|
202
|
+
</ComposerPrimitive.Unstable_TriggerPopoverItems>
|
|
203
|
+
</ComposerPrimitive.Unstable_TriggerPopover>
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
## Combining with Mentions
|
|
207
|
+
|
|
208
|
+
Slash commands and mentions live under the same `TriggerPopoverRoot`. Declare one `TriggerPopover` per trigger — each with its own behavior sub-primitive:
|
|
209
|
+
|
|
210
|
+
```tsx
|
|
211
|
+
<ComposerPrimitive.Unstable_TriggerPopoverRoot>
|
|
212
|
+
<ComposerPrimitive.Root>
|
|
213
|
+
<ComposerPrimitive.Input placeholder="Type @ to mention, / for commands..." />
|
|
214
|
+
<ComposerPrimitive.Send>Send</ComposerPrimitive.Send>
|
|
215
|
+
|
|
216
|
+
{/* @ mention popover */}
|
|
217
|
+
<ComposerPrimitive.Unstable_TriggerPopover
|
|
218
|
+
char="@"
|
|
219
|
+
adapter={mention.adapter}
|
|
220
|
+
>
|
|
221
|
+
<ComposerPrimitive.Unstable_TriggerPopover.Directive {...mention.directive} />
|
|
222
|
+
<ComposerPrimitive.Unstable_TriggerPopoverItems>
|
|
223
|
+
{(items) => items.map((item) => (
|
|
224
|
+
<ComposerPrimitive.Unstable_TriggerPopoverItem key={item.id} item={item}>
|
|
225
|
+
{item.label}
|
|
226
|
+
</ComposerPrimitive.Unstable_TriggerPopoverItem>
|
|
227
|
+
))}
|
|
228
|
+
</ComposerPrimitive.Unstable_TriggerPopoverItems>
|
|
229
|
+
</ComposerPrimitive.Unstable_TriggerPopover>
|
|
230
|
+
|
|
231
|
+
{/* / slash command popover */}
|
|
232
|
+
<ComposerPrimitive.Unstable_TriggerPopover
|
|
233
|
+
char="/"
|
|
234
|
+
adapter={slash.adapter}
|
|
235
|
+
>
|
|
236
|
+
<ComposerPrimitive.Unstable_TriggerPopover.Action {...slash.action} />
|
|
237
|
+
<ComposerPrimitive.Unstable_TriggerPopoverItems>
|
|
238
|
+
{(items) => items.map((item, index) => (
|
|
239
|
+
<ComposerPrimitive.Unstable_TriggerPopoverItem key={item.id} item={item} index={index}>
|
|
240
|
+
{item.label}
|
|
241
|
+
</ComposerPrimitive.Unstable_TriggerPopoverItem>
|
|
242
|
+
))}
|
|
243
|
+
</ComposerPrimitive.Unstable_TriggerPopoverItems>
|
|
244
|
+
</ComposerPrimitive.Unstable_TriggerPopover>
|
|
245
|
+
</ComposerPrimitive.Root>
|
|
246
|
+
</ComposerPrimitive.Unstable_TriggerPopoverRoot>
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
Each `TriggerPopover` is its own scope — the `@` popover and the `/` popover read state from their own declaration and never collide. Keyboard events route to whichever popover is currently active.
|
|
250
|
+
|
|
251
|
+
## Commands with Arguments
|
|
252
|
+
|
|
253
|
+
Some commands accept inline arguments typed after the command word — for example `/translate en` or `/ask what is TypeScript`. Because the adapter's `search` method receives the full text after `/`, you can split on the first space to separate the command from its arguments:
|
|
254
|
+
|
|
255
|
+
```tsx
|
|
256
|
+
const SLASH_COMMANDS: readonly Unstable_SlashCommand[] = [
|
|
257
|
+
{
|
|
258
|
+
id: "translate",
|
|
259
|
+
description: "Translate to a language, e.g. /translate en",
|
|
260
|
+
execute: () => {/* arguments extracted separately, see below */},
|
|
261
|
+
},
|
|
262
|
+
{
|
|
263
|
+
id: "ask",
|
|
264
|
+
description: "Ask about a topic, e.g. /ask what is TypeScript",
|
|
265
|
+
execute: () => {/* arguments extracted separately, see below */},
|
|
266
|
+
},
|
|
267
|
+
];
|
|
268
|
+
|
|
269
|
+
function MyComposer() {
|
|
270
|
+
const composerRef = useRef<HTMLTextAreaElement>(null);
|
|
271
|
+
const slash = unstable_useSlashCommandAdapter({
|
|
272
|
+
commands: SLASH_COMMANDS.map((cmd) => ({
|
|
273
|
+
...cmd,
|
|
274
|
+
execute: () => {
|
|
275
|
+
// Read the full composer text to extract arguments
|
|
276
|
+
const raw = composerRef.current?.value ?? "";
|
|
277
|
+
// Match "/<id> <args>" at start of input
|
|
278
|
+
const match = raw.match(new RegExp(`^\\/${cmd.id}\\s+(.*)`));
|
|
279
|
+
const args = match?.[1]?.trim() ?? "";
|
|
280
|
+
handleCommand(cmd.id, args);
|
|
281
|
+
},
|
|
282
|
+
})),
|
|
283
|
+
removeOnExecute: true,
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
return (
|
|
287
|
+
<ComposerPrimitive.Unstable_TriggerPopoverRoot>
|
|
288
|
+
<ComposerPrimitive.Root>
|
|
289
|
+
<ComposerPrimitive.Input ref={composerRef} placeholder="Type / for commands..." />
|
|
290
|
+
<ComposerPrimitive.Unstable_TriggerPopover char="/" adapter={slash.adapter}>
|
|
291
|
+
<ComposerPrimitive.Unstable_TriggerPopover.Action {...slash.action} />
|
|
292
|
+
<ComposerPrimitive.Unstable_TriggerPopoverItems>
|
|
293
|
+
{(items) => items.map((item, i) => (
|
|
294
|
+
<ComposerPrimitive.Unstable_TriggerPopoverItem key={item.id} item={item} index={i}>
|
|
295
|
+
<strong>{item.label}</strong>
|
|
296
|
+
{item.description && <span>{item.description}</span>}
|
|
297
|
+
</ComposerPrimitive.Unstable_TriggerPopoverItem>
|
|
298
|
+
))}
|
|
299
|
+
</ComposerPrimitive.Unstable_TriggerPopoverItems>
|
|
300
|
+
</ComposerPrimitive.Unstable_TriggerPopover>
|
|
301
|
+
<ComposerPrimitive.Send>Send</ComposerPrimitive.Send>
|
|
302
|
+
</ComposerPrimitive.Root>
|
|
303
|
+
</ComposerPrimitive.Unstable_TriggerPopoverRoot>
|
|
304
|
+
);
|
|
305
|
+
}
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
`removeOnExecute: true` strips the `/translate en` text from the composer so the argument is consumed by the handler rather than sent to the LLM.
|
|
309
|
+
|
|
310
|
+
## Async Command Loading
|
|
311
|
+
|
|
312
|
+
The adapter interface is synchronous, but the command list can come from any async source. Load commands into state (or a query cache) and pass the current snapshot to the hook. Because `unstable_useSlashCommandAdapter` re-runs on every render, the adapter always reflects the latest list.
|
|
313
|
+
|
|
314
|
+
**With React state:**
|
|
315
|
+
|
|
316
|
+
```tsx
|
|
317
|
+
function MyComposer() {
|
|
318
|
+
const [commands, setCommands] = useState<Unstable_SlashCommand[]>([]);
|
|
319
|
+
|
|
320
|
+
useEffect(() => {
|
|
321
|
+
fetchAvailableCommands().then(setCommands);
|
|
322
|
+
}, []);
|
|
323
|
+
|
|
324
|
+
const slash = unstable_useSlashCommandAdapter({ commands });
|
|
325
|
+
|
|
326
|
+
return (/* ... */);
|
|
327
|
+
}
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
**With React Query:**
|
|
331
|
+
|
|
332
|
+
```tsx
|
|
333
|
+
function MyComposer() {
|
|
334
|
+
const { data: commands = [] } = useQuery({
|
|
335
|
+
queryKey: ["slash-commands"],
|
|
336
|
+
queryFn: fetchAvailableCommands,
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
const slash = unstable_useSlashCommandAdapter({ commands });
|
|
340
|
+
|
|
341
|
+
return (/* ... */);
|
|
342
|
+
}
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
## Keyboard Navigation
|
|
346
|
+
|
|
347
|
+
See [ComposerTriggerPopover keyboard navigation](/docs/ui/composer-trigger-popover#keyboard-navigation) for the full key bindings table.
|
|
348
|
+
|
|
349
|
+
## Trigger Popover Architecture
|
|
350
|
+
|
|
351
|
+
Both mentions and slash commands are built on a generic trigger popover system where each `Unstable_TriggerPopover` declares one trigger character, an adapter, and exactly one behavior sub-primitive (`Directive` or `Action`). Multiple triggers coexist under a single `Unstable_TriggerPopoverRoot`. See the [Composer Primitives](/docs/primitives/composer) reference for the complete API.
|
|
352
|
+
|
|
353
|
+
## Primitives Reference
|
|
354
|
+
|
|
355
|
+
See the [Composer Primitives](/docs/primitives/composer) reference for the full list of trigger popover primitives and their props.
|
|
356
|
+
|
|
357
|
+
## Related
|
|
358
|
+
|
|
359
|
+
- [Mentions Guide](/docs/guides/mentions) — `@`-mention system built on the same architecture
|
|
360
|
+
- [Suggestions Guide](/docs/guides/suggestions) — static follow-up prompts (different from slash commands)
|
|
361
|
+
- [Composer Primitives](/docs/primitives/composer) — underlying composer primitives
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Text-to-Speech (Speech Synthesis)
|
|
3
|
+
description: Read messages aloud with Web Speech API or a custom TTS adapter.
|
|
4
|
+
platforms: ["react"]
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
import { SpeechSample } from "@/components/docs/samples/speech";
|
|
8
|
+
|
|
9
|
+
assistant-ui supports text-to-speech via the `SpeechSynthesisAdapter` interface. When a speech adapter is configured, users can trigger playback for any assistant message.
|
|
10
|
+
|
|
11
|
+
<SpeechSample />
|
|
12
|
+
|
|
13
|
+
## SpeechSynthesisAdapter
|
|
14
|
+
|
|
15
|
+
The `SpeechSynthesisAdapter` interface has a single method:
|
|
16
|
+
|
|
17
|
+
```tsx
|
|
18
|
+
import type { SpeechSynthesisAdapter } from "@assistant-ui/react";
|
|
19
|
+
|
|
20
|
+
type SpeechSynthesisAdapter = {
|
|
21
|
+
speak: (text: string) => SpeechSynthesisAdapter.Utterance;
|
|
22
|
+
};
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
`speak` is called with the plain text of an assistant message and must return an `Utterance` object:
|
|
26
|
+
|
|
27
|
+
```tsx
|
|
28
|
+
type Utterance = {
|
|
29
|
+
status: SpeechSynthesisAdapter.Status;
|
|
30
|
+
cancel: () => void;
|
|
31
|
+
subscribe: (callback: () => void) => Unsubscribe;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
type Status =
|
|
35
|
+
| { type: "starting" | "running" }
|
|
36
|
+
| { type: "ended"; reason: "finished" | "cancelled" | "error"; error?: unknown };
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Currently the following built-in adapter is available:
|
|
40
|
+
|
|
41
|
+
- `WebSpeechSynthesisAdapter`: uses the browser's `Web Speech API` (`SpeechSynthesis`)
|
|
42
|
+
|
|
43
|
+
## WebSpeechSynthesisAdapter
|
|
44
|
+
|
|
45
|
+
```tsx
|
|
46
|
+
import { WebSpeechSynthesisAdapter } from "@assistant-ui/react";
|
|
47
|
+
|
|
48
|
+
const runtime = useChatRuntime({
|
|
49
|
+
adapters: {
|
|
50
|
+
speech: new WebSpeechSynthesisAdapter(),
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## UI
|
|
56
|
+
|
|
57
|
+
The default action bar does not include a speech button. Add `ActionBarPrimitive.Speak` and `ActionBarPrimitive.StopSpeaking` to your assistant message action bar:
|
|
58
|
+
|
|
59
|
+
```tsx
|
|
60
|
+
import { ActionBarPrimitive, useMessageTTS } from "@assistant-ui/react";
|
|
61
|
+
import { AudioLinesIcon, StopCircleIcon } from "lucide-react";
|
|
62
|
+
|
|
63
|
+
const AssistantActionBar = () => {
|
|
64
|
+
const isSpeaking = useMessageTTS();
|
|
65
|
+
|
|
66
|
+
return (
|
|
67
|
+
<ActionBarPrimitive.Root>
|
|
68
|
+
{!isSpeaking && (
|
|
69
|
+
<ActionBarPrimitive.Speak>
|
|
70
|
+
<AudioLinesIcon />
|
|
71
|
+
</ActionBarPrimitive.Speak>
|
|
72
|
+
)}
|
|
73
|
+
{isSpeaking && (
|
|
74
|
+
<ActionBarPrimitive.StopSpeaking>
|
|
75
|
+
<StopCircleIcon />
|
|
76
|
+
</ActionBarPrimitive.StopSpeaking>
|
|
77
|
+
)}
|
|
78
|
+
<ActionBarPrimitive.Copy />
|
|
79
|
+
</ActionBarPrimitive.Root>
|
|
80
|
+
);
|
|
81
|
+
};
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
`ActionBarPrimitive.Speak` is automatically disabled when no speech adapter is configured.
|
|
85
|
+
|
|
86
|
+
## Custom Adapters
|
|
87
|
+
|
|
88
|
+
Implement `SpeechSynthesisAdapter` to call any external TTS API:
|
|
89
|
+
|
|
90
|
+
```tsx title="lib/custom-tts-adapter.ts"
|
|
91
|
+
import type { SpeechSynthesisAdapter } from "@assistant-ui/react";
|
|
92
|
+
|
|
93
|
+
export class CustomTTSAdapter implements SpeechSynthesisAdapter {
|
|
94
|
+
private apiUrl: string;
|
|
95
|
+
|
|
96
|
+
constructor(options: { apiUrl: string }) {
|
|
97
|
+
this.apiUrl = options.apiUrl;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
speak(text: string): SpeechSynthesisAdapter.Utterance {
|
|
101
|
+
const subscribers = new Set<() => void>();
|
|
102
|
+
let status: SpeechSynthesisAdapter.Status = { type: "starting" };
|
|
103
|
+
let audio: HTMLAudioElement | null = null;
|
|
104
|
+
|
|
105
|
+
const notify = () => {
|
|
106
|
+
for (const cb of subscribers) cb();
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const finish = (reason: "finished" | "cancelled" | "error", error?: unknown) => {
|
|
110
|
+
if (status.type === "ended") return;
|
|
111
|
+
status = { type: "ended", reason, error };
|
|
112
|
+
notify();
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
fetch(this.apiUrl, {
|
|
116
|
+
method: "POST",
|
|
117
|
+
headers: { "Content-Type": "application/json" },
|
|
118
|
+
body: JSON.stringify({ text }),
|
|
119
|
+
})
|
|
120
|
+
.then((res) => res.blob())
|
|
121
|
+
.then((blob) => {
|
|
122
|
+
audio = new Audio(URL.createObjectURL(blob));
|
|
123
|
+
status = { type: "running" };
|
|
124
|
+
notify();
|
|
125
|
+
audio.onended = () => finish("finished");
|
|
126
|
+
audio.onerror = (e) => finish("error", e);
|
|
127
|
+
audio.play();
|
|
128
|
+
})
|
|
129
|
+
.catch((err) => finish("error", err));
|
|
130
|
+
|
|
131
|
+
return {
|
|
132
|
+
get status() { return status; },
|
|
133
|
+
cancel: () => {
|
|
134
|
+
audio?.pause();
|
|
135
|
+
finish("cancelled");
|
|
136
|
+
},
|
|
137
|
+
subscribe: (cb) => {
|
|
138
|
+
subscribers.add(cb);
|
|
139
|
+
return () => subscribers.delete(cb);
|
|
140
|
+
},
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Wire it up the same way as the built-in adapter:
|
|
147
|
+
|
|
148
|
+
```tsx
|
|
149
|
+
import { CustomTTSAdapter } from "@/lib/custom-tts-adapter";
|
|
150
|
+
|
|
151
|
+
const runtime = useChatRuntime({
|
|
152
|
+
adapters: {
|
|
153
|
+
speech: new CustomTTSAdapter({ apiUrl: "/api/tts" }),
|
|
154
|
+
},
|
|
155
|
+
});
|
|
156
|
+
```
|