* 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(-)
152 lines
5.1 KiB
Rust
152 lines
5.1 KiB
Rust
use serde::{Deserialize, Serialize};
|
||
use serde_json::Value;
|
||
use std::collections::HashMap;
|
||
|
||
// SSOT 模式:不再写供应商副本文件
|
||
|
||
/// 供应商结构体
|
||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||
pub struct Provider {
|
||
pub id: String,
|
||
pub name: String,
|
||
#[serde(rename = "settingsConfig")]
|
||
pub settings_config: Value,
|
||
#[serde(skip_serializing_if = "Option::is_none")]
|
||
#[serde(rename = "websiteUrl")]
|
||
pub website_url: Option<String>,
|
||
#[serde(skip_serializing_if = "Option::is_none")]
|
||
pub category: Option<String>,
|
||
#[serde(skip_serializing_if = "Option::is_none")]
|
||
#[serde(rename = "createdAt")]
|
||
pub created_at: Option<i64>,
|
||
#[serde(skip_serializing_if = "Option::is_none")]
|
||
#[serde(rename = "sortIndex")]
|
||
pub sort_index: Option<usize>,
|
||
/// 备注信息
|
||
#[serde(skip_serializing_if = "Option::is_none")]
|
||
pub notes: Option<String>,
|
||
/// 供应商元数据(不写入 live 配置,仅存于 ~/.cc-switch/config.json)
|
||
#[serde(skip_serializing_if = "Option::is_none")]
|
||
pub meta: Option<ProviderMeta>,
|
||
}
|
||
|
||
impl Provider {
|
||
/// 从现有ID创建供应商
|
||
pub fn with_id(
|
||
id: String,
|
||
name: String,
|
||
settings_config: Value,
|
||
website_url: Option<String>,
|
||
) -> Self {
|
||
Self {
|
||
id,
|
||
name,
|
||
settings_config,
|
||
website_url,
|
||
category: None,
|
||
created_at: None,
|
||
sort_index: None,
|
||
notes: None,
|
||
meta: None,
|
||
}
|
||
}
|
||
}
|
||
|
||
/// 供应商管理器
|
||
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||
pub struct ProviderManager {
|
||
pub providers: HashMap<String, Provider>,
|
||
pub current: String,
|
||
}
|
||
|
||
/// 用量查询脚本配置
|
||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||
pub struct UsageScript {
|
||
pub enabled: bool,
|
||
pub language: String,
|
||
pub code: String,
|
||
#[serde(skip_serializing_if = "Option::is_none")]
|
||
pub timeout: Option<u64>,
|
||
/// 用量查询专用的 API Key(通用模板使用)
|
||
#[serde(skip_serializing_if = "Option::is_none")]
|
||
#[serde(rename = "apiKey")]
|
||
pub api_key: Option<String>,
|
||
/// 用量查询专用的 Base URL(通用和 NewAPI 模板使用)
|
||
#[serde(skip_serializing_if = "Option::is_none")]
|
||
#[serde(rename = "baseUrl")]
|
||
pub base_url: Option<String>,
|
||
/// 访问令牌(用于需要登录的接口,NewAPI 模板使用)
|
||
#[serde(skip_serializing_if = "Option::is_none")]
|
||
#[serde(rename = "accessToken")]
|
||
pub access_token: Option<String>,
|
||
/// 用户ID(用于需要用户标识的接口,NewAPI 模板使用)
|
||
#[serde(skip_serializing_if = "Option::is_none")]
|
||
#[serde(rename = "userId")]
|
||
pub user_id: Option<String>,
|
||
/// 自动查询间隔(单位:分钟,0 表示禁用自动查询)
|
||
#[serde(skip_serializing_if = "Option::is_none")]
|
||
#[serde(rename = "autoQueryInterval")]
|
||
pub auto_query_interval: Option<u64>,
|
||
}
|
||
|
||
/// 用量数据
|
||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||
pub struct UsageData {
|
||
#[serde(skip_serializing_if = "Option::is_none")]
|
||
#[serde(rename = "planName")]
|
||
pub plan_name: Option<String>,
|
||
#[serde(skip_serializing_if = "Option::is_none")]
|
||
pub extra: Option<String>,
|
||
#[serde(skip_serializing_if = "Option::is_none")]
|
||
#[serde(rename = "isValid")]
|
||
pub is_valid: Option<bool>,
|
||
#[serde(skip_serializing_if = "Option::is_none")]
|
||
#[serde(rename = "invalidMessage")]
|
||
pub invalid_message: Option<String>,
|
||
#[serde(skip_serializing_if = "Option::is_none")]
|
||
pub total: Option<f64>,
|
||
#[serde(skip_serializing_if = "Option::is_none")]
|
||
pub used: Option<f64>,
|
||
#[serde(skip_serializing_if = "Option::is_none")]
|
||
pub remaining: Option<f64>,
|
||
#[serde(skip_serializing_if = "Option::is_none")]
|
||
pub unit: Option<String>,
|
||
}
|
||
|
||
/// 用量查询结果(支持多套餐)
|
||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||
pub struct UsageResult {
|
||
pub success: bool,
|
||
#[serde(skip_serializing_if = "Option::is_none")]
|
||
pub data: Option<Vec<UsageData>>, // 支持返回多个套餐
|
||
#[serde(skip_serializing_if = "Option::is_none")]
|
||
pub error: Option<String>,
|
||
}
|
||
|
||
/// 供应商元数据
|
||
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||
pub struct ProviderMeta {
|
||
/// 自定义端点列表(按 URL 去重存储)
|
||
#[serde(default, skip_serializing_if = "HashMap::is_empty")]
|
||
pub custom_endpoints: HashMap<String, crate::settings::CustomEndpoint>,
|
||
/// 用量查询脚本配置
|
||
#[serde(skip_serializing_if = "Option::is_none")]
|
||
pub usage_script: Option<UsageScript>,
|
||
/// 合作伙伴标记(前端使用 isPartner,保持字段名一致)
|
||
#[serde(rename = "isPartner", skip_serializing_if = "Option::is_none")]
|
||
pub is_partner: Option<bool>,
|
||
/// 合作伙伴促销 key,用于识别 PackyCode 等特殊供应商
|
||
#[serde(
|
||
rename = "partnerPromotionKey",
|
||
skip_serializing_if = "Option::is_none"
|
||
)]
|
||
pub partner_promotion_key: Option<String>,
|
||
}
|
||
|
||
impl ProviderManager {
|
||
/// 获取所有供应商
|
||
pub fn get_all_providers(&self) -> &HashMap<String, Provider> {
|
||
&self.providers
|
||
}
|
||
}
|