mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2026-11-14 20:28:27 +00:00
211 lines
8.8 KiB
C#
211 lines
8.8 KiB
C#
using AIStudio.Chat.Embedded;
|
|||
|
|
using AIStudio.Components;
|
||
|
|
using AIStudio.Dialogs;
|
||
|
|
using AIStudio.Dialogs.Settings;
|
||
|
|
|
||
|
|
using Microsoft.AspNetCore.Components;
|
||
|
|
|
||
|
|
using DialogOptions = AIStudio.Dialogs.DialogOptions;
|
||
|
|
|
||
|
|
namespace AIStudio.Assistants.ModelComparison;
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// The mode of the Model Comparison assistant in which the user works on real tasks with both models, one after the
|
||
|
|
/// other, and decides which chat helped more.
|
||
|
|
/// </summary>
|
||
|
|
/// <remarks>
|
||
|
|
/// The page shows one of two views: the list of the comparison series or an opened series. A new series comes from
|
||
|
|
/// ModelComparisonSeriesDialog. A series lives in ModelComparisonStore, so the user can continue it after days or
|
||
|
|
/// weeks.
|
||
|
|
/// </remarks>
|
||
|
|
public partial class AssistantModelComparisonTasks : MSGComponentBase
|
||
|
|
{
|
||
|
|
[Inject]
|
||
|
|
private NavigationManager NavigationManager { get; init; } = null!;
|
||
|
|
|
||
|
|
[Inject]
|
||
|
|
private ModelComparisonStore ModelComparisonStore { get; init; } = null!;
|
||
|
|
|
||
|
|
[Inject]
|
||
|
|
private EmbeddedChatService ChatService { get; init; } = null!;
|
||
|
|
|
||
|
|
[Inject]
|
||
|
|
private IDialogService DialogService { get; init; } = null!;
|
||
|
|
|
||
|
|
[Inject]
|
||
|
|
private ILogger<AssistantModelComparisonTasks> Logger { get; init; } = null!;
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// All comparison series, or null while they load.
|
||
|
|
/// </summary>
|
||
|
|
private IReadOnlyList<ModelComparisonSeries>? allSeries;
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// The series the user opened, or null while the list shows.
|
||
|
|
/// </summary>
|
||
|
|
private ModelComparisonSeries? openSeries;
|
||
|
|
|
||
|
|
#region Overrides of MSGComponentBase
|
||
|
|
|
||
|
|
protected override async Task OnInitializedAsync()
|
||
|
|
{
|
||
|
|
await base.OnInitializedAsync();
|
||
|
|
if (!ModelComparisonAccess.CanOpen(this.SettingsManager, T("Model Comparison")))
|
||
|
|
{
|
||
|
|
this.NavigationManager.NavigateTo(Routes.ASSISTANTS);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
await this.ReloadSeriesAsync();
|
||
|
|
}
|
||
|
|
|
||
|
|
#endregion
|
||
|
|
|
||
|
|
private async Task ReloadSeriesAsync() => this.allSeries = await this.ModelComparisonStore.ListSeriesAsync();
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// Describes a series in the list without naming its models: their names would end the blindness.
|
||
|
|
/// </summary>
|
||
|
|
private string DescribeSeries(ModelComparisonSeries series)
|
||
|
|
{
|
||
|
|
var culture = Tools.PluginSystem.I18N.I.Culture;
|
||
|
|
var decided = series.Tasks.Count(task => task.State is ModelComparisonSeriesTaskState.DONE).CompactCount();
|
||
|
|
var started = CommonTools.FormatTimestampToGeneral(series.CreatedAtUtc.ToLocalTime().DateTime, culture);
|
||
|
|
return series.IsResolved
|
||
|
|
? string.Format(culture, T("Started {0}, decided tasks: {1}, result shown"), started, decided)
|
||
|
|
: string.Format(culture, T("Started {0}, decided tasks: {1}"), started, decided);
|
||
|
|
}
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// Lets the user set up a new series in its dialog, and opens the series once the dialog stored it.
|
||
|
|
/// </summary>
|
||
|
|
/// <param name="basedOn">The series whose setup the new one takes over, or null for the preselected options.</param>
|
||
|
|
private async Task CreateSeriesAsync(ModelComparisonSeries? basedOn)
|
||
|
|
{
|
||
|
|
var parameters = new DialogParameters<ModelComparisonSeriesDialog>
|
||
|
|
{
|
||
|
|
{ dialog => dialog.BasedOn, basedOn }
|
||
|
|
};
|
||
|
|
|
||
|
|
var reference = await this.DialogService.ShowAsync<ModelComparisonSeriesDialog>(T("New comparison series"), parameters, DialogOptions.FULLSCREEN);
|
||
|
|
var result = await reference.Result;
|
||
|
|
if (result is null || result.Canceled || result.Data is not ModelComparisonSeries series)
|
||
|
|
return;
|
||
|
|
|
||
|
|
await this.ReloadSeriesAsync();
|
||
|
|
this.OpenSeries(series);
|
||
|
|
}
|
||
|
|
|
||
|
|
private void OpenSeries(ModelComparisonSeries series) => this.openSeries = series;
|
||
|
|
|
||
|
|
private async Task CloseSeries()
|
||
|
|
{
|
||
|
|
this.openSeries = null;
|
||
|
|
await this.ReloadSeriesAsync();
|
||
|
|
}
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// Asks for a new name of a series and stores it. The series is the one the list or the open view holds, so that
|
||
|
|
/// both show the new name right away.
|
||
|
|
/// </summary>
|
||
|
|
private async Task RenameSeriesAsync(ModelComparisonSeries series)
|
||
|
|
{
|
||
|
|
// The names of the other series, as stored now, so that the new name stays unique:
|
||
|
|
var takenNames = (await this.ModelComparisonStore.ListSeriesAsync()).Where(entry => entry.Id != series.Id).Select(entry => entry.Name).ToList();
|
||
|
|
var parameters = new DialogParameters<SingleInputDialog>
|
||
|
|
{
|
||
|
|
{ dialog => dialog.Message, TA("Enter a new name for this comparison series.") },
|
||
|
|
{ dialog => dialog.InputHeaderText, T("Name of the series") },
|
||
|
|
{ dialog => dialog.UserInput, series.Name },
|
||
|
|
{ dialog => dialog.ConfirmText, T("Rename") },
|
||
|
|
{ dialog => dialog.ConfirmColor, Color.Primary },
|
||
|
|
|
||
|
|
// The name rule rejects an empty name itself, in the same words as the dialog of a new series:
|
||
|
|
{ dialog => dialog.AllowEmptyInput, true },
|
||
|
|
{ dialog => dialog.AdditionalValidation, candidate => ModelComparisonNames.SeriesNameIssue(candidate, takenNames) },
|
||
|
|
};
|
||
|
|
|
||
|
|
var reference = await this.DialogService.ShowAsync<SingleInputDialog>(T("Rename series"), parameters, DialogOptions.FULLSCREEN);
|
||
|
|
var result = await reference.Result;
|
||
|
|
if (result is null || result.Canceled || result.Data is not string name || ModelComparisonNames.SeriesNameIssue(name, takenNames) is not null)
|
||
|
|
return;
|
||
|
|
|
||
|
|
var previousName = series.Name;
|
||
|
|
series.Name = name.Trim();
|
||
|
|
try
|
||
|
|
{
|
||
|
|
await this.ModelComparisonStore.SaveSeriesAsync(series);
|
||
|
|
}
|
||
|
|
catch (Exception exception) when (exception is IOException or UnauthorizedAccessException)
|
||
|
|
{
|
||
|
|
series.Name = previousName;
|
||
|
|
this.Logger.LogError(exception, "Could not rename a comparison series of the Model Comparison assistant.");
|
||
|
|
await this.MessageBus.SendError(new(AppIcons.SAVE_FAILED, TA("AI Studio could not rename the comparison series. Please try again.")));
|
||
|
|
}
|
||
|
|
|
||
|
|
if (this.openSeries is null)
|
||
|
|
await this.ReloadSeriesAsync();
|
||
|
|
}
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// Deletes a series with its chats after asking.
|
||
|
|
/// </summary>
|
||
|
|
private async Task DeleteSeriesAsync(ModelComparisonSeries series)
|
||
|
|
{
|
||
|
|
var parameters = new DialogParameters<ConfirmDialog>
|
||
|
|
{
|
||
|
|
{ dialog => dialog.Message, string.Format(TA("Are you sure you want to delete the comparison series '{0}' with all its chats? This cannot be undone."), series.Name) },
|
||
|
|
};
|
||
|
|
|
||
|
|
var reference = await this.DialogService.ShowAsync<ConfirmDialog>(T("Delete series"), parameters, DialogOptions.FULLSCREEN);
|
||
|
|
var result = await reference.Result;
|
||
|
|
if (result is null || result.Canceled)
|
||
|
|
return;
|
||
|
|
|
||
|
|
// An answer which still runs would otherwise try to store its chat into the deleted series:
|
||
|
|
foreach (var task in series.Tasks)
|
||
|
|
{
|
||
|
|
await this.ChatService.ForgetAsync(task.ChatOne.ChatId);
|
||
|
|
await this.ChatService.ForgetAsync(task.ChatTwo.ChatId);
|
||
|
|
}
|
||
|
|
|
||
|
|
try
|
||
|
|
{
|
||
|
|
await this.ModelComparisonStore.DeleteSeriesAsync(series.Id);
|
||
|
|
}
|
||
|
|
catch (Exception exception) when (exception is IOException or UnauthorizedAccessException)
|
||
|
|
{
|
||
|
|
this.Logger.LogError(exception, "Could not delete a comparison series of the Model Comparison assistant.");
|
||
|
|
await this.MessageBus.SendError(new(AppIcons.DELETE, TA("AI Studio could not delete the comparison series. Please try again.")));
|
||
|
|
}
|
||
|
|
|
||
|
|
await this.ReloadSeriesAsync();
|
||
|
|
}
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// Shows what all stored comparisons say about the models, the series whose result the user saw included.
|
||
|
|
/// </summary>
|
||
|
|
/// <remarks>
|
||
|
|
/// The user may delete all series there. Afterward, the list is read anew, and an open series which is gone
|
||
|
|
/// closes, which also stops its judge.
|
||
|
|
/// </remarks>
|
||
|
|
private async Task OpenResultsDialog()
|
||
|
|
{
|
||
|
|
var reference = await this.DialogService.ShowAsync<ModelComparisonResultsDialog>(T("Results of all comparisons"), DialogOptions.FULLSCREEN);
|
||
|
|
await reference.Result;
|
||
|
|
|
||
|
|
await this.ReloadSeriesAsync();
|
||
|
|
if (this.openSeries is { } series && this.allSeries!.All(entry => entry.Id != series.Id))
|
||
|
|
this.openSeries = null;
|
||
|
|
}
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// Goes back to the choice between the two modes.
|
||
|
|
/// </summary>
|
||
|
|
private void SwitchMode() => this.NavigationManager.NavigateTo(Routes.ASSISTANT_MODEL_COMPARISON);
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// Opens the options of the assistant, which both modes share, as the mode Answer Variance does through its base class.
|
||
|
|
/// </summary>
|
||
|
|
private async Task OpenSettingsDialog() => await this.DialogService.ShowAsync<SettingsDialogModelComparison>(null, new DialogParameters(), DialogOptions.FULLSCREEN);
|
||
|
|
}
|