From ca73d0aa1c92a4f0da2fd4a0ecf8cdc7200dea59 Mon Sep 17 00:00:00 2001 From: coso Date: Wed, 24 Dec 2025 22:28:54 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E9=87=8D=E5=A4=A7=E7=95=8C?= =?UTF-8?q?=E9=9D=A2=E9=87=8D=E6=9E=84=20-=20=E5=88=A0=E9=99=A4=E4=BB=AA?= =?UTF-8?q?=E8=A1=A8=E7=9B=98=EF=BC=8C=E9=87=8D=E7=BB=84=E5=AF=BC=E8=88=AA?= =?UTF-8?q?=E7=BB=93=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 主要变更 ### 1. 删除仪表盘功能 - 删除 Dashboard.tsx 组件和相关前端代码 - 删除后端 get_dashboard_data 命令和 DashboardData 结构 - 删除 getDashboardData API 和相关类型定义 - 删除不再使用的 MonitoringPage 组件 ### 2. 重新设计导航结构 - API Server 现在是默认首页(替代仪表盘) - 左侧导航栏顺序:API Server、凭证池、配置管理、Flow Monitor、设置 - 移除仪表盘菜单项,简化导航体验 ### 3. 设置页面功能整合 - 将扩展功能(MCP、Prompts、Skills、Plugins)移到设置页面的"扩展"标签 - 将路由管理功能移到设置页面的"路由管理 (实验)"标签,标注为实验功能 - 创建 ExtensionsSettings.tsx 和 RoutingSettings.tsx 组件 - 保持所有功能完整性,只是重新组织了访问路径 ### 4. 代码清理 - 清理前后端所有仪表盘相关代码 - 移除未使用的导入和组件引用 - 修复 TypeScript 类型错误 ## 新的界面结构 ``` 左侧导航: ├─ API Server (首页) ├─ 凭证池 ├─ 配置管理 ├─ Flow Monitor └─ 设置 ├─ 通用 ├─ 代理服务 ├─ 安全 ├─ 高级 ├─ 扩展 (新增) ├─ 路由管理 (实验) (新增) └─ 关于 ``` 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4 --- src-tauri/src/commands/telemetry_cmd.rs | 52 --- src-tauri/src/lib.rs | 1 - src/App.tsx | 16 +- src/components/Dashboard.tsx | 379 ------------------ src/components/Sidebar.tsx | 21 +- src/components/monitoring/LogViewer.tsx | 278 ------------- src/components/monitoring/MonitoringPage.tsx | 173 -------- src/components/monitoring/StatsOverview.tsx | 178 -------- src/components/monitoring/TokenStats.tsx | 226 ----------- src/components/monitoring/index.ts | 4 - .../settings/ExtensionsSettings.tsx | 58 +++ src/components/settings/RoutingSettings.tsx | 76 ++++ src/components/settings/SettingsPage.tsx | 15 +- src/lib/api/telemetry.ts | 13 - 14 files changed, 153 insertions(+), 1337 deletions(-) delete mode 100644 src/components/Dashboard.tsx delete mode 100644 src/components/monitoring/LogViewer.tsx delete mode 100644 src/components/monitoring/MonitoringPage.tsx delete mode 100644 src/components/monitoring/StatsOverview.tsx delete mode 100644 src/components/monitoring/TokenStats.tsx delete mode 100644 src/components/monitoring/index.ts create mode 100644 src/components/settings/ExtensionsSettings.tsx create mode 100644 src/components/settings/RoutingSettings.tsx diff --git a/src-tauri/src/commands/telemetry_cmd.rs b/src-tauri/src/commands/telemetry_cmd.rs index a4906f155..f7f27e0cf 100644 --- a/src-tauri/src/commands/telemetry_cmd.rs +++ b/src-tauri/src/commands/telemetry_cmd.rs @@ -284,55 +284,3 @@ pub async fn get_token_stats_by_day( let tokens = state.tokens.read(); Ok(tokens.by_day(days.unwrap_or(7))) } - -// ========== 仪表盘数据命令 ========== - -/// 仪表盘数据 -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct DashboardData { - /// 统计摘要 - pub stats: StatsSummary, - /// Token 摘要 - pub tokens: TokenStatsSummary, - /// 按 Provider 统计 - pub by_provider: HashMap, - /// 最近请求 - pub recent_logs: Vec, -} - -/// 获取仪表盘数据 -#[tauri::command] -pub async fn get_dashboard_data( - state: tauri::State<'_, TelemetryState>, -) -> Result { - // 获取最近 24 小时的统计 - let range = Some(TimeRange::last_hours(24)); - - let stats_guard = state.stats.read(); - let stats = stats_guard.summary(range); - let by_provider: HashMap = stats_guard - .by_provider(range) - .into_iter() - .map(|(k, v)| (k.to_string(), v)) - .collect(); - drop(stats_guard); - - let tokens_guard = state.tokens.read(); - let tokens = tokens_guard.summary( - Some(Utc::now() - chrono::Duration::hours(24)), - Some(Utc::now()), - ); - drop(tokens_guard); - - // 获取最近 20 条日志 - let mut recent_logs = state.logger.get_all(); - recent_logs.sort_by(|a, b| b.timestamp.cmp(&a.timestamp)); - recent_logs.truncate(20); - - Ok(DashboardData { - stats, - tokens, - by_provider, - recent_logs, - }) -} diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 6277f0d39..5917ce2f6 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -2042,7 +2042,6 @@ pub fn run() { commands::telemetry_cmd::get_token_stats_by_provider, commands::telemetry_cmd::get_token_stats_by_model, commands::telemetry_cmd::get_token_stats_by_day, - commands::telemetry_cmd::get_dashboard_data, // Injection commands commands::injection_cmd::get_injection_config, commands::injection_cmd::set_injection_enabled, diff --git a/src/App.tsx b/src/App.tsx index d04880d2d..5c642da0e 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,27 +1,21 @@ import { useState, useEffect } from "react"; import { Sidebar } from "./components/Sidebar"; -import { Dashboard } from "./components/Dashboard"; import { SettingsPage } from "./components/settings"; import { ApiServerPage } from "./components/api-server/ApiServerPage"; import { ProviderPoolPage } from "./components/provider-pool"; -import { RoutingManagementPage } from "./components/routing/RoutingManagementPage"; import { ConfigManagementPage } from "./components/config/ConfigManagementPage"; -import { ExtensionsPage } from "./components/extensions"; import { FlowMonitorPage } from "./pages"; import { flowEventManager } from "./lib/flowEventManager"; type Page = - | "dashboard" | "provider-pool" - | "routing-management" | "config-management" - | "extensions" | "api-server" | "flow-monitor" | "settings"; function App() { - const [currentPage, setCurrentPage] = useState("dashboard"); + const [currentPage, setCurrentPage] = useState("api-server"); // 在应用启动时初始化 Flow 事件订阅 useEffect(() => { @@ -31,16 +25,10 @@ function App() { const renderPage = () => { switch (currentPage) { - case "dashboard": - return ; case "provider-pool": return ; - case "routing-management": - return ; case "config-management": return ; - case "extensions": - return ; case "api-server": return ; case "flow-monitor": @@ -48,7 +36,7 @@ function App() { case "settings": return ; default: - return ; + return ; } }; diff --git a/src/components/Dashboard.tsx b/src/components/Dashboard.tsx deleted file mode 100644 index 82e3cf9c7..000000000 --- a/src/components/Dashboard.tsx +++ /dev/null @@ -1,379 +0,0 @@ -import React, { useState, useEffect, useCallback } from "react"; -import { - Server, - Zap, - Clock, - Key, - Monitor, - Globe, - AlertCircle, - Coins, - RefreshCw, - LayoutDashboard, - Activity, - CheckCircle2, -} from "lucide-react"; -import { - getServerStatus, - getConfig, - ServerStatus, - Config, - getDefaultProvider, -} from "@/hooks/useTauri"; -import { useAllOAuthCredentials } from "@/hooks/useOAuthCredentials"; -import { useProviderPool } from "@/hooks/useProviderPool"; -import { TokenStats } from "./monitoring/TokenStats"; -import { - getTokenStatsByDay, - getTokenStatsByProvider, - type PeriodTokenStats, - type ProviderTokenStats, -} from "@/lib/api/telemetry"; - -type TabType = "overview" | "tokens"; - -export function Dashboard() { - const [activeTab, setActiveTab] = useState("overview"); - const [status, setStatus] = useState(null); - const [config, setConfig] = useState(null); - const [defaultProvider, setDefaultProvider] = useState(""); - const { reload: reloadCredentials } = useAllOAuthCredentials(); - const { refresh: refreshProviderPool } = useProviderPool(); - - // Token 数据状态 - const [tokensByDay, setTokensByDay] = useState([]); - const [tokensByProvider, setTokensByProvider] = useState< - Record - >({}); - const [monitoringLoading, setMonitoringLoading] = useState(false); - - // 获取基础数据 - useEffect(() => { - const fetchData = async () => { - try { - const [s, c, dp] = await Promise.all([ - getServerStatus(), - getConfig(), - getDefaultProvider(), - ]); - setStatus(s); - setConfig(c); - setDefaultProvider(dp); - } catch (e) { - console.error("Failed to fetch data:", e); - } - }; - - fetchData(); - reloadCredentials(); - refreshProviderPool(); - - const interval = setInterval(fetchData, 5000); - return () => clearInterval(interval); - }, [reloadCredentials, refreshProviderPool]); - - // 获取Token数据 - const fetchMonitoringData = useCallback(async () => { - try { - setMonitoringLoading(true); - const [dayStats, providerStats] = await Promise.all([ - getTokenStatsByDay(7), - getTokenStatsByProvider({ preset: "7d" }), - ]); - - setTokensByDay(dayStats); - setTokensByProvider(providerStats); - } catch (e) { - console.error("Failed to fetch token data:", e); - } finally { - setMonitoringLoading(false); - } - }, []); - - // 切换到Token标签时加载数据 - useEffect(() => { - if (activeTab === "tokens") { - fetchMonitoringData(); - } - }, [activeTab, fetchMonitoringData]); - - // 定时刷新Token数据 - useEffect(() => { - if (activeTab === "tokens") { - const interval = setInterval(fetchMonitoringData, 30000); - return () => clearInterval(interval); - } - }, [activeTab, fetchMonitoringData]); - - const formatUptime = (secs: number) => { - const h = Math.floor(secs / 3600); - const m = Math.floor((secs % 3600) / 60); - return `${h}h ${m}m`; - }; - - const serverUrl = status - ? `http://${status.host}:${status.port}` - : "http://localhost:8999"; - - const getProviderName = (id: string) => { - switch (id) { - case "kiro": - return "Kiro Claude"; - case "gemini": - return "Gemini CLI"; - case "qwen": - return "通义千问"; - case "openai": - return "OpenAI 自定义"; - case "claude": - return "Claude 自定义"; - default: - return id; - } - }; - - const tabs: { id: TabType; label: string; icon: React.ElementType }[] = [ - { id: "overview", label: "概览", icon: LayoutDashboard }, - { id: "tokens", label: "Token", icon: Coins }, - ]; - - return ( -
- {/* 页面标题 */} -
-
-

仪表盘

-

系统状态与监控

-
-
- {activeTab === "tokens" && ( - - )} -
-
- - {/* 标签页 */} -
- {tabs.map((tab) => ( - - ))} -
- - {/* 内容区域 */} - {activeTab === "overview" && ( - - )} - - {activeTab === "tokens" && ( - - )} - - {/* 加载状态 */} - {activeTab === "tokens" && - monitoringLoading && - Object.keys(tokensByProvider).length === 0 && ( -
- -
- )} -
- ); -} - -// 概览标签页内容 -function OverviewTab({ - status, - config, - defaultProvider, - serverUrl, - formatUptime, - getProviderName, -}: { - status: ServerStatus | null; - config: Config | null; - defaultProvider: string; - serverUrl: string; - formatUptime: (secs: number) => string; - getProviderName: (id: string) => string; -}) { - return ( -
- {/* Server Status Cards */} -
-
-
- - 服务状态 -
-
-
- - {status?.running ? "运行中" : "已停止"} - -
-
- -
-
- - 请求数 -
-
{status?.requests || 0}
-
- -
-
- - 运行时间 -
-
- {formatUptime(status?.uptime_secs || 0)} -
-
- -
-
- - 默认 Provider -
-
- {defaultProvider ? getProviderName(defaultProvider) : "加载中..."} -
-
-
- - {/* Quick Links */} -
- - - -
- - {/* Server Info */} - {config && ( -
-

服务器信息

-
-
- API 地址: - - {serverUrl} - -
-
- API Key: - - {config.server.api_key.length > 8 - ? `${config.server.api_key.slice(0, 4)}****${config.server.api_key.slice(-4)}` - : "****"} - -
-
-
- )} -
- ); -} - -function QuickLinkCard({ - icon: Icon, - title, - description, - status, - statusText, -}: { - icon: React.ElementType; - title: string; - description: string; - status: "success" | "warning" | "error" | "info"; - statusText: string; -}) { - const statusColors = { - success: "text-green-600", - warning: "text-yellow-600", - error: "text-red-600", - info: "text-blue-600", - }; - - const StatusIcon = status === "success" ? CheckCircle2 : AlertCircle; - - return ( -
-
-
- -
-
-

{title}

-

{description}

-
-
-
- - {statusText} -
-
- ); -} diff --git a/src/components/Sidebar.tsx b/src/components/Sidebar.tsx index 6fa0471d1..75d8e98bc 100644 --- a/src/components/Sidebar.tsx +++ b/src/components/Sidebar.tsx @@ -1,21 +1,9 @@ -import { - LayoutDashboard, - Settings, - Globe, - Database, - Route, - FileCode, - Puzzle, - Activity, -} from "lucide-react"; +import { Settings, Globe, Database, FileCode, Activity } from "lucide-react"; import { cn } from "@/lib/utils"; type Page = - | "dashboard" | "provider-pool" - | "routing-management" | "config-management" - | "extensions" | "api-server" | "flow-monitor" | "settings"; @@ -26,12 +14,9 @@ interface SidebarProps { } const navItems = [ - { id: "dashboard" as Page, label: "仪表盘", icon: LayoutDashboard }, - { id: "provider-pool" as Page, label: "凭证池", icon: Database }, - { id: "routing-management" as Page, label: "路由管理", icon: Route }, - { id: "config-management" as Page, label: "配置管理", icon: FileCode }, - { id: "extensions" as Page, label: "扩展", icon: Puzzle }, { id: "api-server" as Page, label: "API Server", icon: Globe }, + { id: "provider-pool" as Page, label: "凭证池", icon: Database }, + { id: "config-management" as Page, label: "配置管理", icon: FileCode }, { id: "flow-monitor" as Page, label: "Flow Monitor", icon: Activity }, { id: "settings" as Page, label: "设置", icon: Settings }, ]; diff --git a/src/components/monitoring/LogViewer.tsx b/src/components/monitoring/LogViewer.tsx deleted file mode 100644 index 55e8d1924..000000000 --- a/src/components/monitoring/LogViewer.tsx +++ /dev/null @@ -1,278 +0,0 @@ -import React, { useState } from "react"; -import { - FileText, - CheckCircle2, - XCircle, - Clock, - AlertTriangle, - ChevronDown, - ChevronRight, - Trash2, - Filter, -} from "lucide-react"; -import type { RequestLog, RequestStatus } from "@/lib/api/telemetry"; - -interface LogViewerProps { - logs: RequestLog[]; - onClear: () => void; - onFilter?: (filter: { provider?: string; status?: RequestStatus }) => void; -} - -export function LogViewer({ logs, onClear, onFilter }: LogViewerProps) { - const [expandedId, setExpandedId] = useState(null); - const [filterProvider, setFilterProvider] = useState(""); - const [filterStatus, setFilterStatus] = useState(""); - - const getStatusIcon = (status: RequestStatus) => { - switch (status) { - case "success": - return ; - case "failed": - return ; - case "timeout": - return ; - case "retrying": - return ; - case "cancelled": - return ; - } - }; - - const getStatusText = (status: RequestStatus) => { - const texts: Record = { - success: "成功", - failed: "失败", - timeout: "超时", - retrying: "重试中", - cancelled: "已取消", - }; - return texts[status]; - }; - - const getProviderName = (id: string) => { - const names: Record = { - kiro: "Kiro", - gemini: "Gemini", - qwen: "Qwen", - openai: "OpenAI", - claude: "Claude", - antigravity: "Antigravity", - }; - return names[id] || id; - }; - - const formatTime = (timestamp: string) => { - const date = new Date(timestamp); - return date.toLocaleString("zh-CN", { - month: "2-digit", - day: "2-digit", - hour: "2-digit", - minute: "2-digit", - second: "2-digit", - }); - }; - - const formatDuration = (ms: number) => { - if (ms < 1000) return `${ms}ms`; - return `${(ms / 1000).toFixed(2)}s`; - }; - - const handleFilterChange = () => { - onFilter?.({ - provider: filterProvider || undefined, - status: (filterStatus as RequestStatus) || undefined, - }); - }; - - // 获取唯一的 providers - const providers = [...new Set(logs.map((l) => l.provider))]; - - return ( -
- {/* 过滤器和操作 */} -
-
- - - -
- -
- - {/* 日志列表 */} -
-
- - 请求日志 - - ({logs.length} 条) - -
- - {logs.length === 0 ? ( -
- 暂无请求日志 -
- ) : ( -
- {logs.map((log) => ( - - setExpandedId(expandedId === log.id ? null : log.id) - } - getStatusIcon={getStatusIcon} - getStatusText={getStatusText} - getProviderName={getProviderName} - formatTime={formatTime} - formatDuration={formatDuration} - /> - ))} -
- )} -
-
- ); -} - -function LogEntry({ - log, - expanded, - onToggle, - getStatusIcon, - getStatusText, - getProviderName, - formatTime, - formatDuration, -}: { - log: RequestLog; - expanded: boolean; - onToggle: () => void; - getStatusIcon: (status: RequestStatus) => React.ReactNode; - getStatusText: (status: RequestStatus) => string; - getProviderName: (id: string) => string; - formatTime: (timestamp: string) => string; - formatDuration: (ms: number) => string; -}) { - return ( -
-
- {expanded ? ( - - ) : ( - - )} - {getStatusIcon(log.status)} - - {formatTime(log.timestamp)} - - - {getProviderName(log.provider)} - - {log.model} - - {formatDuration(log.duration_ms)} - - {log.total_tokens && ( - - {log.total_tokens} tokens - - )} -
- - {expanded && ( -
-
-
- 状态:{" "} - - {getStatusText(log.status)} - - {log.http_status && ( - - (HTTP {log.http_status}) - - )} -
-
- 流式:{" "} - {log.is_streaming ? "是" : "否"} -
- {log.input_tokens !== undefined && ( -
- 输入 Token:{" "} - {log.input_tokens} -
- )} - {log.output_tokens !== undefined && ( -
- 输出 Token:{" "} - {log.output_tokens} -
- )} - {log.retry_count > 0 && ( -
- 重试次数:{" "} - {log.retry_count} -
- )} - {log.credential_id && ( -
- 凭证 ID:{" "} - - {log.credential_id.slice(0, 8)}... - -
- )} -
- {log.error_message && ( -
- 错误: {log.error_message} -
- )} -
- )} -
- ); -} diff --git a/src/components/monitoring/MonitoringPage.tsx b/src/components/monitoring/MonitoringPage.tsx deleted file mode 100644 index 5fccf7b18..000000000 --- a/src/components/monitoring/MonitoringPage.tsx +++ /dev/null @@ -1,173 +0,0 @@ -import React, { useState, useEffect, useCallback } from "react"; -import { Activity, FileText, Coins, RefreshCw } from "lucide-react"; -import { StatsOverview } from "./StatsOverview"; -import { LogViewer } from "./LogViewer"; -import { TokenStats } from "./TokenStats"; -import { - getDashboardData, - getRequestLogs, - clearRequestLogs, - getTokenStatsByDay, - getTokenStatsByProvider, - type DashboardData, - type RequestLog, - type PeriodTokenStats, - type ProviderTokenStats, - type RequestStatus, -} from "@/lib/api/telemetry"; - -type TabType = "overview" | "logs" | "tokens"; - -export function MonitoringPage() { - const [activeTab, setActiveTab] = useState("overview"); - const [dashboardData, setDashboardData] = useState( - null, - ); - const [logs, setLogs] = useState([]); - const [tokensByDay, setTokensByDay] = useState([]); - const [tokensByProvider, setTokensByProvider] = useState< - Record - >({}); - const [loading, setLoading] = useState(true); - const [error, setError] = useState(null); - - const fetchData = useCallback(async () => { - try { - setLoading(true); - setError(null); - - const [dashboard, logsData, dayStats, providerStats] = await Promise.all([ - getDashboardData(), - getRequestLogs({ limit: 100 }), - getTokenStatsByDay(7), - getTokenStatsByProvider({ preset: "7d" }), - ]); - - setDashboardData(dashboard); - setLogs(logsData); - setTokensByDay(dayStats); - setTokensByProvider(providerStats); - } catch (e) { - console.error("Failed to fetch monitoring data:", e); - setError(e instanceof Error ? e.message : "加载数据失败"); - } finally { - setLoading(false); - } - }, []); - - useEffect(() => { - fetchData(); - // 每 30 秒刷新一次 - const interval = setInterval(fetchData, 30000); - return () => clearInterval(interval); - }, [fetchData]); - - const handleClearLogs = async () => { - try { - await clearRequestLogs(); - setLogs([]); - } catch (e) { - console.error("Failed to clear logs:", e); - } - }; - - const handleFilterLogs = async (filter: { - provider?: string; - status?: RequestStatus; - }) => { - try { - const filteredLogs = await getRequestLogs({ - ...filter, - limit: 100, - }); - setLogs(filteredLogs); - } catch (e) { - console.error("Failed to filter logs:", e); - } - }; - - const tabs: { id: TabType; label: string; icon: React.ElementType }[] = [ - { id: "overview", label: "统计概览", icon: Activity }, - { id: "logs", label: "请求日志", icon: FileText }, - { id: "tokens", label: "Token 统计", icon: Coins }, - ]; - - return ( -
- {/* 页面标题 */} -
-
-

监控中心

-

- 请求统计、日志和 Token 使用情况 -

-
- -
- - {/* 标签页 */} -
- {tabs.map((tab) => ( - - ))} -
- - {/* 错误提示 */} - {error && ( -
- {error} -
- )} - - {/* 内容区域 */} - {loading && !dashboardData ? ( -
- -
- ) : ( - <> - {activeTab === "overview" && dashboardData && ( - - )} - - {activeTab === "logs" && ( - - )} - - {activeTab === "tokens" && dashboardData && ( - - )} - - )} -
- ); -} diff --git a/src/components/monitoring/StatsOverview.tsx b/src/components/monitoring/StatsOverview.tsx deleted file mode 100644 index 26ab69c04..000000000 --- a/src/components/monitoring/StatsOverview.tsx +++ /dev/null @@ -1,178 +0,0 @@ -import React from "react"; -import { - Activity, - CheckCircle2, - XCircle, - Clock, - Zap, - TrendingUp, -} from "lucide-react"; -import type { StatsSummary, ProviderStats } from "@/lib/api/telemetry"; - -interface StatsOverviewProps { - stats: StatsSummary; - byProvider: Record; -} - -export function StatsOverview({ stats, byProvider }: StatsOverviewProps) { - const formatLatency = (ms: number) => { - if (ms < 1000) return `${Math.round(ms)}ms`; - return `${(ms / 1000).toFixed(2)}s`; - }; - - const formatRate = (rate: number) => `${(rate * 100).toFixed(1)}%`; - - const getProviderName = (id: string) => { - const names: Record = { - kiro: "Kiro Claude", - gemini: "Gemini", - qwen: "通义千问", - openai: "OpenAI", - claude: "Claude", - antigravity: "Antigravity", - }; - return names[id] || id; - }; - - return ( -
- {/* 总体统计卡片 */} -
- - = 0.9 - ? "text-green-600" - : stats.success_rate >= 0.7 - ? "text-yellow-600" - : "text-red-600" - } - /> - - -
- - {/* 按 Provider 统计 */} - {Object.keys(byProvider).length > 0 && ( -
-

- 按 Provider 统计 -

-
- {Object.entries(byProvider).map(([provider, providerStats]) => ( - - ))} -
-
- )} -
- ); -} - -function StatCard({ - icon: Icon, - label, - value, - subValue, - valueColor = "text-foreground", -}: { - icon: React.ElementType; - label: string; - value: string; - subValue?: string; - valueColor?: string; -}) { - return ( -
-
- - {label} -
-
{value}
- {subValue && ( -
{subValue}
- )} -
- ); -} - -function ProviderStatRow({ - name, - stats, -}: { - name: string; - stats: ProviderStats; -}) { - const successRate = stats.success_rate * 100; - - return ( -
-
-
{name}
-
- {stats.total_requests} 请求 -
-
-
-
- - {stats.successful_requests} -
-
- - {stats.failed_requests} -
-
-
-
= 90 - ? "bg-green-500" - : successRate >= 70 - ? "bg-yellow-500" - : "bg-red-500" - }`} - style={{ width: `${successRate}%` }} - /> -
-
- - {successRate.toFixed(0)}% - -
-
- ); -} - -function formatNumber(num: number): string { - if (num >= 1000000) return `${(num / 1000000).toFixed(1)}M`; - if (num >= 1000) return `${(num / 1000).toFixed(1)}K`; - return num.toString(); -} diff --git a/src/components/monitoring/TokenStats.tsx b/src/components/monitoring/TokenStats.tsx deleted file mode 100644 index d45835beb..000000000 --- a/src/components/monitoring/TokenStats.tsx +++ /dev/null @@ -1,226 +0,0 @@ -import React from "react"; -import { Coins, TrendingUp, BarChart3 } from "lucide-react"; -import type { - TokenStatsSummary, - ProviderTokenStats, - PeriodTokenStats, -} from "@/lib/api/telemetry"; - -interface TokenStatsProps { - summary: TokenStatsSummary; - byProvider: Record; - byDay: PeriodTokenStats[]; -} - -export function TokenStats({ summary, byProvider, byDay }: TokenStatsProps) { - const getProviderName = (id: string) => { - const names: Record = { - kiro: "Kiro Claude", - gemini: "Gemini", - qwen: "通义千问", - openai: "OpenAI", - claude: "Claude", - antigravity: "Antigravity", - }; - return names[id] || id; - }; - - const formatNumber = (num: number): string => { - if (num >= 1000000) return `${(num / 1000000).toFixed(2)}M`; - if (num >= 1000) return `${(num / 1000).toFixed(1)}K`; - return num.toString(); - }; - - const formatDate = (dateStr?: string) => { - if (!dateStr) return ""; - const date = new Date(dateStr); - return date.toLocaleDateString("zh-CN", { - month: "2-digit", - day: "2-digit", - }); - }; - - // 计算每日最大值用于图表缩放 - const maxDayTokens = Math.max(...byDay.map((d) => d.total_tokens), 1); - - return ( -
- {/* Token 总览 */} -
- - - - -
- - {/* 按 Provider 统计 */} - {Object.keys(byProvider).length > 0 && ( -
-

- 按 Provider Token 使用 -

-
- {Object.entries(byProvider).map(([provider, stats]) => ( - - ))} -
-
- )} - - {/* 每日趋势 */} - {byDay.length > 0 && ( -
-

- - 每日 Token 使用趋势 -

-
- {byDay - .slice() - .reverse() - .map((day, index) => ( - - ))} -
-
- )} -
- ); -} - -function TokenCard({ - icon: Icon, - label, - value, - subValue, -}: { - icon: React.ElementType; - label: string; - value: string; - subValue?: string; -}) { - return ( -
-
- - {label} -
-
{value}
- {subValue && ( -
{subValue}
- )} -
- ); -} - -function ProviderTokenRow({ - name, - stats, - totalTokens, -}: { - name: string; - stats: ProviderTokenStats; - totalTokens: number; -}) { - const percentage = - totalTokens > 0 ? (stats.total_tokens / totalTokens) * 100 : 0; - - const formatNumber = (num: number): string => { - if (num >= 1000000) return `${(num / 1000000).toFixed(2)}M`; - if (num >= 1000) return `${(num / 1000).toFixed(1)}K`; - return num.toString(); - }; - - return ( -
-
-
{name}
-
- {stats.record_count} 条记录 -
-
-
-
- 输入:{" "} - {formatNumber(stats.total_input_tokens)} -
-
- 输出:{" "} - {formatNumber(stats.total_output_tokens)} -
-
-
-
-
-
- - {formatNumber(stats.total_tokens)} - -
-
- ); -} - -function DayBar({ - date, - tokens, - maxTokens, -}: { - date: string; - tokens: number; - maxTokens: number; -}) { - const height = maxTokens > 0 ? (tokens / maxTokens) * 100 : 0; - - const formatNumber = (num: number): string => { - if (num >= 1000000) return `${(num / 1000000).toFixed(1)}M`; - if (num >= 1000) return `${(num / 1000).toFixed(0)}K`; - return num.toString(); - }; - - return ( -
-
-
-
- {date} -
- ); -} diff --git a/src/components/monitoring/index.ts b/src/components/monitoring/index.ts deleted file mode 100644 index 3fc1c4596..000000000 --- a/src/components/monitoring/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export { MonitoringPage } from "./MonitoringPage"; -export { StatsOverview } from "./StatsOverview"; -export { LogViewer } from "./LogViewer"; -export { TokenStats } from "./TokenStats"; diff --git a/src/components/settings/ExtensionsSettings.tsx b/src/components/settings/ExtensionsSettings.tsx new file mode 100644 index 000000000..4b88748d0 --- /dev/null +++ b/src/components/settings/ExtensionsSettings.tsx @@ -0,0 +1,58 @@ +import { useState } from "react"; +import { Plug, MessageSquare, Boxes, Puzzle } from "lucide-react"; +import { cn } from "@/lib/utils"; +import { McpPage } from "../mcp/McpPage"; +import { PromptsPage } from "../prompts/PromptsPage"; +import { SkillsPage } from "../skills/SkillsPage"; +import { PluginManager } from "../plugins/PluginManager"; + +type Tab = "mcp" | "prompts" | "skills" | "plugins"; + +const tabs = [ + { id: "mcp" as Tab, label: "MCP", icon: Plug }, + { id: "prompts" as Tab, label: "Prompts", icon: MessageSquare }, + { id: "skills" as Tab, label: "Skills", icon: Boxes }, + { id: "plugins" as Tab, label: "Plugins", icon: Puzzle }, +]; + +export function ExtensionsSettings() { + const [activeTab, setActiveTab] = useState("mcp"); + + return ( +
+
+

扩展管理

+

+ 管理 MCP 服务器、Prompts 和 Skills +

+
+ + {/* Tab 切换 */} +
+ {tabs.map((tab) => ( + + ))} +
+ + {/* Tab 内容 */} +
+ {activeTab === "mcp" && } + {activeTab === "prompts" && } + {activeTab === "skills" && } + {activeTab === "plugins" && } +
+
+ ); +} diff --git a/src/components/settings/RoutingSettings.tsx b/src/components/settings/RoutingSettings.tsx new file mode 100644 index 000000000..814946b7e --- /dev/null +++ b/src/components/settings/RoutingSettings.tsx @@ -0,0 +1,76 @@ +import { useState } from "react"; +import { Route, Shield, AlertTriangle } from "lucide-react"; +import { cn } from "@/lib/utils"; +import { RoutingPage } from "../routing/RoutingPage"; +import { ResiliencePage } from "../resilience/ResiliencePage"; + +type Tab = "routing" | "resilience"; + +const tabs = [ + { id: "routing" as Tab, label: "智能路由", icon: Route }, + { id: "resilience" as Tab, label: "容错配置", icon: Shield }, +]; + +export function RoutingSettings() { + const [activeTab, setActiveTab] = useState("routing"); + + return ( +
+
+
+

路由管理

+
+ + 实验功能 +
+
+

+ 配置智能路由规则和容错策略(实验性功能,可能不稳定) +

+
+ + {/* Tab 切换 */} +
+ {tabs.map((tab) => ( + + ))} +
+ + {/* Tab 内容 */} +
+ {activeTab === "routing" && } + {activeTab === "resilience" && } +
+
+ ); +} + +// 路由页面内容(去掉标题) +function RoutingPageContent() { + return ( +
+ +
+ ); +} + +// 容错页面内容(去掉标题) +function ResiliencePageContent() { + return ( +
+ +
+ ); +} diff --git a/src/components/settings/SettingsPage.tsx b/src/components/settings/SettingsPage.tsx index 93ca802ac..50f0b3aa8 100644 --- a/src/components/settings/SettingsPage.tsx +++ b/src/components/settings/SettingsPage.tsx @@ -7,14 +7,25 @@ import { AboutSection } from "./AboutSection"; import { TlsSettings } from "./TlsSettings"; import { QuotaSettings } from "./QuotaSettings"; import { RemoteManagementSettings } from "./RemoteManagementSettings"; +import { ExtensionsSettings } from "./ExtensionsSettings"; +import { RoutingSettings } from "./RoutingSettings"; -type SettingsTab = "general" | "proxy" | "security" | "advanced" | "about"; +type SettingsTab = + | "general" + | "proxy" + | "security" + | "advanced" + | "extensions" + | "routing" + | "about"; const tabs: { id: SettingsTab; label: string }[] = [ { id: "general", label: "通用" }, { id: "proxy", label: "代理服务" }, { id: "security", label: "安全" }, { id: "advanced", label: "高级" }, + { id: "extensions", label: "扩展" }, + { id: "routing", label: "路由管理 (实验)" }, { id: "about", label: "关于" }, ]; @@ -65,6 +76,8 @@ export function SettingsPage() {
)} + {activeTab === "extensions" && } + {activeTab === "routing" && } {activeTab === "about" && }
diff --git a/src/lib/api/telemetry.ts b/src/lib/api/telemetry.ts index 0f3c64939..b4c40e113 100644 --- a/src/lib/api/telemetry.ts +++ b/src/lib/api/telemetry.ts @@ -124,13 +124,6 @@ export interface TimeRangeParam { preset?: "1h" | "24h" | "7d" | "30d"; } -export interface DashboardData { - stats: StatsSummary; - tokens: TokenStatsSummary; - by_provider: Record; - recent_logs: RequestLog[]; -} - // ========== 请求日志 API ========== export async function getRequestLogs(params?: { @@ -197,9 +190,3 @@ export async function getTokenStatsByDay( ): Promise { return invoke("get_token_stats_by_day", { days }); } - -// ========== 仪表盘 API ========== - -export async function getDashboardData(): Promise { - return invoke("get_dashboard_data"); -}