mirror of
https://github.com/mfakbar127/Claude-Samurai
synced 2026-06-21 13:57:42 +00:00
i18n
This commit is contained in:
@@ -21,9 +21,9 @@ export function Layout() {
|
||||
label: "MCP"
|
||||
},
|
||||
{
|
||||
to: "/monitor",
|
||||
to: "/usage",
|
||||
icon: ActivityIcon,
|
||||
label: "Monitor"
|
||||
label: "用量"
|
||||
},
|
||||
{
|
||||
to: "/settings",
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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": "全期間"
|
||||
}
|
||||
@@ -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
@@ -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>
|
||||
),
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user