diff --git a/deplink.html b/deplink.html index 29e995bfa..a02d8d66c 100644 --- a/deplink.html +++ b/deplink.html @@ -328,361 +328,6 @@
- -
-

Claude Code 供应商

- - - - - - - - - - -
- - -
-

Codex 供应商

- - - - - - - - -
- - -
-

Gemini 供应商

- - - - - - - - -
@@ -848,303 +493,12 @@
- - - - -
-

✨ 配置文件导入新特性 (v3.8+)

- -
- - - -
-

🏢 供应商导入 v3.8+

- - - - - -

🔌 MCP Servers 导入 v3.8+

- - - - - -
- - - - @@ -1336,77 +636,6 @@

填写参数信息,自动生成深链接并导入到 CC Switch

- - -
-

🏢 供应商导入生成器

- -
- - -
- -
- - -
- -
- - - 您的 API 密钥 -
- -
- - - 完整的 API 端点 URL -
- -
- - -
- -
- - - 可选,留空使用系统默认 -
- -
- - -
- -
- - -
- - - - -
-

🔌 MCP Servers 导入生成器

@@ -1702,6 +931,17 @@
+ + 主 API 端点地址 + +
+ +
+ + + + 多个备用端点用逗号分隔,导入后自动添加为自定义端点 +
@@ -2087,7 +1327,18 @@ requires_openai_auth = true`; // 添加参数 if (homepage) params.append('homepage', homepage); - params.append('endpoint', endpoint); + + // 合并主端点和备用端点 + const extraEndpoints = document.getElementById('extraEndpoints').value.trim(); + let fullEndpoint = endpoint; + if (extraEndpoints) { + const extras = extraEndpoints.split(',').map(e => e.trim()).filter(e => e); + if (extras.length > 0) { + fullEndpoint = endpoint + ',' + extras.join(','); + } + } + params.append('endpoint', fullEndpoint); + params.append('apiKey', apiKey); if (model) params.append('model', model); if (icon) params.append('icon', icon); @@ -2318,8 +1569,8 @@ requires_openai_auth = true`;
`; - // 常规参数 - const regularParams = ['homepage', 'endpoint', 'apiKey', 'model', 'notes']; + // 常规参数(排除 endpoint,单独处理) + const regularParams = ['homepage', 'apiKey', 'model', 'notes']; regularParams.forEach(key => { if (paramsObj[key]) { let displayValue = paramsObj[key]; @@ -2334,6 +1585,38 @@ requires_openai_auth = true`; } }); + // 单独处理 endpoint(支持多端点) + if (paramsObj.endpoint) { + const endpoints = paramsObj.endpoint.split(',').map(e => e.trim()).filter(e => e); + if (endpoints.length === 1) { + // 单个端点 + urlParamsHtml += ` +
endpoint:
+
${endpoints[0]}
+ `; + } else { + // 多个端点 + urlParamsHtml += ` +
endpoints:
+
+ `; + endpoints.forEach((ep, idx) => { + const label = idx === 0 ? '🔹 主端点' : `└ 备用 ${idx}`; + urlParamsHtml += ` +
+ ${label}: ${ep} +
+ `; + }); + urlParamsHtml += ` +
+ 💡 共 ${endpoints.length} 个端点,第一个为主端点,其余为备用端点 +
+
+ `; + } + } + // Claude 专用模型参数 const claudeModelParams = ['haikuModel', 'sonnetModel', 'opusModel']; claudeModelParams.forEach(key => { @@ -2892,4 +2175,4 @@ requires_openai_auth = true`; - \ No newline at end of file + diff --git a/src-tauri/src/deeplink/provider.rs b/src-tauri/src/deeplink/provider.rs index 7dab0e292..a08f1b22a 100644 --- a/src-tauri/src/deeplink/provider.rs +++ b/src-tauri/src/deeplink/provider.rs @@ -33,12 +33,12 @@ pub fn import_provider_from_deeplink( } // Step 1: Merge config file if provided (v3.8+) - let merged_request = parse_and_merge_config(&request)?; + let mut merged_request = parse_and_merge_config(&request)?; // Extract required fields (now as Option) let app_str = merged_request .app - .as_ref() + .clone() .ok_or_else(|| AppError::InvalidInput("Missing 'app' field for provider".to_string()))?; let api_key = merged_request.api_key.as_ref().ok_or_else(|| { @@ -63,10 +63,19 @@ pub fn import_provider_from_deeplink( .filter(|e| !e.is_empty()) .collect(); - let _endpoint = all_endpoints + let primary_endpoint = all_endpoints .first() .ok_or_else(|| AppError::InvalidInput("Endpoint cannot be empty".to_string()))?; + // Auto-infer homepage from endpoint if not provided + if merged_request + .homepage + .as_ref() + .is_none_or(|s| s.is_empty()) + { + merged_request.homepage = infer_homepage_from_endpoint(primary_endpoint); + } + let homepage = merged_request.homepage.as_ref().ok_or_else(|| { AppError::InvalidInput("Homepage is required (either in URL or config file)".to_string()) })?; @@ -79,11 +88,11 @@ pub fn import_provider_from_deeplink( let name = merged_request .name - .as_ref() + .clone() .ok_or_else(|| AppError::InvalidInput("Missing 'name' field for provider".to_string()))?; // Parse app type - let app_type = AppType::from_str(app_str) + let app_type = AppType::from_str(&app_str) .map_err(|_| AppError::InvalidInput(format!("Invalid app type: {app_str}")))?; // Build provider configuration based on app type diff --git a/src-tauri/src/deeplink/tests.rs b/src-tauri/src/deeplink/tests.rs index f3e8839e7..a56187f84 100644 --- a/src-tauri/src/deeplink/tests.rs +++ b/src-tauri/src/deeplink/tests.rs @@ -445,3 +445,16 @@ fn test_parse_endpoints_with_spaces_trimmed() { // Validation should pass (spaces are trimmed during validation) assert!(request.endpoint.is_some()); } + +#[test] +fn test_infer_homepage_from_endpoint_without_homepage() { + // Test that homepage is auto-inferred from endpoint when not provided + assert_eq!( + infer_homepage_from_endpoint("https://api.cubence.com/v1"), + Some("https://cubence.com".to_string()) + ); + assert_eq!( + infer_homepage_from_endpoint("https://cubence.com"), + Some("https://cubence.com".to_string()) + ); +} diff --git a/src-tauri/src/services/provider/live.rs b/src-tauri/src/services/provider/live.rs index 0192f6ecd..9854f2243 100644 --- a/src-tauri/src/services/provider/live.rs +++ b/src-tauri/src/services/provider/live.rs @@ -152,7 +152,7 @@ pub fn sync_current_to_live(state: &AppState) -> Result<(), AppError> { // Skill sync for app_type in [AppType::Claude, AppType::Codex, AppType::Gemini] { if let Err(e) = crate::services::skill::SkillService::sync_to_app(&state.db, &app_type) { - log::warn!("同步 Skill 到 {:?} 失败: {}", app_type, e); + log::warn!("同步 Skill 到 {app_type:?} 失败: {e}"); // Continue syncing other apps, don't abort } } diff --git a/src/components/DeepLinkImportDialog.tsx b/src/components/DeepLinkImportDialog.tsx index 86e6b0e6f..761df8fc1 100644 --- a/src/components/DeepLinkImportDialog.tsx +++ b/src/components/DeepLinkImportDialog.tsx @@ -389,12 +389,27 @@ export function DeepLinkImportDialog() {
{/* API Endpoint */} -
-
+
+
{t("deeplink.endpoint")}
-
- {request.endpoint} +
+ {request.endpoint?.split(",").map((ep, idx) => ( +
+ {idx === 0 ? "🔹 " : "└ "} + {ep.trim()} + {idx === 0 && request.endpoint?.includes(",") && ( + + (主) + + )} +
+ ))}