diff --git a/src-tauri/src/commands/provider.rs b/src-tauri/src/commands/provider.rs index da9a7ea96..329f1f5bd 100644 --- a/src-tauri/src/commands/provider.rs +++ b/src-tauri/src/commands/provider.rs @@ -93,7 +93,29 @@ pub fn switch_provider( } fn import_default_config_internal(state: &AppState, app_type: AppType) -> Result { - ProviderService::import_default_config(state, app_type) + let imported = ProviderService::import_default_config(state, app_type.clone())?; + + if imported { + // Extract common config snippet (mirrors old startup logic in lib.rs) + if state + .db + .get_config_snippet(app_type.as_str()) + .ok() + .flatten() + .is_none() + { + match ProviderService::extract_common_config_snippet(state, app_type.clone()) { + Ok(snippet) if !snippet.is_empty() && snippet != "{}" => { + let _ = state + .db + .set_config_snippet(app_type.as_str(), Some(snippet)); + } + _ => {} + } + } + } + + Ok(imported) } #[cfg_attr(not(feature = "test-hooks"), doc(hidden))] diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 31f168c41..d159aae13 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -448,52 +448,7 @@ pub fn run() { Err(e) => log::warn!("✗ Failed to read skills migration flag: {e}"), } - // 2. 导入供应商配置(已有内置检查:该应用已有供应商则跳过) - for app in [ - crate::app_config::AppType::Claude, - crate::app_config::AppType::Codex, - crate::app_config::AppType::Gemini, - ] { - match crate::services::provider::ProviderService::import_default_config( - &app_state, - app.clone(), - ) { - Ok(true) => { - log::info!("✓ Imported default provider for {}", app.as_str()); - - // 首次运行:自动提取通用配置片段(仅当通用配置为空时) - if app_state - .db - .get_config_snippet(app.as_str()) - .ok() - .flatten() - .is_none() - { - match crate::services::provider::ProviderService::extract_common_config_snippet(&app_state, app.clone()) { - Ok(snippet) if !snippet.is_empty() && snippet != "{}" => { - if let Err(e) = app_state.db.set_config_snippet(app.as_str(), Some(snippet)) { - log::warn!("✗ Failed to save common config snippet for {}: {e}", app.as_str()); - } else { - log::info!("✓ Extracted common config snippet for {}", app.as_str()); - } - } - Ok(_) => log::debug!("○ No common config to extract for {}", app.as_str()), - Err(e) => log::debug!("○ Failed to extract common config for {}: {e}", app.as_str()), - } - } - } - Ok(false) => {} // 已有供应商,静默跳过 - Err(e) => { - log::debug!( - "○ No default provider to import for {}: {}", - app.as_str(), - e - ); - } - } - } - - // 2.1 OpenCode 供应商导入(累加式模式,需特殊处理) + // 2. OpenCode 供应商导入(累加式模式,需特殊处理) // OpenCode 与其他应用不同:配置文件中可同时存在多个供应商 // 需要遍历 provider 字段下的每个供应商并导入 match crate::services::provider::import_opencode_providers_from_live(&app_state) { diff --git a/src/components/providers/ProviderEmptyState.tsx b/src/components/providers/ProviderEmptyState.tsx index a79de0b1d..8c8240747 100644 --- a/src/components/providers/ProviderEmptyState.tsx +++ b/src/components/providers/ProviderEmptyState.tsx @@ -1,12 +1,16 @@ -import { Users } from "lucide-react"; +import { Download, Users } from "lucide-react"; import { useTranslation } from "react-i18next"; import { Button } from "@/components/ui/button"; interface ProviderEmptyStateProps { onCreate?: () => void; + onImport?: () => void; } -export function ProviderEmptyState({ onCreate }: ProviderEmptyStateProps) { +export function ProviderEmptyState({ + onCreate, + onImport, +}: ProviderEmptyStateProps) { const { t } = useTranslation(); return ( @@ -18,11 +22,19 @@ export function ProviderEmptyState({ onCreate }: ProviderEmptyStateProps) {

{t("provider.noProvidersDescription")}

- {onCreate && ( - - )} +
+ {onImport && ( + + )} + {onCreate && ( + + )} +
); } diff --git a/src/components/providers/ProviderList.tsx b/src/components/providers/ProviderList.tsx index a44c27428..928bdbcf4 100644 --- a/src/components/providers/ProviderList.tsx +++ b/src/components/providers/ProviderList.tsx @@ -15,7 +15,8 @@ import { import { AnimatePresence, motion } from "framer-motion"; import { Search, X } from "lucide-react"; import { useTranslation } from "react-i18next"; -import { useQuery } from "@tanstack/react-query"; +import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; +import { toast } from "sonner"; import type { Provider } from "@/types"; import type { AppId } from "@/lib/api"; import { providersApi } from "@/lib/api/providers"; @@ -179,6 +180,23 @@ export function ProviderList({ const [isSearchOpen, setIsSearchOpen] = useState(false); const searchInputRef = useRef(null); + // Import current live config as default provider + const queryClient = useQueryClient(); + const importMutation = useMutation({ + mutationFn: () => providersApi.importDefault(appId), + onSuccess: (imported) => { + if (imported) { + queryClient.invalidateQueries({ queryKey: ["providers", appId] }); + toast.success(t("provider.importCurrentDescription")); + } else { + toast.info(t("provider.noProviders")); + } + }, + onError: (error: Error) => { + toast.error(error.message); + }, + }); + useEffect(() => { const handleKeyDown = (event: KeyboardEvent) => { const key = event.key.toLowerCase(); @@ -231,8 +249,17 @@ export function ProviderList({ ); } + // Only show import button for standard apps (not additive-mode apps like OpenCode/OpenClaw) + const showImportButton = + appId === "claude" || appId === "codex" || appId === "gemini"; + if (sortedProviders.length === 0) { - return ; + return ( + importMutation.mutate() : undefined} + /> + ); } const renderProviderList = () => ( diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json index 9e5a393fc..99c7ae6ef 100644 --- a/src/i18n/locales/en.json +++ b/src/i18n/locales/en.json @@ -77,7 +77,9 @@ "tabProvider": "Provider", "tabUniversal": "Universal", "noProviders": "No providers added yet", - "noProvidersDescription": "Click the \"Add Provider\" button in the top right to configure your first API provider", + "noProvidersDescription": "Import your current live config below, or manually add a new provider", + "importCurrent": "Import Current Config", + "importCurrentDescription": "Import current live config as default provider", "currentlyUsing": "Currently Using", "enable": "Enable", "inUse": "In Use", diff --git a/src/i18n/locales/ja.json b/src/i18n/locales/ja.json index 4d1bedcb7..085bb0441 100644 --- a/src/i18n/locales/ja.json +++ b/src/i18n/locales/ja.json @@ -77,7 +77,9 @@ "tabProvider": "プロバイダー", "tabUniversal": "統一プロバイダー", "noProviders": "まだプロバイダーがありません", - "noProvidersDescription": "右上の「プロバイダーを追加」を押して最初の API プロバイダーを登録してください", + "noProvidersDescription": "下の「現在の設定をインポート」ボタンで既存の設定を取り込むか、新しいプロバイダーを手動で追加してください", + "importCurrent": "現在の設定をインポート", + "importCurrentDescription": "現在使用中の設定をデフォルトプロバイダーとしてインポート", "currentlyUsing": "現在使用中", "enable": "有効化", "inUse": "使用中", diff --git a/src/i18n/locales/zh.json b/src/i18n/locales/zh.json index df93dcb85..cd106dfe2 100644 --- a/src/i18n/locales/zh.json +++ b/src/i18n/locales/zh.json @@ -77,7 +77,9 @@ "tabProvider": "供应商", "tabUniversal": "统一供应商", "noProviders": "还没有添加任何供应商", - "noProvidersDescription": "点击右上角的\"添加供应商\"按钮开始配置您的第一个API供应商", + "noProvidersDescription": "点击下方的\"导入当前配置\"按钮导入已有配置,或手动添加新的供应商", + "importCurrent": "导入当前配置", + "importCurrentDescription": "将当前正在使用的配置导入为默认供应商", "currentlyUsing": "当前使用", "enable": "启用", "inUse": "使用中",