< Summary

Information
Class: UIBlazor.Components.SettingsDialog
Assembly: UIBlazor
File(s): /home/runner/work/InvAit/InvAit/UIBlazor/Components/SettingsDialog.razor
Tag: 14_22728831704
Line coverage
0%
Covered lines: 0
Uncovered lines: 129
Coverable lines: 129
Total lines: 411
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 75
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

File(s)

/home/runner/work/InvAit/InvAit/UIBlazor/Components/SettingsDialog.razor

#LineLine coverage
 1@using Shared.Contracts.Mcp
 2@inherits RadzenComponent
 3
 4<RadzenTabs RenderMode="TabRenderMode.Client" class="settings">
 5    <Tabs>
 6        <RadzenTabsItem Text="@SharedResource.SettingsGeneral">
 7            <RadzenStack Gap="1rem">
 8                <RadzenStack Orientation="Orientation.Horizontal" AlignItems="AlignItems.Center" Gap="0.5rem">
 09                    <RadzenText TextStyle="TextStyle.Subtitle1">@SharedResource.Profile:</RadzenText>
 10                    <RadzenDropDown Data="@ProfileManager.Current.Profiles" TextProperty="Name" ValueProperty="Id"
 11                                    @bind-Value="@ProfileManager.Current.ActiveProfileId"
 12                                    Style="flex: 1;" />
 13                    <RadzenButton Icon="add" ButtonStyle="ButtonStyle.Success" Variant="Variant.Flat" Click="@OnAddProfi
 14                    <RadzenButton Icon="delete" ButtonStyle="ButtonStyle.Danger" Variant="Variant.Flat" Click="@OnDelete
 15                                  Disabled="@(ProfileManager.Current.Profiles.Count <= 1)" />
 16                </RadzenStack>
 17
 18                <RadzenStack Gap="1rem">
 19                    <RadzenFormField Text="@SharedResource.ProfileName" Variant="Variant.Outlined">
 20                        <RadzenTextBox @bind-Value="@ProfileManager.ActiveProfile.Name" Immediate Style="width: 100%" />
 21                    </RadzenFormField>
 22
 23                    <RadzenFormField Text="@SharedResource.Provider" Variant="Variant.Outlined">
 24                        <RadzenDropDown @bind-Value="@ProfileManager.ActiveProfile.Provider" Data="@_providers" Style="w
 25                    </RadzenFormField>
 26
 27                    <RadzenFormField Text="@SharedResource.Endpoint" Variant="Variant.Outlined">
 28                        <RadzenTextBox @bind-Value="@ProfileManager.ActiveProfile.Endpoint" Style="width: 100%" />
 29                    </RadzenFormField>
 30
 31                    <RadzenFormField Text="ApiKey" Variant="Variant.Outlined">
 32                        <RadzenPassword @bind-Value="@ProfileManager.ActiveProfile.ApiKey" Style="width: 100%" />
 33                    </RadzenFormField>
 34
 35                    <RadzenStack Orientation="Orientation.Horizontal" Gap="0.5rem">
 36                        <RadzenDropDown TValue="string"
 37                                        Data="@ProfileManager.ActiveProfile.AvailableModels"
 38                                        @bind-Value="@ProfileManager.ActiveProfile.Model"
 39                                        Placeholder="@SharedResource.SelectModel"
 40                                        AllowCustomText="true"
 41                                        Style="width: 100%" />
 042                        <RadzenButton Icon="autorenew" Click="() => UpdateModelsAsync(CancellationToken.None)" ButtonSty
 43                    </RadzenStack>
 44
 45                    <CheckBox Text="@SharedResource.UseStream" @bind-Value=@ProfileManager.ActiveProfile.Stream />
 46                    <CheckBox Text="@SharedResource.SkipSSL" @bind-Value=@ProfileManager.ActiveProfile.SkipSSL />
 47
 48                    <RadzenStack Gap="0.2rem">
 49                        <RadzenLabel Text="@SharedResource.Temperature" />
 50                        <RadzenStack Orientation="Orientation.Horizontal" Gap="0.5rem">
 51                            <RadzenSlider @bind-Value="@ProfileManager.ActiveProfile.Temperature" Min="0" Max="2" Step="
 52                            <RadzenLabel Text="@Math.Abs(ProfileManager.ActiveProfile.Temperature).ToString("0.0")" Styl
 53                        </RadzenStack>
 54                    </RadzenStack>
 55
 56                    <RadzenStack Gap="0.2rem">
 57                        <RadzenLabel Text="@SharedResource.MaxTokens" />
 58                        <RadzenStack Orientation="Orientation.Horizontal" Gap="0.5rem">
 59                            <RadzenSlider @bind-Value="@ProfileManager.ActiveProfile.MaxTokens" Min=1000 Max=256000 Step
 60                            <RadzenLabel Text="@($"{ProfileManager.ActiveProfile.MaxTokens / 1000}K")" Style="text-align
 61                        </RadzenStack>
 62                    </RadzenStack>
 63
 64                    <RadzenFormField Text="@SharedResource.SystemPrompt" Variant="Variant.Outlined">
 65                        <RadzenTextArea @bind-Value="@ProfileManager.ActiveProfile.SystemPrompt" Style="min-height: 90px
 66                    </RadzenFormField>
 67                </RadzenStack>
 68
 69                <RadzenStack Gap="0.2rem">
 70                    <RadzenLabel Text="@SharedResource.MaxRetries" />
 71                    <RadzenStack Orientation="Orientation.Horizontal" Gap="0.5rem">
 72                        <RadzenSlider @bind-Value="@CommonSettingsProvider.Current.MaxRetries" Min="0" Max="50" Step="1"
 73                        <RadzenLabel Text="@CommonSettingsProvider.Current.MaxRetries.ToString()" Style="text-align: rig
 74                    </RadzenStack>
 75                </RadzenStack>
 76
 77                <CheckBox Text="@SharedResource.SendCurrentFile" @bind-Value="@CommonSettingsProvider.Current.SendCurren
 78                <CheckBox Text="@SharedResource.SendSolutionsStricture" @bind-Value="@CommonSettingsProvider.Current.Sen
 79            </RadzenStack>
 80        </RadzenTabsItem>
 81        <RadzenTabsItem Text="@SharedResource.SettingsTools">
 82            <RadzenStack Gap="1rem">
 083                @foreach (var category in _toolCategories)
 84                {
 085                    var state = ToolManager.Current.CategoryStates.ContainsKey(category)
 086                    ? ToolManager.Current.CategoryStates[category]
 087                    : new ToolCategorySettings();
 88
 89                    <RadzenStack Gap="0.5rem">
 90                        <RadzenStack Orientation="Orientation.Horizontal" Gap="1rem" AlignItems="AlignItems.Center" Just
 91                            <CheckBox Text="@GetCategoryName(category)" @bind-Value="@state.IsEnabled" @bind-Value:after
 92                            <ApprovalModeSelector Disabled="@(!state.IsEnabled)" @bind-Value="@state.ApprovalMode" @bind
 93                        </RadzenStack>
 94
 95                        <div class="@(state.IsEnabled ? "category-tools-visible" : "category-tools-hidden")">
 96                            @if (state.IsEnabled)
 97                            {
 098                                var categoryTools = ToolManager.GetAllTools().Where(t => t.Category == category).ToList(
 99                                if (categoryTools.Count > 1)
 100                                {
 101                                    <RadzenAccordion>
 102                                        <Items>
 103                                            <RadzenAccordionItem Text="@SharedResource.Tools" Icon="build">
 104                                                <RadzenStack Gap="0.5rem">
 0105                                                    @foreach (var tool in categoryTools)
 106                                                    {
 107                                                        <RadzenStack Orientation="Orientation.Vertical" JustifyContent="
 108                                                            <RadzenStack Orientation="Orientation.Horizontal" AlignItems
 0109                                                                <RadzenCheckBox @bind-Value="@tool.Enabled" @bind-Value:
 110                                                                <RadzenLabel Text="@tool.Name" Component="@($"tool_{tool
 111                                                            </RadzenStack>
 0112                                                            <RadzenText TextStyle="TextStyle.Caption" class="tool-descri
 113                                                        </RadzenStack>
 114                                                    }
 115                                                </RadzenStack>
 116                                            </RadzenAccordionItem>
 117                                        </Items>
 118                                    </RadzenAccordion>
 119                                }
 120                            }
 121                        </div>
 122                    </RadzenStack>
 123                }
 124            </RadzenStack>
 125        </RadzenTabsItem>
 126
 127        <RadzenTabsItem Text="MCP">
 128            <RadzenStack Gap="0.5rem">
 129                <RadzenStack Orientation="Orientation.Horizontal" AlignItems="AlignItems.Center" JustifyContent="Justify
 130                    <CheckBox Text="Enable MCP" @bind-Value="@McpSettingsProvider.Current.Enabled" @bind-Value:after="On
 131                    <RadzenStack Orientation="Orientation.Horizontal" Gap="0.3rem" JustifyContent="JustifyContent.End">
 132                        <RadzenButton Icon="edit_note" ButtonStyle="ButtonStyle.Info" Size="ButtonSize.Small" Click="@On
 133                        <RadzenButton Icon="refresh" ButtonStyle="ButtonStyle.Light" Size="ButtonSize.Small" Click="@OnR
 134                    </RadzenStack>
 135                </RadzenStack>
 136
 0137                @if (McpSettingsProvider.Current.Enabled)
 138                {
 0139                    @if (!McpSettingsProvider.Current.Servers.Any())
 140                    {
 141                        <RadzenText TextStyle="TextStyle.Caption" Style="color: var(--rz-text-secondary-color); font-sty
 0142                            @SharedResource.NoMCP
 143                        </RadzenText>
 144                    }
 145
 0146                    @foreach (var server in McpSettingsProvider.Current.Servers)
 147                    {
 0148                        var hasError = McpSettingsProvider.Current.ServerErrors.TryGetValue(server.Name, out var errorMs
 0149                        var borderColor = hasError ? "var(--rz-error-color)" : "var(--rz-border-color)";
 0150                        var serverStyle = $"padding: 0.5rem; border: 1px solid {borderColor}; border-radius: var(--rz-bo
 151
 152                        <RadzenStack Gap="0.5rem" Style="@serverStyle">
 153                            <RadzenStack Orientation="Orientation.Horizontal" AlignItems="AlignItems.Center" JustifyCont
 154                                <RadzenStack Orientation="Orientation.Horizontal" Gap="0.5rem" AlignItems="AlignItems.Ce
 155                                    <CheckBox Text="@server.Name" @bind-Value="@server.Enabled" @bind-Value:after="OnMcp
 156                                </RadzenStack>
 157                                <ApprovalModeSelector Value="@GetMcpServerApprovalMode(server)" Disabled="@(!server.Enab
 0158                                                      ValueChanged="@(mode => OnMcpServerApprovalChanged(server, mode))"
 159                            </RadzenStack>
 160
 0161                            @if (hasError && !string.IsNullOrEmpty(errorMsg))
 162                            {
 163                                <RadzenStack Style="padding: 0.5rem; background: var(--rz-error-background); border-radi
 0164                                    <strong>Error:</strong> @errorMsg
 165                                </RadzenStack>
 166                            }
 167
 0168                            @if (server.Enabled)
 169                            {
 170                                <RadzenStack Orientation="Orientation.Horizontal" Gap="0.5rem" Style="padding-left: 2rem
 171                                    <RadzenBadge Text="@server.Transport" Style="font-size: 0.7rem;" />
 0172                                    @if (hasError)
 173                                    {
 174                                        <RadzenBadge Text="@SharedResource.Error" Style="font-size: 0.7rem; background: 
 175                                    }
 0176                                    else if (server.Enabled && server.Tools.Any())
 177                                    {
 178                                        <RadzenBadge Text="@SharedResource.Ready" Style="font-size: 0.7rem; background: 
 179                                    }
 180                                </RadzenStack>
 181                                <RadzenStack Gap="0.2rem" Style="font-size: 0.8rem; color: var(--rz-text-secondary-color
 0182                                    @if (server.Transport == "stdio")
 183                                    {
 0184                                        <div>Command: @server.Command @(string.Join(" ", server.Args))</div>
 185                                    }
 186                                    else
 187                                    {
 0188                                        <div>URL: @server.Url</div>
 189                                    }
 0190                                    @if (server.Env.Any())
 191                                    {
 0192                                        <div>Env: @server.Env.Count variable(s)</div>
 193                                    }
 194                                </RadzenStack>
 195
 196                                <RadzenStack Orientation="Orientation.Horizontal" Gap="0.3rem" Style="padding-left: 2rem
 197                                    <RadzenButton Text="@SharedResource.RefreshTools" Icon="sync" Size="ButtonSize.Extra
 0198                                    Click="@(() => OnRefreshMcpTools(server))" IsBusy="@(_refreshingServerId == server.N
 199                                </RadzenStack>
 200
 0201                                @if (server.Tools.Any())
 202                                {
 203                                    <RadzenAccordion Style="margin-top: 0.3rem; border: none;">
 204                                        <Items>
 205                                            <RadzenAccordionItem Text="@SharedResource.Tools" Icon="build">
 206                                                <RadzenStack Gap="0.5rem">
 0207                                                    @foreach (var tool in server.Tools)
 208                                                    {
 209                                                        <RadzenStack Orientation="Orientation.Vertical" JustifyContent="
 210                                                            <RadzenStack Orientation="Orientation.Horizontal" Gap="0.5re
 211                                                                <RadzenCheckBox @bind-Value="@tool.Enabled" @bind-Value:
 212                                                                <RadzenLabel Text="@tool.Name" Component="@($"mcp__{serv
 213                                                            </RadzenStack>
 0214                                                            <RadzenText TextStyle="TextStyle.Caption" class="tool-descri
 215                                                        </RadzenStack>
 216                                                    }
 217                                                </RadzenStack>
 218                                            </RadzenAccordionItem>
 219                                        </Items>
 220                                    </RadzenAccordion>
 221                                }
 222                            }
 223                        </RadzenStack>
 224                    }
 225                }
 226            </RadzenStack>
 227        </RadzenTabsItem>
 228        <RadzenTabsItem Text="UI">
 229            <LanguageSelector />
 230        </RadzenTabsItem>
 231    </Tabs>
 232</RadzenTabs>
 233
 234@code
 235{
 0236    [Inject] ChatService ChatService { get; set; } = null!;
 0237    [Inject] NotificationService NotificationService { get; set; } = null!;
 0238    [Inject] IToolManager ToolManager { get; set; } = null!;
 0239    [Inject] IProfileManager ProfileManager { get; set; } = null!;
 0240    [Inject] ICommonSettingsProvider CommonSettingsProvider { get; set; } = null!;
 0241    [Inject] DialogService DialogService { get; set; } = null!;
 0242    [Inject] IMcpSettingsProvider McpSettingsProvider { get; set; } = null!;
 243
 0244    private readonly ToolCategory[] _toolCategories = [
 0245        ToolCategory.ReadFiles,
 0246        ToolCategory.WriteFiles,
 0247        ToolCategory.DeleteFiles,
 0248        ToolCategory.ModeSwitch,
 0249        ToolCategory.Execution
 0250    ];
 0251    private readonly string[] _providers = ["OpenAI Compatible"];
 252
 253    private string? _refreshingServerId;
 254
 255    private async Task OnMcpEnabledChanged()
 256    {
 0257        if (!McpSettingsProvider.Current.Enabled)
 258        {
 0259            await McpSettingsProvider.StopAllAsync();
 260        }
 0261        await McpSettingsProvider.SaveAsync();
 0262    }
 263
 264    private async Task OnOpenMcpSettings()
 265    {
 0266        await McpSettingsProvider.OpenSettingsFileAsync();
 0267    }
 268
 269    private async Task OnReloadMcpSettings()
 270    {
 0271        await McpSettingsProvider.LoadMcpFileAsync();
 0272        StateHasChanged();
 0273    }
 274
 275    private async Task OnRefreshMcpTools(McpServerConfig server)
 276    {
 0277        _refreshingServerId = server.Name;
 0278        StateHasChanged();
 279
 0280        var result = await McpSettingsProvider.RefreshToolsAsync(server);
 281
 0282        _refreshingServerId = null;
 283
 284        // Update error state
 0285        if (result.StartsWith("Success"))
 286        {
 0287            McpSettingsProvider.Current.ServerErrors.Remove(server.Name);
 288        }
 289        else
 290        {
 0291            McpSettingsProvider.Current.ServerErrors[server.Name] = result;
 292        }
 293
 0294        NotificationService.Notify(new NotificationMessage
 0295        {
 0296            Severity = result.StartsWith("Success") ? NotificationSeverity.Success : NotificationSeverity.Error,
 0297            Summary = result,
 0298            Duration = 4000
 0299        });
 300
 0301        await McpSettingsProvider.SaveAsync();
 0302        StateHasChanged();
 0303    }
 304
 305    private ToolApprovalMode GetMcpServerApprovalMode(McpServerConfig server)
 306    {
 0307        return McpSettingsProvider.Current.ServerApprovalModes.TryGetValue(server.Name, out var mode)
 0308            ? mode
 0309            : ToolApprovalMode.Allow;
 310    }
 311
 312    private async Task OnMcpServerApprovalChanged(McpServerConfig server, ToolApprovalMode mode)
 313    {
 0314        McpSettingsProvider.Current.ServerApprovalModes[server.Name] = mode;
 0315        await McpSettingsProvider.SaveAsync();
 0316    }
 317
 318    private async Task OnAddProfile()
 319    {
 0320        var newProfile = new ConnectionProfile();
 0321        ProfileManager.Current.Profiles.Add(newProfile);
 0322        await ProfileManager.ActivateProfileAsync(newProfile.Id, true);
 0323    }
 324
 325    private async Task OnDeleteProfile()
 326    {
 0327        if (string.IsNullOrEmpty(ProfileManager.Current.ActiveProfileId))
 328        {
 0329            return;
 330        }
 331
 0332        var confirm = await DialogService.Confirm($"{SharedResource.DeleteProfileQuestion} '{ProfileManager.ActiveProfil
 0333           new ConfirmOptions() { OkButtonText = SharedResource.Yes, CancelButtonText = SharedResource.No });
 334
 0335        if (confirm == true)
 336        {
 0337            await ProfileManager.DeleteProfileAsync(ProfileManager.Current.ActiveProfileId);
 338        }
 0339    }
 340
 341    private async Task UpdateModelsAsync(CancellationToken cancellationToken)
 342    {
 343        try
 344        {
 0345            var models = await ChatService.GetModelsAsync(cancellationToken);
 0346            var list = models.Data.Select(m => m.Id).ToList();
 347
 348            // TODO возможность добавлять кастомную строку
 0349            ProfileManager.ActiveProfile.AvailableModels = list;
 0350        }
 0351        catch (Exception ex)
 352        {
 0353            NotificationService.Notify(new NotificationMessage
 0354            {
 0355                Severity = NotificationSeverity.Error,
 0356                Summary = SharedResource.ErrorModelsNotFound,
 0357                Detail = ex.Message,
 0358                Duration = 10_000,
 0359                ShowProgress = true,
 0360            });
 0361        }
 0362        StateHasChanged();
 0363    }
 364
 365    private string GetCategoryName(ToolCategory category)
 366    {
 0367        return category switch
 0368        {
 0369            ToolCategory.ReadFiles => SharedResource.CategoryReadFiles,
 0370            ToolCategory.WriteFiles => SharedResource.CategoryWriteFiles,
 0371            ToolCategory.DeleteFiles => SharedResource.CategoryDeleteFiles,
 0372            ToolCategory.ModeSwitch => SharedResource.CategoryModeSwitch,
 0373            ToolCategory.Execution => SharedResource.CategoryExecution,
 0374            _ => category.ToString()
 0375        };
 376    }
 377
 378    private async Task OnCategorySettingChanged(ToolCategory category, ToolCategorySettings state)
 379    {
 0380        ToolManager.UpdateCategorySettings(category, state.IsEnabled, state.ApprovalMode);
 0381        await Task.CompletedTask;
 0382    }
 383
 384    private async Task OnToolSettingChanged(Tool tool)
 385    {
 0386        ToolManager.ToggleTool(tool.Name, tool.Enabled);
 0387        await Task.CompletedTask;
 0388    }
 389
 390    private async Task OnMcpToolSettingChanged()
 391    {
 392        // Update server enabled states
 0393        foreach (var server in McpSettingsProvider.Current.Servers)
 394        {
 0395            McpSettingsProvider.Current.ServerEnabledStates[server.Name] = server.Enabled;
 396        }
 397
 0398        McpSettingsProvider.Current.ToolDisabledStates.Clear();
 0399        foreach (var server in McpSettingsProvider.Current.Servers)
 400        {
 0401            foreach (var tool in server.Tools)
 402            {
 0403                var toolKey = $"{server.Name}:{tool.Name}";
 0404                if (!tool.Enabled)
 405                {
 0406                    McpSettingsProvider.Current.ToolDisabledStates.Add(toolKey);
 407                }
 408            }
 409        }
 0410    }
 411}