Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -133,9 +133,9 @@ export default function AutoRoutingAnalyticsTab() {
<table className="w-full text-sm">
<thead>
<tr className="border-b border-border">
<th className="text-left py-2 px-3 font-medium">Provider</th>
<th className="text-right py-2 px-3 font-medium">Requests</th>
<th className="text-right py-2 px-3 font-medium">Share</th>
<th className="text-left py-2 px-3 font-medium">{t("chartProvider")}</th>
<th className="text-right py-2 px-3 font-medium">{t("chartRequests")}</th>
<th className="text-right py-2 px-3 font-medium">{t("chartShare")}</th>
</tr>
</thead>
<tbody>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -189,8 +189,8 @@ export default function SearchAnalyticsTab() {
check_circle
</span>
<span>
<strong>Free tier available:</strong> Serper (2,500/mo), Brave (2,000/mo), Exa (1,000/mo),
Tavily (1,000/mo) — total 6,500+ free searches/month with automatic failover.
<strong>{t("freeTierAvailable")}:</strong> Serper (2,500/mo), Brave (2,000/mo), Exa
(1,000/mo), Tavily (1,000/mo) — total 6,500+ free searches/month with automatic failover.
</span>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ export default function ComboPlaygroundClient() {
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{/* Combo Selector */}
<div>
<label className="block text-sm font-medium mb-1">Combo</label>
<label className="block text-sm font-medium mb-1">{t("comboLabel")}</label>
<select
className="w-full border border-border rounded-lg px-3 py-2 bg-surface text-sm"
value={selectedComboId}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ function PageHeader() {
return (
<div className="flex flex-col gap-2">
<div className="flex items-center gap-3">
<h1 className="text-2xl font-semibold">OmniGlyph</h1>
<h1 className="text-2xl font-semibold">{t("title")}</h1>
<span className="rounded-full border border-amber-500/40 bg-amber-500/10 px-2 py-0.5 text-xs font-medium text-amber-600 dark:text-amber-400">
{t("preview")}
</span>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,8 +139,7 @@ export default function MediaProviderPageClient({
<div className="text-xs text-text-muted border border-border rounded-lg p-3 flex items-start gap-2">
<span className="material-symbols-outlined text-[16px] text-blue-500 mt-0.5">info</span>
<span>
<strong>Existing connection:</strong> OpenRouter speech-to-text models use your
configured OpenRouter connection. No separate credential is required.
<strong>{t("existingConnection")}:</strong> {t("openrouterSttDescription")}
</span>
</div>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export default function QdrantConfigCard() {
const [saving, setSaving] = useState(false);
const [saveStatus, setSaveStatus] = useState<"" | "saved" | "error">("");
const [health, setHealth] = useState<{ ok: boolean; latencyMs: number; error?: string } | null>(
null,
null
);
const [checking, setChecking] = useState(false);
const [searchQuery, setSearchQuery] = useState("");
Expand Down Expand Up @@ -103,7 +103,7 @@ export default function QdrantConfigCard() {
setSaving(false);
}
},
[qdrant],
[qdrant]
);

const checkHealth = useCallback(async () => {
Expand Down Expand Up @@ -185,11 +185,7 @@ export default function QdrantConfigCard() {
</div>
<span
className={`inline-flex items-center gap-1.5 text-xs font-medium ${
qdrant.enabled
? health?.ok
? "text-emerald-500"
: "text-red-500"
: "text-text-muted"
qdrant.enabled ? (health?.ok ? "text-emerald-500" : "text-red-500") : "text-text-muted"
}`}
>
<span
Expand Down Expand Up @@ -269,7 +265,7 @@ export default function QdrantConfigCard() {

<div className="grid grid-cols-1 md:grid-cols-2 gap-3 mb-4">
<div className="p-3 rounded-lg bg-surface/30 border border-border/30">
<label className="text-xs font-medium block mb-1.5">Host</label>
<label className="text-xs font-medium block mb-1.5">{t("qdrant.hostLabel")}</label>
<input
value={qdrant.host}
onChange={(e) => setQdrant((s) => ({ ...s, host: e.target.value }))}
Expand All @@ -284,14 +280,17 @@ export default function QdrantConfigCard() {
value={qdrant.port}
type="number"
onChange={(e) =>
setQdrant((s) => ({ ...s, port: Math.max(1, Math.min(65535, Number(e.target.value) || 1)) }))
setQdrant((s) => ({
...s,
port: Math.max(1, Math.min(65535, Number(e.target.value) || 1)),
}))
}
placeholder="6333"
className="w-full px-3 py-2 rounded-lg bg-background border border-border text-sm font-mono focus:outline-none focus:ring-1 focus:ring-emerald-500"
/>
</div>
<div className="p-3 rounded-lg bg-surface/30 border border-border/30">
<label className="text-xs font-medium block mb-1.5">Collection</label>
<label className="text-xs font-medium block mb-1.5">{t("qdrant.collectionLabel")}</label>
<input
value={qdrant.collection}
onChange={(e) => setQdrant((s) => ({ ...s, collection: e.target.value }))}
Expand Down Expand Up @@ -355,9 +354,7 @@ export default function QdrantConfigCard() {
</button>
)}
<button
onClick={() =>
save(apiKeyInput.trim() ? { apiKey: apiKeyInput } : {})
}
onClick={() => save(apiKeyInput.trim() ? { apiKey: apiKeyInput } : {})}
disabled={saving}
className="px-4 py-2 text-sm font-medium rounded-lg bg-emerald-500 text-white hover:bg-emerald-600 disabled:opacity-50"
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,24 @@
*/
"use client";

import type { ReactNode } from "react";
import { useState } from "react";
import { useTranslations } from "next-intl";
import { Card, Toggle } from "@/shared/components";
import { useServiceStatus } from "../hooks/useServiceStatus";

const NAME = "cliproxy";

function renderPrefix(chunks: ReactNode) {
return <code className="font-mono bg-bg-subtle px-1 rounded">{chunks}</code>;
}

function renderSmallPrefix(chunks: ReactNode) {
return <code className="font-mono bg-bg-subtle px-1 rounded text-xs">{chunks}</code>;
}

export function CliproxyProviderExposureCard() {
const t = useTranslations("cliproxyProviderExposure");
const { data, mutate } = useServiceStatus(NAME);
const [pending, setPending] = useState(false);
const [msg, setMsg] = useState<{ ok: boolean; text: string } | null>(null);
Expand All @@ -28,14 +39,14 @@ export function CliproxyProviderExposureCard() {
if (!res.ok) {
const body = await res.json().catch(() => ({}));
const errorMsg =
body?.error?.message ?? body?.message ?? `Failed to update (HTTP ${res.status})`;
body?.error?.message ?? body?.message ?? t("updateFailed", { status: res.status });
setMsg({ ok: false, text: errorMsg });
return;
}
setMsg(null);
mutate();
} catch {
setMsg({ ok: false, text: "Network error — could not update provider exposure setting" });
setMsg({ ok: false, text: t("networkError") });
} finally {
setPending(false);
}
Expand All @@ -48,10 +59,9 @@ export function CliproxyProviderExposureCard() {
<span className="material-symbols-outlined text-sky-500 text-xl">hub</span>
</div>
<div>
<h3 className="font-medium text-sm">Provider Exposure</h3>
<h3 className="font-medium text-sm">{t("title")}</h3>
<p className="text-xs text-text-muted">
Expose CLIProxyAPI models as a routing target under the{" "}
<code className="font-mono bg-bg-subtle px-1 rounded">cliproxyapi/</code> prefix.
{t.rich("description", { prefix: renderPrefix })}
</p>
</div>
</div>
Expand All @@ -73,17 +83,15 @@ export function CliproxyProviderExposureCard() {

<div className="flex items-center justify-between">
<div>
<p className="text-sm">
Expose as{" "}
<code className="font-mono bg-bg-subtle px-1 rounded text-xs">cliproxyapi/...</code>
</p>
<p className="text-xs text-text-muted mt-0.5">
When enabled, discovered models appear in provider selects across OmniRoute.
</p>
<p className="text-sm">{t.rich("label", { prefix: renderSmallPrefix })}</p>
<p className="text-xs text-text-muted mt-0.5">{t("hint")}</p>
</div>
<Toggle checked={data?.providerExpose ?? false} onChange={handleToggle} disabled={pending || !data} />
<Toggle
checked={data?.providerExpose ?? false}
onChange={handleToggle}
disabled={pending || !data}
/>
</div>
</Card>
);
}

Original file line number Diff line number Diff line change
Expand Up @@ -233,31 +233,31 @@ export default function CliproxyapiSettingsTab() {
<span className="material-symbols-outlined animate-spin text-base">
progress_activity
</span>
Loading...
{t("loading")}
</div>
) : toolStateError ? (
<p className="text-sm text-text-muted">{toolStateError}</p>
) : toolState ? (
<div className="grid grid-cols-2 gap-3">
<div className="p-3 rounded-lg bg-bg-secondary">
<p className="text-xs text-text-muted mb-1">Status</p>
<p className="text-xs text-text-muted mb-1">{t("cliproxyapiStatusLabel")}</p>
<div className="flex items-center gap-1.5">
<span className={`material-symbols-outlined text-sm ${statusColor}`}>
{statusIcon}
</span>
<p className={`text-sm font-medium capitalize ${statusColor}`}>
{toolState.status?.replace("_", " ") || "Unknown"}
{toolState.status?.replace("_", " ") || t("cliproxyapiUnknown")}
</p>
</div>
</div>
<div className="p-3 rounded-lg bg-bg-secondary">
<p className="text-xs text-text-muted mb-1">Version</p>
<p className="text-xs text-text-muted mb-1">{t("cliproxyapiVersion")}</p>
<p className="text-sm font-medium">
{toolState.installedVersion ? `v${toolState.installedVersion}` : "Not installed"}
</p>
</div>
<div className="p-3 rounded-lg bg-bg-secondary">
<p className="text-xs text-text-muted mb-1">Health</p>
<p className="text-xs text-text-muted mb-1">{t("cliproxyapiHealth")}</p>
<p
className={`text-sm font-medium ${
toolState.healthStatus === "healthy"
Expand All @@ -275,7 +275,7 @@ export default function CliproxyapiSettingsTab() {
</p>
</div>
<div className="p-3 rounded-lg bg-bg-secondary">
<p className="text-xs text-text-muted mb-1">Port</p>
<p className="text-xs text-text-muted mb-1">{t("cliproxyapiPort")}</p>
<p className="text-sm font-mono">{toolState.port || 8317}</p>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -369,7 +369,10 @@ export default function MemorySkillsTab() {
role="note"
data-testid="memory-token-cost-warning"
>
<span className="material-symbols-outlined text-[18px] leading-none mt-0.5" aria-hidden="true">
<span
className="material-symbols-outlined text-[18px] leading-none mt-0.5"
aria-hidden="true"
>
info
</span>
<p className="text-xs leading-relaxed">
Expand Down Expand Up @@ -540,7 +543,7 @@ export default function MemorySkillsTab() {

<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
<div className="p-4 rounded-lg bg-surface/30 border border-border/30">
<label className="text-sm font-medium block mb-2">Host</label>
<label className="text-sm font-medium block mb-2">{t("qdrantHost")}</label>
<input
value={qdrant.host}
onChange={(e) => setQdrant((s) => ({ ...s, host: e.target.value }))}
Expand All @@ -567,7 +570,7 @@ export default function MemorySkillsTab() {
</div>

<div className="p-4 rounded-lg bg-surface/30 border border-border/30">
<label className="text-sm font-medium block mb-2">Collection</label>
<label className="text-sm font-medium block mb-2">{t("qdrantCollection")}</label>
<input
value={qdrant.collection}
onChange={(e) => setQdrant((s) => ({ ...s, collection: e.target.value }))}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ export default function OneproxyTab() {
</Card>
<Card className="p-4">
<div className="text-2xl font-bold text-green-600">{stats.active}</div>
<div className="text-sm text-text-muted">Active</div>
<div className="text-sm text-text-muted">{t("oneproxyActive")}</div>
</Card>
<Card className="p-4">
<div className="text-2xl font-bold text-text-main">
Expand Down Expand Up @@ -238,14 +238,28 @@ export default function OneproxyTab() {
<table className="w-full text-sm">
<thead>
<tr className="border-b border-border">
<th className="text-left py-2 px-3 text-text-muted font-medium">Host</th>
<th className="text-left py-2 px-3 text-text-muted font-medium">Protocol</th>
<th className="text-left py-2 px-3 text-text-muted font-medium">Country</th>
<th className="text-left py-2 px-3 text-text-muted font-medium">Quality</th>
<th className="text-left py-2 px-3 text-text-muted font-medium">Latency</th>
<th className="text-left py-2 px-3 text-text-muted font-medium">Anonymity</th>
<th className="text-left py-2 px-3 text-text-muted font-medium">
{t("oneproxyHost")}
</th>
<th className="text-left py-2 px-3 text-text-muted font-medium">
{t("oneproxyProtocol")}
</th>
<th className="text-left py-2 px-3 text-text-muted font-medium">
{t("oneproxyCountry")}
</th>
<th className="text-left py-2 px-3 text-text-muted font-medium">
{t("oneproxyQuality")}
</th>
<th className="text-left py-2 px-3 text-text-muted font-medium">
{t("oneproxyLatency")}
</th>
<th className="text-left py-2 px-3 text-text-muted font-medium">
{t("oneproxyAnonymity")}
</th>
<th className="text-left py-2 px-3 text-text-muted font-medium">Google</th>
<th className="text-left py-2 px-3 text-text-muted font-medium">Actions</th>
<th className="text-left py-2 px-3 text-text-muted font-medium">
{t("oneproxyActions")}
</th>
</tr>
</thead>
<tbody>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,10 @@ function filterRows(
if (providerFilter !== "all") out = out.filter((m) => m.provider === providerFilter);
if (search.trim()) {
out = out.filter(
(m) => matchesSearch(m.displayName, search) || matchesSearch(m.modelId, search) || matchesSearch(m.provider, search)
(m) =>
matchesSearch(m.displayName, search) ||
matchesSearch(m.modelId, search) ||
matchesSearch(m.provider, search)
);
}
return out;
Expand Down Expand Up @@ -421,11 +424,14 @@ export function FreeBudgetView({
className="mx-3 mt-2 rounded-md border border-emerald-500/30 bg-emerald-500/5 px-3 py-2"
>
<div className="flex items-center gap-1.5">
<span className="material-symbols-outlined text-[14px] text-emerald-500">lock_open</span>
<span className="text-[11px] font-semibold text-emerald-500">No API key required</span>
<span className="material-symbols-outlined text-[14px] text-emerald-500">
lock_open
</span>
<span className="text-[11px] font-semibold text-emerald-500">
{t("noApiKeyRequired")}
</span>
<span className="text-[10.5px] text-text-muted">
({keylessModels.length} model{keylessModels.length !== 1 ? "s" : ""} · {keylessProviders.length}{" "}
provider{keylessProviders.length !== 1 ? "s" : ""})
({keylessModels.length}个模型 · {keylessProviders.length}个提供者)
</span>
</div>
<div className="mt-1 flex flex-wrap gap-1">
Expand Down Expand Up @@ -606,7 +612,7 @@ export default function FreeBudgetCard() {
data-testid="budget-provider-select"
className="rounded border border-border bg-surface px-1.5 py-1 text-[11px] text-text-main"
>
<option value="all">All providers</option>
<option value="all">{t("allProviders")}</option>
{providers.map((p) => (
<option key={p} value={p}>
{p}
Expand Down
Loading