mirror of
https://github.com/multica-ai/multica.git
synced 2026-08-03 11:10:23 +02:00
refactor(views): transcript toolbar uses the shared Button component + labels fix
Toolbar controls were hand-rolled <button>s, so they never got the project's built-in aria-expanded active state — opening a menu left the trigger looking idle. Route every control through the shared Button: - Density / filter / ⓘ / close → Button variant="ghost" via the trigger render prop, so an open menu shows the muted active background for free. - Active filter → Button variant="brand" (the design-system ON state), replacing the ad-hoc blue classes. - Sort → ghost Button on the same chassis (was a segmented tab strip). - Copy → ghost Button. Wording: - Sort reads "Oldest first" / "Newest first" (was the ambiguous "chronological / time order"). - Expand-mode "Smart" → "Focus" — names the result, not a hollow adjective. Start/created/completed timestamps remain in the ⓘ Run-details popover. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -22,6 +22,7 @@ import {
|
||||
} from "lucide-react";
|
||||
import { cn } from "@multica/ui/lib/utils";
|
||||
import { copyText } from "@multica/ui/lib/clipboard";
|
||||
import { Button } from "@multica/ui/components/ui/button";
|
||||
import { Dialog, DialogContent, DialogTitle } from "@multica/ui/components/ui/dialog";
|
||||
import { Popover, PopoverContent, PopoverTrigger } from "@multica/ui/components/ui/popover";
|
||||
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@multica/ui/components/ui/collapsible";
|
||||
@@ -572,9 +573,15 @@ export function AgentTranscriptDialog({
|
||||
{hasRunDetails && (
|
||||
<Popover>
|
||||
<PopoverTrigger
|
||||
aria-label={t(($) => $.transcript.run_info)}
|
||||
title={t(($) => $.transcript.run_info)}
|
||||
className="flex h-7 w-7 items-center justify-center rounded text-muted-foreground transition-colors hover:bg-accent hover:text-foreground"
|
||||
render={
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon-sm"
|
||||
aria-label={t(($) => $.transcript.run_info)}
|
||||
title={t(($) => $.transcript.run_info)}
|
||||
className="text-muted-foreground"
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Info className="h-3.5 w-3.5" />
|
||||
</PopoverTrigger>
|
||||
@@ -623,13 +630,15 @@ export function AgentTranscriptDialog({
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
)}
|
||||
<button
|
||||
type="button"
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon-sm"
|
||||
onClick={() => onOpenChange(false)}
|
||||
className="flex h-7 w-7 items-center justify-center rounded text-muted-foreground hover:text-foreground hover:bg-accent transition-colors"
|
||||
aria-label={t(($) => $.transcript.close)}
|
||||
className="text-muted-foreground"
|
||||
>
|
||||
<X className="h-4 w-4" />
|
||||
</button>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -656,8 +665,14 @@ export function AgentTranscriptDialog({
|
||||
{items.length > 0 && (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger
|
||||
aria-label={t(($) => $.transcript.density_label)}
|
||||
className="flex shrink-0 items-center gap-1 rounded px-2 py-1 text-xs text-muted-foreground transition-colors hover:bg-accent hover:text-foreground"
|
||||
render={
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
aria-label={t(($) => $.transcript.density_label)}
|
||||
className="text-muted-foreground"
|
||||
/>
|
||||
}
|
||||
>
|
||||
<ListCollapse className="h-3 w-3" />
|
||||
<span className="hidden sm:inline">
|
||||
@@ -707,18 +722,19 @@ export function AgentTranscriptDialog({
|
||||
{filterOptions.length > 0 && (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger
|
||||
aria-label={t(($) => $.transcript.filter)}
|
||||
className={cn(
|
||||
"flex shrink-0 items-center gap-1 rounded px-2 py-1 text-xs transition-colors",
|
||||
activeFilterKeys.length > 0
|
||||
? "text-blue-600 dark:text-blue-400 bg-blue-500/10 hover:bg-blue-500/20"
|
||||
: "text-muted-foreground hover:text-foreground hover:bg-accent",
|
||||
)}
|
||||
render={
|
||||
<Button
|
||||
variant={activeFilterKeys.length > 0 ? "brand" : "ghost"}
|
||||
size="sm"
|
||||
aria-label={t(($) => $.transcript.filter)}
|
||||
className={activeFilterKeys.length > 0 ? undefined : "text-muted-foreground"}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Filter className="h-3 w-3" />
|
||||
<span className="hidden sm:inline">{t(($) => $.transcript.filter)}</span>
|
||||
{activeFilterKeys.length > 0 && (
|
||||
<span className="ml-0.5 rounded-full bg-blue-500/20 px-1.5 py-0 text-[10px] font-medium">
|
||||
<span className="ml-0.5 rounded-full bg-brand-foreground/20 px-1.5 py-0 text-[10px] font-medium tabular-nums">
|
||||
{activeFilterKeys.length}
|
||||
</span>
|
||||
)}
|
||||
@@ -751,15 +767,16 @@ export function AgentTranscriptDialog({
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
)}
|
||||
<button
|
||||
type="button"
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={handleCopyAll}
|
||||
aria-label={copyTranscriptLabel}
|
||||
className="flex shrink-0 items-center gap-1 rounded px-2 py-1 text-xs text-muted-foreground hover:text-foreground hover:bg-accent transition-colors"
|
||||
className="text-muted-foreground"
|
||||
>
|
||||
{copied ? <Check className="h-3 w-3" /> : <Copy className="h-3 w-3" />}
|
||||
<span className="hidden sm:inline">{copyTranscriptLabel}</span>
|
||||
</button>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -843,12 +860,13 @@ interface SortDirectionToggleProps {
|
||||
function SortDirectionToggle({ value, onChange, labels }: SortDirectionToggleProps) {
|
||||
const isChronological = value === "chronological";
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => onChange(isChronological ? "newest_first" : "chronological")}
|
||||
aria-label={labels.ariaLabel}
|
||||
title={isChronological ? labels.chronological : labels.newestFirst}
|
||||
className="flex shrink-0 items-center gap-1 rounded px-2 py-1 text-xs text-muted-foreground transition-colors hover:bg-accent hover:text-foreground"
|
||||
title={labels.ariaLabel}
|
||||
className="text-muted-foreground"
|
||||
>
|
||||
{isChronological ? (
|
||||
<ArrowDownNarrowWide className="h-3 w-3" />
|
||||
@@ -858,7 +876,7 @@ function SortDirectionToggle({ value, onChange, labels }: SortDirectionTogglePro
|
||||
<span className="hidden sm:inline">
|
||||
{isChronological ? labels.chronological : labels.newestFirst}
|
||||
</span>
|
||||
</button>
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -781,8 +781,8 @@
|
||||
"sort_newest_first": "Newest first",
|
||||
"preserve_filters": "Preserve filters",
|
||||
"density_label": "Expand mode",
|
||||
"density_smart": "Smart",
|
||||
"density_smart_desc": "Replies and errors read in place; thinking and tool steps stay folded",
|
||||
"density_smart": "Focus",
|
||||
"density_smart_desc": "Replies and errors open; thinking and tool steps stay folded",
|
||||
"density_expanded": "Expand all",
|
||||
"density_expanded_desc": "Every row's full params and output, for step-by-step review",
|
||||
"density_collapsed": "Collapse all",
|
||||
@@ -809,7 +809,8 @@
|
||||
"details_started": "Started",
|
||||
"details_completed": "Completed",
|
||||
"copy_workdir": "Copy working directory",
|
||||
"details_triggered_by": "Triggered by"
|
||||
"details_triggered_by": "Triggered by",
|
||||
"close": "Close"
|
||||
},
|
||||
"task_failure": {
|
||||
"agent_error": "Agent execution error",
|
||||
|
||||
@@ -661,8 +661,8 @@
|
||||
"sort_newest_first": "新しい順",
|
||||
"preserve_filters": "フィルターを保持",
|
||||
"density_label": "展開モード",
|
||||
"density_smart": "スマート",
|
||||
"density_smart_desc": "回答とエラーはそのまま読め、思考とツールの過程は折りたたまれます",
|
||||
"density_smart": "重点展開",
|
||||
"density_smart_desc": "回答とエラーは展開、思考とツールの過程は折りたたみ",
|
||||
"density_expanded": "すべて展開",
|
||||
"density_expanded_desc": "各行のパラメータと出力を完全に展開し、逐次レビューに",
|
||||
"density_collapsed": "すべて折りたたむ",
|
||||
@@ -689,7 +689,8 @@
|
||||
"details_started": "開始",
|
||||
"details_completed": "完了",
|
||||
"copy_workdir": "作業ディレクトリをコピー",
|
||||
"details_triggered_by": "トリガー"
|
||||
"details_triggered_by": "トリガー",
|
||||
"close": "閉じる"
|
||||
},
|
||||
"task_failure": {
|
||||
"agent_error": "エージェント実行エラー",
|
||||
|
||||
@@ -669,8 +669,8 @@
|
||||
"sort_newest_first": "최신 순",
|
||||
"preserve_filters": "필터 유지",
|
||||
"density_label": "펼치기 방식",
|
||||
"density_smart": "스마트",
|
||||
"density_smart_desc": "응답과 오류는 바로 읽을 수 있고, 사고와 도구 과정은 접힌 상태로 유지됩니다",
|
||||
"density_smart": "핵심 펼치기",
|
||||
"density_smart_desc": "응답과 오류는 펼치고, 사고와 도구 과정은 접힌 상태 유지",
|
||||
"density_expanded": "모두 펼치기",
|
||||
"density_expanded_desc": "각 행의 전체 매개변수와 출력을 펼쳐 단계별 검토에 사용",
|
||||
"density_collapsed": "모두 접기",
|
||||
@@ -697,7 +697,8 @@
|
||||
"details_started": "시작",
|
||||
"details_completed": "완료",
|
||||
"copy_workdir": "작업 디렉터리 복사",
|
||||
"details_triggered_by": "트리거한 사람"
|
||||
"details_triggered_by": "트리거한 사람",
|
||||
"close": "닫기"
|
||||
},
|
||||
"task_failure": {
|
||||
"agent_error": "에이전트 실행 오류",
|
||||
|
||||
@@ -761,12 +761,12 @@
|
||||
"antigravity_live_unavailable": "Antigravity 暂不提供实时执行事件。task 完成后即可查看执行记录。",
|
||||
"no_data": "未记录执行数据。",
|
||||
"sort_label": "排序",
|
||||
"sort_chronological": "时间顺序",
|
||||
"sort_chronological": "最早在前",
|
||||
"sort_newest_first": "最新在前",
|
||||
"preserve_filters": "保留筛选",
|
||||
"density_label": "展开方式",
|
||||
"density_smart": "智能",
|
||||
"density_smart_desc": "汇报与错误直接可读,思考和工具过程保持折叠",
|
||||
"density_smart": "重点展开",
|
||||
"density_smart_desc": "汇报与错误展开,思考和工具过程保持折叠",
|
||||
"density_expanded": "全部展开",
|
||||
"density_expanded_desc": "展开每一行的完整参数与输出,逐条审阅用",
|
||||
"density_collapsed": "全部收起",
|
||||
@@ -793,7 +793,8 @@
|
||||
"details_started": "开始",
|
||||
"details_completed": "完成",
|
||||
"copy_workdir": "复制工作目录",
|
||||
"details_triggered_by": "触发人"
|
||||
"details_triggered_by": "触发人",
|
||||
"close": "关闭"
|
||||
},
|
||||
"task_failure": {
|
||||
"agent_error": "智能体执行出错",
|
||||
|
||||
Reference in New Issue
Block a user