/** * 代理错误分层条:proxy | target | auth(产品/设计 P0 不砍) * 线框:中文 LayerTag 芯片「代理 / 目标 / 鉴权」;unknown 中性灰 */ import { openExternal } from "../openExternal"; import { useWorkspace } from "../workspaceStore"; import { cx } from "./common"; export type ProxyErrorLayer = "proxy" | "target" | "auth" | "unknown"; export type ClassifiedError = { layer: ProxyErrorLayer; title: string; detail: string; actions: { label: string; onClick: () => void; primary?: boolean }[]; }; const LAYER_LABEL: Record = { proxy: "代理", target: "目标", auth: "鉴权", unknown: "未知", }; export function classifyRequestError(message: string): ClassifiedError { const m = message.toLowerCase(); if ( m.includes("proxy") || m.includes("econnrefused") || m.includes("8787") || m.includes("failed to fetch") || m.includes("networkerror") || m.includes("gin-proxy") || m.includes("load failed") ) { return { layer: "proxy", title: "代理层失败", detail: message, actions: [ { label: "检查本地代理", primary: true, onClick: () => void openExternal("http://127.0.0.1:8787/health"), }, { label: "切换桌面直连", primary: true, onClick: () => { useWorkspace.getState().patchSettings({ transportMode: "tauri-native" }); }, }, { label: "查看通道说明", onClick: () => useWorkspace.getState().setDialog({ type: "settings" }), }, ], }; } if ( m.includes("401") || m.includes("403") || m.includes("unauthorized") || m.includes("forbidden") || m.includes("token") || m.includes("鉴权") || (m.includes("auth") && !m.includes("author")) ) { return { layer: "auth", title: "鉴权层失败", detail: message, actions: [ { label: "检查 Token / Auth", primary: true, onClick: () => { useWorkspace.getState().setDialog({ type: "environments" }); }, }, ], }; } if ( m.includes("timeout") || m.includes("enotfound") || m.includes("404") || m.includes("502") || m.includes("503") || m.includes("ssl") || m.includes("certificate") || m.includes("target") || m.includes("dns") ) { return { layer: "target", title: "目标服务失败", detail: message, actions: [ { label: "检查 BaseURL / VPN", primary: true, onClick: () => useWorkspace.getState().setDialog({ type: "environments" }), }, ], }; } return { layer: "unknown", title: "请求失败(请按层排查)", detail: message, actions: [ { label: "查代理 :8787", primary: true, onClick: () => void openExternal("http://127.0.0.1:8787/health"), }, { label: "切换桌面直连", primary: true, onClick: () => useWorkspace.getState().patchSettings({ transportMode: "tauri-native" }), }, { label: "查环境 / BaseURL", onClick: () => useWorkspace.getState().setDialog({ type: "environments" }), }, ], }; } const BANNER_STYLE: Record = { proxy: "border-amber-500/40 bg-amber-500/10 text-amber-50", target: "border-sky-500/40 bg-sky-500/10 text-sky-50", auth: "border-rose-500/40 bg-rose-500/10 text-rose-50", unknown: "border-zinc-500/40 bg-zinc-500/10 text-zinc-100", }; const TAG_STYLE: Record = { proxy: "bg-amber-500/25 text-amber-100 ring-amber-400/40", target: "bg-sky-500/25 text-sky-100 ring-sky-400/40", auth: "bg-rose-500/25 text-rose-100 ring-rose-400/40", unknown: "bg-zinc-500/30 text-zinc-200 ring-zinc-400/40", }; function LayerTag({ layer }: { layer: ProxyErrorLayer }) { return ( {LAYER_LABEL[layer]} ); } export function ProxyErrorBanner({ message, onDismiss }: { message: string; onDismiss?: () => void }) { const info = classifyRequestError(message); return (
{info.title}
{onDismiss && ( )}
{info.detail}
{info.actions.map((a) => a.primary ? ( ) : ( ), )}
); }