assistant-ui ActionBar & BranchPicker 原语

ActionBar 消息操作栏(复制、重试、编辑、反馈、导出)与 BranchPicker 多分支回复导航原语。

#type / snippet #status / evergreen #tech / dev / frontend #tech / ai

[!info] related notes

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)

创建于 2026/6/25 更新于 2026/6/25