diff --git a/public/js/components/claude-config.js b/public/js/components/claude-config.js index d240ab0..a402913 100644 --- a/public/js/components/claude-config.js +++ b/public/js/components/claude-config.js @@ -9,6 +9,7 @@ window.Components.claudeConfig = () => ({ configPath: '', // Dynamic path from backend models: [], loading: false, + restoring: false, gemini1mSuffix: false, // Model fields that may contain Gemini model names @@ -141,5 +142,34 @@ window.Components.claudeConfig = () => ({ } finally { this.loading = false; } + }, + + restoreDefaultClaudeConfig() { + document.getElementById('restore_defaults_modal').showModal(); + }, + + async executeRestore() { + this.restoring = true; + const password = Alpine.store('global').webuiPassword; + try { + const { response, newPassword } = await window.utils.request('/api/claude/config/restore', { + method: 'POST', + headers: { 'Content-Type': 'application/json' } + }, password); + if (newPassword) Alpine.store('global').webuiPassword = newPassword; + + if (!response.ok) throw new Error(`HTTP ${response.status}`); + Alpine.store('global').showToast(Alpine.store('global').t('claudeConfigRestored'), 'success'); + + // Close modal + document.getElementById('restore_defaults_modal').close(); + + // Reload the config to reflect the changes + await this.fetchConfig(); + } catch (e) { + Alpine.store('global').showToast(Alpine.store('global').t('restoreConfigFailed') + ': ' + e.message, 'error'); + } finally { + this.restoring = false; + } } }); diff --git a/public/js/store.js b/public/js/store.js index e8727ce..e5a8ee2 100644 --- a/public/js/store.js +++ b/public/js/store.js @@ -157,7 +157,13 @@ document.addEventListener('alpine:init', () => { accountsReloaded: "Accounts reloaded", reloadFailed: "Reload failed", claudeConfigSaved: "Claude configuration saved", + claudeConfigRestored: "Claude CLI restored to defaults", saveConfigFailed: "Failed to save configuration", + restoreConfigFailed: "Failed to restore configuration", + restoreDefault: "Restore Default", + confirmRestoreTitle: "Confirm Restore", + confirmRestoreMessage: "Are you sure you want to restore Claude CLI to default settings? This will remove proxy configuration.", + confirmRestore: "Confirm Restore", claudeActive: "Claude Active", claudeEmpty: "Claude Empty", geminiActive: "Gemini Active", @@ -406,7 +412,13 @@ document.addEventListener('alpine:init', () => { accountsReloaded: "账号配置已重载", reloadFailed: "重载失败", claudeConfigSaved: "Claude 配置已保存", + claudeConfigRestored: "Claude CLI 已恢复默认设置", saveConfigFailed: "保存配置失败", + restoreConfigFailed: "恢复配置失败", + restoreDefault: "恢复默认", + confirmRestoreTitle: "确认恢复", + confirmRestoreMessage: "确定要将 Claude CLI 恢复为默认设置吗?这将移除代理配置。", + confirmRestore: "确认恢复", claudeActive: "Claude 活跃", claudeEmpty: "Claude 耗尽", geminiActive: "Gemini 活跃", diff --git a/public/views/settings.html b/public/views/settings.html index 1d1e844..0760fbd 100644 --- a/public/views/settings.html +++ b/public/views/settings.html @@ -523,7 +523,16 @@ -
+
+
+ + + + + +
diff --git a/src/utils/claude-config.js b/src/utils/claude-config.js index f7986e2..d4e0743 100644 --- a/src/utils/claude-config.js +++ b/src/utils/claude-config.js @@ -83,6 +83,35 @@ export async function updateClaudeConfig(updates) { } } +/** + * Replace the global Claude CLI configuration entirely + * Unlike updateClaudeConfig, this replaces the config instead of merging. + * + * @param {Object} config - The new configuration to write + * @returns {Promise} The written configuration + */ +export async function replaceClaudeConfig(config) { + const configPath = getClaudeConfigPath(); + + // 1. Ensure .claude directory exists + const configDir = path.dirname(configPath); + try { + await fs.mkdir(configDir, { recursive: true }); + } catch (error) { + // Ignore if exists + } + + // 2. Write config directly (no merge) + try { + await fs.writeFile(configPath, JSON.stringify(config, null, 2), 'utf8'); + logger.info(`[ClaudeConfig] Replaced config at ${configPath}`); + return config; + } catch (error) { + logger.error(`[ClaudeConfig] Failed to write config:`, error.message); + throw error; + } +} + /** * Simple deep merge for objects */ diff --git a/src/webui/index.js b/src/webui/index.js index 9594621..2b32aac 100644 --- a/src/webui/index.js +++ b/src/webui/index.js @@ -18,7 +18,7 @@ import { fileURLToPath } from 'url'; import express from 'express'; import { getPublicConfig, saveConfig, config } from '../config.js'; import { DEFAULT_PORT, ACCOUNT_CONFIG_PATH } from '../constants.js'; -import { readClaudeConfig, updateClaudeConfig, getClaudeConfigPath } from '../utils/claude-config.js'; +import { readClaudeConfig, updateClaudeConfig, replaceClaudeConfig, getClaudeConfigPath } from '../utils/claude-config.js'; import { logger } from '../utils/logger.js'; import { getAuthorizationUrl, completeOAuthFlow, startCallbackServer } from '../auth/oauth.js'; import { loadAccounts, saveAccounts } from '../account-manager/storage.js'; @@ -438,6 +438,52 @@ export function mountWebUI(app, dirname, accountManager) { } }); + /** + * POST /api/claude/config/restore - Restore Claude CLI to default (remove proxy settings) + */ + app.post('/api/claude/config/restore', async (req, res) => { + try { + const claudeConfig = await readClaudeConfig(); + + // Proxy-related environment variables to remove when restoring defaults + const PROXY_ENV_VARS = [ + 'ANTHROPIC_BASE_URL', + 'ANTHROPIC_AUTH_TOKEN', + 'ANTHROPIC_MODEL', + 'CLAUDE_CODE_SUBAGENT_MODEL', + 'ANTHROPIC_DEFAULT_OPUS_MODEL', + 'ANTHROPIC_DEFAULT_SONNET_MODEL', + 'ANTHROPIC_DEFAULT_HAIKU_MODEL', + 'ENABLE_EXPERIMENTAL_MCP_CLI' + ]; + + // Remove proxy-related environment variables to restore defaults + if (claudeConfig.env) { + for (const key of PROXY_ENV_VARS) { + delete claudeConfig.env[key]; + } + // Remove env entirely if empty to truly restore defaults + if (Object.keys(claudeConfig.env).length === 0) { + delete claudeConfig.env; + } + } + + // Use replaceClaudeConfig to completely overwrite the config (not merge) + const newConfig = await replaceClaudeConfig(claudeConfig); + + logger.info(`[WebUI] Restored Claude CLI config to defaults at ${getClaudeConfigPath()}`); + + res.json({ + status: 'ok', + config: newConfig, + message: 'Claude CLI configuration restored to defaults' + }); + } catch (error) { + logger.error('[WebUI] Error restoring Claude config:', error); + res.status(500).json({ status: 'error', error: error.message }); + } + }); + /** * POST /api/models/config - Update model configuration (hidden/pinned/alias) */