React 集成
@flue/react 将 Flue 的持久对话流转换为实时 React 状态。useFlueAgent() 观察一个智能体对话并向其中发送消息。HTTP 请求、认证和流传输保留在 @flue/sdk 中。
设置 React
pnpm add @flue/react @flue/sdk
不需要提供商或应用级设置。钩子通过 URL 指定一个对话:你的应用 app.ts 挂载智能体路由的位置(app.route('/agents/support-assistant', createAgentRouter(SupportAssistant)))加上调用者选择的对话 id。开始新对话就是在挂载 URL 后附加一个新 id 来渲染钩子。
智能体必须在 app.ts 中挂载才能让浏览器访问它,你在该挂载点附加的中间件决定谁可以访问哪个对话。参见路由了解挂载和保护智能体路由,包括跨域应用。
构建智能体对话
钩子从持久事件重建对话的转录,然后跟踪新事件:
import { useFlueAgent } from '@flue/react';
import { useState } from 'react';
export function Chat({ conversationId }: { conversationId: string }) {
const [input, setInput] = useState('');
const agent = useFlueAgent({
url: `/api/agents/support-assistant/${conversationId}`,
});
async function submit(event: React.FormEvent) {
event.preventDefault();
const message = input.trim();
if (!message) return;
setInput('');
await agent.sendMessage(message);
}
return (
<section>
<div aria-live="polite">
{agent.messages.map((message) => (
<article key={message.id}>
<strong>{message.role}</strong>
{message.parts.map((part) =>
part.type === 'text' ? <p key={part.text}>{part.text}</p> : null,
)}
</article>
))}
</div>
<form onSubmit={submit}>
<input value={input} onChange={(event) => setInput(event.target.value)} />
<button disabled={!input.trim()} type="submit">
Send
</button>
</form>
</section>
);
}这里的 url 假设 app.ts 将智能体挂载在 /api/agents/support-assistant;使用你的路由映射选择的任何 URL。相对 URL 根据浏览器源解析。
sendMessage() 立即添加用户消息,并在服务器接纳提示时解析,而不是在生成完成时。然后流在不改变其转录位置的情况下将该乐观消息与其持久副本协调。使用 status 区分连接、提交、流式和错误状态。historyReady 在请求的持久历史作为一个连贯快照加载后变为 true;在后续的实时重连中保持 true。
sendMessage(message, options) accepts every SDK send control (idempotencyKey, initialData, uid, signal, …) plus two attachment conveniences folded into the delivered message’s attachments: images ({ type: 'image', data, mimeType, filename? }) and documents ({ type: 'document', data, mimeType: 'application/pdf', filename? }), with data base64. Documents reach the model as native document content on Anthropic, Google, and OpenAI Responses models; see DeliveredAttachment for details.
const data = await fileToBase64(file); // your helper: File → base64 string
await agent.sendMessage('Summarize this quote.', {
documents: [{ type: 'document', data, mimeType: 'application/pdf', filename: file.name }],
});
消息是 Flue 拥有的 FlueConversationMessage 值,具有基于部分的形状:text、reasoning、dynamic-tool 和 file。经过验证的结构化工具输出保存在 dynamic-tool 部分的 output 上,因此应用程序可以渲染自定义工具界面而无需单独的数据事件通道。这些是 Flue 自己的类型——它们不是 AI SDK 类型,@flue/react 在运行时不依赖 ai 也不实现其传输协议。持久的 file 部分携带可直接使用的附件字节 url(通过智能体路由器的附件端点提供);乐观上传携带本地 data: 预览。根据 part.mediaType 分支渲染——image/* 用 <img>,application/pdf 用链接。
钩子使用 Flue 智能体 SDK 的具体化 observe() 层:它加载完整的规范快照,按持久顺序原子发布它,并从该精确检查点继续通过重连和规范重置。消费者不需要协调快照和实时更新或排序 messages。实时更新默认使用 SSE,回退到 Durable Streams 长轮询(live: 'long-poll' 显式选择它)。对于没有实时更新的单次时间点读取,直接使用 SDK 客户端的 history()。流式期间的部分文本和推理是尽力而为的;完成的规范助手消息是权威的。
要观察可能在挂载后带外创建的对话——通过服务器端唤醒、队列 worker 或 webhook——调用 refresh() 重新运行历史追赶并恢复实时更新。何时重新检查是应用的责任。
认证和自定义客户端
对于自定义 headers、bearer token 或自定义 fetch 行为,自己创建对话客户端并传递给钩子。记忆化它——新的客户端实例会替换会话:
import { useFlueAgent } from '@flue/react';
import { createFlueClient } from '@flue/sdk';
import { useMemo } from 'react';
function Chat({ conversationId, token }: { conversationId: string; token: string }) {
const client = useMemo(
() =>
createFlueClient({
url: `/api/agents/support-assistant/${conversationId}`,
token,
}),
[conversationId, token],
);
const agent = useFlueAgent({ client });
// ...
}
钩子不接管你传入的客户端的所有权,因此同一个实例在渲染对话旁边服务于编程需求:使用 createFlueClient 创建一次,将其交给 useFlueAgent({ client }),当应用代码需要等待特定提交的结算或在 React 树外部提取回复时,在同一个实例上调用 observe()、wait() 或 read()。共享一个客户端保持单一的连接集和一个认证配置,而不是按消费者复制。
渲染和延迟标识
在服务器渲染期间,钩子返回空的、空闲状态且不打开连接。相对 url(如 /api/...)根据浏览器源解析,因此服务器渲染保持休眠,钩子在水合后连接。省略的 url(和 client)使钩子保持休眠,直到路由或应用数据解析对话标识。
API 参考
参见 @flue/react 包 README 了解完整的选项、结果类型、状态和消息部分类型。完整的可运行聊天 UI 在 examples/react-chat 中提供。