diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index fa9690098..abd247795 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -23,6 +23,9 @@ jobs:
- platform: macos-latest
target: aarch64-apple-darwin
name: macOS-arm64
+ - platform: macos-latest
+ target: x86_64-apple-darwin
+ name: macOS-x64
- platform: windows-latest
target: x86_64-pc-windows-msvc
name: Windows-x64
@@ -73,6 +76,7 @@ jobs:
### Downloads
- **macOS (Apple Silicon)**: `proxycast_*_aarch64.dmg`
+ - **macOS (Intel)**: `proxycast_*_x64.dmg`
- **Windows (64-bit)**: `proxycast_*_x64-setup.exe`
### macOS 首次安装
diff --git a/package.json b/package.json
index d62fe425b..f9d455af2 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "proxycast",
"private": true,
- "version": "0.13.0",
+ "version": "0.14.0",
"type": "module",
"scripts": {
"dev": "vite",
diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml
index c28fa7a9c..4d5d0bafa 100644
--- a/src-tauri/Cargo.toml
+++ b/src-tauri/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "proxycast"
-version = "0.13.0"
+version = "0.14.0"
description = "AI API Proxy Desktop App"
authors = ["you"]
edition = "2021"
diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json
index 40b00dc94..73f427bed 100644
--- a/src-tauri/tauri.conf.json
+++ b/src-tauri/tauri.conf.json
@@ -1,7 +1,7 @@
{
"$schema": "https://schema.tauri.app/config/2",
"productName": "ProxyCast",
- "version": "0.13.0",
+ "version": "0.14.0",
"identifier": "com.proxycast.app",
"build": {
"beforeDevCommand": "npm run dev",
diff --git a/src/components/api-server/ApiServerPage.tsx b/src/components/api-server/ApiServerPage.tsx
index 988024324..501b3507c 100644
--- a/src/components/api-server/ApiServerPage.tsx
+++ b/src/components/api-server/ApiServerPage.tsx
@@ -15,6 +15,7 @@ import {
import { LogsTab } from "./LogsTab";
import { RoutesTab } from "./RoutesTab";
import { HelpTip } from "@/components/HelpTip";
+import { ProviderIcon } from "@/icons/providers";
import {
startServer,
stopServer,
@@ -260,7 +261,7 @@ export function ApiServerPage() {
path: "/v1/chat/completions",
needsAuth: true,
body: JSON.stringify({
- model: "claude-sonnet-4-5",
+ model: "claude-opus-4-5-20251101",
messages: [{ role: "user", content: "Say hi in one word" }],
}),
},
@@ -271,7 +272,7 @@ export function ApiServerPage() {
path: "/v1/messages",
needsAuth: true,
body: JSON.stringify({
- model: "claude-sonnet-4-5",
+ model: "claude-opus-4-5-20251101",
max_tokens: 100,
messages: [
{
@@ -555,12 +556,24 @@ export function ApiServerPage() {
{(
[
- { id: "kiro", label: "Kiro (AWS)" },
- { id: "gemini", label: "Gemini (Google)" },
- { id: "qwen", label: "Qwen (阿里)" },
- { id: "antigravity", label: "Antigravity (Gemini 3 Pro)" },
- { id: "openai", label: "OpenAI" },
- { id: "claude", label: "Claude (Anthropic)" },
+ { id: "kiro", label: "Kiro (AWS)", iconType: "kiro" },
+ {
+ id: "gemini",
+ label: "Gemini (Google)",
+ iconType: "gemini",
+ },
+ { id: "qwen", label: "Qwen (阿里)", iconType: "qwen" },
+ {
+ id: "antigravity",
+ label: "Antigravity (Gemini 3 Pro)",
+ iconType: "gemini",
+ },
+ { id: "openai", label: "OpenAI", iconType: "openai" },
+ {
+ id: "claude",
+ label: "Claude (Anthropic)",
+ iconType: "claude",
+ },
] as const
).map((p) => {
const overview = poolOverview.find(
@@ -572,12 +585,13 @@ export function ApiServerPage() {
key={p.id}
onClick={() => handleSetDefaultProvider(p.id)}
disabled={loading}
- className={`rounded-lg px-4 py-2 text-sm font-medium ${
+ className={`flex items-center gap-2 rounded-lg px-4 py-2 text-sm font-medium ${
defaultProvider === p.id
? "bg-primary text-primary-foreground"
: "border hover:bg-muted"
} disabled:opacity-50`}
>
+
{p.label}
{count > 0 && (
({count})
diff --git a/src/components/clients/AppTabs.tsx b/src/components/clients/AppTabs.tsx
index d40a10126..afced7bfa 100644
--- a/src/components/clients/AppTabs.tsx
+++ b/src/components/clients/AppTabs.tsx
@@ -1,15 +1,36 @@
import { cn } from "@/lib/utils";
import { AppType } from "@/lib/api/switch";
+import { ProviderIcon } from "@/icons/providers";
interface AppTabsProps {
activeApp: AppType;
onAppChange: (app: AppType) => void;
}
-const apps: { id: AppType; label: string; description: string }[] = [
- { id: "claude", label: "Claude Code", description: "Claude CLI 配置" },
- { id: "codex", label: "Codex", description: "OpenAI Codex CLI" },
- { id: "gemini", label: "Gemini", description: "Google Gemini CLI" },
+const apps: {
+ id: AppType;
+ label: string;
+ description: string;
+ iconType: string;
+}[] = [
+ {
+ id: "claude",
+ label: "Claude Code",
+ description: "Claude CLI 配置",
+ iconType: "claude",
+ },
+ {
+ id: "codex",
+ label: "Codex",
+ description: "OpenAI Codex CLI",
+ iconType: "openai",
+ },
+ {
+ id: "gemini",
+ label: "Gemini",
+ description: "Google Gemini CLI",
+ iconType: "gemini",
+ },
];
export function AppTabs({ activeApp, onAppChange }: AppTabsProps) {
@@ -20,13 +41,14 @@ export function AppTabs({ activeApp, onAppChange }: AppTabsProps) {
key={app.id}
onClick={() => onAppChange(app.id)}
className={cn(
- "px-4 py-2 rounded-t-lg text-sm font-medium transition-colors",
+ "flex items-center gap-2 px-4 py-2 rounded-t-lg text-sm font-medium transition-colors",
activeApp === app.id
? "bg-primary text-primary-foreground"
: "hover:bg-muted text-muted-foreground",
)}
title={app.description}
>
+
{app.label}
))}
diff --git a/src/components/clients/ProviderCard.tsx b/src/components/clients/ProviderCard.tsx
index a27193afa..768cd6f64 100644
--- a/src/components/clients/ProviderCard.tsx
+++ b/src/components/clients/ProviderCard.tsx
@@ -1,6 +1,39 @@
import { Check, Edit2, Trash2, Zap } from "lucide-react";
import { Provider } from "@/lib/api/switch";
import { cn } from "@/lib/utils";
+import { ProviderIcon } from "@/icons/providers";
+
+// 从供应商名称和分类推断图标类型
+function getProviderTypeFromName(name: string, category: string): string {
+ const lowerName = name.toLowerCase();
+
+ // 精确匹配
+ if (lowerName.includes("智谱") || lowerName.includes("glm")) return "zhipu";
+ if (lowerName.includes("proxycast")) return "proxycast";
+ if (lowerName.includes("deepseek")) return "deepseek";
+ if (lowerName.includes("kimi")) return "kimi";
+ if (lowerName.includes("minimax")) return "minimax";
+ if (lowerName.includes("doubao") || lowerName.includes("豆包"))
+ return "doubao";
+ if (lowerName.includes("qwen") || lowerName.includes("通义")) return "qwen";
+ if (lowerName.includes("claude")) return "claude";
+ if (lowerName.includes("anthropic")) return "anthropic";
+ if (lowerName.includes("openai")) return "openai";
+ if (lowerName.includes("gemini")) return "gemini";
+ if (lowerName.includes("google")) return "google";
+ if (lowerName.includes("kiro")) return "kiro";
+ if (lowerName.includes("azure")) return "azure";
+ if (lowerName.includes("alibaba") || lowerName.includes("阿里"))
+ return "alibaba";
+ if (lowerName.includes("copilot")) return "copilot";
+
+ // 根据分类推断
+ if (category === "custom") return "custom";
+ if (category === "proxy") return "amp";
+
+ // 默认回退
+ return lowerName.replace(/\s+/g, "");
+}
interface ProviderCardProps {
provider: Provider;
@@ -34,11 +67,15 @@ export function ProviderCard({
-
- {provider.icon || provider.name.charAt(0).toUpperCase()}
+
{provider.name}
diff --git a/src/components/clients/ProviderForm.tsx b/src/components/clients/ProviderForm.tsx
index 5b452d6f5..8045d86c7 100644
--- a/src/components/clients/ProviderForm.tsx
+++ b/src/components/clients/ProviderForm.tsx
@@ -3,6 +3,7 @@ import { X, ExternalLink, Wand2, Eye, EyeOff, Database } from "lucide-react";
import { Provider, AppType } from "@/lib/api/switch";
import { getConfig } from "@/hooks/useTauri";
import { cn } from "@/lib/utils";
+import { ProviderIcon } from "@/icons/providers";
import {
providerPoolApi,
CredentialDisplay,
@@ -372,7 +373,6 @@ export function ProviderForm({
const generateJsonFromFields = useCallback(() => {
const env: Record
= {};
if (apiKey) {
- env.ANTHROPIC_AUTH_TOKEN = apiKey;
env.ANTHROPIC_API_KEY = apiKey;
}
if (baseUrl) env.ANTHROPIC_BASE_URL = baseUrl;
@@ -790,10 +790,7 @@ export function ProviderForm({
: "border-border hover:border-muted-foreground/50",
)}
>
-
+
{preset.name}
))}
diff --git a/src/components/provider-pool/ProviderPoolPage.tsx b/src/components/provider-pool/ProviderPoolPage.tsx
index 65b9d35a3..976bc761c 100644
--- a/src/components/provider-pool/ProviderPoolPage.tsx
+++ b/src/components/provider-pool/ProviderPoolPage.tsx
@@ -19,6 +19,7 @@ import { getConfig, saveConfig, Config } from "@/hooks/useTauri";
import { GeminiApiKeySection } from "./GeminiApiKeySection";
import { VertexAISection } from "./VertexAISection";
import { AmpConfigSection } from "./AmpConfigSection";
+import { ProviderIcon } from "@/icons/providers";
import type {
PoolProviderType,
CredentialDisplay,
@@ -381,6 +382,7 @@ export const ProviderPoolPage = forwardRef(
: "border-transparent text-muted-foreground hover:text-foreground"
}`}
>
+
{providerLabels[providerType]}
{count > 0 && (
diff --git a/src/components/switch/AppTabs.tsx b/src/components/switch/AppTabs.tsx
index d40a10126..afced7bfa 100644
--- a/src/components/switch/AppTabs.tsx
+++ b/src/components/switch/AppTabs.tsx
@@ -1,15 +1,36 @@
import { cn } from "@/lib/utils";
import { AppType } from "@/lib/api/switch";
+import { ProviderIcon } from "@/icons/providers";
interface AppTabsProps {
activeApp: AppType;
onAppChange: (app: AppType) => void;
}
-const apps: { id: AppType; label: string; description: string }[] = [
- { id: "claude", label: "Claude Code", description: "Claude CLI 配置" },
- { id: "codex", label: "Codex", description: "OpenAI Codex CLI" },
- { id: "gemini", label: "Gemini", description: "Google Gemini CLI" },
+const apps: {
+ id: AppType;
+ label: string;
+ description: string;
+ iconType: string;
+}[] = [
+ {
+ id: "claude",
+ label: "Claude Code",
+ description: "Claude CLI 配置",
+ iconType: "claude",
+ },
+ {
+ id: "codex",
+ label: "Codex",
+ description: "OpenAI Codex CLI",
+ iconType: "openai",
+ },
+ {
+ id: "gemini",
+ label: "Gemini",
+ description: "Google Gemini CLI",
+ iconType: "gemini",
+ },
];
export function AppTabs({ activeApp, onAppChange }: AppTabsProps) {
@@ -20,13 +41,14 @@ export function AppTabs({ activeApp, onAppChange }: AppTabsProps) {
key={app.id}
onClick={() => onAppChange(app.id)}
className={cn(
- "px-4 py-2 rounded-t-lg text-sm font-medium transition-colors",
+ "flex items-center gap-2 px-4 py-2 rounded-t-lg text-sm font-medium transition-colors",
activeApp === app.id
? "bg-primary text-primary-foreground"
: "hover:bg-muted text-muted-foreground",
)}
title={app.description}
>
+
{app.label}
))}
diff --git a/src/components/switch/ProviderCard.tsx b/src/components/switch/ProviderCard.tsx
index 3ad4669d1..31de6e5ee 100644
--- a/src/components/switch/ProviderCard.tsx
+++ b/src/components/switch/ProviderCard.tsx
@@ -1,6 +1,39 @@
import { Check, Edit2, Trash2, Zap } from "lucide-react";
import { Provider } from "@/lib/api/switch";
import { cn } from "@/lib/utils";
+import { ProviderIcon } from "@/icons/providers";
+
+// 从供应商名称和分类推断图标类型
+function getProviderTypeFromName(name: string, category: string): string {
+ const lowerName = name.toLowerCase();
+
+ // 精确匹配
+ if (lowerName.includes("智谱") || lowerName.includes("glm")) return "zhipu";
+ if (lowerName.includes("proxycast")) return "proxycast";
+ if (lowerName.includes("deepseek")) return "deepseek";
+ if (lowerName.includes("kimi")) return "kimi";
+ if (lowerName.includes("minimax")) return "minimax";
+ if (lowerName.includes("doubao") || lowerName.includes("豆包"))
+ return "doubao";
+ if (lowerName.includes("qwen") || lowerName.includes("通义")) return "qwen";
+ if (lowerName.includes("claude")) return "claude";
+ if (lowerName.includes("anthropic")) return "anthropic";
+ if (lowerName.includes("openai")) return "openai";
+ if (lowerName.includes("gemini")) return "gemini";
+ if (lowerName.includes("google")) return "google";
+ if (lowerName.includes("kiro")) return "kiro";
+ if (lowerName.includes("azure")) return "azure";
+ if (lowerName.includes("alibaba") || lowerName.includes("阿里"))
+ return "alibaba";
+ if (lowerName.includes("copilot")) return "copilot";
+
+ // 根据分类推断
+ if (category === "custom") return "custom";
+ if (category === "proxy") return "amp";
+
+ // 默认回退
+ return lowerName.replace(/\s+/g, "");
+}
interface ProviderCardProps {
provider: Provider;
@@ -34,11 +67,15 @@ export function ProviderCard({
-
- {provider.icon || provider.name.charAt(0).toUpperCase()}
+
{provider.name}
diff --git a/src/components/switch/ProviderForm.tsx b/src/components/switch/ProviderForm.tsx
index 052bb87b0..a1f783768 100644
--- a/src/components/switch/ProviderForm.tsx
+++ b/src/components/switch/ProviderForm.tsx
@@ -3,6 +3,7 @@ import { X, ExternalLink, Wand2, Eye, EyeOff } from "lucide-react";
import { Provider, AppType } from "@/lib/api/switch";
import { getConfig } from "@/hooks/useTauri";
import { cn } from "@/lib/utils";
+import { ProviderIcon } from "@/icons/providers";
interface ProviderFormProps {
appType: AppType;
@@ -344,7 +345,6 @@ export function ProviderForm({
const generateJsonFromFields = useCallback(() => {
const env: Record
= {};
if (apiKey) {
- env.ANTHROPIC_AUTH_TOKEN = apiKey;
env.ANTHROPIC_API_KEY = apiKey;
}
if (baseUrl) env.ANTHROPIC_BASE_URL = baseUrl;
@@ -666,10 +666,7 @@ export function ProviderForm({
: "border-border hover:border-muted-foreground/50",
)}
>
-
+
{preset.name}
))}
diff --git a/src/icons/providers/anthropic.svg b/src/icons/providers/anthropic.svg
new file mode 100644
index 000000000..5b81844cb
--- /dev/null
+++ b/src/icons/providers/anthropic.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/icons/providers/gemini.svg b/src/icons/providers/gemini.svg
new file mode 100644
index 000000000..f1cf35757
--- /dev/null
+++ b/src/icons/providers/gemini.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/icons/providers/index.tsx b/src/icons/providers/index.tsx
new file mode 100644
index 000000000..e2d7bd51a
--- /dev/null
+++ b/src/icons/providers/index.tsx
@@ -0,0 +1,69 @@
+import React, { useMemo } from "react";
+import { cn } from "@/lib/utils";
+import { providerIcons, providerTypeToIcon } from "./utils";
+
+interface ProviderIconProps {
+ providerType: string;
+ size?: number | string;
+ className?: string;
+ showFallback?: boolean;
+}
+
+export const ProviderIcon: React.FC = ({
+ providerType,
+ size = 24,
+ className,
+ showFallback = true,
+}) => {
+ const iconName = providerTypeToIcon[providerType] || providerType;
+ const iconSvg = providerIcons[iconName];
+
+ const sizeStyle = useMemo(() => {
+ const sizeValue = typeof size === "number" ? `${size}px` : size;
+ return {
+ width: sizeValue,
+ height: sizeValue,
+ fontSize: sizeValue,
+ lineHeight: 1,
+ };
+ }, [size]);
+
+ if (iconSvg) {
+ return (
+
+ );
+ }
+
+ // Fallback:显示首字母
+ if (showFallback) {
+ const initials = providerType
+ .split("_")
+ .map((word) => word[0])
+ .join("")
+ .toUpperCase()
+ .slice(0, 2);
+ const fallbackFontSize =
+ typeof size === "number" ? `${Math.max(size * 0.5, 12)}px` : "0.5em";
+ return (
+
+ {initials}
+
+ );
+ }
+
+ return null;
+};
diff --git a/src/icons/providers/openai.svg b/src/icons/providers/openai.svg
new file mode 100644
index 000000000..50d94d6c1
--- /dev/null
+++ b/src/icons/providers/openai.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/icons/providers/utils.ts b/src/icons/providers/utils.ts
new file mode 100644
index 000000000..252bab937
--- /dev/null
+++ b/src/icons/providers/utils.ts
@@ -0,0 +1,61 @@
+// Provider 图标辅助函数
+
+// Provider 类型到图标名称的映射
+const providerTypeToIcon: Record = {
+ kiro: "kiro",
+ gemini: "gemini",
+ qwen: "qwen",
+ antigravity: "gemini",
+ openai: "openai",
+ claude: "claude",
+ anthropic: "anthropic",
+ codex: "openai",
+ claude_oauth: "claude",
+ iflow: "openai",
+ amp: "amp",
+ google: "google",
+ alibaba: "alibaba",
+ copilot: "copilot",
+ deepseek: "deepseek",
+ zhipu: "zhipu",
+ kimi: "kimi",
+ minimax: "minimax",
+ doubao: "doubao",
+ azure: "azure",
+};
+
+// Provider 图标 SVG 内联定义
+export const providerIcons: Record = {
+ aws: ``,
+ gemini: ``,
+ anthropic: ``,
+ claude: ``,
+ qwen: ``,
+ google: ``,
+ openai: ``,
+ alibaba: ``,
+ copilot: ``,
+ amp: ``,
+ kiro: ``,
+ deepseek: ``,
+ zhipu: ``,
+ kimi: ``,
+ minimax: ``,
+ doubao: ``,
+ azure: ``,
+};
+
+// 导出图标名称检查函数
+export const hasProviderIcon = (providerType: string): boolean => {
+ const iconName = providerTypeToIcon[providerType] || providerType;
+ return iconName in providerIcons;
+};
+
+// 导出获取图标 SVG 函数
+export const getProviderIcon = (providerType: string): string | undefined => {
+ const iconName = providerTypeToIcon[providerType] || providerType;
+ return providerIcons[iconName];
+};
+
+// 导出 Provider 类型映射
+export { providerTypeToIcon };