"use client"; /** * StatusView — 狀態頁(AF3 實作) * * 對齊 spec §4: * - StatusHero(大狀態指示器,5 變體) * - InfoCard(帳號 / Relay / 連線開始 / Session Token 遮蔽版) * - 主按鈕列(依狀態五套) * - RecentLog(最近 10 筆事件) * - 未配對時顯示 EmptyState + 「前往配對」CTA * * 資料來源(AF3 階段 mock;AF6 會換成真 Wails binding): * - useConnectionStatus() → snapshot * - useRecentLogs(10) → logs * * 與配對 Tab 的互動: * - 「前往配對」按鈕透過 Radix Tabs 的 `TabsContext` 無法直接 setValue(因為我們用 defaultValue), * 改為 dispatch CustomEvent `agent:switch-tab` 由 page.tsx 攔截切換。 * 這一層間接呼叫雖然比 prop-drilling 多一步,但解耦了 view 與 app 結構, * 未來 StatusView 被放到其他容器也不會壞。 */ import { Unlink } from "lucide-react"; import { toast } from "sonner"; import { InfoCard } from "@/components/agent/info-card"; import { RecentLog } from "@/components/agent/recent-log"; import { StatusHero } from "@/components/agent/status-hero"; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger, } from "@/components/ui/alert-dialog"; import { Button } from "@/components/ui/button"; import { Card, CardContent } from "@/components/ui/card"; import { EmptyState } from "@/components/ui/empty-state"; import { Spinner } from "@/components/ui/spinner"; import { useConnectionStatus } from "@/hooks/use-connection-status"; import { useRecentLogs } from "@/hooks/use-recent-logs"; import { agentAPI } from "@/lib/agent-api"; import { useT } from "@/lib/i18n/context"; /** 觸發切換到配對 tab 的自訂事件名(page.tsx 監聽)。 */ export const AGENT_SWITCH_TAB_EVENT = "agent:switch-tab"; /** 發送 CustomEvent 切換 tab(export 給其他 view 共用,避免重複定義 helper)。 */ export function switchAgentTab(value: "status" | "pair" | "settings") { if (typeof window === "undefined") return; window.dispatchEvent( new CustomEvent<{ value: string }>(AGENT_SWITCH_TAB_EVENT, { detail: { value }, }), ); } /** 內部別名(保留舊名以利 view 內部讀起來語意清楚)。 */ const switchTab = switchAgentTab; export function StatusView() { const t = useT(); const { snapshot, loading } = useConnectionStatus(); const { logs } = useRecentLogs(10); /* ---------- Loading(初次拉 snapshot)---------- */ if (loading || !snapshot) { return (

{t("nav.status")}

); } /* ---------- 空狀態(尚未配對,spec §4.3)---------- */ if (snapshot.state === "notPaired") { return (

{t("nav.status")}

switchTab("pair"), }} />
); } /* ---------- 正常狀態(online / offline / reconnecting / error)---------- */ return (

{t("nav.status")}

); } /* -------------------------------------------------------------------------- */ /* ActionButtons — 主按鈕列(依狀態 5 套) */ /* -------------------------------------------------------------------------- */ interface ActionButtonsProps { state: "online" | "offline" | "connecting" | "reconnecting" | "error" | "notPaired"; } function ActionButtons({ state }: ActionButtonsProps) { const t = useT(); // online 與 offline / reconnecting 的主按鈕不同,用 switch 明確分岔 switch (state) { case "online": return (
); case "offline": return (
); case "reconnecting": case "connecting": return (
); case "error": return (
); default: // notPaired 不會到這裡(外層 early-return 了),保留 fallback 讓 switch 完整 return null; } } /** * 「重新配對」次按鈕(spec §4.2 C / Fix-F1)。 * * 顯示時機:error / offline。其他狀態下 ActionButtons 不會 render 這顆。 * * 行為:點擊後切到配對 tab(不直接呼叫 unpair;spec §4.2 C 把 unpair 的破壞性 * 動作留在配對流程裡顯式發生,避免使用者誤點次按鈕就清掉 Session)。 * * 樣式:variant="outline"(次按鈕,不跟主按鈕搶眼)。 */ function RepairButton() { const t = useT(); return ( ); } /** Disconnect 按鈕 — 透過 AlertDialog 確認。 */ function DisconnectButton() { const t = useT(); return ( {t("status.confirm.disconnect.title")} {t("status.confirm.disconnect.description")} {t("common.cancel")} { // AF6:呼叫 Wails Disconnect binding // 成功:connection:status event 會推 "offline" 更新 UI;失敗:toast agentAPI.disconnect().catch((err) => { toast.error( err instanceof Error ? err.message : String(err), ); }); }} > {t("status.action.disconnect")} ); }