2025-10-16 23:56:30 +08:00
|
|
|
|
import { useEffect, useMemo, useState, useCallback } from "react";
|
2025-10-16 10:49:56 +08:00
|
|
|
|
import { useForm } from "react-hook-form";
|
|
|
|
|
|
import { zodResolver } from "@hookform/resolvers/zod";
|
|
|
|
|
|
import { useTranslation } from "react-i18next";
|
|
|
|
|
|
import { Button } from "@/components/ui/button";
|
2025-11-10 12:03:15 +08:00
|
|
|
|
import { Form, FormField, FormItem, FormMessage } from "@/components/ui/form";
|
2025-10-16 12:13:51 +08:00
|
|
|
|
import { providerSchema, type ProviderFormData } from "@/lib/schemas/provider";
|
2025-10-30 14:59:15 +08:00
|
|
|
|
import type { AppId } from "@/lib/api";
|
2025-10-28 20:28:11 +08:00
|
|
|
|
import type { ProviderCategory, ProviderMeta } from "@/types";
|
2025-11-03 10:24:59 +08:00
|
|
|
|
import {
|
|
|
|
|
|
providerPresets,
|
|
|
|
|
|
type ProviderPreset,
|
|
|
|
|
|
} from "@/config/claudeProviderPresets";
|
2025-10-16 13:02:38 +08:00
|
|
|
|
import {
|
|
|
|
|
|
codexProviderPresets,
|
|
|
|
|
|
type CodexProviderPreset,
|
|
|
|
|
|
} from "@/config/codexProviderPresets";
|
2025-11-12 10:47:34 +08:00
|
|
|
|
import {
|
|
|
|
|
|
geminiProviderPresets,
|
|
|
|
|
|
type GeminiProviderPreset,
|
|
|
|
|
|
} from "@/config/geminiProviderPresets";
|
2025-10-16 13:02:38 +08:00
|
|
|
|
import { applyTemplateValues } from "@/utils/providerConfigUtils";
|
2025-10-28 20:28:11 +08:00
|
|
|
|
import { mergeProviderMeta } from "@/utils/providerMetaUtils";
|
2025-11-16 13:23:53 +08:00
|
|
|
|
import { getCodexCustomTemplate } from "@/config/codexTemplates";
|
2025-10-17 14:31:34 +08:00
|
|
|
|
import CodexConfigEditor from "./CodexConfigEditor";
|
2025-10-16 20:32:11 +08:00
|
|
|
|
import { CommonConfigEditor } from "./CommonConfigEditor";
|
2025-11-14 08:31:09 +08:00
|
|
|
|
import GeminiConfigEditor from "./GeminiConfigEditor";
|
2025-10-16 21:40:42 +08:00
|
|
|
|
import { ProviderPresetSelector } from "./ProviderPresetSelector";
|
|
|
|
|
|
import { BasicFormFields } from "./BasicFormFields";
|
|
|
|
|
|
import { ClaudeFormFields } from "./ClaudeFormFields";
|
|
|
|
|
|
import { CodexFormFields } from "./CodexFormFields";
|
2025-11-12 10:47:34 +08:00
|
|
|
|
import { GeminiFormFields } from "./GeminiFormFields";
|
2025-10-16 18:50:44 +08:00
|
|
|
|
import {
|
|
|
|
|
|
useProviderCategory,
|
|
|
|
|
|
useApiKeyState,
|
|
|
|
|
|
useBaseUrlState,
|
|
|
|
|
|
useModelState,
|
|
|
|
|
|
useCodexConfigState,
|
2025-10-16 19:56:00 +08:00
|
|
|
|
useApiKeyLink,
|
2025-10-16 20:25:39 +08:00
|
|
|
|
useTemplateValues,
|
2025-10-16 20:32:11 +08:00
|
|
|
|
useCommonConfigSnippet,
|
2025-10-16 21:04:32 +08:00
|
|
|
|
useCodexCommonConfig,
|
2025-10-16 22:41:36 +08:00
|
|
|
|
useSpeedTestEndpoints,
|
2025-10-16 23:56:30 +08:00
|
|
|
|
useCodexTomlValidation,
|
2025-11-14 08:31:09 +08:00
|
|
|
|
useGeminiConfigState,
|
|
|
|
|
|
useGeminiCommonConfig,
|
2025-10-16 18:50:44 +08:00
|
|
|
|
} from "./hooks";
|
2025-10-16 13:02:38 +08:00
|
|
|
|
|
2025-10-30 09:04:11 +08:00
|
|
|
|
const CLAUDE_DEFAULT_CONFIG = JSON.stringify({ env: {} }, null, 2);
|
2025-10-16 15:32:26 +08:00
|
|
|
|
const CODEX_DEFAULT_CONFIG = JSON.stringify({ auth: {}, config: "" }, null, 2);
|
2025-11-12 10:47:34 +08:00
|
|
|
|
const GEMINI_DEFAULT_CONFIG = JSON.stringify(
|
|
|
|
|
|
{
|
|
|
|
|
|
env: {
|
|
|
|
|
|
GOOGLE_GEMINI_BASE_URL: "",
|
|
|
|
|
|
GEMINI_API_KEY: "",
|
|
|
|
|
|
GEMINI_MODEL: "gemini-2.5-pro",
|
|
|
|
|
|
},
|
|
|
|
|
|
},
|
|
|
|
|
|
null,
|
|
|
|
|
|
2,
|
|
|
|
|
|
);
|
2025-10-16 13:02:38 +08:00
|
|
|
|
|
|
|
|
|
|
type PresetEntry = {
|
|
|
|
|
|
id: string;
|
2025-11-12 10:47:34 +08:00
|
|
|
|
preset: ProviderPreset | CodexProviderPreset | GeminiProviderPreset;
|
2025-10-16 13:02:38 +08:00
|
|
|
|
};
|
2025-10-16 10:49:56 +08:00
|
|
|
|
|
|
|
|
|
|
interface ProviderFormProps {
|
2025-10-30 14:59:15 +08:00
|
|
|
|
appId: AppId;
|
2025-11-04 15:30:54 +08:00
|
|
|
|
providerId?: string;
|
2025-10-16 10:49:56 +08:00
|
|
|
|
submitLabel: string;
|
2025-10-16 13:02:38 +08:00
|
|
|
|
onSubmit: (values: ProviderFormValues) => void;
|
2025-10-16 10:49:56 +08:00
|
|
|
|
onCancel: () => void;
|
|
|
|
|
|
initialData?: {
|
|
|
|
|
|
name?: string;
|
|
|
|
|
|
websiteUrl?: string;
|
feat: add model configuration support and fix Gemini deeplink bug (#251)
* feat(providers): add notes field for provider management
- Add notes field to Provider model (backend and frontend)
- Display notes with higher priority than URL in provider card
- Style notes as non-clickable text to differentiate from URLs
- Add notes input field in provider form
- Add i18n support (zh/en) for notes field
* chore: format code and clean up unused props
- Run cargo fmt on Rust backend code
- Format TypeScript imports and code style
- Remove unused appId prop from ProviderPresetSelector
- Clean up unused variables in tests
- Integrate notes field handling in provider dialogs
* feat(deeplink): implement ccswitch:// protocol for provider import
Add deep link support to enable one-click provider configuration import via ccswitch:// URLs.
Backend:
- Implement URL parsing and validation (src-tauri/src/deeplink.rs)
- Add Tauri commands for parse and import (src-tauri/src/commands/deeplink.rs)
- Register ccswitch:// protocol in macOS Info.plist
- Add comprehensive unit tests (src-tauri/tests/deeplink_import.rs)
Frontend:
- Create confirmation dialog with security review UI (src/components/DeepLinkImportDialog.tsx)
- Add API wrapper (src/lib/api/deeplink.ts)
- Integrate event listeners in App.tsx
Configuration:
- Update Tauri config for deep link handling
- Add i18n support for Chinese and English
- Include test page for deep link validation (deeplink-test.html)
Files: 15 changed, 1312 insertions(+)
* chore(deeplink): integrate deep link handling into app lifecycle
Wire up deep link infrastructure with app initialization and event handling.
Backend Integration:
- Register deep link module and commands in mod.rs
- Add URL handling in app setup (src-tauri/src/lib.rs:handle_deeplink_url)
- Handle deep links from single instance callback (Windows/Linux CLI)
- Handle deep links from macOS system events
- Add tauri-plugin-deep-link dependency (Cargo.toml)
Frontend Integration:
- Listen for deeplink-import/deeplink-error events in App.tsx
- Update DeepLinkImportDialog component imports
Configuration:
- Enable deep link plugin in tauri.conf.json
- Update Cargo.lock for new dependencies
Localization:
- Add Chinese translations for deep link UI (zh.json)
- Add English translations for deep link UI (en.json)
Files: 9 changed, 359 insertions(+), 18 deletions(-)
* refactor(deeplink): enhance Codex provider template generation
Align deep link import with UI preset generation logic by:
- Adding complete config.toml template matching frontend defaults
- Generating safe provider name from sanitized input
- Including model_provider, reasoning_effort, and wire_api settings
- Removing minimal template that only contained base_url
- Cleaning up deprecated test file deeplink-test.html
* style: fix clippy uninlined_format_args warnings
Apply clippy --fix to use inline format arguments in:
- src/mcp.rs (8 fixes)
- src/services/env_manager.rs (10 fixes)
* style: apply code formatting and cleanup
- Format TypeScript files with Prettier (App.tsx, EnvWarningBanner.tsx, formatters.ts)
- Organize Rust imports and module order alphabetically
- Add newline at end of JSON files (en.json, zh.json)
- Update Cargo.lock for dependency changes
* feat: add model name configuration support for Codex and fix Gemini model handling
- Add visual model name input field for Codex providers
- Add model name extraction and update utilities in providerConfigUtils
- Implement model name state management in useCodexConfigState hook
- Add conditional model field rendering in CodexFormFields (non-official only)
- Integrate model name sync with TOML config in ProviderForm
- Fix Gemini deeplink model injection bug
- Correct environment variable name from GOOGLE_GEMINI_MODEL to GEMINI_MODEL
- Add test cases for Gemini model injection (with/without model)
- All tests passing (9/9)
- Fix Gemini model field binding in edit mode
- Add geminiModel state to useGeminiConfigState hook
- Extract model value during initialization and reset
- Sync model field with geminiEnv state to prevent data loss on submit
- Fix missing model value display when editing Gemini providers
Changes:
- 6 files changed, 245 insertions(+), 13 deletions(-)
2025-11-19 09:03:18 +08:00
|
|
|
|
notes?: string;
|
2025-10-16 10:49:56 +08:00
|
|
|
|
settingsConfig?: Record<string, unknown>;
|
2025-10-24 09:24:03 +08:00
|
|
|
|
category?: ProviderCategory;
|
|
|
|
|
|
meta?: ProviderMeta;
|
2025-10-16 10:49:56 +08:00
|
|
|
|
};
|
2025-10-18 23:28:33 +08:00
|
|
|
|
showButtons?: boolean;
|
2025-10-16 10:49:56 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export function ProviderForm({
|
2025-10-30 14:59:15 +08:00
|
|
|
|
appId,
|
2025-11-04 15:30:54 +08:00
|
|
|
|
providerId,
|
2025-10-16 10:49:56 +08:00
|
|
|
|
submitLabel,
|
|
|
|
|
|
onSubmit,
|
|
|
|
|
|
onCancel,
|
|
|
|
|
|
initialData,
|
2025-10-18 23:28:33 +08:00
|
|
|
|
showButtons = true,
|
2025-10-16 10:49:56 +08:00
|
|
|
|
}: ProviderFormProps) {
|
2025-11-16 13:36:33 +08:00
|
|
|
|
const { t } = useTranslation();
|
2025-10-16 17:40:25 +08:00
|
|
|
|
const isEditMode = Boolean(initialData);
|
|
|
|
|
|
|
2025-10-16 16:51:47 +08:00
|
|
|
|
const [selectedPresetId, setSelectedPresetId] = useState<string | null>(
|
2025-10-18 16:52:02 +08:00
|
|
|
|
initialData ? null : "custom",
|
2025-10-16 16:51:47 +08:00
|
|
|
|
);
|
2025-10-16 13:02:38 +08:00
|
|
|
|
const [activePreset, setActivePreset] = useState<{
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
category?: ProviderCategory;
|
2025-11-06 15:22:38 +08:00
|
|
|
|
isPartner?: boolean;
|
2025-11-12 10:47:34 +08:00
|
|
|
|
partnerPromotionKey?: string;
|
2025-10-16 13:02:38 +08:00
|
|
|
|
} | null>(null);
|
2025-10-16 17:44:23 +08:00
|
|
|
|
const [isEndpointModalOpen, setIsEndpointModalOpen] = useState(false);
|
2025-11-12 11:02:43 +08:00
|
|
|
|
const [isCodexEndpointModalOpen, setIsCodexEndpointModalOpen] =
|
|
|
|
|
|
useState(false);
|
2025-10-16 13:02:38 +08:00
|
|
|
|
|
2025-10-16 19:40:22 +08:00
|
|
|
|
// 新建供应商:收集端点测速弹窗中的"自定义端点",提交时一次性落盘到 meta.custom_endpoints
|
2025-11-12 11:02:43 +08:00
|
|
|
|
// 编辑供应商:端点已通过 API 直接保存,不再需要此状态
|
2025-10-16 20:21:42 +08:00
|
|
|
|
const [draftCustomEndpoints, setDraftCustomEndpoints] = useState<string[]>(
|
2025-11-04 15:30:54 +08:00
|
|
|
|
() => {
|
2025-11-12 11:02:43 +08:00
|
|
|
|
// 仅在新建模式下使用
|
|
|
|
|
|
if (initialData) return [];
|
|
|
|
|
|
return [];
|
2025-11-04 15:30:54 +08:00
|
|
|
|
},
|
2025-10-16 20:21:42 +08:00
|
|
|
|
);
|
2025-10-16 19:40:22 +08:00
|
|
|
|
|
2025-10-16 17:40:25 +08:00
|
|
|
|
// 使用 category hook
|
|
|
|
|
|
const { category } = useProviderCategory({
|
2025-10-30 14:59:15 +08:00
|
|
|
|
appId,
|
2025-10-16 17:40:25 +08:00
|
|
|
|
selectedPresetId,
|
|
|
|
|
|
isEditMode,
|
2025-10-24 09:24:03 +08:00
|
|
|
|
initialCategory: initialData?.category,
|
2025-10-16 17:40:25 +08:00
|
|
|
|
});
|
|
|
|
|
|
|
2025-10-16 13:02:38 +08:00
|
|
|
|
useEffect(() => {
|
2025-10-16 16:51:47 +08:00
|
|
|
|
setSelectedPresetId(initialData ? null : "custom");
|
2025-10-16 13:02:38 +08:00
|
|
|
|
setActivePreset(null);
|
2025-11-04 15:30:54 +08:00
|
|
|
|
|
2025-11-12 11:02:43 +08:00
|
|
|
|
// 编辑模式不需要恢复 draftCustomEndpoints,端点已通过 API 管理
|
|
|
|
|
|
if (!initialData) {
|
2025-11-04 15:30:54 +08:00
|
|
|
|
setDraftCustomEndpoints([]);
|
|
|
|
|
|
}
|
2025-10-30 14:59:15 +08:00
|
|
|
|
}, [appId, initialData]);
|
2025-10-16 10:49:56 +08:00
|
|
|
|
|
|
|
|
|
|
const defaultValues: ProviderFormData = useMemo(
|
|
|
|
|
|
() => ({
|
|
|
|
|
|
name: initialData?.name ?? "",
|
|
|
|
|
|
websiteUrl: initialData?.websiteUrl ?? "",
|
feat: add model configuration support and fix Gemini deeplink bug (#251)
* feat(providers): add notes field for provider management
- Add notes field to Provider model (backend and frontend)
- Display notes with higher priority than URL in provider card
- Style notes as non-clickable text to differentiate from URLs
- Add notes input field in provider form
- Add i18n support (zh/en) for notes field
* chore: format code and clean up unused props
- Run cargo fmt on Rust backend code
- Format TypeScript imports and code style
- Remove unused appId prop from ProviderPresetSelector
- Clean up unused variables in tests
- Integrate notes field handling in provider dialogs
* feat(deeplink): implement ccswitch:// protocol for provider import
Add deep link support to enable one-click provider configuration import via ccswitch:// URLs.
Backend:
- Implement URL parsing and validation (src-tauri/src/deeplink.rs)
- Add Tauri commands for parse and import (src-tauri/src/commands/deeplink.rs)
- Register ccswitch:// protocol in macOS Info.plist
- Add comprehensive unit tests (src-tauri/tests/deeplink_import.rs)
Frontend:
- Create confirmation dialog with security review UI (src/components/DeepLinkImportDialog.tsx)
- Add API wrapper (src/lib/api/deeplink.ts)
- Integrate event listeners in App.tsx
Configuration:
- Update Tauri config for deep link handling
- Add i18n support for Chinese and English
- Include test page for deep link validation (deeplink-test.html)
Files: 15 changed, 1312 insertions(+)
* chore(deeplink): integrate deep link handling into app lifecycle
Wire up deep link infrastructure with app initialization and event handling.
Backend Integration:
- Register deep link module and commands in mod.rs
- Add URL handling in app setup (src-tauri/src/lib.rs:handle_deeplink_url)
- Handle deep links from single instance callback (Windows/Linux CLI)
- Handle deep links from macOS system events
- Add tauri-plugin-deep-link dependency (Cargo.toml)
Frontend Integration:
- Listen for deeplink-import/deeplink-error events in App.tsx
- Update DeepLinkImportDialog component imports
Configuration:
- Enable deep link plugin in tauri.conf.json
- Update Cargo.lock for new dependencies
Localization:
- Add Chinese translations for deep link UI (zh.json)
- Add English translations for deep link UI (en.json)
Files: 9 changed, 359 insertions(+), 18 deletions(-)
* refactor(deeplink): enhance Codex provider template generation
Align deep link import with UI preset generation logic by:
- Adding complete config.toml template matching frontend defaults
- Generating safe provider name from sanitized input
- Including model_provider, reasoning_effort, and wire_api settings
- Removing minimal template that only contained base_url
- Cleaning up deprecated test file deeplink-test.html
* style: fix clippy uninlined_format_args warnings
Apply clippy --fix to use inline format arguments in:
- src/mcp.rs (8 fixes)
- src/services/env_manager.rs (10 fixes)
* style: apply code formatting and cleanup
- Format TypeScript files with Prettier (App.tsx, EnvWarningBanner.tsx, formatters.ts)
- Organize Rust imports and module order alphabetically
- Add newline at end of JSON files (en.json, zh.json)
- Update Cargo.lock for dependency changes
* feat: add model name configuration support for Codex and fix Gemini model handling
- Add visual model name input field for Codex providers
- Add model name extraction and update utilities in providerConfigUtils
- Implement model name state management in useCodexConfigState hook
- Add conditional model field rendering in CodexFormFields (non-official only)
- Integrate model name sync with TOML config in ProviderForm
- Fix Gemini deeplink model injection bug
- Correct environment variable name from GOOGLE_GEMINI_MODEL to GEMINI_MODEL
- Add test cases for Gemini model injection (with/without model)
- All tests passing (9/9)
- Fix Gemini model field binding in edit mode
- Add geminiModel state to useGeminiConfigState hook
- Extract model value during initialization and reset
- Sync model field with geminiEnv state to prevent data loss on submit
- Fix missing model value display when editing Gemini providers
Changes:
- 6 files changed, 245 insertions(+), 13 deletions(-)
2025-11-19 09:03:18 +08:00
|
|
|
|
notes: initialData?.notes ?? "",
|
2025-10-16 10:49:56 +08:00
|
|
|
|
settingsConfig: initialData?.settingsConfig
|
|
|
|
|
|
? JSON.stringify(initialData.settingsConfig, null, 2)
|
2025-10-30 14:59:15 +08:00
|
|
|
|
: appId === "codex"
|
2025-10-16 13:02:38 +08:00
|
|
|
|
? CODEX_DEFAULT_CONFIG
|
2025-11-12 10:47:34 +08:00
|
|
|
|
: appId === "gemini"
|
|
|
|
|
|
? GEMINI_DEFAULT_CONFIG
|
|
|
|
|
|
: CLAUDE_DEFAULT_CONFIG,
|
2025-10-16 10:49:56 +08:00
|
|
|
|
}),
|
2025-10-30 14:59:15 +08:00
|
|
|
|
[initialData, appId],
|
2025-10-16 10:49:56 +08:00
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
const form = useForm<ProviderFormData>({
|
|
|
|
|
|
resolver: zodResolver(providerSchema),
|
|
|
|
|
|
defaultValues,
|
|
|
|
|
|
mode: "onSubmit",
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2025-10-16 17:40:25 +08:00
|
|
|
|
// 使用 API Key hook
|
2025-10-16 20:21:42 +08:00
|
|
|
|
const {
|
|
|
|
|
|
apiKey,
|
|
|
|
|
|
handleApiKeyChange,
|
|
|
|
|
|
showApiKey: shouldShowApiKey,
|
|
|
|
|
|
} = useApiKeyState({
|
2025-10-16 17:40:25 +08:00
|
|
|
|
initialConfig: form.watch("settingsConfig"),
|
|
|
|
|
|
onConfigChange: (config) => form.setValue("settingsConfig", config),
|
|
|
|
|
|
selectedPresetId,
|
2025-10-23 12:09:59 +08:00
|
|
|
|
category,
|
2025-10-30 14:59:15 +08:00
|
|
|
|
appType: appId,
|
2025-10-16 17:44:23 +08:00
|
|
|
|
});
|
|
|
|
|
|
|
2025-11-12 10:47:34 +08:00
|
|
|
|
// 使用 Base URL hook (Claude, Codex, Gemini)
|
|
|
|
|
|
const { baseUrl, handleClaudeBaseUrlChange, handleGeminiBaseUrlChange } =
|
|
|
|
|
|
useBaseUrlState({
|
|
|
|
|
|
appType: appId,
|
|
|
|
|
|
category,
|
|
|
|
|
|
settingsConfig: form.watch("settingsConfig"),
|
|
|
|
|
|
codexConfig: "",
|
|
|
|
|
|
onSettingsConfigChange: (config) =>
|
|
|
|
|
|
form.setValue("settingsConfig", config),
|
|
|
|
|
|
onCodexConfigChange: () => {
|
|
|
|
|
|
/* noop */
|
|
|
|
|
|
},
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2025-11-02 18:02:22 +08:00
|
|
|
|
// 使用 Model hook(新:主模型 + Haiku/Sonnet/Opus 默认模型)
|
|
|
|
|
|
const {
|
|
|
|
|
|
claudeModel,
|
|
|
|
|
|
defaultHaikuModel,
|
|
|
|
|
|
defaultSonnetModel,
|
|
|
|
|
|
defaultOpusModel,
|
|
|
|
|
|
handleModelChange,
|
|
|
|
|
|
} = useModelState({
|
|
|
|
|
|
settingsConfig: form.watch("settingsConfig"),
|
|
|
|
|
|
onConfigChange: (config) => form.setValue("settingsConfig", config),
|
|
|
|
|
|
});
|
2025-10-16 17:58:49 +08:00
|
|
|
|
|
2025-10-16 18:50:44 +08:00
|
|
|
|
// 使用 Codex 配置 hook (仅 Codex 模式)
|
|
|
|
|
|
const {
|
|
|
|
|
|
codexAuth,
|
|
|
|
|
|
codexConfig,
|
|
|
|
|
|
codexApiKey,
|
|
|
|
|
|
codexBaseUrl,
|
feat: add model configuration support and fix Gemini deeplink bug (#251)
* feat(providers): add notes field for provider management
- Add notes field to Provider model (backend and frontend)
- Display notes with higher priority than URL in provider card
- Style notes as non-clickable text to differentiate from URLs
- Add notes input field in provider form
- Add i18n support (zh/en) for notes field
* chore: format code and clean up unused props
- Run cargo fmt on Rust backend code
- Format TypeScript imports and code style
- Remove unused appId prop from ProviderPresetSelector
- Clean up unused variables in tests
- Integrate notes field handling in provider dialogs
* feat(deeplink): implement ccswitch:// protocol for provider import
Add deep link support to enable one-click provider configuration import via ccswitch:// URLs.
Backend:
- Implement URL parsing and validation (src-tauri/src/deeplink.rs)
- Add Tauri commands for parse and import (src-tauri/src/commands/deeplink.rs)
- Register ccswitch:// protocol in macOS Info.plist
- Add comprehensive unit tests (src-tauri/tests/deeplink_import.rs)
Frontend:
- Create confirmation dialog with security review UI (src/components/DeepLinkImportDialog.tsx)
- Add API wrapper (src/lib/api/deeplink.ts)
- Integrate event listeners in App.tsx
Configuration:
- Update Tauri config for deep link handling
- Add i18n support for Chinese and English
- Include test page for deep link validation (deeplink-test.html)
Files: 15 changed, 1312 insertions(+)
* chore(deeplink): integrate deep link handling into app lifecycle
Wire up deep link infrastructure with app initialization and event handling.
Backend Integration:
- Register deep link module and commands in mod.rs
- Add URL handling in app setup (src-tauri/src/lib.rs:handle_deeplink_url)
- Handle deep links from single instance callback (Windows/Linux CLI)
- Handle deep links from macOS system events
- Add tauri-plugin-deep-link dependency (Cargo.toml)
Frontend Integration:
- Listen for deeplink-import/deeplink-error events in App.tsx
- Update DeepLinkImportDialog component imports
Configuration:
- Enable deep link plugin in tauri.conf.json
- Update Cargo.lock for new dependencies
Localization:
- Add Chinese translations for deep link UI (zh.json)
- Add English translations for deep link UI (en.json)
Files: 9 changed, 359 insertions(+), 18 deletions(-)
* refactor(deeplink): enhance Codex provider template generation
Align deep link import with UI preset generation logic by:
- Adding complete config.toml template matching frontend defaults
- Generating safe provider name from sanitized input
- Including model_provider, reasoning_effort, and wire_api settings
- Removing minimal template that only contained base_url
- Cleaning up deprecated test file deeplink-test.html
* style: fix clippy uninlined_format_args warnings
Apply clippy --fix to use inline format arguments in:
- src/mcp.rs (8 fixes)
- src/services/env_manager.rs (10 fixes)
* style: apply code formatting and cleanup
- Format TypeScript files with Prettier (App.tsx, EnvWarningBanner.tsx, formatters.ts)
- Organize Rust imports and module order alphabetically
- Add newline at end of JSON files (en.json, zh.json)
- Update Cargo.lock for dependency changes
* feat: add model name configuration support for Codex and fix Gemini model handling
- Add visual model name input field for Codex providers
- Add model name extraction and update utilities in providerConfigUtils
- Implement model name state management in useCodexConfigState hook
- Add conditional model field rendering in CodexFormFields (non-official only)
- Integrate model name sync with TOML config in ProviderForm
- Fix Gemini deeplink model injection bug
- Correct environment variable name from GOOGLE_GEMINI_MODEL to GEMINI_MODEL
- Add test cases for Gemini model injection (with/without model)
- All tests passing (9/9)
- Fix Gemini model field binding in edit mode
- Add geminiModel state to useGeminiConfigState hook
- Extract model value during initialization and reset
- Sync model field with geminiEnv state to prevent data loss on submit
- Fix missing model value display when editing Gemini providers
Changes:
- 6 files changed, 245 insertions(+), 13 deletions(-)
2025-11-19 09:03:18 +08:00
|
|
|
|
codexModelName,
|
2025-10-16 18:50:44 +08:00
|
|
|
|
codexAuthError,
|
|
|
|
|
|
setCodexAuth,
|
|
|
|
|
|
handleCodexApiKeyChange,
|
|
|
|
|
|
handleCodexBaseUrlChange,
|
feat: add model configuration support and fix Gemini deeplink bug (#251)
* feat(providers): add notes field for provider management
- Add notes field to Provider model (backend and frontend)
- Display notes with higher priority than URL in provider card
- Style notes as non-clickable text to differentiate from URLs
- Add notes input field in provider form
- Add i18n support (zh/en) for notes field
* chore: format code and clean up unused props
- Run cargo fmt on Rust backend code
- Format TypeScript imports and code style
- Remove unused appId prop from ProviderPresetSelector
- Clean up unused variables in tests
- Integrate notes field handling in provider dialogs
* feat(deeplink): implement ccswitch:// protocol for provider import
Add deep link support to enable one-click provider configuration import via ccswitch:// URLs.
Backend:
- Implement URL parsing and validation (src-tauri/src/deeplink.rs)
- Add Tauri commands for parse and import (src-tauri/src/commands/deeplink.rs)
- Register ccswitch:// protocol in macOS Info.plist
- Add comprehensive unit tests (src-tauri/tests/deeplink_import.rs)
Frontend:
- Create confirmation dialog with security review UI (src/components/DeepLinkImportDialog.tsx)
- Add API wrapper (src/lib/api/deeplink.ts)
- Integrate event listeners in App.tsx
Configuration:
- Update Tauri config for deep link handling
- Add i18n support for Chinese and English
- Include test page for deep link validation (deeplink-test.html)
Files: 15 changed, 1312 insertions(+)
* chore(deeplink): integrate deep link handling into app lifecycle
Wire up deep link infrastructure with app initialization and event handling.
Backend Integration:
- Register deep link module and commands in mod.rs
- Add URL handling in app setup (src-tauri/src/lib.rs:handle_deeplink_url)
- Handle deep links from single instance callback (Windows/Linux CLI)
- Handle deep links from macOS system events
- Add tauri-plugin-deep-link dependency (Cargo.toml)
Frontend Integration:
- Listen for deeplink-import/deeplink-error events in App.tsx
- Update DeepLinkImportDialog component imports
Configuration:
- Enable deep link plugin in tauri.conf.json
- Update Cargo.lock for new dependencies
Localization:
- Add Chinese translations for deep link UI (zh.json)
- Add English translations for deep link UI (en.json)
Files: 9 changed, 359 insertions(+), 18 deletions(-)
* refactor(deeplink): enhance Codex provider template generation
Align deep link import with UI preset generation logic by:
- Adding complete config.toml template matching frontend defaults
- Generating safe provider name from sanitized input
- Including model_provider, reasoning_effort, and wire_api settings
- Removing minimal template that only contained base_url
- Cleaning up deprecated test file deeplink-test.html
* style: fix clippy uninlined_format_args warnings
Apply clippy --fix to use inline format arguments in:
- src/mcp.rs (8 fixes)
- src/services/env_manager.rs (10 fixes)
* style: apply code formatting and cleanup
- Format TypeScript files with Prettier (App.tsx, EnvWarningBanner.tsx, formatters.ts)
- Organize Rust imports and module order alphabetically
- Add newline at end of JSON files (en.json, zh.json)
- Update Cargo.lock for dependency changes
* feat: add model name configuration support for Codex and fix Gemini model handling
- Add visual model name input field for Codex providers
- Add model name extraction and update utilities in providerConfigUtils
- Implement model name state management in useCodexConfigState hook
- Add conditional model field rendering in CodexFormFields (non-official only)
- Integrate model name sync with TOML config in ProviderForm
- Fix Gemini deeplink model injection bug
- Correct environment variable name from GOOGLE_GEMINI_MODEL to GEMINI_MODEL
- Add test cases for Gemini model injection (with/without model)
- All tests passing (9/9)
- Fix Gemini model field binding in edit mode
- Add geminiModel state to useGeminiConfigState hook
- Extract model value during initialization and reset
- Sync model field with geminiEnv state to prevent data loss on submit
- Fix missing model value display when editing Gemini providers
Changes:
- 6 files changed, 245 insertions(+), 13 deletions(-)
2025-11-19 09:03:18 +08:00
|
|
|
|
handleCodexModelNameChange,
|
2025-10-16 23:56:30 +08:00
|
|
|
|
handleCodexConfigChange: originalHandleCodexConfigChange,
|
2025-10-16 18:50:44 +08:00
|
|
|
|
resetCodexConfig,
|
|
|
|
|
|
} = useCodexConfigState({ initialData });
|
|
|
|
|
|
|
2025-10-16 23:56:30 +08:00
|
|
|
|
// 使用 Codex TOML 校验 hook (仅 Codex 模式)
|
2025-10-18 16:52:02 +08:00
|
|
|
|
const { configError: codexConfigError, debouncedValidate } =
|
|
|
|
|
|
useCodexTomlValidation();
|
2025-10-16 23:56:30 +08:00
|
|
|
|
|
|
|
|
|
|
// 包装 handleCodexConfigChange,添加实时校验
|
2025-10-18 16:52:02 +08:00
|
|
|
|
const handleCodexConfigChange = useCallback(
|
|
|
|
|
|
(value: string) => {
|
|
|
|
|
|
originalHandleCodexConfigChange(value);
|
|
|
|
|
|
debouncedValidate(value);
|
|
|
|
|
|
},
|
|
|
|
|
|
[originalHandleCodexConfigChange, debouncedValidate],
|
|
|
|
|
|
);
|
2025-10-16 23:56:30 +08:00
|
|
|
|
|
2025-11-16 13:36:33 +08:00
|
|
|
|
// Codex 新建模式:初始化时自动填充模板
|
2025-11-16 13:23:53 +08:00
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
if (appId === "codex" && !initialData && selectedPresetId === "custom") {
|
2025-11-16 13:36:33 +08:00
|
|
|
|
const template = getCodexCustomTemplate();
|
2025-11-16 13:23:53 +08:00
|
|
|
|
resetCodexConfig(template.auth, template.config);
|
|
|
|
|
|
}
|
2025-11-16 13:36:33 +08:00
|
|
|
|
}, [appId, initialData, selectedPresetId, resetCodexConfig]);
|
2025-11-16 13:23:53 +08:00
|
|
|
|
|
2025-10-16 10:49:56 +08:00
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
form.reset(defaultValues);
|
|
|
|
|
|
}, [defaultValues, form]);
|
|
|
|
|
|
|
2025-10-16 20:21:42 +08:00
|
|
|
|
const presetCategoryLabels: Record<string, string> = useMemo(
|
|
|
|
|
|
() => ({
|
2025-11-14 08:31:09 +08:00
|
|
|
|
official: t("providerForm.categoryOfficial", {
|
2025-10-16 20:21:42 +08:00
|
|
|
|
defaultValue: "官方",
|
|
|
|
|
|
}),
|
2025-11-14 08:31:09 +08:00
|
|
|
|
cn_official: t("providerForm.categoryCnOfficial", {
|
2025-10-16 20:21:42 +08:00
|
|
|
|
defaultValue: "国内官方",
|
|
|
|
|
|
}),
|
2025-11-14 08:31:09 +08:00
|
|
|
|
aggregator: t("providerForm.categoryAggregation", {
|
2025-10-16 20:21:42 +08:00
|
|
|
|
defaultValue: "聚合服务",
|
|
|
|
|
|
}),
|
2025-11-14 08:31:09 +08:00
|
|
|
|
third_party: t("providerForm.categoryThirdParty", {
|
2025-10-16 20:21:42 +08:00
|
|
|
|
defaultValue: "第三方",
|
|
|
|
|
|
}),
|
|
|
|
|
|
}),
|
2025-10-18 16:52:02 +08:00
|
|
|
|
[t],
|
2025-10-16 20:21:42 +08:00
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
const presetEntries = useMemo(() => {
|
2025-10-30 14:59:15 +08:00
|
|
|
|
if (appId === "codex") {
|
2025-10-16 20:21:42 +08:00
|
|
|
|
return codexProviderPresets.map<PresetEntry>((preset, index) => ({
|
|
|
|
|
|
id: `codex-${index}`,
|
|
|
|
|
|
preset,
|
|
|
|
|
|
}));
|
2025-11-12 10:47:34 +08:00
|
|
|
|
} else if (appId === "gemini") {
|
|
|
|
|
|
return geminiProviderPresets.map<PresetEntry>((preset, index) => ({
|
|
|
|
|
|
id: `gemini-${index}`,
|
|
|
|
|
|
preset,
|
|
|
|
|
|
}));
|
2025-10-16 20:21:42 +08:00
|
|
|
|
}
|
|
|
|
|
|
return providerPresets.map<PresetEntry>((preset, index) => ({
|
|
|
|
|
|
id: `claude-${index}`,
|
|
|
|
|
|
preset,
|
|
|
|
|
|
}));
|
2025-10-30 14:59:15 +08:00
|
|
|
|
}, [appId]);
|
2025-10-16 20:21:42 +08:00
|
|
|
|
|
2025-10-16 20:25:39 +08:00
|
|
|
|
// 使用模板变量 hook (仅 Claude 模式)
|
|
|
|
|
|
const {
|
|
|
|
|
|
templateValues,
|
|
|
|
|
|
templateValueEntries,
|
|
|
|
|
|
selectedPreset: templatePreset,
|
|
|
|
|
|
handleTemplateValueChange,
|
|
|
|
|
|
validateTemplateValues,
|
|
|
|
|
|
} = useTemplateValues({
|
2025-10-30 14:59:15 +08:00
|
|
|
|
selectedPresetId: appId === "claude" ? selectedPresetId : null,
|
|
|
|
|
|
presetEntries: appId === "claude" ? presetEntries : [],
|
2025-10-16 20:25:39 +08:00
|
|
|
|
settingsConfig: form.watch("settingsConfig"),
|
|
|
|
|
|
onConfigChange: (config) => form.setValue("settingsConfig", config),
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2025-10-16 20:32:11 +08:00
|
|
|
|
// 使用通用配置片段 hook (仅 Claude 模式)
|
|
|
|
|
|
const {
|
|
|
|
|
|
useCommonConfig,
|
|
|
|
|
|
commonConfigSnippet,
|
|
|
|
|
|
commonConfigError,
|
|
|
|
|
|
handleCommonConfigToggle,
|
|
|
|
|
|
handleCommonConfigSnippetChange,
|
|
|
|
|
|
} = useCommonConfigSnippet({
|
|
|
|
|
|
settingsConfig: form.watch("settingsConfig"),
|
|
|
|
|
|
onConfigChange: (config) => form.setValue("settingsConfig", config),
|
2025-10-30 14:59:15 +08:00
|
|
|
|
initialData: appId === "claude" ? initialData : undefined,
|
2025-10-16 20:32:11 +08:00
|
|
|
|
});
|
|
|
|
|
|
|
2025-10-16 21:04:32 +08:00
|
|
|
|
// 使用 Codex 通用配置片段 hook (仅 Codex 模式)
|
|
|
|
|
|
const {
|
|
|
|
|
|
useCommonConfig: useCodexCommonConfigFlag,
|
|
|
|
|
|
commonConfigSnippet: codexCommonConfigSnippet,
|
|
|
|
|
|
commonConfigError: codexCommonConfigError,
|
|
|
|
|
|
handleCommonConfigToggle: handleCodexCommonConfigToggle,
|
|
|
|
|
|
handleCommonConfigSnippetChange: handleCodexCommonConfigSnippetChange,
|
|
|
|
|
|
} = useCodexCommonConfig({
|
|
|
|
|
|
codexConfig,
|
|
|
|
|
|
onConfigChange: handleCodexConfigChange,
|
2025-10-30 14:59:15 +08:00
|
|
|
|
initialData: appId === "codex" ? initialData : undefined,
|
2025-10-16 21:04:32 +08:00
|
|
|
|
});
|
|
|
|
|
|
|
2025-11-14 08:31:09 +08:00
|
|
|
|
// 使用 Gemini 配置 hook (仅 Gemini 模式)
|
|
|
|
|
|
const {
|
|
|
|
|
|
geminiEnv,
|
|
|
|
|
|
geminiConfig,
|
feat: add model configuration support and fix Gemini deeplink bug (#251)
* feat(providers): add notes field for provider management
- Add notes field to Provider model (backend and frontend)
- Display notes with higher priority than URL in provider card
- Style notes as non-clickable text to differentiate from URLs
- Add notes input field in provider form
- Add i18n support (zh/en) for notes field
* chore: format code and clean up unused props
- Run cargo fmt on Rust backend code
- Format TypeScript imports and code style
- Remove unused appId prop from ProviderPresetSelector
- Clean up unused variables in tests
- Integrate notes field handling in provider dialogs
* feat(deeplink): implement ccswitch:// protocol for provider import
Add deep link support to enable one-click provider configuration import via ccswitch:// URLs.
Backend:
- Implement URL parsing and validation (src-tauri/src/deeplink.rs)
- Add Tauri commands for parse and import (src-tauri/src/commands/deeplink.rs)
- Register ccswitch:// protocol in macOS Info.plist
- Add comprehensive unit tests (src-tauri/tests/deeplink_import.rs)
Frontend:
- Create confirmation dialog with security review UI (src/components/DeepLinkImportDialog.tsx)
- Add API wrapper (src/lib/api/deeplink.ts)
- Integrate event listeners in App.tsx
Configuration:
- Update Tauri config for deep link handling
- Add i18n support for Chinese and English
- Include test page for deep link validation (deeplink-test.html)
Files: 15 changed, 1312 insertions(+)
* chore(deeplink): integrate deep link handling into app lifecycle
Wire up deep link infrastructure with app initialization and event handling.
Backend Integration:
- Register deep link module and commands in mod.rs
- Add URL handling in app setup (src-tauri/src/lib.rs:handle_deeplink_url)
- Handle deep links from single instance callback (Windows/Linux CLI)
- Handle deep links from macOS system events
- Add tauri-plugin-deep-link dependency (Cargo.toml)
Frontend Integration:
- Listen for deeplink-import/deeplink-error events in App.tsx
- Update DeepLinkImportDialog component imports
Configuration:
- Enable deep link plugin in tauri.conf.json
- Update Cargo.lock for new dependencies
Localization:
- Add Chinese translations for deep link UI (zh.json)
- Add English translations for deep link UI (en.json)
Files: 9 changed, 359 insertions(+), 18 deletions(-)
* refactor(deeplink): enhance Codex provider template generation
Align deep link import with UI preset generation logic by:
- Adding complete config.toml template matching frontend defaults
- Generating safe provider name from sanitized input
- Including model_provider, reasoning_effort, and wire_api settings
- Removing minimal template that only contained base_url
- Cleaning up deprecated test file deeplink-test.html
* style: fix clippy uninlined_format_args warnings
Apply clippy --fix to use inline format arguments in:
- src/mcp.rs (8 fixes)
- src/services/env_manager.rs (10 fixes)
* style: apply code formatting and cleanup
- Format TypeScript files with Prettier (App.tsx, EnvWarningBanner.tsx, formatters.ts)
- Organize Rust imports and module order alphabetically
- Add newline at end of JSON files (en.json, zh.json)
- Update Cargo.lock for dependency changes
* feat: add model name configuration support for Codex and fix Gemini model handling
- Add visual model name input field for Codex providers
- Add model name extraction and update utilities in providerConfigUtils
- Implement model name state management in useCodexConfigState hook
- Add conditional model field rendering in CodexFormFields (non-official only)
- Integrate model name sync with TOML config in ProviderForm
- Fix Gemini deeplink model injection bug
- Correct environment variable name from GOOGLE_GEMINI_MODEL to GEMINI_MODEL
- Add test cases for Gemini model injection (with/without model)
- All tests passing (9/9)
- Fix Gemini model field binding in edit mode
- Add geminiModel state to useGeminiConfigState hook
- Extract model value during initialization and reset
- Sync model field with geminiEnv state to prevent data loss on submit
- Fix missing model value display when editing Gemini providers
Changes:
- 6 files changed, 245 insertions(+), 13 deletions(-)
2025-11-19 09:03:18 +08:00
|
|
|
|
geminiModel,
|
2025-11-14 08:31:09 +08:00
|
|
|
|
envError,
|
|
|
|
|
|
configError: geminiConfigError,
|
|
|
|
|
|
handleGeminiEnvChange,
|
|
|
|
|
|
handleGeminiConfigChange,
|
|
|
|
|
|
resetGeminiConfig,
|
|
|
|
|
|
envStringToObj,
|
feat: add model configuration support and fix Gemini deeplink bug (#251)
* feat(providers): add notes field for provider management
- Add notes field to Provider model (backend and frontend)
- Display notes with higher priority than URL in provider card
- Style notes as non-clickable text to differentiate from URLs
- Add notes input field in provider form
- Add i18n support (zh/en) for notes field
* chore: format code and clean up unused props
- Run cargo fmt on Rust backend code
- Format TypeScript imports and code style
- Remove unused appId prop from ProviderPresetSelector
- Clean up unused variables in tests
- Integrate notes field handling in provider dialogs
* feat(deeplink): implement ccswitch:// protocol for provider import
Add deep link support to enable one-click provider configuration import via ccswitch:// URLs.
Backend:
- Implement URL parsing and validation (src-tauri/src/deeplink.rs)
- Add Tauri commands for parse and import (src-tauri/src/commands/deeplink.rs)
- Register ccswitch:// protocol in macOS Info.plist
- Add comprehensive unit tests (src-tauri/tests/deeplink_import.rs)
Frontend:
- Create confirmation dialog with security review UI (src/components/DeepLinkImportDialog.tsx)
- Add API wrapper (src/lib/api/deeplink.ts)
- Integrate event listeners in App.tsx
Configuration:
- Update Tauri config for deep link handling
- Add i18n support for Chinese and English
- Include test page for deep link validation (deeplink-test.html)
Files: 15 changed, 1312 insertions(+)
* chore(deeplink): integrate deep link handling into app lifecycle
Wire up deep link infrastructure with app initialization and event handling.
Backend Integration:
- Register deep link module and commands in mod.rs
- Add URL handling in app setup (src-tauri/src/lib.rs:handle_deeplink_url)
- Handle deep links from single instance callback (Windows/Linux CLI)
- Handle deep links from macOS system events
- Add tauri-plugin-deep-link dependency (Cargo.toml)
Frontend Integration:
- Listen for deeplink-import/deeplink-error events in App.tsx
- Update DeepLinkImportDialog component imports
Configuration:
- Enable deep link plugin in tauri.conf.json
- Update Cargo.lock for new dependencies
Localization:
- Add Chinese translations for deep link UI (zh.json)
- Add English translations for deep link UI (en.json)
Files: 9 changed, 359 insertions(+), 18 deletions(-)
* refactor(deeplink): enhance Codex provider template generation
Align deep link import with UI preset generation logic by:
- Adding complete config.toml template matching frontend defaults
- Generating safe provider name from sanitized input
- Including model_provider, reasoning_effort, and wire_api settings
- Removing minimal template that only contained base_url
- Cleaning up deprecated test file deeplink-test.html
* style: fix clippy uninlined_format_args warnings
Apply clippy --fix to use inline format arguments in:
- src/mcp.rs (8 fixes)
- src/services/env_manager.rs (10 fixes)
* style: apply code formatting and cleanup
- Format TypeScript files with Prettier (App.tsx, EnvWarningBanner.tsx, formatters.ts)
- Organize Rust imports and module order alphabetically
- Add newline at end of JSON files (en.json, zh.json)
- Update Cargo.lock for dependency changes
* feat: add model name configuration support for Codex and fix Gemini model handling
- Add visual model name input field for Codex providers
- Add model name extraction and update utilities in providerConfigUtils
- Implement model name state management in useCodexConfigState hook
- Add conditional model field rendering in CodexFormFields (non-official only)
- Integrate model name sync with TOML config in ProviderForm
- Fix Gemini deeplink model injection bug
- Correct environment variable name from GOOGLE_GEMINI_MODEL to GEMINI_MODEL
- Add test cases for Gemini model injection (with/without model)
- All tests passing (9/9)
- Fix Gemini model field binding in edit mode
- Add geminiModel state to useGeminiConfigState hook
- Extract model value during initialization and reset
- Sync model field with geminiEnv state to prevent data loss on submit
- Fix missing model value display when editing Gemini providers
Changes:
- 6 files changed, 245 insertions(+), 13 deletions(-)
2025-11-19 09:03:18 +08:00
|
|
|
|
envObjToString,
|
2025-11-14 08:31:09 +08:00
|
|
|
|
} = useGeminiConfigState({
|
|
|
|
|
|
initialData: appId === "gemini" ? initialData : undefined,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 使用 Gemini 通用配置 hook (仅 Gemini 模式)
|
|
|
|
|
|
const {
|
|
|
|
|
|
useCommonConfig: useGeminiCommonConfigFlag,
|
|
|
|
|
|
commonConfigSnippet: geminiCommonConfigSnippet,
|
|
|
|
|
|
commonConfigError: geminiCommonConfigError,
|
|
|
|
|
|
handleCommonConfigToggle: handleGeminiCommonConfigToggle,
|
|
|
|
|
|
handleCommonConfigSnippetChange: handleGeminiCommonConfigSnippetChange,
|
|
|
|
|
|
} = useGeminiCommonConfig({
|
|
|
|
|
|
configValue: geminiConfig,
|
|
|
|
|
|
onConfigChange: handleGeminiConfigChange,
|
|
|
|
|
|
initialData: appId === "gemini" ? initialData : undefined,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2025-10-16 20:32:11 +08:00
|
|
|
|
const [isCommonConfigModalOpen, setIsCommonConfigModalOpen] = useState(false);
|
|
|
|
|
|
|
2025-10-16 10:49:56 +08:00
|
|
|
|
const handleSubmit = (values: ProviderFormData) => {
|
2025-10-16 20:25:39 +08:00
|
|
|
|
// 验证模板变量(仅 Claude 模式)
|
2025-10-30 14:59:15 +08:00
|
|
|
|
if (appId === "claude" && templateValueEntries.length > 0) {
|
2025-10-16 20:25:39 +08:00
|
|
|
|
const validation = validateTemplateValues();
|
|
|
|
|
|
if (!validation.isValid && validation.missingField) {
|
|
|
|
|
|
form.setError("settingsConfig", {
|
|
|
|
|
|
type: "manual",
|
|
|
|
|
|
message: t("providerForm.fillParameter", {
|
|
|
|
|
|
label: validation.missingField.label,
|
2025-11-14 08:31:09 +08:00
|
|
|
|
defaultValue: `请填写 ${validation.missingField.label}`,
|
2025-10-16 20:25:39 +08:00
|
|
|
|
}),
|
|
|
|
|
|
});
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-16 18:50:44 +08:00
|
|
|
|
let settingsConfig: string;
|
|
|
|
|
|
|
|
|
|
|
|
// Codex: 组合 auth 和 config
|
2025-10-30 14:59:15 +08:00
|
|
|
|
if (appId === "codex") {
|
2025-10-16 18:50:44 +08:00
|
|
|
|
try {
|
|
|
|
|
|
const authJson = JSON.parse(codexAuth);
|
|
|
|
|
|
const configObj = {
|
|
|
|
|
|
auth: authJson,
|
|
|
|
|
|
config: codexConfig ?? "",
|
|
|
|
|
|
};
|
|
|
|
|
|
settingsConfig = JSON.stringify(configObj);
|
|
|
|
|
|
} catch (err) {
|
|
|
|
|
|
// 如果解析失败,使用表单中的配置
|
|
|
|
|
|
settingsConfig = values.settingsConfig.trim();
|
|
|
|
|
|
}
|
2025-11-14 08:31:09 +08:00
|
|
|
|
} else if (appId === "gemini") {
|
|
|
|
|
|
// Gemini: 组合 env 和 config
|
|
|
|
|
|
try {
|
|
|
|
|
|
const envObj = envStringToObj(geminiEnv);
|
|
|
|
|
|
const configObj = geminiConfig.trim() ? JSON.parse(geminiConfig) : {};
|
|
|
|
|
|
const combined = {
|
|
|
|
|
|
env: envObj,
|
|
|
|
|
|
config: configObj,
|
|
|
|
|
|
};
|
|
|
|
|
|
settingsConfig = JSON.stringify(combined);
|
|
|
|
|
|
} catch (err) {
|
|
|
|
|
|
// 如果解析失败,使用表单中的配置
|
|
|
|
|
|
settingsConfig = values.settingsConfig.trim();
|
|
|
|
|
|
}
|
2025-10-16 18:50:44 +08:00
|
|
|
|
} else {
|
|
|
|
|
|
// Claude: 使用表单配置
|
|
|
|
|
|
settingsConfig = values.settingsConfig.trim();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-16 13:02:38 +08:00
|
|
|
|
const payload: ProviderFormValues = {
|
2025-10-16 10:49:56 +08:00
|
|
|
|
...values,
|
2025-10-16 13:02:38 +08:00
|
|
|
|
name: values.name.trim(),
|
2025-10-16 10:49:56 +08:00
|
|
|
|
websiteUrl: values.websiteUrl?.trim() ?? "",
|
2025-10-16 18:50:44 +08:00
|
|
|
|
settingsConfig,
|
2025-10-16 13:02:38 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
if (activePreset) {
|
|
|
|
|
|
payload.presetId = activePreset.id;
|
|
|
|
|
|
if (activePreset.category) {
|
|
|
|
|
|
payload.presetCategory = activePreset.category;
|
|
|
|
|
|
}
|
2025-11-06 15:22:38 +08:00
|
|
|
|
// 继承合作伙伴标识
|
|
|
|
|
|
if (activePreset.isPartner) {
|
|
|
|
|
|
payload.isPartner = activePreset.isPartner;
|
|
|
|
|
|
}
|
2025-10-16 13:02:38 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-12 11:02:43 +08:00
|
|
|
|
// 处理 meta 字段:仅在新建模式下从 draftCustomEndpoints 生成 custom_endpoints
|
|
|
|
|
|
// 编辑模式:端点已通过 API 直接保存,不在此处理
|
|
|
|
|
|
if (!isEditMode && draftCustomEndpoints.length > 0) {
|
|
|
|
|
|
const customEndpointsToSave: Record<
|
|
|
|
|
|
string,
|
|
|
|
|
|
import("@/types").CustomEndpoint
|
|
|
|
|
|
> = draftCustomEndpoints.reduce(
|
|
|
|
|
|
(acc, url) => {
|
|
|
|
|
|
const now = Date.now();
|
|
|
|
|
|
acc[url] = { url, addedAt: now, lastUsed: undefined };
|
|
|
|
|
|
return acc;
|
|
|
|
|
|
},
|
|
|
|
|
|
{} as Record<string, import("@/types").CustomEndpoint>,
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
// 检测是否需要清空端点(重要:区分"用户清空端点"和"用户没有修改端点")
|
|
|
|
|
|
const hadEndpoints =
|
|
|
|
|
|
initialData?.meta?.custom_endpoints &&
|
|
|
|
|
|
Object.keys(initialData.meta.custom_endpoints).length > 0;
|
|
|
|
|
|
const needsClearEndpoints =
|
|
|
|
|
|
hadEndpoints && draftCustomEndpoints.length === 0;
|
|
|
|
|
|
|
|
|
|
|
|
// 如果用户明确清空了端点,传递空对象(而不是 null)让后端知道要删除
|
|
|
|
|
|
let mergedMeta = needsClearEndpoints
|
|
|
|
|
|
? mergeProviderMeta(initialData?.meta, {})
|
|
|
|
|
|
: mergeProviderMeta(initialData?.meta, customEndpointsToSave);
|
|
|
|
|
|
|
|
|
|
|
|
// 添加合作伙伴标识与促销 key
|
|
|
|
|
|
if (activePreset?.isPartner) {
|
|
|
|
|
|
mergedMeta = {
|
|
|
|
|
|
...(mergedMeta ?? {}),
|
|
|
|
|
|
isPartner: true,
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
2025-11-12 10:47:34 +08:00
|
|
|
|
|
2025-11-12 11:02:43 +08:00
|
|
|
|
if (activePreset?.partnerPromotionKey) {
|
|
|
|
|
|
mergedMeta = {
|
|
|
|
|
|
...(mergedMeta ?? {}),
|
|
|
|
|
|
partnerPromotionKey: activePreset.partnerPromotionKey,
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
2025-11-12 10:47:34 +08:00
|
|
|
|
|
2025-11-12 11:02:43 +08:00
|
|
|
|
if (mergedMeta !== undefined) {
|
|
|
|
|
|
payload.meta = mergedMeta;
|
|
|
|
|
|
}
|
2025-10-16 19:40:22 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-16 13:02:38 +08:00
|
|
|
|
onSubmit(payload);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const groupedPresets = useMemo(() => {
|
|
|
|
|
|
return presetEntries.reduce<Record<string, PresetEntry[]>>((acc, entry) => {
|
|
|
|
|
|
const category = entry.preset.category ?? "others";
|
|
|
|
|
|
if (!acc[category]) {
|
|
|
|
|
|
acc[category] = [];
|
|
|
|
|
|
}
|
|
|
|
|
|
acc[category].push(entry);
|
|
|
|
|
|
return acc;
|
|
|
|
|
|
}, {});
|
|
|
|
|
|
}, [presetEntries]);
|
|
|
|
|
|
|
|
|
|
|
|
const categoryKeys = useMemo(() => {
|
|
|
|
|
|
return Object.keys(groupedPresets).filter(
|
2025-10-18 16:52:02 +08:00
|
|
|
|
(key) => key !== "custom" && groupedPresets[key]?.length,
|
2025-10-16 13:02:38 +08:00
|
|
|
|
);
|
|
|
|
|
|
}, [groupedPresets]);
|
|
|
|
|
|
|
2025-11-07 11:25:55 +08:00
|
|
|
|
// 判断是否显示端点测速(仅官方类别不显示)
|
|
|
|
|
|
const shouldShowSpeedTest = category !== "official";
|
2025-10-16 17:44:23 +08:00
|
|
|
|
|
2025-10-16 19:56:00 +08:00
|
|
|
|
// 使用 API Key 链接 hook (Claude)
|
|
|
|
|
|
const {
|
|
|
|
|
|
shouldShowApiKeyLink: shouldShowClaudeApiKeyLink,
|
|
|
|
|
|
websiteUrl: claudeWebsiteUrl,
|
2025-11-06 15:22:38 +08:00
|
|
|
|
isPartner: isClaudePartner,
|
|
|
|
|
|
partnerPromotionKey: claudePartnerPromotionKey,
|
2025-10-16 19:56:00 +08:00
|
|
|
|
} = useApiKeyLink({
|
2025-10-30 14:59:15 +08:00
|
|
|
|
appId: "claude",
|
2025-10-16 19:56:00 +08:00
|
|
|
|
category,
|
|
|
|
|
|
selectedPresetId,
|
|
|
|
|
|
presetEntries,
|
|
|
|
|
|
formWebsiteUrl: form.watch("websiteUrl") || "",
|
|
|
|
|
|
});
|
2025-10-16 19:37:43 +08:00
|
|
|
|
|
2025-10-16 19:56:00 +08:00
|
|
|
|
// 使用 API Key 链接 hook (Codex)
|
|
|
|
|
|
const {
|
|
|
|
|
|
shouldShowApiKeyLink: shouldShowCodexApiKeyLink,
|
|
|
|
|
|
websiteUrl: codexWebsiteUrl,
|
2025-11-06 15:22:38 +08:00
|
|
|
|
isPartner: isCodexPartner,
|
|
|
|
|
|
partnerPromotionKey: codexPartnerPromotionKey,
|
2025-10-16 19:56:00 +08:00
|
|
|
|
} = useApiKeyLink({
|
2025-10-30 14:59:15 +08:00
|
|
|
|
appId: "codex",
|
2025-10-16 19:56:00 +08:00
|
|
|
|
category,
|
|
|
|
|
|
selectedPresetId,
|
|
|
|
|
|
presetEntries,
|
|
|
|
|
|
formWebsiteUrl: form.watch("websiteUrl") || "",
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2025-11-12 10:47:34 +08:00
|
|
|
|
// 使用 API Key 链接 hook (Gemini)
|
|
|
|
|
|
const {
|
|
|
|
|
|
shouldShowApiKeyLink: shouldShowGeminiApiKeyLink,
|
|
|
|
|
|
websiteUrl: geminiWebsiteUrl,
|
|
|
|
|
|
isPartner: isGeminiPartner,
|
|
|
|
|
|
partnerPromotionKey: geminiPartnerPromotionKey,
|
|
|
|
|
|
} = useApiKeyLink({
|
|
|
|
|
|
appId: "gemini",
|
|
|
|
|
|
category,
|
|
|
|
|
|
selectedPresetId,
|
|
|
|
|
|
presetEntries,
|
|
|
|
|
|
formWebsiteUrl: form.watch("websiteUrl") || "",
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2025-10-16 22:41:36 +08:00
|
|
|
|
// 使用端点测速候选 hook
|
|
|
|
|
|
const speedTestEndpoints = useSpeedTestEndpoints({
|
2025-10-30 14:59:15 +08:00
|
|
|
|
appId,
|
2025-10-16 22:41:36 +08:00
|
|
|
|
selectedPresetId,
|
|
|
|
|
|
presetEntries,
|
|
|
|
|
|
baseUrl,
|
|
|
|
|
|
codexBaseUrl,
|
|
|
|
|
|
initialData,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2025-10-16 13:02:38 +08:00
|
|
|
|
const handlePresetChange = (value: string) => {
|
|
|
|
|
|
setSelectedPresetId(value);
|
|
|
|
|
|
if (value === "custom") {
|
|
|
|
|
|
setActivePreset(null);
|
2025-10-16 16:51:47 +08:00
|
|
|
|
form.reset(defaultValues);
|
2025-10-16 18:50:44 +08:00
|
|
|
|
|
2025-11-16 13:36:33 +08:00
|
|
|
|
// Codex 自定义模式:加载模板
|
2025-10-30 14:59:15 +08:00
|
|
|
|
if (appId === "codex") {
|
2025-11-16 13:36:33 +08:00
|
|
|
|
const template = getCodexCustomTemplate();
|
2025-11-16 13:23:53 +08:00
|
|
|
|
resetCodexConfig(template.auth, template.config);
|
2025-10-16 18:50:44 +08:00
|
|
|
|
}
|
2025-11-14 08:31:09 +08:00
|
|
|
|
// Gemini 自定义模式:重置为空配置
|
|
|
|
|
|
if (appId === "gemini") {
|
|
|
|
|
|
resetGeminiConfig({}, {});
|
|
|
|
|
|
}
|
2025-10-16 13:02:38 +08:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const entry = presetEntries.find((item) => item.id === value);
|
|
|
|
|
|
if (!entry) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
setActivePreset({
|
|
|
|
|
|
id: value,
|
|
|
|
|
|
category: entry.preset.category,
|
2025-11-06 15:22:38 +08:00
|
|
|
|
isPartner: entry.preset.isPartner,
|
2025-11-12 10:47:34 +08:00
|
|
|
|
partnerPromotionKey: entry.preset.partnerPromotionKey,
|
2025-10-16 13:02:38 +08:00
|
|
|
|
});
|
|
|
|
|
|
|
2025-10-30 14:59:15 +08:00
|
|
|
|
if (appId === "codex") {
|
2025-10-16 13:02:38 +08:00
|
|
|
|
const preset = entry.preset as CodexProviderPreset;
|
2025-10-16 18:50:44 +08:00
|
|
|
|
const auth = preset.auth ?? {};
|
|
|
|
|
|
const config = preset.config ?? "";
|
2025-10-16 13:02:38 +08:00
|
|
|
|
|
2025-10-16 18:50:44 +08:00
|
|
|
|
// 重置 Codex 配置
|
|
|
|
|
|
resetCodexConfig(auth, config);
|
|
|
|
|
|
|
|
|
|
|
|
// 更新表单其他字段
|
2025-10-16 13:02:38 +08:00
|
|
|
|
form.reset({
|
|
|
|
|
|
name: preset.name,
|
|
|
|
|
|
websiteUrl: preset.websiteUrl ?? "",
|
2025-10-16 18:50:44 +08:00
|
|
|
|
settingsConfig: JSON.stringify({ auth, config }, null, 2),
|
2025-10-16 13:02:38 +08:00
|
|
|
|
});
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-12 10:47:34 +08:00
|
|
|
|
if (appId === "gemini") {
|
|
|
|
|
|
const preset = entry.preset as GeminiProviderPreset;
|
2025-11-14 08:31:09 +08:00
|
|
|
|
const env = (preset.settingsConfig as any)?.env ?? {};
|
|
|
|
|
|
const config = (preset.settingsConfig as any)?.config ?? {};
|
|
|
|
|
|
|
|
|
|
|
|
// 重置 Gemini 配置
|
|
|
|
|
|
resetGeminiConfig(env, config);
|
|
|
|
|
|
|
|
|
|
|
|
// 更新表单其他字段
|
2025-11-12 10:47:34 +08:00
|
|
|
|
form.reset({
|
|
|
|
|
|
name: preset.name,
|
|
|
|
|
|
websiteUrl: preset.websiteUrl ?? "",
|
|
|
|
|
|
settingsConfig: JSON.stringify(preset.settingsConfig, null, 2),
|
|
|
|
|
|
});
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-16 13:02:38 +08:00
|
|
|
|
const preset = entry.preset as ProviderPreset;
|
|
|
|
|
|
const config = applyTemplateValues(
|
|
|
|
|
|
preset.settingsConfig,
|
2025-10-18 16:52:02 +08:00
|
|
|
|
preset.templateValues,
|
2025-10-16 13:02:38 +08:00
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
form.reset({
|
|
|
|
|
|
name: preset.name,
|
|
|
|
|
|
websiteUrl: preset.websiteUrl ?? "",
|
|
|
|
|
|
settingsConfig: JSON.stringify(config, null, 2),
|
2025-10-16 10:49:56 +08:00
|
|
|
|
});
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
<Form {...form}>
|
2025-10-24 13:02:35 +08:00
|
|
|
|
<form
|
|
|
|
|
|
id="provider-form"
|
|
|
|
|
|
onSubmit={form.handleSubmit(handleSubmit)}
|
|
|
|
|
|
className="space-y-6"
|
|
|
|
|
|
>
|
2025-10-16 16:51:47 +08:00
|
|
|
|
{/* 预设供应商选择(仅新增模式显示) */}
|
|
|
|
|
|
{!initialData && (
|
2025-10-16 21:40:42 +08:00
|
|
|
|
<ProviderPresetSelector
|
|
|
|
|
|
selectedPresetId={selectedPresetId}
|
|
|
|
|
|
groupedPresets={groupedPresets}
|
|
|
|
|
|
categoryKeys={categoryKeys}
|
|
|
|
|
|
presetCategoryLabels={presetCategoryLabels}
|
|
|
|
|
|
onPresetChange={handlePresetChange}
|
2025-10-19 12:24:47 +08:00
|
|
|
|
category={category}
|
2025-10-16 17:44:23 +08:00
|
|
|
|
/>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
2025-10-16 21:40:42 +08:00
|
|
|
|
{/* 基础字段 */}
|
|
|
|
|
|
<BasicFormFields form={form} />
|
|
|
|
|
|
|
|
|
|
|
|
{/* Claude 专属字段 */}
|
2025-10-30 14:59:15 +08:00
|
|
|
|
{appId === "claude" && (
|
2025-10-16 21:40:42 +08:00
|
|
|
|
<ClaudeFormFields
|
2025-11-04 15:30:54 +08:00
|
|
|
|
providerId={providerId}
|
2025-10-16 21:40:42 +08:00
|
|
|
|
shouldShowApiKey={shouldShowApiKey(
|
|
|
|
|
|
form.watch("settingsConfig"),
|
2025-10-18 16:52:02 +08:00
|
|
|
|
isEditMode,
|
2025-10-16 21:40:42 +08:00
|
|
|
|
)}
|
2025-10-16 20:21:42 +08:00
|
|
|
|
apiKey={apiKey}
|
2025-10-16 21:40:42 +08:00
|
|
|
|
onApiKeyChange={handleApiKeyChange}
|
|
|
|
|
|
category={category}
|
|
|
|
|
|
shouldShowApiKeyLink={shouldShowClaudeApiKeyLink}
|
|
|
|
|
|
websiteUrl={claudeWebsiteUrl}
|
2025-11-06 15:22:38 +08:00
|
|
|
|
isPartner={isClaudePartner}
|
|
|
|
|
|
partnerPromotionKey={claudePartnerPromotionKey}
|
2025-10-16 21:40:42 +08:00
|
|
|
|
templateValueEntries={templateValueEntries}
|
|
|
|
|
|
templateValues={templateValues}
|
|
|
|
|
|
templatePresetName={templatePreset?.name || ""}
|
|
|
|
|
|
onTemplateValueChange={handleTemplateValueChange}
|
|
|
|
|
|
shouldShowSpeedTest={shouldShowSpeedTest}
|
|
|
|
|
|
baseUrl={baseUrl}
|
|
|
|
|
|
onBaseUrlChange={handleClaudeBaseUrlChange}
|
|
|
|
|
|
isEndpointModalOpen={isEndpointModalOpen}
|
|
|
|
|
|
onEndpointModalToggle={setIsEndpointModalOpen}
|
2025-11-12 11:02:43 +08:00
|
|
|
|
onCustomEndpointsChange={
|
|
|
|
|
|
isEditMode ? undefined : setDraftCustomEndpoints
|
|
|
|
|
|
}
|
2025-11-02 20:57:16 +08:00
|
|
|
|
shouldShowModelSelector={category !== "official"}
|
2025-10-16 21:40:42 +08:00
|
|
|
|
claudeModel={claudeModel}
|
2025-11-02 18:02:22 +08:00
|
|
|
|
defaultHaikuModel={defaultHaikuModel}
|
|
|
|
|
|
defaultSonnetModel={defaultSonnetModel}
|
|
|
|
|
|
defaultOpusModel={defaultOpusModel}
|
2025-10-16 21:40:42 +08:00
|
|
|
|
onModelChange={handleModelChange}
|
2025-10-16 22:41:36 +08:00
|
|
|
|
speedTestEndpoints={speedTestEndpoints}
|
2025-10-16 20:21:42 +08:00
|
|
|
|
/>
|
2025-10-16 17:58:49 +08:00
|
|
|
|
)}
|
|
|
|
|
|
|
2025-10-16 21:40:42 +08:00
|
|
|
|
{/* Codex 专属字段 */}
|
2025-10-30 14:59:15 +08:00
|
|
|
|
{appId === "codex" && (
|
2025-10-16 21:40:42 +08:00
|
|
|
|
<CodexFormFields
|
2025-11-04 15:30:54 +08:00
|
|
|
|
providerId={providerId}
|
2025-10-16 21:40:42 +08:00
|
|
|
|
codexApiKey={codexApiKey}
|
|
|
|
|
|
onApiKeyChange={handleCodexApiKeyChange}
|
|
|
|
|
|
category={category}
|
|
|
|
|
|
shouldShowApiKeyLink={shouldShowCodexApiKeyLink}
|
|
|
|
|
|
websiteUrl={codexWebsiteUrl}
|
2025-11-06 15:22:38 +08:00
|
|
|
|
isPartner={isCodexPartner}
|
|
|
|
|
|
partnerPromotionKey={codexPartnerPromotionKey}
|
2025-10-16 21:40:42 +08:00
|
|
|
|
shouldShowSpeedTest={shouldShowSpeedTest}
|
|
|
|
|
|
codexBaseUrl={codexBaseUrl}
|
|
|
|
|
|
onBaseUrlChange={handleCodexBaseUrlChange}
|
|
|
|
|
|
isEndpointModalOpen={isCodexEndpointModalOpen}
|
|
|
|
|
|
onEndpointModalToggle={setIsCodexEndpointModalOpen}
|
2025-11-12 11:02:43 +08:00
|
|
|
|
onCustomEndpointsChange={
|
|
|
|
|
|
isEditMode ? undefined : setDraftCustomEndpoints
|
|
|
|
|
|
}
|
feat: add model configuration support and fix Gemini deeplink bug (#251)
* feat(providers): add notes field for provider management
- Add notes field to Provider model (backend and frontend)
- Display notes with higher priority than URL in provider card
- Style notes as non-clickable text to differentiate from URLs
- Add notes input field in provider form
- Add i18n support (zh/en) for notes field
* chore: format code and clean up unused props
- Run cargo fmt on Rust backend code
- Format TypeScript imports and code style
- Remove unused appId prop from ProviderPresetSelector
- Clean up unused variables in tests
- Integrate notes field handling in provider dialogs
* feat(deeplink): implement ccswitch:// protocol for provider import
Add deep link support to enable one-click provider configuration import via ccswitch:// URLs.
Backend:
- Implement URL parsing and validation (src-tauri/src/deeplink.rs)
- Add Tauri commands for parse and import (src-tauri/src/commands/deeplink.rs)
- Register ccswitch:// protocol in macOS Info.plist
- Add comprehensive unit tests (src-tauri/tests/deeplink_import.rs)
Frontend:
- Create confirmation dialog with security review UI (src/components/DeepLinkImportDialog.tsx)
- Add API wrapper (src/lib/api/deeplink.ts)
- Integrate event listeners in App.tsx
Configuration:
- Update Tauri config for deep link handling
- Add i18n support for Chinese and English
- Include test page for deep link validation (deeplink-test.html)
Files: 15 changed, 1312 insertions(+)
* chore(deeplink): integrate deep link handling into app lifecycle
Wire up deep link infrastructure with app initialization and event handling.
Backend Integration:
- Register deep link module and commands in mod.rs
- Add URL handling in app setup (src-tauri/src/lib.rs:handle_deeplink_url)
- Handle deep links from single instance callback (Windows/Linux CLI)
- Handle deep links from macOS system events
- Add tauri-plugin-deep-link dependency (Cargo.toml)
Frontend Integration:
- Listen for deeplink-import/deeplink-error events in App.tsx
- Update DeepLinkImportDialog component imports
Configuration:
- Enable deep link plugin in tauri.conf.json
- Update Cargo.lock for new dependencies
Localization:
- Add Chinese translations for deep link UI (zh.json)
- Add English translations for deep link UI (en.json)
Files: 9 changed, 359 insertions(+), 18 deletions(-)
* refactor(deeplink): enhance Codex provider template generation
Align deep link import with UI preset generation logic by:
- Adding complete config.toml template matching frontend defaults
- Generating safe provider name from sanitized input
- Including model_provider, reasoning_effort, and wire_api settings
- Removing minimal template that only contained base_url
- Cleaning up deprecated test file deeplink-test.html
* style: fix clippy uninlined_format_args warnings
Apply clippy --fix to use inline format arguments in:
- src/mcp.rs (8 fixes)
- src/services/env_manager.rs (10 fixes)
* style: apply code formatting and cleanup
- Format TypeScript files with Prettier (App.tsx, EnvWarningBanner.tsx, formatters.ts)
- Organize Rust imports and module order alphabetically
- Add newline at end of JSON files (en.json, zh.json)
- Update Cargo.lock for dependency changes
* feat: add model name configuration support for Codex and fix Gemini model handling
- Add visual model name input field for Codex providers
- Add model name extraction and update utilities in providerConfigUtils
- Implement model name state management in useCodexConfigState hook
- Add conditional model field rendering in CodexFormFields (non-official only)
- Integrate model name sync with TOML config in ProviderForm
- Fix Gemini deeplink model injection bug
- Correct environment variable name from GOOGLE_GEMINI_MODEL to GEMINI_MODEL
- Add test cases for Gemini model injection (with/without model)
- All tests passing (9/9)
- Fix Gemini model field binding in edit mode
- Add geminiModel state to useGeminiConfigState hook
- Extract model value during initialization and reset
- Sync model field with geminiEnv state to prevent data loss on submit
- Fix missing model value display when editing Gemini providers
Changes:
- 6 files changed, 245 insertions(+), 13 deletions(-)
2025-11-19 09:03:18 +08:00
|
|
|
|
shouldShowModelField={category !== "official"}
|
|
|
|
|
|
modelName={codexModelName}
|
|
|
|
|
|
onModelNameChange={handleCodexModelNameChange}
|
2025-10-16 22:41:36 +08:00
|
|
|
|
speedTestEndpoints={speedTestEndpoints}
|
2025-10-16 21:40:42 +08:00
|
|
|
|
/>
|
2025-10-16 18:50:44 +08:00
|
|
|
|
)}
|
|
|
|
|
|
|
2025-11-12 10:47:34 +08:00
|
|
|
|
{/* Gemini 专属字段 */}
|
|
|
|
|
|
{appId === "gemini" && (
|
|
|
|
|
|
<GeminiFormFields
|
|
|
|
|
|
providerId={providerId}
|
|
|
|
|
|
shouldShowApiKey={shouldShowApiKey(
|
|
|
|
|
|
form.watch("settingsConfig"),
|
|
|
|
|
|
isEditMode,
|
|
|
|
|
|
)}
|
|
|
|
|
|
apiKey={apiKey}
|
|
|
|
|
|
onApiKeyChange={handleApiKeyChange}
|
|
|
|
|
|
category={category}
|
|
|
|
|
|
shouldShowApiKeyLink={shouldShowGeminiApiKeyLink}
|
|
|
|
|
|
websiteUrl={geminiWebsiteUrl}
|
|
|
|
|
|
isPartner={isGeminiPartner}
|
|
|
|
|
|
partnerPromotionKey={geminiPartnerPromotionKey}
|
|
|
|
|
|
shouldShowSpeedTest={shouldShowSpeedTest}
|
|
|
|
|
|
baseUrl={baseUrl}
|
|
|
|
|
|
onBaseUrlChange={handleGeminiBaseUrlChange}
|
|
|
|
|
|
isEndpointModalOpen={isEndpointModalOpen}
|
|
|
|
|
|
onEndpointModalToggle={setIsEndpointModalOpen}
|
|
|
|
|
|
onCustomEndpointsChange={setDraftCustomEndpoints}
|
|
|
|
|
|
shouldShowModelField={true}
|
feat: add model configuration support and fix Gemini deeplink bug (#251)
* feat(providers): add notes field for provider management
- Add notes field to Provider model (backend and frontend)
- Display notes with higher priority than URL in provider card
- Style notes as non-clickable text to differentiate from URLs
- Add notes input field in provider form
- Add i18n support (zh/en) for notes field
* chore: format code and clean up unused props
- Run cargo fmt on Rust backend code
- Format TypeScript imports and code style
- Remove unused appId prop from ProviderPresetSelector
- Clean up unused variables in tests
- Integrate notes field handling in provider dialogs
* feat(deeplink): implement ccswitch:// protocol for provider import
Add deep link support to enable one-click provider configuration import via ccswitch:// URLs.
Backend:
- Implement URL parsing and validation (src-tauri/src/deeplink.rs)
- Add Tauri commands for parse and import (src-tauri/src/commands/deeplink.rs)
- Register ccswitch:// protocol in macOS Info.plist
- Add comprehensive unit tests (src-tauri/tests/deeplink_import.rs)
Frontend:
- Create confirmation dialog with security review UI (src/components/DeepLinkImportDialog.tsx)
- Add API wrapper (src/lib/api/deeplink.ts)
- Integrate event listeners in App.tsx
Configuration:
- Update Tauri config for deep link handling
- Add i18n support for Chinese and English
- Include test page for deep link validation (deeplink-test.html)
Files: 15 changed, 1312 insertions(+)
* chore(deeplink): integrate deep link handling into app lifecycle
Wire up deep link infrastructure with app initialization and event handling.
Backend Integration:
- Register deep link module and commands in mod.rs
- Add URL handling in app setup (src-tauri/src/lib.rs:handle_deeplink_url)
- Handle deep links from single instance callback (Windows/Linux CLI)
- Handle deep links from macOS system events
- Add tauri-plugin-deep-link dependency (Cargo.toml)
Frontend Integration:
- Listen for deeplink-import/deeplink-error events in App.tsx
- Update DeepLinkImportDialog component imports
Configuration:
- Enable deep link plugin in tauri.conf.json
- Update Cargo.lock for new dependencies
Localization:
- Add Chinese translations for deep link UI (zh.json)
- Add English translations for deep link UI (en.json)
Files: 9 changed, 359 insertions(+), 18 deletions(-)
* refactor(deeplink): enhance Codex provider template generation
Align deep link import with UI preset generation logic by:
- Adding complete config.toml template matching frontend defaults
- Generating safe provider name from sanitized input
- Including model_provider, reasoning_effort, and wire_api settings
- Removing minimal template that only contained base_url
- Cleaning up deprecated test file deeplink-test.html
* style: fix clippy uninlined_format_args warnings
Apply clippy --fix to use inline format arguments in:
- src/mcp.rs (8 fixes)
- src/services/env_manager.rs (10 fixes)
* style: apply code formatting and cleanup
- Format TypeScript files with Prettier (App.tsx, EnvWarningBanner.tsx, formatters.ts)
- Organize Rust imports and module order alphabetically
- Add newline at end of JSON files (en.json, zh.json)
- Update Cargo.lock for dependency changes
* feat: add model name configuration support for Codex and fix Gemini model handling
- Add visual model name input field for Codex providers
- Add model name extraction and update utilities in providerConfigUtils
- Implement model name state management in useCodexConfigState hook
- Add conditional model field rendering in CodexFormFields (non-official only)
- Integrate model name sync with TOML config in ProviderForm
- Fix Gemini deeplink model injection bug
- Correct environment variable name from GOOGLE_GEMINI_MODEL to GEMINI_MODEL
- Add test cases for Gemini model injection (with/without model)
- All tests passing (9/9)
- Fix Gemini model field binding in edit mode
- Add geminiModel state to useGeminiConfigState hook
- Extract model value during initialization and reset
- Sync model field with geminiEnv state to prevent data loss on submit
- Fix missing model value display when editing Gemini providers
Changes:
- 6 files changed, 245 insertions(+), 13 deletions(-)
2025-11-19 09:03:18 +08:00
|
|
|
|
model={geminiModel}
|
2025-11-12 10:47:34 +08:00
|
|
|
|
onModelChange={(model) => {
|
feat: add model configuration support and fix Gemini deeplink bug (#251)
* feat(providers): add notes field for provider management
- Add notes field to Provider model (backend and frontend)
- Display notes with higher priority than URL in provider card
- Style notes as non-clickable text to differentiate from URLs
- Add notes input field in provider form
- Add i18n support (zh/en) for notes field
* chore: format code and clean up unused props
- Run cargo fmt on Rust backend code
- Format TypeScript imports and code style
- Remove unused appId prop from ProviderPresetSelector
- Clean up unused variables in tests
- Integrate notes field handling in provider dialogs
* feat(deeplink): implement ccswitch:// protocol for provider import
Add deep link support to enable one-click provider configuration import via ccswitch:// URLs.
Backend:
- Implement URL parsing and validation (src-tauri/src/deeplink.rs)
- Add Tauri commands for parse and import (src-tauri/src/commands/deeplink.rs)
- Register ccswitch:// protocol in macOS Info.plist
- Add comprehensive unit tests (src-tauri/tests/deeplink_import.rs)
Frontend:
- Create confirmation dialog with security review UI (src/components/DeepLinkImportDialog.tsx)
- Add API wrapper (src/lib/api/deeplink.ts)
- Integrate event listeners in App.tsx
Configuration:
- Update Tauri config for deep link handling
- Add i18n support for Chinese and English
- Include test page for deep link validation (deeplink-test.html)
Files: 15 changed, 1312 insertions(+)
* chore(deeplink): integrate deep link handling into app lifecycle
Wire up deep link infrastructure with app initialization and event handling.
Backend Integration:
- Register deep link module and commands in mod.rs
- Add URL handling in app setup (src-tauri/src/lib.rs:handle_deeplink_url)
- Handle deep links from single instance callback (Windows/Linux CLI)
- Handle deep links from macOS system events
- Add tauri-plugin-deep-link dependency (Cargo.toml)
Frontend Integration:
- Listen for deeplink-import/deeplink-error events in App.tsx
- Update DeepLinkImportDialog component imports
Configuration:
- Enable deep link plugin in tauri.conf.json
- Update Cargo.lock for new dependencies
Localization:
- Add Chinese translations for deep link UI (zh.json)
- Add English translations for deep link UI (en.json)
Files: 9 changed, 359 insertions(+), 18 deletions(-)
* refactor(deeplink): enhance Codex provider template generation
Align deep link import with UI preset generation logic by:
- Adding complete config.toml template matching frontend defaults
- Generating safe provider name from sanitized input
- Including model_provider, reasoning_effort, and wire_api settings
- Removing minimal template that only contained base_url
- Cleaning up deprecated test file deeplink-test.html
* style: fix clippy uninlined_format_args warnings
Apply clippy --fix to use inline format arguments in:
- src/mcp.rs (8 fixes)
- src/services/env_manager.rs (10 fixes)
* style: apply code formatting and cleanup
- Format TypeScript files with Prettier (App.tsx, EnvWarningBanner.tsx, formatters.ts)
- Organize Rust imports and module order alphabetically
- Add newline at end of JSON files (en.json, zh.json)
- Update Cargo.lock for dependency changes
* feat: add model name configuration support for Codex and fix Gemini model handling
- Add visual model name input field for Codex providers
- Add model name extraction and update utilities in providerConfigUtils
- Implement model name state management in useCodexConfigState hook
- Add conditional model field rendering in CodexFormFields (non-official only)
- Integrate model name sync with TOML config in ProviderForm
- Fix Gemini deeplink model injection bug
- Correct environment variable name from GOOGLE_GEMINI_MODEL to GEMINI_MODEL
- Add test cases for Gemini model injection (with/without model)
- All tests passing (9/9)
- Fix Gemini model field binding in edit mode
- Add geminiModel state to useGeminiConfigState hook
- Extract model value during initialization and reset
- Sync model field with geminiEnv state to prevent data loss on submit
- Fix missing model value display when editing Gemini providers
Changes:
- 6 files changed, 245 insertions(+), 13 deletions(-)
2025-11-19 09:03:18 +08:00
|
|
|
|
// 同时更新 form.settingsConfig 和 geminiEnv
|
2025-11-12 10:47:34 +08:00
|
|
|
|
const config = JSON.parse(form.watch("settingsConfig") || "{}");
|
|
|
|
|
|
if (!config.env) config.env = {};
|
|
|
|
|
|
config.env.GEMINI_MODEL = model;
|
|
|
|
|
|
form.setValue("settingsConfig", JSON.stringify(config, null, 2));
|
feat: add model configuration support and fix Gemini deeplink bug (#251)
* feat(providers): add notes field for provider management
- Add notes field to Provider model (backend and frontend)
- Display notes with higher priority than URL in provider card
- Style notes as non-clickable text to differentiate from URLs
- Add notes input field in provider form
- Add i18n support (zh/en) for notes field
* chore: format code and clean up unused props
- Run cargo fmt on Rust backend code
- Format TypeScript imports and code style
- Remove unused appId prop from ProviderPresetSelector
- Clean up unused variables in tests
- Integrate notes field handling in provider dialogs
* feat(deeplink): implement ccswitch:// protocol for provider import
Add deep link support to enable one-click provider configuration import via ccswitch:// URLs.
Backend:
- Implement URL parsing and validation (src-tauri/src/deeplink.rs)
- Add Tauri commands for parse and import (src-tauri/src/commands/deeplink.rs)
- Register ccswitch:// protocol in macOS Info.plist
- Add comprehensive unit tests (src-tauri/tests/deeplink_import.rs)
Frontend:
- Create confirmation dialog with security review UI (src/components/DeepLinkImportDialog.tsx)
- Add API wrapper (src/lib/api/deeplink.ts)
- Integrate event listeners in App.tsx
Configuration:
- Update Tauri config for deep link handling
- Add i18n support for Chinese and English
- Include test page for deep link validation (deeplink-test.html)
Files: 15 changed, 1312 insertions(+)
* chore(deeplink): integrate deep link handling into app lifecycle
Wire up deep link infrastructure with app initialization and event handling.
Backend Integration:
- Register deep link module and commands in mod.rs
- Add URL handling in app setup (src-tauri/src/lib.rs:handle_deeplink_url)
- Handle deep links from single instance callback (Windows/Linux CLI)
- Handle deep links from macOS system events
- Add tauri-plugin-deep-link dependency (Cargo.toml)
Frontend Integration:
- Listen for deeplink-import/deeplink-error events in App.tsx
- Update DeepLinkImportDialog component imports
Configuration:
- Enable deep link plugin in tauri.conf.json
- Update Cargo.lock for new dependencies
Localization:
- Add Chinese translations for deep link UI (zh.json)
- Add English translations for deep link UI (en.json)
Files: 9 changed, 359 insertions(+), 18 deletions(-)
* refactor(deeplink): enhance Codex provider template generation
Align deep link import with UI preset generation logic by:
- Adding complete config.toml template matching frontend defaults
- Generating safe provider name from sanitized input
- Including model_provider, reasoning_effort, and wire_api settings
- Removing minimal template that only contained base_url
- Cleaning up deprecated test file deeplink-test.html
* style: fix clippy uninlined_format_args warnings
Apply clippy --fix to use inline format arguments in:
- src/mcp.rs (8 fixes)
- src/services/env_manager.rs (10 fixes)
* style: apply code formatting and cleanup
- Format TypeScript files with Prettier (App.tsx, EnvWarningBanner.tsx, formatters.ts)
- Organize Rust imports and module order alphabetically
- Add newline at end of JSON files (en.json, zh.json)
- Update Cargo.lock for dependency changes
* feat: add model name configuration support for Codex and fix Gemini model handling
- Add visual model name input field for Codex providers
- Add model name extraction and update utilities in providerConfigUtils
- Implement model name state management in useCodexConfigState hook
- Add conditional model field rendering in CodexFormFields (non-official only)
- Integrate model name sync with TOML config in ProviderForm
- Fix Gemini deeplink model injection bug
- Correct environment variable name from GOOGLE_GEMINI_MODEL to GEMINI_MODEL
- Add test cases for Gemini model injection (with/without model)
- All tests passing (9/9)
- Fix Gemini model field binding in edit mode
- Add geminiModel state to useGeminiConfigState hook
- Extract model value during initialization and reset
- Sync model field with geminiEnv state to prevent data loss on submit
- Fix missing model value display when editing Gemini providers
Changes:
- 6 files changed, 245 insertions(+), 13 deletions(-)
2025-11-19 09:03:18 +08:00
|
|
|
|
|
|
|
|
|
|
// 同步更新 geminiEnv,确保提交时不丢失
|
|
|
|
|
|
const envObj = envStringToObj(geminiEnv);
|
|
|
|
|
|
envObj.GEMINI_MODEL = model.trim();
|
|
|
|
|
|
const newEnv = envObjToString(envObj);
|
|
|
|
|
|
handleGeminiEnvChange(newEnv);
|
2025-11-12 10:47:34 +08:00
|
|
|
|
}}
|
|
|
|
|
|
speedTestEndpoints={speedTestEndpoints}
|
|
|
|
|
|
/>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
|
|
{/* 配置编辑器:Codex、Claude、Gemini 分别使用不同的编辑器 */}
|
2025-10-30 14:59:15 +08:00
|
|
|
|
{appId === "codex" ? (
|
2025-11-09 20:42:25 +08:00
|
|
|
|
<>
|
|
|
|
|
|
<CodexConfigEditor
|
|
|
|
|
|
authValue={codexAuth}
|
|
|
|
|
|
configValue={codexConfig}
|
|
|
|
|
|
onAuthChange={setCodexAuth}
|
|
|
|
|
|
onConfigChange={handleCodexConfigChange}
|
|
|
|
|
|
useCommonConfig={useCodexCommonConfigFlag}
|
|
|
|
|
|
onCommonConfigToggle={handleCodexCommonConfigToggle}
|
|
|
|
|
|
commonConfigSnippet={codexCommonConfigSnippet}
|
|
|
|
|
|
onCommonConfigSnippetChange={handleCodexCommonConfigSnippetChange}
|
|
|
|
|
|
commonConfigError={codexCommonConfigError}
|
|
|
|
|
|
authError={codexAuthError}
|
|
|
|
|
|
configError={codexConfigError}
|
|
|
|
|
|
/>
|
|
|
|
|
|
{/* 配置验证错误显示 */}
|
|
|
|
|
|
<FormField
|
|
|
|
|
|
control={form.control}
|
|
|
|
|
|
name="settingsConfig"
|
|
|
|
|
|
render={() => (
|
|
|
|
|
|
<FormItem className="space-y-0">
|
|
|
|
|
|
<FormMessage />
|
|
|
|
|
|
</FormItem>
|
|
|
|
|
|
)}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</>
|
2025-11-12 10:47:34 +08:00
|
|
|
|
) : appId === "gemini" ? (
|
|
|
|
|
|
<>
|
|
|
|
|
|
<GeminiConfigEditor
|
2025-11-14 08:31:09 +08:00
|
|
|
|
envValue={geminiEnv}
|
|
|
|
|
|
configValue={geminiConfig}
|
|
|
|
|
|
onEnvChange={handleGeminiEnvChange}
|
|
|
|
|
|
onConfigChange={handleGeminiConfigChange}
|
|
|
|
|
|
useCommonConfig={useGeminiCommonConfigFlag}
|
|
|
|
|
|
onCommonConfigToggle={handleGeminiCommonConfigToggle}
|
|
|
|
|
|
commonConfigSnippet={geminiCommonConfigSnippet}
|
|
|
|
|
|
onCommonConfigSnippetChange={
|
|
|
|
|
|
handleGeminiCommonConfigSnippetChange
|
|
|
|
|
|
}
|
|
|
|
|
|
commonConfigError={geminiCommonConfigError}
|
|
|
|
|
|
envError={envError}
|
|
|
|
|
|
configError={geminiConfigError}
|
2025-11-12 10:47:34 +08:00
|
|
|
|
/>
|
|
|
|
|
|
{/* 配置验证错误显示 */}
|
|
|
|
|
|
<FormField
|
|
|
|
|
|
control={form.control}
|
|
|
|
|
|
name="settingsConfig"
|
|
|
|
|
|
render={() => (
|
|
|
|
|
|
<FormItem className="space-y-0">
|
|
|
|
|
|
<FormMessage />
|
|
|
|
|
|
</FormItem>
|
|
|
|
|
|
)}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</>
|
2025-10-16 18:50:44 +08:00
|
|
|
|
) : (
|
2025-11-09 20:42:25 +08:00
|
|
|
|
<>
|
|
|
|
|
|
<CommonConfigEditor
|
|
|
|
|
|
value={form.watch("settingsConfig")}
|
|
|
|
|
|
onChange={(value) => form.setValue("settingsConfig", value)}
|
|
|
|
|
|
useCommonConfig={useCommonConfig}
|
|
|
|
|
|
onCommonConfigToggle={handleCommonConfigToggle}
|
|
|
|
|
|
commonConfigSnippet={commonConfigSnippet}
|
|
|
|
|
|
onCommonConfigSnippetChange={handleCommonConfigSnippetChange}
|
|
|
|
|
|
commonConfigError={commonConfigError}
|
|
|
|
|
|
onEditClick={() => setIsCommonConfigModalOpen(true)}
|
|
|
|
|
|
isModalOpen={isCommonConfigModalOpen}
|
|
|
|
|
|
onModalClose={() => setIsCommonConfigModalOpen(false)}
|
|
|
|
|
|
/>
|
|
|
|
|
|
{/* 配置验证错误显示 */}
|
|
|
|
|
|
<FormField
|
|
|
|
|
|
control={form.control}
|
|
|
|
|
|
name="settingsConfig"
|
|
|
|
|
|
render={() => (
|
|
|
|
|
|
<FormItem className="space-y-0">
|
|
|
|
|
|
<FormMessage />
|
|
|
|
|
|
</FormItem>
|
|
|
|
|
|
)}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</>
|
2025-10-16 18:50:44 +08:00
|
|
|
|
)}
|
2025-10-16 10:49:56 +08:00
|
|
|
|
|
2025-10-18 23:28:33 +08:00
|
|
|
|
{showButtons && (
|
|
|
|
|
|
<div className="flex justify-end gap-2">
|
|
|
|
|
|
<Button variant="outline" type="button" onClick={onCancel}>
|
2025-10-19 11:55:46 +08:00
|
|
|
|
{t("common.cancel")}
|
2025-10-18 23:28:33 +08:00
|
|
|
|
</Button>
|
|
|
|
|
|
<Button type="submit">{submitLabel}</Button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
2025-10-16 10:49:56 +08:00
|
|
|
|
</form>
|
|
|
|
|
|
</Form>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-16 13:02:38 +08:00
|
|
|
|
export type ProviderFormValues = ProviderFormData & {
|
|
|
|
|
|
presetId?: string;
|
|
|
|
|
|
presetCategory?: ProviderCategory;
|
2025-11-06 15:22:38 +08:00
|
|
|
|
isPartner?: boolean;
|
2025-10-28 20:28:11 +08:00
|
|
|
|
meta?: ProviderMeta;
|
2025-10-16 13:02:38 +08:00
|
|
|
|
};
|