@assistant-ui/mcp-docs-server 0.1.34 → 0.1.35
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 +4 -4
- package/.docs/organized/code-examples/with-a2a.md +5 -5
- package/.docs/organized/code-examples/with-ag-ui.md +6 -6
- package/.docs/organized/code-examples/with-ai-sdk-v6.md +7 -7
- package/.docs/organized/code-examples/with-artifacts.md +7 -7
- package/.docs/organized/code-examples/with-assistant-transport.md +5 -5
- package/.docs/organized/code-examples/with-browser-extension.md +5 -5
- package/.docs/organized/code-examples/with-chain-of-thought.md +8 -8
- package/.docs/organized/code-examples/with-cloud-standalone.md +7 -7
- package/.docs/organized/code-examples/with-cloud.md +7 -7
- package/.docs/organized/code-examples/with-custom-thread-list.md +7 -7
- package/.docs/organized/code-examples/with-elevenlabs-conversational.md +9 -9
- package/.docs/organized/code-examples/with-elevenlabs-scribe.md +9 -9
- package/.docs/organized/code-examples/with-expo.md +43 -17
- package/.docs/organized/code-examples/with-external-store.md +5 -5
- package/.docs/organized/code-examples/with-ffmpeg.md +7 -7
- package/.docs/organized/code-examples/with-generative-ui.md +32 -308
- package/.docs/organized/code-examples/with-google-adk.md +5 -5
- package/.docs/organized/code-examples/with-heat-graph.md +4 -4
- package/.docs/organized/code-examples/with-image-generation.md +7 -7
- package/.docs/organized/code-examples/with-interactables.md +7 -7
- package/.docs/organized/code-examples/with-langchain.md +7 -7
- package/.docs/organized/code-examples/with-langgraph.md +6 -6
- package/.docs/organized/code-examples/with-livekit.md +9 -9
- package/.docs/organized/code-examples/with-mcp.md +7 -7
- package/.docs/organized/code-examples/with-opencode.md +106 -580
- package/.docs/organized/code-examples/with-pi.md +2044 -0
- package/.docs/organized/code-examples/with-react-hook-form.md +8 -8
- package/.docs/organized/code-examples/with-react-ink-web.md +691 -0
- package/.docs/organized/code-examples/with-react-ink.md +28 -16
- package/.docs/organized/code-examples/with-react-router.md +5 -5
- package/.docs/organized/code-examples/with-resumable-stream.md +8 -8
- package/.docs/organized/code-examples/with-store.md +14 -10
- package/.docs/organized/code-examples/with-tanstack.md +18 -4
- package/.docs/organized/code-examples/with-tap-runtime.md +5 -5
- package/.docs/organized/code-examples/with-virtualized-thread.md +656 -0
- package/.docs/raw/docs/(docs)/architecture.mdx +13 -12
- package/.docs/raw/docs/(reference)/api-reference/hooks/primitives.mdx +44 -1
- package/.docs/raw/docs/(reference)/api-reference/model-context/context.mdx +1 -1
- package/.docs/raw/docs/(reference)/api-reference/tools/rendering.mdx +2 -23
- package/.docs/raw/docs/(reference)/api-reference/tools/status.mdx +22 -0
- package/.docs/raw/docs/(reference)/api-reference/tools/toolkits.mdx +0 -7
- package/.docs/raw/docs/(reference)/api-reference/utilities/miscellaneous.mdx +20 -21
- package/.docs/raw/docs/guides/chain-of-thought.mdx +1 -1
- package/.docs/raw/docs/guides/index.mdx +3 -0
- package/.docs/raw/docs/guides/input-history.mdx +55 -0
- package/.docs/raw/docs/guides/virtualization.mdx +63 -0
- package/.docs/raw/docs/ink/hooks.mdx +2 -2
- package/.docs/raw/docs/react-native/hooks.mdx +1 -1
- package/.docs/raw/docs/runtimes/ag-ui/runtime-options.mdx +1 -3
- package/.docs/raw/docs/runtimes/concepts/stability.mdx +1 -1
- package/.docs/raw/docs/runtimes/custom/data-stream.mdx +1 -1
- package/.docs/raw/docs/runtimes/custom/local-runtime.mdx +108 -4
- package/.docs/raw/docs/runtimes/pick-a-runtime.mdx +6 -6
- package/.docs/raw/docs/tools/tool-ui.mdx +37 -1
- package/.docs/raw/docs/ui/dot-matrix.mdx +133 -0
- package/.docs/raw/docs/ui/model-selector.mdx +219 -52
- package/.docs/raw/docs/ui/number-roll.mdx +154 -0
- package/.docs/raw/docs/ui/reasoning.mdx +3 -3
- package/.docs/raw/docs/ui/streamdown.mdx +2 -0
- package/.docs/raw/docs/ui/syntax-highlighting.mdx +1 -1
- package/.docs/raw/docs/ui/thread.mdx +52 -0
- package/.docs/raw/docs/ui/tool-fallback.mdx +2 -2
- package/package.json +3 -3
|
@@ -577,12 +577,48 @@ const toolkit = defineToolkit({
|
|
|
577
577
|
|
|
578
578
|
- `undefined`: gate is open, the renderer should ask the user. This is the only state in which `respondToApproval` is legal.
|
|
579
579
|
- `true`: decision recorded as allow. The server is producing the result (or has produced one, available on `result`).
|
|
580
|
-
- `false`: decision recorded as deny.
|
|
580
|
+
- `false`: decision recorded as deny. The runtime records an error result (`isError`) and exposes `approval.reason`.
|
|
581
581
|
|
|
582
582
|
`approval.isAutomatic` is `true` when the runtime granted the decision from a server-side policy rather than the user; render a "auto-approved" badge instead of buttons in that case.
|
|
583
583
|
|
|
584
|
+
Approval gates require a runtime that implements them: the AI SDK v6 runtime emits them for `needsApproval` tools, and `LocalRuntime` supports gates emitted by your `ChatModelAdapter`; see [LocalRuntime approval gates](/docs/runtimes/custom/local-runtime#approval-gates). For tools where the user supplies the result itself, use `unstable_humanToolNames` with `addResult` instead; see [human-in-the-loop tools](/docs/runtimes/custom/local-runtime#human-in-the-loop-tools).
|
|
585
|
+
|
|
584
586
|
For the wire-side setup (`needsApproval`, `sendAutomaticallyWhen`), see [AI SDK v6 server-side tool approval](/docs/runtimes/ai-sdk/v6#server-side-tool-approval).
|
|
585
587
|
|
|
588
|
+
### Approval options
|
|
589
|
+
|
|
590
|
+
Beyond the plain allow / deny pair, the host can attach a list of decision options to an approval, for example "allow once", "allow for this session", and "always allow". Each option carries a machine-readable `kind` (`"allow-once"`, `"allow-always"`, `"reject-once"`, `"reject-always"`); scope semantics like session versus global belong to the option's `id` and `label`, which only the host interprets:
|
|
591
|
+
|
|
592
|
+
```ts
|
|
593
|
+
const approval = {
|
|
594
|
+
id: "a1",
|
|
595
|
+
options: [
|
|
596
|
+
{ id: "once", kind: "allow-once" },
|
|
597
|
+
{ id: "session", kind: "allow-always", label: "Allow for this session" },
|
|
598
|
+
{
|
|
599
|
+
id: "always",
|
|
600
|
+
kind: "allow-always",
|
|
601
|
+
label: "Always allow",
|
|
602
|
+
grants: ["git *"],
|
|
603
|
+
confirm: true,
|
|
604
|
+
},
|
|
605
|
+
{ id: "deny", kind: "reject-once" },
|
|
606
|
+
],
|
|
607
|
+
};
|
|
608
|
+
```
|
|
609
|
+
|
|
610
|
+
Renderers respond with the chosen option instead of a boolean; the option's kind resolves the decision:
|
|
611
|
+
|
|
612
|
+
```tsx
|
|
613
|
+
respondToApproval({ optionId: "session" });
|
|
614
|
+
```
|
|
615
|
+
|
|
616
|
+
The runtime receives `{ approvalId, approved, optionId, reason? }`, so a host that persists "always allow" decisions can key its store off `optionId`. Persistence is entirely host-owned: assistant-ui never stores a decision and never auto-answers future approvals. `grants` lists the patterns an option would persist (shown to the user before they commit), and `confirm` opts the option into a confirmation step. Options with custom `_`-prefixed kinds are skipped by the default `ToolFallback` bar and must be answered with an explicit `approved` value, optionally alongside the `optionId` so the chosen option is still recorded.
|
|
617
|
+
|
|
618
|
+
Approvals that end without a decision (a cancelled run or an expired request) are recorded by the host as `approval.resolution: "cancelled" | "expired"`, which closes the gate without recording a deny.
|
|
619
|
+
|
|
620
|
+
The default `ToolFallback` component renders supplied options automatically, including the confirmation step.
|
|
621
|
+
|
|
586
622
|
## Advanced Features
|
|
587
623
|
|
|
588
624
|
### Tool Status Handling
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Dot Matrix
|
|
3
|
+
description: Tiny 5x5 dot-matrix indicator with 20 state-specific blink patterns.
|
|
4
|
+
platforms: ["react"]
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
import { PreviewCode } from "@/components/docs/preview-code.server";
|
|
8
|
+
import {
|
|
9
|
+
DotMatrixSample,
|
|
10
|
+
DotMatrixLifecycleSample,
|
|
11
|
+
DotMatrixInlineSample,
|
|
12
|
+
DotMatrixSizesSample,
|
|
13
|
+
} from "@/components/docs/samples/dot-matrix";
|
|
14
|
+
|
|
15
|
+
<Callout>
|
|
16
|
+
This is a **standalone component** that does not depend on the assistant-ui runtime. Use it anywhere in your application.
|
|
17
|
+
</Callout>
|
|
18
|
+
|
|
19
|
+
<DotMatrixSample />
|
|
20
|
+
|
|
21
|
+
## Installation
|
|
22
|
+
|
|
23
|
+
<InstallCommand shadcn={["dot-matrix"]} />
|
|
24
|
+
|
|
25
|
+
This adds a `/components/assistant-ui/dot-matrix.tsx` file to your project, which you can adjust as needed. The component has no dependencies beyond React.
|
|
26
|
+
|
|
27
|
+
## Usage
|
|
28
|
+
|
|
29
|
+
```tsx
|
|
30
|
+
import { DotMatrix } from "@/components/assistant-ui/dot-matrix";
|
|
31
|
+
|
|
32
|
+
export function RunIndicator({ isRunning }: { isRunning: boolean }) {
|
|
33
|
+
return <DotMatrix state={isRunning ? "loading" : "success"} />;
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Dots inherit the surrounding text color, so the matrix renders dark dots on light backgrounds and light dots on dark backgrounds without configuration. Every state is a combination of a dot pattern, a motion, and a color, and switching states cross-fades each dot into its new pattern.
|
|
38
|
+
|
|
39
|
+
## States
|
|
40
|
+
|
|
41
|
+
| State | Pattern |
|
|
42
|
+
|-------|---------|
|
|
43
|
+
| `idle` | Dim static grid |
|
|
44
|
+
| `loading` | Randomized twinkle (default) |
|
|
45
|
+
| `thinking` | Diagonal wave |
|
|
46
|
+
| `streaming` | Falling rain, per-column streaks |
|
|
47
|
+
| `searching` | Horizontal sweep |
|
|
48
|
+
| `syncing` | Rotating sweep around the center |
|
|
49
|
+
| `connecting` | Ripple expanding from the center |
|
|
50
|
+
| `waiting` | Ellipsis dots blinking in sequence |
|
|
51
|
+
| `uploading` | Wave rising upward |
|
|
52
|
+
| `downloading` | Wave falling downward |
|
|
53
|
+
| `listening` | Slow equalizer columns |
|
|
54
|
+
| `speaking` | Fast equalizer columns |
|
|
55
|
+
| `recording` | Red center dot breathing |
|
|
56
|
+
| `success` | Green check glyph, static |
|
|
57
|
+
| `error` | Red cross glyph, blinking |
|
|
58
|
+
| `warning` | Amber exclamation glyph, slow blink |
|
|
59
|
+
| `info` | Blue info glyph, static |
|
|
60
|
+
| `paused` | Pause bars glyph, static |
|
|
61
|
+
| `stopped` | Square glyph, static |
|
|
62
|
+
| `offline` | Very dim static grid |
|
|
63
|
+
|
|
64
|
+
The component exports `dotMatrixStates` (the ordered list above) and the `DotMatrixState` union type, so UIs can enumerate or map states without duplicating the list. New states are added by extending the `STATES` record in the component source with a glyph and a per-dot blink function.
|
|
65
|
+
|
|
66
|
+
## Examples
|
|
67
|
+
|
|
68
|
+
### State Lifecycle
|
|
69
|
+
|
|
70
|
+
Drive the `state` prop from your run status; the matrix morphs between patterns instead of swapping components.
|
|
71
|
+
|
|
72
|
+
<PreviewCode file="components/docs/samples/dot-matrix" name="DotMatrixLifecycleSample">
|
|
73
|
+
<DotMatrixLifecycleSample />
|
|
74
|
+
</PreviewCode>
|
|
75
|
+
|
|
76
|
+
### Inline With Text
|
|
77
|
+
|
|
78
|
+
At the default `size-4` the matrix aligns with text like an icon, and the dots adapt to inverted surfaces through `currentColor`.
|
|
79
|
+
|
|
80
|
+
<PreviewCode file="components/docs/samples/dot-matrix" name="DotMatrixInlineSample">
|
|
81
|
+
<DotMatrixInlineSample />
|
|
82
|
+
</PreviewCode>
|
|
83
|
+
|
|
84
|
+
### Sizes
|
|
85
|
+
|
|
86
|
+
The matrix is an SVG, so any size utility scales it crisply.
|
|
87
|
+
|
|
88
|
+
<DotMatrixSizesSample />
|
|
89
|
+
|
|
90
|
+
## How It Works
|
|
91
|
+
|
|
92
|
+
The grid is a 5x5 SVG of `currentColor` circles. Blinking is a single CSS keyframe animation whose high/low opacity bounds come from registered per-dot CSS variables; the animation runs in every state (static states collapse the bounds) and the bounds carry a transition, which is what makes state changes cross-fade per dot. The randomized loading rhythm uses deterministic per-dot delays and durations, so server and client render identical markup and no JavaScript runs after render. With `prefers-reduced-motion`, the dots hold their resting opacity instead of blinking.
|
|
93
|
+
|
|
94
|
+
The root is a `role="status"` live region whose text content is the state name (or the `label` prop), so screen readers announce state changes; the SVG itself is `aria-hidden`.
|
|
95
|
+
|
|
96
|
+
## API Reference
|
|
97
|
+
|
|
98
|
+
### DotMatrix
|
|
99
|
+
|
|
100
|
+
<ParametersTable
|
|
101
|
+
type="DotMatrixProps"
|
|
102
|
+
parameters={[
|
|
103
|
+
{
|
|
104
|
+
name: "state",
|
|
105
|
+
type: "DotMatrixState",
|
|
106
|
+
default: '"loading"',
|
|
107
|
+
description:
|
|
108
|
+
"One of the 20 built-in states listed above, controlling pattern, motion, and color.",
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
name: "label",
|
|
112
|
+
type: "string",
|
|
113
|
+
description:
|
|
114
|
+
"Accessible label announced by screen readers. Defaults to the state name.",
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
name: "className",
|
|
118
|
+
type: "string",
|
|
119
|
+
description:
|
|
120
|
+
"Additional CSS classes. Use size utilities to scale and text color utilities to recolor.",
|
|
121
|
+
},
|
|
122
|
+
]}
|
|
123
|
+
/>
|
|
124
|
+
|
|
125
|
+
### Styling
|
|
126
|
+
|
|
127
|
+
Color follows `currentColor`, so `className="text-blue-500"` recolors the whole matrix; the outcome states (`success`, `error`, `warning`, `info`, `recording`, and the muted static states) set their own color which a `className` can override. Dots are targetable via `[data-slot="dot-matrix"]` and `[data-slot="dot-matrix-dot"]`, and the current state is exposed as `data-state` on the root.
|
|
128
|
+
|
|
129
|
+
## Related Components
|
|
130
|
+
|
|
131
|
+
- [Number Roll](/docs/ui/number-roll) - Animated rolling number
|
|
132
|
+
- [Badge](/docs/ui/badge) - Small status and metadata labels
|
|
133
|
+
- [Voice](/docs/ui/voice) - Voice activity visualization
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
---
|
|
2
|
-
title:
|
|
3
|
-
description:
|
|
2
|
+
title: Model Selector
|
|
3
|
+
description: Composable model picker with reasoning effort levels, search, and runtime integration.
|
|
4
4
|
platforms: ["react"]
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
import { ModelSelectorSample } from "@/components/docs/samples/model-selector";
|
|
8
8
|
|
|
9
|
-
A
|
|
9
|
+
A picker that lets users switch between AI models and choose a reasoning effort (thinking) level. It is built on Popover + Command, so search, provider grouping, and filtering compose in without being built in. The default export integrates with assistant-ui's `ModelContext` system, so the selection reaches your backend on every request with no extra wiring.
|
|
10
10
|
|
|
11
11
|
<ModelSelectorSample />
|
|
12
12
|
|
|
@@ -24,9 +24,9 @@ A select component that lets users switch between AI models. Uses item-aligned p
|
|
|
24
24
|
|
|
25
25
|
### Use in your application
|
|
26
26
|
|
|
27
|
-
Place the `ModelSelector` inside your thread component, typically in the composer area:
|
|
27
|
+
Place the `ModelSelector` inside your thread component, typically in the composer area. Each model needs an `id` and a display `name`; everything else is optional:
|
|
28
28
|
|
|
29
|
-
```tsx title="/components/assistant-ui/thread.tsx" {1,6-
|
|
29
|
+
```tsx title="/components/assistant-ui/thread.tsx" {1,6-15}
|
|
30
30
|
import { ModelSelector } from "@/components/assistant-ui/model-selector";
|
|
31
31
|
|
|
32
32
|
const ComposerAction: FC = () => {
|
|
@@ -36,9 +36,10 @@ const ComposerAction: FC = () => {
|
|
|
36
36
|
models={[
|
|
37
37
|
{ id: "gpt-5.4-nano", name: "GPT-5.4 Nano", description: "Fast and efficient" },
|
|
38
38
|
{ id: "gpt-5.4-mini", name: "GPT-5.4 Mini", description: "Balanced performance" },
|
|
39
|
-
{ id: "gpt-5.5", name: "GPT-5.5", description: "Most capable" },
|
|
39
|
+
{ id: "gpt-5.5", name: "GPT-5.5", description: "Most capable", efforts: true },
|
|
40
40
|
]}
|
|
41
41
|
defaultValue="gpt-5.4-nano"
|
|
42
|
+
defaultEffort="medium"
|
|
42
43
|
size="sm"
|
|
43
44
|
/>
|
|
44
45
|
</div>
|
|
@@ -49,16 +50,22 @@ const ComposerAction: FC = () => {
|
|
|
49
50
|
</Step>
|
|
50
51
|
<Step>
|
|
51
52
|
|
|
52
|
-
### Read the
|
|
53
|
+
### Read the selection in your API route
|
|
53
54
|
|
|
54
|
-
The selected model's `id`
|
|
55
|
+
The selected model's `id` arrives as `config.modelName`, and the effort level as `config.reasoningEffort`:
|
|
55
56
|
|
|
56
|
-
```tsx title="app/api/chat/route.ts" {2,5}
|
|
57
|
+
```tsx title="app/api/chat/route.ts" {2,5-11}
|
|
57
58
|
export async function POST(req: Request) {
|
|
58
59
|
const { messages, config } = await req.json();
|
|
59
60
|
|
|
60
61
|
const result = streamText({
|
|
61
62
|
model: openai(config?.modelName ?? "gpt-5.4-nano"),
|
|
63
|
+
providerOptions: {
|
|
64
|
+
openai:
|
|
65
|
+
config?.reasoningEffort !== undefined
|
|
66
|
+
? { reasoningEffort: config.reasoningEffort }
|
|
67
|
+
: {},
|
|
68
|
+
},
|
|
62
69
|
messages: await convertToModelMessages(messages),
|
|
63
70
|
});
|
|
64
71
|
|
|
@@ -66,77 +73,126 @@ export async function POST(req: Request) {
|
|
|
66
73
|
}
|
|
67
74
|
```
|
|
68
75
|
|
|
76
|
+
`config.reasoningEffort` is only present when the selected model supports the chosen level, so the route only forwards it when it exists. See [How It Works](#how-it-works).
|
|
77
|
+
|
|
69
78
|
</Step>
|
|
70
79
|
</Steps>
|
|
71
80
|
|
|
72
|
-
##
|
|
81
|
+
## Reasoning Efforts
|
|
73
82
|
|
|
74
|
-
|
|
83
|
+
A model that declares `efforts` shows a "Thinking" row at the bottom of the popover. `efforts: true` enables the default Low / Medium / High levels; pass a list of `{ id, name }` objects to define your own:
|
|
75
84
|
|
|
76
85
|
```tsx
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
86
|
+
{
|
|
87
|
+
id: "gpt-5.5",
|
|
88
|
+
name: "GPT-5.5",
|
|
89
|
+
efforts: [
|
|
90
|
+
{ id: "minimal", name: "Minimal" },
|
|
91
|
+
{ id: "high", name: "High" },
|
|
92
|
+
],
|
|
93
|
+
}
|
|
80
94
|
```
|
|
81
95
|
|
|
82
|
-
|
|
83
|
-
| --------- | ---------------------------------------------- |
|
|
84
|
-
| `outline` | Border with transparent background (default) |
|
|
85
|
-
| `ghost` | No background, subtle hover |
|
|
86
|
-
| `muted` | Solid secondary background |
|
|
96
|
+
Omit `efforts` for models without configurable reasoning. The row is hidden while such a model is selected.
|
|
87
97
|
|
|
88
|
-
|
|
98
|
+
### Sticky Selection
|
|
89
99
|
|
|
90
|
-
|
|
100
|
+
The effort selection survives model switches. Switching to a model that doesn't support the current level omits `reasoningEffort` from the request instead of resetting the user's choice, and the level applies again when the user switches back. The exported `resolveModelEffort` helper applies the same rule if you build your own runtime integration around `ModelSelector.Root`; see [`resolveModelEffort`](#resolvemodeleffort).
|
|
91
101
|
|
|
92
|
-
|
|
93
|
-
<ModelSelector size="sm" /> // Compact (h-8, text-xs)
|
|
94
|
-
<ModelSelector size="default" /> // Standard (h-9)
|
|
95
|
-
<ModelSelector size="lg" /> // Large (h-10)
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
## Model Options
|
|
102
|
+
### Custom Effort UI
|
|
99
103
|
|
|
100
|
-
|
|
104
|
+
`ModelSelector.Effort` lays the levels out as horizontal segments, which overflows the popover width once a model has more than a few. For those cases, or for a different layout such as a slider or a sub-dropdown, build your own control with the `useModelSelectorEfforts` hook. It exposes the selected model's levels and the active selection:
|
|
101
105
|
|
|
102
106
|
```tsx
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
107
|
+
import { useModelSelectorEfforts } from "@/components/assistant-ui/model-selector";
|
|
108
|
+
import {
|
|
109
|
+
DropdownMenu,
|
|
110
|
+
DropdownMenuContent,
|
|
111
|
+
DropdownMenuRadioGroup,
|
|
112
|
+
DropdownMenuRadioItem,
|
|
113
|
+
DropdownMenuTrigger,
|
|
114
|
+
} from "@/components/ui/dropdown-menu";
|
|
115
|
+
|
|
116
|
+
function EffortDropdown() {
|
|
117
|
+
const { efforts, effort, setEffort } = useModelSelectorEfforts();
|
|
118
|
+
if (!efforts?.length) return null;
|
|
119
|
+
|
|
120
|
+
return (
|
|
121
|
+
<div className="flex items-center justify-between gap-3 border-t px-3 py-2">
|
|
122
|
+
<span className="text-muted-foreground text-xs">Thinking</span>
|
|
123
|
+
<DropdownMenu>
|
|
124
|
+
<DropdownMenuTrigger className="text-xs">
|
|
125
|
+
{efforts.find((e) => e.id === effort)?.name ?? "Select"}
|
|
126
|
+
</DropdownMenuTrigger>
|
|
127
|
+
<DropdownMenuContent align="end">
|
|
128
|
+
<DropdownMenuRadioGroup value={effort} onValueChange={setEffort}>
|
|
129
|
+
{efforts.map((option) => (
|
|
130
|
+
<DropdownMenuRadioItem key={option.id} value={option.id}>
|
|
131
|
+
{option.name}
|
|
132
|
+
</DropdownMenuRadioItem>
|
|
133
|
+
))}
|
|
134
|
+
</DropdownMenuRadioGroup>
|
|
135
|
+
</DropdownMenuContent>
|
|
136
|
+
</DropdownMenu>
|
|
137
|
+
</div>
|
|
138
|
+
);
|
|
139
|
+
}
|
|
111
140
|
```
|
|
112
141
|
|
|
113
|
-
|
|
142
|
+
Render it inside `ModelSelector.Content` in place of `ModelSelector.Effort`. The same hook supports any shape that reads the levels and writes the selection.
|
|
114
143
|
|
|
115
|
-
|
|
144
|
+
## Search
|
|
116
145
|
|
|
117
|
-
|
|
118
|
-
2. The `AssistantChatTransport` includes `config` in the request body
|
|
119
|
-
3. Your API route reads `config.modelName` to determine which model to use
|
|
146
|
+
Search is opt-in. Pass `searchable` to the default component:
|
|
120
147
|
|
|
121
|
-
|
|
148
|
+
```tsx
|
|
149
|
+
<ModelSelector models={models} searchable />
|
|
150
|
+
```
|
|
122
151
|
|
|
123
|
-
|
|
152
|
+
Or compose `ModelSelector.Search` into a custom layout. Matching runs against each model's `id`, `name`, and `keywords`; add the provider name to `keywords` so typing "openai" finds its models.
|
|
124
153
|
|
|
125
|
-
|
|
154
|
+
## Composition
|
|
126
155
|
|
|
127
|
-
|
|
156
|
+
All parts are exported individually. The default popover content is `List` + `Effort`; replace it to add search, provider groups, or anything else:
|
|
128
157
|
|
|
129
158
|
```tsx
|
|
130
159
|
import {
|
|
131
160
|
ModelSelectorRoot,
|
|
132
161
|
ModelSelectorTrigger,
|
|
133
162
|
ModelSelectorContent,
|
|
163
|
+
ModelSelectorSearch,
|
|
164
|
+
ModelSelectorList,
|
|
165
|
+
ModelSelectorEmpty,
|
|
166
|
+
ModelSelectorGroup,
|
|
134
167
|
ModelSelectorItem,
|
|
168
|
+
ModelSelectorEffort,
|
|
135
169
|
} from "@/components/assistant-ui/model-selector";
|
|
136
170
|
|
|
137
|
-
<ModelSelectorRoot
|
|
171
|
+
<ModelSelectorRoot
|
|
172
|
+
models={models}
|
|
173
|
+
value={modelId}
|
|
174
|
+
onValueChange={setModelId}
|
|
175
|
+
effort={effort}
|
|
176
|
+
onEffortChange={setEffort}
|
|
177
|
+
>
|
|
138
178
|
<ModelSelectorTrigger variant="outline" />
|
|
139
|
-
<ModelSelectorContent
|
|
179
|
+
<ModelSelectorContent>
|
|
180
|
+
<ModelSelectorSearch placeholder="Search models..." />
|
|
181
|
+
<ModelSelectorList>
|
|
182
|
+
<ModelSelectorEmpty />
|
|
183
|
+
<ModelSelectorGroup heading="OpenAI">
|
|
184
|
+
{openaiModels.map((model) => (
|
|
185
|
+
<ModelSelectorItem key={model.id} model={model} />
|
|
186
|
+
))}
|
|
187
|
+
</ModelSelectorGroup>
|
|
188
|
+
<ModelSelectorGroup heading="Anthropic">
|
|
189
|
+
{anthropicModels.map((model) => (
|
|
190
|
+
<ModelSelectorItem key={model.id} model={model} />
|
|
191
|
+
))}
|
|
192
|
+
</ModelSelectorGroup>
|
|
193
|
+
</ModelSelectorList>
|
|
194
|
+
<ModelSelectorEffort label="Thinking" />
|
|
195
|
+
</ModelSelectorContent>
|
|
140
196
|
</ModelSelectorRoot>
|
|
141
197
|
```
|
|
142
198
|
|
|
@@ -144,9 +200,64 @@ import {
|
|
|
144
200
|
|-----------|-------------|
|
|
145
201
|
| `ModelSelector` | Default export with runtime integration |
|
|
146
202
|
| `ModelSelector.Root` | Presentational root (no runtime, controlled state) |
|
|
147
|
-
| `ModelSelector.Trigger` | CVA-styled trigger showing current
|
|
148
|
-
| `ModelSelector.
|
|
149
|
-
| `ModelSelector.
|
|
203
|
+
| `ModelSelector.Trigger` | CVA-styled trigger showing the current selection |
|
|
204
|
+
| `ModelSelector.Value` | Selected model name, icon, and active effort |
|
|
205
|
+
| `ModelSelector.Content` | Popover content wrapping a Command |
|
|
206
|
+
| `ModelSelector.Search` | Search input that filters the list |
|
|
207
|
+
| `ModelSelector.List` | List of model items (renders all models by default) |
|
|
208
|
+
| `ModelSelector.Empty` | Empty state shown when search has no matches |
|
|
209
|
+
| `ModelSelector.Group` | Labeled group of items (e.g. by provider) |
|
|
210
|
+
| `ModelSelector.Separator` | Divider between groups or items |
|
|
211
|
+
| `ModelSelector.Item` | Individual model option |
|
|
212
|
+
| `ModelSelector.Effort` | Thinking level row for the selected model |
|
|
213
|
+
|
|
214
|
+
`ModelSelector.List` is a Command list, so filtering and keyboard navigation work across groups automatically. Custom sorting is plain code: order the models before rendering items.
|
|
215
|
+
|
|
216
|
+
<Callout type="warn">
|
|
217
|
+
`ModelSelector.Content` wraps a Command whose root keydown handler claims
|
|
218
|
+
<kbd>Enter</kbd> to select the highlighted model. Interactive elements
|
|
219
|
+
composed inside it (filter chips, custom effort controls) should stop
|
|
220
|
+
propagation for Enter in their own `onKeyDown` so the focused control
|
|
221
|
+
activates instead. `ModelSelector.Effort` already does this.
|
|
222
|
+
</Callout>
|
|
223
|
+
|
|
224
|
+
## Variants
|
|
225
|
+
|
|
226
|
+
Use the `variant` prop to change the trigger's visual style.
|
|
227
|
+
|
|
228
|
+
```tsx
|
|
229
|
+
<ModelSelector variant="outline" /> // Border (default)
|
|
230
|
+
<ModelSelector variant="ghost" /> // No background
|
|
231
|
+
<ModelSelector variant="muted" /> // Solid background
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
| Variant | Description |
|
|
235
|
+
| --------- | ---------------------------------------------- |
|
|
236
|
+
| `outline` | Border with transparent background (default) |
|
|
237
|
+
| `ghost` | No background, subtle hover |
|
|
238
|
+
| `muted` | Solid secondary background |
|
|
239
|
+
|
|
240
|
+
## Sizes
|
|
241
|
+
|
|
242
|
+
Use the `size` prop to control the trigger dimensions.
|
|
243
|
+
|
|
244
|
+
```tsx
|
|
245
|
+
<ModelSelector size="sm" /> // Compact (h-8, text-xs)
|
|
246
|
+
<ModelSelector size="default" /> // Standard (h-9)
|
|
247
|
+
<ModelSelector size="lg" /> // Large (h-10)
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
## How It Works
|
|
251
|
+
|
|
252
|
+
The default `ModelSelector` export registers the selection with assistant-ui's `ModelContext` system:
|
|
253
|
+
|
|
254
|
+
1. The component calls `aui.modelContext().register()` with `config.modelName`, plus `config.reasoningEffort` when the selected model supports the chosen level
|
|
255
|
+
2. The `AssistantChatTransport` includes `config` in the request body of every chat request
|
|
256
|
+
3. Your API route reads `config.modelName` and `config.reasoningEffort`
|
|
257
|
+
|
|
258
|
+
This works out of the box with `@assistant-ui/react-ai-sdk`. `ModelSelector.Root` performs no registration; it is purely presentational, with controlled and uncontrolled props for the value, effort, and open state.
|
|
259
|
+
|
|
260
|
+
## API Reference
|
|
150
261
|
|
|
151
262
|
### ModelSelector
|
|
152
263
|
|
|
@@ -162,7 +273,7 @@ import {
|
|
|
162
273
|
{
|
|
163
274
|
name: "defaultValue",
|
|
164
275
|
type: "string",
|
|
165
|
-
description: "Initial model ID for uncontrolled usage.",
|
|
276
|
+
description: "Initial model ID for uncontrolled usage. Defaults to the first model, captured on first render; if models loads asynchronously, control the value instead.",
|
|
166
277
|
},
|
|
167
278
|
{
|
|
168
279
|
name: "value",
|
|
@@ -174,6 +285,27 @@ import {
|
|
|
174
285
|
type: "(value: string) => void",
|
|
175
286
|
description: "Callback when selected model changes.",
|
|
176
287
|
},
|
|
288
|
+
{
|
|
289
|
+
name: "defaultEffort",
|
|
290
|
+
type: "string",
|
|
291
|
+
description: "Initial effort level ID for uncontrolled usage.",
|
|
292
|
+
},
|
|
293
|
+
{
|
|
294
|
+
name: "effort",
|
|
295
|
+
type: "string",
|
|
296
|
+
description: "Controlled effort level ID.",
|
|
297
|
+
},
|
|
298
|
+
{
|
|
299
|
+
name: "onEffortChange",
|
|
300
|
+
type: "(effort: string) => void",
|
|
301
|
+
description: "Callback when effort level changes.",
|
|
302
|
+
},
|
|
303
|
+
{
|
|
304
|
+
name: "searchable",
|
|
305
|
+
type: "boolean",
|
|
306
|
+
default: "false",
|
|
307
|
+
description: "Render a search input above the model list.",
|
|
308
|
+
},
|
|
177
309
|
{
|
|
178
310
|
name: "variant",
|
|
179
311
|
type: '"outline" | "ghost" | "muted"',
|
|
@@ -221,5 +353,40 @@ import {
|
|
|
221
353
|
type: "React.ReactNode",
|
|
222
354
|
description: "Optional icon displayed before the model name.",
|
|
223
355
|
},
|
|
356
|
+
{
|
|
357
|
+
name: "disabled",
|
|
358
|
+
type: "boolean",
|
|
359
|
+
description: "Disable selection of this model.",
|
|
360
|
+
},
|
|
361
|
+
{
|
|
362
|
+
name: "keywords",
|
|
363
|
+
type: "string[]",
|
|
364
|
+
description: "Extra search terms matched by ModelSelector.Search (e.g. the provider name).",
|
|
365
|
+
},
|
|
366
|
+
{
|
|
367
|
+
name: "efforts",
|
|
368
|
+
type: "boolean | ModelSelectorEffortOption[]",
|
|
369
|
+
description: "Reasoning effort levels. true enables the default Low/Medium/High; pass a custom { id, name } list to override. Omit for models without configurable reasoning.",
|
|
370
|
+
},
|
|
224
371
|
]}
|
|
225
372
|
/>
|
|
373
|
+
|
|
374
|
+
### `useModelSelectorEfforts`
|
|
375
|
+
|
|
376
|
+
```tsx
|
|
377
|
+
const { efforts, effort, setEffort } = useModelSelectorEfforts();
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
The selected model's effort levels and the active selection, for building a [custom effort UI](#custom-effort-ui) inside `ModelSelector.Content`. `efforts` is `undefined` for models without configurable reasoning.
|
|
381
|
+
|
|
382
|
+
### `resolveModelEffort`
|
|
383
|
+
|
|
384
|
+
```tsx
|
|
385
|
+
resolveModelEffort(models, modelId, effort); // => string | undefined
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
Returns the effort ID when the given model supports it, otherwise `undefined`. This is the [sticky selection](#sticky-selection) rule the default component applies before registering the selection.
|
|
389
|
+
|
|
390
|
+
## Related
|
|
391
|
+
|
|
392
|
+
- [Model Context](/docs/copilots/model-context): How registered context (instructions, tools, config) reaches your backend
|