From ad6f5b388bd543e19bd5edac5184bc5d78ed935f Mon Sep 17 00:00:00 2001 From: Jason Date: Sat, 17 Jan 2026 17:51:32 +0800 Subject: [PATCH] fix(opencode): fix add/remove provider flow and toast messages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Create separate removeFromLiveConfig API for additive mode apps (remove only removes from live config, not database) - Fix useSwitchProviderMutation to invalidate opencodeLiveProviderIds cache so button state updates correctly after add operation - Show appropriate toast messages: - Add: "已添加到配置" / "Added to config" - Remove: "已从配置移除" / "Removed from config" - Add i18n texts for addToConfigSuccess and removeFromConfigSuccess --- src-tauri/src/commands/provider.rs | 10 ++++++++++ src-tauri/src/lib.rs | 1 + src-tauri/src/services/provider/mod.rs | 21 ++++++++++++++++++++ src/App.tsx | 16 ++++++++++++--- src/i18n/locales/en.json | 2 ++ src/i18n/locales/ja.json | 2 ++ src/i18n/locales/zh.json | 2 ++ src/lib/api/providers.ts | 8 ++++++++ src/lib/query/mutations.ts | 27 +++++++++++++++++--------- 9 files changed, 77 insertions(+), 12 deletions(-) diff --git a/src-tauri/src/commands/provider.rs b/src-tauri/src/commands/provider.rs index 30b8cfd8..b1b94a74 100644 --- a/src-tauri/src/commands/provider.rs +++ b/src-tauri/src/commands/provider.rs @@ -60,6 +60,16 @@ pub fn delete_provider( .map_err(|e| e.to_string()) } +/// Remove provider from live config only (for additive mode apps like OpenCode) +/// Does NOT delete from database - provider remains in the list +#[tauri::command] +pub fn remove_provider_from_live_config(app: String, id: String) -> Result { + let app_type = AppType::from_str(&app).map_err(|e| e.to_string())?; + ProviderService::remove_from_live_config(app_type, &id) + .map(|_| true) + .map_err(|e| e.to_string()) +} + /// 切换供应商 fn switch_provider_internal(state: &AppState, app_type: AppType, id: &str) -> Result<(), AppError> { ProviderService::switch(state, app_type, id) diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 43bdbe00..69d2a22c 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -713,6 +713,7 @@ pub fn run() { commands::add_provider, commands::update_provider, commands::delete_provider, + commands::remove_provider_from_live_config, commands::switch_provider, commands::import_default_config, commands::get_claude_config_status, diff --git a/src-tauri/src/services/provider/mod.rs b/src-tauri/src/services/provider/mod.rs index 9e6ecedf..8080d84b 100644 --- a/src-tauri/src/services/provider/mod.rs +++ b/src-tauri/src/services/provider/mod.rs @@ -261,6 +261,27 @@ impl ProviderService { state.db.delete_provider(app_type.as_str(), id) } + /// Remove provider from live config only (for additive mode apps like OpenCode) + /// + /// Does NOT delete from database - provider remains in the list. + /// This is used when user wants to "remove" a provider from active config + /// but keep it available for future use. + pub fn remove_from_live_config(app_type: AppType, id: &str) -> Result<(), AppError> { + match app_type { + AppType::OpenCode => { + remove_opencode_provider_from_live(id)?; + } + // Future: add other additive mode apps here + _ => { + return Err(AppError::Message(format!( + "App {} does not support remove from live config", + app_type.as_str() + ))); + } + } + Ok(()) + } + /// Switch to a provider /// /// Switch flow: diff --git a/src/App.tsx b/src/App.tsx index ad2f9d12..8d6d9180 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -332,9 +332,19 @@ function App() { const { provider, action } = confirmAction; if (action === "remove") { - // Remove from live config only (OpenCode) - // The switch operation with empty/removal semantics - await deleteProvider(provider.id); + // Remove from live config only (for additive mode apps like OpenCode) + // Does NOT delete from database - provider remains in the list + await providersApi.removeFromLiveConfig(provider.id, activeApp); + // Invalidate queries to refresh the isInConfig state + await queryClient.invalidateQueries({ + queryKey: ["opencodeLiveProviderIds"], + }); + toast.success( + t("notifications.removeFromConfigSuccess", { + defaultValue: "已从配置移除", + }), + { closeButton: true }, + ); } else { // Delete from database await deleteProvider(provider.id); diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json index 99948b46..2b0d987f 100644 --- a/src/i18n/locales/en.json +++ b/src/i18n/locales/en.json @@ -137,6 +137,8 @@ "providerSaved": "Provider configuration saved", "providerDeleted": "Provider deleted successfully", "switchSuccess": "Switch successful!", + "addToConfigSuccess": "Added to config", + "removeFromConfigSuccess": "Removed from config", "switchFailedTitle": "Switch failed", "switchFailed": "Switch failed: {{error}}", "autoImported": "Default provider created from existing configuration", diff --git a/src/i18n/locales/ja.json b/src/i18n/locales/ja.json index de3aaf02..2360ae19 100644 --- a/src/i18n/locales/ja.json +++ b/src/i18n/locales/ja.json @@ -137,6 +137,8 @@ "providerSaved": "プロバイダー設定を保存しました", "providerDeleted": "プロバイダーを削除しました", "switchSuccess": "切り替え成功!", + "addToConfigSuccess": "設定に追加しました", + "removeFromConfigSuccess": "設定から削除しました", "switchFailedTitle": "切り替えに失敗しました", "switchFailed": "切り替えに失敗しました: {{error}}", "autoImported": "既存設定からデフォルトプロバイダーを自動作成しました", diff --git a/src/i18n/locales/zh.json b/src/i18n/locales/zh.json index 295dc9d7..2f4181ba 100644 --- a/src/i18n/locales/zh.json +++ b/src/i18n/locales/zh.json @@ -137,6 +137,8 @@ "providerSaved": "供应商配置已保存", "providerDeleted": "供应商删除成功", "switchSuccess": "切换成功!", + "addToConfigSuccess": "已添加到配置", + "removeFromConfigSuccess": "已从配置移除", "switchFailedTitle": "切换失败", "switchFailed": "切换失败:{{error}}", "autoImported": "已从现有配置创建默认供应商", diff --git a/src/lib/api/providers.ts b/src/lib/api/providers.ts index 0c1b76ef..dd154b4e 100644 --- a/src/lib/api/providers.ts +++ b/src/lib/api/providers.ts @@ -38,6 +38,14 @@ export const providersApi = { return await invoke("delete_provider", { id, app: appId }); }, + /** + * Remove provider from live config only (for additive mode apps like OpenCode) + * Does NOT delete from database - provider remains in the list + */ + async removeFromLiveConfig(id: string, appId: AppId): Promise { + return await invoke("remove_provider_from_live_config", { id, app: appId }); + }, + async switch(id: string, appId: AppId): Promise { return await invoke("switch_provider", { id, app: appId }); }, diff --git a/src/lib/query/mutations.ts b/src/lib/query/mutations.ts index 2a3aee8c..9ab4db1f 100644 --- a/src/lib/query/mutations.ts +++ b/src/lib/query/mutations.ts @@ -155,6 +155,13 @@ export const useSwitchProviderMutation = (appId: AppId) => { onSuccess: async () => { await queryClient.invalidateQueries({ queryKey: ["providers", appId] }); + // OpenCode: also invalidate live provider IDs cache to update button state + if (appId === "opencode") { + await queryClient.invalidateQueries({ + queryKey: ["opencodeLiveProviderIds"], + }); + } + // 更新托盘菜单(失败不影响主操作) try { await providersApi.updateTrayMenu(); @@ -165,15 +172,17 @@ export const useSwitchProviderMutation = (appId: AppId) => { ); } - toast.success( - t("notifications.switchSuccess", { - defaultValue: "切换供应商成功", - appName: t(`apps.${appId}`, { defaultValue: appId }), - }), - { - closeButton: true, - }, - ); + // OpenCode: show "added to config" message instead of "switched" + const messageKey = + appId === "opencode" + ? "notifications.addToConfigSuccess" + : "notifications.switchSuccess"; + const defaultMessage = + appId === "opencode" ? "已添加到配置" : "切换供应商成功"; + + toast.success(t(messageKey, { defaultValue: defaultMessage }), { + closeButton: true, + }); }, onError: (error: Error) => { const detail = extractErrorMessage(error) || t("common.unknown");