Files
AI-Studio/app/MindWork AI Studio/Assistants/ModelComparison/AssistantModelComparisonTasks.razor.cs
T

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);
}