diff --git a/CHANGELOG.md b/CHANGELOG.md index d991eaf35f..73f92ef4b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ _Living section — bullets land here as PRs merge into `release/v3.8.47` (paral - **Provider/model param filters**: config-driven parameter denylist/allowlist per provider/model with auto-learn from upstream 400s (#6649 — thanks @ThongAccount, closes #6625) - **Per-combo reasoning token buffer toggle**: the combo builder now exposes an explicit checkbox for the `#3587` reasoning-model `max_tokens` buffer, defaulting to the existing enabled behavior, so a combo can opt out without hand-editing raw JSON config (#6702 — thanks @xz-dev) - **feat(dashboard):** 9router-parity **Routing Strategy** settings card on Settings → Routing, plus a per-provider account-routing override on the provider detail page ([#6678](https://github.com/diegosouzapw/OmniRoute/pull/6678)) — surfaces the existing account round-robin / sticky-limit knobs and adds a new combo-level sticky round-robin (`comboStickyRoundRobinLimit`, resolved via `resolveComboStickyRoundRobinLimit()` — per-combo → global combo sticky → account sticky cascade) so combo targets can batch calls per target the same way account fallback already does. A new `providerStrategies` setting (Zod-validated map, `src/shared/validation/settingsSchemas.ts`) lets a specific provider override the global `fallbackStrategy`/`stickyRoundRobinLimit` without touching the account-wide default, wired into `getProviderCredentials()` (`src/sse/services/auth.ts`) ahead of the global fallback. Regression guard: `tests/unit/combo-rr-sticky-9router.test.ts`, `tests/unit/settings-ui-layout-static.test.ts`. (thanks @SeaXen) +- **feat(icons):** provider logos now resolve local SVG assets first for faster rendering, with a 5-tier fallback chain — local SVG → `@lobehub/icons` React components → `thesvg.org` CDN (external SVG for unknown providers) → local PNG → generic AI icon — replacing the previous LobeHub-first order. Adds dozens of first-party provider SVGs and migrates several bitmap logos (continue/copilot/cursor/deepgram/heroku/openclaw/ovhcloud) from PNG to SVG. Regression guard: `tests/unit/ui/ProviderIcon-icon-url.test.tsx`. ([#6317](https://github.com/diegosouzapw/OmniRoute/pull/6317) — thanks @hamsa0x7) - **Skill Collector CLI detection**: new `GET /api/skills/collect/detect` + `POST /api/skills/collect/install` (and the `cli-skill-collector` agent skill) detect which coding CLIs (Claude Code, Codex, Cursor, Copilot, Cline, Hermes, OpenCode, etc.) are installed locally via `getCliRuntimeStatus()`, match them against GitHub agent-skill repos, and plan an install path per tool — replacing the standalone Skill Collector Python app. Both new routes and `GET/POST /api/github-skills` now require management auth (`requireManagementAuth()`) and are loopback-gated (`LOCAL_ONLY_API_PREFIXES` + `SPAWN_CAPABLE_PREFIXES`) since the detect route spawns a child process per candidate CLI tool (Hard Rules #15 + #17). The `omniroute_github_skills_install` MCP tool now reports the honest `action: "planned"` instead of `"installed"`, matching the REST route (#6294 — thanks @Moseyuh333) - **ClinePass dual-auth**: ClinePass now offers both sign-in methods on its dashboard page — OAuth (reusing the Cline WorkOS flow) as the primary "Connect" path, or a pasted BYOK API key via "Manual API key", instead of only the API-key-only provider shipped in #5942. The registry alias was aligned to `cp` (matching the `OAUTH_PROVIDERS` catalog alias) so `/` routing resolves correctly, the OAuth refresh dispatch now routes `clinepass` to the shared Cline refresh flow, and the duplicate API-key-only catalog entry was removed to keep ClinePass listed once. Regression guard: `tests/unit/clinepass-provider.test.ts`. (#6126 — thanks @hajilok) - **feat(oauth):** Kiro/Amazon Q auto-import now supports enterprise **External IdP** ("Your organization") logins via Microsoft Entra/Okta/Auth0/OneLogin/Ping/Google/Cognito — these org-issued tokens are not AWS SSO tokens (no `aorAAAAAG`-prefixed refresh token) and can't refresh through the AWS OIDC/Kiro-social path, so `tryAwsSsoCache()` now detects them (`authMethod`/`provider === "externalidp"`) and refreshes via the org IdP's own `tokenEndpoint` (public-client OAuth2 refresh grant, no client secret), persisting `TokenType: EXTERNAL_IDP` gating so the runtime executor sends the header the AWS CodeWhisperer API requires for these accounts; `tokenEndpoint` is SSRF-guarded against an HTTPS + known-IdP-host-suffix allowlist. (#6363 — thanks @artickc) diff --git a/public/providers/360ai.svg b/public/providers/360ai.svg index caeb6eb2c9..dc393ef67b 100644 --- a/public/providers/360ai.svg +++ b/public/providers/360ai.svg @@ -1,5 +1 @@ - - - - 360 - +AI360 \ No newline at end of file diff --git a/public/providers/alibaba.svg b/public/providers/alibaba.svg new file mode 100644 index 0000000000..f6b764f99d --- /dev/null +++ b/public/providers/alibaba.svg @@ -0,0 +1,6 @@ + + Alibaba + + \ No newline at end of file diff --git a/public/providers/anthropic.svg b/public/providers/anthropic.svg new file mode 100644 index 0000000000..f31bfac298 --- /dev/null +++ b/public/providers/anthropic.svg @@ -0,0 +1 @@ +Anthropic \ No newline at end of file diff --git a/public/providers/arcee-ai.svg b/public/providers/arcee-ai.svg index b63a13ab3f..dc809fc95e 100644 --- a/public/providers/arcee-ai.svg +++ b/public/providers/arcee-ai.svg @@ -1,5 +1 @@ - - - - AR - +Arcee \ No newline at end of file diff --git a/public/providers/arcee.svg b/public/providers/arcee.svg new file mode 100644 index 0000000000..df3fd30356 --- /dev/null +++ b/public/providers/arcee.svg @@ -0,0 +1,5 @@ + + Arcee + + \ No newline at end of file diff --git a/public/providers/assemblyai.svg b/public/providers/assemblyai.svg new file mode 100644 index 0000000000..c6d4cffee7 --- /dev/null +++ b/public/providers/assemblyai.svg @@ -0,0 +1,7 @@ + + AssemblyAI + + + \ No newline at end of file diff --git a/public/providers/aws.svg b/public/providers/aws.svg new file mode 100644 index 0000000000..0e1a90a7b0 --- /dev/null +++ b/public/providers/aws.svg @@ -0,0 +1,6 @@ + + AWS + + + \ No newline at end of file diff --git a/public/providers/azure.svg b/public/providers/azure.svg new file mode 100644 index 0000000000..038645c7e0 --- /dev/null +++ b/public/providers/azure.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/providers/azureai.svg b/public/providers/azureai.svg new file mode 100644 index 0000000000..b9ae255923 --- /dev/null +++ b/public/providers/azureai.svg @@ -0,0 +1,82 @@ + + AzureAI + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/providers/baichuan.svg b/public/providers/baichuan.svg index fa3789f62e..11b27e7913 100644 --- a/public/providers/baichuan.svg +++ b/public/providers/baichuan.svg @@ -1,5 +1 @@ - - - - BC - +Baichuan \ No newline at end of file diff --git a/public/providers/baidu.svg b/public/providers/baidu.svg index 0c74817895..ead7f89822 100644 --- a/public/providers/baidu.svg +++ b/public/providers/baidu.svg @@ -1,5 +1 @@ - - - - BD - +Baidu \ No newline at end of file diff --git a/public/providers/bailian.svg b/public/providers/bailian.svg new file mode 100644 index 0000000000..7618aaad1c --- /dev/null +++ b/public/providers/bailian.svg @@ -0,0 +1,16 @@ + + Bailian (阿里云百炼) + + + + + + + + \ No newline at end of file diff --git a/public/providers/baseten.svg b/public/providers/baseten.svg new file mode 100644 index 0000000000..c6a5fb32c1 --- /dev/null +++ b/public/providers/baseten.svg @@ -0,0 +1,4 @@ + + Baseten + + \ No newline at end of file diff --git a/public/providers/cerebras.svg b/public/providers/cerebras.svg new file mode 100644 index 0000000000..88b7cb5632 --- /dev/null +++ b/public/providers/cerebras.svg @@ -0,0 +1,8 @@ + + Cerebras + + + \ No newline at end of file diff --git a/public/providers/claude-web.svg b/public/providers/claude-web.svg index 882c6d2245..62dc0db12d 100644 --- a/public/providers/claude-web.svg +++ b/public/providers/claude-web.svg @@ -1,5 +1 @@ - - - - CW - +Claude \ No newline at end of file diff --git a/public/providers/claude.svg b/public/providers/claude.svg index 8bc1fd43ba..62dc0db12d 100644 --- a/public/providers/claude.svg +++ b/public/providers/claude.svg @@ -1 +1 @@ - \ No newline at end of file +Claude \ No newline at end of file diff --git a/public/providers/cline.svg b/public/providers/cline.svg new file mode 100644 index 0000000000..8dcc05786c --- /dev/null +++ b/public/providers/cline.svg @@ -0,0 +1 @@ +Cline \ No newline at end of file diff --git a/public/providers/cloudflare.svg b/public/providers/cloudflare.svg new file mode 100644 index 0000000000..ac59cd621a --- /dev/null +++ b/public/providers/cloudflare.svg @@ -0,0 +1,7 @@ + + Cloudflare + + + \ No newline at end of file diff --git a/public/providers/cohere.svg b/public/providers/cohere.svg new file mode 100644 index 0000000000..62aec42312 --- /dev/null +++ b/public/providers/cohere.svg @@ -0,0 +1,13 @@ + + Cohere + + + + \ No newline at end of file diff --git a/public/providers/comfyui.svg b/public/providers/comfyui.svg new file mode 100644 index 0000000000..85e93cd06b --- /dev/null +++ b/public/providers/comfyui.svg @@ -0,0 +1,5 @@ + + ComfyUI + + \ No newline at end of file diff --git a/public/providers/continue.png b/public/providers/continue.png deleted file mode 100644 index f54685be8d..0000000000 Binary files a/public/providers/continue.png and /dev/null differ diff --git a/public/providers/continue.svg b/public/providers/continue.svg new file mode 100644 index 0000000000..e9f56c259e --- /dev/null +++ b/public/providers/continue.svg @@ -0,0 +1 @@ + diff --git a/public/providers/copilot.png b/public/providers/copilot.png deleted file mode 100644 index 9907963e40..0000000000 Binary files a/public/providers/copilot.png and /dev/null differ diff --git a/public/providers/copilot.svg b/public/providers/copilot.svg new file mode 100644 index 0000000000..5426933b61 --- /dev/null +++ b/public/providers/copilot.svg @@ -0,0 +1 @@ +Copilot \ No newline at end of file diff --git a/public/providers/cursor.png b/public/providers/cursor.png deleted file mode 100644 index ec02b070ba..0000000000 Binary files a/public/providers/cursor.png and /dev/null differ diff --git a/public/providers/cursor.svg b/public/providers/cursor.svg new file mode 100644 index 0000000000..10d50ca847 --- /dev/null +++ b/public/providers/cursor.svg @@ -0,0 +1,12 @@ + + + + + + + + \ No newline at end of file diff --git a/public/providers/deepgram.png b/public/providers/deepgram.png deleted file mode 100644 index 6bee16b860..0000000000 --- a/public/providers/deepgram.png +++ /dev/null @@ -1,52 +0,0 @@ - - - - 404 - - - -
-

404

-

Not Found

-
- - - \ No newline at end of file diff --git a/public/providers/deepgram.svg b/public/providers/deepgram.svg new file mode 100644 index 0000000000..988a37743f --- /dev/null +++ b/public/providers/deepgram.svg @@ -0,0 +1 @@ +Deepgram \ No newline at end of file diff --git a/public/providers/deepinfra.svg b/public/providers/deepinfra.svg new file mode 100644 index 0000000000..458107109c --- /dev/null +++ b/public/providers/deepinfra.svg @@ -0,0 +1,7 @@ + + DeepInfra + + + \ No newline at end of file diff --git a/public/providers/deepseek.svg b/public/providers/deepseek.svg new file mode 100644 index 0000000000..abf56cfecb --- /dev/null +++ b/public/providers/deepseek.svg @@ -0,0 +1,5 @@ + + DeepSeek + + \ No newline at end of file diff --git a/public/providers/dify.svg b/public/providers/dify.svg index 80be2e3447..4cea903478 100644 --- a/public/providers/dify.svg +++ b/public/providers/dify.svg @@ -1,5 +1 @@ - - - - DF - +Dify \ No newline at end of file diff --git a/public/providers/doubao.svg b/public/providers/doubao.svg index 4317871d38..e2511454cf 100644 --- a/public/providers/doubao.svg +++ b/public/providers/doubao.svg @@ -1,5 +1 @@ - - - - DB - +Doubao \ No newline at end of file diff --git a/public/providers/elevenlabs.svg b/public/providers/elevenlabs.svg new file mode 100644 index 0000000000..d44617dbde --- /dev/null +++ b/public/providers/elevenlabs.svg @@ -0,0 +1 @@ +ElevenLabs \ No newline at end of file diff --git a/public/providers/exa.svg b/public/providers/exa.svg new file mode 100644 index 0000000000..bc9f73e106 --- /dev/null +++ b/public/providers/exa.svg @@ -0,0 +1,7 @@ + + Exa + + \ No newline at end of file diff --git a/public/providers/fal.svg b/public/providers/fal.svg new file mode 100644 index 0000000000..0e54aaf865 --- /dev/null +++ b/public/providers/fal.svg @@ -0,0 +1,7 @@ + + Fal + + \ No newline at end of file diff --git a/public/providers/fireworks.svg b/public/providers/fireworks.svg new file mode 100644 index 0000000000..946ee079af --- /dev/null +++ b/public/providers/fireworks.svg @@ -0,0 +1,7 @@ + + Fireworks + + \ No newline at end of file diff --git a/public/providers/friendli.svg b/public/providers/friendli.svg new file mode 100644 index 0000000000..7da5293002 --- /dev/null +++ b/public/providers/friendli.svg @@ -0,0 +1,6 @@ + + Friendli + + + + \ No newline at end of file diff --git a/public/providers/gemini.svg b/public/providers/gemini.svg new file mode 100644 index 0000000000..f8d7189dc6 --- /dev/null +++ b/public/providers/gemini.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/providers/google.svg b/public/providers/google.svg new file mode 100644 index 0000000000..2f69df7189 --- /dev/null +++ b/public/providers/google.svg @@ -0,0 +1,11 @@ + + Google + + + + + \ No newline at end of file diff --git a/public/providers/grok.svg b/public/providers/grok.svg new file mode 100644 index 0000000000..7057a3bfa6 --- /dev/null +++ b/public/providers/grok.svg @@ -0,0 +1,4 @@ + + + + diff --git a/public/providers/groq.svg b/public/providers/groq.svg new file mode 100644 index 0000000000..cf90d287b1 --- /dev/null +++ b/public/providers/groq.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/providers/heroku.png b/public/providers/heroku.png deleted file mode 100644 index b96cb3f6a7..0000000000 Binary files a/public/providers/heroku.png and /dev/null differ diff --git a/public/providers/heroku.svg b/public/providers/heroku.svg new file mode 100644 index 0000000000..6089d6f2c2 --- /dev/null +++ b/public/providers/heroku.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/providers/huggingchat.svg b/public/providers/huggingchat.svg index 30a8cef613..dc1cf3ffb7 100644 --- a/public/providers/huggingchat.svg +++ b/public/providers/huggingchat.svg @@ -1,5 +1 @@ - - - - HC - +HuggingFace \ No newline at end of file diff --git a/public/providers/huggingface.svg b/public/providers/huggingface.svg new file mode 100644 index 0000000000..c141ab811a --- /dev/null +++ b/public/providers/huggingface.svg @@ -0,0 +1,15 @@ + + HuggingFace + + + + + + + \ No newline at end of file diff --git a/public/providers/hyperbolic.svg b/public/providers/hyperbolic.svg new file mode 100644 index 0000000000..591eb157ba --- /dev/null +++ b/public/providers/hyperbolic.svg @@ -0,0 +1,5 @@ + + Hyperbolic + + \ No newline at end of file diff --git a/public/providers/ibm.svg b/public/providers/ibm.svg new file mode 100644 index 0000000000..36fe5889dd --- /dev/null +++ b/public/providers/ibm.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/providers/iflytek.svg b/public/providers/iflytek.svg index bad262efe4..4962d9e161 100644 --- a/public/providers/iflytek.svg +++ b/public/providers/iflytek.svg @@ -1,5 +1 @@ - - - - IF - +iFlyTekCloud \ No newline at end of file diff --git a/public/providers/inference.svg b/public/providers/inference.svg new file mode 100644 index 0000000000..b53e8e4616 --- /dev/null +++ b/public/providers/inference.svg @@ -0,0 +1,4 @@ + + Inference + + \ No newline at end of file diff --git a/public/providers/kimi.svg b/public/providers/kimi.svg new file mode 100644 index 0000000000..ec12852549 --- /dev/null +++ b/public/providers/kimi.svg @@ -0,0 +1,7 @@ + + Kimi + + + \ No newline at end of file diff --git a/public/providers/kiro.svg b/public/providers/kiro.svg index 83e2845fbf..0c651b9747 100644 --- a/public/providers/kiro.svg +++ b/public/providers/kiro.svg @@ -1 +1 @@ - \ No newline at end of file +Kiro \ No newline at end of file diff --git a/public/providers/lambda.svg b/public/providers/lambda.svg new file mode 100644 index 0000000000..be9061029b --- /dev/null +++ b/public/providers/lambda.svg @@ -0,0 +1,4 @@ + + Lambda + + \ No newline at end of file diff --git a/public/providers/longcat.svg b/public/providers/longcat.svg new file mode 100644 index 0000000000..6c05c8ecc2 --- /dev/null +++ b/public/providers/longcat.svg @@ -0,0 +1,8 @@ + + LongCat + + + \ No newline at end of file diff --git a/public/providers/meta.svg b/public/providers/meta.svg new file mode 100644 index 0000000000..45c34b10e5 --- /dev/null +++ b/public/providers/meta.svg @@ -0,0 +1,121 @@ + + Meta + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/providers/metaai.svg b/public/providers/metaai.svg new file mode 100644 index 0000000000..4d623976fd --- /dev/null +++ b/public/providers/metaai.svg @@ -0,0 +1,49 @@ + + MetaAI + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/providers/minimax.svg b/public/providers/minimax.svg new file mode 100644 index 0000000000..b1b0a6a2df --- /dev/null +++ b/public/providers/minimax.svg @@ -0,0 +1 @@ +MiniMax \ No newline at end of file diff --git a/public/providers/mistral.svg b/public/providers/mistral.svg new file mode 100644 index 0000000000..605b629a38 --- /dev/null +++ b/public/providers/mistral.svg @@ -0,0 +1,10 @@ + + Mistral + + + + + + \ No newline at end of file diff --git a/public/providers/moonshot.svg b/public/providers/moonshot.svg new file mode 100644 index 0000000000..b36d4e7e0f --- /dev/null +++ b/public/providers/moonshot.svg @@ -0,0 +1,4 @@ + + Moonshot (月之暗面) + + \ No newline at end of file diff --git a/public/providers/morph.svg b/public/providers/morph.svg new file mode 100644 index 0000000000..474c521785 --- /dev/null +++ b/public/providers/morph.svg @@ -0,0 +1,5 @@ + + Morph + + \ No newline at end of file diff --git a/public/providers/nebius.svg b/public/providers/nebius.svg new file mode 100644 index 0000000000..719baf1544 --- /dev/null +++ b/public/providers/nebius.svg @@ -0,0 +1,5 @@ + + Nebius + + + \ No newline at end of file diff --git a/public/providers/novita.svg b/public/providers/novita.svg new file mode 100644 index 0000000000..7c53907792 --- /dev/null +++ b/public/providers/novita.svg @@ -0,0 +1,7 @@ + + Novita + + \ No newline at end of file diff --git a/public/providers/nvidia.svg b/public/providers/nvidia.svg new file mode 100644 index 0000000000..48c7d75d26 --- /dev/null +++ b/public/providers/nvidia.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/providers/ollama.svg b/public/providers/ollama.svg new file mode 100644 index 0000000000..96e9e86624 --- /dev/null +++ b/public/providers/ollama.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/public/providers/openai.svg b/public/providers/openai.svg new file mode 100644 index 0000000000..b6d542d099 --- /dev/null +++ b/public/providers/openai.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/providers/openclaw.png b/public/providers/openclaw.png deleted file mode 100644 index 7ef77ac754..0000000000 Binary files a/public/providers/openclaw.png and /dev/null differ diff --git a/public/providers/openclaw.svg b/public/providers/openclaw.svg new file mode 100644 index 0000000000..bf6acb9f51 --- /dev/null +++ b/public/providers/openclaw.svg @@ -0,0 +1 @@ +OpenClaw \ No newline at end of file diff --git a/public/providers/openrouter.svg b/public/providers/openrouter.svg new file mode 100644 index 0000000000..61033bfc67 --- /dev/null +++ b/public/providers/openrouter.svg @@ -0,0 +1,21 @@ + + + + + + + + diff --git a/public/providers/ovhcloud.png b/public/providers/ovhcloud.png deleted file mode 100644 index a88d1b448d..0000000000 Binary files a/public/providers/ovhcloud.png and /dev/null differ diff --git a/public/providers/ovhcloud.svg b/public/providers/ovhcloud.svg new file mode 100644 index 0000000000..71ec8e5b13 --- /dev/null +++ b/public/providers/ovhcloud.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/providers/perplexity.svg b/public/providers/perplexity.svg new file mode 100644 index 0000000000..b6e121bbc3 --- /dev/null +++ b/public/providers/perplexity.svg @@ -0,0 +1,6 @@ + + Perplexity + + \ No newline at end of file diff --git a/public/providers/picoclaw.svg b/public/providers/picoclaw.svg new file mode 100644 index 0000000000..c05d253215 --- /dev/null +++ b/public/providers/picoclaw.svg @@ -0,0 +1,30 @@ + + PicoClaw + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/providers/poe.svg b/public/providers/poe.svg new file mode 100644 index 0000000000..cbd9ec4c7d --- /dev/null +++ b/public/providers/poe.svg @@ -0,0 +1 @@ +Poe \ No newline at end of file diff --git a/public/providers/pollinations.svg b/public/providers/pollinations.svg new file mode 100644 index 0000000000..83b569419b --- /dev/null +++ b/public/providers/pollinations.svg @@ -0,0 +1,4 @@ + + Pollinations + + \ No newline at end of file diff --git a/public/providers/qwen.svg b/public/providers/qwen.svg new file mode 100644 index 0000000000..3a2f756e66 --- /dev/null +++ b/public/providers/qwen.svg @@ -0,0 +1 @@ +Qwen \ No newline at end of file diff --git a/public/providers/recraft.svg b/public/providers/recraft.svg new file mode 100644 index 0000000000..e5bb701575 --- /dev/null +++ b/public/providers/recraft.svg @@ -0,0 +1,5 @@ + + Recraft + + + \ No newline at end of file diff --git a/public/providers/replicate.svg b/public/providers/replicate.svg new file mode 100644 index 0000000000..4637f35378 --- /dev/null +++ b/public/providers/replicate.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/public/providers/roocode.svg b/public/providers/roocode.svg new file mode 100644 index 0000000000..3ce8ece8d3 --- /dev/null +++ b/public/providers/roocode.svg @@ -0,0 +1,4 @@ + + RooCode + + \ No newline at end of file diff --git a/public/providers/runway.svg b/public/providers/runway.svg new file mode 100644 index 0000000000..5dc14b1fdf --- /dev/null +++ b/public/providers/runway.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/public/providers/sambanova.svg b/public/providers/sambanova.svg new file mode 100644 index 0000000000..38afe1b9ee --- /dev/null +++ b/public/providers/sambanova.svg @@ -0,0 +1,9 @@ + + SambaNova + + + + \ No newline at end of file diff --git a/public/providers/searchapi.svg b/public/providers/searchapi.svg new file mode 100644 index 0000000000..fe1da13449 --- /dev/null +++ b/public/providers/searchapi.svg @@ -0,0 +1,4 @@ + + SearchApi + + \ No newline at end of file diff --git a/public/providers/sensenova.svg b/public/providers/sensenova.svg index 9bd7ebda11..17f1d82c53 100644 --- a/public/providers/sensenova.svg +++ b/public/providers/sensenova.svg @@ -1,5 +1 @@ - - - - SN - +SenseNova \ No newline at end of file diff --git a/public/providers/snowflake.svg b/public/providers/snowflake.svg new file mode 100644 index 0000000000..f62ab5f446 --- /dev/null +++ b/public/providers/snowflake.svg @@ -0,0 +1,7 @@ + + Snowflake + + \ No newline at end of file diff --git a/public/providers/stepfun.svg b/public/providers/stepfun.svg index 2a7196ea47..920e8a607a 100644 --- a/public/providers/stepfun.svg +++ b/public/providers/stepfun.svg @@ -1,5 +1 @@ - - - - SF - +Stepfun \ No newline at end of file diff --git a/public/providers/suno.svg b/public/providers/suno.svg new file mode 100644 index 0000000000..e2ddb78425 --- /dev/null +++ b/public/providers/suno.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/providers/tavily.svg b/public/providers/tavily.svg new file mode 100644 index 0000000000..c2475ed2be --- /dev/null +++ b/public/providers/tavily.svg @@ -0,0 +1,15 @@ + + Tavily + + + + + + + \ No newline at end of file diff --git a/public/providers/tencent.svg b/public/providers/tencent.svg index d1dff98da9..98da272a26 100644 --- a/public/providers/tencent.svg +++ b/public/providers/tencent.svg @@ -1,5 +1 @@ - - - - TC - +Tencent \ No newline at end of file diff --git a/public/providers/topazlabs.svg b/public/providers/topazlabs.svg new file mode 100644 index 0000000000..c141a9a36b --- /dev/null +++ b/public/providers/topazlabs.svg @@ -0,0 +1,4 @@ + + TopazLabs + + \ No newline at end of file diff --git a/public/providers/trae.svg b/public/providers/trae.svg new file mode 100644 index 0000000000..3d048c0c25 --- /dev/null +++ b/public/providers/trae.svg @@ -0,0 +1,5 @@ + + TRAE + + \ No newline at end of file diff --git a/public/providers/udio.svg b/public/providers/udio.svg new file mode 100644 index 0000000000..7bb9b62c3b --- /dev/null +++ b/public/providers/udio.svg @@ -0,0 +1,5 @@ + + Udio + + \ No newline at end of file diff --git a/public/providers/upstage.svg b/public/providers/upstage.svg new file mode 100644 index 0000000000..d9db3f1838 --- /dev/null +++ b/public/providers/upstage.svg @@ -0,0 +1,14 @@ + + Upstage + + + + + + + + + + + + \ No newline at end of file diff --git a/public/providers/v0.svg b/public/providers/v0.svg new file mode 100644 index 0000000000..32fcc405fd --- /dev/null +++ b/public/providers/v0.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/providers/vercel.svg b/public/providers/vercel.svg new file mode 100644 index 0000000000..75968916c4 --- /dev/null +++ b/public/providers/vercel.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/providers/vllm.svg b/public/providers/vllm.svg new file mode 100644 index 0000000000..3adec97b6b --- /dev/null +++ b/public/providers/vllm.svg @@ -0,0 +1,5 @@ + + vLLM + + + \ No newline at end of file diff --git a/public/providers/volcengine.svg b/public/providers/volcengine.svg new file mode 100644 index 0000000000..1423adbc65 --- /dev/null +++ b/public/providers/volcengine.svg @@ -0,0 +1,11 @@ + + Volcengine (火山引擎) + + + + + \ No newline at end of file diff --git a/public/providers/voyage.svg b/public/providers/voyage.svg new file mode 100644 index 0000000000..76861e9f57 --- /dev/null +++ b/public/providers/voyage.svg @@ -0,0 +1,5 @@ + + Voyage + + \ No newline at end of file diff --git a/public/providers/windsurf.svg b/public/providers/windsurf.svg new file mode 100644 index 0000000000..8f4b214454 --- /dev/null +++ b/public/providers/windsurf.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/providers/xai.svg b/public/providers/xai.svg new file mode 100644 index 0000000000..6f6057d454 --- /dev/null +++ b/public/providers/xai.svg @@ -0,0 +1,4 @@ + + xAI + + \ No newline at end of file diff --git a/public/providers/xinference.svg b/public/providers/xinference.svg new file mode 100644 index 0000000000..d97033d417 --- /dev/null +++ b/public/providers/xinference.svg @@ -0,0 +1,53 @@ + + Xinference + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/providers/yi.svg b/public/providers/yi.svg index 8ddc60de6e..8d0c6470b4 100644 --- a/public/providers/yi.svg +++ b/public/providers/yi.svg @@ -1,5 +1 @@ - - - - YI - +Yi \ No newline at end of file diff --git a/public/providers/zhipu.svg b/public/providers/zhipu.svg new file mode 100644 index 0000000000..a92eaff077 --- /dev/null +++ b/public/providers/zhipu.svg @@ -0,0 +1,6 @@ + + Zhipu (智谱) + + \ No newline at end of file diff --git a/src/shared/components/ProviderIcon.tsx b/src/shared/components/ProviderIcon.tsx index 564ec6ca1d..19aad7ce1a 100644 --- a/src/shared/components/ProviderIcon.tsx +++ b/src/shared/components/ProviderIcon.tsx @@ -1,17 +1,18 @@ "use client"; /** - * ProviderIcon — Renders a provider logo using @lobehub/icons with static asset fallbacks. + * ProviderIcon — Renders a provider logo prioritizing local SVGs for speed. * * Strategy (#529): * 0. If `src` is set (operator-supplied remote icon URL, #2166), render it — this always - * wins over the @lobehub/static resolution below. On load error, falls back to + * wins over the resolution below. On load error, falls back to * `fallbackText`/`fallbackColor` (a colored text badge) if provided, otherwise falls - * through to steps 1-4. - * 1. Try @lobehub/icons direct icon components (no @lobehub/ui peer runtime) - * 2. Fall back to /providers/{id}.png (existing static assets) - * 3. Fall back to /providers/{id}.svg (SVG assets) - * 4. Fall back to a generic AI icon + * through to steps 1-5. + * 1. Try /providers/{id}.svg (local SVG assets — fastest, cached separately from JS bundle) + * 2. Try @lobehub/icons direct React components (no @lobehub/ui peer runtime) + * 3. Fall back to thesvg.org CDN (external SVG) + * 4. Fall back to /providers/{id}.png (legacy static assets) + * 5. Fall back to a generic AI icon * * Usage: * @@ -33,7 +34,7 @@ interface ProviderIconProps { /** * Optional operator-supplied remote icon URL (#2166) — e.g. a custom icon set for an * OpenAI-/Anthropic-compatible provider node. When set, this always takes priority - * over the @lobehub/static resolution. On load error, falls back to `fallbackText` + * over the resolution chain. On load error, falls back to `fallbackText` * (if provided) or the normal resolution chain below. */ src?: string; @@ -51,83 +52,168 @@ function GenericProviderIcon({ size }: { size: number }) { ); } +const KNOWN_SVGS = new Set([ + "360ai", + "alibaba", + "anthropic", + "apikey", + "arcee", + "arcee-ai", + "assemblyai", + "aws", + "azure", + "azureai", + "baichuan", + "baidu", + "bailian", + "baseten", + "bazaarlink", + "brave", + "brave-search", + "cartesia", + "cerebras", + "clarifai", + "claude", + "claude-web", + "cline", + "cloudflare", + "codex", + "cohere", + "comfyui", + "command-code", + "continue", + "copilot", + "coze", + "cursor", + "deepgram", + "deepinfra", + "deepseek", + "dify", + "docker-model-runner", + "doubao", + "droid", + "elevenlabs", + "exa", + "fal", + "fireworks", + "friendli", + "gemini", + "gitlab", + "gitlab-duo", + "google", + "grok", + "groq", + "heroku", + "huggingchat", + "huggingface", + "hyperbolic", + "ibm", + "iflytek", + "inclusionai", + "inference", + "inworld", + "kilo-gateway", + "kilocode", + "kimi", + "kiro", + "krutrim", + "lambda", + "liquid", + "longcat", + "meta", + "metaai", + "minimax", + "mistral", + "modal", + "monsterapi", + "moonshot", + "morph", + "nebius", + "nlpcloud", + "nomic", + "novita", + "nvidia", + "oauth", + "oci", + "ollama", + "openai", + "openclaw", + "opencode", + "openrouter", + "ovhcloud", + "perplexity", + "phind", + "picoclaw", + "playht", + "poe", + "pollinations", + "poolside", + "puter", + "qianfan", + "qwen", + "recraft", + "replicate", + "roocode", + "runway", + "sambanova", + "sap", + "scaleway", + "searchapi", + "searxng-search", + "sensenova", + "serper-search", + "snowflake", + "sparkdesk", + "stepfun", + "suno", + "synthetic", + "tavily", + "tencent", + "topazlabs", + "trae", + "udio", + "upstage", + "v0", + "vercel", + "vllm", + "volcengine", + "voyage", + "wandb", + "windsurf", + "xai", + "xinference", + "yi", + "youcom-search", + "zhipu", +]); + const KNOWN_PNGS = new Set([ + "adapta-web", "agentrouter", - "inner-ai", "aimlapi", "anthropic-m", "blackbox", - "claude", - "continue", - "copilot", - "cursor", - "deepgram", - "ironclaw", - "kie", - "nanobot", - "oai-cc", - "oai-r", - "openclaw", - "zeroclaw", - "adapta-web", "blackbox-web", "cliproxyapi", "empower", "gigachat", - "heroku", + "inner-ai", + "ironclaw", + "kie", "lemonade", "linkup-search", "llamafile", "llamagate", "maritalk", + "nanobot", "nanogpt", "nscale", - "ovhcloud", + "oai-cc", + "oai-r", "piapi", "predibase", "reka", -]); -const KNOWN_SVGS = new Set([ - "apikey", - "bazaarlink", - "brave", - "brave-search", - "cartesia", - "360ai", - "huggingchat", - "iflytek", - "sparkdesk", - "arcee-ai", - "liquid", - "monsterapi", - "nomic", - "poolside", - "clarifai", - "command-code", - "claude-web", - "docker-model-runner", - "droid", - "gitlab", - "gitlab-duo", - "inworld", - "kiro", - "kilo-gateway", - "kilocode", - "modal", - "nlpcloud", - "oauth", - "oci", - "opencode", - "playht", - "puter", - "qianfan", - "sap", - "scaleway", - "serper-search", - "searxng-search", - "synthetic", - "wandb", - "youcom-search", + "zeroclaw", ]); const ProviderIcon = memo(function ProviderIcon({ @@ -143,20 +229,22 @@ const ProviderIcon = memo(function ProviderIcon({ }: ProviderIconProps) { const normalizedId = providerId.toLowerCase(); const lobeIcon = getLobeProviderIcon(normalizedId, type); - const hasPng = KNOWN_PNGS.has(normalizedId); const hasSvg = KNOWN_SVGS.has(normalizedId); + const hasPng = KNOWN_PNGS.has(normalizedId); const [failedAssets, setFailedAssets] = useState>({}); const [remoteSrcFailed, setRemoteSrcFailed] = useState(false); - const pngKey = `${normalizedId}:png`; const svgKey = `${normalizedId}:svg`; - const usePng = !lobeIcon && hasPng && !failedAssets[pngKey]; - const useSvg = !lobeIcon && hasSvg && !failedAssets[svgKey] && (!hasPng || failedAssets[pngKey]); + const pngKey = `${normalizedId}:png`; + const theSvgKey = `${normalizedId}:thesvg`; const trimmedSrc = typeof src === "string" ? src.trim() : ""; + const svgFailed = failedAssets[svgKey]; + const theSvgFailed = failedAssets[theSvgKey]; + const pngFailed = failedAssets[pngKey]; - // #2166: a custom remote icon URL always wins over the @lobehub/static resolution - // below. It is a plain (not next/image) so operators can point at any host + // #2166: a custom remote icon URL always wins over the resolution chain below. + // It is a plain (not next/image) so operators can point at any host // without requiring `images.remotePatterns` allow-listing for arbitrary domains. if (trimmedSrc && !remoteSrcFailed) { return ( @@ -199,43 +287,8 @@ const ProviderIcon = memo(function ProviderIcon({ ); } - if (lobeIcon) { - return ( - - {createElement(lobeIcon, { - "aria-label": providerId, - size, - style: { flex: "none" }, - })} - - ); - } - - if (usePng) { - return ( - - {providerId} { - setFailedAssets((current) => ({ ...current, [pngKey]: true })); - }} - unoptimized - /> - - ); - } - - if (useSvg) { + // Tier 1: Local SVG — fastest, cached separately from the JS bundle + if (hasSvg && !svgFailed) { return ( + {createElement(lobeIcon, { + "aria-label": providerId, + size, + style: { flex: "none" }, + })} + + ); + } + + // Tier 3: thesvg.org CDN — external SVG fallback for unknown providers + if (!theSvgFailed) { + return ( + + {/* eslint-disable-next-line @next/next/no-img-element -- external SVG from thesvg.org, not a static/known asset */} + {providerId} setFailedAssets((current) => ({ ...current, [theSvgKey]: true }))} + /> + + ); + } + + // Tier 4: Local PNG — last resort before generic icon + if (hasPng && !pngFailed) { + return ( + + {providerId} setFailedAssets((current) => ({ ...current, [pngKey]: true }))} + unoptimized + /> + + ); + } + + // Tier 5: Generic AI icon return ( diff --git a/tests/unit/ui/ProviderIcon-icon-url.test.tsx b/tests/unit/ui/ProviderIcon-icon-url.test.tsx index 19977c1455..f34ace21a5 100644 --- a/tests/unit/ui/ProviderIcon-icon-url.test.tsx +++ b/tests/unit/ui/ProviderIcon-icon-url.test.tsx @@ -23,8 +23,8 @@ const { default: ProviderIcon } = await import("@/shared/components/ProviderIcon // ── Helpers ─────────────────────────────────────────────────────────────────── // Deliberately not registered in @lobehub/icons aliases or the KNOWN_PNGS/KNOWN_SVGS -// static-asset sets, so tests exercise only the `src` override + generic-icon fallback -// paths, never the @lobehub/static resolution chain. +// static-asset sets, so tests exercise only the `src` override + fallback chain +// (thesvg.org → generic icon). Never reaches the local SVG or @lobehub tiers. const UNKNOWN_PROVIDER_ID = "openai-compatible-test-node-xyz"; const containers: HTMLElement[] = []; @@ -50,8 +50,9 @@ function fireImgError(container: HTMLElement) { } beforeEach(() => { - (globalThis as typeof globalThis & { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = - true; + ( + globalThis as typeof globalThis & { IS_REACT_ACT_ENVIRONMENT?: boolean } + ).IS_REACT_ACT_ENVIRONMENT = true; }); afterEach(() => { @@ -71,18 +72,31 @@ describe("ProviderIcon — custom remote icon URL (#2166)", () => { expect(img?.getAttribute("src")).toBe("https://example.com/logo.png"); }); - it("falls back to the generic icon when `src` is unset", () => { + it("falls back to thesvg.org CDN when `src` is unset (Tier 3 for unknown providers)", () => { const container = renderIcon({}); - expect(container.querySelector("img")).toBeNull(); - expect(container.querySelector("svg")).not.toBeNull(); + const img = container.querySelector("img"); + expect(img).not.toBeNull(); + expect(img?.getAttribute("src")).toBe( + "https://thesvg.org/icons/openai-compatible-test-node-xyz/default.svg" + ); }); - it("falls back to the generic (lobehub/static) icon chain when `src` load fails and no fallbackText is given", () => { + it("falls back through thesvg.org CDN then generic icon when `src` load fails and no fallbackText is given", () => { const container = renderIcon({ src: "https://example.com/broken.png" }); expect(container.querySelector("img")).not.toBeNull(); fireImgError(container); + // Falls back to thesvg.org + const img = container.querySelector("img"); + expect(img).not.toBeNull(); + expect(img?.getAttribute("src")).toBe( + "https://thesvg.org/icons/openai-compatible-test-node-xyz/default.svg" + ); + + fireImgError(container); + + // thesvg.org fails → generic SVG icon expect(container.querySelector("img")).toBeNull(); expect(container.querySelector("svg")).not.toBeNull(); }); @@ -102,9 +116,12 @@ describe("ProviderIcon — custom remote icon URL (#2166)", () => { expect(container.textContent).toBe("OC"); }); - it("ignores a whitespace-only src and falls back to the generic icon", () => { + it("ignores a whitespace-only src and falls back to thesvg.org CDN", () => { const container = renderIcon({ src: " " }); - expect(container.querySelector("img")).toBeNull(); - expect(container.querySelector("svg")).not.toBeNull(); + const img = container.querySelector("img"); + expect(img).not.toBeNull(); + expect(img?.getAttribute("src")).toBe( + "https://thesvg.org/icons/openai-compatible-test-node-xyz/default.svg" + ); }); });