diff --git a/src/App.tsx b/src/App.tsx index cdf172e..75b9263 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -281,7 +281,7 @@ function App() { return setCurrentView("providers")} />; default: return ( -
+
@@ -356,13 +356,12 @@ function App() { {currentView !== "providers" ? (

{currentView === "settings" && t("settings.title")} diff --git a/src/components/DeepLinkImportDialog.tsx b/src/components/DeepLinkImportDialog.tsx index 49f9f65..ea8b98f 100644 --- a/src/components/DeepLinkImportDialog.tsx +++ b/src/components/DeepLinkImportDialog.tsx @@ -1,4 +1,4 @@ -import { useState, useEffect } from "react"; +import { useState, useEffect, useMemo } from "react"; import { listen } from "@tauri-apps/api/event"; import { DeepLinkImportRequest, deeplinkApi } from "@/lib/api/deeplink"; import { @@ -71,7 +71,6 @@ export function DeepLinkImportDialog() { }); setIsOpen(false); - setRequest(null); } catch (error) { console.error("Failed to import provider from deep link:", error); toast.error(t("deeplink.importError"), { @@ -84,120 +83,314 @@ export function DeepLinkImportDialog() { const handleCancel = () => { setIsOpen(false); - setRequest(null); }; - if (!request) return null; - // Mask API key for display (show first 4 chars + ***) const maskedApiKey = - request.apiKey.length > 4 + request?.apiKey && request.apiKey.length > 4 ? `${request.apiKey.substring(0, 4)}${"*".repeat(20)}` : "****"; + // Check if config file is present + const hasConfigFile = !!(request?.config || request?.configUrl); + const configSource = request?.config + ? "base64" + : request?.configUrl + ? "url" + : null; + + // Parse config file content for display + interface ParsedConfig { + type: "claude" | "codex" | "gemini"; + env?: Record; + auth?: Record; + tomlConfig?: string; + raw: Record; + } + + const parsedConfig = useMemo((): ParsedConfig | null => { + if (!request?.config) return null; + try { + const decoded = atob(request.config); + const parsed = JSON.parse(decoded) as Record; + + if (request.app === "claude") { + // Claude 格式: { env: { ANTHROPIC_AUTH_TOKEN: ..., ... } } + return { + type: "claude", + env: (parsed.env as Record) || {}, + raw: parsed, + }; + } else if (request.app === "codex") { + // Codex 格式: { auth: { OPENAI_API_KEY: ... }, config: "TOML string" } + return { + type: "codex", + auth: (parsed.auth as Record) || {}, + tomlConfig: (parsed.config as string) || "", + raw: parsed, + }; + } else if (request.app === "gemini") { + // Gemini 格式: 扁平结构 { GEMINI_API_KEY: ..., GEMINI_BASE_URL: ... } + return { + type: "gemini", + env: parsed as Record, + raw: parsed, + }; + } + return null; + } catch (e) { + console.error("Failed to parse config:", e); + return null; + } + }, [request?.config, request?.app]); + + // Helper to mask sensitive values + const maskValue = (key: string, value: string): string => { + const sensitiveKeys = ["TOKEN", "KEY", "SECRET", "PASSWORD"]; + const isSensitive = sensitiveKeys.some((k) => + key.toUpperCase().includes(k), + ); + if (isSensitive && value.length > 8) { + return `${value.substring(0, 8)}${"*".repeat(12)}`; + } + return value; + }; + return ( - - - {/* 标题显式左对齐,避免默认居中样式影响 */} - - {t("deeplink.confirmImport")} - - {t("deeplink.confirmImportDescription")} - - + + + {request && ( + <> + {/* 标题显式左对齐,避免默认居中样式影响 */} + + {t("deeplink.confirmImport")} + + {t("deeplink.confirmImportDescription")} + + - {/* 主体内容整体右移,略大于标题内边距,让内容看起来不贴边 */} -
- {/* App Type */} -
-
- {t("deeplink.app")} -
-
- {request.app} -
-
- - {/* Provider Name */} -
-
- {t("deeplink.providerName")} -
-
{request.name}
-
- - {/* Homepage */} -
-
- {t("deeplink.homepage")} -
-
- {request.homepage} -
-
- - {/* API Endpoint */} -
-
- {t("deeplink.endpoint")} -
-
- {request.endpoint} -
-
- - {/* API Key (masked) */} -
-
- {t("deeplink.apiKey")} -
-
- {maskedApiKey} -
-
- - {/* Model (if present) */} - {request.model && ( -
-
- {t("deeplink.model")} + {/* 主体内容整体右移,略大于标题内边距,让内容看起来不贴边 */} +
+ {/* App Type */} +
+
+ {t("deeplink.app")} +
+
+ {request.app} +
-
- {request.model} + + {/* Provider Name */} +
+
+ {t("deeplink.providerName")} +
+
+ {request.name} +
+
+ + {/* Homepage */} +
+
+ {t("deeplink.homepage")} +
+
+ {request.homepage} +
+
+ + {/* API Endpoint */} +
+
+ {t("deeplink.endpoint")} +
+
+ {request.endpoint} +
+
+ + {/* API Key (masked) */} +
+
+ {t("deeplink.apiKey")} +
+
+ {maskedApiKey} +
+
+ + {/* Model (if present) */} + {request.model && ( +
+
+ {t("deeplink.model")} +
+
+ {request.model} +
+
+ )} + + {/* Notes (if present) */} + {request.notes && ( +
+
+ {t("deeplink.notes")} +
+
+ {request.notes} +
+
+ )} + + {/* Config File Details (v3.8+) */} + {hasConfigFile && ( +
+
+
+ {t("deeplink.configSource")} +
+
+ + {configSource === "base64" + ? t("deeplink.configEmbedded") + : t("deeplink.configRemote")} + + {request.configFormat && ( + + {request.configFormat} + + )} +
+
+ + {/* Parsed Config Details */} + {parsedConfig && ( +
+
+ {t("deeplink.configDetails")} +
+ + {/* Claude config */} + {parsedConfig.type === "claude" && parsedConfig.env && ( +
+ {Object.entries(parsedConfig.env).map( + ([key, value]) => ( +
+ + {key} + + + {maskValue(key, String(value))} + +
+ ), + )} +
+ )} + + {/* Codex config */} + {parsedConfig.type === "codex" && ( +
+ {parsedConfig.auth && + Object.keys(parsedConfig.auth).length > 0 && ( +
+
+ Auth: +
+ {Object.entries(parsedConfig.auth).map( + ([key, value]) => ( +
+ + {key} + + + {maskValue(key, String(value))} + +
+ ), + )} +
+ )} + {parsedConfig.tomlConfig && ( +
+
+ TOML Config: +
+
+                                {parsedConfig.tomlConfig.substring(0, 300)}
+                                {parsedConfig.tomlConfig.length > 300 && "..."}
+                              
+
+ )} +
+ )} + + {/* Gemini config */} + {parsedConfig.type === "gemini" && parsedConfig.env && ( +
+ {Object.entries(parsedConfig.env).map( + ([key, value]) => ( +
+ + {key} + + + {maskValue(key, String(value))} + +
+ ), + )} +
+ )} +
+ )} + + {/* Config URL (if remote) */} + {request.configUrl && ( +
+
+ {t("deeplink.configUrl")} +
+
+ {request.configUrl} +
+
+ )} +
+ )} + + {/* Warning */} +
+ {t("deeplink.warning")}
- )} - {/* Notes (if present) */} - {request.notes && ( -
-
- {t("deeplink.notes")} -
-
- {request.notes} -
-
- )} - - {/* Warning */} -
- {t("deeplink.warning")} -
-
- - - - - + + + + + + )}
); diff --git a/src/lib/api/deeplink.ts b/src/lib/api/deeplink.ts index 7acb412..759ce67 100644 --- a/src/lib/api/deeplink.ts +++ b/src/lib/api/deeplink.ts @@ -14,6 +14,10 @@ export interface DeepLinkImportRequest { haikuModel?: string; sonnetModel?: string; opusModel?: string; + // 配置文件导入字段 (v3.8+) + config?: string; // Base64 编码的配置内容 + configFormat?: string; // json/toml + configUrl?: string; // 远程配置 URL } export const deeplinkApi = {