mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2026-11-14 22:08:27 +00:00
Added the Model Comparison assistant for blind comparisons of two models (#1009)
Co-authored-by: Thorsten Sommer <SommerEngineering@users.noreply.github.com>
This commit is contained in:
1 parent
12f26445ba
commit
b7255694cd
221 files changed
+19989
-90
No files matched your search
+211
@@ -0,0 +1,211 @@
|
||||
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);
|
||||
}
|
||||
Reference in new issue
Block a user