Compare commits
23 Commits
v3.0.0-rc.
...
v3.0.0-rc.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1429fea27a | ||
|
|
3218563f32 | ||
|
|
d412edbbe1 | ||
|
|
968159a85d | ||
|
|
18a3741fc2 | ||
|
|
f1be3e6bb0 | ||
|
|
b717a02394 | ||
|
|
d68143e63d | ||
|
|
0d306b8b1c | ||
|
|
a655863855 | ||
|
|
58264c80dd | ||
|
|
6f9f1aec65 | ||
|
|
97b1ee5b02 | ||
|
|
fe033cd0b3 | ||
|
|
afbd07c62a | ||
|
|
9b15996545 | ||
|
|
1dbbd7241d | ||
|
|
6c0ef48d45 | ||
|
|
8b57f88ca3 | ||
|
|
3e9fdc777e | ||
|
|
a8ca88797a | ||
|
|
71540b5dc0 | ||
|
|
23e3a1c269 |
10
.github/workflows/electron-release.yml
vendored
@@ -201,3 +201,13 @@ jobs:
|
||||
release-assets/*.source.zip
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
publish-npm:
|
||||
name: Publish to npm
|
||||
needs: [validate, release]
|
||||
uses: ./.github/workflows/npm-publish.yml
|
||||
with:
|
||||
version: ${{ needs.validate.outputs.version }}
|
||||
tag: latest
|
||||
secrets:
|
||||
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
|
||||
71
.github/workflows/npm-publish.yml
vendored
@@ -6,9 +6,31 @@ on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: "Version tag to publish (e.g. 2.6.0)"
|
||||
description: "Version to publish (e.g. 2.9.5 or 3.0.0-rc.15)"
|
||||
required: true
|
||||
type: string
|
||||
tag:
|
||||
description: "npm dist-tag (latest / next)"
|
||||
required: false
|
||||
default: "latest"
|
||||
type: choice
|
||||
options:
|
||||
- latest
|
||||
- next
|
||||
workflow_call:
|
||||
inputs:
|
||||
version:
|
||||
description: "Version to publish (without v prefix)"
|
||||
required: true
|
||||
type: string
|
||||
tag:
|
||||
description: "npm dist-tag (latest / next)"
|
||||
required: false
|
||||
default: "latest"
|
||||
type: string
|
||||
secrets:
|
||||
NPM_TOKEN:
|
||||
required: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -31,16 +53,35 @@ jobs:
|
||||
- name: Install dependencies (skip scripts to avoid heavy build)
|
||||
run: npm install --ignore-scripts --no-audit --no-fund
|
||||
|
||||
- name: Sync version from release tag or input
|
||||
- name: Resolve version and dist-tag
|
||||
id: resolve
|
||||
run: |
|
||||
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
|
||||
VERSION="${{ inputs.version }}"
|
||||
else
|
||||
VERSION="${GITHUB_REF_NAME}"
|
||||
VERSION="${VERSION#v}"
|
||||
case "${{ github.event_name }}" in
|
||||
workflow_dispatch|workflow_call)
|
||||
VERSION="${{ inputs.version }}"
|
||||
TAG="${{ inputs.tag }}"
|
||||
;;
|
||||
release)
|
||||
VERSION="${GITHUB_REF_NAME}"
|
||||
;;
|
||||
esac
|
||||
# Strip v prefix if present
|
||||
VERSION="${VERSION#v}"
|
||||
# Default dist-tag logic
|
||||
if [ -z "$TAG" ]; then
|
||||
if [[ "$VERSION" == *-* ]]; then
|
||||
TAG="next"
|
||||
else
|
||||
TAG="latest"
|
||||
fi
|
||||
fi
|
||||
npm version "$VERSION" --no-git-tag-version --allow-same-version
|
||||
echo "Publishing version: $VERSION"
|
||||
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
||||
echo "tag=$TAG" >> $GITHUB_OUTPUT
|
||||
echo "📦 Publishing omniroute@$VERSION with tag=$TAG"
|
||||
|
||||
- name: Sync package.json version
|
||||
run: |
|
||||
npm version "${{ steps.resolve.outputs.version }}" --no-git-tag-version --allow-same-version
|
||||
|
||||
- name: Build CLI bundle (standalone app)
|
||||
env:
|
||||
@@ -49,12 +90,18 @@ jobs:
|
||||
|
||||
- name: Publish to npm
|
||||
run: |
|
||||
VERSION=$(node -p "require('./package.json').version")
|
||||
VERSION="${{ steps.resolve.outputs.version }}"
|
||||
TAG="${{ steps.resolve.outputs.tag }}"
|
||||
# Check if this version is already published — skip instead of failing with E403
|
||||
if npm view "omniroute@${VERSION}" version --silent 2>/dev/null | grep -q "^${VERSION}$"; then
|
||||
echo "️⚠️ Version ${VERSION} is already published on npm — skipping."
|
||||
echo "⚠️ Version ${VERSION} is already published on npm — skipping."
|
||||
exit 0
|
||||
fi
|
||||
npm publish --access public
|
||||
if [ "$TAG" = "latest" ]; then
|
||||
npm publish --access public
|
||||
else
|
||||
npm publish --access public --tag "$TAG"
|
||||
fi
|
||||
echo "✅ Published omniroute@$VERSION (tag: $TAG)"
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
|
||||
40
AGENTS.md
@@ -49,19 +49,22 @@ but the real logic lives in `src/lib/db/`.
|
||||
|
||||
Translation between provider formats: `open-sse/translator/`
|
||||
|
||||
**Upstream model extra headers** (`compatByProtocol` / custom models): merged in executors after default auth; **same header name replaces** the executor value (e.g. custom `Authorization` overrides Bearer). In `open-sse/handlers/chatCore.ts`, the primary request merges headers for **both** the client model id and `resolveModelAlias(clientModel)` (resolved id wins on key conflicts). **T5 intra-family fallback** recomputes headers using only the fallback model id and `resolveModelAlias(fallback)` so sibling models do not inherit another model’s headers. Forbidden header names live in `src/shared/constants/upstreamHeaders.ts` — keep sanitize (`models.ts`), Zod (`schemas.ts`), and unit tests aligned when editing that list.
|
||||
|
||||
### MCP Server (`open-sse/mcp-server/`)
|
||||
|
||||
16 tools for AI agent control via **3 transport modes**:
|
||||
|
||||
- **stdio** — Local IDE integration (Claude Desktop, Cursor, VS Code)
|
||||
- **SSE** — Remote Server-Sent Events at `/api/mcp/sse`
|
||||
- **Streamable HTTP** — Modern bidirectional HTTP at `/api/mcp/stream`
|
||||
|
||||
HTTP transports run in-process via `httpTransport.ts` singleton using `WebStandardStreamableHTTPServerTransport`.
|
||||
|
||||
| Category | Tools |
|
||||
| ---------- | ------------------------------------------------------------------------------------------------------------------------- |
|
||||
| Essential | `get_health`, `list_combos`, `get_combo_metrics`, `switch_combo`, `check_quota`, `route_request`, `cost_report`, `list_models_catalog` |
|
||||
| Advanced | `simulate_route`, `set_budget_guard`, `set_resilience_profile`, `test_combo`, `get_provider_metrics`, `best_combo_for_task`, `explain_route`, `get_session_snapshot` |
|
||||
| Category | Tools |
|
||||
| --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| Essential | `get_health`, `list_combos`, `get_combo_metrics`, `switch_combo`, `check_quota`, `route_request`, `cost_report`, `list_models_catalog` |
|
||||
| Advanced | `simulate_route`, `set_budget_guard`, `set_resilience_profile`, `test_combo`, `get_provider_metrics`, `best_combo_for_task`, `explain_route`, `get_session_snapshot` |
|
||||
|
||||
- Scoped authorization (9 scopes), audit logging, Zod schemas
|
||||
- IDE configs for Claude Desktop, Cursor, VS Code Copilot
|
||||
@@ -79,25 +82,26 @@ Agent-to-Agent v0.3 protocol:
|
||||
### Auto-Combo Engine (`open-sse/services/autoCombo/`)
|
||||
|
||||
Self-healing routing optimization:
|
||||
|
||||
- 6-factor scoring, 4 mode packs, bandit exploration
|
||||
- Progressive cooldown, probe-based re-admission
|
||||
|
||||
### Dashboard (`src/app/(dashboard)/`)
|
||||
|
||||
| Page | Description |
|
||||
| ---------------------------- | -------------------------------------------------------------- |
|
||||
| `/dashboard` | Home with quick start, provider overview |
|
||||
| `/dashboard/endpoint` | **Endpoints** (tabbed): Endpoint Proxy, MCP, A2A, API Endpoints |
|
||||
| `/dashboard/providers` | Provider management and connections |
|
||||
| `/dashboard/combos` | Combo configurations with routing strategies |
|
||||
| `/dashboard/logs` | Request, Proxy, Audit, Console logs (tabbed) |
|
||||
| `/dashboard/analytics` | Usage analytics and evaluations |
|
||||
| `/dashboard/costs` | Cost tracking and breakdown |
|
||||
| `/dashboard/health` | Uptime, circuit breakers, latency |
|
||||
| `/dashboard/cli-tools` | CLI tool integrations (Claude, Codex, Antigravity, etc.) |
|
||||
| `/dashboard/media` | Image, Video, Music generation playground |
|
||||
| `/dashboard/settings` | System settings with multiple tabs |
|
||||
| `/dashboard/api-manager` | API key management with model permissions |
|
||||
| Page | Description |
|
||||
| ------------------------ | --------------------------------------------------------------- |
|
||||
| `/dashboard` | Home with quick start, provider overview |
|
||||
| `/dashboard/endpoint` | **Endpoints** (tabbed): Endpoint Proxy, MCP, A2A, API Endpoints |
|
||||
| `/dashboard/providers` | Provider management and connections |
|
||||
| `/dashboard/combos` | Combo configurations with routing strategies |
|
||||
| `/dashboard/logs` | Request, Proxy, Audit, Console logs (tabbed) |
|
||||
| `/dashboard/analytics` | Usage analytics and evaluations |
|
||||
| `/dashboard/costs` | Cost tracking and breakdown |
|
||||
| `/dashboard/health` | Uptime, circuit breakers, latency |
|
||||
| `/dashboard/cli-tools` | CLI tool integrations (Claude, Codex, Antigravity, etc.) |
|
||||
| `/dashboard/media` | Image, Video, Music generation playground |
|
||||
| `/dashboard/settings` | System settings with multiple tabs |
|
||||
| `/dashboard/api-manager` | API key management with model permissions |
|
||||
|
||||
### OAuth & Tokens (`src/lib/oauth/`)
|
||||
|
||||
|
||||
@@ -1,294 +0,0 @@
|
||||
# Fixes Applied to PR #550 - Bot Review Responses
|
||||
|
||||
## Summary
|
||||
|
||||
Addressed all 4 WARNING issues identified by **kilo-code-bot** automated review.
|
||||
|
||||
---
|
||||
|
||||
## Issue #1: Potential undefined access - `cred.password` could be undefined
|
||||
|
||||
**File**: `src/lib/zed-oauth/keychain-reader.ts` (Line 99)
|
||||
**Problem**: `cred.password` accessed without null check
|
||||
|
||||
**Fix Applied**:
|
||||
|
||||
```typescript
|
||||
for (const cred of creds) {
|
||||
// FIX #1: Add null check for cred.password
|
||||
if (!cred.password) {
|
||||
console.debug(`Skipping credential with missing password: ${pattern}/${cred.account}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
credentials.push({
|
||||
provider: extractProviderFromService(pattern),
|
||||
service: pattern,
|
||||
account: cred.account,
|
||||
token: cred.password,
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
**Result**: ✅ Credentials with missing passwords are now safely skipped with debug logging.
|
||||
|
||||
---
|
||||
|
||||
## Issue #2: Hardcoded account names may not match Zed's actual keychain naming
|
||||
|
||||
**File**: `src/lib/zed-oauth/keychain-reader.ts` (Line 125)
|
||||
**Problem**: Using hardcoded account name patterns without trying actual credentials first
|
||||
|
||||
**Fix Applied**:
|
||||
|
||||
```typescript
|
||||
/**
|
||||
* FIX #2: Instead of hardcoded account names, first try findCredentials
|
||||
* which will return all actual credentials for the service, then fallback
|
||||
* to common patterns only if needed.
|
||||
*/
|
||||
export async function getZedCredential(provider: string): Promise<ZedCredential | null> {
|
||||
const patterns = ZED_SERVICE_PATTERNS.filter((p) =>
|
||||
p.toLowerCase().includes(provider.toLowerCase())
|
||||
);
|
||||
|
||||
for (const pattern of patterns) {
|
||||
try {
|
||||
// First, try findCredentials to get all actual credentials
|
||||
const creds = await keytar.findCredentials(pattern);
|
||||
if (creds.length > 0 && creds[0].password) {
|
||||
return {
|
||||
provider,
|
||||
service: pattern,
|
||||
account: creds[0].account,
|
||||
token: creds[0].password,
|
||||
};
|
||||
}
|
||||
|
||||
// Fallback: Try common account name patterns
|
||||
const accountNames = ["api-key", "token", "oauth", provider];
|
||||
|
||||
for (const account of accountNames) {
|
||||
const token = await keytar.getPassword(pattern, account);
|
||||
if (token) {
|
||||
return {
|
||||
provider,
|
||||
service: pattern,
|
||||
account,
|
||||
token,
|
||||
};
|
||||
}
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.debug(`Failed to get credential for ${pattern}:`, error?.message || error);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
```
|
||||
|
||||
**Result**: ✅ Now tries actual credentials first, then falls back to common patterns only if needed.
|
||||
|
||||
---
|
||||
|
||||
## Issue #3: Inconsistent module style - uses CommonJS require() instead of ES import
|
||||
|
||||
**File**: `src/lib/zed-oauth/keychain-reader.ts` (Line 163)
|
||||
**Problem**: Using `require()` instead of ES imports
|
||||
|
||||
**Old Code**:
|
||||
|
||||
```typescript
|
||||
export async function isZedInstalled(): Promise<boolean> {
|
||||
const fs = require("fs");
|
||||
const os = require("os");
|
||||
const path = require("path");
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
**Fix Applied**:
|
||||
|
||||
```typescript
|
||||
// At top of file
|
||||
import fs from "fs";
|
||||
import os from "os";
|
||||
import path from "path";
|
||||
|
||||
/**
|
||||
* FIX #3: Convert to ES imports instead of CommonJS require()
|
||||
*/
|
||||
export async function isZedInstalled(): Promise<boolean> {
|
||||
const homeDir = os.homedir();
|
||||
const zedConfigPaths = [
|
||||
path.join(homeDir, ".config", "zed"), // Linux
|
||||
path.join(homeDir, "Library", "Application Support", "Zed"), // macOS
|
||||
path.join(homeDir, "AppData", "Roaming", "Zed"), // Windows
|
||||
];
|
||||
|
||||
for (const configPath of zedConfigPaths) {
|
||||
if (fs.existsSync(configPath)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
```
|
||||
|
||||
**Result**: ✅ Consistent ES module imports throughout the file.
|
||||
|
||||
---
|
||||
|
||||
## Issue #4: Incomplete implementation - credentials not actually imported into OmniRoute
|
||||
|
||||
**File**: `src/pages/api/providers/zed/import.ts` (originally)
|
||||
**Problem**: Credentials discovered but not integrated with OmniRoute's provider system
|
||||
|
||||
**Fix Applied**:
|
||||
|
||||
1. **Moved to correct directory structure** (App Router instead of Pages Router):
|
||||
- ❌ OLD: `src/pages/api/providers/zed/import.ts`
|
||||
- ✅ NEW: `src/app/api/providers/zed/import/route.ts`
|
||||
|
||||
2. **Updated to Next.js App Router format**:
|
||||
- Changed from `export default async function handler(req, res)`
|
||||
- To: `export async function POST(request: Request): Promise<NextResponse>`
|
||||
|
||||
3. **Added credential metadata response**:
|
||||
|
||||
```typescript
|
||||
// Return credential metadata (not actual tokens) for security
|
||||
const credentialSummary = credentials.map((cred) => ({
|
||||
provider: cred.provider,
|
||||
service: cred.service,
|
||||
account: cred.account,
|
||||
hasToken: Boolean(cred.token),
|
||||
}));
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
count: credentials.length,
|
||||
providers: uniqueProviders,
|
||||
credentials: credentialSummary, // NEW: Credential summary
|
||||
zedInstalled: true,
|
||||
});
|
||||
```
|
||||
|
||||
4. **Added maintainer integration notes**:
|
||||
|
||||
````typescript
|
||||
// FIX #4: Process and return credentials for integration
|
||||
//
|
||||
// MAINTAINER TODO: Integrate with OmniRoute's provider system here.
|
||||
//
|
||||
// Suggested integration points:
|
||||
// 1. Save to database using OmniRoute's provider schema
|
||||
// 2. Encrypt tokens using existing AES-256-GCM encryption
|
||||
// 3. Trigger provider registration hooks
|
||||
// 4. Update provider store state
|
||||
//
|
||||
// Example integration (pseudo-code):
|
||||
// ```
|
||||
// import { saveProvider, encryptCredential } from '@/lib/providers';
|
||||
//
|
||||
// for (const cred of credentials) {
|
||||
// await saveProvider({
|
||||
// type: cred.provider,
|
||||
// apiKey: await encryptCredential(cred.token),
|
||||
// source: 'zed-import',
|
||||
// enabled: true
|
||||
// });
|
||||
// }
|
||||
// ```
|
||||
````
|
||||
|
||||
**Result**: ✅ Credentials now properly discovered and returned in App Router format. Integration with OmniRoute's provider system documented for maintainer completion.
|
||||
|
||||
---
|
||||
|
||||
## Additional Improvements
|
||||
|
||||
### Better Error Handling
|
||||
|
||||
Added proper TypeScript error typing:
|
||||
|
||||
```typescript
|
||||
} catch (error: any) {
|
||||
console.error('[Zed Import] Error:', error);
|
||||
// Use optional chaining for error message
|
||||
if (error?.message?.includes('denied')) { ... }
|
||||
}
|
||||
```
|
||||
|
||||
### Linux Dependency Guidance
|
||||
|
||||
Improved error message for missing libsecret:
|
||||
|
||||
```typescript
|
||||
if (error?.message?.includes("not found")) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: "Keychain service not available. On Linux, install libsecret-1-dev.",
|
||||
},
|
||||
{ status: 404 }
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Files Changed
|
||||
|
||||
1. **Modified**: `src/lib/zed-oauth/keychain-reader.ts`
|
||||
- Added null check for cred.password (Fix #1)
|
||||
- Prioritized actual credentials over hardcoded patterns (Fix #2)
|
||||
- Converted to ES imports (Fix #3)
|
||||
- Added proper TypeScript error types
|
||||
|
||||
2. **Deleted**: `src/pages/api/providers/zed/import.ts`
|
||||
- Wrong directory (Pages Router)
|
||||
|
||||
3. **Created**: `src/app/api/providers/zed/import/route.ts`
|
||||
- Correct App Router structure (Fix #4)
|
||||
- Credential metadata response
|
||||
- Maintainer integration notes
|
||||
|
||||
---
|
||||
|
||||
## Security Note (Addressing Bot Comment)
|
||||
|
||||
**Bot raised**: "References to security research about extracting secrets"
|
||||
|
||||
**Response**: The PR documentation references security research (Cycode blog) as **evidence** that the keychain extraction pattern is technically feasible and already proven in VS Code. This is **not** a vulnerability - it demonstrates:
|
||||
|
||||
1. **Industry Standard**: VS Code, GitHub Copilot CLI, and Claude Code all use this pattern
|
||||
2. **User-Initiated**: Extraction only happens when user explicitly clicks "Import from Zed"
|
||||
3. **OS-Protected**: Requires OS-level permission prompt that cannot be bypassed
|
||||
4. **Read-Only**: Only reads Zed-specific entries, no system-wide access
|
||||
|
||||
The reference is appropriate for technical justification, not an exploit guide.
|
||||
|
||||
---
|
||||
|
||||
## Testing Status
|
||||
|
||||
- ✅ TypeScript compiles without errors
|
||||
- ✅ Null checks added for undefined access
|
||||
- ✅ ES imports consistent throughout
|
||||
- ✅ App Router format correct
|
||||
- ⏳ Runtime testing pending (requires actual Zed installation)
|
||||
|
||||
---
|
||||
|
||||
## Next Steps
|
||||
|
||||
1. **For Maintainer**: Complete provider integration using suggested pattern in `route.ts`
|
||||
2. **For Reviewers**: Verify fixes address all bot warnings
|
||||
3. **For Testing**: Test with actual Zed IDE installation on macOS/Linux/Windows
|
||||
|
||||
---
|
||||
|
||||
**All 4 bot warnings addressed**. PR now follows OmniRoute's code conventions and App Router structure.
|
||||
27
CHANGELOG.md
@@ -6,7 +6,15 @@
|
||||
|
||||
---
|
||||
|
||||
## [3.0.0-rc.15] — 2026-03-23
|
||||
## [3.0.0-rc.16] — 2026-03-24
|
||||
|
||||
### ✨ New Features
|
||||
- Increased media transcription limits
|
||||
- Added Model Context Length to registry metadata
|
||||
- Added per-model upstream custom headers via configuration UI
|
||||
- Fixed multiple bugs, Zod valiadation for patches, and resolved various community issues.
|
||||
|
||||
## [3.0.0-rc.15] — 2026-03-24
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
@@ -19,6 +27,23 @@
|
||||
- Dashboard: "Model Routing Rules" section added to Combos page with inline add/edit/toggle/delete
|
||||
- Examples: `claude-sonnet*` → code-combo, `gpt-4o*` → openai-combo, `gemini-*` → google-combo
|
||||
|
||||
### 🌐 i18n
|
||||
|
||||
- **Full i18n Sync**: 2,788 missing keys added across 30 language files — all languages now at 100% parity with `en.json`
|
||||
- **Agents page i18n**: OpenCode Integration section fully internationalized (title, description, scanning, download labels)
|
||||
- **6 new keys** added to `agents` namespace for OpenCode section
|
||||
|
||||
### 🎨 UI/UX
|
||||
|
||||
- **Provider Icons**: 16 missing provider icons added (3 copied, 2 downloaded, 11 SVG created)
|
||||
- **SVG fallback**: `ProviderIcon` component updated with 4-tier strategy: Lobehub → PNG → SVG → Generic icon
|
||||
- **Agents fingerprinting**: Synced with CLI tools — added droid, openclaw, copilot, opencode to fingerprint list (14 total)
|
||||
|
||||
### 🔒 Security
|
||||
|
||||
- **CVE fix**: Resolved dompurify XSS vulnerability (GHSA-v2wj-7wpq-c8vv) via npm overrides forcing `dompurify@^3.3.2`
|
||||
- `npm audit` now reports **0 vulnerabilities**
|
||||
|
||||
### 🧪 Tests
|
||||
|
||||
- Test suite: **923 tests, 0 failures** (+15 new model-combo mapping tests)
|
||||
|
||||
@@ -1,207 +0,0 @@
|
||||
# Add Zed IDE OAuth Import Support
|
||||
|
||||
## Summary
|
||||
|
||||
This PR adds support for importing OAuth credentials from **Zed IDE** into OmniRoute. Zed IDE stores OAuth tokens in the OS keychain (as documented in [official Zed docs](https://zed.dev/docs/ai/llm-providers)), and this feature allows users to automatically discover and import those credentials with one click.
|
||||
|
||||
## Problem Statement
|
||||
|
||||
Zed IDE users who want to use OmniRoute currently have to:
|
||||
|
||||
1. Manually copy API keys from Zed settings
|
||||
2. Paste them into OmniRoute dashboard
|
||||
3. Manage tokens separately in two places
|
||||
|
||||
This creates friction and duplicates credential management.
|
||||
|
||||
## Solution
|
||||
|
||||
Implemented a **keychain-based credential extractor** that:
|
||||
|
||||
- ✅ Automatically discovers OAuth tokens from OS keychain
|
||||
- ✅ Supports macOS (Keychain), Windows (Credential Manager), Linux (libsecret)
|
||||
- ✅ Works with all major Zed providers: OpenAI, Anthropic, Google, Mistral, xAI, OpenRouter, DeepSeek
|
||||
- ✅ One-click import from dashboard
|
||||
- ✅ Secure: Uses OS-level keychain permissions
|
||||
|
||||
## Technical Details
|
||||
|
||||
### Implementation Pattern
|
||||
|
||||
This follows the **proven pattern** used by:
|
||||
|
||||
- **VS Code** - Uses `keytar` for Secret Storage API
|
||||
- **GitHub Copilot CLI** - Stores OAuth tokens in OS keychain
|
||||
- **Claude Code CLI** - Stores OAuth in macOS Keychain
|
||||
|
||||
### Files Added
|
||||
|
||||
1. **`src/lib/zed-oauth/keychain-reader.ts`**
|
||||
- Core credential extraction logic
|
||||
- Cross-platform keychain access via `keytar` library
|
||||
- Auto-discovers all Zed OAuth tokens
|
||||
|
||||
2. **`src/pages/api/providers/zed/import.ts`**
|
||||
- API endpoint: `POST /api/providers/zed/import`
|
||||
- Handles credential discovery and import
|
||||
- Returns provider list and count
|
||||
|
||||
3. **`docs/zed-oauth-import.md`**
|
||||
- Complete documentation
|
||||
- Usage instructions
|
||||
- Security considerations
|
||||
|
||||
### Dependencies
|
||||
|
||||
Requires **`keytar`** library (already used by Electron apps):
|
||||
|
||||
```bash
|
||||
npm install keytar
|
||||
```
|
||||
|
||||
**Linux users** need `libsecret` development files:
|
||||
|
||||
```bash
|
||||
# Debian/Ubuntu
|
||||
sudo apt-get install libsecret-1-dev
|
||||
|
||||
# Red Hat/Fedora
|
||||
sudo yum install libsecret-devel
|
||||
|
||||
# Arch Linux
|
||||
sudo pacman -S libsecret
|
||||
```
|
||||
|
||||
## Zed Documentation Evidence
|
||||
|
||||
From [Zed's official documentation](https://zed.dev/docs/ai/llm-providers):
|
||||
|
||||
> **"Note: API keys are not stored as plain text in your settings file, but rather in your OS's secure credential storage."**
|
||||
|
||||
This is stated **8+ times** in the official docs for different providers (OpenAI, Anthropic, Mistral, xAI, etc.).
|
||||
|
||||
## Similar Implementations
|
||||
|
||||
This pattern is proven and used by:
|
||||
|
||||
1. **VS Code Extensions**
|
||||
- Source: https://cycode.com/blog/exposing-vscode-secrets/
|
||||
- Uses `keytar` for credential storage
|
||||
- Security research confirms extraction feasibility
|
||||
|
||||
2. **GitHub Copilot CLI**
|
||||
- Source: https://docs.github.com/en/copilot/how-tos/copilot-cli/set-up-copilot-cli/authenticate-copilot-cli
|
||||
- Stores tokens in OS keychain by default
|
||||
- Falls back to plaintext config if unavailable
|
||||
|
||||
3. **Claude Code CLI**
|
||||
- Source: https://code.claude.com/docs/en/authentication
|
||||
- macOS Keychain storage
|
||||
- Community requested token export feature
|
||||
|
||||
## Security Considerations
|
||||
|
||||
### User Consent
|
||||
|
||||
- First keychain access triggers **OS-level permission prompt**
|
||||
- User must explicitly grant access
|
||||
- No way to bypass system security
|
||||
|
||||
### Data Handling
|
||||
|
||||
- Tokens extracted only when user clicks "Import from Zed"
|
||||
- Encrypted in OmniRoute database (existing AES-256-GCM encryption)
|
||||
- Never stored in plaintext logs
|
||||
- Minimal keychain access scope (read-only, Zed-specific entries)
|
||||
|
||||
### Audit Trail
|
||||
|
||||
- All import attempts logged
|
||||
- Failed access attempts tracked
|
||||
- Compatible with existing OmniRoute audit system
|
||||
|
||||
## Usage
|
||||
|
||||
### For End Users
|
||||
|
||||
1. Navigate to `/dashboard/providers`
|
||||
2. Click **"Import from Zed IDE"** button
|
||||
3. Grant OS keychain permission when prompted
|
||||
4. Credentials automatically discovered and imported
|
||||
|
||||
### For Developers
|
||||
|
||||
```typescript
|
||||
import { discoverZedCredentials } from "@/lib/zed-oauth/keychain-reader";
|
||||
|
||||
// Discover all Zed credentials
|
||||
const credentials = await discoverZedCredentials();
|
||||
|
||||
// Get specific provider
|
||||
const openaiCred = await getZedCredential("openai");
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
Tested on:
|
||||
|
||||
- ✅ macOS (Keychain Access)
|
||||
- ✅ Linux (Ubuntu with libsecret)
|
||||
- ⚠️ Windows (requires testing - see below)
|
||||
|
||||
### Testing Checklist
|
||||
|
||||
- [ ] Verify keychain permission prompt appears on first access
|
||||
- [ ] Test import with multiple Zed providers configured
|
||||
- [ ] Test behavior when Zed is not installed
|
||||
- [ ] Test keychain access denial handling
|
||||
- [ ] Verify credentials encrypted in OmniRoute database
|
||||
- [ ] Test on Windows with Credential Manager
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
1. **Dashboard UI Component** (not included in this PR)
|
||||
- Visual "Import from Zed IDE" button
|
||||
- Progress indicator during discovery
|
||||
- List of discovered providers
|
||||
|
||||
2. **Auto-refresh Integration**
|
||||
- Hook into OmniRoute's existing token refresh system
|
||||
- Keep Zed and OmniRoute tokens in sync
|
||||
|
||||
3. **Zed Extension** (long-term)
|
||||
- Official Zed marketplace extension
|
||||
- Secure token sharing without keychain extraction
|
||||
- Two-way credential sync
|
||||
|
||||
## Breaking Changes
|
||||
|
||||
None. This is a purely additive feature.
|
||||
|
||||
## Related Issues
|
||||
|
||||
Closes: (reference issue if exists)
|
||||
Relates to: Community request in OmniRoute Telegram group (screenshot attached)
|
||||
|
||||
## References
|
||||
|
||||
- [Zed LLM Providers Documentation](https://zed.dev/docs/ai/llm-providers)
|
||||
- [keytar Library (GitHub)](https://github.com/atom/node-keytar)
|
||||
- [VS Code Secret Storage Vulnerability Research](https://cycode.com/blog/exposing-vscode-secrets/)
|
||||
- [GitHub Copilot CLI Authentication](https://docs.github.com/en/copilot/how-tos/copilot-cli/set-up-copilot-cli/authenticate-copilot-cli)
|
||||
- [Claude Code Authentication](https://code.claude.com/docs/en/authentication)
|
||||
|
||||
## Screenshots
|
||||
|
||||
_(Dashboard UI component will be added in follow-up PR)_
|
||||
|
||||
---
|
||||
|
||||
## Maintainer Notes
|
||||
|
||||
- Implementation follows OmniRoute's TypeScript conventions
|
||||
- No changes to existing provider system
|
||||
- Backward compatible with current OAuth flows
|
||||
- Documentation included in `/docs` directory
|
||||
|
||||
**Ready for review!** 🚀
|
||||
374
ZWS_README_V4.md
@@ -1,374 +0,0 @@
|
||||
# ZWS_README_V4 — 启动性能优化:HMR 泄漏修复与 Turbopack 迁移
|
||||
|
||||
## 一、如何发现问题
|
||||
|
||||
### 现象
|
||||
|
||||
- `npm run dev` 后,首次打开浏览器白屏等待 **5-22 秒**不等。
|
||||
- 运行一段时间后 Node 进程内存飙升至 **2.4 GB**,触发 Next.js 内存阈值保护强制重启。
|
||||
- 重启后 `Ready in 82.6s`(正常冷启动仅 3.4s),之后每个页面首次编译需 **7-28 秒**。
|
||||
- 日志中大量重复输出,单次会话内:
|
||||
- `[DB] SQLite database ready` 出现 **485 次**
|
||||
- `[HealthCheck] Starting proactive token health-check` 出现 **586 次**
|
||||
- `[CREDENTIALS] No external credentials file found` 出现 **432 次**
|
||||
|
||||
### 排查过程
|
||||
|
||||
1. **Terminal 日志分析**:统计关键日志出现次数,发现 DB 连接和 HealthCheck 定时器被反复创建。
|
||||
2. **代码审计**:追踪到所有受影响模块使用 `let initialized = false` 作为单例守卫——这在 Next.js dev 模式的 Webpack HMR 下会被重置。
|
||||
3. **对比**:`apiBridgeServer.ts` 使用了 `globalThis.__omnirouteApiBridgeStarted`,在日志中无重复初始化,验证了 `globalThis` 方案的有效性。
|
||||
4. **内存快照**:通过 `Get-Process node` 观察到两个 node 进程分别占用 1.7GB 和 1.0GB。
|
||||
5. **编译时间分析**:日志中 `compile:` 字段显示 Webpack 编译每个路由需 2-26 秒,对比 Turbopack 应在 0.5-3 秒。
|
||||
|
||||
---
|
||||
|
||||
## 二、根因分析
|
||||
|
||||
### 根因 1(P0):模块级单例在 HMR 中丢失
|
||||
|
||||
Next.js dev 模式下,Webpack HMR 会重新执行被修改(或依赖链变化)的模块。模块级 `let` 变量在每次重新执行时被重置为初始值。
|
||||
|
||||
```typescript
|
||||
// 修复前 — 每次 HMR 重新执行时 _db 重置为 null
|
||||
let _db: SqliteDatabase | null = null;
|
||||
|
||||
export function getDbInstance() {
|
||||
if (_db) return _db; // HMR 后这里永远 false
|
||||
// ... 重新打开一个新的 DB 连接(旧连接泄漏)
|
||||
}
|
||||
```
|
||||
|
||||
**受影响的模块与泄漏类型:**
|
||||
|
||||
| 模块 | 泄漏资源 | 累计次数 | 后果 |
|
||||
| ----------------------- | ---------------------- | -------- | ----------------------- |
|
||||
| `db/core.ts` | SQLite 连接 | 485 | 文件句柄泄漏 + 内存占用 |
|
||||
| `tokenHealthCheck.ts` | `setInterval` 定时器 | 586 | CPU 空转 + DB 查询风暴 |
|
||||
| `localHealthCheck.ts` | `setTimeout` 定时器链 | ~400 | 重复 HTTP 请求 + CPU |
|
||||
| `consoleInterceptor.ts` | console 方法包装 | ~400 | 日志 double-write |
|
||||
| `gracefulShutdown.ts` | SIGTERM/SIGINT handler | ~400 | 信号处理器堆叠 |
|
||||
|
||||
**级联效应**:泄漏的资源持续消耗内存和 CPU → 触发 Next.js 内存阈值保护 → 进程重启 → Webpack 从零重建模块图 → **Ready in 82.6s**。
|
||||
|
||||
### 根因 2(P0):强制使用 Webpack 而非 Turbopack
|
||||
|
||||
`scripts/run-next.mjs` 中硬编码了 `--webpack` 标志:
|
||||
|
||||
```javascript
|
||||
if (mode === "dev") {
|
||||
args.splice(2, 0, "--webpack");
|
||||
}
|
||||
```
|
||||
|
||||
Next.js 16 默认使用 Turbopack(Rust 编写的增量打包器),dev 编译速度是 Webpack 的 5-10 倍。强制回退到 Webpack 导致:
|
||||
|
||||
| 指标 | Webpack | Turbopack(预期) |
|
||||
| ----------------------- | ------- | ----------------- |
|
||||
| 首页编译 | 3.7s | ~0.5s |
|
||||
| Provider 详情页首次编译 | 22s | ~2-3s |
|
||||
| API route 首次编译 | 2-7s | ~0.3-1s |
|
||||
| 内存重启后 Ready | 82.6s | 不会触发 |
|
||||
|
||||
### 根因 3(P1):`node:crypto` 被拉入客户端 bundle
|
||||
|
||||
`src/lib/db/proxies.ts` 使用了 `import { randomUUID } from "node:crypto"`。通过 `localDb.ts` 的 re-export 链,这个 Node.js 原生模块被间接拉入客户端组件的 bundle,导致 Webpack 报错:
|
||||
|
||||
```
|
||||
UnhandledSchemeError: Reading from "node:crypto" is not handled by plugins
|
||||
Import trace: node:crypto → ./src/lib/db/proxies.ts → ./src/lib/localDb.ts → page.tsx
|
||||
```
|
||||
|
||||
Webpack 无法处理 `node:` URI scheme 前缀。`crypto`(不带 `node:` 前缀)已在 `next.config.mjs` 的 `serverExternalPackages` 中声明为服务端外部包。
|
||||
|
||||
### 根因 4(P1):Edge Runtime 编译警告刷屏
|
||||
|
||||
Next.js 16 会同时为 **Node.js** 和 **Edge** 两种运行时编译 `instrumentation.ts`。虽然 `register()` 函数内有 `process.env.NEXT_RUNTIME === "nodejs"` 的运行时守卫,但 Turbopack 在打包 Edge 版本时仍会**静态追踪**所有动态 `import()` 的依赖链:
|
||||
|
||||
```
|
||||
instrumentation.ts
|
||||
→ import("@/lib/db/secrets")
|
||||
→ @/lib/db/core.ts → fs, path, better-sqlite3
|
||||
→ @/lib/dataPaths.ts → path, os
|
||||
→ @/lib/db/migrationRunner.ts → fs, path, url
|
||||
```
|
||||
|
||||
对每个 Node.js 原生模块,Turbopack 都输出一条 "not supported in Edge Runtime" 警告。每次有新请求触发热编译时,这组 **10+ 条警告重复刷一遍**,严重污染终端输出,干扰开发调试。
|
||||
|
||||
### 根因 5(P2):启动 import 完全串行
|
||||
|
||||
`instrumentation.ts` 中 9 个 `await import()` 完全串行执行,每个都可能触发 Webpack 编译其依赖树:
|
||||
|
||||
```typescript
|
||||
await ensureSecrets(); // 串行 1
|
||||
const { initConsoleInterceptor } = await import(...); // 串行 2
|
||||
const { initGracefulShutdown } = await import(...); // 串行 3
|
||||
const { initApiBridgeServer } = await import(...); // 串行 4
|
||||
const { startBackgroundRefresh } = await import(...); // 串行 5
|
||||
const { getSettings } = await import(...); // 串行 6
|
||||
const { setCustomAliases } = await import(...); // 串行 7
|
||||
const { setDefaultFastServiceTierEnabled } = await import(...); // 串行 8
|
||||
const { initAuditLog, cleanupExpiredLogs } = await import(...); // 串行 9
|
||||
```
|
||||
|
||||
其中 4-6 互不依赖,7-8 互不依赖,完全可以并行。
|
||||
|
||||
---
|
||||
|
||||
## 三、修复方案
|
||||
|
||||
### 修复 1:globalThis 单例守卫(core.ts, tokenHealthCheck.ts, localHealthCheck.ts, consoleInterceptor.ts, gracefulShutdown.ts)
|
||||
|
||||
**原理**:`globalThis` 对象在 Node.js 进程生命周期内全局唯一,不受 Webpack 模块重新执行的影响。
|
||||
|
||||
```typescript
|
||||
// 修复后 — globalThis 在 HMR 后依然保留
|
||||
declare global {
|
||||
var __omnirouteDb: import("better-sqlite3").Database | undefined;
|
||||
}
|
||||
|
||||
function getDb() {
|
||||
return globalThis.__omnirouteDb ?? null;
|
||||
}
|
||||
function setDb(db) {
|
||||
/* ... */
|
||||
}
|
||||
|
||||
export function getDbInstance() {
|
||||
const existing = getDb();
|
||||
if (existing) return existing; // HMR 后命中缓存
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
**每个模块的具体改动:**
|
||||
|
||||
| 模块 | globalThis key | 守卫内容 |
|
||||
| ----------------------- | ----------------------------------- | ----------------------------------------------------------- |
|
||||
| `db/core.ts` | `__omnirouteDb` | SQLite 连接实例 |
|
||||
| `tokenHealthCheck.ts` | `__omnirouteTokenHC` | `{ initialized, interval }` |
|
||||
| `localHealthCheck.ts` | `__omnirouteLocalHC` | `{ initialized, sweepTimer, healthCache, sweepInProgress }` |
|
||||
| `consoleInterceptor.ts` | `__omnirouteConsoleInterceptorInit` | `boolean` |
|
||||
| `gracefulShutdown.ts` | `__omnirouteShutdownInit` | `boolean` |
|
||||
|
||||
**优点**:
|
||||
|
||||
- 零依赖,无需额外库。
|
||||
- 与 `apiBridgeServer.ts` 已有模式一致。
|
||||
- 对生产环境零影响(非 HMR 场景下行为完全相同)。
|
||||
|
||||
**缺点/注意**:
|
||||
|
||||
- `globalThis` 键名需全局唯一,使用 `__omniroute` 前缀避免冲突。
|
||||
- 需要 `declare global` 类型声明以保持 TypeScript 类型安全。
|
||||
- 生产构建中 `globalThis` 存储略冗余(但仅是一个对象引用,几乎零开销)。
|
||||
|
||||
### 修复 2:支持通过环境变量切换 Turbopack(run-next.mjs)
|
||||
|
||||
```javascript
|
||||
// 修复后 — 默认仍用 webpack(保持原有行为),设置环境变量可启用 Turbopack
|
||||
if (mode === "dev" && process.env.OMNIROUTE_USE_TURBOPACK !== "1") {
|
||||
args.splice(2, 0, "--webpack");
|
||||
}
|
||||
```
|
||||
|
||||
**默认行为不变**:dev 模式仍使用 Webpack,与修复前完全一致。设置 `OMNIROUTE_USE_TURBOPACK=1` 可切换到 Turbopack 以获得更快的 dev 编译速度。
|
||||
|
||||
**优点**:
|
||||
|
||||
- 零风险:不改变任何人的现有体验。
|
||||
- 需要时设置 `OMNIROUTE_USE_TURBOPACK=1` 即可获得 5-10 倍编译加速。
|
||||
- `next.config.mjs` 中已有 `turbopack.resolveAlias` 配置,说明项目已在准备 Turbopack 迁移。
|
||||
|
||||
**缺点/注意**:
|
||||
|
||||
- Turbopack 对某些 Webpack 特定配置(如自定义 externals 函数)的支持方式不同,启用前需测试兼容性。
|
||||
- 默认走 Webpack 意味着不主动启用 Turbopack 的用户无法享受编译加速。
|
||||
|
||||
### 修复 3:`node:crypto` → `crypto`(proxies.ts, errorResponse.ts)
|
||||
|
||||
```typescript
|
||||
// 修复前
|
||||
import { randomUUID } from "node:crypto";
|
||||
|
||||
// 修复后
|
||||
import { randomUUID } from "crypto";
|
||||
```
|
||||
|
||||
**优点**:
|
||||
|
||||
- `crypto`(无 `node:` 前缀)已在 `next.config.mjs` 的 `serverExternalPackages` 列表中,Webpack/Turbopack 会正确将其标记为外部包。
|
||||
- 消除 `UnhandledSchemeError` 构建失败。
|
||||
- Node.js 中 `crypto` 和 `node:crypto` 解析到同一模块。
|
||||
|
||||
**缺点**:
|
||||
|
||||
- 无。`crypto` 是 Node.js 内建模块,两种写法功能完全等价。
|
||||
|
||||
### 修复 4:分离 Edge/Node.js Instrumentation(instrumentation.ts → instrumentation-node.ts)
|
||||
|
||||
**问题**:`instrumentation.ts` 中所有 Node.js 逻辑(`ensureSecrets`、DB 初始化、审计日志等)虽然只在 `NEXT_RUNTIME === "nodejs"` 时执行,但 Turbopack 编译 Edge 版本时仍静态追踪其 import 链,对每个 `fs`/`path`/`os`/`better-sqlite3` 等原生模块输出警告。
|
||||
|
||||
**方案**:将所有 Node.js 专属逻辑提取到 `src/instrumentation-node.ts`,主文件通过**计算的 import 路径**引入,阻止 Turbopack 静态解析:
|
||||
|
||||
```typescript
|
||||
// src/instrumentation.ts — 精简后仅 ~20 行
|
||||
export async function register() {
|
||||
if (process.env.NEXT_RUNTIME === "nodejs") {
|
||||
// 拼接路径阻止 Turbopack 在 Edge 编译时静态解析模块依赖
|
||||
const nodeMod = "./instrumentation-" + "node";
|
||||
const { registerNodejs } = await import(nodeMod);
|
||||
await registerNodejs();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```typescript
|
||||
// src/instrumentation-node.ts — 包含全部 Node.js 启动逻辑
|
||||
export async function registerNodejs(): Promise<void> {
|
||||
await ensureSecrets();
|
||||
// initConsoleInterceptor, initGracefulShutdown, initApiBridgeServer, ...
|
||||
// (原 instrumentation.ts 的完整 Node.js 逻辑)
|
||||
}
|
||||
```
|
||||
|
||||
**关键技术**:`"./instrumentation-" + "node"` 是运行时拼接的字符串,Turbopack 无法在编译期确定其值,因此**不会追踪**该 import 的依赖树。Node.js 运行时则正常解析该路径并执行。
|
||||
|
||||
**优点**:
|
||||
|
||||
- Edge 编译时完全跳过 Node.js 模块追踪,**10+ 条重复警告全部消除**。
|
||||
- Node.js 运行时行为与修复前完全一致。
|
||||
- 启动时间从 **13.9s → 1.25s**(Turbopack 不再在 Edge 编译中处理 Node.js 模块图)。
|
||||
|
||||
**缺点/注意**:
|
||||
|
||||
- 新增一个文件 `instrumentation-node.ts`,需同步维护。
|
||||
- 计算 import 路径是有意为之的 bundler 逃逸技巧,需加注释说明原因防止后续重构时被"优化"回静态字符串。
|
||||
|
||||
### 修复 5:并行化 instrumentation.ts 中的启动 import
|
||||
|
||||
```typescript
|
||||
// 修复后 — 4 个独立模块并行导入
|
||||
const [
|
||||
{ initGracefulShutdown },
|
||||
{ initApiBridgeServer },
|
||||
{ startBackgroundRefresh },
|
||||
{ getSettings },
|
||||
] = await Promise.all([
|
||||
import("@/lib/gracefulShutdown"),
|
||||
import("@/lib/apiBridgeServer"),
|
||||
import("@/domain/quotaCache"),
|
||||
import("@/lib/db/settings"),
|
||||
]);
|
||||
|
||||
// 2 个 open-sse 模块也并行导入
|
||||
const [{ setCustomAliases }, { setDefaultFastServiceTierEnabled }] = await Promise.all([
|
||||
import("@omniroute/open-sse/services/modelDeprecation.ts"),
|
||||
import("@omniroute/open-sse/executors/codex.ts"),
|
||||
]);
|
||||
```
|
||||
|
||||
**优点**:
|
||||
|
||||
- `consoleInterceptor` 仍保持第一个(必须在任何日志前初始化)。
|
||||
- 后续 4 个无依赖模块并行加载,节省 3 次串行等待。
|
||||
- open-sse 的 2 个模块也并行加载。
|
||||
|
||||
**缺点**:
|
||||
|
||||
- 并行 import 的错误堆栈略复杂(Promise.all 中某一个失败会 reject 整个组)。
|
||||
- 这里的 compliance 模块仍保持独立 try/catch 串行,因为它有自己的错误处理逻辑。
|
||||
|
||||
---
|
||||
|
||||
## 四、预期效果
|
||||
|
||||
| 指标 | 修复前 | 修复后(预期) |
|
||||
| ----------------------------- | ------------------------- | ------------------------ |
|
||||
| DB 连接创建次数 | 485 次/会话 | 1 次 |
|
||||
| HealthCheck 定时器 | 586 个泄漏 | 1 个 |
|
||||
| 信号处理器注册 | ~400 次重复 | 1 次 |
|
||||
| Console 拦截层数 | ~400 层嵌套 | 1 层 |
|
||||
| 内存使用峰值 | 2.4 GB → OOM 重启 | 预期 < 500 MB |
|
||||
| 冷启动 Ready | 3.4s | ~3s(略快) |
|
||||
| 内存重启 Ready | 82.6s | 不再触发内存重启 |
|
||||
| Login 页首次编译 | 3.7s | ~0.5s (需启用 Turbopack) |
|
||||
| Provider 详情页首次编译 | 22s | ~2-3s (需启用 Turbopack) |
|
||||
| `node:crypto` 构建错误 | 反复出现 | 消除 |
|
||||
| Edge Runtime 编译警告 | 每次热编译刷出 10+ 条 | **0 条** |
|
||||
| instrumentation 启动耗时 | 13.9s(含 Edge 模块追踪) | **1.25s** |
|
||||
| instrumentation import 并行度 | 9 次串行 import | 3 批并行 import |
|
||||
|
||||
---
|
||||
|
||||
## 五、涉及文件清单
|
||||
|
||||
| 区域 | 文件 | 改动类型 |
|
||||
| ------------------- | ------------------------------- | ------------------------------------------------------------------ |
|
||||
| DB 单例 | `src/lib/db/core.ts` | `let _db` → `globalThis.__omnirouteDb` |
|
||||
| Token 健康检查 | `src/lib/tokenHealthCheck.ts` | `let initialized` → `globalThis.__omnirouteTokenHC` |
|
||||
| 本地节点健康检查 | `src/lib/localHealthCheck.ts` | `let initialized` → `globalThis.__omnirouteLocalHC` |
|
||||
| Console 拦截 | `src/lib/consoleInterceptor.ts` | `let initialized` → `globalThis.__omnirouteConsoleInterceptorInit` |
|
||||
| 优雅关停 | `src/lib/gracefulShutdown.ts` | 新增 `globalThis.__omnirouteShutdownInit` 守卫 |
|
||||
| Dev 启动脚本 | `scripts/run-next.mjs` | 新增 `OMNIROUTE_USE_TURBOPACK=1` 开关 |
|
||||
| Proxy 注册表 | `src/lib/db/proxies.ts` | `node:crypto` → `crypto` |
|
||||
| API 错误响应 | `src/lib/api/errorResponse.ts` | `node:crypto` → `crypto` |
|
||||
| 启动钩子(主入口) | `src/instrumentation.ts` | 精简为 ~20 行,计算 import 路径阻止 Edge 追踪 |
|
||||
| 启动钩子(Node.js) | `src/instrumentation-node.ts` | 新文件,承载全部 Node.js 启动逻辑 + `Promise.all` 并行 |
|
||||
|
||||
---
|
||||
|
||||
## 六、回退方案
|
||||
|
||||
- **启用 Turbopack**:设置 `OMNIROUTE_USE_TURBOPACK=1` 环境变量;不设置则默认使用 Webpack(原有行为不变)。
|
||||
- **globalThis 方案异常**:所有 globalThis key 都以 `__omniroute` 为前缀,可通过 `delete globalThis.__omnirouteDb` 等方式手动重置。
|
||||
- **Edge 警告回退**:若 `instrumentation-node.ts` 拆分导致问题,可将其内容合并回 `instrumentation.ts`,恢复为直接 `import()` 调用(警告会重新出现但不影响功能)。
|
||||
- **生产环境**:以上修复对生产构建无负面影响——生产环境不存在 HMR,globalThis 单例仅在首次调用时初始化一次。计算 import 路径在 `next build` 时由 Node.js 正常解析,不影响打包产物。
|
||||
|
||||
---
|
||||
|
||||
## 七、单元测试与备份恢复(pre-commit 验证通过)
|
||||
|
||||
为保证提交前必须通过验证(不再使用 `--no-verify`),对以下失败用例与生产逻辑做了修复与加固。
|
||||
|
||||
### 问题与根因
|
||||
|
||||
| 失败项 | 根因 |
|
||||
| ------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| bootstrap-env 4 个用例 | Windows 上 DATA_DIR 解析用 `APPDATA`/`homedir()`,测试只设了 `HOME`,脚本读不到测试用的 `.env`。 |
|
||||
| domain-persistence costRules 2 个用例 | `core` 在首次 import 时缓存 `DATA_DIR`;测试每测一个 tmpDir 并在 afterEach 删目录,导致后续 describe 使用的 DB 路径已被删,读写得到 0。 |
|
||||
| fixes-p1 restoreDbBackup | 测试在 DB 仍打开时写 stale 侧文件;`restoreDbBackup` 内 pre-restore 备份未 await 就关库,Windows 上句柄未及时释放,unlink 报 EBUSY。 |
|
||||
| fixes-p1 resetStorage 及后续用例 | 上一测留下 DB 打开,下一测 `resetStorage()` 删目录时文件仍被占用,EBUSY。 |
|
||||
|
||||
### 修复 6:bootstrap-env 测试(tests/unit/bootstrap-env.test.mjs)
|
||||
|
||||
在每个用例的 `withTempEnv` 回调开头增加 `process.env.DATA_DIR = dataDir`,使脚本在任意平台(含 Windows)都使用测试临时目录,而不是依赖 `HOME`/`APPDATA`。
|
||||
|
||||
### 修复 7:domain-persistence 测试(tests/unit/domain-persistence.test.mjs)
|
||||
|
||||
- **单例 tmpDir**:全文件共用一个 `fileTmpDir`,在模块加载时创建并设置 `process.env.DATA_DIR`,与 `core` 首次加载时缓存的路径一致。
|
||||
- **每测清 DB 不清目录**:`beforeEach` 中 `resetDbInstance()` 后删除 `storage.sqlite` 及其 `-wal`/`-shm`/`-journal`,保证每测干净 DB,不在 afterEach 删目录,避免路径失效。
|
||||
- **收尾**:`after()` 中恢复 `DATA_DIR` 并删除 `fileTmpDir`。
|
||||
- **costRules 断言**:改为小容差精确校验(`assertAlmostEqual`),继续验证 `4.5` / `4.0` 这类业务关键值,避免把真实累计错误放过去。
|
||||
|
||||
### 修复 8:fixes-p1 测试(tests/unit/fixes-p1.test.mjs)
|
||||
|
||||
- **restoreDbBackup 用例**:在写入 stale 侧文件前调用 `core.resetDbInstance()`,避免 DB 仍打开时写 `-wal`/`-shm` 触发 Windows 锁错误。
|
||||
- **Windows 跳过**:该用例在 Windows 上仍使用 `test(..., { skip: isWindows })`。原因不是业务逻辑不支持 Windows,而是 better-sqlite3 关闭后底层句柄释放存在时序抖动,这条真实 sidecar 集成测试容易退化成不稳定的文件锁测试;Linux/macOS 上照常运行。
|
||||
- **核心兜底测试**:新增平台无关的 `unlinkFileWithRetry` 单测,直接模拟 `EBUSY` / `EPERM` 后重试并最终成功,确保 Windows 相关的重试删除逻辑被稳定覆盖,而不是完全依赖 flaky 的真实文件锁时序。
|
||||
- **resetStorage**:改为 async,对 `rmSync(TEST_DATA_DIR)` 做最多 10 次、间隔 100ms 的 EBUSY/EPERM 重试,避免下一测因上一测句柄未释放而失败。
|
||||
|
||||
### 修复 9:备份恢复逻辑(src/lib/db/backup.ts)
|
||||
|
||||
- **pre-restore 备份改为同步等待**:在 `restoreDbBackup` 内用内联逻辑做 pre-restore 备份并 `await` 完成,再调用 `resetDbInstance()`,避免异步 backup 未结束就关库导致后续 unlink 失败。
|
||||
- **节流语义保持一致**:pre-restore 备份成功后补回 `_lastBackupAt = Date.now()`,避免恢复后紧接着又触发一轮额外自动备份。
|
||||
- **关库后短延迟**:`resetDbInstance()` 后 `await new Promise(r => setTimeout(r, 500))`,再执行 unlink,给 Windows 等平台释放句柄留时间。
|
||||
- **unlink 重试**:将主库及 `-wal`/`-shm`/`-journal` 的删除提取为 `unlinkFileWithRetry`,统一做最多 10 次、间隔 100ms 的 EBUSY/EPERM 重试,提高恢复流程在锁释放较慢环境下的成功率,也便于单测直接覆盖重试逻辑。
|
||||
|
||||
### 涉及文件(本节)
|
||||
|
||||
| 区域 | 文件 | 改动类型 |
|
||||
| -------- | ---------------------------------------- | --------------------------------------------------------------------------------------------------------------- |
|
||||
| 单元测试 | `tests/unit/bootstrap-env.test.mjs` | 各用例内设置 `process.env.DATA_DIR = dataDir` |
|
||||
| 单元测试 | `tests/unit/domain-persistence.test.mjs` | 单例 tmpDir、beforeEach 清 DB 文件、after 删目录;costRules 改为小容差精确断言 |
|
||||
| 单元测试 | `tests/unit/fixes-p1.test.mjs` | restoreDbBackup 前 resetDbInstance、Windows skip 说明、resetStorage 重试、`unlinkFileWithRetry` 核心单测 |
|
||||
| 备份恢复 | `src/lib/db/backup.ts` | pre-restore 内联并 await、恢复 `_lastBackupAt` 节流语义、关库后 500ms 延迟、抽取 `unlinkFileWithRetry` 重试删除 |
|
||||
332
ZWS_README_V5.md
@@ -1,332 +0,0 @@
|
||||
# ZWS_README_V5 — 按协议配置模型兼容性 + 前端性能优化
|
||||
|
||||
V4 内容(HMR 泄漏修复、Edge 警告消除、测试稳定性)已完成;V5 在 V4 基础上实现**按协议维度配置模型兼容性**,新增前端查找性能优化与类型安全改进。
|
||||
|
||||
---
|
||||
|
||||
## 一、如何发现问题
|
||||
|
||||
### 现象
|
||||
|
||||
- 同一模型被 **OpenAI Chat Completions**、**OpenAI Responses API**、**Anthropic Messages** 三种客户端请求形态调用时,V2 的兼容性开关(工具 ID 9 位、不保留 developer 角色)是**全局生效**的——无法为不同协议设置不同的兼容策略。
|
||||
- 例如:用户希望 OpenAI Responses API 请求时不保留 developer 角色(MiniMax 422 修复),但 OpenAI Chat Completions 请求时保留。V2 下只能二选一。
|
||||
- 前端兼容性弹层未标明当前配置对应哪种协议,容易误导。
|
||||
- 前端组件中 `Array.find()` 在每次渲染时对 customModels 和 modelCompatOverrides 做 O(n) 线性扫描,模型数量多时存在不必要的性能开销。
|
||||
- `ModelCompatPatch` 类型定义与运行时逻辑不一致:`preserveOpenAIDeveloperRole` 字段需要支持 `null`(表示取消设置/恢复默认),但类型仅允许 `boolean`。
|
||||
|
||||
### 排查过程
|
||||
|
||||
1. **需求分析**:梳理 `detectFormat(body)` 返回的三种协议键(`openai`、`openai-responses`、`claude`),确认每种协议对 developer 角色和 tool call ID 的需求不同。
|
||||
2. **数据模型设计**:在现有 `normalizeToolCallId` / `preserveOpenAIDeveloperRole` 顶层字段基础上,设计 `compatByProtocol` 嵌套结构,按协议键细分。
|
||||
3. **构建问题**:客户端 `"use client"` 组件直接从 `@/lib/localDb` 引入常量时,间接拉入了 `node:crypto`(经由 `db/proxies.ts`),触发 Webpack `UnhandledSchemeError`。需将常量拆到 `shared/` 层。
|
||||
4. **前端性能**:通过 React DevTools 和代码审计发现 `effectiveNormalizeForProtocol` 等函数每次调用都对数组做 `find()`,在渲染列表时存在 O(n²) 的隐患。
|
||||
|
||||
---
|
||||
|
||||
## 二、根因分析
|
||||
|
||||
### 根因 1(P0):兼容选项无协议维度
|
||||
|
||||
V2 的 `normalizeToolCallId` / `preserveOpenAIDeveloperRole` 存储在模型级别的顶层字段,无法区分请求来源协议。`chatCore.ts` 中的 getter 函数只接收 `(providerId, modelId)` 两个参数,不感知当前请求的 `sourceFormat`。
|
||||
|
||||
**影响**:跨协议场景下用户只能设置一个全局值,无法精确控制。
|
||||
|
||||
### 根因 2(P1):客户端构建拉入 Node.js 模块
|
||||
|
||||
`page.tsx`("use client")→ `@/lib/localDb` → `db/proxies.ts` → `import { randomUUID } from "node:crypto"`
|
||||
|
||||
Webpack 无法处理 `node:` URI scheme,报 `UnhandledSchemeError`。虽然 V4 已将 `node:crypto` → `crypto` 修复了 `proxies.ts`,但 `localDb.ts` 的 barrel export 链仍然存在风险——客户端组件不应引入任何可能传递到 Node.js 模块的路径。
|
||||
|
||||
### 根因 3(P2):前端查找性能
|
||||
|
||||
`effectiveNormalizeForProtocol`、`effectivePreserveForProtocol`、`anyNormalizeCompatBadge`、`anyNoPreserveCompatBadge` 四个函数每次调用都使用 `Array.find()` 在 `customModels` 和 `modelCompatOverrides` 数组中查找目标模型。在模型列表渲染时,每个模型行会调用多次这些函数,导致 O(n × m) 的查找开销(n = 模型数,m = 每行调用次数)。
|
||||
|
||||
### 根因 4(P2):类型定义与运行时不一致
|
||||
|
||||
```typescript
|
||||
// V3 暂存区版本(有问题)
|
||||
export type ModelCompatPatch = Partial<
|
||||
Pick<
|
||||
ModelCompatOverride,
|
||||
"normalizeToolCallId" | "preserveOpenAIDeveloperRole" | "compatByProtocol"
|
||||
>
|
||||
>;
|
||||
```
|
||||
|
||||
`ModelCompatOverride.preserveOpenAIDeveloperRole` 类型为 `boolean | undefined`,但 `mergeModelCompatOverride()` 内部有 `=== null` 判断(用于取消设置/恢复默认),类型层面无法覆盖。
|
||||
|
||||
---
|
||||
|
||||
## 三、修复方案
|
||||
|
||||
### 修复 1:`compatByProtocol` 存储与读取(models.ts)
|
||||
|
||||
**新增数据结构**:
|
||||
|
||||
```typescript
|
||||
type CompatByProtocolMap = Partial<Record<ModelCompatProtocolKey, ModelCompatPerProtocol>>;
|
||||
|
||||
export type ModelCompatOverride = {
|
||||
id: string;
|
||||
normalizeToolCallId?: boolean;
|
||||
preserveOpenAIDeveloperRole?: boolean;
|
||||
compatByProtocol?: CompatByProtocolMap; // 新增
|
||||
};
|
||||
```
|
||||
|
||||
**读取优先级链**(适用于 `getModelNormalizeToolCallId` 和 `getModelPreserveOpenAIDeveloperRole`):
|
||||
|
||||
```
|
||||
compatByProtocol[sourceFormat].field → 顶层 field → 默认值
|
||||
```
|
||||
|
||||
1. 若 `sourceFormat` 属于已知协议键(`openai` / `openai-responses` / `claude`),且 `compatByProtocol[sourceFormat]` 中存在目标字段,使用该值。
|
||||
2. 否则回退到顶层字段。
|
||||
3. 顶层字段也不存在时使用默认值(normalizeToolCallId=false,preserveOpenAIDeveloperRole=undefined)。
|
||||
|
||||
**深度合并逻辑** `deepMergeCompatByProtocol()`:
|
||||
|
||||
- 对每个协议键,逐字段合并而非覆盖。
|
||||
- `normalizeToolCallId=false` 时删除该字段(不存储 false,减少冗余)。
|
||||
- 合并后若整个协议条目为空对象,删除该协议条目。
|
||||
- 协议键通过 `isCompatProtocolKey()` 白名单校验,拒绝未知键。
|
||||
|
||||
**Getter 签名扩展**(向后兼容,第三参数可选):
|
||||
|
||||
```typescript
|
||||
export function getModelNormalizeToolCallId(
|
||||
providerId: string,
|
||||
modelId: string,
|
||||
sourceFormat?: string | null
|
||||
): boolean;
|
||||
|
||||
export function getModelPreserveOpenAIDeveloperRole(
|
||||
providerId: string,
|
||||
modelId: string,
|
||||
sourceFormat?: string | null
|
||||
): boolean | undefined;
|
||||
```
|
||||
|
||||
**优点**:
|
||||
|
||||
- 完全向后兼容:无 `sourceFormat` 参数时行为与 V2 完全一致。
|
||||
- 协议键白名单校验防止存储污染。
|
||||
- 深度合并保留未变更协议的配置。
|
||||
|
||||
**缺点/注意**:
|
||||
|
||||
- JSON 存储体积略增(每个模型最多增加 3 个协议条目)。
|
||||
- 新增 ~80 行 TypeScript 代码。
|
||||
|
||||
### 修复 2:请求管线传入 sourceFormat(chatCore.ts)
|
||||
|
||||
```typescript
|
||||
const normalizeToolCallId = getModelNormalizeToolCallId(
|
||||
provider || "",
|
||||
model || "",
|
||||
sourceFormat // 新增第三参
|
||||
);
|
||||
const preserveDeveloperRole = getModelPreserveOpenAIDeveloperRole(
|
||||
provider || "",
|
||||
model || "",
|
||||
sourceFormat // 新增第三参
|
||||
);
|
||||
```
|
||||
|
||||
`sourceFormat` 由已有的 `detectFormat(body)` 返回,无需新增检测逻辑。
|
||||
|
||||
**优点**:
|
||||
|
||||
- 改动仅 2 行,精准传参。
|
||||
- 不影响其他 handler(embeddings、imageGeneration 等不涉及 developer 角色和 tool call ID)。
|
||||
|
||||
### 修复 3:API 路由支持 compatByProtocol(route.ts)
|
||||
|
||||
**PUT 请求体扩展**:
|
||||
|
||||
- 解构 `compatByProtocol` 并传入 `updateCustomModel()`。
|
||||
- `compatOnly` 判断扩展:仅含 `provider` + `modelId` + 兼容字段时,走 `mergeModelCompatOverride()` 路径。
|
||||
- 使用 `ModelCompatPatch` 类型替代行内类型定义,统一类型来源。
|
||||
|
||||
**Zod 校验 schema**:
|
||||
|
||||
```typescript
|
||||
const modelCompatPerProtocolSchema = z.object({
|
||||
normalizeToolCallId: z.boolean().optional(),
|
||||
preserveOpenAIDeveloperRole: z.boolean().optional(),
|
||||
}).strict(); // strict: 拒绝额外字段
|
||||
|
||||
compatByProtocol: z
|
||||
.record(z.enum(["openai", "openai-responses", "claude"]), modelCompatPerProtocolSchema)
|
||||
.optional(),
|
||||
```
|
||||
|
||||
**优点**:
|
||||
|
||||
- `.strict()` 防止客户端注入额外字段。
|
||||
- `z.enum()` 限定协议键,与后端白名单一致。
|
||||
- 仅传 `compatByProtocol` 即可更新,前端无需拼装完整模型对象。
|
||||
|
||||
### 修复 4:客户端安全常量拆分(modelCompat.ts)
|
||||
|
||||
**新增** `src/shared/constants/modelCompat.ts`:
|
||||
|
||||
```typescript
|
||||
export const MODEL_COMPAT_PROTOCOL_KEYS = ["openai", "openai-responses", "claude"] as const;
|
||||
export type ModelCompatProtocolKey = (typeof MODEL_COMPAT_PROTOCOL_KEYS)[number];
|
||||
```
|
||||
|
||||
- 不依赖 Node.js / DB 代码,客户端组件可安全引入。
|
||||
- `models.ts` 从此模块引入并再导出。
|
||||
- `localDb.ts` 新增 `ModelCompatPatch` 类型导出(供 route.ts 使用),不导出协议常量。
|
||||
- `page.tsx` 改为从 `@/shared/constants/modelCompat` 引入。
|
||||
|
||||
**优点**:
|
||||
|
||||
- 彻底切断客户端 → localDb → db → proxies → node:crypto 的依赖链。
|
||||
- 协议键定义单一来源(Single Source of Truth)。
|
||||
|
||||
### 修复 5:前端协议选择器与按协议解析(page.tsx)
|
||||
|
||||
**ModelCompatPopover 重构**:
|
||||
|
||||
- 新增协议下拉选择器(`<select>`),可选 OpenAI Chat / OpenAI Responses / Anthropic Messages。
|
||||
- 两个开关(工具 ID 9 位、不保留 developer)**针对选中协议**生效。
|
||||
- 选择 Claude 协议时隐藏 developer 角色开关(developer 仅对 OpenAI 系有意义)。
|
||||
- 保存时以 `{ compatByProtocol: { [protocol]: payload } }` 形式提交,后端按协议合并。
|
||||
- 深色模式适配:下拉框使用 `bg-white dark:bg-zinc-800`、`text-zinc-900 dark:text-zinc-100`。
|
||||
|
||||
**Props 接口重构**:
|
||||
|
||||
旧接口(4 个独立值/回调):
|
||||
|
||||
```typescript
|
||||
(normalizeToolCallId, preserveDeveloperRole, onNormalizeChange, onPreserveChange);
|
||||
```
|
||||
|
||||
新接口(3 个函数式 props):
|
||||
|
||||
```typescript
|
||||
effectiveModelNormalize: (protocol: string) => boolean
|
||||
effectiveModelPreserveDeveloper: (protocol: string) => boolean
|
||||
onCompatPatch: (protocol: string, payload: {...}) => void
|
||||
```
|
||||
|
||||
所有消费方(`ModelRow`、`PassthroughModelRow`、`CustomModelsSection`、`CompatibleModelsSection`)已同步更新。
|
||||
|
||||
**角标显示逻辑**:
|
||||
|
||||
- `anyNormalizeCompatBadge()`:任意协议或顶层存在 `normalizeToolCallId=true` 即显示「ID×9」角标。
|
||||
- `anyNoPreserveCompatBadge()`:任意协议或顶层存在 `preserveOpenAIDeveloperRole=false` 即显示「不保留」角标。
|
||||
|
||||
**CustomModelsSection 增强**:
|
||||
|
||||
- 新增 `modelCompatOverrides` 状态,从 API 响应中获取。
|
||||
- 新增 `saveCustomCompat()` 函数,支持仅传 `compatByProtocol` 的独立保存。
|
||||
|
||||
### 修复 6:前端 Map 查找性能优化(page.tsx)
|
||||
|
||||
**问题**:`effectiveNormalizeForProtocol` 等函数对 `customModels` 和 `modelCompatOverrides` 用 `Array.find()` 做 O(n) 查找,在列表渲染时每个模型行多次调用。
|
||||
|
||||
**方案**:使用 `useMemo` + `Map` 将数组预建为 O(1) 查找表。
|
||||
|
||||
```typescript
|
||||
type CompatModelMap = Map<string, CompatModelRow>;
|
||||
|
||||
function buildCompatMap(rows: CompatModelRow[]): CompatModelMap {
|
||||
const m = new Map<string, CompatModelRow>();
|
||||
for (const r of rows) if (r.id) m.set(r.id, r);
|
||||
return m;
|
||||
}
|
||||
|
||||
// 在组件内
|
||||
const customMap = useMemo(() => buildCompatMap(modelMeta.customModels), [modelMeta.customModels]);
|
||||
const overrideMap = useMemo(
|
||||
() => buildCompatMap(modelMeta.modelCompatOverrides),
|
||||
[modelMeta.modelCompatOverrides]
|
||||
);
|
||||
```
|
||||
|
||||
所有查找函数签名从 `(modelId, protocol, customModels[], overrides[])` 改为 `(modelId, protocol, customMap, overrideMap)`,内部使用 `Map.get()` 替代 `Array.find()`。
|
||||
|
||||
**优点**:
|
||||
|
||||
- 查找从 O(n) 降为 O(1)。
|
||||
- `useMemo` 依赖项正确,仅在数据变化时重建 Map。
|
||||
- `CustomModelsSection` 内部也独立构建 Map,不依赖父组件。
|
||||
|
||||
### 修复 7:ModelCompatPatch 类型修正(models.ts)
|
||||
|
||||
```typescript
|
||||
// 修复后 — 显式允许 null
|
||||
export type ModelCompatPatch = {
|
||||
normalizeToolCallId?: boolean;
|
||||
preserveOpenAIDeveloperRole?: boolean | null; // null = 取消设置/恢复默认
|
||||
compatByProtocol?: CompatByProtocolMap;
|
||||
};
|
||||
```
|
||||
|
||||
与 `mergeModelCompatOverride()` 内的 `=== null` 判断逻辑一致,类型安全。
|
||||
|
||||
### 修复 8:CompatByProtocolMap 类型收紧(page.tsx)
|
||||
|
||||
客户端 `CompatByProtocolMap` 从 `Record<string, ...>` 改为 `Record<ModelCompatProtocolKey, ...>`,增强类型安全,防止传入未知协议键。
|
||||
|
||||
### 修复 9:i18n 文案新增
|
||||
|
||||
| 键名 | 中文 | 英文 |
|
||||
| ------------------------------- | --------------------------------------------- | -------------------------------------------------------------- |
|
||||
| `compatProtocolLabel` | 客户端请求协议 | Client request protocol |
|
||||
| `compatProtocolHint` | 以下选项在 OmniRoute 识别到该请求形态时生效。 | These options apply when OmniRoute detects this request shape. |
|
||||
| `compatProtocolOpenAI` | OpenAI Chat Completions | OpenAI Chat Completions |
|
||||
| `compatProtocolOpenAIResponses` | OpenAI Responses API | OpenAI Responses API |
|
||||
| `compatProtocolClaude` | Anthropic Messages | Anthropic Messages |
|
||||
|
||||
---
|
||||
|
||||
## 四、使用方式
|
||||
|
||||
1. 点击模型行的 **「兼容性」** 按钮。
|
||||
2. 在弹层内先选择 **「客户端请求协议」**(OpenAI Chat / OpenAI Responses / Anthropic Messages)。
|
||||
3. 勾选该协议下的「工具 ID 9 位」或「不保留 developer 角色」。
|
||||
4. 保存后,仅在该协议形态的请求下生效。
|
||||
5. 未配置某协议时,该协议下行为回退到顶层兼容字段(若存在),再回退到默认值(保留 developer、不规范化 tool id)。
|
||||
6. 角标「ID×9」「不保留」在任意协议存在对应配置时显示。
|
||||
|
||||
---
|
||||
|
||||
## 五、预期效果
|
||||
|
||||
| 指标 | 修复前 | 修复后 |
|
||||
| ------------------------- | --------------------- | ------------------------------------------ |
|
||||
| 兼容性配置维度 | 全局(模型级) | 按协议(OpenAI Chat / Responses / Claude) |
|
||||
| developer 角色精确控制 | 不支持 | 支持(如:仅 Responses API 不保留) |
|
||||
| 前端兼容性查找性能 | O(n) Array.find | O(1) Map.get(useMemo 缓存) |
|
||||
| ModelCompatPatch 类型安全 | null 值无类型覆盖 | 显式 `boolean \| null` |
|
||||
| 客户端构建风险 | 可能引入 Node.js 模块 | 已隔离(shared/constants 层) |
|
||||
| API 验证 | 无 compatByProtocol | Zod strict schema 校验 |
|
||||
| 深色模式 | 协议选择器不可读 | bg/text 适配 dark 主题 |
|
||||
|
||||
---
|
||||
|
||||
## 六、涉及文件清单
|
||||
|
||||
| 区域 | 文件 | 改动类型 |
|
||||
| ---------- | ----------------------------------------------------------- | ------------------------------------------------------------------------------------------------ |
|
||||
| 协议常量 | `src/shared/constants/modelCompat.ts` | **新建**,客户端安全的协议键与类型 |
|
||||
| 存储与读写 | `src/lib/db/models.ts` | `compatByProtocol` 数据结构、深度合并、getter 第三参 `sourceFormat`、`ModelCompatPatch` 类型修正 |
|
||||
| 再导出层 | `src/lib/localDb.ts` | 新增 `ModelCompatPatch` 类型导出 |
|
||||
| API 路由 | `src/app/api/provider-models/route.ts` | PUT 支持 `compatByProtocol`,使用 `ModelCompatPatch` 类型 |
|
||||
| 输入校验 | `src/shared/validation/schemas.ts` | `modelCompatPerProtocolSchema`(strict)+ `compatByProtocol` 记录校验 |
|
||||
| 请求管线 | `open-sse/handlers/chatCore.ts` | `getModelNormalizeToolCallId` / `getModelPreserveOpenAIDeveloperRole` 传入 `sourceFormat` |
|
||||
| 前端 UI | `src/app/(dashboard)/dashboard/providers/[id]/page.tsx` | 协议选择器、按协议解析/保存、角标逻辑、Map 性能优化、类型收紧 |
|
||||
| i18n | `src/i18n/messages/en.json`,`src/i18n/messages/zh-CN.json` | 5 条新文案 |
|
||||
|
||||
---
|
||||
|
||||
## 七、回退方案
|
||||
|
||||
- **禁用按协议配置**:删除 `compatByProtocol` 字段后,getter 自动回退到顶层字段,行为与 V2 一致。
|
||||
- **前端 Map 优化回退**:将 `Map.get()` 改回 `Array.find()` 即可,纯性能优化无功能耦合。
|
||||
- **客户端常量回退**:将 `MODEL_COMPAT_PROTOCOL_KEYS` 定义移回 `models.ts` 并从 `localDb.ts` 导出(需同时确保 `node:crypto` 问题不再存在)。
|
||||
- **生产环境**:以上修复对生产构建无负面影响。`compatByProtocol` 为可选字段,未配置时默认行为不变。API Zod 校验确保不会接受畸形数据。
|
||||
@@ -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 |
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
openapi: 3.1.0
|
||||
info:
|
||||
title: OmniRoute API
|
||||
version: 3.0.0-rc.15
|
||||
version: 3.0.0-rc.16
|
||||
description: |
|
||||
OmniRoute is a local-first AI API proxy router. It provides an OpenAI-compatible
|
||||
endpoint that routes requests to multiple AI providers with load balancing,
|
||||
|
||||
@@ -1,280 +0,0 @@
|
||||
# Zed IDE OAuth Import - Documentation
|
||||
|
||||
## Overview
|
||||
|
||||
OmniRoute can automatically import OAuth credentials from Zed IDE by accessing the operating system's secure keychain storage. This eliminates manual credential copying and enables seamless integration between Zed IDE and OmniRoute.
|
||||
|
||||
## How It Works
|
||||
|
||||
Zed IDE stores all OAuth tokens in your operating system's native credential storage:
|
||||
- **macOS**: Keychain Access
|
||||
- **Windows**: Credential Manager
|
||||
- **Linux**: libsecret / GNOME Keyring
|
||||
|
||||
As documented in [Zed's official documentation](https://zed.dev/docs/ai/llm-providers):
|
||||
> "API keys are not stored as plain text in your settings file, but rather in your OS's secure credential storage."
|
||||
|
||||
OmniRoute uses the `keytar` library to securely read these credentials with your permission.
|
||||
|
||||
## Supported Providers
|
||||
|
||||
The following Zed IDE providers can be imported:
|
||||
- OpenAI
|
||||
- Anthropic (Claude)
|
||||
- Google AI (Gemini)
|
||||
- Mistral
|
||||
- xAI (Grok)
|
||||
- OpenRouter
|
||||
- DeepSeek
|
||||
|
||||
## Installation
|
||||
|
||||
### Prerequisites
|
||||
|
||||
**Linux users** must install libsecret development files:
|
||||
|
||||
```bash
|
||||
# Debian/Ubuntu
|
||||
sudo apt-get install libsecret-1-dev
|
||||
|
||||
# Red Hat/Fedora
|
||||
sudo yum install libsecret-devel
|
||||
|
||||
# Arch Linux
|
||||
sudo pacman -S libsecret
|
||||
```
|
||||
|
||||
**macOS and Windows** users don't need additional dependencies.
|
||||
|
||||
### Install Dependencies
|
||||
|
||||
```bash
|
||||
npm install keytar
|
||||
```
|
||||
|
||||
Or using pnpm:
|
||||
|
||||
```bash
|
||||
pnpm install keytar
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### API Endpoint
|
||||
|
||||
**Endpoint**: `POST /api/providers/zed/import`
|
||||
|
||||
**Request**:
|
||||
```bash
|
||||
curl -X POST http://localhost:20128/api/providers/zed/import \
|
||||
-H "Content-Type: application/json"
|
||||
```
|
||||
|
||||
**Response** (success):
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"count": 3,
|
||||
"providers": ["openai", "anthropic", "google"],
|
||||
"zedInstalled": true
|
||||
}
|
||||
```
|
||||
|
||||
**Response** (Zed not installed):
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"error": "Zed IDE does not appear to be installed on this system.",
|
||||
"zedInstalled": false
|
||||
}
|
||||
```
|
||||
|
||||
**Response** (permission denied):
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"error": "Keychain access denied. Please grant permission when prompted by your OS."
|
||||
}
|
||||
```
|
||||
|
||||
### Programmatic Usage
|
||||
|
||||
```typescript
|
||||
import {
|
||||
discoverZedCredentials,
|
||||
getZedCredential,
|
||||
isZedInstalled
|
||||
} from '@/lib/zed-oauth/keychain-reader';
|
||||
|
||||
// Check if Zed is installed
|
||||
const installed = await isZedInstalled();
|
||||
|
||||
// Discover all credentials
|
||||
const credentials = await discoverZedCredentials();
|
||||
console.log(`Found ${credentials.length} credentials`);
|
||||
|
||||
// Get specific provider
|
||||
const openaiCred = await getZedCredential('openai');
|
||||
if (openaiCred) {
|
||||
console.log(`OpenAI token: ${openaiCred.token.substring(0, 10)}...`);
|
||||
}
|
||||
```
|
||||
|
||||
## Security
|
||||
|
||||
### Permission Prompt
|
||||
|
||||
The first time OmniRoute accesses the keychain, your operating system will prompt for permission:
|
||||
|
||||
- **macOS**: "OmniRoute wants to access your keychain"
|
||||
- **Windows**: UAC prompt or Credential Manager authorization
|
||||
- **Linux**: "Authentication required to access the default keyring"
|
||||
|
||||
You can grant:
|
||||
- **Allow Once**: Permission for this session only
|
||||
- **Always Allow**: Permanent access (until revoked)
|
||||
- **Deny**: Credential import will fail
|
||||
|
||||
### Data Handling
|
||||
|
||||
1. **No Master Password Storage**: OmniRoute never stores your keychain master password
|
||||
2. **Minimal Access**: Only reads Zed-specific credential entries
|
||||
3. **Encryption at Rest**: Imported tokens are encrypted using AES-256-GCM in OmniRoute's database
|
||||
4. **Audit Logging**: All import attempts are logged for security tracking
|
||||
|
||||
### Revoking Access
|
||||
|
||||
To revoke OmniRoute's keychain access:
|
||||
|
||||
**macOS**:
|
||||
1. Open **Keychain Access** app
|
||||
2. Go to **Keychain Access** → **Preferences** → **Access Control**
|
||||
3. Remove OmniRoute from the allowed applications list
|
||||
|
||||
**Windows**:
|
||||
1. Open **Credential Manager**
|
||||
2. Find OmniRoute entries
|
||||
3. Remove or modify permissions
|
||||
|
||||
**Linux (GNOME)**:
|
||||
1. Open **Seahorse** (Passwords and Keys)
|
||||
2. Find OmniRoute entries under Login keyring
|
||||
3. Remove or edit access control
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### "Keychain access denied" Error
|
||||
|
||||
**Cause**: User denied permission prompt or previous denial cached.
|
||||
|
||||
**Solution**:
|
||||
1. Retry the import (permission prompt will appear again)
|
||||
2. Check system keychain settings (see "Revoking Access" section)
|
||||
3. On macOS, restart Keychain Access app
|
||||
|
||||
### "Keychain service not available" Error
|
||||
|
||||
**Cause**: OS credential storage not configured or missing dependencies.
|
||||
|
||||
**Solution** (Linux):
|
||||
```bash
|
||||
# Install libsecret
|
||||
sudo apt-get install libsecret-1-dev
|
||||
|
||||
# Ensure keyring daemon is running
|
||||
systemctl --user status gnome-keyring-daemon
|
||||
```
|
||||
|
||||
### "Zed IDE does not appear to be installed"
|
||||
|
||||
**Cause**: Zed config directory not found in expected locations.
|
||||
|
||||
**Solution**:
|
||||
- Verify Zed is installed: `zed --version`
|
||||
- Check config exists at:
|
||||
- Linux: `~/.config/zed`
|
||||
- macOS: `~/Library/Application Support/Zed`
|
||||
- Windows: `%APPDATA%\Zed`
|
||||
|
||||
### No Credentials Found
|
||||
|
||||
**Cause**: Zed hasn't stored OAuth tokens yet, or using API keys instead of OAuth.
|
||||
|
||||
**Solution**:
|
||||
1. Open Zed IDE
|
||||
2. Go to Agent Panel settings (⌘/Ctrl+Shift+P → "agent: open settings")
|
||||
3. Add at least one provider with OAuth/API key
|
||||
4. Retry import in OmniRoute
|
||||
|
||||
## Command-Line Alternatives
|
||||
|
||||
For advanced users who prefer manual extraction:
|
||||
|
||||
### macOS
|
||||
|
||||
```bash
|
||||
# Find OpenAI token
|
||||
security find-generic-password -s "zed-openai" -w
|
||||
|
||||
# List all Zed credentials
|
||||
security dump-keychain | grep -i "zed"
|
||||
```
|
||||
|
||||
### Linux (GNOME Keyring)
|
||||
|
||||
```bash
|
||||
# Using secret-tool
|
||||
secret-tool lookup service zed-openai
|
||||
|
||||
# List all Zed entries
|
||||
secret-tool search service zed
|
||||
```
|
||||
|
||||
### Windows (PowerShell)
|
||||
|
||||
```powershell
|
||||
# List Zed credentials
|
||||
cmdkey /list | Select-String "zed"
|
||||
```
|
||||
|
||||
## Technical Reference
|
||||
|
||||
### Service Name Patterns
|
||||
|
||||
Zed IDE uses these service names for keychain storage:
|
||||
|
||||
| Provider | Service Names |
|
||||
|----------|--------------|
|
||||
| OpenAI | `zed-openai`, `ai.zed.openai`, `Zed-OpenAI` |
|
||||
| Anthropic | `zed-anthropic`, `ai.zed.anthropic`, `Zed-Anthropic` |
|
||||
| Google AI | `zed-google`, `ai.zed.google`, `Zed-Google` |
|
||||
| Mistral | `zed-mistral`, `ai.zed.mistral`, `Zed-Mistral` |
|
||||
| xAI | `zed-xai`, `ai.zed.xai`, `Zed-xAI` |
|
||||
| OpenRouter | `zed-openrouter`, `ai.zed.openrouter`, `Zed-OpenRouter` |
|
||||
| DeepSeek | `zed-deepseek`, `ai.zed.deepseek`, `Zed-DeepSeek` |
|
||||
|
||||
### keytar API
|
||||
|
||||
```typescript
|
||||
// Get password for service+account
|
||||
const token = await keytar.getPassword('service-name', 'account-name');
|
||||
|
||||
// Find all credentials for a service
|
||||
const credentials = await keytar.findCredentials('service-name');
|
||||
|
||||
// Set password (not used in import, but available)
|
||||
await keytar.setPassword('service-name', 'account-name', 'password');
|
||||
```
|
||||
|
||||
## References
|
||||
|
||||
- [Zed IDE LLM Providers Documentation](https://zed.dev/docs/ai/llm-providers)
|
||||
- [keytar Library on GitHub](https://github.com/atom/node-keytar)
|
||||
- [VS Code Secret Storage](https://code.visualstudio.com/api/references/vscode-api#SecretStorage)
|
||||
- [GitHub Copilot CLI Authentication](https://docs.github.com/en/copilot/how-tos/copilot-cli/set-up-copilot-cli/authenticate-copilot-cli)
|
||||
|
||||
## Support
|
||||
|
||||
For issues or questions:
|
||||
- Open an issue on [OmniRoute GitHub](https://github.com/diegosouzapw/OmniRoute/issues)
|
||||
- Join the [WhatsApp Community](https://chat.whatsapp.com/JI7cDQ1GyaiDHhVBpLxf8b?mode=gi_t)
|
||||
@@ -29,6 +29,7 @@ const eslintConfig = [
|
||||
ignores: [
|
||||
// Next.js build output
|
||||
".next/**",
|
||||
"src/.next/**",
|
||||
"out/**",
|
||||
"build/**",
|
||||
"next-env.d.ts",
|
||||
|
||||
@@ -26,6 +26,12 @@ const CONFIG_TTL_MS = 60_000;
|
||||
let lastLoadTime = 0;
|
||||
let cachedProviders = null;
|
||||
|
||||
// Survives Next.js dev HMR: module-level cache resets but process is the same (V4 pattern).
|
||||
type CredGlobals = typeof globalThis & { __omnirouteCredNoFileLogged?: boolean };
|
||||
function credGlobals(): CredGlobals {
|
||||
return globalThis as CredGlobals;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the path to provider-credentials.json
|
||||
* Priority: DATA_DIR env → ./data (project root)
|
||||
@@ -51,8 +57,9 @@ export function loadProviderCredentials(providers) {
|
||||
const credPath = resolveCredentialsPath();
|
||||
|
||||
if (!existsSync(credPath)) {
|
||||
if (!cachedProviders) {
|
||||
if (!credGlobals().__omnirouteCredNoFileLogged) {
|
||||
console.log("[CREDENTIALS] No external credentials file found, using defaults.");
|
||||
credGlobals().__omnirouteCredNoFileLogged = true;
|
||||
}
|
||||
cachedProviders = providers;
|
||||
lastLoadTime = Date.now();
|
||||
|
||||
@@ -16,6 +16,8 @@ export interface RegistryModel {
|
||||
toolCalling?: boolean;
|
||||
targetFormat?: string;
|
||||
unsupportedParams?: readonly string[];
|
||||
/** Maximum context window in tokens */
|
||||
contextLength?: number;
|
||||
}
|
||||
|
||||
// Reasoning models reject temperature, top_p, penalties, logprobs, n.
|
||||
@@ -65,6 +67,8 @@ export interface RegistryEntry {
|
||||
chatPath?: string;
|
||||
clientVersion?: string;
|
||||
passthroughModels?: boolean;
|
||||
/** Default context window for all models in this provider (can be overridden per-model) */
|
||||
defaultContextLength?: number;
|
||||
}
|
||||
|
||||
interface LegacyProvider {
|
||||
@@ -137,6 +141,7 @@ export const REGISTRY: Record<string, RegistryEntry> = {
|
||||
urlSuffix: "?beta=true",
|
||||
authType: "oauth",
|
||||
authHeader: "x-api-key",
|
||||
defaultContextLength: 200000,
|
||||
headers: {
|
||||
"Anthropic-Version": "2023-06-01",
|
||||
"Anthropic-Beta":
|
||||
@@ -180,6 +185,7 @@ export const REGISTRY: Record<string, RegistryEntry> = {
|
||||
},
|
||||
authType: "apikey",
|
||||
authHeader: "x-goog-api-key",
|
||||
defaultContextLength: 1000000,
|
||||
oauth: {
|
||||
clientIdEnv: "GEMINI_OAUTH_CLIENT_ID",
|
||||
clientIdDefault: "681255809395-oo8ft2oprdrnp9e3aqf6av3hmdib135j.apps.googleusercontent.com",
|
||||
@@ -216,6 +222,7 @@ export const REGISTRY: Record<string, RegistryEntry> = {
|
||||
},
|
||||
authType: "oauth",
|
||||
authHeader: "bearer",
|
||||
defaultContextLength: 1000000,
|
||||
oauth: {
|
||||
clientIdEnv: "GEMINI_CLI_OAUTH_CLIENT_ID",
|
||||
clientIdDefault: "681255809395-oo8ft2oprdrnp9e3aqf6av3hmdib135j.apps.googleusercontent.com",
|
||||
@@ -247,6 +254,7 @@ export const REGISTRY: Record<string, RegistryEntry> = {
|
||||
baseUrl: "https://chatgpt.com/backend-api/codex/responses",
|
||||
authType: "oauth",
|
||||
authHeader: "bearer",
|
||||
defaultContextLength: 400000,
|
||||
headers: {
|
||||
Version: "0.92.0",
|
||||
"Openai-Beta": "responses=experimental",
|
||||
@@ -399,6 +407,7 @@ export const REGISTRY: Record<string, RegistryEntry> = {
|
||||
responsesBaseUrl: "https://api.githubcopilot.com/responses",
|
||||
authType: "oauth",
|
||||
authHeader: "bearer",
|
||||
defaultContextLength: 128000,
|
||||
headers: {
|
||||
"copilot-integration-id": "vscode-chat",
|
||||
"editor-version": "vscode/1.110.0",
|
||||
@@ -447,6 +456,7 @@ export const REGISTRY: Record<string, RegistryEntry> = {
|
||||
baseUrl: "https://codewhisperer.us-east-1.amazonaws.com/generateAssistantResponse",
|
||||
authType: "oauth",
|
||||
authHeader: "bearer",
|
||||
defaultContextLength: 200000,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Accept: "application/vnd.amazon.eventstream",
|
||||
@@ -473,6 +483,7 @@ export const REGISTRY: Record<string, RegistryEntry> = {
|
||||
chatPath: "/aiserver.v1.ChatService/StreamUnifiedChatWithTools",
|
||||
authType: "oauth",
|
||||
authHeader: "bearer",
|
||||
defaultContextLength: 200000,
|
||||
headers: {
|
||||
"connect-accept-encoding": "gzip",
|
||||
"connect-protocol-version": "1",
|
||||
@@ -501,6 +512,7 @@ export const REGISTRY: Record<string, RegistryEntry> = {
|
||||
baseUrl: "https://api.openai.com/v1/chat/completions",
|
||||
authType: "apikey",
|
||||
authHeader: "bearer",
|
||||
defaultContextLength: 128000,
|
||||
models: [
|
||||
{ id: "gpt-4o", name: "GPT-4o" },
|
||||
{ id: "gpt-4o-mini", name: "GPT-4o Mini" },
|
||||
@@ -522,6 +534,7 @@ export const REGISTRY: Record<string, RegistryEntry> = {
|
||||
urlSuffix: "?beta=true",
|
||||
authType: "apikey",
|
||||
authHeader: "x-api-key",
|
||||
defaultContextLength: 200000,
|
||||
headers: {
|
||||
"Anthropic-Version": "2023-06-01",
|
||||
},
|
||||
@@ -548,6 +561,7 @@ export const REGISTRY: Record<string, RegistryEntry> = {
|
||||
authType: "apikey",
|
||||
authHeader: "Authorization",
|
||||
authPrefix: "Bearer",
|
||||
defaultContextLength: 200000,
|
||||
models: [
|
||||
{ id: "glm-5", name: "GLM-5" },
|
||||
{ id: "kimi-k2.5", name: "Kimi K2.5" },
|
||||
@@ -565,6 +579,7 @@ export const REGISTRY: Record<string, RegistryEntry> = {
|
||||
authType: "apikey",
|
||||
authHeader: "Authorization",
|
||||
authPrefix: "Bearer",
|
||||
defaultContextLength: 200000,
|
||||
models: [
|
||||
{ id: "minimax-m2.5-free", name: "MiniMax M2.5 Free" },
|
||||
{ id: "big-pickle", name: "Big Pickle" },
|
||||
@@ -580,6 +595,7 @@ export const REGISTRY: Record<string, RegistryEntry> = {
|
||||
baseUrl: "https://openrouter.ai/api/v1/chat/completions",
|
||||
authType: "apikey",
|
||||
authHeader: "bearer",
|
||||
defaultContextLength: 128000,
|
||||
headers: {
|
||||
"HTTP-Referer": "https://endpoint-proxy.local",
|
||||
"X-Title": "Endpoint Proxy",
|
||||
@@ -593,6 +609,7 @@ export const REGISTRY: Record<string, RegistryEntry> = {
|
||||
format: "claude",
|
||||
executor: "default",
|
||||
baseUrl: "https://api.z.ai/api/anthropic/v1/messages",
|
||||
defaultContextLength: 200000,
|
||||
urlSuffix: "?beta=true",
|
||||
authType: "apikey",
|
||||
authHeader: "x-api-key",
|
||||
@@ -1353,7 +1370,7 @@ export const REGISTRY: Record<string, RegistryEntry> = {
|
||||
{ id: "qwen/qwen3-32b", name: "Qwen3 32B (Puter)" },
|
||||
{ id: "qwen/qwen3-coder", name: "Qwen3 Coder 480B (Puter)" },
|
||||
],
|
||||
passthroughModels: true, // 500+ models available — users can type any Puter model ID
|
||||
passthroughModels: true, // 500+ models available — users can type arbitrary Puter model IDs
|
||||
},
|
||||
|
||||
"cloudflare-ai": {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import crypto from "crypto";
|
||||
import { BaseExecutor } from "./base.ts";
|
||||
import { BaseExecutor, mergeUpstreamExtraHeaders } from "./base.ts";
|
||||
import { PROVIDERS, OAUTH_ENDPOINTS, HTTP_STATUS } from "../config/constants.ts";
|
||||
|
||||
const MAX_RETRY_AFTER_MS = 10000;
|
||||
@@ -198,7 +198,7 @@ export class AntigravityExecutor extends BaseExecutor {
|
||||
return totalMs > 0 ? totalMs : null;
|
||||
}
|
||||
|
||||
async execute({ model, body, stream, credentials, signal, log }) {
|
||||
async execute({ model, body, stream, credentials, signal, log, upstreamExtraHeaders }) {
|
||||
const fallbackCount = this.getFallbackCount();
|
||||
let lastError = null;
|
||||
let lastStatus = 0;
|
||||
@@ -208,6 +208,7 @@ export class AntigravityExecutor extends BaseExecutor {
|
||||
for (let urlIndex = 0; urlIndex < fallbackCount; urlIndex++) {
|
||||
const url = this.buildUrl(model, stream, urlIndex);
|
||||
const headers = this.buildHeaders(credentials, stream);
|
||||
mergeUpstreamExtraHeaders(headers, upstreamExtraHeaders);
|
||||
const transformedBody = this.transformRequest(model, body, stream, credentials);
|
||||
|
||||
// Initialize retry counter for this URL
|
||||
|
||||
@@ -58,8 +58,23 @@ export type ExecuteInput = {
|
||||
signal?: AbortSignal | null;
|
||||
log?: ExecutorLog | null;
|
||||
extendedContext?: boolean;
|
||||
/** Merged after auth + CLI fingerprint headers (values override same-named defaults). */
|
||||
upstreamExtraHeaders?: Record<string, string> | null;
|
||||
};
|
||||
|
||||
/** Apply model-level extra upstream headers (e.g. Authentication, X-Custom-Auth). */
|
||||
export function mergeUpstreamExtraHeaders(
|
||||
headers: Record<string, string>,
|
||||
extra?: Record<string, string> | null
|
||||
): void {
|
||||
if (!extra) return;
|
||||
for (const [k, v] of Object.entries(extra)) {
|
||||
if (typeof k === "string" && k.length > 0 && typeof v === "string") {
|
||||
headers[k] = v;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function mergeAbortSignals(primary: AbortSignal, secondary: AbortSignal): AbortSignal {
|
||||
const controller = new AbortController();
|
||||
|
||||
@@ -204,7 +219,16 @@ export class BaseExecutor {
|
||||
return { status: response.status, message: bodyText || `HTTP ${response.status}` };
|
||||
}
|
||||
|
||||
async execute({ model, body, stream, credentials, signal, log, extendedContext }: ExecuteInput) {
|
||||
async execute({
|
||||
model,
|
||||
body,
|
||||
stream,
|
||||
credentials,
|
||||
signal,
|
||||
log,
|
||||
extendedContext,
|
||||
upstreamExtraHeaders,
|
||||
}: ExecuteInput) {
|
||||
const fallbackCount = this.getFallbackCount();
|
||||
let lastError: unknown = null;
|
||||
let lastStatus = 0;
|
||||
@@ -258,6 +282,8 @@ export class BaseExecutor {
|
||||
bodyString = fingerprinted.bodyString;
|
||||
}
|
||||
|
||||
mergeUpstreamExtraHeaders(finalHeaders, upstreamExtraHeaders);
|
||||
|
||||
const fetchOptions: RequestInit = {
|
||||
method: "POST",
|
||||
headers: finalHeaders,
|
||||
@@ -289,7 +315,7 @@ export class BaseExecutor {
|
||||
continue;
|
||||
}
|
||||
|
||||
return { response, url, headers, transformedBody };
|
||||
return { response, url, headers: finalHeaders, transformedBody };
|
||||
} catch (error) {
|
||||
// Distinguish timeout errors from other abort errors
|
||||
const err = error instanceof Error ? error : new Error(String(error));
|
||||
|
||||
@@ -20,7 +20,7 @@ declare const EdgeRuntime: string | undefined;
|
||||
* @see cursorProtobuf.js for Protobuf encoding/decoding utilities
|
||||
*/
|
||||
|
||||
import { BaseExecutor } from "./base.ts";
|
||||
import { BaseExecutor, mergeUpstreamExtraHeaders } from "./base.ts";
|
||||
import { PROVIDERS, HTTP_STATUS } from "../config/constants.ts";
|
||||
import {
|
||||
generateCursorBody,
|
||||
@@ -363,9 +363,10 @@ export class CursorExecutor extends BaseExecutor {
|
||||
});
|
||||
}
|
||||
|
||||
async execute({ model, body, stream, credentials, signal, log }) {
|
||||
async execute({ model, body, stream, credentials, signal, log, upstreamExtraHeaders }) {
|
||||
const url = this.buildUrl();
|
||||
const headers = this.buildHeaders(credentials);
|
||||
mergeUpstreamExtraHeaders(headers, upstreamExtraHeaders);
|
||||
const transformedBody = this.transformRequest(model, body, stream, credentials);
|
||||
|
||||
try {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import {
|
||||
BaseExecutor,
|
||||
mergeUpstreamExtraHeaders,
|
||||
type ExecuteInput,
|
||||
type ExecutorLog,
|
||||
type ProviderCredentials,
|
||||
@@ -89,9 +90,18 @@ export class KiroExecutor extends BaseExecutor {
|
||||
/**
|
||||
* Custom execute for Kiro - handles AWS EventStream binary response
|
||||
*/
|
||||
async execute({ model, body, stream, credentials, signal, log }: ExecuteInput) {
|
||||
async execute({
|
||||
model,
|
||||
body,
|
||||
stream,
|
||||
credentials,
|
||||
signal,
|
||||
log,
|
||||
upstreamExtraHeaders,
|
||||
}: ExecuteInput) {
|
||||
const url = this.buildUrl(model, stream, 0);
|
||||
const headers = this.buildHeaders(credentials, stream);
|
||||
mergeUpstreamExtraHeaders(headers, upstreamExtraHeaders);
|
||||
const transformedBody = this.transformRequest(model, body, stream, credentials);
|
||||
|
||||
const response = await fetch(url, {
|
||||
|
||||
@@ -26,7 +26,11 @@ import {
|
||||
appendRequestLog,
|
||||
saveCallLog,
|
||||
} from "@/lib/usageDb";
|
||||
import { getModelNormalizeToolCallId, getModelPreserveOpenAIDeveloperRole } from "@/lib/localDb";
|
||||
import {
|
||||
getModelNormalizeToolCallId,
|
||||
getModelPreserveOpenAIDeveloperRole,
|
||||
getModelUpstreamExtraHeaders,
|
||||
} from "@/lib/localDb";
|
||||
import { getExecutor } from "../executors/index.ts";
|
||||
import {
|
||||
parseCodexQuotaHeaders,
|
||||
@@ -280,6 +284,23 @@ export async function handleChatCore({
|
||||
const modelTargetFormat = getModelTargetFormat(alias, resolvedModel);
|
||||
const targetFormat = modelTargetFormat || getTargetFormat(provider);
|
||||
|
||||
// Primary path: merge client model id + alias target so config on either key applies; resolved
|
||||
// id wins on same header name. T5 family fallback uses only (nextModel, resolveModelAlias(next))
|
||||
// so A-model headers are not sent to B — see buildUpstreamHeadersForExecute.
|
||||
const buildUpstreamHeadersForExecute = (modelToCall: string): Record<string, string> => {
|
||||
if (modelToCall === effectiveModel) {
|
||||
return {
|
||||
...getModelUpstreamExtraHeaders(provider || "", model || "", sourceFormat),
|
||||
...getModelUpstreamExtraHeaders(provider || "", resolvedModel || "", sourceFormat),
|
||||
};
|
||||
}
|
||||
const r = resolveModelAlias(modelToCall);
|
||||
return {
|
||||
...getModelUpstreamExtraHeaders(provider || "", modelToCall || "", sourceFormat),
|
||||
...getModelUpstreamExtraHeaders(provider || "", r || "", sourceFormat),
|
||||
};
|
||||
};
|
||||
|
||||
// Default to false unless client explicitly sets stream: true (OpenAI spec compliant)
|
||||
const acceptHeader =
|
||||
clientRawRequest?.headers && typeof clientRawRequest.headers.get === "function"
|
||||
@@ -593,6 +614,7 @@ export async function handleChatCore({
|
||||
signal: streamController.signal,
|
||||
log,
|
||||
extendedContext,
|
||||
upstreamExtraHeaders: buildUpstreamHeadersForExecute(modelToCall),
|
||||
})
|
||||
);
|
||||
|
||||
@@ -724,16 +746,19 @@ export async function handleChatCore({
|
||||
await onCredentialsRefreshed(newCredentials);
|
||||
}
|
||||
|
||||
// Retry with new credentials
|
||||
// Retry with new credentials — model + extra headers follow translatedBody.model so they
|
||||
// stay aligned if this block ever runs after a path that mutates body.model (e.g. fallback).
|
||||
try {
|
||||
const retryModelId = String(translatedBody.model || effectiveModel);
|
||||
const retryResult = await executor.execute({
|
||||
model,
|
||||
model: retryModelId,
|
||||
body: translatedBody,
|
||||
stream,
|
||||
credentials: getExecutionCredentials(),
|
||||
signal: streamController.signal,
|
||||
log,
|
||||
extendedContext,
|
||||
upstreamExtraHeaders: buildUpstreamHeadersForExecute(retryModelId),
|
||||
});
|
||||
|
||||
if (retryResult.response.ok) {
|
||||
|
||||
@@ -1081,6 +1081,21 @@ async function handleComfyUIImageGeneration({ model, provider, providerConfig, b
|
||||
}
|
||||
}
|
||||
|
||||
type Imagen3ImageGenArgs = {
|
||||
model: string;
|
||||
provider: string;
|
||||
providerConfig: { baseUrl: string };
|
||||
body: { prompt?: string; size?: string; n?: number };
|
||||
credentials: { apiKey?: string; accessToken?: string };
|
||||
log?: { info?: (tag: string, msg: string) => void; error?: (tag: string, msg: string) => void } | null;
|
||||
};
|
||||
|
||||
type Imagen3NormalizedImage = {
|
||||
b64_json?: unknown;
|
||||
url?: unknown;
|
||||
revised_prompt?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle Imagen 3 image generation
|
||||
*/
|
||||
@@ -1091,7 +1106,7 @@ async function handleImagen3ImageGeneration({
|
||||
body,
|
||||
credentials,
|
||||
log,
|
||||
}: any) {
|
||||
}: Imagen3ImageGenArgs) {
|
||||
const startTime = Date.now();
|
||||
const token = credentials.apiKey || credentials.accessToken;
|
||||
const aspectRatio = mapImageSize(body.size);
|
||||
@@ -1142,11 +1157,11 @@ async function handleImagen3ImageGeneration({
|
||||
const data = await response.json();
|
||||
|
||||
// Normalize response to OpenAI format
|
||||
const images: any[] = [];
|
||||
const images: Imagen3NormalizedImage[] = [];
|
||||
if (Array.isArray(data.images)) {
|
||||
images.push(
|
||||
...data.images.map((img: any) => ({
|
||||
b64_json: img.image || img.b64_json || img.url || img,
|
||||
...data.images.map((img: Record<string, unknown>) => ({
|
||||
b64_json: img.image ?? img.b64_json ?? img.url ?? img,
|
||||
revised_prompt: body.prompt,
|
||||
}))
|
||||
);
|
||||
@@ -1174,8 +1189,9 @@ async function handleImagen3ImageGeneration({
|
||||
success: true,
|
||||
data: { created: data.created || Math.floor(Date.now() / 1000), data: images },
|
||||
};
|
||||
} catch (err: any) {
|
||||
if (log) log.error("IMAGE", `${provider} fetch error: ${err.message}`);
|
||||
} catch (err: unknown) {
|
||||
const errMsg = err instanceof Error ? err.message : String(err);
|
||||
if (log) log.error("IMAGE", `${provider} fetch error: ${errMsg}`);
|
||||
|
||||
saveCallLog({
|
||||
method: "POST",
|
||||
@@ -1184,9 +1200,9 @@ async function handleImagen3ImageGeneration({
|
||||
model: `${provider}/${model}`,
|
||||
provider,
|
||||
duration: Date.now() - startTime,
|
||||
error: err.message,
|
||||
error: errMsg,
|
||||
}).catch(() => {});
|
||||
|
||||
return { success: false, status: 502, error: `Image provider error: ${err.message}` };
|
||||
return { success: false, status: 502, error: `Image provider error: ${errMsg}` };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,12 +23,19 @@ export function parseSSEToOpenAIResponse(rawSSE, fallbackModel) {
|
||||
const first = chunks[0];
|
||||
const contentParts = [];
|
||||
const reasoningParts = [];
|
||||
const accumulatedToolCalls = new Map<string, any>();
|
||||
type AccumulatedToolCall = {
|
||||
id: string | null;
|
||||
index: number;
|
||||
type: string;
|
||||
function: { name: string; arguments: string };
|
||||
};
|
||||
|
||||
const accumulatedToolCalls = new Map<string, AccumulatedToolCall>();
|
||||
let unknownToolCallSeq = 0;
|
||||
let finishReason = "stop";
|
||||
let usage = null;
|
||||
|
||||
const getToolCallKey = (toolCall: any) => {
|
||||
const getToolCallKey = (toolCall: Record<string, unknown>) => {
|
||||
if (toolCall?.id) return `id:${toolCall.id}`;
|
||||
if (Number.isInteger(toolCall?.index)) return `idx:${toolCall.index}`;
|
||||
unknownToolCallSeq += 1;
|
||||
|
||||
@@ -435,7 +435,7 @@ async function handleListModelsCatalog(args: { provider?: string; capability?: s
|
||||
let path = "/v1/models";
|
||||
let isProviderSpecific = false;
|
||||
let source = "local_catalog";
|
||||
let warning = undefined;
|
||||
let warning: string | undefined;
|
||||
|
||||
if (args.provider && !args.capability) {
|
||||
// Use direct provider fetch to get real-time API status
|
||||
@@ -451,7 +451,7 @@ async function handleListModelsCatalog(args: { provider?: string; capability?: s
|
||||
const raw = toRecord(await omniRouteFetch(path));
|
||||
|
||||
// If we used the direct provider endpoint
|
||||
let rawModels = [];
|
||||
let rawModels: unknown[] = [];
|
||||
if (isProviderSpecific) {
|
||||
rawModels = Array.isArray(raw.models) ? raw.models : [];
|
||||
source = typeof raw.source === "string" ? raw.source : "api";
|
||||
|
||||
@@ -5,14 +5,34 @@
|
||||
* 3 layers: trim tool messages, compress thinking, aggressive purification.
|
||||
*/
|
||||
|
||||
// Default token limits per provider (rough estimates based on model context windows)
|
||||
const DEFAULT_LIMITS = {
|
||||
import { REGISTRY } from "../config/providerRegistry.ts";
|
||||
|
||||
// Default token limits per provider (fallbacks when not in registry)
|
||||
const DEFAULT_LIMITS: Record<string, number> = {
|
||||
claude: 200000,
|
||||
openai: 128000,
|
||||
gemini: 1000000,
|
||||
codex: 400000,
|
||||
default: 128000,
|
||||
};
|
||||
|
||||
// Environment variable overrides (highest priority)
|
||||
function getEnvOverride(provider: string): number | null {
|
||||
const envKey = `CONTEXT_LENGTH_${provider.toUpperCase().replace(/[^A-Z0-9]/g, "_")}`;
|
||||
const envValue = process.env[envKey];
|
||||
if (envValue) {
|
||||
const parsed = parseInt(envValue, 10);
|
||||
if (!isNaN(parsed) && parsed > 0) return parsed;
|
||||
}
|
||||
// Global override
|
||||
const globalValue = process.env.CONTEXT_LENGTH_DEFAULT;
|
||||
if (globalValue) {
|
||||
const parsed = parseInt(globalValue, 10);
|
||||
if (!isNaN(parsed) && parsed > 0) return parsed;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Rough chars-per-token ratio for quick estimation
|
||||
const CHARS_PER_TOKEN = 4;
|
||||
|
||||
@@ -27,9 +47,20 @@ export function estimateTokens(text) {
|
||||
|
||||
/**
|
||||
* Get token limit for a provider/model combination
|
||||
* Priority: Env override > Registry defaultContextLength > DEFAULT_LIMITS
|
||||
*/
|
||||
export function getTokenLimit(provider, model = null) {
|
||||
// Check if model has a known limit
|
||||
// 1. Check environment variable override first
|
||||
const envOverride = getEnvOverride(provider);
|
||||
if (envOverride) return envOverride;
|
||||
|
||||
// 2. Check registry for provider default
|
||||
const registryEntry = REGISTRY[provider];
|
||||
if (registryEntry?.defaultContextLength) {
|
||||
return registryEntry.defaultContextLength;
|
||||
}
|
||||
|
||||
// 3. Check if model name hints at a known limit
|
||||
if (model) {
|
||||
const lower = model.toLowerCase();
|
||||
if (lower.includes("claude")) return DEFAULT_LIMITS.claude;
|
||||
@@ -38,10 +69,13 @@ export function getTokenLimit(provider, model = null) {
|
||||
lower.includes("gpt") ||
|
||||
lower.includes("o1") ||
|
||||
lower.includes("o3") ||
|
||||
lower.includes("o4")
|
||||
lower.includes("o4") ||
|
||||
lower.includes("codex")
|
||||
)
|
||||
return DEFAULT_LIMITS.openai;
|
||||
return DEFAULT_LIMITS.codex;
|
||||
}
|
||||
|
||||
// 4. Fallback to DEFAULT_LIMITS or default
|
||||
return DEFAULT_LIMITS[provider] || DEFAULT_LIMITS.default;
|
||||
}
|
||||
|
||||
|
||||
@@ -242,8 +242,8 @@ export async function getModelInfoCore(modelStr, aliasesOrGetter) {
|
||||
// FIX #73: Models like claude-haiku-4-5-20251001 sent without provider prefix
|
||||
// would incorrectly route to OpenAI. Use heuristic prefix detection first.
|
||||
if (/^claude-/i.test(modelId)) {
|
||||
// Claude models → Antigravity (Anthropic) provider
|
||||
return { provider: "antigravity", model: modelId, extendedContext };
|
||||
// Claude models → Anthropic provider (canonical source for Claude models)
|
||||
return { provider: "anthropic", model: modelId, extendedContext };
|
||||
}
|
||||
if (/^gemini-/i.test(modelId) || /^gemma-/i.test(modelId)) {
|
||||
// Gemini/Gemma models → Gemini provider
|
||||
|
||||
@@ -29,7 +29,7 @@ type ClaudeTool = {
|
||||
|
||||
/**
|
||||
* T02: Recursively strips empty text blocks from content arrays.
|
||||
* Anthropic returns 400 "text content blocks must be non-empty" if any
|
||||
* Anthropic returns 400 "text content blocks must be non-empty" when a
|
||||
* text block has text: "". Must also recurse into nested tool_result.content.
|
||||
* Ref: sub2api PR #1212
|
||||
*/
|
||||
|
||||
@@ -57,6 +57,8 @@ type TranslateState = ReturnType<typeof initState> & {
|
||||
accumulatedContent?: string;
|
||||
};
|
||||
|
||||
type UsageTokenRecord = Record<string, number>;
|
||||
|
||||
function getOpenAIIntermediateChunks(value: unknown): unknown[] {
|
||||
if (!value || typeof value !== "object") return [];
|
||||
const candidate = (value as JsonRecord)._openaiIntermediate;
|
||||
@@ -108,7 +110,9 @@ export function createSSEStream(options: StreamOptions = {}) {
|
||||
} = options;
|
||||
|
||||
let buffer = "";
|
||||
let usage = null;
|
||||
let usage: UsageTokenRecord | null = null;
|
||||
/** Passthrough (OpenAI CC shape): saw tool_calls in stream before finish_reason */
|
||||
let passthroughHasToolCalls = false;
|
||||
|
||||
// State for translate mode (accumulatedContent for call log response body)
|
||||
const state: TranslateState | null =
|
||||
@@ -223,9 +227,9 @@ export function createSSEStream(options: StreamOptions = {}) {
|
||||
if (extracted) {
|
||||
// Non-destructive merge: never overwrite a positive value with 0
|
||||
// message_start carries input_tokens, message_delta carries output_tokens;
|
||||
if (!usage) usage = {} as any;
|
||||
const u = usage as Record<string, number>;
|
||||
const eu = extracted as Record<string, number>;
|
||||
if (!usage) usage = {};
|
||||
const u = usage;
|
||||
const eu = extracted as UsageTokenRecord;
|
||||
if (eu.prompt_tokens > 0) u.prompt_tokens = eu.prompt_tokens;
|
||||
if (eu.completion_tokens > 0) u.completion_tokens = eu.completion_tokens;
|
||||
if (eu.total_tokens > 0) u.total_tokens = eu.total_tokens;
|
||||
@@ -266,7 +270,7 @@ export function createSSEStream(options: StreamOptions = {}) {
|
||||
|
||||
// T18: Track if we saw tool calls
|
||||
if (delta?.tool_calls && delta.tool_calls.length > 0) {
|
||||
(state as any).passthroughHasToolCalls = true;
|
||||
passthroughHasToolCalls = true;
|
||||
}
|
||||
|
||||
const content = delta?.content || delta?.reasoning_content;
|
||||
@@ -288,7 +292,7 @@ export function createSSEStream(options: StreamOptions = {}) {
|
||||
// T18: Normalize finish_reason to 'tool_calls' if tool calls were used
|
||||
if (
|
||||
isFinishChunk &&
|
||||
(state as any).passthroughHasToolCalls &&
|
||||
passthroughHasToolCalls &&
|
||||
parsed.choices[0].finish_reason !== "tool_calls"
|
||||
) {
|
||||
parsed.choices[0].finish_reason = "tool_calls";
|
||||
|
||||
@@ -140,7 +140,7 @@ export function createDisconnectAwareStream(transformStream, streamController) {
|
||||
const errorMsg = error instanceof Error ? error.message : "Upstream stream error";
|
||||
const statusCode =
|
||||
typeof error === "object" && error !== null && "statusCode" in error
|
||||
? (error as any).statusCode
|
||||
? Number((error as { statusCode?: unknown }).statusCode) || 500
|
||||
: 500;
|
||||
|
||||
const errorEvent = {
|
||||
|
||||
13
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "omniroute",
|
||||
"version": "3.0.0-rc.15",
|
||||
"version": "3.0.0-rc.16",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "omniroute",
|
||||
"version": "3.0.0-rc.15",
|
||||
"version": "3.0.0-rc.16",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"workspaces": [
|
||||
@@ -14606,15 +14606,6 @@
|
||||
"marked": "14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/monaco-editor/node_modules/dompurify": {
|
||||
"version": "3.2.7",
|
||||
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.7.tgz",
|
||||
"integrity": "sha512-WhL/YuveyGXJaerVlMYGWhvQswa7myDG17P7Vu65EWC05o8vfeNbvNf4d/BOvH99+ZW+LlQsc1GDKMa1vNK6dw==",
|
||||
"license": "(MPL-2.0 OR Apache-2.0)",
|
||||
"optionalDependencies": {
|
||||
"@types/trusted-types": "^2.0.7"
|
||||
}
|
||||
},
|
||||
"node_modules/motion": {
|
||||
"version": "12.38.0",
|
||||
"resolved": "https://registry.npmjs.org/motion/-/motion-12.38.0.tgz",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "omniroute",
|
||||
"version": "3.0.0-rc.15",
|
||||
"version": "3.0.0-rc.16",
|
||||
"description": "Smart AI Router with auto fallback — route to FREE & cheap models, zero downtime. Works with Cursor, Cline, Claude Desktop, Codex, and any OpenAI-compatible tool.",
|
||||
"type": "module",
|
||||
"bin": {
|
||||
@@ -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 .",
|
||||
@@ -155,5 +155,8 @@
|
||||
"omniroute",
|
||||
"sharp"
|
||||
]
|
||||
},
|
||||
"overrides": {
|
||||
"dompurify": "^3.3.2"
|
||||
}
|
||||
}
|
||||
|
||||
BIN
public/providers/alibaba.png
Normal file
|
After Width: | Height: | Size: 4.7 KiB |
1
public/providers/apikey.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128" fill="none"><rect width="128" height="128" rx="24" fill="#1e293b"/><path d="M48 60a16 16 0 1 1 12 15.5V88h-8V75.5A16 16 0 0 1 48 60z" fill="none" stroke="#94a3b8" stroke-width="3"/><circle cx="56" cy="56" r="5" fill="#94a3b8"/><path d="M64 76h24M76 72v8M84 72v8" stroke="#94a3b8" stroke-width="3"/></svg>
|
||||
|
After Width: | Height: | Size: 367 B |
BIN
public/providers/bailian-coding-plan.png
Normal file
|
After Width: | Height: | Size: 4.7 KiB |
1
public/providers/cartesia.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128" fill="none"><rect width="128" height="128" rx="24" fill="#0f172a"/><path d="M40 40h48v48H40z" fill="none" stroke="#3b82f6" stroke-width="3"/><path d="M52 52h24v24H52z" fill="#3b82f6" opacity="0.4"/><path d="M58 58h12v12H58z" fill="#60a5fa"/></svg>
|
||||
|
After Width: | Height: | Size: 310 B |
1
public/providers/comfyui.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128" fill="none"><rect width="128" height="128" rx="24" fill="#1a1a2e"/><path d="M32 48h20v8H32zm44 0h20v8H76zm-22 24h20v8H54z" fill="#7c3aed"/><circle cx="52" cy="52" r="4" fill="#a78bfa"/><circle cx="76" cy="76" r="4" fill="#a78bfa"/><path d="M52 52l22 24" stroke="#7c3aed" stroke-width="2"/></svg>
|
||||
|
After Width: | Height: | Size: 358 B |
52
public/providers/deepgram.png
Normal file
@@ -0,0 +1,52 @@
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<title>404</title>
|
||||
<style>
|
||||
@font-face {
|
||||
font-family: 'colfax-web';
|
||||
font-display: swap;
|
||||
src: url('https://www.datocms.com/fonts/colfax-web-regular.woff2') format('woff2'), url('https://www.datocms.com/fonts/colfax-web-regular.woff') format('woff');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'colfax-web';
|
||||
font-display: swap;
|
||||
font-weight: 700;
|
||||
src: url('https://www.datocms.com/fonts/colfax-web-bold.woff2') format('woff2'), url('https://www.datocms.com/fonts/colfax-web-bold.woff') format('woff');
|
||||
}
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
box-sizing: border-box;
|
||||
font-family: colfax;
|
||||
display: grid;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 30px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 50px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<h1>404</h1>
|
||||
<p>Not Found</p>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
37
public/providers/huggingface.svg
Normal file
@@ -0,0 +1,37 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="95" height="88" fill="none">
|
||||
<path fill="#FFD21E" d="M47.21 76.5a34.75 34.75 0 1 0 0-69.5 34.75 34.75 0 0 0 0 69.5Z" />
|
||||
<path
|
||||
fill="#FF9D0B"
|
||||
d="M81.96 41.75a34.75 34.75 0 1 0-69.5 0 34.75 34.75 0 0 0 69.5 0Zm-73.5 0a38.75 38.75 0 1 1 77.5 0 38.75 38.75 0 0 1-77.5 0Z"
|
||||
/>
|
||||
<path
|
||||
fill="#3A3B45"
|
||||
d="M58.5 32.3c1.28.44 1.78 3.06 3.07 2.38a5 5 0 1 0-6.76-2.07c.61 1.15 2.55-.72 3.7-.32ZM34.95 32.3c-1.28.44-1.79 3.06-3.07 2.38a5 5 0 1 1 6.76-2.07c-.61 1.15-2.56-.72-3.7-.32Z"
|
||||
/>
|
||||
<path
|
||||
fill="#FF323D"
|
||||
d="M46.96 56.29c9.83 0 13-8.76 13-13.26 0-2.34-1.57-1.6-4.09-.36-2.33 1.15-5.46 2.74-8.9 2.74-7.19 0-13-6.88-13-2.38s3.16 13.26 13 13.26Z"
|
||||
/>
|
||||
<path
|
||||
fill="#3A3B45"
|
||||
fill-rule="evenodd"
|
||||
d="M39.43 54a8.7 8.7 0 0 1 5.3-4.49c.4-.12.81.57 1.24 1.28.4.68.82 1.37 1.24 1.37.45 0 .9-.68 1.33-1.35.45-.7.89-1.38 1.32-1.25a8.61 8.61 0 0 1 5 4.17c3.73-2.94 5.1-7.74 5.1-10.7 0-2.34-1.57-1.6-4.09-.36l-.14.07c-2.31 1.15-5.39 2.67-8.77 2.67s-6.45-1.52-8.77-2.67c-2.6-1.29-4.23-2.1-4.23.29 0 3.05 1.46 8.06 5.47 10.97Z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
<path
|
||||
fill="#FF9D0B"
|
||||
d="M70.71 37a3.25 3.25 0 1 0 0-6.5 3.25 3.25 0 0 0 0 6.5ZM24.21 37a3.25 3.25 0 1 0 0-6.5 3.25 3.25 0 0 0 0 6.5ZM17.52 48c-1.62 0-3.06.66-4.07 1.87a5.97 5.97 0 0 0-1.33 3.76 7.1 7.1 0 0 0-1.94-.3c-1.55 0-2.95.59-3.94 1.66a5.8 5.8 0 0 0-.8 7 5.3 5.3 0 0 0-1.79 2.82c-.24.9-.48 2.8.8 4.74a5.22 5.22 0 0 0-.37 5.02c1.02 2.32 3.57 4.14 8.52 6.1 3.07 1.22 5.89 2 5.91 2.01a44.33 44.33 0 0 0 10.93 1.6c5.86 0 10.05-1.8 12.46-5.34 3.88-5.69 3.33-10.9-1.7-15.92-2.77-2.78-4.62-6.87-5-7.77-.78-2.66-2.84-5.62-6.25-5.62a5.7 5.7 0 0 0-4.6 2.46c-1-1.26-1.98-2.25-2.86-2.82A7.4 7.4 0 0 0 17.52 48Zm0 4c.51 0 1.14.22 1.82.65 2.14 1.36 6.25 8.43 7.76 11.18.5.92 1.37 1.31 2.14 1.31 1.55 0 2.75-1.53.15-3.48-3.92-2.93-2.55-7.72-.68-8.01.08-.02.17-.02.24-.02 1.7 0 2.45 2.93 2.45 2.93s2.2 5.52 5.98 9.3c3.77 3.77 3.97 6.8 1.22 10.83-1.88 2.75-5.47 3.58-9.16 3.58-3.81 0-7.73-.9-9.92-1.46-.11-.03-13.45-3.8-11.76-7 .28-.54.75-.76 1.34-.76 2.38 0 6.7 3.54 8.57 3.54.41 0 .7-.17.83-.6.79-2.85-12.06-4.05-10.98-8.17.2-.73.71-1.02 1.44-1.02 3.14 0 10.2 5.53 11.68 5.53.11 0 .2-.03.24-.1.74-1.2.33-2.04-4.9-5.2-5.21-3.16-8.88-5.06-6.8-7.33.24-.26.58-.38 1-.38 3.17 0 10.66 6.82 10.66 6.82s2.02 2.1 3.25 2.1c.28 0 .52-.1.68-.38.86-1.46-8.06-8.22-8.56-11.01-.34-1.9.24-2.85 1.31-2.85Z"
|
||||
/>
|
||||
<path
|
||||
fill="#FFD21E"
|
||||
d="M38.6 76.69c2.75-4.04 2.55-7.07-1.22-10.84-3.78-3.77-5.98-9.3-5.98-9.3s-.82-3.2-2.69-2.9c-1.87.3-3.24 5.08.68 8.01 3.91 2.93-.78 4.92-2.29 2.17-1.5-2.75-5.62-9.82-7.76-11.18-2.13-1.35-3.63-.6-3.13 2.2.5 2.79 9.43 9.55 8.56 11-.87 1.47-3.93-1.71-3.93-1.71s-9.57-8.71-11.66-6.44c-2.08 2.27 1.59 4.17 6.8 7.33 5.23 3.16 5.64 4 4.9 5.2-.75 1.2-12.28-8.53-13.36-4.4-1.08 4.11 11.77 5.3 10.98 8.15-.8 2.85-9.06-5.38-10.74-2.18-1.7 3.21 11.65 6.98 11.76 7.01 4.3 1.12 15.25 3.49 19.08-2.12Z"
|
||||
/>
|
||||
<path
|
||||
fill="#FF9D0B"
|
||||
d="M77.4 48c1.62 0 3.07.66 4.07 1.87a5.97 5.97 0 0 1 1.33 3.76 7.1 7.1 0 0 1 1.95-.3c1.55 0 2.95.59 3.94 1.66a5.8 5.8 0 0 1 .8 7 5.3 5.3 0 0 1 1.78 2.82c.24.9.48 2.8-.8 4.74a5.22 5.22 0 0 1 .37 5.02c-1.02 2.32-3.57 4.14-8.51 6.1-3.08 1.22-5.9 2-5.92 2.01a44.33 44.33 0 0 1-10.93 1.6c-5.86 0-10.05-1.8-12.46-5.34-3.88-5.69-3.33-10.9 1.7-15.92 2.78-2.78 4.63-6.87 5.01-7.77.78-2.66 2.83-5.62 6.24-5.62a5.7 5.7 0 0 1 4.6 2.46c1-1.26 1.98-2.25 2.87-2.82A7.4 7.4 0 0 1 77.4 48Zm0 4c-.51 0-1.13.22-1.82.65-2.13 1.36-6.25 8.43-7.76 11.18a2.43 2.43 0 0 1-2.14 1.31c-1.54 0-2.75-1.53-.14-3.48 3.91-2.93 2.54-7.72.67-8.01a1.54 1.54 0 0 0-.24-.02c-1.7 0-2.45 2.93-2.45 2.93s-2.2 5.52-5.97 9.3c-3.78 3.77-3.98 6.8-1.22 10.83 1.87 2.75 5.47 3.58 9.15 3.58 3.82 0 7.73-.9 9.93-1.46.1-.03 13.45-3.8 11.76-7-.29-.54-.75-.76-1.34-.76-2.38 0-6.71 3.54-8.57 3.54-.42 0-.71-.17-.83-.6-.8-2.85 12.05-4.05 10.97-8.17-.19-.73-.7-1.02-1.44-1.02-3.14 0-10.2 5.53-11.68 5.53-.1 0-.19-.03-.23-.1-.74-1.2-.34-2.04 4.88-5.2 5.23-3.16 8.9-5.06 6.8-7.33-.23-.26-.57-.38-.98-.38-3.18 0-10.67 6.82-10.67 6.82s-2.02 2.1-3.24 2.1a.74.74 0 0 1-.68-.38c-.87-1.46 8.05-8.22 8.55-11.01.34-1.9-.24-2.85-1.31-2.85Z"
|
||||
/>
|
||||
<path
|
||||
fill="#FFD21E"
|
||||
d="M56.33 76.69c-2.75-4.04-2.56-7.07 1.22-10.84 3.77-3.77 5.97-9.3 5.97-9.3s.82-3.2 2.7-2.9c1.86.3 3.23 5.08-.68 8.01-3.92 2.93.78 4.92 2.28 2.17 1.51-2.75 5.63-9.82 7.76-11.18 2.13-1.35 3.64-.6 3.13 2.2-.5 2.79-9.42 9.55-8.55 11 .86 1.47 3.92-1.71 3.92-1.71s9.58-8.71 11.66-6.44c2.08 2.27-1.58 4.17-6.8 7.33-5.23 3.16-5.63 4-4.9 5.2.75 1.2 12.28-8.53 13.36-4.4 1.08 4.11-11.76 5.3-10.97 8.15.8 2.85 9.05-5.38 10.74-2.18 1.69 3.21-11.65 6.98-11.76 7.01-4.31 1.12-15.26 3.49-19.08-2.12Z"
|
||||
/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.5 KiB |
BIN
public/providers/kimi-coding-apikey.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
1
public/providers/oauth.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128" fill="none"><rect width="128" height="128" rx="24" fill="#1e293b"/><circle cx="64" cy="64" r="28" fill="none" stroke="#60a5fa" stroke-width="3"/><path d="M64 36v56M36 64h56" stroke="#60a5fa" stroke-width="2" opacity="0.3"/><circle cx="64" cy="64" r="8" fill="#3b82f6"/></svg>
|
||||
|
After Width: | Height: | Size: 338 B |
18
public/providers/opencode-go.svg
Normal file
@@ -0,0 +1,18 @@
|
||||
<svg width="234" height="42" viewBox="0 0 234 42" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M18 30H6V18H18V30Z" fill="#4B4646"/>
|
||||
<path d="M18 12H6V30H18V12ZM24 36H0V6H24V36Z" fill="#B7B1B1"/>
|
||||
<path d="M48 30H36V18H48V30Z" fill="#4B4646"/>
|
||||
<path d="M36 30H48V12H36V30ZM54 36H36V42H30V6H54V36Z" fill="#B7B1B1"/>
|
||||
<path d="M84 24V30H66V24H84Z" fill="#4B4646"/>
|
||||
<path d="M84 24H66V30H84V36H60V6H84V24ZM66 18H78V12H66V18Z" fill="#B7B1B1"/>
|
||||
<path d="M108 36H96V18H108V36Z" fill="#4B4646"/>
|
||||
<path d="M108 12H96V36H90V6H108V12ZM114 36H108V12H114V36Z" fill="#B7B1B1"/>
|
||||
<path d="M144 30H126V18H144V30Z" fill="#4B4646"/>
|
||||
<path d="M144 12H126V30H144V36H120V6H144V12Z" fill="#F1ECEC"/>
|
||||
<path d="M168 30H156V18H168V30Z" fill="#4B4646"/>
|
||||
<path d="M168 12H156V30H168V12ZM174 36H150V6H174V36Z" fill="#F1ECEC"/>
|
||||
<path d="M198 30H186V18H198V30Z" fill="#4B4646"/>
|
||||
<path d="M198 12H186V30H198V12ZM204 36H180V6H198V0H204V36Z" fill="#F1ECEC"/>
|
||||
<path d="M234 24V30H216V24H234Z" fill="#4B4646"/>
|
||||
<path d="M216 12V18H228V12H216ZM234 24H216V30H234V36H210V6H234V24Z" fill="#F1ECEC"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
18
public/providers/opencode-zen.svg
Normal file
@@ -0,0 +1,18 @@
|
||||
<svg width="234" height="42" viewBox="0 0 234 42" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M18 30H6V18H18V30Z" fill="#4B4646"/>
|
||||
<path d="M18 12H6V30H18V12ZM24 36H0V6H24V36Z" fill="#B7B1B1"/>
|
||||
<path d="M48 30H36V18H48V30Z" fill="#4B4646"/>
|
||||
<path d="M36 30H48V12H36V30ZM54 36H36V42H30V6H54V36Z" fill="#B7B1B1"/>
|
||||
<path d="M84 24V30H66V24H84Z" fill="#4B4646"/>
|
||||
<path d="M84 24H66V30H84V36H60V6H84V24ZM66 18H78V12H66V18Z" fill="#B7B1B1"/>
|
||||
<path d="M108 36H96V18H108V36Z" fill="#4B4646"/>
|
||||
<path d="M108 12H96V36H90V6H108V12ZM114 36H108V12H114V36Z" fill="#B7B1B1"/>
|
||||
<path d="M144 30H126V18H144V30Z" fill="#4B4646"/>
|
||||
<path d="M144 12H126V30H144V36H120V6H144V12Z" fill="#F1ECEC"/>
|
||||
<path d="M168 30H156V18H168V30Z" fill="#4B4646"/>
|
||||
<path d="M168 12H156V30H168V12ZM174 36H150V6H174V36Z" fill="#F1ECEC"/>
|
||||
<path d="M198 30H186V18H198V30Z" fill="#4B4646"/>
|
||||
<path d="M198 12H186V30H198V12ZM204 36H180V6H198V0H204V36Z" fill="#F1ECEC"/>
|
||||
<path d="M234 24V30H216V24H234Z" fill="#4B4646"/>
|
||||
<path d="M216 12V18H228V12H216ZM234 24H216V30H234V36H210V6H234V24Z" fill="#F1ECEC"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
1
public/providers/puter.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128" fill="none"><rect width="128" height="128" rx="24" fill="#0c0a09"/><text x="64" y="74" font-family="sans-serif" font-size="36" font-weight="bold" fill="#fbbf24" text-anchor="middle">P</text><circle cx="64" cy="60" r="30" fill="none" stroke="#fbbf24" stroke-width="2" opacity="0.3"/></svg>
|
||||
|
After Width: | Height: | Size: 351 B |
1
public/providers/sdwebui.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128" fill="none"><rect width="128" height="128" rx="24" fill="#1a1a2e"/><text x="64" y="70" font-family="sans-serif" font-size="40" font-weight="bold" fill="#f97316" text-anchor="middle">SD</text><text x="64" y="96" font-family="sans-serif" font-size="16" fill="#94a3b8" text-anchor="middle">WebUI</text></svg>
|
||||
|
After Width: | Height: | Size: 368 B |
1
public/providers/synthetic.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128" fill="none"><rect width="128" height="128" rx="24" fill="#1a1a2e"/><circle cx="64" cy="50" r="16" fill="none" stroke="#10b981" stroke-width="3"/><circle cx="44" cy="86" r="12" fill="none" stroke="#10b981" stroke-width="3"/><circle cx="84" cy="86" r="12" fill="none" stroke="#10b981" stroke-width="3"/><path d="M56 62l-8 16M72 62l8 16M52 86h24" stroke="#10b981" stroke-width="2"/></svg>
|
||||
|
After Width: | Height: | Size: 448 B |
1
public/providers/vertex.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128" fill="none"><rect width="128" height="128" rx="24" fill="#1a1a2e"/><path d="M64 24L104 96H24L64 24z" fill="#4285F4" opacity="0.8"/><path d="M64 44L84 80H44L64 44z" fill="#34A853" opacity="0.9"/><circle cx="64" cy="68" r="8" fill="#FBBC04"/></svg>
|
||||
|
After Width: | Height: | Size: 309 B |
1
public/providers/zai.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128" fill="none"><rect width="128" height="128" rx="24" fill="#1e1b4b"/><text x="64" y="76" font-family="sans-serif" font-size="48" font-weight="bold" fill="#818cf8" text-anchor="middle">Z</text><text x="64" y="100" font-family="sans-serif" font-size="18" fill="#6366f1" text-anchor="middle">.AI</text></svg>
|
||||
|
After Width: | Height: | Size: 366 B |
84
scripts/build-next-isolated.mjs
Normal file
@@ -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();
|
||||
@@ -9,15 +9,15 @@ import { bootstrapEnv } from "./bootstrap-env.mjs";
|
||||
|
||||
const mode = process.argv[2] === "start" ? "start" : "dev";
|
||||
|
||||
const runtimePorts = resolveRuntimePorts();
|
||||
// Load .env / server.env first so PORT / DASHBOARD_PORT from files affect --port below.
|
||||
const env = bootstrapEnv();
|
||||
const runtimePorts = resolveRuntimePorts(env);
|
||||
const { dashboardPort } = runtimePorts;
|
||||
|
||||
// Auto-generate secrets on first run, merge .env + process.env
|
||||
const env = bootstrapEnv();
|
||||
|
||||
const args = ["./node_modules/next/dist/bin/next", mode, "--port", String(dashboardPort)];
|
||||
// Default: use webpack (stable). Set OMNIROUTE_USE_TURBOPACK=1 to use Turbopack (faster dev).
|
||||
if (mode === "dev" && process.env.OMNIROUTE_USE_TURBOPACK !== "1") {
|
||||
// Default: use webpack (stable). Set OMNIROUTE_USE_TURBOPACK=1 in .env for Turbopack (faster dev).
|
||||
// Must read merged `env` from bootstrap — .env is not applied to process.env in the launcher.
|
||||
if (mode === "dev" && env.OMNIROUTE_USE_TURBOPACK !== "1") {
|
||||
args.splice(2, 0, "--webpack");
|
||||
}
|
||||
|
||||
|
||||
@@ -7,10 +7,8 @@ import {
|
||||
} from "./runtime-env.mjs";
|
||||
import { bootstrapEnv } from "./bootstrap-env.mjs";
|
||||
|
||||
const runtimePorts = resolveRuntimePorts();
|
||||
|
||||
// Auto-generate secrets on first run, merge .env + process.env
|
||||
const env = bootstrapEnv();
|
||||
const runtimePorts = resolveRuntimePorts(env);
|
||||
|
||||
spawnWithForwardedSignals("node", ["server.js"], {
|
||||
stdio: "inherit",
|
||||
|
||||
@@ -5,10 +5,14 @@ export function parsePort(value, fallback) {
|
||||
return Number.isFinite(parsed) && parsed > 0 && parsed <= 65535 ? parsed : fallback;
|
||||
}
|
||||
|
||||
export function resolveRuntimePorts() {
|
||||
const basePort = parsePort(process.env.PORT || "20128", 20128);
|
||||
const apiPort = parsePort(process.env.API_PORT || String(basePort), basePort);
|
||||
const dashboardPort = parsePort(process.env.DASHBOARD_PORT || String(basePort), basePort);
|
||||
/**
|
||||
* @param {NodeJS.ProcessEnv | Record<string, string | undefined>} [fromEnv]
|
||||
* Defaults to process.env. Pass bootstrap `merged` so project `.env` PORT applies before spawn.
|
||||
*/
|
||||
export function resolveRuntimePorts(fromEnv = process.env) {
|
||||
const basePort = parsePort(fromEnv.PORT || "20128", 20128);
|
||||
const apiPort = parsePort(fromEnv.API_PORT || String(basePort), basePort);
|
||||
const dashboardPort = parsePort(fromEnv.DASHBOARD_PORT || String(basePort), basePort);
|
||||
|
||||
return { basePort, apiPort, dashboardPort };
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -137,8 +139,9 @@ export default function AgentsPage() {
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="flex items-center justify-center min-h-[400px]">
|
||||
<div className="flex flex-col items-center justify-center min-h-[400px] gap-3">
|
||||
<div className="animate-spin rounded-full h-8 w-8 border-2 border-primary border-t-transparent" />
|
||||
<p className="text-sm text-text-muted">{t("scanning")}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -179,6 +182,51 @@ export default function AgentsPage() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Setup Guide */}
|
||||
<Card>
|
||||
<div className="flex items-center justify-between gap-3 mb-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="p-2 rounded-lg bg-blue-500/10 text-blue-500">
|
||||
<span className="material-symbols-outlined text-[20px]" aria-hidden="true">
|
||||
support
|
||||
</span>
|
||||
</div>
|
||||
<h3 className="text-lg font-semibold">{t("setupGuideTitle")}</h3>
|
||||
</div>
|
||||
<Link
|
||||
href="/dashboard/cli-tools"
|
||||
className="text-xs px-2.5 py-1.5 rounded-lg border border-border/60 hover:bg-surface/40 transition-colors"
|
||||
>
|
||||
{t("openCliTools")}
|
||||
</Link>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-3">
|
||||
<div className="rounded-lg border border-border/50 bg-black/[0.02] dark:bg-white/[0.02] p-3">
|
||||
<div className="flex items-center gap-2 mb-1.5">
|
||||
<span className="material-symbols-outlined text-[16px] text-blue-500">radar</span>
|
||||
<p className="text-sm font-medium">{t("setupGuideDetectCliTitle")}</p>
|
||||
</div>
|
||||
<p className="text-xs text-text-muted">{t("setupGuideDetectCliDesc")}</p>
|
||||
</div>
|
||||
<div className="rounded-lg border border-border/50 bg-black/[0.02] dark:bg-white/[0.02] p-3">
|
||||
<div className="flex items-center gap-2 mb-1.5">
|
||||
<span className="material-symbols-outlined text-[16px] text-amber-500">build</span>
|
||||
<p className="text-sm font-medium">{t("setupGuideCustomAgentTitle")}</p>
|
||||
</div>
|
||||
<p className="text-xs text-text-muted">{t("setupGuideCustomAgentDesc")}</p>
|
||||
</div>
|
||||
<div className="rounded-lg border border-border/50 bg-black/[0.02] dark:bg-white/[0.02] p-3">
|
||||
<div className="flex items-center gap-2 mb-1.5">
|
||||
<span className="material-symbols-outlined text-[16px] text-emerald-500">
|
||||
terminal
|
||||
</span>
|
||||
<p className="text-sm font-medium">{t("setupGuideCommandMissingTitle")}</p>
|
||||
</div>
|
||||
<p className="text-xs text-text-muted">{t("setupGuideCommandMissingDesc")}</p>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
{/* CLI Fingerprint Matching */}
|
||||
<Card>
|
||||
<div className="flex items-center gap-3 mb-4">
|
||||
@@ -192,20 +240,7 @@ export default function AgentsPage() {
|
||||
<div className="flex flex-col gap-4">
|
||||
<p className="text-sm text-text-muted">{ts("cliFingerprintDesc")}</p>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{(
|
||||
[
|
||||
"codex",
|
||||
"claude",
|
||||
"github",
|
||||
"antigravity",
|
||||
"kiro",
|
||||
"cursor",
|
||||
"kimi-coding",
|
||||
"kilocode",
|
||||
"cline",
|
||||
"qwen",
|
||||
] as const
|
||||
).map((providerId) => {
|
||||
{CLI_COMPAT_PROVIDER_IDS.map((providerId) => {
|
||||
const providerMeta = Object.values(AI_PROVIDERS).find(
|
||||
(p: any) => p.id === providerId
|
||||
) as any;
|
||||
@@ -327,22 +362,18 @@ export default function AgentsPage() {
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<h3 className="text-base font-semibold">OpenCode Integration</h3>
|
||||
<h3 className="text-base font-semibold">{t("opencodeIntegration")}</h3>
|
||||
<span className="text-[10px] px-1.5 py-0.5 rounded-full bg-emerald-500/10 text-emerald-600 dark:text-emerald-400 font-medium">
|
||||
opencode {agents.find((a) => a.id === "opencode")?.version} detected
|
||||
{t("opencodeDetected", {
|
||||
version: agents.find((a) => a.id === "opencode")?.version || "",
|
||||
})}
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-sm text-text-muted mb-3">
|
||||
Generate a ready-to-use{" "}
|
||||
<code className="text-xs bg-black/[0.06] dark:bg-white/[0.08] px-1 py-0.5 rounded">
|
||||
opencode.json
|
||||
</code>{" "}
|
||||
with your OmniRoute base URL and all available models — drop it in your project root
|
||||
and run{" "}
|
||||
<code className="text-xs bg-black/[0.06] dark:bg-white/[0.08] px-1 py-0.5 rounded">
|
||||
opencode
|
||||
</code>
|
||||
.
|
||||
{t("opencodeDesc", {
|
||||
configFile: "opencode.json",
|
||||
command: "opencode",
|
||||
})}
|
||||
</p>
|
||||
<Button
|
||||
variant="secondary"
|
||||
@@ -399,7 +430,9 @@ export default function AgentsPage() {
|
||||
<span className="material-symbols-outlined text-[16px] mr-1">
|
||||
{opencodeConfigDone ? "check" : "download"}
|
||||
</span>
|
||||
{opencodeConfigDone ? "Downloaded!" : "Download opencode.json"}
|
||||
{opencodeConfigDone
|
||||
? t("downloaded")
|
||||
: t("downloadConfig", { file: "opencode.json" })}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -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 (
|
||||
<div className="flex flex-col gap-6">
|
||||
<Card>
|
||||
<div className="flex items-start gap-3">
|
||||
<div className="p-2 rounded-lg bg-primary/10 text-primary">
|
||||
<span className="material-symbols-outlined text-[20px]">tips_and_updates</span>
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<h2 className="text-sm font-semibold">{t("howItWorks")}</h2>
|
||||
<div className="mt-2 grid grid-cols-1 md:grid-cols-3 gap-2 text-xs text-text-muted">
|
||||
<div className="rounded-lg border border-border/50 bg-black/[0.02] dark:bg-white/[0.02] p-2.5">
|
||||
{t("installationGuide")}
|
||||
</div>
|
||||
<div className="rounded-lg border border-border/50 bg-black/[0.02] dark:bg-white/[0.02] p-2.5">
|
||||
{t("configureEndpoint")}
|
||||
</div>
|
||||
<div className="rounded-lg border border-border/50 bg-black/[0.02] dark:bg-white/[0.02] p-2.5">
|
||||
{t("testConnection")}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
{!hasActiveProviders && (
|
||||
<Card className="border-yellow-500/50 bg-yellow-500/5">
|
||||
<div className="flex items-center gap-3">
|
||||
@@ -308,7 +352,38 @@ export default function CLIToolsPageClient({ machineId }) {
|
||||
)}
|
||||
|
||||
<div className="flex flex-col gap-4">
|
||||
{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 (
|
||||
<div key={toolId} className="flex flex-col gap-2.5">
|
||||
{renderToolCard(toolId, tool)}
|
||||
<div className="rounded-lg border border-border/50 bg-black/[0.02] dark:bg-white/[0.02] p-3">
|
||||
<div className="flex flex-col sm:flex-row sm:items-start sm:justify-between gap-2.5">
|
||||
<div className="min-w-0">
|
||||
<p className="text-[11px] font-semibold uppercase tracking-wide text-text-muted">
|
||||
{t("whenToUseLabel")}
|
||||
</p>
|
||||
<p className="text-xs text-text-muted mt-1 break-words">
|
||||
{getToolUseCase(toolId, tool)}
|
||||
</p>
|
||||
</div>
|
||||
<a
|
||||
href={docsHref}
|
||||
target={isExternalDocs ? "_blank" : undefined}
|
||||
rel={isExternalDocs ? "noopener noreferrer" : undefined}
|
||||
className="inline-flex items-center gap-1.5 text-xs text-primary hover:text-primary/80 transition-colors whitespace-nowrap"
|
||||
>
|
||||
<span className="material-symbols-outlined text-[14px]" aria-hidden="true">
|
||||
menu_book
|
||||
</span>
|
||||
{t("openToolDocs")}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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() {
|
||||
</div>
|
||||
<span className="text-sm text-text-muted">{t("providers")}</span>
|
||||
</div>
|
||||
<p className="text-xl font-semibold text-text-main">{cbEntries.length}</p>
|
||||
<p className="text-xs text-text-muted mt-1">
|
||||
{t("healthyCount", {
|
||||
count: cbEntries.filter(([, v]: [string, any]) => v.state === "CLOSED").length,
|
||||
<p className="text-xl font-semibold text-text-main">
|
||||
{providerSummary?.configuredCount ?? cbEntries.length}
|
||||
</p>
|
||||
<p
|
||||
className="text-[11px] text-text-muted mt-1 inline-flex items-center gap-1"
|
||||
title={t("configuredProvidersHint")}
|
||||
>
|
||||
{t("configuredProvidersLabel")}
|
||||
<span className="material-symbols-outlined text-[12px]" aria-hidden="true">
|
||||
help
|
||||
</span>
|
||||
</p>
|
||||
<p
|
||||
className="text-xs text-text-muted inline-flex items-center gap-1"
|
||||
title={t("activeProvidersHint")}
|
||||
>
|
||||
{t("activeProviders", { count: providerSummary?.activeCount ?? 0 })}
|
||||
<span className="material-symbols-outlined text-[12px]" aria-hidden="true">
|
||||
info
|
||||
</span>
|
||||
</p>
|
||||
<p
|
||||
className="text-xs text-text-muted inline-flex items-center gap-1"
|
||||
title={t("monitoredProvidersHint")}
|
||||
>
|
||||
{t("monitoredProviders", {
|
||||
count: providerSummary?.monitoredCount ?? cbEntries.length,
|
||||
})}
|
||||
<span className="material-symbols-outlined text-[12px]" aria-hidden="true">
|
||||
info
|
||||
</span>
|
||||
</p>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
@@ -358,6 +358,14 @@ function parseApiError(raw: any, statusCode: number): { message: string; isCrede
|
||||
return { message: String(msg), isCredentials };
|
||||
}
|
||||
|
||||
/** Format file size to human-readable string */
|
||||
function formatFileSize(bytes: number): string {
|
||||
if (bytes < 1024) return `${bytes} B`;
|
||||
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(0)} KB`;
|
||||
if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
||||
return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`;
|
||||
}
|
||||
|
||||
/** Render image result thumbnails */
|
||||
function ImageResults({ data }: { data: any }) {
|
||||
const images: Array<{ url?: string; b64_json?: string; revised_prompt?: string }> =
|
||||
@@ -427,7 +435,9 @@ export default function MediaPageClient() {
|
||||
const [speechFormat, setSpeechFormat] = useState("mp3");
|
||||
|
||||
// Transcription-specific
|
||||
const MAX_TRANSCRIPTION_FILE_SIZE = 4 * 1024 * 1024 * 1024; // 4 GB
|
||||
const [audioFile, setAudioFile] = useState<File | null>(null);
|
||||
const [fileSizeError, setFileSizeError] = useState<string | null>(null);
|
||||
|
||||
// Fix #390: Track which local providers (sdwebui, comfyui) are actually configured
|
||||
// so we can hide them when they haven't been set up in the providers page
|
||||
@@ -743,18 +753,35 @@ export default function MediaPageClient() {
|
||||
{/* Transcription: file upload */}
|
||||
{activeTab === "transcription" ? (
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-text-main mb-2">Audio File</label>
|
||||
<label className="block text-sm font-medium text-text-main mb-2">Audio / Video File</label>
|
||||
<input
|
||||
type="file"
|
||||
accept="audio/*,video/*"
|
||||
onChange={(e) => setAudioFile(e.target.files?.[0] ?? null)}
|
||||
onChange={(e) => {
|
||||
const file = e.target.files?.[0] ?? null;
|
||||
setFileSizeError(null);
|
||||
if (file && file.size > MAX_TRANSCRIPTION_FILE_SIZE) {
|
||||
setFileSizeError(`File too large (${formatFileSize(file.size)}). Maximum allowed: 4 GB.`);
|
||||
setAudioFile(null);
|
||||
e.target.value = "";
|
||||
return;
|
||||
}
|
||||
setAudioFile(file);
|
||||
}}
|
||||
className="w-full px-3 py-2 rounded-lg bg-surface border border-black/10 dark:border-white/10 text-text-main text-sm focus:outline-none focus:ring-2 focus:ring-primary/30 file:mr-3 file:py-1 file:px-3 file:rounded file:border-0 file:bg-primary/10 file:text-primary file:text-sm"
|
||||
/>
|
||||
{audioFile && (
|
||||
<p className="text-xs text-text-muted mt-1">
|
||||
{audioFile.name} ({(audioFile.size / 1024).toFixed(0)} KB)
|
||||
{fileSizeError && (
|
||||
<p className="text-xs text-red-400 mt-1 flex items-center gap-1">
|
||||
<span className="material-symbols-outlined text-[12px]">error</span>
|
||||
{fileSizeError}
|
||||
</p>
|
||||
)}
|
||||
{audioFile && !fileSizeError && (
|
||||
<p className="text-xs text-text-muted mt-1">
|
||||
{audioFile.name} ({formatFileSize(audioFile.size)})
|
||||
</p>
|
||||
)}
|
||||
<p className="text-[10px] text-text-muted/60 mt-1">Supports audio and video files up to 4 GB</p>
|
||||
</div>
|
||||
) : (
|
||||
/* Prompt / Text */
|
||||
|
||||
@@ -89,6 +89,14 @@ export default function OnboardingWizard() {
|
||||
|
||||
const handleSetPassword = async () => {
|
||||
if (skipSecurity) {
|
||||
// (#574) Explicitly disable requireLogin when skipping password setup
|
||||
try {
|
||||
await fetch("/api/settings/require-login", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ requireLogin: false }),
|
||||
});
|
||||
} catch {}
|
||||
handleNext();
|
||||
return;
|
||||
}
|
||||
@@ -176,6 +184,19 @@ export default function OnboardingWizard() {
|
||||
|
||||
const handleFinish = async () => {
|
||||
try {
|
||||
// (#574) If no password was set during wizard, disable requireLogin
|
||||
// to prevent the user from being locked out on the login page
|
||||
const settings = await fetch("/api/settings/require-login")
|
||||
.then((r) => r.json())
|
||||
.catch(() => ({}));
|
||||
if (!settings.hasPassword) {
|
||||
await fetch("/api/settings/require-login", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ requireLogin: false }),
|
||||
}).catch(() => {});
|
||||
}
|
||||
|
||||
await fetch("/api/settings", {
|
||||
method: "PATCH",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect, useCallback, useRef, useMemo } from "react";
|
||||
import { useState, useEffect, useLayoutEffect, useCallback, useRef, useMemo } from "react";
|
||||
import { createPortal } from "react-dom";
|
||||
import { useNotificationStore } from "@/store/notificationStore";
|
||||
import PropTypes from "prop-types";
|
||||
import { useParams, useRouter } from "next/navigation";
|
||||
@@ -39,9 +40,22 @@ import {
|
||||
type CompatByProtocolMap = Partial<
|
||||
Record<
|
||||
ModelCompatProtocolKey,
|
||||
{ normalizeToolCallId?: boolean; preserveOpenAIDeveloperRole?: boolean }
|
||||
{
|
||||
normalizeToolCallId?: boolean;
|
||||
preserveOpenAIDeveloperRole?: boolean;
|
||||
upstreamHeaders?: Record<string, string>;
|
||||
}
|
||||
>
|
||||
>;
|
||||
|
||||
/** PATCH fields for provider model compat (matches API + `ModelCompatPerProtocol` shape). */
|
||||
type ModelCompatSavePatch = {
|
||||
normalizeToolCallId?: boolean;
|
||||
preserveOpenAIDeveloperRole?: boolean;
|
||||
upstreamHeaders?: Record<string, string>;
|
||||
compatByProtocol?: CompatByProtocolMap;
|
||||
};
|
||||
|
||||
type CompatModelRow = {
|
||||
id?: string;
|
||||
name?: string;
|
||||
@@ -50,6 +64,7 @@ type CompatModelRow = {
|
||||
supportedEndpoints?: string[];
|
||||
normalizeToolCallId?: boolean;
|
||||
preserveOpenAIDeveloperRole?: boolean;
|
||||
upstreamHeaders?: Record<string, string>;
|
||||
compatByProtocol?: CompatByProtocolMap;
|
||||
};
|
||||
|
||||
@@ -155,24 +170,115 @@ function anyNoPreserveCompatBadge(
|
||||
return false;
|
||||
}
|
||||
|
||||
function upstreamHeadersRecordsEqual(
|
||||
a: Record<string, string>,
|
||||
b: Record<string, string>
|
||||
): boolean {
|
||||
const ka = Object.keys(a).sort();
|
||||
const kb = Object.keys(b).sort();
|
||||
if (ka.length !== kb.length) return false;
|
||||
return ka.every((k, i) => k === kb[i] && a[k] === b[k]);
|
||||
}
|
||||
|
||||
type HeaderDraftRow = { id: string; name: string; value: string };
|
||||
|
||||
const UPSTREAM_HEADERS_UI_MAX = 16;
|
||||
|
||||
function recordToHeaderRows(rec: Record<string, string>, genId: () => string): HeaderDraftRow[] {
|
||||
const entries = Object.entries(rec).filter(([k]) => k.trim());
|
||||
if (entries.length === 0) return [{ id: genId(), name: "", value: "" }];
|
||||
return entries.map(([name, value]) => ({ id: genId(), name, value }));
|
||||
}
|
||||
|
||||
function headerRowsToRecord(rows: HeaderDraftRow[]): Record<string, string> {
|
||||
const out: Record<string, string> = {};
|
||||
for (const r of rows) {
|
||||
const k = r.name.trim();
|
||||
if (!k) continue;
|
||||
out[k] = r.value;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
type ProviderModelsApiErrorBody = {
|
||||
error?: {
|
||||
message?: string;
|
||||
details?: Array<{ field?: string; message?: string }>;
|
||||
};
|
||||
};
|
||||
|
||||
async function formatProviderModelsErrorResponse(res: Response): Promise<string> {
|
||||
try {
|
||||
const data = (await res.json()) as ProviderModelsApiErrorBody;
|
||||
const err = data?.error;
|
||||
if (Array.isArray(err?.details) && err.details.length > 0) {
|
||||
return err.details
|
||||
.map((d) => {
|
||||
const f = typeof d.field === "string" && d.field ? d.field : "?";
|
||||
const m = typeof d.message === "string" ? d.message : "";
|
||||
return m ? `${f}: ${m}` : f;
|
||||
})
|
||||
.join("; ");
|
||||
}
|
||||
if (typeof err?.message === "string" && err.message.trim()) {
|
||||
return err.message.trim();
|
||||
}
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
const st = res.statusText?.trim();
|
||||
return st || `HTTP ${res.status}`;
|
||||
}
|
||||
|
||||
function effectiveUpstreamHeadersForProtocol(
|
||||
modelId: string,
|
||||
protocol: string,
|
||||
customMap: CompatModelMap,
|
||||
overrideMap: CompatModelMap
|
||||
): Record<string, string> {
|
||||
const c = customMap.get(modelId);
|
||||
const o = overrideMap.get(modelId);
|
||||
const base: Record<string, string> = {};
|
||||
if (c?.upstreamHeaders && typeof c.upstreamHeaders === "object") {
|
||||
Object.assign(base, c.upstreamHeaders);
|
||||
} else if (o?.upstreamHeaders && typeof o.upstreamHeaders === "object") {
|
||||
Object.assign(base, o.upstreamHeaders);
|
||||
}
|
||||
const pc = getProtoSlice(c, o, protocol);
|
||||
if (pc?.upstreamHeaders && typeof pc.upstreamHeaders === "object") {
|
||||
Object.assign(base, pc.upstreamHeaders);
|
||||
}
|
||||
return base;
|
||||
}
|
||||
|
||||
function anyUpstreamHeadersBadge(
|
||||
modelId: string,
|
||||
customMap: CompatModelMap,
|
||||
overrideMap: CompatModelMap
|
||||
): boolean {
|
||||
const c = customMap.get(modelId);
|
||||
const o = overrideMap.get(modelId);
|
||||
const nonempty = (u: unknown) =>
|
||||
u && typeof u === "object" && !Array.isArray(u) && Object.keys(u as object).length > 0;
|
||||
if (nonempty(c?.upstreamHeaders) || nonempty(o?.upstreamHeaders)) return true;
|
||||
for (const p of MODEL_COMPAT_PROTOCOL_KEYS) {
|
||||
const pc = getProtoSlice(c, o, p);
|
||||
if (nonempty(pc?.upstreamHeaders)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
interface ModelRowProps {
|
||||
model: { id: string };
|
||||
fullModel: string;
|
||||
alias?: string;
|
||||
copied?: string;
|
||||
onCopy: (text: string, key: string) => void;
|
||||
t: (key: string, values?: Record<string, unknown>) => string;
|
||||
showDeveloperToggle?: boolean;
|
||||
effectiveModelNormalize: (modelId: string, protocol?: string) => boolean;
|
||||
effectiveModelPreserveDeveloper: (modelId: string, protocol?: string) => boolean;
|
||||
saveModelCompatFlags: (
|
||||
modelId: string,
|
||||
patch: {
|
||||
normalizeToolCallId?: boolean;
|
||||
preserveOpenAIDeveloperRole?: boolean;
|
||||
compatByProtocol?: CompatByProtocolMap;
|
||||
}
|
||||
) => void;
|
||||
saveModelCompatFlags: (modelId: string, patch: ModelCompatSavePatch) => void;
|
||||
getUpstreamHeadersRecord: (protocol: string) => Record<string, string>;
|
||||
compatDisabled?: boolean;
|
||||
}
|
||||
|
||||
@@ -186,14 +292,8 @@ interface PassthroughModelRowProps {
|
||||
showDeveloperToggle?: boolean;
|
||||
effectiveModelNormalize: (modelId: string, protocol?: string) => boolean;
|
||||
effectiveModelPreserveDeveloper: (modelId: string, protocol?: string) => boolean;
|
||||
saveModelCompatFlags: (
|
||||
modelId: string,
|
||||
patch: {
|
||||
normalizeToolCallId?: boolean;
|
||||
preserveOpenAIDeveloperRole?: boolean;
|
||||
compatByProtocol?: CompatByProtocolMap;
|
||||
}
|
||||
) => void;
|
||||
saveModelCompatFlags: (modelId: string, patch: ModelCompatSavePatch) => void;
|
||||
getUpstreamHeadersRecord: (protocol: string) => Record<string, string>;
|
||||
compatDisabled?: boolean;
|
||||
}
|
||||
|
||||
@@ -207,6 +307,7 @@ interface PassthroughModelsSectionProps {
|
||||
t: (key: string, values?: Record<string, unknown>) => string;
|
||||
effectiveModelNormalize: (alias: string) => boolean;
|
||||
effectiveModelPreserveDeveloper: (alias: string) => boolean;
|
||||
getUpstreamHeadersRecord: (modelId: string, protocol: string) => Record<string, string>;
|
||||
saveModelCompatFlags: (
|
||||
modelId: string,
|
||||
flags: {
|
||||
@@ -243,6 +344,7 @@ interface CompatibleModelsSectionProps {
|
||||
t: (key: string, values?: Record<string, unknown>) => string;
|
||||
effectiveModelNormalize: (alias: string) => boolean;
|
||||
effectiveModelPreserveDeveloper: (alias: string) => boolean;
|
||||
getUpstreamHeadersRecord: (modelId: string, protocol: string) => Record<string, string>;
|
||||
saveModelCompatFlags: (
|
||||
modelId: string,
|
||||
flags: {
|
||||
@@ -376,6 +478,7 @@ function ModelCompatPopover({
|
||||
t,
|
||||
effectiveModelNormalize,
|
||||
effectiveModelPreserveDeveloper,
|
||||
getUpstreamHeadersRecord,
|
||||
onCompatPatch,
|
||||
showDeveloperToggle = true,
|
||||
disabled,
|
||||
@@ -383,11 +486,13 @@ function ModelCompatPopover({
|
||||
t: (key: string) => string;
|
||||
effectiveModelNormalize: (protocol: string) => boolean;
|
||||
effectiveModelPreserveDeveloper: (protocol: string) => boolean;
|
||||
getUpstreamHeadersRecord: (protocol: string) => Record<string, string>;
|
||||
onCompatPatch: (
|
||||
protocol: string,
|
||||
payload: {
|
||||
normalizeToolCallId?: boolean;
|
||||
preserveOpenAIDeveloperRole?: boolean;
|
||||
upstreamHeaders?: Record<string, string>;
|
||||
}
|
||||
) => void;
|
||||
showDeveloperToggle?: boolean;
|
||||
@@ -395,15 +500,85 @@ function ModelCompatPopover({
|
||||
}) {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [protocol, setProtocol] = useState<string>(MODEL_COMPAT_PROTOCOL_KEYS[0]);
|
||||
const [headerRows, setHeaderRows] = useState<HeaderDraftRow[]>([]);
|
||||
const [valuePeekRowId, setValuePeekRowId] = useState<string | null>(null);
|
||||
const [valueFocusRowId, setValueFocusRowId] = useState<string | null>(null);
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const panelRef = useRef<HTMLDivElement | null>(null);
|
||||
const [portalPanelRect, setPortalPanelRect] = useState<{
|
||||
top: number;
|
||||
left: number;
|
||||
width: number;
|
||||
} | null>(null);
|
||||
const headerRowIdRef = useRef(0);
|
||||
const headerRowsRef = useRef<HeaderDraftRow[]>([]);
|
||||
headerRowsRef.current = headerRows;
|
||||
|
||||
const genHeaderRowId = () => {
|
||||
headerRowIdRef.current += 1;
|
||||
return `uh-${headerRowIdRef.current}`;
|
||||
};
|
||||
|
||||
const normalizeToolCallId = effectiveModelNormalize(protocol);
|
||||
const preserveDeveloperRole = effectiveModelPreserveDeveloper(protocol);
|
||||
const devToggle = showDeveloperToggle && protocol !== "claude";
|
||||
|
||||
// Click-outside: check both trigger and panel so that if the panel is ever rendered
|
||||
// in a portal (outside this subtree), clicks inside the panel still do not close it.
|
||||
const tryCommitHeaderRows = useCallback(
|
||||
(rows: HeaderDraftRow[]) => {
|
||||
const parsed = headerRowsToRecord(rows);
|
||||
const current = getUpstreamHeadersRecord(protocol);
|
||||
if (upstreamHeadersRecordsEqual(parsed, current)) return;
|
||||
onCompatPatch(protocol, { upstreamHeaders: parsed });
|
||||
},
|
||||
[getUpstreamHeadersRecord, onCompatPatch, protocol]
|
||||
);
|
||||
|
||||
const onHeaderFieldBlur = useCallback(() => {
|
||||
queueMicrotask(() => tryCommitHeaderRows(headerRowsRef.current));
|
||||
}, [tryCommitHeaderRows]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) return;
|
||||
return () => {
|
||||
tryCommitHeaderRows(headerRowsRef.current);
|
||||
};
|
||||
}, [open, tryCommitHeaderRows]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) return;
|
||||
const rec = getUpstreamHeadersRecord(protocol);
|
||||
setHeaderRows(recordToHeaderRows(rec, genHeaderRowId));
|
||||
// Only re-load rows when opening or switching protocol — not when the parent passes a new
|
||||
// inline callback every render (would wipe in-progress edits).
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps -- see above
|
||||
}, [open, protocol]);
|
||||
|
||||
useEffect(() => {
|
||||
setValuePeekRowId(null);
|
||||
setValueFocusRowId(null);
|
||||
}, [open, protocol]);
|
||||
|
||||
const namedHeaderCount = headerRows.filter((r) => r.name.trim()).length;
|
||||
const canAddHeaderRow = namedHeaderCount < UPSTREAM_HEADERS_UI_MAX;
|
||||
|
||||
const updateHeaderRow = (id: string, patch: Partial<Pick<HeaderDraftRow, "name" | "value">>) => {
|
||||
setHeaderRows((prev) => prev.map((r) => (r.id === id ? { ...r, ...patch } : r)));
|
||||
};
|
||||
|
||||
const addHeaderRow = () => {
|
||||
if (!canAddHeaderRow) return;
|
||||
setHeaderRows((prev) => [...prev, { id: genHeaderRowId(), name: "", value: "" }]);
|
||||
};
|
||||
|
||||
const removeHeaderRow = (id: string) => {
|
||||
setHeaderRows((prev) => {
|
||||
const next = prev.filter((r) => r.id !== id);
|
||||
const normalized = next.length === 0 ? [{ id: genHeaderRowId(), name: "", value: "" }] : next;
|
||||
queueMicrotask(() => tryCommitHeaderRows(normalized));
|
||||
return normalized;
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) return;
|
||||
const onDocClick = (e: MouseEvent) => {
|
||||
@@ -416,66 +591,189 @@ function ModelCompatPopover({
|
||||
return () => document.removeEventListener("mousedown", onDocClick);
|
||||
}, [open]);
|
||||
|
||||
const updatePortalPanelRect = useCallback(() => {
|
||||
if (!open || !ref.current) return;
|
||||
const rect = ref.current.getBoundingClientRect();
|
||||
const margin = 10;
|
||||
const width = Math.min(window.innerWidth - 2 * margin, 24 * 16);
|
||||
let left = rect.right - width;
|
||||
left = Math.max(margin, Math.min(left, window.innerWidth - width - margin));
|
||||
setPortalPanelRect({ top: rect.bottom + 8, left, width });
|
||||
}, [open]);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (!open) {
|
||||
setPortalPanelRect(null);
|
||||
return;
|
||||
}
|
||||
updatePortalPanelRect();
|
||||
window.addEventListener("resize", updatePortalPanelRect);
|
||||
window.addEventListener("scroll", updatePortalPanelRect, true);
|
||||
return () => {
|
||||
window.removeEventListener("resize", updatePortalPanelRect);
|
||||
window.removeEventListener("scroll", updatePortalPanelRect, true);
|
||||
};
|
||||
}, [open, updatePortalPanelRect]);
|
||||
|
||||
const panelChromeClass =
|
||||
"flex max-h-[min(82vh,42rem)] flex-col overflow-hidden rounded-xl border-2 border-zinc-200 bg-white shadow-2xl dark:border-zinc-600 dark:bg-zinc-950";
|
||||
|
||||
return (
|
||||
<div className="relative inline-block" ref={ref}>
|
||||
<div className="relative inline-flex" ref={ref}>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setOpen((v) => !v)}
|
||||
disabled={disabled}
|
||||
className="inline-flex items-center gap-1 px-2 py-1 text-xs rounded-md border border-border bg-sidebar/50 hover:bg-sidebar text-text-muted hover:text-text-main disabled:opacity-50"
|
||||
className="inline-flex items-center gap-1.5 px-2.5 py-1.5 text-xs font-medium rounded-lg border border-border bg-background text-text-muted hover:bg-muted hover:text-text-main disabled:opacity-50 transition-colors"
|
||||
title={t("compatAdjustmentsTitle")}
|
||||
>
|
||||
<span className="material-symbols-outlined text-sm">tune</span>
|
||||
<span className="material-symbols-outlined text-base leading-none">tune</span>
|
||||
{t("compatButtonLabel")}
|
||||
</button>
|
||||
{open && (
|
||||
<div
|
||||
ref={panelRef}
|
||||
className="absolute left-0 top-full mt-1 z-50 min-w-[220px] max-w-[92vw] p-3 rounded-lg border border-border bg-white dark:bg-zinc-900 shadow-xl ring-1 ring-black/5 dark:ring-white/10"
|
||||
>
|
||||
<p className="text-[10px] font-semibold uppercase tracking-wide text-text-muted mb-1">
|
||||
{t("compatAdjustmentsTitle")}
|
||||
</p>
|
||||
<p className="text-[10px] text-text-muted mb-2 leading-snug">{t("compatProtocolHint")}</p>
|
||||
<label className="block text-[10px] font-medium text-text-muted mb-1">
|
||||
{t("compatProtocolLabel")}
|
||||
</label>
|
||||
<select
|
||||
value={protocol}
|
||||
onChange={(e) => setProtocol(e.target.value)}
|
||||
disabled={disabled}
|
||||
className="w-full mb-3 px-2 py-1.5 text-xs rounded-md border border-border bg-white dark:bg-zinc-800 text-zinc-900 dark:text-zinc-100 focus:outline-none focus:ring-1 focus:ring-primary/50"
|
||||
{open &&
|
||||
typeof document !== "undefined" &&
|
||||
portalPanelRect &&
|
||||
createPortal(
|
||||
<div
|
||||
ref={panelRef}
|
||||
className={panelChromeClass}
|
||||
style={{
|
||||
position: "fixed",
|
||||
top: portalPanelRect.top,
|
||||
left: portalPanelRect.left,
|
||||
width: portalPanelRect.width,
|
||||
zIndex: 10040,
|
||||
}}
|
||||
>
|
||||
{MODEL_COMPAT_PROTOCOL_KEYS.map((p) => (
|
||||
<option key={p} value={p}>
|
||||
{t(compatProtocolLabelKey(p))}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<div className="flex flex-col gap-3">
|
||||
<Toggle
|
||||
size="sm"
|
||||
label={t("compatToolIdShort")}
|
||||
title={t("normalizeToolCallIdLabel")}
|
||||
checked={normalizeToolCallId}
|
||||
onChange={(v) => onCompatPatch(protocol, { normalizeToolCallId: v })}
|
||||
disabled={disabled}
|
||||
/>
|
||||
{devToggle && (
|
||||
<Toggle
|
||||
size="sm"
|
||||
label={t("compatDoNotPreserveDeveloper")}
|
||||
title={t("preserveDeveloperRoleLabel")}
|
||||
checked={preserveDeveloperRole === false}
|
||||
onChange={(checked) =>
|
||||
onCompatPatch(protocol, { preserveOpenAIDeveloperRole: !checked })
|
||||
}
|
||||
<div className="shrink-0 border-b-2 border-zinc-200 bg-zinc-100 px-3 py-2.5 dark:border-zinc-600 dark:bg-zinc-900">
|
||||
<p className="text-xs font-semibold text-text-main">{t("compatAdjustmentsTitle")}</p>
|
||||
<p className="text-[11px] text-text-muted mt-1 leading-relaxed">
|
||||
{t("compatProtocolHint")}
|
||||
</p>
|
||||
</div>
|
||||
<div className="min-h-0 flex-1 overflow-x-hidden overflow-y-auto bg-white p-3 [scrollbar-gutter:stable] [scrollbar-width:thin] dark:bg-zinc-950">
|
||||
<label className="block text-[11px] font-medium text-text-muted mb-1.5">
|
||||
{t("compatProtocolLabel")}
|
||||
</label>
|
||||
<select
|
||||
value={protocol}
|
||||
onChange={(e) => setProtocol(e.target.value)}
|
||||
disabled={disabled}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
className="mb-4 w-full rounded-lg border border-zinc-200 bg-white px-2.5 py-2 text-xs text-text-main focus:border-primary focus:outline-none focus:ring-2 focus:ring-primary/30 dark:border-zinc-600 dark:bg-zinc-900"
|
||||
>
|
||||
{MODEL_COMPAT_PROTOCOL_KEYS.map((p) => (
|
||||
<option key={p} value={p}>
|
||||
{t(compatProtocolLabelKey(p))}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<div className="flex flex-col gap-3.5">
|
||||
<Toggle
|
||||
size="sm"
|
||||
label={t("compatToolIdShort")}
|
||||
title={t("normalizeToolCallIdLabel")}
|
||||
checked={normalizeToolCallId}
|
||||
onChange={(v) => onCompatPatch(protocol, { normalizeToolCallId: v })}
|
||||
disabled={disabled}
|
||||
/>
|
||||
{devToggle && (
|
||||
<Toggle
|
||||
size="sm"
|
||||
label={t("compatDoNotPreserveDeveloper")}
|
||||
title={t("preserveDeveloperRoleLabel")}
|
||||
checked={preserveDeveloperRole === false}
|
||||
onChange={(checked) =>
|
||||
onCompatPatch(protocol, { preserveOpenAIDeveloperRole: !checked })
|
||||
}
|
||||
disabled={disabled}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="mt-4 rounded-lg border-2 border-zinc-200 bg-zinc-100 p-3 dark:border-zinc-600 dark:bg-zinc-900">
|
||||
<label className="block text-[11px] font-semibold text-text-main mb-1">
|
||||
{t("compatUpstreamHeadersLabel")}
|
||||
</label>
|
||||
<p className="text-[11px] text-text-muted mb-3 leading-relaxed">
|
||||
{t("compatUpstreamHeadersHint")}
|
||||
</p>
|
||||
<div className="space-y-2">
|
||||
<div className="grid grid-cols-[minmax(0,1fr)_minmax(0,1fr)_auto] gap-1.5 items-end text-[10px] font-medium uppercase tracking-wide text-text-muted px-0.5">
|
||||
<span>{t("compatUpstreamHeaderName")}</span>
|
||||
<span className="col-span-1">{t("compatUpstreamHeaderValue")}</span>
|
||||
<span className="w-8 shrink-0" aria-hidden />
|
||||
</div>
|
||||
{headerRows.map((row) => (
|
||||
<div
|
||||
key={row.id}
|
||||
className="grid grid-cols-[minmax(0,1fr)_minmax(0,1fr)_auto] gap-1.5 items-center"
|
||||
>
|
||||
<Input
|
||||
value={row.name}
|
||||
onChange={(e) => updateHeaderRow(row.id, { name: e.target.value })}
|
||||
onBlur={onHeaderFieldBlur}
|
||||
disabled={disabled}
|
||||
placeholder="Authentication"
|
||||
className="gap-0 min-w-0"
|
||||
inputClassName="h-9 bg-white py-1.5 px-2 text-xs font-mono dark:bg-zinc-900"
|
||||
autoComplete="off"
|
||||
/>
|
||||
<div
|
||||
className="min-w-0"
|
||||
onMouseEnter={() => setValuePeekRowId(row.id)}
|
||||
onMouseLeave={() =>
|
||||
setValuePeekRowId((cur) => (cur === row.id ? null : cur))
|
||||
}
|
||||
>
|
||||
<Input
|
||||
type={
|
||||
valuePeekRowId === row.id || valueFocusRowId === row.id
|
||||
? "text"
|
||||
: "password"
|
||||
}
|
||||
value={row.value}
|
||||
onChange={(e) => updateHeaderRow(row.id, { value: e.target.value })}
|
||||
onFocus={() => setValueFocusRowId(row.id)}
|
||||
onBlur={() => {
|
||||
setValueFocusRowId((cur) => (cur === row.id ? null : cur));
|
||||
onHeaderFieldBlur();
|
||||
}}
|
||||
disabled={disabled}
|
||||
placeholder="•••"
|
||||
className="gap-0 min-w-0"
|
||||
inputClassName="h-9 bg-white py-1.5 px-2 text-xs dark:bg-zinc-900"
|
||||
autoComplete="off"
|
||||
spellCheck={false}
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
disabled={disabled || headerRows.length <= 1}
|
||||
onClick={() => removeHeaderRow(row.id)}
|
||||
title={t("compatUpstreamRemoveRow")}
|
||||
className="flex h-9 w-9 shrink-0 items-center justify-center rounded-lg border border-border/80 text-text-muted hover:bg-red-500/10 hover:text-red-600 dark:hover:text-red-400 disabled:opacity-30 disabled:hover:bg-transparent disabled:hover:text-text-muted transition-colors"
|
||||
>
|
||||
<span className="material-symbols-outlined text-lg leading-none">
|
||||
close
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
disabled={disabled || !canAddHeaderRow}
|
||||
onClick={addHeaderRow}
|
||||
className="mt-3 flex w-full items-center justify-center gap-1.5 rounded-lg border border-dashed border-border py-2 text-xs font-medium text-primary hover:bg-primary/5 disabled:opacity-40 disabled:hover:bg-transparent transition-colors"
|
||||
>
|
||||
<span className="material-symbols-outlined text-base leading-none">add</span>
|
||||
{t("compatUpstreamAddRow")}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>,
|
||||
document.body
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1196,14 +1494,13 @@ export default function ProviderDetailPage() {
|
||||
protocol = MODEL_COMPAT_PROTOCOL_KEYS[0]
|
||||
) => effectivePreserveForProtocol(modelId, protocol, customMap, overrideMap);
|
||||
|
||||
const saveModelCompatFlags = async (
|
||||
modelId: string,
|
||||
patch: {
|
||||
normalizeToolCallId?: boolean;
|
||||
preserveOpenAIDeveloperRole?: boolean;
|
||||
compatByProtocol?: CompatByProtocolMap;
|
||||
}
|
||||
) => {
|
||||
const getUpstreamHeadersRecordForModel = useCallback(
|
||||
(modelId: string, protocol: string) =>
|
||||
effectiveUpstreamHeadersForProtocol(modelId, protocol, customMap, overrideMap),
|
||||
[customMap, overrideMap]
|
||||
);
|
||||
|
||||
const saveModelCompatFlags = async (modelId: string, patch: ModelCompatSavePatch) => {
|
||||
setCompatSavingModelId(modelId);
|
||||
try {
|
||||
const c = customMap.get(modelId) as Record<string, unknown> | undefined;
|
||||
@@ -1211,7 +1508,8 @@ export default function ProviderDetailPage() {
|
||||
const onlyCompatByProtocol =
|
||||
patch.compatByProtocol &&
|
||||
patch.normalizeToolCallId === undefined &&
|
||||
patch.preserveOpenAIDeveloperRole === undefined;
|
||||
patch.preserveOpenAIDeveloperRole === undefined &&
|
||||
!("upstreamHeaders" in patch);
|
||||
|
||||
if (c) {
|
||||
if (onlyCompatByProtocol) {
|
||||
@@ -1253,7 +1551,10 @@ export default function ProviderDetailPage() {
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
if (!res.ok) {
|
||||
notify.error(t("failedSaveCustomModel"));
|
||||
const detail = await formatProviderModelsErrorResponse(res);
|
||||
notify.error(
|
||||
detail ? `${t("failedSaveCustomModel")} — ${detail}` : t("failedSaveCustomModel")
|
||||
);
|
||||
return;
|
||||
}
|
||||
} catch {
|
||||
@@ -1286,6 +1587,7 @@ export default function ProviderDetailPage() {
|
||||
t={t}
|
||||
effectiveModelNormalize={effectiveModelNormalize}
|
||||
effectiveModelPreserveDeveloper={effectiveModelPreserveDeveloper}
|
||||
getUpstreamHeadersRecord={getUpstreamHeadersRecordForModel}
|
||||
saveModelCompatFlags={saveModelCompatFlags}
|
||||
compatSavingModelId={compatSavingModelId}
|
||||
onModelsChanged={fetchProviderModelMeta}
|
||||
@@ -1319,6 +1621,7 @@ export default function ProviderDetailPage() {
|
||||
t={t}
|
||||
effectiveModelNormalize={effectiveModelNormalize}
|
||||
effectiveModelPreserveDeveloper={effectiveModelPreserveDeveloper}
|
||||
getUpstreamHeadersRecord={getUpstreamHeadersRecordForModel}
|
||||
saveModelCompatFlags={saveModelCompatFlags}
|
||||
compatSavingModelId={compatSavingModelId}
|
||||
/>
|
||||
@@ -1356,23 +1659,18 @@ export default function ProviderDetailPage() {
|
||||
{importButton}
|
||||
<div className="flex flex-wrap gap-3">
|
||||
{models.map((model) => {
|
||||
const fullModel = `${providerStorageAlias}/${model.id}`;
|
||||
const oldFormatModel = `${providerId}/${model.id}`;
|
||||
const existingAlias = Object.entries(modelAliases).find(
|
||||
([, m]) => m === fullModel || m === oldFormatModel
|
||||
)?.[0];
|
||||
return (
|
||||
<ModelRow
|
||||
key={model.id}
|
||||
model={model}
|
||||
fullModel={`${providerDisplayAlias}/${model.id}`}
|
||||
alias={existingAlias}
|
||||
copied={copied}
|
||||
onCopy={copy}
|
||||
t={t}
|
||||
showDeveloperToggle
|
||||
effectiveModelNormalize={effectiveModelNormalize}
|
||||
effectiveModelPreserveDeveloper={effectiveModelPreserveDeveloper}
|
||||
getUpstreamHeadersRecord={(p) => getUpstreamHeadersRecordForModel(model.id, p)}
|
||||
saveModelCompatFlags={saveModelCompatFlags}
|
||||
compatDisabled={compatSavingModelId === model.id}
|
||||
/>
|
||||
@@ -2008,28 +2306,28 @@ export default function ProviderDetailPage() {
|
||||
function ModelRow({
|
||||
model,
|
||||
fullModel,
|
||||
alias,
|
||||
copied,
|
||||
onCopy,
|
||||
t,
|
||||
showDeveloperToggle = true,
|
||||
effectiveModelNormalize,
|
||||
effectiveModelPreserveDeveloper,
|
||||
getUpstreamHeadersRecord,
|
||||
saveModelCompatFlags,
|
||||
compatDisabled,
|
||||
}: ModelRowProps) {
|
||||
return (
|
||||
<div className="flex flex-col px-3 py-2 rounded-lg border border-border hover:bg-sidebar/50 min-w-[220px] max-w-md">
|
||||
<div className="flex items-center gap-2 flex-wrap">
|
||||
<span className="material-symbols-outlined text-base text-text-muted shrink-0">
|
||||
<div className="flex min-w-[220px] max-w-md items-center gap-2 rounded-lg border border-border px-3 py-2 hover:bg-sidebar/50">
|
||||
<div className="flex min-w-0 flex-1 flex-wrap items-center gap-2">
|
||||
<span className="material-symbols-outlined shrink-0 text-base text-text-muted">
|
||||
smart_toy
|
||||
</span>
|
||||
<code className="text-xs text-text-muted font-mono bg-sidebar px-1.5 py-0.5 rounded">
|
||||
<code className="rounded bg-sidebar px-1.5 py-0.5 font-mono text-xs text-text-muted">
|
||||
{fullModel}
|
||||
</code>
|
||||
<button
|
||||
onClick={() => onCopy(fullModel, `model-${model.id}`)}
|
||||
className="p-0.5 hover:bg-sidebar rounded text-text-muted hover:text-primary"
|
||||
className="rounded p-0.5 text-text-muted hover:bg-sidebar hover:text-primary"
|
||||
title={t("copyModel")}
|
||||
>
|
||||
<span className="material-symbols-outlined text-sm">
|
||||
@@ -2037,16 +2335,19 @@ function ModelRow({
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<ModelCompatPopover
|
||||
t={t}
|
||||
effectiveModelNormalize={(p) => effectiveModelNormalize(model.id, p)}
|
||||
effectiveModelPreserveDeveloper={(p) => effectiveModelPreserveDeveloper(model.id, p)}
|
||||
onCompatPatch={(protocol, payload) =>
|
||||
saveModelCompatFlags(model.id, { compatByProtocol: { [protocol]: payload } })
|
||||
}
|
||||
showDeveloperToggle={showDeveloperToggle}
|
||||
disabled={compatDisabled}
|
||||
/>
|
||||
<div className="shrink-0">
|
||||
<ModelCompatPopover
|
||||
t={t}
|
||||
effectiveModelNormalize={(p) => effectiveModelNormalize(model.id, p)}
|
||||
effectiveModelPreserveDeveloper={(p) => effectiveModelPreserveDeveloper(model.id, p)}
|
||||
getUpstreamHeadersRecord={getUpstreamHeadersRecord}
|
||||
onCompatPatch={(protocol, payload) =>
|
||||
saveModelCompatFlags(model.id, { compatByProtocol: { [protocol]: payload } })
|
||||
}
|
||||
showDeveloperToggle={showDeveloperToggle}
|
||||
disabled={compatDisabled}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -2056,13 +2357,13 @@ ModelRow.propTypes = {
|
||||
id: PropTypes.string.isRequired,
|
||||
}).isRequired,
|
||||
fullModel: PropTypes.string.isRequired,
|
||||
alias: PropTypes.string,
|
||||
copied: PropTypes.string,
|
||||
onCopy: PropTypes.func.isRequired,
|
||||
t: PropTypes.func,
|
||||
showDeveloperToggle: PropTypes.bool,
|
||||
effectiveModelNormalize: PropTypes.func.isRequired,
|
||||
effectiveModelPreserveDeveloper: PropTypes.func.isRequired,
|
||||
getUpstreamHeadersRecord: PropTypes.func.isRequired,
|
||||
saveModelCompatFlags: PropTypes.func.isRequired,
|
||||
compatDisabled: PropTypes.bool,
|
||||
};
|
||||
@@ -2077,6 +2378,7 @@ function PassthroughModelsSection({
|
||||
t,
|
||||
effectiveModelNormalize,
|
||||
effectiveModelPreserveDeveloper,
|
||||
getUpstreamHeadersRecord,
|
||||
saveModelCompatFlags,
|
||||
compatSavingModelId,
|
||||
}: PassthroughModelsSectionProps) {
|
||||
@@ -2161,6 +2463,7 @@ function PassthroughModelsSection({
|
||||
showDeveloperToggle
|
||||
effectiveModelNormalize={effectiveModelNormalize}
|
||||
effectiveModelPreserveDeveloper={effectiveModelPreserveDeveloper}
|
||||
getUpstreamHeadersRecord={(p) => getUpstreamHeadersRecord(modelId, p)}
|
||||
saveModelCompatFlags={saveModelCompatFlags}
|
||||
compatDisabled={compatSavingModelId === modelId}
|
||||
/>
|
||||
@@ -2181,6 +2484,7 @@ PassthroughModelsSection.propTypes = {
|
||||
t: PropTypes.func.isRequired,
|
||||
effectiveModelNormalize: PropTypes.func.isRequired,
|
||||
effectiveModelPreserveDeveloper: PropTypes.func.isRequired,
|
||||
getUpstreamHeadersRecord: PropTypes.func.isRequired,
|
||||
saveModelCompatFlags: PropTypes.func.isRequired,
|
||||
compatSavingModelId: PropTypes.string,
|
||||
};
|
||||
@@ -2195,24 +2499,25 @@ function PassthroughModelRow({
|
||||
showDeveloperToggle = true,
|
||||
effectiveModelNormalize,
|
||||
effectiveModelPreserveDeveloper,
|
||||
getUpstreamHeadersRecord,
|
||||
saveModelCompatFlags,
|
||||
compatDisabled,
|
||||
}: PassthroughModelRowProps) {
|
||||
return (
|
||||
<div className="flex flex-col gap-0 p-3 rounded-lg border border-border hover:bg-sidebar/50">
|
||||
<div className="flex items-start gap-3">
|
||||
<span className="material-symbols-outlined text-base text-text-muted shrink-0">
|
||||
<div className="flex gap-0 rounded-lg border border-border p-3 hover:bg-sidebar/50">
|
||||
<div className="flex min-w-0 flex-1 items-start gap-3">
|
||||
<span className="material-symbols-outlined shrink-0 text-base text-text-muted">
|
||||
smart_toy
|
||||
</span>
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-sm font-medium truncate">{modelId}</p>
|
||||
<div className="flex items-center gap-1 mt-1 flex-wrap">
|
||||
<code className="text-xs text-text-muted font-mono bg-sidebar px-1.5 py-0.5 rounded">
|
||||
<div className="min-w-0 flex-1">
|
||||
<p className="truncate text-sm font-medium">{modelId}</p>
|
||||
<div className="mt-1 flex flex-wrap items-center gap-1">
|
||||
<code className="rounded bg-sidebar px-1.5 py-0.5 font-mono text-xs text-text-muted">
|
||||
{fullModel}
|
||||
</code>
|
||||
<button
|
||||
onClick={() => onCopy(fullModel, `model-${modelId}`)}
|
||||
className="p-0.5 hover:bg-sidebar rounded text-text-muted hover:text-primary"
|
||||
className="rounded p-0.5 text-text-muted hover:bg-sidebar hover:text-primary"
|
||||
title={t("copyModel")}
|
||||
>
|
||||
<span className="material-symbols-outlined text-sm">
|
||||
@@ -2221,25 +2526,26 @@ function PassthroughModelRow({
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
onClick={onDeleteAlias}
|
||||
className="p-1 hover:bg-red-50 rounded text-red-500 shrink-0"
|
||||
title={t("removeModel")}
|
||||
>
|
||||
<span className="material-symbols-outlined text-sm">delete</span>
|
||||
</button>
|
||||
</div>
|
||||
<div className="pl-9">
|
||||
<div className="flex shrink-0 items-center gap-1 self-start">
|
||||
<ModelCompatPopover
|
||||
t={t}
|
||||
effectiveModelNormalize={(p) => effectiveModelNormalize(modelId, p)}
|
||||
effectiveModelPreserveDeveloper={(p) => effectiveModelPreserveDeveloper(modelId, p)}
|
||||
getUpstreamHeadersRecord={getUpstreamHeadersRecord}
|
||||
onCompatPatch={(protocol, payload) =>
|
||||
saveModelCompatFlags(modelId, { compatByProtocol: { [protocol]: payload } })
|
||||
}
|
||||
showDeveloperToggle={showDeveloperToggle}
|
||||
disabled={compatDisabled}
|
||||
/>
|
||||
<button
|
||||
onClick={onDeleteAlias}
|
||||
className="rounded p-1 text-red-500 hover:bg-red-50"
|
||||
title={t("removeModel")}
|
||||
>
|
||||
<span className="material-symbols-outlined text-sm">delete</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -2255,6 +2561,7 @@ PassthroughModelRow.propTypes = {
|
||||
showDeveloperToggle: PropTypes.bool,
|
||||
effectiveModelNormalize: PropTypes.func.isRequired,
|
||||
effectiveModelPreserveDeveloper: PropTypes.func.isRequired,
|
||||
getUpstreamHeadersRecord: PropTypes.func.isRequired,
|
||||
saveModelCompatFlags: PropTypes.func.isRequired,
|
||||
compatDisabled: PropTypes.bool,
|
||||
};
|
||||
@@ -2381,7 +2688,10 @@ function CustomModelsSection({
|
||||
body: JSON.stringify({ provider: providerId, modelId, ...patch }),
|
||||
});
|
||||
if (!res.ok) {
|
||||
notify.error(t("failedSaveCustomModel"));
|
||||
const detail = await formatProviderModelsErrorResponse(res);
|
||||
notify.error(
|
||||
detail ? `${t("failedSaveCustomModel")} — ${detail}` : t("failedSaveCustomModel")
|
||||
);
|
||||
return;
|
||||
}
|
||||
} catch {
|
||||
@@ -2422,7 +2732,8 @@ function CustomModelsSection({
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
throw new Error("Failed to save model endpoint settings");
|
||||
const detail = await formatProviderModelsErrorResponse(res);
|
||||
throw new Error(detail || "Failed to save model endpoint settings");
|
||||
}
|
||||
|
||||
await fetchCustomModels();
|
||||
@@ -2431,7 +2742,9 @@ function CustomModelsSection({
|
||||
cancelEdit();
|
||||
} catch (e) {
|
||||
console.error("Failed to save custom model:", e);
|
||||
notify.error("Failed to save model endpoint settings");
|
||||
notify.error(
|
||||
e instanceof Error && e.message ? e.message : "Failed to save model endpoint settings"
|
||||
);
|
||||
} finally {
|
||||
setSavingModelId(null);
|
||||
}
|
||||
@@ -2542,10 +2855,14 @@ function CustomModelsSection({
|
||||
return (
|
||||
<div
|
||||
key={model.id}
|
||||
className="flex items-center gap-3 p-3 rounded-lg border border-border hover:bg-sidebar/50"
|
||||
className="flex items-center gap-3 rounded-lg border border-border p-3 hover:bg-sidebar/50"
|
||||
>
|
||||
<span className="material-symbols-outlined text-base text-primary">tune</span>
|
||||
<div className="flex-1 min-w-0">
|
||||
{editingModelId !== model.id && (
|
||||
<span className="material-symbols-outlined text-base text-primary shrink-0">
|
||||
tune
|
||||
</span>
|
||||
)}
|
||||
<div className="min-w-0 flex-1">
|
||||
<p className="text-sm font-medium truncate">{model.name || model.id}</p>
|
||||
<div className="flex items-center gap-1 mt-1 flex-wrap">
|
||||
<code className="text-xs text-text-muted font-mono bg-sidebar px-1.5 py-0.5 rounded">
|
||||
@@ -2596,32 +2913,39 @@ function CustomModelsSection({
|
||||
{t("compatBadgeNoPreserve")}
|
||||
</span>
|
||||
)}
|
||||
{anyUpstreamHeadersBadge(model.id, customMap, overrideMap) && (
|
||||
<span
|
||||
className="text-[10px] px-1.5 py-0.5 rounded-full bg-violet-500/15 text-violet-400 font-medium"
|
||||
title={t("compatUpstreamHeadersLabel")}
|
||||
>
|
||||
{t("compatBadgeUpstreamHeaders")}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{editingModelId === model.id && (
|
||||
<div className="mt-3 p-3 rounded-lg border border-border bg-sidebar/40">
|
||||
<div className="flex items-end gap-3 flex-wrap">
|
||||
<div className="w-44">
|
||||
<div className="mt-3 min-w-0 max-w-full rounded-lg border border-border bg-muted p-3 dark:bg-zinc-900">
|
||||
<div className="flex min-w-0 flex-wrap items-end gap-x-3 gap-y-2">
|
||||
<div className="w-[11rem] shrink-0 min-w-0">
|
||||
<label className="text-xs text-text-muted mb-1 block">API Format</label>
|
||||
<select
|
||||
value={editingApiFormat}
|
||||
onChange={(e) => setEditingApiFormat(e.target.value)}
|
||||
className="w-full px-2.5 py-2 text-xs border border-border rounded-lg bg-background focus:outline-none focus:border-primary"
|
||||
className="w-full px-2.5 py-2 text-xs border border-border rounded-lg bg-background text-text-main focus:outline-none focus:border-primary"
|
||||
>
|
||||
<option value="chat-completions">Chat Completions</option>
|
||||
<option value="responses">Responses API</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div className="flex-1 min-w-[240px]">
|
||||
<span className="text-xs text-text-muted mb-1 block">
|
||||
<div className="flex min-w-0 flex-1 flex-wrap items-center gap-x-3 gap-y-1 overflow-x-auto overflow-y-visible [scrollbar-width:thin]">
|
||||
<span className="text-xs text-text-muted shrink-0">
|
||||
Supported Endpoints
|
||||
</span>
|
||||
<div className="flex items-center gap-3 flex-wrap">
|
||||
<div className="flex flex-wrap items-center gap-x-2 sm:gap-x-3 gap-y-1 min-w-0">
|
||||
{["chat", "embeddings", "images", "audio"].map((ep) => (
|
||||
<label
|
||||
key={ep}
|
||||
className="flex items-center gap-1.5 text-xs text-text-main cursor-pointer"
|
||||
className="flex items-center gap-1.5 text-xs text-text-main cursor-pointer whitespace-nowrap"
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
@@ -2648,51 +2972,52 @@ function CustomModelsSection({
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-3 pt-3 border-t border-border/80 w-full">
|
||||
<ModelCompatPopover
|
||||
t={t}
|
||||
effectiveModelNormalize={(p) =>
|
||||
effectiveNormalizeForProtocol(model.id, p, customMap, overrideMap)
|
||||
}
|
||||
effectiveModelPreserveDeveloper={(p) =>
|
||||
effectivePreserveForProtocol(model.id, p, customMap, overrideMap)
|
||||
}
|
||||
onCompatPatch={(protocol, payload) =>
|
||||
saveCustomCompat(model.id, {
|
||||
compatByProtocol: { [protocol]: payload },
|
||||
})
|
||||
}
|
||||
showDeveloperToggle
|
||||
disabled={savingModelId === model.id}
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-3 flex items-center gap-2">
|
||||
<Button
|
||||
size="sm"
|
||||
onClick={() => saveEdit(model.id)}
|
||||
disabled={savingModelId === model.id}
|
||||
>
|
||||
{savingModelId === model.id ? t("saving") : t("save")}
|
||||
</Button>
|
||||
<Button size="sm" variant="ghost" onClick={cancelEdit}>
|
||||
{t("cancel")}
|
||||
</Button>
|
||||
<div className="flex shrink-0 flex-wrap items-center gap-2 pb-0.5">
|
||||
<Button
|
||||
size="sm"
|
||||
onClick={() => saveEdit(model.id)}
|
||||
disabled={savingModelId === model.id}
|
||||
>
|
||||
{savingModelId === model.id ? t("saving") : t("save")}
|
||||
</Button>
|
||||
<Button size="sm" variant="ghost" onClick={cancelEdit}>
|
||||
{t("cancel")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
<div className="flex shrink-0 items-center gap-1">
|
||||
<button
|
||||
onClick={() => beginEdit(model)}
|
||||
className="p-1 hover:bg-sidebar rounded text-text-muted hover:text-primary"
|
||||
className="rounded p-1 text-text-muted hover:bg-sidebar hover:text-primary"
|
||||
title={t("edit")}
|
||||
>
|
||||
<span className="material-symbols-outlined text-sm">edit</span>
|
||||
</button>
|
||||
<ModelCompatPopover
|
||||
t={t}
|
||||
effectiveModelNormalize={(p) =>
|
||||
effectiveNormalizeForProtocol(model.id, p, customMap, overrideMap)
|
||||
}
|
||||
effectiveModelPreserveDeveloper={(p) =>
|
||||
effectivePreserveForProtocol(model.id, p, customMap, overrideMap)
|
||||
}
|
||||
getUpstreamHeadersRecord={(p) =>
|
||||
effectiveUpstreamHeadersForProtocol(model.id, p, customMap, overrideMap)
|
||||
}
|
||||
onCompatPatch={(protocol, payload) =>
|
||||
saveCustomCompat(model.id, {
|
||||
compatByProtocol: { [protocol]: payload },
|
||||
})
|
||||
}
|
||||
showDeveloperToggle
|
||||
disabled={savingModelId === model.id}
|
||||
/>
|
||||
<button
|
||||
onClick={() => handleRemove(model.id)}
|
||||
className="p-1 hover:bg-red-50 rounded text-red-500"
|
||||
className="rounded p-1 text-red-500 hover:bg-red-50"
|
||||
title={t("removeCustomModel")}
|
||||
>
|
||||
<span className="material-symbols-outlined text-sm">delete</span>
|
||||
@@ -2731,6 +3056,7 @@ function CompatibleModelsSection({
|
||||
t,
|
||||
effectiveModelNormalize,
|
||||
effectiveModelPreserveDeveloper,
|
||||
getUpstreamHeadersRecord,
|
||||
saveModelCompatFlags,
|
||||
compatSavingModelId,
|
||||
onModelsChanged,
|
||||
@@ -2944,6 +3270,7 @@ function CompatibleModelsSection({
|
||||
showDeveloperToggle={!isAnthropic}
|
||||
effectiveModelNormalize={effectiveModelNormalize}
|
||||
effectiveModelPreserveDeveloper={effectiveModelPreserveDeveloper}
|
||||
getUpstreamHeadersRecord={(p) => getUpstreamHeadersRecord(modelId, p)}
|
||||
saveModelCompatFlags={saveModelCompatFlags}
|
||||
compatDisabled={compatSavingModelId === modelId}
|
||||
/>
|
||||
@@ -2973,6 +3300,7 @@ CompatibleModelsSection.propTypes = {
|
||||
t: PropTypes.func.isRequired,
|
||||
effectiveModelNormalize: PropTypes.func.isRequired,
|
||||
effectiveModelPreserveDeveloper: PropTypes.func.isRequired,
|
||||
getUpstreamHeadersRecord: PropTypes.func.isRequired,
|
||||
saveModelCompatFlags: PropTypes.func.isRequired,
|
||||
compatSavingModelId: PropTypes.string,
|
||||
onModelsChanged: PropTypes.func,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
<textarea
|
||||
value={query}
|
||||
onChange={(e) => setQuery(e.target.value)}
|
||||
placeholder="Enter search query..."
|
||||
placeholder={t("queryPlaceholder")}
|
||||
className="w-full bg-surface border border-border rounded-lg p-2.5 text-sm text-text-main resize-none h-16 focus:outline-none focus:ring-2 focus:ring-primary/30"
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter" && !e.shiftKey) {
|
||||
@@ -114,7 +115,7 @@ export default function SearchForm({ onSearch, loading, onCancel, providers }: S
|
||||
value={provider}
|
||||
onChange={(e: any) => setProvider(e.target.value)}
|
||||
options={[
|
||||
{ value: "auto", label: "auto (cheapest)" },
|
||||
{ value: "auto", label: t("providerAuto") },
|
||||
...activeProviders.map((p) => ({
|
||||
value: p.id,
|
||||
label: p.name,
|
||||
@@ -131,8 +132,8 @@ export default function SearchForm({ onSearch, loading, onCancel, providers }: S
|
||||
value={searchType}
|
||||
onChange={(e: any) => setSearchType(e.target.value)}
|
||||
options={[
|
||||
{ value: "web", label: "web" },
|
||||
{ value: "news", label: "news" },
|
||||
{ value: "web", label: t("searchTypeWeb") },
|
||||
{ value: "news", label: t("searchTypeNews") },
|
||||
]}
|
||||
className="w-full"
|
||||
/>
|
||||
@@ -172,7 +173,7 @@ export default function SearchForm({ onSearch, loading, onCancel, providers }: S
|
||||
<input
|
||||
value={country}
|
||||
onChange={(e) => setCountry(e.target.value)}
|
||||
placeholder="any"
|
||||
placeholder={t("optionAny")}
|
||||
className="w-full bg-surface border border-border rounded-md px-2 py-1.5 text-xs text-text-main focus:outline-none"
|
||||
/>
|
||||
</div>
|
||||
@@ -181,7 +182,7 @@ export default function SearchForm({ onSearch, loading, onCancel, providers }: S
|
||||
<input
|
||||
value={language}
|
||||
onChange={(e) => setLanguage(e.target.value)}
|
||||
placeholder="any"
|
||||
placeholder={t("optionAny")}
|
||||
className="w-full bg-surface border border-border rounded-md px-2 py-1.5 text-xs text-text-main focus:outline-none"
|
||||
/>
|
||||
</div>
|
||||
@@ -192,11 +193,11 @@ export default function SearchForm({ onSearch, loading, onCancel, providers }: S
|
||||
value={timeRange}
|
||||
onChange={(e: any) => setTimeRange(e.target.value)}
|
||||
options={[
|
||||
{ value: "", label: "any" },
|
||||
{ value: "day", label: "Past day" },
|
||||
{ value: "week", label: "Past week" },
|
||||
{ value: "month", label: "Past month" },
|
||||
{ value: "year", label: "Past year" },
|
||||
{ value: "", label: t("optionAny") },
|
||||
{ value: "day", label: t("timeRangeDay") },
|
||||
{ value: "week", label: t("timeRangeWeek") },
|
||||
{ value: "month", label: t("timeRangeMonth") },
|
||||
{ value: "year", label: t("timeRangeYear") },
|
||||
]}
|
||||
className="w-full"
|
||||
/>
|
||||
@@ -209,7 +210,7 @@ export default function SearchForm({ onSearch, loading, onCancel, providers }: S
|
||||
<input
|
||||
value={domainInput}
|
||||
onChange={(e) => setDomainInput(e.target.value)}
|
||||
placeholder="example.com"
|
||||
placeholder={t("domainPlaceholder")}
|
||||
className="flex-1 bg-surface border border-border rounded-md px-2 py-1.5 text-xs text-text-main focus:outline-none"
|
||||
onKeyDown={(e) => e.key === "Enter" && addDomain("include")}
|
||||
/>
|
||||
@@ -244,7 +245,7 @@ export default function SearchForm({ onSearch, loading, onCancel, providers }: S
|
||||
<input
|
||||
value={excludeDomainInput}
|
||||
onChange={(e) => setExcludeDomainInput(e.target.value)}
|
||||
placeholder="example.com"
|
||||
placeholder={t("domainPlaceholder")}
|
||||
className="flex-1 bg-surface border border-border rounded-md px-2 py-1.5 text-xs text-text-main focus:outline-none"
|
||||
onKeyDown={(e) => e.key === "Enter" && addDomain("exclude")}
|
||||
/>
|
||||
@@ -274,9 +275,9 @@ export default function SearchForm({ onSearch, loading, onCancel, providers }: S
|
||||
value={safeSearch}
|
||||
onChange={(e: any) => setSafeSearch(e.target.value)}
|
||||
options={[
|
||||
{ value: "off", label: "Off" },
|
||||
{ value: "moderate", label: "Moderate" },
|
||||
{ value: "strict", label: "Strict" },
|
||||
{ value: "off", label: t("safeSearchOff") },
|
||||
{ value: "moderate", label: t("safeSearchModerate") },
|
||||
{ value: "strict", label: t("safeSearchStrict") },
|
||||
]}
|
||||
className="w-full"
|
||||
/>
|
||||
@@ -289,7 +290,7 @@ export default function SearchForm({ onSearch, loading, onCancel, providers }: S
|
||||
<div className="p-4 border-b border-border">
|
||||
{loading ? (
|
||||
<Button variant="danger" onClick={onCancel} className="w-full">
|
||||
Cancel
|
||||
{tc("cancel")}
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
@@ -298,7 +299,7 @@ export default function SearchForm({ onSearch, loading, onCancel, providers }: S
|
||||
disabled={noProviders || !query.trim()}
|
||||
className="w-full"
|
||||
>
|
||||
Search
|
||||
{tc("search")}
|
||||
</Button>
|
||||
)}
|
||||
{noProviders && <p className="text-xs text-text-muted mt-2">{t("noSearchProviders")}</p>}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect } from "react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useLocale, useTranslations } from "next-intl";
|
||||
|
||||
interface HistoryEntry {
|
||||
query: string;
|
||||
@@ -14,9 +14,9 @@ interface SearchHistoryProps {
|
||||
onReplay: (entry: HistoryEntry) => void;
|
||||
}
|
||||
|
||||
function timeAgo(timestamp: string): string {
|
||||
function timeAgo(timestamp: string, locale: string): string {
|
||||
try {
|
||||
const rtf = new Intl.RelativeTimeFormat("en", { numeric: "auto" });
|
||||
const rtf = new Intl.RelativeTimeFormat(locale, { numeric: "auto" });
|
||||
const diff = Date.now() - new Date(timestamp).getTime();
|
||||
const minutes = Math.floor(diff / 60_000);
|
||||
if (minutes < 1) return rtf.format(0, "minute");
|
||||
@@ -25,12 +25,13 @@ function timeAgo(timestamp: string): string {
|
||||
if (hours < 24) return rtf.format(-hours, "hour");
|
||||
return rtf.format(-Math.floor(hours / 24), "day");
|
||||
} catch {
|
||||
return new Date(timestamp).toLocaleString();
|
||||
return new Date(timestamp).toLocaleString(locale);
|
||||
}
|
||||
}
|
||||
|
||||
export default function SearchHistory({ onReplay }: SearchHistoryProps) {
|
||||
const t = useTranslations("search");
|
||||
const locale = useLocale();
|
||||
const [entries, setEntries] = useState<HistoryEntry[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -57,7 +58,7 @@ export default function SearchHistory({ onReplay }: SearchHistoryProps) {
|
||||
<div className="text-xs text-text-main truncate">{entry.query}</div>
|
||||
<div className="flex justify-between mt-0.5">
|
||||
<span className="text-[10px] text-text-muted">{entry.provider}</span>
|
||||
<span className="text-[10px] text-text-muted">{timeAgo(entry.timestamp)}</span>
|
||||
<span className="text-[10px] text-text-muted">{timeAgo(entry.timestamp, locale)}</span>
|
||||
</div>
|
||||
</button>
|
||||
))}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { Card, Button, Input, Toggle } from "@/shared/components";
|
||||
import { cn } from "@/shared/utils/cn";
|
||||
import { ROUTING_STRATEGIES } from "@/shared/constants/routingStrategies";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
export default function ComboDefaultsTab() {
|
||||
@@ -21,14 +22,11 @@ export default function ComboDefaultsTab() {
|
||||
const [saving, setSaving] = useState(false);
|
||||
const t = useTranslations("settings");
|
||||
const tc = useTranslations("common");
|
||||
const strategyOptions = [
|
||||
{ value: "priority", label: t("priority"), icon: "sort" },
|
||||
{ value: "weighted", label: t("weighted"), icon: "percent" },
|
||||
{ value: "round-robin", label: t("roundRobin"), icon: "autorenew" },
|
||||
{ value: "random", label: t("random"), icon: "shuffle" },
|
||||
{ value: "least-used", label: t("leastUsed"), icon: "low_priority" },
|
||||
{ value: "cost-optimized", label: t("costOpt"), icon: "savings" },
|
||||
];
|
||||
const strategyOptions = ROUTING_STRATEGIES.map((strategy) => ({
|
||||
value: strategy.value,
|
||||
label: t(strategy.labelKey),
|
||||
icon: strategy.icon,
|
||||
}));
|
||||
const numericSettings = [
|
||||
{ key: "maxRetries", label: t("maxRetriesLabel"), min: 0, max: 5 },
|
||||
{ key: "retryDelayMs", label: t("retryDelayLabel"), min: 500, max: 10000, step: 500 },
|
||||
@@ -87,6 +85,13 @@ export default function ComboDefaultsTab() {
|
||||
<h3 className="text-lg font-semibold">{t("comboDefaultsTitle")}</h3>
|
||||
<span className="text-xs text-text-muted ml-auto">{t("globalComboConfig")}</span>
|
||||
</div>
|
||||
<div className="mb-4 rounded-lg border border-amber-500/20 bg-amber-500/5 p-3">
|
||||
<p className="text-xs font-medium text-amber-700 dark:text-amber-300">
|
||||
{t("comboDefaultsGuideTitle")}
|
||||
</p>
|
||||
<p className="text-xs text-text-muted mt-1">{t("comboDefaultsGuideHint1")}</p>
|
||||
<p className="text-xs text-text-muted">{t("comboDefaultsGuideHint2")}</p>
|
||||
</div>
|
||||
<div className="flex flex-col gap-4">
|
||||
{/* Default Strategy */}
|
||||
<div className="flex items-center justify-between">
|
||||
@@ -97,7 +102,7 @@ export default function ComboDefaultsTab() {
|
||||
<div
|
||||
role="tablist"
|
||||
aria-label={t("comboStrategyAria")}
|
||||
className="grid grid-cols-3 gap-1 p-0.5 rounded-md bg-black/5 dark:bg-white/5"
|
||||
className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-5 gap-1 p-0.5 rounded-md bg-black/5 dark:bg-white/5"
|
||||
>
|
||||
{strategyOptions.map((s) => (
|
||||
<button
|
||||
|
||||
@@ -3,31 +3,20 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { Card, Input, Button } from "@/shared/components";
|
||||
import FallbackChainsEditor from "./FallbackChainsEditor";
|
||||
import {
|
||||
ROUTING_STRATEGIES,
|
||||
SETTINGS_FALLBACK_STRATEGY_VALUES,
|
||||
} from "@/shared/constants/routingStrategies";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
const STRATEGIES = [
|
||||
{
|
||||
value: "fill-first",
|
||||
labelKey: "fillFirst",
|
||||
descKey: "fillFirstDesc",
|
||||
icon: "vertical_align_top",
|
||||
},
|
||||
{ value: "round-robin", labelKey: "roundRobin", descKey: "roundRobinDesc", icon: "loop" },
|
||||
{ value: "p2c", labelKey: "p2c", descKey: "p2cDesc", icon: "balance" },
|
||||
{ value: "random", labelKey: "random", descKey: "randomDesc", icon: "shuffle" },
|
||||
{
|
||||
value: "least-used",
|
||||
labelKey: "leastUsed",
|
||||
descKey: "leastUsedDesc",
|
||||
icon: "low_priority",
|
||||
},
|
||||
{
|
||||
value: "cost-optimized",
|
||||
labelKey: "costOpt",
|
||||
descKey: "costOptDesc",
|
||||
icon: "savings",
|
||||
},
|
||||
];
|
||||
const STRATEGIES = ROUTING_STRATEGIES.filter((strategy) =>
|
||||
SETTINGS_FALLBACK_STRATEGY_VALUES.includes(strategy.value)
|
||||
).map((strategy) => ({
|
||||
value: strategy.value,
|
||||
labelKey: strategy.labelKey,
|
||||
descKey: strategy.settingsDescKey,
|
||||
icon: strategy.icon,
|
||||
}));
|
||||
|
||||
export default function RoutingTab() {
|
||||
const [settings, setSettings] = useState<any>({ fallbackStrategy: "fill-first" });
|
||||
@@ -36,14 +25,10 @@ export default function RoutingTab() {
|
||||
const [newPattern, setNewPattern] = useState("");
|
||||
const [newTarget, setNewTarget] = useState("");
|
||||
const t = useTranslations("settings");
|
||||
const strategyHintKeyByValue: Record<string, string> = {
|
||||
"fill-first": "fillFirstDesc",
|
||||
"round-robin": "roundRobinDesc",
|
||||
p2c: "p2cDesc",
|
||||
random: "randomDesc",
|
||||
"least-used": "leastUsedDesc",
|
||||
"cost-optimized": "costOptDesc",
|
||||
};
|
||||
const strategyHintKeyByValue = STRATEGIES.reduce<Record<string, string>>((acc, strategy) => {
|
||||
acc[strategy.value] = strategy.descKey;
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
useEffect(() => {
|
||||
fetch("/api/settings")
|
||||
@@ -99,6 +84,14 @@ export default function RoutingTab() {
|
||||
<h3 className="text-lg font-semibold">{t("routingStrategy")}</h3>
|
||||
</div>
|
||||
|
||||
<div className="mb-4 rounded-lg border border-blue-500/20 bg-blue-500/5 p-3">
|
||||
<p className="text-xs font-medium text-blue-700 dark:text-blue-300">
|
||||
{t("routingAdvancedGuideTitle")}
|
||||
</p>
|
||||
<p className="text-xs text-text-muted mt-1">{t("routingAdvancedGuideHint1")}</p>
|
||||
<p className="text-xs text-text-muted">{t("routingAdvancedGuideHint2")}</p>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className="grid grid-cols-1 sm:grid-cols-2 xl:grid-cols-3 gap-2 mb-4"
|
||||
style={{ gridAutoRows: "1fr" }}
|
||||
|
||||
@@ -37,7 +37,7 @@ export default function SettingsPage() {
|
||||
const activeTab = userSelectedTab || tabs.find((t) => t.id === tabParam)?.id || "general";
|
||||
|
||||
return (
|
||||
<div className="max-w-2xl mx-auto min-w-0">
|
||||
<div className="max-w-6xl mx-auto min-w-0">
|
||||
<div className="flex flex-col gap-6">
|
||||
{/* Tab navigation */}
|
||||
<div className="w-full overflow-x-auto pb-1">
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { getSettings } from "@/lib/localDb";
|
||||
import { getProviderConnections, getSettings } from "@/lib/localDb";
|
||||
import { APP_CONFIG } from "@/shared/constants/config";
|
||||
import { AI_PROVIDERS } from "@/shared/constants/providers";
|
||||
|
||||
/**
|
||||
* GET /api/monitoring/health — System health overview
|
||||
@@ -16,6 +17,7 @@ export async function GET() {
|
||||
const { getInflightCount } = await import("@omniroute/open-sse/services/requestDedup.ts");
|
||||
|
||||
const settings = await getSettings();
|
||||
const connections = await getProviderConnections();
|
||||
const circuitBreakers = getAllCircuitBreakerStatuses();
|
||||
const rateLimitStatus = getAllRateLimitStatus();
|
||||
const lockouts = getAllModelLockouts();
|
||||
@@ -43,11 +45,22 @@ export async function GET() {
|
||||
};
|
||||
}
|
||||
|
||||
const configuredProviders = new Set(connections.map((c: any) => c.provider));
|
||||
const activeProviders = new Set(
|
||||
connections.filter((c: any) => c.isActive !== false).map((c: any) => c.provider)
|
||||
);
|
||||
|
||||
return NextResponse.json({
|
||||
status: "healthy",
|
||||
timestamp: new Date().toISOString(),
|
||||
system,
|
||||
providerHealth,
|
||||
providerSummary: {
|
||||
catalogCount: Object.keys(AI_PROVIDERS).length,
|
||||
configuredCount: configuredProviders.size,
|
||||
activeCount: activeProviders.size,
|
||||
monitoredCount: Object.keys(providerHealth).length,
|
||||
},
|
||||
localProviders: getAllHealthStatuses(),
|
||||
rateLimitStatus,
|
||||
lockouts,
|
||||
|
||||
@@ -130,6 +130,7 @@ export async function PUT(request) {
|
||||
supportedEndpoints,
|
||||
normalizeToolCallId,
|
||||
preserveOpenAIDeveloperRole,
|
||||
upstreamHeaders,
|
||||
compatByProtocol,
|
||||
} = validation.data;
|
||||
|
||||
@@ -141,6 +142,7 @@ export async function PUT(request) {
|
||||
if ("normalizeToolCallId" in raw) updates.normalizeToolCallId = normalizeToolCallId;
|
||||
if ("preserveOpenAIDeveloperRole" in raw)
|
||||
updates.preserveOpenAIDeveloperRole = preserveOpenAIDeveloperRole;
|
||||
if ("upstreamHeaders" in raw) updates.upstreamHeaders = upstreamHeaders;
|
||||
if ("compatByProtocol" in raw && compatByProtocol !== undefined) {
|
||||
updates.compatByProtocol = compatByProtocol;
|
||||
}
|
||||
@@ -157,11 +159,13 @@ export async function PUT(request) {
|
||||
"modelId",
|
||||
"normalizeToolCallId",
|
||||
"preserveOpenAIDeveloperRole",
|
||||
"upstreamHeaders",
|
||||
"compatByProtocol",
|
||||
].includes(k)
|
||||
) &&
|
||||
("normalizeToolCallId" in raw ||
|
||||
"preserveOpenAIDeveloperRole" in raw ||
|
||||
"upstreamHeaders" in raw ||
|
||||
"compatByProtocol" in raw);
|
||||
if (compatOnly) {
|
||||
const knownProvider =
|
||||
@@ -191,6 +195,12 @@ export async function PUT(request) {
|
||||
if ("compatByProtocol" in raw && compatByProtocol && typeof compatByProtocol === "object") {
|
||||
patch.compatByProtocol = compatByProtocol;
|
||||
}
|
||||
if ("upstreamHeaders" in raw) {
|
||||
patch.upstreamHeaders =
|
||||
upstreamHeaders === null || typeof upstreamHeaders === "object"
|
||||
? upstreamHeaders
|
||||
: undefined;
|
||||
}
|
||||
if (Object.keys(patch).length > 0) {
|
||||
mergeModelCompatOverride(provider, modelId, patch);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { z } from "zod";
|
||||
import { isValidationFailure, validateBody } from "@/shared/validation/helpers";
|
||||
import {
|
||||
getProviderConnectionById,
|
||||
updateProviderConnection,
|
||||
@@ -92,6 +94,11 @@ const CLI_RUNTIME_PROVIDER_MAP = {
|
||||
kilocode: "kilo",
|
||||
};
|
||||
|
||||
/** POST body is optional; when present, only known fields are validated. */
|
||||
const providerConnectionTestBodySchema = z.object({
|
||||
validationModelId: z.string().max(500).optional(),
|
||||
});
|
||||
|
||||
function toSafeMessage(value: any, fallback = "Unknown error"): string {
|
||||
if (typeof value !== "string") return fallback;
|
||||
const trimmed = value.trim();
|
||||
@@ -683,14 +690,17 @@ export async function POST(request: Request, { params }: { params: Promise<{ id:
|
||||
try {
|
||||
const { id } = await params;
|
||||
|
||||
// Parse optional body for validationModelId
|
||||
let validationModelId;
|
||||
let rawBody: unknown = {};
|
||||
try {
|
||||
const body = await request.json();
|
||||
validationModelId = body?.validationModelId;
|
||||
rawBody = await request.json();
|
||||
} catch {
|
||||
// Body is optional
|
||||
// Empty or non-JSON body — treat as {}
|
||||
}
|
||||
const validation = validateBody(providerConnectionTestBodySchema, rawBody);
|
||||
if (isValidationFailure(validation)) {
|
||||
return NextResponse.json({ error: validation.error }, { status: 400 });
|
||||
}
|
||||
const { validationModelId } = validation.data;
|
||||
|
||||
const data = await testSingleConnection(id, validationModelId);
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { z } from "zod";
|
||||
import { isAuthenticated } from "@/shared/utils/apiAuth";
|
||||
import { isValidationFailure, validateBody } from "@/shared/validation/helpers";
|
||||
import { setAccountKeyLimit, getAccountKeyLimit } from "@/lib/db/registeredKeys";
|
||||
|
||||
const limitsSchema = z.object({
|
||||
@@ -32,20 +33,20 @@ export async function PUT(request: Request, { params }: { params: Promise<{ id:
|
||||
return NextResponse.json({ error: { message: "Authentication required" } }, { status: 401 });
|
||||
}
|
||||
|
||||
let body: unknown;
|
||||
let rawBody: unknown;
|
||||
try {
|
||||
body = await request.json();
|
||||
rawBody = await request.json();
|
||||
} catch {
|
||||
return NextResponse.json({ error: "Invalid JSON body" }, { status: 400 });
|
||||
}
|
||||
|
||||
const parsed = limitsSchema.safeParse(body);
|
||||
if (!parsed.success) {
|
||||
return NextResponse.json({ error: parsed.error.flatten() }, { status: 400 });
|
||||
const validation = validateBody(limitsSchema, rawBody);
|
||||
if (isValidationFailure(validation)) {
|
||||
return NextResponse.json({ error: validation.error }, { status: 400 });
|
||||
}
|
||||
|
||||
const resolvedParams = await params;
|
||||
setAccountKeyLimit(resolvedParams.id, parsed.data);
|
||||
setAccountKeyLimit(resolvedParams.id, validation.data);
|
||||
const updated = getAccountKeyLimit(resolvedParams.id);
|
||||
return NextResponse.json({ accountId: resolvedParams.id, limits: updated });
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { z } from "zod";
|
||||
import { isAuthenticated } from "@/shared/utils/apiAuth";
|
||||
import { isValidationFailure, validateBody } from "@/shared/validation/helpers";
|
||||
|
||||
const reportSchema = z.object({
|
||||
title: z.string().min(1).max(300),
|
||||
@@ -26,19 +27,19 @@ export async function POST(request: Request) {
|
||||
return NextResponse.json({ error: { message: "Authentication required" } }, { status: 401 });
|
||||
}
|
||||
|
||||
let body: unknown;
|
||||
let rawBody: unknown;
|
||||
try {
|
||||
body = await request.json();
|
||||
rawBody = await request.json();
|
||||
} catch {
|
||||
return NextResponse.json({ error: "Invalid JSON body" }, { status: 400 });
|
||||
}
|
||||
|
||||
const parsed = reportSchema.safeParse(body);
|
||||
if (!parsed.success) {
|
||||
return NextResponse.json({ error: parsed.error.flatten() }, { status: 400 });
|
||||
const validation = validateBody(reportSchema, rawBody);
|
||||
if (isValidationFailure(validation)) {
|
||||
return NextResponse.json({ error: validation.error }, { status: 400 });
|
||||
}
|
||||
|
||||
const { title, provider, accountId, requestId, errorCode, details, labels = [] } = parsed.data;
|
||||
const { title, provider, accountId, requestId, errorCode, details, labels = [] } = validation.data;
|
||||
|
||||
const repo = process.env.GITHUB_ISSUES_REPO;
|
||||
const token = process.env.GITHUB_ISSUES_TOKEN;
|
||||
|
||||
@@ -16,6 +16,7 @@ import { getAllAudioModels } from "@omniroute/open-sse/config/audioRegistry.ts";
|
||||
import { getAllModerationModels } from "@omniroute/open-sse/config/moderationRegistry.ts";
|
||||
import { getAllVideoModels, getVideoProvider } from "@omniroute/open-sse/config/videoRegistry.ts";
|
||||
import { getAllMusicModels, getMusicProvider } from "@omniroute/open-sse/config/musicRegistry.ts";
|
||||
import { REGISTRY } from "@omniroute/open-sse/config/providerRegistry.ts";
|
||||
|
||||
const FALLBACK_ALIAS_TO_PROVIDER = {
|
||||
ag: "antigravity",
|
||||
@@ -190,6 +191,7 @@ export async function getUnifiedModelsResponse(
|
||||
permission: [],
|
||||
root: combo.name,
|
||||
parent: null,
|
||||
...(combo.context_length ? { context_length: combo.context_length } : {}),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -206,8 +208,15 @@ export async function getUnifiedModelsResponse(
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get default context length from registry (provider-level default)
|
||||
const registryEntry = REGISTRY[alias] || REGISTRY[canonicalProviderId];
|
||||
const defaultContextLength = registryEntry?.defaultContextLength;
|
||||
|
||||
for (const model of providerModels) {
|
||||
const aliasId = `${alias}/${model.id}`;
|
||||
// Model-level context length overrides provider default
|
||||
const contextLength = model.contextLength || defaultContextLength;
|
||||
|
||||
models.push({
|
||||
id: aliasId,
|
||||
object: "model",
|
||||
@@ -216,6 +225,7 @@ export async function getUnifiedModelsResponse(
|
||||
permission: [],
|
||||
root: model.id,
|
||||
parent: null,
|
||||
...(contextLength ? { context_length: contextLength } : {}),
|
||||
});
|
||||
|
||||
// Add provider-id prefix in addition to short alias (ex: kiro/model + kr/model).
|
||||
@@ -229,6 +239,7 @@ export async function getUnifiedModelsResponse(
|
||||
permission: [],
|
||||
root: model.id,
|
||||
parent: aliasId,
|
||||
...(contextLength ? { context_length: contextLength } : {}),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { z } from "zod";
|
||||
import { isAuthenticated } from "@/shared/utils/apiAuth";
|
||||
import { isValidationFailure, validateBody } from "@/shared/validation/helpers";
|
||||
import { setProviderKeyLimit, getProviderKeyLimit } from "@/lib/db/registeredKeys";
|
||||
|
||||
const limitsSchema = z.object({
|
||||
@@ -32,20 +33,20 @@ export async function PUT(request: Request, { params }: { params: Promise<{ prov
|
||||
return NextResponse.json({ error: { message: "Authentication required" } }, { status: 401 });
|
||||
}
|
||||
|
||||
let body: unknown;
|
||||
let rawBody: unknown;
|
||||
try {
|
||||
body = await request.json();
|
||||
rawBody = await request.json();
|
||||
} catch {
|
||||
return NextResponse.json({ error: "Invalid JSON body" }, { status: 400 });
|
||||
}
|
||||
|
||||
const parsed = limitsSchema.safeParse(body);
|
||||
if (!parsed.success) {
|
||||
return NextResponse.json({ error: parsed.error.flatten() }, { status: 400 });
|
||||
const validation = validateBody(limitsSchema, rawBody);
|
||||
if (isValidationFailure(validation)) {
|
||||
return NextResponse.json({ error: validation.error }, { status: 400 });
|
||||
}
|
||||
|
||||
const { provider } = await params;
|
||||
setProviderKeyLimit(provider, parsed.data);
|
||||
setProviderKeyLimit(provider, validation.data);
|
||||
const updated = getProviderKeyLimit(provider);
|
||||
return NextResponse.json({ provider, limits: updated });
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { z } from "zod";
|
||||
import { isAuthenticated } from "@/shared/utils/apiAuth";
|
||||
import { isValidationFailure, validateBody } from "@/shared/validation/helpers";
|
||||
import { issueRegisteredKey, checkQuota, listRegisteredKeys } from "@/lib/db/registeredKeys";
|
||||
|
||||
// ─── Validation ───────────────────────────────────────────────────────────────
|
||||
@@ -53,19 +54,19 @@ export async function POST(request: Request) {
|
||||
return NextResponse.json({ error: { message: "Authentication required" } }, { status: 401 });
|
||||
}
|
||||
|
||||
let body: unknown;
|
||||
let rawBody: unknown;
|
||||
try {
|
||||
body = await request.json();
|
||||
rawBody = await request.json();
|
||||
} catch {
|
||||
return NextResponse.json({ error: "Invalid JSON body" }, { status: 400 });
|
||||
}
|
||||
|
||||
const parsed = issueKeySchema.safeParse(body);
|
||||
if (!parsed.success) {
|
||||
return NextResponse.json({ error: parsed.error.flatten() }, { status: 400 });
|
||||
const validation = validateBody(issueKeySchema, rawBody);
|
||||
if (isValidationFailure(validation)) {
|
||||
return NextResponse.json({ error: validation.error }, { status: 400 });
|
||||
}
|
||||
|
||||
const { provider, accountId } = parsed.data;
|
||||
const { provider, accountId } = validation.data;
|
||||
|
||||
// ── Quota check ──
|
||||
try {
|
||||
@@ -83,7 +84,7 @@ export async function POST(request: Request) {
|
||||
|
||||
// ── Issue ──
|
||||
try {
|
||||
const result = issueRegisteredKey(parsed.data);
|
||||
const result = issueRegisteredKey(validation.data);
|
||||
|
||||
if ("idempotencyConflict" in result) {
|
||||
return NextResponse.json(
|
||||
|
||||
@@ -56,7 +56,9 @@
|
||||
"warning": "تحذير",
|
||||
"note": "ملاحظة",
|
||||
"free": "مجاني",
|
||||
"skipToContent": "انتقل إلى المحتوى"
|
||||
"skipToContent": "انتقل إلى المحتوى",
|
||||
"maintenanceServerIssues": "Server is experiencing issues. Some features may be unavailable.",
|
||||
"maintenanceServerUnreachable": "Server is unreachable. Reconnecting..."
|
||||
},
|
||||
"sidebar": {
|
||||
"home": "الصفحة الرئيسية",
|
||||
@@ -103,7 +105,9 @@
|
||||
"endpoints": "نقاط النهاية",
|
||||
"playground": "ملعب النماذج",
|
||||
"agents": "وكلاء",
|
||||
"cliToolsShort": "أدوات"
|
||||
"cliToolsShort": "أدوات",
|
||||
"autoCombo": "Auto Combo",
|
||||
"searchTools": "Search Tools"
|
||||
},
|
||||
"themesPage": {
|
||||
"title": "المواضيع",
|
||||
@@ -268,7 +272,28 @@
|
||||
"permissionsTitle": "الأذونات: {name}",
|
||||
"allowAllDesc": "يمكن لهذا المفتاح الوصول إلى كافة الموديلات المتاحة.",
|
||||
"restrictDesc": "يمكن لهذا المفتاح الوصول إلى {selectedCount} من طرازات {totalModels}.",
|
||||
"selectedCount": "تم تحديد {count}"
|
||||
"selectedCount": "تم تحديد {count}",
|
||||
"autoResolve": "Auto-Resolve",
|
||||
"autoResolveDesc": "Auto-resolve ambiguous model names to native provider for this API key.",
|
||||
"keyActive": "Key Active",
|
||||
"keyActiveDesc": "Enable or disable this API key. Disabled keys are immediately rejected with 403.",
|
||||
"accessSchedule": "Access Schedule",
|
||||
"accessScheduleDesc": "Restrict access to specific hours and days of the week.",
|
||||
"scheduleFrom": "From",
|
||||
"scheduleUntil": "Until",
|
||||
"scheduleDays": "Days",
|
||||
"scheduleTimezone": "Timezone",
|
||||
"scheduleTimezoneHint": "Use IANA timezone names, e.g. America/New_York, Europe/Berlin",
|
||||
"scheduleActive": "Schedule",
|
||||
"disabled": "Disabled",
|
||||
"daySun": "Sun",
|
||||
"dayMon": "Mon",
|
||||
"dayTue": "Tue",
|
||||
"dayWed": "Wed",
|
||||
"dayThu": "Thu",
|
||||
"dayFri": "Fri",
|
||||
"daySat": "Sat",
|
||||
"keyOnlyAvailableAtCreation": "Full key available only at creation time — copy it when you first create the key"
|
||||
},
|
||||
"auditLog": {
|
||||
"title": "سجل التدقيق",
|
||||
@@ -548,6 +573,27 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"mitmHowWorksDesc": "{toolName} sends requests to its provider endpoint. MITM intercepts and redirects them to OmniRoute.",
|
||||
"mitmStep1": "1. Start MITM to route requests through OmniRoute.",
|
||||
"mitmStep2Prefix": "2. Add",
|
||||
"mitmStep2Suffix": "to your hosts file as 127.0.0.1.",
|
||||
"mitmStep3": "3. Open {toolName} and requests will be proxied.",
|
||||
"whenToUseLabel": "متى تستخدم",
|
||||
"openToolDocs": "افتح مستندات الأداة",
|
||||
"toolUseCases": {
|
||||
"claude": "استخدمه عندما تريد سير عمل تخطيط قوي وعمليات إعادة بناء طويلة متعددة الملفات باستخدام Claude Code.",
|
||||
"codex": "يُستخدم عندما يتم توحيد فريقك في تدفقات OpenAI Codex CLI والمصادقة المستندة إلى الملف الشخصي.",
|
||||
"droid": "استخدمه عندما تحتاج إلى وكيل طرفي خفيف الوزن يركز على الترميز السريع وحلقات تنفيذ الأوامر.",
|
||||
"openclaw": "استخدمه عندما تريد وكيل ترميز نمط Open Claw ولكن يتم توجيهه من خلال سياسات OmniRoute.",
|
||||
"cline": "يُستخدم عندما تقوم بتكوين وكلاء الترميز داخل المحررين وتريد إعدادًا موجهًا باستخدام نماذج OmniRoute.",
|
||||
"kilo": "يُستخدم عندما يعتمد سير عملك على أوامر Kilo Code والتحريرات المتكررة السريعة.",
|
||||
"cursor": "استخدمه عند البرمجة في Cursor وتحتاج إلى نماذج مخصصة متوافقة مع OpenAI من خلال OmniRoute.",
|
||||
"continue": "استخدمه عند تشغيل متابعة في IDEs وتحتاج إلى تكوين موفر محمول يستند إلى JSON.",
|
||||
"opencode": "استخدمه عندما تفضل تشغيل الوكيل الأصلي للمحطة والأتمتة النصية عبر OpenCode.",
|
||||
"kiro": "يُستخدم عند دمج Kiro والتحكم في توجيه النموذج مركزيًا من OmniRoute.",
|
||||
"antigravity": "يُستخدم عندما يجب اعتراض حركة مرور Antigravity/Kiro عبر MITM وتوجيهها إلى OmniRoute.",
|
||||
"copilot": "استخدمه عندما تريد UX بأسلوب دردشة Copilot أثناء فرض مفاتيح OmniRoute وقواعد التوجيه."
|
||||
}
|
||||
},
|
||||
"combos": {
|
||||
@@ -864,7 +910,12 @@
|
||||
"a2aQuickStartStep2": "إرسال طلبات JSON - RPC إلى@@ PH0 @@ باستخدام @@PH1 @@ أو `message/stream`.",
|
||||
"a2aQuickStartStep3": "تتبع المهام والتحكم فيها باستخدام `tasks/get` و `tasks/cancel`.",
|
||||
"completionsLegacy": "Completions (Legacy)",
|
||||
"completionsLegacyDesc": "Legacy OpenAI text completions — accepts both prompt string and messages array format"
|
||||
"completionsLegacyDesc": "Legacy OpenAI text completions — accepts both prompt string and messages array format",
|
||||
"categorySearch": "Search & Discovery",
|
||||
"webSearch": "Web Search",
|
||||
"webSearchDesc": "Unified web search across multiple providers with automatic failover and caching",
|
||||
"searchProvider": "Search Provider",
|
||||
"searchProviderDesc": "This provider is used for web search via POST /v1/search. No model configuration needed — search providers are ready to use once an API key is connected."
|
||||
},
|
||||
"mcpDashboard": {
|
||||
"loading": "جارٍ تحميل لوحة تحكم MCP...",
|
||||
@@ -1054,7 +1105,13 @@
|
||||
"resetAllTitle": "إعادة ضبط كافة قواطع الدائرة إلى الحالة الصحية",
|
||||
"resetting": "إعادة الضبط...",
|
||||
"resetAll": "إعادة ضبط الكل",
|
||||
"until": "حتى {time}"
|
||||
"until": "حتى {time}",
|
||||
"activeProviders": "{count} active",
|
||||
"monitoredProviders": "{count} monitored",
|
||||
"configuredProvidersLabel": "تم تكوينه في لوحة القيادة",
|
||||
"configuredProvidersHint": "الموفرون الذين لديهم بيانات اعتماد محفوظة في /dashboard/providers، بغض النظر عن حالة وقت التشغيل.",
|
||||
"activeProvidersHint": "الموفرون الذين تم تكوينهم ممكّنون حاليًا لطلبات التوجيه.",
|
||||
"monitoredProvidersHint": "يتم تتبع مقدمي الخدمة حاليًا بواسطة مراقبي صحة قاطع الدائرة."
|
||||
},
|
||||
"limits": {
|
||||
"title": "الحدود والحصص",
|
||||
@@ -1396,7 +1453,31 @@
|
||||
"chatPathHint": "Custom chat path for providers with non-standard APIs (e.g. /v4/chat/completions)",
|
||||
"modelsPathLabel": "Models Endpoint Path",
|
||||
"modelsPathPlaceholder": "/models",
|
||||
"modelsPathHint": "Custom models path for validation (e.g. /v4/models)"
|
||||
"modelsPathHint": "Custom models path for validation (e.g. /v4/models)",
|
||||
"builtInModels": "Built-in models",
|
||||
"builtInModelsHint": "Registry models for this provider. Use the pencil to set compatibility options.",
|
||||
"normalizeToolCallIdLabel": "Normalize tool call IDs to 9 characters (e.g. Mistral)",
|
||||
"preserveDeveloperRoleLabel": "Keep OpenAI Responses developer role (do not map to system)",
|
||||
"compatAdjustmentsTitle": "Compatibility",
|
||||
"compatButtonLabel": "Compatibility",
|
||||
"compatToolIdShort": "Tool ID 9",
|
||||
"compatDeveloperShort": "Developer role",
|
||||
"compatDoNotPreserveDeveloper": "Do not preserve developer role",
|
||||
"compatBadgeNoPreserve": "No preserve",
|
||||
"compatProtocolLabel": "Client request protocol",
|
||||
"compatProtocolHint": "These options apply when OmniRoute detects this request shape (OpenAI Chat, Responses API, or Anthropic Messages).",
|
||||
"compatProtocolOpenAI": "OpenAI Chat Completions",
|
||||
"compatProtocolOpenAIResponses": "OpenAI Responses API",
|
||||
"compatProtocolClaude": "Anthropic Messages",
|
||||
"tokenRefreshed": "Token refreshed successfully",
|
||||
"tokenRefreshFailed": "Token refresh failed",
|
||||
"compatBadgeUpstreamHeaders": "Headers",
|
||||
"compatUpstreamAddRow": "Add header",
|
||||
"compatUpstreamHeaderName": "Header name",
|
||||
"compatUpstreamHeaderValue": "Value",
|
||||
"compatUpstreamHeadersHint": "High-privilege setting — same trust level as editing provider API credentials; only trusted admins should use it. Merged after OmniRoute adds auth from the provider API key. If a custom header uses the same name as an existing one (e.g. Authorization), your value fully replaces the auto-generated header (including the Bearer token) — the upstream only sees what you typed, not the key from settings. Misconfiguration can cause 401 or broken upstream auth. One row per header (e.g. extra Authentication for some gateways). Hover or focus the value to preview. Saves on blur, outside click, or closing this panel.",
|
||||
"compatUpstreamHeadersLabel": "Extra upstream headers",
|
||||
"compatUpstreamRemoveRow": "Remove row"
|
||||
},
|
||||
"settings": {
|
||||
"title": "الإعدادات",
|
||||
@@ -1799,7 +1880,13 @@
|
||||
"cliFingerprintDesc": "Match native CLI binary signatures when proxying requests. Reorders headers and body fields to look identical to the official CLI tools. Your proxy IP is preserved.",
|
||||
"cliFingerprintEnabled": "{count} provider(s) with CLI fingerprint active",
|
||||
"enableFingerprintTitle": "Enable fingerprint for {provider}",
|
||||
"disableFingerprintTitle": "Disable fingerprint for {provider}"
|
||||
"disableFingerprintTitle": "Disable fingerprint for {provider}",
|
||||
"routingAdvancedGuideTitle": "توجيه التوجيه المتقدم",
|
||||
"routingAdvancedGuideHint1": "استخدمFill First للحصول على الأولوية التي يمكن التنبؤ بها، وRound Robin للعدالة، وP2C لمرونة زمن الاستجابة.",
|
||||
"routingAdvancedGuideHint2": "إذا اختلف مقدمو الخدمة من حيث الجودة/التكلفة، فابدأ بـ Cost Opt للعمل في الخلفية والأقل استخدامًا للارتداء المتوازن.",
|
||||
"comboDefaultsGuideTitle": "كيفية ضبط إعدادات التحرير والسرد الافتراضية",
|
||||
"comboDefaultsGuideHint1": "اجعل عمليات إعادة المحاولة منخفضة في التدفقات ذات زمن الوصول المنخفض؛ زيادة المهلة فقط لمهام الجيل الطويل.",
|
||||
"comboDefaultsGuideHint2": "استخدم تجاوزات الموفر عندما يحتاج أحد الموفرين إلى سلوك مهلة/إعادة محاولة مختلف عن الإعدادات الافتراضية العامة."
|
||||
},
|
||||
"translator": {
|
||||
"title": "مترجم",
|
||||
@@ -2216,7 +2303,8 @@
|
||||
"orRemovePasswordHashField": "أو قم بإزالة حقل كلمة المرور",
|
||||
"restartServerWithNewPassword": "أعد تشغيل الخادم وسيتم استخدام كلمة المرور الجديدة",
|
||||
"backToLogin": "العودة إلى تسجيل الدخول",
|
||||
"forgotPassword": "هل نسيت كلمة المرور؟"
|
||||
"forgotPassword": "هل نسيت كلمة المرور؟",
|
||||
"defaultPasswordHint": "Default password: 123456 (unless INITIAL_PASSWORD was set)"
|
||||
},
|
||||
"landing": {
|
||||
"brandName": "OmniRoute",
|
||||
@@ -2414,7 +2502,16 @@
|
||||
"troubleshootingCodexFamily": "بالنسبة لنماذج عائلة GitHub Codex، احتفظ بالنموذج كـ gh/codex-model; يقوم جهاز التوجيه بتحديد/الاستجابات تلقائيًا.",
|
||||
"troubleshootingTestConnection": "استخدم لوحة المعلومات > الموفرون > اختبار الاتصال قبل الاختبار من بيئات تطوير متكاملة أو عملاء خارجيين.",
|
||||
"troubleshootingCircuitBreaker": "إذا أظهر مقدم الخدمة أن قاطع الدائرة مفتوح، فانتظر حتى فترة التهدئة أو راجع صفحة الصحة للحصول على التفاصيل.",
|
||||
"troubleshootingOAuth": "بالنسبة لموفري OAuth، قم بإعادة المصادقة إذا انتهت صلاحية الرموز المميزة. تحقق من مؤشر حالة بطاقة المزود."
|
||||
"troubleshootingOAuth": "بالنسبة لموفري OAuth، قم بإعادة المصادقة إذا انتهت صلاحية الرموز المميزة. تحقق من مؤشر حالة بطاقة المزود.",
|
||||
"managementApiReference": "Management API Reference",
|
||||
"managementApiDescription": "Automation endpoints for proxy registry, scope assignments, and legacy proxy migration.",
|
||||
"mgmtProxiesListNote": "List saved proxy registry items (supports pagination).",
|
||||
"mgmtProxiesCreateNote": "Create a reusable proxy item in the registry.",
|
||||
"mgmtProxiesHealthNote": "Get 24h/rolling health metrics per saved proxy from proxy logs.",
|
||||
"mgmtProxiesBulkAssignNote": "Assign or clear one proxy across many scope IDs in one request.",
|
||||
"mgmtAssignmentsListNote": "List proxy assignments by scope, scope_id, or proxy_id.",
|
||||
"mgmtAssignmentsUpdateNote": "Assign or clear proxy for global/provider/account/combo scope.",
|
||||
"mgmtLegacyMigrationNote": "Import legacy proxyConfig maps into registry assignments."
|
||||
},
|
||||
"legal": {
|
||||
"privacyPolicy": "سياسة الخصوصية",
|
||||
@@ -2503,7 +2600,21 @@
|
||||
"binaryName": "Binary Name",
|
||||
"versionCommand": "Version Command",
|
||||
"spawnArgs": "Spawn Args",
|
||||
"addAgent": "Add Agent"
|
||||
"addAgent": "Add Agent",
|
||||
"scanning": "Scanning system for CLI agents...",
|
||||
"opencodeIntegration": "OpenCode Integration",
|
||||
"opencodeDetected": "opencode {version} detected",
|
||||
"opencodeDesc": "Generate a ready-to-use {configFile} with your OmniRoute base URL and all available models — drop it in your project root and run {command}.",
|
||||
"downloadConfig": "Download {file}",
|
||||
"downloaded": "Downloaded!",
|
||||
"setupGuideTitle": "Setup guide",
|
||||
"openCliTools": "Open CLI Tools",
|
||||
"setupGuideDetectCliTitle": "Detect installed CLIs",
|
||||
"setupGuideDetectCliDesc": "Click Refresh after installing or updating a CLI so OmniRoute can rescan binaries and versions.",
|
||||
"setupGuideCustomAgentTitle": "Register custom binary",
|
||||
"setupGuideCustomAgentDesc": "Use Add Custom Agent when your CLI is not in the built-in list. Provide binary name and version command.",
|
||||
"setupGuideCommandMissingTitle": "Fix 'command not found'",
|
||||
"setupGuideCommandMissingDesc": "Ensure the CLI command exists in PATH, open a new terminal session, and rerun Refresh."
|
||||
},
|
||||
"autoCombo": {
|
||||
"title": "Auto-Combo Engine",
|
||||
@@ -2527,5 +2638,56 @@
|
||||
"modePackCostSaver": "Cost Saver",
|
||||
"modePackQualityFirst": "Quality First",
|
||||
"modePackOfflineFriendly": "Offline Friendly"
|
||||
},
|
||||
"search": {
|
||||
"searchQuery": "Search Query",
|
||||
"searchResults": "Search Results",
|
||||
"cachedResult": "Cached",
|
||||
"searchCost": "Cost",
|
||||
"searchTools": "Search Tools",
|
||||
"searchToolsDesc": "Advanced search testing with provider comparison",
|
||||
"compareProviders": "Compare Providers",
|
||||
"rerankResults": "Rerank Results",
|
||||
"searchHistory": "Search History",
|
||||
"urlOverlap": "URL Overlap",
|
||||
"noSearchProviders": "No search providers configured. Add providers in Settings.",
|
||||
"noRerankModels": "No rerank model available",
|
||||
"webSearch": "Web Search",
|
||||
"provider": "Provider",
|
||||
"searchType": "Search Type",
|
||||
"maxResults": "Max Results",
|
||||
"filters": "Filters",
|
||||
"country": "Country",
|
||||
"language": "Language",
|
||||
"timeRange": "Time Range",
|
||||
"includeDomains": "Include Domains",
|
||||
"excludeDomains": "Exclude Domains",
|
||||
"safeSearch": "Safe Search",
|
||||
"formatted": "Formatted",
|
||||
"rawJson": "JSON",
|
||||
"cacheMiss": "cache miss",
|
||||
"cacheHit": "cache hit",
|
||||
"latency": "Latency",
|
||||
"cost": "Cost",
|
||||
"results": "Results",
|
||||
"rerank": "Rerank",
|
||||
"rerankModel": "Rerank Model",
|
||||
"positionDelta": "Position Change",
|
||||
"emptyState": "Send a search query to see results",
|
||||
"safeSearchOff": "Off",
|
||||
"safeSearchModerate": "Moderate",
|
||||
"safeSearchStrict": "Strict",
|
||||
"queryPlaceholder": "Enter search query...",
|
||||
"providerAuto": "auto (cheapest)",
|
||||
"searchTypeWeb": "web",
|
||||
"searchTypeNews": "news",
|
||||
"optionAny": "any",
|
||||
"timeRangeDay": "Past day",
|
||||
"timeRangeWeek": "Past week",
|
||||
"timeRangeMonth": "Past month",
|
||||
"timeRangeYear": "Past year",
|
||||
"domainPlaceholder": "example.com",
|
||||
"requestTimedOut": "Request timed out ({seconds}s)",
|
||||
"networkError": "Network error"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,7 +56,9 @@
|
||||
"warning": "Предупреждение",
|
||||
"note": "Забележка",
|
||||
"free": "безплатно",
|
||||
"skipToContent": "Преминете към съдържанието"
|
||||
"skipToContent": "Преминете към съдържанието",
|
||||
"maintenanceServerIssues": "Server is experiencing issues. Some features may be unavailable.",
|
||||
"maintenanceServerUnreachable": "Server is unreachable. Reconnecting..."
|
||||
},
|
||||
"sidebar": {
|
||||
"home": "Начало",
|
||||
@@ -103,7 +105,9 @@
|
||||
"endpoints": "Крайни точки",
|
||||
"playground": "Площадка",
|
||||
"agents": "Агенти",
|
||||
"cliToolsShort": "Инструменти"
|
||||
"cliToolsShort": "Инструменти",
|
||||
"autoCombo": "Auto Combo",
|
||||
"searchTools": "Search Tools"
|
||||
},
|
||||
"themesPage": {
|
||||
"title": "Теми",
|
||||
@@ -268,7 +272,28 @@
|
||||
"permissionsTitle": "Разрешения: {name}",
|
||||
"allowAllDesc": "Този ключ има достъп до всички налични модели.",
|
||||
"restrictDesc": "Този ключ има достъп до {selectedCount} от {totalModels} модели.",
|
||||
"selectedCount": "{count} избран"
|
||||
"selectedCount": "{count} избран",
|
||||
"autoResolve": "Auto-Resolve",
|
||||
"autoResolveDesc": "Auto-resolve ambiguous model names to native provider for this API key.",
|
||||
"keyActive": "Key Active",
|
||||
"keyActiveDesc": "Enable or disable this API key. Disabled keys are immediately rejected with 403.",
|
||||
"accessSchedule": "Access Schedule",
|
||||
"accessScheduleDesc": "Restrict access to specific hours and days of the week.",
|
||||
"scheduleFrom": "From",
|
||||
"scheduleUntil": "Until",
|
||||
"scheduleDays": "Days",
|
||||
"scheduleTimezone": "Timezone",
|
||||
"scheduleTimezoneHint": "Use IANA timezone names, e.g. America/New_York, Europe/Berlin",
|
||||
"scheduleActive": "Schedule",
|
||||
"disabled": "Disabled",
|
||||
"daySun": "Sun",
|
||||
"dayMon": "Mon",
|
||||
"dayTue": "Tue",
|
||||
"dayWed": "Wed",
|
||||
"dayThu": "Thu",
|
||||
"dayFri": "Fri",
|
||||
"daySat": "Sat",
|
||||
"keyOnlyAvailableAtCreation": "Full key available only at creation time — copy it when you first create the key"
|
||||
},
|
||||
"auditLog": {
|
||||
"title": "Дневник за одит",
|
||||
@@ -548,6 +573,27 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"mitmHowWorksDesc": "{toolName} sends requests to its provider endpoint. MITM intercepts and redirects them to OmniRoute.",
|
||||
"mitmStep1": "1. Start MITM to route requests through OmniRoute.",
|
||||
"mitmStep2Prefix": "2. Add",
|
||||
"mitmStep2Suffix": "to your hosts file as 127.0.0.1.",
|
||||
"mitmStep3": "3. Open {toolName} and requests will be proxied.",
|
||||
"whenToUseLabel": "Кога да използвате",
|
||||
"openToolDocs": "Отворете документите на инструмента",
|
||||
"toolUseCases": {
|
||||
"claude": "Използвайте, когато искате силни работни потоци за планиране и дълги многофайлови рефактори с Claude Code.",
|
||||
"codex": "Използвайте, когато вашият екип е стандартизиран на OpenAI Codex CLI потоци и базирано на профил удостоверяване.",
|
||||
"droid": "Използвайте, когато имате нужда от лек терминален агент, фокусиран върху цикли за бързо кодиране и изпълнение на команди.",
|
||||
"openclaw": "Използвайте, когато искате агент за кодиране в стил Open Claw, но насочен през политиките на OmniRoute.",
|
||||
"cline": "Използвайте, когато конфигурирате кодиращи агенти вътре в редактори и искате насочвана настройка с OmniRoute модели.",
|
||||
"kilo": "Използвайте, когато вашият работен процес зависи от команди на Kilo Code и бързи итеративни редакции.",
|
||||
"cursor": "Използвайте, когато кодирате в Cursor и имате нужда от персонализирани OpenAI-съвместими модели чрез OmniRoute.",
|
||||
"continue": "Използвайте, когато изпълнявате Continue в IDE и имате нужда от преносима JSON-базирана конфигурация на доставчик.",
|
||||
"opencode": "Използвайте, когато предпочитате нативни за терминални агенти и скриптова автоматизация чрез OpenCode.",
|
||||
"kiro": "Използвайте, когато интегрирате Kiro и контролирате маршрутизирането на модела централно от OmniRoute.",
|
||||
"antigravity": "Използвайте, когато трафикът на Antigravity/Kiro трябва да бъде прихванат чрез MITM и насочен към OmniRoute.",
|
||||
"copilot": "Използвайте, когато искате UX в стил на чат Copilot, като същевременно налагате OmniRoute ключове и правила за маршрутизиране."
|
||||
}
|
||||
},
|
||||
"combos": {
|
||||
@@ -864,7 +910,12 @@
|
||||
"a2aQuickStartStep2": "Изпратете JSON - RPC заявки до@@ PH0 @@, като използвате @@ PH1 @@ или @@ PH2 @@.",
|
||||
"a2aQuickStartStep3": "Проследяване и контрол на задачите с помощта на @@ PH0 @@ и @@ PH1 @@.",
|
||||
"completionsLegacy": "Completions (Legacy)",
|
||||
"completionsLegacyDesc": "Legacy OpenAI text completions — accepts both prompt string and messages array format"
|
||||
"completionsLegacyDesc": "Legacy OpenAI text completions — accepts both prompt string and messages array format",
|
||||
"categorySearch": "Search & Discovery",
|
||||
"webSearch": "Web Search",
|
||||
"webSearchDesc": "Unified web search across multiple providers with automatic failover and caching",
|
||||
"searchProvider": "Search Provider",
|
||||
"searchProviderDesc": "This provider is used for web search via POST /v1/search. No model configuration needed — search providers are ready to use once an API key is connected."
|
||||
},
|
||||
"mcpDashboard": {
|
||||
"loading": "Зареждане на таблото за управление на MCP...",
|
||||
@@ -1054,7 +1105,13 @@
|
||||
"resetAllTitle": "Нулирайте всички прекъсвачи в изправно състояние",
|
||||
"resetting": "Нулиране...",
|
||||
"resetAll": "Нулиране на всички",
|
||||
"until": "До {time}"
|
||||
"until": "До {time}",
|
||||
"activeProviders": "{count} active",
|
||||
"monitoredProviders": "{count} monitored",
|
||||
"configuredProvidersLabel": "Конфигуриран в таблото за управление",
|
||||
"configuredProvidersHint": "Доставчици с идентификационни данни, записани в /dashboard/providers, независимо от състоянието на изпълнение.",
|
||||
"activeProvidersHint": "Конфигурираните доставчици в момента са активирани за заявки за маршрутизиране.",
|
||||
"monitoredProvidersHint": "Доставчиците в момента се проследяват от монитори за изправност на прекъсвачи."
|
||||
},
|
||||
"limits": {
|
||||
"title": "Ограничения и квоти",
|
||||
@@ -1396,7 +1453,31 @@
|
||||
"chatPathHint": "Custom chat path for providers with non-standard APIs (e.g. /v4/chat/completions)",
|
||||
"modelsPathLabel": "Models Endpoint Path",
|
||||
"modelsPathPlaceholder": "/models",
|
||||
"modelsPathHint": "Custom models path for validation (e.g. /v4/models)"
|
||||
"modelsPathHint": "Custom models path for validation (e.g. /v4/models)",
|
||||
"builtInModels": "Built-in models",
|
||||
"builtInModelsHint": "Registry models for this provider. Use the pencil to set compatibility options.",
|
||||
"normalizeToolCallIdLabel": "Normalize tool call IDs to 9 characters (e.g. Mistral)",
|
||||
"preserveDeveloperRoleLabel": "Keep OpenAI Responses developer role (do not map to system)",
|
||||
"compatAdjustmentsTitle": "Compatibility",
|
||||
"compatButtonLabel": "Compatibility",
|
||||
"compatToolIdShort": "Tool ID 9",
|
||||
"compatDeveloperShort": "Developer role",
|
||||
"compatDoNotPreserveDeveloper": "Do not preserve developer role",
|
||||
"compatBadgeNoPreserve": "No preserve",
|
||||
"compatProtocolLabel": "Client request protocol",
|
||||
"compatProtocolHint": "These options apply when OmniRoute detects this request shape (OpenAI Chat, Responses API, or Anthropic Messages).",
|
||||
"compatProtocolOpenAI": "OpenAI Chat Completions",
|
||||
"compatProtocolOpenAIResponses": "OpenAI Responses API",
|
||||
"compatProtocolClaude": "Anthropic Messages",
|
||||
"tokenRefreshed": "Token refreshed successfully",
|
||||
"tokenRefreshFailed": "Token refresh failed",
|
||||
"compatBadgeUpstreamHeaders": "Headers",
|
||||
"compatUpstreamAddRow": "Add header",
|
||||
"compatUpstreamHeaderName": "Header name",
|
||||
"compatUpstreamHeaderValue": "Value",
|
||||
"compatUpstreamHeadersHint": "High-privilege setting — same trust level as editing provider API credentials; only trusted admins should use it. Merged after OmniRoute adds auth from the provider API key. If a custom header uses the same name as an existing one (e.g. Authorization), your value fully replaces the auto-generated header (including the Bearer token) — the upstream only sees what you typed, not the key from settings. Misconfiguration can cause 401 or broken upstream auth. One row per header (e.g. extra Authentication for some gateways). Hover or focus the value to preview. Saves on blur, outside click, or closing this panel.",
|
||||
"compatUpstreamHeadersLabel": "Extra upstream headers",
|
||||
"compatUpstreamRemoveRow": "Remove row"
|
||||
},
|
||||
"settings": {
|
||||
"title": "Настройки",
|
||||
@@ -1799,7 +1880,13 @@
|
||||
"cliFingerprintDesc": "Match native CLI binary signatures when proxying requests. Reorders headers and body fields to look identical to the official CLI tools. Your proxy IP is preserved.",
|
||||
"cliFingerprintEnabled": "{count} provider(s) with CLI fingerprint active",
|
||||
"enableFingerprintTitle": "Enable fingerprint for {provider}",
|
||||
"disableFingerprintTitle": "Disable fingerprint for {provider}"
|
||||
"disableFingerprintTitle": "Disable fingerprint for {provider}",
|
||||
"routingAdvancedGuideTitle": "Разширено ръководство за маршрутизиране",
|
||||
"routingAdvancedGuideHint1": "Използвайте Fill First за предвидим приоритет, Round Robin за справедливост и P2C за устойчивост на забавяне.",
|
||||
"routingAdvancedGuideHint2": "Ако доставчиците се различават по отношение на качество/цена, започнете с Cost Opt за фонова работа и Least Used за балансирано износване.",
|
||||
"comboDefaultsGuideTitle": "Как да настроите настройките по подразбиране на комбинацията",
|
||||
"comboDefaultsGuideHint1": "Поддържайте ниски повторни опити в потоци с ниска латентност; увеличете времето за изчакване само за задачи с дълго генериране.",
|
||||
"comboDefaultsGuideHint2": "Използвайте замени на доставчика, когато един доставчик се нуждае от различно поведение при изчакване/повторен опит от глобалните настройки по подразбиране."
|
||||
},
|
||||
"translator": {
|
||||
"title": "Преводач",
|
||||
@@ -2216,7 +2303,8 @@
|
||||
"orRemovePasswordHashField": "или премахнете полето passwordHash",
|
||||
"restartServerWithNewPassword": "Рестартирайте сървъра - той ще използва новата парола",
|
||||
"backToLogin": "Назад към Вход",
|
||||
"forgotPassword": "Забравена парола?"
|
||||
"forgotPassword": "Забравена парола?",
|
||||
"defaultPasswordHint": "Default password: 123456 (unless INITIAL_PASSWORD was set)"
|
||||
},
|
||||
"landing": {
|
||||
"brandName": "OmniRoute",
|
||||
@@ -2414,7 +2502,16 @@
|
||||
"troubleshootingCodexFamily": "За модели от семейството на GitHub Codex, запазете модела като gh/codex-model; рутерът избира /отговаря автоматично.",
|
||||
"troubleshootingTestConnection": "Използвайте Табло > Доставчици > Тестване на връзката, преди да тествате от IDE или външни клиенти.",
|
||||
"troubleshootingCircuitBreaker": "Ако доставчикът покаже отворен прекъсвач, изчакайте охлаждането или проверете страницата Health за подробности.",
|
||||
"troubleshootingOAuth": "За доставчици на OAuth, повторно удостоверяване, ако токените изтекат. Проверете индикатора за състояние на картата на доставчика."
|
||||
"troubleshootingOAuth": "За доставчици на OAuth, повторно удостоверяване, ако токените изтекат. Проверете индикатора за състояние на картата на доставчика.",
|
||||
"managementApiReference": "Management API Reference",
|
||||
"managementApiDescription": "Automation endpoints for proxy registry, scope assignments, and legacy proxy migration.",
|
||||
"mgmtProxiesListNote": "List saved proxy registry items (supports pagination).",
|
||||
"mgmtProxiesCreateNote": "Create a reusable proxy item in the registry.",
|
||||
"mgmtProxiesHealthNote": "Get 24h/rolling health metrics per saved proxy from proxy logs.",
|
||||
"mgmtProxiesBulkAssignNote": "Assign or clear one proxy across many scope IDs in one request.",
|
||||
"mgmtAssignmentsListNote": "List proxy assignments by scope, scope_id, or proxy_id.",
|
||||
"mgmtAssignmentsUpdateNote": "Assign or clear proxy for global/provider/account/combo scope.",
|
||||
"mgmtLegacyMigrationNote": "Import legacy proxyConfig maps into registry assignments."
|
||||
},
|
||||
"legal": {
|
||||
"privacyPolicy": "Политика за поверителност",
|
||||
@@ -2503,7 +2600,21 @@
|
||||
"binaryName": "Binary Name",
|
||||
"versionCommand": "Version Command",
|
||||
"spawnArgs": "Spawn Args",
|
||||
"addAgent": "Add Agent"
|
||||
"addAgent": "Add Agent",
|
||||
"scanning": "Scanning system for CLI agents...",
|
||||
"opencodeIntegration": "OpenCode Integration",
|
||||
"opencodeDetected": "opencode {version} detected",
|
||||
"opencodeDesc": "Generate a ready-to-use {configFile} with your OmniRoute base URL and all available models — drop it in your project root and run {command}.",
|
||||
"downloadConfig": "Download {file}",
|
||||
"downloaded": "Downloaded!",
|
||||
"setupGuideTitle": "Setup guide",
|
||||
"openCliTools": "Open CLI Tools",
|
||||
"setupGuideDetectCliTitle": "Detect installed CLIs",
|
||||
"setupGuideDetectCliDesc": "Click Refresh after installing or updating a CLI so OmniRoute can rescan binaries and versions.",
|
||||
"setupGuideCustomAgentTitle": "Register custom binary",
|
||||
"setupGuideCustomAgentDesc": "Use Add Custom Agent when your CLI is not in the built-in list. Provide binary name and version command.",
|
||||
"setupGuideCommandMissingTitle": "Fix 'command not found'",
|
||||
"setupGuideCommandMissingDesc": "Ensure the CLI command exists in PATH, open a new terminal session, and rerun Refresh."
|
||||
},
|
||||
"autoCombo": {
|
||||
"title": "Auto-Combo Engine",
|
||||
@@ -2527,5 +2638,56 @@
|
||||
"modePackCostSaver": "Cost Saver",
|
||||
"modePackQualityFirst": "Quality First",
|
||||
"modePackOfflineFriendly": "Offline Friendly"
|
||||
},
|
||||
"search": {
|
||||
"searchQuery": "Search Query",
|
||||
"searchResults": "Search Results",
|
||||
"cachedResult": "Cached",
|
||||
"searchCost": "Cost",
|
||||
"searchTools": "Search Tools",
|
||||
"searchToolsDesc": "Advanced search testing with provider comparison",
|
||||
"compareProviders": "Compare Providers",
|
||||
"rerankResults": "Rerank Results",
|
||||
"searchHistory": "Search History",
|
||||
"urlOverlap": "URL Overlap",
|
||||
"noSearchProviders": "No search providers configured. Add providers in Settings.",
|
||||
"noRerankModels": "No rerank model available",
|
||||
"webSearch": "Web Search",
|
||||
"provider": "Provider",
|
||||
"searchType": "Search Type",
|
||||
"maxResults": "Max Results",
|
||||
"filters": "Filters",
|
||||
"country": "Country",
|
||||
"language": "Language",
|
||||
"timeRange": "Time Range",
|
||||
"includeDomains": "Include Domains",
|
||||
"excludeDomains": "Exclude Domains",
|
||||
"safeSearch": "Safe Search",
|
||||
"formatted": "Formatted",
|
||||
"rawJson": "JSON",
|
||||
"cacheMiss": "cache miss",
|
||||
"cacheHit": "cache hit",
|
||||
"latency": "Latency",
|
||||
"cost": "Cost",
|
||||
"results": "Results",
|
||||
"rerank": "Rerank",
|
||||
"rerankModel": "Rerank Model",
|
||||
"positionDelta": "Position Change",
|
||||
"emptyState": "Send a search query to see results",
|
||||
"safeSearchOff": "Off",
|
||||
"safeSearchModerate": "Moderate",
|
||||
"safeSearchStrict": "Strict",
|
||||
"queryPlaceholder": "Enter search query...",
|
||||
"providerAuto": "auto (cheapest)",
|
||||
"searchTypeWeb": "web",
|
||||
"searchTypeNews": "news",
|
||||
"optionAny": "any",
|
||||
"timeRangeDay": "Past day",
|
||||
"timeRangeWeek": "Past week",
|
||||
"timeRangeMonth": "Past month",
|
||||
"timeRangeYear": "Past year",
|
||||
"domainPlaceholder": "example.com",
|
||||
"requestTimedOut": "Request timed out ({seconds}s)",
|
||||
"networkError": "Network error"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,7 +56,9 @@
|
||||
"warning": "Varování",
|
||||
"note": "Poznámka",
|
||||
"free": "Uvolnit",
|
||||
"skipToContent": "Přejít na obsah"
|
||||
"skipToContent": "Přejít na obsah",
|
||||
"maintenanceServerIssues": "Server is experiencing issues. Some features may be unavailable.",
|
||||
"maintenanceServerUnreachable": "Server is unreachable. Reconnecting..."
|
||||
},
|
||||
"sidebar": {
|
||||
"home": "Domov",
|
||||
@@ -364,7 +366,22 @@
|
||||
"rerank": "Přeřadit",
|
||||
"rerankModel": "Přeřazovací Model (Rerank)",
|
||||
"positionDelta": "Změna pozice",
|
||||
"emptyState": "Odešlete vyhledávací dotaz pro zobrazení výsledků"
|
||||
"emptyState": "Odešlete vyhledávací dotaz pro zobrazení výsledků",
|
||||
"safeSearchOff": "Off",
|
||||
"safeSearchModerate": "Moderate",
|
||||
"safeSearchStrict": "Strict",
|
||||
"queryPlaceholder": "Enter search query...",
|
||||
"providerAuto": "auto (cheapest)",
|
||||
"searchTypeWeb": "web",
|
||||
"searchTypeNews": "news",
|
||||
"optionAny": "any",
|
||||
"timeRangeDay": "Past day",
|
||||
"timeRangeWeek": "Past week",
|
||||
"timeRangeMonth": "Past month",
|
||||
"timeRangeYear": "Past year",
|
||||
"domainPlaceholder": "example.com",
|
||||
"requestTimedOut": "Request timed out ({seconds}s)",
|
||||
"networkError": "Network error"
|
||||
},
|
||||
"cliTools": {
|
||||
"title": "CLI Nástroje",
|
||||
@@ -612,7 +629,23 @@
|
||||
"mitmStep1": "Přejděte na stránku Správce API",
|
||||
"mitmStep2Prefix": "Zadejte prefix, např. cc/",
|
||||
"mitmStep2Suffix": "a cílový model jako claude-sonnet-4-20250514",
|
||||
"mitmStep3": "Uložte a restartujte server"
|
||||
"mitmStep3": "Uložte a restartujte server",
|
||||
"whenToUseLabel": "Kdy použít",
|
||||
"openToolDocs": "Otevřete dokumentaci nástroje",
|
||||
"toolUseCases": {
|
||||
"claude": "Použijte, když chcete silné plánovací pracovní postupy a dlouhé vícesouborové refaktory s Claude Code.",
|
||||
"codex": "Použijte, když je váš tým standardizován na tocích OpenAI Codex CLI a ověřování na základě profilu.",
|
||||
"droid": "Použijte, když potřebujete lehkého terminálového agenta zaměřeného na rychlé kódování a smyčky provádění příkazů.",
|
||||
"openclaw": "Použijte, když chcete kódovacího agenta ve stylu Open Claw, ale směrovaného přes zásady OmniRoute.",
|
||||
"cline": "Použijte, když konfigurujete kódovací agenty v editorech a chcete řízené nastavení s modely OmniRoute.",
|
||||
"kilo": "Použijte, když váš pracovní postup závisí na příkazech Kilo Code a rychlých iterativních úpravách.",
|
||||
"cursor": "Použijte při kódování v Cursoru a potřebujete vlastní modely kompatibilní s OpenAI prostřednictvím OmniRoute.",
|
||||
"continue": "Použijte, když spouštíte Pokračovat v IDE a potřebujete přenosnou konfiguraci poskytovatele na bázi JSON.",
|
||||
"opencode": "Použijte, pokud dáváte přednost spouštění nativního agenta a skriptované automatizaci prostřednictvím OpenCode.",
|
||||
"kiro": "Použijte při integraci Kiro a centrálním řízení směrování modelu z OmniRoute.",
|
||||
"antigravity": "Použijte, když provoz Antigravity/Kiro musí být zachycen prostřednictvím MITM a směrován do OmniRoute.",
|
||||
"copilot": "Použijte, když chcete uživatelské rozhraní ve stylu chatu Copilot při vynucování klíčů OmniRoute a pravidel směrování."
|
||||
}
|
||||
},
|
||||
"combos": {
|
||||
"title": "Komba",
|
||||
@@ -1135,7 +1168,13 @@
|
||||
"resetAllTitle": "Resetujte všechny jističe do funkčního stavu",
|
||||
"resetting": "Resetování...",
|
||||
"resetAll": "Obnovit vše",
|
||||
"until": "Do {time}"
|
||||
"until": "Do {time}",
|
||||
"activeProviders": "{count} active",
|
||||
"monitoredProviders": "{count} monitored",
|
||||
"configuredProvidersLabel": "Nakonfigurováno v řídicím panelu",
|
||||
"configuredProvidersHint": "Poskytovatelé s přihlašovacími údaji uloženými v /dashboard/providers, bez ohledu na stav běhu.",
|
||||
"activeProvidersHint": "Konfigurovaní poskytovatelé aktuálně povoleni pro požadavky na směrování.",
|
||||
"monitoredProvidersHint": "Poskytovatelé aktuálně sledovaní monitory stavu vypínačů."
|
||||
},
|
||||
"limits": {
|
||||
"title": "Limity a kvóty",
|
||||
@@ -1494,7 +1533,14 @@
|
||||
"compatProtocolHint": "Formát požadavku",
|
||||
"compatProtocolOpenAI": "OpenAI",
|
||||
"compatProtocolOpenAIResponses": "OpenAI Responses",
|
||||
"compatProtocolClaude": "Anthropic Messages"
|
||||
"compatProtocolClaude": "Anthropic Messages",
|
||||
"compatBadgeUpstreamHeaders": "Headers",
|
||||
"compatUpstreamAddRow": "Add header",
|
||||
"compatUpstreamHeaderName": "Header name",
|
||||
"compatUpstreamHeaderValue": "Value",
|
||||
"compatUpstreamHeadersHint": "High-privilege setting — same trust level as editing provider API credentials; only trusted admins should use it. Merged after OmniRoute adds auth from the provider API key. If a custom header uses the same name as an existing one (e.g. Authorization), your value fully replaces the auto-generated header (including the Bearer token) — the upstream only sees what you typed, not the key from settings. Misconfiguration can cause 401 or broken upstream auth. One row per header (e.g. extra Authentication for some gateways). Hover or focus the value to preview. Saves on blur, outside click, or closing this panel.",
|
||||
"compatUpstreamHeadersLabel": "Extra upstream headers",
|
||||
"compatUpstreamRemoveRow": "Remove row"
|
||||
},
|
||||
"settings": {
|
||||
"title": "Nastavení",
|
||||
@@ -1897,7 +1943,13 @@
|
||||
"customPricingNote": "Výchozí ceny pro konkrétní modely můžete přepsat. Vlastní přepsání má přednost před automaticky zjištěnými cenami.",
|
||||
"editPricing": "Upravit ceny",
|
||||
"viewFullDetails": "Zobrazit všechny podrobnosti",
|
||||
"themeCoral": "Korál"
|
||||
"themeCoral": "Korál",
|
||||
"routingAdvancedGuideTitle": "Pokročilé vedení trasy",
|
||||
"routingAdvancedGuideHint1": "Použijte Fill First pro předvídatelnou prioritu, Round Robin pro spravedlnost a P2C pro odolnost vůči latenci.",
|
||||
"routingAdvancedGuideHint2": "Pokud se poskytovatelé liší v kvalitě/ceně, začněte s Cost Opt pro práci na pozadí a Nejméně použité pro vyvážené opotřebení.",
|
||||
"comboDefaultsGuideTitle": "Jak vyladit výchozí nastavení komba",
|
||||
"comboDefaultsGuideHint1": "Udržujte počet opakování na nízké úrovni v tocích s nízkou latencí; prodlužte časový limit pouze u úloh s dlouhým generováním.",
|
||||
"comboDefaultsGuideHint2": "Použít přepsání poskytovatele, když jeden poskytovatel potřebuje jiné chování časového limitu/opakování než globální výchozí hodnoty."
|
||||
},
|
||||
"translator": {
|
||||
"title": "Překladatel",
|
||||
@@ -2599,7 +2651,21 @@
|
||||
"binaryName": "Binární název",
|
||||
"versionCommand": "Příkaz verze",
|
||||
"spawnArgs": "Spawn Arguments",
|
||||
"addAgent": "Přidat agenta"
|
||||
"addAgent": "Přidat agenta",
|
||||
"scanning": "Scanning system for CLI agents...",
|
||||
"opencodeIntegration": "OpenCode Integration",
|
||||
"opencodeDetected": "opencode {version} detected",
|
||||
"opencodeDesc": "Generate a ready-to-use {configFile} with your OmniRoute base URL and all available models — drop it in your project root and run {command}.",
|
||||
"downloadConfig": "Download {file}",
|
||||
"downloaded": "Downloaded!",
|
||||
"setupGuideTitle": "Setup guide",
|
||||
"openCliTools": "Open CLI Tools",
|
||||
"setupGuideDetectCliTitle": "Detect installed CLIs",
|
||||
"setupGuideDetectCliDesc": "Click Refresh after installing or updating a CLI so OmniRoute can rescan binaries and versions.",
|
||||
"setupGuideCustomAgentTitle": "Register custom binary",
|
||||
"setupGuideCustomAgentDesc": "Use Add Custom Agent when your CLI is not in the built-in list. Provide binary name and version command.",
|
||||
"setupGuideCommandMissingTitle": "Fix 'command not found'",
|
||||
"setupGuideCommandMissingDesc": "Ensure the CLI command exists in PATH, open a new terminal session, and rerun Refresh."
|
||||
},
|
||||
"autoCombo": {
|
||||
"title": "Auto-Kombo Engine",
|
||||
|
||||
@@ -56,7 +56,9 @@
|
||||
"warning": "Advarsel",
|
||||
"note": "Bemærk",
|
||||
"free": "Gratis",
|
||||
"skipToContent": "Gå til indhold"
|
||||
"skipToContent": "Gå til indhold",
|
||||
"maintenanceServerIssues": "Server is experiencing issues. Some features may be unavailable.",
|
||||
"maintenanceServerUnreachable": "Server is unreachable. Reconnecting..."
|
||||
},
|
||||
"sidebar": {
|
||||
"home": "Hjem",
|
||||
@@ -103,7 +105,9 @@
|
||||
"endpoints": "Endpoints",
|
||||
"playground": "Legeplads",
|
||||
"agents": "Agenter",
|
||||
"cliToolsShort": "Værktøjer"
|
||||
"cliToolsShort": "Værktøjer",
|
||||
"autoCombo": "Auto Combo",
|
||||
"searchTools": "Search Tools"
|
||||
},
|
||||
"themesPage": {
|
||||
"title": "Temaer",
|
||||
@@ -268,7 +272,28 @@
|
||||
"permissionsTitle": "Tilladelser: {name}",
|
||||
"allowAllDesc": "Denne nøgle kan få adgang til alle tilgængelige modeller.",
|
||||
"restrictDesc": "Denne nøgle kan få adgang til {selectedCount} af {totalModels} modeller.",
|
||||
"selectedCount": "{count} valgt"
|
||||
"selectedCount": "{count} valgt",
|
||||
"autoResolve": "Auto-Resolve",
|
||||
"autoResolveDesc": "Auto-resolve ambiguous model names to native provider for this API key.",
|
||||
"keyActive": "Key Active",
|
||||
"keyActiveDesc": "Enable or disable this API key. Disabled keys are immediately rejected with 403.",
|
||||
"accessSchedule": "Access Schedule",
|
||||
"accessScheduleDesc": "Restrict access to specific hours and days of the week.",
|
||||
"scheduleFrom": "From",
|
||||
"scheduleUntil": "Until",
|
||||
"scheduleDays": "Days",
|
||||
"scheduleTimezone": "Timezone",
|
||||
"scheduleTimezoneHint": "Use IANA timezone names, e.g. America/New_York, Europe/Berlin",
|
||||
"scheduleActive": "Schedule",
|
||||
"disabled": "Disabled",
|
||||
"daySun": "Sun",
|
||||
"dayMon": "Mon",
|
||||
"dayTue": "Tue",
|
||||
"dayWed": "Wed",
|
||||
"dayThu": "Thu",
|
||||
"dayFri": "Fri",
|
||||
"daySat": "Sat",
|
||||
"keyOnlyAvailableAtCreation": "Full key available only at creation time — copy it when you first create the key"
|
||||
},
|
||||
"auditLog": {
|
||||
"title": "Revisionslog",
|
||||
@@ -548,6 +573,27 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"mitmHowWorksDesc": "{toolName} sends requests to its provider endpoint. MITM intercepts and redirects them to OmniRoute.",
|
||||
"mitmStep1": "1. Start MITM to route requests through OmniRoute.",
|
||||
"mitmStep2Prefix": "2. Add",
|
||||
"mitmStep2Suffix": "to your hosts file as 127.0.0.1.",
|
||||
"mitmStep3": "3. Open {toolName} and requests will be proxied.",
|
||||
"whenToUseLabel": "Hvornår skal bruges",
|
||||
"openToolDocs": "Åbn værktøjsdokumenter",
|
||||
"toolUseCases": {
|
||||
"claude": "Brug, når du ønsker stærke planlægnings-arbejdsgange og lange multi-fil-refaktorer med Claude Code.",
|
||||
"codex": "Bruges, når dit team er standardiseret på OpenAI Codex CLI-flows og profilbaseret godkendelse.",
|
||||
"droid": "Brug, når du har brug for en letvægtsterminalagent med fokus på hurtig kodning og kommandoudførelsesløkker.",
|
||||
"openclaw": "Brug, når du vil have en Open Claw-stil-kodningsagent, men dirigeret gennem OmniRoute-politikker.",
|
||||
"cline": "Bruges, når du konfigurerer kodningsagenter i editorer og ønsker guidet opsætning med OmniRoute-modeller.",
|
||||
"kilo": "Brug, når dit arbejdsflow afhænger af Kilo Code-kommandoer og hurtige iterative redigeringer.",
|
||||
"cursor": "Brug, når du koder i Cursor, og du har brug for brugerdefinerede OpenAI-kompatible modeller gennem OmniRoute.",
|
||||
"continue": "Brug, når du kører Continue i IDE'er, og du har brug for bærbar JSON-baseret udbyderkonfiguration.",
|
||||
"opencode": "Brug, når du foretrækker terminal-native agentkørsler og scriptet automatisering via OpenCode.",
|
||||
"kiro": "Bruges ved integration af Kiro og styring af modelrouting centralt fra OmniRoute.",
|
||||
"antigravity": "Bruges, når Antigravity/Kiro-trafik skal opsnappes gennem MITM og dirigeres til OmniRoute.",
|
||||
"copilot": "Brug, når du ønsker Copilot-chatstil UX, mens du håndhæver OmniRoute-nøgler og routingregler."
|
||||
}
|
||||
},
|
||||
"combos": {
|
||||
@@ -864,7 +910,12 @@
|
||||
"a2aQuickStartStep2": "Send JSON-RPC-anmodninger til`POST /a2a`vedhjælp af `message/send` eller `message/stream`.",
|
||||
"a2aQuickStartStep3": "Spor og kontroller opgaver ved hjælp af `tasks/get` og `tasks/cancel`.",
|
||||
"completionsLegacy": "Completions (Legacy)",
|
||||
"completionsLegacyDesc": "Legacy OpenAI text completions — accepts both prompt string and messages array format"
|
||||
"completionsLegacyDesc": "Legacy OpenAI text completions — accepts both prompt string and messages array format",
|
||||
"categorySearch": "Search & Discovery",
|
||||
"webSearch": "Web Search",
|
||||
"webSearchDesc": "Unified web search across multiple providers with automatic failover and caching",
|
||||
"searchProvider": "Search Provider",
|
||||
"searchProviderDesc": "This provider is used for web search via POST /v1/search. No model configuration needed — search providers are ready to use once an API key is connected."
|
||||
},
|
||||
"mcpDashboard": {
|
||||
"loading": "Indlæser MCP-dashboard...",
|
||||
@@ -1054,7 +1105,13 @@
|
||||
"resetAllTitle": "Nulstil alle afbrydere til sund tilstand",
|
||||
"resetting": "Nulstiller...",
|
||||
"resetAll": "Nulstil alle",
|
||||
"until": "Indtil {time}"
|
||||
"until": "Indtil {time}",
|
||||
"activeProviders": "{count} active",
|
||||
"monitoredProviders": "{count} monitored",
|
||||
"configuredProvidersLabel": "Konfigureret i dashboard",
|
||||
"configuredProvidersHint": "Udbydere med legitimationsoplysninger gemt i /dashboard/udbydere, uanset køretidstilstand.",
|
||||
"activeProvidersHint": "Konfigurerede udbydere, der i øjeblikket er aktiveret for routinganmodninger.",
|
||||
"monitoredProvidersHint": "Udbydere, der i øjeblikket spores af strømafbryderens sundhedsmonitorer."
|
||||
},
|
||||
"limits": {
|
||||
"title": "Grænser og kvoter",
|
||||
@@ -1396,7 +1453,31 @@
|
||||
"chatPathHint": "Custom chat path for providers with non-standard APIs (e.g. /v4/chat/completions)",
|
||||
"modelsPathLabel": "Models Endpoint Path",
|
||||
"modelsPathPlaceholder": "/models",
|
||||
"modelsPathHint": "Custom models path for validation (e.g. /v4/models)"
|
||||
"modelsPathHint": "Custom models path for validation (e.g. /v4/models)",
|
||||
"builtInModels": "Built-in models",
|
||||
"builtInModelsHint": "Registry models for this provider. Use the pencil to set compatibility options.",
|
||||
"normalizeToolCallIdLabel": "Normalize tool call IDs to 9 characters (e.g. Mistral)",
|
||||
"preserveDeveloperRoleLabel": "Keep OpenAI Responses developer role (do not map to system)",
|
||||
"compatAdjustmentsTitle": "Compatibility",
|
||||
"compatButtonLabel": "Compatibility",
|
||||
"compatToolIdShort": "Tool ID 9",
|
||||
"compatDeveloperShort": "Developer role",
|
||||
"compatDoNotPreserveDeveloper": "Do not preserve developer role",
|
||||
"compatBadgeNoPreserve": "No preserve",
|
||||
"compatProtocolLabel": "Client request protocol",
|
||||
"compatProtocolHint": "These options apply when OmniRoute detects this request shape (OpenAI Chat, Responses API, or Anthropic Messages).",
|
||||
"compatProtocolOpenAI": "OpenAI Chat Completions",
|
||||
"compatProtocolOpenAIResponses": "OpenAI Responses API",
|
||||
"compatProtocolClaude": "Anthropic Messages",
|
||||
"tokenRefreshed": "Token refreshed successfully",
|
||||
"tokenRefreshFailed": "Token refresh failed",
|
||||
"compatBadgeUpstreamHeaders": "Headers",
|
||||
"compatUpstreamAddRow": "Add header",
|
||||
"compatUpstreamHeaderName": "Header name",
|
||||
"compatUpstreamHeaderValue": "Value",
|
||||
"compatUpstreamHeadersHint": "High-privilege setting — same trust level as editing provider API credentials; only trusted admins should use it. Merged after OmniRoute adds auth from the provider API key. If a custom header uses the same name as an existing one (e.g. Authorization), your value fully replaces the auto-generated header (including the Bearer token) — the upstream only sees what you typed, not the key from settings. Misconfiguration can cause 401 or broken upstream auth. One row per header (e.g. extra Authentication for some gateways). Hover or focus the value to preview. Saves on blur, outside click, or closing this panel.",
|
||||
"compatUpstreamHeadersLabel": "Extra upstream headers",
|
||||
"compatUpstreamRemoveRow": "Remove row"
|
||||
},
|
||||
"settings": {
|
||||
"title": "Indstillinger",
|
||||
@@ -1799,7 +1880,13 @@
|
||||
"cliFingerprintDesc": "Match native CLI binary signatures when proxying requests. Reorders headers and body fields to look identical to the official CLI tools. Your proxy IP is preserved.",
|
||||
"cliFingerprintEnabled": "{count} provider(s) with CLI fingerprint active",
|
||||
"enableFingerprintTitle": "Enable fingerprint for {provider}",
|
||||
"disableFingerprintTitle": "Disable fingerprint for {provider}"
|
||||
"disableFingerprintTitle": "Disable fingerprint for {provider}",
|
||||
"routingAdvancedGuideTitle": "Avanceret rutevejledning",
|
||||
"routingAdvancedGuideHint1": "Brug Fill First for forudsigelig prioritet, Round Robin for retfærdighed og P2C for latensmodstandsdygtighed.",
|
||||
"routingAdvancedGuideHint2": "Hvis udbydere varierer i kvalitet/omkostninger, start med Cost Opt for baggrundsarbejde og Mindst brugt for balanceret slid.",
|
||||
"comboDefaultsGuideTitle": "Sådan indstiller du combo-standarder",
|
||||
"comboDefaultsGuideHint1": "Hold lave genforsøg i flows med lav latens; øg kun timeout for lange generationsopgaver.",
|
||||
"comboDefaultsGuideHint2": "Brug udbydertilsidesættelser, når en udbyder har brug for en anden timeout-/genforsøgsadfærd end globale standardindstillinger."
|
||||
},
|
||||
"translator": {
|
||||
"title": "Oversætter",
|
||||
@@ -2216,7 +2303,8 @@
|
||||
"orRemovePasswordHashField": "eller fjern feltet passwordHash",
|
||||
"restartServerWithNewPassword": "Genstart serveren - den vil bruge den nye adgangskode",
|
||||
"backToLogin": "Tilbage til Login",
|
||||
"forgotPassword": "Glemt adgangskode?"
|
||||
"forgotPassword": "Glemt adgangskode?",
|
||||
"defaultPasswordHint": "Default password: 123456 (unless INITIAL_PASSWORD was set)"
|
||||
},
|
||||
"landing": {
|
||||
"brandName": "OmniRoute",
|
||||
@@ -2414,7 +2502,16 @@
|
||||
"troubleshootingCodexFamily": "For GitHub Codex-familiemodeller skal du beholde modellen som gh/codex-model; routeren vælger /svar automatisk.",
|
||||
"troubleshootingTestConnection": "Brug Dashboard > Udbydere > Test forbindelse, før du tester fra IDE'er eller eksterne klienter.",
|
||||
"troubleshootingCircuitBreaker": "Hvis en udbyder viser en afbryder åben, skal du vente på nedkøling eller tjekke Health-siden for detaljer.",
|
||||
"troubleshootingOAuth": "For OAuth-udbydere skal du godkende igen, hvis tokens udløber. Tjek udbyderkortets statusindikator."
|
||||
"troubleshootingOAuth": "For OAuth-udbydere skal du godkende igen, hvis tokens udløber. Tjek udbyderkortets statusindikator.",
|
||||
"managementApiReference": "Management API Reference",
|
||||
"managementApiDescription": "Automation endpoints for proxy registry, scope assignments, and legacy proxy migration.",
|
||||
"mgmtProxiesListNote": "List saved proxy registry items (supports pagination).",
|
||||
"mgmtProxiesCreateNote": "Create a reusable proxy item in the registry.",
|
||||
"mgmtProxiesHealthNote": "Get 24h/rolling health metrics per saved proxy from proxy logs.",
|
||||
"mgmtProxiesBulkAssignNote": "Assign or clear one proxy across many scope IDs in one request.",
|
||||
"mgmtAssignmentsListNote": "List proxy assignments by scope, scope_id, or proxy_id.",
|
||||
"mgmtAssignmentsUpdateNote": "Assign or clear proxy for global/provider/account/combo scope.",
|
||||
"mgmtLegacyMigrationNote": "Import legacy proxyConfig maps into registry assignments."
|
||||
},
|
||||
"legal": {
|
||||
"privacyPolicy": "Privatlivspolitik",
|
||||
@@ -2503,7 +2600,21 @@
|
||||
"binaryName": "Binary Name",
|
||||
"versionCommand": "Version Command",
|
||||
"spawnArgs": "Spawn Args",
|
||||
"addAgent": "Add Agent"
|
||||
"addAgent": "Add Agent",
|
||||
"scanning": "Scanning system for CLI agents...",
|
||||
"opencodeIntegration": "OpenCode Integration",
|
||||
"opencodeDetected": "opencode {version} detected",
|
||||
"opencodeDesc": "Generate a ready-to-use {configFile} with your OmniRoute base URL and all available models — drop it in your project root and run {command}.",
|
||||
"downloadConfig": "Download {file}",
|
||||
"downloaded": "Downloaded!",
|
||||
"setupGuideTitle": "Setup guide",
|
||||
"openCliTools": "Open CLI Tools",
|
||||
"setupGuideDetectCliTitle": "Detect installed CLIs",
|
||||
"setupGuideDetectCliDesc": "Click Refresh after installing or updating a CLI so OmniRoute can rescan binaries and versions.",
|
||||
"setupGuideCustomAgentTitle": "Register custom binary",
|
||||
"setupGuideCustomAgentDesc": "Use Add Custom Agent when your CLI is not in the built-in list. Provide binary name and version command.",
|
||||
"setupGuideCommandMissingTitle": "Fix 'command not found'",
|
||||
"setupGuideCommandMissingDesc": "Ensure the CLI command exists in PATH, open a new terminal session, and rerun Refresh."
|
||||
},
|
||||
"autoCombo": {
|
||||
"title": "Auto-Combo Engine",
|
||||
@@ -2527,5 +2638,56 @@
|
||||
"modePackCostSaver": "Cost Saver",
|
||||
"modePackQualityFirst": "Quality First",
|
||||
"modePackOfflineFriendly": "Offline Friendly"
|
||||
},
|
||||
"search": {
|
||||
"searchQuery": "Search Query",
|
||||
"searchResults": "Search Results",
|
||||
"cachedResult": "Cached",
|
||||
"searchCost": "Cost",
|
||||
"searchTools": "Search Tools",
|
||||
"searchToolsDesc": "Advanced search testing with provider comparison",
|
||||
"compareProviders": "Compare Providers",
|
||||
"rerankResults": "Rerank Results",
|
||||
"searchHistory": "Search History",
|
||||
"urlOverlap": "URL Overlap",
|
||||
"noSearchProviders": "No search providers configured. Add providers in Settings.",
|
||||
"noRerankModels": "No rerank model available",
|
||||
"webSearch": "Web Search",
|
||||
"provider": "Provider",
|
||||
"searchType": "Search Type",
|
||||
"maxResults": "Max Results",
|
||||
"filters": "Filters",
|
||||
"country": "Country",
|
||||
"language": "Language",
|
||||
"timeRange": "Time Range",
|
||||
"includeDomains": "Include Domains",
|
||||
"excludeDomains": "Exclude Domains",
|
||||
"safeSearch": "Safe Search",
|
||||
"formatted": "Formatted",
|
||||
"rawJson": "JSON",
|
||||
"cacheMiss": "cache miss",
|
||||
"cacheHit": "cache hit",
|
||||
"latency": "Latency",
|
||||
"cost": "Cost",
|
||||
"results": "Results",
|
||||
"rerank": "Rerank",
|
||||
"rerankModel": "Rerank Model",
|
||||
"positionDelta": "Position Change",
|
||||
"emptyState": "Send a search query to see results",
|
||||
"safeSearchOff": "Off",
|
||||
"safeSearchModerate": "Moderate",
|
||||
"safeSearchStrict": "Strict",
|
||||
"queryPlaceholder": "Enter search query...",
|
||||
"providerAuto": "auto (cheapest)",
|
||||
"searchTypeWeb": "web",
|
||||
"searchTypeNews": "news",
|
||||
"optionAny": "any",
|
||||
"timeRangeDay": "Past day",
|
||||
"timeRangeWeek": "Past week",
|
||||
"timeRangeMonth": "Past month",
|
||||
"timeRangeYear": "Past year",
|
||||
"domainPlaceholder": "example.com",
|
||||
"requestTimedOut": "Request timed out ({seconds}s)",
|
||||
"networkError": "Network error"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,7 +56,9 @@
|
||||
"warning": "Warnung",
|
||||
"note": "Hinweis",
|
||||
"free": "Kostenlos",
|
||||
"skipToContent": "Zum Inhalt springen"
|
||||
"skipToContent": "Zum Inhalt springen",
|
||||
"maintenanceServerIssues": "Server is experiencing issues. Some features may be unavailable.",
|
||||
"maintenanceServerUnreachable": "Server is unreachable. Reconnecting..."
|
||||
},
|
||||
"sidebar": {
|
||||
"home": "Zuhause",
|
||||
@@ -103,7 +105,9 @@
|
||||
"endpoints": "Endpunkte",
|
||||
"playground": "Spielwiese",
|
||||
"agents": "Agenten",
|
||||
"cliToolsShort": "Werkzeuge"
|
||||
"cliToolsShort": "Werkzeuge",
|
||||
"autoCombo": "Auto Combo",
|
||||
"searchTools": "Search Tools"
|
||||
},
|
||||
"themesPage": {
|
||||
"title": "Themen",
|
||||
@@ -268,7 +272,28 @@
|
||||
"permissionsTitle": "Berechtigungen: {name}",
|
||||
"allowAllDesc": "Mit diesem Schlüssel kann auf alle verfügbaren Modelle zugegriffen werden.",
|
||||
"restrictDesc": "Dieser Schlüssel kann auf {selectedCount} von {totalModels} Modellen zugreifen.",
|
||||
"selectedCount": "{count} ausgewählt"
|
||||
"selectedCount": "{count} ausgewählt",
|
||||
"autoResolve": "Auto-Resolve",
|
||||
"autoResolveDesc": "Auto-resolve ambiguous model names to native provider for this API key.",
|
||||
"keyActive": "Key Active",
|
||||
"keyActiveDesc": "Enable or disable this API key. Disabled keys are immediately rejected with 403.",
|
||||
"accessSchedule": "Access Schedule",
|
||||
"accessScheduleDesc": "Restrict access to specific hours and days of the week.",
|
||||
"scheduleFrom": "From",
|
||||
"scheduleUntil": "Until",
|
||||
"scheduleDays": "Days",
|
||||
"scheduleTimezone": "Timezone",
|
||||
"scheduleTimezoneHint": "Use IANA timezone names, e.g. America/New_York, Europe/Berlin",
|
||||
"scheduleActive": "Schedule",
|
||||
"disabled": "Disabled",
|
||||
"daySun": "Sun",
|
||||
"dayMon": "Mon",
|
||||
"dayTue": "Tue",
|
||||
"dayWed": "Wed",
|
||||
"dayThu": "Thu",
|
||||
"dayFri": "Fri",
|
||||
"daySat": "Sat",
|
||||
"keyOnlyAvailableAtCreation": "Full key available only at creation time — copy it when you first create the key"
|
||||
},
|
||||
"auditLog": {
|
||||
"title": "Audit-Protokoll",
|
||||
@@ -548,6 +573,27 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"mitmHowWorksDesc": "{toolName} sends requests to its provider endpoint. MITM intercepts and redirects them to OmniRoute.",
|
||||
"mitmStep1": "1. Start MITM to route requests through OmniRoute.",
|
||||
"mitmStep2Prefix": "2. Add",
|
||||
"mitmStep2Suffix": "to your hosts file as 127.0.0.1.",
|
||||
"mitmStep3": "3. Open {toolName} and requests will be proxied.",
|
||||
"whenToUseLabel": "Wann zu verwenden",
|
||||
"openToolDocs": "Tool-Dokumente öffnen",
|
||||
"toolUseCases": {
|
||||
"claude": "Verwenden Sie Claude Code, wenn Sie starke Planungsworkflows und lange Umgestaltungen mehrerer Dateien wünschen.",
|
||||
"codex": "Verwenden Sie es, wenn Ihr Team auf OpenAI Codex CLI-Flows und profilbasierte Authentifizierung standardisiert ist.",
|
||||
"droid": "Verwenden Sie ihn, wenn Sie einen kompakten Terminalagenten benötigen, der sich auf schnelle Codierung und Befehlsausführungsschleifen konzentriert.",
|
||||
"openclaw": "Verwenden Sie diese Option, wenn Sie einen Codierungsagenten im Open-Claw-Stil wünschen, der jedoch über OmniRoute-Richtlinien weitergeleitet wird.",
|
||||
"cline": "Verwenden Sie diese Option, wenn Sie Codierungsagenten in Editoren konfigurieren und eine geführte Einrichtung mit OmniRoute-Modellen wünschen.",
|
||||
"kilo": "Verwenden Sie diese Option, wenn Ihr Arbeitsablauf auf Kilo-Code-Befehle und schnelle iterative Bearbeitungen angewiesen ist.",
|
||||
"cursor": "Verwenden Sie diese Option, wenn Sie in Cursor codieren und benutzerdefinierte OpenAI-kompatible Modelle über OmniRoute benötigen.",
|
||||
"continue": "Verwenden Sie diese Option, wenn Sie Continue in IDEs ausführen und eine portable JSON-basierte Anbieterkonfiguration benötigen.",
|
||||
"opencode": "Verwenden Sie diese Option, wenn Sie terminalnative Agentenausführungen und Skriptautomatisierung über OpenCode bevorzugen.",
|
||||
"kiro": "Zur Verwendung bei der Integration von Kiro und der zentralen Steuerung des Modellroutings über OmniRoute.",
|
||||
"antigravity": "Wird verwendet, wenn Antigravity/Kiro-Verkehr über MITM abgefangen und an OmniRoute weitergeleitet werden muss.",
|
||||
"copilot": "Verwenden Sie diese Option, wenn Sie eine UX im Copilot-Chat-Stil wünschen und gleichzeitig OmniRoute-Schlüssel und Routing-Regeln durchsetzen möchten."
|
||||
}
|
||||
},
|
||||
"combos": {
|
||||
@@ -864,7 +910,12 @@
|
||||
"a2aQuickStartStep2": "Senden Sie JSON-RPC-Anfragen an `POST /a2a` mit `message/send` oder `message/stream`.",
|
||||
"a2aQuickStartStep3": "Verfolgen und steuern Sie Aufgaben mit `tasks/get` und `tasks/cancel`.",
|
||||
"completionsLegacy": "Completions (Legacy)",
|
||||
"completionsLegacyDesc": "Legacy OpenAI text completions — accepts both prompt string and messages array format"
|
||||
"completionsLegacyDesc": "Legacy OpenAI text completions — accepts both prompt string and messages array format",
|
||||
"categorySearch": "Search & Discovery",
|
||||
"webSearch": "Web Search",
|
||||
"webSearchDesc": "Unified web search across multiple providers with automatic failover and caching",
|
||||
"searchProvider": "Search Provider",
|
||||
"searchProviderDesc": "This provider is used for web search via POST /v1/search. No model configuration needed — search providers are ready to use once an API key is connected."
|
||||
},
|
||||
"mcpDashboard": {
|
||||
"loading": "MCP-Dashboard wird geladen...",
|
||||
@@ -1054,7 +1105,13 @@
|
||||
"resetAllTitle": "Setzen Sie alle Leistungsschalter in den fehlerfreien Zustand zurück",
|
||||
"resetting": "Zurücksetzen...",
|
||||
"resetAll": "Alles zurücksetzen",
|
||||
"until": "Bis {time}"
|
||||
"until": "Bis {time}",
|
||||
"activeProviders": "{count} active",
|
||||
"monitoredProviders": "{count} monitored",
|
||||
"configuredProvidersLabel": "Im Dashboard konfiguriert",
|
||||
"configuredProvidersHint": "Anbieter mit in /dashboard/providers gespeicherten Anmeldeinformationen, unabhängig vom Laufzeitstatus.",
|
||||
"activeProvidersHint": "Konfigurierte Anbieter, die derzeit für Routing-Anfragen aktiviert sind.",
|
||||
"monitoredProvidersHint": "Anbieter, die derzeit von Leistungsschalter-Zustandsmonitoren überwacht werden."
|
||||
},
|
||||
"limits": {
|
||||
"title": "Limits und Quoten",
|
||||
@@ -1396,7 +1453,31 @@
|
||||
"chatPathHint": "Custom chat path for providers with non-standard APIs (e.g. /v4/chat/completions)",
|
||||
"modelsPathLabel": "Models Endpoint Path",
|
||||
"modelsPathPlaceholder": "/models",
|
||||
"modelsPathHint": "Custom models path for validation (e.g. /v4/models)"
|
||||
"modelsPathHint": "Custom models path for validation (e.g. /v4/models)",
|
||||
"builtInModels": "Built-in models",
|
||||
"builtInModelsHint": "Registry models for this provider. Use the pencil to set compatibility options.",
|
||||
"normalizeToolCallIdLabel": "Normalize tool call IDs to 9 characters (e.g. Mistral)",
|
||||
"preserveDeveloperRoleLabel": "Keep OpenAI Responses developer role (do not map to system)",
|
||||
"compatAdjustmentsTitle": "Compatibility",
|
||||
"compatButtonLabel": "Compatibility",
|
||||
"compatToolIdShort": "Tool ID 9",
|
||||
"compatDeveloperShort": "Developer role",
|
||||
"compatDoNotPreserveDeveloper": "Do not preserve developer role",
|
||||
"compatBadgeNoPreserve": "No preserve",
|
||||
"compatProtocolLabel": "Client request protocol",
|
||||
"compatProtocolHint": "These options apply when OmniRoute detects this request shape (OpenAI Chat, Responses API, or Anthropic Messages).",
|
||||
"compatProtocolOpenAI": "OpenAI Chat Completions",
|
||||
"compatProtocolOpenAIResponses": "OpenAI Responses API",
|
||||
"compatProtocolClaude": "Anthropic Messages",
|
||||
"tokenRefreshed": "Token refreshed successfully",
|
||||
"tokenRefreshFailed": "Token refresh failed",
|
||||
"compatBadgeUpstreamHeaders": "Headers",
|
||||
"compatUpstreamAddRow": "Add header",
|
||||
"compatUpstreamHeaderName": "Header name",
|
||||
"compatUpstreamHeaderValue": "Value",
|
||||
"compatUpstreamHeadersHint": "High-privilege setting — same trust level as editing provider API credentials; only trusted admins should use it. Merged after OmniRoute adds auth from the provider API key. If a custom header uses the same name as an existing one (e.g. Authorization), your value fully replaces the auto-generated header (including the Bearer token) — the upstream only sees what you typed, not the key from settings. Misconfiguration can cause 401 or broken upstream auth. One row per header (e.g. extra Authentication for some gateways). Hover or focus the value to preview. Saves on blur, outside click, or closing this panel.",
|
||||
"compatUpstreamHeadersLabel": "Extra upstream headers",
|
||||
"compatUpstreamRemoveRow": "Remove row"
|
||||
},
|
||||
"settings": {
|
||||
"title": "Einstellungen",
|
||||
@@ -1799,7 +1880,13 @@
|
||||
"cliFingerprintDesc": "Match native CLI binary signatures when proxying requests. Reorders headers and body fields to look identical to the official CLI tools. Your proxy IP is preserved.",
|
||||
"cliFingerprintEnabled": "{count} provider(s) with CLI fingerprint active",
|
||||
"enableFingerprintTitle": "Enable fingerprint for {provider}",
|
||||
"disableFingerprintTitle": "Disable fingerprint for {provider}"
|
||||
"disableFingerprintTitle": "Disable fingerprint for {provider}",
|
||||
"routingAdvancedGuideTitle": "Erweiterte Routenführung",
|
||||
"routingAdvancedGuideHint1": "Verwenden Sie Fill First für vorhersehbare Priorität, Round Robin für Fairness und P2C für Latenzstabilität.",
|
||||
"routingAdvancedGuideHint2": "Wenn sich die Qualität/Kosten der Anbieter unterscheiden, beginnen Sie mit „Cost Opt“ für Hintergrundarbeit und „Least Used“ für ausgewogene Abnutzung.",
|
||||
"comboDefaultsGuideTitle": "So optimieren Sie die Combo-Standardeinstellungen",
|
||||
"comboDefaultsGuideHint1": "Halten Sie die Wiederholungsversuche bei Datenflüssen mit geringer Latenz gering. Erhöhen Sie das Timeout nur für Aufgaben mit langer Generierung.",
|
||||
"comboDefaultsGuideHint2": "Verwenden Sie Anbieterüberschreibungen, wenn ein Anbieter ein anderes Timeout-/Wiederholungsverhalten als die globalen Standardwerte benötigt."
|
||||
},
|
||||
"translator": {
|
||||
"title": "Übersetzer",
|
||||
@@ -2216,7 +2303,8 @@
|
||||
"orRemovePasswordHashField": "oder entfernen Sie das Feld „passwordHash“.",
|
||||
"restartServerWithNewPassword": "Starten Sie den Server neu – er verwendet das neue Passwort",
|
||||
"backToLogin": "Zurück zum Anmelden",
|
||||
"forgotPassword": "Passwort vergessen?"
|
||||
"forgotPassword": "Passwort vergessen?",
|
||||
"defaultPasswordHint": "Default password: 123456 (unless INITIAL_PASSWORD was set)"
|
||||
},
|
||||
"landing": {
|
||||
"brandName": "OmniRoute",
|
||||
@@ -2414,7 +2502,16 @@
|
||||
"troubleshootingCodexFamily": "Behalten Sie für Modelle der GitHub Codex-Familie das Modell bei gh/codex-model; Der Router wählt /responses automatisch aus.",
|
||||
"troubleshootingTestConnection": "Verwenden Sie Dashboard > Anbieter > Verbindung testen, bevor Sie Tests mit IDEs oder externen Clients durchführen.",
|
||||
"troubleshootingCircuitBreaker": "Wenn ein Anbieter anzeigt, dass der Leistungsschalter geöffnet ist, warten Sie auf die Abklingzeit oder schauen Sie auf der Seite „Zustand“ nach, um Einzelheiten zu erfahren.",
|
||||
"troubleshootingOAuth": "Führen Sie bei OAuth-Anbietern eine erneute Authentifizierung durch, wenn die Token ablaufen. Überprüfen Sie die Statusanzeige der Anbieterkarte."
|
||||
"troubleshootingOAuth": "Führen Sie bei OAuth-Anbietern eine erneute Authentifizierung durch, wenn die Token ablaufen. Überprüfen Sie die Statusanzeige der Anbieterkarte.",
|
||||
"managementApiReference": "Management API Reference",
|
||||
"managementApiDescription": "Automation endpoints for proxy registry, scope assignments, and legacy proxy migration.",
|
||||
"mgmtProxiesListNote": "List saved proxy registry items (supports pagination).",
|
||||
"mgmtProxiesCreateNote": "Create a reusable proxy item in the registry.",
|
||||
"mgmtProxiesHealthNote": "Get 24h/rolling health metrics per saved proxy from proxy logs.",
|
||||
"mgmtProxiesBulkAssignNote": "Assign or clear one proxy across many scope IDs in one request.",
|
||||
"mgmtAssignmentsListNote": "List proxy assignments by scope, scope_id, or proxy_id.",
|
||||
"mgmtAssignmentsUpdateNote": "Assign or clear proxy for global/provider/account/combo scope.",
|
||||
"mgmtLegacyMigrationNote": "Import legacy proxyConfig maps into registry assignments."
|
||||
},
|
||||
"legal": {
|
||||
"privacyPolicy": "Datenschutzrichtlinie",
|
||||
@@ -2503,7 +2600,21 @@
|
||||
"binaryName": "Binary Name",
|
||||
"versionCommand": "Version Command",
|
||||
"spawnArgs": "Spawn Args",
|
||||
"addAgent": "Add Agent"
|
||||
"addAgent": "Add Agent",
|
||||
"scanning": "Scanning system for CLI agents...",
|
||||
"opencodeIntegration": "OpenCode Integration",
|
||||
"opencodeDetected": "opencode {version} detected",
|
||||
"opencodeDesc": "Generate a ready-to-use {configFile} with your OmniRoute base URL and all available models — drop it in your project root and run {command}.",
|
||||
"downloadConfig": "Download {file}",
|
||||
"downloaded": "Downloaded!",
|
||||
"setupGuideTitle": "Setup guide",
|
||||
"openCliTools": "Open CLI Tools",
|
||||
"setupGuideDetectCliTitle": "Detect installed CLIs",
|
||||
"setupGuideDetectCliDesc": "Click Refresh after installing or updating a CLI so OmniRoute can rescan binaries and versions.",
|
||||
"setupGuideCustomAgentTitle": "Register custom binary",
|
||||
"setupGuideCustomAgentDesc": "Use Add Custom Agent when your CLI is not in the built-in list. Provide binary name and version command.",
|
||||
"setupGuideCommandMissingTitle": "Fix 'command not found'",
|
||||
"setupGuideCommandMissingDesc": "Ensure the CLI command exists in PATH, open a new terminal session, and rerun Refresh."
|
||||
},
|
||||
"autoCombo": {
|
||||
"title": "Auto-Combo Engine",
|
||||
@@ -2527,5 +2638,56 @@
|
||||
"modePackCostSaver": "Cost Saver",
|
||||
"modePackQualityFirst": "Quality First",
|
||||
"modePackOfflineFriendly": "Offline Friendly"
|
||||
},
|
||||
"search": {
|
||||
"searchQuery": "Search Query",
|
||||
"searchResults": "Search Results",
|
||||
"cachedResult": "Cached",
|
||||
"searchCost": "Cost",
|
||||
"searchTools": "Search Tools",
|
||||
"searchToolsDesc": "Advanced search testing with provider comparison",
|
||||
"compareProviders": "Compare Providers",
|
||||
"rerankResults": "Rerank Results",
|
||||
"searchHistory": "Search History",
|
||||
"urlOverlap": "URL Overlap",
|
||||
"noSearchProviders": "No search providers configured. Add providers in Settings.",
|
||||
"noRerankModels": "No rerank model available",
|
||||
"webSearch": "Web Search",
|
||||
"provider": "Provider",
|
||||
"searchType": "Search Type",
|
||||
"maxResults": "Max Results",
|
||||
"filters": "Filters",
|
||||
"country": "Country",
|
||||
"language": "Language",
|
||||
"timeRange": "Time Range",
|
||||
"includeDomains": "Include Domains",
|
||||
"excludeDomains": "Exclude Domains",
|
||||
"safeSearch": "Safe Search",
|
||||
"formatted": "Formatted",
|
||||
"rawJson": "JSON",
|
||||
"cacheMiss": "cache miss",
|
||||
"cacheHit": "cache hit",
|
||||
"latency": "Latency",
|
||||
"cost": "Cost",
|
||||
"results": "Results",
|
||||
"rerank": "Rerank",
|
||||
"rerankModel": "Rerank Model",
|
||||
"positionDelta": "Position Change",
|
||||
"emptyState": "Send a search query to see results",
|
||||
"safeSearchOff": "Off",
|
||||
"safeSearchModerate": "Moderate",
|
||||
"safeSearchStrict": "Strict",
|
||||
"queryPlaceholder": "Enter search query...",
|
||||
"providerAuto": "auto (cheapest)",
|
||||
"searchTypeWeb": "web",
|
||||
"searchTypeNews": "news",
|
||||
"optionAny": "any",
|
||||
"timeRangeDay": "Past day",
|
||||
"timeRangeWeek": "Past week",
|
||||
"timeRangeMonth": "Past month",
|
||||
"timeRangeYear": "Past year",
|
||||
"domainPlaceholder": "example.com",
|
||||
"requestTimedOut": "Request timed out ({seconds}s)",
|
||||
"networkError": "Network error"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,7 +56,9 @@
|
||||
"warning": "Warning",
|
||||
"note": "Note",
|
||||
"free": "Free",
|
||||
"skipToContent": "Skip to content"
|
||||
"skipToContent": "Skip to content",
|
||||
"maintenanceServerIssues": "Server is experiencing issues. Some features may be unavailable.",
|
||||
"maintenanceServerUnreachable": "Server is unreachable. Reconnecting..."
|
||||
},
|
||||
"sidebar": {
|
||||
"home": "Home",
|
||||
@@ -354,6 +356,21 @@
|
||||
"includeDomains": "Include Domains",
|
||||
"excludeDomains": "Exclude Domains",
|
||||
"safeSearch": "Safe Search",
|
||||
"safeSearchOff": "Off",
|
||||
"safeSearchModerate": "Moderate",
|
||||
"safeSearchStrict": "Strict",
|
||||
"queryPlaceholder": "Enter search query...",
|
||||
"providerAuto": "auto (cheapest)",
|
||||
"searchTypeWeb": "web",
|
||||
"searchTypeNews": "news",
|
||||
"optionAny": "any",
|
||||
"timeRangeDay": "Past day",
|
||||
"timeRangeWeek": "Past week",
|
||||
"timeRangeMonth": "Past month",
|
||||
"timeRangeYear": "Past year",
|
||||
"domainPlaceholder": "example.com",
|
||||
"requestTimedOut": "Request timed out ({seconds}s)",
|
||||
"networkError": "Network error",
|
||||
"formatted": "Formatted",
|
||||
"rawJson": "JSON",
|
||||
"cacheMiss": "cache miss",
|
||||
@@ -515,6 +532,22 @@
|
||||
"openClawManualConfiguration": "Open Claw - Manual Configuration",
|
||||
"clineManualConfiguration": "Cline Manual Configuration",
|
||||
"kiloManualConfiguration": "Kilo Code Manual Configuration",
|
||||
"whenToUseLabel": "When to use",
|
||||
"openToolDocs": "Open tool docs",
|
||||
"toolUseCases": {
|
||||
"claude": "Use when you want strong planning workflows and long multi-file refactors with Claude Code.",
|
||||
"codex": "Use when your team is standardized on OpenAI Codex CLI flows and profile-based auth.",
|
||||
"droid": "Use when you need a lightweight terminal agent focused on fast coding and command execution loops.",
|
||||
"openclaw": "Use when you want an Open Claw style coding agent but routed through OmniRoute policies.",
|
||||
"cline": "Use when you configure coding agents inside editors and want guided setup with OmniRoute models.",
|
||||
"kilo": "Use when your workflow depends on Kilo Code commands and fast iterative edits.",
|
||||
"cursor": "Use when coding in Cursor and you need custom OpenAI-compatible models through OmniRoute.",
|
||||
"continue": "Use when running Continue in IDEs and you need portable JSON-based provider configuration.",
|
||||
"opencode": "Use when you prefer terminal-native agent runs and scripted automation via OpenCode.",
|
||||
"kiro": "Use when integrating Kiro and controlling model routing centrally from OmniRoute.",
|
||||
"antigravity": "Use when Antigravity/Kiro traffic must be intercepted through MITM and routed to OmniRoute.",
|
||||
"copilot": "Use when you want Copilot chat style UX while enforcing OmniRoute keys and routing rules."
|
||||
},
|
||||
"toolDescriptions": {
|
||||
"antigravity": "Google Antigravity IDE with MITM",
|
||||
"claude": "Anthropic Claude Code CLI",
|
||||
@@ -1117,6 +1150,12 @@
|
||||
"issuesLabel": "Issues Detected",
|
||||
"operational": "Operational",
|
||||
"providers": "Providers",
|
||||
"configuredProvidersLabel": "Configured in dashboard",
|
||||
"configuredProvidersHint": "Providers with credentials saved in /dashboard/providers, regardless of runtime state.",
|
||||
"activeProviders": "{count} active",
|
||||
"activeProvidersHint": "Configured providers currently enabled for routing requests.",
|
||||
"monitoredProviders": "{count} monitored",
|
||||
"monitoredProvidersHint": "Providers currently tracked by circuit-breaker health monitors.",
|
||||
"healthyCount": "{count} healthy",
|
||||
"nodeVersion": "Node {version}",
|
||||
"failures": "{count} failure",
|
||||
@@ -1443,6 +1482,13 @@
|
||||
"compatProtocolOpenAI": "OpenAI Chat Completions",
|
||||
"compatProtocolOpenAIResponses": "OpenAI Responses API",
|
||||
"compatProtocolClaude": "Anthropic Messages",
|
||||
"compatUpstreamHeadersLabel": "Extra upstream headers",
|
||||
"compatUpstreamHeadersHint": "High-privilege setting — same trust level as editing provider API credentials; only trusted admins should use it. Merged after OmniRoute adds auth from the provider API key. If a custom header uses the same name as an existing one (e.g. Authorization), your value fully replaces the auto-generated header (including the Bearer token) — the upstream only sees what you typed, not the key from settings. Misconfiguration can cause 401 or broken upstream auth. One row per header (e.g. extra Authentication for some gateways). Hover or focus the value to preview. Saves on blur, outside click, or closing this panel.",
|
||||
"compatUpstreamHeaderName": "Header name",
|
||||
"compatUpstreamHeaderValue": "Value",
|
||||
"compatUpstreamAddRow": "Add header",
|
||||
"compatUpstreamRemoveRow": "Remove row",
|
||||
"compatBadgeUpstreamHeaders": "Headers",
|
||||
"modelId": "Model ID",
|
||||
"customModelPlaceholder": "e.g. gpt-4.5-turbo",
|
||||
"loading": "Loading...",
|
||||
@@ -1645,6 +1691,9 @@
|
||||
"enableFingerprintTitle": "Enable fingerprint for {provider}",
|
||||
"disableFingerprintTitle": "Disable fingerprint for {provider}",
|
||||
"routingStrategy": "Routing Strategy",
|
||||
"routingAdvancedGuideTitle": "Advanced routing guidance",
|
||||
"routingAdvancedGuideHint1": "Use Fill First for predictable priority, Round Robin for fairness, and P2C for latency resilience.",
|
||||
"routingAdvancedGuideHint2": "If providers vary in quality/cost, start with Cost Opt for background work and Least Used for balanced wear.",
|
||||
"fillFirst": "Fill First",
|
||||
"fillFirstDesc": "Use accounts in priority order",
|
||||
"roundRobin": "Round Robin",
|
||||
@@ -1739,6 +1788,9 @@
|
||||
"fillModelAndProviders": "Please fill model name and providers",
|
||||
"addAtLeastOneProvider": "Add at least one provider",
|
||||
"comboDefaultsTitle": "Combo Defaults",
|
||||
"comboDefaultsGuideTitle": "How to tune combo defaults",
|
||||
"comboDefaultsGuideHint1": "Keep retries low in low-latency flows; increase timeout only for long generation tasks.",
|
||||
"comboDefaultsGuideHint2": "Use provider overrides when one provider needs different timeout/retry behavior than global defaults.",
|
||||
"globalComboConfig": "Global combo configuration",
|
||||
"defaultStrategy": "Default Strategy",
|
||||
"defaultStrategyDesc": "Applied to new combos without explicit strategy",
|
||||
@@ -2599,7 +2651,21 @@
|
||||
"binaryName": "Binary Name",
|
||||
"versionCommand": "Version Command",
|
||||
"spawnArgs": "Spawn Args",
|
||||
"addAgent": "Add Agent"
|
||||
"addAgent": "Add Agent",
|
||||
"scanning": "Scanning system for CLI agents...",
|
||||
"opencodeIntegration": "OpenCode Integration",
|
||||
"opencodeDetected": "opencode {version} detected",
|
||||
"opencodeDesc": "Generate a ready-to-use {configFile} with your OmniRoute base URL and all available models — drop it in your project root and run {command}.",
|
||||
"downloadConfig": "Download {file}",
|
||||
"downloaded": "Downloaded!",
|
||||
"setupGuideTitle": "Setup guide",
|
||||
"openCliTools": "Open CLI Tools",
|
||||
"setupGuideDetectCliTitle": "Detect installed CLIs",
|
||||
"setupGuideDetectCliDesc": "Click Refresh after installing or updating a CLI so OmniRoute can rescan binaries and versions.",
|
||||
"setupGuideCustomAgentTitle": "Register custom binary",
|
||||
"setupGuideCustomAgentDesc": "Use Add Custom Agent when your CLI is not in the built-in list. Provide binary name and version command.",
|
||||
"setupGuideCommandMissingTitle": "Fix 'command not found'",
|
||||
"setupGuideCommandMissingDesc": "Ensure the CLI command exists in PATH, open a new terminal session, and rerun Refresh."
|
||||
},
|
||||
"autoCombo": {
|
||||
"title": "Auto-Combo Engine",
|
||||
|
||||
@@ -56,7 +56,9 @@
|
||||
"warning": "Advertencia",
|
||||
"note": "Nota",
|
||||
"free": "Gratis",
|
||||
"skipToContent": "Saltar al contenido"
|
||||
"skipToContent": "Saltar al contenido",
|
||||
"maintenanceServerIssues": "Server is experiencing issues. Some features may be unavailable.",
|
||||
"maintenanceServerUnreachable": "Server is unreachable. Reconnecting..."
|
||||
},
|
||||
"sidebar": {
|
||||
"home": "Inicio",
|
||||
@@ -103,7 +105,9 @@
|
||||
"endpoints": "Endpoints",
|
||||
"playground": "Playground",
|
||||
"agents": "Agentes",
|
||||
"cliToolsShort": "Herramientas"
|
||||
"cliToolsShort": "Herramientas",
|
||||
"autoCombo": "Auto Combo",
|
||||
"searchTools": "Search Tools"
|
||||
},
|
||||
"themesPage": {
|
||||
"title": "Temas",
|
||||
@@ -268,7 +272,28 @@
|
||||
"permissionsTitle": "Permisos: {name}",
|
||||
"allowAllDesc": "Esta clave puede acceder a todos los modelos disponibles.",
|
||||
"restrictDesc": "Esta clave puede acceder a {selectedCount} de {totalModels} modelos.",
|
||||
"selectedCount": "{count} seleccionado"
|
||||
"selectedCount": "{count} seleccionado",
|
||||
"autoResolve": "Auto-Resolve",
|
||||
"autoResolveDesc": "Auto-resolve ambiguous model names to native provider for this API key.",
|
||||
"keyActive": "Key Active",
|
||||
"keyActiveDesc": "Enable or disable this API key. Disabled keys are immediately rejected with 403.",
|
||||
"accessSchedule": "Access Schedule",
|
||||
"accessScheduleDesc": "Restrict access to specific hours and days of the week.",
|
||||
"scheduleFrom": "From",
|
||||
"scheduleUntil": "Until",
|
||||
"scheduleDays": "Days",
|
||||
"scheduleTimezone": "Timezone",
|
||||
"scheduleTimezoneHint": "Use IANA timezone names, e.g. America/New_York, Europe/Berlin",
|
||||
"scheduleActive": "Schedule",
|
||||
"disabled": "Disabled",
|
||||
"daySun": "Sun",
|
||||
"dayMon": "Mon",
|
||||
"dayTue": "Tue",
|
||||
"dayWed": "Wed",
|
||||
"dayThu": "Thu",
|
||||
"dayFri": "Fri",
|
||||
"daySat": "Sat",
|
||||
"keyOnlyAvailableAtCreation": "Full key available only at creation time — copy it when you first create the key"
|
||||
},
|
||||
"auditLog": {
|
||||
"title": "Registro de auditoría",
|
||||
@@ -548,6 +573,27 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"mitmHowWorksDesc": "{toolName} sends requests to its provider endpoint. MITM intercepts and redirects them to OmniRoute.",
|
||||
"mitmStep1": "1. Start MITM to route requests through OmniRoute.",
|
||||
"mitmStep2Prefix": "2. Add",
|
||||
"mitmStep2Suffix": "to your hosts file as 127.0.0.1.",
|
||||
"mitmStep3": "3. Open {toolName} and requests will be proxied.",
|
||||
"whenToUseLabel": "cuando usar",
|
||||
"openToolDocs": "Abrir documentos de herramientas",
|
||||
"toolUseCases": {
|
||||
"claude": "Úselo cuando desee flujos de trabajo de planificación sólidos y refactorizaciones largas de varios archivos con Claude Code.",
|
||||
"codex": "Úselo cuando su equipo esté estandarizado en los flujos CLI de OpenAI Codex y la autenticación basada en perfiles.",
|
||||
"droid": "Úselo cuando necesite un agente terminal liviano enfocado en codificación rápida y bucles de ejecución de comandos.",
|
||||
"openclaw": "Úselo cuando desee un agente de codificación estilo Open Claw pero enrutado a través de políticas de OmniRoute.",
|
||||
"cline": "Úselo cuando configure agentes de codificación dentro de editores y desee una configuración guiada con modelos OmniRoute.",
|
||||
"kilo": "Úselo cuando su flujo de trabajo dependa de los comandos de Kilo Code y de ediciones iterativas rápidas.",
|
||||
"cursor": "Úselo cuando codifique en Cursor y necesite modelos personalizados compatibles con OpenAI a través de OmniRoute.",
|
||||
"continue": "Úselo cuando ejecute Continuar en IDE y necesite una configuración de proveedor portátil basada en JSON.",
|
||||
"opencode": "Úselo cuando prefiera ejecuciones de agentes nativos de terminal y automatización con scripts a través de OpenCode.",
|
||||
"kiro": "Utilícelo al integrar Kiro y controlar el enrutamiento de modelos de forma centralizada desde OmniRoute.",
|
||||
"antigravity": "Úselo cuando el tráfico de Antigravity/Kiro debe interceptarse a través de MITM y enrutarse a OmniRoute.",
|
||||
"copilot": "Úselo cuando desee una experiencia de usuario estilo chat Copilot mientras aplica las claves de OmniRoute y las reglas de enrutamiento."
|
||||
}
|
||||
},
|
||||
"combos": {
|
||||
@@ -864,7 +910,12 @@
|
||||
"a2aQuickStartStep2": "Envíe solicitudes JSON-RPC a `POST /a2a` usando `message/send` o `message/stream`.",
|
||||
"a2aQuickStartStep3": "Realice un seguimiento y controle las tareas utilizando `tasks/get` y `tasks/cancel`.",
|
||||
"completionsLegacy": "Completions (Legacy)",
|
||||
"completionsLegacyDesc": "Legacy OpenAI text completions — accepts both prompt string and messages array format"
|
||||
"completionsLegacyDesc": "Legacy OpenAI text completions — accepts both prompt string and messages array format",
|
||||
"categorySearch": "Search & Discovery",
|
||||
"webSearch": "Web Search",
|
||||
"webSearchDesc": "Unified web search across multiple providers with automatic failover and caching",
|
||||
"searchProvider": "Search Provider",
|
||||
"searchProviderDesc": "This provider is used for web search via POST /v1/search. No model configuration needed — search providers are ready to use once an API key is connected."
|
||||
},
|
||||
"mcpDashboard": {
|
||||
"loading": "Cargando el panel de MCP...",
|
||||
@@ -1054,7 +1105,13 @@
|
||||
"resetAllTitle": "Restablezca todos los disyuntores al estado saludable",
|
||||
"resetting": "Restableciendo...",
|
||||
"resetAll": "Restablecer todo",
|
||||
"until": "Hasta {time}"
|
||||
"until": "Hasta {time}",
|
||||
"activeProviders": "{count} active",
|
||||
"monitoredProviders": "{count} monitored",
|
||||
"configuredProvidersLabel": "Configurado en el tablero",
|
||||
"configuredProvidersHint": "Proveedores con credenciales guardadas en /dashboard/providers, independientemente del estado de ejecución.",
|
||||
"activeProvidersHint": "Proveedores configurados actualmente habilitados para enrutar solicitudes.",
|
||||
"monitoredProvidersHint": "Proveedores actualmente rastreados por monitores de estado de interruptores."
|
||||
},
|
||||
"limits": {
|
||||
"title": "Límites y cuotas",
|
||||
@@ -1396,7 +1453,31 @@
|
||||
"chatPathHint": "Custom chat path for providers with non-standard APIs (e.g. /v4/chat/completions)",
|
||||
"modelsPathLabel": "Models Endpoint Path",
|
||||
"modelsPathPlaceholder": "/models",
|
||||
"modelsPathHint": "Custom models path for validation (e.g. /v4/models)"
|
||||
"modelsPathHint": "Custom models path for validation (e.g. /v4/models)",
|
||||
"builtInModels": "Built-in models",
|
||||
"builtInModelsHint": "Registry models for this provider. Use the pencil to set compatibility options.",
|
||||
"normalizeToolCallIdLabel": "Normalize tool call IDs to 9 characters (e.g. Mistral)",
|
||||
"preserveDeveloperRoleLabel": "Keep OpenAI Responses developer role (do not map to system)",
|
||||
"compatAdjustmentsTitle": "Compatibility",
|
||||
"compatButtonLabel": "Compatibility",
|
||||
"compatToolIdShort": "Tool ID 9",
|
||||
"compatDeveloperShort": "Developer role",
|
||||
"compatDoNotPreserveDeveloper": "Do not preserve developer role",
|
||||
"compatBadgeNoPreserve": "No preserve",
|
||||
"compatProtocolLabel": "Client request protocol",
|
||||
"compatProtocolHint": "These options apply when OmniRoute detects this request shape (OpenAI Chat, Responses API, or Anthropic Messages).",
|
||||
"compatProtocolOpenAI": "OpenAI Chat Completions",
|
||||
"compatProtocolOpenAIResponses": "OpenAI Responses API",
|
||||
"compatProtocolClaude": "Anthropic Messages",
|
||||
"tokenRefreshed": "Token refreshed successfully",
|
||||
"tokenRefreshFailed": "Token refresh failed",
|
||||
"compatBadgeUpstreamHeaders": "Headers",
|
||||
"compatUpstreamAddRow": "Add header",
|
||||
"compatUpstreamHeaderName": "Header name",
|
||||
"compatUpstreamHeaderValue": "Value",
|
||||
"compatUpstreamHeadersHint": "High-privilege setting — same trust level as editing provider API credentials; only trusted admins should use it. Merged after OmniRoute adds auth from the provider API key. If a custom header uses the same name as an existing one (e.g. Authorization), your value fully replaces the auto-generated header (including the Bearer token) — the upstream only sees what you typed, not the key from settings. Misconfiguration can cause 401 or broken upstream auth. One row per header (e.g. extra Authentication for some gateways). Hover or focus the value to preview. Saves on blur, outside click, or closing this panel.",
|
||||
"compatUpstreamHeadersLabel": "Extra upstream headers",
|
||||
"compatUpstreamRemoveRow": "Remove row"
|
||||
},
|
||||
"settings": {
|
||||
"title": "Configuración",
|
||||
@@ -1799,7 +1880,13 @@
|
||||
"cliFingerprintDesc": "Match native CLI binary signatures when proxying requests. Reorders headers and body fields to look identical to the official CLI tools. Your proxy IP is preserved.",
|
||||
"cliFingerprintEnabled": "{count} provider(s) with CLI fingerprint active",
|
||||
"enableFingerprintTitle": "Enable fingerprint for {provider}",
|
||||
"disableFingerprintTitle": "Disable fingerprint for {provider}"
|
||||
"disableFingerprintTitle": "Disable fingerprint for {provider}",
|
||||
"routingAdvancedGuideTitle": "Guía de ruta avanzada",
|
||||
"routingAdvancedGuideHint1": "Utilice Fill First para una prioridad predecible, Round Robin para equidad y P2C para resistencia a la latencia.",
|
||||
"routingAdvancedGuideHint2": "Si los proveedores varían en calidad/costo, comience con Opción de costo para trabajo en segundo plano y Menos usado para desgaste equilibrado.",
|
||||
"comboDefaultsGuideTitle": "Cómo ajustar los valores predeterminados del combo",
|
||||
"comboDefaultsGuideHint1": "Mantenga bajos los reintentos en flujos de baja latencia; aumente el tiempo de espera solo para tareas de larga generación.",
|
||||
"comboDefaultsGuideHint2": "Utilice anulaciones de proveedores cuando un proveedor necesite un comportamiento de tiempo de espera/reintento diferente al de los valores predeterminados globales."
|
||||
},
|
||||
"translator": {
|
||||
"title": "Traductor",
|
||||
@@ -2216,7 +2303,8 @@
|
||||
"orRemovePasswordHashField": "o eliminar el campo contraseñaHash",
|
||||
"restartServerWithNewPassword": "Reinicia el servidor; usará la nueva contraseña",
|
||||
"backToLogin": "Volver a iniciar sesión",
|
||||
"forgotPassword": "¿Olvidaste tu contraseña?"
|
||||
"forgotPassword": "¿Olvidaste tu contraseña?",
|
||||
"defaultPasswordHint": "Default password: 123456 (unless INITIAL_PASSWORD was set)"
|
||||
},
|
||||
"landing": {
|
||||
"brandName": "OmniRuta",
|
||||
@@ -2414,7 +2502,16 @@
|
||||
"troubleshootingCodexFamily": "Para los modelos de la familia GitHub Codex, mantenga el modelo como gh/codex-model; El enrutador selecciona/respuestas automáticamente.",
|
||||
"troubleshootingTestConnection": "Utilice Panel > Proveedores > Probar conexión antes de realizar pruebas desde IDE o clientes externos.",
|
||||
"troubleshootingCircuitBreaker": "Si un proveedor muestra el disyuntor abierto, espere el tiempo de reutilización o consulte la página de Salud para obtener más detalles.",
|
||||
"troubleshootingOAuth": "Para los proveedores de OAuth, vuelva a autenticarse si los tokens caducan. Verifique el indicador de estado de la tarjeta del proveedor."
|
||||
"troubleshootingOAuth": "Para los proveedores de OAuth, vuelva a autenticarse si los tokens caducan. Verifique el indicador de estado de la tarjeta del proveedor.",
|
||||
"managementApiReference": "Management API Reference",
|
||||
"managementApiDescription": "Automation endpoints for proxy registry, scope assignments, and legacy proxy migration.",
|
||||
"mgmtProxiesListNote": "List saved proxy registry items (supports pagination).",
|
||||
"mgmtProxiesCreateNote": "Create a reusable proxy item in the registry.",
|
||||
"mgmtProxiesHealthNote": "Get 24h/rolling health metrics per saved proxy from proxy logs.",
|
||||
"mgmtProxiesBulkAssignNote": "Assign or clear one proxy across many scope IDs in one request.",
|
||||
"mgmtAssignmentsListNote": "List proxy assignments by scope, scope_id, or proxy_id.",
|
||||
"mgmtAssignmentsUpdateNote": "Assign or clear proxy for global/provider/account/combo scope.",
|
||||
"mgmtLegacyMigrationNote": "Import legacy proxyConfig maps into registry assignments."
|
||||
},
|
||||
"legal": {
|
||||
"privacyPolicy": "Política de privacidad",
|
||||
@@ -2503,7 +2600,21 @@
|
||||
"binaryName": "Binary Name",
|
||||
"versionCommand": "Version Command",
|
||||
"spawnArgs": "Spawn Args",
|
||||
"addAgent": "Add Agent"
|
||||
"addAgent": "Add Agent",
|
||||
"scanning": "Scanning system for CLI agents...",
|
||||
"opencodeIntegration": "OpenCode Integration",
|
||||
"opencodeDetected": "opencode {version} detected",
|
||||
"opencodeDesc": "Generate a ready-to-use {configFile} with your OmniRoute base URL and all available models — drop it in your project root and run {command}.",
|
||||
"downloadConfig": "Download {file}",
|
||||
"downloaded": "Downloaded!",
|
||||
"setupGuideTitle": "Setup guide",
|
||||
"openCliTools": "Open CLI Tools",
|
||||
"setupGuideDetectCliTitle": "Detect installed CLIs",
|
||||
"setupGuideDetectCliDesc": "Click Refresh after installing or updating a CLI so OmniRoute can rescan binaries and versions.",
|
||||
"setupGuideCustomAgentTitle": "Register custom binary",
|
||||
"setupGuideCustomAgentDesc": "Use Add Custom Agent when your CLI is not in the built-in list. Provide binary name and version command.",
|
||||
"setupGuideCommandMissingTitle": "Fix 'command not found'",
|
||||
"setupGuideCommandMissingDesc": "Ensure the CLI command exists in PATH, open a new terminal session, and rerun Refresh."
|
||||
},
|
||||
"autoCombo": {
|
||||
"title": "Auto-Combo Engine",
|
||||
@@ -2527,5 +2638,56 @@
|
||||
"modePackCostSaver": "Cost Saver",
|
||||
"modePackQualityFirst": "Quality First",
|
||||
"modePackOfflineFriendly": "Offline Friendly"
|
||||
},
|
||||
"search": {
|
||||
"searchQuery": "Search Query",
|
||||
"searchResults": "Search Results",
|
||||
"cachedResult": "Cached",
|
||||
"searchCost": "Cost",
|
||||
"searchTools": "Search Tools",
|
||||
"searchToolsDesc": "Advanced search testing with provider comparison",
|
||||
"compareProviders": "Compare Providers",
|
||||
"rerankResults": "Rerank Results",
|
||||
"searchHistory": "Search History",
|
||||
"urlOverlap": "URL Overlap",
|
||||
"noSearchProviders": "No search providers configured. Add providers in Settings.",
|
||||
"noRerankModels": "No rerank model available",
|
||||
"webSearch": "Web Search",
|
||||
"provider": "Provider",
|
||||
"searchType": "Search Type",
|
||||
"maxResults": "Max Results",
|
||||
"filters": "Filters",
|
||||
"country": "Country",
|
||||
"language": "Language",
|
||||
"timeRange": "Time Range",
|
||||
"includeDomains": "Include Domains",
|
||||
"excludeDomains": "Exclude Domains",
|
||||
"safeSearch": "Safe Search",
|
||||
"formatted": "Formatted",
|
||||
"rawJson": "JSON",
|
||||
"cacheMiss": "cache miss",
|
||||
"cacheHit": "cache hit",
|
||||
"latency": "Latency",
|
||||
"cost": "Cost",
|
||||
"results": "Results",
|
||||
"rerank": "Rerank",
|
||||
"rerankModel": "Rerank Model",
|
||||
"positionDelta": "Position Change",
|
||||
"emptyState": "Send a search query to see results",
|
||||
"safeSearchOff": "Off",
|
||||
"safeSearchModerate": "Moderate",
|
||||
"safeSearchStrict": "Strict",
|
||||
"queryPlaceholder": "Enter search query...",
|
||||
"providerAuto": "auto (cheapest)",
|
||||
"searchTypeWeb": "web",
|
||||
"searchTypeNews": "news",
|
||||
"optionAny": "any",
|
||||
"timeRangeDay": "Past day",
|
||||
"timeRangeWeek": "Past week",
|
||||
"timeRangeMonth": "Past month",
|
||||
"timeRangeYear": "Past year",
|
||||
"domainPlaceholder": "example.com",
|
||||
"requestTimedOut": "Request timed out ({seconds}s)",
|
||||
"networkError": "Network error"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,7 +56,9 @@
|
||||
"warning": "Varoitus",
|
||||
"note": "Huom",
|
||||
"free": "Ilmainen",
|
||||
"skipToContent": "Siirry sisältöön"
|
||||
"skipToContent": "Siirry sisältöön",
|
||||
"maintenanceServerIssues": "Server is experiencing issues. Some features may be unavailable.",
|
||||
"maintenanceServerUnreachable": "Server is unreachable. Reconnecting..."
|
||||
},
|
||||
"sidebar": {
|
||||
"home": "Kotiin",
|
||||
@@ -103,7 +105,9 @@
|
||||
"endpoints": "Päätepisteet",
|
||||
"playground": "Leikkipaikka",
|
||||
"agents": "Agentit",
|
||||
"cliToolsShort": "Työkalut"
|
||||
"cliToolsShort": "Työkalut",
|
||||
"autoCombo": "Auto Combo",
|
||||
"searchTools": "Search Tools"
|
||||
},
|
||||
"themesPage": {
|
||||
"title": "Teemat",
|
||||
@@ -268,7 +272,28 @@
|
||||
"permissionsTitle": "Luvat: {name}",
|
||||
"allowAllDesc": "Tällä avaimella pääsee kaikkiin saatavilla oleviin malleihin.",
|
||||
"restrictDesc": "Tällä avaimella voi käyttää {selectedCount}/{totalModels} mallia.",
|
||||
"selectedCount": "{count} valittu"
|
||||
"selectedCount": "{count} valittu",
|
||||
"autoResolve": "Auto-Resolve",
|
||||
"autoResolveDesc": "Auto-resolve ambiguous model names to native provider for this API key.",
|
||||
"keyActive": "Key Active",
|
||||
"keyActiveDesc": "Enable or disable this API key. Disabled keys are immediately rejected with 403.",
|
||||
"accessSchedule": "Access Schedule",
|
||||
"accessScheduleDesc": "Restrict access to specific hours and days of the week.",
|
||||
"scheduleFrom": "From",
|
||||
"scheduleUntil": "Until",
|
||||
"scheduleDays": "Days",
|
||||
"scheduleTimezone": "Timezone",
|
||||
"scheduleTimezoneHint": "Use IANA timezone names, e.g. America/New_York, Europe/Berlin",
|
||||
"scheduleActive": "Schedule",
|
||||
"disabled": "Disabled",
|
||||
"daySun": "Sun",
|
||||
"dayMon": "Mon",
|
||||
"dayTue": "Tue",
|
||||
"dayWed": "Wed",
|
||||
"dayThu": "Thu",
|
||||
"dayFri": "Fri",
|
||||
"daySat": "Sat",
|
||||
"keyOnlyAvailableAtCreation": "Full key available only at creation time — copy it when you first create the key"
|
||||
},
|
||||
"auditLog": {
|
||||
"title": "Tarkastusloki",
|
||||
@@ -548,6 +573,27 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"mitmHowWorksDesc": "{toolName} sends requests to its provider endpoint. MITM intercepts and redirects them to OmniRoute.",
|
||||
"mitmStep1": "1. Start MITM to route requests through OmniRoute.",
|
||||
"mitmStep2Prefix": "2. Add",
|
||||
"mitmStep2Suffix": "to your hosts file as 127.0.0.1.",
|
||||
"mitmStep3": "3. Open {toolName} and requests will be proxied.",
|
||||
"whenToUseLabel": "Milloin käyttää",
|
||||
"openToolDocs": "Avaa työkaludokumentit",
|
||||
"toolUseCases": {
|
||||
"claude": "Käytä, kun haluat vahvoja suunnittelutyönkulkuja ja pitkiä monitiedostoisia refraktoreita Claude Coden avulla.",
|
||||
"codex": "Käytä, kun tiimisi on standardoitu OpenAI Codex CLI -virtojen ja profiilipohjaisen todennuksen kanssa.",
|
||||
"droid": "Käytä, kun tarvitset kevyen pääteagentin, joka keskittyy nopeaan koodaukseen ja komentojen suoritussilmukoihin.",
|
||||
"openclaw": "Käytä, kun haluat Open Claw -tyyppisen koodausagentin, mutta reititetyn OmniRoute-käytäntöjen kautta.",
|
||||
"cline": "Käytä, kun määrität koodausagentteja editorien sisällä ja haluat ohjatun asennuksen OmniRoute-malleilla.",
|
||||
"kilo": "Käytä, kun työnkulkusi riippuu Kilo Code -komennoista ja nopeista iteratiivisista muokkauksista.",
|
||||
"cursor": "Käytä koodattaessa Cursorissa ja tarvitset mukautettuja OpenAI-yhteensopivia malleja OmniRouten kautta.",
|
||||
"continue": "Käytä, kun käytät Continuea IDE:issä ja tarvitset kannettavan JSON-pohjaisen palveluntarjoajan määrityksen.",
|
||||
"opencode": "Käytä, kun haluat käyttää päätealustaisia agentteja ja komentosarjottua automaatiota OpenCoden kautta.",
|
||||
"kiro": "Käytä integroitaessa Kiroa ja ohjattaessa mallin reititystä keskitetysti OmniRoutesta.",
|
||||
"antigravity": "Käytä, kun Antigravity/Kiro-liikenne on siepattava MITM:n kautta ja ohjattava OmniRouteen.",
|
||||
"copilot": "Käytä, kun haluat Copilot-chat-tyylisen UX:n ja pakota OmniRoute-avaimia ja reitityssääntöjä."
|
||||
}
|
||||
},
|
||||
"combos": {
|
||||
@@ -864,7 +910,12 @@
|
||||
"a2aQuickStartStep2": "Lähetä JSON-RPC-pyynnöt osoitteeseen `POST /a2a` käyttämällä `message/send` tai `message/stream`.",
|
||||
"a2aQuickStartStep3": "Seuraa ja ohjaa tehtäviä käyttämällä `tasks/get` ja `tasks/cancel`.",
|
||||
"completionsLegacy": "Completions (Legacy)",
|
||||
"completionsLegacyDesc": "Legacy OpenAI text completions — accepts both prompt string and messages array format"
|
||||
"completionsLegacyDesc": "Legacy OpenAI text completions — accepts both prompt string and messages array format",
|
||||
"categorySearch": "Search & Discovery",
|
||||
"webSearch": "Web Search",
|
||||
"webSearchDesc": "Unified web search across multiple providers with automatic failover and caching",
|
||||
"searchProvider": "Search Provider",
|
||||
"searchProviderDesc": "This provider is used for web search via POST /v1/search. No model configuration needed — search providers are ready to use once an API key is connected."
|
||||
},
|
||||
"mcpDashboard": {
|
||||
"loading": "Ladataan MCP-hallintapaneelia...",
|
||||
@@ -1054,7 +1105,13 @@
|
||||
"resetAllTitle": "Palauta kaikki katkaisijat terveeseen tilaan",
|
||||
"resetting": "Nollataan...",
|
||||
"resetAll": "Nollaa kaikki",
|
||||
"until": "{time} asti"
|
||||
"until": "{time} asti",
|
||||
"activeProviders": "{count} active",
|
||||
"monitoredProviders": "{count} monitored",
|
||||
"configuredProvidersLabel": "Konfiguroitu kojelaudassa",
|
||||
"configuredProvidersHint": "Palveluntarjoajat, joiden valtuustiedot on tallennettu kansioon /dashboard/providers, suorituksen tilasta riippumatta.",
|
||||
"activeProvidersHint": "Määritetyt palveluntarjoajat, jotka ovat tällä hetkellä käytössä reitityspyyntöjä varten.",
|
||||
"monitoredProvidersHint": "Palveluntarjoajat, joita tällä hetkellä seurataan katkaisijoiden kuntovalvojien avulla."
|
||||
},
|
||||
"limits": {
|
||||
"title": "Rajoitukset ja kiintiöt",
|
||||
@@ -1396,7 +1453,31 @@
|
||||
"chatPathHint": "Custom chat path for providers with non-standard APIs (e.g. /v4/chat/completions)",
|
||||
"modelsPathLabel": "Models Endpoint Path",
|
||||
"modelsPathPlaceholder": "/models",
|
||||
"modelsPathHint": "Custom models path for validation (e.g. /v4/models)"
|
||||
"modelsPathHint": "Custom models path for validation (e.g. /v4/models)",
|
||||
"builtInModels": "Built-in models",
|
||||
"builtInModelsHint": "Registry models for this provider. Use the pencil to set compatibility options.",
|
||||
"normalizeToolCallIdLabel": "Normalize tool call IDs to 9 characters (e.g. Mistral)",
|
||||
"preserveDeveloperRoleLabel": "Keep OpenAI Responses developer role (do not map to system)",
|
||||
"compatAdjustmentsTitle": "Compatibility",
|
||||
"compatButtonLabel": "Compatibility",
|
||||
"compatToolIdShort": "Tool ID 9",
|
||||
"compatDeveloperShort": "Developer role",
|
||||
"compatDoNotPreserveDeveloper": "Do not preserve developer role",
|
||||
"compatBadgeNoPreserve": "No preserve",
|
||||
"compatProtocolLabel": "Client request protocol",
|
||||
"compatProtocolHint": "These options apply when OmniRoute detects this request shape (OpenAI Chat, Responses API, or Anthropic Messages).",
|
||||
"compatProtocolOpenAI": "OpenAI Chat Completions",
|
||||
"compatProtocolOpenAIResponses": "OpenAI Responses API",
|
||||
"compatProtocolClaude": "Anthropic Messages",
|
||||
"tokenRefreshed": "Token refreshed successfully",
|
||||
"tokenRefreshFailed": "Token refresh failed",
|
||||
"compatBadgeUpstreamHeaders": "Headers",
|
||||
"compatUpstreamAddRow": "Add header",
|
||||
"compatUpstreamHeaderName": "Header name",
|
||||
"compatUpstreamHeaderValue": "Value",
|
||||
"compatUpstreamHeadersHint": "High-privilege setting — same trust level as editing provider API credentials; only trusted admins should use it. Merged after OmniRoute adds auth from the provider API key. If a custom header uses the same name as an existing one (e.g. Authorization), your value fully replaces the auto-generated header (including the Bearer token) — the upstream only sees what you typed, not the key from settings. Misconfiguration can cause 401 or broken upstream auth. One row per header (e.g. extra Authentication for some gateways). Hover or focus the value to preview. Saves on blur, outside click, or closing this panel.",
|
||||
"compatUpstreamHeadersLabel": "Extra upstream headers",
|
||||
"compatUpstreamRemoveRow": "Remove row"
|
||||
},
|
||||
"settings": {
|
||||
"title": "Asetukset",
|
||||
@@ -1799,7 +1880,13 @@
|
||||
"cliFingerprintDesc": "Match native CLI binary signatures when proxying requests. Reorders headers and body fields to look identical to the official CLI tools. Your proxy IP is preserved.",
|
||||
"cliFingerprintEnabled": "{count} provider(s) with CLI fingerprint active",
|
||||
"enableFingerprintTitle": "Enable fingerprint for {provider}",
|
||||
"disableFingerprintTitle": "Disable fingerprint for {provider}"
|
||||
"disableFingerprintTitle": "Disable fingerprint for {provider}",
|
||||
"routingAdvancedGuideTitle": "Edistynyt reititysopastus",
|
||||
"routingAdvancedGuideHint1": "Käytä Täytä ensin ennustettavaa prioriteettia varten, Round Robinia oikeudenmukaisuuden varmistamiseksi ja P2C:tä latenssin kestävyyteen.",
|
||||
"routingAdvancedGuideHint2": "Jos palveluntarjoajat vaihtelevat laadultaan/kustannuksiltaan, aloita Cost Opt -vaihtoehdolla taustatyössä ja Vähiten käytetyllä tasapainoiseen kulumiseen.",
|
||||
"comboDefaultsGuideTitle": "Kuinka virittää yhdistelmäoletusasetukset",
|
||||
"comboDefaultsGuideHint1": "Pidä uudelleenyritykset alhaisena matalan viiveen virroissa; lisää aikakatkaisua vain pitkiä sukupolvitehtäviä varten.",
|
||||
"comboDefaultsGuideHint2": "Käytä palveluntarjoajan ohituksia, kun yksi palveluntarjoaja tarvitsee erilaista aikakatkaisu-/uudelleenyritystoimintaa kuin yleiset oletusasetukset."
|
||||
},
|
||||
"translator": {
|
||||
"title": "Kääntäjä",
|
||||
@@ -2216,7 +2303,8 @@
|
||||
"orRemovePasswordHashField": "tai poista salasanaHash-kenttä",
|
||||
"restartServerWithNewPassword": "Käynnistä palvelin uudelleen - se käyttää uutta salasanaa",
|
||||
"backToLogin": "Takaisin kirjautumiseen",
|
||||
"forgotPassword": "Unohditko salasanan?"
|
||||
"forgotPassword": "Unohditko salasanan?",
|
||||
"defaultPasswordHint": "Default password: 123456 (unless INITIAL_PASSWORD was set)"
|
||||
},
|
||||
"landing": {
|
||||
"brandName": "OmniRoute",
|
||||
@@ -2414,7 +2502,16 @@
|
||||
"troubleshootingCodexFamily": "Säilytä GitHub Codex -perheen mallien mallina muodossa gh/codex-model; reititin valitsee / vastaa automaattisesti.",
|
||||
"troubleshootingTestConnection": "Käytä Dashboard > Providers > Test Connection ennen testaamista IDE:istä tai ulkoisista asiakkaista.",
|
||||
"troubleshootingCircuitBreaker": "Jos palveluntarjoaja näyttää katkaisijan auki, odota jäähtymistä tai katso lisätietoja Terveys-sivulta.",
|
||||
"troubleshootingOAuth": "OAuth-palveluntarjoajat todenna uudelleen, jos tunnukset vanhenevat. Tarkista palveluntarjoajan kortin tilailmaisin."
|
||||
"troubleshootingOAuth": "OAuth-palveluntarjoajat todenna uudelleen, jos tunnukset vanhenevat. Tarkista palveluntarjoajan kortin tilailmaisin.",
|
||||
"managementApiReference": "Management API Reference",
|
||||
"managementApiDescription": "Automation endpoints for proxy registry, scope assignments, and legacy proxy migration.",
|
||||
"mgmtProxiesListNote": "List saved proxy registry items (supports pagination).",
|
||||
"mgmtProxiesCreateNote": "Create a reusable proxy item in the registry.",
|
||||
"mgmtProxiesHealthNote": "Get 24h/rolling health metrics per saved proxy from proxy logs.",
|
||||
"mgmtProxiesBulkAssignNote": "Assign or clear one proxy across many scope IDs in one request.",
|
||||
"mgmtAssignmentsListNote": "List proxy assignments by scope, scope_id, or proxy_id.",
|
||||
"mgmtAssignmentsUpdateNote": "Assign or clear proxy for global/provider/account/combo scope.",
|
||||
"mgmtLegacyMigrationNote": "Import legacy proxyConfig maps into registry assignments."
|
||||
},
|
||||
"legal": {
|
||||
"privacyPolicy": "Tietosuojakäytäntö",
|
||||
@@ -2503,7 +2600,21 @@
|
||||
"binaryName": "Binary Name",
|
||||
"versionCommand": "Version Command",
|
||||
"spawnArgs": "Spawn Args",
|
||||
"addAgent": "Add Agent"
|
||||
"addAgent": "Add Agent",
|
||||
"scanning": "Scanning system for CLI agents...",
|
||||
"opencodeIntegration": "OpenCode Integration",
|
||||
"opencodeDetected": "opencode {version} detected",
|
||||
"opencodeDesc": "Generate a ready-to-use {configFile} with your OmniRoute base URL and all available models — drop it in your project root and run {command}.",
|
||||
"downloadConfig": "Download {file}",
|
||||
"downloaded": "Downloaded!",
|
||||
"setupGuideTitle": "Setup guide",
|
||||
"openCliTools": "Open CLI Tools",
|
||||
"setupGuideDetectCliTitle": "Detect installed CLIs",
|
||||
"setupGuideDetectCliDesc": "Click Refresh after installing or updating a CLI so OmniRoute can rescan binaries and versions.",
|
||||
"setupGuideCustomAgentTitle": "Register custom binary",
|
||||
"setupGuideCustomAgentDesc": "Use Add Custom Agent when your CLI is not in the built-in list. Provide binary name and version command.",
|
||||
"setupGuideCommandMissingTitle": "Fix 'command not found'",
|
||||
"setupGuideCommandMissingDesc": "Ensure the CLI command exists in PATH, open a new terminal session, and rerun Refresh."
|
||||
},
|
||||
"autoCombo": {
|
||||
"title": "Auto-Combo Engine",
|
||||
@@ -2527,5 +2638,56 @@
|
||||
"modePackCostSaver": "Cost Saver",
|
||||
"modePackQualityFirst": "Quality First",
|
||||
"modePackOfflineFriendly": "Offline Friendly"
|
||||
},
|
||||
"search": {
|
||||
"searchQuery": "Search Query",
|
||||
"searchResults": "Search Results",
|
||||
"cachedResult": "Cached",
|
||||
"searchCost": "Cost",
|
||||
"searchTools": "Search Tools",
|
||||
"searchToolsDesc": "Advanced search testing with provider comparison",
|
||||
"compareProviders": "Compare Providers",
|
||||
"rerankResults": "Rerank Results",
|
||||
"searchHistory": "Search History",
|
||||
"urlOverlap": "URL Overlap",
|
||||
"noSearchProviders": "No search providers configured. Add providers in Settings.",
|
||||
"noRerankModels": "No rerank model available",
|
||||
"webSearch": "Web Search",
|
||||
"provider": "Provider",
|
||||
"searchType": "Search Type",
|
||||
"maxResults": "Max Results",
|
||||
"filters": "Filters",
|
||||
"country": "Country",
|
||||
"language": "Language",
|
||||
"timeRange": "Time Range",
|
||||
"includeDomains": "Include Domains",
|
||||
"excludeDomains": "Exclude Domains",
|
||||
"safeSearch": "Safe Search",
|
||||
"formatted": "Formatted",
|
||||
"rawJson": "JSON",
|
||||
"cacheMiss": "cache miss",
|
||||
"cacheHit": "cache hit",
|
||||
"latency": "Latency",
|
||||
"cost": "Cost",
|
||||
"results": "Results",
|
||||
"rerank": "Rerank",
|
||||
"rerankModel": "Rerank Model",
|
||||
"positionDelta": "Position Change",
|
||||
"emptyState": "Send a search query to see results",
|
||||
"safeSearchOff": "Off",
|
||||
"safeSearchModerate": "Moderate",
|
||||
"safeSearchStrict": "Strict",
|
||||
"queryPlaceholder": "Enter search query...",
|
||||
"providerAuto": "auto (cheapest)",
|
||||
"searchTypeWeb": "web",
|
||||
"searchTypeNews": "news",
|
||||
"optionAny": "any",
|
||||
"timeRangeDay": "Past day",
|
||||
"timeRangeWeek": "Past week",
|
||||
"timeRangeMonth": "Past month",
|
||||
"timeRangeYear": "Past year",
|
||||
"domainPlaceholder": "example.com",
|
||||
"requestTimedOut": "Request timed out ({seconds}s)",
|
||||
"networkError": "Network error"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,7 +56,9 @@
|
||||
"warning": "Avertissement",
|
||||
"note": "Remarque",
|
||||
"free": "Gratuit",
|
||||
"skipToContent": "Passer au contenu"
|
||||
"skipToContent": "Passer au contenu",
|
||||
"maintenanceServerIssues": "Server is experiencing issues. Some features may be unavailable.",
|
||||
"maintenanceServerUnreachable": "Server is unreachable. Reconnecting..."
|
||||
},
|
||||
"sidebar": {
|
||||
"home": "Accueil",
|
||||
@@ -103,7 +105,9 @@
|
||||
"endpoints": "Points d'accès",
|
||||
"playground": "Playground",
|
||||
"agents": "Agents",
|
||||
"cliToolsShort": "Outils"
|
||||
"cliToolsShort": "Outils",
|
||||
"autoCombo": "Auto Combo",
|
||||
"searchTools": "Search Tools"
|
||||
},
|
||||
"themesPage": {
|
||||
"title": "Thèmes",
|
||||
@@ -268,7 +272,28 @@
|
||||
"permissionsTitle": "Autorisations : {name}",
|
||||
"allowAllDesc": "Cette clé peut accéder à tous les modèles disponibles.",
|
||||
"restrictDesc": "Cette clé peut accéder à {selectedCount} des modèles {totalModels}.",
|
||||
"selectedCount": "{count} sélectionné"
|
||||
"selectedCount": "{count} sélectionné",
|
||||
"autoResolve": "Auto-Resolve",
|
||||
"autoResolveDesc": "Auto-resolve ambiguous model names to native provider for this API key.",
|
||||
"keyActive": "Key Active",
|
||||
"keyActiveDesc": "Enable or disable this API key. Disabled keys are immediately rejected with 403.",
|
||||
"accessSchedule": "Access Schedule",
|
||||
"accessScheduleDesc": "Restrict access to specific hours and days of the week.",
|
||||
"scheduleFrom": "From",
|
||||
"scheduleUntil": "Until",
|
||||
"scheduleDays": "Days",
|
||||
"scheduleTimezone": "Timezone",
|
||||
"scheduleTimezoneHint": "Use IANA timezone names, e.g. America/New_York, Europe/Berlin",
|
||||
"scheduleActive": "Schedule",
|
||||
"disabled": "Disabled",
|
||||
"daySun": "Sun",
|
||||
"dayMon": "Mon",
|
||||
"dayTue": "Tue",
|
||||
"dayWed": "Wed",
|
||||
"dayThu": "Thu",
|
||||
"dayFri": "Fri",
|
||||
"daySat": "Sat",
|
||||
"keyOnlyAvailableAtCreation": "Full key available only at creation time — copy it when you first create the key"
|
||||
},
|
||||
"auditLog": {
|
||||
"title": "Journal d'audit",
|
||||
@@ -548,6 +573,27 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"mitmHowWorksDesc": "{toolName} sends requests to its provider endpoint. MITM intercepts and redirects them to OmniRoute.",
|
||||
"mitmStep1": "1. Start MITM to route requests through OmniRoute.",
|
||||
"mitmStep2Prefix": "2. Add",
|
||||
"mitmStep2Suffix": "to your hosts file as 127.0.0.1.",
|
||||
"mitmStep3": "3. Open {toolName} and requests will be proxied.",
|
||||
"whenToUseLabel": "Quand utiliser",
|
||||
"openToolDocs": "Ouvrir la documentation de l'outil",
|
||||
"toolUseCases": {
|
||||
"claude": "À utiliser lorsque vous souhaitez des flux de travail de planification solides et de longs refactors multi-fichiers avec Claude Code.",
|
||||
"codex": "À utiliser lorsque votre équipe est standardisée sur les flux CLI OpenAI Codex et l'authentification basée sur le profil.",
|
||||
"droid": "À utiliser lorsque vous avez besoin d'un agent de terminal léger axé sur des boucles de codage et d'exécution de commandes rapides.",
|
||||
"openclaw": "À utiliser lorsque vous souhaitez un agent de codage de style Open Claw mais acheminé via des stratégies OmniRoute.",
|
||||
"cline": "À utiliser lorsque vous configurez des agents de codage dans des éditeurs et que vous souhaitez une configuration guidée avec des modèles OmniRoute.",
|
||||
"kilo": "À utiliser lorsque votre flux de travail dépend des commandes Kilo Code et de modifications itératives rapides.",
|
||||
"cursor": "À utiliser lors du codage dans Cursor et vous avez besoin de modèles personnalisés compatibles OpenAI via OmniRoute.",
|
||||
"continue": "À utiliser lors de l'exécution de Continue dans les IDE et que vous avez besoin d'une configuration de fournisseur portable basée sur JSON.",
|
||||
"opencode": "À utiliser lorsque vous préférez les exécutions d'agents natifs du terminal et l'automatisation par script via OpenCode.",
|
||||
"kiro": "À utiliser lors de l'intégration de Kiro et du contrôle centralisé du routage de modèles à partir d'OmniRoute.",
|
||||
"antigravity": "À utiliser lorsque le trafic Antigravity/Kiro doit être intercepté via MITM et acheminé vers OmniRoute.",
|
||||
"copilot": "À utiliser lorsque vous souhaitez une UX de style chat Copilot tout en appliquant les clés OmniRoute et les règles de routage."
|
||||
}
|
||||
},
|
||||
"combos": {
|
||||
@@ -864,7 +910,12 @@
|
||||
"a2aQuickStartStep2": "Envoyez des requêtes JSON-RPC à `POST /a2a` en utilisant `message/send` ou `message/stream`.",
|
||||
"a2aQuickStartStep3": "Suivez et contrôlez les tâches à l’aide de `tasks/get` et `tasks/cancel`.",
|
||||
"completionsLegacy": "Completions (Legacy)",
|
||||
"completionsLegacyDesc": "Legacy OpenAI text completions — accepts both prompt string and messages array format"
|
||||
"completionsLegacyDesc": "Legacy OpenAI text completions — accepts both prompt string and messages array format",
|
||||
"categorySearch": "Search & Discovery",
|
||||
"webSearch": "Web Search",
|
||||
"webSearchDesc": "Unified web search across multiple providers with automatic failover and caching",
|
||||
"searchProvider": "Search Provider",
|
||||
"searchProviderDesc": "This provider is used for web search via POST /v1/search. No model configuration needed — search providers are ready to use once an API key is connected."
|
||||
},
|
||||
"mcpDashboard": {
|
||||
"loading": "Chargement du tableau de bord MCP...",
|
||||
@@ -1054,7 +1105,13 @@
|
||||
"resetAllTitle": "Réinitialisez tous les disjoncteurs à l’état sain",
|
||||
"resetting": "Réinitialisation...",
|
||||
"resetAll": "Tout réinitialiser",
|
||||
"until": "Jusqu'au {time}"
|
||||
"until": "Jusqu'au {time}",
|
||||
"activeProviders": "{count} active",
|
||||
"monitoredProviders": "{count} monitored",
|
||||
"configuredProvidersLabel": "Configuré dans le tableau de bord",
|
||||
"configuredProvidersHint": "Fournisseurs dont les informations d’identification sont enregistrées dans /dashboard/providers, quel que soit l’état d’exécution.",
|
||||
"activeProvidersHint": "Fournisseurs configurés actuellement activés pour les demandes de routage.",
|
||||
"monitoredProvidersHint": "Fournisseurs actuellement suivis par des moniteurs de santé des disjoncteurs."
|
||||
},
|
||||
"limits": {
|
||||
"title": "Limites et quotas",
|
||||
@@ -1396,7 +1453,31 @@
|
||||
"chatPathHint": "Custom chat path for providers with non-standard APIs (e.g. /v4/chat/completions)",
|
||||
"modelsPathLabel": "Models Endpoint Path",
|
||||
"modelsPathPlaceholder": "/models",
|
||||
"modelsPathHint": "Custom models path for validation (e.g. /v4/models)"
|
||||
"modelsPathHint": "Custom models path for validation (e.g. /v4/models)",
|
||||
"builtInModels": "Built-in models",
|
||||
"builtInModelsHint": "Registry models for this provider. Use the pencil to set compatibility options.",
|
||||
"normalizeToolCallIdLabel": "Normalize tool call IDs to 9 characters (e.g. Mistral)",
|
||||
"preserveDeveloperRoleLabel": "Keep OpenAI Responses developer role (do not map to system)",
|
||||
"compatAdjustmentsTitle": "Compatibility",
|
||||
"compatButtonLabel": "Compatibility",
|
||||
"compatToolIdShort": "Tool ID 9",
|
||||
"compatDeveloperShort": "Developer role",
|
||||
"compatDoNotPreserveDeveloper": "Do not preserve developer role",
|
||||
"compatBadgeNoPreserve": "No preserve",
|
||||
"compatProtocolLabel": "Client request protocol",
|
||||
"compatProtocolHint": "These options apply when OmniRoute detects this request shape (OpenAI Chat, Responses API, or Anthropic Messages).",
|
||||
"compatProtocolOpenAI": "OpenAI Chat Completions",
|
||||
"compatProtocolOpenAIResponses": "OpenAI Responses API",
|
||||
"compatProtocolClaude": "Anthropic Messages",
|
||||
"tokenRefreshed": "Token refreshed successfully",
|
||||
"tokenRefreshFailed": "Token refresh failed",
|
||||
"compatBadgeUpstreamHeaders": "Headers",
|
||||
"compatUpstreamAddRow": "Add header",
|
||||
"compatUpstreamHeaderName": "Header name",
|
||||
"compatUpstreamHeaderValue": "Value",
|
||||
"compatUpstreamHeadersHint": "High-privilege setting — same trust level as editing provider API credentials; only trusted admins should use it. Merged after OmniRoute adds auth from the provider API key. If a custom header uses the same name as an existing one (e.g. Authorization), your value fully replaces the auto-generated header (including the Bearer token) — the upstream only sees what you typed, not the key from settings. Misconfiguration can cause 401 or broken upstream auth. One row per header (e.g. extra Authentication for some gateways). Hover or focus the value to preview. Saves on blur, outside click, or closing this panel.",
|
||||
"compatUpstreamHeadersLabel": "Extra upstream headers",
|
||||
"compatUpstreamRemoveRow": "Remove row"
|
||||
},
|
||||
"settings": {
|
||||
"title": "Paramètres",
|
||||
@@ -1799,7 +1880,13 @@
|
||||
"cliFingerprintDesc": "Match native CLI binary signatures when proxying requests. Reorders headers and body fields to look identical to the official CLI tools. Your proxy IP is preserved.",
|
||||
"cliFingerprintEnabled": "{count} provider(s) with CLI fingerprint active",
|
||||
"enableFingerprintTitle": "Enable fingerprint for {provider}",
|
||||
"disableFingerprintTitle": "Disable fingerprint for {provider}"
|
||||
"disableFingerprintTitle": "Disable fingerprint for {provider}",
|
||||
"routingAdvancedGuideTitle": "Guidage d'itinéraire avancé",
|
||||
"routingAdvancedGuideHint1": "Utilisez Fill First pour une priorité prévisible, Round Robin pour l’équité et P2C pour la résilience en matière de latence.",
|
||||
"routingAdvancedGuideHint2": "Si les prestataires varient en termes de qualité/coût, commencez par Opter pour le coût pour le travail de fond et par Moins utilisé pour une usure équilibrée.",
|
||||
"comboDefaultsGuideTitle": "Comment régler les paramètres par défaut du combo",
|
||||
"comboDefaultsGuideHint1": "Maintenez un faible nombre de tentatives dans les flux à faible latence ; augmentez le délai d'attente uniquement pour les tâches de génération longue.",
|
||||
"comboDefaultsGuideHint2": "Utilisez les remplacements de fournisseur lorsqu'un fournisseur a besoin d'un comportement de délai d'attente/nouvelle tentative différent de celui des valeurs par défaut globales."
|
||||
},
|
||||
"translator": {
|
||||
"title": "Traducteur",
|
||||
@@ -2216,7 +2303,8 @@
|
||||
"orRemovePasswordHashField": "ou supprimez le champ passwordHash",
|
||||
"restartServerWithNewPassword": "Redémarrez le serveur - il utilisera le nouveau mot de passe",
|
||||
"backToLogin": "Retour à la connexion",
|
||||
"forgotPassword": "Mot de passe oublié ?"
|
||||
"forgotPassword": "Mot de passe oublié ?",
|
||||
"defaultPasswordHint": "Default password: 123456 (unless INITIAL_PASSWORD was set)"
|
||||
},
|
||||
"landing": {
|
||||
"brandName": "OmniRoute",
|
||||
@@ -2414,7 +2502,16 @@
|
||||
"troubleshootingCodexFamily": "Pour les modèles de la famille GitHub Codex, conservez le modèle sous la forme gh/codex-model ; Le routeur sélectionne /réponses automatiquement.",
|
||||
"troubleshootingTestConnection": "Utilisez Tableau de bord > Fournisseurs > Tester la connexion avant de tester à partir d'IDE ou de clients externes.",
|
||||
"troubleshootingCircuitBreaker": "Si un fournisseur indique que le disjoncteur est ouvert, attendez le temps de recharge ou consultez la page Santé pour plus de détails.",
|
||||
"troubleshootingOAuth": "Pour les fournisseurs OAuth, réauthentifiez-vous si les jetons expirent. Vérifiez l'indicateur d'état de la carte du fournisseur."
|
||||
"troubleshootingOAuth": "Pour les fournisseurs OAuth, réauthentifiez-vous si les jetons expirent. Vérifiez l'indicateur d'état de la carte du fournisseur.",
|
||||
"managementApiReference": "Management API Reference",
|
||||
"managementApiDescription": "Automation endpoints for proxy registry, scope assignments, and legacy proxy migration.",
|
||||
"mgmtProxiesListNote": "List saved proxy registry items (supports pagination).",
|
||||
"mgmtProxiesCreateNote": "Create a reusable proxy item in the registry.",
|
||||
"mgmtProxiesHealthNote": "Get 24h/rolling health metrics per saved proxy from proxy logs.",
|
||||
"mgmtProxiesBulkAssignNote": "Assign or clear one proxy across many scope IDs in one request.",
|
||||
"mgmtAssignmentsListNote": "List proxy assignments by scope, scope_id, or proxy_id.",
|
||||
"mgmtAssignmentsUpdateNote": "Assign or clear proxy for global/provider/account/combo scope.",
|
||||
"mgmtLegacyMigrationNote": "Import legacy proxyConfig maps into registry assignments."
|
||||
},
|
||||
"legal": {
|
||||
"privacyPolicy": "Politique de confidentialité",
|
||||
@@ -2503,7 +2600,21 @@
|
||||
"binaryName": "Binary Name",
|
||||
"versionCommand": "Version Command",
|
||||
"spawnArgs": "Spawn Args",
|
||||
"addAgent": "Add Agent"
|
||||
"addAgent": "Add Agent",
|
||||
"scanning": "Scanning system for CLI agents...",
|
||||
"opencodeIntegration": "OpenCode Integration",
|
||||
"opencodeDetected": "opencode {version} detected",
|
||||
"opencodeDesc": "Generate a ready-to-use {configFile} with your OmniRoute base URL and all available models — drop it in your project root and run {command}.",
|
||||
"downloadConfig": "Download {file}",
|
||||
"downloaded": "Downloaded!",
|
||||
"setupGuideTitle": "Setup guide",
|
||||
"openCliTools": "Open CLI Tools",
|
||||
"setupGuideDetectCliTitle": "Detect installed CLIs",
|
||||
"setupGuideDetectCliDesc": "Click Refresh after installing or updating a CLI so OmniRoute can rescan binaries and versions.",
|
||||
"setupGuideCustomAgentTitle": "Register custom binary",
|
||||
"setupGuideCustomAgentDesc": "Use Add Custom Agent when your CLI is not in the built-in list. Provide binary name and version command.",
|
||||
"setupGuideCommandMissingTitle": "Fix 'command not found'",
|
||||
"setupGuideCommandMissingDesc": "Ensure the CLI command exists in PATH, open a new terminal session, and rerun Refresh."
|
||||
},
|
||||
"autoCombo": {
|
||||
"title": "Auto-Combo Engine",
|
||||
@@ -2527,5 +2638,56 @@
|
||||
"modePackCostSaver": "Cost Saver",
|
||||
"modePackQualityFirst": "Quality First",
|
||||
"modePackOfflineFriendly": "Offline Friendly"
|
||||
},
|
||||
"search": {
|
||||
"searchQuery": "Search Query",
|
||||
"searchResults": "Search Results",
|
||||
"cachedResult": "Cached",
|
||||
"searchCost": "Cost",
|
||||
"searchTools": "Search Tools",
|
||||
"searchToolsDesc": "Advanced search testing with provider comparison",
|
||||
"compareProviders": "Compare Providers",
|
||||
"rerankResults": "Rerank Results",
|
||||
"searchHistory": "Search History",
|
||||
"urlOverlap": "URL Overlap",
|
||||
"noSearchProviders": "No search providers configured. Add providers in Settings.",
|
||||
"noRerankModels": "No rerank model available",
|
||||
"webSearch": "Web Search",
|
||||
"provider": "Provider",
|
||||
"searchType": "Search Type",
|
||||
"maxResults": "Max Results",
|
||||
"filters": "Filters",
|
||||
"country": "Country",
|
||||
"language": "Language",
|
||||
"timeRange": "Time Range",
|
||||
"includeDomains": "Include Domains",
|
||||
"excludeDomains": "Exclude Domains",
|
||||
"safeSearch": "Safe Search",
|
||||
"formatted": "Formatted",
|
||||
"rawJson": "JSON",
|
||||
"cacheMiss": "cache miss",
|
||||
"cacheHit": "cache hit",
|
||||
"latency": "Latency",
|
||||
"cost": "Cost",
|
||||
"results": "Results",
|
||||
"rerank": "Rerank",
|
||||
"rerankModel": "Rerank Model",
|
||||
"positionDelta": "Position Change",
|
||||
"emptyState": "Send a search query to see results",
|
||||
"safeSearchOff": "Off",
|
||||
"safeSearchModerate": "Moderate",
|
||||
"safeSearchStrict": "Strict",
|
||||
"queryPlaceholder": "Enter search query...",
|
||||
"providerAuto": "auto (cheapest)",
|
||||
"searchTypeWeb": "web",
|
||||
"searchTypeNews": "news",
|
||||
"optionAny": "any",
|
||||
"timeRangeDay": "Past day",
|
||||
"timeRangeWeek": "Past week",
|
||||
"timeRangeMonth": "Past month",
|
||||
"timeRangeYear": "Past year",
|
||||
"domainPlaceholder": "example.com",
|
||||
"requestTimedOut": "Request timed out ({seconds}s)",
|
||||
"networkError": "Network error"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,7 +56,9 @@
|
||||
"warning": "אזהרה",
|
||||
"note": "הערה",
|
||||
"free": "חינם",
|
||||
"skipToContent": "דלג לתוכן"
|
||||
"skipToContent": "דלג לתוכן",
|
||||
"maintenanceServerIssues": "Server is experiencing issues. Some features may be unavailable.",
|
||||
"maintenanceServerUnreachable": "Server is unreachable. Reconnecting..."
|
||||
},
|
||||
"sidebar": {
|
||||
"home": "בית",
|
||||
@@ -103,7 +105,9 @@
|
||||
"endpoints": "נקודות קצה",
|
||||
"playground": "Playground",
|
||||
"agents": "סוכנים",
|
||||
"cliToolsShort": "כלים"
|
||||
"cliToolsShort": "כלים",
|
||||
"autoCombo": "Auto Combo",
|
||||
"searchTools": "Search Tools"
|
||||
},
|
||||
"themesPage": {
|
||||
"title": "Themes",
|
||||
@@ -268,7 +272,28 @@
|
||||
"permissionsTitle": "הרשאות: {name}",
|
||||
"allowAllDesc": "מפתח זה יכול לגשת לכל הדגמים הזמינים.",
|
||||
"restrictDesc": "מפתח זה יכול לגשת ל-{selectedCount} מתוך דגמי {totalModels}.",
|
||||
"selectedCount": "{count} נבחר"
|
||||
"selectedCount": "{count} נבחר",
|
||||
"autoResolve": "Auto-Resolve",
|
||||
"autoResolveDesc": "Auto-resolve ambiguous model names to native provider for this API key.",
|
||||
"keyActive": "Key Active",
|
||||
"keyActiveDesc": "Enable or disable this API key. Disabled keys are immediately rejected with 403.",
|
||||
"accessSchedule": "Access Schedule",
|
||||
"accessScheduleDesc": "Restrict access to specific hours and days of the week.",
|
||||
"scheduleFrom": "From",
|
||||
"scheduleUntil": "Until",
|
||||
"scheduleDays": "Days",
|
||||
"scheduleTimezone": "Timezone",
|
||||
"scheduleTimezoneHint": "Use IANA timezone names, e.g. America/New_York, Europe/Berlin",
|
||||
"scheduleActive": "Schedule",
|
||||
"disabled": "Disabled",
|
||||
"daySun": "Sun",
|
||||
"dayMon": "Mon",
|
||||
"dayTue": "Tue",
|
||||
"dayWed": "Wed",
|
||||
"dayThu": "Thu",
|
||||
"dayFri": "Fri",
|
||||
"daySat": "Sat",
|
||||
"keyOnlyAvailableAtCreation": "Full key available only at creation time — copy it when you first create the key"
|
||||
},
|
||||
"auditLog": {
|
||||
"title": "יומן ביקורת",
|
||||
@@ -548,6 +573,27 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"mitmHowWorksDesc": "{toolName} sends requests to its provider endpoint. MITM intercepts and redirects them to OmniRoute.",
|
||||
"mitmStep1": "1. Start MITM to route requests through OmniRoute.",
|
||||
"mitmStep2Prefix": "2. Add",
|
||||
"mitmStep2Suffix": "to your hosts file as 127.0.0.1.",
|
||||
"mitmStep3": "3. Open {toolName} and requests will be proxied.",
|
||||
"whenToUseLabel": "מתי להשתמש",
|
||||
"openToolDocs": "פתח את מסמכי הכלים",
|
||||
"toolUseCases": {
|
||||
"claude": "השתמש כאשר אתה רוצה זרימות עבודה תכנון חזקות וריפקטורים ארוכים מרובי קבצים עם Claude Code.",
|
||||
"codex": "השתמש כאשר הצוות שלך סטנדרטי על זרימות OpenAI Codex CLI ואימות מבוסס פרופיל.",
|
||||
"droid": "השתמש כאשר אתה צריך סוכן מסוף קל משקל המתמקד בלולאות קידוד מהיר וביצוע פקודות.",
|
||||
"openclaw": "השתמש כאשר אתה רוצה סוכן קידוד בסגנון Open Claw אך מנותב דרך מדיניות OmniRoute.",
|
||||
"cline": "השתמש כאשר אתה מגדיר סוכני קידוד בתוך עורכים ורוצה הגדרה מודרכת עם דגמי OmniRoute.",
|
||||
"kilo": "השתמש כאשר זרימת העבודה שלך תלויה בפקודות Kilo Code ועריכות איטרטיביות מהירות.",
|
||||
"cursor": "השתמש בעת קידוד בסמן ואתה צריך מודלים מותאמים אישית תואמי OpenAI דרך OmniRoute.",
|
||||
"continue": "השתמש בעת הפעלת Continue ב-IDEs ואתה זקוק לתצורת ספק ניידת מבוססת JSON.",
|
||||
"opencode": "השתמש כאשר אתה מעדיף ריצות סוכנים מקוריים ומאוטומציה של סקריפטים באמצעות OpenCode.",
|
||||
"kiro": "השתמש בעת שילוב Kiro ושליטה בניתוב מודלים באופן מרכזי מ- OmniRoute.",
|
||||
"antigravity": "השתמש כאשר יש ליירט תעבורת Antigravity/Kiro דרך MITM ולנתב אל OmniRoute.",
|
||||
"copilot": "השתמש כאשר אתה רוצה UX בסגנון צ'אט Copilot תוך אכיפת מפתחות וכללי ניתוב OmniRoute."
|
||||
}
|
||||
},
|
||||
"combos": {
|
||||
@@ -864,7 +910,12 @@
|
||||
"a2aQuickStartStep2": "Send JSON-RPC requests to `POST /a2a` using `message/send` or `message/stream`.",
|
||||
"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"
|
||||
"completionsLegacyDesc": "Legacy OpenAI text completions — accepts both prompt string and messages array format",
|
||||
"categorySearch": "Search & Discovery",
|
||||
"webSearch": "Web Search",
|
||||
"webSearchDesc": "Unified web search across multiple providers with automatic failover and caching",
|
||||
"searchProvider": "Search Provider",
|
||||
"searchProviderDesc": "This provider is used for web search via POST /v1/search. No model configuration needed — search providers are ready to use once an API key is connected."
|
||||
},
|
||||
"mcpDashboard": {
|
||||
"loading": "Loading MCP dashboard...",
|
||||
@@ -1054,7 +1105,13 @@
|
||||
"resetAllTitle": "אפס את כל המפסקים למצב תקין",
|
||||
"resetting": "מאפס...",
|
||||
"resetAll": "אפס הכל",
|
||||
"until": "עד {time}"
|
||||
"until": "עד {time}",
|
||||
"activeProviders": "{count} active",
|
||||
"monitoredProviders": "{count} monitored",
|
||||
"configuredProvidersLabel": "מוגדר בלוח המחוונים",
|
||||
"configuredProvidersHint": "ספקים עם אישורים שנשמרו ב-/dashboard/ספקים, ללא קשר למצב זמן הריצה.",
|
||||
"activeProvidersHint": "ספקים מוגדרים מופעלים כעת עבור בקשות ניתוב.",
|
||||
"monitoredProvidersHint": "ספקים שנמצאים כעת במעקב על-ידי מסכי תקינות מפסקים."
|
||||
},
|
||||
"limits": {
|
||||
"title": "מגבלות ומכסות",
|
||||
@@ -1396,7 +1453,31 @@
|
||||
"chatPathHint": "Custom chat path for providers with non-standard APIs (e.g. /v4/chat/completions)",
|
||||
"modelsPathLabel": "Models Endpoint Path",
|
||||
"modelsPathPlaceholder": "/models",
|
||||
"modelsPathHint": "Custom models path for validation (e.g. /v4/models)"
|
||||
"modelsPathHint": "Custom models path for validation (e.g. /v4/models)",
|
||||
"builtInModels": "Built-in models",
|
||||
"builtInModelsHint": "Registry models for this provider. Use the pencil to set compatibility options.",
|
||||
"normalizeToolCallIdLabel": "Normalize tool call IDs to 9 characters (e.g. Mistral)",
|
||||
"preserveDeveloperRoleLabel": "Keep OpenAI Responses developer role (do not map to system)",
|
||||
"compatAdjustmentsTitle": "Compatibility",
|
||||
"compatButtonLabel": "Compatibility",
|
||||
"compatToolIdShort": "Tool ID 9",
|
||||
"compatDeveloperShort": "Developer role",
|
||||
"compatDoNotPreserveDeveloper": "Do not preserve developer role",
|
||||
"compatBadgeNoPreserve": "No preserve",
|
||||
"compatProtocolLabel": "Client request protocol",
|
||||
"compatProtocolHint": "These options apply when OmniRoute detects this request shape (OpenAI Chat, Responses API, or Anthropic Messages).",
|
||||
"compatProtocolOpenAI": "OpenAI Chat Completions",
|
||||
"compatProtocolOpenAIResponses": "OpenAI Responses API",
|
||||
"compatProtocolClaude": "Anthropic Messages",
|
||||
"tokenRefreshed": "Token refreshed successfully",
|
||||
"tokenRefreshFailed": "Token refresh failed",
|
||||
"compatBadgeUpstreamHeaders": "Headers",
|
||||
"compatUpstreamAddRow": "Add header",
|
||||
"compatUpstreamHeaderName": "Header name",
|
||||
"compatUpstreamHeaderValue": "Value",
|
||||
"compatUpstreamHeadersHint": "High-privilege setting — same trust level as editing provider API credentials; only trusted admins should use it. Merged after OmniRoute adds auth from the provider API key. If a custom header uses the same name as an existing one (e.g. Authorization), your value fully replaces the auto-generated header (including the Bearer token) — the upstream only sees what you typed, not the key from settings. Misconfiguration can cause 401 or broken upstream auth. One row per header (e.g. extra Authentication for some gateways). Hover or focus the value to preview. Saves on blur, outside click, or closing this panel.",
|
||||
"compatUpstreamHeadersLabel": "Extra upstream headers",
|
||||
"compatUpstreamRemoveRow": "Remove row"
|
||||
},
|
||||
"settings": {
|
||||
"title": "הגדרות",
|
||||
@@ -1799,7 +1880,13 @@
|
||||
"cliFingerprintDesc": "Match native CLI binary signatures when proxying requests. Reorders headers and body fields to look identical to the official CLI tools. Your proxy IP is preserved.",
|
||||
"cliFingerprintEnabled": "{count} provider(s) with CLI fingerprint active",
|
||||
"enableFingerprintTitle": "Enable fingerprint for {provider}",
|
||||
"disableFingerprintTitle": "Disable fingerprint for {provider}"
|
||||
"disableFingerprintTitle": "Disable fingerprint for {provider}",
|
||||
"routingAdvancedGuideTitle": "הנחיית ניתוב מתקדמת",
|
||||
"routingAdvancedGuideHint1": "השתמש ב-Fill First לקבלת עדיפות צפויה, ב-Round Robin להגינות, וב-P2C עבור חוסן חביון.",
|
||||
"routingAdvancedGuideHint2": "אם הספקים משתנים באיכות/עלות, התחל עם Cost Opt עבור עבודת רקע והפחות בשימוש עבור בלאי מאוזן.",
|
||||
"comboDefaultsGuideTitle": "כיצד לכוונן ברירות מחדל משולבות",
|
||||
"comboDefaultsGuideHint1": "שמור על ניסיונות חוזרים נמוכים בזרימות עם אחזור נמוך; להגדיל את הזמן הקצוב רק עבור משימות דור ארוך.",
|
||||
"comboDefaultsGuideHint2": "השתמש בעקיפות ספק כאשר ספק אחד זקוק להתנהגות שונה של זמן קצוב/ניסיון חוזר מאשר ברירות מחדל גלובליות."
|
||||
},
|
||||
"translator": {
|
||||
"title": "מתרגם",
|
||||
@@ -2216,7 +2303,8 @@
|
||||
"orRemovePasswordHashField": "או הסר את שדה passwordHash",
|
||||
"restartServerWithNewPassword": "הפעל מחדש את השרת - הוא ישתמש בסיסמה החדשה",
|
||||
"backToLogin": "חזרה לכניסה",
|
||||
"forgotPassword": "שכחת סיסמה?"
|
||||
"forgotPassword": "שכחת סיסמה?",
|
||||
"defaultPasswordHint": "Default password: 123456 (unless INITIAL_PASSWORD was set)"
|
||||
},
|
||||
"landing": {
|
||||
"brandName": "OmniRoute",
|
||||
@@ -2414,7 +2502,16 @@
|
||||
"troubleshootingCodexFamily": "עבור מודלים של משפחת GitHub Codex, שמור את הדגם בתור gh/codex-model; הנתב בוחר / תגובות באופן אוטומטי.",
|
||||
"troubleshootingTestConnection": "השתמש בלוח מחוונים > ספקים > בדוק חיבור לפני בדיקה מ-IDEs או לקוחות חיצוניים.",
|
||||
"troubleshootingCircuitBreaker": "אם ספק מציג מפסק פתוח, המתן להתקררות או בדוק את דף הבריאות לפרטים.",
|
||||
"troubleshootingOAuth": "עבור ספקי OAuth, בצע אימות מחדש אם פג תוקפם של אסימונים. בדוק את מחוון מצב כרטיס הספק."
|
||||
"troubleshootingOAuth": "עבור ספקי OAuth, בצע אימות מחדש אם פג תוקפם של אסימונים. בדוק את מחוון מצב כרטיס הספק.",
|
||||
"managementApiReference": "Management API Reference",
|
||||
"managementApiDescription": "Automation endpoints for proxy registry, scope assignments, and legacy proxy migration.",
|
||||
"mgmtProxiesListNote": "List saved proxy registry items (supports pagination).",
|
||||
"mgmtProxiesCreateNote": "Create a reusable proxy item in the registry.",
|
||||
"mgmtProxiesHealthNote": "Get 24h/rolling health metrics per saved proxy from proxy logs.",
|
||||
"mgmtProxiesBulkAssignNote": "Assign or clear one proxy across many scope IDs in one request.",
|
||||
"mgmtAssignmentsListNote": "List proxy assignments by scope, scope_id, or proxy_id.",
|
||||
"mgmtAssignmentsUpdateNote": "Assign or clear proxy for global/provider/account/combo scope.",
|
||||
"mgmtLegacyMigrationNote": "Import legacy proxyConfig maps into registry assignments."
|
||||
},
|
||||
"legal": {
|
||||
"privacyPolicy": "מדיניות פרטיות",
|
||||
@@ -2503,7 +2600,21 @@
|
||||
"binaryName": "Binary Name",
|
||||
"versionCommand": "Version Command",
|
||||
"spawnArgs": "Spawn Args",
|
||||
"addAgent": "Add Agent"
|
||||
"addAgent": "Add Agent",
|
||||
"scanning": "Scanning system for CLI agents...",
|
||||
"opencodeIntegration": "OpenCode Integration",
|
||||
"opencodeDetected": "opencode {version} detected",
|
||||
"opencodeDesc": "Generate a ready-to-use {configFile} with your OmniRoute base URL and all available models — drop it in your project root and run {command}.",
|
||||
"downloadConfig": "Download {file}",
|
||||
"downloaded": "Downloaded!",
|
||||
"setupGuideTitle": "Setup guide",
|
||||
"openCliTools": "Open CLI Tools",
|
||||
"setupGuideDetectCliTitle": "Detect installed CLIs",
|
||||
"setupGuideDetectCliDesc": "Click Refresh after installing or updating a CLI so OmniRoute can rescan binaries and versions.",
|
||||
"setupGuideCustomAgentTitle": "Register custom binary",
|
||||
"setupGuideCustomAgentDesc": "Use Add Custom Agent when your CLI is not in the built-in list. Provide binary name and version command.",
|
||||
"setupGuideCommandMissingTitle": "Fix 'command not found'",
|
||||
"setupGuideCommandMissingDesc": "Ensure the CLI command exists in PATH, open a new terminal session, and rerun Refresh."
|
||||
},
|
||||
"autoCombo": {
|
||||
"title": "Auto-Combo Engine",
|
||||
@@ -2527,5 +2638,56 @@
|
||||
"modePackCostSaver": "Cost Saver",
|
||||
"modePackQualityFirst": "Quality First",
|
||||
"modePackOfflineFriendly": "Offline Friendly"
|
||||
},
|
||||
"search": {
|
||||
"searchQuery": "Search Query",
|
||||
"searchResults": "Search Results",
|
||||
"cachedResult": "Cached",
|
||||
"searchCost": "Cost",
|
||||
"searchTools": "Search Tools",
|
||||
"searchToolsDesc": "Advanced search testing with provider comparison",
|
||||
"compareProviders": "Compare Providers",
|
||||
"rerankResults": "Rerank Results",
|
||||
"searchHistory": "Search History",
|
||||
"urlOverlap": "URL Overlap",
|
||||
"noSearchProviders": "No search providers configured. Add providers in Settings.",
|
||||
"noRerankModels": "No rerank model available",
|
||||
"webSearch": "Web Search",
|
||||
"provider": "Provider",
|
||||
"searchType": "Search Type",
|
||||
"maxResults": "Max Results",
|
||||
"filters": "Filters",
|
||||
"country": "Country",
|
||||
"language": "Language",
|
||||
"timeRange": "Time Range",
|
||||
"includeDomains": "Include Domains",
|
||||
"excludeDomains": "Exclude Domains",
|
||||
"safeSearch": "Safe Search",
|
||||
"formatted": "Formatted",
|
||||
"rawJson": "JSON",
|
||||
"cacheMiss": "cache miss",
|
||||
"cacheHit": "cache hit",
|
||||
"latency": "Latency",
|
||||
"cost": "Cost",
|
||||
"results": "Results",
|
||||
"rerank": "Rerank",
|
||||
"rerankModel": "Rerank Model",
|
||||
"positionDelta": "Position Change",
|
||||
"emptyState": "Send a search query to see results",
|
||||
"safeSearchOff": "Off",
|
||||
"safeSearchModerate": "Moderate",
|
||||
"safeSearchStrict": "Strict",
|
||||
"queryPlaceholder": "Enter search query...",
|
||||
"providerAuto": "auto (cheapest)",
|
||||
"searchTypeWeb": "web",
|
||||
"searchTypeNews": "news",
|
||||
"optionAny": "any",
|
||||
"timeRangeDay": "Past day",
|
||||
"timeRangeWeek": "Past week",
|
||||
"timeRangeMonth": "Past month",
|
||||
"timeRangeYear": "Past year",
|
||||
"domainPlaceholder": "example.com",
|
||||
"requestTimedOut": "Request timed out ({seconds}s)",
|
||||
"networkError": "Network error"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,7 +56,9 @@
|
||||
"warning": "Figyelmeztetés",
|
||||
"note": "Megjegyzés",
|
||||
"free": "Ingyenes",
|
||||
"skipToContent": "Ugrás a tartalomhoz"
|
||||
"skipToContent": "Ugrás a tartalomhoz",
|
||||
"maintenanceServerIssues": "Server is experiencing issues. Some features may be unavailable.",
|
||||
"maintenanceServerUnreachable": "Server is unreachable. Reconnecting..."
|
||||
},
|
||||
"sidebar": {
|
||||
"home": "Otthon",
|
||||
@@ -103,7 +105,9 @@
|
||||
"endpoints": "Végpontok",
|
||||
"playground": "Játszótér",
|
||||
"agents": "Ügynökök",
|
||||
"cliToolsShort": "Eszközök"
|
||||
"cliToolsShort": "Eszközök",
|
||||
"autoCombo": "Auto Combo",
|
||||
"searchTools": "Search Tools"
|
||||
},
|
||||
"themesPage": {
|
||||
"title": "Témák",
|
||||
@@ -268,7 +272,28 @@
|
||||
"permissionsTitle": "Engedélyek: {name}",
|
||||
"allowAllDesc": "Ezzel a gombbal minden elérhető modell elérhető.",
|
||||
"restrictDesc": "Ezzel a kulccsal {selectedCount}/{totalModels} modell érhető el.",
|
||||
"selectedCount": "{count} kiválasztva"
|
||||
"selectedCount": "{count} kiválasztva",
|
||||
"autoResolve": "Auto-Resolve",
|
||||
"autoResolveDesc": "Auto-resolve ambiguous model names to native provider for this API key.",
|
||||
"keyActive": "Key Active",
|
||||
"keyActiveDesc": "Enable or disable this API key. Disabled keys are immediately rejected with 403.",
|
||||
"accessSchedule": "Access Schedule",
|
||||
"accessScheduleDesc": "Restrict access to specific hours and days of the week.",
|
||||
"scheduleFrom": "From",
|
||||
"scheduleUntil": "Until",
|
||||
"scheduleDays": "Days",
|
||||
"scheduleTimezone": "Timezone",
|
||||
"scheduleTimezoneHint": "Use IANA timezone names, e.g. America/New_York, Europe/Berlin",
|
||||
"scheduleActive": "Schedule",
|
||||
"disabled": "Disabled",
|
||||
"daySun": "Sun",
|
||||
"dayMon": "Mon",
|
||||
"dayTue": "Tue",
|
||||
"dayWed": "Wed",
|
||||
"dayThu": "Thu",
|
||||
"dayFri": "Fri",
|
||||
"daySat": "Sat",
|
||||
"keyOnlyAvailableAtCreation": "Full key available only at creation time — copy it when you first create the key"
|
||||
},
|
||||
"auditLog": {
|
||||
"title": "Ellenőrzési napló",
|
||||
@@ -548,6 +573,27 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"mitmHowWorksDesc": "{toolName} sends requests to its provider endpoint. MITM intercepts and redirects them to OmniRoute.",
|
||||
"mitmStep1": "1. Start MITM to route requests through OmniRoute.",
|
||||
"mitmStep2Prefix": "2. Add",
|
||||
"mitmStep2Suffix": "to your hosts file as 127.0.0.1.",
|
||||
"mitmStep3": "3. Open {toolName} and requests will be proxied.",
|
||||
"whenToUseLabel": "Mikor kell használni",
|
||||
"openToolDocs": "Nyissa meg az eszközdokumentumokat",
|
||||
"toolUseCases": {
|
||||
"claude": "Használja, ha erős tervezési munkafolyamatokat és hosszú, több fájlból álló refaktorokat szeretne a Claude Code segítségével.",
|
||||
"codex": "Akkor használja, ha csapata szabványosítva van az OpenAI Codex CLI-folyamatokon és a profilalapú hitelesítésen.",
|
||||
"droid": "Használja, ha egy könnyű terminálügynökre van szüksége, amely a gyors kódolásra és parancsvégrehajtási ciklusokra összpontosít.",
|
||||
"openclaw": "Akkor használja, ha Open Claw stílusú kódoló ügynököt szeretne, de az OmniRoute házirendeken keresztül irányítja.",
|
||||
"cline": "Akkor használja, ha kódoló ügynököket konfigurál a szerkesztőkön belül, és irányított beállítást szeretne az OmniRoute modellekkel.",
|
||||
"kilo": "Akkor használja, ha a munkafolyamat a Kilo Code parancsoktól és a gyors iteratív szerkesztésektől függ.",
|
||||
"cursor": "Használja a Kurzorban való kódoláshoz, és egyéni OpenAI-kompatibilis modellekre van szüksége az OmniRoute-on keresztül.",
|
||||
"continue": "Használja a Continue futtatásakor az IDE-ben, és hordozható JSON-alapú szolgáltatói konfigurációra van szüksége.",
|
||||
"opencode": "Akkor használja, ha a terminál-natív ügynökfuttatásokat és az OpenCode-on keresztüli parancsfájl-automatizálást részesíti előnyben.",
|
||||
"kiro": "Használja a Kiro integrálásához és a modell-útválasztás központi vezérléséhez az OmniRoute-ból.",
|
||||
"antigravity": "Akkor használja, ha az Antigravity/Kiro forgalmat MITM-en keresztül kell elfogni, és az OmniRoute-hoz kell irányítani.",
|
||||
"copilot": "Használja, ha másodpilóta csevegési stílusú UX-et szeretne, miközben betartja az OmniRoute kulcsokat és útválasztási szabályokat."
|
||||
}
|
||||
},
|
||||
"combos": {
|
||||
@@ -864,7 +910,12 @@
|
||||
"a2aQuickStartStep2": "Send JSON-RPC requests to `POST /a2a` using `message/send` or `message/stream`.",
|
||||
"a2aQuickStartStep3": "Kövesse nyomon és vezérelje a feladatokat a `tasks/get` és `tasks/cancel` használatával.",
|
||||
"completionsLegacy": "Completions (Legacy)",
|
||||
"completionsLegacyDesc": "Legacy OpenAI text completions — accepts both prompt string and messages array format"
|
||||
"completionsLegacyDesc": "Legacy OpenAI text completions — accepts both prompt string and messages array format",
|
||||
"categorySearch": "Search & Discovery",
|
||||
"webSearch": "Web Search",
|
||||
"webSearchDesc": "Unified web search across multiple providers with automatic failover and caching",
|
||||
"searchProvider": "Search Provider",
|
||||
"searchProviderDesc": "This provider is used for web search via POST /v1/search. No model configuration needed — search providers are ready to use once an API key is connected."
|
||||
},
|
||||
"mcpDashboard": {
|
||||
"loading": "Loading MCP dashboard...",
|
||||
@@ -1054,7 +1105,13 @@
|
||||
"resetAllTitle": "Állítsa vissza az összes megszakítót egészséges állapotba",
|
||||
"resetting": "Visszaállítás...",
|
||||
"resetAll": "Összes visszaállítása",
|
||||
"until": "{time}-ig"
|
||||
"until": "{time}-ig",
|
||||
"activeProviders": "{count} active",
|
||||
"monitoredProviders": "{count} monitored",
|
||||
"configuredProvidersLabel": "A műszerfalon konfigurálva",
|
||||
"configuredProvidersHint": "A /dashboard/providers mappába mentett hitelesítő adatokkal rendelkező szolgáltatók, a futásidejű állapottól függetlenül.",
|
||||
"activeProvidersHint": "Konfigurált szolgáltatók, amelyek jelenleg engedélyezve vannak az útválasztási kérelmek számára.",
|
||||
"monitoredProvidersHint": "Azok a szolgáltatók, amelyeket jelenleg a megszakítók állapotfigyelői követnek nyomon."
|
||||
},
|
||||
"limits": {
|
||||
"title": "Korlátok és kvóták",
|
||||
@@ -1396,7 +1453,31 @@
|
||||
"chatPathHint": "Custom chat path for providers with non-standard APIs (e.g. /v4/chat/completions)",
|
||||
"modelsPathLabel": "Models Endpoint Path",
|
||||
"modelsPathPlaceholder": "/models",
|
||||
"modelsPathHint": "Custom models path for validation (e.g. /v4/models)"
|
||||
"modelsPathHint": "Custom models path for validation (e.g. /v4/models)",
|
||||
"builtInModels": "Built-in models",
|
||||
"builtInModelsHint": "Registry models for this provider. Use the pencil to set compatibility options.",
|
||||
"normalizeToolCallIdLabel": "Normalize tool call IDs to 9 characters (e.g. Mistral)",
|
||||
"preserveDeveloperRoleLabel": "Keep OpenAI Responses developer role (do not map to system)",
|
||||
"compatAdjustmentsTitle": "Compatibility",
|
||||
"compatButtonLabel": "Compatibility",
|
||||
"compatToolIdShort": "Tool ID 9",
|
||||
"compatDeveloperShort": "Developer role",
|
||||
"compatDoNotPreserveDeveloper": "Do not preserve developer role",
|
||||
"compatBadgeNoPreserve": "No preserve",
|
||||
"compatProtocolLabel": "Client request protocol",
|
||||
"compatProtocolHint": "These options apply when OmniRoute detects this request shape (OpenAI Chat, Responses API, or Anthropic Messages).",
|
||||
"compatProtocolOpenAI": "OpenAI Chat Completions",
|
||||
"compatProtocolOpenAIResponses": "OpenAI Responses API",
|
||||
"compatProtocolClaude": "Anthropic Messages",
|
||||
"tokenRefreshed": "Token refreshed successfully",
|
||||
"tokenRefreshFailed": "Token refresh failed",
|
||||
"compatBadgeUpstreamHeaders": "Headers",
|
||||
"compatUpstreamAddRow": "Add header",
|
||||
"compatUpstreamHeaderName": "Header name",
|
||||
"compatUpstreamHeaderValue": "Value",
|
||||
"compatUpstreamHeadersHint": "High-privilege setting — same trust level as editing provider API credentials; only trusted admins should use it. Merged after OmniRoute adds auth from the provider API key. If a custom header uses the same name as an existing one (e.g. Authorization), your value fully replaces the auto-generated header (including the Bearer token) — the upstream only sees what you typed, not the key from settings. Misconfiguration can cause 401 or broken upstream auth. One row per header (e.g. extra Authentication for some gateways). Hover or focus the value to preview. Saves on blur, outside click, or closing this panel.",
|
||||
"compatUpstreamHeadersLabel": "Extra upstream headers",
|
||||
"compatUpstreamRemoveRow": "Remove row"
|
||||
},
|
||||
"settings": {
|
||||
"title": "Beállítások elemre",
|
||||
@@ -1799,7 +1880,13 @@
|
||||
"cliFingerprintDesc": "Match native CLI binary signatures when proxying requests. Reorders headers and body fields to look identical to the official CLI tools. Your proxy IP is preserved.",
|
||||
"cliFingerprintEnabled": "{count} provider(s) with CLI fingerprint active",
|
||||
"enableFingerprintTitle": "Enable fingerprint for {provider}",
|
||||
"disableFingerprintTitle": "Disable fingerprint for {provider}"
|
||||
"disableFingerprintTitle": "Disable fingerprint for {provider}",
|
||||
"routingAdvancedGuideTitle": "Speciális útbaigazítás",
|
||||
"routingAdvancedGuideHint1": "Használja a Fill First funkciót a kiszámítható prioritáshoz, a Round Robint a méltányossághoz és a P2C-t a késleltetési rugalmassághoz.",
|
||||
"routingAdvancedGuideHint2": "Ha a szolgáltatók minősége/költségei eltérőek, kezdje a Cost Opt opcióval a háttérmunkához és a Least Used beállítással a kiegyensúlyozott viselet érdekében.",
|
||||
"comboDefaultsGuideTitle": "A kombinált alapértelmezett beállítások hangolása",
|
||||
"comboDefaultsGuideHint1": "Tartsa alacsonyan az újrapróbálkozásokat az alacsony késleltetésű folyamatokban; csak hosszú generációs feladatok esetén növelje az időtúllépést.",
|
||||
"comboDefaultsGuideHint2": "Használja a szolgáltató felülbírálását, ha az egyik szolgáltatónak a globális alapértelmezetttől eltérő időtúllépési/újrapróbálkozási viselkedésre van szüksége."
|
||||
},
|
||||
"translator": {
|
||||
"title": "Fordító",
|
||||
@@ -2216,7 +2303,8 @@
|
||||
"orRemovePasswordHashField": "vagy távolítsa el a passwordHash mezőt",
|
||||
"restartServerWithNewPassword": "Indítsa újra a szervert - az új jelszót fogja használni",
|
||||
"backToLogin": "Vissza a Bejelentkezéshez",
|
||||
"forgotPassword": "Elfelejtetted a jelszavad?"
|
||||
"forgotPassword": "Elfelejtetted a jelszavad?",
|
||||
"defaultPasswordHint": "Default password: 123456 (unless INITIAL_PASSWORD was set)"
|
||||
},
|
||||
"landing": {
|
||||
"brandName": "OmniRoute",
|
||||
@@ -2414,7 +2502,16 @@
|
||||
"troubleshootingCodexFamily": "A GitHub Codex-családhoz tartozó modelleknél tartsa a modellt gh/codex-model; a router automatikusan kiválasztja a /válaszokat.",
|
||||
"troubleshootingTestConnection": "Az IDE-kből vagy külső kliensekből történő tesztelés előtt használja az Irányítópult > Szolgáltatók > Kapcsolat tesztelése menüpontot.",
|
||||
"troubleshootingCircuitBreaker": "Ha a szolgáltató azt mutatja, hogy az áramkör megszakítója nyitva van, várja meg a lehűlést, vagy nézze meg az Egészség oldalt a részletekért.",
|
||||
"troubleshootingOAuth": "OAuth-szolgáltatók esetén hitelesítse újra, ha a tokenek lejárnak. Ellenőrizze a szolgáltatói kártya állapotjelzőjét."
|
||||
"troubleshootingOAuth": "OAuth-szolgáltatók esetén hitelesítse újra, ha a tokenek lejárnak. Ellenőrizze a szolgáltatói kártya állapotjelzőjét.",
|
||||
"managementApiReference": "Management API Reference",
|
||||
"managementApiDescription": "Automation endpoints for proxy registry, scope assignments, and legacy proxy migration.",
|
||||
"mgmtProxiesListNote": "List saved proxy registry items (supports pagination).",
|
||||
"mgmtProxiesCreateNote": "Create a reusable proxy item in the registry.",
|
||||
"mgmtProxiesHealthNote": "Get 24h/rolling health metrics per saved proxy from proxy logs.",
|
||||
"mgmtProxiesBulkAssignNote": "Assign or clear one proxy across many scope IDs in one request.",
|
||||
"mgmtAssignmentsListNote": "List proxy assignments by scope, scope_id, or proxy_id.",
|
||||
"mgmtAssignmentsUpdateNote": "Assign or clear proxy for global/provider/account/combo scope.",
|
||||
"mgmtLegacyMigrationNote": "Import legacy proxyConfig maps into registry assignments."
|
||||
},
|
||||
"legal": {
|
||||
"privacyPolicy": "Adatvédelmi szabályzat",
|
||||
@@ -2503,7 +2600,21 @@
|
||||
"binaryName": "Binary Name",
|
||||
"versionCommand": "Version Command",
|
||||
"spawnArgs": "Spawn Args",
|
||||
"addAgent": "Add Agent"
|
||||
"addAgent": "Add Agent",
|
||||
"scanning": "Scanning system for CLI agents...",
|
||||
"opencodeIntegration": "OpenCode Integration",
|
||||
"opencodeDetected": "opencode {version} detected",
|
||||
"opencodeDesc": "Generate a ready-to-use {configFile} with your OmniRoute base URL and all available models — drop it in your project root and run {command}.",
|
||||
"downloadConfig": "Download {file}",
|
||||
"downloaded": "Downloaded!",
|
||||
"setupGuideTitle": "Setup guide",
|
||||
"openCliTools": "Open CLI Tools",
|
||||
"setupGuideDetectCliTitle": "Detect installed CLIs",
|
||||
"setupGuideDetectCliDesc": "Click Refresh after installing or updating a CLI so OmniRoute can rescan binaries and versions.",
|
||||
"setupGuideCustomAgentTitle": "Register custom binary",
|
||||
"setupGuideCustomAgentDesc": "Use Add Custom Agent when your CLI is not in the built-in list. Provide binary name and version command.",
|
||||
"setupGuideCommandMissingTitle": "Fix 'command not found'",
|
||||
"setupGuideCommandMissingDesc": "Ensure the CLI command exists in PATH, open a new terminal session, and rerun Refresh."
|
||||
},
|
||||
"autoCombo": {
|
||||
"title": "Auto-Combo Engine",
|
||||
@@ -2527,5 +2638,56 @@
|
||||
"modePackCostSaver": "Cost Saver",
|
||||
"modePackQualityFirst": "Quality First",
|
||||
"modePackOfflineFriendly": "Offline Friendly"
|
||||
},
|
||||
"search": {
|
||||
"searchQuery": "Search Query",
|
||||
"searchResults": "Search Results",
|
||||
"cachedResult": "Cached",
|
||||
"searchCost": "Cost",
|
||||
"searchTools": "Search Tools",
|
||||
"searchToolsDesc": "Advanced search testing with provider comparison",
|
||||
"compareProviders": "Compare Providers",
|
||||
"rerankResults": "Rerank Results",
|
||||
"searchHistory": "Search History",
|
||||
"urlOverlap": "URL Overlap",
|
||||
"noSearchProviders": "No search providers configured. Add providers in Settings.",
|
||||
"noRerankModels": "No rerank model available",
|
||||
"webSearch": "Web Search",
|
||||
"provider": "Provider",
|
||||
"searchType": "Search Type",
|
||||
"maxResults": "Max Results",
|
||||
"filters": "Filters",
|
||||
"country": "Country",
|
||||
"language": "Language",
|
||||
"timeRange": "Time Range",
|
||||
"includeDomains": "Include Domains",
|
||||
"excludeDomains": "Exclude Domains",
|
||||
"safeSearch": "Safe Search",
|
||||
"formatted": "Formatted",
|
||||
"rawJson": "JSON",
|
||||
"cacheMiss": "cache miss",
|
||||
"cacheHit": "cache hit",
|
||||
"latency": "Latency",
|
||||
"cost": "Cost",
|
||||
"results": "Results",
|
||||
"rerank": "Rerank",
|
||||
"rerankModel": "Rerank Model",
|
||||
"positionDelta": "Position Change",
|
||||
"emptyState": "Send a search query to see results",
|
||||
"safeSearchOff": "Off",
|
||||
"safeSearchModerate": "Moderate",
|
||||
"safeSearchStrict": "Strict",
|
||||
"queryPlaceholder": "Enter search query...",
|
||||
"providerAuto": "auto (cheapest)",
|
||||
"searchTypeWeb": "web",
|
||||
"searchTypeNews": "news",
|
||||
"optionAny": "any",
|
||||
"timeRangeDay": "Past day",
|
||||
"timeRangeWeek": "Past week",
|
||||
"timeRangeMonth": "Past month",
|
||||
"timeRangeYear": "Past year",
|
||||
"domainPlaceholder": "example.com",
|
||||
"requestTimedOut": "Request timed out ({seconds}s)",
|
||||
"networkError": "Network error"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,7 +56,9 @@
|
||||
"warning": "Peringatan",
|
||||
"note": "Catatan",
|
||||
"free": "Gratis",
|
||||
"skipToContent": "Lewati ke konten"
|
||||
"skipToContent": "Lewati ke konten",
|
||||
"maintenanceServerIssues": "Server is experiencing issues. Some features may be unavailable.",
|
||||
"maintenanceServerUnreachable": "Server is unreachable. Reconnecting..."
|
||||
},
|
||||
"sidebar": {
|
||||
"home": "Rumah",
|
||||
@@ -103,7 +105,9 @@
|
||||
"endpoints": "Endpoint",
|
||||
"playground": "Playground",
|
||||
"agents": "Agen",
|
||||
"cliToolsShort": "Alat"
|
||||
"cliToolsShort": "Alat",
|
||||
"autoCombo": "Auto Combo",
|
||||
"searchTools": "Search Tools"
|
||||
},
|
||||
"themesPage": {
|
||||
"title": "Themes",
|
||||
@@ -268,7 +272,28 @@
|
||||
"permissionsTitle": "Izin: {name}",
|
||||
"allowAllDesc": "Kunci ini dapat mengakses semua model yang tersedia.",
|
||||
"restrictDesc": "Kunci ini dapat mengakses {selectedCount} dari {totalModels} model.",
|
||||
"selectedCount": "{count} dipilih"
|
||||
"selectedCount": "{count} dipilih",
|
||||
"autoResolve": "Auto-Resolve",
|
||||
"autoResolveDesc": "Auto-resolve ambiguous model names to native provider for this API key.",
|
||||
"keyActive": "Key Active",
|
||||
"keyActiveDesc": "Enable or disable this API key. Disabled keys are immediately rejected with 403.",
|
||||
"accessSchedule": "Access Schedule",
|
||||
"accessScheduleDesc": "Restrict access to specific hours and days of the week.",
|
||||
"scheduleFrom": "From",
|
||||
"scheduleUntil": "Until",
|
||||
"scheduleDays": "Days",
|
||||
"scheduleTimezone": "Timezone",
|
||||
"scheduleTimezoneHint": "Use IANA timezone names, e.g. America/New_York, Europe/Berlin",
|
||||
"scheduleActive": "Schedule",
|
||||
"disabled": "Disabled",
|
||||
"daySun": "Sun",
|
||||
"dayMon": "Mon",
|
||||
"dayTue": "Tue",
|
||||
"dayWed": "Wed",
|
||||
"dayThu": "Thu",
|
||||
"dayFri": "Fri",
|
||||
"daySat": "Sat",
|
||||
"keyOnlyAvailableAtCreation": "Full key available only at creation time — copy it when you first create the key"
|
||||
},
|
||||
"auditLog": {
|
||||
"title": "Catatan Audit",
|
||||
@@ -548,6 +573,27 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"mitmHowWorksDesc": "{toolName} sends requests to its provider endpoint. MITM intercepts and redirects them to OmniRoute.",
|
||||
"mitmStep1": "1. Start MITM to route requests through OmniRoute.",
|
||||
"mitmStep2Prefix": "2. Add",
|
||||
"mitmStep2Suffix": "to your hosts file as 127.0.0.1.",
|
||||
"mitmStep3": "3. Open {toolName} and requests will be proxied.",
|
||||
"whenToUseLabel": "Kapan harus digunakan",
|
||||
"openToolDocs": "Buka dokumen alat",
|
||||
"toolUseCases": {
|
||||
"claude": "Gunakan saat Anda menginginkan alur kerja perencanaan yang kuat dan pemfaktoran ulang multi-file yang panjang dengan Claude Code.",
|
||||
"codex": "Gunakan saat tim Anda terstandarisasi pada alur OpenAI Codex CLI dan autentikasi berbasis profil.",
|
||||
"droid": "Gunakan saat Anda membutuhkan agen terminal ringan yang berfokus pada pengkodean cepat dan loop eksekusi perintah.",
|
||||
"openclaw": "Gunakan saat Anda menginginkan agen pengkodean gaya Open Claw tetapi dirutekan melalui kebijakan OmniRoute.",
|
||||
"cline": "Gunakan saat Anda mengonfigurasi agen pengkodean di dalam editor dan ingin penyiapan terpandu dengan model OmniRoute.",
|
||||
"kilo": "Gunakan ketika alur kerja Anda bergantung pada perintah Kode Kilo dan pengeditan berulang yang cepat.",
|
||||
"cursor": "Gunakan saat membuat kode di Cursor dan Anda memerlukan model khusus yang kompatibel dengan OpenAI melalui OmniRoute.",
|
||||
"continue": "Gunakan saat menjalankan Lanjutkan di IDE dan Anda memerlukan konfigurasi penyedia portabel berbasis JSON.",
|
||||
"opencode": "Gunakan saat Anda lebih suka menjalankan agen terminal-asli dan otomatisasi skrip melalui OpenCode.",
|
||||
"kiro": "Gunakan saat mengintegrasikan Kiro dan mengontrol perutean model secara terpusat dari OmniRoute.",
|
||||
"antigravity": "Gunakan ketika lalu lintas Antigravitasi/Kiro harus dicegat melalui MITM dan dialihkan ke OmniRoute.",
|
||||
"copilot": "Gunakan saat Anda menginginkan UX gaya obrolan kopilot sambil menerapkan kunci OmniRoute dan aturan perutean."
|
||||
}
|
||||
},
|
||||
"combos": {
|
||||
@@ -864,7 +910,12 @@
|
||||
"a2aQuickStartStep2": "Send JSON-RPC requests to `POST /a2a` using `message/send` or `message/stream`.",
|
||||
"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"
|
||||
"completionsLegacyDesc": "Legacy OpenAI text completions — accepts both prompt string and messages array format",
|
||||
"categorySearch": "Search & Discovery",
|
||||
"webSearch": "Web Search",
|
||||
"webSearchDesc": "Unified web search across multiple providers with automatic failover and caching",
|
||||
"searchProvider": "Search Provider",
|
||||
"searchProviderDesc": "This provider is used for web search via POST /v1/search. No model configuration needed — search providers are ready to use once an API key is connected."
|
||||
},
|
||||
"mcpDashboard": {
|
||||
"loading": "Loading MCP dashboard...",
|
||||
@@ -1054,7 +1105,13 @@
|
||||
"resetAllTitle": "Setel ulang semua pemutus sirkuit ke kondisi sehat",
|
||||
"resetting": "Menyetel ulang...",
|
||||
"resetAll": "Atur Ulang Semua",
|
||||
"until": "Sampai {time}"
|
||||
"until": "Sampai {time}",
|
||||
"activeProviders": "{count} active",
|
||||
"monitoredProviders": "{count} monitored",
|
||||
"configuredProvidersLabel": "Dikonfigurasi di dasbor",
|
||||
"configuredProvidersHint": "Penyedia dengan kredensial yang disimpan di /dashboard/providers, apa pun status runtimenya.",
|
||||
"activeProvidersHint": "Penyedia yang dikonfigurasi saat ini diaktifkan untuk permintaan perutean.",
|
||||
"monitoredProvidersHint": "Penyedia saat ini dilacak oleh monitor kesehatan pemutus sirkuit."
|
||||
},
|
||||
"limits": {
|
||||
"title": "Batas & Kuota",
|
||||
@@ -1396,7 +1453,31 @@
|
||||
"chatPathHint": "Custom chat path for providers with non-standard APIs (e.g. /v4/chat/completions)",
|
||||
"modelsPathLabel": "Models Endpoint Path",
|
||||
"modelsPathPlaceholder": "/models",
|
||||
"modelsPathHint": "Custom models path for validation (e.g. /v4/models)"
|
||||
"modelsPathHint": "Custom models path for validation (e.g. /v4/models)",
|
||||
"builtInModels": "Built-in models",
|
||||
"builtInModelsHint": "Registry models for this provider. Use the pencil to set compatibility options.",
|
||||
"normalizeToolCallIdLabel": "Normalize tool call IDs to 9 characters (e.g. Mistral)",
|
||||
"preserveDeveloperRoleLabel": "Keep OpenAI Responses developer role (do not map to system)",
|
||||
"compatAdjustmentsTitle": "Compatibility",
|
||||
"compatButtonLabel": "Compatibility",
|
||||
"compatToolIdShort": "Tool ID 9",
|
||||
"compatDeveloperShort": "Developer role",
|
||||
"compatDoNotPreserveDeveloper": "Do not preserve developer role",
|
||||
"compatBadgeNoPreserve": "No preserve",
|
||||
"compatProtocolLabel": "Client request protocol",
|
||||
"compatProtocolHint": "These options apply when OmniRoute detects this request shape (OpenAI Chat, Responses API, or Anthropic Messages).",
|
||||
"compatProtocolOpenAI": "OpenAI Chat Completions",
|
||||
"compatProtocolOpenAIResponses": "OpenAI Responses API",
|
||||
"compatProtocolClaude": "Anthropic Messages",
|
||||
"tokenRefreshed": "Token refreshed successfully",
|
||||
"tokenRefreshFailed": "Token refresh failed",
|
||||
"compatBadgeUpstreamHeaders": "Headers",
|
||||
"compatUpstreamAddRow": "Add header",
|
||||
"compatUpstreamHeaderName": "Header name",
|
||||
"compatUpstreamHeaderValue": "Value",
|
||||
"compatUpstreamHeadersHint": "High-privilege setting — same trust level as editing provider API credentials; only trusted admins should use it. Merged after OmniRoute adds auth from the provider API key. If a custom header uses the same name as an existing one (e.g. Authorization), your value fully replaces the auto-generated header (including the Bearer token) — the upstream only sees what you typed, not the key from settings. Misconfiguration can cause 401 or broken upstream auth. One row per header (e.g. extra Authentication for some gateways). Hover or focus the value to preview. Saves on blur, outside click, or closing this panel.",
|
||||
"compatUpstreamHeadersLabel": "Extra upstream headers",
|
||||
"compatUpstreamRemoveRow": "Remove row"
|
||||
},
|
||||
"settings": {
|
||||
"title": "Pengaturan",
|
||||
@@ -1799,7 +1880,13 @@
|
||||
"cliFingerprintDesc": "Match native CLI binary signatures when proxying requests. Reorders headers and body fields to look identical to the official CLI tools. Your proxy IP is preserved.",
|
||||
"cliFingerprintEnabled": "{count} provider(s) with CLI fingerprint active",
|
||||
"enableFingerprintTitle": "Enable fingerprint for {provider}",
|
||||
"disableFingerprintTitle": "Disable fingerprint for {provider}"
|
||||
"disableFingerprintTitle": "Disable fingerprint for {provider}",
|
||||
"routingAdvancedGuideTitle": "Panduan perutean tingkat lanjut",
|
||||
"routingAdvancedGuideHint1": "Gunakan Fill First untuk prioritas yang dapat diprediksi, Round Robin untuk keadilan, dan P2C untuk ketahanan latensi.",
|
||||
"routingAdvancedGuideHint2": "Jika penyedia memiliki kualitas/biaya yang berbeda-beda, mulailah dengan Cost Opt (Pilihan Biaya) untuk pekerjaan latar belakang dan Paling Sedikit Digunakan untuk pemakaian yang seimbang.",
|
||||
"comboDefaultsGuideTitle": "Cara menyetel default kombo",
|
||||
"comboDefaultsGuideHint1": "Jaga agar percobaan ulang tetap rendah dalam aliran latensi rendah; menambah waktu tunggu hanya untuk tugas-tugas generasi panjang.",
|
||||
"comboDefaultsGuideHint2": "Gunakan penggantian penyedia ketika satu penyedia memerlukan perilaku batas waktu/coba lagi yang berbeda dari default global."
|
||||
},
|
||||
"translator": {
|
||||
"title": "Penerjemah",
|
||||
@@ -2216,7 +2303,8 @@
|
||||
"orRemovePasswordHashField": "atau hapus bidang passwordHash",
|
||||
"restartServerWithNewPassword": "Mulai ulang server - server akan menggunakan kata sandi baru",
|
||||
"backToLogin": "Kembali ke Masuk",
|
||||
"forgotPassword": "Lupa kata sandi?"
|
||||
"forgotPassword": "Lupa kata sandi?",
|
||||
"defaultPasswordHint": "Default password: 123456 (unless INITIAL_PASSWORD was set)"
|
||||
},
|
||||
"landing": {
|
||||
"brandName": "OmniRoute",
|
||||
@@ -2512,7 +2600,21 @@
|
||||
"binaryName": "Binary Name",
|
||||
"versionCommand": "Version Command",
|
||||
"spawnArgs": "Spawn Args",
|
||||
"addAgent": "Add Agent"
|
||||
"addAgent": "Add Agent",
|
||||
"scanning": "Scanning system for CLI agents...",
|
||||
"opencodeIntegration": "OpenCode Integration",
|
||||
"opencodeDetected": "opencode {version} detected",
|
||||
"opencodeDesc": "Generate a ready-to-use {configFile} with your OmniRoute base URL and all available models — drop it in your project root and run {command}.",
|
||||
"downloadConfig": "Download {file}",
|
||||
"downloaded": "Downloaded!",
|
||||
"setupGuideTitle": "Setup guide",
|
||||
"openCliTools": "Open CLI Tools",
|
||||
"setupGuideDetectCliTitle": "Detect installed CLIs",
|
||||
"setupGuideDetectCliDesc": "Click Refresh after installing or updating a CLI so OmniRoute can rescan binaries and versions.",
|
||||
"setupGuideCustomAgentTitle": "Register custom binary",
|
||||
"setupGuideCustomAgentDesc": "Use Add Custom Agent when your CLI is not in the built-in list. Provide binary name and version command.",
|
||||
"setupGuideCommandMissingTitle": "Fix 'command not found'",
|
||||
"setupGuideCommandMissingDesc": "Ensure the CLI command exists in PATH, open a new terminal session, and rerun Refresh."
|
||||
},
|
||||
"autoCombo": {
|
||||
"title": "Auto-Combo Engine",
|
||||
@@ -2536,5 +2638,56 @@
|
||||
"modePackCostSaver": "Cost Saver",
|
||||
"modePackQualityFirst": "Quality First",
|
||||
"modePackOfflineFriendly": "Offline Friendly"
|
||||
},
|
||||
"search": {
|
||||
"searchQuery": "Search Query",
|
||||
"searchResults": "Search Results",
|
||||
"cachedResult": "Cached",
|
||||
"searchCost": "Cost",
|
||||
"searchTools": "Search Tools",
|
||||
"searchToolsDesc": "Advanced search testing with provider comparison",
|
||||
"compareProviders": "Compare Providers",
|
||||
"rerankResults": "Rerank Results",
|
||||
"searchHistory": "Search History",
|
||||
"urlOverlap": "URL Overlap",
|
||||
"noSearchProviders": "No search providers configured. Add providers in Settings.",
|
||||
"noRerankModels": "No rerank model available",
|
||||
"webSearch": "Web Search",
|
||||
"provider": "Provider",
|
||||
"searchType": "Search Type",
|
||||
"maxResults": "Max Results",
|
||||
"filters": "Filters",
|
||||
"country": "Country",
|
||||
"language": "Language",
|
||||
"timeRange": "Time Range",
|
||||
"includeDomains": "Include Domains",
|
||||
"excludeDomains": "Exclude Domains",
|
||||
"safeSearch": "Safe Search",
|
||||
"formatted": "Formatted",
|
||||
"rawJson": "JSON",
|
||||
"cacheMiss": "cache miss",
|
||||
"cacheHit": "cache hit",
|
||||
"latency": "Latency",
|
||||
"cost": "Cost",
|
||||
"results": "Results",
|
||||
"rerank": "Rerank",
|
||||
"rerankModel": "Rerank Model",
|
||||
"positionDelta": "Position Change",
|
||||
"emptyState": "Send a search query to see results",
|
||||
"safeSearchOff": "Off",
|
||||
"safeSearchModerate": "Moderate",
|
||||
"safeSearchStrict": "Strict",
|
||||
"queryPlaceholder": "Enter search query...",
|
||||
"providerAuto": "auto (cheapest)",
|
||||
"searchTypeWeb": "web",
|
||||
"searchTypeNews": "news",
|
||||
"optionAny": "any",
|
||||
"timeRangeDay": "Past day",
|
||||
"timeRangeWeek": "Past week",
|
||||
"timeRangeMonth": "Past month",
|
||||
"timeRangeYear": "Past year",
|
||||
"domainPlaceholder": "example.com",
|
||||
"requestTimedOut": "Request timed out ({seconds}s)",
|
||||
"networkError": "Network error"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,7 +56,9 @@
|
||||
"warning": "चेतावनी",
|
||||
"note": "नोट",
|
||||
"free": "निःशुल्क",
|
||||
"skipToContent": "सामग्री पर जाएं"
|
||||
"skipToContent": "सामग्री पर जाएं",
|
||||
"maintenanceServerIssues": "Server is experiencing issues. Some features may be unavailable.",
|
||||
"maintenanceServerUnreachable": "Server is unreachable. Reconnecting..."
|
||||
},
|
||||
"sidebar": {
|
||||
"home": "घर",
|
||||
@@ -103,7 +105,9 @@
|
||||
"endpoints": "Endpoint",
|
||||
"playground": "Playground",
|
||||
"agents": "एजेंट",
|
||||
"cliToolsShort": "उपकरण"
|
||||
"cliToolsShort": "उपकरण",
|
||||
"autoCombo": "Auto Combo",
|
||||
"searchTools": "Search Tools"
|
||||
},
|
||||
"themesPage": {
|
||||
"title": "Themes",
|
||||
@@ -268,7 +272,28 @@
|
||||
"permissionsTitle": "अनुमतियाँ: {name}",
|
||||
"allowAllDesc": "यह कुंजी सभी उपलब्ध मॉडलों तक पहुंच सकती है.",
|
||||
"restrictDesc": "यह कुंजी {totalModels} मॉडलों में से {selectedCount} तक पहुंच सकती है।",
|
||||
"selectedCount": "{count} चयनित"
|
||||
"selectedCount": "{count} चयनित",
|
||||
"autoResolve": "Auto-Resolve",
|
||||
"autoResolveDesc": "Auto-resolve ambiguous model names to native provider for this API key.",
|
||||
"keyActive": "Key Active",
|
||||
"keyActiveDesc": "Enable or disable this API key. Disabled keys are immediately rejected with 403.",
|
||||
"accessSchedule": "Access Schedule",
|
||||
"accessScheduleDesc": "Restrict access to specific hours and days of the week.",
|
||||
"scheduleFrom": "From",
|
||||
"scheduleUntil": "Until",
|
||||
"scheduleDays": "Days",
|
||||
"scheduleTimezone": "Timezone",
|
||||
"scheduleTimezoneHint": "Use IANA timezone names, e.g. America/New_York, Europe/Berlin",
|
||||
"scheduleActive": "Schedule",
|
||||
"disabled": "Disabled",
|
||||
"daySun": "Sun",
|
||||
"dayMon": "Mon",
|
||||
"dayTue": "Tue",
|
||||
"dayWed": "Wed",
|
||||
"dayThu": "Thu",
|
||||
"dayFri": "Fri",
|
||||
"daySat": "Sat",
|
||||
"keyOnlyAvailableAtCreation": "Full key available only at creation time — copy it when you first create the key"
|
||||
},
|
||||
"auditLog": {
|
||||
"title": "ऑडिट लॉग",
|
||||
@@ -548,6 +573,27 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"mitmHowWorksDesc": "{toolName} sends requests to its provider endpoint. MITM intercepts and redirects them to OmniRoute.",
|
||||
"mitmStep1": "1. Start MITM to route requests through OmniRoute.",
|
||||
"mitmStep2Prefix": "2. Add",
|
||||
"mitmStep2Suffix": "to your hosts file as 127.0.0.1.",
|
||||
"mitmStep3": "3. Open {toolName} and requests will be proxied.",
|
||||
"whenToUseLabel": "कब उपयोग करें",
|
||||
"openToolDocs": "टूल दस्तावेज़ खोलें",
|
||||
"toolUseCases": {
|
||||
"claude": "जब आप क्लाउड कोड के साथ मजबूत नियोजन वर्कफ़्लो और लंबे मल्टी-फ़ाइल रिफैक्टर चाहते हों तो इसका उपयोग करें।",
|
||||
"codex": "जब आपकी टीम OpenAI कोडेक्स CLI प्रवाह और प्रोफ़ाइल-आधारित प्रमाणीकरण पर मानकीकृत हो तो इसका उपयोग करें।",
|
||||
"droid": "जब आपको तेज़ कोडिंग और कमांड निष्पादन लूप पर केंद्रित हल्के टर्मिनल एजेंट की आवश्यकता हो तो इसका उपयोग करें।",
|
||||
"openclaw": "जब आप ओपन क्लॉ स्टाइल कोडिंग एजेंट चाहते हैं लेकिन ओमनीरूट नीतियों के माध्यम से रूट किया जाता है तो इसका उपयोग करें।",
|
||||
"cline": "जब आप संपादकों के अंदर कोडिंग एजेंटों को कॉन्फ़िगर करते हैं और ओमनीरूट मॉडल के साथ निर्देशित सेटअप चाहते हैं तो इसका उपयोग करें।",
|
||||
"kilo": "इसका उपयोग तब करें जब आपका वर्कफ़्लो किलो कोड कमांड और तेज़ पुनरावृत्त संपादन पर निर्भर हो।",
|
||||
"cursor": "कर्सर में कोडिंग करते समय उपयोग करें और आपको ओमनीरूट के माध्यम से कस्टम ओपनएआई-संगत मॉडल की आवश्यकता है।",
|
||||
"continue": "IDE में जारी रखें चलाते समय उपयोग करें और आपको पोर्टेबल JSON-आधारित प्रदाता कॉन्फ़िगरेशन की आवश्यकता है।",
|
||||
"opencode": "जब आप ओपनकोड के माध्यम से टर्मिनल-नेटिव एजेंट रन और स्क्रिप्टेड ऑटोमेशन पसंद करते हैं तो इसका उपयोग करें।",
|
||||
"kiro": "किरो को एकीकृत करते समय और ओमनीरूट से केंद्रीय रूप से मॉडल रूटिंग को नियंत्रित करते समय उपयोग करें।",
|
||||
"antigravity": "इसका उपयोग तब करें जब एंटीग्रेविटी/किरो ट्रैफिक को एमआईटीएम के माध्यम से रोका जाना चाहिए और ओमनीरूट पर भेजा जाना चाहिए।",
|
||||
"copilot": "जब आप ओम्निरूट कुंजी और रूटिंग नियमों को लागू करते समय कोपायलट चैट शैली यूएक्स चाहते हैं तो इसका उपयोग करें।"
|
||||
}
|
||||
},
|
||||
"combos": {
|
||||
@@ -864,7 +910,12 @@
|
||||
"a2aQuickStartStep2": "Send JSON-RPC requests to `POST /a2a` using `message/send` or `message/stream`.",
|
||||
"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"
|
||||
"completionsLegacyDesc": "Legacy OpenAI text completions — accepts both prompt string and messages array format",
|
||||
"categorySearch": "Search & Discovery",
|
||||
"webSearch": "Web Search",
|
||||
"webSearchDesc": "Unified web search across multiple providers with automatic failover and caching",
|
||||
"searchProvider": "Search Provider",
|
||||
"searchProviderDesc": "This provider is used for web search via POST /v1/search. No model configuration needed — search providers are ready to use once an API key is connected."
|
||||
},
|
||||
"mcpDashboard": {
|
||||
"loading": "Loading MCP dashboard...",
|
||||
@@ -1054,7 +1105,13 @@
|
||||
"resetAllTitle": "सभी सर्किट ब्रेकरों को स्वस्थ स्थिति में रीसेट करें",
|
||||
"resetting": "रीसेट किया जा रहा है...",
|
||||
"resetAll": "सभी रीसेट करें",
|
||||
"until": "{time} तक"
|
||||
"until": "{time} तक",
|
||||
"activeProviders": "{count} active",
|
||||
"monitoredProviders": "{count} monitored",
|
||||
"configuredProvidersLabel": "डैशबोर्ड में कॉन्फ़िगर किया गया",
|
||||
"configuredProvidersHint": "रनटाइम स्थिति की परवाह किए बिना, /डैशबोर्ड/प्रदाताओं में सहेजे गए क्रेडेंशियल वाले प्रदाता।",
|
||||
"activeProvidersHint": "कॉन्फ़िगर किए गए प्रदाता वर्तमान में रूटिंग अनुरोधों के लिए सक्षम हैं।",
|
||||
"monitoredProvidersHint": "प्रदाताओं को वर्तमान में सर्किट-ब्रेकर स्वास्थ्य मॉनिटर द्वारा ट्रैक किया जाता है।"
|
||||
},
|
||||
"limits": {
|
||||
"title": "सीमाएँ और कोटा",
|
||||
@@ -1396,7 +1453,31 @@
|
||||
"chatPathHint": "Custom chat path for providers with non-standard APIs (e.g. /v4/chat/completions)",
|
||||
"modelsPathLabel": "Models Endpoint Path",
|
||||
"modelsPathPlaceholder": "/models",
|
||||
"modelsPathHint": "Custom models path for validation (e.g. /v4/models)"
|
||||
"modelsPathHint": "Custom models path for validation (e.g. /v4/models)",
|
||||
"builtInModels": "Built-in models",
|
||||
"builtInModelsHint": "Registry models for this provider. Use the pencil to set compatibility options.",
|
||||
"normalizeToolCallIdLabel": "Normalize tool call IDs to 9 characters (e.g. Mistral)",
|
||||
"preserveDeveloperRoleLabel": "Keep OpenAI Responses developer role (do not map to system)",
|
||||
"compatAdjustmentsTitle": "Compatibility",
|
||||
"compatButtonLabel": "Compatibility",
|
||||
"compatToolIdShort": "Tool ID 9",
|
||||
"compatDeveloperShort": "Developer role",
|
||||
"compatDoNotPreserveDeveloper": "Do not preserve developer role",
|
||||
"compatBadgeNoPreserve": "No preserve",
|
||||
"compatProtocolLabel": "Client request protocol",
|
||||
"compatProtocolHint": "These options apply when OmniRoute detects this request shape (OpenAI Chat, Responses API, or Anthropic Messages).",
|
||||
"compatProtocolOpenAI": "OpenAI Chat Completions",
|
||||
"compatProtocolOpenAIResponses": "OpenAI Responses API",
|
||||
"compatProtocolClaude": "Anthropic Messages",
|
||||
"tokenRefreshed": "Token refreshed successfully",
|
||||
"tokenRefreshFailed": "Token refresh failed",
|
||||
"compatBadgeUpstreamHeaders": "Headers",
|
||||
"compatUpstreamAddRow": "Add header",
|
||||
"compatUpstreamHeaderName": "Header name",
|
||||
"compatUpstreamHeaderValue": "Value",
|
||||
"compatUpstreamHeadersHint": "High-privilege setting — same trust level as editing provider API credentials; only trusted admins should use it. Merged after OmniRoute adds auth from the provider API key. If a custom header uses the same name as an existing one (e.g. Authorization), your value fully replaces the auto-generated header (including the Bearer token) — the upstream only sees what you typed, not the key from settings. Misconfiguration can cause 401 or broken upstream auth. One row per header (e.g. extra Authentication for some gateways). Hover or focus the value to preview. Saves on blur, outside click, or closing this panel.",
|
||||
"compatUpstreamHeadersLabel": "Extra upstream headers",
|
||||
"compatUpstreamRemoveRow": "Remove row"
|
||||
},
|
||||
"settings": {
|
||||
"title": "सेटिंग्स",
|
||||
@@ -1799,7 +1880,13 @@
|
||||
"cliFingerprintDesc": "Match native CLI binary signatures when proxying requests. Reorders headers and body fields to look identical to the official CLI tools. Your proxy IP is preserved.",
|
||||
"cliFingerprintEnabled": "{count} provider(s) with CLI fingerprint active",
|
||||
"enableFingerprintTitle": "Enable fingerprint for {provider}",
|
||||
"disableFingerprintTitle": "Disable fingerprint for {provider}"
|
||||
"disableFingerprintTitle": "Disable fingerprint for {provider}",
|
||||
"routingAdvancedGuideTitle": "उन्नत रूटिंग मार्गदर्शन",
|
||||
"routingAdvancedGuideHint1": "पूर्वानुमानित प्राथमिकता के लिए फ़िल फ़र्स्ट का उपयोग करें, निष्पक्षता के लिए राउंड रॉबिन और विलंबता लचीलेपन के लिए P2C का उपयोग करें।",
|
||||
"routingAdvancedGuideHint2": "यदि प्रदाता गुणवत्ता/लागत में भिन्न हैं, तो पृष्ठभूमि कार्य के लिए कॉस्ट ऑप्ट और संतुलित पहनावे के लिए कम से कम उपयोग से शुरुआत करें।",
|
||||
"comboDefaultsGuideTitle": "कॉम्बो डिफॉल्ट्स को कैसे ट्यून करें",
|
||||
"comboDefaultsGuideHint1": "कम-विलंबता प्रवाह में पुनः प्रयास कम रखें; केवल लंबी पीढ़ी के कार्यों के लिए टाइमआउट बढ़ाएँ।",
|
||||
"comboDefaultsGuideHint2": "जब एक प्रदाता को वैश्विक डिफ़ॉल्ट की तुलना में अलग टाइमआउट/पुनः प्रयास व्यवहार की आवश्यकता होती है तो प्रदाता ओवरराइड का उपयोग करें।"
|
||||
},
|
||||
"translator": {
|
||||
"title": "अनुवादक",
|
||||
@@ -2216,7 +2303,8 @@
|
||||
"orRemovePasswordHashField": "या पासवर्डहैश फ़ील्ड हटा दें",
|
||||
"restartServerWithNewPassword": "सर्वर को पुनरारंभ करें - यह नए पासवर्ड का उपयोग करेगा",
|
||||
"backToLogin": "लॉगइन पर वापस जाएँ",
|
||||
"forgotPassword": "पासवर्ड भूल गए?"
|
||||
"forgotPassword": "पासवर्ड भूल गए?",
|
||||
"defaultPasswordHint": "Default password: 123456 (unless INITIAL_PASSWORD was set)"
|
||||
},
|
||||
"landing": {
|
||||
"brandName": "ओम्निरूट",
|
||||
@@ -2414,7 +2502,16 @@
|
||||
"troubleshootingCodexFamily": "GitHub कोडेक्स-फ़ैमिली मॉडल के लिए, मॉडल को gh/codex-model के रूप में रखें; राउटर स्वचालित रूप से/प्रतिक्रियाओं का चयन करता है।",
|
||||
"troubleshootingTestConnection": "आईडीई या बाहरी क्लाइंट से परीक्षण करने से पहले डैशबोर्ड > प्रदाता > परीक्षण कनेक्शन का उपयोग करें।",
|
||||
"troubleshootingCircuitBreaker": "यदि कोई प्रदाता सर्किट ब्रेकर खुला दिखाता है, तो कूलडाउन की प्रतीक्षा करें या विवरण के लिए स्वास्थ्य पृष्ठ देखें।",
|
||||
"troubleshootingOAuth": "OAuth प्रदाताओं के लिए, यदि टोकन समाप्त हो जाते हैं तो पुनः प्रमाणित करें। प्रदाता कार्ड स्थिति संकेतक की जाँच करें।"
|
||||
"troubleshootingOAuth": "OAuth प्रदाताओं के लिए, यदि टोकन समाप्त हो जाते हैं तो पुनः प्रमाणित करें। प्रदाता कार्ड स्थिति संकेतक की जाँच करें।",
|
||||
"managementApiReference": "Management API Reference",
|
||||
"managementApiDescription": "Automation endpoints for proxy registry, scope assignments, and legacy proxy migration.",
|
||||
"mgmtProxiesListNote": "List saved proxy registry items (supports pagination).",
|
||||
"mgmtProxiesCreateNote": "Create a reusable proxy item in the registry.",
|
||||
"mgmtProxiesHealthNote": "Get 24h/rolling health metrics per saved proxy from proxy logs.",
|
||||
"mgmtProxiesBulkAssignNote": "Assign or clear one proxy across many scope IDs in one request.",
|
||||
"mgmtAssignmentsListNote": "List proxy assignments by scope, scope_id, or proxy_id.",
|
||||
"mgmtAssignmentsUpdateNote": "Assign or clear proxy for global/provider/account/combo scope.",
|
||||
"mgmtLegacyMigrationNote": "Import legacy proxyConfig maps into registry assignments."
|
||||
},
|
||||
"legal": {
|
||||
"privacyPolicy": "गोपनीयता नीति",
|
||||
@@ -2503,7 +2600,21 @@
|
||||
"binaryName": "Binary Name",
|
||||
"versionCommand": "Version Command",
|
||||
"spawnArgs": "Spawn Args",
|
||||
"addAgent": "Add Agent"
|
||||
"addAgent": "Add Agent",
|
||||
"scanning": "Scanning system for CLI agents...",
|
||||
"opencodeIntegration": "OpenCode Integration",
|
||||
"opencodeDetected": "opencode {version} detected",
|
||||
"opencodeDesc": "Generate a ready-to-use {configFile} with your OmniRoute base URL and all available models — drop it in your project root and run {command}.",
|
||||
"downloadConfig": "Download {file}",
|
||||
"downloaded": "Downloaded!",
|
||||
"setupGuideTitle": "Setup guide",
|
||||
"openCliTools": "Open CLI Tools",
|
||||
"setupGuideDetectCliTitle": "Detect installed CLIs",
|
||||
"setupGuideDetectCliDesc": "Click Refresh after installing or updating a CLI so OmniRoute can rescan binaries and versions.",
|
||||
"setupGuideCustomAgentTitle": "Register custom binary",
|
||||
"setupGuideCustomAgentDesc": "Use Add Custom Agent when your CLI is not in the built-in list. Provide binary name and version command.",
|
||||
"setupGuideCommandMissingTitle": "Fix 'command not found'",
|
||||
"setupGuideCommandMissingDesc": "Ensure the CLI command exists in PATH, open a new terminal session, and rerun Refresh."
|
||||
},
|
||||
"autoCombo": {
|
||||
"title": "Auto-Combo Engine",
|
||||
@@ -2527,5 +2638,56 @@
|
||||
"modePackCostSaver": "Cost Saver",
|
||||
"modePackQualityFirst": "Quality First",
|
||||
"modePackOfflineFriendly": "Offline Friendly"
|
||||
},
|
||||
"search": {
|
||||
"searchQuery": "Search Query",
|
||||
"searchResults": "Search Results",
|
||||
"cachedResult": "Cached",
|
||||
"searchCost": "Cost",
|
||||
"searchTools": "Search Tools",
|
||||
"searchToolsDesc": "Advanced search testing with provider comparison",
|
||||
"compareProviders": "Compare Providers",
|
||||
"rerankResults": "Rerank Results",
|
||||
"searchHistory": "Search History",
|
||||
"urlOverlap": "URL Overlap",
|
||||
"noSearchProviders": "No search providers configured. Add providers in Settings.",
|
||||
"noRerankModels": "No rerank model available",
|
||||
"webSearch": "Web Search",
|
||||
"provider": "Provider",
|
||||
"searchType": "Search Type",
|
||||
"maxResults": "Max Results",
|
||||
"filters": "Filters",
|
||||
"country": "Country",
|
||||
"language": "Language",
|
||||
"timeRange": "Time Range",
|
||||
"includeDomains": "Include Domains",
|
||||
"excludeDomains": "Exclude Domains",
|
||||
"safeSearch": "Safe Search",
|
||||
"formatted": "Formatted",
|
||||
"rawJson": "JSON",
|
||||
"cacheMiss": "cache miss",
|
||||
"cacheHit": "cache hit",
|
||||
"latency": "Latency",
|
||||
"cost": "Cost",
|
||||
"results": "Results",
|
||||
"rerank": "Rerank",
|
||||
"rerankModel": "Rerank Model",
|
||||
"positionDelta": "Position Change",
|
||||
"emptyState": "Send a search query to see results",
|
||||
"safeSearchOff": "Off",
|
||||
"safeSearchModerate": "Moderate",
|
||||
"safeSearchStrict": "Strict",
|
||||
"queryPlaceholder": "Enter search query...",
|
||||
"providerAuto": "auto (cheapest)",
|
||||
"searchTypeWeb": "web",
|
||||
"searchTypeNews": "news",
|
||||
"optionAny": "any",
|
||||
"timeRangeDay": "Past day",
|
||||
"timeRangeWeek": "Past week",
|
||||
"timeRangeMonth": "Past month",
|
||||
"timeRangeYear": "Past year",
|
||||
"domainPlaceholder": "example.com",
|
||||
"requestTimedOut": "Request timed out ({seconds}s)",
|
||||
"networkError": "Network error"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,7 +56,9 @@
|
||||
"warning": "Avvertimento",
|
||||
"note": "Nota",
|
||||
"free": "Gratuito",
|
||||
"skipToContent": "Vai al contenuto"
|
||||
"skipToContent": "Vai al contenuto",
|
||||
"maintenanceServerIssues": "Server is experiencing issues. Some features may be unavailable.",
|
||||
"maintenanceServerUnreachable": "Server is unreachable. Reconnecting..."
|
||||
},
|
||||
"sidebar": {
|
||||
"home": "Casa",
|
||||
@@ -103,7 +105,9 @@
|
||||
"endpoints": "Endpoint",
|
||||
"playground": "Playground",
|
||||
"agents": "Agenti",
|
||||
"cliToolsShort": "Strumenti"
|
||||
"cliToolsShort": "Strumenti",
|
||||
"autoCombo": "Auto Combo",
|
||||
"searchTools": "Search Tools"
|
||||
},
|
||||
"themesPage": {
|
||||
"title": "Themes",
|
||||
@@ -268,7 +272,28 @@
|
||||
"permissionsTitle": "Autorizzazioni: {name}",
|
||||
"allowAllDesc": "Questa chiave può accedere a tutti i modelli disponibili.",
|
||||
"restrictDesc": "Questa chiave può accedere a {selectedCount} di {totalModels} modelli.",
|
||||
"selectedCount": "{count} selezionato"
|
||||
"selectedCount": "{count} selezionato",
|
||||
"autoResolve": "Auto-Resolve",
|
||||
"autoResolveDesc": "Auto-resolve ambiguous model names to native provider for this API key.",
|
||||
"keyActive": "Key Active",
|
||||
"keyActiveDesc": "Enable or disable this API key. Disabled keys are immediately rejected with 403.",
|
||||
"accessSchedule": "Access Schedule",
|
||||
"accessScheduleDesc": "Restrict access to specific hours and days of the week.",
|
||||
"scheduleFrom": "From",
|
||||
"scheduleUntil": "Until",
|
||||
"scheduleDays": "Days",
|
||||
"scheduleTimezone": "Timezone",
|
||||
"scheduleTimezoneHint": "Use IANA timezone names, e.g. America/New_York, Europe/Berlin",
|
||||
"scheduleActive": "Schedule",
|
||||
"disabled": "Disabled",
|
||||
"daySun": "Sun",
|
||||
"dayMon": "Mon",
|
||||
"dayTue": "Tue",
|
||||
"dayWed": "Wed",
|
||||
"dayThu": "Thu",
|
||||
"dayFri": "Fri",
|
||||
"daySat": "Sat",
|
||||
"keyOnlyAvailableAtCreation": "Full key available only at creation time — copy it when you first create the key"
|
||||
},
|
||||
"auditLog": {
|
||||
"title": "Registro di controllo",
|
||||
@@ -548,6 +573,27 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"mitmHowWorksDesc": "{toolName} sends requests to its provider endpoint. MITM intercepts and redirects them to OmniRoute.",
|
||||
"mitmStep1": "1. Start MITM to route requests through OmniRoute.",
|
||||
"mitmStep2Prefix": "2. Add",
|
||||
"mitmStep2Suffix": "to your hosts file as 127.0.0.1.",
|
||||
"mitmStep3": "3. Open {toolName} and requests will be proxied.",
|
||||
"whenToUseLabel": "Quando usarlo",
|
||||
"openToolDocs": "Apri i documenti dello strumento",
|
||||
"toolUseCases": {
|
||||
"claude": "Utilizzalo quando desideri flussi di lavoro di pianificazione avanzati e lunghi refactoring multi-file con Claude Code.",
|
||||
"codex": "Da utilizzare quando il tuo team è standardizzato sui flussi CLI di OpenAI Codex e sull'autenticazione basata sul profilo.",
|
||||
"droid": "Da utilizzare quando è necessario un agente terminale leggero incentrato sulla codifica rapida e sui cicli di esecuzione dei comandi.",
|
||||
"openclaw": "Da utilizzare quando si desidera un agente di codifica in stile Open Claw ma instradato tramite policy OmniRoute.",
|
||||
"cline": "Da utilizzare quando si configurano agenti di codifica all'interno degli editor e si desidera un'impostazione guidata con i modelli OmniRoute.",
|
||||
"kilo": "Da utilizzare quando il flusso di lavoro dipende dai comandi Kilo Code e da modifiche iterative rapide.",
|
||||
"cursor": "Da utilizzare durante la codifica in Cursor e sono necessari modelli personalizzati compatibili con OpenAI tramite OmniRoute.",
|
||||
"continue": "Da utilizzare quando si esegue Continue negli IDE ed è necessaria la configurazione portatile del provider basato su JSON.",
|
||||
"opencode": "Utilizzalo quando preferisci l'esecuzione dell'agente nativo del terminale e l'automazione basata su script tramite OpenCode.",
|
||||
"kiro": "Da utilizzare quando si integra Kiro e si controlla l'instradamento del modello centralmente da OmniRoute.",
|
||||
"antigravity": "Da utilizzare quando il traffico Antigravity/Kiro deve essere intercettato tramite MITM e instradato a OmniRoute.",
|
||||
"copilot": "Utilizzalo quando desideri un'esperienza utente in stile chat Copilot applicando al tempo stesso le chiavi OmniRoute e le regole di routing."
|
||||
}
|
||||
},
|
||||
"combos": {
|
||||
@@ -864,7 +910,12 @@
|
||||
"a2aQuickStartStep2": "Send JSON-RPC requests to `POST /a2a` using `message/send` or `message/stream`.",
|
||||
"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"
|
||||
"completionsLegacyDesc": "Legacy OpenAI text completions — accepts both prompt string and messages array format",
|
||||
"categorySearch": "Search & Discovery",
|
||||
"webSearch": "Web Search",
|
||||
"webSearchDesc": "Unified web search across multiple providers with automatic failover and caching",
|
||||
"searchProvider": "Search Provider",
|
||||
"searchProviderDesc": "This provider is used for web search via POST /v1/search. No model configuration needed — search providers are ready to use once an API key is connected."
|
||||
},
|
||||
"mcpDashboard": {
|
||||
"loading": "Loading MCP dashboard...",
|
||||
@@ -1054,7 +1105,13 @@
|
||||
"resetAllTitle": "Ripristinare tutti gli interruttori automatici allo stato integro",
|
||||
"resetting": "Reimpostazione...",
|
||||
"resetAll": "Reimposta tutto",
|
||||
"until": "Fino al {time}"
|
||||
"until": "Fino al {time}",
|
||||
"activeProviders": "{count} active",
|
||||
"monitoredProviders": "{count} monitored",
|
||||
"configuredProvidersLabel": "Configurato nel dashboard",
|
||||
"configuredProvidersHint": "Provider con credenziali salvate in /dashboard/providers, indipendentemente dallo stato di runtime.",
|
||||
"activeProvidersHint": "Provider configurati attualmente abilitati per le richieste di instradamento.",
|
||||
"monitoredProvidersHint": "Fornitori attualmente monitorati dai monitoraggi dello stato degli interruttori automatici."
|
||||
},
|
||||
"limits": {
|
||||
"title": "Limiti e quote",
|
||||
@@ -1396,7 +1453,31 @@
|
||||
"chatPathHint": "Custom chat path for providers with non-standard APIs (e.g. /v4/chat/completions)",
|
||||
"modelsPathLabel": "Models Endpoint Path",
|
||||
"modelsPathPlaceholder": "/models",
|
||||
"modelsPathHint": "Custom models path for validation (e.g. /v4/models)"
|
||||
"modelsPathHint": "Custom models path for validation (e.g. /v4/models)",
|
||||
"builtInModels": "Built-in models",
|
||||
"builtInModelsHint": "Registry models for this provider. Use the pencil to set compatibility options.",
|
||||
"normalizeToolCallIdLabel": "Normalize tool call IDs to 9 characters (e.g. Mistral)",
|
||||
"preserveDeveloperRoleLabel": "Keep OpenAI Responses developer role (do not map to system)",
|
||||
"compatAdjustmentsTitle": "Compatibility",
|
||||
"compatButtonLabel": "Compatibility",
|
||||
"compatToolIdShort": "Tool ID 9",
|
||||
"compatDeveloperShort": "Developer role",
|
||||
"compatDoNotPreserveDeveloper": "Do not preserve developer role",
|
||||
"compatBadgeNoPreserve": "No preserve",
|
||||
"compatProtocolLabel": "Client request protocol",
|
||||
"compatProtocolHint": "These options apply when OmniRoute detects this request shape (OpenAI Chat, Responses API, or Anthropic Messages).",
|
||||
"compatProtocolOpenAI": "OpenAI Chat Completions",
|
||||
"compatProtocolOpenAIResponses": "OpenAI Responses API",
|
||||
"compatProtocolClaude": "Anthropic Messages",
|
||||
"tokenRefreshed": "Token refreshed successfully",
|
||||
"tokenRefreshFailed": "Token refresh failed",
|
||||
"compatBadgeUpstreamHeaders": "Headers",
|
||||
"compatUpstreamAddRow": "Add header",
|
||||
"compatUpstreamHeaderName": "Header name",
|
||||
"compatUpstreamHeaderValue": "Value",
|
||||
"compatUpstreamHeadersHint": "High-privilege setting — same trust level as editing provider API credentials; only trusted admins should use it. Merged after OmniRoute adds auth from the provider API key. If a custom header uses the same name as an existing one (e.g. Authorization), your value fully replaces the auto-generated header (including the Bearer token) — the upstream only sees what you typed, not the key from settings. Misconfiguration can cause 401 or broken upstream auth. One row per header (e.g. extra Authentication for some gateways). Hover or focus the value to preview. Saves on blur, outside click, or closing this panel.",
|
||||
"compatUpstreamHeadersLabel": "Extra upstream headers",
|
||||
"compatUpstreamRemoveRow": "Remove row"
|
||||
},
|
||||
"settings": {
|
||||
"title": "Impostazioni",
|
||||
@@ -1799,7 +1880,13 @@
|
||||
"cliFingerprintDesc": "Match native CLI binary signatures when proxying requests. Reorders headers and body fields to look identical to the official CLI tools. Your proxy IP is preserved.",
|
||||
"cliFingerprintEnabled": "{count} provider(s) with CLI fingerprint active",
|
||||
"enableFingerprintTitle": "Enable fingerprint for {provider}",
|
||||
"disableFingerprintTitle": "Disable fingerprint for {provider}"
|
||||
"disableFingerprintTitle": "Disable fingerprint for {provider}",
|
||||
"routingAdvancedGuideTitle": "Guida al percorso avanzata",
|
||||
"routingAdvancedGuideHint1": "Utilizza Fill First per una priorità prevedibile, Round Robin per l'equità e P2C per la resilienza alla latenza.",
|
||||
"routingAdvancedGuideHint2": "Se i fornitori variano in termini di qualità/costo, iniziare con Opzione costo per il lavoro in background e Meno utilizzato per un consumo equilibrato.",
|
||||
"comboDefaultsGuideTitle": "Come ottimizzare le impostazioni predefinite della combo",
|
||||
"comboDefaultsGuideHint1": "Mantenere bassi i tentativi nei flussi a bassa latenza; aumentare il timeout solo per attività di generazione prolungata.",
|
||||
"comboDefaultsGuideHint2": "Utilizzare le sostituzioni del provider quando un provider necessita di un comportamento di timeout/riprova diverso rispetto alle impostazioni predefinite globali."
|
||||
},
|
||||
"translator": {
|
||||
"title": "Traduttore",
|
||||
@@ -2216,7 +2303,8 @@
|
||||
"orRemovePasswordHashField": "o rimuovere il campo passwordHash",
|
||||
"restartServerWithNewPassword": "Riavvia il server: utilizzerà la nuova password",
|
||||
"backToLogin": "Torna all'accesso",
|
||||
"forgotPassword": "Ha dimenticato la password?"
|
||||
"forgotPassword": "Ha dimenticato la password?",
|
||||
"defaultPasswordHint": "Default password: 123456 (unless INITIAL_PASSWORD was set)"
|
||||
},
|
||||
"landing": {
|
||||
"brandName": "OmniRoute",
|
||||
@@ -2414,7 +2502,16 @@
|
||||
"troubleshootingCodexFamily": "Per i modelli della famiglia GitHub Codex, mantieni il modello come gh/codex-model; il router seleziona/risponde automaticamente.",
|
||||
"troubleshootingTestConnection": "Utilizza Dashboard > Provider > Verifica connessione prima di effettuare test da IDE o client esterni.",
|
||||
"troubleshootingCircuitBreaker": "Se un fornitore mostra l'interruttore aperto, attendi il raffreddamento o controlla la pagina Salute per i dettagli.",
|
||||
"troubleshootingOAuth": "Per i provider OAuth, eseguire nuovamente l'autenticazione se i token scadono. Controlla l'indicatore di stato della carta del fornitore."
|
||||
"troubleshootingOAuth": "Per i provider OAuth, eseguire nuovamente l'autenticazione se i token scadono. Controlla l'indicatore di stato della carta del fornitore.",
|
||||
"managementApiReference": "Management API Reference",
|
||||
"managementApiDescription": "Automation endpoints for proxy registry, scope assignments, and legacy proxy migration.",
|
||||
"mgmtProxiesListNote": "List saved proxy registry items (supports pagination).",
|
||||
"mgmtProxiesCreateNote": "Create a reusable proxy item in the registry.",
|
||||
"mgmtProxiesHealthNote": "Get 24h/rolling health metrics per saved proxy from proxy logs.",
|
||||
"mgmtProxiesBulkAssignNote": "Assign or clear one proxy across many scope IDs in one request.",
|
||||
"mgmtAssignmentsListNote": "List proxy assignments by scope, scope_id, or proxy_id.",
|
||||
"mgmtAssignmentsUpdateNote": "Assign or clear proxy for global/provider/account/combo scope.",
|
||||
"mgmtLegacyMigrationNote": "Import legacy proxyConfig maps into registry assignments."
|
||||
},
|
||||
"legal": {
|
||||
"privacyPolicy": "politica sulla riservatezza",
|
||||
@@ -2503,7 +2600,21 @@
|
||||
"binaryName": "Binary Name",
|
||||
"versionCommand": "Version Command",
|
||||
"spawnArgs": "Spawn Args",
|
||||
"addAgent": "Add Agent"
|
||||
"addAgent": "Add Agent",
|
||||
"scanning": "Scanning system for CLI agents...",
|
||||
"opencodeIntegration": "OpenCode Integration",
|
||||
"opencodeDetected": "opencode {version} detected",
|
||||
"opencodeDesc": "Generate a ready-to-use {configFile} with your OmniRoute base URL and all available models — drop it in your project root and run {command}.",
|
||||
"downloadConfig": "Download {file}",
|
||||
"downloaded": "Downloaded!",
|
||||
"setupGuideTitle": "Setup guide",
|
||||
"openCliTools": "Open CLI Tools",
|
||||
"setupGuideDetectCliTitle": "Detect installed CLIs",
|
||||
"setupGuideDetectCliDesc": "Click Refresh after installing or updating a CLI so OmniRoute can rescan binaries and versions.",
|
||||
"setupGuideCustomAgentTitle": "Register custom binary",
|
||||
"setupGuideCustomAgentDesc": "Use Add Custom Agent when your CLI is not in the built-in list. Provide binary name and version command.",
|
||||
"setupGuideCommandMissingTitle": "Fix 'command not found'",
|
||||
"setupGuideCommandMissingDesc": "Ensure the CLI command exists in PATH, open a new terminal session, and rerun Refresh."
|
||||
},
|
||||
"autoCombo": {
|
||||
"title": "Auto-Combo Engine",
|
||||
@@ -2527,5 +2638,56 @@
|
||||
"modePackCostSaver": "Cost Saver",
|
||||
"modePackQualityFirst": "Quality First",
|
||||
"modePackOfflineFriendly": "Offline Friendly"
|
||||
},
|
||||
"search": {
|
||||
"searchQuery": "Search Query",
|
||||
"searchResults": "Search Results",
|
||||
"cachedResult": "Cached",
|
||||
"searchCost": "Cost",
|
||||
"searchTools": "Search Tools",
|
||||
"searchToolsDesc": "Advanced search testing with provider comparison",
|
||||
"compareProviders": "Compare Providers",
|
||||
"rerankResults": "Rerank Results",
|
||||
"searchHistory": "Search History",
|
||||
"urlOverlap": "URL Overlap",
|
||||
"noSearchProviders": "No search providers configured. Add providers in Settings.",
|
||||
"noRerankModels": "No rerank model available",
|
||||
"webSearch": "Web Search",
|
||||
"provider": "Provider",
|
||||
"searchType": "Search Type",
|
||||
"maxResults": "Max Results",
|
||||
"filters": "Filters",
|
||||
"country": "Country",
|
||||
"language": "Language",
|
||||
"timeRange": "Time Range",
|
||||
"includeDomains": "Include Domains",
|
||||
"excludeDomains": "Exclude Domains",
|
||||
"safeSearch": "Safe Search",
|
||||
"formatted": "Formatted",
|
||||
"rawJson": "JSON",
|
||||
"cacheMiss": "cache miss",
|
||||
"cacheHit": "cache hit",
|
||||
"latency": "Latency",
|
||||
"cost": "Cost",
|
||||
"results": "Results",
|
||||
"rerank": "Rerank",
|
||||
"rerankModel": "Rerank Model",
|
||||
"positionDelta": "Position Change",
|
||||
"emptyState": "Send a search query to see results",
|
||||
"safeSearchOff": "Off",
|
||||
"safeSearchModerate": "Moderate",
|
||||
"safeSearchStrict": "Strict",
|
||||
"queryPlaceholder": "Enter search query...",
|
||||
"providerAuto": "auto (cheapest)",
|
||||
"searchTypeWeb": "web",
|
||||
"searchTypeNews": "news",
|
||||
"optionAny": "any",
|
||||
"timeRangeDay": "Past day",
|
||||
"timeRangeWeek": "Past week",
|
||||
"timeRangeMonth": "Past month",
|
||||
"timeRangeYear": "Past year",
|
||||
"domainPlaceholder": "example.com",
|
||||
"requestTimedOut": "Request timed out ({seconds}s)",
|
||||
"networkError": "Network error"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,7 +56,9 @@
|
||||
"warning": "警告",
|
||||
"note": "注記",
|
||||
"free": "無料",
|
||||
"skipToContent": "コンテンツにスキップ"
|
||||
"skipToContent": "コンテンツにスキップ",
|
||||
"maintenanceServerIssues": "Server is experiencing issues. Some features may be unavailable.",
|
||||
"maintenanceServerUnreachable": "Server is unreachable. Reconnecting..."
|
||||
},
|
||||
"sidebar": {
|
||||
"home": "ホーム",
|
||||
@@ -103,7 +105,9 @@
|
||||
"endpoints": "エンドポイント",
|
||||
"playground": "プレイグラウンド",
|
||||
"agents": "エージェント",
|
||||
"cliToolsShort": "ツール"
|
||||
"cliToolsShort": "ツール",
|
||||
"autoCombo": "Auto Combo",
|
||||
"searchTools": "Search Tools"
|
||||
},
|
||||
"themesPage": {
|
||||
"title": "Themes",
|
||||
@@ -268,7 +272,28 @@
|
||||
"permissionsTitle": "権限: {name}",
|
||||
"allowAllDesc": "このキーは、利用可能なすべてのモデルにアクセスできます。",
|
||||
"restrictDesc": "このキーは、{totalModels} モデルの {selectedCount} にアクセスできます。",
|
||||
"selectedCount": "{count} が選択されました"
|
||||
"selectedCount": "{count} が選択されました",
|
||||
"autoResolve": "Auto-Resolve",
|
||||
"autoResolveDesc": "Auto-resolve ambiguous model names to native provider for this API key.",
|
||||
"keyActive": "Key Active",
|
||||
"keyActiveDesc": "Enable or disable this API key. Disabled keys are immediately rejected with 403.",
|
||||
"accessSchedule": "Access Schedule",
|
||||
"accessScheduleDesc": "Restrict access to specific hours and days of the week.",
|
||||
"scheduleFrom": "From",
|
||||
"scheduleUntil": "Until",
|
||||
"scheduleDays": "Days",
|
||||
"scheduleTimezone": "Timezone",
|
||||
"scheduleTimezoneHint": "Use IANA timezone names, e.g. America/New_York, Europe/Berlin",
|
||||
"scheduleActive": "Schedule",
|
||||
"disabled": "Disabled",
|
||||
"daySun": "Sun",
|
||||
"dayMon": "Mon",
|
||||
"dayTue": "Tue",
|
||||
"dayWed": "Wed",
|
||||
"dayThu": "Thu",
|
||||
"dayFri": "Fri",
|
||||
"daySat": "Sat",
|
||||
"keyOnlyAvailableAtCreation": "Full key available only at creation time — copy it when you first create the key"
|
||||
},
|
||||
"auditLog": {
|
||||
"title": "監査ログ",
|
||||
@@ -548,6 +573,27 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"mitmHowWorksDesc": "{toolName} sends requests to its provider endpoint. MITM intercepts and redirects them to OmniRoute.",
|
||||
"mitmStep1": "1. Start MITM to route requests through OmniRoute.",
|
||||
"mitmStep2Prefix": "2. Add",
|
||||
"mitmStep2Suffix": "to your hosts file as 127.0.0.1.",
|
||||
"mitmStep3": "3. Open {toolName} and requests will be proxied.",
|
||||
"whenToUseLabel": "いつ使用するか",
|
||||
"openToolDocs": "ツールドキュメントを開く",
|
||||
"toolUseCases": {
|
||||
"claude": "クロード コードを使用して強力な計画ワークフローと長い複数ファイルのリファクタリングが必要な場合に使用します。",
|
||||
"codex": "チームが OpenAI Codex CLI フローとプロファイルベースの認証で標準化されている場合に使用します。",
|
||||
"droid": "高速コーディングとコマンド実行ループに重点を置いた軽量のターミナル エージェントが必要な場合に使用します。",
|
||||
"openclaw": "Open Claw スタイルのコーディング エージェントが必要だが、OmniRoute ポリシーを通じてルーティングされる場合に使用します。",
|
||||
"cline": "エディター内でコーディング エージェントを構成し、OmniRoute モデルを使用したガイド付きセットアップが必要な場合に使用します。",
|
||||
"kilo": "ワークフローが Kilo Code コマンドと高速反復編集に依存している場合に使用します。",
|
||||
"cursor": "Cursor でコーディングし、OmniRoute を介してカスタム OpenAI 互換モデルが必要な場合に使用します。",
|
||||
"continue": "IDE で続行を実行し、移植可能な JSON ベースのプロバイダー構成が必要な場合に使用します。",
|
||||
"opencode": "ターミナルネイティブのエージェントの実行と OpenCode によるスクリプトによる自動化を希望する場合に使用します。",
|
||||
"kiro": "Kiro を統合し、OmniRoute からモデルのルーティングを一元的に制御する場合に使用します。",
|
||||
"antigravity": "Antigravity/Kiro トラフィックを MITM 経由でインターセプトし、OmniRoute にルーティングする必要がある場合に使用します。",
|
||||
"copilot": "OmniRoute キーとルーティング ルールを適用しながら、Copilot チャット スタイルの UX が必要な場合に使用します。"
|
||||
}
|
||||
},
|
||||
"combos": {
|
||||
@@ -864,7 +910,12 @@
|
||||
"a2aQuickStartStep2": "Send JSON-RPC requests to `POST /a2a` using `message/send` or `message/stream`.",
|
||||
"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"
|
||||
"completionsLegacyDesc": "Legacy OpenAI text completions — accepts both prompt string and messages array format",
|
||||
"categorySearch": "Search & Discovery",
|
||||
"webSearch": "Web Search",
|
||||
"webSearchDesc": "Unified web search across multiple providers with automatic failover and caching",
|
||||
"searchProvider": "Search Provider",
|
||||
"searchProviderDesc": "This provider is used for web search via POST /v1/search. No model configuration needed — search providers are ready to use once an API key is connected."
|
||||
},
|
||||
"mcpDashboard": {
|
||||
"loading": "Loading MCP dashboard...",
|
||||
@@ -1054,7 +1105,13 @@
|
||||
"resetAllTitle": "すべてのサーキットブレーカーを正常な状態にリセットします",
|
||||
"resetting": "リセット中...",
|
||||
"resetAll": "すべてリセット",
|
||||
"until": "{time} まで"
|
||||
"until": "{time} まで",
|
||||
"activeProviders": "{count} active",
|
||||
"monitoredProviders": "{count} monitored",
|
||||
"configuredProvidersLabel": "ダッシュボードで設定",
|
||||
"configuredProvidersHint": "実行時の状態に関係なく、認証情報を持つプロバイダーは /dashboard/providers に保存されます。",
|
||||
"activeProvidersHint": "リクエストのルーティングが現在有効になっている構成済みプロバイダー。",
|
||||
"monitoredProvidersHint": "現在、プロバイダーはサーキット ブレーカー ヘルス モニターによって追跡されています。"
|
||||
},
|
||||
"limits": {
|
||||
"title": "制限と割り当て",
|
||||
@@ -1396,7 +1453,31 @@
|
||||
"chatPathHint": "Custom chat path for providers with non-standard APIs (e.g. /v4/chat/completions)",
|
||||
"modelsPathLabel": "Models Endpoint Path",
|
||||
"modelsPathPlaceholder": "/models",
|
||||
"modelsPathHint": "Custom models path for validation (e.g. /v4/models)"
|
||||
"modelsPathHint": "Custom models path for validation (e.g. /v4/models)",
|
||||
"builtInModels": "Built-in models",
|
||||
"builtInModelsHint": "Registry models for this provider. Use the pencil to set compatibility options.",
|
||||
"normalizeToolCallIdLabel": "Normalize tool call IDs to 9 characters (e.g. Mistral)",
|
||||
"preserveDeveloperRoleLabel": "Keep OpenAI Responses developer role (do not map to system)",
|
||||
"compatAdjustmentsTitle": "Compatibility",
|
||||
"compatButtonLabel": "Compatibility",
|
||||
"compatToolIdShort": "Tool ID 9",
|
||||
"compatDeveloperShort": "Developer role",
|
||||
"compatDoNotPreserveDeveloper": "Do not preserve developer role",
|
||||
"compatBadgeNoPreserve": "No preserve",
|
||||
"compatProtocolLabel": "Client request protocol",
|
||||
"compatProtocolHint": "These options apply when OmniRoute detects this request shape (OpenAI Chat, Responses API, or Anthropic Messages).",
|
||||
"compatProtocolOpenAI": "OpenAI Chat Completions",
|
||||
"compatProtocolOpenAIResponses": "OpenAI Responses API",
|
||||
"compatProtocolClaude": "Anthropic Messages",
|
||||
"tokenRefreshed": "Token refreshed successfully",
|
||||
"tokenRefreshFailed": "Token refresh failed",
|
||||
"compatBadgeUpstreamHeaders": "Headers",
|
||||
"compatUpstreamAddRow": "Add header",
|
||||
"compatUpstreamHeaderName": "Header name",
|
||||
"compatUpstreamHeaderValue": "Value",
|
||||
"compatUpstreamHeadersHint": "High-privilege setting — same trust level as editing provider API credentials; only trusted admins should use it. Merged after OmniRoute adds auth from the provider API key. If a custom header uses the same name as an existing one (e.g. Authorization), your value fully replaces the auto-generated header (including the Bearer token) — the upstream only sees what you typed, not the key from settings. Misconfiguration can cause 401 or broken upstream auth. One row per header (e.g. extra Authentication for some gateways). Hover or focus the value to preview. Saves on blur, outside click, or closing this panel.",
|
||||
"compatUpstreamHeadersLabel": "Extra upstream headers",
|
||||
"compatUpstreamRemoveRow": "Remove row"
|
||||
},
|
||||
"settings": {
|
||||
"title": "設定",
|
||||
@@ -1799,7 +1880,13 @@
|
||||
"cliFingerprintDesc": "Match native CLI binary signatures when proxying requests. Reorders headers and body fields to look identical to the official CLI tools. Your proxy IP is preserved.",
|
||||
"cliFingerprintEnabled": "{count} provider(s) with CLI fingerprint active",
|
||||
"enableFingerprintTitle": "Enable fingerprint for {provider}",
|
||||
"disableFingerprintTitle": "Disable fingerprint for {provider}"
|
||||
"disableFingerprintTitle": "Disable fingerprint for {provider}",
|
||||
"routingAdvancedGuideTitle": "高度なルーティング ガイダンス",
|
||||
"routingAdvancedGuideHint1": "予測可能な優先度を得るには Fill First を使用し、公平性を得るにはラウンド ロビンを、レイテンシの回復力を得るには P2C を使用します。",
|
||||
"routingAdvancedGuideHint2": "プロバイダーによって品質/コストが異なる場合は、バックグラウンド作業についてはコスト最適化から開始し、バランスのとれた摩耗については最も使用されないようにします。",
|
||||
"comboDefaultsGuideTitle": "コンボのデフォルトを調整する方法",
|
||||
"comboDefaultsGuideHint1": "低遅延フローでは再試行を低く抑えます。長い世代のタスクの場合にのみタイムアウトを増やします。",
|
||||
"comboDefaultsGuideHint2": "1 つのプロバイダーがグローバルなデフォルトとは異なるタイムアウト/再試行動作を必要とする場合は、プロバイダー オーバーライドを使用します。"
|
||||
},
|
||||
"translator": {
|
||||
"title": "翻訳者",
|
||||
@@ -2216,7 +2303,8 @@
|
||||
"orRemovePasswordHashField": "または、passwordHash フィールドを削除します",
|
||||
"restartServerWithNewPassword": "サーバーを再起動すると新しいパスワードが適用されます",
|
||||
"backToLogin": "ログインに戻る",
|
||||
"forgotPassword": "パスワードをお忘れですか?"
|
||||
"forgotPassword": "パスワードをお忘れですか?",
|
||||
"defaultPasswordHint": "Default password: 123456 (unless INITIAL_PASSWORD was set)"
|
||||
},
|
||||
"landing": {
|
||||
"brandName": "オムニルート",
|
||||
@@ -2414,7 +2502,16 @@
|
||||
"troubleshootingCodexFamily": "GitHub Codex ファミリ モデルの場合、モデルを gh/codex-model として保持します。ルーターは自動的に選択/応答します。",
|
||||
"troubleshootingTestConnection": "IDE または外部クライアントからテストする前に、[ダッシュボード] > [プロバイダー] > [接続のテスト] を使用します。",
|
||||
"troubleshootingCircuitBreaker": "プロバイダーがサーキット ブレーカーが開いていることを示している場合は、クールダウンするまで待つか、詳細について [ヘルス] ページを確認してください。",
|
||||
"troubleshootingOAuth": "OAuth プロバイダーの場合、トークンの有効期限が切れた場合は再認証します。プロバイダー カードのステータス インジケーターを確認します。"
|
||||
"troubleshootingOAuth": "OAuth プロバイダーの場合、トークンの有効期限が切れた場合は再認証します。プロバイダー カードのステータス インジケーターを確認します。",
|
||||
"managementApiReference": "Management API Reference",
|
||||
"managementApiDescription": "Automation endpoints for proxy registry, scope assignments, and legacy proxy migration.",
|
||||
"mgmtProxiesListNote": "List saved proxy registry items (supports pagination).",
|
||||
"mgmtProxiesCreateNote": "Create a reusable proxy item in the registry.",
|
||||
"mgmtProxiesHealthNote": "Get 24h/rolling health metrics per saved proxy from proxy logs.",
|
||||
"mgmtProxiesBulkAssignNote": "Assign or clear one proxy across many scope IDs in one request.",
|
||||
"mgmtAssignmentsListNote": "List proxy assignments by scope, scope_id, or proxy_id.",
|
||||
"mgmtAssignmentsUpdateNote": "Assign or clear proxy for global/provider/account/combo scope.",
|
||||
"mgmtLegacyMigrationNote": "Import legacy proxyConfig maps into registry assignments."
|
||||
},
|
||||
"legal": {
|
||||
"privacyPolicy": "プライバシーポリシー",
|
||||
@@ -2503,7 +2600,21 @@
|
||||
"binaryName": "Binary Name",
|
||||
"versionCommand": "Version Command",
|
||||
"spawnArgs": "Spawn Args",
|
||||
"addAgent": "Add Agent"
|
||||
"addAgent": "Add Agent",
|
||||
"scanning": "Scanning system for CLI agents...",
|
||||
"opencodeIntegration": "OpenCode Integration",
|
||||
"opencodeDetected": "opencode {version} detected",
|
||||
"opencodeDesc": "Generate a ready-to-use {configFile} with your OmniRoute base URL and all available models — drop it in your project root and run {command}.",
|
||||
"downloadConfig": "Download {file}",
|
||||
"downloaded": "Downloaded!",
|
||||
"setupGuideTitle": "Setup guide",
|
||||
"openCliTools": "Open CLI Tools",
|
||||
"setupGuideDetectCliTitle": "Detect installed CLIs",
|
||||
"setupGuideDetectCliDesc": "Click Refresh after installing or updating a CLI so OmniRoute can rescan binaries and versions.",
|
||||
"setupGuideCustomAgentTitle": "Register custom binary",
|
||||
"setupGuideCustomAgentDesc": "Use Add Custom Agent when your CLI is not in the built-in list. Provide binary name and version command.",
|
||||
"setupGuideCommandMissingTitle": "Fix 'command not found'",
|
||||
"setupGuideCommandMissingDesc": "Ensure the CLI command exists in PATH, open a new terminal session, and rerun Refresh."
|
||||
},
|
||||
"autoCombo": {
|
||||
"title": "Auto-Combo Engine",
|
||||
@@ -2527,5 +2638,56 @@
|
||||
"modePackCostSaver": "Cost Saver",
|
||||
"modePackQualityFirst": "Quality First",
|
||||
"modePackOfflineFriendly": "Offline Friendly"
|
||||
},
|
||||
"search": {
|
||||
"searchQuery": "Search Query",
|
||||
"searchResults": "Search Results",
|
||||
"cachedResult": "Cached",
|
||||
"searchCost": "Cost",
|
||||
"searchTools": "Search Tools",
|
||||
"searchToolsDesc": "Advanced search testing with provider comparison",
|
||||
"compareProviders": "Compare Providers",
|
||||
"rerankResults": "Rerank Results",
|
||||
"searchHistory": "Search History",
|
||||
"urlOverlap": "URL Overlap",
|
||||
"noSearchProviders": "No search providers configured. Add providers in Settings.",
|
||||
"noRerankModels": "No rerank model available",
|
||||
"webSearch": "Web Search",
|
||||
"provider": "Provider",
|
||||
"searchType": "Search Type",
|
||||
"maxResults": "Max Results",
|
||||
"filters": "Filters",
|
||||
"country": "Country",
|
||||
"language": "Language",
|
||||
"timeRange": "Time Range",
|
||||
"includeDomains": "Include Domains",
|
||||
"excludeDomains": "Exclude Domains",
|
||||
"safeSearch": "Safe Search",
|
||||
"formatted": "Formatted",
|
||||
"rawJson": "JSON",
|
||||
"cacheMiss": "cache miss",
|
||||
"cacheHit": "cache hit",
|
||||
"latency": "Latency",
|
||||
"cost": "Cost",
|
||||
"results": "Results",
|
||||
"rerank": "Rerank",
|
||||
"rerankModel": "Rerank Model",
|
||||
"positionDelta": "Position Change",
|
||||
"emptyState": "Send a search query to see results",
|
||||
"safeSearchOff": "Off",
|
||||
"safeSearchModerate": "Moderate",
|
||||
"safeSearchStrict": "Strict",
|
||||
"queryPlaceholder": "Enter search query...",
|
||||
"providerAuto": "auto (cheapest)",
|
||||
"searchTypeWeb": "web",
|
||||
"searchTypeNews": "news",
|
||||
"optionAny": "any",
|
||||
"timeRangeDay": "Past day",
|
||||
"timeRangeWeek": "Past week",
|
||||
"timeRangeMonth": "Past month",
|
||||
"timeRangeYear": "Past year",
|
||||
"domainPlaceholder": "example.com",
|
||||
"requestTimedOut": "Request timed out ({seconds}s)",
|
||||
"networkError": "Network error"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,7 +56,9 @@
|
||||
"warning": "경고",
|
||||
"note": "참고",
|
||||
"free": "무료",
|
||||
"skipToContent": "콘텐츠로 건너뛰기"
|
||||
"skipToContent": "콘텐츠로 건너뛰기",
|
||||
"maintenanceServerIssues": "Server is experiencing issues. Some features may be unavailable.",
|
||||
"maintenanceServerUnreachable": "Server is unreachable. Reconnecting..."
|
||||
},
|
||||
"sidebar": {
|
||||
"home": "홈",
|
||||
@@ -103,7 +105,9 @@
|
||||
"endpoints": "엔드포인트",
|
||||
"playground": "플레이그라운드",
|
||||
"agents": "에이전트",
|
||||
"cliToolsShort": "도구"
|
||||
"cliToolsShort": "도구",
|
||||
"autoCombo": "Auto Combo",
|
||||
"searchTools": "Search Tools"
|
||||
},
|
||||
"themesPage": {
|
||||
"title": "Themes",
|
||||
@@ -268,7 +272,28 @@
|
||||
"permissionsTitle": "권한: {name}",
|
||||
"allowAllDesc": "이 키는 사용 가능한 모든 모델에 액세스할 수 있습니다.",
|
||||
"restrictDesc": "이 키는 {totalModels} 모델 중 {selectedCount}에 액세스할 수 있습니다.",
|
||||
"selectedCount": "{count} 선택됨"
|
||||
"selectedCount": "{count} 선택됨",
|
||||
"autoResolve": "Auto-Resolve",
|
||||
"autoResolveDesc": "Auto-resolve ambiguous model names to native provider for this API key.",
|
||||
"keyActive": "Key Active",
|
||||
"keyActiveDesc": "Enable or disable this API key. Disabled keys are immediately rejected with 403.",
|
||||
"accessSchedule": "Access Schedule",
|
||||
"accessScheduleDesc": "Restrict access to specific hours and days of the week.",
|
||||
"scheduleFrom": "From",
|
||||
"scheduleUntil": "Until",
|
||||
"scheduleDays": "Days",
|
||||
"scheduleTimezone": "Timezone",
|
||||
"scheduleTimezoneHint": "Use IANA timezone names, e.g. America/New_York, Europe/Berlin",
|
||||
"scheduleActive": "Schedule",
|
||||
"disabled": "Disabled",
|
||||
"daySun": "Sun",
|
||||
"dayMon": "Mon",
|
||||
"dayTue": "Tue",
|
||||
"dayWed": "Wed",
|
||||
"dayThu": "Thu",
|
||||
"dayFri": "Fri",
|
||||
"daySat": "Sat",
|
||||
"keyOnlyAvailableAtCreation": "Full key available only at creation time — copy it when you first create the key"
|
||||
},
|
||||
"auditLog": {
|
||||
"title": "감사 로그",
|
||||
@@ -548,6 +573,27 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"mitmHowWorksDesc": "{toolName} sends requests to its provider endpoint. MITM intercepts and redirects them to OmniRoute.",
|
||||
"mitmStep1": "1. Start MITM to route requests through OmniRoute.",
|
||||
"mitmStep2Prefix": "2. Add",
|
||||
"mitmStep2Suffix": "to your hosts file as 127.0.0.1.",
|
||||
"mitmStep3": "3. Open {toolName} and requests will be proxied.",
|
||||
"whenToUseLabel": "언제 사용하나요?",
|
||||
"openToolDocs": "도구 문서 열기",
|
||||
"toolUseCases": {
|
||||
"claude": "Claude Code를 사용하여 강력한 계획 워크플로와 긴 다중 파일 리팩터링을 원할 때 사용하세요.",
|
||||
"codex": "팀이 OpenAI Codex CLI 흐름 및 프로필 기반 인증으로 표준화된 경우에 사용하세요.",
|
||||
"droid": "빠른 코딩 및 명령 실행 루프에 초점을 맞춘 경량 터미널 에이전트가 필요할 때 사용하세요.",
|
||||
"openclaw": "Open Claw 스타일 코딩 에이전트를 원하지만 OmniRoute 정책을 통해 라우팅되는 경우에 사용합니다.",
|
||||
"cline": "편집기 내에서 코딩 에이전트를 구성하고 OmniRoute 모델을 사용하여 안내 설정을 원하는 경우 사용합니다.",
|
||||
"kilo": "작업 흐름이 Kilo Code 명령과 빠른 반복 편집에 의존하는 경우에 사용하세요.",
|
||||
"cursor": "Cursor에서 코딩할 때 사용하고 OmniRoute를 통해 사용자 정의 OpenAI 호환 모델이 필요합니다.",
|
||||
"continue": "IDE에서 계속을 실행할 때 사용하고 이식 가능한 JSON 기반 공급자 구성이 필요합니다.",
|
||||
"opencode": "OpenCode를 통해 터미널 기반 에이전트 실행 및 스크립트 자동화를 선호할 때 사용하세요.",
|
||||
"kiro": "Kiro를 통합하고 OmniRoute에서 중앙에서 모델 라우팅을 제어할 때 사용합니다.",
|
||||
"antigravity": "Antigravity/Kiro 트래픽이 MITM을 통해 가로채어 OmniRoute로 라우팅되어야 하는 경우에 사용합니다.",
|
||||
"copilot": "OmniRoute 키와 라우팅 규칙을 적용하면서 Copilot 채팅 스타일 UX를 원할 때 사용하세요."
|
||||
}
|
||||
},
|
||||
"combos": {
|
||||
@@ -864,7 +910,12 @@
|
||||
"a2aQuickStartStep2": "Send JSON-RPC requests to `POST /a2a` using `message/send` or `message/stream`.",
|
||||
"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"
|
||||
"completionsLegacyDesc": "Legacy OpenAI text completions — accepts both prompt string and messages array format",
|
||||
"categorySearch": "Search & Discovery",
|
||||
"webSearch": "Web Search",
|
||||
"webSearchDesc": "Unified web search across multiple providers with automatic failover and caching",
|
||||
"searchProvider": "Search Provider",
|
||||
"searchProviderDesc": "This provider is used for web search via POST /v1/search. No model configuration needed — search providers are ready to use once an API key is connected."
|
||||
},
|
||||
"mcpDashboard": {
|
||||
"loading": "Loading MCP dashboard...",
|
||||
@@ -1054,7 +1105,13 @@
|
||||
"resetAllTitle": "모든 회로 차단기를 정상 상태로 재설정",
|
||||
"resetting": "재설정 중...",
|
||||
"resetAll": "모두 재설정",
|
||||
"until": "{time}까지"
|
||||
"until": "{time}까지",
|
||||
"activeProviders": "{count} active",
|
||||
"monitoredProviders": "{count} monitored",
|
||||
"configuredProvidersLabel": "대시보드에 구성됨",
|
||||
"configuredProvidersHint": "런타임 상태에 관계없이 /dashboard/providers에 자격 증명이 저장된 공급자입니다.",
|
||||
"activeProvidersHint": "현재 요청 라우팅을 위해 활성화된 구성된 공급자입니다.",
|
||||
"monitoredProvidersHint": "현재 회로 차단기 상태 모니터로 추적되는 공급자입니다."
|
||||
},
|
||||
"limits": {
|
||||
"title": "한도 및 할당량",
|
||||
@@ -1396,7 +1453,31 @@
|
||||
"chatPathHint": "Custom chat path for providers with non-standard APIs (e.g. /v4/chat/completions)",
|
||||
"modelsPathLabel": "Models Endpoint Path",
|
||||
"modelsPathPlaceholder": "/models",
|
||||
"modelsPathHint": "Custom models path for validation (e.g. /v4/models)"
|
||||
"modelsPathHint": "Custom models path for validation (e.g. /v4/models)",
|
||||
"builtInModels": "Built-in models",
|
||||
"builtInModelsHint": "Registry models for this provider. Use the pencil to set compatibility options.",
|
||||
"normalizeToolCallIdLabel": "Normalize tool call IDs to 9 characters (e.g. Mistral)",
|
||||
"preserveDeveloperRoleLabel": "Keep OpenAI Responses developer role (do not map to system)",
|
||||
"compatAdjustmentsTitle": "Compatibility",
|
||||
"compatButtonLabel": "Compatibility",
|
||||
"compatToolIdShort": "Tool ID 9",
|
||||
"compatDeveloperShort": "Developer role",
|
||||
"compatDoNotPreserveDeveloper": "Do not preserve developer role",
|
||||
"compatBadgeNoPreserve": "No preserve",
|
||||
"compatProtocolLabel": "Client request protocol",
|
||||
"compatProtocolHint": "These options apply when OmniRoute detects this request shape (OpenAI Chat, Responses API, or Anthropic Messages).",
|
||||
"compatProtocolOpenAI": "OpenAI Chat Completions",
|
||||
"compatProtocolOpenAIResponses": "OpenAI Responses API",
|
||||
"compatProtocolClaude": "Anthropic Messages",
|
||||
"tokenRefreshed": "Token refreshed successfully",
|
||||
"tokenRefreshFailed": "Token refresh failed",
|
||||
"compatBadgeUpstreamHeaders": "Headers",
|
||||
"compatUpstreamAddRow": "Add header",
|
||||
"compatUpstreamHeaderName": "Header name",
|
||||
"compatUpstreamHeaderValue": "Value",
|
||||
"compatUpstreamHeadersHint": "High-privilege setting — same trust level as editing provider API credentials; only trusted admins should use it. Merged after OmniRoute adds auth from the provider API key. If a custom header uses the same name as an existing one (e.g. Authorization), your value fully replaces the auto-generated header (including the Bearer token) — the upstream only sees what you typed, not the key from settings. Misconfiguration can cause 401 or broken upstream auth. One row per header (e.g. extra Authentication for some gateways). Hover or focus the value to preview. Saves on blur, outside click, or closing this panel.",
|
||||
"compatUpstreamHeadersLabel": "Extra upstream headers",
|
||||
"compatUpstreamRemoveRow": "Remove row"
|
||||
},
|
||||
"settings": {
|
||||
"title": "설정",
|
||||
@@ -1799,7 +1880,13 @@
|
||||
"cliFingerprintDesc": "Match native CLI binary signatures when proxying requests. Reorders headers and body fields to look identical to the official CLI tools. Your proxy IP is preserved.",
|
||||
"cliFingerprintEnabled": "{count} provider(s) with CLI fingerprint active",
|
||||
"enableFingerprintTitle": "Enable fingerprint for {provider}",
|
||||
"disableFingerprintTitle": "Disable fingerprint for {provider}"
|
||||
"disableFingerprintTitle": "Disable fingerprint for {provider}",
|
||||
"routingAdvancedGuideTitle": "고급 경로 안내",
|
||||
"routingAdvancedGuideHint1": "예측 가능한 우선순위를 위해서는 Fill First를 사용하고, 공정성을 위해서는 Round Robin을, 지연 시간 복원력을 위해서는 P2C를 사용하세요.",
|
||||
"routingAdvancedGuideHint2": "서비스 제공업체의 품질/비용이 다양한 경우 백그라운드 작업에는 비용 선택(Cost Opt)으로 시작하고 균형 잡힌 착용에는 최소 사용(Least Used)으로 시작하세요.",
|
||||
"comboDefaultsGuideTitle": "콤보 기본값을 조정하는 방법",
|
||||
"comboDefaultsGuideHint1": "지연 시간이 짧은 흐름에서는 재시도 횟수를 낮게 유지하세요. 긴 세대 작업에 대해서만 시간 제한을 늘립니다.",
|
||||
"comboDefaultsGuideHint2": "하나의 공급자가 전역 기본값과 다른 시간 초과/재시도 동작을 필요로 하는 경우 공급자 재정의를 사용합니다."
|
||||
},
|
||||
"translator": {
|
||||
"title": "번역기",
|
||||
@@ -2216,7 +2303,8 @@
|
||||
"orRemovePasswordHashField": "또는 PasswordHash 필드를 제거하세요.",
|
||||
"restartServerWithNewPassword": "서버를 다시 시작하세요. 새 비밀번호가 사용됩니다.",
|
||||
"backToLogin": "로그인으로 돌아가기",
|
||||
"forgotPassword": "비밀번호를 잊으셨나요?"
|
||||
"forgotPassword": "비밀번호를 잊으셨나요?",
|
||||
"defaultPasswordHint": "Default password: 123456 (unless INITIAL_PASSWORD was set)"
|
||||
},
|
||||
"landing": {
|
||||
"brandName": "옴니루트",
|
||||
@@ -2414,7 +2502,16 @@
|
||||
"troubleshootingCodexFamily": "GitHub Codex 계열 모델의 경우 모델을 gh/codex-model로 유지합니다. 라우터는 /responses를 자동으로 선택합니다.",
|
||||
"troubleshootingTestConnection": "IDE 또는 외부 클라이언트에서 테스트하기 전에 대시보드 > 공급자 > 연결 테스트를 사용하세요.",
|
||||
"troubleshootingCircuitBreaker": "공급자가 회로 차단기를 열었다고 표시하는 경우 대기 시간을 기다리거나 상태 페이지에서 자세한 내용을 확인하세요.",
|
||||
"troubleshootingOAuth": "OAuth 공급자의 경우 토큰이 만료되면 다시 인증하세요. 공급자 카드 상태 표시기를 확인하십시오."
|
||||
"troubleshootingOAuth": "OAuth 공급자의 경우 토큰이 만료되면 다시 인증하세요. 공급자 카드 상태 표시기를 확인하십시오.",
|
||||
"managementApiReference": "Management API Reference",
|
||||
"managementApiDescription": "Automation endpoints for proxy registry, scope assignments, and legacy proxy migration.",
|
||||
"mgmtProxiesListNote": "List saved proxy registry items (supports pagination).",
|
||||
"mgmtProxiesCreateNote": "Create a reusable proxy item in the registry.",
|
||||
"mgmtProxiesHealthNote": "Get 24h/rolling health metrics per saved proxy from proxy logs.",
|
||||
"mgmtProxiesBulkAssignNote": "Assign or clear one proxy across many scope IDs in one request.",
|
||||
"mgmtAssignmentsListNote": "List proxy assignments by scope, scope_id, or proxy_id.",
|
||||
"mgmtAssignmentsUpdateNote": "Assign or clear proxy for global/provider/account/combo scope.",
|
||||
"mgmtLegacyMigrationNote": "Import legacy proxyConfig maps into registry assignments."
|
||||
},
|
||||
"legal": {
|
||||
"privacyPolicy": "개인 정보 보호 정책",
|
||||
@@ -2503,7 +2600,21 @@
|
||||
"binaryName": "Binary Name",
|
||||
"versionCommand": "Version Command",
|
||||
"spawnArgs": "Spawn Args",
|
||||
"addAgent": "Add Agent"
|
||||
"addAgent": "Add Agent",
|
||||
"scanning": "Scanning system for CLI agents...",
|
||||
"opencodeIntegration": "OpenCode Integration",
|
||||
"opencodeDetected": "opencode {version} detected",
|
||||
"opencodeDesc": "Generate a ready-to-use {configFile} with your OmniRoute base URL and all available models — drop it in your project root and run {command}.",
|
||||
"downloadConfig": "Download {file}",
|
||||
"downloaded": "Downloaded!",
|
||||
"setupGuideTitle": "Setup guide",
|
||||
"openCliTools": "Open CLI Tools",
|
||||
"setupGuideDetectCliTitle": "Detect installed CLIs",
|
||||
"setupGuideDetectCliDesc": "Click Refresh after installing or updating a CLI so OmniRoute can rescan binaries and versions.",
|
||||
"setupGuideCustomAgentTitle": "Register custom binary",
|
||||
"setupGuideCustomAgentDesc": "Use Add Custom Agent when your CLI is not in the built-in list. Provide binary name and version command.",
|
||||
"setupGuideCommandMissingTitle": "Fix 'command not found'",
|
||||
"setupGuideCommandMissingDesc": "Ensure the CLI command exists in PATH, open a new terminal session, and rerun Refresh."
|
||||
},
|
||||
"autoCombo": {
|
||||
"title": "Auto-Combo Engine",
|
||||
@@ -2527,5 +2638,56 @@
|
||||
"modePackCostSaver": "Cost Saver",
|
||||
"modePackQualityFirst": "Quality First",
|
||||
"modePackOfflineFriendly": "Offline Friendly"
|
||||
},
|
||||
"search": {
|
||||
"searchQuery": "Search Query",
|
||||
"searchResults": "Search Results",
|
||||
"cachedResult": "Cached",
|
||||
"searchCost": "Cost",
|
||||
"searchTools": "Search Tools",
|
||||
"searchToolsDesc": "Advanced search testing with provider comparison",
|
||||
"compareProviders": "Compare Providers",
|
||||
"rerankResults": "Rerank Results",
|
||||
"searchHistory": "Search History",
|
||||
"urlOverlap": "URL Overlap",
|
||||
"noSearchProviders": "No search providers configured. Add providers in Settings.",
|
||||
"noRerankModels": "No rerank model available",
|
||||
"webSearch": "Web Search",
|
||||
"provider": "Provider",
|
||||
"searchType": "Search Type",
|
||||
"maxResults": "Max Results",
|
||||
"filters": "Filters",
|
||||
"country": "Country",
|
||||
"language": "Language",
|
||||
"timeRange": "Time Range",
|
||||
"includeDomains": "Include Domains",
|
||||
"excludeDomains": "Exclude Domains",
|
||||
"safeSearch": "Safe Search",
|
||||
"formatted": "Formatted",
|
||||
"rawJson": "JSON",
|
||||
"cacheMiss": "cache miss",
|
||||
"cacheHit": "cache hit",
|
||||
"latency": "Latency",
|
||||
"cost": "Cost",
|
||||
"results": "Results",
|
||||
"rerank": "Rerank",
|
||||
"rerankModel": "Rerank Model",
|
||||
"positionDelta": "Position Change",
|
||||
"emptyState": "Send a search query to see results",
|
||||
"safeSearchOff": "Off",
|
||||
"safeSearchModerate": "Moderate",
|
||||
"safeSearchStrict": "Strict",
|
||||
"queryPlaceholder": "Enter search query...",
|
||||
"providerAuto": "auto (cheapest)",
|
||||
"searchTypeWeb": "web",
|
||||
"searchTypeNews": "news",
|
||||
"optionAny": "any",
|
||||
"timeRangeDay": "Past day",
|
||||
"timeRangeWeek": "Past week",
|
||||
"timeRangeMonth": "Past month",
|
||||
"timeRangeYear": "Past year",
|
||||
"domainPlaceholder": "example.com",
|
||||
"requestTimedOut": "Request timed out ({seconds}s)",
|
||||
"networkError": "Network error"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,7 +56,9 @@
|
||||
"warning": "Amaran",
|
||||
"note": "Nota",
|
||||
"free": "Percuma",
|
||||
"skipToContent": "Langkau ke kandungan"
|
||||
"skipToContent": "Langkau ke kandungan",
|
||||
"maintenanceServerIssues": "Server is experiencing issues. Some features may be unavailable.",
|
||||
"maintenanceServerUnreachable": "Server is unreachable. Reconnecting..."
|
||||
},
|
||||
"sidebar": {
|
||||
"home": "Rumah",
|
||||
@@ -103,7 +105,9 @@
|
||||
"endpoints": "Titik Akhir",
|
||||
"playground": "Playground",
|
||||
"agents": "Ejen",
|
||||
"cliToolsShort": "Alat"
|
||||
"cliToolsShort": "Alat",
|
||||
"autoCombo": "Auto Combo",
|
||||
"searchTools": "Search Tools"
|
||||
},
|
||||
"themesPage": {
|
||||
"title": "Themes",
|
||||
@@ -268,7 +272,28 @@
|
||||
"permissionsTitle": "Kebenaran: {name}",
|
||||
"allowAllDesc": "Kunci ini boleh mengakses semua model yang tersedia.",
|
||||
"restrictDesc": "Kunci ini boleh mengakses {selectedCount} daripada {totalModels} model.",
|
||||
"selectedCount": "{count} dipilih"
|
||||
"selectedCount": "{count} dipilih",
|
||||
"autoResolve": "Auto-Resolve",
|
||||
"autoResolveDesc": "Auto-resolve ambiguous model names to native provider for this API key.",
|
||||
"keyActive": "Key Active",
|
||||
"keyActiveDesc": "Enable or disable this API key. Disabled keys are immediately rejected with 403.",
|
||||
"accessSchedule": "Access Schedule",
|
||||
"accessScheduleDesc": "Restrict access to specific hours and days of the week.",
|
||||
"scheduleFrom": "From",
|
||||
"scheduleUntil": "Until",
|
||||
"scheduleDays": "Days",
|
||||
"scheduleTimezone": "Timezone",
|
||||
"scheduleTimezoneHint": "Use IANA timezone names, e.g. America/New_York, Europe/Berlin",
|
||||
"scheduleActive": "Schedule",
|
||||
"disabled": "Disabled",
|
||||
"daySun": "Sun",
|
||||
"dayMon": "Mon",
|
||||
"dayTue": "Tue",
|
||||
"dayWed": "Wed",
|
||||
"dayThu": "Thu",
|
||||
"dayFri": "Fri",
|
||||
"daySat": "Sat",
|
||||
"keyOnlyAvailableAtCreation": "Full key available only at creation time — copy it when you first create the key"
|
||||
},
|
||||
"auditLog": {
|
||||
"title": "Log Audit",
|
||||
@@ -548,6 +573,27 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"mitmHowWorksDesc": "{toolName} sends requests to its provider endpoint. MITM intercepts and redirects them to OmniRoute.",
|
||||
"mitmStep1": "1. Start MITM to route requests through OmniRoute.",
|
||||
"mitmStep2Prefix": "2. Add",
|
||||
"mitmStep2Suffix": "to your hosts file as 127.0.0.1.",
|
||||
"mitmStep3": "3. Open {toolName} and requests will be proxied.",
|
||||
"whenToUseLabel": "Bila nak guna",
|
||||
"openToolDocs": "Buka dokumen alat",
|
||||
"toolUseCases": {
|
||||
"claude": "Gunakan apabila anda mahukan aliran kerja perancangan yang kukuh dan refactor berbilang fail panjang dengan Kod Claude.",
|
||||
"codex": "Gunakan apabila pasukan anda diseragamkan pada aliran OpenAI Codex CLI dan pengesahan berasaskan profil.",
|
||||
"droid": "Gunakan apabila anda memerlukan ejen terminal ringan yang menumpukan pada pengekodan pantas dan gelung pelaksanaan perintah.",
|
||||
"openclaw": "Gunakan apabila anda mahukan ejen pengekodan gaya Open Claw tetapi dihalakan melalui dasar OmniRoute.",
|
||||
"cline": "Gunakan apabila anda mengkonfigurasi ejen pengekodan dalam editor dan mahukan persediaan berpandu dengan model OmniRoute.",
|
||||
"kilo": "Gunakan apabila aliran kerja anda bergantung pada arahan Kilo Code dan pengeditan berulang yang pantas.",
|
||||
"cursor": "Gunakan semasa pengekodan dalam Kursor dan anda memerlukan model serasi OpenAI tersuai melalui OmniRoute.",
|
||||
"continue": "Gunakan semasa menjalankan Teruskan dalam IDE dan anda memerlukan konfigurasi pembekal berasaskan JSON mudah alih.",
|
||||
"opencode": "Gunakan apabila anda lebih suka ejen terminal-native run dan automasi skrip melalui OpenCode.",
|
||||
"kiro": "Gunakan apabila menyepadukan Kiro dan mengawal penghalaan model secara berpusat daripada OmniRoute.",
|
||||
"antigravity": "Gunakan apabila trafik Antigraviti/Kiro mesti dipintas melalui MITM dan dihalakan ke OmniRoute.",
|
||||
"copilot": "Gunakan apabila anda mahu Copilot gaya sembang UX sambil menguatkuasakan kekunci OmniRoute dan peraturan penghalaan."
|
||||
}
|
||||
},
|
||||
"combos": {
|
||||
@@ -864,7 +910,12 @@
|
||||
"a2aQuickStartStep2": "Hantar permintaan JSON-RPC ke `POST /a2a` menggunakan `message/send` atau `message/stream`.",
|
||||
"a2aQuickStartStep3": "Jejak dan kawal tugas menggunakan `tasks/get` dan `tasks/cancel`.",
|
||||
"completionsLegacy": "Completions (Legacy)",
|
||||
"completionsLegacyDesc": "Legacy OpenAI text completions — accepts both prompt string and messages array format"
|
||||
"completionsLegacyDesc": "Legacy OpenAI text completions — accepts both prompt string and messages array format",
|
||||
"categorySearch": "Search & Discovery",
|
||||
"webSearch": "Web Search",
|
||||
"webSearchDesc": "Unified web search across multiple providers with automatic failover and caching",
|
||||
"searchProvider": "Search Provider",
|
||||
"searchProviderDesc": "This provider is used for web search via POST /v1/search. No model configuration needed — search providers are ready to use once an API key is connected."
|
||||
},
|
||||
"mcpDashboard": {
|
||||
"loading": "Loading MCP dashboard...",
|
||||
@@ -1054,7 +1105,13 @@
|
||||
"resetAllTitle": "Tetapkan semula semua pemutus litar kepada keadaan sihat",
|
||||
"resetting": "Menetapkan semula...",
|
||||
"resetAll": "Tetapkan Semula Semua",
|
||||
"until": "Sehingga {time}"
|
||||
"until": "Sehingga {time}",
|
||||
"activeProviders": "{count} active",
|
||||
"monitoredProviders": "{count} monitored",
|
||||
"configuredProvidersLabel": "Dikonfigurasikan dalam papan pemuka",
|
||||
"configuredProvidersHint": "Pembekal dengan bukti kelayakan disimpan dalam /papan pemuka/penyedia, tanpa mengira keadaan masa jalan.",
|
||||
"activeProvidersHint": "Penyedia yang dikonfigurasikan pada masa ini didayakan untuk permintaan penghalaan.",
|
||||
"monitoredProvidersHint": "Pembekal kini dijejaki oleh pemantau kesihatan pemutus litar."
|
||||
},
|
||||
"limits": {
|
||||
"title": "Had & Kuota",
|
||||
@@ -1396,7 +1453,31 @@
|
||||
"chatPathHint": "Custom chat path for providers with non-standard APIs (e.g. /v4/chat/completions)",
|
||||
"modelsPathLabel": "Models Endpoint Path",
|
||||
"modelsPathPlaceholder": "/models",
|
||||
"modelsPathHint": "Custom models path for validation (e.g. /v4/models)"
|
||||
"modelsPathHint": "Custom models path for validation (e.g. /v4/models)",
|
||||
"builtInModels": "Built-in models",
|
||||
"builtInModelsHint": "Registry models for this provider. Use the pencil to set compatibility options.",
|
||||
"normalizeToolCallIdLabel": "Normalize tool call IDs to 9 characters (e.g. Mistral)",
|
||||
"preserveDeveloperRoleLabel": "Keep OpenAI Responses developer role (do not map to system)",
|
||||
"compatAdjustmentsTitle": "Compatibility",
|
||||
"compatButtonLabel": "Compatibility",
|
||||
"compatToolIdShort": "Tool ID 9",
|
||||
"compatDeveloperShort": "Developer role",
|
||||
"compatDoNotPreserveDeveloper": "Do not preserve developer role",
|
||||
"compatBadgeNoPreserve": "No preserve",
|
||||
"compatProtocolLabel": "Client request protocol",
|
||||
"compatProtocolHint": "These options apply when OmniRoute detects this request shape (OpenAI Chat, Responses API, or Anthropic Messages).",
|
||||
"compatProtocolOpenAI": "OpenAI Chat Completions",
|
||||
"compatProtocolOpenAIResponses": "OpenAI Responses API",
|
||||
"compatProtocolClaude": "Anthropic Messages",
|
||||
"tokenRefreshed": "Token refreshed successfully",
|
||||
"tokenRefreshFailed": "Token refresh failed",
|
||||
"compatBadgeUpstreamHeaders": "Headers",
|
||||
"compatUpstreamAddRow": "Add header",
|
||||
"compatUpstreamHeaderName": "Header name",
|
||||
"compatUpstreamHeaderValue": "Value",
|
||||
"compatUpstreamHeadersHint": "High-privilege setting — same trust level as editing provider API credentials; only trusted admins should use it. Merged after OmniRoute adds auth from the provider API key. If a custom header uses the same name as an existing one (e.g. Authorization), your value fully replaces the auto-generated header (including the Bearer token) — the upstream only sees what you typed, not the key from settings. Misconfiguration can cause 401 or broken upstream auth. One row per header (e.g. extra Authentication for some gateways). Hover or focus the value to preview. Saves on blur, outside click, or closing this panel.",
|
||||
"compatUpstreamHeadersLabel": "Extra upstream headers",
|
||||
"compatUpstreamRemoveRow": "Remove row"
|
||||
},
|
||||
"settings": {
|
||||
"title": "tetapan",
|
||||
@@ -1799,7 +1880,13 @@
|
||||
"cliFingerprintDesc": "Match native CLI binary signatures when proxying requests. Reorders headers and body fields to look identical to the official CLI tools. Your proxy IP is preserved.",
|
||||
"cliFingerprintEnabled": "{count} provider(s) with CLI fingerprint active",
|
||||
"enableFingerprintTitle": "Enable fingerprint for {provider}",
|
||||
"disableFingerprintTitle": "Disable fingerprint for {provider}"
|
||||
"disableFingerprintTitle": "Disable fingerprint for {provider}",
|
||||
"routingAdvancedGuideTitle": "Panduan laluan lanjutan",
|
||||
"routingAdvancedGuideHint1": "Gunakan Fill First untuk keutamaan yang boleh diramal, Round Robin untuk keadilan dan P2C untuk daya tahan latensi.",
|
||||
"routingAdvancedGuideHint2": "Jika pembekal berbeza dalam kualiti/kos, mulakan dengan Pilihan Kos untuk kerja latar belakang dan Paling Kurang Digunakan untuk pemakaian seimbang.",
|
||||
"comboDefaultsGuideTitle": "Bagaimana untuk menala lalai kombo",
|
||||
"comboDefaultsGuideHint1": "Pastikan percubaan semula rendah dalam aliran kependaman rendah; tambahkan tamat masa hanya untuk tugas generasi panjang.",
|
||||
"comboDefaultsGuideHint2": "Gunakan penggantian pembekal apabila satu pembekal memerlukan gelagat tamat masa/cuba semula yang berbeza daripada lalai global."
|
||||
},
|
||||
"translator": {
|
||||
"title": "Penterjemah",
|
||||
@@ -2216,7 +2303,8 @@
|
||||
"orRemovePasswordHashField": "atau alih keluar medan passwordHash",
|
||||
"restartServerWithNewPassword": "Mulakan semula pelayan - ia akan menggunakan kata laluan baharu",
|
||||
"backToLogin": "Kembali ke Log Masuk",
|
||||
"forgotPassword": "Lupa kata laluan?"
|
||||
"forgotPassword": "Lupa kata laluan?",
|
||||
"defaultPasswordHint": "Default password: 123456 (unless INITIAL_PASSWORD was set)"
|
||||
},
|
||||
"landing": {
|
||||
"brandName": "OmniRoute",
|
||||
@@ -2414,7 +2502,16 @@
|
||||
"troubleshootingCodexFamily": "Untuk model keluarga Codex GitHub, kekalkan model sebagai gh/codex-model; penghala memilih /membalas secara automatik.",
|
||||
"troubleshootingTestConnection": "Gunakan Papan Pemuka > Pembekal > Uji Sambungan sebelum menguji daripada IDE atau pelanggan luaran.",
|
||||
"troubleshootingCircuitBreaker": "Jika pembekal menunjukkan pemutus litar terbuka, tunggu masa bertenang atau semak halaman Kesihatan untuk mendapatkan butiran.",
|
||||
"troubleshootingOAuth": "Untuk pembekal OAuth, sahkan semula jika token tamat tempoh. Semak penunjuk status kad pembekal."
|
||||
"troubleshootingOAuth": "Untuk pembekal OAuth, sahkan semula jika token tamat tempoh. Semak penunjuk status kad pembekal.",
|
||||
"managementApiReference": "Management API Reference",
|
||||
"managementApiDescription": "Automation endpoints for proxy registry, scope assignments, and legacy proxy migration.",
|
||||
"mgmtProxiesListNote": "List saved proxy registry items (supports pagination).",
|
||||
"mgmtProxiesCreateNote": "Create a reusable proxy item in the registry.",
|
||||
"mgmtProxiesHealthNote": "Get 24h/rolling health metrics per saved proxy from proxy logs.",
|
||||
"mgmtProxiesBulkAssignNote": "Assign or clear one proxy across many scope IDs in one request.",
|
||||
"mgmtAssignmentsListNote": "List proxy assignments by scope, scope_id, or proxy_id.",
|
||||
"mgmtAssignmentsUpdateNote": "Assign or clear proxy for global/provider/account/combo scope.",
|
||||
"mgmtLegacyMigrationNote": "Import legacy proxyConfig maps into registry assignments."
|
||||
},
|
||||
"legal": {
|
||||
"privacyPolicy": "Dasar Privasi",
|
||||
@@ -2503,7 +2600,21 @@
|
||||
"binaryName": "Binary Name",
|
||||
"versionCommand": "Version Command",
|
||||
"spawnArgs": "Spawn Args",
|
||||
"addAgent": "Add Agent"
|
||||
"addAgent": "Add Agent",
|
||||
"scanning": "Scanning system for CLI agents...",
|
||||
"opencodeIntegration": "OpenCode Integration",
|
||||
"opencodeDetected": "opencode {version} detected",
|
||||
"opencodeDesc": "Generate a ready-to-use {configFile} with your OmniRoute base URL and all available models — drop it in your project root and run {command}.",
|
||||
"downloadConfig": "Download {file}",
|
||||
"downloaded": "Downloaded!",
|
||||
"setupGuideTitle": "Setup guide",
|
||||
"openCliTools": "Open CLI Tools",
|
||||
"setupGuideDetectCliTitle": "Detect installed CLIs",
|
||||
"setupGuideDetectCliDesc": "Click Refresh after installing or updating a CLI so OmniRoute can rescan binaries and versions.",
|
||||
"setupGuideCustomAgentTitle": "Register custom binary",
|
||||
"setupGuideCustomAgentDesc": "Use Add Custom Agent when your CLI is not in the built-in list. Provide binary name and version command.",
|
||||
"setupGuideCommandMissingTitle": "Fix 'command not found'",
|
||||
"setupGuideCommandMissingDesc": "Ensure the CLI command exists in PATH, open a new terminal session, and rerun Refresh."
|
||||
},
|
||||
"autoCombo": {
|
||||
"title": "Auto-Combo Engine",
|
||||
@@ -2527,5 +2638,56 @@
|
||||
"modePackCostSaver": "Cost Saver",
|
||||
"modePackQualityFirst": "Quality First",
|
||||
"modePackOfflineFriendly": "Offline Friendly"
|
||||
},
|
||||
"search": {
|
||||
"searchQuery": "Search Query",
|
||||
"searchResults": "Search Results",
|
||||
"cachedResult": "Cached",
|
||||
"searchCost": "Cost",
|
||||
"searchTools": "Search Tools",
|
||||
"searchToolsDesc": "Advanced search testing with provider comparison",
|
||||
"compareProviders": "Compare Providers",
|
||||
"rerankResults": "Rerank Results",
|
||||
"searchHistory": "Search History",
|
||||
"urlOverlap": "URL Overlap",
|
||||
"noSearchProviders": "No search providers configured. Add providers in Settings.",
|
||||
"noRerankModels": "No rerank model available",
|
||||
"webSearch": "Web Search",
|
||||
"provider": "Provider",
|
||||
"searchType": "Search Type",
|
||||
"maxResults": "Max Results",
|
||||
"filters": "Filters",
|
||||
"country": "Country",
|
||||
"language": "Language",
|
||||
"timeRange": "Time Range",
|
||||
"includeDomains": "Include Domains",
|
||||
"excludeDomains": "Exclude Domains",
|
||||
"safeSearch": "Safe Search",
|
||||
"formatted": "Formatted",
|
||||
"rawJson": "JSON",
|
||||
"cacheMiss": "cache miss",
|
||||
"cacheHit": "cache hit",
|
||||
"latency": "Latency",
|
||||
"cost": "Cost",
|
||||
"results": "Results",
|
||||
"rerank": "Rerank",
|
||||
"rerankModel": "Rerank Model",
|
||||
"positionDelta": "Position Change",
|
||||
"emptyState": "Send a search query to see results",
|
||||
"safeSearchOff": "Off",
|
||||
"safeSearchModerate": "Moderate",
|
||||
"safeSearchStrict": "Strict",
|
||||
"queryPlaceholder": "Enter search query...",
|
||||
"providerAuto": "auto (cheapest)",
|
||||
"searchTypeWeb": "web",
|
||||
"searchTypeNews": "news",
|
||||
"optionAny": "any",
|
||||
"timeRangeDay": "Past day",
|
||||
"timeRangeWeek": "Past week",
|
||||
"timeRangeMonth": "Past month",
|
||||
"timeRangeYear": "Past year",
|
||||
"domainPlaceholder": "example.com",
|
||||
"requestTimedOut": "Request timed out ({seconds}s)",
|
||||
"networkError": "Network error"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,7 +56,9 @@
|
||||
"warning": "Waarschuwing",
|
||||
"note": "Let op",
|
||||
"free": "Gratis",
|
||||
"skipToContent": "Ga naar de inhoud"
|
||||
"skipToContent": "Ga naar de inhoud",
|
||||
"maintenanceServerIssues": "Server is experiencing issues. Some features may be unavailable.",
|
||||
"maintenanceServerUnreachable": "Server is unreachable. Reconnecting..."
|
||||
},
|
||||
"sidebar": {
|
||||
"home": "Thuis",
|
||||
@@ -103,7 +105,9 @@
|
||||
"endpoints": "Eindpunten",
|
||||
"playground": "Speeltuin",
|
||||
"agents": "Agenten",
|
||||
"cliToolsShort": "Gereedschap"
|
||||
"cliToolsShort": "Gereedschap",
|
||||
"autoCombo": "Auto Combo",
|
||||
"searchTools": "Search Tools"
|
||||
},
|
||||
"themesPage": {
|
||||
"title": "Themes",
|
||||
@@ -268,7 +272,28 @@
|
||||
"permissionsTitle": "Machtigingen: {name}",
|
||||
"allowAllDesc": "Met deze sleutel heeft u toegang tot alle beschikbare modellen.",
|
||||
"restrictDesc": "Deze sleutel heeft toegang tot {selectedCount} van {totalModels} modellen.",
|
||||
"selectedCount": "{count} geselecteerd"
|
||||
"selectedCount": "{count} geselecteerd",
|
||||
"autoResolve": "Auto-Resolve",
|
||||
"autoResolveDesc": "Auto-resolve ambiguous model names to native provider for this API key.",
|
||||
"keyActive": "Key Active",
|
||||
"keyActiveDesc": "Enable or disable this API key. Disabled keys are immediately rejected with 403.",
|
||||
"accessSchedule": "Access Schedule",
|
||||
"accessScheduleDesc": "Restrict access to specific hours and days of the week.",
|
||||
"scheduleFrom": "From",
|
||||
"scheduleUntil": "Until",
|
||||
"scheduleDays": "Days",
|
||||
"scheduleTimezone": "Timezone",
|
||||
"scheduleTimezoneHint": "Use IANA timezone names, e.g. America/New_York, Europe/Berlin",
|
||||
"scheduleActive": "Schedule",
|
||||
"disabled": "Disabled",
|
||||
"daySun": "Sun",
|
||||
"dayMon": "Mon",
|
||||
"dayTue": "Tue",
|
||||
"dayWed": "Wed",
|
||||
"dayThu": "Thu",
|
||||
"dayFri": "Fri",
|
||||
"daySat": "Sat",
|
||||
"keyOnlyAvailableAtCreation": "Full key available only at creation time — copy it when you first create the key"
|
||||
},
|
||||
"auditLog": {
|
||||
"title": "Auditlogboek",
|
||||
@@ -548,6 +573,27 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"mitmHowWorksDesc": "{toolName} sends requests to its provider endpoint. MITM intercepts and redirects them to OmniRoute.",
|
||||
"mitmStep1": "1. Start MITM to route requests through OmniRoute.",
|
||||
"mitmStep2Prefix": "2. Add",
|
||||
"mitmStep2Suffix": "to your hosts file as 127.0.0.1.",
|
||||
"mitmStep3": "3. Open {toolName} and requests will be proxied.",
|
||||
"whenToUseLabel": "Wanneer te gebruiken",
|
||||
"openToolDocs": "Gereedschapsdocumentatie openen",
|
||||
"toolUseCases": {
|
||||
"claude": "Gebruik deze optie als u krachtige planningsworkflows en lange refactoren van meerdere bestanden wilt met Claude Code.",
|
||||
"codex": "Gebruik wanneer uw team is gestandaardiseerd op OpenAI Codex CLI-stromen en op profielen gebaseerde authenticatie.",
|
||||
"droid": "Gebruik deze wanneer u een lichtgewicht terminalagent nodig heeft die zich richt op snelle codering en opdrachtuitvoeringslussen.",
|
||||
"openclaw": "Gebruik dit wanneer u een codeeragent in Open Claw-stijl wilt, maar gerouteerd via OmniRoute-beleid.",
|
||||
"cline": "Gebruik deze optie wanneer u codeeragenten in editors configureert en begeleide installatie wilt met OmniRoute-modellen.",
|
||||
"kilo": "Gebruik dit wanneer uw workflow afhankelijk is van Kilo Code-opdrachten en snelle iteratieve bewerkingen.",
|
||||
"cursor": "Gebruik dit bij het coderen in Cursor en u hebt aangepaste OpenAI-compatibele modellen nodig via OmniRoute.",
|
||||
"continue": "Gebruik dit wanneer u Doorgaan in IDE's uitvoert en u een draagbare, op JSON gebaseerde providerconfiguratie nodig hebt.",
|
||||
"opencode": "Gebruik dit wanneer u de voorkeur geeft aan terminal-native agentruns en scriptautomatisering via OpenCode.",
|
||||
"kiro": "Te gebruiken bij het integreren van Kiro en het centraal beheren van modelrouting vanuit OmniRoute.",
|
||||
"antigravity": "Gebruik wanneer Antigravity/Kiro-verkeer moet worden onderschept via MITM en naar OmniRoute moet worden gerouteerd.",
|
||||
"copilot": "Gebruik wanneer u UX in Copilot-chatstijl wilt terwijl u OmniRoute-sleutels en routeringsregels afdwingt."
|
||||
}
|
||||
},
|
||||
"combos": {
|
||||
@@ -864,7 +910,12 @@
|
||||
"a2aQuickStartStep2": "Send JSON-RPC requests to `POST /a2a` using `message/send` or `message/stream`.",
|
||||
"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"
|
||||
"completionsLegacyDesc": "Legacy OpenAI text completions — accepts both prompt string and messages array format",
|
||||
"categorySearch": "Search & Discovery",
|
||||
"webSearch": "Web Search",
|
||||
"webSearchDesc": "Unified web search across multiple providers with automatic failover and caching",
|
||||
"searchProvider": "Search Provider",
|
||||
"searchProviderDesc": "This provider is used for web search via POST /v1/search. No model configuration needed — search providers are ready to use once an API key is connected."
|
||||
},
|
||||
"mcpDashboard": {
|
||||
"loading": "Loading MCP dashboard...",
|
||||
@@ -1054,7 +1105,13 @@
|
||||
"resetAllTitle": "Reset alle stroomonderbrekers naar de gezonde status",
|
||||
"resetting": "Resetten...",
|
||||
"resetAll": "Alles resetten",
|
||||
"until": "Tot {time}"
|
||||
"until": "Tot {time}",
|
||||
"activeProviders": "{count} active",
|
||||
"monitoredProviders": "{count} monitored",
|
||||
"configuredProvidersLabel": "Geconfigureerd in dashboard",
|
||||
"configuredProvidersHint": "Providers met inloggegevens opgeslagen in /dashboard/providers, ongeacht de runtimestatus.",
|
||||
"activeProvidersHint": "Geconfigureerde providers die momenteel zijn ingeschakeld voor het routeren van verzoeken.",
|
||||
"monitoredProvidersHint": "Providers worden momenteel gevolgd door gezondheidsmonitors van stroomonderbrekers."
|
||||
},
|
||||
"limits": {
|
||||
"title": "Limieten en quota's",
|
||||
@@ -1396,7 +1453,31 @@
|
||||
"chatPathHint": "Custom chat path for providers with non-standard APIs (e.g. /v4/chat/completions)",
|
||||
"modelsPathLabel": "Models Endpoint Path",
|
||||
"modelsPathPlaceholder": "/models",
|
||||
"modelsPathHint": "Custom models path for validation (e.g. /v4/models)"
|
||||
"modelsPathHint": "Custom models path for validation (e.g. /v4/models)",
|
||||
"builtInModels": "Built-in models",
|
||||
"builtInModelsHint": "Registry models for this provider. Use the pencil to set compatibility options.",
|
||||
"normalizeToolCallIdLabel": "Normalize tool call IDs to 9 characters (e.g. Mistral)",
|
||||
"preserveDeveloperRoleLabel": "Keep OpenAI Responses developer role (do not map to system)",
|
||||
"compatAdjustmentsTitle": "Compatibility",
|
||||
"compatButtonLabel": "Compatibility",
|
||||
"compatToolIdShort": "Tool ID 9",
|
||||
"compatDeveloperShort": "Developer role",
|
||||
"compatDoNotPreserveDeveloper": "Do not preserve developer role",
|
||||
"compatBadgeNoPreserve": "No preserve",
|
||||
"compatProtocolLabel": "Client request protocol",
|
||||
"compatProtocolHint": "These options apply when OmniRoute detects this request shape (OpenAI Chat, Responses API, or Anthropic Messages).",
|
||||
"compatProtocolOpenAI": "OpenAI Chat Completions",
|
||||
"compatProtocolOpenAIResponses": "OpenAI Responses API",
|
||||
"compatProtocolClaude": "Anthropic Messages",
|
||||
"tokenRefreshed": "Token refreshed successfully",
|
||||
"tokenRefreshFailed": "Token refresh failed",
|
||||
"compatBadgeUpstreamHeaders": "Headers",
|
||||
"compatUpstreamAddRow": "Add header",
|
||||
"compatUpstreamHeaderName": "Header name",
|
||||
"compatUpstreamHeaderValue": "Value",
|
||||
"compatUpstreamHeadersHint": "High-privilege setting — same trust level as editing provider API credentials; only trusted admins should use it. Merged after OmniRoute adds auth from the provider API key. If a custom header uses the same name as an existing one (e.g. Authorization), your value fully replaces the auto-generated header (including the Bearer token) — the upstream only sees what you typed, not the key from settings. Misconfiguration can cause 401 or broken upstream auth. One row per header (e.g. extra Authentication for some gateways). Hover or focus the value to preview. Saves on blur, outside click, or closing this panel.",
|
||||
"compatUpstreamHeadersLabel": "Extra upstream headers",
|
||||
"compatUpstreamRemoveRow": "Remove row"
|
||||
},
|
||||
"settings": {
|
||||
"title": "Instellingen",
|
||||
@@ -1799,7 +1880,13 @@
|
||||
"cliFingerprintDesc": "Match native CLI binary signatures when proxying requests. Reorders headers and body fields to look identical to the official CLI tools. Your proxy IP is preserved.",
|
||||
"cliFingerprintEnabled": "{count} provider(s) with CLI fingerprint active",
|
||||
"enableFingerprintTitle": "Enable fingerprint for {provider}",
|
||||
"disableFingerprintTitle": "Disable fingerprint for {provider}"
|
||||
"disableFingerprintTitle": "Disable fingerprint for {provider}",
|
||||
"routingAdvancedGuideTitle": "Geavanceerde routebegeleiding",
|
||||
"routingAdvancedGuideHint1": "Gebruik Fill First voor voorspelbare prioriteit, Round Robin voor eerlijkheid en P2C voor latency-veerkracht.",
|
||||
"routingAdvancedGuideHint2": "Als aanbieders variëren in kwaliteit/kosten, begin dan met Kosten Opt voor achtergrondwerk en Minst Gebruikt voor evenwichtige slijtage.",
|
||||
"comboDefaultsGuideTitle": "Combo-standaardinstellingen afstemmen",
|
||||
"comboDefaultsGuideHint1": "Houd het aantal nieuwe pogingen laag bij stromen met lage latentie; verhoog de time-out alleen voor lange generatietaken.",
|
||||
"comboDefaultsGuideHint2": "Gebruik provideroverschrijvingen wanneer een provider ander time-out/opnieuw gedrag nodig heeft dan de algemene standaardwaarden."
|
||||
},
|
||||
"translator": {
|
||||
"title": "Vertaler",
|
||||
@@ -2216,7 +2303,8 @@
|
||||
"orRemovePasswordHashField": "of verwijder het wachtwoordHash-veld",
|
||||
"restartServerWithNewPassword": "Start de server opnieuw op. Deze gebruikt het nieuwe wachtwoord",
|
||||
"backToLogin": "Terug naar Inloggen",
|
||||
"forgotPassword": "Wachtwoord vergeten?"
|
||||
"forgotPassword": "Wachtwoord vergeten?",
|
||||
"defaultPasswordHint": "Default password: 123456 (unless INITIAL_PASSWORD was set)"
|
||||
},
|
||||
"landing": {
|
||||
"brandName": "OmniRoute",
|
||||
@@ -2414,7 +2502,16 @@
|
||||
"troubleshootingCodexFamily": "Voor modellen uit de GitHub Codex-familie behoudt u het model als gh/codex-model; router selecteert /responses automatisch.",
|
||||
"troubleshootingTestConnection": "Gebruik Dashboard > Providers > Verbinding testen voordat u gaat testen vanaf IDE's of externe clients.",
|
||||
"troubleshootingCircuitBreaker": "Als een provider aangeeft dat de stroomonderbreker open is, wacht dan op de cooldown of kijk op de Gezondheidspagina voor meer informatie.",
|
||||
"troubleshootingOAuth": "Voor OAuth-providers geldt dat u opnieuw moet verifiëren als tokens verlopen. Controleer de statusindicator van de providerkaart."
|
||||
"troubleshootingOAuth": "Voor OAuth-providers geldt dat u opnieuw moet verifiëren als tokens verlopen. Controleer de statusindicator van de providerkaart.",
|
||||
"managementApiReference": "Management API Reference",
|
||||
"managementApiDescription": "Automation endpoints for proxy registry, scope assignments, and legacy proxy migration.",
|
||||
"mgmtProxiesListNote": "List saved proxy registry items (supports pagination).",
|
||||
"mgmtProxiesCreateNote": "Create a reusable proxy item in the registry.",
|
||||
"mgmtProxiesHealthNote": "Get 24h/rolling health metrics per saved proxy from proxy logs.",
|
||||
"mgmtProxiesBulkAssignNote": "Assign or clear one proxy across many scope IDs in one request.",
|
||||
"mgmtAssignmentsListNote": "List proxy assignments by scope, scope_id, or proxy_id.",
|
||||
"mgmtAssignmentsUpdateNote": "Assign or clear proxy for global/provider/account/combo scope.",
|
||||
"mgmtLegacyMigrationNote": "Import legacy proxyConfig maps into registry assignments."
|
||||
},
|
||||
"legal": {
|
||||
"privacyPolicy": "Privacybeleid",
|
||||
@@ -2503,7 +2600,21 @@
|
||||
"binaryName": "Binary Name",
|
||||
"versionCommand": "Version Command",
|
||||
"spawnArgs": "Spawn Args",
|
||||
"addAgent": "Add Agent"
|
||||
"addAgent": "Add Agent",
|
||||
"scanning": "Scanning system for CLI agents...",
|
||||
"opencodeIntegration": "OpenCode Integration",
|
||||
"opencodeDetected": "opencode {version} detected",
|
||||
"opencodeDesc": "Generate a ready-to-use {configFile} with your OmniRoute base URL and all available models — drop it in your project root and run {command}.",
|
||||
"downloadConfig": "Download {file}",
|
||||
"downloaded": "Downloaded!",
|
||||
"setupGuideTitle": "Setup guide",
|
||||
"openCliTools": "Open CLI Tools",
|
||||
"setupGuideDetectCliTitle": "Detect installed CLIs",
|
||||
"setupGuideDetectCliDesc": "Click Refresh after installing or updating a CLI so OmniRoute can rescan binaries and versions.",
|
||||
"setupGuideCustomAgentTitle": "Register custom binary",
|
||||
"setupGuideCustomAgentDesc": "Use Add Custom Agent when your CLI is not in the built-in list. Provide binary name and version command.",
|
||||
"setupGuideCommandMissingTitle": "Fix 'command not found'",
|
||||
"setupGuideCommandMissingDesc": "Ensure the CLI command exists in PATH, open a new terminal session, and rerun Refresh."
|
||||
},
|
||||
"autoCombo": {
|
||||
"title": "Auto-Combo Engine",
|
||||
@@ -2527,5 +2638,56 @@
|
||||
"modePackCostSaver": "Cost Saver",
|
||||
"modePackQualityFirst": "Quality First",
|
||||
"modePackOfflineFriendly": "Offline Friendly"
|
||||
},
|
||||
"search": {
|
||||
"searchQuery": "Search Query",
|
||||
"searchResults": "Search Results",
|
||||
"cachedResult": "Cached",
|
||||
"searchCost": "Cost",
|
||||
"searchTools": "Search Tools",
|
||||
"searchToolsDesc": "Advanced search testing with provider comparison",
|
||||
"compareProviders": "Compare Providers",
|
||||
"rerankResults": "Rerank Results",
|
||||
"searchHistory": "Search History",
|
||||
"urlOverlap": "URL Overlap",
|
||||
"noSearchProviders": "No search providers configured. Add providers in Settings.",
|
||||
"noRerankModels": "No rerank model available",
|
||||
"webSearch": "Web Search",
|
||||
"provider": "Provider",
|
||||
"searchType": "Search Type",
|
||||
"maxResults": "Max Results",
|
||||
"filters": "Filters",
|
||||
"country": "Country",
|
||||
"language": "Language",
|
||||
"timeRange": "Time Range",
|
||||
"includeDomains": "Include Domains",
|
||||
"excludeDomains": "Exclude Domains",
|
||||
"safeSearch": "Safe Search",
|
||||
"formatted": "Formatted",
|
||||
"rawJson": "JSON",
|
||||
"cacheMiss": "cache miss",
|
||||
"cacheHit": "cache hit",
|
||||
"latency": "Latency",
|
||||
"cost": "Cost",
|
||||
"results": "Results",
|
||||
"rerank": "Rerank",
|
||||
"rerankModel": "Rerank Model",
|
||||
"positionDelta": "Position Change",
|
||||
"emptyState": "Send a search query to see results",
|
||||
"safeSearchOff": "Off",
|
||||
"safeSearchModerate": "Moderate",
|
||||
"safeSearchStrict": "Strict",
|
||||
"queryPlaceholder": "Enter search query...",
|
||||
"providerAuto": "auto (cheapest)",
|
||||
"searchTypeWeb": "web",
|
||||
"searchTypeNews": "news",
|
||||
"optionAny": "any",
|
||||
"timeRangeDay": "Past day",
|
||||
"timeRangeWeek": "Past week",
|
||||
"timeRangeMonth": "Past month",
|
||||
"timeRangeYear": "Past year",
|
||||
"domainPlaceholder": "example.com",
|
||||
"requestTimedOut": "Request timed out ({seconds}s)",
|
||||
"networkError": "Network error"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,7 +56,9 @@
|
||||
"warning": "Advarsel",
|
||||
"note": "Merk",
|
||||
"free": "Gratis",
|
||||
"skipToContent": "Gå til innhold"
|
||||
"skipToContent": "Gå til innhold",
|
||||
"maintenanceServerIssues": "Server is experiencing issues. Some features may be unavailable.",
|
||||
"maintenanceServerUnreachable": "Server is unreachable. Reconnecting..."
|
||||
},
|
||||
"sidebar": {
|
||||
"home": "Hjem",
|
||||
@@ -103,7 +105,9 @@
|
||||
"endpoints": "Endepunkter",
|
||||
"playground": "Lekeplass",
|
||||
"agents": "Agenter",
|
||||
"cliToolsShort": "Verktøy"
|
||||
"cliToolsShort": "Verktøy",
|
||||
"autoCombo": "Auto Combo",
|
||||
"searchTools": "Search Tools"
|
||||
},
|
||||
"themesPage": {
|
||||
"title": "Themes",
|
||||
@@ -268,7 +272,28 @@
|
||||
"permissionsTitle": "Tillatelser: {name}",
|
||||
"allowAllDesc": "Denne nøkkelen har tilgang til alle tilgjengelige modeller.",
|
||||
"restrictDesc": "Denne nøkkelen har tilgang til {selectedCount} av {totalModels}-modeller.",
|
||||
"selectedCount": "{count} valgt"
|
||||
"selectedCount": "{count} valgt",
|
||||
"autoResolve": "Auto-Resolve",
|
||||
"autoResolveDesc": "Auto-resolve ambiguous model names to native provider for this API key.",
|
||||
"keyActive": "Key Active",
|
||||
"keyActiveDesc": "Enable or disable this API key. Disabled keys are immediately rejected with 403.",
|
||||
"accessSchedule": "Access Schedule",
|
||||
"accessScheduleDesc": "Restrict access to specific hours and days of the week.",
|
||||
"scheduleFrom": "From",
|
||||
"scheduleUntil": "Until",
|
||||
"scheduleDays": "Days",
|
||||
"scheduleTimezone": "Timezone",
|
||||
"scheduleTimezoneHint": "Use IANA timezone names, e.g. America/New_York, Europe/Berlin",
|
||||
"scheduleActive": "Schedule",
|
||||
"disabled": "Disabled",
|
||||
"daySun": "Sun",
|
||||
"dayMon": "Mon",
|
||||
"dayTue": "Tue",
|
||||
"dayWed": "Wed",
|
||||
"dayThu": "Thu",
|
||||
"dayFri": "Fri",
|
||||
"daySat": "Sat",
|
||||
"keyOnlyAvailableAtCreation": "Full key available only at creation time — copy it when you first create the key"
|
||||
},
|
||||
"auditLog": {
|
||||
"title": "Revisjonslogg",
|
||||
@@ -548,6 +573,27 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"mitmHowWorksDesc": "{toolName} sends requests to its provider endpoint. MITM intercepts and redirects them to OmniRoute.",
|
||||
"mitmStep1": "1. Start MITM to route requests through OmniRoute.",
|
||||
"mitmStep2Prefix": "2. Add",
|
||||
"mitmStep2Suffix": "to your hosts file as 127.0.0.1.",
|
||||
"mitmStep3": "3. Open {toolName} and requests will be proxied.",
|
||||
"whenToUseLabel": "Når du skal bruke",
|
||||
"openToolDocs": "Åpne verktøydokumenter",
|
||||
"toolUseCases": {
|
||||
"claude": "Bruk når du vil ha sterke planleggingsarbeidsflyter og lange multifilrefaktorer med Claude Code.",
|
||||
"codex": "Bruk når teamet ditt er standardisert på OpenAI Codex CLI-flyter og profilbasert autentisering.",
|
||||
"droid": "Bruk når du trenger en lettvekts terminalagent fokusert på rask koding og løkker for kommandoutførelse.",
|
||||
"openclaw": "Bruk når du vil ha en Open Claw-kodingsagent, men rutet gjennom OmniRoute-policyer.",
|
||||
"cline": "Bruk når du konfigurerer kodeagenter inne i redaktører og ønsker veiledet oppsett med OmniRoute-modeller.",
|
||||
"kilo": "Bruk når arbeidsflyten din avhenger av Kilo Code-kommandoer og raske iterative redigeringer.",
|
||||
"cursor": "Bruk når du koder i Cursor og du trenger tilpassede OpenAI-kompatible modeller gjennom OmniRoute.",
|
||||
"continue": "Bruk når du kjører Continue i IDEer og du trenger bærbar JSON-basert leverandørkonfigurasjon.",
|
||||
"opencode": "Bruk når du foretrekker terminal-native agentkjøringer og skriptautomatisering via OpenCode.",
|
||||
"kiro": "Brukes når du integrerer Kiro og kontrollerer modellruting sentralt fra OmniRoute.",
|
||||
"antigravity": "Brukes når Antigravity/Kiro-trafikk må avskjæres gjennom MITM og rutes til OmniRoute.",
|
||||
"copilot": "Bruk når du vil ha Copilot chat-stil UX mens du håndhever OmniRoute-nøkler og rutingsregler."
|
||||
}
|
||||
},
|
||||
"combos": {
|
||||
@@ -864,7 +910,12 @@
|
||||
"a2aQuickStartStep2": "Send JSON-RPC-forespørsler til `POST /a2a` ved å bruke `message/send` eller `message/stream`.",
|
||||
"a2aQuickStartStep3": "Spor og kontroller oppgaver ved å bruke `tasks/get` og `tasks/cancel`.",
|
||||
"completionsLegacy": "Completions (Legacy)",
|
||||
"completionsLegacyDesc": "Legacy OpenAI text completions — accepts both prompt string and messages array format"
|
||||
"completionsLegacyDesc": "Legacy OpenAI text completions — accepts both prompt string and messages array format",
|
||||
"categorySearch": "Search & Discovery",
|
||||
"webSearch": "Web Search",
|
||||
"webSearchDesc": "Unified web search across multiple providers with automatic failover and caching",
|
||||
"searchProvider": "Search Provider",
|
||||
"searchProviderDesc": "This provider is used for web search via POST /v1/search. No model configuration needed — search providers are ready to use once an API key is connected."
|
||||
},
|
||||
"mcpDashboard": {
|
||||
"loading": "Laster inn MCP-dashbordet ...",
|
||||
@@ -1054,7 +1105,13 @@
|
||||
"resetAllTitle": "Tilbakestill alle effektbrytere til sunn tilstand",
|
||||
"resetting": "Tilbakestiller...",
|
||||
"resetAll": "Tilbakestill alle",
|
||||
"until": "Inntil {time}"
|
||||
"until": "Inntil {time}",
|
||||
"activeProviders": "{count} active",
|
||||
"monitoredProviders": "{count} monitored",
|
||||
"configuredProvidersLabel": "Konfigurert i dashbordet",
|
||||
"configuredProvidersHint": "Leverandører med legitimasjon lagret i /dashboard/leverandører, uavhengig av kjøretidstilstand.",
|
||||
"activeProvidersHint": "Konfigurerte leverandører er for øyeblikket aktivert for rutingforespørsler.",
|
||||
"monitoredProvidersHint": "Leverandører spores for øyeblikket av strømbryterhelsemonitorer."
|
||||
},
|
||||
"limits": {
|
||||
"title": "Grenser og kvoter",
|
||||
@@ -1396,7 +1453,31 @@
|
||||
"chatPathHint": "Custom chat path for providers with non-standard APIs (e.g. /v4/chat/completions)",
|
||||
"modelsPathLabel": "Models Endpoint Path",
|
||||
"modelsPathPlaceholder": "/models",
|
||||
"modelsPathHint": "Custom models path for validation (e.g. /v4/models)"
|
||||
"modelsPathHint": "Custom models path for validation (e.g. /v4/models)",
|
||||
"builtInModels": "Built-in models",
|
||||
"builtInModelsHint": "Registry models for this provider. Use the pencil to set compatibility options.",
|
||||
"normalizeToolCallIdLabel": "Normalize tool call IDs to 9 characters (e.g. Mistral)",
|
||||
"preserveDeveloperRoleLabel": "Keep OpenAI Responses developer role (do not map to system)",
|
||||
"compatAdjustmentsTitle": "Compatibility",
|
||||
"compatButtonLabel": "Compatibility",
|
||||
"compatToolIdShort": "Tool ID 9",
|
||||
"compatDeveloperShort": "Developer role",
|
||||
"compatDoNotPreserveDeveloper": "Do not preserve developer role",
|
||||
"compatBadgeNoPreserve": "No preserve",
|
||||
"compatProtocolLabel": "Client request protocol",
|
||||
"compatProtocolHint": "These options apply when OmniRoute detects this request shape (OpenAI Chat, Responses API, or Anthropic Messages).",
|
||||
"compatProtocolOpenAI": "OpenAI Chat Completions",
|
||||
"compatProtocolOpenAIResponses": "OpenAI Responses API",
|
||||
"compatProtocolClaude": "Anthropic Messages",
|
||||
"tokenRefreshed": "Token refreshed successfully",
|
||||
"tokenRefreshFailed": "Token refresh failed",
|
||||
"compatBadgeUpstreamHeaders": "Headers",
|
||||
"compatUpstreamAddRow": "Add header",
|
||||
"compatUpstreamHeaderName": "Header name",
|
||||
"compatUpstreamHeaderValue": "Value",
|
||||
"compatUpstreamHeadersHint": "High-privilege setting — same trust level as editing provider API credentials; only trusted admins should use it. Merged after OmniRoute adds auth from the provider API key. If a custom header uses the same name as an existing one (e.g. Authorization), your value fully replaces the auto-generated header (including the Bearer token) — the upstream only sees what you typed, not the key from settings. Misconfiguration can cause 401 or broken upstream auth. One row per header (e.g. extra Authentication for some gateways). Hover or focus the value to preview. Saves on blur, outside click, or closing this panel.",
|
||||
"compatUpstreamHeadersLabel": "Extra upstream headers",
|
||||
"compatUpstreamRemoveRow": "Remove row"
|
||||
},
|
||||
"settings": {
|
||||
"title": "Innstillinger",
|
||||
@@ -1799,7 +1880,13 @@
|
||||
"cliFingerprintDesc": "Match native CLI binary signatures when proxying requests. Reorders headers and body fields to look identical to the official CLI tools. Your proxy IP is preserved.",
|
||||
"cliFingerprintEnabled": "{count} provider(s) with CLI fingerprint active",
|
||||
"enableFingerprintTitle": "Enable fingerprint for {provider}",
|
||||
"disableFingerprintTitle": "Disable fingerprint for {provider}"
|
||||
"disableFingerprintTitle": "Disable fingerprint for {provider}",
|
||||
"routingAdvancedGuideTitle": "Avansert ruteveiledning",
|
||||
"routingAdvancedGuideHint1": "Bruk Fill First for forutsigbar prioritet, Round Robin for rettferdighet og P2C for latensmotstandskraft.",
|
||||
"routingAdvancedGuideHint2": "Hvis leverandørene varierer i kvalitet/kostnad, start med Cost Opt for bakgrunnsarbeid og Minst brukt for balansert slitasje.",
|
||||
"comboDefaultsGuideTitle": "Hvordan justere kombinasjonsstandarder",
|
||||
"comboDefaultsGuideHint1": "Hold lave gjenforsøk i flyter med lav latens; øke tidsavbruddet bare for langgenerasjonsoppgaver.",
|
||||
"comboDefaultsGuideHint2": "Bruk leverandøroverstyringer når en leverandør trenger annen tidsavbrudd/forsøk på nytt enn globale standardinnstillinger."
|
||||
},
|
||||
"translator": {
|
||||
"title": "Oversetter",
|
||||
@@ -2216,7 +2303,8 @@
|
||||
"orRemovePasswordHashField": "eller fjern passordHash-feltet",
|
||||
"restartServerWithNewPassword": "Start serveren på nytt - den vil bruke det nye passordet",
|
||||
"backToLogin": "Tilbake til pålogging",
|
||||
"forgotPassword": "Glemt passord?"
|
||||
"forgotPassword": "Glemt passord?",
|
||||
"defaultPasswordHint": "Default password: 123456 (unless INITIAL_PASSWORD was set)"
|
||||
},
|
||||
"landing": {
|
||||
"brandName": "OmniRoute",
|
||||
@@ -2414,7 +2502,16 @@
|
||||
"troubleshootingCodexFamily": "For GitHub Codex-familiemodeller, behold modellen som gh/codex-model; ruteren velger /responser automatisk.",
|
||||
"troubleshootingTestConnection": "Bruk Dashboard > Leverandører > Test tilkobling før du tester fra IDE-er eller eksterne klienter.",
|
||||
"troubleshootingCircuitBreaker": "Hvis en leverandør viser at strømbryteren er åpen, vent på nedkjøling eller sjekk helsesiden for detaljer.",
|
||||
"troubleshootingOAuth": "For OAuth-leverandører, autentiser på nytt hvis tokens utløper. Sjekk leverandørkortets statusindikator."
|
||||
"troubleshootingOAuth": "For OAuth-leverandører, autentiser på nytt hvis tokens utløper. Sjekk leverandørkortets statusindikator.",
|
||||
"managementApiReference": "Management API Reference",
|
||||
"managementApiDescription": "Automation endpoints for proxy registry, scope assignments, and legacy proxy migration.",
|
||||
"mgmtProxiesListNote": "List saved proxy registry items (supports pagination).",
|
||||
"mgmtProxiesCreateNote": "Create a reusable proxy item in the registry.",
|
||||
"mgmtProxiesHealthNote": "Get 24h/rolling health metrics per saved proxy from proxy logs.",
|
||||
"mgmtProxiesBulkAssignNote": "Assign or clear one proxy across many scope IDs in one request.",
|
||||
"mgmtAssignmentsListNote": "List proxy assignments by scope, scope_id, or proxy_id.",
|
||||
"mgmtAssignmentsUpdateNote": "Assign or clear proxy for global/provider/account/combo scope.",
|
||||
"mgmtLegacyMigrationNote": "Import legacy proxyConfig maps into registry assignments."
|
||||
},
|
||||
"legal": {
|
||||
"privacyPolicy": "Personvernerklæring",
|
||||
@@ -2503,7 +2600,21 @@
|
||||
"binaryName": "Binary Name",
|
||||
"versionCommand": "Version Command",
|
||||
"spawnArgs": "Spawn Args",
|
||||
"addAgent": "Add Agent"
|
||||
"addAgent": "Add Agent",
|
||||
"scanning": "Scanning system for CLI agents...",
|
||||
"opencodeIntegration": "OpenCode Integration",
|
||||
"opencodeDetected": "opencode {version} detected",
|
||||
"opencodeDesc": "Generate a ready-to-use {configFile} with your OmniRoute base URL and all available models — drop it in your project root and run {command}.",
|
||||
"downloadConfig": "Download {file}",
|
||||
"downloaded": "Downloaded!",
|
||||
"setupGuideTitle": "Setup guide",
|
||||
"openCliTools": "Open CLI Tools",
|
||||
"setupGuideDetectCliTitle": "Detect installed CLIs",
|
||||
"setupGuideDetectCliDesc": "Click Refresh after installing or updating a CLI so OmniRoute can rescan binaries and versions.",
|
||||
"setupGuideCustomAgentTitle": "Register custom binary",
|
||||
"setupGuideCustomAgentDesc": "Use Add Custom Agent when your CLI is not in the built-in list. Provide binary name and version command.",
|
||||
"setupGuideCommandMissingTitle": "Fix 'command not found'",
|
||||
"setupGuideCommandMissingDesc": "Ensure the CLI command exists in PATH, open a new terminal session, and rerun Refresh."
|
||||
},
|
||||
"autoCombo": {
|
||||
"title": "Auto-Combo Engine",
|
||||
@@ -2527,5 +2638,56 @@
|
||||
"modePackCostSaver": "Cost Saver",
|
||||
"modePackQualityFirst": "Quality First",
|
||||
"modePackOfflineFriendly": "Offline Friendly"
|
||||
},
|
||||
"search": {
|
||||
"searchQuery": "Search Query",
|
||||
"searchResults": "Search Results",
|
||||
"cachedResult": "Cached",
|
||||
"searchCost": "Cost",
|
||||
"searchTools": "Search Tools",
|
||||
"searchToolsDesc": "Advanced search testing with provider comparison",
|
||||
"compareProviders": "Compare Providers",
|
||||
"rerankResults": "Rerank Results",
|
||||
"searchHistory": "Search History",
|
||||
"urlOverlap": "URL Overlap",
|
||||
"noSearchProviders": "No search providers configured. Add providers in Settings.",
|
||||
"noRerankModels": "No rerank model available",
|
||||
"webSearch": "Web Search",
|
||||
"provider": "Provider",
|
||||
"searchType": "Search Type",
|
||||
"maxResults": "Max Results",
|
||||
"filters": "Filters",
|
||||
"country": "Country",
|
||||
"language": "Language",
|
||||
"timeRange": "Time Range",
|
||||
"includeDomains": "Include Domains",
|
||||
"excludeDomains": "Exclude Domains",
|
||||
"safeSearch": "Safe Search",
|
||||
"formatted": "Formatted",
|
||||
"rawJson": "JSON",
|
||||
"cacheMiss": "cache miss",
|
||||
"cacheHit": "cache hit",
|
||||
"latency": "Latency",
|
||||
"cost": "Cost",
|
||||
"results": "Results",
|
||||
"rerank": "Rerank",
|
||||
"rerankModel": "Rerank Model",
|
||||
"positionDelta": "Position Change",
|
||||
"emptyState": "Send a search query to see results",
|
||||
"safeSearchOff": "Off",
|
||||
"safeSearchModerate": "Moderate",
|
||||
"safeSearchStrict": "Strict",
|
||||
"queryPlaceholder": "Enter search query...",
|
||||
"providerAuto": "auto (cheapest)",
|
||||
"searchTypeWeb": "web",
|
||||
"searchTypeNews": "news",
|
||||
"optionAny": "any",
|
||||
"timeRangeDay": "Past day",
|
||||
"timeRangeWeek": "Past week",
|
||||
"timeRangeMonth": "Past month",
|
||||
"timeRangeYear": "Past year",
|
||||
"domainPlaceholder": "example.com",
|
||||
"requestTimedOut": "Request timed out ({seconds}s)",
|
||||
"networkError": "Network error"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,7 +56,9 @@
|
||||
"warning": "Babala",
|
||||
"note": "Tandaan",
|
||||
"free": "Libre",
|
||||
"skipToContent": "Lumaktaw sa nilalaman"
|
||||
"skipToContent": "Lumaktaw sa nilalaman",
|
||||
"maintenanceServerIssues": "Server is experiencing issues. Some features may be unavailable.",
|
||||
"maintenanceServerUnreachable": "Server is unreachable. Reconnecting..."
|
||||
},
|
||||
"sidebar": {
|
||||
"home": "Bahay",
|
||||
@@ -103,7 +105,9 @@
|
||||
"endpoints": "Mga Endpoint",
|
||||
"playground": "Playground",
|
||||
"agents": "Mga Agent",
|
||||
"cliToolsShort": "Mga Tool"
|
||||
"cliToolsShort": "Mga Tool",
|
||||
"autoCombo": "Auto Combo",
|
||||
"searchTools": "Search Tools"
|
||||
},
|
||||
"themesPage": {
|
||||
"title": "Themes",
|
||||
@@ -268,7 +272,28 @@
|
||||
"permissionsTitle": "Mga Pahintulot: {name}",
|
||||
"allowAllDesc": "Maa-access ng key na ito ang lahat ng available na modelo.",
|
||||
"restrictDesc": "Maa-access ng key na ito ang {selectedCount} ng {totalModels} na mga modelo.",
|
||||
"selectedCount": "{count} ang napili"
|
||||
"selectedCount": "{count} ang napili",
|
||||
"autoResolve": "Auto-Resolve",
|
||||
"autoResolveDesc": "Auto-resolve ambiguous model names to native provider for this API key.",
|
||||
"keyActive": "Key Active",
|
||||
"keyActiveDesc": "Enable or disable this API key. Disabled keys are immediately rejected with 403.",
|
||||
"accessSchedule": "Access Schedule",
|
||||
"accessScheduleDesc": "Restrict access to specific hours and days of the week.",
|
||||
"scheduleFrom": "From",
|
||||
"scheduleUntil": "Until",
|
||||
"scheduleDays": "Days",
|
||||
"scheduleTimezone": "Timezone",
|
||||
"scheduleTimezoneHint": "Use IANA timezone names, e.g. America/New_York, Europe/Berlin",
|
||||
"scheduleActive": "Schedule",
|
||||
"disabled": "Disabled",
|
||||
"daySun": "Sun",
|
||||
"dayMon": "Mon",
|
||||
"dayTue": "Tue",
|
||||
"dayWed": "Wed",
|
||||
"dayThu": "Thu",
|
||||
"dayFri": "Fri",
|
||||
"daySat": "Sat",
|
||||
"keyOnlyAvailableAtCreation": "Full key available only at creation time — copy it when you first create the key"
|
||||
},
|
||||
"auditLog": {
|
||||
"title": "Log ng Audit",
|
||||
@@ -548,6 +573,27 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"mitmHowWorksDesc": "{toolName} sends requests to its provider endpoint. MITM intercepts and redirects them to OmniRoute.",
|
||||
"mitmStep1": "1. Start MITM to route requests through OmniRoute.",
|
||||
"mitmStep2Prefix": "2. Add",
|
||||
"mitmStep2Suffix": "to your hosts file as 127.0.0.1.",
|
||||
"mitmStep3": "3. Open {toolName} and requests will be proxied.",
|
||||
"whenToUseLabel": "Kailan gagamitin",
|
||||
"openToolDocs": "Buksan ang tool docs",
|
||||
"toolUseCases": {
|
||||
"claude": "Gamitin kapag gusto mo ng matibay na mga daloy ng trabaho sa pagpaplano at mahabang multi-file refactor na may Claude Code.",
|
||||
"codex": "Gamitin kapag ang iyong koponan ay na-standardize sa mga daloy ng OpenAI Codex CLI at profile-based na auth.",
|
||||
"droid": "Gamitin kapag kailangan mo ng lightweight na terminal agent na nakatuon sa mabilis na coding at command execution loops.",
|
||||
"openclaw": "Gamitin kapag gusto mo ng Open Claw style coding agent ngunit naruta sa pamamagitan ng mga patakaran ng OmniRoute.",
|
||||
"cline": "Gamitin kapag nag-configure ka ng mga ahente ng coding sa loob ng mga editor at gusto mo ng may gabay na pag-setup gamit ang mga modelong OmniRoute.",
|
||||
"kilo": "Gamitin kapag nakadepende ang iyong daloy ng trabaho sa mga command ng Kilo Code at mabilis na umuulit na pag-edit.",
|
||||
"cursor": "Gamitin kapag nagko-coding sa Cursor at kailangan mo ng mga custom na modelong katugma sa OpenAI sa pamamagitan ng OmniRoute.",
|
||||
"continue": "Gamitin kapag nagpapatakbo ng Magpatuloy sa mga IDE at kailangan mo ng portable JSON-based na configuration ng provider.",
|
||||
"opencode": "Gamitin kapag mas gusto mo ang terminal-native agent run at scripted automation sa pamamagitan ng OpenCode.",
|
||||
"kiro": "Gamitin kapag isinasama ang Kiro at kinokontrol ang pagruruta ng modelo sa gitna mula sa OmniRoute.",
|
||||
"antigravity": "Gamitin kapag ang trapiko ng Antigravity/Kiro ay dapat ma-intercept sa MITM at iruta sa OmniRoute.",
|
||||
"copilot": "Gamitin kapag gusto mong Copilot chat style UX habang ipinapatupad ang mga OmniRoute key at mga panuntunan sa pagruruta."
|
||||
}
|
||||
},
|
||||
"combos": {
|
||||
@@ -864,7 +910,12 @@
|
||||
"a2aQuickStartStep2": "Send JSON-RPC requests to `POST /a2a` using `message/send` or `message/stream`.",
|
||||
"a2aQuickStartStep3": "Subaybayan at kontrolin ang mga gawain gamit ang `tasks/get` at `tasks/cancel`.",
|
||||
"completionsLegacy": "Completions (Legacy)",
|
||||
"completionsLegacyDesc": "Legacy OpenAI text completions — accepts both prompt string and messages array format"
|
||||
"completionsLegacyDesc": "Legacy OpenAI text completions — accepts both prompt string and messages array format",
|
||||
"categorySearch": "Search & Discovery",
|
||||
"webSearch": "Web Search",
|
||||
"webSearchDesc": "Unified web search across multiple providers with automatic failover and caching",
|
||||
"searchProvider": "Search Provider",
|
||||
"searchProviderDesc": "This provider is used for web search via POST /v1/search. No model configuration needed — search providers are ready to use once an API key is connected."
|
||||
},
|
||||
"mcpDashboard": {
|
||||
"loading": "Nilo-load ang MCP dashboard...",
|
||||
@@ -1054,7 +1105,13 @@
|
||||
"resetAllTitle": "I-reset ang lahat ng mga circuit breaker sa malusog na estado",
|
||||
"resetting": "Nire-reset...",
|
||||
"resetAll": "I-reset Lahat",
|
||||
"until": "Hanggang {time}"
|
||||
"until": "Hanggang {time}",
|
||||
"activeProviders": "{count} active",
|
||||
"monitoredProviders": "{count} monitored",
|
||||
"configuredProvidersLabel": "Na-configure sa dashboard",
|
||||
"configuredProvidersHint": "Mga provider na may mga kredensyal na naka-save sa /dashboard/providers, anuman ang estado ng runtime.",
|
||||
"activeProvidersHint": "Kasalukuyang pinagana ang mga naka-configure na provider para sa mga kahilingan sa pagruruta.",
|
||||
"monitoredProvidersHint": "Ang mga provider ay kasalukuyang sinusubaybayan ng mga circuit-breaker na monitor ng kalusugan."
|
||||
},
|
||||
"limits": {
|
||||
"title": "Mga Limitasyon at Quota",
|
||||
@@ -1396,7 +1453,31 @@
|
||||
"chatPathHint": "Custom chat path for providers with non-standard APIs (e.g. /v4/chat/completions)",
|
||||
"modelsPathLabel": "Models Endpoint Path",
|
||||
"modelsPathPlaceholder": "/models",
|
||||
"modelsPathHint": "Custom models path for validation (e.g. /v4/models)"
|
||||
"modelsPathHint": "Custom models path for validation (e.g. /v4/models)",
|
||||
"builtInModels": "Built-in models",
|
||||
"builtInModelsHint": "Registry models for this provider. Use the pencil to set compatibility options.",
|
||||
"normalizeToolCallIdLabel": "Normalize tool call IDs to 9 characters (e.g. Mistral)",
|
||||
"preserveDeveloperRoleLabel": "Keep OpenAI Responses developer role (do not map to system)",
|
||||
"compatAdjustmentsTitle": "Compatibility",
|
||||
"compatButtonLabel": "Compatibility",
|
||||
"compatToolIdShort": "Tool ID 9",
|
||||
"compatDeveloperShort": "Developer role",
|
||||
"compatDoNotPreserveDeveloper": "Do not preserve developer role",
|
||||
"compatBadgeNoPreserve": "No preserve",
|
||||
"compatProtocolLabel": "Client request protocol",
|
||||
"compatProtocolHint": "These options apply when OmniRoute detects this request shape (OpenAI Chat, Responses API, or Anthropic Messages).",
|
||||
"compatProtocolOpenAI": "OpenAI Chat Completions",
|
||||
"compatProtocolOpenAIResponses": "OpenAI Responses API",
|
||||
"compatProtocolClaude": "Anthropic Messages",
|
||||
"tokenRefreshed": "Token refreshed successfully",
|
||||
"tokenRefreshFailed": "Token refresh failed",
|
||||
"compatBadgeUpstreamHeaders": "Headers",
|
||||
"compatUpstreamAddRow": "Add header",
|
||||
"compatUpstreamHeaderName": "Header name",
|
||||
"compatUpstreamHeaderValue": "Value",
|
||||
"compatUpstreamHeadersHint": "High-privilege setting — same trust level as editing provider API credentials; only trusted admins should use it. Merged after OmniRoute adds auth from the provider API key. If a custom header uses the same name as an existing one (e.g. Authorization), your value fully replaces the auto-generated header (including the Bearer token) — the upstream only sees what you typed, not the key from settings. Misconfiguration can cause 401 or broken upstream auth. One row per header (e.g. extra Authentication for some gateways). Hover or focus the value to preview. Saves on blur, outside click, or closing this panel.",
|
||||
"compatUpstreamHeadersLabel": "Extra upstream headers",
|
||||
"compatUpstreamRemoveRow": "Remove row"
|
||||
},
|
||||
"settings": {
|
||||
"title": "Mga setting",
|
||||
@@ -1799,7 +1880,13 @@
|
||||
"cliFingerprintDesc": "Match native CLI binary signatures when proxying requests. Reorders headers and body fields to look identical to the official CLI tools. Your proxy IP is preserved.",
|
||||
"cliFingerprintEnabled": "{count} provider(s) with CLI fingerprint active",
|
||||
"enableFingerprintTitle": "Enable fingerprint for {provider}",
|
||||
"disableFingerprintTitle": "Disable fingerprint for {provider}"
|
||||
"disableFingerprintTitle": "Disable fingerprint for {provider}",
|
||||
"routingAdvancedGuideTitle": "Gabay sa advanced na pagruruta",
|
||||
"routingAdvancedGuideHint1": "Gamitin ang Fill First para sa predictable priority, Round Robin para sa fairness, at P2C para sa latency resilience.",
|
||||
"routingAdvancedGuideHint2": "Kung iba-iba ang kalidad/gastos ng mga provider, magsimula sa Cost Opt para sa background na trabaho at Least Used para sa balanseng pagsusuot.",
|
||||
"comboDefaultsGuideTitle": "Paano i-tune ang mga default ng combo",
|
||||
"comboDefaultsGuideHint1": "Panatilihing mababa ang mga muling pagsubok sa mga daloy na mababa ang latency; taasan ang timeout para lang sa mga gawaing pang-generation.",
|
||||
"comboDefaultsGuideHint2": "Gumamit ng mga override ng provider kapag ang isang provider ay nangangailangan ng iba't ibang gawi sa pag-timeout/subukang muli kaysa sa mga pandaigdigang default."
|
||||
},
|
||||
"translator": {
|
||||
"title": "Tagasalin",
|
||||
@@ -2216,7 +2303,8 @@
|
||||
"orRemovePasswordHashField": "o alisin ang field ng passwordHash",
|
||||
"restartServerWithNewPassword": "I-restart ang server - gagamitin nito ang bagong password",
|
||||
"backToLogin": "Bumalik sa Login",
|
||||
"forgotPassword": "Nakalimutan ang password?"
|
||||
"forgotPassword": "Nakalimutan ang password?",
|
||||
"defaultPasswordHint": "Default password: 123456 (unless INITIAL_PASSWORD was set)"
|
||||
},
|
||||
"landing": {
|
||||
"brandName": "OmniRoute",
|
||||
@@ -2414,7 +2502,16 @@
|
||||
"troubleshootingCodexFamily": "Para sa mga modelo ng pamilya ng GitHub Codex, panatilihing gh/codex-model ang modelo; awtomatikong pinipili /tugon ng router.",
|
||||
"troubleshootingTestConnection": "Gamitin ang Dashboard > Mga Provider > Subukan ang Koneksyon bago subukan mula sa mga IDE o external na kliyente.",
|
||||
"troubleshootingCircuitBreaker": "Kung ipinapakita ng provider na bukas ang circuit breaker, hintayin ang cooldown o tingnan ang page ng Health para sa mga detalye.",
|
||||
"troubleshootingOAuth": "Para sa mga provider ng OAuth, muling i-authenticate kung mag-e-expire ang mga token. Suriin ang tagapagpahiwatig ng katayuan ng provider card."
|
||||
"troubleshootingOAuth": "Para sa mga provider ng OAuth, muling i-authenticate kung mag-e-expire ang mga token. Suriin ang tagapagpahiwatig ng katayuan ng provider card.",
|
||||
"managementApiReference": "Management API Reference",
|
||||
"managementApiDescription": "Automation endpoints for proxy registry, scope assignments, and legacy proxy migration.",
|
||||
"mgmtProxiesListNote": "List saved proxy registry items (supports pagination).",
|
||||
"mgmtProxiesCreateNote": "Create a reusable proxy item in the registry.",
|
||||
"mgmtProxiesHealthNote": "Get 24h/rolling health metrics per saved proxy from proxy logs.",
|
||||
"mgmtProxiesBulkAssignNote": "Assign or clear one proxy across many scope IDs in one request.",
|
||||
"mgmtAssignmentsListNote": "List proxy assignments by scope, scope_id, or proxy_id.",
|
||||
"mgmtAssignmentsUpdateNote": "Assign or clear proxy for global/provider/account/combo scope.",
|
||||
"mgmtLegacyMigrationNote": "Import legacy proxyConfig maps into registry assignments."
|
||||
},
|
||||
"legal": {
|
||||
"privacyPolicy": "Patakaran sa Privacy",
|
||||
@@ -2503,7 +2600,21 @@
|
||||
"binaryName": "Binary Name",
|
||||
"versionCommand": "Version Command",
|
||||
"spawnArgs": "Spawn Args",
|
||||
"addAgent": "Add Agent"
|
||||
"addAgent": "Add Agent",
|
||||
"scanning": "Scanning system for CLI agents...",
|
||||
"opencodeIntegration": "OpenCode Integration",
|
||||
"opencodeDetected": "opencode {version} detected",
|
||||
"opencodeDesc": "Generate a ready-to-use {configFile} with your OmniRoute base URL and all available models — drop it in your project root and run {command}.",
|
||||
"downloadConfig": "Download {file}",
|
||||
"downloaded": "Downloaded!",
|
||||
"setupGuideTitle": "Setup guide",
|
||||
"openCliTools": "Open CLI Tools",
|
||||
"setupGuideDetectCliTitle": "Detect installed CLIs",
|
||||
"setupGuideDetectCliDesc": "Click Refresh after installing or updating a CLI so OmniRoute can rescan binaries and versions.",
|
||||
"setupGuideCustomAgentTitle": "Register custom binary",
|
||||
"setupGuideCustomAgentDesc": "Use Add Custom Agent when your CLI is not in the built-in list. Provide binary name and version command.",
|
||||
"setupGuideCommandMissingTitle": "Fix 'command not found'",
|
||||
"setupGuideCommandMissingDesc": "Ensure the CLI command exists in PATH, open a new terminal session, and rerun Refresh."
|
||||
},
|
||||
"autoCombo": {
|
||||
"title": "Auto-Combo Engine",
|
||||
@@ -2527,5 +2638,56 @@
|
||||
"modePackCostSaver": "Cost Saver",
|
||||
"modePackQualityFirst": "Quality First",
|
||||
"modePackOfflineFriendly": "Offline Friendly"
|
||||
},
|
||||
"search": {
|
||||
"searchQuery": "Search Query",
|
||||
"searchResults": "Search Results",
|
||||
"cachedResult": "Cached",
|
||||
"searchCost": "Cost",
|
||||
"searchTools": "Search Tools",
|
||||
"searchToolsDesc": "Advanced search testing with provider comparison",
|
||||
"compareProviders": "Compare Providers",
|
||||
"rerankResults": "Rerank Results",
|
||||
"searchHistory": "Search History",
|
||||
"urlOverlap": "URL Overlap",
|
||||
"noSearchProviders": "No search providers configured. Add providers in Settings.",
|
||||
"noRerankModels": "No rerank model available",
|
||||
"webSearch": "Web Search",
|
||||
"provider": "Provider",
|
||||
"searchType": "Search Type",
|
||||
"maxResults": "Max Results",
|
||||
"filters": "Filters",
|
||||
"country": "Country",
|
||||
"language": "Language",
|
||||
"timeRange": "Time Range",
|
||||
"includeDomains": "Include Domains",
|
||||
"excludeDomains": "Exclude Domains",
|
||||
"safeSearch": "Safe Search",
|
||||
"formatted": "Formatted",
|
||||
"rawJson": "JSON",
|
||||
"cacheMiss": "cache miss",
|
||||
"cacheHit": "cache hit",
|
||||
"latency": "Latency",
|
||||
"cost": "Cost",
|
||||
"results": "Results",
|
||||
"rerank": "Rerank",
|
||||
"rerankModel": "Rerank Model",
|
||||
"positionDelta": "Position Change",
|
||||
"emptyState": "Send a search query to see results",
|
||||
"safeSearchOff": "Off",
|
||||
"safeSearchModerate": "Moderate",
|
||||
"safeSearchStrict": "Strict",
|
||||
"queryPlaceholder": "Enter search query...",
|
||||
"providerAuto": "auto (cheapest)",
|
||||
"searchTypeWeb": "web",
|
||||
"searchTypeNews": "news",
|
||||
"optionAny": "any",
|
||||
"timeRangeDay": "Past day",
|
||||
"timeRangeWeek": "Past week",
|
||||
"timeRangeMonth": "Past month",
|
||||
"timeRangeYear": "Past year",
|
||||
"domainPlaceholder": "example.com",
|
||||
"requestTimedOut": "Request timed out ({seconds}s)",
|
||||
"networkError": "Network error"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,7 +56,9 @@
|
||||
"warning": "Ostrzeżenie",
|
||||
"note": "Uwaga",
|
||||
"free": "Bezpłatny",
|
||||
"skipToContent": "Przejdź do treści"
|
||||
"skipToContent": "Przejdź do treści",
|
||||
"maintenanceServerIssues": "Server is experiencing issues. Some features may be unavailable.",
|
||||
"maintenanceServerUnreachable": "Server is unreachable. Reconnecting..."
|
||||
},
|
||||
"sidebar": {
|
||||
"home": "Dom",
|
||||
@@ -103,7 +105,9 @@
|
||||
"endpoints": "Punkty końcowe",
|
||||
"playground": "Plac zabaw",
|
||||
"agents": "Agenci",
|
||||
"cliToolsShort": "Narzędzia"
|
||||
"cliToolsShort": "Narzędzia",
|
||||
"autoCombo": "Auto Combo",
|
||||
"searchTools": "Search Tools"
|
||||
},
|
||||
"themesPage": {
|
||||
"title": "Themes",
|
||||
@@ -268,7 +272,28 @@
|
||||
"permissionsTitle": "Uprawnienia: {name}",
|
||||
"allowAllDesc": "Ten klucz umożliwia dostęp do wszystkich dostępnych modeli.",
|
||||
"restrictDesc": "Ten klucz umożliwia dostęp do {selectedCount} z {totalModels} modeli.",
|
||||
"selectedCount": "Wybrano {count}"
|
||||
"selectedCount": "Wybrano {count}",
|
||||
"autoResolve": "Auto-Resolve",
|
||||
"autoResolveDesc": "Auto-resolve ambiguous model names to native provider for this API key.",
|
||||
"keyActive": "Key Active",
|
||||
"keyActiveDesc": "Enable or disable this API key. Disabled keys are immediately rejected with 403.",
|
||||
"accessSchedule": "Access Schedule",
|
||||
"accessScheduleDesc": "Restrict access to specific hours and days of the week.",
|
||||
"scheduleFrom": "From",
|
||||
"scheduleUntil": "Until",
|
||||
"scheduleDays": "Days",
|
||||
"scheduleTimezone": "Timezone",
|
||||
"scheduleTimezoneHint": "Use IANA timezone names, e.g. America/New_York, Europe/Berlin",
|
||||
"scheduleActive": "Schedule",
|
||||
"disabled": "Disabled",
|
||||
"daySun": "Sun",
|
||||
"dayMon": "Mon",
|
||||
"dayTue": "Tue",
|
||||
"dayWed": "Wed",
|
||||
"dayThu": "Thu",
|
||||
"dayFri": "Fri",
|
||||
"daySat": "Sat",
|
||||
"keyOnlyAvailableAtCreation": "Full key available only at creation time — copy it when you first create the key"
|
||||
},
|
||||
"auditLog": {
|
||||
"title": "Dziennik audytu",
|
||||
@@ -548,6 +573,27 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"mitmHowWorksDesc": "{toolName} sends requests to its provider endpoint. MITM intercepts and redirects them to OmniRoute.",
|
||||
"mitmStep1": "1. Start MITM to route requests through OmniRoute.",
|
||||
"mitmStep2Prefix": "2. Add",
|
||||
"mitmStep2Suffix": "to your hosts file as 127.0.0.1.",
|
||||
"mitmStep3": "3. Open {toolName} and requests will be proxied.",
|
||||
"whenToUseLabel": "Kiedy używać",
|
||||
"openToolDocs": "Otwórz dokumentację narzędzi",
|
||||
"toolUseCases": {
|
||||
"claude": "Użyj, jeśli chcesz mieć silne przepływy pracy związane z planowaniem i długie refaktoryzacje wielu plików za pomocą Claude Code.",
|
||||
"codex": "Użyj, gdy Twój zespół jest ujednolicony w zakresie przepływów CLI OpenAI Codex i uwierzytelniania opartego na profilach.",
|
||||
"droid": "Użyj, gdy potrzebujesz lekkiego agenta terminalowego skupionego na szybkim kodowaniu i pętlach wykonywania poleceń.",
|
||||
"openclaw": "Użyj, jeśli chcesz agenta kodującego w stylu Open Claw, ale kierowanego przez zasady OmniRoute.",
|
||||
"cline": "Użyj, gdy konfigurujesz agentów kodujących w edytorach i chcesz przeprowadzić konfigurację z pomocą modeli OmniRoute.",
|
||||
"kilo": "Użyj, gdy przepływ pracy zależy od poleceń Kilo Code i szybkich edycji iteracyjnych.",
|
||||
"cursor": "Użyj podczas kodowania w programie Cursor, jeśli potrzebujesz niestandardowych modeli zgodnych z OpenAI za pośrednictwem OmniRoute.",
|
||||
"continue": "Użyj podczas uruchamiania Kontynuuj w środowiskach IDE, jeśli potrzebujesz przenośnej konfiguracji dostawcy opartej na formacie JSON.",
|
||||
"opencode": "Użyj, jeśli wolisz uruchamianie agentów natywnych w terminalu i automatyzację skryptów za pośrednictwem OpenCode.",
|
||||
"kiro": "Użyj podczas integracji Kiro i centralnego sterowania routingiem modeli z OmniRoute.",
|
||||
"antigravity": "Użyj, gdy ruch antygrawitacyjny/Kiro musi zostać przechwycony przez MITM i skierowany do OmniRoute.",
|
||||
"copilot": "Użyj, jeśli chcesz mieć UX w stylu czatu Copilot, jednocześnie wymuszając klucze OmniRoute i reguły routingu."
|
||||
}
|
||||
},
|
||||
"combos": {
|
||||
@@ -864,7 +910,12 @@
|
||||
"a2aQuickStartStep2": "Send JSON-RPC requests to `POST /a2a` using `message/send` or `message/stream`.",
|
||||
"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"
|
||||
"completionsLegacyDesc": "Legacy OpenAI text completions — accepts both prompt string and messages array format",
|
||||
"categorySearch": "Search & Discovery",
|
||||
"webSearch": "Web Search",
|
||||
"webSearchDesc": "Unified web search across multiple providers with automatic failover and caching",
|
||||
"searchProvider": "Search Provider",
|
||||
"searchProviderDesc": "This provider is used for web search via POST /v1/search. No model configuration needed — search providers are ready to use once an API key is connected."
|
||||
},
|
||||
"mcpDashboard": {
|
||||
"loading": "Loading MCP dashboard...",
|
||||
@@ -1054,7 +1105,13 @@
|
||||
"resetAllTitle": "Zresetuj wszystkie wyłączniki automatyczne do stanu prawidłowego",
|
||||
"resetting": "Resetuję...",
|
||||
"resetAll": "Zresetuj wszystko",
|
||||
"until": "Do {time}"
|
||||
"until": "Do {time}",
|
||||
"activeProviders": "{count} active",
|
||||
"monitoredProviders": "{count} monitored",
|
||||
"configuredProvidersLabel": "Skonfigurowane w panelu kontrolnym",
|
||||
"configuredProvidersHint": "Dostawcy z poświadczeniami zapisanymi w /dashboard/providers, niezależnie od stanu środowiska wykonawczego.",
|
||||
"activeProvidersHint": "Skonfigurowani dostawcy aktualnie obsługują żądania routingu.",
|
||||
"monitoredProvidersHint": "Dostawcy są obecnie śledzeni przez monitory stanu wyłączników."
|
||||
},
|
||||
"limits": {
|
||||
"title": "Limity i kwoty",
|
||||
@@ -1396,7 +1453,31 @@
|
||||
"chatPathHint": "Custom chat path for providers with non-standard APIs (e.g. /v4/chat/completions)",
|
||||
"modelsPathLabel": "Models Endpoint Path",
|
||||
"modelsPathPlaceholder": "/models",
|
||||
"modelsPathHint": "Custom models path for validation (e.g. /v4/models)"
|
||||
"modelsPathHint": "Custom models path for validation (e.g. /v4/models)",
|
||||
"builtInModels": "Built-in models",
|
||||
"builtInModelsHint": "Registry models for this provider. Use the pencil to set compatibility options.",
|
||||
"normalizeToolCallIdLabel": "Normalize tool call IDs to 9 characters (e.g. Mistral)",
|
||||
"preserveDeveloperRoleLabel": "Keep OpenAI Responses developer role (do not map to system)",
|
||||
"compatAdjustmentsTitle": "Compatibility",
|
||||
"compatButtonLabel": "Compatibility",
|
||||
"compatToolIdShort": "Tool ID 9",
|
||||
"compatDeveloperShort": "Developer role",
|
||||
"compatDoNotPreserveDeveloper": "Do not preserve developer role",
|
||||
"compatBadgeNoPreserve": "No preserve",
|
||||
"compatProtocolLabel": "Client request protocol",
|
||||
"compatProtocolHint": "These options apply when OmniRoute detects this request shape (OpenAI Chat, Responses API, or Anthropic Messages).",
|
||||
"compatProtocolOpenAI": "OpenAI Chat Completions",
|
||||
"compatProtocolOpenAIResponses": "OpenAI Responses API",
|
||||
"compatProtocolClaude": "Anthropic Messages",
|
||||
"tokenRefreshed": "Token refreshed successfully",
|
||||
"tokenRefreshFailed": "Token refresh failed",
|
||||
"compatBadgeUpstreamHeaders": "Headers",
|
||||
"compatUpstreamAddRow": "Add header",
|
||||
"compatUpstreamHeaderName": "Header name",
|
||||
"compatUpstreamHeaderValue": "Value",
|
||||
"compatUpstreamHeadersHint": "High-privilege setting — same trust level as editing provider API credentials; only trusted admins should use it. Merged after OmniRoute adds auth from the provider API key. If a custom header uses the same name as an existing one (e.g. Authorization), your value fully replaces the auto-generated header (including the Bearer token) — the upstream only sees what you typed, not the key from settings. Misconfiguration can cause 401 or broken upstream auth. One row per header (e.g. extra Authentication for some gateways). Hover or focus the value to preview. Saves on blur, outside click, or closing this panel.",
|
||||
"compatUpstreamHeadersLabel": "Extra upstream headers",
|
||||
"compatUpstreamRemoveRow": "Remove row"
|
||||
},
|
||||
"settings": {
|
||||
"title": "Ustawienia",
|
||||
@@ -1799,7 +1880,13 @@
|
||||
"cliFingerprintDesc": "Match native CLI binary signatures when proxying requests. Reorders headers and body fields to look identical to the official CLI tools. Your proxy IP is preserved.",
|
||||
"cliFingerprintEnabled": "{count} provider(s) with CLI fingerprint active",
|
||||
"enableFingerprintTitle": "Enable fingerprint for {provider}",
|
||||
"disableFingerprintTitle": "Disable fingerprint for {provider}"
|
||||
"disableFingerprintTitle": "Disable fingerprint for {provider}",
|
||||
"routingAdvancedGuideTitle": "Zaawansowane wskazówki dotyczące tras",
|
||||
"routingAdvancedGuideHint1": "Użyj opcji Fill First, aby uzyskać przewidywalny priorytet, Round Robin, aby zapewnić uczciwość, i P2C, aby zapewnić odporność na opóźnienia.",
|
||||
"routingAdvancedGuideHint2": "Jeśli dostawcy różnią się jakością/kosztami, zacznij od opcji Koszt w przypadku pracy w tle i opcji Najmniej używane w celu zapewnienia zrównoważonego zużycia.",
|
||||
"comboDefaultsGuideTitle": "Jak dostroić domyślne ustawienia kombinacji",
|
||||
"comboDefaultsGuideHint1": "Utrzymuj niską liczbę ponownych prób w przepływach o małych opóźnieniach; zwiększaj limit czasu tylko dla zadań o długim generowaniu.",
|
||||
"comboDefaultsGuideHint2": "Użyj zastąpienia dostawcy, gdy jeden z dostawców wymaga innego zachowania związanego z przekroczeniem limitu czasu/ponownej próby niż globalne ustawienia domyślne."
|
||||
},
|
||||
"translator": {
|
||||
"title": "Tłumacz",
|
||||
@@ -2216,7 +2303,8 @@
|
||||
"orRemovePasswordHashField": "lub usuń pole hasłoHash",
|
||||
"restartServerWithNewPassword": "Zrestartuj serwer - będzie używał nowego hasła",
|
||||
"backToLogin": "Powrót do logowania",
|
||||
"forgotPassword": "Zapomniałeś hasła?"
|
||||
"forgotPassword": "Zapomniałeś hasła?",
|
||||
"defaultPasswordHint": "Default password: 123456 (unless INITIAL_PASSWORD was set)"
|
||||
},
|
||||
"landing": {
|
||||
"brandName": "OmniRoute",
|
||||
@@ -2414,7 +2502,16 @@
|
||||
"troubleshootingCodexFamily": "W przypadku modeli z rodziny GitHub Codex zachowaj model jako gh/codex-model; router wybiera opcję /odpowiada automatycznie.",
|
||||
"troubleshootingTestConnection": "Użyj Panelu sterowania > Dostawcy > Testuj połączenie przed testowaniem z IDE lub klientów zewnętrznych.",
|
||||
"troubleshootingCircuitBreaker": "Jeśli dostawca pokazuje, że wyłącznik jest otwarty, poczekaj na ochłodzenie lub sprawdź stronę Zdrowie, aby uzyskać szczegółowe informacje.",
|
||||
"troubleshootingOAuth": "W przypadku dostawców OAuth należy ponownie uwierzytelnić, jeśli tokeny wygasną. Sprawdź wskaźnik stanu karty dostawcy."
|
||||
"troubleshootingOAuth": "W przypadku dostawców OAuth należy ponownie uwierzytelnić, jeśli tokeny wygasną. Sprawdź wskaźnik stanu karty dostawcy.",
|
||||
"managementApiReference": "Management API Reference",
|
||||
"managementApiDescription": "Automation endpoints for proxy registry, scope assignments, and legacy proxy migration.",
|
||||
"mgmtProxiesListNote": "List saved proxy registry items (supports pagination).",
|
||||
"mgmtProxiesCreateNote": "Create a reusable proxy item in the registry.",
|
||||
"mgmtProxiesHealthNote": "Get 24h/rolling health metrics per saved proxy from proxy logs.",
|
||||
"mgmtProxiesBulkAssignNote": "Assign or clear one proxy across many scope IDs in one request.",
|
||||
"mgmtAssignmentsListNote": "List proxy assignments by scope, scope_id, or proxy_id.",
|
||||
"mgmtAssignmentsUpdateNote": "Assign or clear proxy for global/provider/account/combo scope.",
|
||||
"mgmtLegacyMigrationNote": "Import legacy proxyConfig maps into registry assignments."
|
||||
},
|
||||
"legal": {
|
||||
"privacyPolicy": "Polityka prywatności",
|
||||
@@ -2503,7 +2600,21 @@
|
||||
"binaryName": "Binary Name",
|
||||
"versionCommand": "Version Command",
|
||||
"spawnArgs": "Spawn Args",
|
||||
"addAgent": "Add Agent"
|
||||
"addAgent": "Add Agent",
|
||||
"scanning": "Scanning system for CLI agents...",
|
||||
"opencodeIntegration": "OpenCode Integration",
|
||||
"opencodeDetected": "opencode {version} detected",
|
||||
"opencodeDesc": "Generate a ready-to-use {configFile} with your OmniRoute base URL and all available models — drop it in your project root and run {command}.",
|
||||
"downloadConfig": "Download {file}",
|
||||
"downloaded": "Downloaded!",
|
||||
"setupGuideTitle": "Setup guide",
|
||||
"openCliTools": "Open CLI Tools",
|
||||
"setupGuideDetectCliTitle": "Detect installed CLIs",
|
||||
"setupGuideDetectCliDesc": "Click Refresh after installing or updating a CLI so OmniRoute can rescan binaries and versions.",
|
||||
"setupGuideCustomAgentTitle": "Register custom binary",
|
||||
"setupGuideCustomAgentDesc": "Use Add Custom Agent when your CLI is not in the built-in list. Provide binary name and version command.",
|
||||
"setupGuideCommandMissingTitle": "Fix 'command not found'",
|
||||
"setupGuideCommandMissingDesc": "Ensure the CLI command exists in PATH, open a new terminal session, and rerun Refresh."
|
||||
},
|
||||
"autoCombo": {
|
||||
"title": "Auto-Combo Engine",
|
||||
@@ -2527,5 +2638,56 @@
|
||||
"modePackCostSaver": "Cost Saver",
|
||||
"modePackQualityFirst": "Quality First",
|
||||
"modePackOfflineFriendly": "Offline Friendly"
|
||||
},
|
||||
"search": {
|
||||
"searchQuery": "Search Query",
|
||||
"searchResults": "Search Results",
|
||||
"cachedResult": "Cached",
|
||||
"searchCost": "Cost",
|
||||
"searchTools": "Search Tools",
|
||||
"searchToolsDesc": "Advanced search testing with provider comparison",
|
||||
"compareProviders": "Compare Providers",
|
||||
"rerankResults": "Rerank Results",
|
||||
"searchHistory": "Search History",
|
||||
"urlOverlap": "URL Overlap",
|
||||
"noSearchProviders": "No search providers configured. Add providers in Settings.",
|
||||
"noRerankModels": "No rerank model available",
|
||||
"webSearch": "Web Search",
|
||||
"provider": "Provider",
|
||||
"searchType": "Search Type",
|
||||
"maxResults": "Max Results",
|
||||
"filters": "Filters",
|
||||
"country": "Country",
|
||||
"language": "Language",
|
||||
"timeRange": "Time Range",
|
||||
"includeDomains": "Include Domains",
|
||||
"excludeDomains": "Exclude Domains",
|
||||
"safeSearch": "Safe Search",
|
||||
"formatted": "Formatted",
|
||||
"rawJson": "JSON",
|
||||
"cacheMiss": "cache miss",
|
||||
"cacheHit": "cache hit",
|
||||
"latency": "Latency",
|
||||
"cost": "Cost",
|
||||
"results": "Results",
|
||||
"rerank": "Rerank",
|
||||
"rerankModel": "Rerank Model",
|
||||
"positionDelta": "Position Change",
|
||||
"emptyState": "Send a search query to see results",
|
||||
"safeSearchOff": "Off",
|
||||
"safeSearchModerate": "Moderate",
|
||||
"safeSearchStrict": "Strict",
|
||||
"queryPlaceholder": "Enter search query...",
|
||||
"providerAuto": "auto (cheapest)",
|
||||
"searchTypeWeb": "web",
|
||||
"searchTypeNews": "news",
|
||||
"optionAny": "any",
|
||||
"timeRangeDay": "Past day",
|
||||
"timeRangeWeek": "Past week",
|
||||
"timeRangeMonth": "Past month",
|
||||
"timeRangeYear": "Past year",
|
||||
"domainPlaceholder": "example.com",
|
||||
"requestTimedOut": "Request timed out ({seconds}s)",
|
||||
"networkError": "Network error"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,7 +56,9 @@
|
||||
"warning": "Aviso",
|
||||
"note": "Nota",
|
||||
"free": "Gratuito",
|
||||
"skipToContent": "Pular para conteúdo"
|
||||
"skipToContent": "Pular para conteúdo",
|
||||
"maintenanceServerIssues": "Server is experiencing issues. Some features may be unavailable.",
|
||||
"maintenanceServerUnreachable": "Server is unreachable. Reconnecting..."
|
||||
},
|
||||
"sidebar": {
|
||||
"home": "Início",
|
||||
@@ -104,7 +106,8 @@
|
||||
"endpoints": "Endpoints",
|
||||
"playground": "Playground",
|
||||
"agents": "Agentes",
|
||||
"cliToolsShort": "Ferramentas"
|
||||
"cliToolsShort": "Ferramentas",
|
||||
"searchTools": "Search Tools"
|
||||
},
|
||||
"themesPage": {
|
||||
"title": "Themes",
|
||||
@@ -289,7 +292,8 @@
|
||||
"permissionsTitle": "Permissões: {name}",
|
||||
"allowAllDesc": "Esta chave pode acessar todos os modelos disponíveis.",
|
||||
"restrictDesc": "Esta chave pode acessar {selectedCount} de {totalModels} modelos.",
|
||||
"selectedCount": "{count} selecionados"
|
||||
"selectedCount": "{count} selecionados",
|
||||
"keyOnlyAvailableAtCreation": "Full key available only at creation time — copy it when you first create the key"
|
||||
},
|
||||
"auditLog": {
|
||||
"title": "Log de Auditoria",
|
||||
@@ -569,6 +573,27 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"mitmHowWorksDesc": "{toolName} sends requests to its provider endpoint. MITM intercepts and redirects them to OmniRoute.",
|
||||
"mitmStep1": "1. Start MITM to route requests through OmniRoute.",
|
||||
"mitmStep2Prefix": "2. Add",
|
||||
"mitmStep2Suffix": "to your hosts file as 127.0.0.1.",
|
||||
"mitmStep3": "3. Open {toolName} and requests will be proxied.",
|
||||
"whenToUseLabel": "Quando usar",
|
||||
"openToolDocs": "Abrir documentos de ferramentas",
|
||||
"toolUseCases": {
|
||||
"claude": "Use quando desejar fluxos de trabalho de planejamento robustos e refatorações longas de vários arquivos com Claude Code.",
|
||||
"codex": "Use quando sua equipe estiver padronizada em fluxos OpenAI Codex CLI e autenticação baseada em perfil.",
|
||||
"droid": "Use quando precisar de um agente de terminal leve focado em codificação rápida e loops de execução de comandos.",
|
||||
"openclaw": "Use quando desejar um agente de codificação no estilo Open Claw, mas roteado por meio de políticas OmniRoute.",
|
||||
"cline": "Use quando você configura agentes de codificação dentro de editores e deseja configuração guiada com modelos OmniRoute.",
|
||||
"kilo": "Use quando seu fluxo de trabalho depender de comandos Kilo Code e edições iterativas rápidas.",
|
||||
"cursor": "Use ao codificar no Cursor e você precisar de modelos personalizados compatíveis com OpenAI por meio do OmniRoute.",
|
||||
"continue": "Use ao executar Continue em IDEs e você precisar de uma configuração de provedor portátil baseada em JSON.",
|
||||
"opencode": "Use quando preferir execuções de agentes nativos de terminal e automação com script via OpenCode.",
|
||||
"kiro": "Use ao integrar o Kiro e controlar o roteamento do modelo centralmente no OmniRoute.",
|
||||
"antigravity": "Use quando o tráfego Antigravity/Kiro deve ser interceptado através do MITM e roteado para OmniRoute.",
|
||||
"copilot": "Use quando desejar UX no estilo de bate-papo do Copilot enquanto impõe chaves OmniRoute e regras de roteamento."
|
||||
}
|
||||
},
|
||||
"combos": {
|
||||
@@ -897,7 +922,12 @@
|
||||
"a2aQuickStartStep2": "Envie requisições JSON-RPC para `POST /a2a` usando `message/send` ou `message/stream`.",
|
||||
"a2aQuickStartStep3": "Acompanhe e controle tarefas com `tasks/get` e `tasks/cancel`.",
|
||||
"completionsLegacy": "Completions (Legacy)",
|
||||
"completionsLegacyDesc": "Legacy OpenAI text completions — accepts both prompt string and messages array format"
|
||||
"completionsLegacyDesc": "Legacy OpenAI text completions — accepts both prompt string and messages array format",
|
||||
"categorySearch": "Search & Discovery",
|
||||
"webSearch": "Web Search",
|
||||
"webSearchDesc": "Unified web search across multiple providers with automatic failover and caching",
|
||||
"searchProvider": "Search Provider",
|
||||
"searchProviderDesc": "This provider is used for web search via POST /v1/search. No model configuration needed — search providers are ready to use once an API key is connected."
|
||||
},
|
||||
"mcpDashboard": {
|
||||
"loading": "Carregando painel MCP...",
|
||||
@@ -1087,7 +1117,13 @@
|
||||
"resetAllTitle": "Resetar todos os circuit breakers para estado saudável",
|
||||
"resetting": "Resetando...",
|
||||
"resetAll": "Resetar Tudo",
|
||||
"until": "Até {time}"
|
||||
"until": "Até {time}",
|
||||
"activeProviders": "{count} active",
|
||||
"monitoredProviders": "{count} monitored",
|
||||
"configuredProvidersLabel": "Configurado no painel",
|
||||
"configuredProvidersHint": "Provedores com credenciais salvas em /dashboard/providers, independentemente do estado do tempo de execução.",
|
||||
"activeProvidersHint": "Provedores configurados atualmente habilitados para solicitações de roteamento.",
|
||||
"monitoredProvidersHint": "Provedores atualmente monitorados por monitores de integridade dos disjuntores."
|
||||
},
|
||||
"limits": {
|
||||
"title": "Limites e Cotas",
|
||||
@@ -1429,7 +1465,31 @@
|
||||
"chatPathHint": "Custom chat path for providers with non-standard APIs (e.g. /v4/chat/completions)",
|
||||
"modelsPathLabel": "Models Endpoint Path",
|
||||
"modelsPathPlaceholder": "/models",
|
||||
"modelsPathHint": "Custom models path for validation (e.g. /v4/models)"
|
||||
"modelsPathHint": "Custom models path for validation (e.g. /v4/models)",
|
||||
"builtInModels": "Built-in models",
|
||||
"builtInModelsHint": "Registry models for this provider. Use the pencil to set compatibility options.",
|
||||
"normalizeToolCallIdLabel": "Normalize tool call IDs to 9 characters (e.g. Mistral)",
|
||||
"preserveDeveloperRoleLabel": "Keep OpenAI Responses developer role (do not map to system)",
|
||||
"compatAdjustmentsTitle": "Compatibility",
|
||||
"compatButtonLabel": "Compatibility",
|
||||
"compatToolIdShort": "Tool ID 9",
|
||||
"compatDeveloperShort": "Developer role",
|
||||
"compatDoNotPreserveDeveloper": "Do not preserve developer role",
|
||||
"compatBadgeNoPreserve": "No preserve",
|
||||
"compatProtocolLabel": "Client request protocol",
|
||||
"compatProtocolHint": "These options apply when OmniRoute detects this request shape (OpenAI Chat, Responses API, or Anthropic Messages).",
|
||||
"compatProtocolOpenAI": "OpenAI Chat Completions",
|
||||
"compatProtocolOpenAIResponses": "OpenAI Responses API",
|
||||
"compatProtocolClaude": "Anthropic Messages",
|
||||
"tokenRefreshed": "Token refreshed successfully",
|
||||
"tokenRefreshFailed": "Token refresh failed",
|
||||
"compatBadgeUpstreamHeaders": "Headers",
|
||||
"compatUpstreamAddRow": "Add header",
|
||||
"compatUpstreamHeaderName": "Header name",
|
||||
"compatUpstreamHeaderValue": "Value",
|
||||
"compatUpstreamHeadersHint": "High-privilege setting — same trust level as editing provider API credentials; only trusted admins should use it. Merged after OmniRoute adds auth from the provider API key. If a custom header uses the same name as an existing one (e.g. Authorization), your value fully replaces the auto-generated header (including the Bearer token) — the upstream only sees what you typed, not the key from settings. Misconfiguration can cause 401 or broken upstream auth. One row per header (e.g. extra Authentication for some gateways). Hover or focus the value to preview. Saves on blur, outside click, or closing this panel.",
|
||||
"compatUpstreamHeadersLabel": "Extra upstream headers",
|
||||
"compatUpstreamRemoveRow": "Remove row"
|
||||
},
|
||||
"settings": {
|
||||
"title": "Configurações",
|
||||
@@ -1832,7 +1892,13 @@
|
||||
"customPricingNote": "Você pode sobrescrever preços padrão para modelos específicos. Sobrescritas personalizadas têm prioridade sobre preços detectados automaticamente.",
|
||||
"editPricing": "Editar Preços",
|
||||
"viewFullDetails": "Ver Detalhes Completos",
|
||||
"themeCoral": "Coral"
|
||||
"themeCoral": "Coral",
|
||||
"routingAdvancedGuideTitle": "Orientação avançada de roteamento",
|
||||
"routingAdvancedGuideHint1": "Use Fill First para prioridade previsível, Round Robin para justiça e P2C para resiliência de latência.",
|
||||
"routingAdvancedGuideHint2": "Se os fornecedores variarem em qualidade/custo, comece com Opção de custo para trabalho em segundo plano e Menos usado para desgaste equilibrado.",
|
||||
"comboDefaultsGuideTitle": "Como ajustar os padrões de combinação",
|
||||
"comboDefaultsGuideHint1": "Mantenha as tentativas baixas em fluxos de baixa latência; aumente o tempo limite apenas para tarefas de geração longa.",
|
||||
"comboDefaultsGuideHint2": "Use substituições de provedor quando um provedor precisar de um comportamento de tempo limite/nova tentativa diferente dos padrões globais."
|
||||
},
|
||||
"translator": {
|
||||
"title": "Tradutor",
|
||||
@@ -2249,7 +2315,8 @@
|
||||
"orRemovePasswordHashField": "ou remova o campo passwordHash",
|
||||
"restartServerWithNewPassword": "Reinicie o servidor - ele usará a nova senha",
|
||||
"backToLogin": "Voltar para o Login",
|
||||
"forgotPassword": "Esqueceu a senha?"
|
||||
"forgotPassword": "Esqueceu a senha?",
|
||||
"defaultPasswordHint": "Default password: 123456 (unless INITIAL_PASSWORD was set)"
|
||||
},
|
||||
"landing": {
|
||||
"brandName": "OmniRoute",
|
||||
@@ -2447,7 +2514,16 @@
|
||||
"troubleshootingCodexFamily": "Para modelos da família GitHub Codex, mantenha o modelo como gh/codex-model; o roteador seleciona /responses automaticamente.",
|
||||
"troubleshootingTestConnection": "Use Painel > Provedores > Testar Conexão antes de testar por IDEs ou clientes externos.",
|
||||
"troubleshootingCircuitBreaker": "Se um provedor mostrar circuit breaker aberto, aguarde o cooldown ou verifique a página Health para detalhes.",
|
||||
"troubleshootingOAuth": "Para provedores OAuth, autentique novamente se os tokens expirarem. Verifique o indicador de status no card do provedor."
|
||||
"troubleshootingOAuth": "Para provedores OAuth, autentique novamente se os tokens expirarem. Verifique o indicador de status no card do provedor.",
|
||||
"managementApiReference": "Management API Reference",
|
||||
"managementApiDescription": "Automation endpoints for proxy registry, scope assignments, and legacy proxy migration.",
|
||||
"mgmtProxiesListNote": "List saved proxy registry items (supports pagination).",
|
||||
"mgmtProxiesCreateNote": "Create a reusable proxy item in the registry.",
|
||||
"mgmtProxiesHealthNote": "Get 24h/rolling health metrics per saved proxy from proxy logs.",
|
||||
"mgmtProxiesBulkAssignNote": "Assign or clear one proxy across many scope IDs in one request.",
|
||||
"mgmtAssignmentsListNote": "List proxy assignments by scope, scope_id, or proxy_id.",
|
||||
"mgmtAssignmentsUpdateNote": "Assign or clear proxy for global/provider/account/combo scope.",
|
||||
"mgmtLegacyMigrationNote": "Import legacy proxyConfig maps into registry assignments."
|
||||
},
|
||||
"legal": {
|
||||
"privacyPolicy": "Política de Privacidade",
|
||||
@@ -2536,7 +2612,21 @@
|
||||
"binaryName": "Nome do Binário",
|
||||
"versionCommand": "Comando de Versão",
|
||||
"spawnArgs": "Argumentos",
|
||||
"addAgent": "Adicionar Agente"
|
||||
"addAgent": "Adicionar Agente",
|
||||
"scanning": "Scanning system for CLI agents...",
|
||||
"opencodeIntegration": "OpenCode Integration",
|
||||
"opencodeDetected": "opencode {version} detected",
|
||||
"opencodeDesc": "Generate a ready-to-use {configFile} with your OmniRoute base URL and all available models — drop it in your project root and run {command}.",
|
||||
"downloadConfig": "Download {file}",
|
||||
"downloaded": "Downloaded!",
|
||||
"setupGuideTitle": "Setup guide",
|
||||
"openCliTools": "Open CLI Tools",
|
||||
"setupGuideDetectCliTitle": "Detect installed CLIs",
|
||||
"setupGuideDetectCliDesc": "Click Refresh after installing or updating a CLI so OmniRoute can rescan binaries and versions.",
|
||||
"setupGuideCustomAgentTitle": "Register custom binary",
|
||||
"setupGuideCustomAgentDesc": "Use Add Custom Agent when your CLI is not in the built-in list. Provide binary name and version command.",
|
||||
"setupGuideCommandMissingTitle": "Fix 'command not found'",
|
||||
"setupGuideCommandMissingDesc": "Ensure the CLI command exists in PATH, open a new terminal session, and rerun Refresh."
|
||||
},
|
||||
"autoCombo": {
|
||||
"title": "Auto-Combo Engine",
|
||||
@@ -2560,5 +2650,56 @@
|
||||
"modePackCostSaver": "Cost Saver",
|
||||
"modePackQualityFirst": "Quality First",
|
||||
"modePackOfflineFriendly": "Offline Friendly"
|
||||
},
|
||||
"search": {
|
||||
"searchQuery": "Search Query",
|
||||
"searchResults": "Search Results",
|
||||
"cachedResult": "Cached",
|
||||
"searchCost": "Cost",
|
||||
"searchTools": "Search Tools",
|
||||
"searchToolsDesc": "Advanced search testing with provider comparison",
|
||||
"compareProviders": "Compare Providers",
|
||||
"rerankResults": "Rerank Results",
|
||||
"searchHistory": "Search History",
|
||||
"urlOverlap": "URL Overlap",
|
||||
"noSearchProviders": "No search providers configured. Add providers in Settings.",
|
||||
"noRerankModels": "No rerank model available",
|
||||
"webSearch": "Web Search",
|
||||
"provider": "Provider",
|
||||
"searchType": "Search Type",
|
||||
"maxResults": "Max Results",
|
||||
"filters": "Filters",
|
||||
"country": "Country",
|
||||
"language": "Language",
|
||||
"timeRange": "Time Range",
|
||||
"includeDomains": "Include Domains",
|
||||
"excludeDomains": "Exclude Domains",
|
||||
"safeSearch": "Safe Search",
|
||||
"formatted": "Formatted",
|
||||
"rawJson": "JSON",
|
||||
"cacheMiss": "cache miss",
|
||||
"cacheHit": "cache hit",
|
||||
"latency": "Latency",
|
||||
"cost": "Cost",
|
||||
"results": "Results",
|
||||
"rerank": "Rerank",
|
||||
"rerankModel": "Rerank Model",
|
||||
"positionDelta": "Position Change",
|
||||
"emptyState": "Send a search query to see results",
|
||||
"safeSearchOff": "Off",
|
||||
"safeSearchModerate": "Moderate",
|
||||
"safeSearchStrict": "Strict",
|
||||
"queryPlaceholder": "Enter search query...",
|
||||
"providerAuto": "auto (cheapest)",
|
||||
"searchTypeWeb": "web",
|
||||
"searchTypeNews": "news",
|
||||
"optionAny": "any",
|
||||
"timeRangeDay": "Past day",
|
||||
"timeRangeWeek": "Past week",
|
||||
"timeRangeMonth": "Past month",
|
||||
"timeRangeYear": "Past year",
|
||||
"domainPlaceholder": "example.com",
|
||||
"requestTimedOut": "Request timed out ({seconds}s)",
|
||||
"networkError": "Network error"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,7 +56,9 @@
|
||||
"warning": "Aviso",
|
||||
"note": "Nota",
|
||||
"free": "Grátis",
|
||||
"skipToContent": "Pular para o conteúdo"
|
||||
"skipToContent": "Pular para o conteúdo",
|
||||
"maintenanceServerIssues": "Server is experiencing issues. Some features may be unavailable.",
|
||||
"maintenanceServerUnreachable": "Server is unreachable. Reconnecting..."
|
||||
},
|
||||
"sidebar": {
|
||||
"home": "Página inicial",
|
||||
@@ -103,7 +105,9 @@
|
||||
"themeCyan": "Ciano",
|
||||
"playground": "Playground",
|
||||
"agents": "Agentes",
|
||||
"cliToolsShort": "Ferramentas"
|
||||
"cliToolsShort": "Ferramentas",
|
||||
"autoCombo": "Auto Combo",
|
||||
"searchTools": "Search Tools"
|
||||
},
|
||||
"themesPage": {
|
||||
"title": "Themes",
|
||||
@@ -268,7 +272,28 @@
|
||||
"permissionsTitle": "Permissões: {name}",
|
||||
"allowAllDesc": "Esta chave pode acessar todos os modelos disponíveis.",
|
||||
"restrictDesc": "Esta chave pode acessar {selectedCount} de modelos {totalModels}.",
|
||||
"selectedCount": "{count} selecionado"
|
||||
"selectedCount": "{count} selecionado",
|
||||
"autoResolve": "Auto-Resolve",
|
||||
"autoResolveDesc": "Auto-resolve ambiguous model names to native provider for this API key.",
|
||||
"keyActive": "Key Active",
|
||||
"keyActiveDesc": "Enable or disable this API key. Disabled keys are immediately rejected with 403.",
|
||||
"accessSchedule": "Access Schedule",
|
||||
"accessScheduleDesc": "Restrict access to specific hours and days of the week.",
|
||||
"scheduleFrom": "From",
|
||||
"scheduleUntil": "Until",
|
||||
"scheduleDays": "Days",
|
||||
"scheduleTimezone": "Timezone",
|
||||
"scheduleTimezoneHint": "Use IANA timezone names, e.g. America/New_York, Europe/Berlin",
|
||||
"scheduleActive": "Schedule",
|
||||
"disabled": "Disabled",
|
||||
"daySun": "Sun",
|
||||
"dayMon": "Mon",
|
||||
"dayTue": "Tue",
|
||||
"dayWed": "Wed",
|
||||
"dayThu": "Thu",
|
||||
"dayFri": "Fri",
|
||||
"daySat": "Sat",
|
||||
"keyOnlyAvailableAtCreation": "Full key available only at creation time — copy it when you first create the key"
|
||||
},
|
||||
"auditLog": {
|
||||
"title": "Registro de auditoria",
|
||||
@@ -548,6 +573,27 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"mitmHowWorksDesc": "{toolName} sends requests to its provider endpoint. MITM intercepts and redirects them to OmniRoute.",
|
||||
"mitmStep1": "1. Start MITM to route requests through OmniRoute.",
|
||||
"mitmStep2Prefix": "2. Add",
|
||||
"mitmStep2Suffix": "to your hosts file as 127.0.0.1.",
|
||||
"mitmStep3": "3. Open {toolName} and requests will be proxied.",
|
||||
"whenToUseLabel": "Quando usar",
|
||||
"openToolDocs": "Abrir documentos de ferramentas",
|
||||
"toolUseCases": {
|
||||
"claude": "Use quando desejar fluxos de trabalho de planejamento robustos e refatorações longas de vários arquivos com Claude Code.",
|
||||
"codex": "Use quando sua equipe estiver padronizada em fluxos OpenAI Codex CLI e autenticação baseada em perfil.",
|
||||
"droid": "Use quando precisar de um agente de terminal leve focado em codificação rápida e loops de execução de comandos.",
|
||||
"openclaw": "Use quando desejar um agente de codificação no estilo Open Claw, mas roteado por meio de políticas OmniRoute.",
|
||||
"cline": "Use quando você configura agentes de codificação dentro de editores e deseja configuração guiada com modelos OmniRoute.",
|
||||
"kilo": "Use quando seu fluxo de trabalho depender de comandos Kilo Code e edições iterativas rápidas.",
|
||||
"cursor": "Use ao codificar no Cursor e você precisar de modelos personalizados compatíveis com OpenAI por meio do OmniRoute.",
|
||||
"continue": "Use ao executar Continue em IDEs e você precisar de uma configuração de provedor portátil baseada em JSON.",
|
||||
"opencode": "Use quando preferir execuções de agentes nativos de terminal e automação com script via OpenCode.",
|
||||
"kiro": "Use ao integrar o Kiro e controlar o roteamento do modelo centralmente no OmniRoute.",
|
||||
"antigravity": "Use quando o tráfego Antigravity/Kiro deve ser interceptado através do MITM e roteado para OmniRoute.",
|
||||
"copilot": "Use quando desejar UX no estilo de bate-papo do Copilot enquanto impõe chaves OmniRoute e regras de roteamento."
|
||||
}
|
||||
},
|
||||
"combos": {
|
||||
@@ -864,7 +910,12 @@
|
||||
"a2aQuickStartStep2": "Send JSON-RPC requests to `POST /a2a` using `message/send` or `message/stream`.",
|
||||
"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"
|
||||
"completionsLegacyDesc": "Legacy OpenAI text completions — accepts both prompt string and messages array format",
|
||||
"categorySearch": "Search & Discovery",
|
||||
"webSearch": "Web Search",
|
||||
"webSearchDesc": "Unified web search across multiple providers with automatic failover and caching",
|
||||
"searchProvider": "Search Provider",
|
||||
"searchProviderDesc": "This provider is used for web search via POST /v1/search. No model configuration needed — search providers are ready to use once an API key is connected."
|
||||
},
|
||||
"endpoints": {
|
||||
"tabProxy": "Endpoint Proxy",
|
||||
@@ -1066,7 +1117,13 @@
|
||||
"resetAllTitle": "Reinicialize todos os disjuntores para um estado saudável",
|
||||
"resetting": "Redefinindo...",
|
||||
"resetAll": "Redefinir tudo",
|
||||
"until": "Até {time}"
|
||||
"until": "Até {time}",
|
||||
"activeProviders": "{count} active",
|
||||
"monitoredProviders": "{count} monitored",
|
||||
"configuredProvidersLabel": "Configurado no painel",
|
||||
"configuredProvidersHint": "Provedores com credenciais salvas em /dashboard/providers, independentemente do estado do tempo de execução.",
|
||||
"activeProvidersHint": "Provedores configurados atualmente habilitados para solicitações de roteamento.",
|
||||
"monitoredProvidersHint": "Provedores atualmente monitorados por monitores de integridade dos disjuntores."
|
||||
},
|
||||
"limits": {
|
||||
"title": "Limites e cotas",
|
||||
@@ -1408,7 +1465,31 @@
|
||||
"chatPathHint": "Custom chat path for providers with non-standard APIs (e.g. /v4/chat/completions)",
|
||||
"modelsPathLabel": "Models Endpoint Path",
|
||||
"modelsPathPlaceholder": "/models",
|
||||
"modelsPathHint": "Custom models path for validation (e.g. /v4/models)"
|
||||
"modelsPathHint": "Custom models path for validation (e.g. /v4/models)",
|
||||
"builtInModels": "Built-in models",
|
||||
"builtInModelsHint": "Registry models for this provider. Use the pencil to set compatibility options.",
|
||||
"normalizeToolCallIdLabel": "Normalize tool call IDs to 9 characters (e.g. Mistral)",
|
||||
"preserveDeveloperRoleLabel": "Keep OpenAI Responses developer role (do not map to system)",
|
||||
"compatAdjustmentsTitle": "Compatibility",
|
||||
"compatButtonLabel": "Compatibility",
|
||||
"compatToolIdShort": "Tool ID 9",
|
||||
"compatDeveloperShort": "Developer role",
|
||||
"compatDoNotPreserveDeveloper": "Do not preserve developer role",
|
||||
"compatBadgeNoPreserve": "No preserve",
|
||||
"compatProtocolLabel": "Client request protocol",
|
||||
"compatProtocolHint": "These options apply when OmniRoute detects this request shape (OpenAI Chat, Responses API, or Anthropic Messages).",
|
||||
"compatProtocolOpenAI": "OpenAI Chat Completions",
|
||||
"compatProtocolOpenAIResponses": "OpenAI Responses API",
|
||||
"compatProtocolClaude": "Anthropic Messages",
|
||||
"tokenRefreshed": "Token refreshed successfully",
|
||||
"tokenRefreshFailed": "Token refresh failed",
|
||||
"compatBadgeUpstreamHeaders": "Headers",
|
||||
"compatUpstreamAddRow": "Add header",
|
||||
"compatUpstreamHeaderName": "Header name",
|
||||
"compatUpstreamHeaderValue": "Value",
|
||||
"compatUpstreamHeadersHint": "High-privilege setting — same trust level as editing provider API credentials; only trusted admins should use it. Merged after OmniRoute adds auth from the provider API key. If a custom header uses the same name as an existing one (e.g. Authorization), your value fully replaces the auto-generated header (including the Bearer token) — the upstream only sees what you typed, not the key from settings. Misconfiguration can cause 401 or broken upstream auth. One row per header (e.g. extra Authentication for some gateways). Hover or focus the value to preview. Saves on blur, outside click, or closing this panel.",
|
||||
"compatUpstreamHeadersLabel": "Extra upstream headers",
|
||||
"compatUpstreamRemoveRow": "Remove row"
|
||||
},
|
||||
"settings": {
|
||||
"title": "Configurações",
|
||||
@@ -1811,7 +1892,13 @@
|
||||
"cliFingerprintDesc": "Match native CLI binary signatures when proxying requests. Reorders headers and body fields to look identical to the official CLI tools. Your proxy IP is preserved.",
|
||||
"cliFingerprintEnabled": "{count} provider(s) with CLI fingerprint active",
|
||||
"enableFingerprintTitle": "Enable fingerprint for {provider}",
|
||||
"disableFingerprintTitle": "Disable fingerprint for {provider}"
|
||||
"disableFingerprintTitle": "Disable fingerprint for {provider}",
|
||||
"routingAdvancedGuideTitle": "Orientação avançada de roteamento",
|
||||
"routingAdvancedGuideHint1": "Use Fill First para prioridade previsível, Round Robin para justiça e P2C para resiliência de latência.",
|
||||
"routingAdvancedGuideHint2": "Se os fornecedores variarem em qualidade/custo, comece com Opção de custo para trabalho em segundo plano e Menos usado para desgaste equilibrado.",
|
||||
"comboDefaultsGuideTitle": "Como ajustar os padrões de combinação",
|
||||
"comboDefaultsGuideHint1": "Mantenha as tentativas baixas em fluxos de baixa latência; aumente o tempo limite apenas para tarefas de geração longa.",
|
||||
"comboDefaultsGuideHint2": "Use substituições de provedor quando um provedor precisar de um comportamento de tempo limite/nova tentativa diferente dos padrões globais."
|
||||
},
|
||||
"translator": {
|
||||
"title": "Tradutor",
|
||||
@@ -2228,7 +2315,8 @@
|
||||
"orRemovePasswordHashField": "ou remova o campo passwordHash",
|
||||
"restartServerWithNewPassword": "Reinicie o servidor - ele usará a nova senha",
|
||||
"backToLogin": "Voltar ao login",
|
||||
"forgotPassword": "Esqueceu a senha?"
|
||||
"forgotPassword": "Esqueceu a senha?",
|
||||
"defaultPasswordHint": "Default password: 123456 (unless INITIAL_PASSWORD was set)"
|
||||
},
|
||||
"landing": {
|
||||
"brandName": "OmniRoute",
|
||||
@@ -2426,7 +2514,16 @@
|
||||
"troubleshootingCodexFamily": "Para modelos da família GitHub Codex, mantenha o modelo como gh/codex-model; roteador seleciona /respostas automaticamente.",
|
||||
"troubleshootingTestConnection": "Use Painel > Provedores > Testar conexão antes de testar em IDEs ou clientes externos.",
|
||||
"troubleshootingCircuitBreaker": "Se um provedor mostrar o disjuntor aberto, aguarde o resfriamento ou verifique a página de integridade para obter detalhes.",
|
||||
"troubleshootingOAuth": "Para provedores OAuth, autentique novamente se os tokens expirarem. Verifique o indicador de status do cartão do provedor."
|
||||
"troubleshootingOAuth": "Para provedores OAuth, autentique novamente se os tokens expirarem. Verifique o indicador de status do cartão do provedor.",
|
||||
"managementApiReference": "Management API Reference",
|
||||
"managementApiDescription": "Automation endpoints for proxy registry, scope assignments, and legacy proxy migration.",
|
||||
"mgmtProxiesListNote": "List saved proxy registry items (supports pagination).",
|
||||
"mgmtProxiesCreateNote": "Create a reusable proxy item in the registry.",
|
||||
"mgmtProxiesHealthNote": "Get 24h/rolling health metrics per saved proxy from proxy logs.",
|
||||
"mgmtProxiesBulkAssignNote": "Assign or clear one proxy across many scope IDs in one request.",
|
||||
"mgmtAssignmentsListNote": "List proxy assignments by scope, scope_id, or proxy_id.",
|
||||
"mgmtAssignmentsUpdateNote": "Assign or clear proxy for global/provider/account/combo scope.",
|
||||
"mgmtLegacyMigrationNote": "Import legacy proxyConfig maps into registry assignments."
|
||||
},
|
||||
"legal": {
|
||||
"privacyPolicy": "Política de Privacidade",
|
||||
@@ -2503,7 +2600,21 @@
|
||||
"binaryName": "Binary Name",
|
||||
"versionCommand": "Version Command",
|
||||
"spawnArgs": "Spawn Args",
|
||||
"addAgent": "Add Agent"
|
||||
"addAgent": "Add Agent",
|
||||
"scanning": "Scanning system for CLI agents...",
|
||||
"opencodeIntegration": "OpenCode Integration",
|
||||
"opencodeDetected": "opencode {version} detected",
|
||||
"opencodeDesc": "Generate a ready-to-use {configFile} with your OmniRoute base URL and all available models — drop it in your project root and run {command}.",
|
||||
"downloadConfig": "Download {file}",
|
||||
"downloaded": "Downloaded!",
|
||||
"setupGuideTitle": "Setup guide",
|
||||
"openCliTools": "Open CLI Tools",
|
||||
"setupGuideDetectCliTitle": "Detect installed CLIs",
|
||||
"setupGuideDetectCliDesc": "Click Refresh after installing or updating a CLI so OmniRoute can rescan binaries and versions.",
|
||||
"setupGuideCustomAgentTitle": "Register custom binary",
|
||||
"setupGuideCustomAgentDesc": "Use Add Custom Agent when your CLI is not in the built-in list. Provide binary name and version command.",
|
||||
"setupGuideCommandMissingTitle": "Fix 'command not found'",
|
||||
"setupGuideCommandMissingDesc": "Ensure the CLI command exists in PATH, open a new terminal session, and rerun Refresh."
|
||||
},
|
||||
"autoCombo": {
|
||||
"title": "Auto-Combo Engine",
|
||||
@@ -2527,5 +2638,56 @@
|
||||
"modePackCostSaver": "Cost Saver",
|
||||
"modePackQualityFirst": "Quality First",
|
||||
"modePackOfflineFriendly": "Offline Friendly"
|
||||
},
|
||||
"search": {
|
||||
"searchQuery": "Search Query",
|
||||
"searchResults": "Search Results",
|
||||
"cachedResult": "Cached",
|
||||
"searchCost": "Cost",
|
||||
"searchTools": "Search Tools",
|
||||
"searchToolsDesc": "Advanced search testing with provider comparison",
|
||||
"compareProviders": "Compare Providers",
|
||||
"rerankResults": "Rerank Results",
|
||||
"searchHistory": "Search History",
|
||||
"urlOverlap": "URL Overlap",
|
||||
"noSearchProviders": "No search providers configured. Add providers in Settings.",
|
||||
"noRerankModels": "No rerank model available",
|
||||
"webSearch": "Web Search",
|
||||
"provider": "Provider",
|
||||
"searchType": "Search Type",
|
||||
"maxResults": "Max Results",
|
||||
"filters": "Filters",
|
||||
"country": "Country",
|
||||
"language": "Language",
|
||||
"timeRange": "Time Range",
|
||||
"includeDomains": "Include Domains",
|
||||
"excludeDomains": "Exclude Domains",
|
||||
"safeSearch": "Safe Search",
|
||||
"formatted": "Formatted",
|
||||
"rawJson": "JSON",
|
||||
"cacheMiss": "cache miss",
|
||||
"cacheHit": "cache hit",
|
||||
"latency": "Latency",
|
||||
"cost": "Cost",
|
||||
"results": "Results",
|
||||
"rerank": "Rerank",
|
||||
"rerankModel": "Rerank Model",
|
||||
"positionDelta": "Position Change",
|
||||
"emptyState": "Send a search query to see results",
|
||||
"safeSearchOff": "Off",
|
||||
"safeSearchModerate": "Moderate",
|
||||
"safeSearchStrict": "Strict",
|
||||
"queryPlaceholder": "Enter search query...",
|
||||
"providerAuto": "auto (cheapest)",
|
||||
"searchTypeWeb": "web",
|
||||
"searchTypeNews": "news",
|
||||
"optionAny": "any",
|
||||
"timeRangeDay": "Past day",
|
||||
"timeRangeWeek": "Past week",
|
||||
"timeRangeMonth": "Past month",
|
||||
"timeRangeYear": "Past year",
|
||||
"domainPlaceholder": "example.com",
|
||||
"requestTimedOut": "Request timed out ({seconds}s)",
|
||||
"networkError": "Network error"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,7 +56,9 @@
|
||||
"warning": "Avertisment",
|
||||
"note": "Notă",
|
||||
"free": "Gratuit",
|
||||
"skipToContent": "Treci la conținut"
|
||||
"skipToContent": "Treci la conținut",
|
||||
"maintenanceServerIssues": "Server is experiencing issues. Some features may be unavailable.",
|
||||
"maintenanceServerUnreachable": "Server is unreachable. Reconnecting..."
|
||||
},
|
||||
"sidebar": {
|
||||
"home": "Acasă",
|
||||
@@ -103,7 +105,9 @@
|
||||
"endpoints": "Puncte finale",
|
||||
"playground": "Playground",
|
||||
"agents": "Agenți",
|
||||
"cliToolsShort": "Instrumente"
|
||||
"cliToolsShort": "Instrumente",
|
||||
"autoCombo": "Auto Combo",
|
||||
"searchTools": "Search Tools"
|
||||
},
|
||||
"themesPage": {
|
||||
"title": "Themes",
|
||||
@@ -268,7 +272,28 @@
|
||||
"permissionsTitle": "Permisiuni: {name}",
|
||||
"allowAllDesc": "Această cheie poate accesa toate modelele disponibile.",
|
||||
"restrictDesc": "Această cheie poate accesa {selectedCount} din {totalModels} modele.",
|
||||
"selectedCount": "{count} selectat"
|
||||
"selectedCount": "{count} selectat",
|
||||
"autoResolve": "Auto-Resolve",
|
||||
"autoResolveDesc": "Auto-resolve ambiguous model names to native provider for this API key.",
|
||||
"keyActive": "Key Active",
|
||||
"keyActiveDesc": "Enable or disable this API key. Disabled keys are immediately rejected with 403.",
|
||||
"accessSchedule": "Access Schedule",
|
||||
"accessScheduleDesc": "Restrict access to specific hours and days of the week.",
|
||||
"scheduleFrom": "From",
|
||||
"scheduleUntil": "Until",
|
||||
"scheduleDays": "Days",
|
||||
"scheduleTimezone": "Timezone",
|
||||
"scheduleTimezoneHint": "Use IANA timezone names, e.g. America/New_York, Europe/Berlin",
|
||||
"scheduleActive": "Schedule",
|
||||
"disabled": "Disabled",
|
||||
"daySun": "Sun",
|
||||
"dayMon": "Mon",
|
||||
"dayTue": "Tue",
|
||||
"dayWed": "Wed",
|
||||
"dayThu": "Thu",
|
||||
"dayFri": "Fri",
|
||||
"daySat": "Sat",
|
||||
"keyOnlyAvailableAtCreation": "Full key available only at creation time — copy it when you first create the key"
|
||||
},
|
||||
"auditLog": {
|
||||
"title": "Jurnal de audit",
|
||||
@@ -548,6 +573,27 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"mitmHowWorksDesc": "{toolName} sends requests to its provider endpoint. MITM intercepts and redirects them to OmniRoute.",
|
||||
"mitmStep1": "1. Start MITM to route requests through OmniRoute.",
|
||||
"mitmStep2Prefix": "2. Add",
|
||||
"mitmStep2Suffix": "to your hosts file as 127.0.0.1.",
|
||||
"mitmStep3": "3. Open {toolName} and requests will be proxied.",
|
||||
"whenToUseLabel": "Când să utilizați",
|
||||
"openToolDocs": "Deschideți documentele instrumentului",
|
||||
"toolUseCases": {
|
||||
"claude": "Utilizați atunci când doriți fluxuri de lucru puternice de planificare și refactorări lungi cu mai multe fișiere cu Claude Code.",
|
||||
"codex": "Utilizați atunci când echipa dvs. este standardizată pe fluxurile OpenAI Codex CLI și pe autentificarea bazată pe profil.",
|
||||
"droid": "Utilizați atunci când aveți nevoie de un agent terminal ușor, concentrat pe codificarea rapidă și buclele de execuție a comenzilor.",
|
||||
"openclaw": "Utilizați atunci când doriți un agent de codare în stil Open Claw, dar direcționat prin politicile OmniRoute.",
|
||||
"cline": "Utilizați atunci când configurați agenți de codare în cadrul editorilor și doriți o configurare ghidată cu modelele OmniRoute.",
|
||||
"kilo": "Utilizați atunci când fluxul dvs. de lucru depinde de comenzile Kilo Code și de editările iterative rapide.",
|
||||
"cursor": "Utilizați când codați în Cursor și aveți nevoie de modele personalizate compatibile cu OpenAI prin OmniRoute.",
|
||||
"continue": "Utilizați atunci când rulați Continue în IDE-uri și aveți nevoie de o configurație portabilă a furnizorului bazată pe JSON.",
|
||||
"opencode": "Utilizați atunci când preferați rulările de agent nativ terminal și automatizarea prin script prin OpenCode.",
|
||||
"kiro": "Utilizați atunci când integrați Kiro și controlați rutarea modelului central din OmniRoute.",
|
||||
"antigravity": "Utilizați atunci când traficul Antigravity/Kiro trebuie interceptat prin MITM și direcționat către OmniRoute.",
|
||||
"copilot": "Utilizați atunci când doriți UX în stilul de chat Copilot, în timp ce aplicați cheile și regulile de rutare OmniRoute."
|
||||
}
|
||||
},
|
||||
"combos": {
|
||||
@@ -864,7 +910,12 @@
|
||||
"a2aQuickStartStep2": "Send JSON-RPC requests to `POST /a2a` using `message/send` or `message/stream`.",
|
||||
"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"
|
||||
"completionsLegacyDesc": "Legacy OpenAI text completions — accepts both prompt string and messages array format",
|
||||
"categorySearch": "Search & Discovery",
|
||||
"webSearch": "Web Search",
|
||||
"webSearchDesc": "Unified web search across multiple providers with automatic failover and caching",
|
||||
"searchProvider": "Search Provider",
|
||||
"searchProviderDesc": "This provider is used for web search via POST /v1/search. No model configuration needed — search providers are ready to use once an API key is connected."
|
||||
},
|
||||
"mcpDashboard": {
|
||||
"loading": "Loading MCP dashboard...",
|
||||
@@ -1054,7 +1105,13 @@
|
||||
"resetAllTitle": "Resetați toate întreruptoarele la starea sănătoasă",
|
||||
"resetting": "Se resetează...",
|
||||
"resetAll": "Resetați toate",
|
||||
"until": "Până la {time}"
|
||||
"until": "Până la {time}",
|
||||
"activeProviders": "{count} active",
|
||||
"monitoredProviders": "{count} monitored",
|
||||
"configuredProvidersLabel": "Configurat în tabloul de bord",
|
||||
"configuredProvidersHint": "Furnizorii cu acreditări salvate în /dashboard/providers, indiferent de starea de rulare.",
|
||||
"activeProvidersHint": "Furnizorii configurați activați în prezent pentru solicitările de rutare.",
|
||||
"monitoredProvidersHint": "Furnizorii sunt urmăriți în prezent de monitoarele de sănătate ale întreruptoarelor."
|
||||
},
|
||||
"limits": {
|
||||
"title": "Limite și cote",
|
||||
@@ -1396,7 +1453,31 @@
|
||||
"chatPathHint": "Custom chat path for providers with non-standard APIs (e.g. /v4/chat/completions)",
|
||||
"modelsPathLabel": "Models Endpoint Path",
|
||||
"modelsPathPlaceholder": "/models",
|
||||
"modelsPathHint": "Custom models path for validation (e.g. /v4/models)"
|
||||
"modelsPathHint": "Custom models path for validation (e.g. /v4/models)",
|
||||
"builtInModels": "Built-in models",
|
||||
"builtInModelsHint": "Registry models for this provider. Use the pencil to set compatibility options.",
|
||||
"normalizeToolCallIdLabel": "Normalize tool call IDs to 9 characters (e.g. Mistral)",
|
||||
"preserveDeveloperRoleLabel": "Keep OpenAI Responses developer role (do not map to system)",
|
||||
"compatAdjustmentsTitle": "Compatibility",
|
||||
"compatButtonLabel": "Compatibility",
|
||||
"compatToolIdShort": "Tool ID 9",
|
||||
"compatDeveloperShort": "Developer role",
|
||||
"compatDoNotPreserveDeveloper": "Do not preserve developer role",
|
||||
"compatBadgeNoPreserve": "No preserve",
|
||||
"compatProtocolLabel": "Client request protocol",
|
||||
"compatProtocolHint": "These options apply when OmniRoute detects this request shape (OpenAI Chat, Responses API, or Anthropic Messages).",
|
||||
"compatProtocolOpenAI": "OpenAI Chat Completions",
|
||||
"compatProtocolOpenAIResponses": "OpenAI Responses API",
|
||||
"compatProtocolClaude": "Anthropic Messages",
|
||||
"tokenRefreshed": "Token refreshed successfully",
|
||||
"tokenRefreshFailed": "Token refresh failed",
|
||||
"compatBadgeUpstreamHeaders": "Headers",
|
||||
"compatUpstreamAddRow": "Add header",
|
||||
"compatUpstreamHeaderName": "Header name",
|
||||
"compatUpstreamHeaderValue": "Value",
|
||||
"compatUpstreamHeadersHint": "High-privilege setting — same trust level as editing provider API credentials; only trusted admins should use it. Merged after OmniRoute adds auth from the provider API key. If a custom header uses the same name as an existing one (e.g. Authorization), your value fully replaces the auto-generated header (including the Bearer token) — the upstream only sees what you typed, not the key from settings. Misconfiguration can cause 401 or broken upstream auth. One row per header (e.g. extra Authentication for some gateways). Hover or focus the value to preview. Saves on blur, outside click, or closing this panel.",
|
||||
"compatUpstreamHeadersLabel": "Extra upstream headers",
|
||||
"compatUpstreamRemoveRow": "Remove row"
|
||||
},
|
||||
"settings": {
|
||||
"title": "Setări",
|
||||
@@ -1799,7 +1880,13 @@
|
||||
"cliFingerprintDesc": "Match native CLI binary signatures when proxying requests. Reorders headers and body fields to look identical to the official CLI tools. Your proxy IP is preserved.",
|
||||
"cliFingerprintEnabled": "{count} provider(s) with CLI fingerprint active",
|
||||
"enableFingerprintTitle": "Enable fingerprint for {provider}",
|
||||
"disableFingerprintTitle": "Disable fingerprint for {provider}"
|
||||
"disableFingerprintTitle": "Disable fingerprint for {provider}",
|
||||
"routingAdvancedGuideTitle": "Ghid avansat de rutare",
|
||||
"routingAdvancedGuideHint1": "Folosiți Fill First pentru o prioritate previzibilă, Round Robin pentru corectitudine și P2C pentru rezistență la latență.",
|
||||
"routingAdvancedGuideHint2": "Dacă furnizorii variază în ceea ce privește calitatea/costul, începeți cu Cost Opt pentru munca de fundal și Least Used pentru uzura echilibrată.",
|
||||
"comboDefaultsGuideTitle": "Cum să reglați setările implicite de combo",
|
||||
"comboDefaultsGuideHint1": "Menține reîncercările scăzute în fluxurile cu latență scăzută; crește timpul de expirare numai pentru sarcini de generație lungă.",
|
||||
"comboDefaultsGuideHint2": "Folosiți suprascrierile furnizorului atunci când un furnizor are nevoie de un comportament de timeout/reîncercare diferit față de valorile prestabilite globale."
|
||||
},
|
||||
"translator": {
|
||||
"title": "Traducător",
|
||||
@@ -2216,7 +2303,8 @@
|
||||
"orRemovePasswordHashField": "sau eliminați câmpul passwordHash",
|
||||
"restartServerWithNewPassword": "Reporniți serverul - va folosi noua parolă",
|
||||
"backToLogin": "Înapoi la Logare",
|
||||
"forgotPassword": "Ai uitat parola?"
|
||||
"forgotPassword": "Ai uitat parola?",
|
||||
"defaultPasswordHint": "Default password: 123456 (unless INITIAL_PASSWORD was set)"
|
||||
},
|
||||
"landing": {
|
||||
"brandName": "OmniRoute",
|
||||
@@ -2414,7 +2502,16 @@
|
||||
"troubleshootingCodexFamily": "Pentru modelele din familia GitHub Codex, păstrați modelul ca gh/codex-model; routerul selectează/răspunde automat.",
|
||||
"troubleshootingTestConnection": "Utilizați Tabloul de bord > Furnizori > Testați conexiunea înainte de a testa de la IDE-uri sau clienți externi.",
|
||||
"troubleshootingCircuitBreaker": "Dacă un furnizor arată întrerupătorul deschis, așteptați răcirea sau verificați pagina Sănătate pentru detalii.",
|
||||
"troubleshootingOAuth": "Pentru furnizorii OAuth, re-autentificați-vă dacă tokenurile expiră. Verificați indicatorul de stare a cardului furnizorului."
|
||||
"troubleshootingOAuth": "Pentru furnizorii OAuth, re-autentificați-vă dacă tokenurile expiră. Verificați indicatorul de stare a cardului furnizorului.",
|
||||
"managementApiReference": "Management API Reference",
|
||||
"managementApiDescription": "Automation endpoints for proxy registry, scope assignments, and legacy proxy migration.",
|
||||
"mgmtProxiesListNote": "List saved proxy registry items (supports pagination).",
|
||||
"mgmtProxiesCreateNote": "Create a reusable proxy item in the registry.",
|
||||
"mgmtProxiesHealthNote": "Get 24h/rolling health metrics per saved proxy from proxy logs.",
|
||||
"mgmtProxiesBulkAssignNote": "Assign or clear one proxy across many scope IDs in one request.",
|
||||
"mgmtAssignmentsListNote": "List proxy assignments by scope, scope_id, or proxy_id.",
|
||||
"mgmtAssignmentsUpdateNote": "Assign or clear proxy for global/provider/account/combo scope.",
|
||||
"mgmtLegacyMigrationNote": "Import legacy proxyConfig maps into registry assignments."
|
||||
},
|
||||
"legal": {
|
||||
"privacyPolicy": "Politica de confidențialitate",
|
||||
@@ -2503,7 +2600,21 @@
|
||||
"binaryName": "Binary Name",
|
||||
"versionCommand": "Version Command",
|
||||
"spawnArgs": "Spawn Args",
|
||||
"addAgent": "Add Agent"
|
||||
"addAgent": "Add Agent",
|
||||
"scanning": "Scanning system for CLI agents...",
|
||||
"opencodeIntegration": "OpenCode Integration",
|
||||
"opencodeDetected": "opencode {version} detected",
|
||||
"opencodeDesc": "Generate a ready-to-use {configFile} with your OmniRoute base URL and all available models — drop it in your project root and run {command}.",
|
||||
"downloadConfig": "Download {file}",
|
||||
"downloaded": "Downloaded!",
|
||||
"setupGuideTitle": "Setup guide",
|
||||
"openCliTools": "Open CLI Tools",
|
||||
"setupGuideDetectCliTitle": "Detect installed CLIs",
|
||||
"setupGuideDetectCliDesc": "Click Refresh after installing or updating a CLI so OmniRoute can rescan binaries and versions.",
|
||||
"setupGuideCustomAgentTitle": "Register custom binary",
|
||||
"setupGuideCustomAgentDesc": "Use Add Custom Agent when your CLI is not in the built-in list. Provide binary name and version command.",
|
||||
"setupGuideCommandMissingTitle": "Fix 'command not found'",
|
||||
"setupGuideCommandMissingDesc": "Ensure the CLI command exists in PATH, open a new terminal session, and rerun Refresh."
|
||||
},
|
||||
"autoCombo": {
|
||||
"title": "Auto-Combo Engine",
|
||||
@@ -2527,5 +2638,56 @@
|
||||
"modePackCostSaver": "Cost Saver",
|
||||
"modePackQualityFirst": "Quality First",
|
||||
"modePackOfflineFriendly": "Offline Friendly"
|
||||
},
|
||||
"search": {
|
||||
"searchQuery": "Search Query",
|
||||
"searchResults": "Search Results",
|
||||
"cachedResult": "Cached",
|
||||
"searchCost": "Cost",
|
||||
"searchTools": "Search Tools",
|
||||
"searchToolsDesc": "Advanced search testing with provider comparison",
|
||||
"compareProviders": "Compare Providers",
|
||||
"rerankResults": "Rerank Results",
|
||||
"searchHistory": "Search History",
|
||||
"urlOverlap": "URL Overlap",
|
||||
"noSearchProviders": "No search providers configured. Add providers in Settings.",
|
||||
"noRerankModels": "No rerank model available",
|
||||
"webSearch": "Web Search",
|
||||
"provider": "Provider",
|
||||
"searchType": "Search Type",
|
||||
"maxResults": "Max Results",
|
||||
"filters": "Filters",
|
||||
"country": "Country",
|
||||
"language": "Language",
|
||||
"timeRange": "Time Range",
|
||||
"includeDomains": "Include Domains",
|
||||
"excludeDomains": "Exclude Domains",
|
||||
"safeSearch": "Safe Search",
|
||||
"formatted": "Formatted",
|
||||
"rawJson": "JSON",
|
||||
"cacheMiss": "cache miss",
|
||||
"cacheHit": "cache hit",
|
||||
"latency": "Latency",
|
||||
"cost": "Cost",
|
||||
"results": "Results",
|
||||
"rerank": "Rerank",
|
||||
"rerankModel": "Rerank Model",
|
||||
"positionDelta": "Position Change",
|
||||
"emptyState": "Send a search query to see results",
|
||||
"safeSearchOff": "Off",
|
||||
"safeSearchModerate": "Moderate",
|
||||
"safeSearchStrict": "Strict",
|
||||
"queryPlaceholder": "Enter search query...",
|
||||
"providerAuto": "auto (cheapest)",
|
||||
"searchTypeWeb": "web",
|
||||
"searchTypeNews": "news",
|
||||
"optionAny": "any",
|
||||
"timeRangeDay": "Past day",
|
||||
"timeRangeWeek": "Past week",
|
||||
"timeRangeMonth": "Past month",
|
||||
"timeRangeYear": "Past year",
|
||||
"domainPlaceholder": "example.com",
|
||||
"requestTimedOut": "Request timed out ({seconds}s)",
|
||||
"networkError": "Network error"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,7 +56,9 @@
|
||||
"warning": "Предупреждение",
|
||||
"note": "Примечание",
|
||||
"free": "Бесплатно",
|
||||
"skipToContent": "Перейти к содержимому"
|
||||
"skipToContent": "Перейти к содержимому",
|
||||
"maintenanceServerIssues": "Server is experiencing issues. Some features may be unavailable.",
|
||||
"maintenanceServerUnreachable": "Server is unreachable. Reconnecting..."
|
||||
},
|
||||
"sidebar": {
|
||||
"home": "Главная",
|
||||
@@ -103,7 +105,9 @@
|
||||
"endpoints": "Конечные точки",
|
||||
"playground": "Площадка",
|
||||
"agents": "Агенты",
|
||||
"cliToolsShort": "Инструменты"
|
||||
"cliToolsShort": "Инструменты",
|
||||
"autoCombo": "Auto Combo",
|
||||
"searchTools": "Search Tools"
|
||||
},
|
||||
"themesPage": {
|
||||
"title": "Темы",
|
||||
@@ -268,7 +272,28 @@
|
||||
"permissionsTitle": "Разрешения: {name}",
|
||||
"allowAllDesc": "Этот ключ дает доступ ко всем доступным моделям.",
|
||||
"restrictDesc": "Этот ключ может получить доступ к {selectedCount} из моделей {totalModels}.",
|
||||
"selectedCount": "{count} выбрано"
|
||||
"selectedCount": "{count} выбрано",
|
||||
"autoResolve": "Auto-Resolve",
|
||||
"autoResolveDesc": "Auto-resolve ambiguous model names to native provider for this API key.",
|
||||
"keyActive": "Key Active",
|
||||
"keyActiveDesc": "Enable or disable this API key. Disabled keys are immediately rejected with 403.",
|
||||
"accessSchedule": "Access Schedule",
|
||||
"accessScheduleDesc": "Restrict access to specific hours and days of the week.",
|
||||
"scheduleFrom": "From",
|
||||
"scheduleUntil": "Until",
|
||||
"scheduleDays": "Days",
|
||||
"scheduleTimezone": "Timezone",
|
||||
"scheduleTimezoneHint": "Use IANA timezone names, e.g. America/New_York, Europe/Berlin",
|
||||
"scheduleActive": "Schedule",
|
||||
"disabled": "Disabled",
|
||||
"daySun": "Sun",
|
||||
"dayMon": "Mon",
|
||||
"dayTue": "Tue",
|
||||
"dayWed": "Wed",
|
||||
"dayThu": "Thu",
|
||||
"dayFri": "Fri",
|
||||
"daySat": "Sat",
|
||||
"keyOnlyAvailableAtCreation": "Full key available only at creation time — copy it when you first create the key"
|
||||
},
|
||||
"auditLog": {
|
||||
"title": "Журнал аудита",
|
||||
@@ -548,6 +573,27 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"mitmHowWorksDesc": "{toolName} sends requests to its provider endpoint. MITM intercepts and redirects them to OmniRoute.",
|
||||
"mitmStep1": "1. Start MITM to route requests through OmniRoute.",
|
||||
"mitmStep2Prefix": "2. Add",
|
||||
"mitmStep2Suffix": "to your hosts file as 127.0.0.1.",
|
||||
"mitmStep3": "3. Open {toolName} and requests will be proxied.",
|
||||
"whenToUseLabel": "Когда использовать",
|
||||
"openToolDocs": "Открыть документацию по инструменту",
|
||||
"toolUseCases": {
|
||||
"claude": "Используйте, если вам нужны надежные рабочие процессы планирования и длительные рефакторинги нескольких файлов с помощью Claude Code.",
|
||||
"codex": "Используйте, когда ваша команда стандартизирует потоки интерфейса командной строки OpenAI Codex и аутентификацию на основе профилей.",
|
||||
"droid": "Используйте, когда вам нужен легкий агент терминала, ориентированный на быстрое кодирование и циклы выполнения команд.",
|
||||
"openclaw": "Используйте, если вам нужен агент кодирования в стиле Open Claw, но маршрутизируемый через политики OmniRoute.",
|
||||
"cline": "Используйте, если вы настраиваете агенты кодирования внутри редакторов и хотите выполнить управляемую настройку с помощью моделей OmniRoute.",
|
||||
"kilo": "Используйте, когда ваш рабочий процесс зависит от команд Kilo Code и быстрого итеративного редактирования.",
|
||||
"cursor": "Используйте при кодировании в Cursor, если вам нужны пользовательские модели, совместимые с OpenAI, через OmniRoute.",
|
||||
"continue": "Используйте при запуске Продолжить в IDE, если вам нужна переносимая конфигурация поставщика на основе JSON.",
|
||||
"opencode": "Используйте, если вы предпочитаете запускать собственные агенты терминала и автоматизировать скрипты через OpenCode.",
|
||||
"kiro": "Используйте при интеграции Kiro и централизованном управлении маршрутизацией модели из OmniRoute.",
|
||||
"antigravity": "Используйте, когда трафик Антигравитации/Киро необходимо перехватить через MITM и направить в OmniRoute.",
|
||||
"copilot": "Используйте его, если вам нужен пользовательский интерфейс в стиле чата Copilot, одновременно обеспечивая соблюдение ключей OmniRoute и правил маршрутизации."
|
||||
}
|
||||
},
|
||||
"combos": {
|
||||
@@ -864,7 +910,12 @@
|
||||
"a2aQuickStartStep2": "Send JSON-RPC requests to `POST /a2a` using `message/send` or `message/stream`.",
|
||||
"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"
|
||||
"completionsLegacyDesc": "Legacy OpenAI text completions — accepts both prompt string and messages array format",
|
||||
"categorySearch": "Search & Discovery",
|
||||
"webSearch": "Web Search",
|
||||
"webSearchDesc": "Unified web search across multiple providers with automatic failover and caching",
|
||||
"searchProvider": "Search Provider",
|
||||
"searchProviderDesc": "This provider is used for web search via POST /v1/search. No model configuration needed — search providers are ready to use once an API key is connected."
|
||||
},
|
||||
"mcpDashboard": {
|
||||
"loading": "Loading MCP dashboard...",
|
||||
@@ -1054,7 +1105,13 @@
|
||||
"resetAllTitle": "Верните все автоматические выключатели в исправное состояние.",
|
||||
"resetting": "Сброс...",
|
||||
"resetAll": "Сбросить все",
|
||||
"until": "До {time}"
|
||||
"until": "До {time}",
|
||||
"activeProviders": "{count} active",
|
||||
"monitoredProviders": "{count} monitored",
|
||||
"configuredProvidersLabel": "Настраивается в приборной панели",
|
||||
"configuredProvidersHint": "Поставщики, учетные данные которых сохранены в /dashboard/providers, независимо от состояния времени выполнения.",
|
||||
"activeProvidersHint": "Настроенные поставщики в настоящее время включены для маршрутизации запросов.",
|
||||
"monitoredProvidersHint": "Поставщики услуг в настоящее время отслеживаются с помощью мониторов состояния автоматических выключателей."
|
||||
},
|
||||
"limits": {
|
||||
"title": "Лимиты и квоты",
|
||||
@@ -1396,7 +1453,31 @@
|
||||
"chatPathHint": "Custom chat path for providers with non-standard APIs (e.g. /v4/chat/completions)",
|
||||
"modelsPathLabel": "Models Endpoint Path",
|
||||
"modelsPathPlaceholder": "/models",
|
||||
"modelsPathHint": "Custom models path for validation (e.g. /v4/models)"
|
||||
"modelsPathHint": "Custom models path for validation (e.g. /v4/models)",
|
||||
"builtInModels": "Built-in models",
|
||||
"builtInModelsHint": "Registry models for this provider. Use the pencil to set compatibility options.",
|
||||
"normalizeToolCallIdLabel": "Normalize tool call IDs to 9 characters (e.g. Mistral)",
|
||||
"preserveDeveloperRoleLabel": "Keep OpenAI Responses developer role (do not map to system)",
|
||||
"compatAdjustmentsTitle": "Compatibility",
|
||||
"compatButtonLabel": "Compatibility",
|
||||
"compatToolIdShort": "Tool ID 9",
|
||||
"compatDeveloperShort": "Developer role",
|
||||
"compatDoNotPreserveDeveloper": "Do not preserve developer role",
|
||||
"compatBadgeNoPreserve": "No preserve",
|
||||
"compatProtocolLabel": "Client request protocol",
|
||||
"compatProtocolHint": "These options apply when OmniRoute detects this request shape (OpenAI Chat, Responses API, or Anthropic Messages).",
|
||||
"compatProtocolOpenAI": "OpenAI Chat Completions",
|
||||
"compatProtocolOpenAIResponses": "OpenAI Responses API",
|
||||
"compatProtocolClaude": "Anthropic Messages",
|
||||
"tokenRefreshed": "Token refreshed successfully",
|
||||
"tokenRefreshFailed": "Token refresh failed",
|
||||
"compatBadgeUpstreamHeaders": "Headers",
|
||||
"compatUpstreamAddRow": "Add header",
|
||||
"compatUpstreamHeaderName": "Header name",
|
||||
"compatUpstreamHeaderValue": "Value",
|
||||
"compatUpstreamHeadersHint": "High-privilege setting — same trust level as editing provider API credentials; only trusted admins should use it. Merged after OmniRoute adds auth from the provider API key. If a custom header uses the same name as an existing one (e.g. Authorization), your value fully replaces the auto-generated header (including the Bearer token) — the upstream only sees what you typed, not the key from settings. Misconfiguration can cause 401 or broken upstream auth. One row per header (e.g. extra Authentication for some gateways). Hover or focus the value to preview. Saves on blur, outside click, or closing this panel.",
|
||||
"compatUpstreamHeadersLabel": "Extra upstream headers",
|
||||
"compatUpstreamRemoveRow": "Remove row"
|
||||
},
|
||||
"settings": {
|
||||
"title": "Настройки",
|
||||
@@ -1799,7 +1880,13 @@
|
||||
"cliFingerprintDesc": "Match native CLI binary signatures when proxying requests. Reorders headers and body fields to look identical to the official CLI tools. Your proxy IP is preserved.",
|
||||
"cliFingerprintEnabled": "{count} provider(s) with CLI fingerprint active",
|
||||
"enableFingerprintTitle": "Enable fingerprint for {provider}",
|
||||
"disableFingerprintTitle": "Disable fingerprint for {provider}"
|
||||
"disableFingerprintTitle": "Disable fingerprint for {provider}",
|
||||
"routingAdvancedGuideTitle": "Расширенное руководство по маршрутизации",
|
||||
"routingAdvancedGuideHint1": "Используйте «Fill First» для предсказуемого приоритета, Round Robin для обеспечения справедливости и P2C для устойчивости к задержкам.",
|
||||
"routingAdvancedGuideHint2": "Если поставщики различаются по качеству/стоимости, начните с варианта «Стоимость» для фоновой работы и «Наименее используемый» для сбалансированного износа.",
|
||||
"comboDefaultsGuideTitle": "Как настроить комбо по умолчанию",
|
||||
"comboDefaultsGuideHint1": "Сохраняйте низкий уровень повторных попыток в потоках с малой задержкой; увеличивайте таймаут только для задач длинной генерации.",
|
||||
"comboDefaultsGuideHint2": "Используйте переопределения поставщика, если одному поставщику требуется другое поведение по тайм-ауту/повторной попытке, чем глобальные значения по умолчанию."
|
||||
},
|
||||
"translator": {
|
||||
"title": "Переводчик",
|
||||
@@ -2216,7 +2303,8 @@
|
||||
"orRemovePasswordHashField": "или удалите поле пароляHash",
|
||||
"restartServerWithNewPassword": "Перезагрузите сервер - он будет использовать новый пароль.",
|
||||
"backToLogin": "Вернуться к входу",
|
||||
"forgotPassword": "Забыли пароль?"
|
||||
"forgotPassword": "Забыли пароль?",
|
||||
"defaultPasswordHint": "Default password: 123456 (unless INITIAL_PASSWORD was set)"
|
||||
},
|
||||
"landing": {
|
||||
"brandName": "ОмниРоут",
|
||||
@@ -2414,7 +2502,16 @@
|
||||
"troubleshootingCodexFamily": "Для моделей семейства GitHub Codex сохраните модель как gh/codex-model; Маршрутизатор выбирает / отвечает автоматически.",
|
||||
"troubleshootingTestConnection": "Используйте «Панель мониторинга» > «Поставщики» > «Проверить соединение» перед тестированием из IDE или внешних клиентов.",
|
||||
"troubleshootingCircuitBreaker": "Если поставщик услуг показывает, что автоматический выключатель разомкнут, дождитесь охлаждения или посетите страницу «Здоровье» для получения подробной информации.",
|
||||
"troubleshootingOAuth": "Для поставщиков OAuth выполните повторную аутентификацию, если срок действия токенов истечет. Проверьте индикатор состояния карты провайдера."
|
||||
"troubleshootingOAuth": "Для поставщиков OAuth выполните повторную аутентификацию, если срок действия токенов истечет. Проверьте индикатор состояния карты провайдера.",
|
||||
"managementApiReference": "Management API Reference",
|
||||
"managementApiDescription": "Automation endpoints for proxy registry, scope assignments, and legacy proxy migration.",
|
||||
"mgmtProxiesListNote": "List saved proxy registry items (supports pagination).",
|
||||
"mgmtProxiesCreateNote": "Create a reusable proxy item in the registry.",
|
||||
"mgmtProxiesHealthNote": "Get 24h/rolling health metrics per saved proxy from proxy logs.",
|
||||
"mgmtProxiesBulkAssignNote": "Assign or clear one proxy across many scope IDs in one request.",
|
||||
"mgmtAssignmentsListNote": "List proxy assignments by scope, scope_id, or proxy_id.",
|
||||
"mgmtAssignmentsUpdateNote": "Assign or clear proxy for global/provider/account/combo scope.",
|
||||
"mgmtLegacyMigrationNote": "Import legacy proxyConfig maps into registry assignments."
|
||||
},
|
||||
"legal": {
|
||||
"privacyPolicy": "Политика конфиденциальности",
|
||||
@@ -2503,7 +2600,21 @@
|
||||
"binaryName": "Binary Name",
|
||||
"versionCommand": "Version Command",
|
||||
"spawnArgs": "Spawn Args",
|
||||
"addAgent": "Add Agent"
|
||||
"addAgent": "Add Agent",
|
||||
"scanning": "Scanning system for CLI agents...",
|
||||
"opencodeIntegration": "OpenCode Integration",
|
||||
"opencodeDetected": "opencode {version} detected",
|
||||
"opencodeDesc": "Generate a ready-to-use {configFile} with your OmniRoute base URL and all available models — drop it in your project root and run {command}.",
|
||||
"downloadConfig": "Download {file}",
|
||||
"downloaded": "Downloaded!",
|
||||
"setupGuideTitle": "Setup guide",
|
||||
"openCliTools": "Open CLI Tools",
|
||||
"setupGuideDetectCliTitle": "Detect installed CLIs",
|
||||
"setupGuideDetectCliDesc": "Click Refresh after installing or updating a CLI so OmniRoute can rescan binaries and versions.",
|
||||
"setupGuideCustomAgentTitle": "Register custom binary",
|
||||
"setupGuideCustomAgentDesc": "Use Add Custom Agent when your CLI is not in the built-in list. Provide binary name and version command.",
|
||||
"setupGuideCommandMissingTitle": "Fix 'command not found'",
|
||||
"setupGuideCommandMissingDesc": "Ensure the CLI command exists in PATH, open a new terminal session, and rerun Refresh."
|
||||
},
|
||||
"autoCombo": {
|
||||
"title": "Auto-Combo Engine",
|
||||
@@ -2527,5 +2638,56 @@
|
||||
"modePackCostSaver": "Cost Saver",
|
||||
"modePackQualityFirst": "Quality First",
|
||||
"modePackOfflineFriendly": "Offline Friendly"
|
||||
},
|
||||
"search": {
|
||||
"searchQuery": "Search Query",
|
||||
"searchResults": "Search Results",
|
||||
"cachedResult": "Cached",
|
||||
"searchCost": "Cost",
|
||||
"searchTools": "Search Tools",
|
||||
"searchToolsDesc": "Advanced search testing with provider comparison",
|
||||
"compareProviders": "Compare Providers",
|
||||
"rerankResults": "Rerank Results",
|
||||
"searchHistory": "Search History",
|
||||
"urlOverlap": "URL Overlap",
|
||||
"noSearchProviders": "No search providers configured. Add providers in Settings.",
|
||||
"noRerankModels": "No rerank model available",
|
||||
"webSearch": "Web Search",
|
||||
"provider": "Provider",
|
||||
"searchType": "Search Type",
|
||||
"maxResults": "Max Results",
|
||||
"filters": "Filters",
|
||||
"country": "Country",
|
||||
"language": "Language",
|
||||
"timeRange": "Time Range",
|
||||
"includeDomains": "Include Domains",
|
||||
"excludeDomains": "Exclude Domains",
|
||||
"safeSearch": "Safe Search",
|
||||
"formatted": "Formatted",
|
||||
"rawJson": "JSON",
|
||||
"cacheMiss": "cache miss",
|
||||
"cacheHit": "cache hit",
|
||||
"latency": "Latency",
|
||||
"cost": "Cost",
|
||||
"results": "Results",
|
||||
"rerank": "Rerank",
|
||||
"rerankModel": "Rerank Model",
|
||||
"positionDelta": "Position Change",
|
||||
"emptyState": "Send a search query to see results",
|
||||
"safeSearchOff": "Off",
|
||||
"safeSearchModerate": "Moderate",
|
||||
"safeSearchStrict": "Strict",
|
||||
"queryPlaceholder": "Enter search query...",
|
||||
"providerAuto": "auto (cheapest)",
|
||||
"searchTypeWeb": "web",
|
||||
"searchTypeNews": "news",
|
||||
"optionAny": "any",
|
||||
"timeRangeDay": "Past day",
|
||||
"timeRangeWeek": "Past week",
|
||||
"timeRangeMonth": "Past month",
|
||||
"timeRangeYear": "Past year",
|
||||
"domainPlaceholder": "example.com",
|
||||
"requestTimedOut": "Request timed out ({seconds}s)",
|
||||
"networkError": "Network error"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,7 +56,9 @@
|
||||
"warning": "Upozornenie",
|
||||
"note": "Poznámka",
|
||||
"free": "Zadarmo",
|
||||
"skipToContent": "Preskočiť na obsah"
|
||||
"skipToContent": "Preskočiť na obsah",
|
||||
"maintenanceServerIssues": "Server is experiencing issues. Some features may be unavailable.",
|
||||
"maintenanceServerUnreachable": "Server is unreachable. Reconnecting..."
|
||||
},
|
||||
"sidebar": {
|
||||
"home": "Domov",
|
||||
@@ -103,7 +105,9 @@
|
||||
"endpoints": "Koncové body",
|
||||
"playground": "Ihrisko",
|
||||
"agents": "Agenti",
|
||||
"cliToolsShort": "Nástroje"
|
||||
"cliToolsShort": "Nástroje",
|
||||
"autoCombo": "Auto Combo",
|
||||
"searchTools": "Search Tools"
|
||||
},
|
||||
"themesPage": {
|
||||
"title": "Themes",
|
||||
@@ -268,7 +272,28 @@
|
||||
"permissionsTitle": "Povolenia: {name}",
|
||||
"allowAllDesc": "Tento kľúč umožňuje prístup ku všetkým dostupným modelom.",
|
||||
"restrictDesc": "Tento kľúč má prístup k {selectedCount} z {totalModels} modelov.",
|
||||
"selectedCount": "{count} vybraté"
|
||||
"selectedCount": "{count} vybraté",
|
||||
"autoResolve": "Auto-Resolve",
|
||||
"autoResolveDesc": "Auto-resolve ambiguous model names to native provider for this API key.",
|
||||
"keyActive": "Key Active",
|
||||
"keyActiveDesc": "Enable or disable this API key. Disabled keys are immediately rejected with 403.",
|
||||
"accessSchedule": "Access Schedule",
|
||||
"accessScheduleDesc": "Restrict access to specific hours and days of the week.",
|
||||
"scheduleFrom": "From",
|
||||
"scheduleUntil": "Until",
|
||||
"scheduleDays": "Days",
|
||||
"scheduleTimezone": "Timezone",
|
||||
"scheduleTimezoneHint": "Use IANA timezone names, e.g. America/New_York, Europe/Berlin",
|
||||
"scheduleActive": "Schedule",
|
||||
"disabled": "Disabled",
|
||||
"daySun": "Sun",
|
||||
"dayMon": "Mon",
|
||||
"dayTue": "Tue",
|
||||
"dayWed": "Wed",
|
||||
"dayThu": "Thu",
|
||||
"dayFri": "Fri",
|
||||
"daySat": "Sat",
|
||||
"keyOnlyAvailableAtCreation": "Full key available only at creation time — copy it when you first create the key"
|
||||
},
|
||||
"auditLog": {
|
||||
"title": "Audit Log",
|
||||
@@ -548,6 +573,27 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"mitmHowWorksDesc": "{toolName} sends requests to its provider endpoint. MITM intercepts and redirects them to OmniRoute.",
|
||||
"mitmStep1": "1. Start MITM to route requests through OmniRoute.",
|
||||
"mitmStep2Prefix": "2. Add",
|
||||
"mitmStep2Suffix": "to your hosts file as 127.0.0.1.",
|
||||
"mitmStep3": "3. Open {toolName} and requests will be proxied.",
|
||||
"whenToUseLabel": "Kedy použiť",
|
||||
"openToolDocs": "Otvorte dokumentáciu nástroja",
|
||||
"toolUseCases": {
|
||||
"claude": "Použite, keď chcete silné plánovacie pracovné postupy a dlhé viacsúborové refaktory s Claude Code.",
|
||||
"codex": "Použite, keď je váš tím štandardizovaný na tokoch OpenAI Codex CLI a autentifikácii založenej na profile.",
|
||||
"droid": "Použite, keď potrebujete ľahkého terminálového agenta zameraného na rýchle kódovanie a slučky vykonávania príkazov.",
|
||||
"openclaw": "Použite, keď chcete kódovacieho agenta v štýle Open Claw, ale smerovaného cez zásady OmniRoute.",
|
||||
"cline": "Použite, keď konfigurujete kódovacích agentov v editoroch a chcete riadiť nastavenie s modelmi OmniRoute.",
|
||||
"kilo": "Použite, keď váš pracovný postup závisí od príkazov Kilo Code a rýchlych iteračných úprav.",
|
||||
"cursor": "Použite pri kódovaní v Cursore a potrebujete vlastné modely kompatibilné s OpenAI cez OmniRoute.",
|
||||
"continue": "Použite pri spustení Continue in IDE a potrebujete prenosnú konfiguráciu poskytovateľa založenú na JSON.",
|
||||
"opencode": "Použite, ak uprednostňujete spúšťanie natívneho agenta a skriptovanú automatizáciu cez OpenCode.",
|
||||
"kiro": "Použite pri integrácii Kiro a centrálnom riadení smerovania modelu z OmniRoute.",
|
||||
"antigravity": "Použite, keď musí byť premávka Antigravity/Kiro zachytená cez MITM a nasmerovaná na OmniRoute.",
|
||||
"copilot": "Použite, keď chcete UX v štýle chatu Copilot pri presadzovaní kľúčov OmniRoute a pravidiel smerovania."
|
||||
}
|
||||
},
|
||||
"combos": {
|
||||
@@ -864,7 +910,12 @@
|
||||
"a2aQuickStartStep2": "Send JSON-RPC requests to `POST /a2a` using `message/send` or `message/stream`.",
|
||||
"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"
|
||||
"completionsLegacyDesc": "Legacy OpenAI text completions — accepts both prompt string and messages array format",
|
||||
"categorySearch": "Search & Discovery",
|
||||
"webSearch": "Web Search",
|
||||
"webSearchDesc": "Unified web search across multiple providers with automatic failover and caching",
|
||||
"searchProvider": "Search Provider",
|
||||
"searchProviderDesc": "This provider is used for web search via POST /v1/search. No model configuration needed — search providers are ready to use once an API key is connected."
|
||||
},
|
||||
"mcpDashboard": {
|
||||
"loading": "Loading MCP dashboard...",
|
||||
@@ -1054,7 +1105,13 @@
|
||||
"resetAllTitle": "Resetujte všetky ističe do zdravého stavu",
|
||||
"resetting": "Resetovanie...",
|
||||
"resetAll": "Obnoviť všetko",
|
||||
"until": "Do {time}"
|
||||
"until": "Do {time}",
|
||||
"activeProviders": "{count} active",
|
||||
"monitoredProviders": "{count} monitored",
|
||||
"configuredProvidersLabel": "Nakonfigurované na ovládacom paneli",
|
||||
"configuredProvidersHint": "Poskytovatelia s povereniami uloženými v /dashboard/providers, bez ohľadu na stav runtime.",
|
||||
"activeProvidersHint": "Konfigurovaní poskytovatelia majú momentálne povolené požiadavky na smerovanie.",
|
||||
"monitoredProvidersHint": "Poskytovatelia v súčasnosti sledovaní monitormi zdravia ističov."
|
||||
},
|
||||
"limits": {
|
||||
"title": "Limity a kvóty",
|
||||
@@ -1396,7 +1453,31 @@
|
||||
"chatPathHint": "Custom chat path for providers with non-standard APIs (e.g. /v4/chat/completions)",
|
||||
"modelsPathLabel": "Models Endpoint Path",
|
||||
"modelsPathPlaceholder": "/models",
|
||||
"modelsPathHint": "Custom models path for validation (e.g. /v4/models)"
|
||||
"modelsPathHint": "Custom models path for validation (e.g. /v4/models)",
|
||||
"builtInModels": "Built-in models",
|
||||
"builtInModelsHint": "Registry models for this provider. Use the pencil to set compatibility options.",
|
||||
"normalizeToolCallIdLabel": "Normalize tool call IDs to 9 characters (e.g. Mistral)",
|
||||
"preserveDeveloperRoleLabel": "Keep OpenAI Responses developer role (do not map to system)",
|
||||
"compatAdjustmentsTitle": "Compatibility",
|
||||
"compatButtonLabel": "Compatibility",
|
||||
"compatToolIdShort": "Tool ID 9",
|
||||
"compatDeveloperShort": "Developer role",
|
||||
"compatDoNotPreserveDeveloper": "Do not preserve developer role",
|
||||
"compatBadgeNoPreserve": "No preserve",
|
||||
"compatProtocolLabel": "Client request protocol",
|
||||
"compatProtocolHint": "These options apply when OmniRoute detects this request shape (OpenAI Chat, Responses API, or Anthropic Messages).",
|
||||
"compatProtocolOpenAI": "OpenAI Chat Completions",
|
||||
"compatProtocolOpenAIResponses": "OpenAI Responses API",
|
||||
"compatProtocolClaude": "Anthropic Messages",
|
||||
"tokenRefreshed": "Token refreshed successfully",
|
||||
"tokenRefreshFailed": "Token refresh failed",
|
||||
"compatBadgeUpstreamHeaders": "Headers",
|
||||
"compatUpstreamAddRow": "Add header",
|
||||
"compatUpstreamHeaderName": "Header name",
|
||||
"compatUpstreamHeaderValue": "Value",
|
||||
"compatUpstreamHeadersHint": "High-privilege setting — same trust level as editing provider API credentials; only trusted admins should use it. Merged after OmniRoute adds auth from the provider API key. If a custom header uses the same name as an existing one (e.g. Authorization), your value fully replaces the auto-generated header (including the Bearer token) — the upstream only sees what you typed, not the key from settings. Misconfiguration can cause 401 or broken upstream auth. One row per header (e.g. extra Authentication for some gateways). Hover or focus the value to preview. Saves on blur, outside click, or closing this panel.",
|
||||
"compatUpstreamHeadersLabel": "Extra upstream headers",
|
||||
"compatUpstreamRemoveRow": "Remove row"
|
||||
},
|
||||
"settings": {
|
||||
"title": "Nastavenia",
|
||||
@@ -1799,7 +1880,13 @@
|
||||
"cliFingerprintDesc": "Match native CLI binary signatures when proxying requests. Reorders headers and body fields to look identical to the official CLI tools. Your proxy IP is preserved.",
|
||||
"cliFingerprintEnabled": "{count} provider(s) with CLI fingerprint active",
|
||||
"enableFingerprintTitle": "Enable fingerprint for {provider}",
|
||||
"disableFingerprintTitle": "Disable fingerprint for {provider}"
|
||||
"disableFingerprintTitle": "Disable fingerprint for {provider}",
|
||||
"routingAdvancedGuideTitle": "Pokročilé vedenie trasy",
|
||||
"routingAdvancedGuideHint1": "Použite Fill First pre predvídateľnú prioritu, Round Robin pre spravodlivosť a P2C pre odolnosť latencie.",
|
||||
"routingAdvancedGuideHint2": "Ak sa poskytovatelia líšia v kvalite/nákladoch, začnite s Cost Opt pre prácu na pozadí a Najmenej používané pre vyvážené opotrebovanie.",
|
||||
"comboDefaultsGuideTitle": "Ako vyladiť predvolené nastavenia komba",
|
||||
"comboDefaultsGuideHint1": "Udržujte počet opakovaní na nízkej úrovni v tokoch s nízkou latenciou; zvýšiť časový limit iba pre úlohy s dlhým generovaním.",
|
||||
"comboDefaultsGuideHint2": "Použite prepísania poskytovateľa, keď jeden poskytovateľ potrebuje iné správanie pri uplynutí časového limitu/opakovania, ako sú globálne predvolené hodnoty."
|
||||
},
|
||||
"translator": {
|
||||
"title": "Prekladateľ",
|
||||
@@ -2216,7 +2303,8 @@
|
||||
"orRemovePasswordHashField": "alebo odstráňte pole passwordHash",
|
||||
"restartServerWithNewPassword": "Reštartujte server - použije nové heslo",
|
||||
"backToLogin": "Späť na Prihlásenie",
|
||||
"forgotPassword": "Zabudli ste heslo?"
|
||||
"forgotPassword": "Zabudli ste heslo?",
|
||||
"defaultPasswordHint": "Default password: 123456 (unless INITIAL_PASSWORD was set)"
|
||||
},
|
||||
"landing": {
|
||||
"brandName": "OmniRoute",
|
||||
@@ -2414,7 +2502,16 @@
|
||||
"troubleshootingCodexFamily": "Pre modely rodiny GitHub Codex ponechajte model ako gh/codex-model; router vyberá / odpovedá automaticky.",
|
||||
"troubleshootingTestConnection": "Pred testovaním z IDE alebo externých klientov použite Dashboard > Providers > Test Connection.",
|
||||
"troubleshootingCircuitBreaker": "Ak poskytovateľ zobrazí istič otvorený, počkajte na vychladnutie alebo skontrolujte stránku Zdravie, kde nájdete podrobnosti.",
|
||||
"troubleshootingOAuth": "V prípade poskytovateľov OAuth znova overte, ak platnosť tokenov vyprší. Skontrolujte indikátor stavu karty poskytovateľa."
|
||||
"troubleshootingOAuth": "V prípade poskytovateľov OAuth znova overte, ak platnosť tokenov vyprší. Skontrolujte indikátor stavu karty poskytovateľa.",
|
||||
"managementApiReference": "Management API Reference",
|
||||
"managementApiDescription": "Automation endpoints for proxy registry, scope assignments, and legacy proxy migration.",
|
||||
"mgmtProxiesListNote": "List saved proxy registry items (supports pagination).",
|
||||
"mgmtProxiesCreateNote": "Create a reusable proxy item in the registry.",
|
||||
"mgmtProxiesHealthNote": "Get 24h/rolling health metrics per saved proxy from proxy logs.",
|
||||
"mgmtProxiesBulkAssignNote": "Assign or clear one proxy across many scope IDs in one request.",
|
||||
"mgmtAssignmentsListNote": "List proxy assignments by scope, scope_id, or proxy_id.",
|
||||
"mgmtAssignmentsUpdateNote": "Assign or clear proxy for global/provider/account/combo scope.",
|
||||
"mgmtLegacyMigrationNote": "Import legacy proxyConfig maps into registry assignments."
|
||||
},
|
||||
"legal": {
|
||||
"privacyPolicy": "Zásady ochrany osobných údajov",
|
||||
@@ -2503,7 +2600,21 @@
|
||||
"binaryName": "Binary Name",
|
||||
"versionCommand": "Version Command",
|
||||
"spawnArgs": "Spawn Args",
|
||||
"addAgent": "Add Agent"
|
||||
"addAgent": "Add Agent",
|
||||
"scanning": "Scanning system for CLI agents...",
|
||||
"opencodeIntegration": "OpenCode Integration",
|
||||
"opencodeDetected": "opencode {version} detected",
|
||||
"opencodeDesc": "Generate a ready-to-use {configFile} with your OmniRoute base URL and all available models — drop it in your project root and run {command}.",
|
||||
"downloadConfig": "Download {file}",
|
||||
"downloaded": "Downloaded!",
|
||||
"setupGuideTitle": "Setup guide",
|
||||
"openCliTools": "Open CLI Tools",
|
||||
"setupGuideDetectCliTitle": "Detect installed CLIs",
|
||||
"setupGuideDetectCliDesc": "Click Refresh after installing or updating a CLI so OmniRoute can rescan binaries and versions.",
|
||||
"setupGuideCustomAgentTitle": "Register custom binary",
|
||||
"setupGuideCustomAgentDesc": "Use Add Custom Agent when your CLI is not in the built-in list. Provide binary name and version command.",
|
||||
"setupGuideCommandMissingTitle": "Fix 'command not found'",
|
||||
"setupGuideCommandMissingDesc": "Ensure the CLI command exists in PATH, open a new terminal session, and rerun Refresh."
|
||||
},
|
||||
"autoCombo": {
|
||||
"title": "Auto-Combo Engine",
|
||||
@@ -2527,5 +2638,56 @@
|
||||
"modePackCostSaver": "Cost Saver",
|
||||
"modePackQualityFirst": "Quality First",
|
||||
"modePackOfflineFriendly": "Offline Friendly"
|
||||
},
|
||||
"search": {
|
||||
"searchQuery": "Search Query",
|
||||
"searchResults": "Search Results",
|
||||
"cachedResult": "Cached",
|
||||
"searchCost": "Cost",
|
||||
"searchTools": "Search Tools",
|
||||
"searchToolsDesc": "Advanced search testing with provider comparison",
|
||||
"compareProviders": "Compare Providers",
|
||||
"rerankResults": "Rerank Results",
|
||||
"searchHistory": "Search History",
|
||||
"urlOverlap": "URL Overlap",
|
||||
"noSearchProviders": "No search providers configured. Add providers in Settings.",
|
||||
"noRerankModels": "No rerank model available",
|
||||
"webSearch": "Web Search",
|
||||
"provider": "Provider",
|
||||
"searchType": "Search Type",
|
||||
"maxResults": "Max Results",
|
||||
"filters": "Filters",
|
||||
"country": "Country",
|
||||
"language": "Language",
|
||||
"timeRange": "Time Range",
|
||||
"includeDomains": "Include Domains",
|
||||
"excludeDomains": "Exclude Domains",
|
||||
"safeSearch": "Safe Search",
|
||||
"formatted": "Formatted",
|
||||
"rawJson": "JSON",
|
||||
"cacheMiss": "cache miss",
|
||||
"cacheHit": "cache hit",
|
||||
"latency": "Latency",
|
||||
"cost": "Cost",
|
||||
"results": "Results",
|
||||
"rerank": "Rerank",
|
||||
"rerankModel": "Rerank Model",
|
||||
"positionDelta": "Position Change",
|
||||
"emptyState": "Send a search query to see results",
|
||||
"safeSearchOff": "Off",
|
||||
"safeSearchModerate": "Moderate",
|
||||
"safeSearchStrict": "Strict",
|
||||
"queryPlaceholder": "Enter search query...",
|
||||
"providerAuto": "auto (cheapest)",
|
||||
"searchTypeWeb": "web",
|
||||
"searchTypeNews": "news",
|
||||
"optionAny": "any",
|
||||
"timeRangeDay": "Past day",
|
||||
"timeRangeWeek": "Past week",
|
||||
"timeRangeMonth": "Past month",
|
||||
"timeRangeYear": "Past year",
|
||||
"domainPlaceholder": "example.com",
|
||||
"requestTimedOut": "Request timed out ({seconds}s)",
|
||||
"networkError": "Network error"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,7 +56,9 @@
|
||||
"warning": "Varning",
|
||||
"note": "Obs",
|
||||
"free": "Gratis",
|
||||
"skipToContent": "Hoppa till innehållet"
|
||||
"skipToContent": "Hoppa till innehållet",
|
||||
"maintenanceServerIssues": "Server is experiencing issues. Some features may be unavailable.",
|
||||
"maintenanceServerUnreachable": "Server is unreachable. Reconnecting..."
|
||||
},
|
||||
"sidebar": {
|
||||
"home": "Hem",
|
||||
@@ -103,7 +105,9 @@
|
||||
"endpoints": "Ändpunkter",
|
||||
"playground": "Lekplats",
|
||||
"agents": "Agenter",
|
||||
"cliToolsShort": "Verktyg"
|
||||
"cliToolsShort": "Verktyg",
|
||||
"autoCombo": "Auto Combo",
|
||||
"searchTools": "Search Tools"
|
||||
},
|
||||
"themesPage": {
|
||||
"title": "Themes",
|
||||
@@ -268,7 +272,28 @@
|
||||
"permissionsTitle": "Behörigheter: {name}",
|
||||
"allowAllDesc": "Denna nyckel kan komma åt alla tillgängliga modeller.",
|
||||
"restrictDesc": "Denna nyckel kan komma åt {selectedCount} av {totalModels} modeller.",
|
||||
"selectedCount": "{count} har valts"
|
||||
"selectedCount": "{count} har valts",
|
||||
"autoResolve": "Auto-Resolve",
|
||||
"autoResolveDesc": "Auto-resolve ambiguous model names to native provider for this API key.",
|
||||
"keyActive": "Key Active",
|
||||
"keyActiveDesc": "Enable or disable this API key. Disabled keys are immediately rejected with 403.",
|
||||
"accessSchedule": "Access Schedule",
|
||||
"accessScheduleDesc": "Restrict access to specific hours and days of the week.",
|
||||
"scheduleFrom": "From",
|
||||
"scheduleUntil": "Until",
|
||||
"scheduleDays": "Days",
|
||||
"scheduleTimezone": "Timezone",
|
||||
"scheduleTimezoneHint": "Use IANA timezone names, e.g. America/New_York, Europe/Berlin",
|
||||
"scheduleActive": "Schedule",
|
||||
"disabled": "Disabled",
|
||||
"daySun": "Sun",
|
||||
"dayMon": "Mon",
|
||||
"dayTue": "Tue",
|
||||
"dayWed": "Wed",
|
||||
"dayThu": "Thu",
|
||||
"dayFri": "Fri",
|
||||
"daySat": "Sat",
|
||||
"keyOnlyAvailableAtCreation": "Full key available only at creation time — copy it when you first create the key"
|
||||
},
|
||||
"auditLog": {
|
||||
"title": "Revisionslogg",
|
||||
@@ -548,6 +573,27 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"mitmHowWorksDesc": "{toolName} sends requests to its provider endpoint. MITM intercepts and redirects them to OmniRoute.",
|
||||
"mitmStep1": "1. Start MITM to route requests through OmniRoute.",
|
||||
"mitmStep2Prefix": "2. Add",
|
||||
"mitmStep2Suffix": "to your hosts file as 127.0.0.1.",
|
||||
"mitmStep3": "3. Open {toolName} and requests will be proxied.",
|
||||
"whenToUseLabel": "När du ska använda",
|
||||
"openToolDocs": "Öppna verktygsdokument",
|
||||
"toolUseCases": {
|
||||
"claude": "Använd när du vill ha starka planeringsarbetsflöden och långa flerfilsrefaktorer med Claude Code.",
|
||||
"codex": "Använd när ditt team är standardiserat på OpenAI Codex CLI-flöden och profilbaserad autentisering.",
|
||||
"droid": "Använd när du behöver en lätt terminalagent fokuserad på snabb kodning och kommandokörningsloopar.",
|
||||
"openclaw": "Använd när du vill ha en Open Claw-kodningsagent men dirigerad genom OmniRoute-policyer.",
|
||||
"cline": "Använd när du konfigurerar kodningsagenter i redigerare och vill ha guidad installation med OmniRoute-modeller.",
|
||||
"kilo": "Använd när ditt arbetsflöde beror på Kilo Code-kommandon och snabba iterativa redigeringar.",
|
||||
"cursor": "Använd när du kodar i Cursor och du behöver anpassade OpenAI-kompatibla modeller genom OmniRoute.",
|
||||
"continue": "Använd när du kör Continue i IDE och du behöver portabel JSON-baserad leverantörskonfiguration.",
|
||||
"opencode": "Använd när du föredrar terminalbaserade agentkörningar och skriptautomation via OpenCode.",
|
||||
"kiro": "Använd när du integrerar Kiro och styr modelldirigering centralt från OmniRoute.",
|
||||
"antigravity": "Använd när Antigravity/Kiro-trafik måste avlyssnas genom MITM och dirigeras till OmniRoute.",
|
||||
"copilot": "Använd när du vill ha Copilot chattstil UX samtidigt som du upprätthåller OmniRoute-nycklar och routingregler."
|
||||
}
|
||||
},
|
||||
"combos": {
|
||||
@@ -864,7 +910,12 @@
|
||||
"a2aQuickStartStep2": "Send JSON-RPC requests to `POST /a2a` using `message/send` or `message/stream`.",
|
||||
"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"
|
||||
"completionsLegacyDesc": "Legacy OpenAI text completions — accepts both prompt string and messages array format",
|
||||
"categorySearch": "Search & Discovery",
|
||||
"webSearch": "Web Search",
|
||||
"webSearchDesc": "Unified web search across multiple providers with automatic failover and caching",
|
||||
"searchProvider": "Search Provider",
|
||||
"searchProviderDesc": "This provider is used for web search via POST /v1/search. No model configuration needed — search providers are ready to use once an API key is connected."
|
||||
},
|
||||
"mcpDashboard": {
|
||||
"loading": "Loading MCP dashboard...",
|
||||
@@ -1054,7 +1105,13 @@
|
||||
"resetAllTitle": "Återställ alla strömbrytare till friskt tillstånd",
|
||||
"resetting": "Återställer...",
|
||||
"resetAll": "Återställ alla",
|
||||
"until": "Tills {time}"
|
||||
"until": "Tills {time}",
|
||||
"activeProviders": "{count} active",
|
||||
"monitoredProviders": "{count} monitored",
|
||||
"configuredProvidersLabel": "Konfigurerad i instrumentpanelen",
|
||||
"configuredProvidersHint": "Leverantörer med autentiseringsuppgifter sparade i /dashboard/providers, oavsett körtidstillstånd.",
|
||||
"activeProvidersHint": "Konfigurerade leverantörer som för närvarande är aktiverade för routingförfrågningar.",
|
||||
"monitoredProvidersHint": "Leverantörer spåras för närvarande av strömbrytare hälsoövervakare."
|
||||
},
|
||||
"limits": {
|
||||
"title": "Gränser och kvoter",
|
||||
@@ -1396,7 +1453,31 @@
|
||||
"chatPathHint": "Custom chat path for providers with non-standard APIs (e.g. /v4/chat/completions)",
|
||||
"modelsPathLabel": "Models Endpoint Path",
|
||||
"modelsPathPlaceholder": "/models",
|
||||
"modelsPathHint": "Custom models path for validation (e.g. /v4/models)"
|
||||
"modelsPathHint": "Custom models path for validation (e.g. /v4/models)",
|
||||
"builtInModels": "Built-in models",
|
||||
"builtInModelsHint": "Registry models for this provider. Use the pencil to set compatibility options.",
|
||||
"normalizeToolCallIdLabel": "Normalize tool call IDs to 9 characters (e.g. Mistral)",
|
||||
"preserveDeveloperRoleLabel": "Keep OpenAI Responses developer role (do not map to system)",
|
||||
"compatAdjustmentsTitle": "Compatibility",
|
||||
"compatButtonLabel": "Compatibility",
|
||||
"compatToolIdShort": "Tool ID 9",
|
||||
"compatDeveloperShort": "Developer role",
|
||||
"compatDoNotPreserveDeveloper": "Do not preserve developer role",
|
||||
"compatBadgeNoPreserve": "No preserve",
|
||||
"compatProtocolLabel": "Client request protocol",
|
||||
"compatProtocolHint": "These options apply when OmniRoute detects this request shape (OpenAI Chat, Responses API, or Anthropic Messages).",
|
||||
"compatProtocolOpenAI": "OpenAI Chat Completions",
|
||||
"compatProtocolOpenAIResponses": "OpenAI Responses API",
|
||||
"compatProtocolClaude": "Anthropic Messages",
|
||||
"tokenRefreshed": "Token refreshed successfully",
|
||||
"tokenRefreshFailed": "Token refresh failed",
|
||||
"compatBadgeUpstreamHeaders": "Headers",
|
||||
"compatUpstreamAddRow": "Add header",
|
||||
"compatUpstreamHeaderName": "Header name",
|
||||
"compatUpstreamHeaderValue": "Value",
|
||||
"compatUpstreamHeadersHint": "High-privilege setting — same trust level as editing provider API credentials; only trusted admins should use it. Merged after OmniRoute adds auth from the provider API key. If a custom header uses the same name as an existing one (e.g. Authorization), your value fully replaces the auto-generated header (including the Bearer token) — the upstream only sees what you typed, not the key from settings. Misconfiguration can cause 401 or broken upstream auth. One row per header (e.g. extra Authentication for some gateways). Hover or focus the value to preview. Saves on blur, outside click, or closing this panel.",
|
||||
"compatUpstreamHeadersLabel": "Extra upstream headers",
|
||||
"compatUpstreamRemoveRow": "Remove row"
|
||||
},
|
||||
"settings": {
|
||||
"title": "Inställningar",
|
||||
@@ -1799,7 +1880,13 @@
|
||||
"cliFingerprintDesc": "Match native CLI binary signatures when proxying requests. Reorders headers and body fields to look identical to the official CLI tools. Your proxy IP is preserved.",
|
||||
"cliFingerprintEnabled": "{count} provider(s) with CLI fingerprint active",
|
||||
"enableFingerprintTitle": "Enable fingerprint for {provider}",
|
||||
"disableFingerprintTitle": "Disable fingerprint for {provider}"
|
||||
"disableFingerprintTitle": "Disable fingerprint for {provider}",
|
||||
"routingAdvancedGuideTitle": "Avancerad vägledning",
|
||||
"routingAdvancedGuideHint1": "Använd Fill First för förutsägbar prioritet, Round Robin för rättvisa och P2C för latensförmåga.",
|
||||
"routingAdvancedGuideHint2": "Om leverantörer varierar i kvalitet/kostnad, börja med Cost Opt för bakgrundsarbete och Minst Används för balanserat slitage.",
|
||||
"comboDefaultsGuideTitle": "Hur man ställer in kombinationsinställningar",
|
||||
"comboDefaultsGuideHint1": "Håll låga omförsök i flöden med låg latens; öka timeout endast för långa generationsuppgifter.",
|
||||
"comboDefaultsGuideHint2": "Använd åsidosättande av leverantörer när en leverantör behöver ett annat beteende för timeout/försök igen än globala standardinställningar."
|
||||
},
|
||||
"translator": {
|
||||
"title": "Översättare",
|
||||
@@ -2216,7 +2303,8 @@
|
||||
"orRemovePasswordHashField": "eller ta bort fältet passwordHash",
|
||||
"restartServerWithNewPassword": "Starta om servern - den kommer att använda det nya lösenordet",
|
||||
"backToLogin": "Tillbaka till inloggning",
|
||||
"forgotPassword": "Glömt lösenordet?"
|
||||
"forgotPassword": "Glömt lösenordet?",
|
||||
"defaultPasswordHint": "Default password: 123456 (unless INITIAL_PASSWORD was set)"
|
||||
},
|
||||
"landing": {
|
||||
"brandName": "OmniRoute",
|
||||
@@ -2414,7 +2502,16 @@
|
||||
"troubleshootingCodexFamily": "För GitHub Codex-familjens modeller, behåll modellen som gh/codex-model; routern väljer /svarar automatiskt.",
|
||||
"troubleshootingTestConnection": "Använd Dashboard > Leverantörer > Testa anslutning innan du testar från IDE:er eller externa klienter.",
|
||||
"troubleshootingCircuitBreaker": "Om en leverantör visar att strömbrytaren är öppen, vänta på nedkylning eller kolla Health-sidan för detaljer.",
|
||||
"troubleshootingOAuth": "För OAuth-leverantörer, autentisera på nytt om tokens löper ut. Kontrollera leverantörskortets statusindikator."
|
||||
"troubleshootingOAuth": "För OAuth-leverantörer, autentisera på nytt om tokens löper ut. Kontrollera leverantörskortets statusindikator.",
|
||||
"managementApiReference": "Management API Reference",
|
||||
"managementApiDescription": "Automation endpoints for proxy registry, scope assignments, and legacy proxy migration.",
|
||||
"mgmtProxiesListNote": "List saved proxy registry items (supports pagination).",
|
||||
"mgmtProxiesCreateNote": "Create a reusable proxy item in the registry.",
|
||||
"mgmtProxiesHealthNote": "Get 24h/rolling health metrics per saved proxy from proxy logs.",
|
||||
"mgmtProxiesBulkAssignNote": "Assign or clear one proxy across many scope IDs in one request.",
|
||||
"mgmtAssignmentsListNote": "List proxy assignments by scope, scope_id, or proxy_id.",
|
||||
"mgmtAssignmentsUpdateNote": "Assign or clear proxy for global/provider/account/combo scope.",
|
||||
"mgmtLegacyMigrationNote": "Import legacy proxyConfig maps into registry assignments."
|
||||
},
|
||||
"legal": {
|
||||
"privacyPolicy": "Sekretesspolicy",
|
||||
@@ -2503,7 +2600,21 @@
|
||||
"binaryName": "Binary Name",
|
||||
"versionCommand": "Version Command",
|
||||
"spawnArgs": "Spawn Args",
|
||||
"addAgent": "Add Agent"
|
||||
"addAgent": "Add Agent",
|
||||
"scanning": "Scanning system for CLI agents...",
|
||||
"opencodeIntegration": "OpenCode Integration",
|
||||
"opencodeDetected": "opencode {version} detected",
|
||||
"opencodeDesc": "Generate a ready-to-use {configFile} with your OmniRoute base URL and all available models — drop it in your project root and run {command}.",
|
||||
"downloadConfig": "Download {file}",
|
||||
"downloaded": "Downloaded!",
|
||||
"setupGuideTitle": "Setup guide",
|
||||
"openCliTools": "Open CLI Tools",
|
||||
"setupGuideDetectCliTitle": "Detect installed CLIs",
|
||||
"setupGuideDetectCliDesc": "Click Refresh after installing or updating a CLI so OmniRoute can rescan binaries and versions.",
|
||||
"setupGuideCustomAgentTitle": "Register custom binary",
|
||||
"setupGuideCustomAgentDesc": "Use Add Custom Agent when your CLI is not in the built-in list. Provide binary name and version command.",
|
||||
"setupGuideCommandMissingTitle": "Fix 'command not found'",
|
||||
"setupGuideCommandMissingDesc": "Ensure the CLI command exists in PATH, open a new terminal session, and rerun Refresh."
|
||||
},
|
||||
"autoCombo": {
|
||||
"title": "Auto-Combo Engine",
|
||||
@@ -2527,5 +2638,56 @@
|
||||
"modePackCostSaver": "Cost Saver",
|
||||
"modePackQualityFirst": "Quality First",
|
||||
"modePackOfflineFriendly": "Offline Friendly"
|
||||
},
|
||||
"search": {
|
||||
"searchQuery": "Search Query",
|
||||
"searchResults": "Search Results",
|
||||
"cachedResult": "Cached",
|
||||
"searchCost": "Cost",
|
||||
"searchTools": "Search Tools",
|
||||
"searchToolsDesc": "Advanced search testing with provider comparison",
|
||||
"compareProviders": "Compare Providers",
|
||||
"rerankResults": "Rerank Results",
|
||||
"searchHistory": "Search History",
|
||||
"urlOverlap": "URL Overlap",
|
||||
"noSearchProviders": "No search providers configured. Add providers in Settings.",
|
||||
"noRerankModels": "No rerank model available",
|
||||
"webSearch": "Web Search",
|
||||
"provider": "Provider",
|
||||
"searchType": "Search Type",
|
||||
"maxResults": "Max Results",
|
||||
"filters": "Filters",
|
||||
"country": "Country",
|
||||
"language": "Language",
|
||||
"timeRange": "Time Range",
|
||||
"includeDomains": "Include Domains",
|
||||
"excludeDomains": "Exclude Domains",
|
||||
"safeSearch": "Safe Search",
|
||||
"formatted": "Formatted",
|
||||
"rawJson": "JSON",
|
||||
"cacheMiss": "cache miss",
|
||||
"cacheHit": "cache hit",
|
||||
"latency": "Latency",
|
||||
"cost": "Cost",
|
||||
"results": "Results",
|
||||
"rerank": "Rerank",
|
||||
"rerankModel": "Rerank Model",
|
||||
"positionDelta": "Position Change",
|
||||
"emptyState": "Send a search query to see results",
|
||||
"safeSearchOff": "Off",
|
||||
"safeSearchModerate": "Moderate",
|
||||
"safeSearchStrict": "Strict",
|
||||
"queryPlaceholder": "Enter search query...",
|
||||
"providerAuto": "auto (cheapest)",
|
||||
"searchTypeWeb": "web",
|
||||
"searchTypeNews": "news",
|
||||
"optionAny": "any",
|
||||
"timeRangeDay": "Past day",
|
||||
"timeRangeWeek": "Past week",
|
||||
"timeRangeMonth": "Past month",
|
||||
"timeRangeYear": "Past year",
|
||||
"domainPlaceholder": "example.com",
|
||||
"requestTimedOut": "Request timed out ({seconds}s)",
|
||||
"networkError": "Network error"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,7 +56,9 @@
|
||||
"warning": "คำเตือน",
|
||||
"note": "หมายเหตุ",
|
||||
"free": "ฟรี",
|
||||
"skipToContent": "ข้ามไปที่เนื้อหา"
|
||||
"skipToContent": "ข้ามไปที่เนื้อหา",
|
||||
"maintenanceServerIssues": "Server is experiencing issues. Some features may be unavailable.",
|
||||
"maintenanceServerUnreachable": "Server is unreachable. Reconnecting..."
|
||||
},
|
||||
"sidebar": {
|
||||
"home": "บ้าน",
|
||||
@@ -103,7 +105,9 @@
|
||||
"endpoints": "จุดปลายทาง",
|
||||
"playground": "Playground",
|
||||
"agents": "เอเจนต์",
|
||||
"cliToolsShort": "เครื่องมือ"
|
||||
"cliToolsShort": "เครื่องมือ",
|
||||
"autoCombo": "Auto Combo",
|
||||
"searchTools": "Search Tools"
|
||||
},
|
||||
"themesPage": {
|
||||
"title": "ธีมส์",
|
||||
@@ -268,7 +272,28 @@
|
||||
"permissionsTitle": "สิทธิ์: {name}",
|
||||
"allowAllDesc": "กุญแจนี้เข้าได้ทุกรุ่น",
|
||||
"restrictDesc": "คีย์นี้สามารถเข้าถึงโมเดล {selectedCount} จาก {totalModels}",
|
||||
"selectedCount": "{count} เลือกแล้ว"
|
||||
"selectedCount": "{count} เลือกแล้ว",
|
||||
"autoResolve": "Auto-Resolve",
|
||||
"autoResolveDesc": "Auto-resolve ambiguous model names to native provider for this API key.",
|
||||
"keyActive": "Key Active",
|
||||
"keyActiveDesc": "Enable or disable this API key. Disabled keys are immediately rejected with 403.",
|
||||
"accessSchedule": "Access Schedule",
|
||||
"accessScheduleDesc": "Restrict access to specific hours and days of the week.",
|
||||
"scheduleFrom": "From",
|
||||
"scheduleUntil": "Until",
|
||||
"scheduleDays": "Days",
|
||||
"scheduleTimezone": "Timezone",
|
||||
"scheduleTimezoneHint": "Use IANA timezone names, e.g. America/New_York, Europe/Berlin",
|
||||
"scheduleActive": "Schedule",
|
||||
"disabled": "Disabled",
|
||||
"daySun": "Sun",
|
||||
"dayMon": "Mon",
|
||||
"dayTue": "Tue",
|
||||
"dayWed": "Wed",
|
||||
"dayThu": "Thu",
|
||||
"dayFri": "Fri",
|
||||
"daySat": "Sat",
|
||||
"keyOnlyAvailableAtCreation": "Full key available only at creation time — copy it when you first create the key"
|
||||
},
|
||||
"auditLog": {
|
||||
"title": "บันทึกการตรวจสอบ",
|
||||
@@ -548,6 +573,27 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"mitmHowWorksDesc": "{toolName} sends requests to its provider endpoint. MITM intercepts and redirects them to OmniRoute.",
|
||||
"mitmStep1": "1. Start MITM to route requests through OmniRoute.",
|
||||
"mitmStep2Prefix": "2. Add",
|
||||
"mitmStep2Suffix": "to your hosts file as 127.0.0.1.",
|
||||
"mitmStep3": "3. Open {toolName} and requests will be proxied.",
|
||||
"whenToUseLabel": "เมื่อจะใช้",
|
||||
"openToolDocs": "เปิดเอกสารเครื่องมือ",
|
||||
"toolUseCases": {
|
||||
"claude": "ใช้เมื่อคุณต้องการเวิร์กโฟลว์การวางแผนที่แข็งแกร่งและรีแฟคเตอร์หลายไฟล์แบบยาวด้วย Claude Code",
|
||||
"codex": "ใช้เมื่อทีมของคุณได้รับมาตรฐานในโฟลว์ OpenAI Codex CLI และการตรวจสอบสิทธิ์ตามโปรไฟล์",
|
||||
"droid": "ใช้เมื่อคุณต้องการเอเจนต์เทอร์มินัลน้ำหนักเบาที่เน้นไปที่การเขียนโค้ดที่รวดเร็วและลูปการดำเนินการคำสั่ง",
|
||||
"openclaw": "ใช้เมื่อคุณต้องการเอเจนต์การเข้ารหัสสไตล์ Open Claw แต่กำหนดเส้นทางผ่านนโยบาย OmniRoute",
|
||||
"cline": "ใช้เมื่อคุณกำหนดค่าเอเจนต์การเขียนโค้ดภายในโปรแกรมแก้ไข และต้องการการตั้งค่าที่แนะนำด้วยโมเดล OmniRoute",
|
||||
"kilo": "ใช้เมื่อเวิร์กโฟลว์ของคุณขึ้นอยู่กับคำสั่ง Kilo Code และการแก้ไขซ้ำอย่างรวดเร็ว",
|
||||
"cursor": "ใช้เมื่อเขียนโค้ดในเคอร์เซอร์และคุณต้องการโมเดลที่เข้ากันได้กับ OpenAI แบบกำหนดเองผ่าน OmniRoute",
|
||||
"continue": "ใช้เมื่อเรียกใช้ดำเนินการต่อใน IDE และคุณต้องกำหนดค่าผู้ให้บริการที่ใช้ JSON แบบพกพา",
|
||||
"opencode": "ใช้เมื่อคุณต้องการเรียกใช้ Terminal-Native Agent และใช้สคริปต์อัตโนมัติผ่าน OpenCode",
|
||||
"kiro": "ใช้เมื่อรวม Kiro และควบคุมการกำหนดเส้นทางโมเดลจากส่วนกลางจาก OmniRoute",
|
||||
"antigravity": "ใช้เมื่อต้องสกัดกั้นการรับส่งข้อมูล Antigravity/Kiro ผ่าน MITM และกำหนดเส้นทางไปยัง OmniRoute",
|
||||
"copilot": "ใช้เมื่อคุณต้องการ UX รูปแบบการแชทของ Copilot ในขณะที่บังคับใช้คีย์ OmniRoute และกฎการกำหนดเส้นทาง"
|
||||
}
|
||||
},
|
||||
"combos": {
|
||||
@@ -864,7 +910,12 @@
|
||||
"a2aQuickStartStep2": "Send JSON-RPC requests to `POST /a2a` using `message/send` or `message/stream`.",
|
||||
"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"
|
||||
"completionsLegacyDesc": "Legacy OpenAI text completions — accepts both prompt string and messages array format",
|
||||
"categorySearch": "Search & Discovery",
|
||||
"webSearch": "Web Search",
|
||||
"webSearchDesc": "Unified web search across multiple providers with automatic failover and caching",
|
||||
"searchProvider": "Search Provider",
|
||||
"searchProviderDesc": "This provider is used for web search via POST /v1/search. No model configuration needed — search providers are ready to use once an API key is connected."
|
||||
},
|
||||
"mcpDashboard": {
|
||||
"loading": "Loading MCP dashboard...",
|
||||
@@ -1054,7 +1105,13 @@
|
||||
"resetAllTitle": "รีเซ็ตเบรกเกอร์วงจรทั้งหมดให้อยู่ในสถานะปกติ",
|
||||
"resetting": "กำลังรีเซ็ต...",
|
||||
"resetAll": "รีเซ็ตทั้งหมด",
|
||||
"until": "จนถึง {time}"
|
||||
"until": "จนถึง {time}",
|
||||
"activeProviders": "{count} active",
|
||||
"monitoredProviders": "{count} monitored",
|
||||
"configuredProvidersLabel": "กำหนดค่าในแดชบอร์ด",
|
||||
"configuredProvidersHint": "ผู้ให้บริการที่มีข้อมูลรับรองบันทึกไว้ใน /dashboard/providers โดยไม่คำนึงถึงสถานะรันไทม์",
|
||||
"activeProvidersHint": "ผู้ให้บริการที่กำหนดค่าไว้เปิดใช้งานอยู่สำหรับการร้องขอการกำหนดเส้นทาง",
|
||||
"monitoredProvidersHint": "ผู้ให้บริการในปัจจุบันได้รับการติดตามโดยเครื่องตรวจสุขภาพของเซอร์กิตเบรกเกอร์"
|
||||
},
|
||||
"limits": {
|
||||
"title": "ขีดจำกัดและโควต้า",
|
||||
@@ -1396,7 +1453,31 @@
|
||||
"chatPathHint": "Custom chat path for providers with non-standard APIs (e.g. /v4/chat/completions)",
|
||||
"modelsPathLabel": "Models Endpoint Path",
|
||||
"modelsPathPlaceholder": "/models",
|
||||
"modelsPathHint": "Custom models path for validation (e.g. /v4/models)"
|
||||
"modelsPathHint": "Custom models path for validation (e.g. /v4/models)",
|
||||
"builtInModels": "Built-in models",
|
||||
"builtInModelsHint": "Registry models for this provider. Use the pencil to set compatibility options.",
|
||||
"normalizeToolCallIdLabel": "Normalize tool call IDs to 9 characters (e.g. Mistral)",
|
||||
"preserveDeveloperRoleLabel": "Keep OpenAI Responses developer role (do not map to system)",
|
||||
"compatAdjustmentsTitle": "Compatibility",
|
||||
"compatButtonLabel": "Compatibility",
|
||||
"compatToolIdShort": "Tool ID 9",
|
||||
"compatDeveloperShort": "Developer role",
|
||||
"compatDoNotPreserveDeveloper": "Do not preserve developer role",
|
||||
"compatBadgeNoPreserve": "No preserve",
|
||||
"compatProtocolLabel": "Client request protocol",
|
||||
"compatProtocolHint": "These options apply when OmniRoute detects this request shape (OpenAI Chat, Responses API, or Anthropic Messages).",
|
||||
"compatProtocolOpenAI": "OpenAI Chat Completions",
|
||||
"compatProtocolOpenAIResponses": "OpenAI Responses API",
|
||||
"compatProtocolClaude": "Anthropic Messages",
|
||||
"tokenRefreshed": "Token refreshed successfully",
|
||||
"tokenRefreshFailed": "Token refresh failed",
|
||||
"compatBadgeUpstreamHeaders": "Headers",
|
||||
"compatUpstreamAddRow": "Add header",
|
||||
"compatUpstreamHeaderName": "Header name",
|
||||
"compatUpstreamHeaderValue": "Value",
|
||||
"compatUpstreamHeadersHint": "High-privilege setting — same trust level as editing provider API credentials; only trusted admins should use it. Merged after OmniRoute adds auth from the provider API key. If a custom header uses the same name as an existing one (e.g. Authorization), your value fully replaces the auto-generated header (including the Bearer token) — the upstream only sees what you typed, not the key from settings. Misconfiguration can cause 401 or broken upstream auth. One row per header (e.g. extra Authentication for some gateways). Hover or focus the value to preview. Saves on blur, outside click, or closing this panel.",
|
||||
"compatUpstreamHeadersLabel": "Extra upstream headers",
|
||||
"compatUpstreamRemoveRow": "Remove row"
|
||||
},
|
||||
"settings": {
|
||||
"title": "การตั้งค่า",
|
||||
@@ -1799,7 +1880,13 @@
|
||||
"cliFingerprintDesc": "Match native CLI binary signatures when proxying requests. Reorders headers and body fields to look identical to the official CLI tools. Your proxy IP is preserved.",
|
||||
"cliFingerprintEnabled": "{count} provider(s) with CLI fingerprint active",
|
||||
"enableFingerprintTitle": "Enable fingerprint for {provider}",
|
||||
"disableFingerprintTitle": "Disable fingerprint for {provider}"
|
||||
"disableFingerprintTitle": "Disable fingerprint for {provider}",
|
||||
"routingAdvancedGuideTitle": "คำแนะนำการกำหนดเส้นทางขั้นสูง",
|
||||
"routingAdvancedGuideHint1": "ใช้ Fill First เพื่อลำดับความสำคัญที่คาดการณ์ได้ Round Robin เพื่อความยุติธรรม และ P2C เพื่อความยืดหยุ่นในการตอบสนอง",
|
||||
"routingAdvancedGuideHint2": "หากผู้ให้บริการมีคุณภาพ/ต้นทุนแตกต่างกัน ให้เริ่มด้วยการเลือกต้นทุนสำหรับงานเบื้องหลังและใช้งานน้อยที่สุดสำหรับการสึกหรอที่สมดุล",
|
||||
"comboDefaultsGuideTitle": "วิธีปรับแต่งค่าเริ่มต้นคอมโบ",
|
||||
"comboDefaultsGuideHint1": "พยายามลองใหม่ให้ต่ำในกระแสเวลาแฝงต่ำ เพิ่มการหมดเวลาเฉพาะสำหรับงานที่ใช้เวลานานเท่านั้น",
|
||||
"comboDefaultsGuideHint2": "ใช้การแทนที่ผู้ให้บริการเมื่อผู้ให้บริการรายหนึ่งต้องการพฤติกรรมการหมดเวลา/การลองใหม่ที่แตกต่างไปจากค่าเริ่มต้นส่วนกลาง"
|
||||
},
|
||||
"translator": {
|
||||
"title": "นักแปล",
|
||||
@@ -2216,7 +2303,8 @@
|
||||
"orRemovePasswordHashField": "หรือลบฟิลด์รหัสผ่านแฮช",
|
||||
"restartServerWithNewPassword": "รีสตาร์ทเซิร์ฟเวอร์ - จะใช้รหัสผ่านใหม่",
|
||||
"backToLogin": "กลับไปที่เข้าสู่ระบบ",
|
||||
"forgotPassword": "ลืมรหัสผ่าน?"
|
||||
"forgotPassword": "ลืมรหัสผ่าน?",
|
||||
"defaultPasswordHint": "Default password: 123456 (unless INITIAL_PASSWORD was set)"
|
||||
},
|
||||
"landing": {
|
||||
"brandName": "OmniRoute",
|
||||
@@ -2414,7 +2502,16 @@
|
||||
"troubleshootingCodexFamily": "สำหรับโมเดลตระกูล GitHub Codex ให้เก็บโมเดลเป็น gh/codex-model; เราเตอร์เลือก / ตอบสนองโดยอัตโนมัติ",
|
||||
"troubleshootingTestConnection": "ใช้แดชบอร์ด > ผู้ให้บริการ > ทดสอบการเชื่อมต่อ ก่อนการทดสอบจาก IDE หรือไคลเอนต์ภายนอก",
|
||||
"troubleshootingCircuitBreaker": "หากผู้ให้บริการแสดงเซอร์กิตเบรกเกอร์เปิดอยู่ ให้รอคูลดาวน์หรือตรวจสอบหน้าสุขภาพเพื่อดูรายละเอียด",
|
||||
"troubleshootingOAuth": "สำหรับผู้ให้บริการ OAuth ให้ตรวจสอบสิทธิ์อีกครั้งหากโทเค็นหมดอายุ ตรวจสอบตัวบ่งชี้สถานะบัตรผู้ให้บริการ"
|
||||
"troubleshootingOAuth": "สำหรับผู้ให้บริการ OAuth ให้ตรวจสอบสิทธิ์อีกครั้งหากโทเค็นหมดอายุ ตรวจสอบตัวบ่งชี้สถานะบัตรผู้ให้บริการ",
|
||||
"managementApiReference": "Management API Reference",
|
||||
"managementApiDescription": "Automation endpoints for proxy registry, scope assignments, and legacy proxy migration.",
|
||||
"mgmtProxiesListNote": "List saved proxy registry items (supports pagination).",
|
||||
"mgmtProxiesCreateNote": "Create a reusable proxy item in the registry.",
|
||||
"mgmtProxiesHealthNote": "Get 24h/rolling health metrics per saved proxy from proxy logs.",
|
||||
"mgmtProxiesBulkAssignNote": "Assign or clear one proxy across many scope IDs in one request.",
|
||||
"mgmtAssignmentsListNote": "List proxy assignments by scope, scope_id, or proxy_id.",
|
||||
"mgmtAssignmentsUpdateNote": "Assign or clear proxy for global/provider/account/combo scope.",
|
||||
"mgmtLegacyMigrationNote": "Import legacy proxyConfig maps into registry assignments."
|
||||
},
|
||||
"legal": {
|
||||
"privacyPolicy": "นโยบายความเป็นส่วนตัว",
|
||||
@@ -2503,7 +2600,21 @@
|
||||
"binaryName": "Binary Name",
|
||||
"versionCommand": "Version Command",
|
||||
"spawnArgs": "Spawn Args",
|
||||
"addAgent": "Add Agent"
|
||||
"addAgent": "Add Agent",
|
||||
"scanning": "Scanning system for CLI agents...",
|
||||
"opencodeIntegration": "OpenCode Integration",
|
||||
"opencodeDetected": "opencode {version} detected",
|
||||
"opencodeDesc": "Generate a ready-to-use {configFile} with your OmniRoute base URL and all available models — drop it in your project root and run {command}.",
|
||||
"downloadConfig": "Download {file}",
|
||||
"downloaded": "Downloaded!",
|
||||
"setupGuideTitle": "Setup guide",
|
||||
"openCliTools": "Open CLI Tools",
|
||||
"setupGuideDetectCliTitle": "Detect installed CLIs",
|
||||
"setupGuideDetectCliDesc": "Click Refresh after installing or updating a CLI so OmniRoute can rescan binaries and versions.",
|
||||
"setupGuideCustomAgentTitle": "Register custom binary",
|
||||
"setupGuideCustomAgentDesc": "Use Add Custom Agent when your CLI is not in the built-in list. Provide binary name and version command.",
|
||||
"setupGuideCommandMissingTitle": "Fix 'command not found'",
|
||||
"setupGuideCommandMissingDesc": "Ensure the CLI command exists in PATH, open a new terminal session, and rerun Refresh."
|
||||
},
|
||||
"autoCombo": {
|
||||
"title": "Auto-Combo Engine",
|
||||
@@ -2527,5 +2638,56 @@
|
||||
"modePackCostSaver": "Cost Saver",
|
||||
"modePackQualityFirst": "Quality First",
|
||||
"modePackOfflineFriendly": "Offline Friendly"
|
||||
},
|
||||
"search": {
|
||||
"searchQuery": "Search Query",
|
||||
"searchResults": "Search Results",
|
||||
"cachedResult": "Cached",
|
||||
"searchCost": "Cost",
|
||||
"searchTools": "Search Tools",
|
||||
"searchToolsDesc": "Advanced search testing with provider comparison",
|
||||
"compareProviders": "Compare Providers",
|
||||
"rerankResults": "Rerank Results",
|
||||
"searchHistory": "Search History",
|
||||
"urlOverlap": "URL Overlap",
|
||||
"noSearchProviders": "No search providers configured. Add providers in Settings.",
|
||||
"noRerankModels": "No rerank model available",
|
||||
"webSearch": "Web Search",
|
||||
"provider": "Provider",
|
||||
"searchType": "Search Type",
|
||||
"maxResults": "Max Results",
|
||||
"filters": "Filters",
|
||||
"country": "Country",
|
||||
"language": "Language",
|
||||
"timeRange": "Time Range",
|
||||
"includeDomains": "Include Domains",
|
||||
"excludeDomains": "Exclude Domains",
|
||||
"safeSearch": "Safe Search",
|
||||
"formatted": "Formatted",
|
||||
"rawJson": "JSON",
|
||||
"cacheMiss": "cache miss",
|
||||
"cacheHit": "cache hit",
|
||||
"latency": "Latency",
|
||||
"cost": "Cost",
|
||||
"results": "Results",
|
||||
"rerank": "Rerank",
|
||||
"rerankModel": "Rerank Model",
|
||||
"positionDelta": "Position Change",
|
||||
"emptyState": "Send a search query to see results",
|
||||
"safeSearchOff": "Off",
|
||||
"safeSearchModerate": "Moderate",
|
||||
"safeSearchStrict": "Strict",
|
||||
"queryPlaceholder": "Enter search query...",
|
||||
"providerAuto": "auto (cheapest)",
|
||||
"searchTypeWeb": "web",
|
||||
"searchTypeNews": "news",
|
||||
"optionAny": "any",
|
||||
"timeRangeDay": "Past day",
|
||||
"timeRangeWeek": "Past week",
|
||||
"timeRangeMonth": "Past month",
|
||||
"timeRangeYear": "Past year",
|
||||
"domainPlaceholder": "example.com",
|
||||
"requestTimedOut": "Request timed out ({seconds}s)",
|
||||
"networkError": "Network error"
|
||||
}
|
||||
}
|
||||
|
||||