This commit is contained in:
Randy Lu
2025-10-09 17:07:17 +08:00
parent af52575910
commit fd82e09203
8 changed files with 125 additions and 37 deletions
+2 -2
View File
@@ -21,9 +21,9 @@ export function Layout() {
label: "MCP"
},
{
to: "/monitor",
to: "/usage",
icon: ActivityIcon,
label: "Monitor"
label: "用量"
},
{
to: "/settings",
+25 -19
View File
@@ -4,6 +4,7 @@ import { ProjectUsageRecord } from "@/lib/query";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { useState, useMemo, useEffect } from "react";
import { formatLargeNumber } from "@/lib/utils";
import { useTranslation } from "react-i18next";
interface TokenUsageChartProps {
data: ProjectUsageRecord[];
@@ -13,6 +14,7 @@ interface TokenUsageChartProps {
type TimeRange = "5h" | "today" | "7d" | "week" | "month" | "all";
export function TokenUsageChart({ data, onFilteredDataChange }: TokenUsageChartProps) {
const { t } = useTranslation();
const [selectedModel, setSelectedModel] = useState<string>("all");
const [timeRange, setTimeRange] = useState<TimeRange>("5h");
const [activeCategories, setActiveCategories] = useState<string[]>(["Input Tokens", "Output Tokens"]);
@@ -255,7 +257,7 @@ export function TokenUsageChart({ data, onFilteredDataChange }: TokenUsageChartP
if (!data || data.length === 0) {
return (
<div className="h-96 flex items-center justify-center border rounded-lg bg-muted/20">
<p className="text-muted-foreground">No usage data available</p>
<p className="text-muted-foreground">{t("usageChart.noData")}</p>
</div>
);
}
@@ -266,14 +268,14 @@ export function TokenUsageChart({ data, onFilteredDataChange }: TokenUsageChartP
<div className="flex gap-4 items-center flex-wrap pb-5">
<div className="flex items-center gap-2">
<label htmlFor="model-filter" className="text-sm font-medium">
Model:
{t("usageChart.modelFilter")}
</label>
<Select value={selectedModel} onValueChange={setSelectedModel}>
<SelectTrigger id="model-filter" className="w-48">
<SelectValue placeholder="All models" />
<SelectValue placeholder={t("usageChart.allModels")} />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">All models</SelectItem>
<SelectItem value="all">{t("usageChart.allModels")}</SelectItem>
{availableModels.map((model) => (
<SelectItem key={model} value={model}>
{model}
@@ -285,19 +287,19 @@ export function TokenUsageChart({ data, onFilteredDataChange }: TokenUsageChartP
<div className="flex items-center gap-2">
<label htmlFor="time-range" className="text-sm font-medium">
Time Range:
{t("usageChart.timeRange")}
</label>
<Select value={timeRange} onValueChange={(value: TimeRange) => setTimeRange(value)}>
<SelectTrigger id="time-range" className="w-48">
<SelectValue placeholder="Select time range" />
<SelectValue placeholder={t("usageChart.selectTimeRange")} />
</SelectTrigger>
<SelectContent>
<SelectItem value="5h">Last 5 hours</SelectItem>
<SelectItem value="today">Start of today</SelectItem>
<SelectItem value="7d">Last 7 days</SelectItem>
<SelectItem value="week">Start of this week</SelectItem>
<SelectItem value="month">Start of this month</SelectItem>
<SelectItem value="all">All time</SelectItem>
<SelectItem value="5h">{t("usageChart.last5Hours")}</SelectItem>
<SelectItem value="today">{t("usageChart.startOfToday")}</SelectItem>
<SelectItem value="7d">{t("usageChart.last7Days")}</SelectItem>
<SelectItem value="week">{t("usageChart.startOfWeek")}</SelectItem>
<SelectItem value="month">{t("usageChart.startOfMonth")}</SelectItem>
<SelectItem value="all">{t("usageChart.allTime")}</SelectItem>
</SelectContent>
</Select>
</div>
@@ -305,15 +307,19 @@ export function TokenUsageChart({ data, onFilteredDataChange }: TokenUsageChartP
{/* Custom Legend */}
<div className="flex gap-6 items-center justify-center pb-4">
{["Input Tokens", "Output Tokens", "Cache Read Tokens"].map((category) => {
const isActive = activeCategories.includes(category);
const color = category === "Input Tokens" ? "bg-blue-500" :
category === "Output Tokens" ? "bg-emerald-500" :
{[
{ key: "Input Tokens", label: t("usage.inputTokens") },
{ key: "Output Tokens", label: t("usage.outputTokens") },
{ key: "Cache Read Tokens", label: t("usage.cacheReadTokens") }
].map(({ key, label }) => {
const isActive = activeCategories.includes(key);
const color = key === "Input Tokens" ? "bg-blue-500" :
key === "Output Tokens" ? "bg-emerald-500" :
"bg-amber-500";
return (
<button
key={category}
onClick={() => toggleCategory(category)}
key={key}
onClick={() => toggleCategory(key)}
className={`flex items-center gap-2 px-3 py-1 rounded-md text-sm transition-all ${
isActive
? 'opacity-100 hover:bg-gray-100 dark:hover:bg-gray-800'
@@ -321,7 +327,7 @@ export function TokenUsageChart({ data, onFilteredDataChange }: TokenUsageChartP
}`}
>
<span className={`w-3 h-3 rounded-full ${color}`} />
<span className="text-gray-700 dark:text-gray-300">{category}</span>
<span className="text-gray-700 dark:text-gray-300">{label}</span>
</button>
);
})}
+21 -1
View File
@@ -83,5 +83,25 @@
"mcp.addServerError": "Failed to add MCP server",
"mcp.deleteServerConfirm": "Are you sure you want to delete the MCP server \"{{serverName}}\"? This action cannot be undone.",
"mcp.deleteServerTitle": "Delete MCP Server",
"mcp.customPlaceholder": "example:\n\n{\n \"postgres\": {\n \"command\": \"npx\",\n \"args\": [\n \"-y\",\n \"@modelcontextprotocol/server-postgres\",\n \"postgresql://localhost/mydb\"\n ]\n }\n}"
"mcp.customPlaceholder": "example:\n\n{\n \"postgres\": {\n \"command\": \"npx\",\n \"args\": [\n \"-y\",\n \"@modelcontextprotocol/server-postgres\",\n \"postgresql://localhost/mydb\"\n ]\n }\n}",
"usage.title": "Usage",
"usage.description": "Monitor your token usage",
"usage.loading": "Loading usage data...",
"usage.error": "Error loading usage data: {{error}}",
"usage.noData": "No usage data found.",
"usage.inputTokens": "Input Tokens",
"usage.outputTokens": "Output Tokens",
"usage.cacheReadTokens": "Cache Read Tokens",
"usage.cost": "Cost",
"usageChart.noData": "No usage data available",
"usageChart.modelFilter": "Model:",
"usageChart.timeRange": "Time Range:",
"usageChart.allModels": "All models",
"usageChart.selectTimeRange": "Select time range",
"usageChart.last5Hours": "Last 5 hours",
"usageChart.startOfToday": "Start of today",
"usageChart.last7Days": "Last 7 days",
"usageChart.startOfWeek": "Start of this week",
"usageChart.startOfMonth": "Start of this month",
"usageChart.allTime": "All time"
}
+21 -1
View File
@@ -82,5 +82,25 @@
"mcp.addCustomServersTitle": "Ajouter des serveurs MCP personnalisés",
"mcp.deleteServerConfirm": "Êtes-vous sûr de vouloir supprimer le serveur MCP \"{{serverName}}\" ? Cette action ne peut pas être annulée.",
"mcp.deleteServerTitle": "Supprimer le serveur MCP",
"mcp.customPlaceholder": "exemple:\n\n{\n \"postgres\": {\n \"command\": \"npx\",\n \"args\": [\n \"-y\",\n \"@modelcontextprotocol/server-postgres\",\n \"postgresql://localhost/mydb\"\n ]\n }\n}"
"mcp.customPlaceholder": "exemple:\n\n{\n \"postgres\": {\n \"command\": \"npx\",\n \"args\": [\n \"-y\",\n \"@modelcontextprotocol/server-postgres\",\n \"postgresql://localhost/mydb\"\n ]\n }\n}",
"usage.title": "Utilisation",
"usage.description": "Surveillez votre utilisation de jetons",
"usage.loading": "Chargement des données d'utilisation...",
"usage.error": "Erreur lors du chargement des données d'utilisation : {{error}}",
"usage.noData": "Aucune donnée d'utilisation trouvée.",
"usage.inputTokens": "Jetons d'entrée",
"usage.outputTokens": "Jetons de sortie",
"usage.cacheReadTokens": "Jetons de lecture du cache",
"usage.cost": "Coût",
"usageChart.noData": "Aucune donnée d'utilisation disponible",
"usageChart.modelFilter": "Modèle :",
"usageChart.timeRange": "Plage de temps :",
"usageChart.allModels": "Tous les modèles",
"usageChart.selectTimeRange": "Sélectionner une plage de temps",
"usageChart.last5Hours": "Dernières 5 heures",
"usageChart.startOfToday": "Début de la journée",
"usageChart.last7Days": "Derniers 7 jours",
"usageChart.startOfWeek": "Début de la semaine",
"usageChart.startOfMonth": "Début du mois",
"usageChart.allTime": "Toutes les périodes"
}
+21 -1
View File
@@ -82,5 +82,25 @@
"mcp.addCustomServersTitle": "カスタム MCP サーバーを追加",
"mcp.deleteServerConfirm": "MCP サーバー \"{{serverName}}\" を削除してもよろしいですか?この操作は元に戻せません。",
"mcp.deleteServerTitle": "MCP サーバーを削除",
"mcp.customPlaceholder": "例:\n\n{\n \"postgres\": {\n \"command\": \"npx\",\n \"args\": [\n \"-y\",\n \"@modelcontextprotocol/server-postgres\",\n \"postgresql://localhost/mydb\"\n ]\n }\n}"
"mcp.customPlaceholder": "例:\n\n{\n \"postgres\": {\n \"command\": \"npx\",\n \"args\": [\n \"-y\",\n \"@modelcontextprotocol/server-postgres\",\n \"postgresql://localhost/mydb\"\n ]\n }\n}",
"usage.title": "使用量",
"usage.description": "トークン使用量を監視",
"usage.loading": "使用データを読み込み中...",
"usage.error": "使用データの読み込みエラー:{{error}}",
"usage.noData": "使用データが見つかりません。",
"usage.inputTokens": "入力トークン",
"usage.outputTokens": "出力トークン",
"usage.cacheReadTokens": "キャッシュ読み取りトークン",
"usage.cost": "コスト",
"usageChart.noData": "使用データがありません",
"usageChart.modelFilter": "モデル:",
"usageChart.timeRange": "時間範囲:",
"usageChart.allModels": "すべてのモデル",
"usageChart.selectTimeRange": "時間範囲を選択",
"usageChart.last5Hours": "過去 5 時間",
"usageChart.startOfToday": "今日の開始",
"usageChart.last7Days": "過去 7 日間",
"usageChart.startOfWeek": "今週の開始",
"usageChart.startOfMonth": "今月の開始",
"usageChart.allTime": "全期間"
}
+21 -1
View File
@@ -82,5 +82,25 @@
"mcp.addServerError": "添加 MCP 服务失败",
"mcp.deleteServerConfirm": "确定要删除 MCP 服务 \"{{serverName}}\" 吗?此操作无法撤销。",
"mcp.deleteServerTitle": "删除 MCP 服务",
"mcp.customPlaceholder": "例如:\n\n{\n \"postgres\": {\n \"command\": \"npx\",\n \"args\": [\n \"-y\",\n \"@modelcontextprotocol/server-postgres\",\n \"postgresql://localhost/mydb\"\n ]\n }\n}"
"mcp.customPlaceholder": "例如:\n\n{\n \"postgres\": {\n \"command\": \"npx\",\n \"args\": [\n \"-y\",\n \"@modelcontextprotocol/server-postgres\",\n \"postgresql://localhost/mydb\"\n ]\n }\n}",
"usage.title": "用量",
"usage.description": "查看您的 token 使用情况",
"usage.loading": "加载使用数据中...",
"usage.error": "加载使用数据出错:{{error}}",
"usage.noData": "未找到使用数据。",
"usage.inputTokens": "输入 token",
"usage.outputTokens": "输出 token",
"usage.cacheReadTokens": "缓存读取 token",
"usage.cost": "费用",
"usageChart.noData": "暂无使用数据",
"usageChart.modelFilter": "模型:",
"usageChart.timeRange": "时间范围:",
"usageChart.allModels": "所有模型",
"usageChart.selectTimeRange": "选择时间范围",
"usageChart.last5Hours": "最近 5 小时",
"usageChart.startOfToday": "今天开始",
"usageChart.last7Days": "最近 7 天",
"usageChart.startOfWeek": "本周开始",
"usageChart.startOfMonth": "本月开始",
"usageChart.allTime": "全部时间"
}
@@ -3,8 +3,10 @@ import { TokenUsageChart } from "@/components/TokenUsageChart";
import { useState, useEffect } from "react";
import { ProjectUsageRecord } from "@/lib/query";
import { formatLargeNumber } from "@/lib/utils";
import { useTranslation } from "react-i18next";
export function MonitorPage() {
export function UsagePage() {
const { t } = useTranslation();
const { data: usageData, isLoading, error } = useProjectUsageFiles();
const [filteredUsageData, setFilteredUsageData] = useState<ProjectUsageRecord[]>([]);
@@ -19,34 +21,34 @@ export function MonitorPage() {
<div className="">
<div className="flex items-center p-3 border-b px-3 justify-between sticky top-0 bg-background z-10 mb-4" data-tauri-drag-region>
<div data-tauri-drag-region>
<h3 className="font-bold" data-tauri-drag-region>Monitor</h3>
<h3 className="font-bold" data-tauri-drag-region>{t("usage.title")}</h3>
<p className="text-sm text-muted-foreground" data-tauri-drag-region>
Monitor your token usage
{t("usage.description")}
</p>
</div>
</div>
<div className="px-4 space-y-6">
{isLoading ? (
<p>Loading usage data...</p>
<p>{t("usage.loading")}</p>
) : error ? (
<p>Error loading usage data: {error.message}</p>
<p>{t("usage.error", { error: error.message })}</p>
) : usageData && usageData.length > 0 ? (
<>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<div className="bg-zinc-50 p-4 rounded-lg space-y-2">
<h3 className="font-medium">Input Tokens</h3>
<h3 className="font-medium">{t("usage.inputTokens")}</h3>
<p className="text-2xl font-bold">
{formatLargeNumber(filteredUsageData.reduce((sum, record) => sum + (record.usage?.input_tokens || 0), 0))}
</p>
</div>
<div className="bg-zinc-50 p-4 rounded-lg space-y-2">
<h3 className="font-medium">Output Tokens</h3>
<h3 className="font-medium">{t("usage.outputTokens")}</h3>
<p className="text-2xl font-bold">
{formatLargeNumber(filteredUsageData.reduce((sum, record) => sum + (record.usage?.output_tokens || 0), 0))}
</p>
</div>
<div className="bg-zinc-50 p-4 rounded-lg space-y-2">
<h3 className="font-medium">Cache Read Tokens</h3>
<h3 className="font-medium">{t("usage.cacheReadTokens")}</h3>
<p className="text-2xl font-bold">
{formatLargeNumber(filteredUsageData.reduce((sum, record) => sum + (record.usage?.cache_read_input_tokens || 0), 0))}
</p>
@@ -70,7 +72,7 @@ export function MonitorPage() {
</div>
</>
) : (
<p>No usage data found.</p>
<p>{t("usage.noData")}</p>
)}
</div>
</div>
+3 -3
View File
@@ -5,7 +5,7 @@ import { ConfigSwitcherPage } from "./pages/ConfigSwitcherPage";
import { ConfigEditorPage } from "./pages/ConfigEditorPage";
import { SettingsPage } from "./pages/SettingsPage";
import { MCPPage } from "./pages/MCPPage";
import { MonitorPage } from "./pages/MonitorPage";
import { UsagePage } from "./pages/UsagePage";
const router = createBrowserRouter([
{
@@ -49,10 +49,10 @@ const router = createBrowserRouter([
),
},
{
path: "monitor",
path: "usage",
element: (
<RouteWrapper>
<MonitorPage />
<UsagePage />
</RouteWrapper>
),
},