Yogesh
initial deploy
cd8bd0a
Raw
History Blame Contribute Delete
64.1 kB
"use client";
import { useEffect, useMemo, useState } from "react";
import { Button, Card, Collapsible, Input, Select, Toggle } from "@/shared/components";
import { useTranslations } from "next-intl";
import { useNotificationStore } from "@/store/notificationStore";
import {
CLI_COMPAT_PROVIDER_DISPLAY,
CLI_COMPAT_TOGGLE_IDS,
normalizeCliCompatProviderId,
} from "@/shared/constants/cliCompatProviders";
import { AI_PROVIDERS } from "@/shared/constants/providers";
import { compareTr } from "@/shared/utils/turkishText";
// Provider keys (mirror of open-sse/services/systemTransforms.ts).
const PROVIDER_CLAUDE = "claude";
const PROVIDER_CC_BRIDGE = "anthropic-compatible-cc";
const BUILTIN_PROVIDERS = new Set([PROVIDER_CLAUDE, PROVIDER_CC_BRIDGE]);
// Canonical provider catalog for the "Add provider" dropdown. Pulled from the
// shared AI_PROVIDERS registry so the UI stays in sync with backend provider
// definitions. We add the CC bridge synthetic ID (no AI_PROVIDERS entry — it's
// a relay surface, not an upstream provider). Sorted by display name.
type ProviderCatalogEntry = { id: string; name: string };
const PROVIDER_CATALOG: ProviderCatalogEntry[] = (() => {
const entries: ProviderCatalogEntry[] = Object.values(AI_PROVIDERS).map((p) => ({
id: p.id,
name: p.name ?? p.id,
}));
entries.push({ id: PROVIDER_CC_BRIDGE, name: "Anthropic-compatible CC bridge" });
entries.sort((a, b) => compareTr(a.name, b.name));
return entries;
})();
const OPENWEBUI_PARAGRAPH_ANCHORS = [
"github.com/open-webui/open-webui",
"openwebui.com",
"docs.openwebui.com",
];
// Mirrors of ccBridgeTransforms.ts constants used by the native `claude` and
// CC-bridge default pipelines.
const DEFAULT_PARAGRAPH_REMOVAL_ANCHORS = [
"github.com/anomalyco/opencode",
"opencode.ai/docs",
"github.com/cline/cline",
"github.com/getcursor/cursor",
"continue.dev",
];
const PI_PARAGRAPH_ANCHORS = [
"@earendil-works/pi-coding-agent",
"/.pi/",
"Pi documentation (read only when the user asks about pi itself",
];
const DEFAULT_IDENTITY_PREFIXES = ["You are OpenCode"];
const DEFAULT_TEXT_REPLACEMENTS = [
{ match: "if OpenCode honestly", replacement: "if the assistant honestly" },
{
match: "Here is some useful information about the environment you are running in:",
replacement: "Environment context you are running in:",
},
];
const DEFAULT_OBFUSCATE_WORDS = [
"opencode",
"open-code",
"cline",
"roo-cline",
"roo_cline",
"cursor",
"windsurf",
"aider",
"continue.dev",
"copilot",
"avante",
"codecompanion",
"openwebui",
"open-webui",
];
// Mirror of DEFAULT_SYSTEM_TRANSFORMS_CONFIG from open-sse/services/systemTransforms.ts.
// Kept client-side so the UI can render + reset to defaults without a server roundtrip.
// Server remains the source of truth — UI just lets the user inspect, edit, and reset.
const DEFAULT_SYSTEM_TRANSFORMS_CLIENT = {
providers: {
[PROVIDER_CLAUDE]: {
enabled: true,
pipeline: [
{
kind: "drop_paragraph_if_contains",
needles: [
...DEFAULT_PARAGRAPH_REMOVAL_ANCHORS,
...OPENWEBUI_PARAGRAPH_ANCHORS,
...PI_PARAGRAPH_ANCHORS,
],
},
{
kind: "drop_paragraph_if_starts_with",
prefixes: [...DEFAULT_IDENTITY_PREFIXES, "You are Open WebUI"],
},
...DEFAULT_TEXT_REPLACEMENTS.map((r) => ({
kind: "replace_text" as const,
match: r.match,
replacement: r.replacement,
allOccurrences: true,
})),
{
kind: "obfuscate_words",
words: [...DEFAULT_OBFUSCATE_WORDS],
targets: ["system", "messages", "tools"],
},
],
},
[PROVIDER_CC_BRIDGE]: {
enabled: true,
pipeline: [
{
kind: "drop_paragraph_if_contains",
needles: [...OPENWEBUI_PARAGRAPH_ANCHORS],
},
{
kind: "drop_paragraph_if_starts_with",
prefixes: ["You are Open WebUI"],
},
{
kind: "obfuscate_words",
words: ["openwebui", "open-webui"],
targets: ["system", "messages", "tools"],
},
{
kind: "drop_paragraph_if_contains",
needles: [
"github.com/anomalyco/opencode",
"opencode.ai/docs",
"github.com/cline/cline",
"github.com/getcursor/cursor",
"continue.dev",
],
},
{
kind: "drop_paragraph_if_starts_with",
prefixes: ["You are OpenCode"],
},
{
kind: "replace_text",
match: "if OpenCode honestly",
replacement: "if the assistant honestly",
allOccurrences: true,
},
{
kind: "replace_text",
match: "Here is some useful information about the environment you are running in:",
replacement: "Environment context you are running in:",
allOccurrences: true,
},
{
kind: "prepend_system_block",
text: "You are a Claude agent, built on Anthropic's Claude Agent SDK.",
idempotencyKey: "claude-agent-sdk-identity",
},
{
kind: "inject_billing_header",
entrypoint: "sdk-cli",
versionFormat: "ex-machina",
cchAlgo: "sha256-first-user",
},
],
},
},
} as const;
const PROVIDER_TILE_DISPLAY: Record<
string,
{ name: string; description: string; icon: string; tone: string }
> = {
[PROVIDER_CLAUDE]: {
name: "Claude (OAuth)",
description: "Native Claude provider with OAuth-issued tokens.",
icon: "anthropic",
tone: "indigo",
},
[PROVIDER_CC_BRIDGE]: {
name: "Claude-Code Bridge",
description: "Relay endpoints using API keys (anthropic-compatible-cc-*).",
icon: "hub",
tone: "purple",
},
};
type TransformOpKind =
| "drop_paragraph_if_contains"
| "drop_paragraph_if_starts_with"
| "replace_text"
| "replace_regex"
| "drop_block_if_contains"
| "prepend_system_block"
| "append_system_block"
| "inject_billing_header"
| "obfuscate_words";
const OP_KIND_LABELS: Record<TransformOpKind, string> = {
drop_paragraph_if_contains: "routingOpDropParagraphContainsLabel",
drop_paragraph_if_starts_with: "routingOpDropParagraphStartsWithLabel",
replace_text: "routingOpReplaceTextLabel",
replace_regex: "routingOpReplaceRegexLabel",
drop_block_if_contains: "routingOpDropBlockContainsLabel",
prepend_system_block: "routingOpPrependSystemBlockLabel",
append_system_block: "routingOpAppendSystemBlockLabel",
inject_billing_header: "routingOpInjectBillingHeaderLabel",
obfuscate_words: "routingOpObfuscateWordsLabel",
};
// Human-readable description shown above each op's editor. Explains in one
// sentence what the op DOES (transformation effect) and one sentence WHEN
// to use it (the typical fingerprint-sanitization use-case).
const OP_KIND_DESCRIPTIONS: Record<TransformOpKind, string> = {
drop_paragraph_if_contains: "routingOpDropParagraphContainsDesc",
drop_paragraph_if_starts_with: "routingOpDropParagraphStartsWithDesc",
replace_text: "routingOpReplaceTextDesc",
replace_regex: "routingOpReplaceRegexDesc",
drop_block_if_contains: "routingOpDropBlockContainsDesc",
prepend_system_block: "routingOpPrependSystemBlockDesc",
append_system_block: "routingOpAppendSystemBlockDesc",
inject_billing_header: "routingOpInjectBillingHeaderDesc",
obfuscate_words: "routingOpObfuscateWordsDesc",
};
// Per-field hints rendered under each Input/Select/Toggle inside the
// editor. Short, plain-English. Keep under ~120 chars each.
const FIELD_HINTS = {
needles: "routingNeedlesHint",
prefixes: "routingPrefixesHint",
caseSensitive: "routingCaseSensitiveHint",
matchLiteral: "routingMatchLiteralHint",
replacementText: "routingReplacementTextHint",
allOccurrences: "routingAllOccurrencesHint",
pattern: "routingPatternHint",
regexFlags: "routingRegexFlagsHint",
blockText: "routingBlockTextHint",
idempotencyKey: "routingIdempotencyKeyHint",
billingEntrypoint: "routingBillingEntrypointHint",
billingVersionFormat: "routingBillingVersionFormatHint",
billingCchAlgo: "routingBillingCchAlgoHint",
obfuscateWords: "routingObfuscateWordsHint",
obfuscateTargets: "routingObfuscateTargetsHint",
};
function makeDefaultOp(kind: TransformOpKind): any {
switch (kind) {
case "drop_paragraph_if_contains":
return { kind, needles: [""] };
case "drop_paragraph_if_starts_with":
return { kind, prefixes: [""] };
case "replace_text":
return { kind, match: "", replacement: "", allOccurrences: true };
case "replace_regex":
return { kind, pattern: "", flags: "g", replacement: "" };
case "drop_block_if_contains":
return { kind, needles: [""] };
case "prepend_system_block":
return { kind, text: "", idempotencyKey: "" };
case "append_system_block":
return { kind, text: "", idempotencyKey: "" };
case "inject_billing_header":
return {
kind,
entrypoint: "sdk-cli",
versionFormat: "ex-machina",
cchAlgo: "sha256-first-user",
};
case "obfuscate_words":
return { kind, words: [""], targets: ["system", "messages", "tools"] };
}
}
function StringListEditor({
label,
hint,
items,
onChange,
disabled,
}: {
label: string;
hint?: string;
items: string[];
onChange: (next: string[]) => void;
disabled?: boolean;
}) {
const t = useTranslations("settings");
const tCommon = useTranslations("common");
return (
<div className="flex flex-col gap-1.5">
<span className="text-xs font-medium text-text-main">{label}</span>
{hint && <p className="text-xs text-text-muted">{hint}</p>}
{items.map((item, idx) => (
<div key={idx} className="flex items-center gap-2">
<Input
className="flex-1"
value={item}
disabled={disabled}
onChange={(e) => {
const next = [...items];
next[idx] = e.target.value;
onChange(next);
}}
/>
<Button
variant="ghost"
size="sm"
icon="close"
disabled={disabled}
aria-label={t("routingRemoveEntry")}
onClick={() => {
const next = [...items];
next.splice(idx, 1);
onChange(next);
}}
/>
</div>
))}
<Button
variant="ghost"
size="sm"
icon="add"
disabled={disabled}
onClick={() => onChange([...items, ""])}
className="self-start"
>
{tCommon("add") || "Add entry"}
</Button>
</div>
);
}
function OpEditor({
op,
onChange,
disabled,
}: {
op: any;
onChange: (next: any) => void;
disabled?: boolean;
}) {
const t = useTranslations("settings");
const updateField = (field: string, value: any) => onChange({ ...op, [field]: value });
const kind = op?.kind as TransformOpKind | undefined;
const opDescription = kind ? t(OP_KIND_DESCRIPTIONS[kind]) : null;
const wrap = (body: React.ReactNode) => (
<div className="flex flex-col gap-3">
{opDescription && (
<p className="text-[11px] leading-relaxed text-text-muted border-l-2 border-purple-500/30 pl-2 italic">
{opDescription}
</p>
)}
{body}
</div>
);
switch (op?.kind) {
case "drop_paragraph_if_contains":
return wrap(
<div className="flex flex-col gap-2">
<StringListEditor
label={t("routingNeedlesSubstrings")}
hint={t(FIELD_HINTS.needles)}
items={op.needles || []}
onChange={(next) => updateField("needles", next)}
disabled={disabled}
/>
<Toggle
label={t("routingCaseSensitive")}
description={t(FIELD_HINTS.caseSensitive)}
checked={op.caseSensitive !== false}
onChange={(c) => updateField("caseSensitive", c)}
size="sm"
disabled={disabled}
/>
</div>
);
case "drop_paragraph_if_starts_with":
return wrap(
<div className="flex flex-col gap-2">
<StringListEditor
label={t("routingPrefixes")}
hint={t(FIELD_HINTS.prefixes)}
items={op.prefixes || []}
onChange={(next) => updateField("prefixes", next)}
disabled={disabled}
/>
<Toggle
label={t("routingCaseSensitive")}
description={t(FIELD_HINTS.caseSensitive)}
checked={op.caseSensitive !== false}
onChange={(c) => updateField("caseSensitive", c)}
size="sm"
disabled={disabled}
/>
</div>
);
case "replace_text":
return wrap(
<div className="flex flex-col gap-2">
<Input
label={t("routingMatch")}
hint={t(FIELD_HINTS.matchLiteral)}
value={op.match || ""}
disabled={disabled}
onChange={(e) => updateField("match", e.target.value)}
/>
<Input
label={t("routingReplacement")}
hint={t(FIELD_HINTS.replacementText)}
value={op.replacement || ""}
disabled={disabled}
onChange={(e) => updateField("replacement", e.target.value)}
/>
<Toggle
label={t("routingReplaceAllOccurrences")}
description={t(FIELD_HINTS.allOccurrences)}
checked={op.allOccurrences !== false}
onChange={(c) => updateField("allOccurrences", c)}
size="sm"
disabled={disabled}
/>
</div>
);
case "replace_regex":
return wrap(
<div className="flex flex-col gap-2">
<Input
label={t("routingPatternRegex")}
hint={t(FIELD_HINTS.pattern)}
value={op.pattern || ""}
disabled={disabled}
onChange={(e) => updateField("pattern", e.target.value)}
/>
<Input
label={t("routingFlags")}
hint={t(FIELD_HINTS.regexFlags)}
value={op.flags || "g"}
disabled={disabled}
onChange={(e) => updateField("flags", e.target.value)}
/>
<Input
label={t("routingReplacement")}
hint={t(FIELD_HINTS.replacementText)}
value={op.replacement || ""}
disabled={disabled}
onChange={(e) => updateField("replacement", e.target.value)}
/>
</div>
);
case "drop_block_if_contains":
return wrap(
<StringListEditor
label={t("routingNeedles")}
hint={t(FIELD_HINTS.needles)}
items={op.needles || []}
onChange={(next) => updateField("needles", next)}
disabled={disabled}
/>
);
case "prepend_system_block":
case "append_system_block":
return wrap(
<div className="flex flex-col gap-2">
<div className="flex flex-col gap-1.5">
<label className="text-sm font-medium text-text-main">{t("routingBlockText")}</label>
<textarea
rows={3}
value={op.text || ""}
disabled={disabled}
onChange={(e) => updateField("text", e.target.value)}
className="w-full rounded-md border border-black/10 dark:border-white/10 bg-white dark:bg-white/5 px-3 py-2 text-sm text-text-main font-mono focus:ring-1 focus:ring-primary/30 focus:border-primary/50 focus:outline-none transition-all shadow-inner disabled:opacity-50 disabled:cursor-not-allowed"
/>
<p className="text-xs text-text-muted">{t(FIELD_HINTS.blockText)}</p>
</div>
<Input
label={t("routingIdempotencyKey")}
hint={t(FIELD_HINTS.idempotencyKey)}
value={op.idempotencyKey || ""}
disabled={disabled}
onChange={(e) => updateField("idempotencyKey", e.target.value)}
/>
</div>
);
case "inject_billing_header":
return wrap(
<div className="flex flex-col gap-2">
<Input
label={t("routingEntrypoint")}
hint={t(FIELD_HINTS.billingEntrypoint)}
value={op.entrypoint || "sdk-cli"}
disabled={disabled}
onChange={(e) => updateField("entrypoint", e.target.value)}
/>
<Select
label={t("routingVersionFormat")}
hint={t(FIELD_HINTS.billingVersionFormat)}
value={op.versionFormat || "ex-machina"}
disabled={disabled}
onChange={(e) => updateField("versionFormat", e.target.value)}
options={[
{ value: "ex-machina", label: "ex-machina (sha256 per-msg suffix)" },
{ value: "omniroute-daystamp", label: "omniroute-daystamp (sha256 day+version)" },
]}
/>
<Select
label={t("routingCchAlgorithm")}
hint={t(FIELD_HINTS.billingCchAlgo)}
value={op.cchAlgo || "sha256-first-user"}
disabled={disabled}
onChange={(e) => updateField("cchAlgo", e.target.value)}
options={[
{ value: "sha256-first-user", label: "sha256-first-user (ex-machina style)" },
{ value: "xxhash64-body", label: "xxhash64-body (body-level signing)" },
{ value: "static-zero", label: "static-zero (00000 placeholder)" },
]}
/>
</div>
);
case "obfuscate_words":
return wrap(
<div className="flex flex-col gap-2">
<StringListEditor
label={t("routingWordsToObfuscate")}
hint={t(FIELD_HINTS.obfuscateWords)}
items={op.words || []}
onChange={(next) => updateField("words", next)}
disabled={disabled}
/>
<div className="flex flex-col gap-1.5">
<span className="text-xs font-medium text-text-main">
{t("routingObfuscateTargetsLabel")}
</span>
<p className="text-xs text-text-muted">{t(FIELD_HINTS.obfuscateTargets)}</p>
<div className="flex flex-wrap gap-4">
{(["system", "messages", "tools"] as const).map((target) => {
const targets: string[] = op.targets || ["system", "messages", "tools"];
const checked = targets.includes(target);
return (
<Toggle
key={target}
label={target}
checked={checked}
size="sm"
disabled={disabled}
onChange={(c) => {
const next = c ? [...targets, target] : targets.filter((x) => x !== target);
updateField("targets", next);
}}
/>
);
})}
</div>
</div>
</div>
);
default:
return <p className="text-xs text-text-muted">Unknown op kind: {op?.kind}</p>;
}
}
function summarizeTransformOp(op: any, t: any): string {
switch (op?.kind) {
case "drop_paragraph_if_contains":
return t("routingSummarizeDropParagraphContains", {
items:
(op.needles || []).slice(0, 3).join(", ") + ((op.needles || []).length > 3 ? "…" : ""),
});
case "drop_paragraph_if_starts_with":
return t("routingSummarizeDropParagraphStartsWith", {
items:
(op.prefixes || []).slice(0, 3).join(", ") + ((op.prefixes || []).length > 3 ? "…" : ""),
});
case "replace_text":
return t("routingSummarizeReplaceText", {
match: (op.match || "").slice(0, 40) + ((op.match || "").length > 40 ? "…" : ""),
replacement:
(op.replacement || "").slice(0, 40) + ((op.replacement || "").length > 40 ? "…" : ""),
});
case "replace_regex":
return t("routingSummarizeReplaceRegex", {
pattern: op.pattern,
flags: op.flags || "",
replacement: (op.replacement || "").slice(0, 40),
});
case "drop_block_if_contains":
return t("routingSummarizeDropBlockContains", {
items: (op.needles || []).slice(0, 3).join(", "),
});
case "prepend_system_block":
return t("routingSummarizePrependSystemBlock", {
text: (op.text || "").slice(0, 60) + ((op.text || "").length > 60 ? "…" : ""),
});
case "append_system_block":
return t("routingSummarizeAppendSystemBlock", {
text: (op.text || "").slice(0, 60) + ((op.text || "").length > 60 ? "…" : ""),
});
case "inject_billing_header":
return t("routingSummarizeInjectBillingHeader", {
entrypoint: op.entrypoint,
versionFormat: op.versionFormat,
cchAlgo: op.cchAlgo,
});
case "obfuscate_words":
return t("routingSummarizeObfuscateWords", {
count: (op.words || []).length,
targets: (op.targets || ["system", "messages", "tools"]).join("+"),
});
default:
return JSON.stringify(op);
}
}
// Client-side validator — light shape check before we PATCH; the server
// re-validates with the full zod schema in settingsSchemas.ts.
function validateProviderTransformsConfig(value: unknown): string | null {
if (!value || typeof value !== "object") return "Config must be a JSON object";
const cfg = value as { enabled?: unknown; pipeline?: unknown };
if (typeof cfg.enabled !== "boolean") return "`enabled` must be true or false";
if (!Array.isArray(cfg.pipeline)) return "`pipeline` must be an array of ops";
if (cfg.pipeline.length > 50) return "Pipeline cannot exceed 50 ops";
for (let i = 0; i < cfg.pipeline.length; i++) {
const op = cfg.pipeline[i] as { kind?: unknown };
if (!op || typeof op !== "object" || typeof op.kind !== "string") {
return `Op #${i + 1}: missing or invalid \`kind\``;
}
const validKinds = [
"drop_paragraph_if_contains",
"drop_paragraph_if_starts_with",
"replace_text",
"replace_regex",
"drop_block_if_contains",
"prepend_system_block",
"append_system_block",
"inject_billing_header",
"obfuscate_words",
];
if (!validKinds.includes(op.kind)) {
return `Op #${i + 1}: unknown kind "${op.kind}"`;
}
}
return null;
}
export default function RoutingTab() {
const [settings, setSettings] = useState<any>({
alwaysPreserveClientCache: "auto",
antigravitySignatureCacheMode: "enabled",
cliCompatProviders: [],
autoRoutingEnabled: true,
autoRoutingDefaultVariant: "lkgp",
systemTransforms: DEFAULT_SYSTEM_TRANSFORMS_CLIENT,
});
// Per-provider JSON draft + error state for the system-transforms editor.
// Map keyed by provider id; values track the textarea content + last
// validation error string (null when valid). Synced from settings via
// effect so server-side values flow into the editor.
const [jsonDrafts, setJsonDrafts] = useState<Record<string, string>>({});
const [jsonErrors, setJsonErrors] = useState<Record<string, string | null>>({});
// Save-state messages for the per-op structured editor (separate from
// jsonErrors which belongs to the JSON textarea). Cleared when the user
// makes a fresh edit; populated when the server rejects a PATCH.
const [providerSaveErrors, setProviderSaveErrors] = useState<Record<string, string | null>>({});
const [showJsonEditor, setShowJsonEditor] = useState<Record<string, boolean>>({});
const [addOpKind, setAddOpKind] = useState<Record<string, TransformOpKind>>({});
const [newProviderId, setNewProviderId] = useState("");
const [loading, setLoading] = useState(true);
const [lkgpCacheLoading, setLkgpCacheLoading] = useState(false);
const [lkgpCacheStatus, setLkgpCacheStatus] = useState({ type: "", message: "" });
const t = useTranslations("settings");
const tCommon = useTranslations("common");
const notify = useNotificationStore();
useEffect(() => {
fetch("/api/settings")
.then((res) => res.json())
.then((data) => {
setSettings(data);
setLoading(false);
})
.catch(() => setLoading(false));
}, []);
// Optimistic update: apply the patch to local state FIRST so the UI never
// appears to drop the user's edit, then PATCH the server. If the server
// rejects (e.g. blank required field on a freshly-added op), surface the
// error to the caller via onError so the editor can render it inline. Local
// state is intentionally NOT rolled back — the user keeps editing and
// re-saves once the validation passes.
const updateSetting = async (patch: Record<string, unknown>, onError?: (msg: string) => void) => {
setSettings((prev: any) => ({ ...prev, ...patch }));
try {
const res = await fetch("/api/settings", {
method: "PATCH",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(patch),
});
if (!res.ok) {
let serverMsg = `HTTP ${res.status}`;
try {
const body = await res.json();
const details = Array.isArray(body?.error?.details)
? body.error.details
.map((d: { field?: string; message?: string }) =>
d.field ? `${d.field}: ${d.message ?? "invalid"}` : d.message
)
.filter(Boolean)
.join("; ")
: null;
serverMsg = details || body?.error?.message || serverMsg;
} catch {
// body wasn't JSON — keep the HTTP status fallback
}
notify.error(t("saveFailed"), serverMsg);
if (onError) onError(serverMsg);
else console.error("Failed to update settings:", serverMsg);
} else {
notify.success(t("savedSuccessfully"));
}
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
notify.error(t("saveFailed"), msg);
if (onError) onError(msg);
else console.error("Failed to update settings:", msg);
}
};
const cliCompatProviders = useMemo(
() =>
Array.isArray(settings.cliCompatProviders)
? settings.cliCompatProviders.map((providerId: string) =>
normalizeCliCompatProviderId(providerId)
)
: [],
[settings.cliCompatProviders]
);
const cliCompatProviderSet = useMemo(() => new Set(cliCompatProviders), [cliCompatProviders]);
// Normalize the server snapshot into a per-provider map. Legacy v1
// `ccBridgeTransforms` payloads from Phase 2 are migrated client-side
// into providers[PROVIDER_CC_BRIDGE] so the editor never breaks.
const systemTransforms = useMemo(() => {
const raw = settings.systemTransforms;
if (raw && typeof raw === "object" && raw.providers && typeof raw.providers === "object") {
return raw as { providers: Record<string, { enabled: boolean; pipeline: any[] }> };
}
// Legacy migration shim: { enabled, pipeline } → providers[CC_BRIDGE].
const legacy = settings.ccBridgeTransforms;
if (legacy && typeof legacy === "object" && Array.isArray(legacy.pipeline)) {
return {
providers: {
...DEFAULT_SYSTEM_TRANSFORMS_CLIENT.providers,
[PROVIDER_CC_BRIDGE]: {
enabled: legacy.enabled !== false,
pipeline: legacy.pipeline,
},
},
};
}
return DEFAULT_SYSTEM_TRANSFORMS_CLIENT;
}, [settings.systemTransforms, settings.ccBridgeTransforms]);
// Sync JSON drafts from settings whenever the server snapshot changes.
useEffect(() => {
const nextDrafts: Record<string, string> = {};
for (const [providerId, providerCfg] of Object.entries(systemTransforms.providers)) {
nextDrafts[providerId] = JSON.stringify(providerCfg, null, 2);
}
setJsonDrafts(nextDrafts);
setJsonErrors({});
}, [systemTransforms]);
const updateProviderTransforms = (
providerId: string,
next: { enabled: boolean; pipeline: any[] }
) => {
const merged = {
providers: {
...systemTransforms.providers,
[providerId]: next,
},
};
// Clear any prior save error for this provider so the user sees a fresh
// state. If the server rejects, the callback below will repopulate it.
setProviderSaveErrors((prev) => ({ ...prev, [providerId]: null }));
updateSetting({ systemTransforms: merged }, (msg) =>
setProviderSaveErrors((prev) => ({ ...prev, [providerId]: msg }))
);
};
const toggleProviderEnabled = (providerId: string, enabled: boolean) => {
const current = systemTransforms.providers[providerId] ?? { enabled: false, pipeline: [] };
updateProviderTransforms(providerId, { enabled, pipeline: current.pipeline });
};
const applyProviderJson = (providerId: string) => {
const raw = jsonDrafts[providerId] ?? "";
let parsed: unknown;
try {
parsed = JSON.parse(raw);
} catch (err) {
setJsonErrors((prev) => ({
...prev,
[providerId]: `Invalid JSON: ${(err as Error).message}`,
}));
return;
}
const validationError = validateProviderTransformsConfig(parsed);
if (validationError) {
setJsonErrors((prev) => ({ ...prev, [providerId]: validationError }));
return;
}
setJsonErrors((prev) => ({ ...prev, [providerId]: null }));
updateProviderTransforms(providerId, parsed as { enabled: boolean; pipeline: any[] });
};
const resetProviderTransforms = (providerId: string) => {
const def = (DEFAULT_SYSTEM_TRANSFORMS_CLIENT.providers as Record<string, any>)[providerId];
if (!def) return;
setJsonErrors((prev) => ({ ...prev, [providerId]: null }));
updateProviderTransforms(providerId, {
enabled: def.enabled,
pipeline: def.pipeline.map((op: any) => ({ ...op })),
});
};
const updateOp = (providerId: string, opIndex: number, next: any) => {
const current = systemTransforms.providers[providerId] ?? { enabled: false, pipeline: [] };
const pipeline = [...(current.pipeline as any[])];
pipeline[opIndex] = next;
updateProviderTransforms(providerId, { enabled: current.enabled, pipeline });
};
const deleteOp = (providerId: string, opIndex: number) => {
const current = systemTransforms.providers[providerId] ?? { enabled: false, pipeline: [] };
const pipeline = (current.pipeline as any[]).filter((_, i) => i !== opIndex);
updateProviderTransforms(providerId, { enabled: current.enabled, pipeline });
};
const moveOp = (providerId: string, opIndex: number, direction: -1 | 1) => {
const current = systemTransforms.providers[providerId] ?? { enabled: false, pipeline: [] };
const pipeline = [...(current.pipeline as any[])];
const target = opIndex + direction;
if (target < 0 || target >= pipeline.length) return;
[pipeline[opIndex], pipeline[target]] = [pipeline[target], pipeline[opIndex]];
updateProviderTransforms(providerId, { enabled: current.enabled, pipeline });
};
const addOp = (providerId: string) => {
const kind = addOpKind[providerId] ?? "drop_paragraph_if_contains";
const current = systemTransforms.providers[providerId] ?? { enabled: false, pipeline: [] };
const pipeline = [...(current.pipeline as any[]), makeDefaultOp(kind)];
updateProviderTransforms(providerId, { enabled: current.enabled, pipeline });
};
const availableProvidersToAdd = useMemo(
() => PROVIDER_CATALOG.filter((p) => !systemTransforms.providers[p.id]),
[systemTransforms.providers]
);
const addProvider = () => {
const id = newProviderId;
if (!id || systemTransforms.providers[id]) return;
updateProviderTransforms(id, { enabled: false, pipeline: [] });
setNewProviderId("");
};
const removeProvider = (providerId: string) => {
if (BUILTIN_PROVIDERS.has(providerId)) return;
const providers = systemTransforms.providers as Record<string, unknown>;
const { [providerId]: _removed, ...rest } = providers;
updateSetting({ systemTransforms: { providers: rest } });
};
const toggleCliCompatProvider = (providerId: string, enabled: boolean) => {
const normalizedProviderId = normalizeCliCompatProviderId(providerId);
const nextProviders = new Set(cliCompatProviders);
if (enabled) {
nextProviders.add(normalizedProviderId);
} else {
nextProviders.delete(normalizedProviderId);
}
updateSetting({ cliCompatProviders: Array.from(nextProviders) });
};
return (
<div className="flex flex-col gap-6">
<Card>
<div className="flex items-start justify-between gap-4">
<div className="flex gap-3">
<div className="p-2 rounded-lg bg-indigo-500/10 text-indigo-500 h-fit">
<span className="material-symbols-outlined text-[20px]" aria-hidden="true">
auto_awesome
</span>
</div>
<div>
<h3 className="text-lg font-semibold">{t("routingZeroConfigTitle")}</h3>
<p className="text-sm text-text-muted mt-1">{t("routingZeroConfigDesc")}</p>
</div>
</div>
<div className="pt-1">
<Toggle
checked={settings.autoRoutingEnabled !== false}
onChange={(checked) => updateSetting({ autoRoutingEnabled: checked })}
disabled={loading}
ariaLabel={t("routingZeroConfigTitle")}
/>
</div>
</div>
<div className="mt-4 pt-4 border-t border-border/30">
<label className="block text-sm font-medium mb-2">{t("routingDefaultAutoVariant")}</label>
<div className="grid grid-cols-2 sm:grid-cols-3 gap-2">
{[
{
value: "lkgp",
label: t("routingDefaultAutoVariantLKGP"),
desc: t("routingDefaultAutoVariantLKGPDesc"),
},
{
value: "coding",
label: t("routingDefaultAutoVariantCoding"),
desc: t("routingDefaultAutoVariantCodingDesc"),
},
{
value: "fast",
label: t("routingDefaultAutoVariantFast"),
desc: t("routingDefaultAutoVariantFastDesc"),
},
{
value: "cheap",
label: t("routingDefaultAutoVariantCheap"),
desc: t("routingDefaultAutoVariantCheapDesc"),
},
{
value: "offline",
label: t("routingDefaultAutoVariantOffline"),
desc: t("routingDefaultAutoVariantOfflineDesc"),
},
{
value: "smart",
label: t("routingDefaultAutoVariantSmart"),
desc: t("routingDefaultAutoVariantSmartDesc"),
},
].map((option) => (
<button
key={option.value}
onClick={() => updateSetting({ autoRoutingDefaultVariant: option.value })}
disabled={loading}
className={`p-2 rounded-lg border text-left transition-all ${
settings.autoRoutingDefaultVariant === option.value
? "border-indigo-500/50 bg-indigo-500/5 ring-1 ring-indigo-500/20"
: "border-border/50 hover:border-border hover:bg-surface/30"
}`}
>
<div className="flex items-center gap-1">
<span
className={`material-symbols-outlined text-[14px] ${
settings.autoRoutingDefaultVariant === option.value
? "text-indigo-400"
: "text-text-muted"
}`}
>
{settings.autoRoutingDefaultVariant === option.value
? "check_circle"
: "radio_button_unchecked"}
</span>
<span
className={`text-xs font-medium ${settings.autoRoutingDefaultVariant === option.value ? "text-indigo-400" : ""}`}
>
{option.label}
</span>
</div>
</button>
))}
</div>
</div>
</Card>
<Card>
<div className="flex items-start gap-3 mb-4">
<div className="p-2 rounded-lg bg-purple-500/10 text-purple-500 h-fit">
<span className="material-symbols-outlined text-[20px]" aria-hidden="true">
tune
</span>
</div>
<div>
<h3 className="text-lg font-semibold">{t("systemTransforms")}</h3>
<p className="text-sm text-text-muted mt-1">{t("systemTransformsDesc")}</p>
</div>
</div>
{/* Add provider — moved to TOP per UX brief. */}
<div className="mb-4 flex items-end gap-2 rounded-lg border border-dashed border-border/40 bg-surface/30 p-3">
<Select
label={t("systemTransformsAddProvider")}
value={newProviderId}
disabled={loading || availableProvidersToAdd.length === 0}
onChange={(e) => setNewProviderId(e.target.value)}
className="flex-1"
>
<option value="">
{availableProvidersToAdd.length === 0
? t("systemTransformsAddProviderAllConfigured")
: t("systemTransformsAddProviderPlaceholder")}
</option>
{availableProvidersToAdd.map((p) => (
<option key={p.id} value={p.id}>
{p.name} ({p.id})
</option>
))}
</Select>
<Button
variant="secondary"
size="sm"
icon="add"
disabled={loading || !newProviderId || !!systemTransforms.providers[newProviderId]}
onClick={addProvider}
>
{t("systemTransformsAddProvider")}
</Button>
</div>
{Object.keys(systemTransforms.providers).length === 0 && (
<p className="text-sm text-text-muted py-2">{t("systemTransformsNoProviders")}</p>
)}
<div className="flex flex-col gap-3">
{Object.entries(systemTransforms.providers).map(([providerId, providerCfg]) => {
const isBuiltin = BUILTIN_PROVIDERS.has(providerId);
const display = PROVIDER_TILE_DISPLAY[providerId] ?? {
name: providerId,
description: "Custom provider.",
icon: "extension",
tone: "purple",
};
const draft = jsonDrafts[providerId] ?? JSON.stringify(providerCfg, null, 2);
const errorMsg = jsonErrors[providerId] ?? null;
const opCount = Array.isArray(providerCfg.pipeline) ? providerCfg.pipeline.length : 0;
const hasDefault = Boolean(
(DEFAULT_SYSTEM_TRANSFORMS_CLIENT.providers as Record<string, unknown>)[providerId]
);
const isJsonOpen = showJsonEditor[providerId] ?? false;
const enabled = providerCfg.enabled !== false;
const selectedKind =
(addOpKind[providerId] as TransformOpKind | undefined) ??
"drop_paragraph_if_contains";
return (
<Collapsible
key={providerId}
defaultOpen={false}
title={
<div className="flex items-center gap-2 flex-wrap">
<code className="text-xs font-mono rounded bg-surface px-1.5 py-0.5">
{providerId}
</code>
<span className="text-sm font-medium">{display.name}</span>
</div>
}
subtitle={
t("routingOpSummaryCount", { count: opCount }) +
t("routingOpStatusSeparator") +
(enabled ? t("routingOpEnabled") : t("routingOpDisabled"))
}
trailing={
<>
<Toggle
checked={enabled}
onChange={(checked) => toggleProviderEnabled(providerId, checked)}
disabled={loading}
ariaLabel={
tCommon("enable") + " " + display.name + " " + t("systemTransforms")
}
/>
{!isBuiltin && (
<Button
variant="ghost"
size="sm"
icon="delete"
disabled={loading}
aria-label={t("systemTransformsRemoveProvider")}
title={t("systemTransformsRemoveProvider")}
onClick={() => removeProvider(providerId)}
/>
)}
</>
}
>
<p className="text-xs text-text-muted mb-3">{display.description}</p>
{providerSaveErrors[providerId] && (
<div
role="alert"
className="mb-3 rounded border border-red-500/40 bg-red-500/10 p-2 text-xs text-red-300"
>
<span className="font-medium">{t("routingServerRejectedSave")}</span>{" "}
<span className="break-words font-mono">{providerSaveErrors[providerId]}</span>
<p className="mt-1 text-[11px] text-red-200/80">{tCommon("error")}</p>
</div>
)}
{/* Pipeline op list — each op is itself collapsible. */}
{opCount > 0 && (
<ol className="flex flex-col gap-2 mb-3">
{(providerCfg.pipeline as any[]).map((op, index) => (
<li key={index}>
<Collapsible
variant="inline"
defaultOpen={false}
title={
<div className="flex items-center gap-2">
<span className="flex h-5 w-5 shrink-0 items-center justify-center rounded-full bg-purple-500/10 text-[10px] font-semibold text-purple-400">
{index + 1}
</span>
<span className="font-mono text-purple-300 text-xs">
{t(OP_KIND_LABELS[op?.kind as TransformOpKind] || op?.kind)}
</span>
</div>
}
subtitle={summarizeTransformOp(op, t)}
trailing={
<>
<Button
variant="ghost"
size="sm"
icon="keyboard_arrow_up"
disabled={loading || index === 0}
aria-label={t("systemTransformsOpMoveUp")}
title={t("systemTransformsOpMoveUp")}
onClick={() => moveOp(providerId, index, -1)}
/>
<Button
variant="ghost"
size="sm"
icon="keyboard_arrow_down"
disabled={loading || index === opCount - 1}
aria-label={t("systemTransformsOpMoveDown")}
title={t("systemTransformsOpMoveDown")}
onClick={() => moveOp(providerId, index, 1)}
/>
<Button
variant="ghost"
size="sm"
icon="delete"
disabled={loading}
aria-label={t("systemTransformsOpDelete")}
title={t("systemTransformsOpDelete")}
onClick={() => deleteOp(providerId, index)}
/>
</>
}
>
<OpEditor
op={op}
disabled={loading}
onChange={(next) => updateOp(providerId, index, next)}
/>
</Collapsible>
</li>
))}
</ol>
)}
{/* Add op row */}
<div className="flex items-end gap-2 mb-3">
<Select
label={t("routingAddTransformOp")}
className="flex-1"
value={selectedKind}
onChange={(e) =>
setAddOpKind((prev) => ({
...prev,
[providerId]: e.target.value as TransformOpKind,
}))
}
disabled={loading}
options={(Object.keys(OP_KIND_LABELS) as TransformOpKind[]).map((kind) => ({
value: kind,
label: t(OP_KIND_LABELS[kind]),
}))}
/>
<Button
onClick={() => addOp(providerId)}
disabled={loading}
variant="secondary"
size="sm"
icon="add"
>
{tCommon("add")}
</Button>
</div>
{/* JSON import section (collapsible) */}
<div className="border-t border-border/20 pt-2 mt-2">
<button
type="button"
onClick={() =>
setShowJsonEditor((prev) => ({ ...prev, [providerId]: !isJsonOpen }))
}
className="text-[11px] text-primary hover:underline"
>
{isJsonOpen
? "▾ " + tCommon("hide") + " JSON editor"
: "▸ Import / export JSON"}
</button>
{isJsonOpen && (
<div className="mt-2">
<label className="text-[11px] font-medium text-text-muted block mb-1">
JSON ({tCommon("edit")} &amp; Apply, or paste to import)
</label>
<textarea
value={draft}
onChange={(e) =>
setJsonDrafts((prev) => ({ ...prev, [providerId]: e.target.value }))
}
rows={Math.min(40, Math.max(6, draft.split("\n").length))}
disabled={loading}
spellCheck={false}
className="w-full rounded border border-border/50 bg-background/40 p-2 font-mono text-[11px] text-text resize-y"
/>
{errorMsg && (
<p className="mt-1 text-xs text-red-400 break-words">⚠ {errorMsg}</p>
)}
<div className="flex flex-wrap items-center gap-2 mt-2">
<Button
onClick={() => applyProviderJson(providerId)}
disabled={loading}
variant="secondary"
size="sm"
icon="check"
>
Apply JSON
</Button>
{hasDefault && (
<Button
onClick={() => resetProviderTransforms(providerId)}
disabled={loading}
variant="ghost"
size="sm"
icon="restart_alt"
>
{tCommon("reset")}
</Button>
)}
</div>
</div>
)}
</div>
</Collapsible>
);
})}
</div>
<p className="mt-3 text-[11px] text-text-muted">
All transform ops are idempotent on re-run. Changes take effect immediately on the next
request.
</p>
</Card>
<Card>
<div className="flex items-start gap-3 mb-4">
<div className="p-2 rounded-lg bg-indigo-500/10 text-indigo-500 h-fit">
<span className="material-symbols-outlined text-[20px]" aria-hidden="true">
security
</span>
</div>
<div>
<h3 className="text-lg font-semibold">{t("cliFingerprint")}</h3>
<p className="text-sm text-text-muted mt-1">{t("cliFingerprintDesc")}</p>
</div>
</div>
<div className="mb-5">
<h4 className="text-sm font-semibold mb-2">{t("routingHeaderFingerprintTitle")}</h4>
<p className="text-xs text-text-muted mb-2">
{t("cliFingerprintEnabled", { count: cliCompatProviderSet.size })}
</p>
<div className="grid gap-3 md:grid-cols-2">
{CLI_COMPAT_TOGGLE_IDS.map((providerId) => {
const normalizedProviderId = normalizeCliCompatProviderId(providerId);
const providerDisplay = CLI_COMPAT_PROVIDER_DISPLAY[providerId];
const checked = cliCompatProviderSet.has(normalizedProviderId);
const label = providerDisplay?.name || providerId;
const description = providerDisplay?.description || providerId;
const titleText = checked
? t("disableFingerprintTitle", { provider: label })
: t("enableFingerprintTitle", { provider: label });
return (
<button
key={providerId}
type="button"
onClick={() => toggleCliCompatProvider(providerId, !checked)}
disabled={loading}
aria-pressed={checked}
title={titleText}
className={`flex items-start gap-3 rounded-lg border p-3 text-left transition-all ${
checked
? "border-indigo-500/50 bg-indigo-500/5 ring-1 ring-indigo-500/20"
: "border-border/50 hover:border-border hover:bg-surface/30"
} ${loading ? "cursor-not-allowed opacity-60" : ""}`}
>
<span
className={`material-symbols-outlined mt-0.5 text-[18px] ${checked ? "text-indigo-400" : "text-text-muted"}`}
aria-hidden="true"
>
{checked ? "check_circle" : "radio_button_unchecked"}
</span>
<span className="min-w-0 flex-1">
<span
className={`block text-sm font-medium ${checked ? "text-indigo-400" : ""}`}
>
{label}
</span>
<span className="mt-1 block text-xs text-text-muted">{description}</span>
</span>
</button>
);
})}
</div>
</div>
</Card>
<Card>
<div className="flex items-center gap-3 mb-4">
<div className="p-2 rounded-lg bg-green-500/10 text-green-500">
<span className="material-symbols-outlined text-[20px]" aria-hidden="true">
cached
</span>
</div>
<div>
<h3 className="text-lg font-semibold">{t("routingClientCacheControlTitle")}</h3>
<p className="text-sm text-text-muted">{t("routingClientCacheControlDesc")}</p>
</div>
</div>
<div className="space-y-3">
{[
{
value: "auto",
label: tCommon("auto") + " (" + tCommon("recommended") + ")",
desc: t("routingClientCacheControlAutoDesc"),
},
{
value: "always",
label: t("routingClientCacheControlAlwaysLabel"),
desc: t("routingClientCacheControlAlwaysDesc"),
},
{
value: "never",
label: t("routingClientCacheControlNeverLabel"),
desc: t("routingClientCacheControlNeverDesc"),
},
].map((option) => (
<button
key={option.value}
onClick={() => updateSetting({ alwaysPreserveClientCache: option.value })}
disabled={loading}
className={`w-full flex flex-col items-start gap-1 p-3 rounded-lg border text-left transition-all ${
settings.alwaysPreserveClientCache === option.value
? "border-green-500/50 bg-green-500/5 ring-1 ring-green-500/20"
: "border-border/50 hover:border-border hover:bg-surface/30"
}`}
>
<div className="flex items-center gap-2">
<span
className={`material-symbols-outlined text-[16px] ${
settings.alwaysPreserveClientCache === option.value
? "text-green-400"
: "text-text-muted"
}`}
>
{settings.alwaysPreserveClientCache === option.value
? "check_circle"
: "radio_button_unchecked"}
</span>
<span
className={`text-sm font-medium ${settings.alwaysPreserveClientCache === option.value ? "text-green-400" : ""}`}
>
{option.label}
</span>
</div>
<p className="text-xs text-text-muted ml-7">{option.desc}</p>
</button>
))}
</div>
</Card>
<Card>
<div className="flex items-center gap-3 mb-4">
<div className="p-2 rounded-lg bg-sky-500/10 text-sky-500">
<span className="material-symbols-outlined text-[20px]" aria-hidden="true">
fingerprint
</span>
</div>
<div>
<h3 className="text-lg font-semibold">{t("routingAntigravitySignatureTitle")}</h3>
<p className="text-sm text-text-muted">{t("routingAntigravitySignatureDesc")}</p>
</div>
</div>
<div className="space-y-3">
{[
{
value: "enabled",
label: t("routingAntigravitySignatureEnabledLabel"),
desc: t("routingAntigravitySignatureEnabledDesc"),
},
{
value: "bypass",
label: t("routingAntigravitySignatureBypassLabel"),
desc: t("routingAntigravitySignatureBypassDesc"),
},
{
value: "bypass-strict",
label: t("routingAntigravitySignatureBypassStrictLabel"),
desc: t("routingAntigravitySignatureBypassStrictDesc"),
},
].map((option) => (
<button
key={option.value}
onClick={() => updateSetting({ antigravitySignatureCacheMode: option.value })}
disabled={loading}
className={`w-full flex flex-col items-start gap-1 p-3 rounded-lg border text-left transition-all ${
settings.antigravitySignatureCacheMode === option.value
? "border-sky-500/50 bg-sky-500/5 ring-1 ring-sky-500/20"
: "border-border/50 hover:border-border hover:bg-surface/30"
}`}
>
<div className="flex items-center gap-2">
<span
className={`material-symbols-outlined text-[16px] ${
settings.antigravitySignatureCacheMode === option.value
? "text-sky-400"
: "text-text-muted"
}`}
>
{settings.antigravitySignatureCacheMode === option.value
? "check_circle"
: "radio_button_unchecked"}
</span>
<span
className={`text-sm font-medium ${settings.antigravitySignatureCacheMode === option.value ? "text-sky-400" : ""}`}
>
{option.label}
</span>
</div>
<p className="text-xs text-text-muted ml-7">{option.desc}</p>
</button>
))}
</div>
</Card>
<Card>
<div className="flex items-start justify-between gap-4">
<div className="flex gap-3">
<div className="p-2 rounded-lg bg-sky-500/10 text-sky-500 h-fit">
<span className="material-symbols-outlined text-[20px]" aria-hidden="true">
badge
</span>
</div>
<div>
<h3 className="text-lg font-semibold">
{t("echoRequestedModelTitle") || "Echo requested model name in responses"}
</h3>
<p className="text-sm text-text-muted mt-1">
{t("echoRequestedModelDesc") ||
"When enabled, the response model field echoes the alias or combo name the client requested instead of the upstream model name."}
</p>
</div>
</div>
<div className="pt-1">
<Toggle
checked={settings.echoRequestedModelName === true}
onChange={(checked) => updateSetting({ echoRequestedModelName: checked })}
disabled={loading}
ariaLabel={t("echoRequestedModelTitle")}
/>
</div>
</div>
</Card>
{/* #4481 layer 2 — Web-Search Routing (CCR-style Router.webSearch) */}
<Card>
<div className="flex gap-3">
<div className="p-2 rounded-lg bg-emerald-500/10 text-emerald-500 h-fit">
<span className="material-symbols-outlined text-[20px]" aria-hidden="true">
travel_explore
</span>
</div>
<div className="flex-1">
<h3 className="text-lg font-semibold">
{t("webSearchRouteTitle") || "Web search routing"}
</h3>
<p className="text-sm text-text-muted mt-1">
{t("webSearchRouteDesc") ||
"When a request includes a native web_search tool, route the whole request to this model instead of the default — useful for providers that don't implement Anthropic's web_search server tool. Leave blank to disable."}
</p>
<div className="mt-3">
<Input
value={
typeof settings.webSearchRouteModel === "string"
? settings.webSearchRouteModel
: ""
}
onChange={(e) => updateSetting({ webSearchRouteModel: e.target.value })}
placeholder={
t("webSearchRoutePlaceholder") || "e.g. openrouter,anthropic/claude-3.5-sonnet"
}
disabled={loading}
aria-label={t("webSearchRouteTitle") || "Web search routing model"}
/>
</div>
</div>
</div>
</Card>
<Card>
<div className="flex items-start justify-between gap-4">
<div className="flex gap-3">
<div className="p-2 rounded-lg bg-amber-500/10 text-amber-500 h-fit">
<span className="material-symbols-outlined text-[20px]" aria-hidden="true">
verified
</span>
</div>
<div>
<h3 className="text-lg font-semibold">
{t("lkgpToggleTitle") || "Last Known Good Provider (LKGP)"}
</h3>
<p className="text-sm text-text-muted mt-1">
{t("lkgpToggleDesc") ||
"When enabled, the router remembers which provider last served a successful response and tries it first on subsequent requests."}
</p>
</div>
</div>
<div className="pt-1">
<Toggle
checked={settings.lkgpEnabled !== false}
onChange={(checked) => updateSetting({ lkgpEnabled: checked })}
disabled={loading}
ariaLabel={t("lkgpToggleTitle")}
/>
</div>
</div>
<div className="mt-3 pt-3 border-t border-border/30 flex items-center gap-2">
<Button
variant="outline"
size="sm"
loading={lkgpCacheLoading}
onClick={async () => {
setLkgpCacheLoading(true);
setLkgpCacheStatus({ type: "", message: "" });
try {
const res = await fetch("/api/settings/lkgp-cache", { method: "DELETE" });
const data = await res.json();
if (res.ok) {
setLkgpCacheStatus({
type: "success",
message: t("lkgpCacheCleared") || "LKGP cache cleared successfully",
});
} else {
setLkgpCacheStatus({
type: "error",
message:
data.error || t("lkgpCacheClearFailed") || "Failed to clear LKGP cache",
});
}
} catch {
setLkgpCacheStatus({
type: "error",
message: t("errorOccurred") || "An error occurred",
});
} finally {
setLkgpCacheLoading(false);
}
}}
>
<span className="material-symbols-outlined text-[14px] mr-1" aria-hidden="true">
delete_sweep
</span>
{t("clearLkgpCache") || "Clear LKGP Cache"}
</Button>
{lkgpCacheStatus.message && (
<span
className={`text-xs ${lkgpCacheStatus.type === "success" ? "text-green-500" : "text-red-500"}`}
>
{lkgpCacheStatus.message}
</span>
)}
</div>
</Card>
<Card>
<div className="flex items-start justify-between gap-4">
<div className="flex gap-3">
<div className="p-2 rounded-lg bg-emerald-500/10 text-emerald-500 h-fit">
<span className="material-symbols-outlined text-[20px]" aria-hidden="true">
network_ping
</span>
</div>
<div>
<h3 className="text-lg font-semibold">
{t("adaptiveVolumeRouting") || "Adaptive Volume Routing"}
</h3>
<p className="text-sm text-text-muted mt-1">
{t("adaptiveVolumeRoutingDesc") ||
"Automatically adjusts traffic volume between providers based on real-time latency and error rates."}
</p>
</div>
</div>
<div className="pt-1">
<Toggle
checked={!!settings.adaptiveVolumeRouting}
onChange={(checked) => updateSetting({ adaptiveVolumeRouting: checked })}
disabled={loading}
ariaLabel={t("adaptiveVolumeRouting")}
/>
</div>
</div>
</Card>
</div>
);
}