mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-09-17 20:32:25 +03:00
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:
@@ -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,
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
67
package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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 */}
|
||||
|
||||
220
src/app/(dashboard)/dashboard/a2a/page.tsx
Normal file
220
src/app/(dashboard)/dashboard/a2a/page.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@@ -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 <pasted-url>
|
||||
</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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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")}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import ComboHealthTab from "../ComboHealthTab";
|
||||
|
||||
export default function AnalyticsComboHealthPage() {
|
||||
return <ComboHealthTab />;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import CompressionAnalyticsTab from "../CompressionAnalyticsTab";
|
||||
|
||||
export default function AnalyticsCompressionPage() {
|
||||
return <CompressionAnalyticsTab />;
|
||||
}
|
||||
7
src/app/(dashboard)/dashboard/analytics/evals/page.tsx
Normal file
7
src/app/(dashboard)/dashboard/analytics/evals/page.tsx
Normal file
@@ -0,0 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import EvalsTab from "../../usage/components/EvalsTab";
|
||||
|
||||
export default function AnalyticsEvalsPage() {
|
||||
return <EvalsTab />;
|
||||
}
|
||||
@@ -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) => (
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
7
src/app/(dashboard)/dashboard/analytics/search/page.tsx
Normal file
7
src/app/(dashboard)/dashboard/analytics/search/page.tsx
Normal file
@@ -0,0 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import SearchAnalyticsTab from "../SearchAnalyticsTab";
|
||||
|
||||
export default function AnalyticsSearchPage() {
|
||||
return <SearchAnalyticsTab />;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import ProviderUtilizationTab from "../ProviderUtilizationTab";
|
||||
|
||||
export default function AnalyticsUtilizationPage() {
|
||||
return <ProviderUtilizationTab />;
|
||||
}
|
||||
7
src/app/(dashboard)/dashboard/api-endpoints/page.tsx
Normal file
7
src/app/(dashboard)/dashboard/api-endpoints/page.tsx
Normal file
@@ -0,0 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import ApiEndpointsTab from "../endpoint/ApiEndpointsTab";
|
||||
|
||||
export default function ApiEndpointsPage() {
|
||||
return <ApiEndpointsTab />;
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
217
src/app/(dashboard)/dashboard/audit/A2aAuditTab.tsx
Normal file
217
src/app/(dashboard)/dashboard/audit/A2aAuditTab.tsx
Normal 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)}…
|
||||
</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>
|
||||
);
|
||||
}
|
||||
289
src/app/(dashboard)/dashboard/audit/McpAuditTab.tsx
Normal file
289
src/app/(dashboard)/dashboard/audit/McpAuditTab.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
7
src/app/(dashboard)/dashboard/audit/a2a/page.tsx
Normal file
7
src/app/(dashboard)/dashboard/audit/a2a/page.tsx
Normal file
@@ -0,0 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import A2aAuditTab from "../A2aAuditTab";
|
||||
|
||||
export default function AuditA2aPage() {
|
||||
return <A2aAuditTab />;
|
||||
}
|
||||
7
src/app/(dashboard)/dashboard/audit/mcp/page.tsx
Normal file
7
src/app/(dashboard)/dashboard/audit/mcp/page.tsx
Normal file
@@ -0,0 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import McpAuditTab from "../McpAuditTab";
|
||||
|
||||
export default function AuditMcpPage() {
|
||||
return <McpAuditTab />;
|
||||
}
|
||||
@@ -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 />;
|
||||
}
|
||||
|
||||
43
src/app/(dashboard)/dashboard/batch/files/page.tsx
Normal file
43
src/app/(dashboard)/dashboard/batch/files/page.tsx
Normal 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} />
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
6
src/app/(dashboard)/dashboard/cache/page.tsx
vendored
6
src/app/(dashboard)/dashboard/cache/page.tsx
vendored
@@ -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"
|
||||
|
||||
@@ -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" },
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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">
|
||||
|
||||
7
src/app/(dashboard)/dashboard/costs/budget/page.tsx
Normal file
7
src/app/(dashboard)/dashboard/costs/budget/page.tsx
Normal file
@@ -0,0 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import BudgetTab from "../../usage/components/BudgetTab";
|
||||
|
||||
export default function CostsBudgetPage() {
|
||||
return <BudgetTab />;
|
||||
}
|
||||
@@ -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 />;
|
||||
}
|
||||
|
||||
7
src/app/(dashboard)/dashboard/costs/pricing/page.tsx
Normal file
7
src/app/(dashboard)/dashboard/costs/pricing/page.tsx
Normal file
@@ -0,0 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import PricingTab from "../../settings/components/PricingTab";
|
||||
|
||||
export default function CostsPricingPage() {
|
||||
return <PricingTab />;
|
||||
}
|
||||
@@ -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
@@ -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} />
|
||||
|
||||
@@ -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 ?? "—"} />
|
||||
|
||||
@@ -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="" />;
|
||||
}
|
||||
|
||||
@@ -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 */}
|
||||
|
||||
7
src/app/(dashboard)/dashboard/logs/activity/page.tsx
Normal file
7
src/app/(dashboard)/dashboard/logs/activity/page.tsx
Normal file
@@ -0,0 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import AuditLogTab from "../AuditLogTab";
|
||||
|
||||
export default function LogsActivityPage() {
|
||||
return <AuditLogTab />;
|
||||
}
|
||||
7
src/app/(dashboard)/dashboard/logs/console/page.tsx
Normal file
7
src/app/(dashboard)/dashboard/logs/console/page.tsx
Normal file
@@ -0,0 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import ConsoleLogViewer from "@/shared/components/ConsoleLogViewer";
|
||||
|
||||
export default function LogsConsolePage() {
|
||||
return <ConsoleLogViewer />;
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
7
src/app/(dashboard)/dashboard/logs/proxy/page.tsx
Normal file
7
src/app/(dashboard)/dashboard/logs/proxy/page.tsx
Normal file
@@ -0,0 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import ProxyLogger from "@/shared/components/ProxyLogger";
|
||||
|
||||
export default function LogsProxyPage() {
|
||||
return <ProxyLogger />;
|
||||
}
|
||||
355
src/app/(dashboard)/dashboard/mcp/page.tsx
Normal file
355
src/app/(dashboard)/dashboard/mcp/page.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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")}
|
||||
|
||||
@@ -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"
|
||||
>
|
||||
|
||||
@@ -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) => (
|
||||
|
||||
@@ -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"
|
||||
|
||||
15
src/app/(dashboard)/dashboard/settings/advanced/page.tsx
Normal file
15
src/app/(dashboard)/dashboard/settings/advanced/page.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
19
src/app/(dashboard)/dashboard/settings/ai/page.tsx
Normal file
19
src/app/(dashboard)/dashboard/settings/ai/page.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import AppearanceTab from "../components/AppearanceTab";
|
||||
|
||||
export default function SettingsAppearancePage() {
|
||||
return <AppearanceTab />;
|
||||
}
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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"
|
||||
>
|
||||
|
||||
7
src/app/(dashboard)/dashboard/settings/general/page.tsx
Normal file
7
src/app/(dashboard)/dashboard/settings/general/page.tsx
Normal file
@@ -0,0 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import SystemStorageTab from "../components/SystemStorageTab";
|
||||
|
||||
export default function SettingsGeneralPage() {
|
||||
return <SystemStorageTab />;
|
||||
}
|
||||
@@ -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) => (
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import ResilienceTab from "../components/ResilienceTab";
|
||||
|
||||
export default function SettingsResiliencePage() {
|
||||
return <ResilienceTab />;
|
||||
}
|
||||
19
src/app/(dashboard)/dashboard/settings/routing/page.tsx
Normal file
19
src/app/(dashboard)/dashboard/settings/routing/page.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
7
src/app/(dashboard)/dashboard/settings/security/page.tsx
Normal file
7
src/app/(dashboard)/dashboard/settings/security/page.tsx
Normal file
@@ -0,0 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import SecurityTab from "../components/SecurityTab";
|
||||
|
||||
export default function SettingsSecurityPage() {
|
||||
return <SecurityTab />;
|
||||
}
|
||||
@@ -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"
|
||||
|
||||
6
src/app/(dashboard)/dashboard/system/1proxy/page.tsx
Normal file
6
src/app/(dashboard)/dashboard/system/1proxy/page.tsx
Normal file
@@ -0,0 +1,6 @@
|
||||
"use client";
|
||||
import OneproxyTab from "@/app/(dashboard)/dashboard/settings/components/OneproxyTab";
|
||||
|
||||
export default function OneProxyPage() {
|
||||
return <OneproxyTab />;
|
||||
}
|
||||
6
src/app/(dashboard)/dashboard/system/mitm-proxy/page.tsx
Normal file
6
src/app/(dashboard)/dashboard/system/mitm-proxy/page.tsx
Normal file
@@ -0,0 +1,6 @@
|
||||
"use client";
|
||||
import MitmProxyTab from "@/app/(dashboard)/dashboard/settings/components/MitmProxyTab";
|
||||
|
||||
export default function MitmProxyPage() {
|
||||
return <MitmProxyTab />;
|
||||
}
|
||||
@@ -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 />;
|
||||
}
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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"
|
||||
|
||||
408
src/app/(dashboard)/home/ProviderTopology.tsx
Normal file
408
src/app/(dashboard)/home/ProviderTopology.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
22
src/app/(dashboard)/home/page.tsx
Normal file
22
src/app/(dashboard)/home/page.tsx
Normal 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} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
32
src/app/api/network/info/route.ts
Normal file
32
src/app/api/network/info/route.ts
Normal 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 });
|
||||
}
|
||||
@@ -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": "بداية سريعة",
|
||||
|
||||
@@ -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": "Бърз старт",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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": "התחלה מהירה",
|
||||
|
||||
@@ -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": "त्वरित शुरुआत",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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": "クイックスタート",
|
||||
|
||||
@@ -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": "빠른 시작",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user