@assistant-ui/mcp-docs-server 0.1.28 → 0.1.30
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 +18 -10
- package/.docs/organized/code-examples/with-a2a.md +12 -24
- package/.docs/organized/code-examples/with-ag-ui.md +14 -11
- package/.docs/organized/code-examples/with-ai-sdk-v6.md +12 -12
- package/.docs/organized/code-examples/with-artifacts.md +14 -12
- package/.docs/organized/code-examples/with-assistant-transport.md +13 -14
- package/.docs/organized/code-examples/with-chain-of-thought.md +11 -11
- package/.docs/organized/code-examples/with-cloud-standalone.md +17 -14
- package/.docs/organized/code-examples/with-cloud.md +12 -13
- package/.docs/organized/code-examples/with-custom-thread-list.md +12 -12
- package/.docs/organized/code-examples/with-elevenlabs-conversational.md +19 -14
- package/.docs/organized/code-examples/with-elevenlabs-scribe.md +15 -15
- package/.docs/organized/code-examples/with-expo.md +27 -23
- package/.docs/organized/code-examples/with-external-store.md +11 -11
- package/.docs/organized/code-examples/with-ffmpeg.md +19 -14
- package/.docs/organized/code-examples/with-generative-ui.md +11 -11
- package/.docs/organized/code-examples/with-google-adk.md +10 -10
- package/.docs/organized/code-examples/with-heat-graph.md +8 -8
- package/.docs/organized/code-examples/with-interactables.md +12 -27
- package/.docs/organized/code-examples/with-langchain.md +437 -0
- package/.docs/organized/code-examples/with-langgraph.md +20 -20
- package/.docs/organized/code-examples/with-livekit.md +59 -18
- package/.docs/organized/code-examples/with-opencode.md +2392 -0
- package/.docs/organized/code-examples/with-parent-id-grouping.md +12 -12
- package/.docs/organized/code-examples/with-react-hook-form.md +223 -151
- package/.docs/organized/code-examples/with-react-ink.md +3 -3
- package/.docs/organized/code-examples/with-react-router.md +15 -15
- package/.docs/organized/code-examples/with-store.md +11 -8
- package/.docs/organized/code-examples/with-tanstack.md +14 -14
- package/.docs/organized/code-examples/with-tap-runtime.md +13 -9
- package/.docs/raw/docs/(docs)/guides/mentions.mdx +248 -109
- package/.docs/raw/docs/(docs)/guides/slash-commands.mdx +112 -92
- package/.docs/raw/docs/(docs)/guides/voice.mdx +10 -3
- package/.docs/raw/docs/(docs)/rtl.mdx +79 -0
- package/.docs/raw/docs/(reference)/api-reference/primitives/composer.mdx +149 -40
- package/.docs/raw/docs/(reference)/api-reference/primitives/message-part.mdx +2 -0
- package/.docs/raw/docs/(reference)/api-reference/runtimes/thread-list-runtime.mdx +4 -0
- package/.docs/raw/docs/cloud/ai-sdk-assistant-ui.mdx +1 -1
- package/.docs/raw/docs/primitives/composer.mdx +94 -62
- package/.docs/raw/docs/runtimes/ai-sdk/v6.mdx +57 -0
- package/.docs/raw/docs/runtimes/custom/custom-thread-list.mdx +49 -3
- package/.docs/raw/docs/runtimes/custom/external-store.mdx +39 -1
- package/.docs/raw/docs/runtimes/custom/local.mdx +65 -18
- package/.docs/raw/docs/runtimes/google-adk/index.mdx +62 -0
- package/.docs/raw/docs/runtimes/langchain/comparison.mdx +60 -0
- package/.docs/raw/docs/runtimes/langchain/index.mdx +210 -0
- package/.docs/raw/docs/runtimes/langgraph/index.mdx +288 -60
- package/.docs/raw/docs/runtimes/pick-a-runtime.mdx +12 -4
- package/.docs/raw/docs/ui/composer-trigger-popover.mdx +199 -0
- package/.docs/raw/docs/ui/directive-text.mdx +113 -0
- package/.docs/raw/docs/ui/reasoning.mdx +13 -9
- package/dist/utils/logger.js +1 -1
- package/dist/utils/logger.js.map +1 -1
- package/package.json +4 -4
- package/src/utils/logger.ts +1 -1
- package/.docs/raw/docs/ui/mention.mdx +0 -168
|
@@ -3,6 +3,10 @@ title: Getting Started
|
|
|
3
3
|
description: Connect to LangGraph Cloud API for agent workflows with streaming.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
+
<Callout type="info">
|
|
7
|
+
If you are already using `@langchain/react`'s `useStream` hook, the alternative [`@assistant-ui/react-langchain`](/docs/runtimes/langchain) adapter may fit better. `@assistant-ui/react-langgraph` (this page) integrates with `@langchain/langgraph-sdk` directly and has the broader feature set — subgraph events, UI messages, message metadata, end-to-end cancellation. See the [comparison](/docs/runtimes/langchain/comparison).
|
|
8
|
+
</Callout>
|
|
9
|
+
|
|
6
10
|
## Requirements
|
|
7
11
|
|
|
8
12
|
You need a LangGraph Cloud API server. You can start a server locally via [LangGraph Studio](https://github.com/langchain-ai/langgraph-studio) or use [LangSmith](https://www.langchain.com/langsmith) for a hosted version.
|
|
@@ -60,6 +64,8 @@ NEXT_PUBLIC_LANGGRAPH_ASSISTANT_ID=your_graph_id
|
|
|
60
64
|
```tsx twoslash title="@/app/api/[...path]/route.ts"
|
|
61
65
|
import { NextRequest, NextResponse } from "next/server";
|
|
62
66
|
|
|
67
|
+
export const runtime = "edge";
|
|
68
|
+
|
|
63
69
|
function getCorsHeaders() {
|
|
64
70
|
return {
|
|
65
71
|
"Access-Control-Allow-Origin": "*",
|
|
@@ -84,6 +90,7 @@ async function handleRequest(req: NextRequest, method: string) {
|
|
|
84
90
|
headers: {
|
|
85
91
|
"x-api-key": process.env["LANGCHAIN_API_KEY"] || "",
|
|
86
92
|
},
|
|
93
|
+
signal: req.signal,
|
|
87
94
|
};
|
|
88
95
|
|
|
89
96
|
if (["POST", "PUT", "PATCH"].includes(method)) {
|
|
@@ -142,45 +149,15 @@ export const OPTIONS = () =>
|
|
|
142
149
|
// @filename: /lib/chatApi.ts
|
|
143
150
|
|
|
144
151
|
// ---cut---
|
|
145
|
-
import { Client
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
const
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
};
|
|
154
|
-
|
|
155
|
-
export const createThread = async () => {
|
|
156
|
-
const client = createClient();
|
|
157
|
-
return client.threads.create();
|
|
158
|
-
};
|
|
159
|
-
|
|
160
|
-
export const getThreadState = async (
|
|
161
|
-
threadId: string,
|
|
162
|
-
): Promise<ThreadState<{ messages: LangChainMessage[] }>> => {
|
|
163
|
-
const client = createClient();
|
|
164
|
-
return client.threads.getState(threadId);
|
|
165
|
-
};
|
|
166
|
-
|
|
167
|
-
export const sendMessage = async (params: {
|
|
168
|
-
threadId: string;
|
|
169
|
-
messages?: LangChainMessage[];
|
|
170
|
-
command?: LangGraphCommand;
|
|
171
|
-
}) => {
|
|
172
|
-
const client = createClient();
|
|
173
|
-
return client.runs.stream(
|
|
174
|
-
params.threadId,
|
|
175
|
-
process.env["NEXT_PUBLIC_LANGGRAPH_ASSISTANT_ID"]!,
|
|
176
|
-
{
|
|
177
|
-
input: params.messages?.length
|
|
178
|
-
? { messages: params.messages }
|
|
179
|
-
: null,
|
|
180
|
-
command: params.command,
|
|
181
|
-
streamMode: ["messages", "updates"],
|
|
182
|
-
},
|
|
183
|
-
);
|
|
152
|
+
import { Client } from "@langchain/langgraph-sdk";
|
|
153
|
+
|
|
154
|
+
export const createClient = () => {
|
|
155
|
+
const apiUrl =
|
|
156
|
+
process.env["NEXT_PUBLIC_LANGGRAPH_API_URL"] ||
|
|
157
|
+
(typeof window !== "undefined"
|
|
158
|
+
? new URL("/api", window.location.href).href
|
|
159
|
+
: "/api");
|
|
160
|
+
return new Client({ apiUrl });
|
|
184
161
|
};
|
|
185
162
|
```
|
|
186
163
|
|
|
@@ -196,32 +173,41 @@ export const sendMessage = async (params: {
|
|
|
196
173
|
// ---cut---
|
|
197
174
|
"use client";
|
|
198
175
|
|
|
176
|
+
import { useMemo } from "react";
|
|
199
177
|
import { Thread } from "@/components/assistant-ui/thread";
|
|
200
178
|
import { AssistantRuntimeProvider } from "@assistant-ui/react";
|
|
201
|
-
import {
|
|
179
|
+
import {
|
|
180
|
+
unstable_createLangGraphStream,
|
|
181
|
+
useLangGraphRuntime,
|
|
182
|
+
type LangChainMessage,
|
|
183
|
+
} from "@assistant-ui/react-langgraph";
|
|
202
184
|
|
|
203
|
-
import {
|
|
185
|
+
import { createClient } from "@/lib/chatApi";
|
|
204
186
|
|
|
205
|
-
|
|
206
|
-
const runtime = useLangGraphRuntime({
|
|
207
|
-
stream: async function* (messages, { initialize, command }) {
|
|
208
|
-
const { externalId } = await initialize();
|
|
209
|
-
if (!externalId) throw new Error("Thread not found");
|
|
187
|
+
const ASSISTANT_ID = process.env["NEXT_PUBLIC_LANGGRAPH_ASSISTANT_ID"]!;
|
|
210
188
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
189
|
+
export function MyAssistant() {
|
|
190
|
+
const client = useMemo(() => createClient(), []);
|
|
191
|
+
const stream = useMemo(
|
|
192
|
+
() =>
|
|
193
|
+
unstable_createLangGraphStream({
|
|
194
|
+
client,
|
|
195
|
+
assistantId: ASSISTANT_ID,
|
|
196
|
+
}),
|
|
197
|
+
[client],
|
|
198
|
+
);
|
|
216
199
|
|
|
217
|
-
|
|
218
|
-
|
|
200
|
+
const runtime = useLangGraphRuntime({
|
|
201
|
+
unstable_allowCancellation: true,
|
|
202
|
+
stream,
|
|
219
203
|
create: async () => {
|
|
220
|
-
const { thread_id } = await
|
|
204
|
+
const { thread_id } = await client.threads.create();
|
|
221
205
|
return { externalId: thread_id };
|
|
222
206
|
},
|
|
223
207
|
load: async (externalId) => {
|
|
224
|
-
const state = await
|
|
208
|
+
const state = await client.threads.getState<{
|
|
209
|
+
messages: LangChainMessage[];
|
|
210
|
+
}>(externalId);
|
|
225
211
|
return {
|
|
226
212
|
messages: state.values.messages,
|
|
227
213
|
interrupts: state.tasks[0]?.interrupts,
|
|
@@ -320,18 +306,38 @@ const runtime = useLangGraphRuntime({
|
|
|
320
306
|
stream: async (messages, { initialize, ...config }) => { /* ... */ },
|
|
321
307
|
eventHandlers: {
|
|
322
308
|
onMessageChunk: (chunk, metadata) => {
|
|
323
|
-
// Fired for each chunk in messages-tuple mode
|
|
324
|
-
// metadata contains langgraph_step, langgraph_node, ls_model_name, etc.
|
|
309
|
+
// Fired for each chunk in messages-tuple mode.
|
|
310
|
+
// `metadata` contains langgraph_step, langgraph_node, ls_model_name, etc.
|
|
311
|
+
// For pipe-namespaced events emitted by subgraphs (e.g. `messages|tools:call_abc`),
|
|
312
|
+
// `metadata.namespace` holds the suffix ("tools:call_abc"). Use it to attribute
|
|
313
|
+
// a chunk to a specific subgraph.
|
|
325
314
|
},
|
|
326
315
|
onValues: (values) => {
|
|
327
|
-
// Fired when a
|
|
316
|
+
// Fired when a top-level `values` event is received.
|
|
317
|
+
// Subgraph `values` events are routed to `onSubgraphValues` instead.
|
|
328
318
|
},
|
|
329
319
|
onUpdates: (updates) => {
|
|
330
|
-
// Fired when
|
|
320
|
+
// Fired when a top-level `updates` event is received.
|
|
321
|
+
// Subgraph `updates` events are routed to `onSubgraphUpdates` instead.
|
|
322
|
+
},
|
|
323
|
+
onSubgraphValues: (namespace, values) => {
|
|
324
|
+
// Fired when a subgraph `values|<namespace>` event is received
|
|
325
|
+
// (e.g. `namespace === "tools:call_abc"`). Use this to observe
|
|
326
|
+
// subgraph-internal state without mixing it into `onValues`.
|
|
327
|
+
},
|
|
328
|
+
onSubgraphUpdates: (namespace, updates) => {
|
|
329
|
+
// Fired when a subgraph `updates|<namespace>` event is received.
|
|
331
330
|
},
|
|
332
331
|
onMetadata: (metadata) => { /* thread metadata */ },
|
|
333
332
|
onInfo: (info) => { /* informational messages */ },
|
|
334
|
-
onError: (error) => {
|
|
333
|
+
onError: (error) => {
|
|
334
|
+
// Fired for both top-level and subgraph errors.
|
|
335
|
+
},
|
|
336
|
+
onSubgraphError: (namespace, error) => {
|
|
337
|
+
// Additionally fired for subgraph errors with the namespace.
|
|
338
|
+
// Use to attribute a subgraph failure to its source without marking
|
|
339
|
+
// the parent message as incomplete (that only happens for top-level errors).
|
|
340
|
+
},
|
|
335
341
|
onCustomEvent: (type, data) => { /* custom events */ },
|
|
336
342
|
},
|
|
337
343
|
});
|
|
@@ -396,6 +402,51 @@ const runtime = useLangGraphRuntime({
|
|
|
396
402
|
|
|
397
403
|
See the [Cloud Persistence guide](/docs/cloud/langgraph) for detailed setup instructions.
|
|
398
404
|
|
|
405
|
+
### Custom Thread List
|
|
406
|
+
|
|
407
|
+
To surface pre-existing LangGraph `thread_id`s in the thread picker without running assistant-cloud, pass a `RemoteThreadListAdapter` via `unstable_threadListAdapter`. A common implementation backs `list()` with `client.threads.search()` and `initialize()` with `client.threads.create()`.
|
|
408
|
+
|
|
409
|
+
```typescript
|
|
410
|
+
import type { RemoteThreadListAdapter } from "@assistant-ui/react";
|
|
411
|
+
import { Client } from "@langchain/langgraph-sdk";
|
|
412
|
+
|
|
413
|
+
const client = new Client({ apiUrl: process.env.NEXT_PUBLIC_LANGGRAPH_API_URL });
|
|
414
|
+
|
|
415
|
+
const threadListAdapter: RemoteThreadListAdapter = {
|
|
416
|
+
async list() {
|
|
417
|
+
const threads = await client.threads.search({ limit: 50 });
|
|
418
|
+
return {
|
|
419
|
+
threads: threads.map((t) => ({
|
|
420
|
+
status: "regular",
|
|
421
|
+
remoteId: t.thread_id,
|
|
422
|
+
externalId: t.thread_id,
|
|
423
|
+
title: (t.metadata as { title?: string } | undefined)?.title,
|
|
424
|
+
})),
|
|
425
|
+
};
|
|
426
|
+
},
|
|
427
|
+
async initialize() {
|
|
428
|
+
const t = await client.threads.create();
|
|
429
|
+
return { remoteId: t.thread_id, externalId: t.thread_id };
|
|
430
|
+
},
|
|
431
|
+
async delete(remoteId) {
|
|
432
|
+
await client.threads.delete(remoteId);
|
|
433
|
+
},
|
|
434
|
+
// rename, archive, unarchive, fetch, generateTitle — see link below
|
|
435
|
+
};
|
|
436
|
+
|
|
437
|
+
const runtime = useLangGraphRuntime({
|
|
438
|
+
stream: async function* (messages, { initialize }) { /* ... */ },
|
|
439
|
+
load: async (externalId) => { /* ... */ },
|
|
440
|
+
unstable_threadListAdapter: threadListAdapter,
|
|
441
|
+
});
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
Setting `remoteId === externalId` keeps the ids assistant-ui stores aligned with the LangGraph thread ids your `load` and `stream` callbacks receive. See the [Custom Thread List guide](/docs/runtimes/custom/custom-thread-list) for the full adapter contract.
|
|
445
|
+
|
|
446
|
+
<Callout type="info">
|
|
447
|
+
When `unstable_threadListAdapter` is provided, the `cloud`, `create`, and `delete` options are ignored — the adapter owns the full thread-list lifecycle.
|
|
448
|
+
</Callout>
|
|
449
|
+
|
|
399
450
|
## Message Editing & Regeneration
|
|
400
451
|
|
|
401
452
|
LangGraph uses server-side checkpoints for state management. To support message editing (branching) and regeneration, you need to provide a `getCheckpointId` callback that resolves the appropriate checkpoint for server-side forking.
|
|
@@ -469,3 +520,180 @@ LangGraph supports interrupting the execution flow to request user input or hand
|
|
|
469
520
|
3. The runtime will automatically restore the interrupt state when switching threads
|
|
470
521
|
|
|
471
522
|
This feature is particularly useful for applications that require user approval flows, multi-step forms, or any other interactive elements that might span multiple thread switches.
|
|
523
|
+
|
|
524
|
+
## Generative UI (`ui_message`)
|
|
525
|
+
|
|
526
|
+
LangGraph's [Generative UI](https://docs.langchain.com/langsmith/generative-ui-react) lets your graph emit structured UI components alongside assistant messages via `push_ui_message` (Python) or `typedUi().push()` (TypeScript). The assistant-ui LangGraph adapter translates these into [`DataMessagePart`s](/docs/guides/tool-ui) on the associated assistant message, which you render with the existing `makeAssistantDataUI` API.
|
|
527
|
+
|
|
528
|
+
### Enable the `custom` stream mode
|
|
529
|
+
|
|
530
|
+
UI messages are emitted through LangGraph's `custom` stream channel. Make sure your `sendMessage` helper includes `"custom"` in `streamMode`:
|
|
531
|
+
|
|
532
|
+
```ts
|
|
533
|
+
streamMode: ["messages", "updates", "custom"]
|
|
534
|
+
```
|
|
535
|
+
|
|
536
|
+
Alternatively, if your graph accumulates UI messages in state under the `ui` key (the default for `typedUi`), `"values"` also works — the adapter reads both paths.
|
|
537
|
+
|
|
538
|
+
### Custom state key
|
|
539
|
+
|
|
540
|
+
If your graph uses a non-default `stateKey` with `typedUi(config, { stateKey: "my_ui" })` on the server, pass the matching `uiStateKey` option to `useLangGraphRuntime` on the client:
|
|
541
|
+
|
|
542
|
+
```ts
|
|
543
|
+
const runtime = useLangGraphRuntime({
|
|
544
|
+
stream: async function* (messages, { initialize }) { /* ... */ },
|
|
545
|
+
uiStateKey: "my_ui",
|
|
546
|
+
});
|
|
547
|
+
```
|
|
548
|
+
|
|
549
|
+
This only affects the `values` stream path — the `custom` channel carries each UI event individually and doesn't rely on the state key.
|
|
550
|
+
|
|
551
|
+
### Emit a UI message from your graph
|
|
552
|
+
|
|
553
|
+
```python title="Python"
|
|
554
|
+
from langgraph.graph.ui import push_ui_message
|
|
555
|
+
from langchain_core.messages import AIMessage
|
|
556
|
+
|
|
557
|
+
async def chart_node(state, config):
|
|
558
|
+
message = AIMessage(id="msg-1", content="Here's your chart.")
|
|
559
|
+
push_ui_message(
|
|
560
|
+
"chart",
|
|
561
|
+
{"series": [1, 2, 3], "title": "Sales"},
|
|
562
|
+
message=message, # Links the UI to this AI message
|
|
563
|
+
)
|
|
564
|
+
return {"messages": [message]}
|
|
565
|
+
```
|
|
566
|
+
|
|
567
|
+
```ts title="TypeScript"
|
|
568
|
+
import { typedUi } from "@langchain/langgraph-sdk/react-ui/server";
|
|
569
|
+
import type { ComponentRegistry } from "./components";
|
|
570
|
+
|
|
571
|
+
export async function chartNode(state, config) {
|
|
572
|
+
const ui = typedUi<ComponentRegistry>(config);
|
|
573
|
+
const message = { id: "msg-1", type: "ai", content: "Here's your chart." };
|
|
574
|
+
ui.push(
|
|
575
|
+
{ name: "chart", props: { series: [1, 2, 3], title: "Sales" } },
|
|
576
|
+
{ message },
|
|
577
|
+
);
|
|
578
|
+
return { messages: [message] };
|
|
579
|
+
}
|
|
580
|
+
```
|
|
581
|
+
|
|
582
|
+
Passing `message` (Python) or `{ message }` (TypeScript) is what links the UI component to a specific assistant message — the adapter reads `metadata.message_id` to attach the generated `DataMessagePart` to the correct message in the thread.
|
|
583
|
+
|
|
584
|
+
### Register a renderer on the client
|
|
585
|
+
|
|
586
|
+
```tsx title="@/components/ChartUI.tsx"
|
|
587
|
+
import { makeAssistantDataUI } from "@assistant-ui/react";
|
|
588
|
+
|
|
589
|
+
type ChartProps = {
|
|
590
|
+
series: number[];
|
|
591
|
+
title: string;
|
|
592
|
+
};
|
|
593
|
+
|
|
594
|
+
export const ChartUI = makeAssistantDataUI<ChartProps>({
|
|
595
|
+
name: "chart",
|
|
596
|
+
render: ({ data }) => (
|
|
597
|
+
<div>
|
|
598
|
+
<h3>{data.title}</h3>
|
|
599
|
+
<Chart series={data.series} />
|
|
600
|
+
</div>
|
|
601
|
+
),
|
|
602
|
+
});
|
|
603
|
+
```
|
|
604
|
+
|
|
605
|
+
Mount the component once somewhere inside the `AssistantRuntimeProvider` tree. It renders nothing itself — it only registers the renderer:
|
|
606
|
+
|
|
607
|
+
```tsx title="@/components/MyAssistant.tsx"
|
|
608
|
+
<AssistantRuntimeProvider runtime={runtime}>
|
|
609
|
+
<ChartUI />
|
|
610
|
+
<Thread />
|
|
611
|
+
</AssistantRuntimeProvider>
|
|
612
|
+
```
|
|
613
|
+
|
|
614
|
+
When a matching UI message arrives, the adapter appends a `{ type: "data", name: "chart", data: { series, title } }` part to the parent assistant message and the registered component renders inline.
|
|
615
|
+
|
|
616
|
+
### Register renderers via `uiComponents`
|
|
617
|
+
|
|
618
|
+
Instead of mounting separate `makeAssistantDataUI` components, you can register renderers directly on the runtime hook via the `uiComponents` option:
|
|
619
|
+
|
|
620
|
+
```tsx title="@/components/MyAssistant.tsx"
|
|
621
|
+
const runtime = useLangGraphRuntime({
|
|
622
|
+
stream: async function* (messages, { initialize }) { /* ... */ },
|
|
623
|
+
uiComponents: {
|
|
624
|
+
renderers: {
|
|
625
|
+
chart: ({ data }) => <Chart series={data.series} title={data.title} />,
|
|
626
|
+
table: ({ data }) => <DataTable rows={data.rows} />,
|
|
627
|
+
},
|
|
628
|
+
},
|
|
629
|
+
});
|
|
630
|
+
```
|
|
631
|
+
|
|
632
|
+
Static `renderers` are matched by `ui_message` name. If no match is found, the part renders nothing unless a `fallback` is provided.
|
|
633
|
+
|
|
634
|
+
### Dynamic loading with `fallback`
|
|
635
|
+
|
|
636
|
+
LangSmith's [Generative UI](https://docs.langchain.com/langsmith/generative-ui-react) supports colocating UI code with your graph and loading it at runtime via `LoadExternalComponent`. The `fallback` option handles any `ui_message` name that has no static renderer:
|
|
637
|
+
|
|
638
|
+
```tsx title="@/components/MyAssistant.tsx"
|
|
639
|
+
import { LoadExternalComponent } from "@langchain/langgraph-sdk/react-ui";
|
|
640
|
+
|
|
641
|
+
const runtime = useLangGraphRuntime({
|
|
642
|
+
stream: async function* (messages, { initialize }) { /* ... */ },
|
|
643
|
+
uiComponents: {
|
|
644
|
+
fallback: ({ name, data }) => (
|
|
645
|
+
<LoadExternalComponent name={name} props={data} />
|
|
646
|
+
),
|
|
647
|
+
renderers: {
|
|
648
|
+
chart: ({ data }) => <Chart {...data} />,
|
|
649
|
+
},
|
|
650
|
+
},
|
|
651
|
+
});
|
|
652
|
+
```
|
|
653
|
+
|
|
654
|
+
With this setup:
|
|
655
|
+
- A `ui_message` with `name: "chart"` renders the static `Chart` component
|
|
656
|
+
- Any other name (e.g. `"dashboard"`, `"form"`) is handled by `fallback`, which fetches the component from LangSmith at runtime
|
|
657
|
+
|
|
658
|
+
The `fallback` component receives the same props as any data renderer: `name`, `data`, and part state metadata. This lets you pass the component name and props straight through to `LoadExternalComponent`.
|
|
659
|
+
|
|
660
|
+
### Semantics
|
|
661
|
+
|
|
662
|
+
The adapter mirrors the reducer in `@langchain/langgraph-sdk/react-ui` exactly:
|
|
663
|
+
|
|
664
|
+
- UI messages are keyed by their own `id`. Pushing the same id again **replaces** the existing entry
|
|
665
|
+
- Passing `metadata: { merge: true }` shallow-merges `props` onto the previous entry
|
|
666
|
+
- Emitting `{ type: "remove-ui", id }` (via `delete_ui_message` / `ui.delete(id)`) removes the entry
|
|
667
|
+
- UI messages without `metadata.message_id` are held in the runtime but not injected into any message; use `useLangGraphUIMessages()` to access the raw list if needed
|
|
668
|
+
|
|
669
|
+
### Restore persisted UI messages on thread switch
|
|
670
|
+
|
|
671
|
+
If your graph persists UI messages in state via `typedUi`, return them from the `load` callback so they're restored when the user switches threads or refreshes the page:
|
|
672
|
+
|
|
673
|
+
```tsx
|
|
674
|
+
const runtime = useLangGraphRuntime({
|
|
675
|
+
stream: async function* (messages, { initialize }) { /* ... */ },
|
|
676
|
+
load: async (externalId) => {
|
|
677
|
+
const state = await getThreadState(externalId);
|
|
678
|
+
return {
|
|
679
|
+
messages: state.values.messages,
|
|
680
|
+
uiMessages: state.values.ui,
|
|
681
|
+
interrupts: state.tasks[0]?.interrupts,
|
|
682
|
+
};
|
|
683
|
+
},
|
|
684
|
+
});
|
|
685
|
+
```
|
|
686
|
+
|
|
687
|
+
Without this, each reload starts with an empty UI list even though the messages themselves are loaded.
|
|
688
|
+
|
|
689
|
+
### Escape hatch: `useLangGraphUIMessages`
|
|
690
|
+
|
|
691
|
+
```tsx
|
|
692
|
+
import { useLangGraphUIMessages } from "@assistant-ui/react-langgraph";
|
|
693
|
+
|
|
694
|
+
function Sidebar() {
|
|
695
|
+
const uiMessages = useLangGraphUIMessages();
|
|
696
|
+
// Filter, group, or render UI messages outside the thread
|
|
697
|
+
return <>{uiMessages.map(/* ... */)}</>;
|
|
698
|
+
}
|
|
699
|
+
```
|
|
@@ -11,7 +11,8 @@ Choosing the right runtime is crucial for your assistant-ui implementation. This
|
|
|
11
11
|
graph TD
|
|
12
12
|
A[What's your starting point?] --> B{Existing Framework?}
|
|
13
13
|
B -->|Vercel AI SDK| C[Use AI SDK Integration]
|
|
14
|
-
B -->|LangGraph|
|
|
14
|
+
B -->|LangGraph via langgraph-sdk| D1[Use react-langgraph]
|
|
15
|
+
B -->|LangChain via @langchain/react useStream| D2[Use react-langchain]
|
|
15
16
|
B -->|LangServe| E[Use LangServe Runtime]
|
|
16
17
|
B -->|Mastra| F[Use Mastra Runtime]
|
|
17
18
|
B -->|AG-UI Protocol| J[Use AG-UI Runtime]
|
|
@@ -55,9 +56,14 @@ For popular frameworks, we provide ready-to-use integrations built on top of our
|
|
|
55
56
|
/>
|
|
56
57
|
<Card
|
|
57
58
|
title="LangGraph"
|
|
58
|
-
description="
|
|
59
|
+
description="Integrates with `@langchain/langgraph-sdk` directly. Broader feature set: subgraph events, UI messages, message metadata, cancellation."
|
|
59
60
|
href="/docs/runtimes/langgraph"
|
|
60
61
|
/>
|
|
62
|
+
<Card
|
|
63
|
+
title="LangChain useStream"
|
|
64
|
+
description="Wraps `useStream` from `@langchain/react`. Lighter-weight, stays aligned with upstream. Fewer features today."
|
|
65
|
+
href="/docs/runtimes/langchain"
|
|
66
|
+
/>
|
|
61
67
|
<Card
|
|
62
68
|
title="LangServe"
|
|
63
69
|
description="For LangChain applications deployed with LangServe"
|
|
@@ -87,13 +93,14 @@ For popular frameworks, we provide ready-to-use integrations built on top of our
|
|
|
87
93
|
The pre-built integrations (AI SDK, LangGraph, etc.) are **not separate runtime types**. They're convenient wrappers built on top of our core runtimes:
|
|
88
94
|
|
|
89
95
|
- **AI SDK Integration** → Built on `LocalRuntime` with streaming adapter
|
|
90
|
-
- **LangGraph Runtime** → Built on `
|
|
96
|
+
- **LangGraph Runtime** → Built on `ExternalStoreRuntime`, integrates with `@langchain/langgraph-sdk`
|
|
97
|
+
- **LangChain useStream Runtime** → Built on `ExternalStoreRuntime`, wraps `useStream` from `@langchain/react`
|
|
91
98
|
- **LangServe Runtime** → Built on `LocalRuntime` with LangServe client adapter
|
|
92
99
|
- **Mastra Runtime** → Built on `LocalRuntime` with workflow adapter
|
|
93
100
|
- **AG-UI Runtime** → Built on `LocalRuntime` with AG-UI protocol adapter
|
|
94
101
|
- **A2A Runtime** → Built on `LocalRuntime` with Agent-to-Agent protocol adapter
|
|
95
102
|
|
|
96
|
-
This means
|
|
103
|
+
This means pre-built integrations give you assistant-ui's features — state management, streaming, UI primitives — with zero configuration for your specific framework, regardless of whether the adapter happens to build on `LocalRuntime` or `ExternalStoreRuntime` internally. The list above tells you which core runtime each adapter uses.
|
|
97
104
|
|
|
98
105
|
### When to Use Pre-Built vs Core Runtimes
|
|
99
106
|
|
|
@@ -228,6 +235,7 @@ Explore our implementation examples:
|
|
|
228
235
|
- [`LocalRuntime` Guide](/docs/runtimes/custom/local)
|
|
229
236
|
- [`ExternalStoreRuntime` Guide](/docs/runtimes/custom/external-store)
|
|
230
237
|
- [LangGraph Integration](/docs/runtimes/langgraph)
|
|
238
|
+
- [LangChain useStream Integration](/docs/runtimes/langchain)
|
|
231
239
|
3. **Start with an example** from our [examples repository](https://github.com/assistant-ui/assistant-ui/tree/main/examples)
|
|
232
240
|
4. **Add features progressively** using adapters
|
|
233
241
|
5. **Consider Assistant Cloud** for production persistence
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Composer Trigger Popover
|
|
3
|
+
description: Reusable picker UI for @ mentions, / slash commands, and any other character-triggered popover.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
import { ComposerTriggerPopoverSample } from "@/components/docs/samples/composer-trigger-popover";
|
|
7
|
+
|
|
8
|
+
<ComposerTriggerPopoverSample />
|
|
9
|
+
|
|
10
|
+
## Getting Started
|
|
11
|
+
|
|
12
|
+
<Steps>
|
|
13
|
+
<Step>
|
|
14
|
+
|
|
15
|
+
### Add `composer-trigger-popover`
|
|
16
|
+
|
|
17
|
+
<InstallCommand shadcn={["composer-trigger-popover"]} />
|
|
18
|
+
|
|
19
|
+
This adds `/components/assistant-ui/composer-trigger-popover.tsx` — a generic picker UI (Categories + Items + Back) driven by an adapter and one of two behavior props: `directive` (insert a chip) or `action` (run a callback).
|
|
20
|
+
|
|
21
|
+
</Step>
|
|
22
|
+
<Step>
|
|
23
|
+
|
|
24
|
+
### Wrap the composer
|
|
25
|
+
|
|
26
|
+
Place `ComposerPrimitive.Unstable_TriggerPopoverRoot` around your composer. Any number of `ComposerTriggerPopover` declarations can live inside — each with its own trigger character, adapter, and behavior prop.
|
|
27
|
+
|
|
28
|
+
```tsx title="components/assistant-ui/thread.tsx"
|
|
29
|
+
import { ComposerPrimitive } from "@assistant-ui/react";
|
|
30
|
+
import { ComposerTriggerPopover } from "@/components/assistant-ui/composer-trigger-popover";
|
|
31
|
+
|
|
32
|
+
const Composer = () => (
|
|
33
|
+
<ComposerPrimitive.Unstable_TriggerPopoverRoot>
|
|
34
|
+
<ComposerPrimitive.Root>
|
|
35
|
+
<ComposerPrimitive.Input placeholder="Type @ to mention, / for commands..." />
|
|
36
|
+
<ComposerPrimitive.Send />
|
|
37
|
+
|
|
38
|
+
{/* triggers declared here */}
|
|
39
|
+
</ComposerPrimitive.Root>
|
|
40
|
+
</ComposerPrimitive.Unstable_TriggerPopoverRoot>
|
|
41
|
+
);
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
</Step>
|
|
45
|
+
</Steps>
|
|
46
|
+
|
|
47
|
+
## @ Mention
|
|
48
|
+
|
|
49
|
+
Pair the popover with `unstable_useMentionAdapter` — the hook returns a spreadable `{ adapter, directive }` bundle so selecting an item writes a `:tool[Label]{name=id}` directive into the composer text.
|
|
50
|
+
|
|
51
|
+
```tsx
|
|
52
|
+
import { unstable_useMentionAdapter } from "@assistant-ui/react";
|
|
53
|
+
import { WrenchIcon } from "lucide-react";
|
|
54
|
+
|
|
55
|
+
const mention = unstable_useMentionAdapter();
|
|
56
|
+
|
|
57
|
+
<ComposerTriggerPopover
|
|
58
|
+
char="@"
|
|
59
|
+
{...mention}
|
|
60
|
+
fallbackIcon={WrenchIcon}
|
|
61
|
+
/>;
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Override formatter or add an `onInserted` callback via hook options: `unstable_useMentionAdapter({ formatter, onInserted })`.
|
|
65
|
+
|
|
66
|
+
`unstable_useMentionAdapter` also accepts `items` (flat custom list), `categories` (multi-category drill-down), and `includeModelContextTools` for fine-grained control. See the [Mentions guide](/docs/guides/mentions#built-in-mention-adapter).
|
|
67
|
+
|
|
68
|
+
Render selected mentions as chips in user messages with [`DirectiveText`](/docs/ui/directive-text). For inline chips **inside** the composer, use [`LexicalComposerInput`](/docs/guides/mentions#textarea-vs-lexical).
|
|
69
|
+
|
|
70
|
+
## / Slash Command
|
|
71
|
+
|
|
72
|
+
Use [`unstable_useSlashCommandAdapter`](/docs/guides/slash-commands) to bundle commands (data + `execute`) into `{ adapter, action }` — then plug both into `ComposerTriggerPopover`. By default a directive chip is left in the composer as an audit trail; pass `removeOnExecute` to strip the `/command` text entirely. `iconMap` maps `metadata.icon` strings on items and categories to Lucide icons.
|
|
73
|
+
|
|
74
|
+
```tsx
|
|
75
|
+
import {
|
|
76
|
+
unstable_useSlashCommandAdapter,
|
|
77
|
+
type Unstable_SlashCommand,
|
|
78
|
+
} from "@assistant-ui/react";
|
|
79
|
+
import { FileTextIcon, GlobeIcon, LanguagesIcon, SlashIcon } from "lucide-react";
|
|
80
|
+
|
|
81
|
+
const SLASH_COMMANDS: readonly Unstable_SlashCommand[] = [
|
|
82
|
+
{
|
|
83
|
+
id: "summarize",
|
|
84
|
+
description: "Summarize the conversation",
|
|
85
|
+
icon: "FileText",
|
|
86
|
+
execute: () => {/* ... */},
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
id: "translate",
|
|
90
|
+
description: "Translate to another language",
|
|
91
|
+
icon: "Languages",
|
|
92
|
+
execute: () => {/* ... */},
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
id: "search",
|
|
96
|
+
description: "Search the web",
|
|
97
|
+
icon: "Globe",
|
|
98
|
+
execute: () => {/* ... */},
|
|
99
|
+
},
|
|
100
|
+
];
|
|
101
|
+
|
|
102
|
+
function SlashComposer() {
|
|
103
|
+
const slash = unstable_useSlashCommandAdapter({ commands: SLASH_COMMANDS });
|
|
104
|
+
|
|
105
|
+
return (
|
|
106
|
+
<ComposerTriggerPopover
|
|
107
|
+
char="/"
|
|
108
|
+
{...slash}
|
|
109
|
+
iconMap={{
|
|
110
|
+
FileText: FileTextIcon,
|
|
111
|
+
Languages: LanguagesIcon,
|
|
112
|
+
Globe: GlobeIcon,
|
|
113
|
+
}}
|
|
114
|
+
fallbackIcon={SlashIcon}
|
|
115
|
+
/>
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Combining Triggers
|
|
121
|
+
|
|
122
|
+
Multiple popovers coexist under one `TriggerPopoverRoot`. Each reads state from its own declaration, so `@` and `/` never collide.
|
|
123
|
+
|
|
124
|
+
```tsx
|
|
125
|
+
const commandHandlers: Record<string, () => void> = {
|
|
126
|
+
summarize: () => {/* ... */},
|
|
127
|
+
translate: () => {/* ... */},
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
<ComposerPrimitive.Unstable_TriggerPopoverRoot>
|
|
131
|
+
<ComposerPrimitive.Root>
|
|
132
|
+
<ComposerPrimitive.Input placeholder="Type @ to mention, / for commands..." />
|
|
133
|
+
|
|
134
|
+
<ComposerTriggerPopover
|
|
135
|
+
char="@"
|
|
136
|
+
adapter={mentionAdapter}
|
|
137
|
+
directive={{ formatter: unstable_defaultDirectiveFormatter }}
|
|
138
|
+
fallbackIcon={WrenchIcon}
|
|
139
|
+
/>
|
|
140
|
+
<ComposerTriggerPopover
|
|
141
|
+
char="/"
|
|
142
|
+
adapter={slashAdapter}
|
|
143
|
+
action={{
|
|
144
|
+
formatter: unstable_defaultDirectiveFormatter,
|
|
145
|
+
onExecute: (item) => commandHandlers[item.id]?.(),
|
|
146
|
+
}}
|
|
147
|
+
iconMap={slashIcons}
|
|
148
|
+
fallbackIcon={SlashIcon}
|
|
149
|
+
/>
|
|
150
|
+
</ComposerPrimitive.Root>
|
|
151
|
+
</ComposerPrimitive.Unstable_TriggerPopoverRoot>
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## Keyboard Navigation
|
|
155
|
+
|
|
156
|
+
| Key | Action |
|
|
157
|
+
| --- | --- |
|
|
158
|
+
| <Kbd>ArrowDown</Kbd> | Highlight next item |
|
|
159
|
+
| <Kbd>ArrowUp</Kbd> | Highlight previous item |
|
|
160
|
+
| <Kbd>Enter</Kbd> | Select highlighted item / drill into category |
|
|
161
|
+
| <Kbd>Escape</Kbd> | Close popover |
|
|
162
|
+
| <Kbd>Backspace</Kbd> | Go back to categories (when query is empty) |
|
|
163
|
+
|
|
164
|
+
## API Reference
|
|
165
|
+
|
|
166
|
+
| Prop | Type | Default | Description |
|
|
167
|
+
| --- | --- | --- | --- |
|
|
168
|
+
| `char` | `string` | — | Trigger character, e.g. `"@"` or `"/"` (required; unique within the root) |
|
|
169
|
+
| `adapter` | `Unstable_TriggerAdapter` | — | Provides categories, items, and search (required) |
|
|
170
|
+
| `directive` | `{ formatter, onInserted?, chip? }` | — | Enables directive-insert behavior. Mutually exclusive with `action`. |
|
|
171
|
+
| `action` | `{ formatter, onExecute, removeOnExecute?, chip? }` | — | Enables action behavior. Mutually exclusive with `directive`. |
|
|
172
|
+
| `iconMap` | `Record<string, IconComponent>` | — | Maps `item.metadata.icon` / `category.metadata.icon` strings to icons |
|
|
173
|
+
| `fallbackIcon` | `IconComponent` | `SparklesIcon` | Icon used when no `iconMap` entry matches |
|
|
174
|
+
| `backLabel` | `string` | `"Back"` | Back button label |
|
|
175
|
+
| `emptyCategoriesLabel` | `string` | `"No items available"` | Shown when no categories are available |
|
|
176
|
+
| `emptyItemsLabel` | `string` | `"No matching items"` | Shown when no items match |
|
|
177
|
+
|
|
178
|
+
All other props (`className`, etc.) forward to the underlying popover `div`.
|
|
179
|
+
|
|
180
|
+
### `directive` object
|
|
181
|
+
|
|
182
|
+
| Field | Type | Description |
|
|
183
|
+
| --- | --- | --- |
|
|
184
|
+
| `formatter` | `Unstable_DirectiveFormatter` | Serializes the selected item into the directive text written to the composer |
|
|
185
|
+
| `onInserted` | `(item) => void` | Optional callback fired after the directive has been inserted |
|
|
186
|
+
|
|
187
|
+
### `action` object
|
|
188
|
+
|
|
189
|
+
| Field | Type | Description |
|
|
190
|
+
| --- | --- | --- |
|
|
191
|
+
| `formatter` | `Unstable_DirectiveFormatter` | Serializes the selected item into the chip left behind (unused when `removeOnExecute`) |
|
|
192
|
+
| `onExecute` | `(item) => void` | Callback fired when an item is selected |
|
|
193
|
+
| `removeOnExecute` | `boolean` | When `true`, strips the trigger text instead of leaving a chip. Default `false`. |
|
|
194
|
+
|
|
195
|
+
## Related
|
|
196
|
+
|
|
197
|
+
- [Directive Text](/docs/ui/directive-text) — renderer for mention chips in user messages
|
|
198
|
+
- [Mentions guide](/docs/guides/mentions) — `@`-mention architecture and formatter details
|
|
199
|
+
- [Slash Commands guide](/docs/guides/slash-commands) — `/`-command architecture
|