Skip to content

Add multi-AI provider profile switching for environment variable configuration#72

Open
Copilot wants to merge 3 commits intomainfrom
copilot/add-multi-ai-environment-switch
Open

Add multi-AI provider profile switching for environment variable configuration#72
Copilot wants to merge 3 commits intomainfrom
copilot/add-multi-ai-environment-switch

Conversation

Copy link
Contributor

Copilot AI commented Mar 3, 2026

  • Create CliToolEnvProfile model (database entity with: Id, ToolId, ProfileName, IsActive, EnvVarsJson)
  • Create ICliToolEnvProfileRepository interface and CliToolEnvProfileRepository implementation
  • Add profile management methods to ICliToolEnvironmentService and CliToolEnvironmentService
  • Update GetEnvironmentVariablesAsync to check active profile first, then fall back to default DB config, then appsettings
  • Update EnvironmentVariableConfigModal.razor with profile management UI
  • Add localization strings for the new profile UI in all 4 language files (zh-CN, en-US, ja-JP, ko-KR)
  • Fix: Add delete confirmation step (two-step: click delete → inline confirm/cancel row before actual deletion)
  • Fix: Enable profile rename during edit (name input now shown for both add and edit modes; _newProfileName used for save in both cases)
Original prompt

This section details on the original issue you should resolve

<issue_title>环境变量配置可以添加多种AI的,可以自由切换</issue_title>
<issue_description>Image
如果某个AI限额了可以根据配置的环境变量快速切换AI</issue_description>

<agent_instructions>完成这个需求。可以有多套环境变量 进行切换</agent_instructions>

Comments on the Issue (you are @copilot in this section)


💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

Co-authored-by: xuzeyu91 <26290929+xuzeyu91@users.noreply.github.com>
Copilot AI changed the title [WIP] Add support for multiple AI environment variable configurations Add multi-AI provider profile switching for environment variable configuration Mar 3, 2026
@xuzeyu91
Copy link
Collaborator

xuzeyu91 commented Mar 5, 2026

@copilot 评审一下

@xuzeyu91 xuzeyu91 marked this pull request as ready for review March 5, 2026 04:47
Copilot AI review requested due to automatic review settings March 5, 2026 04:47
Co-authored-by: xuzeyu91 <26290929+xuzeyu91@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Mar 5, 2026

@copilot 评审一下

代码评审完成,发现两个问题并已修复(commit 696f9ce):

问题1:删除无确认步骤
原代码点击删除图标会立即删除方案,没有二次确认。现已改为两步操作:点击删除 → 行内展示确认/取消按钮(红色警告样式),防止误删。

问题2:编辑已有方案时无法重命名
原代码方案名称输入框仅在新建时显示,编辑时用 _editingProfile.ProfileName(旧名称)保存。现已将名称输入框同时显示于编辑模式,并统一使用 _newProfileName 保存,支持编辑时重命名。

UI 改进效果(删除确认行):

┌─────────────────────────────────────────────────────┐
│ ⚠ 确认删除此配置方案?   [确认]  [取消]              │  ← 红色背景确认行
└─────────────────────────────────────────────────────┘

方案名称输入框(新建和编辑均显示):

┌─────────────────────────────────────────────────────┐
│ 配置方案                                              │
│ [OpenAI (可修改方案名)                             ] │
└─────────────────────────────────────────────────────┘

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds support for switching between multiple AI provider environment-variable configurations per CLI tool by introducing named “profiles”, with UI controls to manage/activate them and localization updates to support the new UX.

Changes:

  • Added CliToolEnvProfile model + repository to persist per-tool named env-var profiles and activate/deactivate them.
  • Updated CliToolEnvironmentService to resolve env vars by priority: active profile → DB default → appsettings, and exposed profile CRUD APIs.
  • Updated EnvironmentVariableConfigModal to manage profiles (create/edit/activate/deactivate/delete) and added new localization keys.

Reviewed changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated 14 comments.

Show a summary per file
File Description
WebCodeCli.Domain/Domain/Model/CliToolEnvProfile.cs New SqlSugar entity for storing per-tool env-var profiles.
WebCodeCli.Domain/Repositories/Base/CliToolEnv/ICliToolEnvProfileRepository.cs Repository interface for profile queries and activation/deactivation.
WebCodeCli.Domain/Repositories/Base/CliToolEnv/CliToolEnvProfileRepository.cs Repository implementation for profile persistence and activation logic.
WebCodeCli.Domain/Domain/Service/CliToolEnvironmentService.cs Adds profile APIs and updates env-var resolution priority.
WebCodeCli/Components/EnvironmentVariableConfigModal.razor UI for profile management and contextual editing of default vs profile configs.
WebCodeCli/Resources/Localization/zh-CN.json Adds profile-management localization keys (source-of-truth).
WebCodeCli/Resources/Localization/en-US.json Adds profile-management localization keys (source-of-truth).
WebCodeCli/Resources/Localization/ja-JP.json Adds profile-management localization keys (source-of-truth).
WebCodeCli/Resources/Localization/ko-KR.json Adds profile-management localization keys (source-of-truth).
WebCodeCli/wwwroot/Resources/Localization/zh-CN.json Same localization keys under wwwroot (appears to be build output).
WebCodeCli/wwwroot/Resources/Localization/en-US.json Same localization keys under wwwroot (appears to be build output).
WebCodeCli/wwwroot/Resources/Localization/ja-JP.json Same localization keys under wwwroot (appears to be build output).
WebCodeCli/wwwroot/Resources/Localization/ko-KR.json Same localization keys under wwwroot (appears to be build output).

Comment on lines +410 to 413
"saveProfile": "프로파일 저장",
"cancelEdit": "편집 취소"
},
"fileSearch": {
Copy link

Copilot AI Mar 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These localization files under wwwroot/Resources/Localization look like build outputs. The project copies Resources/Localization/*.json into wwwroot/Resources/Localization at build time (see WebCodeCli.csproj CopyLocalizationFiles), so committing edits here will either be overwritten or create drift. Suggest removing wwwroot/Resources/Localization/*.json changes from the PR and keeping WebCodeCli/Resources/Localization/*.json as the single source of truth.

Suggested change
"saveProfile": "프로파일 저장",
"cancelEdit": "편집 취소"
},
"fileSearch": {
"saveProfile": "프로파일 저장"
},
"fileSearch": {
"fileSearch": {

Copilot uses AI. Check for mistakes.
Comment on lines +454 to +458
_isLoading = true;
StateHasChanged();
try
{
await CliToolEnvironmentService.ActivateProfileAsync(_selectedTool.Id, profile.Id);
Copy link

Copilot AI Mar 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DeactivateProfiles() refreshes _profiles but doesn’t reload the default env vars into the editor. After deactivation the effective config falls back to default, but the editor may remain empty until the modal is reopened. Suggest calling GetEnvironmentVariablesAsync (or ResetToDefaultAsync depending on intent) to repopulate _envVars after successful deactivation.

Copilot uses AI. Check for mistakes.
Comment on lines +100 to +104
_logger.LogInformation("从激活方案 [{ProfileName}] 加载工具 {ToolId} 的环境变量配置", activeProfile.ProfileName, toolId);
var profileVars = JsonSerializer.Deserialize<Dictionary<string, string>>(activeProfile.EnvVarsJson) ?? new();
return profileVars
.Where(kvp => !string.IsNullOrWhiteSpace(kvp.Value))
.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
Copy link

Copilot AI Mar 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If activeProfile.EnvVarsJson is invalid JSON, JsonSerializer.Deserialize will throw and the outer catch returns an empty dictionary, skipping the intended fallback to DB default/appsettings. Suggest catching JsonException around the profile deserialization (log + ignore profile) and then continuing to the DB/appsettings resolution path.

Suggested change
_logger.LogInformation("从激活方案 [{ProfileName}] 加载工具 {ToolId} 的环境变量配置", activeProfile.ProfileName, toolId);
var profileVars = JsonSerializer.Deserialize<Dictionary<string, string>>(activeProfile.EnvVarsJson) ?? new();
return profileVars
.Where(kvp => !string.IsNullOrWhiteSpace(kvp.Value))
.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
try
{
_logger.LogInformation("从激活方案 [{ProfileName}] 加载工具 {ToolId} 的环境变量配置", activeProfile.ProfileName, toolId);
var profileVars = JsonSerializer.Deserialize<Dictionary<string, string>>(activeProfile.EnvVarsJson) ?? new();
return profileVars
.Where(kvp => !string.IsNullOrWhiteSpace(kvp.Value))
.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
}
catch (JsonException jsonEx)
{
_logger.LogWarning(jsonEx,
"激活方案 [{ProfileName}] 的环境变量 JSON 无效,忽略该方案并回退到数据库和配置文件。ToolId: {ToolId}",
activeProfile.ProfileName,
toolId);
}

Copilot uses AI. Check for mistakes.
Comment on lines +115 to +119
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")
</button>
}
Copy link

Copilot AI Mar 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Delete action doesn’t prompt for confirmation even though envConfig.deleteProfileConfirm is added to localization. Consider asking for confirmation (e.g., via JS confirm or an existing modal pattern) before calling DeleteProfile(profile) to avoid accidental profile deletion.

Copilot uses AI. Check for mistakes.
Comment on lines +363 to +364
"saveProfile": "Save Profile",
"cancelEdit": "Cancel Edit"
Copy link

Copilot AI Mar 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These localization files under wwwroot/Resources/Localization look like build outputs. The project copies Resources/Localization/*.json into wwwroot/Resources/Localization at build time (see WebCodeCli.csproj CopyLocalizationFiles), so committing edits here will either be overwritten or create drift. Suggest removing wwwroot/Resources/Localization/*.json changes from the PR and keeping WebCodeCli/Resources/Localization/*.json as the single source of truth.

Suggested change
"saveProfile": "Save Profile",
"cancelEdit": "Cancel Edit"
"saveProfile": "Save Profile"

Copilot uses AI. Check for mistakes.
Comment on lines +109 to +119
/// 删除指定方案
/// </summary>
public async Task<bool> DeleteProfileAsync(int profileId)
{
try
{
return await DeleteAsync(x => x.Id == profileId);
}
catch (Exception ex)
{
_logger.LogError(ex, "删除方案 {ProfileId} 失败", profileId);
Copy link

Copilot AI Mar 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DeleteProfileAsync(int profileId) deletes by ID only. Since higher-level APIs are tool-scoped, consider deleting with an additional ToolId predicate (or first loading and validating ownership) to avoid cross-tool deletion if a wrong ID is ever passed.

Suggested change
/// 删除指定方案
/// </summary>
public async Task<bool> DeleteProfileAsync(int profileId)
{
try
{
return await DeleteAsync(x => x.Id == profileId);
}
catch (Exception ex)
{
_logger.LogError(ex, "删除方案 {ProfileId} 失败", profileId);
/// 删除指定工具下的指定方案
/// </summary>
public async Task<bool> DeleteProfileAsync(string toolId, int profileId)
{
try
{
return await DeleteAsync(x => x.Id == profileId && x.ToolId == toolId);
}
catch (Exception ex)
{
_logger.LogError(ex, "删除工具 {ToolId} 的方案 {ProfileId} 失败", toolId, profileId);

Copilot uses AI. Check for mistakes.
Comment on lines +420 to +424
_newProfileName = profile.ProfileName;

try
{
if (!string.IsNullOrWhiteSpace(profile.EnvVarsJson))
Copy link

Copilot AI Mar 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CancelProfileEdit() clears _envVars, so after canceling (or after saving a profile, since Save() calls CancelProfileEdit()), the UI falls back to “editing default config” but shows an empty env-var list even when a default config exists. Suggest reloading the default env vars when exiting profile edit (and only clearing when a profile is active, matching ShowAsync).

Copilot uses AI. Check for mistakes.
Comment on lines +410 to +411
"saveProfile": "プロファイルを保存",
"cancelEdit": "編集をキャンセル"
Copy link

Copilot AI Mar 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These localization files under wwwroot/Resources/Localization look like build outputs. The project copies Resources/Localization/*.json into wwwroot/Resources/Localization at build time (see WebCodeCli.csproj CopyLocalizationFiles), so committing edits here will either be overwritten or create drift. Suggest removing wwwroot/Resources/Localization/*.json changes from the PR and keeping WebCodeCli/Resources/Localization/*.json as the single source of truth.

Suggested change
"saveProfile": "プロファイルを保存",
"cancelEdit": "編集をキャンセル"
"saveProfile": "プロファイルを保存"

Copilot uses AI. Check for mistakes.
Comment on lines +514 to +516
{
await CliToolEnvironmentService.DeleteProfileAsync(_selectedTool.Id, profile.Id);
_profiles = await CliToolEnvironmentService.GetProfilesAsync(_selectedTool.Id);
Copy link

Copilot AI Mar 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fallback profile name "未命名方案" is hard-coded and not localized. Suggest either requiring a non-empty profile name (validation + disable save) or using a localized string for the default name so all languages behave consistently.

Copilot uses AI. Check for mistakes.
{
try
{
var result = await _profileRepository.DeleteProfileAsync(profileId);
Copy link

Copilot AI Mar 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DeleteProfileAsync(string toolId, int profileId) ignores toolId and deletes purely by profileId. To prevent accidental cross-tool deletion (and to make the toolId parameter meaningful), verify the profile belongs to toolId before deleting or push the (toolId, profileId) constraint into the repository delete query.

Suggested change
var result = await _profileRepository.DeleteProfileAsync(profileId);
var result = await _profileRepository.DeleteProfileAsync(toolId, profileId);

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

环境变量配置可以添加多种AI的,可以自由切换

3 participants