From 3218563f32f39476a8c998d649aefda3a4e87de6 Mon Sep 17 00:00:00 2001 From: diegosouzapw Date: Tue, 24 Mar 2026 13:32:05 -0300 Subject: [PATCH] chore: merge PRs #581, #582 + local improvements for rc16 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Merged PRs: - #582 — model prefix stripping option (closes #568) - #581 — npm publish workflow fix (refs #579) Local changes: - Restored stashed i18n, CLI tools, and maintenance banner updates - 926 tests passing --- docs/API_REFERENCE.md | 12 +-- docs/ARCHITECTURE.md | 22 ++++- docs/CLI-TOOLS.md | 73 +++++++------- eslint.config.mjs | 1 + package.json | 2 +- scripts/build-next-isolated.mjs | 84 +++++++++++++++++ src/app/(dashboard)/dashboard/agents/page.tsx | 66 +++++++++---- .../cli-tools/CLIToolsPageClient.tsx | 77 ++++++++++++++- src/app/(dashboard)/dashboard/combos/page.tsx | 23 ++--- src/app/(dashboard)/dashboard/health/page.tsx | 36 ++++++- .../search-tools/SearchToolsClient.tsx | 4 +- .../search-tools/components/SearchForm.tsx | 37 ++++---- .../search-tools/components/SearchHistory.tsx | 11 ++- .../settings/components/ComboDefaultsTab.tsx | 23 +++-- .../settings/components/RoutingTab.tsx | 55 +++++------ .../(dashboard)/dashboard/settings/page.tsx | 2 +- src/app/api/monitoring/health/route.ts | 15 ++- src/i18n/messages/ar.json | 74 +++++++++++++-- src/i18n/messages/bg.json | 74 +++++++++++++-- src/i18n/messages/cs.json | 74 +++++++++++++-- src/i18n/messages/da.json | 74 +++++++++++++-- src/i18n/messages/de.json | 74 +++++++++++++-- src/i18n/messages/en.json | 57 ++++++++++- src/i18n/messages/es.json | 74 +++++++++++++-- src/i18n/messages/fi.json | 74 +++++++++++++-- src/i18n/messages/fr.json | 74 +++++++++++++-- src/i18n/messages/he.json | 74 +++++++++++++-- src/i18n/messages/hu.json | 74 +++++++++++++-- src/i18n/messages/id.json | 74 +++++++++++++-- src/i18n/messages/in.json | 74 +++++++++++++-- src/i18n/messages/it.json | 74 +++++++++++++-- src/i18n/messages/ja.json | 74 +++++++++++++-- src/i18n/messages/ko.json | 74 +++++++++++++-- src/i18n/messages/ms.json | 74 +++++++++++++-- src/i18n/messages/nl.json | 74 +++++++++++++-- src/i18n/messages/no.json | 74 +++++++++++++-- src/i18n/messages/phi.json | 74 +++++++++++++-- src/i18n/messages/pl.json | 74 +++++++++++++-- src/i18n/messages/pt-BR.json | 74 +++++++++++++-- src/i18n/messages/pt.json | 74 +++++++++++++-- src/i18n/messages/ro.json | 74 +++++++++++++-- src/i18n/messages/ru.json | 74 +++++++++++++-- src/i18n/messages/sk.json | 74 +++++++++++++-- src/i18n/messages/sv.json | 74 +++++++++++++-- src/i18n/messages/th.json | 74 +++++++++++++-- src/i18n/messages/uk-UA.json | 74 +++++++++++++-- src/i18n/messages/vi.json | 74 +++++++++++++-- src/i18n/messages/zh-CN.json | 65 +++++++++++-- src/shared/components/MaintenanceBanner.tsx | 38 ++++++-- src/shared/constants/cliCompatProviders.ts | 30 ++++++ src/shared/constants/cliTools.ts | 12 +++ src/shared/constants/routingStrategies.ts | 94 +++++++++++++++++++ 52 files changed, 2613 insertions(+), 372 deletions(-) create mode 100644 scripts/build-next-isolated.mjs create mode 100644 src/shared/constants/cliCompatProviders.ts create mode 100644 src/shared/constants/routingStrategies.ts diff --git a/docs/API_REFERENCE.md b/docs/API_REFERENCE.md index a0186e804f..77ff35d592 100644 --- a/docs/API_REFERENCE.md +++ b/docs/API_REFERENCE.md @@ -142,10 +142,10 @@ The provider prefix is auto-added if missing. Mismatched models return `400`. ```bash # Get cache stats -GET /api/cache +GET /api/cache/stats # Clear all caches -DELETE /api/cache +DELETE /api/cache/stats ``` Response example: @@ -218,7 +218,7 @@ Response example: | Endpoint | Method | Description | | ------------------------------- | ------- | ---------------------- | -| `/api/settings` | GET/PUT | General settings | +| `/api/settings` | GET/PUT/PATCH | General settings | | `/api/settings/proxy` | GET/PUT | Network proxy config | | `/api/settings/proxy/test` | POST | Test proxy connection | | `/api/settings/ip-filter` | GET/PUT | IP allowlist/blocklist | @@ -231,8 +231,8 @@ Response example: | ------------------------ | ---------- | ----------------------- | | `/api/sessions` | GET | Active session tracking | | `/api/rate-limits` | GET | Per-account rate limits | -| `/api/monitoring/health` | GET | Health check | -| `/api/cache` | GET/DELETE | Cache stats / clear | +| `/api/monitoring/health` | GET | Health check + provider summary (`catalogCount`, `configuredCount`, `activeCount`, `monitoredCount`) | +| `/api/cache/stats` | GET/DELETE | Cache stats / clear | ### Backup & Export/Import @@ -279,7 +279,7 @@ GET response includes `agents[]` (id, name, binary, version, installed, protocol | Endpoint | Method | Description | | ----------------------- | ------- | ------------------------------- | -| `/api/resilience` | GET/PUT | Get/update resilience profiles | +| `/api/resilience` | GET/PATCH | Get/update resilience profiles | | `/api/resilience/reset` | POST | Reset circuit breakers | | `/api/rate-limits` | GET | Per-account rate limit status | | `/api/rate-limit` | GET | Global rate limit configuration | diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index b18f515c4c..3e4592ae38 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -2,7 +2,7 @@ 🌐 **Languages:** 🇺🇸 [English](ARCHITECTURE.md) | 🇧🇷 [Português (Brasil)](i18n/pt-BR/ARCHITECTURE.md) | 🇪🇸 [Español](i18n/es/ARCHITECTURE.md) | 🇫🇷 [Français](i18n/fr/ARCHITECTURE.md) | 🇮🇹 [Italiano](i18n/it/ARCHITECTURE.md) | 🇷🇺 [Русский](i18n/ru/ARCHITECTURE.md) | 🇨🇳 [中文 (简体)](i18n/zh-CN/ARCHITECTURE.md) | 🇩🇪 [Deutsch](i18n/de/ARCHITECTURE.md) | 🇮🇳 [हिन्दी](i18n/in/ARCHITECTURE.md) | 🇹🇭 [ไทย](i18n/th/ARCHITECTURE.md) | 🇺🇦 [Українська](i18n/uk-UA/ARCHITECTURE.md) | 🇸🇦 [العربية](i18n/ar/ARCHITECTURE.md) | 🇯🇵 [日本語](i18n/ja/ARCHITECTURE.md) | 🇻🇳 [Tiếng Việt](i18n/vi/ARCHITECTURE.md) | 🇧🇬 [Български](i18n/bg/ARCHITECTURE.md) | 🇩🇰 [Dansk](i18n/da/ARCHITECTURE.md) | 🇫🇮 [Suomi](i18n/fi/ARCHITECTURE.md) | 🇮🇱 [עברית](i18n/he/ARCHITECTURE.md) | 🇭🇺 [Magyar](i18n/hu/ARCHITECTURE.md) | 🇮🇩 [Bahasa Indonesia](i18n/id/ARCHITECTURE.md) | 🇰🇷 [한국어](i18n/ko/ARCHITECTURE.md) | 🇲🇾 [Bahasa Melayu](i18n/ms/ARCHITECTURE.md) | 🇳🇱 [Nederlands](i18n/nl/ARCHITECTURE.md) | 🇳🇴 [Norsk](i18n/no/ARCHITECTURE.md) | 🇵🇹 [Português (Portugal)](i18n/pt/ARCHITECTURE.md) | 🇷🇴 [Română](i18n/ro/ARCHITECTURE.md) | 🇵🇱 [Polski](i18n/pl/ARCHITECTURE.md) | 🇸🇰 [Slovenčina](i18n/sk/ARCHITECTURE.md) | 🇸🇪 [Svenska](i18n/sv/ARCHITECTURE.md) | 🇵🇭 [Filipino](i18n/phi/ARCHITECTURE.md) | 🇨🇿 [Čeština](i18n/cs/ARCHITECTURE.md) -_Last updated: 2026-03-04_ +_Last updated: 2026-03-24_ ## Executive Summary @@ -65,6 +65,26 @@ Primary runtime model: - Provider SLA/control plane outside local process - External CLI binaries themselves (Claude CLI, Codex CLI, etc.) +## Dashboard Surface (Current) + +Main pages under `src/app/(dashboard)/dashboard/`: + +- `/dashboard` — quick start + provider overview +- `/dashboard/endpoint` — endpoint proxy + MCP + A2A + API endpoint tabs +- `/dashboard/providers` — provider connections and credentials +- `/dashboard/combos` — combo strategies, templates, model routing rules +- `/dashboard/costs` — cost aggregation and pricing visibility +- `/dashboard/analytics` — usage analytics and evaluations +- `/dashboard/limits` — quota/rate controls +- `/dashboard/cli-tools` — CLI onboarding, runtime detection, config generation +- `/dashboard/agents` — detected ACP agents + custom agent registration +- `/dashboard/media` — image/video/music playground +- `/dashboard/search-tools` — search provider testing and history +- `/dashboard/health` — uptime, circuit breakers, rate limits +- `/dashboard/logs` — request/proxy/audit/console logs +- `/dashboard/settings` — system settings tabs (general, routing, combo defaults, etc.) +- `/dashboard/api-manager` — API key lifecycle and model permissions + ## High-Level System Context ```mermaid diff --git a/docs/CLI-TOOLS.md b/docs/CLI-TOOLS.md index b94623b728..0ff2eb803c 100644 --- a/docs/CLI-TOOLS.md +++ b/docs/CLI-TOOLS.md @@ -9,7 +9,7 @@ cost tracking, model switching, and request logging across every tool. ## How It Works ``` -Claude / Codex / Gemini CLI / OpenCode / Cline / KiloCode / Continue / Kiro CLI +Claude / Codex / OpenCode / Cline / KiloCode / Continue / Kiro / Cursor / Copilot │ ▼ (all point to OmniRoute) http://YOUR_SERVER:20128/v1 @@ -27,21 +27,38 @@ Claude / Codex / Gemini CLI / OpenCode / Cline / KiloCode / Continue / Kiro CLI --- -## Supported Tools +## Supported Tools (Dashboard Source of Truth) -| Tool | Command | Type | Install Method | -| ---------------- | ------------------- | ----------------- | -------------- | -| **Claude Code** | `claude` | CLI | npm | -| **OpenAI Codex** | `codex` | CLI | npm | -| **Gemini CLI** | `gemini` | CLI | npm | -| **OpenCode** | `opencode` | CLI | npm | -| **Cline** | `cline` | CLI + VS Code ext | npm | -| **KiloCode** | `kilocode` / `kilo` | CLI + VS Code ext | npm | -| **Continue** | guide-based | VS Code ext | VS Code | -| **Kiro CLI** | `kiro-cli` | CLI | curl installer | -| **Cursor** | `cursor` | Desktop app | Download | -| **Droid** | web-based | Built-in agent | OmniRoute | -| **OpenClaw** | web-based | Built-in agent | OmniRoute | +The dashboard cards in `/dashboard/cli-tools` are generated from `src/shared/constants/cliTools.ts`. +Current list (v3.0.0-rc.16): + +| Tool | ID | Command | Setup Mode | Install Method | +| ---------------- | ------------- | ------------ | ---------- | -------------- | +| **Claude Code** | `claude` | `claude` | env | npm | +| **OpenAI Codex** | `codex` | `codex` | custom | npm | +| **Factory Droid**| `droid` | `droid` | custom | bundled/CLI | +| **OpenClaw** | `openclaw` | `openclaw` | custom | bundled/CLI | +| **Cursor** | `cursor` | app | guide | desktop app | +| **Cline** | `cline` | `cline` | custom | npm | +| **Kilo Code** | `kilo` | `kilocode` | custom | npm | +| **Continue** | `continue` | extension | guide | VS Code | +| **Antigravity** | `antigravity` | internal | mitm | OmniRoute | +| **GitHub Copilot**| `copilot` | extension | custom | VS Code | +| **OpenCode** | `opencode` | `opencode` | guide | npm | +| **Kiro AI** | `kiro` | app/cli | mitm | desktop/CLI | + +### CLI fingerprint sync (Agents + Settings) + +`/dashboard/agents` and `Settings > CLI Fingerprint` use `src/shared/constants/cliCompatProviders.ts`. +This keeps provider IDs aligned with CLI cards and legacy IDs. + +| CLI ID | Fingerprint Provider ID | +| ------ | ----------------------- | +| `kilo` | `kilocode` | +| `copilot` | `github` | +| `claude` / `codex` / `antigravity` / `kiro` / `cursor` / `cline` / `opencode` / `droid` / `openclaw` | same ID | + +Legacy IDs still accepted for compatibility: `copilot`, `kimi-coding`, `qwen`. --- @@ -67,9 +84,6 @@ npm install -g @anthropic-ai/claude-code # OpenAI Codex npm install -g @openai/codex -# Gemini CLI (Google) -npm install -g @google/gemini-cli - # OpenCode npm install -g opencode-ai @@ -77,7 +91,7 @@ npm install -g opencode-ai npm install -g cline # KiloCode -npm install -g kilecode +npm install -g kilocode # Kiro CLI (Amazon — requires curl + unzip) apt-get install -y unzip # on Debian/Ubuntu @@ -90,7 +104,6 @@ export PATH="$HOME/.local/bin:$PATH" # add to ~/.bashrc ```bash claude --version # 2.x.x codex --version # 0.x.x -gemini --version # 0.x.x opencode --version # x.x.x cline --version # 2.x.x kilocode --version # x.x.x (or: kilo --version) @@ -153,21 +166,6 @@ EOF --- -### Gemini CLI - -```bash -mkdir -p ~/.gemini && cat > ~/.gemini/settings.json << EOF -{ - "apiKey": "sk-your-omniroute-key", - "baseUrl": "http://localhost:20128/v1" -} -EOF -``` - -**Test:** `gemini "hello"` - ---- - ### OpenCode ```bash @@ -324,17 +322,16 @@ They run as internal routes and use OmniRoute's model routing automatically. OMNIROUTE_URL="http://localhost:20128/v1" OMNIROUTE_KEY="sk-your-omniroute-key" -npm install -g @anthropic-ai/claude-code @openai/codex @google/gemini-cli opencode-ai cline kilecode +npm install -g @anthropic-ai/claude-code @openai/codex opencode-ai cline kilocode # Kiro CLI apt-get install -y unzip 2>/dev/null; curl -fsSL https://cli.kiro.dev/install | bash # Write configs -mkdir -p ~/.claude ~/.codex ~/.gemini ~/.config/opencode ~/.continue +mkdir -p ~/.claude ~/.codex ~/.config/opencode ~/.continue cat > ~/.claude/settings.json <<< "{\"apiBaseUrl\":\"$OMNIROUTE_URL\",\"apiKey\":\"$OMNIROUTE_KEY\"}" cat > ~/.codex/config.yaml <<< "model: auto\napiKey: $OMNIROUTE_KEY\napiBaseUrl: $OMNIROUTE_URL" -cat > ~/.gemini/settings.json <<< "{\"apiKey\":\"$OMNIROUTE_KEY\",\"baseUrl\":\"$OMNIROUTE_URL\"}" cat >> ~/.bashrc << EOF export OPENAI_BASE_URL="$OMNIROUTE_URL" export OPENAI_API_KEY="$OMNIROUTE_KEY" diff --git a/eslint.config.mjs b/eslint.config.mjs index 0b02a757c2..579a08f8b0 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -29,6 +29,7 @@ const eslintConfig = [ ignores: [ // Next.js build output ".next/**", + "src/.next/**", "out/**", "build/**", "next-env.d.ts", diff --git a/package.json b/package.json index 9b029df497..df46d471c4 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,7 @@ "homepage": "https://omniroute.online", "scripts": { "dev": "node scripts/run-next.mjs dev", - "build": "next build", + "build": "node scripts/build-next-isolated.mjs", "build:cli": "node scripts/prepublish.mjs", "start": "node scripts/run-next.mjs start", "lint": "eslint .", diff --git a/scripts/build-next-isolated.mjs b/scripts/build-next-isolated.mjs new file mode 100644 index 0000000000..68d9acb864 --- /dev/null +++ b/scripts/build-next-isolated.mjs @@ -0,0 +1,84 @@ +#!/usr/bin/env node + +import fs from "node:fs/promises"; +import path from "node:path"; +import { spawn } from "node:child_process"; + +/** + * This repository contains a legacy `app/` snapshot (packaging/runtime artifacts) + * alongside the active Next.js source in `src/app/`. Next.js route discovery scans + * both and fails the build on legacy files. We temporarily move the legacy folder + * out of the project root during `next build`, then restore it in all outcomes. + */ + +const projectRoot = process.cwd(); +const legacyAppDir = path.join(projectRoot, "app"); +const backupDir = path.join(projectRoot, `.app-build-backup-${process.pid}-${Date.now()}`); + +async function exists(targetPath) { + try { + await fs.access(targetPath); + return true; + } catch { + return false; + } +} + +function runNextBuild() { + return new Promise((resolve) => { + const nextBin = path.join(projectRoot, "node_modules", "next", "dist", "bin", "next"); + const child = spawn(process.execPath, [nextBin, "build"], { + cwd: projectRoot, + stdio: "inherit", + env: process.env, + }); + + const forward = (signal) => { + if (!child.killed) child.kill(signal); + }; + + process.on("SIGINT", forward); + process.on("SIGTERM", forward); + + child.on("exit", (code, signal) => { + process.off("SIGINT", forward); + process.off("SIGTERM", forward); + if (signal) { + resolve({ code: 1, signal }); + return; + } + resolve({ code: code ?? 1, signal: null }); + }); + }); +} + +async function main() { + let moved = false; + + try { + if (await exists(legacyAppDir)) { + await fs.rename(legacyAppDir, backupDir); + moved = true; + } + + const result = await runNextBuild(); + process.exitCode = result.code; + } catch (error) { + console.error("[build-next-isolated] Build failed:", error); + process.exitCode = 1; + } finally { + if (moved) { + try { + await fs.rename(backupDir, legacyAppDir); + } catch (restoreError) { + console.error( + `[build-next-isolated] Failed to restore legacy app dir from ${backupDir}:`, + restoreError + ); + process.exitCode = 1; + } + } + } +} + +await main(); diff --git a/src/app/(dashboard)/dashboard/agents/page.tsx b/src/app/(dashboard)/dashboard/agents/page.tsx index 82806ac9c4..1be6096a2a 100644 --- a/src/app/(dashboard)/dashboard/agents/page.tsx +++ b/src/app/(dashboard)/dashboard/agents/page.tsx @@ -1,9 +1,11 @@ "use client"; import { useState, useEffect, useCallback } from "react"; +import Link from "next/link"; import { Card, Button, Input } from "@/shared/components"; import { useTranslations } from "next-intl"; import { AI_PROVIDERS } from "@/shared/constants/config"; +import { CLI_COMPAT_PROVIDER_IDS } from "@/shared/constants/cliCompatProviders"; interface AgentInfo { id: string; @@ -180,6 +182,51 @@ export default function AgentsPage() { )} + {/* Setup Guide */} + +
+
+
+ +
+

{t("setupGuideTitle")}

+
+ + {t("openCliTools")} + +
+
+
+
+ radar +

{t("setupGuideDetectCliTitle")}

+
+

{t("setupGuideDetectCliDesc")}

+
+
+
+ build +

{t("setupGuideCustomAgentTitle")}

+
+

{t("setupGuideCustomAgentDesc")}

+
+
+
+ + terminal + +

{t("setupGuideCommandMissingTitle")}

+
+

{t("setupGuideCommandMissingDesc")}

+
+
+
+ {/* CLI Fingerprint Matching */}
@@ -193,24 +240,7 @@ export default function AgentsPage() {

{ts("cliFingerprintDesc")}

- {( - [ - "codex", - "claude", - "github", - "antigravity", - "kiro", - "cursor", - "kimi-coding", - "kilocode", - "cline", - "qwen", - "droid", - "openclaw", - "copilot", - "opencode", - ] as const - ).map((providerId) => { + {CLI_COMPAT_PROVIDER_IDS.map((providerId) => { const providerMeta = Object.values(AI_PROVIDERS).find( (p: any) => p.id === providerId ) as any; diff --git a/src/app/(dashboard)/dashboard/cli-tools/CLIToolsPageClient.tsx b/src/app/(dashboard)/dashboard/cli-tools/CLIToolsPageClient.tsx index 069e11a357..4cded35570 100644 --- a/src/app/(dashboard)/dashboard/cli-tools/CLIToolsPageClient.tsx +++ b/src/app/(dashboard)/dashboard/cli-tools/CLIToolsPageClient.tsx @@ -33,6 +33,16 @@ export default function CLIToolsPageClient({ machineId }) { const [apiKeys, setApiKeys] = useState([]); const [toolStatuses, setToolStatuses] = useState({}); const [statusesLoaded, setStatusesLoaded] = useState(false); + const translateOrFallback = useCallback( + (key, fallback, values = undefined) => { + try { + return t(key, values); + } catch { + return fallback; + } + }, + [t] + ); useEffect(() => { fetchConnections(); @@ -291,8 +301,42 @@ export default function CLIToolsPageClient({ machineId }) { } }; + const getToolDocsHref = (toolId, tool) => { + if (typeof tool.docsUrl === "string" && tool.docsUrl.trim()) { + return tool.docsUrl.trim(); + } + return `/docs?section=cli-tools&tool=${toolId}`; + }; + + const getToolUseCase = (toolId, tool) => { + const fallbackDescription = translateOrFallback(`toolDescriptions.${toolId}`, tool.description); + return translateOrFallback(`toolUseCases.${toolId}`, fallbackDescription); + }; + return (
+ +
+
+ tips_and_updates +
+
+

{t("howItWorks")}

+
+
+ {t("installationGuide")} +
+
+ {t("configureEndpoint")} +
+
+ {t("testConnection")} +
+
+
+
+
+ {!hasActiveProviders && (
@@ -308,7 +352,38 @@ export default function CLIToolsPageClient({ machineId }) { )}
- {Object.entries(CLI_TOOLS).map(([toolId, tool]) => renderToolCard(toolId, tool))} + {Object.entries(CLI_TOOLS).map(([toolId, tool]) => { + const docsHref = getToolDocsHref(toolId, tool); + const isExternalDocs = /^https?:\/\//i.test(docsHref); + return ( +
+ {renderToolCard(toolId, tool)} +
+
+
+

+ {t("whenToUseLabel")} +

+

+ {getToolUseCase(toolId, tool)} +

+
+ + + {t("openToolDocs")} + +
+
+
+ ); + })}
); diff --git a/src/app/(dashboard)/dashboard/combos/page.tsx b/src/app/(dashboard)/dashboard/combos/page.tsx index 11832e3cb5..f1291523a5 100644 --- a/src/app/(dashboard)/dashboard/combos/page.tsx +++ b/src/app/(dashboard)/dashboard/combos/page.tsx @@ -16,27 +16,18 @@ import Tooltip from "@/shared/components/Tooltip"; import ModelRoutingSection from "@/shared/components/ModelRoutingSection"; import { useCopyToClipboard } from "@/shared/hooks/useCopyToClipboard"; import { useNotificationStore } from "@/store/notificationStore"; +import { ROUTING_STRATEGIES } from "@/shared/constants/routingStrategies"; import { useTranslations } from "next-intl"; // Validate combo name: letters, numbers, -, _, /, . const VALID_NAME_REGEX = /^[a-zA-Z0-9_/.-]+$/; -const STRATEGY_OPTIONS = [ - { value: "priority", labelKey: "priority", descKey: "priorityDesc", icon: "sort" }, - { value: "weighted", labelKey: "weighted", descKey: "weightedDesc", icon: "percent" }, - { value: "round-robin", labelKey: "roundRobin", descKey: "roundRobinDesc", icon: "autorenew" }, - { value: "random", labelKey: "random", descKey: "randomDesc", icon: "shuffle" }, - { value: "least-used", labelKey: "leastUsed", descKey: "leastUsedDesc", icon: "low_priority" }, - { value: "cost-optimized", labelKey: "costOpt", descKey: "costOptimizedDesc", icon: "savings" }, - { - value: "fill-first", - labelKey: "fillFirst", - descKey: "fillFirstDesc", - icon: "stacked_bar_chart", - }, - { value: "p2c", labelKey: "p2c", descKey: "p2cDesc", icon: "compare_arrows" }, - { value: "strict-random", labelKey: "strictRandom", descKey: "strictRandomDesc", icon: "casino" }, -]; +const STRATEGY_OPTIONS = ROUTING_STRATEGIES.map((strategy) => ({ + value: strategy.value, + labelKey: strategy.labelKey, + descKey: strategy.combosDescKey, + icon: strategy.icon, +})); const STRATEGY_GUIDANCE_FALLBACK = { priority: { diff --git a/src/app/(dashboard)/dashboard/health/page.tsx b/src/app/(dashboard)/dashboard/health/page.tsx index 71dc0775fd..bf25185f62 100644 --- a/src/app/(dashboard)/dashboard/health/page.tsx +++ b/src/app/(dashboard)/dashboard/health/page.tsx @@ -134,7 +134,7 @@ export default function HealthPage() { ); } - const { system, providerHealth, rateLimitStatus, lockouts } = data; + const { system, providerHealth, providerSummary, rateLimitStatus, lockouts } = data; const cbEntries = Object.entries(providerHealth || {}); const lockoutEntries = Object.entries(lockouts || {}); @@ -235,11 +235,37 @@ export default function HealthPage() {
{t("providers")}
-

{cbEntries.length}

-

- {t("healthyCount", { - count: cbEntries.filter(([, v]: [string, any]) => v.state === "CLOSED").length, +

+ {providerSummary?.configuredCount ?? cbEntries.length} +

+

+ {t("configuredProvidersLabel")} + +

+

+ {t("activeProviders", { count: providerSummary?.activeCount ?? 0 })} + +

+

+ {t("monitoredProviders", { + count: providerSummary?.monitoredCount ?? cbEntries.length, })} +

diff --git a/src/app/(dashboard)/dashboard/search-tools/SearchToolsClient.tsx b/src/app/(dashboard)/dashboard/search-tools/SearchToolsClient.tsx index 3caa463b0c..6cedad890c 100644 --- a/src/app/(dashboard)/dashboard/search-tools/SearchToolsClient.tsx +++ b/src/app/(dashboard)/dashboard/search-tools/SearchToolsClient.tsx @@ -119,9 +119,9 @@ export default function SearchToolsClient() { } catch (err: any) { setDuration(Date.now() - start); if (err.name === "AbortError") { - setError("Request timed out (15s)"); + setError(t("requestTimedOut", { seconds: 15 })); } else { - setError(err.message || "Network error"); + setError(err?.message || t("networkError")); } } finally { setLoading(false); diff --git a/src/app/(dashboard)/dashboard/search-tools/components/SearchForm.tsx b/src/app/(dashboard)/dashboard/search-tools/components/SearchForm.tsx index 726b3f628e..64a5477876 100644 --- a/src/app/(dashboard)/dashboard/search-tools/components/SearchForm.tsx +++ b/src/app/(dashboard)/dashboard/search-tools/components/SearchForm.tsx @@ -33,6 +33,7 @@ interface SearchFormProps { export default function SearchForm({ onSearch, loading, onCancel, providers }: SearchFormProps) { const t = useTranslations("search"); + const tc = useTranslations("common"); const [query, setQuery] = useState(""); const [provider, setProvider] = useState("auto"); const [searchType, setSearchType] = useState("web"); @@ -92,7 +93,7 @@ export default function SearchForm({ onSearch, loading, onCancel, providers }: S