Merge pull request #2315 from diegosouzapw/refactor/pages

refactor(dashboard): pages overhaul, providers UX, OAuth token refresh fixes [deferred in develop]
This commit is contained in:
Diego Rodrigues de Sa e Souza
2026-05-17 01:44:30 -03:00
committed by GitHub
133 changed files with 8197 additions and 3515 deletions

View File

@@ -145,7 +145,7 @@ const nextConfig = {
"process",
],
transpilePackages: ["@omniroute/open-sse", "@lobehub/icons"],
allowedDevOrigins: ["localhost", "127.0.0.1", "192.168.*"],
allowedDevOrigins: ["localhost", "127.0.0.1", "192.168.0.250"],
typescript: {
// TODO: Re-enable after fixing all sub-component useTranslations scope issues
ignoreBuildErrors: true,

View File

@@ -919,6 +919,31 @@ export const REGISTRY: Record<string, RegistryEntry> = {
],
},
opencode: {
id: "opencode",
alias: "oc",
format: "openai",
executor: "opencode",
baseUrl: "https://opencode.ai/zen/v1",
modelsUrl: "https://opencode.ai/zen/v1/models",
authType: "apikey",
authHeader: "Authorization",
authPrefix: "Bearer",
passthroughModels: true,
defaultContextLength: 200000,
models: [
{ id: "big-pickle", name: "Big Pickle" },
{ id: "minimax-m2.5-free", name: "MiniMax M2.5 Free", contextLength: 204800 },
{ id: "ling-2.6-1t-free", name: "Ling 2.6 Free", contextLength: 262000 },
{
id: "trinity-large-preview-free",
name: "Trinity Large Preview Free",
contextLength: 131000,
},
{ id: "nemotron-3-super-free", name: "Nemotron 3 Super Free", contextLength: 1000000 },
],
},
"opencode-go": {
id: "opencode-go",
alias: "opencode-go",

View File

@@ -1193,12 +1193,16 @@ export class CodexExecutor extends BaseExecutor {
return null;
}
const result = await getAccessToken("codex", credentials, log);
if (!result || result.error) {
if (!result) {
log?.warn?.("TOKEN_REFRESH", "Codex: token refresh failed — re-authentication required");
return null;
}
if (result.error) {
log?.warn?.(
"TOKEN_REFRESH",
`Codex: token refresh failed${result?.error ? ` (${result.error})` : ""} — re-authentication required`
`Codex: token refresh failed (${result.error}) — re-authentication required`
);
return null;
return result;
}
return result;
}

View File

@@ -14,7 +14,7 @@ import { resolveStreamReadinessTimeout } from "../utils/streamReadinessPolicy.ts
import { createStreamController, pipeWithDisconnect } from "../utils/streamHandler.ts";
import { createSseHeartbeatTransform, shapeForClientFormat } from "../utils/sseHeartbeat.ts";
import { addBufferToUsage, filterUsageForFormat, estimateUsage } from "../utils/usageTracking.ts";
import { refreshWithRetry } from "../services/tokenRefresh.ts";
import { refreshWithRetry, isUnrecoverableRefreshError } from "../services/tokenRefresh.ts";
import { createRequestLogger } from "../utils/requestLogger.ts";
import { getModelTargetFormat, PROVIDER_ID_TO_ALIAS } from "../config/providerModels.ts";
import {
@@ -3530,6 +3530,9 @@ export async function handleChatCore({
}
} else {
log?.warn?.("TOKEN", `${provider.toUpperCase()} | refresh failed`);
if (isUnrecoverableRefreshError(newCredentials) && onCredentialsRefreshed) {
await onCredentialsRefreshed({ testStatus: "expired", isActive: false });
}
}
}

View File

@@ -349,11 +349,20 @@ export async function refreshClaudeOAuthToken(refreshToken, log, proxyConfig: un
);
if (!response.ok) {
const errorText = await response.text();
let errorBody: { error?: string; error_description?: string } = {};
try {
errorBody = await response.json();
} catch {
const text = await response.text().catch(() => "unknown");
errorBody = { error: text };
}
log?.error?.("TOKEN_REFRESH", "Failed to refresh Claude OAuth token", {
status: response.status,
error: errorText,
error: errorBody,
});
if (errorBody.error === "invalid_grant" || errorBody.error === "invalid_request") {
return { error: errorBody.error, code: `http_${response.status}` };
}
return null;
}
@@ -1280,6 +1289,13 @@ export async function refreshWithRetry(
try {
const result = await withTimeout(refreshFn, REFRESH_TIMEOUT_MS);
if (isUnrecoverableRefreshError(result)) {
log?.warn?.(
"TOKEN_REFRESH",
`Unrecoverable refresh error for ${provider}: ${result.error} — skipping retries`
);
return result;
}
if (result) {
recordSuccess(provider);
return result;

67
package-lock.json generated
View File

@@ -18,6 +18,7 @@
"@monaco-editor/react": "^4.7.0",
"@ngrok/ngrok": "^1.7.0",
"@swc/helpers": "0.5.21",
"@xyflow/react": "^12.10.2",
"axios": "^1.16.1",
"bcryptjs": "^3.0.3",
"better-sqlite3": "^12.10.0",
@@ -5339,6 +5340,66 @@
"dev": true,
"license": "MIT"
},
"node_modules/@xyflow/react": {
"version": "12.10.2",
"resolved": "https://registry.npmjs.org/@xyflow/react/-/react-12.10.2.tgz",
"integrity": "sha512-CgIi6HwlcHXwlkTpr0fxLv/0sRVNZ8IdwKLzzeCscaYBwpvfcH1QFOCeaTCuEn1FQEs/B8CjnTSjhs8udgmBgQ==",
"license": "MIT",
"dependencies": {
"@xyflow/system": "0.0.76",
"classcat": "^5.0.3",
"zustand": "^4.4.0"
},
"peerDependencies": {
"react": ">=17",
"react-dom": ">=17"
}
},
"node_modules/@xyflow/react/node_modules/zustand": {
"version": "4.5.7",
"resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.7.tgz",
"integrity": "sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==",
"license": "MIT",
"dependencies": {
"use-sync-external-store": "^1.2.2"
},
"engines": {
"node": ">=12.7.0"
},
"peerDependencies": {
"@types/react": ">=16.8",
"immer": ">=9.0.6",
"react": ">=16.8"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"immer": {
"optional": true
},
"react": {
"optional": true
}
}
},
"node_modules/@xyflow/system": {
"version": "0.0.76",
"resolved": "https://registry.npmjs.org/@xyflow/system/-/system-0.0.76.tgz",
"integrity": "sha512-hvwvnRS1B3REwVDlWexsq7YQaPZeG3/mKo1jv38UmnpWmxihp14bW6VtEOuHEwJX2FvzFw8k77LyKSk/wiZVNA==",
"license": "MIT",
"dependencies": {
"@types/d3-drag": "^3.0.7",
"@types/d3-interpolate": "^3.0.4",
"@types/d3-selection": "^3.0.10",
"@types/d3-transition": "^3.0.8",
"@types/d3-zoom": "^3.0.8",
"d3-drag": "^3.0.0",
"d3-interpolate": "^3.0.1",
"d3-selection": "^3.0.0",
"d3-zoom": "^3.0.0"
}
},
"node_modules/accepts": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz",
@@ -6443,6 +6504,12 @@
"integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==",
"license": "ISC"
},
"node_modules/classcat": {
"version": "5.0.5",
"resolved": "https://registry.npmjs.org/classcat/-/classcat-5.0.5.tgz",
"integrity": "sha512-JhZUT7JFcQy/EzW605k/ktHtncoo9vnyW/2GspNYwFlN1C/WmjuV/xtS04e9SOkL2sTdw0VAZ2UGCcQ9lR6p6w==",
"license": "MIT"
},
"node_modules/cli-boxes": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz",

View File

@@ -135,6 +135,7 @@
"@monaco-editor/react": "^4.7.0",
"@ngrok/ngrok": "^1.7.0",
"@swc/helpers": "0.5.21",
"@xyflow/react": "^12.10.2",
"axios": "^1.16.1",
"bcryptjs": "^3.0.3",
"better-sqlite3": "^12.10.0",

View File

@@ -10,6 +10,17 @@ import { createOmnirouteWsBridge } from "./v1-ws-bridge.mjs";
import { createResponsesWsProxy } from "./responses-ws-proxy.mjs";
import { randomUUID } from "node:crypto";
// Pre-read DATA_DIR from local .env before bootstrap resolves paths
if (!process.env.DATA_DIR) {
try {
const raw = fs.readFileSync(path.join(process.cwd(), ".env"), "utf8");
const match = raw.match(/^DATA_DIR=(.+)$/m);
if (match?.[1]?.trim()) process.env.DATA_DIR = match[1].trim();
} catch {
/* .env ausente ou ilegível — ok, bootstrap usa o padrão */
}
}
// Add check for conflicting app/ directory (Issue #1206)
const rootAppDir = path.join(process.cwd(), "app");
if (fs.existsSync(rootAppDir) && fs.statSync(rootAppDir).isDirectory()) {
@@ -86,7 +97,7 @@ async function start() {
await new Promise((resolve) => server.close(resolve));
await nextApp.close();
} catch (error) {
console.error(`[SHUTDOWN] Failed during ${signal}:`, error);
console.error("[SHUTDOWN] Failed during signal:", signal, error);
} finally {
process.exit(0);
}

View File

@@ -3,7 +3,7 @@
import { useTranslations } from "next-intl";
import { useState, useEffect, useMemo, useCallback } from "react";
import Image from "next/image";
import dynamic from "next/dynamic";
import Link from "next/link";
import { useRouter } from "next/navigation";
import { Card, CardSkeleton, Button, Modal } from "@/shared/components";
@@ -11,6 +11,8 @@ import ProviderIcon from "@/shared/components/ProviderIcon";
import { AI_PROVIDERS, FREE_PROVIDERS, OAUTH_PROVIDERS } from "@/shared/constants/providers";
import { useNotificationStore } from "@/store/notificationStore";
import { copyToClipboard } from "@/shared/utils/clipboard";
const ProviderTopology = dynamic(() => import("../home/ProviderTopology"), { ssr: false });
import type { NewsAnnouncement } from "@/shared/utils/releaseNotes";
import { TierCoverageWidget } from "./TierCoverageWidget";
@@ -180,21 +182,6 @@ export default function HomePageClient({ machineId }: HomePageClientProps) {
return models.filter((m) => providerKeys.has(m.provider));
}, [selectedProvider, models]);
const quickStartLinks = [
{ label: t("documentation"), href: "/docs", icon: "menu_book" },
{ label: ts("providers"), href: "/dashboard/providers", icon: "dns" },
{ label: ts("combos"), href: "/dashboard/combos", icon: "layers" },
{ label: ts("analytics"), href: "/dashboard/analytics", icon: "analytics" },
{ label: t("healthMonitor"), href: "/dashboard/health", icon: "health_and_safety" },
{ label: ts("cliTools"), href: "/dashboard/cli-tools", icon: "terminal" },
{
label: t("reportIssue"),
href: "https://github.com/diegosouzapw/OmniRoute/issues",
external: true,
icon: "bug_report",
},
];
const pollBackgroundUpdate = useCallback(
async ({
channel,
@@ -729,73 +716,38 @@ export default function HomePageClient({ machineId }: HomePageClientProps) {
</div>
</li>
</ol>
<div className="flex flex-wrap gap-2">
{quickStartLinks.map((link) => (
<a
key={link.href}
href={link.href}
target={link.external ? "_blank" : undefined}
rel={link.external ? "noopener noreferrer" : undefined}
className="inline-flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-xs font-medium border border-border text-text-muted hover:text-text-main hover:bg-bg-subtle transition-colors"
>
<span className="material-symbols-outlined text-[14px]">
{link.icon || (link.external ? "open_in_new" : "arrow_forward")}
</span>
{link.label}
</a>
))}
</div>
</div>
</Card>
{/* Tier Coverage */}
<TierCoverageWidget />
{/* Providers Overview */}
{/* Provider Topology */}
<Card>
<div className="flex items-center justify-between mb-4">
<div className="flex items-center justify-between mb-3">
<div>
<h2 className="text-lg font-semibold">{t("providersOverview")}</h2>
<p className="text-sm text-text-muted">
{t("configuredOf", {
configured: providerStats.filter((item) => item.total > 0).length,
total: providerStats.length,
})}
<h2 className="text-base font-semibold">Provider Topology</h2>
<p className="text-xs text-text-muted">
Connected providers routing through OmniRoute in real time
</p>
</div>
<div className="flex items-center gap-4">
<div className="hidden sm:flex items-center gap-3 text-[11px] text-text-muted">
<span className="flex items-center gap-1">
<span className="size-2 rounded-full bg-green-500" /> {tc("free")}
</span>
<span className="flex items-center gap-1">
<span className="size-2 rounded-full bg-blue-500" /> {t("oauthLabel")}
</span>
<span className="flex items-center gap-1">
<span className="size-2 rounded-full bg-amber-500" /> {t("apiKeyLabel")}
</span>
</div>
<Link
href="/dashboard/providers"
className="inline-flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-xs font-medium border border-border text-text-muted hover:text-text-main hover:bg-bg-subtle transition-colors"
>
<span className="material-symbols-outlined text-[14px]">settings</span>
{tc("manage")}
</Link>
<div className="flex items-center gap-3 text-[11px] text-text-muted">
<span className="flex items-center gap-1.5">
<span className="size-2 rounded-full bg-green-500" /> Active
</span>
<span className="flex items-center gap-1.5">
<span className="size-2 rounded-full bg-amber-500" /> Recent
</span>
<span className="flex items-center gap-1.5">
<span className="size-2 rounded-full bg-red-500" /> Error
</span>
</div>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-3">
{providerStats.map((item) => (
<ProviderOverviewCard
key={item.id}
item={item}
metrics={providerMetrics[item.provider.alias] || providerMetrics[item.id]}
onClick={() => setSelectedProvider(item)}
/>
))}
</div>
<ProviderTopology
providers={providerStats
.filter((p) => p.total > 0)
.map((p) => ({ id: p.id, provider: p.id, name: p.provider.name }))}
/>
</Card>
{/* Provider Models Modal */}

View File

@@ -0,0 +1,220 @@
"use client";
import { useState, useEffect, useCallback } from "react";
import { Card } from "@/shared/components";
import A2ADashboardPage from "../endpoint/components/A2ADashboard";
type ServiceStatus = { online: boolean; loading: boolean };
function ServiceToggle({
label,
status,
enabled,
onToggle,
toggling,
}: {
label: string;
status: ServiceStatus;
enabled: boolean;
onToggle: () => void;
toggling: boolean;
}) {
const online = enabled && status.online;
const loading = enabled && status.loading;
return (
<div className="flex items-center gap-3">
<div
className="flex items-center gap-1.5 px-2.5 py-1 rounded-full text-xs font-medium border"
style={{
borderColor: loading
? "var(--color-border)"
: online
? "rgba(34,197,94,0.3)"
: "rgba(239,68,68,0.3)",
background: loading
? "transparent"
: online
? "rgba(34,197,94,0.1)"
: "rgba(239,68,68,0.1)",
color: loading ? "var(--color-text-muted)" : online ? "rgb(34,197,94)" : "rgb(239,68,68)",
}}
>
<span
className="inline-block w-2 h-2 rounded-full"
style={{
background: loading
? "var(--color-text-muted)"
: online
? "rgb(34,197,94)"
: "rgb(239,68,68)",
animation: online ? "pulse 2s infinite" : "none",
}}
/>
{loading ? "..." : online ? "Online" : "Offline"}
</div>
<button
onClick={onToggle}
disabled={toggling}
className="relative inline-flex items-center h-7 w-[52px] rounded-full transition-all duration-300 focus:outline-none border"
style={{
background: enabled ? "rgb(34,197,94)" : "var(--color-bg-tertiary)",
borderColor: enabled ? "rgba(34,197,94,0.5)" : "var(--color-border)",
opacity: toggling ? 0.6 : 1,
cursor: toggling ? "wait" : "pointer",
}}
title={enabled ? `Disable ${label}` : `Enable ${label}`}
>
<span
className="inline-block w-5 h-5 rounded-full shadow-md transition-all duration-300"
style={{
transform: enabled ? "translateX(26px)" : "translateX(3px)",
background: enabled ? "#fff" : "var(--color-text-muted)",
}}
/>
</button>
<span
className="text-xs font-medium min-w-[24px]"
style={{ color: enabled ? "rgb(34,197,94)" : "var(--color-text-muted)" }}
>
{toggling ? "..." : enabled ? "ON" : "OFF"}
</span>
</div>
);
}
function DisabledPanel() {
return (
<Card className="p-6">
<div className="flex items-start gap-3">
<div
className="flex size-10 shrink-0 items-center justify-center overflow-hidden rounded-lg"
style={{ background: "var(--color-bg-tertiary)" }}
>
<span
aria-hidden="true"
className="relative block size-5 rounded-full border-2"
style={{ borderColor: "var(--color-text-muted)" }}
>
<span
className="absolute left-1/2 top-[-3px] h-3 w-0.5 -translate-x-1/2 rounded-full"
style={{ background: "var(--color-text-muted)" }}
/>
</span>
</div>
<div>
<h2 className="text-base font-semibold" style={{ color: "var(--color-text)" }}>
A2A is disabled
</h2>
<p className="text-sm mt-1" style={{ color: "var(--color-text-muted)" }}>
Enable A2A above to view task telemetry, agent details, and validation tools.
</p>
</div>
</div>
</Card>
);
}
export default function A2APage() {
const [a2aStatus, setA2aStatus] = useState<ServiceStatus>({ online: false, loading: true });
const [a2aEnabled, setA2aEnabled] = useState(false);
const [a2aToggling, setA2aToggling] = useState(false);
const patchSetting = useCallback(async (body: Record<string, unknown>) => {
return fetch("/api/settings", {
method: "PATCH",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(body),
});
}, []);
useEffect(() => {
const fetchSettings = async () => {
try {
const res = await fetch("/api/settings");
if (res.ok) {
const data = await res.json();
setA2aEnabled(!!data.a2aEnabled);
}
} catch {
// defaults stay
}
};
void fetchSettings();
}, []);
const refreshStatus = useCallback(async () => {
setA2aStatus((prev) => ({ ...prev, loading: true }));
try {
const res = await fetch("/api/a2a/status");
const data = res.ok ? await res.json() : null;
setA2aStatus({ online: data?.status === "ok", loading: false });
} catch {
setA2aStatus({ online: false, loading: false });
}
}, []);
useEffect(() => {
void refreshStatus();
const interval = setInterval(() => void refreshStatus(), 30000);
return () => clearInterval(interval);
}, [refreshStatus]);
const toggleA2a = useCallback(async () => {
const newValue = !a2aEnabled;
setA2aToggling(true);
try {
const res = await patchSetting({ a2aEnabled: newValue });
if (res.ok) setA2aEnabled(newValue);
} catch {
// keep current
} finally {
setA2aToggling(false);
}
}, [a2aEnabled, patchSetting]);
return (
<div className="flex flex-col gap-6">
<Card>
<div className="flex items-start justify-between gap-4">
<div>
<p className="text-sm" style={{ color: "var(--color-text-muted)" }}>
Agent2Agent JSON-RPC 2.0 endpoint send tasks, stream responses, cancel in-flight
jobs.
</p>
<ol
className="mt-2 text-sm space-y-0.5 list-decimal list-inside"
style={{ color: "var(--color-text-muted)" }}
>
<li>
Discover the agent card at <code className="text-xs">/.well-known/agent.json</code>.
</li>
<li>
Send JSON-RPC to <code className="text-xs">POST /a2a</code> using{" "}
<code className="text-xs">message/send</code> or{" "}
<code className="text-xs">message/stream</code>.
</li>
<li>
Track and cancel tasks with <code className="text-xs">tasks/get</code> and{" "}
<code className="text-xs">tasks/cancel</code>.
</li>
</ol>
</div>
<div className="shrink-0">
<ServiceToggle
label="A2A"
status={a2aStatus}
enabled={a2aEnabled}
onToggle={() => void toggleA2a()}
toggling={a2aToggling}
/>
</div>
</div>
</Card>
{a2aEnabled ? <A2ADashboardPage /> : <DisabledPanel />}
</div>
);
}

View File

@@ -16,16 +16,14 @@ function CopyButton({ url }: { url: string }) {
return (
<button
onClick={() => void copy(url, url)}
className="flex items-center gap-1 rounded px-2 py-1 text-xs font-medium transition-colors"
style={{
background: isCopied
? "var(--color-success-bg, #d1fae5)"
: "var(--color-surface-2, #f3f4f6)",
color: isCopied ? "var(--color-success, #065f46)" : "var(--color-text-2, #6b7280)",
}}
className={`flex items-center gap-1 rounded px-2 py-1 text-xs font-medium transition-colors ${
isCopied
? "bg-emerald-500/10 text-emerald-700 dark:text-emerald-400"
: "bg-bg-subtle text-text-muted hover:text-text-main"
}`}
title="Copy raw URL to clipboard"
>
<span className="material-symbols-outlined" style={{ fontSize: 14 }}>
<span className="material-symbols-outlined text-[14px]">
{isCopied ? "check" : "content_copy"}
</span>
{isCopied ? "Copied!" : "Copy URL"}
@@ -38,65 +36,31 @@ function SkillRow({ skill }: { skill: AgentSkill }) {
const blobUrl = getAgentSkillBlobUrl(skill.id);
return (
<div
className="flex items-start gap-3 rounded-lg border p-3 transition-colors hover:bg-[var(--color-surface-hover,#f9fafb)]"
style={{ borderColor: "var(--color-border, #e5e7eb)" }}
>
<div
className="flex h-9 w-9 shrink-0 items-center justify-center rounded-lg"
style={{ background: "var(--color-surface-2, #f3f4f6)" }}
>
<span
className="material-symbols-outlined"
style={{ fontSize: 18, color: "var(--color-text-2, #6b7280)" }}
>
{skill.icon}
</span>
<div className="flex items-start gap-3 rounded-lg border border-border p-3 transition-colors hover:bg-bg-subtle">
<div className="flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-bg-subtle">
<span className="material-symbols-outlined text-[18px] text-text-muted">{skill.icon}</span>
</div>
<div className="min-w-0 flex-1">
<div className="mb-0.5 flex flex-wrap items-center gap-1.5">
<span className="text-sm font-semibold" style={{ color: "var(--color-text, #111827)" }}>
{skill.name}
</span>
<span className="text-sm font-semibold text-text-main">{skill.name}</span>
{skill.isEntry && (
<span
className="rounded-full px-1.5 py-0.5 text-[10px] font-bold uppercase tracking-wide"
style={{
background: "var(--color-primary-bg, #eff6ff)",
color: "var(--color-primary, #2563eb)",
}}
>
<span className="rounded-full bg-primary/10 px-1.5 py-0.5 text-[10px] font-bold uppercase tracking-wide text-primary">
Start Here
</span>
)}
{skill.isNew && (
<span
className="rounded-full px-1.5 py-0.5 text-[10px] font-bold uppercase tracking-wide"
style={{
background: "var(--color-warning-bg, #fef3c7)",
color: "var(--color-warning, #92400e)",
}}
>
<span className="rounded-full bg-amber-500/10 px-1.5 py-0.5 text-[10px] font-bold uppercase tracking-wide text-amber-700 dark:text-amber-400">
New
</span>
)}
{skill.endpoint && (
<code
className="rounded px-1.5 py-0.5 text-[10px]"
style={{
background: "var(--color-surface-2, #f3f4f6)",
color: "var(--color-text-2, #6b7280)",
fontFamily: "monospace",
}}
>
<code className="rounded bg-bg-subtle px-1.5 py-0.5 font-mono text-[10px] text-text-muted">
{skill.endpoint}
</code>
)}
</div>
<p className="text-xs leading-relaxed" style={{ color: "var(--color-text-2, #6b7280)" }}>
{skill.description}
</p>
<p className="text-xs leading-relaxed text-text-muted">{skill.description}</p>
</div>
<div className="flex shrink-0 items-center gap-1">
@@ -104,13 +68,10 @@ function SkillRow({ skill }: { skill: AgentSkill }) {
href={blobUrl}
target="_blank"
rel="noopener noreferrer"
className="flex items-center gap-1 rounded px-2 py-1 text-xs font-medium transition-colors hover:bg-[var(--color-surface-2,#f3f4f6)]"
style={{ color: "var(--color-text-3, #9ca3af)" }}
className="flex items-center gap-1 rounded px-2 py-1 text-xs font-medium text-text-muted transition-colors hover:bg-bg-subtle hover:text-text-main"
title="View on GitHub"
>
<span className="material-symbols-outlined" style={{ fontSize: 14 }}>
open_in_new
</span>
<span className="material-symbols-outlined text-[14px]">open_in_new</span>
</a>
<CopyButton url={rawUrl} />
</div>
@@ -130,21 +91,12 @@ function SkillSection({
skills: AgentSkill[];
}) {
return (
<section>
<div className="mb-3 flex items-center gap-2">
<span
className="material-symbols-outlined"
style={{ fontSize: 20, color: "var(--color-text-2, #6b7280)" }}
>
{icon}
</span>
<section className="space-y-3 rounded-xl border border-border bg-bg p-4">
<div className="flex items-center gap-2">
<span className="material-symbols-outlined text-[20px] text-text-muted">{icon}</span>
<div>
<h2 className="text-sm font-semibold" style={{ color: "var(--color-text, #111827)" }}>
{title}
</h2>
<p className="text-xs" style={{ color: "var(--color-text-3, #9ca3af)" }}>
{subtitle}
</p>
<h2 className="text-sm font-semibold text-text-main">{title}</h2>
<p className="text-xs text-text-muted">{subtitle}</p>
</div>
</div>
<div className="flex flex-col gap-2">
@@ -161,51 +113,22 @@ export default function AgentSkillsPage() {
const cliSkills = AGENT_SKILLS.filter((s) => s.category === "cli");
return (
<div className="mx-auto max-w-3xl space-y-8 p-6">
{/* Header */}
<div>
<h1 className="text-xl font-bold" style={{ color: "var(--color-text, #111827)" }}>
AgentSkills
</h1>
<p className="mt-1 text-sm" style={{ color: "var(--color-text-2, #6b7280)" }}>
SKILL.md manifests for AI agents paste a URL into Claude, Cursor, Cline, or any agent to
give it full knowledge of OmniRoute.
</p>
</div>
{/* How to use */}
<div
className="rounded-lg border p-4"
style={{
borderColor: "var(--color-border, #e5e7eb)",
background: "var(--color-surface-2, #f9fafb)",
}}
>
<div className="space-y-6">
{/* How to use — full width */}
<div className="rounded-xl border border-border bg-bg-subtle/50 p-4">
<div className="mb-2 flex items-center gap-2">
<span
className="material-symbols-outlined"
style={{ fontSize: 18, color: "var(--color-primary, #2563eb)" }}
>
info
</span>
<span className="text-sm font-semibold" style={{ color: "var(--color-text, #111827)" }}>
How to use
</span>
<span className="material-symbols-outlined text-[18px] text-primary">info</span>
<span className="text-sm font-semibold text-text-main">How to use</span>
</div>
<ol className="space-y-1 text-xs" style={{ color: "var(--color-text-2, #6b7280)" }}>
<ol className="space-y-1 text-xs text-text-muted">
<li>
1. Click <strong>Copy URL</strong> on the skill you want your agent to know about.
1. Click <strong className="text-text-main">Copy URL</strong> on the skill you want your
agent to know about.
</li>
<li>
2. In your AI agent (Claude, Cursor, Cline), say:
<br />
<code
className="mt-1 block rounded px-2 py-1 font-mono text-[11px]"
style={{
background: "var(--color-surface, #fff)",
border: "1px solid var(--color-border, #e5e7eb)",
}}
>
<code className="mt-1 block rounded border border-border bg-bg px-2 py-1 font-mono text-[11px]">
Use the skill at &lt;pasted-url&gt;
</code>
</li>
@@ -218,31 +141,28 @@ export default function AgentSkillsPage() {
href={AGENT_SKILLS_REPO_URL}
target="_blank"
rel="noopener noreferrer"
className="mt-3 inline-flex items-center gap-1 text-xs font-medium"
style={{ color: "var(--color-primary, #2563eb)" }}
className="mt-3 inline-flex items-center gap-1 text-xs font-medium text-primary hover:underline"
>
<span className="material-symbols-outlined" style={{ fontSize: 14 }}>
open_in_new
</span>
<span className="material-symbols-outlined text-[14px]">open_in_new</span>
Browse all skills on GitHub
</a>
</div>
{/* API Skills */}
<SkillSection
title="API Skills"
subtitle={`${apiSkills.length} skills — control OmniRoute via REST / HTTP`}
icon="api"
skills={apiSkills}
/>
{/* CLI Skills */}
<SkillSection
title="CLI Skills"
subtitle={`${cliSkills.length} skills — control OmniRoute via the omniroute terminal binary`}
icon="terminal"
skills={cliSkills}
/>
{/* Two-column grid: API Skills | CLI Skills */}
<div className="grid grid-cols-1 gap-6 lg:grid-cols-2">
<SkillSection
title="API Skills"
subtitle={`${apiSkills.length} skills — control OmniRoute via REST / HTTP`}
icon="api"
skills={apiSkills}
/>
<SkillSection
title="CLI Skills"
subtitle={`${cliSkills.length} skills — control OmniRoute via the omniroute terminal binary`}
icon="terminal"
skills={cliSkills}
/>
</div>
</div>
);
}

View File

@@ -152,13 +152,8 @@ export default function AgentsPage() {
}
return (
<div className="flex flex-col gap-6 p-6 max-w-5xl mx-auto">
{/* Header */}
<div className="flex items-center justify-between">
<div>
<h1 className="text-2xl font-bold">{t("title")}</h1>
<p className="text-text-muted mt-1">{t("description")}</p>
</div>
<div className="flex flex-col gap-6">
<div className="flex justify-end">
<Button variant="secondary" onClick={handleRefresh} loading={refreshing}>
<span className="material-symbols-outlined text-[16px] mr-1">refresh</span>
{t("refresh")}

View File

@@ -0,0 +1,7 @@
"use client";
import ComboHealthTab from "../ComboHealthTab";
export default function AnalyticsComboHealthPage() {
return <ComboHealthTab />;
}

View File

@@ -0,0 +1,7 @@
"use client";
import CompressionAnalyticsTab from "../CompressionAnalyticsTab";
export default function AnalyticsCompressionPage() {
return <CompressionAnalyticsTab />;
}

View File

@@ -0,0 +1,7 @@
"use client";
import EvalsTab from "../../usage/components/EvalsTab";
export default function AnalyticsEvalsPage() {
return <EvalsTab />;
}

View File

@@ -4,7 +4,7 @@ import { Skeleton } from "@/shared/components/Loading";
export default function AnalyticsLoading() {
return (
<div className="space-y-6 p-6" role="status" aria-live="polite" aria-busy="true">
<div className="space-y-6" role="status" aria-live="polite" aria-busy="true">
<Skeleton className="h-8 w-40" />
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
{[0, 1, 2, 3].map((index) => (

View File

@@ -1,69 +1,16 @@
"use client";
import { useState, Suspense } from "react";
import { UsageAnalytics, CardSkeleton, SegmentedControl } from "@/shared/components";
import EvalsTab from "../usage/components/EvalsTab";
import SearchAnalyticsTab from "./SearchAnalyticsTab";
import CompressionAnalyticsTab from "./CompressionAnalyticsTab";
import { Suspense } from "react";
import { UsageAnalytics, CardSkeleton } from "@/shared/components";
import DiversityScoreCard from "./components/DiversityScoreCard";
import ProviderUtilizationTab from "./ProviderUtilizationTab";
import ComboHealthTab from "./ComboHealthTab";
import AutoRoutingAnalyticsTab from "./AutoRoutingAnalyticsTab";
import { useTranslations } from "next-intl";
export default function AnalyticsPage() {
const [activeTab, setActiveTab] = useState("overview");
const t = useTranslations("analytics");
const tabDescriptions: Record<string, string> = {
overview: t("overviewDescription"),
evals: t("evalsDescription"),
search: "Search request analytics — provider breakdown, cache hit rate, and cost tracking.",
utilization: t("utilizationDescription"),
comboHealth: t("comboHealthDescription"),
compression: t("compressionAnalyticsDescription"),
autoRouting:
"Auto-routing analytics — variant usage, provider selection, and LKGP performance.",
};
return (
<div className="flex flex-col gap-6">
{/* Page Header */}
<div>
<h1 className="text-2xl font-bold flex items-center gap-2">
<span className="material-symbols-outlined text-primary text-[28px]">analytics</span>
{t("title")}
</h1>
<p className="text-sm text-text-muted mt-1">{tabDescriptions[activeTab]}</p>
</div>
<SegmentedControl
options={[
{ value: "overview", label: t("overview") },
{ value: "evals", label: t("evals") },
{ value: "search", label: "Search" },
{ value: "utilization", label: t("utilization") },
{ value: "comboHealth", label: t("comboHealth") },
{ value: "compression", label: t("compressionAnalyticsTitle") },
{ value: "autoRouting", label: "Auto-Routing" },
]}
value={activeTab}
onChange={setActiveTab}
/>
{activeTab === "overview" && (
<>
<Suspense fallback={<CardSkeleton />}>
<UsageAnalytics />
</Suspense>
<DiversityScoreCard />
</>
)}
{activeTab === "evals" && <EvalsTab />}
{activeTab === "search" && <SearchAnalyticsTab />}
{activeTab === "utilization" && <ProviderUtilizationTab />}
{activeTab === "comboHealth" && <ComboHealthTab />}
{activeTab === "compression" && <CompressionAnalyticsTab />}
<Suspense fallback={<CardSkeleton />}>
<UsageAnalytics />
</Suspense>
<DiversityScoreCard />
</div>
);
}

View File

@@ -0,0 +1,7 @@
"use client";
import SearchAnalyticsTab from "../SearchAnalyticsTab";
export default function AnalyticsSearchPage() {
return <SearchAnalyticsTab />;
}

View File

@@ -0,0 +1,7 @@
"use client";
import ProviderUtilizationTab from "../ProviderUtilizationTab";
export default function AnalyticsUtilizationPage() {
return <ProviderUtilizationTab />;
}

View File

@@ -0,0 +1,7 @@
"use client";
import ApiEndpointsTab from "../endpoint/ApiEndpointsTab";
export default function ApiEndpointsPage() {
return <ApiEndpointsTab />;
}

View File

@@ -550,27 +550,6 @@ export default function ApiManagerPageClient() {
</div>
)}
{/* Header Card */}
<Card>
<div className="flex items-center justify-between mb-4">
<div>
<h2 className="text-lg font-semibold">{t("keyManagement")}</h2>
<p className="text-sm text-text-muted">{t("keyManagementDesc")}</p>
</div>
<Button
icon="add"
onClick={() => {
setNameError(null);
setCreateError(null);
clearPageError();
setShowAddModal(true);
}}
>
{t("createKey")}
</Button>
</div>
</Card>
{/* Keys List Card */}
<Card>
<div className="flex items-center justify-between mb-4">
@@ -588,6 +567,17 @@ export default function ApiManagerPageClient() {
</p>
</div>
</div>
<Button
icon="add"
onClick={() => {
setNameError(null);
setCreateError(null);
clearPageError();
setShowAddModal(true);
}}
>
{t("createKey")}
</Button>
</div>
<p className="text-sm text-text-muted mb-4">{t("keysSecurityNote")}</p>

View File

@@ -0,0 +1,217 @@
"use client";
import { useCallback, useEffect, useState } from "react";
import { useTranslations } from "next-intl";
import { Card } from "@/shared/components";
import type { A2ATask, TaskState } from "@/lib/a2a/taskManager";
type TaskListResponse = {
tasks: A2ATask[];
total: number;
limit: number;
offset: number;
};
const A2A_PAGE_SIZE = 25;
const STATE_STYLES: Record<TaskState, string> = {
submitted: "border-amber-500/30 bg-amber-500/10 text-amber-600",
working: "border-blue-500/30 bg-blue-500/10 text-blue-600",
completed: "border-emerald-500/30 bg-emerald-500/10 text-emerald-600",
failed: "border-red-500/30 bg-red-500/10 text-red-600",
cancelled: "border-border bg-sidebar/40 text-text-muted",
};
function taskDuration(task: A2ATask): string {
const ms = new Date(task.updatedAt).getTime() - new Date(task.createdAt).getTime();
if (ms < 1000) return `${ms}ms`;
return `${(ms / 1000).toFixed(1)}s`;
}
export default function A2aAuditTab() {
const t = useTranslations("compliance");
const [data, setData] = useState<TaskListResponse>({
tasks: [],
total: 0,
limit: A2A_PAGE_SIZE,
offset: 0,
});
const [loading, setLoading] = useState(true);
const [skillFilter, setSkillFilter] = useState("");
const [stateFilter, setStateFilter] = useState<TaskState | "all">("all");
const [offset, setOffset] = useState(0);
const fetchTasks = useCallback(async () => {
setLoading(true);
try {
const params = new URLSearchParams();
params.set("limit", String(A2A_PAGE_SIZE));
params.set("offset", String(offset));
if (skillFilter) params.set("skill", skillFilter);
if (stateFilter !== "all") params.set("state", stateFilter);
const response = await fetch(`/api/a2a/tasks?${params.toString()}`);
const json = (await response.json().catch(() => ({}))) as Partial<TaskListResponse>;
setData({
tasks: Array.isArray(json.tasks) ? json.tasks : [],
total: Number(json.total || 0),
limit: Number(json.limit || A2A_PAGE_SIZE),
offset: Number(json.offset || offset),
});
} finally {
setLoading(false);
}
}, [offset, skillFilter, stateFilter]);
useEffect(() => {
void fetchTasks();
}, [fetchTasks]);
return (
<div className="space-y-5">
<Card className="p-5">
<div className="flex flex-col gap-4 lg:flex-row lg:items-start lg:justify-between">
<div>
<h2 className="text-xl font-semibold text-text-main">{t("a2aAudit")}</h2>
<p className="mt-1 text-sm text-text-muted">{t("a2aAuditDesc")}</p>
<p className="mt-2 text-xs text-text-muted">
{t("a2aShowingTasks", { count: data.tasks.length, total: data.total })}
</p>
</div>
<button
onClick={() => void fetchTasks()}
disabled={loading}
className="inline-flex items-center gap-2 rounded-lg border border-border px-3 py-2 text-sm font-medium text-text-main transition-colors hover:bg-sidebar disabled:opacity-40"
>
<span
className={`material-symbols-outlined text-[16px] ${loading ? "animate-spin" : ""}`}
>
refresh
</span>
{t("refresh")}
</button>
</div>
</Card>
<Card className="p-4">
<div className="grid gap-3 md:grid-cols-3">
<label className="space-y-1">
<span className="text-xs font-medium uppercase tracking-wider text-text-muted">
{t("a2aSkill")}
</span>
<input
value={skillFilter}
onChange={(e) => {
setOffset(0);
setSkillFilter(e.target.value);
}}
placeholder={t("a2aSkillPlaceholder")}
className="w-full rounded-lg border border-border bg-surface px-3 py-2 text-sm text-text-main focus:outline-none focus:ring-2 focus:ring-primary/40"
/>
</label>
<label className="space-y-1">
<span className="text-xs font-medium uppercase tracking-wider text-text-muted">
{t("a2aState")}
</span>
<select
value={stateFilter}
onChange={(e) => {
setOffset(0);
setStateFilter(e.target.value as TaskState | "all");
}}
className="w-full rounded-lg border border-border bg-surface px-3 py-2 text-sm text-text-main focus:outline-none focus:ring-2 focus:ring-primary/40"
>
<option value="all">{t("a2aAllStates")}</option>
<option value="submitted">{t("a2aStateSubmitted")}</option>
<option value="working">{t("a2aStateWorking")}</option>
<option value="completed">{t("a2aStateCompleted")}</option>
<option value="failed">{t("a2aStateFailed")}</option>
<option value="cancelled">{t("a2aStateCancelled")}</option>
</select>
</label>
<div className="flex items-end">
<button
onClick={() => {
setSkillFilter("");
setStateFilter("all");
setOffset(0);
}}
className="w-full rounded-lg border border-border px-3 py-2 text-sm font-medium text-text-main transition-colors hover:bg-sidebar"
>
{t("clearFilters")}
</button>
</div>
</div>
</Card>
<Card className="overflow-hidden">
{loading ? (
<div className="p-8 text-center text-sm text-text-muted">{t("a2aLoadingTasks")}</div>
) : data.tasks.length === 0 ? (
<div className="p-10 text-center">
<span className="material-symbols-outlined text-[40px] text-text-muted">
device_hub
</span>
<p className="mt-3 text-sm text-text-muted">{t("a2aNoTasks")}</p>
</div>
) : (
<div className="overflow-x-auto">
<table className="w-full min-w-[760px] text-left text-sm">
<thead className="border-b border-border bg-sidebar/40 text-xs uppercase tracking-wider text-text-muted">
<tr>
<th className="px-4 py-3 font-medium">{t("timestamp")}</th>
<th className="px-4 py-3 font-medium">{t("a2aTaskId")}</th>
<th className="px-4 py-3 font-medium">{t("a2aSkill")}</th>
<th className="px-4 py-3 font-medium">{t("a2aState")}</th>
<th className="px-4 py-3 font-medium">{t("duration")}</th>
<th className="px-4 py-3 font-medium">{t("a2aEvents")}</th>
<th className="px-4 py-3 font-medium">{t("a2aArtifacts")}</th>
</tr>
</thead>
<tbody className="divide-y divide-border">
{data.tasks.map((task) => (
<tr key={task.id} className="transition-colors hover:bg-sidebar/30">
<td className="whitespace-nowrap px-4 py-3 font-mono text-xs text-text-muted">
{new Date(task.createdAt).toLocaleString()}
</td>
<td className="px-4 py-3 font-mono text-xs text-text-muted">
{task.id.slice(0, 8)}&hellip;
</td>
<td className="px-4 py-3 font-mono text-xs text-text-main">{task.skill}</td>
<td className="px-4 py-3">
<span
className={`rounded-full border px-2 py-1 text-xs font-medium ${STATE_STYLES[task.state]}`}
>
{task.state}
</span>
</td>
<td className="px-4 py-3 text-text-muted">{taskDuration(task)}</td>
<td className="px-4 py-3 text-text-muted">{task.events.length}</td>
<td className="px-4 py-3 text-text-muted">{task.artifacts.length}</td>
</tr>
))}
</tbody>
</table>
</div>
)}
</Card>
<div className="flex items-center justify-end gap-2">
<button
onClick={() => setOffset((c) => Math.max(0, c - A2A_PAGE_SIZE))}
disabled={offset === 0 || loading}
className="rounded-lg border border-border px-3 py-2 text-sm font-medium text-text-main transition-colors hover:bg-sidebar disabled:opacity-40"
>
{t("previous")}
</button>
<button
onClick={() => setOffset((c) => c + A2A_PAGE_SIZE)}
disabled={offset + A2A_PAGE_SIZE >= data.total || loading}
className="rounded-lg border border-border px-3 py-2 text-sm font-medium text-text-main transition-colors hover:bg-sidebar disabled:opacity-40"
>
{t("next")}
</button>
</div>
</div>
);
}

View File

@@ -0,0 +1,289 @@
"use client";
import { useCallback, useEffect, useState } from "react";
import { useTranslations } from "next-intl";
import { Card } from "@/shared/components";
type McpAuditEntry = {
id: number;
toolName: string;
inputHash: string;
outputSummary: string;
durationMs: number;
apiKeyId: string | null;
success: boolean;
errorCode: string | null;
createdAt: string;
};
type McpAuditResponse = {
entries: McpAuditEntry[];
total: number;
limit: number;
offset: number;
};
type McpAuditStats = {
totalCalls: number;
successRate: number;
avgDurationMs: number;
topTools: Array<{ tool: string; count: number }>;
};
const MCP_PAGE_SIZE = 25;
export default function McpAuditTab() {
const t = useTranslations("compliance");
const [data, setData] = useState<McpAuditResponse>({
entries: [],
total: 0,
limit: MCP_PAGE_SIZE,
offset: 0,
});
const [stats, setStats] = useState<McpAuditStats | null>(null);
const [loading, setLoading] = useState(true);
const [toolFilter, setToolFilter] = useState("");
const [successFilter, setSuccessFilter] = useState<"all" | "true" | "false">("all");
const [offset, setOffset] = useState(0);
const fetchStats = useCallback(async () => {
try {
const res = await fetch("/api/mcp/audit/stats");
if (res.ok) setStats((await res.json()) as McpAuditStats);
} catch {
// non-fatal
}
}, []);
useEffect(() => {
void fetchStats();
}, [fetchStats]);
const fetchAudit = useCallback(async () => {
setLoading(true);
try {
const params = new URLSearchParams();
params.set("limit", String(MCP_PAGE_SIZE));
params.set("offset", String(offset));
if (toolFilter) params.set("tool", toolFilter);
if (successFilter !== "all") params.set("success", successFilter);
const response = await fetch(`/api/mcp/audit?${params.toString()}`);
const json = await response.json().catch(() => ({}));
if (!response.ok) {
throw new Error(json.error || t("failedFetchMcpAudit"));
}
setData({
entries: Array.isArray(json.entries) ? json.entries : [],
total: Number(json.total || 0),
limit: Number(json.limit || MCP_PAGE_SIZE),
offset: Number(json.offset || offset),
});
} finally {
setLoading(false);
}
}, [offset, successFilter, t, toolFilter]);
useEffect(() => {
void fetchAudit();
}, [fetchAudit]);
return (
<div className="space-y-5">
<Card className="p-5">
<div className="flex flex-col gap-4 lg:flex-row lg:items-start lg:justify-between">
<div>
<h2 className="text-xl font-semibold text-text-main">{t("mcpAudit")}</h2>
<p className="mt-1 text-sm text-text-muted">{t("mcpAuditDesc")}</p>
<p className="mt-2 text-xs text-text-muted">
{t("showing", { count: data.entries.length, total: data.total })}
</p>
</div>
<button
onClick={() => {
void fetchAudit();
void fetchStats();
}}
disabled={loading}
className="inline-flex items-center gap-2 rounded-lg border border-border px-3 py-2 text-sm font-medium text-text-main transition-colors hover:bg-sidebar disabled:opacity-40"
>
<span
className={`material-symbols-outlined text-[16px] ${loading ? "animate-spin" : ""}`}
>
refresh
</span>
{t("refresh")}
</button>
</div>
</Card>
{stats && (
<div className="grid grid-cols-2 gap-3 md:grid-cols-4">
{[
{
label: "Calls (24h)",
value: stats.totalCalls.toLocaleString(),
icon: "terminal",
},
{
label: "Success rate",
value: `${Math.round(stats.successRate * 100)}%`,
icon: "check_circle",
highlight: stats.successRate >= 0.9,
},
{
label: "Avg duration",
value: `${Math.round(stats.avgDurationMs)}ms`,
icon: "timer",
},
{
label: "Top tool",
value: stats.topTools[0]?.tool ?? "—",
icon: "star",
},
].map((item) => (
<Card key={item.label} className="px-4 py-3">
<div className="flex items-center gap-2 mb-1">
<span
className="material-symbols-outlined text-[14px]"
style={{ color: "var(--color-text-muted)" }}
>
{item.icon}
</span>
<span className="text-xs text-text-muted uppercase tracking-wider">
{item.label}
</span>
</div>
<p
className="text-lg font-semibold truncate"
style={{ color: item.highlight ? "rgb(34,197,94)" : "var(--color-text)" }}
>
{item.value}
</p>
</Card>
))}
</div>
)}
<Card className="p-4">
<div className="grid gap-3 md:grid-cols-3">
<label className="space-y-1">
<span className="text-xs font-medium uppercase tracking-wider text-text-muted">
{t("tool")}
</span>
<input
value={toolFilter}
onChange={(event) => {
setOffset(0);
setToolFilter(event.target.value);
}}
placeholder={t("toolPlaceholder")}
className="w-full rounded-lg border border-border bg-surface px-3 py-2 text-sm text-text-main focus:outline-none focus:ring-2 focus:ring-primary/40"
/>
</label>
<label className="space-y-1">
<span className="text-xs font-medium uppercase tracking-wider text-text-muted">
{t("result")}
</span>
<select
value={successFilter}
onChange={(event) => {
setOffset(0);
setSuccessFilter(event.target.value as "all" | "true" | "false");
}}
className="w-full rounded-lg border border-border bg-surface px-3 py-2 text-sm text-text-main focus:outline-none focus:ring-2 focus:ring-primary/40"
>
<option value="all">{t("allResults")}</option>
<option value="true">{t("success")}</option>
<option value="false">{t("failure")}</option>
</select>
</label>
<div className="flex items-end">
<button
onClick={() => {
setToolFilter("");
setSuccessFilter("all");
setOffset(0);
}}
className="w-full rounded-lg border border-border px-3 py-2 text-sm font-medium text-text-main transition-colors hover:bg-sidebar"
>
{t("clearFilters")}
</button>
</div>
</div>
</Card>
<Card className="overflow-hidden">
{loading ? (
<div className="p-8 text-center text-sm text-text-muted">{t("loading")}</div>
) : data.entries.length === 0 ? (
<div className="p-10 text-center">
<span className="material-symbols-outlined text-[40px] text-text-muted">terminal</span>
<p className="mt-3 text-sm text-text-muted">{t("noMcpEvents")}</p>
</div>
) : (
<div className="overflow-x-auto">
<table className="w-full min-w-[860px] text-left text-sm">
<thead className="border-b border-border bg-sidebar/40 text-xs uppercase tracking-wider text-text-muted">
<tr>
<th className="px-4 py-3 font-medium">{t("timestamp")}</th>
<th className="px-4 py-3 font-medium">{t("tool")}</th>
<th className="px-4 py-3 font-medium">{t("duration")}</th>
<th className="px-4 py-3 font-medium">{t("result")}</th>
<th className="px-4 py-3 font-medium">{t("apiKey")}</th>
<th className="px-4 py-3 font-medium">{t("output")}</th>
</tr>
</thead>
<tbody className="divide-y divide-border">
{data.entries.map((entry) => (
<tr key={entry.id} className="transition-colors hover:bg-sidebar/30">
<td className="whitespace-nowrap px-4 py-3 font-mono text-xs text-text-muted">
{new Date(entry.createdAt).toLocaleString()}
</td>
<td className="px-4 py-3 font-mono text-xs text-text-main">{entry.toolName}</td>
<td className="px-4 py-3 text-text-muted">{entry.durationMs}ms</td>
<td className="px-4 py-3">
<span
className={`rounded-full border px-2 py-1 text-xs font-medium ${
entry.success
? "border-emerald-500/30 bg-emerald-500/10 text-emerald-600"
: "border-red-500/30 bg-red-500/10 text-red-600"
}`}
>
{entry.success ? t("success") : entry.errorCode || t("failure")}
</span>
</td>
<td className="px-4 py-3 font-mono text-xs text-text-muted">
{entry.apiKeyId || t("notAvailable")}
</td>
<td className="max-w-[280px] truncate px-4 py-3 text-xs text-text-muted">
{entry.outputSummary || t("notAvailable")}
</td>
</tr>
))}
</tbody>
</table>
</div>
)}
</Card>
<div className="flex items-center justify-end gap-2">
<button
onClick={() => setOffset((current) => Math.max(0, current - MCP_PAGE_SIZE))}
disabled={offset === 0 || loading}
className="rounded-lg border border-border px-3 py-2 text-sm font-medium text-text-main transition-colors hover:bg-sidebar disabled:opacity-40"
>
{t("previous")}
</button>
<button
onClick={() => setOffset((current) => current + MCP_PAGE_SIZE)}
disabled={offset + MCP_PAGE_SIZE >= data.total || loading}
className="rounded-lg border border-border px-3 py-2 text-sm font-medium text-text-main transition-colors hover:bg-sidebar disabled:opacity-40"
>
{t("next")}
</button>
</div>
</div>
);
}

View File

@@ -0,0 +1,7 @@
"use client";
import A2aAuditTab from "../A2aAuditTab";
export default function AuditA2aPage() {
return <A2aAuditTab />;
}

View File

@@ -0,0 +1,7 @@
"use client";
import McpAuditTab from "../McpAuditTab";
export default function AuditMcpPage() {
return <McpAuditTab />;
}

View File

@@ -1,246 +1,7 @@
"use client";
import { useCallback, useEffect, useState } from "react";
import { useTranslations } from "next-intl";
import { Card, SegmentedControl } from "@/shared/components";
import ComplianceTab from "./ComplianceTab";
type McpAuditEntry = {
id: number;
toolName: string;
inputHash: string;
outputSummary: string;
durationMs: number;
apiKeyId: string | null;
success: boolean;
errorCode: string | null;
createdAt: string;
};
type McpAuditResponse = {
entries: McpAuditEntry[];
total: number;
limit: number;
offset: number;
};
const MCP_PAGE_SIZE = 25;
function McpAuditTab() {
const t = useTranslations("compliance");
const [data, setData] = useState<McpAuditResponse>({
entries: [],
total: 0,
limit: MCP_PAGE_SIZE,
offset: 0,
});
const [loading, setLoading] = useState(true);
const [toolFilter, setToolFilter] = useState("");
const [successFilter, setSuccessFilter] = useState<"all" | "true" | "false">("all");
const [offset, setOffset] = useState(0);
const fetchAudit = useCallback(async () => {
setLoading(true);
try {
const params = new URLSearchParams();
params.set("limit", String(MCP_PAGE_SIZE));
params.set("offset", String(offset));
if (toolFilter) params.set("tool", toolFilter);
if (successFilter !== "all") params.set("success", successFilter);
const response = await fetch(`/api/mcp/audit?${params.toString()}`);
const json = await response.json().catch(() => ({}));
if (!response.ok) {
throw new Error(json.error || t("failedFetchMcpAudit"));
}
setData({
entries: Array.isArray(json.entries) ? json.entries : [],
total: Number(json.total || 0),
limit: Number(json.limit || MCP_PAGE_SIZE),
offset: Number(json.offset || offset),
});
} finally {
setLoading(false);
}
}, [offset, successFilter, t, toolFilter]);
useEffect(() => {
void fetchAudit();
}, [fetchAudit]);
return (
<div className="space-y-5">
<Card className="p-5">
<div className="flex flex-col gap-4 lg:flex-row lg:items-start lg:justify-between">
<div>
<h2 className="text-xl font-semibold text-text-main">{t("mcpAudit")}</h2>
<p className="mt-1 text-sm text-text-muted">{t("mcpAuditDesc")}</p>
<p className="mt-2 text-xs text-text-muted">
{t("showing", { count: data.entries.length, total: data.total })}
</p>
</div>
<button
onClick={() => void fetchAudit()}
disabled={loading}
className="inline-flex items-center gap-2 rounded-lg border border-border px-3 py-2 text-sm font-medium text-text-main transition-colors hover:bg-sidebar disabled:opacity-40"
>
<span
className={`material-symbols-outlined text-[16px] ${loading ? "animate-spin" : ""}`}
>
refresh
</span>
{t("refresh")}
</button>
</div>
</Card>
<Card className="p-4">
<div className="grid gap-3 md:grid-cols-3">
<label className="space-y-1">
<span className="text-xs font-medium uppercase tracking-wider text-text-muted">
{t("tool")}
</span>
<input
value={toolFilter}
onChange={(event) => {
setOffset(0);
setToolFilter(event.target.value);
}}
placeholder={t("toolPlaceholder")}
className="w-full rounded-lg border border-border bg-surface px-3 py-2 text-sm text-text-main focus:outline-none focus:ring-2 focus:ring-primary/40"
/>
</label>
<label className="space-y-1">
<span className="text-xs font-medium uppercase tracking-wider text-text-muted">
{t("result")}
</span>
<select
value={successFilter}
onChange={(event) => {
setOffset(0);
setSuccessFilter(event.target.value as "all" | "true" | "false");
}}
className="w-full rounded-lg border border-border bg-surface px-3 py-2 text-sm text-text-main focus:outline-none focus:ring-2 focus:ring-primary/40"
>
<option value="all">{t("allResults")}</option>
<option value="true">{t("success")}</option>
<option value="false">{t("failure")}</option>
</select>
</label>
<div className="flex items-end">
<button
onClick={() => {
setToolFilter("");
setSuccessFilter("all");
setOffset(0);
}}
className="w-full rounded-lg border border-border px-3 py-2 text-sm font-medium text-text-main transition-colors hover:bg-sidebar"
>
{t("clearFilters")}
</button>
</div>
</div>
</Card>
<Card className="overflow-hidden">
{loading ? (
<div className="p-8 text-center text-sm text-text-muted">{t("loading")}</div>
) : data.entries.length === 0 ? (
<div className="p-10 text-center">
<span className="material-symbols-outlined text-[40px] text-text-muted">terminal</span>
<p className="mt-3 text-sm text-text-muted">{t("noMcpEvents")}</p>
</div>
) : (
<div className="overflow-x-auto">
<table className="w-full min-w-[860px] text-left text-sm">
<thead className="border-b border-border bg-sidebar/40 text-xs uppercase tracking-wider text-text-muted">
<tr>
<th className="px-4 py-3 font-medium">{t("timestamp")}</th>
<th className="px-4 py-3 font-medium">{t("tool")}</th>
<th className="px-4 py-3 font-medium">{t("duration")}</th>
<th className="px-4 py-3 font-medium">{t("result")}</th>
<th className="px-4 py-3 font-medium">{t("apiKey")}</th>
<th className="px-4 py-3 font-medium">{t("output")}</th>
</tr>
</thead>
<tbody className="divide-y divide-border">
{data.entries.map((entry) => (
<tr key={entry.id} className="transition-colors hover:bg-sidebar/30">
<td className="whitespace-nowrap px-4 py-3 font-mono text-xs text-text-muted">
{new Date(entry.createdAt).toLocaleString()}
</td>
<td className="px-4 py-3 font-mono text-xs text-text-main">{entry.toolName}</td>
<td className="px-4 py-3 text-text-muted">{entry.durationMs}ms</td>
<td className="px-4 py-3">
<span
className={`rounded-full border px-2 py-1 text-xs font-medium ${
entry.success
? "border-emerald-500/30 bg-emerald-500/10 text-emerald-600"
: "border-red-500/30 bg-red-500/10 text-red-600"
}`}
>
{entry.success ? t("success") : entry.errorCode || t("failure")}
</span>
</td>
<td className="px-4 py-3 font-mono text-xs text-text-muted">
{entry.apiKeyId || t("notAvailable")}
</td>
<td className="max-w-[280px] truncate px-4 py-3 text-xs text-text-muted">
{entry.outputSummary || t("notAvailable")}
</td>
</tr>
))}
</tbody>
</table>
</div>
)}
</Card>
<div className="flex items-center justify-end gap-2">
<button
onClick={() => setOffset((current) => Math.max(0, current - MCP_PAGE_SIZE))}
disabled={offset === 0 || loading}
className="rounded-lg border border-border px-3 py-2 text-sm font-medium text-text-main transition-colors hover:bg-sidebar disabled:opacity-40"
>
{t("previous")}
</button>
<button
onClick={() => setOffset((current) => current + MCP_PAGE_SIZE)}
disabled={offset + MCP_PAGE_SIZE >= data.total || loading}
className="rounded-lg border border-border px-3 py-2 text-sm font-medium text-text-main transition-colors hover:bg-sidebar disabled:opacity-40"
>
{t("next")}
</button>
</div>
</div>
);
}
export default function AuditPage() {
const t = useTranslations("compliance");
const [activeTab, setActiveTab] = useState("compliance");
return (
<div className="mx-auto max-w-7xl space-y-6 p-6">
<div>
<div className="flex items-center gap-2">
<span className="material-symbols-outlined text-[24px] text-primary">policy</span>
<h1 className="text-3xl font-bold tracking-tight text-text-main">{t("auditTitle")}</h1>
</div>
<p className="mt-1 text-sm text-text-muted">{t("auditDescription")}</p>
</div>
<SegmentedControl
options={[
{ value: "compliance", label: t("complianceTab") },
{ value: "mcp", label: t("mcpTab") },
]}
value={activeTab}
onChange={setActiveTab}
/>
{activeTab === "compliance" ? <ComplianceTab /> : <McpAuditTab />}
</div>
);
return <ComplianceTab />;
}

View File

@@ -0,0 +1,43 @@
"use client";
import { useState, useEffect, useCallback } from "react";
import FilesListTab from "../FilesListTab";
import { mapFileApiToRecord, mapBatchApiToRecord } from "../batch-utils";
import { FileRecord } from "@/lib/db/files";
import { BatchRecord } from "@/lib/db/batches";
export default function BatchFilesPage() {
const [files, setFiles] = useState<FileRecord[]>([]);
const [batches, setBatches] = useState<BatchRecord[]>([]);
const [loading, setLoading] = useState(true);
const fetchAll = useCallback(async () => {
setLoading(true);
try {
const [filesRes, batchesRes] = await Promise.all([
fetch("/api/v1/files?limit=20"),
fetch("/api/v1/batches?limit=20"),
]);
if (filesRes.ok) {
const data = await filesRes.json();
setFiles((data.data || []).map(mapFileApiToRecord));
}
if (batchesRes.ok) {
const data = await batchesRes.json();
setBatches((data.data || []).map(mapBatchApiToRecord));
}
} catch {
// ignore
} finally {
setLoading(false);
}
}, []);
useEffect(() => {
void fetchAll();
}, [fetchAll]);
return (
<FilesListTab files={files} loading={loading} onRefresh={fetchAll} batches={batches} />
);
}

View File

@@ -1,9 +1,7 @@
"use client";
import { useState, useEffect, useCallback, useRef } from "react";
import { SegmentedControl } from "@/shared/components";
import BatchListTab from "./BatchListTab";
import FilesListTab from "./FilesListTab";
import { FileRecord } from "@/lib/db/files";
import { BatchRecord } from "@/lib/db/batches";
import { mapBatchApiToRecord, mapFileApiToRecord } from "./batch-utils";
@@ -12,39 +10,28 @@ export default function BatchPage() {
const [batches, setBatches] = useState<BatchRecord[]>([]);
const [files, setFiles] = useState<FileRecord[]>([]);
const [batchesTotal, setBatchesTotal] = useState(0);
const [filesTotal, setFilesTotal] = useState(0);
const [loading, setLoading] = useState(true);
const [loadingMore, setLoadingMore] = useState(false);
const [activeTab, setActiveTab] = useState<"batches" | "files">("batches");
const [batchesHasMore, setBatchesHasMore] = useState(false);
const [batchesLastId, setBatchesLastId] = useState<string | null>(null);
const [filesHasMore, setFilesHasMore] = useState(false);
const [filesLastId, setFilesLastId] = useState<string | null>(null);
const bottomRefBatches = useRef<HTMLDivElement>(null);
const bottomRefFiles = useRef<HTMLDivElement>(null);
const listContainerRef = useRef<HTMLDivElement>(null);
const refreshTimeoutRef = useRef<NodeJS.Timeout | null>(null);
const isFetchingRef = useRef(false);
const fetchDataRef = useRef<typeof fetchData | null>(null);
const fetchData = useCallback(
async (
isBackground = false,
opts: { appendBatches?: boolean; appendFiles?: boolean; limit?: number } = {}
) => {
async (isBackground = false, opts: { appendBatches?: boolean; limit?: number } = {}) => {
if (isFetchingRef.current) return;
if (!isBackground) setLoading(true);
if (opts.appendBatches || opts.appendFiles) setLoadingMore(true);
if (opts.appendBatches) setLoadingMore(true);
isFetchingRef.current = true;
const limit = opts.limit ?? 20;
try {
const batchUrl =
`/api/v1/batches?limit=${limit}` +
(opts.appendBatches && batchesLastId ? `&after=${batchesLastId}` : "");
const filesUrl =
`/api/v1/files?limit=${limit}` +
(opts.appendFiles && filesLastId ? `&after=${filesLastId}` : "");
const filesUrl = `/api/v1/files?limit=${limit}`;
const [batchesRes, filesRes] = await Promise.all([fetch(batchUrl), fetch(filesUrl)]);
@@ -79,13 +66,7 @@ export default function BatchPage() {
if (filesRes.ok) {
const data = await filesRes.json();
const mapped = (data.data || []).map(mapFileApiToRecord);
if (opts.appendFiles) {
setFiles((prev) => [...prev, ...mapped]);
setFilesHasMore(Boolean(data.has_more));
setFilesLastId(data.last_id || null);
} else if (isBackground) {
// Background refresh: merge new items with existing ones, preserve pagination state
if (isBackground) {
setFiles((prev) => {
const fileMap = new Map(prev.map((f) => [f.id, f]));
for (const m of mapped) {
@@ -95,23 +76,19 @@ export default function BatchPage() {
(a, b) => b.createdAt - a.createdAt || b.id.localeCompare(a.id)
);
});
// Don't reset filesLastId or filesHasMore on background refresh
} else {
setFiles(mapped);
setFilesHasMore(Boolean(data.has_more));
setFilesLastId(data.last_id || null);
}
setFilesTotal(data.total_count || 0);
}
} catch (error) {
console.error("Failed to fetch batches/files", error);
} finally {
isFetchingRef.current = false;
if (!isBackground) setLoading(false);
if (opts.appendBatches || opts.appendFiles) setLoadingMore(false);
if (opts.appendBatches) setLoadingMore(false);
}
},
[batchesLastId, filesLastId]
[batchesLastId]
);
// Keep fetchData ref in sync
@@ -147,46 +124,33 @@ export default function BatchPage() {
};
}, []); // Empty deps - only run once, uses ref for latest fetchData
// IntersectionObserver for infinite scroll - re-created only when tab or hasMore state changes
// (NOT when loadingMore changes, to avoid re-triggering immediately after load)
// IntersectionObserver for infinite scroll on batches
useEffect(() => {
const currentBottomRef = activeTab === "batches" ? bottomRefBatches : bottomRefFiles;
const observer = new IntersectionObserver(
(entries) => {
if (entries[0].isIntersecting) {
if (activeTab === "batches" && batchesHasMore && !loadingMoreRef.current) {
fetchDataRef.current?.(true, { appendBatches: true });
} else if (activeTab === "files" && filesHasMore && !loadingMoreRef.current) {
fetchDataRef.current?.(true, { appendFiles: true });
}
if (entries[0].isIntersecting && batchesHasMore && !loadingMoreRef.current) {
fetchDataRef.current?.(true, { appendBatches: true });
}
},
{ threshold: 0.1 }
);
if (currentBottomRef.current) {
observer.observe(currentBottomRef.current);
if (bottomRefBatches.current) {
observer.observe(bottomRefBatches.current);
}
return () => observer.disconnect();
}, [activeTab, batchesHasMore, filesHasMore]);
}, [batchesHasMore]);
const batchesCount = batches.length;
const filesCount = files.length;
return (
<div className="flex flex-col gap-6">
{/* Toolbar */}
<div className="flex items-center justify-between gap-4 flex-wrap">
<SegmentedControl
options={[
{ value: "batches", label: `Batches${batchesTotal ? ` (${batchesTotal})` : ""}` },
{ value: "files", label: `Files${filesTotal ? ` (${filesTotal})` : ""}` },
]}
value={activeTab}
onChange={(v) => setActiveTab(v as "batches" | "files")}
/>
<span className="text-sm text-text-muted">
{batchesTotal ? `${batchesTotal} batches` : "Batches"}
</span>
<button
onClick={() => fetchData(false)}
@@ -202,35 +166,17 @@ export default function BatchPage() {
</button>
</div>
{/* Tab content with scroll container for position preservation */}
<div ref={listContainerRef} className="flex flex-col gap-6">
{activeTab === "batches" ? (
<>
<BatchListTab
batches={batches}
files={files}
loading={loading}
onRefresh={() => fetchData(false)}
/>
{loadingMore && batchesCount > 0 && (
<div className="text-center text-sm">Loading more</div>
)}
<div ref={bottomRefBatches} className="h-10" />
</>
) : (
<>
<FilesListTab
files={files}
loading={loading}
onRefresh={() => fetchData(false)}
batches={batches}
/>
{loadingMore && filesCount > 0 && (
<div className="text-center text-sm">Loading more</div>
)}
<div ref={bottomRefFiles} className="h-10" />
</>
<div className="flex flex-col gap-6">
<BatchListTab
batches={batches}
files={files}
loading={loading}
onRefresh={() => fetchData(false)}
/>
{loadingMore && batchesCount > 0 && (
<div className="text-center text-sm">Loading more</div>
)}
<div ref={bottomRefBatches} className="h-10" />
</div>
</div>
);

View File

@@ -726,12 +726,6 @@ export default function MediaPageClient() {
return (
<div className="space-y-6">
{/* Header */}
<div>
<h1 className="text-2xl font-bold text-text-main">{t("title")}</h1>
<p className="text-text-muted text-sm mt-1">{t("subtitle")}</p>
</div>
{/* Modality Tabs */}
<div className="flex flex-wrap gap-2 p-1 bg-surface/50 rounded-xl border border-black/5 dark:border-white/5">
{(Object.keys(MODALITY_CONFIG) as Modality[]).map((key) => {

View File

@@ -428,11 +428,7 @@ export default function CachePage() {
return (
<div className="flex flex-col gap-6">
<div className="flex items-start justify-between gap-4">
<div>
<h1 className="text-xl font-semibold">{t("title")}</h1>
<p className="mt-0.5 text-sm text-text-muted">{t("description")}</p>
</div>
<div className="flex justify-end">
<Button
variant="secondary"
icon="refresh"

View File

@@ -1,26 +1,17 @@
"use client";
import { useState } from "react";
import { useTranslations } from "next-intl";
import { Card, SegmentedControl } from "@/shared/components";
import ChangelogViewer from "./components/ChangelogViewer";
import NewsViewer from "./components/NewsViewer";
export default function ChangelogPage() {
const [activeTab, setActiveTab] = useState<"news" | "changelog">("news");
const t = useTranslations("sidebar");
const title = typeof t.has === "function" && t.has("changelog") ? t("changelog") : "Changelog";
return (
<div className="flex flex-col gap-6 max-w-5xl mx-auto w-full">
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4">
<div>
<h1 className="text-2xl font-bold text-text-main">{title}</h1>
<p className="text-sm text-text-muted mt-1">
Stay up to date with the latest platform features and announcements.
</p>
</div>
<div className="shrink-0 w-full sm:w-[240px]">
<div className="flex flex-col gap-6">
<div className="flex justify-end">
<div className="w-full sm:w-[240px]">
<SegmentedControl
options={[
{ label: "News", value: "news" },

View File

@@ -254,14 +254,7 @@ export default function CloudAgentsPage() {
}
return (
<div className="flex flex-col gap-6 p-6 max-w-6xl mx-auto">
<div className="flex items-center justify-between">
<div>
<h1 className="text-2xl font-bold">{t("title")}</h1>
<p className="text-text-muted mt-1">{t("description")}</p>
</div>
</div>
<div className="flex flex-col gap-6">
<Card className="border-purple-500/20 bg-purple-500/5">
<div className="flex flex-col gap-4">
<div className="flex items-start justify-between gap-4 flex-wrap">

View File

@@ -116,17 +116,7 @@ export default function CavemanContextPageClient() {
const previewPrompt = `[OmniRoute Caveman Output Mode]\n${t(`preview.${outputMode.intensity}`)}`;
return (
<div className="mx-auto flex max-w-6xl flex-col gap-6">
<header className="flex flex-col gap-2">
<div className="flex items-center gap-3">
<span className="material-symbols-outlined text-[30px] text-primary">compress</span>
<div>
<h1 className="text-2xl font-bold text-text-main">{t("title")}</h1>
<p className="text-sm text-text-muted">{t("description")}</p>
</div>
</div>
</header>
<div className="flex flex-col gap-6">
<section className="grid grid-cols-1 gap-3 sm:grid-cols-4">
{statCards.map(([label, value]) => (
<div key={label} className="rounded-lg border border-border bg-surface p-4">

View File

@@ -174,17 +174,7 @@ export default function CompressionCombosPageClient() {
};
return (
<div className="mx-auto flex max-w-6xl flex-col gap-6">
<header className="flex flex-col gap-2">
<div className="flex items-center gap-3">
<span className="material-symbols-outlined text-[30px] text-primary">hub</span>
<div>
<h1 className="text-2xl font-bold text-text-main">{t("title")}</h1>
<p className="text-sm text-text-muted">{t("description")}</p>
</div>
</div>
</header>
<div className="flex flex-col gap-6">
<section className="rounded-lg border border-border bg-surface p-4">
<div className="grid grid-cols-1 gap-3 md:grid-cols-2">
<input

View File

@@ -138,17 +138,7 @@ export default function RtkContextPageClient() {
];
return (
<div className="mx-auto flex max-w-6xl flex-col gap-6">
<header className="flex flex-col gap-2">
<div className="flex items-center gap-3">
<span className="material-symbols-outlined text-[30px] text-primary">filter_alt</span>
<div>
<h1 className="text-2xl font-bold text-text-main">{t("title")}</h1>
<p className="text-sm text-text-muted">{t("description")}</p>
</div>
</div>
</header>
<div className="flex flex-col gap-6">
<section className="grid grid-cols-1 gap-3 sm:grid-cols-2 xl:grid-cols-4">
{statCards.map(([label, value]) => (
<div key={label} className="rounded-lg border border-border bg-surface p-4">

View File

@@ -0,0 +1,7 @@
"use client";
import BudgetTab from "../../usage/components/BudgetTab";
export default function CostsBudgetPage() {
return <BudgetTab />;
}

View File

@@ -1,40 +1,7 @@
"use client";
import { useState } from "react";
import { SegmentedControl } from "@/shared/components";
import BudgetTab from "../usage/components/BudgetTab";
import PricingTab from "../settings/components/PricingTab";
import CostOverviewTab from "./CostOverviewTab";
import { useTranslations } from "next-intl";
export default function CostsPage() {
const [activeTab, setActiveTab] = useState("overview");
const t = useTranslations("costs");
const ts = useTranslations("settings");
return (
<div className="flex flex-col gap-6">
<div>
<h1 className="text-2xl font-bold flex items-center gap-2">
<span className="material-symbols-outlined text-primary text-[28px]">payments</span>
{t("title")}
</h1>
<p className="text-sm text-text-muted mt-1">{t("pageDescription")}</p>
</div>
<SegmentedControl
options={[
{ value: "overview", label: t("overview") },
{ value: "budget", label: t("budget") },
{ value: "pricing", label: ts("pricing") },
]}
value={activeTab}
onChange={setActiveTab}
/>
{activeTab === "overview" && <CostOverviewTab />}
{activeTab === "budget" && <BudgetTab />}
{activeTab === "pricing" && <PricingTab />}
</div>
);
return <CostOverviewTab />;
}

View File

@@ -0,0 +1,7 @@
"use client";
import PricingTab from "../../settings/components/PricingTab";
export default function CostsPricingPage() {
return <PricingTab />;
}

View File

@@ -25,19 +25,6 @@ interface CatalogData {
schemas: string[];
}
interface WebhookItem {
id: string;
url: string;
events: string[];
secret: string | null;
enabled: boolean;
description: string;
created_at: string;
last_triggered_at: string | null;
last_status: number | null;
failure_count: number;
}
interface TryItResult {
status: number;
statusText: string;
@@ -55,22 +42,12 @@ const METHOD_COLORS: Record<string, string> = {
DELETE: "bg-red-500/15 text-red-500 border-red-500/30",
};
const WEBHOOK_EVENTS = [
"request.completed",
"request.failed",
"provider.error",
"provider.recovered",
"quota.exceeded",
"combo.switched",
];
/* ─── Main Component ─────────────────────────────────── */
export default function ApiEndpointsTab() {
const baseUrl = useDisplayBaseUrl();
const [catalog, setCatalog] = useState<CatalogData | null>(null);
const [catalogError, setCatalogError] = useState<string | null>(null);
const [loading, setLoading] = useState(true);
const [section, setSection] = useState<"catalog" | "webhooks">("catalog");
const [search, setSearch] = useState("");
const [expandedEndpoint, setExpandedEndpoint] = useState<string | null>(null);
const [selectedTag, setSelectedTag] = useState<string | null>(null);
@@ -81,16 +58,6 @@ export default function ApiEndpointsTab() {
const [tryResult, setTryResult] = useState<TryItResult | null>(null);
const [trying, setTrying] = useState(false);
// Webhooks state
const [webhooks, setWebhooks] = useState<WebhookItem[]>([]);
const [webhooksLoading, setWebhooksLoading] = useState(false);
const [showAddWebhook, setShowAddWebhook] = useState(false);
const [whUrl, setWhUrl] = useState("");
const [whEvents, setWhEvents] = useState<string[]>(["*"]);
const [whDesc, setWhDesc] = useState("");
const [testingWebhookId, setTestingWebhookId] = useState<string | null>(null);
// Load catalog
const loadCatalog = async () => {
try {
const res = await fetch("/api/openapi/spec");
@@ -124,39 +91,6 @@ export default function ApiEndpointsTab() {
};
}, []);
// Load webhooks
const fetchWebhooksData = async (): Promise<WebhookItem[]> => {
try {
const res = await fetch("/api/webhooks");
if (res.ok) {
const data = await res.json();
return data.webhooks || [];
}
} catch {}
return [];
};
const loadWebhooks = async () => {
setWebhooksLoading(true);
const data = await fetchWebhooksData();
setWebhooks(data);
setWebhooksLoading(false);
};
useEffect(() => {
if (section !== "webhooks") return;
let cancelled = false;
fetchWebhooksData().then((data) => {
if (!cancelled) {
setWebhooks(data);
setWebhooksLoading(false);
}
});
return () => {
cancelled = true;
};
}, [section]);
// Filter endpoints
const filteredEndpoints = useMemo(() => {
if (!catalog) return [];
@@ -226,64 +160,17 @@ export default function ApiEndpointsTab() {
setTrying(false);
};
// Webhook handlers
const addWebhook = async () => {
if (!whUrl.trim()) return;
try {
await fetch("/api/webhooks", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ url: whUrl, events: whEvents, description: whDesc }),
});
setWhUrl("");
setWhEvents(["*"]);
setWhDesc("");
setShowAddWebhook(false);
await loadWebhooks();
} catch {}
};
const toggleWebhook = async (wh: WebhookItem) => {
try {
await fetch(`/api/webhooks/${wh.id}`, {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ enabled: !wh.enabled }),
});
setWebhooks((prev) => prev.map((w) => (w.id === wh.id ? { ...w, enabled: !w.enabled } : w)));
} catch {}
};
const deleteWebhook = async (id: string) => {
if (!confirm("Delete this webhook?")) return;
try {
await fetch(`/api/webhooks/${id}`, { method: "DELETE" });
setWebhooks((prev) => prev.filter((w) => w.id !== id));
} catch {}
};
const testWebhook = async (id: string) => {
setTestingWebhookId(id);
try {
await fetch(`/api/webhooks/${id}/test`, { method: "POST" });
await loadWebhooks();
} catch {}
setTestingWebhookId(null);
};
if (loading) {
return (
<div className="p-6 max-w-6xl mx-auto">
<div className="animate-pulse space-y-4">
<div className="h-8 bg-white/5 rounded-lg w-1/3" />
<div className="h-64 bg-white/5 rounded-xl" />
</div>
<div className="animate-pulse space-y-4">
<div className="h-8 bg-white/5 rounded-lg w-1/3" />
<div className="h-64 bg-white/5 rounded-xl" />
</div>
);
}
return (
<div className="p-6 max-w-6xl mx-auto space-y-5">
<div className="space-y-5">
{/* Header with spec info */}
{catalog && (
<Card className="p-5">
@@ -329,30 +216,8 @@ export default function ApiEndpointsTab() {
</Card>
)}
{/* Section tabs */}
<div className="flex gap-1 p-1 rounded-xl bg-black/5 dark:bg-white/[0.03] w-fit">
{[
{ id: "catalog" as const, label: "API Catalog", icon: "menu_book" },
{ id: "webhooks" as const, label: "Webhooks", icon: "webhook" },
].map((tab) => (
<button
key={tab.id}
onClick={() => setSection(tab.id)}
className={`flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium rounded-lg transition-all
${
section === tab.id
? "bg-white dark:bg-white/10 text-text-main shadow-sm"
: "text-text-muted hover:text-text-main"
}`}
>
<span className="material-symbols-outlined text-[14px]">{tab.icon}</span>
{tab.label}
</button>
))}
</div>
{/* ═══ API CATALOG ═══ */}
{section === "catalog" && !catalog && (
{!catalog && (
<Card className="p-6">
<div className="flex items-start gap-3">
<div className="flex size-10 items-center justify-center rounded-lg bg-red-500/10">
@@ -378,7 +243,7 @@ export default function ApiEndpointsTab() {
</Card>
)}
{section === "catalog" && catalog && (
{catalog && (
<>
{/* Search & filter */}
<div className="flex items-center gap-2">
@@ -649,232 +514,6 @@ export default function ApiEndpointsTab() {
)}
</>
)}
{/* ═══ WEBHOOKS ═══ */}
{section === "webhooks" && (
<>
<Card className="p-4">
<div className="flex items-center justify-between mb-3">
<div className="flex items-center gap-2">
<span className="material-symbols-outlined text-primary text-[18px]">webhook</span>
<div>
<h3 className="text-sm font-semibold">Event Webhooks</h3>
<p className="text-[11px] text-text-muted">
Receive HTTP callbacks when events occur in OmniRoute
</p>
</div>
</div>
{!showAddWebhook && (
<button
onClick={() => setShowAddWebhook(true)}
className="flex items-center gap-1 px-2.5 py-1 text-xs font-medium rounded-lg
bg-primary/10 text-primary hover:bg-primary/20 transition-colors"
>
<span className="material-symbols-outlined text-[14px]">add</span>
Add Webhook
</button>
)}
</div>
{/* Add webhook form */}
{showAddWebhook && (
<div className="mb-4 p-3 rounded-lg border border-primary/20 bg-primary/[0.03] space-y-2">
<div className="grid grid-cols-1 sm:grid-cols-2 gap-2">
<div>
<label className="text-[10px] font-medium text-text-muted uppercase tracking-wider">
Webhook URL
</label>
<input
value={whUrl}
onChange={(e) => setWhUrl(e.target.value)}
placeholder="https://example.com/webhook"
className="w-full mt-0.5 px-2.5 py-1.5 text-xs rounded-lg border border-black/10 dark:border-white/10
bg-white dark:bg-black/20 focus:outline-none focus:ring-1 focus:ring-primary"
/>
</div>
<div>
<label className="text-[10px] font-medium text-text-muted uppercase tracking-wider">
Description
</label>
<input
value={whDesc}
onChange={(e) => setWhDesc(e.target.value)}
placeholder="Production monitoring"
className="w-full mt-0.5 px-2.5 py-1.5 text-xs rounded-lg border border-black/10 dark:border-white/10
bg-white dark:bg-black/20 focus:outline-none focus:ring-1 focus:ring-primary"
/>
</div>
</div>
<div>
<label className="text-[10px] font-medium text-text-muted uppercase tracking-wider">
Events
</label>
<div className="flex flex-wrap gap-1.5 mt-1">
<button
onClick={() => setWhEvents(["*"])}
className={`px-2 py-0.5 text-[10px] font-medium rounded transition-colors
${
whEvents.includes("*")
? "bg-primary/10 text-primary"
: "bg-black/5 dark:bg-white/5 text-text-muted"
}`}
>
All events
</button>
{WEBHOOK_EVENTS.map((ev) => (
<button
key={ev}
onClick={() => {
if (whEvents.includes("*")) {
setWhEvents([ev]);
} else if (whEvents.includes(ev)) {
setWhEvents(whEvents.filter((e) => e !== ev));
} else {
setWhEvents([...whEvents, ev]);
}
}}
className={`px-2 py-0.5 text-[10px] font-medium rounded transition-colors
${
whEvents.includes(ev) || whEvents.includes("*")
? "bg-primary/10 text-primary"
: "bg-black/5 dark:bg-white/5 text-text-muted"
}`}
>
{ev}
</button>
))}
</div>
</div>
<div className="flex gap-2 mt-2">
<button
onClick={addWebhook}
disabled={!whUrl.trim()}
className="px-3 py-1 text-xs font-medium rounded-lg bg-primary text-white
hover:bg-primary/90 disabled:opacity-40 transition-colors"
>
Save
</button>
<button
onClick={() => setShowAddWebhook(false)}
className="px-3 py-1 text-xs font-medium rounded-lg
bg-black/5 dark:bg-white/5 hover:bg-black/10 dark:hover:bg-white/10 transition-colors"
>
Cancel
</button>
</div>
</div>
)}
{/* Webhooks list */}
{webhooksLoading ? (
<div className="text-xs text-text-muted py-4 text-center">Loading...</div>
) : webhooks.length === 0 ? (
<div className="text-center py-6">
<span className="material-symbols-outlined text-[32px] text-text-muted">
webhook
</span>
<p className="text-xs text-text-muted mt-2">
No webhooks configured. Add one to receive event notifications.
</p>
</div>
) : (
<div className="space-y-2">
{webhooks.map((wh) => (
<div
key={wh.id}
className={`flex items-center justify-between px-3 py-2.5 rounded-lg border transition-colors
${
wh.enabled
? "border-black/10 dark:border-white/10 bg-white/50 dark:bg-white/[0.02]"
: "border-black/5 dark:border-white/5 opacity-50"
}`}
>
<div className="min-w-0 flex-1">
<div className="flex items-center gap-2">
<code className="text-xs font-mono text-text-main truncate">{wh.url}</code>
{wh.failure_count > 0 && (
<span className="text-[9px] px-1 py-0.5 rounded bg-red-500/10 text-red-500">
{wh.failure_count} failures
</span>
)}
</div>
<div className="flex items-center gap-2 mt-0.5">
{wh.description && (
<span className="text-[10px] text-text-muted">{wh.description}</span>
)}
<span className="text-[9px] text-text-muted">
Events: {wh.events.join(", ")}
</span>
{wh.last_triggered_at && (
<span className="text-[9px] text-text-muted">
Last: {new Date(wh.last_triggered_at).toLocaleString()}
{wh.last_status ? ` (${wh.last_status})` : ""}
</span>
)}
</div>
</div>
<div className="flex items-center gap-1 shrink-0 ml-2">
<button
onClick={() => testWebhook(wh.id)}
disabled={testingWebhookId === wh.id}
className="p-1 rounded hover:bg-black/5 dark:hover:bg-white/5 transition-colors"
title="Send test event"
>
<span
className={`material-symbols-outlined text-[14px] ${testingWebhookId === wh.id ? "animate-spin text-primary" : "text-text-muted"}`}
>
{testingWebhookId === wh.id ? "sync" : "send"}
</span>
</button>
<button
onClick={() => toggleWebhook(wh)}
className="p-1 rounded hover:bg-black/5 dark:hover:bg-white/5 transition-colors"
title={wh.enabled ? "Disable" : "Enable"}
>
<span
className={`material-symbols-outlined text-[14px] ${wh.enabled ? "text-emerald-500" : "text-text-muted"}`}
>
{wh.enabled ? "toggle_on" : "toggle_off"}
</span>
</button>
<button
onClick={() => deleteWebhook(wh.id)}
className="p-1 rounded hover:bg-red-500/10 transition-colors"
title="Delete"
>
<span className="material-symbols-outlined text-[14px] text-red-500">
delete
</span>
</button>
</div>
</div>
))}
</div>
)}
</Card>
{/* Webhook signature info */}
<Card className="p-4">
<div className="flex items-center gap-2 mb-2">
<span className="material-symbols-outlined text-[14px] text-amber-500">vpn_key</span>
<h3 className="text-xs font-semibold">Webhook Signatures</h3>
</div>
<p className="text-[11px] text-text-muted mb-2">
Each webhook delivery includes an{" "}
<code className="px-1 py-0.5 rounded bg-black/5 dark:bg-white/5">
X-Webhook-Signature
</code>{" "}
header signed with HMAC-SHA256 using the webhook secret. Verify the signature to
ensure the payload is authentic.
</p>
<div className="rounded-lg bg-black/5 dark:bg-black/30 p-3">
<code className="text-[10px] font-mono text-text-main">
{`const crypto = require('crypto');\nconst sig = 'sha256=' + crypto.createHmac('sha256', secret).update(body).digest('hex');\nif (sig !== req.headers['x-webhook-signature']) throw new Error('Invalid signature');`}
</code>
</div>
</Card>
</>
)}
</div>
);
}

File diff suppressed because it is too large Load Diff

View File

@@ -295,15 +295,11 @@ export default function A2ADashboardPage() {
};
if (loading) {
return (
<div className="p-6 max-w-7xl mx-auto">
<div className="text-sm text-text-muted">{t("loading")}</div>
</div>
);
return <div className="text-sm text-text-muted">{t("loading")}</div>;
}
return (
<div className="p-6 max-w-7xl mx-auto space-y-6">
<div className="space-y-6">
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-4 gap-4">
<StatCard label={t("health")} value={status?.status === "ok" ? t("ok") : "—"} />
<StatCard label={t("totalTasks")} value={status?.tasks?.total || 0} />

View File

@@ -355,15 +355,11 @@ export default function McpDashboardPage() {
const topTools = status?.activity?.topTools || [];
if (loading) {
return (
<div className="p-6 max-w-7xl mx-auto">
<div className="text-sm text-text-muted">{t("loading")}</div>
</div>
);
return <div className="text-sm text-text-muted">{t("loading")}</div>;
}
return (
<div className="p-6 max-w-7xl mx-auto space-y-6">
<div className="space-y-6">
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-4 gap-4">
<StatCard label={t("processStatus")} value={status?.online ? t("online") : t("offline")} />
<StatCard label={t("pid")} value={status?.heartbeat?.pid ?? "—"} />

View File

@@ -1,424 +1,5 @@
"use client";
import { useState, useEffect, useCallback } from "react";
import { Card, SegmentedControl } from "@/shared/components";
import EndpointPageClient from "./EndpointPageClient";
import McpDashboardPage from "./components/MCPDashboard";
import A2ADashboardPage from "./components/A2ADashboard";
import ApiEndpointsTab from "./ApiEndpointsTab";
import { useTranslations } from "next-intl";
import { copyToClipboard } from "@/shared/utils/clipboard";
type ServiceStatus = {
online: boolean;
loading: boolean;
};
type McpTransport = "stdio" | "sse" | "streamable-http";
/* ────── Toggle Switch ────── */
function ServiceToggle({
label,
status,
enabled,
onToggle,
toggling,
}: {
label: string;
status: ServiceStatus;
enabled: boolean;
onToggle: () => void;
toggling: boolean;
}) {
const online = enabled && status.online;
const loading = enabled && status.loading;
return (
<div className="flex items-center gap-3 ml-auto">
<div
className="flex items-center gap-1.5 px-2.5 py-1 rounded-full text-xs font-medium border"
style={{
borderColor: loading
? "var(--color-border)"
: online
? "rgba(34,197,94,0.3)"
: "rgba(239,68,68,0.3)",
background: loading
? "transparent"
: online
? "rgba(34,197,94,0.1)"
: "rgba(239,68,68,0.1)",
color: loading ? "var(--color-text-muted)" : online ? "rgb(34,197,94)" : "rgb(239,68,68)",
}}
>
<span
className="inline-block w-2 h-2 rounded-full"
style={{
background: loading
? "var(--color-text-muted)"
: online
? "rgb(34,197,94)"
: "rgb(239,68,68)",
animation: online ? "pulse 2s infinite" : "none",
}}
/>
{loading ? "..." : online ? "Online" : "Offline"}
</div>
<button
onClick={onToggle}
disabled={toggling}
className="relative inline-flex items-center h-7 w-[52px] rounded-full transition-all duration-300 focus:outline-none border"
style={{
background: enabled ? "rgb(34,197,94)" : "var(--color-bg-tertiary)",
borderColor: enabled ? "rgba(34,197,94,0.5)" : "var(--color-border)",
opacity: toggling ? 0.6 : 1,
cursor: toggling ? "wait" : "pointer",
}}
title={enabled ? `Disable ${label}` : `Enable ${label}`}
>
<span
className="inline-block w-5 h-5 rounded-full shadow-md transition-all duration-300"
style={{
transform: enabled ? "translateX(26px)" : "translateX(3px)",
background: enabled ? "#fff" : "var(--color-text-muted)",
}}
/>
</button>
<span
className="text-xs font-medium min-w-[24px]"
style={{ color: enabled ? "rgb(34,197,94)" : "var(--color-text-muted)" }}
>
{toggling ? "..." : enabled ? "ON" : "OFF"}
</span>
</div>
);
}
function DisabledServicePanel({ title, description }: { title: string; description: string }) {
return (
<Card className="p-6">
<div className="flex items-start gap-3">
<div
className="flex size-10 shrink-0 items-center justify-center overflow-hidden rounded-lg"
style={{ background: "var(--color-bg-tertiary)" }}
>
<span
aria-hidden="true"
className="relative block size-5 rounded-full border-2"
style={{ borderColor: "var(--color-text-muted)", color: "var(--color-text-muted)" }}
>
<span
className="absolute left-1/2 top-[-3px] h-3 w-0.5 -translate-x-1/2 rounded-full"
style={{ background: "var(--color-text-muted)" }}
/>
</span>
</div>
<div>
<h2 className="text-base font-semibold" style={{ color: "var(--color-text)" }}>
{title}
</h2>
<p className="text-sm mt-1" style={{ color: "var(--color-text-muted)" }}>
{description}
</p>
</div>
</div>
</Card>
);
}
/* ────── Transport Selector ────── */
function TransportSelector({
value,
onChange,
disabled,
baseUrl,
}: {
value: McpTransport;
onChange: (t: McpTransport) => void;
disabled: boolean;
baseUrl: string;
}) {
const options: { value: McpTransport; label: string; desc: string }[] = [
{ value: "stdio", label: "stdio", desc: "Local — IDE spawns process via omniroute --mcp" },
{ value: "sse", label: "SSE", desc: "Remote — Server-Sent Events over HTTP" },
{
value: "streamable-http",
label: "Streamable HTTP",
desc: "Remote — Modern bidirectional HTTP",
},
];
const urlMap: Record<McpTransport, string> = {
stdio: "omniroute --mcp",
sse: `${baseUrl}/api/mcp/sse`,
"streamable-http": `${baseUrl}/api/mcp/stream`,
};
return (
<div
className="rounded-lg border p-4 mt-3"
style={{ borderColor: "var(--color-border)", background: "var(--color-bg-secondary)" }}
>
<div className="flex items-center gap-2 mb-3">
<span
className="material-symbols-rounded text-base"
style={{ color: "var(--color-primary)" }}
>
swap_horiz
</span>
<span className="text-sm font-medium" style={{ color: "var(--color-text)" }}>
Transport Mode
</span>
</div>
<div className="flex gap-2 flex-wrap">
{options.map((opt) => (
<button
key={opt.value}
onClick={() => onChange(opt.value)}
disabled={disabled}
className="flex flex-col items-start px-4 py-2.5 rounded-lg border transition-all duration-200 text-left"
style={{
borderColor: value === opt.value ? "var(--color-primary)" : "var(--color-border)",
background:
value === opt.value
? "rgba(var(--color-primary-rgb, 99,102,241), 0.1)"
: "transparent",
opacity: disabled ? 0.5 : 1,
cursor: disabled ? "wait" : "pointer",
}}
>
<span
className="text-sm font-semibold"
style={{
color: value === opt.value ? "var(--color-primary)" : "var(--color-text)",
}}
>
{opt.label}
</span>
<span className="text-xs mt-0.5" style={{ color: "var(--color-text-muted)" }}>
{opt.desc}
</span>
</button>
))}
</div>
{/* Connection info */}
<div
className="mt-3 rounded-md px-3 py-2 flex items-center gap-2"
style={{ background: "var(--color-bg-tertiary)" }}
>
<span
className="material-symbols-rounded text-sm"
style={{ color: "var(--color-text-muted)" }}
>
{value === "stdio" ? "terminal" : "link"}
</span>
<code className="text-xs break-all" style={{ color: "var(--color-text-muted)" }}>
{urlMap[value]}
</code>
{value !== "stdio" && (
<button
className="ml-auto text-xs px-2 py-0.5 rounded border hover:opacity-80 transition-opacity"
style={{
borderColor: "var(--color-border)",
color: "var(--color-text-muted)",
}}
onClick={() => void copyToClipboard(urlMap[value])}
title="Copy URL"
>
Copy
</button>
)}
</div>
</div>
);
}
/* ────── Main Page ────── */
export default function EndpointPage() {
const [activeTab, setActiveTab] = useState("endpoint-proxy");
const t = useTranslations("endpoints");
const [mcpStatus, setMcpStatus] = useState<ServiceStatus>({ online: false, loading: true });
const [a2aStatus, setA2aStatus] = useState<ServiceStatus>({ online: false, loading: true });
const [mcpEnabled, setMcpEnabled] = useState(false);
const [a2aEnabled, setA2aEnabled] = useState(false);
const [mcpToggling, setMcpToggling] = useState(false);
const [a2aToggling, setA2aToggling] = useState(false);
const [mcpTransport, setMcpTransport] = useState<McpTransport>("stdio");
const [transportSaving, setTransportSaving] = useState(false);
const [baseUrl, setBaseUrl] = useState("");
// Detect base URL from browser
useEffect(() => {
if (typeof window !== "undefined") {
setBaseUrl(`${window.location.protocol}//${window.location.host}`);
}
}, []);
// Fetch initial settings
useEffect(() => {
const fetchSettings = async () => {
try {
const res = await fetch("/api/settings");
if (res.ok) {
const data = await res.json();
setMcpEnabled(!!data.mcpEnabled);
setA2aEnabled(!!data.a2aEnabled);
setMcpTransport((data.mcpTransport as McpTransport) || "stdio");
}
} catch {
// defaults stay
}
};
void fetchSettings();
}, []);
const patchSetting = useCallback(async (body: Record<string, unknown>) => {
return fetch("/api/settings", {
method: "PATCH",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(body),
});
}, []);
const toggleService = useCallback(
async (service: "mcp" | "a2a") => {
const setToggling = service === "mcp" ? setMcpToggling : setA2aToggling;
const setEnabled = service === "mcp" ? setMcpEnabled : setA2aEnabled;
const currentlyEnabled = service === "mcp" ? mcpEnabled : a2aEnabled;
const newValue = !currentlyEnabled;
setToggling(true);
try {
const res = await patchSetting({
[service === "mcp" ? "mcpEnabled" : "a2aEnabled"]: newValue,
});
if (res.ok) setEnabled(newValue);
} catch {
// keep current state
} finally {
setToggling(false);
}
},
[mcpEnabled, a2aEnabled, patchSetting]
);
const changeTransport = useCallback(
async (newTransport: McpTransport) => {
setTransportSaving(true);
try {
const res = await patchSetting({ mcpTransport: newTransport });
if (res.ok) setMcpTransport(newTransport);
} catch {
// keep current
} finally {
setTransportSaving(false);
}
},
[patchSetting]
);
const refreshMcpStatus = useCallback(async () => {
setMcpStatus((prev) => ({ ...prev, loading: true }));
try {
const res = await fetch("/api/mcp/status");
if (res.ok) {
const data = await res.json();
setMcpStatus({ online: !!data.online, loading: false });
} else {
setMcpStatus({ online: false, loading: false });
}
} catch {
setMcpStatus({ online: false, loading: false });
}
}, []);
const refreshA2aStatus = useCallback(async () => {
setA2aStatus((prev) => ({ ...prev, loading: true }));
try {
const res = await fetch("/api/a2a/status");
if (res.ok) {
const data = await res.json();
setA2aStatus({ online: data.status === "ok", loading: false });
} else {
setA2aStatus({ online: false, loading: false });
}
} catch {
setA2aStatus({ online: false, loading: false });
}
}, []);
useEffect(() => {
const load = () => {
void refreshMcpStatus();
void refreshA2aStatus();
};
load();
const interval = setInterval(load, 30000);
return () => clearInterval(interval);
}, [refreshMcpStatus, refreshA2aStatus]);
return (
<div className="flex flex-col gap-6">
<div className="flex flex-wrap items-center gap-3">
<SegmentedControl
options={[
{ value: "endpoint-proxy", label: t("tabProxy"), icon: "api" },
{ value: "mcp", label: "MCP", icon: "hub" },
{ value: "a2a", label: "A2A", icon: "group_work" },
{ value: "api-endpoints", label: t("tabApiEndpoints"), icon: "code" },
]}
value={activeTab}
onChange={setActiveTab}
/>
{activeTab === "mcp" && (
<ServiceToggle
label="MCP"
status={mcpStatus}
enabled={mcpEnabled}
onToggle={() => void toggleService("mcp")}
toggling={mcpToggling}
/>
)}
{activeTab === "a2a" && (
<ServiceToggle
label="A2A"
status={a2aStatus}
enabled={a2aEnabled}
onToggle={() => void toggleService("a2a")}
toggling={a2aToggling}
/>
)}
</div>
{/* Transport selector for MCP */}
{activeTab === "mcp" && mcpEnabled && (
<TransportSelector
value={mcpTransport}
onChange={(t) => void changeTransport(t)}
disabled={transportSaving}
baseUrl={baseUrl}
/>
)}
{activeTab === "endpoint-proxy" && <EndpointPageClient machineId="" />}
{activeTab === "mcp" && <McpDashboardPage />}
{activeTab === "a2a" &&
(a2aEnabled ? (
<A2ADashboardPage />
) : (
<DisabledServicePanel
title="A2A is disabled"
description="Enable A2A above to view task telemetry, agent details, and validation tools."
/>
))}
{activeTab === "api-endpoints" && <ApiEndpointsTab />}
</div>
);
return <EndpointPageClient machineId="" />;
}

View File

@@ -157,7 +157,7 @@ export default function HealthPage() {
if (!data && !error) {
return (
<div className="p-6 flex items-center justify-center min-h-[400px]">
<div className="flex items-center justify-center min-h-[400px]">
<div className="text-center">
<div className="inline-block animate-spin rounded-full h-8 w-8 border-b-2 border-primary" />
<p className="text-text-muted mt-4">{t("loadingHealth")}</p>
@@ -168,7 +168,7 @@ export default function HealthPage() {
if (error && !data) {
return (
<div className="p-6">
<div>
<div className="bg-red-500/10 border border-red-500/30 rounded-xl p-6 text-center">
<span className="material-symbols-outlined text-red-500 text-[32px] mb-2">error</span>
<p className="text-red-400">{t("failedToLoad", { error })}</p>
@@ -197,31 +197,24 @@ export default function HealthPage() {
const lockoutEntries = Object.entries(lockouts || {});
return (
<div className="p-6 space-y-6 max-w-6xl mx-auto">
{/* Header */}
<div className="flex items-center justify-between">
<div>
<h1 className="text-2xl font-bold text-text-main">{t("title")}</h1>
<p className="text-sm text-text-muted mt-1">{t("description")}</p>
</div>
<div className="flex items-center gap-3">
{lastRefresh && (
<span className="text-xs text-text-muted">
{t("updatedAt", { time: lastRefresh.toLocaleTimeString() })}
</span>
)}
<button
onClick={() => {
fetchHealth();
fetchExtras();
fetchDbHealth();
}}
className="p-2 rounded-lg bg-surface hover:bg-surface/80 text-text-muted hover:text-text-main transition-colors"
title={tc("refresh")}
>
<span className="material-symbols-outlined text-[18px]">refresh</span>
</button>
</div>
<div className="space-y-6">
<div className="flex items-center justify-end gap-3">
{lastRefresh && (
<span className="text-xs text-text-muted">
{t("updatedAt", { time: lastRefresh.toLocaleTimeString() })}
</span>
)}
<button
onClick={() => {
fetchHealth();
fetchExtras();
fetchDbHealth();
}}
className="p-2 rounded-lg bg-surface hover:bg-surface/80 text-text-muted hover:text-text-main transition-colors"
title={tc("refresh")}
>
<span className="material-symbols-outlined text-[18px]">refresh</span>
</button>
</div>
{/* Status Banner */}

View File

@@ -0,0 +1,7 @@
"use client";
import AuditLogTab from "../AuditLogTab";
export default function LogsActivityPage() {
return <AuditLogTab />;
}

View File

@@ -0,0 +1,7 @@
"use client";
import ConsoleLogViewer from "@/shared/components/ConsoleLogViewer";
export default function LogsConsolePage() {
return <ConsoleLogViewer />;
}

View File

@@ -1,11 +1,8 @@
"use client";
import { useState, useRef, useEffect } from "react";
import { useSearchParams } from "next/navigation";
import { RequestLoggerV2, ProxyLogger, SegmentedControl } from "@/shared/components";
import ConsoleLogViewer from "@/shared/components/ConsoleLogViewer";
import { RequestLoggerV2 } from "@/shared/components";
import ActiveRequestsPanel from "@/shared/components/ActiveRequestsPanel";
import AuditLogTab from "./AuditLogTab";
import { useTranslations } from "next-intl";
const TIME_RANGES = [
@@ -15,30 +12,14 @@ const TIME_RANGES = [
{ label: "24h", hours: 24 },
];
const TAB_TO_LOG_TYPE: Record<string, string> = {
"request-logs": "request-logs",
"proxy-logs": "proxy-logs",
"audit-logs": "call-logs",
console: "call-logs",
};
const LOG_TYPE = "request-logs";
export default function LogsPage() {
const searchParams = useSearchParams();
const requestedTab = searchParams.get("tab");
const [activeTab, setActiveTab] = useState(
requestedTab && TAB_TO_LOG_TYPE[requestedTab] ? requestedTab : "request-logs"
);
const [showExport, setShowExport] = useState(false);
const [exporting, setExporting] = useState(false);
const dropdownRef = useRef<HTMLDivElement>(null);
const t = useTranslations("logs");
useEffect(() => {
if (requestedTab && TAB_TO_LOG_TYPE[requestedTab] && requestedTab !== activeTab) {
setActiveTab(requestedTab);
}
}, [activeTab, requestedTab]);
useEffect(() => {
function handleClickOutside(e: MouseEvent) {
if (dropdownRef.current && !dropdownRef.current.contains(e.target as Node)) {
@@ -53,14 +34,13 @@ export default function LogsPage() {
setExporting(true);
setShowExport(false);
try {
const logType = TAB_TO_LOG_TYPE[activeTab] || "call-logs";
const res = await fetch(`/api/logs/export?hours=${hours}&type=${logType}`);
const res = await fetch(`/api/logs/export?hours=${hours}&type=${LOG_TYPE}`);
if (!res.ok) throw new Error(t("exportFailed"));
const blob = await res.blob();
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = `omniroute-${logType}-${hours}h-${new Date().toISOString().slice(0, 10)}.json`;
a.download = `omniroute-${LOG_TYPE}-${hours}h-${new Date().toISOString().slice(0, 10)}.json`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
@@ -74,18 +54,7 @@ export default function LogsPage() {
return (
<div className="flex flex-col gap-6">
<div className="flex items-center justify-between gap-4 flex-wrap">
<SegmentedControl
options={[
{ value: "request-logs", label: t("requestLogs") },
{ value: "proxy-logs", label: t("proxyLogs") },
{ value: "audit-logs", label: t("auditLog") },
{ value: "console", label: t("console") },
]}
value={activeTab}
onChange={setActiveTab}
/>
<div className="flex items-center justify-end gap-4 flex-wrap">
<div className="relative" ref={dropdownRef}>
<button
id="export-logs-btn"
@@ -143,16 +112,10 @@ export default function LogsPage() {
</div>
</div>
{/* Content */}
{activeTab === "request-logs" && (
<div className="flex flex-col gap-6">
<ActiveRequestsPanel />
<RequestLoggerV2 />
</div>
)}
{activeTab === "proxy-logs" && <ProxyLogger />}
{activeTab === "audit-logs" && <AuditLogTab />}
{activeTab === "console" && <ConsoleLogViewer />}
<div className="flex flex-col gap-6">
<ActiveRequestsPanel />
<RequestLoggerV2 />
</div>
</div>
);
}

View File

@@ -0,0 +1,7 @@
"use client";
import ProxyLogger from "@/shared/components/ProxyLogger";
export default function LogsProxyPage() {
return <ProxyLogger />;
}

View File

@@ -0,0 +1,355 @@
"use client";
import { useState, useEffect, useCallback } from "react";
import { Card } from "@/shared/components";
import { copyToClipboard } from "@/shared/utils/clipboard";
import McpDashboardPage from "../endpoint/components/MCPDashboard";
type ServiceStatus = { online: boolean; loading: boolean };
type McpTransport = "stdio" | "sse" | "streamable-http";
function ServiceToggle({
label,
status,
enabled,
onToggle,
toggling,
}: {
label: string;
status: ServiceStatus;
enabled: boolean;
onToggle: () => void;
toggling: boolean;
}) {
const online = enabled && status.online;
const loading = enabled && status.loading;
return (
<div className="flex items-center gap-3">
<div
className="flex items-center gap-1.5 px-2.5 py-1 rounded-full text-xs font-medium border"
style={{
borderColor: loading
? "var(--color-border)"
: online
? "rgba(34,197,94,0.3)"
: "rgba(239,68,68,0.3)",
background: loading
? "transparent"
: online
? "rgba(34,197,94,0.1)"
: "rgba(239,68,68,0.1)",
color: loading ? "var(--color-text-muted)" : online ? "rgb(34,197,94)" : "rgb(239,68,68)",
}}
>
<span
className="inline-block w-2 h-2 rounded-full"
style={{
background: loading
? "var(--color-text-muted)"
: online
? "rgb(34,197,94)"
: "rgb(239,68,68)",
animation: online ? "pulse 2s infinite" : "none",
}}
/>
{loading ? "..." : online ? "Online" : "Offline"}
</div>
<button
onClick={onToggle}
disabled={toggling}
className="relative inline-flex items-center h-7 w-[52px] rounded-full transition-all duration-300 focus:outline-none border"
style={{
background: enabled ? "rgb(34,197,94)" : "var(--color-bg-tertiary)",
borderColor: enabled ? "rgba(34,197,94,0.5)" : "var(--color-border)",
opacity: toggling ? 0.6 : 1,
cursor: toggling ? "wait" : "pointer",
}}
title={enabled ? `Disable ${label}` : `Enable ${label}`}
>
<span
className="inline-block w-5 h-5 rounded-full shadow-md transition-all duration-300"
style={{
transform: enabled ? "translateX(26px)" : "translateX(3px)",
background: enabled ? "#fff" : "var(--color-text-muted)",
}}
/>
</button>
<span
className="text-xs font-medium min-w-[24px]"
style={{ color: enabled ? "rgb(34,197,94)" : "var(--color-text-muted)" }}
>
{toggling ? "..." : enabled ? "ON" : "OFF"}
</span>
</div>
);
}
function TransportSelector({
value,
onChange,
disabled,
baseUrl,
}: {
value: McpTransport;
onChange: (t: McpTransport) => void;
disabled: boolean;
baseUrl: string;
}) {
const options: { value: McpTransport; label: string; desc: string }[] = [
{ value: "stdio", label: "stdio", desc: "Local — IDE spawns process via omniroute --mcp" },
{ value: "sse", label: "SSE", desc: "Remote — Server-Sent Events over HTTP" },
{
value: "streamable-http",
label: "Streamable HTTP",
desc: "Remote — Modern bidirectional HTTP",
},
];
const urlMap: Record<McpTransport, string> = {
stdio: "omniroute --mcp",
sse: `${baseUrl}/api/mcp/sse`,
"streamable-http": `${baseUrl}/api/mcp/stream`,
};
return (
<div
className="rounded-lg border p-4"
style={{ borderColor: "var(--color-border)", background: "var(--color-bg-secondary)" }}
>
<div className="flex items-center gap-2 mb-3">
<span
className="material-symbols-rounded text-base"
style={{ color: "var(--color-primary)" }}
>
swap_horiz
</span>
<span className="text-sm font-medium" style={{ color: "var(--color-text)" }}>
Transport Mode
</span>
</div>
<div className="flex gap-2 flex-wrap">
{options.map((opt) => (
<button
key={opt.value}
onClick={() => onChange(opt.value)}
disabled={disabled}
className="flex flex-col items-start px-4 py-2.5 rounded-lg border transition-all duration-200 text-left"
style={{
borderColor: value === opt.value ? "var(--color-primary)" : "var(--color-border)",
background:
value === opt.value
? "rgba(var(--color-primary-rgb, 99,102,241), 0.1)"
: "transparent",
opacity: disabled ? 0.5 : 1,
cursor: disabled ? "wait" : "pointer",
}}
>
<span
className="text-sm font-semibold"
style={{
color: value === opt.value ? "var(--color-primary)" : "var(--color-text)",
}}
>
{opt.label}
</span>
<span className="text-xs mt-0.5" style={{ color: "var(--color-text-muted)" }}>
{opt.desc}
</span>
</button>
))}
</div>
<div
className="mt-3 rounded-md px-3 py-2 flex items-center gap-2"
style={{ background: "var(--color-bg-tertiary)" }}
>
<span
className="material-symbols-rounded text-sm"
style={{ color: "var(--color-text-muted)" }}
>
{value === "stdio" ? "terminal" : "link"}
</span>
<code className="text-xs break-all" style={{ color: "var(--color-text-muted)" }}>
{urlMap[value]}
</code>
{value !== "stdio" && (
<button
className="ml-auto text-xs px-2 py-0.5 rounded border hover:opacity-80 transition-opacity"
style={{ borderColor: "var(--color-border)", color: "var(--color-text-muted)" }}
onClick={() => void copyToClipboard(urlMap[value])}
title="Copy URL"
>
Copy
</button>
)}
</div>
</div>
);
}
function DisabledPanel() {
return (
<Card className="p-6">
<div className="flex items-start gap-3">
<div
className="flex size-10 shrink-0 items-center justify-center overflow-hidden rounded-lg"
style={{ background: "var(--color-bg-tertiary)" }}
>
<span
aria-hidden="true"
className="relative block size-5 rounded-full border-2"
style={{ borderColor: "var(--color-text-muted)" }}
>
<span
className="absolute left-1/2 top-[-3px] h-3 w-0.5 -translate-x-1/2 rounded-full"
style={{ background: "var(--color-text-muted)" }}
/>
</span>
</div>
<div>
<h2 className="text-base font-semibold" style={{ color: "var(--color-text)" }}>
MCP is disabled
</h2>
<p className="text-sm mt-1" style={{ color: "var(--color-text-muted)" }}>
Enable MCP above to configure transport mode and view server telemetry.
</p>
</div>
</div>
</Card>
);
}
export default function McpPage() {
const [mcpStatus, setMcpStatus] = useState<ServiceStatus>({ online: false, loading: true });
const [mcpEnabled, setMcpEnabled] = useState(false);
const [mcpToggling, setMcpToggling] = useState(false);
const [mcpTransport, setMcpTransport] = useState<McpTransport>("stdio");
const [transportSaving, setTransportSaving] = useState(false);
const [baseUrl, setBaseUrl] = useState("");
useEffect(() => {
if (typeof window !== "undefined") {
setBaseUrl(`${window.location.protocol}//${window.location.host}`);
}
}, []);
const patchSetting = useCallback(async (body: Record<string, unknown>) => {
return fetch("/api/settings", {
method: "PATCH",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(body),
});
}, []);
useEffect(() => {
const fetchSettings = async () => {
try {
const res = await fetch("/api/settings");
if (res.ok) {
const data = await res.json();
setMcpEnabled(!!data.mcpEnabled);
setMcpTransport((data.mcpTransport as McpTransport) || "stdio");
}
} catch {
// defaults stay
}
};
void fetchSettings();
}, []);
const refreshStatus = useCallback(async () => {
setMcpStatus((prev) => ({ ...prev, loading: true }));
try {
const res = await fetch("/api/mcp/status");
setMcpStatus({ online: res.ok ? !!(await res.json()).online : false, loading: false });
} catch {
setMcpStatus({ online: false, loading: false });
}
}, []);
useEffect(() => {
void refreshStatus();
const interval = setInterval(() => void refreshStatus(), 30000);
return () => clearInterval(interval);
}, [refreshStatus]);
const toggleMcp = useCallback(async () => {
const newValue = !mcpEnabled;
setMcpToggling(true);
try {
const res = await patchSetting({ mcpEnabled: newValue });
if (res.ok) setMcpEnabled(newValue);
} catch {
// keep current
} finally {
setMcpToggling(false);
}
}, [mcpEnabled, patchSetting]);
const changeTransport = useCallback(
async (newTransport: McpTransport) => {
setTransportSaving(true);
try {
const res = await patchSetting({ mcpTransport: newTransport });
if (res.ok) setMcpTransport(newTransport);
} catch {
// keep current
} finally {
setTransportSaving(false);
}
},
[patchSetting]
);
return (
<div className="flex flex-col gap-6">
<Card>
<div className="flex items-start justify-between gap-4">
<div>
<p className="text-sm" style={{ color: "var(--color-text-muted)" }}>
Model Context Protocol 37 tools across 13 scopes, 3 transports (stdio / SSE /
Streamable HTTP).
</p>
<ol
className="mt-2 text-sm space-y-0.5 list-decimal list-inside"
style={{ color: "var(--color-text-muted)" }}
>
<li>
Run via <code className="text-xs">omniroute --mcp</code>
</li>
<li>Configure your MCP client to connect over stdio transport.</li>
<li>
Invoke tools like <code className="text-xs">omniroute_get_health</code> and{" "}
<code className="text-xs">omniroute_list_combos</code>.
</li>
</ol>
</div>
<div className="shrink-0">
<ServiceToggle
label="MCP"
status={mcpStatus}
enabled={mcpEnabled}
onToggle={() => void toggleMcp()}
toggling={mcpToggling}
/>
</div>
</div>
</Card>
{mcpEnabled && (
<TransportSelector
value={mcpTransport}
onChange={(t) => void changeTransport(t)}
disabled={transportSaving}
baseUrl={baseUrl}
/>
)}
{mcpEnabled ? <McpDashboardPage /> : <DisabledPanel />}
</div>
);
}

View File

@@ -203,31 +203,28 @@ export default function MemoryPage() {
}
return (
<div className="space-y-6 p-6">
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<h1 className="text-2xl font-bold">{t("title")}</h1>
<div className="flex items-center gap-2">
{health !== null && (
<span
className={`inline-block w-3 h-3 rounded-full ${health.working ? "bg-green-500" : "bg-red-500"}`}
title={
health.working
? t("pipelineOk", { latencyMs: health.latencyMs })
: t("pipelineError")
}
/>
)}
{health === null && !checkingHealth && (
<span
className="inline-block w-3 h-3 rounded-full bg-gray-400"
title={t("healthUnknown")}
/>
)}
<Button variant="outline" size="sm" onClick={checkHealth} disabled={checkingHealth}>
{checkingHealth ? t("checkingHealth") : t("checkHealth")}
</Button>
</div>
<div className="space-y-6">
<div className="flex items-center justify-end gap-3">
<div className="flex items-center gap-2">
{health !== null && (
<span
className={`inline-block w-3 h-3 rounded-full ${health.working ? "bg-green-500" : "bg-red-500"}`}
title={
health.working
? t("pipelineOk", { latencyMs: health.latencyMs })
: t("pipelineError")
}
/>
)}
{health === null && !checkingHealth && (
<span
className="inline-block w-3 h-3 rounded-full bg-gray-400"
title={t("healthUnknown")}
/>
)}
<Button variant="outline" size="sm" onClick={checkHealth} disabled={checkingHealth}>
{checkingHealth ? t("checkingHealth") : t("checkHealth")}
</Button>
</div>
<div className="flex gap-2">
<input

View File

@@ -1,23 +1,7 @@
import { redirect } from "next/navigation";
import { getMachineId } from "@/shared/utils/machine";
import { getSettings } from "@/lib/localDb";
import HomePageClient from "./HomePageClient";
import BootstrapBanner from "./BootstrapBanner";
// Must be dynamic — depends on DB state (setupComplete) that changes at runtime
export const dynamic = "force-dynamic";
export default async function DashboardPage() {
const settings = await getSettings();
if (!settings.setupComplete) {
redirect("/dashboard/onboarding");
}
const machineId = await getMachineId();
const isBootstrapped = process.env.OMNIROUTE_BOOTSTRAPPED === "true";
return (
<>
{isBootstrapped && <BootstrapBanner />}
<HomePageClient machineId={machineId} />
</>
);
export default function DashboardPage() {
redirect("/home");
}

View File

@@ -19,9 +19,11 @@ import {
Toggle,
Select,
ProxyConfigModal,
NoAuthProviderCard,
} from "@/shared/components";
import {
LOCAL_PROVIDERS,
FREE_PROVIDERS,
getProviderAlias,
isOpenAICompatibleProvider,
isAnthropicCompatibleProvider,
@@ -1086,6 +1088,7 @@ export default function ProviderDetailPage() {
providerInfo?.toggleAuthType === "oauth" || providerInfo?.toggleAuthType === "free";
const providerSupportsPat = supportsApiKeyOnFreeProvider(providerId);
const isOAuth = providerSupportsOAuth && !providerSupportsPat;
const isFreeNoAuth = FREE_PROVIDERS[providerId]?.noAuth === true;
const registryModels = getModelsByProviderId(providerId);
// Prefer synced API-discovered models when available, then merge built-ins
// and user-managed custom models without duplicating IDs.
@@ -2545,7 +2548,7 @@ export default function ProviderDetailPage() {
}
};
const canImportModels = connections.some((conn) => conn.isActive !== false);
const canImportModels = isFreeNoAuth || connections.some((conn) => conn.isActive !== false);
// Auto-sync toggle state: read from first active connection's providerSpecificData
const autoSyncConnection = connections.find((conn: any) => conn.isActive !== false);
@@ -3174,7 +3177,8 @@ export default function ProviderDetailPage() {
)}
{/* Connections */}
{!isUpstreamProxyProvider && (
{!isUpstreamProxyProvider && isFreeNoAuth && <NoAuthProviderCard />}
{!isUpstreamProxyProvider && !isFreeNoAuth && (
<Card>
<div className="mb-4 flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
<div className="flex min-w-0 flex-wrap items-center gap-2">

View File

@@ -51,6 +51,7 @@ const DOT_COLORS: Record<string, string> = {
audio: "bg-rose-500",
local: "bg-emerald-500",
"upstream-proxy": "bg-indigo-500",
"cloud-agent": "bg-violet-500",
};
function getStatusDisplay(
@@ -149,25 +150,27 @@ export default function ProviderCard({
<div className="flex items-center justify-between">
<div className="flex items-center gap-3 min-w-0 pr-2">
<div
className="size-8 rounded-lg flex items-center justify-center shrink-0"
className="size-7 rounded-lg flex items-center justify-center shrink-0"
style={{ backgroundColor: `${provider.color || "#64748b"}15` }}
>
{staticIconPath ? (
<Image
src={staticIconPath}
alt={provider.name}
width={30}
height={30}
className="object-contain rounded-lg max-w-[30px] max-h-[30px]"
sizes="30px"
width={26}
height={26}
className="object-contain rounded-lg max-w-[26px] max-h-[26px]"
sizes="26px"
/>
) : (
<ProviderIcon providerId={provider.id || providerId} size={28} type="color" />
<ProviderIcon providerId={provider.id || providerId} size={24} type="color" />
)}
</div>
<div className="min-w-0">
<h3 className="font-semibold flex items-center gap-1.5 truncate">
<span className={provider.deprecated ? "line-through opacity-60" : ""}>
<h3 className="text-sm font-semibold flex items-center gap-1 min-w-0">
<span
className={`truncate min-w-0 flex-1 ${provider.deprecated ? "line-through opacity-60" : ""}`}
>
{provider.name}
</span>
{provider.deprecated && (
@@ -183,9 +186,15 @@ export default function ProviderCard({
</Badge>
)}
<span
className={`size-2 rounded-full ${DOT_COLORS[authType] || DOT_COLORS.apikey} shrink-0`}
className={`size-2 rounded-full shrink-0 ${DOT_COLORS[authType] || DOT_COLORS.apikey}`}
title={dotLabels[authType] || t("apiKeyLabel")}
/>
{provider.hasFree === true && authType !== "free" && (
<span
className="size-2 rounded-full shrink-0 bg-green-500"
title={provider.freeNote || t("freeTierAvailable")}
/>
)}
</h3>
<div className="flex items-center gap-2 text-xs flex-wrap">
{allDisabled ? (
@@ -198,18 +207,6 @@ export default function ProviderCard({
) : (
<>
{getStatusDisplay(connected, error, stats.errorCode, t, codexFastChip)}
{(authType === "free" || provider.hasFree === true) && (
<Badge
variant="success"
size="sm"
title={provider.freeNote || t("freeTierAvailable")}
>
<span className="flex items-center gap-0.5">
<span className="material-symbols-outlined text-[10px]">redeem</span>
{t("freeTier")}
</span>
</Badge>
)}
{stats.expiryStatus === "expired" && (
<Badge variant="error" size="sm" dot>
{t("expiredBadge")}
@@ -247,7 +244,7 @@ export default function ProviderCard({
{Number(stats.total || 0) > 0 && (
<div onClick={handleToggle}>
<Toggle
size="sm"
size="xs"
checked={!allDisabled}
onChange={() => {}}
title={allDisabled ? t("enableProvider") : t("disableProvider")}

View File

@@ -9,7 +9,7 @@ export default function ProvidersError({
}) {
return (
<div
className="flex flex-col items-center justify-center min-h-[400px] p-6"
className="flex flex-col items-center justify-center min-h-[400px]"
role="alert"
aria-live="assertive"
>

View File

@@ -4,7 +4,7 @@ import { CardSkeleton, Skeleton } from "@/shared/components/Loading";
export default function ProvidersLoading() {
return (
<div className="space-y-6 p-6" role="status" aria-live="polite" aria-busy="true">
<div className="space-y-6" role="status" aria-live="polite" aria-busy="true">
<Skeleton className="h-8 w-48" />
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{[0, 1, 2].map((index) => (

View File

@@ -1,7 +1,7 @@
"use client";
import { useState, useEffect, useCallback } from "react";
import { CardSkeleton, Badge, Button, Input, Toggle } from "@/shared/components";
import { Card, CardSkeleton, Badge, Button, Input, Toggle } from "@/shared/components";
import {
FREE_PROVIDERS,
OAUTH_PROVIDERS,
@@ -532,6 +532,57 @@ export default function ProvidersPage() {
searchQuery
);
const FREE_SECTION_IDS = new Set([
"kiro",
"amazon-q",
"gemini-cli",
"qoder",
"pollinations",
"llm7",
"opencode",
"gemini",
"groq",
"cerebras",
"mistral",
"nvidia",
"openrouter",
"cloudflare-ai",
"together",
"siliconflow",
"deepseek",
"longcat",
"glhf",
"morph",
"bazaarlink",
"uncloseai",
"completions",
"freetheai",
"enally",
"puter",
"blackbox",
]);
const freeSectionEntriesAll = [...oauthProviderEntriesAll, ...apiKeyProviderEntriesAll].filter(
(e) => FREE_SECTION_IDS.has(e.providerId)
);
const freeSectionEntries = filterConfiguredProviderEntries(
freeSectionEntriesAll,
showConfiguredOnly,
searchQuery
);
const oauthOnlyEntriesAll = oauthProviderEntriesAll.filter((e) => e.toggleAuthType === "oauth");
const summaryStats = {
all: {
configured:
oauthProviderEntriesAll.filter((e) => Number(e.stats?.total || 0) > 0).length +
apiKeyProviderEntriesAll.filter((e) => Number(e.stats?.total || 0) > 0).length,
total: oauthProviderEntriesAll.length + apiKeyProviderEntriesAll.length,
},
free: countConfigured(freeSectionEntriesAll),
oauth: countConfigured(oauthOnlyEntriesAll),
apikey: countConfigured(apiKeyProviderEntriesAll),
};
if (loading) {
return (
<div className="flex flex-col gap-8">
@@ -543,29 +594,164 @@ export default function ProvidersPage() {
return (
<div className="flex flex-col gap-6">
{/* Search Bar */}
<div className="flex items-center gap-3">
<div className="relative flex-1">
<span className="material-symbols-outlined absolute left-3 top-1/2 -translate-y-1/2 text-text-muted text-[20px]">
search
</span>
<Input
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
placeholder={t("searchProviders")}
aria-label={t("searchProviders")}
className="pl-10 pr-10"
/>
{searchQuery && (
{/* Provider Summary Card */}
<Card padding="sm">
<div className="flex flex-col gap-3">
{/* Row 1: Search + Controls */}
<div className="flex items-center gap-3 flex-wrap">
<div className="relative flex-1 min-w-[160px]">
<Input
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
placeholder={t("searchProviders")}
aria-label={t("searchProviders")}
icon="search"
inputClassName={searchQuery ? "pr-9" : ""}
/>
{searchQuery && (
<button
onClick={() => setSearchQuery("")}
className="absolute inset-y-0 right-0 flex items-center pr-2.5 text-text-muted hover:text-text-primary transition-colors"
aria-label={tc("clear")}
>
<span className="material-symbols-outlined text-[18px]">close</span>
</button>
)}
</div>
<Toggle
size="sm"
checked={showConfiguredOnly}
onChange={setShowConfiguredOnly}
label={t("showConfiguredOnly")}
className="rounded-lg border border-border bg-bg-subtle px-3 py-1.5"
/>
<button
onClick={() => setSearchQuery("")}
className="absolute inset-y-0 right-0 flex items-center pr-3 text-text-muted hover:text-text-primary transition-colors"
aria-label={tc("clear")}
onClick={() => handleBatchTest("all")}
disabled={!!testingMode}
className={`flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-xs font-medium border transition-colors ${
testingMode === "all"
? "bg-primary/20 border-primary/40 text-primary animate-pulse"
: "bg-bg-subtle border-border text-text-muted hover:text-text-primary hover:border-primary/40"
}`}
title={t("testAll")}
>
<span className="material-symbols-outlined text-[20px]">close</span>
<span className="material-symbols-outlined text-[14px]">
{testingMode === "all" ? "sync" : "play_arrow"}
</span>
{testingMode === "all" ? t("testing") : t("testAll")}
</button>
)}
</div>
{/* Row 2: Legend */}
<div className="flex flex-wrap items-center gap-x-3 gap-y-0.5 text-[11px] text-text-muted select-none">
{(
[
["bg-green-500", tc("free")],
["bg-blue-500", t("oauthLabel")],
["bg-amber-500", t("apiKeyLabel")],
["bg-orange-500", t("compatibleLabel")],
["bg-purple-500", t("webCookieProviders")],
["bg-teal-500", t("searchProvidersHeading")],
["bg-rose-500", t("audioProvidersHeading")],
["bg-emerald-500", t("localProviders")],
["bg-indigo-500", t("upstreamProxyProviders")],
["bg-violet-500", t("cloudAgentProviders")],
] as [string, string][]
).map(([color, label]) => (
<span key={color} className="flex items-center gap-1 whitespace-nowrap">
<span className={`size-2 rounded-full shrink-0 ${color}`} />
{label}
</span>
))}
</div>
{/* Divider + Stats */}
<div className="border-t border-border pt-3 flex flex-wrap items-center gap-x-5 gap-y-1">
{(
[
[null, t("providerSummaryAll"), summaryStats.all],
["bg-green-500", tc("free"), summaryStats.free],
["bg-blue-500", t("oauthLabel"), summaryStats.oauth],
["bg-amber-500", t("apiKeyLabel"), summaryStats.apikey],
] as [string | null, string, { configured: number; total: number }][]
).map(([color, label, stat]) => (
<span key={label} className="flex items-center gap-1.5">
{color && <span className={`size-2 rounded-full shrink-0 ${color}`} />}
<span className="text-xs text-text-muted">{label}</span>
<span className="text-sm font-semibold text-text-main">
{stat.configured}
<span className="font-normal text-text-muted">/{stat.total}</span>
</span>
</span>
))}
</div>
</div>
</Card>
{/* API Key Compatible Providers — dynamic (OpenAI/Anthropic compatible) */}
<div className="flex flex-col gap-4">
<div className="flex flex-wrap items-center gap-2">
<h2 className="text-xl font-semibold flex items-center gap-2 flex-1 min-w-0">
{t("compatibleProviders")}{" "}
<span className="size-2.5 rounded-full bg-orange-500" title={t("compatibleLabel")} />
<ProviderCountBadge {...countConfigured(compatibleProviderEntriesAll)} />
</h2>
<div className="flex flex-wrap gap-2">
{(compatibleProviders.length > 0 ||
anthropicCompatibleProviders.length > 0 ||
ccCompatibleProviders.length > 0) && (
<button
onClick={() => handleBatchTest("compatible")}
disabled={!!testingMode}
className={`flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-xs font-medium border transition-colors ${
testingMode === "compatible"
? "bg-primary/20 border-primary/40 text-primary animate-pulse"
: "bg-bg-subtle border-border text-text-muted hover:text-text-primary hover:border-primary/40"
}`}
title={t("testAllCompatible")}
>
<span className="material-symbols-outlined text-[14px]">
{testingMode === "compatible" ? "sync" : "play_arrow"}
</span>
{testingMode === "compatible" ? t("testing") : t("testAll")}
</button>
)}
{ccCompatibleProviderEnabled && (
<Button size="sm" icon="add" onClick={() => setShowAddCcCompatibleModal(true)}>
{addCcCompatibleLabel}
</Button>
)}
<Button size="sm" icon="add" onClick={() => setShowAddAnthropicCompatibleModal(true)}>
{t("addAnthropicCompatible")}
</Button>
<Button size="sm" icon="add" onClick={() => setShowAddCompatibleModal(true)}>
{t("addOpenAICompatible")}
</Button>
</div>
</div>
{compatibleProviders.length === 0 &&
anthropicCompatibleProviders.length === 0 &&
ccCompatibleProviders.length === 0 ? (
<div className="flex items-center justify-center gap-2 py-2 border border-dashed border-border rounded-xl text-text-muted text-sm">
<span className="material-symbols-outlined text-[18px]">extension</span>
<span>{t("noCompatibleYet")}</span>
</div>
) : (
<div className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 gap-3">
{compatibleProviderEntries.map(
({ providerId, provider, stats, displayAuthType, toggleAuthType }) => (
<ProviderCard
key={providerId}
providerId={providerId}
provider={provider}
stats={stats}
authType={displayAuthType}
onToggle={(active) => handleToggleProvider(providerId, toggleAuthType, active)}
/>
)
)}
</div>
)}
</div>
{/* Expiration Banner */}
@@ -604,6 +790,51 @@ export default function ProvidersPage() {
</div>
)}
{/* Free Tier Providers */}
{freeSectionEntries.length > 0 && (
<div className="flex flex-col gap-4">
<div className="flex flex-wrap items-start gap-2">
<div className="flex-1 min-w-0">
<h2 className="text-xl font-semibold flex items-center gap-2">
{t("freeTierProviders")}
<span className="size-2.5 rounded-full bg-green-500" title={t("freeTierLabel")} />
<ProviderCountBadge {...countConfigured(freeSectionEntriesAll)} />
</h2>
<p className="text-sm text-text-muted mt-1">{t("freeTierProvidersDesc")}</p>
</div>
<button
onClick={() => handleBatchTest("free")}
disabled={!!testingMode}
className={`flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-xs font-medium border transition-colors ${
testingMode === "free"
? "bg-primary/20 border-primary/40 text-primary animate-pulse"
: "bg-bg-subtle border-border text-text-muted hover:text-text-primary hover:border-primary/40"
}`}
title={t("testAll")}
>
<span className="material-symbols-outlined text-[14px]">
{testingMode === "free" ? "sync" : "play_arrow"}
</span>
{testingMode === "free" ? t("testing") : t("testAll")}
</button>
</div>
<div className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 gap-3">
{freeSectionEntries.map(
({ providerId, provider, stats, displayAuthType, toggleAuthType }) => (
<ProviderCard
key={`free-section-${providerId}`}
providerId={providerId}
provider={{ ...provider, hasFree: false }}
stats={stats}
authType={toggleAuthType === "free" ? "free" : displayAuthType}
onToggle={(active) => handleToggleProvider(providerId, toggleAuthType, active)}
/>
)
)}
</div>
</div>
)}
{/* OAuth Providers (including providers that expose free tiers via OAuth) */}
<div className="flex flex-col gap-4">
<div className="flex flex-wrap items-center gap-2">
@@ -613,13 +844,6 @@ export default function ProvidersPage() {
<ProviderCountBadge {...countConfigured(oauthProviderEntriesAll)} />
</h2>
<div className="flex items-center gap-2">
<Toggle
size="sm"
checked={showConfiguredOnly}
onChange={setShowConfiguredOnly}
label={t("showConfiguredOnly")}
className="rounded-lg border border-border bg-bg-subtle px-3 py-1.5"
/>
<button
onClick={handleZedImport}
disabled={importingZed}
@@ -669,7 +893,7 @@ export default function ProvidersPage() {
</button>
</div>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
<div className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 gap-3">
{oauthProviderEntries.map(
({ providerId, provider, stats, displayAuthType, toggleAuthType }) => (
<ProviderCard
@@ -715,7 +939,7 @@ export default function ProvidersPage() {
<h3 className="text-xs font-semibold uppercase tracking-wider text-text-muted">
{t("llmProviders")}
</h3>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
<div className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 gap-3">
{llmProviderEntries.map(
({ providerId, provider, stats, displayAuthType, toggleAuthType }) => (
<ProviderCard
@@ -737,7 +961,7 @@ export default function ProvidersPage() {
<h3 className="text-xs font-semibold uppercase tracking-wider text-text-muted">
{t("aggregatorsGateways")}
</h3>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
<div className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 gap-3">
{aggregatorProviderEntries.map(
({ providerId, provider, stats, displayAuthType, toggleAuthType }) => (
<ProviderCard
@@ -759,7 +983,7 @@ export default function ProvidersPage() {
<h3 className="text-xs font-semibold uppercase tracking-wider text-text-muted">
{t("enterpriseCloud")}
</h3>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
<div className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 gap-3">
{enterpriseProviderEntries.map(
({ providerId, provider, stats, displayAuthType, toggleAuthType }) => (
<ProviderCard
@@ -781,7 +1005,7 @@ export default function ProvidersPage() {
<h3 className="text-xs font-semibold uppercase tracking-wider text-text-muted">
{t("imageProviders")}
</h3>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
<div className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 gap-3">
{imageProviderEntries.map(
({ providerId, provider, stats, displayAuthType, toggleAuthType }) => (
<ProviderCard
@@ -803,7 +1027,7 @@ export default function ProvidersPage() {
<h3 className="text-xs font-semibold uppercase tracking-wider text-text-muted">
{t("videoProviders")}
</h3>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
<div className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 gap-3">
{videoProviderEntries.map(
({ providerId, provider, stats, displayAuthType, toggleAuthType }) => (
<ProviderCard
@@ -825,7 +1049,7 @@ export default function ProvidersPage() {
<h3 className="text-xs font-semibold uppercase tracking-wider text-text-muted">
{t("embeddingRerankProviders")}
</h3>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
<div className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 gap-3">
{embeddingRerankProviderEntries.map(
({ providerId, provider, stats, displayAuthType, toggleAuthType }) => (
<ProviderCard
@@ -871,19 +1095,17 @@ export default function ProvidersPage() {
{testingMode === "web-cookie" ? t("testing") : t("testAll")}
</button>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
{webCookieProviderEntries.map(
({ providerId, provider, stats, displayAuthType, toggleAuthType }) => (
<ProviderCard
key={providerId}
providerId={providerId}
provider={provider}
stats={stats}
authType={displayAuthType}
onToggle={(active) => handleToggleProvider(providerId, toggleAuthType, active)}
/>
)
)}
<div className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 gap-3">
{webCookieProviderEntries.map(({ providerId, provider, stats, toggleAuthType }) => (
<ProviderCard
key={providerId}
providerId={providerId}
provider={provider}
stats={stats}
authType="web-cookie"
onToggle={(active) => handleToggleProvider(providerId, toggleAuthType, active)}
/>
))}
</div>
</div>
)}
@@ -916,19 +1138,17 @@ export default function ProvidersPage() {
{testingMode === "search" ? t("testing") : t("testAll")}
</button>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
{searchProviderEntries.map(
({ providerId, provider, stats, displayAuthType, toggleAuthType }) => (
<ProviderCard
key={providerId}
providerId={providerId}
provider={provider}
stats={stats}
authType={displayAuthType}
onToggle={(active) => handleToggleProvider(providerId, toggleAuthType, active)}
/>
)
)}
<div className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 gap-3">
{searchProviderEntries.map(({ providerId, provider, stats, toggleAuthType }) => (
<ProviderCard
key={providerId}
providerId={providerId}
provider={provider}
stats={stats}
authType="search"
onToggle={(active) => handleToggleProvider(providerId, toggleAuthType, active)}
/>
))}
</div>
</div>
)}
@@ -961,19 +1181,17 @@ export default function ProvidersPage() {
{testingMode === "audio" ? t("testing") : t("testAll")}
</button>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
{audioProviderEntries.map(
({ providerId, provider, stats, displayAuthType, toggleAuthType }) => (
<ProviderCard
key={providerId}
providerId={providerId}
provider={provider}
stats={stats}
authType={displayAuthType}
onToggle={(active) => handleToggleProvider(providerId, toggleAuthType, active)}
/>
)
)}
<div className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 gap-3">
{audioProviderEntries.map(({ providerId, provider, stats, toggleAuthType }) => (
<ProviderCard
key={providerId}
providerId={providerId}
provider={provider}
stats={stats}
authType="audio"
onToggle={(active) => handleToggleProvider(providerId, toggleAuthType, active)}
/>
))}
</div>
</div>
)}
@@ -1006,19 +1224,17 @@ export default function ProvidersPage() {
{testingMode === "cloud-agent" ? t("testing") : t("testAll")}
</button>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
{cloudAgentProviderEntries.map(
({ providerId, provider, stats, displayAuthType, toggleAuthType }) => (
<ProviderCard
key={providerId}
providerId={providerId}
provider={provider}
stats={stats}
authType={displayAuthType}
onToggle={(active) => handleToggleProvider(providerId, toggleAuthType, active)}
/>
)
)}
<div className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 gap-3">
{cloudAgentProviderEntries.map(({ providerId, provider, stats, toggleAuthType }) => (
<ProviderCard
key={providerId}
providerId={providerId}
provider={provider}
stats={stats}
authType="cloud-agent"
onToggle={(active) => handleToggleProvider(providerId, toggleAuthType, active)}
/>
))}
</div>
</div>
)}
@@ -1048,19 +1264,17 @@ export default function ProvidersPage() {
{testingMode === "local" ? t("testing") : t("testAll")}
</button>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
{localProviderEntries.map(
({ providerId, provider, stats, displayAuthType, toggleAuthType }) => (
<ProviderCard
key={providerId}
providerId={providerId}
provider={provider}
stats={stats}
authType={displayAuthType}
onToggle={(active) => handleToggleProvider(providerId, toggleAuthType, active)}
/>
)
)}
<div className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 gap-3">
{localProviderEntries.map(({ providerId, provider, stats, toggleAuthType }) => (
<ProviderCard
key={providerId}
providerId={providerId}
provider={provider}
stats={stats}
authType="local"
onToggle={(active) => handleToggleProvider(providerId, toggleAuthType, active)}
/>
))}
</div>
</div>
)}
@@ -1093,91 +1307,21 @@ export default function ProvidersPage() {
{testingMode === "upstream-proxy" ? t("testing") : t("testAll")}
</button>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
{upstreamProxyEntries.map(
({ providerId, provider, stats, displayAuthType, toggleAuthType }) => (
<ProviderCard
key={providerId}
providerId={providerId}
provider={provider}
stats={stats}
authType={displayAuthType}
onToggle={(active) => handleToggleProvider(providerId, toggleAuthType, active)}
/>
)
)}
<div className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 gap-3">
{upstreamProxyEntries.map(({ providerId, provider, stats, toggleAuthType }) => (
<ProviderCard
key={providerId}
providerId={providerId}
provider={provider}
stats={stats}
authType="upstream-proxy"
onToggle={(active) => handleToggleProvider(providerId, toggleAuthType, active)}
/>
))}
</div>
</div>
)}
{/* API Key Compatible Providers — dynamic (OpenAI/Anthropic compatible) */}
<div className="flex flex-col gap-4">
<div className="flex flex-wrap items-center gap-2">
<h2 className="text-xl font-semibold flex items-center gap-2 flex-1 min-w-0">
{t("compatibleProviders")}{" "}
<span className="size-2.5 rounded-full bg-orange-500" title={t("compatibleLabel")} />
<ProviderCountBadge {...countConfigured(compatibleProviderEntriesAll)} />
</h2>
<div className="flex flex-wrap gap-2">
{(compatibleProviders.length > 0 ||
anthropicCompatibleProviders.length > 0 ||
ccCompatibleProviders.length > 0) && (
<button
onClick={() => handleBatchTest("compatible")}
disabled={!!testingMode}
className={`flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-xs font-medium border transition-colors ${
testingMode === "compatible"
? "bg-primary/20 border-primary/40 text-primary animate-pulse"
: "bg-bg-subtle border-border text-text-muted hover:text-text-primary hover:border-primary/40"
}`}
title={t("testAllCompatible")}
>
<span className="material-symbols-outlined text-[14px]">
{testingMode === "compatible" ? "sync" : "play_arrow"}
</span>
{testingMode === "compatible" ? t("testing") : t("testAll")}
</button>
)}
{ccCompatibleProviderEnabled && (
<Button size="sm" icon="add" onClick={() => setShowAddCcCompatibleModal(true)}>
{addCcCompatibleLabel}
</Button>
)}
<Button size="sm" icon="add" onClick={() => setShowAddAnthropicCompatibleModal(true)}>
{t("addAnthropicCompatible")}
</Button>
<Button size="sm" icon="add" onClick={() => setShowAddCompatibleModal(true)}>
{t("addOpenAICompatible")}
</Button>
</div>
</div>
{compatibleProviders.length === 0 &&
anthropicCompatibleProviders.length === 0 &&
ccCompatibleProviders.length === 0 ? (
<div className="text-center py-8 border border-dashed border-border rounded-xl">
<span className="material-symbols-outlined text-[32px] text-text-muted mb-2">
extension
</span>
<p className="text-text-muted text-sm">{t("noCompatibleYet")}</p>
<p className="text-text-muted text-xs mt-1">{t("compatibleHint")}</p>
</div>
) : (
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
{compatibleProviderEntries.map(
({ providerId, provider, stats, displayAuthType, toggleAuthType }) => (
<ProviderCard
key={providerId}
providerId={providerId}
provider={provider}
stats={stats}
authType={displayAuthType}
onToggle={(active) => handleToggleProvider(providerId, toggleAuthType, active)}
/>
)
)}
</div>
)}
</div>
<AddCompatibleProviderModal
isOpen={showAddCompatibleModal}
mode="openai"

View File

@@ -0,0 +1,15 @@
"use client";
import PayloadRulesTab from "../components/PayloadRulesTab";
import RequestLimitsTab from "../components/RequestLimitsTab";
import CliproxyapiSettingsTab from "../components/CliproxyapiSettingsTab";
export default function SettingsAdvancedPage() {
return (
<div className="space-y-6">
<PayloadRulesTab />
<RequestLimitsTab />
<CliproxyapiSettingsTab />
</div>
);
}

View File

@@ -0,0 +1,19 @@
"use client";
import ThinkingBudgetTab from "../components/ThinkingBudgetTab";
import VisionBridgeSettingsTab from "../components/VisionBridgeSettingsTab";
import SystemPromptTab from "../components/SystemPromptTab";
import MemorySkillsTab from "../components/MemorySkillsTab";
import ModelsDevSyncTab from "../components/ModelsDevSyncTab";
export default function SettingsAiPage() {
return (
<div className="space-y-6">
<ThinkingBudgetTab />
<VisionBridgeSettingsTab />
<SystemPromptTab />
<MemorySkillsTab />
<ModelsDevSyncTab />
</div>
);
}

View File

@@ -0,0 +1,7 @@
"use client";
import AppearanceTab from "../components/AppearanceTab";
export default function SettingsAppearancePage() {
return <AppearanceTab />;
}

View File

@@ -15,6 +15,7 @@ import {
HIDDEN_SIDEBAR_ITEMS_SETTING_KEY,
SIDEBAR_SECTIONS,
SIDEBAR_SETTINGS_UPDATED_EVENT,
getSectionItems,
normalizeHiddenSidebarItems,
type HideableSidebarItemId,
} from "@/shared/constants/sidebarVisibility";
@@ -102,7 +103,7 @@ export default function AppearanceTab() {
}
}
} catch (err) {
console.error(`Failed to update ${key}:`, err);
console.error("Failed to update", key, err);
}
};
@@ -148,7 +149,7 @@ export default function AppearanceTab() {
).map((section) => ({
...section,
title: getSidebarLabel(section.titleKey, section.titleFallback),
items: section.items.map((item) => ({ ...item, label: tSidebar(item.i18nKey) })),
items: getSectionItems(section).map((item) => ({ ...item, label: tSidebar(item.i18nKey) })),
}));
const toggleSidebarItem = (itemId: HideableSidebarItemId) => {

View File

@@ -9,7 +9,7 @@ export default function SettingsError({
}) {
return (
<div
className="flex flex-col items-center justify-center min-h-[400px] p-6"
className="flex flex-col items-center justify-center min-h-[400px]"
role="alert"
aria-live="assertive"
>

View File

@@ -0,0 +1,7 @@
"use client";
import SystemStorageTab from "../components/SystemStorageTab";
export default function SettingsGeneralPage() {
return <SystemStorageTab />;
}

View File

@@ -4,7 +4,7 @@ import { Skeleton } from "@/shared/components/Loading";
export default function SettingsLoading() {
return (
<div className="space-y-6 p-6" role="status" aria-live="polite" aria-busy="true">
<div className="space-y-6" role="status" aria-live="polite" aria-busy="true">
<Skeleton className="h-8 w-36" />
<div className="space-y-4">
{[0, 1, 2, 3].map((index) => (

View File

@@ -1,156 +1,5 @@
"use client";
import { useState } from "react";
import Link from "next/link";
import { useSearchParams } from "next/navigation";
import { cn } from "@/shared/utils/cn";
import { APP_CONFIG } from "@/shared/constants/appConfig";
import { useTranslations } from "next-intl";
import SystemStorageTab from "./components/SystemStorageTab";
import SecurityTab from "./components/SecurityTab";
import RoutingTab from "./components/RoutingTab";
import ComboDefaultsTab from "./components/ComboDefaultsTab";
import AppearanceTab from "./components/AppearanceTab";
import ThinkingBudgetTab from "./components/ThinkingBudgetTab";
import SystemPromptTab from "./components/SystemPromptTab";
import ModelAliasesUnified from "./components/ModelAliasesUnified";
import BackgroundDegradationTab from "./components/BackgroundDegradationTab";
import MemorySkillsTab from "./components/MemorySkillsTab";
import ModelsDevSyncTab from "./components/ModelsDevSyncTab";
import ResilienceTab from "./components/ResilienceTab";
import CliproxyapiSettingsTab from "./components/CliproxyapiSettingsTab";
import PayloadRulesTab from "./components/PayloadRulesTab";
import VisionBridgeSettingsTab from "./components/VisionBridgeSettingsTab";
import RequestLimitsTab from "./components/RequestLimitsTab";
import ModelRoutingSection from "@/shared/components/ModelRoutingSection";
const tabs = [
{ id: "general", labelKey: "general", icon: "settings" },
{ id: "appearance", labelKey: "appearance", icon: "palette" },
{ id: "ai", labelKey: "ai", icon: "smart_toy" },
{ id: "security", labelKey: "security", icon: "shield" },
{ id: "routing", labelKey: "routing", icon: "route" },
{ id: "resilience", labelKey: "resilience", icon: "electrical_services" },
{ id: "advanced", labelKey: "advanced", icon: "tune" },
];
import { redirect } from "next/navigation";
export default function SettingsPage() {
const t = useTranslations("settings");
const searchParams = useSearchParams();
const tabParam = searchParams.get("tab");
const [userSelectedTab, setUserSelectedTab] = useState(null);
const activeTab = userSelectedTab || tabs.find((t) => t.id === tabParam)?.id || "general";
return (
<div className="max-w-6xl mx-auto min-w-0">
<div className="flex flex-col gap-6">
{/* Tab navigation */}
<div className="sticky top-0 z-20 w-full overflow-x-auto pb-1 pt-1 bg-bg-primary/95 supports-[backdrop-filter]:bg-bg-primary/80 backdrop-blur">
<div
role="tablist"
aria-label={t("settingsSectionsAria")}
className="inline-flex items-center p-1 rounded-lg bg-black/5 dark:bg-white/5 min-w-max"
>
{tabs.map((tab) => (
<button
key={tab.id}
role="tab"
aria-selected={activeTab === tab.id}
tabIndex={activeTab === tab.id ? 0 : -1}
onClick={() => setUserSelectedTab(tab.id)}
className={cn(
"flex items-center gap-2 px-4 py-2 rounded-md font-medium transition-all text-sm",
activeTab === tab.id
? "bg-white dark:bg-white/10 text-text-main shadow-sm"
: "text-text-muted hover:text-text-main"
)}
>
<span className="material-symbols-outlined text-[18px]" aria-hidden="true">
{tab.icon}
</span>
<span className="hidden sm:inline whitespace-nowrap">{t(tab.labelKey)}</span>
</button>
))}
</div>
</div>
{/* Tab contents */}
<div
role="tabpanel"
aria-label={t(tabs.find((t2) => t2.id === activeTab)?.labelKey || "general")}
>
{activeTab === "general" && (
<div className="flex flex-col gap-6">
<SystemStorageTab />
</div>
)}
{activeTab === "appearance" && (
<div className="flex flex-col gap-6">
<AppearanceTab />
</div>
)}
{activeTab === "ai" && (
<div className="flex flex-col gap-6">
<ThinkingBudgetTab />
<Link
href="/dashboard/context/caveman"
className="flex items-center justify-between rounded-lg border border-border bg-surface p-4 transition-colors hover:bg-sidebar/50"
>
<span className="flex min-w-0 items-center gap-3">
<span className="material-symbols-outlined text-[20px] text-primary">
compress
</span>
<span className="min-w-0">
<span className="block text-sm font-medium text-text-main">
{t("compressionTitle")}
</span>
<span className="block text-xs text-text-muted">{t("compressionDesc")}</span>
</span>
</span>
<span className="material-symbols-outlined text-[18px] text-text-muted">
chevron_right
</span>
</Link>
<VisionBridgeSettingsTab />
<SystemPromptTab />
<MemorySkillsTab />
<ModelsDevSyncTab />
</div>
)}
{activeTab === "security" && <SecurityTab />}
{activeTab === "routing" && (
<div className="flex flex-col gap-6">
<RoutingTab />
<ModelRoutingSection />
<ComboDefaultsTab />
<ModelAliasesUnified />
<BackgroundDegradationTab />
</div>
)}
{activeTab === "resilience" && <ResilienceTab />}
{activeTab === "advanced" && (
<div className="flex flex-col gap-6">
<PayloadRulesTab />
<RequestLimitsTab />
<CliproxyapiSettingsTab />
</div>
)}
</div>
{/* App Info */}
<div className="text-center text-sm text-text-muted py-4">
<p>
{APP_CONFIG.name} v{APP_CONFIG.version}
</p>
<p className="mt-1">{t("localMode")}</p>
</div>
</div>
</div>
);
redirect("/dashboard/settings/general");
}

View File

@@ -1,170 +1,5 @@
"use client";
import { redirect } from "next/navigation";
import { useState, useEffect } from "react";
import { useRouter } from "next/navigation";
import Card from "@/shared/components/Card";
import PricingModal from "@/shared/components/PricingModal";
import { useTranslations } from "next-intl";
export default function PricingSettingsPage() {
const router = useRouter();
const [showModal, setShowModal] = useState(false);
const [currentPricing, setCurrentPricing] = useState(null);
const [loading, setLoading] = useState(true);
const t = useTranslations("settings");
useEffect(() => {
loadPricing();
}, []);
const loadPricing = async () => {
setLoading(true);
try {
const response = await fetch("/api/pricing");
if (response.ok) {
const data = await response.json();
setCurrentPricing(data);
}
} catch (error) {
console.error("Failed to load pricing:", error);
} finally {
setLoading(false);
}
};
const handlePricingUpdated = () => {
loadPricing();
};
// Count total models with pricing
const getModelCount = () => {
if (!currentPricing) return 0;
let count = 0;
for (const provider in currentPricing) {
count += Object.keys(currentPricing[provider]).length;
}
return count;
};
// Get providers list
const getProviders = () => {
if (!currentPricing) return [];
return Object.keys(currentPricing).sort();
};
return (
<div className="max-w-6xl mx-auto p-6 space-y-6">
{/* Header */}
<div className="flex items-center justify-between">
<div>
<h1 className="text-3xl font-bold">{t("pricingSettingsTitle")}</h1>
<p className="text-text-muted mt-1">{t("modelPricingDesc")}</p>
</div>
<button
onClick={() => setShowModal(true)}
className="px-4 py-2 bg-primary text-white rounded hover:bg-primary/90 transition-colors"
>
{t("editPricing")}
</button>
</div>
{/* Quick Stats */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<Card className="p-4">
<div className="text-text-muted text-sm uppercase font-semibold">{t("totalModels")}</div>
<div className="text-2xl font-bold mt-1">{loading ? "..." : getModelCount()}</div>
</Card>
<Card className="p-4">
<div className="text-text-muted text-sm uppercase font-semibold">{t("providers")}</div>
<div className="text-2xl font-bold mt-1">{loading ? "..." : getProviders().length}</div>
</Card>
<Card className="p-4">
<div className="text-text-muted text-sm uppercase font-semibold">{t("status")}</div>
<div className="text-2xl font-bold mt-1 text-success">
{loading ? "..." : t("active")}
</div>
</Card>
</div>
{/* Info Section */}
<Card className="p-6">
<h2 className="text-xl font-semibold mb-4">{t("howPricingWorks")}</h2>
<div className="space-y-3 text-sm text-text-muted">
<p>
<strong>{t("costCalculation")}:</strong> {t("costCalculationDesc")}
</p>
<p>
<strong>{t("pricingFormat")}:</strong> {t("pricingFormatDesc")}
</p>
<p>
<strong>{t("tokenTypes")}:</strong>
</p>
<ul className="list-disc list-inside ml-4 space-y-1">
<li>
<strong>{t("input")}:</strong> {t("inputTokenDesc")}
</li>
<li>
<strong>{t("output")}:</strong> {t("outputTokenDesc")}
</li>
<li>
<strong>{t("cached")}:</strong> {t("cachedTokenDesc")}
</li>
<li>
<strong>{t("reasoning")}:</strong> {t("reasoningTokenDesc")}
</li>
<li>
<strong>{t("cacheCreation")}:</strong> {t("cacheCreationTokenDesc")}
</li>
</ul>
<p>{t("customPricingNote")}</p>
</div>
</Card>
{/* Current Pricing Preview */}
<Card className="p-6">
<div className="flex items-center justify-between mb-4">
<h2 className="text-xl font-semibold">{t("currentPricing")}</h2>
<button
onClick={() => setShowModal(true)}
className="text-primary hover:underline text-sm"
>
{t("viewFullDetails")}
</button>
</div>
{loading ? (
<div className="text-center py-4 text-text-muted">{t("loadingPricing")}</div>
) : currentPricing ? (
<div className="space-y-3">
{Object.keys(currentPricing)
.slice(0, 5)
.map((provider) => (
<div key={provider} className="text-sm">
<span className="font-semibold">{provider.toUpperCase()}:</span>{" "}
<span className="text-text-muted">
{Object.keys(currentPricing[provider]).length} {t("models")}
</span>
</div>
))}
{Object.keys(currentPricing).length > 5 && (
<div className="text-sm text-text-muted">
+ {t("moreProviders", { count: Object.keys(currentPricing).length - 5 })}
</div>
)}
</div>
) : (
<div className="text-text-muted">{t("noPricing")}</div>
)}
</Card>
{/* Pricing Modal */}
{showModal && (
<PricingModal
isOpen={showModal}
onClose={() => setShowModal(false)}
onSave={handlePricingUpdated}
/>
)}
</div>
);
export default function SettingsPricingPage() {
redirect("/dashboard/costs/pricing");
}

View File

@@ -0,0 +1,7 @@
"use client";
import ResilienceTab from "../components/ResilienceTab";
export default function SettingsResiliencePage() {
return <ResilienceTab />;
}

View File

@@ -0,0 +1,19 @@
"use client";
import RoutingTab from "../components/RoutingTab";
import ModelRoutingSection from "@/shared/components/ModelRoutingSection";
import ComboDefaultsTab from "../components/ComboDefaultsTab";
import ModelAliasesUnified from "../components/ModelAliasesUnified";
import BackgroundDegradationTab from "../components/BackgroundDegradationTab";
export default function SettingsRoutingPage() {
return (
<div className="space-y-6">
<RoutingTab />
<ModelRoutingSection />
<ComboDefaultsTab />
<ModelAliasesUnified />
<BackgroundDegradationTab />
</div>
);
}

View File

@@ -0,0 +1,7 @@
"use client";
import SecurityTab from "../components/SecurityTab";
export default function SettingsSecurityPage() {
return <SecurityTab />;
}

View File

@@ -312,11 +312,7 @@ export default function SkillsPage() {
return (
<div className="flex flex-col gap-6">
<div className="flex items-center justify-between">
<div>
<h1 className="text-2xl font-bold">OmniSkills</h1>
<p className="text-text-muted mt-1">{t("description")}</p>
</div>
<div className="flex justify-end">
<button
onClick={() => setShowInstallModal(true)}
className="px-4 py-2 text-sm font-medium rounded-lg bg-violet-500 text-white hover:bg-violet-600 transition-colors"

View File

@@ -0,0 +1,6 @@
"use client";
import OneproxyTab from "@/app/(dashboard)/dashboard/settings/components/OneproxyTab";
export default function OneProxyPage() {
return <OneproxyTab />;
}

View File

@@ -0,0 +1,6 @@
"use client";
import MitmProxyTab from "@/app/(dashboard)/dashboard/settings/components/MitmProxyTab";
export default function MitmProxyPage() {
return <MitmProxyTab />;
}

View File

@@ -1,79 +1,6 @@
"use client";
import { useState } from "react";
import { useTranslations } from "next-intl";
import { cn } from "@/shared/utils/cn";
import ProxyTab from "@/app/(dashboard)/dashboard/settings/components/ProxyTab";
import MitmProxyTab from "@/app/(dashboard)/dashboard/settings/components/MitmProxyTab";
import OneproxyTab from "@/app/(dashboard)/dashboard/settings/components/OneproxyTab";
const subTabs = [
{ id: "http", labelKey: "httpProxy", icon: "dns" },
{ id: "mitm", labelKey: "mitmProxy", icon: "lan" },
{ id: "oneproxy", labelKey: "1proxy", icon: "public" },
];
export default function ProxyPage() {
const t = useTranslations("settings");
const [activeSubTab, setActiveSubTab] = useState("http");
return (
<div className="max-w-6xl mx-auto min-w-0">
<div className="flex flex-col gap-6">
<div className="w-full overflow-x-auto pb-1">
<div
role="tablist"
aria-label={t("proxySubTabsAria")}
className="inline-flex items-center p-1 rounded-lg bg-black/5 dark:bg-white/5 min-w-max"
>
{subTabs.map((subTab) => (
<button
key={subTab.id}
role="tab"
aria-selected={activeSubTab === subTab.id}
tabIndex={activeSubTab === subTab.id ? 0 : -1}
onClick={() => setActiveSubTab(subTab.id)}
className={cn(
"flex items-center gap-2 px-4 py-2 rounded-md font-medium transition-all text-sm",
activeSubTab === subTab.id
? "bg-white dark:bg-white/10 text-text-main shadow-sm"
: "text-text-muted hover:text-text-main"
)}
>
<span className="material-symbols-outlined text-[18px]" aria-hidden="true">
{subTab.icon}
</span>
<span className="whitespace-nowrap">
{subTab.id === "http" ? t("proxy") : t(subTab.labelKey)}
</span>
</button>
))}
</div>
</div>
<div
role="tabpanel"
aria-label={t(subTabs.find((t) => t.id === activeSubTab)?.labelKey || "proxy")}
>
{activeSubTab === "http" && (
<div className="flex flex-col gap-6">
<ProxyTab />
</div>
)}
{activeSubTab === "mitm" && (
<div className="flex flex-col gap-6">
<MitmProxyTab />
</div>
)}
{activeSubTab === "oneproxy" && (
<div className="flex flex-col gap-6">
<OneproxyTab />
</div>
)}
</div>
</div>
</div>
);
return <ProxyTab />;
}

View File

@@ -72,27 +72,15 @@ export default function TranslatorPageClient() {
};
return (
<div className="p-4 sm:p-8 space-y-6 min-w-0">
{/* Header */}
<div className="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-4 min-w-0">
<div>
<h1 className="text-2xl font-bold text-text-main flex items-center gap-2">
<span className="material-symbols-outlined text-primary text-[28px]">translate</span>
{t("playgroundTitle")}
</h1>
<p className="text-sm text-text-muted mt-1">
{modeDescriptions[mode] || t("modeDescriptionFallback")}
</p>
</div>
<div className="w-full sm:w-auto overflow-x-auto">
<SegmentedControl
options={modes}
value={mode}
onChange={setMode}
size="md"
className="min-w-max"
/>
</div>
<div className="space-y-6 min-w-0">
<div className="flex justify-end min-w-0 overflow-x-auto">
<SegmentedControl
options={modes}
value={mode}
onChange={setMode}
size="md"
className="min-w-max"
/>
</div>
<Card className="border-primary/10 bg-primary/5">

View File

@@ -273,15 +273,8 @@ export default function WebhooksPage() {
const isModalOpen = formMode !== null;
return (
<div className="mx-auto max-w-7xl space-y-6 p-6">
<div className="flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between">
<div>
<div className="flex items-center gap-2">
<span className="material-symbols-outlined text-[24px] text-primary">webhook</span>
<h1 className="text-3xl font-bold tracking-tight text-text-main">{t("title")}</h1>
</div>
<p className="mt-1 text-sm text-text-muted">{t("description")}</p>
</div>
<div className="space-y-6">
<div className="flex justify-end">
<button
onClick={openCreateModal}
className="inline-flex items-center justify-center gap-2 rounded-lg bg-primary px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-primary/90"

View File

@@ -0,0 +1,408 @@
"use client";
import { useMemo, useState, useEffect, useCallback, useRef } from "react";
import {
ReactFlow,
Handle,
Position,
Controls,
type Node,
type Edge,
type NodeTypes,
} from "@xyflow/react";
import "@xyflow/react/dist/style.css";
import { AI_PROVIDERS } from "@/shared/constants/providers";
import ProviderIcon from "@/shared/components/ProviderIcon";
const FE_ACTIVE_TIMEOUT_MS = 60_000;
const FE_ACTIVE_TICK_MS = 1_000;
// Rings: [capacity, rx, ry]. Each successive ring fits ~6 more nodes.
const RINGS: [number, number, number][] = [
[8, 210, 132],
[14, 370, 233],
[20, 530, 334],
[26, 690, 435],
[32, 850, 536],
[38, 1010, 637],
];
type ProviderConfig = { color?: string; name?: string; textIcon?: string };
function getProviderConfig(providerId: string): ProviderConfig {
return (
(AI_PROVIDERS as Record<string, ProviderConfig>)[providerId] || {
color: "#6b7280",
name: providerId,
}
);
}
type ProviderNodeData = {
label: string;
color: string;
providerId: string;
active: boolean;
error: boolean;
};
function ProviderNode({ data }: { data: ProviderNodeData }) {
const { label, color, providerId, active, error } = data;
return (
<div
className="flex items-center gap-2 px-2.5 py-1.5 rounded-lg border-2 transition-all duration-300 bg-bg"
style={{
borderColor: error ? "#ef4444" : active ? color : "var(--color-border)",
boxShadow: error ? `0 0 12px #ef444430` : active ? `0 0 12px ${color}30` : "none",
minWidth: "136px",
}}
>
<Handle
type="target"
position={Position.Top}
id="top"
className="!bg-transparent !border-0 !w-0 !h-0"
/>
<Handle
type="target"
position={Position.Bottom}
id="bottom"
className="!bg-transparent !border-0 !w-0 !h-0"
/>
<Handle
type="target"
position={Position.Left}
id="left"
className="!bg-transparent !border-0 !w-0 !h-0"
/>
<Handle
type="target"
position={Position.Right}
id="right"
className="!bg-transparent !border-0 !w-0 !h-0"
/>
<div
className="size-6 rounded flex items-center justify-center shrink-0"
style={{ backgroundColor: `${color}18` }}
>
<ProviderIcon providerId={providerId} size={16} type="color" />
</div>
<span
className="text-xs font-medium truncate flex-1"
style={{ color: active ? color : error ? "#ef4444" : "var(--color-text-main)" }}
>
{label}
</span>
{(active || error) && (
<span className="relative flex size-1.5 shrink-0">
<span
className="animate-ping absolute inline-flex h-full w-full rounded-full opacity-70"
style={{ backgroundColor: error ? "#ef4444" : color }}
/>
<span
className="relative inline-flex rounded-full size-1.5"
style={{ backgroundColor: error ? "#ef4444" : color }}
/>
</span>
)}
</div>
);
}
type RouterNodeData = { activeCount: number };
function RouterNode({ data }: { data: RouterNodeData }) {
return (
<div className="flex items-center gap-2 px-5 py-3 rounded-xl border-2 border-primary bg-primary/8 shadow-lg min-w-[140px] justify-center">
<Handle
type="source"
position={Position.Top}
id="top"
className="!bg-transparent !border-0 !w-0 !h-0"
/>
<Handle
type="source"
position={Position.Bottom}
id="bottom"
className="!bg-transparent !border-0 !w-0 !h-0"
/>
<Handle
type="source"
position={Position.Left}
id="left"
className="!bg-transparent !border-0 !w-0 !h-0"
/>
<Handle
type="source"
position={Position.Right}
id="right"
className="!bg-transparent !border-0 !w-0 !h-0"
/>
<div className="flex items-center justify-center size-7 rounded-md bg-primary/15 shrink-0">
<span className="material-symbols-outlined text-primary text-[16px]">route</span>
</div>
<span className="text-sm font-bold text-primary">OmniRoute</span>
{data.activeCount > 0 && (
<span className="ml-1 px-1.5 py-0.5 rounded-full bg-primary text-white text-[10px] font-bold leading-none">
{data.activeCount}
</span>
)}
</div>
);
}
const nodeTypes: NodeTypes = {
provider: ProviderNode as any,
router: RouterNode as any,
};
type ProviderEntry = { id?: string; provider: string; name?: string };
function edgeStyle(active: boolean, last: boolean, error: boolean) {
if (error) return { stroke: "#ef4444", strokeWidth: 2, opacity: 0.85 };
if (active) return { stroke: "#22c55e", strokeWidth: 2.5, opacity: 1 };
if (last) return { stroke: "#f59e0b", strokeWidth: 1.5, opacity: 0.6 };
return { stroke: "var(--color-border)", strokeWidth: 1, opacity: 0.2 };
}
function getHandles(angle: number, cx: number): { sourceHandle: string; targetHandle: string } {
const rel = (((angle + Math.PI / 2) % (2 * Math.PI)) + 2 * Math.PI) % (2 * Math.PI);
if (rel < Math.PI / 4 || rel > (7 * Math.PI) / 4)
return { sourceHandle: "top", targetHandle: "bottom" };
if (rel > (3 * Math.PI) / 4 && rel < (5 * Math.PI) / 4)
return { sourceHandle: "bottom", targetHandle: "top" };
return cx > 0
? { sourceHandle: "right", targetHandle: "left" }
: { sourceHandle: "left", targetHandle: "right" };
}
function buildLayout(
providers: ProviderEntry[],
activeSet: Set<string>,
lastSet: Set<string>,
errorSet: Set<string>
): { nodes: Node[]; edges: Edge[] } {
const nodeW = 156;
const nodeH = 28;
const routerW = 148;
const routerH = 44;
const nodes: Node[] = [];
const edges: Edge[] = [];
nodes.push({
id: "router",
type: "router",
position: { x: -routerW / 2, y: -routerH / 2 },
data: { activeCount: activeSet.size },
draggable: false,
});
if (providers.length === 0) return { nodes, edges };
// Sort: active → error → last-used → rest (alpha within groups)
const sorted = [...providers].sort((a, b) => {
const aId = a.provider.toLowerCase();
const bId = b.provider.toLowerCase();
const rank = (id: string) => {
if (activeSet.has(id)) return 0;
if (errorSet.has(id)) return 1;
if (lastSet.has(id)) return 2;
return 3;
};
const d = rank(aId) - rank(bId);
return d !== 0 ? d : aId.localeCompare(bId);
});
let provIdx = 0;
for (let ri = 0; ri < RINGS.length && provIdx < sorted.length; ri++) {
const [cap, rx, ry] = RINGS[ri];
const count = Math.min(cap, sorted.length - provIdx);
for (let i = 0; i < count; i++) {
const p = sorted[provIdx++];
const pid = p.provider.toLowerCase();
const active = activeSet.has(pid);
const error = !active && errorSet.has(pid);
const last = !active && !error && lastSet.has(pid);
const config = getProviderConfig(p.provider);
const nodeId = `provider-${p.provider}`;
const angle = -Math.PI / 2 + (2 * Math.PI * i) / count;
const cx = rx * Math.cos(angle);
const cy = ry * Math.sin(angle);
const { sourceHandle, targetHandle } = getHandles(angle, cx);
nodes.push({
id: nodeId,
type: "provider",
position: { x: cx - nodeW / 2, y: cy - nodeH / 2 },
data: {
label: config.name || p.name || p.provider,
color: config.color || "#6b7280",
providerId: p.provider,
active,
error,
} satisfies ProviderNodeData,
draggable: false,
});
edges.push({
id: `e-${nodeId}`,
source: "router",
sourceHandle,
target: nodeId,
targetHandle,
animated: active,
style: edgeStyle(active, last, error),
});
}
}
return { nodes, edges };
}
type Props = {
providers?: ProviderEntry[];
activeRequests?: Array<{ provider?: string; model?: string }>;
lastProvider?: string;
errorProvider?: string;
};
export default function ProviderTopology({
providers = [],
activeRequests = [],
lastProvider = "",
errorProvider = "",
}: Props) {
const activeKey = useMemo(
() =>
activeRequests
.map((r) => r.provider?.toLowerCase())
.filter(Boolean)
.sort()
.join(","),
[activeRequests]
);
const lastKey = lastProvider.toLowerCase();
const errorKey = errorProvider.toLowerCase();
const rawActiveSet = useMemo(
() => new Set<string>(activeKey ? activeKey.split(",") : []),
[activeKey]
);
const lastSet = useMemo(() => new Set<string>(lastKey ? [lastKey] : []), [lastKey]);
const errorSet = useMemo(() => new Set<string>(errorKey ? [errorKey] : []), [errorKey]);
const firstSeenRef = useRef<Record<string, number>>({});
const [tick, setTick] = useState(0);
useEffect(() => {
const seen = firstSeenRef.current;
const now = Date.now();
for (const p of rawActiveSet) {
if (!seen[p]) seen[p] = now;
}
for (const p of Object.keys(seen)) {
if (!rawActiveSet.has(p)) delete seen[p];
}
}, [rawActiveSet]);
useEffect(() => {
if (rawActiveSet.size === 0) return;
const id = setInterval(() => setTick((t) => t + 1), FE_ACTIVE_TICK_MS);
return () => clearInterval(id);
}, [rawActiveSet]);
const activeSet = useMemo(() => {
const now = Date.now();
const filtered = new Set<string>();
for (const p of rawActiveSet) {
const ts = firstSeenRef.current[p];
if (!ts || now - ts < FE_ACTIVE_TIMEOUT_MS) filtered.add(p);
}
return filtered;
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [rawActiveSet, tick]);
const { nodes, edges } = useMemo(
() => buildLayout(providers, activeSet, lastSet, errorSet),
// eslint-disable-next-line react-hooks/exhaustive-deps
[providers, activeSet, lastKey, errorKey]
);
const providersKey = useMemo(
() =>
providers
.map((p) => p.provider)
.sort()
.join(","),
[providers]
);
const rfInstance = useRef<any>(null);
const containerRef = useRef<HTMLDivElement>(null);
const fitOpts = { padding: 0.22, duration: 250 };
const onInit = useCallback((instance: any) => {
rfInstance.current = instance;
setTimeout(() => instance.fitView(fitOpts), 60);
}, []);
useEffect(() => {
const el = containerRef.current;
if (!el) return;
const ro = new ResizeObserver(() => {
rfInstance.current?.fitView(fitOpts);
});
ro.observe(el);
return () => ro.disconnect();
}, []);
useEffect(() => {
const id = setTimeout(() => rfInstance.current?.fitView(fitOpts), 60);
return () => clearTimeout(id);
}, [nodes.length]);
return (
<div
ref={containerRef}
className="h-[300px] w-full min-w-0 rounded-xl border border-border bg-bg-subtle/20 overflow-hidden sm:h-[420px]"
>
{providers.length === 0 ? (
<div className="h-full flex flex-col items-center justify-center gap-2 text-text-muted">
<span className="material-symbols-outlined text-[32px]">device_hub</span>
<p className="text-sm">No providers connected yet</p>
</div>
) : (
<ReactFlow
key={providersKey}
nodes={nodes}
edges={edges}
nodeTypes={nodeTypes}
fitView
fitViewOptions={fitOpts}
minZoom={0.08}
maxZoom={2}
onInit={onInit}
proOptions={{ hideAttribution: true }}
panOnDrag
zoomOnScroll
zoomOnPinch
zoomOnDoubleClick
preventScrolling={false}
nodesDraggable={false}
nodesConnectable={false}
elementsSelectable={false}
>
<Controls showInteractive={false} />
</ReactFlow>
)}
</div>
);
}

View File

@@ -0,0 +1,22 @@
import { redirect } from "next/navigation";
import { getMachineId } from "@/shared/utils/machine";
import { getSettings } from "@/lib/localDb";
import HomePageClient from "../dashboard/HomePageClient";
import BootstrapBanner from "../dashboard/BootstrapBanner";
export const dynamic = "force-dynamic";
export default async function HomePage() {
const settings = await getSettings();
if (!settings.setupComplete) {
redirect("/dashboard/onboarding");
}
const machineId = await getMachineId();
const isBootstrapped = process.env.OMNIROUTE_BOOTSTRAPPED === "true";
return (
<>
{isBootstrapped && <BootstrapBanner />}
<HomePageClient machineId={machineId} />
</>
);
}

View File

@@ -0,0 +1,32 @@
import os from "os";
import { NextResponse } from "next/server";
import { isAuthenticated } from "@/shared/utils/apiAuth";
import { getRuntimePorts } from "@/lib/runtime/ports";
export const dynamic = "force-dynamic";
export async function GET(request: Request) {
if (!(await isAuthenticated(request))) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const { apiPort } = getRuntimePorts();
const interfaces = os.networkInterfaces();
const lanUrls: string[] = [];
let tailscaleIpUrl: string | null = null;
for (const [ifaceName, addrs] of Object.entries(interfaces)) {
for (const addr of addrs ?? []) {
if (addr.family !== "IPv4" || addr.internal) continue;
const isTailscale =
ifaceName.toLowerCase().startsWith("tailscale") || addr.address.startsWith("100.");
if (isTailscale) {
tailscaleIpUrl = `http://${addr.address}:${apiPort}/v1`;
} else {
lanUrls.push(`http://${addr.address}:${apiPort}/v1`);
}
}
}
return NextResponse.json({ lanUrls, tailscaleIpUrl });
}

View File

@@ -743,7 +743,55 @@
"contextSection": "Context & Cache",
"contextCaveman": "Caveman",
"contextRtk": "RTK",
"contextCombos": "Compression Combos"
"contextCombos": "Compression Combos",
"routingSection": "Routing",
"protocolsSection": "Protocols",
"agentsAiSection": "Agents & AI",
"cacheContextSection": "Cache & Context",
"analyticsSection": "Analytics",
"costsSection": "Costs",
"monitoringSection": "Monitoring",
"auditSecuritySection": "Audit & Security",
"devtoolsSection": "Dev Tools",
"configurationSection": "Configuration",
"aiFeaturesSection": "AI Features",
"mcp": "MCP",
"a2a": "A2A",
"apiEndpoints": "API Endpoints",
"batchFiles": "Files",
"analyticsEvals": "Evals",
"analyticsSearch": "Search",
"analyticsUtilization": "Utilization",
"analyticsComboHealth": "Combo Health",
"analyticsCompression": "Compression",
"costsBudget": "Budget",
"costsPricing": "Pricing",
"logsProxy": "Proxy Logs",
"logsConsole": "Console",
"logsActivity": "Activity",
"auditMcp": "MCP Audit",
"settingsGeneral": "General",
"settingsAppearance": "Appearance",
"settingsAi": "AI Settings",
"settingsSecurity": "Security",
"settingsRouting": "Routing",
"settingsResilience": "Resilience",
"settingsAdvanced": "Advanced",
"omniProxySection": "OmniProxy",
"quotaTracker": "Quota Tracker",
"consoleLogs": "Console Logs",
"globalRouting": "Global Routing",
"mitmProxy": "MITM Proxy",
"oneProxy": "1Proxy",
"agenticFeaturesSection": "Agentic Features",
"otherFeaturesSection": "Other Features",
"compressionContextGroup": "Compression Context",
"toolsGroup": "Tools",
"integrationsGroup": "Integrations",
"proxyGroup": "Proxy",
"costsParametersGroup": "Costs Parameters",
"auditGroup": "Audit",
"batchGroup": "Batch"
},
"webhooks": {
"title": "__MISSING__:Webhooks",
@@ -879,7 +927,51 @@
"media": "الإعلام",
"mediaDescription": "إنشاء الصور ومقاطع الفيديو والموسيقى",
"themes": "المواضيع",
"themesDescription": "اختر سمة لون للوحة المعلومات بأكملها"
"themesDescription": "اختر سمة لون للوحة المعلومات بأكملها",
"costsDescription": "Track spending, analyze trends, and manage your AI budget across all providers",
"cacheDescription": "Monitor provider prompt cache efficiency and local semantic response reuse.",
"limitsDescription": "Configure rate limits and quotas per API key and provider",
"apiManagerDescription": "Manage API keys and access control for your OmniRoute instance",
"batchDescription": "Process large volumes of requests asynchronously with batched API calls",
"contextCavemanDescription": "Rule-based message compression, language packs, analytics and output mode controls.",
"contextRtkDescription": "Command-aware compression for tool output, terminal logs and build results.",
"contextCombosDescription": "Define how engines are combined for different routing scenarios.",
"changelogDescription": "Stay up to date with the latest platform features and announcements.",
"agentsDescription": "Manage and configure AI agent tools: Codex, Devin, Jules, and custom agents",
"cloudAgentsDescription": "Orchestrate cloud-based AI agents with live task tracking and plan approval",
"memoryDescription": "Persistent conversational memory with semantic search and FTS5 full-text indexing",
"skillsDescription": "Install and manage sandbox skills for automated prompt and tool execution",
"agentSkillsDescription": "Agent-ready skills catalog with one-click URL copy for AI client integration",
"translatorDescription": "Translate and test prompts across API formats: OpenAI ↔ Claude ↔ Gemini",
"playgroundDescription": "Test prompts interactively with live provider responses and format inspection",
"searchToolsDescription": "Search analytics, provider breakdown, cache hit rates, and cost tracking",
"logsDescription": "Real-time request logs, error traces, and streaming event inspector",
"auditDescription": "Compliance audit trail of API key usage, MCP tool calls, and policy events",
"webhooksDescription": "Configure webhook endpoints to receive real-time event notifications",
"healthDescription": "System health overview: providers, circuit breakers, rate limits, and database",
"proxyDescription": "Configure upstream proxy settings for outbound provider connections",
"apiEndpointsDescription": "Manage custom API endpoint configurations and routing overrides",
"batchFilesDescription": "Browse and manage batch job output files and results",
"analyticsEvalsDescription": "Model evaluation results and performance benchmarks",
"analyticsSearchDescription": "Search query analytics, cache hit rates, and cost tracking",
"analyticsUtilizationDescription": "Provider utilization metrics and capacity planning",
"analyticsComboHealthDescription": "Real-time health and performance of combo routing configurations",
"analyticsCompressionDescription": "Context compression analytics and token savings",
"costsBudgetDescription": "Budget limits and spending alerts per API key and provider",
"costsPricingDescription": "Custom pricing configuration for token cost calculations",
"logsProxyDescription": "Upstream proxy request logs and traffic inspection",
"logsConsoleDescription": "Application console output and debug logs",
"logsActivityDescription": "Audit trail of user actions and system events",
"auditMcpDescription": "MCP tool invocation audit trail and compliance records",
"settingsGeneralDescription": "Storage, database, and general instance configuration",
"settingsAppearanceDescription": "Theme, branding, and visual customization",
"settingsAiDescription": "AI behaviors, thinking budgets, vision, and memory settings",
"settingsSecurityDescription": "Authentication, authorization, and access control settings",
"settingsRoutingDescription": "Routing rules, model aliases, combo defaults, and degradation settings",
"settingsResilienceDescription": "Circuit breaker, retry, and fallback configuration",
"settingsAdvancedDescription": "Advanced payload rules, request limits, and proxy API settings",
"mitmProxyDescription": "Configure MITM proxy settings for traffic inspection and debugging",
"oneProxyDescription": "Configure 1Proxy settings for advanced proxy chaining"
},
"home": {
"quickStart": "بداية سريعة",

View File

@@ -743,7 +743,55 @@
"contextSection": "Context & Cache",
"contextCaveman": "Caveman",
"contextRtk": "RTK",
"contextCombos": "Compression Combos"
"contextCombos": "Compression Combos",
"routingSection": "Routing",
"protocolsSection": "Protocols",
"agentsAiSection": "Agents & AI",
"cacheContextSection": "Cache & Context",
"analyticsSection": "Analytics",
"costsSection": "Costs",
"monitoringSection": "Monitoring",
"auditSecuritySection": "Audit & Security",
"devtoolsSection": "Dev Tools",
"configurationSection": "Configuration",
"aiFeaturesSection": "AI Features",
"mcp": "MCP",
"a2a": "A2A",
"apiEndpoints": "API Endpoints",
"batchFiles": "Files",
"analyticsEvals": "Evals",
"analyticsSearch": "Search",
"analyticsUtilization": "Utilization",
"analyticsComboHealth": "Combo Health",
"analyticsCompression": "Compression",
"costsBudget": "Budget",
"costsPricing": "Pricing",
"logsProxy": "Proxy Logs",
"logsConsole": "Console",
"logsActivity": "Activity",
"auditMcp": "MCP Audit",
"settingsGeneral": "General",
"settingsAppearance": "Appearance",
"settingsAi": "AI Settings",
"settingsSecurity": "Security",
"settingsRouting": "Routing",
"settingsResilience": "Resilience",
"settingsAdvanced": "Advanced",
"omniProxySection": "OmniProxy",
"quotaTracker": "Quota Tracker",
"consoleLogs": "Console Logs",
"globalRouting": "Global Routing",
"mitmProxy": "MITM Proxy",
"oneProxy": "1Proxy",
"agenticFeaturesSection": "Agentic Features",
"otherFeaturesSection": "Other Features",
"compressionContextGroup": "Compression Context",
"toolsGroup": "Tools",
"integrationsGroup": "Integrations",
"proxyGroup": "Proxy",
"costsParametersGroup": "Costs Parameters",
"auditGroup": "Audit",
"batchGroup": "Batch"
},
"webhooks": {
"title": "__MISSING__:Webhooks",
@@ -879,7 +927,51 @@
"media": "Медия",
"mediaDescription": "Генериране на изображения, видеоклипове и музика",
"themes": "Теми",
"themesDescription": "Изберете цветова тема за целия панел на таблото"
"themesDescription": "Изберете цветова тема за целия панел на таблото",
"costsDescription": "Track spending, analyze trends, and manage your AI budget across all providers",
"cacheDescription": "Monitor provider prompt cache efficiency and local semantic response reuse.",
"limitsDescription": "Configure rate limits and quotas per API key and provider",
"apiManagerDescription": "Manage API keys and access control for your OmniRoute instance",
"batchDescription": "Process large volumes of requests asynchronously with batched API calls",
"contextCavemanDescription": "Rule-based message compression, language packs, analytics and output mode controls.",
"contextRtkDescription": "Command-aware compression for tool output, terminal logs and build results.",
"contextCombosDescription": "Define how engines are combined for different routing scenarios.",
"changelogDescription": "Stay up to date with the latest platform features and announcements.",
"agentsDescription": "Manage and configure AI agent tools: Codex, Devin, Jules, and custom agents",
"cloudAgentsDescription": "Orchestrate cloud-based AI agents with live task tracking and plan approval",
"memoryDescription": "Persistent conversational memory with semantic search and FTS5 full-text indexing",
"skillsDescription": "Install and manage sandbox skills for automated prompt and tool execution",
"agentSkillsDescription": "Agent-ready skills catalog with one-click URL copy for AI client integration",
"translatorDescription": "Translate and test prompts across API formats: OpenAI ↔ Claude ↔ Gemini",
"playgroundDescription": "Test prompts interactively with live provider responses and format inspection",
"searchToolsDescription": "Search analytics, provider breakdown, cache hit rates, and cost tracking",
"logsDescription": "Real-time request logs, error traces, and streaming event inspector",
"auditDescription": "Compliance audit trail of API key usage, MCP tool calls, and policy events",
"webhooksDescription": "Configure webhook endpoints to receive real-time event notifications",
"healthDescription": "System health overview: providers, circuit breakers, rate limits, and database",
"proxyDescription": "Configure upstream proxy settings for outbound provider connections",
"apiEndpointsDescription": "Manage custom API endpoint configurations and routing overrides",
"batchFilesDescription": "Browse and manage batch job output files and results",
"analyticsEvalsDescription": "Model evaluation results and performance benchmarks",
"analyticsSearchDescription": "Search query analytics, cache hit rates, and cost tracking",
"analyticsUtilizationDescription": "Provider utilization metrics and capacity planning",
"analyticsComboHealthDescription": "Real-time health and performance of combo routing configurations",
"analyticsCompressionDescription": "Context compression analytics and token savings",
"costsBudgetDescription": "Budget limits and spending alerts per API key and provider",
"costsPricingDescription": "Custom pricing configuration for token cost calculations",
"logsProxyDescription": "Upstream proxy request logs and traffic inspection",
"logsConsoleDescription": "Application console output and debug logs",
"logsActivityDescription": "Audit trail of user actions and system events",
"auditMcpDescription": "MCP tool invocation audit trail and compliance records",
"settingsGeneralDescription": "Storage, database, and general instance configuration",
"settingsAppearanceDescription": "Theme, branding, and visual customization",
"settingsAiDescription": "AI behaviors, thinking budgets, vision, and memory settings",
"settingsSecurityDescription": "Authentication, authorization, and access control settings",
"settingsRoutingDescription": "Routing rules, model aliases, combo defaults, and degradation settings",
"settingsResilienceDescription": "Circuit breaker, retry, and fallback configuration",
"settingsAdvancedDescription": "Advanced payload rules, request limits, and proxy API settings",
"mitmProxyDescription": "Configure MITM proxy settings for traffic inspection and debugging",
"oneProxyDescription": "Configure 1Proxy settings for advanced proxy chaining"
},
"home": {
"quickStart": "Бърз старт",

View File

@@ -675,7 +675,7 @@
"docs": "Docs",
"issues": "Issues",
"endpoints": "Endpoints",
"apiManager": "API Manager",
"apiManager": "API Key Manager",
"logs": "Logs",
"webhooks": "__MISSING__:Webhooks",
"auditLog": "Audit Log",
@@ -743,7 +743,55 @@
"contextSection": "Context & Cache",
"contextCaveman": "Caveman",
"contextRtk": "RTK",
"contextCombos": "Compression Combos"
"contextCombos": "Compression Combos",
"routingSection": "Routing",
"protocolsSection": "Protocols",
"agentsAiSection": "Agents & AI",
"cacheContextSection": "Cache & Context",
"analyticsSection": "Analytics",
"costsSection": "Costs",
"monitoringSection": "Monitoring",
"auditSecuritySection": "Audit & Security",
"devtoolsSection": "Dev Tools",
"configurationSection": "Configuration",
"aiFeaturesSection": "AI Features",
"mcp": "MCP",
"a2a": "A2A",
"apiEndpoints": "API Endpoints",
"batchFiles": "Files",
"analyticsEvals": "Evals",
"analyticsSearch": "Search",
"analyticsUtilization": "Utilization",
"analyticsComboHealth": "Combo Health",
"analyticsCompression": "Compression",
"costsBudget": "Budget",
"costsPricing": "Pricing",
"logsProxy": "Proxy Logs",
"logsConsole": "Console",
"logsActivity": "Activity",
"auditMcp": "MCP Audit",
"settingsGeneral": "General",
"settingsAppearance": "Appearance",
"settingsAi": "AI Settings",
"settingsSecurity": "Security",
"settingsRouting": "Routing",
"settingsResilience": "Resilience",
"settingsAdvanced": "Advanced",
"omniProxySection": "OmniProxy",
"quotaTracker": "Quota Tracker",
"consoleLogs": "Console Logs",
"globalRouting": "Global Routing",
"mitmProxy": "MITM Proxy",
"oneProxy": "1Proxy",
"agenticFeaturesSection": "Agentic Features",
"otherFeaturesSection": "Other Features",
"compressionContextGroup": "Compression Context",
"toolsGroup": "Tools",
"integrationsGroup": "Integrations",
"proxyGroup": "Proxy",
"costsParametersGroup": "Costs Parameters",
"auditGroup": "Audit",
"batchGroup": "Batch"
},
"webhooks": {
"title": "__MISSING__:Webhooks",
@@ -879,7 +927,51 @@
"media": "Media",
"mediaDescription": "Generate images, videos, and music",
"themes": "Themes",
"themesDescription": "Choose a color theme for the whole dashboard panel"
"themesDescription": "Choose a color theme for the whole dashboard panel",
"costsDescription": "Track spending, analyze trends, and manage your AI budget across all providers",
"cacheDescription": "Monitor provider prompt cache efficiency and local semantic response reuse.",
"limitsDescription": "Configure rate limits and quotas per API key and provider",
"apiManagerDescription": "Manage API keys and access control for your OmniRoute instance",
"batchDescription": "Process large volumes of requests asynchronously with batched API calls",
"contextCavemanDescription": "Rule-based message compression, language packs, analytics and output mode controls.",
"contextRtkDescription": "Command-aware compression for tool output, terminal logs and build results.",
"contextCombosDescription": "Define how engines are combined for different routing scenarios.",
"changelogDescription": "Stay up to date with the latest platform features and announcements.",
"agentsDescription": "Manage and configure AI agent tools: Codex, Devin, Jules, and custom agents",
"cloudAgentsDescription": "Orchestrate cloud-based AI agents with live task tracking and plan approval",
"memoryDescription": "Persistent conversational memory with semantic search and FTS5 full-text indexing",
"skillsDescription": "Install and manage sandbox skills for automated prompt and tool execution",
"agentSkillsDescription": "Agent-ready skills catalog with one-click URL copy for AI client integration",
"translatorDescription": "Translate and test prompts across API formats: OpenAI ↔ Claude ↔ Gemini",
"playgroundDescription": "Test prompts interactively with live provider responses and format inspection",
"searchToolsDescription": "Search analytics, provider breakdown, cache hit rates, and cost tracking",
"logsDescription": "Real-time request logs, error traces, and streaming event inspector",
"auditDescription": "Compliance audit trail of API key usage, MCP tool calls, and policy events",
"webhooksDescription": "Configure webhook endpoints to receive real-time event notifications",
"healthDescription": "System health overview: providers, circuit breakers, rate limits, and database",
"proxyDescription": "Configure upstream proxy settings for outbound provider connections",
"apiEndpointsDescription": "Manage custom API endpoint configurations and routing overrides",
"batchFilesDescription": "Browse and manage batch job output files and results",
"analyticsEvalsDescription": "Model evaluation results and performance benchmarks",
"analyticsSearchDescription": "Search query analytics, cache hit rates, and cost tracking",
"analyticsUtilizationDescription": "Provider utilization metrics and capacity planning",
"analyticsComboHealthDescription": "Real-time health and performance of combo routing configurations",
"analyticsCompressionDescription": "Context compression analytics and token savings",
"costsBudgetDescription": "Budget limits and spending alerts per API key and provider",
"costsPricingDescription": "Custom pricing configuration for token cost calculations",
"logsProxyDescription": "Upstream proxy request logs and traffic inspection",
"logsConsoleDescription": "Application console output and debug logs",
"logsActivityDescription": "Audit trail of user actions and system events",
"auditMcpDescription": "MCP tool invocation audit trail and compliance records",
"settingsGeneralDescription": "Storage, database, and general instance configuration",
"settingsAppearanceDescription": "Theme, branding, and visual customization",
"settingsAiDescription": "AI behaviors, thinking budgets, vision, and memory settings",
"settingsSecurityDescription": "Authentication, authorization, and access control settings",
"settingsRoutingDescription": "Routing rules, model aliases, combo defaults, and degradation settings",
"settingsResilienceDescription": "Circuit breaker, retry, and fallback configuration",
"settingsAdvancedDescription": "Advanced payload rules, request limits, and proxy API settings",
"mitmProxyDescription": "Configure MITM proxy settings for traffic inspection and debugging",
"oneProxyDescription": "Configure 1Proxy settings for advanced proxy chaining"
},
"home": {
"quickStart": "Quick Start",

View File

@@ -743,7 +743,55 @@
"contextSection": "Context & Cache",
"contextCaveman": "Caveman",
"contextRtk": "RTK",
"contextCombos": "Compression Combos"
"contextCombos": "Compression Combos",
"routingSection": "Routing",
"protocolsSection": "Protocols",
"agentsAiSection": "Agents & AI",
"cacheContextSection": "Cache & Context",
"analyticsSection": "Analytics",
"costsSection": "Costs",
"monitoringSection": "Monitoring",
"auditSecuritySection": "Audit & Security",
"devtoolsSection": "Dev Tools",
"configurationSection": "Configuration",
"aiFeaturesSection": "AI Features",
"mcp": "MCP",
"a2a": "A2A",
"apiEndpoints": "API Endpoints",
"batchFiles": "Files",
"analyticsEvals": "Evals",
"analyticsSearch": "Search",
"analyticsUtilization": "Utilization",
"analyticsComboHealth": "Combo Health",
"analyticsCompression": "Compression",
"costsBudget": "Budget",
"costsPricing": "Pricing",
"logsProxy": "Proxy Logs",
"logsConsole": "Console",
"logsActivity": "Activity",
"auditMcp": "MCP Audit",
"settingsGeneral": "General",
"settingsAppearance": "Appearance",
"settingsAi": "AI Settings",
"settingsSecurity": "Security",
"settingsRouting": "Routing",
"settingsResilience": "Resilience",
"settingsAdvanced": "Advanced",
"omniProxySection": "OmniProxy",
"quotaTracker": "Quota Tracker",
"consoleLogs": "Console Logs",
"globalRouting": "Global Routing",
"mitmProxy": "MITM Proxy",
"oneProxy": "1Proxy",
"agenticFeaturesSection": "Agentic Features",
"otherFeaturesSection": "Other Features",
"compressionContextGroup": "Compression Context",
"toolsGroup": "Tools",
"integrationsGroup": "Integrations",
"proxyGroup": "Proxy",
"costsParametersGroup": "Costs Parameters",
"auditGroup": "Audit",
"batchGroup": "Batch"
},
"webhooks": {
"title": "__MISSING__:Webhooks",
@@ -879,7 +927,51 @@
"media": "Média",
"mediaDescription": "Generování obrázků, videí a hudby",
"themes": "Motivy",
"themesDescription": "Vyberte barevný motiv pro celý panel nástěnky"
"themesDescription": "Vyberte barevný motiv pro celý panel nástěnky",
"costsDescription": "Track spending, analyze trends, and manage your AI budget across all providers",
"cacheDescription": "Monitor provider prompt cache efficiency and local semantic response reuse.",
"limitsDescription": "Configure rate limits and quotas per API key and provider",
"apiManagerDescription": "Manage API keys and access control for your OmniRoute instance",
"batchDescription": "Process large volumes of requests asynchronously with batched API calls",
"contextCavemanDescription": "Rule-based message compression, language packs, analytics and output mode controls.",
"contextRtkDescription": "Command-aware compression for tool output, terminal logs and build results.",
"contextCombosDescription": "Define how engines are combined for different routing scenarios.",
"changelogDescription": "Stay up to date with the latest platform features and announcements.",
"agentsDescription": "Manage and configure AI agent tools: Codex, Devin, Jules, and custom agents",
"cloudAgentsDescription": "Orchestrate cloud-based AI agents with live task tracking and plan approval",
"memoryDescription": "Persistent conversational memory with semantic search and FTS5 full-text indexing",
"skillsDescription": "Install and manage sandbox skills for automated prompt and tool execution",
"agentSkillsDescription": "Agent-ready skills catalog with one-click URL copy for AI client integration",
"translatorDescription": "Translate and test prompts across API formats: OpenAI ↔ Claude ↔ Gemini",
"playgroundDescription": "Test prompts interactively with live provider responses and format inspection",
"searchToolsDescription": "Search analytics, provider breakdown, cache hit rates, and cost tracking",
"logsDescription": "Real-time request logs, error traces, and streaming event inspector",
"auditDescription": "Compliance audit trail of API key usage, MCP tool calls, and policy events",
"webhooksDescription": "Configure webhook endpoints to receive real-time event notifications",
"healthDescription": "System health overview: providers, circuit breakers, rate limits, and database",
"proxyDescription": "Configure upstream proxy settings for outbound provider connections",
"apiEndpointsDescription": "Manage custom API endpoint configurations and routing overrides",
"batchFilesDescription": "Browse and manage batch job output files and results",
"analyticsEvalsDescription": "Model evaluation results and performance benchmarks",
"analyticsSearchDescription": "Search query analytics, cache hit rates, and cost tracking",
"analyticsUtilizationDescription": "Provider utilization metrics and capacity planning",
"analyticsComboHealthDescription": "Real-time health and performance of combo routing configurations",
"analyticsCompressionDescription": "Context compression analytics and token savings",
"costsBudgetDescription": "Budget limits and spending alerts per API key and provider",
"costsPricingDescription": "Custom pricing configuration for token cost calculations",
"logsProxyDescription": "Upstream proxy request logs and traffic inspection",
"logsConsoleDescription": "Application console output and debug logs",
"logsActivityDescription": "Audit trail of user actions and system events",
"auditMcpDescription": "MCP tool invocation audit trail and compliance records",
"settingsGeneralDescription": "Storage, database, and general instance configuration",
"settingsAppearanceDescription": "Theme, branding, and visual customization",
"settingsAiDescription": "AI behaviors, thinking budgets, vision, and memory settings",
"settingsSecurityDescription": "Authentication, authorization, and access control settings",
"settingsRoutingDescription": "Routing rules, model aliases, combo defaults, and degradation settings",
"settingsResilienceDescription": "Circuit breaker, retry, and fallback configuration",
"settingsAdvancedDescription": "Advanced payload rules, request limits, and proxy API settings",
"mitmProxyDescription": "Configure MITM proxy settings for traffic inspection and debugging",
"oneProxyDescription": "Configure 1Proxy settings for advanced proxy chaining"
},
"home": {
"quickStart": "Rychlý start",

View File

@@ -675,7 +675,7 @@
"docs": "Dokumenter",
"issues": "Problemer",
"endpoints": "Endpoints",
"apiManager": "API Manager",
"apiManager": "API Key Manager",
"logs": "Logs",
"webhooks": "__MISSING__:Webhooks",
"auditLog": "Revisionslog",
@@ -743,7 +743,55 @@
"contextSection": "Context & Cache",
"contextCaveman": "Caveman",
"contextRtk": "RTK",
"contextCombos": "Compression Combos"
"contextCombos": "Compression Combos",
"routingSection": "Routing",
"protocolsSection": "Protocols",
"agentsAiSection": "Agents & AI",
"cacheContextSection": "Cache & Context",
"analyticsSection": "Analytics",
"costsSection": "Costs",
"monitoringSection": "Monitoring",
"auditSecuritySection": "Audit & Security",
"devtoolsSection": "Dev Tools",
"configurationSection": "Configuration",
"aiFeaturesSection": "AI Features",
"mcp": "MCP",
"a2a": "A2A",
"apiEndpoints": "API Endpoints",
"batchFiles": "Files",
"analyticsEvals": "Evals",
"analyticsSearch": "Search",
"analyticsUtilization": "Utilization",
"analyticsComboHealth": "Combo Health",
"analyticsCompression": "Compression",
"costsBudget": "Budget",
"costsPricing": "Pricing",
"logsProxy": "Proxy Logs",
"logsConsole": "Console",
"logsActivity": "Activity",
"auditMcp": "MCP Audit",
"settingsGeneral": "General",
"settingsAppearance": "Appearance",
"settingsAi": "AI Settings",
"settingsSecurity": "Security",
"settingsRouting": "Routing",
"settingsResilience": "Resilience",
"settingsAdvanced": "Advanced",
"omniProxySection": "OmniProxy",
"quotaTracker": "Quota Tracker",
"consoleLogs": "Console Logs",
"globalRouting": "Global Routing",
"mitmProxy": "MITM Proxy",
"oneProxy": "1Proxy",
"agenticFeaturesSection": "Agentic Features",
"otherFeaturesSection": "Other Features",
"compressionContextGroup": "Compression Context",
"toolsGroup": "Tools",
"integrationsGroup": "Integrations",
"proxyGroup": "Proxy",
"costsParametersGroup": "Costs Parameters",
"auditGroup": "Audit",
"batchGroup": "Batch"
},
"webhooks": {
"title": "__MISSING__:Webhooks",
@@ -879,7 +927,51 @@
"media": "Medie",
"mediaDescription": "Generer billeder, videoer og musik",
"themes": "Temaer",
"themesDescription": "Vælg et farvetema til hele dashboardpanelet"
"themesDescription": "Vælg et farvetema til hele dashboardpanelet",
"costsDescription": "Track spending, analyze trends, and manage your AI budget across all providers",
"cacheDescription": "Monitor provider prompt cache efficiency and local semantic response reuse.",
"limitsDescription": "Configure rate limits and quotas per API key and provider",
"apiManagerDescription": "Manage API keys and access control for your OmniRoute instance",
"batchDescription": "Process large volumes of requests asynchronously with batched API calls",
"contextCavemanDescription": "Rule-based message compression, language packs, analytics and output mode controls.",
"contextRtkDescription": "Command-aware compression for tool output, terminal logs and build results.",
"contextCombosDescription": "Define how engines are combined for different routing scenarios.",
"changelogDescription": "Stay up to date with the latest platform features and announcements.",
"agentsDescription": "Manage and configure AI agent tools: Codex, Devin, Jules, and custom agents",
"cloudAgentsDescription": "Orchestrate cloud-based AI agents with live task tracking and plan approval",
"memoryDescription": "Persistent conversational memory with semantic search and FTS5 full-text indexing",
"skillsDescription": "Install and manage sandbox skills for automated prompt and tool execution",
"agentSkillsDescription": "Agent-ready skills catalog with one-click URL copy for AI client integration",
"translatorDescription": "Translate and test prompts across API formats: OpenAI ↔ Claude ↔ Gemini",
"playgroundDescription": "Test prompts interactively with live provider responses and format inspection",
"searchToolsDescription": "Search analytics, provider breakdown, cache hit rates, and cost tracking",
"logsDescription": "Real-time request logs, error traces, and streaming event inspector",
"auditDescription": "Compliance audit trail of API key usage, MCP tool calls, and policy events",
"webhooksDescription": "Configure webhook endpoints to receive real-time event notifications",
"healthDescription": "System health overview: providers, circuit breakers, rate limits, and database",
"proxyDescription": "Configure upstream proxy settings for outbound provider connections",
"apiEndpointsDescription": "Manage custom API endpoint configurations and routing overrides",
"batchFilesDescription": "Browse and manage batch job output files and results",
"analyticsEvalsDescription": "Model evaluation results and performance benchmarks",
"analyticsSearchDescription": "Search query analytics, cache hit rates, and cost tracking",
"analyticsUtilizationDescription": "Provider utilization metrics and capacity planning",
"analyticsComboHealthDescription": "Real-time health and performance of combo routing configurations",
"analyticsCompressionDescription": "Context compression analytics and token savings",
"costsBudgetDescription": "Budget limits and spending alerts per API key and provider",
"costsPricingDescription": "Custom pricing configuration for token cost calculations",
"logsProxyDescription": "Upstream proxy request logs and traffic inspection",
"logsConsoleDescription": "Application console output and debug logs",
"logsActivityDescription": "Audit trail of user actions and system events",
"auditMcpDescription": "MCP tool invocation audit trail and compliance records",
"settingsGeneralDescription": "Storage, database, and general instance configuration",
"settingsAppearanceDescription": "Theme, branding, and visual customization",
"settingsAiDescription": "AI behaviors, thinking budgets, vision, and memory settings",
"settingsSecurityDescription": "Authentication, authorization, and access control settings",
"settingsRoutingDescription": "Routing rules, model aliases, combo defaults, and degradation settings",
"settingsResilienceDescription": "Circuit breaker, retry, and fallback configuration",
"settingsAdvancedDescription": "Advanced payload rules, request limits, and proxy API settings",
"mitmProxyDescription": "Configure MITM proxy settings for traffic inspection and debugging",
"oneProxyDescription": "Configure 1Proxy settings for advanced proxy chaining"
},
"home": {
"quickStart": "Hurtig start",

View File

@@ -743,7 +743,55 @@
"contextSection": "Context & Cache",
"contextCaveman": "Caveman",
"contextRtk": "RTK",
"contextCombos": "Compression Combos"
"contextCombos": "Compression Combos",
"routingSection": "Routing",
"protocolsSection": "Protocols",
"agentsAiSection": "Agents & AI",
"cacheContextSection": "Cache & Context",
"analyticsSection": "Analytics",
"costsSection": "Costs",
"monitoringSection": "Monitoring",
"auditSecuritySection": "Audit & Security",
"devtoolsSection": "Dev Tools",
"configurationSection": "Configuration",
"aiFeaturesSection": "AI Features",
"mcp": "MCP",
"a2a": "A2A",
"apiEndpoints": "API Endpoints",
"batchFiles": "Files",
"analyticsEvals": "Evals",
"analyticsSearch": "Search",
"analyticsUtilization": "Utilization",
"analyticsComboHealth": "Combo Health",
"analyticsCompression": "Compression",
"costsBudget": "Budget",
"costsPricing": "Pricing",
"logsProxy": "Proxy Logs",
"logsConsole": "Console",
"logsActivity": "Activity",
"auditMcp": "MCP Audit",
"settingsGeneral": "General",
"settingsAppearance": "Appearance",
"settingsAi": "AI Settings",
"settingsSecurity": "Security",
"settingsRouting": "Routing",
"settingsResilience": "Resilience",
"settingsAdvanced": "Advanced",
"omniProxySection": "OmniProxy",
"quotaTracker": "Quota Tracker",
"consoleLogs": "Console Logs",
"globalRouting": "Global Routing",
"mitmProxy": "MITM Proxy",
"oneProxy": "1Proxy",
"agenticFeaturesSection": "Agentic Features",
"otherFeaturesSection": "Other Features",
"compressionContextGroup": "Compression Context",
"toolsGroup": "Tools",
"integrationsGroup": "Integrations",
"proxyGroup": "Proxy",
"costsParametersGroup": "Costs Parameters",
"auditGroup": "Audit",
"batchGroup": "Batch"
},
"webhooks": {
"title": "__MISSING__:Webhooks",
@@ -879,7 +927,51 @@
"media": "Medien",
"mediaDescription": "Generieren Sie Bilder, Videos und Musik",
"themes": "Themen",
"themesDescription": "Wählen Sie ein Farbthema für das gesamte Dashboard-Panel"
"themesDescription": "Wählen Sie ein Farbthema für das gesamte Dashboard-Panel",
"costsDescription": "Track spending, analyze trends, and manage your AI budget across all providers",
"cacheDescription": "Monitor provider prompt cache efficiency and local semantic response reuse.",
"limitsDescription": "Configure rate limits and quotas per API key and provider",
"apiManagerDescription": "Manage API keys and access control for your OmniRoute instance",
"batchDescription": "Process large volumes of requests asynchronously with batched API calls",
"contextCavemanDescription": "Rule-based message compression, language packs, analytics and output mode controls.",
"contextRtkDescription": "Command-aware compression for tool output, terminal logs and build results.",
"contextCombosDescription": "Define how engines are combined for different routing scenarios.",
"changelogDescription": "Stay up to date with the latest platform features and announcements.",
"agentsDescription": "Manage and configure AI agent tools: Codex, Devin, Jules, and custom agents",
"cloudAgentsDescription": "Orchestrate cloud-based AI agents with live task tracking and plan approval",
"memoryDescription": "Persistent conversational memory with semantic search and FTS5 full-text indexing",
"skillsDescription": "Install and manage sandbox skills for automated prompt and tool execution",
"agentSkillsDescription": "Agent-ready skills catalog with one-click URL copy for AI client integration",
"translatorDescription": "Translate and test prompts across API formats: OpenAI ↔ Claude ↔ Gemini",
"playgroundDescription": "Test prompts interactively with live provider responses and format inspection",
"searchToolsDescription": "Search analytics, provider breakdown, cache hit rates, and cost tracking",
"logsDescription": "Real-time request logs, error traces, and streaming event inspector",
"auditDescription": "Compliance audit trail of API key usage, MCP tool calls, and policy events",
"webhooksDescription": "Configure webhook endpoints to receive real-time event notifications",
"healthDescription": "System health overview: providers, circuit breakers, rate limits, and database",
"proxyDescription": "Configure upstream proxy settings for outbound provider connections",
"apiEndpointsDescription": "Manage custom API endpoint configurations and routing overrides",
"batchFilesDescription": "Browse and manage batch job output files and results",
"analyticsEvalsDescription": "Model evaluation results and performance benchmarks",
"analyticsSearchDescription": "Search query analytics, cache hit rates, and cost tracking",
"analyticsUtilizationDescription": "Provider utilization metrics and capacity planning",
"analyticsComboHealthDescription": "Real-time health and performance of combo routing configurations",
"analyticsCompressionDescription": "Context compression analytics and token savings",
"costsBudgetDescription": "Budget limits and spending alerts per API key and provider",
"costsPricingDescription": "Custom pricing configuration for token cost calculations",
"logsProxyDescription": "Upstream proxy request logs and traffic inspection",
"logsConsoleDescription": "Application console output and debug logs",
"logsActivityDescription": "Audit trail of user actions and system events",
"auditMcpDescription": "MCP tool invocation audit trail and compliance records",
"settingsGeneralDescription": "Storage, database, and general instance configuration",
"settingsAppearanceDescription": "Theme, branding, and visual customization",
"settingsAiDescription": "AI behaviors, thinking budgets, vision, and memory settings",
"settingsSecurityDescription": "Authentication, authorization, and access control settings",
"settingsRoutingDescription": "Routing rules, model aliases, combo defaults, and degradation settings",
"settingsResilienceDescription": "Circuit breaker, retry, and fallback configuration",
"settingsAdvancedDescription": "Advanced payload rules, request limits, and proxy API settings",
"mitmProxyDescription": "Configure MITM proxy settings for traffic inspection and debugging",
"oneProxyDescription": "Configure 1Proxy settings for advanced proxy chaining"
},
"home": {
"quickStart": "Schnellstart",

View File

@@ -594,6 +594,9 @@
"syncingData": "Syncing Data",
"cloudBenefitPorts": "Cloud Benefit Ports",
"compatibleProviders": "Compatible Providers",
"freeTierProviders": "Free Tier Providers",
"freeTierLabel": "Free tier available",
"freeTierProvidersDesc": "Providers with free tiers — some require an API key signup, others need no credentials at all.",
"clearCache": "Clear Cache",
"reqs": "Reqs",
"addAnthropicCompatible": "Add Anthropic Compatible",
@@ -661,7 +664,7 @@
"costs": "Costs",
"health": "Health",
"proxy": "Proxy",
"limits": "Limits & Quotas",
"limits": "Quota Limits",
"cliTools": "CLI Tools",
"media": "Media",
"settings": "Settings",
@@ -672,12 +675,12 @@
"cloudAgents": "Cloud Agents",
"memory": "Memory",
"skills": "Skills",
"omniSkills": "Omni Skills",
"agentSkills": "Agent Skills",
"omniSkills": "OmniSkills",
"agentSkills": "AgentSkills",
"docs": "Docs",
"issues": "Issues",
"endpoints": "Endpoints",
"apiManager": "API Manager",
"apiManager": "API Key Manager",
"logs": "Logs",
"webhooks": "Webhooks",
"auditLog": "Audit Log",
@@ -745,7 +748,56 @@
"contextSection": "Context & Cache",
"contextCaveman": "Caveman",
"contextRtk": "RTK",
"contextCombos": "Compression Combos"
"contextCombos": "Compression Combos",
"routingSection": "Routing",
"protocolsSection": "Protocols",
"agentsAiSection": "Agents & AI",
"cacheContextSection": "Cache & Context",
"analyticsSection": "Analytics",
"costsSection": "Costs",
"monitoringSection": "Monitoring",
"auditSecuritySection": "Audit & Security",
"devtoolsSection": "Dev Tools",
"configurationSection": "Configuration",
"aiFeaturesSection": "AI Features",
"mcp": "MCP Server",
"a2a": "A2A Server",
"apiEndpoints": "API Endpoints",
"batchFiles": "Files",
"analyticsEvals": "Evals",
"analyticsSearch": "Search",
"analyticsUtilization": "Utilization",
"analyticsComboHealth": "Combo Health",
"analyticsCompression": "Compression",
"costsBudget": "Budget",
"costsPricing": "Pricing",
"logsProxy": "Proxy Logs",
"logsConsole": "Console",
"logsActivity": "Activity",
"auditMcp": "MCP Audit",
"auditA2a": "A2A Audit",
"settingsGeneral": "General",
"settingsAppearance": "Appearance",
"settingsAi": "AI Settings",
"settingsSecurity": "Security",
"settingsRouting": "Routing",
"settingsResilience": "Resilience",
"settingsAdvanced": "Advanced",
"omniProxySection": "OmniProxy",
"quotaTracker": "Quota Tracker",
"consoleLogs": "Console Logs",
"globalRouting": "Global Routing",
"mitmProxy": "MITM Proxy",
"oneProxy": "1Proxy",
"agenticFeaturesSection": "Agentic Features",
"otherFeaturesSection": "Other Features",
"compressionContextGroup": "Compression Context",
"toolsGroup": "Tools",
"integrationsGroup": "Integrations",
"proxyGroup": "Proxy",
"costsParametersGroup": "Costs Parameters",
"auditGroup": "Audit",
"batchGroup": "Batch"
},
"webhooks": {
"title": "Webhooks",
@@ -842,7 +894,24 @@
"allResults": "All results",
"success": "Success",
"failure": "Failure",
"noMcpEvents": "No MCP audit events recorded."
"noMcpEvents": "No MCP audit events recorded.",
"a2aAudit": "A2A Audit",
"a2aAuditDesc": "Task execution audit trail recorded by the A2A server.",
"a2aShowingTasks": "Showing {count} of {total} tasks",
"a2aSkill": "Skill",
"a2aSkillPlaceholder": "Filter by skill name",
"a2aState": "State",
"a2aAllStates": "All states",
"a2aStateSubmitted": "Submitted",
"a2aStateWorking": "Working",
"a2aStateCompleted": "Completed",
"a2aStateFailed": "Failed",
"a2aStateCancelled": "Cancelled",
"a2aTaskId": "Task ID",
"a2aEvents": "Events",
"a2aArtifacts": "Artifacts",
"a2aNoTasks": "No A2A tasks recorded.",
"a2aLoadingTasks": "Loading A2A tasks..."
},
"themesPage": {
"title": "Themes",
@@ -870,9 +939,9 @@
"homeDescription": "Welcome to OmniRoute",
"endpoint": "Endpoints",
"endpointDescription": "Manage proxy endpoints, MCP, A2A, and API endpoints",
"mcp": "MCP",
"mcp": "MCP Server",
"mcpDescription": "Model Context Protocol server management and tools",
"a2a": "A2A",
"a2a": "A2A Server",
"a2aDescription": "Agent-to-Agent protocol tasks and observability",
"settings": "Settings",
"settingsDescription": "Manage your preferences",
@@ -881,7 +950,53 @@
"media": "Media",
"mediaDescription": "Generate images, videos, and music",
"themes": "Themes",
"themesDescription": "Choose a color theme for the whole dashboard panel"
"themesDescription": "Choose a color theme for the whole dashboard panel",
"costsDescription": "Track spending, analyze trends, and manage your AI budget across all providers",
"cacheDescription": "Monitor provider prompt cache efficiency and local semantic response reuse.",
"limitsDescription": "Configure rate limits and quotas per API key and provider",
"apiManagerDescription": "Manage API keys and access control for your OmniRoute instance",
"batchDescription": "Process large volumes of requests asynchronously with batched API calls",
"contextCavemanDescription": "Rule-based message compression, language packs, analytics and output mode controls.",
"contextRtkDescription": "Command-aware compression for tool output, terminal logs and build results.",
"contextCombosDescription": "Define how engines are combined for different routing scenarios.",
"changelogDescription": "Stay up to date with the latest platform features and announcements.",
"agentsDescription": "Manage and configure AI agent tools: Codex, Devin, Jules, and custom agents",
"cloudAgentsDescription": "Orchestrate cloud-based AI agents with live task tracking and plan approval",
"memoryDescription": "Persistent conversational memory with semantic search and FTS5 full-text indexing",
"skillsDescription": "Install and manage sandbox skills for automated prompt and tool execution",
"agentSkillsDescription": "Agent-ready skills catalog with one-click URL copy for AI client integration",
"translatorDescription": "Translate and test prompts across API formats: OpenAI ↔ Claude ↔ Gemini",
"playgroundDescription": "Test prompts interactively with live provider responses and format inspection",
"searchToolsDescription": "Search analytics, provider breakdown, cache hit rates, and cost tracking",
"logsDescription": "Real-time request logs, error traces, and streaming event inspector",
"auditDescription": "Compliance audit trail of API key usage, MCP tool calls, and policy events",
"webhooksDescription": "Configure webhook endpoints to receive real-time event notifications",
"healthDescription": "System health overview: providers, circuit breakers, rate limits, and database",
"proxyDescription": "Configure upstream proxy settings for outbound provider connections",
"apiEndpointsDescription": "Manage custom API endpoint configurations and routing overrides",
"batchFilesDescription": "Browse and manage batch job output files and results",
"analyticsEvalsDescription": "Model evaluation results and performance benchmarks",
"analyticsSearchDescription": "Search query analytics, cache hit rates, and cost tracking",
"analyticsUtilizationDescription": "Provider utilization metrics and capacity planning",
"analyticsComboHealthDescription": "Real-time health and performance of combo routing configurations",
"analyticsCompressionDescription": "Context compression analytics and token savings",
"costsBudgetDescription": "Budget limits and spending alerts per API key and provider",
"costsPricingDescription": "Custom pricing configuration for token cost calculations",
"logsProxyDescription": "Upstream proxy request logs and traffic inspection",
"logsConsoleDescription": "Application console output and debug logs",
"logsActivityDescription": "Audit trail of user actions and system events",
"auditMcpDescription": "MCP tool invocation audit trail and compliance records",
"auditA2a": "A2A Audit",
"auditA2aDescription": "A2A task execution audit trail, state transitions, and skill invocation records",
"settingsGeneralDescription": "Storage, database, and general instance configuration",
"settingsAppearanceDescription": "Theme, branding, and visual customization",
"settingsAiDescription": "AI behaviors, thinking budgets, vision, and memory settings",
"settingsSecurityDescription": "Authentication, authorization, and access control settings",
"settingsRoutingDescription": "Routing rules, model aliases, combo defaults, and degradation settings",
"settingsResilienceDescription": "Circuit breaker, retry, and fallback configuration",
"settingsAdvancedDescription": "Advanced payload rules, request limits, and proxy API settings",
"mitmProxyDescription": "Configure MITM proxy settings for traffic inspection and debugging",
"oneProxyDescription": "Configure 1Proxy settings for advanced proxy chaining"
},
"home": {
"quickStart": "Quick Start",
@@ -2116,6 +2231,14 @@
"a2aQuickStartStep3": "Track and control tasks using `tasks/get` and `tasks/cancel`.",
"completionsLegacy": "Completions (Legacy)",
"completionsLegacyDesc": "Legacy OpenAI text completions — accepts both prompt string and messages array format",
"messagesApi": "Messages",
"messagesApiDesc": "Native Anthropic Messages API format for Claude-compatible providers",
"imageEdits": "Image Edits",
"imageEditsDesc": "Edit and modify existing images with AI (inpainting, outpainting, variations)",
"batchApi": "Batch API",
"batchApiDesc": "Process large batches of requests asynchronously (OpenAI-compatible)",
"filesApi": "Files API",
"filesApiDesc": "Upload and manage files for batch processing",
"videoGeneration": "Video Generation",
"videoDesc": "Generate videos using AI models like ComfyUI and Stable Video Diffusion.",
"tailscaleRequestFailed": "Failed to load Tailscale status",
@@ -3131,7 +3254,11 @@
"zedImportNetworkError": "Zed Import Network Error",
"zedImportNone": "Zed Import None",
"zedImportSuccess": "Zed Import Success",
"zedImporting": "Zed Importing"
"zedImporting": "Zed Importing",
"freeTierProviders": "Free Tier Providers",
"freeTierLabel": "Free tier available",
"freeTierProvidersDesc": "Providers with free tiers — some require an API key signup, others need no credentials at all.",
"providerSummaryAll": "Total"
},
"settings": {
"title": "Settings",

View File

@@ -743,7 +743,55 @@
"contextSection": "Context & Cache",
"contextCaveman": "Caveman",
"contextRtk": "RTK",
"contextCombos": "Compression Combos"
"contextCombos": "Compression Combos",
"routingSection": "Routing",
"protocolsSection": "Protocols",
"agentsAiSection": "Agents & AI",
"cacheContextSection": "Cache & Context",
"analyticsSection": "Analytics",
"costsSection": "Costs",
"monitoringSection": "Monitoring",
"auditSecuritySection": "Audit & Security",
"devtoolsSection": "Dev Tools",
"configurationSection": "Configuration",
"aiFeaturesSection": "AI Features",
"mcp": "MCP",
"a2a": "A2A",
"apiEndpoints": "API Endpoints",
"batchFiles": "Files",
"analyticsEvals": "Evals",
"analyticsSearch": "Search",
"analyticsUtilization": "Utilization",
"analyticsComboHealth": "Combo Health",
"analyticsCompression": "Compression",
"costsBudget": "Budget",
"costsPricing": "Pricing",
"logsProxy": "Proxy Logs",
"logsConsole": "Console",
"logsActivity": "Activity",
"auditMcp": "MCP Audit",
"settingsGeneral": "General",
"settingsAppearance": "Appearance",
"settingsAi": "AI Settings",
"settingsSecurity": "Security",
"settingsRouting": "Routing",
"settingsResilience": "Resilience",
"settingsAdvanced": "Advanced",
"omniProxySection": "OmniProxy",
"quotaTracker": "Quota Tracker",
"consoleLogs": "Console Logs",
"globalRouting": "Global Routing",
"mitmProxy": "MITM Proxy",
"oneProxy": "1Proxy",
"agenticFeaturesSection": "Agentic Features",
"otherFeaturesSection": "Other Features",
"compressionContextGroup": "Compression Context",
"toolsGroup": "Tools",
"integrationsGroup": "Integrations",
"proxyGroup": "Proxy",
"costsParametersGroup": "Costs Parameters",
"auditGroup": "Audit",
"batchGroup": "Batch"
},
"webhooks": {
"title": "__MISSING__:Webhooks",
@@ -879,7 +927,51 @@
"media": "Medios de comunicación",
"mediaDescription": "Genera imágenes, vídeos y música.",
"themes": "Temas",
"themesDescription": "Elija un tema de color para todo el panel del tablero"
"themesDescription": "Elija un tema de color para todo el panel del tablero",
"costsDescription": "Track spending, analyze trends, and manage your AI budget across all providers",
"cacheDescription": "Monitor provider prompt cache efficiency and local semantic response reuse.",
"limitsDescription": "Configure rate limits and quotas per API key and provider",
"apiManagerDescription": "Manage API keys and access control for your OmniRoute instance",
"batchDescription": "Process large volumes of requests asynchronously with batched API calls",
"contextCavemanDescription": "Rule-based message compression, language packs, analytics and output mode controls.",
"contextRtkDescription": "Command-aware compression for tool output, terminal logs and build results.",
"contextCombosDescription": "Define how engines are combined for different routing scenarios.",
"changelogDescription": "Stay up to date with the latest platform features and announcements.",
"agentsDescription": "Manage and configure AI agent tools: Codex, Devin, Jules, and custom agents",
"cloudAgentsDescription": "Orchestrate cloud-based AI agents with live task tracking and plan approval",
"memoryDescription": "Persistent conversational memory with semantic search and FTS5 full-text indexing",
"skillsDescription": "Install and manage sandbox skills for automated prompt and tool execution",
"agentSkillsDescription": "Agent-ready skills catalog with one-click URL copy for AI client integration",
"translatorDescription": "Translate and test prompts across API formats: OpenAI ↔ Claude ↔ Gemini",
"playgroundDescription": "Test prompts interactively with live provider responses and format inspection",
"searchToolsDescription": "Search analytics, provider breakdown, cache hit rates, and cost tracking",
"logsDescription": "Real-time request logs, error traces, and streaming event inspector",
"auditDescription": "Compliance audit trail of API key usage, MCP tool calls, and policy events",
"webhooksDescription": "Configure webhook endpoints to receive real-time event notifications",
"healthDescription": "System health overview: providers, circuit breakers, rate limits, and database",
"proxyDescription": "Configure upstream proxy settings for outbound provider connections",
"apiEndpointsDescription": "Manage custom API endpoint configurations and routing overrides",
"batchFilesDescription": "Browse and manage batch job output files and results",
"analyticsEvalsDescription": "Model evaluation results and performance benchmarks",
"analyticsSearchDescription": "Search query analytics, cache hit rates, and cost tracking",
"analyticsUtilizationDescription": "Provider utilization metrics and capacity planning",
"analyticsComboHealthDescription": "Real-time health and performance of combo routing configurations",
"analyticsCompressionDescription": "Context compression analytics and token savings",
"costsBudgetDescription": "Budget limits and spending alerts per API key and provider",
"costsPricingDescription": "Custom pricing configuration for token cost calculations",
"logsProxyDescription": "Upstream proxy request logs and traffic inspection",
"logsConsoleDescription": "Application console output and debug logs",
"logsActivityDescription": "Audit trail of user actions and system events",
"auditMcpDescription": "MCP tool invocation audit trail and compliance records",
"settingsGeneralDescription": "Storage, database, and general instance configuration",
"settingsAppearanceDescription": "Theme, branding, and visual customization",
"settingsAiDescription": "AI behaviors, thinking budgets, vision, and memory settings",
"settingsSecurityDescription": "Authentication, authorization, and access control settings",
"settingsRoutingDescription": "Routing rules, model aliases, combo defaults, and degradation settings",
"settingsResilienceDescription": "Circuit breaker, retry, and fallback configuration",
"settingsAdvancedDescription": "Advanced payload rules, request limits, and proxy API settings",
"mitmProxyDescription": "Configure MITM proxy settings for traffic inspection and debugging",
"oneProxyDescription": "Configure 1Proxy settings for advanced proxy chaining"
},
"home": {
"quickStart": "Inicio rápido",

View File

@@ -675,7 +675,7 @@
"docs": "Docs",
"issues": "Issues",
"endpoints": "Endpoints",
"apiManager": "API Manager",
"apiManager": "API Key Manager",
"logs": "Logs",
"webhooks": "__MISSING__:Webhooks",
"auditLog": "Audit Log",
@@ -743,7 +743,55 @@
"contextSection": "Context & Cache",
"contextCaveman": "Caveman",
"contextRtk": "RTK",
"contextCombos": "Compression Combos"
"contextCombos": "Compression Combos",
"routingSection": "Routing",
"protocolsSection": "Protocols",
"agentsAiSection": "Agents & AI",
"cacheContextSection": "Cache & Context",
"analyticsSection": "Analytics",
"costsSection": "Costs",
"monitoringSection": "Monitoring",
"auditSecuritySection": "Audit & Security",
"devtoolsSection": "Dev Tools",
"configurationSection": "Configuration",
"aiFeaturesSection": "AI Features",
"mcp": "MCP",
"a2a": "A2A",
"apiEndpoints": "API Endpoints",
"batchFiles": "Files",
"analyticsEvals": "Evals",
"analyticsSearch": "Search",
"analyticsUtilization": "Utilization",
"analyticsComboHealth": "Combo Health",
"analyticsCompression": "Compression",
"costsBudget": "Budget",
"costsPricing": "Pricing",
"logsProxy": "Proxy Logs",
"logsConsole": "Console",
"logsActivity": "Activity",
"auditMcp": "MCP Audit",
"settingsGeneral": "General",
"settingsAppearance": "Appearance",
"settingsAi": "AI Settings",
"settingsSecurity": "Security",
"settingsRouting": "Routing",
"settingsResilience": "Resilience",
"settingsAdvanced": "Advanced",
"omniProxySection": "OmniProxy",
"quotaTracker": "Quota Tracker",
"consoleLogs": "Console Logs",
"globalRouting": "Global Routing",
"mitmProxy": "MITM Proxy",
"oneProxy": "1Proxy",
"agenticFeaturesSection": "Agentic Features",
"otherFeaturesSection": "Other Features",
"compressionContextGroup": "Compression Context",
"toolsGroup": "Tools",
"integrationsGroup": "Integrations",
"proxyGroup": "Proxy",
"costsParametersGroup": "Costs Parameters",
"auditGroup": "Audit",
"batchGroup": "Batch"
},
"webhooks": {
"title": "__MISSING__:Webhooks",
@@ -879,7 +927,51 @@
"media": "Media",
"mediaDescription": "Generate images, videos, and music",
"themes": "Themes",
"themesDescription": "Choose a color theme for the whole dashboard panel"
"themesDescription": "Choose a color theme for the whole dashboard panel",
"costsDescription": "Track spending, analyze trends, and manage your AI budget across all providers",
"cacheDescription": "Monitor provider prompt cache efficiency and local semantic response reuse.",
"limitsDescription": "Configure rate limits and quotas per API key and provider",
"apiManagerDescription": "Manage API keys and access control for your OmniRoute instance",
"batchDescription": "Process large volumes of requests asynchronously with batched API calls",
"contextCavemanDescription": "Rule-based message compression, language packs, analytics and output mode controls.",
"contextRtkDescription": "Command-aware compression for tool output, terminal logs and build results.",
"contextCombosDescription": "Define how engines are combined for different routing scenarios.",
"changelogDescription": "Stay up to date with the latest platform features and announcements.",
"agentsDescription": "Manage and configure AI agent tools: Codex, Devin, Jules, and custom agents",
"cloudAgentsDescription": "Orchestrate cloud-based AI agents with live task tracking and plan approval",
"memoryDescription": "Persistent conversational memory with semantic search and FTS5 full-text indexing",
"skillsDescription": "Install and manage sandbox skills for automated prompt and tool execution",
"agentSkillsDescription": "Agent-ready skills catalog with one-click URL copy for AI client integration",
"translatorDescription": "Translate and test prompts across API formats: OpenAI ↔ Claude ↔ Gemini",
"playgroundDescription": "Test prompts interactively with live provider responses and format inspection",
"searchToolsDescription": "Search analytics, provider breakdown, cache hit rates, and cost tracking",
"logsDescription": "Real-time request logs, error traces, and streaming event inspector",
"auditDescription": "Compliance audit trail of API key usage, MCP tool calls, and policy events",
"webhooksDescription": "Configure webhook endpoints to receive real-time event notifications",
"healthDescription": "System health overview: providers, circuit breakers, rate limits, and database",
"proxyDescription": "Configure upstream proxy settings for outbound provider connections",
"apiEndpointsDescription": "Manage custom API endpoint configurations and routing overrides",
"batchFilesDescription": "Browse and manage batch job output files and results",
"analyticsEvalsDescription": "Model evaluation results and performance benchmarks",
"analyticsSearchDescription": "Search query analytics, cache hit rates, and cost tracking",
"analyticsUtilizationDescription": "Provider utilization metrics and capacity planning",
"analyticsComboHealthDescription": "Real-time health and performance of combo routing configurations",
"analyticsCompressionDescription": "Context compression analytics and token savings",
"costsBudgetDescription": "Budget limits and spending alerts per API key and provider",
"costsPricingDescription": "Custom pricing configuration for token cost calculations",
"logsProxyDescription": "Upstream proxy request logs and traffic inspection",
"logsConsoleDescription": "Application console output and debug logs",
"logsActivityDescription": "Audit trail of user actions and system events",
"auditMcpDescription": "MCP tool invocation audit trail and compliance records",
"settingsGeneralDescription": "Storage, database, and general instance configuration",
"settingsAppearanceDescription": "Theme, branding, and visual customization",
"settingsAiDescription": "AI behaviors, thinking budgets, vision, and memory settings",
"settingsSecurityDescription": "Authentication, authorization, and access control settings",
"settingsRoutingDescription": "Routing rules, model aliases, combo defaults, and degradation settings",
"settingsResilienceDescription": "Circuit breaker, retry, and fallback configuration",
"settingsAdvancedDescription": "Advanced payload rules, request limits, and proxy API settings",
"mitmProxyDescription": "Configure MITM proxy settings for traffic inspection and debugging",
"oneProxyDescription": "Configure 1Proxy settings for advanced proxy chaining"
},
"home": {
"quickStart": "Quick Start",

View File

@@ -675,7 +675,7 @@
"docs": "Asiakirjat",
"issues": "Ongelmat",
"endpoints": "Päätepisteet",
"apiManager": "API Manager",
"apiManager": "API Key Manager",
"logs": "Lokit",
"webhooks": "__MISSING__:Webhooks",
"auditLog": "Tarkastusloki",
@@ -743,7 +743,55 @@
"contextSection": "Context & Cache",
"contextCaveman": "Caveman",
"contextRtk": "RTK",
"contextCombos": "Compression Combos"
"contextCombos": "Compression Combos",
"routingSection": "Routing",
"protocolsSection": "Protocols",
"agentsAiSection": "Agents & AI",
"cacheContextSection": "Cache & Context",
"analyticsSection": "Analytics",
"costsSection": "Costs",
"monitoringSection": "Monitoring",
"auditSecuritySection": "Audit & Security",
"devtoolsSection": "Dev Tools",
"configurationSection": "Configuration",
"aiFeaturesSection": "AI Features",
"mcp": "MCP",
"a2a": "A2A",
"apiEndpoints": "API Endpoints",
"batchFiles": "Files",
"analyticsEvals": "Evals",
"analyticsSearch": "Search",
"analyticsUtilization": "Utilization",
"analyticsComboHealth": "Combo Health",
"analyticsCompression": "Compression",
"costsBudget": "Budget",
"costsPricing": "Pricing",
"logsProxy": "Proxy Logs",
"logsConsole": "Console",
"logsActivity": "Activity",
"auditMcp": "MCP Audit",
"settingsGeneral": "General",
"settingsAppearance": "Appearance",
"settingsAi": "AI Settings",
"settingsSecurity": "Security",
"settingsRouting": "Routing",
"settingsResilience": "Resilience",
"settingsAdvanced": "Advanced",
"omniProxySection": "OmniProxy",
"quotaTracker": "Quota Tracker",
"consoleLogs": "Console Logs",
"globalRouting": "Global Routing",
"mitmProxy": "MITM Proxy",
"oneProxy": "1Proxy",
"agenticFeaturesSection": "Agentic Features",
"otherFeaturesSection": "Other Features",
"compressionContextGroup": "Compression Context",
"toolsGroup": "Tools",
"integrationsGroup": "Integrations",
"proxyGroup": "Proxy",
"costsParametersGroup": "Costs Parameters",
"auditGroup": "Audit",
"batchGroup": "Batch"
},
"webhooks": {
"title": "__MISSING__:Webhooks",
@@ -879,7 +927,51 @@
"media": "Media",
"mediaDescription": "Luo kuvia, videoita ja musiikkia",
"themes": "Teemat",
"themesDescription": "Valitse väriteema koko kojelautapaneelille"
"themesDescription": "Valitse väriteema koko kojelautapaneelille",
"costsDescription": "Track spending, analyze trends, and manage your AI budget across all providers",
"cacheDescription": "Monitor provider prompt cache efficiency and local semantic response reuse.",
"limitsDescription": "Configure rate limits and quotas per API key and provider",
"apiManagerDescription": "Manage API keys and access control for your OmniRoute instance",
"batchDescription": "Process large volumes of requests asynchronously with batched API calls",
"contextCavemanDescription": "Rule-based message compression, language packs, analytics and output mode controls.",
"contextRtkDescription": "Command-aware compression for tool output, terminal logs and build results.",
"contextCombosDescription": "Define how engines are combined for different routing scenarios.",
"changelogDescription": "Stay up to date with the latest platform features and announcements.",
"agentsDescription": "Manage and configure AI agent tools: Codex, Devin, Jules, and custom agents",
"cloudAgentsDescription": "Orchestrate cloud-based AI agents with live task tracking and plan approval",
"memoryDescription": "Persistent conversational memory with semantic search and FTS5 full-text indexing",
"skillsDescription": "Install and manage sandbox skills for automated prompt and tool execution",
"agentSkillsDescription": "Agent-ready skills catalog with one-click URL copy for AI client integration",
"translatorDescription": "Translate and test prompts across API formats: OpenAI ↔ Claude ↔ Gemini",
"playgroundDescription": "Test prompts interactively with live provider responses and format inspection",
"searchToolsDescription": "Search analytics, provider breakdown, cache hit rates, and cost tracking",
"logsDescription": "Real-time request logs, error traces, and streaming event inspector",
"auditDescription": "Compliance audit trail of API key usage, MCP tool calls, and policy events",
"webhooksDescription": "Configure webhook endpoints to receive real-time event notifications",
"healthDescription": "System health overview: providers, circuit breakers, rate limits, and database",
"proxyDescription": "Configure upstream proxy settings for outbound provider connections",
"apiEndpointsDescription": "Manage custom API endpoint configurations and routing overrides",
"batchFilesDescription": "Browse and manage batch job output files and results",
"analyticsEvalsDescription": "Model evaluation results and performance benchmarks",
"analyticsSearchDescription": "Search query analytics, cache hit rates, and cost tracking",
"analyticsUtilizationDescription": "Provider utilization metrics and capacity planning",
"analyticsComboHealthDescription": "Real-time health and performance of combo routing configurations",
"analyticsCompressionDescription": "Context compression analytics and token savings",
"costsBudgetDescription": "Budget limits and spending alerts per API key and provider",
"costsPricingDescription": "Custom pricing configuration for token cost calculations",
"logsProxyDescription": "Upstream proxy request logs and traffic inspection",
"logsConsoleDescription": "Application console output and debug logs",
"logsActivityDescription": "Audit trail of user actions and system events",
"auditMcpDescription": "MCP tool invocation audit trail and compliance records",
"settingsGeneralDescription": "Storage, database, and general instance configuration",
"settingsAppearanceDescription": "Theme, branding, and visual customization",
"settingsAiDescription": "AI behaviors, thinking budgets, vision, and memory settings",
"settingsSecurityDescription": "Authentication, authorization, and access control settings",
"settingsRoutingDescription": "Routing rules, model aliases, combo defaults, and degradation settings",
"settingsResilienceDescription": "Circuit breaker, retry, and fallback configuration",
"settingsAdvancedDescription": "Advanced payload rules, request limits, and proxy API settings",
"mitmProxyDescription": "Configure MITM proxy settings for traffic inspection and debugging",
"oneProxyDescription": "Configure 1Proxy settings for advanced proxy chaining"
},
"home": {
"quickStart": "Pika-aloitus",

View File

@@ -743,7 +743,55 @@
"contextSection": "Context & Cache",
"contextCaveman": "Caveman",
"contextRtk": "RTK",
"contextCombos": "Compression Combos"
"contextCombos": "Compression Combos",
"routingSection": "Routing",
"protocolsSection": "Protocols",
"agentsAiSection": "Agents & AI",
"cacheContextSection": "Cache & Context",
"analyticsSection": "Analytics",
"costsSection": "Costs",
"monitoringSection": "Monitoring",
"auditSecuritySection": "Audit & Security",
"devtoolsSection": "Dev Tools",
"configurationSection": "Configuration",
"aiFeaturesSection": "AI Features",
"mcp": "MCP",
"a2a": "A2A",
"apiEndpoints": "API Endpoints",
"batchFiles": "Files",
"analyticsEvals": "Evals",
"analyticsSearch": "Search",
"analyticsUtilization": "Utilization",
"analyticsComboHealth": "Combo Health",
"analyticsCompression": "Compression",
"costsBudget": "Budget",
"costsPricing": "Pricing",
"logsProxy": "Proxy Logs",
"logsConsole": "Console",
"logsActivity": "Activity",
"auditMcp": "MCP Audit",
"settingsGeneral": "General",
"settingsAppearance": "Appearance",
"settingsAi": "AI Settings",
"settingsSecurity": "Security",
"settingsRouting": "Routing",
"settingsResilience": "Resilience",
"settingsAdvanced": "Advanced",
"omniProxySection": "OmniProxy",
"quotaTracker": "Quota Tracker",
"consoleLogs": "Console Logs",
"globalRouting": "Global Routing",
"mitmProxy": "MITM Proxy",
"oneProxy": "1Proxy",
"agenticFeaturesSection": "Agentic Features",
"otherFeaturesSection": "Other Features",
"compressionContextGroup": "Compression Context",
"toolsGroup": "Tools",
"integrationsGroup": "Integrations",
"proxyGroup": "Proxy",
"costsParametersGroup": "Costs Parameters",
"auditGroup": "Audit",
"batchGroup": "Batch"
},
"webhooks": {
"title": "__MISSING__:Webhooks",
@@ -879,7 +927,51 @@
"media": "Médias",
"mediaDescription": "Générez des images, des vidéos et de la musique",
"themes": "Thèmes",
"themesDescription": "Choisissez un thème de couleur pour l'ensemble du panneau du tableau de bord"
"themesDescription": "Choisissez un thème de couleur pour l'ensemble du panneau du tableau de bord",
"costsDescription": "Track spending, analyze trends, and manage your AI budget across all providers",
"cacheDescription": "Monitor provider prompt cache efficiency and local semantic response reuse.",
"limitsDescription": "Configure rate limits and quotas per API key and provider",
"apiManagerDescription": "Manage API keys and access control for your OmniRoute instance",
"batchDescription": "Process large volumes of requests asynchronously with batched API calls",
"contextCavemanDescription": "Rule-based message compression, language packs, analytics and output mode controls.",
"contextRtkDescription": "Command-aware compression for tool output, terminal logs and build results.",
"contextCombosDescription": "Define how engines are combined for different routing scenarios.",
"changelogDescription": "Stay up to date with the latest platform features and announcements.",
"agentsDescription": "Manage and configure AI agent tools: Codex, Devin, Jules, and custom agents",
"cloudAgentsDescription": "Orchestrate cloud-based AI agents with live task tracking and plan approval",
"memoryDescription": "Persistent conversational memory with semantic search and FTS5 full-text indexing",
"skillsDescription": "Install and manage sandbox skills for automated prompt and tool execution",
"agentSkillsDescription": "Agent-ready skills catalog with one-click URL copy for AI client integration",
"translatorDescription": "Translate and test prompts across API formats: OpenAI ↔ Claude ↔ Gemini",
"playgroundDescription": "Test prompts interactively with live provider responses and format inspection",
"searchToolsDescription": "Search analytics, provider breakdown, cache hit rates, and cost tracking",
"logsDescription": "Real-time request logs, error traces, and streaming event inspector",
"auditDescription": "Compliance audit trail of API key usage, MCP tool calls, and policy events",
"webhooksDescription": "Configure webhook endpoints to receive real-time event notifications",
"healthDescription": "System health overview: providers, circuit breakers, rate limits, and database",
"proxyDescription": "Configure upstream proxy settings for outbound provider connections",
"apiEndpointsDescription": "Manage custom API endpoint configurations and routing overrides",
"batchFilesDescription": "Browse and manage batch job output files and results",
"analyticsEvalsDescription": "Model evaluation results and performance benchmarks",
"analyticsSearchDescription": "Search query analytics, cache hit rates, and cost tracking",
"analyticsUtilizationDescription": "Provider utilization metrics and capacity planning",
"analyticsComboHealthDescription": "Real-time health and performance of combo routing configurations",
"analyticsCompressionDescription": "Context compression analytics and token savings",
"costsBudgetDescription": "Budget limits and spending alerts per API key and provider",
"costsPricingDescription": "Custom pricing configuration for token cost calculations",
"logsProxyDescription": "Upstream proxy request logs and traffic inspection",
"logsConsoleDescription": "Application console output and debug logs",
"logsActivityDescription": "Audit trail of user actions and system events",
"auditMcpDescription": "MCP tool invocation audit trail and compliance records",
"settingsGeneralDescription": "Storage, database, and general instance configuration",
"settingsAppearanceDescription": "Theme, branding, and visual customization",
"settingsAiDescription": "AI behaviors, thinking budgets, vision, and memory settings",
"settingsSecurityDescription": "Authentication, authorization, and access control settings",
"settingsRoutingDescription": "Routing rules, model aliases, combo defaults, and degradation settings",
"settingsResilienceDescription": "Circuit breaker, retry, and fallback configuration",
"settingsAdvancedDescription": "Advanced payload rules, request limits, and proxy API settings",
"mitmProxyDescription": "Configure MITM proxy settings for traffic inspection and debugging",
"oneProxyDescription": "Configure 1Proxy settings for advanced proxy chaining"
},
"home": {
"quickStart": "Démarrage rapide",

View File

@@ -675,7 +675,7 @@
"docs": "Docs",
"issues": "Issues",
"endpoints": "Endpoints",
"apiManager": "API Manager",
"apiManager": "API Key Manager",
"logs": "Logs",
"webhooks": "__MISSING__:Webhooks",
"auditLog": "Audit Log",
@@ -743,7 +743,55 @@
"contextSection": "Context & Cache",
"contextCaveman": "Caveman",
"contextRtk": "RTK",
"contextCombos": "Compression Combos"
"contextCombos": "Compression Combos",
"routingSection": "Routing",
"protocolsSection": "Protocols",
"agentsAiSection": "Agents & AI",
"cacheContextSection": "Cache & Context",
"analyticsSection": "Analytics",
"costsSection": "Costs",
"monitoringSection": "Monitoring",
"auditSecuritySection": "Audit & Security",
"devtoolsSection": "Dev Tools",
"configurationSection": "Configuration",
"aiFeaturesSection": "AI Features",
"mcp": "MCP",
"a2a": "A2A",
"apiEndpoints": "API Endpoints",
"batchFiles": "Files",
"analyticsEvals": "Evals",
"analyticsSearch": "Search",
"analyticsUtilization": "Utilization",
"analyticsComboHealth": "Combo Health",
"analyticsCompression": "Compression",
"costsBudget": "Budget",
"costsPricing": "Pricing",
"logsProxy": "Proxy Logs",
"logsConsole": "Console",
"logsActivity": "Activity",
"auditMcp": "MCP Audit",
"settingsGeneral": "General",
"settingsAppearance": "Appearance",
"settingsAi": "AI Settings",
"settingsSecurity": "Security",
"settingsRouting": "Routing",
"settingsResilience": "Resilience",
"settingsAdvanced": "Advanced",
"omniProxySection": "OmniProxy",
"quotaTracker": "Quota Tracker",
"consoleLogs": "Console Logs",
"globalRouting": "Global Routing",
"mitmProxy": "MITM Proxy",
"oneProxy": "1Proxy",
"agenticFeaturesSection": "Agentic Features",
"otherFeaturesSection": "Other Features",
"compressionContextGroup": "Compression Context",
"toolsGroup": "Tools",
"integrationsGroup": "Integrations",
"proxyGroup": "Proxy",
"costsParametersGroup": "Costs Parameters",
"auditGroup": "Audit",
"batchGroup": "Batch"
},
"webhooks": {
"title": "__MISSING__:Webhooks",
@@ -879,7 +927,51 @@
"media": "Media",
"mediaDescription": "Generate images, videos, and music",
"themes": "Themes",
"themesDescription": "Choose a color theme for the whole dashboard panel"
"themesDescription": "Choose a color theme for the whole dashboard panel",
"costsDescription": "Track spending, analyze trends, and manage your AI budget across all providers",
"cacheDescription": "Monitor provider prompt cache efficiency and local semantic response reuse.",
"limitsDescription": "Configure rate limits and quotas per API key and provider",
"apiManagerDescription": "Manage API keys and access control for your OmniRoute instance",
"batchDescription": "Process large volumes of requests asynchronously with batched API calls",
"contextCavemanDescription": "Rule-based message compression, language packs, analytics and output mode controls.",
"contextRtkDescription": "Command-aware compression for tool output, terminal logs and build results.",
"contextCombosDescription": "Define how engines are combined for different routing scenarios.",
"changelogDescription": "Stay up to date with the latest platform features and announcements.",
"agentsDescription": "Manage and configure AI agent tools: Codex, Devin, Jules, and custom agents",
"cloudAgentsDescription": "Orchestrate cloud-based AI agents with live task tracking and plan approval",
"memoryDescription": "Persistent conversational memory with semantic search and FTS5 full-text indexing",
"skillsDescription": "Install and manage sandbox skills for automated prompt and tool execution",
"agentSkillsDescription": "Agent-ready skills catalog with one-click URL copy for AI client integration",
"translatorDescription": "Translate and test prompts across API formats: OpenAI ↔ Claude ↔ Gemini",
"playgroundDescription": "Test prompts interactively with live provider responses and format inspection",
"searchToolsDescription": "Search analytics, provider breakdown, cache hit rates, and cost tracking",
"logsDescription": "Real-time request logs, error traces, and streaming event inspector",
"auditDescription": "Compliance audit trail of API key usage, MCP tool calls, and policy events",
"webhooksDescription": "Configure webhook endpoints to receive real-time event notifications",
"healthDescription": "System health overview: providers, circuit breakers, rate limits, and database",
"proxyDescription": "Configure upstream proxy settings for outbound provider connections",
"apiEndpointsDescription": "Manage custom API endpoint configurations and routing overrides",
"batchFilesDescription": "Browse and manage batch job output files and results",
"analyticsEvalsDescription": "Model evaluation results and performance benchmarks",
"analyticsSearchDescription": "Search query analytics, cache hit rates, and cost tracking",
"analyticsUtilizationDescription": "Provider utilization metrics and capacity planning",
"analyticsComboHealthDescription": "Real-time health and performance of combo routing configurations",
"analyticsCompressionDescription": "Context compression analytics and token savings",
"costsBudgetDescription": "Budget limits and spending alerts per API key and provider",
"costsPricingDescription": "Custom pricing configuration for token cost calculations",
"logsProxyDescription": "Upstream proxy request logs and traffic inspection",
"logsConsoleDescription": "Application console output and debug logs",
"logsActivityDescription": "Audit trail of user actions and system events",
"auditMcpDescription": "MCP tool invocation audit trail and compliance records",
"settingsGeneralDescription": "Storage, database, and general instance configuration",
"settingsAppearanceDescription": "Theme, branding, and visual customization",
"settingsAiDescription": "AI behaviors, thinking budgets, vision, and memory settings",
"settingsSecurityDescription": "Authentication, authorization, and access control settings",
"settingsRoutingDescription": "Routing rules, model aliases, combo defaults, and degradation settings",
"settingsResilienceDescription": "Circuit breaker, retry, and fallback configuration",
"settingsAdvancedDescription": "Advanced payload rules, request limits, and proxy API settings",
"mitmProxyDescription": "Configure MITM proxy settings for traffic inspection and debugging",
"oneProxyDescription": "Configure 1Proxy settings for advanced proxy chaining"
},
"home": {
"quickStart": "Quick Start",

View File

@@ -743,7 +743,55 @@
"contextSection": "Context & Cache",
"contextCaveman": "Caveman",
"contextRtk": "RTK",
"contextCombos": "Compression Combos"
"contextCombos": "Compression Combos",
"routingSection": "Routing",
"protocolsSection": "Protocols",
"agentsAiSection": "Agents & AI",
"cacheContextSection": "Cache & Context",
"analyticsSection": "Analytics",
"costsSection": "Costs",
"monitoringSection": "Monitoring",
"auditSecuritySection": "Audit & Security",
"devtoolsSection": "Dev Tools",
"configurationSection": "Configuration",
"aiFeaturesSection": "AI Features",
"mcp": "MCP",
"a2a": "A2A",
"apiEndpoints": "API Endpoints",
"batchFiles": "Files",
"analyticsEvals": "Evals",
"analyticsSearch": "Search",
"analyticsUtilization": "Utilization",
"analyticsComboHealth": "Combo Health",
"analyticsCompression": "Compression",
"costsBudget": "Budget",
"costsPricing": "Pricing",
"logsProxy": "Proxy Logs",
"logsConsole": "Console",
"logsActivity": "Activity",
"auditMcp": "MCP Audit",
"settingsGeneral": "General",
"settingsAppearance": "Appearance",
"settingsAi": "AI Settings",
"settingsSecurity": "Security",
"settingsRouting": "Routing",
"settingsResilience": "Resilience",
"settingsAdvanced": "Advanced",
"omniProxySection": "OmniProxy",
"quotaTracker": "Quota Tracker",
"consoleLogs": "Console Logs",
"globalRouting": "Global Routing",
"mitmProxy": "MITM Proxy",
"oneProxy": "1Proxy",
"agenticFeaturesSection": "Agentic Features",
"otherFeaturesSection": "Other Features",
"compressionContextGroup": "Compression Context",
"toolsGroup": "Tools",
"integrationsGroup": "Integrations",
"proxyGroup": "Proxy",
"costsParametersGroup": "Costs Parameters",
"auditGroup": "Audit",
"batchGroup": "Batch"
},
"webhooks": {
"title": "__MISSING__:Webhooks",
@@ -879,7 +927,51 @@
"media": "Media",
"mediaDescription": "Generate images, videos, and music",
"themes": "Themes",
"themesDescription": "Choose a color theme for the whole dashboard panel"
"themesDescription": "Choose a color theme for the whole dashboard panel",
"costsDescription": "Track spending, analyze trends, and manage your AI budget across all providers",
"cacheDescription": "Monitor provider prompt cache efficiency and local semantic response reuse.",
"limitsDescription": "Configure rate limits and quotas per API key and provider",
"apiManagerDescription": "Manage API keys and access control for your OmniRoute instance",
"batchDescription": "Process large volumes of requests asynchronously with batched API calls",
"contextCavemanDescription": "Rule-based message compression, language packs, analytics and output mode controls.",
"contextRtkDescription": "Command-aware compression for tool output, terminal logs and build results.",
"contextCombosDescription": "Define how engines are combined for different routing scenarios.",
"changelogDescription": "Stay up to date with the latest platform features and announcements.",
"agentsDescription": "Manage and configure AI agent tools: Codex, Devin, Jules, and custom agents",
"cloudAgentsDescription": "Orchestrate cloud-based AI agents with live task tracking and plan approval",
"memoryDescription": "Persistent conversational memory with semantic search and FTS5 full-text indexing",
"skillsDescription": "Install and manage sandbox skills for automated prompt and tool execution",
"agentSkillsDescription": "Agent-ready skills catalog with one-click URL copy for AI client integration",
"translatorDescription": "Translate and test prompts across API formats: OpenAI ↔ Claude ↔ Gemini",
"playgroundDescription": "Test prompts interactively with live provider responses and format inspection",
"searchToolsDescription": "Search analytics, provider breakdown, cache hit rates, and cost tracking",
"logsDescription": "Real-time request logs, error traces, and streaming event inspector",
"auditDescription": "Compliance audit trail of API key usage, MCP tool calls, and policy events",
"webhooksDescription": "Configure webhook endpoints to receive real-time event notifications",
"healthDescription": "System health overview: providers, circuit breakers, rate limits, and database",
"proxyDescription": "Configure upstream proxy settings for outbound provider connections",
"apiEndpointsDescription": "Manage custom API endpoint configurations and routing overrides",
"batchFilesDescription": "Browse and manage batch job output files and results",
"analyticsEvalsDescription": "Model evaluation results and performance benchmarks",
"analyticsSearchDescription": "Search query analytics, cache hit rates, and cost tracking",
"analyticsUtilizationDescription": "Provider utilization metrics and capacity planning",
"analyticsComboHealthDescription": "Real-time health and performance of combo routing configurations",
"analyticsCompressionDescription": "Context compression analytics and token savings",
"costsBudgetDescription": "Budget limits and spending alerts per API key and provider",
"costsPricingDescription": "Custom pricing configuration for token cost calculations",
"logsProxyDescription": "Upstream proxy request logs and traffic inspection",
"logsConsoleDescription": "Application console output and debug logs",
"logsActivityDescription": "Audit trail of user actions and system events",
"auditMcpDescription": "MCP tool invocation audit trail and compliance records",
"settingsGeneralDescription": "Storage, database, and general instance configuration",
"settingsAppearanceDescription": "Theme, branding, and visual customization",
"settingsAiDescription": "AI behaviors, thinking budgets, vision, and memory settings",
"settingsSecurityDescription": "Authentication, authorization, and access control settings",
"settingsRoutingDescription": "Routing rules, model aliases, combo defaults, and degradation settings",
"settingsResilienceDescription": "Circuit breaker, retry, and fallback configuration",
"settingsAdvancedDescription": "Advanced payload rules, request limits, and proxy API settings",
"mitmProxyDescription": "Configure MITM proxy settings for traffic inspection and debugging",
"oneProxyDescription": "Configure 1Proxy settings for advanced proxy chaining"
},
"home": {
"quickStart": "התחלה מהירה",

View File

@@ -743,7 +743,55 @@
"contextSection": "Context & Cache",
"contextCaveman": "Caveman",
"contextRtk": "RTK",
"contextCombos": "Compression Combos"
"contextCombos": "Compression Combos",
"routingSection": "Routing",
"protocolsSection": "Protocols",
"agentsAiSection": "Agents & AI",
"cacheContextSection": "Cache & Context",
"analyticsSection": "Analytics",
"costsSection": "Costs",
"monitoringSection": "Monitoring",
"auditSecuritySection": "Audit & Security",
"devtoolsSection": "Dev Tools",
"configurationSection": "Configuration",
"aiFeaturesSection": "AI Features",
"mcp": "MCP",
"a2a": "A2A",
"apiEndpoints": "API Endpoints",
"batchFiles": "Files",
"analyticsEvals": "Evals",
"analyticsSearch": "Search",
"analyticsUtilization": "Utilization",
"analyticsComboHealth": "Combo Health",
"analyticsCompression": "Compression",
"costsBudget": "Budget",
"costsPricing": "Pricing",
"logsProxy": "Proxy Logs",
"logsConsole": "Console",
"logsActivity": "Activity",
"auditMcp": "MCP Audit",
"settingsGeneral": "General",
"settingsAppearance": "Appearance",
"settingsAi": "AI Settings",
"settingsSecurity": "Security",
"settingsRouting": "Routing",
"settingsResilience": "Resilience",
"settingsAdvanced": "Advanced",
"omniProxySection": "OmniProxy",
"quotaTracker": "Quota Tracker",
"consoleLogs": "Console Logs",
"globalRouting": "Global Routing",
"mitmProxy": "MITM Proxy",
"oneProxy": "1Proxy",
"agenticFeaturesSection": "Agentic Features",
"otherFeaturesSection": "Other Features",
"compressionContextGroup": "Compression Context",
"toolsGroup": "Tools",
"integrationsGroup": "Integrations",
"proxyGroup": "Proxy",
"costsParametersGroup": "Costs Parameters",
"auditGroup": "Audit",
"batchGroup": "Batch"
},
"webhooks": {
"title": "__MISSING__:Webhooks",
@@ -879,7 +927,51 @@
"media": "Media",
"mediaDescription": "Generate images, videos, and music",
"themes": "Themes",
"themesDescription": "Choose a color theme for the whole dashboard panel"
"themesDescription": "Choose a color theme for the whole dashboard panel",
"costsDescription": "Track spending, analyze trends, and manage your AI budget across all providers",
"cacheDescription": "Monitor provider prompt cache efficiency and local semantic response reuse.",
"limitsDescription": "Configure rate limits and quotas per API key and provider",
"apiManagerDescription": "Manage API keys and access control for your OmniRoute instance",
"batchDescription": "Process large volumes of requests asynchronously with batched API calls",
"contextCavemanDescription": "Rule-based message compression, language packs, analytics and output mode controls.",
"contextRtkDescription": "Command-aware compression for tool output, terminal logs and build results.",
"contextCombosDescription": "Define how engines are combined for different routing scenarios.",
"changelogDescription": "Stay up to date with the latest platform features and announcements.",
"agentsDescription": "Manage and configure AI agent tools: Codex, Devin, Jules, and custom agents",
"cloudAgentsDescription": "Orchestrate cloud-based AI agents with live task tracking and plan approval",
"memoryDescription": "Persistent conversational memory with semantic search and FTS5 full-text indexing",
"skillsDescription": "Install and manage sandbox skills for automated prompt and tool execution",
"agentSkillsDescription": "Agent-ready skills catalog with one-click URL copy for AI client integration",
"translatorDescription": "Translate and test prompts across API formats: OpenAI ↔ Claude ↔ Gemini",
"playgroundDescription": "Test prompts interactively with live provider responses and format inspection",
"searchToolsDescription": "Search analytics, provider breakdown, cache hit rates, and cost tracking",
"logsDescription": "Real-time request logs, error traces, and streaming event inspector",
"auditDescription": "Compliance audit trail of API key usage, MCP tool calls, and policy events",
"webhooksDescription": "Configure webhook endpoints to receive real-time event notifications",
"healthDescription": "System health overview: providers, circuit breakers, rate limits, and database",
"proxyDescription": "Configure upstream proxy settings for outbound provider connections",
"apiEndpointsDescription": "Manage custom API endpoint configurations and routing overrides",
"batchFilesDescription": "Browse and manage batch job output files and results",
"analyticsEvalsDescription": "Model evaluation results and performance benchmarks",
"analyticsSearchDescription": "Search query analytics, cache hit rates, and cost tracking",
"analyticsUtilizationDescription": "Provider utilization metrics and capacity planning",
"analyticsComboHealthDescription": "Real-time health and performance of combo routing configurations",
"analyticsCompressionDescription": "Context compression analytics and token savings",
"costsBudgetDescription": "Budget limits and spending alerts per API key and provider",
"costsPricingDescription": "Custom pricing configuration for token cost calculations",
"logsProxyDescription": "Upstream proxy request logs and traffic inspection",
"logsConsoleDescription": "Application console output and debug logs",
"logsActivityDescription": "Audit trail of user actions and system events",
"auditMcpDescription": "MCP tool invocation audit trail and compliance records",
"settingsGeneralDescription": "Storage, database, and general instance configuration",
"settingsAppearanceDescription": "Theme, branding, and visual customization",
"settingsAiDescription": "AI behaviors, thinking budgets, vision, and memory settings",
"settingsSecurityDescription": "Authentication, authorization, and access control settings",
"settingsRoutingDescription": "Routing rules, model aliases, combo defaults, and degradation settings",
"settingsResilienceDescription": "Circuit breaker, retry, and fallback configuration",
"settingsAdvancedDescription": "Advanced payload rules, request limits, and proxy API settings",
"mitmProxyDescription": "Configure MITM proxy settings for traffic inspection and debugging",
"oneProxyDescription": "Configure 1Proxy settings for advanced proxy chaining"
},
"home": {
"quickStart": "त्वरित शुरुआत",

View File

@@ -743,7 +743,55 @@
"contextSection": "Context & Cache",
"contextCaveman": "Caveman",
"contextRtk": "RTK",
"contextCombos": "Compression Combos"
"contextCombos": "Compression Combos",
"routingSection": "Routing",
"protocolsSection": "Protocols",
"agentsAiSection": "Agents & AI",
"cacheContextSection": "Cache & Context",
"analyticsSection": "Analytics",
"costsSection": "Costs",
"monitoringSection": "Monitoring",
"auditSecuritySection": "Audit & Security",
"devtoolsSection": "Dev Tools",
"configurationSection": "Configuration",
"aiFeaturesSection": "AI Features",
"mcp": "MCP",
"a2a": "A2A",
"apiEndpoints": "API Endpoints",
"batchFiles": "Files",
"analyticsEvals": "Evals",
"analyticsSearch": "Search",
"analyticsUtilization": "Utilization",
"analyticsComboHealth": "Combo Health",
"analyticsCompression": "Compression",
"costsBudget": "Budget",
"costsPricing": "Pricing",
"logsProxy": "Proxy Logs",
"logsConsole": "Console",
"logsActivity": "Activity",
"auditMcp": "MCP Audit",
"settingsGeneral": "General",
"settingsAppearance": "Appearance",
"settingsAi": "AI Settings",
"settingsSecurity": "Security",
"settingsRouting": "Routing",
"settingsResilience": "Resilience",
"settingsAdvanced": "Advanced",
"omniProxySection": "OmniProxy",
"quotaTracker": "Quota Tracker",
"consoleLogs": "Console Logs",
"globalRouting": "Global Routing",
"mitmProxy": "MITM Proxy",
"oneProxy": "1Proxy",
"agenticFeaturesSection": "Agentic Features",
"otherFeaturesSection": "Other Features",
"compressionContextGroup": "Compression Context",
"toolsGroup": "Tools",
"integrationsGroup": "Integrations",
"proxyGroup": "Proxy",
"costsParametersGroup": "Costs Parameters",
"auditGroup": "Audit",
"batchGroup": "Batch"
},
"webhooks": {
"title": "__MISSING__:Webhooks",
@@ -879,7 +927,51 @@
"media": "Média",
"mediaDescription": "Készítsen képeket, videókat és zenét",
"themes": "Témák",
"themesDescription": "Válasszon színtémát az egész irányítópult panelhez"
"themesDescription": "Válasszon színtémát az egész irányítópult panelhez",
"costsDescription": "Track spending, analyze trends, and manage your AI budget across all providers",
"cacheDescription": "Monitor provider prompt cache efficiency and local semantic response reuse.",
"limitsDescription": "Configure rate limits and quotas per API key and provider",
"apiManagerDescription": "Manage API keys and access control for your OmniRoute instance",
"batchDescription": "Process large volumes of requests asynchronously with batched API calls",
"contextCavemanDescription": "Rule-based message compression, language packs, analytics and output mode controls.",
"contextRtkDescription": "Command-aware compression for tool output, terminal logs and build results.",
"contextCombosDescription": "Define how engines are combined for different routing scenarios.",
"changelogDescription": "Stay up to date with the latest platform features and announcements.",
"agentsDescription": "Manage and configure AI agent tools: Codex, Devin, Jules, and custom agents",
"cloudAgentsDescription": "Orchestrate cloud-based AI agents with live task tracking and plan approval",
"memoryDescription": "Persistent conversational memory with semantic search and FTS5 full-text indexing",
"skillsDescription": "Install and manage sandbox skills for automated prompt and tool execution",
"agentSkillsDescription": "Agent-ready skills catalog with one-click URL copy for AI client integration",
"translatorDescription": "Translate and test prompts across API formats: OpenAI ↔ Claude ↔ Gemini",
"playgroundDescription": "Test prompts interactively with live provider responses and format inspection",
"searchToolsDescription": "Search analytics, provider breakdown, cache hit rates, and cost tracking",
"logsDescription": "Real-time request logs, error traces, and streaming event inspector",
"auditDescription": "Compliance audit trail of API key usage, MCP tool calls, and policy events",
"webhooksDescription": "Configure webhook endpoints to receive real-time event notifications",
"healthDescription": "System health overview: providers, circuit breakers, rate limits, and database",
"proxyDescription": "Configure upstream proxy settings for outbound provider connections",
"apiEndpointsDescription": "Manage custom API endpoint configurations and routing overrides",
"batchFilesDescription": "Browse and manage batch job output files and results",
"analyticsEvalsDescription": "Model evaluation results and performance benchmarks",
"analyticsSearchDescription": "Search query analytics, cache hit rates, and cost tracking",
"analyticsUtilizationDescription": "Provider utilization metrics and capacity planning",
"analyticsComboHealthDescription": "Real-time health and performance of combo routing configurations",
"analyticsCompressionDescription": "Context compression analytics and token savings",
"costsBudgetDescription": "Budget limits and spending alerts per API key and provider",
"costsPricingDescription": "Custom pricing configuration for token cost calculations",
"logsProxyDescription": "Upstream proxy request logs and traffic inspection",
"logsConsoleDescription": "Application console output and debug logs",
"logsActivityDescription": "Audit trail of user actions and system events",
"auditMcpDescription": "MCP tool invocation audit trail and compliance records",
"settingsGeneralDescription": "Storage, database, and general instance configuration",
"settingsAppearanceDescription": "Theme, branding, and visual customization",
"settingsAiDescription": "AI behaviors, thinking budgets, vision, and memory settings",
"settingsSecurityDescription": "Authentication, authorization, and access control settings",
"settingsRoutingDescription": "Routing rules, model aliases, combo defaults, and degradation settings",
"settingsResilienceDescription": "Circuit breaker, retry, and fallback configuration",
"settingsAdvancedDescription": "Advanced payload rules, request limits, and proxy API settings",
"mitmProxyDescription": "Configure MITM proxy settings for traffic inspection and debugging",
"oneProxyDescription": "Configure 1Proxy settings for advanced proxy chaining"
},
"home": {
"quickStart": "Gyors kezdés",

View File

@@ -743,7 +743,55 @@
"contextSection": "Context & Cache",
"contextCaveman": "Caveman",
"contextRtk": "RTK",
"contextCombos": "Compression Combos"
"contextCombos": "Compression Combos",
"routingSection": "Routing",
"protocolsSection": "Protocols",
"agentsAiSection": "Agents & AI",
"cacheContextSection": "Cache & Context",
"analyticsSection": "Analytics",
"costsSection": "Costs",
"monitoringSection": "Monitoring",
"auditSecuritySection": "Audit & Security",
"devtoolsSection": "Dev Tools",
"configurationSection": "Configuration",
"aiFeaturesSection": "AI Features",
"mcp": "MCP",
"a2a": "A2A",
"apiEndpoints": "API Endpoints",
"batchFiles": "Files",
"analyticsEvals": "Evals",
"analyticsSearch": "Search",
"analyticsUtilization": "Utilization",
"analyticsComboHealth": "Combo Health",
"analyticsCompression": "Compression",
"costsBudget": "Budget",
"costsPricing": "Pricing",
"logsProxy": "Proxy Logs",
"logsConsole": "Console",
"logsActivity": "Activity",
"auditMcp": "MCP Audit",
"settingsGeneral": "General",
"settingsAppearance": "Appearance",
"settingsAi": "AI Settings",
"settingsSecurity": "Security",
"settingsRouting": "Routing",
"settingsResilience": "Resilience",
"settingsAdvanced": "Advanced",
"omniProxySection": "OmniProxy",
"quotaTracker": "Quota Tracker",
"consoleLogs": "Console Logs",
"globalRouting": "Global Routing",
"mitmProxy": "MITM Proxy",
"oneProxy": "1Proxy",
"agenticFeaturesSection": "Agentic Features",
"otherFeaturesSection": "Other Features",
"compressionContextGroup": "Compression Context",
"toolsGroup": "Tools",
"integrationsGroup": "Integrations",
"proxyGroup": "Proxy",
"costsParametersGroup": "Costs Parameters",
"auditGroup": "Audit",
"batchGroup": "Batch"
},
"webhooks": {
"title": "__MISSING__:Webhooks",
@@ -879,7 +927,51 @@
"media": "Media",
"mediaDescription": "Generate images, videos, and music",
"themes": "Themes",
"themesDescription": "Choose a color theme for the whole dashboard panel"
"themesDescription": "Choose a color theme for the whole dashboard panel",
"costsDescription": "Track spending, analyze trends, and manage your AI budget across all providers",
"cacheDescription": "Monitor provider prompt cache efficiency and local semantic response reuse.",
"limitsDescription": "Configure rate limits and quotas per API key and provider",
"apiManagerDescription": "Manage API keys and access control for your OmniRoute instance",
"batchDescription": "Process large volumes of requests asynchronously with batched API calls",
"contextCavemanDescription": "Rule-based message compression, language packs, analytics and output mode controls.",
"contextRtkDescription": "Command-aware compression for tool output, terminal logs and build results.",
"contextCombosDescription": "Define how engines are combined for different routing scenarios.",
"changelogDescription": "Stay up to date with the latest platform features and announcements.",
"agentsDescription": "Manage and configure AI agent tools: Codex, Devin, Jules, and custom agents",
"cloudAgentsDescription": "Orchestrate cloud-based AI agents with live task tracking and plan approval",
"memoryDescription": "Persistent conversational memory with semantic search and FTS5 full-text indexing",
"skillsDescription": "Install and manage sandbox skills for automated prompt and tool execution",
"agentSkillsDescription": "Agent-ready skills catalog with one-click URL copy for AI client integration",
"translatorDescription": "Translate and test prompts across API formats: OpenAI ↔ Claude ↔ Gemini",
"playgroundDescription": "Test prompts interactively with live provider responses and format inspection",
"searchToolsDescription": "Search analytics, provider breakdown, cache hit rates, and cost tracking",
"logsDescription": "Real-time request logs, error traces, and streaming event inspector",
"auditDescription": "Compliance audit trail of API key usage, MCP tool calls, and policy events",
"webhooksDescription": "Configure webhook endpoints to receive real-time event notifications",
"healthDescription": "System health overview: providers, circuit breakers, rate limits, and database",
"proxyDescription": "Configure upstream proxy settings for outbound provider connections",
"apiEndpointsDescription": "Manage custom API endpoint configurations and routing overrides",
"batchFilesDescription": "Browse and manage batch job output files and results",
"analyticsEvalsDescription": "Model evaluation results and performance benchmarks",
"analyticsSearchDescription": "Search query analytics, cache hit rates, and cost tracking",
"analyticsUtilizationDescription": "Provider utilization metrics and capacity planning",
"analyticsComboHealthDescription": "Real-time health and performance of combo routing configurations",
"analyticsCompressionDescription": "Context compression analytics and token savings",
"costsBudgetDescription": "Budget limits and spending alerts per API key and provider",
"costsPricingDescription": "Custom pricing configuration for token cost calculations",
"logsProxyDescription": "Upstream proxy request logs and traffic inspection",
"logsConsoleDescription": "Application console output and debug logs",
"logsActivityDescription": "Audit trail of user actions and system events",
"auditMcpDescription": "MCP tool invocation audit trail and compliance records",
"settingsGeneralDescription": "Storage, database, and general instance configuration",
"settingsAppearanceDescription": "Theme, branding, and visual customization",
"settingsAiDescription": "AI behaviors, thinking budgets, vision, and memory settings",
"settingsSecurityDescription": "Authentication, authorization, and access control settings",
"settingsRoutingDescription": "Routing rules, model aliases, combo defaults, and degradation settings",
"settingsResilienceDescription": "Circuit breaker, retry, and fallback configuration",
"settingsAdvancedDescription": "Advanced payload rules, request limits, and proxy API settings",
"mitmProxyDescription": "Configure MITM proxy settings for traffic inspection and debugging",
"oneProxyDescription": "Configure 1Proxy settings for advanced proxy chaining"
},
"home": {
"quickStart": "Mulai Cepat",

View File

@@ -675,7 +675,7 @@
"docs": "Docs",
"issues": "Issues",
"endpoints": "Endpoints",
"apiManager": "API Manager",
"apiManager": "API Key Manager",
"logs": "Logs",
"webhooks": "__MISSING__:Webhooks",
"auditLog": "Audit Log",
@@ -743,7 +743,55 @@
"contextSection": "Context & Cache",
"contextCaveman": "Caveman",
"contextRtk": "RTK",
"contextCombos": "Compression Combos"
"contextCombos": "Compression Combos",
"routingSection": "Routing",
"protocolsSection": "Protocols",
"agentsAiSection": "Agents & AI",
"cacheContextSection": "Cache & Context",
"analyticsSection": "Analytics",
"costsSection": "Costs",
"monitoringSection": "Monitoring",
"auditSecuritySection": "Audit & Security",
"devtoolsSection": "Dev Tools",
"configurationSection": "Configuration",
"aiFeaturesSection": "AI Features",
"mcp": "MCP",
"a2a": "A2A",
"apiEndpoints": "API Endpoints",
"batchFiles": "Files",
"analyticsEvals": "Evals",
"analyticsSearch": "Search",
"analyticsUtilization": "Utilization",
"analyticsComboHealth": "Combo Health",
"analyticsCompression": "Compression",
"costsBudget": "Budget",
"costsPricing": "Pricing",
"logsProxy": "Proxy Logs",
"logsConsole": "Console",
"logsActivity": "Activity",
"auditMcp": "MCP Audit",
"settingsGeneral": "General",
"settingsAppearance": "Appearance",
"settingsAi": "AI Settings",
"settingsSecurity": "Security",
"settingsRouting": "Routing",
"settingsResilience": "Resilience",
"settingsAdvanced": "Advanced",
"omniProxySection": "OmniProxy",
"quotaTracker": "Quota Tracker",
"consoleLogs": "Console Logs",
"globalRouting": "Global Routing",
"mitmProxy": "MITM Proxy",
"oneProxy": "1Proxy",
"agenticFeaturesSection": "Agentic Features",
"otherFeaturesSection": "Other Features",
"compressionContextGroup": "Compression Context",
"toolsGroup": "Tools",
"integrationsGroup": "Integrations",
"proxyGroup": "Proxy",
"costsParametersGroup": "Costs Parameters",
"auditGroup": "Audit",
"batchGroup": "Batch"
},
"webhooks": {
"title": "__MISSING__:Webhooks",
@@ -879,7 +927,51 @@
"media": "Media",
"mediaDescription": "Generate images, videos, and music",
"themes": "Themes",
"themesDescription": "Choose a color theme for the whole dashboard panel"
"themesDescription": "Choose a color theme for the whole dashboard panel",
"costsDescription": "Track spending, analyze trends, and manage your AI budget across all providers",
"cacheDescription": "Monitor provider prompt cache efficiency and local semantic response reuse.",
"limitsDescription": "Configure rate limits and quotas per API key and provider",
"apiManagerDescription": "Manage API keys and access control for your OmniRoute instance",
"batchDescription": "Process large volumes of requests asynchronously with batched API calls",
"contextCavemanDescription": "Rule-based message compression, language packs, analytics and output mode controls.",
"contextRtkDescription": "Command-aware compression for tool output, terminal logs and build results.",
"contextCombosDescription": "Define how engines are combined for different routing scenarios.",
"changelogDescription": "Stay up to date with the latest platform features and announcements.",
"agentsDescription": "Manage and configure AI agent tools: Codex, Devin, Jules, and custom agents",
"cloudAgentsDescription": "Orchestrate cloud-based AI agents with live task tracking and plan approval",
"memoryDescription": "Persistent conversational memory with semantic search and FTS5 full-text indexing",
"skillsDescription": "Install and manage sandbox skills for automated prompt and tool execution",
"agentSkillsDescription": "Agent-ready skills catalog with one-click URL copy for AI client integration",
"translatorDescription": "Translate and test prompts across API formats: OpenAI ↔ Claude ↔ Gemini",
"playgroundDescription": "Test prompts interactively with live provider responses and format inspection",
"searchToolsDescription": "Search analytics, provider breakdown, cache hit rates, and cost tracking",
"logsDescription": "Real-time request logs, error traces, and streaming event inspector",
"auditDescription": "Compliance audit trail of API key usage, MCP tool calls, and policy events",
"webhooksDescription": "Configure webhook endpoints to receive real-time event notifications",
"healthDescription": "System health overview: providers, circuit breakers, rate limits, and database",
"proxyDescription": "Configure upstream proxy settings for outbound provider connections",
"apiEndpointsDescription": "Manage custom API endpoint configurations and routing overrides",
"batchFilesDescription": "Browse and manage batch job output files and results",
"analyticsEvalsDescription": "Model evaluation results and performance benchmarks",
"analyticsSearchDescription": "Search query analytics, cache hit rates, and cost tracking",
"analyticsUtilizationDescription": "Provider utilization metrics and capacity planning",
"analyticsComboHealthDescription": "Real-time health and performance of combo routing configurations",
"analyticsCompressionDescription": "Context compression analytics and token savings",
"costsBudgetDescription": "Budget limits and spending alerts per API key and provider",
"costsPricingDescription": "Custom pricing configuration for token cost calculations",
"logsProxyDescription": "Upstream proxy request logs and traffic inspection",
"logsConsoleDescription": "Application console output and debug logs",
"logsActivityDescription": "Audit trail of user actions and system events",
"auditMcpDescription": "MCP tool invocation audit trail and compliance records",
"settingsGeneralDescription": "Storage, database, and general instance configuration",
"settingsAppearanceDescription": "Theme, branding, and visual customization",
"settingsAiDescription": "AI behaviors, thinking budgets, vision, and memory settings",
"settingsSecurityDescription": "Authentication, authorization, and access control settings",
"settingsRoutingDescription": "Routing rules, model aliases, combo defaults, and degradation settings",
"settingsResilienceDescription": "Circuit breaker, retry, and fallback configuration",
"settingsAdvancedDescription": "Advanced payload rules, request limits, and proxy API settings",
"mitmProxyDescription": "Configure MITM proxy settings for traffic inspection and debugging",
"oneProxyDescription": "Configure 1Proxy settings for advanced proxy chaining"
},
"home": {
"quickStart": "Quick Start",

View File

@@ -743,7 +743,55 @@
"contextSection": "Context & Cache",
"contextCaveman": "Caveman",
"contextRtk": "RTK",
"contextCombos": "Compression Combos"
"contextCombos": "Compression Combos",
"routingSection": "Routing",
"protocolsSection": "Protocols",
"agentsAiSection": "Agents & AI",
"cacheContextSection": "Cache & Context",
"analyticsSection": "Analytics",
"costsSection": "Costs",
"monitoringSection": "Monitoring",
"auditSecuritySection": "Audit & Security",
"devtoolsSection": "Dev Tools",
"configurationSection": "Configuration",
"aiFeaturesSection": "AI Features",
"mcp": "MCP",
"a2a": "A2A",
"apiEndpoints": "API Endpoints",
"batchFiles": "Files",
"analyticsEvals": "Evals",
"analyticsSearch": "Search",
"analyticsUtilization": "Utilization",
"analyticsComboHealth": "Combo Health",
"analyticsCompression": "Compression",
"costsBudget": "Budget",
"costsPricing": "Pricing",
"logsProxy": "Proxy Logs",
"logsConsole": "Console",
"logsActivity": "Activity",
"auditMcp": "MCP Audit",
"settingsGeneral": "General",
"settingsAppearance": "Appearance",
"settingsAi": "AI Settings",
"settingsSecurity": "Security",
"settingsRouting": "Routing",
"settingsResilience": "Resilience",
"settingsAdvanced": "Advanced",
"omniProxySection": "OmniProxy",
"quotaTracker": "Quota Tracker",
"consoleLogs": "Console Logs",
"globalRouting": "Global Routing",
"mitmProxy": "MITM Proxy",
"oneProxy": "1Proxy",
"agenticFeaturesSection": "Agentic Features",
"otherFeaturesSection": "Other Features",
"compressionContextGroup": "Compression Context",
"toolsGroup": "Tools",
"integrationsGroup": "Integrations",
"proxyGroup": "Proxy",
"costsParametersGroup": "Costs Parameters",
"auditGroup": "Audit",
"batchGroup": "Batch"
},
"webhooks": {
"title": "__MISSING__:Webhooks",
@@ -879,7 +927,51 @@
"media": "Media",
"mediaDescription": "Generate images, videos, and music",
"themes": "Themes",
"themesDescription": "Choose a color theme for the whole dashboard panel"
"themesDescription": "Choose a color theme for the whole dashboard panel",
"costsDescription": "Track spending, analyze trends, and manage your AI budget across all providers",
"cacheDescription": "Monitor provider prompt cache efficiency and local semantic response reuse.",
"limitsDescription": "Configure rate limits and quotas per API key and provider",
"apiManagerDescription": "Manage API keys and access control for your OmniRoute instance",
"batchDescription": "Process large volumes of requests asynchronously with batched API calls",
"contextCavemanDescription": "Rule-based message compression, language packs, analytics and output mode controls.",
"contextRtkDescription": "Command-aware compression for tool output, terminal logs and build results.",
"contextCombosDescription": "Define how engines are combined for different routing scenarios.",
"changelogDescription": "Stay up to date with the latest platform features and announcements.",
"agentsDescription": "Manage and configure AI agent tools: Codex, Devin, Jules, and custom agents",
"cloudAgentsDescription": "Orchestrate cloud-based AI agents with live task tracking and plan approval",
"memoryDescription": "Persistent conversational memory with semantic search and FTS5 full-text indexing",
"skillsDescription": "Install and manage sandbox skills for automated prompt and tool execution",
"agentSkillsDescription": "Agent-ready skills catalog with one-click URL copy for AI client integration",
"translatorDescription": "Translate and test prompts across API formats: OpenAI ↔ Claude ↔ Gemini",
"playgroundDescription": "Test prompts interactively with live provider responses and format inspection",
"searchToolsDescription": "Search analytics, provider breakdown, cache hit rates, and cost tracking",
"logsDescription": "Real-time request logs, error traces, and streaming event inspector",
"auditDescription": "Compliance audit trail of API key usage, MCP tool calls, and policy events",
"webhooksDescription": "Configure webhook endpoints to receive real-time event notifications",
"healthDescription": "System health overview: providers, circuit breakers, rate limits, and database",
"proxyDescription": "Configure upstream proxy settings for outbound provider connections",
"apiEndpointsDescription": "Manage custom API endpoint configurations and routing overrides",
"batchFilesDescription": "Browse and manage batch job output files and results",
"analyticsEvalsDescription": "Model evaluation results and performance benchmarks",
"analyticsSearchDescription": "Search query analytics, cache hit rates, and cost tracking",
"analyticsUtilizationDescription": "Provider utilization metrics and capacity planning",
"analyticsComboHealthDescription": "Real-time health and performance of combo routing configurations",
"analyticsCompressionDescription": "Context compression analytics and token savings",
"costsBudgetDescription": "Budget limits and spending alerts per API key and provider",
"costsPricingDescription": "Custom pricing configuration for token cost calculations",
"logsProxyDescription": "Upstream proxy request logs and traffic inspection",
"logsConsoleDescription": "Application console output and debug logs",
"logsActivityDescription": "Audit trail of user actions and system events",
"auditMcpDescription": "MCP tool invocation audit trail and compliance records",
"settingsGeneralDescription": "Storage, database, and general instance configuration",
"settingsAppearanceDescription": "Theme, branding, and visual customization",
"settingsAiDescription": "AI behaviors, thinking budgets, vision, and memory settings",
"settingsSecurityDescription": "Authentication, authorization, and access control settings",
"settingsRoutingDescription": "Routing rules, model aliases, combo defaults, and degradation settings",
"settingsResilienceDescription": "Circuit breaker, retry, and fallback configuration",
"settingsAdvancedDescription": "Advanced payload rules, request limits, and proxy API settings",
"mitmProxyDescription": "Configure MITM proxy settings for traffic inspection and debugging",
"oneProxyDescription": "Configure 1Proxy settings for advanced proxy chaining"
},
"home": {
"quickStart": "Avvio rapido",

View File

@@ -743,7 +743,55 @@
"contextSection": "Context & Cache",
"contextCaveman": "Caveman",
"contextRtk": "RTK",
"contextCombos": "Compression Combos"
"contextCombos": "Compression Combos",
"routingSection": "Routing",
"protocolsSection": "Protocols",
"agentsAiSection": "Agents & AI",
"cacheContextSection": "Cache & Context",
"analyticsSection": "Analytics",
"costsSection": "Costs",
"monitoringSection": "Monitoring",
"auditSecuritySection": "Audit & Security",
"devtoolsSection": "Dev Tools",
"configurationSection": "Configuration",
"aiFeaturesSection": "AI Features",
"mcp": "MCP",
"a2a": "A2A",
"apiEndpoints": "API Endpoints",
"batchFiles": "Files",
"analyticsEvals": "Evals",
"analyticsSearch": "Search",
"analyticsUtilization": "Utilization",
"analyticsComboHealth": "Combo Health",
"analyticsCompression": "Compression",
"costsBudget": "Budget",
"costsPricing": "Pricing",
"logsProxy": "Proxy Logs",
"logsConsole": "Console",
"logsActivity": "Activity",
"auditMcp": "MCP Audit",
"settingsGeneral": "General",
"settingsAppearance": "Appearance",
"settingsAi": "AI Settings",
"settingsSecurity": "Security",
"settingsRouting": "Routing",
"settingsResilience": "Resilience",
"settingsAdvanced": "Advanced",
"omniProxySection": "OmniProxy",
"quotaTracker": "Quota Tracker",
"consoleLogs": "Console Logs",
"globalRouting": "Global Routing",
"mitmProxy": "MITM Proxy",
"oneProxy": "1Proxy",
"agenticFeaturesSection": "Agentic Features",
"otherFeaturesSection": "Other Features",
"compressionContextGroup": "Compression Context",
"toolsGroup": "Tools",
"integrationsGroup": "Integrations",
"proxyGroup": "Proxy",
"costsParametersGroup": "Costs Parameters",
"auditGroup": "Audit",
"batchGroup": "Batch"
},
"webhooks": {
"title": "__MISSING__:Webhooks",
@@ -879,7 +927,51 @@
"media": "Media",
"mediaDescription": "Generate images, videos, and music",
"themes": "Themes",
"themesDescription": "Choose a color theme for the whole dashboard panel"
"themesDescription": "Choose a color theme for the whole dashboard panel",
"costsDescription": "Track spending, analyze trends, and manage your AI budget across all providers",
"cacheDescription": "Monitor provider prompt cache efficiency and local semantic response reuse.",
"limitsDescription": "Configure rate limits and quotas per API key and provider",
"apiManagerDescription": "Manage API keys and access control for your OmniRoute instance",
"batchDescription": "Process large volumes of requests asynchronously with batched API calls",
"contextCavemanDescription": "Rule-based message compression, language packs, analytics and output mode controls.",
"contextRtkDescription": "Command-aware compression for tool output, terminal logs and build results.",
"contextCombosDescription": "Define how engines are combined for different routing scenarios.",
"changelogDescription": "Stay up to date with the latest platform features and announcements.",
"agentsDescription": "Manage and configure AI agent tools: Codex, Devin, Jules, and custom agents",
"cloudAgentsDescription": "Orchestrate cloud-based AI agents with live task tracking and plan approval",
"memoryDescription": "Persistent conversational memory with semantic search and FTS5 full-text indexing",
"skillsDescription": "Install and manage sandbox skills for automated prompt and tool execution",
"agentSkillsDescription": "Agent-ready skills catalog with one-click URL copy for AI client integration",
"translatorDescription": "Translate and test prompts across API formats: OpenAI ↔ Claude ↔ Gemini",
"playgroundDescription": "Test prompts interactively with live provider responses and format inspection",
"searchToolsDescription": "Search analytics, provider breakdown, cache hit rates, and cost tracking",
"logsDescription": "Real-time request logs, error traces, and streaming event inspector",
"auditDescription": "Compliance audit trail of API key usage, MCP tool calls, and policy events",
"webhooksDescription": "Configure webhook endpoints to receive real-time event notifications",
"healthDescription": "System health overview: providers, circuit breakers, rate limits, and database",
"proxyDescription": "Configure upstream proxy settings for outbound provider connections",
"apiEndpointsDescription": "Manage custom API endpoint configurations and routing overrides",
"batchFilesDescription": "Browse and manage batch job output files and results",
"analyticsEvalsDescription": "Model evaluation results and performance benchmarks",
"analyticsSearchDescription": "Search query analytics, cache hit rates, and cost tracking",
"analyticsUtilizationDescription": "Provider utilization metrics and capacity planning",
"analyticsComboHealthDescription": "Real-time health and performance of combo routing configurations",
"analyticsCompressionDescription": "Context compression analytics and token savings",
"costsBudgetDescription": "Budget limits and spending alerts per API key and provider",
"costsPricingDescription": "Custom pricing configuration for token cost calculations",
"logsProxyDescription": "Upstream proxy request logs and traffic inspection",
"logsConsoleDescription": "Application console output and debug logs",
"logsActivityDescription": "Audit trail of user actions and system events",
"auditMcpDescription": "MCP tool invocation audit trail and compliance records",
"settingsGeneralDescription": "Storage, database, and general instance configuration",
"settingsAppearanceDescription": "Theme, branding, and visual customization",
"settingsAiDescription": "AI behaviors, thinking budgets, vision, and memory settings",
"settingsSecurityDescription": "Authentication, authorization, and access control settings",
"settingsRoutingDescription": "Routing rules, model aliases, combo defaults, and degradation settings",
"settingsResilienceDescription": "Circuit breaker, retry, and fallback configuration",
"settingsAdvancedDescription": "Advanced payload rules, request limits, and proxy API settings",
"mitmProxyDescription": "Configure MITM proxy settings for traffic inspection and debugging",
"oneProxyDescription": "Configure 1Proxy settings for advanced proxy chaining"
},
"home": {
"quickStart": "クイックスタート",

View File

@@ -743,7 +743,55 @@
"contextSection": "Context & Cache",
"contextCaveman": "Caveman",
"contextRtk": "RTK",
"contextCombos": "Compression Combos"
"contextCombos": "Compression Combos",
"routingSection": "Routing",
"protocolsSection": "Protocols",
"agentsAiSection": "Agents & AI",
"cacheContextSection": "Cache & Context",
"analyticsSection": "Analytics",
"costsSection": "Costs",
"monitoringSection": "Monitoring",
"auditSecuritySection": "Audit & Security",
"devtoolsSection": "Dev Tools",
"configurationSection": "Configuration",
"aiFeaturesSection": "AI Features",
"mcp": "MCP",
"a2a": "A2A",
"apiEndpoints": "API Endpoints",
"batchFiles": "Files",
"analyticsEvals": "Evals",
"analyticsSearch": "Search",
"analyticsUtilization": "Utilization",
"analyticsComboHealth": "Combo Health",
"analyticsCompression": "Compression",
"costsBudget": "Budget",
"costsPricing": "Pricing",
"logsProxy": "Proxy Logs",
"logsConsole": "Console",
"logsActivity": "Activity",
"auditMcp": "MCP Audit",
"settingsGeneral": "General",
"settingsAppearance": "Appearance",
"settingsAi": "AI Settings",
"settingsSecurity": "Security",
"settingsRouting": "Routing",
"settingsResilience": "Resilience",
"settingsAdvanced": "Advanced",
"omniProxySection": "OmniProxy",
"quotaTracker": "Quota Tracker",
"consoleLogs": "Console Logs",
"globalRouting": "Global Routing",
"mitmProxy": "MITM Proxy",
"oneProxy": "1Proxy",
"agenticFeaturesSection": "Agentic Features",
"otherFeaturesSection": "Other Features",
"compressionContextGroup": "Compression Context",
"toolsGroup": "Tools",
"integrationsGroup": "Integrations",
"proxyGroup": "Proxy",
"costsParametersGroup": "Costs Parameters",
"auditGroup": "Audit",
"batchGroup": "Batch"
},
"webhooks": {
"title": "__MISSING__:Webhooks",
@@ -879,7 +927,51 @@
"media": "Media",
"mediaDescription": "Generate images, videos, and music",
"themes": "Themes",
"themesDescription": "Choose a color theme for the whole dashboard panel"
"themesDescription": "Choose a color theme for the whole dashboard panel",
"costsDescription": "Track spending, analyze trends, and manage your AI budget across all providers",
"cacheDescription": "Monitor provider prompt cache efficiency and local semantic response reuse.",
"limitsDescription": "Configure rate limits and quotas per API key and provider",
"apiManagerDescription": "Manage API keys and access control for your OmniRoute instance",
"batchDescription": "Process large volumes of requests asynchronously with batched API calls",
"contextCavemanDescription": "Rule-based message compression, language packs, analytics and output mode controls.",
"contextRtkDescription": "Command-aware compression for tool output, terminal logs and build results.",
"contextCombosDescription": "Define how engines are combined for different routing scenarios.",
"changelogDescription": "Stay up to date with the latest platform features and announcements.",
"agentsDescription": "Manage and configure AI agent tools: Codex, Devin, Jules, and custom agents",
"cloudAgentsDescription": "Orchestrate cloud-based AI agents with live task tracking and plan approval",
"memoryDescription": "Persistent conversational memory with semantic search and FTS5 full-text indexing",
"skillsDescription": "Install and manage sandbox skills for automated prompt and tool execution",
"agentSkillsDescription": "Agent-ready skills catalog with one-click URL copy for AI client integration",
"translatorDescription": "Translate and test prompts across API formats: OpenAI ↔ Claude ↔ Gemini",
"playgroundDescription": "Test prompts interactively with live provider responses and format inspection",
"searchToolsDescription": "Search analytics, provider breakdown, cache hit rates, and cost tracking",
"logsDescription": "Real-time request logs, error traces, and streaming event inspector",
"auditDescription": "Compliance audit trail of API key usage, MCP tool calls, and policy events",
"webhooksDescription": "Configure webhook endpoints to receive real-time event notifications",
"healthDescription": "System health overview: providers, circuit breakers, rate limits, and database",
"proxyDescription": "Configure upstream proxy settings for outbound provider connections",
"apiEndpointsDescription": "Manage custom API endpoint configurations and routing overrides",
"batchFilesDescription": "Browse and manage batch job output files and results",
"analyticsEvalsDescription": "Model evaluation results and performance benchmarks",
"analyticsSearchDescription": "Search query analytics, cache hit rates, and cost tracking",
"analyticsUtilizationDescription": "Provider utilization metrics and capacity planning",
"analyticsComboHealthDescription": "Real-time health and performance of combo routing configurations",
"analyticsCompressionDescription": "Context compression analytics and token savings",
"costsBudgetDescription": "Budget limits and spending alerts per API key and provider",
"costsPricingDescription": "Custom pricing configuration for token cost calculations",
"logsProxyDescription": "Upstream proxy request logs and traffic inspection",
"logsConsoleDescription": "Application console output and debug logs",
"logsActivityDescription": "Audit trail of user actions and system events",
"auditMcpDescription": "MCP tool invocation audit trail and compliance records",
"settingsGeneralDescription": "Storage, database, and general instance configuration",
"settingsAppearanceDescription": "Theme, branding, and visual customization",
"settingsAiDescription": "AI behaviors, thinking budgets, vision, and memory settings",
"settingsSecurityDescription": "Authentication, authorization, and access control settings",
"settingsRoutingDescription": "Routing rules, model aliases, combo defaults, and degradation settings",
"settingsResilienceDescription": "Circuit breaker, retry, and fallback configuration",
"settingsAdvancedDescription": "Advanced payload rules, request limits, and proxy API settings",
"mitmProxyDescription": "Configure MITM proxy settings for traffic inspection and debugging",
"oneProxyDescription": "Configure 1Proxy settings for advanced proxy chaining"
},
"home": {
"quickStart": "빠른 시작",

View File

@@ -675,7 +675,7 @@
"docs": "Docs",
"issues": "Issues",
"endpoints": "Endpoints",
"apiManager": "API Manager",
"apiManager": "API Key Manager",
"logs": "Logs",
"webhooks": "__MISSING__:Webhooks",
"auditLog": "Audit Log",
@@ -743,7 +743,55 @@
"contextSection": "Context & Cache",
"contextCaveman": "Caveman",
"contextRtk": "RTK",
"contextCombos": "Compression Combos"
"contextCombos": "Compression Combos",
"routingSection": "Routing",
"protocolsSection": "Protocols",
"agentsAiSection": "Agents & AI",
"cacheContextSection": "Cache & Context",
"analyticsSection": "Analytics",
"costsSection": "Costs",
"monitoringSection": "Monitoring",
"auditSecuritySection": "Audit & Security",
"devtoolsSection": "Dev Tools",
"configurationSection": "Configuration",
"aiFeaturesSection": "AI Features",
"mcp": "MCP",
"a2a": "A2A",
"apiEndpoints": "API Endpoints",
"batchFiles": "Files",
"analyticsEvals": "Evals",
"analyticsSearch": "Search",
"analyticsUtilization": "Utilization",
"analyticsComboHealth": "Combo Health",
"analyticsCompression": "Compression",
"costsBudget": "Budget",
"costsPricing": "Pricing",
"logsProxy": "Proxy Logs",
"logsConsole": "Console",
"logsActivity": "Activity",
"auditMcp": "MCP Audit",
"settingsGeneral": "General",
"settingsAppearance": "Appearance",
"settingsAi": "AI Settings",
"settingsSecurity": "Security",
"settingsRouting": "Routing",
"settingsResilience": "Resilience",
"settingsAdvanced": "Advanced",
"omniProxySection": "OmniProxy",
"quotaTracker": "Quota Tracker",
"consoleLogs": "Console Logs",
"globalRouting": "Global Routing",
"mitmProxy": "MITM Proxy",
"oneProxy": "1Proxy",
"agenticFeaturesSection": "Agentic Features",
"otherFeaturesSection": "Other Features",
"compressionContextGroup": "Compression Context",
"toolsGroup": "Tools",
"integrationsGroup": "Integrations",
"proxyGroup": "Proxy",
"costsParametersGroup": "Costs Parameters",
"auditGroup": "Audit",
"batchGroup": "Batch"
},
"webhooks": {
"title": "__MISSING__:Webhooks",
@@ -879,7 +927,51 @@
"media": "Media",
"mediaDescription": "Generate images, videos, and music",
"themes": "Themes",
"themesDescription": "Choose a color theme for the whole dashboard panel"
"themesDescription": "Choose a color theme for the whole dashboard panel",
"costsDescription": "Track spending, analyze trends, and manage your AI budget across all providers",
"cacheDescription": "Monitor provider prompt cache efficiency and local semantic response reuse.",
"limitsDescription": "Configure rate limits and quotas per API key and provider",
"apiManagerDescription": "Manage API keys and access control for your OmniRoute instance",
"batchDescription": "Process large volumes of requests asynchronously with batched API calls",
"contextCavemanDescription": "Rule-based message compression, language packs, analytics and output mode controls.",
"contextRtkDescription": "Command-aware compression for tool output, terminal logs and build results.",
"contextCombosDescription": "Define how engines are combined for different routing scenarios.",
"changelogDescription": "Stay up to date with the latest platform features and announcements.",
"agentsDescription": "Manage and configure AI agent tools: Codex, Devin, Jules, and custom agents",
"cloudAgentsDescription": "Orchestrate cloud-based AI agents with live task tracking and plan approval",
"memoryDescription": "Persistent conversational memory with semantic search and FTS5 full-text indexing",
"skillsDescription": "Install and manage sandbox skills for automated prompt and tool execution",
"agentSkillsDescription": "Agent-ready skills catalog with one-click URL copy for AI client integration",
"translatorDescription": "Translate and test prompts across API formats: OpenAI ↔ Claude ↔ Gemini",
"playgroundDescription": "Test prompts interactively with live provider responses and format inspection",
"searchToolsDescription": "Search analytics, provider breakdown, cache hit rates, and cost tracking",
"logsDescription": "Real-time request logs, error traces, and streaming event inspector",
"auditDescription": "Compliance audit trail of API key usage, MCP tool calls, and policy events",
"webhooksDescription": "Configure webhook endpoints to receive real-time event notifications",
"healthDescription": "System health overview: providers, circuit breakers, rate limits, and database",
"proxyDescription": "Configure upstream proxy settings for outbound provider connections",
"apiEndpointsDescription": "Manage custom API endpoint configurations and routing overrides",
"batchFilesDescription": "Browse and manage batch job output files and results",
"analyticsEvalsDescription": "Model evaluation results and performance benchmarks",
"analyticsSearchDescription": "Search query analytics, cache hit rates, and cost tracking",
"analyticsUtilizationDescription": "Provider utilization metrics and capacity planning",
"analyticsComboHealthDescription": "Real-time health and performance of combo routing configurations",
"analyticsCompressionDescription": "Context compression analytics and token savings",
"costsBudgetDescription": "Budget limits and spending alerts per API key and provider",
"costsPricingDescription": "Custom pricing configuration for token cost calculations",
"logsProxyDescription": "Upstream proxy request logs and traffic inspection",
"logsConsoleDescription": "Application console output and debug logs",
"logsActivityDescription": "Audit trail of user actions and system events",
"auditMcpDescription": "MCP tool invocation audit trail and compliance records",
"settingsGeneralDescription": "Storage, database, and general instance configuration",
"settingsAppearanceDescription": "Theme, branding, and visual customization",
"settingsAiDescription": "AI behaviors, thinking budgets, vision, and memory settings",
"settingsSecurityDescription": "Authentication, authorization, and access control settings",
"settingsRoutingDescription": "Routing rules, model aliases, combo defaults, and degradation settings",
"settingsResilienceDescription": "Circuit breaker, retry, and fallback configuration",
"settingsAdvancedDescription": "Advanced payload rules, request limits, and proxy API settings",
"mitmProxyDescription": "Configure MITM proxy settings for traffic inspection and debugging",
"oneProxyDescription": "Configure 1Proxy settings for advanced proxy chaining"
},
"home": {
"quickStart": "Quick Start",

View File

@@ -743,7 +743,55 @@
"contextSection": "Context & Cache",
"contextCaveman": "Caveman",
"contextRtk": "RTK",
"contextCombos": "Compression Combos"
"contextCombos": "Compression Combos",
"routingSection": "Routing",
"protocolsSection": "Protocols",
"agentsAiSection": "Agents & AI",
"cacheContextSection": "Cache & Context",
"analyticsSection": "Analytics",
"costsSection": "Costs",
"monitoringSection": "Monitoring",
"auditSecuritySection": "Audit & Security",
"devtoolsSection": "Dev Tools",
"configurationSection": "Configuration",
"aiFeaturesSection": "AI Features",
"mcp": "MCP",
"a2a": "A2A",
"apiEndpoints": "API Endpoints",
"batchFiles": "Files",
"analyticsEvals": "Evals",
"analyticsSearch": "Search",
"analyticsUtilization": "Utilization",
"analyticsComboHealth": "Combo Health",
"analyticsCompression": "Compression",
"costsBudget": "Budget",
"costsPricing": "Pricing",
"logsProxy": "Proxy Logs",
"logsConsole": "Console",
"logsActivity": "Activity",
"auditMcp": "MCP Audit",
"settingsGeneral": "General",
"settingsAppearance": "Appearance",
"settingsAi": "AI Settings",
"settingsSecurity": "Security",
"settingsRouting": "Routing",
"settingsResilience": "Resilience",
"settingsAdvanced": "Advanced",
"omniProxySection": "OmniProxy",
"quotaTracker": "Quota Tracker",
"consoleLogs": "Console Logs",
"globalRouting": "Global Routing",
"mitmProxy": "MITM Proxy",
"oneProxy": "1Proxy",
"agenticFeaturesSection": "Agentic Features",
"otherFeaturesSection": "Other Features",
"compressionContextGroup": "Compression Context",
"toolsGroup": "Tools",
"integrationsGroup": "Integrations",
"proxyGroup": "Proxy",
"costsParametersGroup": "Costs Parameters",
"auditGroup": "Audit",
"batchGroup": "Batch"
},
"webhooks": {
"title": "__MISSING__:Webhooks",
@@ -879,7 +927,51 @@
"media": "Media",
"mediaDescription": "Generate images, videos, and music",
"themes": "Themes",
"themesDescription": "Choose a color theme for the whole dashboard panel"
"themesDescription": "Choose a color theme for the whole dashboard panel",
"costsDescription": "Track spending, analyze trends, and manage your AI budget across all providers",
"cacheDescription": "Monitor provider prompt cache efficiency and local semantic response reuse.",
"limitsDescription": "Configure rate limits and quotas per API key and provider",
"apiManagerDescription": "Manage API keys and access control for your OmniRoute instance",
"batchDescription": "Process large volumes of requests asynchronously with batched API calls",
"contextCavemanDescription": "Rule-based message compression, language packs, analytics and output mode controls.",
"contextRtkDescription": "Command-aware compression for tool output, terminal logs and build results.",
"contextCombosDescription": "Define how engines are combined for different routing scenarios.",
"changelogDescription": "Stay up to date with the latest platform features and announcements.",
"agentsDescription": "Manage and configure AI agent tools: Codex, Devin, Jules, and custom agents",
"cloudAgentsDescription": "Orchestrate cloud-based AI agents with live task tracking and plan approval",
"memoryDescription": "Persistent conversational memory with semantic search and FTS5 full-text indexing",
"skillsDescription": "Install and manage sandbox skills for automated prompt and tool execution",
"agentSkillsDescription": "Agent-ready skills catalog with one-click URL copy for AI client integration",
"translatorDescription": "Translate and test prompts across API formats: OpenAI ↔ Claude ↔ Gemini",
"playgroundDescription": "Test prompts interactively with live provider responses and format inspection",
"searchToolsDescription": "Search analytics, provider breakdown, cache hit rates, and cost tracking",
"logsDescription": "Real-time request logs, error traces, and streaming event inspector",
"auditDescription": "Compliance audit trail of API key usage, MCP tool calls, and policy events",
"webhooksDescription": "Configure webhook endpoints to receive real-time event notifications",
"healthDescription": "System health overview: providers, circuit breakers, rate limits, and database",
"proxyDescription": "Configure upstream proxy settings for outbound provider connections",
"apiEndpointsDescription": "Manage custom API endpoint configurations and routing overrides",
"batchFilesDescription": "Browse and manage batch job output files and results",
"analyticsEvalsDescription": "Model evaluation results and performance benchmarks",
"analyticsSearchDescription": "Search query analytics, cache hit rates, and cost tracking",
"analyticsUtilizationDescription": "Provider utilization metrics and capacity planning",
"analyticsComboHealthDescription": "Real-time health and performance of combo routing configurations",
"analyticsCompressionDescription": "Context compression analytics and token savings",
"costsBudgetDescription": "Budget limits and spending alerts per API key and provider",
"costsPricingDescription": "Custom pricing configuration for token cost calculations",
"logsProxyDescription": "Upstream proxy request logs and traffic inspection",
"logsConsoleDescription": "Application console output and debug logs",
"logsActivityDescription": "Audit trail of user actions and system events",
"auditMcpDescription": "MCP tool invocation audit trail and compliance records",
"settingsGeneralDescription": "Storage, database, and general instance configuration",
"settingsAppearanceDescription": "Theme, branding, and visual customization",
"settingsAiDescription": "AI behaviors, thinking budgets, vision, and memory settings",
"settingsSecurityDescription": "Authentication, authorization, and access control settings",
"settingsRoutingDescription": "Routing rules, model aliases, combo defaults, and degradation settings",
"settingsResilienceDescription": "Circuit breaker, retry, and fallback configuration",
"settingsAdvancedDescription": "Advanced payload rules, request limits, and proxy API settings",
"mitmProxyDescription": "Configure MITM proxy settings for traffic inspection and debugging",
"oneProxyDescription": "Configure 1Proxy settings for advanced proxy chaining"
},
"home": {
"quickStart": "Mula Pantas",

View File

@@ -743,7 +743,55 @@
"contextSection": "Context & Cache",
"contextCaveman": "Caveman",
"contextRtk": "RTK",
"contextCombos": "Compression Combos"
"contextCombos": "Compression Combos",
"routingSection": "Routing",
"protocolsSection": "Protocols",
"agentsAiSection": "Agents & AI",
"cacheContextSection": "Cache & Context",
"analyticsSection": "Analytics",
"costsSection": "Costs",
"monitoringSection": "Monitoring",
"auditSecuritySection": "Audit & Security",
"devtoolsSection": "Dev Tools",
"configurationSection": "Configuration",
"aiFeaturesSection": "AI Features",
"mcp": "MCP",
"a2a": "A2A",
"apiEndpoints": "API Endpoints",
"batchFiles": "Files",
"analyticsEvals": "Evals",
"analyticsSearch": "Search",
"analyticsUtilization": "Utilization",
"analyticsComboHealth": "Combo Health",
"analyticsCompression": "Compression",
"costsBudget": "Budget",
"costsPricing": "Pricing",
"logsProxy": "Proxy Logs",
"logsConsole": "Console",
"logsActivity": "Activity",
"auditMcp": "MCP Audit",
"settingsGeneral": "General",
"settingsAppearance": "Appearance",
"settingsAi": "AI Settings",
"settingsSecurity": "Security",
"settingsRouting": "Routing",
"settingsResilience": "Resilience",
"settingsAdvanced": "Advanced",
"omniProxySection": "OmniProxy",
"quotaTracker": "Quota Tracker",
"consoleLogs": "Console Logs",
"globalRouting": "Global Routing",
"mitmProxy": "MITM Proxy",
"oneProxy": "1Proxy",
"agenticFeaturesSection": "Agentic Features",
"otherFeaturesSection": "Other Features",
"compressionContextGroup": "Compression Context",
"toolsGroup": "Tools",
"integrationsGroup": "Integrations",
"proxyGroup": "Proxy",
"costsParametersGroup": "Costs Parameters",
"auditGroup": "Audit",
"batchGroup": "Batch"
},
"webhooks": {
"title": "__MISSING__:Webhooks",
@@ -879,7 +927,51 @@
"media": "Media",
"mediaDescription": "Genereer afbeeldingen, video's en muziek",
"themes": "Themes",
"themesDescription": "Choose a color theme for the whole dashboard panel"
"themesDescription": "Choose a color theme for the whole dashboard panel",
"costsDescription": "Track spending, analyze trends, and manage your AI budget across all providers",
"cacheDescription": "Monitor provider prompt cache efficiency and local semantic response reuse.",
"limitsDescription": "Configure rate limits and quotas per API key and provider",
"apiManagerDescription": "Manage API keys and access control for your OmniRoute instance",
"batchDescription": "Process large volumes of requests asynchronously with batched API calls",
"contextCavemanDescription": "Rule-based message compression, language packs, analytics and output mode controls.",
"contextRtkDescription": "Command-aware compression for tool output, terminal logs and build results.",
"contextCombosDescription": "Define how engines are combined for different routing scenarios.",
"changelogDescription": "Stay up to date with the latest platform features and announcements.",
"agentsDescription": "Manage and configure AI agent tools: Codex, Devin, Jules, and custom agents",
"cloudAgentsDescription": "Orchestrate cloud-based AI agents with live task tracking and plan approval",
"memoryDescription": "Persistent conversational memory with semantic search and FTS5 full-text indexing",
"skillsDescription": "Install and manage sandbox skills for automated prompt and tool execution",
"agentSkillsDescription": "Agent-ready skills catalog with one-click URL copy for AI client integration",
"translatorDescription": "Translate and test prompts across API formats: OpenAI ↔ Claude ↔ Gemini",
"playgroundDescription": "Test prompts interactively with live provider responses and format inspection",
"searchToolsDescription": "Search analytics, provider breakdown, cache hit rates, and cost tracking",
"logsDescription": "Real-time request logs, error traces, and streaming event inspector",
"auditDescription": "Compliance audit trail of API key usage, MCP tool calls, and policy events",
"webhooksDescription": "Configure webhook endpoints to receive real-time event notifications",
"healthDescription": "System health overview: providers, circuit breakers, rate limits, and database",
"proxyDescription": "Configure upstream proxy settings for outbound provider connections",
"apiEndpointsDescription": "Manage custom API endpoint configurations and routing overrides",
"batchFilesDescription": "Browse and manage batch job output files and results",
"analyticsEvalsDescription": "Model evaluation results and performance benchmarks",
"analyticsSearchDescription": "Search query analytics, cache hit rates, and cost tracking",
"analyticsUtilizationDescription": "Provider utilization metrics and capacity planning",
"analyticsComboHealthDescription": "Real-time health and performance of combo routing configurations",
"analyticsCompressionDescription": "Context compression analytics and token savings",
"costsBudgetDescription": "Budget limits and spending alerts per API key and provider",
"costsPricingDescription": "Custom pricing configuration for token cost calculations",
"logsProxyDescription": "Upstream proxy request logs and traffic inspection",
"logsConsoleDescription": "Application console output and debug logs",
"logsActivityDescription": "Audit trail of user actions and system events",
"auditMcpDescription": "MCP tool invocation audit trail and compliance records",
"settingsGeneralDescription": "Storage, database, and general instance configuration",
"settingsAppearanceDescription": "Theme, branding, and visual customization",
"settingsAiDescription": "AI behaviors, thinking budgets, vision, and memory settings",
"settingsSecurityDescription": "Authentication, authorization, and access control settings",
"settingsRoutingDescription": "Routing rules, model aliases, combo defaults, and degradation settings",
"settingsResilienceDescription": "Circuit breaker, retry, and fallback configuration",
"settingsAdvancedDescription": "Advanced payload rules, request limits, and proxy API settings",
"mitmProxyDescription": "Configure MITM proxy settings for traffic inspection and debugging",
"oneProxyDescription": "Configure 1Proxy settings for advanced proxy chaining"
},
"home": {
"quickStart": "Snel beginnen",

Some files were not shown because too many files have changed in this diff Show More