mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2026-11-11 14:47:21 +00:00
Added an introduction and an onboarding chat to the BeyondFacts Lab (#1178)
This commit is contained in:
1 parent
79e0c80031
commit
22d94eb8fd
35 files changed
+1377
-361
No files matched your search
@@ -7,16 +7,26 @@ namespace AIStudio.Assistants.BeyondFactsLab;
|
||||
public sealed partial class BeyondFactsLabStore
|
||||
{
|
||||
/// <summary>
|
||||
/// The file of a step which holds its chat.
|
||||
/// The file which holds a chat of the lab.
|
||||
/// </summary>
|
||||
private const string THREAD_FILE_NAME = "thread.json";
|
||||
|
||||
/// <summary>
|
||||
/// Loads the chat of a step.
|
||||
/// The folder of the conversation about whether the lab fits a task. It is no ID, so no preparation can have it.
|
||||
/// </summary>
|
||||
/// <param name="location">The step of the preparation.</param>
|
||||
private const string ONBOARDING_DIRECTORY_NAME = "onboarding";
|
||||
|
||||
/// <summary>
|
||||
/// Guards the conversation about whether the lab fits a task, which belongs to no preparation.
|
||||
/// </summary>
|
||||
private readonly SemaphoreSlim onboardingLock = new(1, 1);
|
||||
|
||||
/// <summary>
|
||||
/// Loads a chat of the lab.
|
||||
/// </summary>
|
||||
/// <param name="location">Where the chat belongs.</param>
|
||||
/// <param name="token">The cancellation token.</param>
|
||||
/// <returns>The chat, or null while the step has none.</returns>
|
||||
/// <returns>The chat, or null while there is none.</returns>
|
||||
/// <exception cref="InvalidDataException">This version cannot open the chat.</exception>
|
||||
public async Task<ChatThread?> LoadThreadAsync(BflThreadLocation location, CancellationToken token = default)
|
||||
{
|
||||
@@ -25,34 +35,34 @@ public sealed partial class BeyondFactsLabStore
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stores the chat of a step and counts it as a change of the preparation.
|
||||
/// Stores a chat of the lab. The chat of a step counts as a change of its preparation.
|
||||
/// </summary>
|
||||
/// <param name="location">The step of the preparation.</param>
|
||||
/// <param name="location">Where the chat belongs.</param>
|
||||
/// <param name="thread">The chat.</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, e.g., because the person deleted it while the model answered.</exception>
|
||||
/// <exception cref="FileNotFoundException">The preparation of the step does not exist, e.g., because the person deleted it while the model answered.</exception>
|
||||
/// <exception cref="InvalidDataException">This version cannot open the preparation or the stored chat, so it does not write them either.</exception>
|
||||
public async Task<BflProject> SaveThreadAsync(BflThreadLocation location, ChatThread thread, CancellationToken token = default)
|
||||
public async Task SaveThreadAsync(BflThreadLocation location, ChatThread thread, CancellationToken token = default)
|
||||
{
|
||||
if (thread.ChatId != location.ChatId)
|
||||
throw new ArgumentException("The chat belongs to another step.", nameof(thread));
|
||||
throw new ArgumentException("The chat belongs to another place.", nameof(thread));
|
||||
|
||||
await this.InitializeAsync(token);
|
||||
var gate = this.GetLock(location.ProjectId);
|
||||
var gate = this.ThreadLock(location);
|
||||
await gate.WaitAsync(token);
|
||||
try
|
||||
{
|
||||
var project = await this.LoadRequiredWithoutInitializeAsync(location.ProjectId, token);
|
||||
var project = location is BflStepThreadLocation stepLocation ? await this.LoadRequiredWithoutInitializeAsync(stepLocation.ProjectId, token) : null;
|
||||
|
||||
// A chat which a newer version stored, or which is damaged, stays as it is:
|
||||
await this.LoadThreadWithoutInitializeAsync(location, token);
|
||||
|
||||
await WriteTextAtomicAsync(this.ThreadPath(location), JsonSerializer.Serialize(thread, WorkspaceBehaviour.JSON_OPTIONS), token);
|
||||
if (project is null)
|
||||
return;
|
||||
|
||||
project.ModifiedAtUtc = DateTimeOffset.UtcNow;
|
||||
await this.StoreProjectAsync(project, token);
|
||||
return project;
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -61,18 +71,18 @@ public sealed partial class BeyondFactsLabStore
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deletes the chat of a step, so that its conversation starts over.
|
||||
/// Deletes a chat of the lab, so that its conversation starts over.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Unlike the other files of a step, a chat which this version cannot open is deleted as well: the person asked
|
||||
/// to start the conversation over, and nothing else refers to it.
|
||||
/// </remarks>
|
||||
/// <param name="location">The step of the preparation.</param>
|
||||
/// <param name="location">Where the chat belongs.</param>
|
||||
/// <param name="token">The cancellation token.</param>
|
||||
public async Task DeleteThreadAsync(BflThreadLocation location, CancellationToken token = default)
|
||||
{
|
||||
await this.InitializeAsync(token);
|
||||
var gate = this.GetLock(location.ProjectId);
|
||||
var gate = this.ThreadLock(location);
|
||||
await gate.WaitAsync(token);
|
||||
try
|
||||
{
|
||||
@@ -87,40 +97,43 @@ public sealed partial class BeyondFactsLabStore
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds the chats of every preparation on the disk, from the folders alone.
|
||||
/// Finds every chat of the lab on the disk, from the folders alone.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// For the cleanup of the provenance store at the start, which must know every chat which may refer to an entry,
|
||||
/// also when the lab is switched off. A preparation this version cannot open keeps its chats as well.
|
||||
/// </remarks>
|
||||
/// <returns>The step of each chat, by the ID of the chat.</returns>
|
||||
/// <returns>The place of each chat, by the ID of the chat.</returns>
|
||||
public IReadOnlyDictionary<Guid, BflThreadLocation> ReadAllThreadLocations()
|
||||
{
|
||||
var locations = new Dictionary<Guid, BflThreadLocation>();
|
||||
if (!Directory.Exists(this.RootDirectory))
|
||||
return locations;
|
||||
|
||||
AddWhenStored(BflOnboardingThreadLocation.INSTANCE);
|
||||
foreach (var directory in Directory.EnumerateDirectories(this.RootDirectory))
|
||||
{
|
||||
if (!Guid.TryParse(Path.GetFileName(directory), out var projectId))
|
||||
continue;
|
||||
|
||||
foreach (var step in BflStep.All)
|
||||
{
|
||||
var location = new BflThreadLocation(projectId, step);
|
||||
if (File.Exists(this.ThreadPath(location)))
|
||||
locations[location.ChatId] = location;
|
||||
}
|
||||
AddWhenStored(new BflStepThreadLocation(projectId, step));
|
||||
}
|
||||
|
||||
return locations;
|
||||
|
||||
void AddWhenStored(BflThreadLocation location)
|
||||
{
|
||||
if (File.Exists(this.ThreadPath(location)))
|
||||
locations[location.ChatId] = location;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tells when the chat of a step was last written.
|
||||
/// Tells when a chat of the lab was last written.
|
||||
/// </summary>
|
||||
/// <param name="location">The step of the preparation.</param>
|
||||
/// <returns>The time, or null when the step has no chat.</returns>
|
||||
/// <param name="location">Where the chat belongs.</param>
|
||||
/// <returns>The time, or null when there is no chat.</returns>
|
||||
public DateTimeOffset? GetThreadWrittenAt(BflThreadLocation location)
|
||||
{
|
||||
var path = this.ThreadPath(location);
|
||||
@@ -128,9 +141,9 @@ public sealed partial class BeyondFactsLabStore
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads the chat of a step without the first start of the store.
|
||||
/// Reads a chat of the lab without the first start of the store.
|
||||
/// </summary>
|
||||
/// <param name="location">The step of the preparation.</param>
|
||||
/// <param name="location">Where the chat belongs.</param>
|
||||
/// <param name="token">The cancellation token.</param>
|
||||
/// <returns>The chat, or null when there is no file.</returns>
|
||||
/// <exception cref="InvalidDataException">This version cannot open the chat.</exception>
|
||||
@@ -151,7 +164,7 @@ public sealed partial class BeyondFactsLabStore
|
||||
throw new InvalidDataException("This version of AI Studio cannot open the chat.", exception);
|
||||
}
|
||||
|
||||
// A chat copied into another step would mix up what the provenance store keeps for it:
|
||||
// A chat copied into another place would mix up what the provenance store keeps for it:
|
||||
if (thread is null || thread.ChatId != location.ChatId || IsNull(thread.Blocks))
|
||||
throw new InvalidDataException("This version of AI Studio cannot open the chat.");
|
||||
|
||||
@@ -159,8 +172,26 @@ public sealed partial class BeyondFactsLabStore
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the file of the chat of a step.
|
||||
/// Gets the file of a chat of the lab.
|
||||
/// </summary>
|
||||
/// <param name="location">The step of the preparation.</param>
|
||||
private string ThreadPath(BflThreadLocation location) => Path.Combine(this.ProjectDirectory(location.ProjectId), STEPS_DIRECTORY_NAME, location.Step.FolderName, THREAD_FILE_NAME);
|
||||
/// <param name="location">Where the chat belongs.</param>
|
||||
private string ThreadPath(BflThreadLocation location) => location switch
|
||||
{
|
||||
BflStepThreadLocation stepLocation => Path.Combine(this.ProjectDirectory(stepLocation.ProjectId), STEPS_DIRECTORY_NAME, stepLocation.Step.FolderName, THREAD_FILE_NAME),
|
||||
BflOnboardingThreadLocation => Path.Combine(this.RootDirectory, ONBOARDING_DIRECTORY_NAME, THREAD_FILE_NAME),
|
||||
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(location), location, "The lab keeps no chat there.")
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Gets the lock which guards a chat of the lab: the chat of a step shares the lock of its preparation.
|
||||
/// </summary>
|
||||
/// <param name="location">Where the chat belongs.</param>
|
||||
private SemaphoreSlim ThreadLock(BflThreadLocation location) => location switch
|
||||
{
|
||||
BflStepThreadLocation stepLocation => this.GetLock(stepLocation.ProjectId),
|
||||
BflOnboardingThreadLocation => this.onboardingLock,
|
||||
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(location), location, "The lab keeps no chat there.")
|
||||
};
|
||||
}
|
||||
Reference in new issue
Block a user