Files
catonline_ai/vw-agentic-rag/docs/topics/assistant-ui.md
2025-09-26 17:15:54 +08:00

298 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

给你一份可直接落地的前端实现:我已把完整的 React/Next.js 单文件示例(含自定义 Tool UI、样式与 Data Stream 运行时接入)放到右侧画布里。按下面步骤即可跑起来:
```
"use client";
// ------------------------------------------------------------
// assistant-ui × LangGraph(FastAPI) 流式前端 (Data Stream 协议)
// ------------------------------------------------------------
// 说明:
// 1) 该文件可作为 Next.js `app/page.tsx` 或任意 React 页面组件使用。
// 2) 依赖:
// npm i @assistant-ui/react @assistant-ui/react-ui @assistant-ui/react-data-stream \
// @assistant-ui/react-markdown remark-gfm @radix-ui/react-tooltip \
// @radix-ui/react-slot lucide-react class-variance-authority clsx tailwindcss-animate
// 3) 样式:
// - 在 tailwind.config.ts 中加入插件:
// plugins: [
// require("tailwindcss-animate"),
// require("@assistant-ui/react-ui/tailwindcss")({ components: ["thread", "thread-list"], shadcn: true })
// ]
// - 在全局布局文件(如 app/layout.tsx)中引入:
// import "@assistant-ui/react-ui/styles/index.css";
// 4) 运行约定:后端 FastAPI 暴露 POST /api/chat返回基于 Data Stream 协议的 SSE。
// - 响应头需包含:'x-vercel-ai-ui-message-stream': 'v1'
// - 事件类型至少包含start、text-start / text-delta / text-end、
// tool-input-start / tool-input-delta / tool-input-available、
// tool-output-available、start-step、finish-step、finish、[DONE]
// - 这些事件来自 LangGraph 的 run/工具事件映射(由后端转成 Data Stream 协议)。
// ------------------------------------------------------------
import React, { useMemo } from "react";
import {
AssistantRuntimeProvider,
makeAssistantToolUI,
} from "@assistant-ui/react";
import { useDataStreamRuntime } from "@assistant-ui/react-data-stream";
import { Thread } from "@assistant-ui/react-ui";
import { Check, Globe, Search, Terminal } from "lucide-react";
// ---------------------------
// 1) 自定义 Tool UI可选
// ---------------------------
// 将 LangGraph 工具事件以特定工具名注册到前端 UI 中,
// toolName 需与后端发送的工具名完全一致。
// Web 搜索工具 UI示例toolName: "web_search"
const WebSearchToolUI = makeAssistantToolUI<{ query: string }, { results: Array<{ title: string; url: string; snippet?: string }>; took_ms?: number }>({
toolName: "web_search",
render: ({ args, result, status }) => {
return (
<div className="rounded-2xl border bg-card text-card-foreground p-3 my-2">
<div className="flex items-center gap-2 text-sm font-medium opacity-80">
<Search className="h-4 w-4" />
<span>Web 搜索</span>
<span className="opacity-60">— {args?.query ?? ""}</span>
</div>
{status.type === "running" && (
<p className="text-sm mt-2 opacity-80">正在搜索…</p>
)}
{status.type === "requires_action" && (
<p className="text-sm mt-2 opacity-80">等待后端确认…</p>
)}
{status.type === "incomplete" && (
<p className="text-sm mt-2 text-destructive">搜索失败</p>
)}
{status.type === "complete" && result && (
<ul className="mt-2 space-y-2">
{result.results?.slice(0, 6).map((r, i) => (
<li key={i} className="text-sm">
<a className="underline underline-offset-4" href={r.url} target="_blank" rel="noreferrer">
{r.title}
</a>
{r.snippet && <p className="opacity-80 mt-1">{r.snippet}</p>}
</li>
))}
{typeof result.took_ms === "number" && (
<li className="text-xs opacity-60">耗时 {result.took_ms}ms</li>
)}
</ul>
)}
</div>
);
},
});
// URL 抓取工具 UI示例toolName: "fetch_url"
const FetchUrlToolUI = makeAssistantToolUI<{ url: string }, { title?: string; content?: string } | { error: string }>({
toolName: "fetch_url",
render: ({ args, result, status }) => {
return (
<div className="rounded-2xl border bg-card text-card-foreground p-3 my-2">
<div className="flex items-center gap-2 text-sm font-medium opacity-80">
<Globe className="h-4 w-4" />
<span>抓取网页</span>
<span className="opacity-60">— {args?.url ?? ""}</span>
</div>
{status.type === "running" && (
<p className="text-sm mt-2 opacity-80">抓取中…</p>
)}
{status.type === "complete" && result && "error" in result && (
<p className="text-sm mt-2 text-destructive">错误:{result.error}</p>
)}
{status.type === "complete" && result && !("error" in result) && (
<div className="mt-2 text-sm space-y-1">
{result.title && <p className="font-medium">{result.title}</p>}
{result.content && (
<p className="opacity-80 line-clamp-4" title={result.content}>
{result.content}
</p>
)}
</div>
)}
</div>
);
},
});
// Python 代码执行 UI示例toolName: "python" 或 "run_python"
const PythonToolUI = makeAssistantToolUI<{ code: string }, { stdout?: string; stderr?: string; elapsed_ms?: number }>({
toolName: "python",
render: ({ args, result, status }) => {
return (
<div className="rounded-2xl border bg-card text-card-foreground p-3 my-2">
<div className="flex items-center gap-2 text-sm font-medium opacity-80">
<Terminal className="h-4 w-4" />
<span>执行 Python</span>
</div>
<pre className="bg-muted/60 rounded-xl p-3 mt-2 text-xs overflow-auto max-h-64">
{args?.code}
</pre>
{status.type === "running" && (
<p className="text-sm mt-2 opacity-80">运行中…</p>
)}
{status.type === "complete" && result && (
<div className="mt-2 text-sm space-y-2">
{result.stdout && (
<div>
<p className="font-medium">stdout</p>
<pre className="bg-muted/60 rounded-xl p-3 mt-1 text-xs overflow-auto max-h-64">{result.stdout}</pre>
</div>
)}
{result.stderr && (
<div>
<p className="font-medium">stderr</p>
<pre className="bg-muted/60 rounded-xl p-3 mt-1 text-xs overflow-auto max-h-64 text-red-600">{result.stderr}</pre>
</div>
)}
{typeof result.elapsed_ms === "number" && (
<div className="flex items-center gap-2 text-xs opacity-60">
<Check className="h-3 w-3" /> 用时 {result.elapsed_ms}ms
</div>
)}
</div>
)}
</div>
);
},
});
// ---------------------------
// 2) Runtime ProviderData Stream 协议SSE
// ---------------------------
// useDataStreamRuntime 会:
// - 在发送消息后,自动通过 EventSource 连接到 /api/chat 的 SSE 流;
// - 解析 Data Stream 协议事件并更新到线程消息;
// - 支持多步/工具调用的可视化(配合上方 Tool UIs
function AssistantProvider({ children }: { children: React.ReactNode }) {
const runtime = useDataStreamRuntime({
api: "/api/chat", // 对应 FastAPI 的 POST /api/chat
// 如果需要自定义 headers/cookies可传入 fetcher:
// fetcher: (input, init) => fetch(input, { ...init, credentials: "include" })
});
return (
<AssistantRuntimeProvider runtime={runtime}>{children}</AssistantRuntimeProvider>
);
}
// ---------------------------
// 3) 主界面Thread预设主题开箱即用
// ---------------------------
// 你也可以改用更细粒度的 primitives 自定义外观;此处采用 @assistant-ui/react-ui 的 Thread 组件。
export default function App() {
// 你可通过 URL 参数或路由传入 threadId 等信息(示例保留默认主线程)。
const header = useMemo(
() => (
<div className="border-b bg-background/60 backdrop-blur supports-[backdrop-filter]:bg-background/60">
<div className="mx-auto max-w-3xl px-4 py-3">
<div className="text-sm opacity-70">LangGraph Agent · FastAPI · Streaming</div>
<h1 className="text-lg font-semibold">assistant-ui × LangGraph(FastAPI) 演示</h1>
</div>
</div>
),
[]
);
return (
<div className="h-screen w-full flex flex-col">
{header}
<div className="mx-auto max-w-3xl w-full grow px-4">
<div className="h-full py-4">
<AssistantProvider>
{/* 注册前端 Tool UI顺序无关可按需增减未注册的工具将 fallback 到纯文本或你自定义的 ToolFallback*/}
<WebSearchToolUI />
<FetchUrlToolUI />
<PythonToolUI />
{/* 线程组件:包含消息视图 + 输入框,默认支持 Markdown、高亮、附件、撤回/编辑、自动滚动等 */}
<Thread className="h-full rounded-2xl border" placeholder="问我任何问题,或让代理调用工具…" />
</AssistantProvider>
</div>
</div>
</div>
);
}
// ------------------------------------------------------------
// 4) 与 FastAPI/LangGraph 的接口契约(仅供参考,非前端代码)
// ------------------------------------------------------------
// FastAPI 需要在 /api/chat 接收形如 { messages: UIMessage[] } 的 JSON并返回 SSE
// Content-Type: text/event-stream
// x-vercel-ai-ui-message-stream: v1
// 核心事件示例(每行以 `data: <json>\n\n` 形式发送;最后 `data: [DONE]\n\n` 终止):
// data: {"type":"start","messageId":"..."}
// data: {"type":"text-start","id":"..."}
// data: {"type":"text-delta","id":"...","delta":"Hello"}
// data: {"type":"text-end","id":"..."}
// data: {"type":"tool-input-start","toolCallId":"...","toolName":"python"}
// data: {"type":"tool-input-delta","toolCallId":"...","inputTextDelta":"print(1)"}
// data: {"type":"tool-input-available","toolCallId":"...","toolName":"python","input":{"code":"print(1)"}}
// data: {"type":"tool-output-available","toolCallId":"...","output":{"stdout":"1\n"}}
// data: {"type":"finish-step"}
// data: {"type":"finish"}
// data: [DONE]
// 这些事件可由 LangGraph 的 streaming 回调/事件(如 on_event/on_tool_start/on_tool_end转换而来。
```
# 怎么用
1. 安装依赖
```bash
npm i @assistant-ui/react @assistant-ui/react-ui @assistant-ui/react-data-stream \
@assistant-ui/react-markdown remark-gfm @radix-ui/react-tooltip \
@radix-ui/react-slot lucide-react class-variance-authority clsx tailwindcss-animate
```
2. 加样式
* `tailwind.config.ts` 添加:
```ts
plugins: [
require("tailwindcss-animate"),
require("@assistant-ui/react-ui/tailwindcss")({ components: ["thread","thread-list"], shadcn: true })
]
```
* 在全局布局(如 `app/layout.tsx`)里加入:
```ts
import "@assistant-ui/react-ui/styles/index.css";
```
3. 放代码
把我提供的文件作为 `app/page.tsx`(或你的任意页面组件)使用即可。
4. 与后端的对接约定FastAPI + LangGraph
* 前端默认向 `POST /api/chat` 发送 `{ messages: UIMessage[] }`,并期望 **SSE** 流式返回,协议为 **Vercel AI SDK 的 Data Stream 协议**:响应头 **必须** 包含 `x-vercel-ai-ui-message-stream: v1`。
* 你的 FastAPI 需要把 LangGraph 的运行/工具事件映射为以下事件并逐条写出SSE `data:` 行,一行一个 JSON末尾 `[DONE]` 终止),例如:
* `start`、`text-start` / `text-delta` / `text-end`
* `tool-input-start` / `tool-input-delta` / `tool-input-available`
* `tool-output-available`
* `start-step` / `finish-step`、`finish`、`[DONE]`
画布代码里我已注册了常见工具 UI`web_search`、`fetch_url`、`python`),多步/工具调用会自动以卡片形式流式显示、并和模型文本交替渲染。
# 关键参考(确认兼容性)
* assistant-ui 官方入门与组件:([assistant-ui.com][1])
* 预置样式包与示例 Thread 用法:([assistant-ui.com][2])
* Data Stream 运行时(前端)与 API([assistant-ui.com][3])
* Data Stream 协议(后端需要按此协议推送 SSE事件清单与 `x-vercel-ai-ui-message-stream: v1` 头要求见 ([AI SDK][4])
* LangGraph/assistant-ui 官方整合说明与示例:([assistant-ui.com][5], [LangChain Blog][6])
如果你愿意,我也可以给你一份最小可用的 FastAPI 端 `/api/chat` 实现(把 LangGraph 的事件转成 Data Stream 协议 SSE直接贴到你服务里就能和前端对上。
[1]: https://www.assistant-ui.com/docs?utm_source=chatgpt.com "Getting Started"
[2]: https://www.assistant-ui.com/docs/legacy/styled/Thread "Thread | assistant-ui"
[3]: https://www.assistant-ui.com/docs/api-reference/integrations/react-data-stream?utm_source=chatgpt.com "assistant-ui/react-data-stream"
[4]: https://ai-sdk.dev/docs/ai-sdk-ui/stream-protocol "AI SDK UI: Stream Protocols"
[5]: https://www.assistant-ui.com/docs/runtimes/langgraph?utm_source=chatgpt.com "Getting Started"
[6]: https://blog.langchain.dev/assistant-ui/?utm_source=chatgpt.com "Build stateful conversational AI agents with LangGraph and ..."