WebberUI

AI 對話分支切換(React)

同一則 AI 回覆多版本切換的 React 元件:‹ 2/3 › 分頁器切換時內容依方向左右滑動、重新生成即開新分支並顯示打字骨架、氣泡上方可選迷你分支樹、焦點在氣泡內按 ←/→ 換版本,含複製與模型/時間小字。

把同一則回覆的多個版本收在一顆助理氣泡裡:底部工具列是「‹ 2/3 ›」分頁器、重新生成與複製按鈕,右側是模型與時間小字。切換時內容用 AnimatePresence 依方向滑動——往後翻新版本從右邊進、舊的往左退,往回翻則相反;按重新生成會呼叫 onRegenerate,你在串流期間把 isGenerating 設為 true,內容區就換成打字骨架、分頁器與分支樹末端預留一格,新版本推進 versions 後從右側滑進來。開啟 showTree 會在氣泡上方畫一條主線加每個版本一個小圓點,目前版本高亮並可點擊切換;焦點在氣泡內時 ←/→(與 Home/End)也能換版本。索引支援受控/非受控。

載入預覽⋯
npx shadcn@latest add https://webberui.com/r/ai-branch-switcher.json

Playground

即時調整 props、程式碼片段同步更新——直接試出你要的樣子再複製。

<AiBranchSwitcher />

安裝

npx shadcn@latest add https://webberui.com/r/ai-branch-switcher.json

或在 components.json 設定 registries 後,改用 @webberui/ai-branch-switcher 安裝。

使用

import { AiBranchSwitcher, type BranchVersion } from "@/components/ui/ai-branch-switcher";

// 內容與模型名皆為虛構示範
const [versions, setVersions] = React.useState<BranchVersion[]>([
  { id: "v1", model: "Aurora-7", createdAt: "09:12", content: "【專業版】……" },
  { id: "v2", model: "Aurora-7", createdAt: "09:13", content: "【輕鬆版】……" },
]);
const [index, setIndex] = React.useState(1);
const [generating, setGenerating] = React.useState(false);

<AiBranchSwitcher
  versions={versions}
  index={index}
  onIndexChange={setIndex}
  isGenerating={generating}
  showTree
  onRegenerate={async () => {
    setGenerating(true);
    const content = await regenerate(); // 你的串流/請求
    // 同一批更新:推入新版本、索引跳到最後、結束產生中
    setVersions((prev) => [...prev, { id: crypto.randomUUID(), content, model: "Aurora-7" }]);
    setIndex(versions.length);
    setGenerating(false);
  }}
/>

Props

Prop型別預設值說明
versionsBranchVersion[]同一則回覆的所有版本(分支),依產生順序排列
indexnumber受控目前索引(0 起算);提供時由外部掌控
defaultIndexnumber0非受控初始索引(0 起算)
onIndexChange(index: number) => void索引變動時回呼(分頁器、分支樹、鍵盤,以及非受控自動跟到最新版本時都會觸發)
onRegenerate() => void點擊「重新生成」時回呼;未提供時不顯示重新生成按鈕
isGeneratingbooleanfalse是否正在產生尚未加入 versions 的新分支:內容區改顯示打字骨架、分頁器與分支樹末端預留一格、工具列停用
showTreebooleanfalse是否在氣泡上方顯示迷你分支樹(一條主線+每個版本一個小圓點,可點擊切換)
compactbooleanfalse緊湊模式:縮小內距、字級與工具列
renderContent(version: BranchVersion) => React.ReactNode自訂內容渲染(例如接 Markdown);未提供時把純文字依空行切成段落
regenerateLabelstring「重新生成」按鈕文字;等同 labels.regenerate 的捷徑,兩者同時提供時以此為準
labelsPartial<AiBranchSwitcherLabels>介面文案,可覆寫任一項
classNamestring透傳到最外層容器

BranchVersion

欄位型別預設值說明
idstring唯一識別碼,作為 React key 與滑動切換動畫的邊界
contentstring回覆內容;預設以段落渲染純文字(空行分段),可用 renderContent 換成自訂渲染
createdAtstring顯示用的產生時間文字(例如「14:32」);元件不做時區換算,原樣顯示
modelstring產生此版本的模型名稱,顯示在工具列右側小字

AiBranchSwitcherLabels

欄位型別預設值說明
regeneratestring"重新生成"「重新生成」按鈕文字
previousstring"上一個版本"「上一個版本」按鈕的無障礙標籤
nextstring"下一個版本"「下一個版本」按鈕的無障礙標籤
copystring"複製這個版本"複製按鈕的無障礙標籤
copiedstring"已複製"複製成功後短暫顯示的文字
generatingstring"正在產生新的版本"生成中的提示文字(骨架下方與 aria-live 宣告)
treestring"回覆版本分支"分支樹區域的無障礙標籤
emptystring"尚無回覆版本"沒有任何版本時顯示的文字
position(current: number, total: number) => string第 ${current} 個版本,共 ${total} 個分頁器與 aria-live 宣告的位置文字

另外具名匯出 clampBranchIndex(index, count)(把索引夾在合法範圍)與 renderPlainParagraphs(content)(預設的純文字分段渲染),想在自訂 renderContent 裡沿用預設段落樣式時可直接呼叫。

細節

  • 方向感:切換方向在 render 期間依「上一次的索引」推導,受控、非受控與外部直接改 index 都正確;新版本從右進、舊的往左出,往回翻則相反。開始產生新分支時一律視為往前,避免上一次往回翻時骨架與舊內容同側進出撞在一起。
  • 重新生成的資料流:元件本身不打 API——按鈕只呼叫 onRegenerate。你在請求期間把 isGenerating 設為 true(內容區換成骨架、分頁器顯示「4/4」預留格),拿到結果後同一批推入 versions、把 index 指到最後、isGenerating 設回 false,新版本就會從右側滑進來。
  • 非受控自動跟隨:沒有傳 index 時,versions 變多會自動切到最新版本並觸發 onIndexChange;受控模式交由外部決定要不要跳。
  • 內容高度:切換用 AnimatePresencepopLayout,新舊版本同時在場、退場的一份被 overflow-hidden 裁掉;氣泡高度隨新版本內容直接更新,不做高度動畫,長短差很多的版本也不會拖慢切換。
  • 時間文字createdAt 是顯示用字串,元件不解析、不做時區換算——由你決定要顯示「14:32」還是「昨天 09:10」,SSR 與客戶端輸出一致。
  • 複製:使用 navigator.clipboard.writeText,非 HTTPS 或使用者拒絕權限時靜默失敗(內容仍可手動選取);成功後圖示切成勾勾 1.6 秒。

可及性

  • 氣泡為 role="group"aria-roledescription="回覆版本"aria-label 為目前位置(「第 2 個版本,共 3 個」),整顆可聚焦(tabIndex=0)並有 focus-visible ring;焦點在氣泡內時 ←/→ 切換上一個/下一個版本,Home/End 跳到最前/最後
  • 分頁器兩顆按鈕都是原生 buttontype="button")並帶 aria-label(可用 labels.previouslabels.next 換語言),在邊界時 disabled;分頁器群組本身也帶位置文字,視覺上的「2/3」對輔助科技隱藏以免重複朗讀
  • 分支樹的每個小圓點是 buttonaria-label 為該版本的位置文字,目前版本加 aria-current="true";產生中時整組停用
  • 另有 role="status"aria-live="polite" 的隱藏區域,切換版本時朗讀新位置、產生新分支時朗讀 labels.generating;產生中氣泡加 aria-busy
  • 使用者系統開啟「減少動態效果」時,停用左右滑動、模糊、數字滾動、分支樹高亮環的位移與骨架閃爍,切換只保留淡入淡出

本頁目錄