diff --git a/src-tauri/src/provider.rs b/src-tauri/src/provider.rs index 924cfbf..1e10cee 100644 --- a/src-tauri/src/provider.rs +++ b/src-tauri/src/provider.rs @@ -14,6 +14,8 @@ pub struct Provider { #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "websiteUrl")] pub website_url: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub category: Option, } impl Provider { @@ -29,6 +31,7 @@ impl Provider { name, settings_config, website_url, + category: None, } } } diff --git a/src/components/ProviderForm.tsx b/src/components/ProviderForm.tsx index 015581e..7f1816a 100644 --- a/src/components/ProviderForm.tsx +++ b/src/components/ProviderForm.tsx @@ -1,5 +1,5 @@ import React, { useState, useEffect } from "react"; -import { Provider } from "../types"; +import { Provider, ProviderCategory } from "../types"; import { AppType } from "../lib/tauri-api"; import { updateCoAuthoredSetting, @@ -16,6 +16,7 @@ import ClaudeConfigEditor from "./ProviderForm/ClaudeConfigEditor"; import CodexConfigEditor from "./ProviderForm/CodexConfigEditor"; import KimiModelSelector from "./ProviderForm/KimiModelSelector"; import { X, AlertCircle, Save } from "lucide-react"; +// 分类仅用于控制少量交互(如官方禁用 API Key),不显示介绍组件 interface ProviderFormProps { appType?: AppType; @@ -46,6 +47,9 @@ const ProviderForm: React.FC = ({ ? JSON.stringify(initialData.settingsConfig, null, 2) : "", }); + const [category, setCategory] = useState( + initialData?.category, + ); // Codex 特有的状态 const [codexAuth, setCodexAuth] = useState(""); @@ -113,6 +117,26 @@ const ProviderForm: React.FC = ({ } }, [initialData]); + // 当选择预设变化时,同步类别 + useEffect(() => { + if (!showPresets) return; + if (!isCodex) { + if (selectedPreset !== null && selectedPreset >= 0) { + const preset = providerPresets[selectedPreset]; + setCategory(preset?.category || (preset?.isOfficial ? "official" : undefined)); + } else if (selectedPreset === -1) { + setCategory("custom"); + } + } else { + if (selectedCodexPreset !== null && selectedCodexPreset >= 0) { + const preset = codexProviderPresets[selectedCodexPreset]; + setCategory(preset?.category || (preset?.isOfficial ? "official" : undefined)); + } else if (selectedCodexPreset === -1) { + setCategory("custom"); + } + } + }, [showPresets, isCodex, selectedPreset, selectedCodexPreset]); + const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); setError(""); @@ -177,6 +201,8 @@ const ProviderForm: React.FC = ({ name: formData.name, websiteUrl: formData.websiteUrl, settingsConfig, + // 仅在用户选择了预设或手动选择“自定义”时持久化分类 + ...(category ? { category } : {}), }); }; @@ -230,6 +256,7 @@ const ProviderForm: React.FC = ({ websiteUrl: preset.websiteUrl, settingsConfig: configString, }); + setCategory(preset.category || (preset.isOfficial ? "official" : undefined)); // 设置选中的预设 setSelectedPreset(index); @@ -272,6 +299,7 @@ const ProviderForm: React.FC = ({ setDisableCoAuthored(false); setKimiAnthropicModel(""); setKimiAnthropicSmallFastModel(""); + setCategory("custom"); }; // Codex: 应用预设 @@ -290,6 +318,7 @@ const ProviderForm: React.FC = ({ })); setSelectedCodexPreset(index); + setCategory(preset.category || (preset.isOfficial ? "official" : undefined)); // 清空 API Key,让用户重新输入 setCodexApiKey(""); @@ -306,6 +335,7 @@ const ProviderForm: React.FC = ({ setCodexAuth(""); setCodexConfig(""); setCodexApiKey(""); + setCategory("custom"); }; // 处理 API Key 输入并自动更新配置 @@ -349,9 +379,11 @@ const ProviderForm: React.FC = ({ // 判断当前选中的预设是否是官方 const isOfficialPreset = - selectedPreset !== null && - selectedPreset >= 0 && - providerPresets[selectedPreset]?.isOfficial === true; + (selectedPreset !== null && + selectedPreset >= 0 && + (providerPresets[selectedPreset]?.isOfficial === true || + providerPresets[selectedPreset]?.category === "official")) || + category === "official"; // 判断当前选中的预设是否是 Kimi const isKimiPreset = @@ -385,10 +417,14 @@ const ProviderForm: React.FC = ({ (selectedCodexPreset !== null && selectedCodexPreset !== -1) || (!showPresets && getCodexAuthApiKey(codexAuth) !== ""); + // 不再渲染分类介绍组件,避免造成干扰 + const isCodexOfficialPreset = - selectedCodexPreset !== null && - selectedCodexPreset >= 0 && - codexProviderPresets[selectedCodexPreset]?.isOfficial === true; + (selectedCodexPreset !== null && + selectedCodexPreset >= 0 && + (codexProviderPresets[selectedCodexPreset]?.isOfficial === true || + codexProviderPresets[selectedCodexPreset]?.category === "official")) || + category === "official"; // Kimi 模型选择处理函数 const handleKimiModelChange = ( diff --git a/src/components/ProviderForm/PresetSelector.tsx b/src/components/ProviderForm/PresetSelector.tsx index bcd8470..b8cef89 100644 --- a/src/components/ProviderForm/PresetSelector.tsx +++ b/src/components/ProviderForm/PresetSelector.tsx @@ -1,9 +1,11 @@ import React from "react"; import { Zap } from "lucide-react"; +import { ProviderCategory } from "../../types"; interface Preset { name: string; isOfficial?: boolean; + category?: ProviderCategory; } interface PresetSelectorProps { @@ -23,13 +25,13 @@ const PresetSelector: React.FC = ({ onCustomClick, customLabel = "自定义", }) => { - const getButtonClass = (index: number, isOfficial?: boolean) => { + const getButtonClass = (index: number, isOfficial?: boolean, category?: ProviderCategory) => { const isSelected = selectedIndex === index; const baseClass = "inline-flex items-center gap-2 px-4 py-2 rounded-lg text-sm font-medium transition-colors"; if (isSelected) { - return isOfficial + return isOfficial || category === "official" ? `${baseClass} bg-amber-500 text-white` : `${baseClass} bg-blue-500 text-white`; } @@ -44,8 +46,8 @@ const PresetSelector: React.FC = ({ if (selectedIndex !== null && selectedIndex >= 0) { const preset = presets[selectedIndex]; - return preset?.isOfficial - ? "Claude 官方登录,不需要填写 API Key" + return preset?.isOfficial || preset?.category === "official" + ? "官方登录,不需要填写 API Key" : "使用预设配置,只需填写 API Key"; } @@ -70,10 +72,10 @@ const PresetSelector: React.FC = ({ ))} diff --git a/src/components/ProviderList.tsx b/src/components/ProviderList.tsx index 304420d..a9d667b 100644 --- a/src/components/ProviderList.tsx +++ b/src/components/ProviderList.tsx @@ -2,6 +2,7 @@ import React from "react"; import { Provider } from "../types"; import { Play, Edit3, Trash2, CheckCircle2, Users } from "lucide-react"; import { buttonStyles, cardStyles, badgeStyles, cn } from "../lib/styles"; +// 不再在列表中显示分类徽章,避免造成困惑 interface ProviderListProps { providers: Record; @@ -99,6 +100,7 @@ const ProviderList: React.FC = ({

{provider.name}

+ {/* 分类徽章已移除 */} {isCurrent && (
diff --git a/src/config/codexProviderPresets.ts b/src/config/codexProviderPresets.ts index 2d956f6..3e7728b 100644 --- a/src/config/codexProviderPresets.ts +++ b/src/config/codexProviderPresets.ts @@ -1,12 +1,15 @@ /** * Codex 预设供应商配置模板 */ +import { ProviderCategory } from "../types"; + export interface CodexProviderPreset { name: string; websiteUrl: string; auth: Record; // 将写入 ~/.codex/auth.json config: string; // 将写入 ~/.codex/config.toml(TOML 字符串) isOfficial?: boolean; // 标识是否为官方预设 + category?: ProviderCategory; // 新增:分类 } export const codexProviderPresets: CodexProviderPreset[] = [ @@ -14,6 +17,7 @@ export const codexProviderPresets: CodexProviderPreset[] = [ name: "Codex官方", websiteUrl: "https://chatgpt.com/codex", isOfficial: true, + category: "official", // 官方的 key 为null auth: { OPENAI_API_KEY: null, @@ -23,6 +27,7 @@ export const codexProviderPresets: CodexProviderPreset[] = [ { name: "PackyCode", websiteUrl: "https://codex.packycode.com/", + category: "third_party", // PackyCode 一般通过 API Key;请将占位符替换为你的实际 key auth: { OPENAI_API_KEY: "sk-your-api-key-here", diff --git a/src/config/providerPresets.ts b/src/config/providerPresets.ts index e47bf0e..b1ce1bb 100644 --- a/src/config/providerPresets.ts +++ b/src/config/providerPresets.ts @@ -1,11 +1,14 @@ /** * 预设供应商配置模板 */ +import { ProviderCategory } from "../types"; + export interface ProviderPreset { name: string; websiteUrl: string; settingsConfig: object; isOfficial?: boolean; // 标识是否为官方预设 + category?: ProviderCategory; // 新增:分类 } export const providerPresets: ProviderPreset[] = [ @@ -16,6 +19,7 @@ export const providerPresets: ProviderPreset[] = [ env: {}, }, isOfficial: true, // 明确标识为官方预设 + category: "official", }, { name: "DeepSeek v3.1", @@ -28,6 +32,7 @@ export const providerPresets: ProviderPreset[] = [ ANTHROPIC_SMALL_FAST_MODEL: "deepseek-chat", }, }, + category: "cn_official", }, { name: "智谱GLM", @@ -38,6 +43,7 @@ export const providerPresets: ProviderPreset[] = [ ANTHROPIC_AUTH_TOKEN: "", }, }, + category: "cn_official", }, { name: "千问Qwen-Coder", @@ -49,6 +55,7 @@ export const providerPresets: ProviderPreset[] = [ ANTHROPIC_AUTH_TOKEN: "", }, }, + category: "cn_official", }, { name: "Kimi k2", @@ -61,6 +68,7 @@ export const providerPresets: ProviderPreset[] = [ ANTHROPIC_SMALL_FAST_MODEL: "kimi-k2-turbo-preview", }, }, + category: "cn_official", }, { name: "魔搭", @@ -73,6 +81,7 @@ export const providerPresets: ProviderPreset[] = [ ANTHROPIC_SMALL_FAST_MODEL: "ZhipuAI/GLM-4.5", }, }, + category: "aggregator", }, { name: "PackyCode", @@ -83,5 +92,6 @@ export const providerPresets: ProviderPreset[] = [ ANTHROPIC_AUTH_TOKEN: "", }, }, + category: "third_party", }, ]; diff --git a/src/types.ts b/src/types.ts index 2767598..610977c 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,8 +1,17 @@ +export type ProviderCategory = + | "official" // 官方 + | "cn_official" // 国产官方 + | "aggregator" // 聚合网站 + | "third_party" // 第三方供应商 + | "custom"; // 自定义 + export interface Provider { id: string; name: string; settingsConfig: Record; // 应用配置对象:Claude 为 settings.json;Codex 为 { auth, config } websiteUrl?: string; + // 新增:供应商分类(用于差异化提示/能力开关) + category?: ProviderCategory; createdAt?: number; // 添加时间戳(毫秒) }