From aa72cf13ff32e3711a2a3c61155ff384262f3104 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Mar 2026 14:10:17 +0000 Subject: [PATCH 1/3] Initial plan From 48c36d01f9ca107e944234472ea8ecf8e0e59bca Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Mar 2026 14:25:01 +0000 Subject: [PATCH 2/3] Add multi-AI environment variable profile switching Co-authored-by: xuzeyu91 <26290929+xuzeyu91@users.noreply.github.com> --- .../Domain/Model/CliToolEnvProfile.cs | 52 +++ .../Service/CliToolEnvironmentService.cs | 191 ++++++++++- .../CliToolEnv/CliToolEnvProfileRepository.cs | 123 +++++++ .../ICliToolEnvProfileRepository.cs | 35 ++ .../EnvironmentVariableConfigModal.razor | 311 +++++++++++++++++- WebCodeCli/Resources/Localization/en-US.json | 29 +- WebCodeCli/Resources/Localization/ja-JP.json | 29 +- WebCodeCli/Resources/Localization/ko-KR.json | 29 +- WebCodeCli/Resources/Localization/zh-CN.json | 33 +- .../wwwroot/Resources/Localization/en-US.json | 29 +- .../wwwroot/Resources/Localization/ja-JP.json | 29 +- .../wwwroot/Resources/Localization/ko-KR.json | 29 +- .../wwwroot/Resources/Localization/zh-CN.json | 33 +- 13 files changed, 877 insertions(+), 75 deletions(-) create mode 100644 WebCodeCli.Domain/Domain/Model/CliToolEnvProfile.cs create mode 100644 WebCodeCli.Domain/Repositories/Base/CliToolEnv/CliToolEnvProfileRepository.cs create mode 100644 WebCodeCli.Domain/Repositories/Base/CliToolEnv/ICliToolEnvProfileRepository.cs diff --git a/WebCodeCli.Domain/Domain/Model/CliToolEnvProfile.cs b/WebCodeCli.Domain/Domain/Model/CliToolEnvProfile.cs new file mode 100644 index 0000000..cf061d9 --- /dev/null +++ b/WebCodeCli.Domain/Domain/Model/CliToolEnvProfile.cs @@ -0,0 +1,52 @@ +using SqlSugar; + +namespace WebCodeCli.Domain.Domain.Model; + +/// +/// CLI 工具环境变量配置方案(支持多套 AI 配置快速切换) +/// +[SugarTable("cli_tool_env_profiles")] +public class CliToolEnvProfile +{ + /// + /// 主键 ID + /// + [SugarColumn(IsPrimaryKey = true, IsIdentity = true)] + public int Id { get; set; } + + /// + /// CLI 工具 ID + /// + [SugarColumn(Length = 50, IsNullable = false, ColumnDescription = "CLI工具ID")] + public string ToolId { get; set; } = string.Empty; + + /// + /// 方案名称(如 "OpenAI", "DeepSeek", "Anthropic") + /// + [SugarColumn(Length = 100, IsNullable = false, ColumnDescription = "方案名称")] + public string ProfileName { get; set; } = string.Empty; + + /// + /// 是否为当前激活方案 + /// + [SugarColumn(IsNullable = false, ColumnDescription = "是否激活")] + public bool IsActive { get; set; } = false; + + /// + /// 环境变量(JSON 格式存储键值对) + /// + [SugarColumn(Length = 8000, IsNullable = true, ColumnDescription = "环境变量JSON")] + public string? EnvVarsJson { get; set; } + + /// + /// 创建时间 + /// + [SugarColumn(IsNullable = false, ColumnDescription = "创建时间")] + public DateTime CreatedAt { get; set; } = DateTime.Now; + + /// + /// 更新时间 + /// + [SugarColumn(IsNullable = false, ColumnDescription = "更新时间")] + public DateTime UpdatedAt { get; set; } = DateTime.Now; +} diff --git a/WebCodeCli.Domain/Domain/Service/CliToolEnvironmentService.cs b/WebCodeCli.Domain/Domain/Service/CliToolEnvironmentService.cs index 5aa93ad..ecb7c29 100644 --- a/WebCodeCli.Domain/Domain/Service/CliToolEnvironmentService.cs +++ b/WebCodeCli.Domain/Domain/Service/CliToolEnvironmentService.cs @@ -1,8 +1,10 @@ +using System.Text.Json; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using WebCodeCli.Domain.Common.Extensions; using WebCodeCli.Domain.Common.Options; +using WebCodeCli.Domain.Domain.Model; using WebCodeCli.Domain.Repositories.Base.CliToolEnv; namespace WebCodeCli.Domain.Domain.Service; @@ -13,12 +15,12 @@ namespace WebCodeCli.Domain.Domain.Service; public interface ICliToolEnvironmentService { /// - /// 获取指定工具的环境变量配置(优先从数据库读取,否则从appsettings读取) + /// 获取指定工具的环境变量配置(优先使用激活方案,其次数据库默认配置,最后 appsettings) /// Task> GetEnvironmentVariablesAsync(string toolId); /// - /// 保存指定工具的环境变量配置到数据库 + /// 保存指定工具的默认环境变量配置到数据库 /// Task SaveEnvironmentVariablesAsync(string toolId, Dictionary envVars); @@ -31,6 +33,33 @@ public interface ICliToolEnvironmentService /// 重置为appsettings中的默认配置 /// Task> ResetToDefaultAsync(string toolId); + + // ── 配置方案(多套 AI 环境变量) ── + + /// + /// 获取指定工具的所有配置方案 + /// + Task> GetProfilesAsync(string toolId); + + /// + /// 保存(新建或更新)一个配置方案 + /// + Task SaveProfileAsync(string toolId, int profileId, string profileName, Dictionary envVars); + + /// + /// 激活指定配置方案(将其设为当前生效方案) + /// + Task ActivateProfileAsync(string toolId, int profileId); + + /// + /// 取消所有方案激活,回退到默认配置 + /// + Task DeactivateProfilesAsync(string toolId); + + /// + /// 删除指定配置方案 + /// + Task DeleteProfileAsync(string toolId, int profileId); } /// @@ -42,43 +71,54 @@ public class CliToolEnvironmentService : ICliToolEnvironmentService private readonly ILogger _logger; private readonly CliToolsOption _options; private readonly ICliToolEnvironmentVariableRepository _repository; + private readonly ICliToolEnvProfileRepository _profileRepository; public CliToolEnvironmentService( ILogger logger, IOptions options, - ICliToolEnvironmentVariableRepository repository) + ICliToolEnvironmentVariableRepository repository, + ICliToolEnvProfileRepository profileRepository) { _logger = logger; _options = options.Value; _repository = repository; + _profileRepository = profileRepository; } /// - /// 获取指定工具的环境变量配置(优先从数据库读取,否则从appsettings读取) + /// 获取指定工具的环境变量配置 + /// 优先级:激活的配置方案 > 数据库默认配置 > appsettings 配置 /// public async Task> GetEnvironmentVariablesAsync(string toolId) { try { - // 尝试从数据库读取 + // 1. 优先使用激活的配置方案 + var activeProfile = await _profileRepository.GetActiveProfileAsync(toolId); + if (activeProfile != null && !string.IsNullOrWhiteSpace(activeProfile.EnvVarsJson)) + { + _logger.LogInformation("从激活方案 [{ProfileName}] 加载工具 {ToolId} 的环境变量配置", activeProfile.ProfileName, toolId); + var profileVars = JsonSerializer.Deserialize>(activeProfile.EnvVarsJson) ?? new(); + return profileVars + .Where(kvp => !string.IsNullOrWhiteSpace(kvp.Value)) + .ToDictionary(kvp => kvp.Key, kvp => kvp.Value); + } + + // 2. 从数据库默认配置读取 var dbEnvVars = await _repository.GetEnvironmentVariablesByToolIdAsync(toolId); - - // 如果数据库中有配置,则使用数据库配置(过滤空值) if (dbEnvVars.Any()) { _logger.LogInformation("从数据库加载工具 {ToolId} 的环境变量配置", toolId); - // 过滤掉空值的环境变量,避免空字符串覆盖系统默认配置 return dbEnvVars .Where(kvp => !string.IsNullOrWhiteSpace(kvp.Value)) .ToDictionary(kvp => kvp.Key, kvp => kvp.Value); } - // 否则从appsettings读取 + // 3. 从 appsettings 读取 var tool = _options.Tools.FirstOrDefault(t => t.Id == toolId); if (tool?.EnvironmentVariables != null && tool.EnvironmentVariables.Any()) { _logger.LogInformation("从配置文件加载工具 {ToolId} 的环境变量配置", toolId); - // 同样过滤掉空值 return tool.EnvironmentVariables .Where(kvp => !string.IsNullOrWhiteSpace(kvp.Value)) .ToDictionary(kvp => kvp.Key, kvp => kvp.Value); @@ -161,4 +201,135 @@ public async Task> ResetToDefaultAsync(string toolId) return new Dictionary(); } } + + // ── 配置方案(多套 AI 环境变量) ── + + /// + /// 获取指定工具的所有配置方案 + /// + public async Task> GetProfilesAsync(string toolId) + { + try + { + return await _profileRepository.GetProfilesByToolIdAsync(toolId); + } + catch (Exception ex) + { + _logger.LogError(ex, "获取工具 {ToolId} 的配置方案列表失败", toolId); + return new List(); + } + } + + /// + /// 保存(新建或更新)一个配置方案 + /// + public async Task SaveProfileAsync(string toolId, int profileId, string profileName, Dictionary envVars) + { + try + { + var envVarsJson = JsonSerializer.Serialize(envVars); + + if (profileId <= 0) + { + // 新建方案 + var newProfile = new CliToolEnvProfile + { + ToolId = toolId, + ProfileName = profileName, + IsActive = false, + EnvVarsJson = envVarsJson, + CreatedAt = DateTime.Now, + UpdatedAt = DateTime.Now + }; + var newId = await _profileRepository.InsertReturnIdentityAsync(newProfile); + newProfile.Id = newId; + _logger.LogInformation("成功新建工具 {ToolId} 的配置方案 [{ProfileName}]", toolId, profileName); + return newProfile; + } + else + { + // 更新已有方案 + var existing = await _profileRepository.GetByIdAsync(profileId); + if (existing == null || existing.ToolId != toolId) + { + _logger.LogWarning("未找到工具 {ToolId} 的配置方案 {ProfileId}", toolId, profileId); + return null; + } + existing.ProfileName = profileName; + existing.EnvVarsJson = envVarsJson; + existing.UpdatedAt = DateTime.Now; + await _profileRepository.UpdateAsync(existing); + _logger.LogInformation("成功更新工具 {ToolId} 的配置方案 [{ProfileName}]", toolId, profileName); + return existing; + } + } + catch (Exception ex) + { + _logger.LogError(ex, "保存工具 {ToolId} 的配置方案失败", toolId); + return null; + } + } + + /// + /// 激活指定配置方案 + /// + public async Task ActivateProfileAsync(string toolId, int profileId) + { + try + { + var result = await _profileRepository.ActivateProfileAsync(toolId, profileId); + if (result) + { + _logger.LogInformation("成功激活工具 {ToolId} 的配置方案 {ProfileId}", toolId, profileId); + } + return result; + } + catch (Exception ex) + { + _logger.LogError(ex, "激活工具 {ToolId} 的配置方案 {ProfileId} 失败", toolId, profileId); + return false; + } + } + + /// + /// 取消所有方案激活,回退到默认配置 + /// + public async Task DeactivateProfilesAsync(string toolId) + { + try + { + var result = await _profileRepository.DeactivateAllProfilesAsync(toolId); + if (result) + { + _logger.LogInformation("已取消工具 {ToolId} 的所有方案激活状态", toolId); + } + return result; + } + catch (Exception ex) + { + _logger.LogError(ex, "取消工具 {ToolId} 方案激活状态失败", toolId); + return false; + } + } + + /// + /// 删除指定配置方案 + /// + public async Task DeleteProfileAsync(string toolId, int profileId) + { + try + { + var result = await _profileRepository.DeleteProfileAsync(profileId); + if (result) + { + _logger.LogInformation("成功删除工具 {ToolId} 的配置方案 {ProfileId}", toolId, profileId); + } + return result; + } + catch (Exception ex) + { + _logger.LogError(ex, "删除工具 {ToolId} 的配置方案 {ProfileId} 失败", toolId, profileId); + return false; + } + } } diff --git a/WebCodeCli.Domain/Repositories/Base/CliToolEnv/CliToolEnvProfileRepository.cs b/WebCodeCli.Domain/Repositories/Base/CliToolEnv/CliToolEnvProfileRepository.cs new file mode 100644 index 0000000..418a52b --- /dev/null +++ b/WebCodeCli.Domain/Repositories/Base/CliToolEnv/CliToolEnvProfileRepository.cs @@ -0,0 +1,123 @@ +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using WebCodeCli.Domain.Common.Extensions; +using WebCodeCli.Domain.Domain.Model; +using WebCodeCli.Domain.Repositories.Base; +using AntSK.Domain.Repositories.Base; + +namespace WebCodeCli.Domain.Repositories.Base.CliToolEnv; + +/// +/// CLI 工具环境变量配置方案仓储实现 +/// +[ServiceDescription(typeof(ICliToolEnvProfileRepository), ServiceLifetime.Scoped)] +public class CliToolEnvProfileRepository : Repository, ICliToolEnvProfileRepository +{ + private readonly ILogger _logger; + + public CliToolEnvProfileRepository(ILogger logger) + { + _logger = logger; + } + + /// + /// 获取指定工具的所有配置方案 + /// + public async Task> GetProfilesByToolIdAsync(string toolId) + { + try + { + return await GetListAsync(x => x.ToolId == toolId); + } + catch (Exception ex) + { + _logger.LogError(ex, "获取工具 {ToolId} 的配置方案列表失败", toolId); + return new List(); + } + } + + /// + /// 获取指定工具的当前激活方案 + /// + public async Task GetActiveProfileAsync(string toolId) + { + try + { + return await GetFirstAsync(x => x.ToolId == toolId && x.IsActive); + } + catch (Exception ex) + { + _logger.LogError(ex, "获取工具 {ToolId} 的激活方案失败", toolId); + return null; + } + } + + /// + /// 激活指定方案(同时取消同工具其他方案的激活状态) + /// + public async Task ActivateProfileAsync(string toolId, int profileId) + { + try + { + // 取消该工具所有方案的激活状态 + var profiles = await GetListAsync(x => x.ToolId == toolId); + foreach (var profile in profiles) + { + profile.IsActive = profile.Id == profileId; + profile.UpdatedAt = DateTime.Now; + } + if (profiles.Any()) + { + await UpdateRangeAsync(profiles); + } + return true; + } + catch (Exception ex) + { + _logger.LogError(ex, "激活工具 {ToolId} 的方案 {ProfileId} 失败", toolId, profileId); + return false; + } + } + + /// + /// 取消指定工具的所有方案激活状态 + /// + public async Task DeactivateAllProfilesAsync(string toolId) + { + try + { + var profiles = await GetListAsync(x => x.ToolId == toolId && x.IsActive); + foreach (var profile in profiles) + { + profile.IsActive = false; + profile.UpdatedAt = DateTime.Now; + } + if (profiles.Any()) + { + await UpdateRangeAsync(profiles); + } + return true; + } + catch (Exception ex) + { + _logger.LogError(ex, "取消工具 {ToolId} 所有方案激活状态失败", toolId); + return false; + } + } + + /// + /// 删除指定方案 + /// + public async Task DeleteProfileAsync(int profileId) + { + try + { + return await DeleteAsync(x => x.Id == profileId); + } + catch (Exception ex) + { + _logger.LogError(ex, "删除方案 {ProfileId} 失败", profileId); + return false; + } + } +} diff --git a/WebCodeCli.Domain/Repositories/Base/CliToolEnv/ICliToolEnvProfileRepository.cs b/WebCodeCli.Domain/Repositories/Base/CliToolEnv/ICliToolEnvProfileRepository.cs new file mode 100644 index 0000000..a4c1193 --- /dev/null +++ b/WebCodeCli.Domain/Repositories/Base/CliToolEnv/ICliToolEnvProfileRepository.cs @@ -0,0 +1,35 @@ +using WebCodeCli.Domain.Domain.Model; +using WebCodeCli.Domain.Repositories.Base; + +namespace WebCodeCli.Domain.Repositories.Base.CliToolEnv; + +/// +/// CLI 工具环境变量配置方案仓储接口 +/// +public interface ICliToolEnvProfileRepository : IRepository +{ + /// + /// 获取指定工具的所有配置方案 + /// + Task> GetProfilesByToolIdAsync(string toolId); + + /// + /// 获取指定工具的当前激活方案 + /// + Task GetActiveProfileAsync(string toolId); + + /// + /// 激活指定方案(同时取消同工具其他方案的激活状态) + /// + Task ActivateProfileAsync(string toolId, int profileId); + + /// + /// 取消指定工具的所有方案激活状态 + /// + Task DeactivateAllProfilesAsync(string toolId); + + /// + /// 删除指定工具的指定方案 + /// + Task DeleteProfileAsync(int profileId); +} diff --git a/WebCodeCli/Components/EnvironmentVariableConfigModal.razor b/WebCodeCli/Components/EnvironmentVariableConfigModal.razor index 0c67592..cc9e988 100644 --- a/WebCodeCli/Components/EnvironmentVariableConfigModal.razor +++ b/WebCodeCli/Components/EnvironmentVariableConfigModal.razor @@ -56,6 +56,113 @@ + + + + + @T("envConfig.profilesTitle") + @T("envConfig.profilesDesc") + + + + + + @T("envConfig.addProfile") + + + + @if (_profiles.Any()) + { + + @foreach (var profile in _profiles) + { + + @if (profile.IsActive) + { + + + + + @T("envConfig.activeProfile") + + } + @profile.ProfileName + + @if (!profile.IsActive) + { + ActivateProfile(profile)" + class="px-2 py-1 text-xs font-semibold text-green-700 bg-green-50 border border-green-200 rounded-md hover:bg-green-100 transition-all" + title="@T("envConfig.activateProfile")"> + @T("envConfig.activateProfile") + + } + else + { + + @T("envConfig.deactivateProfile") + + } + EditProfile(profile)" + class="p-1.5 text-blue-600 hover:bg-blue-50 rounded-md transition-all" + title="@T("envConfig.editProfile")"> + + + + + DeleteProfile(profile)" + class="p-1.5 text-red-500 hover:bg-red-50 rounded-md transition-all" + title="@T("envConfig.deleteProfile")"> + + + + + + + } + + } + + + + + + @if (_editingProfile != null) + { + @T("envConfig.editingProfile", ("name", _editingProfile.ProfileName)) + } + else if (_isAddingProfile) + { + @T("envConfig.addProfile") + } + else + { + @T("envConfig.editingDefault") + } + + @if (_editingProfile != null || _isAddingProfile) + { + + @T("envConfig.cancelEdit") + + } + + + + @if (_isAddingProfile) + { + + @T("envConfig.profilesTitle") + + + } + @foreach (var (index, kvp) in _envVars.Select((kvp, i) => (i, kvp))) { @@ -119,11 +226,18 @@ - - @T("envConfig.resetButton") - + @if (_editingProfile == null && !_isAddingProfile) + { + + @T("envConfig.resetButton") + + } + else + { + + } @T("envConfig.saving") } + else if (_editingProfile != null || _isAddingProfile) + { + @T("envConfig.saveProfile") + } else { @T("envConfig.save") @@ -158,6 +276,12 @@ private CliToolConfig? _selectedTool; private List _envVars = new(); + // 配置方案相关 + private List _profiles = new(); + private CliToolEnvProfile? _editingProfile = null; + private bool _isAddingProfile = false; + private string _newProfileName = string.Empty; + // 本地化相关 private Dictionary _translations = new(); private string _currentLanguage = "zh-CN"; @@ -178,14 +302,30 @@ _selectedTool = tool; _isVisible = true; _isLoading = true; + _editingProfile = null; + _isAddingProfile = false; + _newProfileName = string.Empty; StateHasChanged(); try { _currentLanguage = await L.GetCurrentLanguageAsync(); await LoadTranslationsAsync(); - var envVars = await CliToolEnvironmentService.GetEnvironmentVariablesAsync(tool.Id); - _envVars = envVars.Select(kvp => new EnvVarItem { Key = kvp.Key, Value = kvp.Value }).ToList(); + + // 加载配置方案列表 + _profiles = await CliToolEnvironmentService.GetProfilesAsync(tool.Id); + + // 若没有激活方案,加载默认配置到编辑区;若有激活方案则编辑区空置 + var hasActiveProfile = _profiles.Any(p => p.IsActive); + if (hasActiveProfile) + { + _envVars = new List(); + } + else + { + var envVars = await CliToolEnvironmentService.GetEnvironmentVariablesAsync(tool.Id); + _envVars = envVars.Select(kvp => new EnvVarItem { Key = kvp.Key, Value = kvp.Value }).ToList(); + } } catch (Exception ex) { @@ -203,6 +343,10 @@ _isVisible = false; _selectedTool = null; _envVars.Clear(); + _profiles.Clear(); + _editingProfile = null; + _isAddingProfile = false; + _newProfileName = string.Empty; StateHasChanged(); } @@ -235,6 +379,121 @@ } } + // ── 配置方案操作 ── + + private void StartAddProfile() + { + _isAddingProfile = true; + _editingProfile = null; + _newProfileName = string.Empty; + _envVars = new List(); + StateHasChanged(); + } + + private void EditProfile(CliToolEnvProfile profile) + { + _editingProfile = profile; + _isAddingProfile = false; + _newProfileName = profile.ProfileName; + + try + { + if (!string.IsNullOrWhiteSpace(profile.EnvVarsJson)) + { + var dict = System.Text.Json.JsonSerializer.Deserialize>(profile.EnvVarsJson) ?? new(); + _envVars = dict.Select(kvp => new EnvVarItem { Key = kvp.Key, Value = kvp.Value }).ToList(); + } + else + { + _envVars = new List(); + } + } + catch + { + _envVars = new List(); + } + StateHasChanged(); + } + + private void CancelProfileEdit() + { + _editingProfile = null; + _isAddingProfile = false; + _newProfileName = string.Empty; + _envVars = new List(); + StateHasChanged(); + } + + private async Task ActivateProfile(CliToolEnvProfile profile) + { + if (_selectedTool == null) return; + _isLoading = true; + StateHasChanged(); + try + { + await CliToolEnvironmentService.ActivateProfileAsync(_selectedTool.Id, profile.Id); + _profiles = await CliToolEnvironmentService.GetProfilesAsync(_selectedTool.Id); + Console.WriteLine($"方案已激活: {profile.ProfileName}"); + } + catch (Exception ex) + { + Console.WriteLine($"激活方案失败: {ex.Message}"); + } + finally + { + _isLoading = false; + StateHasChanged(); + } + } + + private async Task DeactivateProfiles() + { + if (_selectedTool == null) return; + _isLoading = true; + StateHasChanged(); + try + { + await CliToolEnvironmentService.DeactivateProfilesAsync(_selectedTool.Id); + _profiles = await CliToolEnvironmentService.GetProfilesAsync(_selectedTool.Id); + Console.WriteLine("已取消所有方案激活"); + } + catch (Exception ex) + { + Console.WriteLine($"取消激活失败: {ex.Message}"); + } + finally + { + _isLoading = false; + StateHasChanged(); + } + } + + private async Task DeleteProfile(CliToolEnvProfile profile) + { + if (_selectedTool == null) return; + _isLoading = true; + StateHasChanged(); + try + { + await CliToolEnvironmentService.DeleteProfileAsync(_selectedTool.Id, profile.Id); + _profiles = await CliToolEnvironmentService.GetProfilesAsync(_selectedTool.Id); + if (_editingProfile?.Id == profile.Id) + { + CancelProfileEdit(); + } + Console.WriteLine($"方案已删除: {profile.ProfileName}"); + } + catch (Exception ex) + { + Console.WriteLine($"删除方案失败: {ex.Message}"); + } + finally + { + _isLoading = false; + StateHasChanged(); + } + } + private async Task Save() { if (_selectedTool == null) return; @@ -244,26 +503,48 @@ try { - // 过滤掉空的键或空的值的环境变量,只保存有实际值的配置 + // 过滤掉空键或空值的条目 var validEnvVars = _envVars .Where(ev => !string.IsNullOrWhiteSpace(ev.Key) && !string.IsNullOrWhiteSpace(ev.Value)) .ToDictionary(ev => ev.Key.Trim(), ev => ev.Value!.Trim()); - var success = await CliToolEnvironmentService.SaveEnvironmentVariablesAsync(_selectedTool.Id, validEnvVars); - - if (success) + if (_isAddingProfile || _editingProfile != null) { - Console.WriteLine("环境变量保存成功"); - Close(); + // 保存配置方案 + var profileName = _isAddingProfile ? _newProfileName.Trim() : (_editingProfile?.ProfileName ?? string.Empty); + if (string.IsNullOrWhiteSpace(profileName)) profileName = "未命名方案"; + + var profileId = _editingProfile?.Id ?? 0; + var savedProfile = await CliToolEnvironmentService.SaveProfileAsync(_selectedTool.Id, profileId, profileName, validEnvVars); + if (savedProfile != null) + { + _profiles = await CliToolEnvironmentService.GetProfilesAsync(_selectedTool.Id); + CancelProfileEdit(); + Console.WriteLine($"方案已保存: {profileName}"); + } + else + { + Console.WriteLine("方案保存失败"); + } } else { - Console.WriteLine("环境变量保存失败"); + // 保存默认配置 + var success = await CliToolEnvironmentService.SaveEnvironmentVariablesAsync(_selectedTool.Id, validEnvVars); + if (success) + { + Console.WriteLine("默认环境变量保存成功"); + Close(); + } + else + { + Console.WriteLine("环境变量保存失败"); + } } } catch (Exception ex) { - Console.WriteLine($"保存环境变量失败: {ex.Message}"); + Console.WriteLine($"保存失败: {ex.Message}"); } finally { diff --git a/WebCodeCli/Resources/Localization/en-US.json b/WebCodeCli/Resources/Localization/en-US.json index 342338f..8a090de 100644 --- a/WebCodeCli/Resources/Localization/en-US.json +++ b/WebCodeCli/Resources/Localization/en-US.json @@ -142,9 +142,9 @@ "event": "Event ({type})" }, "content": { - "threadId": "Thread ID: {id}", - "threadCreated": "A new conversation thread has been created.", - "turnStarted": "Waiting for response...", + "threadId": "Thread ID: {id}", + "threadCreated": "A new conversation thread has been created.", + "turnStarted": "Waiting for response...", "turnCompleted": "This turn has completed.", "turnCompletedWithUsage": "This turn has completed. See token usage below." }, @@ -343,7 +343,25 @@ "addButton": "Add Environment Variable", "resetButton": "Reset to Default", "saving": "Saving...", - "save": "Save" + "save": "Save", + "profilesTitle": "Configuration Profiles", + "profilesDesc": "Create multiple environment variable profiles to quickly switch between AI configurations", + "addProfile": "New Profile", + "profileNamePlaceholder": "Profile name, e.g. OpenAI / DeepSeek / Anthropic", + "activateProfile": "Activate", + "activeProfile": "Active", + "editProfile": "Edit", + "deleteProfile": "Delete", + "deleteProfileConfirm": "Delete this configuration profile?", + "deactivateProfile": "Deactivate", + "profileSaved": "Profile saved", + "profileActivated": "Profile activated", + "profileDeactivated": "Switched back to default config", + "profileDeleted": "Profile deleted", + "editingProfile": "Editing profile: {name}", + "editingDefault": "Editing default config", + "saveProfile": "Save Profile", + "cancelEdit": "Cancel Edit" }, "contextPreview": { "title": "Context Preview", @@ -799,5 +817,4 @@ "createFailedWithMessage": "Create failed: {message}" } } -} - +} \ No newline at end of file diff --git a/WebCodeCli/Resources/Localization/ja-JP.json b/WebCodeCli/Resources/Localization/ja-JP.json index 3e914f7..ab26afd 100644 --- a/WebCodeCli/Resources/Localization/ja-JP.json +++ b/WebCodeCli/Resources/Localization/ja-JP.json @@ -191,9 +191,9 @@ "event": "イベント({type})" }, "content": { - "threadId": "スレッドID: {id}", - "threadCreated": "新しい会話スレッドを作成しました。", - "turnStarted": "応答待ち...", + "threadId": "スレッドID: {id}", + "threadCreated": "新しい会話スレッドを作成しました。", + "turnStarted": "応答待ち...", "turnCompleted": "このターンは完了しました。", "turnCompletedWithUsage": "このターンは完了しました。下のトークン統計をご確認ください。" }, @@ -390,7 +390,25 @@ "addButton": "環境変数を追加", "resetButton": "既定に戻す", "saving": "保存中...", - "save": "保存" + "save": "保存", + "profilesTitle": "設定プロファイル", + "profilesDesc": "複数の環境変数プロファイルを作成して、AI設定をすばやく切り替えられます", + "addProfile": "新規プロファイル", + "profileNamePlaceholder": "プロファイル名(例: OpenAI / DeepSeek / Anthropic)", + "activateProfile": "有効化", + "activeProfile": "有効中", + "editProfile": "編集", + "deleteProfile": "削除", + "deleteProfileConfirm": "このプロファイルを削除しますか?", + "deactivateProfile": "無効化", + "profileSaved": "プロファイルを保存しました", + "profileActivated": "プロファイルを有効化しました", + "profileDeactivated": "デフォルト設定に戻しました", + "profileDeleted": "プロファイルを削除しました", + "editingProfile": "プロファイルを編集中:{name}", + "editingDefault": "デフォルト設定を編集", + "saveProfile": "プロファイルを保存", + "cancelEdit": "編集をキャンセル" }, "fileSearch": { "debugLabel": "デバッグ", @@ -797,5 +815,4 @@ "createFailedWithMessage": "作成失敗: {message}" } } -} - +} \ No newline at end of file diff --git a/WebCodeCli/Resources/Localization/ko-KR.json b/WebCodeCli/Resources/Localization/ko-KR.json index 2e3b069..7dd10d6 100644 --- a/WebCodeCli/Resources/Localization/ko-KR.json +++ b/WebCodeCli/Resources/Localization/ko-KR.json @@ -191,9 +191,9 @@ "event": "이벤트 ({type})" }, "content": { - "threadId": "스레드 ID: {id}", - "threadCreated": "새 대화 스레드를 만들었습니다.", - "turnStarted": "응답을 기다리는 중...", + "threadId": "스레드 ID: {id}", + "threadCreated": "새 대화 스레드를 만들었습니다.", + "turnStarted": "응답을 기다리는 중...", "turnCompleted": "이번 턴이 완료되었습니다.", "turnCompletedWithUsage": "이번 턴이 완료되었습니다. 아래 토큰 통계를 확인하세요." }, @@ -390,7 +390,25 @@ "addButton": "환경 변수 추가", "resetButton": "기본값으로 재설정", "saving": "저장 중...", - "save": "저장" + "save": "저장", + "profilesTitle": "구성 프로파일", + "profilesDesc": "여러 환경 변수 프로파일을 생성하여 AI 구성을 빠르게 전환할 수 있습니다", + "addProfile": "새 프로파일", + "profileNamePlaceholder": "프로파일 이름 (예: OpenAI / DeepSeek / Anthropic)", + "activateProfile": "활성화", + "activeProfile": "활성", + "editProfile": "편집", + "deleteProfile": "삭제", + "deleteProfileConfirm": "이 구성 프로파일을 삭제하시겠습니까?", + "deactivateProfile": "비활성화", + "profileSaved": "프로파일이 저장되었습니다", + "profileActivated": "프로파일이 활성화되었습니다", + "profileDeactivated": "기본 구성으로 전환되었습니다", + "profileDeleted": "프로파일이 삭제되었습니다", + "editingProfile": "프로파일 편집 중: {name}", + "editingDefault": "기본 구성 편집", + "saveProfile": "프로파일 저장", + "cancelEdit": "편집 취소" }, "fileSearch": { "debugLabel": "디버그", @@ -797,5 +815,4 @@ "createFailedWithMessage": "생성 실패: {message}" } } -} - +} \ No newline at end of file diff --git a/WebCodeCli/Resources/Localization/zh-CN.json b/WebCodeCli/Resources/Localization/zh-CN.json index 273c951..4214094 100644 --- a/WebCodeCli/Resources/Localization/zh-CN.json +++ b/WebCodeCli/Resources/Localization/zh-CN.json @@ -142,9 +142,9 @@ "event": "事件 ({type})" }, "content": { - "threadId": "线程标识: {id}", - "threadCreated": "已创建新的对话线程", - "turnStarted": "等待响应...", + "threadId": "线程标识: {id}", + "threadCreated": "已创建新的对话线程", + "turnStarted": "等待响应...", "turnCompleted": "本轮交互已完成。", "turnCompletedWithUsage": "本轮交互已完成,详见下方 token 统计。" }, @@ -343,7 +343,25 @@ "addButton": "添加环境变量", "resetButton": "重置为默认", "saving": "保存中...", - "save": "保存" + "save": "保存", + "profilesTitle": "配置方案", + "profilesDesc": "创建多套环境变量方案,一键切换不同的 AI 配置", + "addProfile": "新建方案", + "profileNamePlaceholder": "方案名称,如 OpenAI / DeepSeek / Anthropic", + "activateProfile": "激活", + "activeProfile": "当前激活", + "editProfile": "编辑", + "deleteProfile": "删除", + "deleteProfileConfirm": "确认删除此配置方案?", + "deactivateProfile": "取消激活", + "profileSaved": "方案已保存", + "profileActivated": "方案已激活", + "profileDeactivated": "已切换回默认配置", + "profileDeleted": "方案已删除", + "editingProfile": "正在编辑方案:{name}", + "editingDefault": "编辑默认配置", + "saveProfile": "保存方案", + "cancelEdit": "取消编辑" }, "contextPreview": { "title": "上下文预览", @@ -471,7 +489,9 @@ "workspaceRootDesc": "用于存放会话工作文件的目录。Docker 部署时建议使用默认值 {path}", "configTipTitle": "配置说明", "claudeConfigDesc": "配置 Claude Code 所需的环境变量。如果暂时不使用,可以跳过此步骤,稍后在设置中配置。", - "codexConfigDesc": "配置 Codex (OpenAI) 所需的环境变量。如果暂时不使用,可以跳过此步骤,稍后在设置中配置。", "codexDocsLink": "查看 Codex 配置文档", "opencodeConfigDesc": "配置 OpenCode CLI 所需的环境变量。如果暂时不使用,可以跳过此步骤,稍后在设置中配置。", + "codexConfigDesc": "配置 Codex (OpenAI) 所需的环境变量。如果暂时不使用,可以跳过此步骤,稍后在设置中配置。", + "codexDocsLink": "查看 Codex 配置文档", + "opencodeConfigDesc": "配置 OpenCode CLI 所需的环境变量。如果暂时不使用,可以跳过此步骤,稍后在设置中配置。", "opencodeDocsLink": "查看 OpenCode 环境变量文档", "envVarNameLabel": "环境变量名", "envVarValueLabel": "值", @@ -797,5 +817,4 @@ "createFailedWithMessage": "创建失败: {message}" } } -} - +} \ No newline at end of file diff --git a/WebCodeCli/wwwroot/Resources/Localization/en-US.json b/WebCodeCli/wwwroot/Resources/Localization/en-US.json index 342338f..8a090de 100644 --- a/WebCodeCli/wwwroot/Resources/Localization/en-US.json +++ b/WebCodeCli/wwwroot/Resources/Localization/en-US.json @@ -142,9 +142,9 @@ "event": "Event ({type})" }, "content": { - "threadId": "Thread ID: {id}", - "threadCreated": "A new conversation thread has been created.", - "turnStarted": "Waiting for response...", + "threadId": "Thread ID: {id}", + "threadCreated": "A new conversation thread has been created.", + "turnStarted": "Waiting for response...", "turnCompleted": "This turn has completed.", "turnCompletedWithUsage": "This turn has completed. See token usage below." }, @@ -343,7 +343,25 @@ "addButton": "Add Environment Variable", "resetButton": "Reset to Default", "saving": "Saving...", - "save": "Save" + "save": "Save", + "profilesTitle": "Configuration Profiles", + "profilesDesc": "Create multiple environment variable profiles to quickly switch between AI configurations", + "addProfile": "New Profile", + "profileNamePlaceholder": "Profile name, e.g. OpenAI / DeepSeek / Anthropic", + "activateProfile": "Activate", + "activeProfile": "Active", + "editProfile": "Edit", + "deleteProfile": "Delete", + "deleteProfileConfirm": "Delete this configuration profile?", + "deactivateProfile": "Deactivate", + "profileSaved": "Profile saved", + "profileActivated": "Profile activated", + "profileDeactivated": "Switched back to default config", + "profileDeleted": "Profile deleted", + "editingProfile": "Editing profile: {name}", + "editingDefault": "Editing default config", + "saveProfile": "Save Profile", + "cancelEdit": "Cancel Edit" }, "contextPreview": { "title": "Context Preview", @@ -799,5 +817,4 @@ "createFailedWithMessage": "Create failed: {message}" } } -} - +} \ No newline at end of file diff --git a/WebCodeCli/wwwroot/Resources/Localization/ja-JP.json b/WebCodeCli/wwwroot/Resources/Localization/ja-JP.json index 3e914f7..ab26afd 100644 --- a/WebCodeCli/wwwroot/Resources/Localization/ja-JP.json +++ b/WebCodeCli/wwwroot/Resources/Localization/ja-JP.json @@ -191,9 +191,9 @@ "event": "イベント({type})" }, "content": { - "threadId": "スレッドID: {id}", - "threadCreated": "新しい会話スレッドを作成しました。", - "turnStarted": "応答待ち...", + "threadId": "スレッドID: {id}", + "threadCreated": "新しい会話スレッドを作成しました。", + "turnStarted": "応答待ち...", "turnCompleted": "このターンは完了しました。", "turnCompletedWithUsage": "このターンは完了しました。下のトークン統計をご確認ください。" }, @@ -390,7 +390,25 @@ "addButton": "環境変数を追加", "resetButton": "既定に戻す", "saving": "保存中...", - "save": "保存" + "save": "保存", + "profilesTitle": "設定プロファイル", + "profilesDesc": "複数の環境変数プロファイルを作成して、AI設定をすばやく切り替えられます", + "addProfile": "新規プロファイル", + "profileNamePlaceholder": "プロファイル名(例: OpenAI / DeepSeek / Anthropic)", + "activateProfile": "有効化", + "activeProfile": "有効中", + "editProfile": "編集", + "deleteProfile": "削除", + "deleteProfileConfirm": "このプロファイルを削除しますか?", + "deactivateProfile": "無効化", + "profileSaved": "プロファイルを保存しました", + "profileActivated": "プロファイルを有効化しました", + "profileDeactivated": "デフォルト設定に戻しました", + "profileDeleted": "プロファイルを削除しました", + "editingProfile": "プロファイルを編集中:{name}", + "editingDefault": "デフォルト設定を編集", + "saveProfile": "プロファイルを保存", + "cancelEdit": "編集をキャンセル" }, "fileSearch": { "debugLabel": "デバッグ", @@ -797,5 +815,4 @@ "createFailedWithMessage": "作成失敗: {message}" } } -} - +} \ No newline at end of file diff --git a/WebCodeCli/wwwroot/Resources/Localization/ko-KR.json b/WebCodeCli/wwwroot/Resources/Localization/ko-KR.json index 2e3b069..7dd10d6 100644 --- a/WebCodeCli/wwwroot/Resources/Localization/ko-KR.json +++ b/WebCodeCli/wwwroot/Resources/Localization/ko-KR.json @@ -191,9 +191,9 @@ "event": "이벤트 ({type})" }, "content": { - "threadId": "스레드 ID: {id}", - "threadCreated": "새 대화 스레드를 만들었습니다.", - "turnStarted": "응답을 기다리는 중...", + "threadId": "스레드 ID: {id}", + "threadCreated": "새 대화 스레드를 만들었습니다.", + "turnStarted": "응답을 기다리는 중...", "turnCompleted": "이번 턴이 완료되었습니다.", "turnCompletedWithUsage": "이번 턴이 완료되었습니다. 아래 토큰 통계를 확인하세요." }, @@ -390,7 +390,25 @@ "addButton": "환경 변수 추가", "resetButton": "기본값으로 재설정", "saving": "저장 중...", - "save": "저장" + "save": "저장", + "profilesTitle": "구성 프로파일", + "profilesDesc": "여러 환경 변수 프로파일을 생성하여 AI 구성을 빠르게 전환할 수 있습니다", + "addProfile": "새 프로파일", + "profileNamePlaceholder": "프로파일 이름 (예: OpenAI / DeepSeek / Anthropic)", + "activateProfile": "활성화", + "activeProfile": "활성", + "editProfile": "편집", + "deleteProfile": "삭제", + "deleteProfileConfirm": "이 구성 프로파일을 삭제하시겠습니까?", + "deactivateProfile": "비활성화", + "profileSaved": "프로파일이 저장되었습니다", + "profileActivated": "프로파일이 활성화되었습니다", + "profileDeactivated": "기본 구성으로 전환되었습니다", + "profileDeleted": "프로파일이 삭제되었습니다", + "editingProfile": "프로파일 편집 중: {name}", + "editingDefault": "기본 구성 편집", + "saveProfile": "프로파일 저장", + "cancelEdit": "편집 취소" }, "fileSearch": { "debugLabel": "디버그", @@ -797,5 +815,4 @@ "createFailedWithMessage": "생성 실패: {message}" } } -} - +} \ No newline at end of file diff --git a/WebCodeCli/wwwroot/Resources/Localization/zh-CN.json b/WebCodeCli/wwwroot/Resources/Localization/zh-CN.json index 273c951..4214094 100644 --- a/WebCodeCli/wwwroot/Resources/Localization/zh-CN.json +++ b/WebCodeCli/wwwroot/Resources/Localization/zh-CN.json @@ -142,9 +142,9 @@ "event": "事件 ({type})" }, "content": { - "threadId": "线程标识: {id}", - "threadCreated": "已创建新的对话线程", - "turnStarted": "等待响应...", + "threadId": "线程标识: {id}", + "threadCreated": "已创建新的对话线程", + "turnStarted": "等待响应...", "turnCompleted": "本轮交互已完成。", "turnCompletedWithUsage": "本轮交互已完成,详见下方 token 统计。" }, @@ -343,7 +343,25 @@ "addButton": "添加环境变量", "resetButton": "重置为默认", "saving": "保存中...", - "save": "保存" + "save": "保存", + "profilesTitle": "配置方案", + "profilesDesc": "创建多套环境变量方案,一键切换不同的 AI 配置", + "addProfile": "新建方案", + "profileNamePlaceholder": "方案名称,如 OpenAI / DeepSeek / Anthropic", + "activateProfile": "激活", + "activeProfile": "当前激活", + "editProfile": "编辑", + "deleteProfile": "删除", + "deleteProfileConfirm": "确认删除此配置方案?", + "deactivateProfile": "取消激活", + "profileSaved": "方案已保存", + "profileActivated": "方案已激活", + "profileDeactivated": "已切换回默认配置", + "profileDeleted": "方案已删除", + "editingProfile": "正在编辑方案:{name}", + "editingDefault": "编辑默认配置", + "saveProfile": "保存方案", + "cancelEdit": "取消编辑" }, "contextPreview": { "title": "上下文预览", @@ -471,7 +489,9 @@ "workspaceRootDesc": "用于存放会话工作文件的目录。Docker 部署时建议使用默认值 {path}", "configTipTitle": "配置说明", "claudeConfigDesc": "配置 Claude Code 所需的环境变量。如果暂时不使用,可以跳过此步骤,稍后在设置中配置。", - "codexConfigDesc": "配置 Codex (OpenAI) 所需的环境变量。如果暂时不使用,可以跳过此步骤,稍后在设置中配置。", "codexDocsLink": "查看 Codex 配置文档", "opencodeConfigDesc": "配置 OpenCode CLI 所需的环境变量。如果暂时不使用,可以跳过此步骤,稍后在设置中配置。", + "codexConfigDesc": "配置 Codex (OpenAI) 所需的环境变量。如果暂时不使用,可以跳过此步骤,稍后在设置中配置。", + "codexDocsLink": "查看 Codex 配置文档", + "opencodeConfigDesc": "配置 OpenCode CLI 所需的环境变量。如果暂时不使用,可以跳过此步骤,稍后在设置中配置。", "opencodeDocsLink": "查看 OpenCode 环境变量文档", "envVarNameLabel": "环境变量名", "envVarValueLabel": "值", @@ -797,5 +817,4 @@ "createFailedWithMessage": "创建失败: {message}" } } -} - +} \ No newline at end of file From 696f9cee53885be334b514c47a796e0ebcf18a4c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 5 Mar 2026 04:47:53 +0000 Subject: [PATCH 3/3] Fix delete confirmation and enable profile rename during edit Co-authored-by: xuzeyu91 <26290929+xuzeyu91@users.noreply.github.com> --- .../EnvironmentVariableConfigModal.razor | 125 ++++++++++++------ 1 file changed, 81 insertions(+), 44 deletions(-) diff --git a/WebCodeCli/Components/EnvironmentVariableConfigModal.razor b/WebCodeCli/Components/EnvironmentVariableConfigModal.razor index cc9e988..b14106c 100644 --- a/WebCodeCli/Components/EnvironmentVariableConfigModal.razor +++ b/WebCodeCli/Components/EnvironmentVariableConfigModal.razor @@ -77,50 +77,71 @@ @foreach (var profile in _profiles) { - - @if (profile.IsActive) - { - - - - - @T("envConfig.activeProfile") - - } - @profile.ProfileName - - @if (!profile.IsActive) + @if (_profilePendingDelete?.Id == profile.Id) + { + + + + + + @T("envConfig.deleteProfileConfirm") + DeleteProfile(profile)" + class="px-2 py-1 text-xs font-semibold text-white bg-red-600 rounded-md hover:bg-red-700 transition-all"> + @T("common.confirm") + + + @T("common.cancel") + + + } + else + { + + @if (profile.IsActive) { - ActivateProfile(profile)" - class="px-2 py-1 text-xs font-semibold text-green-700 bg-green-50 border border-green-200 rounded-md hover:bg-green-100 transition-all" - title="@T("envConfig.activateProfile")"> - @T("envConfig.activateProfile") - + + + + + @T("envConfig.activeProfile") + } - else - { - - @T("envConfig.deactivateProfile") + @profile.ProfileName + + @if (!profile.IsActive) + { + ActivateProfile(profile)" + class="px-2 py-1 text-xs font-semibold text-green-700 bg-green-50 border border-green-200 rounded-md hover:bg-green-100 transition-all" + title="@T("envConfig.activateProfile")"> + @T("envConfig.activateProfile") + + } + else + { + + @T("envConfig.deactivateProfile") + + } + EditProfile(profile)" + class="p-1.5 text-blue-600 hover:bg-blue-50 rounded-md transition-all" + title="@T("envConfig.editProfile")"> + + + - } - EditProfile(profile)" - class="p-1.5 text-blue-600 hover:bg-blue-50 rounded-md transition-all" - title="@T("envConfig.editProfile")"> - - - - - DeleteProfile(profile)" - class="p-1.5 text-red-500 hover:bg-red-50 rounded-md transition-all" - title="@T("envConfig.deleteProfile")"> - - - - + RequestDeleteProfile(profile)" + class="p-1.5 text-red-500 hover:bg-red-50 rounded-md transition-all" + title="@T("envConfig.deleteProfile")"> + + + + + - + } } } @@ -151,8 +172,8 @@ } - - @if (_isAddingProfile) + + @if (_isAddingProfile || _editingProfile != null) { @T("envConfig.profilesTitle") @@ -281,6 +302,7 @@ private CliToolEnvProfile? _editingProfile = null; private bool _isAddingProfile = false; private string _newProfileName = string.Empty; + private CliToolEnvProfile? _profilePendingDelete = null; // 本地化相关 private Dictionary _translations = new(); @@ -347,6 +369,7 @@ _editingProfile = null; _isAddingProfile = false; _newProfileName = string.Empty; + _profilePendingDelete = null; StateHasChanged(); } @@ -420,6 +443,7 @@ _editingProfile = null; _isAddingProfile = false; _newProfileName = string.Empty; + _profilePendingDelete = null; _envVars = new List(); StateHasChanged(); } @@ -468,9 +492,22 @@ } } + private void RequestDeleteProfile(CliToolEnvProfile profile) + { + _profilePendingDelete = profile; + StateHasChanged(); + } + + private void CancelDeleteProfile() + { + _profilePendingDelete = null; + StateHasChanged(); + } + private async Task DeleteProfile(CliToolEnvProfile profile) { if (_selectedTool == null) return; + _profilePendingDelete = null; _isLoading = true; StateHasChanged(); try @@ -510,8 +547,8 @@ if (_isAddingProfile || _editingProfile != null) { - // 保存配置方案 - var profileName = _isAddingProfile ? _newProfileName.Trim() : (_editingProfile?.ProfileName ?? string.Empty); + // 保存配置方案(新建和编辑均使用 _newProfileName,支持重命名) + var profileName = _newProfileName.Trim(); if (string.IsNullOrWhiteSpace(profileName)) profileName = "未命名方案"; var profileId = _editingProfile?.Id ?? 0;
@T("envConfig.profilesTitle")
@T("envConfig.profilesDesc")
+ @if (_editingProfile != null) + { + @T("envConfig.editingProfile", ("name", _editingProfile.ProfileName)) + } + else if (_isAddingProfile) + { + @T("envConfig.addProfile") + } + else + { + @T("envConfig.editingDefault") + } +