Fix bug
This commit is contained in:
parent
8640503b36
commit
9708541f6d
13 changed files with 471 additions and 13 deletions
|
|
@ -38,12 +38,14 @@ const getPageInfo = (pathname) => {
|
|||
return {
|
||||
title: "Providers",
|
||||
description: "Manage your AI provider connections",
|
||||
icon: "dns",
|
||||
breadcrumbs: [],
|
||||
};
|
||||
if (pathname.includes("/combos"))
|
||||
return {
|
||||
title: "Combos",
|
||||
description: "Model combos with fallback",
|
||||
icon: "layers",
|
||||
breadcrumbs: [],
|
||||
};
|
||||
if (pathname.includes("/usage"))
|
||||
|
|
@ -51,48 +53,70 @@ const getPageInfo = (pathname) => {
|
|||
title: "Usage & Analytics",
|
||||
description:
|
||||
"Monitor your API usage, token consumption, and request logs",
|
||||
icon: "bar_chart",
|
||||
breadcrumbs: [],
|
||||
};
|
||||
if (pathname.includes("/quota"))
|
||||
return {
|
||||
title: "Quota Tracker",
|
||||
description: "Track and manage your API quota limits",
|
||||
icon: "data_usage",
|
||||
breadcrumbs: [],
|
||||
};
|
||||
if (pathname.includes("/mitm"))
|
||||
return {
|
||||
title: "MITM Proxy",
|
||||
description: "Intercept CLI tool traffic and route through 9Router",
|
||||
icon: "security",
|
||||
breadcrumbs: [],
|
||||
};
|
||||
if (pathname.includes("/cli-tools"))
|
||||
return {
|
||||
title: "CLI Tools",
|
||||
description: "Configure CLI tools",
|
||||
icon: "terminal",
|
||||
breadcrumbs: [],
|
||||
};
|
||||
if (pathname.includes("/proxy-pools"))
|
||||
return {
|
||||
title: "Proxy Pools",
|
||||
description: "Manage your proxy pool configurations",
|
||||
icon: "lan",
|
||||
breadcrumbs: [],
|
||||
};
|
||||
if (pathname.includes("/endpoint"))
|
||||
return {
|
||||
title: "Endpoint",
|
||||
description: "API endpoint configuration",
|
||||
icon: "api",
|
||||
breadcrumbs: [],
|
||||
};
|
||||
if (pathname.includes("/profile"))
|
||||
return {
|
||||
title: "Settings",
|
||||
description: "Manage your preferences",
|
||||
icon: "settings",
|
||||
breadcrumbs: [],
|
||||
};
|
||||
if (pathname.includes("/translator"))
|
||||
return {
|
||||
title: "Translator",
|
||||
description: "Debug translation flow between formats",
|
||||
icon: "translate",
|
||||
breadcrumbs: [],
|
||||
};
|
||||
if (pathname.includes("/console-log"))
|
||||
return {
|
||||
title: "Console Log",
|
||||
description: "Live server console output",
|
||||
icon: "monitor",
|
||||
breadcrumbs: [],
|
||||
};
|
||||
if (pathname === "/dashboard")
|
||||
return {
|
||||
title: "Endpoint",
|
||||
description: "API endpoint configuration",
|
||||
icon: "api",
|
||||
breadcrumbs: [],
|
||||
};
|
||||
return { title: "", description: "", breadcrumbs: [] };
|
||||
|
|
@ -104,7 +128,7 @@ export default function Header({ onMenuClick, showMenuButton = true }) {
|
|||
|
||||
// Memoize page info to prevent unnecessary recalculations
|
||||
const pageInfo = useMemo(() => getPageInfo(pathname), [pathname]);
|
||||
const { title, description, breadcrumbs } = pageInfo;
|
||||
const { title, description, icon, breadcrumbs } = pageInfo;
|
||||
|
||||
const handleLogout = async () => {
|
||||
try {
|
||||
|
|
@ -174,9 +198,16 @@ export default function Header({ onMenuClick, showMenuButton = true }) {
|
|||
</div>
|
||||
) : title ? (
|
||||
<div>
|
||||
<h1 className="text-2xl font-semibold text-text-main tracking-tight">
|
||||
{translate(title)}
|
||||
</h1>
|
||||
<div className="flex items-center gap-2">
|
||||
{icon && (
|
||||
<span className="material-symbols-outlined text-primary text-2xl">
|
||||
{icon}
|
||||
</span>
|
||||
)}
|
||||
<h1 className="text-2xl font-semibold tracking-tight">
|
||||
{translate(title)}
|
||||
</h1>
|
||||
</div>
|
||||
{description && (
|
||||
<p className="text-sm text-text-muted">
|
||||
{translate(description)}
|
||||
|
|
|
|||
|
|
@ -191,6 +191,7 @@ export default function UsageStats() {
|
|||
const [loading, setLoading] = useState(true);
|
||||
const [fetching, setFetching] = useState(false);
|
||||
const [tableView, setTableView] = useState("model");
|
||||
const [viewMode, setViewMode] = useState("costs");
|
||||
const [providers, setProviders] = useState([]);
|
||||
const [period, setPeriod] = useState("7d");
|
||||
|
||||
|
|
@ -236,14 +237,14 @@ export default function UsageStats() {
|
|||
es.onmessage = (e) => {
|
||||
try {
|
||||
const data = JSON.parse(e.data);
|
||||
// Only update real-time fields from SSE, keep filtered stats intact
|
||||
setStats((prev) => prev ? {
|
||||
...prev,
|
||||
// Always merge only real-time fields, never overwrite full stats from REST
|
||||
setStats((prev) => ({
|
||||
...(prev || {}),
|
||||
activeRequests: data.activeRequests,
|
||||
recentRequests: data.recentRequests,
|
||||
errorProvider: data.errorProvider,
|
||||
pending: data.pending,
|
||||
} : data);
|
||||
}));
|
||||
setLoading(false);
|
||||
} catch (err) {
|
||||
console.error("[SSE CLIENT] parse error:", err);
|
||||
|
|
@ -443,6 +444,20 @@ export default function UsageStats() {
|
|||
<option key={opt.value} value={opt.value}>{opt.label}</option>
|
||||
))}
|
||||
</select>
|
||||
<div className="flex items-center gap-1 bg-bg-subtle rounded-lg p-1 border border-border">
|
||||
<button
|
||||
onClick={() => setViewMode("costs")}
|
||||
className={`px-3 py-1 rounded-md text-sm font-medium transition-colors ${viewMode === "costs" ? "bg-primary text-white shadow-sm" : "text-text-muted hover:text-text hover:bg-bg-hover"}`}
|
||||
>
|
||||
Costs
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setViewMode("tokens")}
|
||||
className={`px-3 py-1 rounded-md text-sm font-medium transition-colors ${viewMode === "tokens" ? "bg-primary text-white shadow-sm" : "text-text-muted hover:text-text hover:bg-bg-hover"}`}
|
||||
>
|
||||
Tokens
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{loading ? spinner : activeTableConfig && (
|
||||
<UsageTable
|
||||
|
|
@ -453,6 +468,7 @@ export default function UsageStats() {
|
|||
sortBy={sortBy}
|
||||
sortOrder={sortOrder}
|
||||
onToggleSort={toggleSort}
|
||||
viewMode={viewMode}
|
||||
storageKey={activeTableConfig.storageKey}
|
||||
renderSummaryCells={activeTableConfig.renderSummaryCells}
|
||||
renderDetailCells={activeTableConfig.renderDetailCells}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ export const FREE_PROVIDERS = {
|
|||
"gemini-cli": { id: "gemini-cli", alias: "gc", name: "Gemini CLI", icon: "terminal", color: "#4285F4", deprecated: true, deprecationNotice: "Google has tightened Gemini CLI abuse detection and restricted Pro models to paid accounts (Mar 25, 2026). Using this provider may violate ToS and risk account bans." },
|
||||
// gitlab: { id: "gitlab", alias: "gl", name: "GitLab Duo", icon: "code", color: "#FC6D26" },
|
||||
// codebuddy: { id: "codebuddy", alias: "cb", name: "CodeBuddy", icon: "smart_toy", color: "#006EFF" },
|
||||
// qoder: { id: "qoder", alias: "qd", name: "Qoder AI", icon: "water_drop", color: "#EC4899" },
|
||||
iflow: { id: "iflow", alias: "if", name: "iFlow AI", icon: "water_drop", color: "#6366F1" },
|
||||
};
|
||||
|
||||
|
|
@ -14,14 +15,14 @@ export const FREE_PROVIDERS = {
|
|||
export const FREE_TIER_PROVIDERS = {
|
||||
openrouter: { id: "openrouter", alias: "openrouter", name: "OpenRouter", icon: "router", color: "#F97316", textIcon: "OR", passthroughModels: true, website: "https://openrouter.ai", notice: { text: "Free tier: 27+ free models, no credit card needed, 200 req/day. After $10 credit: 1,000 req/day.", apiKeyUrl: "https://openrouter.ai/settings/keys" }, modelsFetcher: { url: "https://openrouter.ai/api/v1/models", type: "openrouter-free" } },
|
||||
nvidia: { id: "nvidia", alias: "nvidia", name: "NVIDIA NIM", icon: "developer_board", color: "#76B900", textIcon: "NV", website: "https://developer.nvidia.com/nim", notice: { text: "Free access for NVIDIA Developer Program members (prototyping & testing).", apiKeyUrl: "https://build.nvidia.com/settings/api-keys" } },
|
||||
ollama: { id: "ollama", alias: "ollama", name: "Ollama Cloud", icon: "cloud", color: "#ffffffff", textIcon: "OL", website: "https://ollama.com", notice: { text: "Free tier: light usage, 1 cloud model at a time (limits reset every 5h & 7d). Pro $20/mo · Max $100/mo.", apiKeyUrl: "https://ollama.com/settings/api-keys" } },
|
||||
ollama: { id: "ollama", alias: "ollama", name: "Ollama Cloud", icon: "cloud", color: "#ffffffff", textIcon: "OL", website: "https://ollama.com", notice: { text: "Free tier: light usage, 1 cloud model at a time (limits reset every 5h & 7d). Pro $20/mo · Max $100/mo.", apiKeyUrl: "https://ollama.com/settings/keys" } },
|
||||
vertex: { id: "vertex", alias: "vx", name: "Vertex AI", icon: "cloud", color: "#4285F4", textIcon: "VX", website: "https://cloud.google.com/vertex-ai", notice: { text: "New Google Cloud accounts get $300 free credits. Requires GCP project + Service Account with Vertex AI API enabled.", apiKeyUrl: "https://console.cloud.google.com/iam-admin/serviceaccounts" } },
|
||||
};
|
||||
|
||||
// OAuth Providers
|
||||
export const OAUTH_PROVIDERS = {
|
||||
claude: { id: "claude", alias: "cc", name: "Claude Code", icon: "smart_toy", color: "#D97757" },
|
||||
antigravity: { id: "antigravity", alias: "ag", name: "Antigravity", icon: "rocket_launch", color: "#F59E0B" },
|
||||
antigravity: { id: "antigravity", alias: "ag", name: "Antigravity", icon: "rocket_launch", color: "#F59E0B", deprecated: true, deprecationNotice: "Antigravity has tightened abuse detection and restricted model access. Using this provider may violate ToS and risk account bans." },
|
||||
codex: { id: "codex", alias: "cx", name: "OpenAI Codex", icon: "code", color: "#3B82F6" },
|
||||
github: { id: "github", alias: "gh", name: "GitHub Copilot", icon: "code", color: "#333333" },
|
||||
cursor: { id: "cursor", alias: "cu", name: "Cursor IDE", icon: "edit_note", color: "#00D4AA" },
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue