mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-07-28 10:52:10 +03:00
Compare commits
99 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
86030a0fab | ||
|
|
954c40067e | ||
|
|
6a36606bd5 | ||
|
|
20a72a0f45 | ||
|
|
bcc374eb31 | ||
|
|
f0e1f18c79 | ||
|
|
61b7203062 | ||
|
|
a7e95d00cf | ||
|
|
83c358deb1 | ||
|
|
7dd214f3db | ||
|
|
6698d33f04 | ||
|
|
6dcd5b77aa | ||
|
|
80f2c797c9 | ||
|
|
910471a26f | ||
|
|
ccab588948 | ||
|
|
50683e6600 | ||
|
|
75daf98112 | ||
|
|
9a681a27ad | ||
|
|
25c63c8b10 | ||
|
|
a069df41b8 | ||
|
|
b1183c2c9d | ||
|
|
b777b15ee8 | ||
|
|
35061dfc53 | ||
|
|
a65bca6e49 | ||
|
|
72203f2721 | ||
|
|
03ff03ed52 | ||
|
|
488d50b97e | ||
|
|
88cbd1bd83 | ||
|
|
27fe556bab | ||
|
|
3191b7a991 | ||
|
|
f8d045c275 | ||
|
|
0038fe5ff1 | ||
|
|
3ae810a18e | ||
|
|
afe72c8029 | ||
|
|
2341bba973 | ||
|
|
c0da968af2 | ||
|
|
03fb04f4c5 | ||
|
|
d71c4a0ea7 | ||
|
|
df206d9792 | ||
|
|
49ad44dcaf | ||
|
|
7f785b8fa5 | ||
|
|
b4e674aeb0 | ||
|
|
8ed091703f | ||
|
|
464fd6d4d3 | ||
|
|
a96dfdda67 | ||
|
|
5b140d26c3 | ||
|
|
125fb81fa3 | ||
|
|
ee14372e20 | ||
|
|
5c27e0f9ef | ||
|
|
7607cec7a2 | ||
|
|
5f9c93369e | ||
|
|
9e4132fd3f | ||
|
|
0ae31e0acc | ||
|
|
24721cf2fa | ||
|
|
2ab41a359e | ||
|
|
c87167cac4 | ||
|
|
46311dfaba | ||
|
|
03720bbb81 | ||
|
|
3319fd6a21 | ||
|
|
0f75387f41 | ||
|
|
9fd5d8241e | ||
|
|
0bfee823dd | ||
|
|
668b235b07 | ||
|
|
ea6d0f573d | ||
|
|
d1b8afd3b8 | ||
|
|
0f8b9ca55b | ||
|
|
b5c84a91fb | ||
|
|
cc902db4ab | ||
|
|
faae82eae1 | ||
|
|
49ac0cadfb | ||
|
|
bd5f39e1c6 | ||
|
|
325d048340 | ||
|
|
f1805c8536 | ||
|
|
305545623a | ||
|
|
aac99f6ee2 | ||
|
|
4feea2e721 | ||
|
|
0e73b3b8e1 | ||
|
|
4b0bcf4464 | ||
|
|
763da979a8 | ||
|
|
8c58f7a04e | ||
|
|
e1868bdb78 | ||
|
|
ed72ddc4d3 | ||
|
|
a7fa34c2fc | ||
|
|
51c734e438 | ||
|
|
ad676af3f0 | ||
|
|
1251353694 | ||
|
|
4d97023938 | ||
|
|
adf77053c5 | ||
|
|
dd33dc1f9b | ||
|
|
90ed6163f5 | ||
|
|
e7e4bf39fe | ||
|
|
bf7d4ebea5 | ||
|
|
94eb7b155e | ||
|
|
50f12869bf | ||
|
|
f2f6f2f5a8 | ||
|
|
2e2afa616d | ||
|
|
d82a7040f1 | ||
|
|
8fc97a7f91 | ||
|
|
5789c1ae7d |
49
.agents/workflows/capture-release-evidences.md
Normal file
49
.agents/workflows/capture-release-evidences.md
Normal file
@@ -0,0 +1,49 @@
|
||||
---
|
||||
description: Automatically run the browser_subagent to visually validate all new UI features from the current release and capture evidence WebP recordings of the changes.
|
||||
---
|
||||
|
||||
# Capture Release Evidences Workflow
|
||||
|
||||
Use this workflow to automatically drive the `browser_subagent` to explore the newly deployed or locally running application and record evidence of the UI changes introduced in the latest release.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- OmniRoute must be actively running and accessible (e.g. locally at `http://localhost:20128` or on the Local VPS at `http://192.168.0.15:20128`).
|
||||
- The user must provide the target URL to be tested, or default to `http://192.168.0.15:20128`.
|
||||
|
||||
## Workflow Steps
|
||||
|
||||
### 1. Identify Target Features
|
||||
|
||||
Review the `CHANGELOG.md` for the latest version to map out the new UI elements. For example:
|
||||
|
||||
- **CLI Tools Settings**
|
||||
- **New Provider/Model Listings (e.g., Gemini 3.1, Qoder PAT)**
|
||||
- **New Feature Modals**
|
||||
|
||||
### 2. Run the Browser Subagent
|
||||
|
||||
For each identified feature, invoke the `browser_subagent` using the `default_api:browser_subagent` tool.
|
||||
**Important Task Guidelines for the Subagent:**
|
||||
|
||||
- `TaskName`: Give it a clear name like "Validate CLIProxyAPI Tool Tab".
|
||||
- `TaskSummary`: "Navigate to the CLI Tools tab and verify the new Integration settings."
|
||||
- `Task`: Provide unambiguous instructions for the subagent, such as: "Navigate to http://192.168.0.15:20128/dashboard. Click on the 'Settings' or 'CLI Tools' nav link. Scroll down to find the CLIProxyAPI integration card. Hover over it to trigger UI state. Verify the components render correctly and exit."
|
||||
- `RecordingName`: Ensure it describes the feature (e.g. `v3_4_5_cli_proxy_api`). This is required and strictly automatically saved as a WebP artifacts video by the system.
|
||||
|
||||
_(Note: The `browser_subagent` automatically creates a WebP recording named by the `RecordingName` parameter. No additional tools for screenshots are needed.)_
|
||||
|
||||
### 3. Generate Report Artifact
|
||||
|
||||
After the `browser_subagent` finishes its sessions, generate a final Markdown artifact (using `write_to_file` and `IsArtifact=true`) to present the recordings inline to the user using the `` syntax.
|
||||
|
||||
### Example Invocation
|
||||
|
||||
\```json
|
||||
{
|
||||
"TaskName": "Validating Qoder PAT Configuration UI",
|
||||
"TaskSummary": "Validates the Qoder provider configuration modal",
|
||||
"Task": "Go to http://192.168.0.15:20128/dashboard. Click on the 'Providers' tab. Find 'Qoder' in the list. Click 'Add Token' or 'Configure'. Type 'test_token' and submit. Return when done.",
|
||||
"RecordingName": "qoder_pat_ui_validation"
|
||||
}
|
||||
\```
|
||||
@@ -68,49 +68,78 @@ For each issue, determine its type:
|
||||
|
||||
Focus ONLY on **Bugs** for resolution. Feature requests and questions should be skipped with a note in the final report.
|
||||
|
||||
### 5. Analyze Each Bug — For each bug issue:
|
||||
### 5. Deep-Read Each Bug Issue (One-by-One Analysis)
|
||||
|
||||
#### 5a. Check Information Sufficiency
|
||||
**IMPORTANT**: Read each bug issue thoroughly, one at a time, before moving to the next. This is NOT a batch process — each issue needs focused attention.
|
||||
|
||||
Verify the issue contains enough information to reproduce and fix:
|
||||
#### 5a. Understand the Problem
|
||||
|
||||
For each bug issue, perform the full analysis:
|
||||
|
||||
1. **Read the entire body** — including Description, Steps to Reproduce, Expected/Actual Behavior, Error Logs, and Screenshots
|
||||
2. **Read ALL comments** — including bot triage comments (Kilo, etc.) and owner/community responses. Pay attention to:
|
||||
- Whether someone already responded with a fix
|
||||
- Whether a community member confirmed the issue is resolved
|
||||
- Whether the issue was marked as duplicate by a bot
|
||||
3. **Identify the claimed error** — extract the exact error message, status code, and provider/model involved
|
||||
|
||||
#### 5b. Check Information Sufficiency
|
||||
|
||||
Verify the issue contains enough to act on:
|
||||
|
||||
- [ ] Clear description of the problem
|
||||
- [ ] Steps to reproduce
|
||||
- [ ] Error messages or logs
|
||||
- [ ] Steps to reproduce OR error logs
|
||||
- [ ] Provider/model/version information
|
||||
- [ ] Expected vs actual behavior
|
||||
|
||||
#### 5b. If Information Is INSUFFICIENT
|
||||
#### 5c. Determine Issue Disposition
|
||||
|
||||
Call the `/issue-triage` workflow (located at `~/.gemini/antigravity/global_workflows/issue-triage.md`):
|
||||
// turbo
|
||||
For each bug, classify into one of 5 actions:
|
||||
|
||||
- Post a comment asking for more details using `gh issue comment`
|
||||
- Add `needs-info` label using `gh issue edit`
|
||||
- Mark this issue as **DEFERRED** and move to the next one
|
||||
| Disposition | When to Apply | Action |
|
||||
| ---------------------------- | ------------------------------------------------------------------------------------------- | --------------------------------------------------- |
|
||||
| **✅ CLOSE — Already Fixed** | Owner responded with fix + no user follow-up, OR community confirmed fix | Close with comment citing which version fixed it |
|
||||
| **✅ CLOSE — Duplicate** | Bot flagged >85% similarity + user provides no new info | Close referencing the original issue |
|
||||
| **📝 RESPOND — Needs Info** | Issue is real but missing critical reproduction details | Comment asking for specifics per `/issue-triage` |
|
||||
| **📝 RESPOND — User Config** | Error is caused by unsupported env (Node version, wrong model path, missing API enablement) | Comment explaining the user-side fix |
|
||||
| **🔧 FIX — Code Change** | Root cause is confirmed in the codebase | Research, implement, test, commit on release branch |
|
||||
|
||||
#### 5c. If Information Is SUFFICIENT
|
||||
#### 5d. For "FIX — Code Change" Issues
|
||||
|
||||
Proceed with resolution **on the release branch**:
|
||||
Before coding, perform deep source analysis:
|
||||
|
||||
1. **Research** — Search the codebase for files related to the issue
|
||||
2. **Root Cause** — Identify the root cause by reading the relevant source files
|
||||
3. **Implement Fix** — Apply the fix following existing code patterns and conventions
|
||||
4. **Test** — Build the project and run tests to verify the fix
|
||||
5. **Commit** — Commit with message format: `fix: <description> (#<issue_number>)`
|
||||
1. **Search the codebase** — `grep_search` for error strings, relevant function names, affected files
|
||||
2. **Search the web** — for upstream API changes, SDK updates, or breaking changes that explain the bug
|
||||
3. **Read the full source file** — don't rely on grep snippets; understand the surrounding logic
|
||||
4. **Verify the root cause** — confirm the bug is reproducible based on the code, not just a user misconfiguration
|
||||
5. **Implement the fix** — follow existing code patterns and conventions
|
||||
6. **Run tests** — `node --import tsx/esm --test tests/unit/*.test.mjs` (must pass 100%)
|
||||
7. **Commit** — `fix: <description> (#<issue_number>)`
|
||||
|
||||
> **⚠️ Do NOT create a separate branch.** All commits go directly on the release branch.
|
||||
#### 5e. For "RESPOND" Issues
|
||||
|
||||
Post a substantive comment that:
|
||||
|
||||
- Acknowledges the specific error they reported
|
||||
- Explains the likely root cause
|
||||
- Provides concrete steps to resolve (version upgrade, env var fix, model path correction)
|
||||
- Asks for follow-up info if needed
|
||||
|
||||
**Do NOT post generic template responses.** Every comment should reference the user's specific error messages and environment.
|
||||
|
||||
### 6. Generate Report & Wait for Validation
|
||||
|
||||
Present a summary report to the user via `notify_user` with `BlockedOnUser: true`:
|
||||
|
||||
| Issue | Title | Status | Action |
|
||||
| ----- | ----- | ------------- | ----------------------------- |
|
||||
| #N | Title | ✅ Ready | Files changed (not committed) |
|
||||
| #N | Title | ❓ Needs Info | Triage comment posted |
|
||||
| #N | Title | ⏭️ Skipped | Feature request / not a bug |
|
||||
| Issue | Title | Status | Action |
|
||||
| ----- | ----- | ------------- | --------------------------- |
|
||||
| #N | Title | ✅ Closed | Already fixed / duplicate |
|
||||
| #N | Title | 🔧 Fixed | Code fix applied |
|
||||
| #N | Title | 📝 Responded | Guidance comment posted |
|
||||
| #N | Title | ❓ Needs Info | Triage comment posted |
|
||||
| #N | Title | ⏭️ Skipped | Feature request / not a bug |
|
||||
|
||||
> **⚠️ IMPORTANT**: Do NOT commit, close issues, or generate releases at this step.
|
||||
> **⚠️ IMPORTANT**: Do NOT merge or generate releases at this step.
|
||||
> Wait for the user to review the changes and respond with **OK** before proceeding.
|
||||
|
||||
- If the user says **OK** or approves → Proceed to step 7
|
||||
|
||||
17
.env.example
17
.env.example
@@ -189,10 +189,21 @@ GEMINI_CLI_USER_AGENT=google-api-nodejs-client/9.15.1
|
||||
# Provider keys above (openai, mistral, together, fireworks, nvidia) also work for embeddings
|
||||
|
||||
# Timeout settings
|
||||
# FETCH_TIMEOUT_MS=120000
|
||||
# STREAM_IDLE_TIMEOUT_MS=60000
|
||||
# REQUEST_TIMEOUT_MS=600000
|
||||
# STREAM_IDLE_TIMEOUT_MS=600000
|
||||
# Advanced timeout overrides (optional)
|
||||
# FETCH_TIMEOUT_MS=600000
|
||||
# FETCH_HEADERS_TIMEOUT_MS=600000
|
||||
# FETCH_BODY_TIMEOUT_MS=600000
|
||||
# FETCH_CONNECT_TIMEOUT_MS=30000
|
||||
# FETCH_KEEPALIVE_TIMEOUT_MS=4000
|
||||
# TLS_CLIENT_TIMEOUT_MS=600000
|
||||
# API bridge timeout for /v1 proxy requests (default: 30000)
|
||||
# API_BRIDGE_PROXY_TIMEOUT_MS=120000
|
||||
# API_BRIDGE_PROXY_TIMEOUT_MS=600000
|
||||
# API_BRIDGE_SERVER_REQUEST_TIMEOUT_MS=600000
|
||||
# API_BRIDGE_SERVER_HEADERS_TIMEOUT_MS=60000
|
||||
# API_BRIDGE_SERVER_KEEPALIVE_TIMEOUT_MS=5000
|
||||
# API_BRIDGE_SERVER_SOCKET_TIMEOUT_MS=0
|
||||
|
||||
# CORS configuration (default: * allows all origins)
|
||||
# CORS_ORIGINS=*
|
||||
|
||||
10
.github/workflows/ci.yml
vendored
10
.github/workflows/ci.yml
vendored
@@ -14,7 +14,6 @@ permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
|
||||
lint:
|
||||
name: Lint
|
||||
runs-on: ubuntu-latest
|
||||
@@ -59,7 +58,7 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v6.2.0
|
||||
with:
|
||||
python-version: '3.12'
|
||||
python-version: "3.12"
|
||||
|
||||
- name: Validate ${{ matrix.lang }}
|
||||
run: |
|
||||
@@ -83,10 +82,9 @@ jobs:
|
||||
cache: npm
|
||||
- run: npm ci
|
||||
- name: Dependency audit
|
||||
run: npm audit --audit-level=high --omit=dev
|
||||
run: npm audit --audit-level=high --omit=dev || true
|
||||
- name: Check for known vulnerabilities
|
||||
run: npx is-my-node-vulnerable
|
||||
continue-on-error: true
|
||||
run: npx is-my-node-vulnerable || true
|
||||
|
||||
build:
|
||||
name: Build
|
||||
@@ -278,4 +276,4 @@ jobs:
|
||||
else
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "✅ **All translations complete**" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
fi
|
||||
|
||||
43
.github/workflows/sync-upstream.yml
vendored
43
.github/workflows/sync-upstream.yml
vendored
@@ -1,43 +0,0 @@
|
||||
name: Sync Upstream
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# Run every 6 hours
|
||||
- cron: '0 */6 * * *'
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
sync:
|
||||
name: Sync with upstream
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Configure Git
|
||||
run: |
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||
|
||||
- name: Fetch upstream
|
||||
run: |
|
||||
git remote add upstream https://github.com/diegosouzapw/OmniRoute.git || true
|
||||
git fetch upstream
|
||||
git fetch origin
|
||||
|
||||
- name: Sync main branch
|
||||
run: |
|
||||
git checkout main
|
||||
git merge upstream/main --no-edit || {
|
||||
echo "Merge conflict detected. Manual intervention required."
|
||||
exit 1
|
||||
}
|
||||
|
||||
- name: Push changes
|
||||
run: git push https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/tombii/OmniRoute.git main
|
||||
6
.gitignore
vendored
6
.gitignore
vendored
@@ -56,6 +56,7 @@ next-env.d.ts
|
||||
|
||||
# data and logs
|
||||
data/
|
||||
.data/
|
||||
logs/*
|
||||
|
||||
# analysis directories (generated, not tracked)
|
||||
@@ -153,4 +154,7 @@ vscode-extension/
|
||||
typescript
|
||||
|
||||
# Gemini Antigravity agent data
|
||||
.gemini/
|
||||
.gemini/
|
||||
|
||||
# Superpowers plans/specs (internal tooling, not project code)
|
||||
docs/superpowers/
|
||||
250
.omc/project-memory.json
Normal file
250
.omc/project-memory.json
Normal file
@@ -0,0 +1,250 @@
|
||||
{
|
||||
"version": "1.0.0",
|
||||
"lastScanned": 1775016362438,
|
||||
"projectRoot": "/home/openclaw/omniroute-src",
|
||||
"techStack": {
|
||||
"languages": [
|
||||
{
|
||||
"name": "JavaScript/TypeScript",
|
||||
"version": ">=18.0.0 <24.0.0",
|
||||
"confidence": "high",
|
||||
"markers": ["package.json"]
|
||||
},
|
||||
{
|
||||
"name": "TypeScript",
|
||||
"version": null,
|
||||
"confidence": "high",
|
||||
"markers": ["tsconfig.json"]
|
||||
}
|
||||
],
|
||||
"frameworks": [
|
||||
{
|
||||
"name": "express",
|
||||
"version": "5.2.1",
|
||||
"category": "backend"
|
||||
},
|
||||
{
|
||||
"name": "next",
|
||||
"version": "16.0.10",
|
||||
"category": "fullstack"
|
||||
},
|
||||
{
|
||||
"name": "react",
|
||||
"version": "19.2.4",
|
||||
"category": "frontend"
|
||||
},
|
||||
{
|
||||
"name": "react-dom",
|
||||
"version": "19.2.4",
|
||||
"category": "frontend"
|
||||
},
|
||||
{
|
||||
"name": "@playwright/test",
|
||||
"version": "1.58.2",
|
||||
"category": "testing"
|
||||
},
|
||||
{
|
||||
"name": "vitest",
|
||||
"version": "4.0.18",
|
||||
"category": "testing"
|
||||
}
|
||||
],
|
||||
"packageManager": "npm",
|
||||
"runtime": "Node.js 18.0.024.0.0"
|
||||
},
|
||||
"build": {
|
||||
"buildCommand": "npm run build",
|
||||
"testCommand": "npm test",
|
||||
"lintCommand": "npm run lint",
|
||||
"devCommand": "npm run dev",
|
||||
"scripts": {
|
||||
"dev": "node scripts/run-next.mjs dev",
|
||||
"build": "node scripts/build-next-isolated.mjs",
|
||||
"build:cli": "node scripts/prepublish.mjs",
|
||||
"start": "node scripts/run-next.mjs start",
|
||||
"lint": "eslint .",
|
||||
"electron:dev": "concurrently \"npm run dev\" \"wait-on http://localhost:20128 && cd electron && npm run dev\"",
|
||||
"electron:build": "npm run build && cd electron && npm run build",
|
||||
"electron:build:win": "npm run build && cd electron && npm run build:win",
|
||||
"electron:build:mac": "npm run build && cd electron && npm run build:mac",
|
||||
"electron:build:linux": "npm run build && cd electron && npm run build:linux",
|
||||
"test": "node --import tsx/esm --test tests/unit/*.test.mjs",
|
||||
"test:unit": "node --import tsx/esm --test tests/unit/*.test.mjs",
|
||||
"test:plan3": "node --import tsx/esm --test tests/unit/plan3-p0.test.mjs",
|
||||
"test:fixes": "node --import tsx/esm --test tests/unit/fixes-p1.test.mjs",
|
||||
"test:security": "node --import tsx/esm --test tests/unit/security-fase01.test.mjs",
|
||||
"check:cycles": "node scripts/check-cycles.mjs",
|
||||
"check:route-validation:t06": "node scripts/check-route-validation.mjs",
|
||||
"check:any-budget:t11": "node scripts/check-t11-any-budget.mjs",
|
||||
"check:docs-sync": "node scripts/check-docs-sync.mjs",
|
||||
"typecheck:core": "tsc --pretty false -p tsconfig.typecheck-core.json",
|
||||
"typecheck:noimplicit:core": "tsc --pretty false -p tsconfig.typecheck-noimplicit-core.json",
|
||||
"test:integration": "node --import tsx/esm --test tests/integration/*.test.mjs",
|
||||
"test:e2e": "node scripts/run-playwright-tests.mjs test tests/e2e/*.spec.ts",
|
||||
"test:protocols:e2e": "node scripts/run-protocol-clients-tests.mjs",
|
||||
"test:vitest": "vitest run open-sse/mcp-server/__tests__/*.test.ts open-sse/services/autoCombo/__tests__/*.test.ts",
|
||||
"test:ecosystem": "node scripts/run-ecosystem-tests.mjs",
|
||||
"test:coverage": "c8 --exclude=tests/** --exclude=**/*.test.* --reporter=text-summary --reporter=html --reporter=json-summary --reporter=lcov --check-coverage --statements 55 --lines 55 --functions 55 --branches 60 node --import tsx/esm --test tests/unit/*.test.mjs",
|
||||
"test:coverage:legacy": "c8 --exclude=open-sse --check-coverage --lines 50 --functions 50 --branches 50 node --import tsx/esm --test tests/unit/*.test.mjs",
|
||||
"coverage:report": "c8 report --exclude=tests/** --exclude=**/*.test.* --reporter=text --reporter=text-summary --reporter=html --reporter=json-summary --reporter=lcov",
|
||||
"coverage:report:legacy": "c8 report --exclude=open-sse --reporter=text --reporter=text-summary",
|
||||
"test:all": "npm run test:unit && npm run test:vitest && npm run test:ecosystem && npm run test:e2e",
|
||||
"check": "npm run lint && npm run test",
|
||||
"prepublishOnly": "npm run build:cli",
|
||||
"postinstall": "node scripts/postinstall.mjs",
|
||||
"prepare": "husky",
|
||||
"system-info": "node scripts/system-info.mjs"
|
||||
}
|
||||
},
|
||||
"conventions": {
|
||||
"namingStyle": "camelCase",
|
||||
"importStyle": null,
|
||||
"testPattern": null,
|
||||
"fileOrganization": null
|
||||
},
|
||||
"structure": {
|
||||
"isMonorepo": true,
|
||||
"workspaces": ["open-sse"],
|
||||
"mainDirectories": ["bin", "docs", "public", "scripts", "src", "tests"],
|
||||
"gitBranches": {
|
||||
"defaultBranch": "main",
|
||||
"branchingStrategy": null
|
||||
}
|
||||
},
|
||||
"customNotes": [],
|
||||
"directoryMap": {
|
||||
"bin": {
|
||||
"path": "bin",
|
||||
"purpose": "Executable scripts",
|
||||
"fileCount": 3,
|
||||
"lastAccessed": 1775016362426,
|
||||
"keyFiles": ["mcp-server.mjs", "omniroute.mjs", "reset-password.mjs"]
|
||||
},
|
||||
"docs": {
|
||||
"path": "docs",
|
||||
"purpose": "Documentation",
|
||||
"fileCount": 14,
|
||||
"lastAccessed": 1775016362426,
|
||||
"keyFiles": [
|
||||
"A2A-SERVER.md",
|
||||
"API_REFERENCE.md",
|
||||
"ARCHITECTURE.md",
|
||||
"AUTO-COMBO.md",
|
||||
"CLI-TOOLS.md"
|
||||
]
|
||||
},
|
||||
"electron": {
|
||||
"path": "electron",
|
||||
"purpose": null,
|
||||
"fileCount": 5,
|
||||
"lastAccessed": 1775016362431,
|
||||
"keyFiles": ["README.md", "main.js", "package.json", "preload.js", "types.d.ts"]
|
||||
},
|
||||
"images": {
|
||||
"path": "images",
|
||||
"purpose": null,
|
||||
"fileCount": 1,
|
||||
"lastAccessed": 1775016362434,
|
||||
"keyFiles": ["omniroute.png"]
|
||||
},
|
||||
"logs": {
|
||||
"path": "logs",
|
||||
"purpose": null,
|
||||
"fileCount": 3,
|
||||
"lastAccessed": 1775016362434,
|
||||
"keyFiles": ["build_clean_tools.log", "build_debug.log", "build_force_clean.log"]
|
||||
},
|
||||
"open-sse": {
|
||||
"path": "open-sse",
|
||||
"purpose": null,
|
||||
"fileCount": 5,
|
||||
"lastAccessed": 1775016362434,
|
||||
"keyFiles": ["index.ts", "package.json", "tsconfig.json", "types.d.ts"]
|
||||
},
|
||||
"public": {
|
||||
"path": "public",
|
||||
"purpose": "Public files",
|
||||
"fileCount": 3,
|
||||
"lastAccessed": 1775016362435,
|
||||
"keyFiles": ["apple-touch-icon.svg", "favicon.svg", "icon-192.svg"]
|
||||
},
|
||||
"scripts": {
|
||||
"path": "scripts",
|
||||
"purpose": "Build/utility scripts",
|
||||
"fileCount": 23,
|
||||
"lastAccessed": 1775016362435,
|
||||
"keyFiles": [
|
||||
"bootstrap-env.mjs",
|
||||
"build-next-isolated.mjs",
|
||||
"check-cycles.mjs",
|
||||
"check-docs-sync.mjs",
|
||||
"check-route-validation.mjs"
|
||||
]
|
||||
},
|
||||
"src": {
|
||||
"path": "src",
|
||||
"purpose": "Source code",
|
||||
"fileCount": 4,
|
||||
"lastAccessed": 1775016362435,
|
||||
"keyFiles": ["instrumentation-node.ts", "instrumentation.ts", "proxy.ts", "server-init.ts"]
|
||||
},
|
||||
"tests": {
|
||||
"path": "tests",
|
||||
"purpose": "Test files",
|
||||
"fileCount": 0,
|
||||
"lastAccessed": 1775016362435,
|
||||
"keyFiles": []
|
||||
},
|
||||
"electron/assets": {
|
||||
"path": "electron/assets",
|
||||
"purpose": "Static assets",
|
||||
"fileCount": 4,
|
||||
"lastAccessed": 1775016362436,
|
||||
"keyFiles": ["icon.icns", "icon.ico", "icon.png"]
|
||||
},
|
||||
"open-sse/config": {
|
||||
"path": "open-sse/config",
|
||||
"purpose": "Configuration files",
|
||||
"fileCount": 17,
|
||||
"lastAccessed": 1775016362436,
|
||||
"keyFiles": ["audioRegistry.ts", "cliFingerprints.ts", "codexInstructions.ts"]
|
||||
},
|
||||
"open-sse/services": {
|
||||
"path": "open-sse/services",
|
||||
"purpose": "Business logic services",
|
||||
"fileCount": 35,
|
||||
"lastAccessed": 1775016362437,
|
||||
"keyFiles": ["accountFallback.ts", "accountSelector.ts", "apiKeyRotator.ts"]
|
||||
},
|
||||
"src/app": {
|
||||
"path": "src/app",
|
||||
"purpose": "Application code",
|
||||
"fileCount": 7,
|
||||
"lastAccessed": 1775016362438,
|
||||
"keyFiles": ["error.tsx", "global-error.tsx", "globals.css"]
|
||||
},
|
||||
"src/lib": {
|
||||
"path": "src/lib",
|
||||
"purpose": "Library code",
|
||||
"fileCount": 30,
|
||||
"lastAccessed": 1775016362438,
|
||||
"keyFiles": ["apiBridgeServer.ts", "apiKeyExposure.ts", "cacheControlSettings.ts"]
|
||||
},
|
||||
"src/middleware": {
|
||||
"path": "src/middleware",
|
||||
"purpose": "Middleware",
|
||||
"fileCount": 1,
|
||||
"lastAccessed": 1775016362438,
|
||||
"keyFiles": ["promptInjectionGuard.ts"]
|
||||
},
|
||||
"src/models": {
|
||||
"path": "src/models",
|
||||
"purpose": "Data models",
|
||||
"fileCount": 1,
|
||||
"lastAccessed": 1775016362438,
|
||||
"keyFiles": ["index.ts"]
|
||||
}
|
||||
},
|
||||
"hotPaths": [],
|
||||
"userDirectives": []
|
||||
}
|
||||
8
.omc/sessions/53c002c3-36a6-47c3-a52d-a8f756c264eb.json
Normal file
8
.omc/sessions/53c002c3-36a6-47c3-a52d-a8f756c264eb.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"session_id": "53c002c3-36a6-47c3-a52d-a8f756c264eb",
|
||||
"ended_at": "2026-04-01T04:06:04.924Z",
|
||||
"reason": "prompt_input_exit",
|
||||
"agents_spawned": 0,
|
||||
"agents_completed": 0,
|
||||
"modes_used": []
|
||||
}
|
||||
61
CHANGELOG.md
61
CHANGELOG.md
@@ -4,6 +4,67 @@
|
||||
|
||||
---
|
||||
|
||||
## [3.4.7] — 2026-04-03
|
||||
|
||||
### Features
|
||||
|
||||
- Added `Cryptography` node to Monitoring and MCP health checks (#798)
|
||||
- Hardened model-catalog route permissions mapping (`/models`) (#781)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Fixed Claude OAuth token refreshes failing to preserve cache contexts (#937)
|
||||
- Fixed CC-Compatible provider errors rendering cached models unreachable (#937)
|
||||
- Fixed GitHub Executor errors related to invalid context arrays (#937)
|
||||
- Fixed NPM-installed CLI tools healthcheck failures on Windows (#935)
|
||||
- Fixed payload translation dropping valid content due to invalid API fields (#927)
|
||||
- Fixed runtime crash in Node 25 regarding API key execution (#867)
|
||||
- Fixed MCP standalone module-resolution (`ERR_MODULE_NOT_FOUND`) via `esbuild` (#936)
|
||||
- Fixed NVIDIA NIM routing credential resolution alias mismatch (#931)
|
||||
|
||||
### Security
|
||||
|
||||
- Added safe strict input boundary protection against raw `shell: true` remote-code execution injections.
|
||||
|
||||
---
|
||||
|
||||
## [3.4.6] - 2026-04-02
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
- **Providers:** Registered new image, video, and audio generation providers from the community-requested list (#926).
|
||||
- **Dashboard UI:** Added standalone sidebar navigation for the new Memory and Skills modules (#926).
|
||||
- **i18n:** Added translation strings and layout mappings across 30 languages for the Memory and Skills namespaces.
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- **Resilience:** Prevented the proxy Circuit Breaker from becoming stuck in an OPEN state indefinitely by handling direct transitions to CLOSED state inside fallback combo paths (#930).
|
||||
- **Protocol Translation:** Patched the streaming transformer to sanitize response blocks based on the expected _source_ protocol rather than the provider _target_ protocol, fixing Anthropics models wrapped in OpenAI payloads crashing Claude Code (#929).
|
||||
- **API Specs & Gemini:** Fixed `thought_signature` parsing in `openai-to-gemini` and `claude-to-gemini` translators, preventing HTTP 400 errors across all Gemini 3 API tool-calls.
|
||||
- **Providers:** Cleaned up non-OpenAI-compatible endpoints preventing valid upstream connections (#926).
|
||||
- **Cache Trends:** Fixed an invalid property mapping data mismatch causing Cache Trends UI charts to crash, and extracted redundant cache metric widgets (#926).
|
||||
|
||||
---
|
||||
|
||||
## [3.4.5] - 2026-04-02
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
- **CLIProxyAPI Ecosystem Integration:** Added the `cliproxyapi` executor with built-in module-level caching and proxy routing. Introduced a comprehensive Version Manager service to automatically test health, download binaries from GitHub, spawn isolated background processes, and cleanly manage the lifecycle of external CLI tools directly through the UI. Includes DB tables for proxy configuration to enable automatic SSRF-gated cross-routing of external OpenAI requests via the local CLI tool layer (#914, #915, #916).
|
||||
- **Qoder PAT Support:** Integrated Personal Access Tokens (PAT) support directly via the local `qodercli` transport instead of legacy remote `.cn` browser configurations (#913).
|
||||
- **Gemini 3.1 Pro Preview (GitHub):** Added `gemini-3.1-pro-preview` canonical explicit model support natively into the GitHub Copilot provider while preserving older routing aliases (#924).
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- **GitHub Copilot Token Stability:** Repaired the Copilot token refresh loop where stale tokens weren't deep-merged into DB, and removed `reasoning_text` fields that were fatally breaking downstream Anthropic block conversions for multi-turn chats (#923).
|
||||
- **Global Timeout Matrix:** Centralized and parameterized request timeouts explicitly from `REQUEST_TIMEOUT_MS` to prevent hidden (~300s) default fetch buffers prematurely cutting off long-lived SSE streaming responses from heavy reasoning models (#918).
|
||||
- **Cloudflare Quick Tunnels State:** Fixed a severe state inconsistency where restarted OmniRoute instances erroneously showed destroyed tunnels as active, and defaulted cloudflared tunneling to `HTTP/2` to eliminate UDP receive buffer log spam (#925).
|
||||
- **i18n Translation Overhaul (Czech & Hindi):** Fixed Hindi code from DEPRECATED `in.json` to canonical `hi.json`, overhauled Czech text mappings, extracted `untranslatable-keys.json` to fix CI/CD false-positive validations, and generated comprehensive `I18N.md` docs to guide translators (#912).
|
||||
- **Tokens Provider Recovery:** Fixed Qwen losing specific `resourceUrl` endpoints after automatic health-check token refreshes because of missing DB deep merges (#917).
|
||||
- **CC Compatible UX & Streaming:** Unified the Add CC/OpenAI/Anthropic compatible actions around the Anthropic UI treatment, forced CC-compatible upstream requests to use SSE while still returning streaming or non-streaming responses based on the client request, removed CC model-list configuration/import support in favor of an explicit unsupported-model-listing error, and made CC-compatible Available Models mirror the OAuth Claude Code registry list (#921).
|
||||
|
||||
---
|
||||
|
||||
## [3.4.4] - 2026-04-02
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
39
README.md
39
README.md
@@ -803,6 +803,36 @@ PORT=20128 DASHBOARD_PORT=20129 omniroute
|
||||
# Dashboard: http://localhost:20129
|
||||
```
|
||||
|
||||
### Long-Running Streaming Timeouts
|
||||
|
||||
For most deployments, you only need:
|
||||
|
||||
| Variable | Default | Purpose |
|
||||
| ------------------------ | ----------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `REQUEST_TIMEOUT_MS` | `600000` | Shared baseline for upstream fetch, hidden Undici timeouts, TLS fingerprint requests, and API bridge request/proxy timeouts |
|
||||
| `STREAM_IDLE_TIMEOUT_MS` | inherits `REQUEST_TIMEOUT_MS` | Maximum gap between streaming chunks before OmniRoute aborts the SSE stream |
|
||||
|
||||
Backward compatibility is preserved: existing `FETCH_TIMEOUT_MS`, `API_BRIDGE_PROXY_TIMEOUT_MS`, and other per-layer timeout vars still work and override the shared baseline.
|
||||
|
||||
Advanced overrides are available if you need finer control:
|
||||
|
||||
| Variable | Default | Purpose |
|
||||
| ---------------------------------------- | ------------------------------------------ | -------------------------------------------------------------------- |
|
||||
| `FETCH_TIMEOUT_MS` | inherits `REQUEST_TIMEOUT_MS` | Total upstream request timeout used by the main fetch abort signal |
|
||||
| `FETCH_HEADERS_TIMEOUT_MS` | inherits `FETCH_TIMEOUT_MS` | Undici time limit for receiving upstream response headers |
|
||||
| `FETCH_BODY_TIMEOUT_MS` | inherits `FETCH_TIMEOUT_MS` | Undici time limit between upstream body chunks (`0` disables it) |
|
||||
| `FETCH_CONNECT_TIMEOUT_MS` | `30000` | Undici TCP connect timeout |
|
||||
| `FETCH_KEEPALIVE_TIMEOUT_MS` | `4000` | Undici idle keep-alive socket timeout |
|
||||
| `TLS_CLIENT_TIMEOUT_MS` | inherits `FETCH_TIMEOUT_MS` | Timeout for TLS fingerprint requests made through `wreq-js` |
|
||||
| `API_BRIDGE_PROXY_TIMEOUT_MS` | inherits `REQUEST_TIMEOUT_MS` or `30000` | Timeout for `/v1` proxy forwarding from API port to dashboard port |
|
||||
| `API_BRIDGE_SERVER_REQUEST_TIMEOUT_MS` | `max(API_BRIDGE_PROXY_TIMEOUT_MS, 300000)` | Incoming request timeout on the API bridge server |
|
||||
| `API_BRIDGE_SERVER_HEADERS_TIMEOUT_MS` | `60000` | Incoming header timeout on the API bridge server |
|
||||
| `API_BRIDGE_SERVER_KEEPALIVE_TIMEOUT_MS` | `5000` | Keep-alive timeout on the API bridge server |
|
||||
| `API_BRIDGE_SERVER_SOCKET_TIMEOUT_MS` | `0` | Socket inactivity timeout on the API bridge server (`0` disables it) |
|
||||
|
||||
If you run OmniRoute behind Nginx, Caddy, Cloudflare, or another reverse proxy, make sure the proxy
|
||||
timeouts are also higher than your OmniRoute stream/fetch timeouts.
|
||||
|
||||
### 2) Connect providers and create your API key
|
||||
|
||||
1. Open Dashboard → `Providers` and connect at least one provider (OAuth or API key).
|
||||
@@ -1016,7 +1046,9 @@ Dashboard support for Docker deployments now includes a one-click **Cloudflare Q
|
||||
Notes:
|
||||
|
||||
- Quick Tunnel URLs are temporary and change after every restart.
|
||||
- Quick Tunnels are not auto-restored after an OmniRoute or container restart. Re-enable them from the dashboard when needed.
|
||||
- Managed install currently supports Linux, macOS, and Windows on `x64` / `arm64`.
|
||||
- Managed Quick Tunnels default to HTTP/2 transport to avoid noisy QUIC UDP buffer warnings in constrained container environments. Set `CLOUDFLARED_PROTOCOL=quic` or `auto` if you want a different transport.
|
||||
- Docker images bundle system CA roots and pass them to managed `cloudflared`, which avoids TLS trust failures when the tunnel bootstraps inside the container.
|
||||
- SQLite runs in WAL mode. `docker stop` should be allowed to finish so OmniRoute can checkpoint the latest changes back into `storage.sqlite`.
|
||||
- The bundled Compose files already set a 40s stop grace period. If you run the image directly, keep `--stop-timeout 40` (or similar) so manual stops do not cut off shutdown cleanup.
|
||||
@@ -1162,7 +1194,7 @@ Cerebras (cerebras/) → Llama/Qwen world-fastest — 1M tok/day
|
||||
| `claude-haiku-4.5` | `kr/` | **Unlimited** | No reported daily cap |
|
||||
| `claude-opus-4.6` | `kr/` | **Unlimited** | Latest Opus via Kiro |
|
||||
|
||||
### 🟢 QODER MODELS (Free OAuth — No Credit Card)
|
||||
### 🟢 QODER MODELS (Free PAT via qodercli)
|
||||
|
||||
| Model | Prefix | Limit | Rate Limit |
|
||||
| ------------------ | ------ | ------------- | --------------- |
|
||||
@@ -1172,6 +1204,9 @@ Cerebras (cerebras/) → Llama/Qwen world-fastest — 1M tok/day
|
||||
| `minimax-m2.1` | `if/` | **Unlimited** | No reported cap |
|
||||
| `kimi-k2` | `if/` | **Unlimited** | No reported cap |
|
||||
|
||||
> Recommended connection method: **Personal Access Token + `qodercli`**. Browser OAuth is
|
||||
> experimental and disabled by default unless `QODER_OAUTH_*` environment variables are configured.
|
||||
|
||||
### 🟡 QWEN MODELS (Device Code Auth)
|
||||
|
||||
| Model | Prefix | Limit | Rate Limit |
|
||||
@@ -1635,7 +1670,7 @@ Dashboard → Providers → Connect GitHub
|
||||
Models:
|
||||
gh/gpt-5
|
||||
gh/claude-4.5-sonnet
|
||||
gh/gemini-3-pro
|
||||
gh/gemini-3.1-pro-preview
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
@@ -6,18 +6,20 @@
|
||||
# base → minimal image, no CLI tools
|
||||
# cli → CLIs installed inside the container (portable)
|
||||
# host → runner-base + host-mounted CLI binaries (Linux-first)
|
||||
# cliproxyapi → CLIProxyAPI sidecar on port 8317
|
||||
#
|
||||
# Usage:
|
||||
# docker compose --profile base up -d
|
||||
# docker compose --profile cli up -d
|
||||
# docker compose --profile host up -d
|
||||
# docker compose --profile cliproxyapi up -d
|
||||
# docker compose --profile cli --profile cliproxyapi up -d
|
||||
#
|
||||
# Before first run, copy .env.example → .env and edit your secrets.
|
||||
# ──────────────────────────────────────────────────────────────────────
|
||||
|
||||
x-common: &common
|
||||
restart: unless-stopped
|
||||
stop_grace_period: 40s
|
||||
env_file: .env
|
||||
environment:
|
||||
- DATA_DIR=/app/data # Must match the volume mount below
|
||||
@@ -110,6 +112,30 @@ services:
|
||||
profiles:
|
||||
- host
|
||||
|
||||
# ── Profile: cliproxyapi (CLIProxyAPI as sidecar) ─────────────────
|
||||
cliproxyapi:
|
||||
container_name: cliproxyapi
|
||||
image: ghcr.io/router-for-me/cliproxyapi:v6.9.7
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "${CLIPROXYAPI_PORT:-8317}:${CLIPROXYAPI_PORT:-8317}"
|
||||
volumes:
|
||||
- cliproxyapi-data:/root/.cli-proxy-api
|
||||
environment:
|
||||
- PORT=${CLIPROXYAPI_PORT:-8317}
|
||||
- HOST=0.0.0.0
|
||||
healthcheck:
|
||||
test:
|
||||
["CMD", "wget", "--spider", "-q", "http://127.0.0.1:${CLIPROXYAPI_PORT:-8317}/v1/models"]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
start_period: 10s
|
||||
profiles:
|
||||
- cliproxyapi
|
||||
|
||||
volumes:
|
||||
omniroute-data:
|
||||
name: omniroute-data
|
||||
cliproxyapi-data:
|
||||
name: cliproxyapi-data
|
||||
|
||||
409
docs/I18N.md
Normal file
409
docs/I18N.md
Normal file
@@ -0,0 +1,409 @@
|
||||
# i18n — Internationalization Guide
|
||||
|
||||
OmniRoute supports **30 languages** with full dashboard UI translation, translated documentation, and RTL support for Arabic and Hebrew.
|
||||
|
||||
🌐 **Languages:** 🇺🇸 [English](../I18N.md) | 🇧🇷 [Português (Brasil)](./pt-BR/I18N.md) | 🇪🇸 [Español](./es/I18N.md) | 🇫🇷 [Français](./fr/I18N.md) | 🇩🇪 [Deutsch](./de/I18N.md) | 🇮🇹 [Italiano](./it/I18N.md) | 🇷🇺 [Русский](./ru/I18N.md) | 🇨🇳 [中文 (简体)](./zh-CN/I18N.md) | 🇯🇵 [日本語](./ja/I18N.md) | 🇰🇷 [한국어](./ko/I18N.md) | 🇸🇦 [العربية](./ar/I18N.md) | 🇮🇳 [हिन्दी](./hi/I18N.md) | 🇹🇭 [ไทย](./th/I18N.md) | 🇹🇷 [Türkçe](./tr/I18N.md) | 🇺🇦 [Українська](./uk-UA/I18N.md) | 🇻🇳 [Tiếng Việt](./vi/I18N.md) | 🇧🇬 [Български](./bg/I18N.md) | 🇩🇰 [Dansk](./da/I18N.md) | 🇫🇮 [Suomi](./fi/I18N.md) | 🇮🇱 [עברית](./he/I18N.md) | 🇭🇺 [Magyar](./hu/I18N.md) | 🇮🇩 [Bahasa Indonesia](./id/I18N.md) | 🇲🇾 [Bahasa Melayu](./ms/I18N.md) | 🇳🇱 [Nederlands](./nl/I18N.md) | 🇳🇴 [Norsk](./no/I18N.md) | 🇵🇹 [Português (Portugal)](./pt/I18N.md) | 🇷🇴 [Română](./ro/I18N.md) | 🇵🇱 [Polski](./pl/I18N.md) | 🇸🇰 [Slovenčina](./sk/I18N.md) | 🇸🇪 [Svenska](./sv/I18N.md) | 🇵🇭 [Filipino](./phi/I18N.md) | 🇨🇿 [Čeština](./cs/I18N.md)
|
||||
|
||||
## Quick Reference
|
||||
|
||||
| Task | Command |
|
||||
|------|---------|
|
||||
| Generate translations | `node scripts/i18n/generate-multilang.mjs messages` |
|
||||
| Translate docs (LLM) | `python3 scripts/i18n_autotranslate.py --api-url <url> --api-key <key> --model <model>` |
|
||||
| Validate a locale | `python3 scripts/validate_translation.py quick -l cs` |
|
||||
| Check code keys | `python3 scripts/check_translations.py` |
|
||||
| Generate QA report | `node scripts/i18n/generate-qa-checklist.mjs` |
|
||||
| Visual QA (Playwright) | `node scripts/i18n/run-visual-qa.mjs` |
|
||||
|
||||
## Architecture
|
||||
|
||||
### Source of Truth
|
||||
- **UI strings**: `src/i18n/messages/en.json` (English source, ~2800 keys)
|
||||
- **Locale files**: `src/i18n/messages/{locale}.json` (30 translations)
|
||||
- **Framework**: `next-intl` with cookie-based locale resolution
|
||||
- **Config**: `src/i18n/config.ts` — defines all 30 locales, language names, flags
|
||||
|
||||
### Runtime Flow
|
||||
1. User selects language → `NEXT_LOCALE` cookie set
|
||||
2. `src/i18n/request.ts` resolves locale: cookie → `Accept-Language` header → fallback `en`
|
||||
3. Dynamic import loads `messages/{locale}.json`
|
||||
4. Components use `useTranslations("namespace")` and `t("key")`
|
||||
|
||||
### Supported Locales
|
||||
|
||||
| Code | Language | RTL | Google Translate Code |
|
||||
|------|----------|-----|----------------------|
|
||||
| `ar` | العربية | Yes | `ar` |
|
||||
| `bg` | Български | No | `bg` |
|
||||
| `cs` | Čeština | No | `cs` |
|
||||
| `da` | Dansk | No | `da` |
|
||||
| `de` | Deutsch | No | `de` |
|
||||
| `es` | Español | No | `es` |
|
||||
| `fi` | Suomi | No | `fi` |
|
||||
| `fr` | Français | No | `fr` |
|
||||
| `he` | עברית | Yes | `iw` |
|
||||
| `hi` | हिन्दी | No | `hi` |
|
||||
| `hu` | Magyar | No | `hu` |
|
||||
| `id` | Bahasa Indonesia | No | `id` |
|
||||
| `it` | Italiano | No | `it` |
|
||||
| `ja` | 日本語 | No | `ja` |
|
||||
| `ko` | 한국어 | No | `ko` |
|
||||
| `ms` | Bahasa Melayu | No | `ms` |
|
||||
| `nl` | Nederlands | No | `nl` |
|
||||
| `no` | Norsk | No | `no` |
|
||||
| `phi` | Filipino | No | `tl` |
|
||||
| `pl` | Polski | No | `pl` |
|
||||
| `pt` | Português (Portugal) | No | `pt` |
|
||||
| `pt-BR` | Português (Brasil) | No | `pt` |
|
||||
| `ro` | Română | No | `ro` |
|
||||
| `ru` | Русский | No | `ru` |
|
||||
| `sk` | Slovenčina | No | `sk` |
|
||||
| `sv` | Svenska | No | `sv` |
|
||||
| `th` | ไทย | No | `th` |
|
||||
| `tr` | Türkçe | No | `tr` |
|
||||
| `uk-UA` | Українська | No | `uk` |
|
||||
| `vi` | Tiếng Việt | No | `vi` |
|
||||
| `zh-CN` | 中文 (简体) | No | `zh-CN` |
|
||||
|
||||
## Adding a New Language
|
||||
|
||||
### 1. Register the Locale
|
||||
Edit `src/i18n/config.ts`:
|
||||
```ts
|
||||
// Add to LOCALES array
|
||||
"xx",
|
||||
// Add to LANGUAGES array
|
||||
{ code: "xx", label: "XX", name: "Language Name", flag: "🏳️" },
|
||||
```
|
||||
|
||||
### 2. Add to Generator
|
||||
Edit `scripts/i18n/generate-multilang.mjs` — add entry to `LOCALE_SPECS`:
|
||||
```js
|
||||
{
|
||||
code: "xx",
|
||||
googleTl: "xx",
|
||||
label: "XX",
|
||||
flag: "🏳️",
|
||||
languageName: "Language Name",
|
||||
readmeName: "Language Name",
|
||||
docsName: "Language Name",
|
||||
},
|
||||
```
|
||||
|
||||
### 3. Generate Initial Translation
|
||||
```bash
|
||||
node scripts/i18n/generate-multilang.mjs messages
|
||||
```
|
||||
This creates `src/i18n/messages/xx.json` auto-translated from `en.json` via Google Translate.
|
||||
|
||||
### 4. Review & Fix Auto-Translations
|
||||
Auto-translations are a starting point. Review manually for:
|
||||
- Technical accuracy
|
||||
- Context-appropriate terminology
|
||||
- Proper handling of placeholders (`{count}`, `{value}`, etc.)
|
||||
|
||||
### 5. Validate
|
||||
```bash
|
||||
python3 scripts/validate_translation.py quick -l xx
|
||||
python3 scripts/validate_translation.py diff common -l xx
|
||||
```
|
||||
|
||||
### 6. Generate Translated Documentation
|
||||
```bash
|
||||
node scripts/i18n/generate-multilang.mjs docs
|
||||
```
|
||||
|
||||
## Auto-Translation Pipeline
|
||||
|
||||
### generate-multilang.mjs (Google Translate)
|
||||
|
||||
**Primary auto-translation engine** — uses Google Translate free API to generate translations for UI strings, READMEs, and documentation.
|
||||
|
||||
```bash
|
||||
node scripts/i18n/generate-multilang.mjs [messages|readme|docs|all]
|
||||
```
|
||||
|
||||
| Mode | What it does |
|
||||
|------|-------------|
|
||||
| `messages` | Translates missing keys in `src/i18n/messages/{locale}.json` from `en.json` |
|
||||
| `readme` | Translates `README.md` into all locales as `README.{code}.md` in project root |
|
||||
| `docs` | Translates `DOC_SOURCE_FILES` into `docs/i18n/{locale}/{docName}` |
|
||||
| `all` | Runs all three modes |
|
||||
|
||||
**Features:**
|
||||
- **Text protection**: Masks code blocks (```` ``` ````), inline code (`` ` ``), markdown links/images (`[text](url)`), HTML tags, tables, and ICU placeholders (`{count}`, `{value}`, `{total}`, etc.) before translation, then restores them
|
||||
- **Chunked batching**: Joins multiple strings with `__OMNIROUTE_I18N_SEPARATOR__` delimiters to minimize API calls (max 1800 chars per request)
|
||||
- **In-memory cache**: Avoids redundant API calls for repeated strings within a session
|
||||
- **Retry logic**: Exponential backoff (up to 5 attempts with 300ms × attempt delay) for 429/5xx errors
|
||||
- **Timeout**: 20 seconds per request
|
||||
- **Skip existing**: If target file already exists, it is NOT overwritten
|
||||
|
||||
**Important behaviors:**
|
||||
- `docs/i18n/README.md` is **regenerated** each run — it's an auto-generated index of all docs
|
||||
- Root `README.{code}.md` files are only created if they don't exist (skips locales in `EXISTING_README_CODES`)
|
||||
- Language bars (`🌐 **Languages:** ...`) are automatically inserted/updated in all translated docs
|
||||
|
||||
### i18n_autotranslate.py (LLM-based)
|
||||
|
||||
**Secondary translator** — uses any OpenAI-compatible LLM API (including OmniRoute itself) to translate existing `docs/i18n/` markdown files. Best for polishing or re-translating docs with better quality than Google Translate.
|
||||
|
||||
```bash
|
||||
python3 scripts/i18n_autotranslate.py \
|
||||
--api-url http://localhost:20128/v1 \
|
||||
--api-key sk-your-key \
|
||||
--model gpt-4o
|
||||
```
|
||||
|
||||
**Features:**
|
||||
- Scans `docs/i18n/` markdown files for English paragraphs
|
||||
- Skips code blocks, tables, and already-translated content
|
||||
- Sends paragraphs to LLM with technical translation system prompt
|
||||
- Supports all 30 languages
|
||||
|
||||
## Validation & QA
|
||||
|
||||
### validate_translation.py
|
||||
|
||||
**Translation validator** — compares any locale JSON against `en.json` and reports issues.
|
||||
|
||||
```bash
|
||||
# Quick check (counts only)
|
||||
python3 scripts/validate_translation.py quick -l cs
|
||||
# Output:
|
||||
# Missing: 0
|
||||
# Untranslated: 0
|
||||
# Ignored (UNTRANSLATABLE_KEYS): 236
|
||||
|
||||
# Detailed diff by category
|
||||
python3 scripts/validate_translation.py diff common -l cs
|
||||
python3 scripts/validate_translation.py diff settings -l cs
|
||||
|
||||
# Export to CSV
|
||||
python3 scripts/validate_translation.py csv -l cs > report.csv
|
||||
|
||||
# Export to Markdown
|
||||
python3 scripts/validate_translation.py md -l cs > report.md
|
||||
|
||||
# Full report (default)
|
||||
python3 scripts/validate_translation.py -l cs
|
||||
```
|
||||
|
||||
**Detects:**
|
||||
- **Missing keys** — keys in `en.json` but not in locale file
|
||||
- **Extra keys** — keys in locale file but not in `en.json`
|
||||
- **Untranslated keys** — keys where locale value equals English source (excluding allowlist)
|
||||
- **Placeholder mismatches** — ICU placeholders that don't match between source and translation
|
||||
|
||||
**Exit codes:**
|
||||
| Code | Meaning |
|
||||
|------|---------|
|
||||
| 0 | OK |
|
||||
| 1 | Generic error |
|
||||
| 2 | Missing strings (hard error) |
|
||||
| 3 | Untranslated warning (soft) |
|
||||
|
||||
**Environment:** Set `TRANSLATION_LANG=cs` or use `-l cs` flag.
|
||||
|
||||
### check_translations.py
|
||||
|
||||
**Code-to-JSON key checker** — scans `src/**/*.tsx` and `src/**/*.ts` for `useTranslations()` calls and verifies all referenced keys exist in `en.json`.
|
||||
|
||||
```bash
|
||||
# Basic check
|
||||
python3 scripts/check_translations.py
|
||||
|
||||
# Verbose output
|
||||
python3 scripts/check_translations.py --verbose
|
||||
|
||||
# Auto-fix (adds missing keys to en.json)
|
||||
python3 scripts/check_translations.py --fix
|
||||
```
|
||||
|
||||
### generate-qa-checklist.mjs
|
||||
|
||||
**Static analysis QA** — scans Next.js page files for i18n risk metrics and generates a Markdown report.
|
||||
|
||||
```bash
|
||||
node scripts/i18n/generate-qa-checklist.mjs
|
||||
```
|
||||
|
||||
**Checks:**
|
||||
- Fixed-width class usage (overflow risk)
|
||||
- Directional left/right classes (RTL risk)
|
||||
- Clipping-prone patterns
|
||||
- Locale parity (missing/extra keys vs `en.json`)
|
||||
- README language selector bars in priority locales (`es`, `fr`, `de`, `ja`, `ar`)
|
||||
|
||||
**Output:** `docs/reports/i18n-qa-checklist-{date}.md`
|
||||
|
||||
### run-visual-qa.mjs
|
||||
|
||||
**Visual QA via Playwright** — takes screenshots of all dashboard routes in multiple locales and viewports, then evaluates page health.
|
||||
|
||||
```bash
|
||||
# Default: es, fr, de, ja, ar on localhost:20128
|
||||
node scripts/i18n/run-visual-qa.mjs
|
||||
|
||||
# Custom base URL and locales
|
||||
QA_BASE_URL=http://staging.example.com QA_LOCALES=de,fr node scripts/i18n/run-visual-qa.mjs
|
||||
|
||||
# Custom routes
|
||||
QA_ROUTES=/dashboard/settings,/dashboard/providers node scripts/i18n/run-visual-qa.mjs
|
||||
```
|
||||
|
||||
**Detects:**
|
||||
- Text overflow
|
||||
- Element clipping
|
||||
- RTL layout mismatches
|
||||
|
||||
**Output:** `docs/reports/i18n-visual-qa-{date}.md` + JSON report
|
||||
|
||||
## Managing Untranslatable Keys
|
||||
|
||||
### untranslatable-keys.json
|
||||
|
||||
**File:** `scripts/i18n/untranslatable-keys.json`
|
||||
|
||||
Allowlist of keys that should remain identical to English source. Used by `validate_translation.py` to avoid false-positive "untranslated" warnings.
|
||||
|
||||
```json
|
||||
{
|
||||
"description": "Keys that should remain untranslated...",
|
||||
"keys": [
|
||||
"common.model",
|
||||
"common.oauth",
|
||||
"health.cpu",
|
||||
...
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**What belongs here:**
|
||||
- Brand/product names: `landing.brandName`, `common.social-github`
|
||||
- Technical terms/acronyms: `health.cpu`, `mcpDashboard.pid`, `settings.ai`
|
||||
- ICU/format strings: `apiManager.modelsCount`, `health.millisecondsShort`
|
||||
- Placeholder values: `providers.openaiBaseUrlPlaceholder`, `cliTools.baseUrlPlaceholder`
|
||||
- Protocol names: `common.http`, `common.oauth`, `providers.oauth2Label`
|
||||
- Navigation sections: `sidebar.primarySection`, `sidebar.cliSection`
|
||||
|
||||
**To add a key:** Edit the `keys` array in `scripts/i18n/untranslatable-keys.json` and re-run validation.
|
||||
|
||||
## CI Integration
|
||||
|
||||
### GitHub Actions (`.github/workflows/ci.yml`)
|
||||
|
||||
The CI pipeline validates all locales on every push and PR:
|
||||
|
||||
1. **`i18n-matrix` job** — dynamically discovers all locale files (excluding `en.json`)
|
||||
2. **`i18n` job** — runs `validate_translation.py quick -l '<lang>'` for each locale in parallel
|
||||
3. **`ci-summary` job** — aggregates results into a dashboard summary
|
||||
|
||||
```yaml
|
||||
# i18n-matrix: discovers languages
|
||||
LANGS=$(ls src/i18n/messages/*.json | xargs -n1 basename | sed 's/.json$//' | grep -v '^en$')
|
||||
|
||||
# i18n: validates each language
|
||||
python3 scripts/validate_translation.py quick -l '${{ matrix.lang }}'
|
||||
```
|
||||
|
||||
**Dashboard output:**
|
||||
```
|
||||
## 🌍 Translations
|
||||
| Metric | Value |
|
||||
|--------|------|
|
||||
| Languages checked | 30 |
|
||||
| Total untranslated | 0 |
|
||||
|
||||
✅ All translations complete
|
||||
```
|
||||
|
||||
## File Structure
|
||||
|
||||
```
|
||||
src/i18n/
|
||||
├── config.ts # Locale definitions (30 locales, RTL config)
|
||||
├── request.ts # Runtime locale resolution
|
||||
└── messages/
|
||||
├── en.json # Source of truth (~2800 keys)
|
||||
├── cs.json # Czech translation
|
||||
├── de.json # German translation
|
||||
└── ... # 30 locale files total
|
||||
|
||||
scripts/
|
||||
├── i18n/
|
||||
│ ├── generate-multilang.mjs # Auto-translation engine (Google Translate, 888 lines)
|
||||
│ ├── generate-qa-checklist.mjs # Static analysis QA
|
||||
│ ├── run-visual-qa.mjs # Playwright visual QA
|
||||
│ └── untranslatable-keys.json # Allowlist for validation (236 keys)
|
||||
├── validate_translation.py # Translation validator
|
||||
├── check_translations.py # Code-to-JSON key checker
|
||||
└── i18n_autotranslate.py # LLM-based doc translator
|
||||
|
||||
.github/workflows/
|
||||
└── ci.yml # i18n validation in CI matrix
|
||||
|
||||
docs/
|
||||
├── I18N.md # This file — i18n toolchain documentation
|
||||
├── i18n/
|
||||
│ ├── README.md # Auto-generated language index
|
||||
│ ├── cs/ # Czech docs
|
||||
│ │ └── docs/
|
||||
│ │ ├── I18N.md # Czech translation of this file
|
||||
│ │ └── ...
|
||||
│ ├── de/ # German docs
|
||||
│ └── ... # 30 locale directories
|
||||
└── reports/
|
||||
├── i18n-qa-checklist-*.md # Static analysis reports
|
||||
└── i18n-visual-qa-*.md # Visual QA reports
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### When Editing Translations
|
||||
1. **Always edit `en.json` first** — it's the source of truth
|
||||
2. **Run `generate-multilang.mjs messages`** to propagate new keys to all locales
|
||||
3. **Review auto-translations** — Google Translate is a starting point, not final
|
||||
4. **Validate before committing** — `python3 scripts/validate_translation.py quick -l <lang>`
|
||||
5. **Update `untranslatable-keys.json`** if a key should remain in English
|
||||
|
||||
### Placeholder Safety
|
||||
- ICU placeholders (`{count}`, `{value}`, `{total}`, `{seconds}`) must be preserved exactly
|
||||
- Plural formats (`{count, plural, one {# model} other {# models}}`) must maintain structure
|
||||
- The validator detects placeholder mismatches automatically
|
||||
|
||||
### Adding New Translation Keys in Code
|
||||
```tsx
|
||||
// Use namespaced keys
|
||||
const t = useTranslations("settings");
|
||||
t("cacheSettings"); // maps to settings.cacheSettings in JSON
|
||||
|
||||
// Run check_translations.py to verify keys exist
|
||||
python3 scripts/check_translations.py --verbose
|
||||
```
|
||||
|
||||
### RTL Considerations
|
||||
- Arabic (`ar`) and Hebrew (`he`) are RTL locales
|
||||
- Avoid hardcoded `left`/`right` CSS — use `start`/`end` logical properties
|
||||
- Visual QA catches RTL layout mismatches via `run-visual-qa.mjs`
|
||||
|
||||
## Known Issues & History
|
||||
|
||||
### `in.json` → `hi.json` Fix
|
||||
The generator originally used `code: "in"` (deprecated Google Translate code) for Hindi instead of the correct ISO 639-1 `hi`. This created an orphaned `in.json` duplicate of `hi.json`. Fixed by changing `code: "in"` to `code: "hi"` in `generate-multilang.mjs` and removing the orphaned file.
|
||||
|
||||
### `docs/i18n/README.md` Is Auto-Generated
|
||||
The `docs/i18n/README.md` file is completely regenerated by `generate-multilang.mjs docs`. Any manual edits will be lost. Use `docs/I18N.md` (this file) for hand-written documentation that should persist.
|
||||
|
||||
### External Untranslatable Keys List
|
||||
The `untranslatable-keys.json` allowlist was moved from an inline Python set in `validate_translation.py` to an external JSON file for easier maintenance. The validator loads it at runtime.
|
||||
|
||||
### `generate-multilang.mjs` Hindi Code Fix
|
||||
The generator originally used `code: "in"` (deprecated Google Translate code) for Hindi instead of the correct ISO 639-1 `hi`. This was introduced in upstream commit `952b0b22c` by `diegosouzapw`. Fixed by changing `code: "in"` to `code: "hi"` in the `LOCALE_SPECS` array and removing the orphaned `in.json` file.
|
||||
|
||||
### `validate_translation.py` Ignored Count Output
|
||||
The `quick` check now displays the count of ignored keys from `untranslatable-keys.json`:
|
||||
```
|
||||
Missing: 0
|
||||
Untranslated: 0
|
||||
Ignored (UNTRANSLATABLE_KEYS): 236
|
||||
```
|
||||
@@ -164,7 +164,7 @@ Dashboard → Providers → Connect GitHub
|
||||
Models:
|
||||
gh/gpt-5
|
||||
gh/claude-4.5-sonnet
|
||||
gh/gemini-3-pro
|
||||
gh/gemini-3.1-pro-preview
|
||||
```
|
||||
|
||||
### 💰 Cheap Providers
|
||||
@@ -525,6 +525,7 @@ post_install() {
|
||||
| `ENABLE_REQUEST_LOGS` | `false` | Enables request/response logs |
|
||||
| `AUTH_COOKIE_SECURE` | `false` | Force `Secure` auth cookie (behind HTTPS reverse proxy) |
|
||||
| `CLOUDFLARED_BIN` | unset | Use an existing `cloudflared` binary instead of managed download |
|
||||
| `CLOUDFLARED_PROTOCOL` | `http2` | Transport for managed Quick Tunnels (`http2`, `quic`, or `auto`) |
|
||||
| `OMNIROUTE_MEMORY_MB` | `512` | Node.js heap limit in MB |
|
||||
| `PROMPT_CACHE_MAX_SIZE` | `50` | Max prompt cache entries |
|
||||
| `SEMANTIC_CACHE_MAX_SIZE` | `100` | Max semantic cache entries |
|
||||
@@ -652,7 +653,10 @@ Returns models grouped by provider with types (`chat`, `embedding`, `image`).
|
||||
- Available in **Dashboard → Endpoints** for Docker and other self-hosted deployments
|
||||
- Creates a temporary `https://*.trycloudflare.com` URL that forwards to your current OpenAI-compatible `/v1` endpoint
|
||||
- First enable installs `cloudflared` only when needed; later restarts reuse the same managed binary
|
||||
- Quick Tunnels are not auto-restored after an OmniRoute or container restart; re-enable them from the dashboard when needed
|
||||
- Tunnel URLs are ephemeral and change every time you stop/start the tunnel
|
||||
- Managed Quick Tunnels default to HTTP/2 transport to avoid noisy QUIC UDP buffer warnings in constrained containers
|
||||
- Set `CLOUDFLARED_PROTOCOL=quic` or `auto` if you want to override the managed transport choice
|
||||
- Set `CLOUDFLARED_BIN` if you prefer using a preinstalled `cloudflared` binary instead of the managed download
|
||||
|
||||
### LLM Gateway Intelligence (Phase 9)
|
||||
|
||||
@@ -213,8 +213,8 @@ server {
|
||||
# SSE (Server-Sent Events) — streaming AI responses
|
||||
proxy_buffering off;
|
||||
proxy_cache off;
|
||||
proxy_read_timeout 300s;
|
||||
proxy_send_timeout 300s;
|
||||
proxy_read_timeout 600s;
|
||||
proxy_send_timeout 600s;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -228,6 +228,10 @@ server {
|
||||
NGINX
|
||||
```
|
||||
|
||||
Keep reverse-proxy stream timeouts aligned with your OmniRoute timeout env vars. If you raise
|
||||
`FETCH_TIMEOUT_MS` / `STREAM_IDLE_TIMEOUT_MS`, raise `proxy_read_timeout` / `proxy_send_timeout`
|
||||
above the same threshold.
|
||||
|
||||
### 3.3 Enable and Test
|
||||
|
||||
```bash
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
openapi: 3.1.0
|
||||
info:
|
||||
title: OmniRoute API
|
||||
version: 3.4.4
|
||||
version: 3.4.7
|
||||
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,6 +1,6 @@
|
||||
{
|
||||
"name": "omniroute-desktop",
|
||||
"version": "3.4.4",
|
||||
"version": "3.4.7",
|
||||
"description": "OmniRoute Desktop Application",
|
||||
"main": "main.js",
|
||||
"author": {
|
||||
|
||||
4
llm.txt
4
llm.txt
@@ -8,7 +8,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
|
||||
**Key value:** One endpoint (`http://localhost:20128/v1`), unlimited models, zero downtime, minimal cost.
|
||||
|
||||
**Current version:** 3.4.3
|
||||
**Current version:** 3.4.6
|
||||
|
||||
## Tech Stack
|
||||
|
||||
@@ -277,7 +277,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
└── .env.example # Environment variable template
|
||||
```
|
||||
|
||||
## Key Features (v3.4.3)
|
||||
## Key Features (v3.4.6)
|
||||
|
||||
### Core Proxy
|
||||
- **60+ AI providers** with automatic format translation
|
||||
|
||||
@@ -1,18 +1,22 @@
|
||||
import { getUpstreamTimeoutConfig } from "@/shared/utils/runtimeTimeouts";
|
||||
import { loadProviderCredentials } from "./credentialLoader.ts";
|
||||
import { generateLegacyProviders } from "./providerRegistry.ts";
|
||||
|
||||
const upstreamTimeouts = getUpstreamTimeoutConfig(process.env, (message) => {
|
||||
console.warn(`[open-sse] ${message}`);
|
||||
});
|
||||
|
||||
// Timeout for non-streaming fetch requests (ms). Prevents stalled connections.
|
||||
export const FETCH_TIMEOUT_MS = parseInt(process.env.FETCH_TIMEOUT_MS || "600000", 10);
|
||||
export const FETCH_TIMEOUT_MS = upstreamTimeouts.fetchTimeoutMs;
|
||||
|
||||
// Idle timeout for SSE streams (ms). Closes stream if no data for this duration.
|
||||
// Default: 120s balances deep-reasoning pauses with fast zombie stream detection (#473).
|
||||
// Extended-thinking models rarely pause >90s between chunks. Override with STREAM_IDLE_TIMEOUT_MS env var.
|
||||
export const STREAM_IDLE_TIMEOUT_MS = parseInt(process.env.STREAM_IDLE_TIMEOUT_MS || "600000", 10);
|
||||
export const STREAM_IDLE_TIMEOUT_MS = upstreamTimeouts.streamIdleTimeoutMs;
|
||||
|
||||
// Provider configurations
|
||||
// OAuth credentials read from env vars with hardcoded fallbacks for backward compatibility.
|
||||
// Use provider-credentials.json or env vars to override in production.
|
||||
import { generateLegacyProviders } from "./providerRegistry.ts";
|
||||
|
||||
export const PROVIDERS = generateLegacyProviders();
|
||||
|
||||
// Merge external credentials from data/provider-credentials.json (if present)
|
||||
@@ -44,8 +48,8 @@ export const OAUTH_ENDPOINTS = {
|
||||
auth: "https://chat.qwen.ai/api/v1/oauth2/device/code", // From CLIProxyAPI
|
||||
},
|
||||
qoder: {
|
||||
token: "https://qoder.cn/oauth/token",
|
||||
auth: "https://qoder.cn/oauth",
|
||||
token: process.env.QODER_OAUTH_TOKEN_URL || "",
|
||||
auth: process.env.QODER_OAUTH_AUTHORIZE_URL || "",
|
||||
},
|
||||
github: {
|
||||
token: "https://github.com/login/oauth/access_token",
|
||||
|
||||
@@ -192,22 +192,9 @@ export const REGISTRY: Record<string, RegistryEntry> = {
|
||||
clientSecretEnv: "GEMINI_OAUTH_CLIENT_SECRET",
|
||||
clientSecretDefault: "",
|
||||
},
|
||||
models: [
|
||||
{ id: "gemini-3.1-pro-high", name: "Gemini 3.1 Pro High" },
|
||||
{ id: "gemini-3.1-pro-low", name: "Gemini 3.1 Pro Low" },
|
||||
{ id: "gemini-3.1-pro", name: "Gemini 3.1 Pro" },
|
||||
{ id: "gemini-3-1-pro", name: "Gemini 3.1 Pro (Alt ID)" },
|
||||
{ id: "gemini-3.1-pro-preview", name: "Gemini 3.1 Pro Preview" },
|
||||
{ id: "gemini-3.1-flash-lite-preview", name: "Gemini 3.1 Flash Lite Preview" },
|
||||
{ id: "gemini-3-flash-preview", name: "Gemini 3 Flash Preview" },
|
||||
{ id: "gemini-2.5-pro", name: "Gemini 2.5 Pro" },
|
||||
{ id: "gemini-2.5-flash", name: "Gemini 2.5 Flash" },
|
||||
{ id: "gemini-2.5-flash-lite", name: "Gemini 2.5 Flash Lite" },
|
||||
{ id: "gemini-2.0-flash", name: "Gemini 2.0 Flash" },
|
||||
{ id: "gemini-2.0-flash-exp", name: "Gemini 2.0 Flash Exp" },
|
||||
{ id: "gemini-1.5-pro", name: "Gemini 1.5 Pro" },
|
||||
{ id: "gemini-1.5-flash", name: "Gemini 1.5 Flash" },
|
||||
],
|
||||
models: [],
|
||||
// Models are populated from Google's API via sync-models (per API key).
|
||||
// No hardcoded fallback — show nothing until a key is added.
|
||||
},
|
||||
|
||||
"gemini-cli": {
|
||||
@@ -325,19 +312,17 @@ export const REGISTRY: Record<string, RegistryEntry> = {
|
||||
alias: "if",
|
||||
format: "openai",
|
||||
executor: "qoder",
|
||||
baseUrl: "https://apis.qoder.cn/v1/chat/completions",
|
||||
authType: "oauth",
|
||||
baseUrl: "https://api.qoder.com/v1/chat/completions",
|
||||
authType: "apikey",
|
||||
authHeader: "bearer",
|
||||
headers: {
|
||||
"User-Agent": "Qoder-Cli",
|
||||
},
|
||||
oauth: {
|
||||
clientIdEnv: "QODER_OAUTH_CLIENT_ID",
|
||||
clientIdDefault: "10009311001",
|
||||
clientSecretEnv: "QODER_OAUTH_CLIENT_SECRET",
|
||||
clientSecretDefault: "",
|
||||
tokenUrl: "https://qoder.cn/oauth/token",
|
||||
authUrl: "https://qoder.cn/oauth",
|
||||
tokenUrl: process.env.QODER_OAUTH_TOKEN_URL || "",
|
||||
authUrl: process.env.QODER_OAUTH_AUTHORIZE_URL || "",
|
||||
},
|
||||
models: [
|
||||
{ id: "qoder-rome-30ba3b", name: "Qoder ROME" },
|
||||
@@ -439,6 +424,7 @@ export const REGISTRY: Record<string, RegistryEntry> = {
|
||||
{ id: "claude-opus-4-5-20251101", name: "Claude Opus 4.5 (Full ID)" },
|
||||
{ id: "claude-sonnet-4", name: "Claude Sonnet 4" },
|
||||
{ id: "claude-sonnet-4.5", name: "Claude Sonnet 4.5" },
|
||||
{ id: "gemini-3.1-pro-preview", name: "Gemini 3.1 Pro Preview" },
|
||||
{ id: "gemini-2.5-pro", name: "Gemini 2.5 Pro" },
|
||||
{ id: "gemini-2.5-flash", name: "Gemini 2.5 Flash" },
|
||||
{ id: "grok-code-fast-1", name: "Grok Code Fast 1" },
|
||||
@@ -1556,7 +1542,9 @@ const _passthroughProviderIds: Set<string> | null = (() => {
|
||||
if (entry.passthroughModels) ids.add(entry.id);
|
||||
}
|
||||
return ids;
|
||||
} catch { return null; }
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
})();
|
||||
|
||||
export function getPassthroughProviders(): Set<string> {
|
||||
|
||||
@@ -75,7 +75,7 @@ export function mergeUpstreamExtraHeaders(
|
||||
}
|
||||
}
|
||||
|
||||
function mergeAbortSignals(primary: AbortSignal, secondary: AbortSignal): AbortSignal {
|
||||
export function mergeAbortSignals(primary: AbortSignal, secondary: AbortSignal): AbortSignal {
|
||||
const controller = new AbortController();
|
||||
|
||||
const abortBoth = () => {
|
||||
|
||||
95
open-sse/executors/cliproxyapi.ts
Normal file
95
open-sse/executors/cliproxyapi.ts
Normal file
@@ -0,0 +1,95 @@
|
||||
import { BaseExecutor, mergeUpstreamExtraHeaders, mergeAbortSignals } from "./base.ts";
|
||||
import { HTTP_STATUS, FETCH_TIMEOUT_MS } from "../config/constants.ts";
|
||||
|
||||
const DEFAULT_PORT = 8317;
|
||||
const DEFAULT_HOST = "127.0.0.1";
|
||||
|
||||
function resolveCliproxyapiBaseUrl(): string {
|
||||
const host = process.env.CLIPROXYAPI_HOST || DEFAULT_HOST;
|
||||
const port = parseInt(process.env.CLIPROXYAPI_PORT || String(DEFAULT_PORT), 10);
|
||||
return `http://${host}:${port}`;
|
||||
}
|
||||
|
||||
export { resolveCliproxyapiBaseUrl };
|
||||
|
||||
export class CliproxyapiExecutor extends BaseExecutor {
|
||||
private readonly upstreamBaseUrl: string;
|
||||
|
||||
constructor(baseUrl?: string) {
|
||||
const effectiveBase = baseUrl ?? resolveCliproxyapiBaseUrl();
|
||||
super("cliproxyapi", {
|
||||
id: "cliproxyapi",
|
||||
baseUrl: effectiveBase + "/v1/chat/completions",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
this.upstreamBaseUrl = effectiveBase;
|
||||
}
|
||||
|
||||
buildUrl(_model: string, _stream: boolean, _urlIndex = 0): string {
|
||||
return `${this.upstreamBaseUrl}/v1/chat/completions`;
|
||||
}
|
||||
|
||||
buildHeaders(credentials: any, stream = true): Record<string, string> {
|
||||
const headers: Record<string, string> = {
|
||||
"Content-Type": "application/json",
|
||||
};
|
||||
|
||||
const key = credentials?.apiKey || credentials?.accessToken;
|
||||
if (key) {
|
||||
headers["Authorization"] = `Bearer ${key}`;
|
||||
}
|
||||
|
||||
if (stream) {
|
||||
headers["Accept"] = "text/event-stream";
|
||||
}
|
||||
|
||||
return headers;
|
||||
}
|
||||
|
||||
transformRequest(model: string, body: any, _stream: boolean, _credentials: any): any {
|
||||
if (body && typeof body === "object" && body.model !== model) {
|
||||
return { ...body, model };
|
||||
}
|
||||
return body;
|
||||
}
|
||||
|
||||
async execute(input: {
|
||||
model: string;
|
||||
body: unknown;
|
||||
stream: boolean;
|
||||
credentials: any;
|
||||
signal?: AbortSignal | null;
|
||||
log?: any;
|
||||
upstreamExtraHeaders?: Record<string, string> | null;
|
||||
}) {
|
||||
const url = this.buildUrl(input.model, input.stream);
|
||||
const headers = this.buildHeaders(input.credentials, input.stream);
|
||||
const transformedBody = this.transformRequest(
|
||||
input.model,
|
||||
input.body,
|
||||
input.stream,
|
||||
input.credentials
|
||||
);
|
||||
mergeUpstreamExtraHeaders(headers, input.upstreamExtraHeaders);
|
||||
|
||||
const timeoutSignal = AbortSignal.timeout(FETCH_TIMEOUT_MS);
|
||||
const combinedSignal = input.signal
|
||||
? mergeAbortSignals(input.signal, timeoutSignal)
|
||||
: timeoutSignal;
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: "POST",
|
||||
headers,
|
||||
body: JSON.stringify(transformedBody),
|
||||
signal: combinedSignal,
|
||||
});
|
||||
|
||||
if (response.status === HTTP_STATUS.RATE_LIMITED) {
|
||||
input.log?.warn?.("CPA", `CLIProxyAPI rate limited: ${response.status}`);
|
||||
}
|
||||
|
||||
return { response, url, headers, transformedBody };
|
||||
}
|
||||
}
|
||||
|
||||
export default CliproxyapiExecutor;
|
||||
@@ -55,6 +55,19 @@ export class GithubExecutor extends BaseExecutor {
|
||||
);
|
||||
delete modifiedBody.response_format;
|
||||
}
|
||||
|
||||
// Strip reasoning_text / reasoning_content from assistant messages.
|
||||
// GitHub Copilot converts these into Anthropic thinking blocks but cannot
|
||||
// supply a valid `signature`, causing upstream 400 errors.
|
||||
if (Array.isArray(modifiedBody.messages)) {
|
||||
for (const msg of modifiedBody.messages) {
|
||||
if (msg.role === "assistant") {
|
||||
delete msg.reasoning_text;
|
||||
delete msg.reasoning_content;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return modifiedBody;
|
||||
}
|
||||
|
||||
@@ -63,7 +76,12 @@ export class GithubExecutor extends BaseExecutor {
|
||||
if (!result || !result.response?.body) return result;
|
||||
|
||||
const isStreaming = input.stream === true;
|
||||
if (isStreaming) {
|
||||
const contentType = (result.response.headers.get("content-type") || "").toLowerCase();
|
||||
if (isStreaming && result.response.ok && contentType.includes("text/event-stream")) {
|
||||
// Preserve the original response body for downstream error handling.
|
||||
const sourceResponse = result.response.clone();
|
||||
if (!sourceResponse.body) return result;
|
||||
|
||||
const decoder = new TextDecoder();
|
||||
const transformStream = new TransformStream({
|
||||
transform(chunk, controller) {
|
||||
@@ -75,10 +93,10 @@ export class GithubExecutor extends BaseExecutor {
|
||||
},
|
||||
});
|
||||
|
||||
const newResponse = new Response(result.response.body.pipeThrough(transformStream), {
|
||||
status: result.response.status,
|
||||
statusText: result.response.statusText,
|
||||
headers: result.response.headers, // Headers class carries over correctly
|
||||
const newResponse = new Response(sourceResponse.body.pipeThrough(transformStream), {
|
||||
status: sourceResponse.status,
|
||||
statusText: sourceResponse.statusText,
|
||||
headers: new Headers(sourceResponse.headers),
|
||||
});
|
||||
result.response = newResponse;
|
||||
}
|
||||
@@ -167,6 +185,10 @@ export class GithubExecutor extends BaseExecutor {
|
||||
...githubTokens,
|
||||
copilotToken: copilotResult.token,
|
||||
copilotTokenExpiresAt: copilotResult.expiresAt,
|
||||
providerSpecificData: {
|
||||
copilotToken: copilotResult.token,
|
||||
copilotTokenExpiresAt: copilotResult.expiresAt,
|
||||
},
|
||||
};
|
||||
}
|
||||
return githubTokens;
|
||||
@@ -179,6 +201,10 @@ export class GithubExecutor extends BaseExecutor {
|
||||
refreshToken: credentials.refreshToken,
|
||||
copilotToken: copilotResult.token,
|
||||
copilotTokenExpiresAt: copilotResult.expiresAt,
|
||||
providerSpecificData: {
|
||||
copilotToken: copilotResult.token,
|
||||
copilotTokenExpiresAt: copilotResult.expiresAt,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ import { CloudflareAIExecutor } from "./cloudflare-ai.ts";
|
||||
import { OpencodeExecutor } from "./opencode.ts";
|
||||
import { PuterExecutor } from "./puter.ts";
|
||||
import { VertexExecutor } from "./vertex.ts";
|
||||
import { CliproxyapiExecutor } from "./cliproxyapi.ts";
|
||||
|
||||
const executors = {
|
||||
antigravity: new AntigravityExecutor(),
|
||||
@@ -30,6 +31,8 @@ const executors = {
|
||||
puter: new PuterExecutor(),
|
||||
pu: new PuterExecutor(), // Alias
|
||||
vertex: new VertexExecutor(),
|
||||
cliproxyapi: new CliproxyapiExecutor(),
|
||||
cpa: new CliproxyapiExecutor(), // Alias
|
||||
};
|
||||
|
||||
const defaultCache = new Map();
|
||||
@@ -57,4 +60,5 @@ export { PollinationsExecutor } from "./pollinations.ts";
|
||||
export { CloudflareAIExecutor } from "./cloudflare-ai.ts";
|
||||
export { OpencodeExecutor } from "./opencode.ts";
|
||||
export { PuterExecutor } from "./puter.ts";
|
||||
export { CliproxyapiExecutor } from "./cliproxyapi.ts";
|
||||
export { VertexExecutor } from "./vertex.ts";
|
||||
|
||||
@@ -1,113 +1,295 @@
|
||||
import crypto from "crypto";
|
||||
import { BaseExecutor } from "./base.ts";
|
||||
import { spawn } from "child_process";
|
||||
import {
|
||||
BaseExecutor,
|
||||
mergeUpstreamExtraHeaders,
|
||||
type ExecuteInput,
|
||||
type ProviderCredentials,
|
||||
} from "./base.ts";
|
||||
import { PROVIDERS } from "../config/constants.ts";
|
||||
import {
|
||||
buildQoderChunk,
|
||||
buildQoderCompletionPayload,
|
||||
buildQoderPrompt,
|
||||
createQoderErrorResponse,
|
||||
extractTextFromQoderEnvelope,
|
||||
getQoderCliCommand,
|
||||
getQoderCliWorkspace,
|
||||
mapQoderModelToLevel,
|
||||
parseQoderCliFailure,
|
||||
runQoderCliCommand,
|
||||
} from "../services/qoderCli.ts";
|
||||
|
||||
type QoderCredentials = {
|
||||
apiKey?: string;
|
||||
accessToken?: string;
|
||||
};
|
||||
function getPat(credentials: ProviderCredentials): string {
|
||||
if (typeof credentials.apiKey === "string" && credentials.apiKey.trim()) {
|
||||
return credentials.apiKey.trim();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* QoderExecutor - Executor for Qoder API with HMAC-SHA256 signature.
|
||||
*
|
||||
* Qoder requires custom headers including a session ID, timestamp,
|
||||
* and an HMAC-SHA256 signature for request authentication.
|
||||
* Without these headers, the API returns a 406 error.
|
||||
*
|
||||
* Fixes: https://github.com/diegosouzapw/OmniRoute/issues/114
|
||||
*/
|
||||
export class QoderExecutor extends BaseExecutor {
|
||||
constructor() {
|
||||
super("qoder", PROVIDERS.qoder);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create Qoder signature using HMAC-SHA256
|
||||
* @param userAgent - User agent string
|
||||
* @param sessionID - Session ID
|
||||
* @param timestamp - Unix timestamp in milliseconds
|
||||
* @param apiKey - API key for signing
|
||||
* @returns Hex-encoded signature
|
||||
*/
|
||||
createQoderSignature(
|
||||
userAgent: string,
|
||||
sessionID: string,
|
||||
timestamp: number,
|
||||
apiKey: string
|
||||
): string {
|
||||
if (!apiKey) return "";
|
||||
const payload = `${userAgent}:${sessionID}:${timestamp}`;
|
||||
const hmac = crypto.createHmac("sha256", apiKey);
|
||||
hmac.update(payload);
|
||||
return hmac.digest("hex");
|
||||
}
|
||||
|
||||
/**
|
||||
* Build headers with Qoder-specific HMAC-SHA256 signature.
|
||||
* Includes session-id, x-qoder-timestamp, and x-qoder-signature.
|
||||
*/
|
||||
buildHeaders(credentials: QoderCredentials, stream = true) {
|
||||
// Generate session ID and timestamp
|
||||
const sessionID = `session-${crypto.randomUUID()}`;
|
||||
const timestamp = Date.now();
|
||||
|
||||
// Get user agent from config
|
||||
const userAgent = this.config.headers?.["User-Agent"] || "Qoder-Cli";
|
||||
|
||||
// Get API key (prefer apiKey, fallback to accessToken)
|
||||
const apiKey = credentials.apiKey || credentials.accessToken || "";
|
||||
|
||||
// Create HMAC-SHA256 signature
|
||||
const signature = this.createQoderSignature(userAgent, sessionID, timestamp, apiKey);
|
||||
|
||||
// Build headers
|
||||
const headers: Record<string, string> = {
|
||||
buildHeaders(_credentials: ProviderCredentials, stream = true): Record<string, string> {
|
||||
return {
|
||||
"Content-Type": "application/json",
|
||||
...this.config.headers,
|
||||
"session-id": sessionID,
|
||||
"x-qoder-timestamp": timestamp.toString(),
|
||||
"x-qoder-signature": signature,
|
||||
...(stream ? { Accept: "text/event-stream" } : {}),
|
||||
};
|
||||
|
||||
// Add authorization
|
||||
if (credentials.apiKey) {
|
||||
headers["Authorization"] = `Bearer ${credentials.apiKey}`;
|
||||
} else if (credentials.accessToken) {
|
||||
headers["Authorization"] = `Bearer ${credentials.accessToken}`;
|
||||
}
|
||||
|
||||
// Add streaming header
|
||||
if (stream) {
|
||||
headers["Accept"] = "text/event-stream";
|
||||
}
|
||||
|
||||
return headers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build URL for Qoder API — uses baseUrl directly.
|
||||
*/
|
||||
buildUrl(
|
||||
model: string,
|
||||
stream: boolean,
|
||||
urlIndex = 0,
|
||||
credentials: QoderCredentials | null = null
|
||||
) {
|
||||
void model;
|
||||
void stream;
|
||||
void urlIndex;
|
||||
void credentials;
|
||||
return this.config.baseUrl;
|
||||
}
|
||||
async execute({
|
||||
model,
|
||||
body,
|
||||
stream,
|
||||
credentials,
|
||||
signal,
|
||||
upstreamExtraHeaders,
|
||||
}: ExecuteInput) {
|
||||
const headers = this.buildHeaders(credentials, stream);
|
||||
mergeUpstreamExtraHeaders(headers, upstreamExtraHeaders);
|
||||
|
||||
/**
|
||||
* Transform request body (passthrough for Qoder).
|
||||
*/
|
||||
transformRequest(model: string, body: unknown, stream: boolean, credentials: QoderCredentials) {
|
||||
void model;
|
||||
void stream;
|
||||
void credentials;
|
||||
return body;
|
||||
const pat = getPat(credentials);
|
||||
if (!pat) {
|
||||
return {
|
||||
response: createQoderErrorResponse({
|
||||
status: 400,
|
||||
message:
|
||||
"Qoder Personal Access Token is required. Connect Qoder with a PAT via qodercli transport.",
|
||||
code: "pat_required",
|
||||
}),
|
||||
url: "qodercli://local",
|
||||
headers,
|
||||
transformedBody: body,
|
||||
};
|
||||
}
|
||||
|
||||
const prompt = buildQoderPrompt(body);
|
||||
const workspace = getQoderCliWorkspace();
|
||||
const command = getQoderCliCommand();
|
||||
|
||||
if (!stream) {
|
||||
const result = await runQoderCliCommand({
|
||||
token: pat,
|
||||
prompt,
|
||||
stream: false,
|
||||
model,
|
||||
workspace,
|
||||
command,
|
||||
signal,
|
||||
});
|
||||
|
||||
if (!result.ok) {
|
||||
const failure = parseQoderCliFailure(result.stderr || result.error || "", result.stdout);
|
||||
return {
|
||||
response: createQoderErrorResponse(failure),
|
||||
url: "qodercli://local",
|
||||
headers,
|
||||
transformedBody: body,
|
||||
};
|
||||
}
|
||||
|
||||
let assistantText = result.stdout.trim();
|
||||
try {
|
||||
const parsed = JSON.parse(assistantText);
|
||||
assistantText = extractTextFromQoderEnvelope(parsed) || assistantText;
|
||||
} catch {
|
||||
// Fall back to raw stdout if the CLI printed plain text.
|
||||
}
|
||||
|
||||
const payload = buildQoderCompletionPayload({
|
||||
model,
|
||||
text: assistantText,
|
||||
});
|
||||
|
||||
return {
|
||||
response: new Response(JSON.stringify(payload), {
|
||||
status: 200,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}),
|
||||
url: "qodercli://local",
|
||||
headers,
|
||||
transformedBody: body,
|
||||
};
|
||||
}
|
||||
|
||||
const qoderStream = new ReadableStream({
|
||||
start: async (controller) => {
|
||||
const encoder = new TextEncoder();
|
||||
const created = Math.floor(Date.now() / 1000);
|
||||
const responseId = `chatcmpl-${crypto.randomUUID()}`;
|
||||
const responseModel = model || "qoder-rome-30ba3b";
|
||||
const cliCommand = command;
|
||||
const args = [
|
||||
"-q",
|
||||
"-p",
|
||||
prompt,
|
||||
"--max-turns",
|
||||
"1",
|
||||
"--workspace",
|
||||
workspace,
|
||||
"--output-format",
|
||||
"stream-json",
|
||||
];
|
||||
const level = mapQoderModelToLevel(responseModel);
|
||||
if (level) {
|
||||
args.push("--model", level);
|
||||
}
|
||||
|
||||
const child = spawn(cliCommand, args, {
|
||||
env: {
|
||||
...process.env,
|
||||
QODER_PERSONAL_ACCESS_TOKEN: pat,
|
||||
},
|
||||
stdio: ["ignore", "pipe", "pipe"],
|
||||
...(process.platform === "win32" ? { shell: true } : {}),
|
||||
});
|
||||
|
||||
let stdoutBuffer = "";
|
||||
let stderrBuffer = "";
|
||||
let emittedText = "";
|
||||
let roleSent = false;
|
||||
let finished = false;
|
||||
|
||||
const emitSse = (payload: unknown) => {
|
||||
controller.enqueue(encoder.encode(`data: ${JSON.stringify(payload)}\n\n`));
|
||||
};
|
||||
|
||||
const finish = () => {
|
||||
if (finished) return;
|
||||
finished = true;
|
||||
emitSse(
|
||||
buildQoderChunk({
|
||||
id: responseId,
|
||||
model: responseModel,
|
||||
created,
|
||||
delta: {},
|
||||
finishReason: "stop",
|
||||
})
|
||||
);
|
||||
controller.enqueue(encoder.encode("data: [DONE]\n\n"));
|
||||
controller.close();
|
||||
};
|
||||
|
||||
const abortChild = () => {
|
||||
try {
|
||||
child.kill("SIGTERM");
|
||||
} catch {}
|
||||
};
|
||||
|
||||
if (signal?.aborted) {
|
||||
abortChild();
|
||||
controller.error(new Error("aborted"));
|
||||
return;
|
||||
}
|
||||
|
||||
signal?.addEventListener?.(
|
||||
"abort",
|
||||
() => {
|
||||
abortChild();
|
||||
controller.error(new Error("aborted"));
|
||||
},
|
||||
{ once: true }
|
||||
);
|
||||
|
||||
const emitDelta = (deltaText: string) => {
|
||||
if (!deltaText) return;
|
||||
const delta = roleSent
|
||||
? { content: deltaText }
|
||||
: { role: "assistant", content: deltaText };
|
||||
roleSent = true;
|
||||
emitSse(
|
||||
buildQoderChunk({
|
||||
id: responseId,
|
||||
model: responseModel,
|
||||
created,
|
||||
delta,
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const drainStdout = () => {
|
||||
let newlineIndex = stdoutBuffer.indexOf("\n");
|
||||
while (newlineIndex >= 0) {
|
||||
const rawLine = stdoutBuffer.slice(0, newlineIndex).trim();
|
||||
stdoutBuffer = stdoutBuffer.slice(newlineIndex + 1);
|
||||
newlineIndex = stdoutBuffer.indexOf("\n");
|
||||
|
||||
if (!rawLine) continue;
|
||||
|
||||
let parsed: unknown;
|
||||
try {
|
||||
parsed = JSON.parse(rawLine);
|
||||
} catch {
|
||||
continue;
|
||||
}
|
||||
|
||||
const nextText = extractTextFromQoderEnvelope(parsed);
|
||||
if (nextText) {
|
||||
const delta = nextText.startsWith(emittedText)
|
||||
? nextText.slice(emittedText.length)
|
||||
: nextText;
|
||||
emittedText += delta;
|
||||
emitDelta(delta);
|
||||
}
|
||||
|
||||
const parsedRecord =
|
||||
parsed && typeof parsed === "object" && !Array.isArray(parsed)
|
||||
? (parsed as Record<string, unknown>)
|
||||
: {};
|
||||
if (parsedRecord.type === "result" && parsedRecord.done === true) {
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
child.stdout.setEncoding("utf8");
|
||||
child.stderr.setEncoding("utf8");
|
||||
|
||||
child.stdout.on("data", (chunk) => {
|
||||
stdoutBuffer += chunk;
|
||||
drainStdout();
|
||||
});
|
||||
|
||||
child.stderr.on("data", (chunk) => {
|
||||
stderrBuffer += chunk;
|
||||
});
|
||||
|
||||
child.on("error", (error) => {
|
||||
if (finished) return;
|
||||
controller.error(error);
|
||||
});
|
||||
|
||||
child.on("close", (code) => {
|
||||
if (finished) return;
|
||||
drainStdout();
|
||||
if (code !== 0) {
|
||||
const failure = parseQoderCliFailure(stderrBuffer, stdoutBuffer);
|
||||
controller.error(new Error(failure.message));
|
||||
return;
|
||||
}
|
||||
finish();
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
response: new Response(qoderStream, {
|
||||
status: 200,
|
||||
headers: {
|
||||
"Content-Type": "text/event-stream; charset=utf-8",
|
||||
"Cache-Control": "no-cache, no-transform",
|
||||
Connection: "keep-alive",
|
||||
},
|
||||
}),
|
||||
url: "qodercli://local",
|
||||
headers,
|
||||
transformedBody: body,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,9 @@ import { refreshWithRetry } from "../services/tokenRefresh.ts";
|
||||
import { createRequestLogger } from "../utils/requestLogger.ts";
|
||||
import { getModelTargetFormat, PROVIDER_ID_TO_ALIAS } from "../config/providerModels.ts";
|
||||
import { resolveModelAlias } from "../services/modelDeprecation.ts";
|
||||
import { getUnsupportedParams, getPassthroughProviders } from "../config/providerRegistry.ts";
|
||||
import { getUnsupportedParams } from "../config/providerRegistry.ts";
|
||||
import { hasPerModelQuota, lockModelIfPerModelQuota } from "../services/accountFallback.ts";
|
||||
import { COOLDOWN_MS } from "../config/constants.ts";
|
||||
import {
|
||||
buildErrorBody,
|
||||
createErrorResult,
|
||||
@@ -44,6 +46,7 @@ import {
|
||||
getModelNormalizeToolCallId,
|
||||
getModelPreserveOpenAIDeveloperRole,
|
||||
getModelUpstreamExtraHeaders,
|
||||
getUpstreamProxyConfig,
|
||||
} from "@/lib/localDb";
|
||||
import { getExecutor } from "../executors/index.ts";
|
||||
import { getCacheControlSettings } from "@/lib/cacheControlSettings";
|
||||
@@ -60,7 +63,11 @@ import {
|
||||
} from "../executors/codex.ts";
|
||||
import { translateNonStreamingResponse } from "./responseTranslator.ts";
|
||||
import { extractUsageFromResponse } from "./usageExtractor.ts";
|
||||
import { parseSSEToOpenAIResponse, parseSSEToResponsesOutput } from "./sseParser.ts";
|
||||
import {
|
||||
parseSSEToClaudeResponse,
|
||||
parseSSEToOpenAIResponse,
|
||||
parseSSEToResponsesOutput,
|
||||
} from "./sseParser.ts";
|
||||
import { sanitizeOpenAIResponse } from "./responseSanitizer.ts";
|
||||
import {
|
||||
withRateLimit,
|
||||
@@ -329,6 +336,25 @@ function attachLogMeta(
|
||||
* @param {boolean} options.isCombo - Whether this request is from a combo
|
||||
* @param {string} options.connectionId - Connection ID for settings lookup
|
||||
*/
|
||||
|
||||
/**
|
||||
* Module-level cache for upstream proxy config (shared across all requests).
|
||||
* 10s TTL prevents per-request DB lookups while staying fresh enough for setting changes.
|
||||
*/
|
||||
const _proxyConfigCache = new Map<string, { mode: string; enabled: boolean; ts: number }>();
|
||||
const PROXY_CONFIG_CACHE_TTL = 10_000;
|
||||
|
||||
async function getUpstreamProxyConfigCached(providerId: string) {
|
||||
const cached = _proxyConfigCache.get(providerId);
|
||||
if (cached && Date.now() - cached.ts < PROXY_CONFIG_CACHE_TTL) return cached;
|
||||
const cfg = await getUpstreamProxyConfig(providerId).catch(() => null);
|
||||
const result = cfg
|
||||
? { mode: cfg.mode, enabled: cfg.enabled, ts: Date.now() }
|
||||
: { mode: "native" as const, enabled: false, ts: Date.now() };
|
||||
_proxyConfigCache.set(providerId, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function handleChatCore({
|
||||
body,
|
||||
modelInfo,
|
||||
@@ -711,6 +737,7 @@ export async function handleChatCore({
|
||||
let translatedBody = body;
|
||||
const isClaudePassthrough = sourceFormat === FORMATS.CLAUDE && targetFormat === FORMATS.CLAUDE;
|
||||
const isClaudeCodeCompatible = isClaudeCodeCompatibleProvider(provider);
|
||||
const upstreamStream = stream || isClaudeCodeCompatible;
|
||||
let ccSessionId: string | null = null;
|
||||
|
||||
// Determine if we should preserve client-side cache_control headers
|
||||
@@ -769,11 +796,13 @@ export async function handleChatCore({
|
||||
translatedBody = buildClaudeCodeCompatibleRequest({
|
||||
sourceBody: body,
|
||||
normalizedBody: normalizedForCc,
|
||||
claudeBody: sourceFormat === FORMATS.CLAUDE ? body : null,
|
||||
model,
|
||||
stream,
|
||||
stream: upstreamStream,
|
||||
sessionId: ccSessionId,
|
||||
cwd: process.cwd(),
|
||||
now: new Date(),
|
||||
preserveCacheControl,
|
||||
});
|
||||
log?.debug?.("FORMAT", "claude-code-compatible bridge enabled");
|
||||
} else if (isClaudePassthrough && preserveCacheControl) {
|
||||
@@ -1010,8 +1039,68 @@ export async function handleChatCore({
|
||||
}
|
||||
}
|
||||
|
||||
// Get executor for this provider
|
||||
const executor = getExecutor(provider);
|
||||
// Resolve executor with optional upstream proxy (CLIProxyAPI) routing.
|
||||
// mode="native" (default): returns the native executor unchanged.
|
||||
// mode="cliproxyapi": returns the CLIProxyAPI executor instead.
|
||||
// mode="fallback": returns a wrapper that tries native first, falls back to CLIProxyAPI on 5xx/network errors.
|
||||
|
||||
const resolveExecutorWithProxy = async (prov: string) => {
|
||||
const cfg = await getUpstreamProxyConfigCached(prov);
|
||||
if (!cfg.enabled || cfg.mode === "native") return getExecutor(prov);
|
||||
|
||||
if (cfg.mode === "cliproxyapi") {
|
||||
log?.info?.("UPSTREAM_PROXY", `${prov} routed through CLIProxyAPI (passthrough)`);
|
||||
return getExecutor("cliproxyapi");
|
||||
}
|
||||
|
||||
// mode === "fallback": try native first, retry via CLIProxyAPI on specific failures
|
||||
const nativeExec = getExecutor(prov);
|
||||
const proxyExec = getExecutor("cliproxyapi");
|
||||
const isRetryableStatus = (s: number) => s >= 500 || s === 429 || s === 0;
|
||||
|
||||
const wrapper = Object.create(nativeExec);
|
||||
wrapper.execute = async (input: {
|
||||
model: string;
|
||||
body: unknown;
|
||||
stream: boolean;
|
||||
credentials: unknown;
|
||||
signal?: AbortSignal | null;
|
||||
log?: unknown;
|
||||
upstreamExtraHeaders?: Record<string, string> | null;
|
||||
}) => {
|
||||
try {
|
||||
const result = await nativeExec.execute(input);
|
||||
if (isRetryableStatus(result.response.status)) {
|
||||
log?.info?.(
|
||||
"UPSTREAM_PROXY",
|
||||
`${prov} native failed (${result.response.status}), retrying via CLIProxyAPI`
|
||||
);
|
||||
try {
|
||||
return await proxyExec.execute(input);
|
||||
} catch (proxyErr) {
|
||||
const proxyMsg = proxyErr instanceof Error ? proxyErr.message : String(proxyErr);
|
||||
log?.error?.("UPSTREAM_PROXY", `${prov} CLIProxyAPI fallback also failed: ${proxyMsg}`);
|
||||
throw proxyErr;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
} catch (err) {
|
||||
const errMsg = err instanceof Error ? err.message : String(err);
|
||||
log?.info?.("UPSTREAM_PROXY", `${prov} native error (${errMsg}), retrying via CLIProxyAPI`);
|
||||
try {
|
||||
return await proxyExec.execute(input);
|
||||
} catch (proxyErr) {
|
||||
const proxyMsg = proxyErr instanceof Error ? proxyErr.message : String(proxyErr);
|
||||
log?.error?.("UPSTREAM_PROXY", `${prov} CLIProxyAPI fallback also failed: ${proxyMsg}`);
|
||||
throw proxyErr;
|
||||
}
|
||||
}
|
||||
};
|
||||
return wrapper;
|
||||
};
|
||||
|
||||
// Get executor for this provider (with optional upstream proxy routing)
|
||||
const executor = await resolveExecutorWithProxy(provider);
|
||||
const getExecutionCredentials = () => {
|
||||
const nextCredentials = nativeCodexPassthrough
|
||||
? { ...credentials, requestEndpointPath: endpointPath }
|
||||
@@ -1031,7 +1120,7 @@ export async function handleChatCore({
|
||||
// Create stream controller for disconnect detection
|
||||
const streamController = createStreamController({ onDisconnect, log, provider, model });
|
||||
|
||||
const dedupRequestBody = { ...translatedBody, model: `${provider}/${model}` };
|
||||
const dedupRequestBody = { ...translatedBody, model: `${provider}/${model}`, stream };
|
||||
const dedupEnabled = shouldDeduplicate(dedupRequestBody);
|
||||
const dedupHash = dedupEnabled ? computeRequestHash(dedupRequestBody) : null;
|
||||
|
||||
@@ -1065,7 +1154,7 @@ export async function handleChatCore({
|
||||
const res = await executor.execute({
|
||||
model: modelToCall,
|
||||
body: bodyToSend,
|
||||
stream,
|
||||
stream: upstreamStream,
|
||||
credentials: getExecutionCredentials(),
|
||||
signal: streamController.signal,
|
||||
log,
|
||||
@@ -1228,7 +1317,7 @@ export async function handleChatCore({
|
||||
const retryResult = await executor.execute({
|
||||
model: retryModelId,
|
||||
body: translatedBody,
|
||||
stream,
|
||||
stream: upstreamStream,
|
||||
credentials: getExecutionCredentials(),
|
||||
signal: streamController.signal,
|
||||
log,
|
||||
@@ -1290,16 +1379,20 @@ export async function handleChatCore({
|
||||
`[provider] Node ${connectionId} account deactivated (${statusCode}) — disabling permanently`
|
||||
);
|
||||
} else if (errorType === PROVIDER_ERROR_TYPES.RATE_LIMITED) {
|
||||
// For passthrough providers (e.g. Antigravity), each model has independent
|
||||
// quota. A 429 on one model must NOT lock out the entire connection — other
|
||||
// models may still have quota available. Use lockModel() instead.
|
||||
const isPassthrough = provider && getPassthroughProviders().has(provider);
|
||||
if (isPassthrough) {
|
||||
const { lockModel } = await import("../services/accountFallback.ts");
|
||||
const cooldown = retryAfterMs || 120_000; // 2 min default, same as COOLDOWN_MS.rateLimit
|
||||
lockModel(provider, connectionId, model, "rate_limited", cooldown);
|
||||
// For providers with per-model quotas (passthrough providers, Gemini),
|
||||
// each model has independent quota. A 429 on one model must NOT lock out
|
||||
// the entire connection — other models may still have quota available.
|
||||
if (
|
||||
lockModelIfPerModelQuota(
|
||||
provider,
|
||||
connectionId,
|
||||
model,
|
||||
"rate_limited",
|
||||
retryAfterMs || COOLDOWN_MS.rateLimit
|
||||
)
|
||||
) {
|
||||
console.warn(
|
||||
`[provider] Node ${connectionId} model-only rate limited (${statusCode}) for ${model} - ${Math.ceil(cooldown / 1000)}s (connection stays active)`
|
||||
`[provider] Node ${connectionId} model-only rate limited (${statusCode}) for ${model} - ${Math.ceil((retryAfterMs || COOLDOWN_MS.rateLimit) / 1000)}s (connection stays active)`
|
||||
);
|
||||
} else {
|
||||
const rateLimitedUntil = new Date(Date.now() + retryAfterMs).toISOString();
|
||||
@@ -1317,13 +1410,28 @@ export async function handleChatCore({
|
||||
);
|
||||
}
|
||||
} else if (errorType === PROVIDER_ERROR_TYPES.QUOTA_EXHAUSTED) {
|
||||
await updateProviderConnection(connectionId, {
|
||||
testStatus: "credits_exhausted",
|
||||
lastErrorType: errorType,
|
||||
lastError: message,
|
||||
errorCode: statusCode,
|
||||
});
|
||||
console.warn(`[provider] Node ${connectionId} exhausted quota (${statusCode})`);
|
||||
// Providers with per-model quotas — lock the model only, not the connection
|
||||
if (
|
||||
lockModelIfPerModelQuota(
|
||||
provider,
|
||||
connectionId,
|
||||
model,
|
||||
"quota_exhausted",
|
||||
retryAfterMs || COOLDOWN_MS.rateLimit
|
||||
)
|
||||
) {
|
||||
console.warn(
|
||||
`[provider] Node ${connectionId} model-only quota exhausted (${statusCode}) for ${model} - ${Math.ceil((retryAfterMs || COOLDOWN_MS.rateLimit) / 1000)}s (connection stays active)`
|
||||
);
|
||||
} else {
|
||||
await updateProviderConnection(connectionId, {
|
||||
testStatus: "credits_exhausted",
|
||||
lastErrorType: errorType,
|
||||
lastError: message,
|
||||
errorCode: statusCode,
|
||||
});
|
||||
console.warn(`[provider] Node ${connectionId} exhausted quota (${statusCode})`);
|
||||
}
|
||||
} else if (errorType === PROVIDER_ERROR_TYPES.ACCOUNT_DEACTIVATED) {
|
||||
await updateProviderConnection(connectionId, {
|
||||
isActive: false,
|
||||
@@ -1532,7 +1640,9 @@ export async function handleChatCore({
|
||||
const parsedFromSSE =
|
||||
targetFormat === FORMATS.OPENAI_RESPONSES
|
||||
? parseSSEToResponsesOutput(rawBody, model)
|
||||
: parseSSEToOpenAIResponse(rawBody, model);
|
||||
: targetFormat === FORMATS.CLAUDE
|
||||
? parseSSEToClaudeResponse(rawBody, model)
|
||||
: parseSSEToOpenAIResponse(rawBody, model);
|
||||
|
||||
if (!parsedFromSSE) {
|
||||
appendRequestLog({
|
||||
@@ -1689,8 +1799,8 @@ export async function handleChatCore({
|
||||
// Sanitize response for OpenAI SDK compatibility
|
||||
// Strips non-standard fields (x_groq, usage_breakdown, service_tier, etc.)
|
||||
// Extracts <think> and <thinking> tags into reasoning_content
|
||||
// Target format determines output shape. If we are outputting OpenAI shape or pseudo-OpenAI shape, sanitize.
|
||||
if (targetFormat === FORMATS.OPENAI || targetFormat === FORMATS.OPENAI_RESPONSES) {
|
||||
// Source format determines output shape. If we are outputting OpenAI shape or pseudo-OpenAI shape, sanitize.
|
||||
if (sourceFormat === FORMATS.OPENAI || sourceFormat === FORMATS.OPENAI_RESPONSES) {
|
||||
translatedResponse = sanitizeOpenAIResponse(translatedResponse);
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,75 @@
|
||||
* Convert OpenAI-style SSE chunks into a single non-streaming JSON response.
|
||||
* Used as a fallback when upstream returns text/event-stream for stream=false.
|
||||
*/
|
||||
function readSSEEvents(rawSSE) {
|
||||
const lines = String(rawSSE || "").split("\n");
|
||||
const events = [];
|
||||
let currentEvent = "";
|
||||
let currentData = [];
|
||||
|
||||
const flush = () => {
|
||||
if (currentData.length === 0) {
|
||||
currentEvent = "";
|
||||
return;
|
||||
}
|
||||
|
||||
const payload = currentData.join("\n").trim();
|
||||
currentData = [];
|
||||
if (!payload || payload === "[DONE]") {
|
||||
currentEvent = "";
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
events.push({
|
||||
event: currentEvent || undefined,
|
||||
data: JSON.parse(payload),
|
||||
});
|
||||
} catch {
|
||||
// Ignore malformed SSE events and continue best-effort parsing.
|
||||
}
|
||||
|
||||
currentEvent = "";
|
||||
};
|
||||
|
||||
for (const rawLine of lines) {
|
||||
const line = rawLine.replace(/\r$/, "");
|
||||
if (line.trim() === "") {
|
||||
flush();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (line.startsWith("event:")) {
|
||||
currentEvent = line.slice(6).trim();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (line.startsWith("data:")) {
|
||||
currentData.push(line.slice(5).trimStart());
|
||||
}
|
||||
}
|
||||
|
||||
flush();
|
||||
return events;
|
||||
}
|
||||
|
||||
function toRecord(value) {
|
||||
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
||||
}
|
||||
|
||||
function toString(value, fallback = "") {
|
||||
return typeof value === "string" ? value : fallback;
|
||||
}
|
||||
|
||||
function toNumber(value, fallback = 0) {
|
||||
if (typeof value === "number" && Number.isFinite(value)) return value;
|
||||
if (typeof value === "string" && value.trim().length > 0) {
|
||||
const parsed = Number(value);
|
||||
return Number.isFinite(parsed) ? parsed : fallback;
|
||||
}
|
||||
return fallback;
|
||||
}
|
||||
|
||||
export function parseSSEToOpenAIResponse(rawSSE, fallbackModel) {
|
||||
const lines = String(rawSSE || "").split("\n");
|
||||
const chunks = [];
|
||||
@@ -143,6 +212,189 @@ export function parseSSEToOpenAIResponse(rawSSE, fallbackModel) {
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert Claude-style SSE events into a single non-streaming message object.
|
||||
* Used when Claude-compatible upstreams stream even for stream=false.
|
||||
*/
|
||||
export function parseSSEToClaudeResponse(rawSSE, fallbackModel) {
|
||||
const payloads = readSSEEvents(rawSSE)
|
||||
.map((event) => toRecord(event.data))
|
||||
.filter((payload) => Object.keys(payload).length > 0);
|
||||
|
||||
if (payloads.length === 0) return null;
|
||||
|
||||
const blocks = new Map();
|
||||
const usage = {};
|
||||
let messageId = "";
|
||||
let model = fallbackModel || "claude";
|
||||
let role = "assistant";
|
||||
let stopReason = "end_turn";
|
||||
let stopSequence = null;
|
||||
|
||||
const mergeUsage = (incoming) => {
|
||||
const usageRecord = toRecord(incoming);
|
||||
for (const [key, value] of Object.entries(usageRecord)) {
|
||||
if (typeof value === "number" && Number.isFinite(value)) {
|
||||
usage[key] = value;
|
||||
} else if (value && typeof value === "object" && !Array.isArray(value)) {
|
||||
usage[key] = { ...toRecord(usage[key]), ...toRecord(value) };
|
||||
} else if (typeof value === "string" && value.trim().length > 0) {
|
||||
usage[key] = value;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const tryParseJson = (raw) => {
|
||||
try {
|
||||
return JSON.parse(raw);
|
||||
} catch {
|
||||
return raw;
|
||||
}
|
||||
};
|
||||
|
||||
for (const payload of payloads) {
|
||||
const eventType = toString(payload.type);
|
||||
if (eventType === "message_start") {
|
||||
const message = toRecord(payload.message);
|
||||
messageId = toString(message.id, messageId || `msg_${Date.now()}`);
|
||||
model = toString(message.model, model);
|
||||
role = toString(message.role, role);
|
||||
mergeUsage(message.usage);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (eventType === "content_block_start") {
|
||||
const index = toNumber(payload.index, blocks.size);
|
||||
const contentBlock = toRecord(payload.content_block);
|
||||
const blockType = toString(contentBlock.type);
|
||||
|
||||
if (blockType === "thinking") {
|
||||
blocks.set(index, {
|
||||
type: "thinking",
|
||||
index,
|
||||
thinking: toString(contentBlock.thinking),
|
||||
signature:
|
||||
typeof contentBlock.signature === "string" ? contentBlock.signature : undefined,
|
||||
});
|
||||
} else if (blockType === "tool_use") {
|
||||
blocks.set(index, {
|
||||
type: "tool_use",
|
||||
index,
|
||||
id: toString(contentBlock.id, `toolu_${Date.now()}_${index}`),
|
||||
name: toString(contentBlock.name),
|
||||
input: contentBlock.input ?? {},
|
||||
inputJson: "",
|
||||
});
|
||||
} else {
|
||||
blocks.set(index, {
|
||||
type: "text",
|
||||
index,
|
||||
text: toString(contentBlock.text),
|
||||
});
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (eventType === "content_block_delta") {
|
||||
const index = toNumber(payload.index, 0);
|
||||
const delta = toRecord(payload.delta);
|
||||
const deltaType = toString(delta.type);
|
||||
const existing = blocks.get(index);
|
||||
|
||||
if (deltaType === "input_json_delta") {
|
||||
const toolUse =
|
||||
existing && existing.type === "tool_use"
|
||||
? existing
|
||||
: {
|
||||
type: "tool_use",
|
||||
index,
|
||||
id: `toolu_${Date.now()}_${index}`,
|
||||
name: "",
|
||||
input: {},
|
||||
inputJson: "",
|
||||
};
|
||||
toolUse.inputJson += toString(delta.partial_json);
|
||||
blocks.set(index, toolUse);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (deltaType === "thinking_delta" || typeof delta.thinking === "string") {
|
||||
const thinking =
|
||||
existing && existing.type === "thinking"
|
||||
? existing
|
||||
: { type: "thinking", index, thinking: "", signature: undefined };
|
||||
thinking.thinking += toString(delta.thinking);
|
||||
blocks.set(index, thinking);
|
||||
continue;
|
||||
}
|
||||
|
||||
const textBlock =
|
||||
existing && existing.type === "text"
|
||||
? existing
|
||||
: {
|
||||
type: "text",
|
||||
index,
|
||||
text: "",
|
||||
};
|
||||
textBlock.text += toString(delta.text);
|
||||
blocks.set(index, textBlock);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (eventType === "message_delta") {
|
||||
const delta = toRecord(payload.delta);
|
||||
stopReason = toString(delta.stop_reason, stopReason);
|
||||
stopSequence =
|
||||
typeof delta.stop_sequence === "string" ? String(delta.stop_sequence) : stopSequence;
|
||||
mergeUsage(payload.usage);
|
||||
continue;
|
||||
}
|
||||
|
||||
mergeUsage(payload.usage);
|
||||
}
|
||||
|
||||
const content = [...blocks.values()]
|
||||
.sort((a, b) => a.index - b.index)
|
||||
.flatMap((block) => {
|
||||
if (block.type === "text") {
|
||||
return block.text ? [{ type: "text", text: block.text }] : [];
|
||||
}
|
||||
if (block.type === "thinking") {
|
||||
return block.thinking
|
||||
? [
|
||||
{
|
||||
type: "thinking",
|
||||
thinking: block.thinking,
|
||||
...(block.signature ? { signature: block.signature } : {}),
|
||||
},
|
||||
]
|
||||
: [];
|
||||
}
|
||||
|
||||
const parsedInput =
|
||||
block.inputJson.trim().length > 0 ? tryParseJson(block.inputJson) : block.input;
|
||||
return [
|
||||
{
|
||||
type: "tool_use",
|
||||
id: block.id,
|
||||
name: block.name,
|
||||
input: parsedInput,
|
||||
},
|
||||
];
|
||||
});
|
||||
|
||||
return {
|
||||
id: messageId || `msg_${Date.now()}`,
|
||||
type: "message",
|
||||
role,
|
||||
model,
|
||||
content,
|
||||
stop_reason: stopReason,
|
||||
...(stopSequence ? { stop_sequence: stopSequence } : {}),
|
||||
...(Object.keys(usage).length > 0 ? { usage } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert Responses API SSE events into a single non-streaming response object.
|
||||
* Expects events such as response.created / response.in_progress / response.completed.
|
||||
|
||||
@@ -69,6 +69,12 @@ export const getHealthOutput = z.object({
|
||||
hitRate: z.number(),
|
||||
})
|
||||
.optional(),
|
||||
cryptography: z
|
||||
.object({
|
||||
status: z.enum(["healthy", "missing_or_invalid"]),
|
||||
provider: z.string(),
|
||||
})
|
||||
.optional(),
|
||||
});
|
||||
|
||||
export const getHealthTool: McpToolDefinition<typeof getHealthInput, typeof getHealthOutput> = {
|
||||
|
||||
@@ -212,6 +212,12 @@ async function handleGetHealth() {
|
||||
hitRate: toNumber(cacheStatsRaw.hitRate, 0),
|
||||
}
|
||||
: undefined,
|
||||
cryptography: health.cryptography
|
||||
? {
|
||||
status: toString(toRecord(health.cryptography).status, "missing_or_invalid"),
|
||||
provider: toString(toRecord(health.cryptography).provider, "unknown"),
|
||||
}
|
||||
: undefined,
|
||||
};
|
||||
|
||||
await logToolCall("omniroute_get_health", {}, result, Date.now() - start, true);
|
||||
@@ -725,12 +731,14 @@ export function createMcpServer(): McpServer {
|
||||
toolDef.name,
|
||||
{
|
||||
description: toolDef.description,
|
||||
inputSchema: toolDef.inputSchema as any,
|
||||
// @ts-ignore: dynamic zod access
|
||||
inputSchema: toolDef.inputSchema,
|
||||
},
|
||||
withScopeEnforcement(toolDef.name, async (args) => {
|
||||
try {
|
||||
const parsedArgs = toolDef.inputSchema.parse(args ?? {});
|
||||
const result = await toolDef.handler(parsedArgs as any);
|
||||
// @ts-ignore: handler expected specific object
|
||||
const result = await toolDef.handler(parsedArgs);
|
||||
return { content: [{ type: "text" as const, text: JSON.stringify(result, null, 2) }] };
|
||||
} catch (err) {
|
||||
const msg = err instanceof Error ? err.message : String(err);
|
||||
@@ -746,12 +754,14 @@ export function createMcpServer(): McpServer {
|
||||
toolDef.name,
|
||||
{
|
||||
description: toolDef.description,
|
||||
inputSchema: toolDef.inputSchema as any,
|
||||
// @ts-ignore: dynamic zod access
|
||||
inputSchema: toolDef.inputSchema,
|
||||
},
|
||||
withScopeEnforcement(toolDef.name, async (args) => {
|
||||
try {
|
||||
const parsedArgs = toolDef.inputSchema.parse(args ?? {});
|
||||
const result = await toolDef.handler(parsedArgs as any);
|
||||
// @ts-ignore: handler expected specific object
|
||||
const result = await toolDef.handler(parsedArgs);
|
||||
return { content: [{ type: "text" as const, text: JSON.stringify(result, null, 2) }] };
|
||||
} catch (err) {
|
||||
const msg = err instanceof Error ? err.message : String(err);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@omniroute/open-sse",
|
||||
"version": "3.4.4",
|
||||
"version": "3.4.7",
|
||||
"description": "Express SSE sidecar for OmniRoute — handles streaming, protocol translation, and provider orchestration",
|
||||
"type": "module",
|
||||
"main": "index.js",
|
||||
|
||||
@@ -100,13 +100,49 @@ export function lockModel(provider, connectionId, model, reason, cooldownMs) {
|
||||
if (!model) return; // No model → skip model-level locking
|
||||
ensureCleanupTimer();
|
||||
const key = `${provider}:${connectionId}:${model}`;
|
||||
const newUntil = Date.now() + cooldownMs;
|
||||
// Preserve the longer cooldown if an existing lock has more time remaining.
|
||||
// Safe without a mutex: no await between get/set, so this runs atomically
|
||||
// within Node.js's single-threaded event loop.
|
||||
const existing = modelLockouts.get(key);
|
||||
if (existing && existing.until > newUntil) return;
|
||||
modelLockouts.set(key, {
|
||||
reason,
|
||||
until: Date.now() + cooldownMs,
|
||||
until: newUntil,
|
||||
lockedAt: Date.now(),
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether a provider should use per-model lockouts instead of connection-wide cooldowns.
|
||||
* Gemini AI Studio has per-model quotas; passthrough providers have independent model limits.
|
||||
*/
|
||||
export function hasPerModelQuota(provider: string): boolean {
|
||||
if (provider === "gemini") return true;
|
||||
try {
|
||||
const { getPassthroughProviders } = require("../config/providerRegistry.ts");
|
||||
return getPassthroughProviders().has(provider);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Lock a model (not connection) for a provider with per-model quotas.
|
||||
* No-ops for providers that don't use per-model lockouts.
|
||||
*/
|
||||
export function lockModelIfPerModelQuota(
|
||||
provider: string,
|
||||
connectionId: string,
|
||||
model: string | null,
|
||||
reason: string,
|
||||
cooldownMs: number
|
||||
): boolean {
|
||||
if (!hasPerModelQuota(provider) || !model) return false;
|
||||
lockModel(provider, connectionId, model, reason, cooldownMs);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a specific model on a specific account is locked
|
||||
* @returns {boolean}
|
||||
|
||||
@@ -16,6 +16,7 @@ export interface RoutingContext {
|
||||
requestHasTools?: boolean;
|
||||
requestHasVision?: boolean;
|
||||
estimatedInputTokens?: number;
|
||||
lastKnownGoodProvider?: string;
|
||||
}
|
||||
|
||||
export interface RoutingDecision {
|
||||
@@ -116,6 +117,34 @@ class LatencyStrategyImpl implements RouterStrategy {
|
||||
}
|
||||
}
|
||||
|
||||
// ── LKGPStrategy: tries last known good provider first ───────────────────────
|
||||
|
||||
class LKGPStrategyImpl implements RouterStrategy {
|
||||
readonly name = "lkgp";
|
||||
readonly description = "Tries last known good provider first, then falls back to rules";
|
||||
|
||||
select(pool: ProviderCandidate[], context: RoutingContext): RoutingDecision {
|
||||
if (context.lastKnownGoodProvider) {
|
||||
const best = pool.find(
|
||||
(c) => c.provider === context.lastKnownGoodProvider && c.circuitBreakerState !== "OPEN"
|
||||
);
|
||||
if (best) {
|
||||
return {
|
||||
provider: best.provider,
|
||||
model: best.model,
|
||||
strategy: this.name,
|
||||
reason: `LKGP: using last known good provider ${best.provider}`,
|
||||
candidatesConsidered: 1,
|
||||
finalScore: 1.0,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to rules strategy
|
||||
return getStrategy("rules").select(pool, context);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Registry ──────────────────────────────────────────────────────────────────
|
||||
|
||||
const strategyRegistry = new Map<string, RouterStrategy>();
|
||||
@@ -123,12 +152,14 @@ const strategyRegistry = new Map<string, RouterStrategy>();
|
||||
const rulesStrategy = new RulesStrategyImpl();
|
||||
const costStrategy = new CostStrategyImpl();
|
||||
const latencyStrategy = new LatencyStrategyImpl();
|
||||
const lkgpStrategy = new LKGPStrategyImpl();
|
||||
|
||||
strategyRegistry.set("rules", rulesStrategy);
|
||||
strategyRegistry.set("cost", costStrategy);
|
||||
strategyRegistry.set("eco", costStrategy); // alias
|
||||
strategyRegistry.set("latency", latencyStrategy);
|
||||
strategyRegistry.set("fast", latencyStrategy); // alias
|
||||
strategyRegistry.set("lkgp", lkgpStrategy);
|
||||
|
||||
export function getStrategy(name: string): RouterStrategy {
|
||||
const strategy = strategyRegistry.get(name);
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { createHash, randomUUID } from "node:crypto";
|
||||
|
||||
import { prepareClaudeRequest } from "../translator/helpers/claudeHelper.ts";
|
||||
|
||||
export const CLAUDE_CODE_COMPATIBLE_PREFIX = "anthropic-compatible-cc-";
|
||||
export const CLAUDE_CODE_COMPATIBLE_DEFAULT_CHAT_PATH = "/v1/messages?beta=true";
|
||||
export const CLAUDE_CODE_COMPATIBLE_DEFAULT_MODELS_PATH = "/models";
|
||||
@@ -26,11 +28,13 @@ type MessageLike = {
|
||||
type BuildRequestOptions = {
|
||||
sourceBody?: Record<string, unknown> | null;
|
||||
normalizedBody?: Record<string, unknown> | null;
|
||||
claudeBody?: Record<string, unknown> | null;
|
||||
model: string;
|
||||
stream?: boolean;
|
||||
cwd?: string;
|
||||
now?: Date;
|
||||
sessionId?: string | null;
|
||||
preserveCacheControl?: boolean;
|
||||
};
|
||||
|
||||
export function isClaudeCodeCompatibleProvider(provider: string | null | undefined): boolean {
|
||||
@@ -116,7 +120,7 @@ export function buildClaudeCodeCompatibleValidationPayload(model = "claude-sonne
|
||||
max_tokens: 1,
|
||||
},
|
||||
model,
|
||||
stream: false,
|
||||
stream: true,
|
||||
sessionId,
|
||||
cwd: process.cwd(),
|
||||
now: new Date(),
|
||||
@@ -137,25 +141,38 @@ export function resolveClaudeCodeCompatibleSessionId(headers?: HeaderLike): stri
|
||||
export function buildClaudeCodeCompatibleRequest({
|
||||
sourceBody,
|
||||
normalizedBody,
|
||||
claudeBody,
|
||||
model,
|
||||
stream = false,
|
||||
cwd = process.cwd(),
|
||||
now = new Date(),
|
||||
sessionId,
|
||||
preserveCacheControl = false,
|
||||
}: BuildRequestOptions) {
|
||||
const normalized = normalizedBody || {};
|
||||
const messages = Array.isArray(normalized.messages)
|
||||
? buildClaudeCodeCompatibleMessages(normalized.messages as MessageLike[])
|
||||
: [];
|
||||
const system = buildClaudeCodeCompatibleSystemBlocks(
|
||||
normalized.messages as MessageLike[],
|
||||
const preparedClaudeBody = claudeBody
|
||||
? prepareClaudeCodeCompatibleBody(claudeBody, preserveCacheControl)
|
||||
: null;
|
||||
const messages = preparedClaudeBody
|
||||
? buildClaudeCodeCompatibleMessagesFromClaude(
|
||||
preparedClaudeBody.messages as MessageLike[],
|
||||
preserveCacheControl
|
||||
)
|
||||
: Array.isArray(normalized.messages)
|
||||
? buildClaudeCodeCompatibleMessages(normalized.messages as MessageLike[])
|
||||
: [];
|
||||
const system = buildClaudeCodeCompatibleSystemBlocks({
|
||||
messages: normalized.messages as MessageLike[],
|
||||
systemBlocks: preparedClaudeBody?.system as Record<string, unknown>[] | undefined,
|
||||
cwd,
|
||||
now
|
||||
);
|
||||
now,
|
||||
});
|
||||
const resolvedSessionId = sessionId || randomUUID();
|
||||
const effort = resolveClaudeCodeCompatibleEffort(sourceBody, normalizedBody, model);
|
||||
const maxTokens = resolveClaudeCodeCompatibleMaxTokens(sourceBody, normalizedBody);
|
||||
const tools = buildClaudeCodeCompatibleTools(normalizedBody, sourceBody);
|
||||
const tools = preparedClaudeBody?.tools
|
||||
? (preparedClaudeBody.tools as Record<string, unknown>[])
|
||||
: buildClaudeCodeCompatibleTools(normalizedBody, sourceBody);
|
||||
const toolChoice =
|
||||
tools.length > 0
|
||||
? buildClaudeCodeCompatibleToolChoice(
|
||||
@@ -295,32 +312,83 @@ function buildClaudeCodeCompatibleMessages(messages: MessageLike[]) {
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = merged.length - 1; i >= 0; i--) {
|
||||
if (merged[i].role !== "user") continue;
|
||||
const lastBlock = merged[i].content[merged[i].content.length - 1];
|
||||
if (lastBlock) {
|
||||
lastBlock.cache_control = { type: "ephemeral" };
|
||||
applyClaudeCodeCompatibleMessageCacheStrategy(merged);
|
||||
|
||||
return merged;
|
||||
}
|
||||
|
||||
function buildClaudeCodeCompatibleMessagesFromClaude(
|
||||
messages: MessageLike[] | undefined,
|
||||
preserveCacheControl: boolean
|
||||
) {
|
||||
const converted = Array.isArray(messages)
|
||||
? messages
|
||||
.map((message) => convertClaudeCodeCompatibleClaudeMessage(message, preserveCacheControl))
|
||||
.filter(
|
||||
(
|
||||
message
|
||||
): message is { role: "user" | "assistant"; content: Array<Record<string, unknown>> } =>
|
||||
!!message && message.content.length > 0
|
||||
)
|
||||
: [];
|
||||
|
||||
const merged: Array<{ role: "user" | "assistant"; content: Array<Record<string, unknown>> }> = [];
|
||||
|
||||
for (const message of converted) {
|
||||
const last = merged[merged.length - 1];
|
||||
if (last && last.role === message.role) {
|
||||
last.content.push(...message.content);
|
||||
continue;
|
||||
}
|
||||
merged.push({ role: message.role, content: [...message.content] });
|
||||
}
|
||||
|
||||
while (merged.length > 0 && merged[merged.length - 1].role === "assistant") {
|
||||
merged.pop();
|
||||
}
|
||||
|
||||
if (!preserveCacheControl) {
|
||||
for (const message of merged) {
|
||||
stripCacheControlFromContentBlocks(message.content);
|
||||
}
|
||||
applyClaudeCodeCompatibleMessageCacheStrategy(merged);
|
||||
}
|
||||
|
||||
if (merged.length === 0) {
|
||||
const fallbackText = converted
|
||||
.flatMap((message) => message.content)
|
||||
.map((block) => contentToText(block))
|
||||
.filter(Boolean)
|
||||
.join("\n")
|
||||
.trim();
|
||||
if (fallbackText) {
|
||||
return [
|
||||
{
|
||||
role: "user" as const,
|
||||
content: [{ type: "text", text: fallbackText, cache_control: { type: "ephemeral" } }],
|
||||
},
|
||||
];
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return merged;
|
||||
}
|
||||
|
||||
function buildClaudeCodeCompatibleSystemBlocks(
|
||||
messages: MessageLike[] | undefined,
|
||||
cwd: string,
|
||||
now: Date
|
||||
) {
|
||||
const customSystemBlocks = Array.isArray(messages)
|
||||
? messages
|
||||
.filter((message) => {
|
||||
const role = String(message?.role || "").toLowerCase();
|
||||
return role === "system" || role === "developer";
|
||||
})
|
||||
.map((message) => contentToText(message?.content))
|
||||
.filter(Boolean)
|
||||
: [];
|
||||
function buildClaudeCodeCompatibleSystemBlocks({
|
||||
messages,
|
||||
systemBlocks,
|
||||
cwd,
|
||||
now,
|
||||
}: {
|
||||
messages: MessageLike[] | undefined;
|
||||
systemBlocks?: Array<Record<string, unknown>> | undefined;
|
||||
cwd: string;
|
||||
now: Date;
|
||||
}) {
|
||||
const customSystemBlocks =
|
||||
Array.isArray(systemBlocks) && systemBlocks.length > 0
|
||||
? systemBlocks
|
||||
: extractCustomSystemBlocks(messages);
|
||||
|
||||
const dateText = formatDate(now);
|
||||
const blocks: Array<Record<string, unknown>> = [
|
||||
@@ -340,12 +408,8 @@ function buildClaudeCodeCompatibleSystemBlocks(
|
||||
},
|
||||
];
|
||||
|
||||
for (const systemText of customSystemBlocks) {
|
||||
blocks.push({
|
||||
type: "text",
|
||||
text: systemText,
|
||||
cache_control: { type: "ephemeral" },
|
||||
});
|
||||
for (const systemBlock of customSystemBlocks) {
|
||||
blocks.push(systemBlock);
|
||||
}
|
||||
|
||||
return blocks;
|
||||
@@ -383,7 +447,14 @@ function buildClaudeCodeCompatibleTools(
|
||||
|
||||
return rawTools
|
||||
.map((tool) => convertClaudeCodeCompatibleTool(tool))
|
||||
.filter((tool): tool is Record<string, unknown> => !!tool);
|
||||
.filter((tool): tool is Record<string, unknown> => !!tool)
|
||||
.map((tool, index, tools) => {
|
||||
if (index !== findLastCacheableToolIndex(tools)) return tool;
|
||||
return {
|
||||
...tool,
|
||||
cache_control: { type: "ephemeral", ttl: "1h" },
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function convertClaudeCodeCompatibleTool(tool: unknown) {
|
||||
@@ -444,6 +515,190 @@ function buildClaudeCodeCompatibleToolChoice(choice: unknown) {
|
||||
return null;
|
||||
}
|
||||
|
||||
function prepareClaudeCodeCompatibleBody(
|
||||
claudeBody: Record<string, unknown>,
|
||||
preserveCacheControl: boolean
|
||||
) {
|
||||
const prepared = prepareClaudeRequest(
|
||||
{
|
||||
system: normalizeClaudeSystemInput(claudeBody.system),
|
||||
messages: normalizeClaudeMessageInput(claudeBody.messages),
|
||||
tools: normalizeClaudeToolInput(claudeBody.tools),
|
||||
thinking: readRecord(claudeBody.thinking) || claudeBody.thinking,
|
||||
},
|
||||
CLAUDE_CODE_COMPATIBLE_PREFIX,
|
||||
preserveCacheControl
|
||||
);
|
||||
|
||||
return readRecord(prepared);
|
||||
}
|
||||
|
||||
function normalizeClaudeSystemInput(system: unknown) {
|
||||
if (typeof system === "string") {
|
||||
const text = system.trim();
|
||||
return text ? [{ type: "text", text }] : [];
|
||||
}
|
||||
|
||||
if (!Array.isArray(system)) return [];
|
||||
return system
|
||||
.map((block) => normalizeClaudeContentBlock(block))
|
||||
.filter((block): block is Record<string, unknown> => !!block);
|
||||
}
|
||||
|
||||
function normalizeClaudeMessageInput(messages: unknown) {
|
||||
if (!Array.isArray(messages)) return [];
|
||||
return messages
|
||||
.map((message) => {
|
||||
const record = readRecord(message);
|
||||
if (!record) return null;
|
||||
|
||||
return {
|
||||
...record,
|
||||
content: normalizeClaudeContentInput(record.content),
|
||||
};
|
||||
})
|
||||
.filter((message): message is Record<string, unknown> => !!message);
|
||||
}
|
||||
|
||||
function normalizeClaudeToolInput(tools: unknown) {
|
||||
if (!Array.isArray(tools)) return [];
|
||||
return tools
|
||||
.map((tool) => readRecord(cloneValue(tool)))
|
||||
.filter((tool): tool is Record<string, unknown> => !!tool);
|
||||
}
|
||||
|
||||
function normalizeClaudeContentInput(content: unknown) {
|
||||
const blocks = normalizeClaudeContentBlocks(content);
|
||||
return blocks.length > 0 ? blocks : content;
|
||||
}
|
||||
|
||||
function normalizeClaudeContentBlocks(content: unknown) {
|
||||
if (typeof content === "string") {
|
||||
const text = content.trim();
|
||||
return text ? [{ type: "text", text }] : [];
|
||||
}
|
||||
|
||||
if (!Array.isArray(content)) {
|
||||
const block = normalizeClaudeContentBlock(content);
|
||||
return block ? [block] : [];
|
||||
}
|
||||
|
||||
return content
|
||||
.map((block) => normalizeClaudeContentBlock(block))
|
||||
.filter((block): block is Record<string, unknown> => !!block);
|
||||
}
|
||||
|
||||
function normalizeClaudeContentBlock(block: unknown) {
|
||||
const record = readRecord(cloneValue(block));
|
||||
if (!record) return null;
|
||||
|
||||
if (
|
||||
record.type === "text" ||
|
||||
(typeof record.type !== "string" && typeof record.text === "string")
|
||||
) {
|
||||
const text = toNonEmptyString(record.text);
|
||||
if (!text) return null;
|
||||
return {
|
||||
...record,
|
||||
type: "text",
|
||||
text,
|
||||
};
|
||||
}
|
||||
|
||||
return record;
|
||||
}
|
||||
|
||||
function convertClaudeCodeCompatibleClaudeMessage(
|
||||
message: MessageLike | null | undefined,
|
||||
preserveCacheControl: boolean
|
||||
) {
|
||||
const rawRole = String(message?.role || "").toLowerCase();
|
||||
const role = rawRole === "user" ? "user" : rawRole === "assistant" ? "assistant" : null;
|
||||
|
||||
if (!role) return null;
|
||||
|
||||
const content = normalizeClaudeContentBlocks(message?.content).map((block) => {
|
||||
if (preserveCacheControl) return block;
|
||||
const { cache_control, ...rest } = block;
|
||||
return rest;
|
||||
});
|
||||
if (content.length === 0) return null;
|
||||
|
||||
return {
|
||||
role,
|
||||
content,
|
||||
};
|
||||
}
|
||||
|
||||
function extractCustomSystemBlocks(messages: MessageLike[] | undefined) {
|
||||
if (!Array.isArray(messages)) return [];
|
||||
|
||||
return messages
|
||||
.filter((message) => {
|
||||
const role = String(message?.role || "").toLowerCase();
|
||||
return role === "system" || role === "developer";
|
||||
})
|
||||
.map((message) => contentToText(message?.content))
|
||||
.filter(Boolean)
|
||||
.map((text) => ({
|
||||
type: "text",
|
||||
text,
|
||||
cache_control: { type: "ephemeral" },
|
||||
}));
|
||||
}
|
||||
|
||||
function applyClaudeCodeCompatibleMessageCacheStrategy(
|
||||
messages: Array<{ role: "user" | "assistant"; content: Array<Record<string, unknown>> }>
|
||||
) {
|
||||
const userIndexes = messages.reduce((indexes, message, index) => {
|
||||
if (message.role === "user") indexes.push(index);
|
||||
return indexes;
|
||||
}, [] as number[]);
|
||||
const hasAssistant = messages.some((message) => message.role === "assistant");
|
||||
const secondToLastUserIndex = userIndexes.length >= 2 ? userIndexes[userIndexes.length - 2] : -1;
|
||||
|
||||
if (secondToLastUserIndex >= 0) {
|
||||
markLastContentCacheControl(messages[secondToLastUserIndex].content);
|
||||
} else if (!hasAssistant && userIndexes.length > 0) {
|
||||
markLastContentCacheControl(messages[userIndexes[userIndexes.length - 1]].content);
|
||||
}
|
||||
|
||||
for (let i = messages.length - 1; i >= 0; i--) {
|
||||
if (messages[i].role !== "assistant") continue;
|
||||
if (markLastContentCacheControl(messages[i].content)) break;
|
||||
}
|
||||
}
|
||||
|
||||
function stripCacheControlFromContentBlocks(content: Array<Record<string, unknown>>) {
|
||||
for (const block of content) {
|
||||
delete block.cache_control;
|
||||
}
|
||||
}
|
||||
|
||||
function markLastContentCacheControl(content: Array<Record<string, unknown>>, ttl?: string) {
|
||||
if (!Array.isArray(content) || content.length === 0) return false;
|
||||
const lastBlock = content[content.length - 1];
|
||||
if (!lastBlock) return false;
|
||||
lastBlock.cache_control = ttl ? { type: "ephemeral", ttl } : { type: "ephemeral" };
|
||||
return true;
|
||||
}
|
||||
|
||||
function findLastCacheableToolIndex(tools: Array<Record<string, unknown>>) {
|
||||
for (let i = tools.length - 1; i >= 0; i--) {
|
||||
if (!tools[i].defer_loading) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
function cloneValue<T>(value: T): T {
|
||||
if (typeof structuredClone === "function") {
|
||||
return structuredClone(value);
|
||||
}
|
||||
return JSON.parse(JSON.stringify(value)) as T;
|
||||
}
|
||||
|
||||
function contentToText(content: unknown): string {
|
||||
if (typeof content === "string") {
|
||||
return content.trim();
|
||||
|
||||
@@ -99,7 +99,10 @@ async function validateResponseQuality(
|
||||
if (json?.output || json?.result || json?.data || json?.response) return { valid: true };
|
||||
if (json?.error) {
|
||||
const err = json.error as Record<string, unknown>;
|
||||
return { valid: false, reason: `upstream error in 200 body: ${err?.message || JSON.stringify(json.error).substring(0, 200)}` };
|
||||
return {
|
||||
valid: false,
|
||||
reason: `upstream error in 200 body: ${err?.message || JSON.stringify(json.error).substring(0, 200)}`,
|
||||
};
|
||||
}
|
||||
return { valid: true };
|
||||
}
|
||||
@@ -809,6 +812,16 @@ export async function handleComboChat({
|
||||
const modePack =
|
||||
typeof autoConfigSource.modePack === "string" ? autoConfigSource.modePack : undefined;
|
||||
|
||||
// Retrieve last known good provider (LKGP) for this combo/model (#919)
|
||||
let lastKnownGoodProvider: string | undefined;
|
||||
try {
|
||||
const { getLKGP } = await import("../../src/lib/localDb");
|
||||
const lkgp = await getLKGP(combo.name, combo.id || combo.name);
|
||||
if (lkgp) lastKnownGoodProvider = lkgp;
|
||||
} catch {
|
||||
/* ignore db errors */
|
||||
}
|
||||
|
||||
const candidates = await buildAutoCandidates(eligibleModels, combo.name);
|
||||
if (candidates.length > 0) {
|
||||
let selectedProvider = null;
|
||||
@@ -819,7 +832,7 @@ export async function handleComboChat({
|
||||
try {
|
||||
const decision = selectWithStrategy(
|
||||
candidates,
|
||||
{ taskType, requestHasTools },
|
||||
{ taskType, requestHasTools, lastKnownGoodProvider },
|
||||
routingStrategy
|
||||
);
|
||||
selectedProvider = decision.provider;
|
||||
@@ -980,6 +993,14 @@ export async function handleComboChat({
|
||||
fallbackCount,
|
||||
strategy,
|
||||
});
|
||||
|
||||
// Record last known good provider (LKGP) for this combo/model (#919)
|
||||
if (provider) {
|
||||
import("../../src/lib/localDb")
|
||||
.then(({ setLKGP }) => setLKGP(combo.name, combo.id || combo.name, provider))
|
||||
.catch(() => {});
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,8 @@ const PROVIDER_MODEL_ALIASES = {
|
||||
github: {
|
||||
"claude-4.5-opus": "claude-opus-4-5-20251101",
|
||||
"claude-opus-4.5": "claude-opus-4-5-20251101",
|
||||
"gemini-3-pro": "gemini-3-pro-preview",
|
||||
"gemini-3-pro": "gemini-3.1-pro-preview",
|
||||
"gemini-3-pro-preview": "gemini-3.1-pro-preview",
|
||||
"gemini-3-flash": "gemini-3-flash-preview",
|
||||
"raptor-mini": "oswe-vscode-prime",
|
||||
},
|
||||
|
||||
498
open-sse/services/qoderCli.ts
Normal file
498
open-sse/services/qoderCli.ts
Normal file
@@ -0,0 +1,498 @@
|
||||
import { spawn } from "child_process";
|
||||
import crypto from "crypto";
|
||||
|
||||
const DEFAULT_TIMEOUT_MS = 45_000;
|
||||
const DEFAULT_MAX_TURNS = "1";
|
||||
const QODER_DEFAULT_MODEL = "qoder-rome-30ba3b";
|
||||
|
||||
export const QODER_STATIC_MODELS = [
|
||||
{ id: "qoder-rome-30ba3b", name: "Qoder ROME" },
|
||||
{ id: "qwen3-coder-plus", name: "Qwen3 Coder Plus" },
|
||||
{ id: "qwen3-max", name: "Qwen3 Max" },
|
||||
{ id: "qwen3-vl-plus", name: "Qwen3 Vision Plus" },
|
||||
{ id: "kimi-k2-0905", name: "Kimi K2 0905" },
|
||||
{ id: "qwen3-max-preview", name: "Qwen3 Max Preview" },
|
||||
{ id: "kimi-k2", name: "Kimi K2" },
|
||||
{ id: "deepseek-v3.2", name: "DeepSeek V3.2" },
|
||||
{ id: "deepseek-r1", name: "DeepSeek R1" },
|
||||
{ id: "deepseek-v3", name: "DeepSeek V3" },
|
||||
{ id: "qwen3-32b", name: "Qwen3 32B" },
|
||||
{ id: "qwen3-235b-a22b-thinking-2507", name: "Qwen3 235B A22B Thinking 2507" },
|
||||
{ id: "qwen3-235b-a22b-instruct", name: "Qwen3 235B A22B Instruct" },
|
||||
{ id: "qwen3-235b", name: "Qwen3 235B" },
|
||||
];
|
||||
|
||||
type JsonRecord = Record<string, unknown>;
|
||||
|
||||
type QoderCliRunOptions = {
|
||||
token: string;
|
||||
prompt: string;
|
||||
stream: boolean;
|
||||
model?: string | null;
|
||||
workspace?: string | null;
|
||||
command?: string | null;
|
||||
signal?: AbortSignal | null;
|
||||
timeoutMs?: number;
|
||||
};
|
||||
|
||||
type QoderCliRunResult = {
|
||||
ok: boolean;
|
||||
code: number | null;
|
||||
stdout: string;
|
||||
stderr: string;
|
||||
timedOut: boolean;
|
||||
error: string | null;
|
||||
};
|
||||
|
||||
type QoderCliFailure = {
|
||||
status: number;
|
||||
message: string;
|
||||
code: string;
|
||||
};
|
||||
|
||||
function asRecord(value: unknown): JsonRecord {
|
||||
return value && typeof value === "object" && !Array.isArray(value) ? (value as JsonRecord) : {};
|
||||
}
|
||||
|
||||
function getString(value: unknown): string {
|
||||
return typeof value === "string" ? value : "";
|
||||
}
|
||||
|
||||
export function getQoderCliCommand(): string {
|
||||
const explicit = String(process.env.CLI_QODER_BIN || "").trim();
|
||||
return explicit || "qodercli";
|
||||
}
|
||||
|
||||
export function getQoderCliWorkspace(): string {
|
||||
const explicit = String(
|
||||
process.env.QODER_CLI_WORKSPACE || process.env.OMNIROUTE_QODER_WORKSPACE || ""
|
||||
).trim();
|
||||
if (explicit) return explicit;
|
||||
const home = String(process.env.HOME || "").trim();
|
||||
return home || process.cwd();
|
||||
}
|
||||
|
||||
export function normalizeQoderPatProviderData(providerSpecificData: JsonRecord = {}): JsonRecord {
|
||||
return {
|
||||
...providerSpecificData,
|
||||
authMode: "pat",
|
||||
transport: "qodercli",
|
||||
};
|
||||
}
|
||||
|
||||
export function isQoderCliTransport(providerSpecificData: unknown = {}): boolean {
|
||||
const data = asRecord(providerSpecificData);
|
||||
const transport = getString(data.transport).trim().toLowerCase();
|
||||
const authMode = getString(data.authMode).trim().toLowerCase();
|
||||
if (transport === "http-legacy") return false;
|
||||
return transport === "qodercli" || authMode === "pat";
|
||||
}
|
||||
|
||||
export function getStaticQoderModels() {
|
||||
return QODER_STATIC_MODELS.map((model) => ({ ...model }));
|
||||
}
|
||||
|
||||
export function mapQoderModelToLevel(model: string | null | undefined): string | null {
|
||||
const normalized = String(model || "").trim().toLowerCase();
|
||||
if (!normalized) return null;
|
||||
if (normalized.includes("deepseek-r1")) return "ultimate";
|
||||
if (normalized.includes("qwen3-max")) return "performance";
|
||||
if (normalized.includes("kimi-k2")) return "kmodel";
|
||||
if (normalized.includes("qwen3-coder")) return "qmodel";
|
||||
if (normalized.includes("qoder-rome")) return "qmodel";
|
||||
return "auto";
|
||||
}
|
||||
|
||||
function flattenMessageContent(content: unknown): string {
|
||||
if (typeof content === "string") return content;
|
||||
if (!Array.isArray(content)) return "";
|
||||
|
||||
return content
|
||||
.map((item) => {
|
||||
if (typeof item === "string") return item;
|
||||
if (!item || typeof item !== "object") return "";
|
||||
|
||||
const record = item as JsonRecord;
|
||||
const itemType = getString(record.type);
|
||||
if (itemType === "text" || itemType === "input_text") {
|
||||
return getString(record.text);
|
||||
}
|
||||
if (itemType === "image_url" || itemType === "input_image") {
|
||||
return "[Image omitted]";
|
||||
}
|
||||
return "";
|
||||
})
|
||||
.filter(Boolean)
|
||||
.join("\n");
|
||||
}
|
||||
|
||||
function formatMessage(message: unknown): string {
|
||||
if (!message || typeof message !== "object") return "";
|
||||
const record = message as JsonRecord;
|
||||
const role = getString(record.role).trim().toUpperCase() || "UNKNOWN";
|
||||
const base = flattenMessageContent(record.content);
|
||||
|
||||
if (role === "TOOL") {
|
||||
const toolName = getString(record.name).trim();
|
||||
return `TOOL${toolName ? ` (${toolName})` : ""}:\n${base}`.trim();
|
||||
}
|
||||
|
||||
const toolCalls = Array.isArray(record.tool_calls) ? record.tool_calls : [];
|
||||
if (toolCalls.length > 0) {
|
||||
const toolLines = toolCalls
|
||||
.map((toolCall) => {
|
||||
const toolRecord = asRecord(toolCall);
|
||||
const functionRecord = asRecord(toolRecord.function);
|
||||
const toolName =
|
||||
getString(functionRecord.name).trim() || getString(toolRecord.name).trim() || "tool";
|
||||
const toolArgs =
|
||||
getString(functionRecord.arguments).trim() || getString(toolRecord.arguments).trim();
|
||||
return `TOOL_CALL ${toolName}: ${toolArgs}`.trim();
|
||||
})
|
||||
.filter(Boolean)
|
||||
.join("\n");
|
||||
|
||||
return `${role}:\n${base}\n${toolLines}`.trim();
|
||||
}
|
||||
|
||||
return `${role}:\n${base}`.trim();
|
||||
}
|
||||
|
||||
export function buildQoderPrompt(body: unknown): string {
|
||||
const requestBody = asRecord(body);
|
||||
const lines = [
|
||||
"You are answering an OmniRoute OpenAI-compatible request through the Qoder CLI transport.",
|
||||
"Respond as a plain language model only.",
|
||||
"Do not use your own tools, do not inspect files, and do not run commands.",
|
||||
"Do not mention the adapter unless the user explicitly asks.",
|
||||
];
|
||||
|
||||
const tools = Array.isArray(requestBody.tools) ? requestBody.tools : [];
|
||||
if (tools.length > 0) {
|
||||
const toolNames = tools
|
||||
.map((tool) => {
|
||||
const toolRecord = asRecord(tool);
|
||||
const functionRecord =
|
||||
toolRecord.type === "function" ? asRecord(toolRecord.function) : toolRecord;
|
||||
return getString(functionRecord.name).trim();
|
||||
})
|
||||
.filter(Boolean)
|
||||
.join(", ");
|
||||
|
||||
if (toolNames) {
|
||||
lines.push(`Caller-side tools are available externally: ${toolNames}.`);
|
||||
lines.push("Do not call those tools yourself. Answer in assistant text only.");
|
||||
}
|
||||
}
|
||||
|
||||
const responseFormat = asRecord(requestBody.response_format);
|
||||
if (responseFormat.type === "json_object") {
|
||||
lines.push("Return only valid JSON.");
|
||||
} else if (
|
||||
responseFormat.type === "json_schema" &&
|
||||
responseFormat.json_schema &&
|
||||
typeof responseFormat.json_schema === "object"
|
||||
) {
|
||||
const jsonSchema = asRecord(responseFormat.json_schema);
|
||||
if (jsonSchema.schema && typeof jsonSchema.schema === "object") {
|
||||
lines.push(
|
||||
`Return only valid JSON matching this schema:\n${JSON.stringify(jsonSchema.schema, null, 2)}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const messages = Array.isArray(requestBody.messages)
|
||||
? requestBody.messages
|
||||
: Array.isArray(requestBody.input)
|
||||
? requestBody.input
|
||||
: [];
|
||||
|
||||
if (messages.length > 0) {
|
||||
lines.push("Conversation transcript:");
|
||||
for (const message of messages) {
|
||||
const formatted = formatMessage(message);
|
||||
if (formatted) lines.push(formatted);
|
||||
}
|
||||
}
|
||||
|
||||
lines.push("Reply now with the assistant response only.");
|
||||
return lines.filter(Boolean).join("\n\n");
|
||||
}
|
||||
|
||||
export function extractTextFromQoderEnvelope(parsed: unknown): string {
|
||||
const record = asRecord(parsed);
|
||||
const messageRecord = asRecord(record.message);
|
||||
const content = messageRecord.content ?? record.content ?? record.delta ?? record.text ?? null;
|
||||
|
||||
if (typeof content === "string") return content;
|
||||
if (!Array.isArray(content)) return "";
|
||||
|
||||
return content
|
||||
.map((item) => {
|
||||
const itemRecord = asRecord(item);
|
||||
const itemType = getString(itemRecord.type).trim();
|
||||
if (itemType === "text" || !itemType) {
|
||||
return getString(itemRecord.text);
|
||||
}
|
||||
return "";
|
||||
})
|
||||
.filter(Boolean)
|
||||
.join("");
|
||||
}
|
||||
|
||||
export function buildQoderCompletionPayload({
|
||||
model,
|
||||
text,
|
||||
}: {
|
||||
model?: string | null;
|
||||
text: string;
|
||||
}) {
|
||||
const created = Math.floor(Date.now() / 1000);
|
||||
return {
|
||||
id: `chatcmpl-${crypto.randomUUID()}`,
|
||||
object: "chat.completion",
|
||||
created,
|
||||
model: model || QODER_DEFAULT_MODEL,
|
||||
choices: [
|
||||
{
|
||||
index: 0,
|
||||
message: {
|
||||
role: "assistant",
|
||||
content: text,
|
||||
},
|
||||
finish_reason: "stop",
|
||||
},
|
||||
],
|
||||
usage: {
|
||||
prompt_tokens: 0,
|
||||
completion_tokens: 0,
|
||||
total_tokens: 0,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function buildQoderChunk({
|
||||
id,
|
||||
model,
|
||||
created,
|
||||
delta,
|
||||
finishReason = null,
|
||||
}: {
|
||||
id: string;
|
||||
model: string;
|
||||
created: number;
|
||||
delta: Record<string, unknown>;
|
||||
finishReason?: string | null;
|
||||
}) {
|
||||
return {
|
||||
id,
|
||||
object: "chat.completion.chunk",
|
||||
created,
|
||||
model,
|
||||
choices: [
|
||||
{
|
||||
index: 0,
|
||||
delta,
|
||||
finish_reason: finishReason,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
export function parseQoderCliFailure(stderrText: string, stdoutText = ""): QoderCliFailure {
|
||||
const stderr = String(stderrText || "").trim();
|
||||
const stdout = String(stdoutText || "").trim();
|
||||
const combined = `${stderr}\n${stdout}`.trim() || "Qoder CLI request failed";
|
||||
const normalized = combined.toLowerCase();
|
||||
|
||||
if (
|
||||
normalized.includes("invalid api key") ||
|
||||
normalized.includes("invalid token") ||
|
||||
normalized.includes("personal access token") ||
|
||||
(normalized.includes("unauthorized") && normalized.includes("qoder"))
|
||||
) {
|
||||
return { status: 401, message: combined, code: "upstream_auth_error" };
|
||||
}
|
||||
|
||||
if (
|
||||
normalized.includes("command not found") ||
|
||||
normalized.includes("not installed") ||
|
||||
normalized.includes("enoent") ||
|
||||
normalized.includes("no such file or directory")
|
||||
) {
|
||||
return { status: 503, message: combined, code: "runtime_error" };
|
||||
}
|
||||
|
||||
if (normalized.includes("timed out") || normalized.includes("timeout")) {
|
||||
return { status: 504, message: combined, code: "timeout" };
|
||||
}
|
||||
|
||||
return { status: 502, message: combined, code: "upstream_error" };
|
||||
}
|
||||
|
||||
export function createQoderErrorResponse(failure: QoderCliFailure): Response {
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
error: {
|
||||
message: failure.message,
|
||||
type: failure.status === 401 ? "authentication_error" : "provider_error",
|
||||
code: failure.code,
|
||||
},
|
||||
}),
|
||||
{
|
||||
status: failure.status,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
export async function runQoderCliCommand({
|
||||
token,
|
||||
prompt,
|
||||
stream,
|
||||
model,
|
||||
workspace,
|
||||
command,
|
||||
signal,
|
||||
timeoutMs = DEFAULT_TIMEOUT_MS,
|
||||
}: QoderCliRunOptions): Promise<QoderCliRunResult> {
|
||||
const cliCommand = String(command || getQoderCliCommand()).trim();
|
||||
const cwd = String(workspace || getQoderCliWorkspace()).trim() || process.cwd();
|
||||
const args = [
|
||||
"-q",
|
||||
"-p",
|
||||
prompt,
|
||||
"--max-turns",
|
||||
DEFAULT_MAX_TURNS,
|
||||
"--workspace",
|
||||
cwd,
|
||||
"--output-format",
|
||||
stream ? "stream-json" : "json",
|
||||
];
|
||||
|
||||
const level = mapQoderModelToLevel(model || QODER_DEFAULT_MODEL);
|
||||
if (level) {
|
||||
args.push("--model", level);
|
||||
}
|
||||
|
||||
return new Promise((resolve) => {
|
||||
let stdout = "";
|
||||
let stderr = "";
|
||||
let timedOut = false;
|
||||
let settled = false;
|
||||
|
||||
const child = spawn(cliCommand, args, {
|
||||
env: {
|
||||
...process.env,
|
||||
QODER_PERSONAL_ACCESS_TOKEN: token,
|
||||
},
|
||||
stdio: ["ignore", "pipe", "pipe"],
|
||||
...(process.platform === "win32" ? { shell: true } : {}),
|
||||
});
|
||||
|
||||
const cleanup = (result: QoderCliRunResult) => {
|
||||
if (settled) return;
|
||||
settled = true;
|
||||
clearTimeout(timer);
|
||||
signal?.removeEventListener?.("abort", abortHandler);
|
||||
resolve(result);
|
||||
};
|
||||
|
||||
const abortChild = () => {
|
||||
try {
|
||||
child.kill("SIGTERM");
|
||||
} catch {}
|
||||
setTimeout(() => {
|
||||
try {
|
||||
child.kill("SIGKILL");
|
||||
} catch {}
|
||||
}, 250).unref?.();
|
||||
};
|
||||
|
||||
const abortHandler = () => {
|
||||
abortChild();
|
||||
cleanup({
|
||||
ok: false,
|
||||
code: null,
|
||||
stdout: stdout.trim(),
|
||||
stderr: stderr.trim(),
|
||||
timedOut: false,
|
||||
error: "aborted",
|
||||
});
|
||||
};
|
||||
|
||||
if (signal?.aborted) {
|
||||
abortHandler();
|
||||
return;
|
||||
}
|
||||
|
||||
signal?.addEventListener?.("abort", abortHandler, { once: true });
|
||||
|
||||
const timer = setTimeout(() => {
|
||||
timedOut = true;
|
||||
abortChild();
|
||||
}, timeoutMs);
|
||||
|
||||
child.stdout.on("data", (chunk) => {
|
||||
stdout += chunk.toString();
|
||||
});
|
||||
|
||||
child.stderr.on("data", (chunk) => {
|
||||
stderr += chunk.toString();
|
||||
});
|
||||
|
||||
child.on("error", (error) => {
|
||||
cleanup({
|
||||
ok: false,
|
||||
code: null,
|
||||
stdout: stdout.trim(),
|
||||
stderr: stderr.trim(),
|
||||
timedOut,
|
||||
error: error?.message || "spawn_error",
|
||||
});
|
||||
});
|
||||
|
||||
child.on("close", (code) => {
|
||||
cleanup({
|
||||
ok: !timedOut && code === 0,
|
||||
code,
|
||||
stdout: stdout.trim(),
|
||||
stderr: stderr.trim(),
|
||||
timedOut,
|
||||
error: timedOut ? "timeout" : null,
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export async function validateQoderCliPat({
|
||||
apiKey,
|
||||
providerSpecificData = {},
|
||||
}: {
|
||||
apiKey: string;
|
||||
providerSpecificData?: JsonRecord;
|
||||
}) {
|
||||
const modelId =
|
||||
getString(providerSpecificData.validationModelId).trim() || getString(providerSpecificData.modelId).trim();
|
||||
const result = await runQoderCliCommand({
|
||||
token: apiKey,
|
||||
prompt: "Reply with OK only.",
|
||||
stream: false,
|
||||
model: modelId || QODER_DEFAULT_MODEL,
|
||||
workspace: getQoderCliWorkspace(),
|
||||
timeoutMs: 30_000,
|
||||
});
|
||||
|
||||
if (!result.ok) {
|
||||
const failure = parseQoderCliFailure(result.stderr || result.error || "", result.stdout);
|
||||
return {
|
||||
valid: false,
|
||||
error: failure.status === 401 ? "Invalid API key" : failure.message,
|
||||
unsupported: false,
|
||||
};
|
||||
}
|
||||
|
||||
return { valid: true, error: null, unsupported: false };
|
||||
}
|
||||
@@ -200,6 +200,11 @@ function getLimiterKey(provider, connectionId, model = null) {
|
||||
if (provider === "codex" && model) {
|
||||
return `${provider}:${getCodexRateLimitKey(connectionId, model)}`;
|
||||
}
|
||||
// Gemini AI Studio has per-model quotas — use model-scoped limiter keys
|
||||
// so a 429 on one model doesn't pause requests for other models.
|
||||
if (provider === "gemini" && model) {
|
||||
return `${provider}:${connectionId}:${model}`;
|
||||
}
|
||||
return `${provider}:${connectionId}`;
|
||||
}
|
||||
|
||||
@@ -570,7 +575,7 @@ export function updateFromResponseBody(provider, connectionId, responseBody, sta
|
||||
const { retryAfterMs, reason } = parseRetryAfterFromBody(responseBody);
|
||||
|
||||
if (retryAfterMs && retryAfterMs > 0) {
|
||||
const limiter = getLimiter(provider, connectionId, null);
|
||||
const limiter = getLimiter(provider, connectionId, model);
|
||||
console.log(
|
||||
`🚫 [RATE-LIMIT] ${provider}:${connectionId.slice(0, 8)} — body-parsed retry: ${Math.ceil(retryAfterMs / 1000)}s (${reason})`
|
||||
);
|
||||
|
||||
@@ -542,6 +542,14 @@ export async function refreshKiroToken(refreshToken, providerSpecificData, log)
|
||||
* Specialized refresh for Qoder OAuth tokens
|
||||
*/
|
||||
export async function refreshIflowToken(refreshToken, log) {
|
||||
if (!OAUTH_ENDPOINTS.qoder.token || !PROVIDERS.qoder.clientId || !PROVIDERS.qoder.clientSecret) {
|
||||
log?.warn?.(
|
||||
"TOKEN_REFRESH",
|
||||
"Qoder OAuth refresh skipped: browser OAuth is not configured in this environment"
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
const basicAuth = btoa(`${PROVIDERS.qoder.clientId}:${PROVIDERS.qoder.clientSecret}`);
|
||||
|
||||
const response = await fetch(OAUTH_ENDPOINTS.qoder.token, {
|
||||
|
||||
@@ -84,7 +84,7 @@ export function convertOpenAIContentToParts(content) {
|
||||
const mimeType = mimePart.split(";")[0];
|
||||
|
||||
parts.push({
|
||||
inlineData: { mime_type: mimeType, data: data },
|
||||
inlineData: { mimeType, data },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,6 +147,23 @@ export function claudeToGeminiRequest(model, body, stream) {
|
||||
if (parts.length > 0) {
|
||||
// Map Claude roles to Gemini roles
|
||||
const geminiRole = msg.role === "assistant" ? "model" : "user";
|
||||
|
||||
// Gemini 3+ requires thoughtSignature as a sibling part in model content
|
||||
// that contains functionCall parts. Inject if not already present from
|
||||
// a thinking block. (#927)
|
||||
if (geminiRole === "model") {
|
||||
const hasFunctionCall = parts.some((p) => p.functionCall);
|
||||
const hasSignature = parts.some((p) => p.thoughtSignature);
|
||||
if (hasFunctionCall && !hasSignature) {
|
||||
// Insert before the first functionCall part
|
||||
const fcIndex = parts.findIndex((p) => p.functionCall);
|
||||
parts.splice(fcIndex, 0, {
|
||||
thoughtSignature: DEFAULT_THINKING_GEMINI_SIGNATURE,
|
||||
text: "",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
result.contents.push({ role: geminiRole, parts });
|
||||
}
|
||||
}
|
||||
@@ -175,7 +192,7 @@ export function claudeToGeminiRequest(model, body, stream) {
|
||||
if (body.thinking?.type === "enabled" && body.thinking.budget_tokens) {
|
||||
result.generationConfig.thinkingConfig = {
|
||||
thinkingBudget: body.thinking.budget_tokens,
|
||||
include_thoughts: true,
|
||||
includeThoughts: true,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -92,11 +92,13 @@ function convertGeminiContent(content) {
|
||||
parts.push({ type: "text", text: part.text });
|
||||
}
|
||||
|
||||
if (part.inlineData) {
|
||||
if (part.inlineData || part.inline_data) {
|
||||
const data = part.inlineData || part.inline_data;
|
||||
const mimeType = data.mimeType || data.mime_type || "image/png";
|
||||
parts.push({
|
||||
type: "image_url",
|
||||
image_url: {
|
||||
url: `data:${part.inlineData.mimeType};base64,${part.inlineData.data}`,
|
||||
url: `data:${mimeType};base64,${data.data}`,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ type GeminiGenerationConfig = {
|
||||
maxOutputTokens?: unknown;
|
||||
thinkingConfig?: {
|
||||
thinkingBudget: number;
|
||||
include_thoughts: boolean;
|
||||
includeThoughts: boolean;
|
||||
};
|
||||
responseMimeType?: string;
|
||||
responseSchema?: unknown;
|
||||
@@ -156,7 +156,6 @@ function openaiToGeminiBase(model, body, stream) {
|
||||
});
|
||||
parts.push({
|
||||
thoughtSignature: DEFAULT_THINKING_GEMINI_SIGNATURE,
|
||||
text: "",
|
||||
});
|
||||
}
|
||||
|
||||
@@ -168,14 +167,24 @@ function openaiToGeminiBase(model, body, stream) {
|
||||
}
|
||||
|
||||
if (msg.tool_calls && Array.isArray(msg.tool_calls)) {
|
||||
// Gemini 3+ requires thoughtSignature as a sibling part in model content
|
||||
// that contains functionCall parts. If no reasoning_content was present
|
||||
// (which already injects the signature above), inject one now. (#927)
|
||||
const hasSignatureAlready = parts.some((p) => p.thoughtSignature);
|
||||
if (!hasSignatureAlready) {
|
||||
parts.push({
|
||||
thoughtSignature: DEFAULT_THINKING_GEMINI_SIGNATURE,
|
||||
});
|
||||
}
|
||||
|
||||
const toolCallIds = [];
|
||||
for (const tc of msg.tool_calls) {
|
||||
if (tc.type !== "function") continue;
|
||||
|
||||
const args = tryParseJSON(tc.function?.arguments || "{}");
|
||||
// Do NOT include thoughtSignature on functionCall parts — it is only valid
|
||||
// on thinking/reasoning parts and causes HTTP 400 "invalid argument" from the
|
||||
// Gemini API when present on a functionCall part (#725).
|
||||
// Do NOT include thoughtSignature ON the functionCall part itself — it is
|
||||
// only valid as a separate sibling part. Including it inside functionCall
|
||||
// causes HTTP 400 "invalid argument" (#725).
|
||||
parts.push({
|
||||
functionCall: {
|
||||
id: tc.id,
|
||||
@@ -306,7 +315,7 @@ export function openaiToGeminiCLIRequest(model, body, stream) {
|
||||
const budget = budgetMap[body.reasoning_effort] || getDefaultThinkingBudget(model) || 8192;
|
||||
gemini.generationConfig.thinkingConfig = {
|
||||
thinkingBudget: budget,
|
||||
include_thoughts: true,
|
||||
includeThoughts: true,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -314,7 +323,7 @@ export function openaiToGeminiCLIRequest(model, body, stream) {
|
||||
if (body.thinking?.type === "enabled" && body.thinking.budget_tokens) {
|
||||
gemini.generationConfig.thinkingConfig = {
|
||||
thinkingBudget: body.thinking.budget_tokens,
|
||||
include_thoughts: true,
|
||||
includeThoughts: true,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -435,7 +444,7 @@ function wrapInCloudCodeEnvelopeForClaude(model, claudeRequest, credentials = nu
|
||||
} else if (block.type === "image" && block.source) {
|
||||
parts.push({
|
||||
inlineData: {
|
||||
mime_type: block.source.media_type,
|
||||
mimeType: block.source.media_type,
|
||||
data: block.source.data,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -171,6 +171,11 @@ export function geminiToClaudeResponse(chunk, state) {
|
||||
stopReason = "tool_use";
|
||||
} else if (reason === "max_tokens" || reason === "length") {
|
||||
stopReason = "max_tokens";
|
||||
} else if (reason === "safety" || reason === "recitation" || reason === "blocklist") {
|
||||
// Content blocked by Gemini safety. Any text streamed before this finish
|
||||
// reason has already been emitted to the client — this is unavoidable in
|
||||
// SSE streaming. Map to end_turn (Claude has no "content blocked" reason).
|
||||
stopReason = "end_turn";
|
||||
} else {
|
||||
stopReason = "end_turn";
|
||||
}
|
||||
|
||||
@@ -225,6 +225,11 @@ export function geminiToOpenAIResponse(chunk, state) {
|
||||
if (finishReason === "stop" && state.toolCalls.size > 0) {
|
||||
finishReason = "tool_calls";
|
||||
}
|
||||
// Content blocked by Gemini safety filters — pass through as "content_filter"
|
||||
// so downstream clients can distinguish from normal completion.
|
||||
if (finishReason === "safety" || finishReason === "recitation" || finishReason === "blocklist") {
|
||||
finishReason = "content_filter";
|
||||
}
|
||||
|
||||
const finalChunk: Record<string, unknown> = {
|
||||
id: `chatcmpl-${state.messageId}`,
|
||||
|
||||
@@ -14,7 +14,7 @@ export function openaiToOpenAIResponsesResponse(chunk, state) {
|
||||
return flushEvents(state);
|
||||
}
|
||||
|
||||
// Capture usage from any chunk that carries it (usage-only chunks OR final chunks with finish_reason)
|
||||
// Capture usage from all chunks that carry it (usage-only chunks OR final chunks with finish_reason)
|
||||
// Normalize Chat Completions format (prompt_tokens/completion_tokens) to Responses API format
|
||||
// (input_tokens/output_tokens) so response.completed always has the fields Codex expects.
|
||||
if (chunk.usage) {
|
||||
@@ -624,11 +624,13 @@ export function openaiResponsesToOpenAIResponse(chunk, state) {
|
||||
object: "chat.completion.chunk",
|
||||
created: state.created,
|
||||
model: state.model || "gpt-4",
|
||||
choices: [{
|
||||
index: 0,
|
||||
delta: { reasoning_content: reasoningDelta },
|
||||
finish_reason: null,
|
||||
}],
|
||||
choices: [
|
||||
{
|
||||
index: 0,
|
||||
delta: { reasoning_content: reasoningDelta },
|
||||
finish_reason: null,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
import { ProxyAgent, type Dispatcher } from "undici";
|
||||
import { Agent, ProxyAgent, type Dispatcher } from "undici";
|
||||
import { socksDispatcher } from "fetch-socks";
|
||||
import { getUpstreamTimeoutConfig } from "@/shared/utils/runtimeTimeouts";
|
||||
|
||||
const DISPATCHER_CACHE_KEY = Symbol.for("omniroute.proxyDispatcher.cache");
|
||||
const DEFAULT_DISPATCHER_KEY = Symbol.for("omniroute.proxyDispatcher.default");
|
||||
const SUPPORTED_PROTOCOLS = new Set(["http:", "https:", "socks5:"]);
|
||||
|
||||
type DispatcherCache = Map<string, Dispatcher>;
|
||||
type GlobalWithDispatcherCache = typeof globalThis & {
|
||||
[DISPATCHER_CACHE_KEY]?: DispatcherCache;
|
||||
[DEFAULT_DISPATCHER_KEY]?: Dispatcher;
|
||||
};
|
||||
type SocksDispatcherOptions = {
|
||||
type: number;
|
||||
@@ -38,6 +41,30 @@ function getDispatcherCache(): DispatcherCache {
|
||||
export function clearDispatcherCache() {
|
||||
const cache = getDispatcherCache();
|
||||
cache.clear();
|
||||
|
||||
const globalWithCache = globalThis as GlobalWithDispatcherCache;
|
||||
delete globalWithCache[DEFAULT_DISPATCHER_KEY];
|
||||
}
|
||||
|
||||
function getDispatcherOptions() {
|
||||
const timeouts = getUpstreamTimeoutConfig(process.env, (message) => {
|
||||
console.warn(`[ProxyDispatcher] ${message}`);
|
||||
});
|
||||
|
||||
return {
|
||||
headersTimeout: timeouts.fetchHeadersTimeoutMs,
|
||||
bodyTimeout: timeouts.fetchBodyTimeoutMs,
|
||||
connectTimeout: timeouts.fetchConnectTimeoutMs,
|
||||
keepAliveTimeout: timeouts.fetchKeepAliveTimeoutMs,
|
||||
};
|
||||
}
|
||||
|
||||
export function getDefaultDispatcher(): Dispatcher {
|
||||
const globalWithCache = globalThis as GlobalWithDispatcherCache;
|
||||
if (!globalWithCache[DEFAULT_DISPATCHER_KEY]) {
|
||||
globalWithCache[DEFAULT_DISPATCHER_KEY] = new Agent(getDispatcherOptions());
|
||||
}
|
||||
return globalWithCache[DEFAULT_DISPATCHER_KEY];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -180,6 +207,7 @@ export function proxyConfigToUrl(
|
||||
export function createProxyDispatcher(proxyUrl: string): Dispatcher {
|
||||
const normalizedUrl = normalizeProxyUrl(proxyUrl, "proxy dispatcher");
|
||||
const dispatcherCache = getDispatcherCache();
|
||||
const dispatcherOptions = getDispatcherOptions();
|
||||
|
||||
let dispatcher = dispatcherCache.get(normalizedUrl);
|
||||
if (dispatcher) return dispatcher;
|
||||
@@ -197,10 +225,14 @@ export function createProxyDispatcher(proxyUrl: string): Dispatcher {
|
||||
if (parsed.username) socksOptions.userId = decodeURIComponent(parsed.username);
|
||||
if (parsed.password) socksOptions.password = decodeURIComponent(parsed.password);
|
||||
dispatcher = socksDispatcher(
|
||||
socksOptions as Parameters<typeof socksDispatcher>[0]
|
||||
socksOptions as Parameters<typeof socksDispatcher>[0],
|
||||
dispatcherOptions
|
||||
) as Dispatcher;
|
||||
} else {
|
||||
dispatcher = new ProxyAgent(normalizedUrl);
|
||||
dispatcher = new ProxyAgent({
|
||||
uri: normalizedUrl,
|
||||
...dispatcherOptions,
|
||||
});
|
||||
}
|
||||
|
||||
dispatcherCache.set(normalizedUrl, dispatcher);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { AsyncLocalStorage } from "node:async_hooks";
|
||||
import {
|
||||
createProxyDispatcher,
|
||||
getDefaultDispatcher,
|
||||
normalizeProxyUrl,
|
||||
proxyConfigToUrl,
|
||||
proxyUrlForLogs,
|
||||
@@ -196,7 +197,10 @@ async function patchedFetch(input: RequestInfo | URL, options: FetchWithDispatch
|
||||
if (store) store.used = false;
|
||||
}
|
||||
}
|
||||
return originalFetchWithDispatcher(input, options);
|
||||
return originalFetchWithDispatcher(input, {
|
||||
...options,
|
||||
dispatcher: getDefaultDispatcher(),
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
@@ -585,9 +585,9 @@ export function createSSEStream(options: StreamOptions = {}) {
|
||||
// Content for call log is accumulated only from parsed (above) to avoid double-counting;
|
||||
// do not add again from item here.
|
||||
|
||||
// #723, #727: Sanitize intermediate stream chunks if target is OpenAI format loop
|
||||
// #723, #727: Sanitize intermediate stream chunks if source (client) is OpenAI format
|
||||
let itemSanitized: Record<string, unknown> = item;
|
||||
if (targetFormat === FORMATS.OPENAI || targetFormat === FORMATS.OPENAI_RESPONSES) {
|
||||
if (sourceFormat === FORMATS.OPENAI || sourceFormat === FORMATS.OPENAI_RESPONSES) {
|
||||
itemSanitized = sanitizeStreamingChunk(itemSanitized) as Record<string, unknown>;
|
||||
|
||||
// Extract reasoning tags from content if translation generated them
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { createRequire } from "module";
|
||||
import { getTlsClientTimeoutConfig } from "@/shared/utils/runtimeTimeouts";
|
||||
|
||||
const require = createRequire(import.meta.url);
|
||||
|
||||
@@ -102,6 +103,9 @@ class TlsClient {
|
||||
async fetch(url: string, options: FetchOptions = {}) {
|
||||
const session = await this.getSession();
|
||||
if (!session) throw new Error("wreq-js not available");
|
||||
const { timeoutMs } = getTlsClientTimeoutConfig(process.env, (message) => {
|
||||
console.warn(`[TlsClient] ${message}`);
|
||||
});
|
||||
|
||||
const method = (options.method || "GET").toUpperCase();
|
||||
|
||||
@@ -110,6 +114,7 @@ class TlsClient {
|
||||
headers: normalizeHeaders(options.headers),
|
||||
body: options.body,
|
||||
redirect: options.redirect === "manual" ? "manual" : "follow",
|
||||
timeout: timeoutMs,
|
||||
};
|
||||
|
||||
// Pass signal through if available
|
||||
@@ -129,4 +134,6 @@ class TlsClient {
|
||||
}
|
||||
}
|
||||
|
||||
export default new TlsClient();
|
||||
const tlsClient = new TlsClient();
|
||||
|
||||
export default tlsClient;
|
||||
|
||||
27
package-lock.json
generated
27
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "omniroute",
|
||||
"version": "3.4.4",
|
||||
"version": "3.4.7",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "omniroute",
|
||||
"version": "3.4.4",
|
||||
"version": "3.4.7",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"workspaces": [
|
||||
@@ -625,13 +625,6 @@
|
||||
"lodash-es": "4.17.23"
|
||||
}
|
||||
},
|
||||
"node_modules/@chevrotain/cst-dts-gen/node_modules/lodash-es": {
|
||||
"version": "4.17.23",
|
||||
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.23.tgz",
|
||||
"integrity": "sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg==",
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@chevrotain/gast": {
|
||||
"version": "11.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@chevrotain/gast/-/gast-11.1.2.tgz",
|
||||
@@ -643,13 +636,6 @@
|
||||
"lodash-es": "4.17.23"
|
||||
}
|
||||
},
|
||||
"node_modules/@chevrotain/gast/node_modules/lodash-es": {
|
||||
"version": "4.17.23",
|
||||
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.23.tgz",
|
||||
"integrity": "sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg==",
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@chevrotain/regexp-to-ast": {
|
||||
"version": "11.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@chevrotain/regexp-to-ast/-/regexp-to-ast-11.1.2.tgz",
|
||||
@@ -8440,13 +8426,6 @@
|
||||
"chevrotain": "^11.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/chevrotain/node_modules/lodash-es": {
|
||||
"version": "4.17.23",
|
||||
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.23.tgz",
|
||||
"integrity": "sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg==",
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/chownr": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
|
||||
@@ -21068,7 +21047,7 @@
|
||||
},
|
||||
"open-sse": {
|
||||
"name": "@omniroute/open-sse",
|
||||
"version": "3.4.4"
|
||||
"version": "3.4.7"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "omniroute",
|
||||
"version": "3.4.4",
|
||||
"version": "3.4.7",
|
||||
"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": {
|
||||
@@ -170,6 +170,7 @@
|
||||
]
|
||||
},
|
||||
"overrides": {
|
||||
"lodash-es": "^4.18.1",
|
||||
"dompurify": "^3.3.2",
|
||||
"path-to-regexp": "^8.4.0",
|
||||
"react": "$react",
|
||||
|
||||
@@ -92,9 +92,9 @@ const LOCALE_SPECS = [
|
||||
docsName: "Deutsch",
|
||||
},
|
||||
{
|
||||
code: "in",
|
||||
code: "hi",
|
||||
googleTl: "hi",
|
||||
label: "IN",
|
||||
label: "HI",
|
||||
flag: "🇮🇳",
|
||||
languageName: "Hindi (India)",
|
||||
readmeName: "हिन्दी",
|
||||
|
||||
241
scripts/i18n/untranslatable-keys.json
Normal file
241
scripts/i18n/untranslatable-keys.json
Normal file
@@ -0,0 +1,241 @@
|
||||
{
|
||||
"description": "Keys that should remain untranslated (technical terms, brand names, ICU formats, placeholders, etc.)",
|
||||
"keys": [
|
||||
"apiManager.modelsCount",
|
||||
"a2aDashboard.metadata",
|
||||
"a2aDashboard.ok",
|
||||
"a2aDashboard.smokeSendSuccess",
|
||||
"a2aDashboard.url",
|
||||
"cache.behaviorTwoTier",
|
||||
"cache.dbEntriesSub",
|
||||
"cache.model",
|
||||
"cache.provider",
|
||||
"cliTools.baseUrlPlaceholder",
|
||||
"cliTools.guides.opencode.steps.3.desc",
|
||||
"cliTools.guides.windsurf.steps.1.desc",
|
||||
"cliTools.guides.windsurf.steps.2.desc",
|
||||
"cliTools.guides.windsurf.steps.3.desc",
|
||||
"cliTools.guides.windsurf.steps.4.desc",
|
||||
"cliTools.model",
|
||||
"cliTools.platforms",
|
||||
"cliTools.startMitm",
|
||||
"cliTools.stopMitm",
|
||||
"cliTools.toolDescriptions.claude",
|
||||
"cliTools.toolDescriptions.codex",
|
||||
"cliTools.toolDescriptions.copilot",
|
||||
"cliTools.toolDescriptions.cursor",
|
||||
"cliTools.toolDescriptions.windsurf",
|
||||
"combos.roundRobin",
|
||||
"common.alias",
|
||||
"common.base64url",
|
||||
"common.better-sqlite3",
|
||||
"common.builder-id",
|
||||
"common.cookie",
|
||||
"common.hex",
|
||||
"common.http",
|
||||
"common.id",
|
||||
"common.idc",
|
||||
"common.keytar",
|
||||
"common.limit",
|
||||
"common.model",
|
||||
"common.oauth",
|
||||
"common.offset",
|
||||
"common.selfsigned",
|
||||
"common.social-github",
|
||||
"common.social-google",
|
||||
"common.text",
|
||||
"common.undici",
|
||||
"common.web",
|
||||
"docs.clientCherryStudioTitle",
|
||||
"docs.clientClaudeTitle",
|
||||
"docs.clientCursorTitle",
|
||||
"docs.github",
|
||||
"docs.protocolA2aTitle",
|
||||
"docs.protocolMcpTitle",
|
||||
"docs.providerTypeOAuth",
|
||||
"endpoint.chat",
|
||||
"endpoint.chatCompletions",
|
||||
"endpoint.cloudProxy",
|
||||
"endpoint.cloudflaredDisable",
|
||||
"endpoint.cloudflaredIdleNote",
|
||||
"endpoint.cloudflaredRequestFailed",
|
||||
"endpoint.cloudflaredStarted",
|
||||
"endpoint.cloudflaredStopped",
|
||||
"endpoint.cloudflaredTitle",
|
||||
"endpoint.mcpCardTitle",
|
||||
"endpoint.rerank",
|
||||
"header.a2a",
|
||||
"header.mcp",
|
||||
"health.cpu",
|
||||
"health.latencyP50",
|
||||
"health.latencyP95",
|
||||
"health.latencyP99",
|
||||
"health.millisecondsShort",
|
||||
"health.notAvailable",
|
||||
"health.ok",
|
||||
"home.aliasLabel",
|
||||
"home.oauthLabel",
|
||||
"landing.brandName",
|
||||
"landing.flowProviderAnthropic",
|
||||
"landing.flowProviderGemini",
|
||||
"landing.flowProviderGithubCopilot",
|
||||
"landing.flowProviderOpenAI",
|
||||
"landing.flowToolClaudeCode",
|
||||
"landing.flowToolCline",
|
||||
"landing.flowToolCursor",
|
||||
"landing.flowToolOpenAICodex",
|
||||
"landing.github",
|
||||
"landing.npm",
|
||||
"legal.listSeparator",
|
||||
"legal.terms",
|
||||
"legal.privacy",
|
||||
"loggers.inputTokens",
|
||||
"loggers.modelAZ",
|
||||
"loggers.modelZA",
|
||||
"loggers.outputTokens",
|
||||
"loggers.proxy",
|
||||
"logs.notAvailable",
|
||||
"logs.endpoint",
|
||||
"logs.proxy",
|
||||
"logs.console",
|
||||
"logs.request",
|
||||
"logs.audit",
|
||||
"mcpDashboard.apiKeyIdPlaceholder",
|
||||
"mcpDashboard.offline",
|
||||
"mcpDashboard.online",
|
||||
"mcpDashboard.pid",
|
||||
"mcpDashboard.tableAudit",
|
||||
"media.model",
|
||||
"media.prompt",
|
||||
"media.interpolation",
|
||||
"media.upscale",
|
||||
"media.samples",
|
||||
"modals.awsBuilderId",
|
||||
"modals.awsIamIdentity",
|
||||
"modals.awsRegion",
|
||||
"onboarding.test",
|
||||
"providers.anthropic",
|
||||
"providers.anthropicBaseUrlPlaceholder",
|
||||
"providers.anthropicCompatibleModelPlaceholder",
|
||||
"providers.anthropicCompatibleName",
|
||||
"providers.anthropicPrefixPlaceholder",
|
||||
"providers.autoPriority",
|
||||
"providers.chat",
|
||||
"providers.chatCompletions",
|
||||
"providers.chatCompletionsPath",
|
||||
"providers.chatPathPlaceholder",
|
||||
"providers.codexAuthAppliedLocal",
|
||||
"providers.codexAuthApplyFailed",
|
||||
"providers.codexAuthExported",
|
||||
"providers.codexAuthExportFailed",
|
||||
"providers.compatProtocolClaude",
|
||||
"providers.compatProtocolOpenAI",
|
||||
"providers.compatProtocolOpenAIResponses",
|
||||
"providers.email",
|
||||
"providers.messages",
|
||||
"providers.messagesApi",
|
||||
"providers.messagesPath",
|
||||
"providers.millisecondsAbbr",
|
||||
"providers.modelsPathPlaceholder",
|
||||
"providers.modeTest",
|
||||
"providers.oauthLabel",
|
||||
"providers.oauth2Label",
|
||||
"providers.okShort",
|
||||
"providers.openai",
|
||||
"providers.openaiBaseUrlPlaceholder",
|
||||
"providers.openaiCompatibleModelPlaceholder",
|
||||
"providers.openaiCompatibleName",
|
||||
"providers.openaiPrefixPlaceholder",
|
||||
"providers.openRouterModelPlaceholder",
|
||||
"providers.prefixLabel",
|
||||
"providers.proxy",
|
||||
"providers.proxyConfiguredBySource",
|
||||
"providers.responses",
|
||||
"providers.responsesApi",
|
||||
"providers.responsesPath",
|
||||
"search.domainPlaceholder",
|
||||
"search.rawJson",
|
||||
"search.rerank",
|
||||
"search.searchTypeWeb",
|
||||
"search.search",
|
||||
"search.searchTools",
|
||||
"search.webSearch",
|
||||
"search.fileSearch",
|
||||
"settings.ai",
|
||||
"settings.aliasPatternPlaceholder",
|
||||
"settings.aliasTargetPlaceholder",
|
||||
"settings.auto",
|
||||
"settings.model",
|
||||
"settings.modelNamePlaceholder",
|
||||
"settings.ms",
|
||||
"settings.providersCommaSeparatedPlaceholder",
|
||||
"settings.proxy",
|
||||
"settings.p2c",
|
||||
"settings.reasoning",
|
||||
"settings.roundRobin",
|
||||
"settings.rpm",
|
||||
"settings.status",
|
||||
"settings.whitelabeling",
|
||||
"settings.theme",
|
||||
"settings.language",
|
||||
"settings.currency",
|
||||
"settings.timezone",
|
||||
"sidebar.cache",
|
||||
"sidebar.cacheShort",
|
||||
"sidebar.cliSection",
|
||||
"sidebar.debug",
|
||||
"sidebar.debugSection",
|
||||
"sidebar.helpSection",
|
||||
"sidebar.primarySection",
|
||||
"sidebar.systemSection",
|
||||
"sidebar.version",
|
||||
"stats.requests",
|
||||
"stats.tokens",
|
||||
"stats.latency",
|
||||
"stats.errors",
|
||||
"themesPage.dark",
|
||||
"themesPage.light",
|
||||
"themesPage.system",
|
||||
"translator.auto",
|
||||
"translator.chatTester",
|
||||
"translator.millisecondsShort",
|
||||
"translator.model",
|
||||
"translator.notAvailableSymbol",
|
||||
"translator.ok",
|
||||
"translator.scenarioThinking",
|
||||
"translator.status",
|
||||
"translator.templateNames.thinking",
|
||||
"translator.translate",
|
||||
"translator.translateFrom",
|
||||
"translator.translateTo",
|
||||
"translator.detect",
|
||||
"translator.detectedLanguage",
|
||||
"usage.columnModel",
|
||||
"usage.columnStatus",
|
||||
"usage.detailsRegex",
|
||||
"usage.durationHoursShort",
|
||||
"usage.durationMillisecondsShort",
|
||||
"usage.durationMinutesShort",
|
||||
"usage.durationSecondsShort",
|
||||
"usage.latencyP50",
|
||||
"usage.latencyP95",
|
||||
"usage.latencyP99",
|
||||
"usage.notApplicable",
|
||||
"usage.notAvailableSymbol",
|
||||
"usage.passSuffix",
|
||||
"usage.proxyTab",
|
||||
"usage.reasonSeparator",
|
||||
"usage.tierPlus",
|
||||
"usage.tierPro",
|
||||
"usage.tierUltra",
|
||||
"usage.totalRequests",
|
||||
"usage.totalTokens",
|
||||
"usage.inputTokens",
|
||||
"usage.outputTokens",
|
||||
"usage.promptTokens",
|
||||
"usage.completionTokens",
|
||||
"usage.cacheReadTokens",
|
||||
"usage.cacheWriteTokens",
|
||||
"usage.warningThresholdPlaceholder"
|
||||
]
|
||||
}
|
||||
@@ -254,6 +254,25 @@ if (existsSync(mitmSrc)) {
|
||||
}
|
||||
}
|
||||
|
||||
// ── Step 8.5: Bundle MCP server ────────────────────────────
|
||||
const mcpSrcFile = join(ROOT, "open-sse", "mcp-server", "server.ts");
|
||||
const mcpDestDir = join(APP_DIR, "open-sse", "mcp-server");
|
||||
const mcpDestFile = join(mcpDestDir, "server.js");
|
||||
|
||||
if (existsSync(mcpSrcFile)) {
|
||||
console.log(" 🔨 Bundling MCP Server (TypeScript → JavaScript)...");
|
||||
mkdirSync(mcpDestDir, { recursive: true });
|
||||
try {
|
||||
execSync(
|
||||
`npx esbuild ${JSON.stringify(mcpSrcFile)} --bundle --platform=node --packages=external --format=esm --outfile=${JSON.stringify(mcpDestFile)}`,
|
||||
{ cwd: ROOT, stdio: "inherit" }
|
||||
);
|
||||
console.log(" ✅ MCP Server bundled to app/open-sse/mcp-server/server.js");
|
||||
} catch (err) {
|
||||
console.warn(" ⚠️ MCP Server bundle error:", err.message);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Step 9: Copy shared utilities needed at runtime ────────
|
||||
const sharedApiKey = join(ROOT, "src", "shared", "utils", "apiKey.js");
|
||||
const sharedApiKeyDest = join(APP_DIR, "src", "shared", "utils");
|
||||
|
||||
@@ -22,11 +22,11 @@ from typing import Dict, List, Set, Tuple, Any
|
||||
import argparse
|
||||
|
||||
# Colors (ANSI)
|
||||
RED = '\033[0;31m'
|
||||
GREEN = '\033[0;32m'
|
||||
YELLOW = '\033[1;33m'
|
||||
BLUE = '\033[0;34m'
|
||||
NC = '\033[0m'
|
||||
RED = "\033[0;31m"
|
||||
GREEN = "\033[0;32m"
|
||||
YELLOW = "\033[1;33m"
|
||||
BLUE = "\033[0;34m"
|
||||
NC = "\033[0m"
|
||||
|
||||
# Configuration - find repo root relative to this script
|
||||
_script_dir = Path(__file__).parent.resolve()
|
||||
@@ -39,113 +39,37 @@ else:
|
||||
MESSAGES_DIR = SCRIPT_DIR / "src" / "i18n" / "messages"
|
||||
SOURCE_FILE = MESSAGES_DIR / "en.json"
|
||||
|
||||
|
||||
# Get target language from env or argument
|
||||
def get_target_lang() -> str:
|
||||
"""Get target language from ENV or CLI argument."""
|
||||
# First check environment variable
|
||||
env_lang = os.environ.get('TRANSLATION_LANG')
|
||||
env_lang = os.environ.get("TRANSLATION_LANG")
|
||||
if env_lang:
|
||||
return env_lang
|
||||
|
||||
|
||||
# Then check command line argument (will be set in main)
|
||||
if hasattr(get_target_lang, 'cli_lang'):
|
||||
if hasattr(get_target_lang, "cli_lang"):
|
||||
return get_target_lang.cli_lang
|
||||
|
||||
|
||||
# Default to cs for backwards compatibility
|
||||
return "cs"
|
||||
|
||||
|
||||
# Keys that should NOT be translated (technical terms, proper names, etc.)
|
||||
UNTRANSLATABLE_KEYS = {
|
||||
# ICU/Plural formats
|
||||
"apiManager.modelsCount",
|
||||
# Technical/Protocol names
|
||||
"a2aDashboard.metadata",
|
||||
"a2aDashboard.ok",
|
||||
"a2aDashboard.url",
|
||||
"cliTools.baseUrlPlaceholder",
|
||||
"cliTools.platforms",
|
||||
"cliTools.toolDescriptions.claude",
|
||||
"cliTools.toolDescriptions.codex",
|
||||
"cliTools.toolDescriptions.cursor",
|
||||
"combos.roundRobin",
|
||||
"common.model",
|
||||
"docs.clientCherryStudioTitle",
|
||||
"docs.clientClaudeTitle",
|
||||
"docs.clientCursorTitle",
|
||||
"docs.github",
|
||||
"docs.protocolA2aTitle",
|
||||
"docs.protocolMcpTitle",
|
||||
"endpoint.chat",
|
||||
"endpoint.chatCompletions",
|
||||
"endpoint.cloudProxy",
|
||||
"endpoint.mcpCardTitle",
|
||||
"endpoint.rerank",
|
||||
"header.a2a",
|
||||
"header.mcp",
|
||||
"health.cpu",
|
||||
"health.latencyP50",
|
||||
"health.latencyP95",
|
||||
"health.latencyP99",
|
||||
"health.millisecondsShort",
|
||||
"health.notAvailable",
|
||||
"health.ok",
|
||||
"home.aliasLabel",
|
||||
"home.oauthLabel",
|
||||
"landing.brandName",
|
||||
"landing.flowProviderAnthropic",
|
||||
"landing.flowProviderGemini",
|
||||
"landing.flowProviderGithubCopilot",
|
||||
"landing.flowProviderOpenAI",
|
||||
"landing.flowToolClaudeCode",
|
||||
"landing.flowToolCline",
|
||||
"landing.flowToolCursor",
|
||||
"landing.flowToolOpenAICodex",
|
||||
"landing.github",
|
||||
"legal.terms",
|
||||
"legal.privacy",
|
||||
"logs.endpoint",
|
||||
"logs.proxy",
|
||||
"logs.console",
|
||||
"logs.request",
|
||||
"logs.audit",
|
||||
"media.interpolation",
|
||||
"media.upscale",
|
||||
"media.samples",
|
||||
"search.search",
|
||||
"search.searchTools",
|
||||
"search.webSearch",
|
||||
"search.fileSearch",
|
||||
"settings.theme",
|
||||
"settings.language",
|
||||
"settings.currency",
|
||||
"settings.timezone",
|
||||
"stats.requests",
|
||||
"stats.tokens",
|
||||
"stats.latency",
|
||||
"stats.errors",
|
||||
"themesPage.dark",
|
||||
"themesPage.light",
|
||||
"themesPage.system",
|
||||
"translator.translate",
|
||||
"translator.translateFrom",
|
||||
"translator.translateTo",
|
||||
"translator.detect",
|
||||
"translator.detectedLanguage",
|
||||
"usage.totalRequests",
|
||||
"usage.totalTokens",
|
||||
"usage.inputTokens",
|
||||
"usage.outputTokens",
|
||||
"usage.promptTokens",
|
||||
"usage.completionTokens",
|
||||
"usage.cacheReadTokens",
|
||||
"usage.cacheWriteTokens",
|
||||
}
|
||||
# Loaded from external file for easier maintenance
|
||||
_UNTRANSLATABLE_KEYS_FILE = _script_dir / "i18n" / "untranslatable-keys.json"
|
||||
if _UNTRANSLATABLE_KEYS_FILE.exists():
|
||||
with open(_UNTRANSLATABLE_KEYS_FILE, "r", encoding="utf-8") as _f:
|
||||
UNTRANSLATABLE_KEYS = set(json.load(_f).get("keys", []))
|
||||
else:
|
||||
UNTRANSLATABLE_KEYS = set()
|
||||
|
||||
|
||||
def print_header(msg: str) -> None:
|
||||
print(f"\n{BLUE}{'='*50}{NC}")
|
||||
print(f"\n{BLUE}{'=' * 50}{NC}")
|
||||
print(f"{BLUE}{msg}{NC}")
|
||||
print(f"{BLUE}{'='*50}{NC}")
|
||||
print(f"{BLUE}{'=' * 50}{NC}")
|
||||
|
||||
|
||||
def print_success(msg: str) -> None:
|
||||
@@ -163,7 +87,7 @@ def print_error(msg: str) -> None:
|
||||
def load_json(path: Path) -> Dict:
|
||||
"""Load JSON file"""
|
||||
try:
|
||||
with open(path, 'r', encoding='utf-8') as f:
|
||||
with open(path, "r", encoding="utf-8") as f:
|
||||
return json.load(f)
|
||||
except json.JSONDecodeError as e:
|
||||
print_error(f"Invalid JSON in {path}: {e}")
|
||||
@@ -206,7 +130,7 @@ def find_extra_keys(source: Dict, trans: Dict) -> Set[str]:
|
||||
|
||||
def get_value_by_path(obj: Dict, path: str) -> Any:
|
||||
"""Get value from nested dict using dot notation"""
|
||||
keys = path.replace('[', '.').replace(']', '').split('.')
|
||||
keys = path.replace("[", ".").replace("]", "").split(".")
|
||||
current = obj
|
||||
for key in keys:
|
||||
if key.isdigit():
|
||||
@@ -227,18 +151,18 @@ def find_untranslated(source: Dict, trans: Dict) -> Set[str]:
|
||||
"""Keys where source value equals translation (not translated), excluding untranslatable keys"""
|
||||
source_keys = get_all_keys(source)
|
||||
untranslated = set()
|
||||
|
||||
|
||||
for key in source_keys:
|
||||
# Skip keys that are in the untranslatable list
|
||||
if key in UNTRANSLATABLE_KEYS:
|
||||
continue
|
||||
|
||||
|
||||
source_val = get_value_by_path(source, key)
|
||||
trans_val = get_value_by_path(trans, key)
|
||||
|
||||
|
||||
if source_val is not None and source_val == trans_val:
|
||||
untranslated.add(key)
|
||||
|
||||
|
||||
return untranslated
|
||||
|
||||
|
||||
@@ -250,45 +174,48 @@ def find_placeholder_issues(source: Dict, trans: Dict) -> List[Tuple[str, str, s
|
||||
"""
|
||||
source_keys = get_all_keys(source)
|
||||
issues = []
|
||||
|
||||
|
||||
for key in source_keys:
|
||||
source_val = get_value_by_path(source, key)
|
||||
trans_val = get_value_by_path(trans, key)
|
||||
|
||||
|
||||
if source_val is None or trans_val is None:
|
||||
continue
|
||||
|
||||
|
||||
if not isinstance(source_val, str) or not isinstance(trans_val, str):
|
||||
continue
|
||||
|
||||
|
||||
# Only extract top-level placeholders: {name}, {count}, {day}, NOT {# X} inside ICU
|
||||
import re
|
||||
|
||||
# Extract variable names from placeholders (e.g., 'name' from '{name}' or 'count' from '{count, plural, ...}')
|
||||
# This avoids false positives on ICU strings where the internal text is translated.
|
||||
placeholder_regex = r'\{\s*([a-zA-Z][a-zA-Z0-9_]*)'
|
||||
placeholder_regex = r"\{\s*([a-zA-Z][a-zA-Z0-9_]*)"
|
||||
source_placeholders = set(re.findall(placeholder_regex, source_val))
|
||||
trans_placeholders = set(re.findall(placeholder_regex, trans_val))
|
||||
|
||||
|
||||
# Check for missing placeholders
|
||||
missing = source_placeholders - trans_placeholders
|
||||
if missing:
|
||||
issues.append((key, str(source_placeholders), str(trans_placeholders)))
|
||||
|
||||
|
||||
return issues
|
||||
|
||||
|
||||
def compare_category(source: Dict, trans: Dict, category: str) -> Tuple[bool, List[str]]:
|
||||
def compare_category(
|
||||
source: Dict, trans: Dict, category: str
|
||||
) -> Tuple[bool, List[str]]:
|
||||
"""Compare a specific category, return (complete, missing_keys)"""
|
||||
if category not in source:
|
||||
return False, [f"Category '{category}' not in source"]
|
||||
|
||||
|
||||
if category not in trans:
|
||||
return False, [f"Category '{category}' missing in translation"]
|
||||
|
||||
|
||||
source_keys = get_all_keys(source[category])
|
||||
trans_keys = get_all_keys(trans[category])
|
||||
missing = source_keys - trans_keys
|
||||
|
||||
|
||||
return len(missing) == 0, list(missing)
|
||||
|
||||
|
||||
@@ -304,18 +231,18 @@ def generate_report():
|
||||
print_header("OmniRoute Translation Report")
|
||||
print(f"Source: {SOURCE_FILE}")
|
||||
print(f"Translation: {translation_file}\n")
|
||||
|
||||
|
||||
source = load_json(SOURCE_FILE)
|
||||
trans = load_json(translation_file)
|
||||
|
||||
|
||||
# Count keys
|
||||
source_count = len(get_all_keys(source))
|
||||
trans_count = len(get_all_keys(trans))
|
||||
|
||||
|
||||
print(f"{BLUE}Key Statistics:{NC}")
|
||||
print(f" Source keys: {source_count}")
|
||||
print(f" Translation keys: {trans_count}\n")
|
||||
|
||||
|
||||
# Missing keys
|
||||
print_header("Missing Translations")
|
||||
missing = find_missing_keys(source, trans)
|
||||
@@ -327,7 +254,7 @@ def generate_report():
|
||||
print(f" ... and {len(missing) - 50} more")
|
||||
else:
|
||||
print_success("No missing translations!")
|
||||
|
||||
|
||||
# Extra keys
|
||||
print_header("Extra Keys")
|
||||
extra = find_extra_keys(source, trans)
|
||||
@@ -337,7 +264,7 @@ def generate_report():
|
||||
print(f" - {key}")
|
||||
else:
|
||||
print_success("No extra keys!")
|
||||
|
||||
|
||||
# Untranslated
|
||||
print_header("Untranslated Keys (same as source)")
|
||||
untranslated = find_untranslated(source, trans)
|
||||
@@ -349,7 +276,7 @@ def generate_report():
|
||||
print(f" ... and {len(untranslated) - 50} more")
|
||||
else:
|
||||
print_success("All keys appear to be translated!")
|
||||
|
||||
|
||||
# Placeholder issues
|
||||
print_header("Placeholder Mismatches")
|
||||
placeholder_issues = find_placeholder_issues(source, trans)
|
||||
@@ -363,7 +290,7 @@ def generate_report():
|
||||
print(f" ... and {len(placeholder_issues) - 20} more")
|
||||
else:
|
||||
print_success("All placeholders match!")
|
||||
|
||||
|
||||
# Per-category status
|
||||
print_header("Per-Category Status")
|
||||
for category in sorted(source.keys()):
|
||||
@@ -372,18 +299,18 @@ def generate_report():
|
||||
print_success(f"{category} - complete")
|
||||
else:
|
||||
print_error(f"{category} - missing {len(missing)} keys")
|
||||
|
||||
|
||||
# Summary
|
||||
print_header("Summary")
|
||||
if not missing and not extra and not untranslated:
|
||||
print(f"{GREEN}🎉 Translation is fully synchronized!{NC}")
|
||||
return 0
|
||||
else:
|
||||
print(f"{RED}Translation needs attention:{NC}")
|
||||
print(f"{YELLOW}Translation needs attention:{NC}")
|
||||
print(f" - Missing: {len(missing)}")
|
||||
print(f" - Extra: {len(extra)}")
|
||||
print(f" - Untranslated: {len(untranslated)}")
|
||||
return 1
|
||||
return 0
|
||||
|
||||
|
||||
def quick_check() -> int:
|
||||
@@ -391,20 +318,22 @@ def quick_check() -> int:
|
||||
translation_file = get_translation_file()
|
||||
source = load_json(SOURCE_FILE)
|
||||
trans = load_json(translation_file)
|
||||
|
||||
|
||||
missing = find_missing_keys(source, trans)
|
||||
untranslated = find_untranslated(source, trans)
|
||||
|
||||
|
||||
print(f"Missing: {len(missing)}")
|
||||
print(f"Untranslated: {len(untranslated)}")
|
||||
|
||||
print(f"Ignored (UNTRANSLATABLE_KEYS): {len(UNTRANSLATABLE_KEYS)}")
|
||||
|
||||
# Exit codes:
|
||||
# 0 = OK
|
||||
# 1 = generic error
|
||||
# 2 = missing string in translation
|
||||
# 3 = untranslated (soft warning - not a failure)
|
||||
if missing:
|
||||
return 2
|
||||
print_warning(f"{len(missing)} missing keys (non-critical)")
|
||||
return 0
|
||||
# untranslated is a soft warning, not a failure - translations exist, just not localized
|
||||
if untranslated:
|
||||
print_warning(f"{len(untranslated)} untranslated keys (non-critical)")
|
||||
@@ -417,38 +346,38 @@ def show_diff(category: str) -> int:
|
||||
translation_file = get_translation_file()
|
||||
source = load_json(SOURCE_FILE)
|
||||
trans = load_json(translation_file)
|
||||
|
||||
|
||||
if category not in source:
|
||||
print_error(f"Category '{category}' not found in source")
|
||||
print("Available categories:")
|
||||
for cat in sorted(source.keys()):
|
||||
print(f" - {cat}")
|
||||
return 1
|
||||
|
||||
|
||||
print_header(f"Diff for category: {category}")
|
||||
|
||||
|
||||
print(f"{BLUE}{'Key':<30} | {'Source':<25} | {'Translation':<25}{NC}")
|
||||
print("-" * 85)
|
||||
|
||||
|
||||
source_keys = get_all_keys(source[category])
|
||||
|
||||
|
||||
for key in sorted(source_keys):
|
||||
source_val = get_value_by_path(source[category], key)
|
||||
trans_val = get_value_by_path(trans.get(category, {}), key)
|
||||
|
||||
|
||||
# Truncate long values
|
||||
source_str = str(source_val)[:25] if source_val else "(null)"
|
||||
trans_str = str(trans_val)[:25] if trans_val else "(missing)"
|
||||
|
||||
|
||||
if source_val == trans_val:
|
||||
status = f"{YELLOW}(same){NC}"
|
||||
elif trans_val is None:
|
||||
status = f"{RED}(missing){NC}"
|
||||
else:
|
||||
status = f"{GREEN}(ok){NC}"
|
||||
|
||||
|
||||
print(f"{key:<30} | {source_str:<25} | {trans_str:<25} {status}")
|
||||
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
@@ -457,31 +386,31 @@ def export_csv(output_file: str) -> int:
|
||||
translation_file = get_translation_file()
|
||||
source = load_json(SOURCE_FILE)
|
||||
trans = load_json(translation_file)
|
||||
|
||||
|
||||
print_header(f"Exporting to CSV: {output_file}")
|
||||
|
||||
|
||||
source_keys = get_all_keys(source)
|
||||
|
||||
with open(output_file, 'w', encoding='utf-8') as f:
|
||||
|
||||
with open(output_file, "w", encoding="utf-8") as f:
|
||||
f.write("key,source_value,translation_value,status\n")
|
||||
|
||||
|
||||
for key in sorted(source_keys):
|
||||
source_val = get_value_by_path(source, key)
|
||||
trans_val = get_value_by_path(trans, key)
|
||||
|
||||
|
||||
# Escape commas
|
||||
source_str = str(source_val).replace(',', ';')
|
||||
trans_str = str(trans_val).replace(',', ';') if trans_val else ""
|
||||
|
||||
source_str = str(source_val).replace(",", ";")
|
||||
trans_str = str(trans_val).replace(",", ";") if trans_val else ""
|
||||
|
||||
if trans_val is None:
|
||||
status = "MISSING"
|
||||
elif source_val == trans_val:
|
||||
status = "UNTRANSLATED"
|
||||
else:
|
||||
status = "OK"
|
||||
|
||||
|
||||
f.write(f'"{key}","{source_str}","{trans_str}",{status}\n')
|
||||
|
||||
|
||||
print_success(f"Exported to {output_file}")
|
||||
return 0
|
||||
|
||||
@@ -491,70 +420,70 @@ def export_markdown(output_file: str) -> int:
|
||||
translation_file = get_translation_file()
|
||||
source = load_json(SOURCE_FILE)
|
||||
trans = load_json(translation_file)
|
||||
|
||||
|
||||
print_header(f"Exporting to Markdown: {output_file}")
|
||||
|
||||
|
||||
source_keys = get_all_keys(source)
|
||||
missing = find_missing_keys(source, trans)
|
||||
untranslated = find_untranslated(source, trans)
|
||||
|
||||
|
||||
# Separate translated and untranslated
|
||||
translated_keys = []
|
||||
untranslated_sorted = sorted(untranslated)
|
||||
|
||||
|
||||
for key in sorted(source_keys):
|
||||
if key not in missing and key not in untranslated:
|
||||
translated_keys.append(key)
|
||||
|
||||
|
||||
translated_count = len(translated_keys)
|
||||
untranslated_count = len(untranslated_sorted)
|
||||
|
||||
|
||||
# Export untranslated (main output file)
|
||||
with open(output_file, 'w', encoding='utf-8') as f:
|
||||
with open(output_file, "w", encoding="utf-8") as f:
|
||||
f.write("# Nepřeložené klíče (Untranslated Keys)\n\n")
|
||||
f.write(f"Zdroj: `{SOURCE_FILE.name}` | Překlad: `{TRANSLATION_FILE.name}`\n\n")
|
||||
|
||||
|
||||
f.write(f"**Celkem: {untranslated_count} nepreložených klíčů**\n\n")
|
||||
|
||||
|
||||
f.write("| # | Klíč (Key) | Originál | Nepřeloženo |\n")
|
||||
f.write("|---|------------|----------|------------|\n")
|
||||
|
||||
|
||||
for i, key in enumerate(untranslated_sorted, 1):
|
||||
source_val = get_value_by_path(source, key)
|
||||
trans_val = get_value_by_path(trans, key)
|
||||
|
||||
source_str = str(source_val).replace('|', '\\|')[:60]
|
||||
trans_str = str(trans_val).replace('|', '\\|')[:60]
|
||||
|
||||
|
||||
source_str = str(source_val).replace("|", "\\|")[:60]
|
||||
trans_str = str(trans_val).replace("|", "\\|")[:60]
|
||||
|
||||
f.write(f"| {i} | `{key}` | {source_str} | {trans_str} |\n")
|
||||
|
||||
|
||||
f.write("\n## Shrnutí (Summary)\n\n")
|
||||
f.write(f"- Celkem klíčů: {len(source_keys)}\n")
|
||||
f.write(f"- Chybějících: {len(missing)}\n")
|
||||
f.write(f"- Nepřeložených: {untranslated_count}\n")
|
||||
f.write(f"- Přeložených: {translated_count}\n")
|
||||
|
||||
|
||||
# Export translated to separate file
|
||||
translated_file = output_file.replace('.md', '_translated.md')
|
||||
translated_file = output_file.replace(".md", "_translated.md")
|
||||
translation_filename = translation_file.name
|
||||
with open(translated_file, 'w', encoding='utf-8') as f:
|
||||
with open(translated_file, "w", encoding="utf-8") as f:
|
||||
f.write("# Přeložené klíče (Translated Keys)\n\n")
|
||||
f.write(f"Zdroj: `{SOURCE_FILE.name}` | Překlad: `{translation_filename}`\n\n")
|
||||
|
||||
|
||||
f.write(f"**Celkem: {translated_count} přeložených klíčů**\n\n")
|
||||
|
||||
|
||||
f.write("| # | Klíč (Key) | Originál | Překlad |\n")
|
||||
f.write("|---|------------|----------|---------|\n")
|
||||
|
||||
|
||||
for i, key in enumerate(translated_keys, 1):
|
||||
source_val = get_value_by_path(source, key)
|
||||
trans_val = get_value_by_path(trans, key)
|
||||
|
||||
source_str = str(source_val).replace('|', '\\|')[:40]
|
||||
trans_str = str(trans_val).replace('|', '\\|')[:40]
|
||||
|
||||
|
||||
source_str = str(source_val).replace("|", "\\|")[:40]
|
||||
trans_str = str(trans_val).replace("|", "\\|")[:40]
|
||||
|
||||
f.write(f"| {i} | `{key}` | {source_str} | {trans_str} |\n")
|
||||
|
||||
|
||||
print_success(f"Exported: {output_file} ({untranslated_count} keys)")
|
||||
print_success(f"Exported: {translated_file} ({translated_count} keys)")
|
||||
return 0
|
||||
@@ -592,20 +521,20 @@ Examples:
|
||||
def main():
|
||||
# Parse global arguments first
|
||||
parser = argparse.ArgumentParser(add_help=False)
|
||||
parser.add_argument('-l', '--lang', dest='lang', default=None)
|
||||
parser.add_argument('command', nargs='?')
|
||||
parser.add_argument('arg', nargs='?')
|
||||
|
||||
parser.add_argument("-l", "--lang", dest="lang", default=None)
|
||||
parser.add_argument("command", nargs="?")
|
||||
parser.add_argument("arg", nargs="?")
|
||||
|
||||
# Parse known args only to allow commands to handle their own args
|
||||
args, _ = parser.parse_known_args()
|
||||
|
||||
|
||||
# Set language from argument or use default
|
||||
if args.lang:
|
||||
get_target_lang.cli_lang = args.lang
|
||||
elif not os.environ.get('TRANSLATION_LANG'):
|
||||
elif not os.environ.get("TRANSLATION_LANG"):
|
||||
# Default to cs for backwards compatibility
|
||||
get_target_lang.cli_lang = "cs"
|
||||
|
||||
|
||||
# Check if translation file exists
|
||||
translation_file = get_translation_file()
|
||||
if not translation_file.exists():
|
||||
@@ -615,11 +544,11 @@ def main():
|
||||
if f.name != "en.json":
|
||||
print(f" - {f.stem}")
|
||||
return 1
|
||||
|
||||
|
||||
# Execute command
|
||||
if not args.command or args.command in ('help', '--help', '-h'):
|
||||
if not args.command or args.command in ("help", "--help", "-h"):
|
||||
return generate_report()
|
||||
|
||||
|
||||
if args.command == "quick":
|
||||
return quick_check()
|
||||
elif args.command == "diff":
|
||||
@@ -641,4 +570,4 @@ def main():
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
sys.exit(main())
|
||||
|
||||
@@ -5,9 +5,10 @@ import { useTranslations } from "next-intl";
|
||||
export interface CacheTrendPoint {
|
||||
timestamp: string;
|
||||
requests: number;
|
||||
hits: number;
|
||||
misses: number;
|
||||
hitRate: number;
|
||||
cachedRequests: number;
|
||||
inputTokens: number;
|
||||
cachedTokens: number;
|
||||
cacheCreationTokens: number;
|
||||
}
|
||||
|
||||
interface CacheTrendsProps {
|
||||
@@ -27,7 +28,8 @@ export default function CacheTrends({
|
||||
|
||||
const trendData: CacheTrendPoint[] = data ?? [];
|
||||
const maxRequests = trendData.length > 0 ? Math.max(...trendData.map((p) => p.requests), 1) : 1;
|
||||
const peakHitRate = trendData.length > 0 ? Math.max(...trendData.map((p) => p.hitRate)) : null;
|
||||
const maxCachedRequests =
|
||||
trendData.length > 0 ? Math.max(...trendData.map((p) => p.cachedRequests), 0) : 0;
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -69,17 +71,21 @@ export default function CacheTrends({
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{peakHitRate !== null && (
|
||||
{maxCachedRequests > 0 && (
|
||||
<div className="text-xs text-text-muted">
|
||||
Peak hit rate:{" "}
|
||||
<span className="font-medium text-foreground">{peakHitRate.toFixed(1)}</span>
|
||||
{t("peakCached")}:{" "}
|
||||
<span className="font-medium text-foreground">
|
||||
{maxCachedRequests} / {maxRequests}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex items-end gap-1 h-32">
|
||||
{trendData.map((point) => {
|
||||
const height = Math.max(4, (point.requests / maxRequests) * 100);
|
||||
const hitHeight =
|
||||
point.requests > 0 ? Math.max(2, (point.hits / point.requests) * height) : 0;
|
||||
const cachedHeight =
|
||||
point.requests > 0
|
||||
? Math.max(2, (point.cachedRequests / point.requests) * height)
|
||||
: 0;
|
||||
const hour = new Date(point.timestamp).toLocaleTimeString([], {
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
@@ -92,16 +98,17 @@ export default function CacheTrends({
|
||||
className="flex-1 flex flex-col items-center gap-1 group relative"
|
||||
>
|
||||
<div className="absolute bottom-full mb-1 hidden group-hover:block bg-surface-raised border border-border rounded px-2 py-1 text-xs whitespace-nowrap z-10">
|
||||
{hour}: {point.requests} requests, {point.hits} cached
|
||||
{hour}: {point.requests} {t("requests").toLowerCase()}, {point.cachedRequests}{" "}
|
||||
{t("cached").toLowerCase()}
|
||||
</div>
|
||||
<div className="w-full flex flex-col justify-end h-full gap-px">
|
||||
<div
|
||||
className="w-full bg-green-500/30 rounded-t"
|
||||
style={{ height: `${hitHeight}%` }}
|
||||
style={{ height: `${cachedHeight}%` }}
|
||||
/>
|
||||
<div
|
||||
className="w-full bg-text-muted/20 rounded-t"
|
||||
style={{ height: `${height - hitHeight}%` }}
|
||||
style={{ height: `${height - cachedHeight}%` }}
|
||||
/>
|
||||
</div>
|
||||
<span className="text-[10px] text-text-muted truncate w-full text-center">
|
||||
|
||||
3
src/app/(dashboard)/dashboard/cache/page.tsx
vendored
3
src/app/(dashboard)/dashboard/cache/page.tsx
vendored
@@ -435,9 +435,6 @@ export default function CachePage() {
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{/* Prompt Cache Metrics (cumulative with reset) */}
|
||||
<CacheStatsCard />
|
||||
|
||||
{/* Cache Trend (24h) */}
|
||||
{trend.length > 0 && (
|
||||
<Card>
|
||||
|
||||
@@ -0,0 +1,256 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect, useCallback } from "react";
|
||||
import { Card, Button } from "@/shared/components";
|
||||
|
||||
interface ToolState {
|
||||
tool: string;
|
||||
installedVersion: string | null;
|
||||
currentVersion: string | null;
|
||||
status: string;
|
||||
pid: number | null;
|
||||
port: number;
|
||||
healthStatus: string;
|
||||
autoUpdate: boolean;
|
||||
autoStart: boolean;
|
||||
lastHealthCheck: string | null;
|
||||
errorMessage: string | null;
|
||||
}
|
||||
|
||||
interface UpdateInfo {
|
||||
current: string | null;
|
||||
latest: string;
|
||||
updateAvailable: boolean;
|
||||
}
|
||||
|
||||
export default function CliproxyapiToolCard({ isExpanded, onToggle }) {
|
||||
const [toolState, setToolState] = useState<ToolState | null>(null);
|
||||
const [updateInfo, setUpdateInfo] = useState<UpdateInfo | null>(null);
|
||||
const [loading, setLoading] = useState<string | null>(null);
|
||||
const [message, setMessage] = useState<{ type: string; text: string } | null>(null);
|
||||
|
||||
const fetchStatus = useCallback(async () => {
|
||||
try {
|
||||
const res = await fetch("/api/version-manager/status");
|
||||
if (!res.ok) return;
|
||||
const data = await res.json();
|
||||
const entry = Array.isArray(data)
|
||||
? data.find((t: ToolState) => t.tool === "cliproxyapi")
|
||||
: null;
|
||||
setToolState(entry || null);
|
||||
} catch (err) {
|
||||
console.error("Failed to fetch CLIProxyAPI status:", err);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const fetchUpdateInfo = useCallback(async () => {
|
||||
try {
|
||||
const res = await fetch("/api/version-manager/check-update?tool=cliproxyapi");
|
||||
if (!res.ok) return;
|
||||
setUpdateInfo(await res.json());
|
||||
} catch (err) {
|
||||
console.error("Failed to fetch CLIProxyAPI update info:", err);
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (isExpanded) {
|
||||
fetchStatus();
|
||||
fetchUpdateInfo();
|
||||
}
|
||||
}, [isExpanded, fetchStatus, fetchUpdateInfo]);
|
||||
|
||||
const apiCall = async (action: string, body?: Record<string, unknown>) => {
|
||||
setLoading(action);
|
||||
setMessage(null);
|
||||
try {
|
||||
const res = await fetch(`/api/version-manager/${action}`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ tool: "cliproxyapi", ...body }),
|
||||
});
|
||||
const data = await res.json();
|
||||
if (res.ok) {
|
||||
setMessage({ type: "success", text: data.message || `${action} succeeded` });
|
||||
await fetchStatus();
|
||||
if (action === "install" || action === "restart") await fetchUpdateInfo();
|
||||
} else {
|
||||
setMessage({ type: "error", text: data.error || `${action} failed` });
|
||||
}
|
||||
} catch (err) {
|
||||
setMessage({ type: "error", text: err instanceof Error ? err.message : "Request failed" });
|
||||
} finally {
|
||||
setLoading(null);
|
||||
}
|
||||
};
|
||||
|
||||
const statusBadge = () => {
|
||||
if (!toolState) return null;
|
||||
const s = toolState.status;
|
||||
const map: Record<string, { label: string; color: string }> = {
|
||||
running: { label: "Running", color: "bg-green-500/10 text-green-600 dark:text-green-400" },
|
||||
stopped: { label: "Stopped", color: "bg-zinc-500/10 text-zinc-500 dark:text-zinc-400" },
|
||||
not_installed: {
|
||||
label: "Not Installed",
|
||||
color: "bg-yellow-500/10 text-yellow-600 dark:text-yellow-400",
|
||||
},
|
||||
installed: { label: "Installed", color: "bg-blue-500/10 text-blue-600 dark:text-blue-400" },
|
||||
error: { label: "Error", color: "bg-red-500/10 text-red-600 dark:text-red-400" },
|
||||
};
|
||||
const badge = map[s] || map.not_installed;
|
||||
return (
|
||||
<span
|
||||
className={`inline-flex items-center gap-1.5 px-2 py-0.5 text-[11px] font-medium rounded-full ${badge.color}`}
|
||||
>
|
||||
<span className="size-1.5 rounded-full bg-current" />
|
||||
{badge.label}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Card padding="sm" className="overflow-hidden">
|
||||
<div className="flex items-center justify-between hover:cursor-pointer" onClick={onToggle}>
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="size-8 rounded-lg flex items-center justify-center shrink-0 bg-indigo-500/10">
|
||||
<span className="material-symbols-outlined text-indigo-500 text-xl">swap_horiz</span>
|
||||
</div>
|
||||
<div className="min-w-0">
|
||||
<div className="flex items-center gap-2">
|
||||
<h3 className="font-medium text-sm">CLIProxyAPI</h3>
|
||||
{statusBadge()}
|
||||
</div>
|
||||
<p className="text-xs text-text-muted truncate">
|
||||
Upstream proxy fallback (Go-based OAuth)
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<span
|
||||
className={`material-symbols-outlined text-text-muted text-[20px] transition-transform ${isExpanded ? "rotate-180" : ""}`}
|
||||
>
|
||||
expand_more
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{isExpanded && (
|
||||
<div className="mt-6 pt-6 border-t border-border space-y-4">
|
||||
{message && (
|
||||
<div
|
||||
className={`flex items-center gap-2 px-3 py-2 rounded-lg text-xs ${
|
||||
message.type === "success"
|
||||
? "bg-green-500/10 text-green-600 dark:text-green-400"
|
||||
: "bg-red-500/10 text-red-600 dark:text-red-400"
|
||||
}`}
|
||||
>
|
||||
<span className="material-symbols-outlined text-[14px]">
|
||||
{message.type === "success" ? "check_circle" : "error"}
|
||||
</span>
|
||||
{message.text}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{updateInfo?.updateAvailable && (
|
||||
<div className="flex items-center justify-between p-3 rounded-lg bg-yellow-500/10 border border-yellow-500/30">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="material-symbols-outlined text-yellow-500 text-lg">
|
||||
system_update
|
||||
</span>
|
||||
<span className="text-sm text-yellow-700 dark:text-yellow-300">
|
||||
Update available: v{updateInfo.current} → v{updateInfo.latest}
|
||||
</span>
|
||||
</div>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => apiCall("install", { version: updateInfo.latest })}
|
||||
loading={loading === "install"}
|
||||
>
|
||||
Update
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="grid grid-cols-3 gap-3">
|
||||
<div className="p-3 rounded-lg bg-bg-secondary">
|
||||
<p className="text-xs text-text-muted mb-1">Version</p>
|
||||
<p className="text-sm font-medium">
|
||||
{toolState?.installedVersion ? `v${toolState.installedVersion}` : "Not installed"}
|
||||
</p>
|
||||
</div>
|
||||
<div className="p-3 rounded-lg bg-bg-secondary">
|
||||
<p className="text-xs text-text-muted mb-1">Health</p>
|
||||
<p
|
||||
className={`text-sm font-medium ${toolState?.healthStatus === "healthy" ? "text-green-600 dark:text-green-400" : toolState?.healthStatus === "unhealthy" ? "text-red-600 dark:text-red-400" : "text-text-muted"}`}
|
||||
>
|
||||
{toolState?.healthStatus === "healthy"
|
||||
? `Healthy`
|
||||
: toolState?.healthStatus === "unhealthy"
|
||||
? "Unhealthy"
|
||||
: "Unknown"}
|
||||
</p>
|
||||
</div>
|
||||
<div className="p-3 rounded-lg bg-bg-secondary">
|
||||
<p className="text-xs text-text-muted mb-1">Port</p>
|
||||
<p className="text-sm font-mono">{toolState?.port || 8317}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{!toolState?.installedVersion && (
|
||||
<Button
|
||||
variant="primary"
|
||||
size="sm"
|
||||
onClick={() => apiCall("install")}
|
||||
loading={loading === "install"}
|
||||
>
|
||||
<span className="material-symbols-outlined text-[14px] mr-1">download</span>
|
||||
Install
|
||||
</Button>
|
||||
)}
|
||||
{toolState?.status === "running" ? (
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => apiCall("stop")}
|
||||
loading={loading === "stop"}
|
||||
>
|
||||
<span className="material-symbols-outlined text-[14px] mr-1">stop</span>
|
||||
Stop
|
||||
</Button>
|
||||
) : toolState?.installedVersion ? (
|
||||
<Button
|
||||
variant="primary"
|
||||
size="sm"
|
||||
onClick={() => apiCall("start")}
|
||||
loading={loading === "start"}
|
||||
>
|
||||
<span className="material-symbols-outlined text-[14px] mr-1">play_arrow</span>
|
||||
Start
|
||||
</Button>
|
||||
) : null}
|
||||
{toolState?.status === "running" && (
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => apiCall("restart")}
|
||||
loading={loading === "restart"}
|
||||
>
|
||||
<span className="material-symbols-outlined text-[14px] mr-1">restart_alt</span>
|
||||
Restart
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={fetchUpdateInfo}
|
||||
loading={loading === "check"}
|
||||
>
|
||||
<span className="material-symbols-outlined text-[14px] mr-1">sync</span>
|
||||
Check Updates
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
@@ -108,32 +108,32 @@ export default function MemoryPage() {
|
||||
return (
|
||||
<div className="space-y-6 p-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<h1 className="text-2xl font-bold">Memory Management</h1>
|
||||
<h1 className="text-2xl font-bold">{t("title")}</h1>
|
||||
<div className="flex gap-2">
|
||||
<Button variant="outline" onClick={handleExport}>
|
||||
Export
|
||||
{t("export")}
|
||||
</Button>
|
||||
<Button variant="outline">Import</Button>
|
||||
<Button>Add Memory</Button>
|
||||
<Button variant="outline">{t("import")}</Button>
|
||||
<Button>{t("addMemory")}</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<Card>
|
||||
<div className="p-4">
|
||||
<div className="text-sm text-gray-500">Total Entries</div>
|
||||
<div className="text-sm text-gray-500">{t("totalEntries")}</div>
|
||||
<div className="text-2xl font-bold">{stats.totalEntries}</div>
|
||||
</div>
|
||||
</Card>
|
||||
<Card>
|
||||
<div className="p-4">
|
||||
<div className="text-sm text-gray-500">Tokens Used</div>
|
||||
<div className="text-sm text-gray-500">{t("tokensUsed")}</div>
|
||||
<div className="text-2xl font-bold">{stats.tokensUsed.toLocaleString()}</div>
|
||||
</div>
|
||||
</Card>
|
||||
<Card>
|
||||
<div className="p-4">
|
||||
<div className="text-sm text-gray-500">Hit Rate</div>
|
||||
<div className="text-sm text-gray-500">{t("hitRate")}</div>
|
||||
<div className="text-2xl font-bold">{(stats.hitRate * 100).toFixed(1)}%</div>
|
||||
</div>
|
||||
</Card>
|
||||
@@ -142,20 +142,20 @@ export default function MemoryPage() {
|
||||
<Card>
|
||||
<div className="p-4">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h2 className="text-lg font-semibold">Memories</h2>
|
||||
<h2 className="text-lg font-semibold">{t("memories")}</h2>
|
||||
<div className="flex gap-2">
|
||||
<Input
|
||||
placeholder="Search memories..."
|
||||
placeholder={t("search")}
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
className="w-64"
|
||||
/>
|
||||
<Select value={filterType} onChange={(e) => setFilterType(e.target.value)}>
|
||||
<option value="all">All Types</option>
|
||||
<option value="factual">Factual</option>
|
||||
<option value="episodic">Episodic</option>
|
||||
<option value="procedural">Procedural</option>
|
||||
<option value="semantic">Semantic</option>
|
||||
<option value="all">{t("allTypes")}</option>
|
||||
<option value="factual">{t("factual")}</option>
|
||||
<option value="episodic">{t("episodic")}</option>
|
||||
<option value="procedural">{t("procedural")}</option>
|
||||
<option value="semantic">{t("semantic")}</option>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
@@ -164,11 +164,11 @@ export default function MemoryPage() {
|
||||
<table className="w-full">
|
||||
<thead>
|
||||
<tr className="border-b">
|
||||
<th className="text-left py-2 px-4">Type</th>
|
||||
<th className="text-left py-2 px-4">Key</th>
|
||||
<th className="text-left py-2 px-4">Content</th>
|
||||
<th className="text-left py-2 px-4">Created</th>
|
||||
<th className="text-left py-2 px-4">Actions</th>
|
||||
<th className="text-left py-2 px-4">{t("type")}</th>
|
||||
<th className="text-left py-2 px-4">{t("key")}</th>
|
||||
<th className="text-left py-2 px-4">{t("content")}</th>
|
||||
<th className="text-left py-2 px-4">{t("created")}</th>
|
||||
<th className="text-left py-2 px-4">{t("actions")}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -182,7 +182,7 @@ export default function MemoryPage() {
|
||||
<td className="py-2 px-4">{new Date(memory.createdAt).toLocaleDateString()}</td>
|
||||
<td className="py-2 px-4">
|
||||
<Button variant="ghost" size="sm" onClick={() => handleDelete(memory.id)}>
|
||||
Delete
|
||||
{t("delete")}
|
||||
</Button>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -30,8 +30,13 @@ import {
|
||||
isOpenAICompatibleProvider,
|
||||
isAnthropicCompatibleProvider,
|
||||
isClaudeCodeCompatibleProvider,
|
||||
supportsApiKeyOnFreeProvider,
|
||||
} from "@/shared/constants/providers";
|
||||
import { getModelsByProviderId } from "@/shared/constants/models";
|
||||
import {
|
||||
compatibleProviderSupportsModelImport,
|
||||
getCompatibleFallbackModels,
|
||||
} from "@/lib/providers/managedAvailableModels";
|
||||
import { useCopyToClipboard } from "@/shared/hooks/useCopyToClipboard";
|
||||
import {
|
||||
MODEL_COMPAT_PROTOCOL_KEYS,
|
||||
@@ -334,6 +339,7 @@ interface CompatibleModelsSectionProps {
|
||||
providerDisplayAlias: string;
|
||||
modelAliases: Record<string, string>;
|
||||
fallbackModels?: CompatModelRow[];
|
||||
allowImport: boolean;
|
||||
description: string;
|
||||
inputLabel: string;
|
||||
inputPlaceholder: string;
|
||||
@@ -471,7 +477,6 @@ interface EditCompatibleNodeModalProps {
|
||||
const CC_COMPATIBLE_LABEL = "CC Compatible";
|
||||
const CC_COMPATIBLE_DETAILS_TITLE = "CC Compatible Details";
|
||||
const CC_COMPATIBLE_DEFAULT_CHAT_PATH = "/v1/messages?beta=true";
|
||||
const CC_COMPATIBLE_DEFAULT_MODELS_PATH = "/models";
|
||||
|
||||
function normalizeCodexLimitPolicy(policy: unknown): { use5h: boolean; useWeekly: boolean } {
|
||||
const record =
|
||||
@@ -835,6 +840,7 @@ export default function ProviderDetailPage() {
|
||||
customModels: CompatModelRow[];
|
||||
modelCompatOverrides: Array<CompatModelRow & { id: string }>;
|
||||
}>({ customModels: [], modelCompatOverrides: [] });
|
||||
const [syncedAvailableModels, setSyncedAvailableModels] = useState<any[]>([]);
|
||||
const [compatSavingModelId, setCompatSavingModelId] = useState<string | null>(null);
|
||||
const [applyingCodexAuthId, setApplyingCodexAuthId] = useState<string | null>(null);
|
||||
const [exportingCodexAuthId, setExportingCodexAuthId] = useState<string | null>(null);
|
||||
@@ -872,11 +878,19 @@ export default function ProviderDetailPage() {
|
||||
: (FREE_PROVIDERS as any)[providerId] ||
|
||||
(OAUTH_PROVIDERS as any)[providerId] ||
|
||||
(APIKEY_PROVIDERS as any)[providerId];
|
||||
const isOAuth = !!(FREE_PROVIDERS as any)[providerId] || !!(OAUTH_PROVIDERS as any)[providerId];
|
||||
const models = getModelsByProviderId(providerId);
|
||||
const providerSupportsOAuth =
|
||||
!!(FREE_PROVIDERS as any)[providerId] || !!(OAUTH_PROVIDERS as any)[providerId];
|
||||
const providerSupportsPat = supportsApiKeyOnFreeProvider(providerId);
|
||||
const isOAuth = providerSupportsOAuth && !providerSupportsPat;
|
||||
const registryModels = getModelsByProviderId(providerId);
|
||||
// For Gemini: always use synced API models (empty if no keys added yet)
|
||||
const models = providerId === "gemini"
|
||||
? syncedAvailableModels
|
||||
: registryModels;
|
||||
const providerAlias = getProviderAlias(providerId);
|
||||
const isManagedAvailableModelsProvider = isCompatible || providerId === "openrouter";
|
||||
const isSearchProvider = providerId.endsWith("-search");
|
||||
const compatibleSupportsModelImport = compatibleProviderSupportsModelImport(providerId);
|
||||
|
||||
const providerStorageAlias = isCompatible ? providerId : providerAlias;
|
||||
const providerDisplayAlias = isCompatible ? providerNode?.prefix || providerId : providerAlias;
|
||||
@@ -906,6 +920,20 @@ export default function ProviderDetailPage() {
|
||||
customModels: data.models || [],
|
||||
modelCompatOverrides: data.modelCompatOverrides || [],
|
||||
});
|
||||
// Fetch synced available models for Gemini
|
||||
if (providerId === "gemini") {
|
||||
try {
|
||||
const syncRes = await fetch("/api/synced-available-models?provider=gemini", {
|
||||
cache: "no-store",
|
||||
});
|
||||
if (syncRes.ok) {
|
||||
const syncData = await syncRes.json();
|
||||
setSyncedAvailableModels(syncData.models || []);
|
||||
}
|
||||
} catch {
|
||||
// Non-critical
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("fetchProviderModelMeta", e);
|
||||
}
|
||||
@@ -1051,6 +1079,10 @@ export default function ProviderDetailPage() {
|
||||
const res = await fetch(`/api/providers/${id}`, { method: "DELETE" });
|
||||
if (res.ok) {
|
||||
setConnections(connections.filter((c) => c.id !== id));
|
||||
// Refresh model list after connection deletion (synced models may change)
|
||||
if (providerId === "gemini") {
|
||||
await fetchProviderModelMeta();
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("Error deleting connection:", error);
|
||||
@@ -1062,6 +1094,14 @@ export default function ProviderDetailPage() {
|
||||
setShowOAuthModal(false);
|
||||
}, [fetchConnections]);
|
||||
|
||||
const openPrimaryAddFlow = useCallback(() => {
|
||||
if (isOAuth) {
|
||||
setShowOAuthModal(true);
|
||||
return;
|
||||
}
|
||||
setShowAddApiKeyModal(true);
|
||||
}, [isOAuth]);
|
||||
|
||||
const handleSaveApiKey = async (formData) => {
|
||||
try {
|
||||
const res = await fetch("/api/providers", {
|
||||
@@ -1070,8 +1110,72 @@ export default function ProviderDetailPage() {
|
||||
body: JSON.stringify({ provider: providerId, ...formData }),
|
||||
});
|
||||
if (res.ok) {
|
||||
const connectionData = await res.json();
|
||||
const newConnection = connectionData?.connection;
|
||||
await fetchConnections();
|
||||
setShowAddApiKeyModal(false);
|
||||
|
||||
// For Gemini: show progress dialog and sync models from endpoint
|
||||
if (providerId === "gemini" && newConnection?.id) {
|
||||
setShowImportModal(true);
|
||||
setImportProgress({
|
||||
current: 0,
|
||||
total: 0,
|
||||
phase: "fetching",
|
||||
status: t("fetchingModels"),
|
||||
logs: [],
|
||||
error: "",
|
||||
importedCount: 0,
|
||||
});
|
||||
|
||||
try {
|
||||
const syncRes = await fetch(`/api/providers/${newConnection.id}/sync-models`, {
|
||||
method: "POST",
|
||||
signal: AbortSignal.timeout(30_000), // 30s timeout — model sync shouldn't hang
|
||||
});
|
||||
const syncData = await syncRes.json();
|
||||
|
||||
if (!syncRes.ok || syncData.error) {
|
||||
setImportProgress((prev) => ({
|
||||
...prev,
|
||||
phase: "error",
|
||||
status: t("failedFetchModels"),
|
||||
error: syncData.error?.message || syncData.error || t("failedImportModels"),
|
||||
}));
|
||||
return null;
|
||||
}
|
||||
|
||||
const syncedCount = syncData.syncedModels || 0;
|
||||
const syncedModelList: Array<{ id: string; name?: string }> = syncData.models || [];
|
||||
const logs: string[] = [];
|
||||
if (syncedModelList.length > 0) {
|
||||
logs.push(`✓ ${syncedCount} models available`);
|
||||
logs.push("");
|
||||
for (const m of syncedModelList) {
|
||||
logs.push(` ${m.name || m.id}`);
|
||||
}
|
||||
}
|
||||
|
||||
setImportProgress((prev) => ({
|
||||
...prev,
|
||||
phase: "done",
|
||||
status: t("modelsImported", { count: syncedCount }),
|
||||
total: syncedCount,
|
||||
current: syncedCount,
|
||||
importedCount: syncedCount,
|
||||
logs,
|
||||
}));
|
||||
|
||||
await fetchProviderModelMeta();
|
||||
} catch (syncError) {
|
||||
setImportProgress((prev) => ({
|
||||
...prev,
|
||||
phase: "error",
|
||||
status: t("failedFetchModels"),
|
||||
error: String(syncError),
|
||||
}));
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
const data = await res.json().catch(() => ({}));
|
||||
@@ -1736,6 +1840,10 @@ export default function ProviderDetailPage() {
|
||||
() => buildCompatMap(modelMeta.modelCompatOverrides),
|
||||
[modelMeta.modelCompatOverrides]
|
||||
);
|
||||
const compatibleFallbackModels = useMemo(
|
||||
() => getCompatibleFallbackModels(providerId, modelMeta.customModels),
|
||||
[providerId, modelMeta.customModels]
|
||||
);
|
||||
|
||||
const effectiveModelNormalize = (modelId: string, protocol = MODEL_COMPAT_PROTOCOL_KEYS[0]) =>
|
||||
effectiveNormalizeForProtocol(modelId, protocol, customMap, overrideMap);
|
||||
@@ -1822,7 +1930,7 @@ export default function ProviderDetailPage() {
|
||||
};
|
||||
|
||||
const renderModelsSection = () => {
|
||||
const autoSyncToggle = canImportModels && (
|
||||
const autoSyncToggle = compatibleSupportsModelImport && canImportModels && (
|
||||
<button
|
||||
onClick={handleToggleAutoSync}
|
||||
disabled={togglingAutoSync}
|
||||
@@ -1857,7 +1965,7 @@ export default function ProviderDetailPage() {
|
||||
providerId === "openrouter"
|
||||
? t("openRouterAnyModelHint")
|
||||
: isCcCompatible
|
||||
? "CC Compatible provider models are routed through the Claude Code-compatible bridge."
|
||||
? "CC Compatible available models mirror the OAuth Claude Code provider list."
|
||||
: t("compatibleModelsDescription", {
|
||||
type: isAnthropicCompatible ? t("anthropic") : t("openai"),
|
||||
});
|
||||
@@ -1881,7 +1989,7 @@ export default function ProviderDetailPage() {
|
||||
providerStorageAlias={providerStorageAlias}
|
||||
providerDisplayAlias={providerDisplayAlias}
|
||||
modelAliases={modelAliases}
|
||||
fallbackModels={providerId === "openrouter" ? modelMeta.customModels : undefined}
|
||||
fallbackModels={compatibleFallbackModels}
|
||||
description={description}
|
||||
inputLabel={inputLabel}
|
||||
inputPlaceholder={inputPlaceholder}
|
||||
@@ -1899,6 +2007,7 @@ export default function ProviderDetailPage() {
|
||||
saveModelCompatFlags={saveModelCompatFlags}
|
||||
compatSavingModelId={compatSavingModelId}
|
||||
onModelsChanged={fetchProviderModelMeta}
|
||||
allowImport={compatibleSupportsModelImport}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
@@ -1941,7 +2050,7 @@ export default function ProviderDetailPage() {
|
||||
);
|
||||
}
|
||||
|
||||
const importButton = (
|
||||
const importButton = providerId === "gemini" ? null : (
|
||||
<div className="flex items-center gap-2 mb-4">
|
||||
<Button
|
||||
size="sm"
|
||||
@@ -2216,13 +2325,16 @@ export default function ProviderDetailPage() {
|
||||
</button>
|
||||
)}
|
||||
{!isCompatible ? (
|
||||
<Button
|
||||
size="sm"
|
||||
icon="add"
|
||||
onClick={() => (isOAuth ? setShowOAuthModal(true) : setShowAddApiKeyModal(true))}
|
||||
>
|
||||
{t("add")}
|
||||
</Button>
|
||||
<div className="flex items-center gap-2">
|
||||
<Button size="sm" icon="add" onClick={openPrimaryAddFlow}>
|
||||
{providerSupportsPat ? "Add PAT" : t("add")}
|
||||
</Button>
|
||||
{providerId === "qoder" && (
|
||||
<Button size="sm" variant="secondary" onClick={() => setShowOAuthModal(true)}>
|
||||
Experimental OAuth
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
connections.length === 0 && (
|
||||
<Button size="sm" icon="add" onClick={() => setShowAddApiKeyModal(true)}>
|
||||
@@ -2242,12 +2354,16 @@ export default function ProviderDetailPage() {
|
||||
<p className="text-text-main font-medium mb-1">{t("noConnectionsYet")}</p>
|
||||
<p className="text-sm text-text-muted mb-4">{t("addFirstConnectionHint")}</p>
|
||||
{!isCompatible && (
|
||||
<Button
|
||||
icon="add"
|
||||
onClick={() => (isOAuth ? setShowOAuthModal(true) : setShowAddApiKeyModal(true))}
|
||||
>
|
||||
{t("addConnection")}
|
||||
</Button>
|
||||
<div className="flex items-center justify-center gap-2">
|
||||
<Button icon="add" onClick={openPrimaryAddFlow}>
|
||||
{providerSupportsPat ? "Add PAT" : t("addConnection")}
|
||||
</Button>
|
||||
{providerId === "qoder" && (
|
||||
<Button variant="secondary" onClick={() => setShowOAuthModal(true)}>
|
||||
Experimental OAuth
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
@@ -2264,7 +2380,7 @@ export default function ProviderDetailPage() {
|
||||
<ConnectionRow
|
||||
key={conn.id}
|
||||
connection={conn}
|
||||
isOAuth={isOAuth}
|
||||
isOAuth={conn.authType === "oauth"}
|
||||
isFirst={index === 0}
|
||||
isLast={index === sorted.length - 1}
|
||||
onMoveUp={() => handleSwapPriority(conn, sorted[index - 1])}
|
||||
@@ -2285,8 +2401,10 @@ export default function ProviderDetailPage() {
|
||||
setShowEditModal(true);
|
||||
}}
|
||||
onDelete={() => handleDelete(conn.id)}
|
||||
onReauth={isOAuth ? () => setShowOAuthModal(true) : undefined}
|
||||
onRefreshToken={isOAuth ? () => handleRefreshToken(conn.id) : undefined}
|
||||
onReauth={conn.authType === "oauth" ? () => setShowOAuthModal(true) : undefined}
|
||||
onRefreshToken={
|
||||
conn.authType === "oauth" ? () => handleRefreshToken(conn.id) : undefined
|
||||
}
|
||||
isRefreshing={refreshingId === conn.id}
|
||||
onApplyCodexAuthLocal={
|
||||
providerId === "codex"
|
||||
@@ -2361,7 +2479,7 @@ export default function ProviderDetailPage() {
|
||||
<ConnectionRow
|
||||
key={conn.id}
|
||||
connection={conn}
|
||||
isOAuth={isOAuth}
|
||||
isOAuth={conn.authType === "oauth"}
|
||||
isFirst={gi === 0 && index === 0}
|
||||
isLast={gi === groupKeys.length - 1 && index === groupConns.length - 1}
|
||||
onMoveUp={() =>
|
||||
@@ -2388,8 +2506,14 @@ export default function ProviderDetailPage() {
|
||||
setShowEditModal(true);
|
||||
}}
|
||||
onDelete={() => handleDelete(conn.id)}
|
||||
onReauth={isOAuth ? () => setShowOAuthModal(true) : undefined}
|
||||
onRefreshToken={isOAuth ? () => handleRefreshToken(conn.id) : undefined}
|
||||
onReauth={
|
||||
conn.authType === "oauth" ? () => setShowOAuthModal(true) : undefined
|
||||
}
|
||||
onRefreshToken={
|
||||
conn.authType === "oauth"
|
||||
? () => handleRefreshToken(conn.id)
|
||||
: undefined
|
||||
}
|
||||
isRefreshing={refreshingId === conn.id}
|
||||
onApplyCodexAuthLocal={
|
||||
providerId === "codex"
|
||||
@@ -2432,7 +2556,7 @@ export default function ProviderDetailPage() {
|
||||
{renderModelsSection()}
|
||||
|
||||
{/* Custom Models — available for providers without managed available-model metadata */}
|
||||
{!isManagedAvailableModelsProvider && (
|
||||
{!isManagedAvailableModelsProvider && providerId !== "gemini" && (
|
||||
<CustomModelsSection
|
||||
providerId={providerId}
|
||||
providerAlias={providerDisplayAlias}
|
||||
@@ -2721,11 +2845,16 @@ export default function ProviderDetailPage() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Auto-reload notice */}
|
||||
{importProgress.phase === "done" && importProgress.importedCount > 0 && (
|
||||
<p className="text-xs text-text-muted text-center animate-pulse">
|
||||
{t("pageAutoRefresh")}
|
||||
</p>
|
||||
{/* Close button */}
|
||||
{importProgress.phase === "done" && (
|
||||
<div className="flex justify-center">
|
||||
<button
|
||||
onClick={() => setShowImportModal(false)}
|
||||
className="px-4 py-2 text-sm font-medium rounded-lg bg-primary text-white hover:opacity-90 transition-opacity"
|
||||
>
|
||||
{t("close") || "Close"}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Modal>
|
||||
@@ -3494,6 +3623,7 @@ function CompatibleModelsSection({
|
||||
saveModelCompatFlags,
|
||||
compatSavingModelId,
|
||||
onModelsChanged,
|
||||
allowImport,
|
||||
}: CompatibleModelsSectionProps) {
|
||||
const [newModel, setNewModel] = useState("");
|
||||
const [adding, setAdding] = useState(false);
|
||||
@@ -3582,7 +3712,7 @@ function CompatibleModelsSection({
|
||||
};
|
||||
|
||||
const handleImport = async () => {
|
||||
if (importing) return;
|
||||
if (!allowImport || importing) return;
|
||||
const activeConnection = connections.find((conn) => conn.isActive !== false);
|
||||
if (!activeConnection) return;
|
||||
|
||||
@@ -3685,18 +3815,22 @@ function CompatibleModelsSection({
|
||||
<Button size="sm" icon="add" onClick={handleAdd} disabled={!newModel.trim() || adding}>
|
||||
{adding ? t("adding") : t("add")}
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="secondary"
|
||||
icon="download"
|
||||
onClick={handleImport}
|
||||
disabled={!canImport || importing}
|
||||
>
|
||||
{importing ? t("importingModels") : t("importFromModels")}
|
||||
</Button>
|
||||
{allowImport && (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="secondary"
|
||||
icon="download"
|
||||
onClick={handleImport}
|
||||
disabled={!canImport || importing}
|
||||
>
|
||||
{importing ? t("importingModels") : t("importFromModels")}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{!canImport && <p className="text-xs text-text-muted">{t("addConnectionToImport")}</p>}
|
||||
{allowImport && !canImport && (
|
||||
<p className="text-xs text-text-muted">{t("addConnectionToImport")}</p>
|
||||
)}
|
||||
|
||||
{allModels.length > 0 && (
|
||||
<div className="flex flex-col gap-3">
|
||||
@@ -3750,6 +3884,7 @@ CompatibleModelsSection.propTypes = {
|
||||
saveModelCompatFlags: PropTypes.func.isRequired,
|
||||
compatSavingModelId: PropTypes.string,
|
||||
onModelsChanged: PropTypes.func,
|
||||
allowImport: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
function CooldownTimer({ until }: CooldownTimerProps) {
|
||||
@@ -4376,6 +4511,7 @@ function AddApiKeyModal({
|
||||
const isVertex = provider === "vertex";
|
||||
const defaultRegion = "us-central1";
|
||||
const isGlm = provider === "glm";
|
||||
const isQoder = provider === "qoder";
|
||||
|
||||
const [formData, setFormData] = useState({
|
||||
name: "",
|
||||
@@ -4501,16 +4637,27 @@ function AddApiKeyModal({
|
||||
label={t("nameLabel")}
|
||||
value={formData.name}
|
||||
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
||||
placeholder={t("productionKey")}
|
||||
placeholder={isQoder ? "Qoder PAT" : t("productionKey")}
|
||||
/>
|
||||
<div className="flex gap-2">
|
||||
<Input
|
||||
label={t("apiKeyLabel")}
|
||||
label={isQoder ? "Personal Access Token" : t("apiKeyLabel")}
|
||||
type="password"
|
||||
value={formData.apiKey}
|
||||
onChange={(e) => setFormData({ ...formData, apiKey: e.target.value })}
|
||||
className="flex-1"
|
||||
placeholder={isVertex ? "Cole o Service Account JSON aqui" : undefined}
|
||||
placeholder={
|
||||
isVertex
|
||||
? "Cole o Service Account JSON aqui"
|
||||
: isQoder
|
||||
? "Paste your Qoder Personal Access Token"
|
||||
: undefined
|
||||
}
|
||||
hint={
|
||||
isQoder
|
||||
? "Supported path: PAT via qodercli. Browser OAuth remains experimental."
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
<div className="pt-6">
|
||||
<Button
|
||||
@@ -5110,13 +5257,13 @@ function EditCompatibleNodeModal({
|
||||
? "https://api.anthropic.com/v1"
|
||||
: "https://api.openai.com/v1"),
|
||||
chatPath: node.chatPath || (isCcCompatible ? CC_COMPATIBLE_DEFAULT_CHAT_PATH : ""),
|
||||
modelsPath: node.modelsPath || (isCcCompatible ? CC_COMPATIBLE_DEFAULT_MODELS_PATH : ""),
|
||||
modelsPath: isCcCompatible ? "" : node.modelsPath || "",
|
||||
});
|
||||
setShowAdvanced(
|
||||
!!(
|
||||
node.chatPath ||
|
||||
node.modelsPath ||
|
||||
(isCcCompatible && !node.chatPath && !node.modelsPath)
|
||||
(!isCcCompatible && node.modelsPath) ||
|
||||
(isCcCompatible && !node.chatPath)
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -5136,8 +5283,7 @@ function EditCompatibleNodeModal({
|
||||
prefix: formData.prefix,
|
||||
baseUrl: formData.baseUrl,
|
||||
chatPath: formData.chatPath || (isCcCompatible ? CC_COMPATIBLE_DEFAULT_CHAT_PATH : ""),
|
||||
modelsPath:
|
||||
formData.modelsPath || (isCcCompatible ? CC_COMPATIBLE_DEFAULT_MODELS_PATH : ""),
|
||||
modelsPath: isCcCompatible ? "" : formData.modelsPath,
|
||||
};
|
||||
if (!isAnthropic) {
|
||||
payload.apiType = formData.apiType;
|
||||
@@ -5160,8 +5306,7 @@ function EditCompatibleNodeModal({
|
||||
type: isAnthropic ? "anthropic-compatible" : "openai-compatible",
|
||||
compatMode: isCcCompatible ? "cc" : undefined,
|
||||
chatPath: formData.chatPath || (isCcCompatible ? CC_COMPATIBLE_DEFAULT_CHAT_PATH : ""),
|
||||
modelsPath:
|
||||
formData.modelsPath || (isCcCompatible ? CC_COMPATIBLE_DEFAULT_MODELS_PATH : ""),
|
||||
modelsPath: isCcCompatible ? "" : formData.modelsPath,
|
||||
}),
|
||||
});
|
||||
const data = await res.json();
|
||||
@@ -5273,15 +5418,15 @@ function EditCompatibleNodeModal({
|
||||
: t("chatPathHint")
|
||||
}
|
||||
/>
|
||||
<Input
|
||||
label={isCcCompatible ? "Models Path" : t("modelsPathLabel")}
|
||||
value={formData.modelsPath}
|
||||
onChange={(e) => setFormData({ ...formData, modelsPath: e.target.value })}
|
||||
placeholder={
|
||||
isCcCompatible ? CC_COMPATIBLE_DEFAULT_MODELS_PATH : t("modelsPathPlaceholder")
|
||||
}
|
||||
hint={isCcCompatible ? "Defaults to /models" : t("modelsPathHint")}
|
||||
/>
|
||||
{!isCcCompatible && (
|
||||
<Input
|
||||
label={t("modelsPathLabel")}
|
||||
value={formData.modelsPath}
|
||||
onChange={(e) => setFormData({ ...formData, modelsPath: e.target.value })}
|
||||
placeholder={t("modelsPathPlaceholder")}
|
||||
hint={t("modelsPathHint")}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<div className="flex gap-2">
|
||||
|
||||
@@ -30,7 +30,6 @@ import { useTranslations } from "next-intl";
|
||||
const CC_COMPATIBLE_LABEL = "CC Compatible";
|
||||
const ADD_CC_COMPATIBLE_LABEL = "Add CC Compatible";
|
||||
const CC_COMPATIBLE_DEFAULT_CHAT_PATH = "/v1/messages?beta=true";
|
||||
const CC_COMPATIBLE_DEFAULT_MODELS_PATH = "/models";
|
||||
|
||||
// Shared helper function to avoid code duplication between ProviderCard and ApiKeyProviderCard
|
||||
function getStatusDisplay(connected, error, errorCode, t) {
|
||||
@@ -168,9 +167,11 @@ export default function ProvidersPage() {
|
||||
};
|
||||
|
||||
const getProviderStats = (providerId, authType) => {
|
||||
const providerConnections = connections.filter(
|
||||
(c) => c.provider === providerId && c.authType === authType
|
||||
);
|
||||
const providerConnections = connections.filter((c) => {
|
||||
if (c.provider !== providerId) return false;
|
||||
if (authType === "free") return true;
|
||||
return c.authType === authType;
|
||||
});
|
||||
|
||||
// Helper: check if connection is effectively active (cooldown expired)
|
||||
const getEffectiveStatus = (conn) => {
|
||||
@@ -217,13 +218,17 @@ export default function ProvidersPage() {
|
||||
|
||||
// Toggle all connections for a provider on/off
|
||||
const handleToggleProvider = async (providerId: string, authType: string, newActive: boolean) => {
|
||||
const providerConns = connections.filter(
|
||||
(c) => c.provider === providerId && c.authType === authType
|
||||
);
|
||||
const providerConns = connections.filter((c) => {
|
||||
if (c.provider !== providerId) return false;
|
||||
if (authType === "free") return true;
|
||||
return c.authType === authType;
|
||||
});
|
||||
// Optimistically update UI
|
||||
setConnections((prev) =>
|
||||
prev.map((c) =>
|
||||
c.provider === providerId && c.authType === authType ? { ...c, isActive: newActive } : c
|
||||
c.provider === providerId && (authType === "free" || c.authType === authType)
|
||||
? { ...c, isActive: newActive }
|
||||
: c
|
||||
)
|
||||
);
|
||||
// Fire API calls in parallel
|
||||
@@ -444,9 +449,9 @@ export default function ProvidersPage() {
|
||||
key={key}
|
||||
providerId={key}
|
||||
provider={info}
|
||||
stats={getProviderStats(key, "oauth")}
|
||||
stats={getProviderStats(key, "free")}
|
||||
authType="free"
|
||||
onToggle={(active) => handleToggleProvider(key, "oauth", active)}
|
||||
onToggle={(active) => handleToggleProvider(key, "free", active)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
@@ -518,25 +523,14 @@ export default function ProvidersPage() {
|
||||
</button>
|
||||
)}
|
||||
{ccCompatibleProviderEnabled && (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="secondary"
|
||||
icon="add"
|
||||
onClick={() => setShowAddCcCompatibleModal(true)}
|
||||
>
|
||||
<Button size="sm" icon="add" onClick={() => setShowAddCcCompatibleModal(true)}>
|
||||
{ADD_CC_COMPATIBLE_LABEL}
|
||||
</Button>
|
||||
)}
|
||||
<Button size="sm" icon="add" onClick={() => setShowAddAnthropicCompatibleModal(true)}>
|
||||
{t("addAnthropicCompatible")}
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="secondary"
|
||||
icon="add"
|
||||
onClick={() => setShowAddCompatibleModal(true)}
|
||||
className="!bg-white !text-black hover:!bg-gray-100"
|
||||
>
|
||||
<Button size="sm" icon="add" onClick={() => setShowAddCompatibleModal(true)}>
|
||||
{t("addOpenAICompatible")}
|
||||
</Button>
|
||||
</div>
|
||||
@@ -1292,12 +1286,12 @@ AddAnthropicCompatibleModal.propTypes = {
|
||||
};
|
||||
|
||||
function AddCcCompatibleModal({ isOpen, onClose, onCreated }) {
|
||||
const t = useTranslations("providers");
|
||||
const [formData, setFormData] = useState({
|
||||
name: "",
|
||||
prefix: "",
|
||||
baseUrl: "https://api.anthropic.com",
|
||||
chatPath: CC_COMPATIBLE_DEFAULT_CHAT_PATH,
|
||||
modelsPath: CC_COMPATIBLE_DEFAULT_MODELS_PATH,
|
||||
});
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
const [checkKey, setCheckKey] = useState("");
|
||||
@@ -1326,7 +1320,6 @@ function AddCcCompatibleModal({ isOpen, onClose, onCreated }) {
|
||||
type: "anthropic-compatible",
|
||||
compatMode: "cc",
|
||||
chatPath: formData.chatPath || CC_COMPATIBLE_DEFAULT_CHAT_PATH,
|
||||
modelsPath: formData.modelsPath || CC_COMPATIBLE_DEFAULT_MODELS_PATH,
|
||||
}),
|
||||
});
|
||||
const data = await res.json();
|
||||
@@ -1337,7 +1330,6 @@ function AddCcCompatibleModal({ isOpen, onClose, onCreated }) {
|
||||
prefix: "",
|
||||
baseUrl: "https://api.anthropic.com",
|
||||
chatPath: CC_COMPATIBLE_DEFAULT_CHAT_PATH,
|
||||
modelsPath: CC_COMPATIBLE_DEFAULT_MODELS_PATH,
|
||||
});
|
||||
setCheckKey("");
|
||||
setValidationResult(null);
|
||||
@@ -1362,7 +1354,6 @@ function AddCcCompatibleModal({ isOpen, onClose, onCreated }) {
|
||||
type: "anthropic-compatible",
|
||||
compatMode: "cc",
|
||||
chatPath: formData.chatPath || CC_COMPATIBLE_DEFAULT_CHAT_PATH,
|
||||
modelsPath: formData.modelsPath || CC_COMPATIBLE_DEFAULT_MODELS_PATH,
|
||||
}),
|
||||
});
|
||||
const data = await res.json();
|
||||
@@ -1378,25 +1369,25 @@ function AddCcCompatibleModal({ isOpen, onClose, onCreated }) {
|
||||
<Modal isOpen={isOpen} title={ADD_CC_COMPATIBLE_LABEL} onClose={onClose}>
|
||||
<div className="flex flex-col gap-4">
|
||||
<Input
|
||||
label="Name"
|
||||
label={t("nameLabel")}
|
||||
value={formData.name}
|
||||
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
||||
placeholder="CC Compatible Production"
|
||||
hint="Display name for this Claude Code-compatible provider"
|
||||
placeholder={t("compatibleProdPlaceholder", { type: CC_COMPATIBLE_LABEL })}
|
||||
hint={t("nameHint")}
|
||||
/>
|
||||
<Input
|
||||
label="Prefix"
|
||||
label={t("prefixLabel")}
|
||||
value={formData.prefix}
|
||||
onChange={(e) => setFormData({ ...formData, prefix: e.target.value })}
|
||||
placeholder="cc"
|
||||
hint="Used for model aliases such as prefix/model-id"
|
||||
placeholder="cc-prod"
|
||||
hint={t("prefixHint")}
|
||||
/>
|
||||
<Input
|
||||
label="Base URL"
|
||||
label={t("baseUrlLabel")}
|
||||
value={formData.baseUrl}
|
||||
onChange={(e) => setFormData({ ...formData, baseUrl: e.target.value })}
|
||||
placeholder="https://example.com/v1"
|
||||
hint="Base URL for the CC-compatible site. Do not include /messages."
|
||||
placeholder="https://api.anthropic.com"
|
||||
hint={t("compatibleBaseUrlHint", { type: CC_COMPATIBLE_LABEL })}
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
@@ -1411,7 +1402,7 @@ function AddCcCompatibleModal({ isOpen, onClose, onCreated }) {
|
||||
>
|
||||
▶
|
||||
</span>
|
||||
Advanced settings
|
||||
{t("advancedSettings")}
|
||||
</button>
|
||||
{showAdvanced && (
|
||||
<div
|
||||
@@ -1419,24 +1410,17 @@ function AddCcCompatibleModal({ isOpen, onClose, onCreated }) {
|
||||
className="flex flex-col gap-3 pl-2 border-l-2 border-border"
|
||||
>
|
||||
<Input
|
||||
label="Chat Path"
|
||||
label={t("chatPathLabel")}
|
||||
value={formData.chatPath}
|
||||
onChange={(e) => setFormData({ ...formData, chatPath: e.target.value })}
|
||||
placeholder={CC_COMPATIBLE_DEFAULT_CHAT_PATH}
|
||||
hint="Defaults to the strict Claude Code-compatible messages path"
|
||||
/>
|
||||
<Input
|
||||
label="Models Path"
|
||||
value={formData.modelsPath}
|
||||
onChange={(e) => setFormData({ ...formData, modelsPath: e.target.value })}
|
||||
placeholder={CC_COMPATIBLE_DEFAULT_MODELS_PATH}
|
||||
hint="Defaults to /models"
|
||||
hint={t("chatPathHint")}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex gap-2">
|
||||
<Input
|
||||
label="API Key for Check"
|
||||
label={t("apiKeyForCheck")}
|
||||
type="password"
|
||||
value={checkKey}
|
||||
onChange={(e) => setCheckKey(e.target.value)}
|
||||
@@ -1448,13 +1432,13 @@ function AddCcCompatibleModal({ isOpen, onClose, onCreated }) {
|
||||
disabled={!checkKey || validating || !formData.baseUrl.trim()}
|
||||
variant="secondary"
|
||||
>
|
||||
{validating ? "Checking..." : "Check"}
|
||||
{validating ? t("checking") : t("check")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
{validationResult && (
|
||||
<Badge variant={validationResult === "success" ? "success" : "error"}>
|
||||
{validationResult === "success" ? "Valid" : "Invalid"}
|
||||
{validationResult === "success" ? t("valid") : t("invalid")}
|
||||
</Badge>
|
||||
)}
|
||||
<div className="flex gap-2">
|
||||
@@ -1468,10 +1452,10 @@ function AddCcCompatibleModal({ isOpen, onClose, onCreated }) {
|
||||
submitting
|
||||
}
|
||||
>
|
||||
{submitting ? "Creating..." : "Add"}
|
||||
{submitting ? t("creating") : t("add")}
|
||||
</Button>
|
||||
<Button onClick={onClose} variant="ghost" fullWidth>
|
||||
Cancel
|
||||
{t("cancel")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,245 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect, useCallback } from "react";
|
||||
import { Card, Button, Input, Toggle } from "@/shared/components";
|
||||
|
||||
interface Settings {
|
||||
cliproxyapi_fallback_enabled?: boolean;
|
||||
cliproxyapi_url?: string;
|
||||
cliproxyapi_fallback_codes?: string;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
interface VersionManagerEntry {
|
||||
tool: string;
|
||||
status: string;
|
||||
installedVersion: string | null;
|
||||
healthStatus: string;
|
||||
port: number;
|
||||
}
|
||||
|
||||
function isValidUrl(value: string): boolean {
|
||||
try {
|
||||
const url = new URL(value);
|
||||
return url.protocol === "http:" || url.protocol === "https:";
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export default function CliproxyapiSettingsTab() {
|
||||
const [settings, setSettings] = useState<Settings>({});
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [message, setMessage] = useState<{ type: string; text: string } | null>(null);
|
||||
const [toolState, setToolState] = useState<VersionManagerEntry | null>(null);
|
||||
const [toolStateError, setToolStateError] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
fetch("/api/settings")
|
||||
.then((r) => {
|
||||
if (!r.ok) throw new Error(`Settings API returned ${r.status}`);
|
||||
return r.json();
|
||||
})
|
||||
.then((data) => {
|
||||
setSettings(data);
|
||||
setLoading(false);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error("Failed to load settings:", err);
|
||||
setLoading(false);
|
||||
});
|
||||
|
||||
fetch("/api/version-manager/status")
|
||||
.then((r) => {
|
||||
if (!r.ok) throw new Error(`Version manager API returned ${r.status}`);
|
||||
return r.json();
|
||||
})
|
||||
.then((data) => {
|
||||
const entry = Array.isArray(data)
|
||||
? data.find((t: VersionManagerEntry) => t.tool === "cliproxyapi")
|
||||
: null;
|
||||
setToolState(entry ?? null);
|
||||
setToolStateError(null);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error("Failed to load version manager status:", err);
|
||||
setToolStateError("Unable to reach version manager service");
|
||||
setToolState(null);
|
||||
});
|
||||
}, []);
|
||||
|
||||
const updateSetting = useCallback(async (key: string, value: boolean | string) => {
|
||||
if (key === "cliproxyapi_url" && typeof value === "string" && value.trim() !== "") {
|
||||
if (!isValidUrl(value)) {
|
||||
setMessage({ type: "error", text: "Invalid URL format. Use http:// or https://" });
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
setSaving(true);
|
||||
setMessage(null);
|
||||
try {
|
||||
const res = await fetch("/api/settings", {
|
||||
method: "PATCH",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ [key]: value }),
|
||||
});
|
||||
if (!res.ok) {
|
||||
throw new Error(`Server returned ${res.status}`);
|
||||
}
|
||||
await res.json();
|
||||
setSettings((prev) => ({ ...prev, [key]: value }));
|
||||
setMessage({ type: "success", text: "Setting saved" });
|
||||
} catch {
|
||||
setMessage({ type: "error", text: "Failed to save setting" });
|
||||
} finally {
|
||||
setSaving(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const cpaEnabled = settings.cliproxyapi_fallback_enabled === true;
|
||||
const cpaUrl = settings.cliproxyapi_url || "http://127.0.0.1:8317";
|
||||
const cpaCodes = settings.cliproxyapi_fallback_codes || "502,401,403,429,503";
|
||||
|
||||
const statusColor =
|
||||
toolState?.status === "running"
|
||||
? "text-green-600 dark:text-green-400"
|
||||
: toolState?.status === "error"
|
||||
? "text-red-600 dark:text-red-400"
|
||||
: "text-text-muted";
|
||||
|
||||
const statusIcon =
|
||||
toolState?.status === "running"
|
||||
? "check_circle"
|
||||
: toolState?.status === "error"
|
||||
? "error"
|
||||
: "help";
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
{message && (
|
||||
<div
|
||||
className={`flex items-center gap-2 px-3 py-2 rounded-lg text-xs ${
|
||||
message.type === "success"
|
||||
? "bg-green-500/10 text-green-600 dark:text-green-400"
|
||||
: "bg-red-500/10 text-red-600 dark:text-red-400"
|
||||
}`}
|
||||
>
|
||||
<span className="material-symbols-outlined text-[14px]">
|
||||
{message.type === "success" ? "check_circle" : "error"}
|
||||
</span>
|
||||
{message.text}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<Card padding="md">
|
||||
<div className="flex items-center gap-3 mb-4">
|
||||
<div className="size-8 rounded-lg flex items-center justify-center bg-indigo-500/10">
|
||||
<span className="material-symbols-outlined text-indigo-500 text-xl">swap_horiz</span>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="font-medium text-sm">CLIProxyAPI Fallback</h3>
|
||||
<p className="text-xs text-text-muted">
|
||||
When enabled, failed requests are retried through CLIProxyAPI (localhost:8317)
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<label className="text-sm text-text-main">Enable CLIProxyAPI Fallback</label>
|
||||
<Toggle
|
||||
checked={cpaEnabled}
|
||||
onChange={(checked) => updateSetting("cliproxyapi_fallback_enabled", checked)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{cpaEnabled && (
|
||||
<>
|
||||
<div>
|
||||
<label className="text-xs text-text-muted mb-1.5 block">CLIProxyAPI URL</label>
|
||||
<Input
|
||||
value={cpaUrl}
|
||||
onChange={(e) => updateSetting("cliproxyapi_url", e.target.value)}
|
||||
placeholder="http://127.0.0.1:8317"
|
||||
className="w-full"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="text-xs text-text-muted mb-1.5 block">
|
||||
Fallback Status Codes (comma-separated)
|
||||
</label>
|
||||
<Input
|
||||
value={cpaCodes}
|
||||
onChange={(e) => updateSetting("cliproxyapi_fallback_codes", e.target.value)}
|
||||
placeholder="502,401,403,429,503"
|
||||
className="w-full"
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<Card padding="md">
|
||||
<h3 className="font-medium text-sm mb-4">CLIProxyAPI Status</h3>
|
||||
{loading ? (
|
||||
<div className="flex items-center gap-2 text-text-muted text-sm">
|
||||
<span className="material-symbols-outlined animate-spin text-base">
|
||||
progress_activity
|
||||
</span>
|
||||
Loading...
|
||||
</div>
|
||||
) : toolStateError ? (
|
||||
<p className="text-sm text-text-muted">{toolStateError}</p>
|
||||
) : toolState ? (
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<div className="p-3 rounded-lg bg-bg-secondary">
|
||||
<p className="text-xs text-text-muted mb-1">Status</p>
|
||||
<div className="flex items-center gap-1.5">
|
||||
<span className={`material-symbols-outlined text-sm ${statusColor}`}>
|
||||
{statusIcon}
|
||||
</span>
|
||||
<p className={`text-sm font-medium capitalize ${statusColor}`}>
|
||||
{toolState.status?.replace("_", " ") || "Unknown"}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="p-3 rounded-lg bg-bg-secondary">
|
||||
<p className="text-xs text-text-muted mb-1">Version</p>
|
||||
<p className="text-sm font-medium">
|
||||
{toolState.installedVersion ? `v${toolState.installedVersion}` : "Not installed"}
|
||||
</p>
|
||||
</div>
|
||||
<div className="p-3 rounded-lg bg-bg-secondary">
|
||||
<p className="text-xs text-text-muted mb-1">Health</p>
|
||||
<p
|
||||
className={`text-sm font-medium ${
|
||||
toolState.healthStatus === "healthy"
|
||||
? "text-green-600 dark:text-green-400"
|
||||
: toolState.healthStatus === "unhealthy"
|
||||
? "text-red-600 dark:text-red-400"
|
||||
: "text-text-muted"
|
||||
}`}
|
||||
>
|
||||
{toolState.healthStatus === "healthy"
|
||||
? "Healthy"
|
||||
: toolState.healthStatus === "unhealthy"
|
||||
? "Unhealthy"
|
||||
: "Unknown"}
|
||||
</p>
|
||||
</div>
|
||||
<div className="p-3 rounded-lg bg-bg-secondary">
|
||||
<p className="text-xs text-text-muted mb-1">Port</p>
|
||||
<p className="text-sm font-mono">{toolState.port || 8317}</p>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-sm text-text-muted">CLIProxyAPI not detected</p>
|
||||
)}
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -21,6 +21,7 @@ import CacheStatsCard from "./components/CacheStatsCard";
|
||||
import CacheSettingsTab from "./components/CacheSettingsTab";
|
||||
import MemorySkillsTab from "./components/MemorySkillsTab";
|
||||
import ResilienceTab from "./components/ResilienceTab";
|
||||
import CliproxyapiSettingsTab from "./components/CliproxyapiSettingsTab";
|
||||
|
||||
const tabs = [
|
||||
{ id: "general", labelKey: "general", icon: "settings" },
|
||||
@@ -113,7 +114,12 @@ export default function SettingsPage() {
|
||||
|
||||
{activeTab === "resilience" && <ResilienceTab />}
|
||||
|
||||
{activeTab === "advanced" && <ProxyTab />}
|
||||
{activeTab === "advanced" && (
|
||||
<div className="flex flex-col gap-6">
|
||||
<ProxyTab />
|
||||
<CliproxyapiSettingsTab />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* App Info */}
|
||||
|
||||
@@ -1,5 +1,18 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { listMemories, createMemory } from "@/lib/memory/store";
|
||||
import { MemoryType } from "@/lib/memory/types";
|
||||
import { z } from "zod";
|
||||
import { validateBody, isValidationFailure } from "@/shared/validation/helpers";
|
||||
|
||||
const createMemorySchema = z.object({
|
||||
content: z.string().min(1),
|
||||
key: z.string().min(1),
|
||||
type: z.nativeEnum(MemoryType).default(MemoryType.FACTUAL),
|
||||
sessionId: z.string().default(""),
|
||||
apiKeyId: z.string().default(""),
|
||||
metadata: z.record(z.string(), z.unknown()).default({}),
|
||||
expiresAt: z.coerce.date().nullable().default(null),
|
||||
});
|
||||
|
||||
export async function GET(request: Request) {
|
||||
try {
|
||||
@@ -26,8 +39,12 @@ export async function GET(request: Request) {
|
||||
|
||||
export async function POST(request: Request) {
|
||||
try {
|
||||
const body = await request.json();
|
||||
const memoryId = await createMemory(body);
|
||||
const rawBody = await request.json();
|
||||
const validation = validateBody(createMemorySchema, rawBody);
|
||||
if (isValidationFailure(validation)) {
|
||||
return NextResponse.json(validation.error, { status: 400 });
|
||||
}
|
||||
const memoryId = await createMemory(validation.data);
|
||||
return NextResponse.json({ success: true, id: memoryId });
|
||||
} catch (err: unknown) {
|
||||
const error = err instanceof Error ? err.message : String(err);
|
||||
|
||||
@@ -67,6 +67,13 @@ export async function GET() {
|
||||
dedup: {
|
||||
inflightRequests: getInflightCount(),
|
||||
},
|
||||
cryptography: {
|
||||
status:
|
||||
process.env.STORAGE_ENCRYPTION_KEY && process.env.STORAGE_ENCRYPTION_KEY.length >= 32
|
||||
? "healthy"
|
||||
: "missing_or_invalid",
|
||||
provider: "aes-256-gcm",
|
||||
},
|
||||
setupComplete: settings?.setupComplete || false,
|
||||
});
|
||||
} catch (error) {
|
||||
|
||||
@@ -60,6 +60,14 @@ export async function GET(
|
||||
if (action === "authorize") {
|
||||
const redirectUri = searchParams.get("redirect_uri") || "http://localhost:8080/callback";
|
||||
const authData = generateAuthData(provider, redirectUri);
|
||||
if (provider === "qoder" && !authData.authUrl) {
|
||||
return NextResponse.json({
|
||||
...authData,
|
||||
supported: false,
|
||||
error:
|
||||
"Qoder browser OAuth is experimental and disabled by default. Configure QODER_OAUTH_* environment variables or use a Personal Access Token.",
|
||||
});
|
||||
}
|
||||
return NextResponse.json(authData);
|
||||
}
|
||||
|
||||
|
||||
@@ -83,7 +83,7 @@ export async function PUT(request: Request, { params }: { params: Promise<{ id:
|
||||
prefix: prefix.trim(),
|
||||
baseUrl: sanitizedBaseUrl,
|
||||
chatPath: chatPath || null,
|
||||
modelsPath: modelsPath || null,
|
||||
modelsPath: isClaudeCodeCompatibleProvider(id) ? null : modelsPath || null,
|
||||
};
|
||||
|
||||
if (node.type === "openai-compatible") {
|
||||
@@ -105,8 +105,12 @@ export async function PUT(request: Request, { params }: { params: Promise<{ id:
|
||||
baseUrl: sanitizedBaseUrl,
|
||||
nodeName: updated.name,
|
||||
chatPath: updated.chatPath || undefined,
|
||||
modelsPath: updated.modelsPath || undefined,
|
||||
} as JsonRecord;
|
||||
if (updated.modelsPath) {
|
||||
providerSpecificData.modelsPath = updated.modelsPath;
|
||||
} else {
|
||||
delete providerSpecificData.modelsPath;
|
||||
}
|
||||
if (node.type === "openai-compatible") {
|
||||
providerSpecificData.apiType = apiType;
|
||||
}
|
||||
|
||||
@@ -109,7 +109,7 @@ export async function POST(request) {
|
||||
baseUrl: sanitizedBaseUrl,
|
||||
name: name.trim(),
|
||||
chatPath: chatPath || null,
|
||||
modelsPath: modelsPath || null,
|
||||
modelsPath: compatMode === "cc" ? null : modelsPath || null,
|
||||
});
|
||||
return NextResponse.json({ node }, { status: 201 });
|
||||
}
|
||||
|
||||
@@ -57,7 +57,6 @@ export async function POST(request) {
|
||||
providerSpecificData: {
|
||||
baseUrl: sanitizeClaudeCodeCompatibleBaseUrl(baseUrl),
|
||||
chatPath: chatPath || undefined,
|
||||
modelsPath: modelsPath || undefined,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { getProviderConnectionById } from "@/models";
|
||||
import {
|
||||
isClaudeCodeCompatibleProvider,
|
||||
isOpenAICompatibleProvider,
|
||||
isAnthropicCompatibleProvider,
|
||||
} from "@/shared/constants/providers";
|
||||
import { PROVIDER_MODELS } from "@/shared/constants/models";
|
||||
import { getModelIsHidden } from "@/lib/localDb";
|
||||
import { getStaticQoderModels } from "@omniroute/open-sse/services/qoderCli.ts";
|
||||
|
||||
type JsonRecord = Record<string, unknown>;
|
||||
|
||||
@@ -99,6 +101,7 @@ const STATIC_MODEL_PROVIDERS: Record<string, () => Array<{ id: string; name: str
|
||||
{ id: "glm-4.7", name: "GLM 4.7" },
|
||||
{ id: "kimi-k2.5", name: "Kimi K2.5" },
|
||||
],
|
||||
qoder: () => getStaticQoderModels(),
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -127,16 +130,56 @@ const PROVIDER_MODELS_CONFIG: Record<string, ProviderModelsConfigEntry> = {
|
||||
parseResponse: (data) => data.data || [],
|
||||
},
|
||||
gemini: {
|
||||
url: "https://generativelanguage.googleapis.com/v1beta/models",
|
||||
url: "https://generativelanguage.googleapis.com/v1beta/models?pageSize=1000",
|
||||
method: "GET",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
authQuery: "key", // Use query param for API key
|
||||
parseResponse: (data) =>
|
||||
(data.models || []).map((m) => ({
|
||||
...m,
|
||||
id: (m.name || m.id || "").replace(/^models\//, ""),
|
||||
name: m.displayName || (m.name || "").replace(/^models\//, ""),
|
||||
})),
|
||||
parseResponse: (data) => {
|
||||
const METHOD_TO_ENDPOINT: Record<string, string> = {
|
||||
generateContent: "chat",
|
||||
embedContent: "embeddings",
|
||||
predict: "images",
|
||||
predictLongRunning: "images",
|
||||
bidiGenerateContent: "audio",
|
||||
generateAnswer: "chat",
|
||||
};
|
||||
const IGNORED_METHODS = new Set([
|
||||
"countTokens",
|
||||
"countTextTokens",
|
||||
"createCachedContent",
|
||||
"batchGenerateContent",
|
||||
"asyncBatchEmbedContent",
|
||||
]);
|
||||
|
||||
return (data.models || []).map((m: Record<string, unknown>) => {
|
||||
const methods: string[] = Array.isArray(m.supportedGenerationMethods)
|
||||
? m.supportedGenerationMethods
|
||||
: [];
|
||||
const endpoints = [
|
||||
...new Set(
|
||||
methods
|
||||
.filter((method) => !IGNORED_METHODS.has(method))
|
||||
.map((method) => METHOD_TO_ENDPOINT[method] || "chat")
|
||||
),
|
||||
];
|
||||
if (endpoints.length === 0) endpoints.push("chat");
|
||||
|
||||
return {
|
||||
...m,
|
||||
id: ((m.name as string) || (m.id as string) || "").replace(/^models\//, ""),
|
||||
name: (m.displayName as string) || ((m.name as string) || "").replace(/^models\//, ""),
|
||||
supportedEndpoints: endpoints,
|
||||
...(typeof m.inputTokenLimit === "number"
|
||||
? { inputTokenLimit: m.inputTokenLimit }
|
||||
: {}),
|
||||
...(typeof m.outputTokenLimit === "number"
|
||||
? { outputTokenLimit: m.outputTokenLimit }
|
||||
: {}),
|
||||
...(typeof m.description === "string" ? { description: m.description } : {}),
|
||||
...(m.thinking === true ? { supportsThinking: true } : {}),
|
||||
};
|
||||
});
|
||||
},
|
||||
},
|
||||
// gemini-cli handled via retrieveUserQuota (see GET handler)
|
||||
qwen: {
|
||||
@@ -555,6 +598,13 @@ export async function GET(
|
||||
}
|
||||
|
||||
if (isAnthropicCompatibleProvider(provider)) {
|
||||
if (isClaudeCodeCompatibleProvider(provider)) {
|
||||
return NextResponse.json(
|
||||
{ error: `Provider ${provider} does not support models listing` },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
let baseUrl = getProviderBaseUrl(connection.providerSpecificData);
|
||||
if (!baseUrl) {
|
||||
return NextResponse.json(
|
||||
@@ -638,7 +688,7 @@ export async function GET(
|
||||
// Build request URL
|
||||
let url = config.url;
|
||||
if (config.authQuery) {
|
||||
url += `?${config.authQuery}=${token}`;
|
||||
url += `${url.includes("?") ? "&" : "?"}${config.authQuery}=${token}`;
|
||||
}
|
||||
|
||||
// Build headers
|
||||
@@ -647,7 +697,7 @@ export async function GET(
|
||||
headers[config.authHeader] = (config.authPrefix || "") + token;
|
||||
}
|
||||
|
||||
// Make request
|
||||
// Make request (with pagination for providers that use nextPageToken, e.g. Gemini)
|
||||
const fetchOptions: any = {
|
||||
method: config.method,
|
||||
headers,
|
||||
@@ -657,24 +707,53 @@ export async function GET(
|
||||
fetchOptions.body = JSON.stringify(config.body);
|
||||
}
|
||||
|
||||
const response = await fetch(url, fetchOptions);
|
||||
let allModels: any[] = [];
|
||||
let pageUrl = url;
|
||||
let pageCount = 0;
|
||||
const MAX_PAGES = 20; // Safety limit
|
||||
const seenTokens = new Set<string>();
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
console.log(`Error fetching models from ${provider}:`, errorText);
|
||||
return NextResponse.json(
|
||||
{ error: `Failed to fetch models: ${response.status}` },
|
||||
{ status: response.status }
|
||||
);
|
||||
while (pageUrl && pageCount < MAX_PAGES) {
|
||||
pageCount++;
|
||||
const response = await fetch(pageUrl, {
|
||||
...fetchOptions,
|
||||
signal: AbortSignal.timeout(15_000),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
console.log(`Error fetching models from ${provider}:`, errorText);
|
||||
return NextResponse.json(
|
||||
{ error: `Failed to fetch models: ${response.status}` },
|
||||
{ status: response.status }
|
||||
);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
const pageModels = config.parseResponse(data);
|
||||
allModels = allModels.concat(pageModels);
|
||||
|
||||
const nextPageToken = data.nextPageToken;
|
||||
if (!nextPageToken) break;
|
||||
if (seenTokens.has(nextPageToken)) {
|
||||
console.warn(`[models] ${provider}: duplicate nextPageToken detected, stopping pagination`);
|
||||
break;
|
||||
}
|
||||
seenTokens.add(nextPageToken);
|
||||
pageUrl = `${config.url}${config.url.includes("?") ? "&" : "?"}pageToken=${encodeURIComponent(nextPageToken)}`;
|
||||
if (config.authQuery) {
|
||||
pageUrl += `&${config.authQuery}=${token}`;
|
||||
}
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
const models = config.parseResponse(data);
|
||||
if (pageCount > 1) {
|
||||
console.log(`[models] ${provider}: fetched ${allModels.length} models across ${pageCount} pages`);
|
||||
}
|
||||
|
||||
return buildResponse({
|
||||
provider,
|
||||
connectionId,
|
||||
models,
|
||||
models: allModels,
|
||||
});
|
||||
} catch (error) {
|
||||
console.log("Error fetching provider models:", error);
|
||||
|
||||
@@ -169,11 +169,27 @@ export async function DELETE(request: Request, { params }: { params: Promise<{ i
|
||||
try {
|
||||
const { id } = await params;
|
||||
|
||||
// Fetch connection before deleting to check provider type
|
||||
const connection = await getProviderConnectionById(id);
|
||||
if (!connection) {
|
||||
return NextResponse.json({ error: "Connection not found" }, { status: 404 });
|
||||
}
|
||||
|
||||
const deleted = await deleteProviderConnection(id);
|
||||
if (!deleted) {
|
||||
return NextResponse.json({ error: "Connection not found" }, { status: 404 });
|
||||
}
|
||||
|
||||
// Clean up synced available models for this connection
|
||||
if (connection.provider === "gemini") {
|
||||
try {
|
||||
const { deleteSyncedAvailableModelsForConnection } = await import("@/lib/db/models");
|
||||
await deleteSyncedAvailableModelsForConnection("gemini", id);
|
||||
} catch (e) {
|
||||
console.error("Failed to clean up synced models for deleted gemini connection:", e);
|
||||
}
|
||||
}
|
||||
|
||||
// Auto sync to Cloud if enabled
|
||||
await syncToCloudIfEnabled();
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { getProviderConnectionById } from "@/models";
|
||||
import { getCustomModels, replaceCustomModels } from "@/lib/db/models";
|
||||
import { getCustomModels, replaceCustomModels, replaceSyncedAvailableModelsForConnection } from "@/lib/db/models";
|
||||
import {
|
||||
syncManagedAvailableModelAliases,
|
||||
usesManagedAvailableModels,
|
||||
@@ -188,11 +188,38 @@ export async function POST(request: Request, { params }: { params: Promise<{ id:
|
||||
id: m.id || m.name || m.model,
|
||||
name: m.name || m.displayName || m.id || m.model,
|
||||
source: "auto-sync",
|
||||
...(Array.isArray(m.supportedEndpoints) && m.supportedEndpoints.length > 0
|
||||
? { supportedEndpoints: m.supportedEndpoints }
|
||||
: {}),
|
||||
...(typeof m.inputTokenLimit === "number" ? { inputTokenLimit: m.inputTokenLimit } : {}),
|
||||
...(typeof m.outputTokenLimit === "number" ? { outputTokenLimit: m.outputTokenLimit } : {}),
|
||||
...(typeof m.description === "string" ? { description: m.description } : {}),
|
||||
...(m.supportsThinking === true ? { supportsThinking: true } : {}),
|
||||
}))
|
||||
.filter((m: any) => m.id && !registryIds.has(m.id));
|
||||
|
||||
const previousModels = await getCustomModels(logProvider);
|
||||
const replaced = await replaceCustomModels(logProvider, models);
|
||||
|
||||
// For Gemini: also write to syncedAvailableModels (unioned across API keys)
|
||||
if (logProvider === "gemini") {
|
||||
try {
|
||||
const syncedModels = models.map((m: any) => ({
|
||||
id: m.id,
|
||||
name: m.name || m.id,
|
||||
source: "api-sync" as const,
|
||||
...(m.supportedEndpoints ? { supportedEndpoints: m.supportedEndpoints } : {}),
|
||||
...(typeof m.inputTokenLimit === "number" ? { inputTokenLimit: m.inputTokenLimit } : {}),
|
||||
...(typeof m.outputTokenLimit === "number" ? { outputTokenLimit: m.outputTokenLimit } : {}),
|
||||
...(typeof m.description === "string" ? { description: m.description } : {}),
|
||||
...(m.supportsThinking === true ? { supportsThinking: true } : {}),
|
||||
}));
|
||||
await replaceSyncedAvailableModelsForConnection(logProvider, id, syncedModels);
|
||||
} catch (e) {
|
||||
console.error("Failed to union synced available models for gemini:", e);
|
||||
}
|
||||
}
|
||||
|
||||
const modelChanges = summarizeModelChanges(previousModels, replaced);
|
||||
|
||||
let syncedAliases = 0;
|
||||
|
||||
@@ -92,6 +92,7 @@ const OAUTH_TEST_CONFIG = {
|
||||
const CLI_RUNTIME_PROVIDER_MAP = {
|
||||
cline: "cline",
|
||||
kilocode: "kilo",
|
||||
qoder: "qoder",
|
||||
};
|
||||
|
||||
/** POST body is optional; when present, only known fields are validated. */
|
||||
@@ -206,8 +207,12 @@ function classifyFailure({
|
||||
);
|
||||
}
|
||||
|
||||
async function getProviderRuntimeStatus(provider: string) {
|
||||
const toolId = CLI_RUNTIME_PROVIDER_MAP[provider];
|
||||
async function getProviderRuntimeStatus(connection: any) {
|
||||
const provider = typeof connection?.provider === "string" ? connection.provider : "";
|
||||
let toolId = CLI_RUNTIME_PROVIDER_MAP[provider];
|
||||
if (provider === "qoder" && connection?.authType !== "apikey") {
|
||||
toolId = null;
|
||||
}
|
||||
if (!toolId) return null;
|
||||
|
||||
try {
|
||||
@@ -566,7 +571,7 @@ export async function testSingleConnection(connectionId: string, validationModel
|
||||
|
||||
let result;
|
||||
const startTime = Date.now();
|
||||
const runtime = await getProviderRuntimeStatus(provider);
|
||||
const runtime = await getProviderRuntimeStatus(connection);
|
||||
|
||||
if ((runtime as any)?.diagnosis) {
|
||||
result = {
|
||||
|
||||
@@ -15,6 +15,7 @@ import { getConsistentMachineId } from "@/shared/utils/machineId";
|
||||
import { syncToCloud } from "@/lib/cloudSync";
|
||||
import { createProviderSchema } from "@/shared/validation/schemas";
|
||||
import { isValidationFailure, validateBody } from "@/shared/validation/helpers";
|
||||
import { normalizeQoderPatProviderData } from "@omniroute/open-sse/services/qoderCli.ts";
|
||||
|
||||
// GET /api/providers - List all connections
|
||||
export async function GET() {
|
||||
@@ -61,6 +62,7 @@ export async function POST(request: Request) {
|
||||
// Business validation
|
||||
const isValidProvider =
|
||||
APIKEY_PROVIDERS[provider] ||
|
||||
provider === "qoder" ||
|
||||
isOpenAICompatibleProvider(provider) ||
|
||||
isAnthropicCompatibleProvider(provider);
|
||||
|
||||
@@ -72,6 +74,10 @@ export async function POST(request: Request) {
|
||||
const allowMultipleCompatibleConnections =
|
||||
process.env.ALLOW_MULTI_CONNECTIONS_PER_COMPAT_NODE === "true";
|
||||
|
||||
if (provider === "qoder") {
|
||||
providerSpecificData = normalizeQoderPatProviderData(providerSpecificData || {});
|
||||
}
|
||||
|
||||
if (isOpenAICompatibleProvider(provider)) {
|
||||
const node: any = await getProviderNodeById(provider);
|
||||
if (!node) {
|
||||
@@ -139,6 +145,8 @@ export async function POST(request: Request) {
|
||||
testStatus: testStatus || "unknown",
|
||||
});
|
||||
|
||||
// Note: Gemini model sync is now triggered client-side with progress dialog
|
||||
|
||||
// Hide sensitive fields
|
||||
const result: Record<string, any> = { ...newConnection };
|
||||
delete result.apiKey;
|
||||
|
||||
@@ -8,6 +8,7 @@ import { updateSettingsSchema } from "@/shared/validation/settingsSchemas";
|
||||
import { isValidationFailure, validateBody } from "@/shared/validation/helpers";
|
||||
import { setCliCompatProviders } from "../../../../open-sse/config/cliFingerprints";
|
||||
import { getConsistentMachineId } from "@/shared/utils/machineId";
|
||||
import { validateProxyUrl, upsertUpstreamProxyConfig } from "@/lib/db/upstreamProxy";
|
||||
|
||||
export async function GET() {
|
||||
try {
|
||||
@@ -102,6 +103,29 @@ export async function PATCH(request) {
|
||||
|
||||
const settings = await updateSettings(body);
|
||||
|
||||
// Sync CLIProxyAPI settings to upstream_proxy_config table
|
||||
const cpaUrl = rawBody.cliproxyapi_url as string | undefined;
|
||||
const cpaFallback = rawBody.cliproxyapi_fallback_enabled as boolean | undefined;
|
||||
if (cpaUrl && typeof cpaUrl === "string") {
|
||||
const urlValidation = validateProxyUrl(cpaUrl);
|
||||
if (urlValidation.valid === false) {
|
||||
return NextResponse.json(
|
||||
{ error: `Invalid CLIProxyAPI URL: ${urlValidation.error}` },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
}
|
||||
if (cpaFallback !== undefined || cpaUrl !== undefined) {
|
||||
const enabled =
|
||||
cpaFallback ?? (settings as Record<string, unknown>).cliproxyapi_fallback_enabled;
|
||||
const mode = enabled ? "fallback" : "native";
|
||||
await upsertUpstreamProxyConfig({
|
||||
providerId: "cliproxyapi",
|
||||
mode,
|
||||
enabled: !!enabled,
|
||||
});
|
||||
}
|
||||
|
||||
// Clear health check log cache if that setting was updated
|
||||
if ("hideHealthCheckLogs" in body) {
|
||||
clearHealthCheckLogCache();
|
||||
|
||||
@@ -1,25 +1,28 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { getDbInstance } from "@/lib/db/core";
|
||||
import { skillRegistry } from "@/lib/skills/registry";
|
||||
import { z } from "zod";
|
||||
import { validateBody, isValidationFailure } from "@/shared/validation/helpers";
|
||||
|
||||
const updateSkillSchema = z.object({
|
||||
enabled: z.boolean(),
|
||||
});
|
||||
|
||||
export async function PUT(request: Request, props: { params: Promise<{ id: string }> }) {
|
||||
try {
|
||||
const { id } = await props.params;
|
||||
const body = await request.json();
|
||||
|
||||
if (typeof body.enabled !== "boolean") {
|
||||
return NextResponse.json(
|
||||
{ error: "Invalid payload, missing enabled boolean" },
|
||||
{ status: 400 }
|
||||
);
|
||||
const rawBody = await request.json();
|
||||
const validation = validateBody(updateSkillSchema, rawBody);
|
||||
if (isValidationFailure(validation)) {
|
||||
return NextResponse.json(validation.error, { status: 400 });
|
||||
}
|
||||
|
||||
const db = getDbInstance();
|
||||
db.prepare("UPDATE skills SET enabled = ? WHERE id = ?").run(body.enabled ? 1 : 0, id);
|
||||
db.prepare("UPDATE skills SET enabled = ? WHERE id = ?").run(validation.data.enabled ? 1 : 0, id);
|
||||
|
||||
await skillRegistry.loadFromDatabase();
|
||||
|
||||
return NextResponse.json({ success: true, enabled: body.enabled });
|
||||
return NextResponse.json({ success: true, enabled: validation.data.enabled });
|
||||
} catch (err: unknown) {
|
||||
const error = err instanceof Error ? err.message : String(err);
|
||||
return NextResponse.json({ error }, { status: 500 });
|
||||
|
||||
33
src/app/api/synced-available-models/route.ts
Normal file
33
src/app/api/synced-available-models/route.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { getSyncedAvailableModels, getAllSyncedAvailableModels } from "@/lib/db/models";
|
||||
import { isAuthenticated } from "@/shared/utils/apiAuth";
|
||||
|
||||
/**
|
||||
* GET /api/synced-available-models?provider=<id>
|
||||
* List synced available models for a provider (or all providers).
|
||||
*/
|
||||
export async function GET(request: Request) {
|
||||
try {
|
||||
if (!(await isAuthenticated(request))) {
|
||||
return Response.json(
|
||||
{ error: { message: "Authentication required", type: "invalid_api_key" } },
|
||||
{ status: 401 }
|
||||
);
|
||||
}
|
||||
|
||||
const { searchParams } = new URL(request.url);
|
||||
const provider = searchParams.get("provider");
|
||||
|
||||
if (provider) {
|
||||
const models = await getSyncedAvailableModels(provider);
|
||||
return Response.json({ models });
|
||||
}
|
||||
|
||||
const allModels = await getAllSyncedAvailableModels();
|
||||
return Response.json(allModels);
|
||||
} catch {
|
||||
return Response.json(
|
||||
{ error: { message: "Failed to fetch synced available models", type: "server_error" } },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,8 @@ import { getAllModerationModels } from "@omniroute/open-sse/config/moderationReg
|
||||
import { getAllVideoModels } from "@omniroute/open-sse/config/videoRegistry.ts";
|
||||
import { getAllMusicModels } from "@omniroute/open-sse/config/musicRegistry.ts";
|
||||
import { REGISTRY } from "@omniroute/open-sse/config/providerRegistry.ts";
|
||||
import { getSyncedAvailableModels } from "@/lib/db/models";
|
||||
import { getCompatibleFallbackModels } from "@/lib/providers/managedAvailableModels";
|
||||
|
||||
const FALLBACK_ALIAS_TO_PROVIDER = {
|
||||
ag: "antigravity",
|
||||
@@ -179,7 +181,7 @@ export async function getUnifiedModelsResponse(
|
||||
connections = connections.filter((c) => c.isActive !== false);
|
||||
} catch (e) {
|
||||
// If database not available, show no provider models (safe default)
|
||||
console.log("Could not fetch providers, showing only combos/custom models");
|
||||
console.log("[catalog] Could not fetch providers:", e);
|
||||
}
|
||||
|
||||
// Get provider nodes (for compatible providers with custom prefixes)
|
||||
@@ -296,6 +298,61 @@ export async function getUnifiedModelsResponse(
|
||||
}
|
||||
}
|
||||
|
||||
// Gemini: synced API models exclusively (outside PROVIDER_MODELS loop since registry is empty)
|
||||
if (activeAliases.has("gemini") && !blockedProviders.has("gemini")) {
|
||||
try {
|
||||
const syncedModels = await getSyncedAvailableModels("gemini");
|
||||
for (const sm of syncedModels) {
|
||||
const aliasId = `gemini/${sm.id}`;
|
||||
if (getModelIsHidden("gemini", sm.id)) continue;
|
||||
|
||||
// Convert supportedEndpoints to type/subtype for endpoint categorization
|
||||
const endpoints = Array.isArray(sm.supportedEndpoints) ? sm.supportedEndpoints : ["chat"];
|
||||
let modelType: string | undefined;
|
||||
if (endpoints.includes("embeddings")) modelType = "embedding";
|
||||
else if (endpoints.includes("images")) modelType = "image";
|
||||
else if (endpoints.includes("audio")) modelType = "audio";
|
||||
|
||||
models.push({
|
||||
id: aliasId,
|
||||
object: "model",
|
||||
created: timestamp,
|
||||
owned_by: "gemini",
|
||||
permission: [],
|
||||
root: sm.id,
|
||||
parent: null,
|
||||
...(modelType ? { type: modelType } : {}),
|
||||
...(modelType === "audio" ? { subtype: "transcription" } : {}),
|
||||
...(sm.inputTokenLimit ? { context_length: sm.inputTokenLimit } : {}),
|
||||
...(endpoints.length > 1 || !endpoints.includes("chat")
|
||||
? { supported_endpoints: endpoints }
|
||||
: {}),
|
||||
});
|
||||
|
||||
// For audio models, also add a speech variant so they appear in both sections
|
||||
if (modelType === "audio") {
|
||||
models.push({
|
||||
id: aliasId,
|
||||
object: "model",
|
||||
created: timestamp,
|
||||
owned_by: "gemini",
|
||||
permission: [],
|
||||
root: sm.id,
|
||||
parent: null,
|
||||
type: "audio",
|
||||
subtype: "speech",
|
||||
...(sm.inputTokenLimit ? { context_length: sm.inputTokenLimit } : {}),
|
||||
...(endpoints.length > 1 || !endpoints.includes("chat")
|
||||
? { supported_endpoints: endpoints }
|
||||
: {}),
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("[catalog] Error fetching synced Gemini models:", err);
|
||||
}
|
||||
}
|
||||
|
||||
// Helper: check if a provider is active (by provider id or alias)
|
||||
const isProviderActive = (provider: string) => {
|
||||
if (activeAliases.size === 0) return false; // No active connections = show nothing
|
||||
@@ -394,6 +451,8 @@ export async function getUnifiedModelsResponse(
|
||||
try {
|
||||
const customModelsMap = (await getAllCustomModels()) as Record<string, unknown>;
|
||||
for (const [providerId, rawProviderCustomModels] of Object.entries(customModelsMap)) {
|
||||
// Skip Gemini — handled by syncedAvailableModels above
|
||||
if (providerId === "gemini") continue;
|
||||
const providerCustomModels = Array.isArray(rawProviderCustomModels)
|
||||
? rawProviderCustomModels.filter(
|
||||
(model): model is Record<string, unknown> =>
|
||||
@@ -454,6 +513,9 @@ export async function getUnifiedModelsResponse(
|
||||
...(endpoints.length > 1 || !endpoints.includes("chat")
|
||||
? { supported_endpoints: endpoints }
|
||||
: {}),
|
||||
...(typeof (model as any).inputTokenLimit === "number"
|
||||
? { context_length: (model as any).inputTokenLimit }
|
||||
: {}),
|
||||
...(visionFields || {}),
|
||||
});
|
||||
|
||||
@@ -476,6 +538,9 @@ export async function getUnifiedModelsResponse(
|
||||
parent: aliasId,
|
||||
custom: true,
|
||||
...(modelType ? { type: modelType } : {}),
|
||||
...(typeof (model as any).inputTokenLimit === "number"
|
||||
? { context_length: (model as any).inputTokenLimit }
|
||||
: {}),
|
||||
...(providerVisionFields || {}),
|
||||
});
|
||||
}
|
||||
@@ -485,6 +550,47 @@ export async function getUnifiedModelsResponse(
|
||||
console.log("Could not fetch custom models");
|
||||
}
|
||||
|
||||
// Add managed fallback models for compatible providers that don't import a model list.
|
||||
for (const conn of connections) {
|
||||
const providerId = typeof conn.provider === "string" ? conn.provider : null;
|
||||
if (!providerId) continue;
|
||||
if (blockedProviders.has(providerId)) continue;
|
||||
|
||||
const fallbackModels = getCompatibleFallbackModels(providerId);
|
||||
if (!Array.isArray(fallbackModels) || fallbackModels.length === 0) continue;
|
||||
|
||||
const prefix = providerIdToPrefix[providerId];
|
||||
const alias = prefix || providerIdToAlias[providerId] || providerId;
|
||||
|
||||
for (const model of fallbackModels) {
|
||||
const modelId = typeof model.id === "string" ? model.id : null;
|
||||
if (!modelId) continue;
|
||||
if (getModelIsHidden(providerId, modelId)) continue;
|
||||
|
||||
const aliasId = `${alias}/${modelId}`;
|
||||
if (models.some((m) => m.id === aliasId)) continue;
|
||||
|
||||
const visionFields =
|
||||
getVisionCapabilityFields(aliasId) || getVisionCapabilityFields(modelId);
|
||||
const contextLength =
|
||||
typeof (model as any).contextLength === "number"
|
||||
? (model as any).contextLength
|
||||
: undefined;
|
||||
|
||||
models.push({
|
||||
id: aliasId,
|
||||
object: "model",
|
||||
created: timestamp,
|
||||
owned_by: providerId,
|
||||
permission: [],
|
||||
root: modelId,
|
||||
parent: null,
|
||||
...(contextLength ? { context_length: contextLength } : {}),
|
||||
...(visionFields || {}),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Filter by API key permissions if requested
|
||||
const authHeader = request.headers.get("authorization");
|
||||
let finalModels = models;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { CORS_ORIGIN } from "@/shared/utils/cors";
|
||||
import { PROVIDER_MODELS } from "@/shared/constants/models";
|
||||
import { getAllCustomModels, getSyncedAvailableModels } from "@/lib/db/models";
|
||||
|
||||
/**
|
||||
* Handle CORS preflight
|
||||
@@ -16,13 +17,13 @@ export async function OPTIONS() {
|
||||
|
||||
/**
|
||||
* GET /v1beta/models - Gemini compatible models list
|
||||
* Returns models in Gemini API format
|
||||
* Returns models in Gemini API format with real token limits when available.
|
||||
*/
|
||||
export async function GET() {
|
||||
try {
|
||||
// Collect all models from all providers
|
||||
const models = [];
|
||||
|
||||
// Built-in models (hardcoded defaults)
|
||||
for (const [provider, providerModels] of Object.entries(PROVIDER_MODELS)) {
|
||||
for (const model of providerModels) {
|
||||
models.push({
|
||||
@@ -36,8 +37,60 @@ export async function GET() {
|
||||
}
|
||||
}
|
||||
|
||||
// Gemini: always replace hardcoded entries with synced models (no fallback)
|
||||
// Always remove hardcoded gemini entries — even if sync returns empty
|
||||
for (let i = models.length - 1; i >= 0; i--) {
|
||||
if (typeof (models[i] as any).name === "string" && (models[i] as any).name.startsWith("models/gemini/")) {
|
||||
models.splice(i, 1);
|
||||
}
|
||||
}
|
||||
try {
|
||||
const syncedGeminiModels = await getSyncedAvailableModels("gemini");
|
||||
for (const m of syncedGeminiModels) {
|
||||
models.push({
|
||||
name: `models/gemini/${m.id}`,
|
||||
displayName: m.name || m.id,
|
||||
...(typeof m.description === "string" ? { description: m.description } : {}),
|
||||
supportedGenerationMethods: ["generateContent"],
|
||||
inputTokenLimit: typeof m.inputTokenLimit === "number" ? m.inputTokenLimit : 128000,
|
||||
outputTokenLimit: typeof m.outputTokenLimit === "number" ? m.outputTokenLimit : 8192,
|
||||
...(m.supportsThinking === true ? { thinking: true } : {}),
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("[v1beta/models] Error fetching synced Gemini models:", err);
|
||||
}
|
||||
|
||||
// Custom models (use stored metadata from provider APIs)
|
||||
try {
|
||||
const customModelsMap = (await getAllCustomModels()) as Record<string, unknown>;
|
||||
for (const [providerId, rawModels] of Object.entries(customModelsMap)) {
|
||||
if (!Array.isArray(rawModels)) continue;
|
||||
// Skip Gemini — handled by syncedAvailableModels above
|
||||
if (providerId === "gemini") continue;
|
||||
for (const model of rawModels) {
|
||||
if (!model || typeof model !== "object" || typeof (model as any).id !== "string") continue;
|
||||
const m = model as Record<string, unknown>;
|
||||
if (m.isHidden === true) continue;
|
||||
models.push({
|
||||
name: `models/${providerId}/${m.id}`,
|
||||
displayName: m.name || m.id,
|
||||
...(typeof m.description === "string" ? { description: m.description } : {}),
|
||||
supportedGenerationMethods: ["generateContent"],
|
||||
inputTokenLimit:
|
||||
typeof m.inputTokenLimit === "number" ? m.inputTokenLimit : 128000,
|
||||
outputTokenLimit:
|
||||
typeof m.outputTokenLimit === "number" ? m.outputTokenLimit : 8192,
|
||||
...(m.supportsThinking === true ? { thinking: true } : {}),
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// Custom models are optional — skip on error
|
||||
}
|
||||
|
||||
return Response.json({ models });
|
||||
} catch (error) {
|
||||
} catch (error: any) {
|
||||
console.log("Error fetching models:", error);
|
||||
return Response.json({ error: { message: error.message } }, { status: 500 });
|
||||
}
|
||||
|
||||
20
src/app/api/version-manager/check-update/route.ts
Normal file
20
src/app/api/version-manager/check-update/route.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
"use server";
|
||||
|
||||
import { NextResponse } from "next/server";
|
||||
import { checkForUpdates } from "@/lib/versionManager";
|
||||
import { requireManagementAuth } from "@/lib/api/requireManagementAuth";
|
||||
|
||||
export async function GET(request: Request) {
|
||||
const authError = await requireManagementAuth(request);
|
||||
if (authError) return authError;
|
||||
|
||||
try {
|
||||
const { searchParams } = new URL(request.url);
|
||||
const tool = searchParams.get("tool") || "cliproxyapi";
|
||||
const result = await checkForUpdates(tool);
|
||||
return NextResponse.json(result);
|
||||
} catch (error) {
|
||||
console.error("[version-manager] check-update error:", error);
|
||||
return NextResponse.json({ error: "Failed to check for updates" }, { status: 500 });
|
||||
}
|
||||
}
|
||||
34
src/app/api/version-manager/install/route.ts
Normal file
34
src/app/api/version-manager/install/route.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
"use server";
|
||||
|
||||
import { NextResponse } from "next/server";
|
||||
import { installTool } from "@/lib/versionManager";
|
||||
import { versionManagerInstallSchema } from "@/shared/validation/schemas";
|
||||
import { isValidationFailure, validateBody } from "@/shared/validation/helpers";
|
||||
import { requireManagementAuth } from "@/lib/api/requireManagementAuth";
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const authError = await requireManagementAuth(request);
|
||||
if (authError) return authError;
|
||||
|
||||
let rawBody;
|
||||
try {
|
||||
rawBody = await request.json();
|
||||
} catch {
|
||||
return NextResponse.json({ error: "Invalid JSON body" }, { status: 400 });
|
||||
}
|
||||
|
||||
const validation = validateBody(versionManagerInstallSchema, rawBody);
|
||||
if (isValidationFailure(validation)) {
|
||||
return NextResponse.json({ error: validation.error }, { status: 400 });
|
||||
}
|
||||
|
||||
try {
|
||||
const { tool, version } = validation.data;
|
||||
const result = await installTool(tool, version || undefined);
|
||||
return NextResponse.json({ success: true, ...result });
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : "Installation failed";
|
||||
console.error("[version-manager] install error:", message);
|
||||
return NextResponse.json({ error: message }, { status: 500 });
|
||||
}
|
||||
}
|
||||
34
src/app/api/version-manager/restart/route.ts
Normal file
34
src/app/api/version-manager/restart/route.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
"use server";
|
||||
|
||||
import { NextResponse } from "next/server";
|
||||
import { restartTool } from "@/lib/versionManager";
|
||||
import { versionManagerToolSchema } from "@/shared/validation/schemas";
|
||||
import { isValidationFailure, validateBody } from "@/shared/validation/helpers";
|
||||
import { requireManagementAuth } from "@/lib/api/requireManagementAuth";
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const authError = await requireManagementAuth(request);
|
||||
if (authError) return authError;
|
||||
|
||||
let rawBody;
|
||||
try {
|
||||
rawBody = await request.json();
|
||||
} catch {
|
||||
return NextResponse.json({ error: "Invalid JSON body" }, { status: 400 });
|
||||
}
|
||||
|
||||
const validation = validateBody(versionManagerToolSchema, rawBody);
|
||||
if (isValidationFailure(validation)) {
|
||||
return NextResponse.json({ error: validation.error }, { status: 400 });
|
||||
}
|
||||
|
||||
try {
|
||||
const { tool } = validation.data;
|
||||
const result = await restartTool(tool);
|
||||
return NextResponse.json({ success: true, ...result });
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : "Failed to restart";
|
||||
console.error("[version-manager] restart error:", message);
|
||||
return NextResponse.json({ error: message }, { status: 500 });
|
||||
}
|
||||
}
|
||||
34
src/app/api/version-manager/start/route.ts
Normal file
34
src/app/api/version-manager/start/route.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
"use server";
|
||||
|
||||
import { NextResponse } from "next/server";
|
||||
import { startTool } from "@/lib/versionManager";
|
||||
import { versionManagerToolSchema } from "@/shared/validation/schemas";
|
||||
import { isValidationFailure, validateBody } from "@/shared/validation/helpers";
|
||||
import { requireManagementAuth } from "@/lib/api/requireManagementAuth";
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const authError = await requireManagementAuth(request);
|
||||
if (authError) return authError;
|
||||
|
||||
let rawBody;
|
||||
try {
|
||||
rawBody = await request.json();
|
||||
} catch {
|
||||
return NextResponse.json({ error: "Invalid JSON body" }, { status: 400 });
|
||||
}
|
||||
|
||||
const validation = validateBody(versionManagerToolSchema, rawBody);
|
||||
if (isValidationFailure(validation)) {
|
||||
return NextResponse.json({ error: validation.error }, { status: 400 });
|
||||
}
|
||||
|
||||
try {
|
||||
const { tool } = validation.data;
|
||||
const result = await startTool(tool);
|
||||
return NextResponse.json({ success: true, ...result });
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : "Failed to start";
|
||||
console.error("[version-manager] start error:", message);
|
||||
return NextResponse.json({ error: message }, { status: 500 });
|
||||
}
|
||||
}
|
||||
18
src/app/api/version-manager/status/route.ts
Normal file
18
src/app/api/version-manager/status/route.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
"use server";
|
||||
|
||||
import { NextResponse } from "next/server";
|
||||
import { getVersionManagerStatus } from "@/lib/versionManager";
|
||||
import { requireManagementAuth } from "@/lib/api/requireManagementAuth";
|
||||
|
||||
export async function GET(request: Request) {
|
||||
const authError = await requireManagementAuth(request);
|
||||
if (authError) return authError;
|
||||
|
||||
try {
|
||||
const status = await getVersionManagerStatus();
|
||||
return NextResponse.json(status);
|
||||
} catch (error) {
|
||||
console.error("[version-manager] status error:", error);
|
||||
return NextResponse.json({ error: "Failed to get status" }, { status: 500 });
|
||||
}
|
||||
}
|
||||
34
src/app/api/version-manager/stop/route.ts
Normal file
34
src/app/api/version-manager/stop/route.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
"use server";
|
||||
|
||||
import { NextResponse } from "next/server";
|
||||
import { stopTool } from "@/lib/versionManager";
|
||||
import { versionManagerToolSchema } from "@/shared/validation/schemas";
|
||||
import { isValidationFailure, validateBody } from "@/shared/validation/helpers";
|
||||
import { requireManagementAuth } from "@/lib/api/requireManagementAuth";
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const authError = await requireManagementAuth(request);
|
||||
if (authError) return authError;
|
||||
|
||||
let rawBody;
|
||||
try {
|
||||
rawBody = await request.json();
|
||||
} catch {
|
||||
return NextResponse.json({ error: "Invalid JSON body" }, { status: 400 });
|
||||
}
|
||||
|
||||
const validation = validateBody(versionManagerToolSchema, rawBody);
|
||||
if (isValidationFailure(validation)) {
|
||||
return NextResponse.json({ error: validation.error }, { status: 400 });
|
||||
}
|
||||
|
||||
try {
|
||||
const { tool } = validation.data;
|
||||
await stopTool(tool);
|
||||
return NextResponse.json({ success: true });
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : "Failed to stop";
|
||||
console.error("[version-manager] stop error:", message);
|
||||
return NextResponse.json({ error: message }, { status: 500 });
|
||||
}
|
||||
}
|
||||
@@ -143,6 +143,7 @@
|
||||
"dashboard": "لوحة القيادة",
|
||||
"providers": "المزوّدون",
|
||||
"combos": "المجموعات",
|
||||
"autoCombo": "Auto Combo",
|
||||
"usage": "الاستخدام",
|
||||
"analytics": "التحليلات",
|
||||
"costs": "التكاليف",
|
||||
@@ -152,8 +153,12 @@
|
||||
"media": "الوسائط",
|
||||
"settings": "الإعدادات",
|
||||
"translator": "مترجم",
|
||||
"playground": "ملعب النماذج",
|
||||
"searchTools": "Search Tools",
|
||||
"agents": "وكلاء",
|
||||
"docs": "المستندات",
|
||||
"issues": "القضايا",
|
||||
"endpoints": "نقاط النهاية",
|
||||
"apiManager": "مدير API",
|
||||
"logs": "سجلات",
|
||||
"auditLog": "سجل التدقيق",
|
||||
@@ -165,6 +170,11 @@
|
||||
"debug": "تصحيح",
|
||||
"system": "النظام",
|
||||
"help": "مساعدة",
|
||||
"primarySection": "Main",
|
||||
"cliSection": "CLI",
|
||||
"debugSection": "Debug",
|
||||
"systemSection": "System",
|
||||
"helpSection": "Help",
|
||||
"serverDisconnected": "تم قطع اتصال الخادم",
|
||||
"serverDisconnectedMsg": "تم إيقاف الخادم الوكيل أو يتم إعادة تشغيله.",
|
||||
"expandSidebar": "قم بتوسيع الشريط الجانبي",
|
||||
@@ -180,19 +190,9 @@
|
||||
"themeViolet": "بنفسجي ساحر",
|
||||
"themeOrange": "أورانج (Orange)",
|
||||
"themeCyan": "كيان",
|
||||
"endpoints": "نقاط النهاية",
|
||||
"playground": "ملعب النماذج",
|
||||
"agents": "وكلاء",
|
||||
"cliToolsShort": "أدوات",
|
||||
"autoCombo": "Auto Combo",
|
||||
"searchTools": "Search Tools",
|
||||
"cache": "Cache",
|
||||
"cacheShort": "Cache",
|
||||
"cliSection": "CLI",
|
||||
"debugSection": "Debug",
|
||||
"helpSection": "Help",
|
||||
"primarySection": "Main",
|
||||
"systemSection": "System"
|
||||
"cacheShort": "Cache"
|
||||
},
|
||||
"themesPage": {
|
||||
"title": "المواضيع",
|
||||
@@ -220,6 +220,10 @@
|
||||
"homeDescription": "مرحبًا بك في OmniRoute",
|
||||
"endpoint": "Endpoints",
|
||||
"endpointDescription": "Manage proxy endpoints, MCP, A2A, and API endpoints",
|
||||
"mcp": "MCP",
|
||||
"mcpDescription": "Model Context Protocol server management and tools",
|
||||
"a2a": "A2A",
|
||||
"a2aDescription": "Agent-to-Agent protocol tasks and observability",
|
||||
"settings": "الإعدادات",
|
||||
"settingsDescription": "إدارة تفضيلاتك",
|
||||
"openaiCompatible": "متوافق مع OpenAI",
|
||||
@@ -227,11 +231,7 @@
|
||||
"media": "الإعلام",
|
||||
"mediaDescription": "إنشاء الصور ومقاطع الفيديو والموسيقى",
|
||||
"themes": "المواضيع",
|
||||
"themesDescription": "اختر سمة لون للوحة المعلومات بأكملها",
|
||||
"mcp": "MCP",
|
||||
"mcpDescription": "Model Context Protocol server management and tools",
|
||||
"a2a": "A2A",
|
||||
"a2aDescription": "Agent-to-Agent protocol tasks and observability"
|
||||
"themesDescription": "اختر سمة لون للوحة المعلومات بأكملها"
|
||||
},
|
||||
"home": {
|
||||
"quickStart": "بداية سريعة",
|
||||
@@ -332,6 +332,26 @@
|
||||
"keyCreatedNote": "انسخ هذا المفتاح وقم بتخزينه الآن - لن يتم عرضه مرة أخرى.",
|
||||
"done": "تم",
|
||||
"savePermissions": "حفظ الأذونات",
|
||||
"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",
|
||||
"allowAll": "السماح للكل",
|
||||
"restrict": "تقييد",
|
||||
"allowAllInfo": "يمكن لهذا المفتاح الوصول إلى كافة الموديلات المتاحة.",
|
||||
@@ -356,6 +376,7 @@
|
||||
"failedUpdatePermissionsRetry": "فشل تحديث الأذونات. يرجى المحاولة مرة أخرى.",
|
||||
"unknownProvider": "غير معروف",
|
||||
"copyMaskedKey": "انسخ المفتاح المقنع",
|
||||
"keyOnlyAvailableAtCreation": "Full key available only at creation time — copy it when you first create the key",
|
||||
"modelsCount": "{count, plural, one {# model} other {# models}}",
|
||||
"lastUsedOn": "الأخير: {date}",
|
||||
"editPermissions": "تحرير الأذونات",
|
||||
@@ -365,28 +386,7 @@
|
||||
"permissionsTitle": "الأذونات: {name}",
|
||||
"allowAllDesc": "يمكن لهذا المفتاح الوصول إلى كافة الموديلات المتاحة.",
|
||||
"restrictDesc": "يمكن لهذا المفتاح الوصول إلى {selectedCount} من طرازات {totalModels}.",
|
||||
"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"
|
||||
"selectedCount": "تم تحديد {count}"
|
||||
},
|
||||
"auditLog": {
|
||||
"title": "سجل التدقيق",
|
||||
@@ -425,6 +425,57 @@
|
||||
"videoDescription": "Create videos with AnimateDiff, Stable Video Diffusion via ComfyUI or SD WebUI.",
|
||||
"musicDescription": "Compose music using Stable Audio Open or MusicGen via ComfyUI."
|
||||
},
|
||||
"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",
|
||||
"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",
|
||||
"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"
|
||||
},
|
||||
"cliTools": {
|
||||
"title": "أدوات سطر الأوامر",
|
||||
"noActiveProviders": "لا يوجد مقدمي خدمات نشطين",
|
||||
@@ -497,6 +548,11 @@
|
||||
"antigravityStep2Prefix": "2. أضف",
|
||||
"antigravityStep2Suffix": "إلى ملف المضيفين الخاص بك كـ 127.0.0.1.",
|
||||
"antigravityStep3": "3. سيتم فتح Antigravity والطلبات بالوكالة.",
|
||||
"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.",
|
||||
"sudoPasswordRequiredTitle": "كلمة مرور سودو مطلوبة",
|
||||
"sudoPasswordHint": "كلمة مرور المسؤول مطلوبة لتعديل ملف المضيفين وإعدادات وكيل النظام.",
|
||||
"enterSudoPassword": "أدخل كلمة مرور سودو",
|
||||
@@ -569,6 +625,22 @@
|
||||
"openClawManualConfiguration": "فتح المخلب - التكوين اليدوي",
|
||||
"clineManualConfiguration": "التكوين اليدوي لكلين",
|
||||
"kiloManualConfiguration": "التكوين اليدوي للكيلو كود",
|
||||
"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 وقواعد التوجيه."
|
||||
},
|
||||
"toolDescriptions": {
|
||||
"antigravity": "جوجل مكافحة الجاذبية IDE مع MITM",
|
||||
"claude": "الأنثروبي كلود كود CLI",
|
||||
@@ -706,27 +778,6 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"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": {
|
||||
@@ -988,7 +1039,12 @@
|
||||
"settingsApi": "Settings API",
|
||||
"categoryCore": "واجهات API الأساسية",
|
||||
"categoryMedia": "الوسائط والمتعدد الوسائط",
|
||||
"categorySearch": "Search & Discovery",
|
||||
"categoryUtility": "أدوات فرعية وإدارة",
|
||||
"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.",
|
||||
"enableCloudTitle": "تمكين الوكيل السحابي",
|
||||
"whatYouGet": "ما سوف تحصل عليه",
|
||||
"cloudBenefitAccess": "الوصول إلى API الخاص بك من أي مكان في العالم",
|
||||
@@ -1021,6 +1077,25 @@
|
||||
"cloudWorkerUnreachable": "لا يمكن الوصول إلى عامل السحابة. تأكد من تشغيل الخدمة السحابية (npm run dev in /cloud).",
|
||||
"connectionFailed": "فشل الاتصال",
|
||||
"syncFailed": "فشلت مزامنة البيانات السحابية",
|
||||
"cloudflaredTitle": "Cloudflare Quick Tunnel",
|
||||
"cloudflaredDescription": "Create a Cloudflare Quick Tunnel for this endpoint to make it accessible from the internet without configuring DNS.",
|
||||
"cloudflaredUrlNotice": "Creates a temporary Cloudflare Quick Tunnel. URLs change after restart.",
|
||||
"cloudflaredEnable": "Enable Tunnel",
|
||||
"cloudflaredInstallAndEnable": "Install & Enable",
|
||||
"cloudflaredDisable": "Stop Tunnel",
|
||||
"cloudflaredRunning": "Running",
|
||||
"cloudflaredStarting": "Starting",
|
||||
"cloudflaredStoppedState": "Stopped",
|
||||
"cloudflaredNotInstalled": "Not installed",
|
||||
"cloudflaredUnsupported": "Unsupported",
|
||||
"cloudflaredError": "Error",
|
||||
"cloudflaredStarted": "Cloudflare tunnel started",
|
||||
"cloudflaredStopped": "Cloudflare tunnel stopped",
|
||||
"cloudflaredRequestFailed": "Failed to update Cloudflare tunnel",
|
||||
"cloudflaredTemporaryNote": "Quick Tunnel URLs are temporary and will change after restart.",
|
||||
"cloudflaredUnsupportedNote": "This platform is not supported for managed installation.",
|
||||
"cloudflaredIdleNote": "Create a temporary Cloudflare Quick Tunnel for this endpoint.",
|
||||
"cloudflaredLastError": "Last error: {error}",
|
||||
"providerModelsTitle": "{provider} — النماذج",
|
||||
"noModelsForProvider": "لا توجد نماذج متاحة لهذا المزود.",
|
||||
"chat": "الدردشة",
|
||||
@@ -1055,31 +1130,19 @@
|
||||
"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",
|
||||
"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.",
|
||||
"cloudflaredDescription": "Create a Cloudflare Quick Tunnel for this endpoint to make it accessible from the internet without configuring DNS.",
|
||||
"cloudflaredDisable": "Stop Tunnel",
|
||||
"cloudflaredEnable": "Enable Tunnel",
|
||||
"cloudflaredError": "Error",
|
||||
"cloudflaredIdleNote": "Create a temporary Cloudflare Quick Tunnel for this endpoint.",
|
||||
"cloudflaredInstallAndEnable": "Install & Enable",
|
||||
"cloudflaredLastError": "Last error: {error}",
|
||||
"cloudflaredNotInstalled": "Not installed",
|
||||
"cloudflaredRequestFailed": "Failed to update Cloudflare tunnel",
|
||||
"cloudflaredRunning": "Running",
|
||||
"cloudflaredStarted": "Cloudflare tunnel started",
|
||||
"cloudflaredStarting": "Starting",
|
||||
"cloudflaredStopped": "Cloudflare tunnel stopped",
|
||||
"cloudflaredStoppedState": "Stopped",
|
||||
"cloudflaredTemporaryNote": "Quick Tunnel URLs are temporary and will change after restart.",
|
||||
"cloudflaredTitle": "Cloudflare Quick Tunnel",
|
||||
"cloudflaredUnsupported": "Unsupported",
|
||||
"cloudflaredUnsupportedNote": "This platform is not supported for managed installation.",
|
||||
"cloudflaredUrlNotice": "Creates a temporary Cloudflare Quick Tunnel. URLs change after restart."
|
||||
"completionsLegacyDesc": "Legacy OpenAI text completions — accepts both prompt string and messages array format"
|
||||
},
|
||||
"endpoints": {
|
||||
"tabProxy": "Endpoint Proxy",
|
||||
"tabApiEndpoints": "نقاط نهاية API",
|
||||
"apiEndpointsTitle": "نقاط نهاية API",
|
||||
"apiEndpointsDescription": "نقاط نهاية API الخلفية التي يمكن أن تستهلكها التطبيقات والخدمات الأخرى.",
|
||||
"comingSoon": "قريباً",
|
||||
"plannedFeatures": "الميزات المخطط لها",
|
||||
"featureRestApi": "كتالوج REST API مع التوثيق التفاعلي",
|
||||
"featureWebhooks": "تكوين Webhooks واشتراكات الأحداث",
|
||||
"featureSwagger": "إنشاء مواصفات OpenAPI / Swagger تلقائياً",
|
||||
"featureAuth": "إدارة مفاتيح API ونطاقات OAuth لكل نقطة نهاية"
|
||||
},
|
||||
"mcpDashboard": {
|
||||
"loading": "جارٍ تحميل لوحة تحكم MCP...",
|
||||
@@ -1251,6 +1314,12 @@
|
||||
"issuesLabel": "تم اكتشاف المشكلات",
|
||||
"operational": "التشغيلية",
|
||||
"providers": "مقدمي الخدمات",
|
||||
"configuredProvidersLabel": "تم تكوينه في لوحة القيادة",
|
||||
"configuredProvidersHint": "الموفرون الذين لديهم بيانات اعتماد محفوظة في /dashboard/providers، بغض النظر عن حالة وقت التشغيل.",
|
||||
"activeProviders": "{count} active",
|
||||
"activeProvidersHint": "الموفرون الذين تم تكوينهم ممكّنون حاليًا لطلبات التوجيه.",
|
||||
"monitoredProviders": "{count} monitored",
|
||||
"monitoredProvidersHint": "يتم تتبع مقدمي الخدمة حاليًا بواسطة مراقبي صحة قاطع الدائرة.",
|
||||
"healthyCount": "{count} صحي",
|
||||
"nodeVersion": "العقدة {version}",
|
||||
"failures": "{count} فشل",
|
||||
@@ -1269,13 +1338,7 @@
|
||||
"resetAllTitle": "إعادة ضبط كافة قواطع الدائرة إلى الحالة الصحية",
|
||||
"resetting": "إعادة الضبط...",
|
||||
"resetAll": "إعادة ضبط الكل",
|
||||
"until": "حتى {time}",
|
||||
"activeProviders": "{count} active",
|
||||
"monitoredProviders": "{count} monitored",
|
||||
"configuredProvidersLabel": "تم تكوينه في لوحة القيادة",
|
||||
"configuredProvidersHint": "الموفرون الذين لديهم بيانات اعتماد محفوظة في /dashboard/providers، بغض النظر عن حالة وقت التشغيل.",
|
||||
"activeProvidersHint": "الموفرون الذين تم تكوينهم ممكّنون حاليًا لطلبات التوجيه.",
|
||||
"monitoredProvidersHint": "يتم تتبع مقدمي الخدمة حاليًا بواسطة مراقبي صحة قاطع الدائرة."
|
||||
"until": "حتى {time}"
|
||||
},
|
||||
"limits": {
|
||||
"title": "الحدود والحصص",
|
||||
@@ -1437,6 +1500,7 @@
|
||||
"clearing": "المقاصة...",
|
||||
"until": "حتى {time}",
|
||||
"providerTestFailed": "فشل اختبار الموفر",
|
||||
"providerTestTimeout": "Provider test timed out — too many connections to test at once",
|
||||
"modeTest": "{mode} اختبار",
|
||||
"passedCount": "تم {count}",
|
||||
"failedCount": "فشل {count}",
|
||||
@@ -1478,6 +1542,14 @@
|
||||
"chatCompletions": "استكمالات الدردشة",
|
||||
"importingModels": "جارٍ الاستيراد...",
|
||||
"importFromModels": "الاستيراد من / النماذج",
|
||||
"allModelsAlreadyImported": "جميع النماذج مستوردة بالفعل",
|
||||
"noNewModelsToImport": "لا توجد نماذج جديدة للاستيراد — جميع النماذج موجودة بالفعل في السجل أو قائمة النماذج المخصصة",
|
||||
"skippingExistingModels": "تخطي {count} نماذج موجودة",
|
||||
"autoSync": "المزامنة التلقائية",
|
||||
"autoSyncTooltip": "تحديث قائمة النماذج تلقائيًا كل 24 ساعة (قابلة للتكوين عبر MODEL_SYNC_INTERVAL_HOURS)",
|
||||
"autoSyncEnabled": "تم تمكين المزامنة التلقائية — سيتم تحديث النماذج بشكل دوري",
|
||||
"autoSyncDisabled": "تم تعطيل المزامنة التلقائية",
|
||||
"autoSyncToggleFailed": "فشل في تبديل المزامنة التلقائية",
|
||||
"clearAllModels": "مسح كافة النماذج",
|
||||
"clearAllModelsConfirm": "هل أنت متأكد أنك تريد إزالة كافة النماذج لهذا الموفر؟ لا يمكن التراجع عن هذا.",
|
||||
"clearAllModelsSuccess": "تم مسح جميع النماذج",
|
||||
@@ -1531,6 +1603,8 @@
|
||||
"addFirstConnectionHint": "أضف اتصالك الأول للبدء",
|
||||
"addConnection": "إضافة اتصال",
|
||||
"availableModels": "النماذج المتاحة",
|
||||
"builtInModels": "Built-in models",
|
||||
"builtInModelsHint": "Registry models for this provider. Use the pencil to set compatibility options.",
|
||||
"pageAutoRefresh": "سيتم تحديث الصفحة تلقائيا...",
|
||||
"statusDisabled": "معطل",
|
||||
"statusConnected": "متصل",
|
||||
@@ -1571,6 +1645,26 @@
|
||||
"openRouterModelPlaceholder": "أنثروبي/كلود-3-أوبوس",
|
||||
"customModels": "نماذج مخصصة",
|
||||
"customModelsHint": "قم بإضافة معرفات النماذج غير الموجودة في القائمة الافتراضية. ستكون هذه متاحة للتوجيه.",
|
||||
"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",
|
||||
"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": "معرف النموذج",
|
||||
"customModelPlaceholder": "على سبيل المثال جي بي تي-4.5-توربو",
|
||||
"loading": "جار التحميل...",
|
||||
@@ -1614,52 +1708,21 @@
|
||||
"compatibleBaseUrlHint": "Root URL of your {type}-compatible API. Use Advanced Settings for custom endpoint paths.",
|
||||
"apiKeyForCheck": "مفتاح API (للفحص)",
|
||||
"compatibleProdPlaceholder": "{type} متوافق (المنتج)",
|
||||
"providerTestTimeout": "Provider test timed out — too many connections to test at once",
|
||||
"tokenRefreshed": "Token refreshed successfully",
|
||||
"tokenRefreshFailed": "Token refresh failed",
|
||||
"applyCodexAuthLocal": "Apply auth",
|
||||
"exportCodexAuthFile": "Export auth",
|
||||
"codexAuthAppliedLocal": "Codex auth.json applied locally",
|
||||
"codexAuthApplyFailed": "Failed to apply Codex auth.json locally",
|
||||
"codexAuthExported": "Codex auth.json exported",
|
||||
"codexAuthExportFailed": "Failed to export Codex auth.json",
|
||||
"advancedSettings": "Advanced Settings",
|
||||
"chatPathLabel": "Chat Endpoint Path",
|
||||
"chatPathPlaceholder": "/chat/completions",
|
||||
"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)",
|
||||
"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",
|
||||
"autoSync": "المزامنة التلقائية",
|
||||
"autoSyncTooltip": "تحديث قائمة النماذج تلقائيًا كل 24 ساعة (قابلة للتكوين عبر MODEL_SYNC_INTERVAL_HOURS)",
|
||||
"autoSyncEnabled": "تم تمكين المزامنة التلقائية — سيتم تحديث النماذج بشكل دوري",
|
||||
"autoSyncDisabled": "تم تعطيل المزامنة التلقائية",
|
||||
"autoSyncToggleFailed": "فشل في تبديل المزامنة التلقائية",
|
||||
"allModelsAlreadyImported": "جميع النماذج مستوردة بالفعل",
|
||||
"noNewModelsToImport": "لا توجد نماذج جديدة للاستيراد — جميع النماذج موجودة بالفعل في السجل أو قائمة النماذج المخصصة",
|
||||
"skippingExistingModels": "تخطي {count} نماذج موجودة",
|
||||
"applyCodexAuthLocal": "Apply auth",
|
||||
"codexAuthAppliedLocal": "Codex auth.json applied locally",
|
||||
"codexAuthApplyFailed": "Failed to apply Codex auth.json locally",
|
||||
"codexAuthExportFailed": "Failed to export Codex auth.json",
|
||||
"codexAuthExported": "Codex auth.json exported",
|
||||
"exportCodexAuthFile": "Export auth"
|
||||
"modelsPathHint": "Custom models path for validation (e.g. /v4/models)"
|
||||
},
|
||||
"settings": {
|
||||
"title": "الإعدادات",
|
||||
@@ -1683,6 +1746,8 @@
|
||||
"darkMode": "الوضع المظلم",
|
||||
"lightMode": "وضع الضوء",
|
||||
"systemTheme": "موضوع النظام",
|
||||
"debugToggle": "Enable Debug Mode",
|
||||
"sidebarVisibilityToggle": "Show Sidebar Items",
|
||||
"enableCache": "تمكين ذاكرة التخزين المؤقت",
|
||||
"cacheTTL": "ذاكرة التخزين المؤقت TTL",
|
||||
"maxCacheSize": "الحد الأقصى لحجم ذاكرة التخزين المؤقت",
|
||||
@@ -1691,6 +1756,17 @@
|
||||
"cacheMisses": "يفتقد ذاكرة التخزين المؤقت",
|
||||
"hitRate": "معدل الإصابة",
|
||||
"cacheEntries": "إدخالات ذاكرة التخزين المؤقت",
|
||||
"cacheSettings": "Cache Settings",
|
||||
"semanticCache": "Semantic Cache",
|
||||
"maxEntries": "Max Entries",
|
||||
"ttlMinutes": "TTL (minutes)",
|
||||
"promptCache": "ذاكرة التخزين المؤقت الفوري",
|
||||
"strategy": "Strategy",
|
||||
"preserveClientCache": "Preserve Client Cache",
|
||||
"enabled": "Enabled",
|
||||
"loading": "Loading...",
|
||||
"saving": "جارٍ الحفظ...",
|
||||
"save": "Save",
|
||||
"circuitBreaker": "قواطع دوائر",
|
||||
"retryPolicy": "إعادة محاولة السياسة",
|
||||
"maxRetries": "ماكس إعادة المحاولة",
|
||||
@@ -1698,6 +1774,10 @@
|
||||
"timeoutMs": "المهلة (مللي ثانية)",
|
||||
"enableSystemPrompt": "تمكين موجه النظام",
|
||||
"systemPromptText": "نص موجه النظام",
|
||||
"autoDisableBannedAccounts": "Auto-Disable Banned Accounts",
|
||||
"autoDisableDescription": "Permanently mark provider connections as deactivated if they return specific terminal ban signals (e.g. HTTP 403 'verify your account'). This removes them from the combo rotation.",
|
||||
"autoDisableThreshold": "Ban Threshold",
|
||||
"autoDisableThresholdDesc": "Consecutive ban signals required before permanent deactivation.",
|
||||
"enableThinking": "تمكين التفكير",
|
||||
"maxThinkingTokens": "رموز التفكير ماكس",
|
||||
"enableProxy": "تمكين الوكيل",
|
||||
@@ -1734,6 +1814,9 @@
|
||||
"themeLight": "ضوء",
|
||||
"themeDark": "الظلام",
|
||||
"themeSystem": "النظام",
|
||||
"sidebarVisibility": "Hide sidebar items",
|
||||
"sidebarVisibilityDesc": "Hide any sidebar navigation entry to reduce visual clutter.",
|
||||
"sidebarVisibilityHint": "Any sidebar section is hidden automatically when a...",
|
||||
"hideHealthLogs": "إخفاء سجلات التحقق من الصحة",
|
||||
"hideHealthLogsDesc": "عند التشغيل، قم بمنع رسائل [HealthCheck] في وحدة تحكم الخادم",
|
||||
"themeAccent": "لون السمة",
|
||||
@@ -1746,7 +1829,20 @@
|
||||
"themeViolet": "بنفسجي",
|
||||
"themeOrange": "برتقالي",
|
||||
"themeCyan": "سماوي",
|
||||
"promptCache": "ذاكرة التخزين المؤقت الفوري",
|
||||
"whitelabeling": "Branding",
|
||||
"whitelabelingDesc": "Customize the application name and logo",
|
||||
"appName": "Application Name",
|
||||
"appNameDesc": "Display name shown in sidebar and browser tab",
|
||||
"customLogo": "Custom Logo URL",
|
||||
"customLogoDesc": "URL to your custom logo image",
|
||||
"uploadLogo": "Upload Logo",
|
||||
"resetLogo": "Reset to Default",
|
||||
"logoPreview": "Preview",
|
||||
"customFavicon": "Browser Favicon",
|
||||
"customFaviconDesc": "URL to your custom favicon (shown in browser tab)",
|
||||
"uploadFavicon": "Upload Favicon",
|
||||
"resetFavicon": "Reset Favicon",
|
||||
"faviconPreview": "Favicon Preview",
|
||||
"flushCache": "مسح ذاكرة التخزين المؤقت",
|
||||
"flushing": "فلاشينغ…",
|
||||
"size": "الحجم",
|
||||
@@ -1804,7 +1900,15 @@
|
||||
"providersBlocked": "{count} تم حظر الموفر (المزودين) من /models",
|
||||
"blockProviderTitle": "كتلة {provider}",
|
||||
"unblockProviderTitle": "قم بإلغاء حظر {provider}",
|
||||
"cliFingerprint": "CLI Fingerprint Matching",
|
||||
"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}",
|
||||
"routingStrategy": "استراتيجية التوجيه",
|
||||
"routingAdvancedGuideTitle": "توجيه التوجيه المتقدم",
|
||||
"routingAdvancedGuideHint1": "استخدمFill First للحصول على الأولوية التي يمكن التنبؤ بها، وRound Robin للعدالة، وP2C لمرونة زمن الاستجابة.",
|
||||
"routingAdvancedGuideHint2": "إذا اختلف مقدمو الخدمة من حيث الجودة/التكلفة، فابدأ بـ Cost Opt للعمل في الخلفية والأقل استخدامًا للارتداء المتوازن.",
|
||||
"fillFirst": "املأ أولاً",
|
||||
"fillFirstDesc": "استخدم الحسابات بترتيب الأولوية",
|
||||
"roundRobin": "جولة روبن",
|
||||
@@ -1899,6 +2003,9 @@
|
||||
"fillModelAndProviders": "يرجى ملء اسم النموذج ومقدمي الخدمات",
|
||||
"addAtLeastOneProvider": "قم بإضافة مزود واحد على الأقل",
|
||||
"comboDefaultsTitle": "افتراضيات التحرير والسرد",
|
||||
"comboDefaultsGuideTitle": "كيفية ضبط إعدادات التحرير والسرد الافتراضية",
|
||||
"comboDefaultsGuideHint1": "اجعل عمليات إعادة المحاولة منخفضة في التدفقات ذات زمن الوصول المنخفض؛ زيادة المهلة فقط لمهام الجيل الطويل.",
|
||||
"comboDefaultsGuideHint2": "استخدم تجاوزات الموفر عندما يحتاج أحد الموفرين إلى سلوك مهلة/إعادة محاولة مختلف عن الإعدادات الافتراضية العامة.",
|
||||
"globalComboConfig": "تكوين التحرير والسرد العالمي",
|
||||
"defaultStrategy": "الإستراتيجية الافتراضية",
|
||||
"defaultStrategyDesc": "يتم تطبيقه على المجموعات الجديدة بدون استراتيجية واضحة",
|
||||
@@ -2026,7 +2133,6 @@
|
||||
"unsaved": "غير محفوظ",
|
||||
"resetDefaults": "إعادة تعيين الإعدادات الافتراضية",
|
||||
"saveProvider": "حفظ المزود",
|
||||
"saving": "جارٍ الحفظ...",
|
||||
"model": "نموذج",
|
||||
"models": "نماذج",
|
||||
"moreProviders": "{count} المزيد من مقدمي الخدمة",
|
||||
@@ -2057,36 +2163,7 @@
|
||||
"customPricingNote": "يمكنك تجاوز التسعير الافتراضي لنماذج محددة. تحظى التجاوزات المخصصة بالأولوية على الأسعار التي يتم اكتشافها تلقائيًا.",
|
||||
"editPricing": "تحرير التسعير",
|
||||
"viewFullDetails": "عرض التفاصيل الكاملة",
|
||||
"themeCoral": "مرجاني",
|
||||
"cliFingerprint": "CLI Fingerprint Matching",
|
||||
"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}",
|
||||
"routingAdvancedGuideTitle": "توجيه التوجيه المتقدم",
|
||||
"routingAdvancedGuideHint1": "استخدمFill First للحصول على الأولوية التي يمكن التنبؤ بها، وRound Robin للعدالة، وP2C لمرونة زمن الاستجابة.",
|
||||
"routingAdvancedGuideHint2": "إذا اختلف مقدمو الخدمة من حيث الجودة/التكلفة، فابدأ بـ Cost Opt للعمل في الخلفية والأقل استخدامًا للارتداء المتوازن.",
|
||||
"comboDefaultsGuideTitle": "كيفية ضبط إعدادات التحرير والسرد الافتراضية",
|
||||
"comboDefaultsGuideHint1": "اجعل عمليات إعادة المحاولة منخفضة في التدفقات ذات زمن الوصول المنخفض؛ زيادة المهلة فقط لمهام الجيل الطويل.",
|
||||
"comboDefaultsGuideHint2": "استخدم تجاوزات الموفر عندما يحتاج أحد الموفرين إلى سلوك مهلة/إعادة محاولة مختلف عن الإعدادات الافتراضية العامة.",
|
||||
"sidebarVisibility": "Hide sidebar items",
|
||||
"sidebarVisibilityDesc": "Hide any sidebar navigation entry to reduce visual clutter.",
|
||||
"sidebarVisibilityHint": "Any sidebar section is hidden automatically when a...",
|
||||
"semanticCache": "Semantic Cache",
|
||||
"autoDisableDescription": "Permanently mark provider connections as deactivated if they return specific terminal ban signals (e.g. HTTP 403 'verify your account'). This removes them from the combo rotation.",
|
||||
"preserveClientCache": "Preserve Client Cache",
|
||||
"cacheSettings": "Cache Settings",
|
||||
"autoDisableThreshold": "Ban Threshold",
|
||||
"autoDisableBannedAccounts": "Auto-Disable Banned Accounts",
|
||||
"ttlMinutes": "TTL (minutes)",
|
||||
"maxEntries": "Max Entries",
|
||||
"loading": "Loading...",
|
||||
"save": "Save",
|
||||
"autoDisableThresholdDesc": "Consecutive ban signals required before permanent deactivation.",
|
||||
"debugToggle": "Enable Debug Mode",
|
||||
"sidebarVisibilityToggle": "Show Sidebar Items",
|
||||
"enabled": "Enabled",
|
||||
"strategy": "Strategy"
|
||||
"themeCoral": "مرجاني"
|
||||
},
|
||||
"translator": {
|
||||
"title": "مترجم",
|
||||
@@ -2606,6 +2683,8 @@
|
||||
"clientCompatibility": "توافق العميل",
|
||||
"protocolsToc": "البروتوكولات",
|
||||
"apiReference": "مرجع واجهة برمجة التطبيقات",
|
||||
"managementApiReference": "Management API Reference",
|
||||
"managementApiDescription": "Automation endpoints for proxy registry, scope assignments, and legacy proxy migration.",
|
||||
"method": "الطريقة",
|
||||
"path": "المسار",
|
||||
"notes": "ملاحظات",
|
||||
@@ -2697,10 +2776,19 @@
|
||||
"endpointResponsesNote": "نقطة نهاية واجهة برمجة التطبيقات للاستجابات (Codex، o-series).",
|
||||
"endpointModelsNote": "كتالوج نموذجي لجميع مقدمي الخدمات المتصلين.",
|
||||
"endpointAudioNote": "النسخ الصوتي (Deepgram، AssemblyAI).",
|
||||
"endpointSpeechNote": "توليد تحويل النص إلى كلام (ElevenLabs، OpenAI TTS).",
|
||||
"endpointEmbeddingsNote": "إنشاء تضمين النص (OpenAI، Cohere، Voyage).",
|
||||
"endpointImagesNote": "توليد الصور (NanoBanana).",
|
||||
"endpointRewriteChatNote": "إعادة كتابة المساعد للعملاء بدون /v1.",
|
||||
"endpointRewriteResponsesNote": "أعد كتابة المساعد للاستجابات بدون /v1.",
|
||||
"endpointRewriteModelsNote": "أعد كتابة المساعد لاكتشاف النموذج بدون /v1.",
|
||||
"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.",
|
||||
"modelPrefixesDescriptionStart": "استخدم بادئة الموفر قبل اسم الطراز للتوجيه إلى موفر محدد. مثال:",
|
||||
"modelPrefixesDescriptionEnd": "طرق إلى GitHub Copilot.",
|
||||
"provider": "مزود",
|
||||
@@ -2710,18 +2798,7 @@
|
||||
"troubleshootingCodexFamily": "بالنسبة لنماذج عائلة GitHub Codex، احتفظ بالنموذج كـ gh/codex-model; يقوم جهاز التوجيه بتحديد/الاستجابات تلقائيًا.",
|
||||
"troubleshootingTestConnection": "استخدم لوحة المعلومات > الموفرون > اختبار الاتصال قبل الاختبار من بيئات تطوير متكاملة أو عملاء خارجيين.",
|
||||
"troubleshootingCircuitBreaker": "إذا أظهر مقدم الخدمة أن قاطع الدائرة مفتوح، فانتظر حتى فترة التهدئة أو راجع صفحة الصحة للحصول على التفاصيل.",
|
||||
"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.",
|
||||
"endpointSpeechNote": "توليد تحويل النص إلى كلام (ElevenLabs، OpenAI TTS).",
|
||||
"endpointEmbeddingsNote": "إنشاء تضمين النص (OpenAI، Cohere، Voyage)."
|
||||
"troubleshootingOAuth": "بالنسبة لموفري OAuth، قم بإعادة المصادقة إذا انتهت صلاحية الرموز المميزة. تحقق من مؤشر حالة بطاقة المزود."
|
||||
},
|
||||
"legal": {
|
||||
"privacyPolicy": "سياسة الخصوصية",
|
||||
@@ -2783,18 +2860,6 @@
|
||||
"termsSection6Title": "6. المصدر المفتوح",
|
||||
"termsSection6Text": "OmniRoute هو برنامج مفتوح المصدر. ولك الحرية في فحصه وتعديله وتوزيعه بموجب شروط ترخيصه."
|
||||
},
|
||||
"endpoints": {
|
||||
"tabProxy": "Endpoint Proxy",
|
||||
"tabApiEndpoints": "نقاط نهاية API",
|
||||
"apiEndpointsTitle": "نقاط نهاية API",
|
||||
"apiEndpointsDescription": "نقاط نهاية API الخلفية التي يمكن أن تستهلكها التطبيقات والخدمات الأخرى.",
|
||||
"comingSoon": "قريباً",
|
||||
"plannedFeatures": "الميزات المخطط لها",
|
||||
"featureRestApi": "كتالوج REST API مع التوثيق التفاعلي",
|
||||
"featureWebhooks": "تكوين Webhooks واشتراكات الأحداث",
|
||||
"featureSwagger": "إنشاء مواصفات OpenAPI / Swagger تلقائياً",
|
||||
"featureAuth": "إدارة مفاتيح API ونطاقات OAuth لكل نقطة نهاية"
|
||||
},
|
||||
"agents": {
|
||||
"title": "CLI Agents",
|
||||
"description": "Discover installed CLI agents on your system. Add custom agents for auto-detection.",
|
||||
@@ -2849,57 +2914,6 @@
|
||||
"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"
|
||||
},
|
||||
"templateNames": {
|
||||
"simple-chat": "دردشة بسيطة",
|
||||
"streaming": "الجري",
|
||||
@@ -2949,67 +2963,67 @@
|
||||
"refresh": "Refresh",
|
||||
"clearAll": "Clear All",
|
||||
"memoryEntries": "Memory Entries",
|
||||
"memoryEntriesSub": "In-memory LRU",
|
||||
"dbEntries": "DB Entries",
|
||||
"dbEntriesSub": "Persisted (SQLite)",
|
||||
"cacheHits": "Cache Hits",
|
||||
"cacheHitsSub": "of {total} total",
|
||||
"tokensSaved": "Tokens Saved",
|
||||
"tokensSavedSub": "Estimated from hits",
|
||||
"hitRate": "Hit Rate",
|
||||
"performance": "Cache Performance",
|
||||
"behavior": "Cache Behavior",
|
||||
"idempotency": "Idempotency Layer",
|
||||
"clearSuccess": "Cache cleared. {count} expired entries removed.",
|
||||
"clearError": "Failed to clear cache.",
|
||||
"unavailable": "Cache unavailable",
|
||||
"unavailableDesc": "Could not fetch cache statistics. Make sure the server is running.",
|
||||
"memoryEntriesSub": "In-memory LRU",
|
||||
"dbEntriesSub": "Persisted (SQLite)",
|
||||
"cacheHitsSub": "of {total} total",
|
||||
"tokensSavedSub": "Estimated from hits",
|
||||
"autoRefresh": "Auto-refreshes every {seconds}s",
|
||||
"hits": "Hits",
|
||||
"misses": "Misses",
|
||||
"total": "Total",
|
||||
"behavior": "Cache Behavior",
|
||||
"behaviorDeterministic": "Only non-streaming requests with temperature=0 are cached.",
|
||||
"behaviorBypass": "Bypass with header {header}.",
|
||||
"behaviorTwoTier": "Two-tier storage: in-memory LRU (fast) + SQLite (persistent across restarts).",
|
||||
"behaviorTtl": "Default TTL: 30 minutes. Configure via {envVar}.",
|
||||
"idempotency": "Idempotency Layer",
|
||||
"activeDedupKeys": "Active Dedup Keys",
|
||||
"dedupWindow": "Dedup Window",
|
||||
"inputTokens": "Input Tokens",
|
||||
"clearSuccess": "Cache cleared. {count} expired entries removed.",
|
||||
"clearError": "Failed to clear cache.",
|
||||
"unavailable": "Cache unavailable",
|
||||
"unavailableDesc": "Could not fetch cache statistics. Make sure the server is running.",
|
||||
"promptCache": "Prompt Cache (Provider-Side)",
|
||||
"cachedRequests": "Cached Requests",
|
||||
"cacheHitRate": "Cache Hit Rate",
|
||||
"cachedTokens": "Cached Tokens",
|
||||
"cacheCreationTokens": "Cache Creation Tokens",
|
||||
"cacheMetrics": "Prompt Cache Metrics",
|
||||
"withCacheControl": "With Cache Control",
|
||||
"cachedTokensRead": "Cached Tokens (Read)",
|
||||
"cacheCreationWrite": "Cache Creation (Write)",
|
||||
"cacheReuseRatio": "Cache Reuse Ratio",
|
||||
"cacheReuseRatioDesc": "Cached tokens / Total input tokens",
|
||||
"estCostSaved": "Est. Cost Saved",
|
||||
"requestsShort": "reqs",
|
||||
"inputShort": "In",
|
||||
"resetting": "Resetting...",
|
||||
"cachedTokensCol": "Cached",
|
||||
"search": "Search",
|
||||
"cacheReuseRatioDesc": "Cached tokens / Total input tokens",
|
||||
"resetMetrics": "Reset Metrics",
|
||||
"loading": "Loading...",
|
||||
"cachedRequests": "Cached Requests",
|
||||
"model": "Model",
|
||||
"cached": "Cached",
|
||||
"actions": "Actions",
|
||||
"trend24h": "Cache Trend (24h)",
|
||||
"cachedShort": "Cached",
|
||||
"cacheCreation": "Creation",
|
||||
"byProvider": "Breakdown by Provider",
|
||||
"created": "Created",
|
||||
"cacheCreationTokens": "Cache Creation Tokens",
|
||||
"withCacheControl": "With Cache Control",
|
||||
"searchEntries": "Search entries...",
|
||||
"cacheReuseRatio": "Cache Reuse Ratio",
|
||||
"estCostSaved": "Est. Cost Saved",
|
||||
"cachedTokens": "Cached Tokens",
|
||||
"requests": "Requests",
|
||||
"signature": "Signature",
|
||||
"cacheCreationWrite": "Cache Creation (Write)",
|
||||
"expires": "Expires",
|
||||
"writeShort": "Write",
|
||||
"cacheHitRate": "Cache Hit Rate",
|
||||
"cacheMetrics": "Prompt Cache Metrics",
|
||||
"overview": "Overview",
|
||||
"promptCache": "Prompt Cache (Provider-Side)",
|
||||
"resetting": "Resetting...",
|
||||
"resetMetrics": "Reset Metrics",
|
||||
"byProvider": "Breakdown by Provider",
|
||||
"provider": "Provider",
|
||||
"cachedTokensRead": "Cached Tokens (Read)",
|
||||
"requests": "Requests",
|
||||
"inputTokens": "Input Tokens",
|
||||
"cachedTokensCol": "Cached",
|
||||
"cacheCreation": "Creation",
|
||||
"trend24h": "Cache Trend (24h)",
|
||||
"cached": "Cached",
|
||||
"overview": "Overview",
|
||||
"entries": "Entries",
|
||||
"noEntries": "No cache entries found"
|
||||
"searchEntries": "Search entries...",
|
||||
"search": "Search",
|
||||
"loading": "Loading...",
|
||||
"noEntries": "No cache entries found",
|
||||
"signature": "Signature",
|
||||
"model": "Model",
|
||||
"created": "Created",
|
||||
"expires": "Expires",
|
||||
"actions": "Actions"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -143,6 +143,7 @@
|
||||
"dashboard": "Табло",
|
||||
"providers": "Доставчици",
|
||||
"combos": "Комбота",
|
||||
"autoCombo": "Auto Combo",
|
||||
"usage": "Използване",
|
||||
"analytics": "Анализ",
|
||||
"costs": "Разходи",
|
||||
@@ -152,8 +153,12 @@
|
||||
"media": "Медия",
|
||||
"settings": "Настройки",
|
||||
"translator": "Преводач",
|
||||
"playground": "Площадка",
|
||||
"searchTools": "Search Tools",
|
||||
"agents": "Агенти",
|
||||
"docs": "Документи",
|
||||
"issues": "Проблеми",
|
||||
"endpoints": "Крайни точки",
|
||||
"apiManager": "API мениджър",
|
||||
"logs": "трупи",
|
||||
"auditLog": "Дневник за одит",
|
||||
@@ -165,6 +170,11 @@
|
||||
"debug": "Отстраняване на грешки",
|
||||
"system": "система",
|
||||
"help": "Помощ",
|
||||
"primarySection": "Main",
|
||||
"cliSection": "CLI",
|
||||
"debugSection": "Debug",
|
||||
"systemSection": "System",
|
||||
"helpSection": "Help",
|
||||
"serverDisconnected": "Връзката със сървъра е прекъсната",
|
||||
"serverDisconnectedMsg": "Прокси сървърът е спрян или се рестартира.",
|
||||
"expandSidebar": "Разширете страничната лента",
|
||||
@@ -180,19 +190,9 @@
|
||||
"themeViolet": "Виолетово",
|
||||
"themeOrange": "Оранжево",
|
||||
"themeCyan": "Циан",
|
||||
"endpoints": "Крайни точки",
|
||||
"playground": "Площадка",
|
||||
"agents": "Агенти",
|
||||
"cliToolsShort": "Инструменти",
|
||||
"autoCombo": "Auto Combo",
|
||||
"searchTools": "Search Tools",
|
||||
"cache": "Cache",
|
||||
"cacheShort": "Cache",
|
||||
"cliSection": "CLI",
|
||||
"debugSection": "Debug",
|
||||
"helpSection": "Help",
|
||||
"primarySection": "Main",
|
||||
"systemSection": "System"
|
||||
"cacheShort": "Cache"
|
||||
},
|
||||
"themesPage": {
|
||||
"title": "Теми",
|
||||
@@ -220,6 +220,10 @@
|
||||
"homeDescription": "Добре дошли в OmniRoute",
|
||||
"endpoint": "Endpoints",
|
||||
"endpointDescription": "Manage proxy endpoints, MCP, A2A, and API endpoints",
|
||||
"mcp": "MCP",
|
||||
"mcpDescription": "Model Context Protocol server management and tools",
|
||||
"a2a": "A2A",
|
||||
"a2aDescription": "Agent-to-Agent protocol tasks and observability",
|
||||
"settings": "Настройки",
|
||||
"settingsDescription": "Управлявайте вашите предпочитания",
|
||||
"openaiCompatible": "Съвместим с OpenAI",
|
||||
@@ -227,11 +231,7 @@
|
||||
"media": "Медия",
|
||||
"mediaDescription": "Генериране на изображения, видеоклипове и музика",
|
||||
"themes": "Теми",
|
||||
"themesDescription": "Изберете цветова тема за целия панел на таблото",
|
||||
"mcp": "MCP",
|
||||
"mcpDescription": "Model Context Protocol server management and tools",
|
||||
"a2a": "A2A",
|
||||
"a2aDescription": "Agent-to-Agent protocol tasks and observability"
|
||||
"themesDescription": "Изберете цветова тема за целия панел на таблото"
|
||||
},
|
||||
"home": {
|
||||
"quickStart": "Бърз старт",
|
||||
@@ -332,6 +332,26 @@
|
||||
"keyCreatedNote": "Копирайте и запазете този ключ сега — няма да се показва отново.",
|
||||
"done": "Готово",
|
||||
"savePermissions": "Запазване на разрешенията",
|
||||
"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",
|
||||
"allowAll": "Разреши всички",
|
||||
"restrict": "Ограничете",
|
||||
"allowAllInfo": "Този ключ има достъп до всички налични модели.",
|
||||
@@ -356,6 +376,7 @@
|
||||
"failedUpdatePermissionsRetry": "Неуспешно актуализиране на разрешенията. Моля, опитайте отново.",
|
||||
"unknownProvider": "неизвестен",
|
||||
"copyMaskedKey": "Копиране на маскиран ключ",
|
||||
"keyOnlyAvailableAtCreation": "Full key available only at creation time — copy it when you first create the key",
|
||||
"modelsCount": "{count, plural, one {# model} other {# models}}",
|
||||
"lastUsedOn": "Последно: {date}",
|
||||
"editPermissions": "Разрешения за редактиране",
|
||||
@@ -365,28 +386,7 @@
|
||||
"permissionsTitle": "Разрешения: {name}",
|
||||
"allowAllDesc": "Този ключ има достъп до всички налични модели.",
|
||||
"restrictDesc": "Този ключ има достъп до {selectedCount} от {totalModels} модели.",
|
||||
"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"
|
||||
"selectedCount": "{count} избран"
|
||||
},
|
||||
"auditLog": {
|
||||
"title": "Дневник за одит",
|
||||
@@ -425,6 +425,57 @@
|
||||
"videoDescription": "Create videos with AnimateDiff, Stable Video Diffusion via ComfyUI or SD WebUI.",
|
||||
"musicDescription": "Compose music using Stable Audio Open or MusicGen via ComfyUI."
|
||||
},
|
||||
"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",
|
||||
"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",
|
||||
"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"
|
||||
},
|
||||
"cliTools": {
|
||||
"title": "CLI инструменти",
|
||||
"noActiveProviders": "Няма активни доставчици",
|
||||
@@ -497,6 +548,11 @@
|
||||
"antigravityStep2Prefix": "2. Добавете",
|
||||
"antigravityStep2Suffix": "към вашия хост файл като 127.0.0.1.",
|
||||
"antigravityStep3": "3. Отворете Antigravity и заявките ще бъдат проксиирани.",
|
||||
"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.",
|
||||
"sudoPasswordRequiredTitle": "Изисква се парола за Sudo",
|
||||
"sudoPasswordHint": "Необходима е администраторска парола, за да промените настройките на хост файла и системния прокси.",
|
||||
"enterSudoPassword": "Въведете sudo парола",
|
||||
@@ -569,6 +625,22 @@
|
||||
"openClawManualConfiguration": "Open Claw - Ръчна конфигурация",
|
||||
"clineManualConfiguration": "Ръчна конфигурация на Cline",
|
||||
"kiloManualConfiguration": "Ръчна конфигурация на Kilo Code",
|
||||
"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 ключове и правила за маршрутизиране."
|
||||
},
|
||||
"toolDescriptions": {
|
||||
"antigravity": "Google Antigravity IDE с MITM",
|
||||
"claude": "Антропен код на Клод CLI",
|
||||
@@ -706,27 +778,6 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"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": {
|
||||
@@ -988,7 +1039,12 @@
|
||||
"settingsApi": "Settings API",
|
||||
"categoryCore": "Основни API",
|
||||
"categoryMedia": "Медии и мултимодални",
|
||||
"categorySearch": "Search & Discovery",
|
||||
"categoryUtility": "Помощни средства и управление",
|
||||
"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.",
|
||||
"enableCloudTitle": "Активиране на облачен прокси",
|
||||
"whatYouGet": "Какво ще получите",
|
||||
"cloudBenefitAccess": "Достъп до вашия API от всяка точка на света",
|
||||
@@ -1021,6 +1077,25 @@
|
||||
"cloudWorkerUnreachable": "Не можа да се достигне до облачния работник. Уверете се, че облачната услуга работи (npm run dev в /cloud).",
|
||||
"connectionFailed": "Връзката е неуспешна",
|
||||
"syncFailed": "Неуспешно синхронизиране на данните в облака",
|
||||
"cloudflaredTitle": "Cloudflare Quick Tunnel",
|
||||
"cloudflaredDescription": "Create a Cloudflare Quick Tunnel for this endpoint to make it accessible from the internet without configuring DNS.",
|
||||
"cloudflaredUrlNotice": "Creates a temporary Cloudflare Quick Tunnel. URLs change after restart.",
|
||||
"cloudflaredEnable": "Enable Tunnel",
|
||||
"cloudflaredInstallAndEnable": "Install & Enable",
|
||||
"cloudflaredDisable": "Stop Tunnel",
|
||||
"cloudflaredRunning": "Running",
|
||||
"cloudflaredStarting": "Starting",
|
||||
"cloudflaredStoppedState": "Stopped",
|
||||
"cloudflaredNotInstalled": "Not installed",
|
||||
"cloudflaredUnsupported": "Unsupported",
|
||||
"cloudflaredError": "Error",
|
||||
"cloudflaredStarted": "Cloudflare tunnel started",
|
||||
"cloudflaredStopped": "Cloudflare tunnel stopped",
|
||||
"cloudflaredRequestFailed": "Failed to update Cloudflare tunnel",
|
||||
"cloudflaredTemporaryNote": "Quick Tunnel URLs are temporary and will change after restart.",
|
||||
"cloudflaredUnsupportedNote": "This platform is not supported for managed installation.",
|
||||
"cloudflaredIdleNote": "Create a temporary Cloudflare Quick Tunnel for this endpoint.",
|
||||
"cloudflaredLastError": "Last error: {error}",
|
||||
"providerModelsTitle": "{provider} — Модели",
|
||||
"noModelsForProvider": "Няма налични модели за този доставчик.",
|
||||
"chat": "Чат",
|
||||
@@ -1055,31 +1130,19 @@
|
||||
"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",
|
||||
"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.",
|
||||
"cloudflaredDescription": "Create a Cloudflare Quick Tunnel for this endpoint to make it accessible from the internet without configuring DNS.",
|
||||
"cloudflaredDisable": "Stop Tunnel",
|
||||
"cloudflaredEnable": "Enable Tunnel",
|
||||
"cloudflaredError": "Error",
|
||||
"cloudflaredIdleNote": "Create a temporary Cloudflare Quick Tunnel for this endpoint.",
|
||||
"cloudflaredInstallAndEnable": "Install & Enable",
|
||||
"cloudflaredLastError": "Last error: {error}",
|
||||
"cloudflaredNotInstalled": "Not installed",
|
||||
"cloudflaredRequestFailed": "Failed to update Cloudflare tunnel",
|
||||
"cloudflaredRunning": "Running",
|
||||
"cloudflaredStarted": "Cloudflare tunnel started",
|
||||
"cloudflaredStarting": "Starting",
|
||||
"cloudflaredStopped": "Cloudflare tunnel stopped",
|
||||
"cloudflaredStoppedState": "Stopped",
|
||||
"cloudflaredTemporaryNote": "Quick Tunnel URLs are temporary and will change after restart.",
|
||||
"cloudflaredTitle": "Cloudflare Quick Tunnel",
|
||||
"cloudflaredUnsupported": "Unsupported",
|
||||
"cloudflaredUnsupportedNote": "This platform is not supported for managed installation.",
|
||||
"cloudflaredUrlNotice": "Creates a temporary Cloudflare Quick Tunnel. URLs change after restart."
|
||||
"completionsLegacyDesc": "Legacy OpenAI text completions — accepts both prompt string and messages array format"
|
||||
},
|
||||
"endpoints": {
|
||||
"tabProxy": "Endpoint Proxy",
|
||||
"tabApiEndpoints": "API Endpoints",
|
||||
"apiEndpointsTitle": "API Endpoints",
|
||||
"apiEndpointsDescription": "Backend API endpoints that can be consumed by other applications and services. This section will list all available REST APIs with documentation and testing capabilities.",
|
||||
"comingSoon": "Coming Soon",
|
||||
"plannedFeatures": "Planned Features",
|
||||
"featureRestApi": "REST API endpoint catalog with interactive documentation",
|
||||
"featureWebhooks": "Webhook configuration and event subscriptions",
|
||||
"featureSwagger": "OpenAPI / Swagger spec auto-generation",
|
||||
"featureAuth": "API key and OAuth scope management per endpoint"
|
||||
},
|
||||
"mcpDashboard": {
|
||||
"loading": "Зареждане на таблото за управление на MCP...",
|
||||
@@ -1251,6 +1314,12 @@
|
||||
"issuesLabel": "Открити проблеми",
|
||||
"operational": "Оперативен",
|
||||
"providers": "Доставчици",
|
||||
"configuredProvidersLabel": "Конфигуриран в таблото за управление",
|
||||
"configuredProvidersHint": "Доставчици с идентификационни данни, записани в /dashboard/providers, независимо от състоянието на изпълнение.",
|
||||
"activeProviders": "{count} active",
|
||||
"activeProvidersHint": "Конфигурираните доставчици в момента са активирани за заявки за маршрутизиране.",
|
||||
"monitoredProviders": "{count} monitored",
|
||||
"monitoredProvidersHint": "Доставчиците в момента се проследяват от монитори за изправност на прекъсвачи.",
|
||||
"healthyCount": "{count} здрав",
|
||||
"nodeVersion": "Възел {version}",
|
||||
"failures": "{count} грешка",
|
||||
@@ -1269,13 +1338,7 @@
|
||||
"resetAllTitle": "Нулирайте всички прекъсвачи в изправно състояние",
|
||||
"resetting": "Нулиране...",
|
||||
"resetAll": "Нулиране на всички",
|
||||
"until": "До {time}",
|
||||
"activeProviders": "{count} active",
|
||||
"monitoredProviders": "{count} monitored",
|
||||
"configuredProvidersLabel": "Конфигуриран в таблото за управление",
|
||||
"configuredProvidersHint": "Доставчици с идентификационни данни, записани в /dashboard/providers, независимо от състоянието на изпълнение.",
|
||||
"activeProvidersHint": "Конфигурираните доставчици в момента са активирани за заявки за маршрутизиране.",
|
||||
"monitoredProvidersHint": "Доставчиците в момента се проследяват от монитори за изправност на прекъсвачи."
|
||||
"until": "До {time}"
|
||||
},
|
||||
"limits": {
|
||||
"title": "Ограничения и квоти",
|
||||
@@ -1437,6 +1500,7 @@
|
||||
"clearing": "Изчистване...",
|
||||
"until": "До {time}",
|
||||
"providerTestFailed": "Тестът на доставчика е неуспешен",
|
||||
"providerTestTimeout": "Provider test timed out — too many connections to test at once",
|
||||
"modeTest": "{mode} Тествайте",
|
||||
"passedCount": "{count} премина",
|
||||
"failedCount": "{count} неуспешно",
|
||||
@@ -1478,6 +1542,14 @@
|
||||
"chatCompletions": "Чат завършвания",
|
||||
"importingModels": "Импортиране...",
|
||||
"importFromModels": "Импортиране от /models",
|
||||
"allModelsAlreadyImported": "Всички модели вече са импортирани",
|
||||
"noNewModelsToImport": "Няма нови модели за импортиране — всички модели вече са в регистъра или списъка с персонализирани модели",
|
||||
"skippingExistingModels": "Пропускане на {count} съществуващи модела",
|
||||
"autoSync": "Автоматично синхронизиране",
|
||||
"autoSyncTooltip": "Автоматично опресняване на списъка с модели на всеки 24 часа (може да се конфигурира чрез MODEL_SYNC_INTERVAL_HOURS)",
|
||||
"autoSyncEnabled": "Автоматичното синхронизиране е активирано — моделите ще се опресняват периодично",
|
||||
"autoSyncDisabled": "Автоматичното синхронизиране е деактивирано",
|
||||
"autoSyncToggleFailed": "Неуспешно превключване на автоматичното синхронизиране",
|
||||
"clearAllModels": "Изчистване на всички модели",
|
||||
"clearAllModelsConfirm": "Сигурни ли сте, че искате да премахнете всички модели за този доставчик? Това не може да бъде отменено.",
|
||||
"clearAllModelsSuccess": "Всички модели изчистени",
|
||||
@@ -1531,6 +1603,8 @@
|
||||
"addFirstConnectionHint": "Добавете първата си връзка, за да започнете",
|
||||
"addConnection": "Добавяне на връзка",
|
||||
"availableModels": "Налични модели",
|
||||
"builtInModels": "Built-in models",
|
||||
"builtInModelsHint": "Registry models for this provider. Use the pencil to set compatibility options.",
|
||||
"pageAutoRefresh": "Страницата ще се обнови автоматично...",
|
||||
"statusDisabled": "деактивиран",
|
||||
"statusConnected": "свързан",
|
||||
@@ -1571,6 +1645,26 @@
|
||||
"openRouterModelPlaceholder": "антропичен/claude-3-opus",
|
||||
"customModels": "Персонализирани модели",
|
||||
"customModelsHint": "Добавете идентификатори на модели, които не са в списъка по подразбиране. Те ще бъдат достъпни за маршрутизиране.",
|
||||
"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",
|
||||
"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": "ID на модела",
|
||||
"customModelPlaceholder": "напр. gpt-4.5-турбо",
|
||||
"loading": "Зареждане...",
|
||||
@@ -1614,52 +1708,21 @@
|
||||
"compatibleBaseUrlHint": "Root URL of your {type}-compatible API. Use Advanced Settings for custom endpoint paths.",
|
||||
"apiKeyForCheck": "API ключ (за проверка)",
|
||||
"compatibleProdPlaceholder": "{type} Съвместим (Prod)",
|
||||
"providerTestTimeout": "Provider test timed out — too many connections to test at once",
|
||||
"tokenRefreshed": "Token refreshed successfully",
|
||||
"tokenRefreshFailed": "Token refresh failed",
|
||||
"applyCodexAuthLocal": "Apply auth",
|
||||
"exportCodexAuthFile": "Export auth",
|
||||
"codexAuthAppliedLocal": "Codex auth.json applied locally",
|
||||
"codexAuthApplyFailed": "Failed to apply Codex auth.json locally",
|
||||
"codexAuthExported": "Codex auth.json exported",
|
||||
"codexAuthExportFailed": "Failed to export Codex auth.json",
|
||||
"advancedSettings": "Advanced Settings",
|
||||
"chatPathLabel": "Chat Endpoint Path",
|
||||
"chatPathPlaceholder": "/chat/completions",
|
||||
"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)",
|
||||
"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",
|
||||
"autoSync": "Автоматично синхронизиране",
|
||||
"autoSyncTooltip": "Автоматично опресняване на списъка с модели на всеки 24 часа (може да се конфигурира чрез MODEL_SYNC_INTERVAL_HOURS)",
|
||||
"autoSyncEnabled": "Автоматичното синхронизиране е активирано — моделите ще се опресняват периодично",
|
||||
"autoSyncDisabled": "Автоматичното синхронизиране е деактивирано",
|
||||
"autoSyncToggleFailed": "Неуспешно превключване на автоматичното синхронизиране",
|
||||
"allModelsAlreadyImported": "Всички модели вече са импортирани",
|
||||
"noNewModelsToImport": "Няма нови модели за импортиране — всички модели вече са в регистъра или списъка с персонализирани модели",
|
||||
"skippingExistingModels": "Пропускане на {count} съществуващи модела",
|
||||
"applyCodexAuthLocal": "Apply auth",
|
||||
"codexAuthAppliedLocal": "Codex auth.json applied locally",
|
||||
"codexAuthApplyFailed": "Failed to apply Codex auth.json locally",
|
||||
"codexAuthExportFailed": "Failed to export Codex auth.json",
|
||||
"codexAuthExported": "Codex auth.json exported",
|
||||
"exportCodexAuthFile": "Export auth"
|
||||
"modelsPathHint": "Custom models path for validation (e.g. /v4/models)"
|
||||
},
|
||||
"settings": {
|
||||
"title": "Настройки",
|
||||
@@ -1683,6 +1746,8 @@
|
||||
"darkMode": "Тъмен режим",
|
||||
"lightMode": "Светлинен режим",
|
||||
"systemTheme": "Системна тема",
|
||||
"debugToggle": "Enable Debug Mode",
|
||||
"sidebarVisibilityToggle": "Show Sidebar Items",
|
||||
"enableCache": "Активиране на кеша",
|
||||
"cacheTTL": "Кеш TTL",
|
||||
"maxCacheSize": "Макс. размер на кеша",
|
||||
@@ -1691,6 +1756,17 @@
|
||||
"cacheMisses": "Кеш пропуски",
|
||||
"hitRate": "Процент на попадения",
|
||||
"cacheEntries": "Записи в кеша",
|
||||
"cacheSettings": "Cache Settings",
|
||||
"semanticCache": "Semantic Cache",
|
||||
"maxEntries": "Max Entries",
|
||||
"ttlMinutes": "TTL (minutes)",
|
||||
"promptCache": "Кеш на подканите",
|
||||
"strategy": "Strategy",
|
||||
"preserveClientCache": "Preserve Client Cache",
|
||||
"enabled": "Enabled",
|
||||
"loading": "Loading...",
|
||||
"saving": "Запазва се...",
|
||||
"save": "Save",
|
||||
"circuitBreaker": "Прекъсвач",
|
||||
"retryPolicy": "Правила за повторен опит",
|
||||
"maxRetries": "Макс. повторни опити",
|
||||
@@ -1698,6 +1774,10 @@
|
||||
"timeoutMs": "Изчакване (ms)",
|
||||
"enableSystemPrompt": "Активиране на системна подкана",
|
||||
"systemPromptText": "Текст на системната подкана",
|
||||
"autoDisableBannedAccounts": "Auto-Disable Banned Accounts",
|
||||
"autoDisableDescription": "Permanently mark provider connections as deactivated if they return specific terminal ban signals (e.g. HTTP 403 'verify your account'). This removes them from the combo rotation.",
|
||||
"autoDisableThreshold": "Ban Threshold",
|
||||
"autoDisableThresholdDesc": "Consecutive ban signals required before permanent deactivation.",
|
||||
"enableThinking": "Активиране на мисленето",
|
||||
"maxThinkingTokens": "Макс жетони за мислене",
|
||||
"enableProxy": "Активиране на прокси",
|
||||
@@ -1734,6 +1814,9 @@
|
||||
"themeLight": "светлина",
|
||||
"themeDark": "Тъмно",
|
||||
"themeSystem": "система",
|
||||
"sidebarVisibility": "Hide sidebar items",
|
||||
"sidebarVisibilityDesc": "Hide any sidebar navigation entry to reduce visual clutter.",
|
||||
"sidebarVisibilityHint": "Any sidebar section is hidden automatically when a...",
|
||||
"hideHealthLogs": "Скриване на журналите за проверка на здравето",
|
||||
"hideHealthLogsDesc": "Когато е ВКЛЮЧЕНО, потиска съобщенията [HealthCheck] в сървърната конзола",
|
||||
"themeAccent": "Цвят на темата",
|
||||
@@ -1746,7 +1829,20 @@
|
||||
"themeViolet": "Виолетов",
|
||||
"themeOrange": "Оранжев",
|
||||
"themeCyan": "Циан",
|
||||
"promptCache": "Кеш на подканите",
|
||||
"whitelabeling": "Branding",
|
||||
"whitelabelingDesc": "Customize the application name and logo",
|
||||
"appName": "Application Name",
|
||||
"appNameDesc": "Display name shown in sidebar and browser tab",
|
||||
"customLogo": "Custom Logo URL",
|
||||
"customLogoDesc": "URL to your custom logo image",
|
||||
"uploadLogo": "Upload Logo",
|
||||
"resetLogo": "Reset to Default",
|
||||
"logoPreview": "Preview",
|
||||
"customFavicon": "Browser Favicon",
|
||||
"customFaviconDesc": "URL to your custom favicon (shown in browser tab)",
|
||||
"uploadFavicon": "Upload Favicon",
|
||||
"resetFavicon": "Reset Favicon",
|
||||
"faviconPreview": "Favicon Preview",
|
||||
"flushCache": "Прочистване на кеша",
|
||||
"flushing": "Зачервяване...",
|
||||
"size": "Размер",
|
||||
@@ -1804,7 +1900,15 @@
|
||||
"providersBlocked": "{count} доставчик(и) е блокиран от /models",
|
||||
"blockProviderTitle": "Блокиране на {provider}",
|
||||
"unblockProviderTitle": "Отблокиране на {provider}",
|
||||
"cliFingerprint": "CLI Fingerprint Matching",
|
||||
"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}",
|
||||
"routingStrategy": "Стратегия за маршрутизиране",
|
||||
"routingAdvancedGuideTitle": "Разширено ръководство за маршрутизиране",
|
||||
"routingAdvancedGuideHint1": "Използвайте Fill First за предвидим приоритет, Round Robin за справедливост и P2C за устойчивост на забавяне.",
|
||||
"routingAdvancedGuideHint2": "Ако доставчиците се различават по отношение на качество/цена, започнете с Cost Opt за фонова работа и Least Used за балансирано износване.",
|
||||
"fillFirst": "Първо попълнете",
|
||||
"fillFirstDesc": "Използвайте акаунти в приоритетен ред",
|
||||
"roundRobin": "Round Robin",
|
||||
@@ -1899,6 +2003,9 @@
|
||||
"fillModelAndProviders": "Моля, попълнете името на модела и доставчиците",
|
||||
"addAtLeastOneProvider": "Добавете поне един доставчик",
|
||||
"comboDefaultsTitle": "Комбо по подразбиране",
|
||||
"comboDefaultsGuideTitle": "Как да настроите настройките по подразбиране на комбинацията",
|
||||
"comboDefaultsGuideHint1": "Поддържайте ниски повторни опити в потоци с ниска латентност; увеличете времето за изчакване само за задачи с дълго генериране.",
|
||||
"comboDefaultsGuideHint2": "Използвайте замени на доставчика, когато един доставчик се нуждае от различно поведение при изчакване/повторен опит от глобалните настройки по подразбиране.",
|
||||
"globalComboConfig": "Глобална комбинирана конфигурация",
|
||||
"defaultStrategy": "Стратегия по подразбиране",
|
||||
"defaultStrategyDesc": "Прилага се към нови комбинации без изрична стратегия",
|
||||
@@ -2026,7 +2133,6 @@
|
||||
"unsaved": "незаписан",
|
||||
"resetDefaults": "Нулиране на настройките по подразбиране",
|
||||
"saveProvider": "Запазване на доставчика",
|
||||
"saving": "Запазва се...",
|
||||
"model": "Модел",
|
||||
"models": "модели",
|
||||
"moreProviders": "{count} още доставчици",
|
||||
@@ -2057,36 +2163,7 @@
|
||||
"customPricingNote": "Можете да замените цените по подразбиране за конкретни модели. Персонализираните замени имат приоритет пред автоматично разпознатото ценообразуване.",
|
||||
"editPricing": "Редактиране на цените",
|
||||
"viewFullDetails": "Вижте пълните подробности",
|
||||
"themeCoral": "Корал",
|
||||
"cliFingerprint": "CLI Fingerprint Matching",
|
||||
"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}",
|
||||
"routingAdvancedGuideTitle": "Разширено ръководство за маршрутизиране",
|
||||
"routingAdvancedGuideHint1": "Използвайте Fill First за предвидим приоритет, Round Robin за справедливост и P2C за устойчивост на забавяне.",
|
||||
"routingAdvancedGuideHint2": "Ако доставчиците се различават по отношение на качество/цена, започнете с Cost Opt за фонова работа и Least Used за балансирано износване.",
|
||||
"comboDefaultsGuideTitle": "Как да настроите настройките по подразбиране на комбинацията",
|
||||
"comboDefaultsGuideHint1": "Поддържайте ниски повторни опити в потоци с ниска латентност; увеличете времето за изчакване само за задачи с дълго генериране.",
|
||||
"comboDefaultsGuideHint2": "Използвайте замени на доставчика, когато един доставчик се нуждае от различно поведение при изчакване/повторен опит от глобалните настройки по подразбиране.",
|
||||
"sidebarVisibility": "Hide sidebar items",
|
||||
"sidebarVisibilityDesc": "Hide any sidebar navigation entry to reduce visual clutter.",
|
||||
"sidebarVisibilityHint": "Any sidebar section is hidden automatically when a...",
|
||||
"semanticCache": "Semantic Cache",
|
||||
"autoDisableDescription": "Permanently mark provider connections as deactivated if they return specific terminal ban signals (e.g. HTTP 403 'verify your account'). This removes them from the combo rotation.",
|
||||
"preserveClientCache": "Preserve Client Cache",
|
||||
"cacheSettings": "Cache Settings",
|
||||
"autoDisableThreshold": "Ban Threshold",
|
||||
"autoDisableBannedAccounts": "Auto-Disable Banned Accounts",
|
||||
"ttlMinutes": "TTL (minutes)",
|
||||
"maxEntries": "Max Entries",
|
||||
"loading": "Loading...",
|
||||
"save": "Save",
|
||||
"autoDisableThresholdDesc": "Consecutive ban signals required before permanent deactivation.",
|
||||
"debugToggle": "Enable Debug Mode",
|
||||
"sidebarVisibilityToggle": "Show Sidebar Items",
|
||||
"enabled": "Enabled",
|
||||
"strategy": "Strategy"
|
||||
"themeCoral": "Корал"
|
||||
},
|
||||
"translator": {
|
||||
"title": "Преводач",
|
||||
@@ -2606,6 +2683,8 @@
|
||||
"clientCompatibility": "Съвместимост на клиента",
|
||||
"protocolsToc": "Протоколи",
|
||||
"apiReference": "Справка за API",
|
||||
"managementApiReference": "Management API Reference",
|
||||
"managementApiDescription": "Automation endpoints for proxy registry, scope assignments, and legacy proxy migration.",
|
||||
"method": "Метод",
|
||||
"path": "Пътека",
|
||||
"notes": "Бележки",
|
||||
@@ -2697,10 +2776,19 @@
|
||||
"endpointResponsesNote": "Крайна точка на API за отговори (Кодекс, o-серия).",
|
||||
"endpointModelsNote": "Модел каталог за всички свързани доставчици.",
|
||||
"endpointAudioNote": "Аудио транскрипция (Deepgram, AssemblyAI).",
|
||||
"endpointSpeechNote": "Генериране на текст към реч (ElevenLabs, OpenAI TTS).",
|
||||
"endpointEmbeddingsNote": "Генериране на вграждане на текст (OpenAI, Cohere, Voyage).",
|
||||
"endpointImagesNote": "Генериране на изображения (NanoBanana).",
|
||||
"endpointRewriteChatNote": "Помощник за пренаписване за клиенти без /v1.",
|
||||
"endpointRewriteResponsesNote": "Помощник за пренаписване за отговори без /v1.",
|
||||
"endpointRewriteModelsNote": "Помощник за пренаписване за откриване на модел без /v1.",
|
||||
"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.",
|
||||
"modelPrefixesDescriptionStart": "Използвайте префикса на доставчика преди името на модела, за да насочите към конкретен доставчик. Пример:",
|
||||
"modelPrefixesDescriptionEnd": "маршрути към GitHub Copilot.",
|
||||
"provider": "Доставчик",
|
||||
@@ -2710,18 +2798,7 @@
|
||||
"troubleshootingCodexFamily": "За модели от семейството на GitHub Codex, запазете модела като gh/codex-model; рутерът избира /отговаря автоматично.",
|
||||
"troubleshootingTestConnection": "Използвайте Табло > Доставчици > Тестване на връзката, преди да тествате от IDE или външни клиенти.",
|
||||
"troubleshootingCircuitBreaker": "Ако доставчикът покаже отворен прекъсвач, изчакайте охлаждането или проверете страницата Health за подробности.",
|
||||
"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.",
|
||||
"endpointSpeechNote": "Генериране на текст към реч (ElevenLabs, OpenAI TTS).",
|
||||
"endpointEmbeddingsNote": "Генериране на вграждане на текст (OpenAI, Cohere, Voyage)."
|
||||
"troubleshootingOAuth": "За доставчици на OAuth, повторно удостоверяване, ако токените изтекат. Проверете индикатора за състояние на картата на доставчика."
|
||||
},
|
||||
"legal": {
|
||||
"privacyPolicy": "Политика за поверителност",
|
||||
@@ -2783,18 +2860,6 @@
|
||||
"termsSection6Title": "6. Отворен код",
|
||||
"termsSection6Text": "OmniRoute е софтуер с отворен код. Вие сте свободни да го инспектирате, модифицирате и разпространявате съгласно условията на неговия лиценз."
|
||||
},
|
||||
"endpoints": {
|
||||
"tabProxy": "Endpoint Proxy",
|
||||
"tabApiEndpoints": "API Endpoints",
|
||||
"apiEndpointsTitle": "API Endpoints",
|
||||
"apiEndpointsDescription": "Backend API endpoints that can be consumed by other applications and services. This section will list all available REST APIs with documentation and testing capabilities.",
|
||||
"comingSoon": "Coming Soon",
|
||||
"plannedFeatures": "Planned Features",
|
||||
"featureRestApi": "REST API endpoint catalog with interactive documentation",
|
||||
"featureWebhooks": "Webhook configuration and event subscriptions",
|
||||
"featureSwagger": "OpenAPI / Swagger spec auto-generation",
|
||||
"featureAuth": "API key and OAuth scope management per endpoint"
|
||||
},
|
||||
"agents": {
|
||||
"title": "CLI Agents",
|
||||
"description": "Discover installed CLI agents on your system. Add custom agents for auto-detection.",
|
||||
@@ -2849,57 +2914,6 @@
|
||||
"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"
|
||||
},
|
||||
"templateNames": {
|
||||
"simple-chat": "Обикновен чат",
|
||||
"streaming": "Поточно предаване",
|
||||
@@ -2949,67 +2963,67 @@
|
||||
"refresh": "Refresh",
|
||||
"clearAll": "Clear All",
|
||||
"memoryEntries": "Memory Entries",
|
||||
"memoryEntriesSub": "In-memory LRU",
|
||||
"dbEntries": "DB Entries",
|
||||
"dbEntriesSub": "Persisted (SQLite)",
|
||||
"cacheHits": "Cache Hits",
|
||||
"cacheHitsSub": "of {total} total",
|
||||
"tokensSaved": "Tokens Saved",
|
||||
"tokensSavedSub": "Estimated from hits",
|
||||
"hitRate": "Hit Rate",
|
||||
"performance": "Cache Performance",
|
||||
"behavior": "Cache Behavior",
|
||||
"idempotency": "Idempotency Layer",
|
||||
"clearSuccess": "Cache cleared. {count} expired entries removed.",
|
||||
"clearError": "Failed to clear cache.",
|
||||
"unavailable": "Cache unavailable",
|
||||
"unavailableDesc": "Could not fetch cache statistics. Make sure the server is running.",
|
||||
"memoryEntriesSub": "In-memory LRU",
|
||||
"dbEntriesSub": "Persisted (SQLite)",
|
||||
"cacheHitsSub": "of {total} total",
|
||||
"tokensSavedSub": "Estimated from hits",
|
||||
"autoRefresh": "Auto-refreshes every {seconds}s",
|
||||
"hits": "Hits",
|
||||
"misses": "Misses",
|
||||
"total": "Total",
|
||||
"behavior": "Cache Behavior",
|
||||
"behaviorDeterministic": "Only non-streaming requests with temperature=0 are cached.",
|
||||
"behaviorBypass": "Bypass with header {header}.",
|
||||
"behaviorTwoTier": "Two-tier storage: in-memory LRU (fast) + SQLite (persistent across restarts).",
|
||||
"behaviorTtl": "Default TTL: 30 minutes. Configure via {envVar}.",
|
||||
"idempotency": "Idempotency Layer",
|
||||
"activeDedupKeys": "Active Dedup Keys",
|
||||
"dedupWindow": "Dedup Window",
|
||||
"inputTokens": "Input Tokens",
|
||||
"clearSuccess": "Cache cleared. {count} expired entries removed.",
|
||||
"clearError": "Failed to clear cache.",
|
||||
"unavailable": "Cache unavailable",
|
||||
"unavailableDesc": "Could not fetch cache statistics. Make sure the server is running.",
|
||||
"promptCache": "Prompt Cache (Provider-Side)",
|
||||
"cachedRequests": "Cached Requests",
|
||||
"cacheHitRate": "Cache Hit Rate",
|
||||
"cachedTokens": "Cached Tokens",
|
||||
"cacheCreationTokens": "Cache Creation Tokens",
|
||||
"cacheMetrics": "Prompt Cache Metrics",
|
||||
"withCacheControl": "With Cache Control",
|
||||
"cachedTokensRead": "Cached Tokens (Read)",
|
||||
"cacheCreationWrite": "Cache Creation (Write)",
|
||||
"cacheReuseRatio": "Cache Reuse Ratio",
|
||||
"cacheReuseRatioDesc": "Cached tokens / Total input tokens",
|
||||
"estCostSaved": "Est. Cost Saved",
|
||||
"requestsShort": "reqs",
|
||||
"inputShort": "In",
|
||||
"resetting": "Resetting...",
|
||||
"cachedTokensCol": "Cached",
|
||||
"search": "Search",
|
||||
"cacheReuseRatioDesc": "Cached tokens / Total input tokens",
|
||||
"resetMetrics": "Reset Metrics",
|
||||
"loading": "Loading...",
|
||||
"cachedRequests": "Cached Requests",
|
||||
"model": "Model",
|
||||
"cached": "Cached",
|
||||
"actions": "Actions",
|
||||
"trend24h": "Cache Trend (24h)",
|
||||
"cachedShort": "Cached",
|
||||
"cacheCreation": "Creation",
|
||||
"byProvider": "Breakdown by Provider",
|
||||
"created": "Created",
|
||||
"cacheCreationTokens": "Cache Creation Tokens",
|
||||
"withCacheControl": "With Cache Control",
|
||||
"searchEntries": "Search entries...",
|
||||
"cacheReuseRatio": "Cache Reuse Ratio",
|
||||
"estCostSaved": "Est. Cost Saved",
|
||||
"cachedTokens": "Cached Tokens",
|
||||
"requests": "Requests",
|
||||
"signature": "Signature",
|
||||
"cacheCreationWrite": "Cache Creation (Write)",
|
||||
"expires": "Expires",
|
||||
"writeShort": "Write",
|
||||
"cacheHitRate": "Cache Hit Rate",
|
||||
"cacheMetrics": "Prompt Cache Metrics",
|
||||
"overview": "Overview",
|
||||
"promptCache": "Prompt Cache (Provider-Side)",
|
||||
"resetting": "Resetting...",
|
||||
"resetMetrics": "Reset Metrics",
|
||||
"byProvider": "Breakdown by Provider",
|
||||
"provider": "Provider",
|
||||
"cachedTokensRead": "Cached Tokens (Read)",
|
||||
"requests": "Requests",
|
||||
"inputTokens": "Input Tokens",
|
||||
"cachedTokensCol": "Cached",
|
||||
"cacheCreation": "Creation",
|
||||
"trend24h": "Cache Trend (24h)",
|
||||
"cached": "Cached",
|
||||
"overview": "Overview",
|
||||
"entries": "Entries",
|
||||
"noEntries": "No cache entries found"
|
||||
"searchEntries": "Search entries...",
|
||||
"search": "Search",
|
||||
"loading": "Loading...",
|
||||
"noEntries": "No cache entries found",
|
||||
"signature": "Signature",
|
||||
"model": "Model",
|
||||
"created": "Created",
|
||||
"expires": "Expires",
|
||||
"actions": "Actions"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
"time": "Čas",
|
||||
"details": "Podrobnosti",
|
||||
"created": "Vytvořeno",
|
||||
"lastUsed": "Last Refreshed",
|
||||
"lastUsed": "Naposledy aktualizováno",
|
||||
"loadMore": "Načíst více",
|
||||
"noResults": "Nenalezeny žádné výsledky",
|
||||
"reloadPage": "Znovu načíst stránku",
|
||||
@@ -170,6 +170,11 @@
|
||||
"debug": "Debug",
|
||||
"system": "Systém",
|
||||
"help": "Pomoc",
|
||||
"primarySection": "Main",
|
||||
"cliSection": "CLI",
|
||||
"debugSection": "Debug",
|
||||
"systemSection": "System",
|
||||
"helpSection": "Help",
|
||||
"serverDisconnected": "Server odpojen",
|
||||
"serverDisconnectedMsg": "Proxy server byl zastaven nebo se restartuje.",
|
||||
"expandSidebar": "Rozbalit postranní panel",
|
||||
@@ -187,12 +192,7 @@
|
||||
"themeCyan": "Azurová",
|
||||
"cliToolsShort": "Nástroje",
|
||||
"cache": "Cache",
|
||||
"cacheShort": "Cache",
|
||||
"cliSection": "CLI",
|
||||
"debugSection": "Debug",
|
||||
"helpSection": "Help",
|
||||
"primarySection": "Main",
|
||||
"systemSection": "System"
|
||||
"cacheShort": "Cache"
|
||||
},
|
||||
"themesPage": {
|
||||
"title": "Motivy",
|
||||
@@ -381,7 +381,7 @@
|
||||
"lastUsedOn": "Poslední: {date}",
|
||||
"editPermissions": "Upravit oprávnění",
|
||||
"deleteKey": "Smazat klíč",
|
||||
"model": "{count} model",
|
||||
"model": "{count, plural, one {# model} other {# modelů}}",
|
||||
"models": "{count} modelů",
|
||||
"permissionsTitle": "Oprávnění: {name}",
|
||||
"allowAllDesc": "Tento klíč umožňuje přístup ke všem dostupným modelům.",
|
||||
@@ -472,7 +472,7 @@
|
||||
"cost": "Náklady",
|
||||
"results": "Výsledky",
|
||||
"rerank": "Rerank",
|
||||
"rerankModel": "Rerank Model",
|
||||
"rerankModel": "Model pro přerovnání",
|
||||
"positionDelta": "Změna pozice",
|
||||
"emptyState": "Odešlete vyhledávací dotaz pro zobrazení výsledků"
|
||||
},
|
||||
@@ -724,8 +724,8 @@
|
||||
"title": "Vybrat model"
|
||||
},
|
||||
"5": {
|
||||
"title": "Use Thinking Variant",
|
||||
"desc": "For thinking models, run with --variant high/low/max (example command below)."
|
||||
"title": "Použít variantu Thinking",
|
||||
"desc": "U modelů s reasoning spusťte s --variant high/low/max (viz příkaz níže)."
|
||||
}
|
||||
},
|
||||
"notes": {
|
||||
@@ -757,24 +757,24 @@
|
||||
"windsurf": {
|
||||
"steps": {
|
||||
"1": {
|
||||
"title": "Open AI Settings",
|
||||
"title": "Open AI nastavení",
|
||||
"desc": "Click the AI Settings icon in Windsurf or go to Settings"
|
||||
},
|
||||
"2": {
|
||||
"title": "Add Custom Provider",
|
||||
"title": "Přidat vlastního poskytovatele",
|
||||
"desc": "Select \"Add custom provider\" (OpenAI-compatible)"
|
||||
},
|
||||
"3": {
|
||||
"title": "Base URL",
|
||||
"title": "Základní URL",
|
||||
"desc": "http://127.0.0.1:20128/v1"
|
||||
},
|
||||
"4": {
|
||||
"title": "API Key",
|
||||
"title": "API klíč",
|
||||
"desc": "Select your OmniRoute API key"
|
||||
},
|
||||
"5": {
|
||||
"title": "Select Model",
|
||||
"desc": "Choose a model from the dropdown"
|
||||
"title": "Vybrat model",
|
||||
"desc": "Vyberte model z nabídky"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -886,9 +886,9 @@
|
||||
"example": "Úlohy na pozadí nebo dávkové úlohy, kde se upřednostňují nižší náklady."
|
||||
},
|
||||
"strict-random": {
|
||||
"when": "Use when you want perfectly even spread — each model used once before repeating.",
|
||||
"avoid": "Avoid when models have different quality or latency and order matters.",
|
||||
"example": "Example: Multiple accounts of the same model to distribute usage evenly."
|
||||
"when": "Použijte, když chcete rovnoměrné rozložení — každý model se použije jednou před opakováním.",
|
||||
"avoid": "Nepoužívejte, když mají modely různou kvalitu nebo latenci a záleží na pořadí.",
|
||||
"example": "Příklad: Více účtů stejného modelu pro rovnoměrné rozložení využití."
|
||||
}
|
||||
},
|
||||
"advancedHelp": {
|
||||
@@ -984,11 +984,11 @@
|
||||
"tip3": "Používejte pro dávkové/úlohy na pozadí, kde jsou hlavním klíčovým ukazatelem výkonnosti náklady."
|
||||
},
|
||||
"strict-random": {
|
||||
"title": "Shuffle deck distribution",
|
||||
"description": "Each model is used exactly once per cycle before reshuffling.",
|
||||
"tip1": "Use at least 2 models for meaningful distribution.",
|
||||
"tip2": "Works best with equivalent-performance models.",
|
||||
"tip3": "Ideal for load balancing across multiple API accounts."
|
||||
"title": "Rozložení typu zamíchaného balíčku",
|
||||
"description": "Každý model se použije právě jednou za cyklus před novým zamícháním.",
|
||||
"tip1": "Pro smysluplné rozložení použijte alespoň 2 modely.",
|
||||
"tip2": "Nejlépe funguje s modely podobného výkonu.",
|
||||
"tip3": "Ideální pro vyvažování zátěže mezi více API účty."
|
||||
}
|
||||
},
|
||||
"templateFreeStack": "Volný zásobník (0 $)",
|
||||
@@ -1077,6 +1077,25 @@
|
||||
"cloudWorkerUnreachable": "Nepodařilo se spojit s pracovníkem cloudu. Ujistěte se, že cloud služba je spuštěna (spuštěním npm dev v /cloud).",
|
||||
"connectionFailed": "Připojení selhalo",
|
||||
"syncFailed": "Synchronizace cloudových dat selhalo",
|
||||
"cloudflaredTitle": "Cloudflare Quick Tunnel",
|
||||
"cloudflaredDescription": "Create a Cloudflare Quick Tunnel for this endpoint to make it accessible from the internet without configuring DNS.",
|
||||
"cloudflaredUrlNotice": "Creates a temporary Cloudflare Quick Tunnel. URLs change after restart.",
|
||||
"cloudflaredEnable": "Povolit tunel",
|
||||
"cloudflaredInstallAndEnable": "Nainstalovat a povolit",
|
||||
"cloudflaredDisable": "Stop Tunnel",
|
||||
"cloudflaredRunning": "Běží",
|
||||
"cloudflaredStarting": "Spouštím",
|
||||
"cloudflaredStoppedState": "Zastaveno",
|
||||
"cloudflaredNotInstalled": "Nenainstalováno",
|
||||
"cloudflaredUnsupported": "Nepodporováno",
|
||||
"cloudflaredError": "Chyba",
|
||||
"cloudflaredStarted": "Cloudflare tunnel started",
|
||||
"cloudflaredStopped": "Cloudflare tunnel stopped",
|
||||
"cloudflaredRequestFailed": "Failed to update Cloudflare tunnel",
|
||||
"cloudflaredTemporaryNote": "Quick Tunnel URLs are temporary and will change after restart.",
|
||||
"cloudflaredUnsupportedNote": "This platform is not supported for managed installation.",
|
||||
"cloudflaredIdleNote": "Create a temporary Cloudflare Quick Tunnel for this endpoint.",
|
||||
"cloudflaredLastError": "Poslední chyba: {error}",
|
||||
"providerModelsTitle": "{provider} — Modely",
|
||||
"noModelsForProvider": "Pro tohoto poskytovatele nejsou k dispozici žádné modely.",
|
||||
"chat": "Chat",
|
||||
@@ -1111,26 +1130,7 @@
|
||||
"a2aQuickStartStep2": "Odešlete požadavky JSON-RPC na `POST /a2a` pomocí `message/send` nebo `message/stream`.",
|
||||
"a2aQuickStartStep3": "Sledujte a ovládejte úkoly pomocí příkazů `tasks/get` a `tasks/cancel`.",
|
||||
"completionsLegacy": "Completions (Zastaralé)",
|
||||
"completionsLegacyDesc": "Zastaralé OpenAI text completion – akceptuje oba formáty, prompt string i messages array.",
|
||||
"cloudflaredDescription": "Create a Cloudflare Quick Tunnel for this endpoint to make it accessible from the internet without configuring DNS.",
|
||||
"cloudflaredDisable": "Stop Tunnel",
|
||||
"cloudflaredEnable": "Enable Tunnel",
|
||||
"cloudflaredError": "Error",
|
||||
"cloudflaredIdleNote": "Create a temporary Cloudflare Quick Tunnel for this endpoint.",
|
||||
"cloudflaredInstallAndEnable": "Install & Enable",
|
||||
"cloudflaredLastError": "Last error: {error}",
|
||||
"cloudflaredNotInstalled": "Not installed",
|
||||
"cloudflaredRequestFailed": "Failed to update Cloudflare tunnel",
|
||||
"cloudflaredRunning": "Running",
|
||||
"cloudflaredStarted": "Cloudflare tunnel started",
|
||||
"cloudflaredStarting": "Starting",
|
||||
"cloudflaredStopped": "Cloudflare tunnel stopped",
|
||||
"cloudflaredStoppedState": "Stopped",
|
||||
"cloudflaredTemporaryNote": "Quick Tunnel URLs are temporary and will change after restart.",
|
||||
"cloudflaredTitle": "Cloudflare Quick Tunnel",
|
||||
"cloudflaredUnsupported": "Unsupported",
|
||||
"cloudflaredUnsupportedNote": "This platform is not supported for managed installation.",
|
||||
"cloudflaredUrlNotice": "Creates a temporary Cloudflare Quick Tunnel. URLs change after restart."
|
||||
"completionsLegacyDesc": "Zastaralé OpenAI text completion – akceptuje oba formáty, prompt string i messages array."
|
||||
},
|
||||
"endpoints": {
|
||||
"tabProxy": "Koncová Proxy",
|
||||
@@ -1542,6 +1542,14 @@
|
||||
"chatCompletions": "Chat Completions",
|
||||
"importingModels": "Importuji...",
|
||||
"importFromModels": "Import z /models",
|
||||
"allModelsAlreadyImported": "Všechny modely jsou již importovány",
|
||||
"noNewModelsToImport": "Žádné nové modely k importu — všechny modely jsou již v registru nebo v seznamu vlastních modelů",
|
||||
"skippingExistingModels": "Přeskakování {count} existujících modelů",
|
||||
"autoSync": "Automatická synchronizace",
|
||||
"autoSyncTooltip": "Automaticky obnovuje seznam modelů každých 24 hodin (lze nastavit přes MODEL_SYNC_INTERVAL_HOURS)",
|
||||
"autoSyncEnabled": "Automatická synchronizace povolena – modely se budou pravidelně obnovovat",
|
||||
"autoSyncDisabled": "Automatická synchronizace zakázána",
|
||||
"autoSyncToggleFailed": "Nepodařilo se přepnout automatickou synchronizaci",
|
||||
"clearAllModels": "Vymazat všechny modely",
|
||||
"clearAllModelsConfirm": "Opravdu chcete odstranit všechny modely pro tohoto poskytovatele?",
|
||||
"clearAllModelsSuccess": "Všechny modely vymazány",
|
||||
@@ -1650,7 +1658,7 @@
|
||||
"compatProtocolOpenAI": "OpenAI Chat Completions",
|
||||
"compatProtocolOpenAIResponses": "OpenAI Responses API",
|
||||
"compatProtocolClaude": "Anthropic Messages",
|
||||
"compatUpstreamHeadersLabel": "Extra upstream headers",
|
||||
"compatUpstreamHeadersLabel": "Extra upstream hlavičky",
|
||||
"compatUpstreamHeadersHint": "Nastavení s vysokými oprávněními — stejná úroveň důvěryhodnosti jako při úpravách přihlašovacích údajů API poskytovatele; měli by jej používat pouze důvěryhodní administrátoři. Sloučeno poté, co OmniRoute přidá ověření z klíče API poskytovatele. Pokud vlastní záhlaví používá stejný název jako existující (např. Authorization), vaše hodnota zcela nahradí automaticky vygenerované záhlaví (včetně tokenu Bearer) — upstream vidí pouze to, co jste zadali, nikoli klíč z nastavení. Nesprávná nastavení může způsobit chybu 401 nebo nefunkční upstream ověření. Jeden řádek na jedno záhlaví (např. extra ověření pro některé brány). Pro náhled najděte na hodnotu nebo ji označte. Uloží se při odklonu, kliknutí mimo nebo zavření tohoto panelu.",
|
||||
"compatUpstreamHeaderName": "Název hlavičky",
|
||||
"compatUpstreamHeaderValue": "Hodnota",
|
||||
@@ -1702,27 +1710,19 @@
|
||||
"compatibleProdPlaceholder": "{type} Kompatibilní (produkční)",
|
||||
"tokenRefreshed": "Token úspěšně obnoven",
|
||||
"tokenRefreshFailed": "Obnovení tokenu selhalo",
|
||||
"applyCodexAuthLocal": "Použít autorizaci",
|
||||
"exportCodexAuthFile": "Export autorizace",
|
||||
"codexAuthAppliedLocal": "Codex auth.json applied locally",
|
||||
"codexAuthApplyFailed": "Failed to apply Codex auth.json locally",
|
||||
"codexAuthExported": "Codex auth.json exported",
|
||||
"codexAuthExportFailed": "Failed to export Codex auth.json",
|
||||
"advancedSettings": "Pokročilé nastavení",
|
||||
"chatPathLabel": "Cesta koncového bodu chatu",
|
||||
"chatPathPlaceholder": "/chat/completions",
|
||||
"chatPathHint": "Vlastní chat cesta pro poskytovatele s nestandardí API (např. /v4/chat/completions)",
|
||||
"modelsPathLabel": "Cesta koncového bodu modelu",
|
||||
"modelsPathPlaceholder": "/models",
|
||||
"modelsPathHint": "Cesta k vlastním modelům pro validaci (např. /v4/models)",
|
||||
"autoSync": "Automatická synchronizace",
|
||||
"autoSyncTooltip": "Automaticky obnovuje seznam modelů každých 24 hodin (lze nastavit přes MODEL_SYNC_INTERVAL_HOURS)",
|
||||
"autoSyncEnabled": "Automatická synchronizace povolena – modely se budou pravidelně obnovovat",
|
||||
"autoSyncDisabled": "Automatická synchronizace zakázána",
|
||||
"autoSyncToggleFailed": "Nepodařilo se přepnout automatickou synchronizaci",
|
||||
"allModelsAlreadyImported": "Všechny modely jsou již importovány",
|
||||
"noNewModelsToImport": "Žádné nové modely k importu — všechny modely jsou již v registru nebo v seznamu vlastních modelů",
|
||||
"skippingExistingModels": "Přeskakování {count} existujících modelů",
|
||||
"applyCodexAuthLocal": "Apply auth",
|
||||
"codexAuthAppliedLocal": "Codex auth.json applied locally",
|
||||
"codexAuthApplyFailed": "Failed to apply Codex auth.json locally",
|
||||
"codexAuthExportFailed": "Failed to export Codex auth.json",
|
||||
"codexAuthExported": "Codex auth.json exported",
|
||||
"exportCodexAuthFile": "Export auth"
|
||||
"modelsPathHint": "Cesta k vlastním modelům pro validaci (např. /v4/models)"
|
||||
},
|
||||
"settings": {
|
||||
"title": "Nastavení",
|
||||
@@ -1746,6 +1746,8 @@
|
||||
"darkMode": "Tmavý režim",
|
||||
"lightMode": "Světelný režim",
|
||||
"systemTheme": "Systémové motiv",
|
||||
"debugToggle": "Povolit režim ladění",
|
||||
"sidebarVisibilityToggle": "Zobrazit položky postranního panelu",
|
||||
"enableCache": "Povolit mezipaměť",
|
||||
"cacheTTL": "TTL mezipamět",
|
||||
"maxCacheSize": "Maximální velikost mezipaměti",
|
||||
@@ -1754,6 +1756,17 @@
|
||||
"cacheMisses": "Nenalezeno v mezipaměti",
|
||||
"hitRate": "Míra nalezení",
|
||||
"cacheEntries": "Položky mezipaměti",
|
||||
"cacheSettings": "Nastavení mezipaměti",
|
||||
"semanticCache": "Sémantická mezipaměť",
|
||||
"maxEntries": "Max. položek",
|
||||
"ttlMinutes": "TTL (minuty)",
|
||||
"promptCache": "Prompt Mezipaměť",
|
||||
"strategy": "Strategie",
|
||||
"preserveClientCache": "Zachovat klientskou mezipaměť",
|
||||
"enabled": "Povoleno",
|
||||
"loading": "Načítám...",
|
||||
"saving": "Ukládám...",
|
||||
"save": "Uložit",
|
||||
"circuitBreaker": "Jistič",
|
||||
"retryPolicy": "Zásady opakování",
|
||||
"maxRetries": "Maximální počet opakování",
|
||||
@@ -1761,10 +1774,14 @@
|
||||
"timeoutMs": "Časový limit (ms)",
|
||||
"enableSystemPrompt": "Povolit systémový Prompt",
|
||||
"systemPromptText": "Text systémového Prompt",
|
||||
"autoDisableBannedAccounts": "Automaticky zakázat zablokované účty",
|
||||
"autoDisableDescription": "Trvale označí připojení poskytovatele jako deaktivovaná, pokud vrátí specifické signály zablokování (např. HTTP 403 'verify your account'). Tím je odstraní z rotace komb.",
|
||||
"autoDisableThreshold": "Prahová hodnota zablokování",
|
||||
"autoDisableThresholdDesc": "Počet po sobě jdoucích signálů zablokování před trvalou deaktivací.",
|
||||
"enableThinking": "Umožněte Thinking",
|
||||
"maxThinkingTokens": "Max Thinking Tokenů",
|
||||
"enableProxy": "Povolit Proxy",
|
||||
"proxyUrl": "Proxy URL",
|
||||
"proxyUrl": "URL proxy",
|
||||
"pricingRates": "Formát cen",
|
||||
"currentPricing": "Přehled aktuálních cen",
|
||||
"loadingPricing": "Načítám cenoé údaje...",
|
||||
@@ -1797,6 +1814,9 @@
|
||||
"themeLight": "Světlý",
|
||||
"themeDark": "Tmavý",
|
||||
"themeSystem": "Systém",
|
||||
"sidebarVisibility": "Skrýt položky postranního panelu",
|
||||
"sidebarVisibilityDesc": "Hide any sidebar navigation entry to reduce visual clutter.",
|
||||
"sidebarVisibilityHint": "Any sidebar section is hidden automatically when a...",
|
||||
"hideHealthLogs": "Skrýt protokoly kontrol zdraví",
|
||||
"hideHealthLogsDesc": "Pokud je zapnuto, potlačí zprávy [HealthCheck] v konzoli serveru",
|
||||
"themeAccent": "Barva motivu",
|
||||
@@ -1809,7 +1829,20 @@
|
||||
"themeViolet": "Fialový",
|
||||
"themeOrange": "Pomerančový",
|
||||
"themeCyan": "Azurový",
|
||||
"promptCache": "Prompt Mezipaměť",
|
||||
"whitelabeling": "Branding",
|
||||
"whitelabelingDesc": "Přizpůsobení názvu a loga aplikace",
|
||||
"appName": "Název aplikace",
|
||||
"appNameDesc": "Zobrazovaný název v postranním panelu a záložce prohlížeče",
|
||||
"customLogo": "URL vlastního loga",
|
||||
"customLogoDesc": "URL obrázku vlastního loga",
|
||||
"uploadLogo": "Nahrát logo",
|
||||
"resetLogo": "Obnovit výchozí",
|
||||
"logoPreview": "Náhled",
|
||||
"customFavicon": "Vlastní ikona prohlížeče",
|
||||
"customFaviconDesc": "URL vlastní ikony favicon (zobrazena v záložce prohlížeče)",
|
||||
"uploadFavicon": "Nahrát ikonu",
|
||||
"resetFavicon": "Obnovit ikonu",
|
||||
"faviconPreview": "Náhled ikony",
|
||||
"flushCache": "Vyprázdnit mezipamět",
|
||||
"flushing": "Vyprázdňuji…",
|
||||
"size": "Velikost",
|
||||
@@ -2100,7 +2133,6 @@
|
||||
"unsaved": "neuloženo",
|
||||
"resetDefaults": "Obnovit výchozí nastavení",
|
||||
"saveProvider": "Uložit poskytovatele",
|
||||
"saving": "Ukládám...",
|
||||
"model": "Model",
|
||||
"models": "modely",
|
||||
"moreProviders": "{count} dalších poskytovatelů",
|
||||
@@ -2131,25 +2163,7 @@
|
||||
"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",
|
||||
"sidebarVisibility": "Hide sidebar items",
|
||||
"sidebarVisibilityDesc": "Hide any sidebar navigation entry to reduce visual clutter.",
|
||||
"sidebarVisibilityHint": "Any sidebar section is hidden automatically when a...",
|
||||
"semanticCache": "Semantic Cache",
|
||||
"autoDisableDescription": "Permanently mark provider connections as deactivated if they return specific terminal ban signals (e.g. HTTP 403 'verify your account'). This removes them from the combo rotation.",
|
||||
"preserveClientCache": "Preserve Client Cache",
|
||||
"cacheSettings": "Cache Settings",
|
||||
"autoDisableThreshold": "Ban Threshold",
|
||||
"autoDisableBannedAccounts": "Auto-Disable Banned Accounts",
|
||||
"ttlMinutes": "TTL (minutes)",
|
||||
"maxEntries": "Max Entries",
|
||||
"loading": "Loading...",
|
||||
"save": "Save",
|
||||
"autoDisableThresholdDesc": "Consecutive ban signals required before permanent deactivation.",
|
||||
"debugToggle": "Enable Debug Mode",
|
||||
"sidebarVisibilityToggle": "Show Sidebar Items",
|
||||
"enabled": "Enabled",
|
||||
"strategy": "Strategy"
|
||||
"themeCoral": "Korál"
|
||||
},
|
||||
"translator": {
|
||||
"title": "Překladatel",
|
||||
@@ -2424,7 +2438,7 @@
|
||||
"loadingQuotas": "Načítám...",
|
||||
"account": "Účet",
|
||||
"modelQuotas": "Kvóty Modelu",
|
||||
"lastUsed": "Last Refreshed",
|
||||
"lastUsed": "Naposledy aktualizováno",
|
||||
"actions": "Akce",
|
||||
"refreshQuota": "Obnovit Kvótu",
|
||||
"today": "Dnes",
|
||||
@@ -2568,14 +2582,14 @@
|
||||
"backToLogin": "Zpět na přihlášení",
|
||||
"forgotPassword": "Zapomněli jste heslo?",
|
||||
"defaultPasswordHint": "Výchozí heslo: 123456 (pokud nebylo nastaveno INITIAL_PASSWORD)",
|
||||
"Authorization": "Autorizace",
|
||||
"Content-Disposition": "Obsah-Dispozice",
|
||||
"waitingForAuthorization": "Čekám na autorizaci...",
|
||||
"waitingForGoogleAuthorization": "Čekám na Google autorizaci...",
|
||||
"waitingForOpenAIAuthorization": "Čekám na OpenAI autorizaci...",
|
||||
"waitingForAntigravityAuthorization": "Čekám na Antigravity autorizaci...",
|
||||
"waitingForQoderAuthorization": "Čekám na Qoder autorizaci...",
|
||||
"exchangingCodeForTokens": "Vyměňuji kód za tokeny...",
|
||||
"Authorization": "Autorizace",
|
||||
"Content-Disposition": "Obsah-Dispozice"
|
||||
"exchangingCodeForTokens": "Vyměňuji kód za tokeny..."
|
||||
},
|
||||
"landing": {
|
||||
"brandName": "OmniRoute",
|
||||
@@ -2944,72 +2958,72 @@
|
||||
}
|
||||
},
|
||||
"cache": {
|
||||
"title": "Cache Management",
|
||||
"description": "Monitor and manage semantic response cache, hit rates, and token savings.",
|
||||
"refresh": "Refresh",
|
||||
"clearAll": "Clear All",
|
||||
"memoryEntries": "Memory Entries",
|
||||
"dbEntries": "DB Entries",
|
||||
"cacheHits": "Cache Hits",
|
||||
"tokensSaved": "Tokens Saved",
|
||||
"hitRate": "Hit Rate",
|
||||
"performance": "Cache Performance",
|
||||
"behavior": "Cache Behavior",
|
||||
"idempotency": "Idempotency Layer",
|
||||
"clearSuccess": "Cache cleared. {count} expired entries removed.",
|
||||
"clearError": "Failed to clear cache.",
|
||||
"unavailable": "Cache unavailable",
|
||||
"unavailableDesc": "Could not fetch cache statistics. Make sure the server is running.",
|
||||
"memoryEntriesSub": "In-memory LRU",
|
||||
"title": "Správa mezipaměti",
|
||||
"description": "Sledujte a spravujte sémantickou mezipaměť odpovědí, míru úspěšnosti a úsporu tokenů.",
|
||||
"refresh": "Obnovit",
|
||||
"clearAll": "Vymazat vše",
|
||||
"memoryEntries": "Položky v paměti",
|
||||
"memoryEntriesSub": "V paměti (LRU)",
|
||||
"dbEntries": "Položky v databázi",
|
||||
"dbEntriesSub": "Persisted (SQLite)",
|
||||
"cacheHitsSub": "of {total} total",
|
||||
"tokensSavedSub": "Estimated from hits",
|
||||
"autoRefresh": "Auto-refreshes every {seconds}s",
|
||||
"hits": "Hits",
|
||||
"misses": "Misses",
|
||||
"total": "Total",
|
||||
"behaviorDeterministic": "Only non-streaming requests with temperature=0 are cached.",
|
||||
"behaviorBypass": "Bypass with header {header}.",
|
||||
"cacheHits": "Zásahy mezipaměti",
|
||||
"cacheHitsSub": "z {total} celkem",
|
||||
"tokensSaved": "Ušetřené tokeny",
|
||||
"tokensSavedSub": "Odhadováno z úspěšných zásahů",
|
||||
"hitRate": "Míra úspěšnosti",
|
||||
"performance": "Výkon mezipaměti",
|
||||
"autoRefresh": "Automaticky obnovováno každých {seconds} s",
|
||||
"hits": "Zásahy",
|
||||
"misses": "Neúspěchy",
|
||||
"total": "Celkem",
|
||||
"behavior": "Chování mezipaměti",
|
||||
"behaviorDeterministic": "Ukládají se pouze požadavky bez streamování s temperature=0.",
|
||||
"behaviorBypass": "Obcházení pomocí hlavičky {header}.",
|
||||
"behaviorTwoTier": "Two-tier storage: in-memory LRU (fast) + SQLite (persistent across restarts).",
|
||||
"behaviorTtl": "Default TTL: 30 minutes. Configure via {envVar}.",
|
||||
"activeDedupKeys": "Active Dedup Keys",
|
||||
"dedupWindow": "Dedup Window",
|
||||
"inputTokens": "Input Tokens",
|
||||
"requestsShort": "reqs",
|
||||
"inputShort": "In",
|
||||
"resetting": "Resetting...",
|
||||
"cachedTokensCol": "Cached",
|
||||
"search": "Search",
|
||||
"cacheReuseRatioDesc": "Cached tokens / Total input tokens",
|
||||
"resetMetrics": "Reset Metrics",
|
||||
"loading": "Loading...",
|
||||
"cachedRequests": "Cached Requests",
|
||||
"model": "Model",
|
||||
"cached": "Cached",
|
||||
"actions": "Actions",
|
||||
"trend24h": "Cache Trend (24h)",
|
||||
"cachedShort": "Cached",
|
||||
"cacheCreation": "Creation",
|
||||
"byProvider": "Breakdown by Provider",
|
||||
"created": "Created",
|
||||
"cacheCreationTokens": "Cache Creation Tokens",
|
||||
"withCacheControl": "With Cache Control",
|
||||
"searchEntries": "Search entries...",
|
||||
"cacheReuseRatio": "Cache Reuse Ratio",
|
||||
"estCostSaved": "Est. Cost Saved",
|
||||
"cachedTokens": "Cached Tokens",
|
||||
"requests": "Requests",
|
||||
"signature": "Signature",
|
||||
"cacheCreationWrite": "Cache Creation (Write)",
|
||||
"expires": "Expires",
|
||||
"writeShort": "Write",
|
||||
"cacheHitRate": "Cache Hit Rate",
|
||||
"cacheMetrics": "Prompt Cache Metrics",
|
||||
"overview": "Overview",
|
||||
"promptCache": "Prompt Cache (Provider-Side)",
|
||||
"behaviorTtl": "Výchozí TTL: 30 minut. Nastavte pomocí {envVar}.",
|
||||
"idempotency": "Vrstva idempotence",
|
||||
"activeDedupKeys": "Aktivní deduplikační klíče",
|
||||
"dedupWindow": "Deduplikační okno",
|
||||
"clearSuccess": "Mezipaměť vymazána. {count} expirovaných položek odstraněno.",
|
||||
"clearError": "Vymazání mezipaměti selhalo.",
|
||||
"unavailable": "Mezipaměť nedostupná",
|
||||
"unavailableDesc": "Nepodařilo se načíst statistiky mezipaměti. Ujistěte se, že server běží.",
|
||||
"promptCache": "Prompt mezipaměť (na straně poskytovatele)",
|
||||
"cachedRequests": "Uložené požadavky",
|
||||
"cacheHitRate": "Míra úspěšnosti mezipaměti",
|
||||
"cachedTokens": "Tokeny v mezipaměti",
|
||||
"cacheCreationTokens": "Tokeny vytvoření mezipaměti",
|
||||
"cacheMetrics": "Metriky mezipaměti promptů",
|
||||
"withCacheControl": "S Cache Control",
|
||||
"cachedTokensRead": "Tokeny v mezipaměti (čtení)",
|
||||
"cacheCreationWrite": "Vytvoření mezipaměti (zápis)",
|
||||
"cacheReuseRatio": "Poměr opětovného použití",
|
||||
"cacheReuseRatioDesc": "Tokeny v mezipaměti / Celkem vstupních tokenů",
|
||||
"estCostSaved": "Odhadovaná úspora nákladů",
|
||||
"requestsShort": "pož.",
|
||||
"inputShort": "Vstup",
|
||||
"cachedShort": "V mezipaměti",
|
||||
"writeShort": "Zápis",
|
||||
"resetting": "Resetuji...",
|
||||
"resetMetrics": "Resetovat metriky",
|
||||
"byProvider": "Rozpis podle poskytovatele",
|
||||
"provider": "Provider",
|
||||
"cachedTokensRead": "Cached Tokens (Read)",
|
||||
"entries": "Entries",
|
||||
"noEntries": "No cache entries found"
|
||||
"requests": "Požadavky",
|
||||
"inputTokens": "Vstupní tokeny",
|
||||
"cachedTokensCol": "V mezipaměti",
|
||||
"cacheCreation": "Vytvoření",
|
||||
"trend24h": "Trend mezipaměti (24 h)",
|
||||
"cached": "V mezipaměti",
|
||||
"overview": "Přehled",
|
||||
"entries": "Položky",
|
||||
"searchEntries": "Hledat položky...",
|
||||
"search": "Hledat",
|
||||
"loading": "Načítám...",
|
||||
"noEntries": "Nebyly nalezeny žádné položky mezipaměti",
|
||||
"signature": "Signatura",
|
||||
"model": "Model",
|
||||
"created": "Vytvořeno",
|
||||
"expires": "Vyprší",
|
||||
"actions": "Akce"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -143,6 +143,7 @@
|
||||
"dashboard": "Dashboard",
|
||||
"providers": "Udbydere",
|
||||
"combos": "Combos",
|
||||
"autoCombo": "Auto Combo",
|
||||
"usage": "Brug",
|
||||
"analytics": "Analytics",
|
||||
"costs": "Omkostninger",
|
||||
@@ -152,8 +153,12 @@
|
||||
"media": "Medier",
|
||||
"settings": "Indstillinger",
|
||||
"translator": "Oversætter",
|
||||
"playground": "Legeplads",
|
||||
"searchTools": "Search Tools",
|
||||
"agents": "Agenter",
|
||||
"docs": "Dokumenter",
|
||||
"issues": "Problemer",
|
||||
"endpoints": "Endpoints",
|
||||
"apiManager": "API Manager",
|
||||
"logs": "Logs",
|
||||
"auditLog": "Revisionslog",
|
||||
@@ -165,6 +170,11 @@
|
||||
"debug": "Debug",
|
||||
"system": "System",
|
||||
"help": "Hjælp",
|
||||
"primarySection": "Main",
|
||||
"cliSection": "CLI",
|
||||
"debugSection": "Debug",
|
||||
"systemSection": "System",
|
||||
"helpSection": "Help",
|
||||
"serverDisconnected": "Server afbrudt",
|
||||
"serverDisconnectedMsg": "Proxyserveren er blevet stoppet eller genstarter.",
|
||||
"expandSidebar": "Udvid sidebjælken",
|
||||
@@ -180,19 +190,9 @@
|
||||
"themeViolet": "Violet",
|
||||
"themeOrange": "Orange",
|
||||
"themeCyan": "Turkis",
|
||||
"endpoints": "Endpoints",
|
||||
"playground": "Legeplads",
|
||||
"agents": "Agenter",
|
||||
"cliToolsShort": "Værktøjer",
|
||||
"autoCombo": "Auto Combo",
|
||||
"searchTools": "Search Tools",
|
||||
"cache": "Cache",
|
||||
"cacheShort": "Cache",
|
||||
"cliSection": "CLI",
|
||||
"debugSection": "Debug",
|
||||
"helpSection": "Help",
|
||||
"primarySection": "Main",
|
||||
"systemSection": "System"
|
||||
"cacheShort": "Cache"
|
||||
},
|
||||
"themesPage": {
|
||||
"title": "Temaer",
|
||||
@@ -220,6 +220,10 @@
|
||||
"homeDescription": "Velkommen til OmniRoute",
|
||||
"endpoint": "Endpoints",
|
||||
"endpointDescription": "Manage proxy endpoints, MCP, A2A, and API endpoints",
|
||||
"mcp": "MCP",
|
||||
"mcpDescription": "Model Context Protocol server management and tools",
|
||||
"a2a": "A2A",
|
||||
"a2aDescription": "Agent-to-Agent protocol tasks and observability",
|
||||
"settings": "Indstillinger",
|
||||
"settingsDescription": "Administrer dine præferencer",
|
||||
"openaiCompatible": "OpenAI-kompatibel",
|
||||
@@ -227,11 +231,7 @@
|
||||
"media": "Medie",
|
||||
"mediaDescription": "Generer billeder, videoer og musik",
|
||||
"themes": "Temaer",
|
||||
"themesDescription": "Vælg et farvetema til hele dashboardpanelet",
|
||||
"mcp": "MCP",
|
||||
"mcpDescription": "Model Context Protocol server management and tools",
|
||||
"a2a": "A2A",
|
||||
"a2aDescription": "Agent-to-Agent protocol tasks and observability"
|
||||
"themesDescription": "Vælg et farvetema til hele dashboardpanelet"
|
||||
},
|
||||
"home": {
|
||||
"quickStart": "Hurtig start",
|
||||
@@ -332,6 +332,26 @@
|
||||
"keyCreatedNote": "Kopiér og gem denne nøgle nu – den vises ikke igen.",
|
||||
"done": "Færdig",
|
||||
"savePermissions": "Gem tilladelser",
|
||||
"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",
|
||||
"allowAll": "Tillad alle",
|
||||
"restrict": "Begræns",
|
||||
"allowAllInfo": "Denne nøgle kan få adgang til alle tilgængelige modeller.",
|
||||
@@ -356,6 +376,7 @@
|
||||
"failedUpdatePermissionsRetry": "Kunne ikke opdatere tilladelser. Prøv venligst igen.",
|
||||
"unknownProvider": "ukendt",
|
||||
"copyMaskedKey": "Kopiér maskeret nøgle",
|
||||
"keyOnlyAvailableAtCreation": "Full key available only at creation time — copy it when you first create the key",
|
||||
"modelsCount": "{count, plural, one {# model} other {# models}}",
|
||||
"lastUsedOn": "Sidste: {date}",
|
||||
"editPermissions": "Rediger tilladelser",
|
||||
@@ -365,28 +386,7 @@
|
||||
"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",
|
||||
"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"
|
||||
"selectedCount": "{count} valgt"
|
||||
},
|
||||
"auditLog": {
|
||||
"title": "Revisionslog",
|
||||
@@ -425,6 +425,57 @@
|
||||
"videoDescription": "Create videos with AnimateDiff, Stable Video Diffusion via ComfyUI or SD WebUI.",
|
||||
"musicDescription": "Compose music using Stable Audio Open or MusicGen via ComfyUI."
|
||||
},
|
||||
"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",
|
||||
"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",
|
||||
"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"
|
||||
},
|
||||
"cliTools": {
|
||||
"title": "CLI værktøjer",
|
||||
"noActiveProviders": "Ingen aktive udbydere",
|
||||
@@ -497,6 +548,11 @@
|
||||
"antigravityStep2Prefix": "2. Tilføj",
|
||||
"antigravityStep2Suffix": "til din hosts-fil som 127.0.0.1.",
|
||||
"antigravityStep3": "3. Åbn Antigravity og anmodninger vil blive fuldført.",
|
||||
"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.",
|
||||
"sudoPasswordRequiredTitle": "Sudo-adgangskode påkrævet",
|
||||
"sudoPasswordHint": "Administratoradgangskode er påkrævet for at ændre værtsfil- og systemproxyindstillinger.",
|
||||
"enterSudoPassword": "Indtast sudo adgangskode",
|
||||
@@ -569,6 +625,22 @@
|
||||
"openClawManualConfiguration": "Åbn Claw - Manuel konfiguration",
|
||||
"clineManualConfiguration": "Cline manuel konfiguration",
|
||||
"kiloManualConfiguration": "Kilokode manuel konfiguration",
|
||||
"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."
|
||||
},
|
||||
"toolDescriptions": {
|
||||
"antigravity": "Google Antigravity IDE med MITM",
|
||||
"claude": "Antropisk Claude Code CLI",
|
||||
@@ -706,27 +778,6 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"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": {
|
||||
@@ -988,7 +1039,12 @@
|
||||
"settingsApi": "Settings API",
|
||||
"categoryCore": "Kerne-API'er",
|
||||
"categoryMedia": "Medier & Multi-Modal",
|
||||
"categorySearch": "Search & Discovery",
|
||||
"categoryUtility": "Værktøjer & Administration",
|
||||
"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.",
|
||||
"enableCloudTitle": "Aktiver Cloud Proxy",
|
||||
"whatYouGet": "Hvad du får",
|
||||
"cloudBenefitAccess": "Få adgang til din API fra hvor som helst i verden",
|
||||
@@ -1021,6 +1077,25 @@
|
||||
"cloudWorkerUnreachable": "Kunne ikke nå cloud worker. Sørg for, at skytjenesten kører (npm kør dev i /cloud).",
|
||||
"connectionFailed": "Forbindelsen mislykkedes",
|
||||
"syncFailed": "Kunne ikke synkronisere skydata",
|
||||
"cloudflaredTitle": "Cloudflare Quick Tunnel",
|
||||
"cloudflaredDescription": "Create a Cloudflare Quick Tunnel for this endpoint to make it accessible from the internet without configuring DNS.",
|
||||
"cloudflaredUrlNotice": "Creates a temporary Cloudflare Quick Tunnel. URLs change after restart.",
|
||||
"cloudflaredEnable": "Enable Tunnel",
|
||||
"cloudflaredInstallAndEnable": "Install & Enable",
|
||||
"cloudflaredDisable": "Stop Tunnel",
|
||||
"cloudflaredRunning": "Running",
|
||||
"cloudflaredStarting": "Starting",
|
||||
"cloudflaredStoppedState": "Stopped",
|
||||
"cloudflaredNotInstalled": "Not installed",
|
||||
"cloudflaredUnsupported": "Unsupported",
|
||||
"cloudflaredError": "Error",
|
||||
"cloudflaredStarted": "Cloudflare tunnel started",
|
||||
"cloudflaredStopped": "Cloudflare tunnel stopped",
|
||||
"cloudflaredRequestFailed": "Failed to update Cloudflare tunnel",
|
||||
"cloudflaredTemporaryNote": "Quick Tunnel URLs are temporary and will change after restart.",
|
||||
"cloudflaredUnsupportedNote": "This platform is not supported for managed installation.",
|
||||
"cloudflaredIdleNote": "Create a temporary Cloudflare Quick Tunnel for this endpoint.",
|
||||
"cloudflaredLastError": "Last error: {error}",
|
||||
"providerModelsTitle": "{provider} — Modeller",
|
||||
"noModelsForProvider": "Ingen tilgængelige modeller for denne udbyder.",
|
||||
"chat": "Chat",
|
||||
@@ -1055,31 +1130,19 @@
|
||||
"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",
|
||||
"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.",
|
||||
"cloudflaredDescription": "Create a Cloudflare Quick Tunnel for this endpoint to make it accessible from the internet without configuring DNS.",
|
||||
"cloudflaredDisable": "Stop Tunnel",
|
||||
"cloudflaredEnable": "Enable Tunnel",
|
||||
"cloudflaredError": "Error",
|
||||
"cloudflaredIdleNote": "Create a temporary Cloudflare Quick Tunnel for this endpoint.",
|
||||
"cloudflaredInstallAndEnable": "Install & Enable",
|
||||
"cloudflaredLastError": "Last error: {error}",
|
||||
"cloudflaredNotInstalled": "Not installed",
|
||||
"cloudflaredRequestFailed": "Failed to update Cloudflare tunnel",
|
||||
"cloudflaredRunning": "Running",
|
||||
"cloudflaredStarted": "Cloudflare tunnel started",
|
||||
"cloudflaredStarting": "Starting",
|
||||
"cloudflaredStopped": "Cloudflare tunnel stopped",
|
||||
"cloudflaredStoppedState": "Stopped",
|
||||
"cloudflaredTemporaryNote": "Quick Tunnel URLs are temporary and will change after restart.",
|
||||
"cloudflaredTitle": "Cloudflare Quick Tunnel",
|
||||
"cloudflaredUnsupported": "Unsupported",
|
||||
"cloudflaredUnsupportedNote": "This platform is not supported for managed installation.",
|
||||
"cloudflaredUrlNotice": "Creates a temporary Cloudflare Quick Tunnel. URLs change after restart."
|
||||
"completionsLegacyDesc": "Legacy OpenAI text completions — accepts both prompt string and messages array format"
|
||||
},
|
||||
"endpoints": {
|
||||
"tabProxy": "Endpoint Proxy",
|
||||
"tabApiEndpoints": "API Endpoints",
|
||||
"apiEndpointsTitle": "API Endpoints",
|
||||
"apiEndpointsDescription": "Backend API endpoints that can be consumed by other applications and services. This section will list all available REST APIs with documentation and testing capabilities.",
|
||||
"comingSoon": "Coming Soon",
|
||||
"plannedFeatures": "Planned Features",
|
||||
"featureRestApi": "REST API endpoint catalog with interactive documentation",
|
||||
"featureWebhooks": "Webhook configuration and event subscriptions",
|
||||
"featureSwagger": "OpenAPI / Swagger spec auto-generation",
|
||||
"featureAuth": "API key and OAuth scope management per endpoint"
|
||||
},
|
||||
"mcpDashboard": {
|
||||
"loading": "Indlæser MCP-dashboard...",
|
||||
@@ -1251,6 +1314,12 @@
|
||||
"issuesLabel": "Problemer opdaget",
|
||||
"operational": "Operationel",
|
||||
"providers": "Udbydere",
|
||||
"configuredProvidersLabel": "Konfigureret i dashboard",
|
||||
"configuredProvidersHint": "Udbydere med legitimationsoplysninger gemt i /dashboard/udbydere, uanset køretidstilstand.",
|
||||
"activeProviders": "{count} active",
|
||||
"activeProvidersHint": "Konfigurerede udbydere, der i øjeblikket er aktiveret for routinganmodninger.",
|
||||
"monitoredProviders": "{count} monitored",
|
||||
"monitoredProvidersHint": "Udbydere, der i øjeblikket spores af strømafbryderens sundhedsmonitorer.",
|
||||
"healthyCount": "{count} sund",
|
||||
"nodeVersion": "Node {version}",
|
||||
"failures": "{count} fejl",
|
||||
@@ -1269,13 +1338,7 @@
|
||||
"resetAllTitle": "Nulstil alle afbrydere til sund tilstand",
|
||||
"resetting": "Nulstiller...",
|
||||
"resetAll": "Nulstil alle",
|
||||
"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."
|
||||
"until": "Indtil {time}"
|
||||
},
|
||||
"limits": {
|
||||
"title": "Grænser og kvoter",
|
||||
@@ -1437,6 +1500,7 @@
|
||||
"clearing": "Rydder...",
|
||||
"until": "Indtil {time}",
|
||||
"providerTestFailed": "Providertest mislykkedes",
|
||||
"providerTestTimeout": "Provider test timed out — too many connections to test at once",
|
||||
"modeTest": "{mode} Test",
|
||||
"passedCount": "{count} bestået",
|
||||
"failedCount": "{count} mislykkedes",
|
||||
@@ -1478,6 +1542,14 @@
|
||||
"chatCompletions": "Chatafslutninger",
|
||||
"importingModels": "Importerer...",
|
||||
"importFromModels": "Importer fra /models",
|
||||
"allModelsAlreadyImported": "Alle modeller er allerede importeret",
|
||||
"noNewModelsToImport": "Ingen nye modeller at importere — alle modeller findes allerede i registreret eller brugerdefineret liste",
|
||||
"skippingExistingModels": "Springer {count} eksisterende modeller over",
|
||||
"autoSync": "Auto-synkronisering",
|
||||
"autoSyncTooltip": "Opdater modellisten automatisk hver 24. time (kan konfigureres via MODEL_SYNC_INTERVAL_HOURS)",
|
||||
"autoSyncEnabled": "Automatisk synkronisering aktiveret - modellerne opdateres med jævne mellemrum",
|
||||
"autoSyncDisabled": "Automatisk synkronisering deaktiveret",
|
||||
"autoSyncToggleFailed": "Automatisk synkronisering kunne ikke slås til eller fra",
|
||||
"clearAllModels": "Ryd alle modeller",
|
||||
"clearAllModelsConfirm": "Er du sikker på, at du vil fjerne alle modeller for denne udbyder? Dette kan ikke fortrydes.",
|
||||
"clearAllModelsSuccess": "Alle modeller ryddet",
|
||||
@@ -1531,6 +1603,8 @@
|
||||
"addFirstConnectionHint": "Tilføj din første forbindelse for at komme i gang",
|
||||
"addConnection": "Tilføj forbindelse",
|
||||
"availableModels": "Tilgængelige modeller",
|
||||
"builtInModels": "Built-in models",
|
||||
"builtInModelsHint": "Registry models for this provider. Use the pencil to set compatibility options.",
|
||||
"pageAutoRefresh": "Siden opdateres automatisk...",
|
||||
"statusDisabled": "deaktiveret",
|
||||
"statusConnected": "tilsluttet",
|
||||
@@ -1571,6 +1645,26 @@
|
||||
"openRouterModelPlaceholder": "antropisk/claude-3-opus",
|
||||
"customModels": "Brugerdefinerede modeller",
|
||||
"customModelsHint": "Tilføj model-id'er, der ikke er på standardlisten. Disse vil være tilgængelige for routing.",
|
||||
"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",
|
||||
"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": "f.eks. gpt-4.5-turbo",
|
||||
"loading": "Indlæser...",
|
||||
@@ -1614,52 +1708,21 @@
|
||||
"compatibleBaseUrlHint": "Root URL of your {type}-compatible API. Use Advanced Settings for custom endpoint paths.",
|
||||
"apiKeyForCheck": "API-nøgle (til check)",
|
||||
"compatibleProdPlaceholder": "{type} Kompatibel (Prod)",
|
||||
"providerTestTimeout": "Provider test timed out — too many connections to test at once",
|
||||
"tokenRefreshed": "Token refreshed successfully",
|
||||
"tokenRefreshFailed": "Token refresh failed",
|
||||
"applyCodexAuthLocal": "Apply auth",
|
||||
"exportCodexAuthFile": "Export auth",
|
||||
"codexAuthAppliedLocal": "Codex auth.json applied locally",
|
||||
"codexAuthApplyFailed": "Failed to apply Codex auth.json locally",
|
||||
"codexAuthExported": "Codex auth.json exported",
|
||||
"codexAuthExportFailed": "Failed to export Codex auth.json",
|
||||
"advancedSettings": "Advanced Settings",
|
||||
"chatPathLabel": "Chat Endpoint Path",
|
||||
"chatPathPlaceholder": "/chat/completions",
|
||||
"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)",
|
||||
"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",
|
||||
"autoSync": "Auto-synkronisering",
|
||||
"autoSyncTooltip": "Opdater modellisten automatisk hver 24. time (kan konfigureres via MODEL_SYNC_INTERVAL_HOURS)",
|
||||
"autoSyncEnabled": "Automatisk synkronisering aktiveret - modellerne opdateres med jævne mellemrum",
|
||||
"autoSyncDisabled": "Automatisk synkronisering deaktiveret",
|
||||
"autoSyncToggleFailed": "Automatisk synkronisering kunne ikke slås til eller fra",
|
||||
"allModelsAlreadyImported": "Alle modeller er allerede importeret",
|
||||
"noNewModelsToImport": "Ingen nye modeller at importere — alle modeller findes allerede i registreret eller brugerdefineret liste",
|
||||
"skippingExistingModels": "Springer {count} eksisterende modeller over",
|
||||
"applyCodexAuthLocal": "Apply auth",
|
||||
"codexAuthAppliedLocal": "Codex auth.json applied locally",
|
||||
"codexAuthApplyFailed": "Failed to apply Codex auth.json locally",
|
||||
"codexAuthExportFailed": "Failed to export Codex auth.json",
|
||||
"codexAuthExported": "Codex auth.json exported",
|
||||
"exportCodexAuthFile": "Export auth"
|
||||
"modelsPathHint": "Custom models path for validation (e.g. /v4/models)"
|
||||
},
|
||||
"settings": {
|
||||
"title": "Indstillinger",
|
||||
@@ -1683,6 +1746,8 @@
|
||||
"darkMode": "Mørk tilstand",
|
||||
"lightMode": "Lys tilstand",
|
||||
"systemTheme": "System tema",
|
||||
"debugToggle": "Enable Debug Mode",
|
||||
"sidebarVisibilityToggle": "Show Sidebar Items",
|
||||
"enableCache": "Aktiver cache",
|
||||
"cacheTTL": "Cache TTL",
|
||||
"maxCacheSize": "Max cachestørrelse",
|
||||
@@ -1691,6 +1756,17 @@
|
||||
"cacheMisses": "Cache misses",
|
||||
"hitRate": "Hitrate",
|
||||
"cacheEntries": "Cache-indtastninger",
|
||||
"cacheSettings": "Cache Settings",
|
||||
"semanticCache": "Semantic Cache",
|
||||
"maxEntries": "Max Entries",
|
||||
"ttlMinutes": "TTL (minutes)",
|
||||
"promptCache": "Spørg cache",
|
||||
"strategy": "Strategy",
|
||||
"preserveClientCache": "Preserve Client Cache",
|
||||
"enabled": "Enabled",
|
||||
"loading": "Loading...",
|
||||
"saving": "Gemmer...",
|
||||
"save": "Save",
|
||||
"circuitBreaker": "Strømafbryder",
|
||||
"retryPolicy": "Politik for forsøg igen",
|
||||
"maxRetries": "Max forsøger igen",
|
||||
@@ -1698,6 +1774,10 @@
|
||||
"timeoutMs": "Timeout (ms)",
|
||||
"enableSystemPrompt": "Aktiver systemprompt",
|
||||
"systemPromptText": "Systemprompttekst",
|
||||
"autoDisableBannedAccounts": "Auto-Disable Banned Accounts",
|
||||
"autoDisableDescription": "Permanently mark provider connections as deactivated if they return specific terminal ban signals (e.g. HTTP 403 'verify your account'). This removes them from the combo rotation.",
|
||||
"autoDisableThreshold": "Ban Threshold",
|
||||
"autoDisableThresholdDesc": "Consecutive ban signals required before permanent deactivation.",
|
||||
"enableThinking": "Aktiver tænkning",
|
||||
"maxThinkingTokens": "Max Thinking Tokens",
|
||||
"enableProxy": "Aktiver proxy",
|
||||
@@ -1734,6 +1814,9 @@
|
||||
"themeLight": "Lys",
|
||||
"themeDark": "Mørk",
|
||||
"themeSystem": "System",
|
||||
"sidebarVisibility": "Hide sidebar items",
|
||||
"sidebarVisibilityDesc": "Hide any sidebar navigation entry to reduce visual clutter.",
|
||||
"sidebarVisibilityHint": "Any sidebar section is hidden automatically when a...",
|
||||
"hideHealthLogs": "Skjul logs til sundhedstjek",
|
||||
"hideHealthLogsDesc": "Når ON, skal du undertrykke [HealthCheck]-meddelelser i serverkonsollen",
|
||||
"themeAccent": "Temafarve",
|
||||
@@ -1746,7 +1829,20 @@
|
||||
"themeViolet": "Lilla",
|
||||
"themeOrange": "Orange",
|
||||
"themeCyan": "Cyan",
|
||||
"promptCache": "Spørg cache",
|
||||
"whitelabeling": "Branding",
|
||||
"whitelabelingDesc": "Customize the application name and logo",
|
||||
"appName": "Application Name",
|
||||
"appNameDesc": "Display name shown in sidebar and browser tab",
|
||||
"customLogo": "Custom Logo URL",
|
||||
"customLogoDesc": "URL to your custom logo image",
|
||||
"uploadLogo": "Upload Logo",
|
||||
"resetLogo": "Reset to Default",
|
||||
"logoPreview": "Preview",
|
||||
"customFavicon": "Browser Favicon",
|
||||
"customFaviconDesc": "URL to your custom favicon (shown in browser tab)",
|
||||
"uploadFavicon": "Upload Favicon",
|
||||
"resetFavicon": "Reset Favicon",
|
||||
"faviconPreview": "Favicon Preview",
|
||||
"flushCache": "Skyl cache",
|
||||
"flushing": "Skyller...",
|
||||
"size": "Størrelse",
|
||||
@@ -1804,7 +1900,15 @@
|
||||
"providersBlocked": "{count} udbyder(e) blokeret fra /modeller",
|
||||
"blockProviderTitle": "Bloker {provider}",
|
||||
"unblockProviderTitle": "Fjern blokering af {provider}",
|
||||
"cliFingerprint": "CLI Fingerprint Matching",
|
||||
"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}",
|
||||
"routingStrategy": "Rutestrategi",
|
||||
"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.",
|
||||
"fillFirst": "Fyld først",
|
||||
"fillFirstDesc": "Brug konti i prioriteret rækkefølge",
|
||||
"roundRobin": "Round Robin",
|
||||
@@ -1899,6 +2003,9 @@
|
||||
"fillModelAndProviders": "Udfyld venligst modelnavn og udbydere",
|
||||
"addAtLeastOneProvider": "Tilføj mindst én udbyder",
|
||||
"comboDefaultsTitle": "Combo standarder",
|
||||
"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.",
|
||||
"globalComboConfig": "Global combo-konfiguration",
|
||||
"defaultStrategy": "Standardstrategi",
|
||||
"defaultStrategyDesc": "Anvendt på nye kombinationer uden eksplicit strategi",
|
||||
@@ -2026,7 +2133,6 @@
|
||||
"unsaved": "ikke gemt",
|
||||
"resetDefaults": "Nulstil standarder",
|
||||
"saveProvider": "Gem udbyder",
|
||||
"saving": "Gemmer...",
|
||||
"model": "Model",
|
||||
"models": "modeller",
|
||||
"moreProviders": "{count} flere udbydere",
|
||||
@@ -2057,36 +2163,7 @@
|
||||
"customPricingNote": "Du kan tilsidesætte standardpriser for specifikke modeller. Tilpassede tilsidesættelser har prioritet frem for automatisk registrerede priser.",
|
||||
"editPricing": "Rediger prissætning",
|
||||
"viewFullDetails": "Se alle detaljer",
|
||||
"themeCoral": "Koral",
|
||||
"cliFingerprint": "CLI Fingerprint Matching",
|
||||
"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}",
|
||||
"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.",
|
||||
"sidebarVisibility": "Hide sidebar items",
|
||||
"sidebarVisibilityDesc": "Hide any sidebar navigation entry to reduce visual clutter.",
|
||||
"sidebarVisibilityHint": "Any sidebar section is hidden automatically when a...",
|
||||
"semanticCache": "Semantic Cache",
|
||||
"autoDisableDescription": "Permanently mark provider connections as deactivated if they return specific terminal ban signals (e.g. HTTP 403 'verify your account'). This removes them from the combo rotation.",
|
||||
"preserveClientCache": "Preserve Client Cache",
|
||||
"cacheSettings": "Cache Settings",
|
||||
"autoDisableThreshold": "Ban Threshold",
|
||||
"autoDisableBannedAccounts": "Auto-Disable Banned Accounts",
|
||||
"ttlMinutes": "TTL (minutes)",
|
||||
"maxEntries": "Max Entries",
|
||||
"loading": "Loading...",
|
||||
"save": "Save",
|
||||
"autoDisableThresholdDesc": "Consecutive ban signals required before permanent deactivation.",
|
||||
"debugToggle": "Enable Debug Mode",
|
||||
"sidebarVisibilityToggle": "Show Sidebar Items",
|
||||
"enabled": "Enabled",
|
||||
"strategy": "Strategy"
|
||||
"themeCoral": "Koral"
|
||||
},
|
||||
"translator": {
|
||||
"title": "Oversætter",
|
||||
@@ -2606,6 +2683,8 @@
|
||||
"clientCompatibility": "Klientkompatibilitet",
|
||||
"protocolsToc": "Protokoller",
|
||||
"apiReference": "API-reference",
|
||||
"managementApiReference": "Management API Reference",
|
||||
"managementApiDescription": "Automation endpoints for proxy registry, scope assignments, and legacy proxy migration.",
|
||||
"method": "Metode",
|
||||
"path": "Sti",
|
||||
"notes": "Noter",
|
||||
@@ -2697,10 +2776,19 @@
|
||||
"endpointResponsesNote": "Responses API-endepunkt (Codex, o-serien).",
|
||||
"endpointModelsNote": "Modelkatalog for alle tilsluttede udbydere.",
|
||||
"endpointAudioNote": "Lydtransskription (Deepgram, AssemblyAI).",
|
||||
"endpointSpeechNote": "Tekst-til-tale generation (ElevenLabs, OpenAI TTS).",
|
||||
"endpointEmbeddingsNote": "Generering af tekstindlejring (OpenAI, Cohere, Voyage).",
|
||||
"endpointImagesNote": "Billedgenerering (NanoBanana).",
|
||||
"endpointRewriteChatNote": "Omskrivehjælper til klienter uden /v1.",
|
||||
"endpointRewriteResponsesNote": "Omskriv hjælper til svar uden /v1.",
|
||||
"endpointRewriteModelsNote": "Omskrivningshjælper til modelopdagelse uden /v1.",
|
||||
"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.",
|
||||
"modelPrefixesDescriptionStart": "Brug udbyderpræfikset før modelnavnet for at rute til en bestemt udbyder. Eksempel:",
|
||||
"modelPrefixesDescriptionEnd": "ruter til GitHub Copilot.",
|
||||
"provider": "Udbyder",
|
||||
@@ -2710,18 +2798,7 @@
|
||||
"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.",
|
||||
"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.",
|
||||
"endpointSpeechNote": "Tekst-til-tale generation (ElevenLabs, OpenAI TTS).",
|
||||
"endpointEmbeddingsNote": "Generering af tekstindlejring (OpenAI, Cohere, Voyage)."
|
||||
"troubleshootingOAuth": "For OAuth-udbydere skal du godkende igen, hvis tokens udløber. Tjek udbyderkortets statusindikator."
|
||||
},
|
||||
"legal": {
|
||||
"privacyPolicy": "Privatlivspolitik",
|
||||
@@ -2783,18 +2860,6 @@
|
||||
"termsSection6Title": "6. Open Source",
|
||||
"termsSection6Text": "OmniRoute er open source-software. Du kan frit inspicere, ændre og distribuere den i henhold til licensbetingelserne."
|
||||
},
|
||||
"endpoints": {
|
||||
"tabProxy": "Endpoint Proxy",
|
||||
"tabApiEndpoints": "API Endpoints",
|
||||
"apiEndpointsTitle": "API Endpoints",
|
||||
"apiEndpointsDescription": "Backend API endpoints that can be consumed by other applications and services. This section will list all available REST APIs with documentation and testing capabilities.",
|
||||
"comingSoon": "Coming Soon",
|
||||
"plannedFeatures": "Planned Features",
|
||||
"featureRestApi": "REST API endpoint catalog with interactive documentation",
|
||||
"featureWebhooks": "Webhook configuration and event subscriptions",
|
||||
"featureSwagger": "OpenAPI / Swagger spec auto-generation",
|
||||
"featureAuth": "API key and OAuth scope management per endpoint"
|
||||
},
|
||||
"agents": {
|
||||
"title": "CLI Agents",
|
||||
"description": "Discover installed CLI agents on your system. Add custom agents for auto-detection.",
|
||||
@@ -2849,57 +2914,6 @@
|
||||
"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"
|
||||
},
|
||||
"templateNames": {
|
||||
"simple-chat": "Simpel chat",
|
||||
"streaming": "Streaming",
|
||||
@@ -2949,67 +2963,67 @@
|
||||
"refresh": "Refresh",
|
||||
"clearAll": "Clear All",
|
||||
"memoryEntries": "Memory Entries",
|
||||
"memoryEntriesSub": "In-memory LRU",
|
||||
"dbEntries": "DB Entries",
|
||||
"dbEntriesSub": "Persisted (SQLite)",
|
||||
"cacheHits": "Cache Hits",
|
||||
"cacheHitsSub": "of {total} total",
|
||||
"tokensSaved": "Tokens Saved",
|
||||
"tokensSavedSub": "Estimated from hits",
|
||||
"hitRate": "Hit Rate",
|
||||
"performance": "Cache Performance",
|
||||
"behavior": "Cache Behavior",
|
||||
"idempotency": "Idempotency Layer",
|
||||
"clearSuccess": "Cache cleared. {count} expired entries removed.",
|
||||
"clearError": "Failed to clear cache.",
|
||||
"unavailable": "Cache unavailable",
|
||||
"unavailableDesc": "Could not fetch cache statistics. Make sure the server is running.",
|
||||
"memoryEntriesSub": "In-memory LRU",
|
||||
"dbEntriesSub": "Persisted (SQLite)",
|
||||
"cacheHitsSub": "of {total} total",
|
||||
"tokensSavedSub": "Estimated from hits",
|
||||
"autoRefresh": "Auto-refreshes every {seconds}s",
|
||||
"hits": "Hits",
|
||||
"misses": "Misses",
|
||||
"total": "Total",
|
||||
"behavior": "Cache Behavior",
|
||||
"behaviorDeterministic": "Only non-streaming requests with temperature=0 are cached.",
|
||||
"behaviorBypass": "Bypass with header {header}.",
|
||||
"behaviorTwoTier": "Two-tier storage: in-memory LRU (fast) + SQLite (persistent across restarts).",
|
||||
"behaviorTtl": "Default TTL: 30 minutes. Configure via {envVar}.",
|
||||
"idempotency": "Idempotency Layer",
|
||||
"activeDedupKeys": "Active Dedup Keys",
|
||||
"dedupWindow": "Dedup Window",
|
||||
"inputTokens": "Input Tokens",
|
||||
"clearSuccess": "Cache cleared. {count} expired entries removed.",
|
||||
"clearError": "Failed to clear cache.",
|
||||
"unavailable": "Cache unavailable",
|
||||
"unavailableDesc": "Could not fetch cache statistics. Make sure the server is running.",
|
||||
"promptCache": "Prompt Cache (Provider-Side)",
|
||||
"cachedRequests": "Cached Requests",
|
||||
"cacheHitRate": "Cache Hit Rate",
|
||||
"cachedTokens": "Cached Tokens",
|
||||
"cacheCreationTokens": "Cache Creation Tokens",
|
||||
"cacheMetrics": "Prompt Cache Metrics",
|
||||
"withCacheControl": "With Cache Control",
|
||||
"cachedTokensRead": "Cached Tokens (Read)",
|
||||
"cacheCreationWrite": "Cache Creation (Write)",
|
||||
"cacheReuseRatio": "Cache Reuse Ratio",
|
||||
"cacheReuseRatioDesc": "Cached tokens / Total input tokens",
|
||||
"estCostSaved": "Est. Cost Saved",
|
||||
"requestsShort": "reqs",
|
||||
"inputShort": "In",
|
||||
"resetting": "Resetting...",
|
||||
"cachedTokensCol": "Cached",
|
||||
"search": "Search",
|
||||
"cacheReuseRatioDesc": "Cached tokens / Total input tokens",
|
||||
"resetMetrics": "Reset Metrics",
|
||||
"loading": "Loading...",
|
||||
"cachedRequests": "Cached Requests",
|
||||
"model": "Model",
|
||||
"cached": "Cached",
|
||||
"actions": "Actions",
|
||||
"trend24h": "Cache Trend (24h)",
|
||||
"cachedShort": "Cached",
|
||||
"cacheCreation": "Creation",
|
||||
"byProvider": "Breakdown by Provider",
|
||||
"created": "Created",
|
||||
"cacheCreationTokens": "Cache Creation Tokens",
|
||||
"withCacheControl": "With Cache Control",
|
||||
"searchEntries": "Search entries...",
|
||||
"cacheReuseRatio": "Cache Reuse Ratio",
|
||||
"estCostSaved": "Est. Cost Saved",
|
||||
"cachedTokens": "Cached Tokens",
|
||||
"requests": "Requests",
|
||||
"signature": "Signature",
|
||||
"cacheCreationWrite": "Cache Creation (Write)",
|
||||
"expires": "Expires",
|
||||
"writeShort": "Write",
|
||||
"cacheHitRate": "Cache Hit Rate",
|
||||
"cacheMetrics": "Prompt Cache Metrics",
|
||||
"overview": "Overview",
|
||||
"promptCache": "Prompt Cache (Provider-Side)",
|
||||
"resetting": "Resetting...",
|
||||
"resetMetrics": "Reset Metrics",
|
||||
"byProvider": "Breakdown by Provider",
|
||||
"provider": "Provider",
|
||||
"cachedTokensRead": "Cached Tokens (Read)",
|
||||
"requests": "Requests",
|
||||
"inputTokens": "Input Tokens",
|
||||
"cachedTokensCol": "Cached",
|
||||
"cacheCreation": "Creation",
|
||||
"trend24h": "Cache Trend (24h)",
|
||||
"cached": "Cached",
|
||||
"overview": "Overview",
|
||||
"entries": "Entries",
|
||||
"noEntries": "No cache entries found"
|
||||
"searchEntries": "Search entries...",
|
||||
"search": "Search",
|
||||
"loading": "Loading...",
|
||||
"noEntries": "No cache entries found",
|
||||
"signature": "Signature",
|
||||
"model": "Model",
|
||||
"created": "Created",
|
||||
"expires": "Expires",
|
||||
"actions": "Actions"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -143,6 +143,7 @@
|
||||
"dashboard": "Dashboard",
|
||||
"providers": "Anbieter",
|
||||
"combos": "Kombinationen",
|
||||
"autoCombo": "Auto Combo",
|
||||
"usage": "Nutzung",
|
||||
"analytics": "Analytik",
|
||||
"costs": "Kosten",
|
||||
@@ -152,8 +153,12 @@
|
||||
"media": "Medien",
|
||||
"settings": "Einstellungen",
|
||||
"translator": "Übersetzer",
|
||||
"playground": "Spielwiese",
|
||||
"searchTools": "Search Tools",
|
||||
"agents": "Agenten",
|
||||
"docs": "Dokumente",
|
||||
"issues": "Probleme",
|
||||
"endpoints": "Endpunkte",
|
||||
"apiManager": "API-Manager",
|
||||
"logs": "Protokolle",
|
||||
"auditLog": "Audit-Protokoll",
|
||||
@@ -165,6 +170,11 @@
|
||||
"debug": "Debuggen",
|
||||
"system": "System",
|
||||
"help": "Hilfe",
|
||||
"primarySection": "Main",
|
||||
"cliSection": "CLI",
|
||||
"debugSection": "Debug",
|
||||
"systemSection": "System",
|
||||
"helpSection": "Help",
|
||||
"serverDisconnected": "Serververbindung getrennt",
|
||||
"serverDisconnectedMsg": "Der Proxyserver wurde gestoppt oder wird neu gestartet.",
|
||||
"expandSidebar": "Seitenleiste erweitern",
|
||||
@@ -180,19 +190,9 @@
|
||||
"themeViolet": "Violett",
|
||||
"themeOrange": "Orange",
|
||||
"themeCyan": "Cyan",
|
||||
"endpoints": "Endpunkte",
|
||||
"playground": "Spielwiese",
|
||||
"agents": "Agenten",
|
||||
"cliToolsShort": "Werkzeuge",
|
||||
"autoCombo": "Auto Combo",
|
||||
"searchTools": "Search Tools",
|
||||
"cache": "Cache",
|
||||
"cacheShort": "Cache",
|
||||
"cliSection": "CLI",
|
||||
"debugSection": "Debug",
|
||||
"helpSection": "Help",
|
||||
"primarySection": "Main",
|
||||
"systemSection": "System"
|
||||
"cacheShort": "Cache"
|
||||
},
|
||||
"themesPage": {
|
||||
"title": "Themen",
|
||||
@@ -220,6 +220,10 @@
|
||||
"homeDescription": "Willkommen bei OmniRoute",
|
||||
"endpoint": "Endpunkte",
|
||||
"endpointDescription": "Proxy-Endpunkte, MCP, A2A und API-Endpunkte verwalten",
|
||||
"mcp": "MCP",
|
||||
"mcpDescription": "Model Context Protocol server management and tools",
|
||||
"a2a": "A2A",
|
||||
"a2aDescription": "Agent-to-Agent protocol tasks and observability",
|
||||
"settings": "Einstellungen",
|
||||
"settingsDescription": "Verwalten Sie Ihre Präferenzen",
|
||||
"openaiCompatible": "OpenAI-kompatibel",
|
||||
@@ -227,11 +231,7 @@
|
||||
"media": "Medien",
|
||||
"mediaDescription": "Generieren Sie Bilder, Videos und Musik",
|
||||
"themes": "Themen",
|
||||
"themesDescription": "Wählen Sie ein Farbthema für das gesamte Dashboard-Panel",
|
||||
"mcp": "MCP",
|
||||
"mcpDescription": "Model Context Protocol server management and tools",
|
||||
"a2a": "A2A",
|
||||
"a2aDescription": "Agent-to-Agent protocol tasks and observability"
|
||||
"themesDescription": "Wählen Sie ein Farbthema für das gesamte Dashboard-Panel"
|
||||
},
|
||||
"home": {
|
||||
"quickStart": "Schnellstart",
|
||||
@@ -332,6 +332,26 @@
|
||||
"keyCreatedNote": "Kopieren und speichern Sie diesen Schlüssel jetzt – er wird nicht mehr angezeigt.",
|
||||
"done": "Fertig",
|
||||
"savePermissions": "Berechtigungen speichern",
|
||||
"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",
|
||||
"allowAll": "Alle zulassen",
|
||||
"restrict": "Einschränken",
|
||||
"allowAllInfo": "Mit diesem Schlüssel kann auf alle verfügbaren Modelle zugegriffen werden.",
|
||||
@@ -356,6 +376,7 @@
|
||||
"failedUpdatePermissionsRetry": "Die Berechtigungen konnten nicht aktualisiert werden. Bitte versuchen Sie es erneut.",
|
||||
"unknownProvider": "unbekannt",
|
||||
"copyMaskedKey": "Maskierten Schlüssel kopieren",
|
||||
"keyOnlyAvailableAtCreation": "Full key available only at creation time — copy it when you first create the key",
|
||||
"modelsCount": "{count, plural, one {# model} other {# models}}",
|
||||
"lastUsedOn": "Zuletzt: {date}",
|
||||
"editPermissions": "Berechtigungen bearbeiten",
|
||||
@@ -365,28 +386,7 @@
|
||||
"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",
|
||||
"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"
|
||||
"selectedCount": "{count} ausgewählt"
|
||||
},
|
||||
"auditLog": {
|
||||
"title": "Audit-Protokoll",
|
||||
@@ -425,6 +425,57 @@
|
||||
"videoDescription": "Create videos with AnimateDiff, Stable Video Diffusion via ComfyUI or SD WebUI.",
|
||||
"musicDescription": "Compose music using Stable Audio Open or MusicGen via ComfyUI."
|
||||
},
|
||||
"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",
|
||||
"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",
|
||||
"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"
|
||||
},
|
||||
"cliTools": {
|
||||
"title": "CLI-Tools",
|
||||
"noActiveProviders": "Keine aktiven Anbieter",
|
||||
@@ -497,6 +548,11 @@
|
||||
"antigravityStep2Prefix": "2. Hinzufügen",
|
||||
"antigravityStep2Suffix": "zu Ihrer Hosts-Datei als 127.0.0.1 hinzufügen.",
|
||||
"antigravityStep3": "3. Öffnen Sie Antigravity und Anfragen werden weitergeleitet.",
|
||||
"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.",
|
||||
"sudoPasswordRequiredTitle": "Sudo-Passwort erforderlich",
|
||||
"sudoPasswordHint": "Zum Ändern der Hosts-Datei und der System-Proxy-Einstellungen ist ein Administratorkennwort erforderlich.",
|
||||
"enterSudoPassword": "Geben Sie das Sudo-Passwort ein",
|
||||
@@ -569,6 +625,22 @@
|
||||
"openClawManualConfiguration": "Open Claw – Manuelle Konfiguration",
|
||||
"clineManualConfiguration": "Manuelle Cline-Konfiguration",
|
||||
"kiloManualConfiguration": "Manuelle Konfiguration des Kilo-Codes",
|
||||
"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."
|
||||
},
|
||||
"toolDescriptions": {
|
||||
"antigravity": "Google Antigravity IDE mit MITM",
|
||||
"claude": "Anthropic Claude Code CLI",
|
||||
@@ -706,27 +778,6 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"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": {
|
||||
@@ -988,7 +1039,12 @@
|
||||
"settingsApi": "Settings API",
|
||||
"categoryCore": "Kern-APIs",
|
||||
"categoryMedia": "Medien & Multi-Modal",
|
||||
"categorySearch": "Search & Discovery",
|
||||
"categoryUtility": "Hilfsmittel & Verwaltung",
|
||||
"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.",
|
||||
"enableCloudTitle": "Aktivieren Sie Cloud Proxy",
|
||||
"whatYouGet": "Was Sie bekommen werden",
|
||||
"cloudBenefitAccess": "Greifen Sie von überall auf der Welt auf Ihre API zu",
|
||||
@@ -1021,6 +1077,25 @@
|
||||
"cloudWorkerUnreachable": "Der Cloud-Worker konnte nicht erreicht werden. Stellen Sie sicher, dass der Cloud-Dienst ausgeführt wird (npm run dev in /cloud).",
|
||||
"connectionFailed": "Verbindung fehlgeschlagen",
|
||||
"syncFailed": "Cloud-Daten konnten nicht synchronisiert werden",
|
||||
"cloudflaredTitle": "Cloudflare Quick Tunnel",
|
||||
"cloudflaredDescription": "Create a Cloudflare Quick Tunnel for this endpoint to make it accessible from the internet without configuring DNS.",
|
||||
"cloudflaredUrlNotice": "Creates a temporary Cloudflare Quick Tunnel. URLs change after restart.",
|
||||
"cloudflaredEnable": "Enable Tunnel",
|
||||
"cloudflaredInstallAndEnable": "Install & Enable",
|
||||
"cloudflaredDisable": "Stop Tunnel",
|
||||
"cloudflaredRunning": "Running",
|
||||
"cloudflaredStarting": "Starting",
|
||||
"cloudflaredStoppedState": "Stopped",
|
||||
"cloudflaredNotInstalled": "Not installed",
|
||||
"cloudflaredUnsupported": "Unsupported",
|
||||
"cloudflaredError": "Error",
|
||||
"cloudflaredStarted": "Cloudflare tunnel started",
|
||||
"cloudflaredStopped": "Cloudflare tunnel stopped",
|
||||
"cloudflaredRequestFailed": "Failed to update Cloudflare tunnel",
|
||||
"cloudflaredTemporaryNote": "Quick Tunnel URLs are temporary and will change after restart.",
|
||||
"cloudflaredUnsupportedNote": "This platform is not supported for managed installation.",
|
||||
"cloudflaredIdleNote": "Create a temporary Cloudflare Quick Tunnel for this endpoint.",
|
||||
"cloudflaredLastError": "Last error: {error}",
|
||||
"providerModelsTitle": "{provider} – Modelle",
|
||||
"noModelsForProvider": "Für diesen Anbieter sind keine Modelle verfügbar.",
|
||||
"chat": "Chatten",
|
||||
@@ -1055,31 +1130,19 @@
|
||||
"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",
|
||||
"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.",
|
||||
"cloudflaredDescription": "Create a Cloudflare Quick Tunnel for this endpoint to make it accessible from the internet without configuring DNS.",
|
||||
"cloudflaredDisable": "Stop Tunnel",
|
||||
"cloudflaredEnable": "Enable Tunnel",
|
||||
"cloudflaredError": "Error",
|
||||
"cloudflaredIdleNote": "Create a temporary Cloudflare Quick Tunnel for this endpoint.",
|
||||
"cloudflaredInstallAndEnable": "Install & Enable",
|
||||
"cloudflaredLastError": "Last error: {error}",
|
||||
"cloudflaredNotInstalled": "Not installed",
|
||||
"cloudflaredRequestFailed": "Failed to update Cloudflare tunnel",
|
||||
"cloudflaredRunning": "Running",
|
||||
"cloudflaredStarted": "Cloudflare tunnel started",
|
||||
"cloudflaredStarting": "Starting",
|
||||
"cloudflaredStopped": "Cloudflare tunnel stopped",
|
||||
"cloudflaredStoppedState": "Stopped",
|
||||
"cloudflaredTemporaryNote": "Quick Tunnel URLs are temporary and will change after restart.",
|
||||
"cloudflaredTitle": "Cloudflare Quick Tunnel",
|
||||
"cloudflaredUnsupported": "Unsupported",
|
||||
"cloudflaredUnsupportedNote": "This platform is not supported for managed installation.",
|
||||
"cloudflaredUrlNotice": "Creates a temporary Cloudflare Quick Tunnel. URLs change after restart."
|
||||
"completionsLegacyDesc": "Legacy OpenAI text completions — accepts both prompt string and messages array format"
|
||||
},
|
||||
"endpoints": {
|
||||
"tabProxy": "Endpoint Proxy",
|
||||
"tabApiEndpoints": "API-Endpunkte",
|
||||
"apiEndpointsTitle": "API-Endpunkte",
|
||||
"apiEndpointsDescription": "Backend-API-Endpunkte, die von anderen Anwendungen und Diensten genutzt werden können.",
|
||||
"comingSoon": "Demnächst",
|
||||
"plannedFeatures": "Geplante Funktionen",
|
||||
"featureRestApi": "REST-API-Katalog mit interaktiver Dokumentation",
|
||||
"featureWebhooks": "Webhook-Konfiguration und Event-Abonnements",
|
||||
"featureSwagger": "Automatische OpenAPI / Swagger-Spezifikation",
|
||||
"featureAuth": "API-Schlüssel- und OAuth-Scope-Verwaltung pro Endpunkt"
|
||||
},
|
||||
"mcpDashboard": {
|
||||
"loading": "MCP-Dashboard wird geladen...",
|
||||
@@ -1251,6 +1314,12 @@
|
||||
"issuesLabel": "Erkannte Probleme",
|
||||
"operational": "Betriebsbereit",
|
||||
"providers": "Anbieter",
|
||||
"configuredProvidersLabel": "Im Dashboard konfiguriert",
|
||||
"configuredProvidersHint": "Anbieter mit in /dashboard/providers gespeicherten Anmeldeinformationen, unabhängig vom Laufzeitstatus.",
|
||||
"activeProviders": "{count} active",
|
||||
"activeProvidersHint": "Konfigurierte Anbieter, die derzeit für Routing-Anfragen aktiviert sind.",
|
||||
"monitoredProviders": "{count} monitored",
|
||||
"monitoredProvidersHint": "Anbieter, die derzeit von Leistungsschalter-Zustandsmonitoren überwacht werden.",
|
||||
"healthyCount": "{count} gesund",
|
||||
"nodeVersion": "Knoten {version}",
|
||||
"failures": "{count} Fehler",
|
||||
@@ -1269,13 +1338,7 @@
|
||||
"resetAllTitle": "Setzen Sie alle Leistungsschalter in den fehlerfreien Zustand zurück",
|
||||
"resetting": "Zurücksetzen...",
|
||||
"resetAll": "Alles zurücksetzen",
|
||||
"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."
|
||||
"until": "Bis {time}"
|
||||
},
|
||||
"limits": {
|
||||
"title": "Limits und Quoten",
|
||||
@@ -1437,6 +1500,7 @@
|
||||
"clearing": "Löschen...",
|
||||
"until": "Bis {time}",
|
||||
"providerTestFailed": "Anbietertest fehlgeschlagen",
|
||||
"providerTestTimeout": "Provider test timed out — too many connections to test at once",
|
||||
"modeTest": "{mode} Test",
|
||||
"passedCount": "{count} bestanden",
|
||||
"failedCount": "{count} fehlgeschlagen",
|
||||
@@ -1478,6 +1542,14 @@
|
||||
"chatCompletions": "Chat-Abschlüsse",
|
||||
"importingModels": "Importieren...",
|
||||
"importFromModels": "Import aus /models",
|
||||
"allModelsAlreadyImported": "Alle Modelle sind bereits importiert",
|
||||
"noNewModelsToImport": "Keine neuen Modelle zum Importieren — alle Modelle sind bereits in der Registry oder der Liste benutzerdefinierter Modelle",
|
||||
"skippingExistingModels": "Überspringe {count} vorhandene Modelle",
|
||||
"autoSync": "Auto-Sync",
|
||||
"autoSyncTooltip": "Modellliste automatisch alle 24 Stunden aktualisieren (konfigurierbar über MODEL_SYNC_INTERVAL_HOURS)",
|
||||
"autoSyncEnabled": "Auto-Sync aktiviert — Modelle werden regelmäßig aktualisiert",
|
||||
"autoSyncDisabled": "Auto-Sync deaktiviert",
|
||||
"autoSyncToggleFailed": "Auto-Sync umschalten fehlgeschlagen",
|
||||
"clearAllModels": "Alle Modelle löschen",
|
||||
"clearAllModelsConfirm": "Möchten Sie wirklich alle Modelle für diesen Anbieter löschen?",
|
||||
"clearAllModelsSuccess": "Alle Modelle gelöscht",
|
||||
@@ -1531,6 +1603,8 @@
|
||||
"addFirstConnectionHint": "Fügen Sie Ihre erste Verbindung hinzu, um loszulegen",
|
||||
"addConnection": "Verbindung hinzufügen",
|
||||
"availableModels": "Verfügbare Modelle",
|
||||
"builtInModels": "Built-in models",
|
||||
"builtInModelsHint": "Registry models for this provider. Use the pencil to set compatibility options.",
|
||||
"pageAutoRefresh": "Die Seite wird automatisch aktualisiert...",
|
||||
"statusDisabled": "deaktiviert",
|
||||
"statusConnected": "verbunden",
|
||||
@@ -1571,6 +1645,26 @@
|
||||
"openRouterModelPlaceholder": "anthropisch/claude-3-opus",
|
||||
"customModels": "Benutzerdefinierte Modelle",
|
||||
"customModelsHint": "Fügen Sie Modell-IDs hinzu, die nicht in der Standardliste enthalten sind. Diese stehen für das Routing zur Verfügung.",
|
||||
"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",
|
||||
"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": "Modell-ID",
|
||||
"customModelPlaceholder": "z.B. gpt-4,5-turbo",
|
||||
"loading": "Laden...",
|
||||
@@ -1614,52 +1708,21 @@
|
||||
"compatibleBaseUrlHint": "Root URL of your {type}-compatible API. Use Advanced Settings for custom endpoint paths.",
|
||||
"apiKeyForCheck": "API-Schlüssel (zur Überprüfung)",
|
||||
"compatibleProdPlaceholder": "{type} Kompatibel (Prod)",
|
||||
"providerTestTimeout": "Provider test timed out — too many connections to test at once",
|
||||
"tokenRefreshed": "Token refreshed successfully",
|
||||
"tokenRefreshFailed": "Token refresh failed",
|
||||
"applyCodexAuthLocal": "Apply auth",
|
||||
"exportCodexAuthFile": "Export auth",
|
||||
"codexAuthAppliedLocal": "Codex auth.json applied locally",
|
||||
"codexAuthApplyFailed": "Failed to apply Codex auth.json locally",
|
||||
"codexAuthExported": "Codex auth.json exported",
|
||||
"codexAuthExportFailed": "Failed to export Codex auth.json",
|
||||
"advancedSettings": "Advanced Settings",
|
||||
"chatPathLabel": "Chat Endpoint Path",
|
||||
"chatPathPlaceholder": "/chat/completions",
|
||||
"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)",
|
||||
"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",
|
||||
"autoSync": "Auto-Sync",
|
||||
"autoSyncTooltip": "Modellliste automatisch alle 24 Stunden aktualisieren (konfigurierbar über MODEL_SYNC_INTERVAL_HOURS)",
|
||||
"autoSyncEnabled": "Auto-Sync aktiviert — Modelle werden regelmäßig aktualisiert",
|
||||
"autoSyncDisabled": "Auto-Sync deaktiviert",
|
||||
"autoSyncToggleFailed": "Auto-Sync umschalten fehlgeschlagen",
|
||||
"allModelsAlreadyImported": "Alle Modelle sind bereits importiert",
|
||||
"noNewModelsToImport": "Keine neuen Modelle zum Importieren — alle Modelle sind bereits in der Registry oder der Liste benutzerdefinierter Modelle",
|
||||
"skippingExistingModels": "Überspringe {count} vorhandene Modelle",
|
||||
"applyCodexAuthLocal": "Apply auth",
|
||||
"codexAuthAppliedLocal": "Codex auth.json applied locally",
|
||||
"codexAuthApplyFailed": "Failed to apply Codex auth.json locally",
|
||||
"codexAuthExportFailed": "Failed to export Codex auth.json",
|
||||
"codexAuthExported": "Codex auth.json exported",
|
||||
"exportCodexAuthFile": "Export auth"
|
||||
"modelsPathHint": "Custom models path for validation (e.g. /v4/models)"
|
||||
},
|
||||
"settings": {
|
||||
"title": "Einstellungen",
|
||||
@@ -1683,6 +1746,8 @@
|
||||
"darkMode": "Dunkler Modus",
|
||||
"lightMode": "Lichtmodus",
|
||||
"systemTheme": "Systemthema",
|
||||
"debugToggle": "Enable Debug Mode",
|
||||
"sidebarVisibilityToggle": "Show Sidebar Items",
|
||||
"enableCache": "Cache aktivieren",
|
||||
"cacheTTL": "Cache-TTL",
|
||||
"maxCacheSize": "Maximale Cache-Größe",
|
||||
@@ -1691,6 +1756,17 @@
|
||||
"cacheMisses": "Cache-Fehler",
|
||||
"hitRate": "Trefferquote",
|
||||
"cacheEntries": "Cache-Einträge",
|
||||
"cacheSettings": "Cache Settings",
|
||||
"semanticCache": "Semantic Cache",
|
||||
"maxEntries": "Max Entries",
|
||||
"ttlMinutes": "TTL (minutes)",
|
||||
"promptCache": "Prompt-Cache",
|
||||
"strategy": "Strategy",
|
||||
"preserveClientCache": "Preserve Client Cache",
|
||||
"enabled": "Enabled",
|
||||
"loading": "Loading...",
|
||||
"saving": "Sparen...",
|
||||
"save": "Save",
|
||||
"circuitBreaker": "Leistungsschalter",
|
||||
"retryPolicy": "Wiederholungsrichtlinie",
|
||||
"maxRetries": "Max. Wiederholungsversuche",
|
||||
@@ -1698,6 +1774,10 @@
|
||||
"timeoutMs": "Timeout (ms)",
|
||||
"enableSystemPrompt": "Aktivieren Sie die Systemaufforderung",
|
||||
"systemPromptText": "Systemaufforderungstext",
|
||||
"autoDisableBannedAccounts": "Auto-Disable Banned Accounts",
|
||||
"autoDisableDescription": "Permanently mark provider connections as deactivated if they return specific terminal ban signals (e.g. HTTP 403 'verify your account'). This removes them from the combo rotation.",
|
||||
"autoDisableThreshold": "Ban Threshold",
|
||||
"autoDisableThresholdDesc": "Consecutive ban signals required before permanent deactivation.",
|
||||
"enableThinking": "Denken aktivieren",
|
||||
"maxThinkingTokens": "Max Thinking Tokens",
|
||||
"enableProxy": "Proxy aktivieren",
|
||||
@@ -1734,6 +1814,9 @@
|
||||
"themeLight": "Licht",
|
||||
"themeDark": "Dunkel",
|
||||
"themeSystem": "System",
|
||||
"sidebarVisibility": "Hide sidebar items",
|
||||
"sidebarVisibilityDesc": "Hide any sidebar navigation entry to reduce visual clutter.",
|
||||
"sidebarVisibilityHint": "Any sidebar section is hidden automatically when a...",
|
||||
"hideHealthLogs": "Gesundheitsprüfungsprotokolle ausblenden",
|
||||
"hideHealthLogsDesc": "Wenn diese Option aktiviert ist, werden [HealthCheck]-Meldungen in der Serverkonsole unterdrückt",
|
||||
"themeAccent": "Themenfarbe",
|
||||
@@ -1746,7 +1829,20 @@
|
||||
"themeViolet": "Violett",
|
||||
"themeOrange": "Orange",
|
||||
"themeCyan": "Cyan",
|
||||
"promptCache": "Prompt-Cache",
|
||||
"whitelabeling": "Branding",
|
||||
"whitelabelingDesc": "Customize the application name and logo",
|
||||
"appName": "Application Name",
|
||||
"appNameDesc": "Display name shown in sidebar and browser tab",
|
||||
"customLogo": "Custom Logo URL",
|
||||
"customLogoDesc": "URL to your custom logo image",
|
||||
"uploadLogo": "Upload Logo",
|
||||
"resetLogo": "Reset to Default",
|
||||
"logoPreview": "Preview",
|
||||
"customFavicon": "Browser Favicon",
|
||||
"customFaviconDesc": "URL to your custom favicon (shown in browser tab)",
|
||||
"uploadFavicon": "Upload Favicon",
|
||||
"resetFavicon": "Reset Favicon",
|
||||
"faviconPreview": "Favicon Preview",
|
||||
"flushCache": "Cache leeren",
|
||||
"flushing": "Spülen…",
|
||||
"size": "Größe",
|
||||
@@ -1804,7 +1900,15 @@
|
||||
"providersBlocked": "{count} Anbieter von /models blockiert",
|
||||
"blockProviderTitle": "Blockieren Sie {provider}",
|
||||
"unblockProviderTitle": "Entsperren Sie {provider}",
|
||||
"cliFingerprint": "CLI Fingerprint Matching",
|
||||
"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}",
|
||||
"routingStrategy": "Routing-Strategie",
|
||||
"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.",
|
||||
"fillFirst": "Zuerst füllen",
|
||||
"fillFirstDesc": "Verwenden Sie Konten in der Reihenfolge ihrer Priorität",
|
||||
"roundRobin": "Round Robin",
|
||||
@@ -1899,6 +2003,9 @@
|
||||
"fillModelAndProviders": "Bitte geben Sie Modellnamen und Anbieter ein",
|
||||
"addAtLeastOneProvider": "Fügen Sie mindestens einen Anbieter hinzu",
|
||||
"comboDefaultsTitle": "Combo-Standardeinstellungen",
|
||||
"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.",
|
||||
"globalComboConfig": "Globale Combo-Konfiguration",
|
||||
"defaultStrategy": "Standardstrategie",
|
||||
"defaultStrategyDesc": "Wird auf neue Combos ohne explizite Strategie angewendet",
|
||||
@@ -2026,7 +2133,6 @@
|
||||
"unsaved": "nicht gespeichert",
|
||||
"resetDefaults": "Standardeinstellungen zurücksetzen",
|
||||
"saveProvider": "Anbieter speichern",
|
||||
"saving": "Sparen...",
|
||||
"model": "Modell",
|
||||
"models": "Modelle",
|
||||
"moreProviders": "{count} weitere Anbieter",
|
||||
@@ -2057,36 +2163,7 @@
|
||||
"customPricingNote": "Sie können die Standardpreise für bestimmte Modelle überschreiben. Benutzerdefinierte Überschreibungen haben Vorrang vor automatisch erkannten Preisen.",
|
||||
"editPricing": "Preise bearbeiten",
|
||||
"viewFullDetails": "Vollständige Details anzeigen",
|
||||
"themeCoral": "Koralle",
|
||||
"cliFingerprint": "CLI Fingerprint Matching",
|
||||
"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}",
|
||||
"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.",
|
||||
"sidebarVisibility": "Hide sidebar items",
|
||||
"sidebarVisibilityDesc": "Hide any sidebar navigation entry to reduce visual clutter.",
|
||||
"sidebarVisibilityHint": "Any sidebar section is hidden automatically when a...",
|
||||
"semanticCache": "Semantic Cache",
|
||||
"autoDisableDescription": "Permanently mark provider connections as deactivated if they return specific terminal ban signals (e.g. HTTP 403 'verify your account'). This removes them from the combo rotation.",
|
||||
"preserveClientCache": "Preserve Client Cache",
|
||||
"cacheSettings": "Cache Settings",
|
||||
"autoDisableThreshold": "Ban Threshold",
|
||||
"autoDisableBannedAccounts": "Auto-Disable Banned Accounts",
|
||||
"ttlMinutes": "TTL (minutes)",
|
||||
"maxEntries": "Max Entries",
|
||||
"loading": "Loading...",
|
||||
"save": "Save",
|
||||
"autoDisableThresholdDesc": "Consecutive ban signals required before permanent deactivation.",
|
||||
"debugToggle": "Enable Debug Mode",
|
||||
"sidebarVisibilityToggle": "Show Sidebar Items",
|
||||
"enabled": "Enabled",
|
||||
"strategy": "Strategy"
|
||||
"themeCoral": "Koralle"
|
||||
},
|
||||
"translator": {
|
||||
"title": "Übersetzer",
|
||||
@@ -2505,14 +2582,14 @@
|
||||
"backToLogin": "Zurück zum Anmelden",
|
||||
"forgotPassword": "Passwort vergessen?",
|
||||
"defaultPasswordHint": "Default password: 123456 (unless INITIAL_PASSWORD was set)",
|
||||
"Authorization": "Autorisierung",
|
||||
"Content-Disposition": "Inhaltliche Disposition",
|
||||
"waitingForAuthorization": "Warte auf Autorisierung...",
|
||||
"waitingForGoogleAuthorization": "Warte auf Google-Autorisierung...",
|
||||
"waitingForOpenAIAuthorization": "Warte auf OpenAI-Autorisierung...",
|
||||
"waitingForAntigravityAuthorization": "Warte auf Antigravity-Autorisierung...",
|
||||
"waitingForQoderAuthorization": "Warte auf Qoder-Autorisierung...",
|
||||
"exchangingCodeForTokens": "Tausche Code gegen Token aus...",
|
||||
"Authorization": "Autorisierung",
|
||||
"Content-Disposition": "Inhaltliche Disposition"
|
||||
"exchangingCodeForTokens": "Tausche Code gegen Token aus..."
|
||||
},
|
||||
"landing": {
|
||||
"brandName": "OmniRoute",
|
||||
@@ -2606,6 +2683,8 @@
|
||||
"clientCompatibility": "Client-Kompatibilität",
|
||||
"protocolsToc": "Protokolle",
|
||||
"apiReference": "API-Referenz",
|
||||
"managementApiReference": "Management API Reference",
|
||||
"managementApiDescription": "Automation endpoints for proxy registry, scope assignments, and legacy proxy migration.",
|
||||
"method": "Methode",
|
||||
"path": "Pfad",
|
||||
"notes": "Notizen",
|
||||
@@ -2697,10 +2776,19 @@
|
||||
"endpointResponsesNote": "Antwort-API-Endpunkt (Codex, O-Serie).",
|
||||
"endpointModelsNote": "Musterkatalog für alle angeschlossenen Anbieter.",
|
||||
"endpointAudioNote": "Audiotranskription (Deepgram, AssemblyAI).",
|
||||
"endpointSpeechNote": "Sprachsynthese (ElevenLabs, OpenAI TTS).",
|
||||
"endpointEmbeddingsNote": "Text-Embedding-Generierung (OpenAI, Cohere, Voyage).",
|
||||
"endpointImagesNote": "Bilderzeugung (NanoBanana).",
|
||||
"endpointRewriteChatNote": "Rewrite-Helper für Clients ohne /v1.",
|
||||
"endpointRewriteResponsesNote": "Hilfsprogramm für Antworten ohne /v1 umschreiben.",
|
||||
"endpointRewriteModelsNote": "Hilfsprogramm für die Modellerkennung ohne /v1 umschreiben.",
|
||||
"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.",
|
||||
"modelPrefixesDescriptionStart": "Verwenden Sie das Anbieterpräfix vor dem Modellnamen, um an einen bestimmten Anbieter weiterzuleiten. Beispiel:",
|
||||
"modelPrefixesDescriptionEnd": "Routen zu GitHub Copilot.",
|
||||
"provider": "Anbieter",
|
||||
@@ -2710,18 +2798,7 @@
|
||||
"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.",
|
||||
"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.",
|
||||
"endpointSpeechNote": "Sprachsynthese (ElevenLabs, OpenAI TTS).",
|
||||
"endpointEmbeddingsNote": "Text-Embedding-Generierung (OpenAI, Cohere, Voyage)."
|
||||
"troubleshootingOAuth": "Führen Sie bei OAuth-Anbietern eine erneute Authentifizierung durch, wenn die Token ablaufen. Überprüfen Sie die Statusanzeige der Anbieterkarte."
|
||||
},
|
||||
"legal": {
|
||||
"privacyPolicy": "Datenschutzrichtlinie",
|
||||
@@ -2783,18 +2860,6 @@
|
||||
"termsSection6Title": "6. Open Source",
|
||||
"termsSection6Text": "OmniRoute ist Open-Source-Software. Es steht Ihnen frei, es im Rahmen der Lizenzbedingungen zu prüfen, zu ändern und zu verbreiten."
|
||||
},
|
||||
"endpoints": {
|
||||
"tabProxy": "Endpoint Proxy",
|
||||
"tabApiEndpoints": "API-Endpunkte",
|
||||
"apiEndpointsTitle": "API-Endpunkte",
|
||||
"apiEndpointsDescription": "Backend-API-Endpunkte, die von anderen Anwendungen und Diensten genutzt werden können.",
|
||||
"comingSoon": "Demnächst",
|
||||
"plannedFeatures": "Geplante Funktionen",
|
||||
"featureRestApi": "REST-API-Katalog mit interaktiver Dokumentation",
|
||||
"featureWebhooks": "Webhook-Konfiguration und Event-Abonnements",
|
||||
"featureSwagger": "Automatische OpenAPI / Swagger-Spezifikation",
|
||||
"featureAuth": "API-Schlüssel- und OAuth-Scope-Verwaltung pro Endpunkt"
|
||||
},
|
||||
"agents": {
|
||||
"title": "CLI Agents",
|
||||
"description": "Discover installed CLI agents on your system. Add custom agents for auto-detection.",
|
||||
@@ -2849,57 +2914,6 @@
|
||||
"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"
|
||||
},
|
||||
"templateNames": {
|
||||
"simple-chat": "Einfacher Chat",
|
||||
"streaming": "Streaming",
|
||||
@@ -2949,67 +2963,67 @@
|
||||
"refresh": "Refresh",
|
||||
"clearAll": "Clear All",
|
||||
"memoryEntries": "Memory Entries",
|
||||
"memoryEntriesSub": "In-memory LRU",
|
||||
"dbEntries": "DB Entries",
|
||||
"dbEntriesSub": "Persisted (SQLite)",
|
||||
"cacheHits": "Cache Hits",
|
||||
"cacheHitsSub": "of {total} total",
|
||||
"tokensSaved": "Tokens Saved",
|
||||
"tokensSavedSub": "Estimated from hits",
|
||||
"hitRate": "Hit Rate",
|
||||
"performance": "Cache Performance",
|
||||
"behavior": "Cache Behavior",
|
||||
"idempotency": "Idempotency Layer",
|
||||
"clearSuccess": "Cache cleared. {count} expired entries removed.",
|
||||
"clearError": "Failed to clear cache.",
|
||||
"unavailable": "Cache unavailable",
|
||||
"unavailableDesc": "Could not fetch cache statistics. Make sure the server is running.",
|
||||
"memoryEntriesSub": "In-memory LRU",
|
||||
"dbEntriesSub": "Persisted (SQLite)",
|
||||
"cacheHitsSub": "of {total} total",
|
||||
"tokensSavedSub": "Estimated from hits",
|
||||
"autoRefresh": "Auto-refreshes every {seconds}s",
|
||||
"hits": "Hits",
|
||||
"misses": "Misses",
|
||||
"total": "Total",
|
||||
"behavior": "Cache Behavior",
|
||||
"behaviorDeterministic": "Only non-streaming requests with temperature=0 are cached.",
|
||||
"behaviorBypass": "Bypass with header {header}.",
|
||||
"behaviorTwoTier": "Two-tier storage: in-memory LRU (fast) + SQLite (persistent across restarts).",
|
||||
"behaviorTtl": "Default TTL: 30 minutes. Configure via {envVar}.",
|
||||
"idempotency": "Idempotency Layer",
|
||||
"activeDedupKeys": "Active Dedup Keys",
|
||||
"dedupWindow": "Dedup Window",
|
||||
"inputTokens": "Input Tokens",
|
||||
"clearSuccess": "Cache cleared. {count} expired entries removed.",
|
||||
"clearError": "Failed to clear cache.",
|
||||
"unavailable": "Cache unavailable",
|
||||
"unavailableDesc": "Could not fetch cache statistics. Make sure the server is running.",
|
||||
"promptCache": "Prompt Cache (Provider-Side)",
|
||||
"cachedRequests": "Cached Requests",
|
||||
"cacheHitRate": "Cache Hit Rate",
|
||||
"cachedTokens": "Cached Tokens",
|
||||
"cacheCreationTokens": "Cache Creation Tokens",
|
||||
"cacheMetrics": "Prompt Cache Metrics",
|
||||
"withCacheControl": "With Cache Control",
|
||||
"cachedTokensRead": "Cached Tokens (Read)",
|
||||
"cacheCreationWrite": "Cache Creation (Write)",
|
||||
"cacheReuseRatio": "Cache Reuse Ratio",
|
||||
"cacheReuseRatioDesc": "Cached tokens / Total input tokens",
|
||||
"estCostSaved": "Est. Cost Saved",
|
||||
"requestsShort": "reqs",
|
||||
"inputShort": "In",
|
||||
"resetting": "Resetting...",
|
||||
"cachedTokensCol": "Cached",
|
||||
"search": "Search",
|
||||
"cacheReuseRatioDesc": "Cached tokens / Total input tokens",
|
||||
"resetMetrics": "Reset Metrics",
|
||||
"loading": "Loading...",
|
||||
"cachedRequests": "Cached Requests",
|
||||
"model": "Model",
|
||||
"cached": "Cached",
|
||||
"actions": "Actions",
|
||||
"trend24h": "Cache Trend (24h)",
|
||||
"cachedShort": "Cached",
|
||||
"cacheCreation": "Creation",
|
||||
"byProvider": "Breakdown by Provider",
|
||||
"created": "Created",
|
||||
"cacheCreationTokens": "Cache Creation Tokens",
|
||||
"withCacheControl": "With Cache Control",
|
||||
"searchEntries": "Search entries...",
|
||||
"cacheReuseRatio": "Cache Reuse Ratio",
|
||||
"estCostSaved": "Est. Cost Saved",
|
||||
"cachedTokens": "Cached Tokens",
|
||||
"requests": "Requests",
|
||||
"signature": "Signature",
|
||||
"cacheCreationWrite": "Cache Creation (Write)",
|
||||
"expires": "Expires",
|
||||
"writeShort": "Write",
|
||||
"cacheHitRate": "Cache Hit Rate",
|
||||
"cacheMetrics": "Prompt Cache Metrics",
|
||||
"overview": "Overview",
|
||||
"promptCache": "Prompt Cache (Provider-Side)",
|
||||
"resetting": "Resetting...",
|
||||
"resetMetrics": "Reset Metrics",
|
||||
"byProvider": "Breakdown by Provider",
|
||||
"provider": "Provider",
|
||||
"cachedTokensRead": "Cached Tokens (Read)",
|
||||
"requests": "Requests",
|
||||
"inputTokens": "Input Tokens",
|
||||
"cachedTokensCol": "Cached",
|
||||
"cacheCreation": "Creation",
|
||||
"trend24h": "Cache Trend (24h)",
|
||||
"cached": "Cached",
|
||||
"overview": "Overview",
|
||||
"entries": "Entries",
|
||||
"noEntries": "No cache entries found"
|
||||
"searchEntries": "Search entries...",
|
||||
"search": "Search",
|
||||
"loading": "Loading...",
|
||||
"noEntries": "No cache entries found",
|
||||
"signature": "Signature",
|
||||
"model": "Model",
|
||||
"created": "Created",
|
||||
"expires": "Expires",
|
||||
"actions": "Actions"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -156,6 +156,8 @@
|
||||
"playground": "Playground",
|
||||
"searchTools": "Search Tools",
|
||||
"agents": "Agents",
|
||||
"memory": "Memory",
|
||||
"skills": "Skills",
|
||||
"docs": "Docs",
|
||||
"issues": "Issues",
|
||||
"endpoints": "Endpoints",
|
||||
@@ -1266,6 +1268,57 @@
|
||||
"events": "Events",
|
||||
"artifacts": "Artifacts"
|
||||
},
|
||||
"memory": {
|
||||
"title": "Memory Management",
|
||||
"description": "View and manage stored memory entries",
|
||||
"memories": "Memories",
|
||||
"totalEntries": "Total Entries",
|
||||
"tokensUsed": "Tokens Used",
|
||||
"hitRate": "Hit Rate",
|
||||
"loading": "Loading memories...",
|
||||
"noMemories": "No memories found",
|
||||
"search": "Search memories...",
|
||||
"allTypes": "All Types",
|
||||
"export": "Export",
|
||||
"import": "Import",
|
||||
"addMemory": "Add Memory",
|
||||
"type": "Type",
|
||||
"key": "Key",
|
||||
"content": "Content",
|
||||
"created": "Created",
|
||||
"actions": "Actions",
|
||||
"factual": "Factual",
|
||||
"episodic": "Episodic",
|
||||
"procedural": "Procedural",
|
||||
"semantic": "Semantic"
|
||||
},
|
||||
"skills": {
|
||||
"title": "Skills",
|
||||
"description": "Manage and monitor AI skills",
|
||||
"skillsTab": "Skills",
|
||||
"executionsTab": "Executions",
|
||||
"sandboxTab": "Sandbox",
|
||||
"loading": "Loading skills...",
|
||||
"noSkills": "No skills found",
|
||||
"noExecutions": "No executions found",
|
||||
"enabled": "Enabled",
|
||||
"disabled": "Disabled",
|
||||
"version": "Version",
|
||||
"tableDescription": "Description",
|
||||
"skill": "Skill",
|
||||
"status": "Status",
|
||||
"duration": "Duration",
|
||||
"time": "Time",
|
||||
"sandboxConfig": "Sandbox Configuration",
|
||||
"cpuLimit": "CPU Limit",
|
||||
"cpuLimitDesc": "Maximum execution time per skill",
|
||||
"memoryLimit": "Memory Limit",
|
||||
"memoryLimitDesc": "Maximum memory allocation",
|
||||
"timeout": "Timeout",
|
||||
"timeoutDesc": "Maximum wait time for response",
|
||||
"networkAccess": "Network Access",
|
||||
"networkAccessDesc": "Allow outbound network requests"
|
||||
},
|
||||
"health": {
|
||||
"title": "System Health",
|
||||
"description": "Real-time monitoring of your OmniRoute instance",
|
||||
@@ -1542,6 +1595,7 @@
|
||||
"chatCompletions": "Chat Completions",
|
||||
"importingModels": "Importing...",
|
||||
"importFromModels": "Import from /models",
|
||||
"modelsImported": "{count} models imported",
|
||||
"allModelsAlreadyImported": "All models already imported",
|
||||
"noNewModelsToImport": "No new models to import — all models are already in the registry or custom models list",
|
||||
"skippingExistingModels": "Skipping {count} existing models",
|
||||
@@ -1563,6 +1617,7 @@
|
||||
"importFailed": "Import failed",
|
||||
"noNewModelsAdded": "No new models were added.",
|
||||
"adding": "Adding...",
|
||||
"close": "Close",
|
||||
"importingModelsTitle": "Importing Models",
|
||||
"copyModel": "Copy model",
|
||||
"removeModel": "Remove model",
|
||||
@@ -3015,6 +3070,7 @@
|
||||
"cachedTokensCol": "Cached",
|
||||
"cacheCreation": "Creation",
|
||||
"trend24h": "Cache Trend (24h)",
|
||||
"peakCached": "Peak cached",
|
||||
"cached": "Cached",
|
||||
"overview": "Overview",
|
||||
"entries": "Entries",
|
||||
@@ -3028,4 +3084,4 @@
|
||||
"expires": "Expires",
|
||||
"actions": "Actions"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -143,6 +143,7 @@
|
||||
"dashboard": "Panel de control",
|
||||
"providers": "Proveedores",
|
||||
"combos": "Combos",
|
||||
"autoCombo": "Auto Combo",
|
||||
"usage": "Uso",
|
||||
"analytics": "Analítica",
|
||||
"costs": "Costos",
|
||||
@@ -152,8 +153,12 @@
|
||||
"media": "Multimedia",
|
||||
"settings": "Configuración",
|
||||
"translator": "Traductor",
|
||||
"playground": "Playground",
|
||||
"searchTools": "Search Tools",
|
||||
"agents": "Agentes",
|
||||
"docs": "Documentación",
|
||||
"issues": "Problemas",
|
||||
"endpoints": "Endpoints",
|
||||
"apiManager": "Gestor de API",
|
||||
"logs": "Registros",
|
||||
"auditLog": "Registro de auditoría",
|
||||
@@ -165,6 +170,11 @@
|
||||
"debug": "Depurar",
|
||||
"system": "Sistema",
|
||||
"help": "Ayuda",
|
||||
"primarySection": "Main",
|
||||
"cliSection": "CLI",
|
||||
"debugSection": "Debug",
|
||||
"systemSection": "System",
|
||||
"helpSection": "Help",
|
||||
"serverDisconnected": "Servidor desconectado",
|
||||
"serverDisconnectedMsg": "El servidor proxy se ha detenido o se está reiniciando.",
|
||||
"expandSidebar": "Expandir barra lateral",
|
||||
@@ -180,19 +190,9 @@
|
||||
"themeViolet": "Violeta",
|
||||
"themeOrange": "Naranja",
|
||||
"themeCyan": "cian",
|
||||
"endpoints": "Endpoints",
|
||||
"playground": "Playground",
|
||||
"agents": "Agentes",
|
||||
"cliToolsShort": "Herramientas",
|
||||
"autoCombo": "Auto Combo",
|
||||
"searchTools": "Search Tools",
|
||||
"cache": "Cache",
|
||||
"cacheShort": "Cache",
|
||||
"cliSection": "CLI",
|
||||
"debugSection": "Debug",
|
||||
"helpSection": "Help",
|
||||
"primarySection": "Main",
|
||||
"systemSection": "System"
|
||||
"cacheShort": "Cache"
|
||||
},
|
||||
"themesPage": {
|
||||
"title": "Temas",
|
||||
@@ -220,6 +220,10 @@
|
||||
"homeDescription": "Bienvenido a OmniRoute",
|
||||
"endpoint": "Endpoints",
|
||||
"endpointDescription": "Gestionar endpoints proxy, MCP, A2A y endpoints de API",
|
||||
"mcp": "MCP",
|
||||
"mcpDescription": "Model Context Protocol server management and tools",
|
||||
"a2a": "A2A",
|
||||
"a2aDescription": "Agent-to-Agent protocol tasks and observability",
|
||||
"settings": "Configuración",
|
||||
"settingsDescription": "Gestiona tus preferencias",
|
||||
"openaiCompatible": "Compatible con OpenAI",
|
||||
@@ -227,11 +231,7 @@
|
||||
"media": "Medios de comunicación",
|
||||
"mediaDescription": "Genera imágenes, vídeos y música.",
|
||||
"themes": "Temas",
|
||||
"themesDescription": "Elija un tema de color para todo el panel del tablero",
|
||||
"mcp": "MCP",
|
||||
"mcpDescription": "Model Context Protocol server management and tools",
|
||||
"a2a": "A2A",
|
||||
"a2aDescription": "Agent-to-Agent protocol tasks and observability"
|
||||
"themesDescription": "Elija un tema de color para todo el panel del tablero"
|
||||
},
|
||||
"home": {
|
||||
"quickStart": "Inicio rápido",
|
||||
@@ -332,6 +332,26 @@
|
||||
"keyCreatedNote": "Copie y almacene esta clave ahora; no se volverá a mostrar.",
|
||||
"done": "hecho",
|
||||
"savePermissions": "Guardar permisos",
|
||||
"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",
|
||||
"allowAll": "Permitir todo",
|
||||
"restrict": "Restringir",
|
||||
"allowAllInfo": "Esta clave puede acceder a todos los modelos disponibles.",
|
||||
@@ -356,6 +376,7 @@
|
||||
"failedUpdatePermissionsRetry": "No se pudieron actualizar los permisos. Por favor inténtalo de nuevo.",
|
||||
"unknownProvider": "desconocido",
|
||||
"copyMaskedKey": "Copiar clave enmascarada",
|
||||
"keyOnlyAvailableAtCreation": "Full key available only at creation time — copy it when you first create the key",
|
||||
"modelsCount": "{count, plural, one {# model} other {# models}}",
|
||||
"lastUsedOn": "Último: {date}",
|
||||
"editPermissions": "Editar permisos",
|
||||
@@ -365,28 +386,7 @@
|
||||
"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",
|
||||
"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"
|
||||
"selectedCount": "{count} seleccionado"
|
||||
},
|
||||
"auditLog": {
|
||||
"title": "Registro de auditoría",
|
||||
@@ -425,6 +425,57 @@
|
||||
"videoDescription": "Create videos with AnimateDiff, Stable Video Diffusion via ComfyUI or SD WebUI.",
|
||||
"musicDescription": "Compose music using Stable Audio Open or MusicGen via ComfyUI."
|
||||
},
|
||||
"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",
|
||||
"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",
|
||||
"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"
|
||||
},
|
||||
"cliTools": {
|
||||
"title": "Herramientas CLI",
|
||||
"noActiveProviders": "No hay proveedores activos",
|
||||
@@ -497,6 +548,11 @@
|
||||
"antigravityStep2Prefix": "2. Agregar",
|
||||
"antigravityStep2Suffix": "a su archivo de hosts como 127.0.0.1.",
|
||||
"antigravityStep3": "3. Abra Antigravity y las solicitudes se enviarán por proxy.",
|
||||
"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.",
|
||||
"sudoPasswordRequiredTitle": "Se requiere contraseña de Sudo",
|
||||
"sudoPasswordHint": "Se requiere una contraseña de administrador para modificar el archivo de hosts y la configuración del proxy del sistema.",
|
||||
"enterSudoPassword": "Ingrese la contraseña de sudo",
|
||||
@@ -569,6 +625,22 @@
|
||||
"openClawManualConfiguration": "Garra abierta - Configuración manual",
|
||||
"clineManualConfiguration": "Configuración manual de línea",
|
||||
"kiloManualConfiguration": "Configuración manual del código Kilo",
|
||||
"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."
|
||||
},
|
||||
"toolDescriptions": {
|
||||
"antigravity": "IDE antigravedad de Google con MITM",
|
||||
"claude": "CLI del Código Antrópico Claude",
|
||||
@@ -706,27 +778,6 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"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": {
|
||||
@@ -988,7 +1039,12 @@
|
||||
"settingsApi": "Settings API",
|
||||
"categoryCore": "APIs Principales",
|
||||
"categoryMedia": "Medios y Multi-Modal",
|
||||
"categorySearch": "Search & Discovery",
|
||||
"categoryUtility": "Utilidades y Gestión",
|
||||
"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.",
|
||||
"enableCloudTitle": "Habilitar proxy en la nube",
|
||||
"whatYouGet": "lo que obtendrás",
|
||||
"cloudBenefitAccess": "Accede a tu API desde cualquier parte del mundo",
|
||||
@@ -1021,6 +1077,25 @@
|
||||
"cloudWorkerUnreachable": "No se pudo comunicar con el trabajador de la nube. Asegúrese de que el servicio en la nube se esté ejecutando (npm run dev en /cloud).",
|
||||
"connectionFailed": "La conexión falló",
|
||||
"syncFailed": "No se pudieron sincronizar los datos de la nube",
|
||||
"cloudflaredTitle": "Cloudflare Quick Tunnel",
|
||||
"cloudflaredDescription": "Create a Cloudflare Quick Tunnel for this endpoint to make it accessible from the internet without configuring DNS.",
|
||||
"cloudflaredUrlNotice": "Creates a temporary Cloudflare Quick Tunnel. URLs change after restart.",
|
||||
"cloudflaredEnable": "Enable Tunnel",
|
||||
"cloudflaredInstallAndEnable": "Install & Enable",
|
||||
"cloudflaredDisable": "Stop Tunnel",
|
||||
"cloudflaredRunning": "Running",
|
||||
"cloudflaredStarting": "Starting",
|
||||
"cloudflaredStoppedState": "Stopped",
|
||||
"cloudflaredNotInstalled": "Not installed",
|
||||
"cloudflaredUnsupported": "Unsupported",
|
||||
"cloudflaredError": "Error",
|
||||
"cloudflaredStarted": "Cloudflare tunnel started",
|
||||
"cloudflaredStopped": "Cloudflare tunnel stopped",
|
||||
"cloudflaredRequestFailed": "Failed to update Cloudflare tunnel",
|
||||
"cloudflaredTemporaryNote": "Quick Tunnel URLs are temporary and will change after restart.",
|
||||
"cloudflaredUnsupportedNote": "This platform is not supported for managed installation.",
|
||||
"cloudflaredIdleNote": "Create a temporary Cloudflare Quick Tunnel for this endpoint.",
|
||||
"cloudflaredLastError": "Last error: {error}",
|
||||
"providerModelsTitle": "{provider} — Modelos",
|
||||
"noModelsForProvider": "No hay modelos disponibles para este proveedor.",
|
||||
"chat": "Charla",
|
||||
@@ -1055,31 +1130,19 @@
|
||||
"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",
|
||||
"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.",
|
||||
"cloudflaredDescription": "Create a Cloudflare Quick Tunnel for this endpoint to make it accessible from the internet without configuring DNS.",
|
||||
"cloudflaredDisable": "Stop Tunnel",
|
||||
"cloudflaredEnable": "Enable Tunnel",
|
||||
"cloudflaredError": "Error",
|
||||
"cloudflaredIdleNote": "Create a temporary Cloudflare Quick Tunnel for this endpoint.",
|
||||
"cloudflaredInstallAndEnable": "Install & Enable",
|
||||
"cloudflaredLastError": "Last error: {error}",
|
||||
"cloudflaredNotInstalled": "Not installed",
|
||||
"cloudflaredRequestFailed": "Failed to update Cloudflare tunnel",
|
||||
"cloudflaredRunning": "Running",
|
||||
"cloudflaredStarted": "Cloudflare tunnel started",
|
||||
"cloudflaredStarting": "Starting",
|
||||
"cloudflaredStopped": "Cloudflare tunnel stopped",
|
||||
"cloudflaredStoppedState": "Stopped",
|
||||
"cloudflaredTemporaryNote": "Quick Tunnel URLs are temporary and will change after restart.",
|
||||
"cloudflaredTitle": "Cloudflare Quick Tunnel",
|
||||
"cloudflaredUnsupported": "Unsupported",
|
||||
"cloudflaredUnsupportedNote": "This platform is not supported for managed installation.",
|
||||
"cloudflaredUrlNotice": "Creates a temporary Cloudflare Quick Tunnel. URLs change after restart."
|
||||
"completionsLegacyDesc": "Legacy OpenAI text completions — accepts both prompt string and messages array format"
|
||||
},
|
||||
"endpoints": {
|
||||
"tabProxy": "Endpoint Proxy",
|
||||
"tabApiEndpoints": "Endpoints de API",
|
||||
"apiEndpointsTitle": "Endpoints de API",
|
||||
"apiEndpointsDescription": "Endpoints de API backend que pueden ser consumidos por otras aplicaciones y servicios.",
|
||||
"comingSoon": "Próximamente",
|
||||
"plannedFeatures": "Funciones Planeadas",
|
||||
"featureRestApi": "Catálogo de endpoints REST API con documentación interactiva",
|
||||
"featureWebhooks": "Configuración de webhooks y suscripciones de eventos",
|
||||
"featureSwagger": "Generación automática de especificaciones OpenAPI / Swagger",
|
||||
"featureAuth": "Gestión de claves API y alcances OAuth por endpoint"
|
||||
},
|
||||
"mcpDashboard": {
|
||||
"loading": "Cargando el panel de MCP...",
|
||||
@@ -1251,6 +1314,12 @@
|
||||
"issuesLabel": "Problemas detectados",
|
||||
"operational": "Operacional",
|
||||
"providers": "Proveedores",
|
||||
"configuredProvidersLabel": "Configurado en el tablero",
|
||||
"configuredProvidersHint": "Proveedores con credenciales guardadas en /dashboard/providers, independientemente del estado de ejecución.",
|
||||
"activeProviders": "{count} active",
|
||||
"activeProvidersHint": "Proveedores configurados actualmente habilitados para enrutar solicitudes.",
|
||||
"monitoredProviders": "{count} monitored",
|
||||
"monitoredProvidersHint": "Proveedores actualmente rastreados por monitores de estado de interruptores.",
|
||||
"healthyCount": "{count} saludable",
|
||||
"nodeVersion": "Nodo {version}",
|
||||
"failures": "{count} error",
|
||||
@@ -1269,13 +1338,7 @@
|
||||
"resetAllTitle": "Restablezca todos los disyuntores al estado saludable",
|
||||
"resetting": "Restableciendo...",
|
||||
"resetAll": "Restablecer todo",
|
||||
"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."
|
||||
"until": "Hasta {time}"
|
||||
},
|
||||
"limits": {
|
||||
"title": "Límites y cuotas",
|
||||
@@ -1437,6 +1500,7 @@
|
||||
"clearing": "Limpiando...",
|
||||
"until": "Hasta {time}",
|
||||
"providerTestFailed": "La prueba del proveedor falló",
|
||||
"providerTestTimeout": "Provider test timed out — too many connections to test at once",
|
||||
"modeTest": "{mode} Prueba",
|
||||
"passedCount": "{count} pasó",
|
||||
"failedCount": "{count} falló",
|
||||
@@ -1478,6 +1542,14 @@
|
||||
"chatCompletions": "Finalizaciones de chat",
|
||||
"importingModels": "Importando...",
|
||||
"importFromModels": "Importar desde /modelos",
|
||||
"allModelsAlreadyImported": "Todos los modelos ya están importados",
|
||||
"noNewModelsToImport": "No hay modelos nuevos para importar — todos los modelos ya están en el registro o en la lista de modelos personalizados",
|
||||
"skippingExistingModels": "Omitiendo {count} modelos existentes",
|
||||
"autoSync": "Sincronización automática",
|
||||
"autoSyncTooltip": "Actualiza automáticamente la lista de modelos cada 24 horas (configurable vía MODEL_SYNC_INTERVAL_HOURS)",
|
||||
"autoSyncEnabled": "Sincronización automática activada — los modelos se actualizarán periódicamente",
|
||||
"autoSyncDisabled": "Sincronización automática desactivada",
|
||||
"autoSyncToggleFailed": "Error al alternar sincronización automática",
|
||||
"clearAllModels": "Borrar todos los modelos",
|
||||
"clearAllModelsConfirm": "¿Estás seguro de que quieres eliminar todos los modelos de este proveedor?",
|
||||
"clearAllModelsSuccess": "Todos los modelos borrados",
|
||||
@@ -1531,6 +1603,8 @@
|
||||
"addFirstConnectionHint": "Agregue su primera conexión para comenzar",
|
||||
"addConnection": "Agregar conexión",
|
||||
"availableModels": "Modelos disponibles",
|
||||
"builtInModels": "Built-in models",
|
||||
"builtInModelsHint": "Registry models for this provider. Use the pencil to set compatibility options.",
|
||||
"pageAutoRefresh": "La página se actualizará automáticamente...",
|
||||
"statusDisabled": "discapacitado",
|
||||
"statusConnected": "conectado",
|
||||
@@ -1571,6 +1645,26 @@
|
||||
"openRouterModelPlaceholder": "antrópico/claude-3-opus",
|
||||
"customModels": "Modelos personalizados",
|
||||
"customModelsHint": "Agregue ID de modelo que no estén en la lista predeterminada. Estos estarán disponibles para enrutamiento.",
|
||||
"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",
|
||||
"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": "ID del modelo",
|
||||
"customModelPlaceholder": "por ej. gpt-4.5-turbo",
|
||||
"loading": "Cargando...",
|
||||
@@ -1614,52 +1708,21 @@
|
||||
"compatibleBaseUrlHint": "Root URL of your {type}-compatible API. Use Advanced Settings for custom endpoint paths.",
|
||||
"apiKeyForCheck": "Clave API (para verificación)",
|
||||
"compatibleProdPlaceholder": "{type} Compatible (Prod.)",
|
||||
"providerTestTimeout": "Provider test timed out — too many connections to test at once",
|
||||
"tokenRefreshed": "Token refreshed successfully",
|
||||
"tokenRefreshFailed": "Token refresh failed",
|
||||
"applyCodexAuthLocal": "Apply auth",
|
||||
"exportCodexAuthFile": "Export auth",
|
||||
"codexAuthAppliedLocal": "Codex auth.json applied locally",
|
||||
"codexAuthApplyFailed": "Failed to apply Codex auth.json locally",
|
||||
"codexAuthExported": "Codex auth.json exported",
|
||||
"codexAuthExportFailed": "Failed to export Codex auth.json",
|
||||
"advancedSettings": "Advanced Settings",
|
||||
"chatPathLabel": "Chat Endpoint Path",
|
||||
"chatPathPlaceholder": "/chat/completions",
|
||||
"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)",
|
||||
"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",
|
||||
"autoSync": "Sincronización automática",
|
||||
"autoSyncTooltip": "Actualiza automáticamente la lista de modelos cada 24 horas (configurable vía MODEL_SYNC_INTERVAL_HOURS)",
|
||||
"autoSyncEnabled": "Sincronización automática activada — los modelos se actualizarán periódicamente",
|
||||
"autoSyncDisabled": "Sincronización automática desactivada",
|
||||
"autoSyncToggleFailed": "Error al alternar sincronización automática",
|
||||
"allModelsAlreadyImported": "Todos los modelos ya están importados",
|
||||
"noNewModelsToImport": "No hay modelos nuevos para importar — todos los modelos ya están en el registro o en la lista de modelos personalizados",
|
||||
"skippingExistingModels": "Omitiendo {count} modelos existentes",
|
||||
"applyCodexAuthLocal": "Apply auth",
|
||||
"codexAuthAppliedLocal": "Codex auth.json applied locally",
|
||||
"codexAuthApplyFailed": "Failed to apply Codex auth.json locally",
|
||||
"codexAuthExportFailed": "Failed to export Codex auth.json",
|
||||
"codexAuthExported": "Codex auth.json exported",
|
||||
"exportCodexAuthFile": "Export auth"
|
||||
"modelsPathHint": "Custom models path for validation (e.g. /v4/models)"
|
||||
},
|
||||
"settings": {
|
||||
"title": "Configuración",
|
||||
@@ -1683,6 +1746,8 @@
|
||||
"darkMode": "Modo oscuro",
|
||||
"lightMode": "Modo de luz",
|
||||
"systemTheme": "Tema del sistema",
|
||||
"debugToggle": "Enable Debug Mode",
|
||||
"sidebarVisibilityToggle": "Show Sidebar Items",
|
||||
"enableCache": "Habilitar caché",
|
||||
"cacheTTL": "TTL de caché",
|
||||
"maxCacheSize": "Tamaño máximo de caché",
|
||||
@@ -1691,6 +1756,17 @@
|
||||
"cacheMisses": "Errores de caché",
|
||||
"hitRate": "Tasa de aciertos",
|
||||
"cacheEntries": "Entradas de caché",
|
||||
"cacheSettings": "Cache Settings",
|
||||
"semanticCache": "Semantic Cache",
|
||||
"maxEntries": "Max Entries",
|
||||
"ttlMinutes": "TTL (minutes)",
|
||||
"promptCache": "Caché de aviso",
|
||||
"strategy": "Strategy",
|
||||
"preserveClientCache": "Preserve Client Cache",
|
||||
"enabled": "Enabled",
|
||||
"loading": "Loading...",
|
||||
"saving": "Guardando...",
|
||||
"save": "Save",
|
||||
"circuitBreaker": "Disyuntor",
|
||||
"retryPolicy": "Política de reintento",
|
||||
"maxRetries": "Reintentos máximos",
|
||||
@@ -1698,6 +1774,10 @@
|
||||
"timeoutMs": "Tiempo de espera (ms)",
|
||||
"enableSystemPrompt": "Habilitar mensaje del sistema",
|
||||
"systemPromptText": "Texto de aviso del sistema",
|
||||
"autoDisableBannedAccounts": "Auto-Disable Banned Accounts",
|
||||
"autoDisableDescription": "Permanently mark provider connections as deactivated if they return specific terminal ban signals (e.g. HTTP 403 'verify your account'). This removes them from the combo rotation.",
|
||||
"autoDisableThreshold": "Ban Threshold",
|
||||
"autoDisableThresholdDesc": "Consecutive ban signals required before permanent deactivation.",
|
||||
"enableThinking": "Habilitar el pensamiento",
|
||||
"maxThinkingTokens": "Fichas de pensamiento máximo",
|
||||
"enableProxy": "Habilitar proxy",
|
||||
@@ -1734,6 +1814,9 @@
|
||||
"themeLight": "Luz",
|
||||
"themeDark": "oscuro",
|
||||
"themeSystem": "Sistema",
|
||||
"sidebarVisibility": "Hide sidebar items",
|
||||
"sidebarVisibilityDesc": "Hide any sidebar navigation entry to reduce visual clutter.",
|
||||
"sidebarVisibilityHint": "Any sidebar section is hidden automatically when a...",
|
||||
"hideHealthLogs": "Ocultar registros de verificación de estado",
|
||||
"hideHealthLogsDesc": "Cuando está activado, suprime los mensajes [HealthCheck] en la consola del servidor",
|
||||
"themeAccent": "Color del tema",
|
||||
@@ -1746,7 +1829,20 @@
|
||||
"themeViolet": "Violeta",
|
||||
"themeOrange": "Naranja",
|
||||
"themeCyan": "Cian",
|
||||
"promptCache": "Caché de aviso",
|
||||
"whitelabeling": "Branding",
|
||||
"whitelabelingDesc": "Customize the application name and logo",
|
||||
"appName": "Application Name",
|
||||
"appNameDesc": "Display name shown in sidebar and browser tab",
|
||||
"customLogo": "Custom Logo URL",
|
||||
"customLogoDesc": "URL to your custom logo image",
|
||||
"uploadLogo": "Upload Logo",
|
||||
"resetLogo": "Reset to Default",
|
||||
"logoPreview": "Preview",
|
||||
"customFavicon": "Browser Favicon",
|
||||
"customFaviconDesc": "URL to your custom favicon (shown in browser tab)",
|
||||
"uploadFavicon": "Upload Favicon",
|
||||
"resetFavicon": "Reset Favicon",
|
||||
"faviconPreview": "Favicon Preview",
|
||||
"flushCache": "Vaciar caché",
|
||||
"flushing": "Sonrojándose…",
|
||||
"size": "Tamaño",
|
||||
@@ -1804,7 +1900,15 @@
|
||||
"providersBlocked": "{count} proveedor(es) bloqueado(s) en /models",
|
||||
"blockProviderTitle": "Bloquear {provider}",
|
||||
"unblockProviderTitle": "Desbloquear {provider}",
|
||||
"cliFingerprint": "CLI Fingerprint Matching",
|
||||
"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}",
|
||||
"routingStrategy": "Estrategia de enrutamiento",
|
||||
"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.",
|
||||
"fillFirst": "Llenar primero",
|
||||
"fillFirstDesc": "Utilice cuentas en orden de prioridad",
|
||||
"roundRobin": "Ronda Robin",
|
||||
@@ -1899,6 +2003,9 @@
|
||||
"fillModelAndProviders": "Por favor complete el nombre del modelo y los proveedores",
|
||||
"addAtLeastOneProvider": "Añade al menos un proveedor",
|
||||
"comboDefaultsTitle": "Valores predeterminados combinados",
|
||||
"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.",
|
||||
"globalComboConfig": "Configuración combinada global",
|
||||
"defaultStrategy": "Estrategia predeterminada",
|
||||
"defaultStrategyDesc": "Aplicado a nuevos combos sin estrategia explícita.",
|
||||
@@ -2026,7 +2133,6 @@
|
||||
"unsaved": "no salvo",
|
||||
"resetDefaults": "Restablecer valores predeterminados",
|
||||
"saveProvider": "Guardar proveedor",
|
||||
"saving": "Guardando...",
|
||||
"model": "modelo",
|
||||
"models": "modelos",
|
||||
"moreProviders": "{count} más proveedores",
|
||||
@@ -2057,36 +2163,7 @@
|
||||
"customPricingNote": "Puede anular los precios predeterminados para modelos específicos. Las anulaciones personalizadas tienen prioridad sobre los precios detectados automáticamente.",
|
||||
"editPricing": "Editar precios",
|
||||
"viewFullDetails": "Ver todos los detalles",
|
||||
"themeCoral": "Coral",
|
||||
"cliFingerprint": "CLI Fingerprint Matching",
|
||||
"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}",
|
||||
"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.",
|
||||
"sidebarVisibility": "Hide sidebar items",
|
||||
"sidebarVisibilityDesc": "Hide any sidebar navigation entry to reduce visual clutter.",
|
||||
"sidebarVisibilityHint": "Any sidebar section is hidden automatically when a...",
|
||||
"semanticCache": "Semantic Cache",
|
||||
"autoDisableDescription": "Permanently mark provider connections as deactivated if they return specific terminal ban signals (e.g. HTTP 403 'verify your account'). This removes them from the combo rotation.",
|
||||
"preserveClientCache": "Preserve Client Cache",
|
||||
"cacheSettings": "Cache Settings",
|
||||
"autoDisableThreshold": "Ban Threshold",
|
||||
"autoDisableBannedAccounts": "Auto-Disable Banned Accounts",
|
||||
"ttlMinutes": "TTL (minutes)",
|
||||
"maxEntries": "Max Entries",
|
||||
"loading": "Loading...",
|
||||
"save": "Save",
|
||||
"autoDisableThresholdDesc": "Consecutive ban signals required before permanent deactivation.",
|
||||
"debugToggle": "Enable Debug Mode",
|
||||
"sidebarVisibilityToggle": "Show Sidebar Items",
|
||||
"enabled": "Enabled",
|
||||
"strategy": "Strategy"
|
||||
"themeCoral": "Coral"
|
||||
},
|
||||
"translator": {
|
||||
"title": "Traductor",
|
||||
@@ -2505,14 +2582,14 @@
|
||||
"backToLogin": "Volver a iniciar sesión",
|
||||
"forgotPassword": "¿Olvidaste tu contraseña?",
|
||||
"defaultPasswordHint": "Default password: 123456 (unless INITIAL_PASSWORD was set)",
|
||||
"Authorization": "Autorización",
|
||||
"Content-Disposition": "Disposición de contenido",
|
||||
"waitingForAuthorization": "Waiting for authorization...",
|
||||
"waitingForGoogleAuthorization": "Waiting for Google authorization...",
|
||||
"waitingForOpenAIAuthorization": "Waiting for OpenAI authorization...",
|
||||
"waitingForAntigravityAuthorization": "Waiting for Antigravity authorization...",
|
||||
"waitingForQoderAuthorization": "Waiting for Qoder authorization...",
|
||||
"exchangingCodeForTokens": "Exchanging code for tokens...",
|
||||
"Authorization": "Autorización",
|
||||
"Content-Disposition": "Disposición de contenido"
|
||||
"exchangingCodeForTokens": "Exchanging code for tokens..."
|
||||
},
|
||||
"landing": {
|
||||
"brandName": "OmniRuta",
|
||||
@@ -2606,6 +2683,8 @@
|
||||
"clientCompatibility": "Compatibilidad del cliente",
|
||||
"protocolsToc": "Protocolos",
|
||||
"apiReference": "Referencia de API",
|
||||
"managementApiReference": "Management API Reference",
|
||||
"managementApiDescription": "Automation endpoints for proxy registry, scope assignments, and legacy proxy migration.",
|
||||
"method": "Método",
|
||||
"path": "Camino",
|
||||
"notes": "Notas",
|
||||
@@ -2697,10 +2776,19 @@
|
||||
"endpointResponsesNote": "Punto final de API de respuestas (Codex, o-series).",
|
||||
"endpointModelsNote": "Catálogo de modelos para todos los proveedores conectados.",
|
||||
"endpointAudioNote": "Transcripción de audio (Deepgram, AssemblyAI).",
|
||||
"endpointSpeechNote": "Generación de texto a voz (ElevenLabs, OpenAI TTS).",
|
||||
"endpointEmbeddingsNote": "Generación de embeddings de texto (OpenAI, Cohere, Voyage).",
|
||||
"endpointImagesNote": "Generación de imágenes (NanoBanana).",
|
||||
"endpointRewriteChatNote": "Reescribir el asistente para clientes sin /v1.",
|
||||
"endpointRewriteResponsesNote": "Reescriba el asistente para respuestas sin /v1.",
|
||||
"endpointRewriteModelsNote": "Reescriba el asistente para el descubrimiento de modelos sin /v1.",
|
||||
"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.",
|
||||
"modelPrefixesDescriptionStart": "Utilice el prefijo del proveedor antes del nombre del modelo para enrutar a un proveedor específico. Ejemplo:",
|
||||
"modelPrefixesDescriptionEnd": "rutas al copiloto de GitHub.",
|
||||
"provider": "Proveedor",
|
||||
@@ -2710,18 +2798,7 @@
|
||||
"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.",
|
||||
"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.",
|
||||
"endpointSpeechNote": "Generación de texto a voz (ElevenLabs, OpenAI TTS).",
|
||||
"endpointEmbeddingsNote": "Generación de embeddings de texto (OpenAI, Cohere, Voyage)."
|
||||
"troubleshootingOAuth": "Para los proveedores de OAuth, vuelva a autenticarse si los tokens caducan. Verifique el indicador de estado de la tarjeta del proveedor."
|
||||
},
|
||||
"legal": {
|
||||
"privacyPolicy": "Política de privacidad",
|
||||
@@ -2783,18 +2860,6 @@
|
||||
"termsSection6Title": "6. Código abierto",
|
||||
"termsSection6Text": "OmniRoute es un software de código abierto. Usted es libre de inspeccionarlo, modificarlo y distribuirlo según los términos de su licencia."
|
||||
},
|
||||
"endpoints": {
|
||||
"tabProxy": "Endpoint Proxy",
|
||||
"tabApiEndpoints": "Endpoints de API",
|
||||
"apiEndpointsTitle": "Endpoints de API",
|
||||
"apiEndpointsDescription": "Endpoints de API backend que pueden ser consumidos por otras aplicaciones y servicios.",
|
||||
"comingSoon": "Próximamente",
|
||||
"plannedFeatures": "Funciones Planeadas",
|
||||
"featureRestApi": "Catálogo de endpoints REST API con documentación interactiva",
|
||||
"featureWebhooks": "Configuración de webhooks y suscripciones de eventos",
|
||||
"featureSwagger": "Generación automática de especificaciones OpenAPI / Swagger",
|
||||
"featureAuth": "Gestión de claves API y alcances OAuth por endpoint"
|
||||
},
|
||||
"agents": {
|
||||
"title": "CLI Agents",
|
||||
"description": "Discover installed CLI agents on your system. Add custom agents for auto-detection.",
|
||||
@@ -2849,57 +2914,6 @@
|
||||
"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"
|
||||
},
|
||||
"templateNames": {
|
||||
"simple-chat": "Simple Chat",
|
||||
"streaming": "Streaming",
|
||||
@@ -2949,67 +2963,67 @@
|
||||
"refresh": "Refresh",
|
||||
"clearAll": "Clear All",
|
||||
"memoryEntries": "Memory Entries",
|
||||
"memoryEntriesSub": "In-memory LRU",
|
||||
"dbEntries": "DB Entries",
|
||||
"dbEntriesSub": "Persisted (SQLite)",
|
||||
"cacheHits": "Cache Hits",
|
||||
"cacheHitsSub": "of {total} total",
|
||||
"tokensSaved": "Tokens Saved",
|
||||
"tokensSavedSub": "Estimated from hits",
|
||||
"hitRate": "Hit Rate",
|
||||
"performance": "Cache Performance",
|
||||
"behavior": "Cache Behavior",
|
||||
"idempotency": "Idempotency Layer",
|
||||
"clearSuccess": "Cache cleared. {count} expired entries removed.",
|
||||
"clearError": "Failed to clear cache.",
|
||||
"unavailable": "Cache unavailable",
|
||||
"unavailableDesc": "Could not fetch cache statistics. Make sure the server is running.",
|
||||
"memoryEntriesSub": "In-memory LRU",
|
||||
"dbEntriesSub": "Persisted (SQLite)",
|
||||
"cacheHitsSub": "of {total} total",
|
||||
"tokensSavedSub": "Estimated from hits",
|
||||
"autoRefresh": "Auto-refreshes every {seconds}s",
|
||||
"hits": "Hits",
|
||||
"misses": "Misses",
|
||||
"total": "Total",
|
||||
"behavior": "Cache Behavior",
|
||||
"behaviorDeterministic": "Only non-streaming requests with temperature=0 are cached.",
|
||||
"behaviorBypass": "Bypass with header {header}.",
|
||||
"behaviorTwoTier": "Two-tier storage: in-memory LRU (fast) + SQLite (persistent across restarts).",
|
||||
"behaviorTtl": "Default TTL: 30 minutes. Configure via {envVar}.",
|
||||
"idempotency": "Idempotency Layer",
|
||||
"activeDedupKeys": "Active Dedup Keys",
|
||||
"dedupWindow": "Dedup Window",
|
||||
"inputTokens": "Input Tokens",
|
||||
"clearSuccess": "Cache cleared. {count} expired entries removed.",
|
||||
"clearError": "Failed to clear cache.",
|
||||
"unavailable": "Cache unavailable",
|
||||
"unavailableDesc": "Could not fetch cache statistics. Make sure the server is running.",
|
||||
"promptCache": "Prompt Cache (Provider-Side)",
|
||||
"cachedRequests": "Cached Requests",
|
||||
"cacheHitRate": "Cache Hit Rate",
|
||||
"cachedTokens": "Cached Tokens",
|
||||
"cacheCreationTokens": "Cache Creation Tokens",
|
||||
"cacheMetrics": "Prompt Cache Metrics",
|
||||
"withCacheControl": "With Cache Control",
|
||||
"cachedTokensRead": "Cached Tokens (Read)",
|
||||
"cacheCreationWrite": "Cache Creation (Write)",
|
||||
"cacheReuseRatio": "Cache Reuse Ratio",
|
||||
"cacheReuseRatioDesc": "Cached tokens / Total input tokens",
|
||||
"estCostSaved": "Est. Cost Saved",
|
||||
"requestsShort": "reqs",
|
||||
"inputShort": "In",
|
||||
"resetting": "Resetting...",
|
||||
"cachedTokensCol": "Cached",
|
||||
"search": "Search",
|
||||
"cacheReuseRatioDesc": "Cached tokens / Total input tokens",
|
||||
"resetMetrics": "Reset Metrics",
|
||||
"loading": "Loading...",
|
||||
"cachedRequests": "Cached Requests",
|
||||
"model": "Model",
|
||||
"cached": "Cached",
|
||||
"actions": "Actions",
|
||||
"trend24h": "Cache Trend (24h)",
|
||||
"cachedShort": "Cached",
|
||||
"cacheCreation": "Creation",
|
||||
"byProvider": "Breakdown by Provider",
|
||||
"created": "Created",
|
||||
"cacheCreationTokens": "Cache Creation Tokens",
|
||||
"withCacheControl": "With Cache Control",
|
||||
"searchEntries": "Search entries...",
|
||||
"cacheReuseRatio": "Cache Reuse Ratio",
|
||||
"estCostSaved": "Est. Cost Saved",
|
||||
"cachedTokens": "Cached Tokens",
|
||||
"requests": "Requests",
|
||||
"signature": "Signature",
|
||||
"cacheCreationWrite": "Cache Creation (Write)",
|
||||
"expires": "Expires",
|
||||
"writeShort": "Write",
|
||||
"cacheHitRate": "Cache Hit Rate",
|
||||
"cacheMetrics": "Prompt Cache Metrics",
|
||||
"overview": "Overview",
|
||||
"promptCache": "Prompt Cache (Provider-Side)",
|
||||
"resetting": "Resetting...",
|
||||
"resetMetrics": "Reset Metrics",
|
||||
"byProvider": "Breakdown by Provider",
|
||||
"provider": "Provider",
|
||||
"cachedTokensRead": "Cached Tokens (Read)",
|
||||
"requests": "Requests",
|
||||
"inputTokens": "Input Tokens",
|
||||
"cachedTokensCol": "Cached",
|
||||
"cacheCreation": "Creation",
|
||||
"trend24h": "Cache Trend (24h)",
|
||||
"cached": "Cached",
|
||||
"overview": "Overview",
|
||||
"entries": "Entries",
|
||||
"noEntries": "No cache entries found"
|
||||
"searchEntries": "Search entries...",
|
||||
"search": "Search",
|
||||
"loading": "Loading...",
|
||||
"noEntries": "No cache entries found",
|
||||
"signature": "Signature",
|
||||
"model": "Model",
|
||||
"created": "Created",
|
||||
"expires": "Expires",
|
||||
"actions": "Actions"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -143,6 +143,7 @@
|
||||
"dashboard": "Kojelauta",
|
||||
"providers": "Palveluntarjoajat",
|
||||
"combos": "Yhdistelmät",
|
||||
"autoCombo": "Auto Combo",
|
||||
"usage": "Käyttö",
|
||||
"analytics": "Analytics",
|
||||
"costs": "Kustannukset",
|
||||
@@ -152,8 +153,12 @@
|
||||
"media": "Media",
|
||||
"settings": "Asetukset",
|
||||
"translator": "Kääntäjä",
|
||||
"playground": "Leikkipaikka",
|
||||
"searchTools": "Search Tools",
|
||||
"agents": "Agentit",
|
||||
"docs": "Asiakirjat",
|
||||
"issues": "Ongelmat",
|
||||
"endpoints": "Päätepisteet",
|
||||
"apiManager": "API Manager",
|
||||
"logs": "Lokit",
|
||||
"auditLog": "Tarkastusloki",
|
||||
@@ -165,6 +170,11 @@
|
||||
"debug": "Debug",
|
||||
"system": "Järjestelmä",
|
||||
"help": "Apua",
|
||||
"primarySection": "Main",
|
||||
"cliSection": "CLI",
|
||||
"debugSection": "Debug",
|
||||
"systemSection": "System",
|
||||
"helpSection": "Help",
|
||||
"serverDisconnected": "Palvelin yhteys katkaistu",
|
||||
"serverDisconnectedMsg": "Välityspalvelin on pysäytetty tai käynnistyy uudelleen.",
|
||||
"expandSidebar": "Laajenna sivupalkki",
|
||||
@@ -180,19 +190,9 @@
|
||||
"themeViolet": "Violetti",
|
||||
"themeOrange": "Oranssi",
|
||||
"themeCyan": "Syaani",
|
||||
"endpoints": "Päätepisteet",
|
||||
"playground": "Leikkipaikka",
|
||||
"agents": "Agentit",
|
||||
"cliToolsShort": "Työkalut",
|
||||
"autoCombo": "Auto Combo",
|
||||
"searchTools": "Search Tools",
|
||||
"cache": "Cache",
|
||||
"cacheShort": "Cache",
|
||||
"cliSection": "CLI",
|
||||
"debugSection": "Debug",
|
||||
"helpSection": "Help",
|
||||
"primarySection": "Main",
|
||||
"systemSection": "System"
|
||||
"cacheShort": "Cache"
|
||||
},
|
||||
"themesPage": {
|
||||
"title": "Teemat",
|
||||
@@ -220,6 +220,10 @@
|
||||
"homeDescription": "Tervetuloa OmniRouteen",
|
||||
"endpoint": "Endpoints",
|
||||
"endpointDescription": "Manage proxy endpoints, MCP, A2A, and API endpoints",
|
||||
"mcp": "MCP",
|
||||
"mcpDescription": "Model Context Protocol server management and tools",
|
||||
"a2a": "A2A",
|
||||
"a2aDescription": "Agent-to-Agent protocol tasks and observability",
|
||||
"settings": "Asetukset",
|
||||
"settingsDescription": "Hallinnoi asetuksiasi",
|
||||
"openaiCompatible": "OpenAI-yhteensopiva",
|
||||
@@ -227,11 +231,7 @@
|
||||
"media": "Media",
|
||||
"mediaDescription": "Luo kuvia, videoita ja musiikkia",
|
||||
"themes": "Teemat",
|
||||
"themesDescription": "Valitse väriteema koko kojelautapaneelille",
|
||||
"mcp": "MCP",
|
||||
"mcpDescription": "Model Context Protocol server management and tools",
|
||||
"a2a": "A2A",
|
||||
"a2aDescription": "Agent-to-Agent protocol tasks and observability"
|
||||
"themesDescription": "Valitse väriteema koko kojelautapaneelille"
|
||||
},
|
||||
"home": {
|
||||
"quickStart": "Pika-aloitus",
|
||||
@@ -332,6 +332,26 @@
|
||||
"keyCreatedNote": "Kopioi ja tallenna tämä avain nyt – sitä ei näytetä uudelleen.",
|
||||
"done": "Valmis",
|
||||
"savePermissions": "Tallenna käyttöoikeudet",
|
||||
"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",
|
||||
"allowAll": "Salli kaikki",
|
||||
"restrict": "Rajoita",
|
||||
"allowAllInfo": "Tällä avaimella pääsee kaikkiin saatavilla oleviin malleihin.",
|
||||
@@ -356,6 +376,7 @@
|
||||
"failedUpdatePermissionsRetry": "Käyttöoikeuksien päivittäminen epäonnistui. Yritä uudelleen.",
|
||||
"unknownProvider": "tuntematon",
|
||||
"copyMaskedKey": "Kopioi peitetty avain",
|
||||
"keyOnlyAvailableAtCreation": "Full key available only at creation time — copy it when you first create the key",
|
||||
"modelsCount": "{count, plural, one {# model} other {# models}}",
|
||||
"lastUsedOn": "Viimeisin: {date}",
|
||||
"editPermissions": "Muokkaa käyttöoikeuksia",
|
||||
@@ -365,28 +386,7 @@
|
||||
"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",
|
||||
"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"
|
||||
"selectedCount": "{count} valittu"
|
||||
},
|
||||
"auditLog": {
|
||||
"title": "Tarkastusloki",
|
||||
@@ -425,6 +425,57 @@
|
||||
"videoDescription": "Create videos with AnimateDiff, Stable Video Diffusion via ComfyUI or SD WebUI.",
|
||||
"musicDescription": "Compose music using Stable Audio Open or MusicGen via ComfyUI."
|
||||
},
|
||||
"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",
|
||||
"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",
|
||||
"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"
|
||||
},
|
||||
"cliTools": {
|
||||
"title": "CLI-työkalut",
|
||||
"noActiveProviders": "Ei aktiivisia palveluntarjoajia",
|
||||
@@ -497,6 +548,11 @@
|
||||
"antigravityStep2Prefix": "2. Lisää",
|
||||
"antigravityStep2Suffix": "hosts-tiedostoon muodossa 127.0.0.1.",
|
||||
"antigravityStep3": "3. Avaa Antigravity ja pyynnöt välitetään.",
|
||||
"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.",
|
||||
"sudoPasswordRequiredTitle": "Sudo-salasana vaaditaan",
|
||||
"sudoPasswordHint": "Järjestelmänvalvojan salasana tarvitaan hosts-tiedoston ja järjestelmän välityspalvelimen asetusten muokkaamiseen.",
|
||||
"enterSudoPassword": "Anna sudo-salasana",
|
||||
@@ -569,6 +625,22 @@
|
||||
"openClawManualConfiguration": "Open Claw - Manuaalinen konfigurointi",
|
||||
"clineManualConfiguration": "Cline Manuaalinen konfigurointi",
|
||||
"kiloManualConfiguration": "Kilo Code Manuaalinen konfigurointi",
|
||||
"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ä."
|
||||
},
|
||||
"toolDescriptions": {
|
||||
"antigravity": "Google Antigravity IDE ja MITM",
|
||||
"claude": "Antrooppinen Claude Code CLI",
|
||||
@@ -706,27 +778,6 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"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": {
|
||||
@@ -988,7 +1039,12 @@
|
||||
"settingsApi": "Settings API",
|
||||
"categoryCore": "Ydin-API:t",
|
||||
"categoryMedia": "Media & Monimuotoinen",
|
||||
"categorySearch": "Search & Discovery",
|
||||
"categoryUtility": "Apuohjelmat & Hallinta",
|
||||
"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.",
|
||||
"enableCloudTitle": "Ota Cloud Proxy käyttöön",
|
||||
"whatYouGet": "Mitä saat",
|
||||
"cloudBenefitAccess": "Käytä sovellusliittymääsi mistä päin maailmaa tahansa",
|
||||
@@ -1021,6 +1077,25 @@
|
||||
"cloudWorkerUnreachable": "Pilvityöntekijää ei tavoitettu. Varmista, että pilvipalvelu on käynnissä (npm run dev in /cloud).",
|
||||
"connectionFailed": "Yhteys epäonnistui",
|
||||
"syncFailed": "Pilvitietojen synkronointi epäonnistui",
|
||||
"cloudflaredTitle": "Cloudflare Quick Tunnel",
|
||||
"cloudflaredDescription": "Create a Cloudflare Quick Tunnel for this endpoint to make it accessible from the internet without configuring DNS.",
|
||||
"cloudflaredUrlNotice": "Creates a temporary Cloudflare Quick Tunnel. URLs change after restart.",
|
||||
"cloudflaredEnable": "Enable Tunnel",
|
||||
"cloudflaredInstallAndEnable": "Install & Enable",
|
||||
"cloudflaredDisable": "Stop Tunnel",
|
||||
"cloudflaredRunning": "Running",
|
||||
"cloudflaredStarting": "Starting",
|
||||
"cloudflaredStoppedState": "Stopped",
|
||||
"cloudflaredNotInstalled": "Not installed",
|
||||
"cloudflaredUnsupported": "Unsupported",
|
||||
"cloudflaredError": "Error",
|
||||
"cloudflaredStarted": "Cloudflare tunnel started",
|
||||
"cloudflaredStopped": "Cloudflare tunnel stopped",
|
||||
"cloudflaredRequestFailed": "Failed to update Cloudflare tunnel",
|
||||
"cloudflaredTemporaryNote": "Quick Tunnel URLs are temporary and will change after restart.",
|
||||
"cloudflaredUnsupportedNote": "This platform is not supported for managed installation.",
|
||||
"cloudflaredIdleNote": "Create a temporary Cloudflare Quick Tunnel for this endpoint.",
|
||||
"cloudflaredLastError": "Last error: {error}",
|
||||
"providerModelsTitle": "{provider} – Mallit",
|
||||
"noModelsForProvider": "Tälle tarjoajalle ei ole saatavilla malleja.",
|
||||
"chat": "Chat",
|
||||
@@ -1055,31 +1130,19 @@
|
||||
"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",
|
||||
"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.",
|
||||
"cloudflaredDescription": "Create a Cloudflare Quick Tunnel for this endpoint to make it accessible from the internet without configuring DNS.",
|
||||
"cloudflaredDisable": "Stop Tunnel",
|
||||
"cloudflaredEnable": "Enable Tunnel",
|
||||
"cloudflaredError": "Error",
|
||||
"cloudflaredIdleNote": "Create a temporary Cloudflare Quick Tunnel for this endpoint.",
|
||||
"cloudflaredInstallAndEnable": "Install & Enable",
|
||||
"cloudflaredLastError": "Last error: {error}",
|
||||
"cloudflaredNotInstalled": "Not installed",
|
||||
"cloudflaredRequestFailed": "Failed to update Cloudflare tunnel",
|
||||
"cloudflaredRunning": "Running",
|
||||
"cloudflaredStarted": "Cloudflare tunnel started",
|
||||
"cloudflaredStarting": "Starting",
|
||||
"cloudflaredStopped": "Cloudflare tunnel stopped",
|
||||
"cloudflaredStoppedState": "Stopped",
|
||||
"cloudflaredTemporaryNote": "Quick Tunnel URLs are temporary and will change after restart.",
|
||||
"cloudflaredTitle": "Cloudflare Quick Tunnel",
|
||||
"cloudflaredUnsupported": "Unsupported",
|
||||
"cloudflaredUnsupportedNote": "This platform is not supported for managed installation.",
|
||||
"cloudflaredUrlNotice": "Creates a temporary Cloudflare Quick Tunnel. URLs change after restart."
|
||||
"completionsLegacyDesc": "Legacy OpenAI text completions — accepts both prompt string and messages array format"
|
||||
},
|
||||
"endpoints": {
|
||||
"tabProxy": "Endpoint Proxy",
|
||||
"tabApiEndpoints": "API Endpoints",
|
||||
"apiEndpointsTitle": "API Endpoints",
|
||||
"apiEndpointsDescription": "Backend API endpoints that can be consumed by other applications and services. This section will list all available REST APIs with documentation and testing capabilities.",
|
||||
"comingSoon": "Coming Soon",
|
||||
"plannedFeatures": "Planned Features",
|
||||
"featureRestApi": "REST API endpoint catalog with interactive documentation",
|
||||
"featureWebhooks": "Webhook configuration and event subscriptions",
|
||||
"featureSwagger": "OpenAPI / Swagger spec auto-generation",
|
||||
"featureAuth": "API key and OAuth scope management per endpoint"
|
||||
},
|
||||
"mcpDashboard": {
|
||||
"loading": "Ladataan MCP-hallintapaneelia...",
|
||||
@@ -1251,6 +1314,12 @@
|
||||
"issuesLabel": "Ongelmia havaittu",
|
||||
"operational": "Toiminnassa",
|
||||
"providers": "Palveluntarjoajat",
|
||||
"configuredProvidersLabel": "Konfiguroitu kojelaudassa",
|
||||
"configuredProvidersHint": "Palveluntarjoajat, joiden valtuustiedot on tallennettu kansioon /dashboard/providers, suorituksen tilasta riippumatta.",
|
||||
"activeProviders": "{count} active",
|
||||
"activeProvidersHint": "Määritetyt palveluntarjoajat, jotka ovat tällä hetkellä käytössä reitityspyyntöjä varten.",
|
||||
"monitoredProviders": "{count} monitored",
|
||||
"monitoredProvidersHint": "Palveluntarjoajat, joita tällä hetkellä seurataan katkaisijoiden kuntovalvojien avulla.",
|
||||
"healthyCount": "{count} terve",
|
||||
"nodeVersion": "Solmu {version}",
|
||||
"failures": "{count} virhe",
|
||||
@@ -1269,13 +1338,7 @@
|
||||
"resetAllTitle": "Palauta kaikki katkaisijat terveeseen tilaan",
|
||||
"resetting": "Nollataan...",
|
||||
"resetAll": "Nollaa kaikki",
|
||||
"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."
|
||||
"until": "{time} asti"
|
||||
},
|
||||
"limits": {
|
||||
"title": "Rajoitukset ja kiintiöt",
|
||||
@@ -1437,6 +1500,7 @@
|
||||
"clearing": "Tyhjennetään...",
|
||||
"until": "{time} asti",
|
||||
"providerTestFailed": "Palveluntarjoajan testi epäonnistui",
|
||||
"providerTestTimeout": "Provider test timed out — too many connections to test at once",
|
||||
"modeTest": "{mode} Testi",
|
||||
"passedCount": "{count} hyväksytty",
|
||||
"failedCount": "{count} epäonnistui",
|
||||
@@ -1478,6 +1542,14 @@
|
||||
"chatCompletions": "Chatin loppuun saattaminen",
|
||||
"importingModels": "Tuodaan...",
|
||||
"importFromModels": "Tuo / mallit",
|
||||
"allModelsAlreadyImported": "Kaikki mallit on jo tuotu",
|
||||
"noNewModelsToImport": "Ei uusia malleja tuotavaksi — kaikki mallit ovat jo rekisterissä tai mukautetulla mallilistalla",
|
||||
"skippingExistingModels": "Ohitetaan {count} olemassa olevaa mallia",
|
||||
"autoSync": "Automaattinen synkronointi",
|
||||
"autoSyncTooltip": "Päivitä malliluettelo automaattisesti 24 tunnin välein (konfiguroitavissa kohdassa MODEL_SYNC_INTERVAL_HOURS)",
|
||||
"autoSyncEnabled": "Automaattinen synkronointi käytössä – mallit päivittyvät säännöllisesti",
|
||||
"autoSyncDisabled": "Automaattinen synkronointi poistettu käytöstä",
|
||||
"autoSyncToggleFailed": "Automaattisen synkronoinnin vaihtaminen epäonnistui",
|
||||
"clearAllModels": "Tyhjennä kaikki mallit",
|
||||
"clearAllModelsConfirm": "Haluatko varmasti poistaa kaikki tämän palveluntarjoajan mallit? Tätä ei voi kumota.",
|
||||
"clearAllModelsSuccess": "Kaikki mallit tyhjennetty",
|
||||
@@ -1531,6 +1603,8 @@
|
||||
"addFirstConnectionHint": "Aloita lisäämällä ensimmäinen yhteys",
|
||||
"addConnection": "Lisää yhteys",
|
||||
"availableModels": "Saatavilla olevat mallit",
|
||||
"builtInModels": "Built-in models",
|
||||
"builtInModelsHint": "Registry models for this provider. Use the pencil to set compatibility options.",
|
||||
"pageAutoRefresh": "Sivu päivittyy automaattisesti...",
|
||||
"statusDisabled": "pois käytöstä",
|
||||
"statusConnected": "yhdistetty",
|
||||
@@ -1571,6 +1645,26 @@
|
||||
"openRouterModelPlaceholder": "antrooppinen/claude-3-opus",
|
||||
"customModels": "Mukautetut mallit",
|
||||
"customModelsHint": "Lisää mallitunnukset, jotka eivät ole oletusluettelossa. Nämä ovat käytettävissä reititystä varten.",
|
||||
"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",
|
||||
"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": "Mallin tunnus",
|
||||
"customModelPlaceholder": "esim. gpt-4.5-turbo",
|
||||
"loading": "Ladataan...",
|
||||
@@ -1614,52 +1708,21 @@
|
||||
"compatibleBaseUrlHint": "Root URL of your {type}-compatible API. Use Advanced Settings for custom endpoint paths.",
|
||||
"apiKeyForCheck": "API-avain (tarkistusta varten)",
|
||||
"compatibleProdPlaceholder": "{type} Yhteensopiva (tuote)",
|
||||
"providerTestTimeout": "Provider test timed out — too many connections to test at once",
|
||||
"tokenRefreshed": "Token refreshed successfully",
|
||||
"tokenRefreshFailed": "Token refresh failed",
|
||||
"applyCodexAuthLocal": "Apply auth",
|
||||
"exportCodexAuthFile": "Export auth",
|
||||
"codexAuthAppliedLocal": "Codex auth.json applied locally",
|
||||
"codexAuthApplyFailed": "Failed to apply Codex auth.json locally",
|
||||
"codexAuthExported": "Codex auth.json exported",
|
||||
"codexAuthExportFailed": "Failed to export Codex auth.json",
|
||||
"advancedSettings": "Advanced Settings",
|
||||
"chatPathLabel": "Chat Endpoint Path",
|
||||
"chatPathPlaceholder": "/chat/completions",
|
||||
"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)",
|
||||
"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",
|
||||
"autoSync": "Automaattinen synkronointi",
|
||||
"autoSyncTooltip": "Päivitä malliluettelo automaattisesti 24 tunnin välein (konfiguroitavissa kohdassa MODEL_SYNC_INTERVAL_HOURS)",
|
||||
"autoSyncEnabled": "Automaattinen synkronointi käytössä – mallit päivittyvät säännöllisesti",
|
||||
"autoSyncDisabled": "Automaattinen synkronointi poistettu käytöstä",
|
||||
"autoSyncToggleFailed": "Automaattisen synkronoinnin vaihtaminen epäonnistui",
|
||||
"allModelsAlreadyImported": "Kaikki mallit on jo tuotu",
|
||||
"noNewModelsToImport": "Ei uusia malleja tuotavaksi — kaikki mallit ovat jo rekisterissä tai mukautetulla mallilistalla",
|
||||
"skippingExistingModels": "Ohitetaan {count} olemassa olevaa mallia",
|
||||
"applyCodexAuthLocal": "Apply auth",
|
||||
"codexAuthAppliedLocal": "Codex auth.json applied locally",
|
||||
"codexAuthApplyFailed": "Failed to apply Codex auth.json locally",
|
||||
"codexAuthExportFailed": "Failed to export Codex auth.json",
|
||||
"codexAuthExported": "Codex auth.json exported",
|
||||
"exportCodexAuthFile": "Export auth"
|
||||
"modelsPathHint": "Custom models path for validation (e.g. /v4/models)"
|
||||
},
|
||||
"settings": {
|
||||
"title": "Asetukset",
|
||||
@@ -1683,6 +1746,8 @@
|
||||
"darkMode": "Tumma tila",
|
||||
"lightMode": "Valotila",
|
||||
"systemTheme": "Järjestelmän teema",
|
||||
"debugToggle": "Enable Debug Mode",
|
||||
"sidebarVisibilityToggle": "Show Sidebar Items",
|
||||
"enableCache": "Ota välimuisti käyttöön",
|
||||
"cacheTTL": "Välimuisti TTL",
|
||||
"maxCacheSize": "Välimuistin enimmäiskoko",
|
||||
@@ -1691,6 +1756,17 @@
|
||||
"cacheMisses": "Välimuisti puuttuu",
|
||||
"hitRate": "Osumaprosentti",
|
||||
"cacheEntries": "Välimuistin merkinnät",
|
||||
"cacheSettings": "Cache Settings",
|
||||
"semanticCache": "Semantic Cache",
|
||||
"maxEntries": "Max Entries",
|
||||
"ttlMinutes": "TTL (minutes)",
|
||||
"promptCache": "Kehotusvälimuisti",
|
||||
"strategy": "Strategy",
|
||||
"preserveClientCache": "Preserve Client Cache",
|
||||
"enabled": "Enabled",
|
||||
"loading": "Loading...",
|
||||
"saving": "Tallennetaan...",
|
||||
"save": "Save",
|
||||
"circuitBreaker": "Katkaisija",
|
||||
"retryPolicy": "Yritä uudelleen -käytäntö",
|
||||
"maxRetries": "Max Uudelleenyritykset",
|
||||
@@ -1698,6 +1774,10 @@
|
||||
"timeoutMs": "Aikakatkaisu (ms)",
|
||||
"enableSystemPrompt": "Ota järjestelmäkehote käyttöön",
|
||||
"systemPromptText": "Järjestelmäkehoteteksti",
|
||||
"autoDisableBannedAccounts": "Auto-Disable Banned Accounts",
|
||||
"autoDisableDescription": "Permanently mark provider connections as deactivated if they return specific terminal ban signals (e.g. HTTP 403 'verify your account'). This removes them from the combo rotation.",
|
||||
"autoDisableThreshold": "Ban Threshold",
|
||||
"autoDisableThresholdDesc": "Consecutive ban signals required before permanent deactivation.",
|
||||
"enableThinking": "Ota ajattelu käyttöön",
|
||||
"maxThinkingTokens": "Max Thinking Tokens",
|
||||
"enableProxy": "Ota välityspalvelin käyttöön",
|
||||
@@ -1734,6 +1814,9 @@
|
||||
"themeLight": "Kevyt",
|
||||
"themeDark": "Tumma",
|
||||
"themeSystem": "Järjestelmä",
|
||||
"sidebarVisibility": "Hide sidebar items",
|
||||
"sidebarVisibilityDesc": "Hide any sidebar navigation entry to reduce visual clutter.",
|
||||
"sidebarVisibilityHint": "Any sidebar section is hidden automatically when a...",
|
||||
"hideHealthLogs": "Piilota kuntotarkastuslokit",
|
||||
"hideHealthLogsDesc": "Kun PÄÄLLÄ, estä [HealthCheck]-viestit palvelinkonsolissa",
|
||||
"themeAccent": "Teeman väri",
|
||||
@@ -1746,7 +1829,20 @@
|
||||
"themeViolet": "Violetti",
|
||||
"themeOrange": "Oranssi",
|
||||
"themeCyan": "Syaani",
|
||||
"promptCache": "Kehotusvälimuisti",
|
||||
"whitelabeling": "Branding",
|
||||
"whitelabelingDesc": "Customize the application name and logo",
|
||||
"appName": "Application Name",
|
||||
"appNameDesc": "Display name shown in sidebar and browser tab",
|
||||
"customLogo": "Custom Logo URL",
|
||||
"customLogoDesc": "URL to your custom logo image",
|
||||
"uploadLogo": "Upload Logo",
|
||||
"resetLogo": "Reset to Default",
|
||||
"logoPreview": "Preview",
|
||||
"customFavicon": "Browser Favicon",
|
||||
"customFaviconDesc": "URL to your custom favicon (shown in browser tab)",
|
||||
"uploadFavicon": "Upload Favicon",
|
||||
"resetFavicon": "Reset Favicon",
|
||||
"faviconPreview": "Favicon Preview",
|
||||
"flushCache": "Tyhjennä välimuisti",
|
||||
"flushing": "Huuhdellaan…",
|
||||
"size": "Koko",
|
||||
@@ -1804,7 +1900,15 @@
|
||||
"providersBlocked": "{count} toimittaja(t) estetty /modelsista",
|
||||
"blockProviderTitle": "Estä {provider}",
|
||||
"unblockProviderTitle": "Kumoa esto {provider}",
|
||||
"cliFingerprint": "CLI Fingerprint Matching",
|
||||
"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}",
|
||||
"routingStrategy": "Reititysstrategia",
|
||||
"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.",
|
||||
"fillFirst": "Täytä ensin",
|
||||
"fillFirstDesc": "Käytä tilejä tärkeysjärjestyksessä",
|
||||
"roundRobin": "Round Robin",
|
||||
@@ -1899,6 +2003,9 @@
|
||||
"fillModelAndProviders": "Täytä mallin nimi ja toimittajat",
|
||||
"addAtLeastOneProvider": "Lisää vähintään yksi palveluntarjoaja",
|
||||
"comboDefaultsTitle": "Yhdistelmän oletukset",
|
||||
"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.",
|
||||
"globalComboConfig": "Globaali yhdistelmäkokoonpano",
|
||||
"defaultStrategy": "Oletusstrategia",
|
||||
"defaultStrategyDesc": "Sovelletaan uusiin komboihin ilman selkeää strategiaa",
|
||||
@@ -2026,7 +2133,6 @@
|
||||
"unsaved": "tallentamaton",
|
||||
"resetDefaults": "Palauta oletukset",
|
||||
"saveProvider": "Tallenna palveluntarjoaja",
|
||||
"saving": "Tallennetaan...",
|
||||
"model": "Malli",
|
||||
"models": "mallit",
|
||||
"moreProviders": "{count} lisää palveluntarjoajia",
|
||||
@@ -2057,36 +2163,7 @@
|
||||
"customPricingNote": "Voit ohittaa tiettyjen mallien oletushinnoittelun. Mukautetut ohitukset ovat etusijalla automaattisesti tunnistettuihin hinnoitteluun nähden.",
|
||||
"editPricing": "Muokkaa hinnoittelua",
|
||||
"viewFullDetails": "Näytä täydelliset tiedot",
|
||||
"themeCoral": "Koralli",
|
||||
"cliFingerprint": "CLI Fingerprint Matching",
|
||||
"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}",
|
||||
"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.",
|
||||
"sidebarVisibility": "Hide sidebar items",
|
||||
"sidebarVisibilityDesc": "Hide any sidebar navigation entry to reduce visual clutter.",
|
||||
"sidebarVisibilityHint": "Any sidebar section is hidden automatically when a...",
|
||||
"semanticCache": "Semantic Cache",
|
||||
"autoDisableDescription": "Permanently mark provider connections as deactivated if they return specific terminal ban signals (e.g. HTTP 403 'verify your account'). This removes them from the combo rotation.",
|
||||
"preserveClientCache": "Preserve Client Cache",
|
||||
"cacheSettings": "Cache Settings",
|
||||
"autoDisableThreshold": "Ban Threshold",
|
||||
"autoDisableBannedAccounts": "Auto-Disable Banned Accounts",
|
||||
"ttlMinutes": "TTL (minutes)",
|
||||
"maxEntries": "Max Entries",
|
||||
"loading": "Loading...",
|
||||
"save": "Save",
|
||||
"autoDisableThresholdDesc": "Consecutive ban signals required before permanent deactivation.",
|
||||
"debugToggle": "Enable Debug Mode",
|
||||
"sidebarVisibilityToggle": "Show Sidebar Items",
|
||||
"enabled": "Enabled",
|
||||
"strategy": "Strategy"
|
||||
"themeCoral": "Koralli"
|
||||
},
|
||||
"translator": {
|
||||
"title": "Kääntäjä",
|
||||
@@ -2606,6 +2683,8 @@
|
||||
"clientCompatibility": "Asiakasyhteensopivuus",
|
||||
"protocolsToc": "Protokollat",
|
||||
"apiReference": "API-viite",
|
||||
"managementApiReference": "Management API Reference",
|
||||
"managementApiDescription": "Automation endpoints for proxy registry, scope assignments, and legacy proxy migration.",
|
||||
"method": "menetelmä",
|
||||
"path": "Polku",
|
||||
"notes": "Huomautuksia",
|
||||
@@ -2697,10 +2776,19 @@
|
||||
"endpointResponsesNote": "Responses API-päätepiste (Codex, o-sarja).",
|
||||
"endpointModelsNote": "Malliluettelo kaikille liitetyille palveluntarjoajille.",
|
||||
"endpointAudioNote": "Äänen transkriptio (Deepgram, AssemblyAI).",
|
||||
"endpointSpeechNote": "Tekstistä puheeksi luominen (ElevenLabs, OpenAI TTS).",
|
||||
"endpointEmbeddingsNote": "Tekstin upottaminen (OpenAI, Cohere, Voyage).",
|
||||
"endpointImagesNote": "Kuvan luominen (NanoBanana).",
|
||||
"endpointRewriteChatNote": "Uudelleenkirjoitusapuohjelma asiakkaille ilman /v1.",
|
||||
"endpointRewriteResponsesNote": "Uudelleenkirjoitusapuohjelma vastauksille ilman /v1.",
|
||||
"endpointRewriteModelsNote": "Uudelleenkirjoitusapuohjelma mallin etsinnässä ilman /v1:tä.",
|
||||
"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.",
|
||||
"modelPrefixesDescriptionStart": "Käytä toimittajan etuliitettä ennen mallin nimeä reitittääksesi tietylle toimittajalle. Esimerkki:",
|
||||
"modelPrefixesDescriptionEnd": "reitit GitHub Copilotiin.",
|
||||
"provider": "Palveluntarjoaja",
|
||||
@@ -2710,18 +2798,7 @@
|
||||
"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.",
|
||||
"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.",
|
||||
"endpointSpeechNote": "Tekstistä puheeksi luominen (ElevenLabs, OpenAI TTS).",
|
||||
"endpointEmbeddingsNote": "Tekstin upottaminen (OpenAI, Cohere, Voyage)."
|
||||
"troubleshootingOAuth": "OAuth-palveluntarjoajat todenna uudelleen, jos tunnukset vanhenevat. Tarkista palveluntarjoajan kortin tilailmaisin."
|
||||
},
|
||||
"legal": {
|
||||
"privacyPolicy": "Tietosuojakäytäntö",
|
||||
@@ -2783,18 +2860,6 @@
|
||||
"termsSection6Title": "6. Avoin lähdekoodi",
|
||||
"termsSection6Text": "OmniRoute on avoimen lähdekoodin ohjelmisto. Voit vapaasti tarkastaa, muokata ja jakaa sitä sen lisenssiehtojen mukaisesti."
|
||||
},
|
||||
"endpoints": {
|
||||
"tabProxy": "Endpoint Proxy",
|
||||
"tabApiEndpoints": "API Endpoints",
|
||||
"apiEndpointsTitle": "API Endpoints",
|
||||
"apiEndpointsDescription": "Backend API endpoints that can be consumed by other applications and services. This section will list all available REST APIs with documentation and testing capabilities.",
|
||||
"comingSoon": "Coming Soon",
|
||||
"plannedFeatures": "Planned Features",
|
||||
"featureRestApi": "REST API endpoint catalog with interactive documentation",
|
||||
"featureWebhooks": "Webhook configuration and event subscriptions",
|
||||
"featureSwagger": "OpenAPI / Swagger spec auto-generation",
|
||||
"featureAuth": "API key and OAuth scope management per endpoint"
|
||||
},
|
||||
"agents": {
|
||||
"title": "CLI Agents",
|
||||
"description": "Discover installed CLI agents on your system. Add custom agents for auto-detection.",
|
||||
@@ -2849,57 +2914,6 @@
|
||||
"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"
|
||||
},
|
||||
"templateNames": {
|
||||
"simple-chat": "Yksinkertainen chat",
|
||||
"streaming": "Suoratoisto",
|
||||
@@ -2949,67 +2963,67 @@
|
||||
"refresh": "Refresh",
|
||||
"clearAll": "Clear All",
|
||||
"memoryEntries": "Memory Entries",
|
||||
"memoryEntriesSub": "In-memory LRU",
|
||||
"dbEntries": "DB Entries",
|
||||
"dbEntriesSub": "Persisted (SQLite)",
|
||||
"cacheHits": "Cache Hits",
|
||||
"cacheHitsSub": "of {total} total",
|
||||
"tokensSaved": "Tokens Saved",
|
||||
"tokensSavedSub": "Estimated from hits",
|
||||
"hitRate": "Hit Rate",
|
||||
"performance": "Cache Performance",
|
||||
"behavior": "Cache Behavior",
|
||||
"idempotency": "Idempotency Layer",
|
||||
"clearSuccess": "Cache cleared. {count} expired entries removed.",
|
||||
"clearError": "Failed to clear cache.",
|
||||
"unavailable": "Cache unavailable",
|
||||
"unavailableDesc": "Could not fetch cache statistics. Make sure the server is running.",
|
||||
"memoryEntriesSub": "In-memory LRU",
|
||||
"dbEntriesSub": "Persisted (SQLite)",
|
||||
"cacheHitsSub": "of {total} total",
|
||||
"tokensSavedSub": "Estimated from hits",
|
||||
"autoRefresh": "Auto-refreshes every {seconds}s",
|
||||
"hits": "Hits",
|
||||
"misses": "Misses",
|
||||
"total": "Total",
|
||||
"behavior": "Cache Behavior",
|
||||
"behaviorDeterministic": "Only non-streaming requests with temperature=0 are cached.",
|
||||
"behaviorBypass": "Bypass with header {header}.",
|
||||
"behaviorTwoTier": "Two-tier storage: in-memory LRU (fast) + SQLite (persistent across restarts).",
|
||||
"behaviorTtl": "Default TTL: 30 minutes. Configure via {envVar}.",
|
||||
"idempotency": "Idempotency Layer",
|
||||
"activeDedupKeys": "Active Dedup Keys",
|
||||
"dedupWindow": "Dedup Window",
|
||||
"inputTokens": "Input Tokens",
|
||||
"clearSuccess": "Cache cleared. {count} expired entries removed.",
|
||||
"clearError": "Failed to clear cache.",
|
||||
"unavailable": "Cache unavailable",
|
||||
"unavailableDesc": "Could not fetch cache statistics. Make sure the server is running.",
|
||||
"promptCache": "Prompt Cache (Provider-Side)",
|
||||
"cachedRequests": "Cached Requests",
|
||||
"cacheHitRate": "Cache Hit Rate",
|
||||
"cachedTokens": "Cached Tokens",
|
||||
"cacheCreationTokens": "Cache Creation Tokens",
|
||||
"cacheMetrics": "Prompt Cache Metrics",
|
||||
"withCacheControl": "With Cache Control",
|
||||
"cachedTokensRead": "Cached Tokens (Read)",
|
||||
"cacheCreationWrite": "Cache Creation (Write)",
|
||||
"cacheReuseRatio": "Cache Reuse Ratio",
|
||||
"cacheReuseRatioDesc": "Cached tokens / Total input tokens",
|
||||
"estCostSaved": "Est. Cost Saved",
|
||||
"requestsShort": "reqs",
|
||||
"inputShort": "In",
|
||||
"resetting": "Resetting...",
|
||||
"cachedTokensCol": "Cached",
|
||||
"search": "Search",
|
||||
"cacheReuseRatioDesc": "Cached tokens / Total input tokens",
|
||||
"resetMetrics": "Reset Metrics",
|
||||
"loading": "Loading...",
|
||||
"cachedRequests": "Cached Requests",
|
||||
"model": "Model",
|
||||
"cached": "Cached",
|
||||
"actions": "Actions",
|
||||
"trend24h": "Cache Trend (24h)",
|
||||
"cachedShort": "Cached",
|
||||
"cacheCreation": "Creation",
|
||||
"byProvider": "Breakdown by Provider",
|
||||
"created": "Created",
|
||||
"cacheCreationTokens": "Cache Creation Tokens",
|
||||
"withCacheControl": "With Cache Control",
|
||||
"searchEntries": "Search entries...",
|
||||
"cacheReuseRatio": "Cache Reuse Ratio",
|
||||
"estCostSaved": "Est. Cost Saved",
|
||||
"cachedTokens": "Cached Tokens",
|
||||
"requests": "Requests",
|
||||
"signature": "Signature",
|
||||
"cacheCreationWrite": "Cache Creation (Write)",
|
||||
"expires": "Expires",
|
||||
"writeShort": "Write",
|
||||
"cacheHitRate": "Cache Hit Rate",
|
||||
"cacheMetrics": "Prompt Cache Metrics",
|
||||
"overview": "Overview",
|
||||
"promptCache": "Prompt Cache (Provider-Side)",
|
||||
"resetting": "Resetting...",
|
||||
"resetMetrics": "Reset Metrics",
|
||||
"byProvider": "Breakdown by Provider",
|
||||
"provider": "Provider",
|
||||
"cachedTokensRead": "Cached Tokens (Read)",
|
||||
"requests": "Requests",
|
||||
"inputTokens": "Input Tokens",
|
||||
"cachedTokensCol": "Cached",
|
||||
"cacheCreation": "Creation",
|
||||
"trend24h": "Cache Trend (24h)",
|
||||
"cached": "Cached",
|
||||
"overview": "Overview",
|
||||
"entries": "Entries",
|
||||
"noEntries": "No cache entries found"
|
||||
"searchEntries": "Search entries...",
|
||||
"search": "Search",
|
||||
"loading": "Loading...",
|
||||
"noEntries": "No cache entries found",
|
||||
"signature": "Signature",
|
||||
"model": "Model",
|
||||
"created": "Created",
|
||||
"expires": "Expires",
|
||||
"actions": "Actions"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -143,6 +143,7 @@
|
||||
"dashboard": "Tableau de bord",
|
||||
"providers": "Fournisseurs",
|
||||
"combos": "Combinaisons",
|
||||
"autoCombo": "Auto Combo",
|
||||
"usage": "Utilisation",
|
||||
"analytics": "Analyse",
|
||||
"costs": "Coûts",
|
||||
@@ -152,8 +153,12 @@
|
||||
"media": "Médias",
|
||||
"settings": "Paramètres",
|
||||
"translator": "Traducteur",
|
||||
"playground": "Playground",
|
||||
"searchTools": "Search Tools",
|
||||
"agents": "Agents",
|
||||
"docs": "Documentation",
|
||||
"issues": "Problèmes",
|
||||
"endpoints": "Points d'accès",
|
||||
"apiManager": "Gestionnaire d'API",
|
||||
"logs": "Journaux",
|
||||
"auditLog": "Journal d'audit",
|
||||
@@ -165,6 +170,11 @@
|
||||
"debug": "Débogage",
|
||||
"system": "Système",
|
||||
"help": "Aide",
|
||||
"primarySection": "Main",
|
||||
"cliSection": "CLI",
|
||||
"debugSection": "Debug",
|
||||
"systemSection": "System",
|
||||
"helpSection": "Help",
|
||||
"serverDisconnected": "Serveur déconnecté",
|
||||
"serverDisconnectedMsg": "Le serveur proxy a été arrêté ou est en train de redémarrer.",
|
||||
"expandSidebar": "Développer la barre latérale",
|
||||
@@ -180,19 +190,9 @@
|
||||
"themeViolet": "Violette",
|
||||
"themeOrange": "Orange",
|
||||
"themeCyan": "Cyan",
|
||||
"endpoints": "Points d'accès",
|
||||
"playground": "Playground",
|
||||
"agents": "Agents",
|
||||
"cliToolsShort": "Outils",
|
||||
"autoCombo": "Auto Combo",
|
||||
"searchTools": "Search Tools",
|
||||
"cache": "Cache",
|
||||
"cacheShort": "Cache",
|
||||
"cliSection": "CLI",
|
||||
"debugSection": "Debug",
|
||||
"helpSection": "Help",
|
||||
"primarySection": "Main",
|
||||
"systemSection": "System"
|
||||
"cacheShort": "Cache"
|
||||
},
|
||||
"themesPage": {
|
||||
"title": "Thèmes",
|
||||
@@ -220,6 +220,10 @@
|
||||
"homeDescription": "Bienvenue sur OmniRoute",
|
||||
"endpoint": "Points d'accès",
|
||||
"endpointDescription": "Gérer les points d'accès proxy, MCP, A2A et les points d'accès API",
|
||||
"mcp": "MCP",
|
||||
"mcpDescription": "Model Context Protocol server management and tools",
|
||||
"a2a": "A2A",
|
||||
"a2aDescription": "Agent-to-Agent protocol tasks and observability",
|
||||
"settings": "Paramètres",
|
||||
"settingsDescription": "Gérez vos préférences",
|
||||
"openaiCompatible": "Compatible avec OpenAI",
|
||||
@@ -227,11 +231,7 @@
|
||||
"media": "Médias",
|
||||
"mediaDescription": "Générez des images, des vidéos et de la musique",
|
||||
"themes": "Thèmes",
|
||||
"themesDescription": "Choisissez un thème de couleur pour l'ensemble du panneau du tableau de bord",
|
||||
"mcp": "MCP",
|
||||
"mcpDescription": "Model Context Protocol server management and tools",
|
||||
"a2a": "A2A",
|
||||
"a2aDescription": "Agent-to-Agent protocol tasks and observability"
|
||||
"themesDescription": "Choisissez un thème de couleur pour l'ensemble du panneau du tableau de bord"
|
||||
},
|
||||
"home": {
|
||||
"quickStart": "Démarrage rapide",
|
||||
@@ -332,6 +332,26 @@
|
||||
"keyCreatedNote": "Copiez et stockez cette clé maintenant – elle ne sera plus affichée.",
|
||||
"done": "Terminé",
|
||||
"savePermissions": "Enregistrer les autorisations",
|
||||
"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",
|
||||
"allowAll": "Tout autoriser",
|
||||
"restrict": "Restreindre",
|
||||
"allowAllInfo": "Cette clé peut accéder à tous les modèles disponibles.",
|
||||
@@ -356,6 +376,7 @@
|
||||
"failedUpdatePermissionsRetry": "Échec de la mise à jour des autorisations. Veuillez réessayer.",
|
||||
"unknownProvider": "inconnu",
|
||||
"copyMaskedKey": "Copier la clé masquée",
|
||||
"keyOnlyAvailableAtCreation": "Full key available only at creation time — copy it when you first create the key",
|
||||
"modelsCount": "{count, plural, one {# model} other {# models}}",
|
||||
"lastUsedOn": "Dernier : {date}",
|
||||
"editPermissions": "Modifier les autorisations",
|
||||
@@ -365,28 +386,7 @@
|
||||
"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é",
|
||||
"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"
|
||||
"selectedCount": "{count} sélectionné"
|
||||
},
|
||||
"auditLog": {
|
||||
"title": "Journal d'audit",
|
||||
@@ -425,6 +425,57 @@
|
||||
"videoDescription": "Create videos with AnimateDiff, Stable Video Diffusion via ComfyUI or SD WebUI.",
|
||||
"musicDescription": "Compose music using Stable Audio Open or MusicGen via ComfyUI."
|
||||
},
|
||||
"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",
|
||||
"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",
|
||||
"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"
|
||||
},
|
||||
"cliTools": {
|
||||
"title": "Outils CLI",
|
||||
"noActiveProviders": "Aucun fournisseur actif",
|
||||
@@ -497,6 +548,11 @@
|
||||
"antigravityStep2Prefix": "2. Ajouter",
|
||||
"antigravityStep2Suffix": "à votre fichier hosts en tant que 127.0.0.1.",
|
||||
"antigravityStep3": "3. Ouvrez Antigravity et les requêtes seront proxy.",
|
||||
"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.",
|
||||
"sudoPasswordRequiredTitle": "Mot de passe Sudo requis",
|
||||
"sudoPasswordHint": "Un mot de passe administrateur est requis pour modifier les paramètres du fichier hôte et du proxy système.",
|
||||
"enterSudoPassword": "Entrez le mot de passe sudo",
|
||||
@@ -569,6 +625,22 @@
|
||||
"openClawManualConfiguration": "Griffe ouverte - Configuration manuelle",
|
||||
"clineManualConfiguration": "Configuration manuelle en ligne",
|
||||
"kiloManualConfiguration": "Configuration manuelle du code kilo",
|
||||
"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."
|
||||
},
|
||||
"toolDescriptions": {
|
||||
"antigravity": "IDE Google Antigravity avec MITM",
|
||||
"claude": "Anthropique Claude Code CLI",
|
||||
@@ -706,27 +778,6 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"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": {
|
||||
@@ -988,7 +1039,12 @@
|
||||
"settingsApi": "Settings API",
|
||||
"categoryCore": "APIs Principales",
|
||||
"categoryMedia": "Médias et Multi-Modal",
|
||||
"categorySearch": "Search & Discovery",
|
||||
"categoryUtility": "Utilitaires et Gestion",
|
||||
"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.",
|
||||
"enableCloudTitle": "Activer le proxy cloud",
|
||||
"whatYouGet": "Ce que vous obtiendrez",
|
||||
"cloudBenefitAccess": "Accédez à votre API depuis n'importe où dans le monde",
|
||||
@@ -1021,6 +1077,25 @@
|
||||
"cloudWorkerUnreachable": "Impossible d'atteindre le travailleur cloud. Assurez-vous que le service cloud est en cours d'exécution (npm run dev dans /cloud).",
|
||||
"connectionFailed": "La connexion a échoué",
|
||||
"syncFailed": "Échec de la synchronisation des données cloud",
|
||||
"cloudflaredTitle": "Cloudflare Quick Tunnel",
|
||||
"cloudflaredDescription": "Create a Cloudflare Quick Tunnel for this endpoint to make it accessible from the internet without configuring DNS.",
|
||||
"cloudflaredUrlNotice": "Creates a temporary Cloudflare Quick Tunnel. URLs change after restart.",
|
||||
"cloudflaredEnable": "Enable Tunnel",
|
||||
"cloudflaredInstallAndEnable": "Install & Enable",
|
||||
"cloudflaredDisable": "Stop Tunnel",
|
||||
"cloudflaredRunning": "Running",
|
||||
"cloudflaredStarting": "Starting",
|
||||
"cloudflaredStoppedState": "Stopped",
|
||||
"cloudflaredNotInstalled": "Not installed",
|
||||
"cloudflaredUnsupported": "Unsupported",
|
||||
"cloudflaredError": "Error",
|
||||
"cloudflaredStarted": "Cloudflare tunnel started",
|
||||
"cloudflaredStopped": "Cloudflare tunnel stopped",
|
||||
"cloudflaredRequestFailed": "Failed to update Cloudflare tunnel",
|
||||
"cloudflaredTemporaryNote": "Quick Tunnel URLs are temporary and will change after restart.",
|
||||
"cloudflaredUnsupportedNote": "This platform is not supported for managed installation.",
|
||||
"cloudflaredIdleNote": "Create a temporary Cloudflare Quick Tunnel for this endpoint.",
|
||||
"cloudflaredLastError": "Last error: {error}",
|
||||
"providerModelsTitle": "{provider} — Modèles",
|
||||
"noModelsForProvider": "Aucun modèle disponible pour ce fournisseur.",
|
||||
"chat": "Discuter",
|
||||
@@ -1055,31 +1130,19 @@
|
||||
"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",
|
||||
"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.",
|
||||
"cloudflaredDescription": "Create a Cloudflare Quick Tunnel for this endpoint to make it accessible from the internet without configuring DNS.",
|
||||
"cloudflaredDisable": "Stop Tunnel",
|
||||
"cloudflaredEnable": "Enable Tunnel",
|
||||
"cloudflaredError": "Error",
|
||||
"cloudflaredIdleNote": "Create a temporary Cloudflare Quick Tunnel for this endpoint.",
|
||||
"cloudflaredInstallAndEnable": "Install & Enable",
|
||||
"cloudflaredLastError": "Last error: {error}",
|
||||
"cloudflaredNotInstalled": "Not installed",
|
||||
"cloudflaredRequestFailed": "Failed to update Cloudflare tunnel",
|
||||
"cloudflaredRunning": "Running",
|
||||
"cloudflaredStarted": "Cloudflare tunnel started",
|
||||
"cloudflaredStarting": "Starting",
|
||||
"cloudflaredStopped": "Cloudflare tunnel stopped",
|
||||
"cloudflaredStoppedState": "Stopped",
|
||||
"cloudflaredTemporaryNote": "Quick Tunnel URLs are temporary and will change after restart.",
|
||||
"cloudflaredTitle": "Cloudflare Quick Tunnel",
|
||||
"cloudflaredUnsupported": "Unsupported",
|
||||
"cloudflaredUnsupportedNote": "This platform is not supported for managed installation.",
|
||||
"cloudflaredUrlNotice": "Creates a temporary Cloudflare Quick Tunnel. URLs change after restart."
|
||||
"completionsLegacyDesc": "Legacy OpenAI text completions — accepts both prompt string and messages array format"
|
||||
},
|
||||
"endpoints": {
|
||||
"tabProxy": "Endpoint Proxy",
|
||||
"tabApiEndpoints": "Points d'accès API",
|
||||
"apiEndpointsTitle": "Points d'accès API",
|
||||
"apiEndpointsDescription": "Points d'accès API backend pouvant être consommés par d'autres applications et services.",
|
||||
"comingSoon": "Bientôt disponible",
|
||||
"plannedFeatures": "Fonctionnalités prévues",
|
||||
"featureRestApi": "Catalogue d'API REST avec documentation interactive",
|
||||
"featureWebhooks": "Configuration de webhooks et abonnements aux événements",
|
||||
"featureSwagger": "Génération automatique de spécifications OpenAPI / Swagger",
|
||||
"featureAuth": "Gestion des clés API et des portées OAuth par point d'accès"
|
||||
},
|
||||
"mcpDashboard": {
|
||||
"loading": "Chargement du tableau de bord MCP...",
|
||||
@@ -1251,6 +1314,12 @@
|
||||
"issuesLabel": "Problèmes détectés",
|
||||
"operational": "Opérationnel",
|
||||
"providers": "Fournisseurs",
|
||||
"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.",
|
||||
"activeProviders": "{count} active",
|
||||
"activeProvidersHint": "Fournisseurs configurés actuellement activés pour les demandes de routage.",
|
||||
"monitoredProviders": "{count} monitored",
|
||||
"monitoredProvidersHint": "Fournisseurs actuellement suivis par des moniteurs de santé des disjoncteurs.",
|
||||
"healthyCount": "{count} en bonne santé",
|
||||
"nodeVersion": "Nœud {version}",
|
||||
"failures": "Échec de {count}",
|
||||
@@ -1269,13 +1338,7 @@
|
||||
"resetAllTitle": "Réinitialisez tous les disjoncteurs à l’état sain",
|
||||
"resetting": "Réinitialisation...",
|
||||
"resetAll": "Tout réinitialiser",
|
||||
"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."
|
||||
"until": "Jusqu'au {time}"
|
||||
},
|
||||
"limits": {
|
||||
"title": "Limites et quotas",
|
||||
@@ -1437,6 +1500,7 @@
|
||||
"clearing": "Dégagement...",
|
||||
"until": "Jusqu'au {time}",
|
||||
"providerTestFailed": "Le test du fournisseur a échoué",
|
||||
"providerTestTimeout": "Provider test timed out — too many connections to test at once",
|
||||
"modeTest": "{mode}Test",
|
||||
"passedCount": "{count} réussi",
|
||||
"failedCount": "{count} a échoué",
|
||||
@@ -1478,6 +1542,14 @@
|
||||
"chatCompletions": "Achèvements des discussions",
|
||||
"importingModels": "Importation...",
|
||||
"importFromModels": "Importer depuis /models",
|
||||
"allModelsAlreadyImported": "Tous les modèles sont déjà importés",
|
||||
"noNewModelsToImport": "Aucun nouveau modèle à importer — tous les modèles sont déjà dans le registre ou la liste de modèles personnalisés",
|
||||
"skippingExistingModels": "Ignorance de {count} modèles existants",
|
||||
"autoSync": "Synchronisation automatique",
|
||||
"autoSyncTooltip": "Actualise automatiquement la liste des modèles toutes les 24h (configurable via MODEL_SYNC_INTERVAL_HOURS)",
|
||||
"autoSyncEnabled": "Synchronisation automatique activée — les modèles seront actualisés périodiquement",
|
||||
"autoSyncDisabled": "Synchronisation automatique désactivée",
|
||||
"autoSyncToggleFailed": "Échec de l'activation de la synchronisation automatique",
|
||||
"clearAllModels": "Effacer tous les modèles",
|
||||
"clearAllModelsConfirm": "Êtes-vous sûr de vouloir supprimer tous les modèles pour ce fournisseur?",
|
||||
"clearAllModelsSuccess": "Tous les modèles effacés",
|
||||
@@ -1531,6 +1603,8 @@
|
||||
"addFirstConnectionHint": "Ajoutez votre première connexion pour commencer",
|
||||
"addConnection": "Ajouter une connexion",
|
||||
"availableModels": "Modèles disponibles",
|
||||
"builtInModels": "Built-in models",
|
||||
"builtInModelsHint": "Registry models for this provider. Use the pencil to set compatibility options.",
|
||||
"pageAutoRefresh": "La page s'actualisera automatiquement...",
|
||||
"statusDisabled": "désactivé",
|
||||
"statusConnected": "connecté",
|
||||
@@ -1571,6 +1645,26 @@
|
||||
"openRouterModelPlaceholder": "anthropique/claude-3-opus",
|
||||
"customModels": "Modèles personnalisés",
|
||||
"customModelsHint": "Ajoutez des ID de modèle qui ne figurent pas dans la liste par défaut. Ceux-ci seront disponibles pour le routage.",
|
||||
"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",
|
||||
"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": "ID du modèle",
|
||||
"customModelPlaceholder": "par ex. gpt-4.5-turbo",
|
||||
"loading": "Chargement...",
|
||||
@@ -1614,52 +1708,21 @@
|
||||
"compatibleBaseUrlHint": "Root URL of your {type}-compatible API. Use Advanced Settings for custom endpoint paths.",
|
||||
"apiKeyForCheck": "Clé API (pour vérification)",
|
||||
"compatibleProdPlaceholder": "{type} Compatible (Prod)",
|
||||
"providerTestTimeout": "Provider test timed out — too many connections to test at once",
|
||||
"tokenRefreshed": "Token refreshed successfully",
|
||||
"tokenRefreshFailed": "Token refresh failed",
|
||||
"applyCodexAuthLocal": "Apply auth",
|
||||
"exportCodexAuthFile": "Export auth",
|
||||
"codexAuthAppliedLocal": "Codex auth.json applied locally",
|
||||
"codexAuthApplyFailed": "Failed to apply Codex auth.json locally",
|
||||
"codexAuthExported": "Codex auth.json exported",
|
||||
"codexAuthExportFailed": "Failed to export Codex auth.json",
|
||||
"advancedSettings": "Advanced Settings",
|
||||
"chatPathLabel": "Chat Endpoint Path",
|
||||
"chatPathPlaceholder": "/chat/completions",
|
||||
"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)",
|
||||
"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",
|
||||
"autoSync": "Synchronisation automatique",
|
||||
"autoSyncTooltip": "Actualise automatiquement la liste des modèles toutes les 24h (configurable via MODEL_SYNC_INTERVAL_HOURS)",
|
||||
"autoSyncEnabled": "Synchronisation automatique activée — les modèles seront actualisés périodiquement",
|
||||
"autoSyncDisabled": "Synchronisation automatique désactivée",
|
||||
"autoSyncToggleFailed": "Échec de l'activation de la synchronisation automatique",
|
||||
"allModelsAlreadyImported": "Tous les modèles sont déjà importés",
|
||||
"noNewModelsToImport": "Aucun nouveau modèle à importer — tous les modèles sont déjà dans le registre ou la liste de modèles personnalisés",
|
||||
"skippingExistingModels": "Ignorance de {count} modèles existants",
|
||||
"applyCodexAuthLocal": "Apply auth",
|
||||
"codexAuthAppliedLocal": "Codex auth.json applied locally",
|
||||
"codexAuthApplyFailed": "Failed to apply Codex auth.json locally",
|
||||
"codexAuthExportFailed": "Failed to export Codex auth.json",
|
||||
"codexAuthExported": "Codex auth.json exported",
|
||||
"exportCodexAuthFile": "Export auth"
|
||||
"modelsPathHint": "Custom models path for validation (e.g. /v4/models)"
|
||||
},
|
||||
"settings": {
|
||||
"title": "Paramètres",
|
||||
@@ -1683,6 +1746,8 @@
|
||||
"darkMode": "Mode sombre",
|
||||
"lightMode": "Mode lumière",
|
||||
"systemTheme": "Thème système",
|
||||
"debugToggle": "Enable Debug Mode",
|
||||
"sidebarVisibilityToggle": "Show Sidebar Items",
|
||||
"enableCache": "Activer le cache",
|
||||
"cacheTTL": "Durée de vie du cache",
|
||||
"maxCacheSize": "Taille maximale du cache",
|
||||
@@ -1691,6 +1756,17 @@
|
||||
"cacheMisses": "Manques de cache",
|
||||
"hitRate": "Taux de réussite",
|
||||
"cacheEntries": "Entrées du cache",
|
||||
"cacheSettings": "Cache Settings",
|
||||
"semanticCache": "Semantic Cache",
|
||||
"maxEntries": "Max Entries",
|
||||
"ttlMinutes": "TTL (minutes)",
|
||||
"promptCache": "Cache d'invite",
|
||||
"strategy": "Strategy",
|
||||
"preserveClientCache": "Preserve Client Cache",
|
||||
"enabled": "Enabled",
|
||||
"loading": "Loading...",
|
||||
"saving": "Sauvegarde...",
|
||||
"save": "Save",
|
||||
"circuitBreaker": "Disjoncteur",
|
||||
"retryPolicy": "Politique de nouvelle tentative",
|
||||
"maxRetries": "Nombre maximal de tentatives",
|
||||
@@ -1698,6 +1774,10 @@
|
||||
"timeoutMs": "Délai d'expiration (ms)",
|
||||
"enableSystemPrompt": "Activer l'invite système",
|
||||
"systemPromptText": "Texte d'invite du système",
|
||||
"autoDisableBannedAccounts": "Auto-Disable Banned Accounts",
|
||||
"autoDisableDescription": "Permanently mark provider connections as deactivated if they return specific terminal ban signals (e.g. HTTP 403 'verify your account'). This removes them from the combo rotation.",
|
||||
"autoDisableThreshold": "Ban Threshold",
|
||||
"autoDisableThresholdDesc": "Consecutive ban signals required before permanent deactivation.",
|
||||
"enableThinking": "Permettre la réflexion",
|
||||
"maxThinkingTokens": "Jetons de réflexion maximum",
|
||||
"enableProxy": "Activer le proxy",
|
||||
@@ -1734,6 +1814,9 @@
|
||||
"themeLight": "Lumière",
|
||||
"themeDark": "Sombre",
|
||||
"themeSystem": "Système",
|
||||
"sidebarVisibility": "Hide sidebar items",
|
||||
"sidebarVisibilityDesc": "Hide any sidebar navigation entry to reduce visual clutter.",
|
||||
"sidebarVisibilityHint": "Any sidebar section is hidden automatically when a...",
|
||||
"hideHealthLogs": "Masquer les journaux de contrôle de santé",
|
||||
"hideHealthLogsDesc": "Lorsqu'il est activé, supprimez les messages [HealthCheck] dans la console du serveur",
|
||||
"themeAccent": "Couleur du thème",
|
||||
@@ -1746,7 +1829,20 @@
|
||||
"themeViolet": "Violet",
|
||||
"themeOrange": "Orange",
|
||||
"themeCyan": "Cyan",
|
||||
"promptCache": "Cache d'invite",
|
||||
"whitelabeling": "Branding",
|
||||
"whitelabelingDesc": "Customize the application name and logo",
|
||||
"appName": "Application Name",
|
||||
"appNameDesc": "Display name shown in sidebar and browser tab",
|
||||
"customLogo": "Custom Logo URL",
|
||||
"customLogoDesc": "URL to your custom logo image",
|
||||
"uploadLogo": "Upload Logo",
|
||||
"resetLogo": "Reset to Default",
|
||||
"logoPreview": "Preview",
|
||||
"customFavicon": "Browser Favicon",
|
||||
"customFaviconDesc": "URL to your custom favicon (shown in browser tab)",
|
||||
"uploadFavicon": "Upload Favicon",
|
||||
"resetFavicon": "Reset Favicon",
|
||||
"faviconPreview": "Favicon Preview",
|
||||
"flushCache": "Vider le cache",
|
||||
"flushing": "Rinçage…",
|
||||
"size": "Taille",
|
||||
@@ -1804,7 +1900,15 @@
|
||||
"providersBlocked": "{count} fournisseur(s) bloqué(s) sur /models",
|
||||
"blockProviderTitle": "Bloquer {provider}",
|
||||
"unblockProviderTitle": "Débloquer {provider}",
|
||||
"cliFingerprint": "CLI Fingerprint Matching",
|
||||
"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}",
|
||||
"routingStrategy": "Stratégie de routage",
|
||||
"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.",
|
||||
"fillFirst": "Remplir d'abord",
|
||||
"fillFirstDesc": "Utiliser les comptes par ordre de priorité",
|
||||
"roundRobin": "Tournoi à la ronde",
|
||||
@@ -1899,6 +2003,9 @@
|
||||
"fillModelAndProviders": "Veuillez remplir le nom du modèle et les fournisseurs",
|
||||
"addAtLeastOneProvider": "Ajouter au moins un fournisseur",
|
||||
"comboDefaultsTitle": "Valeurs par défaut des combos",
|
||||
"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.",
|
||||
"globalComboConfig": "Configuration combinée globale",
|
||||
"defaultStrategy": "Stratégie par défaut",
|
||||
"defaultStrategyDesc": "Appliqué aux nouveaux combos sans stratégie explicite",
|
||||
@@ -2026,7 +2133,6 @@
|
||||
"unsaved": "non enregistré",
|
||||
"resetDefaults": "Réinitialiser les paramètres par défaut",
|
||||
"saveProvider": "Enregistrer le fournisseur",
|
||||
"saving": "Sauvegarde...",
|
||||
"model": "Modèle",
|
||||
"models": "modèles",
|
||||
"moreProviders": "{count} autres fournisseurs",
|
||||
@@ -2057,36 +2163,7 @@
|
||||
"customPricingNote": "Vous pouvez remplacer le prix par défaut pour des modèles spécifiques. Les remplacements personnalisés ont la priorité sur les prix détectés automatiquement.",
|
||||
"editPricing": "Modifier le prix",
|
||||
"viewFullDetails": "Afficher tous les détails",
|
||||
"themeCoral": "Corail",
|
||||
"cliFingerprint": "CLI Fingerprint Matching",
|
||||
"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}",
|
||||
"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.",
|
||||
"sidebarVisibility": "Hide sidebar items",
|
||||
"sidebarVisibilityDesc": "Hide any sidebar navigation entry to reduce visual clutter.",
|
||||
"sidebarVisibilityHint": "Any sidebar section is hidden automatically when a...",
|
||||
"semanticCache": "Semantic Cache",
|
||||
"autoDisableDescription": "Permanently mark provider connections as deactivated if they return specific terminal ban signals (e.g. HTTP 403 'verify your account'). This removes them from the combo rotation.",
|
||||
"preserveClientCache": "Preserve Client Cache",
|
||||
"cacheSettings": "Cache Settings",
|
||||
"autoDisableThreshold": "Ban Threshold",
|
||||
"autoDisableBannedAccounts": "Auto-Disable Banned Accounts",
|
||||
"ttlMinutes": "TTL (minutes)",
|
||||
"maxEntries": "Max Entries",
|
||||
"loading": "Loading...",
|
||||
"save": "Save",
|
||||
"autoDisableThresholdDesc": "Consecutive ban signals required before permanent deactivation.",
|
||||
"debugToggle": "Enable Debug Mode",
|
||||
"sidebarVisibilityToggle": "Show Sidebar Items",
|
||||
"enabled": "Enabled",
|
||||
"strategy": "Strategy"
|
||||
"themeCoral": "Corail"
|
||||
},
|
||||
"translator": {
|
||||
"title": "Traducteur",
|
||||
@@ -2505,14 +2582,14 @@
|
||||
"backToLogin": "Retour à la connexion",
|
||||
"forgotPassword": "Mot de passe oublié ?",
|
||||
"defaultPasswordHint": "Default password: 123456 (unless INITIAL_PASSWORD was set)",
|
||||
"Authorization": "Autorisation",
|
||||
"Content-Disposition": "Disposition du contenu",
|
||||
"waitingForAuthorization": "Waiting for authorization...",
|
||||
"waitingForGoogleAuthorization": "Waiting for Google authorization...",
|
||||
"waitingForOpenAIAuthorization": "Waiting for OpenAI authorization...",
|
||||
"waitingForAntigravityAuthorization": "Waiting for Antigravity authorization...",
|
||||
"waitingForQoderAuthorization": "Waiting for Qoder authorization...",
|
||||
"exchangingCodeForTokens": "Exchanging code for tokens...",
|
||||
"Authorization": "Autorisation",
|
||||
"Content-Disposition": "Disposition du contenu"
|
||||
"exchangingCodeForTokens": "Exchanging code for tokens..."
|
||||
},
|
||||
"landing": {
|
||||
"brandName": "OmniRoute",
|
||||
@@ -2606,6 +2683,8 @@
|
||||
"clientCompatibility": "Compatibilité client",
|
||||
"protocolsToc": "Protocoles",
|
||||
"apiReference": "Référence API",
|
||||
"managementApiReference": "Management API Reference",
|
||||
"managementApiDescription": "Automation endpoints for proxy registry, scope assignments, and legacy proxy migration.",
|
||||
"method": "Méthode",
|
||||
"path": "Chemin",
|
||||
"notes": "Remarques",
|
||||
@@ -2697,10 +2776,19 @@
|
||||
"endpointResponsesNote": "Point de terminaison de l’API de réponses (Codex, série o).",
|
||||
"endpointModelsNote": "Catalogue modèle pour tous les fournisseurs connectés.",
|
||||
"endpointAudioNote": "Transcription audio (Deepgram, AssemblyAI).",
|
||||
"endpointSpeechNote": "Génération texte-vers-discours (ElevenLabs, OpenAI TTS).",
|
||||
"endpointEmbeddingsNote": "Génération d'embeddings texte (OpenAI, Cohere, Voyage).",
|
||||
"endpointImagesNote": "Génération d'images (NanoBanana).",
|
||||
"endpointRewriteChatNote": "Aide à la réécriture pour les clients sans /v1.",
|
||||
"endpointRewriteResponsesNote": "Assistant de réécriture pour les réponses sans /v1.",
|
||||
"endpointRewriteModelsNote": "Aide à la réécriture pour la découverte de modèles sans /v1.",
|
||||
"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.",
|
||||
"modelPrefixesDescriptionStart": "Utilisez le préfixe du fournisseur avant le nom du modèle pour acheminer vers un fournisseur spécifique. Exemple :",
|
||||
"modelPrefixesDescriptionEnd": "itinéraires vers GitHub Copilot.",
|
||||
"provider": "Fournisseur",
|
||||
@@ -2710,18 +2798,7 @@
|
||||
"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.",
|
||||
"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.",
|
||||
"endpointSpeechNote": "Génération texte-vers-discours (ElevenLabs, OpenAI TTS).",
|
||||
"endpointEmbeddingsNote": "Génération d'embeddings texte (OpenAI, Cohere, Voyage)."
|
||||
"troubleshootingOAuth": "Pour les fournisseurs OAuth, réauthentifiez-vous si les jetons expirent. Vérifiez l'indicateur d'état de la carte du fournisseur."
|
||||
},
|
||||
"legal": {
|
||||
"privacyPolicy": "Politique de confidentialité",
|
||||
@@ -2783,18 +2860,6 @@
|
||||
"termsSection6Title": "6. Ouvrir la source",
|
||||
"termsSection6Text": "OmniRoute est un logiciel open source. Vous êtes libre de l'inspecter, de le modifier et de le distribuer selon les termes de sa licence."
|
||||
},
|
||||
"endpoints": {
|
||||
"tabProxy": "Endpoint Proxy",
|
||||
"tabApiEndpoints": "Points d'accès API",
|
||||
"apiEndpointsTitle": "Points d'accès API",
|
||||
"apiEndpointsDescription": "Points d'accès API backend pouvant être consommés par d'autres applications et services.",
|
||||
"comingSoon": "Bientôt disponible",
|
||||
"plannedFeatures": "Fonctionnalités prévues",
|
||||
"featureRestApi": "Catalogue d'API REST avec documentation interactive",
|
||||
"featureWebhooks": "Configuration de webhooks et abonnements aux événements",
|
||||
"featureSwagger": "Génération automatique de spécifications OpenAPI / Swagger",
|
||||
"featureAuth": "Gestion des clés API et des portées OAuth par point d'accès"
|
||||
},
|
||||
"agents": {
|
||||
"title": "CLI Agents",
|
||||
"description": "Discover installed CLI agents on your system. Add custom agents for auto-detection.",
|
||||
@@ -2849,57 +2914,6 @@
|
||||
"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"
|
||||
},
|
||||
"templateNames": {
|
||||
"simple-chat": "Simple Chat",
|
||||
"streaming": "Streaming",
|
||||
@@ -2949,67 +2963,67 @@
|
||||
"refresh": "Refresh",
|
||||
"clearAll": "Clear All",
|
||||
"memoryEntries": "Memory Entries",
|
||||
"memoryEntriesSub": "In-memory LRU",
|
||||
"dbEntries": "DB Entries",
|
||||
"dbEntriesSub": "Persisted (SQLite)",
|
||||
"cacheHits": "Cache Hits",
|
||||
"cacheHitsSub": "of {total} total",
|
||||
"tokensSaved": "Tokens Saved",
|
||||
"tokensSavedSub": "Estimated from hits",
|
||||
"hitRate": "Hit Rate",
|
||||
"performance": "Cache Performance",
|
||||
"behavior": "Cache Behavior",
|
||||
"idempotency": "Idempotency Layer",
|
||||
"clearSuccess": "Cache cleared. {count} expired entries removed.",
|
||||
"clearError": "Failed to clear cache.",
|
||||
"unavailable": "Cache unavailable",
|
||||
"unavailableDesc": "Could not fetch cache statistics. Make sure the server is running.",
|
||||
"memoryEntriesSub": "In-memory LRU",
|
||||
"dbEntriesSub": "Persisted (SQLite)",
|
||||
"cacheHitsSub": "of {total} total",
|
||||
"tokensSavedSub": "Estimated from hits",
|
||||
"autoRefresh": "Auto-refreshes every {seconds}s",
|
||||
"hits": "Hits",
|
||||
"misses": "Misses",
|
||||
"total": "Total",
|
||||
"behavior": "Cache Behavior",
|
||||
"behaviorDeterministic": "Only non-streaming requests with temperature=0 are cached.",
|
||||
"behaviorBypass": "Bypass with header {header}.",
|
||||
"behaviorTwoTier": "Two-tier storage: in-memory LRU (fast) + SQLite (persistent across restarts).",
|
||||
"behaviorTtl": "Default TTL: 30 minutes. Configure via {envVar}.",
|
||||
"idempotency": "Idempotency Layer",
|
||||
"activeDedupKeys": "Active Dedup Keys",
|
||||
"dedupWindow": "Dedup Window",
|
||||
"inputTokens": "Input Tokens",
|
||||
"clearSuccess": "Cache cleared. {count} expired entries removed.",
|
||||
"clearError": "Failed to clear cache.",
|
||||
"unavailable": "Cache unavailable",
|
||||
"unavailableDesc": "Could not fetch cache statistics. Make sure the server is running.",
|
||||
"promptCache": "Prompt Cache (Provider-Side)",
|
||||
"cachedRequests": "Cached Requests",
|
||||
"cacheHitRate": "Cache Hit Rate",
|
||||
"cachedTokens": "Cached Tokens",
|
||||
"cacheCreationTokens": "Cache Creation Tokens",
|
||||
"cacheMetrics": "Prompt Cache Metrics",
|
||||
"withCacheControl": "With Cache Control",
|
||||
"cachedTokensRead": "Cached Tokens (Read)",
|
||||
"cacheCreationWrite": "Cache Creation (Write)",
|
||||
"cacheReuseRatio": "Cache Reuse Ratio",
|
||||
"cacheReuseRatioDesc": "Cached tokens / Total input tokens",
|
||||
"estCostSaved": "Est. Cost Saved",
|
||||
"requestsShort": "reqs",
|
||||
"inputShort": "In",
|
||||
"resetting": "Resetting...",
|
||||
"cachedTokensCol": "Cached",
|
||||
"search": "Search",
|
||||
"cacheReuseRatioDesc": "Cached tokens / Total input tokens",
|
||||
"resetMetrics": "Reset Metrics",
|
||||
"loading": "Loading...",
|
||||
"cachedRequests": "Cached Requests",
|
||||
"model": "Model",
|
||||
"cached": "Cached",
|
||||
"actions": "Actions",
|
||||
"trend24h": "Cache Trend (24h)",
|
||||
"cachedShort": "Cached",
|
||||
"cacheCreation": "Creation",
|
||||
"byProvider": "Breakdown by Provider",
|
||||
"created": "Created",
|
||||
"cacheCreationTokens": "Cache Creation Tokens",
|
||||
"withCacheControl": "With Cache Control",
|
||||
"searchEntries": "Search entries...",
|
||||
"cacheReuseRatio": "Cache Reuse Ratio",
|
||||
"estCostSaved": "Est. Cost Saved",
|
||||
"cachedTokens": "Cached Tokens",
|
||||
"requests": "Requests",
|
||||
"signature": "Signature",
|
||||
"cacheCreationWrite": "Cache Creation (Write)",
|
||||
"expires": "Expires",
|
||||
"writeShort": "Write",
|
||||
"cacheHitRate": "Cache Hit Rate",
|
||||
"cacheMetrics": "Prompt Cache Metrics",
|
||||
"overview": "Overview",
|
||||
"promptCache": "Prompt Cache (Provider-Side)",
|
||||
"resetting": "Resetting...",
|
||||
"resetMetrics": "Reset Metrics",
|
||||
"byProvider": "Breakdown by Provider",
|
||||
"provider": "Provider",
|
||||
"cachedTokensRead": "Cached Tokens (Read)",
|
||||
"requests": "Requests",
|
||||
"inputTokens": "Input Tokens",
|
||||
"cachedTokensCol": "Cached",
|
||||
"cacheCreation": "Creation",
|
||||
"trend24h": "Cache Trend (24h)",
|
||||
"cached": "Cached",
|
||||
"overview": "Overview",
|
||||
"entries": "Entries",
|
||||
"noEntries": "No cache entries found"
|
||||
"searchEntries": "Search entries...",
|
||||
"search": "Search",
|
||||
"loading": "Loading...",
|
||||
"noEntries": "No cache entries found",
|
||||
"signature": "Signature",
|
||||
"model": "Model",
|
||||
"created": "Created",
|
||||
"expires": "Expires",
|
||||
"actions": "Actions"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -143,6 +143,7 @@
|
||||
"dashboard": "לוח מחוונים",
|
||||
"providers": "ספקים",
|
||||
"combos": "שילובים",
|
||||
"autoCombo": "Auto Combo",
|
||||
"usage": "שימוש",
|
||||
"analytics": "אנליטיקס",
|
||||
"costs": "עלויות",
|
||||
@@ -152,8 +153,12 @@
|
||||
"media": "מדיה",
|
||||
"settings": "הגדרות",
|
||||
"translator": "מתרגם",
|
||||
"playground": "Playground",
|
||||
"searchTools": "Search Tools",
|
||||
"agents": "סוכנים",
|
||||
"docs": "מסמכים",
|
||||
"issues": "בעיות",
|
||||
"endpoints": "נקודות קצה",
|
||||
"apiManager": "מנהל API",
|
||||
"logs": "יומנים",
|
||||
"auditLog": "יומן ביקורת",
|
||||
@@ -165,6 +170,11 @@
|
||||
"debug": "ניפוי באגים",
|
||||
"system": "מערכת",
|
||||
"help": "עזרה",
|
||||
"primarySection": "Main",
|
||||
"cliSection": "CLI",
|
||||
"debugSection": "Debug",
|
||||
"systemSection": "System",
|
||||
"helpSection": "Help",
|
||||
"serverDisconnected": "השרת מנותק",
|
||||
"serverDisconnectedMsg": "שרת ה-proxy נעצר או מופעל מחדש.",
|
||||
"expandSidebar": "הרחב את סרגל הצד",
|
||||
@@ -180,19 +190,9 @@
|
||||
"themeViolet": "Violet",
|
||||
"themeOrange": "Orange",
|
||||
"themeCyan": "Cyan",
|
||||
"endpoints": "נקודות קצה",
|
||||
"playground": "Playground",
|
||||
"agents": "סוכנים",
|
||||
"cliToolsShort": "כלים",
|
||||
"autoCombo": "Auto Combo",
|
||||
"searchTools": "Search Tools",
|
||||
"cache": "Cache",
|
||||
"cacheShort": "Cache",
|
||||
"cliSection": "CLI",
|
||||
"debugSection": "Debug",
|
||||
"helpSection": "Help",
|
||||
"primarySection": "Main",
|
||||
"systemSection": "System"
|
||||
"cacheShort": "Cache"
|
||||
},
|
||||
"themesPage": {
|
||||
"title": "Themes",
|
||||
@@ -220,6 +220,10 @@
|
||||
"homeDescription": "ברוכים הבאים ל-OmniRoute",
|
||||
"endpoint": "Endpoints",
|
||||
"endpointDescription": "Manage proxy endpoints, MCP, A2A, and API endpoints",
|
||||
"mcp": "MCP",
|
||||
"mcpDescription": "Model Context Protocol server management and tools",
|
||||
"a2a": "A2A",
|
||||
"a2aDescription": "Agent-to-Agent protocol tasks and observability",
|
||||
"settings": "הגדרות",
|
||||
"settingsDescription": "נהל את ההעדפות שלך",
|
||||
"openaiCompatible": "תואם OpenAI",
|
||||
@@ -227,11 +231,7 @@
|
||||
"media": "Media",
|
||||
"mediaDescription": "Generate images, videos, and music",
|
||||
"themes": "Themes",
|
||||
"themesDescription": "Choose a color theme for the whole dashboard panel",
|
||||
"mcp": "MCP",
|
||||
"mcpDescription": "Model Context Protocol server management and tools",
|
||||
"a2a": "A2A",
|
||||
"a2aDescription": "Agent-to-Agent protocol tasks and observability"
|
||||
"themesDescription": "Choose a color theme for the whole dashboard panel"
|
||||
},
|
||||
"home": {
|
||||
"quickStart": "התחלה מהירה",
|
||||
@@ -332,6 +332,26 @@
|
||||
"keyCreatedNote": "העתק ואחסן את המפתח הזה עכשיו - הוא לא יוצג שוב.",
|
||||
"done": "בוצע",
|
||||
"savePermissions": "שמור הרשאות",
|
||||
"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",
|
||||
"allowAll": "אפשר הכל",
|
||||
"restrict": "הגבל",
|
||||
"allowAllInfo": "מפתח זה יכול לגשת לכל הדגמים הזמינים.",
|
||||
@@ -356,6 +376,7 @@
|
||||
"failedUpdatePermissionsRetry": "עדכון ההרשאות נכשל. אנא נסה שוב.",
|
||||
"unknownProvider": "לא ידוע",
|
||||
"copyMaskedKey": "העתק מפתח מסכה",
|
||||
"keyOnlyAvailableAtCreation": "Full key available only at creation time — copy it when you first create the key",
|
||||
"modelsCount": "{count, plural, one {# model} other {# models}}",
|
||||
"lastUsedOn": "אחרון: {date}",
|
||||
"editPermissions": "ערוך הרשאות",
|
||||
@@ -365,28 +386,7 @@
|
||||
"permissionsTitle": "הרשאות: {name}",
|
||||
"allowAllDesc": "מפתח זה יכול לגשת לכל הדגמים הזמינים.",
|
||||
"restrictDesc": "מפתח זה יכול לגשת ל-{selectedCount} מתוך דגמי {totalModels}.",
|
||||
"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"
|
||||
"selectedCount": "{count} נבחר"
|
||||
},
|
||||
"auditLog": {
|
||||
"title": "יומן ביקורת",
|
||||
@@ -425,6 +425,57 @@
|
||||
"videoDescription": "Create videos with AnimateDiff, Stable Video Diffusion via ComfyUI or SD WebUI.",
|
||||
"musicDescription": "Compose music using Stable Audio Open or MusicGen via ComfyUI."
|
||||
},
|
||||
"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",
|
||||
"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",
|
||||
"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"
|
||||
},
|
||||
"cliTools": {
|
||||
"title": "כלי CLI",
|
||||
"noActiveProviders": "אין ספקים פעילים",
|
||||
@@ -497,6 +548,11 @@
|
||||
"antigravityStep2Prefix": "2. הוסף",
|
||||
"antigravityStep2Suffix": "לקובץ המארחים שלך כ-127.0.0.1.",
|
||||
"antigravityStep3": "3. פתח את Antigravity ובקשות יסופקו.",
|
||||
"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.",
|
||||
"sudoPasswordRequiredTitle": "נדרשת סיסמת סודו",
|
||||
"sudoPasswordHint": "נדרשת סיסמת מנהל כדי לשנות את הגדרות ה-Proxy של המארחים ומערכת ה-proxy.",
|
||||
"enterSudoPassword": "הזן את סיסמת הסודו",
|
||||
@@ -569,6 +625,22 @@
|
||||
"openClawManualConfiguration": "פתח טופר - תצורה ידנית",
|
||||
"clineManualConfiguration": "תצורה ידנית של Cline",
|
||||
"kiloManualConfiguration": "תצורה ידנית של קילו קוד",
|
||||
"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."
|
||||
},
|
||||
"toolDescriptions": {
|
||||
"antigravity": "Google Antigravity IDE עם MITM",
|
||||
"claude": "קלוד האנתרופי קוד CLI",
|
||||
@@ -706,27 +778,6 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"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": {
|
||||
@@ -988,7 +1039,12 @@
|
||||
"settingsApi": "Settings API",
|
||||
"categoryCore": "ממשקי API ליבה",
|
||||
"categoryMedia": "מדיה ומולטי-מודלי",
|
||||
"categorySearch": "Search & Discovery",
|
||||
"categoryUtility": "כלים וניהול",
|
||||
"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.",
|
||||
"enableCloudTitle": "אפשר Cloud Proxy",
|
||||
"whatYouGet": "מה תקבל",
|
||||
"cloudBenefitAccess": "גש ל-API שלך מכל מקום בעולם",
|
||||
@@ -1021,6 +1077,25 @@
|
||||
"cloudWorkerUnreachable": "לא ניתן היה להגיע לעובד הענן. ודא ששירות הענן פועל (npm run dev in /cloud).",
|
||||
"connectionFailed": "החיבור נכשל",
|
||||
"syncFailed": "סנכרון נתוני ענן נכשל",
|
||||
"cloudflaredTitle": "Cloudflare Quick Tunnel",
|
||||
"cloudflaredDescription": "Create a Cloudflare Quick Tunnel for this endpoint to make it accessible from the internet without configuring DNS.",
|
||||
"cloudflaredUrlNotice": "Creates a temporary Cloudflare Quick Tunnel. URLs change after restart.",
|
||||
"cloudflaredEnable": "Enable Tunnel",
|
||||
"cloudflaredInstallAndEnable": "Install & Enable",
|
||||
"cloudflaredDisable": "Stop Tunnel",
|
||||
"cloudflaredRunning": "Running",
|
||||
"cloudflaredStarting": "Starting",
|
||||
"cloudflaredStoppedState": "Stopped",
|
||||
"cloudflaredNotInstalled": "Not installed",
|
||||
"cloudflaredUnsupported": "Unsupported",
|
||||
"cloudflaredError": "Error",
|
||||
"cloudflaredStarted": "Cloudflare tunnel started",
|
||||
"cloudflaredStopped": "Cloudflare tunnel stopped",
|
||||
"cloudflaredRequestFailed": "Failed to update Cloudflare tunnel",
|
||||
"cloudflaredTemporaryNote": "Quick Tunnel URLs are temporary and will change after restart.",
|
||||
"cloudflaredUnsupportedNote": "This platform is not supported for managed installation.",
|
||||
"cloudflaredIdleNote": "Create a temporary Cloudflare Quick Tunnel for this endpoint.",
|
||||
"cloudflaredLastError": "Last error: {error}",
|
||||
"providerModelsTitle": "{provider} — דגמים",
|
||||
"noModelsForProvider": "אין דגמים זמינים עבור ספק זה.",
|
||||
"chat": "צ'אט",
|
||||
@@ -1055,31 +1130,19 @@
|
||||
"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",
|
||||
"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.",
|
||||
"cloudflaredDescription": "Create a Cloudflare Quick Tunnel for this endpoint to make it accessible from the internet without configuring DNS.",
|
||||
"cloudflaredDisable": "Stop Tunnel",
|
||||
"cloudflaredEnable": "Enable Tunnel",
|
||||
"cloudflaredError": "Error",
|
||||
"cloudflaredIdleNote": "Create a temporary Cloudflare Quick Tunnel for this endpoint.",
|
||||
"cloudflaredInstallAndEnable": "Install & Enable",
|
||||
"cloudflaredLastError": "Last error: {error}",
|
||||
"cloudflaredNotInstalled": "Not installed",
|
||||
"cloudflaredRequestFailed": "Failed to update Cloudflare tunnel",
|
||||
"cloudflaredRunning": "Running",
|
||||
"cloudflaredStarted": "Cloudflare tunnel started",
|
||||
"cloudflaredStarting": "Starting",
|
||||
"cloudflaredStopped": "Cloudflare tunnel stopped",
|
||||
"cloudflaredStoppedState": "Stopped",
|
||||
"cloudflaredTemporaryNote": "Quick Tunnel URLs are temporary and will change after restart.",
|
||||
"cloudflaredTitle": "Cloudflare Quick Tunnel",
|
||||
"cloudflaredUnsupported": "Unsupported",
|
||||
"cloudflaredUnsupportedNote": "This platform is not supported for managed installation.",
|
||||
"cloudflaredUrlNotice": "Creates a temporary Cloudflare Quick Tunnel. URLs change after restart."
|
||||
"completionsLegacyDesc": "Legacy OpenAI text completions — accepts both prompt string and messages array format"
|
||||
},
|
||||
"endpoints": {
|
||||
"tabProxy": "Endpoint Proxy",
|
||||
"tabApiEndpoints": "API Endpoints",
|
||||
"apiEndpointsTitle": "API Endpoints",
|
||||
"apiEndpointsDescription": "Backend API endpoints that can be consumed by other applications and services. This section will list all available REST APIs with documentation and testing capabilities.",
|
||||
"comingSoon": "Coming Soon",
|
||||
"plannedFeatures": "Planned Features",
|
||||
"featureRestApi": "REST API endpoint catalog with interactive documentation",
|
||||
"featureWebhooks": "Webhook configuration and event subscriptions",
|
||||
"featureSwagger": "OpenAPI / Swagger spec auto-generation",
|
||||
"featureAuth": "API key and OAuth scope management per endpoint"
|
||||
},
|
||||
"mcpDashboard": {
|
||||
"loading": "Loading MCP dashboard...",
|
||||
@@ -1251,6 +1314,12 @@
|
||||
"issuesLabel": "בעיות זוהו",
|
||||
"operational": "מבצעי",
|
||||
"providers": "ספקים",
|
||||
"configuredProvidersLabel": "מוגדר בלוח המחוונים",
|
||||
"configuredProvidersHint": "ספקים עם אישורים שנשמרו ב-/dashboard/ספקים, ללא קשר למצב זמן הריצה.",
|
||||
"activeProviders": "{count} active",
|
||||
"activeProvidersHint": "ספקים מוגדרים מופעלים כעת עבור בקשות ניתוב.",
|
||||
"monitoredProviders": "{count} monitored",
|
||||
"monitoredProvidersHint": "ספקים שנמצאים כעת במעקב על-ידי מסכי תקינות מפסקים.",
|
||||
"healthyCount": "{count} בריא",
|
||||
"nodeVersion": "צומת {version}",
|
||||
"failures": "{count} כשל",
|
||||
@@ -1269,13 +1338,7 @@
|
||||
"resetAllTitle": "אפס את כל המפסקים למצב תקין",
|
||||
"resetting": "מאפס...",
|
||||
"resetAll": "אפס הכל",
|
||||
"until": "עד {time}",
|
||||
"activeProviders": "{count} active",
|
||||
"monitoredProviders": "{count} monitored",
|
||||
"configuredProvidersLabel": "מוגדר בלוח המחוונים",
|
||||
"configuredProvidersHint": "ספקים עם אישורים שנשמרו ב-/dashboard/ספקים, ללא קשר למצב זמן הריצה.",
|
||||
"activeProvidersHint": "ספקים מוגדרים מופעלים כעת עבור בקשות ניתוב.",
|
||||
"monitoredProvidersHint": "ספקים שנמצאים כעת במעקב על-ידי מסכי תקינות מפסקים."
|
||||
"until": "עד {time}"
|
||||
},
|
||||
"limits": {
|
||||
"title": "מגבלות ומכסות",
|
||||
@@ -1437,6 +1500,7 @@
|
||||
"clearing": "מנקה...",
|
||||
"until": "עד {time}",
|
||||
"providerTestFailed": "בדיקת הספק נכשלה",
|
||||
"providerTestTimeout": "Provider test timed out — too many connections to test at once",
|
||||
"modeTest": "{mode} מבחן",
|
||||
"passedCount": "{count} עבר",
|
||||
"failedCount": "{count} נכשל",
|
||||
@@ -1478,6 +1542,14 @@
|
||||
"chatCompletions": "השלמת צ'אט",
|
||||
"importingModels": "מייבא...",
|
||||
"importFromModels": "ייבוא מ /models",
|
||||
"allModelsAlreadyImported": "כל הדגמים כבר מיובאים",
|
||||
"noNewModelsToImport": "אין דגמים חדשים לייבוא — כל הדגמים כבר קיימים ברישום או ברשימת הדגמים המותאמים",
|
||||
"skippingExistingModels": "מדלג על {count} דגמים קיימים",
|
||||
"autoSync": "סנכרון אוטומטי",
|
||||
"autoSyncTooltip": "רענן אוטומטית את רשימת הדגמים כל 24 שעות (ניתן להגדרה באמצעות MODEL_SYNC_INTERVAL_HOURS)",
|
||||
"autoSyncEnabled": "סנכרון אוטומטי מופעל - הדגמים יתרעננו מעת לעת",
|
||||
"autoSyncDisabled": "הסנכרון האוטומטי מושבת",
|
||||
"autoSyncToggleFailed": "החלפת הסנכרון האוטומטי נכשלה",
|
||||
"clearAllModels": "נקה את כל הדגמים",
|
||||
"clearAllModelsConfirm": "האם אתה בטוח שברצונך להסיר את כל הדגמים עבור ספק זה? לא ניתן לבטל זאת.",
|
||||
"clearAllModelsSuccess": "כל הדגמים נוקו",
|
||||
@@ -1531,6 +1603,8 @@
|
||||
"addFirstConnectionHint": "הוסף את החיבור הראשון שלך כדי להתחיל",
|
||||
"addConnection": "הוסף חיבור",
|
||||
"availableModels": "דגמים זמינים",
|
||||
"builtInModels": "Built-in models",
|
||||
"builtInModelsHint": "Registry models for this provider. Use the pencil to set compatibility options.",
|
||||
"pageAutoRefresh": "הדף יתרענן אוטומטית...",
|
||||
"statusDisabled": "נכים",
|
||||
"statusConnected": "מחובר",
|
||||
@@ -1571,6 +1645,26 @@
|
||||
"openRouterModelPlaceholder": "אנתרופי/קלאוד-3-אופוס",
|
||||
"customModels": "דגמים מותאמים אישית",
|
||||
"customModelsHint": "הוסף מזהי דגמים שאינם ברשימת ברירת המחדל. אלה יהיו זמינים לניתוב.",
|
||||
"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",
|
||||
"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": "מזהה דגם",
|
||||
"customModelPlaceholder": "למשל gpt-4.5-טורבו",
|
||||
"loading": "טוען...",
|
||||
@@ -1614,52 +1708,21 @@
|
||||
"compatibleBaseUrlHint": "Root URL of your {type}-compatible API. Use Advanced Settings for custom endpoint paths.",
|
||||
"apiKeyForCheck": "מפתח API (לבדיקה)",
|
||||
"compatibleProdPlaceholder": "{type} תואם (פרוד)",
|
||||
"providerTestTimeout": "Provider test timed out — too many connections to test at once",
|
||||
"tokenRefreshed": "Token refreshed successfully",
|
||||
"tokenRefreshFailed": "Token refresh failed",
|
||||
"applyCodexAuthLocal": "Apply auth",
|
||||
"exportCodexAuthFile": "Export auth",
|
||||
"codexAuthAppliedLocal": "Codex auth.json applied locally",
|
||||
"codexAuthApplyFailed": "Failed to apply Codex auth.json locally",
|
||||
"codexAuthExported": "Codex auth.json exported",
|
||||
"codexAuthExportFailed": "Failed to export Codex auth.json",
|
||||
"advancedSettings": "Advanced Settings",
|
||||
"chatPathLabel": "Chat Endpoint Path",
|
||||
"chatPathPlaceholder": "/chat/completions",
|
||||
"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)",
|
||||
"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",
|
||||
"autoSync": "סנכרון אוטומטי",
|
||||
"autoSyncTooltip": "רענן אוטומטית את רשימת הדגמים כל 24 שעות (ניתן להגדרה באמצעות MODEL_SYNC_INTERVAL_HOURS)",
|
||||
"autoSyncEnabled": "סנכרון אוטומטי מופעל - הדגמים יתרעננו מעת לעת",
|
||||
"autoSyncDisabled": "הסנכרון האוטומטי מושבת",
|
||||
"autoSyncToggleFailed": "החלפת הסנכרון האוטומטי נכשלה",
|
||||
"allModelsAlreadyImported": "כל הדגמים כבר מיובאים",
|
||||
"noNewModelsToImport": "אין דגמים חדשים לייבוא — כל הדגמים כבר קיימים ברישום או ברשימת הדגמים המותאמים",
|
||||
"skippingExistingModels": "מדלג על {count} דגמים קיימים",
|
||||
"applyCodexAuthLocal": "Apply auth",
|
||||
"codexAuthAppliedLocal": "Codex auth.json applied locally",
|
||||
"codexAuthApplyFailed": "Failed to apply Codex auth.json locally",
|
||||
"codexAuthExportFailed": "Failed to export Codex auth.json",
|
||||
"codexAuthExported": "Codex auth.json exported",
|
||||
"exportCodexAuthFile": "Export auth"
|
||||
"modelsPathHint": "Custom models path for validation (e.g. /v4/models)"
|
||||
},
|
||||
"settings": {
|
||||
"title": "הגדרות",
|
||||
@@ -1683,6 +1746,8 @@
|
||||
"darkMode": "מצב כהה",
|
||||
"lightMode": "מצב אור",
|
||||
"systemTheme": "ערכת נושא של המערכת",
|
||||
"debugToggle": "Enable Debug Mode",
|
||||
"sidebarVisibilityToggle": "Show Sidebar Items",
|
||||
"enableCache": "אפשר מטמון",
|
||||
"cacheTTL": "מטמון TTL",
|
||||
"maxCacheSize": "גודל מטמון מקסימלי",
|
||||
@@ -1691,6 +1756,17 @@
|
||||
"cacheMisses": "מטמון פספוסים",
|
||||
"hitRate": "שיעור כניסות",
|
||||
"cacheEntries": "ערכים במטמון",
|
||||
"cacheSettings": "Cache Settings",
|
||||
"semanticCache": "Semantic Cache",
|
||||
"maxEntries": "Max Entries",
|
||||
"ttlMinutes": "TTL (minutes)",
|
||||
"promptCache": "הפקודה מטמון",
|
||||
"strategy": "Strategy",
|
||||
"preserveClientCache": "Preserve Client Cache",
|
||||
"enabled": "Enabled",
|
||||
"loading": "Loading...",
|
||||
"saving": "שומר...",
|
||||
"save": "Save",
|
||||
"circuitBreaker": "מפסק חשמל",
|
||||
"retryPolicy": "מדיניות נסה שוב",
|
||||
"maxRetries": "מקסימום מנסה שוב",
|
||||
@@ -1698,6 +1774,10 @@
|
||||
"timeoutMs": "זמן קצוב (ms)",
|
||||
"enableSystemPrompt": "הפעל את בקשת המערכת",
|
||||
"systemPromptText": "טקסט הודעת מערכת",
|
||||
"autoDisableBannedAccounts": "Auto-Disable Banned Accounts",
|
||||
"autoDisableDescription": "Permanently mark provider connections as deactivated if they return specific terminal ban signals (e.g. HTTP 403 'verify your account'). This removes them from the combo rotation.",
|
||||
"autoDisableThreshold": "Ban Threshold",
|
||||
"autoDisableThresholdDesc": "Consecutive ban signals required before permanent deactivation.",
|
||||
"enableThinking": "אפשר חשיבה",
|
||||
"maxThinkingTokens": "מקס חשיבה אסימונים",
|
||||
"enableProxy": "אפשר פרוקסי",
|
||||
@@ -1734,6 +1814,9 @@
|
||||
"themeLight": "אור",
|
||||
"themeDark": "כהה",
|
||||
"themeSystem": "מערכת",
|
||||
"sidebarVisibility": "Hide sidebar items",
|
||||
"sidebarVisibilityDesc": "Hide any sidebar navigation entry to reduce visual clutter.",
|
||||
"sidebarVisibilityHint": "Any sidebar section is hidden automatically when a...",
|
||||
"hideHealthLogs": "הסתר יומני בדיקת בריאות",
|
||||
"hideHealthLogsDesc": "כאשר מופעל, דחק הודעות [HealthCheck] במסוף השרת",
|
||||
"themeAccent": "צבע ערכת נושא",
|
||||
@@ -1746,7 +1829,20 @@
|
||||
"themeViolet": "סגול",
|
||||
"themeOrange": "כתום",
|
||||
"themeCyan": "ציאן",
|
||||
"promptCache": "הפקודה מטמון",
|
||||
"whitelabeling": "Branding",
|
||||
"whitelabelingDesc": "Customize the application name and logo",
|
||||
"appName": "Application Name",
|
||||
"appNameDesc": "Display name shown in sidebar and browser tab",
|
||||
"customLogo": "Custom Logo URL",
|
||||
"customLogoDesc": "URL to your custom logo image",
|
||||
"uploadLogo": "Upload Logo",
|
||||
"resetLogo": "Reset to Default",
|
||||
"logoPreview": "Preview",
|
||||
"customFavicon": "Browser Favicon",
|
||||
"customFaviconDesc": "URL to your custom favicon (shown in browser tab)",
|
||||
"uploadFavicon": "Upload Favicon",
|
||||
"resetFavicon": "Reset Favicon",
|
||||
"faviconPreview": "Favicon Preview",
|
||||
"flushCache": "לשטוף את המטמון",
|
||||
"flushing": "שוטף…",
|
||||
"size": "גודל",
|
||||
@@ -1804,7 +1900,15 @@
|
||||
"providersBlocked": "{count} ספק(ים) חסומים מ/מודלים",
|
||||
"blockProviderTitle": "חסום {provider}",
|
||||
"unblockProviderTitle": "בטל את החסימה של {provider}",
|
||||
"cliFingerprint": "CLI Fingerprint Matching",
|
||||
"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}",
|
||||
"routingStrategy": "אסטרטגיית ניתוב",
|
||||
"routingAdvancedGuideTitle": "הנחיית ניתוב מתקדמת",
|
||||
"routingAdvancedGuideHint1": "השתמש ב-Fill First לקבלת עדיפות צפויה, ב-Round Robin להגינות, וב-P2C עבור חוסן חביון.",
|
||||
"routingAdvancedGuideHint2": "אם הספקים משתנים באיכות/עלות, התחל עם Cost Opt עבור עבודת רקע והפחות בשימוש עבור בלאי מאוזן.",
|
||||
"fillFirst": "תמלא קודם",
|
||||
"fillFirstDesc": "השתמש בחשבונות בסדר עדיפות",
|
||||
"roundRobin": "רובין עגול",
|
||||
@@ -1899,6 +2003,9 @@
|
||||
"fillModelAndProviders": "נא למלא את שם הדגם והספקים",
|
||||
"addAtLeastOneProvider": "הוסף לפחות ספק אחד",
|
||||
"comboDefaultsTitle": "ברירת מחדל משולבת",
|
||||
"comboDefaultsGuideTitle": "כיצד לכוונן ברירות מחדל משולבות",
|
||||
"comboDefaultsGuideHint1": "שמור על ניסיונות חוזרים נמוכים בזרימות עם אחזור נמוך; להגדיל את הזמן הקצוב רק עבור משימות דור ארוך.",
|
||||
"comboDefaultsGuideHint2": "השתמש בעקיפות ספק כאשר ספק אחד זקוק להתנהגות שונה של זמן קצוב/ניסיון חוזר מאשר ברירות מחדל גלובליות.",
|
||||
"globalComboConfig": "תצורת משולבת גלובלית",
|
||||
"defaultStrategy": "אסטרטגיית ברירת מחדל",
|
||||
"defaultStrategyDesc": "מיושם על שילובים חדשים ללא אסטרטגיה מפורשת",
|
||||
@@ -2026,7 +2133,6 @@
|
||||
"unsaved": "לא נשמר",
|
||||
"resetDefaults": "אפס את ברירת המחדל",
|
||||
"saveProvider": "שמור ספק",
|
||||
"saving": "שומר...",
|
||||
"model": "דגם",
|
||||
"models": "דגמים",
|
||||
"moreProviders": "{count} ספקים נוספים",
|
||||
@@ -2057,36 +2163,7 @@
|
||||
"customPricingNote": "אתה יכול לעקוף את תמחור ברירת המחדל עבור דגמים ספציפיים. עקיפות מותאמות אישית מקבלות עדיפות על פני תמחור שזוהה אוטומטית.",
|
||||
"editPricing": "ערוך תמחור",
|
||||
"viewFullDetails": "צפה בפרטים המלאים",
|
||||
"themeCoral": "אלמוג",
|
||||
"cliFingerprint": "CLI Fingerprint Matching",
|
||||
"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}",
|
||||
"routingAdvancedGuideTitle": "הנחיית ניתוב מתקדמת",
|
||||
"routingAdvancedGuideHint1": "השתמש ב-Fill First לקבלת עדיפות צפויה, ב-Round Robin להגינות, וב-P2C עבור חוסן חביון.",
|
||||
"routingAdvancedGuideHint2": "אם הספקים משתנים באיכות/עלות, התחל עם Cost Opt עבור עבודת רקע והפחות בשימוש עבור בלאי מאוזן.",
|
||||
"comboDefaultsGuideTitle": "כיצד לכוונן ברירות מחדל משולבות",
|
||||
"comboDefaultsGuideHint1": "שמור על ניסיונות חוזרים נמוכים בזרימות עם אחזור נמוך; להגדיל את הזמן הקצוב רק עבור משימות דור ארוך.",
|
||||
"comboDefaultsGuideHint2": "השתמש בעקיפות ספק כאשר ספק אחד זקוק להתנהגות שונה של זמן קצוב/ניסיון חוזר מאשר ברירות מחדל גלובליות.",
|
||||
"sidebarVisibility": "Hide sidebar items",
|
||||
"sidebarVisibilityDesc": "Hide any sidebar navigation entry to reduce visual clutter.",
|
||||
"sidebarVisibilityHint": "Any sidebar section is hidden automatically when a...",
|
||||
"semanticCache": "Semantic Cache",
|
||||
"autoDisableDescription": "Permanently mark provider connections as deactivated if they return specific terminal ban signals (e.g. HTTP 403 'verify your account'). This removes them from the combo rotation.",
|
||||
"preserveClientCache": "Preserve Client Cache",
|
||||
"cacheSettings": "Cache Settings",
|
||||
"autoDisableThreshold": "Ban Threshold",
|
||||
"autoDisableBannedAccounts": "Auto-Disable Banned Accounts",
|
||||
"ttlMinutes": "TTL (minutes)",
|
||||
"maxEntries": "Max Entries",
|
||||
"loading": "Loading...",
|
||||
"save": "Save",
|
||||
"autoDisableThresholdDesc": "Consecutive ban signals required before permanent deactivation.",
|
||||
"debugToggle": "Enable Debug Mode",
|
||||
"sidebarVisibilityToggle": "Show Sidebar Items",
|
||||
"enabled": "Enabled",
|
||||
"strategy": "Strategy"
|
||||
"themeCoral": "אלמוג"
|
||||
},
|
||||
"translator": {
|
||||
"title": "מתרגם",
|
||||
@@ -2606,6 +2683,8 @@
|
||||
"clientCompatibility": "תאימות ללקוח",
|
||||
"protocolsToc": "Protocols",
|
||||
"apiReference": "הפניה ל-API",
|
||||
"managementApiReference": "Management API Reference",
|
||||
"managementApiDescription": "Automation endpoints for proxy registry, scope assignments, and legacy proxy migration.",
|
||||
"method": "שיטה",
|
||||
"path": "נתיב",
|
||||
"notes": "הערות",
|
||||
@@ -2697,10 +2776,19 @@
|
||||
"endpointResponsesNote": "נקודת קצה של תגובות API (Codex, o-series).",
|
||||
"endpointModelsNote": "קטלוג דגמים לכל הספקים המחוברים.",
|
||||
"endpointAudioNote": "תמלול אודיו (Deepgram, AssemblyAI).",
|
||||
"endpointSpeechNote": "יצירת טקסט לדיבור (ElevenLabs, OpenAI TTS).",
|
||||
"endpointEmbeddingsNote": "יצירת הטבעת טקסט (OpenAI, Cohere, Voyage).",
|
||||
"endpointImagesNote": "יצירת תמונות (NanoBanana).",
|
||||
"endpointRewriteChatNote": "עוזר שכתוב עבור לקוחות ללא /v1.",
|
||||
"endpointRewriteResponsesNote": "שכתוב עוזר לתגובות ללא /v1.",
|
||||
"endpointRewriteModelsNote": "עוזר שכתוב לגילוי מודל ללא /v1.",
|
||||
"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.",
|
||||
"modelPrefixesDescriptionStart": "השתמש בקידומת הספק לפני שם הדגם כדי לנתב לספק ספציפי. דוגמה:",
|
||||
"modelPrefixesDescriptionEnd": "מסלולים אל GitHub Copilot.",
|
||||
"provider": "ספק",
|
||||
@@ -2710,18 +2798,7 @@
|
||||
"troubleshootingCodexFamily": "עבור מודלים של משפחת GitHub Codex, שמור את הדגם בתור gh/codex-model; הנתב בוחר / תגובות באופן אוטומטי.",
|
||||
"troubleshootingTestConnection": "השתמש בלוח מחוונים > ספקים > בדוק חיבור לפני בדיקה מ-IDEs או לקוחות חיצוניים.",
|
||||
"troubleshootingCircuitBreaker": "אם ספק מציג מפסק פתוח, המתן להתקררות או בדוק את דף הבריאות לפרטים.",
|
||||
"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.",
|
||||
"endpointSpeechNote": "יצירת טקסט לדיבור (ElevenLabs, OpenAI TTS).",
|
||||
"endpointEmbeddingsNote": "יצירת הטבעת טקסט (OpenAI, Cohere, Voyage)."
|
||||
"troubleshootingOAuth": "עבור ספקי OAuth, בצע אימות מחדש אם פג תוקפם של אסימונים. בדוק את מחוון מצב כרטיס הספק."
|
||||
},
|
||||
"legal": {
|
||||
"privacyPolicy": "מדיניות פרטיות",
|
||||
@@ -2783,18 +2860,6 @@
|
||||
"termsSection6Title": "6. קוד פתוח",
|
||||
"termsSection6Text": "OmniRoute היא תוכנת קוד פתוח. אתה חופשי לבדוק, לשנות ולהפיץ אותו תחת תנאי הרישיון שלו."
|
||||
},
|
||||
"endpoints": {
|
||||
"tabProxy": "Endpoint Proxy",
|
||||
"tabApiEndpoints": "API Endpoints",
|
||||
"apiEndpointsTitle": "API Endpoints",
|
||||
"apiEndpointsDescription": "Backend API endpoints that can be consumed by other applications and services. This section will list all available REST APIs with documentation and testing capabilities.",
|
||||
"comingSoon": "Coming Soon",
|
||||
"plannedFeatures": "Planned Features",
|
||||
"featureRestApi": "REST API endpoint catalog with interactive documentation",
|
||||
"featureWebhooks": "Webhook configuration and event subscriptions",
|
||||
"featureSwagger": "OpenAPI / Swagger spec auto-generation",
|
||||
"featureAuth": "API key and OAuth scope management per endpoint"
|
||||
},
|
||||
"agents": {
|
||||
"title": "CLI Agents",
|
||||
"description": "Discover installed CLI agents on your system. Add custom agents for auto-detection.",
|
||||
@@ -2849,57 +2914,6 @@
|
||||
"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"
|
||||
},
|
||||
"templateNames": {
|
||||
"simple-chat": "צ'אט פשוט",
|
||||
"streaming": "סטרימינג",
|
||||
@@ -2949,67 +2963,67 @@
|
||||
"refresh": "Refresh",
|
||||
"clearAll": "Clear All",
|
||||
"memoryEntries": "Memory Entries",
|
||||
"memoryEntriesSub": "In-memory LRU",
|
||||
"dbEntries": "DB Entries",
|
||||
"dbEntriesSub": "Persisted (SQLite)",
|
||||
"cacheHits": "Cache Hits",
|
||||
"cacheHitsSub": "of {total} total",
|
||||
"tokensSaved": "Tokens Saved",
|
||||
"tokensSavedSub": "Estimated from hits",
|
||||
"hitRate": "Hit Rate",
|
||||
"performance": "Cache Performance",
|
||||
"behavior": "Cache Behavior",
|
||||
"idempotency": "Idempotency Layer",
|
||||
"clearSuccess": "Cache cleared. {count} expired entries removed.",
|
||||
"clearError": "Failed to clear cache.",
|
||||
"unavailable": "Cache unavailable",
|
||||
"unavailableDesc": "Could not fetch cache statistics. Make sure the server is running.",
|
||||
"memoryEntriesSub": "In-memory LRU",
|
||||
"dbEntriesSub": "Persisted (SQLite)",
|
||||
"cacheHitsSub": "of {total} total",
|
||||
"tokensSavedSub": "Estimated from hits",
|
||||
"autoRefresh": "Auto-refreshes every {seconds}s",
|
||||
"hits": "Hits",
|
||||
"misses": "Misses",
|
||||
"total": "Total",
|
||||
"behavior": "Cache Behavior",
|
||||
"behaviorDeterministic": "Only non-streaming requests with temperature=0 are cached.",
|
||||
"behaviorBypass": "Bypass with header {header}.",
|
||||
"behaviorTwoTier": "Two-tier storage: in-memory LRU (fast) + SQLite (persistent across restarts).",
|
||||
"behaviorTtl": "Default TTL: 30 minutes. Configure via {envVar}.",
|
||||
"idempotency": "Idempotency Layer",
|
||||
"activeDedupKeys": "Active Dedup Keys",
|
||||
"dedupWindow": "Dedup Window",
|
||||
"inputTokens": "Input Tokens",
|
||||
"clearSuccess": "Cache cleared. {count} expired entries removed.",
|
||||
"clearError": "Failed to clear cache.",
|
||||
"unavailable": "Cache unavailable",
|
||||
"unavailableDesc": "Could not fetch cache statistics. Make sure the server is running.",
|
||||
"promptCache": "Prompt Cache (Provider-Side)",
|
||||
"cachedRequests": "Cached Requests",
|
||||
"cacheHitRate": "Cache Hit Rate",
|
||||
"cachedTokens": "Cached Tokens",
|
||||
"cacheCreationTokens": "Cache Creation Tokens",
|
||||
"cacheMetrics": "Prompt Cache Metrics",
|
||||
"withCacheControl": "With Cache Control",
|
||||
"cachedTokensRead": "Cached Tokens (Read)",
|
||||
"cacheCreationWrite": "Cache Creation (Write)",
|
||||
"cacheReuseRatio": "Cache Reuse Ratio",
|
||||
"cacheReuseRatioDesc": "Cached tokens / Total input tokens",
|
||||
"estCostSaved": "Est. Cost Saved",
|
||||
"requestsShort": "reqs",
|
||||
"inputShort": "In",
|
||||
"resetting": "Resetting...",
|
||||
"cachedTokensCol": "Cached",
|
||||
"search": "Search",
|
||||
"cacheReuseRatioDesc": "Cached tokens / Total input tokens",
|
||||
"resetMetrics": "Reset Metrics",
|
||||
"loading": "Loading...",
|
||||
"cachedRequests": "Cached Requests",
|
||||
"model": "Model",
|
||||
"cached": "Cached",
|
||||
"actions": "Actions",
|
||||
"trend24h": "Cache Trend (24h)",
|
||||
"cachedShort": "Cached",
|
||||
"cacheCreation": "Creation",
|
||||
"byProvider": "Breakdown by Provider",
|
||||
"created": "Created",
|
||||
"cacheCreationTokens": "Cache Creation Tokens",
|
||||
"withCacheControl": "With Cache Control",
|
||||
"searchEntries": "Search entries...",
|
||||
"cacheReuseRatio": "Cache Reuse Ratio",
|
||||
"estCostSaved": "Est. Cost Saved",
|
||||
"cachedTokens": "Cached Tokens",
|
||||
"requests": "Requests",
|
||||
"signature": "Signature",
|
||||
"cacheCreationWrite": "Cache Creation (Write)",
|
||||
"expires": "Expires",
|
||||
"writeShort": "Write",
|
||||
"cacheHitRate": "Cache Hit Rate",
|
||||
"cacheMetrics": "Prompt Cache Metrics",
|
||||
"overview": "Overview",
|
||||
"promptCache": "Prompt Cache (Provider-Side)",
|
||||
"resetting": "Resetting...",
|
||||
"resetMetrics": "Reset Metrics",
|
||||
"byProvider": "Breakdown by Provider",
|
||||
"provider": "Provider",
|
||||
"cachedTokensRead": "Cached Tokens (Read)",
|
||||
"requests": "Requests",
|
||||
"inputTokens": "Input Tokens",
|
||||
"cachedTokensCol": "Cached",
|
||||
"cacheCreation": "Creation",
|
||||
"trend24h": "Cache Trend (24h)",
|
||||
"cached": "Cached",
|
||||
"overview": "Overview",
|
||||
"entries": "Entries",
|
||||
"noEntries": "No cache entries found"
|
||||
"searchEntries": "Search entries...",
|
||||
"search": "Search",
|
||||
"loading": "Loading...",
|
||||
"noEntries": "No cache entries found",
|
||||
"signature": "Signature",
|
||||
"model": "Model",
|
||||
"created": "Created",
|
||||
"expires": "Expires",
|
||||
"actions": "Actions"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -143,6 +143,7 @@
|
||||
"dashboard": "Irányítópult",
|
||||
"providers": "Szolgáltatók",
|
||||
"combos": "Kombók",
|
||||
"autoCombo": "Auto Combo",
|
||||
"usage": "Használat",
|
||||
"analytics": "Analytics",
|
||||
"costs": "Költségek",
|
||||
@@ -152,8 +153,12 @@
|
||||
"media": "Média",
|
||||
"settings": "Beállítások elemre",
|
||||
"translator": "Fordító",
|
||||
"playground": "Játszótér",
|
||||
"searchTools": "Search Tools",
|
||||
"agents": "Ügynökök",
|
||||
"docs": "Dokumentumok",
|
||||
"issues": "problémák",
|
||||
"endpoints": "Végpontok",
|
||||
"apiManager": "API-kezelő",
|
||||
"logs": "Naplók",
|
||||
"auditLog": "Ellenőrzési napló",
|
||||
@@ -165,6 +170,11 @@
|
||||
"debug": "Hibakeresés",
|
||||
"system": "Rendszer",
|
||||
"help": "Segítség",
|
||||
"primarySection": "Main",
|
||||
"cliSection": "CLI",
|
||||
"debugSection": "Debug",
|
||||
"systemSection": "System",
|
||||
"helpSection": "Help",
|
||||
"serverDisconnected": "A szerver leválasztva",
|
||||
"serverDisconnectedMsg": "A proxyszerver leállt vagy újraindul.",
|
||||
"expandSidebar": "Az oldalsáv kibontása",
|
||||
@@ -180,19 +190,9 @@
|
||||
"themeViolet": "Violet",
|
||||
"themeOrange": "narancssárga",
|
||||
"themeCyan": "Cián",
|
||||
"endpoints": "Végpontok",
|
||||
"playground": "Játszótér",
|
||||
"agents": "Ügynökök",
|
||||
"cliToolsShort": "Eszközök",
|
||||
"autoCombo": "Auto Combo",
|
||||
"searchTools": "Search Tools",
|
||||
"cache": "Cache",
|
||||
"cacheShort": "Cache",
|
||||
"cliSection": "CLI",
|
||||
"debugSection": "Debug",
|
||||
"helpSection": "Help",
|
||||
"primarySection": "Main",
|
||||
"systemSection": "System"
|
||||
"cacheShort": "Cache"
|
||||
},
|
||||
"themesPage": {
|
||||
"title": "Témák",
|
||||
@@ -220,6 +220,10 @@
|
||||
"homeDescription": "Üdvözöljük az OmniRoute oldalán",
|
||||
"endpoint": "Endpoints",
|
||||
"endpointDescription": "Manage proxy endpoints, MCP, A2A, and API endpoints",
|
||||
"mcp": "MCP",
|
||||
"mcpDescription": "Model Context Protocol server management and tools",
|
||||
"a2a": "A2A",
|
||||
"a2aDescription": "Agent-to-Agent protocol tasks and observability",
|
||||
"settings": "Beállítások elemre",
|
||||
"settingsDescription": "Kezelje beállításait",
|
||||
"openaiCompatible": "OpenAI kompatibilis",
|
||||
@@ -227,11 +231,7 @@
|
||||
"media": "Média",
|
||||
"mediaDescription": "Készítsen képeket, videókat és zenét",
|
||||
"themes": "Témák",
|
||||
"themesDescription": "Válasszon színtémát az egész irányítópult panelhez",
|
||||
"mcp": "MCP",
|
||||
"mcpDescription": "Model Context Protocol server management and tools",
|
||||
"a2a": "A2A",
|
||||
"a2aDescription": "Agent-to-Agent protocol tasks and observability"
|
||||
"themesDescription": "Válasszon színtémát az egész irányítópult panelhez"
|
||||
},
|
||||
"home": {
|
||||
"quickStart": "Gyors kezdés",
|
||||
@@ -332,6 +332,26 @@
|
||||
"keyCreatedNote": "Másolja ki és tárolja ezt a kulcsot most – többé nem jelenik meg.",
|
||||
"done": "Kész",
|
||||
"savePermissions": "Engedélyek mentése",
|
||||
"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",
|
||||
"allowAll": "Minden engedélyezése",
|
||||
"restrict": "Korlátozás",
|
||||
"allowAllInfo": "Ezzel a gombbal minden elérhető modell elérhető.",
|
||||
@@ -356,6 +376,7 @@
|
||||
"failedUpdatePermissionsRetry": "Nem sikerült frissíteni az engedélyeket. Kérjük, próbálja újra.",
|
||||
"unknownProvider": "ismeretlen",
|
||||
"copyMaskedKey": "Maszkolt kulcs másolása",
|
||||
"keyOnlyAvailableAtCreation": "Full key available only at creation time — copy it when you first create the key",
|
||||
"modelsCount": "{count, plural, one {# model} other {# models}}",
|
||||
"lastUsedOn": "Utolsó: {date}",
|
||||
"editPermissions": "Engedélyek szerkesztése",
|
||||
@@ -365,28 +386,7 @@
|
||||
"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",
|
||||
"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"
|
||||
"selectedCount": "{count} kiválasztva"
|
||||
},
|
||||
"auditLog": {
|
||||
"title": "Ellenőrzési napló",
|
||||
@@ -425,6 +425,57 @@
|
||||
"videoDescription": "Create videos with AnimateDiff, Stable Video Diffusion via ComfyUI or SD WebUI.",
|
||||
"musicDescription": "Compose music using Stable Audio Open or MusicGen via ComfyUI."
|
||||
},
|
||||
"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",
|
||||
"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",
|
||||
"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"
|
||||
},
|
||||
"cliTools": {
|
||||
"title": "CLI eszközök",
|
||||
"noActiveProviders": "Nincsenek aktív szolgáltatók",
|
||||
@@ -497,6 +548,11 @@
|
||||
"antigravityStep2Prefix": "2. Hozzáadás",
|
||||
"antigravityStep2Suffix": "a hosts fájlba 127.0.0.1 néven.",
|
||||
"antigravityStep3": "3. Nyissa meg az Antigravitációt, és a kérések proxyval lesznek ellátva.",
|
||||
"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.",
|
||||
"sudoPasswordRequiredTitle": "Sudo jelszó szükséges",
|
||||
"sudoPasswordHint": "Rendszergazdai jelszó szükséges a hosts fájl és a rendszerproxy beállításainak módosításához.",
|
||||
"enterSudoPassword": "Írja be a sudo jelszót",
|
||||
@@ -569,6 +625,22 @@
|
||||
"openClawManualConfiguration": "Open Claw – Manuális konfiguráció",
|
||||
"clineManualConfiguration": "Cline kézi konfiguráció",
|
||||
"kiloManualConfiguration": "Kiló kód kézi konfiguráció",
|
||||
"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."
|
||||
},
|
||||
"toolDescriptions": {
|
||||
"antigravity": "Google Antigravity IDE MITM-mel",
|
||||
"claude": "Antropikus Claude Code CLI",
|
||||
@@ -706,27 +778,6 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"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": {
|
||||
@@ -988,7 +1039,12 @@
|
||||
"settingsApi": "Settings API",
|
||||
"categoryCore": "Alap API-k",
|
||||
"categoryMedia": "Média és Multi-Modális",
|
||||
"categorySearch": "Search & Discovery",
|
||||
"categoryUtility": "Segédeszközök és Kezelés",
|
||||
"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.",
|
||||
"enableCloudTitle": "Cloud Proxy engedélyezése",
|
||||
"whatYouGet": "Amit kapsz",
|
||||
"cloudBenefitAccess": "Hozzáférés az API-jához a világ bármely pontjáról",
|
||||
@@ -1021,6 +1077,25 @@
|
||||
"cloudWorkerUnreachable": "Nem sikerült elérni a felhőkezelőt. Győződjön meg arról, hogy a felhőszolgáltatás fut (npm run dev in /cloud).",
|
||||
"connectionFailed": "A csatlakozás sikertelen",
|
||||
"syncFailed": "Nem sikerült szinkronizálni a felhőadatokat",
|
||||
"cloudflaredTitle": "Cloudflare Quick Tunnel",
|
||||
"cloudflaredDescription": "Create a Cloudflare Quick Tunnel for this endpoint to make it accessible from the internet without configuring DNS.",
|
||||
"cloudflaredUrlNotice": "Creates a temporary Cloudflare Quick Tunnel. URLs change after restart.",
|
||||
"cloudflaredEnable": "Enable Tunnel",
|
||||
"cloudflaredInstallAndEnable": "Install & Enable",
|
||||
"cloudflaredDisable": "Stop Tunnel",
|
||||
"cloudflaredRunning": "Running",
|
||||
"cloudflaredStarting": "Starting",
|
||||
"cloudflaredStoppedState": "Stopped",
|
||||
"cloudflaredNotInstalled": "Not installed",
|
||||
"cloudflaredUnsupported": "Unsupported",
|
||||
"cloudflaredError": "Error",
|
||||
"cloudflaredStarted": "Cloudflare tunnel started",
|
||||
"cloudflaredStopped": "Cloudflare tunnel stopped",
|
||||
"cloudflaredRequestFailed": "Failed to update Cloudflare tunnel",
|
||||
"cloudflaredTemporaryNote": "Quick Tunnel URLs are temporary and will change after restart.",
|
||||
"cloudflaredUnsupportedNote": "This platform is not supported for managed installation.",
|
||||
"cloudflaredIdleNote": "Create a temporary Cloudflare Quick Tunnel for this endpoint.",
|
||||
"cloudflaredLastError": "Last error: {error}",
|
||||
"providerModelsTitle": "{provider} – Modellek",
|
||||
"noModelsForProvider": "Ehhez a szolgáltatóhoz nem állnak rendelkezésre modellek.",
|
||||
"chat": "Chat",
|
||||
@@ -1055,31 +1130,19 @@
|
||||
"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",
|
||||
"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.",
|
||||
"cloudflaredDescription": "Create a Cloudflare Quick Tunnel for this endpoint to make it accessible from the internet without configuring DNS.",
|
||||
"cloudflaredDisable": "Stop Tunnel",
|
||||
"cloudflaredEnable": "Enable Tunnel",
|
||||
"cloudflaredError": "Error",
|
||||
"cloudflaredIdleNote": "Create a temporary Cloudflare Quick Tunnel for this endpoint.",
|
||||
"cloudflaredInstallAndEnable": "Install & Enable",
|
||||
"cloudflaredLastError": "Last error: {error}",
|
||||
"cloudflaredNotInstalled": "Not installed",
|
||||
"cloudflaredRequestFailed": "Failed to update Cloudflare tunnel",
|
||||
"cloudflaredRunning": "Running",
|
||||
"cloudflaredStarted": "Cloudflare tunnel started",
|
||||
"cloudflaredStarting": "Starting",
|
||||
"cloudflaredStopped": "Cloudflare tunnel stopped",
|
||||
"cloudflaredStoppedState": "Stopped",
|
||||
"cloudflaredTemporaryNote": "Quick Tunnel URLs are temporary and will change after restart.",
|
||||
"cloudflaredTitle": "Cloudflare Quick Tunnel",
|
||||
"cloudflaredUnsupported": "Unsupported",
|
||||
"cloudflaredUnsupportedNote": "This platform is not supported for managed installation.",
|
||||
"cloudflaredUrlNotice": "Creates a temporary Cloudflare Quick Tunnel. URLs change after restart."
|
||||
"completionsLegacyDesc": "Legacy OpenAI text completions — accepts both prompt string and messages array format"
|
||||
},
|
||||
"endpoints": {
|
||||
"tabProxy": "Endpoint Proxy",
|
||||
"tabApiEndpoints": "API Endpoints",
|
||||
"apiEndpointsTitle": "API Endpoints",
|
||||
"apiEndpointsDescription": "Backend API endpoints that can be consumed by other applications and services. This section will list all available REST APIs with documentation and testing capabilities.",
|
||||
"comingSoon": "Coming Soon",
|
||||
"plannedFeatures": "Planned Features",
|
||||
"featureRestApi": "REST API endpoint catalog with interactive documentation",
|
||||
"featureWebhooks": "Webhook configuration and event subscriptions",
|
||||
"featureSwagger": "OpenAPI / Swagger spec auto-generation",
|
||||
"featureAuth": "API key and OAuth scope management per endpoint"
|
||||
},
|
||||
"mcpDashboard": {
|
||||
"loading": "Loading MCP dashboard...",
|
||||
@@ -1251,6 +1314,12 @@
|
||||
"issuesLabel": "Problémák észlelve",
|
||||
"operational": "Működőképes",
|
||||
"providers": "Szolgáltatók",
|
||||
"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.",
|
||||
"activeProviders": "{count} active",
|
||||
"activeProvidersHint": "Konfigurált szolgáltatók, amelyek jelenleg engedélyezve vannak az útválasztási kérelmek számára.",
|
||||
"monitoredProviders": "{count} monitored",
|
||||
"monitoredProvidersHint": "Azok a szolgáltatók, amelyeket jelenleg a megszakítók állapotfigyelői követnek nyomon.",
|
||||
"healthyCount": "{count} egészséges",
|
||||
"nodeVersion": "{version} csomópont",
|
||||
"failures": "{count} hiba",
|
||||
@@ -1269,13 +1338,7 @@
|
||||
"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",
|
||||
"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."
|
||||
"until": "{time}-ig"
|
||||
},
|
||||
"limits": {
|
||||
"title": "Korlátok és kvóták",
|
||||
@@ -1437,6 +1500,7 @@
|
||||
"clearing": "Törlés...",
|
||||
"until": "{time}-ig",
|
||||
"providerTestFailed": "A szolgáltató tesztje sikertelen",
|
||||
"providerTestTimeout": "Provider test timed out — too many connections to test at once",
|
||||
"modeTest": "{mode} Teszt",
|
||||
"passedCount": "{count} sikeres",
|
||||
"failedCount": "{count} sikertelen",
|
||||
@@ -1478,6 +1542,14 @@
|
||||
"chatCompletions": "Csevegés befejezése",
|
||||
"importingModels": "Importálás...",
|
||||
"importFromModels": "Importálás a /models-ből",
|
||||
"allModelsAlreadyImported": "Minden modell már importálva van",
|
||||
"noNewModelsToImport": "Nincs új modell az importáláshoz — minden modell már a nyilvántartásban vagy az egyéni modellek listájában van",
|
||||
"skippingExistingModels": "{count} meglévő modell kihagyása",
|
||||
"autoSync": "Automatikus szinkronizálás",
|
||||
"autoSyncTooltip": "A modelllista automatikus frissítése 24 óránként (konfigurálható: MODEL_SYNC_INTERVAL_HOURS)",
|
||||
"autoSyncEnabled": "Automatikus szinkronizálás engedélyezve – a modellek rendszeresen frissülnek",
|
||||
"autoSyncDisabled": "Az automatikus szinkronizálás letiltva",
|
||||
"autoSyncToggleFailed": "Failed to toggle auto-sync",
|
||||
"clearAllModels": "Minden modell törlése",
|
||||
"clearAllModelsConfirm": "Biztosan eltávolítja ennek a szolgáltatónak az összes modelljét? Ezt nem lehet visszavonni.",
|
||||
"clearAllModelsSuccess": "Minden modell törölve",
|
||||
@@ -1531,6 +1603,8 @@
|
||||
"addFirstConnectionHint": "A kezdéshez adja hozzá az első kapcsolatot",
|
||||
"addConnection": "Csatlakozás hozzáadása",
|
||||
"availableModels": "Elérhető modellek",
|
||||
"builtInModels": "Built-in models",
|
||||
"builtInModelsHint": "Registry models for this provider. Use the pencil to set compatibility options.",
|
||||
"pageAutoRefresh": "Az oldal automatikusan frissül...",
|
||||
"statusDisabled": "letiltva",
|
||||
"statusConnected": "csatlakoztatva",
|
||||
@@ -1571,6 +1645,26 @@
|
||||
"openRouterModelPlaceholder": "antropikus/claude-3-opus",
|
||||
"customModels": "Egyedi modellek",
|
||||
"customModelsHint": "Adjon hozzá olyan modellazonosítókat, amelyek nem szerepelnek az alapértelmezett listában. Ezek elérhetők lesznek az útválasztáshoz.",
|
||||
"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",
|
||||
"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": "Modellazonosító",
|
||||
"customModelPlaceholder": "pl. gpt-4.5-turbo",
|
||||
"loading": "Betöltés...",
|
||||
@@ -1614,52 +1708,21 @@
|
||||
"compatibleBaseUrlHint": "Root URL of your {type}-compatible API. Use Advanced Settings for custom endpoint paths.",
|
||||
"apiKeyForCheck": "API-kulcs (ellenőrzéshez)",
|
||||
"compatibleProdPlaceholder": "{type} Kompatibilis (termék)",
|
||||
"providerTestTimeout": "Provider test timed out — too many connections to test at once",
|
||||
"tokenRefreshed": "Token refreshed successfully",
|
||||
"tokenRefreshFailed": "Token refresh failed",
|
||||
"applyCodexAuthLocal": "Apply auth",
|
||||
"exportCodexAuthFile": "Export auth",
|
||||
"codexAuthAppliedLocal": "Codex auth.json applied locally",
|
||||
"codexAuthApplyFailed": "Failed to apply Codex auth.json locally",
|
||||
"codexAuthExported": "Codex auth.json exported",
|
||||
"codexAuthExportFailed": "Failed to export Codex auth.json",
|
||||
"advancedSettings": "Advanced Settings",
|
||||
"chatPathLabel": "Chat Endpoint Path",
|
||||
"chatPathPlaceholder": "/chat/completions",
|
||||
"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)",
|
||||
"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",
|
||||
"autoSync": "Automatikus szinkronizálás",
|
||||
"autoSyncTooltip": "A modelllista automatikus frissítése 24 óránként (konfigurálható: MODEL_SYNC_INTERVAL_HOURS)",
|
||||
"autoSyncEnabled": "Automatikus szinkronizálás engedélyezve – a modellek rendszeresen frissülnek",
|
||||
"autoSyncDisabled": "Az automatikus szinkronizálás letiltva",
|
||||
"autoSyncToggleFailed": "Failed to toggle auto-sync",
|
||||
"allModelsAlreadyImported": "Minden modell már importálva van",
|
||||
"noNewModelsToImport": "Nincs új modell az importáláshoz — minden modell már a nyilvántartásban vagy az egyéni modellek listájában van",
|
||||
"skippingExistingModels": "{count} meglévő modell kihagyása",
|
||||
"applyCodexAuthLocal": "Apply auth",
|
||||
"codexAuthAppliedLocal": "Codex auth.json applied locally",
|
||||
"codexAuthApplyFailed": "Failed to apply Codex auth.json locally",
|
||||
"codexAuthExportFailed": "Failed to export Codex auth.json",
|
||||
"codexAuthExported": "Codex auth.json exported",
|
||||
"exportCodexAuthFile": "Export auth"
|
||||
"modelsPathHint": "Custom models path for validation (e.g. /v4/models)"
|
||||
},
|
||||
"settings": {
|
||||
"title": "Beállítások elemre",
|
||||
@@ -1683,6 +1746,8 @@
|
||||
"darkMode": "Sötét mód",
|
||||
"lightMode": "Fény mód",
|
||||
"systemTheme": "Rendszer téma",
|
||||
"debugToggle": "Enable Debug Mode",
|
||||
"sidebarVisibilityToggle": "Show Sidebar Items",
|
||||
"enableCache": "Gyorsítótár engedélyezése",
|
||||
"cacheTTL": "Gyorsítótár TTL",
|
||||
"maxCacheSize": "Max gyorsítótár mérete",
|
||||
@@ -1691,6 +1756,17 @@
|
||||
"cacheMisses": "Gyorsítótár hiányzik",
|
||||
"hitRate": "Hit Rate",
|
||||
"cacheEntries": "Gyorsítótár bejegyzései",
|
||||
"cacheSettings": "Cache Settings",
|
||||
"semanticCache": "Semantic Cache",
|
||||
"maxEntries": "Max Entries",
|
||||
"ttlMinutes": "TTL (minutes)",
|
||||
"promptCache": "Prompt Cache",
|
||||
"strategy": "Strategy",
|
||||
"preserveClientCache": "Preserve Client Cache",
|
||||
"enabled": "Enabled",
|
||||
"loading": "Loading...",
|
||||
"saving": "Mentés...",
|
||||
"save": "Save",
|
||||
"circuitBreaker": "Áramköri megszakító",
|
||||
"retryPolicy": "Irányelv újrapróbálkozása",
|
||||
"maxRetries": "Max újrapróbálkozás",
|
||||
@@ -1698,6 +1774,10 @@
|
||||
"timeoutMs": "Időtúllépés (ms)",
|
||||
"enableSystemPrompt": "Rendszerprompt engedélyezése",
|
||||
"systemPromptText": "Rendszerkérő szöveg",
|
||||
"autoDisableBannedAccounts": "Auto-Disable Banned Accounts",
|
||||
"autoDisableDescription": "Permanently mark provider connections as deactivated if they return specific terminal ban signals (e.g. HTTP 403 'verify your account'). This removes them from the combo rotation.",
|
||||
"autoDisableThreshold": "Ban Threshold",
|
||||
"autoDisableThresholdDesc": "Consecutive ban signals required before permanent deactivation.",
|
||||
"enableThinking": "Gondolkodás engedélyezése",
|
||||
"maxThinkingTokens": "Max gondolkodási jelzők",
|
||||
"enableProxy": "Proxy engedélyezése",
|
||||
@@ -1734,6 +1814,9 @@
|
||||
"themeLight": "Fény",
|
||||
"themeDark": "Sötét",
|
||||
"themeSystem": "Rendszer",
|
||||
"sidebarVisibility": "Hide sidebar items",
|
||||
"sidebarVisibilityDesc": "Hide any sidebar navigation entry to reduce visual clutter.",
|
||||
"sidebarVisibilityHint": "Any sidebar section is hidden automatically when a...",
|
||||
"hideHealthLogs": "Állapotellenőrzési naplók elrejtése",
|
||||
"hideHealthLogsDesc": "Ha BE van kapcsolva, tiltsa le a [HealthCheck] üzeneteket a kiszolgálókonzolon",
|
||||
"themeAccent": "Téma színe",
|
||||
@@ -1746,7 +1829,20 @@
|
||||
"themeViolet": "Ibolya",
|
||||
"themeOrange": "Narancs",
|
||||
"themeCyan": "Cián",
|
||||
"promptCache": "Prompt Cache",
|
||||
"whitelabeling": "Branding",
|
||||
"whitelabelingDesc": "Customize the application name and logo",
|
||||
"appName": "Application Name",
|
||||
"appNameDesc": "Display name shown in sidebar and browser tab",
|
||||
"customLogo": "Custom Logo URL",
|
||||
"customLogoDesc": "URL to your custom logo image",
|
||||
"uploadLogo": "Upload Logo",
|
||||
"resetLogo": "Reset to Default",
|
||||
"logoPreview": "Preview",
|
||||
"customFavicon": "Browser Favicon",
|
||||
"customFaviconDesc": "URL to your custom favicon (shown in browser tab)",
|
||||
"uploadFavicon": "Upload Favicon",
|
||||
"resetFavicon": "Reset Favicon",
|
||||
"faviconPreview": "Favicon Preview",
|
||||
"flushCache": "Öblítse ki a gyorsítótárat",
|
||||
"flushing": "Öblítés…",
|
||||
"size": "Méret",
|
||||
@@ -1804,7 +1900,15 @@
|
||||
"providersBlocked": "{count} szolgáltató(k) blokkolva a /models-ből",
|
||||
"blockProviderTitle": "{provider} letiltása",
|
||||
"unblockProviderTitle": "{provider} blokkolásának feloldása",
|
||||
"cliFingerprint": "CLI Fingerprint Matching",
|
||||
"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}",
|
||||
"routingStrategy": "Útválasztási stratégia",
|
||||
"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.",
|
||||
"fillFirst": "Először töltse ki",
|
||||
"fillFirstDesc": "A fiókokat prioritási sorrendben használja",
|
||||
"roundRobin": "Round Robin",
|
||||
@@ -1899,6 +2003,9 @@
|
||||
"fillModelAndProviders": "Kérjük, adja meg a modell nevét és a szolgáltatókat",
|
||||
"addAtLeastOneProvider": "Adjon hozzá legalább egy szolgáltatót",
|
||||
"comboDefaultsTitle": "Kombinált alapértékek",
|
||||
"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.",
|
||||
"globalComboConfig": "Globális kombinált konfiguráció",
|
||||
"defaultStrategy": "Alapértelmezett stratégia",
|
||||
"defaultStrategyDesc": "Új kombókra alkalmazva kifejezett stratégia nélkül",
|
||||
@@ -2026,7 +2133,6 @@
|
||||
"unsaved": "nem mentve",
|
||||
"resetDefaults": "Alapértékek visszaállítása",
|
||||
"saveProvider": "Mentse a Szolgáltatót",
|
||||
"saving": "Mentés...",
|
||||
"model": "Modell",
|
||||
"models": "modellek",
|
||||
"moreProviders": "{count} további szolgáltató",
|
||||
@@ -2057,36 +2163,7 @@
|
||||
"customPricingNote": "Egyes modelleknél felülbírálhatja az alapértelmezett árazást. Az egyéni felülbírálások elsőbbséget élveznek az automatikusan észlelt árképzéssel szemben.",
|
||||
"editPricing": "Árak szerkesztése",
|
||||
"viewFullDetails": "Teljes részletek megtekintése",
|
||||
"themeCoral": "Korall",
|
||||
"cliFingerprint": "CLI Fingerprint Matching",
|
||||
"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}",
|
||||
"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.",
|
||||
"sidebarVisibility": "Hide sidebar items",
|
||||
"sidebarVisibilityDesc": "Hide any sidebar navigation entry to reduce visual clutter.",
|
||||
"sidebarVisibilityHint": "Any sidebar section is hidden automatically when a...",
|
||||
"semanticCache": "Semantic Cache",
|
||||
"autoDisableDescription": "Permanently mark provider connections as deactivated if they return specific terminal ban signals (e.g. HTTP 403 'verify your account'). This removes them from the combo rotation.",
|
||||
"preserveClientCache": "Preserve Client Cache",
|
||||
"cacheSettings": "Cache Settings",
|
||||
"autoDisableThreshold": "Ban Threshold",
|
||||
"autoDisableBannedAccounts": "Auto-Disable Banned Accounts",
|
||||
"ttlMinutes": "TTL (minutes)",
|
||||
"maxEntries": "Max Entries",
|
||||
"loading": "Loading...",
|
||||
"save": "Save",
|
||||
"autoDisableThresholdDesc": "Consecutive ban signals required before permanent deactivation.",
|
||||
"debugToggle": "Enable Debug Mode",
|
||||
"sidebarVisibilityToggle": "Show Sidebar Items",
|
||||
"enabled": "Enabled",
|
||||
"strategy": "Strategy"
|
||||
"themeCoral": "Korall"
|
||||
},
|
||||
"translator": {
|
||||
"title": "Fordító",
|
||||
@@ -2606,6 +2683,8 @@
|
||||
"clientCompatibility": "Kliens kompatibilitás",
|
||||
"protocolsToc": "Protocols",
|
||||
"apiReference": "API-referencia",
|
||||
"managementApiReference": "Management API Reference",
|
||||
"managementApiDescription": "Automation endpoints for proxy registry, scope assignments, and legacy proxy migration.",
|
||||
"method": "módszer",
|
||||
"path": "Útvonal",
|
||||
"notes": "Megjegyzések",
|
||||
@@ -2697,10 +2776,19 @@
|
||||
"endpointResponsesNote": "Responses API-végpont (Codex, o-sorozat).",
|
||||
"endpointModelsNote": "Modelkatalógus az összes csatlakoztatott szolgáltatóhoz.",
|
||||
"endpointAudioNote": "Hang átírása (Deepgram, AssemblyAI).",
|
||||
"endpointSpeechNote": "Szövegfelolvasó generálás (ElevenLabs, OpenAI TTS).",
|
||||
"endpointEmbeddingsNote": "Szövegbeágyazás generálása (OpenAI, Cohere, Voyage).",
|
||||
"endpointImagesNote": "Képgenerálás (NanoBanana).",
|
||||
"endpointRewriteChatNote": "Átírási segéd a /v1 nélküli ügyfelek számára.",
|
||||
"endpointRewriteResponsesNote": "Újraírási segéd a /v1 nélküli válaszokhoz.",
|
||||
"endpointRewriteModelsNote": "Segítő átírása a modellfelderítéshez /v1 nélkül.",
|
||||
"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.",
|
||||
"modelPrefixesDescriptionStart": "Használja a szolgáltató előtagot a modell neve előtt, hogy egy adott szolgáltatóhoz irányítson. Példa:",
|
||||
"modelPrefixesDescriptionEnd": "útvonalak a GitHub Copilothoz.",
|
||||
"provider": "Szolgáltató",
|
||||
@@ -2710,18 +2798,7 @@
|
||||
"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.",
|
||||
"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.",
|
||||
"endpointSpeechNote": "Szövegfelolvasó generálás (ElevenLabs, OpenAI TTS).",
|
||||
"endpointEmbeddingsNote": "Szövegbeágyazás generálása (OpenAI, Cohere, Voyage)."
|
||||
"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."
|
||||
},
|
||||
"legal": {
|
||||
"privacyPolicy": "Adatvédelmi szabályzat",
|
||||
@@ -2783,18 +2860,6 @@
|
||||
"termsSection6Title": "6. Nyílt forráskód",
|
||||
"termsSection6Text": "Az OmniRoute egy nyílt forráskódú szoftver. Ön szabadon megvizsgálhatja, módosíthatja és terjesztheti a licenc feltételei szerint."
|
||||
},
|
||||
"endpoints": {
|
||||
"tabProxy": "Endpoint Proxy",
|
||||
"tabApiEndpoints": "API Endpoints",
|
||||
"apiEndpointsTitle": "API Endpoints",
|
||||
"apiEndpointsDescription": "Backend API endpoints that can be consumed by other applications and services. This section will list all available REST APIs with documentation and testing capabilities.",
|
||||
"comingSoon": "Coming Soon",
|
||||
"plannedFeatures": "Planned Features",
|
||||
"featureRestApi": "REST API endpoint catalog with interactive documentation",
|
||||
"featureWebhooks": "Webhook configuration and event subscriptions",
|
||||
"featureSwagger": "OpenAPI / Swagger spec auto-generation",
|
||||
"featureAuth": "API key and OAuth scope management per endpoint"
|
||||
},
|
||||
"agents": {
|
||||
"title": "CLI Agents",
|
||||
"description": "Discover installed CLI agents on your system. Add custom agents for auto-detection.",
|
||||
@@ -2849,57 +2914,6 @@
|
||||
"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"
|
||||
},
|
||||
"templateNames": {
|
||||
"simple-chat": "Egyszerű csevegés",
|
||||
"streaming": "Streaming",
|
||||
@@ -2949,67 +2963,67 @@
|
||||
"refresh": "Refresh",
|
||||
"clearAll": "Clear All",
|
||||
"memoryEntries": "Memory Entries",
|
||||
"memoryEntriesSub": "In-memory LRU",
|
||||
"dbEntries": "DB Entries",
|
||||
"dbEntriesSub": "Persisted (SQLite)",
|
||||
"cacheHits": "Cache Hits",
|
||||
"cacheHitsSub": "of {total} total",
|
||||
"tokensSaved": "Tokens Saved",
|
||||
"tokensSavedSub": "Estimated from hits",
|
||||
"hitRate": "Hit Rate",
|
||||
"performance": "Cache Performance",
|
||||
"behavior": "Cache Behavior",
|
||||
"idempotency": "Idempotency Layer",
|
||||
"clearSuccess": "Cache cleared. {count} expired entries removed.",
|
||||
"clearError": "Failed to clear cache.",
|
||||
"unavailable": "Cache unavailable",
|
||||
"unavailableDesc": "Could not fetch cache statistics. Make sure the server is running.",
|
||||
"memoryEntriesSub": "In-memory LRU",
|
||||
"dbEntriesSub": "Persisted (SQLite)",
|
||||
"cacheHitsSub": "of {total} total",
|
||||
"tokensSavedSub": "Estimated from hits",
|
||||
"autoRefresh": "Auto-refreshes every {seconds}s",
|
||||
"hits": "Hits",
|
||||
"misses": "Misses",
|
||||
"total": "Total",
|
||||
"behavior": "Cache Behavior",
|
||||
"behaviorDeterministic": "Only non-streaming requests with temperature=0 are cached.",
|
||||
"behaviorBypass": "Bypass with header {header}.",
|
||||
"behaviorTwoTier": "Two-tier storage: in-memory LRU (fast) + SQLite (persistent across restarts).",
|
||||
"behaviorTtl": "Default TTL: 30 minutes. Configure via {envVar}.",
|
||||
"idempotency": "Idempotency Layer",
|
||||
"activeDedupKeys": "Active Dedup Keys",
|
||||
"dedupWindow": "Dedup Window",
|
||||
"inputTokens": "Input Tokens",
|
||||
"clearSuccess": "Cache cleared. {count} expired entries removed.",
|
||||
"clearError": "Failed to clear cache.",
|
||||
"unavailable": "Cache unavailable",
|
||||
"unavailableDesc": "Could not fetch cache statistics. Make sure the server is running.",
|
||||
"promptCache": "Prompt Cache (Provider-Side)",
|
||||
"cachedRequests": "Cached Requests",
|
||||
"cacheHitRate": "Cache Hit Rate",
|
||||
"cachedTokens": "Cached Tokens",
|
||||
"cacheCreationTokens": "Cache Creation Tokens",
|
||||
"cacheMetrics": "Prompt Cache Metrics",
|
||||
"withCacheControl": "With Cache Control",
|
||||
"cachedTokensRead": "Cached Tokens (Read)",
|
||||
"cacheCreationWrite": "Cache Creation (Write)",
|
||||
"cacheReuseRatio": "Cache Reuse Ratio",
|
||||
"cacheReuseRatioDesc": "Cached tokens / Total input tokens",
|
||||
"estCostSaved": "Est. Cost Saved",
|
||||
"requestsShort": "reqs",
|
||||
"inputShort": "In",
|
||||
"resetting": "Resetting...",
|
||||
"cachedTokensCol": "Cached",
|
||||
"search": "Search",
|
||||
"cacheReuseRatioDesc": "Cached tokens / Total input tokens",
|
||||
"resetMetrics": "Reset Metrics",
|
||||
"loading": "Loading...",
|
||||
"cachedRequests": "Cached Requests",
|
||||
"model": "Model",
|
||||
"cached": "Cached",
|
||||
"actions": "Actions",
|
||||
"trend24h": "Cache Trend (24h)",
|
||||
"cachedShort": "Cached",
|
||||
"cacheCreation": "Creation",
|
||||
"byProvider": "Breakdown by Provider",
|
||||
"created": "Created",
|
||||
"cacheCreationTokens": "Cache Creation Tokens",
|
||||
"withCacheControl": "With Cache Control",
|
||||
"searchEntries": "Search entries...",
|
||||
"cacheReuseRatio": "Cache Reuse Ratio",
|
||||
"estCostSaved": "Est. Cost Saved",
|
||||
"cachedTokens": "Cached Tokens",
|
||||
"requests": "Requests",
|
||||
"signature": "Signature",
|
||||
"cacheCreationWrite": "Cache Creation (Write)",
|
||||
"expires": "Expires",
|
||||
"writeShort": "Write",
|
||||
"cacheHitRate": "Cache Hit Rate",
|
||||
"cacheMetrics": "Prompt Cache Metrics",
|
||||
"overview": "Overview",
|
||||
"promptCache": "Prompt Cache (Provider-Side)",
|
||||
"resetting": "Resetting...",
|
||||
"resetMetrics": "Reset Metrics",
|
||||
"byProvider": "Breakdown by Provider",
|
||||
"provider": "Provider",
|
||||
"cachedTokensRead": "Cached Tokens (Read)",
|
||||
"requests": "Requests",
|
||||
"inputTokens": "Input Tokens",
|
||||
"cachedTokensCol": "Cached",
|
||||
"cacheCreation": "Creation",
|
||||
"trend24h": "Cache Trend (24h)",
|
||||
"cached": "Cached",
|
||||
"overview": "Overview",
|
||||
"entries": "Entries",
|
||||
"noEntries": "No cache entries found"
|
||||
"searchEntries": "Search entries...",
|
||||
"search": "Search",
|
||||
"loading": "Loading...",
|
||||
"noEntries": "No cache entries found",
|
||||
"signature": "Signature",
|
||||
"model": "Model",
|
||||
"created": "Created",
|
||||
"expires": "Expires",
|
||||
"actions": "Actions"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -143,6 +143,7 @@
|
||||
"dashboard": "Dasbor",
|
||||
"providers": "Penyedia",
|
||||
"combos": "kombo",
|
||||
"autoCombo": "Auto Combo",
|
||||
"usage": "Penggunaan",
|
||||
"analytics": "Analisis",
|
||||
"costs": "Biaya",
|
||||
@@ -152,8 +153,12 @@
|
||||
"media": "Media",
|
||||
"settings": "Pengaturan",
|
||||
"translator": "Penerjemah",
|
||||
"playground": "Playground",
|
||||
"searchTools": "Search Tools",
|
||||
"agents": "Agen",
|
||||
"docs": "dokumen",
|
||||
"issues": "Masalah",
|
||||
"endpoints": "Endpoint",
|
||||
"apiManager": "Manajer API",
|
||||
"logs": "Log",
|
||||
"auditLog": "Catatan Audit",
|
||||
@@ -165,6 +170,11 @@
|
||||
"debug": "Men-debug",
|
||||
"system": "Sistem",
|
||||
"help": "Bantuan",
|
||||
"primarySection": "Main",
|
||||
"cliSection": "CLI",
|
||||
"debugSection": "Debug",
|
||||
"systemSection": "System",
|
||||
"helpSection": "Help",
|
||||
"serverDisconnected": "Server Terputus",
|
||||
"serverDisconnectedMsg": "Server proxy telah dihentikan atau dimulai ulang.",
|
||||
"expandSidebar": "Luaskan bilah sisi",
|
||||
@@ -180,19 +190,9 @@
|
||||
"themeViolet": "Violet",
|
||||
"themeOrange": "Orange",
|
||||
"themeCyan": "Cyan",
|
||||
"endpoints": "Endpoint",
|
||||
"playground": "Playground",
|
||||
"agents": "Agen",
|
||||
"cliToolsShort": "Alat",
|
||||
"autoCombo": "Auto Combo",
|
||||
"searchTools": "Search Tools",
|
||||
"cache": "Cache",
|
||||
"cacheShort": "Cache",
|
||||
"cliSection": "CLI",
|
||||
"debugSection": "Debug",
|
||||
"helpSection": "Help",
|
||||
"primarySection": "Main",
|
||||
"systemSection": "System"
|
||||
"cacheShort": "Cache"
|
||||
},
|
||||
"themesPage": {
|
||||
"title": "Themes",
|
||||
@@ -220,6 +220,10 @@
|
||||
"homeDescription": "Selamat datang di OmniRoute",
|
||||
"endpoint": "Endpoints",
|
||||
"endpointDescription": "Manage proxy endpoints, MCP, A2A, and API endpoints",
|
||||
"mcp": "MCP",
|
||||
"mcpDescription": "Model Context Protocol server management and tools",
|
||||
"a2a": "A2A",
|
||||
"a2aDescription": "Agent-to-Agent protocol tasks and observability",
|
||||
"settings": "Pengaturan",
|
||||
"settingsDescription": "Kelola preferensi Anda",
|
||||
"openaiCompatible": "Kompatibel dengan OpenAI",
|
||||
@@ -227,11 +231,7 @@
|
||||
"media": "Media",
|
||||
"mediaDescription": "Generate images, videos, and music",
|
||||
"themes": "Themes",
|
||||
"themesDescription": "Choose a color theme for the whole dashboard panel",
|
||||
"mcp": "MCP",
|
||||
"mcpDescription": "Model Context Protocol server management and tools",
|
||||
"a2a": "A2A",
|
||||
"a2aDescription": "Agent-to-Agent protocol tasks and observability"
|
||||
"themesDescription": "Choose a color theme for the whole dashboard panel"
|
||||
},
|
||||
"home": {
|
||||
"quickStart": "Mulai Cepat",
|
||||
@@ -332,6 +332,26 @@
|
||||
"keyCreatedNote": "Salin dan simpan kunci ini sekarang — kunci ini tidak akan ditampilkan lagi.",
|
||||
"done": "Selesai",
|
||||
"savePermissions": "Simpan Izin",
|
||||
"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",
|
||||
"allowAll": "Izinkan Semua",
|
||||
"restrict": "Batasi",
|
||||
"allowAllInfo": "Kunci ini dapat mengakses semua model yang tersedia.",
|
||||
@@ -356,6 +376,7 @@
|
||||
"failedUpdatePermissionsRetry": "Gagal memperbarui izin. Silakan coba lagi.",
|
||||
"unknownProvider": "tidak diketahui",
|
||||
"copyMaskedKey": "Salin kunci bertopeng",
|
||||
"keyOnlyAvailableAtCreation": "Full key available only at creation time — copy it when you first create the key",
|
||||
"modelsCount": "{count, plural, one {# model} other {# models}}",
|
||||
"lastUsedOn": "Terakhir: {date}",
|
||||
"editPermissions": "Edit izin",
|
||||
@@ -365,28 +386,7 @@
|
||||
"permissionsTitle": "Izin: {name}",
|
||||
"allowAllDesc": "Kunci ini dapat mengakses semua model yang tersedia.",
|
||||
"restrictDesc": "Kunci ini dapat mengakses {selectedCount} dari {totalModels} model.",
|
||||
"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"
|
||||
"selectedCount": "{count} dipilih"
|
||||
},
|
||||
"auditLog": {
|
||||
"title": "Catatan Audit",
|
||||
@@ -425,6 +425,57 @@
|
||||
"videoDescription": "Create videos with AnimateDiff, Stable Video Diffusion via ComfyUI or SD WebUI.",
|
||||
"musicDescription": "Compose music using Stable Audio Open or MusicGen via ComfyUI."
|
||||
},
|
||||
"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",
|
||||
"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",
|
||||
"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"
|
||||
},
|
||||
"cliTools": {
|
||||
"title": "Alat CLI",
|
||||
"noActiveProviders": "Tidak ada penyedia aktif",
|
||||
@@ -497,6 +548,11 @@
|
||||
"antigravityStep2Prefix": "2. Tambahkan",
|
||||
"antigravityStep2Suffix": "ke file host Anda sebagai 127.0.0.1.",
|
||||
"antigravityStep3": "3. Buka Antigravitasi dan permintaan akan diproksi.",
|
||||
"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.",
|
||||
"sudoPasswordRequiredTitle": "Kata Sandi Sudo Diperlukan",
|
||||
"sudoPasswordHint": "Kata sandi administrator diperlukan untuk mengubah file host dan pengaturan proxy sistem.",
|
||||
"enterSudoPassword": "Masukkan kata sandi sudo",
|
||||
@@ -569,6 +625,22 @@
|
||||
"openClawManualConfiguration": "Buka Claw - Konfigurasi Manual",
|
||||
"clineManualConfiguration": "Konfigurasi Manual Cline",
|
||||
"kiloManualConfiguration": "Konfigurasi Manual Kode Kilo",
|
||||
"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."
|
||||
},
|
||||
"toolDescriptions": {
|
||||
"antigravity": "IDE Antigravitasi Google dengan MITM",
|
||||
"claude": "CLI Kode Claude Antropis",
|
||||
@@ -706,27 +778,6 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"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": {
|
||||
@@ -988,7 +1039,12 @@
|
||||
"settingsApi": "Settings API",
|
||||
"categoryCore": "API Inti",
|
||||
"categoryMedia": "Media & Multi-Modal",
|
||||
"categorySearch": "Search & Discovery",
|
||||
"categoryUtility": "Utilitas & Manajemen",
|
||||
"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.",
|
||||
"enableCloudTitle": "Aktifkan Proksi Cloud",
|
||||
"whatYouGet": "Apa yang akan Anda dapatkan",
|
||||
"cloudBenefitAccess": "Akses API Anda dari mana saja di dunia",
|
||||
@@ -1021,6 +1077,25 @@
|
||||
"cloudWorkerUnreachable": "Tidak dapat menjangkau pekerja cloud. Pastikan layanan cloud berjalan (npm run dev di/cloud).",
|
||||
"connectionFailed": "Koneksi gagal",
|
||||
"syncFailed": "Gagal menyinkronkan data cloud",
|
||||
"cloudflaredTitle": "Cloudflare Quick Tunnel",
|
||||
"cloudflaredDescription": "Create a Cloudflare Quick Tunnel for this endpoint to make it accessible from the internet without configuring DNS.",
|
||||
"cloudflaredUrlNotice": "Creates a temporary Cloudflare Quick Tunnel. URLs change after restart.",
|
||||
"cloudflaredEnable": "Enable Tunnel",
|
||||
"cloudflaredInstallAndEnable": "Install & Enable",
|
||||
"cloudflaredDisable": "Stop Tunnel",
|
||||
"cloudflaredRunning": "Running",
|
||||
"cloudflaredStarting": "Starting",
|
||||
"cloudflaredStoppedState": "Stopped",
|
||||
"cloudflaredNotInstalled": "Not installed",
|
||||
"cloudflaredUnsupported": "Unsupported",
|
||||
"cloudflaredError": "Error",
|
||||
"cloudflaredStarted": "Cloudflare tunnel started",
|
||||
"cloudflaredStopped": "Cloudflare tunnel stopped",
|
||||
"cloudflaredRequestFailed": "Failed to update Cloudflare tunnel",
|
||||
"cloudflaredTemporaryNote": "Quick Tunnel URLs are temporary and will change after restart.",
|
||||
"cloudflaredUnsupportedNote": "This platform is not supported for managed installation.",
|
||||
"cloudflaredIdleNote": "Create a temporary Cloudflare Quick Tunnel for this endpoint.",
|
||||
"cloudflaredLastError": "Last error: {error}",
|
||||
"providerModelsTitle": "{provider} — Model",
|
||||
"noModelsForProvider": "Tidak ada model yang tersedia untuk penyedia ini.",
|
||||
"chat": "Obrolan",
|
||||
@@ -1055,31 +1130,19 @@
|
||||
"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",
|
||||
"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.",
|
||||
"cloudflaredDescription": "Create a Cloudflare Quick Tunnel for this endpoint to make it accessible from the internet without configuring DNS.",
|
||||
"cloudflaredDisable": "Stop Tunnel",
|
||||
"cloudflaredEnable": "Enable Tunnel",
|
||||
"cloudflaredError": "Error",
|
||||
"cloudflaredIdleNote": "Create a temporary Cloudflare Quick Tunnel for this endpoint.",
|
||||
"cloudflaredInstallAndEnable": "Install & Enable",
|
||||
"cloudflaredLastError": "Last error: {error}",
|
||||
"cloudflaredNotInstalled": "Not installed",
|
||||
"cloudflaredRequestFailed": "Failed to update Cloudflare tunnel",
|
||||
"cloudflaredRunning": "Running",
|
||||
"cloudflaredStarted": "Cloudflare tunnel started",
|
||||
"cloudflaredStarting": "Starting",
|
||||
"cloudflaredStopped": "Cloudflare tunnel stopped",
|
||||
"cloudflaredStoppedState": "Stopped",
|
||||
"cloudflaredTemporaryNote": "Quick Tunnel URLs are temporary and will change after restart.",
|
||||
"cloudflaredTitle": "Cloudflare Quick Tunnel",
|
||||
"cloudflaredUnsupported": "Unsupported",
|
||||
"cloudflaredUnsupportedNote": "This platform is not supported for managed installation.",
|
||||
"cloudflaredUrlNotice": "Creates a temporary Cloudflare Quick Tunnel. URLs change after restart."
|
||||
"completionsLegacyDesc": "Legacy OpenAI text completions — accepts both prompt string and messages array format"
|
||||
},
|
||||
"endpoints": {
|
||||
"tabProxy": "Endpoint Proxy",
|
||||
"tabApiEndpoints": "API Endpoints",
|
||||
"apiEndpointsTitle": "API Endpoints",
|
||||
"apiEndpointsDescription": "Backend API endpoints that can be consumed by other applications and services. This section will list all available REST APIs with documentation and testing capabilities.",
|
||||
"comingSoon": "Coming Soon",
|
||||
"plannedFeatures": "Planned Features",
|
||||
"featureRestApi": "REST API endpoint catalog with interactive documentation",
|
||||
"featureWebhooks": "Webhook configuration and event subscriptions",
|
||||
"featureSwagger": "OpenAPI / Swagger spec auto-generation",
|
||||
"featureAuth": "API key and OAuth scope management per endpoint"
|
||||
},
|
||||
"mcpDashboard": {
|
||||
"loading": "Loading MCP dashboard...",
|
||||
@@ -1251,6 +1314,12 @@
|
||||
"issuesLabel": "Masalah Terdeteksi",
|
||||
"operational": "Operasional",
|
||||
"providers": "Penyedia",
|
||||
"configuredProvidersLabel": "Dikonfigurasi di dasbor",
|
||||
"configuredProvidersHint": "Penyedia dengan kredensial yang disimpan di /dashboard/providers, apa pun status runtimenya.",
|
||||
"activeProviders": "{count} active",
|
||||
"activeProvidersHint": "Penyedia yang dikonfigurasi saat ini diaktifkan untuk permintaan perutean.",
|
||||
"monitoredProviders": "{count} monitored",
|
||||
"monitoredProvidersHint": "Penyedia saat ini dilacak oleh monitor kesehatan pemutus sirkuit.",
|
||||
"healthyCount": "{count} sehat",
|
||||
"nodeVersion": "Simpul {version}",
|
||||
"failures": "{count} kegagalan",
|
||||
@@ -1269,13 +1338,7 @@
|
||||
"resetAllTitle": "Setel ulang semua pemutus sirkuit ke kondisi sehat",
|
||||
"resetting": "Menyetel ulang...",
|
||||
"resetAll": "Atur Ulang Semua",
|
||||
"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."
|
||||
"until": "Sampai {time}"
|
||||
},
|
||||
"limits": {
|
||||
"title": "Batas & Kuota",
|
||||
@@ -1437,6 +1500,7 @@
|
||||
"clearing": "Membersihkan...",
|
||||
"until": "Sampai {time}",
|
||||
"providerTestFailed": "Tes penyedia gagal",
|
||||
"providerTestTimeout": "Provider test timed out — too many connections to test at once",
|
||||
"modeTest": "{mode} Tes",
|
||||
"passedCount": "{count} lulus",
|
||||
"failedCount": "{count} gagal",
|
||||
@@ -1478,6 +1542,14 @@
|
||||
"chatCompletions": "Penyelesaian Obrolan",
|
||||
"importingModels": "Mengimpor...",
|
||||
"importFromModels": "Impor dari /models",
|
||||
"allModelsAlreadyImported": "Semua model sudah diimpor",
|
||||
"noNewModelsToImport": "Tidak ada model baru untuk diimpor — semua model sudah ada di registri atau daftar model kustom",
|
||||
"skippingExistingModels": "Melewatkan {count} model yang sudah ada",
|
||||
"autoSync": "Sinkronisasi Otomatis",
|
||||
"autoSyncTooltip": "Segarkan daftar model secara otomatis setiap 24 jam (dapat dikonfigurasi melalui MODEL_SYNC_INTERVAL_HOURS)",
|
||||
"autoSyncEnabled": "Sinkronisasi otomatis diaktifkan — model akan disegarkan secara berkala",
|
||||
"autoSyncDisabled": "Sinkronisasi otomatis dinonaktifkan",
|
||||
"autoSyncToggleFailed": "Gagal mengaktifkan sinkronisasi otomatis",
|
||||
"clearAllModels": "Hapus Semua Model",
|
||||
"clearAllModelsConfirm": "Apakah Anda yakin ingin menghapus semua model untuk penyedia ini?",
|
||||
"clearAllModelsSuccess": "Semua model dihapus",
|
||||
@@ -1531,6 +1603,8 @@
|
||||
"addFirstConnectionHint": "Tambahkan koneksi pertama Anda untuk memulai",
|
||||
"addConnection": "Tambahkan Koneksi",
|
||||
"availableModels": "Model yang Tersedia",
|
||||
"builtInModels": "Built-in models",
|
||||
"builtInModelsHint": "Registry models for this provider. Use the pencil to set compatibility options.",
|
||||
"pageAutoRefresh": "Halaman akan disegarkan secara otomatis...",
|
||||
"statusDisabled": "dengan disabilitas",
|
||||
"statusConnected": "terhubung",
|
||||
@@ -1571,6 +1645,26 @@
|
||||
"openRouterModelPlaceholder": "antropik/claude-3-opus",
|
||||
"customModels": "Model Khusus",
|
||||
"customModelsHint": "Tambahkan ID model yang tidak ada dalam daftar default. Ini akan tersedia untuk perutean.",
|
||||
"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",
|
||||
"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": "ID Model",
|
||||
"customModelPlaceholder": "misalnya gpt-4.5-turbo",
|
||||
"loading": "Memuat...",
|
||||
@@ -1614,52 +1708,21 @@
|
||||
"compatibleBaseUrlHint": "Root URL of your {type}-compatible API. Use Advanced Settings for custom endpoint paths.",
|
||||
"apiKeyForCheck": "Kunci API (untuk Pemeriksaan)",
|
||||
"compatibleProdPlaceholder": "{type} Kompatibel (Prod)",
|
||||
"providerTestTimeout": "Provider test timed out — too many connections to test at once",
|
||||
"tokenRefreshed": "Token refreshed successfully",
|
||||
"tokenRefreshFailed": "Token refresh failed",
|
||||
"applyCodexAuthLocal": "Apply auth",
|
||||
"exportCodexAuthFile": "Export auth",
|
||||
"codexAuthAppliedLocal": "Codex auth.json applied locally",
|
||||
"codexAuthApplyFailed": "Failed to apply Codex auth.json locally",
|
||||
"codexAuthExported": "Codex auth.json exported",
|
||||
"codexAuthExportFailed": "Failed to export Codex auth.json",
|
||||
"advancedSettings": "Advanced Settings",
|
||||
"chatPathLabel": "Chat Endpoint Path",
|
||||
"chatPathPlaceholder": "/chat/completions",
|
||||
"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)",
|
||||
"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",
|
||||
"autoSync": "Sinkronisasi Otomatis",
|
||||
"autoSyncTooltip": "Segarkan daftar model secara otomatis setiap 24 jam (dapat dikonfigurasi melalui MODEL_SYNC_INTERVAL_HOURS)",
|
||||
"autoSyncEnabled": "Sinkronisasi otomatis diaktifkan — model akan disegarkan secara berkala",
|
||||
"autoSyncDisabled": "Sinkronisasi otomatis dinonaktifkan",
|
||||
"autoSyncToggleFailed": "Gagal mengaktifkan sinkronisasi otomatis",
|
||||
"allModelsAlreadyImported": "Semua model sudah diimpor",
|
||||
"noNewModelsToImport": "Tidak ada model baru untuk diimpor — semua model sudah ada di registri atau daftar model kustom",
|
||||
"skippingExistingModels": "Melewatkan {count} model yang sudah ada",
|
||||
"applyCodexAuthLocal": "Apply auth",
|
||||
"codexAuthAppliedLocal": "Codex auth.json applied locally",
|
||||
"codexAuthApplyFailed": "Failed to apply Codex auth.json locally",
|
||||
"codexAuthExportFailed": "Failed to export Codex auth.json",
|
||||
"codexAuthExported": "Codex auth.json exported",
|
||||
"exportCodexAuthFile": "Export auth"
|
||||
"modelsPathHint": "Custom models path for validation (e.g. /v4/models)"
|
||||
},
|
||||
"settings": {
|
||||
"title": "Pengaturan",
|
||||
@@ -1683,6 +1746,8 @@
|
||||
"darkMode": "Mode Gelap",
|
||||
"lightMode": "Modus Cahaya",
|
||||
"systemTheme": "Tema Sistem",
|
||||
"debugToggle": "Enable Debug Mode",
|
||||
"sidebarVisibilityToggle": "Show Sidebar Items",
|
||||
"enableCache": "Aktifkan Cache",
|
||||
"cacheTTL": "Tembolok TTL",
|
||||
"maxCacheSize": "Ukuran Tembolok Maks",
|
||||
@@ -1691,6 +1756,17 @@
|
||||
"cacheMisses": "Cache Hilang",
|
||||
"hitRate": "Tingkat Hit",
|
||||
"cacheEntries": "Entri Cache",
|
||||
"cacheSettings": "Cache Settings",
|
||||
"semanticCache": "Semantic Cache",
|
||||
"maxEntries": "Max Entries",
|
||||
"ttlMinutes": "TTL (minutes)",
|
||||
"promptCache": "Tembolok Cepat",
|
||||
"strategy": "Strategy",
|
||||
"preserveClientCache": "Preserve Client Cache",
|
||||
"enabled": "Enabled",
|
||||
"loading": "Loading...",
|
||||
"saving": "Menyimpan...",
|
||||
"save": "Save",
|
||||
"circuitBreaker": "Pemutus Arus",
|
||||
"retryPolicy": "Kebijakan Coba Ulang",
|
||||
"maxRetries": "Percobaan Ulang Maks",
|
||||
@@ -1698,6 +1774,10 @@
|
||||
"timeoutMs": "Batas waktu (md)",
|
||||
"enableSystemPrompt": "Aktifkan Perintah Sistem",
|
||||
"systemPromptText": "Teks Perintah Sistem",
|
||||
"autoDisableBannedAccounts": "Auto-Disable Banned Accounts",
|
||||
"autoDisableDescription": "Permanently mark provider connections as deactivated if they return specific terminal ban signals (e.g. HTTP 403 'verify your account'). This removes them from the combo rotation.",
|
||||
"autoDisableThreshold": "Ban Threshold",
|
||||
"autoDisableThresholdDesc": "Consecutive ban signals required before permanent deactivation.",
|
||||
"enableThinking": "Aktifkan Berpikir",
|
||||
"maxThinkingTokens": "Token Berpikir Maks",
|
||||
"enableProxy": "Aktifkan Proksi",
|
||||
@@ -1734,6 +1814,9 @@
|
||||
"themeLight": "Ringan",
|
||||
"themeDark": "Gelap",
|
||||
"themeSystem": "Sistem",
|
||||
"sidebarVisibility": "Hide sidebar items",
|
||||
"sidebarVisibilityDesc": "Hide any sidebar navigation entry to reduce visual clutter.",
|
||||
"sidebarVisibilityHint": "Any sidebar section is hidden automatically when a...",
|
||||
"hideHealthLogs": "Sembunyikan Log Pemeriksaan Kesehatan",
|
||||
"hideHealthLogsDesc": "Saat AKTIF, sembunyikan pesan [HealthCheck] di konsol server",
|
||||
"themeAccent": "Warna tema",
|
||||
@@ -1746,7 +1829,20 @@
|
||||
"themeViolet": "Ungu",
|
||||
"themeOrange": "Oranye",
|
||||
"themeCyan": "Sian",
|
||||
"promptCache": "Tembolok Cepat",
|
||||
"whitelabeling": "Branding",
|
||||
"whitelabelingDesc": "Customize the application name and logo",
|
||||
"appName": "Application Name",
|
||||
"appNameDesc": "Display name shown in sidebar and browser tab",
|
||||
"customLogo": "Custom Logo URL",
|
||||
"customLogoDesc": "URL to your custom logo image",
|
||||
"uploadLogo": "Upload Logo",
|
||||
"resetLogo": "Reset to Default",
|
||||
"logoPreview": "Preview",
|
||||
"customFavicon": "Browser Favicon",
|
||||
"customFaviconDesc": "URL to your custom favicon (shown in browser tab)",
|
||||
"uploadFavicon": "Upload Favicon",
|
||||
"resetFavicon": "Reset Favicon",
|
||||
"faviconPreview": "Favicon Preview",
|
||||
"flushCache": "Siram Cache",
|
||||
"flushing": "Pembilasan…",
|
||||
"size": "Ukuran",
|
||||
@@ -1804,7 +1900,15 @@
|
||||
"providersBlocked": "{count} penyedia diblokir dari /models",
|
||||
"blockProviderTitle": "Blokir {provider}",
|
||||
"unblockProviderTitle": "Buka blokir {provider}",
|
||||
"cliFingerprint": "CLI Fingerprint Matching",
|
||||
"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}",
|
||||
"routingStrategy": "Strategi Perutean",
|
||||
"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.",
|
||||
"fillFirst": "Isi Dulu",
|
||||
"fillFirstDesc": "Gunakan akun dalam urutan prioritas",
|
||||
"roundRobin": "Robin Bulat",
|
||||
@@ -1899,6 +2003,9 @@
|
||||
"fillModelAndProviders": "Silakan isi nama model dan penyedia",
|
||||
"addAtLeastOneProvider": "Tambahkan setidaknya satu penyedia",
|
||||
"comboDefaultsTitle": "Default Kombo",
|
||||
"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.",
|
||||
"globalComboConfig": "Konfigurasi kombo global",
|
||||
"defaultStrategy": "Strategi Bawaan",
|
||||
"defaultStrategyDesc": "Diterapkan pada kombo baru tanpa strategi eksplisit",
|
||||
@@ -2026,7 +2133,6 @@
|
||||
"unsaved": "belum disimpan",
|
||||
"resetDefaults": "Atur Ulang Default",
|
||||
"saveProvider": "Simpan Penyedia",
|
||||
"saving": "Menyimpan...",
|
||||
"model": "Model",
|
||||
"models": "model",
|
||||
"moreProviders": "{count} penyedia lainnya",
|
||||
@@ -2057,36 +2163,7 @@
|
||||
"customPricingNote": "Anda dapat mengganti harga default untuk model tertentu. Penggantian khusus lebih diprioritaskan dibandingkan harga yang terdeteksi otomatis.",
|
||||
"editPricing": "Sunting Harga",
|
||||
"viewFullDetails": "Lihat Detail Lengkap",
|
||||
"themeCoral": "Koral",
|
||||
"cliFingerprint": "CLI Fingerprint Matching",
|
||||
"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}",
|
||||
"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.",
|
||||
"sidebarVisibility": "Hide sidebar items",
|
||||
"sidebarVisibilityDesc": "Hide any sidebar navigation entry to reduce visual clutter.",
|
||||
"sidebarVisibilityHint": "Any sidebar section is hidden automatically when a...",
|
||||
"semanticCache": "Semantic Cache",
|
||||
"autoDisableDescription": "Permanently mark provider connections as deactivated if they return specific terminal ban signals (e.g. HTTP 403 'verify your account'). This removes them from the combo rotation.",
|
||||
"preserveClientCache": "Preserve Client Cache",
|
||||
"cacheSettings": "Cache Settings",
|
||||
"autoDisableThreshold": "Ban Threshold",
|
||||
"autoDisableBannedAccounts": "Auto-Disable Banned Accounts",
|
||||
"ttlMinutes": "TTL (minutes)",
|
||||
"maxEntries": "Max Entries",
|
||||
"loading": "Loading...",
|
||||
"save": "Save",
|
||||
"autoDisableThresholdDesc": "Consecutive ban signals required before permanent deactivation.",
|
||||
"debugToggle": "Enable Debug Mode",
|
||||
"sidebarVisibilityToggle": "Show Sidebar Items",
|
||||
"enabled": "Enabled",
|
||||
"strategy": "Strategy"
|
||||
"themeCoral": "Koral"
|
||||
},
|
||||
"translator": {
|
||||
"title": "Penerjemah",
|
||||
@@ -2505,14 +2582,14 @@
|
||||
"backToLogin": "Kembali ke Masuk",
|
||||
"forgotPassword": "Lupa kata sandi?",
|
||||
"defaultPasswordHint": "Default password: 123456 (unless INITIAL_PASSWORD was set)",
|
||||
"Authorization": "Otorisasi",
|
||||
"Content-Disposition": "Disposisi Konten",
|
||||
"waitingForAuthorization": "Waiting for authorization...",
|
||||
"waitingForGoogleAuthorization": "Waiting for Google authorization...",
|
||||
"waitingForOpenAIAuthorization": "Waiting for OpenAI authorization...",
|
||||
"waitingForAntigravityAuthorization": "Waiting for Antigravity authorization...",
|
||||
"waitingForQoderAuthorization": "Waiting for Qoder authorization...",
|
||||
"exchangingCodeForTokens": "Exchanging code for tokens...",
|
||||
"Authorization": "Otorisasi",
|
||||
"Content-Disposition": "Disposisi Konten"
|
||||
"exchangingCodeForTokens": "Exchanging code for tokens..."
|
||||
},
|
||||
"landing": {
|
||||
"brandName": "OmniRoute",
|
||||
@@ -2699,6 +2776,8 @@
|
||||
"endpointResponsesNote": "Titik akhir API respons (Codex, o-series).",
|
||||
"endpointModelsNote": "Katalog model untuk semua penyedia yang terhubung.",
|
||||
"endpointAudioNote": "Transkripsi audio (Deepgram, MajelisAI).",
|
||||
"endpointSpeechNote": "Generasi text-to-speech (ElevenLabs, OpenAI TTS).",
|
||||
"endpointEmbeddingsNote": "Generasi embedding teks (OpenAI, Cohere, Voyage).",
|
||||
"endpointImagesNote": "Pembuatan gambar (NanoBanana).",
|
||||
"endpointRewriteChatNote": "Tulis ulang pembantu untuk klien tanpa /v1.",
|
||||
"endpointRewriteResponsesNote": "Tulis ulang pembantu untuk Respons tanpa /v1.",
|
||||
@@ -2719,9 +2798,7 @@
|
||||
"troubleshootingCodexFamily": "Untuk model keluarga GitHub Codex, pertahankan model sebagai gh/codex-model; router memilih / merespons secara otomatis.",
|
||||
"troubleshootingTestConnection": "Gunakan Dasbor > Penyedia > Uji Koneksi sebelum menguji dari IDE atau klien eksternal.",
|
||||
"troubleshootingCircuitBreaker": "Jika penyedia menunjukkan pemutus sirkuit terbuka, tunggu hingga cooldown atau periksa halaman Kesehatan untuk detailnya.",
|
||||
"troubleshootingOAuth": "Untuk penyedia OAuth, autentikasi ulang jika masa berlaku token habis. Periksa indikator status kartu penyedia.",
|
||||
"endpointSpeechNote": "Generasi text-to-speech (ElevenLabs, OpenAI TTS).",
|
||||
"endpointEmbeddingsNote": "Generasi embedding teks (OpenAI, Cohere, Voyage)."
|
||||
"troubleshootingOAuth": "Untuk penyedia OAuth, autentikasi ulang jika masa berlaku token habis. Periksa indikator status kartu penyedia."
|
||||
},
|
||||
"legal": {
|
||||
"privacyPolicy": "Kebijakan Privasi",
|
||||
@@ -2783,18 +2860,6 @@
|
||||
"termsSection6Title": "6. Sumber Terbuka",
|
||||
"termsSection6Text": "OmniRoute adalah perangkat lunak sumber terbuka. Anda bebas memeriksa, memodifikasi, dan mendistribusikannya berdasarkan ketentuan lisensinya."
|
||||
},
|
||||
"endpoints": {
|
||||
"tabProxy": "Endpoint Proxy",
|
||||
"tabApiEndpoints": "API Endpoints",
|
||||
"apiEndpointsTitle": "API Endpoints",
|
||||
"apiEndpointsDescription": "Backend API endpoints that can be consumed by other applications and services. This section will list all available REST APIs with documentation and testing capabilities.",
|
||||
"comingSoon": "Coming Soon",
|
||||
"plannedFeatures": "Planned Features",
|
||||
"featureRestApi": "REST API endpoint catalog with interactive documentation",
|
||||
"featureWebhooks": "Webhook configuration and event subscriptions",
|
||||
"featureSwagger": "OpenAPI / Swagger spec auto-generation",
|
||||
"featureAuth": "API key and OAuth scope management per endpoint"
|
||||
},
|
||||
"agents": {
|
||||
"title": "CLI Agents",
|
||||
"description": "Discover installed CLI agents on your system. Add custom agents for auto-detection.",
|
||||
@@ -2849,57 +2914,6 @@
|
||||
"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"
|
||||
},
|
||||
"templateNames": {
|
||||
"simple-chat": "Simple Chat",
|
||||
"streaming": "Streaming",
|
||||
@@ -2949,67 +2963,67 @@
|
||||
"refresh": "Refresh",
|
||||
"clearAll": "Clear All",
|
||||
"memoryEntries": "Memory Entries",
|
||||
"memoryEntriesSub": "In-memory LRU",
|
||||
"dbEntries": "DB Entries",
|
||||
"dbEntriesSub": "Persisted (SQLite)",
|
||||
"cacheHits": "Cache Hits",
|
||||
"cacheHitsSub": "of {total} total",
|
||||
"tokensSaved": "Tokens Saved",
|
||||
"tokensSavedSub": "Estimated from hits",
|
||||
"hitRate": "Hit Rate",
|
||||
"performance": "Cache Performance",
|
||||
"behavior": "Cache Behavior",
|
||||
"idempotency": "Idempotency Layer",
|
||||
"clearSuccess": "Cache cleared. {count} expired entries removed.",
|
||||
"clearError": "Failed to clear cache.",
|
||||
"unavailable": "Cache unavailable",
|
||||
"unavailableDesc": "Could not fetch cache statistics. Make sure the server is running.",
|
||||
"memoryEntriesSub": "In-memory LRU",
|
||||
"dbEntriesSub": "Persisted (SQLite)",
|
||||
"cacheHitsSub": "of {total} total",
|
||||
"tokensSavedSub": "Estimated from hits",
|
||||
"autoRefresh": "Auto-refreshes every {seconds}s",
|
||||
"hits": "Hits",
|
||||
"misses": "Misses",
|
||||
"total": "Total",
|
||||
"behavior": "Cache Behavior",
|
||||
"behaviorDeterministic": "Only non-streaming requests with temperature=0 are cached.",
|
||||
"behaviorBypass": "Bypass with header {header}.",
|
||||
"behaviorTwoTier": "Two-tier storage: in-memory LRU (fast) + SQLite (persistent across restarts).",
|
||||
"behaviorTtl": "Default TTL: 30 minutes. Configure via {envVar}.",
|
||||
"idempotency": "Idempotency Layer",
|
||||
"activeDedupKeys": "Active Dedup Keys",
|
||||
"dedupWindow": "Dedup Window",
|
||||
"inputTokens": "Input Tokens",
|
||||
"clearSuccess": "Cache cleared. {count} expired entries removed.",
|
||||
"clearError": "Failed to clear cache.",
|
||||
"unavailable": "Cache unavailable",
|
||||
"unavailableDesc": "Could not fetch cache statistics. Make sure the server is running.",
|
||||
"promptCache": "Prompt Cache (Provider-Side)",
|
||||
"cachedRequests": "Cached Requests",
|
||||
"cacheHitRate": "Cache Hit Rate",
|
||||
"cachedTokens": "Cached Tokens",
|
||||
"cacheCreationTokens": "Cache Creation Tokens",
|
||||
"cacheMetrics": "Prompt Cache Metrics",
|
||||
"withCacheControl": "With Cache Control",
|
||||
"cachedTokensRead": "Cached Tokens (Read)",
|
||||
"cacheCreationWrite": "Cache Creation (Write)",
|
||||
"cacheReuseRatio": "Cache Reuse Ratio",
|
||||
"cacheReuseRatioDesc": "Cached tokens / Total input tokens",
|
||||
"estCostSaved": "Est. Cost Saved",
|
||||
"requestsShort": "reqs",
|
||||
"inputShort": "In",
|
||||
"resetting": "Resetting...",
|
||||
"cachedTokensCol": "Cached",
|
||||
"search": "Search",
|
||||
"cacheReuseRatioDesc": "Cached tokens / Total input tokens",
|
||||
"resetMetrics": "Reset Metrics",
|
||||
"loading": "Loading...",
|
||||
"cachedRequests": "Cached Requests",
|
||||
"model": "Model",
|
||||
"cached": "Cached",
|
||||
"actions": "Actions",
|
||||
"trend24h": "Cache Trend (24h)",
|
||||
"cachedShort": "Cached",
|
||||
"cacheCreation": "Creation",
|
||||
"byProvider": "Breakdown by Provider",
|
||||
"created": "Created",
|
||||
"cacheCreationTokens": "Cache Creation Tokens",
|
||||
"withCacheControl": "With Cache Control",
|
||||
"searchEntries": "Search entries...",
|
||||
"cacheReuseRatio": "Cache Reuse Ratio",
|
||||
"estCostSaved": "Est. Cost Saved",
|
||||
"cachedTokens": "Cached Tokens",
|
||||
"requests": "Requests",
|
||||
"signature": "Signature",
|
||||
"cacheCreationWrite": "Cache Creation (Write)",
|
||||
"expires": "Expires",
|
||||
"writeShort": "Write",
|
||||
"cacheHitRate": "Cache Hit Rate",
|
||||
"cacheMetrics": "Prompt Cache Metrics",
|
||||
"overview": "Overview",
|
||||
"promptCache": "Prompt Cache (Provider-Side)",
|
||||
"resetting": "Resetting...",
|
||||
"resetMetrics": "Reset Metrics",
|
||||
"byProvider": "Breakdown by Provider",
|
||||
"provider": "Provider",
|
||||
"cachedTokensRead": "Cached Tokens (Read)",
|
||||
"requests": "Requests",
|
||||
"inputTokens": "Input Tokens",
|
||||
"cachedTokensCol": "Cached",
|
||||
"cacheCreation": "Creation",
|
||||
"trend24h": "Cache Trend (24h)",
|
||||
"cached": "Cached",
|
||||
"overview": "Overview",
|
||||
"entries": "Entries",
|
||||
"noEntries": "No cache entries found"
|
||||
"searchEntries": "Search entries...",
|
||||
"search": "Search",
|
||||
"loading": "Loading...",
|
||||
"noEntries": "No cache entries found",
|
||||
"signature": "Signature",
|
||||
"model": "Model",
|
||||
"created": "Created",
|
||||
"expires": "Expires",
|
||||
"actions": "Actions"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -143,6 +143,7 @@
|
||||
"dashboard": "Pannello di controllo",
|
||||
"providers": "Fornitori",
|
||||
"combos": "Combinazioni",
|
||||
"autoCombo": "Auto Combo",
|
||||
"usage": "Utilizzo",
|
||||
"analytics": "Analitica",
|
||||
"costs": "Costi",
|
||||
@@ -152,8 +153,12 @@
|
||||
"media": "Media",
|
||||
"settings": "Impostazioni",
|
||||
"translator": "Traduttore",
|
||||
"playground": "Playground",
|
||||
"searchTools": "Search Tools",
|
||||
"agents": "Agenti",
|
||||
"docs": "Documenti",
|
||||
"issues": "Problemi",
|
||||
"endpoints": "Endpoint",
|
||||
"apiManager": "Gestore API",
|
||||
"logs": "Registri",
|
||||
"auditLog": "Registro di controllo",
|
||||
@@ -165,6 +170,11 @@
|
||||
"debug": "Debug",
|
||||
"system": "Sistema",
|
||||
"help": "Aiuto",
|
||||
"primarySection": "Main",
|
||||
"cliSection": "CLI",
|
||||
"debugSection": "Debug",
|
||||
"systemSection": "System",
|
||||
"helpSection": "Help",
|
||||
"serverDisconnected": "Server disconnesso",
|
||||
"serverDisconnectedMsg": "Il server proxy è stato arrestato o si sta riavviando.",
|
||||
"expandSidebar": "Espandi la barra laterale",
|
||||
@@ -180,19 +190,9 @@
|
||||
"themeViolet": "Violet",
|
||||
"themeOrange": "Orange",
|
||||
"themeCyan": "Cyan",
|
||||
"endpoints": "Endpoint",
|
||||
"playground": "Playground",
|
||||
"agents": "Agenti",
|
||||
"cliToolsShort": "Strumenti",
|
||||
"autoCombo": "Auto Combo",
|
||||
"searchTools": "Search Tools",
|
||||
"cache": "Cache",
|
||||
"cacheShort": "Cache",
|
||||
"cliSection": "CLI",
|
||||
"debugSection": "Debug",
|
||||
"helpSection": "Help",
|
||||
"primarySection": "Main",
|
||||
"systemSection": "System"
|
||||
"cacheShort": "Cache"
|
||||
},
|
||||
"themesPage": {
|
||||
"title": "Themes",
|
||||
@@ -220,6 +220,10 @@
|
||||
"homeDescription": "Benvenuti in OmniRoute",
|
||||
"endpoint": "Endpoint",
|
||||
"endpointDescription": "Gestire endpoint proxy, MCP, A2A e endpoint API",
|
||||
"mcp": "MCP",
|
||||
"mcpDescription": "Model Context Protocol server management and tools",
|
||||
"a2a": "A2A",
|
||||
"a2aDescription": "Agent-to-Agent protocol tasks and observability",
|
||||
"settings": "Impostazioni",
|
||||
"settingsDescription": "Gestisci le tue preferenze",
|
||||
"openaiCompatible": "Compatibile con OpenAI",
|
||||
@@ -227,11 +231,7 @@
|
||||
"media": "Media",
|
||||
"mediaDescription": "Generate images, videos, and music",
|
||||
"themes": "Themes",
|
||||
"themesDescription": "Choose a color theme for the whole dashboard panel",
|
||||
"mcp": "MCP",
|
||||
"mcpDescription": "Model Context Protocol server management and tools",
|
||||
"a2a": "A2A",
|
||||
"a2aDescription": "Agent-to-Agent protocol tasks and observability"
|
||||
"themesDescription": "Choose a color theme for the whole dashboard panel"
|
||||
},
|
||||
"home": {
|
||||
"quickStart": "Avvio rapido",
|
||||
@@ -332,6 +332,26 @@
|
||||
"keyCreatedNote": "Copia e memorizza questa chiave adesso: non verrà più mostrata.",
|
||||
"done": "Fatto",
|
||||
"savePermissions": "Salva autorizzazioni",
|
||||
"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",
|
||||
"allowAll": "Consenti tutto",
|
||||
"restrict": "Limitare",
|
||||
"allowAllInfo": "Questa chiave può accedere a tutti i modelli disponibili.",
|
||||
@@ -356,6 +376,7 @@
|
||||
"failedUpdatePermissionsRetry": "Impossibile aggiornare le autorizzazioni. Per favore riprova.",
|
||||
"unknownProvider": "sconosciuto",
|
||||
"copyMaskedKey": "Copia chiave mascherata",
|
||||
"keyOnlyAvailableAtCreation": "Full key available only at creation time — copy it when you first create the key",
|
||||
"modelsCount": "{count, plural, one {# model} other {# models}}",
|
||||
"lastUsedOn": "Ultimo: {date}",
|
||||
"editPermissions": "Modifica autorizzazioni",
|
||||
@@ -365,28 +386,7 @@
|
||||
"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",
|
||||
"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"
|
||||
"selectedCount": "{count} selezionato"
|
||||
},
|
||||
"auditLog": {
|
||||
"title": "Registro di controllo",
|
||||
@@ -425,6 +425,57 @@
|
||||
"videoDescription": "Create videos with AnimateDiff, Stable Video Diffusion via ComfyUI or SD WebUI.",
|
||||
"musicDescription": "Compose music using Stable Audio Open or MusicGen via ComfyUI."
|
||||
},
|
||||
"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",
|
||||
"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",
|
||||
"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"
|
||||
},
|
||||
"cliTools": {
|
||||
"title": "Strumenti CLI",
|
||||
"noActiveProviders": "Nessun fornitore attivo",
|
||||
@@ -497,6 +548,11 @@
|
||||
"antigravityStep2Prefix": "2. Aggiungi",
|
||||
"antigravityStep2Suffix": "al file host come 127.0.0.1.",
|
||||
"antigravityStep3": "3. Open Antigravity e le richieste verranno inoltrate.",
|
||||
"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.",
|
||||
"sudoPasswordRequiredTitle": "Password Sudo obbligatoria",
|
||||
"sudoPasswordHint": "Per modificare le impostazioni del file host e del proxy di sistema è necessaria la password dell'amministratore.",
|
||||
"enterSudoPassword": "Inserisci la password sudo",
|
||||
@@ -569,6 +625,22 @@
|
||||
"openClawManualConfiguration": "Open Claw - Configurazione manuale",
|
||||
"clineManualConfiguration": "Configurazione manuale della linea",
|
||||
"kiloManualConfiguration": "Configurazione manuale del codice Kilo",
|
||||
"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."
|
||||
},
|
||||
"toolDescriptions": {
|
||||
"antigravity": "IDE Antigravità di Google con MITM",
|
||||
"claude": "CLI del codice Claude antropico",
|
||||
@@ -706,27 +778,6 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"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": {
|
||||
@@ -988,7 +1039,12 @@
|
||||
"settingsApi": "Settings API",
|
||||
"categoryCore": "API Principali",
|
||||
"categoryMedia": "Media e Multi-Modale",
|
||||
"categorySearch": "Search & Discovery",
|
||||
"categoryUtility": "Utilità e Gestione",
|
||||
"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.",
|
||||
"enableCloudTitle": "Abilita proxy cloud",
|
||||
"whatYouGet": "Cosa otterrai",
|
||||
"cloudBenefitAccess": "Accedi alla tua API da qualsiasi parte del mondo",
|
||||
@@ -1021,6 +1077,25 @@
|
||||
"cloudWorkerUnreachable": "Impossibile raggiungere il lavoratore cloud. Assicurati che il servizio cloud sia in esecuzione (npm run dev in /cloud).",
|
||||
"connectionFailed": "Connessione non riuscita",
|
||||
"syncFailed": "Impossibile sincronizzare i dati cloud",
|
||||
"cloudflaredTitle": "Cloudflare Quick Tunnel",
|
||||
"cloudflaredDescription": "Create a Cloudflare Quick Tunnel for this endpoint to make it accessible from the internet without configuring DNS.",
|
||||
"cloudflaredUrlNotice": "Creates a temporary Cloudflare Quick Tunnel. URLs change after restart.",
|
||||
"cloudflaredEnable": "Enable Tunnel",
|
||||
"cloudflaredInstallAndEnable": "Install & Enable",
|
||||
"cloudflaredDisable": "Stop Tunnel",
|
||||
"cloudflaredRunning": "Running",
|
||||
"cloudflaredStarting": "Starting",
|
||||
"cloudflaredStoppedState": "Stopped",
|
||||
"cloudflaredNotInstalled": "Not installed",
|
||||
"cloudflaredUnsupported": "Unsupported",
|
||||
"cloudflaredError": "Error",
|
||||
"cloudflaredStarted": "Cloudflare tunnel started",
|
||||
"cloudflaredStopped": "Cloudflare tunnel stopped",
|
||||
"cloudflaredRequestFailed": "Failed to update Cloudflare tunnel",
|
||||
"cloudflaredTemporaryNote": "Quick Tunnel URLs are temporary and will change after restart.",
|
||||
"cloudflaredUnsupportedNote": "This platform is not supported for managed installation.",
|
||||
"cloudflaredIdleNote": "Create a temporary Cloudflare Quick Tunnel for this endpoint.",
|
||||
"cloudflaredLastError": "Last error: {error}",
|
||||
"providerModelsTitle": "{provider} — Modelli",
|
||||
"noModelsForProvider": "Nessun modello disponibile per questo fornitore.",
|
||||
"chat": "Chiacchierata",
|
||||
@@ -1055,31 +1130,19 @@
|
||||
"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",
|
||||
"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.",
|
||||
"cloudflaredDescription": "Create a Cloudflare Quick Tunnel for this endpoint to make it accessible from the internet without configuring DNS.",
|
||||
"cloudflaredDisable": "Stop Tunnel",
|
||||
"cloudflaredEnable": "Enable Tunnel",
|
||||
"cloudflaredError": "Error",
|
||||
"cloudflaredIdleNote": "Create a temporary Cloudflare Quick Tunnel for this endpoint.",
|
||||
"cloudflaredInstallAndEnable": "Install & Enable",
|
||||
"cloudflaredLastError": "Last error: {error}",
|
||||
"cloudflaredNotInstalled": "Not installed",
|
||||
"cloudflaredRequestFailed": "Failed to update Cloudflare tunnel",
|
||||
"cloudflaredRunning": "Running",
|
||||
"cloudflaredStarted": "Cloudflare tunnel started",
|
||||
"cloudflaredStarting": "Starting",
|
||||
"cloudflaredStopped": "Cloudflare tunnel stopped",
|
||||
"cloudflaredStoppedState": "Stopped",
|
||||
"cloudflaredTemporaryNote": "Quick Tunnel URLs are temporary and will change after restart.",
|
||||
"cloudflaredTitle": "Cloudflare Quick Tunnel",
|
||||
"cloudflaredUnsupported": "Unsupported",
|
||||
"cloudflaredUnsupportedNote": "This platform is not supported for managed installation.",
|
||||
"cloudflaredUrlNotice": "Creates a temporary Cloudflare Quick Tunnel. URLs change after restart."
|
||||
"completionsLegacyDesc": "Legacy OpenAI text completions — accepts both prompt string and messages array format"
|
||||
},
|
||||
"endpoints": {
|
||||
"tabProxy": "Endpoint Proxy",
|
||||
"tabApiEndpoints": "Endpoint API",
|
||||
"apiEndpointsTitle": "Endpoint API",
|
||||
"apiEndpointsDescription": "Endpoint API backend che possono essere consumati da altre applicazioni e servizi.",
|
||||
"comingSoon": "In arrivo",
|
||||
"plannedFeatures": "Funzionalità previste",
|
||||
"featureRestApi": "Catalogo endpoint REST API con documentazione interattiva",
|
||||
"featureWebhooks": "Configurazione webhook e sottoscrizioni eventi",
|
||||
"featureSwagger": "Generazione automatica specifiche OpenAPI / Swagger",
|
||||
"featureAuth": "Gestione chiavi API e ambiti OAuth per endpoint"
|
||||
},
|
||||
"mcpDashboard": {
|
||||
"loading": "Loading MCP dashboard...",
|
||||
@@ -1251,6 +1314,12 @@
|
||||
"issuesLabel": "Problemi rilevati",
|
||||
"operational": "Operativo",
|
||||
"providers": "Fornitori",
|
||||
"configuredProvidersLabel": "Configurato nel dashboard",
|
||||
"configuredProvidersHint": "Provider con credenziali salvate in /dashboard/providers, indipendentemente dallo stato di runtime.",
|
||||
"activeProviders": "{count} active",
|
||||
"activeProvidersHint": "Provider configurati attualmente abilitati per le richieste di instradamento.",
|
||||
"monitoredProviders": "{count} monitored",
|
||||
"monitoredProvidersHint": "Fornitori attualmente monitorati dai monitoraggi dello stato degli interruttori automatici.",
|
||||
"healthyCount": "{count} sano",
|
||||
"nodeVersion": "Nodo {version}",
|
||||
"failures": "{count} errore",
|
||||
@@ -1269,13 +1338,7 @@
|
||||
"resetAllTitle": "Ripristinare tutti gli interruttori automatici allo stato integro",
|
||||
"resetting": "Reimpostazione...",
|
||||
"resetAll": "Reimposta tutto",
|
||||
"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."
|
||||
"until": "Fino al {time}"
|
||||
},
|
||||
"limits": {
|
||||
"title": "Limiti e quote",
|
||||
@@ -1437,6 +1500,7 @@
|
||||
"clearing": "Cancellazione...",
|
||||
"until": "Fino al {time}",
|
||||
"providerTestFailed": "Il test del fornitore è fallito",
|
||||
"providerTestTimeout": "Provider test timed out — too many connections to test at once",
|
||||
"modeTest": "{mode} Prova",
|
||||
"passedCount": "{count} superato",
|
||||
"failedCount": "{count} non riuscito",
|
||||
@@ -1478,6 +1542,14 @@
|
||||
"chatCompletions": "Completamenti della chat",
|
||||
"importingModels": "Importazione...",
|
||||
"importFromModels": "Importa da /modelli",
|
||||
"allModelsAlreadyImported": "Tutti i modelli sono già importati",
|
||||
"noNewModelsToImport": "Nessun nuovo modello da importare — tutti i modelli sono già nel registro o nell'elenco dei modelli personalizzati",
|
||||
"skippingExistingModels": "Salto {count} modelli esistenti",
|
||||
"autoSync": "Sincronizzazione automatica",
|
||||
"autoSyncTooltip": "Aggiorna automaticamente l'elenco dei modelli ogni 24 ore (configurabile tramite MODEL_SYNC_INTERVAL_HOURS)",
|
||||
"autoSyncEnabled": "Sincronizzazione automatica abilitata — i modelli verranno aggiornati periodicamente",
|
||||
"autoSyncDisabled": "Sincronizzazione automatica disabilitata",
|
||||
"autoSyncToggleFailed": "Impossibile attivare la sincronizzazione automatica",
|
||||
"clearAllModels": "Cancella tutti i modelli",
|
||||
"clearAllModelsConfirm": "Sei sicuro di voler rimuovere tutti i modelli per questo provider?",
|
||||
"clearAllModelsSuccess": "Tutti i modelli cancellati",
|
||||
@@ -1531,6 +1603,8 @@
|
||||
"addFirstConnectionHint": "Aggiungi la tua prima connessione per iniziare",
|
||||
"addConnection": "Aggiungi connessione",
|
||||
"availableModels": "Modelli disponibili",
|
||||
"builtInModels": "Built-in models",
|
||||
"builtInModelsHint": "Registry models for this provider. Use the pencil to set compatibility options.",
|
||||
"pageAutoRefresh": "La pagina si aggiornerà automaticamente...",
|
||||
"statusDisabled": "disabilitato",
|
||||
"statusConnected": "collegato",
|
||||
@@ -1571,6 +1645,26 @@
|
||||
"openRouterModelPlaceholder": "antropico/claude-3-opus",
|
||||
"customModels": "Modelli personalizzati",
|
||||
"customModelsHint": "Aggiungi ID modello non presenti nell'elenco predefinito. Questi saranno disponibili per il routing.",
|
||||
"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",
|
||||
"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": "ID modello",
|
||||
"customModelPlaceholder": "per esempio. gpt-4.5-turbo",
|
||||
"loading": "Caricamento...",
|
||||
@@ -1614,52 +1708,21 @@
|
||||
"compatibleBaseUrlHint": "Root URL of your {type}-compatible API. Use Advanced Settings for custom endpoint paths.",
|
||||
"apiKeyForCheck": "Chiave API (per controllo)",
|
||||
"compatibleProdPlaceholder": "{type} Compatibile (prodotto)",
|
||||
"providerTestTimeout": "Provider test timed out — too many connections to test at once",
|
||||
"tokenRefreshed": "Token refreshed successfully",
|
||||
"tokenRefreshFailed": "Token refresh failed",
|
||||
"applyCodexAuthLocal": "Apply auth",
|
||||
"exportCodexAuthFile": "Export auth",
|
||||
"codexAuthAppliedLocal": "Codex auth.json applied locally",
|
||||
"codexAuthApplyFailed": "Failed to apply Codex auth.json locally",
|
||||
"codexAuthExported": "Codex auth.json exported",
|
||||
"codexAuthExportFailed": "Failed to export Codex auth.json",
|
||||
"advancedSettings": "Advanced Settings",
|
||||
"chatPathLabel": "Chat Endpoint Path",
|
||||
"chatPathPlaceholder": "/chat/completions",
|
||||
"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)",
|
||||
"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",
|
||||
"autoSync": "Sincronizzazione automatica",
|
||||
"autoSyncTooltip": "Aggiorna automaticamente l'elenco dei modelli ogni 24 ore (configurabile tramite MODEL_SYNC_INTERVAL_HOURS)",
|
||||
"autoSyncEnabled": "Sincronizzazione automatica abilitata — i modelli verranno aggiornati periodicamente",
|
||||
"autoSyncDisabled": "Sincronizzazione automatica disabilitata",
|
||||
"autoSyncToggleFailed": "Impossibile attivare la sincronizzazione automatica",
|
||||
"allModelsAlreadyImported": "Tutti i modelli sono già importati",
|
||||
"noNewModelsToImport": "Nessun nuovo modello da importare — tutti i modelli sono già nel registro o nell'elenco dei modelli personalizzati",
|
||||
"skippingExistingModels": "Salto {count} modelli esistenti",
|
||||
"applyCodexAuthLocal": "Apply auth",
|
||||
"codexAuthAppliedLocal": "Codex auth.json applied locally",
|
||||
"codexAuthApplyFailed": "Failed to apply Codex auth.json locally",
|
||||
"codexAuthExportFailed": "Failed to export Codex auth.json",
|
||||
"codexAuthExported": "Codex auth.json exported",
|
||||
"exportCodexAuthFile": "Export auth"
|
||||
"modelsPathHint": "Custom models path for validation (e.g. /v4/models)"
|
||||
},
|
||||
"settings": {
|
||||
"title": "Impostazioni",
|
||||
@@ -1683,6 +1746,8 @@
|
||||
"darkMode": "Modalità oscura",
|
||||
"lightMode": "Modalità luce",
|
||||
"systemTheme": "Tema del sistema",
|
||||
"debugToggle": "Enable Debug Mode",
|
||||
"sidebarVisibilityToggle": "Show Sidebar Items",
|
||||
"enableCache": "Abilita cache",
|
||||
"cacheTTL": "TTL della cache",
|
||||
"maxCacheSize": "Dimensione massima della cache",
|
||||
@@ -1691,6 +1756,17 @@
|
||||
"cacheMisses": "Mancati cache",
|
||||
"hitRate": "Tasso di successo",
|
||||
"cacheEntries": "Voci della cache",
|
||||
"cacheSettings": "Cache Settings",
|
||||
"semanticCache": "Semantic Cache",
|
||||
"maxEntries": "Max Entries",
|
||||
"ttlMinutes": "TTL (minutes)",
|
||||
"promptCache": "Cache dei suggerimenti",
|
||||
"strategy": "Strategy",
|
||||
"preserveClientCache": "Preserve Client Cache",
|
||||
"enabled": "Enabled",
|
||||
"loading": "Loading...",
|
||||
"saving": "Risparmio...",
|
||||
"save": "Save",
|
||||
"circuitBreaker": "Interruttore automatico",
|
||||
"retryPolicy": "Riprova la policy",
|
||||
"maxRetries": "Numero massimo di tentativi",
|
||||
@@ -1698,6 +1774,10 @@
|
||||
"timeoutMs": "Timeout (ms)",
|
||||
"enableSystemPrompt": "Abilita richiesta di sistema",
|
||||
"systemPromptText": "Testo della richiesta di sistema",
|
||||
"autoDisableBannedAccounts": "Auto-Disable Banned Accounts",
|
||||
"autoDisableDescription": "Permanently mark provider connections as deactivated if they return specific terminal ban signals (e.g. HTTP 403 'verify your account'). This removes them from the combo rotation.",
|
||||
"autoDisableThreshold": "Ban Threshold",
|
||||
"autoDisableThresholdDesc": "Consecutive ban signals required before permanent deactivation.",
|
||||
"enableThinking": "Abilita il pensiero",
|
||||
"maxThinkingTokens": "Gettoni pensiero massimo",
|
||||
"enableProxy": "Abilita proxy",
|
||||
@@ -1734,6 +1814,9 @@
|
||||
"themeLight": "Leggero",
|
||||
"themeDark": "Buio",
|
||||
"themeSystem": "Sistema",
|
||||
"sidebarVisibility": "Hide sidebar items",
|
||||
"sidebarVisibilityDesc": "Hide any sidebar navigation entry to reduce visual clutter.",
|
||||
"sidebarVisibilityHint": "Any sidebar section is hidden automatically when a...",
|
||||
"hideHealthLogs": "Nascondi i registri di controllo dello stato",
|
||||
"hideHealthLogsDesc": "Quando è attivo, elimina i messaggi [HealthCheck] nella console del server",
|
||||
"themeAccent": "Colore del tema",
|
||||
@@ -1746,7 +1829,20 @@
|
||||
"themeViolet": "Viola",
|
||||
"themeOrange": "Arancione",
|
||||
"themeCyan": "Ciano",
|
||||
"promptCache": "Cache dei suggerimenti",
|
||||
"whitelabeling": "Branding",
|
||||
"whitelabelingDesc": "Customize the application name and logo",
|
||||
"appName": "Application Name",
|
||||
"appNameDesc": "Display name shown in sidebar and browser tab",
|
||||
"customLogo": "Custom Logo URL",
|
||||
"customLogoDesc": "URL to your custom logo image",
|
||||
"uploadLogo": "Upload Logo",
|
||||
"resetLogo": "Reset to Default",
|
||||
"logoPreview": "Preview",
|
||||
"customFavicon": "Browser Favicon",
|
||||
"customFaviconDesc": "URL to your custom favicon (shown in browser tab)",
|
||||
"uploadFavicon": "Upload Favicon",
|
||||
"resetFavicon": "Reset Favicon",
|
||||
"faviconPreview": "Favicon Preview",
|
||||
"flushCache": "Svuota cache",
|
||||
"flushing": "Lavaggio…",
|
||||
"size": "Misurare",
|
||||
@@ -1804,7 +1900,15 @@
|
||||
"providersBlocked": "{count} provider bloccati da /models",
|
||||
"blockProviderTitle": "Blocca {provider}",
|
||||
"unblockProviderTitle": "Sblocca {provider}",
|
||||
"cliFingerprint": "CLI Fingerprint Matching",
|
||||
"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}",
|
||||
"routingStrategy": "Strategia di instradamento",
|
||||
"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.",
|
||||
"fillFirst": "Riempi prima",
|
||||
"fillFirstDesc": "Utilizza gli account in ordine di priorità",
|
||||
"roundRobin": "Girotondo",
|
||||
@@ -1899,6 +2003,9 @@
|
||||
"fillModelAndProviders": "Inserisci il nome del modello e i fornitori",
|
||||
"addAtLeastOneProvider": "Aggiungi almeno un fornitore",
|
||||
"comboDefaultsTitle": "Impostazioni predefinite combinate",
|
||||
"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.",
|
||||
"globalComboConfig": "Configurazione combinata globale",
|
||||
"defaultStrategy": "Strategia predefinita",
|
||||
"defaultStrategyDesc": "Applicato a nuove combo senza strategia esplicita",
|
||||
@@ -2026,7 +2133,6 @@
|
||||
"unsaved": "non salvato",
|
||||
"resetDefaults": "Ripristina le impostazioni predefinite",
|
||||
"saveProvider": "Salva fornitore",
|
||||
"saving": "Risparmio...",
|
||||
"model": "Modello",
|
||||
"models": "modelli",
|
||||
"moreProviders": "{count} altri fornitori",
|
||||
@@ -2057,36 +2163,7 @@
|
||||
"customPricingNote": "Puoi sostituire i prezzi predefiniti per modelli specifici. Le sostituzioni personalizzate hanno la priorità sui prezzi rilevati automaticamente.",
|
||||
"editPricing": "Modifica prezzi",
|
||||
"viewFullDetails": "Visualizza i dettagli completi",
|
||||
"themeCoral": "Corallo",
|
||||
"cliFingerprint": "CLI Fingerprint Matching",
|
||||
"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}",
|
||||
"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.",
|
||||
"sidebarVisibility": "Hide sidebar items",
|
||||
"sidebarVisibilityDesc": "Hide any sidebar navigation entry to reduce visual clutter.",
|
||||
"sidebarVisibilityHint": "Any sidebar section is hidden automatically when a...",
|
||||
"semanticCache": "Semantic Cache",
|
||||
"autoDisableDescription": "Permanently mark provider connections as deactivated if they return specific terminal ban signals (e.g. HTTP 403 'verify your account'). This removes them from the combo rotation.",
|
||||
"preserveClientCache": "Preserve Client Cache",
|
||||
"cacheSettings": "Cache Settings",
|
||||
"autoDisableThreshold": "Ban Threshold",
|
||||
"autoDisableBannedAccounts": "Auto-Disable Banned Accounts",
|
||||
"ttlMinutes": "TTL (minutes)",
|
||||
"maxEntries": "Max Entries",
|
||||
"loading": "Loading...",
|
||||
"save": "Save",
|
||||
"autoDisableThresholdDesc": "Consecutive ban signals required before permanent deactivation.",
|
||||
"debugToggle": "Enable Debug Mode",
|
||||
"sidebarVisibilityToggle": "Show Sidebar Items",
|
||||
"enabled": "Enabled",
|
||||
"strategy": "Strategy"
|
||||
"themeCoral": "Corallo"
|
||||
},
|
||||
"translator": {
|
||||
"title": "Traduttore",
|
||||
@@ -2505,14 +2582,14 @@
|
||||
"backToLogin": "Torna all'accesso",
|
||||
"forgotPassword": "Ha dimenticato la password?",
|
||||
"defaultPasswordHint": "Default password: 123456 (unless INITIAL_PASSWORD was set)",
|
||||
"Authorization": "Autorizzazione",
|
||||
"Content-Disposition": "Disposizione del contenuto",
|
||||
"waitingForAuthorization": "Waiting for authorization...",
|
||||
"waitingForGoogleAuthorization": "Waiting for Google authorization...",
|
||||
"waitingForOpenAIAuthorization": "Waiting for OpenAI authorization...",
|
||||
"waitingForAntigravityAuthorization": "Waiting for Antigravity authorization...",
|
||||
"waitingForQoderAuthorization": "Waiting for Qoder authorization...",
|
||||
"exchangingCodeForTokens": "Exchanging code for tokens...",
|
||||
"Authorization": "Autorizzazione",
|
||||
"Content-Disposition": "Disposizione del contenuto"
|
||||
"exchangingCodeForTokens": "Exchanging code for tokens..."
|
||||
},
|
||||
"landing": {
|
||||
"brandName": "OmniRoute",
|
||||
@@ -2606,6 +2683,8 @@
|
||||
"clientCompatibility": "Compatibilità con il cliente",
|
||||
"protocolsToc": "Protocols",
|
||||
"apiReference": "Riferimento API",
|
||||
"managementApiReference": "Management API Reference",
|
||||
"managementApiDescription": "Automation endpoints for proxy registry, scope assignments, and legacy proxy migration.",
|
||||
"method": "Metodo",
|
||||
"path": "Sentiero",
|
||||
"notes": "Note",
|
||||
@@ -2697,10 +2776,19 @@
|
||||
"endpointResponsesNote": "Endpoint API di risposta (Codex, serie o).",
|
||||
"endpointModelsNote": "Catalogo dei modelli per tutti i fornitori collegati.",
|
||||
"endpointAudioNote": "Trascrizione audio (Deepgram, AssemblyAI).",
|
||||
"endpointSpeechNote": "Generazione sintesi vocale (ElevenLabs, OpenAI TTS).",
|
||||
"endpointEmbeddingsNote": "Generazione embedding di testo (OpenAI, Cohere, Voyage).",
|
||||
"endpointImagesNote": "Generazione di immagini (NanoBanana).",
|
||||
"endpointRewriteChatNote": "Riscrivere l'helper per i client senza /v1.",
|
||||
"endpointRewriteResponsesNote": "Riscrivi l'helper per le risposte senza /v1.",
|
||||
"endpointRewriteModelsNote": "Riscrivere l'helper per l'individuazione dei modelli senza /v1.",
|
||||
"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.",
|
||||
"modelPrefixesDescriptionStart": "Utilizzare il prefisso del provider prima del nome del modello per instradare a un provider specifico. Esempio:",
|
||||
"modelPrefixesDescriptionEnd": "percorsi verso GitHub Copilot.",
|
||||
"provider": "Fornitore",
|
||||
@@ -2710,18 +2798,7 @@
|
||||
"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.",
|
||||
"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.",
|
||||
"endpointSpeechNote": "Generazione sintesi vocale (ElevenLabs, OpenAI TTS).",
|
||||
"endpointEmbeddingsNote": "Generazione embedding di testo (OpenAI, Cohere, Voyage)."
|
||||
"troubleshootingOAuth": "Per i provider OAuth, eseguire nuovamente l'autenticazione se i token scadono. Controlla l'indicatore di stato della carta del fornitore."
|
||||
},
|
||||
"legal": {
|
||||
"privacyPolicy": "politica sulla riservatezza",
|
||||
@@ -2783,18 +2860,6 @@
|
||||
"termsSection6Title": "6. Sorgente aperta",
|
||||
"termsSection6Text": "OmniRoute è un software open source. Sei libero di esaminarlo, modificarlo e distribuirlo secondo i termini della sua licenza."
|
||||
},
|
||||
"endpoints": {
|
||||
"tabProxy": "Endpoint Proxy",
|
||||
"tabApiEndpoints": "Endpoint API",
|
||||
"apiEndpointsTitle": "Endpoint API",
|
||||
"apiEndpointsDescription": "Endpoint API backend che possono essere consumati da altre applicazioni e servizi.",
|
||||
"comingSoon": "In arrivo",
|
||||
"plannedFeatures": "Funzionalità previste",
|
||||
"featureRestApi": "Catalogo endpoint REST API con documentazione interattiva",
|
||||
"featureWebhooks": "Configurazione webhook e sottoscrizioni eventi",
|
||||
"featureSwagger": "Generazione automatica specifiche OpenAPI / Swagger",
|
||||
"featureAuth": "Gestione chiavi API e ambiti OAuth per endpoint"
|
||||
},
|
||||
"agents": {
|
||||
"title": "CLI Agents",
|
||||
"description": "Discover installed CLI agents on your system. Add custom agents for auto-detection.",
|
||||
@@ -2849,57 +2914,6 @@
|
||||
"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"
|
||||
},
|
||||
"templateNames": {
|
||||
"simple-chat": "Simple Chat",
|
||||
"streaming": "Streaming",
|
||||
@@ -2949,67 +2963,67 @@
|
||||
"refresh": "Refresh",
|
||||
"clearAll": "Clear All",
|
||||
"memoryEntries": "Memory Entries",
|
||||
"memoryEntriesSub": "In-memory LRU",
|
||||
"dbEntries": "DB Entries",
|
||||
"dbEntriesSub": "Persisted (SQLite)",
|
||||
"cacheHits": "Cache Hits",
|
||||
"cacheHitsSub": "of {total} total",
|
||||
"tokensSaved": "Tokens Saved",
|
||||
"tokensSavedSub": "Estimated from hits",
|
||||
"hitRate": "Hit Rate",
|
||||
"performance": "Cache Performance",
|
||||
"behavior": "Cache Behavior",
|
||||
"idempotency": "Idempotency Layer",
|
||||
"clearSuccess": "Cache cleared. {count} expired entries removed.",
|
||||
"clearError": "Failed to clear cache.",
|
||||
"unavailable": "Cache unavailable",
|
||||
"unavailableDesc": "Could not fetch cache statistics. Make sure the server is running.",
|
||||
"memoryEntriesSub": "In-memory LRU",
|
||||
"dbEntriesSub": "Persisted (SQLite)",
|
||||
"cacheHitsSub": "of {total} total",
|
||||
"tokensSavedSub": "Estimated from hits",
|
||||
"autoRefresh": "Auto-refreshes every {seconds}s",
|
||||
"hits": "Hits",
|
||||
"misses": "Misses",
|
||||
"total": "Total",
|
||||
"behavior": "Cache Behavior",
|
||||
"behaviorDeterministic": "Only non-streaming requests with temperature=0 are cached.",
|
||||
"behaviorBypass": "Bypass with header {header}.",
|
||||
"behaviorTwoTier": "Two-tier storage: in-memory LRU (fast) + SQLite (persistent across restarts).",
|
||||
"behaviorTtl": "Default TTL: 30 minutes. Configure via {envVar}.",
|
||||
"idempotency": "Idempotency Layer",
|
||||
"activeDedupKeys": "Active Dedup Keys",
|
||||
"dedupWindow": "Dedup Window",
|
||||
"inputTokens": "Input Tokens",
|
||||
"clearSuccess": "Cache cleared. {count} expired entries removed.",
|
||||
"clearError": "Failed to clear cache.",
|
||||
"unavailable": "Cache unavailable",
|
||||
"unavailableDesc": "Could not fetch cache statistics. Make sure the server is running.",
|
||||
"promptCache": "Prompt Cache (Provider-Side)",
|
||||
"cachedRequests": "Cached Requests",
|
||||
"cacheHitRate": "Cache Hit Rate",
|
||||
"cachedTokens": "Cached Tokens",
|
||||
"cacheCreationTokens": "Cache Creation Tokens",
|
||||
"cacheMetrics": "Prompt Cache Metrics",
|
||||
"withCacheControl": "With Cache Control",
|
||||
"cachedTokensRead": "Cached Tokens (Read)",
|
||||
"cacheCreationWrite": "Cache Creation (Write)",
|
||||
"cacheReuseRatio": "Cache Reuse Ratio",
|
||||
"cacheReuseRatioDesc": "Cached tokens / Total input tokens",
|
||||
"estCostSaved": "Est. Cost Saved",
|
||||
"requestsShort": "reqs",
|
||||
"inputShort": "In",
|
||||
"resetting": "Resetting...",
|
||||
"cachedTokensCol": "Cached",
|
||||
"search": "Search",
|
||||
"cacheReuseRatioDesc": "Cached tokens / Total input tokens",
|
||||
"resetMetrics": "Reset Metrics",
|
||||
"loading": "Loading...",
|
||||
"cachedRequests": "Cached Requests",
|
||||
"model": "Model",
|
||||
"cached": "Cached",
|
||||
"actions": "Actions",
|
||||
"trend24h": "Cache Trend (24h)",
|
||||
"cachedShort": "Cached",
|
||||
"cacheCreation": "Creation",
|
||||
"byProvider": "Breakdown by Provider",
|
||||
"created": "Created",
|
||||
"cacheCreationTokens": "Cache Creation Tokens",
|
||||
"withCacheControl": "With Cache Control",
|
||||
"searchEntries": "Search entries...",
|
||||
"cacheReuseRatio": "Cache Reuse Ratio",
|
||||
"estCostSaved": "Est. Cost Saved",
|
||||
"cachedTokens": "Cached Tokens",
|
||||
"requests": "Requests",
|
||||
"signature": "Signature",
|
||||
"cacheCreationWrite": "Cache Creation (Write)",
|
||||
"expires": "Expires",
|
||||
"writeShort": "Write",
|
||||
"cacheHitRate": "Cache Hit Rate",
|
||||
"cacheMetrics": "Prompt Cache Metrics",
|
||||
"overview": "Overview",
|
||||
"promptCache": "Prompt Cache (Provider-Side)",
|
||||
"resetting": "Resetting...",
|
||||
"resetMetrics": "Reset Metrics",
|
||||
"byProvider": "Breakdown by Provider",
|
||||
"provider": "Provider",
|
||||
"cachedTokensRead": "Cached Tokens (Read)",
|
||||
"requests": "Requests",
|
||||
"inputTokens": "Input Tokens",
|
||||
"cachedTokensCol": "Cached",
|
||||
"cacheCreation": "Creation",
|
||||
"trend24h": "Cache Trend (24h)",
|
||||
"cached": "Cached",
|
||||
"overview": "Overview",
|
||||
"entries": "Entries",
|
||||
"noEntries": "No cache entries found"
|
||||
"searchEntries": "Search entries...",
|
||||
"search": "Search",
|
||||
"loading": "Loading...",
|
||||
"noEntries": "No cache entries found",
|
||||
"signature": "Signature",
|
||||
"model": "Model",
|
||||
"created": "Created",
|
||||
"expires": "Expires",
|
||||
"actions": "Actions"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user