assistant-ui ActionBar & BranchPicker 原语
ActionBar 消息操作栏(复制、重试、编辑、反馈、导出)与 BranchPicker 多分支回复导航原语。
#type / snippet
#status / evergreen
#tech / dev / frontend
#tech / ai
[!info] related notes
- 概览: assistant-ui 概览
- Thread & Message: Thread & Message 原语
- Composer: Composer 原语
assistant-ui ActionBar & BranchPicker 原语
ActionBar 提供消息级操作按钮,BranchPicker 提供多分支回复导航。两者配合实现 ChatGPT 式的消息交互体验。
ActionBar 子组件
| 子组件 | 默认元素 | 职责 |
|---|---|---|
Root | <div> | 操作栏容器,管理自动隐藏 |
Copy | <button> | 复制消息文本到剪贴板 |
Reload | <button> | 重新生成助手回复 |
Edit | <button> | 进入消息编辑模式 |
Speak | <button> | 语音播放(需 SpeechSynthesisAdapter) |
StopSpeaking | <button> | 停止语音播放 |
FeedbackPositive | <button> | 👍 正面反馈 |
FeedbackNegative | <button> | 👎 负面反馈 |
ExportMarkdown | <button> | 导出消息为 Markdown |
基础用法
// components/AssistantActionBar.tsx
import { ActionBarPrimitive } from "@assistant-ui/react";
import { Copy, RefreshCw, ThumbsUp, ThumbsDown } from "lucide-react";
function AssistantActionBar() {
return (
<ActionBarPrimitive.Root
hideWhenRunning // 生成中隐藏
autohide="not-last" // 仅最新消息显示
autohideFloat="single-branch" // 隐藏时用 data-floating 代替移除
className="flex gap-1 opacity-0 group-hover:opacity-100 transition-opacity"
>
<ActionBarPrimitive.Copy
copiedDuration={3000}
className="rounded-md p-1 hover:bg-muted"
>
<Copy size={16} />
</ActionBarPrimitive.Copy>
<ActionBarPrimitive.Reload className="rounded-md p-1 hover:bg-muted">
<RefreshCw size={16} />
</ActionBarPrimitive.Reload>
<ActionBarPrimitive.FeedbackPositive className="rounded-md p-1 hover:bg-muted">
<ThumbsUp size={16} />
</ActionBarPrimitive.FeedbackPositive>
<ActionBarPrimitive.FeedbackNegative className="rounded-md p-1 hover:bg-muted">
<ThumbsDown size={16} />
</ActionBarPrimitive.FeedbackNegative>
</ActionBarPrimitive.Root>
);
}
自动隐藏模式
autohide | 行为 |
|---|---|
"never" (默认) | 始终显示 |
"not-last" | 非最新消息隐藏,hover 显示 |
"always" | 所有消息隐藏,hover 显示 |
autohideFloat | 行为 |
|---|---|
"never" (默认) | 隐藏时从 DOM 移除 |
"always" | 隐藏时添加 data-floating 属性 |
"single-branch" | 单分支时添加 data-floating |
// data-floating 可用于 CSS 浮动动画
ActionBarPrimitive.Root[data-floating] {
position: absolute;
bottom: 0;
opacity: 0;
transition: opacity 0.2s;
}
复制状态反馈
// 纯 CSS 实现复制成功图标切换
<ActionBarPrimitive.Copy copiedDuration={3000}>
<span className="group">
<CopyIcon className="group-data-[copied]:hidden" />
<CheckIcon className="hidden group-data-[copied]:block" />
</span>
</ActionBarPrimitive.Copy>
data-copied 属性在点击后持续 3000ms,无需 JS state。
导出 Markdown
// 自定义导出处理
<ActionBarPrimitive.ExportMarkdown
filename="chat-export.md"
onExport={async (content) => {
await navigator.clipboard.writeText(content);
toast.success("已复制到剪贴板");
}}
>
<DownloadIcon size={16} />
</ActionBarPrimitive.ExportMarkdown>
用户消息操作栏
// components/UserActionBar.tsx
function UserActionBar() {
return (
<ActionBarPrimitive.Root
hideWhenRunning
autohide="not-last"
className="flex gap-1 opacity-0 group-hover:opacity-100"
>
<ActionBarPrimitive.Edit className="rounded-md p-1 hover:bg-muted">
<Pencil size={16} />
</ActionBarPrimitive.Edit>
</ActionBarPrimitive.Root>
);
}
BranchPicker 子组件
| 子组件 | 默认元素 | 职责 |
|---|---|---|
Root | <div> | 容器,hideWhenSingleBranch 控制单分支隐藏 |
Previous | <button> | 切换到上一个分支 |
Next | <button> | 切换到下一个分支 |
Number | 纯文本 | 当前分支索引 |
Count | 纯文本 | 总分支数 |
BranchPicker 用法
// components/BranchPicker.tsx
import { BranchPickerPrimitive } from "@assistant-ui/react";
import { ChevronLeft, ChevronRight } from "lucide-react";
function BranchPicker() {
return (
<BranchPickerPrimitive.Root
hideWhenSingleBranch // 仅一个分支时隐藏
className="flex items-center gap-1 text-sm text-muted-foreground"
>
<BranchPickerPrimitive.Previous className="rounded-md p-0.5 hover:bg-muted disabled:opacity-30">
<ChevronLeft size={16} />
</BranchPickerPrimitive.Previous>
<BranchPickerPrimitive.Number />
<span>/</span>
<BranchPickerPrimitive.Count />
<BranchPickerPrimitive.Next className="rounded-md p-0.5 hover:bg-muted disabled:opacity-30">
<ChevronRight size={16} />
</BranchPickerPrimitive.Next>
</BranchPickerPrimitive.Root>
);
}
组合布局
// 消息底部:BranchPicker + ActionBar 并排
<MessagePrimitive.Root className="group relative">
<MessagePrimitive.Parts>
{({ part }) => <PartRenderer part={part} />}
</MessagePrimitive.Parts>
{/* 消息底部操作区 */}
<div className="flex items-center justify-between mt-1">
<BranchPicker />
<AssistantActionBar />
</div>
</MessagePrimitive.Root>
优雅之处:
autohide="not-last"+autohideFloat实现了 ChatGPT 式的渐显操作栏,CSS 动画替代 JS 状态管理data-copied纯 CSS 图标切换,零 JS 开销hideWhenSingleBranch让单回复消息不显示多余的导航控件- BranchPicker 边界自动禁用(首分支禁 Previous,末分支禁 Next)