Added the BeyondFacts Lab as a prototype (#1175)

This commit is contained in:
Thorsten Sommer authored and GitHub committed 2026-11-08 19:49:46 +01:00
1 parent db8dae1cf6
commit b6c3fbb5db
90 files changed
+5936 -209

No files matched your search

@@ -0,0 +1,127 @@
@attribute [Route(Routes.ASSISTANT_BEYOND_FACTS_LAB)]
@inherits MSGComponentBase
<CascadingValue Value="Components.BEYOND_FACTS_LAB_ASSISTANT">
@* The lab does not inherit AssistantBase, so it sets up the inner scrolling itself, the way the
Visual Briefing assistant does. *@
<div class="inner-scrolling-context">
<PreviewPrototype ApplyInnerScrollingFix="true"/>
<MudStack Row="true" AlignItems="AlignItems.Center" Class="mb-3 mr-3" StretchItems="StretchItems.Start">
<MudText Typo="Typo.h3">@T("BeyondFacts Lab")</MudText>
<MudSpacer/>
<MudIconButton Variant="Variant.Text" Icon="@Icons.Material.Filled.Settings" OnClick="@this.OpenSettingsDialogAsync"/>
</MudStack>
<InnerScrolling>
<MudText Typo="Typo.body1" Class="mb-3 mr-3">
@T("Prepare a challenging appearance as a researcher step by step, e.g., a panel discussion, an interview, or a hearing. Each preparation belongs to one event and has its own steps, chats, and work states.")
</MudText>
<MudText Typo="Typo.h5" Class="mb-1">@T("Your preparations")</MudText>
@if (this.projects.Count == 0)
{
<MudText Typo="Typo.body2" Class="mb-3">@T("You have no preparations yet. Create one for the event you want to prepare for.")</MudText>
}
else
{
<MudList T="Guid"
Color="Color.Primary"
Class="mb-1"
SelectedValue="@(this.selectedProject?.ProjectId ?? Guid.Empty)"
SelectedValueChanged="@this.SelectProjectAsync">
@foreach (var project in this.projects)
{
<MudListItem T="Guid" @key="project.ProjectId" Value="@project.ProjectId" Icon="@ProjectIcon(project)">
<MudStack Spacing="0">
<MudText Typo="Typo.body1">@this.ProjectDisplayName(project)</MudText>
<MudText Typo="Typo.caption">@this.ProjectCaption(project)</MudText>
@if (this.projectsWithMissingDetails.Contains(project.ProjectId))
{
<MudText Typo="Typo.caption" Color="Color.Warning">@T("Details missing")</MudText>
}
@if (!project.IsAvailable)
{
<MudText Typo="Typo.caption" Color="Color.Error">@this.ProjectStatusName(project.Status)</MudText>
}
</MudStack>
</MudListItem>
}
</MudList>
}
<MudButton Variant="Variant.Filled" Color="Color.Primary" StartIcon="@Icons.Material.Filled.Add" OnClick="@this.CreateProjectAsync" Disabled="@this.isBusy" Class="mt-1">
@T("New preparation")
</MudButton>
<MudDivider Style="height: 0.25ch; margin: 1rem 0;" Class="mt-6"/>
@if (this.selectedProject is { IsAvailable: false } unavailableProject)
{
<MudPaper Outlined="true" Class="pa-6 mr-3">
<MudStack Spacing="3">
<MudText Typo="Typo.h4">@this.ProjectDisplayName(unavailableProject)</MudText>
<MudAlert Severity="Severity.Error" Variant="Variant.Outlined">
@this.ProjectRecoveryMessage(unavailableProject.Status)
</MudAlert>
<MudText Typo="Typo.body1">@T("AI Studio has left the files of this preparation unchanged. A future update may make it accessible again.")</MudText>
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="1" Wrap="Wrap.Wrap">
<MudText Typo="Typo.body2"><strong>@T("Preparation ID"):</strong> @unavailableProject.ProjectId.ToString("D")</MudText>
<MudCopyClipboardButton TooltipMessage="@T("Copy preparation ID")" StringContent="@unavailableProject.ProjectId.ToString("D")"/>
</MudStack>
<MudStack Row="true" Spacing="1" Wrap="Wrap.Wrap">
<MudButton Variant="Variant.Filled" StartIcon="@Icons.Material.Filled.DeleteForever" Color="Color.Error" OnClick="@this.DeleteProjectAsync" Disabled="@this.isBusy">@T("Delete")</MudButton>
</MudStack>
</MudStack>
</MudPaper>
}
else if (this.selectedProject?.Project is { } project)
{
<MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center" Wrap="Wrap.Wrap" Class="mb-3 mr-3">
<MudText Typo="Typo.h4">@project.Name</MudText>
<MudStack Row="true" Spacing="1">
<MudButton StartIcon="@Icons.Material.Filled.DriveFileRenameOutline" OnClick="@this.RenameProjectAsync" Disabled="@this.isBusy">@T("Rename")</MudButton>
<MudButton StartIcon="@Icons.Material.Filled.DeleteForever" Color="Color.Error" OnClick="@this.DeleteProjectAsync" Disabled="@this.isBusy">@T("Delete")</MudButton>
</MudStack>
</MudStack>
<MudSwitch T="bool" Value="@(project.HappenedAtUtc is not null)" ValueChanged="@this.SetHappenedAsync" Disabled="@this.isBusy" Color="Color.Primary" Class="mx-1">
@T("The event has taken place")
</MudSwitch>
@if (project.HappenedAtUtc is { } happenedAtUtc)
{
<MudText Typo="Typo.caption" Class="mx-1">@string.Format(Culture, T("Marked on {0}."), happenedAtUtc.ToLocalTime().ToString("d", Culture))</MudText>
}
<MudStepperWithoutActions NonLinear="@true" ActiveIndex="@this.ActiveStepIndex" ActiveIndexChanged="@this.SelectStepAsync" Class="mt-4 mr-3">
<ChildContent>
@foreach (var step in BflStep.All)
{
<MudStep @key="step" Title="@step.Title" SecondaryText="@this.StepSecondaryText(step)">
<MudText Typo="Typo.h5" Class="mt-2">@step.Title</MudText>
<MudText Typo="Typo.body2" Class="mb-4">@step.Description</MudText>
<MudText Typo="Typo.h6" Class="mb-2">@T("Your details")</MudText>
@if (this.forms.TryGetValue(step, out var form))
{
<BflStepForm Step="@step" Form="@form" FormChanged="@(() => this.SaveFormAsync(step))"/>
}
else
{
<MudAlert Severity="Severity.Error" Variant="Variant.Outlined">
@T("AI Studio cannot open your details for this step. Their file may be damaged, or a newer version of AI Studio stored it. AI Studio leaves the file unchanged.")
</MudAlert>
}
</MudStep>
}
</ChildContent>
</MudStepperWithoutActions>
}
else
{
<MudPaper Outlined="true" Class="pa-6 mr-3">
<MudText Typo="Typo.h5">@T("Create a preparation to begin.")</MudText>
</MudPaper>
}
</InnerScrolling>
</div>
</CascadingValue>
@@ -0,0 +1,271 @@
using System.Globalization;
using AIStudio.Dialogs;
using DialogOptions = AIStudio.Dialogs.DialogOptions;
namespace AIStudio.Assistants.BeyondFactsLab;
public partial class BeyondFactsLabAssistant
{
/// <summary>
/// The preparations, the one changed last first.
/// </summary>
private IReadOnlyList<BflProjectEntry> projects = [];
/// <summary>
/// The preparation shown, or null when there is none.
/// </summary>
private BflProjectEntry? selectedProject;
/// <summary>
/// Whether a change of the preparations is running.
/// </summary>
private bool isBusy;
/// <summary>
/// The IDs of the preparations which lack key details.
/// </summary>
private HashSet<Guid> projectsWithMissingDetails = [];
/// <summary>
/// Gets the culture of the active language plugin, for dates.
/// </summary>
private static CultureInfo Culture => Tools.PluginSystem.I18N.I.Culture;
/// <summary>
/// Reads the preparations again and chooses the one to show.
/// </summary>
/// <param name="preferredId">The preparation to show, or null to keep the one shown so far.</param>
private async Task ReloadProjectsAsync(Guid? preferredId)
{
this.projects = await this.Store.ListProjectsAsync();
var withMissingDetails = new HashSet<Guid>();
foreach (var project in this.projects.Where(project => project.IsAvailable))
{
if (BflMissingDetails.StepsOf(await this.Store.LoadFormsAsync(project.ProjectId)).Count > 0)
withMissingDetails.Add(project.ProjectId);
}
this.projectsWithMissingDetails = withMissingDetails;
await this.ShowProjectAsync(BflProjectSelection.Choose(this.projects, preferredId ?? this.selectedProject?.ProjectId, this.Store.LastSelectedProjectId));
}
/// <summary>
/// Shows the preparation the person selected and remembers it for the next time.
/// </summary>
/// <param name="projectId">The ID of the preparation.</param>
private async Task SelectProjectAsync(Guid projectId)
{
if (this.selectedProject?.ProjectId == projectId)
return;
var project = this.projects.FirstOrDefault(candidate => candidate.ProjectId == projectId);
if (project is null)
return;
await this.RunStoreActionAsync(async () =>
{
await this.ShowProjectAsync(project);
await this.Store.RememberSelectionAsync(projectId);
});
}
/// <summary>
/// Asks for a name and creates a preparation.
/// </summary>
private async Task CreateProjectAsync()
{
var name = await this.AskForNameAsync(T("New preparation"), T("Name the event you want to prepare for, e.g., the title of a panel discussion."), string.Empty, T("Create"));
if (name is null)
return;
await this.RunStoreActionAsync(async () =>
{
var project = await this.Store.CreateAsync(name);
await this.Store.RememberSelectionAsync(project.ProjectId);
await this.ReloadProjectsAsync(project.ProjectId);
});
}
/// <summary>
/// Asks for a new name and renames the preparation shown.
/// </summary>
private async Task RenameProjectAsync()
{
if (this.selectedProject?.Project is not { } project)
return;
var name = await this.AskForNameAsync(T("Rename preparation"), T("Enter a new name for this preparation."), project.Name, T("Rename"));
if (name is null)
return;
await this.RunStoreActionAsync(async () =>
{
await this.Store.RenameAsync(project.ProjectId, name);
await this.ReloadProjectsAsync(project.ProjectId);
});
}
/// <summary>
/// Deletes the preparation shown, after the person confirmed it.
/// </summary>
private async Task DeleteProjectAsync()
{
if (this.selectedProject is not { } project)
return;
var parameters = new DialogParameters<ConfirmDialog>
{
{
dialog => dialog.Message,
project.IsAvailable
? string.Format(T("Permanently delete the preparation '{0}' with all of its steps, chats, and work states?"), project.Name)
: T("This preparation cannot be opened. Permanently delete it with all of its files?")
},
};
var reference = await this.DialogService.ShowAsync<ConfirmDialog>(T("Delete preparation permanently"), parameters, DialogOptions.FULLSCREEN);
var result = await reference.Result;
if (result is null || result.Canceled)
return;
await this.RunStoreActionAsync(async () =>
{
await this.Store.DeleteAsync(project.ProjectId);
this.selectedProject = null;
await this.ReloadProjectsAsync(null);
});
}
/// <summary>
/// Marks the event of the preparation shown as having taken place, or takes that back.
/// </summary>
/// <param name="happened">Whether the event has taken place.</param>
private async Task SetHappenedAsync(bool happened)
{
if (this.selectedProject?.Project is not { } project)
return;
await this.RunStoreActionAsync(async () =>
{
await this.Store.SetHappenedAsync(project.ProjectId, happened ? DateTimeOffset.UtcNow : null);
await this.ReloadProjectsAsync(project.ProjectId);
});
}
/// <summary>
/// Asks for the name of a preparation.
/// </summary>
/// <param name="title">The title of the dialog.</param>
/// <param name="message">What the dialog asks for.</param>
/// <param name="currentName">The name to start with.</param>
/// <param name="confirmText">The text of the button which confirms.</param>
/// <returns>The name entered, or null when the person canceled.</returns>
private async Task<string?> AskForNameAsync(string title, string message, string currentName, string confirmText)
{
var parameters = new DialogParameters<SingleInputDialog>
{
{ dialog => dialog.Message, message },
{ dialog => dialog.InputHeaderText, T("Name of the preparation") },
{ dialog => dialog.UserInput, currentName },
{ dialog => dialog.ConfirmText, confirmText },
{ dialog => dialog.ConfirmColor, Color.Primary },
{ dialog => dialog.AllowEmptyInput, false },
{ dialog => dialog.EmptyInputErrorMessage, T("Please enter a name.") },
};
var reference = await this.DialogService.ShowAsync<SingleInputDialog>(title, parameters, DialogOptions.FULLSCREEN);
var result = await reference.Result;
if (result is null || result.Canceled || result.Data is not string name || string.IsNullOrWhiteSpace(name))
return null;
return name;
}
/// <summary>
/// Runs a change of the preparations, and tells the person when it fails.
/// </summary>
/// <param name="action">The change.</param>
private async Task RunStoreActionAsync(Func<Task> action)
{
this.isBusy = true;
try
{
await action();
}
catch (Exception exception) when (exception is IOException or UnauthorizedAccessException or InvalidDataException or ArgumentException)
{
this.Logger.LogError(exception, "A change of the preparations of the BeyondFacts Lab failed.");
await this.MessageBus.SendError(new(Icons.Material.Filled.RecordVoiceOver, T("AI Studio could not save the change of your preparations. Please try again.")));
await this.ReloadAfterFailureAsync();
}
finally
{
this.isBusy = false;
}
}
/// <summary>
/// Reads the preparations again after a change failed, so the page shows what is stored.
/// </summary>
private async Task ReloadAfterFailureAsync()
{
try
{
await this.ReloadProjectsAsync(null);
}
catch (Exception exception) when (exception is IOException or UnauthorizedAccessException)
{
this.Logger.LogError(exception, "The preparations of the BeyondFacts Lab could not be read.");
}
}
/// <summary>
/// Gets the icon of a preparation in the list.
/// </summary>
/// <param name="project">The preparation.</param>
private static string ProjectIcon(BflProjectEntry project) => project switch
{
{ IsAvailable: false } => Icons.Material.Filled.WarningAmber,
{ Project.HappenedAtUtc: not null } => Icons.Material.Filled.EventAvailable,
_ => Icons.Material.Filled.Event,
};
/// <summary>
/// Gets the name of a preparation in the list, also when it cannot be read.
/// </summary>
/// <param name="project">The preparation.</param>
private string ProjectDisplayName(BflProjectEntry project) => string.IsNullOrWhiteSpace(project.Name) ? T("Unavailable preparation") : project.Name;
/// <summary>
/// Gets the line below the name of a preparation in the list: when it changed, and whether its event has taken place.
/// </summary>
/// <param name="project">The preparation.</param>
private string ProjectCaption(BflProjectEntry project)
{
var changed = string.Format(Culture, T("Changed on {0}"), project.ModifiedAtUtc.ToLocalTime().ToString("g", Culture));
return project.Project?.HappenedAtUtc is null ? changed : $"{changed} · {T("The event has taken place")}";
}
/// <summary>
/// Gets the short reason why a preparation cannot be opened.
/// </summary>
/// <param name="status">The status of the preparation.</param>
private string ProjectStatusName(BflProjectLoadStatus status) => status switch
{
BflProjectLoadStatus.NEWER_VERSION => T("Requires a newer AI Studio version"),
_ => T("Cannot be opened"),
};
/// <summary>
/// Explains why a preparation cannot be opened.
/// </summary>
/// <param name="status">The status of the preparation.</param>
private string ProjectRecoveryMessage(BflProjectLoadStatus status) => status switch
{
BflProjectLoadStatus.NEWER_VERSION => T("A newer version of AI Studio stored this preparation. This version cannot open it. Please update AI Studio."),
_ => T("AI Studio cannot read this preparation. Its files may be damaged."),
};
}
@@ -0,0 +1,115 @@
namespace AIStudio.Assistants.BeyondFactsLab;
public partial class BeyondFactsLabAssistant
{
/// <summary>
/// The forms of the preparation shown, by their steps. A step missing here has a form this version cannot open.
/// </summary>
private Dictionary<BflStep, BflStepFormData> forms = [];
/// <summary>
/// The step shown.
/// </summary>
private BflStep activeStep = BflStep.FRAMEWORK;
/// <summary>
/// Gets the position of the step shown in the stepper, which lists the steps in the order of their values.
/// </summary>
private int ActiveStepIndex => (int)this.activeStep;
/// <summary>
/// Shows a preparation with its forms, at the step the person looked at last.
/// </summary>
/// <param name="project">The preparation, or null to show none.</param>
private async Task ShowProjectAsync(BflProjectEntry? project)
{
this.selectedProject = project;
if (project?.Project is { } openedProject)
{
this.forms = new(await this.Store.LoadFormsAsync(openedProject.ProjectId));
this.activeStep = openedProject.CurrentStep;
}
else
{
this.forms = [];
this.activeStep = BflStep.FRAMEWORK;
}
}
/// <summary>
/// Shows the step the person chose in the stepper, and remembers it for the next time.
/// </summary>
/// <param name="index">The position of the step in the stepper.</param>
private async Task SelectStepAsync(int index)
{
var steps = BflStep.All;
if (index < 0 || index >= steps.Count || steps[index] == this.activeStep)
return;
this.activeStep = steps[index];
if (this.selectedProject?.Project is not { } project)
return;
try
{
this.ReplaceProject(await this.Store.SetCurrentStepAsync(project.ProjectId, this.activeStep));
}
catch (Exception exception) when (exception is IOException or UnauthorizedAccessException or InvalidDataException)
{
// Only the step to open next time is lost, which is not worth bothering the person with:
this.Logger.LogWarning(exception, "Could not remember the step shown last in the BeyondFacts Lab.");
}
}
/// <summary>
/// Stores the form of a step after the person changed a value.
/// </summary>
/// <remarks>
/// Unlike the other changes, saving leaves the page usable: the fields stay enabled, so that the next field
/// keeps the focus while the previous one is stored.
/// </remarks>
/// <param name="step">The step.</param>
private async Task SaveFormAsync(BflStep step)
{
if (this.selectedProject?.Project is not { } project || !this.forms.TryGetValue(step, out var form))
return;
try
{
this.ReplaceProject(await this.Store.SaveFormAsync(project.ProjectId, step, form));
if (BflMissingDetails.StepsOf(this.forms).Count > 0)
this.projectsWithMissingDetails.Add(project.ProjectId);
else
this.projectsWithMissingDetails.Remove(project.ProjectId);
}
catch (Exception exception) when (exception is IOException or UnauthorizedAccessException or InvalidDataException)
{
this.Logger.LogError(exception, "Could not save the details of a step in the BeyondFacts Lab.");
await this.MessageBus.SendError(new(Icons.Material.Filled.RecordVoiceOver, T("AI Studio could not save your details. Please try again.")));
}
}
/// <summary>
/// Puts a preparation the store just wrote into the list, without reading the list again.
/// </summary>
/// <param name="project">The preparation.</param>
private void ReplaceProject(BflProject project)
{
var entry = BflProjectEntry.FromProject(project);
this.projects = [.. this.projects.Select(candidate => candidate.ProjectId == project.ProjectId ? entry : candidate).OrderByDescending(candidate => candidate.ModifiedAtUtc)];
if (this.selectedProject?.ProjectId == project.ProjectId)
this.selectedProject = entry;
}
/// <summary>
/// Gets the line below the title of a step in the stepper.
/// </summary>
/// <param name="step">The step.</param>
private string StepSecondaryText(BflStep step)
{
if (!this.forms.TryGetValue(step, out var form))
return T("Cannot be opened");
return BflMissingDetails.Of(step, form).Count > 0 ? T("Details missing") : string.Empty;
}
}
@@ -0,0 +1,94 @@
using AIStudio.Components;
using AIStudio.Dialogs.Settings;
using Microsoft.AspNetCore.Components;
using DialogOptions = AIStudio.Dialogs.DialogOptions;
namespace AIStudio.Assistants.BeyondFactsLab;
/// <summary>
/// The page of the BeyondFacts Lab, which guides researchers through the preparation of a challenging appearance.
/// </summary>
/// <remarks>
/// The lab keeps several preparations, each with steps of their own, so it does not inherit AssistantBase,
/// which knows one input and one result. Like the Visual Briefing assistant, it checks by itself whether it
/// may be opened.
/// </remarks>
public partial class BeyondFactsLabAssistant : MSGComponentBase
{
[Inject]
private IDialogService DialogService { get; init; } = null!;
[Inject]
private NavigationManager NavigationManager { get; init; } = null!;
[Inject]
private BeyondFactsLabStore Store { get; init; } = null!;
[Inject]
private ILogger<BeyondFactsLabAssistant> Logger { get; init; } = null!;
/// <summary>
/// Whether the person may open the lab, so that the page did not send them away.
/// </summary>
private bool mayOpen;
/// <inheritdoc />
protected override async Task OnInitializedAsync()
{
await base.OnInitializedAsync();
if (!BflAccess.CanOpen(this.SettingsManager, T("BeyondFacts Lab")))
{
this.NavigationManager.NavigateTo(Routes.ASSISTANTS);
return;
}
this.ApplyFilters([], [Event.CONFIGURATION_CHANGED]);
this.mayOpen = true;
await this.RunStoreActionAsync(() => this.ReloadProjectsAsync(null));
}
/// <inheritdoc />
protected override async Task OnAfterRenderAsync(bool firstRender)
{
await base.OnAfterRenderAsync(firstRender);
if (firstRender && this.mayOpen)
await this.ShowNoticeIfNeededAsync();
}
/// <summary>
/// Shows the notes of the lab, unless the person confirmed them as they are now.
/// </summary>
private async Task ShowNoticeIfNeededAsync()
{
var notice = BflNotice.Text;
var currentHash = BflNotice.ComputeHash(notice);
if (BflNotice.IsAccepted(this.SettingsManager.ConfigurationData.BeyondFactsLab.AcceptedNoticeHash, currentHash))
return;
var parameters = new DialogParameters<BflNoticeDialog> { { dialog => dialog.Notice, notice } };
var reference = await this.DialogService.ShowAsync<BflNoticeDialog>(T("BeyondFacts Lab"), parameters, DialogOptions.BLOCKING_FULLSCREEN);
var result = await reference.Result;
if (result is null || result.Canceled)
return;
this.SettingsManager.ConfigurationData.BeyondFactsLab.AcceptedNoticeHash = currentHash;
await this.SettingsManager.StoreSettings();
}
/// <inheritdoc />
protected override async Task ProcessIncomingMessage<T>(ComponentBase? sendingComponent, Event triggeredEvent, T? data) where T : default
{
// An organization may hide the lab, or the person may switch its preview feature off, while the page is open:
if (triggeredEvent is Event.CONFIGURATION_CHANGED && !BflAccess.CanOpen(this.SettingsManager))
{
this.NavigationManager.NavigateTo(Routes.ASSISTANTS);
return;
}
await base.ProcessIncomingMessage(sendingComponent, triggeredEvent, data);
}
private async Task OpenSettingsDialogAsync() => await this.DialogService.ShowAsync<SettingsDialogBeyondFactsLab>(null, new DialogParameters(), DialogOptions.FULLSCREEN);
}
@@ -0,0 +1,31 @@
using System.Text.Encodings.Web;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace AIStudio.Assistants.BeyondFactsLab;
/// <summary>
/// The JSON options of the files of the BeyondFacts Lab.
/// </summary>
internal static class BeyondFactsLabJson
{
/// <summary>
/// The options with which the lab writes and reads its files.
/// </summary>
/// <remarks>
/// The files stay readable for people who look into the data folder: indented, enums by name, and umlauts
/// as they are rather than as escapes. The relaxed encoder is fine here, because nothing embeds these files
/// into HTML. A field this version does not know makes a file unreadable rather than letting a save drop
/// it; such a preparation is listed as one of a newer version or as unavailable, and nothing writes it.
/// </remarks>
internal static readonly JsonSerializerOptions PERSISTENCE = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
PropertyNameCaseInsensitive = false,
WriteIndented = true,
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
UnmappedMemberHandling = JsonUnmappedMemberHandling.Disallow,
Converters = { new JsonStringEnumConverter() },
TypeInfoResolver = BeyondFactsLabJsonContext.Default,
}.AsReadOnly();
}
@@ -0,0 +1,14 @@
using System.Text.Json.Serialization;
namespace AIStudio.Assistants.BeyondFactsLab;
/// <summary>
/// The contracts with which System.Text.Json reads and writes the files of the BeyondFacts Lab, built at compile time.
/// </summary>
[JsonSourceGenerationOptions(GenerationMode = JsonSourceGenerationMode.Metadata)]
[JsonSerializable(typeof(BflProject))]
[JsonSerializable(typeof(BflStepFormData))]
// The preparation selected last:
[JsonSerializable(typeof(Guid?))]
internal sealed partial class BeyondFactsLabJsonContext : JsonSerializerContext;
@@ -0,0 +1,134 @@
using System.Text.Json;
namespace AIStudio.Assistants.BeyondFactsLab;
public sealed partial class BeyondFactsLabStore
{
/// <summary>
/// The folder of a preparation which holds a folder for each step.
/// </summary>
private const string STEPS_DIRECTORY_NAME = "steps";
/// <summary>
/// The file of a step which holds its form "Your details".
/// </summary>
private const string FORM_FILE_NAME = "form.json";
/// <summary>
/// Loads the form of a step.
/// </summary>
/// <param name="projectId">The ID of the preparation.</param>
/// <param name="step">The step.</param>
/// <param name="token">The cancellation token.</param>
/// <returns>The form, or an empty one when the person entered nothing yet.</returns>
/// <exception cref="InvalidDataException">This version cannot open the form.</exception>
public async Task<BflStepFormData> LoadFormAsync(Guid projectId, BflStep step, CancellationToken token = default)
{
await this.InitializeAsync(token);
return await this.LoadFormWithoutInitializeAsync(projectId, step, token);
}
/// <summary>
/// Loads the forms of every step of a preparation, e.g., to find missing details.
/// </summary>
/// <param name="projectId">The ID of the preparation.</param>
/// <param name="token">The cancellation token.</param>
/// <returns>The forms this version can open, by their steps.</returns>
public async Task<IReadOnlyDictionary<BflStep, BflStepFormData>> LoadFormsAsync(Guid projectId, CancellationToken token = default)
{
await this.InitializeAsync(token);
var forms = new Dictionary<BflStep, BflStepFormData>();
foreach (var step in BflStep.All)
{
try
{
forms[step] = await this.LoadFormWithoutInitializeAsync(projectId, step, token);
}
catch (InvalidDataException exception)
{
logger.LogWarning(exception, "Could not open the form of the step '{Step}' of the preparation '{ProjectId}' of the BeyondFacts Lab.", step, projectId);
}
}
return forms;
}
/// <summary>
/// Stores the form of a step and counts it as a change of the preparation.
/// </summary>
/// <param name="projectId">The ID of the preparation.</param>
/// <param name="step">The step.</param>
/// <param name="form">The form.</param>
/// <param name="token">The cancellation token.</param>
/// <returns>The preparation with its new time of change.</returns>
/// <exception cref="FileNotFoundException">The preparation does not exist.</exception>
/// <exception cref="InvalidDataException">This version cannot open the preparation or the stored form, so it does not write them either.</exception>
public async Task<BflProject> SaveFormAsync(Guid projectId, BflStep step, BflStepFormData form, CancellationToken token = default)
{
await this.InitializeAsync(token);
var gate = this.GetLock(projectId);
await gate.WaitAsync(token);
try
{
var project = await this.LoadRequiredWithoutInitializeAsync(projectId, token);
// A form which a newer version stored, or which is damaged, stays as it is:
await this.LoadFormWithoutInitializeAsync(projectId, step, token);
form.SchemaVersion = BflStepFormData.CURRENT_SCHEMA_VERSION;
await WriteTextAtomicAsync(this.FormPath(projectId, step), JsonSerializer.Serialize(form, JSON_OPTIONS), token);
project.ModifiedAtUtc = DateTimeOffset.UtcNow;
await this.StoreProjectAsync(project, token);
return project;
}
finally
{
gate.Release();
}
}
/// <summary>
/// Reads the form of a step without the first start of the store.
/// </summary>
/// <param name="projectId">The ID of the preparation.</param>
/// <param name="step">The step.</param>
/// <param name="token">The cancellation token.</param>
/// <returns>The form, or an empty one when there is no file.</returns>
/// <exception cref="InvalidDataException">This version cannot open the form.</exception>
private async Task<BflStepFormData> LoadFormWithoutInitializeAsync(Guid projectId, BflStep step, CancellationToken token)
{
var path = this.FormPath(projectId, step);
if (!File.Exists(path))
return new BflStepFormData();
BflStepFormData? form;
try
{
await using var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, 65_536, true);
form = await JsonSerializer.DeserializeAsync<BflStepFormData>(stream, JSON_OPTIONS, token);
}
catch (Exception exception) when (exception is IOException or UnauthorizedAccessException or JsonException)
{
throw new InvalidDataException("This version of AI Studio cannot open the form.", exception);
}
if (form is null || form.SchemaVersion is < 1 or > BflStepFormData.CURRENT_SCHEMA_VERSION || IsNull(form.Values))
throw new InvalidDataException("This version of AI Studio cannot open the form.");
return form;
}
/// <summary>
/// Gets the file of the form of a step.
/// </summary>
/// <param name="projectId">The ID of the preparation.</param>
/// <param name="step">The step.</param>
private string FormPath(Guid projectId, BflStep step) => Path.Combine(this.ProjectDirectory(projectId), STEPS_DIRECTORY_NAME, step.FolderName, FORM_FILE_NAME);
/// <summary>
/// Checks for null a value which the type system says cannot be null, but which JSON can make null.
/// </summary>
/// <param name="value">The value.</param>
private static bool IsNull<T>(T? value) => value is null;
}
@@ -0,0 +1,435 @@
using System.Text.Json;
namespace AIStudio.Assistants.BeyondFactsLab;
public sealed partial class BeyondFactsLabStore
{
/// <summary>
/// Gets the preparation the person selected last, or null when there is none or it does not exist anymore.
/// </summary>
public Guid? LastSelectedProjectId { get; private set; }
/// <summary>
/// Lists every preparation, also those this version cannot open, the one changed last first.
/// </summary>
/// <param name="token">The cancellation token.</param>
/// <returns>The preparations.</returns>
public async Task<IReadOnlyList<BflProjectEntry>> ListProjectsAsync(CancellationToken token = default)
{
await this.InitializeAsync(token);
List<BflProjectEntry> projects = [];
foreach (var directory in Directory.EnumerateDirectories(this.RootDirectory))
{
token.ThrowIfCancellationRequested();
if (!Guid.TryParseExact(Path.GetFileName(directory), "D", out var projectId))
continue;
projects.Add(await this.LoadProjectEntryAsync(projectId, directory, token));
}
return [.. projects.OrderByDescending(project => project.ModifiedAtUtc)];
}
/// <summary>
/// Loads a preparation.
/// </summary>
/// <param name="projectId">The ID of the preparation.</param>
/// <param name="token">The cancellation token.</param>
/// <returns>The preparation, or null when it does not exist or this version cannot open it.</returns>
public async Task<BflProject?> LoadAsync(Guid projectId, CancellationToken token = default)
{
await this.InitializeAsync(token);
try
{
return await this.LoadWithoutInitializeAsync(projectId, token);
}
catch (Exception exception) when (exception is IOException or UnauthorizedAccessException or JsonException)
{
this.LogUnavailableProject(projectId, exception);
return null;
}
}
/// <summary>
/// Creates a preparation.
/// </summary>
/// <param name="name">The name the person gave it.</param>
/// <param name="token">The cancellation token.</param>
/// <returns>The new preparation.</returns>
/// <exception cref="ArgumentException">The name is empty.</exception>
public async Task<BflProject> CreateAsync(string name, CancellationToken token = default)
{
var sanitizedName = RequireName(name);
await this.InitializeAsync(token);
var projectId = Guid.NewGuid();
var gate = this.GetLock(projectId);
await gate.WaitAsync(token);
try
{
var directory = this.ProjectDirectory(projectId);
if (Directory.Exists(directory))
throw new IOException($"A preparation with the ID '{projectId}' exists already.");
var now = DateTimeOffset.UtcNow;
var project = new BflProject
{
ProjectId = projectId,
Name = sanitizedName,
CreatedAtUtc = now,
ModifiedAtUtc = now,
};
await this.StoreProjectAsync(project, token);
return project;
}
finally
{
gate.Release();
}
}
/// <summary>
/// Renames a preparation.
/// </summary>
/// <param name="projectId">The ID of the preparation.</param>
/// <param name="name">The new name.</param>
/// <param name="token">The cancellation token.</param>
/// <returns>The renamed preparation.</returns>
/// <exception cref="ArgumentException">The name is empty.</exception>
public async Task<BflProject> RenameAsync(Guid projectId, string name, CancellationToken token = default)
{
var sanitizedName = RequireName(name);
return await this.MutateProjectAsync(projectId, project => project.Name = sanitizedName, token);
}
/// <summary>
/// Marks the event of a preparation as having taken place, or takes that back.
/// </summary>
/// <param name="projectId">The ID of the preparation.</param>
/// <param name="happenedAtUtc">When the event took place, or null when it has not.</param>
/// <param name="token">The cancellation token.</param>
/// <returns>The changed preparation.</returns>
public async Task<BflProject> SetHappenedAsync(Guid projectId, DateTimeOffset? happenedAtUtc, CancellationToken token = default)
{
return await this.MutateProjectAsync(projectId, project => project.HappenedAtUtc = happenedAtUtc?.ToUniversalTime(), token);
}
/// <summary>
/// Deletes a preparation with everything it holds, also when this version cannot open it.
/// </summary>
/// <param name="projectId">The ID of the preparation.</param>
/// <param name="token">The cancellation token.</param>
public async Task DeleteAsync(Guid projectId, CancellationToken token = default)
{
await this.InitializeAsync(token);
var gate = this.GetLock(projectId);
await gate.WaitAsync(token);
try
{
var directory = this.ProjectDirectory(projectId);
if (Directory.Exists(directory))
Directory.Delete(directory, recursive: true);
}
finally
{
gate.Release();
this.ForgetLock(projectId);
}
await this.ForgetSelectionAsync(projectId, token);
}
/// <summary>
/// Remembers the preparation the person selected, for the next time the lab opens.
/// </summary>
/// <param name="projectId">The ID of the preparation.</param>
/// <param name="token">The cancellation token.</param>
public async Task RememberSelectionAsync(Guid projectId, CancellationToken token = default)
{
await this.InitializeAsync(token);
if (this.LastSelectedProjectId == projectId)
return;
await this.selectionLock.WaitAsync(token);
try
{
this.LastSelectedProjectId = projectId;
await WriteTextAtomicAsync(this.SelectionPath(), JsonSerializer.Serialize<Guid?>(projectId, JSON_OPTIONS), token);
}
finally
{
this.selectionLock.Release();
}
}
/// <summary>
/// Forgets the selection when it names the given preparation.
/// </summary>
/// <param name="projectId">The ID of the preparation.</param>
/// <param name="token">The cancellation token.</param>
private async Task ForgetSelectionAsync(Guid projectId, CancellationToken token)
{
if (this.LastSelectedProjectId != projectId)
return;
await this.selectionLock.WaitAsync(token);
try
{
if (this.LastSelectedProjectId != projectId)
return;
this.LastSelectedProjectId = null;
await WriteTextAtomicAsync(this.SelectionPath(), JsonSerializer.Serialize<Guid?>(null, JSON_OPTIONS), token);
}
finally
{
this.selectionLock.Release();
}
}
/// <summary>
/// Reads the preparation selected last, when it still exists.
/// </summary>
/// <param name="token">The cancellation token.</param>
private async Task LoadSelectionAsync(CancellationToken token)
{
var path = this.SelectionPath();
if (!File.Exists(path))
return;
try
{
var serialized = await File.ReadAllTextAsync(path, token);
var selected = JsonSerializer.Deserialize<Guid?>(serialized, JSON_OPTIONS);
this.LastSelectedProjectId = selected is not null && Directory.Exists(this.ProjectDirectory(selected.Value)) ? selected : null;
}
catch (Exception exception) when (exception is IOException or UnauthorizedAccessException or JsonException)
{
logger.LogWarning(exception, "Could not read the preparation of the BeyondFacts Lab selected last.");
this.LastSelectedProjectId = null;
}
}
/// <summary>
/// Loads a preparation for the list, or an entry which says why it cannot be opened.
/// </summary>
/// <param name="projectId">The ID of the preparation.</param>
/// <param name="directory">The folder of the preparation.</param>
/// <param name="token">The cancellation token.</param>
private async Task<BflProjectEntry> LoadProjectEntryAsync(Guid projectId, string directory, CancellationToken token)
{
var path = this.ProjectPath(projectId);
var fallbackModifiedAtUtc = LastWriteTimeUtc(path, directory);
if (!File.Exists(path))
return new(projectId, string.Empty, fallbackModifiedAtUtc, BflProjectLoadStatus.UNAVAILABLE, null);
string json;
try
{
json = await File.ReadAllTextAsync(path, token);
}
catch (Exception exception) when (exception is IOException or UnauthorizedAccessException)
{
this.LogUnavailableProject(projectId, exception);
return new(projectId, string.Empty, fallbackModifiedAtUtc, BflProjectLoadStatus.UNAVAILABLE, null);
}
try
{
var project = JsonSerializer.Deserialize<BflProject>(json, JSON_OPTIONS);
if (project is not null && IsValidProject(project, projectId))
return BflProjectEntry.FromProject(project);
}
catch (JsonException exception)
{
this.LogUnavailableProject(projectId, exception);
}
return ReadRecoveryEntry(projectId, json, fallbackModifiedAtUtc);
}
/// <summary>
/// Reads what the list can show about a preparation this version cannot open: its name, when it changed,
/// and whether a newer version stored it.
/// </summary>
/// <param name="projectId">The ID of the preparation.</param>
/// <param name="json">The content of its file.</param>
/// <param name="fallbackModifiedAtUtc">When the file changed, for files without a readable date.</param>
private static BflProjectEntry ReadRecoveryEntry(Guid projectId, string json, DateTimeOffset fallbackModifiedAtUtc)
{
try
{
using var document = JsonDocument.Parse(json);
var root = document.RootElement;
if (root.ValueKind is not JsonValueKind.Object)
return new(projectId, string.Empty, fallbackModifiedAtUtc, BflProjectLoadStatus.UNAVAILABLE, null);
var name = root.TryGetProperty("name", out var nameElement) && nameElement.ValueKind is JsonValueKind.String ? SanitizeName(nameElement.GetString()) : string.Empty;
var modifiedAtUtc = root.TryGetProperty("modifiedAtUtc", out var modifiedElement) && modifiedElement.ValueKind is JsonValueKind.String && modifiedElement.TryGetDateTimeOffset(out var parsedModifiedAtUtc)
? parsedModifiedAtUtc
: fallbackModifiedAtUtc;
var isNewer = root.TryGetProperty("schemaVersion", out var versionElement) && versionElement.ValueKind is JsonValueKind.Number && versionElement.TryGetInt32(out var schemaVersion) && schemaVersion > BflProject.CURRENT_SCHEMA_VERSION;
return new(projectId, name, modifiedAtUtc, isNewer ? BflProjectLoadStatus.NEWER_VERSION : BflProjectLoadStatus.UNAVAILABLE, null);
}
catch (JsonException)
{
return new(projectId, string.Empty, fallbackModifiedAtUtc, BflProjectLoadStatus.UNAVAILABLE, null);
}
}
/// <summary>
/// Gets when the file of a preparation, or else its folder, changed last.
/// </summary>
/// <param name="projectPath">The file of the preparation.</param>
/// <param name="directory">The folder of the preparation.</param>
private static DateTimeOffset LastWriteTimeUtc(string projectPath, string directory)
{
try
{
var timestamp = File.Exists(projectPath) ? File.GetLastWriteTimeUtc(projectPath) : Directory.GetLastWriteTimeUtc(directory);
return new DateTimeOffset(timestamp);
}
catch (Exception exception) when (exception is IOException or UnauthorizedAccessException)
{
return DateTimeOffset.UnixEpoch;
}
}
/// <summary>
/// Remembers the step the person looked at last, without counting it as a change of the preparation.
/// </summary>
/// <param name="projectId">The ID of the preparation.</param>
/// <param name="step">The step.</param>
/// <param name="token">The cancellation token.</param>
/// <returns>The preparation.</returns>
public async Task<BflProject> SetCurrentStepAsync(Guid projectId, BflStep step, CancellationToken token = default)
{
return await this.MutateProjectAsync(projectId, project => project.CurrentStep = step, token, isChange: false);
}
/// <summary>
/// Changes a preparation under its lock and stores it.
/// </summary>
/// <param name="projectId">The ID of the preparation.</param>
/// <param name="mutation">The change.</param>
/// <param name="token">The cancellation token.</param>
/// <param name="isChange">Whether the list shows the preparation as changed now.</param>
/// <returns>The changed preparation.</returns>
/// <exception cref="FileNotFoundException">The preparation does not exist.</exception>
/// <exception cref="InvalidDataException">This version cannot open the preparation, so it does not write it either.</exception>
private async Task<BflProject> MutateProjectAsync(Guid projectId, Action<BflProject> mutation, CancellationToken token, bool isChange = true)
{
await this.InitializeAsync(token);
var gate = this.GetLock(projectId);
await gate.WaitAsync(token);
try
{
var project = await this.LoadRequiredWithoutInitializeAsync(projectId, token);
mutation(project);
if (isChange)
project.ModifiedAtUtc = DateTimeOffset.UtcNow;
await this.StoreProjectAsync(project, token);
return project;
}
finally
{
gate.Release();
}
}
/// <summary>
/// Reads a preparation which has to exist and has to be one this version can open, without the first start of the store.
/// </summary>
/// <param name="projectId">The ID of the preparation.</param>
/// <param name="token">The cancellation token.</param>
/// <returns>The preparation.</returns>
/// <exception cref="FileNotFoundException">The preparation does not exist.</exception>
/// <exception cref="InvalidDataException">This version cannot open the preparation.</exception>
private async Task<BflProject> LoadRequiredWithoutInitializeAsync(Guid projectId, CancellationToken token)
{
var path = this.ProjectPath(projectId);
if (!File.Exists(path))
throw new FileNotFoundException("The preparation does not exist.", path);
BflProject? project;
try
{
project = await this.LoadWithoutInitializeAsync(projectId, token);
}
catch (JsonException exception)
{
throw new InvalidDataException("This version of AI Studio cannot open the preparation.", exception);
}
return project ?? throw new InvalidDataException("This version of AI Studio cannot open the preparation.");
}
/// <summary>
/// Reads a preparation without the first start of the store.
/// </summary>
/// <param name="projectId">The ID of the preparation.</param>
/// <param name="token">The cancellation token.</param>
/// <returns>The preparation, or null when it does not exist or is not valid.</returns>
private async Task<BflProject?> LoadWithoutInitializeAsync(Guid projectId, CancellationToken token)
{
var path = this.ProjectPath(projectId);
if (!File.Exists(path))
return null;
await using var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, 65_536, true);
var project = await JsonSerializer.DeserializeAsync<BflProject>(stream, JSON_OPTIONS, token);
return project is not null && IsValidProject(project, projectId) ? project : null;
}
/// <summary>
/// Writes a preparation in the form of this version.
/// </summary>
/// <param name="project">The preparation.</param>
/// <param name="token">The cancellation token.</param>
private async Task StoreProjectAsync(BflProject project, CancellationToken token)
{
project.SchemaVersion = BflProject.CURRENT_SCHEMA_VERSION;
var json = JsonSerializer.Serialize(project, JSON_OPTIONS);
await WriteTextAtomicAsync(this.ProjectPath(project.ProjectId), json, token);
}
/// <summary>
/// Checks a preparation which was read from a file.
/// </summary>
/// <param name="project">The preparation.</param>
/// <param name="expectedProjectId">The ID its folder names.</param>
private static bool IsValidProject(BflProject project, Guid expectedProjectId) =>
project.SchemaVersion is >= 1 and <= BflProject.CURRENT_SCHEMA_VERSION &&
project.ProjectId != Guid.Empty &&
project.ProjectId == expectedProjectId &&
!string.IsNullOrWhiteSpace(project.Name) &&
Enum.IsDefined(project.CurrentStep);
/// <summary>
/// Cleans a name and refuses one which is empty afterward.
/// </summary>
/// <param name="name">The name as entered.</param>
/// <returns>The cleaned name.</returns>
/// <exception cref="ArgumentException">The name is empty.</exception>
private static string RequireName(string name)
{
var sanitizedName = SanitizeName(name);
if (sanitizedName.Length == 0)
throw new ArgumentException("A preparation needs a name.", nameof(name));
return sanitizedName;
}
/// <summary>
/// Logs why a preparation cannot be opened.
/// </summary>
/// <param name="projectId">The ID of the preparation.</param>
/// <param name="exception">What went wrong.</param>
private void LogUnavailableProject(Guid projectId, Exception exception)
{
logger.LogWarning(exception, "Could not open the preparation '{ProjectId}' of the BeyondFacts Lab.", projectId);
}
}
@@ -0,0 +1,189 @@
using System.Collections.Concurrent;
using System.Text;
using System.Text.Json;
using AIStudio.Settings;
namespace AIStudio.Assistants.BeyondFactsLab;
/// <summary>
/// Stores the preparations of the BeyondFacts Lab as files below the data directory.
/// </summary>
/// <remarks>
/// Each preparation lives in a folder named by its ID. The store keeps no paths: it derives every path from
/// IDs, so a backup works on another computer as well. It writes each file atomically, guards each preparation
/// with a lock of its own, and never deletes anything on its own; only the person deletes a preparation.
/// </remarks>
/// <param name="logger">The logger.</param>
/// <param name="storageOptions">Overrides the data directory, for tests.</param>
public sealed partial class BeyondFactsLabStore(ILogger<BeyondFactsLabStore> logger, BflStorageOptions? storageOptions = null)
{
/// <summary>
/// The name of the folder below the data directory.
/// </summary>
public const string ROOT_DIRECTORY_NAME = "beyondFactsLab";
/// <summary>
/// The longest name of a preparation the store keeps.
/// </summary>
public const int MAX_NAME_LENGTH = 200;
/// <summary>
/// The file of a preparation which holds its name and dates.
/// </summary>
private const string PROJECT_FILE_NAME = "project.json";
/// <summary>
/// The file which holds the preparation selected last.
/// </summary>
private const string SELECTION_FILE_NAME = "selection.json";
/// <summary>
/// The JSON options of all files of the lab.
/// </summary>
private static readonly JsonSerializerOptions JSON_OPTIONS = BeyondFactsLabJson.PERSISTENCE;
/// <summary>
/// The lock of each preparation.
/// </summary>
private readonly ConcurrentDictionary<Guid, SemaphoreSlim> projectLocks = [];
/// <summary>
/// Serializes the first start of the store.
/// </summary>
private readonly SemaphoreSlim initializationLock = new(1, 1);
/// <summary>
/// Serializes writing the selection.
/// </summary>
private readonly SemaphoreSlim selectionLock = new(1, 1);
/// <summary>
/// Whether the store cleaned up and read the selection already.
/// </summary>
private bool initialized;
/// <summary>
/// Gets the folder of the lab below the data directory.
/// </summary>
private string RootDirectory => Path.Combine(
storageOptions?.DataDirectory ??
SettingsManager.DataDirectory ??
throw new InvalidOperationException("The AI Studio data directory is not initialized."),
ROOT_DIRECTORY_NAME);
/// <summary>
/// Writes a text file so that readers see either the old or the new content, never a part of it.
/// </summary>
/// <param name="targetPath">The file to write.</param>
/// <param name="content">The new content.</param>
/// <param name="token">The cancellation token.</param>
private static async Task WriteTextAtomicAsync(string targetPath, string content, CancellationToken token)
{
Directory.CreateDirectory(Path.GetDirectoryName(targetPath)!);
var temporaryPath = $"{targetPath}.tmp-{Guid.NewGuid():N}";
try
{
await File.WriteAllTextAsync(temporaryPath, content, new UTF8Encoding(false), token);
await using (var stream = new FileStream(temporaryPath, FileMode.Open, FileAccess.ReadWrite, FileShare.None, 4_096, true))
await stream.FlushAsync(token);
File.Move(temporaryPath, targetPath, overwrite: true);
}
finally
{
TryDeleteFile(temporaryPath);
}
}
/// <summary>
/// Deletes a file when it exists, without failing.
/// </summary>
/// <param name="path">The file to delete.</param>
private static void TryDeleteFile(string path)
{
try
{
if (File.Exists(path))
File.Delete(path);
}
catch (Exception exception) when (exception is IOException or UnauthorizedAccessException)
{
// Cleaning up a temporary file is best effort; the next start tries again.
}
}
/// <summary>
/// Removes control characters from a name, trims it, and cuts it to the longest name the store keeps.
/// </summary>
/// <param name="name">The name as entered or as read from a file.</param>
/// <returns>The cleaned name, empty when nothing remains.</returns>
internal static string SanitizeName(string? name)
{
if (string.IsNullOrWhiteSpace(name))
return string.Empty;
var sanitized = new string(name.Where(character => !char.IsControl(character)).ToArray()).Trim();
return sanitized.Length <= MAX_NAME_LENGTH ? sanitized : sanitized[..MAX_NAME_LENGTH].TrimEnd();
}
/// <summary>
/// Gets the lock of a preparation.
/// </summary>
/// <param name="projectId">The ID of the preparation.</param>
private SemaphoreSlim GetLock(Guid projectId) => this.projectLocks.GetOrAdd(projectId, _ => new(1, 1));
/// <summary>
/// Drops the lock of a preparation which does not exist anymore.
/// </summary>
/// <remarks>
/// The semaphore is not disposed: another operation might still wait on it.
/// </remarks>
/// <param name="projectId">The ID of the preparation.</param>
private void ForgetLock(Guid projectId) => this.projectLocks.TryRemove(projectId, out _);
/// <summary>
/// Gets the folder of a preparation.
/// </summary>
/// <param name="projectId">The ID of the preparation.</param>
private string ProjectDirectory(Guid projectId) => Path.Combine(this.RootDirectory, projectId.ToString("D"));
/// <summary>
/// Gets the file which holds the name and the dates of a preparation.
/// </summary>
/// <param name="projectId">The ID of the preparation.</param>
private string ProjectPath(Guid projectId) => Path.Combine(this.ProjectDirectory(projectId), PROJECT_FILE_NAME);
/// <summary>
/// Gets the file which holds the preparation selected last.
/// </summary>
private string SelectionPath() => Path.Combine(this.RootDirectory, SELECTION_FILE_NAME);
/// <summary>
/// Creates the folder of the lab, removes temporary files a crash left behind, and reads the selection.
/// </summary>
/// <param name="token">The cancellation token.</param>
private async Task InitializeAsync(CancellationToken token)
{
if (this.initialized)
return;
await this.initializationLock.WaitAsync(token);
try
{
if (this.initialized)
return;
Directory.CreateDirectory(this.RootDirectory);
foreach (var temporaryPath in Directory.EnumerateFiles(this.RootDirectory, "*.tmp-*", SearchOption.AllDirectories))
TryDeleteFile(temporaryPath);
await this.LoadSelectionAsync(token);
this.initialized = true;
}
finally
{
this.initializationLock.Release();
}
}
}
@@ -0,0 +1,27 @@
using AIStudio.Settings;
using ComponentKind = AIStudio.Tools.Components;
namespace AIStudio.Assistants.BeyondFactsLab;
/// <summary>
/// Decides whether the BeyondFacts Lab may be opened.
/// </summary>
/// <remarks>
/// The lab is a prototype behind its own preview feature, and an organization can hide it like any other
/// assistant. The tile asks the same question through its required preview feature; the page asks here,
/// because somebody might reach its address without the tile, e.g., from a link or the browser history.
/// </remarks>
public static class BflAccess
{
/// <summary>
/// Checks whether the BeyondFacts Lab may be opened with the given settings.
/// </summary>
/// <param name="settingsManager">The settings to check.</param>
/// <param name="assistantName">The name of the lab for the log; without one, nothing gets logged.</param>
/// <returns>True, when the preview feature is switched on and no configuration hides the lab.</returns>
public static bool CanOpen(SettingsManager settingsManager, string assistantName = "") => settingsManager.IsAssistantVisible(
ComponentKind.BEYOND_FACTS_LAB_ASSISTANT,
assistantName: assistantName,
requiredPreviewFeature: ComponentKind.BEYOND_FACTS_LAB_ASSISTANT.RequiredPreviewFeature);
}
@@ -0,0 +1,17 @@
namespace AIStudio.Assistants.BeyondFactsLab;
/// <summary>
/// How the BeyondFacts Lab addresses the person in languages which tell a formal address from an informal one, such as German.
/// </summary>
public enum BflAddressForm
{
/// <summary>
/// The formal address, such as "Sie" in German. The texts of AI Studio use it as well.
/// </summary>
FORMAL,
/// <summary>
/// The informal address, such as "du" in German.
/// </summary>
INFORMAL,
}
@@ -0,0 +1,21 @@
namespace AIStudio.Assistants.BeyondFactsLab;
public static class BflAddressFormExtensions
{
private static string TB(string fallbackEN) => Tools.PluginSystem.I18N.I.T(fallbackEN, typeof(BflAddressFormExtensions).Namespace, nameof(BflAddressFormExtensions));
/// <param name="addressForm">The form of address.</param>
extension(BflAddressForm addressForm)
{
/// <summary>
/// Gets the name of the form of address, as the settings show it.
/// </summary>
public string Name => addressForm switch
{
BflAddressForm.FORMAL => TB("Formal address (Sie in German)"),
BflAddressForm.INFORMAL => TB("Informal address (du in German)"),
_ => TB("Formal address (Sie in German)"),
};
}
}
@@ -0,0 +1,124 @@
namespace AIStudio.Assistants.BeyondFactsLab;
/// <summary>
/// Every field of the forms "Your details" in the BeyondFacts Lab.
/// </summary>
/// <remarks>
/// The forms store their values under the IDs of the fields and options, so these IDs never change. A field which
/// is no longer needed keeps its ID reserved. The texts of the fields come from BflFieldTexts.
/// </remarks>
public static class BflFieldCatalog
{
// Event details:
public const string FORMAT = "format";
public const string EVENT_TOPIC = "event_topic";
public const string AUDIENCE = "audience";
public const string CONTRIBUTION_TOPIC = "contribution_topic";
public const string EVENT_DATE = "event_date";
public const string EVENT_LOCATION = "event_location";
public const string EVENT_DURATION = "event_duration";
public const string CONTRIBUTION_DURATION = "contribution_duration";
public const string OTHER_PARTICIPANTS = "other_participants";
public const string EVENT_LINK = "event_link";
// Knowledge and uncertainty:
public const string CONTRIBUTION_SUMMARY = "contribution_summary";
public const string CORE_THESES = "core_theses";
public const string UNCERTAINTIES = "uncertainties";
public const string MATERIAL_NOTES = "material_notes";
// Goals and role:
public const string GOALS = "goals";
public const string GOAL_DIRECTIONS = "goal_directions";
// Values and perspectives:
public const string VALUES_AND_CONCERNS = "values_and_concerns";
public const string PERSONAL_CONCERN = "personal_concern";
public const string OTHER_PERSPECTIVES = "other_perspectives";
// Strategy:
public const string COMMUNICATION_WORRIES = "communication_worries";
// Test situation:
public const string QUESTION_TYPE = "question_type";
public const string DIFFICULTY = "difficulty";
public const string TEST_NOTES = "test_notes";
// Results and materials:
public const string POSITION_CHANGES = "position_changes";
// The options of the directions of a goal:
public const string GOAL_INFORM = "goal_inform";
public const string GOAL_MODERATE = "goal_moderate";
public const string GOAL_TAKE_POSITION = "goal_take_position";
public const string GOAL_RAISE_AWARENESS = "goal_raise_awareness";
public const string GOAL_SHOW_OPTIONS = "goal_show_options";
public const string GOAL_INFLUENCE_POLICY = "goal_influence_policy";
// The options of the type of questions:
public const string QUESTIONS_MIXED = "questions_mixed";
public const string QUESTIONS_SCIENTIFIC_CRITICISM = "questions_scientific_criticism";
public const string QUESTIONS_EMOTIONAL_CRITICISM = "questions_emotional_criticism";
public const string QUESTIONS_MANIPULATIVE = "questions_manipulative";
public const string QUESTIONS_POLITICAL_INSTRUMENTALIZATION = "questions_political_instrumentalization";
// The options of the difficulty:
public const string DIFFICULTY_MODERATE = "difficulty_moderate";
public const string DIFFICULTY_DEMANDING = "difficulty_demanding";
public const string DIFFICULTY_VERY_DEMANDING = "difficulty_very_demanding";
/// <summary>
/// Every field, step by step, in the order the forms show them.
/// </summary>
public static readonly IReadOnlyList<BflFieldDefinition> FIELDS =
[
new(FORMAT, BflStep.FRAMEWORK, BflFieldKind.SINGLE_LINE) { IsKeyDetail = true, IsSharedWithResearch = true },
new(EVENT_TOPIC, BflStep.FRAMEWORK, BflFieldKind.SINGLE_LINE) { IsKeyDetail = true, IsSharedWithResearch = true },
new(AUDIENCE, BflStep.FRAMEWORK, BflFieldKind.SINGLE_LINE) { IsKeyDetail = true },
// The topic of the contribution may be unpublished, so it never reaches the research:
new(CONTRIBUTION_TOPIC, BflStep.FRAMEWORK, BflFieldKind.MULTI_LINE) { IsKeyDetail = true },
new(EVENT_DATE, BflStep.FRAMEWORK, BflFieldKind.SINGLE_LINE) { IsSharedWithResearch = true },
new(EVENT_LOCATION, BflStep.FRAMEWORK, BflFieldKind.SINGLE_LINE) { IsSharedWithResearch = true },
new(EVENT_DURATION, BflStep.FRAMEWORK, BflFieldKind.SINGLE_LINE),
new(CONTRIBUTION_DURATION, BflStep.FRAMEWORK, BflFieldKind.SINGLE_LINE),
new(OTHER_PARTICIPANTS, BflStep.FRAMEWORK, BflFieldKind.MULTI_LINE) { IsSharedWithResearch = true, NameRole = BflNameRole.PARTICIPANTS },
new(EVENT_LINK, BflStep.FRAMEWORK, BflFieldKind.LINK) { IsSharedWithResearch = true },
new(CONTRIBUTION_SUMMARY, BflStep.KNOWLEDGE, BflFieldKind.MULTI_LINE) { IsKeyDetail = true },
new(CORE_THESES, BflStep.KNOWLEDGE, BflFieldKind.MULTI_LINE),
new(UNCERTAINTIES, BflStep.KNOWLEDGE, BflFieldKind.MULTI_LINE),
new(MATERIAL_NOTES, BflStep.KNOWLEDGE, BflFieldKind.MULTI_LINE),
new(GOALS, BflStep.GOALS_AND_ROLE, BflFieldKind.MULTI_LINE),
new(GOAL_DIRECTIONS, BflStep.GOALS_AND_ROLE, BflFieldKind.MULTI_CHOICE)
{
Options = [new(GOAL_INFORM), new(GOAL_MODERATE), new(GOAL_TAKE_POSITION), new(GOAL_RAISE_AWARENESS), new(GOAL_SHOW_OPTIONS), new(GOAL_INFLUENCE_POLICY)],
},
new(VALUES_AND_CONCERNS, BflStep.VALUES_AND_PERSPECTIVES, BflFieldKind.MULTI_LINE),
new(PERSONAL_CONCERN, BflStep.VALUES_AND_PERSPECTIVES, BflFieldKind.MULTI_LINE) { IsPersonalDisclosure = true },
new(OTHER_PERSPECTIVES, BflStep.VALUES_AND_PERSPECTIVES, BflFieldKind.MULTI_LINE),
new(COMMUNICATION_WORRIES, BflStep.STRATEGY, BflFieldKind.MULTI_LINE),
new(QUESTION_TYPE, BflStep.TEST_SITUATION, BflFieldKind.CHOICE)
{
Options = [new(QUESTIONS_MIXED), new(QUESTIONS_SCIENTIFIC_CRITICISM), new(QUESTIONS_EMOTIONAL_CRITICISM), new(QUESTIONS_MANIPULATIVE), new(QUESTIONS_POLITICAL_INSTRUMENTALIZATION)],
},
new(DIFFICULTY, BflStep.TEST_SITUATION, BflFieldKind.CHOICE)
{
Options = [new(DIFFICULTY_MODERATE), new(DIFFICULTY_DEMANDING), new(DIFFICULTY_VERY_DEMANDING)],
},
new(TEST_NOTES, BflStep.TEST_SITUATION, BflFieldKind.MULTI_LINE),
new(POSITION_CHANGES, BflStep.RESULTS, BflFieldKind.MULTI_LINE),
];
/// <summary>
/// Gets the fields of a step, in the order its form shows them.
/// </summary>
/// <param name="step">The step.</param>
public static IReadOnlyList<BflFieldDefinition> ForStep(BflStep step) => [.. FIELDS.Where(field => field.Step == step)];
}
@@ -0,0 +1,40 @@
namespace AIStudio.Assistants.BeyondFactsLab;
/// <summary>
/// A field of the form "Your details" of a step in the BeyondFacts Lab.
/// </summary>
/// <param name="Id">The ID of the field, which a form stores. It never changes.</param>
/// <param name="Step">The step whose form shows the field.</param>
/// <param name="Kind">How the field takes its value.</param>
public sealed record BflFieldDefinition(string Id, BflStep Step, BflFieldKind Kind)
{
/// <summary>
/// Gets whether the step works poorly without the field, so the lab marks it as missing while it is empty.
/// The person may continue anyway; the chat asks for it.
/// </summary>
public bool IsKeyDetail { get; init; }
/// <summary>
/// Gets whether the field holds something personal, which exports and sharing leave out unless the person
/// chooses otherwise.
/// </summary>
public bool IsPersonalDisclosure { get; init; }
/// <summary>
/// Gets whether the field holds public details about the event, which the research on the web may use.
/// </summary>
/// <remarks>
/// Nothing else of a preparation reaches the research: neither the other fields, nor materials, nor work states.
/// </remarks>
public bool IsSharedWithResearch { get; init; }
/// <summary>
/// Gets whether the field holds names of people.
/// </summary>
public BflNameRole NameRole { get; init; } = BflNameRole.NONE;
/// <summary>
/// Gets the options of a field which offers a choice, in the order to show them.
/// </summary>
public IReadOnlyList<BflFieldOption> Options { get; init; } = [];
}
@@ -0,0 +1,32 @@
namespace AIStudio.Assistants.BeyondFactsLab;
/// <summary>
/// How a field of the BeyondFacts Lab takes its value.
/// </summary>
public enum BflFieldKind
{
/// <summary>
/// A short text in one line.
/// </summary>
SINGLE_LINE,
/// <summary>
/// A longer text over several lines.
/// </summary>
MULTI_LINE,
/// <summary>
/// The address of a web page.
/// </summary>
LINK,
/// <summary>
/// One of the options of the field.
/// </summary>
CHOICE,
/// <summary>
/// Any number of the options of the field.
/// </summary>
MULTI_CHOICE,
}
@@ -0,0 +1,31 @@
namespace AIStudio.Assistants.BeyondFactsLab;
public static class BflFieldKindExtensions
{
/// <param name="kind">The kind of field.</param>
extension(BflFieldKind kind)
{
/// <summary>
/// Gets the longest value a field of this kind takes, in characters.
/// </summary>
/// <remarks>
/// The values go into the prompts of the chats, so each kind gets a limit that leaves room for the
/// rest of a preparation.
/// </remarks>
public int MaxLength => kind switch
{
BflFieldKind.SINGLE_LINE => 300,
BflFieldKind.MULTI_LINE => 4_000,
BflFieldKind.LINK => 2_000,
BflFieldKind.CHOICE => 100,
BflFieldKind.MULTI_CHOICE => 1_000,
_ => 0,
};
/// <summary>
/// Gets whether a field of this kind offers options to choose from.
/// </summary>
public bool HasOptions => kind is BflFieldKind.CHOICE or BflFieldKind.MULTI_CHOICE;
}
}
@@ -0,0 +1,7 @@
namespace AIStudio.Assistants.BeyondFactsLab;
/// <summary>
/// An option of a field of the BeyondFacts Lab.
/// </summary>
/// <param name="Id">The ID of the option, which a form stores. It never changes and is unique across all fields.</param>
public sealed record BflFieldOption(string Id);
@@ -0,0 +1,112 @@
namespace AIStudio.Assistants.BeyondFactsLab;
/// <summary>
/// The texts of the fields and options of the BeyondFacts Lab, in the language of the app.
/// </summary>
public static class BflFieldTexts
{
private static string TB(string fallbackEN) => Tools.PluginSystem.I18N.I.T(fallbackEN, typeof(BflFieldTexts).Namespace, nameof(BflFieldTexts));
/// <param name="definition">The field.</param>
extension(BflFieldDefinition definition)
{
/// <summary>
/// Gets the label of the field.
/// </summary>
public string Label => definition.Id switch
{
BflFieldCatalog.FORMAT => TB("Format of the event"),
BflFieldCatalog.EVENT_TOPIC => TB("Topic of the event"),
BflFieldCatalog.AUDIENCE => TB("Audience"),
BflFieldCatalog.CONTRIBUTION_TOPIC => TB("Topic of your contribution or your specific assignment"),
BflFieldCatalog.EVENT_DATE => TB("Date and time"),
BflFieldCatalog.EVENT_LOCATION => TB("Place"),
BflFieldCatalog.EVENT_DURATION => TB("Total duration of the event"),
BflFieldCatalog.CONTRIBUTION_DURATION => TB("Duration of your contribution"),
BflFieldCatalog.OTHER_PARTICIPANTS => TB("Other participants"),
BflFieldCatalog.EVENT_LINK => TB("Link to the event"),
BflFieldCatalog.CONTRIBUTION_SUMMARY => TB("Short description of your contribution"),
BflFieldCatalog.CORE_THESES => TB("Core theses and arguments (if any)"),
BflFieldCatalog.UNCERTAINTIES => TB("Uncertainties and controversies in content and science (if known)"),
BflFieldCatalog.MATERIAL_NOTES => TB("Notes on how to use your materials"),
BflFieldCatalog.GOALS => TB("What do you want to achieve with your contribution?"),
BflFieldCatalog.GOAL_DIRECTIONS => TB("Suggestions"),
BflFieldCatalog.VALUES_AND_CONCERNS => TB("Values and concerns: What matters most to you about this topic?"),
BflFieldCatalog.PERSONAL_CONCERN => TB("Personal involvement: Where does the topic touch you personally? Which question or criticism could trigger a strong reaction?"),
BflFieldCatalog.OTHER_PERSPECTIVES => TB("Other perspectives: Whose point of view would you like to understand better, or have you hardly considered so far?"),
BflFieldCatalog.COMMUNICATION_WORRIES => TB("Uncertainties and worries about communicating"),
BflFieldCatalog.QUESTION_TYPE => TB("Type of questions"),
BflFieldCatalog.DIFFICULTY => TB("Difficulty at the start"),
BflFieldCatalog.TEST_NOTES => TB("Special notes for the test"),
BflFieldCatalog.POSITION_CHANGES => TB("What have you changed, kept, or left open compared to your initial position, and why?"),
_ => string.Empty,
};
/// <summary>
/// Gets the help text below the field, or an empty string when it needs none.
/// </summary>
public string Hint => definition.Id switch
{
BflFieldCatalog.FORMAT => TB("E.g., panel, keynote, or interview; online, on site, or hybrid"),
BflFieldCatalog.EVENT_TOPIC => TB("The central question or the title"),
BflFieldCatalog.AUDIENCE => TB("E.g., students, experts, or the general public"),
BflFieldCatalog.CONTRIBUTION_TOPIC => TB("Stays in your preparation and never goes into the research on the web"),
BflFieldCatalog.EVENT_DATE => TB("E.g., December 10, 2 pm"),
BflFieldCatalog.EVENT_LOCATION => TB("E.g., the venue and the city, or online"),
BflFieldCatalog.EVENT_DURATION => TB("E.g., 90 minutes"),
BflFieldCatalog.CONTRIBUTION_DURATION => TB("E.g., 8 minutes"),
BflFieldCatalog.OTHER_PARTICIPANTS => TB("Names and roles, as far as you know them"),
BflFieldCatalog.EVENT_LINK => TB("The address of the event page, beginning with https://"),
BflFieldCatalog.CONTRIBUTION_SUMMARY => TB("A few sentences are enough"),
BflFieldCatalog.MATERIAL_NOTES => TB("E.g., use only paper X as the basis for the content"),
BflFieldCatalog.GOALS => TB("In your own words, with the suggestions below, or still open: the chat sharpens your goal and role with you."),
BflFieldCatalog.PERSONAL_CONCERN => TB("Personal detail: it goes into exports and shared sessions only when you explicitly choose so."),
BflFieldCatalog.COMMUNICATION_WORRIES => TB("E.g., aggressive moderation, time pressure, or a particular counterpart"),
BflFieldCatalog.DIFFICULTY => TB("You can raise the difficulty after each question."),
BflFieldCatalog.TEST_NOTES => TB("E.g., take up a confirmed conflict of values or a perspective from the step about values and perspectives"),
_ => string.Empty,
};
}
/// <param name="option">The option.</param>
extension(BflFieldOption option)
{
/// <summary>
/// Gets the label of the option.
/// </summary>
public string Label => option.Id switch
{
BflFieldCatalog.GOAL_INFORM => TB("Inform"),
BflFieldCatalog.GOAL_MODERATE => TB("Moderate the discussion"),
BflFieldCatalog.GOAL_TAKE_POSITION => TB("Take a position in the debate"),
BflFieldCatalog.GOAL_RAISE_AWARENESS => TB("Raise awareness"),
BflFieldCatalog.GOAL_SHOW_OPTIONS => TB("Show options for action"),
BflFieldCatalog.GOAL_INFLUENCE_POLICY => TB("Influence policy"),
BflFieldCatalog.QUESTIONS_MIXED => TB("Mixed"),
BflFieldCatalog.QUESTIONS_SCIENTIFIC_CRITICISM => TB("Scientific criticism"),
BflFieldCatalog.QUESTIONS_EMOTIONAL_CRITICISM => TB("Emotionally charged criticism"),
BflFieldCatalog.QUESTIONS_MANIPULATIVE => TB("Manipulative questions and straw men"),
BflFieldCatalog.QUESTIONS_POLITICAL_INSTRUMENTALIZATION => TB("Political instrumentalization"),
BflFieldCatalog.DIFFICULTY_MODERATE => TB("Moderate"),
BflFieldCatalog.DIFFICULTY_DEMANDING => TB("Demanding"),
BflFieldCatalog.DIFFICULTY_VERY_DEMANDING => TB("Very demanding"),
_ => string.Empty,
};
}
}
@@ -0,0 +1,27 @@
namespace AIStudio.Assistants.BeyondFactsLab;
/// <summary>
/// Finds the key details a preparation of the BeyondFacts Lab still lacks.
/// </summary>
/// <remarks>
/// Missing details never block anything: the person may continue, and the chat asks for them.
/// </remarks>
public static class BflMissingDetails
{
/// <summary>
/// Gets the key details of a step which its form leaves empty.
/// </summary>
/// <param name="step">The step.</param>
/// <param name="form">The form of the step.</param>
/// <returns>The fields which are missing, in the order the form shows them.</returns>
public static IReadOnlyList<BflFieldDefinition> Of(BflStep step, BflStepFormData form) =>
[.. BflFieldCatalog.ForStep(step).Where(field => field.IsKeyDetail && string.IsNullOrWhiteSpace(form.Get(field.Id)))];
/// <summary>
/// Gets the steps whose forms leave key details empty.
/// </summary>
/// <param name="forms">The forms of a preparation, by their steps; a step without a form counts as empty.</param>
/// <returns>The steps with missing details, in their order.</returns>
public static IReadOnlyList<BflStep> StepsOf(IReadOnlyDictionary<BflStep, BflStepFormData> forms) =>
[.. BflStep.All.Where(step => Of(step, forms.GetValueOrDefault(step) ?? new BflStepFormData()).Count > 0)];
}
@@ -0,0 +1,17 @@
namespace AIStudio.Assistants.BeyondFactsLab;
/// <summary>
/// Whether a field of the BeyondFacts Lab holds names of people, which sharing a session replaces.
/// </summary>
public enum BflNameRole
{
/// <summary>
/// The field holds no names on purpose.
/// </summary>
NONE,
/// <summary>
/// The field holds the names of other participants of the event.
/// </summary>
PARTICIPANTS,
}
@@ -0,0 +1,50 @@
using System.Security.Cryptography;
using System.Text;
namespace AIStudio.Assistants.BeyondFactsLab;
/// <summary>
/// The notes which the BeyondFacts Lab shows before the first use, and again whenever their text changes.
/// </summary>
/// <remarks>
/// The notes are a text of the localization, so they come in every language of the app. The settings keep a hash of
/// the notes as the app showed them when the person confirmed them. A new wording, in English or in a translation,
/// therefore shows them again, and so does a switch to another language, whose notes the person has not read yet.
/// </remarks>
public static class BflNotice
{
private static string TB(string fallbackEN) => Tools.PluginSystem.I18N.I.T(fallbackEN, typeof(BflNotice).Namespace, nameof(BflNotice));
/// <summary>
/// Gets the notes in the language of the app, as Markdown.
/// </summary>
public static string Text => TB("""
## Welcome!
The BeyondFacts Lab guides you through an AI-supported process of reflection, which helps you take a position in challenging communication situations and stay able to communicate.
Data protection depends on the AI provider you choose, its rules for processing and storing data, and your configuration. Enter only data that are suitable and approved for this.
Please check all results critically. Depending on the AI model, the answers may differ. Hallucinations cannot be ruled out.
This is a prototype, so we look forward to your feedback.
""");
/// <summary>
/// Computes the hash of the notes as the app shows them.
/// </summary>
/// <remarks>
/// Line endings and the space around the text do not count, so that a build from a checkout with other line endings
/// does not show the notes again.
/// </remarks>
/// <param name="notes">The notes, as the app shows them.</param>
/// <returns>The hash, as lowercase hexadecimal digits.</returns>
public static string ComputeHash(string notes) => Convert.ToHexStringLower(SHA256.HashData(Encoding.UTF8.GetBytes(notes.ReplaceLineEndings("\n").Trim())));
/// <summary>
/// Gets whether the person confirmed the notes as the app shows them now.
/// </summary>
/// <param name="acceptedHash">The hash the settings keep.</param>
/// <param name="currentHash">The hash of the notes as the app shows them now.</param>
public static bool IsAccepted(string acceptedHash, string currentHash) => string.Equals(acceptedHash, currentHash, StringComparison.OrdinalIgnoreCase);
}
@@ -0,0 +1,11 @@
@inherits MSGComponentBase
<MudDialog>
<DialogContent>
<MudJustifiedMarkdown Value="@this.Notice"/>
</DialogContent>
<DialogActions>
<MudButton OnClick="@this.Confirm" Variant="Variant.Filled" Color="Color.Primary">
@T("I have read the notes")
</MudButton>
</DialogActions>
</MudDialog>
@@ -0,0 +1,22 @@
using AIStudio.Components;
using Microsoft.AspNetCore.Components;
namespace AIStudio.Assistants.BeyondFactsLab;
/// <summary>
/// Shows the notes of the BeyondFacts Lab, which the person confirms having read.
/// </summary>
public partial class BflNoticeDialog : MSGComponentBase
{
[CascadingParameter]
private IMudDialogInstance MudDialog { get; set; } = null!;
/// <summary>
/// Gets or sets the notes, as Markdown.
/// </summary>
[Parameter]
public string Notice { get; set; } = string.Empty;
private void Confirm() => this.MudDialog.Close(DialogResult.Ok(true));
}
@@ -0,0 +1,57 @@
namespace AIStudio.Assistants.BeyondFactsLab;
/// <summary>
/// A preparation in the BeyondFacts Lab: one event, for which a person works through the steps of the lab.
/// </summary>
/// <remarks>
/// The lab stores it as project.json in the folder of the preparation. Everything else the preparation
/// holds lies in files beside it, which the lab finds by the ID of the preparation, never by a stored path.
/// </remarks>
public sealed class BflProject
{
/// <summary>
/// The version of the stored form this version of AI Studio writes.
/// </summary>
/// <remarks>
/// A preparation of a newer form is listed but neither opened nor written, so that an older version of
/// AI Studio cannot lose what a newer one stored. Raise it with every field a released version does not
/// know yet: that version refuses fields it does not know, and only the higher number tells the person
/// that a newer version stored the preparation.
/// </remarks>
public const int CURRENT_SCHEMA_VERSION = 1;
/// <summary>
/// Gets or sets the version of the stored form.
/// </summary>
public int SchemaVersion { get; set; } = CURRENT_SCHEMA_VERSION;
/// <summary>
/// Gets or sets the ID of the preparation, which is also the name of its folder.
/// </summary>
public Guid ProjectId { get; set; }
/// <summary>
/// Gets or sets the name the person gave the preparation.
/// </summary>
public string Name { get; set; } = string.Empty;
/// <summary>
/// Gets or sets when the preparation was created.
/// </summary>
public DateTimeOffset CreatedAtUtc { get; set; }
/// <summary>
/// Gets or sets when the preparation was changed last.
/// </summary>
public DateTimeOffset ModifiedAtUtc { get; set; }
/// <summary>
/// Gets or sets when the person marked the event as having taken place, or null while it has not.
/// </summary>
public DateTimeOffset? HappenedAtUtc { get; set; }
/// <summary>
/// Gets or sets the step the person looked at last, where the lab opens the preparation again.
/// </summary>
public BflStep CurrentStep { get; set; } = BflStep.FRAMEWORK;
}
@@ -0,0 +1,23 @@
namespace AIStudio.Assistants.BeyondFactsLab;
/// <summary>
/// A preparation in the list of the BeyondFacts Lab, also when it cannot be opened.
/// </summary>
/// <param name="ProjectId">The ID of the preparation.</param>
/// <param name="Name">The name to show; empty when even the name cannot be read.</param>
/// <param name="ModifiedAtUtc">When the preparation was changed last.</param>
/// <param name="Status">Whether the preparation can be opened.</param>
/// <param name="Project">The preparation, when it can be opened.</param>
public sealed record BflProjectEntry(Guid ProjectId, string Name, DateTimeOffset ModifiedAtUtc, BflProjectLoadStatus Status, BflProject? Project)
{
/// <summary>
/// Gets whether the preparation can be opened.
/// </summary>
public bool IsAvailable => this.Status is BflProjectLoadStatus.AVAILABLE && this.Project is not null;
/// <summary>
/// Creates the entry of a preparation which can be opened.
/// </summary>
/// <param name="project">The preparation.</param>
public static BflProjectEntry FromProject(BflProject project) => new(project.ProjectId, project.Name, project.ModifiedAtUtc, BflProjectLoadStatus.AVAILABLE, project);
}
@@ -0,0 +1,22 @@
namespace AIStudio.Assistants.BeyondFactsLab;
/// <summary>
/// Whether this version of AI Studio can open a stored preparation of the BeyondFacts Lab.
/// </summary>
public enum BflProjectLoadStatus
{
/// <summary>
/// The preparation can be opened.
/// </summary>
AVAILABLE,
/// <summary>
/// A newer version of AI Studio stored the preparation. It stays as it is until that version opens it again.
/// </summary>
NEWER_VERSION,
/// <summary>
/// The preparation cannot be read, e.g., because its file is damaged. It stays as it is; nothing deletes it on its own.
/// </summary>
UNAVAILABLE,
}
@@ -0,0 +1,29 @@
namespace AIStudio.Assistants.BeyondFactsLab;
/// <summary>
/// Decides which preparation the BeyondFacts Lab shows after its list changed.
/// </summary>
public static class BflProjectSelection
{
/// <summary>
/// Chooses the preparation to show: the one asked for, else the one selected last, else the one changed last.
/// </summary>
/// <param name="projects">The preparations, the one changed last first.</param>
/// <param name="preferredId">The preparation asked for, e.g., one just created or the one shown so far.</param>
/// <param name="lastSelectedId">The preparation the person selected last, from an earlier session.</param>
/// <returns>The preparation to show, or null when there is none.</returns>
public static BflProjectEntry? Choose(IReadOnlyList<BflProjectEntry> projects, Guid? preferredId, Guid? lastSelectedId)
{
foreach (var candidateId in new[] { preferredId, lastSelectedId })
{
if (candidateId is null)
continue;
var candidate = projects.FirstOrDefault(project => project.ProjectId == candidateId);
if (candidate is not null)
return candidate;
}
return projects.Count > 0 ? projects[0] : null;
}
}
@@ -0,0 +1,45 @@
namespace AIStudio.Assistants.BeyondFactsLab;
/// <summary>
/// The steps of a preparation in the BeyondFacts Lab, in the order the lab suggests.
/// </summary>
/// <remarks>
/// The names are stored, e.g., as the step shown last, so they never change. People may jump to any step.
/// </remarks>
public enum BflStep
{
/// <summary>
/// What the event is about, for whom, and what the person was asked to contribute.
/// </summary>
FRAMEWORK,
/// <summary>
/// What the person knows for sure, what is uncertain, and where their expertise ends.
/// </summary>
KNOWLEDGE,
/// <summary>
/// What the person wants to achieve, and in which role they speak.
/// </summary>
GOALS_AND_ROLE,
/// <summary>
/// What matters to the person, what touches them, and what moves others.
/// </summary>
VALUES_AND_PERSPECTIVES,
/// <summary>
/// How the person conveys evidence, trade-offs between values, and open questions.
/// </summary>
STRATEGY,
/// <summary>
/// Critical questions and opposing positions, played through.
/// </summary>
TEST_SITUATION,
/// <summary>
/// What the person takes away from the preparation, and the materials for the event.
/// </summary>
RESULTS,
}
@@ -0,0 +1,77 @@
namespace AIStudio.Assistants.BeyondFactsLab;
public static class BflStepExtensions
{
private static string TB(string fallbackEN) => Tools.PluginSystem.I18N.I.T(fallbackEN, typeof(BflStepExtensions).Namespace, nameof(BflStepExtensions));
/// <param name="step">The step.</param>
extension(BflStep step)
{
/// <summary>
/// Gets the title of the step. It names the topic, never a number.
/// </summary>
public string Title => step switch
{
BflStep.FRAMEWORK => TB("Event details"),
BflStep.KNOWLEDGE => TB("Knowledge and uncertainty"),
BflStep.GOALS_AND_ROLE => TB("Goals and role"),
BflStep.VALUES_AND_PERSPECTIVES => TB("Values and perspectives"),
BflStep.STRATEGY => TB("Strategy"),
BflStep.TEST_SITUATION => TB("Test situation"),
BflStep.RESULTS => TB("Results and materials"),
_ => string.Empty,
};
/// <summary>
/// Gets one sentence about what the step is for.
/// </summary>
public string Description => step switch
{
BflStep.FRAMEWORK => TB("What the event is about, for whom, and what you were asked to contribute."),
BflStep.KNOWLEDGE => TB("What you know for sure, what is uncertain, and where your expertise ends."),
BflStep.GOALS_AND_ROLE => TB("What you want to achieve, and in which role you speak."),
BflStep.VALUES_AND_PERSPECTIVES => TB("What matters to you, what touches you, and what moves others."),
BflStep.STRATEGY => TB("How you convey evidence, trade-offs between values, and open questions."),
BflStep.TEST_SITUATION => TB("Play through critical questions and opposing positions."),
BflStep.RESULTS => TB("What you take away from your preparation."),
_ => string.Empty,
};
/// <summary>
/// Gets a hint for the form of the step, or an empty string when the step needs none.
/// </summary>
public string FormHint => step switch
{
BflStep.VALUES_AND_PERSPECTIVES => TB("You can fill in single fields or start right away in the chat."),
_ => string.Empty,
};
/// <summary>
/// Gets the name of the folder in which a preparation keeps the files of the step.
/// </summary>
/// <remarks>
/// The number keeps the folders in the order of the steps for people who look into the data folder.
/// These names are stored on disk, so they never change.
/// </remarks>
public string FolderName => step switch
{
BflStep.FRAMEWORK => "01-framework",
BflStep.KNOWLEDGE => "02-knowledge",
BflStep.GOALS_AND_ROLE => "03-goals",
BflStep.VALUES_AND_PERSPECTIVES => "04-values",
BflStep.STRATEGY => "05-strategy",
BflStep.TEST_SITUATION => "06-test",
BflStep.RESULTS => "07-results",
_ => throw new ArgumentOutOfRangeException(nameof(step), step, "The step has no folder."),
};
/// <summary>
/// Gets every step, in the order the lab suggests.
/// </summary>
public static IReadOnlyList<BflStep> All => Enum.GetValues<BflStep>();
}
}
@@ -0,0 +1,97 @@
@inherits MSGComponentBase
@if (this.MissingFields.Count > 0)
{
<MudAlert Severity="Severity.Info" Variant="Variant.Outlined" Dense="true" Class="mb-3">
@T("Some details are still missing. The chat will ask for them, and you can continue anyway.")
<br/>
@string.Join(", ", this.MissingFields.Select(field => field.Label))
</MudAlert>
}
@if (!string.IsNullOrWhiteSpace(this.Step.FormHint))
{
<MudText Typo="Typo.body2" Class="mb-3">@this.Step.FormHint</MudText>
}
<MudStack Spacing="3">
@foreach (var field in this.Fields)
{
switch (field.Kind)
{
case BflFieldKind.SINGLE_LINE:
case BflFieldKind.LINK:
<MudTextField @key="field.Id"
T="string"
Value="@this.Form.Get(field.Id)"
ValueChanged="@(value => this.SetValueAsync(field, value))"
Label="@LabelOf(field)"
HelperText="@field.Hint"
MaxLength="@field.Kind.MaxLength"
Validation="@this.ValidationOf(field)"
Disabled="@this.Disabled"
Variant="Variant.Outlined"/>
break;
case BflFieldKind.MULTI_LINE:
<MudTextField @key="field.Id"
T="string"
Value="@this.Form.Get(field.Id)"
ValueChanged="@(value => this.SetValueAsync(field, value))"
Label="@LabelOf(field)"
HelperText="@field.Hint"
MaxLength="@field.Kind.MaxLength"
Disabled="@this.Disabled"
Variant="Variant.Outlined"
Lines="3"
Sizing="InputSizing.Auto"
MaxLines="12"/>
break;
case BflFieldKind.CHOICE:
<MudSelect @key="field.Id"
T="string"
Value="@this.Form.Get(field.Id)"
ValueChanged="@(value => this.SetValueAsync(field, value))"
Label="@LabelOf(field)"
HelperText="@field.Hint"
Clearable="true"
Disabled="@this.Disabled"
Variant="Variant.Outlined">
@foreach (var option in field.Options)
{
<MudSelectItem T="string" Value="@option.Id">@option.Label</MudSelectItem>
}
</MudSelect>
break;
case BflFieldKind.MULTI_CHOICE:
<div @key="field.Id">
<MudText Typo="Typo.body2">@LabelOf(field)</MudText>
<MudChipSet T="string"
SelectionMode="SelectionMode.MultiSelection"
SelectedValues="@this.Form.GetChoices(field.Id)"
SelectedValuesChanged="@(values => this.SetChoicesAsync(field, values))"
CheckMark="true"
Color="Color.Primary"
Variant="Variant.Outlined"
Disabled="@this.Disabled">
@foreach (var option in field.Options)
{
<MudChip T="string" Value="@option.Id" Text="@option.Label"/>
}
</MudChipSet>
@if (!string.IsNullOrWhiteSpace(field.Hint))
{
<MudText Typo="Typo.caption">@field.Hint</MudText>
}
</div>
break;
}
}
</MudStack>
@if (this.HasKeyDetails)
{
<MudText Typo="Typo.caption" Class="mt-2">@T("* The chat works best when you fill in these details.")</MudText>
}
@@ -0,0 +1,75 @@
using AIStudio.Components;
using Microsoft.AspNetCore.Components;
namespace AIStudio.Assistants.BeyondFactsLab;
/// <summary>
/// The form "Your details" of one step of the BeyondFacts Lab.
/// </summary>
/// <remarks>
/// The form changes the values it gets and tells its parent, which stores them. Empty key details are marked but
/// never block anything.
/// </remarks>
public partial class BflStepForm : MSGComponentBase
{
/// <summary>
/// Gets or sets the step whose fields the form shows.
/// </summary>
[Parameter, EditorRequired]
public BflStep Step { get; set; }
/// <summary>
/// Gets or sets the values of the form, which the form changes.
/// </summary>
[Parameter, EditorRequired]
public BflStepFormData Form { get; set; } = new();
/// <summary>
/// Gets or sets whether the fields cannot be changed.
/// </summary>
[Parameter]
public bool Disabled { get; set; }
/// <summary>
/// Gets or sets the callback raised after a value changed.
/// </summary>
[Parameter]
public EventCallback FormChanged { get; set; }
private IReadOnlyList<BflFieldDefinition> Fields => BflFieldCatalog.ForStep(this.Step);
private IReadOnlyList<BflFieldDefinition> MissingFields => BflMissingDetails.Of(this.Step, this.Form);
private bool HasKeyDetails => this.Fields.Any(definition => definition.IsKeyDetail);
private static string LabelOf(BflFieldDefinition field) => field.IsKeyDetail ? $"{field.Label} *" : field.Label;
private async Task SetValueAsync(BflFieldDefinition field, string? value)
{
var limited = value is not null && value.Length > field.Kind.MaxLength ? value[..field.Kind.MaxLength] : value;
if (string.Equals(this.Form.Get(field.Id), limited ?? string.Empty, StringComparison.Ordinal))
return;
this.Form.Set(field.Id, limited);
await this.FormChanged.InvokeAsync();
}
private async Task SetChoicesAsync(BflFieldDefinition field, IReadOnlyCollection<string>? optionIds)
{
this.Form.SetChoices(field, optionIds ?? []);
await this.FormChanged.InvokeAsync();
}
private Func<string, string?>? ValidationOf(BflFieldDefinition field) => field.Kind is BflFieldKind.LINK ? this.ValidateLink : null;
private string? ValidateLink(string value)
{
if (string.IsNullOrWhiteSpace(value))
return null;
return Uri.TryCreate(value.Trim(), UriKind.Absolute, out var uri) && (uri.Scheme == Uri.UriSchemeHttps || uri.Scheme == Uri.UriSchemeHttp)
? null
: T("Please enter an address that begins with https:// or http://.");
}
}
@@ -0,0 +1,69 @@
namespace AIStudio.Assistants.BeyondFactsLab;
/// <summary>
/// What the person entered into the form "Your details" of one step, stored as form.json in the folder of the step.
/// </summary>
/// <remarks>
/// The values are stored under the IDs of the fields of BflFieldCatalog. A value of a field this version does not
/// know, e.g., one a newer version added, stays as it is and is written back unchanged.
/// </remarks>
public sealed class BflStepFormData
{
/// <summary>
/// The version of the stored form this version of AI Studio writes. Raise it with every new property.
/// </summary>
public const int CURRENT_SCHEMA_VERSION = 1;
/// <summary>
/// The character which separates the options of a field with several choices.
/// </summary>
private const char OPTION_SEPARATOR = ',';
/// <summary>
/// Gets or sets the version of the stored form.
/// </summary>
public int SchemaVersion { get; set; } = CURRENT_SCHEMA_VERSION;
/// <summary>
/// Gets or sets the values, by the IDs of their fields.
/// </summary>
public Dictionary<string, string> Values { get; set; } = new(StringComparer.Ordinal);
/// <summary>
/// Gets the value of a field, or an empty string when the field has none.
/// </summary>
/// <param name="fieldId">The ID of the field.</param>
public string Get(string fieldId) => this.Values.GetValueOrDefault(fieldId, string.Empty);
/// <summary>
/// Sets the value of a field; an empty value removes it.
/// </summary>
/// <param name="fieldId">The ID of the field.</param>
/// <param name="value">The value.</param>
public void Set(string fieldId, string? value)
{
if (string.IsNullOrWhiteSpace(value))
this.Values.Remove(fieldId);
else
this.Values[fieldId] = value;
}
/// <summary>
/// Gets the options chosen in a field with several choices.
/// </summary>
/// <param name="fieldId">The ID of the field.</param>
public IReadOnlySet<string> GetChoices(string fieldId) => this.Get(fieldId).Split(OPTION_SEPARATOR, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries).ToHashSet(StringComparer.Ordinal);
/// <summary>
/// Sets the options chosen in a field with several choices.
/// </summary>
/// <param name="field">The field.</param>
/// <param name="optionIds">The IDs of the chosen options.</param>
public void SetChoices(BflFieldDefinition field, IEnumerable<string> optionIds)
{
var chosen = optionIds.ToHashSet(StringComparer.Ordinal);
// Stored in the order of the options, so that the same choice always gives the same value:
this.Set(field.Id, string.Join(OPTION_SEPARATOR, field.Options.Select(option => option.Id).Where(chosen.Contains)));
}
}
@@ -0,0 +1,12 @@
namespace AIStudio.Assistants.BeyondFactsLab;
/// <summary>
/// Overrides where the BeyondFacts Lab stores its preparations, for tests.
/// </summary>
public sealed class BflStorageOptions
{
/// <summary>
/// Gets or initializes the folder in which the folder beyondFactsLab is created.
/// </summary>
public string? DataDirectory { get; init; }
}
@@ -38,9 +38,7 @@ else if (!this.isLoading && string.IsNullOrWhiteSpace(this.loadingIssue))
@context.Key
</pre>
</MudTd>
<MudTd>
@context.Value
</MudTd>
<MudTd Style="white-space: pre-line;">@context.Value</MudTd>
</RowTemplate>
<PagerContent>
<MudTablePager />
@@ -68,9 +66,7 @@ else if (!this.isLoading && string.IsNullOrWhiteSpace(this.loadingIssue))
@context.Key
</pre>
</MudTd>
<MudTd>
@context.Value
</MudTd>
<MudTd Style="white-space: pre-line;">@context.Value</MudTd>
</RowTemplate>
<PagerContent>
<MudTablePager />
@@ -112,9 +108,7 @@ else if (!this.isLoading && string.IsNullOrWhiteSpace(this.loadingIssue))
@context.Key
</pre>
</MudTd>
<MudTd>
@context.Value
</MudTd>
<MudTd Style="white-space: pre-line;">@context.Value</MudTd>
</RowTemplate>
<PagerContent>
<MudTablePager />
@@ -5,14 +5,8 @@ using AIStudio.Dialogs.Settings;
using AIStudio.Tools.AssistantSessions;
using AIStudio.Tools.PluginSystem;
using Microsoft.Extensions.FileProviders;
using SharedTools;
#if RELEASE
using System.Reflection;
#endif
namespace AIStudio.Assistants.I18N;
public partial class AssistantI18N : AssistantBaseCore<SettingsDialogI18N>
@@ -42,7 +36,10 @@ public partial class AssistantI18N : AssistantBaseCore<SettingsDialogI18N>
daily lives with it. The app offers the regular chat mode for which LLMs have become
known. However, AI Studio also offers so-called assistants, where users no longer
have to prompt.
Some texts are longer notes in Markdown, with headings, paragraphs, or lists. Keep their
Markdown and their line breaks exactly as they are, and translate only the words.
# Target Audience
The app is intended for everyone, not just IT specialists or scientists. When translating,
make sure the texts are easy for everyone to understand.
@@ -250,17 +247,7 @@ public partial class AssistantI18N : AssistantBaseCore<SettingsDialogI18N>
//
// Read the file `Assistants\I18N\allTexts.lua`:
//
#if DEBUG
var filePath = Path.Join(Environment.CurrentDirectory, "Assistants", "I18N");
var resourceFileProvider = new PhysicalFileProvider(filePath);
#else
var resourceFileProvider = new ManifestEmbeddedFileProvider(Assembly.GetAssembly(type: typeof(Program))!, "Assistants/I18N");
#endif
var file = resourceFileProvider.GetFileInfo("allTexts.lua");
await using var fileStream = file.CreateReadStream();
using var reader = new StreamReader(fileStream);
var newI18NDataLuaCode = await reader.ReadToEndAsync();
var newI18NDataLuaCode = await AppResources.ReadTextAsync("Assistants/I18N/allTexts.lua");
//
// Next, we try to load the text as a language plugin -- without
@@ -733,6 +733,336 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::BATCHPROCESSINGPROMPTSOU
-- Use a document analysis policy
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::BATCHPROCESSINGPROMPTSOURCEEXTENSIONS::T3309547196"] = "Use a document analysis policy"
-- Prepare a challenging appearance as a researcher step by step, e.g., a panel discussion, an interview, or a hearing. Each preparation belongs to one event and has its own steps, chats, and work states.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BEYONDFACTSLABASSISTANT::T1218290190"] = "Prepare a challenging appearance as a researcher step by step, e.g., a panel discussion, an interview, or a hearing. Each preparation belongs to one event and has its own steps, chats, and work states."
-- Changed on {0}
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BEYONDFACTSLABASSISTANT::T1279137966"] = "Changed on {0}"
-- Delete
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BEYONDFACTSLABASSISTANT::T1469573738"] = "Delete"
-- AI Studio cannot read this preparation. Its files may be damaged.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BEYONDFACTSLABASSISTANT::T1496519091"] = "AI Studio cannot read this preparation. Its files may be damaged."
-- AI Studio cannot open your details for this step. Their file may be damaged, or a newer version of AI Studio stored it. AI Studio leaves the file unchanged.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BEYONDFACTSLABASSISTANT::T1564342983"] = "AI Studio cannot open your details for this step. Their file may be damaged, or a newer version of AI Studio stored it. AI Studio leaves the file unchanged."
-- You have no preparations yet. Create one for the event you want to prepare for.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BEYONDFACTSLABASSISTANT::T1599306665"] = "You have no preparations yet. Create one for the event you want to prepare for."
-- Enter a new name for this preparation.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BEYONDFACTSLABASSISTANT::T193031277"] = "Enter a new name for this preparation."
-- Cannot be opened
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BEYONDFACTSLABASSISTANT::T1981873292"] = "Cannot be opened"
-- Copy preparation ID
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BEYONDFACTSLABASSISTANT::T2344012850"] = "Copy preparation ID"
-- Your details
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BEYONDFACTSLABASSISTANT::T25208484"] = "Your details"
-- Create
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BEYONDFACTSLABASSISTANT::T2567824509"] = "Create"
-- Preparation ID
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BEYONDFACTSLABASSISTANT::T2671360307"] = "Preparation ID"
-- Create a preparation to begin.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BEYONDFACTSLABASSISTANT::T2751865753"] = "Create a preparation to begin."
-- AI Studio has left the files of this preparation unchanged. A future update may make it accessible again.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BEYONDFACTSLABASSISTANT::T2824385338"] = "AI Studio has left the files of this preparation unchanged. A future update may make it accessible again."
-- The event has taken place
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BEYONDFACTSLABASSISTANT::T2842348392"] = "The event has taken place"
-- Marked on {0}.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BEYONDFACTSLABASSISTANT::T3254361846"] = "Marked on {0}."
-- Rename preparation
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BEYONDFACTSLABASSISTANT::T3342203974"] = "Rename preparation"
-- Rename
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BEYONDFACTSLABASSISTANT::T3355849203"] = "Rename"
-- Delete preparation permanently
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BEYONDFACTSLABASSISTANT::T354252674"] = "Delete preparation permanently"
-- Unavailable preparation
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BEYONDFACTSLABASSISTANT::T3646236084"] = "Unavailable preparation"
-- This preparation cannot be opened. Permanently delete it with all of its files?
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BEYONDFACTSLABASSISTANT::T3647862078"] = "This preparation cannot be opened. Permanently delete it with all of its files?"
-- Name the event you want to prepare for, e.g., the title of a panel discussion.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BEYONDFACTSLABASSISTANT::T3679923414"] = "Name the event you want to prepare for, e.g., the title of a panel discussion."
-- Name of the preparation
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BEYONDFACTSLABASSISTANT::T3746973353"] = "Name of the preparation"
-- Please enter a name.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BEYONDFACTSLABASSISTANT::T3818326829"] = "Please enter a name."
-- AI Studio could not save your details. Please try again.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BEYONDFACTSLABASSISTANT::T3921500806"] = "AI Studio could not save your details. Please try again."
-- BeyondFacts Lab
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BEYONDFACTSLABASSISTANT::T3956097526"] = "BeyondFacts Lab"
-- A newer version of AI Studio stored this preparation. This version cannot open it. Please update AI Studio.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BEYONDFACTSLABASSISTANT::T395932079"] = "A newer version of AI Studio stored this preparation. This version cannot open it. Please update AI Studio."
-- AI Studio could not save the change of your preparations. Please try again.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BEYONDFACTSLABASSISTANT::T4073131406"] = "AI Studio could not save the change of your preparations. Please try again."
-- Requires a newer AI Studio version
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BEYONDFACTSLABASSISTANT::T4087140083"] = "Requires a newer AI Studio version"
-- Details missing
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BEYONDFACTSLABASSISTANT::T506453229"] = "Details missing"
-- Permanently delete the preparation '{0}' with all of its steps, chats, and work states?
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BEYONDFACTSLABASSISTANT::T772787118"] = "Permanently delete the preparation '{0}' with all of its steps, chats, and work states?"
-- New preparation
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BEYONDFACTSLABASSISTANT::T851005164"] = "New preparation"
-- Your preparations
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BEYONDFACTSLABASSISTANT::T972511272"] = "Your preparations"
-- Informal address (du in German)
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLADDRESSFORMEXTENSIONS::T554958468"] = "Informal address (du in German)"
-- Formal address (Sie in German)
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLADDRESSFORMEXTENSIONS::T859201973"] = "Formal address (Sie in German)"
-- Moderate the discussion
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T1131866893"] = "Moderate the discussion"
-- E.g., take up a confirmed conflict of values or a perspective from the step about values and perspectives
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T1134014442"] = "E.g., take up a confirmed conflict of values or a perspective from the step about values and perspectives"
-- What have you changed, kept, or left open compared to your initial position, and why?
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T1455147822"] = "What have you changed, kept, or left open compared to your initial position, and why?"
-- Show options for action
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T1601690165"] = "Show options for action"
-- Manipulative questions and straw men
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T1656068515"] = "Manipulative questions and straw men"
-- Topic of the event
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T1766174370"] = "Topic of the event"
-- Moderate
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T177463328"] = "Moderate"
-- Other perspectives: Whose point of view would you like to understand better, or have you hardly considered so far?
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T1885081930"] = "Other perspectives: Whose point of view would you like to understand better, or have you hardly considered so far?"
-- Stays in your preparation and never goes into the research on the web
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T1949096445"] = "Stays in your preparation and never goes into the research on the web"
-- Very demanding
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T1967151608"] = "Very demanding"
-- Date and time
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T2054731205"] = "Date and time"
-- Notes on how to use your materials
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T2123646094"] = "Notes on how to use your materials"
-- Duration of your contribution
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T223853289"] = "Duration of your contribution"
-- Special notes for the test
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T2242825987"] = "Special notes for the test"
-- Uncertainties and worries about communicating
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T2428368968"] = "Uncertainties and worries about communicating"
-- Difficulty at the start
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T243923502"] = "Difficulty at the start"
-- E.g., 90 minutes
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T2537467295"] = "E.g., 90 minutes"
-- Place
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T2597472412"] = "Place"
-- E.g., 8 minutes
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T2697611204"] = "E.g., 8 minutes"
-- A few sentences are enough
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T2765665158"] = "A few sentences are enough"
-- Other participants
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T277557865"] = "Other participants"
-- What do you want to achieve with your contribution?
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T2818219287"] = "What do you want to achieve with your contribution?"
-- Scientific criticism
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T2865565843"] = "Scientific criticism"
-- The address of the event page, beginning with https://
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T2900883219"] = "The address of the event page, beginning with https://"
-- Names and roles, as far as you know them
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T3020176946"] = "Names and roles, as far as you know them"
-- Personal detail: it goes into exports and shared sessions only when you explicitly choose so.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T3063635296"] = "Personal detail: it goes into exports and shared sessions only when you explicitly choose so."
-- Political instrumentalization
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T3078371152"] = "Political instrumentalization"
-- Mixed
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T3114856344"] = "Mixed"
-- Personal involvement: Where does the topic touch you personally? Which question or criticism could trigger a strong reaction?
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T3199962614"] = "Personal involvement: Where does the topic touch you personally? Which question or criticism could trigger a strong reaction?"
-- Format of the event
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T3217479436"] = "Format of the event"
-- E.g., panel, keynote, or interview; online, on site, or hybrid
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T3297023821"] = "E.g., panel, keynote, or interview; online, on site, or hybrid"
-- Values and concerns: What matters most to you about this topic?
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T3444467007"] = "Values and concerns: What matters most to you about this topic?"
-- Audience
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T3506118019"] = "Audience"
-- Suggestions
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T3534677498"] = "Suggestions"
-- Emotionally charged criticism
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T3552262211"] = "Emotionally charged criticism"
-- Link to the event
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T3658981841"] = "Link to the event"
-- Raise awareness
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T3790372874"] = "Raise awareness"
-- E.g., use only paper X as the basis for the content
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T3804983466"] = "E.g., use only paper X as the basis for the content"
-- Core theses and arguments (if any)
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T39824489"] = "Core theses and arguments (if any)"
-- Total duration of the event
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T4051894943"] = "Total duration of the event"
-- Influence policy
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T4052553234"] = "Influence policy"
-- You can raise the difficulty after each question.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T519332031"] = "You can raise the difficulty after each question."
-- E.g., students, experts, or the general public
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T533097975"] = "E.g., students, experts, or the general public"
-- Short description of your contribution
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T667686615"] = "Short description of your contribution"
-- Type of questions
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T805059831"] = "Type of questions"
-- The central question or the title
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T828389221"] = "The central question or the title"
-- Uncertainties and controversies in content and science (if known)
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T82922542"] = "Uncertainties and controversies in content and science (if known)"
-- Inform
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T85613896"] = "Inform"
-- E.g., December 10, 2 pm
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T881586300"] = "E.g., December 10, 2 pm"
-- E.g., the venue and the city, or online
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T916990010"] = "E.g., the venue and the city, or online"
-- In your own words, with the suggestions below, or still open: the chat sharpens your goal and role with you.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T917276711"] = "In your own words, with the suggestions below, or still open: the chat sharpens your goal and role with you."
-- Topic of your contribution or your specific assignment
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T932670647"] = "Topic of your contribution or your specific assignment"
-- E.g., aggressive moderation, time pressure, or a particular counterpart
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T965551209"] = "E.g., aggressive moderation, time pressure, or a particular counterpart"
-- Take a position in the debate
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T974131333"] = "Take a position in the debate"
-- Demanding
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T985014450"] = "Demanding"
-- ## Welcome! The BeyondFacts Lab guides you through an AI-supported process of reflection, which helps you take a position in challenging communication situations and stay able to communicate. Data protection depends on the AI provider you choose, its rules for processing and storing data, and your configuration. Enter only data that are suitable and approved for this. Please check all results critically. Depending on the AI model, the answers may differ. Hallucinations cannot be ruled out. This is a prototype, so we look forward to your feedback.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLNOTICE::T940905480"] = "## Welcome!\n\nThe BeyondFacts Lab guides you through an AI-supported process of reflection, which helps you take a position in challenging communication situations and stay able to communicate.\n\nData protection depends on the AI provider you choose, its rules for processing and storing data, and your configuration. Enter only data that are suitable and approved for this.\n\nPlease check all results critically. Depending on the AI model, the answers may differ. Hallucinations cannot be ruled out.\n\nThis is a prototype, so we look forward to your feedback."
-- I have read the notes
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLNOTICEDIALOG::T4071737336"] = "I have read the notes"
-- Test situation
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLSTEPEXTENSIONS::T1049923473"] = "Test situation"
-- Knowledge and uncertainty
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLSTEPEXTENSIONS::T1147870258"] = "Knowledge and uncertainty"
-- How you convey evidence, trade-offs between values, and open questions.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLSTEPEXTENSIONS::T1290723890"] = "How you convey evidence, trade-offs between values, and open questions."
-- Event details
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLSTEPEXTENSIONS::T135387869"] = "Event details"
-- What the event is about, for whom, and what you were asked to contribute.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLSTEPEXTENSIONS::T158292580"] = "What the event is about, for whom, and what you were asked to contribute."
-- You can fill in single fields or start right away in the chat.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLSTEPEXTENSIONS::T2524378548"] = "You can fill in single fields or start right away in the chat."
-- Goals and role
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLSTEPEXTENSIONS::T2988481290"] = "Goals and role"
-- What matters to you, what touches you, and what moves others.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLSTEPEXTENSIONS::T3022557767"] = "What matters to you, what touches you, and what moves others."
-- What you want to achieve, and in which role you speak.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLSTEPEXTENSIONS::T3052319680"] = "What you want to achieve, and in which role you speak."
-- Strategy
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLSTEPEXTENSIONS::T3205650756"] = "Strategy"
-- Results and materials
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLSTEPEXTENSIONS::T3602322182"] = "Results and materials"
-- Values and perspectives
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLSTEPEXTENSIONS::T494574525"] = "Values and perspectives"
-- What you take away from your preparation.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLSTEPEXTENSIONS::T517692701"] = "What you take away from your preparation."
-- Play through critical questions and opposing positions.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLSTEPEXTENSIONS::T573043138"] = "Play through critical questions and opposing positions."
-- What you know for sure, what is uncertain, and where your expertise ends.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLSTEPEXTENSIONS::T762525208"] = "What you know for sure, what is uncertain, and where your expertise ends."
-- Some details are still missing. The chat will ask for them, and you can continue anyway.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLSTEPFORM::T1474406854"] = "Some details are still missing. The chat will ask for them, and you can continue anyway."
-- Please enter an address that begins with https:// or http://.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLSTEPFORM::T2837809367"] = "Please enter an address that begins with https:// or http://."
-- * The chat works best when you fill in these details.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLSTEPFORM::T3011046288"] = "* The chat works best when you fill in these details."
-- Extended bias poster
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BIASDAY::BIASOFTHEDAYASSISTANT::T1241605514"] = "Extended bias poster"
@@ -4414,6 +4744,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATAFOLDERS::T1259707710"] = "Your projec
-- Your temporary chats, along with their transcripts.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATAFOLDERS::T1293614512"] = "Your temporary chats, along with their transcripts."
-- Your preparations in the BeyondFacts Lab.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATAFOLDERS::T1415474409"] = "Your preparations in the BeyondFacts Lab."
-- What belongs into a backup
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATAFOLDERS::T1682175486"] = "What belongs into a backup"
@@ -9589,6 +9922,24 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T82
-- Restore default
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T913168756"] = "Restore default"
-- How the lab addresses you in languages with a formal and an informal address, such as German.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBEYONDFACTSLAB::T1832497993"] = "How the lab addresses you in languages with a formal and an informal address, such as German."
-- Assistant: BeyondFacts Lab Options
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBEYONDFACTSLAB::T19824556"] = "Assistant: BeyondFacts Lab Options"
-- Form of address
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBEYONDFACTSLAB::T2183481736"] = "Form of address"
-- Select a minimum confidence level
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBEYONDFACTSLAB::T2579793544"] = "Select a minimum confidence level"
-- Close
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBEYONDFACTSLAB::T3448155331"] = "Close"
-- Your preparations usually contain unpublished research. Therefore, the lab accepts only providers with at least medium confidence. You can raise this minimum, e.g., to use only self-hosted models.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBEYONDFACTSLAB::T4002746484"] = "Your preparations usually contain unpublished research. Therefore, the lab accepts only providers with at least medium confidence. You can raise this minimum, e.g., to use only self-hosted models."
-- Files are sent as they were first read
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHAT::T1320692831"] = "Files are sent as they were first read"
@@ -11197,12 +11548,18 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T3733831260"] = "Generate your own
-- Generate an ERI server to integrate business systems.
UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T3756213118"] = "Generate an ERI server to integrate business systems."
-- Prepare step by step for a challenging appearance as a researcher, e.g., a panel discussion or an interview.
UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T386812830"] = "Prepare step by step for a challenging appearance as a researcher, e.g., a panel discussion or an interview."
-- Use an LLM to find an icon for a given context.
UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T3881504200"] = "Use an LLM to find an icon for a given context."
-- Job Posting
UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T3930052338"] = "Job Posting"
-- BeyondFacts Lab
UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T3956097526"] = "BeyondFacts Lab"
-- Ask a question about a legal document.
UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T3970214537"] = "Ask a question about a legal document."
@@ -12919,6 +13276,9 @@ UI_TEXT_CONTENT["AISTUDIO::SETTINGS::DATAMODEL::OUTBOUNDDATARESTRICTIONEXTENSION
-- Only services configured in AI Studio
UI_TEXT_CONTENT["AISTUDIO::SETTINGS::DATAMODEL::OUTBOUNDDATARESTRICTIONEXTENSIONS::T750634166"] = "Only services configured in AI Studio"
-- BeyondFacts Lab: Prepare for challenging conversations about your research, step by step
UI_TEXT_CONTENT["AISTUDIO::SETTINGS::DATAMODEL::PREVIEWFEATURESEXTENSIONS::T1110065980"] = "BeyondFacts Lab: Prepare for challenging conversations about your research, step by step"
-- Visual Briefing Assistant: Turn source material into an interactive briefing
UI_TEXT_CONTENT["AISTUDIO::SETTINGS::DATAMODEL::PREVIEWFEATURESEXTENSIONS::T1217946647"] = "Visual Briefing Assistant: Turn source material into an interactive briefing"
@@ -13168,6 +13528,9 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::COMPONENTSEXTENSIONS::T348883878"] = "Document
-- Translation Assistant
UI_TEXT_CONTENT["AISTUDIO::TOOLS::COMPONENTSEXTENSIONS::T3887962308"] = "Translation Assistant"
-- BeyondFacts Lab
UI_TEXT_CONTENT["AISTUDIO::TOOLS::COMPONENTSEXTENSIONS::T3956097526"] = "BeyondFacts Lab"
-- Agenda Assistant
UI_TEXT_CONTENT["AISTUDIO::TOOLS::COMPONENTSEXTENSIONS::T4034795997"] = "Agenda Assistant"
@@ -10,11 +10,6 @@ using AIStudio.Tools.Rust;
using AIStudio.Tools.Services;
using Microsoft.AspNetCore.Components;
#if !DEBUG
using System.Reflection;
using Microsoft.Extensions.FileProviders;
#endif
namespace AIStudio.Assistants.PromptOptimizer;
public partial class AssistantPromptOptimizer : AssistantBaseCore<SettingsDialogPromptOptimizer>
@@ -578,24 +573,7 @@ public partial class AssistantPromptOptimizer : AssistantBaseCore<SettingsDialog
});
}
private static async Task<string> ReadPromptingGuidelineAsync()
{
#if DEBUG
var guidelinePath = Path.Join(Environment.CurrentDirectory, "Assistants", "PromptOptimizer", "prompting_guideline.md");
return File.Exists(guidelinePath)
? await File.ReadAllTextAsync(guidelinePath)
: string.Empty;
#else
var resourceFileProvider = new ManifestEmbeddedFileProvider(Assembly.GetAssembly(type: typeof(Program))!, "Assistants/PromptOptimizer");
var file = resourceFileProvider.GetFileInfo("prompting_guideline.md");
if (!file.Exists)
return string.Empty;
await using var fileStream = file.CreateReadStream();
using var reader = new StreamReader(fileStream);
return await reader.ReadToEndAsync();
#endif
}
private static async Task<string> ReadPromptingGuidelineAsync() => await AppResources.ReadTextAsync("Assistants/PromptOptimizer/prompting_guideline.md");
private async Task<string> GetPromptingGuidelineForOptimizationAsync()
{
@@ -82,6 +82,7 @@ public partial class DataFolders : MSGComponentBase
DataBackupEntryKind.PLUGINS => T("Your plugins, such as the assistants you built, apart from the folders listed below."),
DataBackupEntryKind.TOKENIZERS => T("The tokenizers you chose for your providers."),
DataBackupEntryKind.VISUAL_BRIEFINGS => T("Your projects in the Visual Briefing Assistant."),
DataBackupEntryKind.BEYOND_FACTS_LAB => T("Your preparations in the BeyondFacts Lab."),
DataBackupEntryKind.DATA_SOURCE_INDEX => T("The index of your local data sources: which files AI Studio embedded, and their text."),
DataBackupEntryKind.VECTOR_STORES => T("The embeddings of your local data sources."),
DataBackupEntryKind.MEDIA_TRANSCRIPTS => T("The transcripts of the audio and video files in your local data sources."),
@@ -3,7 +3,7 @@
CompletedStepColor="Color.Primary"
CurrentStepColor="Color.Primary"
ErrorStepColor="Color.Error"
NonLinear="@false"
NonLinear="@this.NonLinear"
ShowResetButton="@false"
OnPreviewInteraction="@this.PreviewInteractionAsync"
Class="@this.Classname">
@@ -36,6 +36,17 @@ public partial class MudStepperWithoutActions : ComponentBase
[Parameter]
public bool ReadOnly { get; set; }
/// <summary>
/// Gets or sets whether the user may jump to any step, not only to the next one.
/// </summary>
/// <remarks>
/// Off by default, which keeps the order of a process that runs step by step, such as a build.
/// Switch it on where people work through the steps themselves and may return to an earlier one
/// or look ahead, as in the BeyondFacts Lab.
/// </remarks>
[Parameter]
public bool NonLinear { get; set; }
/// <summary>
/// Gets or sets additional CSS classes for the stepper.
/// </summary>
@@ -0,0 +1,21 @@
@using AIStudio.Settings
@inherits SettingsDialogBase
<MudDialog>
<TitleContent>
<MudText Typo="Typo.h6" Class="d-flex align-center">
<MudIcon Icon="@Icons.Material.Filled.RecordVoiceOver" Class="mr-2"/>
@T("Assistant: BeyondFacts Lab Options")
</MudText>
</TitleContent>
<DialogContent>
<MudPaper Class="pa-3 mb-8 border-dashed border rounded-lg">
<ConfigurationSelect OptionDescription="@T("Form of address")" SelectedValue="@(() => this.SettingsManager.ConfigurationData.BeyondFactsLab.AddressForm)" Data="@ConfigurationSelectDataFactory.GetBeyondFactsLabAddressFormData()" SelectionUpdate="@(value => this.SettingsManager.ConfigurationData.BeyondFactsLab.AddressForm = value)" OptionHelp="@T("How the lab addresses you in languages with a formal and an informal address, such as German.")" IsLocked="() => ManagedConfiguration.TryGet(x => x.BeyondFactsLab, x => x.AddressForm, out var meta) && meta.IsLocked"/>
<ConfigurationSelect OptionDescription="@T("Select a minimum confidence level")" SelectedValue="@this.SelectedMinimumConfidence" Data="@ConfigurationSelectDataFactory.GetBeyondFactsLabConfidenceLevelsData(this.SettingsManager)" SelectionUpdate="@(value => this.SettingsManager.ConfigurationData.BeyondFactsLab.MinimumProviderConfidence = value)" OptionHelp="@T("Your preparations usually contain unpublished research. Therefore, the lab accepts only providers with at least medium confidence. You can raise this minimum, e.g., to use only self-hosted models.")" IsLocked="() => ManagedConfiguration.TryGet(x => x.BeyondFactsLab, x => x.MinimumProviderConfidence, out var meta) && meta.IsLocked"/>
<ConfigurationProviderSelection Component="Components.BEYOND_FACTS_LAB_ASSISTANT" Data="@this.AvailableLLMProviders" SelectedValue="@(() => this.SettingsManager.ConfigurationData.BeyondFactsLab.PreselectedProvider)" SelectionUpdate="@(value => this.SettingsManager.ConfigurationData.BeyondFactsLab.PreselectedProvider = value)" IsLocked="() => ManagedConfiguration.TryGet(x => x.BeyondFactsLab, x => x.PreselectedProvider, out var meta) && meta.IsLocked"/>
</MudPaper>
</DialogContent>
<DialogActions>
<MudButton OnClick="@this.Close" Variant="Variant.Filled">@T("Close")</MudButton>
</DialogActions>
</MudDialog>
@@ -0,0 +1,18 @@
using AIStudio.Provider;
namespace AIStudio.Dialogs.Settings;
/// <summary>
/// Provides the code-behind type for the settings of the BeyondFacts Lab.
/// </summary>
public partial class SettingsDialogBeyondFactsLab : SettingsDialogBase
{
/// <summary>
/// Shows the minimum confidence which applies, never one below the floor of the lab.
/// </summary>
/// <remarks>
/// A value below the floor, e.g., from a configuration plugin, has no effect, and the list does not
/// offer it either. Showing the floor instead tells what really counts.
/// </remarks>
private ConfidenceLevel SelectedMinimumConfidence() => Tools.Components.BEYOND_FACTS_LAB_ASSISTANT.MinimumConfidence(this.SettingsManager);
}
@@ -70,6 +70,7 @@
<AssistantCategoryBlock Title="@T("Learning")">
<AssistantBlock TSettings="SettingsDialogAssistantBias" Component="Components.BIAS_DAY_ASSISTANT" Name="@T("Bias of the Day")" Description="@T("Learn about one cognitive bias every day.")" Icon="@Icons.Material.Filled.Psychology" Link="@Routes.ASSISTANT_BIAS"/>
<AssistantBlock TSettings="SettingsDialogBeyondFactsLab" Component="Components.BEYOND_FACTS_LAB_ASSISTANT" RequiredPreviewFeature="Components.BEYOND_FACTS_LAB_ASSISTANT.RequiredPreviewFeature" Name="@T("BeyondFacts Lab")" Description="@T("Prepare step by step for a challenging appearance as a researcher, e.g., a panel discussion or an interview.")" Icon="@Icons.Material.Filled.RecordVoiceOver" Link="@Routes.ASSISTANT_BEYOND_FACTS_LAB"/>
</AssistantCategoryBlock>
<AssistantCategoryBlock Title="@T("Software Engineering")">
@@ -914,7 +914,7 @@ CONFIG["SETTINGS"] = {}
-- LEGAL_CHECK_ASSISTANT, SYNONYMS_ASSISTANT, MY_TASKS_ASSISTANT,
-- JOB_POSTING_ASSISTANT, BIAS_DAY_ASSISTANT, ERI_ASSISTANT,
-- DOCUMENT_ANALYSIS_ASSISTANT, BATCH_PROCESSING_ASSISTANT, SLIDE_BUILDER_ASSISTANT,
-- VISUAL_BRIEFING_ASSISTANT, I18N_ASSISTANT,
-- VISUAL_BRIEFING_ASSISTANT, BEYOND_FACTS_LAB_ASSISTANT, I18N_ASSISTANT,
-- LOG_VIEWER_ASSISTANT
--
-- Replaces, does not merge: a configuration with a higher priority replaces this list
@@ -982,6 +982,25 @@ CONFIG["SETTINGS"] = {}
-- documents. Providers below this level cannot be selected in the assistant.
-- CONFIG["SETTINGS"]["DataVisualBriefing.MinimumProviderConfidence"] = "NONE"
-- Configure organization defaults for the BeyondFacts Lab.
-- The lab guides researchers step by step through the preparation of a challenging appearance,
-- such as a panel discussion or an interview. It is a prototype: users see it only when they allow
-- prototypes and enable the preview feature PRE_BEYOND_FACTS_LAB_2026.
--
-- Configure the provider which new preparations start with.
-- It must be one of the provider IDs defined in CONFIG["LLM_PROVIDERS"].
-- CONFIG["SETTINGS"]["DataBeyondFactsLab.PreselectedProvider"] = "00000000-0000-0000-0000-000000000000"
--
-- Configure the minimum provider confidence required by the lab.
-- Allowed values are: NONE, UNTRUSTED, UNKNOWN, VERY_LOW, LOW, MODERATE, MEDIUM, HIGH
-- Unpublished research is the usual material of the lab, so the lab never accepts less than MEDIUM.
-- A lower value has no effect; HIGH allows only self-hosted models in the built-in confidence schemes.
-- CONFIG["SETTINGS"]["DataBeyondFactsLab.MinimumProviderConfidence"] = "MEDIUM"
--
-- Configure how the lab addresses users in languages with a formal and an informal address, such as German.
-- Allowed values are: FORMAL, INFORMAL
-- CONFIG["SETTINGS"]["DataBeyondFactsLab.AddressForm"] = "FORMAL"
-- Configure defaults for the Assistant Builder.
-- Preselection must be enabled for the remaining Assistant Builder settings to take effect.
-- CONFIG["SETTINGS"]["DataAssistantBuilder.PreselectOptions"] = true
@@ -735,6 +735,336 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::BATCHPROCESSINGPROMPTSOU
-- Use a document analysis policy
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::BATCHPROCESSINGPROMPTSOURCEEXTENSIONS::T3309547196"] = "Regelwerk für die Dokumentenanalyse verwenden"
-- Prepare a challenging appearance as a researcher step by step, e.g., a panel discussion, an interview, or a hearing. Each preparation belongs to one event and has its own steps, chats, and work states.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BEYONDFACTSLABASSISTANT::T1218290190"] = "Bereiten Sie einen herausfordernden Auftritt mit Ihrer Forschung Schritt für Schritt vor, etwa eine Podiumsdiskussion, ein Interview oder eine Anhörung. Jede Vorbereitung gehört zu einer Veranstaltung und hat eigene Schritte, Chats und Arbeitsstände."
-- Changed on {0}
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BEYONDFACTSLABASSISTANT::T1279137966"] = "Geändert am {0}"
-- Delete
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BEYONDFACTSLABASSISTANT::T1469573738"] = "Löschen"
-- AI Studio cannot read this preparation. Its files may be damaged.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BEYONDFACTSLABASSISTANT::T1496519091"] = "AI Studio kann diese Vorbereitung nicht lesen. Die Dateien dieser Vorbereitung sind möglicherweise beschädigt."
-- AI Studio cannot open your details for this step. Their file may be damaged, or a newer version of AI Studio stored it. AI Studio leaves the file unchanged.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BEYONDFACTSLABASSISTANT::T1564342983"] = "AI Studio kann Ihre Angaben zu diesem Schritt nicht öffnen. Die Datei ist möglicherweise beschädigt oder wurde mit einer neueren Version von AI Studio gespeichert. AI Studio lässt die Datei unverändert."
-- You have no preparations yet. Create one for the event you want to prepare for.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BEYONDFACTSLABASSISTANT::T1599306665"] = "Sie haben noch keine Vorbereitung. Erstellen Sie eine für die Veranstaltung, auf die Sie sich vorbereiten möchten."
-- Enter a new name for this preparation.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BEYONDFACTSLABASSISTANT::T193031277"] = "Geben Sie einen neuen Namen für diese Vorbereitung ein."
-- Cannot be opened
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BEYONDFACTSLABASSISTANT::T1981873292"] = "Kann nicht geöffnet werden"
-- Copy preparation ID
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BEYONDFACTSLABASSISTANT::T2344012850"] = "Vorbereitungs-ID kopieren"
-- Your details
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BEYONDFACTSLABASSISTANT::T25208484"] = "Ihre Angaben"
-- Create
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BEYONDFACTSLABASSISTANT::T2567824509"] = "Erstellen"
-- Preparation ID
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BEYONDFACTSLABASSISTANT::T2671360307"] = "Vorbereitungs-ID"
-- Create a preparation to begin.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BEYONDFACTSLABASSISTANT::T2751865753"] = "Erstellen Sie eine Vorbereitung, um zu beginnen."
-- AI Studio has left the files of this preparation unchanged. A future update may make it accessible again.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BEYONDFACTSLABASSISTANT::T2824385338"] = "AI Studio hat die Dateien dieser Vorbereitung unverändert gelassen. Ein zukünftiges Update könnte sie wieder zugänglich machen."
-- The event has taken place
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BEYONDFACTSLABASSISTANT::T2842348392"] = "Die Veranstaltung hat stattgefunden"
-- Marked on {0}.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BEYONDFACTSLABASSISTANT::T3254361846"] = "Markiert am {0}."
-- Rename preparation
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BEYONDFACTSLABASSISTANT::T3342203974"] = "Vorbereitung umbenennen"
-- Rename
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BEYONDFACTSLABASSISTANT::T3355849203"] = "Umbenennen"
-- Delete preparation permanently
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BEYONDFACTSLABASSISTANT::T354252674"] = "Vorbereitung endgültig löschen"
-- Unavailable preparation
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BEYONDFACTSLABASSISTANT::T3646236084"] = "Vorbereitung nicht verfügbar"
-- This preparation cannot be opened. Permanently delete it with all of its files?
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BEYONDFACTSLABASSISTANT::T3647862078"] = "Diese Vorbereitung kann nicht geöffnet werden. Möchten Sie sie mit allen Dateien endgültig löschen?"
-- Name the event you want to prepare for, e.g., the title of a panel discussion.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BEYONDFACTSLABASSISTANT::T3679923414"] = "Geben Sie die Veranstaltung an, auf die Sie sich vorbereiten möchten, z. B. den Titel einer Podiumsdiskussion."
-- Name of the preparation
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BEYONDFACTSLABASSISTANT::T3746973353"] = "Name der Vorbereitung"
-- Please enter a name.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BEYONDFACTSLABASSISTANT::T3818326829"] = "Bitte geben Sie einen Namen ein."
-- AI Studio could not save your details. Please try again.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BEYONDFACTSLABASSISTANT::T3921500806"] = "AI Studio konnte Ihre Angaben nicht speichern. Bitte versuchen Sie es erneut."
-- BeyondFacts Lab
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BEYONDFACTSLABASSISTANT::T3956097526"] = "BeyondFacts Lab"
-- A newer version of AI Studio stored this preparation. This version cannot open it. Please update AI Studio.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BEYONDFACTSLABASSISTANT::T395932079"] = "Eine neuere Version von AI Studio hat diese Vorbereitung gespeichert. Diese Version kann sie nicht öffnen. Bitte aktualisieren Sie AI Studio."
-- AI Studio could not save the change of your preparations. Please try again.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BEYONDFACTSLABASSISTANT::T4073131406"] = "AI Studio konnte die Änderung an Ihren Vorbereitungen nicht speichern. Bitte versuchen Sie es erneut."
-- Requires a newer AI Studio version
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BEYONDFACTSLABASSISTANT::T4087140083"] = "Erfordert eine neuere Version von AI Studio"
-- Details missing
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BEYONDFACTSLABASSISTANT::T506453229"] = "Angaben fehlen"
-- Permanently delete the preparation '{0}' with all of its steps, chats, and work states?
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BEYONDFACTSLABASSISTANT::T772787118"] = "Die Vorbereitung „{0}“ mit allen Schritten, Chats und Arbeitsständen endgültig löschen?"
-- New preparation
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BEYONDFACTSLABASSISTANT::T851005164"] = "Neue Vorbereitung"
-- Your preparations
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BEYONDFACTSLABASSISTANT::T972511272"] = "Ihre Vorbereitungen"
-- Informal address (du in German)
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLADDRESSFORMEXTENSIONS::T554958468"] = "Informelle Anrede (du)"
-- Formal address (Sie in German)
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLADDRESSFORMEXTENSIONS::T859201973"] = "Formelle Anrede (Sie)"
-- Moderate the discussion
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T1131866893"] = "Diskussion moderieren"
-- E.g., take up a confirmed conflict of values or a perspective from the step about values and perspectives
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T1134014442"] = "Z. B. einen bestätigten Wertekonflikt oder eine Perspektive aus dem Schritt „Werte und Perspektiven“ aufgreifen"
-- What have you changed, kept, or left open compared to your initial position, and why?
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T1455147822"] = "Was haben Sie gegenüber Ihrer Ausgangsposition verändert, beibehalten oder offengelassen, und weshalb?"
-- Show options for action
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T1601690165"] = "Handlungsoptionen aufzeigen"
-- Manipulative questions and straw men
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T1656068515"] = "Manipulative Fragen und Strohmänner"
-- Topic of the event
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T1766174370"] = "Thema der Veranstaltung"
-- Moderate
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T177463328"] = "Moderat"
-- Other perspectives: Whose point of view would you like to understand better, or have you hardly considered so far?
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T1885081930"] = "Andere Perspektiven: Wessen Sichtweise möchten Sie besser verstehen oder haben Sie bisher wenig berücksichtigt?"
-- Stays in your preparation and never goes into the research on the web
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T1949096445"] = "Bleibt in Ihrer Vorbereitung und geht nie in die Recherche im Web ein"
-- Very demanding
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T1967151608"] = "Sehr anspruchsvoll"
-- Date and time
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T2054731205"] = "Datum und Uhrzeit"
-- Notes on how to use your materials
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T2123646094"] = "Hinweise zur Nutzung Ihrer Materialien"
-- Duration of your contribution
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T223853289"] = "Dauer Ihres Beitrags"
-- Special notes for the test
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T2242825987"] = "Besondere Hinweise für den Test"
-- Uncertainties and worries about communicating
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T2428368968"] = "Kommunikative Unsicherheiten und Sorgen"
-- Difficulty at the start
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T243923502"] = "Schwierigkeit zu Beginn"
-- E.g., 90 minutes
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T2537467295"] = "Z. B. 90 Minuten"
-- Place
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T2597472412"] = "Ort"
-- E.g., 8 minutes
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T2697611204"] = "Z. B. 8 Minuten"
-- A few sentences are enough
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T2765665158"] = "Ein paar Sätze genügen"
-- Other participants
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T277557865"] = "Weitere Teilnehmende"
-- What do you want to achieve with your contribution?
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T2818219287"] = "Was möchten Sie mit Ihrem Beitrag erreichen?"
-- Scientific criticism
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T2865565843"] = "Wissenschaftliche Kritik"
-- The address of the event page, beginning with https://
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T2900883219"] = "Die Adresse der Veranstaltungsseite, beginnend mit https://"
-- Names and roles, as far as you know them
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T3020176946"] = "Namen und Rollen, soweit bekannt"
-- Personal detail: it goes into exports and shared sessions only when you explicitly choose so.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T3063635296"] = "Persönliche Angabe: Exporte und geteilte Sitzungen enthalten sie nur, wenn Sie das ausdrücklich wählen."
-- Political instrumentalization
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T3078371152"] = "Politische Instrumentalisierung"
-- Mixed
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T3114856344"] = "Gemischt"
-- Personal involvement: Where does the topic touch you personally? Which question or criticism could trigger a strong reaction?
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T3199962614"] = "Persönliche Betroffenheit: Wo berührt Sie das Thema persönlich? Welche Frage oder Kritik könnte bei Ihnen eine starke Reaktion auslösen?"
-- Format of the event
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T3217479436"] = "Format der Veranstaltung"
-- E.g., panel, keynote, or interview; online, on site, or hybrid
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T3297023821"] = "Z. B. Podiumsdiskussion, Keynote oder Interview; online, vor Ort oder hybrid"
-- Values and concerns: What matters most to you about this topic?
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T3444467007"] = "Werte und Anliegen: Was ist Ihnen bei diesem Thema besonders wichtig?"
-- Audience
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T3506118019"] = "Zielgruppe"
-- Suggestions
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T3534677498"] = "Vorschläge"
-- Emotionally charged criticism
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T3552262211"] = "Emotionalisierte Kritik"
-- Link to the event
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T3658981841"] = "Link zur Veranstaltung"
-- Raise awareness
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T3790372874"] = "Bewusstsein schaffen"
-- E.g., use only paper X as the basis for the content
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T3804983466"] = "Z. B. nur Paper X als inhaltliche Grundlage verwenden"
-- Core theses and arguments (if any)
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T39824489"] = "Kernthesen und Argumente (falls vorhanden)"
-- Total duration of the event
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T4051894943"] = "Dauer der Veranstaltung insgesamt"
-- Influence policy
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T4052553234"] = "Politischen Einfluss nehmen"
-- You can raise the difficulty after each question.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T519332031"] = "Nach jeder Frage können Sie die Schwierigkeit erhöhen."
-- E.g., students, experts, or the general public
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T533097975"] = "Z. B. Studierende, Fachpublikum oder die breite Öffentlichkeit"
-- Short description of your contribution
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T667686615"] = "Kurzbeschreibung Ihres Beitrags"
-- Type of questions
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T805059831"] = "Art der Fragen"
-- The central question or the title
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T828389221"] = "Die zentrale Fragestellung oder der Titel"
-- Uncertainties and controversies in content and science (if known)
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T82922542"] = "Inhaltliche und wissenschaftliche Unsicherheiten und Kontroversen (falls bekannt)"
-- Inform
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T85613896"] = "Informieren"
-- E.g., December 10, 2 pm
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T881586300"] = "Z. B. 10. Dezember, 14 Uhr"
-- E.g., the venue and the city, or online
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T916990010"] = "Z. B. der Veranstaltungsort und die Stadt oder online"
-- In your own words, with the suggestions below, or still open: the chat sharpens your goal and role with you.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T917276711"] = "Ob in eigenen Worten, mit den Vorschlägen unten oder noch ganz offen: Im Chat schärfen Sie gemeinsam mit der KI Ihr Ziel und Ihre Rolle."
-- Topic of your contribution or your specific assignment
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T932670647"] = "Thema Ihres Beitrags oder Ihr konkreter Auftrag"
-- E.g., aggressive moderation, time pressure, or a particular counterpart
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T965551209"] = "Z. B. aggressive Moderation, Zeitdruck oder ein bestimmtes Gegenüber"
-- Take a position in the debate
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T974131333"] = "Sich in der Debatte positionieren"
-- Demanding
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T985014450"] = "Anspruchsvoll"
-- ## Welcome! The BeyondFacts Lab guides you through an AI-supported process of reflection, which helps you take a position in challenging communication situations and stay able to communicate. Data protection depends on the AI provider you choose, its rules for processing and storing data, and your configuration. Enter only data that are suitable and approved for this. Please check all results critically. Depending on the AI model, the answers may differ. Hallucinations cannot be ruled out. This is a prototype, so we look forward to your feedback.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLNOTICE::T940905480"] = "## Herzlich willkommen!\n\nIm BeyondFacts Lab werden Sie durch einen geführten, KI-unterstützten Reflexionsprozess geleitet, der Ihnen dabei hilft, sich in herausfordernden Kommunikationssituationen zu positionieren und kommunikationsfähig zu bleiben.\n\nDer Datenschutz hängt vom gewählten KI-Anbieter, dessen Verarbeitungs- und Speicherregeln sowie Ihrer Konfiguration ab. Geben Sie nur Daten ein, die dafür geeignet und freigegeben sind.\n\nBitte prüfen Sie alle Ergebnisse kritisch. Die Antworten können, je nach KI-Modell, unterschiedlich ausfallen. Halluzinationen sind nicht ausgeschlossen.\n\nEs handelt sich um einen Prototyp, daher freuen wir uns über Ihr Feedback."
-- I have read the notes
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLNOTICEDIALOG::T4071737336"] = "Ich habe die Hinweise gelesen"
-- Test situation
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLSTEPEXTENSIONS::T1049923473"] = "Testsituation"
-- Knowledge and uncertainty
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLSTEPEXTENSIONS::T1147870258"] = "Wissen und Unsicherheit"
-- How you convey evidence, trade-offs between values, and open questions.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLSTEPEXTENSIONS::T1290723890"] = "Wie Sie Evidenz, Wertabwägungen und offene Fragen vermitteln."
-- Event details
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLSTEPEXTENSIONS::T135387869"] = "Rahmendaten"
-- What the event is about, for whom, and what you were asked to contribute.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLSTEPEXTENSIONS::T158292580"] = "Worum es bei der Veranstaltung geht, für wen sie gedacht ist und was Sie beitragen sollen."
-- You can fill in single fields or start right away in the chat.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLSTEPEXTENSIONS::T2524378548"] = "Sie können einzelne Felder ausfüllen oder direkt im Chat beginnen."
-- Goals and role
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLSTEPEXTENSIONS::T2988481290"] = "Ziele und Rolle"
-- What matters to you, what touches you, and what moves others.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLSTEPEXTENSIONS::T3022557767"] = "Was Ihnen wichtig ist, was Sie berührt und was andere bewegt."
-- What you want to achieve, and in which role you speak.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLSTEPEXTENSIONS::T3052319680"] = "Was Sie erreichen möchten und in welcher Rolle Sie sprechen."
-- Strategy
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLSTEPEXTENSIONS::T3205650756"] = "Strategie"
-- Results and materials
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLSTEPEXTENSIONS::T3602322182"] = "Ergebnisse und Materialien"
-- Values and perspectives
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLSTEPEXTENSIONS::T494574525"] = "Werte und Perspektiven"
-- What you take away from your preparation.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLSTEPEXTENSIONS::T517692701"] = "Was Sie aus Ihrer Vorbereitung mitnehmen."
-- Play through critical questions and opposing positions.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLSTEPEXTENSIONS::T573043138"] = "Kritische Nachfragen und Gegenpositionen durchspielen."
-- What you know for sure, what is uncertain, and where your expertise ends.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLSTEPEXTENSIONS::T762525208"] = "Was Sie sicher wissen, was unsicher ist und wo Ihre Expertise endet."
-- Some details are still missing. The chat will ask for them, and you can continue anyway.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLSTEPFORM::T1474406854"] = "Einige Angaben fehlen noch. Der Chat fragt danach, und Sie können trotzdem weitermachen."
-- Please enter an address that begins with https:// or http://.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLSTEPFORM::T2837809367"] = "Bitte geben Sie eine Adresse ein, die mit https:// oder http:// beginnt."
-- * The chat works best when you fill in these details.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLSTEPFORM::T3011046288"] = "* Der Chat funktioniert am besten, wenn Sie diese Angaben ausfüllen."
-- Extended bias poster
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BIASDAY::BIASOFTHEDAYASSISTANT::T1241605514"] = "Erweitertes Poster der Verzerrungen"
@@ -4416,6 +4746,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATAFOLDERS::T1259707710"] = "Ihre Projek
-- Your temporary chats, along with their transcripts.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATAFOLDERS::T1293614512"] = "Ihre temporären Chats mit ihren Transkripten."
-- Your preparations in the BeyondFacts Lab.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATAFOLDERS::T1415474409"] = "Ihre Vorbereitungen im BeyondFacts Lab."
-- What belongs into a backup
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATAFOLDERS::T1682175486"] = "Was in eine Sicherung gehört"
@@ -9591,6 +9924,24 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T82
-- Restore default
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T913168756"] = "Standard wiederherstellen"
-- How the lab addresses you in languages with a formal and an informal address, such as German.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBEYONDFACTSLAB::T1832497993"] = "Wie das Lab Sie in Sprachen anspricht, die eine formelle und eine informelle Anrede kennen, etwa im Deutschen."
-- Assistant: BeyondFacts Lab Options
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBEYONDFACTSLAB::T19824556"] = "Assistent: Optionen für das BeyondFacts Lab"
-- Form of address
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBEYONDFACTSLAB::T2183481736"] = "Anrede"
-- Select a minimum confidence level
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBEYONDFACTSLAB::T2579793544"] = "Wählen Sie ein minimales Vertrauensniveau aus"
-- Close
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBEYONDFACTSLAB::T3448155331"] = "Schließen"
-- Your preparations usually contain unpublished research. Therefore, the lab accepts only providers with at least medium confidence. You can raise this minimum, e.g., to use only self-hosted models.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBEYONDFACTSLAB::T4002746484"] = "Ihre Vorbereitungen enthalten meist unveröffentlichte Forschung. Daher akzeptiert das Lab nur Anbieter mit mindestens dem Vertrauensniveau „Mittel“. Sie können diese Untergrenze anheben, etwa um nur selbst gehostete Modelle zu verwenden."
-- Files are sent as they were first read
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHAT::T1320692831"] = "Dateien werden so gesendet, wie sie zuerst eingelesen wurden"
@@ -11199,12 +11550,18 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T3733831260"] = "Erstellen Sie Ihr
-- Generate an ERI server to integrate business systems.
UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T3756213118"] = "Erstellen Sie einen ERI-Server zur Integration von Geschäftssystemen."
-- Prepare step by step for a challenging appearance as a researcher, e.g., a panel discussion or an interview.
UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T386812830"] = "Bereiten Sie sich Schritt für Schritt auf einen herausfordernden Auftritt mit Ihrer Forschung vor, etwa eine Podiumsdiskussion oder ein Interview."
-- Use an LLM to find an icon for a given context.
UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T3881504200"] = "Verwenden Sie ein LLM, um ein Icon für einen bestimmten Kontext zu finden."
-- Job Posting
UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T3930052338"] = "Stellenanzeige"
-- BeyondFacts Lab
UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T3956097526"] = "BeyondFacts Lab"
-- Ask a question about a legal document.
UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T3970214537"] = "Stellen Sie Fragen zu einem juristischen Dokument."
@@ -12921,6 +13278,9 @@ UI_TEXT_CONTENT["AISTUDIO::SETTINGS::DATAMODEL::OUTBOUNDDATARESTRICTIONEXTENSION
-- Only services configured in AI Studio
UI_TEXT_CONTENT["AISTUDIO::SETTINGS::DATAMODEL::OUTBOUNDDATARESTRICTIONEXTENSIONS::T750634166"] = "Nur in AI Studio konfigurierte Dienste"
-- BeyondFacts Lab: Prepare for challenging conversations about your research, step by step
UI_TEXT_CONTENT["AISTUDIO::SETTINGS::DATAMODEL::PREVIEWFEATURESEXTENSIONS::T1110065980"] = "BeyondFacts Lab: Sich Schritt für Schritt auf herausfordernde Gespräche über die eigene Forschung vorbereiten"
-- Visual Briefing Assistant: Turn source material into an interactive briefing
UI_TEXT_CONTENT["AISTUDIO::SETTINGS::DATAMODEL::PREVIEWFEATURESEXTENSIONS::T1217946647"] = "Assistent für visuelle Briefings: Quellmaterial in ein interaktives Briefing verwandeln"
@@ -13170,6 +13530,9 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::COMPONENTSEXTENSIONS::T348883878"] = "Assisten
-- Translation Assistant
UI_TEXT_CONTENT["AISTUDIO::TOOLS::COMPONENTSEXTENSIONS::T3887962308"] = "Übersetzungs-Assistent"
-- BeyondFacts Lab
UI_TEXT_CONTENT["AISTUDIO::TOOLS::COMPONENTSEXTENSIONS::T3956097526"] = "BeyondFacts Lab"
-- Agenda Assistant
UI_TEXT_CONTENT["AISTUDIO::TOOLS::COMPONENTSEXTENSIONS::T4034795997"] = "Agenda-Assistent"
@@ -735,6 +735,336 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::BATCHPROCESSINGPROMPTSOU
-- Use a document analysis policy
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::BATCHPROCESSINGPROMPTSOURCEEXTENSIONS::T3309547196"] = "Use a document analysis policy"
-- Prepare a challenging appearance as a researcher step by step, e.g., a panel discussion, an interview, or a hearing. Each preparation belongs to one event and has its own steps, chats, and work states.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BEYONDFACTSLABASSISTANT::T1218290190"] = "Prepare a challenging appearance as a researcher step by step, e.g., a panel discussion, an interview, or a hearing. Each preparation belongs to one event and has its own steps, chats, and work states."
-- Changed on {0}
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BEYONDFACTSLABASSISTANT::T1279137966"] = "Changed on {0}"
-- Delete
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BEYONDFACTSLABASSISTANT::T1469573738"] = "Delete"
-- AI Studio cannot read this preparation. Its files may be damaged.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BEYONDFACTSLABASSISTANT::T1496519091"] = "AI Studio cannot read this preparation. Its files may be damaged."
-- AI Studio cannot open your details for this step. Their file may be damaged, or a newer version of AI Studio stored it. AI Studio leaves the file unchanged.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BEYONDFACTSLABASSISTANT::T1564342983"] = "AI Studio cannot open your details for this step. Their file may be damaged, or a newer version of AI Studio stored it. AI Studio leaves the file unchanged."
-- You have no preparations yet. Create one for the event you want to prepare for.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BEYONDFACTSLABASSISTANT::T1599306665"] = "You have no preparations yet. Create one for the event you want to prepare for."
-- Enter a new name for this preparation.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BEYONDFACTSLABASSISTANT::T193031277"] = "Enter a new name for this preparation."
-- Cannot be opened
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BEYONDFACTSLABASSISTANT::T1981873292"] = "Cannot be opened"
-- Copy preparation ID
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BEYONDFACTSLABASSISTANT::T2344012850"] = "Copy preparation ID"
-- Your details
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BEYONDFACTSLABASSISTANT::T25208484"] = "Your details"
-- Create
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BEYONDFACTSLABASSISTANT::T2567824509"] = "Create"
-- Preparation ID
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BEYONDFACTSLABASSISTANT::T2671360307"] = "Preparation ID"
-- Create a preparation to begin.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BEYONDFACTSLABASSISTANT::T2751865753"] = "Create a preparation to begin."
-- AI Studio has left the files of this preparation unchanged. A future update may make it accessible again.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BEYONDFACTSLABASSISTANT::T2824385338"] = "AI Studio has left the files of this preparation unchanged. A future update may make it accessible again."
-- The event has taken place
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BEYONDFACTSLABASSISTANT::T2842348392"] = "The event has taken place"
-- Marked on {0}.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BEYONDFACTSLABASSISTANT::T3254361846"] = "Marked on {0}."
-- Rename preparation
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BEYONDFACTSLABASSISTANT::T3342203974"] = "Rename preparation"
-- Rename
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BEYONDFACTSLABASSISTANT::T3355849203"] = "Rename"
-- Delete preparation permanently
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BEYONDFACTSLABASSISTANT::T354252674"] = "Delete preparation permanently"
-- Unavailable preparation
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BEYONDFACTSLABASSISTANT::T3646236084"] = "Unavailable preparation"
-- This preparation cannot be opened. Permanently delete it with all of its files?
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BEYONDFACTSLABASSISTANT::T3647862078"] = "This preparation cannot be opened. Permanently delete it with all of its files?"
-- Name the event you want to prepare for, e.g., the title of a panel discussion.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BEYONDFACTSLABASSISTANT::T3679923414"] = "Name the event you want to prepare for, e.g., the title of a panel discussion."
-- Name of the preparation
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BEYONDFACTSLABASSISTANT::T3746973353"] = "Name of the preparation"
-- Please enter a name.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BEYONDFACTSLABASSISTANT::T3818326829"] = "Please enter a name."
-- AI Studio could not save your details. Please try again.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BEYONDFACTSLABASSISTANT::T3921500806"] = "AI Studio could not save your details. Please try again."
-- BeyondFacts Lab
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BEYONDFACTSLABASSISTANT::T3956097526"] = "BeyondFacts Lab"
-- A newer version of AI Studio stored this preparation. This version cannot open it. Please update AI Studio.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BEYONDFACTSLABASSISTANT::T395932079"] = "A newer version of AI Studio stored this preparation. This version cannot open it. Please update AI Studio."
-- AI Studio could not save the change of your preparations. Please try again.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BEYONDFACTSLABASSISTANT::T4073131406"] = "AI Studio could not save the change of your preparations. Please try again."
-- Requires a newer AI Studio version
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BEYONDFACTSLABASSISTANT::T4087140083"] = "Requires a newer AI Studio version"
-- Details missing
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BEYONDFACTSLABASSISTANT::T506453229"] = "Details missing"
-- Permanently delete the preparation '{0}' with all of its steps, chats, and work states?
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BEYONDFACTSLABASSISTANT::T772787118"] = "Permanently delete the preparation '{0}' with all of its steps, chats, and work states?"
-- New preparation
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BEYONDFACTSLABASSISTANT::T851005164"] = "New preparation"
-- Your preparations
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BEYONDFACTSLABASSISTANT::T972511272"] = "Your preparations"
-- Informal address (du in German)
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLADDRESSFORMEXTENSIONS::T554958468"] = "Informal address (du in German)"
-- Formal address (Sie in German)
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLADDRESSFORMEXTENSIONS::T859201973"] = "Formal address (Sie in German)"
-- Moderate the discussion
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T1131866893"] = "Moderate the discussion"
-- E.g., take up a confirmed conflict of values or a perspective from the step about values and perspectives
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T1134014442"] = "E.g., take up a confirmed conflict of values or a perspective from the step about values and perspectives"
-- What have you changed, kept, or left open compared to your initial position, and why?
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T1455147822"] = "What have you changed, kept, or left open compared to your initial position, and why?"
-- Show options for action
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T1601690165"] = "Show options for action"
-- Manipulative questions and straw men
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T1656068515"] = "Manipulative questions and straw men"
-- Topic of the event
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T1766174370"] = "Topic of the event"
-- Moderate
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T177463328"] = "Moderate"
-- Other perspectives: Whose point of view would you like to understand better, or have you hardly considered so far?
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T1885081930"] = "Other perspectives: Whose point of view would you like to understand better, or have you hardly considered so far?"
-- Stays in your preparation and never goes into the research on the web
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T1949096445"] = "Stays in your preparation and never goes into the research on the web"
-- Very demanding
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T1967151608"] = "Very demanding"
-- Date and time
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T2054731205"] = "Date and time"
-- Notes on how to use your materials
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T2123646094"] = "Notes on how to use your materials"
-- Duration of your contribution
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T223853289"] = "Duration of your contribution"
-- Special notes for the test
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T2242825987"] = "Special notes for the test"
-- Uncertainties and worries about communicating
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T2428368968"] = "Uncertainties and worries about communicating"
-- Difficulty at the start
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T243923502"] = "Difficulty at the start"
-- E.g., 90 minutes
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T2537467295"] = "E.g., 90 minutes"
-- Place
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T2597472412"] = "Place"
-- E.g., 8 minutes
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T2697611204"] = "E.g., 8 minutes"
-- A few sentences are enough
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T2765665158"] = "A few sentences are enough"
-- Other participants
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T277557865"] = "Other participants"
-- What do you want to achieve with your contribution?
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T2818219287"] = "What do you want to achieve with your contribution?"
-- Scientific criticism
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T2865565843"] = "Scientific criticism"
-- The address of the event page, beginning with https://
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T2900883219"] = "The address of the event page, beginning with https://"
-- Names and roles, as far as you know them
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T3020176946"] = "Names and roles, as far as you know them"
-- Personal detail: it goes into exports and shared sessions only when you explicitly choose so.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T3063635296"] = "Personal detail: it goes into exports and shared sessions only when you explicitly choose so."
-- Political instrumentalization
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T3078371152"] = "Political instrumentalization"
-- Mixed
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T3114856344"] = "Mixed"
-- Personal involvement: Where does the topic touch you personally? Which question or criticism could trigger a strong reaction?
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T3199962614"] = "Personal involvement: Where does the topic touch you personally? Which question or criticism could trigger a strong reaction?"
-- Format of the event
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T3217479436"] = "Format of the event"
-- E.g., panel, keynote, or interview; online, on site, or hybrid
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T3297023821"] = "E.g., panel, keynote, or interview; online, on site, or hybrid"
-- Values and concerns: What matters most to you about this topic?
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T3444467007"] = "Values and concerns: What matters most to you about this topic?"
-- Audience
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T3506118019"] = "Audience"
-- Suggestions
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T3534677498"] = "Suggestions"
-- Emotionally charged criticism
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T3552262211"] = "Emotionally charged criticism"
-- Link to the event
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T3658981841"] = "Link to the event"
-- Raise awareness
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T3790372874"] = "Raise awareness"
-- E.g., use only paper X as the basis for the content
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T3804983466"] = "E.g., use only paper X as the basis for the content"
-- Core theses and arguments (if any)
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T39824489"] = "Core theses and arguments (if any)"
-- Total duration of the event
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T4051894943"] = "Total duration of the event"
-- Influence policy
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T4052553234"] = "Influence policy"
-- You can raise the difficulty after each question.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T519332031"] = "You can raise the difficulty after each question."
-- E.g., students, experts, or the general public
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T533097975"] = "E.g., students, experts, or the general public"
-- Short description of your contribution
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T667686615"] = "Short description of your contribution"
-- Type of questions
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T805059831"] = "Type of questions"
-- The central question or the title
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T828389221"] = "The central question or the title"
-- Uncertainties and controversies in content and science (if known)
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T82922542"] = "Uncertainties and controversies in content and science (if known)"
-- Inform
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T85613896"] = "Inform"
-- E.g., December 10, 2 pm
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T881586300"] = "E.g., December 10, 2 pm"
-- E.g., the venue and the city, or online
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T916990010"] = "E.g., the venue and the city, or online"
-- In your own words, with the suggestions below, or still open: the chat sharpens your goal and role with you.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T917276711"] = "In your own words, with the suggestions below, or still open: the chat sharpens your goal and role with you."
-- Topic of your contribution or your specific assignment
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T932670647"] = "Topic of your contribution or your specific assignment"
-- E.g., aggressive moderation, time pressure, or a particular counterpart
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T965551209"] = "E.g., aggressive moderation, time pressure, or a particular counterpart"
-- Take a position in the debate
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T974131333"] = "Take a position in the debate"
-- Demanding
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T985014450"] = "Demanding"
-- ## Welcome! The BeyondFacts Lab guides you through an AI-supported process of reflection, which helps you take a position in challenging communication situations and stay able to communicate. Data protection depends on the AI provider you choose, its rules for processing and storing data, and your configuration. Enter only data that are suitable and approved for this. Please check all results critically. Depending on the AI model, the answers may differ. Hallucinations cannot be ruled out. This is a prototype, so we look forward to your feedback.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLNOTICE::T940905480"] = "## Welcome!\n\nThe BeyondFacts Lab guides you through an AI-supported process of reflection, which helps you take a position in challenging communication situations and stay able to communicate.\n\nData protection depends on the AI provider you choose, its rules for processing and storing data, and your configuration. Enter only data that are suitable and approved for this.\n\nPlease check all results critically. Depending on the AI model, the answers may differ. Hallucinations cannot be ruled out.\n\nThis is a prototype, so we look forward to your feedback."
-- I have read the notes
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLNOTICEDIALOG::T4071737336"] = "I have read the notes"
-- Test situation
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLSTEPEXTENSIONS::T1049923473"] = "Test situation"
-- Knowledge and uncertainty
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLSTEPEXTENSIONS::T1147870258"] = "Knowledge and uncertainty"
-- How you convey evidence, trade-offs between values, and open questions.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLSTEPEXTENSIONS::T1290723890"] = "How you convey evidence, trade-offs between values, and open questions."
-- Event details
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLSTEPEXTENSIONS::T135387869"] = "Event details"
-- What the event is about, for whom, and what you were asked to contribute.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLSTEPEXTENSIONS::T158292580"] = "What the event is about, for whom, and what you were asked to contribute."
-- You can fill in single fields or start right away in the chat.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLSTEPEXTENSIONS::T2524378548"] = "You can fill in single fields or start right away in the chat."
-- Goals and role
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLSTEPEXTENSIONS::T2988481290"] = "Goals and role"
-- What matters to you, what touches you, and what moves others.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLSTEPEXTENSIONS::T3022557767"] = "What matters to you, what touches you, and what moves others."
-- What you want to achieve, and in which role you speak.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLSTEPEXTENSIONS::T3052319680"] = "What you want to achieve, and in which role you speak."
-- Strategy
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLSTEPEXTENSIONS::T3205650756"] = "Strategy"
-- Results and materials
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLSTEPEXTENSIONS::T3602322182"] = "Results and materials"
-- Values and perspectives
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLSTEPEXTENSIONS::T494574525"] = "Values and perspectives"
-- What you take away from your preparation.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLSTEPEXTENSIONS::T517692701"] = "What you take away from your preparation."
-- Play through critical questions and opposing positions.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLSTEPEXTENSIONS::T573043138"] = "Play through critical questions and opposing positions."
-- What you know for sure, what is uncertain, and where your expertise ends.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLSTEPEXTENSIONS::T762525208"] = "What you know for sure, what is uncertain, and where your expertise ends."
-- Some details are still missing. The chat will ask for them, and you can continue anyway.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLSTEPFORM::T1474406854"] = "Some details are still missing. The chat will ask for them, and you can continue anyway."
-- Please enter an address that begins with https:// or http://.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLSTEPFORM::T2837809367"] = "Please enter an address that begins with https:// or http://."
-- * The chat works best when you fill in these details.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLSTEPFORM::T3011046288"] = "* The chat works best when you fill in these details."
-- Extended bias poster
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BIASDAY::BIASOFTHEDAYASSISTANT::T1241605514"] = "Extended bias poster"
@@ -4416,6 +4746,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATAFOLDERS::T1259707710"] = "Your projec
-- Your temporary chats, along with their transcripts.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATAFOLDERS::T1293614512"] = "Your temporary chats, along with their transcripts."
-- Your preparations in the BeyondFacts Lab.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATAFOLDERS::T1415474409"] = "Your preparations in the BeyondFacts Lab."
-- What belongs into a backup
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATAFOLDERS::T1682175486"] = "What belongs into a backup"
@@ -9591,6 +9924,24 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T82
-- Restore default
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T913168756"] = "Restore default"
-- How the lab addresses you in languages with a formal and an informal address, such as German.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBEYONDFACTSLAB::T1832497993"] = "How the lab addresses you in languages with a formal and an informal address, such as German."
-- Assistant: BeyondFacts Lab Options
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBEYONDFACTSLAB::T19824556"] = "Assistant: BeyondFacts Lab Options"
-- Form of address
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBEYONDFACTSLAB::T2183481736"] = "Form of address"
-- Select a minimum confidence level
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBEYONDFACTSLAB::T2579793544"] = "Select a minimum confidence level"
-- Close
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBEYONDFACTSLAB::T3448155331"] = "Close"
-- Your preparations usually contain unpublished research. Therefore, the lab accepts only providers with at least medium confidence. You can raise this minimum, e.g., to use only self-hosted models.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBEYONDFACTSLAB::T4002746484"] = "Your preparations usually contain unpublished research. Therefore, the lab accepts only providers with at least medium confidence. You can raise this minimum, e.g., to use only self-hosted models."
-- Files are sent as they were first read
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHAT::T1320692831"] = "Files are sent as they were first read"
@@ -11199,12 +11550,18 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T3733831260"] = "Generate your own
-- Generate an ERI server to integrate business systems.
UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T3756213118"] = "Generate an ERI server to integrate business systems."
-- Prepare step by step for a challenging appearance as a researcher, e.g., a panel discussion or an interview.
UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T386812830"] = "Prepare step by step for a challenging appearance as a researcher, e.g., a panel discussion or an interview."
-- Use an LLM to find an icon for a given context.
UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T3881504200"] = "Use an LLM to find an icon for a given context."
-- Job Posting
UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T3930052338"] = "Job Posting"
-- BeyondFacts Lab
UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T3956097526"] = "BeyondFacts Lab"
-- Ask a question about a legal document.
UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T3970214537"] = "Ask a question about a legal document."
@@ -12921,6 +13278,9 @@ UI_TEXT_CONTENT["AISTUDIO::SETTINGS::DATAMODEL::OUTBOUNDDATARESTRICTIONEXTENSION
-- Only services configured in AI Studio
UI_TEXT_CONTENT["AISTUDIO::SETTINGS::DATAMODEL::OUTBOUNDDATARESTRICTIONEXTENSIONS::T750634166"] = "Only services configured in AI Studio"
-- BeyondFacts Lab: Prepare for challenging conversations about your research, step by step
UI_TEXT_CONTENT["AISTUDIO::SETTINGS::DATAMODEL::PREVIEWFEATURESEXTENSIONS::T1110065980"] = "BeyondFacts Lab: Prepare for challenging conversations about your research, step by step"
-- Visual Briefing Assistant: Turn source material into an interactive briefing
UI_TEXT_CONTENT["AISTUDIO::SETTINGS::DATAMODEL::PREVIEWFEATURESEXTENSIONS::T1217946647"] = "Visual Briefing Assistant: Turn source material into an interactive briefing"
@@ -13170,6 +13530,9 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::COMPONENTSEXTENSIONS::T348883878"] = "Document
-- Translation Assistant
UI_TEXT_CONTENT["AISTUDIO::TOOLS::COMPONENTSEXTENSIONS::T3887962308"] = "Translation Assistant"
-- BeyondFacts Lab
UI_TEXT_CONTENT["AISTUDIO::TOOLS::COMPONENTSEXTENSIONS::T3956097526"] = "BeyondFacts Lab"
-- Agenda Assistant
UI_TEXT_CONTENT["AISTUDIO::TOOLS::COMPONENTSEXTENSIONS::T4034795997"] = "Agenda Assistant"
+2
View File
@@ -1,5 +1,6 @@
using AIStudio.Agents;
using AIStudio.Agents.AssistantAudit;
using AIStudio.Assistants.BeyondFactsLab;
using AIStudio.Assistants.VisualBriefing;
using AIStudio.Settings;
using AIStudio.Tools.ToolCallingSystem;
@@ -229,6 +230,7 @@ internal sealed class Program
builder.Services.AddSingleton<VisualBriefingBuildOrchestrator>();
builder.Services.AddSingleton<VisualBriefingPreviewTokenService>();
builder.Services.AddSingleton<IMediaTranscriptStorage, VisualBriefingTranscriptStorage>();
builder.Services.AddSingleton<BeyondFactsLabStore>();
builder.Services.AddSingleton<PluginInstallService>();
builder.Services.AddSingleton<UpdatePolicy>();
builder.Services.AddSingleton<AssistantPluginGenerationService>();
+1
View File
@@ -27,6 +27,7 @@ public sealed partial class Routes
public const string ASSISTANT_SYNONYMS = "/assistant/synonyms";
public const string ASSISTANT_SLIDE_BUILDER = "/assistant/slide-builder";
public const string ASSISTANT_VISUAL_BRIEFING = "/assistant/visual-briefing";
public const string ASSISTANT_BEYOND_FACTS_LAB = "/assistant/beyondfacts-lab";
public const string ASSISTANT_MY_TASKS = "/assistant/my-tasks";
public const string ASSISTANT_JOB_POSTING = "/assistant/job-posting";
public const string ASSISTANT_BIAS = "/assistant/bias-of-the-day";
@@ -29,6 +29,7 @@ public enum ConfigurableAssistant
LOG_VIEWER_ASSISTANT,
VISUAL_BRIEFING_ASSISTANT,
BATCH_PROCESSING_ASSISTANT,
BEYOND_FACTS_LAB_ASSISTANT,
// ReSharper disable InconsistentNaming
I18N_ASSISTANT,
@@ -1,4 +1,5 @@
using AIStudio.Assistants.Agenda;
using AIStudio.Assistants.BeyondFactsLab;
using AIStudio.Assistants.Coding;
using AIStudio.Assistants.IconFinder;
using AIStudio.Assistants.RewriteImprove;
@@ -315,6 +316,27 @@ public static class ConfigurationSelectDataFactory
}
}
/// <summary>
/// Gets the minimum confidence levels the BeyondFacts Lab offers: none below its floor.
/// </summary>
/// <param name="settingsManager">The settings, whose enforced global minimum may lift the floor.</param>
public static IEnumerable<ConfigurationSelectData<ConfidenceLevel>> GetBeyondFactsLabConfidenceLevelsData(SettingsManager settingsManager)
{
var minimumLevel = DataBeyondFactsLab.MINIMUM_CONFIDENCE_FLOOR;
if (settingsManager.ConfigurationData.Confidence is { EnforceGlobalMinimumConfidence: true } && settingsManager.ConfigurationData.Confidence.GlobalMinimumConfidence > minimumLevel)
minimumLevel = settingsManager.ConfigurationData.Confidence.GlobalMinimumConfidence;
foreach (var level in Enum.GetValues<ConfidenceLevel>())
if (level >= minimumLevel)
yield return new(level.Name, level);
}
public static IEnumerable<ConfigurationSelectData<BflAddressForm>> GetBeyondFactsLabAddressFormData()
{
foreach (var addressForm in Enum.GetValues<BflAddressForm>())
yield return new(addressForm.Name, addressForm);
}
public static IEnumerable<ConfigurationSelectData<ConfidenceLevel>> GetDataSourceConfidenceLevelsData()
{
foreach (var level in Enum.GetValues<ConfidenceLevel>())
@@ -217,6 +217,11 @@ public sealed class Data
/// Gets the managed Visual Briefing Assistant defaults.
/// </summary>
public DataVisualBriefing VisualBriefing { get; set; } = new(x => x.VisualBriefing);
/// <summary>
/// Gets the managed settings of the BeyondFacts Lab.
/// </summary>
public DataBeyondFactsLab BeyondFactsLab { get; set; } = new(x => x.BeyondFactsLab);
public DataLegalCheck LegalCheck { get; set; } = new();
@@ -0,0 +1,56 @@
using System.Linq.Expressions;
using AIStudio.Assistants.BeyondFactsLab;
using AIStudio.Provider;
namespace AIStudio.Settings.DataModel;
/// <summary>
/// Stores the settings of the BeyondFacts Lab, which an organization may manage.
/// </summary>
/// <param name="configSelection">The managed-configuration selector.</param>
public sealed class DataBeyondFactsLab(Expression<Func<Data, DataBeyondFactsLab>>? configSelection = null)
{
/// <summary>
/// The lowest minimum confidence the BeyondFacts Lab accepts, whatever the person or their organization configures.
/// </summary>
/// <remarks>
/// Unpublished research is the usual material of the lab. The built-in confidence schemes give this level
/// to the cloud providers of the region one trusts and to self-hosted models; HIGH alone would leave only
/// self-hosted models. A higher minimum of the person or the organization wins.
/// </remarks>
public const ConfidenceLevel MINIMUM_CONFIDENCE_FLOOR = ConfidenceLevel.MEDIUM;
/// <summary>
/// Initializes unmanaged settings of the BeyondFacts Lab.
/// </summary>
public DataBeyondFactsLab() : this(null)
{
}
/// <summary>
/// Gets or sets the provider which new preparations start with.
/// </summary>
public string PreselectedProvider { get; set; } = ManagedConfiguration.Register(configSelection, value => value.PreselectedProvider, string.Empty);
/// <summary>
/// Gets or sets the minimum confidence a provider needs for the lab.
/// </summary>
/// <remarks>
/// A value below MINIMUM_CONFIDENCE_FLOOR has no effect, see ComponentsExtensions.MinimumConfidence.
/// </remarks>
public ConfidenceLevel MinimumProviderConfidence { get; set; } = ManagedConfiguration.Register(configSelection, value => value.MinimumProviderConfidence, MINIMUM_CONFIDENCE_FLOOR);
/// <summary>
/// Gets or sets how the lab addresses the person in languages with a formal and an informal address.
/// </summary>
public BflAddressForm AddressForm { get; set; } = ManagedConfiguration.Register(configSelection, value => value.AddressForm, BflAddressForm.FORMAL);
/// <summary>
/// Gets or sets the hash of the notes the person confirmed, see BflNotice; empty while they confirmed none.
/// </summary>
/// <remarks>
/// Only the person can confirm the notes, so an organization cannot manage this setting.
/// </remarks>
public string AcceptedNoticeHash { get; set; } = string.Empty;
}
@@ -19,4 +19,5 @@ public enum PreviewFeatures
PRE_VISUAL_BRIEFING_ASSISTANT_2026,
PRE_MAILBOXES_2026,
PRE_MATHEMATICS_2026,
PRE_BEYOND_FACTS_LAB_2026,
}
@@ -22,6 +22,7 @@ public static class PreviewFeaturesExtensions
PreviewFeatures.PRE_VISUAL_BRIEFING_ASSISTANT_2026 => TB("Visual Briefing Assistant: Turn source material into an interactive briefing"),
PreviewFeatures.PRE_MAILBOXES_2026 => TB("Mailboxes: Let the AI search and read your e-mails, kept in a local index on your computer"),
PreviewFeatures.PRE_MATHEMATICS_2026 => TB("Mathematics: Let the AI calculate exactly and work with formulas, right on your computer"),
PreviewFeatures.PRE_BEYOND_FACTS_LAB_2026 => TB("BeyondFacts Lab: Prepare for challenging conversations about your research, step by step"),
_ => TB("Unknown preview feature")
};
@@ -27,6 +27,7 @@ public static class PreviewVisibilityExtensions
{
features.Add(PreviewFeatures.PRE_MAILBOXES_2026);
features.Add(PreviewFeatures.PRE_MATHEMATICS_2026);
features.Add(PreviewFeatures.PRE_BEYOND_FACTS_LAB_2026);
}
if (visibility >= PreviewVisibility.EXPERIMENTAL)
@@ -0,0 +1,64 @@
using System.Text;
using Microsoft.Extensions.FileProviders;
#if RELEASE
using System.Reflection;
#endif
namespace AIStudio.Tools;
/// <summary>
/// Reads files which come with AI Studio, such as prompts, guides, or the built-in plugins.
/// </summary>
/// <remarks>
/// The files lie below the project folder and are listed as EmbeddedResource in the project file. A development
/// build reads them from the disk, so that a change shows without building again; a release reads them from the
/// resources embedded into the app. Paths are relative to the project folder and use forward slashes.
/// </remarks>
public static class AppResources
{
/// <summary>
/// Gets a file provider for a folder of the app.
/// </summary>
/// <param name="relativeFolder">The folder, relative to the project folder, e.g., "Plugins".</param>
/// <returns>The file provider, whose paths are relative to that folder.</returns>
public static IFileProvider FolderProvider(string relativeFolder)
{
var folder = Normalize(relativeFolder);
#if DEBUG
return new PhysicalFileProvider(Path.Join(Environment.CurrentDirectory, folder));
#else
return new ManifestEmbeddedFileProvider(Assembly.GetAssembly(typeof(Program))!, folder);
#endif
}
/// <summary>
/// Reads a text file of the app.
/// </summary>
/// <param name="relativePath">The file, relative to the project folder, e.g., "Assistants/PromptOptimizer/prompting_guideline.md".</param>
/// <param name="token">The cancellation token.</param>
/// <returns>The text, or an empty string when the app has no such file.</returns>
public static async Task<string> ReadTextAsync(string relativePath, CancellationToken token = default)
{
var path = Normalize(relativePath);
#if DEBUG
var filePath = Path.Join(Environment.CurrentDirectory, path);
return File.Exists(filePath) ? await File.ReadAllTextAsync(filePath, Encoding.UTF8, token) : string.Empty;
#else
var file = new ManifestEmbeddedFileProvider(Assembly.GetAssembly(typeof(Program))!).GetFileInfo(path);
if (!file.Exists || file.IsDirectory)
return string.Empty;
await using var stream = file.CreateReadStream();
using var reader = new StreamReader(stream, Encoding.UTF8);
return await reader.ReadToEndAsync(token);
#endif
}
/// <summary>
/// Turns a path into the form both kinds of builds understand: forward slashes, without a slash at either end.
/// </summary>
/// <param name="relativePath">The path.</param>
private static string Normalize(string relativePath) => relativePath.Replace('\\', '/').Trim('/');
}
@@ -72,6 +72,7 @@ public static class AssistantVisibilityExtensions
Components.BATCH_PROCESSING_ASSISTANT => ConfigurableAssistant.BATCH_PROCESSING_ASSISTANT,
Components.SLIDE_BUILDER_ASSISTANT => ConfigurableAssistant.SLIDE_BUILDER_ASSISTANT,
Components.VISUAL_BRIEFING_ASSISTANT => ConfigurableAssistant.VISUAL_BRIEFING_ASSISTANT,
Components.BEYOND_FACTS_LAB_ASSISTANT => ConfigurableAssistant.BEYOND_FACTS_LAB_ASSISTANT,
Components.I18N_ASSISTANT => ConfigurableAssistant.I18N_ASSISTANT,
Components.LOG_VIEWER_ASSISTANT => ConfigurableAssistant.LOG_VIEWER_ASSISTANT,
@@ -43,4 +43,5 @@ public enum Components
LOG_VIEWER_ASSISTANT,
VISUAL_BRIEFING_ASSISTANT,
BATCH_PROCESSING_ASSISTANT,
BEYOND_FACTS_LAB_ASSISTANT,
}
@@ -22,6 +22,7 @@ public static class ComponentsExtensions
public PreviewFeatures RequiredPreviewFeature => component switch
{
Components.VISUAL_BRIEFING_ASSISTANT => PreviewFeatures.PRE_VISUAL_BRIEFING_ASSISTANT_2026,
Components.BEYOND_FACTS_LAB_ASSISTANT => PreviewFeatures.PRE_BEYOND_FACTS_LAB_2026,
_ => PreviewFeatures.NONE,
};
@@ -33,15 +34,16 @@ public static class ComponentsExtensions
/// <remarks>
/// Components return <c>false</c> for three different reasons. The chat has no assistant sessions
/// at all. Dynamic assistants key their sessions per plugin. The visual briefing assistant keys
/// its sessions per briefing, so it owns one slot per stored briefing rather than one per
/// component. All must be excluded from the single-slot checks, which is why this is a
/// capability and not a component comparison.
/// its sessions per briefing, and the BeyondFacts Lab per step of a preparation, so they own one
/// slot per stored item rather than one per component. All must be excluded from the single-slot
/// checks, which is why this is a capability and not a component comparison.
/// </remarks>
/// <value><c>true</c> when the component owns exactly one session slot.</value>
public bool HasSingleSessionSlot => component switch
{
Components.CHAT or Components.DYNAMIC_ASSISTANT => false,
Components.VISUAL_BRIEFING_ASSISTANT => false,
Components.BEYOND_FACTS_LAB_ASSISTANT => false,
_ => true,
};
@@ -68,7 +70,10 @@ public static class ComponentsExtensions
Components.DOCUMENT_ANALYSIS_ASSISTANT => false,
Components.BATCH_PROCESSING_ASSISTANT => false,
Components.LOG_VIEWER_ASSISTANT => false,
// The lab takes in nothing from other places, and its answers stay in its own steps:
Components.BEYOND_FACTS_LAB_ASSISTANT => false,
Components.APP_SETTINGS => false,
Components.WRITER => false,
@@ -105,7 +110,8 @@ public static class ComponentsExtensions
Components.VISUAL_BRIEFING_ASSISTANT => TB("Visual Briefing Assistant"),
Components.META_ASSISTANT => TB("Assistant Builder"),
Components.LOG_VIEWER_ASSISTANT => TB("Log Viewer Assistant"),
Components.BEYOND_FACTS_LAB_ASSISTANT => TB("BeyondFacts Lab"),
Components.CHAT => TB("New Chat"),
_ => Enum.GetName(component)!,
@@ -157,7 +163,10 @@ public static class ComponentsExtensions
Components.SLIDE_BUILDER_ASSISTANT => settingsManager.ConfigurationData.SlideBuilder.PreselectOptions ? settingsManager.ConfigurationData.SlideBuilder.MinimumProviderConfidence : default,
Components.META_ASSISTANT => settingsManager.ConfigurationData.AssistantBuilder.PreselectOptions ? settingsManager.ConfigurationData.AssistantBuilder.MinimumProviderConfidence : default,
Components.VISUAL_BRIEFING_ASSISTANT => settingsManager.ConfigurationData.VisualBriefing.MinimumProviderConfidence,
// Unpublished research is the usual material of the lab, so its minimum never drops below the floor:
Components.BEYOND_FACTS_LAB_ASSISTANT => settingsManager.ConfigurationData.BeyondFactsLab.MinimumProviderConfidence > DataBeyondFactsLab.MINIMUM_CONFIDENCE_FLOOR ? settingsManager.ConfigurationData.BeyondFactsLab.MinimumProviderConfidence : DataBeyondFactsLab.MINIMUM_CONFIDENCE_FLOOR,
// The minimum confidence for the Document Analysis Assistant is set per policy.
// We do this inside the Document Analysis Assistant component:
Components.DOCUMENT_ANALYSIS_ASSISTANT => ConfidenceLevel.NONE,
@@ -191,6 +200,7 @@ public static class ComponentsExtensions
Components.SLIDE_BUILDER_ASSISTANT => settingsManager.ConfigurationData.SlideBuilder.PreselectOptions ? settingsManager.GetProviderById(settingsManager.ConfigurationData.SlideBuilder.PreselectedProvider) : Settings.Provider.NONE,
Components.META_ASSISTANT => settingsManager.ConfigurationData.AssistantBuilder.PreselectOptions ? settingsManager.GetProviderById(settingsManager.ConfigurationData.AssistantBuilder.PreselectedProvider) : Settings.Provider.NONE,
Components.VISUAL_BRIEFING_ASSISTANT => settingsManager.GetProviderById(settingsManager.ConfigurationData.VisualBriefing.PreselectedProvider),
Components.BEYOND_FACTS_LAB_ASSISTANT => settingsManager.GetProviderById(settingsManager.ConfigurationData.BeyondFactsLab.PreselectedProvider),
// The Document Analysis Assistant does not have a preselected provider at the component level.
// The provider is selected per policy instead. We do this inside the Document Analysis Assistant component.
@@ -229,6 +239,9 @@ public static class ComponentsExtensions
// The profile is selected per policy instead. We do this inside the Document Analysis Assistant component:
Components.DOCUMENT_ANALYSIS_ASSISTANT => Profile.NO_PROFILE.Id,
// The lab works with what the person enters in each preparation and never with a profile:
Components.BEYOND_FACTS_LAB_ASSISTANT => Profile.NO_PROFILE.Id,
_ => string.Empty,
};
@@ -45,6 +45,7 @@ public static class DataBackupContents
new(DataBackupLocation.DATA_DIRECTORY, PLUGINS_FOLDER, DataBackupEntryKind.PLUGINS),
new(DataBackupLocation.DATA_DIRECTORY, "tokenizers/", DataBackupEntryKind.TOKENIZERS),
new(DataBackupLocation.DATA_DIRECTORY, "visualBriefings/", DataBackupEntryKind.VISUAL_BRIEFINGS),
new(DataBackupLocation.DATA_DIRECTORY, "beyondFactsLab/", DataBackupEntryKind.BEYOND_FACTS_LAB),
new(DataBackupLocation.DATA_DIRECTORY, "databases/sqlite/", DataBackupEntryKind.DATA_SOURCE_INDEX),
new(DataBackupLocation.DATA_DIRECTORY, "databases/vector_database/stores/", DataBackupEntryKind.VECTOR_STORES),
new(DataBackupLocation.DATA_DIRECTORY, "mediaTranscripts/", DataBackupEntryKind.MEDIA_TRANSCRIPTS),
@@ -18,6 +18,7 @@ public sealed record DataBackupEntry(DataBackupLocation Location, string Relativ
or DataBackupEntryKind.PLUGINS
or DataBackupEntryKind.TOKENIZERS
or DataBackupEntryKind.VISUAL_BRIEFINGS
or DataBackupEntryKind.BEYOND_FACTS_LAB
or DataBackupEntryKind.DATA_SOURCE_INDEX
or DataBackupEntryKind.VECTOR_STORES
or DataBackupEntryKind.MEDIA_TRANSCRIPTS;
@@ -41,6 +41,11 @@ public enum DataBackupEntryKind
/// </summary>
VISUAL_BRIEFINGS,
/// <summary>
/// The preparations of the BeyondFacts Lab.
/// </summary>
BEYOND_FACTS_LAB,
/// <summary>
/// The SQLite index of the data sources: which files are embedded, and their text chunks.
/// </summary>
@@ -19,7 +19,7 @@ public static class ILangExtensions
typeName = typeName[..typeName.IndexOf('`')];
var ns = $"{typeNamespace}::{typeName}".ToUpperInvariant().Replace(".", "::");
var key = $"root::{ns}::T{fallbackEN.ToFNV32()}";
var key = $"root::{ns}::T{I18NKeyHash.Of(fallbackEN)}";
if(plugin is NoPluginLanguage)
return fallbackEN;
@@ -516,6 +516,11 @@ public sealed class PluginConfiguration(bool isInternal, LuaState state, PluginT
ManagedConfiguration.TryProcessConfiguration(x => x.VisualBriefing, x => x.OptimizeImages, this.Id, settingsTable, dryRun);
ManagedConfiguration.TryProcessConfiguration(x => x.VisualBriefing, x => x.MinimumProviderConfidence, this.Id, settingsTable, dryRun);
// Config: BeyondFacts Lab defaults?
ManagedConfiguration.TryProcessConfiguration(x => x.BeyondFactsLab, x => x.PreselectedProvider, Guid.Empty, this.Id, settingsTable, dryRun);
ManagedConfiguration.TryProcessConfiguration(x => x.BeyondFactsLab, x => x.MinimumProviderConfidence, this.Id, settingsTable, dryRun);
ManagedConfiguration.TryProcessConfiguration(x => x.BeyondFactsLab, x => x.AddressForm, this.Id, settingsTable, dryRun);
// Config: Assistant Builder defaults?
ManagedConfiguration.TryProcessConfiguration(x => x.AssistantBuilder, x => x.PreselectOptions, this.Id, settingsTable, dryRun);
ManagedConfiguration.TryProcessConfiguration(x => x.AssistantBuilder, x => x.PreselectedProvider, Guid.Empty, this.Id, settingsTable, dryRun);
@@ -1,9 +1,5 @@
using Microsoft.Extensions.FileProviders;
#if RELEASE
using System.Reflection;
#endif
namespace AIStudio.Tools.PluginSystem;
public static partial class PluginFactory
@@ -48,13 +44,7 @@ public static partial class PluginFactory
{
try
{
#if DEBUG
var basePath = Path.Join(Environment.CurrentDirectory, "Plugins");
var resourceFileProvider = new PhysicalFileProvider(basePath);
#else
var resourceFileProvider = new ManifestEmbeddedFileProvider(Assembly.GetAssembly(type: typeof(Program))!, "Plugins");
#endif
var resourceFileProvider = AppResources.FolderProvider("Plugins");
var metaData = plugin.MetaData();
var mainResourcePath = $"{metaData.ResourcePath}/plugin.lua";
var resourceInfo = resourceFileProvider.GetFileInfo(mainResourcePath);
@@ -1,7 +1,3 @@
// ReSharper disable RedundantUsingDirective
using System.Reflection;
using Microsoft.Extensions.FileProviders;
// ReSharper restore RedundantUsingDirective
using System.Text;
using System.Text.Json;
using AIStudio.Assistants.Builder;
@@ -255,7 +251,7 @@ public sealed class AssistantPluginGenerationService(ToolRegistry toolRegistry,
foreach (var contextFile in ASSISTANT_CONTEXT_FILES)
{
var content = await ReadAppResourceTextAsync(contextFile.RelativePath);
var content = await AppResources.ReadTextAsync(contextFile.RelativePath);
if (string.IsNullOrWhiteSpace(content))
{
logger.LogError($"The context for \"{contextFile.Title}\" could not be read from the assembly. Path: {contextFile.RelativePath}");
@@ -713,7 +709,7 @@ public sealed class AssistantPluginGenerationService(ToolRegistry toolRegistry,
private async Task<string> LoadLuaResponseSchemaAsync()
{
var responseSchema = await ReadAppResourceTextAsync(LUA_RESPONSE_SCHEMA_PATH);
var responseSchema = await AppResources.ReadTextAsync(LUA_RESPONSE_SCHEMA_PATH);
if (!string.IsNullOrWhiteSpace(responseSchema))
return responseSchema.Trim();
@@ -743,26 +739,6 @@ public sealed class AssistantPluginGenerationService(ToolRegistry toolRegistry,
return builder.ToString().Trim();
}
private static async Task<string> ReadAppResourceTextAsync(string relativePath)
{
relativePath = relativePath.Replace('\\', '/');
#if DEBUG
var filePath = Path.Join(Environment.CurrentDirectory, relativePath);
return File.Exists(filePath)
? await File.ReadAllTextAsync(filePath)
: string.Empty;
#else
var provider = new ManifestEmbeddedFileProvider(Assembly.GetAssembly(type: typeof(Program))!);
var file = provider.GetFileInfo(relativePath);
if (!file.Exists)
return string.Empty;
await using var stream = file.CreateReadStream();
using var reader = new StreamReader(stream, Encoding.UTF8);
return await reader.ReadToEndAsync();
#endif
}
private static bool ProviderIsUsable(ProviderSettings provider) => provider != ProviderSettings.NONE && provider.UsedLLMProvider is not LLMProviders.NONE;
private static bool IsValidChatLaunchRequest(AssistantBuilderChatLaunchRequest? launch)
@@ -1 +1,2 @@
# v26.11.2, build 261 (2026-11-xx xx:xx UTC)
- Added the BeyondFacts Lab as a prototype. It helps researchers prepare step by step for a challenging appearance, such as a panel discussion, an interview, or a hearing. Each event gets a preparation of its own, which leads you through seven steps, from the details of the event to what you take away, and shows you which key details are still missing. Your preparations are part of your data backups. To try it, open the app settings, allow preview features down to prototype, and enable the BeyondFacts Lab. Thanks to Lara Wüster from the Technical University of Munich (TUM) for the concept and the content of the lab.