Skip to content

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

Merged
xuzeyu91 merged 5 commits intomainfrom
copilot/add-multi-ai-environment-switch
Mar 11, 2026
Merged

Add multi-AI provider profile switching for environment variable configuration#72
xuzeyu91 merged 5 commits intomainfrom
copilot/add-multi-ai-environment-switch

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Mar 3, 2026

  • Review current PR feedback and inspect remaining risks before merge
  • Verify current implementation against reviewer comments and current code
  • Check recent CI/workflow status and failure logs
  • Remove committed wwwroot/Resources/Localization/*.json build-output changes from the PR
  • Fix profile activation/delete safety in repository and service layers
  • Refresh modal editor state correctly after activate/deactivate/save/delete/cancel actions
  • Localize the fallback unnamed profile label in all 4 source localization files
  • Rebuild WebCodeCli/WebCodeCli.csproj successfully after changes
  • Run final code review and security scan
  • Reply on whether the PR is merge-ready, with screenshot reference
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
Copy Markdown
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
Copy Markdown
Contributor Author

Copilot AI commented Mar 5, 2026

@copilot 评审一下

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

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

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

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

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

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

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

Copy link
Copy Markdown

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.
@xuzeyu91
Copy link
Copy Markdown
Collaborator

@copilot 还有问题吗 可否合入

Copilot AI and others added 2 commits March 11, 2026 12:30
Co-authored-by: xuzeyu91 <26290929+xuzeyu91@users.noreply.github.com>
Co-authored-by: xuzeyu91 <26290929+xuzeyu91@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented Mar 11, 2026

@copilot 还有问题吗 可否合入

代码层面的阻塞我这边已经处理完了,最新提交是 8d7b3de

当前状态:

  • 本地 dotnet build WebCodeCli/WebCodeCli.csproj 通过
  • CodeQL 扫描结果:0 alerts
  • 之前评审里提到的几个实质问题已修复:激活/取消激活后的编辑区状态同步、无效 JSON 回退、按 toolId 约束删除、激活过程加事务与目标校验、wwwroot 本地化生成文件不再作为源修改

是否可合入:

  • 从代码角度看可以了
  • 建议等当前分支最新 CI 绿灯后再点合入。我查到最近列表里旧提交有成功记录,但最新 head 还没看到对应完成状态。

界面截图参考:
https://github.com/user-attachments/assets/f1f4300f-4be6-4ff1-a2b9-a3898d1b2afc

@xuzeyu91 xuzeyu91 merged commit 7a2d5a8 into main Mar 11, 2026
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