Added tool calling support (#731)

Co-authored-by: krut_ni <nils.kruthoff@dlr.de>
Co-authored-by: Thorsten Sommer <SommerEngineering@users.noreply.github.com>
This commit is contained in:
authored and GitHub committed 2026-09-04 15:48:07 +02:00
1 parent b00c3f9ab3
commit 4d8d30e15e
266 files changed
+11186 -740

No files matched your search

@@ -175,6 +175,12 @@
<ProfileSelection MarginLeft="" @bind-CurrentProfile="@this.CurrentProfile"/>
}
@* No selection where the assistant's own rules already name the tools: *@
@if (this.SettingsManager.AreToolsEnabled() && this.AssistantManagedToolIds is null && this.SettingsManager.IsToolSelectionVisible(this.Component))
{
<ToolSelection Component="@this.Component" LLMProvider="@this.ProviderSettings" SelectedToolIds="@this.SelectedToolIds" SelectedToolIdsChanged="@this.SelectedToolIdsChanged" Disabled="@this.IsProcessing" />
}
<MudSpacer />
<HalluzinationReminder ContainerClass="my-0 ml-2"/>
</MudStack>
@@ -6,6 +6,7 @@ using AIStudio.Tools.AIJobs;
using AIStudio.Tools.AssistantSessions;
using AIStudio.Tools.Media;
using AIStudio.Tools.Services;
using AIStudio.Tools.ToolCallingSystem;
using Microsoft.AspNetCore.Components;
@@ -27,6 +28,9 @@ public abstract partial class AssistantBase<TSettings> : AssistantLowerBase wher
[Inject]
protected RustService RustService { get; init; } = null!;
[Inject]
protected ToolRegistry ToolRegistry { get; init; } = null!;
[Inject]
protected NavigationManager NavigationManager { get; init; } = null!;
@@ -127,8 +131,10 @@ public abstract partial class AssistantBase<TSettings> : AssistantLowerBase wher
protected virtual bool HasSettingsPanel => typeof(TSettings) != typeof(NoSettingsPanel);
protected HashSet<string> SelectedToolIds = [];
private readonly Timer formChangeTimer = new(TimeSpan.FromSeconds(1.6));
protected MudForm? Form;
protected CancellationTokenSource? CancellationTokenSource;
private bool isDisposed;
@@ -185,6 +191,7 @@ public abstract partial class AssistantBase<TSettings> : AssistantLowerBase wher
this.ProviderSettings = this.SettingsManager.GetPreselectedProvider(this.Component);
this.CurrentProfile = this.SettingsManager.GetPreselectedProfile(this.Component);
this.CurrentChatTemplate = this.SettingsManager.GetPreselectedChatTemplate(this.Component);
this.SelectedToolIds = this.SettingsManager.GetDefaultToolIds(this.Component);
await this.OnDefaultsAppliedAsync();
this.assistantSessionKey = new(this.Component, this.AssistantSessionInstanceId);
await this.AttachAssistantSessionIfAvailable();
@@ -236,6 +243,10 @@ public abstract partial class AssistantBase<TSettings> : AssistantLowerBase wher
private async Task Start()
{
await this.RefreshProviderSelectionFromConfigurationAsync();
if (this.ProviderSettings == Settings.Provider.NONE)
return;
if (this.MediaTranscriptionService.IsBusy(this.CurrentMediaImportOwner))
return;
@@ -357,6 +368,7 @@ public abstract partial class AssistantBase<TSettings> : AssistantLowerBase wher
ChatId = Guid.NewGuid(),
Name = string.Format(this.TB("Assistant - {0}"), this.Title),
Blocks = [],
RuntimeComponent = this.Component,
};
}
@@ -373,16 +385,71 @@ public abstract partial class AssistantBase<TSettings> : AssistantLowerBase wher
ChatId = chatId,
Name = name,
Blocks = [],
RuntimeComponent = this.Component,
};
return chatId;
}
private Task RefreshProviderSelectionFromConfigurationAsync()
{
this.ProviderSettings = this.SettingsManager.GetPreselectedProvider(this.Component, this.ProviderSettings.Id);
return Task.CompletedTask;
}
protected virtual void ResetProviderAndProfileSelection()
{
this.ProviderSettings = this.SettingsManager.GetPreselectedProvider(this.Component);
this.CurrentProfile = this.SettingsManager.GetPreselectedProfile(this.Component);
this.CurrentChatTemplate = this.SettingsManager.GetPreselectedChatTemplate(this.Component);
this.SelectedToolIds = this.SettingsManager.GetDefaultToolIds(this.Component);
}
/// <summary>
/// The tools this assistant runs with when its own rules name them, instead of asking the user.
/// </summary>
/// <remarks>
/// Null is the normal case: the user picks the tools. An assistant whose configuration already
/// says which tools belong to a run — a document analysis policy, for instance — returns them
/// here. Its tool selection then disappears from the footer, because there is nothing left to
/// choose: whoever wrote the policy has decided, and a user working with a policy rolled out by
/// their organization gets it as configured.
/// </remarks>
protected virtual IReadOnlySet<string>? AssistantManagedToolIds => null;
/// <summary>
/// The tools this assistant may hand to a model with the provider it currently uses.
/// </summary>
/// <remarks>
/// Whether the tools come from the assistant's own rules or from the user, the provider filter
/// always has the last word: a tool asking for more confidence than the selected provider has
/// never reaches the model, no matter who put it on the list. That filter belongs here rather
/// than into the stored selection, because a provider with too little confidence must not cost
/// the user a tool for good.
/// </remarks>
protected HashSet<string> GetRunnableToolIds()
{
if (this.AssistantManagedToolIds is not null)
return this.ToolRegistry.FilterToolIdsForProvider(this.ProviderSettings, this.AssistantManagedToolIds);
// What the user cannot see, the assistant does not use:
if (!this.SettingsManager.IsToolSelectionVisible(this.Component))
return [];
return this.ToolRegistry.FilterToolIdsForProvider(this.ProviderSettings, this.SelectedToolIds);
}
/// <summary>
/// Takes over a changed tool selection, no matter where the user made it.
/// </summary>
/// <remarks>
/// The footer offers one; an assistant may instead put the tools next to the setting they
/// belong to, as the batch processing does with its instructions. Both end up here.
/// </remarks>
protected Task SelectedToolIdsChanged(HashSet<string> updatedToolIds)
{
this.SelectedToolIds = ToolSelectionRules.NormalizeSelection(updatedToolIds);
return Task.CompletedTask;
}
protected DateTimeOffset AddUserRequest(string request, bool hideContentFromUser = false, params List<FileAttachment> attachments)
@@ -443,6 +510,10 @@ public abstract partial class AssistantBase<TSettings> : AssistantLowerBase wher
{
this.ChatThread.Blocks.Add(this.ResultingContentBlock);
this.ChatThread.SelectedProvider = this.ProviderSettings.Id;
this.ChatThread.RuntimeComponent = this.Component;
this.ChatThread.SelectedToolIds = [..this.SelectedToolIds];
this.ChatThread.RuntimeSelectedToolIds = this.GetRunnableToolIds();
this.ChatThread.RuntimeToolsAreAssistantManaged = this.AssistantManagedToolIds is not null;
}
this.IsProcessing = true;
@@ -911,6 +982,7 @@ public abstract partial class AssistantBase<TSettings> : AssistantLowerBase wher
state.Set(RESULTING_CONTENT_BLOCK_STATE_KEY, this.ResultingContentBlock);
state.Set(INPUT_ISSUES_STATE_KEY, this.InputIssues);
state.Set(IS_PROCESSING_STATE_KEY, this.IsProcessing);
state.Set(SELECTED_TOOL_IDS_STATE_KEY, this.SelectedToolIds);
this.CaptureCustomAssistantSessionState(state);
return state.ToDictionary();
@@ -938,6 +1010,7 @@ public abstract partial class AssistantBase<TSettings> : AssistantLowerBase wher
reader.Restore(RESULTING_CONTENT_BLOCK_STATE_KEY, value => this.ResultingContentBlock = value);
reader.Restore(INPUT_ISSUES_STATE_KEY, value => this.InputIssues = value);
reader.Restore(IS_PROCESSING_STATE_KEY, value => this.IsProcessing = value);
reader.Restore(SELECTED_TOOL_IDS_STATE_KEY, value => this.SelectedToolIds = ToolSelectionRules.NormalizeSelection(value));
this.RestoreCustomAssistantSessionState(reader);
}
@@ -948,4 +1021,4 @@ public abstract partial class AssistantBase<TSettings> : AssistantLowerBase wher
protected virtual void RestoreCustomAssistantSessionState(AssistantSessionStateReader state) { }
#endregion
}
}
@@ -22,6 +22,7 @@ public abstract class AssistantLowerBase : MSGComponentBase
protected static readonly AssistantSessionStateKey<ContentBlock?> RESULTING_CONTENT_BLOCK_STATE_KEY = new(nameof(ResultingContentBlock));
protected static readonly AssistantSessionStateKey<string[]> INPUT_ISSUES_STATE_KEY = new(nameof(InputIssues));
protected static readonly AssistantSessionStateKey<bool> IS_PROCESSING_STATE_KEY = new(nameof(IsProcessing));
protected static readonly AssistantSessionStateKey<HashSet<string>> SELECTED_TOOL_IDS_STATE_KEY = new("SelectedToolIds");
protected AIStudio.Settings.Provider ProviderSettings = Settings.Provider.NONE;
protected bool InputIsValid;
@@ -47,6 +47,8 @@
<ReadFileContent Text="@T("Load prompt from file")" @bind-FileContent="@this.freePrompt" EnableDragDrop="true" Layer="@DropLayers.ASSISTANTS" CatchAllDocuments="true" Disabled="@this.isProcessingBatch"/>
<MudTextField T="string" @bind-Text="@this.freePrompt" Validation="@this.ValidateFreePrompt" Immediate="@true" Disabled="@this.isProcessingBatch" Label="@T("What should the AI do with each document?")" HelperText="@T("These instructions are applied to every single document of the batch run.")" Variant="Variant.Outlined" Margin="Margin.Normal" Lines="5" AutoGrow="@true" MaxLines="26" Class="mb-3" UserAttributes="@USER_INPUT_ATTRIBUTES"/>
<ToolSelectionField Component="@this.Component" SelectedToolIds="@this.SelectedToolIds" SelectedToolIdsChanged="@this.SelectedToolIdsChanged" Disabled="@this.isProcessingBatch" Label="@T("Tools for this batch run")" Help="@T("The AI may use these tools while working on each document. Every tool has to meet the confidence requirements of the selected provider, so a tool may stay unavailable even when it is selected here.")"/>
}
else if (this.promptSource is BatchProcessingPromptSource.FILE_IMPORT)
{
@@ -65,6 +67,8 @@ else if (this.promptSource is BatchProcessingPromptSource.FILE_IMPORT)
<MudJustifiedText Typo="Typo.body2" Class="mb-3">
@T("The content of the selected file is used as the instructions for every single document of the batch run.")
</MudJustifiedText>
<ToolSelectionField Component="@this.Component" SelectedToolIds="@this.SelectedToolIds" SelectedToolIdsChanged="@this.SelectedToolIdsChanged" Disabled="@this.isProcessingBatch" Label="@T("Tools for this batch run")" Help="@T("The AI may use these tools while working on each document. Every tool has to meet the confidence requirements of the selected provider, so a tool may stay unavailable even when it is selected here.")"/>
}
else
{
@@ -99,6 +103,12 @@ else
@this.selectedPolicy.PolicyDescription
</MudJustifiedText>
}
@* Read-only: the policy decides its tools, and this run follows the policy. *@
@if (this.selectedPolicy is not null)
{
<ToolSelectionField Component="@this.Component" SelectedToolIds="@this.PolicyToolIds" ReadOnly="@true" Label="@T("Tools of this policy")" Help="@T("These tools are part of the selected policy and cannot be changed here. Every tool has to meet the confidence requirements of the selected provider, so a tool may stay unavailable even when the policy permits it.")"/>
}
}
}
@@ -185,6 +195,15 @@ else
</MudAlert>
}
@*
Only for a policy run: where the user picks the tools themselves, the selection field already
shows what is locked, and they can simply switch a blocked tool off.
*@
@if (this.promptSource is BatchProcessingPromptSource.POLICY)
{
<ManagedToolsWarning Component="@this.Component" ToolIds="@this.PolicyToolIds" ProviderSettings="@this.ProviderSettings"/>
}
<ProviderSelection @bind-ProviderSettings="@this.ProviderSettings" ValidateProvider="@this.ValidatingProviderWithBatchState" Disabled="@this.isProcessingBatch" ExplicitMinimumConfidence="@this.GetMinimumConfidenceLevel()"/>
@if (this.fileResults.Count > 0)
@@ -106,9 +106,9 @@ public partial class AssistantBatchProcessing
private async Task WriteLogAsync(string resolvedOutputDirectory)
{
var sb = new StringBuilder();
sb.AppendLine(CsvWriter.ToRow(LOG_SEPARATOR, T("File"), T("Time"), T("Model"), T("Status"), T("Details")));
sb.AppendLine(CsvWriter.ToRow(LOG_SEPARATOR, T("File"), T("Time"), T("Model"), T("Status"), T("Details"), T("Tools used")));
foreach (var fileResult in this.fileResults.Where(x => x.Status is not BatchProcessingFileStatus.QUEUED and not BatchProcessingFileStatus.PROCESSING))
sb.AppendLine(CsvWriter.ToRow(LOG_SEPARATOR, fileResult.RelativePath, fileResult.ProcessedAt.ToString(TIME_FORMAT, CultureInfo.InvariantCulture), fileResult.ModelName, fileResult.Status.ToString(), fileResult.Message));
sb.AppendLine(CsvWriter.ToRow(LOG_SEPARATOR, fileResult.RelativePath, fileResult.ProcessedAt.ToString(TIME_FORMAT, CultureInfo.InvariantCulture), fileResult.ModelName, fileResult.Status.ToString(), fileResult.Message, fileResult.UsedTools));
await this.WriteCsvFileAsync(Path.Join(resolvedOutputDirectory, LOG_FILENAME), sb.ToString());
}
@@ -176,7 +176,9 @@ public partial class AssistantBatchProcessing
try
{
var content = await File.ReadAllTextAsync(logFilePath);
var rows = BatchProcessingCsv.ParseWithDetectedSeparator(content, 5, LOG_SEPARATOR, '|');
// A log written before the tools column existed has five fields. It stays
// readable, so that a run started with an earlier version can be continued:
var rows = BatchProcessingCsv.ParseWithDetectedSeparator(content, [6, 5], LOG_SEPARATOR, '|');
// The first row is the header, which we skip:
foreach (var row in rows.Skip(1))
@@ -184,7 +186,7 @@ public partial class AssistantBatchProcessing
if (row.Count < 5 || string.IsNullOrWhiteSpace(row[0]))
continue;
entries[row[0]] = new BatchProcessingLogEntry(row[0], row[1], row[2], row[3], row[4]);
entries[row[0]] = new BatchProcessingLogEntry(row[0], row[1], row[2], row[3], row[4], row.Count > 5 ? row[5] : string.Empty);
}
}
catch (Exception e)
@@ -213,7 +215,7 @@ public partial class AssistantBatchProcessing
var content = await File.ReadAllTextAsync(resultsFilePath);
var configuredSeparator = this.csvSeparator.Character(this.customCsvSeparator);
var rows = BatchProcessingCsv.ParseWithDetectedSeparator(content, 2, configuredSeparator, ';', '|', ',', '\t');
var rows = BatchProcessingCsv.ParseWithDetectedSeparator(content, [2], configuredSeparator, ';', '|', ',', '\t');
foreach (var row in rows.Skip(1))
{
if (row.Count < 2 || string.IsNullOrWhiteSpace(row[0]))
@@ -1,6 +1,7 @@
using AIStudio.Chat;
using AIStudio.Provider;
using AIStudio.Settings;
using AIStudio.Tools.ToolCallingSystem;
namespace AIStudio.Assistants.BatchProcessing;
@@ -86,18 +87,34 @@ public partial class AssistantBatchProcessing
""";
}
private async Task<string> CallAIAsync(string fileName, string fileContent, CancellationToken token)
/// <param name="fileName">The name of the document being processed.</param>
/// <param name="fileContent">The content handed to the model.</param>
/// <param name="token">The cancellation token.</param>
/// <returns>The answer of the model, and which tools it used to get there.</returns>
private async Task<(string Answer, string UsedTools)> CallAIAsync(string fileName, string fileContent, CancellationToken token)
{
//
// Every file of the batch gets the tools the user picked for the job. The batch builds its
// own throwaway thread per file instead of going through the assistant's own thread, so it
// has to hand the tools over itself.
//
var chatThread = new ChatThread
{
IncludeDateTime = false,
SelectedProvider = this.ProviderSettings.Id,
SelectedProfile = Profile.NO_PROFILE.Id,
SelectedToolIds = [..this.SelectedToolIds],
SystemPrompt = this.SystemPrompt,
WorkspaceId = Guid.Empty,
ChatId = Guid.NewGuid(),
Name = this.Title,
Blocks = [],
RuntimeComponent = this.Component,
RuntimeSelectedToolIds = this.GetRunnableToolIds(),
// Always true here, unlike in the assistant base: a batch run takes its tools from the
// selected policy or from its own field, never from the tool selection in the footer.
RuntimeToolsAreAssistantManaged = true,
};
var userPrompt = new ContentText
@@ -123,6 +140,32 @@ public partial class AssistantBatchProcessing
});
await aiText.CreateFromProviderAsync(this.ProviderSettings.CreateProvider(), this.ProviderSettings.Model, userPrompt, chatThread, token);
return aiText.Text.RemoveThinkTags().Trim();
return (aiText.Text.RemoveThinkTags().Trim(), this.SummarizeToolUsage(aiText));
}
/// <summary>
/// Sums up the tool calls of one document for the log.
/// </summary>
/// <remarks>
/// Names each tool once with how often it ran, because a model may search
/// several times for the same document. A call that failed or was blocked
/// is named with its outcome: for judging an answer it matters whether a
/// tool delivered or came back empty-handed.
/// </remarks>
private string SummarizeToolUsage(ContentText aiText) => string.Join(", ", aiText.ToolInvocations
.GroupBy(invocation => (invocation.ToolName, invocation.Status))
.OrderBy(group => group.Key.ToolName, StringComparer.OrdinalIgnoreCase)
.Select(group => this.FormatToolUsage(group.Key.ToolName, group.Key.Status, group.Count())));
private string FormatToolUsage(string toolName, ToolInvocationTraceStatus status, int count)
{
var nameWithCount = count > 1 ? $"{toolName} ({count}x)" : toolName;
return status switch
{
ToolInvocationTraceStatus.ERROR => $"{nameWithCount} [{this.T("failed")}]",
ToolInvocationTraceStatus.BLOCKED => $"{nameWithCount} [{this.T("blocked")}]",
_ => nameWithCount,
};
}
}
@@ -70,6 +70,7 @@ public partial class AssistantBatchProcessing
fileResult.Status = BatchProcessingFileStatus.DONE;
fileResult.Message = logEntry.Details;
fileResult.ModelName = logEntry.Model;
fileResult.UsedTools = logEntry.UsedTools;
fileResult.ResultText = previousResults.GetValueOrDefault(relativePath, string.Empty);
if (DateTimeOffset.TryParseExact(logEntry.Time, TIME_FORMAT, CultureInfo.InvariantCulture, DateTimeStyles.AssumeLocal, out var processedAt))
@@ -194,7 +195,7 @@ public partial class AssistantBatchProcessing
string aiAnswer;
try
{
aiAnswer = await this.CallAIAsync(fileResult.FileName, fileContent, token);
(aiAnswer, fileResult.UsedTools) = await this.CallAIAsync(fileResult.FileName, fileContent, token);
}
catch (OperationCanceledException)
{
@@ -31,6 +31,24 @@ public partial class AssistantBatchProcessing : AssistantBaseCore<SettingsDialog
protected override Tools.Components Component => Tools.Components.BATCH_PROCESSING_ASSISTANT;
/// <summary>
/// The tools a run uses, taken from wherever the instructions come from.
/// </summary>
/// <remarks>
/// Never from the footer: the tools belong to the instructions, and that is where they are
/// chosen. Working from a document analysis policy means following it, tools included, so
/// there is nothing left to pick. With instructions of one's own, the field next to them
/// decides.
/// </remarks>
protected override IReadOnlySet<string> AssistantManagedToolIds => this.promptSource is BatchProcessingPromptSource.POLICY
? this.PolicyToolIds
: this.SelectedToolIds;
/// <summary>
/// The tools of the selected policy, or none while no policy is selected.
/// </summary>
private HashSet<string> PolicyToolIds => this.selectedPolicy is null ? [] : [..this.selectedPolicy.AllowedToolIds];
protected override string Title => T("Batch Processing Assistant");
protected override string Description => T("Process all documents and media files of a folder in one batch run: documents are converted to Markdown, while audio and video files are transcribed automatically, before their content is sent to the AI along with your instructions. You choose whether each answer is stored as its own Markdown file or whether all answers are collected in one CSV results table. A log records what happened to every file, so a run which was interrupted or produced errors can be continued later. A single failing file never stops the entire run.");
@@ -99,7 +99,12 @@ public static class BatchProcessingCsv
/// content with it. Preferred separators are used as fallbacks for files
/// whose first record does not reveal a valid separator.
/// </summary>
public static List<List<string>> ParseWithDetectedSeparator(string content, int expectedNumFields, params char[] preferredSeparators)
/// <remarks>
/// Several accepted field counts allow a file written by an earlier version
/// to be read as well. The log gained a column, and a run started with the
/// previous version must still be continuable.
/// </remarks>
public static List<List<string>> ParseWithDetectedSeparator(string content, IReadOnlyList<int> acceptedNumFields, params char[] preferredSeparators)
{
var firstRecord = ReadFirstRecord(content);
var candidates = new List<char>();
@@ -135,7 +140,7 @@ public static class BatchProcessingCsv
foreach (var separator in candidates)
{
var header = Parse(firstRecord, separator);
if (header.Count is 1 && header[0].Count == expectedNumFields)
if (header.Count is 1 && acceptedNumFields.Contains(header[0].Count))
return Parse(content, separator);
}
@@ -56,4 +56,14 @@ public sealed class BatchProcessingFileResult
/// The time when the processing of this file finished.
/// </summary>
public DateTimeOffset ProcessedAt { get; set; }
/// <summary>
/// The tools the model used for this file, ready to be read in the log.
/// </summary>
/// <remarks>
/// Recorded per file, because the model decides per document whether it
/// needs a tool at all. Without this, a batch run gives no clue why one
/// answer is better informed than the next.
/// </remarks>
public string UsedTools { get; set; } = string.Empty;
}
@@ -3,7 +3,11 @@ namespace AIStudio.Assistants.BatchProcessing;
/// <summary>
/// One row of the log of a previous batch run.
/// </summary>
public sealed record BatchProcessingLogEntry(string RelativePath, string Time, string Model, string Status, string Details)
/// <remarks>
/// The tools column arrived later than the rest. A log written before it existed
/// leaves it empty, which is also what a run without any tool call looks like.
/// </remarks>
public sealed record BatchProcessingLogEntry(string RelativePath, string Time, string Model, string Status, string Details, string UsedTools = "")
{
public bool WasSuccessful => string.Equals(this.Status, nameof(BatchProcessingFileStatus.DONE), StringComparison.OrdinalIgnoreCase);
}
@@ -39,6 +39,7 @@
@bind-ProfileId="@this.launcherProfileId"
@bind-ChatTemplateId="@this.launcherChatTemplateId"
@bind-DataSourceIds="@this.launcherDataSourceIds"
@bind-ToolIds="@this.launcherToolIds"
ValidateWorkspaceName="@this.ValidateLauncherWorkspaceName"/>
</MudPaper>
}
@@ -6,6 +6,7 @@ using AIStudio.Tools.PluginSystem;
using AIStudio.Tools.PluginSystem.Assistants;
using AIStudio.Tools.PluginSystem.Assistants.DataModel;
using AIStudio.Tools.Services;
using AIStudio.Tools.ToolCallingSystem;
using Microsoft.AspNetCore.Components;
using DialogOptions = AIStudio.Dialogs.DialogOptions;
@@ -101,6 +102,7 @@ public partial class AssistantBuilder : AssistantBaseCore<NoSettingsPanel>
private string launcherProfileId = string.Empty;
private string launcherChatTemplateId = string.Empty;
private IEnumerable<string> launcherDataSourceIds = [];
private HashSet<string> launcherToolIds = [];
private IEnumerable<AssistantComponentType> selectedAssistantComponents = [];
private CommonLanguages selectedOutputLanguage = CommonLanguages.AS_IS;
private string customOutputLanguage = string.Empty;
@@ -138,6 +140,7 @@ public partial class AssistantBuilder : AssistantBaseCore<NoSettingsPanel>
private static readonly AssistantSessionStateKey<string> LAUNCHER_PROFILE_ID_STATE_KEY = new(nameof(launcherProfileId));
private static readonly AssistantSessionStateKey<string> LAUNCHER_CHAT_TEMPLATE_ID_STATE_KEY = new(nameof(launcherChatTemplateId));
private static readonly AssistantSessionStateKey<List<string>> LAUNCHER_DATA_SOURCE_IDS_STATE_KEY = new(nameof(launcherDataSourceIds));
private static readonly AssistantSessionStateKey<HashSet<string>> LAUNCHER_TOOL_IDS_STATE_KEY = new(nameof(launcherToolIds));
private static readonly AssistantSessionStateKey<List<AssistantComponentType>> SELECTED_ASSISTANT_COMPONENTS_STATE_KEY = new(nameof(selectedAssistantComponents));
private static readonly AssistantSessionStateKey<CommonLanguages> SELECTED_OUTPUT_LANGUAGE_STATE_KEY = new(nameof(selectedOutputLanguage));
private static readonly AssistantSessionStateKey<string> CUSTOM_OUTPUT_LANGUAGE_STATE_KEY = new(nameof(customOutputLanguage));
@@ -243,6 +246,7 @@ public partial class AssistantBuilder : AssistantBaseCore<NoSettingsPanel>
this.launcherProfileId = string.Empty;
this.launcherChatTemplateId = string.Empty;
this.launcherDataSourceIds = [];
this.launcherToolIds = [];
this.selectedAssistantComponents = [];
this.selectedOutputLanguage = CommonLanguages.AS_IS;
this.customOutputLanguage = string.Empty;
@@ -279,6 +283,7 @@ public partial class AssistantBuilder : AssistantBaseCore<NoSettingsPanel>
state.Set(LAUNCHER_PROFILE_ID_STATE_KEY, this.launcherProfileId);
state.Set(LAUNCHER_CHAT_TEMPLATE_ID_STATE_KEY, this.launcherChatTemplateId);
state.SetList(LAUNCHER_DATA_SOURCE_IDS_STATE_KEY, this.launcherDataSourceIds);
state.SetHashSet(LAUNCHER_TOOL_IDS_STATE_KEY, this.launcherToolIds);
state.SetList(SELECTED_ASSISTANT_COMPONENTS_STATE_KEY, this.selectedAssistantComponents);
state.Set(SELECTED_OUTPUT_LANGUAGE_STATE_KEY, this.selectedOutputLanguage);
state.Set(CUSTOM_OUTPUT_LANGUAGE_STATE_KEY, this.customOutputLanguage);
@@ -320,6 +325,7 @@ public partial class AssistantBuilder : AssistantBaseCore<NoSettingsPanel>
state.Restore(LAUNCHER_PROFILE_ID_STATE_KEY, value => this.launcherProfileId = value);
state.Restore(LAUNCHER_CHAT_TEMPLATE_ID_STATE_KEY, value => this.launcherChatTemplateId = value);
state.Restore(LAUNCHER_DATA_SOURCE_IDS_STATE_KEY, value => this.launcherDataSourceIds = value);
state.Restore(LAUNCHER_TOOL_IDS_STATE_KEY, value => this.launcherToolIds = ToolSelectionRules.NormalizeSelection(value));
state.Restore(SELECTED_ASSISTANT_COMPONENTS_STATE_KEY, value => this.selectedAssistantComponents = value);
state.Restore(SELECTED_OUTPUT_LANGUAGE_STATE_KEY, value => this.selectedOutputLanguage = value);
state.Restore(CUSTOM_OUTPUT_LANGUAGE_STATE_KEY, value => this.customOutputLanguage = value);
@@ -543,12 +549,14 @@ public partial class AssistantBuilder : AssistantBaseCore<NoSettingsPanel>
return null;
var dataSourceIds = this.launcherDataSourceIds.Distinct(StringComparer.OrdinalIgnoreCase).ToArray();
var toolIds = ToolSelectionRules.NormalizeSelection(this.launcherToolIds).ToArray();
return new(
this.launcherWorkspaceName.Trim(),
NullIfEmpty(this.launcherProviderId),
NullIfEmpty(this.launcherProfileId),
NullIfEmpty(this.launcherChatTemplateId),
dataSourceIds.Length == 0 ? null : dataSourceIds);
dataSourceIds.Length == 0 ? null : dataSourceIds,
toolIds.Length == 0 ? null : toolIds);
}
private void CreateChatLauncherChanged(bool createLauncher)
@@ -8,5 +8,6 @@ internal sealed class AssistantBuilderAssistantMetadata
public string? SystemPrompt { get; init; }
public string? SubmitText { get; init; }
public bool? AllowAiStudioProfiles { get; init; }
public string[]? ToolIds { get; init; }
public AssistantBuilderChatLaunchMetadata? Launch { get; init; }
}
@@ -7,4 +7,5 @@ internal sealed class AssistantBuilderChatLaunchMetadata
public string? ProfileId { get; init; }
public string? ChatTemplateId { get; init; }
public string[]? DataSourceIds { get; init; }
public string[]? ToolIds { get; init; }
}
@@ -90,6 +90,15 @@
},
"allow_ai_studio_profiles": {
"type": "boolean"
},
"tool_ids": {
"type": "array",
"minItems": 1,
"uniqueItems": true,
"items": {
"type": "string",
"minLength": 1
}
}
}
},
@@ -156,6 +165,15 @@
"const": "00000000-0000-0000-0000-000000000000"
}
}
},
"tool_ids": {
"type": "array",
"minItems": 1,
"uniqueItems": true,
"items": {
"type": "string",
"minLength": 1
}
}
}
}
@@ -0,0 +1,90 @@
using System.Text.Json;
namespace AIStudio.Assistants.Builder;
/// <summary>
/// The three texts a model writes for a direct chat launcher.
/// </summary>
/// <remarks>
/// A launcher has no system prompt, no form, and no prompt builder, and its chat settings come
/// straight from the Builder form. That leaves nothing for a model to write except the names a
/// person reads, so it is asked for those alone and AI Studio writes the plugin.lua itself.
/// </remarks>
internal sealed class LauncherTextsResponse
{
public const string SCHEMA_VERSION_VALUE = "assistant_builder_launcher_texts_v1";
private static readonly JsonSerializerOptions JSON_OPTIONS = new()
{
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,
AllowTrailingCommas = false,
ReadCommentHandling = JsonCommentHandling.Disallow,
MaxDepth = 8,
};
public string SchemaVersion { get; init; } = string.Empty;
/// <summary>
/// The plugin name, shown on the plugins page.
/// </summary>
public string PluginName { get; init; } = string.Empty;
/// <summary>
/// The title on the tile.
/// </summary>
public string Title { get; init; } = string.Empty;
/// <summary>
/// The short description, used for both the plugin and the tile.
/// </summary>
public string Description { get; init; } = string.Empty;
public static bool TryParse(string modelResponse, out LauncherTextsResponse response, out LuaResponseParseError error, out string technicalDetails)
{
response = new();
error = LuaResponseParseError.NONE;
technicalDetails = string.Empty;
var json = LuaResponse.ExtractJson(modelResponse);
if (string.IsNullOrWhiteSpace(json))
{
error = LuaResponseParseError.MISSING_JSON_OBJECT;
return false;
}
LauncherTextsResponse? parsed;
try
{
parsed = JsonSerializer.Deserialize<LauncherTextsResponse>(json, JSON_OPTIONS);
}
catch (JsonException e)
{
error = LuaResponseParseError.INVALID_JSON;
technicalDetails = e.Message;
return false;
}
if (parsed is null)
{
error = LuaResponseParseError.EMPTY_JSON_OBJECT;
return false;
}
if (!string.Equals(parsed.SchemaVersion, SCHEMA_VERSION_VALUE, StringComparison.Ordinal))
{
error = LuaResponseParseError.UNSUPPORTED_SCHEMA_VERSION;
return false;
}
if (string.IsNullOrWhiteSpace(parsed.PluginName) ||
string.IsNullOrWhiteSpace(parsed.Title) ||
string.IsNullOrWhiteSpace(parsed.Description))
{
error = LuaResponseParseError.INCOMPLETE_ASSISTANT_METADATA;
return false;
}
response = parsed;
return true;
}
}
@@ -109,10 +109,15 @@ internal sealed partial class LuaResponse
"FORM" => !string.IsNullOrWhiteSpace(assistant.SystemPrompt) &&
!string.IsNullOrWhiteSpace(assistant.SubmitText) &&
assistant.AllowAiStudioProfiles.HasValue &&
IsValidToolIds(assistant.ToolIds) &&
assistant.Launch is null,
// A launcher names its tools inside launch, so the same field one level up would be a
// second, competing selection:
"CHAT_LAUNCHER" => assistant.SystemPrompt is null &&
assistant.SubmitText is null &&
assistant.AllowAiStudioProfiles is null &&
assistant.ToolIds is null &&
IsValidChatLaunchMetadata(assistant.Launch),
_ => false,
};
@@ -127,16 +132,36 @@ internal sealed partial class LuaResponse
!IsOptionalGuid(launch.ChatTemplateId, allowEmpty: true))
return false;
return launch.DataSourceIds is null ||
launch.DataSourceIds.Length > 0 &&
launch.DataSourceIds.All(id => Guid.TryParse(id, out var parsed) && parsed != Guid.Empty) &&
launch.DataSourceIds.Distinct(StringComparer.OrdinalIgnoreCase).Count() == launch.DataSourceIds.Length;
if (launch.DataSourceIds is not null &&
(launch.DataSourceIds.Length == 0 ||
!launch.DataSourceIds.All(id => Guid.TryParse(id, out var parsed) && parsed != Guid.Empty) ||
launch.DataSourceIds.Distinct(StringComparer.OrdinalIgnoreCase).Count() != launch.DataSourceIds.Length))
return false;
return IsValidToolIds(launch.ToolIds);
}
/// <remarks>
/// Tool IDs are plain names, so only their shape can be checked here. Whether the named tools
/// exist is decided later, against the tools this AI Studio actually has.
/// </remarks>
private static bool IsValidToolIds(string[]? toolIds) =>
toolIds is null ||
toolIds.Length > 0 &&
toolIds.All(id => !string.IsNullOrWhiteSpace(id)) &&
toolIds.Distinct(StringComparer.Ordinal).Count() == toolIds.Length;
private static bool IsOptionalGuid(string? value, bool allowEmpty) => value is null ||
Guid.TryParse(value, out var parsed) && (allowEmpty || parsed != Guid.Empty);
private static string ExtractJson(string input)
/// <summary>
/// Reads the first complete JSON object out of a model answer that may carry text around it.
/// </summary>
/// <remarks>
/// Shared with the launcher texts response, which is a different shape but arrives the same
/// way, wrapped in whatever prose the model felt like adding.
/// </remarks>
internal static string ExtractJson(string input)
{
var start = input.IndexOf('{');
if (start < 0)
@@ -13,26 +13,4 @@ public enum LuaResponseParseError
INCOMPLETE_ASSISTANT_METADATA,
MISSING_LUA,
LUA_MISSING_ID,
}
public static class LuaResponseParseErrorExtension
{
private static string TB(string fallbackEN) => Tools.PluginSystem.I18N.I.T(fallbackEN, typeof(LuaResponseParseErrorExtension).Namespace, nameof(LuaResponseParseErrorExtension));
public static string GetMessage(this LuaResponseParseError parseError, string technicalDetails) => parseError switch
{
LuaResponseParseError.MISSING_JSON_OBJECT => TB("The model response is missing or unreadable."),
LuaResponseParseError.INVALID_JSON => string.IsNullOrWhiteSpace(technicalDetails)
? TB("The model returned an invalid response.")
: string.Format(TB("The model returned an invalid response: {0}"), technicalDetails),
LuaResponseParseError.EMPTY_JSON_OBJECT => TB("The model returned an empty JSON object."),
LuaResponseParseError.UNSUPPORTED_SCHEMA_VERSION => TB("The model responded with an unsupported or deprecated JSON schema."),
LuaResponseParseError.MISSING_PLUGIN_METADATA => TB("The model's answer is missing the plugin metadata."),
LuaResponseParseError.MISSING_ASSISTANT_METADATA => TB("The model's answer is missing the assistant metadata."),
LuaResponseParseError.INCOMPLETE_PLUGIN_METADATA => TB("The model's answer contains incomplete plugin metadata."),
LuaResponseParseError.INCOMPLETE_ASSISTANT_METADATA => TB("The model's answer contains incomplete assistant metadata."),
LuaResponseParseError.MISSING_LUA => TB("The model response does not contain the generated Lua plugin code."),
LuaResponseParseError.LUA_MISSING_ID => TB("The generated Lua plugin code does not contain a readable plugin ID."),
_ => TB("The model returned an unusable JSON response."),
};
}
}
@@ -0,0 +1,23 @@
namespace AIStudio.Assistants.Builder;
public static class LuaResponseParseErrorExtension
{
private static string TB(string fallbackEN) => Tools.PluginSystem.I18N.I.T(fallbackEN, typeof(LuaResponseParseError).Namespace, nameof(LuaResponseParseError));
public static string GetMessage(this LuaResponseParseError parseError, string technicalDetails) => parseError switch
{
LuaResponseParseError.MISSING_JSON_OBJECT => TB("The model response is missing or unreadable."),
LuaResponseParseError.INVALID_JSON => string.IsNullOrWhiteSpace(technicalDetails)
? TB("The model returned an invalid response.")
: string.Format(TB("The model returned an invalid response: {0}"), technicalDetails),
LuaResponseParseError.EMPTY_JSON_OBJECT => TB("The model returned an empty JSON object."),
LuaResponseParseError.UNSUPPORTED_SCHEMA_VERSION => TB("The model responded with an unsupported or deprecated JSON schema."),
LuaResponseParseError.MISSING_PLUGIN_METADATA => TB("The model's answer is missing the plugin metadata."),
LuaResponseParseError.MISSING_ASSISTANT_METADATA => TB("The model's answer is missing the assistant metadata."),
LuaResponseParseError.INCOMPLETE_PLUGIN_METADATA => TB("The model's answer contains incomplete plugin metadata."),
LuaResponseParseError.INCOMPLETE_ASSISTANT_METADATA => TB("The model's answer contains incomplete assistant metadata."),
LuaResponseParseError.MISSING_LUA => TB("The model response does not contain the generated Lua plugin code."),
LuaResponseParseError.LUA_MISSING_ID => TB("The generated Lua plugin code does not contain a readable plugin ID."),
_ => TB("The model returned an unusable JSON response."),
};
}
@@ -106,7 +106,9 @@ else
<ConfigurationMinConfidenceSelection Disabled="@(() => this.IsNoPolicySelectedOrProtected)" RestrictToGlobalMinimumConfidence="true" SelectedValue="@(() => this.policyMinimumProviderConfidence)" SelectionUpdateAsync="@(async level => await this.PolicyMinimumConfidenceWasChangedAsync(level))" />
<ConfigurationProviderSelection Component="Components.DOCUMENT_ANALYSIS_ASSISTANT" Data="@this.availableLLMProviders" Disabled="@(() => this.IsNoPolicySelectedOrProtected)" SelectedValue="@(() => this.policyPreselectedProviderId)" SelectionUpdate="@(providerId => this.PolicyPreselectedProviderWasChanged(providerId))" ExplicitMinimumConfidence="@this.GetPolicyMinimumConfidenceLevel()"/>
<ToolSelectionField Component="@this.Component" SelectedToolIds="@this.policyAllowedToolIds" SelectedToolIdsChanged="@this.PolicyAllowedToolsWasChangedAsync" Disabled="@this.IsNoPolicySelectedOrProtected" Label="@T("Tools this policy permits")" Help="@T("Only the tools selected here can be used by the AI for an analysis with this policy. Every tool still has to meet the confidence requirements of the selected provider, so a tool may remain unavailable even when this policy permits it.")"/>
<ConfigurationProviderSelection Component="Components.DOCUMENT_ANALYSIS_ASSISTANT" Data="@this.availableLLMProviders" Disabled="@(() => this.IsNoPolicySelectedOrProtected)" SelectedValue="@(() => this.policyPreselectedProviderId)" SelectionUpdate="@this.PolicyPreselectedProviderWasChanged" ExplicitMinimumConfidence="@this.GetPolicyMinimumConfidenceLevel()"/>
<ConfigurationSelect OptionDescription="@T("Preselect a profile")" Disabled="@(() => this.IsNoPolicySelected)" SelectedValue="@(() => this.policyPreselectedProfile)" Data="@ConfigurationSelectDataFactory.GetComponentProfilesData(this.SettingsManager.ConfigurationData.Profiles)" SelectionUpdateAsync="@(async selection => await this.PolicyPreselectedProfileWasChangedAsync(selection))" OptionHelp="@T("Choose whether the policy should use the app default profile, no profile, or a specific profile.")"/>
@@ -170,4 +172,7 @@ else
</MudExpansionPanels>
}
@* The warning sits right at the provider selection, because choosing another provider resolves it: *@
<ManagedToolsWarning Component="@this.Component" ToolIds="@this.policyAllowedToolIds" ProviderSettings="@this.ProviderSettings"/>
<ProviderSelection @bind-ProviderSettings="@this.ProviderSettings" ValidateProvider="@this.ValidatingProvider" ExplicitMinimumConfidence="@this.GetPolicyMinimumConfidenceLevel()"/>
@@ -23,7 +23,18 @@ public partial class DocumentAnalysisAssistant : AssistantBaseCore<NoSettingsPan
private IDialogService DialogService { get; init; } = null!;
protected override Tools.Components Component => Tools.Components.DOCUMENT_ANALYSIS_ASSISTANT;
/// <summary>
/// The policy decides which tools its analysis uses; the user does not pick them.
/// </summary>
/// <remarks>
/// Two ways of working, one answer: someone writing a policy for themselves settles the tools
/// while writing it, and a policy rolled out by an organization arrives ready to use, with the
/// tools its authors tested it with. Either way there is nothing left for the user to switch,
/// which is why the tool selection does not appear in this assistant.
/// </remarks>
protected override IReadOnlySet<string> AssistantManagedToolIds => this.policyAllowedToolIds;
protected override string Title => T("Document Analysis Assistant");
protected override string Description => T("The document analysis assistant helps you to analyze and extract information from documents based on predefined policies. You can create, edit, and manage document analysis policies that define how documents should be processed and what information should be extracted. Some policies might be protected by your organization and cannot be modified or deleted.");
@@ -178,6 +189,7 @@ public partial class DocumentAnalysisAssistant : AssistantBaseCore<NoSettingsPan
this.policyAnalysisRules = string.Empty;
this.policyOutputRules = string.Empty;
this.policyMinimumProviderConfidence = ConfidenceLevel.NONE;
this.policyAllowedToolIds = [];
this.policyPreselectedProviderId = string.Empty;
this.policyPreselectedProfile = ProfilePreselection.NoProfile;
}
@@ -205,6 +217,7 @@ public partial class DocumentAnalysisAssistant : AssistantBaseCore<NoSettingsPan
this.policyAnalysisRules = this.selectedPolicy.AnalysisRules;
this.policyOutputRules = this.selectedPolicy.OutputRules;
this.policyMinimumProviderConfidence = this.selectedPolicy.MinimumProviderConfidence;
this.policyAllowedToolIds = [..this.selectedPolicy.AllowedToolIds];
this.policyPreselectedProviderId = this.selectedPolicy.PreselectedProvider;
this.policyPreselectedProfile = ProfilePreselection.FromStoredValue(this.selectedPolicy.PreselectedProfile);
@@ -262,6 +275,7 @@ public partial class DocumentAnalysisAssistant : AssistantBaseCore<NoSettingsPan
this.selectedPolicy.AnalysisRules = this.policyAnalysisRules;
this.selectedPolicy.OutputRules = this.policyOutputRules;
this.selectedPolicy.MinimumProviderConfidence = this.policyMinimumProviderConfidence;
this.selectedPolicy.AllowedToolIds = [..this.policyAllowedToolIds];
}
await this.SettingsManager.StoreSettings();
@@ -276,6 +290,7 @@ public partial class DocumentAnalysisAssistant : AssistantBaseCore<NoSettingsPan
private string policyAnalysisRules = string.Empty;
private string policyOutputRules = string.Empty;
private ConfidenceLevel policyMinimumProviderConfidence = ConfidenceLevel.NONE;
private HashSet<string> policyAllowedToolIds = [];
private string policyPreselectedProviderId = string.Empty;
private ProfilePreselection policyPreselectedProfile = ProfilePreselection.NoProfile;
private HashSet<FileAttachment> loadedDocumentPaths = [];
@@ -528,6 +543,15 @@ public partial class DocumentAnalysisAssistant : AssistantBaseCore<NoSettingsPan
return this.SettingsManager.GetAppPreselectedProfile();
}
/// <summary>
/// Takes over the tools this policy permits.
/// </summary>
private async Task PolicyAllowedToolsWasChangedAsync(HashSet<string> allowedToolIds)
{
this.policyAllowedToolIds = allowedToolIds;
await this.AutoSave();
}
private async Task PolicyMinimumConfidenceWasChangedAsync(ConfidenceLevel level)
{
this.policyMinimumProviderConfidence = level;
@@ -813,10 +837,26 @@ public partial class DocumentAnalysisAssistant : AssistantBaseCore<NoSettingsPan
}
await this.AutoSave();
await this.Form!.Validate();
if (!this.InputIsValid)
//
// Only what the export actually writes is checked. Validating the whole form would demand
// a selected provider, which the export does not contain: it describes the policy, not the
// way one user happens to run it.
//
var policyIssues = this.GetPolicyExportIssues();
if (policyIssues.Count > 0)
{
await this.MessageBus.SendError(new (Icons.Material.Filled.Policy, this.T("The selected policy contains invalid data. Please fix the issues before exporting the policy.")));
//
// Name the issues in both places. A message saying only that something is invalid
// leaves the user searching a long form, and leaves us without a clue in the log:
//
this.Logger.LogWarning(
"Was not able to export the document analysis policy '{PolicyName}'. It has {IssueCount} validation issue(s): {Issues}",
this.selectedPolicy?.PolicyName,
policyIssues.Count,
string.Join(" | ", policyIssues));
await this.MessageBus.SendError(new (Icons.Material.Filled.Policy, $"{this.T("The selected policy contains invalid data. Please fix the issues before exporting the policy.")} {string.Join(" ", policyIssues)}"));
return;
}
@@ -824,6 +864,27 @@ public partial class DocumentAnalysisAssistant : AssistantBaseCore<NoSettingsPan
await this.RustService.CopyText2Clipboard(luaCode);
}
/// <summary>
/// Checks the fields the export writes, using the same rules the form applies to them.
/// </summary>
private List<string> GetPolicyExportIssues()
{
List<string> issues = [];
foreach (var issue in new[]
{
this.ValidatePolicyName(this.policyName),
this.ValidatePolicyDescription(this.policyDescription),
this.ValidateAnalysisRules(this.policyAnalysisRules),
this.ValidateOutputRules(this.policyOutputRules),
})
{
if (!string.IsNullOrWhiteSpace(issue))
issues.Add(issue);
}
return issues;
}
private string GenerateLuaPolicyExport()
{
if(this.selectedPolicy is null)
@@ -832,6 +893,7 @@ public partial class DocumentAnalysisAssistant : AssistantBaseCore<NoSettingsPan
var preselectedProvider = string.IsNullOrWhiteSpace(this.selectedPolicy.PreselectedProvider) ? string.Empty : this.selectedPolicy.PreselectedProvider;
var preselectedProfile = string.IsNullOrWhiteSpace(this.selectedPolicy.PreselectedProfile) ? string.Empty : this.selectedPolicy.PreselectedProfile;
var id = string.IsNullOrWhiteSpace(this.selectedPolicy.Id) ? Guid.NewGuid().ToString() : this.selectedPolicy.Id;
var allowedToolIds = string.Join(", ", this.selectedPolicy.AllowedToolIds.OrderBy(x => x, StringComparer.Ordinal).Select(x => LuaTools.ToLuaStringLiteral(x)));
return $$"""
CONFIG["DOCUMENT_ANALYSIS_POLICIES"][#CONFIG["DOCUMENT_ANALYSIS_POLICIES"]+1] = {
@@ -847,6 +909,12 @@ public partial class DocumentAnalysisAssistant : AssistantBaseCore<NoSettingsPan
-- Allowed values are: NONE, VERY_LOW, LOW, MODERATE, MEDIUM, HIGH
["MinimumProviderConfidence"] = "{{this.selectedPolicy.MinimumProviderConfidence}}",
-- The tools an analysis with this policy may use, by tool ID.
-- This is a limit, not a preselection: a tool which is not listed here cannot
-- be used for this policy. An empty list means no tools. A listed tool must
-- still meet the confidence requirements of the provider in use.
["AllowedToolIds"] = { {{allowedToolIds}} },
-- Optional: preselect a provider or profile by ID.
-- The IDs must exist in CONFIG["LLM_PROVIDERS"] or CONFIG["PROFILES"].
["PreselectedProvider"] = "{{preselectedProvider}}",
@@ -35,6 +35,19 @@ else
</MudPaper>
}
@*
The plugin names the tools, so there is nothing for the user to switch on or off. What is
left is to say which tools run here, and to warn when the selected provider keeps one of
them out of reach.
*@
@if (this.assistantToolIds is { Count: > 0 } toolIds && this.SettingsManager.AreToolsEnabled())
{
<MudPaper Class="pa-4 ma-4" Elevation="0">
<ManagedToolsWarning Component="@this.Component" ToolIds="@toolIds" ProviderSettings="@this.ProviderSettings"/>
<ToolSelectionField Component="@this.Component" SelectedToolIds="@toolIds" ReadOnly="@true" Label="@T("Tools of this assistant")" Help="@T("The author of this assistant chose these tools, so they cannot be changed here. Every tool has to meet the confidence requirements of the selected provider, so a tool may stay unavailable even when this assistant names it.")"/>
</MudPaper>
}
@foreach (var component in this.RootComponent.Children)
{
@this.RenderComponent(component)
@@ -9,6 +9,7 @@ using AIStudio.Tools.PluginSystem;
using AIStudio.Tools.PluginSystem.Assistants;
using AIStudio.Tools.PluginSystem.Assistants.DataModel;
using AIStudio.Tools.Services;
using AIStudio.Tools.ToolCallingSystem;
using Lua;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.WebUtilities;
@@ -34,6 +35,13 @@ public partial class AssistantDynamic : AssistantBaseCore<NoSettingsPanel>
protected override bool ShowProfileSelection => this.showFooterProfileSelection;
protected override string SubmitText => this.submitText;
protected override Func<Task> SubmitAction => this.Submit;
/// <remarks>
/// A plugin that names its tools has decided for the user: its author wrote and tested the
/// assistant with exactly these. Null keeps the footer selection for every other plugin.
/// </remarks>
protected override IReadOnlySet<string>? AssistantManagedToolIds => this.assistantToolIds;
protected override bool SubmitDisabled => this.isSecurityBlocked;
// Dynamic assistants do not have dedicated settings yet. Their internal identity keeps their
// session and media state separate while ComponentsExtensions derives their defaults from chat.
@@ -50,6 +58,7 @@ public partial class AssistantDynamic : AssistantBaseCore<NoSettingsPanel>
private bool allowProfiles = true;
private string submitText = string.Empty;
private bool showFooterProfileSelection = true;
private HashSet<string>? assistantToolIds;
private PluginAssistants? assistantPlugin;
private readonly AssistantState assistantState = new();
@@ -69,6 +78,7 @@ public partial class AssistantDynamic : AssistantBaseCore<NoSettingsPanel>
private static readonly AssistantSessionStateKey<bool> ALLOW_PROFILES_STATE_KEY = new(nameof(allowProfiles));
private static readonly AssistantSessionStateKey<string> SUBMIT_TEXT_STATE_KEY = new(nameof(submitText));
private static readonly AssistantSessionStateKey<bool> SHOW_FOOTER_PROFILE_SELECTION_STATE_KEY = new(nameof(showFooterProfileSelection));
private static readonly AssistantSessionStateKey<HashSet<string>?> ASSISTANT_TOOL_IDS_STATE_KEY = new(nameof(assistantToolIds));
private static readonly AssistantSessionStateKey<PluginAssistants?> ASSISTANT_PLUGIN_STATE_KEY = new(nameof(assistantPlugin));
private static readonly AssistantSessionStateKey<AssistantState> ASSISTANT_STATE_STATE_KEY = new(nameof(assistantState));
private static readonly AssistantSessionStateKey<Dictionary<string, string>> IMAGE_CACHE_STATE_KEY = new(nameof(imageCache));
@@ -90,6 +100,7 @@ public partial class AssistantDynamic : AssistantBaseCore<NoSettingsPanel>
state.Set(ALLOW_PROFILES_STATE_KEY, this.allowProfiles);
state.Set(SUBMIT_TEXT_STATE_KEY, this.submitText);
state.Set(SHOW_FOOTER_PROFILE_SELECTION_STATE_KEY, this.showFooterProfileSelection);
state.Set(ASSISTANT_TOOL_IDS_STATE_KEY, this.assistantToolIds);
state.Set(ASSISTANT_PLUGIN_STATE_KEY, this.assistantPlugin);
state.Set(ASSISTANT_STATE_STATE_KEY, this.assistantState.Clone());
state.SetDictionary(IMAGE_CACHE_STATE_KEY, this.imageCache);
@@ -110,6 +121,7 @@ public partial class AssistantDynamic : AssistantBaseCore<NoSettingsPanel>
state.Restore(ALLOW_PROFILES_STATE_KEY, value => this.allowProfiles = value);
state.Restore(SUBMIT_TEXT_STATE_KEY, value => this.submitText = value);
state.Restore(SHOW_FOOTER_PROFILE_SELECTION_STATE_KEY, value => this.showFooterProfileSelection = value);
state.Restore(ASSISTANT_TOOL_IDS_STATE_KEY, value => this.assistantToolIds = value);
state.Restore(ASSISTANT_PLUGIN_STATE_KEY, value => this.assistantPlugin = value);
state.Restore(ASSISTANT_STATE_STATE_KEY, value => this.assistantState.CopyFrom(value));
state.RestoreDictionary(IMAGE_CACHE_STATE_KEY, this.imageCache);
@@ -159,6 +171,7 @@ public partial class AssistantDynamic : AssistantBaseCore<NoSettingsPanel>
this.systemPrompt = pluginAssistant.SystemPrompt;
this.submitText = pluginAssistant.SubmitText;
this.allowProfiles = pluginAssistant.AllowProfiles;
this.assistantToolIds = ReadPluginToolIds(pluginAssistant);
this.showFooterProfileSelection = !pluginAssistant.HasEmbeddedProfileSelection;
this.pluginPath = pluginAssistant.PluginPath;
var pluginHash = pluginAssistant.ComputeAuditHash();
@@ -353,6 +366,7 @@ public partial class AssistantDynamic : AssistantBaseCore<NoSettingsPanel>
this.systemPrompt = updatedPlugin.SystemPrompt;
this.submitText = updatedPlugin.SubmitText;
this.allowProfiles = updatedPlugin.AllowProfiles;
this.assistantToolIds = ReadPluginToolIds(updatedPlugin);
this.showFooterProfileSelection = !updatedPlugin.HasEmbeddedProfileSelection;
this.pluginPath = updatedPlugin.PluginPath;
var pluginHash = updatedPlugin.ComputeAuditHash();
@@ -369,6 +383,16 @@ public partial class AssistantDynamic : AssistantBaseCore<NoSettingsPanel>
#endregion
/// <summary>
/// Reads the tools this plugin names for its assistant.
/// </summary>
/// <remarks>
/// An ID this installation does not know stays in the set on purpose: the tool may arrive with
/// a plugin installed later, and dropping it here would silently turn a plugin that names tools
/// into one that lets the user choose.
/// </remarks>
private static HashSet<string>? ReadPluginToolIds(PluginAssistants plugin) => plugin.AssistantToolIds is { } toolIds ? ToolSelectionRules.NormalizeSelection(toolIds) : null;
private string ResolveImageSource(AssistantImage image)
{
if (string.IsNullOrWhiteSpace(image.Src))
@@ -343,6 +343,9 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING
-- Name of the results table (optional)
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T1127380661"] = "Name of the results table (optional)"
-- These tools are part of the selected policy and cannot be changed here. Every tool has to meet the confidence requirements of the selected provider, so a tool may stay unavailable even when the policy permits it.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T1133257227"] = "These tools are part of the selected policy and cannot be changed here. Every tool has to meet the confidence requirements of the selected provider, so a tool may stay unavailable even when the policy permits it."
-- Your organization requires a pause of at least {0} seconds between files.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T1155517317"] = "Your organization requires a pause of at least {0} seconds between files."
@@ -391,6 +394,9 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING
-- Which files should be processed? Separate multiple patterns with a semicolon, e.g., *.pdf;*.docx
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T1482642245"] = "Which files should be processed? Separate multiple patterns with a semicolon, e.g., *.pdf;*.docx"
-- blocked
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T1516072627"] = "blocked"
-- No matching files were found in the selected folder.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T1528532808"] = "No matching files were found in the selected folder."
@@ -445,6 +451,9 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING
-- Configured instructions file: {0}
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T2215428124"] = "Configured instructions file: {0}"
-- Tools for this batch run
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T2247412388"] = "Tools for this batch run"
-- No usable transcription provider is configured.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T2282521655"] = "No usable transcription provider is configured."
@@ -553,6 +562,12 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING
-- Time
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T3756319748"] = "Time"
-- failed
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T3769421748"] = "failed"
-- Tools used
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T3809968257"] = "Tools used"
-- Cancel the batch run
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T3830551741"] = "Cancel the batch run"
@@ -568,6 +583,9 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING
-- Output
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T4000727844"] = "Output"
-- Tools of this policy
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T4031686919"] = "Tools of this policy"
-- Continue the previous batch run?
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T4037527734"] = "Continue the previous batch run?"
@@ -631,6 +649,9 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING
-- A separate output folder is excluded from document discovery. This includes the default 'ai-results' folder, so results from an earlier run are not processed again. If the input folder itself is used for output, known batch result files are excluded instead.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T822136905"] = "A separate output folder is excluded from document discovery. This includes the default 'ai-results' folder, so results from an earlier run are not processed again. If the input folder itself is used for output, known batch result files are excluded instead."
-- The AI may use these tools while working on each document. Every tool has to meet the confidence requirements of the selected provider, so a tool may stay unavailable even when it is selected here.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T967206794"] = "The AI may use these tools while working on each document. Every tool has to meet the confidence requirements of the selected provider, so a tool may stay unavailable even when it is selected here."
-- Comma (,)
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::BATCHPROCESSINGCSVSEPARATOREXTENSIONS::T1676507543"] = "Comma (,)"
@@ -971,40 +992,40 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BUILDER::ASSISTANTBUILDER::T911303749"] =
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BUILDER::ASSISTANTBUILDER::T997013004"] = "Potentially Unsafe Assistant"
-- The generated Lua plugin code does not contain a readable plugin ID.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BUILDER::LUARESPONSEPARSEERROR::T1163279436"] = "The generated Lua plugin code does not contain a readable plugin ID."
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BUILDER::LUARESPONSEPARSEERROREXTENSION::T1163279436"] = "The generated Lua plugin code does not contain a readable plugin ID."
-- The model's answer is missing the assistant metadata.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BUILDER::LUARESPONSEPARSEERROR::T1389066899"] = "The model's answer is missing the assistant metadata."
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BUILDER::LUARESPONSEPARSEERROREXTENSION::T1389066899"] = "The model's answer is missing the assistant metadata."
-- The model's answer contains incomplete plugin metadata.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BUILDER::LUARESPONSEPARSEERROR::T181258566"] = "The model's answer contains incomplete plugin metadata."
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BUILDER::LUARESPONSEPARSEERROREXTENSION::T181258566"] = "The model's answer contains incomplete plugin metadata."
-- The model's answer contains incomplete assistant metadata.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BUILDER::LUARESPONSEPARSEERROR::T1863964049"] = "The model's answer contains incomplete assistant metadata."
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BUILDER::LUARESPONSEPARSEERROREXTENSION::T1863964049"] = "The model's answer contains incomplete assistant metadata."
-- The model returned an empty JSON object.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BUILDER::LUARESPONSEPARSEERROR::T2410202327"] = "The model returned an empty JSON object."
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BUILDER::LUARESPONSEPARSEERROREXTENSION::T2410202327"] = "The model returned an empty JSON object."
-- The model returned an unusable JSON response.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BUILDER::LUARESPONSEPARSEERROR::T2967613975"] = "The model returned an unusable JSON response."
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BUILDER::LUARESPONSEPARSEERROREXTENSION::T2967613975"] = "The model returned an unusable JSON response."
-- The model returned an invalid response.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BUILDER::LUARESPONSEPARSEERROR::T3368485003"] = "The model returned an invalid response."
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BUILDER::LUARESPONSEPARSEERROREXTENSION::T3368485003"] = "The model returned an invalid response."
-- The model response does not contain the generated Lua plugin code.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BUILDER::LUARESPONSEPARSEERROR::T3523772974"] = "The model response does not contain the generated Lua plugin code."
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BUILDER::LUARESPONSEPARSEERROREXTENSION::T3523772974"] = "The model response does not contain the generated Lua plugin code."
-- The model returned an invalid response: {0}
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BUILDER::LUARESPONSEPARSEERROR::T3546551801"] = "The model returned an invalid response: {0}"
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BUILDER::LUARESPONSEPARSEERROREXTENSION::T3546551801"] = "The model returned an invalid response: {0}"
-- The model's answer is missing the plugin metadata.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BUILDER::LUARESPONSEPARSEERROR::T3731646796"] = "The model's answer is missing the plugin metadata."
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BUILDER::LUARESPONSEPARSEERROREXTENSION::T3731646796"] = "The model's answer is missing the plugin metadata."
-- The model response is missing or unreadable.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BUILDER::LUARESPONSEPARSEERROR::T3865942038"] = "The model response is missing or unreadable."
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BUILDER::LUARESPONSEPARSEERROREXTENSION::T3865942038"] = "The model response is missing or unreadable."
-- The model responded with an unsupported or deprecated JSON schema.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BUILDER::LUARESPONSEPARSEERROR::T531597860"] = "The model responded with an unsupported or deprecated JSON schema."
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BUILDER::LUARESPONSEPARSEERROREXTENSION::T531597860"] = "The model responded with an unsupported or deprecated JSON schema."
-- Coding Assistant
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::CODING::ASSISTANTCODING::T1082499335"] = "Coding Assistant"
@@ -1072,6 +1093,9 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTA
-- Please provide a description of your analysis rules. This rules will be used to instruct the AI on how to analyze the documents.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T1291179736"] = "Please provide a description of your analysis rules. This rules will be used to instruct the AI on how to analyze the documents."
-- Only the tools selected here can be used by the AI for an analysis with this policy. Every tool still has to meet the confidence requirements of the selected provider, so a tool may remain unavailable even when this policy permits it.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T1692505801"] = "Only the tools selected here can be used by the AI for an analysis with this policy. Every tool still has to meet the confidence requirements of the selected provider, so a tool may remain unavailable even when this policy permits it."
-- Yes, protect this policy
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T1762380857"] = "Yes, protect this policy"
@@ -1153,6 +1177,9 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTA
-- Delete this policy
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T3119086260"] = "Delete this policy"
-- Tools this policy permits
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T31356122"] = "Tools this policy permits"
-- Policy {0}
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T3157740273"] = "Policy {0}"
@@ -1219,6 +1246,12 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTA
-- Revise Assistant
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DYNAMIC::ASSISTANTDYNAMIC::T1070696505"] = "Revise Assistant"
-- Tools of this assistant
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DYNAMIC::ASSISTANTDYNAMIC::T1456501183"] = "Tools of this assistant"
-- The author of this assistant chose these tools, so they cannot be changed here. Every tool has to meet the confidence requirements of the selected provider, so a tool may stay unavailable even when this assistant names it.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DYNAMIC::ASSISTANTDYNAMIC::T1835492160"] = "The author of this assistant chose these tools, so they cannot be changed here. Every tool has to meet the confidence requirements of the selected provider, so a tool may stay unavailable even when this assistant names it."
-- No assistant plugin are currently installed.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DYNAMIC::ASSISTANTDYNAMIC::T1913566603"] = "No assistant plugin are currently installed."
@@ -3193,21 +3226,42 @@ UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T1183581066"] = "Edit Me
-- Table {0} ({1})
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T1340759627"] = "Table {0} ({1})"
-- Result
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T1347088452"] = "Result"
-- Do you really want to remove this message?
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T1347427447"] = "Do you really want to remove this message?"
-- Yes, remove the AI response and edit it
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T1350385882"] = "Yes, remove the AI response and edit it"
-- Failed
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T1434043348"] = "Failed"
-- Tool Calls ({0})
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T1493057571"] = "Tool Calls ({0})"
-- Executed
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T1564757972"] = "Executed"
-- Yes, regenerate it
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T1603883875"] = "Yes, regenerate it"
-- No result
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T1684269223"] = "No result"
-- Yes, remove it
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T1820166585"] = "Yes, remove it"
-- Number of sources
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T1848978959"] = "Number of sources"
-- Show {0} tool calls
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T1981771421"] = "Show {0} tool calls"
-- Show tool call for {0}
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T2004842583"] = "Show tool call for {0}"
-- Do you really want to edit this message? In order to edit this message, the AI response will be deleted.
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T2018431076"] = "Do you really want to edit this message? In order to edit this message, the AI response will be deleted."
@@ -3220,6 +3274,9 @@ UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T2308444540"] = "Regener
-- Failed to export this message, because the file format '{0}' is unknown.
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T2544592344"] = "Failed to export this message, because the file format '{0}' is unknown."
-- Arguments
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T2738624831"] = "Arguments"
-- Export AI response
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T2822776450"] = "Export AI response"
@@ -3232,9 +3289,15 @@ UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T3175548294"] = "Cannot
-- Edit
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T3267849393"] = "Edit"
-- Unknown
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T3424652889"] = "Unknown"
-- Regenerate
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T3587744975"] = "Regenerate"
-- Blocked
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T3816336467"] = "Blocked"
-- Do you really want to regenerate this message?
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T3878878761"] = "Do you really want to regenerate this message?"
@@ -3244,6 +3307,12 @@ UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T4070211974"] = "Remove
-- No, keep it
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T4188329028"] = "No, keep it"
-- No tool calls
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T4224149521"] = "No tool calls"
-- No arguments
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T931993614"] = "No arguments"
-- The file '{0}' is currently not available and was not sent.
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTTEXT::T1432544573"] = "The file '{0}' is currently not available and was not sent."
@@ -3319,12 +3388,18 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ASSISTANTPLUGINSECURITYCARD::T1841954939"
-- Company approved
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ASSISTANTPLUGINSECURITYCARD::T2036497459"] = "Company approved"
-- Uses 1 tool
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ASSISTANTPLUGINSECURITYCARD::T2143098104"] = "Uses 1 tool"
-- Approved name
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ASSISTANTPLUGINSECURITYCARD::T2282386733"] = "Approved name"
-- Required minimum
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ASSISTANTPLUGINSECURITYCARD::T2354026284"] = "Required minimum"
-- Tools
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ASSISTANTPLUGINSECURITYCARD::T2499909372"] = "Tools"
-- Audit provider
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ASSISTANTPLUGINSECURITYCARD::T2757790517"] = "Audit provider"
@@ -3343,6 +3418,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ASSISTANTPLUGINSECURITYCARD::T3240350158"
-- Confidence
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ASSISTANTPLUGINSECURITYCARD::T3243388657"] = "Confidence"
-- Uses {0} tools
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ASSISTANTPLUGINSECURITYCARD::T3368476832"] = "Uses {0} tools"
-- Unknown
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ASSISTANTPLUGINSECURITYCARD::T3424652889"] = "Unknown"
@@ -3562,14 +3640,14 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIGURATIONMINCONFIDENCESELECTION::T252
-- Select a minimum confidence level
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIGURATIONMINCONFIDENCESELECTION::T2579793544"] = "Select a minimum confidence level"
-- You have selected 1 preview feature.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIGURATIONMULTISELECT::T1384241824"] = "You have selected 1 preview feature."
-- You have selected {0} items.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIGURATIONMULTISELECT::T2530254201"] = "You have selected {0} items."
-- No preview features selected.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIGURATIONMULTISELECT::T2809641588"] = "No preview features selected."
-- No items selected.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIGURATIONMULTISELECT::T3309488347"] = "No items selected."
-- You have selected {0} preview features.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIGURATIONMULTISELECT::T3513450626"] = "You have selected {0} preview features."
-- You have selected 1 item.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIGURATIONMULTISELECT::T95098799"] = "You have selected 1 item."
-- Preselected provider
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIGURATIONPROVIDERSELECTION::T1469984996"] = "Preselected provider"
@@ -3652,6 +3730,12 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCESELECTION::T700666808"] = "Mana
-- Available Data Sources
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCESELECTION::T86053874"] = "Available Data Sources"
-- Tools (Optional)
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DIRECTCHATLAUNCHERFORM::T1019749907"] = "Tools (Optional)"
-- These tools are preselected when the chat opens. Users can change the selection in the chat, and every tool has to meet the confidence requirements of the provider in use.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DIRECTCHATLAUNCHERFORM::T1286170698"] = "These tools are preselected when the chat opens. Users can change the selection in the chat, and every tool has to meet the confidence requirements of the provider in use."
-- Chat provider
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DIRECTCHATLAUNCHERFORM::T1648955896"] = "Chat provider"
@@ -3703,6 +3787,15 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::HALLUZINATIONREMINDER::T3528806904"] = "L
-- Issues
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ISSUES::T3229841001"] = "Issues"
-- Some tools selected for this run are not fully configured and stay unused: {0}. Please complete their settings.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::MANAGEDTOOLSWARNING::T1319635088"] = "Some tools selected for this run are not fully configured and stay unused: {0}. Please complete their settings."
-- Not all tools selected for this run can be used with the chosen AI provider: {0}. Please choose a provider with a higher confidence level to use all of them.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::MANAGEDTOOLSWARNING::T2430645786"] = "Not all tools selected for this run can be used with the chosen AI provider: {0}. Please choose a provider with a higher confidence level to use all of them."
-- Tools were selected for this run, but the chosen model cannot use tools. It runs without them. Please choose a model which supports tools.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::MANAGEDTOOLSWARNING::T3008114108"] = "Tools were selected for this run, but the chosen model cannot use tools. It runs without them. Please choose a model which supports tools."
-- Your Pandoc installation meets the requirements.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::MANAGEPANDOCDEPENDENCY::T1167365374"] = "Your Pandoc installation meets the requirements."
@@ -3982,6 +4075,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::READWEBCONTENT::T2939928117"] = "Cleanup
-- Hide web content options
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::READWEBCONTENT::T3031774728"] = "Hide web content options"
-- The content of '{0}' could not be loaded: {1}
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::READWEBCONTENT::T3073906267"] = "The content of '{0}' could not be loaded: {1}"
-- Please provide a valid HTTP or HTTPS URL.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::READWEBCONTENT::T307442288"] = "Please provide a valid HTTP or HTTPS URL."
@@ -4168,6 +4264,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T1364944735"]
-- Additional root certificates are enabled
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T1380446131"] = "Additional root certificates are enabled"
-- You have selected 1 preview feature.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T1384241824"] = "You have selected 1 preview feature."
-- Select preview features
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T1439783084"] = "Select preview features"
@@ -4252,6 +4351,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T2655930524"]
-- Path to a PEM file containing one or more root CA certificates. For Flatpak deployments, this file must be placed in a location that is readable inside the sandbox.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T2700836219"] = "Path to a PEM file containing one or more root CA certificates. For Flatpak deployments, this file must be placed in a location that is readable inside the sandbox."
-- No preview features selected.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T2809641588"] = "No preview features selected."
-- This installation does not check for updates itself. Contact the person or organization that installed AI Studio for update information.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T2918560776"] = "This installation does not check for updates itself. Contact the person or organization that installed AI Studio for update information."
@@ -4270,6 +4372,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T3165555978"]
-- External HTTPS certificates
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T348936513"] = "External HTTPS certificates"
-- You have selected {0} preview features.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T3513450626"] = "You have selected {0} preview features."
-- Allowed hosts for additional root certificates
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T3562495752"] = "Allowed hosts for additional root certificates"
@@ -4579,6 +4684,42 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T782238
-- Export configuration
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T975426229"] = "Export configuration"
-- Settings
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTOOLS::T1258653480"] = "Settings"
-- Description
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTOOLS::T1725856265"] = "Description"
-- Icon
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTOOLS::T1759955728"] = "Icon"
-- This tool still needs to be configured.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTOOLS::T1958939818"] = "This tool still needs to be configured."
-- Missing required settings: {0}
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTOOLS::T2588115579"] = "Missing required settings: {0}"
-- Name
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTOOLS::T266367750"] = "Name"
-- No minimum confidence level chosen
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTOOLS::T2828607242"] = "No minimum confidence level chosen"
-- Minimum provider confidence
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTOOLS::T3461070436"] = "Minimum provider confidence"
-- Configure global settings for each tool.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTOOLS::T3728248397"] = "Configure global settings for each tool."
-- Tool Settings
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTOOLS::T3730473128"] = "Tool Settings"
-- This tool has been disabled by your organization.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTOOLS::T3794167684"] = "This tool has been disabled by your organization."
-- Status
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTOOLS::T6222351"] = "Status"
-- No transcription provider configured yet.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T1079350363"] = "No transcription provider configured yet."
@@ -4654,6 +4795,72 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::THIRDPARTYCOMPONENT::T1392042694"] = "Ope
-- License:
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::THIRDPARTYCOMPONENT::T1908172666"] = "License:"
-- Tool selection is hidden
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::TOOLDEFAULTSCONFIGURATION::T2096103917"] = "Tool selection is hidden"
-- You have selected 1 tool.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::TOOLDEFAULTSCONFIGURATION::T2493128368"] = "You have selected 1 tool."
-- Choose which tools should be preselected for new runs of this assistant.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::TOOLDEFAULTSCONFIGURATION::T2696618758"] = "Choose which tools should be preselected for new runs of this assistant."
-- Default tools for this assistant
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::TOOLDEFAULTSCONFIGURATION::T3253667950"] = "Default tools for this assistant"
-- Tool selection is visible
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::TOOLDEFAULTSCONFIGURATION::T3384582069"] = "Tool selection is visible"
-- Show tool selection in this assistant?
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::TOOLDEFAULTSCONFIGURATION::T3494508870"] = "Show tool selection in this assistant?"
-- You have selected {0} tools.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::TOOLDEFAULTSCONFIGURATION::T3729156356"] = "You have selected {0} tools."
-- No tools selected.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::TOOLDEFAULTSCONFIGURATION::T3934845540"] = "No tools selected."
-- Default tools for chat
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::TOOLDEFAULTSCONFIGURATION::T907403808"] = "Default tools for chat"
-- Choose which tools should be preselected for new chats.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::TOOLDEFAULTSCONFIGURATION::T948842182"] = "Choose which tools should be preselected for new chats."
-- Tool changes are locked while a response is running. Your current selection is shown below and applies again from the next message once the run is finished.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::TOOLSELECTION::T1688023907"] = "Tool changes are locked while a response is running. Your current selection is shown below and applies again from the next message once the run is finished."
-- Tools allow the LLM to perform targeted additional actions such as web searches or reading web pages.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::TOOLSELECTION::T1944689297"] = "Tools allow the LLM to perform targeted additional actions such as web searches or reading web pages."
-- Required settings are missing. Configure this tool before enabling it.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::TOOLSELECTION::T3119156561"] = "Required settings are missing. Configure this tool before enabling it."
-- Close
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::TOOLSELECTION::T3448155331"] = "Close"
-- This tool has been disabled by your organization.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::TOOLSELECTION::T3794167684"] = "This tool has been disabled by your organization."
-- No tools are available in this context.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::TOOLSELECTION::T3904490680"] = "No tools are available in this context."
-- This tool requires provider confidence {0}. The selected provider has {1}.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::TOOLSELECTION::T4097602620"] = "This tool requires provider confidence {0}. The selected provider has {1}."
-- Tool Selection
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::TOOLSELECTION::T749664565"] = "Tool Selection"
-- Select tools
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::TOOLSELECTION::T998515990"] = "Select tools"
-- No tools selected
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::TOOLSELECTIONFIELD::T2892114594"] = "No tools selected"
-- 1 tool selected
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::TOOLSELECTIONFIELD::T4209882371"] = "1 tool selected"
-- {0} tools selected
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::TOOLSELECTIONFIELD::T807707919"] = "{0} tools selected"
-- You'll interact with the AI systems using your voice. To achieve this, we want to integrate voice input (speech-to-text) and output (text-to-speech). However, later on, it should also have a natural conversation flow, i.e., seamless conversation.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::VISION::T1015366320"] = "You'll interact with the AI systems using your voice. To achieve this, we want to integrate voice input (speech-to-text) and output (text-to-speech). However, later on, it should also have a natural conversation flow, i.e., seamless conversation."
@@ -6424,6 +6631,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T2646845972"] = "Add"
-- Additional API parameters
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T2728244552"] = "Additional API parameters"
-- Tool calling
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T2745173751"] = "Tool calling"
-- Invalid JSON: Add the parameters in proper JSON formatting, e.g., "temperature": 0.5. Remove trailing commas. The usual surrounding curly brackets {} must not be used, though.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T2765821959"] = "Invalid JSON: Add the parameters in proper JSON formatting, e.g., \"temperature\": 0.5. Remove trailing commas. The usual surrounding curly brackets {} must not be used, though."
@@ -6865,6 +7075,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T22
-- The lower end of the random pause interval. AI Studio never allows less than 6 seconds.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T237774509"] = "The lower end of the random pause interval. AI Studio never allows less than 6 seconds."
-- A policy brings its own tools, so there is nothing to preselect here. You configure them with the policy in the Document Analysis Assistant.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T2391906382"] = "A policy brings its own tools, so there is nothing to preselect here. You configure them with the policy in the Document Analysis Assistant."
-- When enabled, new batch runs start with the defaults configured below.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T2592677194"] = "When enabled, new batch runs start with the defaults configured below."
@@ -7978,6 +8191,30 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGWRITINGEMAILS::T3547
-- Preselect e-mail options?
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGWRITINGEMAILS::T3832719342"] = "Preselect e-mail options?"
-- Save
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::TOOLSETTINGSDIALOG::T1294818664"] = "Save"
-- Please configure the required settings: {0}
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::TOOLSETTINGSDIALOG::T2412603418"] = "Please configure the required settings: {0}"
-- Not set
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::TOOLSETTINGSDIALOG::T3616903110"] = "Not set"
-- Tool Settings
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::TOOLSETTINGSDIALOG::T3730473128"] = "Tool Settings"
-- This tool has been disabled by your organization.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::TOOLSETTINGSDIALOG::T3794167684"] = "This tool has been disabled by your organization."
-- The selected tool could not be loaded.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::TOOLSETTINGSDIALOG::T3907843187"] = "The selected tool could not be loaded."
-- {0} Default: {1}
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::TOOLSETTINGSDIALOG::T403490413"] = "{0} Default: {1}"
-- Cancel
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::TOOLSETTINGSDIALOG::T900713019"] = "Cancel"
-- Save
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SHORTCUTDIALOG::T1294818664"] = "Save"
@@ -10243,6 +10480,9 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::PLUGINASSISTANTS::T3
-- The provided ASSISTANT lua table does not contain a valid system prompt.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::PLUGINASSISTANTS::T3402798667"] = "The provided ASSISTANT lua table does not contain a valid system prompt."
-- The ASSISTANT table contains invalid ToolIds. Expected a non-empty list of unique, non-empty tool IDs.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::PLUGINASSISTANTS::T3416855489"] = "The ASSISTANT table contains invalid ToolIds. Expected a non-empty list of unique, non-empty tool IDs."
-- The ASSISTANT table does not contain a valid system prompt.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::PLUGINASSISTANTS::T3723171842"] = "The ASSISTANT table does not contain a valid system prompt."
@@ -10708,6 +10948,9 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::SECURITY::PROMPTINJECTIONGUARDSERVICE::T246048
-- AI Studio removed suspicious instructions from {0} sources before using them.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SECURITY::PROMPTINJECTIONGUARDSERVICE::T3489536228"] = "AI Studio removed suspicious instructions from {0} sources before using them."
-- AI Studio could not check {0} sources for prompt injections. The content is used as it is.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SECURITY::PROMPTINJECTIONGUARDSERVICE::T3583030090"] = "AI Studio could not check {0} sources for prompt injections. The content is used as it is."
-- Chat attachment
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SECURITY::PROMPTINJECTIONSOURCEKINDEXTENSIONS::T1071345316"] = "Chat attachment"
@@ -10738,6 +10981,9 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::ASSISTANTPLUGINGENERATIONSERVICE::T1
-- The revision model did not return a usable answer.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::ASSISTANTPLUGINGENERATIONSERVICE::T1411545143"] = "The revision model did not return a usable answer."
-- The revised assistant plugin asks for tools this AI Studio does not have: \"{0}\". Please try again.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::ASSISTANTPLUGINGENERATIONSERVICE::T1427741438"] = "The revised assistant plugin asks for tools this AI Studio does not have: \\\"{0}\\\". Please try again."
-- Description
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::ASSISTANTPLUGINGENERATIONSERVICE::T1725856265"] = "Description"
@@ -10759,6 +11005,9 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::ASSISTANTPLUGINGENERATIONSERVICE::T2
-- The current plugin.lua content is empty.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::ASSISTANTPLUGINGENERATIONSERVICE::T2491968008"] = "The current plugin.lua content is empty."
-- Tools
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::ASSISTANTPLUGINGENERATIONSERVICE::T2499909372"] = "Tools"
-- Inputs
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::ASSISTANTPLUGINGENERATIONSERVICE::T2647381688"] = "Inputs"
@@ -10777,6 +11026,12 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::ASSISTANTPLUGINGENERATIONSERVICE::T2
-- UI Components
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::ASSISTANTPLUGINGENERATIONSERVICE::T3053707933"] = "UI Components"
-- The generated assistant plugin asks for tools this AI Studio does not have: \"{0}\". Please try again.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::ASSISTANTPLUGINGENERATIONSERVICE::T3058747041"] = "The generated assistant plugin asks for tools this AI Studio does not have: \\\"{0}\\\". Please try again."
-- The generated assistant plugin must be a form assistant, not a chat launcher.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::ASSISTANTPLUGINGENERATIONSERVICE::T3203271639"] = "The generated assistant plugin must be a form assistant, not a chat launcher."
-- Assistant Plugin Revision
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::ASSISTANTPLUGINGENERATIONSERVICE::T3245954919"] = "Assistant Plugin Revision"
@@ -10798,9 +11053,6 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::ASSISTANTPLUGINGENERATIONSERVICE::T3
-- The revised assistant metadata does not match the revised plugin.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::ASSISTANTPLUGINGENERATIONSERVICE::T3578379466"] = "The revised assistant metadata does not match the revised plugin."
-- The generated assistant plugin does not match the selected chat launcher configuration.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::ASSISTANTPLUGINGENERATIONSERVICE::T3631147451"] = "The generated assistant plugin does not match the selected chat launcher configuration."
-- Safety Notes
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::ASSISTANTPLUGINGENERATIONSERVICE::T3633499050"] = "Safety Notes"
@@ -10831,6 +11083,9 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::ASSISTANTPLUGINGENERATIONSERVICE::T4
-- Prompt Strategy
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::ASSISTANTPLUGINGENERATIONSERVICE::T410529216"] = "Prompt Strategy"
-- The generated chat launcher is not a valid assistant plugin.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::ASSISTANTPLUGINGENERATIONSERVICE::T4182589474"] = "The generated chat launcher is not a valid assistant plugin."
-- The draft model did not return a usable answer.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::ASSISTANTPLUGINGENERATIONSERVICE::T4183375977"] = "The draft model did not return a usable answer."
@@ -11149,6 +11404,165 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::SOURCEEXTENSIONS::T4174900468"] = "Sources pro
-- Sources provided by the AI
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SOURCEEXTENSIONS::T4261248356"] = "Sources provided by the AI"
-- Sources used by tools
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SOURCEEXTENSIONS::T535360212"] = "Sources used by tools"
-- The provider '{0}' returned an invalid tool calling response. Check the provider's tool calling configuration and see the logs for details.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::HARNESS::TOOLCALLINGMESSAGES::T2768311456"] = "The provider '{0}' returned an invalid tool calling response. Check the provider's tool calling configuration and see the logs for details."
-- The tool calling request failed with status code {0}. See the logs for details.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::HARNESS::TOOLCALLINGMESSAGES::T3117779001"] = "The tool calling request failed with status code {0}. See the logs for details."
-- Tool
UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::ITOOLIMPLEMENTATION::T3517012711"] = "Tool"
-- Tool description
UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::ITOOLIMPLEMENTATION::T4056470505"] = "Tool description"
-- Please select an LLM provider.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGAVAILABILITYEXTENSIONS::T1110311702"] = "Please select an LLM provider."
-- Tool calling support is not enabled by default for this model, but you can enable this capability in the expert settings of the provider if you are sure the model supports it.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGAVAILABILITYEXTENSIONS::T3805542503"] = "Tool calling support is not enabled by default for this model, but you can enable this capability in the expert settings of the provider if you are sure the model supports it."
-- Allowed private hosts must be host names only, without scheme or path.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::READWEBPAGETOOL::T2196457612"] = "Allowed private hosts must be host names only, without scheme or path."
-- The web page was not loaded because private or VPN web pages require a High-confidence provider or a provider trusted by your organization's configuration.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::READWEBPAGETOOL::T2563437007"] = "The web page was not loaded because private or VPN web pages require a High-confidence provider or a provider trusted by your organization's configuration."
-- Maximum Content Characters
UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::READWEBPAGETOOL::T2801581200"] = "Maximum Content Characters"
-- Allowed private host '{0}' is not valid.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::READWEBPAGETOOL::T3089707139"] = "Allowed private host '{0}' is not valid."
-- Allowed Private Hosts
UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::READWEBPAGETOOL::T3415515539"] = "Allowed Private Hosts"
-- Timeout Seconds
UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::READWEBPAGETOOL::T3567699845"] = "Timeout Seconds"
-- Read Web Page
UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::READWEBPAGETOOL::T3612587998"] = "Read Web Page"
-- Load a web page and extract its readable content, links, and page details.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::READWEBPAGETOOL::T3715690061"] = "Load a web page and extract its readable content, links, and page details."
-- (Optional) Host allowlist for private or VPN web pages. For security reasons, private or VPN web pages aren't allowed to be read by default. Separate host patterns with commas, such as example.de, *.example.de. Allowed private hosts require a High-confidence provider or a provider trusted by your organization's configuration. For allowed HTTPS internal hosts, AI Studio also tries the operating system's default sign-in automatically when the server responds with integrated authentication.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::READWEBPAGETOOL::T3802894016"] = "(Optional) Host allowlist for private or VPN web pages. For security reasons, private or VPN web pages aren't allowed to be read by default. Separate host patterns with commas, such as example.de, *.example.de. Allowed private hosts require a High-confidence provider or a provider trusted by your organization's configuration. For allowed HTTPS internal hosts, AI Studio also tries the operating system's default sign-in automatically when the server responds with integrated authentication."
-- (Optional) HTTP timeout for loading a web page in seconds.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::READWEBPAGETOOL::T4126164830"] = "(Optional) HTTP timeout for loading a web page in seconds."
-- The setting '{0}' must be a positive integer.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::READWEBPAGETOOL::T4199432074"] = "The setting '{0}' must be a positive integer."
-- (Optional) Global truncation limit for extracted characters returned to the model.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::READWEBPAGETOOL::T900659180"] = "(Optional) Global truncation limit for extracted characters returned to the model."
-- The language to search in when the AI model does not ask for a specific one. This is required: without a language, many search engines return no results at all, and the search would come back empty without telling you why. Choose 'Any language' if you do not want to restrict the results.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::SEARXNGWEBSEARCHTOOL::T114991220"] = "The language to search in when the AI model does not ask for a specific one. This is required: without a language, many search engines return no results at all, and the search would come back empty without telling you why. Choose 'Any language' if you do not want to restrict the results."
-- Maximum Results
UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::SEARXNGWEBSEARCHTOOL::T1273024715"] = "Maximum Results"
-- The setting '{0}' must be less than or equal to {1}.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::SEARXNGWEBSEARCHTOOL::T1391527409"] = "The setting '{0}' must be less than or equal to {1}."
-- All Pages Retrieval Timeout Seconds
UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::SEARXNGWEBSEARCHTOOL::T1633427398"] = "All Pages Retrieval Timeout Seconds"
-- Optional minimum character budget reserved for each successfully retrieved website.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::SEARXNGWEBSEARCHTOOL::T1671995661"] = "Optional minimum character budget reserved for each successfully retrieved website."
-- A SearXNG URL is required.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::SEARXNGWEBSEARCHTOOL::T1746583720"] = "A SearXNG URL is required."
-- The total content budget must reserve at least {0} characters for each of up to {1} results.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::SEARXNGWEBSEARCHTOOL::T2124070269"] = "The total content budget must reserve at least {0} characters for each of up to {1} results."
-- Default Safe Search Policy
UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::SEARXNGWEBSEARCHTOOL::T2514181501"] = "Default Safe Search Policy"
-- Default Language
UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::SEARXNGWEBSEARCHTOOL::T2526826120"] = "Default Language"
-- The configured web search content budget is not valid.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::SEARXNGWEBSEARCHTOOL::T299004879"] = "The configured web search content budget is not valid."
-- The configured SearXNG URL is not a valid absolute URL.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::SEARXNGWEBSEARCHTOOL::T3038368943"] = "The configured SearXNG URL is not a valid absolute URL."
-- Search Timeout Seconds
UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::SEARXNGWEBSEARCHTOOL::T3219072199"] = "Search Timeout Seconds"
-- Search the web with a configured SearXNG instance and retrieve the readable content of the best matching pages.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::SEARXNGWEBSEARCHTOOL::T3361633224"] = "Search the web with a configured SearXNG instance and retrieve the readable content of the best matching pages."
-- Page Timeout Seconds
UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::SEARXNGWEBSEARCHTOOL::T3459475852"] = "Page Timeout Seconds"
-- Optional default maximum number of results returned to the model when the model does not provide a limit.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::SEARXNGWEBSEARCHTOOL::T3603838271"] = "Optional default maximum number of results returned to the model when the model does not provide a limit."
-- Maximum Total Content Characters
UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::SEARXNGWEBSEARCHTOOL::T366488298"] = "Maximum Total Content Characters"
-- Optional timeout for loading each individual result page in seconds.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::SEARXNGWEBSEARCHTOOL::T3668086641"] = "Optional timeout for loading each individual result page in seconds."
-- Web Search
UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::SEARXNGWEBSEARCHTOOL::T3815068443"] = "Web Search"
-- Optional overall timeout for retrieving all result pages in seconds.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::SEARXNGWEBSEARCHTOOL::T3854998169"] = "Optional overall timeout for retrieving all result pages in seconds."
-- Optional safe search policy sent to SearXNG when configured.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::SEARXNGWEBSEARCHTOOL::T3967748757"] = "Optional safe search policy sent to SearXNG when configured."
-- Optional HTTP timeout for the SearXNG search request in seconds.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::SEARXNGWEBSEARCHTOOL::T408390115"] = "Optional HTTP timeout for the SearXNG search request in seconds."
-- Base URL of the SearXNG instance. You can enter either the instance root URL or the /search endpoint. The instance must have the JSON format enabled, which means 'json' has to be listed under 'search.formats' in its settings.yml. Public instances usually serve only the web interface and additionally block automated requests, so a self-hosted instance is the reliable option.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::SEARXNGWEBSEARCHTOOL::T4198847064"] = "Base URL of the SearXNG instance. You can enter either the instance root URL or the /search endpoint. The instance must have the JSON format enabled, which means 'json' has to be listed under 'search.formats' in its settings.yml. Public instances usually serve only the web interface and additionally block automated requests, so a self-hosted instance is the reliable option."
-- The setting '{0}' must be a positive integer.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::SEARXNGWEBSEARCHTOOL::T4199432074"] = "The setting '{0}' must be a positive integer."
-- Minimum Content Characters Budget Per Website
UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::SEARXNGWEBSEARCHTOOL::T4200431837"] = "Minimum Content Characters Budget Per Website"
-- The setting '{0}' holds the value '{1}', which is not one of the available options. Please choose one of the offered values.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::SEARXNGWEBSEARCHTOOL::T68683294"] = "The setting '{0}' holds the value '{1}', which is not one of the available options. Please choose one of the offered values."
-- Optional total character budget shared by all retrieved pages.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::SEARXNGWEBSEARCHTOOL::T836062282"] = "Optional total character budget shared by all retrieved pages."
-- The configured SearXNG URL must start with http:// or https://.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::SEARXNGWEBSEARCHTOOL::T944878454"] = "The configured SearXNG URL must start with http:// or https://."
-- SearXNG URL
UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::SEARXNGWEBSEARCHTOOL::T993547568"] = "SearXNG URL"
-- Using tools: {0}
UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLRUNTIMESTATUS::T2834986024"] = "Using tools: {0}"
-- Using tool: {0}
UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLRUNTIMESTATUS::T4185351801"] = "Using tool: {0}"
-- Moderate
UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLSETTINGSOPTIONSOURCES::T177463328"] = "Moderate"
-- Strict
UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLSETTINGSOPTIONSOURCES::T1834358932"] = "Strict"
-- Off
UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLSETTINGSOPTIONSOURCES::T231126186"] = "Off"
-- Any language
UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLSETTINGSOPTIONSOURCES::T747012729"] = "Any language"
-- The file path is null or empty and the file therefore can not be loaded.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::USERFILE::T932243993"] = "The file path is null or empty and the file therefore can not be loaded."
@@ -11290,11 +11704,17 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::VALIDATION::PROVIDERVALIDATION::T649507886"] =
-- Please select a model.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::VALIDATION::PROVIDERVALIDATION::T818893091"] = "Please select a model."
-- Are you sure you want to delete the chat '{0}' in the workspace '{1}'?
UI_TEXT_CONTENT["AISTUDIO::TOOLS::WORKSPACEBEHAVIOUR::T1016188706"] = "Are you sure you want to delete the chat '{0}' in the workspace '{1}'?"
-- Unnamed workspace
UI_TEXT_CONTENT["AISTUDIO::TOOLS::WORKSPACEBEHAVIOUR::T1307384014"] = "Unnamed workspace"
-- Delete Chat
UI_TEXT_CONTENT["AISTUDIO::TOOLS::WORKSPACEBEHAVIOUR::T2244038752"] = "Delete Chat"
-- Are you sure you want to delete the temporary chat '{0}'?
UI_TEXT_CONTENT["AISTUDIO::TOOLS::WORKSPACEBEHAVIOUR::T3043761007"] = "Are you sure you want to delete the temporary chat '{0}'?"
-- Unnamed chat
UI_TEXT_CONTENT["AISTUDIO::TOOLS::WORKSPACEBEHAVIOUR::T3310482275"] = "Unnamed chat"
@@ -0,0 +1,24 @@
using System.Text.Json.Serialization;
namespace AIStudio.Assistants.PromptOptimizer;
public sealed class PromptOptimizationRecommendations
{
[JsonPropertyName("clarity_and_directness")]
public string ClarityAndDirectness { get; set; } = string.Empty;
[JsonPropertyName("examples_and_context")]
public string ExamplesAndContext { get; set; } = string.Empty;
[JsonPropertyName("sequential_steps")]
public string SequentialSteps { get; set; } = string.Empty;
[JsonPropertyName("structure_with_markers")]
public string StructureWithMarkers { get; set; } = string.Empty;
[JsonPropertyName("role_definition")]
public string RoleDefinition { get; set; } = string.Empty;
[JsonPropertyName("language_choice")]
public string LanguageChoice { get; set; } = string.Empty;
}
@@ -9,25 +9,4 @@ public sealed class PromptOptimizationResult
[JsonPropertyName("recommendations")]
public PromptOptimizationRecommendations Recommendations { get; set; } = new();
}
public sealed class PromptOptimizationRecommendations
{
[JsonPropertyName("clarity_and_directness")]
public string ClarityAndDirectness { get; set; } = string.Empty;
[JsonPropertyName("examples_and_context")]
public string ExamplesAndContext { get; set; } = string.Empty;
[JsonPropertyName("sequential_steps")]
public string SequentialSteps { get; set; } = string.Empty;
[JsonPropertyName("structure_with_markers")]
public string StructureWithMarkers { get; set; } = string.Empty;
[JsonPropertyName("role_definition")]
public string RoleDefinition { get; set; } = string.Empty;
[JsonPropertyName("language_choice")]
public string LanguageChoice { get; set; } = string.Empty;
}
}
@@ -44,7 +44,7 @@ public sealed partial class VisualBriefingStore
: VisualBriefingBuildStatus.ACTIVE;
matching.Failure = null;
matching.UpdatedAtUtc = DateTimeOffset.UtcNow;
await this.StoreBuildAtomicAsync(matching, token);
await this.StoreBuildAtomicAsync(matching, overwrite: true, token);
return (matching, true);
}
@@ -56,10 +56,10 @@ public sealed partial class VisualBriefingStore
{
stale.Status = VisualBriefingBuildStatus.SUPERSEDED;
stale.UpdatedAtUtc = DateTimeOffset.UtcNow;
await this.StoreBuildAtomicAsync(stale, token);
await this.StoreBuildAtomicAsync(stale, overwrite: true, token);
}
await this.StoreBuildAtomicAsync(candidate, token, overwrite: false);
await this.StoreBuildAtomicAsync(candidate, overwrite: false, token);
return (candidate, false);
}
finally
@@ -81,7 +81,7 @@ public sealed partial class VisualBriefingStore
try
{
await this.StoreBuildAtomicAsync(build, token);
await this.StoreBuildAtomicAsync(build, overwrite: true, token);
}
finally
{
@@ -372,12 +372,11 @@ public sealed partial class VisualBriefingStore
/// Writes one build record atomically.
/// </summary>
/// <param name="build">The build record.</param>
/// <param name="token">The cancellation token.</param>
/// <param name="overwrite">Whether an existing record may be replaced.</param>
private async Task StoreBuildAtomicAsync(
VisualBriefingBuildRecord build,
CancellationToken token,
bool overwrite = true)
/// <param name="token">The cancellation token.</param>
private async Task StoreBuildAtomicAsync(VisualBriefingBuildRecord build,
bool overwrite,
CancellationToken token)
{
if (build.BuildVersion != VisualBriefingVersions.BUILD ||
build.BuildId == Guid.Empty ||
@@ -386,7 +385,7 @@ public sealed partial class VisualBriefingStore
throw new InvalidDataException("The visual briefing build record is invalid.");
var json = JsonSerializer.Serialize(build, JSON_OPTIONS);
await WriteTextAtomicAsync(this.BuildPath(build.BriefingId, build.BuildId), json, token, overwrite);
await WriteTextAtomicAsync(this.BuildPath(build.BriefingId, build.BuildId), json, overwrite, token);
}
/// <summary>
@@ -22,7 +22,7 @@ public sealed partial class VisualBriefingStore
try
{
this.LastSelectedBriefingId = briefingId;
await WriteTextAtomicAsync(this.SelectionPath(), JsonSerializer.Serialize<Guid?>(briefingId), token);
await WriteTextAtomicAsync(this.SelectionPath(), JsonSerializer.Serialize<Guid?>(briefingId), overwrite: true, token);
}
finally
{
@@ -45,7 +45,7 @@ public sealed partial class VisualBriefingStore
return;
this.LastSelectedBriefingId = null;
await WriteTextAtomicAsync(this.SelectionPath(), JsonSerializer.Serialize<Guid?>(null), token);
await WriteTextAtomicAsync(this.SelectionPath(), JsonSerializer.Serialize<Guid?>(null), overwrite: true, token);
}
finally
{
@@ -456,7 +456,7 @@ public sealed partial class VisualBriefingStore
private async Task StoreManifestAtomicAsync(VisualBriefingManifest manifest, CancellationToken token)
{
var json = JsonSerializer.Serialize(manifest, JSON_OPTIONS);
await WriteTextAtomicAsync(this.ManifestPath(manifest.BriefingId), json, token);
await WriteTextAtomicAsync(this.ManifestPath(manifest.BriefingId), json, overwrite: true, token);
}
/// <summary>
@@ -59,7 +59,7 @@ public sealed partial class VisualBriefingStore
committedBuild.Failure = null;
committedBuild.UpdatedAtUtc = DateTimeOffset.UtcNow;
await this.StoreBuildAtomicAsync(committedBuild, token);
await this.StoreBuildAtomicAsync(committedBuild, overwrite: true, token);
}
foreach (var interruptedBuild in builds.Where(build => build.Status is VisualBriefingBuildStatus.ACTIVE))
@@ -99,7 +99,7 @@ public sealed partial class VisualBriefingStore
interruptedBuild.Status = VisualBriefingBuildStatus.FAILED;
interruptedBuild.Failure = interruptedFailure;
interruptedBuild.UpdatedAtUtc = DateTimeOffset.UtcNow;
await this.StoreBuildAtomicAsync(interruptedBuild, token);
await this.StoreBuildAtomicAsync(interruptedBuild, overwrite: true, token);
}
var changed = manifest.Versions.RemoveAll(version =>
@@ -166,7 +166,7 @@ public sealed partial class VisualBriefingStore
matchingBuild.Status = VisualBriefingBuildStatus.COMPLETED;
matchingBuild.Failure = null;
matchingBuild.UpdatedAtUtc = DateTimeOffset.UtcNow;
await this.StoreBuildAtomicAsync(matchingBuild, token);
await this.StoreBuildAtomicAsync(matchingBuild, overwrite: true, token);
}
changed = true;
@@ -85,7 +85,7 @@ public sealed partial class VisualBriefingStore
var source = manifest.Sources.FirstOrDefault(candidate => candidate.SourceId == sourceId)
?? throw new InvalidOperationException("The media source does not exist in this briefing.");
var transcriptPath = this.TranscriptPath(briefingId, source.SourceId);
await WriteTextAtomicAsync(transcriptPath, transcript, token);
await WriteTextAtomicAsync(transcriptPath, transcript, overwrite: true, token);
source.TranscriptStatus = VisualBriefingTranscriptStatus.CURRENT;
ApplyFileSnapshot(source, source.Path);
manifest.ModifiedAtUtc = DateTimeOffset.UtcNow;
@@ -132,8 +132,7 @@ public sealed partial class VisualBriefingStore
await WriteTextAtomicAsync(
Path.Combine(this.VersionsDirectory(manifest.BriefingId), version.FileName),
html,
token,
overwrite: false);
overwrite: false, token);
manifest.Versions.Add(version);
if (request.EditMode is not (VisualBriefingEditMode.CHANGE_DESIGN or VisualBriefingEditMode.RECOMPILE))
@@ -357,7 +356,7 @@ public sealed partial class VisualBriefingStore
var storedVersion = await this.OpenIntegrityCheckedVersionAsync(existing.BriefingId, knownRevision.RevisionId, token);
if (storedVersion is null)
{
await WriteTextAtomicAsync(this.VersionPath(existing.BriefingId, knownRevision), html, token);
await WriteTextAtomicAsync(this.VersionPath(existing.BriefingId, knownRevision), html, overwrite: true, token);
var restoredHashes = ComputeSectionHashes(parts);
knownRevision.DataHash = restoredHashes.DataHash;
knownRevision.AssetHash = restoredHashes.AssetHash;
@@ -415,8 +414,7 @@ public sealed partial class VisualBriefingStore
await WriteTextAtomicAsync(
Path.Combine(this.VersionsDirectory(existing.BriefingId), version.FileName),
html,
token,
overwrite: false);
overwrite: false, token);
existing.Versions.Add(version);
existing.ModifiedAtUtc = DateTimeOffset.UtcNow;
@@ -107,17 +107,16 @@ public sealed partial class VisualBriefingStore(
string json,
CancellationToken token)
{
await WriteTextAtomicAsync(path, json, token, overwrite: false);
await WriteTextAtomicAsync(path, json, overwrite: false, token);
}
/// <summary>
/// Defines <c>WriteTextAtomicAsync</c> for the visual briefing feature.
/// </summary>
private static async Task WriteTextAtomicAsync(
string targetPath,
private static async Task WriteTextAtomicAsync(string targetPath,
string content,
CancellationToken token,
bool overwrite = true)
bool overwrite,
CancellationToken token)
{
Directory.CreateDirectory(Path.GetDirectoryName(targetPath)!);
var temporaryPath = $"{targetPath}.tmp-{Guid.NewGuid():N}";