AI 模型對戰(React)
競技場式雙欄並排盲測的 React 元件:兩個匿名回覆同步串流打字,投票 A/B/平手/都不好之後翻牌揭曉模型名與供應商色塊,勝方加冠並讓計分板數字滾動。
這是 WebberUI Pro 元件
上線活動期間免費:註冊或登入後,在上方預覽區按「複製安裝指令」就能直接安裝,不需付費、不用綁信用卡。下方那條指令未登入時會回 401。
把兩個模型對同一題的回覆並排放進「模型 A」「模型 B」兩張匿名卡片,同一個字元計數器同步逐字揭示、游標閃爍;串流結束才啟用底下四顆投票按鈕。投票後兩張卡以 rotateY 翻牌揭曉模型名與供應商色塊,被選的一方加上冠軍徽章並脈動一次,計分板數字逐位滾動;按「下一題」換入下一組題目就自動重新開始。揭曉狀態與計分都支援受控/非受控。
載入預覽⋯
npx shadcn@latest add "https://webberui.com/r/ai-model-arena.json?t=<安裝 token>"Playground
即時調整 props、程式碼片段同步更新——直接試出你要的樣子再複製。
2
240
<AiModelArena />
安裝
npx shadcn@latest add "https://webberui.com/r/ai-model-arena.json?t=<安裝 token>"或在 components.json 設定 registries 後,改用 @webberui/ai-model-arena 安裝。
使用
import { AiModelArena, type ArenaVote } from "@/components/ui/ai-model-arena";
// 模型名與供應商皆為虛構示範
const round = {
prompt: "用一句話解釋什麼是「複利」。",
left: { model: "Aurora-7", provider: "北極光實驗室", response: "複利就是利息也會生利息……" },
right: { model: "Sage-Coder", provider: "鼠尾草智能", response: "本期利息併入本金,下一期再計息……" },
};
<AiModelArena
prompt={round.prompt}
left={round.left}
right={round.right}
onVote={(choice: ArenaVote) => console.log(choice)} // "left" | "right" | "tie" | "both-bad"
onNext={() => loadNextRound()} // 換入下一組題目與回覆,元件會自動從頭打字
/>計分想自己管(例如平手各加 0.5 分)時,改成受控 scores:
const [scores, setScores] = React.useState<Record<string, number>>({});
<AiModelArena
{...round}
scores={scores}
onVote={(choice) =>
setScores((prev) => {
const next = { ...prev };
if (choice === "left") next[round.left.model] = (next[round.left.model] ?? 0) + 1;
if (choice === "right") next[round.right.model] = (next[round.right.model] ?? 0) + 1;
if (choice === "tie") {
next[round.left.model] = (next[round.left.model] ?? 0) + 0.5;
next[round.right.model] = (next[round.right.model] ?? 0) + 0.5;
}
return next;
})
}
/>Props
| Prop | 型別 | 預設值 | 說明 |
|---|---|---|---|
prompt | string | — | 題目文字,顯示在頂部的氣泡 |
left | ArenaContestant | — | 左欄參賽者(模型名與回覆) |
right | ArenaContestant | — | 右欄參賽者(模型名與回覆) |
streaming | boolean | true | 是否以打字效果逐字揭示回覆;false 或使用者偏好減少動態時直接完整顯示 |
charsPerTick | number | 2 | 每一拍(40ms)揭示的字元數,越大越快 |
onVote | (choice: ArenaVote) => void | — | 投票時回呼 |
scores | Record<string, number> | — | 受控計分:以模型名為 key 的累計分數,計分板只列出這裡有的模型;未提供時元件內部自行累計(投票後兩位參賽者都登記,勝方加 1 分) |
showScores | boolean | true | 是否顯示計分板 |
revealed | boolean | — | 受控:是否已翻牌揭曉;不提供時由投票/下一題自動切換 |
defaultRevealed | boolean | false | 非受控模式的初始揭曉狀態 |
onRevealedChange | (revealed: boolean) => void | — | 揭曉狀態變動時回呼(投票後為 true、按下一題為 false) |
onNext | () => void | — | 按「下一題」時回呼,通常在此換入下一組題目與回覆 |
maxHeight | number | string | 240 | 回覆區最大高度(px 或 CSS 長度),超出時欄內捲動 |
labels | ArenaLabels | — | 介面文案,可覆寫任一項 |
className | string | — | 透傳到最外層容器 |
ArenaContestant
| 欄位 | 型別 | 預設值 | 說明 |
|---|---|---|---|
model | string | — | 模型名稱;投票前隱藏,翻牌後才揭曉 |
provider | string | — | 供應商名稱,翻牌後顯示於模型名下方並用於色塊配色;未提供時以模型名代替 |
hue | number | — | 供應商色塊的色相(0–360);未提供時依供應商名稱確定性產生 |
response | string | — | 回覆全文;streaming 時由 0 逐字揭示 |
ArenaLabels
| 欄位 | 型別 | 預設值 | 說明 |
|---|---|---|---|
leftAnonymous | string | "模型 A" | 左欄匿名徽章文字 |
rightAnonymous | string | "模型 B" | 右欄匿名徽章文字 |
leftBetter | string | "A 較好" | 「左邊較好」按鈕文字 |
rightBetter | string | "B 較好" | 「右邊較好」按鈕文字 |
tie | string | "平手" | 「平手」按鈕與徽章文字 |
bothBad | string | "都不好" | 「都不好」按鈕與徽章文字 |
next | string | "下一題" | 「下一題」按鈕文字 |
winner | string | "勝出" | 勝出徽章文字 |
scoreboard | string | "計分板" | 計分板標題 |
noScores | string | "投票後開始計分" | 計分板尚無任何分數時的提示文字 |
prompt | string | "題目" | 題目氣泡的小標 |
另外具名匯出 ArenaVote 型別("left" \| "right" \| "tie" \| "both-bad")與 providerHue(name)——依供應商名稱產生 0–360 的確定性色相,想在別處畫同一組供應商色塊時可重複使用。
細節
- 同步串流:兩欄共用同一個字元計數器,每 40ms 前進
charsPerTick個字,短的那側先打完就停下游標、長的那側繼續;兩側都完成才啟用投票列。 - 翻牌才揭曉:卡片背面的模型名與供應商色塊只在揭曉後才掛載——投票前 DOM 裡不存在模型名,搜尋、Ctrl+F 或輔助科技都提前讀不到。
- 計分規則:非受控時投完票才把兩位參賽者登記進計分板(輸家 0 分),「A 較好」「B 較好」為對應模型加 1 分,平手與都不好不加分;要用不同規則(例如平手各加 0.5)就改傳受控
scores並在onVote裡自己加。 - 計分板不洩題:計分板只列出
scores裡有的模型,本回合兩位參賽者要等投票揭曉後才出現——投票前計分板不會透露這一對是誰;受控scores若預先列出所有模型,就由你決定要不要保留這層匿名。 - 換題不用重掛:只要
prompt或任一response變了就視為新回合,自動從頭打字並清掉上一票;請不要對元件用key重掛,那會連內部計分一起清空。 - 供應商色塊:
provider名稱經確定性 hash 得到色相(SSR 與客戶端一致),同一供應商永遠同色;也可用hue自訂。
可及性
- 根容器
role="group"並以aria-labelledby指向題目文字;兩張卡片未揭曉時背面aria-hidden,揭曉後正面aria-hidden,螢幕閱讀器只會讀到當前那一面 - 四顆投票鍵與「下一題」都是原生
button(type="button"),Tab/Enter/Space 可操作,focus-visible有明顯焦點環;串流未完成時投票鍵disabled,投票後被選的那顆以aria-pressed標示,焦點自動移到「下一題」,鍵盤使用者不會掉到 body aria-live="polite"區域會播報「回覆完成」與揭曉結果(例如「模型 A:Aurora-7,模型 B:Sage-Coder。A 較好」);計分板數字另有sr-only的實際數值,滾動的視覺數字為aria-hidden- 使用者系統開啟「減少動態效果」時:不逐字打字、直接完整顯示並立即啟用投票;翻牌改為兩面淡入淡出,游標閃爍、勝方脈動、數字滾動與名次平移全部關閉