mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-07-26 09:52:11 +03:00
chore(release): v3.3.3 — Core UI bugfixes and AutoUpdate repairs
This commit is contained in:
32
.github/workflows/ci.yml
vendored
32
.github/workflows/ci.yml
vendored
@@ -18,8 +18,8 @@ jobs:
|
||||
name: Lint
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-node@v6
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: npm
|
||||
@@ -36,8 +36,8 @@ jobs:
|
||||
name: Security Audit
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-node@v6
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: npm
|
||||
@@ -55,8 +55,8 @@ jobs:
|
||||
matrix:
|
||||
node-version: [20, 22]
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-node@v6
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
cache: npm
|
||||
@@ -74,8 +74,8 @@ jobs:
|
||||
JWT_SECRET: ci-test-secret-with-sufficient-length-for-validation
|
||||
API_KEY_SECRET: ci-test-api-key-secret-long
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-node@v6
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
cache: npm
|
||||
@@ -90,8 +90,8 @@ jobs:
|
||||
JWT_SECRET: ci-test-secret-with-sufficient-length-for-validation
|
||||
API_KEY_SECRET: ci-test-api-key-secret-long
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-node@v6
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: npm
|
||||
@@ -109,8 +109,8 @@ jobs:
|
||||
JWT_SECRET: ci-test-secret-with-sufficient-length-for-validation
|
||||
API_KEY_SECRET: ci-test-api-key-secret-long
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-node@v6
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: npm
|
||||
@@ -129,8 +129,8 @@ jobs:
|
||||
INITIAL_PASSWORD: ci-test-password-for-integration
|
||||
DATA_DIR: /tmp/omniroute-ci
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-node@v6
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: npm
|
||||
@@ -145,8 +145,8 @@ jobs:
|
||||
JWT_SECRET: ci-test-secret-with-sufficient-length-for-validation
|
||||
API_KEY_SECRET: ci-test-api-key-secret-long
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-node@v6
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: npm
|
||||
|
||||
14
.github/workflows/docker-publish.yml
vendored
14
.github/workflows/docker-publish.yml
vendored
@@ -25,24 +25,24 @@ jobs:
|
||||
IMAGE_NAME: diegosouzapw/omniroute
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event_name == 'workflow_dispatch' && format('refs/tags/v{0}', inputs.version) || '' }}
|
||||
|
||||
- name: Set up QEMU (for multi-arch builds)
|
||||
uses: docker/setup-qemu-action@v4
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v4
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v4
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v4
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
@@ -61,7 +61,7 @@ jobs:
|
||||
echo "Publishing Docker image: $IMAGE_NAME:$VERSION"
|
||||
|
||||
- name: Build and push multi-arch image
|
||||
uses: docker/build-push-action@v7
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
target: runner-base
|
||||
@@ -83,7 +83,7 @@ jobs:
|
||||
docker buildx imagetools inspect "${{ env.IMAGE_NAME }}:${{ steps.version.outputs.version }}"
|
||||
|
||||
- name: Update Docker Hub description
|
||||
uses: peter-evans/dockerhub-description@v5
|
||||
uses: peter-evans/dockerhub-description@v4
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
14
.github/workflows/electron-release.yml
vendored
14
.github/workflows/electron-release.yml
vendored
@@ -22,7 +22,7 @@ jobs:
|
||||
version: ${{ steps.validate.outputs.version }}
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
@@ -70,16 +70,16 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v6
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: npm
|
||||
|
||||
- name: Cache node_modules
|
||||
uses: actions/cache@v5
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: node_modules
|
||||
key: ${{ runner.os }}-node-${{ hashFiles('package-lock.json') }}
|
||||
@@ -146,7 +146,7 @@ jobs:
|
||||
fi
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v7
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: electron-${{ matrix.platform }}
|
||||
path: release-assets/
|
||||
@@ -157,12 +157,12 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Download all artifacts
|
||||
uses: actions/download-artifact@v8
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: release-assets
|
||||
merge-multiple: true
|
||||
|
||||
8
.github/workflows/npm-publish.yml
vendored
8
.github/workflows/npm-publish.yml
vendored
@@ -43,10 +43,10 @@ jobs:
|
||||
environment: NPM_TOKEN
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v6
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
registry-url: https://registry.npmjs.org
|
||||
@@ -111,11 +111,11 @@ jobs:
|
||||
run: |
|
||||
VERSION="${{ steps.resolve.outputs.version }}"
|
||||
TAG="${{ steps.resolve.outputs.tag }}"
|
||||
|
||||
|
||||
echo "Configuring for GitHub Packages..."
|
||||
echo "//npm.pkg.github.com/:_authToken=${{ secrets.GITHUB_TOKEN }}" > .npmrc
|
||||
npm pkg set name="@diegosouzapw/omniroute"
|
||||
|
||||
|
||||
if [ "$TAG" = "latest" ]; then
|
||||
npm publish --registry=https://npm.pkg.github.com || echo "⚠️ Version ${VERSION} might already be published on GitHub."
|
||||
else
|
||||
|
||||
11
CHANGELOG.md
11
CHANGELOG.md
@@ -4,6 +4,17 @@
|
||||
|
||||
---
|
||||
|
||||
## [3.3.3] - 2026-03-29
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- **CI/CD Reliability:** Patched GitHub Actions to stable dependency versions (`actions/checkout@v4`, `actions/upload-artifact@v4`) to mitigate unannounced builder environment deprecations.
|
||||
- **Image Fallbacks:** Replaced arbitrary fallback chains in `ProviderIcon.tsx` with explicit asset validation to prevent UI loading `<Image>` components for files that don't exist, eliminating `404` errors in dashboard console logs (#745).
|
||||
- **Admin Updater:** Dynamic source-installation detection for the dashboard Updater. Safely disables the `Update Now` button when OmniRoute is built locally rather than through npm, prompting for `git pull` (#743).
|
||||
- **Update ERESOLVE Error:** Injected `package.json` overrides for `react`/`react-dom` and enabled `--legacy-peer-deps` within the internal automatic updater scripts to resolve breaking dependency tree conflicts with `@lobehub/ui`.
|
||||
|
||||
---
|
||||
|
||||
## [3.3.2] - 2026-03-29
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
openapi: 3.1.0
|
||||
info:
|
||||
title: OmniRoute API
|
||||
version: 3.3.2
|
||||
version: 3.3.3
|
||||
description: |
|
||||
OmniRoute is a local-first AI API proxy router. It provides an OpenAI-compatible
|
||||
endpoint that routes requests to multiple AI providers with load balancing,
|
||||
|
||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "omniroute",
|
||||
"version": "3.3.2",
|
||||
"version": "3.3.3",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "omniroute",
|
||||
"version": "3.3.2",
|
||||
"version": "3.3.3",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"workspaces": [
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "omniroute",
|
||||
"version": "3.3.2",
|
||||
"version": "3.3.3",
|
||||
"description": "Smart AI Router with auto fallback — route to FREE & cheap models, zero downtime. Works with Cursor, Cline, Claude Desktop, Codex, and any OpenAI-compatible tool.",
|
||||
"type": "module",
|
||||
"bin": {
|
||||
@@ -162,6 +162,8 @@
|
||||
},
|
||||
"overrides": {
|
||||
"dompurify": "^3.3.2",
|
||||
"path-to-regexp": "^8.4.0"
|
||||
"path-to-regexp": "^8.4.0",
|
||||
"react": "$react",
|
||||
"react-dom": "$react-dom"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -546,18 +546,22 @@ export default function HomePageClient({ machineId }) {
|
||||
<div>
|
||||
<p className="font-semibold text-sm">Update Available: v{versionInfo.latest}</p>
|
||||
<p className="text-xs opacity-80 mt-0.5">
|
||||
{t("updateAvailableDesc") ||
|
||||
`You are currently using v${versionInfo.current}. Update to access the latest features and bug fixes.`}
|
||||
{versionInfo.autoUpdateSupported
|
||||
? t("updateAvailableDesc") ||
|
||||
`You are currently using v${versionInfo.current}. Update to access the latest features and bug fixes.`
|
||||
: versionInfo.autoUpdateError ||
|
||||
"Manual update required for this installation type."}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
size="sm"
|
||||
onClick={handleUpdate}
|
||||
disabled={updating}
|
||||
onClick={versionInfo.autoUpdateSupported ? handleUpdate : undefined}
|
||||
disabled={updating || !versionInfo.autoUpdateSupported}
|
||||
className="shrink-0 ml-4 font-semibold"
|
||||
title={versionInfo.autoUpdateError || ""}
|
||||
>
|
||||
{t("updateNow") || "Update Now"}
|
||||
{versionInfo.autoUpdateSupported ? t("updateNow") || "Update Now" : "Manual Update"}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -7,6 +7,7 @@ import Card from "@/shared/components/Card";
|
||||
import Badge from "@/shared/components/Badge";
|
||||
import QuotaProgressBar from "./QuotaProgressBar";
|
||||
import { calculatePercentage } from "./utils";
|
||||
import ProviderIcon from "@/shared/components/ProviderIcon";
|
||||
|
||||
const planVariants = {
|
||||
free: "default",
|
||||
@@ -70,15 +71,7 @@ export default function ProviderLimitCard({
|
||||
{provider?.slice(0, 2).toUpperCase() || "PR"}
|
||||
</span>
|
||||
) : (
|
||||
<Image
|
||||
src={`/providers/${provider}.png`}
|
||||
alt={provider || t("providerLimits")}
|
||||
width={40}
|
||||
height={40}
|
||||
className="object-contain rounded-lg"
|
||||
sizes="40px"
|
||||
onError={() => setImgError(true)}
|
||||
/>
|
||||
<ProviderIcon providerId={provider} size={40} />
|
||||
)}
|
||||
</div>
|
||||
|
||||
|
||||
@@ -95,6 +95,17 @@ export async function POST(req: NextRequest) {
|
||||
}
|
||||
|
||||
const config = getAutoUpdateConfig();
|
||||
const validation = await validateAutoUpdateRuntime(config);
|
||||
|
||||
if (!validation.supported) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: validation.reason || "Auto-update is not supported in this environment.",
|
||||
},
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
// If we are in docker-compose mode, use the detached shell script background updates
|
||||
if (config.mode === "docker-compose") {
|
||||
@@ -132,9 +143,13 @@ export async function POST(req: NextRequest) {
|
||||
try {
|
||||
// Step 1: Install
|
||||
send({ step: "install", status: "running", message: `Installing omniroute@${latest}...` });
|
||||
await execFileAsync("npm", ["install", "-g", `omniroute@${latest}`, "--ignore-scripts"], {
|
||||
timeout: 300000,
|
||||
});
|
||||
await execFileAsync(
|
||||
"npm",
|
||||
["install", "-g", `omniroute@${latest}`, "--ignore-scripts", "--legacy-peer-deps"],
|
||||
{
|
||||
timeout: 300000,
|
||||
}
|
||||
);
|
||||
send({ step: "install", status: "done", message: `Installed omniroute@${latest}` });
|
||||
|
||||
// Step 2: Rebuild native modules (critical for better-sqlite3)
|
||||
|
||||
@@ -55,15 +55,26 @@ function shellQuote(value: string): string {
|
||||
}
|
||||
|
||||
function parsePatchCommits(raw: string | undefined): string[] {
|
||||
return (raw || "").split(/[\s,]+/).map((value) => value.trim()).filter(Boolean);
|
||||
return (raw || "")
|
||||
.split(/[\s,]+/)
|
||||
.map((value) => value.trim())
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
export function getAutoUpdateConfig(env: NodeJS.ProcessEnv = process.env): AutoUpdateConfig {
|
||||
const dataDir = env.DATA_DIR || "/tmp/omniroute";
|
||||
const repoDir = env.AUTO_UPDATE_REPO_DIR || "/workspace/omniroute";
|
||||
|
||||
let mode = normalizeMode(env.AUTO_UPDATE_MODE);
|
||||
if (mode === "npm") {
|
||||
const fs = require("node:fs");
|
||||
if (fs.existsSync(path.join(process.cwd(), ".git"))) {
|
||||
mode = "source" as any;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
mode: normalizeMode(env.AUTO_UPDATE_MODE),
|
||||
mode,
|
||||
repoDir,
|
||||
composeFile: env.AUTO_UPDATE_COMPOSE_FILE || path.join(repoDir, "docker-compose.yml"),
|
||||
composeProfile: env.AUTO_UPDATE_COMPOSE_PROFILE || "cli",
|
||||
@@ -97,6 +108,15 @@ export async function validateAutoUpdateRuntime(
|
||||
execFileImpl: ExecFileLike = execFileAsync,
|
||||
existsImpl: (targetPath: string) => Promise<boolean> = pathExists
|
||||
): Promise<AutoUpdateValidation> {
|
||||
if (config.mode === ("source" as any)) {
|
||||
return {
|
||||
supported: false,
|
||||
reason:
|
||||
"Manual 'git pull && npm install && npm run build' is required for source installations.",
|
||||
composeCommand: null,
|
||||
};
|
||||
}
|
||||
|
||||
if (config.mode !== "docker-compose") {
|
||||
return { supported: true, reason: null, composeCommand: null };
|
||||
}
|
||||
@@ -139,7 +159,8 @@ export async function validateAutoUpdateRuntime(
|
||||
if (!composeCommand) {
|
||||
return {
|
||||
supported: false,
|
||||
reason: "Neither docker compose nor docker-compose is available inside the OmniRoute container.",
|
||||
reason:
|
||||
"Neither docker compose nor docker-compose is available inside the OmniRoute container.",
|
||||
composeCommand: null,
|
||||
};
|
||||
}
|
||||
@@ -150,7 +171,7 @@ export async function validateAutoUpdateRuntime(
|
||||
export function buildNpmUpdateScript(latest: string): string {
|
||||
return [
|
||||
"set -eu",
|
||||
`npm install -g omniroute@${latest} --ignore-scripts`,
|
||||
`npm install -g omniroute@${latest} --ignore-scripts --legacy-peer-deps`,
|
||||
"if command -v pm2 >/dev/null 2>&1; then",
|
||||
" pm2 restart omniroute || true",
|
||||
"fi",
|
||||
@@ -173,7 +194,7 @@ export function buildDockerComposeUpdateScript({
|
||||
? 'docker compose -f "$COMPOSE_FILE" up -d --build "$SERVICE"'
|
||||
: 'docker-compose -f "$COMPOSE_FILE" up -d --build "$SERVICE"';
|
||||
const patchLines = config.patchCommits.length
|
||||
? [`git cherry-pick --keep-redundant-commits ${config.patchCommits.map(shellQuote).join(' ')}`]
|
||||
? [`git cherry-pick --keep-redundant-commits ${config.patchCommits.map(shellQuote).join(" ")}`]
|
||||
: [];
|
||||
|
||||
return [
|
||||
@@ -189,13 +210,13 @@ export function buildDockerComposeUpdateScript({
|
||||
'git config --global --add safe.directory "$REPO_DIR" >/dev/null 2>&1 || true',
|
||||
'if [ -n "$(git status --porcelain)" ]; then',
|
||||
' echo "[AutoUpdate] Refusing update: git worktree has local changes." >&2',
|
||||
' exit 1',
|
||||
'fi',
|
||||
" exit 1",
|
||||
"fi",
|
||||
'git fetch --tags "$REMOTE"',
|
||||
'if ! git rev-parse -q --verify "refs/tags/$TARGET_TAG" >/dev/null 2>&1; then',
|
||||
' echo "[AutoUpdate] Tag $TARGET_TAG not found on remote $REMOTE." >&2',
|
||||
' exit 1',
|
||||
'fi',
|
||||
" exit 1",
|
||||
"fi",
|
||||
'backup_branch="autoupdate/pre-${TARGET_TAG#v}-$(date +%Y%m%d-%H%M%S)"',
|
||||
'git branch "$backup_branch" >/dev/null 2>&1 || true',
|
||||
'git checkout -B "autoupdate/${TARGET_TAG#v}" "$TARGET_TAG"',
|
||||
|
||||
@@ -114,6 +114,99 @@ function GenericProviderIcon({ size }: { size: number }) {
|
||||
);
|
||||
}
|
||||
|
||||
const KNOWN_PNGS = new Set([
|
||||
"aimlapi",
|
||||
"alibaba",
|
||||
"alicode-intl",
|
||||
"alicode",
|
||||
"anthropic-m",
|
||||
"anthropic",
|
||||
"antigravity",
|
||||
"bailian-coding-plan",
|
||||
"blackbox",
|
||||
"brave-search",
|
||||
"brave",
|
||||
"cerebras",
|
||||
"claude",
|
||||
"cline",
|
||||
"codex",
|
||||
"cohere",
|
||||
"continue",
|
||||
"copilot",
|
||||
"cursor",
|
||||
"deepgram",
|
||||
"deepseek",
|
||||
"droid",
|
||||
"exa-search",
|
||||
"fireworks",
|
||||
"gemini-cli",
|
||||
"gemini",
|
||||
"github",
|
||||
"glm",
|
||||
"groq",
|
||||
"iflow",
|
||||
"ironclaw",
|
||||
"kilo-gateway",
|
||||
"kilocode",
|
||||
"kimi-coding-apikey",
|
||||
"kimi-coding",
|
||||
"kimi",
|
||||
"kiro",
|
||||
"longcat",
|
||||
"minimax-cn",
|
||||
"minimax",
|
||||
"mistral",
|
||||
"nanobot",
|
||||
"nebius",
|
||||
"nvidia",
|
||||
"oai-cc",
|
||||
"oai-r",
|
||||
"ollama-cloud",
|
||||
"openai",
|
||||
"openclaw",
|
||||
"openrouter",
|
||||
"perplexity-search",
|
||||
"perplexity",
|
||||
"pollinations",
|
||||
"qwen",
|
||||
"roo",
|
||||
"serper-search",
|
||||
"serper",
|
||||
"siliconflow",
|
||||
"tavily-search",
|
||||
"tavily",
|
||||
"together",
|
||||
"xai",
|
||||
"zeroclaw",
|
||||
]);
|
||||
const KNOWN_SVGS = new Set([
|
||||
"apikey",
|
||||
"assemblyai",
|
||||
"brave",
|
||||
"cartesia",
|
||||
"cloudflare-ai",
|
||||
"comfyui",
|
||||
"elevenlabs",
|
||||
"exa-search",
|
||||
"exa",
|
||||
"huggingface",
|
||||
"hyperbolic",
|
||||
"inworld",
|
||||
"nanobanana",
|
||||
"oauth",
|
||||
"opencode-go",
|
||||
"opencode-zen",
|
||||
"opencode",
|
||||
"playht",
|
||||
"puter",
|
||||
"scaleway",
|
||||
"sdwebui",
|
||||
"synthetic",
|
||||
"vertex",
|
||||
"windsurf",
|
||||
"zai",
|
||||
]);
|
||||
|
||||
const ProviderIcon = memo(function ProviderIcon({
|
||||
providerId,
|
||||
size = 24,
|
||||
@@ -121,10 +214,14 @@ const ProviderIcon = memo(function ProviderIcon({
|
||||
className,
|
||||
style,
|
||||
}: ProviderIconProps) {
|
||||
const lobehubId = LOBEHUB_PROVIDER_MAP[providerId.toLowerCase()] ?? null;
|
||||
const normalizedId = providerId.toLowerCase();
|
||||
const lobehubId = LOBEHUB_PROVIDER_MAP[normalizedId] ?? null;
|
||||
const hasPng = KNOWN_PNGS.has(normalizedId);
|
||||
const hasSvg = KNOWN_SVGS.has(normalizedId);
|
||||
|
||||
const [useLobehub, setUseLobehub] = useState(lobehubId !== null);
|
||||
const [usePng, setUsePng] = useState(true);
|
||||
const [useSvg, setUseSvg] = useState(true);
|
||||
const [usePng, setUsePng] = useState(hasPng);
|
||||
const [useSvg, setUseSvg] = useState(!hasPng && hasSvg);
|
||||
|
||||
if (useLobehub && lobehubId) {
|
||||
return (
|
||||
@@ -146,12 +243,15 @@ const ProviderIcon = memo(function ProviderIcon({
|
||||
style={{ display: "inline-flex", alignItems: "center", ...style }}
|
||||
>
|
||||
<Image
|
||||
src={`/providers/${providerId}.png`}
|
||||
src={`/providers/${normalizedId}.png`}
|
||||
alt={providerId}
|
||||
width={size}
|
||||
height={size}
|
||||
style={{ objectFit: "contain" }}
|
||||
onError={() => setUsePng(false)}
|
||||
onError={() => {
|
||||
setUsePng(false);
|
||||
setUseSvg(hasSvg);
|
||||
}}
|
||||
unoptimized
|
||||
/>
|
||||
</span>
|
||||
@@ -165,7 +265,7 @@ const ProviderIcon = memo(function ProviderIcon({
|
||||
style={{ display: "inline-flex", alignItems: "center", ...style }}
|
||||
>
|
||||
<Image
|
||||
src={`/providers/${providerId}.svg`}
|
||||
src={`/providers/${normalizedId}.svg`}
|
||||
alt={providerId}
|
||||
width={size}
|
||||
height={size}
|
||||
|
||||
Reference in New Issue
Block a user