v0.6.3: 添加默认供应商支持

- 新增 StepFun Plan 套餐预设
- 供应商编辑页 API Key 下方添加获取 Key 链接(支持推广链接)
- 修复链接无法打开浏览器的问题(Rust open_external_url)
- 无推广链接时回退打开官网
- 必填字段(供应商名称/API Key/请求地址)添加红色 * 标注
- 用户自定义预设方案文档
This commit is contained in:
KimiSwitch Dev committed 2026-07-30 14:19:27 +08:00
1 parent bee981aefa
commit f35924e15c
11 files changed
+223 -7

No files matched your search

+25 -3
View File
@@ -3,6 +3,7 @@ import { createPortal } from "react-dom";
import { invoke } from "@tauri-apps/api/core";
import { useTranslation } from "../i18n";
import type { TranslationKey } from "../i18n/zh";
import { findPresetForProvider } from "../config/providerPresets";
import { getDefaultMaxContextSize } from "../lib/model-defaults";
import { capabilitiesFromRef, getModelRef } from "../lib/models-dev";
import { getIconMetadata } from "../icons/extracted/metadata";
@@ -117,6 +118,10 @@ export function ProviderEdit({
const [showApiKey, setShowApiKey] = useState(false);
const [showIconPicker, setShowIconPicker] = useState(false);
// Preset this provider was created from (if any) — provides the
// "Get API Key" / referral links shown under the key input.
const preset = findPresetForProvider(provider);
useEffect(() => {
const def = defaultBaseUrl(agent, provider.provider_type);
if (!def) return;
@@ -215,7 +220,7 @@ export function ProviderEdit({
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label htmlFor={nameId} className="block text-sm text-content-muted mb-1.5">
{t("providerName")}
{t("providerName")} <span className="text-red-500">*</span>
</label>
<input
id={nameId}
@@ -296,7 +301,7 @@ export function ProviderEdit({
{!provider.managed && (
<div>
<label htmlFor={apiKeyId} className="block text-sm text-content-muted mb-1.5">
{t("apiKey")}
{t("apiKey")} <span className="text-red-500">*</span>
</label>
<div className="relative">
<input
@@ -316,12 +321,29 @@ export function ProviderEdit({
{showApiKey ? t("hide") : t("show")}
</button>
</div>
{(() => {
const linkUrl = preset?.apiKeyUrl ?? provider.official_url ?? null;
if (!linkUrl) return null;
return (
<div className="mt-1.5 flex items-center gap-4 text-xs">
<button
type="button"
onClick={() =>
invoke("open_external_url", { url: linkUrl }).catch(() => {})
}
className="text-blue-600 dark:text-blue-400 hover:underline"
>
{preset?.apiKeyUrl ? t("getApiKeyLink") : t("officialUrl")}
</button>
</div>
);
})()}
</div>
)}
<div>
<label htmlFor={baseUrlId} className="block text-sm text-content-muted mb-1.5">
{t("requestUrl")}
{t("requestUrl")} <span className="text-red-500">*</span>
</label>
<input
id={baseUrlId}
+34 -1
View File
@@ -48,6 +48,7 @@ export interface ProviderPreset {
nameKey?: string;
/** Official website / console URL. */
websiteUrl?: string;
/** Where to create / fetch an API key; shown as a link under the key input. */
apiKeyUrl?: string;
/** Category: sorting & badge. */
category: PresetCategory;
@@ -111,6 +112,7 @@ export const providerPresets: ProviderPreset[] = [
name: "Kimi For Coding",
nameKey: "presetNameKimiCoding",
websiteUrl: "https://www.kimi.com/code",
apiKeyUrl: "https://kimi-bot.com/activities/zh-cn/invite/share?scenario=invite&from=share_poster&invitation_code=6UJX7J",
category: "official",
providerType: "kimi",
baseUrl: "https://api.kimi.com/coding/v1",
@@ -248,6 +250,7 @@ export const providerPresets: ProviderPreset[] = [
name: "MiniMax Token Plan",
nameKey: "presetNameMinimaxTokenPlan",
websiteUrl: "https://platform.minimaxi.com",
apiKeyUrl: "https://platform.minimaxi.com/subscribe/token-plan?code=GmmZA629b5&source=link",
category: "cn_official",
providerType: "openai",
// Token Plan is sold against the same platform endpoint as the
@@ -274,6 +277,21 @@ export const providerPresets: ProviderPreset[] = [
billingMode: "pay_as_you_go",
usageKinds: ["balance:stepfun"],
},
{
id: "stepfun-plan",
name: "StepFun Plan",
nameKey: "presetNameStepfunPlan",
websiteUrl: "https://platform.stepfun.com/step-plan",
apiKeyUrl: "https://platform.stepfun.com/?invite_code=HSIGHTPS",
category: "cn_official",
providerType: "openai",
// Step Plan endpoint — same host, different path prefix than the
// pay-as-you-go API (/v1). Serves both OpenAI and Anthropic protocols.
baseUrl: "https://api.stepfun.com/step_plan/v1",
icon: "stepfun",
models: [{ model: "step-3.7-flash" }, { model: "step-3.5-flash" }],
billingMode: "subscription",
},
{
id: "siliconflow",
name: "SiliconFlow",
@@ -401,7 +419,7 @@ export const providerPresets: ProviderPreset[] = [
nameKey: "presetNameOpencodeGo",
// Referral link — supports the OpenCode project
websiteUrl: "https://opencode.ai/go?ref=DFCNADQCEM",
apiKeyUrl: "https://opencode.ai/zen/",
apiKeyUrl: "https://opencode.ai/go?ref=DFCNADQCEM",
category: "third_party",
providerType: "openai",
baseUrl: "https://opencode.ai/zen/go/v1",
@@ -521,6 +539,21 @@ export function presetToProviderAndModels(
};
}
/**
* Best-effort reverse lookup: which preset (if any) a Provider was created
* from. Matches the provider name with any "-2"/"-3" dedup suffix stripped,
* falling back to an exact official_url match. Used to surface preset-only
* metadata (apiKeyUrl / referralUrl) in the edit form without persisting
* extra fields on Provider.
*/
export function findPresetForProvider(provider: Provider): ProviderPreset | undefined {
const base = provider.name.replace(/-\d+$/, "");
return (
providerPresets.find((p) => p.id === base) ??
providerPresets.find((p) => p.websiteUrl != null && p.websiteUrl === provider.official_url)
);
}
// Dev-only cross-end drift check: assert that every usage kind referenced by
// any preset is one the Rust `UsageKind` enum knows how to handle. The
// runtime filter in `query_provider_usage` silently drops unknown kinds,
+2
View File
@@ -60,6 +60,7 @@ export const enTranslations: Record<TranslationKey, string> = {
apiFormat: "API Format",
authField: "Auth Field",
apiKey: "API Key",
getApiKeyLink: "Get API Key",
show: "Show",
hide: "Hide",
requestUrl: "Request URL",
@@ -349,6 +350,7 @@ export const enTranslations: Record<TranslationKey, string> = {
presetNameMinimax: "MiniMax",
presetNameMinimaxTokenPlan: "MiniMax Token Plan",
presetNameStepfun: "StepFun",
presetNameStepfunPlan: "StepFun Plan",
presetNameSiliconflow: "SiliconFlow",
presetNameNovita: "Novita AI",
presetNameOpenrouter: "OpenRouter",
+2
View File
@@ -58,6 +58,7 @@ export const zhTranslations = {
apiFormat: "API 格式",
authField: "认证字段",
apiKey: "API Key",
getApiKeyLink: "获取 API Key",
show: "显示",
hide: "隐藏",
requestUrl: "请求地址",
@@ -344,6 +345,7 @@ export const zhTranslations = {
presetNameMinimax: "MiniMax",
presetNameMinimaxTokenPlan: "MiniMax Token Plan",
presetNameStepfun: "阶跃星辰",
presetNameStepfunPlan: "阶跃星辰 Step Plan",
presetNameSiliconflow: "硅基流动",
presetNameNovita: "Novita AI",
presetNameOpenrouter: "OpenRouter",