diff --git a/.playwright-mcp/console-2026-04-04T08-03-37-016Z.log b/.playwright-mcp/console-2026-04-04T08-03-37-016Z.log new file mode 100644 index 0000000000..1fcd29f636 --- /dev/null +++ b/.playwright-mcp/console-2026-04-04T08-03-37-016Z.log @@ -0,0 +1 @@ +[ 32698ms] [ERROR] Failed to load resource: the server responded with a status of 404 (Not Found) @ http://localhost:20130/dashboard/usage?_rsc=18t7j:0 diff --git a/.playwright-mcp/page-2026-04-04T08-03-38-145Z.yml b/.playwright-mcp/page-2026-04-04T08-03-38-145Z.yml new file mode 100644 index 0000000000..dc09664b78 --- /dev/null +++ b/.playwright-mcp/page-2026-04-04T08-03-38-145Z.yml @@ -0,0 +1,158 @@ +- generic [active] [ref=e1]: + - link "Skip to content" [ref=e2] [cursor=pointer]: + - /url: "#main-content" + - generic [ref=e3]: + - complementary [ref=e5]: + - link "Skip to content" [ref=e6] [cursor=pointer]: + - /url: "#main-content" + - link "OmniRoute v3.5.0" [ref=e12] [cursor=pointer]: + - /url: /dashboard + - img [ref=e14] + - generic [ref=e26]: + - heading "OmniRoute" [level=1] [ref=e27] + - generic [ref=e28]: v3.5.0 + - navigation "Main navigation" [ref=e29]: + - generic [ref=e30]: + - link "home Home" [ref=e31] [cursor=pointer]: + - /url: /dashboard + - generic [ref=e32]: home + - generic [ref=e33]: Home + - link "api Endpoints" [ref=e34] [cursor=pointer]: + - /url: /dashboard/endpoint + - generic [ref=e35]: api + - generic [ref=e36]: Endpoints + - link "vpn_key API Manager" [ref=e37] [cursor=pointer]: + - /url: /dashboard/api-manager + - generic [ref=e38]: vpn_key + - generic [ref=e39]: API Manager + - link "dns Providers" [ref=e40] [cursor=pointer]: + - /url: /dashboard/providers + - generic [ref=e41]: dns + - generic [ref=e42]: Providers + - link "layers Combos" [ref=e43] [cursor=pointer]: + - /url: /dashboard/combos + - generic [ref=e44]: layers + - generic [ref=e45]: Combos + - link "auto_awesome Auto Combo" [ref=e46] [cursor=pointer]: + - /url: /dashboard/auto-combo + - generic [ref=e47]: auto_awesome + - generic [ref=e48]: Auto Combo + - link "account_balance_wallet Costs" [ref=e49] [cursor=pointer]: + - /url: /dashboard/costs + - generic [ref=e50]: account_balance_wallet + - generic [ref=e51]: Costs + - link "analytics Analytics" [ref=e52] [cursor=pointer]: + - /url: /dashboard/analytics + - generic [ref=e53]: analytics + - generic [ref=e54]: Analytics + - link "tune Limits & Quotas" [ref=e55] [cursor=pointer]: + - /url: /dashboard/limits + - generic [ref=e56]: tune + - generic [ref=e57]: Limits & Quotas + - link "cached Cache" [ref=e58] [cursor=pointer]: + - /url: /dashboard/cache + - generic [ref=e59]: cached + - generic [ref=e60]: Cache + - link "perm_media Media" [ref=e61] [cursor=pointer]: + - /url: /dashboard/cache/media + - generic [ref=e62]: perm_media + - generic [ref=e63]: Media + - generic [ref=e64]: + - paragraph [ref=e65]: CLI + - link "terminal Tools" [ref=e66] [cursor=pointer]: + - /url: /dashboard/cli-tools + - generic [ref=e67]: terminal + - generic [ref=e68]: Tools + - link "smart_toy Agents" [ref=e69] [cursor=pointer]: + - /url: /dashboard/agents + - generic [ref=e70]: smart_toy + - generic [ref=e71]: Agents + - link "psychology Memory" [ref=e72] [cursor=pointer]: + - /url: /dashboard/memory + - generic [ref=e73]: psychology + - generic [ref=e74]: Memory + - link "auto_fix_high Skills" [ref=e75] [cursor=pointer]: + - /url: /dashboard/skills + - generic [ref=e76]: auto_fix_high + - generic [ref=e77]: Skills + - generic [ref=e78]: + - paragraph [ref=e79]: System + - link "health_and_safety Health" [ref=e80] [cursor=pointer]: + - /url: /dashboard/health + - generic [ref=e81]: health_and_safety + - generic [ref=e82]: Health + - link "description Logs" [ref=e83] [cursor=pointer]: + - /url: /dashboard/logs + - generic [ref=e84]: description + - generic [ref=e85]: Logs + - link "history Audit Log" [ref=e86] [cursor=pointer]: + - /url: /dashboard/audit + - generic [ref=e87]: history + - generic [ref=e88]: Audit Log + - link "settings Settings" [ref=e89] [cursor=pointer]: + - /url: /dashboard/settings + - generic [ref=e90]: settings + - generic [ref=e91]: Settings + - generic [ref=e92]: + - paragraph [ref=e93]: Help + - link "menu_book Docs" [ref=e94] [cursor=pointer]: + - /url: /docs + - generic [ref=e95]: menu_book + - generic [ref=e96]: Docs + - link "bug_report Issues" [ref=e97] [cursor=pointer]: + - /url: https://github.com/diegosouzapw/OmniRoute/issues + - generic [ref=e98]: bug_report + - generic [ref=e99]: Issues + - generic [ref=e100]: + - button "restart_alt Restart" [ref=e101]: + - generic: restart_alt + - text: Restart + - button "power_settings_new Shutdown" [ref=e102]: + - generic: power_settings_new + - text: Shutdown + - main [ref=e103]: + - generic [ref=e104]: + - button "menu" [ref=e106]: + - generic: menu + - generic [ref=e107]: + - button "🇺🇸 EN expand_more" [ref=e109]: + - generic [ref=e110]: 🇺🇸 + - generic [ref=e111]: EN + - generic: expand_more + - button "Switch to dark mode" [ref=e112]: + - generic: dark_mode + - button "logout" [ref=e113]: + - generic: logout + - generic [ref=e115]: + - navigation "Breadcrumb" [ref=e116]: + - link "Dashboard" [ref=e118] [cursor=pointer]: + - /url: /dashboard + - generic [ref=e119]: + - generic [ref=e120]: › + - generic [ref=e121]: Onboarding + - generic [ref=e123]: + - generic [ref=e124]: + - generic [ref=e126]: "1" + - generic [ref=e129]: "2" + - generic [ref=e132]: "3" + - generic [ref=e135]: "4" + - generic [ref=e138]: "5" + - generic [ref=e139]: + - generic [ref=e140]: + - generic [ref=e141]: waving_hand + - heading "Welcome" [level=2] [ref=e142] + - generic [ref=e144]: + - paragraph [ref=e145]: OmniRoute is your local AI API proxy. It routes requests to multiple AI providers with load balancing, failover, and usage tracking. + - generic [ref=e146]: + - generic [ref=e147]: + - generic [ref=e148]: swap_horiz + - text: Multi-Provider + - generic [ref=e149]: + - generic [ref=e150]: monitoring + - text: Usage Tracking + - generic [ref=e151]: + - generic [ref=e152]: shield + - text: API Key Mgmt + - button "Get Started" [ref=e155] [cursor=pointer] + - button "Skip wizard entirely" [ref=e157] [cursor=pointer] + - alert [ref=e158] diff --git a/.playwright-mcp/page-2026-04-04T08-04-15-839Z.yml b/.playwright-mcp/page-2026-04-04T08-04-15-839Z.yml new file mode 100644 index 0000000000..88cb680c33 --- /dev/null +++ b/.playwright-mcp/page-2026-04-04T08-04-15-839Z.yml @@ -0,0 +1,1011 @@ +- generic [active] [ref=e1]: + - link "Skip to content" [ref=e2] [cursor=pointer]: + - /url: "#main-content" + - generic [ref=e3]: + - complementary [ref=e5]: + - link "Skip to content" [ref=e6] [cursor=pointer]: + - /url: "#main-content" + - link "OmniRoute v3.5.0" [ref=e12] [cursor=pointer]: + - /url: /dashboard + - img [ref=e14] + - generic [ref=e26]: + - heading "OmniRoute" [level=1] [ref=e27] + - generic [ref=e28]: v3.5.0 + - navigation "Main navigation" [ref=e29]: + - generic [ref=e30]: + - link "home Home" [ref=e31] [cursor=pointer]: + - /url: /dashboard + - generic [ref=e32]: home + - generic [ref=e33]: Home + - link "api Endpoints" [ref=e34] [cursor=pointer]: + - /url: /dashboard/endpoint + - generic [ref=e35]: api + - generic [ref=e36]: Endpoints + - link "vpn_key API Manager" [ref=e37] [cursor=pointer]: + - /url: /dashboard/api-manager + - generic [ref=e38]: vpn_key + - generic [ref=e39]: API Manager + - link "dns Providers" [ref=e40] [cursor=pointer]: + - /url: /dashboard/providers + - generic [ref=e41]: dns + - generic [ref=e42]: Providers + - link "layers Combos" [ref=e43] [cursor=pointer]: + - /url: /dashboard/combos + - generic [ref=e44]: layers + - generic [ref=e45]: Combos + - link "auto_awesome Auto Combo" [ref=e46] [cursor=pointer]: + - /url: /dashboard/auto-combo + - generic [ref=e47]: auto_awesome + - generic [ref=e48]: Auto Combo + - link "account_balance_wallet Costs" [ref=e49] [cursor=pointer]: + - /url: /dashboard/costs + - generic [ref=e50]: account_balance_wallet + - generic [ref=e51]: Costs + - link "analytics Analytics" [ref=e52] [cursor=pointer]: + - /url: /dashboard/analytics + - generic [ref=e53]: analytics + - generic [ref=e54]: Analytics + - link "tune Limits & Quotas" [ref=e55] [cursor=pointer]: + - /url: /dashboard/limits + - generic [ref=e56]: tune + - generic [ref=e57]: Limits & Quotas + - link "cached Cache" [ref=e58] [cursor=pointer]: + - /url: /dashboard/cache + - generic [ref=e59]: cached + - generic [ref=e60]: Cache + - link "perm_media Media" [ref=e61] [cursor=pointer]: + - /url: /dashboard/cache/media + - generic [ref=e62]: perm_media + - generic [ref=e63]: Media + - generic [ref=e64]: + - paragraph [ref=e65]: CLI + - link "terminal Tools" [ref=e66] [cursor=pointer]: + - /url: /dashboard/cli-tools + - generic [ref=e67]: terminal + - generic [ref=e68]: Tools + - link "smart_toy Agents" [ref=e69] [cursor=pointer]: + - /url: /dashboard/agents + - generic [ref=e70]: smart_toy + - generic [ref=e71]: Agents + - link "psychology Memory" [ref=e72] [cursor=pointer]: + - /url: /dashboard/memory + - generic [ref=e73]: psychology + - generic [ref=e74]: Memory + - link "auto_fix_high Skills" [ref=e75] [cursor=pointer]: + - /url: /dashboard/skills + - generic [ref=e76]: auto_fix_high + - generic [ref=e77]: Skills + - generic [ref=e78]: + - paragraph [ref=e79]: System + - link "health_and_safety Health" [ref=e80] [cursor=pointer]: + - /url: /dashboard/health + - generic [ref=e81]: health_and_safety + - generic [ref=e82]: Health + - link "description Logs" [ref=e83] [cursor=pointer]: + - /url: /dashboard/logs + - generic [ref=e84]: description + - generic [ref=e85]: Logs + - link "history Audit Log" [ref=e86] [cursor=pointer]: + - /url: /dashboard/audit + - generic [ref=e87]: history + - generic [ref=e88]: Audit Log + - link "settings Settings" [ref=e89] [cursor=pointer]: + - /url: /dashboard/settings + - generic [ref=e90]: settings + - generic [ref=e91]: Settings + - generic [ref=e92]: + - paragraph [ref=e93]: Help + - link "menu_book Docs" [ref=e94] [cursor=pointer]: + - /url: /docs + - generic [ref=e95]: menu_book + - generic [ref=e96]: Docs + - link "bug_report Issues" [ref=e97] [cursor=pointer]: + - /url: https://github.com/diegosouzapw/OmniRoute/issues + - generic [ref=e98]: bug_report + - generic [ref=e99]: Issues + - generic [ref=e100]: + - button "restart_alt Restart" [ref=e101]: + - generic: restart_alt + - text: Restart + - button "power_settings_new Shutdown" [ref=e102]: + - generic: power_settings_new + - text: Shutdown + - main [ref=e103]: + - generic [ref=e104]: + - button "menu" [ref=e106]: + - generic: menu + - generic [ref=e107]: + - button "🇺🇸 EN expand_more" [ref=e109]: + - generic [ref=e110]: 🇺🇸 + - generic [ref=e111]: EN + - generic: expand_more + - button "Switch to dark mode" [ref=e112]: + - generic: dark_mode + - button "logout" [ref=e113]: + - generic: logout + - generic [ref=e159]: + - generic [ref=e161]: + - generic [ref=e162]: + - generic [ref=e163]: + - heading "Quick Start" [level=2] [ref=e164] + - paragraph [ref=e165]: Get up and running in 4 steps. Connect providers, route models, monitor everything. + - link "menu_book Full Docs" [ref=e166] [cursor=pointer]: + - /url: /docs + - generic [ref=e167]: menu_book + - text: Full Docs + - list [ref=e168]: + - listitem [ref=e169]: + - generic [ref=e171]: key + - generic [ref=e172]: + - text: 1. Create API key + - paragraph [ref=e173]: + - text: Go to + - link "Endpoint" [ref=e174] [cursor=pointer]: + - /url: /dashboard/endpoint + - text: "-> Registered Keys. Generate one key per environment." + - listitem [ref=e175]: + - generic [ref=e177]: dns + - generic [ref=e178]: + - text: 2. Connect providers + - paragraph [ref=e179]: + - text: Add accounts in + - link "Providers" [ref=e180] [cursor=pointer]: + - /url: /dashboard/providers + - text: . Supports OAuth, API Key, and free tiers. + - listitem [ref=e181]: + - generic [ref=e183]: link + - generic [ref=e184]: + - text: 3. Point your client + - paragraph [ref=e185]: Set base URL to http://localhost:20130/v1 in your IDE or API client. + - listitem [ref=e186]: + - generic [ref=e188]: analytics + - generic [ref=e189]: + - text: 4. Monitor & optimize + - paragraph [ref=e190]: + - text: Track tokens, cost and errors in + - link "Request Logs" [ref=e191] [cursor=pointer]: + - /url: /dashboard/usage + - text: and + - link "Analytics" [ref=e192] [cursor=pointer]: + - /url: /dashboard/analytics + - text: . + - generic [ref=e193]: + - link "menu_book Documentation" [ref=e194] [cursor=pointer]: + - /url: /docs + - generic [ref=e195]: menu_book + - text: Documentation + - link "dns Providers" [ref=e196] [cursor=pointer]: + - /url: /dashboard/providers + - generic [ref=e197]: dns + - text: Providers + - link "layers Combos" [ref=e198] [cursor=pointer]: + - /url: /dashboard/combos + - generic [ref=e199]: layers + - text: Combos + - link "analytics Analytics" [ref=e200] [cursor=pointer]: + - /url: /dashboard/analytics + - generic [ref=e201]: analytics + - text: Analytics + - link "health_and_safety Health Monitor" [ref=e202] [cursor=pointer]: + - /url: /dashboard/health + - generic [ref=e203]: health_and_safety + - text: Health Monitor + - link "terminal CLI Tools" [ref=e204] [cursor=pointer]: + - /url: /dashboard/cli-tools + - generic [ref=e205]: terminal + - text: CLI Tools + - link "bug_report Report issue" [ref=e206] [cursor=pointer]: + - /url: https://github.com/diegosouzapw/OmniRoute/issues + - generic [ref=e207]: bug_report + - text: Report issue + - generic [ref=e208]: + - generic [ref=e209]: + - generic [ref=e210]: + - heading "Providers Overview" [level=2] [ref=e211] + - paragraph [ref=e212]: 0 configured of 72 available providers + - generic [ref=e213]: + - generic [ref=e214]: + - generic [ref=e215]: Free + - generic [ref=e217]: OAuth + - generic [ref=e219]: API Key + - link "settings Manage" [ref=e221] [cursor=pointer]: + - /url: /dashboard/providers + - generic [ref=e222]: settings + - text: Manage + - generic [ref=e223]: + - button "Qoder AI Free Not configured 0 models" [ref=e224] [cursor=pointer]: + - generic [ref=e225]: + - img [ref=e228] + - generic [ref=e231]: + - generic [ref=e232]: + - paragraph [ref=e233]: Qoder AI + - generic "Free" [ref=e234] + - paragraph [ref=e235]: Not configured + - generic [ref=e236]: + - paragraph [ref=e237]: "0" + - paragraph [ref=e238]: models + - button "AlibabaCloud Qwen Code Free Not configured 0 models" [ref=e239] [cursor=pointer]: + - generic [ref=e240]: + - img "AlibabaCloud" [ref=e243] + - generic [ref=e246]: + - generic [ref=e247]: + - paragraph [ref=e248]: Qwen Code + - generic "Free" [ref=e249] + - paragraph [ref=e250]: Not configured + - generic [ref=e251]: + - paragraph [ref=e252]: "0" + - paragraph [ref=e253]: models + - button "gemini-cli Gemini CLI Free Not configured 0 models" [ref=e254] [cursor=pointer]: + - generic [ref=e255]: + - img "gemini-cli" [ref=e258] + - generic [ref=e259]: + - generic [ref=e260]: + - paragraph [ref=e261]: Gemini CLI + - generic "Free" [ref=e262] + - paragraph [ref=e263]: Not configured + - generic [ref=e264]: + - paragraph [ref=e265]: "0" + - paragraph [ref=e266]: models + - button "kiro Kiro AI Free Not configured 0 models" [ref=e267] [cursor=pointer]: + - generic [ref=e268]: + - img "kiro" [ref=e271] + - generic [ref=e272]: + - generic [ref=e273]: + - paragraph [ref=e274]: Kiro AI + - generic "Free" [ref=e275] + - paragraph [ref=e276]: Not configured + - generic [ref=e277]: + - paragraph [ref=e278]: "0" + - paragraph [ref=e279]: models + - button "Anthropic Claude Code OAuth Not configured 0 models" [ref=e280] [cursor=pointer]: + - generic [ref=e281]: + - img "Anthropic" [ref=e284] + - generic [ref=e286]: + - generic [ref=e287]: + - paragraph [ref=e288]: Claude Code + - generic "OAuth" [ref=e289] + - paragraph [ref=e290]: Not configured + - generic [ref=e291]: + - paragraph [ref=e292]: "0" + - paragraph [ref=e293]: models + - button "antigravity Antigravity OAuth Not configured 0 models" [ref=e294] [cursor=pointer]: + - generic [ref=e295]: + - img "antigravity" [ref=e298] + - generic [ref=e299]: + - generic [ref=e300]: + - paragraph [ref=e301]: Antigravity + - generic "OAuth" [ref=e302] + - paragraph [ref=e303]: Not configured + - generic [ref=e304]: + - paragraph [ref=e305]: "0" + - paragraph [ref=e306]: models + - button "OpenAI OpenAI Codex OAuth Not configured 0 models" [ref=e307] [cursor=pointer]: + - generic [ref=e308]: + - img "OpenAI" [ref=e311] + - generic [ref=e313]: + - generic [ref=e314]: + - paragraph [ref=e315]: OpenAI Codex + - generic "OAuth" [ref=e316] + - paragraph [ref=e317]: Not configured + - generic [ref=e318]: + - paragraph [ref=e319]: "0" + - paragraph [ref=e320]: models + - button "github GitHub Copilot OAuth Not configured 0 models" [ref=e321] [cursor=pointer]: + - generic [ref=e322]: + - img "github" [ref=e325] + - generic [ref=e326]: + - generic [ref=e327]: + - paragraph [ref=e328]: GitHub Copilot + - generic "OAuth" [ref=e329] + - paragraph [ref=e330]: Not configured + - generic [ref=e331]: + - paragraph [ref=e332]: "0" + - paragraph [ref=e333]: models + - button "cursor Cursor IDE OAuth Not configured 0 models" [ref=e334] [cursor=pointer]: + - generic [ref=e335]: + - img "cursor" [ref=e338] + - generic [ref=e339]: + - generic [ref=e340]: + - paragraph [ref=e341]: Cursor IDE + - generic "OAuth" [ref=e342] + - paragraph [ref=e343]: Not configured + - generic [ref=e344]: + - paragraph [ref=e345]: "0" + - paragraph [ref=e346]: models + - button "kimi-coding Kimi Coding OAuth Not configured 0 models" [ref=e347] [cursor=pointer]: + - generic [ref=e348]: + - img "kimi-coding" [ref=e351] + - generic [ref=e352]: + - generic [ref=e353]: + - paragraph [ref=e354]: Kimi Coding + - generic "OAuth" [ref=e355] + - paragraph [ref=e356]: Not configured + - generic [ref=e357]: + - paragraph [ref=e358]: "0" + - paragraph [ref=e359]: models + - button "kilocode Kilo Code OAuth Not configured 0 models" [ref=e360] [cursor=pointer]: + - generic [ref=e361]: + - img "kilocode" [ref=e364] + - generic [ref=e365]: + - generic [ref=e366]: + - paragraph [ref=e367]: Kilo Code + - generic "OAuth" [ref=e368] + - paragraph [ref=e369]: Not configured + - generic [ref=e370]: + - paragraph [ref=e371]: "0" + - paragraph [ref=e372]: models + - button "cline Cline OAuth Not configured 0 models" [ref=e373] [cursor=pointer]: + - generic [ref=e374]: + - img "cline" [ref=e377] + - generic [ref=e378]: + - generic [ref=e379]: + - paragraph [ref=e380]: Cline + - generic "OAuth" [ref=e381] + - paragraph [ref=e382]: Not configured + - generic [ref=e383]: + - paragraph [ref=e384]: "0" + - paragraph [ref=e385]: models + - button "OpenRouter OpenRouter API Key Not configured 0 models" [ref=e386] [cursor=pointer]: + - generic [ref=e387]: + - img "OpenRouter" [ref=e390] + - generic [ref=e392]: + - generic [ref=e393]: + - paragraph [ref=e394]: OpenRouter + - generic "API Key" [ref=e395] + - paragraph [ref=e396]: Not configured + - generic [ref=e397]: + - paragraph [ref=e398]: "0" + - paragraph [ref=e399]: models + - button "glm GLM Coding API Key Not configured 0 models" [ref=e400] [cursor=pointer]: + - generic [ref=e401]: + - img "glm" [ref=e404] + - generic [ref=e405]: + - generic [ref=e406]: + - paragraph [ref=e407]: GLM Coding + - generic "API Key" [ref=e408] + - paragraph [ref=e409]: Not configured + - generic [ref=e410]: + - paragraph [ref=e411]: "0" + - paragraph [ref=e412]: models + - button "bailian-coding-plan Alibaba Coding Plan API Key Not configured 0 models" [ref=e413] [cursor=pointer]: + - generic [ref=e414]: + - img "bailian-coding-plan" [ref=e417] + - generic [ref=e418]: + - generic [ref=e419]: + - paragraph [ref=e420]: Alibaba Coding Plan + - generic "API Key" [ref=e421] + - paragraph [ref=e422]: Not configured + - generic [ref=e423]: + - paragraph [ref=e424]: "0" + - paragraph [ref=e425]: models + - button "MoonshotAI Kimi API Key Not configured 0 models" [ref=e426] [cursor=pointer]: + - generic [ref=e427]: + - img "MoonshotAI" [ref=e430] + - generic [ref=e432]: + - generic [ref=e433]: + - paragraph [ref=e434]: Kimi + - generic "API Key" [ref=e435] + - paragraph [ref=e436]: Not configured + - generic [ref=e437]: + - paragraph [ref=e438]: "0" + - paragraph [ref=e439]: models + - button "kimi-coding-apikey Kimi Coding (API Key) API Key Not configured 0 models" [ref=e440] [cursor=pointer]: + - generic [ref=e441]: + - img "kimi-coding-apikey" [ref=e444] + - generic [ref=e445]: + - generic [ref=e446]: + - paragraph [ref=e447]: Kimi Coding (API Key) + - generic "API Key" [ref=e448] + - paragraph [ref=e449]: Not configured + - generic [ref=e450]: + - paragraph [ref=e451]: "0" + - paragraph [ref=e452]: models + - button "Minimax Minimax Coding API Key Not configured 0 models" [ref=e453] [cursor=pointer]: + - generic [ref=e454]: + - img "Minimax" [ref=e457] + - generic [ref=e459]: + - generic [ref=e460]: + - paragraph [ref=e461]: Minimax Coding + - generic "API Key" [ref=e462] + - paragraph [ref=e463]: Not configured + - generic [ref=e464]: + - paragraph [ref=e465]: "0" + - paragraph [ref=e466]: models + - button "minimax-cn Minimax (China) API Key Not configured 0 models" [ref=e467] [cursor=pointer]: + - generic [ref=e468]: + - img "minimax-cn" [ref=e471] + - generic [ref=e472]: + - generic [ref=e473]: + - paragraph [ref=e474]: Minimax (China) + - generic "API Key" [ref=e475] + - paragraph [ref=e476]: Not configured + - generic [ref=e477]: + - paragraph [ref=e478]: "0" + - paragraph [ref=e479]: models + - button "alicode Alibaba API Key Not configured 0 models" [ref=e480] [cursor=pointer]: + - generic [ref=e481]: + - img "alicode" [ref=e484] + - generic [ref=e485]: + - generic [ref=e486]: + - paragraph [ref=e487]: Alibaba + - generic "API Key" [ref=e488] + - paragraph [ref=e489]: Not configured + - generic [ref=e490]: + - paragraph [ref=e491]: "0" + - paragraph [ref=e492]: models + - button "alicode-intl Alibaba Intl API Key Not configured 0 models" [ref=e493] [cursor=pointer]: + - generic [ref=e494]: + - img "alicode-intl" [ref=e497] + - generic [ref=e498]: + - generic [ref=e499]: + - paragraph [ref=e500]: Alibaba Intl + - generic "API Key" [ref=e501] + - paragraph [ref=e502]: Not configured + - generic [ref=e503]: + - paragraph [ref=e504]: "0" + - paragraph [ref=e505]: models + - button "OpenAI OpenAI API Key Not configured 0 models" [ref=e506] [cursor=pointer]: + - generic [ref=e507]: + - img "OpenAI" [ref=e510] + - generic [ref=e512]: + - generic [ref=e513]: + - paragraph [ref=e514]: OpenAI + - generic "API Key" [ref=e515] + - paragraph [ref=e516]: Not configured + - generic [ref=e517]: + - paragraph [ref=e518]: "0" + - paragraph [ref=e519]: models + - button "Anthropic Anthropic API Key Not configured 0 models" [ref=e520] [cursor=pointer]: + - generic [ref=e521]: + - img "Anthropic" [ref=e524] + - generic [ref=e526]: + - generic [ref=e527]: + - paragraph [ref=e528]: Anthropic + - generic "API Key" [ref=e529] + - paragraph [ref=e530]: Not configured + - generic [ref=e531]: + - paragraph [ref=e532]: "0" + - paragraph [ref=e533]: models + - button "Google Gemini (Google AI Studio) API Key Not configured 0 models" [ref=e534] [cursor=pointer]: + - generic [ref=e535]: + - img "Google" [ref=e538] + - generic [ref=e543]: + - generic [ref=e544]: + - paragraph [ref=e545]: Gemini (Google AI Studio) + - generic "API Key" [ref=e546] + - paragraph [ref=e547]: Not configured + - generic [ref=e548]: + - paragraph [ref=e549]: "0" + - paragraph [ref=e550]: models + - button "DeepSeek DeepSeek API Key Not configured 0 models" [ref=e551] [cursor=pointer]: + - generic [ref=e552]: + - img "DeepSeek" [ref=e555] + - generic [ref=e557]: + - generic [ref=e558]: + - paragraph [ref=e559]: DeepSeek + - generic "API Key" [ref=e560] + - paragraph [ref=e561]: Not configured + - generic [ref=e562]: + - paragraph [ref=e563]: "0" + - paragraph [ref=e564]: models + - button "Groq Groq API Key Not configured 0 models" [ref=e565] [cursor=pointer]: + - generic [ref=e566]: + - img "Groq" [ref=e569] + - generic [ref=e571]: + - generic [ref=e572]: + - paragraph [ref=e573]: Groq + - generic "API Key" [ref=e574] + - paragraph [ref=e575]: Not configured + - generic [ref=e576]: + - paragraph [ref=e577]: "0" + - paragraph [ref=e578]: models + - button "Blackbox AI API Key Not configured 0 models" [ref=e579] [cursor=pointer]: + - generic [ref=e580]: + - img [ref=e583]: + - img [ref=e584] + - generic [ref=e589]: + - generic [ref=e590]: + - paragraph [ref=e591]: Blackbox AI + - generic "API Key" [ref=e592] + - paragraph [ref=e593]: Not configured + - generic [ref=e594]: + - paragraph [ref=e595]: "0" + - paragraph [ref=e596]: models + - button "Grok xAI (Grok) API Key Not configured 0 models" [ref=e597] [cursor=pointer]: + - generic [ref=e598]: + - img "Grok" [ref=e601] + - generic [ref=e603]: + - generic [ref=e604]: + - paragraph [ref=e605]: xAI (Grok) + - generic "API Key" [ref=e606] + - paragraph [ref=e607]: Not configured + - generic [ref=e608]: + - paragraph [ref=e609]: "0" + - paragraph [ref=e610]: models + - button "Mistral Mistral API Key Not configured 0 models" [ref=e611] [cursor=pointer]: + - generic [ref=e612]: + - img "Mistral" [ref=e615] + - generic [ref=e621]: + - generic [ref=e622]: + - paragraph [ref=e623]: Mistral + - generic "API Key" [ref=e624] + - paragraph [ref=e625]: Not configured + - generic [ref=e626]: + - paragraph [ref=e627]: "0" + - paragraph [ref=e628]: models + - button "Perplexity Perplexity API Key Not configured 0 models" [ref=e629] [cursor=pointer]: + - generic [ref=e630]: + - img "Perplexity" [ref=e633] + - generic [ref=e635]: + - generic [ref=e636]: + - paragraph [ref=e637]: Perplexity + - generic "API Key" [ref=e638] + - paragraph [ref=e639]: Not configured + - generic [ref=e640]: + - paragraph [ref=e641]: "0" + - paragraph [ref=e642]: models + - button "together.ai Together AI API Key Not configured 0 models" [ref=e643] [cursor=pointer]: + - generic [ref=e644]: + - img "together.ai" [ref=e647] + - generic [ref=e651]: + - generic [ref=e652]: + - paragraph [ref=e653]: Together AI + - generic "API Key" [ref=e654] + - paragraph [ref=e655]: Not configured + - generic [ref=e656]: + - paragraph [ref=e657]: "0" + - paragraph [ref=e658]: models + - button "Fireworks AI API Key Not configured 0 models" [ref=e659] [cursor=pointer]: + - generic [ref=e660]: + - img [ref=e663]: + - img [ref=e664] + - generic [ref=e669]: + - generic [ref=e670]: + - paragraph [ref=e671]: Fireworks AI + - generic "API Key" [ref=e672] + - paragraph [ref=e673]: Not configured + - generic [ref=e674]: + - paragraph [ref=e675]: "0" + - paragraph [ref=e676]: models + - button "Cerebras Cerebras API Key Not configured 0 models" [ref=e677] [cursor=pointer]: + - generic [ref=e678]: + - img "Cerebras" [ref=e681] + - generic [ref=e684]: + - generic [ref=e685]: + - paragraph [ref=e686]: Cerebras + - generic "API Key" [ref=e687] + - paragraph [ref=e688]: Not configured + - generic [ref=e689]: + - paragraph [ref=e690]: "0" + - paragraph [ref=e691]: models + - button "Cohere Cohere API Key Not configured 0 models" [ref=e692] [cursor=pointer]: + - generic [ref=e693]: + - img "Cohere" [ref=e696] + - generic [ref=e700]: + - generic [ref=e701]: + - paragraph [ref=e702]: Cohere + - generic "API Key" [ref=e703] + - paragraph [ref=e704]: Not configured + - generic [ref=e705]: + - paragraph [ref=e706]: "0" + - paragraph [ref=e707]: models + - button "Nvidia NVIDIA NIM API Key Not configured 0 models" [ref=e708] [cursor=pointer]: + - generic [ref=e709]: + - img "Nvidia" [ref=e712] + - generic [ref=e714]: + - generic [ref=e715]: + - paragraph [ref=e716]: NVIDIA NIM + - generic "API Key" [ref=e717] + - paragraph [ref=e718]: Not configured + - generic [ref=e719]: + - paragraph [ref=e720]: "0" + - paragraph [ref=e721]: models + - button "nebius Nebius AI API Key Not configured 0 models" [ref=e722] [cursor=pointer]: + - generic [ref=e723]: + - img "nebius" [ref=e726] + - generic [ref=e727]: + - generic [ref=e728]: + - paragraph [ref=e729]: Nebius AI + - generic "API Key" [ref=e730] + - paragraph [ref=e731]: Not configured + - generic [ref=e732]: + - paragraph [ref=e733]: "0" + - paragraph [ref=e734]: models + - button "siliconflow SiliconFlow API Key Not configured 0 models" [ref=e735] [cursor=pointer]: + - generic [ref=e736]: + - img "siliconflow" [ref=e739] + - generic [ref=e740]: + - generic [ref=e741]: + - paragraph [ref=e742]: SiliconFlow + - generic "API Key" [ref=e743] + - paragraph [ref=e744]: Not configured + - generic [ref=e745]: + - paragraph [ref=e746]: "0" + - paragraph [ref=e747]: models + - button "hyperbolic Hyperbolic API Key Not configured 0 models" [ref=e748] [cursor=pointer]: + - generic [ref=e749]: + - img "hyperbolic" [ref=e752] + - generic [ref=e753]: + - generic [ref=e754]: + - paragraph [ref=e755]: Hyperbolic + - generic "API Key" [ref=e756] + - paragraph [ref=e757]: Not configured + - generic [ref=e758]: + - paragraph [ref=e759]: "0" + - paragraph [ref=e760]: models + - button "deepgram Deepgram API Key Not configured 0 models" [ref=e761] [cursor=pointer]: + - generic [ref=e762]: + - img "deepgram" [ref=e765] + - generic [ref=e766]: + - generic [ref=e767]: + - paragraph [ref=e768]: Deepgram + - generic "API Key" [ref=e769] + - paragraph [ref=e770]: Not configured + - generic [ref=e771]: + - paragraph [ref=e772]: "0" + - paragraph [ref=e773]: models + - button "assemblyai AssemblyAI API Key Not configured 0 models" [ref=e774] [cursor=pointer]: + - generic [ref=e775]: + - img "assemblyai" [ref=e778] + - generic [ref=e779]: + - generic [ref=e780]: + - paragraph [ref=e781]: AssemblyAI + - generic "API Key" [ref=e782] + - paragraph [ref=e783]: Not configured + - generic [ref=e784]: + - paragraph [ref=e785]: "0" + - paragraph [ref=e786]: models + - button "nanobanana NanoBanana API Key Not configured 0 models" [ref=e787] [cursor=pointer]: + - generic [ref=e788]: + - img "nanobanana" [ref=e791] + - generic [ref=e792]: + - generic [ref=e793]: + - paragraph [ref=e794]: NanoBanana + - generic "API Key" [ref=e795] + - paragraph [ref=e796]: Not configured + - generic [ref=e797]: + - paragraph [ref=e798]: "0" + - paragraph [ref=e799]: models + - button "ollama-cloud Ollama Cloud API Key Not configured 0 models" [ref=e800] [cursor=pointer]: + - generic [ref=e801]: + - img "ollama-cloud" [ref=e804] + - generic [ref=e805]: + - generic [ref=e806]: + - paragraph [ref=e807]: Ollama Cloud + - generic "API Key" [ref=e808] + - paragraph [ref=e809]: Not configured + - generic [ref=e810]: + - paragraph [ref=e811]: "0" + - paragraph [ref=e812]: models + - button "elevenlabs ElevenLabs API Key Not configured 0 models" [ref=e813] [cursor=pointer]: + - generic [ref=e814]: + - img "elevenlabs" [ref=e817] + - generic [ref=e818]: + - generic [ref=e819]: + - paragraph [ref=e820]: ElevenLabs + - generic "API Key" [ref=e821] + - paragraph [ref=e822]: Not configured + - generic [ref=e823]: + - paragraph [ref=e824]: "0" + - paragraph [ref=e825]: models + - button "cartesia Cartesia API Key Not configured 0 models" [ref=e826] [cursor=pointer]: + - generic [ref=e827]: + - img "cartesia" [ref=e830] + - generic [ref=e831]: + - generic [ref=e832]: + - paragraph [ref=e833]: Cartesia + - generic "API Key" [ref=e834] + - paragraph [ref=e835]: Not configured + - generic [ref=e836]: + - paragraph [ref=e837]: "0" + - paragraph [ref=e838]: models + - button "playht PlayHT API Key Not configured 0 models" [ref=e839] [cursor=pointer]: + - generic [ref=e840]: + - img "playht" [ref=e843] + - generic [ref=e844]: + - generic [ref=e845]: + - paragraph [ref=e846]: PlayHT + - generic "API Key" [ref=e847] + - paragraph [ref=e848]: Not configured + - generic [ref=e849]: + - paragraph [ref=e850]: "0" + - paragraph [ref=e851]: models + - button "inworld Inworld API Key Not configured 0 models" [ref=e852] [cursor=pointer]: + - generic [ref=e853]: + - img "inworld" [ref=e856] + - generic [ref=e857]: + - generic [ref=e858]: + - paragraph [ref=e859]: Inworld + - generic "API Key" [ref=e860] + - paragraph [ref=e861]: Not configured + - generic [ref=e862]: + - paragraph [ref=e863]: "0" + - paragraph [ref=e864]: models + - button "sdwebui SD WebUI API Key Not configured 0 models" [ref=e865] [cursor=pointer]: + - generic [ref=e866]: + - img "sdwebui" [ref=e869] + - generic [ref=e870]: + - generic [ref=e871]: + - paragraph [ref=e872]: SD WebUI + - generic "API Key" [ref=e873] + - paragraph [ref=e874]: Not configured + - generic [ref=e875]: + - paragraph [ref=e876]: "0" + - paragraph [ref=e877]: models + - button "comfyui ComfyUI API Key Not configured 0 models" [ref=e878] [cursor=pointer]: + - generic [ref=e879]: + - img "comfyui" [ref=e882] + - generic [ref=e883]: + - generic [ref=e884]: + - paragraph [ref=e885]: ComfyUI + - generic "API Key" [ref=e886] + - paragraph [ref=e887]: Not configured + - generic [ref=e888]: + - paragraph [ref=e889]: "0" + - paragraph [ref=e890]: models + - button "HuggingFace HuggingFace API Key Not configured 0 models" [ref=e891] [cursor=pointer]: + - generic [ref=e892]: + - img "HuggingFace" [ref=e895] + - generic [ref=e902]: + - generic [ref=e903]: + - paragraph [ref=e904]: HuggingFace + - generic "API Key" [ref=e905] + - paragraph [ref=e906]: Not configured + - generic [ref=e907]: + - paragraph [ref=e908]: "0" + - paragraph [ref=e909]: models + - button "synthetic Synthetic API Key Not configured 0 models" [ref=e910] [cursor=pointer]: + - generic [ref=e911]: + - img "synthetic" [ref=e914] + - generic [ref=e915]: + - generic [ref=e916]: + - paragraph [ref=e917]: Synthetic + - generic "API Key" [ref=e918] + - paragraph [ref=e919]: Not configured + - generic [ref=e920]: + - paragraph [ref=e921]: "0" + - paragraph [ref=e922]: models + - button "kilo-gateway Kilo Gateway API Key Not configured 0 models" [ref=e923] [cursor=pointer]: + - generic [ref=e924]: + - img "kilo-gateway" [ref=e927] + - generic [ref=e928]: + - generic [ref=e929]: + - paragraph [ref=e930]: Kilo Gateway + - generic "API Key" [ref=e931] + - paragraph [ref=e932]: Not configured + - generic [ref=e933]: + - paragraph [ref=e934]: "0" + - paragraph [ref=e935]: models + - button "vertex Vertex AI API Key Not configured 0 models" [ref=e936] [cursor=pointer]: + - generic [ref=e937]: + - img "vertex" [ref=e940] + - generic [ref=e941]: + - generic [ref=e942]: + - paragraph [ref=e943]: Vertex AI + - generic "API Key" [ref=e944] + - paragraph [ref=e945]: Not configured + - generic [ref=e946]: + - paragraph [ref=e947]: "0" + - paragraph [ref=e948]: models + - button "zai Z.AI API Key Not configured 0 models" [ref=e949] [cursor=pointer]: + - generic [ref=e950]: + - img "zai" [ref=e953] + - generic [ref=e954]: + - generic [ref=e955]: + - paragraph [ref=e956]: Z.AI + - generic "API Key" [ref=e957] + - paragraph [ref=e958]: Not configured + - generic [ref=e959]: + - paragraph [ref=e960]: "0" + - paragraph [ref=e961]: models + - button "perplexity-search Perplexity Search API Key Not configured 0 models" [ref=e962] [cursor=pointer]: + - generic [ref=e963]: + - img "perplexity-search" [ref=e966] + - generic [ref=e967]: + - generic [ref=e968]: + - paragraph [ref=e969]: Perplexity Search + - generic "API Key" [ref=e970] + - paragraph [ref=e971]: Not configured + - generic [ref=e972]: + - paragraph [ref=e973]: "0" + - paragraph [ref=e974]: models + - button "serper-search Serper Search API Key Not configured 0 models" [ref=e975] [cursor=pointer]: + - generic [ref=e976]: + - img "serper-search" [ref=e979] + - generic [ref=e980]: + - generic [ref=e981]: + - paragraph [ref=e982]: Serper Search + - generic "API Key" [ref=e983] + - paragraph [ref=e984]: Not configured + - generic [ref=e985]: + - paragraph [ref=e986]: "0" + - paragraph [ref=e987]: models + - button "brave-search Brave Search API Key Not configured 0 models" [ref=e988] [cursor=pointer]: + - generic [ref=e989]: + - img "brave-search" [ref=e992] + - generic [ref=e993]: + - generic [ref=e994]: + - paragraph [ref=e995]: Brave Search + - generic "API Key" [ref=e996] + - paragraph [ref=e997]: Not configured + - generic [ref=e998]: + - paragraph [ref=e999]: "0" + - paragraph [ref=e1000]: models + - button "exa-search Exa Search API Key Not configured 0 models" [ref=e1001] [cursor=pointer]: + - generic [ref=e1002]: + - img "exa-search" [ref=e1005] + - generic [ref=e1006]: + - generic [ref=e1007]: + - paragraph [ref=e1008]: Exa Search + - generic "API Key" [ref=e1009] + - paragraph [ref=e1010]: Not configured + - generic [ref=e1011]: + - paragraph [ref=e1012]: "0" + - paragraph [ref=e1013]: models + - button "tavily-search Tavily Search API Key Not configured 0 models" [ref=e1014] [cursor=pointer]: + - generic [ref=e1015]: + - img "tavily-search" [ref=e1018] + - generic [ref=e1019]: + - generic [ref=e1020]: + - paragraph [ref=e1021]: Tavily Search + - generic "API Key" [ref=e1022] + - paragraph [ref=e1023]: Not configured + - generic [ref=e1024]: + - paragraph [ref=e1025]: "0" + - paragraph [ref=e1026]: models + - button "opencode-zen OpenCode Zen API Key Not configured 0 models" [ref=e1027] [cursor=pointer]: + - generic [ref=e1028]: + - img "opencode-zen" [ref=e1031] + - generic [ref=e1032]: + - generic [ref=e1033]: + - paragraph [ref=e1034]: OpenCode Zen + - generic "API Key" [ref=e1035] + - paragraph [ref=e1036]: Not configured + - generic [ref=e1037]: + - paragraph [ref=e1038]: "0" + - paragraph [ref=e1039]: models + - button "opencode-go OpenCode Go API Key Not configured 0 models" [ref=e1040] [cursor=pointer]: + - generic [ref=e1041]: + - img "opencode-go" [ref=e1044] + - generic [ref=e1045]: + - generic [ref=e1046]: + - paragraph [ref=e1047]: OpenCode Go + - generic "API Key" [ref=e1048] + - paragraph [ref=e1049]: Not configured + - generic [ref=e1050]: + - paragraph [ref=e1051]: "0" + - paragraph [ref=e1052]: models + - button "AlibabaCloud Alibaba Cloud (DashScope) API Key Not configured 0 models" [ref=e1053] [cursor=pointer]: + - generic [ref=e1054]: + - img "AlibabaCloud" [ref=e1057] + - generic [ref=e1060]: + - generic [ref=e1061]: + - paragraph [ref=e1062]: Alibaba Cloud (DashScope) + - generic "API Key" [ref=e1063] + - paragraph [ref=e1064]: Not configured + - generic [ref=e1065]: + - paragraph [ref=e1066]: "0" + - paragraph [ref=e1067]: models + - button "longcat LongCat AI API Key Not configured 0 models" [ref=e1068] [cursor=pointer]: + - generic [ref=e1069]: + - img "longcat" [ref=e1072] + - generic [ref=e1073]: + - generic [ref=e1074]: + - paragraph [ref=e1075]: LongCat AI + - generic "API Key" [ref=e1076] + - paragraph [ref=e1077]: Not configured + - generic [ref=e1078]: + - paragraph [ref=e1079]: "0" + - paragraph [ref=e1080]: models + - button "Pollinations AI API Key Not configured 0 models" [ref=e1081] [cursor=pointer]: + - generic [ref=e1082]: + - img [ref=e1085]: + - img [ref=e1086] + - generic [ref=e1091]: + - generic [ref=e1092]: + - paragraph [ref=e1093]: Pollinations AI + - generic "API Key" [ref=e1094] + - paragraph [ref=e1095]: Not configured + - generic [ref=e1096]: + - paragraph [ref=e1097]: "0" + - paragraph [ref=e1098]: models + - button "puter Puter AI API Key Not configured 0 models" [ref=e1099] [cursor=pointer]: + - generic [ref=e1100]: + - img "puter" [ref=e1103] + - generic [ref=e1104]: + - generic [ref=e1105]: + - paragraph [ref=e1106]: Puter AI + - generic "API Key" [ref=e1107] + - paragraph [ref=e1108]: Not configured + - generic [ref=e1109]: + - paragraph [ref=e1110]: "0" + - paragraph [ref=e1111]: models + - button "Cloudflare Cloudflare Workers AI API Key Not configured 0 models" [ref=e1112] [cursor=pointer]: + - generic [ref=e1113]: + - img "Cloudflare" [ref=e1116] + - generic [ref=e1119]: + - generic [ref=e1120]: + - paragraph [ref=e1121]: Cloudflare Workers AI + - generic "API Key" [ref=e1122] + - paragraph [ref=e1123]: Not configured + - generic [ref=e1124]: + - paragraph [ref=e1125]: "0" + - paragraph [ref=e1126]: models + - button "scaleway Scaleway AI API Key Not configured 0 models" [ref=e1127] [cursor=pointer]: + - generic [ref=e1128]: + - img "scaleway" [ref=e1131] + - generic [ref=e1132]: + - generic [ref=e1133]: + - paragraph [ref=e1134]: Scaleway AI + - generic "API Key" [ref=e1135] + - paragraph [ref=e1136]: Not configured + - generic [ref=e1137]: + - paragraph [ref=e1138]: "0" + - paragraph [ref=e1139]: models + - button "aimlapi AI/ML API API Key Not configured 0 models" [ref=e1140] [cursor=pointer]: + - generic [ref=e1141]: + - img "aimlapi" [ref=e1144] + - generic [ref=e1145]: + - generic [ref=e1146]: + - paragraph [ref=e1147]: AI/ML API + - generic "API Key" [ref=e1148] + - paragraph [ref=e1149]: Not configured + - generic [ref=e1150]: + - paragraph [ref=e1151]: "0" + - paragraph [ref=e1152]: models + - button "Novita AI API Key Not configured 0 models" [ref=e1153] [cursor=pointer]: + - generic [ref=e1154]: + - img [ref=e1157] + - generic [ref=e1160]: + - generic [ref=e1161]: + - paragraph [ref=e1162]: Novita AI + - generic "API Key" [ref=e1163] + - paragraph [ref=e1164]: Not configured + - generic [ref=e1165]: + - paragraph [ref=e1166]: "0" + - paragraph [ref=e1167]: models + - button "PiAPI API Key Not configured 0 models" [ref=e1168] [cursor=pointer]: + - generic [ref=e1169]: + - img [ref=e1172] + - generic [ref=e1175]: + - generic [ref=e1176]: + - paragraph [ref=e1177]: PiAPI + - generic "API Key" [ref=e1178] + - paragraph [ref=e1179]: Not configured + - generic [ref=e1180]: + - paragraph [ref=e1181]: "0" + - paragraph [ref=e1182]: models + - button "GoAPI API Key Not configured 0 models" [ref=e1183] [cursor=pointer]: + - generic [ref=e1184]: + - img [ref=e1187] + - generic [ref=e1190]: + - generic [ref=e1191]: + - paragraph [ref=e1192]: GoAPI + - generic "API Key" [ref=e1193] + - paragraph [ref=e1194]: Not configured + - generic [ref=e1195]: + - paragraph [ref=e1196]: "0" + - paragraph [ref=e1197]: models + - button "LaoZhang AI API Key Not configured 0 models" [ref=e1198] [cursor=pointer]: + - generic [ref=e1199]: + - img [ref=e1202] + - generic [ref=e1205]: + - generic [ref=e1206]: + - paragraph [ref=e1207]: LaoZhang AI + - generic "API Key" [ref=e1208] + - paragraph [ref=e1209]: Not configured + - generic [ref=e1210]: + - paragraph [ref=e1211]: "0" + - paragraph [ref=e1212]: models + - button "CLIProxyAPI API Key Not configured 0 models" [ref=e1213] [cursor=pointer]: + - generic [ref=e1214]: + - img [ref=e1217] + - generic [ref=e1220]: + - generic [ref=e1221]: + - paragraph [ref=e1222]: CLIProxyAPI + - generic "API Key" [ref=e1223] + - paragraph [ref=e1224]: Not configured + - generic [ref=e1225]: + - paragraph [ref=e1226]: "0" + - paragraph [ref=e1227]: models + - alert [ref=e158] diff --git a/.playwright-mcp/page-2026-04-04T08-04-46-313Z.yml b/.playwright-mcp/page-2026-04-04T08-04-46-313Z.yml new file mode 100644 index 0000000000..c83adc1401 --- /dev/null +++ b/.playwright-mcp/page-2026-04-04T08-04-46-313Z.yml @@ -0,0 +1,132 @@ +- generic [active] [ref=e1]: + - link "Skip to content" [ref=e2] [cursor=pointer]: + - /url: "#main-content" + - generic [ref=e3]: + - complementary [ref=e5]: + - link "Skip to content" [ref=e6] [cursor=pointer]: + - /url: "#main-content" + - link "OmniRoute v3.5.0" [ref=e12] [cursor=pointer]: + - /url: /dashboard + - img [ref=e14] + - generic [ref=e26]: + - heading "OmniRoute" [level=1] [ref=e27] + - generic [ref=e28]: v3.5.0 + - navigation "Main navigation" [ref=e29]: + - generic [ref=e30]: + - link "home Home" [ref=e31] [cursor=pointer]: + - /url: /dashboard + - generic [ref=e32]: home + - generic [ref=e33]: Home + - link "api Endpoints" [ref=e34] [cursor=pointer]: + - /url: /dashboard/endpoint + - generic [ref=e35]: api + - generic [ref=e36]: Endpoints + - link "vpn_key API Manager" [ref=e37] [cursor=pointer]: + - /url: /dashboard/api-manager + - generic [ref=e38]: vpn_key + - generic [ref=e39]: API Manager + - link "dns Providers" [ref=e40] [cursor=pointer]: + - /url: /dashboard/providers + - generic [ref=e41]: dns + - generic [ref=e42]: Providers + - link "layers Combos" [ref=e43] [cursor=pointer]: + - /url: /dashboard/combos + - generic [ref=e44]: layers + - generic [ref=e45]: Combos + - link "auto_awesome Auto Combo" [ref=e46] [cursor=pointer]: + - /url: /dashboard/auto-combo + - generic [ref=e47]: auto_awesome + - generic [ref=e48]: Auto Combo + - link "account_balance_wallet Costs" [ref=e49] [cursor=pointer]: + - /url: /dashboard/costs + - generic [ref=e50]: account_balance_wallet + - generic [ref=e51]: Costs + - link "analytics Analytics" [ref=e52] [cursor=pointer]: + - /url: /dashboard/analytics + - generic [ref=e53]: analytics + - generic [ref=e54]: Analytics + - link "tune Limits & Quotas" [ref=e55] [cursor=pointer]: + - /url: /dashboard/limits + - generic [ref=e56]: tune + - generic [ref=e57]: Limits & Quotas + - link "cached Cache" [ref=e58] [cursor=pointer]: + - /url: /dashboard/cache + - generic [ref=e59]: cached + - generic [ref=e60]: Cache + - link "perm_media Media" [ref=e61] [cursor=pointer]: + - /url: /dashboard/cache/media + - generic [ref=e62]: perm_media + - generic [ref=e63]: Media + - generic [ref=e64]: + - paragraph [ref=e65]: CLI + - link "terminal Tools" [ref=e66] [cursor=pointer]: + - /url: /dashboard/cli-tools + - generic [ref=e67]: terminal + - generic [ref=e68]: Tools + - link "smart_toy Agents" [ref=e69] [cursor=pointer]: + - /url: /dashboard/agents + - generic [ref=e70]: smart_toy + - generic [ref=e71]: Agents + - link "psychology Memory" [ref=e72] [cursor=pointer]: + - /url: /dashboard/memory + - generic [ref=e73]: psychology + - generic [ref=e74]: Memory + - link "auto_fix_high Skills" [ref=e75] [cursor=pointer]: + - /url: /dashboard/skills + - generic [ref=e76]: auto_fix_high + - generic [ref=e77]: Skills + - generic [ref=e78]: + - paragraph [ref=e79]: System + - link "health_and_safety Health" [ref=e80] [cursor=pointer]: + - /url: /dashboard/health + - generic [ref=e81]: health_and_safety + - generic [ref=e82]: Health + - link "description Logs" [ref=e83] [cursor=pointer]: + - /url: /dashboard/logs + - generic [ref=e84]: description + - generic [ref=e85]: Logs + - link "history Audit Log" [ref=e86] [cursor=pointer]: + - /url: /dashboard/audit + - generic [ref=e87]: history + - generic [ref=e88]: Audit Log + - link "settings Settings" [ref=e89] [cursor=pointer]: + - /url: /dashboard/settings + - generic [ref=e90]: settings + - generic [ref=e91]: Settings + - generic [ref=e92]: + - paragraph [ref=e93]: Help + - link "menu_book Docs" [ref=e94] [cursor=pointer]: + - /url: /docs + - generic [ref=e95]: menu_book + - generic [ref=e96]: Docs + - link "bug_report Issues" [ref=e97] [cursor=pointer]: + - /url: https://github.com/diegosouzapw/OmniRoute/issues + - generic [ref=e98]: bug_report + - generic [ref=e99]: Issues + - generic [ref=e100]: + - button "restart_alt Restart" [ref=e101]: + - generic: restart_alt + - text: Restart + - button "power_settings_new Shutdown" [ref=e102]: + - generic: power_settings_new + - text: Shutdown + - main [ref=e103]: + - generic [ref=e104]: + - button "menu" [ref=e106]: + - generic: menu + - generic [ref=e107]: + - button "🇺🇸 EN expand_more" [ref=e109]: + - generic [ref=e110]: 🇺🇸 + - generic [ref=e111]: EN + - generic: expand_more + - button "Switch to dark mode" [ref=e112]: + - generic: dark_mode + - button "logout" [ref=e113]: + - generic: logout + - navigation "Breadcrumb" [ref=e116]: + - link "Dashboard" [ref=e118] [cursor=pointer]: + - /url: /dashboard + - generic [ref=e119]: + - generic [ref=e120]: › + - generic [ref=e121]: Providers + - alert [ref=e135] diff --git a/src/app/(dashboard)/dashboard/settings/components/ModelsDevSyncTab.tsx b/src/app/(dashboard)/dashboard/settings/components/ModelsDevSyncTab.tsx new file mode 100644 index 0000000000..60d6bdfceb --- /dev/null +++ b/src/app/(dashboard)/dashboard/settings/components/ModelsDevSyncTab.tsx @@ -0,0 +1,346 @@ +"use client"; + +import { useState, useEffect, useCallback } from "react"; +import { Card } from "@/shared/components"; +import { useTranslations } from "next-intl"; + +interface ModelsDevStatus { + enabled: boolean; + lastSync: string | null; + lastSyncModelCount: number; + lastSyncCapabilityCount: number; + nextSync: string | null; + intervalMs: number; + providerCount: number; + modelCount: number; + capabilityCount: number; +} + +interface SyncResult { + success: boolean; + modelCount: number; + providerCount: number; + capabilityCount: number; + error?: string; +} + +export default function ModelsDevSyncTab() { + const t = useTranslations("settings"); + const [status, setStatus] = useState(null); + const [loading, setLoading] = useState(true); + const [syncing, setSyncing] = useState(false); + const [saving, setSaving] = useState(false); + const [enabled, setEnabled] = useState(false); + const [intervalHours, setIntervalHours] = useState(24); + const [feedback, setFeedback] = useState<{ type: "success" | "error"; message: string } | null>( + null + ); + + const fetchStatus = useCallback(async () => { + try { + const res = await fetch("/api/settings/models-dev?action=status"); + if (res.ok) { + const data = await res.json(); + setStatus(data); + } + } catch { + // Silently fail — sync may not be initialized yet + } + setLoading(false); + }, []); + + useEffect(() => { + Promise.all([fetchStatus(), fetch("/api/settings").then((r) => (r.ok ? r.json() : null))]) + .then(([, settingsData]) => { + if (settingsData) { + setEnabled(settingsData.modelsDevSyncEnabled === true); + const intervalMs = settingsData.modelsDevSyncInterval || 86400000; + setIntervalHours(Math.round(intervalMs / 3600000)); + } + }) + .catch((err) => { + console.error("Failed to fetch models.dev settings:", err); + setFeedback({ type: "error", message: "Failed to load settings" }); + }); + }, [fetchStatus]); + + const triggerSync = async () => { + setSyncing(true); + setFeedback(null); + try { + const res = await fetch("/api/settings/models-dev", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ action: "sync" }), + }); + if (res.ok) { + const result: SyncResult = await res.json(); + if (result.success) { + setFeedback({ + type: "success", + message: `Synced ${result.modelCount.toLocaleString()} pricing entries, ${result.capabilityCount.toLocaleString()} capabilities`, + }); + fetchStatus(); + } else { + setFeedback({ type: "error", message: result.error || "Sync failed" }); + } + } else { + setFeedback({ type: "error", message: "Sync request failed" }); + } + } catch { + setFeedback({ type: "error", message: "Network error" }); + } finally { + setSyncing(false); + setTimeout(() => setFeedback(null), 5000); + } + }; + + const toggleEnabled = async () => { + const newVal = !enabled; + setEnabled(newVal); + setSaving(true); + try { + const res = await fetch("/api/settings", { + method: "PATCH", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ modelsDevSyncEnabled: newVal }), + }); + if (!res.ok) { + setEnabled(!newVal); + setFeedback({ type: "error", message: t("enableSyncError") || "Failed to update" }); + } else { + setFeedback({ type: "success", message: "Settings saved" }); + } + } catch { + setEnabled(!newVal); + setFeedback({ type: "error", message: "Network error" }); + } finally { + setSaving(false); + setTimeout(() => setFeedback(null), 3000); + } + }; + + const updateInterval = async (hours: number) => { + const oldInterval = intervalHours; + setIntervalHours(hours); + try { + const res = await fetch("/api/settings", { + method: "PATCH", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ modelsDevSyncInterval: hours * 3600000 }), + }); + if (!res.ok) { + setIntervalHours(oldInterval); + setFeedback({ type: "error", message: t("enableSyncError") || "Failed to update" }); + } else { + setFeedback({ type: "success", message: "Interval updated" }); + } + } catch { + setIntervalHours(oldInterval); + setFeedback({ type: "error", message: "Network error" }); + } finally { + setTimeout(() => setFeedback(null), 3000); + } + }; + + if (loading) { + return ( + +
+
+ +
+
+

{t("modelsDevTitle")}

+

{t("modelsDevDesc")}

+
+
+
{t("loading")}...
+
+ ); + } + + const formatLastSync = (iso: string | null) => { + if (!iso) return t("never"); + const d = new Date(iso); + const now = new Date(); + const diffMs = now.getTime() - d.getTime(); + const diffMin = Math.floor(diffMs / 60000); + if (diffMin < 1) return t("justNow"); + if (diffMin < 60) return `${diffMin}m ago`; + const diffHr = Math.floor(diffMin / 60); + if (diffHr < 24) return `${diffHr}h ago`; + return d.toLocaleDateString(); + }; + + return ( +
+ {/* Main sync card */} + +
+
+ +
+
+

{t("modelsDevTitle")}

+

{t("modelsDevDesc")}

+
+ {feedback && ( + + + {feedback.type === "success" ? "check_circle" : "error"} + {" "} + {feedback.message} + + )} +
+ + {/* Enable toggle */} +
+
+

{t("modelsDevEnabled")}

+

{t("modelsDevEnabledDesc")}

+
+ +
+ + {/* Sync interval */} + {enabled && ( +
+
+

{t("modelsDevInterval")}

+ {intervalHours}h +
+ updateInterval(parseInt(e.target.value))} + className="w-full accent-blue-500" + /> +
+ 1h + 6h + 24h + 7d +
+
+ )} + + {/* Manual sync button */} +
+ + {status?.lastSync && ( + + {t("lastSync")}: {formatLastSync(status.lastSync)} + + )} +
+
+ + {/* Stats card */} + {status && (status.providerCount > 0 || status.modelCount > 0) && ( + +
+
+ +
+
+

{t("modelsDevStats")}

+

{t("modelsDevStatsDesc")}

+
+
+ +
+
+

+ {status.providerCount.toLocaleString()} +

+

{t("providers")}

+
+
+

+ {status.modelCount.toLocaleString()} +

+

{t("modelsWithPricing")}

+
+
+

+ {status.capabilityCount.toLocaleString()} +

+

{t("capabilities")}

+
+
+

+ {status.lastSyncModelCount > 0 ? status.lastSyncModelCount.toLocaleString() : "—"} +

+

{t("lastSyncCount")}

+
+
+ + {status.lastSync && ( +
+ {t("lastSyncFull")}: {new Date(status.lastSync).toLocaleString()} +
+ )} +
+ )} + + {/* Info card */} + +
+
+ +
+
+

{t("modelsDevInfo")}

+
+
+
+

{t("modelsDevInfoDesc")}

+

{t("modelsDevInfoResolution")}

+

{t("modelsDevInfoOrder")}

+
+
+
+ ); +} diff --git a/src/app/(dashboard)/dashboard/settings/page.tsx b/src/app/(dashboard)/dashboard/settings/page.tsx index aec76c7de6..286ff5d3b2 100644 --- a/src/app/(dashboard)/dashboard/settings/page.tsx +++ b/src/app/(dashboard)/dashboard/settings/page.tsx @@ -18,6 +18,7 @@ import ModelAliasesTab from "./components/ModelAliasesTab"; import BackgroundDegradationTab from "./components/BackgroundDegradationTab"; import CacheSettingsTab from "./components/CacheSettingsTab"; import MemorySkillsTab from "./components/MemorySkillsTab"; +import ModelsDevSyncTab from "./components/ModelsDevSyncTab"; import ResilienceTab from "./components/ResilienceTab"; import CliproxyapiSettingsTab from "./components/CliproxyapiSettingsTab"; @@ -95,6 +96,7 @@ export default function SettingsPage() { + )} diff --git a/src/app/api/settings/models-dev/route.ts b/src/app/api/settings/models-dev/route.ts new file mode 100644 index 0000000000..0b4507a86a --- /dev/null +++ b/src/app/api/settings/models-dev/route.ts @@ -0,0 +1,92 @@ +import { NextRequest, NextResponse } from "next/server"; +import { z } from "zod"; +import { isAuthenticated } from "@/shared/utils/apiAuth"; +import { isValidationFailure, validateBody } from "@/shared/validation/helpers"; +import { + syncModelsDev, + getModelsDevPricing, + getSyncedCapabilities, + getSyncStatus, + startPeriodicSync, + stopPeriodicSync, +} from "@/lib/modelsDevSync"; + +const modelsDevActionSchema = z.object({ + action: z.enum(["sync", "start", "stop"]), + dryRun: z.boolean().optional(), + syncCapabilities: z.boolean().optional(), +}); + +export async function GET(request: NextRequest) { + if (!(await isAuthenticated(request))) { + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); + } + + const { searchParams } = new URL(request.url); + const action = searchParams.get("action"); + + if (action === "status") { + const status = getSyncStatus(); + const pricing = getModelsDevPricing(); + const caps = getSyncedCapabilities(); + + const providerCount = Object.keys(pricing).length; + const modelCount = Object.values(pricing).reduce( + (sum, models) => sum + Object.keys(models).length, + 0 + ); + const capabilityCount = Object.values(caps).reduce( + (sum, models) => sum + Object.keys(models).length, + 0 + ); + + return NextResponse.json({ + ...status, + providerCount, + modelCount, + capabilityCount, + }); + } + + return NextResponse.json({ error: "Unknown action" }, { status: 400 }); +} + +export async function POST(request: NextRequest) { + if (!(await isAuthenticated(request))) { + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); + } + + let rawBody: unknown; + try { + rawBody = await request.json(); + } catch { + return NextResponse.json({ error: "Invalid JSON body" }, { status: 400 }); + } + + const validation = validateBody(modelsDevActionSchema, rawBody); + if (isValidationFailure(validation)) { + return validation.response; + } + + const { action, dryRun, syncCapabilities } = validation.data; + + if (action === "sync") { + const result = await syncModelsDev({ + dryRun: dryRun ?? false, + syncCapabilities: syncCapabilities !== false, + }); + return NextResponse.json(result); + } + + if (action === "start") { + startPeriodicSync(); + return NextResponse.json({ success: true, message: "Periodic sync started" }); + } + + if (action === "stop") { + stopPeriodicSync(); + return NextResponse.json({ success: true, message: "Periodic sync stopped" }); + } + + return NextResponse.json({ error: "Unknown action" }, { status: 400 }); +} diff --git a/src/app/api/settings/route.ts b/src/app/api/settings/route.ts index 514c57bd00..e84e3f35a9 100644 --- a/src/app/api/settings/route.ts +++ b/src/app/api/settings/route.ts @@ -44,6 +44,12 @@ export async function PATCH(request) { try { const rawBody = await request.json(); + // Capture old settings BEFORE update for comparison (needed for models.dev sync toggle) + const oldSettings = + "modelsDevSyncEnabled" in rawBody || "modelsDevSyncInterval" in rawBody + ? await getSettings() + : null; + // Zod validation const validation = validateBody(updateSettingsSchema, rawBody); if (isValidationFailure(validation)) { @@ -149,6 +155,26 @@ export async function PATCH(request) { invalidateCacheControlSettingsCache(); } + // Sync models.dev sync settings (compare old vs new state) + if (oldSettings && ("modelsDevSyncEnabled" in body || "modelsDevSyncInterval" in body)) { + const { stopPeriodicSync, startPeriodicSync } = await import("@/lib/modelsDevSync"); + const wasEnabled = (oldSettings as Record).modelsDevSyncEnabled === true; + const isEnabled = settings.modelsDevSyncEnabled === true; + const oldInterval = (oldSettings as Record).modelsDevSyncInterval as + | number + | undefined; + const newInterval = settings.modelsDevSyncInterval as number | undefined; + + if (wasEnabled && !isEnabled) { + stopPeriodicSync(); + } else if (!wasEnabled && isEnabled) { + startPeriodicSync(newInterval); + } else if (isEnabled && oldInterval !== newInterval) { + stopPeriodicSync(); + startPeriodicSync(newInterval); + } + } + const { password, ...safeSettings } = settings; return NextResponse.json(safeSettings); } catch (error) { diff --git a/src/i18n/messages/ar.json b/src/i18n/messages/ar.json index 9f3ec0510d..70c0955aca 100644 --- a/src/i18n/messages/ar.json +++ b/src/i18n/messages/ar.json @@ -2164,7 +2164,46 @@ "customPricingNote": "يمكنك تجاوز التسعير الافتراضي لنماذج محددة. تحظى التجاوزات المخصصة بالأولوية على الأسعار التي يتم اكتشافها تلقائيًا.", "editPricing": "تحرير التسعير", "viewFullDetails": "عرض التفاصيل الكاملة", - "themeCoral": "مرجاني" + "themeCoral": "مرجاني", + "modelsDevTitle": "Model Database", + "modelsDevDesc": "Auto-sync pricing, capabilities & specs from models.dev", + "modelsDevEnabled": "Enable models.dev Sync", + "modelsDevEnabledDesc": "Fetch model pricing, capabilities, and specs from the open-source models.dev database", + "modelsDevInterval": "Sync Interval", + "syncNow": "Sync Now", + "syncing": "Syncing...", + "lastSync": "Last sync", + "never": "Never", + "modelsDevStats": "Sync Statistics", + "modelsDevStatsDesc": "Current models.dev data coverage", + "modelsWithPricing": "Models with Pricing", + "capabilities": "Capabilities", + "lastSyncCount": "Last Sync Count", + "lastSyncFull": "Last full sync", + "modelsDevInfo": "How It Works", + "modelsDevInfoDesc": "models.dev is an open-source database of AI model specifications maintained by the SST/OpenCode team. It provides pricing, capabilities, context limits, and modalities for 4,000+ models across 100+ providers.", + "modelsDevInfoResolution": "Pricing resolution order (highest priority first):", + "modelsDevInfoOrder": "User Override → models.dev → LiteLLM → Hardcoded Default", + "memoryTitle": "Memory", + "memoryDesc": "Persistent cross-session conversational memory", + "memoryEnabled": "Enable Memory", + "memoryEnabledDesc": "When enabled, OmniRoute will inject relevant past context.", + "maxTokens": "Max Tokens", + "retentionDays": "Retention", + "recent": "Recent", + "recentDesc": "Chronological window", + "semantic": "Semantic", + "semanticDesc": "Vector search", + "hybrid": "Hybrid", + "hybridDesc": "Recent + Semantic", + "skillsTitle": "Skills", + "skillsDesc": "Tools for models", + "skillsEnabled": "Enable Skills", + "skillsEnabledDesc": "Allows agents to trigger functions.", + "skillsComingSoon": "Skills marketplace coming soon.", + "memorySkillsTitle": "Memory & Skills", + "memorySkillsDesc": "Persistent context & capabilities", + "days": "Days" }, "translator": { "title": "مترجم", diff --git a/src/i18n/messages/bg.json b/src/i18n/messages/bg.json index 7f0fb6c8b8..785e157e63 100644 --- a/src/i18n/messages/bg.json +++ b/src/i18n/messages/bg.json @@ -2164,7 +2164,46 @@ "customPricingNote": "Можете да замените цените по подразбиране за конкретни модели. Персонализираните замени имат приоритет пред автоматично разпознатото ценообразуване.", "editPricing": "Редактиране на цените", "viewFullDetails": "Вижте пълните подробности", - "themeCoral": "Корал" + "themeCoral": "Корал", + "modelsDevTitle": "Model Database", + "modelsDevDesc": "Auto-sync pricing, capabilities & specs from models.dev", + "modelsDevEnabled": "Enable models.dev Sync", + "modelsDevEnabledDesc": "Fetch model pricing, capabilities, and specs from the open-source models.dev database", + "modelsDevInterval": "Sync Interval", + "syncNow": "Sync Now", + "syncing": "Syncing...", + "lastSync": "Last sync", + "never": "Never", + "modelsDevStats": "Sync Statistics", + "modelsDevStatsDesc": "Current models.dev data coverage", + "modelsWithPricing": "Models with Pricing", + "capabilities": "Capabilities", + "lastSyncCount": "Last Sync Count", + "lastSyncFull": "Last full sync", + "modelsDevInfo": "How It Works", + "modelsDevInfoDesc": "models.dev is an open-source database of AI model specifications maintained by the SST/OpenCode team. It provides pricing, capabilities, context limits, and modalities for 4,000+ models across 100+ providers.", + "modelsDevInfoResolution": "Pricing resolution order (highest priority first):", + "modelsDevInfoOrder": "User Override → models.dev → LiteLLM → Hardcoded Default", + "memoryTitle": "Memory", + "memoryDesc": "Persistent cross-session conversational memory", + "memoryEnabled": "Enable Memory", + "memoryEnabledDesc": "When enabled, OmniRoute will inject relevant past context.", + "maxTokens": "Max Tokens", + "retentionDays": "Retention", + "recent": "Recent", + "recentDesc": "Chronological window", + "semantic": "Semantic", + "semanticDesc": "Vector search", + "hybrid": "Hybrid", + "hybridDesc": "Recent + Semantic", + "skillsTitle": "Skills", + "skillsDesc": "Tools for models", + "skillsEnabled": "Enable Skills", + "skillsEnabledDesc": "Allows agents to trigger functions.", + "skillsComingSoon": "Skills marketplace coming soon.", + "memorySkillsTitle": "Memory & Skills", + "memorySkillsDesc": "Persistent context & capabilities", + "days": "Days" }, "translator": { "title": "Преводач", diff --git a/src/i18n/messages/cs.json b/src/i18n/messages/cs.json index f67a74a560..ff84196f9e 100644 --- a/src/i18n/messages/cs.json +++ b/src/i18n/messages/cs.json @@ -2164,7 +2164,46 @@ "customPricingNote": "Výchozí ceny pro konkrétní modely můžete přepsat. Vlastní přepsání má přednost před automaticky zjištěnými cenami.", "editPricing": "Upravit ceny", "viewFullDetails": "Zobrazit všechny podrobnosti", - "themeCoral": "Korál" + "themeCoral": "Korál", + "modelsDevTitle": "Model Database", + "modelsDevDesc": "Auto-sync pricing, capabilities & specs from models.dev", + "modelsDevEnabled": "Enable models.dev Sync", + "modelsDevEnabledDesc": "Fetch model pricing, capabilities, and specs from the open-source models.dev database", + "modelsDevInterval": "Sync Interval", + "syncNow": "Sync Now", + "syncing": "Syncing...", + "lastSync": "Last sync", + "never": "Never", + "modelsDevStats": "Sync Statistics", + "modelsDevStatsDesc": "Current models.dev data coverage", + "modelsWithPricing": "Models with Pricing", + "capabilities": "Capabilities", + "lastSyncCount": "Last Sync Count", + "lastSyncFull": "Last full sync", + "modelsDevInfo": "How It Works", + "modelsDevInfoDesc": "models.dev is an open-source database of AI model specifications maintained by the SST/OpenCode team. It provides pricing, capabilities, context limits, and modalities for 4,000+ models across 100+ providers.", + "modelsDevInfoResolution": "Pricing resolution order (highest priority first):", + "modelsDevInfoOrder": "User Override → models.dev → LiteLLM → Hardcoded Default", + "memoryTitle": "Memory", + "memoryDesc": "Persistent cross-session conversational memory", + "memoryEnabled": "Enable Memory", + "memoryEnabledDesc": "When enabled, OmniRoute will inject relevant past context.", + "maxTokens": "Max Tokens", + "retentionDays": "Retention", + "recent": "Recent", + "recentDesc": "Chronological window", + "semantic": "Semantic", + "semanticDesc": "Vector search", + "hybrid": "Hybrid", + "hybridDesc": "Recent + Semantic", + "skillsTitle": "Skills", + "skillsDesc": "Tools for models", + "skillsEnabled": "Enable Skills", + "skillsEnabledDesc": "Allows agents to trigger functions.", + "skillsComingSoon": "Skills marketplace coming soon.", + "memorySkillsTitle": "Memory & Skills", + "memorySkillsDesc": "Persistent context & capabilities", + "days": "Days" }, "translator": { "title": "Překladatel", diff --git a/src/i18n/messages/da.json b/src/i18n/messages/da.json index 0c8c28434b..ee53e11908 100644 --- a/src/i18n/messages/da.json +++ b/src/i18n/messages/da.json @@ -2164,7 +2164,46 @@ "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" + "themeCoral": "Koral", + "modelsDevTitle": "Model Database", + "modelsDevDesc": "Auto-sync pricing, capabilities & specs from models.dev", + "modelsDevEnabled": "Enable models.dev Sync", + "modelsDevEnabledDesc": "Fetch model pricing, capabilities, and specs from the open-source models.dev database", + "modelsDevInterval": "Sync Interval", + "syncNow": "Sync Now", + "syncing": "Syncing...", + "lastSync": "Last sync", + "never": "Never", + "modelsDevStats": "Sync Statistics", + "modelsDevStatsDesc": "Current models.dev data coverage", + "modelsWithPricing": "Models with Pricing", + "capabilities": "Capabilities", + "lastSyncCount": "Last Sync Count", + "lastSyncFull": "Last full sync", + "modelsDevInfo": "How It Works", + "modelsDevInfoDesc": "models.dev is an open-source database of AI model specifications maintained by the SST/OpenCode team. It provides pricing, capabilities, context limits, and modalities for 4,000+ models across 100+ providers.", + "modelsDevInfoResolution": "Pricing resolution order (highest priority first):", + "modelsDevInfoOrder": "User Override → models.dev → LiteLLM → Hardcoded Default", + "memoryTitle": "Memory", + "memoryDesc": "Persistent cross-session conversational memory", + "memoryEnabled": "Enable Memory", + "memoryEnabledDesc": "When enabled, OmniRoute will inject relevant past context.", + "maxTokens": "Max Tokens", + "retentionDays": "Retention", + "recent": "Recent", + "recentDesc": "Chronological window", + "semantic": "Semantic", + "semanticDesc": "Vector search", + "hybrid": "Hybrid", + "hybridDesc": "Recent + Semantic", + "skillsTitle": "Skills", + "skillsDesc": "Tools for models", + "skillsEnabled": "Enable Skills", + "skillsEnabledDesc": "Allows agents to trigger functions.", + "skillsComingSoon": "Skills marketplace coming soon.", + "memorySkillsTitle": "Memory & Skills", + "memorySkillsDesc": "Persistent context & capabilities", + "days": "Days" }, "translator": { "title": "Oversætter", diff --git a/src/i18n/messages/de.json b/src/i18n/messages/de.json index d92fc9323d..76d2ae4d8f 100644 --- a/src/i18n/messages/de.json +++ b/src/i18n/messages/de.json @@ -2164,7 +2164,46 @@ "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" + "themeCoral": "Koralle", + "modelsDevTitle": "Model Database", + "modelsDevDesc": "Auto-sync pricing, capabilities & specs from models.dev", + "modelsDevEnabled": "Enable models.dev Sync", + "modelsDevEnabledDesc": "Fetch model pricing, capabilities, and specs from the open-source models.dev database", + "modelsDevInterval": "Sync Interval", + "syncNow": "Sync Now", + "syncing": "Syncing...", + "lastSync": "Last sync", + "never": "Never", + "modelsDevStats": "Sync Statistics", + "modelsDevStatsDesc": "Current models.dev data coverage", + "modelsWithPricing": "Models with Pricing", + "capabilities": "Capabilities", + "lastSyncCount": "Last Sync Count", + "lastSyncFull": "Last full sync", + "modelsDevInfo": "How It Works", + "modelsDevInfoDesc": "models.dev is an open-source database of AI model specifications maintained by the SST/OpenCode team. It provides pricing, capabilities, context limits, and modalities for 4,000+ models across 100+ providers.", + "modelsDevInfoResolution": "Pricing resolution order (highest priority first):", + "modelsDevInfoOrder": "User Override → models.dev → LiteLLM → Hardcoded Default", + "memoryTitle": "Memory", + "memoryDesc": "Persistent cross-session conversational memory", + "memoryEnabled": "Enable Memory", + "memoryEnabledDesc": "When enabled, OmniRoute will inject relevant past context.", + "maxTokens": "Max Tokens", + "retentionDays": "Retention", + "recent": "Recent", + "recentDesc": "Chronological window", + "semantic": "Semantic", + "semanticDesc": "Vector search", + "hybrid": "Hybrid", + "hybridDesc": "Recent + Semantic", + "skillsTitle": "Skills", + "skillsDesc": "Tools for models", + "skillsEnabled": "Enable Skills", + "skillsEnabledDesc": "Allows agents to trigger functions.", + "skillsComingSoon": "Skills marketplace coming soon.", + "memorySkillsTitle": "Memory & Skills", + "memorySkillsDesc": "Persistent context & capabilities", + "days": "Days" }, "translator": { "title": "Übersetzer", diff --git a/src/i18n/messages/en.json b/src/i18n/messages/en.json index 7a554b35bc..d5623cd60e 100644 --- a/src/i18n/messages/en.json +++ b/src/i18n/messages/en.json @@ -1827,6 +1827,27 @@ "skillsComingSoon": "Skills marketplace coming soon.", "memorySkillsTitle": "Memory & Skills", "memorySkillsDesc": "Persistent context & capabilities", + "modelsDevTitle": "Model Database", + "modelsDevDesc": "Auto-sync pricing, capabilities & specs from models.dev", + "modelsDevEnabled": "Enable models.dev Sync", + "modelsDevEnabledDesc": "Fetch model pricing, capabilities, and specs from the open-source models.dev database", + "modelsDevInterval": "Sync Interval", + "syncNow": "Sync Now", + "syncing": "Syncing...", + "lastSync": "Last sync", + "never": "Never", + "justNow": "Just now", + "modelsDevStats": "Sync Statistics", + "modelsDevStatsDesc": "Current models.dev data coverage", + "providers": "Providers", + "modelsWithPricing": "Models with Pricing", + "capabilities": "Capabilities", + "lastSyncCount": "Last Sync Count", + "lastSyncFull": "Last full sync", + "modelsDevInfo": "How It Works", + "modelsDevInfoDesc": "models.dev is an open-source database of AI model specifications maintained by the SST/OpenCode team. It provides pricing, capabilities, context limits, and modalities for 4,000+ models across 100+ providers.", + "modelsDevInfoResolution": "Pricing resolution order (highest priority first):", + "modelsDevInfoOrder": "User Override → models.dev → LiteLLM → Hardcoded Default", "systemTheme": "System Theme", "debugToggle": "Enable Debug Mode", "sidebarVisibilityToggle": "Show Sidebar Items", diff --git a/src/i18n/messages/es.json b/src/i18n/messages/es.json index 6f5553e5b7..a2dfe6965b 100644 --- a/src/i18n/messages/es.json +++ b/src/i18n/messages/es.json @@ -2164,7 +2164,46 @@ "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" + "themeCoral": "Coral", + "modelsDevTitle": "Model Database", + "modelsDevDesc": "Auto-sync pricing, capabilities & specs from models.dev", + "modelsDevEnabled": "Enable models.dev Sync", + "modelsDevEnabledDesc": "Fetch model pricing, capabilities, and specs from the open-source models.dev database", + "modelsDevInterval": "Sync Interval", + "syncNow": "Sync Now", + "syncing": "Syncing...", + "lastSync": "Last sync", + "never": "Never", + "modelsDevStats": "Sync Statistics", + "modelsDevStatsDesc": "Current models.dev data coverage", + "modelsWithPricing": "Models with Pricing", + "capabilities": "Capabilities", + "lastSyncCount": "Last Sync Count", + "lastSyncFull": "Last full sync", + "modelsDevInfo": "How It Works", + "modelsDevInfoDesc": "models.dev is an open-source database of AI model specifications maintained by the SST/OpenCode team. It provides pricing, capabilities, context limits, and modalities for 4,000+ models across 100+ providers.", + "modelsDevInfoResolution": "Pricing resolution order (highest priority first):", + "modelsDevInfoOrder": "User Override → models.dev → LiteLLM → Hardcoded Default", + "memoryTitle": "Memory", + "memoryDesc": "Persistent cross-session conversational memory", + "memoryEnabled": "Enable Memory", + "memoryEnabledDesc": "When enabled, OmniRoute will inject relevant past context.", + "maxTokens": "Max Tokens", + "retentionDays": "Retention", + "recent": "Recent", + "recentDesc": "Chronological window", + "semantic": "Semantic", + "semanticDesc": "Vector search", + "hybrid": "Hybrid", + "hybridDesc": "Recent + Semantic", + "skillsTitle": "Skills", + "skillsDesc": "Tools for models", + "skillsEnabled": "Enable Skills", + "skillsEnabledDesc": "Allows agents to trigger functions.", + "skillsComingSoon": "Skills marketplace coming soon.", + "memorySkillsTitle": "Memory & Skills", + "memorySkillsDesc": "Persistent context & capabilities", + "days": "Days" }, "translator": { "title": "Traductor", diff --git a/src/i18n/messages/fi.json b/src/i18n/messages/fi.json index 715e857305..687476be1c 100644 --- a/src/i18n/messages/fi.json +++ b/src/i18n/messages/fi.json @@ -2164,7 +2164,46 @@ "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" + "themeCoral": "Koralli", + "modelsDevTitle": "Model Database", + "modelsDevDesc": "Auto-sync pricing, capabilities & specs from models.dev", + "modelsDevEnabled": "Enable models.dev Sync", + "modelsDevEnabledDesc": "Fetch model pricing, capabilities, and specs from the open-source models.dev database", + "modelsDevInterval": "Sync Interval", + "syncNow": "Sync Now", + "syncing": "Syncing...", + "lastSync": "Last sync", + "never": "Never", + "modelsDevStats": "Sync Statistics", + "modelsDevStatsDesc": "Current models.dev data coverage", + "modelsWithPricing": "Models with Pricing", + "capabilities": "Capabilities", + "lastSyncCount": "Last Sync Count", + "lastSyncFull": "Last full sync", + "modelsDevInfo": "How It Works", + "modelsDevInfoDesc": "models.dev is an open-source database of AI model specifications maintained by the SST/OpenCode team. It provides pricing, capabilities, context limits, and modalities for 4,000+ models across 100+ providers.", + "modelsDevInfoResolution": "Pricing resolution order (highest priority first):", + "modelsDevInfoOrder": "User Override → models.dev → LiteLLM → Hardcoded Default", + "memoryTitle": "Memory", + "memoryDesc": "Persistent cross-session conversational memory", + "memoryEnabled": "Enable Memory", + "memoryEnabledDesc": "When enabled, OmniRoute will inject relevant past context.", + "maxTokens": "Max Tokens", + "retentionDays": "Retention", + "recent": "Recent", + "recentDesc": "Chronological window", + "semantic": "Semantic", + "semanticDesc": "Vector search", + "hybrid": "Hybrid", + "hybridDesc": "Recent + Semantic", + "skillsTitle": "Skills", + "skillsDesc": "Tools for models", + "skillsEnabled": "Enable Skills", + "skillsEnabledDesc": "Allows agents to trigger functions.", + "skillsComingSoon": "Skills marketplace coming soon.", + "memorySkillsTitle": "Memory & Skills", + "memorySkillsDesc": "Persistent context & capabilities", + "days": "Days" }, "translator": { "title": "Kääntäjä", diff --git a/src/i18n/messages/fr.json b/src/i18n/messages/fr.json index b76871f3df..b37abe59b2 100644 --- a/src/i18n/messages/fr.json +++ b/src/i18n/messages/fr.json @@ -2164,7 +2164,46 @@ "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" + "themeCoral": "Corail", + "modelsDevTitle": "Model Database", + "modelsDevDesc": "Auto-sync pricing, capabilities & specs from models.dev", + "modelsDevEnabled": "Enable models.dev Sync", + "modelsDevEnabledDesc": "Fetch model pricing, capabilities, and specs from the open-source models.dev database", + "modelsDevInterval": "Sync Interval", + "syncNow": "Sync Now", + "syncing": "Syncing...", + "lastSync": "Last sync", + "never": "Never", + "modelsDevStats": "Sync Statistics", + "modelsDevStatsDesc": "Current models.dev data coverage", + "modelsWithPricing": "Models with Pricing", + "capabilities": "Capabilities", + "lastSyncCount": "Last Sync Count", + "lastSyncFull": "Last full sync", + "modelsDevInfo": "How It Works", + "modelsDevInfoDesc": "models.dev is an open-source database of AI model specifications maintained by the SST/OpenCode team. It provides pricing, capabilities, context limits, and modalities for 4,000+ models across 100+ providers.", + "modelsDevInfoResolution": "Pricing resolution order (highest priority first):", + "modelsDevInfoOrder": "User Override → models.dev → LiteLLM → Hardcoded Default", + "memoryTitle": "Memory", + "memoryDesc": "Persistent cross-session conversational memory", + "memoryEnabled": "Enable Memory", + "memoryEnabledDesc": "When enabled, OmniRoute will inject relevant past context.", + "maxTokens": "Max Tokens", + "retentionDays": "Retention", + "recent": "Recent", + "recentDesc": "Chronological window", + "semantic": "Semantic", + "semanticDesc": "Vector search", + "hybrid": "Hybrid", + "hybridDesc": "Recent + Semantic", + "skillsTitle": "Skills", + "skillsDesc": "Tools for models", + "skillsEnabled": "Enable Skills", + "skillsEnabledDesc": "Allows agents to trigger functions.", + "skillsComingSoon": "Skills marketplace coming soon.", + "memorySkillsTitle": "Memory & Skills", + "memorySkillsDesc": "Persistent context & capabilities", + "days": "Days" }, "translator": { "title": "Traducteur", diff --git a/src/i18n/messages/he.json b/src/i18n/messages/he.json index c8e7f98a44..2951d9ebbc 100644 --- a/src/i18n/messages/he.json +++ b/src/i18n/messages/he.json @@ -2164,7 +2164,46 @@ "customPricingNote": "אתה יכול לעקוף את תמחור ברירת המחדל עבור דגמים ספציפיים. עקיפות מותאמות אישית מקבלות עדיפות על פני תמחור שזוהה אוטומטית.", "editPricing": "ערוך תמחור", "viewFullDetails": "צפה בפרטים המלאים", - "themeCoral": "אלמוג" + "themeCoral": "אלמוג", + "modelsDevTitle": "Model Database", + "modelsDevDesc": "Auto-sync pricing, capabilities & specs from models.dev", + "modelsDevEnabled": "Enable models.dev Sync", + "modelsDevEnabledDesc": "Fetch model pricing, capabilities, and specs from the open-source models.dev database", + "modelsDevInterval": "Sync Interval", + "syncNow": "Sync Now", + "syncing": "Syncing...", + "lastSync": "Last sync", + "never": "Never", + "modelsDevStats": "Sync Statistics", + "modelsDevStatsDesc": "Current models.dev data coverage", + "modelsWithPricing": "Models with Pricing", + "capabilities": "Capabilities", + "lastSyncCount": "Last Sync Count", + "lastSyncFull": "Last full sync", + "modelsDevInfo": "How It Works", + "modelsDevInfoDesc": "models.dev is an open-source database of AI model specifications maintained by the SST/OpenCode team. It provides pricing, capabilities, context limits, and modalities for 4,000+ models across 100+ providers.", + "modelsDevInfoResolution": "Pricing resolution order (highest priority first):", + "modelsDevInfoOrder": "User Override → models.dev → LiteLLM → Hardcoded Default", + "memoryTitle": "Memory", + "memoryDesc": "Persistent cross-session conversational memory", + "memoryEnabled": "Enable Memory", + "memoryEnabledDesc": "When enabled, OmniRoute will inject relevant past context.", + "maxTokens": "Max Tokens", + "retentionDays": "Retention", + "recent": "Recent", + "recentDesc": "Chronological window", + "semantic": "Semantic", + "semanticDesc": "Vector search", + "hybrid": "Hybrid", + "hybridDesc": "Recent + Semantic", + "skillsTitle": "Skills", + "skillsDesc": "Tools for models", + "skillsEnabled": "Enable Skills", + "skillsEnabledDesc": "Allows agents to trigger functions.", + "skillsComingSoon": "Skills marketplace coming soon.", + "memorySkillsTitle": "Memory & Skills", + "memorySkillsDesc": "Persistent context & capabilities", + "days": "Days" }, "translator": { "title": "מתרגם", diff --git a/src/i18n/messages/hi.json b/src/i18n/messages/hi.json index d43b2a0815..5da00122b9 100644 --- a/src/i18n/messages/hi.json +++ b/src/i18n/messages/hi.json @@ -2164,7 +2164,46 @@ "customPricingNote": "आप विशिष्ट मॉडलों के लिए डिफ़ॉल्ट मूल्य निर्धारण को ओवरराइड कर सकते हैं। कस्टम ओवरराइड्स को स्वतः-पता लगाए गए मूल्य-निर्धारण पर प्राथमिकता दी जाती है।", "editPricing": "मूल्य निर्धारण संपादित करें", "viewFullDetails": "पूर्ण विवरण देखें", - "themeCoral": "कोरल" + "themeCoral": "कोरल", + "modelsDevTitle": "Model Database", + "modelsDevDesc": "Auto-sync pricing, capabilities & specs from models.dev", + "modelsDevEnabled": "Enable models.dev Sync", + "modelsDevEnabledDesc": "Fetch model pricing, capabilities, and specs from the open-source models.dev database", + "modelsDevInterval": "Sync Interval", + "syncNow": "Sync Now", + "syncing": "Syncing...", + "lastSync": "Last sync", + "never": "Never", + "modelsDevStats": "Sync Statistics", + "modelsDevStatsDesc": "Current models.dev data coverage", + "modelsWithPricing": "Models with Pricing", + "capabilities": "Capabilities", + "lastSyncCount": "Last Sync Count", + "lastSyncFull": "Last full sync", + "modelsDevInfo": "How It Works", + "modelsDevInfoDesc": "models.dev is an open-source database of AI model specifications maintained by the SST/OpenCode team. It provides pricing, capabilities, context limits, and modalities for 4,000+ models across 100+ providers.", + "modelsDevInfoResolution": "Pricing resolution order (highest priority first):", + "modelsDevInfoOrder": "User Override → models.dev → LiteLLM → Hardcoded Default", + "memoryTitle": "Memory", + "memoryDesc": "Persistent cross-session conversational memory", + "memoryEnabled": "Enable Memory", + "memoryEnabledDesc": "When enabled, OmniRoute will inject relevant past context.", + "maxTokens": "Max Tokens", + "retentionDays": "Retention", + "recent": "Recent", + "recentDesc": "Chronological window", + "semantic": "Semantic", + "semanticDesc": "Vector search", + "hybrid": "Hybrid", + "hybridDesc": "Recent + Semantic", + "skillsTitle": "Skills", + "skillsDesc": "Tools for models", + "skillsEnabled": "Enable Skills", + "skillsEnabledDesc": "Allows agents to trigger functions.", + "skillsComingSoon": "Skills marketplace coming soon.", + "memorySkillsTitle": "Memory & Skills", + "memorySkillsDesc": "Persistent context & capabilities", + "days": "Days" }, "translator": { "title": "अनुवादक", diff --git a/src/i18n/messages/hu.json b/src/i18n/messages/hu.json index 7b2260fafa..4549c87be7 100644 --- a/src/i18n/messages/hu.json +++ b/src/i18n/messages/hu.json @@ -2164,7 +2164,46 @@ "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" + "themeCoral": "Korall", + "modelsDevTitle": "Model Database", + "modelsDevDesc": "Auto-sync pricing, capabilities & specs from models.dev", + "modelsDevEnabled": "Enable models.dev Sync", + "modelsDevEnabledDesc": "Fetch model pricing, capabilities, and specs from the open-source models.dev database", + "modelsDevInterval": "Sync Interval", + "syncNow": "Sync Now", + "syncing": "Syncing...", + "lastSync": "Last sync", + "never": "Never", + "modelsDevStats": "Sync Statistics", + "modelsDevStatsDesc": "Current models.dev data coverage", + "modelsWithPricing": "Models with Pricing", + "capabilities": "Capabilities", + "lastSyncCount": "Last Sync Count", + "lastSyncFull": "Last full sync", + "modelsDevInfo": "How It Works", + "modelsDevInfoDesc": "models.dev is an open-source database of AI model specifications maintained by the SST/OpenCode team. It provides pricing, capabilities, context limits, and modalities for 4,000+ models across 100+ providers.", + "modelsDevInfoResolution": "Pricing resolution order (highest priority first):", + "modelsDevInfoOrder": "User Override → models.dev → LiteLLM → Hardcoded Default", + "memoryTitle": "Memory", + "memoryDesc": "Persistent cross-session conversational memory", + "memoryEnabled": "Enable Memory", + "memoryEnabledDesc": "When enabled, OmniRoute will inject relevant past context.", + "maxTokens": "Max Tokens", + "retentionDays": "Retention", + "recent": "Recent", + "recentDesc": "Chronological window", + "semantic": "Semantic", + "semanticDesc": "Vector search", + "hybrid": "Hybrid", + "hybridDesc": "Recent + Semantic", + "skillsTitle": "Skills", + "skillsDesc": "Tools for models", + "skillsEnabled": "Enable Skills", + "skillsEnabledDesc": "Allows agents to trigger functions.", + "skillsComingSoon": "Skills marketplace coming soon.", + "memorySkillsTitle": "Memory & Skills", + "memorySkillsDesc": "Persistent context & capabilities", + "days": "Days" }, "translator": { "title": "Fordító", diff --git a/src/i18n/messages/id.json b/src/i18n/messages/id.json index 610dea7e19..1afbebe040 100644 --- a/src/i18n/messages/id.json +++ b/src/i18n/messages/id.json @@ -2164,7 +2164,46 @@ "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" + "themeCoral": "Koral", + "modelsDevTitle": "Model Database", + "modelsDevDesc": "Auto-sync pricing, capabilities & specs from models.dev", + "modelsDevEnabled": "Enable models.dev Sync", + "modelsDevEnabledDesc": "Fetch model pricing, capabilities, and specs from the open-source models.dev database", + "modelsDevInterval": "Sync Interval", + "syncNow": "Sync Now", + "syncing": "Syncing...", + "lastSync": "Last sync", + "never": "Never", + "modelsDevStats": "Sync Statistics", + "modelsDevStatsDesc": "Current models.dev data coverage", + "modelsWithPricing": "Models with Pricing", + "capabilities": "Capabilities", + "lastSyncCount": "Last Sync Count", + "lastSyncFull": "Last full sync", + "modelsDevInfo": "How It Works", + "modelsDevInfoDesc": "models.dev is an open-source database of AI model specifications maintained by the SST/OpenCode team. It provides pricing, capabilities, context limits, and modalities for 4,000+ models across 100+ providers.", + "modelsDevInfoResolution": "Pricing resolution order (highest priority first):", + "modelsDevInfoOrder": "User Override → models.dev → LiteLLM → Hardcoded Default", + "memoryTitle": "Memory", + "memoryDesc": "Persistent cross-session conversational memory", + "memoryEnabled": "Enable Memory", + "memoryEnabledDesc": "When enabled, OmniRoute will inject relevant past context.", + "maxTokens": "Max Tokens", + "retentionDays": "Retention", + "recent": "Recent", + "recentDesc": "Chronological window", + "semantic": "Semantic", + "semanticDesc": "Vector search", + "hybrid": "Hybrid", + "hybridDesc": "Recent + Semantic", + "skillsTitle": "Skills", + "skillsDesc": "Tools for models", + "skillsEnabled": "Enable Skills", + "skillsEnabledDesc": "Allows agents to trigger functions.", + "skillsComingSoon": "Skills marketplace coming soon.", + "memorySkillsTitle": "Memory & Skills", + "memorySkillsDesc": "Persistent context & capabilities", + "days": "Days" }, "translator": { "title": "Penerjemah", diff --git a/src/i18n/messages/it.json b/src/i18n/messages/it.json index 9cbb72fb91..f26506e6f4 100644 --- a/src/i18n/messages/it.json +++ b/src/i18n/messages/it.json @@ -2164,7 +2164,46 @@ "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" + "themeCoral": "Corallo", + "modelsDevTitle": "Model Database", + "modelsDevDesc": "Auto-sync pricing, capabilities & specs from models.dev", + "modelsDevEnabled": "Enable models.dev Sync", + "modelsDevEnabledDesc": "Fetch model pricing, capabilities, and specs from the open-source models.dev database", + "modelsDevInterval": "Sync Interval", + "syncNow": "Sync Now", + "syncing": "Syncing...", + "lastSync": "Last sync", + "never": "Never", + "modelsDevStats": "Sync Statistics", + "modelsDevStatsDesc": "Current models.dev data coverage", + "modelsWithPricing": "Models with Pricing", + "capabilities": "Capabilities", + "lastSyncCount": "Last Sync Count", + "lastSyncFull": "Last full sync", + "modelsDevInfo": "How It Works", + "modelsDevInfoDesc": "models.dev is an open-source database of AI model specifications maintained by the SST/OpenCode team. It provides pricing, capabilities, context limits, and modalities for 4,000+ models across 100+ providers.", + "modelsDevInfoResolution": "Pricing resolution order (highest priority first):", + "modelsDevInfoOrder": "User Override → models.dev → LiteLLM → Hardcoded Default", + "memoryTitle": "Memory", + "memoryDesc": "Persistent cross-session conversational memory", + "memoryEnabled": "Enable Memory", + "memoryEnabledDesc": "When enabled, OmniRoute will inject relevant past context.", + "maxTokens": "Max Tokens", + "retentionDays": "Retention", + "recent": "Recent", + "recentDesc": "Chronological window", + "semantic": "Semantic", + "semanticDesc": "Vector search", + "hybrid": "Hybrid", + "hybridDesc": "Recent + Semantic", + "skillsTitle": "Skills", + "skillsDesc": "Tools for models", + "skillsEnabled": "Enable Skills", + "skillsEnabledDesc": "Allows agents to trigger functions.", + "skillsComingSoon": "Skills marketplace coming soon.", + "memorySkillsTitle": "Memory & Skills", + "memorySkillsDesc": "Persistent context & capabilities", + "days": "Days" }, "translator": { "title": "Traduttore", diff --git a/src/i18n/messages/ja.json b/src/i18n/messages/ja.json index e1bc2c5e80..daf4a46df4 100644 --- a/src/i18n/messages/ja.json +++ b/src/i18n/messages/ja.json @@ -2164,7 +2164,46 @@ "customPricingNote": "特定のモデルのデフォルトの価格をオーバーライドできます。カスタム オーバーライドは、自動検出された価格設定よりも優先されます。", "editPricing": "価格の編集", "viewFullDetails": "詳細を表示", - "themeCoral": "コーラル" + "themeCoral": "コーラル", + "modelsDevTitle": "Model Database", + "modelsDevDesc": "Auto-sync pricing, capabilities & specs from models.dev", + "modelsDevEnabled": "Enable models.dev Sync", + "modelsDevEnabledDesc": "Fetch model pricing, capabilities, and specs from the open-source models.dev database", + "modelsDevInterval": "Sync Interval", + "syncNow": "Sync Now", + "syncing": "Syncing...", + "lastSync": "Last sync", + "never": "Never", + "modelsDevStats": "Sync Statistics", + "modelsDevStatsDesc": "Current models.dev data coverage", + "modelsWithPricing": "Models with Pricing", + "capabilities": "Capabilities", + "lastSyncCount": "Last Sync Count", + "lastSyncFull": "Last full sync", + "modelsDevInfo": "How It Works", + "modelsDevInfoDesc": "models.dev is an open-source database of AI model specifications maintained by the SST/OpenCode team. It provides pricing, capabilities, context limits, and modalities for 4,000+ models across 100+ providers.", + "modelsDevInfoResolution": "Pricing resolution order (highest priority first):", + "modelsDevInfoOrder": "User Override → models.dev → LiteLLM → Hardcoded Default", + "memoryTitle": "Memory", + "memoryDesc": "Persistent cross-session conversational memory", + "memoryEnabled": "Enable Memory", + "memoryEnabledDesc": "When enabled, OmniRoute will inject relevant past context.", + "maxTokens": "Max Tokens", + "retentionDays": "Retention", + "recent": "Recent", + "recentDesc": "Chronological window", + "semantic": "Semantic", + "semanticDesc": "Vector search", + "hybrid": "Hybrid", + "hybridDesc": "Recent + Semantic", + "skillsTitle": "Skills", + "skillsDesc": "Tools for models", + "skillsEnabled": "Enable Skills", + "skillsEnabledDesc": "Allows agents to trigger functions.", + "skillsComingSoon": "Skills marketplace coming soon.", + "memorySkillsTitle": "Memory & Skills", + "memorySkillsDesc": "Persistent context & capabilities", + "days": "Days" }, "translator": { "title": "翻訳者", diff --git a/src/i18n/messages/ko.json b/src/i18n/messages/ko.json index 4ff553da45..2db87f5453 100644 --- a/src/i18n/messages/ko.json +++ b/src/i18n/messages/ko.json @@ -2164,7 +2164,46 @@ "customPricingNote": "특정 모델의 기본 가격을 재정의할 수 있습니다. 맞춤 재정의는 자동 감지된 가격보다 우선 적용됩니다.", "editPricing": "가격 편집", "viewFullDetails": "전체 세부정보 보기", - "themeCoral": "코랄" + "themeCoral": "코랄", + "modelsDevTitle": "Model Database", + "modelsDevDesc": "Auto-sync pricing, capabilities & specs from models.dev", + "modelsDevEnabled": "Enable models.dev Sync", + "modelsDevEnabledDesc": "Fetch model pricing, capabilities, and specs from the open-source models.dev database", + "modelsDevInterval": "Sync Interval", + "syncNow": "Sync Now", + "syncing": "Syncing...", + "lastSync": "Last sync", + "never": "Never", + "modelsDevStats": "Sync Statistics", + "modelsDevStatsDesc": "Current models.dev data coverage", + "modelsWithPricing": "Models with Pricing", + "capabilities": "Capabilities", + "lastSyncCount": "Last Sync Count", + "lastSyncFull": "Last full sync", + "modelsDevInfo": "How It Works", + "modelsDevInfoDesc": "models.dev is an open-source database of AI model specifications maintained by the SST/OpenCode team. It provides pricing, capabilities, context limits, and modalities for 4,000+ models across 100+ providers.", + "modelsDevInfoResolution": "Pricing resolution order (highest priority first):", + "modelsDevInfoOrder": "User Override → models.dev → LiteLLM → Hardcoded Default", + "memoryTitle": "Memory", + "memoryDesc": "Persistent cross-session conversational memory", + "memoryEnabled": "Enable Memory", + "memoryEnabledDesc": "When enabled, OmniRoute will inject relevant past context.", + "maxTokens": "Max Tokens", + "retentionDays": "Retention", + "recent": "Recent", + "recentDesc": "Chronological window", + "semantic": "Semantic", + "semanticDesc": "Vector search", + "hybrid": "Hybrid", + "hybridDesc": "Recent + Semantic", + "skillsTitle": "Skills", + "skillsDesc": "Tools for models", + "skillsEnabled": "Enable Skills", + "skillsEnabledDesc": "Allows agents to trigger functions.", + "skillsComingSoon": "Skills marketplace coming soon.", + "memorySkillsTitle": "Memory & Skills", + "memorySkillsDesc": "Persistent context & capabilities", + "days": "Days" }, "translator": { "title": "번역기", diff --git a/src/i18n/messages/ms.json b/src/i18n/messages/ms.json index c3f708e6c9..3096e2e9b7 100644 --- a/src/i18n/messages/ms.json +++ b/src/i18n/messages/ms.json @@ -2164,7 +2164,46 @@ "customPricingNote": "Anda boleh mengatasi harga lalai untuk model tertentu. Penggantian tersuai diutamakan berbanding harga yang dikesan secara automatik.", "editPricing": "Edit Harga", "viewFullDetails": "Lihat Butiran Penuh", - "themeCoral": "Koral" + "themeCoral": "Koral", + "modelsDevTitle": "Model Database", + "modelsDevDesc": "Auto-sync pricing, capabilities & specs from models.dev", + "modelsDevEnabled": "Enable models.dev Sync", + "modelsDevEnabledDesc": "Fetch model pricing, capabilities, and specs from the open-source models.dev database", + "modelsDevInterval": "Sync Interval", + "syncNow": "Sync Now", + "syncing": "Syncing...", + "lastSync": "Last sync", + "never": "Never", + "modelsDevStats": "Sync Statistics", + "modelsDevStatsDesc": "Current models.dev data coverage", + "modelsWithPricing": "Models with Pricing", + "capabilities": "Capabilities", + "lastSyncCount": "Last Sync Count", + "lastSyncFull": "Last full sync", + "modelsDevInfo": "How It Works", + "modelsDevInfoDesc": "models.dev is an open-source database of AI model specifications maintained by the SST/OpenCode team. It provides pricing, capabilities, context limits, and modalities for 4,000+ models across 100+ providers.", + "modelsDevInfoResolution": "Pricing resolution order (highest priority first):", + "modelsDevInfoOrder": "User Override → models.dev → LiteLLM → Hardcoded Default", + "memoryTitle": "Memory", + "memoryDesc": "Persistent cross-session conversational memory", + "memoryEnabled": "Enable Memory", + "memoryEnabledDesc": "When enabled, OmniRoute will inject relevant past context.", + "maxTokens": "Max Tokens", + "retentionDays": "Retention", + "recent": "Recent", + "recentDesc": "Chronological window", + "semantic": "Semantic", + "semanticDesc": "Vector search", + "hybrid": "Hybrid", + "hybridDesc": "Recent + Semantic", + "skillsTitle": "Skills", + "skillsDesc": "Tools for models", + "skillsEnabled": "Enable Skills", + "skillsEnabledDesc": "Allows agents to trigger functions.", + "skillsComingSoon": "Skills marketplace coming soon.", + "memorySkillsTitle": "Memory & Skills", + "memorySkillsDesc": "Persistent context & capabilities", + "days": "Days" }, "translator": { "title": "Penterjemah", diff --git a/src/i18n/messages/nl.json b/src/i18n/messages/nl.json index 43e10f4b8e..a5171b25c9 100644 --- a/src/i18n/messages/nl.json +++ b/src/i18n/messages/nl.json @@ -2164,7 +2164,46 @@ "customPricingNote": "U kunt de standaardprijzen voor specifieke modellen overschrijven. Aangepaste overschrijvingen hebben voorrang op automatisch gedetecteerde prijzen.", "editPricing": "Prijzen bewerken", "viewFullDetails": "Bekijk volledige details", - "themeCoral": "Koraal" + "themeCoral": "Koraal", + "modelsDevTitle": "Model Database", + "modelsDevDesc": "Auto-sync pricing, capabilities & specs from models.dev", + "modelsDevEnabled": "Enable models.dev Sync", + "modelsDevEnabledDesc": "Fetch model pricing, capabilities, and specs from the open-source models.dev database", + "modelsDevInterval": "Sync Interval", + "syncNow": "Sync Now", + "syncing": "Syncing...", + "lastSync": "Last sync", + "never": "Never", + "modelsDevStats": "Sync Statistics", + "modelsDevStatsDesc": "Current models.dev data coverage", + "modelsWithPricing": "Models with Pricing", + "capabilities": "Capabilities", + "lastSyncCount": "Last Sync Count", + "lastSyncFull": "Last full sync", + "modelsDevInfo": "How It Works", + "modelsDevInfoDesc": "models.dev is an open-source database of AI model specifications maintained by the SST/OpenCode team. It provides pricing, capabilities, context limits, and modalities for 4,000+ models across 100+ providers.", + "modelsDevInfoResolution": "Pricing resolution order (highest priority first):", + "modelsDevInfoOrder": "User Override → models.dev → LiteLLM → Hardcoded Default", + "memoryTitle": "Memory", + "memoryDesc": "Persistent cross-session conversational memory", + "memoryEnabled": "Enable Memory", + "memoryEnabledDesc": "When enabled, OmniRoute will inject relevant past context.", + "maxTokens": "Max Tokens", + "retentionDays": "Retention", + "recent": "Recent", + "recentDesc": "Chronological window", + "semantic": "Semantic", + "semanticDesc": "Vector search", + "hybrid": "Hybrid", + "hybridDesc": "Recent + Semantic", + "skillsTitle": "Skills", + "skillsDesc": "Tools for models", + "skillsEnabled": "Enable Skills", + "skillsEnabledDesc": "Allows agents to trigger functions.", + "skillsComingSoon": "Skills marketplace coming soon.", + "memorySkillsTitle": "Memory & Skills", + "memorySkillsDesc": "Persistent context & capabilities", + "days": "Days" }, "translator": { "title": "Vertaler", diff --git a/src/i18n/messages/no.json b/src/i18n/messages/no.json index f72dcb12ee..5a37804933 100644 --- a/src/i18n/messages/no.json +++ b/src/i18n/messages/no.json @@ -2164,7 +2164,46 @@ "customPricingNote": "Du kan overstyre standardpriser for spesifikke modeller. Egendefinerte overstyringer prioriteres fremfor automatisk oppdagede priser.", "editPricing": "Rediger priser", "viewFullDetails": "Se alle detaljer", - "themeCoral": "Korall" + "themeCoral": "Korall", + "modelsDevTitle": "Model Database", + "modelsDevDesc": "Auto-sync pricing, capabilities & specs from models.dev", + "modelsDevEnabled": "Enable models.dev Sync", + "modelsDevEnabledDesc": "Fetch model pricing, capabilities, and specs from the open-source models.dev database", + "modelsDevInterval": "Sync Interval", + "syncNow": "Sync Now", + "syncing": "Syncing...", + "lastSync": "Last sync", + "never": "Never", + "modelsDevStats": "Sync Statistics", + "modelsDevStatsDesc": "Current models.dev data coverage", + "modelsWithPricing": "Models with Pricing", + "capabilities": "Capabilities", + "lastSyncCount": "Last Sync Count", + "lastSyncFull": "Last full sync", + "modelsDevInfo": "How It Works", + "modelsDevInfoDesc": "models.dev is an open-source database of AI model specifications maintained by the SST/OpenCode team. It provides pricing, capabilities, context limits, and modalities for 4,000+ models across 100+ providers.", + "modelsDevInfoResolution": "Pricing resolution order (highest priority first):", + "modelsDevInfoOrder": "User Override → models.dev → LiteLLM → Hardcoded Default", + "memoryTitle": "Memory", + "memoryDesc": "Persistent cross-session conversational memory", + "memoryEnabled": "Enable Memory", + "memoryEnabledDesc": "When enabled, OmniRoute will inject relevant past context.", + "maxTokens": "Max Tokens", + "retentionDays": "Retention", + "recent": "Recent", + "recentDesc": "Chronological window", + "semantic": "Semantic", + "semanticDesc": "Vector search", + "hybrid": "Hybrid", + "hybridDesc": "Recent + Semantic", + "skillsTitle": "Skills", + "skillsDesc": "Tools for models", + "skillsEnabled": "Enable Skills", + "skillsEnabledDesc": "Allows agents to trigger functions.", + "skillsComingSoon": "Skills marketplace coming soon.", + "memorySkillsTitle": "Memory & Skills", + "memorySkillsDesc": "Persistent context & capabilities", + "days": "Days" }, "translator": { "title": "Oversetter", diff --git a/src/i18n/messages/phi.json b/src/i18n/messages/phi.json index e401e40ae6..3757198ac6 100644 --- a/src/i18n/messages/phi.json +++ b/src/i18n/messages/phi.json @@ -2164,7 +2164,46 @@ "customPricingNote": "Maaari mong i-override ang default na pagpepresyo para sa mga partikular na modelo. Mas inuuna ang mga custom na override kaysa sa awtomatikong natukoy na pagpepresyo.", "editPricing": "I-edit ang Pagpepresyo", "viewFullDetails": "Tingnan ang Buong Detalye", - "themeCoral": "Coral" + "themeCoral": "Coral", + "modelsDevTitle": "Model Database", + "modelsDevDesc": "Auto-sync pricing, capabilities & specs from models.dev", + "modelsDevEnabled": "Enable models.dev Sync", + "modelsDevEnabledDesc": "Fetch model pricing, capabilities, and specs from the open-source models.dev database", + "modelsDevInterval": "Sync Interval", + "syncNow": "Sync Now", + "syncing": "Syncing...", + "lastSync": "Last sync", + "never": "Never", + "modelsDevStats": "Sync Statistics", + "modelsDevStatsDesc": "Current models.dev data coverage", + "modelsWithPricing": "Models with Pricing", + "capabilities": "Capabilities", + "lastSyncCount": "Last Sync Count", + "lastSyncFull": "Last full sync", + "modelsDevInfo": "How It Works", + "modelsDevInfoDesc": "models.dev is an open-source database of AI model specifications maintained by the SST/OpenCode team. It provides pricing, capabilities, context limits, and modalities for 4,000+ models across 100+ providers.", + "modelsDevInfoResolution": "Pricing resolution order (highest priority first):", + "modelsDevInfoOrder": "User Override → models.dev → LiteLLM → Hardcoded Default", + "memoryTitle": "Memory", + "memoryDesc": "Persistent cross-session conversational memory", + "memoryEnabled": "Enable Memory", + "memoryEnabledDesc": "When enabled, OmniRoute will inject relevant past context.", + "maxTokens": "Max Tokens", + "retentionDays": "Retention", + "recent": "Recent", + "recentDesc": "Chronological window", + "semantic": "Semantic", + "semanticDesc": "Vector search", + "hybrid": "Hybrid", + "hybridDesc": "Recent + Semantic", + "skillsTitle": "Skills", + "skillsDesc": "Tools for models", + "skillsEnabled": "Enable Skills", + "skillsEnabledDesc": "Allows agents to trigger functions.", + "skillsComingSoon": "Skills marketplace coming soon.", + "memorySkillsTitle": "Memory & Skills", + "memorySkillsDesc": "Persistent context & capabilities", + "days": "Days" }, "translator": { "title": "Tagasalin", diff --git a/src/i18n/messages/pl.json b/src/i18n/messages/pl.json index 560cb82ce6..928c75d705 100644 --- a/src/i18n/messages/pl.json +++ b/src/i18n/messages/pl.json @@ -2164,7 +2164,46 @@ "customPricingNote": "Możesz zastąpić domyślne ceny dla określonych modeli. Zastąpienia niestandardowe mają pierwszeństwo przed automatycznie wykrytymi cenami.", "editPricing": "Edytuj ceny", "viewFullDetails": "Zobacz pełne szczegóły", - "themeCoral": "Koral" + "themeCoral": "Koral", + "modelsDevTitle": "Model Database", + "modelsDevDesc": "Auto-sync pricing, capabilities & specs from models.dev", + "modelsDevEnabled": "Enable models.dev Sync", + "modelsDevEnabledDesc": "Fetch model pricing, capabilities, and specs from the open-source models.dev database", + "modelsDevInterval": "Sync Interval", + "syncNow": "Sync Now", + "syncing": "Syncing...", + "lastSync": "Last sync", + "never": "Never", + "modelsDevStats": "Sync Statistics", + "modelsDevStatsDesc": "Current models.dev data coverage", + "modelsWithPricing": "Models with Pricing", + "capabilities": "Capabilities", + "lastSyncCount": "Last Sync Count", + "lastSyncFull": "Last full sync", + "modelsDevInfo": "How It Works", + "modelsDevInfoDesc": "models.dev is an open-source database of AI model specifications maintained by the SST/OpenCode team. It provides pricing, capabilities, context limits, and modalities for 4,000+ models across 100+ providers.", + "modelsDevInfoResolution": "Pricing resolution order (highest priority first):", + "modelsDevInfoOrder": "User Override → models.dev → LiteLLM → Hardcoded Default", + "memoryTitle": "Memory", + "memoryDesc": "Persistent cross-session conversational memory", + "memoryEnabled": "Enable Memory", + "memoryEnabledDesc": "When enabled, OmniRoute will inject relevant past context.", + "maxTokens": "Max Tokens", + "retentionDays": "Retention", + "recent": "Recent", + "recentDesc": "Chronological window", + "semantic": "Semantic", + "semanticDesc": "Vector search", + "hybrid": "Hybrid", + "hybridDesc": "Recent + Semantic", + "skillsTitle": "Skills", + "skillsDesc": "Tools for models", + "skillsEnabled": "Enable Skills", + "skillsEnabledDesc": "Allows agents to trigger functions.", + "skillsComingSoon": "Skills marketplace coming soon.", + "memorySkillsTitle": "Memory & Skills", + "memorySkillsDesc": "Persistent context & capabilities", + "days": "Days" }, "translator": { "title": "Tłumacz", diff --git a/src/i18n/messages/pt-BR.json b/src/i18n/messages/pt-BR.json index 3c86ae94ad..86b1d38249 100644 --- a/src/i18n/messages/pt-BR.json +++ b/src/i18n/messages/pt-BR.json @@ -2250,7 +2250,26 @@ "memorySkillsTitle": "Memória e Skills", "memorySkillsDesc": "Contexto persistente e capacidades A2A", "purgeExpiredLogs": "Purgar Logs Expirados", - "purgeLogsFailed": "Falha ao purgar logs" + "purgeLogsFailed": "Falha ao purgar logs", + "modelsDevTitle": "Model Database", + "modelsDevDesc": "Auto-sync pricing, capabilities & specs from models.dev", + "modelsDevEnabled": "Enable models.dev Sync", + "modelsDevEnabledDesc": "Fetch model pricing, capabilities, and specs from the open-source models.dev database", + "modelsDevInterval": "Sync Interval", + "syncNow": "Sync Now", + "syncing": "Syncing...", + "lastSync": "Last sync", + "never": "Never", + "modelsDevStats": "Sync Statistics", + "modelsDevStatsDesc": "Current models.dev data coverage", + "modelsWithPricing": "Models with Pricing", + "capabilities": "Capabilities", + "lastSyncCount": "Last Sync Count", + "lastSyncFull": "Last full sync", + "modelsDevInfo": "How It Works", + "modelsDevInfoDesc": "models.dev is an open-source database of AI model specifications maintained by the SST/OpenCode team. It provides pricing, capabilities, context limits, and modalities for 4,000+ models across 100+ providers.", + "modelsDevInfoResolution": "Pricing resolution order (highest priority first):", + "modelsDevInfoOrder": "User Override → models.dev → LiteLLM → Hardcoded Default" }, "translator": { "title": "Tradutor", diff --git a/src/i18n/messages/pt.json b/src/i18n/messages/pt.json index 4ccbf82e4e..1274a8f333 100644 --- a/src/i18n/messages/pt.json +++ b/src/i18n/messages/pt.json @@ -2250,7 +2250,26 @@ "memorySkillsTitle": "Memória e Skills", "memorySkillsDesc": "Contexto persistente e capacidades A2A", "purgeExpiredLogs": "Purgar Logs Expirados", - "purgeLogsFailed": "Falha ao purgar logs" + "purgeLogsFailed": "Falha ao purgar logs", + "modelsDevTitle": "Model Database", + "modelsDevDesc": "Auto-sync pricing, capabilities & specs from models.dev", + "modelsDevEnabled": "Enable models.dev Sync", + "modelsDevEnabledDesc": "Fetch model pricing, capabilities, and specs from the open-source models.dev database", + "modelsDevInterval": "Sync Interval", + "syncNow": "Sync Now", + "syncing": "Syncing...", + "lastSync": "Last sync", + "never": "Never", + "modelsDevStats": "Sync Statistics", + "modelsDevStatsDesc": "Current models.dev data coverage", + "modelsWithPricing": "Models with Pricing", + "capabilities": "Capabilities", + "lastSyncCount": "Last Sync Count", + "lastSyncFull": "Last full sync", + "modelsDevInfo": "How It Works", + "modelsDevInfoDesc": "models.dev is an open-source database of AI model specifications maintained by the SST/OpenCode team. It provides pricing, capabilities, context limits, and modalities for 4,000+ models across 100+ providers.", + "modelsDevInfoResolution": "Pricing resolution order (highest priority first):", + "modelsDevInfoOrder": "User Override → models.dev → LiteLLM → Hardcoded Default" }, "translator": { "title": "Tradutor", diff --git a/src/i18n/messages/ro.json b/src/i18n/messages/ro.json index 4005b2e66b..c81ebc39f4 100644 --- a/src/i18n/messages/ro.json +++ b/src/i18n/messages/ro.json @@ -2164,7 +2164,46 @@ "customPricingNote": "Puteți suprascrie prețurile implicite pentru anumite modele. Anulările personalizate au prioritate față de prețurile detectate automat.", "editPricing": "Editați prețul", "viewFullDetails": "Vezi detalii complete", - "themeCoral": "Coral" + "themeCoral": "Coral", + "modelsDevTitle": "Model Database", + "modelsDevDesc": "Auto-sync pricing, capabilities & specs from models.dev", + "modelsDevEnabled": "Enable models.dev Sync", + "modelsDevEnabledDesc": "Fetch model pricing, capabilities, and specs from the open-source models.dev database", + "modelsDevInterval": "Sync Interval", + "syncNow": "Sync Now", + "syncing": "Syncing...", + "lastSync": "Last sync", + "never": "Never", + "modelsDevStats": "Sync Statistics", + "modelsDevStatsDesc": "Current models.dev data coverage", + "modelsWithPricing": "Models with Pricing", + "capabilities": "Capabilities", + "lastSyncCount": "Last Sync Count", + "lastSyncFull": "Last full sync", + "modelsDevInfo": "How It Works", + "modelsDevInfoDesc": "models.dev is an open-source database of AI model specifications maintained by the SST/OpenCode team. It provides pricing, capabilities, context limits, and modalities for 4,000+ models across 100+ providers.", + "modelsDevInfoResolution": "Pricing resolution order (highest priority first):", + "modelsDevInfoOrder": "User Override → models.dev → LiteLLM → Hardcoded Default", + "memoryTitle": "Memory", + "memoryDesc": "Persistent cross-session conversational memory", + "memoryEnabled": "Enable Memory", + "memoryEnabledDesc": "When enabled, OmniRoute will inject relevant past context.", + "maxTokens": "Max Tokens", + "retentionDays": "Retention", + "recent": "Recent", + "recentDesc": "Chronological window", + "semantic": "Semantic", + "semanticDesc": "Vector search", + "hybrid": "Hybrid", + "hybridDesc": "Recent + Semantic", + "skillsTitle": "Skills", + "skillsDesc": "Tools for models", + "skillsEnabled": "Enable Skills", + "skillsEnabledDesc": "Allows agents to trigger functions.", + "skillsComingSoon": "Skills marketplace coming soon.", + "memorySkillsTitle": "Memory & Skills", + "memorySkillsDesc": "Persistent context & capabilities", + "days": "Days" }, "translator": { "title": "Traducător", diff --git a/src/i18n/messages/ru.json b/src/i18n/messages/ru.json index 5ef51f9352..6f3fa7156f 100644 --- a/src/i18n/messages/ru.json +++ b/src/i18n/messages/ru.json @@ -2164,7 +2164,46 @@ "customPricingNote": "Вы можете переопределить цены по умолчанию для определенных моделей. Пользовательские переопределения имеют приоритет над ценами, определяемыми автоматически.", "editPricing": "Изменить цену", "viewFullDetails": "Посмотреть полную информацию", - "themeCoral": "Коралл" + "themeCoral": "Коралл", + "modelsDevTitle": "Model Database", + "modelsDevDesc": "Auto-sync pricing, capabilities & specs from models.dev", + "modelsDevEnabled": "Enable models.dev Sync", + "modelsDevEnabledDesc": "Fetch model pricing, capabilities, and specs from the open-source models.dev database", + "modelsDevInterval": "Sync Interval", + "syncNow": "Sync Now", + "syncing": "Syncing...", + "lastSync": "Last sync", + "never": "Never", + "modelsDevStats": "Sync Statistics", + "modelsDevStatsDesc": "Current models.dev data coverage", + "modelsWithPricing": "Models with Pricing", + "capabilities": "Capabilities", + "lastSyncCount": "Last Sync Count", + "lastSyncFull": "Last full sync", + "modelsDevInfo": "How It Works", + "modelsDevInfoDesc": "models.dev is an open-source database of AI model specifications maintained by the SST/OpenCode team. It provides pricing, capabilities, context limits, and modalities for 4,000+ models across 100+ providers.", + "modelsDevInfoResolution": "Pricing resolution order (highest priority first):", + "modelsDevInfoOrder": "User Override → models.dev → LiteLLM → Hardcoded Default", + "memoryTitle": "Memory", + "memoryDesc": "Persistent cross-session conversational memory", + "memoryEnabled": "Enable Memory", + "memoryEnabledDesc": "When enabled, OmniRoute will inject relevant past context.", + "maxTokens": "Max Tokens", + "retentionDays": "Retention", + "recent": "Recent", + "recentDesc": "Chronological window", + "semantic": "Semantic", + "semanticDesc": "Vector search", + "hybrid": "Hybrid", + "hybridDesc": "Recent + Semantic", + "skillsTitle": "Skills", + "skillsDesc": "Tools for models", + "skillsEnabled": "Enable Skills", + "skillsEnabledDesc": "Allows agents to trigger functions.", + "skillsComingSoon": "Skills marketplace coming soon.", + "memorySkillsTitle": "Memory & Skills", + "memorySkillsDesc": "Persistent context & capabilities", + "days": "Days" }, "translator": { "title": "Переводчик", diff --git a/src/i18n/messages/sk.json b/src/i18n/messages/sk.json index f7b03fc73e..7c4d62951f 100644 --- a/src/i18n/messages/sk.json +++ b/src/i18n/messages/sk.json @@ -2164,7 +2164,46 @@ "customPricingNote": "Predvolené ceny pre konkrétne modely môžete prepísať. Vlastné prepísania majú prednosť pred automaticky zistenými cenami.", "editPricing": "Upraviť ceny", "viewFullDetails": "Zobraziť úplné podrobnosti", - "themeCoral": "Korál" + "themeCoral": "Korál", + "modelsDevTitle": "Model Database", + "modelsDevDesc": "Auto-sync pricing, capabilities & specs from models.dev", + "modelsDevEnabled": "Enable models.dev Sync", + "modelsDevEnabledDesc": "Fetch model pricing, capabilities, and specs from the open-source models.dev database", + "modelsDevInterval": "Sync Interval", + "syncNow": "Sync Now", + "syncing": "Syncing...", + "lastSync": "Last sync", + "never": "Never", + "modelsDevStats": "Sync Statistics", + "modelsDevStatsDesc": "Current models.dev data coverage", + "modelsWithPricing": "Models with Pricing", + "capabilities": "Capabilities", + "lastSyncCount": "Last Sync Count", + "lastSyncFull": "Last full sync", + "modelsDevInfo": "How It Works", + "modelsDevInfoDesc": "models.dev is an open-source database of AI model specifications maintained by the SST/OpenCode team. It provides pricing, capabilities, context limits, and modalities for 4,000+ models across 100+ providers.", + "modelsDevInfoResolution": "Pricing resolution order (highest priority first):", + "modelsDevInfoOrder": "User Override → models.dev → LiteLLM → Hardcoded Default", + "memoryTitle": "Memory", + "memoryDesc": "Persistent cross-session conversational memory", + "memoryEnabled": "Enable Memory", + "memoryEnabledDesc": "When enabled, OmniRoute will inject relevant past context.", + "maxTokens": "Max Tokens", + "retentionDays": "Retention", + "recent": "Recent", + "recentDesc": "Chronological window", + "semantic": "Semantic", + "semanticDesc": "Vector search", + "hybrid": "Hybrid", + "hybridDesc": "Recent + Semantic", + "skillsTitle": "Skills", + "skillsDesc": "Tools for models", + "skillsEnabled": "Enable Skills", + "skillsEnabledDesc": "Allows agents to trigger functions.", + "skillsComingSoon": "Skills marketplace coming soon.", + "memorySkillsTitle": "Memory & Skills", + "memorySkillsDesc": "Persistent context & capabilities", + "days": "Days" }, "translator": { "title": "Prekladateľ", diff --git a/src/i18n/messages/sv.json b/src/i18n/messages/sv.json index 862a63bc74..b893eaed1b 100644 --- a/src/i18n/messages/sv.json +++ b/src/i18n/messages/sv.json @@ -2164,7 +2164,46 @@ "customPricingNote": "Du kan åsidosätta standardpriser för specifika modeller. Anpassade åsidosättningar har prioritet framför automatiskt identifierade priser.", "editPricing": "Redigera prissättning", "viewFullDetails": "Visa fullständiga detaljer", - "themeCoral": "Korall" + "themeCoral": "Korall", + "modelsDevTitle": "Model Database", + "modelsDevDesc": "Auto-sync pricing, capabilities & specs from models.dev", + "modelsDevEnabled": "Enable models.dev Sync", + "modelsDevEnabledDesc": "Fetch model pricing, capabilities, and specs from the open-source models.dev database", + "modelsDevInterval": "Sync Interval", + "syncNow": "Sync Now", + "syncing": "Syncing...", + "lastSync": "Last sync", + "never": "Never", + "modelsDevStats": "Sync Statistics", + "modelsDevStatsDesc": "Current models.dev data coverage", + "modelsWithPricing": "Models with Pricing", + "capabilities": "Capabilities", + "lastSyncCount": "Last Sync Count", + "lastSyncFull": "Last full sync", + "modelsDevInfo": "How It Works", + "modelsDevInfoDesc": "models.dev is an open-source database of AI model specifications maintained by the SST/OpenCode team. It provides pricing, capabilities, context limits, and modalities for 4,000+ models across 100+ providers.", + "modelsDevInfoResolution": "Pricing resolution order (highest priority first):", + "modelsDevInfoOrder": "User Override → models.dev → LiteLLM → Hardcoded Default", + "memoryTitle": "Memory", + "memoryDesc": "Persistent cross-session conversational memory", + "memoryEnabled": "Enable Memory", + "memoryEnabledDesc": "When enabled, OmniRoute will inject relevant past context.", + "maxTokens": "Max Tokens", + "retentionDays": "Retention", + "recent": "Recent", + "recentDesc": "Chronological window", + "semantic": "Semantic", + "semanticDesc": "Vector search", + "hybrid": "Hybrid", + "hybridDesc": "Recent + Semantic", + "skillsTitle": "Skills", + "skillsDesc": "Tools for models", + "skillsEnabled": "Enable Skills", + "skillsEnabledDesc": "Allows agents to trigger functions.", + "skillsComingSoon": "Skills marketplace coming soon.", + "memorySkillsTitle": "Memory & Skills", + "memorySkillsDesc": "Persistent context & capabilities", + "days": "Days" }, "translator": { "title": "Översättare", diff --git a/src/i18n/messages/th.json b/src/i18n/messages/th.json index 6212e8b5c6..4cd7d51e28 100644 --- a/src/i18n/messages/th.json +++ b/src/i18n/messages/th.json @@ -2164,7 +2164,46 @@ "customPricingNote": "คุณสามารถแทนที่ราคาเริ่มต้นสำหรับรุ่นเฉพาะได้ การแทนที่แบบกำหนดเองจะมีลำดับความสำคัญมากกว่าการกำหนดราคาที่ตรวจพบอัตโนมัติ", "editPricing": "แก้ไขราคา", "viewFullDetails": "ดูรายละเอียดทั้งหมด", - "themeCoral": "คอรัล" + "themeCoral": "คอรัล", + "modelsDevTitle": "Model Database", + "modelsDevDesc": "Auto-sync pricing, capabilities & specs from models.dev", + "modelsDevEnabled": "Enable models.dev Sync", + "modelsDevEnabledDesc": "Fetch model pricing, capabilities, and specs from the open-source models.dev database", + "modelsDevInterval": "Sync Interval", + "syncNow": "Sync Now", + "syncing": "Syncing...", + "lastSync": "Last sync", + "never": "Never", + "modelsDevStats": "Sync Statistics", + "modelsDevStatsDesc": "Current models.dev data coverage", + "modelsWithPricing": "Models with Pricing", + "capabilities": "Capabilities", + "lastSyncCount": "Last Sync Count", + "lastSyncFull": "Last full sync", + "modelsDevInfo": "How It Works", + "modelsDevInfoDesc": "models.dev is an open-source database of AI model specifications maintained by the SST/OpenCode team. It provides pricing, capabilities, context limits, and modalities for 4,000+ models across 100+ providers.", + "modelsDevInfoResolution": "Pricing resolution order (highest priority first):", + "modelsDevInfoOrder": "User Override → models.dev → LiteLLM → Hardcoded Default", + "memoryTitle": "Memory", + "memoryDesc": "Persistent cross-session conversational memory", + "memoryEnabled": "Enable Memory", + "memoryEnabledDesc": "When enabled, OmniRoute will inject relevant past context.", + "maxTokens": "Max Tokens", + "retentionDays": "Retention", + "recent": "Recent", + "recentDesc": "Chronological window", + "semantic": "Semantic", + "semanticDesc": "Vector search", + "hybrid": "Hybrid", + "hybridDesc": "Recent + Semantic", + "skillsTitle": "Skills", + "skillsDesc": "Tools for models", + "skillsEnabled": "Enable Skills", + "skillsEnabledDesc": "Allows agents to trigger functions.", + "skillsComingSoon": "Skills marketplace coming soon.", + "memorySkillsTitle": "Memory & Skills", + "memorySkillsDesc": "Persistent context & capabilities", + "days": "Days" }, "translator": { "title": "นักแปล", diff --git a/src/i18n/messages/tr.json b/src/i18n/messages/tr.json index c0b5c31f7a..a29ea87753 100644 --- a/src/i18n/messages/tr.json +++ b/src/i18n/messages/tr.json @@ -2164,7 +2164,46 @@ "customPricingNote": "Belirli modeller için varsayılan fiyatlandırmayı geçersiz kılabilirsiniz. Özel geçersiz kılmalar, otomatik algılanan fiyatlandırmaya göre öncelik kazanır.", "editPricing": "Fiyatlandırmayı Düzenle", "viewFullDetails": "Tüm Ayrıntıları Görüntüle", - "themeCoral": "Mercan" + "themeCoral": "Mercan", + "modelsDevTitle": "Model Database", + "modelsDevDesc": "Auto-sync pricing, capabilities & specs from models.dev", + "modelsDevEnabled": "Enable models.dev Sync", + "modelsDevEnabledDesc": "Fetch model pricing, capabilities, and specs from the open-source models.dev database", + "modelsDevInterval": "Sync Interval", + "syncNow": "Sync Now", + "syncing": "Syncing...", + "lastSync": "Last sync", + "never": "Never", + "modelsDevStats": "Sync Statistics", + "modelsDevStatsDesc": "Current models.dev data coverage", + "modelsWithPricing": "Models with Pricing", + "capabilities": "Capabilities", + "lastSyncCount": "Last Sync Count", + "lastSyncFull": "Last full sync", + "modelsDevInfo": "How It Works", + "modelsDevInfoDesc": "models.dev is an open-source database of AI model specifications maintained by the SST/OpenCode team. It provides pricing, capabilities, context limits, and modalities for 4,000+ models across 100+ providers.", + "modelsDevInfoResolution": "Pricing resolution order (highest priority first):", + "modelsDevInfoOrder": "User Override → models.dev → LiteLLM → Hardcoded Default", + "memoryTitle": "Memory", + "memoryDesc": "Persistent cross-session conversational memory", + "memoryEnabled": "Enable Memory", + "memoryEnabledDesc": "When enabled, OmniRoute will inject relevant past context.", + "maxTokens": "Max Tokens", + "retentionDays": "Retention", + "recent": "Recent", + "recentDesc": "Chronological window", + "semantic": "Semantic", + "semanticDesc": "Vector search", + "hybrid": "Hybrid", + "hybridDesc": "Recent + Semantic", + "skillsTitle": "Skills", + "skillsDesc": "Tools for models", + "skillsEnabled": "Enable Skills", + "skillsEnabledDesc": "Allows agents to trigger functions.", + "skillsComingSoon": "Skills marketplace coming soon.", + "memorySkillsTitle": "Memory & Skills", + "memorySkillsDesc": "Persistent context & capabilities", + "days": "Days" }, "translator": { "title": "Çeviri", diff --git a/src/i18n/messages/uk-UA.json b/src/i18n/messages/uk-UA.json index f6e1afd9e3..01bc578ef1 100644 --- a/src/i18n/messages/uk-UA.json +++ b/src/i18n/messages/uk-UA.json @@ -2164,7 +2164,46 @@ "customPricingNote": "Ви можете змінити ціни за умовчанням для певних моделей. Спеціальні зміни мають пріоритет над автоматично визначеними цінами.", "editPricing": "Редагувати ціни", "viewFullDetails": "Переглянути повну інформацію", - "themeCoral": "Корал" + "themeCoral": "Корал", + "modelsDevTitle": "Model Database", + "modelsDevDesc": "Auto-sync pricing, capabilities & specs from models.dev", + "modelsDevEnabled": "Enable models.dev Sync", + "modelsDevEnabledDesc": "Fetch model pricing, capabilities, and specs from the open-source models.dev database", + "modelsDevInterval": "Sync Interval", + "syncNow": "Sync Now", + "syncing": "Syncing...", + "lastSync": "Last sync", + "never": "Never", + "modelsDevStats": "Sync Statistics", + "modelsDevStatsDesc": "Current models.dev data coverage", + "modelsWithPricing": "Models with Pricing", + "capabilities": "Capabilities", + "lastSyncCount": "Last Sync Count", + "lastSyncFull": "Last full sync", + "modelsDevInfo": "How It Works", + "modelsDevInfoDesc": "models.dev is an open-source database of AI model specifications maintained by the SST/OpenCode team. It provides pricing, capabilities, context limits, and modalities for 4,000+ models across 100+ providers.", + "modelsDevInfoResolution": "Pricing resolution order (highest priority first):", + "modelsDevInfoOrder": "User Override → models.dev → LiteLLM → Hardcoded Default", + "memoryTitle": "Memory", + "memoryDesc": "Persistent cross-session conversational memory", + "memoryEnabled": "Enable Memory", + "memoryEnabledDesc": "When enabled, OmniRoute will inject relevant past context.", + "maxTokens": "Max Tokens", + "retentionDays": "Retention", + "recent": "Recent", + "recentDesc": "Chronological window", + "semantic": "Semantic", + "semanticDesc": "Vector search", + "hybrid": "Hybrid", + "hybridDesc": "Recent + Semantic", + "skillsTitle": "Skills", + "skillsDesc": "Tools for models", + "skillsEnabled": "Enable Skills", + "skillsEnabledDesc": "Allows agents to trigger functions.", + "skillsComingSoon": "Skills marketplace coming soon.", + "memorySkillsTitle": "Memory & Skills", + "memorySkillsDesc": "Persistent context & capabilities", + "days": "Days" }, "translator": { "title": "Перекладач", diff --git a/src/i18n/messages/vi.json b/src/i18n/messages/vi.json index a5217ef662..adfa3c59c2 100644 --- a/src/i18n/messages/vi.json +++ b/src/i18n/messages/vi.json @@ -2164,7 +2164,46 @@ "customPricingNote": "Bạn có thể ghi đè giá mặc định cho các kiểu máy cụ thể. Ghi đè tùy chỉnh được ưu tiên hơn giá được tự động phát hiện.", "editPricing": "Chỉnh sửa giá", "viewFullDetails": "Xem chi tiết đầy đủ", - "themeCoral": "San hô" + "themeCoral": "San hô", + "modelsDevTitle": "Model Database", + "modelsDevDesc": "Auto-sync pricing, capabilities & specs from models.dev", + "modelsDevEnabled": "Enable models.dev Sync", + "modelsDevEnabledDesc": "Fetch model pricing, capabilities, and specs from the open-source models.dev database", + "modelsDevInterval": "Sync Interval", + "syncNow": "Sync Now", + "syncing": "Syncing...", + "lastSync": "Last sync", + "never": "Never", + "modelsDevStats": "Sync Statistics", + "modelsDevStatsDesc": "Current models.dev data coverage", + "modelsWithPricing": "Models with Pricing", + "capabilities": "Capabilities", + "lastSyncCount": "Last Sync Count", + "lastSyncFull": "Last full sync", + "modelsDevInfo": "How It Works", + "modelsDevInfoDesc": "models.dev is an open-source database of AI model specifications maintained by the SST/OpenCode team. It provides pricing, capabilities, context limits, and modalities for 4,000+ models across 100+ providers.", + "modelsDevInfoResolution": "Pricing resolution order (highest priority first):", + "modelsDevInfoOrder": "User Override → models.dev → LiteLLM → Hardcoded Default", + "memoryTitle": "Memory", + "memoryDesc": "Persistent cross-session conversational memory", + "memoryEnabled": "Enable Memory", + "memoryEnabledDesc": "When enabled, OmniRoute will inject relevant past context.", + "maxTokens": "Max Tokens", + "retentionDays": "Retention", + "recent": "Recent", + "recentDesc": "Chronological window", + "semantic": "Semantic", + "semanticDesc": "Vector search", + "hybrid": "Hybrid", + "hybridDesc": "Recent + Semantic", + "skillsTitle": "Skills", + "skillsDesc": "Tools for models", + "skillsEnabled": "Enable Skills", + "skillsEnabledDesc": "Allows agents to trigger functions.", + "skillsComingSoon": "Skills marketplace coming soon.", + "memorySkillsTitle": "Memory & Skills", + "memorySkillsDesc": "Persistent context & capabilities", + "days": "Days" }, "translator": { "title": "Người phiên dịch", diff --git a/src/i18n/messages/zh-CN.json b/src/i18n/messages/zh-CN.json index 0c10142fd8..cedab5608c 100644 --- a/src/i18n/messages/zh-CN.json +++ b/src/i18n/messages/zh-CN.json @@ -2176,7 +2176,46 @@ "lkgpCacheClearFailed": "清除 LKGP 缓存失败", "maintenance": "维护", "purgeExpiredLogs": "清理过期日志", - "purgeLogsFailed": "清理日志失败" + "purgeLogsFailed": "清理日志失败", + "modelsDevTitle": "Model Database", + "modelsDevDesc": "Auto-sync pricing, capabilities & specs from models.dev", + "modelsDevEnabled": "Enable models.dev Sync", + "modelsDevEnabledDesc": "Fetch model pricing, capabilities, and specs from the open-source models.dev database", + "modelsDevInterval": "Sync Interval", + "syncNow": "Sync Now", + "syncing": "Syncing...", + "lastSync": "Last sync", + "never": "Never", + "modelsDevStats": "Sync Statistics", + "modelsDevStatsDesc": "Current models.dev data coverage", + "modelsWithPricing": "Models with Pricing", + "capabilities": "Capabilities", + "lastSyncCount": "Last Sync Count", + "lastSyncFull": "Last full sync", + "modelsDevInfo": "How It Works", + "modelsDevInfoDesc": "models.dev is an open-source database of AI model specifications maintained by the SST/OpenCode team. It provides pricing, capabilities, context limits, and modalities for 4,000+ models across 100+ providers.", + "modelsDevInfoResolution": "Pricing resolution order (highest priority first):", + "modelsDevInfoOrder": "User Override → models.dev → LiteLLM → Hardcoded Default", + "memoryTitle": "Memory", + "memoryDesc": "Persistent cross-session conversational memory", + "memoryEnabled": "Enable Memory", + "memoryEnabledDesc": "When enabled, OmniRoute will inject relevant past context.", + "maxTokens": "Max Tokens", + "retentionDays": "Retention", + "recent": "Recent", + "recentDesc": "Chronological window", + "semantic": "Semantic", + "semanticDesc": "Vector search", + "hybrid": "Hybrid", + "hybridDesc": "Recent + Semantic", + "skillsTitle": "Skills", + "skillsDesc": "Tools for models", + "skillsEnabled": "Enable Skills", + "skillsEnabledDesc": "Allows agents to trigger functions.", + "skillsComingSoon": "Skills marketplace coming soon.", + "memorySkillsTitle": "Memory & Skills", + "memorySkillsDesc": "Persistent context & capabilities", + "days": "Days" }, "translator": { "title": "翻译者", diff --git a/src/lib/db/settings.ts b/src/lib/db/settings.ts index 0ba6ebe2a7..77d43bc6ce 100644 --- a/src/lib/db/settings.ts +++ b/src/lib/db/settings.ts @@ -103,7 +103,7 @@ export async function getPricing() { const { getDefaultPricing } = await import("@/shared/constants/pricing"); const defaultPricing = getDefaultPricing(); - // Layer 2: Synced external pricing (middle priority) + // Layer 2: Synced external pricing from LiteLLM (middle-low priority) const syncedRows = db .prepare("SELECT key, value FROM key_value WHERE namespace = 'pricing_synced'") .all(); @@ -116,7 +116,24 @@ export async function getPricing() { syncedPricing[key] = toRecord(JSON.parse(rawValue)) as PricingModels; } - // Layer 3: User overrides (highest priority) + // Layer 3: Synced pricing from models.dev (middle-high priority) + const modelsDevRows = db + .prepare("SELECT key, value FROM key_value WHERE namespace = 'models_dev_pricing'") + .all(); + const modelsDevPricing: PricingByProvider = {}; + for (const row of modelsDevRows) { + const record = toRecord(row); + const key = typeof record.key === "string" ? record.key : null; + const rawValue = typeof record.value === "string" ? record.value : null; + if (!key || rawValue === null) continue; + try { + modelsDevPricing[key] = JSON.parse(rawValue) as PricingModels; + } catch { + // Corrupted data — skip silently, fallback to lower layers + } + } + + // Layer 4: User overrides (highest priority) const rows = db.prepare("SELECT key, value FROM key_value WHERE namespace = 'pricing'").all(); const userPricing: PricingByProvider = {}; for (const row of rows) { @@ -127,7 +144,7 @@ export async function getPricing() { userPricing[key] = toRecord(JSON.parse(rawValue)) as PricingModels; } - // Merge: defaults → synced → user (each layer overrides the previous) + // Merge: defaults → LiteLLM → models.dev → user (each layer overrides the previous) const mergedPricing: PricingByProvider = {}; // Start with defaults @@ -135,8 +152,8 @@ export async function getPricing() { mergedPricing[provider] = { ...(toRecord(models) as PricingModels) }; } - // Layer synced then user on top (each higher-priority layer overrides) - for (const layer of [syncedPricing, userPricing]) { + // Layer synced (LiteLLM), then models.dev, then user on top + for (const layer of [syncedPricing, modelsDevPricing, userPricing]) { for (const [provider, models] of Object.entries(layer)) { if (!mergedPricing[provider]) { mergedPricing[provider] = { ...models }; diff --git a/src/lib/modelsDevSync.ts b/src/lib/modelsDevSync.ts new file mode 100644 index 0000000000..320ce047b5 --- /dev/null +++ b/src/lib/modelsDevSync.ts @@ -0,0 +1,694 @@ +/** + * modelsDevSync.ts — Fetch model specs, pricing, and capabilities from models.dev + * + * models.dev (https://github.com/anomalyco/models.dev) is an open-source database + * of AI model specifications maintained by the SST/OpenCode team (MIT license). + * + * API: https://models.dev/api.json + * - 109 providers, 4,146+ models + * - Data: pricing, capabilities, limits, modalities, metadata + * + * Resolution order (highest → lowest): + * 1. User overrides (`pricing` namespace) + * 2. models.dev sync (`models_dev_pricing` namespace) + * 3. LiteLLM sync (`pricing_synced` namespace) + * 4. Hardcoded defaults (`pricing.ts`) + * + * Opt-in via MODELS_DEV_SYNC_ENABLED=true (default: false). + */ + +import { getDbInstance } from "./db/core"; +import { invalidateDbCache } from "./db/readCache"; +import { backupDbFile } from "./db/backup"; + +// ─── Types ─────────────────────────────────────────────── + +type PricingEntry = { + input: number; + output: number; + cached?: number; + cache_creation?: number; + reasoning?: number; +}; + +type PricingModels = Record; +type PricingByProvider = Record; + +interface CapabilityEntry { + tool_call: boolean | null; + reasoning: boolean | null; + attachment: boolean | null; + structured_output: boolean | null; + temperature: boolean | null; + modalities_input: string; // JSON array + modalities_output: string; // JSON array + knowledge_cutoff: string | null; + release_date: string | null; + last_updated: string | null; + status: string | null; + family: string | null; + open_weights: boolean | null; + limit_context: number | null; + limit_input: number | null; + limit_output: number | null; + interleaved_field: string | null; +} + +type CapabilitiesByProvider = Record>; + +interface SyncStatus { + enabled: boolean; + lastSync: string | null; + lastSyncModelCount: number; + lastSyncCapabilityCount: number; + nextSync: string | null; + intervalMs: number; +} + +interface SyncResult { + success: boolean; + modelCount: number; + providerCount: number; + capabilityCount: number; + dryRun: boolean; + data?: { pricing: PricingByProvider; capabilities: CapabilitiesByProvider }; + error?: string; +} + +// ─── models.dev API types (raw) ────────────────────────── + +interface ModelsDevCost { + input?: number; + output?: number; + reasoning?: number; + cache_read?: number; + cache_write?: number; + input_audio?: number; + output_audio?: number; +} + +interface ModelsDevLimit { + context?: number; + input?: number; + output?: number; +} + +interface ModelsDevModalities { + input?: string[]; + output?: string[]; +} + +interface ModelsDevInterleaved { + field?: string; +} + +interface ModelsDevModel { + id: string; + name: string; + family?: string; + attachment?: boolean; + reasoning?: boolean; + tool_call?: boolean; + structured_output?: boolean; + temperature?: boolean; + knowledge?: string; + release_date?: string; + last_updated?: string; + open_weights?: boolean; + status?: string; + cost?: ModelsDevCost; + limit?: ModelsDevLimit; + modalities?: ModelsDevModalities; + interleaved?: ModelsDevInterleaved | boolean; +} + +interface ModelsDevProvider { + id: string; + name?: string; + env?: string[]; + npm?: string; + api?: string; + doc?: string; + models: Record; +} + +type ModelsDevData = Record; + +// ─── Configuration ─────────────────────────────────────── + +const MODELS_DEV_API_URL = "https://models.dev/api.json"; + +const parsedInterval = parseInt(process.env.MODELS_DEV_SYNC_INTERVAL || "86400", 10); +const SYNC_INTERVAL_MS = + Number.isFinite(parsedInterval) && parsedInterval > 0 ? parsedInterval * 1000 : 86400 * 1000; + +// ─── Provider mapping: models.dev provider ID → OmniRoute provider IDs/aliases ── +// +// models.dev uses canonical provider IDs (e.g. "openai", "anthropic", "google"). +// OmniRoute uses both full IDs and short aliases (e.g. "cc" for claude, "cx" for codex). +// We map each models.dev provider to ALL OmniRoute identifiers that should receive +// its pricing/capability data. + +const MODELS_DEV_PROVIDER_MAP: Record = { + // Major providers + openai: ["openai", "cx"], // cx = Codex (uses OpenAI models) + anthropic: ["anthropic", "cc"], // cc = Claude Code + google: ["gemini", "gemini-cli"], + vertex_ai: ["gemini", "vertex"], + deepseek: ["deepseek", "if"], // if = Qoder (routes through DeepSeek) + groq: ["groq"], + xai: ["xai"], + mistral: ["mistral"], + together_ai: ["together", "openrouter"], + fireworks: ["fireworks"], + cerebras: ["cerebras"], + cohere: ["cohere"], + nvidia: ["nvidia"], + nebius: ["nebius"], + siliconflow: ["siliconflow"], + hyperbolic: ["hyperbolic"], + huggingface: ["hf", "huggingface"], + openrouter: ["openrouter"], + perplexity: ["pplx", "perplexity"], + // OAuth / special providers + bedrock: ["kiro", "kr"], // kr = Kiro (AWS Bedrock) + // Additional providers that may overlap with OmniRoute + alibaba: ["ali", "alibaba", "bcp", "alicode", "alicode-intl"], + zai: ["zai", "glm"], // GLM models via Z.AI + moonshot: ["kimi", "kimi-coding", "kmc", "kmca"], + minimax: ["minimax", "minimax-cn"], + longcat: ["lc", "longcat"], + pollinations: ["pol", "pollinations"], + puter: ["pu", "puter"], + cloudflare: ["cf"], + scaleway: ["scw"], + ollama: ["ollamacloud", "ollama-cloud"], + blackbox: ["bb", "blackbox"], + kilocode: ["kc", "kilocode"], + cline: ["cl", "cline"], + cursor: ["cu", "cursor"], + github: ["gh", "github"], + // Fallback: if no mapping exists, use the models.dev ID as-is +}; + +/** + * Map a models.dev provider ID to OmniRoute provider IDs. + * Returns array of provider identifiers (may include aliases). + */ +export function mapProviderId(modelsDevProviderId: string): string[] { + return MODELS_DEV_PROVIDER_MAP[modelsDevProviderId] || [modelsDevProviderId]; +} + +// ─── Periodic sync state ───────────────────────────────── + +let syncTimer: ReturnType | null = null; +let lastSyncTime: string | null = null; +let lastSyncModelCount = 0; +let lastSyncCapabilityCount = 0; +let activeSyncIntervalMs = SYNC_INTERVAL_MS; +let cachedData: ModelsDevData | null = null; +let cacheTime = 0; +const CACHE_TTL_MS = 24 * 60 * 60 * 1000; // 24 hours + +// ─── Core: Fetch ───────────────────────────────────────── + +/** + * Fetch raw data from models.dev API. + * Uses in-memory cache with 24h TTL to avoid repeated fetches. + */ +export async function fetchModelsDev(): Promise { + // Return cached data if still fresh + if (cachedData && Date.now() - cacheTime < CACHE_TTL_MS) { + return cachedData; + } + + const response = await fetch(MODELS_DEV_API_URL, { + signal: AbortSignal.timeout(30000), + }); + if (!response.ok) { + throw new Error(`models.dev fetch failed [${response.status}]: ${response.statusText}`); + } + const text = await response.text(); + try { + const data = JSON.parse(text) as ModelsDevData; + cachedData = data; + cacheTime = Date.now(); + return data; + } catch { + throw new Error(`models.dev returned invalid JSON (${text.slice(0, 100)}...)`); + } +} + +// ─── Transform: Pricing ────────────────────────────────── + +/** + * Transform models.dev raw data → OmniRoute PricingByProvider format. + * + * models.dev costs are already in $/1M tokens (same as OmniRoute format). + * Maps: cache_read → cached, cache_write → cache_creation. + */ +export function transformModelsDevToPricing(raw: ModelsDevData): PricingByProvider { + const result: PricingByProvider = {}; + + for (const [providerId, providerData] of Object.entries(raw)) { + const omniRouteProviders = mapProviderId(providerId); + + for (const [modelId, model] of Object.entries(providerData.models || {})) { + if (!model.cost) continue; + + // Must have at least input pricing + if (model.cost.input == null) continue; + + const entry: PricingEntry = { + input: model.cost.input, + output: model.cost.output ?? 0, + }; + + if (model.cost.cache_read != null) { + entry.cached = model.cost.cache_read; + } + if (model.cost.cache_write != null) { + entry.cache_creation = model.cost.cache_write; + } + if (model.cost.reasoning != null) { + entry.reasoning = model.cost.reasoning; + } + + // Write to ALL mapped OmniRoute providers + for (const omniProvider of omniRouteProviders) { + if (!result[omniProvider]) result[omniProvider] = {}; + result[omniProvider][modelId] = entry; + } + } + } + + return result; +} + +// ─── Transform: Capabilities ───────────────────────────── + +/** + * Transform models.dev raw data → CapabilitiesByProvider format. + */ +export function transformModelsDevToCapabilities(raw: ModelsDevData): CapabilitiesByProvider { + const result: CapabilitiesByProvider = {}; + + for (const [providerId, providerData] of Object.entries(raw)) { + const omniRouteProviders = mapProviderId(providerId); + + for (const [modelId, model] of Object.entries(providerData.models || {})) { + const cap: CapabilityEntry = { + tool_call: model.tool_call ?? null, + reasoning: model.reasoning ?? null, + attachment: model.attachment ?? null, + structured_output: model.structured_output ?? null, + temperature: model.temperature ?? null, + modalities_input: JSON.stringify(model.modalities?.input ?? []), + modalities_output: JSON.stringify(model.modalities?.output ?? []), + knowledge_cutoff: model.knowledge ?? null, + release_date: model.release_date ?? null, + last_updated: model.last_updated ?? null, + status: model.status ?? null, + family: model.family ?? null, + open_weights: model.open_weights ?? null, + limit_context: model.limit?.context ?? null, + limit_input: model.limit?.input ?? null, + limit_output: model.limit?.output ?? null, + interleaved_field: + typeof model.interleaved === "object" && model.interleaved?.field + ? model.interleaved.field + : model.interleaved === true + ? "reasoning_content" + : null, + }; + + for (const omniProvider of omniRouteProviders) { + if (!result[omniProvider]) result[omniProvider] = {}; + result[omniProvider][modelId] = cap; + } + } + } + + return result; +} + +// ─── DB: models.dev pricing namespace ──────────────────── + +function toRecord(value: unknown): Record { + return value && typeof value === "object" ? (value as Record) : {}; +} + +/** + * Read synced pricing from `models_dev_pricing` namespace. + */ +export function getModelsDevPricing(): PricingByProvider { + const db = getDbInstance(); + const rows = db + .prepare("SELECT key, value FROM key_value WHERE namespace = 'models_dev_pricing'") + .all(); + const synced: PricingByProvider = {}; + for (const row of rows) { + const record = toRecord(row); + const key = typeof record.key === "string" ? record.key : null; + const rawValue = typeof record.value === "string" ? record.value : null; + if (!key || rawValue === null) continue; + try { + synced[key] = JSON.parse(rawValue) as PricingModels; + } catch { + console.warn(`[MODELS_DEV] Corrupted pricing data for provider "${key}", skipping`); + } + } + return synced; +} + +/** + * Save synced pricing to `models_dev_pricing` namespace (full replace). + */ +export function saveModelsDevPricing(data: PricingByProvider): void { + const db = getDbInstance(); + const del = db.prepare("DELETE FROM key_value WHERE namespace = 'models_dev_pricing'"); + const insert = db.prepare( + "INSERT INTO key_value (namespace, key, value) VALUES ('models_dev_pricing', ?, ?)" + ); + const tx = db.transaction(() => { + del.run(); + for (const [provider, models] of Object.entries(data)) { + insert.run(provider, JSON.stringify(models)); + } + }); + tx(); + backupDbFile("pre-write"); + invalidateDbCache("pricing"); +} + +/** + * Clear all models.dev synced pricing data. + */ +export function clearModelsDevPricing(): void { + const db = getDbInstance(); + db.prepare("DELETE FROM key_value WHERE namespace = 'models_dev_pricing'").run(); + backupDbFile("pre-write"); + invalidateDbCache("pricing"); +} + +// ─── DB: model_capabilities table ──────────────────────── + +/** + * Ensure the model_capabilities table exists. + * Call this before any capability operations. + */ +export function ensureCapabilitiesTable(): void { + const db = getDbInstance(); + db.exec(` + CREATE TABLE IF NOT EXISTS model_capabilities ( + provider TEXT NOT NULL, + model_id TEXT NOT NULL, + tool_call BOOLEAN, + reasoning BOOLEAN, + attachment BOOLEAN, + structured_output BOOLEAN, + temperature BOOLEAN, + modalities_input TEXT, + modalities_output TEXT, + knowledge_cutoff TEXT, + release_date TEXT, + last_updated TEXT, + status TEXT, + family TEXT, + open_weights BOOLEAN, + limit_context INTEGER, + limit_input INTEGER, + limit_output INTEGER, + interleaved_field TEXT, + last_synced TEXT, + PRIMARY KEY (provider, model_id) + ) + `); +} + +/** + * Read synced capabilities from `model_capabilities` table. + */ +export function getSyncedCapabilities( + provider?: string, + modelId?: string +): Record> { + const db = getDbInstance(); + ensureCapabilitiesTable(); + + let query = "SELECT * FROM model_capabilities"; + const params: (string | number)[] = []; + + if (provider) { + query += " WHERE provider = ?"; + params.push(provider); + if (modelId) { + query += " AND model_id = ?"; + params.push(modelId); + } + } + + const rows = db.prepare(query).all(...params); + const result: Record> = {}; + + for (const row of rows) { + const record = toRecord(row); + const prov = typeof record.provider === "string" ? record.provider : null; + const mid = typeof record.model_id === "string" ? record.model_id : null; + if (!prov || !mid) continue; + + if (!result[prov]) result[prov] = {}; + result[prov][mid] = { + tool_call: record.tool_call === 1 ? true : record.tool_call === 0 ? false : null, + reasoning: record.reasoning === 1 ? true : record.reasoning === 0 ? false : null, + attachment: record.attachment === 1 ? true : record.attachment === 0 ? false : null, + structured_output: + record.structured_output === 1 ? true : record.structured_output === 0 ? false : null, + temperature: record.temperature === 1 ? true : record.temperature === 0 ? false : null, + modalities_input: + typeof record.modalities_input === "string" ? record.modalities_input : "[]", + modalities_output: + typeof record.modalities_output === "string" ? record.modalities_output : "[]", + knowledge_cutoff: + typeof record.knowledge_cutoff === "string" ? record.knowledge_cutoff : null, + release_date: typeof record.release_date === "string" ? record.release_date : null, + last_updated: typeof record.last_updated === "string" ? record.last_updated : null, + status: typeof record.status === "string" ? record.status : null, + family: typeof record.family === "string" ? record.family : null, + open_weights: record.open_weights === 1 ? true : record.open_weights === 0 ? false : null, + limit_context: typeof record.limit_context === "number" ? record.limit_context : null, + limit_input: typeof record.limit_input === "number" ? record.limit_input : null, + limit_output: typeof record.limit_output === "number" ? record.limit_output : null, + interleaved_field: + typeof record.interleaved_field === "string" ? record.interleaved_field : null, + }; + } + + return result; +} + +/** + * Save synced capabilities to `model_capabilities` table (full replace). + */ +export function saveModelsDevCapabilities(data: CapabilitiesByProvider): void { + const db = getDbInstance(); + ensureCapabilitiesTable(); + + const del = db.prepare("DELETE FROM model_capabilities"); + const insert = db.prepare(` + INSERT INTO model_capabilities ( + provider, model_id, tool_call, reasoning, attachment, structured_output, + temperature, modalities_input, modalities_output, knowledge_cutoff, + release_date, last_updated, status, family, open_weights, + limit_context, limit_input, limit_output, interleaved_field, last_synced + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + `); + + const now = new Date().toISOString(); + const tx = db.transaction(() => { + del.run(); + for (const [provider, models] of Object.entries(data)) { + for (const [modelId, cap] of Object.entries(models)) { + insert.run( + provider, + modelId, + cap.tool_call === null ? null : cap.tool_call ? 1 : 0, + cap.reasoning === null ? null : cap.reasoning ? 1 : 0, + cap.attachment === null ? null : cap.attachment ? 1 : 0, + cap.structured_output === null ? null : cap.structured_output ? 1 : 0, + cap.temperature === null ? null : cap.temperature ? 1 : 0, + cap.modalities_input, + cap.modalities_output, + cap.knowledge_cutoff, + cap.release_date, + cap.last_updated, + cap.status, + cap.family, + cap.open_weights === null ? null : cap.open_weights ? 1 : 0, + cap.limit_context, + cap.limit_input, + cap.limit_output, + cap.interleaved_field, + now + ); + } + } + }); + tx(); + backupDbFile("pre-write"); +} + +/** + * Clear all synced capability data. + */ +export function clearModelsDevCapabilities(): void { + const db = getDbInstance(); + ensureCapabilitiesTable(); + db.prepare("DELETE FROM model_capabilities").run(); + backupDbFile("pre-write"); +} + +// ─── Main sync function ────────────────────────────────── + +/** + * Fetch, transform, and save pricing + capabilities from models.dev. + */ +export async function syncModelsDev(opts?: { + dryRun?: boolean; + syncCapabilities?: boolean; +}): Promise { + const dryRun = opts?.dryRun ?? false; + const syncCapabilities = opts?.syncCapabilities ?? true; + + try { + const raw = await fetchModelsDev(); + const pricing = transformModelsDevToPricing(raw); + const capabilities = syncCapabilities ? transformModelsDevToCapabilities(raw) : {}; + + const modelCount = Object.values(pricing).reduce( + (sum, models) => sum + Object.keys(models).length, + 0 + ); + const providerCount = Object.keys(pricing).length; + const capabilityCount = syncCapabilities + ? Object.values(capabilities).reduce((sum, models) => sum + Object.keys(models).length, 0) + : 0; + + if (!dryRun) { + saveModelsDevPricing(pricing); + if (syncCapabilities) { + ensureCapabilitiesTable(); + saveModelsDevCapabilities(capabilities); + } + lastSyncTime = new Date().toISOString(); + lastSyncModelCount = modelCount; + lastSyncCapabilityCount = capabilityCount; + } + + return { + success: true, + modelCount, + providerCount, + capabilityCount, + dryRun, + ...(dryRun ? { data: { pricing, capabilities } } : {}), + }; + } catch (err) { + const message = err instanceof Error ? err.message : String(err); + console.warn("[MODELS_DEV] Sync failed:", message); + return { + success: false, + modelCount: 0, + providerCount: 0, + capabilityCount: 0, + dryRun, + error: message, + }; + } +} + +// ─── Periodic sync ─────────────────────────────────────── + +/** + * Start periodic models.dev sync (non-blocking). + */ +export function startPeriodicSync(intervalMs?: number): void { + if (syncTimer) return; // Already running + + const interval = intervalMs ?? SYNC_INTERVAL_MS; + activeSyncIntervalMs = interval; + console.log(`[MODELS_DEV] Starting periodic sync every ${interval / 1000}s`); + + // Initial sync (non-blocking) + syncModelsDev() + .then((result) => { + if (result.success) { + console.log( + `[MODELS_DEV] Initial sync complete: ${result.modelCount} pricing entries, ${result.capabilityCount} capabilities from ${result.providerCount} providers` + ); + } + }) + .catch((err) => { + console.warn("[MODELS_DEV] Initial sync error:", err instanceof Error ? err.message : err); + }); + + syncTimer = setInterval(() => { + syncModelsDev() + .then((result) => { + if (result.success) { + console.log(`[MODELS_DEV] Periodic sync complete: ${result.modelCount} pricing entries`); + } + }) + .catch((err) => { + console.warn("[MODELS_DEV] Periodic sync error:", err instanceof Error ? err.message : err); + }); + }, interval); +} + +/** + * Stop periodic sync and cleanup timer. + */ +export function stopPeriodicSync(): void { + if (syncTimer) { + clearInterval(syncTimer); + syncTimer = null; + console.log("[MODELS_DEV] Periodic sync stopped"); + } +} + +/** + * Get current sync status. + */ +export function getSyncStatus(): SyncStatus { + // If the sync timer is active, it's enabled. + const enabled = syncTimer !== null; + return { + enabled, + lastSync: lastSyncTime, + lastSyncModelCount, + lastSyncCapabilityCount, + nextSync: + syncTimer && lastSyncTime + ? new Date(new Date(lastSyncTime).getTime() + activeSyncIntervalMs).toISOString() + : null, + intervalMs: activeSyncIntervalMs, + }; +} + +// ─── Init (called from server-init.ts) ─────────────────── + +/** + * Initialize models.dev sync if enabled. + */ +export async function initModelsDevSync(): Promise { + const { getSettings } = await import("./localDb"); + const settings = await getSettings(); + + if (settings.modelsDevSyncEnabled !== true) { + console.log("[MODELS_DEV] Disabled (enable via Settings > AI)"); + return; + } + + const interval = settings.modelsDevSyncInterval as number | undefined; + startPeriodicSync(interval); +} diff --git a/src/shared/validation/settingsSchemas.ts b/src/shared/validation/settingsSchemas.ts index 9d0352506c..692560a98b 100644 --- a/src/shared/validation/settingsSchemas.ts +++ b/src/shared/validation/settingsSchemas.ts @@ -72,4 +72,7 @@ export const updateSettingsSchema = z.object({ .optional(), // SkillsMP marketplace API key skillsmpApiKey: z.string().max(200).optional(), + // models.dev sync settings + modelsDevSyncEnabled: z.boolean().optional(), + modelsDevSyncInterval: z.number().int().min(3600).max(604800).optional(), }); diff --git a/tests/integration/modelsDevSync.test.mjs b/tests/integration/modelsDevSync.test.mjs new file mode 100644 index 0000000000..7fd675a142 --- /dev/null +++ b/tests/integration/modelsDevSync.test.mjs @@ -0,0 +1,186 @@ +/** + * Integration tests for models.dev sync: live fetch → DB save → retrieve → resolution order. + * + * Uses a test DB file in /tmp to avoid touching production data. + */ + +import { describe, it, before, after } from "node:test"; +import assert from "node:assert/strict"; +import { unlinkSync, existsSync } from "node:fs"; + +// Point DB at a temp file +const TEST_DB = "/tmp/omniroute-test-modelsdev.sqlite"; + +before(() => { + if (existsSync(TEST_DB)) unlinkSync(TEST_DB); + process.env.DATA_DIR = "/tmp"; + process.env.DB_FILE = TEST_DB; +}); + +after(() => { + if (existsSync(TEST_DB)) unlinkSync(TEST_DB); +}); + +describe("modelsDevSync — integration: live fetch → DB → retrieve", () => { + it("fetches, saves pricing, and retrieves from DB", async () => { + const { + syncModelsDev, + getModelsDevPricing, + getSyncedCapabilities, + clearModelsDevPricing, + clearModelsDevCapabilities, + } = await import("../../src/lib/modelsDevSync.ts"); + + // 1. Sync from live API + const result = await syncModelsDev({ dryRun: false, syncCapabilities: true }); + assert.equal(result.success, true, `sync should succeed: ${result.error || ""}`); + assert.ok(result.modelCount > 0, `should have pricing entries, got ${result.modelCount}`); + assert.ok(result.providerCount > 0, `should have providers, got ${result.providerCount}`); + assert.ok( + result.capabilityCount > 0, + `should have capabilities, got ${result.capabilityCount}` + ); + + // 2. Retrieve pricing from DB + const pricing = getModelsDevPricing(); + assert.ok(Object.keys(pricing).length > 0, "should have pricing in DB"); + + // 3. Verify specific known models + assert.ok(pricing.openai, "openai pricing should exist"); + assert.ok(pricing.openai["gpt-4o"], "gpt-4o pricing should exist in openai"); + assert.ok(pricing.openai["gpt-4o"].input > 0, "gpt-4o input price should be > 0"); + assert.ok(pricing.openai["gpt-4o"].output > 0, "gpt-4o output price should be > 0"); + + assert.ok(pricing.anthropic, "anthropic pricing should exist"); + const claudeModels = Object.keys(pricing.anthropic).filter((m) => m.includes("claude")); + assert.ok(claudeModels.length > 0, "should have claude pricing entries"); + + assert.ok(pricing.deepseek, "deepseek pricing should exist"); + assert.ok(pricing.if, "if (Qoder) pricing should exist (mapped from deepseek)"); + + // 4. Retrieve capabilities from DB + const caps = getSyncedCapabilities(); + assert.ok(Object.keys(caps).length > 0, "should have capabilities in DB"); + + assert.ok(caps.openai, "openai capabilities should exist"); + assert.ok(caps.openai["gpt-4o"], "gpt-4o capabilities should exist"); + assert.equal(caps.openai["gpt-4o"].tool_call, true); + assert.equal(caps.openai["gpt-4o"].reasoning, false); + assert.equal(caps.openai["gpt-4o"].attachment, true); + + // 5. Query specific capability + const specificCaps = getSyncedCapabilities("openai", "gpt-4o"); + assert.ok(specificCaps.openai, "specific query should return openai"); + assert.ok(specificCaps.openai["gpt-4o"], "specific query should return gpt-4o"); + assert.equal(specificCaps.openai["gpt-4o"].limit_context, 128000); + + // 6. Cleanup + clearModelsDevPricing(); + clearModelsDevCapabilities(); + + const afterPricing = getModelsDevPricing(); + assert.equal(Object.keys(afterPricing).length, 0, "pricing should be cleared"); + + const afterCaps = getSyncedCapabilities(); + assert.equal(Object.keys(afterCaps).length, 0, "capabilities should be cleared"); + }); + + it("dryRun returns data without saving to DB", async () => { + const { syncModelsDev, getModelsDevPricing, clearModelsDevPricing } = + await import("../../src/lib/modelsDevSync.ts"); + + // Clear any existing data first + clearModelsDevPricing(); + + const result = await syncModelsDev({ dryRun: true, syncCapabilities: false }); + assert.equal(result.success, true); + assert.ok(result.data, "dryRun should return data"); + assert.ok(result.data.pricing, "dryRun should return pricing data"); + + // Verify nothing was saved + const pricing = getModelsDevPricing(); + assert.equal(Object.keys(pricing).length, 0, "dryRun should not save to DB"); + }); + + it("sync without capabilities only saves pricing", async () => { + const { + syncModelsDev, + getModelsDevPricing, + getSyncedCapabilities, + clearModelsDevPricing, + clearModelsDevCapabilities, + } = await import("../../src/lib/modelsDevSync.ts"); + + clearModelsDevPricing(); + clearModelsDevCapabilities(); + + const result = await syncModelsDev({ dryRun: false, syncCapabilities: false }); + assert.equal(result.success, true); + assert.ok(result.modelCount > 0, "should have pricing entries"); + assert.equal(result.capabilityCount, 0, "capabilityCount should be 0 when disabled"); + + // Pricing should be saved + const pricing = getModelsDevPricing(); + assert.ok(Object.keys(pricing).length > 0, "pricing should be saved"); + + // Capabilities should NOT be saved + const caps = getSyncedCapabilities(); + assert.equal(Object.keys(caps).length, 0, "capabilities should not be saved"); + + clearModelsDevPricing(); + }); +}); + +describe("modelsDevSync — resolution order: user > models.dev > LiteLLM > default", () => { + it("returns models.dev pricing when no user override exists", async () => { + const { syncModelsDev, getModelsDevPricing, clearModelsDevPricing } = + await import("../../src/lib/modelsDevSync.ts"); + + clearModelsDevPricing(); + await syncModelsDev({ dryRun: false, syncCapabilities: false }); + + const pricing = getModelsDevPricing(); + assert.ok(pricing.openai, "openai pricing should exist"); + assert.ok(pricing.openai["gpt-4o"], "gpt-4o should have models.dev pricing"); + + clearModelsDevPricing(); + }); + + it("getPricing() merges all layers correctly", async () => { + const { getPricing } = await import("../../src/lib/db/settings.ts"); + const { syncModelsDev, clearModelsDevPricing, saveModelsDevPricing } = + await import("../../src/lib/modelsDevSync.ts"); + const { updatePricing, resetPricing } = await import("../../src/lib/db/settings.ts"); + + // Clear all synced data first + clearModelsDevPricing(); + + // Sync models.dev pricing + await syncModelsDev({ dryRun: false, syncCapabilities: false }); + + // Get merged pricing + const merged = await getPricing(); + + // Should have openai pricing (either from models.dev or hardcoded defaults) + assert.ok(merged.openai || merged.cx, "should have openai/cx pricing in merged result"); + + // Add a user override + await updatePricing({ + openai: { + "gpt-4o": { input: 999, output: 999 }, + }, + }); + + // Verify user override wins + const mergedWithOverride = await getPricing(); + assert.equal( + mergedWithOverride.openai?.["gpt-4o"]?.input, + 999, + "user override should win over models.dev" + ); + + // Reset the override + await resetPricing("openai", "gpt-4o"); + clearModelsDevPricing(); + }); +}); diff --git a/tests/unit/modelsDevSync.test.mjs b/tests/unit/modelsDevSync.test.mjs new file mode 100644 index 0000000000..49f8a848f4 --- /dev/null +++ b/tests/unit/modelsDevSync.test.mjs @@ -0,0 +1,449 @@ +/** + * Unit tests for modelsDevSync.ts + * + * Tests: fetch, transform (pricing + capabilities), provider mapping, + * DB save/retrieve, and resolution order. + */ + +import { describe, it, before, after } from "node:test"; +import assert from "node:assert/strict"; +import { + transformModelsDevToPricing, + transformModelsDevToCapabilities, + mapProviderId, + fetchModelsDev, +} from "../../src/lib/modelsDevSync.ts"; + +// ─── Mock data ─────────────────────────────────────────── + +const MOCK_MODELS_DEV_DATA = { + openai: { + id: "openai", + name: "OpenAI", + npm: "@ai-sdk/openai", + env: ["OPENAI_API_KEY"], + doc: "https://platform.openai.com/docs/models", + models: { + "gpt-4o": { + id: "gpt-4o", + name: "GPT-4o", + family: "gpt-4", + attachment: true, + reasoning: false, + tool_call: true, + structured_output: true, + temperature: true, + knowledge: "2024-10", + release_date: "2024-05-13", + last_updated: "2024-10-01", + open_weights: false, + cost: { + input: 2.5, + output: 10.0, + cache_read: 1.25, + cache_write: 2.5, + }, + limit: { + context: 128000, + input: 128000, + output: 16384, + }, + modalities: { + input: ["text", "image"], + output: ["text"], + }, + }, + "o3-mini": { + id: "o3-mini", + name: "o3-mini", + family: "o3", + attachment: false, + reasoning: true, + tool_call: true, + structured_output: true, + temperature: false, + release_date: "2025-01-31", + last_updated: "2025-01-31", + open_weights: false, + cost: { + input: 1.1, + output: 4.4, + reasoning: 4.4, + }, + limit: { + context: 200000, + output: 100000, + }, + modalities: { + input: ["text"], + output: ["text"], + }, + }, + }, + }, + anthropic: { + id: "anthropic", + name: "Anthropic", + npm: "@ai-sdk/anthropic", + env: ["ANTHROPIC_API_KEY"], + doc: "https://docs.anthropic.com/en/docs/about-claud/models", + models: { + "claude-sonnet-4-20250514": { + id: "claude-sonnet-4-20250514", + name: "Claude Sonnet 4", + family: "claude-4", + attachment: true, + reasoning: false, + tool_call: true, + structured_output: true, + temperature: true, + release_date: "2025-05-14", + last_updated: "2025-05-14", + open_weights: false, + cost: { + input: 3.0, + output: 15.0, + cache_read: 0.3, + cache_write: 3.75, + }, + limit: { + context: 200000, + output: 64000, + }, + modalities: { + input: ["text", "image"], + output: ["text"], + }, + }, + }, + }, + deepseek: { + id: "deepseek", + name: "DeepSeek", + npm: "@ai-sdk/openai-compatible", + api: "https://api.deepseek.com/v1", + env: ["DEEPSEEK_API_KEY"], + doc: "https://api-docs.deepseek.com/", + models: { + "deepseek-chat": { + id: "deepseek-chat", + name: "DeepSeek V3", + family: "deepseek-v3", + attachment: false, + reasoning: false, + tool_call: true, + temperature: true, + release_date: "2024-12-26", + last_updated: "2025-01-20", + open_weights: true, + cost: { + input: 0.27, + output: 1.1, + cache_read: 0.07, + }, + limit: { + context: 64000, + output: 8192, + }, + modalities: { + input: ["text"], + output: ["text"], + }, + }, + }, + }, + "unknown-provider": { + id: "unknown-provider", + name: "Unknown", + npm: "@ai-sdk/openai-compatible", + api: "https://api.unknown.com/v1", + env: ["UNKNOWN_API_KEY"], + models: { + "some-model": { + id: "some-model", + name: "Some Model", + attachment: false, + reasoning: false, + tool_call: false, + release_date: "2025-01-01", + last_updated: "2025-01-01", + open_weights: false, + cost: { + input: 1.0, + output: 2.0, + }, + limit: { + context: 32000, + output: 4096, + }, + modalities: { + input: ["text"], + output: ["text"], + }, + }, + }, + }, +}; + +// ─── Tests ─────────────────────────────────────────────── + +describe("modelsDevSync — transformModelsDevToPricing", () => { + it("transforms pricing data correctly for openai provider", () => { + const pricing = transformModelsDevToPricing(MOCK_MODELS_DEV_DATA); + + // openai maps to ["openai", "cx"] + assert.ok(pricing.openai, "openai provider should exist"); + assert.ok(pricing.openai["gpt-4o"], "gpt-4o pricing should exist"); + assert.equal(pricing.openai["gpt-4o"].input, 2.5); + assert.equal(pricing.openai["gpt-4o"].output, 10.0); + assert.equal(pricing.openai["gpt-4o"].cached, 1.25); + assert.equal(pricing.openai["gpt-4o"].cache_creation, 2.5); + + // cx (Codex alias) should also have the same pricing + assert.ok(pricing.cx, "cx provider should exist"); + assert.ok(pricing.cx["gpt-4o"], "gpt-4o pricing in cx should exist"); + assert.equal(pricing.cx["gpt-4o"].input, 2.5); + }); + + it("maps reasoning cost correctly", () => { + const pricing = transformModelsDevToPricing(MOCK_MODELS_DEV_DATA); + + assert.ok(pricing.openai["o3-mini"], "o3-mini pricing should exist"); + assert.equal(pricing.openai["o3-mini"].input, 1.1); + assert.equal(pricing.openai["o3-mini"].output, 4.4); + assert.equal(pricing.openai["o3-mini"].reasoning, 4.4); + // o3-mini has no cache_read/cache_write + assert.equal(pricing.openai["o3-mini"].cached, undefined); + assert.equal(pricing.openai["o3-mini"].cache_creation, undefined); + }); + + it("maps deepseek to both deepseek and if (Qoder) providers", () => { + const pricing = transformModelsDevToPricing(MOCK_MODELS_DEV_DATA); + + assert.ok(pricing.deepseek, "deepseek provider should exist"); + assert.ok(pricing.if, "if (Qoder) provider should exist"); + assert.equal(pricing.deepseek["deepseek-chat"].input, 0.27); + assert.equal(pricing.deepseek["deepseek-chat"].output, 1.1); + assert.equal(pricing.deepseek["deepseek-chat"].cached, 0.07); + // Should be same in if provider + assert.equal(pricing.if["deepseek-chat"].input, 0.27); + }); + + it("handles unknown providers by using the models.dev ID as-is", () => { + const pricing = transformModelsDevToPricing(MOCK_MODELS_DEV_DATA); + + assert.ok(pricing["unknown-provider"], "unknown-provider should exist"); + assert.equal(pricing["unknown-provider"]["some-model"].input, 1.0); + assert.equal(pricing["unknown-provider"]["some-model"].output, 2.0); + }); + + it("skips models without cost data", () => { + const dataWithNoCost = { + testprovider: { + id: "testprovider", + models: { + "no-cost-model": { + id: "no-cost-model", + name: "No Cost Model", + attachment: false, + reasoning: false, + tool_call: false, + release_date: "2025-01-01", + last_updated: "2025-01-01", + open_weights: false, + // No cost field + limit: { context: 4096, output: 2048 }, + modalities: { input: ["text"], output: ["text"] }, + }, + }, + }, + }; + const pricing = transformModelsDevToPricing(dataWithNoCost); + assert.equal(pricing.testprovider, undefined, "provider without cost should not exist"); + }); + + it("counts pricing entries correctly", () => { + const pricing = transformModelsDevToPricing(MOCK_MODELS_DEV_DATA); + const totalModels = Object.values(pricing).reduce( + (sum, models) => sum + Object.keys(models).length, + 0 + ); + // openai: 2 models × 2 providers (openai, cx) = 4 + // anthropic: 1 model × 2 providers (anthropic, cc) = 2 + // deepseek: 1 model × 2 providers (deepseek, if) = 2 + // unknown-provider: 1 model × 1 provider = 1 + assert.equal(totalModels, 9); + }); +}); + +describe("modelsDevSync — transformModelsDevToCapabilities", () => { + it("transforms capability data correctly", () => { + const caps = transformModelsDevToCapabilities(MOCK_MODELS_DEV_DATA); + + assert.ok(caps.openai, "openai capabilities should exist"); + assert.ok(caps.openai["gpt-4o"], "gpt-4o capabilities should exist"); + + const gpt4o = caps.openai["gpt-4o"]; + assert.equal(gpt4o.tool_call, true); + assert.equal(gpt4o.reasoning, false); + assert.equal(gpt4o.attachment, true); + assert.equal(gpt4o.structured_output, true); + assert.equal(gpt4o.temperature, true); + assert.equal(gpt4o.limit_context, 128000); + assert.equal(gpt4o.limit_output, 16384); + assert.equal(gpt4o.status, null); // not set in mock + assert.equal(gpt4o.family, "gpt-4"); + assert.equal(gpt4o.open_weights, false); + assert.equal(JSON.parse(gpt4o.modalities_input).length, 2); + assert.equal(JSON.parse(gpt4o.modalities_output).length, 1); + }); + + it("handles interleaved reasoning field", () => { + const dataWithInterleaved = { + testprovider: { + id: "testprovider", + models: { + "reasoning-model": { + id: "reasoning-model", + name: "Reasoning Model", + attachment: false, + reasoning: true, + tool_call: true, + release_date: "2025-01-01", + last_updated: "2025-01-01", + open_weights: true, + interleaved: { field: "reasoning_content" }, + limit: { context: 200000, output: 64000 }, + modalities: { input: ["text"], output: ["text"] }, + }, + }, + }, + }; + const caps = transformModelsDevToCapabilities(dataWithInterleaved); + assert.equal(caps.testprovider["reasoning-model"].interleaved_field, "reasoning_content"); + }); + + it("handles interleaved as boolean true", () => { + const dataWithBoolInterleaved = { + testprovider: { + id: "testprovider", + models: { + "bool-model": { + id: "bool-model", + name: "Bool Model", + attachment: false, + reasoning: true, + tool_call: true, + release_date: "2025-01-01", + last_updated: "2025-01-01", + open_weights: false, + interleaved: true, + limit: { context: 100000, output: 32000 }, + modalities: { input: ["text"], output: ["text"] }, + }, + }, + }, + }; + const caps = transformModelsDevToCapabilities(dataWithBoolInterleaved); + assert.equal(caps.testprovider["bool-model"].interleaved_field, "reasoning_content"); + }); + + it("handles null/missing optional fields", () => { + const dataWithMinimal = { + minimal: { + id: "minimal", + models: { + "minimal-model": { + id: "minimal-model", + name: "Minimal", + attachment: false, + reasoning: false, + tool_call: false, + release_date: "2025-01-01", + last_updated: "2025-01-01", + open_weights: false, + // No cost, no interleaved, no status, no family + limit: { context: 4096, output: 2048 }, + modalities: { input: ["text"], output: ["text"] }, + }, + }, + }, + }; + const caps = transformModelsDevToCapabilities(dataWithMinimal); + const m = caps.minimal["minimal-model"]; + assert.equal(m.tool_call, false); + assert.equal(m.reasoning, false); + assert.equal(m.attachment, false); + assert.equal(m.structured_output, null); + assert.equal(m.temperature, null); + assert.equal(m.knowledge_cutoff, null); + assert.equal(m.status, null); + assert.equal(m.family, null); + assert.equal(m.interleaved_field, null); + assert.equal(m.limit_context, 4096); + }); +}); + +describe("modelsDevSync — mapProviderId", () => { + it("maps openai to [openai, cx]", () => { + assert.deepEqual(mapProviderId("openai"), ["openai", "cx"]); + }); + + it("maps anthropic to [anthropic, cc]", () => { + assert.deepEqual(mapProviderId("anthropic"), ["anthropic", "cc"]); + }); + + it("maps google to [gemini, gemini-cli]", () => { + assert.deepEqual(mapProviderId("google"), ["gemini", "gemini-cli"]); + }); + + it("maps deepseek to [deepseek, if]", () => { + assert.deepEqual(mapProviderId("deepseek"), ["deepseek", "if"]); + }); + + it("falls back to original ID for unmapped providers", () => { + assert.deepEqual(mapProviderId("some-new-provider"), ["some-new-provider"]); + }); + + it("maps bedrock to [kiro, kr]", () => { + assert.deepEqual(mapProviderId("bedrock"), ["kiro", "kr"]); + }); +}); + +describe("modelsDevSync — fetchModelsDev (live API)", () => { + it("fetches data from models.dev API", async () => { + const data = await fetchModelsDev(); + assert.ok(typeof data === "object", "data should be an object"); + + const providerCount = Object.keys(data).length; + assert.ok(providerCount >= 100, `should have 100+ providers, got ${providerCount}`); + + let modelCount = 0; + for (const provider of Object.values(data)) { + const p = provider; + if (p.models) { + modelCount += Object.keys(p.models).length; + } + } + assert.ok(modelCount >= 4000, `should have 4000+ models, got ${modelCount}`); + }); + + it("returns cached data on second call", async () => { + const data1 = await fetchModelsDev(); + const data2 = await fetchModelsDev(); + assert.strictEqual(data1, data2, "should return same cached reference"); + }); + + it("has openai provider with gpt-4o model", async () => { + const data = await fetchModelsDev(); + assert.ok(data.openai, "openai provider should exist"); + assert.ok(data.openai.models["gpt-4o"], "gpt-4o model should exist"); + }); + + it("has anthropic provider with claude models", async () => { + const data = await fetchModelsDev(); + assert.ok(data.anthropic, "anthropic provider should exist"); + const claudeModels = Object.keys(data.anthropic.models).filter((m) => m.includes("claude")); + assert.ok(claudeModels.length > 0, "should have claude models"); + }); +});