feat: add partner promotion feature for Zhipu GLM
- Add isPartner and partnerPromotionKey fields to Provider and ProviderPreset types - Display gold star badge on partner presets in selector - Show promotional message in API Key section for partners - Configure Zhipu GLM as official partner with 10% discount promotion - Support both Claude and Codex provider presets - Add i18n support for partner promotion messages (zh/en)
This commit is contained in:
@@ -19,6 +19,8 @@ interface ClaudeFormFieldsProps {
|
||||
category?: ProviderCategory;
|
||||
shouldShowApiKeyLink: boolean;
|
||||
websiteUrl: string;
|
||||
isPartner?: boolean;
|
||||
partnerPromotionKey?: string;
|
||||
|
||||
// Template Values
|
||||
templateValueEntries: Array<[string, TemplateValueConfig]>;
|
||||
@@ -61,6 +63,8 @@ export function ClaudeFormFields({
|
||||
category,
|
||||
shouldShowApiKeyLink,
|
||||
websiteUrl,
|
||||
isPartner,
|
||||
partnerPromotionKey,
|
||||
templateValueEntries,
|
||||
templateValues,
|
||||
templatePresetName,
|
||||
@@ -91,6 +95,8 @@ export function ClaudeFormFields({
|
||||
category={category}
|
||||
shouldShowLink={shouldShowApiKeyLink}
|
||||
websiteUrl={websiteUrl}
|
||||
isPartner={isPartner}
|
||||
partnerPromotionKey={partnerPromotionKey}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
||||
@@ -15,6 +15,8 @@ interface CodexFormFieldsProps {
|
||||
category?: ProviderCategory;
|
||||
shouldShowApiKeyLink: boolean;
|
||||
websiteUrl: string;
|
||||
isPartner?: boolean;
|
||||
partnerPromotionKey?: string;
|
||||
|
||||
// Base URL
|
||||
shouldShowSpeedTest: boolean;
|
||||
@@ -35,6 +37,8 @@ export function CodexFormFields({
|
||||
category,
|
||||
shouldShowApiKeyLink,
|
||||
websiteUrl,
|
||||
isPartner,
|
||||
partnerPromotionKey,
|
||||
shouldShowSpeedTest,
|
||||
codexBaseUrl,
|
||||
onBaseUrlChange,
|
||||
@@ -56,6 +60,8 @@ export function CodexFormFields({
|
||||
category={category}
|
||||
shouldShowLink={shouldShowApiKeyLink}
|
||||
websiteUrl={websiteUrl}
|
||||
isPartner={isPartner}
|
||||
partnerPromotionKey={partnerPromotionKey}
|
||||
placeholder={{
|
||||
official: t("providerForm.codexOfficialNoApiKey", {
|
||||
defaultValue: "官方供应商无需 API Key",
|
||||
|
||||
@@ -79,6 +79,7 @@ export function ProviderForm({
|
||||
const [activePreset, setActivePreset] = useState<{
|
||||
id: string;
|
||||
category?: ProviderCategory;
|
||||
isPartner?: boolean;
|
||||
} | null>(null);
|
||||
const [isEndpointModalOpen, setIsEndpointModalOpen] = useState(false);
|
||||
|
||||
@@ -326,6 +327,10 @@ export function ProviderForm({
|
||||
if (activePreset.category) {
|
||||
payload.presetCategory = activePreset.category;
|
||||
}
|
||||
// 继承合作伙伴标识
|
||||
if (activePreset.isPartner) {
|
||||
payload.isPartner = activePreset.isPartner;
|
||||
}
|
||||
}
|
||||
|
||||
// 处理 meta 字段:基于 draftCustomEndpoints 生成 custom_endpoints
|
||||
@@ -399,6 +404,8 @@ export function ProviderForm({
|
||||
const {
|
||||
shouldShowApiKeyLink: shouldShowClaudeApiKeyLink,
|
||||
websiteUrl: claudeWebsiteUrl,
|
||||
isPartner: isClaudePartner,
|
||||
partnerPromotionKey: claudePartnerPromotionKey,
|
||||
} = useApiKeyLink({
|
||||
appId: "claude",
|
||||
category,
|
||||
@@ -411,6 +418,8 @@ export function ProviderForm({
|
||||
const {
|
||||
shouldShowApiKeyLink: shouldShowCodexApiKeyLink,
|
||||
websiteUrl: codexWebsiteUrl,
|
||||
isPartner: isCodexPartner,
|
||||
partnerPromotionKey: codexPartnerPromotionKey,
|
||||
} = useApiKeyLink({
|
||||
appId: "codex",
|
||||
category,
|
||||
@@ -450,6 +459,7 @@ export function ProviderForm({
|
||||
setActivePreset({
|
||||
id: value,
|
||||
category: entry.preset.category,
|
||||
isPartner: entry.preset.isPartner,
|
||||
});
|
||||
|
||||
if (appId === "codex") {
|
||||
@@ -523,6 +533,8 @@ export function ProviderForm({
|
||||
category={category}
|
||||
shouldShowApiKeyLink={shouldShowClaudeApiKeyLink}
|
||||
websiteUrl={claudeWebsiteUrl}
|
||||
isPartner={isClaudePartner}
|
||||
partnerPromotionKey={claudePartnerPromotionKey}
|
||||
templateValueEntries={templateValueEntries}
|
||||
templateValues={templateValues}
|
||||
templatePresetName={templatePreset?.name || ""}
|
||||
@@ -552,6 +564,8 @@ export function ProviderForm({
|
||||
category={category}
|
||||
shouldShowApiKeyLink={shouldShowCodexApiKeyLink}
|
||||
websiteUrl={codexWebsiteUrl}
|
||||
isPartner={isCodexPartner}
|
||||
partnerPromotionKey={codexPartnerPromotionKey}
|
||||
shouldShowSpeedTest={shouldShowSpeedTest}
|
||||
codexBaseUrl={codexBaseUrl}
|
||||
onBaseUrlChange={handleCodexBaseUrlChange}
|
||||
@@ -612,5 +626,6 @@ export function ProviderForm({
|
||||
export type ProviderFormValues = ProviderFormData & {
|
||||
presetId?: string;
|
||||
presetCategory?: ProviderCategory;
|
||||
isPartner?: boolean;
|
||||
meta?: ProviderMeta;
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { FormLabel } from "@/components/ui/form";
|
||||
import { ClaudeIcon, CodexIcon } from "@/components/BrandIcons";
|
||||
import { Zap } from "lucide-react";
|
||||
import { Zap, Star } from "lucide-react";
|
||||
import type { ProviderPreset } from "@/config/claudeProviderPresets";
|
||||
import type { CodexProviderPreset } from "@/config/codexProviderPresets";
|
||||
import type { ProviderCategory } from "@/types";
|
||||
@@ -157,12 +157,13 @@ export function ProviderPresetSelector({
|
||||
if (!entries || entries.length === 0) return null;
|
||||
return entries.map((entry) => {
|
||||
const isSelected = selectedPresetId === entry.id;
|
||||
const isPartner = entry.preset.isPartner;
|
||||
return (
|
||||
<button
|
||||
key={entry.id}
|
||||
type="button"
|
||||
onClick={() => onPresetChange(entry.id)}
|
||||
className={getPresetButtonClass(isSelected, entry.preset)}
|
||||
className={`${getPresetButtonClass(isSelected, entry.preset)} relative`}
|
||||
style={getPresetButtonStyle(isSelected, entry.preset)}
|
||||
title={
|
||||
presetCategoryLabels[category] ??
|
||||
@@ -173,6 +174,11 @@ export function ProviderPresetSelector({
|
||||
>
|
||||
{renderPresetIcon(entry.preset)}
|
||||
{entry.preset.name}
|
||||
{isPartner && (
|
||||
<span className="absolute -top-1 -right-1 flex items-center gap-0.5 rounded-full bg-gradient-to-r from-amber-500 to-yellow-500 px-1.5 py-0.5 text-[10px] font-bold text-white shadow-md">
|
||||
<Star className="h-2.5 w-2.5 fill-current" />
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -37,20 +37,34 @@ export function useApiKeyLink({
|
||||
);
|
||||
}, [category]);
|
||||
|
||||
// 获取当前预设条目
|
||||
const currentPresetEntry = useMemo(() => {
|
||||
if (selectedPresetId && selectedPresetId !== "custom") {
|
||||
return presetEntries.find((item) => item.id === selectedPresetId);
|
||||
}
|
||||
return undefined;
|
||||
}, [selectedPresetId, presetEntries]);
|
||||
|
||||
// 获取当前供应商的网址(用于 API Key 链接)
|
||||
const getWebsiteUrl = useMemo(() => {
|
||||
if (selectedPresetId && selectedPresetId !== "custom") {
|
||||
const entry = presetEntries.find((item) => item.id === selectedPresetId);
|
||||
if (entry) {
|
||||
const preset = entry.preset;
|
||||
// 第三方供应商优先使用 apiKeyUrl
|
||||
return preset.category === "third_party"
|
||||
? preset.apiKeyUrl || preset.websiteUrl || ""
|
||||
: preset.websiteUrl || "";
|
||||
}
|
||||
if (currentPresetEntry) {
|
||||
const preset = currentPresetEntry.preset;
|
||||
// 第三方供应商优先使用 apiKeyUrl
|
||||
return preset.category === "third_party"
|
||||
? preset.apiKeyUrl || preset.websiteUrl || ""
|
||||
: preset.websiteUrl || "";
|
||||
}
|
||||
return formWebsiteUrl || "";
|
||||
}, [selectedPresetId, presetEntries, formWebsiteUrl]);
|
||||
}, [currentPresetEntry, formWebsiteUrl]);
|
||||
|
||||
// 提取合作伙伴信息
|
||||
const isPartner = useMemo(() => {
|
||||
return currentPresetEntry?.preset.isPartner ?? false;
|
||||
}, [currentPresetEntry]);
|
||||
|
||||
const partnerPromotionKey = useMemo(() => {
|
||||
return currentPresetEntry?.preset.partnerPromotionKey;
|
||||
}, [currentPresetEntry]);
|
||||
|
||||
return {
|
||||
shouldShowApiKeyLink:
|
||||
@@ -60,5 +74,7 @@ export function useApiKeyLink({
|
||||
? shouldShowApiKeyLink
|
||||
: false,
|
||||
websiteUrl: getWebsiteUrl,
|
||||
isPartner,
|
||||
partnerPromotionKey,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -15,6 +15,8 @@ interface ApiKeySectionProps {
|
||||
thirdParty: string;
|
||||
};
|
||||
disabled?: boolean;
|
||||
isPartner?: boolean;
|
||||
partnerPromotionKey?: string;
|
||||
}
|
||||
|
||||
export function ApiKeySection({
|
||||
@@ -27,6 +29,8 @@ export function ApiKeySection({
|
||||
websiteUrl,
|
||||
placeholder,
|
||||
disabled,
|
||||
isPartner,
|
||||
partnerPromotionKey,
|
||||
}: ApiKeySectionProps) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
@@ -57,7 +61,7 @@ export function ApiKeySection({
|
||||
/>
|
||||
{/* API Key 获取链接 */}
|
||||
{shouldShowLink && websiteUrl && (
|
||||
<div className="-mt-1 pl-1">
|
||||
<div className="space-y-2 -mt-1 pl-1">
|
||||
<a
|
||||
href={websiteUrl}
|
||||
target="_blank"
|
||||
@@ -68,6 +72,18 @@ export function ApiKeySection({
|
||||
defaultValue: "获取 API Key",
|
||||
})}
|
||||
</a>
|
||||
|
||||
{/* 合作伙伴促销信息 */}
|
||||
{isPartner && partnerPromotionKey && (
|
||||
<div className="rounded-md bg-blue-50 dark:bg-blue-950/30 p-2.5 border border-blue-200 dark:border-blue-800">
|
||||
<p className="text-xs leading-relaxed text-blue-700 dark:text-blue-300">
|
||||
💡{" "}
|
||||
{t(`providerForm.partnerPromotion.${partnerPromotionKey}`, {
|
||||
defaultValue: "",
|
||||
})}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -29,6 +29,8 @@ export interface ProviderPreset {
|
||||
apiKeyUrl?: string;
|
||||
settingsConfig: object;
|
||||
isOfficial?: boolean; // 标识是否为官方预设
|
||||
isPartner?: boolean; // 标识是否为商业合作伙伴
|
||||
partnerPromotionKey?: string; // 合作伙伴促销信息的 i18n key
|
||||
category?: ProviderCategory; // 新增:分类
|
||||
// 新增:指定该预设所使用的 API Key 字段名(默认 ANTHROPIC_AUTH_TOKEN)
|
||||
apiKeyField?: "ANTHROPIC_AUTH_TOKEN" | "ANTHROPIC_API_KEY";
|
||||
@@ -73,6 +75,7 @@ export const providerPresets: ProviderPreset[] = [
|
||||
{
|
||||
name: "Zhipu GLM",
|
||||
websiteUrl: "https://open.bigmodel.cn",
|
||||
apiKeyUrl: "https://www.bigmodel.cn/claude-code?ic=RRVJPB5SII",
|
||||
settingsConfig: {
|
||||
env: {
|
||||
ANTHROPIC_BASE_URL: "https://open.bigmodel.cn/api/anthropic",
|
||||
@@ -84,6 +87,8 @@ export const providerPresets: ProviderPreset[] = [
|
||||
},
|
||||
},
|
||||
category: "cn_official",
|
||||
isPartner: true, // 商业合作伙伴
|
||||
partnerPromotionKey: "zhipu", // 促销信息 i18n key
|
||||
},
|
||||
{
|
||||
name: "Qwen Coder",
|
||||
|
||||
@@ -12,6 +12,8 @@ export interface CodexProviderPreset {
|
||||
auth: Record<string, any>; // 将写入 ~/.codex/auth.json
|
||||
config: string; // 将写入 ~/.codex/config.toml(TOML 字符串)
|
||||
isOfficial?: boolean; // 标识是否为官方预设
|
||||
isPartner?: boolean; // 标识是否为商业合作伙伴
|
||||
partnerPromotionKey?: string; // 合作伙伴促销信息的 i18n key
|
||||
category?: ProviderCategory; // 新增:分类
|
||||
isCustomTemplate?: boolean; // 标识是否为自定义模板
|
||||
// 新增:请求地址候选列表(用于地址管理/测速)
|
||||
|
||||
@@ -238,6 +238,9 @@
|
||||
"customApiKeyHint": "💡 Custom configuration requires manually filling all necessary fields",
|
||||
"officialHint": "💡 Official provider uses browser login, no API Key needed",
|
||||
"getApiKey": "Get API Key",
|
||||
"partnerPromotion": {
|
||||
"zhipu": "Zhipu GLM is an official partner of CC Switch. Use this link to top up and get a 10% discount"
|
||||
},
|
||||
"parameterConfig": "Parameter Config - {{name}} *",
|
||||
"mainModel": "Main Model (optional)",
|
||||
"mainModelPlaceholder": "e.g., GLM-4.6",
|
||||
|
||||
@@ -238,6 +238,9 @@
|
||||
"customApiKeyHint": "💡 自定义配置需手动填写所有必要字段",
|
||||
"officialHint": "💡 官方供应商使用浏览器登录,无需配置 API Key",
|
||||
"getApiKey": "获取 API Key",
|
||||
"partnerPromotion": {
|
||||
"zhipu": "智谱 GLM 是 CC Switch 的官方合作伙伴,使用此链接充值可以获得9折优惠"
|
||||
},
|
||||
"parameterConfig": "参数配置 - {{name}} *",
|
||||
"mainModel": "主模型 (可选)",
|
||||
"mainModelPlaceholder": "例如: GLM-4.6",
|
||||
|
||||
@@ -14,6 +14,8 @@ export interface Provider {
|
||||
category?: ProviderCategory;
|
||||
createdAt?: number; // 添加时间戳(毫秒)
|
||||
sortIndex?: number; // 排序索引(用于自定义拖拽排序)
|
||||
// 新增:是否为商业合作伙伴
|
||||
isPartner?: boolean;
|
||||
// 可选:供应商元数据(仅存于 ~/.cc-switch/config.json,不写入 live 配置)
|
||||
meta?: ProviderMeta;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user