mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2026-11-10 17:27:21 +00:00
Added the BeyondFacts Lab as a prototype (#1175)
This commit is contained in:
1 parent
db8dae1cf6
commit
b6c3fbb5db
90 files changed
+5936
-209
No files matched your search
@@ -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)
|
||||
|
||||
Reference in new issue
Block a user