mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-20 22:22:57 +03:00
committed by
GitHub
parent
08e18867fd
commit
3d75fb3fae
@@ -37,126 +37,69 @@ function GenericProviderIcon({ size }: { size: number }) {
|
||||
}
|
||||
|
||||
const KNOWN_PNGS = new Set([
|
||||
"agentrouter",
|
||||
"aimlapi",
|
||||
"alibaba",
|
||||
"alicode-intl",
|
||||
"alicode",
|
||||
"anthropic-m",
|
||||
"anthropic",
|
||||
"antigravity",
|
||||
"bailian-coding-plan",
|
||||
"blackbox",
|
||||
"brave-search",
|
||||
"brave",
|
||||
"cerebras",
|
||||
"claude",
|
||||
"cline",
|
||||
"codex",
|
||||
"cohere",
|
||||
"continue",
|
||||
"copilot",
|
||||
"cursor",
|
||||
"deepgram",
|
||||
"deepseek",
|
||||
"droid",
|
||||
"exa-search",
|
||||
"fireworks",
|
||||
"gemini-cli",
|
||||
"gemini",
|
||||
"github",
|
||||
"glm",
|
||||
"glmt",
|
||||
"groq",
|
||||
"ironclaw",
|
||||
"kilo-gateway",
|
||||
"kilocode",
|
||||
"kimi-coding-apikey",
|
||||
"kimi-coding",
|
||||
"kimi",
|
||||
"kiro",
|
||||
"longcat",
|
||||
"minimax-cn",
|
||||
"minimax",
|
||||
"mistral",
|
||||
"kie",
|
||||
"nanobot",
|
||||
"nebius",
|
||||
"nvidia",
|
||||
"oai-cc",
|
||||
"oai-r",
|
||||
"ollama-cloud",
|
||||
"openai",
|
||||
"openclaw",
|
||||
"openrouter",
|
||||
"perplexity-search",
|
||||
"perplexity",
|
||||
"pollinations",
|
||||
"qwen",
|
||||
"roo",
|
||||
"serper-search",
|
||||
"serper",
|
||||
"siliconflow",
|
||||
"tavily-search",
|
||||
"tavily",
|
||||
"together",
|
||||
"xai",
|
||||
"zeroclaw",
|
||||
"aws-polly",
|
||||
"blackbox-web",
|
||||
"cliproxyapi",
|
||||
"databricks",
|
||||
"empower",
|
||||
"gigachat",
|
||||
"gitlab-duo",
|
||||
"gitlab",
|
||||
"heroku",
|
||||
"lemonade",
|
||||
"linkup-search",
|
||||
"llamafile",
|
||||
"llamagate",
|
||||
"maritalk",
|
||||
"modal",
|
||||
"nanogpt",
|
||||
"nscale",
|
||||
"oci",
|
||||
"ovhcloud",
|
||||
"piapi",
|
||||
"poe",
|
||||
"predibase",
|
||||
"qoder",
|
||||
"recraft",
|
||||
"reka",
|
||||
"runwayml",
|
||||
"triton",
|
||||
"venice",
|
||||
"voyage-ai",
|
||||
"wandb",
|
||||
"youcom-search",
|
||||
]);
|
||||
const KNOWN_SVGS = new Set([
|
||||
"apikey",
|
||||
"assemblyai",
|
||||
"brave",
|
||||
"brave-search",
|
||||
"cartesia",
|
||||
"cloudflare-ai",
|
||||
"comfyui",
|
||||
"elevenlabs",
|
||||
"exa-search",
|
||||
"exa",
|
||||
"huggingface",
|
||||
"hyperbolic",
|
||||
"clarifai",
|
||||
"docker-model-runner",
|
||||
"droid",
|
||||
"gemini-cli",
|
||||
"gitlab",
|
||||
"gitlab-duo",
|
||||
"inworld",
|
||||
"nanobanana",
|
||||
"kiro",
|
||||
"kilo-gateway",
|
||||
"kilocode",
|
||||
"modal",
|
||||
"nlpcloud",
|
||||
"oauth",
|
||||
"opencode-go",
|
||||
"opencode-zen",
|
||||
"oci",
|
||||
"opencode",
|
||||
"playht",
|
||||
"puter",
|
||||
"qianfan",
|
||||
"sap",
|
||||
"scaleway",
|
||||
"sdwebui",
|
||||
"serper-search",
|
||||
"searxng-search",
|
||||
"synthetic",
|
||||
"vertex",
|
||||
"windsurf",
|
||||
"zai",
|
||||
"wandb",
|
||||
"youcom-search",
|
||||
]);
|
||||
|
||||
const ProviderIcon = memo(function ProviderIcon({
|
||||
|
||||
@@ -11,6 +11,7 @@ interface ToggleProps {
|
||||
size?: "sm" | "md" | "lg";
|
||||
className?: string;
|
||||
title?: string;
|
||||
ariaLabel?: string;
|
||||
}
|
||||
|
||||
export default function Toggle({
|
||||
@@ -21,6 +22,8 @@ export default function Toggle({
|
||||
disabled = false,
|
||||
size = "md",
|
||||
className,
|
||||
title,
|
||||
ariaLabel,
|
||||
}: ToggleProps) {
|
||||
const sizes = {
|
||||
sm: {
|
||||
@@ -58,7 +61,8 @@ export default function Toggle({
|
||||
type="button"
|
||||
role="switch"
|
||||
aria-checked={checked}
|
||||
aria-label={!label ? description || "Toggle" : undefined}
|
||||
aria-label={ariaLabel || label || description || title || "Toggle"}
|
||||
title={title}
|
||||
disabled={disabled}
|
||||
onClick={handleClick}
|
||||
className={cn(
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
ProviderCostDonut,
|
||||
ModelOverTimeChart,
|
||||
ProviderTable,
|
||||
ServiceTierBreakdown,
|
||||
ApiKeyFilterDropdown,
|
||||
CustomRangePicker,
|
||||
} from "./analytics";
|
||||
@@ -61,16 +62,15 @@ export default function UsageAnalytics() {
|
||||
setError(null);
|
||||
|
||||
// Update available keys from unfiltered data (only when no filter is active).
|
||||
// Use apiKeyName as the stable identifier — it is always populated
|
||||
// for every OmniRoute API key regardless of the downstream provider.
|
||||
if (selectedApiKeys.length === 0 && data.byApiKey?.length > 0) {
|
||||
const seen = new Set<string>();
|
||||
const keys: { id: string; name: string }[] = [];
|
||||
for (const k of data.byApiKey) {
|
||||
const id = k.apiKeyId || k.apiKeyName || "unknown";
|
||||
const name = k.apiKeyName || k.apiKeyId || "unknown";
|
||||
if (seen.has(name)) continue;
|
||||
seen.add(name);
|
||||
keys.push({ id: name, name });
|
||||
if (seen.has(id)) continue;
|
||||
seen.add(id);
|
||||
keys.push({ id, name });
|
||||
}
|
||||
setAvailableApiKeys(keys);
|
||||
}
|
||||
@@ -311,10 +311,10 @@ export default function UsageAnalytics() {
|
||||
color: "text-violet-500",
|
||||
},
|
||||
{
|
||||
icon: "swap_horiz",
|
||||
label: "Fallback Rate",
|
||||
value: `${Number(s.fallbackRatePct || 0).toFixed(1)}%`,
|
||||
color: "text-amber-500",
|
||||
icon: "bolt",
|
||||
label: "Fast Requests",
|
||||
value: fmt(s.fastRequests || 0),
|
||||
color: "text-sky-500",
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -331,6 +331,12 @@ export default function UsageAnalytics() {
|
||||
value: `${providerDiversity.toFixed(1)}%`,
|
||||
color: "text-sky-500",
|
||||
},
|
||||
{
|
||||
icon: "swap_horiz",
|
||||
label: "Fallback Rate",
|
||||
value: `${Number(s.fallbackRatePct || 0).toFixed(1)}%`,
|
||||
color: "text-amber-500",
|
||||
},
|
||||
],
|
||||
},
|
||||
]}
|
||||
@@ -351,6 +357,9 @@ export default function UsageAnalytics() {
|
||||
<ProviderCostDonut byProvider={analytics?.byProvider} />
|
||||
</div>
|
||||
|
||||
{/* Fast / Standard service tier split */}
|
||||
<ServiceTierBreakdown byServiceTier={analytics?.byServiceTier} summary={s} />
|
||||
|
||||
{/* Model Usage Over Time (stacked area) */}
|
||||
<ModelOverTimeChart
|
||||
dailyByModel={analytics?.dailyByModel}
|
||||
|
||||
@@ -1081,6 +1081,71 @@ export function ModelTable({ byModel, summary }) {
|
||||
);
|
||||
}
|
||||
|
||||
export function ServiceTierBreakdown({ byServiceTier, summary }) {
|
||||
const data = useMemo(() => byServiceTier || [], [byServiceTier]);
|
||||
const totalRequests = Number(summary?.totalRequests || 0);
|
||||
const totalCost = Number(summary?.totalCost || 0);
|
||||
|
||||
if (!data.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Card className="overflow-hidden">
|
||||
<div className="p-4 border-b border-border flex items-center justify-between gap-3">
|
||||
<h3 className="text-sm font-semibold text-text-muted uppercase tracking-wider">
|
||||
Service Tier
|
||||
</h3>
|
||||
<span className="text-[11px] text-text-muted">Fast / Standard cost split</span>
|
||||
</div>
|
||||
<div className="divide-y divide-border">
|
||||
{data.map((tier) => {
|
||||
const isFast = tier.serviceTier === "priority";
|
||||
const requestPct =
|
||||
totalRequests > 0
|
||||
? ((Number(tier.requests || 0) / totalRequests) * 100).toFixed(1)
|
||||
: "0";
|
||||
const costPct =
|
||||
totalCost > 0 ? ((Number(tier.cost || 0) / totalCost) * 100).toFixed(1) : "0";
|
||||
return (
|
||||
<div key={tier.serviceTier} className="p-4 flex flex-col gap-3">
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<span
|
||||
className={`material-symbols-outlined text-[18px] ${
|
||||
isFast ? "text-sky-500" : "text-text-muted"
|
||||
}`}
|
||||
>
|
||||
{isFast ? "bolt" : "speed"}
|
||||
</span>
|
||||
<div>
|
||||
<div className="text-sm font-semibold text-text-main">{tier.label}</div>
|
||||
<div className="text-xs text-text-muted">
|
||||
{fmtFull(tier.requests)} requests · {fmt(tier.totalTokens)} tokens
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<div className="font-mono text-sm font-semibold text-amber-500">
|
||||
{fmtCost(tier.cost)}
|
||||
</div>
|
||||
<div className="text-xs text-text-muted">{costPct}% of cost</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="h-1.5 rounded-full bg-black/5 dark:bg-white/10 overflow-hidden">
|
||||
<div
|
||||
className={`h-full rounded-full ${isFast ? "bg-sky-500" : "bg-text-muted/50"}`}
|
||||
style={{ width: `${requestPct}%` }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
// ── UsageDetail ────────────────────────────────────────────────────────────
|
||||
|
||||
export function UsageDetail({ summary }) {
|
||||
|
||||
@@ -25,6 +25,7 @@ export {
|
||||
ProviderCostDonut,
|
||||
ModelOverTimeChart,
|
||||
ProviderTable,
|
||||
ServiceTierBreakdown,
|
||||
} from "./charts";
|
||||
|
||||
export { default as ApiKeyFilterDropdown } from "./ApiKeyFilterDropdown";
|
||||
|
||||
@@ -75,6 +75,8 @@ import KimiColorIcon from "@lobehub/icons/es/Kimi/components/Color";
|
||||
import KimiMonoIcon from "@lobehub/icons/es/Kimi/components/Mono";
|
||||
import LambdaMonoIcon from "@lobehub/icons/es/Lambda/components/Mono";
|
||||
import LmStudioMonoIcon from "@lobehub/icons/es/LmStudio/components/Mono";
|
||||
import LongCatColorIcon from "@lobehub/icons/es/LongCat/components/Color";
|
||||
import LongCatMonoIcon from "@lobehub/icons/es/LongCat/components/Mono";
|
||||
import MetaColorIcon from "@lobehub/icons/es/Meta/components/Color";
|
||||
import MetaMonoIcon from "@lobehub/icons/es/Meta/components/Mono";
|
||||
import MetaAIColorIcon from "@lobehub/icons/es/MetaAI/components/Color";
|
||||
@@ -104,12 +106,14 @@ import PerplexityColorIcon from "@lobehub/icons/es/Perplexity/components/Color";
|
||||
import PerplexityMonoIcon from "@lobehub/icons/es/Perplexity/components/Mono";
|
||||
import PoeColorIcon from "@lobehub/icons/es/Poe/components/Color";
|
||||
import PoeMonoIcon from "@lobehub/icons/es/Poe/components/Mono";
|
||||
import PollinationsMonoIcon from "@lobehub/icons/es/Pollinations/components/Mono";
|
||||
import QoderColorIcon from "@lobehub/icons/es/Qoder/components/Color";
|
||||
import QoderMonoIcon from "@lobehub/icons/es/Qoder/components/Mono";
|
||||
import QwenColorIcon from "@lobehub/icons/es/Qwen/components/Color";
|
||||
import QwenMonoIcon from "@lobehub/icons/es/Qwen/components/Mono";
|
||||
import RecraftMonoIcon from "@lobehub/icons/es/Recraft/components/Mono";
|
||||
import ReplicateMonoIcon from "@lobehub/icons/es/Replicate/components/Mono";
|
||||
import RooCodeMonoIcon from "@lobehub/icons/es/RooCode/components/Mono";
|
||||
import RunwayMonoIcon from "@lobehub/icons/es/Runway/components/Mono";
|
||||
import SambaNovaColorIcon from "@lobehub/icons/es/SambaNova/components/Color";
|
||||
import SambaNovaMonoIcon from "@lobehub/icons/es/SambaNova/components/Mono";
|
||||
@@ -139,6 +143,7 @@ import VolcengineColorIcon from "@lobehub/icons/es/Volcengine/components/Color";
|
||||
import VolcengineMonoIcon from "@lobehub/icons/es/Volcengine/components/Mono";
|
||||
import VoyageColorIcon from "@lobehub/icons/es/Voyage/components/Color";
|
||||
import VoyageMonoIcon from "@lobehub/icons/es/Voyage/components/Mono";
|
||||
import WindsurfMonoIcon from "@lobehub/icons/es/Windsurf/components/Mono";
|
||||
import WorkersAIColorIcon from "@lobehub/icons/es/WorkersAI/components/Color";
|
||||
import WorkersAIMonoIcon from "@lobehub/icons/es/WorkersAI/components/Mono";
|
||||
import XAIMonoIcon from "@lobehub/icons/es/XAI/components/Mono";
|
||||
@@ -208,6 +213,7 @@ const LOBE_ICON_COMPONENTS = {
|
||||
Kimi: { mono: KimiMonoIcon, color: KimiColorIcon },
|
||||
Lambda: { mono: LambdaMonoIcon },
|
||||
LmStudio: { mono: LmStudioMonoIcon },
|
||||
LongCat: { mono: LongCatMonoIcon, color: LongCatColorIcon },
|
||||
Meta: { mono: MetaMonoIcon, color: MetaColorIcon },
|
||||
MetaAI: { mono: MetaAIMonoIcon, color: MetaAIColorIcon },
|
||||
Minimax: { mono: MinimaxMonoIcon, color: MinimaxColorIcon },
|
||||
@@ -226,10 +232,12 @@ const LOBE_ICON_COMPONENTS = {
|
||||
OpenRouter: { mono: OpenRouterMonoIcon },
|
||||
Perplexity: { mono: PerplexityMonoIcon, color: PerplexityColorIcon },
|
||||
Poe: { mono: PoeMonoIcon, color: PoeColorIcon },
|
||||
Pollinations: { mono: PollinationsMonoIcon },
|
||||
Qoder: { mono: QoderMonoIcon, color: QoderColorIcon },
|
||||
Qwen: { mono: QwenMonoIcon, color: QwenColorIcon },
|
||||
Recraft: { mono: RecraftMonoIcon },
|
||||
Replicate: { mono: ReplicateMonoIcon },
|
||||
RooCode: { mono: RooCodeMonoIcon },
|
||||
Runway: { mono: RunwayMonoIcon },
|
||||
SambaNova: { mono: SambaNovaMonoIcon, color: SambaNovaColorIcon },
|
||||
SearchApi: { mono: SearchApiMonoIcon },
|
||||
@@ -247,6 +255,7 @@ const LOBE_ICON_COMPONENTS = {
|
||||
Vllm: { mono: VllmMonoIcon, color: VllmColorIcon },
|
||||
Volcengine: { mono: VolcengineMonoIcon, color: VolcengineColorIcon },
|
||||
Voyage: { mono: VoyageMonoIcon, color: VoyageColorIcon },
|
||||
Windsurf: { mono: WindsurfMonoIcon },
|
||||
WorkersAI: { mono: WorkersAIMonoIcon, color: WorkersAIColorIcon },
|
||||
XAI: { mono: XAIMonoIcon },
|
||||
XiaomiMiMo: { mono: XiaomiMiMoMonoIcon },
|
||||
@@ -275,6 +284,7 @@ const LOBE_PROVIDER_ALIASES = {
|
||||
bfl: "Bfl",
|
||||
"black-forest-labs": "Bfl",
|
||||
cerebras: "Cerebras",
|
||||
"chatgpt-web": "OpenAI",
|
||||
claude: "ClaudeCode",
|
||||
cline: "Cline",
|
||||
cloudflare: "Cloudflare",
|
||||
@@ -325,6 +335,7 @@ const LOBE_PROVIDER_ALIASES = {
|
||||
"lambda-ai": "Lambda",
|
||||
"lm-studio": "LmStudio",
|
||||
lmstudio: "LmStudio",
|
||||
longcat: "LongCat",
|
||||
"meta-llama": "Meta",
|
||||
minimax: "Minimax",
|
||||
"minimax-cn": "Minimax",
|
||||
@@ -352,10 +363,12 @@ const LOBE_PROVIDER_ALIASES = {
|
||||
"perplexity-search": "Perplexity",
|
||||
"perplexity-web": "Perplexity",
|
||||
poe: "Poe",
|
||||
pollinations: "Pollinations",
|
||||
qoder: "Qoder",
|
||||
qwen: "Qwen",
|
||||
recraft: "Recraft",
|
||||
replicate: "Replicate",
|
||||
roo: "RooCode",
|
||||
runwayml: "Runway",
|
||||
sambanova: "SambaNova",
|
||||
sdwebui: "Automatic",
|
||||
@@ -369,6 +382,7 @@ const LOBE_PROVIDER_ALIASES = {
|
||||
"tavily-search": "Tavily",
|
||||
together: "Together",
|
||||
topaz: "TopazLabs",
|
||||
triton: "Nvidia",
|
||||
upstage: "Upstage",
|
||||
v0: "V0",
|
||||
"v0-vercel": "V0",
|
||||
@@ -382,6 +396,7 @@ const LOBE_PROVIDER_ALIASES = {
|
||||
voyage: "Voyage",
|
||||
"voyage-ai": "Voyage",
|
||||
watsonx: "IBM",
|
||||
windsurf: "Windsurf",
|
||||
"workers-ai": "WorkersAI",
|
||||
workersai: "WorkersAI",
|
||||
xai: "XAI",
|
||||
|
||||
Reference in New Issue
Block a user