Compare commits

...
Author SHA1 Message Date
Thorsten Sommer 7ba5f01388 Added a way for organizations to prohibit providers (#1105) 2026-10-22 18:57:12 +02:00
nilskruthoffandThorsten Sommer 052868b24e Added a settings dialog with organization-managed defaults to the Assistant Builder (#908)
Co-authored-by: Thorsten Sommer <SommerEngineering@users.noreply.github.com>
2026-10-22 15:26:04 +02:00
Thorsten Sommer 4695fb3fef Added the prototype badge to the mailbox dialog (#1103) 2026-10-22 14:48:25 +02:00
Peer HogeterpandThorsten Sommer 24c4c31035 Added profile, provider, and workspace preselection to chat templates (#1099)
Co-authored-by: Thorsten Sommer <SommerEngineering@users.noreply.github.com>
2026-10-22 14:15:10 +02:00
Thorsten Sommer ab9df99cf0 Added the page jump for PdfCraft on Windows, macOS, and Linux (#1102) 2026-10-22 12:08:42 +02:00
Thorsten Sommer ba99bf6fae Scaled down and converted images before sending them to AI models (#1101) 2026-10-22 10:57:04 +02:00
Thorsten Sommer 1c8632bbe0 Fixed the Batch Processing assistant with an output folder above the input folder (#1098) 2026-10-21 15:51:22 +02:00
Thorsten Sommer 7fbb894d5b Upgraded to Rust v1.99.1 (#1097) 2026-10-21 14:53:16 +02:00
Peer HogeterpandThorsten Sommer e019c24ea2 Allowed the Batch Processing assistant to process images (#1087)
Co-authored-by: Thorsten Sommer <SommerEngineering@users.noreply.github.com>
2026-10-21 13:52:26 +02:00
Paul KoudelkaandThorsten Sommer 5d6e48ef93 Allowed moving local data sources without embedding them again (#1091)
Co-authored-by: Thorsten Sommer <SommerEngineering@users.noreply.github.com>
2026-10-20 18:50:09 +02:00
153 changed files with 7908 additions and 769 deletions

No files matched your search

+1
View File
@@ -5,6 +5,7 @@ startup.env
libpdfium.dylib libpdfium.dylib
libpdfium.so libpdfium.so
libpdfium.dll libpdfium.dll
pdfium.dll
# Ignore qdrant database: # Ignore qdrant database:
qdrant-aarch64-apple-darwin qdrant-aarch64-apple-darwin
@@ -25,6 +25,10 @@
@T("Supported audio and video files are transcribed automatically without an additional dialog. Each transcript is stored next to its media file as '<media-file>.transcript.md' and reused when an interrupted run is continued.") @T("Supported audio and video files are transcribed automatically without an additional dialog. Each transcript is stored next to its media file as '<media-file>.transcript.md' and reused when an interrupted run is continued.")
</MudJustifiedText> </MudJustifiedText>
<MudJustifiedText Typo="Typo.body2" Class="mb-3">
@T("Images in JPG, PNG, WebP, GIF, BMP, or TIFF format are given to the model as images, so you need a model that understands images. If the selected model cannot read images, remove the image patterns, such as *.jpg or *.png, before you start.")
</MudJustifiedText>
<MudStack Class="mb-3"> <MudStack Class="mb-3">
<MudButton OnClick="@this.ToggleExpertSettings" Variant="Variant.Text" Color="Color.Primary" StartIcon="@Icons.Material.Filled.Tune"> <MudButton OnClick="@this.ToggleExpertSettings" Variant="Variant.Text" Color="Color.Primary" StartIcon="@Icons.Material.Filled.Tune">
@(this.showExpertSettings ? T("Hide Expert Settings") : T("Show Expert Settings")) @(this.showExpertSettings ? T("Hide Expert Settings") : T("Show Expert Settings"))
@@ -1,18 +1,90 @@
using AIStudio.Chat;
using AIStudio.Settings;
using AIStudio.Tools.Media; using AIStudio.Tools.Media;
using AIStudio.Tools.Rust;
namespace AIStudio.Assistants.BatchProcessing; namespace AIStudio.Assistants.BatchProcessing;
public partial class AssistantBatchProcessing public partial class AssistantBatchProcessing
{ {
/// <summary> /// <summary>
/// Loads a document through the Rust content stream or resolves a persistent /// What one file of the batch gives to the model: either text or an image.
/// transcript for an audio or video file.
/// </summary> /// </summary>
private Task<string?> LoadInputContentAsync(BatchProcessingFileResult fileResult, CancellationToken token) private readonly record struct BatchInput(string Text, FileAttachmentImage? Image);
/// <summary>
/// Loads a document through the Rust content stream, resolves a persistent
/// transcript for an audio or video file, or prepares an image for the model.
/// </summary>
/// <returns>The input for the model, or <c>null</c> when the file already got its final status.</returns>
private async Task<BatchInput?> LoadInputContentAsync(BatchProcessingFileResult fileResult, CancellationToken token)
{ {
return IsTranscribableMedia(fileResult.FilePath) if (IsImage(fileResult.FilePath))
? this.LoadMediaTranscriptAsync(fileResult, token) return await this.LoadImageAsync(fileResult, token) is { } image ? new BatchInput(string.Empty, image) : null;
: this.LoadDocumentContentAsync(fileResult, token);
var text = IsTranscribableMedia(fileResult.FilePath)
? await this.LoadMediaTranscriptAsync(fileResult, token)
: await this.LoadDocumentContentAsync(fileResult, token);
return text is null ? null : new BatchInput(text, null);
}
/// <summary>
/// Prepares an image for the model before the request is built.
/// </summary>
/// <remarks>
/// The request itself would only skip an image it cannot send, with a message, and ask the model
/// anyway. The model would then answer about nothing, and the file would count as done, so that
/// continuing the run would skip it as well. Preparing the image here lets it fail as a file
/// instead, with the reason, e.g., an image format AI Studio cannot decode. The request then finds
/// the prepared image in the cache of ModelImageService, so it is not prepared twice. A failed
/// image is processed again when the run is continued, e.g., with a model that can read images.
///
/// How large an image may be needs no check of its own either: the preparation scales it down
/// until it fits the limit of the provider, and fails only when even that is not enough.
///
/// How many images a model accepts needs no check of its own: every file gets a thread of its own,
/// see CallAIAsync, so each request carries exactly one image, in every round with the model. A
/// limit of one image is therefore always met, and SupportsImageInput already refuses a limit of zero.
///
/// Unlike the text of documents and transcripts, an image does not pass the prompt injection
/// filter of the runtime, which reads text only. Images in a chat do not either. Instructions
/// within an image therefore meet only the safeguards behind the filter: the system prompt tells
/// the model to ignore them, a thread which read internal content, such as a mailbox, may no
/// longer send data out, and without free address choice, its default, the Read Web Page tool
/// refuses an address which appears only in the image.
/// </remarks>
private async Task<FileAttachmentImage?> LoadImageAsync(BatchProcessingFileResult fileResult, CancellationToken token)
{
if (!this.ProviderSettings.SupportsImageInput())
{
this.FinishFileResult(fileResult, BatchProcessingFileStatus.FAILED, T("The selected model cannot read images. Select a model with image support to process this image."));
return null;
}
PreparedModelImage prepared;
FileAttachmentImage image;
try
{
image = new FileAttachmentImage(fileResult.FileName, fileResult.FilePath, new FileInfo(fileResult.FilePath).Length);
prepared = await this.ModelImageService.PrepareAsync(image, this.ProviderSettings, token);
}
catch (OperationCanceledException)
{
this.FinishFileResult(fileResult, BatchProcessingFileStatus.CANCELED, T("The batch run was canceled."));
return null;
}
catch (Exception e)
{
this.FinishFileResult(fileResult, BatchProcessingFileStatus.FAILED, string.Format(T("Was not able to read the file: {0}"), e.Message), e);
return null;
}
if (prepared.IsPrepared)
return image;
this.FinishFileResult(fileResult, BatchProcessingFileStatus.FAILED, prepared.ErrorCode.ToUserMessage(fileResult.FileName));
return null;
} }
private async Task<string?> LoadDocumentContentAsync(BatchProcessingFileResult fileResult, CancellationToken token) private async Task<string?> LoadDocumentContentAsync(BatchProcessingFileResult fileResult, CancellationToken token)
@@ -109,5 +181,7 @@ public partial class AssistantBatchProcessing
private static bool IsTranscribableMedia(string filePath) => PersistentMediaTranscript.IsMediaPath(filePath); private static bool IsTranscribableMedia(string filePath) => PersistentMediaTranscript.IsMediaPath(filePath);
private static bool IsImage(string filePath) => FileTypes.IsAllowedPath(filePath, FileTypes.IMAGE);
private static bool HasReusableTranscript(string mediaFilePath) => MediaSidecarTranscripts.HasReusableTranscript(mediaFilePath); private static bool HasReusableTranscript(string mediaFilePath) => MediaSidecarTranscripts.HasReusableTranscript(mediaFilePath);
} }
@@ -56,6 +56,8 @@ public partial class AssistantBatchProcessing
# Task description # Task description
You are a batch document processing agent. Each request contains exactly one DOCUMENT. You are a batch document processing agent. Each request contains exactly one DOCUMENT.
Your task is to process this DOCUMENT strictly according to the instructions below. Your task is to process this DOCUMENT strictly according to the instructions below.
A DOCUMENT may also be an image attached to the request. Then, the content of the
DOCUMENT is what the image shows, including any text within it.
# Scope and precedence # Scope and precedence
Use only information explicitly contained in the DOCUMENT and the instructions. Use only information explicitly contained in the DOCUMENT and the instructions.
@@ -88,11 +90,20 @@ public partial class AssistantBatchProcessing
"""; """;
} }
private static string BuildImageUserPrompt(string fileName)
{
return $"""
# DOCUMENT
File name: {fileName}
Content: The DOCUMENT is the image attached to this request.
""";
}
/// <param name="fileName">The name of the document being processed.</param> /// <param name="fileName">The name of the document being processed.</param>
/// <param name="fileContent">The content handed to the model.</param> /// <param name="input">The text or the image handed to the model.</param>
/// <param name="token">The cancellation token.</param> /// <param name="token">The cancellation token.</param>
/// <returns>The answer of the model, and which tools it used to get there.</returns> /// <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) private async Task<(string Answer, string UsedTools)> CallAIAsync(string fileName, BatchInput input, CancellationToken token)
{ {
// //
// Every file of the batch gets the tools the user picked for the job. The batch builds its // Every file of the batch gets the tools the user picked for the job. The batch builds its
@@ -120,10 +131,9 @@ public partial class AssistantBatchProcessing
}; };
this.AssignRuntimeIdentity(chatThread); this.AssignRuntimeIdentity(chatThread);
var userPrompt = new ContentText var userPrompt = input.Image is null
{ ? new ContentText { Text = BuildUserPrompt(fileName, input.Text) }
Text = BuildUserPrompt(fileName, fileContent), : new ContentText { Text = BuildImageUserPrompt(fileName), FileAttachments = [input.Image] };
};
chatThread.Blocks.Add(new ContentBlock chatThread.Blocks.Add(new ContentBlock
{ {
@@ -1,6 +1,8 @@
using System.Diagnostics; using System.Diagnostics;
using System.Globalization; using System.Globalization;
using AIStudio.Settings;
namespace AIStudio.Assistants.BatchProcessing; namespace AIStudio.Assistants.BatchProcessing;
public partial class AssistantBatchProcessing public partial class AssistantBatchProcessing
@@ -42,6 +44,7 @@ public partial class AssistantBatchProcessing
} }
this.PrepareFileResults(resolvedOutputDirectory, files, previousLog, previousResults); this.PrepareFileResults(resolvedOutputDirectory, files, previousLog, previousResults);
await this.WarnAboutUnreadableImagesAsync();
await this.CheckpointAssistantSession(); await this.CheckpointAssistantSession();
await this.RunBatchAsync(resolvedOutputDirectory); await this.RunBatchAsync(resolvedOutputDirectory);
} }
@@ -88,6 +91,27 @@ public partial class AssistantBatchProcessing
} }
} }
/// <summary>
/// Tells the user before the first file when the images of the run will fail, because the
/// selected model cannot read images.
/// </summary>
/// <remarks>
/// Counts only the images which this run still processes. Images done by an earlier run of a
/// continued batch keep their results and do not fail.
/// </remarks>
private async Task WarnAboutUnreadableImagesAsync()
{
var numImages = this.fileResults.Count(fileResult => fileResult.Status is not BatchProcessingFileStatus.DONE && IsImage(fileResult.FilePath));
if (numImages == 0 || this.ProviderSettings.SupportsImageInput())
return;
var warning = numImages == 1
? T("The selected model cannot read images, so the image among the selected files will fail. All other files are processed as usual.")
: string.Format(T("The selected model cannot read images, so the {0} images among the selected files will fail. All other files are processed as usual."), numImages);
await this.MessageBus.SendWarning(new(Icons.Material.Filled.ImageNotSupported, warning));
}
/// <summary> /// <summary>
/// Processes all documents which are not restored from a previous run. /// Processes all documents which are not restored from a previous run.
/// </summary> /// </summary>
@@ -188,14 +212,14 @@ public partial class AssistantBatchProcessing
/// </remarks> /// </remarks>
private async Task ProcessOneFileAsync(BatchProcessingFileResult fileResult, string resolvedOutputDirectory, CancellationToken token) private async Task ProcessOneFileAsync(BatchProcessingFileResult fileResult, string resolvedOutputDirectory, CancellationToken token)
{ {
var fileContent = await this.LoadInputContentAsync(fileResult, token); var input = await this.LoadInputContentAsync(fileResult, token);
if (fileContent is null) if (input is null)
return; return;
string aiAnswer; string aiAnswer;
try try
{ {
(aiAnswer, fileResult.UsedTools) = await this.CallAIAsync(fileResult.FileName, fileContent, token); (aiAnswer, fileResult.UsedTools) = await this.CallAIAsync(fileResult.FileName, input.Value, token);
} }
catch (OperationCanceledException) catch (OperationCanceledException)
{ {
@@ -217,6 +241,17 @@ public partial class AssistantBatchProcessing
return; return;
} }
//
// The request reads the image anew in every round with the model, e.g., after each tool
// call, and quietly leaves out an image which is gone by then. The answer would be about
// nothing and must not count as a result, so that continuing the run processes the image again:
//
if (input.Value.Image is { Exists: false })
{
this.FinishFileResult(fileResult, BatchProcessingFileStatus.FAILED, T("The image was no longer available while the model was asked about it."));
return;
}
if (string.IsNullOrWhiteSpace(aiAnswer)) if (string.IsNullOrWhiteSpace(aiAnswer))
{ {
this.FinishFileResult(fileResult, BatchProcessingFileStatus.FAILED, T("The AI answer was empty.")); this.FinishFileResult(fileResult, BatchProcessingFileStatus.FAILED, T("The AI answer was empty."));
@@ -1,3 +1,4 @@
using AIStudio.Settings;
using AIStudio.Tools.Media; using AIStudio.Tools.Media;
namespace AIStudio.Assistants.BatchProcessing; namespace AIStudio.Assistants.BatchProcessing;
@@ -82,39 +83,18 @@ public partial class AssistantBatchProcessing
private IReadOnlyList<string> FindInputFiles(string resolvedOutputDirectory) private IReadOnlyList<string> FindInputFiles(string resolvedOutputDirectory)
{ {
var selection = new FolderFileSelection(FileNamePatterns.Parse(this.filePatterns), FileExclusions.Parse(this.excludePatterns), this.includeSubdirectories, this.includeHiddenFilesAndFolders, this.maxFileSizeMegabytes); var selection = new FolderFileSelection(FileNamePatterns.Parse(this.filePatterns), FileExclusions.Parse(this.excludePatterns), this.includeSubdirectories, this.includeHiddenFilesAndFolders, this.maxFileSizeMegabytes);
var outputFolder = new BatchProcessingOutputFolder(this.inputDirectory, resolvedOutputDirectory, this.ResolveResultsFileName());
var files = new SortedSet<string>(StringComparer.OrdinalIgnoreCase); var files = new SortedSet<string>(StringComparer.OrdinalIgnoreCase);
this.numTooLargeFiles = 0; this.numTooLargeFiles = 0;
var normalizedInputDirectory = TrimDirectorySeparator(Path.GetFullPath(this.inputDirectory));
var normalizedOutputDirectory = TrimDirectorySeparator(Path.GetFullPath(resolvedOutputDirectory));
// When the output folder is a folder of its own, we skip everything
// inside it. When it is the input folder itself, we must not skip the
// whole folder: we would not find any document at all. We then skip
// our own output artifacts instead.
var isOutputSeparateFolder = !string.Equals(normalizedInputDirectory, normalizedOutputDirectory, StringComparison.OrdinalIgnoreCase);
// The separator is essential: without it, an output folder named 'out'
// would also exclude a document named 'output-notes.md':
var outputDirectoryPrefix = normalizedOutputDirectory + Path.DirectorySeparatorChar;
foreach (var file in selection.EnumerateCandidates( foreach (var file in selection.EnumerateCandidates(
this.inputDirectory, this.inputDirectory,
includeDirectory: directory => !isOutputSeparateFolder includeDirectory: outputFolder.Enters,
|| !string.Equals(TrimDirectorySeparator(Path.GetFullPath(directory)), normalizedOutputDirectory, StringComparison.OrdinalIgnoreCase),
reportInaccessibleDirectory: (directory, exception) => reportInaccessibleDirectory: (directory, exception) =>
throw new IOException($"Could not access directory '{directory}'.", exception))) throw new IOException($"Could not access directory '{directory}'.", exception)))
{ {
var normalizedFile = Path.GetFullPath(file); var normalizedFile = Path.GetFullPath(file);
if (!selection.Selects(normalizedFile) || IsTranscriptArtifact(normalizedFile)) if (!selection.Selects(normalizedFile) || IsTranscriptArtifact(normalizedFile) || outputFolder.IsOutputOfRun(normalizedFile))
continue;
if (isOutputSeparateFolder)
{
if (normalizedFile.StartsWith(outputDirectoryPrefix, StringComparison.OrdinalIgnoreCase))
continue;
}
else if (this.IsOwnOutputArtifact(normalizedFile))
continue; continue;
// //
@@ -136,25 +116,6 @@ public partial class AssistantBatchProcessing
return [.. files]; return [.. files];
} }
private static string TrimDirectorySeparator(string path) => path.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
/// <summary>
/// Checks whether a file is an output artifact of this assistant. We need
/// this when the output folder is the input folder: without it, the results
/// of a previous run would be processed as documents.
/// </summary>
private bool IsOwnOutputArtifact(string filePath)
{
var fileName = Path.GetFileName(filePath);
if (string.Equals(fileName, LOG_FILENAME, StringComparison.OrdinalIgnoreCase))
return true;
if (string.Equals(fileName, this.ResolveResultsFileName(), StringComparison.OrdinalIgnoreCase))
return true;
return fileName.EndsWith(RESULT_FILE_SUFFIX, StringComparison.OrdinalIgnoreCase);
}
/// <summary> /// <summary>
/// Checks for persistent or interrupted media transcript artifacts. They /// Checks for persistent or interrupted media transcript artifacts. They
/// always live beside their source file, independently of the output folder. /// always live beside their source file, independently of the output folder.
@@ -217,6 +178,17 @@ public partial class AssistantBatchProcessing
return null; return null;
} }
//
// A model that cannot read images would get the text of the request alone, without the
// image, and answer about nothing. Such images fail one by one, see LoadImageAsync, while the
// documents next to them are processed. A run of nothing but images does not start at all:
//
if (files.All(IsImage) && !this.ProviderSettings.SupportsImageInput())
{
this.AddInputIssue(T("All selected files are images, but the selected model cannot read images. Select a model with image support."));
return null;
}
try try
{ {
Directory.CreateDirectory(resolvedOutputDirectory); Directory.CreateDirectory(resolvedOutputDirectory);
@@ -15,10 +15,13 @@ public partial class AssistantBatchProcessing : AssistantBaseCore<SettingsDialog
[Inject] [Inject]
private PandocAvailabilityService PandocAvailability { get; init; } = null!; private PandocAvailabilityService PandocAvailability { get; init; } = null!;
[Inject]
private ModelImageService ModelImageService { get; init; } = null!;
private const string DEFAULT_OUTPUT_DIRECTORY_NAME = "ai-results"; private const string DEFAULT_OUTPUT_DIRECTORY_NAME = "ai-results";
private const string DEFAULT_RESULTS_FILENAME = "batch-results.csv"; private const string DEFAULT_RESULTS_FILENAME = "batch-results.csv";
private const string CSV_EXTENSION = ".csv"; private const string CSV_EXTENSION = ".csv";
private const string RESULT_FILE_SUFFIX = "_result"; internal const string RESULT_FILE_SUFFIX = "_result";
private const string TIME_FORMAT = "yyyy-MM-dd HH:mm:ss"; private const string TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
private const char LOG_SEPARATOR = ';'; private const char LOG_SEPARATOR = ';';
@@ -26,7 +29,7 @@ public partial class AssistantBatchProcessing : AssistantBaseCore<SettingsDialog
/// The name of the log file. It is fixed, so that a later batch run finds /// The name of the log file. It is fixed, so that a later batch run finds
/// the log of a previous run and can continue it. /// the log of a previous run and can continue it.
/// </summary> /// </summary>
private const string LOG_FILENAME = "log.csv"; internal const string LOG_FILENAME = "log.csv";
protected override Tools.Components Component => Tools.Components.BATCH_PROCESSING_ASSISTANT; protected override Tools.Components Component => Tools.Components.BATCH_PROCESSING_ASSISTANT;
@@ -290,13 +293,13 @@ public partial class AssistantBatchProcessing : AssistantBaseCore<SettingsDialog
var minimumLevel = this.GetMinimumConfidenceLevel(); var minimumLevel = this.GetMinimumConfidenceLevel();
var policyProvider = this.SettingsManager.GetPreselectedProvider(this.Component, this.selectedPolicy.PreselectedProvider); var policyProvider = this.SettingsManager.GetPreselectedProvider(this.Component, this.selectedPolicy.PreselectedProvider);
if (policyProvider != Settings.Provider.NONE if (policyProvider != Settings.Provider.NONE
&& policyProvider.UsedLLMProvider.GetConfidence(this.SettingsManager).Level >= minimumLevel) && this.SettingsManager.IsProviderUsable(policyProvider.UsedLLMProvider, minimumLevel))
this.ProviderSettings = policyProvider; this.ProviderSettings = policyProvider;
else else
{ {
var fallbackProvider = this.SettingsManager.GetPreselectedProvider(this.Component, usePreselectionBeforeCurrentProvider: true); var fallbackProvider = this.SettingsManager.GetPreselectedProvider(this.Component, usePreselectionBeforeCurrentProvider: true);
this.ProviderSettings = fallbackProvider != Settings.Provider.NONE this.ProviderSettings = fallbackProvider != Settings.Provider.NONE
&& fallbackProvider.UsedLLMProvider.GetConfidence(this.SettingsManager).Level >= minimumLevel && this.SettingsManager.IsProviderUsable(fallbackProvider.UsedLLMProvider, minimumLevel)
? fallbackProvider ? fallbackProvider
: Settings.Provider.NONE; : Settings.Provider.NONE;
} }
@@ -0,0 +1,88 @@
namespace AIStudio.Assistants.BatchProcessing;
/// <summary>
/// Tells the search for documents which of the files it finds a batch run wrote itself.
/// </summary>
/// <remarks>
/// A run writes its result files, its log, and its results table directly into the output folder.
/// What the search must leave out therefore depends on where this folder lies. Below the input
/// folder, the search leaves out all of it. When it is the input folder itself, leaving out the
/// whole folder would leave no document at all, so the search leaves out what a run writes, by
/// name. Anywhere else, above the input folder included, the search never comes across the output
/// folder and leaves out nothing.
/// </remarks>
/// <param name="inputDirectory">The folder the run reads its documents from.</param>
/// <param name="outputDirectory">The folder the run writes into.</param>
/// <param name="resultsFileName">The file name of the results table.</param>
internal sealed class BatchProcessingOutputFolder(string inputDirectory, string outputDirectory, string resultsFileName)
{
private readonly string fullOutputDirectory = Path.GetFullPath(outputDirectory);
private readonly bool isInputFolder = IsSameFolder(inputDirectory, outputDirectory);
//
// The same folder with a separator at its end would count as lying below itself:
//
private readonly bool liesBelowInputFolder = !IsSameFolder(inputDirectory, outputDirectory) && PathTools.IsInsideDirectory(inputDirectory, outputDirectory);
/// <summary>
/// Whether the search enters a subfolder of the input folder: every one but the output folder.
/// </summary>
/// <param name="directory">The subfolder.</param>
public bool Enters(string directory) => !this.liesBelowInputFolder || !IsSameFolder(directory, this.fullOutputDirectory);
/// <summary>
/// Whether a file the search found was written by a run instead of being a document.
/// </summary>
/// <param name="filePath">The full path of the file.</param>
public bool IsOutputOfRun(string filePath)
{
//
// Checked with the separator after the name of the folder: otherwise, an output folder
// named 'out' would also leave out a document named 'output-notes.md':
//
if (this.liesBelowInputFolder)
return PathTools.IsInsideDirectory(this.fullOutputDirectory, filePath);
//
// A run writes into the output folder itself, never into one of its subfolders:
//
return this.isInputFolder
&& Path.GetDirectoryName(Path.GetFullPath(filePath)) is { } fileDirectory
&& IsSameFolder(fileDirectory, this.fullOutputDirectory)
&& this.IsOwnOutputArtifact(filePath);
}
/// <summary>
/// Checks whether a file is an output artifact of the assistant. We need
/// this when the output folder is the input folder: without it, the results
/// of a previous run would be processed as documents.
/// </summary>
/// <remarks>
/// A result file is recognized by its name without the extension, e.g., report_result.md or
/// report_result_2.docx, cf. AssistantBatchProcessing.CreateResultFileName. Its extension does
/// not count, because the user may have chosen another file format since a previous run.
/// </remarks>
private bool IsOwnOutputArtifact(string filePath)
{
var fileName = Path.GetFileName(filePath);
if (string.Equals(fileName, AssistantBatchProcessing.LOG_FILENAME, StringComparison.OrdinalIgnoreCase)
|| string.Equals(fileName, resultsFileName, StringComparison.OrdinalIgnoreCase))
return true;
var stem = Path.GetFileNameWithoutExtension(fileName);
var suffixIndex = stem.LastIndexOf(AssistantBatchProcessing.RESULT_FILE_SUFFIX, StringComparison.OrdinalIgnoreCase);
if (suffixIndex < 0)
return false;
//
// A second result of the same name gets a counter, e.g., report_result_2.md:
//
var counter = stem[(suffixIndex + AssistantBatchProcessing.RESULT_FILE_SUFFIX.Length)..];
return counter.Length is 0 || (counter.Length > 1 && counter[0] is '_' && counter[1..].All(char.IsAsciiDigit));
}
private static bool IsSameFolder(string firstDirectory, string secondDirectory) => string.Equals(TrimDirectorySeparator(Path.GetFullPath(firstDirectory)), TrimDirectorySeparator(Path.GetFullPath(secondDirectory)), PathTools.COMPARISON);
private static string TrimDirectorySeparator(string path) => path.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
}
@@ -1,7 +1,7 @@
@attribute [Route(Routes.ASSISTANT_META_ASSISTANT)] @attribute [Route(Routes.ASSISTANT_META_ASSISTANT)]
@using AIStudio.Agents.AssistantAudit @using AIStudio.Agents.AssistantAudit
@using AIStudio.Tools.PluginSystem.Assistants.DataModel @using AIStudio.Tools.PluginSystem.Assistants.DataModel
@inherits AssistantBaseCore<AIStudio.Dialogs.Settings.NoSettingsPanel> @inherits AssistantBaseCore<AIStudio.Dialogs.Settings.SettingsDialogAssistantBuilder>
<PreviewBeta ApplyInnerScrollingFix="true"/> <PreviewBeta ApplyInnerScrollingFix="true"/>
@@ -11,7 +11,7 @@ using DialogOptions = AIStudio.Dialogs.DialogOptions;
namespace AIStudio.Assistants.Builder; namespace AIStudio.Assistants.Builder;
public partial class AssistantBuilder : AssistantBaseCore<NoSettingsPanel> public partial class AssistantBuilder : AssistantBaseCore<SettingsDialogAssistantBuilder>
{ {
[Inject] [Inject]
private IDialogService DialogService { get; init; } = null!; private IDialogService DialogService { get; init; } = null!;
@@ -82,8 +82,6 @@ public partial class AssistantBuilder : AssistantBaseCore<NoSettingsPanel>
// The result is the Lua code of a plugin, and a notice below it would make that code invalid: // The result is the Lua code of a plugin, and a notice below it would make that code invalid:
protected override bool AddsAIGeneratedContentDisclosure => false; protected override bool AddsAIGeneratedContentDisclosure => false;
protected override bool HasSettingsPanel => false;
protected override Func<string> Result2Copy => () => !string.IsNullOrWhiteSpace(this.generatedLuaAssistant) ? this.generatedLuaAssistant : this.generatedAssistantSpec; protected override Func<string> Result2Copy => () => !string.IsNullOrWhiteSpace(this.generatedLuaAssistant) ? this.generatedLuaAssistant : this.generatedAssistantSpec;
private BuilderStep step = BuilderStep.DESCRIBE; private BuilderStep step = BuilderStep.DESCRIBE;
@@ -251,8 +249,6 @@ public partial class AssistantBuilder : AssistantBaseCore<NoSettingsPanel>
this.launcherDataSourceIds = []; this.launcherDataSourceIds = [];
this.launcherToolIds = []; this.launcherToolIds = [];
this.selectedAssistantComponents = []; this.selectedAssistantComponents = [];
this.selectedOutputLanguage = CommonLanguages.AS_IS;
this.customOutputLanguage = string.Empty;
this.allowGeneratedAssistantProfiles = true; this.allowGeneratedAssistantProfiles = true;
this.extraRules = string.Empty; this.extraRules = string.Empty;
this.exampleRequest = string.Empty; this.exampleRequest = string.Empty;
@@ -260,9 +256,25 @@ public partial class AssistantBuilder : AssistantBaseCore<NoSettingsPanel>
this.reviewNotes = string.Empty; this.reviewNotes = string.Empty;
this.generatedLuaAssistant = string.Empty; this.generatedLuaAssistant = string.Empty;
this.ResetInstallFlow(); this.ResetInstallFlow();
if (!this.MightPreselectValues())
{
this.selectedOutputLanguage = CommonLanguages.AS_IS;
this.customOutputLanguage = string.Empty;
}
} }
protected override bool MightPreselectValues() => false; protected override bool MightPreselectValues()
{
if (this.SettingsManager.ConfigurationData.AssistantBuilder.PreselectOptions)
{
this.selectedOutputLanguage = this.SettingsManager.ConfigurationData.AssistantBuilder.PreselectedOutputLanguage;
this.customOutputLanguage = this.SettingsManager.ConfigurationData.AssistantBuilder.PreselectedOtherOutputLanguage;
return true;
}
return false;
}
/// <inheritdoc /> /// <inheritdoc />
protected override void CaptureCustomAssistantSessionState(AssistantSessionStateWriter state) protected override void CaptureCustomAssistantSessionState(AssistantSessionStateWriter state)
@@ -619,7 +619,7 @@ public partial class DocumentAnalysisAssistant : AssistantBaseCore<NoSettingsPan
{ {
// Keep the current provider if it still satisfies the minimum confidence: // Keep the current provider if it still satisfies the minimum confidence:
if (this.ProviderSettings != Settings.Provider.NONE && if (this.ProviderSettings != Settings.Provider.NONE &&
this.ProviderSettings.UsedLLMProvider.GetConfidence(this.SettingsManager).Level >= minimumLevel) this.SettingsManager.IsProviderUsable(this.ProviderSettings.UsedLLMProvider, minimumLevel))
{ {
this.CurrentProfile = this.ResolveProfileSelection(); this.CurrentProfile = this.ResolveProfileSelection();
return; return;
@@ -628,7 +628,7 @@ public partial class DocumentAnalysisAssistant : AssistantBaseCore<NoSettingsPan
// Try to apply the policy preselection: // Try to apply the policy preselection:
var policyProvider = this.SettingsManager.GetProviderById(this.selectedPolicy.PreselectedProvider); var policyProvider = this.SettingsManager.GetProviderById(this.selectedPolicy.PreselectedProvider);
if (policyProvider != Settings.Provider.NONE && policyProvider.UsedLLMProvider.GetConfidence(this.SettingsManager).Level >= minimumLevel) if (policyProvider != Settings.Provider.NONE && this.SettingsManager.IsProviderUsable(policyProvider.UsedLLMProvider, minimumLevel))
{ {
this.ProviderSettings = policyProvider; this.ProviderSettings = policyProvider;
this.CurrentProfile = this.ResolveProfileSelection(); this.CurrentProfile = this.ResolveProfileSelection();
@@ -637,7 +637,7 @@ public partial class DocumentAnalysisAssistant : AssistantBaseCore<NoSettingsPan
var fallbackProvider = this.SettingsManager.GetPreselectedProvider(this.Component, this.ProviderSettings.Id); var fallbackProvider = this.SettingsManager.GetPreselectedProvider(this.Component, this.ProviderSettings.Id);
if (fallbackProvider != Settings.Provider.NONE && if (fallbackProvider != Settings.Provider.NONE &&
fallbackProvider.UsedLLMProvider.GetConfidence(this.SettingsManager).Level < minimumLevel) !this.SettingsManager.IsProviderUsable(fallbackProvider.UsedLLMProvider, minimumLevel))
fallbackProvider = Settings.Provider.NONE; fallbackProvider = Settings.Provider.NONE;
this.ProviderSettings = fallbackProvider; this.ProviderSettings = fallbackProvider;
@@ -424,6 +424,9 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING
-- The selected folder does not exist. -- The selected folder does not exist.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T17705912"] = "The selected folder does not exist." UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T17705912"] = "The selected folder does not exist."
-- The image was no longer available while the model was asked about it.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T1805791865"] = "The image was no longer available while the model was asked about it."
-- Minimum pause between files -- Minimum pause between files
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T1829787634"] = "Minimum pause between files" UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T1829787634"] = "Minimum pause between files"
@@ -529,9 +532,15 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING
-- Was not able to write the result file: {0} -- Was not able to write the result file: {0}
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T2991295581"] = "Was not able to write the result file: {0}" UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T2991295581"] = "Was not able to write the result file: {0}"
-- Images in JPG, PNG, WebP, GIF, BMP, or TIFF format are given to the model as images, so you need a model that understands images. If the selected model cannot read images, remove the image patterns, such as *.jpg or *.png, before you start.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T3011193639"] = "Images in JPG, PNG, WebP, GIF, BMP, or TIFF format are given to the model as images, so you need a model that understands images. If the selected model cannot read images, remove the image patterns, such as *.jpg or *.png, before you start."
-- The batch run finished, but one file could not be processed. See the progress table and log for details. -- The batch run finished, but one file could not be processed. See the progress table and log for details.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T3201532790"] = "The batch run finished, but one file could not be processed. See the progress table and log for details." UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T3201532790"] = "The batch run finished, but one file could not be processed. See the progress table and log for details."
-- The selected model cannot read images, so the {0} images among the selected files will fail. All other files are processed as usual.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T324865195"] = "The selected model cannot read images, so the {0} images among the selected files will fail. All other files are processed as usual."
-- Maximum pause between files -- Maximum pause between files
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T3250003796"] = "Maximum pause between files" UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T3250003796"] = "Maximum pause between files"
@@ -550,6 +559,9 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING
-- Show Expert Settings -- Show Expert Settings
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T3361153305"] = "Show Expert Settings" UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T3361153305"] = "Show Expert Settings"
-- All selected files are images, but the selected model cannot read images. Select a model with image support.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T3365102967"] = "All selected files are images, but the selected model cannot read images. Select a model with image support."
-- Header of the result column (optional) -- Header of the result column (optional)
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T340994102"] = "Header of the result column (optional)" UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T340994102"] = "Header of the result column (optional)"
@@ -562,6 +574,9 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING
-- The file name of the CSV results table. We add the ending .csv when it is missing. When left empty, we use 'batch-results.csv'. -- The file name of the CSV results table. We add the ending .csv when it is missing. When left empty, we use 'batch-results.csv'.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T3439247329"] = "The file name of the CSV results table. We add the ending .csv when it is missing. When left empty, we use 'batch-results.csv'." UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T3439247329"] = "The file name of the CSV results table. We add the ending .csv when it is missing. When left empty, we use 'batch-results.csv'."
-- The selected model cannot read images. Select a model with image support to process this image.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T346986514"] = "The selected model cannot read images. Select a model with image support to process this image."
-- Document analysis policy -- Document analysis policy
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T3510564924"] = "Document analysis policy" UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T3510564924"] = "Document analysis policy"
@@ -676,6 +691,9 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING
-- 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. -- 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." 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."
-- The selected model cannot read images, so the image among the selected files will fail. All other files are processed as usual.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T9738678"] = "The selected model cannot read images, so the image among the selected files will fail. All other files are processed as usual."
-- Comma (,) -- Comma (,)
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::BATCHPROCESSINGCSVSEPARATOREXTENSIONS::T1676507543"] = "Comma (,)" UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::BATCHPROCESSINGCSVSEPARATOREXTENSIONS::T1676507543"] = "Comma (,)"
@@ -3625,6 +3643,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T1543974632"] = "The media
-- {0}, of which approx. {1} from tools -- {0}, of which approx. {1} from tools
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T1614399447"] = "{0}, of which approx. {1} from tools" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T1614399447"] = "{0}, of which approx. {1} from tools"
-- The use of this provider has been prohibited by your organization. Please select a different provider.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T1807387355"] = "The use of this provider has been prohibited by your organization. Please select a different provider."
-- Type your input here... -- Type your input here...
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T1849313532"] = "Type your input here..." UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T1849313532"] = "Type your input here..."
@@ -4120,9 +4141,6 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCEMANAGEMENT::T1084943026"] = "Yo
-- Automatic local data source refresh -- Automatic local data source refresh
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCEMANAGEMENT::T1208397349"] = "Automatic local data source refresh" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCEMANAGEMENT::T1208397349"] = "Automatic local data source refresh"
-- Edit Local Directory Data Source
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCEMANAGEMENT::T1215599168"] = "Edit Local Directory Data Source"
-- Refresh -- Refresh
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCEMANAGEMENT::T135637716"] = "Refresh" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCEMANAGEMENT::T135637716"] = "Refresh"
@@ -4150,12 +4168,6 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCEMANAGEMENT::T2067030738"] = "Ar
-- Local Directory Data Source Information -- Local Directory Data Source Information
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCEMANAGEMENT::T2146756020"] = "Local Directory Data Source Information" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCEMANAGEMENT::T2146756020"] = "Local Directory Data Source Information"
-- Edit ERI v1 Data Source
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCEMANAGEMENT::T221059217"] = "Edit ERI v1 Data Source"
-- Edit Local File Data Source
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCEMANAGEMENT::T2453292893"] = "Edit Local File Data Source"
-- Local data sources refresh when files change, mailboxes every 16 minutes. -- Local data sources refresh when files change, mailboxes every 16 minutes.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCEMANAGEMENT::T2511157475"] = "Local data sources refresh when files change, mailboxes every 16 minutes." UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCEMANAGEMENT::T2511157475"] = "Local data sources refresh when files change, mailboxes every 16 minutes."
@@ -4390,9 +4402,15 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DIRECTCHATLAUNCHERFORM::T1797236585"] = "
-- Workspace name (Optional) -- Workspace name (Optional)
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DIRECTCHATLAUNCHERFORM::T1873204484"] = "Workspace name (Optional)" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DIRECTCHATLAUNCHERFORM::T1873204484"] = "Workspace name (Optional)"
-- The chosen chat template preselects a provider of its own, and it wins over a selection made here as long as that provider can be used for chats.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DIRECTCHATLAUNCHERFORM::T1876510820"] = "The chosen chat template preselects a provider of its own, and it wins over a selection made here as long as that provider can be used for chats."
-- Use no profile -- Use no profile
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DIRECTCHATLAUNCHERFORM::T2205839602"] = "Use no profile" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DIRECTCHATLAUNCHERFORM::T2205839602"] = "Use no profile"
-- The chosen chat template keeps its chats in the workspace '{0}', so the tile opens its chat there instead. A workspace entered here wins over the one of the template.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DIRECTCHATLAUNCHERFORM::T2344103917"] = "The chosen chat template keeps its chats in the workspace '{0}', so the tile opens its chat there instead. A workspace entered here wins over the one of the template."
-- Existing workspace (Optional) -- Existing workspace (Optional)
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DIRECTCHATLAUNCHERFORM::T2364306588"] = "Existing workspace (Optional)" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DIRECTCHATLAUNCHERFORM::T2364306588"] = "Existing workspace (Optional)"
@@ -4414,6 +4432,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DIRECTCHATLAUNCHERFORM::T2901190527"] = "
-- Data sources (Optional) -- Data sources (Optional)
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DIRECTCHATLAUNCHERFORM::T3259309302"] = "Data sources (Optional)" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DIRECTCHATLAUNCHERFORM::T3259309302"] = "Data sources (Optional)"
-- The chosen chat template preselects a profile of its own, and it wins over a selection made here as long as that profile exists.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DIRECTCHATLAUNCHERFORM::T3419242912"] = "The chosen chat template preselects a profile of its own, and it wins over a selection made here as long as that profile exists."
-- Without a workspace, the tile opens a disappearing chat: it belongs to no workspace and is deleted according to your workspace maintenance settings. -- Without a workspace, the tile opens a disappearing chat: it belongs to no workspace and is deleted according to your workspace maintenance settings.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DIRECTCHATLAUNCHERFORM::T3611496116"] = "Without a workspace, the tile opens a disappearing chat: it belongs to no workspace and is deleted according to your workspace maintenance settings." UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DIRECTCHATLAUNCHERFORM::T3611496116"] = "Without a workspace, the tile opens a disappearing chat: it belongs to no workspace and is deleted according to your workspace maintenance settings."
@@ -4690,6 +4711,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::PROVIDERSELECTION::T2685487365"] = "Image
-- Speech input possible -- Speech input possible
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::PROVIDERSELECTION::T3005724142"] = "Speech input possible" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::PROVIDERSELECTION::T3005724142"] = "Speech input possible"
-- The use of all your LLM providers has been prohibited by your organization. Add a different provider in the app settings.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::PROVIDERSELECTION::T3506708164"] = "The use of all your LLM providers has been prohibited by your organization. Add a different provider in the app settings."
-- Uses reasoning (thinking) by default -- Uses reasoning (thinking) by default
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::PROVIDERSELECTION::T3891860124"] = "Uses reasoning (thinking) by default" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::PROVIDERSELECTION::T3891860124"] = "Uses reasoning (thinking) by default"
@@ -5260,6 +5284,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELCONFIDENCE::T45885
-- Not yet configured -- Not yet configured
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELCONFIDENCE::T48051324"] = "Not yet configured" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELCONFIDENCE::T48051324"] = "Not yet configured"
-- The use of this provider has been prohibited by your organization. Therefore, it is untrusted, and you cannot change its confidence level.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELCONFIDENCE::T489079668"] = "The use of this provider has been prohibited by your organization. Therefore, it is untrusted, and you cannot change its confidence level."
-- Do you want to always see how trustworthy your providers are? This way, you stay in control of which provider you send your data to. You can choose a common schema or configure the trust levels for each provider yourself. -- Do you want to always see how trustworthy your providers are? This way, you stay in control of which provider you send your data to. You can choose a common schema or configure the trust levels for each provider yourself.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELCONFIDENCE::T700839804"] = "Do you want to always see how trustworthy your providers are? This way, you stay in control of which provider you send your data to. You can choose a common schema or configure the trust levels for each provider yourself." UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELCONFIDENCE::T700839804"] = "Do you want to always see how trustworthy your providers are? This way, you stay in control of which provider you send your data to. You can choose a common schema or configure the trust levels for each provider yourself."
@@ -5278,6 +5305,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T13870
-- Delete -- Delete
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T1469573738"] = "Delete" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T1469573738"] = "Delete"
-- The use of this embedding provider has been prohibited by your organization.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T1509600117"] = "The use of this embedding provider has been prohibited by your organization."
-- Embed text -- Embed text
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T1644934561"] = "Embed text" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T1644934561"] = "Embed text"
@@ -5428,6 +5458,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T341592
-- Actions -- Actions
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T3865031940"] = "Actions" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T3865031940"] = "Actions"
-- The use of this provider has been prohibited by your organization.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T4229691938"] = "The use of this provider has been prohibited by your organization."
-- Delete LLM Provider -- Delete LLM Provider
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T4269256234"] = "Delete LLM Provider" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T4269256234"] = "Delete LLM Provider"
@@ -5491,6 +5524,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T16
-- Uses the provider-configured model -- Uses the provider-configured model
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T1760715963"] = "Uses the provider-configured model" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T1760715963"] = "Uses the provider-configured model"
-- The use of this transcription provider has been prohibited by your organization.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T2059509230"] = "The use of this transcription provider has been prohibited by your organization."
-- Add Transcription Provider -- Add Transcription Provider
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T2066315685"] = "Add Transcription Provider" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T2066315685"] = "Add Transcription Provider"
@@ -6178,12 +6214,21 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T1396308587"] = "The cha
-- The same goes for your data. A chat template may bring its own data source options, which includes leaving the choice of sources to the AI. Without them, those chats start with the data source options from your chat options. -- The same goes for your data. A chat template may bring its own data source options, which includes leaving the choice of sources to the AI. Without them, those chats start with the data source options from your chat options.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T1442266827"] = "The same goes for your data. A chat template may bring its own data source options, which includes leaving the choice of sources to the AI. Without them, those chats start with the data source options from your chat options." UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T1442266827"] = "The same goes for your data. A chat template may bring its own data source options, which includes leaving the choice of sources to the AI. Without them, those chats start with the data source options from your chat options."
-- Preselected provider
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T1469984996"] = "Preselected provider"
-- Please enter a name for the chat template. -- Please enter a name for the chat template.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T1548747185"] = "Please enter a name for the chat template." UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T1548747185"] = "Please enter a name for the chat template."
-- A chat template may decide which provider, and with it which model, a chat starts with. Without such a decision, those chats start with the provider they would start with anyway. The provider stays changeable in the chat, and it must meet the confidence level required for chats.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T171066641"] = "A chat template may decide which provider, and with it which model, a chat starts with. Without such a decision, those chats start with the provider they would start with anyway. The provider stays changeable in the chat, and it must meet the confidence level required for chats."
-- Yes, this template decides which data a chat starts with -- Yes, this template decides which data a chat starts with
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T17861006"] = "Yes, this template decides which data a chat starts with" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T17861006"] = "Yes, this template decides which data a chat starts with"
-- Preselected profile
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T178965494"] = "Preselected profile"
-- Enter the full path of a file on this computer, or remove the attachment. -- Enter the full path of a file on this computer, or remove the attachment.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T1798848159"] = "Enter the full path of a file on this computer, or remove the attachment." UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T1798848159"] = "Enter the full path of a file on this computer, or remove the attachment."
@@ -6193,12 +6238,21 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T1837026610"] = "Load pr
-- Update -- Update
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T1847791252"] = "Update" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T1847791252"] = "Update"
-- Yes, this template decides which profile a chat starts with
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T1865005833"] = "Yes, this template decides which profile a chat starts with"
-- The chat template name must not exceed 40 characters. -- The chat template name must not exceed 40 characters.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T204496403"] = "The chat template name must not exceed 40 characters." UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T204496403"] = "The chat template name must not exceed 40 characters."
-- Profile Usage -- Profile Usage
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2147062613"] = "Profile Usage" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2147062613"] = "Profile Usage"
-- Use no profile
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2205839602"] = "Use no profile"
-- Yes, chats with this template go into a workspace
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2205963533"] = "Yes, chats with this template go into a workspace"
-- Relink attachment: {0} -- Relink attachment: {0}
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2234793579"] = "Relink attachment: {0}" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2234793579"] = "Relink attachment: {0}"
@@ -6208,6 +6262,12 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2292424657"] = "Add mes
-- File Attachments -- File Attachments
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2294745309"] = "File Attachments" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2294745309"] = "File Attachments"
-- No, chats go where they would go anyway
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2355334446"] = "No, chats go where they would go anyway"
-- Existing workspace (Optional)
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2364306588"] = "Existing workspace (Optional)"
-- Role -- Role
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2418769465"] = "Role" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2418769465"] = "Role"
@@ -6220,6 +6280,12 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2499909372"] = "Tools"
-- What predefined user input do you want to use? -- What predefined user input do you want to use?
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2501284417"] = "What predefined user input do you want to use?" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2501284417"] = "What predefined user input do you want to use?"
-- Please enter the workspace for the chats of this template, or switch the preselection off.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2504276031"] = "Please enter the workspace for the chats of this template, or switch the preselection off."
-- Does this chat template preselect a provider?
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2541393632"] = "Does this chat template preselect a provider?"
-- Tell the AI your system prompt. -- Tell the AI your system prompt.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2552381261"] = "Tell the AI your system prompt." UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2552381261"] = "Tell the AI your system prompt."
@@ -6229,6 +6295,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2579080722"] = "No, pro
-- You might want to predefine a first message that will be copied into the user prompt, when you use this chat template. This message could for example be a blueprint for a structured message that this chat template is defined to work with. -- You might want to predefine a first message that will be copied into the user prompt, when you use this chat template. This message could for example be a blueprint for a structured message that this chat template is defined to work with.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2579208700"] = "You might want to predefine a first message that will be copied into the user prompt, when you use this chat template. This message could for example be a blueprint for a structured message that this chat template is defined to work with." UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2579208700"] = "You might want to predefine a first message that will be copied into the user prompt, when you use this chat template. This message could for example be a blueprint for a structured message that this chat template is defined to work with."
-- Does this chat template preselect a profile?
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2602340928"] = "Does this chat template preselect a profile?"
-- Predefined User Input -- Predefined User Input
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2607897066"] = "Predefined User Input" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2607897066"] = "Predefined User Input"
@@ -6244,9 +6313,15 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2719480499"] = "The mes
-- Chat Template Name -- Chat Template Name
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T275026390"] = "Chat Template Name" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T275026390"] = "Chat Template Name"
-- Workspaces are switched off in your workspace settings. While they are off, chats with this template belong to no workspace, and they do not move into a workspace when you switch workspaces on again. You can change this choice once workspaces are on.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2828180414"] = "Workspaces are switched off in your workspace settings. While they are off, chats with this template belong to no workspace, and they do not move into a workspace when you switch workspaces on again. You can change this choice once workspaces are on."
-- Please relink the attachment '{0}' to a file on this computer or remove it before you save. -- Please relink the attachment '{0}' to a file on this computer or remove it before you save.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2884684079"] = "Please relink the attachment '{0}' to a file on this computer or remove it before you save." UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2884684079"] = "Please relink the attachment '{0}' to a file on this computer or remove it before you save."
-- Workspace name
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T295876489"] = "Workspace name"
-- Commit Changes -- Commit Changes
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2996302517"] = "Commit Changes" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2996302517"] = "Commit Changes"
@@ -6262,21 +6337,42 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T3108503534"] = "You can
-- Are you unsure which system prompt to use? You might start with the default system prompt that AI Studio uses for all chats. -- Are you unsure which system prompt to use? You might start with the default system prompt that AI Studio uses for all chats.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T3127437308"] = "Are you unsure which system prompt to use? You might start with the default system prompt that AI Studio uses for all chats." UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T3127437308"] = "Are you unsure which system prompt to use? You might start with the default system prompt that AI Studio uses for all chats."
-- A chat template may also decide which profile a chat starts with, including starting without one. Without such a decision, those chats start with the profile they would start with anyway. The profile stays changeable in the chat.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T313529011"] = "A chat template may also decide which profile a chat starts with, including starting without one. Without such a decision, those chats start with the profile they would start with anyway. The profile stays changeable in the chat."
-- The profile this chat template preselects does not exist on this computer. Chats started with it keep their usual profile.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T3170335831"] = "The profile this chat template preselects does not exist on this computer. Chats started with it keep their usual profile."
-- Using some chat templates in tandem with profiles might cause issues. Therefore, you might prohibit the usage of profiles here. -- Using some chat templates in tandem with profiles might cause issues. Therefore, you might prohibit the usage of profiles here.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T3227981830"] = "Using some chat templates in tandem with profiles might cause issues. Therefore, you might prohibit the usage of profiles here." UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T3227981830"] = "Using some chat templates in tandem with profiles might cause issues. Therefore, you might prohibit the usage of profiles here."
-- Choose an existing workspace or enter a name that should be created when a chat with this template starts.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T3256439227"] = "Choose an existing workspace or enter a name that should be created when a chat with this template starts."
-- No, chats keep the data source options from your chat options -- No, chats keep the data source options from your chat options
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T3268470871"] = "No, chats keep the data source options from your chat options" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T3268470871"] = "No, chats keep the data source options from your chat options"
-- A chat template may decide which tools a chat starts with. Without such a decision, those chats start with the tools you chose as your default in the chat options. Deciding and then picking nothing is a different statement: such chats start with no tool at all, no matter what your default says. -- A chat template may decide which tools a chat starts with. Without such a decision, those chats start with the tools you chose as your default in the chat options. Deciding and then picking nothing is a different statement: such chats start with no tool at all, no matter what your default says.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T3329415439"] = "A chat template may decide which tools a chat starts with. Without such a decision, those chats start with the tools you chose as your default in the chat options. Deciding and then picking nothing is a different statement: such chats start with no tool at all, no matter what your default says." UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T3329415439"] = "A chat template may decide which tools a chat starts with. Without such a decision, those chats start with the tools you chose as your default in the chat options. Deciding and then picking nothing is a different statement: such chats start with no tool at all, no matter what your default says."
-- A chat template may keep its chats in a workspace of its own, which is created when it does not exist yet. This applies whenever a chat would otherwise be a disappearing chat, for example, when you start a new chat outside the workspaces or pick this template in a new chat. A chat you start in a workspace on purpose, for example, from the workspace list, stays in that workspace.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T3352941260"] = "A chat template may keep its chats in a workspace of its own, which is created when it does not exist yet. This applies whenever a chat would otherwise be a disappearing chat, for example, when you start a new chat outside the workspaces or pick this template in a new chat. A chat you start in a workspace on purpose, for example, from the workspace list, stays in that workspace."
-- Add a message -- Add a message
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T3372872324"] = "Add a message" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T3372872324"] = "Add a message"
-- Yes, this template decides which provider a chat starts with
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T3422337353"] = "Yes, this template decides which provider a chat starts with"
-- Close -- Close
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T3448155331"] = "Close" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T3448155331"] = "Close"
-- No, chats keep their usual profile
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T3465925873"] = "No, chats keep their usual profile"
-- The provider this chat template preselects does not exist on this computer or does not meet the confidence level required for chats. Chats started with it keep their usual provider.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T349281189"] = "The provider this chat template preselects does not exist on this computer or does not meet the confidence level required for chats. Chats started with it keep their usual provider."
-- Unsupported content type -- Unsupported content type
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T3570316759"] = "Unsupported content type" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T3570316759"] = "Unsupported content type"
@@ -6313,12 +6409,18 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T4051106111"] = "Use the
-- Tell the AI your predefined user input. -- Tell the AI your predefined user input.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T4052406705"] = "Tell the AI your predefined user input." UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T4052406705"] = "Tell the AI your predefined user input."
-- Does this chat template keep its chats in a workspace?
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T4057825866"] = "Does this chat template keep its chats in a workspace?"
-- Some attachments of this chat template are not on this computer. Please relink or remove them before you add the template. -- Some attachments of this chat template are not on this computer. Please relink or remove them before you add the template.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T4194591607"] = "Some attachments of this chat template are not on this computer. Please relink or remove them before you add the template." UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T4194591607"] = "Some attachments of this chat template are not on this computer. Please relink or remove them before you add the template."
-- Create your custom chat template to tailor the LLM's behavior for specific tasks or domains. Define a custom system prompt and provide an example conversation to design an AI experience perfectly suited to your requirements. -- Create your custom chat template to tailor the LLM's behavior for specific tasks or domains. Define a custom system prompt and provide an example conversation to design an AI experience perfectly suited to your requirements.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T4199560726"] = "Create your custom chat template to tailor the LLM's behavior for specific tasks or domains. Define a custom system prompt and provide an example conversation to design an AI experience perfectly suited to your requirements." UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T4199560726"] = "Create your custom chat template to tailor the LLM's behavior for specific tasks or domains. Define a custom system prompt and provide an example conversation to design an AI experience perfectly suited to your requirements."
-- Please choose the provider this chat template preselects, or switch the preselection off.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T442309864"] = "Please choose the provider this chat template preselects, or switch the preselection off."
-- Enter a message -- Enter a message
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T446374405"] = "Enter a message" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T446374405"] = "Enter a message"
@@ -6331,15 +6433,24 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T628396066"] = "System P
-- A chat started with this template begins with these data sources and options. All of it stays changeable in the chat itself. -- A chat started with this template begins with these data sources and options. All of it stays changeable in the chat itself.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T650711085"] = "A chat started with this template begins with these data sources and options. All of it stays changeable in the chat itself." UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T650711085"] = "A chat started with this template begins with these data sources and options. All of it stays changeable in the chat itself."
-- Workspace
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T658612054"] = "Workspace"
-- Remove this attachment -- Remove this attachment
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T70791272"] = "Remove this attachment" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T70791272"] = "Remove this attachment"
-- The selection stays changeable in the chat, and every tool still has to meet the confidence requirements of the provider in use. -- The selection stays changeable in the chat, and every tool still has to meet the confidence requirements of the provider in use.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T722788866"] = "The selection stays changeable in the chat, and every tool still has to meet the confidence requirements of the provider in use." UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T722788866"] = "The selection stays changeable in the chat, and every tool still has to meet the confidence requirements of the provider in use."
-- No, chats keep their usual provider
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T787085105"] = "No, chats keep their usual provider"
-- Allow the use of profiles together with this chat template? -- Allow the use of profiles together with this chat template?
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T823785464"] = "Allow the use of profiles together with this chat template?" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T823785464"] = "Allow the use of profiles together with this chat template?"
-- Provider
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T900237532"] = "Provider"
-- Cancel -- Cancel
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T900713019"] = "Cancel" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T900713019"] = "Cancel"
@@ -6805,9 +6916,6 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T2460883298"
-- Add -- Add
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T2646845972"] = "Add" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T2646845972"] = "Add"
-- The documents of this data source are already prepared, so its folder cannot be changed. Another folder holds other documents, which makes it another data source: please add one for it. The embedding provider below can be changed.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T2686150329"] = "The documents of this data source are already prepared, so its folder cannot be changed. Another folder holds other documents, which makes it another data source: please add one for it. The embedding provider below can be changed."
-- The embedding you selected runs locally or in your organization. Your data is not sent to the cloud. -- The embedding you selected runs locally or in your organization. Your data is not sent to the cloud.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T2814869210"] = "The embedding you selected runs locally or in your organization. Your data is not sent to the cloud." UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T2814869210"] = "The embedding you selected runs locally or in your organization. Your data is not sent to the cloud."
@@ -7066,9 +7174,6 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T787118522"] = "T
-- Data Source Name -- Data Source Name
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T813773421"] = "Data Source Name" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T813773421"] = "Data Source Name"
-- The documents of this data source are already prepared, so its file cannot be changed. Another file holds other content, which makes it another data source: please add one for it. The embedding provider below can be changed.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T890383764"] = "The documents of this data source are already prepared, so its file cannot be changed. Another file holds other content, which makes it another data source: please add one for it. The embedding provider below can be changed."
-- Cancel -- Cancel
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T900713019"] = "Cancel" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T900713019"] = "Cancel"
@@ -7546,6 +7651,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T1727440780"] = "Ho
-- Load -- Load
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T1756340745"] = "Load" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T1756340745"] = "Load"
-- The use of this provider has been prohibited by your organization. Please select a different provider.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T1807387355"] = "The use of this provider has been prohibited by your organization. Please select a different provider."
-- Update -- Update
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T1847791252"] = "Update" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T1847791252"] = "Update"
@@ -8071,6 +8179,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T1727440780"] = "Hostname"
-- Always on -- Always on
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T1761671861"] = "Always on" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T1761671861"] = "Always on"
-- The use of this provider has been prohibited by your organization. Please select a different provider.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T1807387355"] = "The use of this provider has been prohibited by your organization. Please select a different provider."
-- Reset -- Reset
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T180921696"] = "Reset" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T180921696"] = "Reset"
@@ -8578,6 +8689,33 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGASSISTANTBIAS::T6790
-- When enabled, you can preselect options. This is might be useful when you prefer a specific language or LLM model. -- When enabled, you can preselect options. This is might be useful when you prefer a specific language or LLM model.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGASSISTANTBIAS::T711745239"] = "When enabled, you can preselect options. This is might be useful when you prefer a specific language or LLM model." UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGASSISTANTBIAS::T711745239"] = "When enabled, you can preselect options. This is might be useful when you prefer a specific language or LLM model."
-- When enabled, the Assistant Builder starts with the defaults configured below. When disabled, it uses the app-wide default provider and preselects no output language.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGASSISTANTBUILDER::T1496952909"] = "When enabled, the Assistant Builder starts with the defaults configured below. When disabled, it uses the app-wide default provider and preselects no output language."
-- Preselect Assistant Builder options?
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGASSISTANTBUILDER::T2127606148"] = "Preselect Assistant Builder options?"
-- Default custom output language
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGASSISTANTBUILDER::T3360584376"] = "Default custom output language"
-- Close
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGASSISTANTBUILDER::T3448155331"] = "Close"
-- Default output language
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGASSISTANTBUILDER::T3652922567"] = "Default output language"
-- Assistant: Assistant Builder defaults
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGASSISTANTBUILDER::T3668274570"] = "Assistant: Assistant Builder defaults"
-- Assistant Builder options are preselected
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGASSISTANTBUILDER::T3962364100"] = "Assistant Builder options are preselected"
-- No Assistant Builder options are preselected
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGASSISTANTBUILDER::T4104323209"] = "No Assistant Builder options are preselected"
-- The language in which the assistants you build answer.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGASSISTANTBUILDER::T42126731"] = "The language in which the assistants you build answer."
-- Default minimum pause between files -- Default minimum pause between files
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T1008440099"] = "Default minimum pause between files" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T1008440099"] = "Default minimum pause between files"
@@ -8623,6 +8761,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T22
-- Hidden files and folders are included -- Hidden files and folders are included
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T2288334048"] = "Hidden files and folders are included" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T2288334048"] = "Hidden files and folders are included"
-- Separate multiple file patterns with a semicolon, e.g., *.pdf;*.docx. The standard patterns include all supported audio and video formats as well as images in JPG, PNG, WebP, GIF, BMP, and TIFF format.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T232588799"] = "Separate multiple file patterns with a semicolon, e.g., *.pdf;*.docx. The standard patterns include all supported audio and video formats as well as images in JPG, PNG, WebP, GIF, BMP, and TIFF format."
-- The lower end of the random pause interval. AI Studio never allows less than 6 seconds. -- 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." UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T237774509"] = "The lower end of the random pause interval. AI Studio never allows less than 6 seconds."
@@ -8635,9 +8776,6 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T24
-- When enabled, new batch runs start with the defaults configured below. -- 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." UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T2592677194"] = "When enabled, new batch runs start with the defaults configured below."
-- Separate multiple file patterns with a semicolon, e.g., *.pdf;*.docx. The standard patterns include all supported audio and video formats.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T2594325620"] = "Separate multiple file patterns with a semicolon, e.g., *.pdf;*.docx. The standard patterns include all supported audio and video formats."
-- Subfolders are included -- Subfolders are included
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T2607092632"] = "Subfolders are included" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T2607092632"] = "Subfolders are included"
@@ -9823,6 +9961,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T1727440780"] =
-- Load -- Load
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T1756340745"] = "Load" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T1756340745"] = "Load"
-- The use of this provider has been prohibited by your organization. Please select a different provider.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T1807387355"] = "The use of this provider has been prohibited by your organization. Please select a different provider."
-- Update -- Update
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T1847791252"] = "Update" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T1847791252"] = "Update"
@@ -10372,6 +10513,9 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::EMBEDDINGS::T819555436"] = "Failed mails: {0}"
-- Skipped -- Skipped
UI_TEXT_CONTENT["AISTUDIO::PAGES::EMBEDDINGS::T852993367"] = "Skipped" UI_TEXT_CONTENT["AISTUDIO::PAGES::EMBEDDINGS::T852993367"] = "Skipped"
-- Edit this data source
UI_TEXT_CONTENT["AISTUDIO::PAGES::EMBEDDINGS::T878700886"] = "Edit this data source"
-- Open the transcription settings -- Open the transcription settings
UI_TEXT_CONTENT["AISTUDIO::PAGES::EMBEDDINGS::T905182399"] = "Open the transcription settings" UI_TEXT_CONTENT["AISTUDIO::PAGES::EMBEDDINGS::T905182399"] = "Open the transcription settings"
@@ -11863,6 +12007,9 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::AIJOBS::AIJOBSERVICE::T1578005752"] = "The sel
-- The selected provider is not allowed for this chat. -- The selected provider is not allowed for this chat.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::AIJOBS::AIJOBSERVICE::T174545104"] = "The selected provider is not allowed for this chat." UI_TEXT_CONTENT["AISTUDIO::TOOLS::AIJOBS::AIJOBSERVICE::T174545104"] = "The selected provider is not allowed for this chat."
-- The use of this provider has been prohibited by your organization. Please select a different provider.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::AIJOBS::AIJOBSERVICE::T1807387355"] = "The use of this provider has been prohibited by your organization. Please select a different provider."
-- The AI job failed. The message is: '{0}' -- The AI job failed. The message is: '{0}'
UI_TEXT_CONTENT["AISTUDIO::TOOLS::AIJOBS::AIJOBSERVICE::T237448388"] = "The AI job failed. The message is: '{0}'" UI_TEXT_CONTENT["AISTUDIO::TOOLS::AIJOBS::AIJOBSERVICE::T237448388"] = "The AI job failed. The message is: '{0}'"
@@ -12208,18 +12355,39 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATABASES::VECTORSTORE::QDRANTEDGECLIENTIMPLEM
-- Qdrant Edge is not available. -- Qdrant Edge is not available.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATABASES::VECTORSTORE::QDRANTEDGECLIENTIMPLEMENTATION::T744445696"] = "Qdrant Edge is not available." UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATABASES::VECTORSTORE::QDRANTEDGECLIENTIMPLEMENTATION::T744445696"] = "Qdrant Edge is not available."
-- Edit Local Directory Data Source
UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATASOURCEEDITING::T1215599168"] = "Edit Local Directory Data Source"
-- Edit ERI v1 Data Source
UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATASOURCEEDITING::T221059217"] = "Edit ERI v1 Data Source"
-- Edit Local File Data Source
UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATASOURCEEDITING::T2453292893"] = "Edit Local File Data Source"
-- Every other file goes to your embedding provider once more, which takes a while with many files. Files missing at the new location are removed from the index, while your files on disk stay as they are.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATASOURCEREINDEXWARNING::T1555760974"] = "Every other file goes to your embedding provider once more, which takes a while with many files. Files missing at the new location are removed from the index, while your files on disk stay as they are."
-- AI Studio keeps what it has already prepared for every file it finds unchanged at the new location, that is, of the same type and with the same size and modification time. In a folder, such a file also has to keep its name and the subfolder it is in.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATASOURCEREINDEXWARNING::T2205693550"] = "AI Studio keeps what it has already prepared for every file it finds unchanged at the new location, that is, of the same type and with the same size and modification time. In a folder, such a file also has to keep its name and the subfolder it is in."
-- They keep answering keyword searches, but searching them by meaning stops working, and no further documents can be prepared for them. The ones which are already prepared stay tied to this provider as well, so you cannot simply move them to another one. -- They keep answering keyword searches, but searching them by meaning stops working, and no further documents can be prepared for them. The ones which are already prepared stay tied to this provider as well, so you cannot simply move them to another one.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATASOURCEREINDEXWARNING::T2343773457"] = "They keep answering keyword searches, but searching them by meaning stops working, and no further documents can be prepared for them. The ones which are already prepared stay tied to this provider as well, so you cannot simply move them to another one." UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATASOURCEREINDEXWARNING::T2343773457"] = "They keep answering keyword searches, but searching them by meaning stops working, and no further documents can be prepared for them. The ones which are already prepared stay tied to this provider as well, so you cannot simply move them to another one."
-- and {0} more. -- and {0} more.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATASOURCEREINDEXWARNING::T2519847121"] = "and {0} more." UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATASOURCEREINDEXWARNING::T2519847121"] = "and {0} more."
-- Your embedding provider runs in the cloud, so preparing files again costs money.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATASOURCEREINDEXWARNING::T2635623258"] = "Your embedding provider runs in the cloud, so preparing files again costs money."
-- This change makes the prepared documents of the following data sources unusable ({0}): -- This change makes the prepared documents of the following data sources unusable ({0}):
UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATASOURCEREINDEXWARNING::T3337378891"] = "This change makes the prepared documents of the following data sources unusable ({0}):" UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATASOURCEREINDEXWARNING::T3337378891"] = "This change makes the prepared documents of the following data sources unusable ({0}):"
-- Do you want to apply this change anyway? -- Do you want to apply this change anyway?
UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATASOURCEREINDEXWARNING::T3419411838"] = "Do you want to apply this change anyway?" UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATASOURCEREINDEXWARNING::T3419411838"] = "Do you want to apply this change anyway?"
-- New Location for This Data Source
UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATASOURCEREINDEXWARNING::T3462449568"] = "New Location for This Data Source"
-- Documents Will Be Prepared Again -- Documents Will Be Prepared Again
UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATASOURCEREINDEXWARNING::T737291513"] = "Documents Will Be Prepared Again" UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATASOURCEREINDEXWARNING::T737291513"] = "Documents Will Be Prepared Again"
@@ -12967,6 +13135,9 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::PLUGINASSISTANTSECUR
-- The token or password of the data source cannot be decrypted on this computer. Such data sources can only be imported where the encryption secret of your organization is configured. -- The token or password of the data source cannot be decrypted on this computer. Such data sources can only be imported where the encryption secret of your organization is configured.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETIMPORT::T186946778"] = "The token or password of the data source cannot be decrypted on this computer. Such data sources can only be imported where the encryption secret of your organization is configured." UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETIMPORT::T186946778"] = "The token or password of the data source cannot be decrypted on this computer. Such data sources can only be imported where the encryption secret of your organization is configured."
-- The chat template preselects the provider '{0}', which does not meet the confidence level required for chats. Chats started with it use your usual provider instead.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETIMPORT::T190892810"] = "The chat template preselects the provider '{0}', which does not meet the confidence level required for chats. Chats started with it use your usual provider instead."
-- Data sources which sign in with Kerberos cannot be imported yet. Please add them yourself. -- Data sources which sign in with Kerberos cannot be imported yet. Please add them yourself.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETIMPORT::T206419407"] = "Data sources which sign in with Kerberos cannot be imported yet. Please add them yourself." UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETIMPORT::T206419407"] = "Data sources which sign in with Kerberos cannot be imported yet. Please add them yourself."
@@ -12979,6 +13150,12 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETIMPORT::T260
-- The chat template preselects tools which are not available on this computer: {0}. -- The chat template preselects tools which are not available on this computer: {0}.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETIMPORT::T2693686582"] = "The chat template preselects tools which are not available on this computer: {0}." UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETIMPORT::T2693686582"] = "The chat template preselects tools which are not available on this computer: {0}."
-- The chat template preselects a profile which does not exist on this computer: {0}. Chats started with it use your usual profile instead.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETIMPORT::T2735390326"] = "The chat template preselects a profile which does not exist on this computer: {0}. Chats started with it use your usual profile instead."
-- The chat template preselects a provider which does not exist on this computer: {0}. Chats started with it use your usual provider instead.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETIMPORT::T3321789654"] = "The chat template preselects a provider which does not exist on this computer: {0}. Chats started with it use your usual provider instead."
-- The API key in the pasted configuration cannot be decrypted on this computer. Please enter your API key before you save the provider. -- The API key in the pasted configuration cannot be decrypted on this computer. Please enter your API key before you save the provider.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETIMPORT::T3589070261"] = "The API key in the pasted configuration cannot be decrypted on this computer. Please enter your API key before you save the provider." UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETIMPORT::T3589070261"] = "The API key in the pasted configuration cannot be decrypted on this computer. Please enter your API key before you save the provider."
@@ -13000,6 +13177,9 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETIMPORT::T600
-- The pasted configuration contains an API key which is not encrypted, so it was left out. Please enter your API key before you save the provider. -- The pasted configuration contains an API key which is not encrypted, so it was left out. Please enter your API key before you save the provider.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETIMPORT::T639320638"] = "The pasted configuration contains an API key which is not encrypted, so it was left out. Please enter your API key before you save the provider." UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETIMPORT::T639320638"] = "The pasted configuration contains an API key which is not encrypted, so it was left out. Please enter your API key before you save the provider."
-- The chat template preselects a profile but does not allow profiles. Chats started with it ignore that profile.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETIMPORT::T769062029"] = "The chat template preselects a profile but does not allow profiles. Chats started with it ignore that profile."
-- Expected a quoted string at character {0}. -- Expected a quoted string at character {0}.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETPARSER::T1235695745"] = "Expected a quoted string at character {0}." UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETPARSER::T1235695745"] = "Expected a quoted string at character {0}."
@@ -13402,9 +13582,36 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPES::T378481461"] = "Source like p
-- Document -- Document
UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPES::T4165204724"] = "Document" UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPES::T4165204724"] = "Document"
-- Image for AI models
UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPES::T791951937"] = "Image for AI models"
-- Plugin archive -- Plugin archive
UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPES::T927001356"] = "Plugin archive" UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPES::T927001356"] = "Plugin archive"
-- AI Studio was not able to prepare the image '{0}' for the AI, so it was not sent.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::MODELIMAGEERRORCODEEXTENSIONS::T1345506748"] = "AI Studio was not able to prepare the image '{0}' for the AI, so it was not sent."
-- The image '{0}' could not be made small enough for the selected provider, so it was not sent.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::MODELIMAGEERRORCODEEXTENSIONS::T1463083567"] = "The image '{0}' could not be made small enough for the selected provider, so it was not sent."
-- The image '{0}' could not be opened and was not sent. When the image is stored on a network drive, the drive might be unavailable.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::MODELIMAGEERRORCODEEXTENSIONS::T2022724454"] = "The image '{0}' could not be opened and was not sent. When the image is stored on a network drive, the drive might be unavailable."
-- The image '{0}' does not exist anymore and was not sent.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::MODELIMAGEERRORCODEEXTENSIONS::T2330229240"] = "The image '{0}' does not exist anymore and was not sent."
-- The image '{0}' is not what its file extension claims, so it was not sent. Please correct its file extension.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::MODELIMAGEERRORCODEEXTENSIONS::T3457568536"] = "The image '{0}' is not what its file extension claims, so it was not sent. Please correct its file extension."
-- The image '{0}' could not be read and was not sent. It might be damaged or transferred incompletely.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::MODELIMAGEERRORCODEEXTENSIONS::T4029166607"] = "The image '{0}' could not be read and was not sent. It might be damaged or transferred incompletely."
-- The image '{0}' has too many pixels for AI Studio to prepare it, so it was not sent. Please scale the image down first.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::MODELIMAGEERRORCODEEXTENSIONS::T567941882"] = "The image '{0}' has too many pixels for AI Studio to prepare it, so it was not sent. Please scale the image down first."
-- The image '{0}' uses a format which AI Studio cannot give to an AI model, so it was not sent. Please convert the image to JPG or PNG.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::MODELIMAGEERRORCODEEXTENSIONS::T568830141"] = "The image '{0}' uses a format which AI Studio cannot give to an AI model, so it was not sent. Please convert the image to JPG or PNG."
-- Attempt to override instructions -- Attempt to override instructions
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SECURITY::PROMPTINJECTIONFINDINGCATEGORYEXTENSIONS::T161976090"] = "Attempt to override instructions" UI_TEXT_CONTENT["AISTUDIO::TOOLS::SECURITY::PROMPTINJECTIONFINDINGCATEGORYEXTENSIONS::T161976090"] = "Attempt to override instructions"
@@ -13648,6 +13855,9 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::DATASOURCEEMBEDDINGSERVICE::T1738200
-- The index of the data source '{0}' is missing. The data source stays out of your chats until its index was built anew. Use the repair action to start that. -- The index of the data source '{0}' is missing. The data source stays out of your chats until its index was built anew. Use the repair action to start that.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::DATASOURCEEMBEDDINGSERVICE::T204783307"] = "The index of the data source '{0}' is missing. The data source stays out of your chats until its index was built anew. Use the repair action to start that." UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::DATASOURCEEMBEDDINGSERVICE::T204783307"] = "The index of the data source '{0}' is missing. The data source stays out of your chats until its index was built anew. Use the repair action to start that."
-- The use of the embedding provider of this data source has been prohibited by your organization. Please select a different embedding provider in the settings of the data source.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::DATASOURCEEMBEDDINGSERVICE::T2092244746"] = "The use of the embedding provider of this data source has been prohibited by your organization. Please select a different embedding provider in the settings of the data source."
-- The selected embedding provider is not allowed to index this data source. The data source asks for the confidence level '{0}', while the embedding provider has '{1}'. -- The selected embedding provider is not allowed to index this data source. The data source asks for the confidence level '{0}', while the embedding provider has '{1}'.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::DATASOURCEEMBEDDINGSERVICE::T2186533187"] = "The selected embedding provider is not allowed to index this data source. The data source asks for the confidence level '{0}', while the embedding provider has '{1}'." UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::DATASOURCEEMBEDDINGSERVICE::T2186533187"] = "The selected embedding provider is not allowed to index this data source. The data source asks for the confidence level '{0}', while the embedding provider has '{1}'."
@@ -13759,18 +13969,21 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::INDEXING::DOCUMENTRUNPROGRESS::T8042
-- The folder '{0}' could not be opened. Please check whether you are allowed to read it. Nothing was removed from the index. -- The folder '{0}' could not be opened. Please check whether you are allowed to read it. Nothing was removed from the index.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::INDEXING::FILESOURCEINDEXER::T1214987337"] = "The folder '{0}' could not be opened. Please check whether you are allowed to read it. Nothing was removed from the index." UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::INDEXING::FILESOURCEINDEXER::T1214987337"] = "The folder '{0}' could not be opened. Please check whether you are allowed to read it. Nothing was removed from the index."
-- The file '{0}' cannot be reached. Perhaps the drive it is on is not connected, or the file was moved or renamed. Nothing was removed from the index. Once the file is back, refresh this data source. When it is gone for good, remove this data source.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::INDEXING::FILESOURCEINDEXER::T1393648320"] = "The file '{0}' cannot be reached. Perhaps the drive it is on is not connected, or the file was moved or renamed. Nothing was removed from the index. Once the file is back, refresh this data source. When it is gone for good, remove this data source."
-- The file '{0}' changed while it was being indexed. What was indexed of it is discarded, and the file is tried again during the next run. -- The file '{0}' changed while it was being indexed. What was indexed of it is discarded, and the file is tried again during the next run.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::INDEXING::FILESOURCEINDEXER::T1935191670"] = "The file '{0}' changed while it was being indexed. What was indexed of it is discarded, and the file is tried again during the next run." UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::INDEXING::FILESOURCEINDEXER::T1935191670"] = "The file '{0}' changed while it was being indexed. What was indexed of it is discarded, and the file is tried again during the next run."
-- The file '{0}' cannot be reached. Perhaps the drive it is on is not connected, or the file was moved or renamed. Nothing was removed from the index. If the file was moved or renamed, edit this data source to select its new location. Once the file is back, refresh this data source. When it is gone for good, remove this data source.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::INDEXING::FILESOURCEINDEXER::T2268993048"] = "The file '{0}' cannot be reached. Perhaps the drive it is on is not connected, or the file was moved or renamed. Nothing was removed from the index. If the file was moved or renamed, edit this data source to select its new location. Once the file is back, refresh this data source. When it is gone for good, remove this data source."
-- The folder '{0}' cannot be reached. Perhaps the drive it is on is not connected, or the folder was moved or renamed. Nothing was removed from the index. If the folder was moved or renamed, edit this data source to select its new location. Once the folder is back, refresh this data source.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::INDEXING::FILESOURCEINDEXER::T2692299965"] = "The folder '{0}' cannot be reached. Perhaps the drive it is on is not connected, or the folder was moved or renamed. Nothing was removed from the index. If the folder was moved or renamed, edit this data source to select its new location. Once the folder is back, refresh this data source."
-- The folder '{0}' currently appears empty. AI Studio kept the index of this data source, because the folder of a drive that is not connected can look empty. Connect the drive and refresh this data source. If you emptied the folder on purpose, remove this data source.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::INDEXING::FILESOURCEINDEXER::T2999277901"] = "The folder '{0}' currently appears empty. AI Studio kept the index of this data source, because the folder of a drive that is not connected can look empty. Connect the drive and refresh this data source. If you emptied the folder on purpose, remove this data source."
-- The folder '{0}' could not be opened. Please check whether you are allowed to read it. -- The folder '{0}' could not be opened. Please check whether you are allowed to read it.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::INDEXING::FILESOURCEINDEXER::T3230000698"] = "The folder '{0}' could not be opened. Please check whether you are allowed to read it." UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::INDEXING::FILESOURCEINDEXER::T3230000698"] = "The folder '{0}' could not be opened. Please check whether you are allowed to read it."
-- The folder '{0}' cannot be reached. Perhaps the drive it is on is not connected, or the folder was moved or renamed. Nothing was removed from the index. Once the folder is back, refresh this data source.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::INDEXING::FILESOURCEINDEXER::T3722338131"] = "The folder '{0}' cannot be reached. Perhaps the drive it is on is not connected, or the folder was moved or renamed. Nothing was removed from the index. Once the folder is back, refresh this data source."
-- The file '{0}' could not be read. Please check whether you are allowed to read it. -- The file '{0}' could not be read. Please check whether you are allowed to read it.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::INDEXING::FILESOURCEINDEXER::T3924882233"] = "The file '{0}' could not be read. Please check whether you are allowed to read it." UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::INDEXING::FILESOURCEINDEXER::T3924882233"] = "The file '{0}' could not be read. Please check whether you are allowed to read it."
@@ -14575,6 +14788,9 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::VALIDATION::DATASOURCEVALIDATION::T2222598306"
-- Please select your security policy. -- Please select your security policy.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::VALIDATION::DATASOURCEVALIDATION::T2250909198"] = "Please select your security policy." UI_TEXT_CONTENT["AISTUDIO::TOOLS::VALIDATION::DATASOURCEVALIDATION::T2250909198"] = "Please select your security policy."
-- The use of this embedding provider has been prohibited by your organization. Please select a different embedding provider.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::VALIDATION::DATASOURCEVALIDATION::T2376506873"] = "The use of this embedding provider has been prohibited by your organization. Please select a different embedding provider."
-- Please acknowledge that you are aware of the cloud transcription implications. -- Please acknowledge that you are aware of the cloud transcription implications.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::VALIDATION::DATASOURCEVALIDATION::T2818048408"] = "Please acknowledge that you are aware of the cloud transcription implications." UI_TEXT_CONTENT["AISTUDIO::TOOLS::VALIDATION::DATASOURCEVALIDATION::T2818048408"] = "Please acknowledge that you are aware of the cloud transcription implications."
@@ -14632,11 +14848,8 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::VALIDATION::DATASOURCEVALIDATION::T90753429"]
-- Please enter the secret necessary for authentication. -- Please enter the secret necessary for authentication.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::VALIDATION::DATASOURCEVALIDATION::T968385876"] = "Please enter the secret necessary for authentication." UI_TEXT_CONTENT["AISTUDIO::TOOLS::VALIDATION::DATASOURCEVALIDATION::T968385876"] = "Please enter the secret necessary for authentication."
-- Unsupported image format -- AI Studio cannot give {0} images to an AI model. Please convert the image to JPG or PNG.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::VALIDATION::FILEEXTENSIONVALIDATION::T1398282880"] = "Unsupported image format" UI_TEXT_CONTENT["AISTUDIO::TOOLS::VALIDATION::FILEEXTENSIONVALIDATION::T1578559061"] = "AI Studio cannot give {0} images to an AI model. Please convert the image to JPG or PNG."
-- File has no extension
UI_TEXT_CONTENT["AISTUDIO::TOOLS::VALIDATION::FILEEXTENSIONVALIDATION::T1555980031"] = "File has no extension"
-- Audio files are not supported yet -- Audio files are not supported yet
UI_TEXT_CONTENT["AISTUDIO::TOOLS::VALIDATION::FILEEXTENSIONVALIDATION::T2919730669"] = "Audio files are not supported yet" UI_TEXT_CONTENT["AISTUDIO::TOOLS::VALIDATION::FILEEXTENSIONVALIDATION::T2919730669"] = "Audio files are not supported yet"
@@ -10,6 +10,8 @@ public static class ChatThreadExtensions
/// Checks if the specified provider is allowed for the chat thread. /// Checks if the specified provider is allowed for the chat thread.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// A provider the organization prohibited is never allowed, whatever the chat thread.<br/><br/>
///
/// We don't check if the provider is allowed to use the data sources of the chat thread. /// We don't check if the provider is allowed to use the data sources of the chat thread.
/// That kind of check is done when the available data sources are resolved.<br/><br/> /// That kind of check is done when the available data sources are resolved.<br/><br/>
/// ///
@@ -23,11 +25,16 @@ public static class ChatThreadExtensions
/// <returns>True, when the provider is allowed for the chat thread. False, otherwise.</returns> /// <returns>True, when the provider is allowed for the chat thread. False, otherwise.</returns>
public static bool IsLLMProviderAllowed<T>(this ChatThread? chatThread, T provider) public static bool IsLLMProviderAllowed<T>(this ChatThread? chatThread, T provider)
{ {
var settingsManager = Program.SERVICE_PROVIDER.GetRequiredService<SettingsManager>();
// A provider the organization prohibited is allowed in no chat, not even in a new one:
if (settingsManager.IsProviderProhibited(GetLLMProvider(provider)))
return false;
// No chat thread available means we have a new chat. That's fine: // No chat thread available means we have a new chat. That's fine:
if (chatThread is null) if (chatThread is null)
return true; return true;
var settingsManager = Program.SERVICE_PROVIDER.GetRequiredService<SettingsManager>();
var providerConfidence = provider switch var providerConfidence = provider switch
{ {
IProvider p => p.GetConfidenceLevel(settingsManager), IProvider p => p.GetConfidenceLevel(settingsManager),
@@ -75,4 +82,12 @@ public static class ChatThreadExtensions
false => chatThread.DataSecurity is not DataSourceSecurity.SELF_HOSTED, false => chatThread.DataSecurity is not DataSourceSecurity.SELF_HOSTED,
}; };
} }
private static LLMProviders GetLLMProvider<T>(T provider) => provider switch
{
IProvider p => p.Provider,
AIStudio.Settings.Provider p => p.UsedLLMProvider,
_ => LLMProviders.NONE,
};
} }
@@ -1,7 +1,6 @@
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using AIStudio.Provider; using AIStudio.Provider;
using AIStudio.Tools.Validation;
namespace AIStudio.Chat; namespace AIStudio.Chat;
@@ -53,23 +52,6 @@ public sealed class ContentImage : IContent, IImageSource
#endregion #endregion
/// <summary>
/// Creates a ContentImage from a local file path.
/// </summary>
/// <param name="filePath">The path to the image file.</param>
/// <returns>A new ContentImage instance if the file is valid, null otherwise.</returns>
public static async Task<ContentImage?> CreateFromFileAsync(string filePath)
{
if (!await FileExtensionValidation.IsImageExtensionValidWithNotifyAsync(filePath))
return null;
return new ContentImage
{
SourceType = ContentImageSource.LOCAL_PATH,
Source = filePath,
};
}
/// <summary> /// <summary>
/// The type of the image source. /// The type of the image source.
/// </summary> /// </summary>
+1 -1
View File
@@ -149,7 +149,7 @@ public sealed class ContentText : IContent
if(!chatThread.IsLLMProviderAllowed(provider)) if(!chatThread.IsLLMProviderAllowed(provider))
{ {
LOGGER.LogError("The provider is not allowed for this chat thread due to data security or confidence-level requirements. Skipping the AI process."); LOGGER.LogError("The provider is not allowed for this chat thread due to a prohibition of the organization, data security, or confidence-level requirements. Skipping the AI process.");
await this.CompleteWithoutStreaming(); await this.CompleteWithoutStreaming();
return chatThread; return chatThread;
} }
@@ -1,4 +1,5 @@
using AIStudio.Tools.MIME; using AIStudio.Provider;
using AIStudio.Provider.SelfHosted;
using AIStudio.Tools.PluginSystem; using AIStudio.Tools.PluginSystem;
namespace AIStudio.Chat; namespace AIStudio.Chat;
@@ -7,72 +8,40 @@ public static class IImageSourceExtensions
{ {
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(IImageSourceExtensions).Namespace, nameof(IImageSourceExtensions)); private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(IImageSourceExtensions).Namespace, nameof(IImageSourceExtensions));
public static MIMEType DetermineMimeType(this IImageSource image) /// <summary>
{ /// The largest image, in bytes, we send anywhere: no image prepared for a model is larger, and
switch (image.SourceType) /// TryAsBase64 skips every larger file.
{ /// </summary>
case ContentImageSource.BASE64: public const long MAX_IMAGE_SIZE_BYTES = 10_000_000;
{
// Try to detect the mime type from the base64 string:
var base64Data = image.Source;
if (base64Data.StartsWith("data:", StringComparison.OrdinalIgnoreCase))
{
var mimeEnd = base64Data.IndexOf(';');
if (mimeEnd > 5)
return Builder.FromTextRepresentation(base64Data[5..mimeEnd]);
}
// Fallback: /// <summary>
return Builder.Create().UseApplication().UseSubtype(ApplicationSubtype.OCTET_STREAM).Build(); /// The largest image file, in bytes, which we send through the given provider.
} /// </summary>
/// <remarks>
case ContentImageSource.URL: /// The smallest of the limits we know: the one of a self-hosted engine, the one of the provider,
/// and MAX_IMAGE_SIZE_BYTES, our own. A provider or engine which states no limit gets ours.
/// </remarks>
/// <param name="provider">The configured provider the image is sent through.</param>
/// <returns>The size limit of one image file.</returns>
public static long MaxImageSizeBytesFor(AIStudio.Settings.Provider provider)
{ {
// Try to detect the mime type from the URL extension: var engineLimit = provider.UsedLLMProvider is LLMProviders.SELF_HOSTED ? provider.Host.GetMaxImageSizeBytes() : null;
var uri = new Uri(image.Source); var providerLimit = provider.UsedLLMProvider.GetMaxImageSizeBytes();
var extension = Path.GetExtension(uri.AbsolutePath).ToLowerInvariant(); return Math.Min(Math.Min(engineLimit ?? MAX_IMAGE_SIZE_BYTES, providerLimit ?? MAX_IMAGE_SIZE_BYTES), MAX_IMAGE_SIZE_BYTES);
return DeriveMIMETypeFromExtension(extension);
}
case ContentImageSource.LOCAL_PATH:
{
var extension = Path.GetExtension(image.Source).ToLowerInvariant();
return DeriveMIMETypeFromExtension(extension);
}
default:
return Builder.Create().UseApplication().UseSubtype(ApplicationSubtype.OCTET_STREAM).Build();
}
}
private static MIMEType DeriveMIMETypeFromExtension(string extension)
{
var imageBuilder = Builder.Create().UseImage();
return extension switch
{
".png" => imageBuilder.UseSubtype(ImageSubtype.PNG).Build(),
".jpg" or ".jpeg" => imageBuilder.UseSubtype(ImageSubtype.JPEG).Build(),
".gif" => imageBuilder.UseSubtype(ImageSubtype.GIF).Build(),
".webp" => imageBuilder.UseSubtype(ImageSubtype.WEBP).Build(),
".tiff" or ".tif" => imageBuilder.UseSubtype(ImageSubtype.TIFF).Build(),
".heic" or ".heif" => imageBuilder.UseSubtype(ImageSubtype.HEIC).Build(),
_ => Builder.Create().UseApplication().UseSubtype(ApplicationSubtype.OCTET_STREAM).Build()
};
} }
/// <summary> /// <summary>
/// Read the image content as a base64 string. /// Read the image content as a base64 string.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// This is for the images inside the content of a chat, which go to external data sources and
/// agents as they are. Attached images, which go to a model, take ModelImageService instead: it
/// scales them down and encodes them anew within the limit of the provider.<br/>
/// <br/>
/// The images are directly converted to base64 strings. The maximum /// The images are directly converted to base64 strings. The maximum
/// size of the image is around 10 MB. If the image is larger, the method /// size of the image is around 10 MB. If the image is larger, the method
/// returns an empty string.<br/> /// returns an empty string.<br/>
/// <br/> /// <br/>
/// As of now, this method does no sort of image processing. LLMs usually
/// do not work with arbitrary image sizes. In the future, we might have
/// to resize the images before sending them to the model.<br/>
/// <br/>
/// Note as well that this method returns just the base64 string without /// Note as well that this method returns just the base64 string without
/// any data URI prefix (like "data:image/png;base64,"). The caller has /// any data URI prefix (like "data:image/png;base64,"). The caller has
/// to take care of that if needed. /// to take care of that if needed.
@@ -97,7 +66,7 @@ public static class IImageSourceExtensions
{ {
// Read the length of the content: // Read the length of the content:
var lengthBytes = response.Content.Headers.ContentLength; var lengthBytes = response.Content.Headers.ContentLength;
if(lengthBytes > 10_000_000) if(lengthBytes > MAX_IMAGE_SIZE_BYTES)
{ {
await MessageBus.INSTANCE.SendError(new(Icons.Material.Filled.ImageNotSupported, TB("The image at the URL is too large (>10 MB). Skipping the image."))); await MessageBus.INSTANCE.SendError(new(Icons.Material.Filled.ImageNotSupported, TB("The image at the URL is too large (>10 MB). Skipping the image.")));
return (success: false, string.Empty); return (success: false, string.Empty);
@@ -116,7 +85,7 @@ public static class IImageSourceExtensions
{ {
// Read the content length: // Read the content length:
var length = new FileInfo(image.Source).Length; var length = new FileInfo(image.Source).Length;
if(length > 10_000_000) if(length > MAX_IMAGE_SIZE_BYTES)
{ {
await MessageBus.INSTANCE.SendError(new(Icons.Material.Filled.ImageNotSupported, TB("The local image file is too large (>10 MB). Skipping the image."))); await MessageBus.INSTANCE.SendError(new(Icons.Material.Filled.ImageNotSupported, TB("The local image file is too large (>10 MB). Skipping the image.")));
return (success: false, string.Empty); return (success: false, string.Empty);
@@ -1,7 +1,9 @@
using AIStudio.Provider; using AIStudio.Provider;
using AIStudio.Provider.OpenAI; using AIStudio.Provider.OpenAI;
using AIStudio.Settings; using AIStudio.Settings;
using AIStudio.Tools.Rust;
using AIStudio.Tools.Security; using AIStudio.Tools.Security;
using AIStudio.Tools.Services;
namespace AIStudio.Chat; namespace AIStudio.Chat;
@@ -14,14 +16,16 @@ public static class ListContentBlockExtensions
/// <param name="roleTransformer">A function that transforms each content block into a message result asynchronously.</param> /// <param name="roleTransformer">A function that transforms each content block into a message result asynchronously.</param>
/// <param name="provider">The configured provider, whose model is being written to.</param> /// <param name="provider">The configured provider, whose model is being written to.</param>
/// <param name="textSubContentFactory">A factory function to create text sub-content.</param> /// <param name="textSubContentFactory">A factory function to create text sub-content.</param>
/// <param name="imageSubContentFactory">A factory function to create image sub-content.</param> /// <param name="imageSubContentFactory">A factory function to create image sub-content from an image prepared for the model.</param>
/// <param name="token">The cancellation token.</param>
/// <returns>An asynchronous task that resolves to a list of transformed results.</returns> /// <returns>An asynchronous task that resolves to a list of transformed results.</returns>
public static async Task<IList<IMessageBase>> BuildMessagesAsync( public static async Task<IList<IMessageBase>> BuildMessagesAsync(
this List<ContentBlock> blocks, this List<ContentBlock> blocks,
AIStudio.Settings.Provider provider, AIStudio.Settings.Provider provider,
Func<ChatRole, string> roleTransformer, Func<ChatRole, string> roleTransformer,
Func<string, ISubContent> textSubContentFactory, Func<string, ISubContent> textSubContentFactory,
Func<FileAttachmentImage, Task<ISubContent>> imageSubContentFactory) Func<PreparedModelImage, ISubContent> imageSubContentFactory,
CancellationToken token = default)
{ {
// //
// Asked through the configured provider, so that what a person set in their expert settings // Asked through the configured provider, so that what a person set in their expert settings
@@ -59,7 +63,7 @@ public static class ListContentBlockExtensions
// Text prompt with images as attachments, and the provider/model can process images: // Text prompt with images as attachments, and the provider/model can process images:
case ContentText text when block.ContentType is ContentType.TEXT && !string.IsNullOrWhiteSpace(text.Text) && text.FileAttachments.ContainsImages(): case ContentText text when block.ContentType is ContentType.TEXT && !string.IsNullOrWhiteSpace(text.Text) && text.FileAttachments.ContainsImages():
messageTaskList.Add(CreateMultimodalMessageAsync(block, text, textSubContentFactory, imageSubContentFactory)); messageTaskList.Add(CreateMultimodalMessageAsync(block, text));
break; break;
} }
} }
@@ -81,34 +85,41 @@ public static class ListContentBlockExtensions
} }
// Local function to create a multimodal message asynchronously. // Local function to create a multimodal message asynchronously.
Task<IMessageBase> CreateMultimodalMessageAsync( Task<IMessageBase> CreateMultimodalMessageAsync(ContentBlock block, ContentText text)
ContentBlock block,
ContentText text,
Func<string, ISubContent> innerTextSubContentFactory,
Func<FileAttachmentImage, Task<ISubContent>> innerImageSubContentFactory)
{ {
return Task.Run(async () => return Task.Run(async () =>
{ {
var imagesTasks = text.FileAttachments var images = await Task.WhenAll(text.FileAttachments
.Where(x => x is { IsImage: true, Exists: true }) .Where(x => x is { IsImage: true, Exists: true })
.Cast<FileAttachmentImage>() .Cast<FileAttachmentImage>()
.Select(innerImageSubContentFactory) .Select(CreateImageSubContentAsync));
.ToList();
Task.WaitAll(imagesTasks);
var images = imagesTasks.Select(t => t.Result).ToList();
return new MultimodalMessage return new MultimodalMessage
{ {
Role = roleTransformer(block.Role), Role = roleTransformer(block.Role),
Content = Content =
[ [
innerTextSubContentFactory(await text.PrepareTextContentForAI(reportingScope)), textSubContentFactory(await text.PrepareTextContentForAI(reportingScope)),
..images, ..images.OfType<ISubContent>(),
] ]
} as IMessageBase; } as IMessageBase;
}); });
} }
//
// Local function to prepare one image for the model. An image which cannot be prepared
// stays out of the message, and the user learns why. It used to go along as an image
// without data, which the providers refuse together with the whole request.
//
async Task<ISubContent?> CreateImageSubContentAsync(FileAttachmentImage image)
{
var prepared = await Program.SERVICE_PROVIDER.GetRequiredService<ModelImageService>().PrepareAsync(image, provider, token);
if (prepared.IsPrepared)
return imageSubContentFactory(prepared);
await MessageBus.INSTANCE.SendError(new(Icons.Material.Filled.ImageNotSupported, prepared.ErrorCode.ToUserMessage(image.FileName)));
return null;
}
} }
/// <summary> /// <summary>
@@ -158,20 +169,16 @@ public static class ListContentBlockExtensions
Text = text, Text = text,
}; };
private static async Task<ISubContent> DirectImageSubContentFactory(FileAttachmentImage attachment) => new SubContentImageUrl private static ISubContent DirectImageSubContentFactory(PreparedModelImage image) => new SubContentImageUrl
{ {
ImageUrl = await attachment.TryAsBase64() is (true, var base64Content) ImageUrl = image.ToDataUrl(),
? $"data:{attachment.DetermineMimeType()};base64,{base64Content}"
: string.Empty,
}; };
private static async Task<ISubContent> NestedImageSubContentFactory(FileAttachmentImage attachment) => new SubContentImageUrlNested private static ISubContent NestedImageSubContentFactory(PreparedModelImage image) => new SubContentImageUrlNested
{ {
ImageUrl = new SubContentImageUrlData ImageUrl = new SubContentImageUrlData
{ {
Url = await attachment.TryAsBase64() is (true, var base64Content) Url = image.ToDataUrl(),
? $"data:{attachment.DetermineMimeType()};base64,{base64Content}"
: string.Empty,
}, },
}; };
@@ -157,7 +157,7 @@
@if (!this.ChatThread.IsLLMProviderAllowed(this.Provider)) @if (!this.ChatThread.IsLLMProviderAllowed(this.Provider))
{ {
<MudTooltip Text="@T("The selected provider is not allowed in this chat due to data security or confidence-level requirements.")" Placement="@TOOLBAR_TOOLTIP_PLACEMENT"> <MudTooltip Text="@(this.SettingsManager.IsProviderProhibited(this.Provider.UsedLLMProvider) ? T("The use of this provider has been prohibited by your organization. Please select a different provider.") : T("The selected provider is not allowed in this chat due to data security or confidence-level requirements."))" Placement="@TOOLBAR_TOOLTIP_PLACEMENT">
<MudIconButton Icon="@Icons.Material.Filled.Error" Color="Color.Error"/> <MudIconButton Icon="@Icons.Material.Filled.Error" Color="Color.Error"/>
</MudTooltip> </MudTooltip>
} }
@@ -901,6 +901,84 @@ public partial class ChatComponent : MSGComponentBase
this.selectedToolIds = await this.ToolRegistry.FilterSelectableToolIdsAsync(Tools.Components.CHAT, templateToolIds); this.selectedToolIds = await this.ToolRegistry.FilterSelectableToolIdsAsync(Tools.Components.CHAT, templateToolIds);
} }
/// <summary>
/// Takes over the profile and provider which the chat template preselects, wherever it preselects one.
/// </summary>
/// <remarks>
/// A preselection, not a limit — the user changes both in the chat as usual. A template which
/// says nothing, or names a provider which is gone or may not be used for chats, leaves the
/// current choice in place, so the chat starts as it would without the template. A template
/// whose profile is gone starts with the default profile of the chat settings instead. What the
/// previous template preselected is not carried over, see SettingsManager.ChooseChatTemplateProfile.
/// The caller tells the parent about a new provider.
/// </remarks>
/// <param name="previousChatTemplate">The chat template the current profile and provider were chosen with; the current one when the template does not change.</param>
/// <returns>True when the provider changed.</returns>
private bool ApplyChatTemplateProfileAndProvider(ChatTemplate previousChatTemplate)
{
this.currentProfile = this.SettingsManager.ChooseChatTemplateProfile(this.currentChatTemplate, previousChatTemplate, this.currentProfile);
var provider = this.SettingsManager.ChooseChatTemplateProvider(this.currentChatTemplate, previousChatTemplate, this.Provider);
if (provider == this.Provider)
return false;
this.Provider = provider;
return true;
}
/// <summary>
/// The workspace a chat which has no thread yet gets with its first content.
/// </summary>
/// <remarks>
/// The first content is the first message, a media import, or a draft kept when the user leaves
/// the page. A chat without a thread belongs to no workspace, so the workspace of its chat
/// template fills in, opened or created by its name. That happens only here, when the chat gets
/// something to keep: merely opening the chat page or trying out a template creates no
/// workspace, and a workspace the user deleted stays gone until a chat with that template
/// actually starts. Without a usable workspace of the template, the chat stays without one.
/// </remarks>
/// <returns>The ID of the workspace, or Guid.Empty for none.</returns>
private async Task<Guid> ResolveWorkspaceOfFirstThreadAsync()
{
if (this.currentWorkspaceId != Guid.Empty)
return this.currentWorkspaceId;
if (this.SettingsManager.GetChatTemplateWorkspaceName(this.currentChatTemplate) is not { } workspaceName)
return Guid.Empty;
return await WorkspaceBehaviour.ResolveOrCreateChatTemplateWorkspaceAsync(workspaceName, this.currentChatTemplate.GetSafeName());
}
/// <summary>
/// Hands a thread which this component just created for a chat without one to the parent.
/// </summary>
/// <remarks>
/// The thread is a parameter of this component, and the parent hands over its own whenever it
/// renders. So the parent hears about the new thread before anything else may render it, see
/// StartNewChat. Only afterward, the header shows the workspace of the thread, since showing a
/// workspace name renders the parent as well.
/// </remarks>
private async Task PublishNewChatThreadAsync()
{
this.MarkCurrentChatAsLoadedParameter();
await this.ChatThreadChanged.InvokeAsync(this.ChatThread);
// The header shows the workspace of the new thread from now on, e.g., the one of its chat template:
if (this.ChatThread is not null && this.ChatThread.WorkspaceId != Guid.Empty)
await this.SyncWorkspaceHeaderWithChatThreadAsync();
}
/// <summary>
/// Whether the open chat is in the workspace which the given chat template names.
/// </summary>
/// <remarks>
/// Such a workspace was the choice of the template rather than of the user, so a chat started
/// with another template does not have to stay there.
/// </remarks>
private bool IsInWorkspaceOfChatTemplate(ChatTemplate chatTemplate) => this.currentWorkspaceId != Guid.Empty
&& this.SettingsManager.GetChatTemplateWorkspaceName(chatTemplate) is { } workspaceName
&& string.Equals(WorkspaceBehaviour.NormalizeWorkspaceName(this.currentWorkspaceName), workspaceName, StringComparison.OrdinalIgnoreCase);
/// <summary> /// <summary>
/// The data source options a chat starts with: those of its chat template, or the chat defaults. /// The data source options a chat starts with: those of its chat template, or the chat defaults.
/// </summary> /// </summary>
@@ -1006,24 +1084,36 @@ public partial class ChatComponent : MSGComponentBase
private async Task ChatTemplateWasChanged(ChatTemplate chatTemplate) private async Task ChatTemplateWasChanged(ChatTemplate chatTemplate)
{ {
this.currentChatTemplate = this.SettingsManager.GetChatTemplateById(chatTemplate.Id); var nextChatTemplate = this.SettingsManager.GetChatTemplateById(chatTemplate.Id);
if (this.ChatThread is not null)
{
//
// Starting the new chat is what hands the new template to it, with its text, attachments,
// and selections. Nothing changes here before, since the user may still decline to leave
// the open chat; it then stays as it was, with its template. The new chat stays in the
// workspace of the open one, unless that workspace is merely the one of the current
// template: then the new template decides where its chat goes.
//
await this.StartNewChat(useSameWorkspace: !this.IsInWorkspaceOfChatTemplate(this.currentChatTemplate), chatTemplate: nextChatTemplate);
return;
}
var previousChatTemplate = this.currentChatTemplate;
this.currentChatTemplate = nextChatTemplate;
if(!string.IsNullOrWhiteSpace(this.currentChatTemplate.PredefinedUserPrompt)) if(!string.IsNullOrWhiteSpace(this.currentChatTemplate.PredefinedUserPrompt))
this.ComposerState.SetSystemInput(this.currentChatTemplate.PredefinedUserPrompt); this.ComposerState.SetSystemInput(this.currentChatTemplate.PredefinedUserPrompt);
// Apply template's file attachments (replaces existing): // Apply template's file attachments (replaces existing):
this.ComposerState.ReplaceFileAttachments(this.currentChatTemplate.FileAttachments); this.ComposerState.ReplaceFileAttachments(this.currentChatTemplate.FileAttachments);
if (this.ChatThread is not null)
{
// Starting the new chat is what hands the selection of the new template to it:
await this.StartNewChat(true);
return;
}
// //
// Without a thread there is nothing to start anew, so the selection of the new template is // Without a thread there is nothing to start anew, so the selection of the new template is
// applied right here. It travels into the thread which the first message creates. // applied right here. It travels into the thread which the first message creates, and that
// message also decides the workspace, see ResolveWorkspaceOfFirstThreadAsync.
// //
if (this.ApplyChatTemplateProfileAndProvider(previousChatTemplate))
await this.ProviderChanged.InvokeAsync(this.Provider);
await this.ApplyChatTemplateToolSelectionAsync(); await this.ApplyChatTemplateToolSelectionAsync();
this.ApplyAutomaticDataSourceOptions(); this.ApplyAutomaticDataSourceOptions();
} }
@@ -1047,6 +1137,19 @@ public partial class ChatComponent : MSGComponentBase
{ {
this.currentProfile = this.SettingsManager.GetPreselectedProfile(Tools.Components.CHAT); this.currentProfile = this.SettingsManager.GetPreselectedProfile(Tools.Components.CHAT);
this.currentChatTemplate = this.SettingsManager.GetPreselectedChatTemplate(Tools.Components.CHAT); this.currentChatTemplate = this.SettingsManager.GetPreselectedChatTemplate(Tools.Components.CHAT);
//
// A chat which has not started yet keeps up with its chat template. The provider follows
// only when the template now preselects another one, so that a provider the user picked
// by hand survives a change of some unrelated setting:
//
if (previousChatTemplate.Id != this.currentChatTemplate.Id || previousChatTemplate.ProviderId != this.currentChatTemplate.ProviderId)
{
if (this.ApplyChatTemplateProfileAndProvider(previousChatTemplate))
await this.ProviderChanged.InvokeAsync(this.Provider);
}
else if (this.SettingsManager.GetChatTemplateStartProfile(this.currentChatTemplate) is { } templateProfile)
this.currentProfile = templateProfile;
} }
else else
{ {
@@ -1256,10 +1359,16 @@ public partial class ChatComponent : MSGComponentBase
if (!this.ComposerState.HasVisibleUserDraft) if (!this.ComposerState.HasVisibleUserDraft)
return; return;
//
// The message moves out of the composer before anything below waits, so that leaving a
// second time meanwhile finds nothing left to keep. Only the workspace follows afterward,
// since finding or creating the one of the chat template may take a moment:
//
this.RefreshCurrentProfileAndChatTemplate(); this.RefreshCurrentProfileAndChatTemplate();
chat = this.NewChatThread(this.ExtractThreadName(this.ComposerState.UserInput)); chat = this.NewChatThread(this.ExtractThreadName(this.ComposerState.UserInput), Guid.Empty);
this.MirrorComposerIntoDraft(chat); this.MirrorComposerIntoDraft(chat);
this.ComposerState.Clear(); this.ComposerState.Clear();
chat.WorkspaceId = await this.ResolveWorkspaceOfFirstThreadAsync();
} }
else if (unstoredChatBeingLeft is not null && (!unstoredChatBeingLeft.Draft.IsEmpty || this.MediaTranscriptionService.IsBusy(MediaImportOwner.ForChat(unstoredChatBeingLeft.ChatId)))) else if (unstoredChatBeingLeft is not null && (!unstoredChatBeingLeft.Draft.IsEmpty || this.MediaTranscriptionService.IsBusy(MediaImportOwner.ForChat(unstoredChatBeingLeft.ChatId))))
{ {
@@ -1330,22 +1439,28 @@ public partial class ChatComponent : MSGComponentBase
? string.Format(T("Transcription: {0}"), Path.GetFileName(firstMediaPath)) ? string.Format(T("Transcription: {0}"), Path.GetFileName(firstMediaPath))
: promptName; : promptName;
this.ChatThread = this.NewChatThread(threadName); var chat = this.NewChatThread(threadName, await this.ResolveWorkspaceOfFirstThreadAsync());
// A message the user started belongs to the new chat, so it is stored with it: // A message the user started belongs to the new chat, so it is stored with it:
if (this.ComposerState.HasUserDraft) if (this.ComposerState.HasUserDraft)
this.MirrorComposerIntoDraft(this.ChatThread); this.MirrorComposerIntoDraft(chat);
//
// The parent hears about the new thread before storing it waits, as with the first message.
// A render of the parent in between would otherwise hand back no thread at all, and the
// transcript would arrive in a chat which is nowhere:
//
this.ChatThread = chat;
await this.PublishNewChatThreadAsync();
// //
// Like saving, this goes through the workspace component whenever there is one, so its tree // Like saving, this goes through the workspace component whenever there is one, so its tree
// lists the new chat right away, together with the progress of the transcription, instead // lists the new chat right away, together with the progress of the transcription, instead
// of only after the first message was sent: // of only after the first message was sent:
// //
await this.StoreAndListChatAsync(this.ChatThread); await this.StoreAndListChatAsync(chat);
this.MarkCurrentChatAsLoadedParameter();
await this.ChatThreadChanged.InvokeAsync(this.ChatThread);
await this.SyncForegroundChatAsync(); await this.SyncForegroundChatAsync();
return this.ChatThread; return chat;
} }
private async Task SendMessage(bool reuseLastUserPrompt = false) private async Task SendMessage(bool reuseLastUserPrompt = false)
@@ -1377,9 +1492,8 @@ public partial class ChatComponent : MSGComponentBase
// Create a new chat thread if necessary: // Create a new chat thread if necessary:
if (this.ChatThread is null) if (this.ChatThread is null)
{ {
this.ChatThread = this.NewChatThread(this.ExtractThreadName(this.ComposerState.UserInput)); this.ChatThread = this.NewChatThread(this.ExtractThreadName(this.ComposerState.UserInput), await this.ResolveWorkspaceOfFirstThreadAsync());
this.MarkCurrentChatAsLoadedParameter(); await this.PublishNewChatThreadAsync();
await this.ChatThreadChanged.InvokeAsync(this.ChatThread);
} }
else else
{ {
@@ -1576,12 +1690,17 @@ public partial class ChatComponent : MSGComponentBase
/// Every way to start a new chat leads here: the buttons of the toolbar, a change of the chat /// Every way to start a new chat leads here: the buttons of the toolbar, a change of the chat
/// template, and the workspace tree. So every new chat gets its chat template with the data /// template, and the workspace tree. So every new chat gets its chat template with the data
/// sources, tools, and example conversation, its profile, and its provider in the same way, and /// sources, tools, and example conversation, its profile, and its provider in the same way, and
/// the user is asked about unsaved changes in the same way. /// the user is asked about unsaved changes in the same way.<br/><br/>
///
/// A new chat template takes over only once the user agreed to leave the open chat and that
/// chat is stored or deleted. Declining leaves the open chat as it was, with its template, and
/// the template it runs with stays the one whose preselections the next chat must not carry over.
/// </remarks> /// </remarks>
/// <param name="useSameWorkspace">True starts the new chat with a thread right away, in the workspace of the open chat. False starts it outside any workspace, and its thread comes with the first message.</param> /// <param name="useSameWorkspace">True starts the new chat with a thread right away, in the workspace of the open chat. False starts it outside any workspace, and its thread comes with the first message. A chat which would belong to no workspace either way, while its chat template names one, also gets its thread only with the first message, in the workspace of the template.</param>
/// <param name="deletePreviousChat">Whether to delete the open chat first.</param> /// <param name="deletePreviousChat">Whether to delete the open chat first.</param>
/// <param name="targetWorkspaceId">The workspace for the new chat, when the workspace tree asks for one. When given, the new chat gets a thread in that workspace, whatever useSameWorkspace says.</param> /// <param name="targetWorkspaceId">The workspace for the new chat, when the workspace tree asks for one. When given, the new chat gets a thread in that workspace, whatever useSameWorkspace says.</param>
private async Task StartNewChat(bool useSameWorkspace = false, bool deletePreviousChat = false, Guid? targetWorkspaceId = null) /// <param name="chatTemplate">The chat template of the new chat, when the user picked another one. Without one, the new chat keeps the template of the open chat.</param>
private async Task StartNewChat(bool useSameWorkspace = false, bool deletePreviousChat = false, Guid? targetWorkspaceId = null, ChatTemplate? chatTemplate = null)
{ {
// //
// Where the new chat goes is decided right away, when it is asked for. Deleting the open chat // Where the new chat goes is decided right away, when it is asked for. Deleting the open chat
@@ -1589,6 +1708,9 @@ public partial class ChatComponent : MSGComponentBase
// //
var workspaceOfNewChat = targetWorkspaceId ?? this.currentWorkspaceId; var workspaceOfNewChat = targetWorkspaceId ?? this.currentWorkspaceId;
// The template the profile and the provider of the open chat were chosen with:
var previousChatTemplate = this.currentChatTemplate;
// //
// Want the user to manage the chat storage manually? In that case, we have to ask the user // Want the user to manage the chat storage manually? In that case, we have to ask the user
// about possible data loss: // about possible data loss:
@@ -1650,6 +1772,11 @@ public partial class ChatComponent : MSGComponentBase
// //
this.hasUnsavedChanges = false; this.hasUnsavedChanges = false;
this.ComposerState.Clear(); this.ComposerState.Clear();
// The open chat is left now, so the new template takes over:
if (chatTemplate is not null)
this.currentChatTemplate = chatTemplate;
this.RefreshCurrentProfileAndChatTemplate(); this.RefreshCurrentProfileAndChatTemplate();
// //
@@ -1673,11 +1800,26 @@ public partial class ChatComponent : MSGComponentBase
break; break;
} }
// What the chat template preselects comes last, so that it wins over the behavior above:
if (this.ApplyChatTemplateProfileAndProvider(previousChatTemplate))
await this.ProviderChanged.InvokeAsync(this.Provider);
// //
// Reset the chat thread or create a new one. A target workspace always means a new chat in // Reset the chat thread or create a new one. A target workspace always means a new chat in
// that workspace, no matter what useSameWorkspace says: // that workspace, no matter what useSameWorkspace says:
// //
var startsOutsideWorkspaces = !useSameWorkspace && targetWorkspaceId is null; var startsOutsideWorkspaces = !useSameWorkspace && targetWorkspaceId is null;
//
// The workspace of the chat template fills in only where the new chat would belong to no
// workspace otherwise. A workspace asked for on purpose wins: the one of the workspace list,
// and the open one for the button that starts a new chat in it. Such a chat gets no thread
// here: its first message creates one in that workspace, so that the workspace is created
// only once there is something to keep, see ResolveWorkspaceOfFirstThreadAsync.
//
if (targetWorkspaceId is null && workspaceOfNewChat == Guid.Empty && this.SettingsManager.GetChatTemplateWorkspaceName(this.currentChatTemplate) is not null)
startsOutsideWorkspaces = true;
if (startsOutsideWorkspaces) if (startsOutsideWorkspaces)
{ {
// //
@@ -1917,6 +2059,10 @@ public partial class ChatComponent : MSGComponentBase
this.Provider = this.SettingsManager.GetChatProviderForLoadedChat(chatProvider); this.Provider = this.SettingsManager.GetChatProviderForLoadedChat(chatProvider);
// A chat without a thread has not started yet, so it starts with what its chat template preselects:
if (this.ChatThread is null)
this.ApplyChatTemplateProfileAndProvider(this.currentChatTemplate);
await this.ProviderChanged.InvokeAsync(this.Provider); await this.ProviderChanged.InvokeAsync(this.Provider);
// Try to select the profile: // Try to select the profile:
@@ -2054,7 +2200,7 @@ public partial class ChatComponent : MSGComponentBase
// that, and it may even bring an example conversation along -- reporting nothing for all // that, and it may even bring an example conversation along -- reporting nothing for all
// of it would tell a person their window is empty while their first message is not. // of it would tell a person their window is empty while their first message is not.
// //
var thread = this.ChatThread ?? this.NewChatThread(string.Empty); var thread = this.ChatThread ?? this.NewChatThread(string.Empty, this.currentWorkspaceId);
var toolDefinitions = this.GetRunnableToolDefinitions(offersSemanticSearch); var toolDefinitions = this.GetRunnableToolDefinitions(offersSemanticSearch);
provider = this.Provider; provider = this.Provider;
parts = ConversationParts.Of(thread, this.BuildSystemPromptFor(thread, toolDefinitions), this.UserInput, this.ComposerState.FileAttachments, provider.SupportsImageInput(), toolDefinitions); parts = ConversationParts.Of(thread, this.BuildSystemPromptFor(thread, toolDefinitions), this.UserInput, this.ComposerState.FileAttachments, provider.SupportsImageInput(), toolDefinitions);
@@ -2153,18 +2299,22 @@ public partial class ChatComponent : MSGComponentBase
/// The thread a new chat starts with, as the selections made so far decide it. /// The thread a new chat starts with, as the selections made so far decide it.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// In one place because three code paths used to write it out, and because the token count has /// The only place which builds a new thread: every way to start a chat comes here, and so does
/// to measure the same thing they build. A count against a thread assembled differently from /// the token count, which has to measure the same thing they build. A count against a thread
/// the one which is then sent would be wrong in exactly the moment a person looks at it: before /// assembled differently from the one which is then sent would be wrong in exactly the moment a
/// they send their first message. /// person looks at it: before they send their first message.
///
/// The workspace is no default of its own but always named by the caller, since that is the one
/// part the ways to start a chat decide differently: StartNewChat decides it right away, and a
/// chat which gets its thread with its first content asks ResolveWorkspaceOfFirstThreadAsync.
/// ///
/// The data source options are those chosen before the chat had a thread. Starting a new chat in /// The data source options are those chosen before the chat had a thread. Starting a new chat in
/// a workspace replaces them right afterward with those its chat template asks for. /// a workspace replaces them right afterward with those its chat template asks for.
/// </remarks> /// </remarks>
/// <param name="name">The name of the thread.</param> /// <param name="name">The name of the thread.</param>
/// <param name="workspaceId">The workspace of the thread. Without one, the thread goes to the workspace of the open chat.</param> /// <param name="workspaceId">The workspace of the thread, or Guid.Empty for none.</param>
/// <returns>The new thread.</returns> /// <returns>The new thread.</returns>
private ChatThread NewChatThread(string name, Guid? workspaceId = null) => new() private ChatThread NewChatThread(string name, Guid workspaceId) => new()
{ {
DataSourceOptions = this.earlyDataSourceOptions, DataSourceOptions = this.earlyDataSourceOptions,
IncludeDateTime = true, IncludeDateTime = true,
@@ -2173,7 +2323,7 @@ public partial class ChatComponent : MSGComponentBase
SelectedChatTemplate = this.currentChatTemplate.Id, SelectedChatTemplate = this.currentChatTemplate.Id,
SelectedToolIds = [..this.selectedToolIds], SelectedToolIds = [..this.selectedToolIds],
SystemPrompt = SystemPrompts.DEFAULT, SystemPrompt = SystemPrompts.DEFAULT,
WorkspaceId = workspaceId ?? this.currentWorkspaceId, WorkspaceId = workspaceId,
ChatId = Guid.NewGuid(), ChatId = Guid.NewGuid(),
Name = name, Name = name,
Blocks = this.currentChatTemplate == ChatTemplate.NO_CHAT_TEMPLATE ? [] : this.currentChatTemplate.ExampleConversation.Select(block => block.DeepClone()).ToList(), Blocks = this.currentChatTemplate == ChatTemplate.NO_CHAT_TEMPLATE ? [] : this.currentChatTemplate.ExampleConversation.Select(block => block.DeepClone()).ToList(),
@@ -270,69 +270,7 @@ public partial class DataSourceManagement : MSGComponentBase
private async Task EditDataSource(IDataSource dataSource) private async Task EditDataSource(IDataSource dataSource)
{ {
if (dataSource.IsEnterpriseConfiguration) if (await DataSourceEditing.EditAsync(this.DialogService, this.SettingsManager, this.DataSourceEmbeddingService, dataSource.Id))
return;
IDataSource? editedDataSource = null;
var lockDataSourceOrigin = dataSource is IInternalDataSource
&& await this.DataSourceEmbeddingService.ShouldLockDataSourceOriginAsync(dataSource.Id);
switch (dataSource)
{
case DataSourceLocalFile localFile:
var localFileDialogParameters = new DialogParameters<DataSourceLocalFileDialog>
{
{ x => x.IsEditing, true },
{ x => x.DataSource, localFile },
{ x => x.LockSource, lockDataSourceOrigin },
};
var localFileDialogReference = await this.DialogService.ShowAsync<DataSourceLocalFileDialog>(T("Edit Local File Data Source"), localFileDialogParameters, DialogOptions.FULLSCREEN);
var localFileDialogResult = await localFileDialogReference.Result;
if (localFileDialogResult is null || localFileDialogResult.Canceled)
return;
editedDataSource = (DataSourceLocalFile)localFileDialogResult.Data!;
break;
case DataSourceLocalDirectory localDirectory:
var localDirectoryDialogParameters = new DialogParameters<DataSourceLocalDirectoryDialog>
{
{ x => x.IsEditing, true },
{ x => x.DataSource, localDirectory },
{ x => x.LockSource, lockDataSourceOrigin },
};
var localDirectoryDialogReference = await this.DialogService.ShowAsync<DataSourceLocalDirectoryDialog>(T("Edit Local Directory Data Source"), localDirectoryDialogParameters, DialogOptions.FULLSCREEN);
var localDirectoryDialogResult = await localDirectoryDialogReference.Result;
if (localDirectoryDialogResult is null || localDirectoryDialogResult.Canceled)
return;
editedDataSource = (DataSourceLocalDirectory)localDirectoryDialogResult.Data!;
break;
case DataSourceERI_V1 eriDataSource:
var eriDialogParameters = new DialogParameters<DataSourceERI_V1Dialog>
{
{ x => x.IsEditing, true },
{ x => x.DataSource, eriDataSource },
};
var eriDialogReference = await this.DialogService.ShowAsync<DataSourceERI_V1Dialog>(T("Edit ERI v1 Data Source"), eriDialogParameters, DialogOptions.FULLSCREEN);
var eriDialogResult = await eriDialogReference.Result;
if (eriDialogResult is null || eriDialogResult.Canceled)
return;
editedDataSource = (DataSourceERI_V1)eriDialogResult.Data!;
break;
}
if(editedDataSource is null)
return;
this.SettingsManager.ConfigurationData.DataSources[this.SettingsManager.ConfigurationData.DataSources.IndexOf(dataSource)] = editedDataSource;
await this.SettingsManager.StoreSettings();
await this.DataSourceEmbeddingService.QueueDataSourceAsync(editedDataSource);
await this.MessageBus.SendMessage<bool>(this, Event.CONFIGURATION_CHANGED); await this.MessageBus.SendMessage<bool>(this, Event.CONFIGURATION_CHANGED);
} }
@@ -1,16 +1,7 @@
@using AIStudio.Settings.DataModel
@inherits MSGComponentBase @inherits MSGComponentBase
@if (this.availableWorkspaces.Count > 0) <WorkspaceNameField WorkspaceName="@this.WorkspaceName" WorkspaceNameChanged="@this.SetWorkspaceName" SelectLabel="@T("Existing workspace (Optional)")" Label="@T("Workspace name (Optional)")" HelperText="@T("Choose an existing workspace or enter a name that should be created when the launcher is opened.")" AdornmentIcon="@(this.OpensTemporaryChat ? Icons.Material.Filled.Timer : Icons.Material.Filled.CreateNewFolder)" AdornmentIconSize="Size.Small" TextFieldClass="mb-1"/>
{
<MudSelect T="string" Value="@this.WorkspaceName" ValueChanged="@this.SelectExistingWorkspace" Strict="@false" Label="@T("Existing workspace (Optional)")" Variant="Variant.Outlined" Margin="Margin.Dense" Class="mb-3 rounded-lg" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.Folder">
@foreach (var workspace in this.availableWorkspaces)
{
<MudSelectItem T="string" Value="@workspace.Name">@workspace.Name</MudSelectItem>
}
</MudSelect>
}
<MudTextField T="string" Text="@this.WorkspaceName" TextChanged="@this.SetWorkspaceName" AdornmentIcon="@(this.OpensTemporaryChat ? Icons.Material.Filled.Timer : Icons.Material.Filled.CreateNewFolder)" Adornment="Adornment.Start" IconSize="Size.Small" Label="@T("Workspace name (Optional)")" HelperText="@T("Choose an existing workspace or enter a name that should be created when the launcher is opened.")" Variant="Variant.Outlined" Margin="Margin.Dense" Class="mb-1" UserAttributes="@USER_INPUT_ATTRIBUTES"/>
@* The tile behaves differently with and without a workspace, so the form says which of the two is @* The tile behaves differently with and without a workspace, so the form says which of the two is
chosen right now instead of only explaining that both are possible. *@ chosen right now instead of only explaining that both are possible. *@
@@ -19,6 +10,12 @@
? T("Without a workspace, the tile opens a disappearing chat: it belongs to no workspace and is deleted according to your workspace maintenance settings.") ? T("Without a workspace, the tile opens a disappearing chat: it belongs to no workspace and is deleted according to your workspace maintenance settings.")
: T("The tile opens its chat in this workspace and creates the workspace when it does not exist yet.")) : T("The tile opens its chat in this workspace and creates the workspace when it does not exist yet."))
</MudText> </MudText>
@if (this.OpensTemporaryChat && this.SettingsManager.GetChatTemplateWorkspaceName(this.SelectedChatTemplate) is { } templateWorkspaceName)
{
<MudText Typo="Typo.body2" Class="mud-text-secondary mb-3">
@string.Format(T("The chosen chat template keeps its chats in the workspace '{0}', so the tile opens its chat there instead. A workspace entered here wins over the one of the template."), templateWorkspaceName)
</MudText>
}
<MudSelect T="string" Value="@this.ProviderId" ValueChanged="@this.SetProviderId" Label="@T("Chat provider")" Variant="Variant.Outlined" Margin="Margin.Dense" Class="mb-3 rounded-lg" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.SmartToy"> <MudSelect T="string" Value="@this.ProviderId" ValueChanged="@this.SetProviderId" Label="@T("Chat provider")" Variant="Variant.Outlined" Margin="Margin.Dense" Class="mb-3 rounded-lg" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.SmartToy">
<MudSelectItem T="string" Value="@string.Empty">@T("Use chat default")</MudSelectItem> <MudSelectItem T="string" Value="@string.Empty">@T("Use chat default")</MudSelectItem>
@foreach (var provider in this.SettingsManager.GetConfidentProviders(Components.CHAT)) @foreach (var provider in this.SettingsManager.GetConfidentProviders(Components.CHAT))
@@ -28,6 +25,12 @@
</MudSelectItem> </MudSelectItem>
} }
</MudSelect> </MudSelect>
@if (this.SelectedChatTemplate.ProviderId is not null)
{
<MudText Typo="Typo.body2" Class="mud-text-secondary mb-3">
@T("The chosen chat template preselects a provider of its own, and it wins over a selection made here as long as that provider can be used for chats.")
</MudText>
}
<MudSelect T="string" Value="@this.ProfileId" ValueChanged="@this.SetProfileId" Label="@T("Chat profile")" Variant="Variant.Outlined" Margin="Margin.Dense" Class="mb-3 rounded-lg" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.Person"> <MudSelect T="string" Value="@this.ProfileId" ValueChanged="@this.SetProfileId" Label="@T("Chat profile")" Variant="Variant.Outlined" Margin="Margin.Dense" Class="mb-3 rounded-lg" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.Person">
<MudSelectItem T="string" Value="@string.Empty">@T("Use chat default")</MudSelectItem> <MudSelectItem T="string" Value="@string.Empty">@T("Use chat default")</MudSelectItem>
<MudSelectItem T="string" Value="@Guid.Empty.ToString()">@T("Use no profile")</MudSelectItem> <MudSelectItem T="string" Value="@Guid.Empty.ToString()">@T("Use no profile")</MudSelectItem>
@@ -36,6 +39,12 @@
<MudSelectItem T="string" Value="@profile.Id">@profile.GetSafeName()</MudSelectItem> <MudSelectItem T="string" Value="@profile.Id">@profile.GetSafeName()</MudSelectItem>
} }
</MudSelect> </MudSelect>
@if (this.SelectedChatTemplate is { AllowProfileUsage: true, ProfileId: not null })
{
<MudText Typo="Typo.body2" Class="mud-text-secondary mb-3">
@T("The chosen chat template preselects a profile of its own, and it wins over a selection made here as long as that profile exists.")
</MudText>
}
<MudSelect T="string" Value="@this.ChatTemplateId" ValueChanged="@this.SetChatTemplateId" Label="@T("Chat template")" Variant="Variant.Outlined" Margin="Margin.Dense" Class="mb-3 rounded-lg" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.Article"> <MudSelect T="string" Value="@this.ChatTemplateId" ValueChanged="@this.SetChatTemplateId" Label="@T("Chat template")" Variant="Variant.Outlined" Margin="Margin.Dense" Class="mb-3 rounded-lg" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.Article">
<MudSelectItem T="string" Value="@string.Empty">@T("Use chat default")</MudSelectItem> <MudSelectItem T="string" Value="@string.Empty">@T("Use chat default")</MudSelectItem>
<MudSelectItem T="string" Value="@Guid.Empty.ToString()">@T("Use no chat template")</MudSelectItem> <MudSelectItem T="string" Value="@Guid.Empty.ToString()">@T("Use no chat template")</MudSelectItem>
@@ -44,20 +53,25 @@
<MudSelectItem T="string" Value="@chatTemplate.Id">@chatTemplate.GetSafeName()</MudSelectItem> <MudSelectItem T="string" Value="@chatTemplate.Id">@chatTemplate.GetSafeName()</MudSelectItem>
} }
</MudSelect> </MudSelect>
<MudSelect T="string" Label="@T("Data sources (Optional)")" MultiSelection="@true" SelectedValues="@this.DataSourceIds" SelectedValuesChanged="@this.SetDataSourceIds" MultiSelectionTextFunc="@this.GetSelectedDataSourceText" Variant="Variant.Outlined" Margin="Margin.Dense" Class="mb-3 rounded-lg" Adornment="Adornment.Start" AdornmentIcon="@AppIcons.DATABASE"> @* Without the RAG preview, the chat uses no data sources, so there is nothing to choose here. A
selection made before stays with the launcher and counts once RAG is switched on again: *@
@if (PreviewFeatures.PRE_RAG_2024.IsEnabled(this.SettingsManager))
{
<MudSelect T="string" Label="@T("Data sources (Optional)")" MultiSelection="@true" SelectedValues="@this.DataSourceIds" SelectedValuesChanged="@this.SetDataSourceIds" MultiSelectionTextFunc="@this.GetSelectedDataSourceText" Variant="Variant.Outlined" Margin="Margin.Dense" Class="mb-3 rounded-lg" Adornment="Adornment.Start" AdornmentIcon="@AppIcons.DATABASE">
@foreach (var dataSource in this.SettingsManager.ConfigurationData.DataSources) @foreach (var dataSource in this.SettingsManager.ConfigurationData.DataSources)
{ {
<MudSelectItem T="string" Value="@dataSource.Id">@dataSource.Name</MudSelectItem> <MudSelectItem T="string" Value="@dataSource.Id">@dataSource.Name</MudSelectItem>
} }
</MudSelect> </MudSelect>
@* A chat template wins over what is chosen here, so the form says so before somebody picks @* A chat template wins over what is chosen here, so the form says so before somebody picks
something which would never take effect: *@ something which would never take effect: *@
@if (this.SelectedChatTemplate.DataSourceOptions is not null) @if (this.SelectedChatTemplate.DataSourceOptions is not null)
{ {
<MudText Typo="Typo.body2" Class="mud-text-secondary mb-3"> <MudText Typo="Typo.body2" Class="mud-text-secondary mb-3">
@T("The chosen chat template brings data sources of its own, and those win over a selection made here. Only a template can also leave the choice of sources to the AI, which is why it decides this on its own.") @T("The chosen chat template brings data sources of its own, and those win over a selection made here. Only a template can also leave the choice of sources to the AI, which is why it decides this on its own.")
</MudText> </MudText>
}
} }
<ToolSelectionField Component="Components.CHAT" SelectedToolIds="@this.ToolIds" SelectedToolIdsChanged="@this.SetToolIds" Label="@T("Tools (Optional)")" Help="@T("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.")"/> <ToolSelectionField Component="Components.CHAT" SelectedToolIds="@this.ToolIds" SelectedToolIdsChanged="@this.SetToolIds" Label="@T("Tools (Optional)")" Help="@T("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.")"/>
@@ -96,37 +96,6 @@ public partial class DirectChatLauncherForm : MSGComponentBase
? this.SettingsManager.GetPreselectedChatTemplate(Tools.Components.CHAT) ? this.SettingsManager.GetPreselectedChatTemplate(Tools.Components.CHAT)
: this.SettingsManager.GetChatTemplateById(this.ChatTemplateId); : this.SettingsManager.GetChatTemplateById(this.ChatTemplateId);
private IReadOnlyList<WorkspaceTreeWorkspace> availableWorkspaces = [];
private static readonly Dictionary<string, object?> USER_INPUT_ATTRIBUTES = new();
#region Overrides of MSGComponentBase
protected override async Task OnInitializedAsync()
{
// Configure the spellchecking for the workspace name input:
this.SettingsManager.InjectSpellchecking(USER_INPUT_ATTRIBUTES);
await base.OnInitializedAsync();
var workspaceSnapshot = await WorkspaceBehaviour.GetOrLoadWorkspaceTreeShellAsync();
this.availableWorkspaces = workspaceSnapshot.Workspaces;
}
#endregion
//
// Picking an existing workspace fills the name field. Clearing the select must not wipe a name
// the user typed, though, so an empty selection is ignored:
//
private async Task SelectExistingWorkspace(string workspaceName)
{
if (string.IsNullOrWhiteSpace(workspaceName))
return;
await this.SetWorkspaceName(workspaceName);
}
private async Task SetWorkspaceName(string workspaceName) private async Task SetWorkspaceName(string workspaceName)
{ {
this.WorkspaceName = workspaceName; this.WorkspaceName = workspaceName;
@@ -76,21 +76,25 @@ public partial class ProviderSelection : MSGComponentBase
/// Says why there is nothing to choose from, or nothing at all when that is not the user's doing. /// Says why there is nothing to choose from, or nothing at all when that is not the user's doing.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// An empty list has two causes the user can act on, and they lead to different places in the /// An empty list has three causes the user can act on, and they lead to different places in the
/// settings: there is no provider yet, or none of the configured ones reaches the confidence /// settings: there is no provider yet, the organization prohibited all of them, or none of the
/// this component asks for. Naming the wrong one sends the user looking in the wrong place -- /// configured ones reaches the confidence this component asks for. Naming the wrong one sends
/// a first start has nobody to blame for a confidence level it never set. A missing or invalid /// the user looking in the wrong place -- a first start has nobody to blame for a confidence
/// component is a third case and neither of those: it is a defect, it was logged as one, and /// level it never set. A missing or invalid component is another case and none of those: it is
/// any explanation offered to the user here would be a guess. /// a defect, it was logged as one, and any explanation offered to the user here would be a guess.
/// </remarks> /// </remarks>
private string? GetEmptySelectionHint() private string? GetEmptySelectionHint()
{ {
if (this.Component is null or Tools.Components.NONE) if (this.Component is null or Tools.Components.NONE)
return null; return null;
if (!this.SettingsManager.GetAllProviders().Any(x => x.UsedLLMProvider is not LLMProviders.NONE)) var configuredProviders = this.SettingsManager.GetAllProviders().Where(x => x.UsedLLMProvider is not LLMProviders.NONE).ToList();
if (configuredProviders.Count == 0)
return this.T("No LLM providers are configured yet. Add a provider in the app settings."); return this.T("No LLM providers are configured yet. Add a provider in the app settings.");
if (configuredProviders.All(x => this.SettingsManager.IsProviderProhibited(x.UsedLLMProvider)))
return this.T("The use of all your LLM providers has been prohibited by your organization. Add a different provider in the app settings.");
return this.T("No LLM providers meet the confidence requirements. Configure an eligible provider in the app settings."); return this.T("No LLM providers meet the confidence requirements. Configure an eligible provider in the app settings.");
} }
@@ -166,6 +170,17 @@ public partial class ProviderSelection : MSGComponentBase
await this.ProviderSettingsChanged.InvokeAsync(updatedProvider); await this.ProviderSettingsChanged.InvokeAsync(updatedProvider);
} }
//
// A configuration may also take our provider out of reach, e.g., when the organization
// prohibits it or raises the minimum confidence. The list already leaves it out, so the
// selection must not keep showing it either:
//
if (this.Component is not (null or Tools.Components.NONE) && this.ProviderSettings != AIStudio.Settings.Provider.NONE && !this.GetAvailableProviders().Contains(this.ProviderSettings))
{
this.ProviderSettings = AIStudio.Settings.Provider.NONE;
await this.ProviderSettingsChanged.InvokeAsync(AIStudio.Settings.Provider.NONE);
}
this.StateHasChanged(); this.StateHasChanged();
} }
} }
@@ -105,7 +105,7 @@ public partial class SettingsPanelApp : SettingsPanelBase
var minimumLevel = this.SettingsManager.GetMinimumConfidenceLevel(Tools.Components.APP_SETTINGS); var minimumLevel = this.SettingsManager.GetMinimumConfidenceLevel(Tools.Components.APP_SETTINGS);
foreach (var provider in this.SettingsManager.GetAllTranscriptionProviders()) foreach (var provider in this.SettingsManager.GetAllTranscriptionProviders())
{ {
if (provider.UsedLLMProvider.GetConfidence(this.SettingsManager).Level >= minimumLevel) if (this.SettingsManager.IsProviderUsable(provider.UsedLLMProvider, minimumLevel))
yield return new(provider.Name, provider.Id); yield return new(provider.Name, provider.Id);
} }
} }
@@ -39,9 +39,15 @@
</MudTd> </MudTd>
<MudTd> <MudTd>
<MudMarkdown Value="@context.GetConfidence(this.SettingsManager).Description" MarkdownPipeline="Markdown.SAFE_MARKDOWN_PIPELINE"/> <MudMarkdown Value="@context.GetConfidence(this.SettingsManager).Description" MarkdownPipeline="Markdown.SAFE_MARKDOWN_PIPELINE"/>
@if (this.SettingsManager.IsProviderProhibited(context))
{
<MudAlert Severity="Severity.Warning" Dense="@true" Class="mt-2">
@T("The use of this provider has been prohibited by your organization. Therefore, it is untrusted, and you cannot change its confidence level.")
</MudAlert>
}
</MudTd> </MudTd>
<MudTd Style="vertical-align: top;"> <MudTd Style="vertical-align: top;">
<MudMenu StartIcon="@Icons.Material.Filled.Security" EndIcon="@Icons.Material.Filled.KeyboardArrowDown" Label="@this.GetCurrentConfidenceLevelName(context)" Variant="Variant.Filled" Style="@this.SetCurrentConfidenceLevelColorStyle(context)" Disabled="@this.IsCustomConfidenceSchemeLocked()"> <MudMenu StartIcon="@Icons.Material.Filled.Security" EndIcon="@Icons.Material.Filled.KeyboardArrowDown" Label="@this.GetCurrentConfidenceLevelName(context)" Variant="Variant.Filled" Style="@this.SetCurrentConfidenceLevelColorStyle(context)" Disabled="@this.IsConfidenceLevelLocked(context)">
@foreach (var confidenceLevel in Enum.GetValues<ConfidenceLevel>().OrderBy(n => n)) @foreach (var confidenceLevel in Enum.GetValues<ConfidenceLevel>().OrderBy(n => n))
{ {
if(confidenceLevel is ConfidenceLevel.NONE or ConfidenceLevel.UNKNOWN) if(confidenceLevel is ConfidenceLevel.NONE or ConfidenceLevel.UNKNOWN)
@@ -7,7 +7,7 @@ public partial class SettingsPanelConfidence : SettingsPanelBase
{ {
private string GetCurrentConfidenceLevelName(LLMProviders llmProvider) private string GetCurrentConfidenceLevelName(LLMProviders llmProvider)
{ {
if (this.SettingsManager.ConfigurationData.Confidence.CustomConfidenceScheme.TryGetValue(llmProvider, out var level)) if (this.TryGetShownConfidenceLevel(llmProvider, out var level))
return level.GetName(); return level.GetName();
return T("Not yet configured"); return T("Not yet configured");
@@ -15,20 +15,41 @@ public partial class SettingsPanelConfidence : SettingsPanelBase
private string SetCurrentConfidenceLevelColorStyle(LLMProviders llmProvider) private string SetCurrentConfidenceLevelColorStyle(LLMProviders llmProvider)
{ {
if (this.SettingsManager.ConfigurationData.Confidence.CustomConfidenceScheme.TryGetValue(llmProvider, out var level)) if (this.TryGetShownConfidenceLevel(llmProvider, out var level))
return $"background-color: {level.GetColor(this.SettingsManager)};"; return $"background-color: {level.GetColor(this.SettingsManager)};";
return $"background-color: {ConfidenceLevel.UNKNOWN.GetColor(this.SettingsManager)};"; return $"background-color: {ConfidenceLevel.UNKNOWN.GetColor(this.SettingsManager)};";
} }
/// <summary>
/// Determines the confidence level the table shows for a provider.
/// </summary>
/// <remarks>
/// A prohibited provider is untrusted, whatever level was chosen for it, see
/// SettingsManager.GetConfiguredConfidenceLevel. The chosen level stays stored, so the table
/// shows it again once the prohibition ends.
/// </remarks>
private bool TryGetShownConfidenceLevel(LLMProviders llmProvider, out ConfidenceLevel level)
{
if (this.SettingsManager.IsProviderProhibited(llmProvider))
{
level = ConfidenceLevel.UNTRUSTED;
return true;
}
return this.SettingsManager.ConfigurationData.Confidence.CustomConfidenceScheme.TryGetValue(llmProvider, out level);
}
private bool IsCustomConfidenceSchemeLocked() private bool IsCustomConfidenceSchemeLocked()
{ {
return ManagedConfiguration.TryGet(x => x.Confidence, x => x.CustomConfidenceScheme, out var meta) && meta.IsLocked; return ManagedConfiguration.TryGet(x => x.Confidence, x => x.CustomConfidenceScheme, out var meta) && meta.IsLocked;
} }
private bool IsConfidenceLevelLocked(LLMProviders llmProvider) => this.IsCustomConfidenceSchemeLocked() || this.SettingsManager.IsProviderProhibited(llmProvider);
private async Task ChangeCustomConfidenceLevel(LLMProviders llmProvider, ConfidenceLevel level) private async Task ChangeCustomConfidenceLevel(LLMProviders llmProvider, ConfidenceLevel level)
{ {
if (this.IsCustomConfidenceSchemeLocked()) if (this.IsConfidenceLevelLocked(llmProvider))
return; return;
this.SettingsManager.ConfigurationData.Confidence.CustomConfidenceScheme[llmProvider] = level; this.SettingsManager.ConfigurationData.Confidence.CustomConfidenceScheme[llmProvider] = level;
@@ -44,6 +44,12 @@
<MudTd> <MudTd>
<MudStack Row="true" Class="mb-2 mt-2" Spacing="1" Wrap="Wrap.Wrap"> <MudStack Row="true" Class="mb-2 mt-2" Spacing="1" Wrap="Wrap.Wrap">
@if (this.SettingsManager.IsProviderProhibited(context.UsedLLMProvider))
{
<MudTooltip Text="@T("The use of this embedding provider has been prohibited by your organization.")">
<MudIconButton Color="Color.Error" Icon="@Icons.Material.Filled.Block" Disabled="true"/>
</MudTooltip>
}
@if (context.IsTrustedForDataSourceSecurityChecks(this.SettingsManager)) @if (context.IsTrustedForDataSourceSecurityChecks(this.SettingsManager))
{ {
<MudTooltip Text="@(context.IsSelfHosted ? T("This self-hosted embedding provider is trusted for data source security checks. Local data can be sent to it without security warnings.") : T("This embedding provider is trusted by your organization for data source security checks. Local data can be sent to it without security warnings."))"> <MudTooltip Text="@(context.IsSelfHosted ? T("This self-hosted embedding provider is trusted for data source security checks. Local data can be sent to it without security warnings.") : T("This embedding provider is trusted by your organization for data source security checks. Local data can be sent to it without security warnings."))">
@@ -62,7 +68,7 @@
<MudIconButton Color="Color.Info" Icon="@Icons.Material.Filled.Key" OnClick="@(() => this.EditEmbeddingProvider(context))"/> <MudIconButton Color="Color.Info" Icon="@Icons.Material.Filled.Key" OnClick="@(() => this.EditEmbeddingProvider(context))"/>
</MudTooltip> </MudTooltip>
<MudTooltip Text="@T("Test")"> <MudTooltip Text="@T("Test")">
<MudIconButton Color="Color.Info" Icon="@Icons.Material.Filled.Api" OnClick="@(() => this.TestEmbeddingProvider(context))"/> <MudIconButton Color="Color.Info" Icon="@Icons.Material.Filled.Api" OnClick="@(() => this.TestEmbeddingProvider(context))" Disabled="@this.SettingsManager.IsProviderProhibited(context.UsedLLMProvider)"/>
</MudTooltip> </MudTooltip>
} }
else else
@@ -78,7 +84,7 @@
<MudIconButton Color="Color.Error" Icon="@Icons.Material.Filled.Delete" OnClick="@(() => this.DeleteEmbeddingProvider(context))"/> <MudIconButton Color="Color.Error" Icon="@Icons.Material.Filled.Delete" OnClick="@(() => this.DeleteEmbeddingProvider(context))"/>
</MudTooltip> </MudTooltip>
<MudTooltip Text="@T("Test")"> <MudTooltip Text="@T("Test")">
<MudIconButton Color="Color.Info" Icon="@Icons.Material.Filled.Api" OnClick="@(() => this.TestEmbeddingProvider(context))"/> <MudIconButton Color="Color.Info" Icon="@Icons.Material.Filled.Api" OnClick="@(() => this.TestEmbeddingProvider(context))" Disabled="@this.SettingsManager.IsProviderProhibited(context.UsedLLMProvider)"/>
</MudTooltip> </MudTooltip>
} }
</MudStack> </MudStack>
@@ -36,6 +36,12 @@
<MudTd>@this.GetLLMProviderModelName(context)</MudTd> <MudTd>@this.GetLLMProviderModelName(context)</MudTd>
<MudTd> <MudTd>
<MudStack Row="true" Class="mb-2 mt-2" Spacing="1" Wrap="Wrap.Wrap"> <MudStack Row="true" Class="mb-2 mt-2" Spacing="1" Wrap="Wrap.Wrap">
@if (this.SettingsManager.IsProviderProhibited(context.UsedLLMProvider))
{
<MudTooltip Text="@T("The use of this provider has been prohibited by your organization.")">
<MudIconButton Color="Color.Error" Icon="@Icons.Material.Filled.Block" Disabled="true"/>
</MudTooltip>
}
@if (context.IsTrustedForDataSourceSecurityChecks(this.SettingsManager)) @if (context.IsTrustedForDataSourceSecurityChecks(this.SettingsManager))
{ {
<MudTooltip Text="@(context.IsSelfHosted ? T("This self-hosted provider is trusted for data source security checks.") : T("This provider is trusted by your organization for data source security checks."))"> <MudTooltip Text="@(context.IsSelfHosted ? T("This self-hosted provider is trusted for data source security checks.") : T("This provider is trusted by your organization for data source security checks."))">
@@ -41,6 +41,12 @@
<MudTd> <MudTd>
<MudStack Row="true" Class="mb-2 mt-2" Spacing="1" Wrap="Wrap.Wrap"> <MudStack Row="true" Class="mb-2 mt-2" Spacing="1" Wrap="Wrap.Wrap">
@if (this.SettingsManager.IsProviderProhibited(context.UsedLLMProvider))
{
<MudTooltip Text="@T("The use of this transcription provider has been prohibited by your organization.")">
<MudIconButton Color="Color.Error" Icon="@Icons.Material.Filled.Block" Disabled="true"/>
</MudTooltip>
}
@if (context.IsTrustedForDataSourceSecurityChecks(this.SettingsManager)) @if (context.IsTrustedForDataSourceSecurityChecks(this.SettingsManager))
{ {
<MudTooltip Text="@(context.IsSelfHosted ? T("This self-hosted transcription provider is trusted for data source security checks.") : T("This transcription provider is trusted by your organization for data source security checks."))"> <MudTooltip Text="@(context.IsSelfHosted ? T("This self-hosted transcription provider is trusted for data source security checks.") : T("This transcription provider is trusted by your organization for data source security checks."))">
@@ -0,0 +1,13 @@
@inherits MSGComponentBase
@if (this.availableWorkspaces.Count > 0 && !this.ReadOnly)
{
<MudSelect T="string" Value="@this.WorkspaceName" ValueChanged="@this.SelectExistingWorkspace" Strict="@false" Label="@this.SelectLabel" Variant="Variant.Outlined" Margin="Margin.Dense" Class="@this.SelectClass" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.Folder">
@foreach (var workspace in this.availableWorkspaces)
{
<MudSelectItem T="string" Value="@workspace.Name">@workspace.Name</MudSelectItem>
}
</MudSelect>
}
<MudTextField T="string" Text="@this.WorkspaceName" TextChanged="@this.SetWorkspaceName" Label="@this.Label" HelperText="@this.HelperText" Validation="@this.Validation" Immediate="@this.Immediate" ReadOnly="@this.ReadOnly" Variant="Variant.Outlined" Margin="Margin.Dense" Class="@this.TextFieldClass" Adornment="Adornment.Start" AdornmentIcon="@this.AdornmentIcon" IconSize="@this.AdornmentIconSize" UserAttributes="@SPELLCHECK_ATTRIBUTES"/>
@@ -0,0 +1,107 @@
using Microsoft.AspNetCore.Components;
namespace AIStudio.Components;
/// <summary>
/// Names a workspace by its name: an existing one to pick from, or a new name to type.
/// </summary>
/// <remarks>
/// Assistant chat launchers and chat templates both name their workspace this way, because a
/// workspace has an ID of its own on every computer, and both create it when it does not exist
/// yet. The field offers the existing workspaces only as a shortcut; the name it hands out is
/// whatever the text field says. All texts come from the form around it, which knows what the
/// name is used for.
/// </remarks>
public partial class WorkspaceNameField : MSGComponentBase
{
/// <summary>
/// The name of the workspace. An empty name is up to the form around the field, e.g., a
/// disappearing chat for an assistant chat launcher.
/// </summary>
[Parameter]
public string WorkspaceName { get; set; } = string.Empty;
[Parameter]
public EventCallback<string> WorkspaceNameChanged { get; set; }
/// <summary>
/// The label of the selection of existing workspaces.
/// </summary>
[Parameter]
public string SelectLabel { get; set; } = string.Empty;
/// <summary>
/// The label of the text field for the name.
/// </summary>
[Parameter]
public string Label { get; set; } = string.Empty;
[Parameter]
public string HelperText { get; set; } = string.Empty;
[Parameter]
public string AdornmentIcon { get; set; } = Icons.Material.Filled.CreateNewFolder;
[Parameter]
public Size AdornmentIconSize { get; set; } = Size.Medium;
/// <summary>
/// The validation of the name, or null for none.
/// </summary>
[Parameter]
public Func<string, string?>? Validation { get; set; }
/// <summary>
/// Whether every keystroke counts as a change, e.g., so that a validation follows the typing.
/// </summary>
[Parameter]
public bool Immediate { get; set; }
/// <summary>
/// Shows the name without letting the user change it. The selection of existing workspaces
/// is left out then, since picking from it would change the name.
/// </summary>
[Parameter]
public bool ReadOnly { get; set; }
[Parameter]
public string SelectClass { get; set; } = "mb-3 rounded-lg";
[Parameter]
public string TextFieldClass { get; set; } = "mb-3";
private static readonly Dictionary<string, object?> SPELLCHECK_ATTRIBUTES = new();
private IReadOnlyList<WorkspaceTreeWorkspace> availableWorkspaces = [];
#region Overrides of MSGComponentBase
protected override async Task OnInitializedAsync()
{
this.SettingsManager.InjectSpellchecking(SPELLCHECK_ATTRIBUTES);
await base.OnInitializedAsync();
var workspaceSnapshot = await WorkspaceBehaviour.GetOrLoadWorkspaceTreeShellAsync();
this.availableWorkspaces = workspaceSnapshot.Workspaces;
}
#endregion
//
// Picking an existing workspace fills the name field. Clearing the select must not wipe a name
// the user typed, though, so an empty selection is ignored:
//
private async Task SelectExistingWorkspace(string workspaceName)
{
if (string.IsNullOrWhiteSpace(workspaceName))
return;
await this.SetWorkspaceName(workspaceName);
}
private async Task SetWorkspaceName(string workspaceName)
{
this.WorkspaceName = workspaceName;
await this.WorkspaceNameChanged.InvokeAsync(workspaceName);
}
}
@@ -1,4 +1,5 @@
@using AIStudio.Chat @using AIStudio.Chat
@using AIStudio.Settings
@using AIStudio.Settings.DataModel @using AIStudio.Settings.DataModel
@inherits MSGComponentBase @inherits MSGComponentBase
@@ -136,6 +137,73 @@
@T("Using some chat templates in tandem with profiles might cause issues. Therefore, you might prohibit the usage of profiles here.") @T("Using some chat templates in tandem with profiles might cause issues. Therefore, you might prohibit the usage of profiles here.")
</MudJustifiedText> </MudJustifiedText>
<MudTextSwitch @bind-Value="@this.AllowProfileUsage" Color="Color.Primary" Label="@T("Allow the use of profiles together with this chat template?")" LabelOn="@T("Yes, allow profiles when using this template")" LabelOff="@T("No, prohibit profile use for this template")" Disabled="@this.IsReadOnly" /> <MudTextSwitch @bind-Value="@this.AllowProfileUsage" Color="Color.Primary" Label="@T("Allow the use of profiles together with this chat template?")" LabelOn="@T("Yes, allow profiles when using this template")" LabelOff="@T("No, prohibit profile use for this template")" Disabled="@this.IsReadOnly" />
@if (this.AllowProfileUsage)
{
<MudJustifiedText Class="mb-3 mt-3" Typo="Typo.body1">
@T("A chat template may also decide which profile a chat starts with, including starting without one. Without such a decision, those chats start with the profile they would start with anyway. The profile stays changeable in the chat.")
</MudJustifiedText>
<MudTextSwitch @bind-Value="@this.preselectProfile" Color="Color.Primary" Label="@T("Does this chat template preselect a profile?")" LabelOn="@T("Yes, this template decides which profile a chat starts with")" LabelOff="@T("No, chats keep their usual profile")" Disabled="@this.IsReadOnly"/>
@if (this.preselectProfile)
{
<MudSelect T="string" @bind-Value="@this.selectedProfileId" Label="@T("Preselected profile")" Variant="Variant.Outlined" Margin="Margin.Dense" Class="mb-3 mt-3 rounded-lg" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.Person" Disabled="@this.IsReadOnly">
<MudSelectItem T="string" Value="@Profile.NO_PROFILE.Id">@T("Use no profile")</MudSelectItem>
@foreach (var profile in this.SettingsManager.ConfigurationData.Profiles)
{
<MudSelectItem T="string" Value="@profile.Id">@profile.GetSafeName()</MudSelectItem>
}
</MudSelect>
@if (this.IsPreselectedProfileMissing)
{
<MudAlert Severity="Severity.Warning" Class="mb-3">
@T("The profile this chat template preselects does not exist on this computer. Chats started with it keep their usual profile.")
</MudAlert>
}
}
}
<MudText Typo="Typo.h6" Class="mb-3 mt-6">
@T("Provider")
</MudText>
<MudJustifiedText Class="mb-3" Typo="Typo.body1">
@T("A chat template may decide which provider, and with it which model, a chat starts with. Without such a decision, those chats start with the provider they would start with anyway. The provider stays changeable in the chat, and it must meet the confidence level required for chats.")
</MudJustifiedText>
<MudTextSwitch @bind-Value="@this.preselectProvider" Color="Color.Primary" Label="@T("Does this chat template preselect a provider?")" LabelOn="@T("Yes, this template decides which provider a chat starts with")" LabelOff="@T("No, chats keep their usual provider")" Disabled="@this.IsReadOnly"/>
@if (this.preselectProvider)
{
<MudSelect T="string" @bind-Value="@this.selectedProviderId" Label="@T("Preselected provider")" Validation="@this.ValidatePreselectedProvider" Variant="Variant.Outlined" Margin="Margin.Dense" Class="mb-3 mt-3 rounded-lg" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.SmartToy" Disabled="@this.IsReadOnly">
@foreach (var provider in this.SettingsManager.GetConfidentProviders(Components.CHAT))
{
<MudSelectItem T="string" Value="@provider.Id">
<ProviderLabel ProviderSettings="@provider" Text="@provider.InstanceName"/>
</MudSelectItem>
}
</MudSelect>
@if (this.IsPreselectedProviderUnusable)
{
<MudAlert Severity="Severity.Warning" Class="mb-3">
@T("The provider this chat template preselects does not exist on this computer or does not meet the confidence level required for chats. Chats started with it keep their usual provider.")
</MudAlert>
}
}
<MudText Typo="Typo.h6" Class="mb-3 mt-6">
@T("Workspace")
</MudText>
<MudJustifiedText Class="mb-3" Typo="Typo.body1">
@T("A chat template may keep its chats in a workspace of its own, which is created when it does not exist yet. This applies whenever a chat would otherwise be a disappearing chat, for example, when you start a new chat outside the workspaces or pick this template in a new chat. A chat you start in a workspace on purpose, for example, from the workspace list, stays in that workspace.")
</MudJustifiedText>
@* While workspaces are off, nothing goes into a workspace, so choosing one here would promise what no chat does: *@
@if (this.AreWorkspacesDisabled)
{
<MudAlert Severity="Severity.Warning" Class="mb-3">
@T("Workspaces are switched off in your workspace settings. While they are off, chats with this template belong to no workspace, and they do not move into a workspace when you switch workspaces on again. You can change this choice once workspaces are on.")
</MudAlert>
}
<MudTextSwitch @bind-Value="@this.preselectWorkspace" Color="Color.Primary" Label="@T("Does this chat template keep its chats in a workspace?")" LabelOn="@T("Yes, chats with this template go into a workspace")" LabelOff="@T("No, chats go where they would go anyway")" Disabled="@this.IsWorkspaceChoiceLocked"/>
@if (this.preselectWorkspace)
{
<WorkspaceNameField @bind-WorkspaceName="@this.selectedWorkspaceName" SelectLabel="@T("Existing workspace (Optional)")" Label="@T("Workspace name")" HelperText="@T("Choose an existing workspace or enter a name that should be created when a chat with this template starts.")" Validation="@this.ValidateWorkspaceName" Immediate="@true" ReadOnly="@this.IsWorkspaceChoiceLocked" SelectClass="mb-3 mt-3 rounded-lg" TextFieldClass="mb-3 mt-3"/>
}
<MudText Typo="Typo.h6" Class="mb-3 mt-6"> <MudText Typo="Typo.h6" Class="mb-3 mt-6">
@T("Tools") @T("Tools")
@@ -79,6 +79,25 @@ public partial class ChatTemplateDialog : MSGComponentBase
[Parameter] [Parameter]
public DataSourceOptions? DataSourceOptions { get; set; } public DataSourceOptions? DataSourceOptions { get; set; }
/// <summary>
/// The profile this template preselects, the ID of the no-profile entry to start without one,
/// or null when it says nothing about profiles.
/// </summary>
[Parameter]
public string? ProfileId { get; set; }
/// <summary>
/// The provider this template preselects, or null when it says nothing about providers.
/// </summary>
[Parameter]
public string? ProviderId { get; set; }
/// <summary>
/// The name of the workspace this template keeps its chats in, or null when it says nothing about workspaces.
/// </summary>
[Parameter]
public string? WorkspaceName { get; set; }
[Parameter] [Parameter]
public bool CreateFromExistingChatThread { get; set; } public bool CreateFromExistingChatThread { get; set; }
@@ -105,6 +124,12 @@ public partial class ChatTemplateDialog : MSGComponentBase
private HashSet<string> selectedToolIds = new(StringComparer.Ordinal); private HashSet<string> selectedToolIds = new(StringComparer.Ordinal);
private bool preselectDataSources; private bool preselectDataSources;
private DataSourceOptions templateDataSourceOptions = new(); private DataSourceOptions templateDataSourceOptions = new();
private bool preselectProfile;
private string selectedProfileId = Profile.NO_PROFILE.Id;
private bool preselectProvider;
private string selectedProviderId = string.Empty;
private bool preselectWorkspace;
private string selectedWorkspaceName = string.Empty;
private string[] dataIssues = []; private string[] dataIssues = [];
private string dataEditingPreviousName = string.Empty; private string dataEditingPreviousName = string.Empty;
private bool isInlineEditOnGoing; private bool isInlineEditOnGoing;
@@ -138,6 +163,14 @@ public partial class ChatTemplateDialog : MSGComponentBase
// them, so the switch inside the selection starts on instead of at its usual default: // them, so the switch inside the selection starts on instead of at its usual default:
this.templateDataSourceOptions = this.DataSourceOptions?.CreateCopy() ?? new DataSourceOptions { DisableDataSources = false }; this.templateDataSourceOptions = this.DataSourceOptions?.CreateCopy() ?? new DataSourceOptions { DisableDataSources = false };
// The profile and the provider follow the same pattern, with a switch each:
this.preselectProfile = this.ProfileId is not null;
this.selectedProfileId = this.ProfileId ?? Profile.NO_PROFILE.Id;
this.preselectProvider = this.ProviderId is not null;
this.selectedProviderId = this.ProviderId ?? string.Empty;
this.preselectWorkspace = this.WorkspaceName is not null;
this.selectedWorkspaceName = this.WorkspaceName ?? string.Empty;
// When editing, we need to load the data: // When editing, we need to load the data:
if(this.IsEditing) if(this.IsEditing)
this.dataEditingPreviousName = this.DataName.ToLowerInvariant(); this.dataEditingPreviousName = this.DataName.ToLowerInvariant();
@@ -183,6 +216,11 @@ public partial class ChatTemplateDialog : MSGComponentBase
ToolIds = this.preselectTools ? new HashSet<string>(this.selectedToolIds, StringComparer.Ordinal) : null, ToolIds = this.preselectTools ? new HashSet<string>(this.selectedToolIds, StringComparer.Ordinal) : null,
DataSourceOptions = this.preselectDataSources ? this.templateDataSourceOptions.CreateCopy() : null, DataSourceOptions = this.preselectDataSources ? this.templateDataSourceOptions.CreateCopy() : null,
// A template which forbids profiles has no use for a profile, so none is kept for it:
ProfileId = this.AllowProfileUsage && this.preselectProfile ? this.selectedProfileId : null,
ProviderId = this.preselectProvider && !string.IsNullOrWhiteSpace(this.selectedProviderId) ? this.selectedProviderId : null,
WorkspaceName = this.preselectWorkspace && !string.IsNullOrWhiteSpace(this.selectedWorkspaceName) ? this.selectedWorkspaceName.Trim() : null,
EnterpriseConfigurationPluginId = Guid.Empty, EnterpriseConfigurationPluginId = Guid.Empty,
IsEnterpriseConfiguration = false, IsEnterpriseConfiguration = false,
}; };
@@ -205,6 +243,57 @@ public partial class ChatTemplateDialog : MSGComponentBase
private void SetSelectedToolIds(HashSet<string> toolIds) => this.selectedToolIds = toolIds; private void SetSelectedToolIds(HashSet<string> toolIds) => this.selectedToolIds = toolIds;
/// <summary>
/// Whether the preselected profile is gone, so that chats started with this template keep their usual profile.
/// </summary>
private bool IsPreselectedProfileMissing => this.preselectProfile
&& this.selectedProfileId != Profile.NO_PROFILE.Id
&& this.SettingsManager.GetProfileById(this.selectedProfileId) == Profile.NO_PROFILE;
/// <summary>
/// Whether the preselected provider is gone or may not be used for chats, so that chats started
/// with this template keep their usual provider.
/// </summary>
/// <remarks>
/// The list to pick from holds only the providers which may be used for chats. A template from
/// another machine or from a configuration plugin may name another one, though, and the list
/// alone would then show nothing about it.
/// </remarks>
private bool IsPreselectedProviderUnusable => this.preselectProvider
&& !string.IsNullOrWhiteSpace(this.selectedProviderId)
&& this.SettingsManager.GetChatTemplateProvider(this.selectedProviderId) == AIStudio.Settings.Provider.NONE;
/// <summary>
/// Whether workspaces are switched off, so that a workspace of this template has no effect for now.
/// </summary>
private bool AreWorkspacesDisabled => this.SettingsManager.ConfigurationData.Workspace.StorageBehavior is WorkspaceStorageBehavior.DISABLE_WORKSPACES;
/// <summary>
/// Whether the workspace of this template can be changed right now.
/// </summary>
/// <remarks>
/// Not while workspaces are switched off: a choice made then would take effect for no chat,
/// and nothing in the chat would say so. A workspace the template already names stays as it
/// is, so it applies again once workspaces are back.
/// </remarks>
private bool IsWorkspaceChoiceLocked => this.IsReadOnly || this.AreWorkspacesDisabled;
private string? ValidateWorkspaceName(string workspaceName)
{
if (this.preselectWorkspace && string.IsNullOrWhiteSpace(workspaceName))
return T("Please enter the workspace for the chats of this template, or switch the preselection off.");
return null;
}
private string? ValidatePreselectedProvider(string providerId)
{
if (this.preselectProvider && string.IsNullOrWhiteSpace(providerId))
return T("Please choose the provider this chat template preselects, or switch the preselection off.");
return null;
}
private void RemoveMessage(ContentBlock item) private void RemoveMessage(ContentBlock item)
{ {
if (this.IsReadOnly) if (this.IsReadOnly)
@@ -47,31 +47,7 @@
<MudJustifiedText Typo="Typo.body1" Class="mb-3"> <MudJustifiedText Typo="Typo.body1" Class="mb-3">
@T("Select a root directory for this data source.") @T("Select a root directory for this data source.")
</MudJustifiedText> </MudJustifiedText>
@if (!this.CanChangeSource)
{
<MudAlert Severity="Severity.Info" Variant="Variant.Outlined" Class="mb-3">
@T("The documents of this data source are already prepared, so its folder cannot be changed. Another folder holds other documents, which makes it another data source: please add one for it. The embedding provider below can be changed.")
</MudAlert>
}
@if (this.CanChangeSource)
{
<SelectDirectory @bind-Directory="@this.dataPath" Label="@T("Selected base directory for this data source")" DirectoryDialogTitle="@T("Select the base directory")" Validation="@this.dataSourceValidation.ValidatePath" EnableDragDrop="true" CatchAllDocuments="true" /> <SelectDirectory @bind-Directory="@this.dataPath" Label="@T("Selected base directory for this data source")" DirectoryDialogTitle="@T("Select the base directory")" Validation="@this.dataSourceValidation.ValidatePath" EnableDragDrop="true" CatchAllDocuments="true" />
}
else
{
<MudTextField
T="string"
Text="@this.dataPath"
Label="@T("Selected base directory for this data source")"
Class="mb-3"
ReadOnly="@true"
Validation="@this.dataSourceValidation.ValidatePath"
Adornment="Adornment.Start"
AdornmentIcon="@Icons.Material.Filled.Folder"
UserAttributes="@SPELLCHECK_ATTRIBUTES"
Variant="Variant.Outlined"
/>
}
<MudTextSwitch <MudTextSwitch
Label="@T("Include subfolders?")" Label="@T("Include subfolders?")"
@@ -23,16 +23,6 @@ public partial class DataSourceLocalDirectoryDialog : MSGComponentBase
[Parameter] [Parameter]
public DataSourceLocalDirectory DataSource { get; set; } public DataSourceLocalDirectory DataSource { get; set; }
/// <summary>
/// Whether the folder this data source reads must stay as it is.
/// </summary>
/// <remarks>
/// Set once the index holds something for this data source. The embedding is not locked along
/// with it: it can be changed, and DataSourceReindexWarning asks what that costs.
/// </remarks>
[Parameter]
public bool LockSource { get; set; }
[Inject] [Inject]
private IDialogService DialogService { get; init; } = null!; private IDialogService DialogService { get; init; } = null!;
@@ -215,8 +205,6 @@ public partial class DataSourceLocalDirectoryDialog : MSGComponentBase
? T("Audio and video files which this data source selects are skipped, because your transcription provider does not meet the confidence level this data source requires.") ? T("Audio and video files which this data source selects are skipped, because your transcription provider does not meet the confidence level this data source requires.")
: null; : null;
private bool CanChangeSource => !this.IsEditing || !this.LockSource;
private IEnumerable<ConfigurationSelectData<ConfidenceLevel>> ConfidenceLevels => ConfigurationSelectDataFactory.GetDataSourceConfidenceLevelsData(); private IEnumerable<ConfigurationSelectData<ConfidenceLevel>> ConfidenceLevels => ConfigurationSelectDataFactory.GetDataSourceConfidenceLevelsData();
private string SelectedEmbeddingTokenizerText => this.SelectedEmbedding is null private string SelectedEmbeddingTokenizerText => this.SelectedEmbedding is null
@@ -244,9 +232,7 @@ public partial class DataSourceLocalDirectoryDialog : MSGComponentBase
Type = DataSourceType.LOCAL_DIRECTORY, Type = DataSourceType.LOCAL_DIRECTORY,
EmbeddingId = this.dataEmbeddingId, EmbeddingId = this.dataEmbeddingId,
// Kept out of reach of the form while the source is locked, so a stale field cannot point an Path = this.dataPath,
// indexed data source somewhere else:
Path = this.CanChangeSource ? this.dataPath : this.DataSource.Path,
FilePatterns = this.NormalizeFilePatterns(), FilePatterns = this.NormalizeFilePatterns(),
// Kept as typed, since the line breaks group the entries for the eye: // Kept as typed, since the line breaks group the entries for the eye:
@@ -268,6 +254,14 @@ public partial class DataSourceLocalDirectoryDialog : MSGComponentBase
if (!this.dataIsValid) if (!this.dataIsValid)
return; return;
await this.StoreValidatedDataSourceAsync();
}
/// <summary>
/// Continues Save after form validation, asking before the draft is returned to its caller.
/// </summary>
internal async Task StoreValidatedDataSourceAsync()
{
var addedDataSource = this.CreateDataSource(); var addedDataSource = this.CreateDataSource();
// //
@@ -47,31 +47,7 @@
<MudJustifiedText Typo="Typo.body1" Class="mb-3"> <MudJustifiedText Typo="Typo.body1" Class="mb-3">
@T("Select a file for this data source. The content of this file will be processed for the data source. An audio or video file is transcribed by your transcription provider first.") @T("Select a file for this data source. The content of this file will be processed for the data source. An audio or video file is transcribed by your transcription provider first.")
</MudJustifiedText> </MudJustifiedText>
@if (!this.CanChangeSource)
{
<MudAlert Severity="Severity.Info" Variant="Variant.Outlined" Class="mb-3">
@T("The documents of this data source are already prepared, so its file cannot be changed. Another file holds other content, which makes it another data source: please add one for it. The embedding provider below can be changed.")
</MudAlert>
}
@if (this.CanChangeSource)
{
<SelectFile @bind-File="@this.dataFilePath" Label="@T("Selected file path for this data source")" FileDialogTitle="@T("Select the file")" Validation="@this.dataSourceValidation.ValidateFilePath" EnableDragDrop="true" CatchAllDocuments="true" /> <SelectFile @bind-File="@this.dataFilePath" Label="@T("Selected file path for this data source")" FileDialogTitle="@T("Select the file")" Validation="@this.dataSourceValidation.ValidateFilePath" EnableDragDrop="true" CatchAllDocuments="true" />
}
else
{
<MudTextField
T="string"
Text="@this.dataFilePath"
Label="@T("Selected file path for this data source")"
Class="mb-3"
ReadOnly="@true"
Validation="@this.dataSourceValidation.ValidateFilePath"
Adornment="Adornment.Start"
AdornmentIcon="@Icons.Material.Filled.AttachFile"
UserAttributes="@SPELLCHECK_ATTRIBUTES"
Variant="Variant.Outlined"
/>
}
<MudJustifiedText Typo="Typo.body1" Class="mb-3"> <MudJustifiedText Typo="Typo.body1" Class="mb-3">
@T("In order for the AI to be able to determine the appropriate data at any time, you must choose an embedding provider.") @T("In order for the AI to be able to determine the appropriate data at any time, you must choose an embedding provider.")
@@ -24,16 +24,6 @@ public partial class DataSourceLocalFileDialog : MSGComponentBase
[Parameter] [Parameter]
public DataSourceLocalFile DataSource { get; set; } public DataSourceLocalFile DataSource { get; set; }
/// <summary>
/// Whether the file this data source reads must stay as it is.
/// </summary>
/// <remarks>
/// Set once the index holds something for this data source. The embedding is not locked along
/// with it: it can be changed, and DataSourceReindexWarning asks what that costs.
/// </remarks>
[Parameter]
public bool LockSource { get; set; }
[Inject] [Inject]
private IDialogService DialogService { get; init; } = null!; private IDialogService DialogService { get; init; } = null!;
@@ -206,8 +196,6 @@ public partial class DataSourceLocalFileDialog : MSGComponentBase
? T("This audio or video file is skipped, because your transcription provider does not meet the confidence level this data source requires.") ? T("This audio or video file is skipped, because your transcription provider does not meet the confidence level this data source requires.")
: null; : null;
private bool CanChangeSource => !this.IsEditing || !this.LockSource;
private IEnumerable<ConfigurationSelectData<ConfidenceLevel>> ConfidenceLevels => ConfigurationSelectDataFactory.GetDataSourceConfidenceLevelsData(); private IEnumerable<ConfigurationSelectData<ConfidenceLevel>> ConfidenceLevels => ConfigurationSelectDataFactory.GetDataSourceConfidenceLevelsData();
private string SelectedEmbeddingTokenizerText => this.SelectedEmbedding is null private string SelectedEmbeddingTokenizerText => this.SelectedEmbedding is null
@@ -235,9 +223,7 @@ public partial class DataSourceLocalFileDialog : MSGComponentBase
Type = DataSourceType.LOCAL_FILE, Type = DataSourceType.LOCAL_FILE,
EmbeddingId = this.dataEmbeddingId, EmbeddingId = this.dataEmbeddingId,
// Kept out of reach of the form while the source is locked, so a stale field cannot point an FilePath = this.dataFilePath,
// indexed data source somewhere else:
FilePath = this.CanChangeSource ? this.dataFilePath : this.DataSource.FilePath,
MaxChunkTokenLength = this.dataMaxChunkTokenLength, MaxChunkTokenLength = this.dataMaxChunkTokenLength,
ChunkOverlapTokenLength = this.dataChunkOverlapTokenLength, ChunkOverlapTokenLength = this.dataChunkOverlapTokenLength,
ConfidenceLevel = this.dataConfidenceLevel, ConfidenceLevel = this.dataConfidenceLevel,
@@ -252,6 +238,14 @@ public partial class DataSourceLocalFileDialog : MSGComponentBase
if (!this.dataIsValid) if (!this.dataIsValid)
return; return;
await this.StoreValidatedDataSourceAsync();
}
/// <summary>
/// Continues Save after form validation, asking before the draft is returned to its caller.
/// </summary>
internal async Task StoreValidatedDataSourceAsync()
{
var addedDataSource = this.CreateDataSource(); var addedDataSource = this.CreateDataSource();
// //
@@ -6,6 +6,12 @@
@inherits MSGComponentBase @inherits MSGComponentBase
<MudDialog> <MudDialog>
<TitleContent>
<PreviewPrototype/>
<MudText Typo="Typo.h6">
@this.MudDialog.Title
</MudText>
</TitleContent>
<DialogContent> <DialogContent>
<MudForm @ref="@this.form" @bind-IsValid="@this.dataIsValid" @bind-Errors="@this.dataIssues" FieldChanged="@this.RevalidateAfterFieldChange"> <MudForm @ref="@this.form" @bind-IsValid="@this.dataIsValid" @bind-Errors="@this.dataIssues" FieldChanged="@this.RevalidateAfterFieldChange">
@if (this.IsAddingNotAllowed) @if (this.IsAddingNotAllowed)
@@ -25,11 +25,19 @@
} }
} }
</MudSelect> </MudSelect>
<MudButton Disabled="@(!this.DataLLMProvider.ShowRegisterButton())" Variant="Variant.Filled" Size="Size.Small" StartIcon="@Icons.Material.Filled.OpenInBrowser" Href="@this.DataLLMProvider.GetCreationURL()" Target="_blank"> <MudButton Disabled="@(!this.DataLLMProvider.ShowRegisterButton() || this.IsProviderProhibited)" Variant="Variant.Filled" Size="Size.Small" StartIcon="@Icons.Material.Filled.OpenInBrowser" Href="@this.DataLLMProvider.GetCreationURL()" Target="_blank">
@T("Create account") @T("Create account")
</MudButton> </MudButton>
</MudStack> </MudStack>
@if (this.IsProviderProhibited)
{
<MudAlert Severity="Severity.Warning" Class="mb-3">
@T("The use of this provider has been prohibited by your organization. Please select a different provider.")
</MudAlert>
}
else
{
@if (this.DataLLMProvider.IsAPIKeyNeeded(this.DataHost)) @if (this.DataLLMProvider.IsAPIKeyNeeded(this.DataHost))
{ {
<SecretInputField Secret="@this.dataAPIKey" SecretChanged="@this.OnAPIKeyChanged" Label="@this.APIKeyText" Validation="@this.providerValidation.ValidatingAPIKey"/> <SecretInputField Secret="@this.dataAPIKey" SecretChanged="@this.OnAPIKeyChanged" Label="@this.APIKeyText" Validation="@this.providerValidation.ValidatingAPIKey"/>
@@ -211,8 +219,9 @@
</MudCollapse> </MudCollapse>
</MudStack> </MudStack>
} }
}
</MudForm> </MudForm>
@if (this.dataStoreWasAttempted) @if (this.dataStoreWasAttempted && !this.IsProviderProhibited)
{ {
<Issues IssuesData="@this.dataIssues"/> <Issues IssuesData="@this.dataIssues"/>
} }
@@ -221,7 +230,7 @@
<MudButton OnClick="@this.Cancel" Variant="Variant.Filled"> <MudButton OnClick="@this.Cancel" Variant="Variant.Filled">
@T("Cancel") @T("Cancel")
</MudButton> </MudButton>
<MudButton OnClick="@this.Store" Variant="Variant.Filled" Color="Color.Primary"> <MudButton OnClick="@this.Store" Variant="Variant.Filled" Color="Color.Primary" Disabled="@this.IsProviderProhibited">
@if (this.IsEditing) @if (this.IsEditing)
{ {
@T("Update") @T("Update")
@@ -136,6 +136,15 @@ public partial class EmbeddingProviderDialog : MSGComponentBase, ISecretId
/// </summary> /// </summary>
private List<string> UsedInstanceNames { get; set; } = []; private List<string> UsedInstanceNames { get; set; } = [];
/// <summary>
/// Whether the organization prohibited the selected provider.
/// </summary>
/// <remarks>
/// The provider stays selectable, so that nobody wonders where it went. The dialog then shows
/// the reason instead of the form, and nothing can be stored.
/// </remarks>
private bool IsProviderProhibited => this.SettingsManager.IsProviderProhibited(this.DataLLMProvider);
private bool dataIsValid; private bool dataIsValid;
private string[] dataIssues = []; private string[] dataIssues = [];
private string dataAPIKey = string.Empty; private string dataAPIKey = string.Empty;
@@ -284,6 +293,9 @@ public partial class EmbeddingProviderDialog : MSGComponentBase, ISecretId
private async Task Store() private async Task Store()
{ {
if (this.IsProviderProhibited)
return;
this.dataStoreWasAttempted = true; this.dataStoreWasAttempted = true;
await this.dataTokenizerValidationTask; await this.dataTokenizerValidationTask;
await this.form.Validate(); await this.form.Validate();
@@ -544,6 +556,10 @@ public partial class EmbeddingProviderDialog : MSGComponentBase, ISecretId
private async Task ReloadModels() private async Task ReloadModels()
{ {
// Loading the models would contact the provider:
if (this.IsProviderProhibited)
return;
this.dataLoadingModelsIssue = string.Empty; this.dataLoadingModelsIssue = string.Empty;
this.dataServerWasAskedForItsModels = true; this.dataServerWasAskedForItsModels = true;
var currentEmbeddingProviderSettings = this.CreateEmbeddingProviderSettings(); var currentEmbeddingProviderSettings = this.CreateEmbeddingProviderSettings();
@@ -27,11 +27,19 @@
</MudSelectItem> </MudSelectItem>
} }
</MudSelect> </MudSelect>
<MudButton Disabled="@(!this.DataLLMProvider.ShowRegisterButton())" Variant="Variant.Filled" Size="Size.Small" StartIcon="@Icons.Material.Filled.OpenInBrowser" Href="@this.DataLLMProvider.GetCreationURL()" Target="_blank"> <MudButton Disabled="@(!this.DataLLMProvider.ShowRegisterButton() || this.IsProviderProhibited)" Variant="Variant.Filled" Size="Size.Small" StartIcon="@Icons.Material.Filled.OpenInBrowser" Href="@this.DataLLMProvider.GetCreationURL()" Target="_blank">
@T("Create account") @T("Create account")
</MudButton> </MudButton>
</MudStack> </MudStack>
@if (this.IsProviderProhibited)
{
<MudAlert Severity="Severity.Warning" Class="mb-3">
@T("The use of this provider has been prohibited by your organization. Please select a different provider.")
</MudAlert>
}
else
{
@if (this.DataLLMProvider.IsAPIKeyNeeded(this.DataHost)) @if (this.DataLLMProvider.IsAPIKeyNeeded(this.DataHost))
{ {
<SecretInputField Secret="@this.dataAPIKey" SecretChanged="@this.OnAPIKeyChanged" Label="@this.APIKeyText" Validation="@this.providerValidation.ValidatingAPIKey"/> <SecretInputField Secret="@this.dataAPIKey" SecretChanged="@this.OnAPIKeyChanged" Label="@this.APIKeyText" Validation="@this.providerValidation.ValidatingAPIKey"/>
@@ -339,14 +347,18 @@
} }
</MudCollapse> </MudCollapse>
</MudStack> </MudStack>
}
</MudForm> </MudForm>
@if (!this.IsProviderProhibited)
{
<Issues IssuesData="@this.dataIssues"/> <Issues IssuesData="@this.dataIssues"/>
}
</DialogContent> </DialogContent>
<DialogActions> <DialogActions>
<MudButton OnClick="@this.Cancel" Variant="Variant.Filled"> <MudButton OnClick="@this.Cancel" Variant="Variant.Filled">
@T("Cancel") @T("Cancel")
</MudButton> </MudButton>
<MudButton OnClick="@this.Store" Variant="Variant.Filled" Color="Color.Primary"> <MudButton OnClick="@this.Store" Variant="Variant.Filled" Color="Color.Primary" Disabled="@this.IsProviderProhibited">
@if(this.IsEditing) @if(this.IsEditing)
{ {
@T("Update") @T("Update")
@@ -154,6 +154,15 @@ public partial class ProviderDialog : MSGComponentBase, ISecretId
/// </summary> /// </summary>
private List<string> UsedInstanceNames { get; set; } = []; private List<string> UsedInstanceNames { get; set; } = [];
/// <summary>
/// Whether the organization prohibited the selected provider.
/// </summary>
/// <remarks>
/// The provider stays selectable, so that nobody wonders where it went. The dialog then shows
/// the reason instead of the form, and nothing can be stored.
/// </remarks>
private bool IsProviderProhibited => this.SettingsManager.IsProviderProhibited(this.DataLLMProvider);
private bool dataIsValid; private bool dataIsValid;
private string[] dataIssues = []; private string[] dataIssues = [];
private string dataAPIKey = string.Empty; private string dataAPIKey = string.Empty;
@@ -333,6 +342,9 @@ public partial class ProviderDialog : MSGComponentBase, ISecretId
private async Task Store() private async Task Store()
{ {
if (this.IsProviderProhibited)
return;
this.dataStoreWasAttempted = true; this.dataStoreWasAttempted = true;
await this.dataTokenizerValidationTask; await this.dataTokenizerValidationTask;
await this.form.Validate(); await this.form.Validate();
@@ -593,6 +605,10 @@ public partial class ProviderDialog : MSGComponentBase, ISecretId
private async Task ReloadModels() private async Task ReloadModels()
{ {
// Loading the models would contact the provider:
if (this.IsProviderProhibited)
return;
this.dataLoadingModelsIssue = string.Empty; this.dataLoadingModelsIssue = string.Empty;
var currentProviderSettings = this.CreateProviderSettings(); var currentProviderSettings = this.CreateProviderSettings();
var provider = currentProviderSettings.CreateProvider(); var provider = currentProviderSettings.CreateProvider();
@@ -0,0 +1,26 @@
@using AIStudio.Settings
@inherits SettingsDialogBase
<MudDialog>
<TitleContent>
<MudText Typo="Typo.h6" Class="d-flex align-center">
<MudIcon Icon="@Icons.Material.Filled.AutoMode" Class="mr-2"/>
@T("Assistant: Assistant Builder defaults")
</MudText>
</TitleContent>
<DialogContent>
<MudPaper Class="pa-3 mb-8 border-dashed border rounded-lg">
<ConfigurationOption OptionDescription="@T("Preselect Assistant Builder options?")" LabelOn="@T("Assistant Builder options are preselected")" LabelOff="@T("No Assistant Builder options are preselected")" State="@(() => this.SettingsManager.ConfigurationData.AssistantBuilder.PreselectOptions)" StateUpdate="@(value => this.SettingsManager.ConfigurationData.AssistantBuilder.PreselectOptions = value)" OptionHelp="@T("When enabled, the Assistant Builder starts with the defaults configured below. When disabled, it uses the app-wide default provider and preselects no output language.")" IsLocked="() => ManagedConfiguration.TryGet(x => x.AssistantBuilder, x => x.PreselectOptions, out var meta) && meta.IsLocked"/>
<ConfigurationSelect OptionDescription="@T("Default output language")" Disabled="@this.DefaultsDisabled" SelectedValue="@(() => this.SettingsManager.ConfigurationData.AssistantBuilder.PreselectedOutputLanguage)" Data="@ConfigurationSelectDataFactory.GetCommonLanguagesOptionalData()" SelectionUpdate="@(value => this.SettingsManager.ConfigurationData.AssistantBuilder.PreselectedOutputLanguage = value)" OptionHelp="@T("The language in which the assistants you build answer.")" IsLocked="() => ManagedConfiguration.TryGet(x => x.AssistantBuilder, x => x.PreselectedOutputLanguage, out var meta) && meta.IsLocked"/>
@if (this.SettingsManager.ConfigurationData.AssistantBuilder.PreselectedOutputLanguage is CommonLanguages.OTHER)
{
<ConfigurationText OptionDescription="@T("Default custom output language")" Disabled="@this.DefaultsDisabled" Icon="@Icons.Material.Filled.Translate" Text="@(() => this.SettingsManager.ConfigurationData.AssistantBuilder.PreselectedOtherOutputLanguage)" TextUpdate="@(value => this.SettingsManager.ConfigurationData.AssistantBuilder.PreselectedOtherOutputLanguage = value)" IsLocked="() => ManagedConfiguration.TryGet(x => x.AssistantBuilder, x => x.PreselectedOtherOutputLanguage, out var meta) && meta.IsLocked"/>
}
<ConfigurationMinConfidenceSelection Disabled="@this.DefaultsDisabled" RestrictToGlobalMinimumConfidence="@true" SelectedValue="@(() => this.SettingsManager.ConfigurationData.AssistantBuilder.MinimumProviderConfidence)" SelectionUpdate="@(value => this.SettingsManager.ConfigurationData.AssistantBuilder.MinimumProviderConfidence = value)" IsLocked="() => ManagedConfiguration.TryGet(x => x.AssistantBuilder, x => x.MinimumProviderConfidence, out var meta) && meta.IsLocked"/>
<ConfigurationProviderSelection Component="Components.META_ASSISTANT" Data="@this.AvailableLLMProviders" Disabled="@this.DefaultsDisabled" SelectedValue="@(() => this.SettingsManager.ConfigurationData.AssistantBuilder.PreselectedProvider)" SelectionUpdate="@(value => this.SettingsManager.ConfigurationData.AssistantBuilder.PreselectedProvider = value)" IsLocked="() => ManagedConfiguration.TryGet(x => x.AssistantBuilder, x => x.PreselectedProvider, out var meta) && meta.IsLocked"/>
</MudPaper>
</DialogContent>
<DialogActions>
<MudButton OnClick="@this.Close" Variant="Variant.Filled">@T("Close")</MudButton>
</DialogActions>
</MudDialog>
@@ -0,0 +1,6 @@
namespace AIStudio.Dialogs.Settings;
public partial class SettingsDialogAssistantBuilder : SettingsDialogBase
{
private bool DefaultsDisabled() => !this.SettingsManager.ConfigurationData.AssistantBuilder.PreselectOptions;
}
@@ -21,7 +21,7 @@
<ConfigurationDirectory OptionDescription="@T("Default input folder")" Disabled="@this.DefaultsDisabled" DirectoryDialogTitle="@T("Select the default input folder")" Text="@(() => this.SettingsManager.ConfigurationData.BatchProcessing.InputDirectory)" TextUpdate="@(value => this.SettingsManager.ConfigurationData.BatchProcessing.InputDirectory = value)" OptionHelp="@T("Leave empty when an input folder should be selected for every batch run.")" IsLocked="() => ManagedConfiguration.TryGet(x => x.BatchProcessing, x => x.InputDirectory, out var meta) && meta.IsLocked"/> <ConfigurationDirectory OptionDescription="@T("Default input folder")" Disabled="@this.DefaultsDisabled" DirectoryDialogTitle="@T("Select the default input folder")" Text="@(() => this.SettingsManager.ConfigurationData.BatchProcessing.InputDirectory)" TextUpdate="@(value => this.SettingsManager.ConfigurationData.BatchProcessing.InputDirectory = value)" OptionHelp="@T("Leave empty when an input folder should be selected for every batch run.")" IsLocked="() => ManagedConfiguration.TryGet(x => x.BatchProcessing, x => x.InputDirectory, out var meta) && meta.IsLocked"/>
<ConfigurationOption OptionDescription="@T("Include subfolders by default?")" Disabled="@this.DefaultsDisabled" LabelOn="@T("Subfolders are included")" LabelOff="@T("Only the selected folder is processed")" State="@(() => this.SettingsManager.ConfigurationData.BatchProcessing.IncludeSubdirectories)" StateUpdate="@(value => this.SettingsManager.ConfigurationData.BatchProcessing.IncludeSubdirectories = value)" IsLocked="() => ManagedConfiguration.TryGet(x => x.BatchProcessing, x => x.IncludeSubdirectories, out var meta) && meta.IsLocked"/> <ConfigurationOption OptionDescription="@T("Include subfolders by default?")" Disabled="@this.DefaultsDisabled" LabelOn="@T("Subfolders are included")" LabelOff="@T("Only the selected folder is processed")" State="@(() => this.SettingsManager.ConfigurationData.BatchProcessing.IncludeSubdirectories)" StateUpdate="@(value => this.SettingsManager.ConfigurationData.BatchProcessing.IncludeSubdirectories = value)" IsLocked="() => ManagedConfiguration.TryGet(x => x.BatchProcessing, x => x.IncludeSubdirectories, out var meta) && meta.IsLocked"/>
<ConfigurationOption OptionDescription="@T("Include hidden files and folders by default?")" Disabled="@this.DefaultsDisabled" LabelOn="@T("Hidden files and folders are included")" LabelOff="@T("Hidden files and folders are ignored")" State="@(() => this.SettingsManager.ConfigurationData.BatchProcessing.IncludeHiddenFilesAndFolders)" StateUpdate="@(value => this.SettingsManager.ConfigurationData.BatchProcessing.IncludeHiddenFilesAndFolders = value)" OptionHelp="@T("Hidden are files and folders whose names start with a dot, such as .git or .env, and those hidden by the operating system. A file pattern which starts with a dot, such as .gitignore, selects such files anyway.")" IsLocked="() => ManagedConfiguration.TryGet(x => x.BatchProcessing, x => x.IncludeHiddenFilesAndFolders, out var meta) && meta.IsLocked"/> <ConfigurationOption OptionDescription="@T("Include hidden files and folders by default?")" Disabled="@this.DefaultsDisabled" LabelOn="@T("Hidden files and folders are included")" LabelOff="@T("Hidden files and folders are ignored")" State="@(() => this.SettingsManager.ConfigurationData.BatchProcessing.IncludeHiddenFilesAndFolders)" StateUpdate="@(value => this.SettingsManager.ConfigurationData.BatchProcessing.IncludeHiddenFilesAndFolders = value)" OptionHelp="@T("Hidden are files and folders whose names start with a dot, such as .git or .env, and those hidden by the operating system. A file pattern which starts with a dot, such as .gitignore, selects such files anyway.")" IsLocked="() => ManagedConfiguration.TryGet(x => x.BatchProcessing, x => x.IncludeHiddenFilesAndFolders, out var meta) && meta.IsLocked"/>
<ConfigurationText OptionDescription="@T("Default file patterns")" Disabled="@this.DefaultsDisabled" Icon="@Icons.Material.Filled.FilterAlt" Text="@(() => this.SettingsManager.ConfigurationData.BatchProcessing.FilePatterns)" TextUpdate="@(value => this.SettingsManager.ConfigurationData.BatchProcessing.FilePatterns = value)" ResetValue="@(() => DataBatchProcessing.DEFAULT_FILE_PATTERNS)" ResetButtonText="@T("Restore default patterns")" OptionHelp="@T("Separate multiple file patterns with a semicolon, e.g., *.pdf;*.docx. The standard patterns include all supported audio and video formats.")" IsLocked="() => ManagedConfiguration.TryGet(x => x.BatchProcessing, x => x.FilePatterns, out var meta) && meta.IsLocked"/> <ConfigurationText OptionDescription="@T("Default file patterns")" Disabled="@this.DefaultsDisabled" Icon="@Icons.Material.Filled.FilterAlt" Text="@(() => this.SettingsManager.ConfigurationData.BatchProcessing.FilePatterns)" TextUpdate="@(value => this.SettingsManager.ConfigurationData.BatchProcessing.FilePatterns = value)" ResetValue="@(() => DataBatchProcessing.DEFAULT_FILE_PATTERNS)" ResetButtonText="@T("Restore default patterns")" OptionHelp="@T("Separate multiple file patterns with a semicolon, e.g., *.pdf;*.docx. The standard patterns include all supported audio and video formats as well as images in JPG, PNG, WebP, GIF, BMP, and TIFF format.")" IsLocked="() => ManagedConfiguration.TryGet(x => x.BatchProcessing, x => x.FilePatterns, out var meta) && meta.IsLocked"/>
<ConfigurationText OptionDescription="@T("Default excluded files and folders")" Disabled="@this.DefaultsDisabled" Icon="@Icons.Material.Filled.FilterAltOff" Text="@(() => this.SettingsManager.ConfigurationData.BatchProcessing.ExcludePatterns)" TextUpdate="@(value => this.SettingsManager.ConfigurationData.BatchProcessing.ExcludePatterns = value)" NumLines="@FileExclusions.DEFAULT_LINE_COUNT" MaxLines="@FileExclusions.MAX_LINE_COUNT" ResetValue="@(() => FileExclusions.DEFAULT_PATTERNS)" ResetButtonText="@T("Restore default")" OptionHelp="@T("These files and folders are left out, even when a file pattern selects them. Separate entries with a semicolon or a line break. End an entry with a slash to exclude every folder of that name, e.g., node_modules/. Other entries name files, e.g., *.min.js.")" IsLocked="() => ManagedConfiguration.TryGet(x => x.BatchProcessing, x => x.ExcludePatterns, out var meta) && meta.IsLocked"/> <ConfigurationText OptionDescription="@T("Default excluded files and folders")" Disabled="@this.DefaultsDisabled" Icon="@Icons.Material.Filled.FilterAltOff" Text="@(() => this.SettingsManager.ConfigurationData.BatchProcessing.ExcludePatterns)" TextUpdate="@(value => this.SettingsManager.ConfigurationData.BatchProcessing.ExcludePatterns = value)" NumLines="@FileExclusions.DEFAULT_LINE_COUNT" MaxLines="@FileExclusions.MAX_LINE_COUNT" ResetValue="@(() => FileExclusions.DEFAULT_PATTERNS)" ResetButtonText="@T("Restore default")" OptionHelp="@T("These files and folders are left out, even when a file pattern selects them. Separate entries with a semicolon or a line break. End an entry with a slash to exclude every folder of that name, e.g., node_modules/. Other entries name files, e.g., *.min.js.")" IsLocked="() => ManagedConfiguration.TryGet(x => x.BatchProcessing, x => x.ExcludePatterns, out var meta) && meta.IsLocked"/>
<ConfigurationNumber T="int" OptionDescription="@T("Ignore files larger than")" Min="@FolderFileSelection.SMALLEST_MAX_FILE_SIZE_MEGABYTES" Unit="MB" Disabled="@this.DefaultsDisabled" Value="@(() => this.SettingsManager.ConfigurationData.BatchProcessing.MaxFileSizeMegabytes)" ValueUpdate="@(value => this.SettingsManager.ConfigurationData.BatchProcessing.MaxFileSizeMegabytes = value)" OptionHelp="@T("Matching files above this size are left out of a batch run, e.g., images of virtual machines.")" IsLocked="() => ManagedConfiguration.TryGet(x => x.BatchProcessing, x => x.MaxFileSizeMegabytes, out var meta) && meta.IsLocked"/> <ConfigurationNumber T="int" OptionDescription="@T("Ignore files larger than")" Min="@FolderFileSelection.SMALLEST_MAX_FILE_SIZE_MEGABYTES" Unit="MB" Disabled="@this.DefaultsDisabled" Value="@(() => this.SettingsManager.ConfigurationData.BatchProcessing.MaxFileSizeMegabytes)" ValueUpdate="@(value => this.SettingsManager.ConfigurationData.BatchProcessing.MaxFileSizeMegabytes = value)" OptionHelp="@T("Matching files above this size are left out of a batch run, e.g., images of virtual machines.")" IsLocked="() => ManagedConfiguration.TryGet(x => x.BatchProcessing, x => x.MaxFileSizeMegabytes, out var meta) && meta.IsLocked"/>
@@ -114,6 +114,9 @@ public partial class SettingsDialogChatTemplate : SettingsDialogBase
{ x => x.AllowProfileUsage, chatTemplate.AllowProfileUsage }, { x => x.AllowProfileUsage, chatTemplate.AllowProfileUsage },
{ x => x.ToolIds, chatTemplate.ToolIds }, { x => x.ToolIds, chatTemplate.ToolIds },
{ x => x.DataSourceOptions, chatTemplate.DataSourceOptions }, { x => x.DataSourceOptions, chatTemplate.DataSourceOptions },
{ x => x.ProfileId, chatTemplate.ProfileId },
{ x => x.ProviderId, chatTemplate.ProviderId },
{ x => x.WorkspaceName, chatTemplate.WorkspaceName },
}; };
private async Task DeleteChatTemplate(ChatTemplate chatTemplate) private async Task DeleteChatTemplate(ChatTemplate chatTemplate)
@@ -25,11 +25,19 @@
} }
} }
</MudSelect> </MudSelect>
<MudButton Disabled="@(!this.DataLLMProvider.ShowRegisterButton())" Variant="Variant.Filled" Size="Size.Small" StartIcon="@Icons.Material.Filled.OpenInBrowser" Href="@this.DataLLMProvider.GetCreationURL()" Target="_blank"> <MudButton Disabled="@(!this.DataLLMProvider.ShowRegisterButton() || this.IsProviderProhibited)" Variant="Variant.Filled" Size="Size.Small" StartIcon="@Icons.Material.Filled.OpenInBrowser" Href="@this.DataLLMProvider.GetCreationURL()" Target="_blank">
@T("Create account") @T("Create account")
</MudButton> </MudButton>
</MudStack> </MudStack>
@if (this.IsProviderProhibited)
{
<MudAlert Severity="Severity.Warning" Class="mb-3">
@T("The use of this provider has been prohibited by your organization. Please select a different provider.")
</MudAlert>
}
else
{
@if (this.DataLLMProvider.IsAPIKeyNeeded(this.DataHost)) @if (this.DataLLMProvider.IsAPIKeyNeeded(this.DataHost))
{ {
<SecretInputField Secret="@this.dataAPIKey" SecretChanged="@this.OnAPIKeyChanged" Label="@this.APIKeyText" Validation="@this.providerValidation.ValidatingAPIKey"/> <SecretInputField Secret="@this.dataAPIKey" SecretChanged="@this.OnAPIKeyChanged" Label="@this.APIKeyText" Validation="@this.providerValidation.ValidatingAPIKey"/>
@@ -144,15 +152,19 @@
Validation="@this.providerValidation.ValidatingInstanceName" Validation="@this.providerValidation.ValidatingInstanceName"
UserAttributes="@SPELLCHECK_ATTRIBUTES" UserAttributes="@SPELLCHECK_ATTRIBUTES"
/> />
}
</MudForm> </MudForm>
@if (!this.IsProviderProhibited)
{
<Issues IssuesData="@this.dataIssues"/> <Issues IssuesData="@this.dataIssues"/>
}
</DialogContent> </DialogContent>
<DialogActions> <DialogActions>
<MudButton OnClick="@this.Cancel" Variant="Variant.Filled"> <MudButton OnClick="@this.Cancel" Variant="Variant.Filled">
@T("Cancel") @T("Cancel")
</MudButton> </MudButton>
<MudButton OnClick="@this.Store" Variant="Variant.Filled" Color="Color.Primary"> <MudButton OnClick="@this.Store" Variant="Variant.Filled" Color="Color.Primary" Disabled="@this.IsProviderProhibited">
@if(this.IsEditing) @if(this.IsEditing)
{ {
@T("Update") @T("Update")
@@ -108,6 +108,15 @@ public partial class TranscriptionProviderDialog : MSGComponentBase, ISecretId
/// </summary> /// </summary>
private List<string> UsedInstanceNames { get; set; } = []; private List<string> UsedInstanceNames { get; set; } = [];
/// <summary>
/// Whether the organization prohibited the selected provider.
/// </summary>
/// <remarks>
/// The provider stays selectable, so that nobody wonders where it went. The dialog then shows
/// the reason instead of the form, and nothing can be stored.
/// </remarks>
private bool IsProviderProhibited => this.SettingsManager.IsProviderProhibited(this.DataLLMProvider);
private bool dataIsValid; private bool dataIsValid;
private string[] dataIssues = []; private string[] dataIssues = [];
private string dataAPIKey = string.Empty; private string dataAPIKey = string.Empty;
@@ -239,6 +248,9 @@ public partial class TranscriptionProviderDialog : MSGComponentBase, ISecretId
private async Task Store() private async Task Store()
{ {
if (this.IsProviderProhibited)
return;
await this.form.Validate(); await this.form.Validate();
this.dataAPIKeyStorageIssue = string.Empty; this.dataAPIKeyStorageIssue = string.Empty;
@@ -328,6 +340,10 @@ public partial class TranscriptionProviderDialog : MSGComponentBase, ISecretId
private async Task ReloadModels() private async Task ReloadModels()
{ {
// Loading the models would contact the provider:
if (this.IsProviderProhibited)
return;
this.dataLoadingModelsIssue = string.Empty; this.dataLoadingModelsIssue = string.Empty;
var currentTranscriptionProviderSettings = this.CreateTranscriptionProviderSettings(); var currentTranscriptionProviderSettings = this.CreateTranscriptionProviderSettings();
var provider = currentTranscriptionProviderSettings.CreateProvider(); var provider = currentTranscriptionProviderSettings.CreateProvider();
@@ -19,7 +19,7 @@
<AssistantBlock TSettings="SettingsDialogRewrite" Component="Components.REWRITE_ASSISTANT" Name="@T("Rewrite & Improve")" Description="@T("Rewrite and improve a given text for a chosen style.")" Icon="@Icons.Material.Filled.Edit" Link="@Routes.ASSISTANT_REWRITE"/> <AssistantBlock TSettings="SettingsDialogRewrite" Component="Components.REWRITE_ASSISTANT" Name="@T("Rewrite & Improve")" Description="@T("Rewrite and improve a given text for a chosen style.")" Icon="@Icons.Material.Filled.Edit" Link="@Routes.ASSISTANT_REWRITE"/>
<AssistantBlock TSettings="SettingsDialogPromptOptimizer" Component="Components.PROMPT_OPTIMIZER_ASSISTANT" Name="@T("Prompt Optimizer")" Description="@T("Optimize your prompt using a structured guideline.")" Icon="@Icons.Material.Filled.AutoFixHigh" Link="@Routes.ASSISTANT_PROMPT_OPTIMIZER"/> <AssistantBlock TSettings="SettingsDialogPromptOptimizer" Component="Components.PROMPT_OPTIMIZER_ASSISTANT" Name="@T("Prompt Optimizer")" Description="@T("Optimize your prompt using a structured guideline.")" Icon="@Icons.Material.Filled.AutoFixHigh" Link="@Routes.ASSISTANT_PROMPT_OPTIMIZER"/>
<AssistantBlock TSettings="SettingsDialogSynonyms" Component="Components.SYNONYMS_ASSISTANT" Name="@T("Synonyms")" Description="@T("Find synonyms for a given word or phrase.")" Icon="@Icons.Material.Filled.Spellcheck" Link="@Routes.ASSISTANT_SYNONYMS"/> <AssistantBlock TSettings="SettingsDialogSynonyms" Component="Components.SYNONYMS_ASSISTANT" Name="@T("Synonyms")" Description="@T("Find synonyms for a given word or phrase.")" Icon="@Icons.Material.Filled.Spellcheck" Link="@Routes.ASSISTANT_SYNONYMS"/>
<AssistantBlock TSettings="NoSettingsPanel" Component="Components.META_ASSISTANT" RequiredPreviewFeature="PreviewFeatures.PRE_META_ASSISTANT_V1" Name="@T("Assistant Builder")" Description="@T("Generate your own assistants.")" Icon="@Icons.Material.Filled.AutoMode" Link="@Routes.ASSISTANT_META_ASSISTANT"/> <AssistantBlock TSettings="SettingsDialogAssistantBuilder" Component="Components.META_ASSISTANT" RequiredPreviewFeature="PreviewFeatures.PRE_META_ASSISTANT_V1" Name="@T("Assistant Builder")" Description="@T("Generate your own assistants.")" Icon="@Icons.Material.Filled.AutoMode" Link="@Routes.ASSISTANT_META_ASSISTANT"/>
</AssistantCategoryBlock> </AssistantCategoryBlock>
@if (this.AssistantPlugins.Count > 0) @if (this.AssistantPlugins.Count > 0)
@@ -296,7 +296,7 @@
@* @*
What the user has to decide comes with the buttons to decide it. The What the user has to decide comes with the buttons to decide it. The
sentence above says what is at stake; reading it and then having to find sentence above says what is at stake; reading it and then having to find
the settings of the mailbox is where people give up. the settings of the mailbox or the data source is where people give up.
*@ *@
@if (status.Attention is DataSourceAttention.AUTH_FAILED) @if (status.Attention is DataSourceAttention.AUTH_FAILED)
{ {
@@ -320,6 +320,14 @@
</MudButton> </MudButton>
</MudStack> </MudStack>
} }
else if (status.Attention is DataSourceAttention.LOCATION_UNREACHABLE && this.CanEditDataSource(status))
{
<MudStack Row="@true" Wrap="Wrap.Wrap" Spacing="2">
<MudButton Variant="Variant.Filled" Color="Color.Primary" StartIcon="@Icons.Material.Filled.Edit" OnClick="@(async () => await this.EditDataSource(status))">
@T("Edit this data source")
</MudButton>
</MudStack>
}
</MudStack> </MudStack>
</ChildContent> </ChildContent>
</MudExpansionPanel> </MudExpansionPanel>
@@ -509,6 +509,25 @@ public partial class Embeddings : MSGComponentBase
await this.MessageBus.SendMessage<bool>(this, Event.CONFIGURATION_CHANGED); await this.MessageBus.SendMessage<bool>(this, Event.CONFIGURATION_CHANGED);
} }
/// <remarks>
/// Not offered for a data source the organization manages: its settings cannot be edited.
/// </remarks>
private bool CanEditDataSource(DataSourceEmbeddingStatus status) => DataSourceEditing.CanEdit(this.SettingsManager, status.DataSourceId);
/// <summary>
/// Opens the settings of a data source, e.g. to select the new location of a moved folder.
/// </summary>
/// <remarks>
/// Saving them indexes the data source right away, and that run reconnects what is already
/// stored to the new location. The page reloads through CONFIGURATION_CHANGED, which it
/// receives like everybody else.
/// </remarks>
private async Task EditDataSource(DataSourceEmbeddingStatus status)
{
if (await DataSourceEditing.EditAsync(this.DialogService, this.SettingsManager, this.DataSourceEmbeddingService, status.DataSourceId))
await this.MessageBus.SendMessage<bool>(this, Event.CONFIGURATION_CHANGED);
}
/// <summary> /// <summary>
/// Agrees to the removal a mailbox held back, for the very number the user read. /// Agrees to the removal a mailbox held back, for the very number the user read.
/// </summary> /// </summary>
@@ -153,6 +153,8 @@ ASSISTANT = {
- A selected chat template supplies the chat system prompt, profile allowance, predefined user prompt, attachments, and cloned example conversation. A launcher `SystemPrompt`, if retained in an older plugin, is ignored, so there is never a second competing system prompt. - A selected chat template supplies the chat system prompt, profile allowance, predefined user prompt, attachments, and cloned example conversation. A launcher `SystemPrompt`, if retained in an older plugin, is ignored, so there is never a second competing system prompt.
- When the selected chat template does not allow profiles, the template wins: the launcher `ProfileId` is dropped and the chat starts without a profile. This matches the disabled profile selection such a template produces in the chat. - When the selected chat template does not allow profiles, the template wins: the launcher `ProfileId` is dropped and the chat starts without a profile. This matches the disabled profile selection such a template produces in the chat.
- A chat template may preselect tools and data sources as well. When it does, it decides them alone: the launcher `ToolIds` and `DataSourceIds` are dropped, and AI Studio writes a warning into the log naming both sides. The rule is the same for tools and for data sources, so there is only one to remember. - A chat template may preselect tools and data sources as well. When it does, it decides them alone: the launcher `ToolIds` and `DataSourceIds` are dropped, and AI Studio writes a warning into the log naming both sides. The rule is the same for tools and for data sources, so there is only one to remember.
- A chat template may also preselect a provider and a profile, and the same rule applies: they win over the launcher `ProviderId` and `ProfileId`, with a warning in the log. A provider of the template which is gone or does not meet the confidence level required for chats, and a profile of the template which is gone, are left out instead of stopping the launch; the launcher then decides as if the template named none.
- A chat template may name a workspace for its chats as well. Here the launcher wins when it names a workspace of its own, because that choice was made on purpose. A launcher that opens a disappearing chat opens it in the workspace of its template instead, which is created when it does not exist yet; if that fails, the chat stays a disappearing chat.
- The reason the template wins as a whole rather than field by field is a difference in what the two can express. A launcher can only ever say "these sources, picked by hand", while a chat template carries the whole data source options and can also say "let an agent pick the sources for each message". A mix of both would be something neither of them asked for. - The reason the template wins as a whole rather than field by field is a difference in what the two can express. A launcher can only ever say "these sources, picked by hand", while a chat template carries the whole data source options and can also say "let an agent pick the sources for each message". A mix of both would be something neither of them asked for.
- The data sources of a chat template are checked exactly like the ones of a launcher: a source which no longer exists, or which is not permitted for the effective provider, stops the launch before a workspace or chat is created. The message then names the chat template as the cause, not the launcher. A template which leaves the choice to an agent names no source and is therefore not checked here; that decision is made per message in the chat. - The data sources of a chat template are checked exactly like the ones of a launcher: a source which no longer exists, or which is not permitted for the effective provider, stops the launch before a workspace or chat is created. The message then names the chat template as the cause, not the launcher. A template which leaves the choice to an agent names no source and is therefore not checked here; that decision is made per message in the chat.
- The predefined user prompt and the attachments of the selected chat template are placed into the chat input, unless the user already has an unsent draft there. - The predefined user prompt and the attachments of the selected chat template are placed into the chat input, unless the user already has an unsent draft there.
@@ -454,6 +454,34 @@ CONFIG["SETTINGS"] = {}
-- CONFIG["SETTINGS"]["DataApp.AllowUserToAddEmbeddingProvider"] = false -- CONFIG["SETTINGS"]["DataApp.AllowUserToAddEmbeddingProvider"] = false
-- CONFIG["SETTINGS"]["DataApp.AllowUserToAddTranscriptionProvider"] = false -- CONFIG["SETTINGS"]["DataApp.AllowUserToAddTranscriptionProvider"] = false
-- Prohibit providers, e.g., when your organization must not send any data to them. The list
-- applies to LLM providers, embedding providers, and transcription providers alike. Allowed
-- values are the provider keys of DataConfidence.CustomConfidenceScheme, see below. A name
-- AI Studio does not know is ignored and logged as a warning.
--
-- A prohibition names the operator of an API, not a family of models: prohibiting the API of
-- a model maker keeps AI Studio away from that API, but the models of that maker may remain
-- available through a hub which offers the models of several makers, or on a server of your
-- own. Prohibit these as well if you need to.
--
-- Users can still select a prohibited provider when they add one, but the dialog then only
-- explains that your organization does not allow it. Providers which users set up before stay
-- in their settings, marked as prohibited, and users can delete them. AI Studio no longer
-- offers or contacts them. Once you lift the prohibition, they work again.
--
-- A prohibited provider is always UNTRUSTED, whatever the confidence scheme says. A level which
-- DataConfidence.CustomConfidenceScheme names for it is ignored and logged as a warning, and
-- users cannot change it in the app. Once you lift the prohibition, that level applies again.
--
-- Replaces, does not merge: a configuration with a higher priority replaces the whole list.
-- A configuration which prohibits one more provider has to repeat the providers of the base
-- configuration. Otherwise, these become usable again.
--
-- Available since AI Studio v26.10.1. Older versions ignore this setting. Until all devices
-- run that version, you can in addition use the CUSTOM confidence scheme, set the provider to
-- UNTRUSTED there, and enforce a global minimum confidence level of at least VERY_LOW.
-- CONFIG["SETTINGS"]["DataApp.ProhibitedProviders"] = { "<PROVIDER_KEY>" }
-- Configure the permission to add mailboxes. A mailbox is a data source, not a provider, so -- Configure the permission to add mailboxes. A mailbox is a data source, not a provider, so
-- DataApp.AllowUserToAddProvider does not apply to it. When set to false, the menu entry for -- DataApp.AllowUserToAddProvider does not apply to it. When set to false, the menu entry for
-- adding a mailbox stays visible but is disabled. Mailboxes the user added before stay; to keep -- adding a mailbox stays visible but is disabled. Mailboxes the user added before stay; to keep
@@ -650,7 +678,7 @@ CONFIG["SETTINGS"] = {}
-- --
-- Configure the default file patterns and whether subfolders as well as hidden files and folders are included. -- Configure the default file patterns and whether subfolders as well as hidden files and folders are included.
-- Separate multiple patterns with semicolons. -- Separate multiple patterns with semicolons.
-- CONFIG["SETTINGS"]["DataBatchProcessing.FilePatterns"] = "*.pdf;*.docx;*.pptx;*.xlsx;*.md;*.txt;*.mp3;*.wav;*.wave;*.aac;*.flac;*.ogg;*.opus;*.m4a;*.m4b;*.wma;*.alac;*.aif;*.aiff;*.caf;*.mp4;*.m4v;*.avi;*.mkv;*.mov;*.wmv;*.flv;*.webm" -- CONFIG["SETTINGS"]["DataBatchProcessing.FilePatterns"] = "*.pdf;*.docx;*.pptx;*.xlsx;*.md;*.txt;*.jpg;*.jpeg;*.png;*.webp;*.gif;*.bmp;*.tif;*.tiff;*.mp3;*.wav;*.wave;*.aac;*.flac;*.ogg;*.opus;*.m4a;*.m4b;*.wma;*.alac;*.aif;*.aiff;*.caf;*.mp4;*.m4v;*.avi;*.mkv;*.mov;*.wmv;*.flv;*.webm"
-- CONFIG["SETTINGS"]["DataBatchProcessing.IncludeSubdirectories"] = false -- CONFIG["SETTINGS"]["DataBatchProcessing.IncludeSubdirectories"] = false
-- CONFIG["SETTINGS"]["DataBatchProcessing.IncludeHiddenFilesAndFolders"] = false -- CONFIG["SETTINGS"]["DataBatchProcessing.IncludeHiddenFilesAndFolders"] = false
-- --
@@ -818,6 +846,37 @@ CONFIG["SETTINGS"] = {}
-- documents. Providers below this level cannot be selected in the assistant. -- documents. Providers below this level cannot be selected in the assistant.
-- CONFIG["SETTINGS"]["DataVisualBriefing.MinimumProviderConfidence"] = "NONE" -- CONFIG["SETTINGS"]["DataVisualBriefing.MinimumProviderConfidence"] = "NONE"
-- Configure defaults for the Assistant Builder.
-- Preselection must be enabled for the remaining Assistant Builder settings to take effect.
-- CONFIG["SETTINGS"]["DataAssistantBuilder.PreselectOptions"] = true
--
-- Configure the preselected provider for building assistants.
-- It must be one of the provider IDs defined in CONFIG["LLM_PROVIDERS"].
-- Without a preselected provider, the Assistant Builder uses the app-wide default provider.
-- CONFIG["SETTINGS"]["DataAssistantBuilder.PreselectedProvider"] = "00000000-0000-0000-0000-000000000000"
--
-- Configure the language in which the generated assistants answer.
-- Allowed values are: AS_IS, EN_US, EN_GB, ZH_CN, HI_IN, ES_ES, FR_FR, DE_DE, DE_CH, DE_AT,
-- JA_JP, RU_RU, OTHER
-- AS_IS leaves the output language of the generated assistants open.
-- CONFIG["SETTINGS"]["DataAssistantBuilder.PreselectedOutputLanguage"] = "AS_IS"
--
-- Configure a free-form language, used only when PreselectedOutputLanguage is "OTHER".
-- CONFIG["SETTINGS"]["DataAssistantBuilder.PreselectedOtherOutputLanguage"] = ""
--
-- Configure the minimum provider confidence required to build assistants.
-- Allowed values are: NONE, UNTRUSTED, UNKNOWN, VERY_LOW, LOW, MODERATE, MEDIUM, HIGH
-- Providers below this level cannot be selected in the Assistant Builder. This minimum applies
-- only while preselection is enabled; the app-wide minimum confidence applies in any case.
-- CONFIG["SETTINGS"]["DataAssistantBuilder.MinimumProviderConfidence"] = "NONE"
--
-- Allow users to change individual managed Assistant Builder defaults locally.
-- CONFIG["SETTINGS"]["DataAssistantBuilder.PreselectOptions.AllowUserOverride"] = true
-- CONFIG["SETTINGS"]["DataAssistantBuilder.PreselectedProvider.AllowUserOverride"] = true
-- CONFIG["SETTINGS"]["DataAssistantBuilder.PreselectedOutputLanguage.AllowUserOverride"] = true
-- CONFIG["SETTINGS"]["DataAssistantBuilder.PreselectedOtherOutputLanguage.AllowUserOverride"] = true
-- CONFIG["SETTINGS"]["DataAssistantBuilder.MinimumProviderConfidence.AllowUserOverride"] = true
-- Configure enterprise approvals for assistant plugins. -- Configure enterprise approvals for assistant plugins.
-- Each approval is matched only by the current SHA-256 hash over all Lua files -- Each approval is matched only by the current SHA-256 hash over all Lua files
-- in the assistant plugin folder, in canonical sorted order. -- in the assistant plugin folder, in canonical sorted order.
@@ -1134,6 +1193,9 @@ CONFIG["SETTINGS"] = {}
-- SELF_HOSTED, HELMHOLTZ, GWDG -- SELF_HOSTED, HELMHOLTZ, GWDG
-- Allowed confidence values are: UNTRUSTED, VERY_LOW, LOW, MODERATE, MEDIUM, HIGH -- Allowed confidence values are: UNTRUSTED, VERY_LOW, LOW, MODERATE, MEDIUM, HIGH
-- --
-- A provider which DataApp.ProhibitedProviders names is always UNTRUSTED. Its level in this
-- table is ignored and logged as a warning, see DataApp.ProhibitedProviders above.
--
-- Replaces, does not merge: a configuration with a higher priority replaces the whole -- Replaces, does not merge: a configuration with a higher priority replaces the whole
-- table. Every configuration that sets this must therefore list all providers it wants -- table. Every configuration that sets this must therefore list all providers it wants
-- to cover. A partial table is not completed from the configuration below it, and the -- to cover. A partial table is not completed from the configuration below it, and the
@@ -1363,6 +1425,42 @@ CONFIG["CHAT_TEMPLATES"] = {}
-- }, -- },
-- } -- }
-- An example chat template which preselects a provider, a profile, and a workspace:
-- All three are optional and independent of each other, and leaving one out keeps what a
-- chat starts with anyway. They are a preselection, not a limit: users change the provider
-- and the profile in the chat as usual, and move the chat to another workspace. The IDs may
-- name a provider or profile of this configuration, which keeps its ID on every computer, or
-- one of another configuration of your organization.
-- CONFIG["CHAT_TEMPLATES"][#CONFIG["CHAT_TEMPLATES"]+1] = {
-- ["Id"] = "00000000-0000-0000-0000-000000000003",
-- ["Name"] = "Mail Assistant",
-- ["SystemPrompt"] = "You help <Company Name>'s employees read, sort, and answer their mails.",
-- ["AllowProfileUsage"] = true,
--
-- -- Optional: the provider, and with it the model, a chat with this template starts with.
-- -- Must be an ID from CONFIG["LLM_PROVIDERS"] or of a provider of another configuration.
-- -- The provider has to meet the confidence level required for chats. A provider which
-- -- does not, or an ID which resolves to nothing, is ignored, and the chat starts with
-- -- the provider it would start with anyway.
-- ["ProviderId"] = "11111111-1111-1111-1111-111111111111",
--
-- -- Optional: the profile a chat with this template starts with.
-- -- Must be an ID from CONFIG["PROFILES"] or of a profile of another configuration.
-- -- "00000000-0000-0000-0000-000000000000" starts the chat without a profile, whatever
-- -- the user chose as their default. An ID which resolves to nothing is ignored, and the
-- -- chat starts with the default profile of the user. The whole field is ignored when
-- -- AllowProfileUsage is false.
-- ["ProfileId"] = "22222222-2222-2222-2222-222222222222",
--
-- -- Optional: the workspace, by its name, that keeps the chats started with this template.
-- -- The workspace is created with the first message of such a chat, when it does not exist
-- -- yet. It only applies where a chat would otherwise be a disappearing chat: a chat users
-- -- start in a workspace on purpose, for example, from the workspace list, stays there, and
-- -- so does every chat while workspaces are switched off. An assistant chat launcher which
-- -- names a workspace keeps its own.
-- ["WorkspaceName"] = "Mails",
-- }
-- Introduction texts shown as expansion panels on the welcome page: -- Introduction texts shown as expansion panels on the welcome page:
CONFIG["INTRODUCTIONS"] = {} CONFIG["INTRODUCTIONS"] = {}
@@ -426,6 +426,9 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING
-- The selected folder does not exist. -- The selected folder does not exist.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T17705912"] = "Der ausgewählte Ordner existiert nicht." UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T17705912"] = "Der ausgewählte Ordner existiert nicht."
-- The image was no longer available while the model was asked about it.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T1805791865"] = "Das Bild war nicht mehr verfügbar, während das Modell dazu befragt wurde."
-- Minimum pause between files -- Minimum pause between files
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T1829787634"] = "Mindestpause zwischen Dateien" UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T1829787634"] = "Mindestpause zwischen Dateien"
@@ -531,9 +534,15 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING
-- Was not able to write the result file: {0} -- Was not able to write the result file: {0}
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T2991295581"] = "Die Ergebnisdatei konnte nicht geschrieben werden: {0}" UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T2991295581"] = "Die Ergebnisdatei konnte nicht geschrieben werden: {0}"
-- Images in JPG, PNG, WebP, GIF, BMP, or TIFF format are given to the model as images, so you need a model that understands images. If the selected model cannot read images, remove the image patterns, such as *.jpg or *.png, before you start.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T3011193639"] = "Bilder im JPG-, PNG-, WebP-, GIF-, BMP- oder TIFF-Format werden als Bilder an das Modell übergeben, daher benötigen Sie ein Modell, das Bilder versteht. Falls das ausgewählte Modell keine Bilder lesen kann, entfernen Sie die Dateimuster für Bilder, z. B. *.jpg oder *.png, bevor Sie starten."
-- The batch run finished, but one file could not be processed. See the progress table and log for details. -- The batch run finished, but one file could not be processed. See the progress table and log for details.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T3201532790"] = "Die Stapelverarbeitung ist abgeschlossen, aber eine Datei konnte nicht verarbeitet werden. Weitere Informationen finden Sie in der Fortschrittstabelle und im Protokoll." UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T3201532790"] = "Die Stapelverarbeitung ist abgeschlossen, aber eine Datei konnte nicht verarbeitet werden. Weitere Informationen finden Sie in der Fortschrittstabelle und im Protokoll."
-- The selected model cannot read images, so the {0} images among the selected files will fail. All other files are processed as usual.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T324865195"] = "Das ausgewählte Modell kann keine Bilder lesen, daher schlägt die Verarbeitung der {0} Bilder unter den ausgewählten Dateien fehl. Alle anderen Dateien werden wie gewohnt verarbeitet."
-- Maximum pause between files -- Maximum pause between files
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T3250003796"] = "Maximale Pause zwischen Dateien" UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T3250003796"] = "Maximale Pause zwischen Dateien"
@@ -552,6 +561,9 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING
-- Show Expert Settings -- Show Expert Settings
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T3361153305"] = "Experten-Einstellungen anzeigen" UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T3361153305"] = "Experten-Einstellungen anzeigen"
-- All selected files are images, but the selected model cannot read images. Select a model with image support.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T3365102967"] = "Alle ausgewählten Dateien sind Bilder, aber das ausgewählte Modell kann keine Bilder lesen. Wählen Sie ein Modell mit Bildunterstützung aus."
-- Header of the result column (optional) -- Header of the result column (optional)
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T340994102"] = "Überschrift der Ergebnisspalte (optional)" UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T340994102"] = "Überschrift der Ergebnisspalte (optional)"
@@ -564,6 +576,9 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING
-- The file name of the CSV results table. We add the ending .csv when it is missing. When left empty, we use 'batch-results.csv'. -- The file name of the CSV results table. We add the ending .csv when it is missing. When left empty, we use 'batch-results.csv'.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T3439247329"] = "Der Dateiname der CSV-Ergebnistabelle. Die Endung .csv wird ergänzt, falls sie fehlt. Wenn Sie das Feld leer lassen, wird 'batch-results.csv' verwendet." UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T3439247329"] = "Der Dateiname der CSV-Ergebnistabelle. Die Endung .csv wird ergänzt, falls sie fehlt. Wenn Sie das Feld leer lassen, wird 'batch-results.csv' verwendet."
-- The selected model cannot read images. Select a model with image support to process this image.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T346986514"] = "Das ausgewählte Modell kann keine Bilder lesen. Wählen Sie ein Modell mit Bildunterstützung aus, um dieses Bild zu verarbeiten."
-- Document analysis policy -- Document analysis policy
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T3510564924"] = "Regelwerk für die Dokumentenanalyse" UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T3510564924"] = "Regelwerk für die Dokumentenanalyse"
@@ -678,6 +693,9 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING
-- 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. -- 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"] = "Die KI kann diese Werkzeuge bei der Arbeit an jedem Dokument verwenden. Jedes Werkzeug muss die Vertrauensanforderungen des ausgewählten Anbieters erfüllen. Daher kann ein Werkzeug trotz Auswahl hier weiterhin nicht verfügbar sein." UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T967206794"] = "Die KI kann diese Werkzeuge bei der Arbeit an jedem Dokument verwenden. Jedes Werkzeug muss die Vertrauensanforderungen des ausgewählten Anbieters erfüllen. Daher kann ein Werkzeug trotz Auswahl hier weiterhin nicht verfügbar sein."
-- The selected model cannot read images, so the image among the selected files will fail. All other files are processed as usual.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T9738678"] = "Das ausgewählte Modell kann keine Bilder lesen, daher schlägt die Verarbeitung des Bildes unter den ausgewählten Dateien fehl. Alle anderen Dateien werden wie gewohnt verarbeitet."
-- Comma (,) -- Comma (,)
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::BATCHPROCESSINGCSVSEPARATOREXTENSIONS::T1676507543"] = "Komma (,)" UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::BATCHPROCESSINGCSVSEPARATOREXTENSIONS::T1676507543"] = "Komma (,)"
@@ -3627,6 +3645,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T1543974632"] = "Die Medie
-- {0}, of which approx. {1} from tools -- {0}, of which approx. {1} from tools
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T1614399447"] = "{0}, davon ca. {1} aus Werkzeugen" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T1614399447"] = "{0}, davon ca. {1} aus Werkzeugen"
-- The use of this provider has been prohibited by your organization. Please select a different provider.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T1807387355"] = "Die Verwendung dieses Anbieters wurde von Ihrer Organisation untersagt. Bitte wählen Sie einen anderen Anbieter."
-- Type your input here... -- Type your input here...
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T1849313532"] = "Geben Sie hier Ihre Eingabe ein..." UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T1849313532"] = "Geben Sie hier Ihre Eingabe ein..."
@@ -4122,9 +4143,6 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCEMANAGEMENT::T1084943026"] = "Si
-- Automatic local data source refresh -- Automatic local data source refresh
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCEMANAGEMENT::T1208397349"] = "Automatische Aktualisierung lokaler Datenquellen" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCEMANAGEMENT::T1208397349"] = "Automatische Aktualisierung lokaler Datenquellen"
-- Edit Local Directory Data Source
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCEMANAGEMENT::T1215599168"] = "Datenquelle bearbeiten: Lokaler Ordner"
-- Refresh -- Refresh
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCEMANAGEMENT::T135637716"] = "Aktualisieren" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCEMANAGEMENT::T135637716"] = "Aktualisieren"
@@ -4152,12 +4170,6 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCEMANAGEMENT::T2067030738"] = "M
-- Local Directory Data Source Information -- Local Directory Data Source Information
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCEMANAGEMENT::T2146756020"] = "Informationen zur lokalen Ordner-Datenquelle" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCEMANAGEMENT::T2146756020"] = "Informationen zur lokalen Ordner-Datenquelle"
-- Edit ERI v1 Data Source
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCEMANAGEMENT::T221059217"] = "ERI v1 Datenquelle bearbeiten"
-- Edit Local File Data Source
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCEMANAGEMENT::T2453292893"] = "Datenquelle bearbeiten: Lokale Datei"
-- Local data sources refresh when files change, mailboxes every 16 minutes. -- Local data sources refresh when files change, mailboxes every 16 minutes.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCEMANAGEMENT::T2511157475"] = "Lokale Datenquellen werden aktualisiert, wenn sich Dateien ändern, Postfächer alle 16 Minuten." UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCEMANAGEMENT::T2511157475"] = "Lokale Datenquellen werden aktualisiert, wenn sich Dateien ändern, Postfächer alle 16 Minuten."
@@ -4392,9 +4404,15 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DIRECTCHATLAUNCHERFORM::T1797236585"] = "
-- Workspace name (Optional) -- Workspace name (Optional)
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DIRECTCHATLAUNCHERFORM::T1873204484"] = "Name des Arbeitsbereichs (optional)" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DIRECTCHATLAUNCHERFORM::T1873204484"] = "Name des Arbeitsbereichs (optional)"
-- The chosen chat template preselects a provider of its own, and it wins over a selection made here as long as that provider can be used for chats.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DIRECTCHATLAUNCHERFORM::T1876510820"] = "Die ausgewählte Chat-Vorlage wählt einen eigenen Anbieter vorab aus; dieser hat Vorrang vor einer hier getroffenen Auswahl, solange er für Chats verwendet werden kann."
-- Use no profile -- Use no profile
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DIRECTCHATLAUNCHERFORM::T2205839602"] = "Kein Profil verwenden" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DIRECTCHATLAUNCHERFORM::T2205839602"] = "Kein Profil verwenden"
-- The chosen chat template keeps its chats in the workspace '{0}', so the tile opens its chat there instead. A workspace entered here wins over the one of the template.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DIRECTCHATLAUNCHERFORM::T2344103917"] = "Die ausgewählte Chat-Vorlage legt ihre Chats im Arbeitsbereich „{0}“ ab, daher öffnet die Kachel ihren Chat dort. Ein hier eingegebener Arbeitsbereich hat Vorrang vor dem der Vorlage."
-- Existing workspace (Optional) -- Existing workspace (Optional)
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DIRECTCHATLAUNCHERFORM::T2364306588"] = "Vorhandener Arbeitsbereich (optional)" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DIRECTCHATLAUNCHERFORM::T2364306588"] = "Vorhandener Arbeitsbereich (optional)"
@@ -4416,6 +4434,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DIRECTCHATLAUNCHERFORM::T2901190527"] = "
-- Data sources (Optional) -- Data sources (Optional)
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DIRECTCHATLAUNCHERFORM::T3259309302"] = "Datenquellen (optional)" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DIRECTCHATLAUNCHERFORM::T3259309302"] = "Datenquellen (optional)"
-- The chosen chat template preselects a profile of its own, and it wins over a selection made here as long as that profile exists.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DIRECTCHATLAUNCHERFORM::T3419242912"] = "Die ausgewählte Chat-Vorlage wählt ein eigenes Profil vorab aus; dieses hat Vorrang vor einer hier getroffenen Auswahl, solange das Profil existiert."
-- Without a workspace, the tile opens a disappearing chat: it belongs to no workspace and is deleted according to your workspace maintenance settings. -- Without a workspace, the tile opens a disappearing chat: it belongs to no workspace and is deleted according to your workspace maintenance settings.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DIRECTCHATLAUNCHERFORM::T3611496116"] = "Ohne Arbeitsbereich öffnet die Kachel einen selbstlöschenden Chat: Er gehört keinem Arbeitsbereich an und wird gemäß den Wartungseinstellungen für Ihre Arbeitsbereiche gelöscht." UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DIRECTCHATLAUNCHERFORM::T3611496116"] = "Ohne Arbeitsbereich öffnet die Kachel einen selbstlöschenden Chat: Er gehört keinem Arbeitsbereich an und wird gemäß den Wartungseinstellungen für Ihre Arbeitsbereiche gelöscht."
@@ -4692,6 +4713,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::PROVIDERSELECTION::T2685487365"] = "Bilde
-- Speech input possible -- Speech input possible
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::PROVIDERSELECTION::T3005724142"] = "Spracheingabe möglich" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::PROVIDERSELECTION::T3005724142"] = "Spracheingabe möglich"
-- The use of all your LLM providers has been prohibited by your organization. Add a different provider in the app settings.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::PROVIDERSELECTION::T3506708164"] = "Die Verwendung aller Ihrer LLM-Anbieter wurde von Ihrer Organisation untersagt. Bitte fügen Sie einen anderen Anbieter in den App-Einstellungen hinzu."
-- Uses reasoning (thinking) by default -- Uses reasoning (thinking) by default
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::PROVIDERSELECTION::T3891860124"] = "Nutzt standardmäßig Schlussfolgerungen (Denken)" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::PROVIDERSELECTION::T3891860124"] = "Nutzt standardmäßig Schlussfolgerungen (Denken)"
@@ -5262,6 +5286,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELCONFIDENCE::T45885
-- Not yet configured -- Not yet configured
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELCONFIDENCE::T48051324"] = "Noch nicht konfiguriert" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELCONFIDENCE::T48051324"] = "Noch nicht konfiguriert"
-- The use of this provider has been prohibited by your organization. Therefore, it is untrusted, and you cannot change its confidence level.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELCONFIDENCE::T489079668"] = "Die Verwendung dieses Anbieters wurde von Ihrer Organisation untersagt. Daher gilt er als nicht vertrauenswürdig, und Sie können sein Vertrauensniveau nicht ändern."
-- Do you want to always see how trustworthy your providers are? This way, you stay in control of which provider you send your data to. You can choose a common schema or configure the trust levels for each provider yourself. -- Do you want to always see how trustworthy your providers are? This way, you stay in control of which provider you send your data to. You can choose a common schema or configure the trust levels for each provider yourself.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELCONFIDENCE::T700839804"] = "Möchten Sie immer sehen, wie vertrauenswürdig Ihre Anbieter sind? So behalten Sie die Kontrolle darüber, an welchen Anbieter Sie Ihre Daten senden. Sie können ein gängiges Schema wählen oder die Vertrauensniveaus für jeden Anbieter selbst festlegen." UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELCONFIDENCE::T700839804"] = "Möchten Sie immer sehen, wie vertrauenswürdig Ihre Anbieter sind? So behalten Sie die Kontrolle darüber, an welchen Anbieter Sie Ihre Daten senden. Sie können ein gängiges Schema wählen oder die Vertrauensniveaus für jeden Anbieter selbst festlegen."
@@ -5280,6 +5307,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T13870
-- Delete -- Delete
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T1469573738"] = "Löschen" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T1469573738"] = "Löschen"
-- The use of this embedding provider has been prohibited by your organization.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T1509600117"] = "Die Verwendung dieses Einbettungsanbieters wurde von Ihrer Organisation untersagt."
-- Embed text -- Embed text
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T1644934561"] = "Text einbetten" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T1644934561"] = "Text einbetten"
@@ -5425,11 +5455,14 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T331371
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T3346433704"] = "LLM-Anbieter hinzufügen" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T3346433704"] = "LLM-Anbieter hinzufügen"
-- This provider is managed by your organization. -- This provider is managed by your organization.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T3415927576"] = "Dieser Anbieter wird von ihrer Organisation verwaltet." UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T3415927576"] = "Dieser Anbieter wird von Ihrer Organisation verwaltet."
-- Actions -- Actions
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T3865031940"] = "Aktionen" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T3865031940"] = "Aktionen"
-- The use of this provider has been prohibited by your organization.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T4229691938"] = "Die Verwendung dieses Anbieters wurde von Ihrer Organisation untersagt."
-- Delete LLM Provider -- Delete LLM Provider
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T4269256234"] = "LLM-Anbieter löschen" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T4269256234"] = "LLM-Anbieter löschen"
@@ -5493,6 +5526,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T16
-- Uses the provider-configured model -- Uses the provider-configured model
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T1760715963"] = "Verwendet das vom Anbieter konfigurierte Modell" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T1760715963"] = "Verwendet das vom Anbieter konfigurierte Modell"
-- The use of this transcription provider has been prohibited by your organization.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T2059509230"] = "Die Verwendung dieses Anbieters für Transkriptionen wurde von Ihrer Organisation untersagt."
-- Add Transcription Provider -- Add Transcription Provider
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T2066315685"] = "Anbieter für Transkriptionen hinzufügen" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T2066315685"] = "Anbieter für Transkriptionen hinzufügen"
@@ -6180,14 +6216,23 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T1396308587"] = "Der Nam
-- The same goes for your data. A chat template may bring its own data source options, which includes leaving the choice of sources to the AI. Without them, those chats start with the data source options from your chat options. -- The same goes for your data. A chat template may bring its own data source options, which includes leaving the choice of sources to the AI. Without them, those chats start with the data source options from your chat options.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T1442266827"] = "Das Gleiche gilt für Ihre Daten. Eine Chat-Vorlage kann eigene Datenquellen-Optionen mitbringen – dazu gehört auch, die Auswahl der Quellen der KI zu überlassen. Ohne solche Optionen starten diese Chats mit den Datenquellen-Optionen aus Ihren Chat-Optionen." UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T1442266827"] = "Das Gleiche gilt für Ihre Daten. Eine Chat-Vorlage kann eigene Datenquellen-Optionen mitbringen – dazu gehört auch, die Auswahl der Quellen der KI zu überlassen. Ohne solche Optionen starten diese Chats mit den Datenquellen-Optionen aus Ihren Chat-Optionen."
-- Preselected provider
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T1469984996"] = "Vorausgewählter Anbieter"
-- Please enter a name for the chat template. -- Please enter a name for the chat template.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T1548747185"] = "Bitte geben Sie einen Namen für die Chat-Vorlage ein." UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T1548747185"] = "Bitte geben Sie einen Namen für die Chat-Vorlage ein."
-- A chat template may decide which provider, and with it which model, a chat starts with. Without such a decision, those chats start with the provider they would start with anyway. The provider stays changeable in the chat, and it must meet the confidence level required for chats.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T171066641"] = "Eine Chat-Vorlage kann festlegen, mit welchem Anbieter – und damit mit welchem Modell – ein Chat startet. Ohne eine solche Festlegung starten diese Chats mit dem Anbieter, mit dem sie ohnehin starten würden. Der Anbieter lässt sich im Chat weiterhin ändern und muss das für Chats erforderliche Vertrauensniveau erfüllen."
-- Yes, this template decides which data a chat starts with -- Yes, this template decides which data a chat starts with
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T17861006"] = "Ja, diese Vorlage legt fest, mit welchen Daten ein Chat startet" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T17861006"] = "Ja, diese Vorlage legt fest, mit welchen Daten ein Chat startet"
-- Preselected profile
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T178965494"] = "Vorausgewähltes Profil"
-- Enter the full path of a file on this computer, or remove the attachment. -- Enter the full path of a file on this computer, or remove the attachment.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T1798848159"] = "Geben Sie den vollständigen Pfad zu einer Datei auf diesem Computer ein oder entfernen Sie den Anhang." UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T1798848159"] = "Geben Sie den vollständigen Pfad zu einer Datei auf diesem Rechner ein oder entfernen Sie den Anhang."
-- Load predefined user input from file -- Load predefined user input from file
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T1837026610"] = "Vordefinierte Benutzereingabe aus Datei laden" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T1837026610"] = "Vordefinierte Benutzereingabe aus Datei laden"
@@ -6195,12 +6240,21 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T1837026610"] = "Vordefi
-- Update -- Update
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T1847791252"] = "Aktualisieren" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T1847791252"] = "Aktualisieren"
-- Yes, this template decides which profile a chat starts with
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T1865005833"] = "Ja, diese Vorlage legt fest, mit welchem Profil ein Chat startet"
-- The chat template name must not exceed 40 characters. -- The chat template name must not exceed 40 characters.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T204496403"] = "Der Name der Chat-Vorlage darf maximal 40 Zeichen lang sein." UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T204496403"] = "Der Name der Chat-Vorlage darf maximal 40 Zeichen lang sein."
-- Profile Usage -- Profile Usage
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2147062613"] = "Profilnutzung" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2147062613"] = "Profilnutzung"
-- Use no profile
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2205839602"] = "Kein Profil verwenden"
-- Yes, chats with this template go into a workspace
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2205963533"] = "Ja, Chats mit dieser Vorlage werden in einem Arbeitsbereich abgelegt"
-- Relink attachment: {0} -- Relink attachment: {0}
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2234793579"] = "Anhang erneut verknüpfen: {0}" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2234793579"] = "Anhang erneut verknüpfen: {0}"
@@ -6210,6 +6264,12 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2292424657"] = "Fügen
-- File Attachments -- File Attachments
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2294745309"] = "Dateianhänge" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2294745309"] = "Dateianhänge"
-- No, chats go where they would go anyway
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2355334446"] = "Nein, Chats werden dort abgelegt, wo sie ohnehin landen würden"
-- Existing workspace (Optional)
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2364306588"] = "Vorhandener Arbeitsbereich (optional)"
-- Role -- Role
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2418769465"] = "Rolle" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2418769465"] = "Rolle"
@@ -6222,6 +6282,12 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2499909372"] = "Werkzeu
-- What predefined user input do you want to use? -- What predefined user input do you want to use?
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2501284417"] = "Welche vordefinierte Benutzereingabe möchten Sie verwenden?" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2501284417"] = "Welche vordefinierte Benutzereingabe möchten Sie verwenden?"
-- Please enter the workspace for the chats of this template, or switch the preselection off.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2504276031"] = "Bitte geben Sie den Arbeitsbereich für die Chats dieser Vorlage ein oder deaktivieren Sie die Vorauswahl."
-- Does this chat template preselect a provider?
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2541393632"] = "Wählt diese Chat-Vorlage einen Anbieter vorab aus?"
-- Tell the AI your system prompt. -- Tell the AI your system prompt.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2552381261"] = "Teilen Sie der KI einen System-Prompt mit." UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2552381261"] = "Teilen Sie der KI einen System-Prompt mit."
@@ -6231,6 +6297,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2579080722"] = "Nein, d
-- You might want to predefine a first message that will be copied into the user prompt, when you use this chat template. This message could for example be a blueprint for a structured message that this chat template is defined to work with. -- You might want to predefine a first message that will be copied into the user prompt, when you use this chat template. This message could for example be a blueprint for a structured message that this chat template is defined to work with.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2579208700"] = "Sie können eine Nachricht vordefinieren, die als Benutzereingabe verwendet wird, wenn Sie diese Chat-Vorlage verwenden. Diese Nachricht könnte beispielsweise eine Vorlage für eine strukturierte Nachricht sein, für die diese Chat-Vorlage entwickelt wurde." UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2579208700"] = "Sie können eine Nachricht vordefinieren, die als Benutzereingabe verwendet wird, wenn Sie diese Chat-Vorlage verwenden. Diese Nachricht könnte beispielsweise eine Vorlage für eine strukturierte Nachricht sein, für die diese Chat-Vorlage entwickelt wurde."
-- Does this chat template preselect a profile?
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2602340928"] = "Wählt diese Chat-Vorlage ein Profil vorab aus?"
-- Predefined User Input -- Predefined User Input
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2607897066"] = "Vordefinierte Benutzereingabe" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2607897066"] = "Vordefinierte Benutzereingabe"
@@ -6246,8 +6315,14 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2719480499"] = "Die Nac
-- Chat Template Name -- Chat Template Name
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T275026390"] = "Name der Chat-Vorlage" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T275026390"] = "Name der Chat-Vorlage"
-- Workspaces are switched off in your workspace settings. While they are off, chats with this template belong to no workspace, and they do not move into a workspace when you switch workspaces on again. You can change this choice once workspaces are on.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2828180414"] = "In Ihren Arbeitsbereichseinstellungen sind Arbeitsbereiche deaktiviert. Solange sie deaktiviert sind, gehören Chats mit dieser Vorlage zu keinem Arbeitsbereich und werden auch nicht in einen Arbeitsbereich verschoben, wenn Sie Arbeitsbereiche wieder aktivieren. Sie können diese Auswahl ändern, sobald Arbeitsbereiche aktiviert sind."
-- Please relink the attachment '{0}' to a file on this computer or remove it before you save. -- Please relink the attachment '{0}' to a file on this computer or remove it before you save.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2884684079"] = "Bitte verknüpfen Sie den Anhang „{0}“ vor dem Speichern erneut mit einer Datei auf diesem Computer oder entfernen Sie ihn." UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2884684079"] = "Bitte verknüpfen Sie den Anhang „{0}“ vor dem Speichern erneut mit einer Datei auf diesem Rechner oder entfernen Sie ihn."
-- Workspace name
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T295876489"] = "Name des Arbeitsbereichs"
-- Commit Changes -- Commit Changes
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2996302517"] = "Änderungen übernehmen" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2996302517"] = "Änderungen übernehmen"
@@ -6264,21 +6339,42 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T3108503534"] = "Sie kö
-- Are you unsure which system prompt to use? You might start with the default system prompt that AI Studio uses for all chats. -- Are you unsure which system prompt to use? You might start with the default system prompt that AI Studio uses for all chats.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T3127437308"] = "Sind Sie unsicher, welchen System-Prompt Sie verwenden sollen? Sie können mit dem Standard-System-Prompt beginnen, den AI Studio für alle Chats verwendet." UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T3127437308"] = "Sind Sie unsicher, welchen System-Prompt Sie verwenden sollen? Sie können mit dem Standard-System-Prompt beginnen, den AI Studio für alle Chats verwendet."
-- A chat template may also decide which profile a chat starts with, including starting without one. Without such a decision, those chats start with the profile they would start with anyway. The profile stays changeable in the chat.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T313529011"] = "Eine Chat-Vorlage kann auch festlegen, mit welchem Profil ein Chat startet, oder dass er ohne Profil startet. Ohne eine solche Festlegung starten diese Chats mit dem Profil, mit dem sie ohnehin starten würden. Das Profil lässt sich im Chat weiterhin ändern."
-- The profile this chat template preselects does not exist on this computer. Chats started with it keep their usual profile.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T3170335831"] = "Das Profil, das diese Chat-Vorlage vorab auswählt, ist auf diesem Rechner nicht vorhanden. Mit ihr gestartete Chats behalten ihr übliches Profil."
-- Using some chat templates in tandem with profiles might cause issues. Therefore, you might prohibit the usage of profiles here. -- Using some chat templates in tandem with profiles might cause issues. Therefore, you might prohibit the usage of profiles here.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T3227981830"] = "Die gleichzeitige Verwendung einiger Chat-Vorlagen mit Profilen kann zu Problemen führen. Deshalb könnten Sie hier die Nutzung von Profilen untersagen." UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T3227981830"] = "Die gleichzeitige Verwendung einiger Chat-Vorlagen mit Profilen kann zu Problemen führen. Deshalb könnten Sie hier die Nutzung von Profilen untersagen."
-- Choose an existing workspace or enter a name that should be created when a chat with this template starts.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T3256439227"] = "Wählen Sie einen vorhandenen Arbeitsbereich aus oder geben Sie einen Namen ein, der erstellt werden soll, wenn ein Chat mit dieser Vorlage startet."
-- No, chats keep the data source options from your chat options -- No, chats keep the data source options from your chat options
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T3268470871"] = "Nein, Chats behalten die Datenquellen-Optionen aus Ihren Chat-Optionen" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T3268470871"] = "Nein, Chats behalten die Datenquellen-Optionen aus Ihren Chat-Optionen"
-- A chat template may decide which tools a chat starts with. Without such a decision, those chats start with the tools you chose as your default in the chat options. Deciding and then picking nothing is a different statement: such chats start with no tool at all, no matter what your default says. -- A chat template may decide which tools a chat starts with. Without such a decision, those chats start with the tools you chose as your default in the chat options. Deciding and then picking nothing is a different statement: such chats start with no tool at all, no matter what your default says.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T3329415439"] = "Eine Chat-Vorlage kann festlegen, mit welchen Werkzeugen ein Chat startet. Ohne eine solche Festlegung starten diese Chats mit den Werkzeugen, die Sie in den Chat-Optionen als Standard ausgewählt haben. Legen Sie es fest und wählen dann nichts aus, ist das eine andere Aussage: Solche Chats starten ohne jedes Werkzeug, ganz gleich, was Ihr Standard vorsieht." UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T3329415439"] = "Eine Chat-Vorlage kann festlegen, mit welchen Werkzeugen ein Chat startet. Ohne eine solche Festlegung starten diese Chats mit den Werkzeugen, die Sie in den Chat-Optionen als Standard ausgewählt haben. Legen Sie es fest und wählen dann nichts aus, ist das eine andere Aussage: Solche Chats starten ohne jedes Werkzeug, ganz gleich, was Ihr Standard vorsieht."
-- A chat template may keep its chats in a workspace of its own, which is created when it does not exist yet. This applies whenever a chat would otherwise be a disappearing chat, for example, when you start a new chat outside the workspaces or pick this template in a new chat. A chat you start in a workspace on purpose, for example, from the workspace list, stays in that workspace.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T3352941260"] = "Eine Chat-Vorlage kann ihre Chats in einem eigenen Arbeitsbereich ablegen, der erstellt wird, falls er noch nicht existiert. Das gilt immer dann, wenn ein Chat sonst ein selbstlöschender Chat wäre, zum Beispiel wenn Sie einen neuen Chat außerhalb der Arbeitsbereiche starten oder diese Vorlage in einem neuen Chat auswählen. Ein Chat, den Sie gezielt in einem Arbeitsbereich starten, zum Beispiel über die Liste der Arbeitsbereiche, bleibt in diesem Arbeitsbereich."
-- Add a message -- Add a message
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T3372872324"] = "Nachricht hinzufügen" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T3372872324"] = "Nachricht hinzufügen"
-- Yes, this template decides which provider a chat starts with
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T3422337353"] = "Ja, diese Vorlage legt fest, mit welchem Anbieter ein Chat startet"
-- Close -- Close
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T3448155331"] = "Schließen" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T3448155331"] = "Schließen"
-- No, chats keep their usual profile
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T3465925873"] = "Nein, Chats behalten ihr übliches Profil"
-- The provider this chat template preselects does not exist on this computer or does not meet the confidence level required for chats. Chats started with it keep their usual provider.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T349281189"] = "Der Anbieter, den diese Chat-Vorlage vorab auswählt, ist auf diesem Rechner nicht vorhanden oder erfüllt das für Chats erforderliche Vertrauensniveau nicht. Mit ihr gestartete Chats behalten ihren üblichen Anbieter."
-- Unsupported content type -- Unsupported content type
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T3570316759"] = "Nicht unterstützter Inhaltstyp" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T3570316759"] = "Nicht unterstützter Inhaltstyp"
@@ -6315,12 +6411,18 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T4051106111"] = "Verwend
-- Tell the AI your predefined user input. -- Tell the AI your predefined user input.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T4052406705"] = "Teilen Sie der KI Ihre vordefinierte Benutzereingabe mit." UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T4052406705"] = "Teilen Sie der KI Ihre vordefinierte Benutzereingabe mit."
-- Does this chat template keep its chats in a workspace?
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T4057825866"] = "Legt diese Chat-Vorlage ihre Chats in einem Arbeitsbereich ab?"
-- Some attachments of this chat template are not on this computer. Please relink or remove them before you add the template. -- Some attachments of this chat template are not on this computer. Please relink or remove them before you add the template.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T4194591607"] = "Einige Anhänge dieser Chat-Vorlage sind auf diesem Computer nicht vorhanden. Bitte verknüpfen Sie sie erneut oder entfernen Sie sie, bevor Sie die Vorlage hinzufügen." UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T4194591607"] = "Einige Anhänge dieser Chat-Vorlage sind auf diesem Rechner nicht vorhanden. Bitte verknüpfen Sie sie erneut oder entfernen Sie sie, bevor Sie die Vorlage hinzufügen."
-- Create your custom chat template to tailor the LLM's behavior for specific tasks or domains. Define a custom system prompt and provide an example conversation to design an AI experience perfectly suited to your requirements. -- Create your custom chat template to tailor the LLM's behavior for specific tasks or domains. Define a custom system prompt and provide an example conversation to design an AI experience perfectly suited to your requirements.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T4199560726"] = "Erstellen Sie Ihre eigene Chat-Vorlage, um das Verhalten des LLMs für bestimmte Aufgaben oder Bereiche anzupassen. Definieren Sie einen individuellen System-Prompt und geben Sie eine Beispiel-Konversation vor, um eine KI-Erfahrung zu gestalten, die genau auf Ihre Anforderungen zugeschnitten ist." UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T4199560726"] = "Erstellen Sie Ihre eigene Chat-Vorlage, um das Verhalten des LLMs für bestimmte Aufgaben oder Bereiche anzupassen. Definieren Sie einen individuellen System-Prompt und geben Sie eine Beispiel-Konversation vor, um eine KI-Erfahrung zu gestalten, die genau auf Ihre Anforderungen zugeschnitten ist."
-- Please choose the provider this chat template preselects, or switch the preselection off.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T442309864"] = "Bitte wählen Sie den Anbieter aus, den diese Chat-Vorlage vorab auswählt, oder deaktivieren Sie die Vorauswahl."
-- Enter a message -- Enter a message
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T446374405"] = "Nachricht eingeben" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T446374405"] = "Nachricht eingeben"
@@ -6333,15 +6435,24 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T628396066"] = "System-P
-- A chat started with this template begins with these data sources and options. All of it stays changeable in the chat itself. -- A chat started with this template begins with these data sources and options. All of it stays changeable in the chat itself.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T650711085"] = "Ein mit dieser Vorlage gestarteter Chat beginnt mit diesen Datenquellen und Optionen. Alles davon lässt sich im Chat selbst weiterhin ändern." UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T650711085"] = "Ein mit dieser Vorlage gestarteter Chat beginnt mit diesen Datenquellen und Optionen. Alles davon lässt sich im Chat selbst weiterhin ändern."
-- Workspace
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T658612054"] = "Arbeitsbereich"
-- Remove this attachment -- Remove this attachment
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T70791272"] = "Diesen Anhang entfernen" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T70791272"] = "Diesen Anhang entfernen"
-- The selection stays changeable in the chat, and every tool still has to meet the confidence requirements of the provider in use. -- The selection stays changeable in the chat, and every tool still has to meet the confidence requirements of the provider in use.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T722788866"] = "Die Auswahl lässt sich im Chat weiterhin ändern, und jedes Werkzeug muss die Vertrauensanforderungen des verwendeten Anbieters erfüllen." UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T722788866"] = "Die Auswahl lässt sich im Chat weiterhin ändern, und jedes Werkzeug muss die Vertrauensanforderungen des verwendeten Anbieters erfüllen."
-- No, chats keep their usual provider
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T787085105"] = "Nein, Chats behalten ihren üblichen Anbieter"
-- Allow the use of profiles together with this chat template? -- Allow the use of profiles together with this chat template?
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T823785464"] = "Erlauben Sie die Verwendung von Profilen zusammen mit dieser Chat-Vorlage?" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T823785464"] = "Erlauben Sie die Verwendung von Profilen zusammen mit dieser Chat-Vorlage?"
-- Provider
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T900237532"] = "Anbieter"
-- Cancel -- Cancel
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T900713019"] = "Abbrechen" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T900713019"] = "Abbrechen"
@@ -6807,9 +6918,6 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T2460883298"
-- Add -- Add
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T2646845972"] = "Hinzufügen" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T2646845972"] = "Hinzufügen"
-- The documents of this data source are already prepared, so its folder cannot be changed. Another folder holds other documents, which makes it another data source: please add one for it. The embedding provider below can be changed.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T2686150329"] = "Die Dokumente dieser Datenquelle sind bereits vorbereitet, daher kann ihr Ordner nicht geändert werden. Ein anderer Ordner enthält andere Dokumente und ist damit eine andere Datenquelle: Bitte fügen Sie dafür eine neue hinzu. Den Einbettungsanbieter darunter können Sie ändern."
-- The embedding you selected runs locally or in your organization. Your data is not sent to the cloud. -- The embedding you selected runs locally or in your organization. Your data is not sent to the cloud.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T2814869210"] = "Die von Ihnen ausgewählte Einbettung läuft lokal oder innerhalb Ihrer Organisation. Ihre Daten werden nicht in die Cloud übertragen." UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T2814869210"] = "Die von Ihnen ausgewählte Einbettung läuft lokal oder innerhalb Ihrer Organisation. Ihre Daten werden nicht in die Cloud übertragen."
@@ -7068,9 +7176,6 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T787118522"] = "D
-- Data Source Name -- Data Source Name
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T813773421"] = "Name der Datenquelle" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T813773421"] = "Name der Datenquelle"
-- The documents of this data source are already prepared, so its file cannot be changed. Another file holds other content, which makes it another data source: please add one for it. The embedding provider below can be changed.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T890383764"] = "Die Dokumente dieser Datenquelle sind bereits vorbereitet, daher kann ihre Datei nicht geändert werden. Eine andere Datei enthält andere Inhalte und ist damit eine andere Datenquelle: Bitte fügen Sie dafür eine neue hinzu. Den Einbettungsanbieter darunter können Sie ändern."
-- Cancel -- Cancel
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T900713019"] = "Abbrechen" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T900713019"] = "Abbrechen"
@@ -7548,6 +7653,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T1727440780"] = "Ho
-- Load -- Load
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T1756340745"] = "Laden" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T1756340745"] = "Laden"
-- The use of this provider has been prohibited by your organization. Please select a different provider.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T1807387355"] = "Die Verwendung dieses Anbieters wurde von Ihrer Organisation untersagt. Bitte wählen Sie einen anderen Anbieter."
-- Update -- Update
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T1847791252"] = "Aktualisieren" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T1847791252"] = "Aktualisieren"
@@ -8073,6 +8181,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T1727440780"] = "Hostname"
-- Always on -- Always on
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T1761671861"] = "Immer an" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T1761671861"] = "Immer an"
-- The use of this provider has been prohibited by your organization. Please select a different provider.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T1807387355"] = "Die Verwendung dieses Anbieters wurde von Ihrer Organisation untersagt. Bitte wählen Sie einen anderen Anbieter."
-- Reset -- Reset
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T180921696"] = "Zurücksetzen" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T180921696"] = "Zurücksetzen"
@@ -8580,6 +8691,33 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGASSISTANTBIAS::T6790
-- When enabled, you can preselect options. This is might be useful when you prefer a specific language or LLM model. -- When enabled, you can preselect options. This is might be useful when you prefer a specific language or LLM model.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGASSISTANTBIAS::T711745239"] = "Wenn diese Option aktiviert ist, können Sie Voreinstellungen vornehmen. Das kann nützlich sein, wenn Sie eine bestimmte Sprache oder ein bestimmtes LLM-Modell bevorzugen." UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGASSISTANTBIAS::T711745239"] = "Wenn diese Option aktiviert ist, können Sie Voreinstellungen vornehmen. Das kann nützlich sein, wenn Sie eine bestimmte Sprache oder ein bestimmtes LLM-Modell bevorzugen."
-- When enabled, the Assistant Builder starts with the defaults configured below. When disabled, it uses the app-wide default provider and preselects no output language.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGASSISTANTBUILDER::T1496952909"] = "Wenn diese Option aktiviert ist, startet der Assistenten-Builder mit den unten festgelegten Standardwerten. Wenn sie deaktiviert ist, verwendet er den appweiten Standardanbieter und wählt keine Ausgabesprache vorab aus."
-- Preselect Assistant Builder options?
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGASSISTANTBUILDER::T2127606148"] = "Optionen für den Assistenten-Builder vorauswählen?"
-- Default custom output language
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGASSISTANTBUILDER::T3360584376"] = "Standardmäßige benutzerdefinierte Ausgabesprache"
-- Close
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGASSISTANTBUILDER::T3448155331"] = "Schließen"
-- Default output language
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGASSISTANTBUILDER::T3652922567"] = "Standard-Ausgabesprache"
-- Assistant: Assistant Builder defaults
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGASSISTANTBUILDER::T3668274570"] = "Assistent: Standardwerte für den Assistenten-Builder"
-- Assistant Builder options are preselected
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGASSISTANTBUILDER::T3962364100"] = "Optionen für den Assistenten-Builder sind vorausgewählt"
-- No Assistant Builder options are preselected
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGASSISTANTBUILDER::T4104323209"] = "Für den Assistenten-Builder sind keine Optionen vorausgewählt"
-- The language in which the assistants you build answer.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGASSISTANTBUILDER::T42126731"] = "Die Sprache, in der die von Ihnen erstellten Assistenten antworten."
-- Default minimum pause between files -- Default minimum pause between files
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T1008440099"] = "Standardmäßige Mindestpause zwischen Dateien" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T1008440099"] = "Standardmäßige Mindestpause zwischen Dateien"
@@ -8625,6 +8763,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T22
-- Hidden files and folders are included -- Hidden files and folders are included
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T2288334048"] = "Versteckte Dateien und Ordner werden einbezogen" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T2288334048"] = "Versteckte Dateien und Ordner werden einbezogen"
-- Separate multiple file patterns with a semicolon, e.g., *.pdf;*.docx. The standard patterns include all supported audio and video formats as well as images in JPG, PNG, WebP, GIF, BMP, and TIFF format.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T232588799"] = "Trennen Sie mehrere Dateimuster durch ein Semikolon, z. B. *.pdf;*.docx. Die Standardmuster umfassen alle unterstützten Audio- und Videoformate sowie Bilder im JPG-, PNG-, WebP-, GIF-, BMP- und TIFF-Format."
-- The lower end of the random pause interval. AI Studio never allows less than 6 seconds. -- The lower end of the random pause interval. AI Studio never allows less than 6 seconds.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T237774509"] = "Das untere Ende des Intervalls für die zufällige Pause. AI Studio erlaubt niemals weniger als 6 Sekunden." UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T237774509"] = "Das untere Ende des Intervalls für die zufällige Pause. AI Studio erlaubt niemals weniger als 6 Sekunden."
@@ -8637,9 +8778,6 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T24
-- When enabled, new batch runs start with the defaults configured below. -- When enabled, new batch runs start with the defaults configured below.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T2592677194"] = "Wenn aktiviert, werden neue Stapel-Durchläufe mit den unten konfigurierten Standardwerten gestartet." UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T2592677194"] = "Wenn aktiviert, werden neue Stapel-Durchläufe mit den unten konfigurierten Standardwerten gestartet."
-- Separate multiple file patterns with a semicolon, e.g., *.pdf;*.docx. The standard patterns include all supported audio and video formats.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T2594325620"] = "Trennen Sie mehrere Dateimuster durch ein Semikolon, z. B. *.pdf;*.docx. Die Standardmuster umfassen alle unterstützten Audio- und Videoformate."
-- Subfolders are included -- Subfolders are included
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T2607092632"] = "Unterordner werden einbezogen" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T2607092632"] = "Unterordner werden einbezogen"
@@ -9825,6 +9963,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T1727440780"] =
-- Load -- Load
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T1756340745"] = "Laden" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T1756340745"] = "Laden"
-- The use of this provider has been prohibited by your organization. Please select a different provider.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T1807387355"] = "Die Verwendung dieses Anbieters wurde von Ihrer Organisation untersagt. Bitte wählen Sie einen anderen Anbieter."
-- Update -- Update
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T1847791252"] = "Aktualisieren" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T1847791252"] = "Aktualisieren"
@@ -10374,6 +10515,9 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::EMBEDDINGS::T819555436"] = "Fehlgeschlagene E-
-- Skipped -- Skipped
UI_TEXT_CONTENT["AISTUDIO::PAGES::EMBEDDINGS::T852993367"] = "Übersprungen" UI_TEXT_CONTENT["AISTUDIO::PAGES::EMBEDDINGS::T852993367"] = "Übersprungen"
-- Edit this data source
UI_TEXT_CONTENT["AISTUDIO::PAGES::EMBEDDINGS::T878700886"] = "Diese Datenquelle bearbeiten"
-- Open the transcription settings -- Open the transcription settings
UI_TEXT_CONTENT["AISTUDIO::PAGES::EMBEDDINGS::T905182399"] = "Transkriptionseinstellungen öffnen" UI_TEXT_CONTENT["AISTUDIO::PAGES::EMBEDDINGS::T905182399"] = "Transkriptionseinstellungen öffnen"
@@ -11865,6 +12009,9 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::AIJOBS::AIJOBSERVICE::T1578005752"] = "Das aus
-- The selected provider is not allowed for this chat. -- The selected provider is not allowed for this chat.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::AIJOBS::AIJOBSERVICE::T174545104"] = "Der ausgewählte Anbieter ist für diesen Chat nicht zulässig." UI_TEXT_CONTENT["AISTUDIO::TOOLS::AIJOBS::AIJOBSERVICE::T174545104"] = "Der ausgewählte Anbieter ist für diesen Chat nicht zulässig."
-- The use of this provider has been prohibited by your organization. Please select a different provider.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::AIJOBS::AIJOBSERVICE::T1807387355"] = "Die Verwendung dieses Anbieters wurde von Ihrer Organisation untersagt. Bitte wählen Sie einen anderen Anbieter."
-- The AI job failed. The message is: '{0}' -- The AI job failed. The message is: '{0}'
UI_TEXT_CONTENT["AISTUDIO::TOOLS::AIJOBS::AIJOBSERVICE::T237448388"] = "Der KI-Auftrag ist fehlgeschlagen. Die Meldung lautet: „{0}“" UI_TEXT_CONTENT["AISTUDIO::TOOLS::AIJOBS::AIJOBSERVICE::T237448388"] = "Der KI-Auftrag ist fehlgeschlagen. Die Meldung lautet: „{0}“"
@@ -12210,18 +12357,39 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATABASES::VECTORSTORE::QDRANTEDGECLIENTIMPLEM
-- Qdrant Edge is not available. -- Qdrant Edge is not available.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATABASES::VECTORSTORE::QDRANTEDGECLIENTIMPLEMENTATION::T744445696"] = "Qdrant Edge ist nicht verfügbar." UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATABASES::VECTORSTORE::QDRANTEDGECLIENTIMPLEMENTATION::T744445696"] = "Qdrant Edge ist nicht verfügbar."
-- Edit Local Directory Data Source
UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATASOURCEEDITING::T1215599168"] = "Datenquelle bearbeiten: Lokaler Ordner"
-- Edit ERI v1 Data Source
UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATASOURCEEDITING::T221059217"] = "ERI v1 Datenquelle bearbeiten"
-- Edit Local File Data Source
UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATASOURCEEDITING::T2453292893"] = "Datenquelle bearbeiten: Lokale Datei"
-- Every other file goes to your embedding provider once more, which takes a while with many files. Files missing at the new location are removed from the index, while your files on disk stay as they are.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATASOURCEREINDEXWARNING::T1555760974"] = "Jede andere Datei wird erneut an Ihren Einbettungsanbieter gesendet, was bei vielen Dateien eine Weile dauert. Dateien, die am neuen Speicherort fehlen, werden aus dem Index entfernt, während Ihre Dateien selbst unverändert bleiben."
-- AI Studio keeps what it has already prepared for every file it finds unchanged at the new location, that is, of the same type and with the same size and modification time. In a folder, such a file also has to keep its name and the subfolder it is in.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATASOURCEREINDEXWARNING::T2205693550"] = "Was AI Studio bereits vorbereitet hat, bleibt für jede Datei erhalten, die es am neuen Speicherort unverändert findet, also mit demselben Dateityp, derselben Größe und demselben Änderungsdatum. In einem Ordner muss eine solche Datei außerdem ihren Namen behalten und im selben Unterordner liegen."
-- They keep answering keyword searches, but searching them by meaning stops working, and no further documents can be prepared for them. The ones which are already prepared stay tied to this provider as well, so you cannot simply move them to another one. -- They keep answering keyword searches, but searching them by meaning stops working, and no further documents can be prepared for them. The ones which are already prepared stay tied to this provider as well, so you cannot simply move them to another one.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATASOURCEREINDEXWARNING::T2343773457"] = "Sie beantworten weiterhin Stichwortsuchen, aber die Suche nach Bedeutung funktioniert nicht mehr, und es können keine weiteren Dokumente für sie vorbereitet werden. Bereits vorbereitete Datenquellen bleiben zudem an diesen Anbieter gebunden, sodass Sie sie nicht einfach auf einen anderen umstellen können." UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATASOURCEREINDEXWARNING::T2343773457"] = "Sie beantworten weiterhin Stichwortsuchen, aber die Suche nach Bedeutung funktioniert nicht mehr, und es können keine weiteren Dokumente für sie vorbereitet werden. Bereits vorbereitete Datenquellen bleiben zudem an diesen Anbieter gebunden, sodass Sie sie nicht einfach auf einen anderen umstellen können."
-- and {0} more. -- and {0} more.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATASOURCEREINDEXWARNING::T2519847121"] = "und {0} weitere." UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATASOURCEREINDEXWARNING::T2519847121"] = "und {0} weitere."
-- Your embedding provider runs in the cloud, so preparing files again costs money.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATASOURCEREINDEXWARNING::T2635623258"] = "Ihr Einbettungsanbieter läuft in der Cloud, daher kostet es Geld, Dateien erneut vorzubereiten."
-- This change makes the prepared documents of the following data sources unusable ({0}): -- This change makes the prepared documents of the following data sources unusable ({0}):
UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATASOURCEREINDEXWARNING::T3337378891"] = "Durch diese Änderung werden die vorbereiteten Dokumente der folgenden Datenquellen unbrauchbar ({0}):" UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATASOURCEREINDEXWARNING::T3337378891"] = "Durch diese Änderung werden die vorbereiteten Dokumente der folgenden Datenquellen unbrauchbar ({0}):"
-- Do you want to apply this change anyway? -- Do you want to apply this change anyway?
UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATASOURCEREINDEXWARNING::T3419411838"] = "Möchten Sie diese Änderung trotzdem übernehmen?" UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATASOURCEREINDEXWARNING::T3419411838"] = "Möchten Sie diese Änderung trotzdem übernehmen?"
-- New Location for This Data Source
UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATASOURCEREINDEXWARNING::T3462449568"] = "Neuer Speicherort für diese Datenquelle"
-- Documents Will Be Prepared Again -- Documents Will Be Prepared Again
UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATASOURCEREINDEXWARNING::T737291513"] = "Dokumente werden erneut vorbereitet" UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATASOURCEREINDEXWARNING::T737291513"] = "Dokumente werden erneut vorbereitet"
@@ -12967,41 +13135,53 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::PLUGINASSISTANTSECUR
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::PLUGINASSISTANTSECURITYRESOLVER::T995107927"] = "Der Plugin-Code wurde nach der letzten Sicherheitsprüfung geändert. Das gespeicherte Ergebnis stimmt nicht mehr mit dem aktuellen Code überein, daher muss dieses Assistenten-Plugin erneut geprüft werden, bevor es aktiviert oder verwendet werden darf." UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::PLUGINASSISTANTSECURITYRESOLVER::T995107927"] = "Der Plugin-Code wurde nach der letzten Sicherheitsprüfung geändert. Das gespeicherte Ergebnis stimmt nicht mehr mit dem aktuellen Code überein, daher muss dieses Assistenten-Plugin erneut geprüft werden, bevor es aktiviert oder verwendet werden darf."
-- The token or password of the data source cannot be decrypted on this computer. Such data sources can only be imported where the encryption secret of your organization is configured. -- The token or password of the data source cannot be decrypted on this computer. Such data sources can only be imported where the encryption secret of your organization is configured.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETIMPORT::T186946778"] = "Das Token oder Passwort der Datenquelle lässt sich auf diesem Computer nicht entschlüsseln. Datenquellen dieser Art können nur auf einem Computer importiert werden, auf dem das Verschlüsselungsgeheimnis Ihrer Organisation eingerichtet ist." UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETIMPORT::T186946778"] = "Das Token oder Passwort der Datenquelle lässt sich auf diesem Rechner nicht entschlüsseln. Datenquellen dieser Art können nur auf einem Rechner importiert werden, auf dem das Verschlüsselungsgeheimnis Ihrer Organisation eingerichtet ist."
-- The chat template preselects the provider '{0}', which does not meet the confidence level required for chats. Chats started with it use your usual provider instead.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETIMPORT::T190892810"] = "Die Chat-Vorlage wählt den Anbieter „{0}“ vorab aus, der das für Chats erforderliche Vertrauensniveau nicht erfüllt. Mit ihr gestartete Chats verwenden dann Ihren üblichen Anbieter."
-- Data sources which sign in with Kerberos cannot be imported yet. Please add them yourself. -- Data sources which sign in with Kerberos cannot be imported yet. Please add them yourself.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETIMPORT::T206419407"] = "Datenquellen, die Kerberos zur Anmeldung verwenden, können noch nicht importiert werden. Bitte fügen Sie sie selbst hinzu." UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETIMPORT::T206419407"] = "Datenquellen, die Kerberos zur Anmeldung verwenden, können noch nicht importiert werden. Bitte fügen Sie sie selbst hinzu."
-- The tokenizer '{0}' is not on this computer, so the provider is imported without it. -- The tokenizer '{0}' is not on this computer, so the provider is imported without it.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETIMPORT::T2556276619"] = "Der Tokenizer „{0}“ ist auf diesem Computer nicht vorhanden. Daher wird der Anbieter ohne ihn importiert." UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETIMPORT::T2556276619"] = "Der Tokenizer „{0}“ ist auf diesem Rechner nicht vorhanden. Daher wird der Anbieter ohne ihn importiert."
-- The chat template preselects data sources which do not exist on this computer: {0}. -- The chat template preselects data sources which do not exist on this computer: {0}.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETIMPORT::T2608225446"] = "Die Chat-Vorlage wählt Datenquellen vorab aus, die auf diesem Computer nicht vorhanden sind: {0}." UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETIMPORT::T2608225446"] = "Die Chat-Vorlage wählt Datenquellen vorab aus, die auf diesem Rechner nicht vorhanden sind: {0}."
-- The chat template preselects tools which are not available on this computer: {0}. -- The chat template preselects tools which are not available on this computer: {0}.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETIMPORT::T2693686582"] = "Die Chat-Vorlage wählt Werkzeuge vorab aus, die auf diesem Computer nicht verfügbar sind: {0}." UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETIMPORT::T2693686582"] = "Die Chat-Vorlage wählt Werkzeuge vorab aus, die auf diesem Rechner nicht verfügbar sind: {0}."
-- The chat template preselects a profile which does not exist on this computer: {0}. Chats started with it use your usual profile instead.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETIMPORT::T2735390326"] = "Die Chat-Vorlage wählt ein Profil vorab aus, das auf diesem Rechner nicht vorhanden ist: {0}. Mit ihr gestartete Chats verwenden dann Ihr übliches Profil."
-- The chat template preselects a provider which does not exist on this computer: {0}. Chats started with it use your usual provider instead.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETIMPORT::T3321789654"] = "Die Chat-Vorlage wählt einen Anbieter vorab aus, der auf diesem Rechner nicht vorhanden ist: {0}. Mit ihr gestartete Chats verwenden dann Ihren üblichen Anbieter."
-- The API key in the pasted configuration cannot be decrypted on this computer. Please enter your API key before you save the provider. -- The API key in the pasted configuration cannot be decrypted on this computer. Please enter your API key before you save the provider.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETIMPORT::T3589070261"] = "Der API-Schlüssel in der eingefügten Konfiguration kann auf diesem Computer nicht entschlüsselt werden. Bitte geben Sie Ihren API-Schlüssel ein, bevor Sie den Anbieter speichern." UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETIMPORT::T3589070261"] = "Der API-Schlüssel in der eingefügten Konfiguration kann auf diesem Rechner nicht entschlüsselt werden. Bitte geben Sie Ihren API-Schlüssel ein, bevor Sie den Anbieter speichern."
-- The chat template attaches these files from this computer to every chat started with it: {0}. Please check that you want to share them. -- The chat template attaches these files from this computer to every chat started with it: {0}. Please check that you want to share them.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETIMPORT::T3702015680"] = "Die Chat-Vorlage fügt jedem damit gestarteten Chat folgende Dateien von diesem Computer hinzu: {0}. Bitte vergewissern Sie sich, dass Sie diese Dateien teilen möchten." UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETIMPORT::T3702015680"] = "Die Chat-Vorlage fügt jedem damit gestarteten Chat folgende Dateien von diesem Rechner hinzu: {0}. Bitte vergewissern Sie sich, dass Sie diese Dateien teilen möchten."
-- The policy preselects a provider which does not exist on this computer: {0}. -- The policy preselects a provider which does not exist on this computer: {0}.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETIMPORT::T3849646238"] = "Das Regelwerk wählt einen Anbieter vorab aus, der auf diesem Computer nicht vorhanden ist: {0}." UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETIMPORT::T3849646238"] = "Das Regelwerk wählt einen Anbieter vorab aus, der auf diesem Rechner nicht vorhanden ist: {0}."
-- The pasted configuration is not a valid configuration of the type '{0}'. -- The pasted configuration is not a valid configuration of the type '{0}'.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETIMPORT::T4231596123"] = "Die eingefügte Konfiguration ist keine gültige Konfiguration vom Typ „{0}“." UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETIMPORT::T4231596123"] = "Die eingefügte Konfiguration ist keine gültige Konfiguration vom Typ „{0}“."
-- The policy permits tools which are not available on this computer: {0}. -- The policy permits tools which are not available on this computer: {0}.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETIMPORT::T582643438"] = "Das Regelwerk erlaubt Werkzeuge, die auf diesem Computer nicht verfügbar sind: {0}." UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETIMPORT::T582643438"] = "Das Regelwerk erlaubt Werkzeuge, die auf diesem Rechner nicht verfügbar sind: {0}."
-- The policy preselects a profile which does not exist on this computer: {0}. -- The policy preselects a profile which does not exist on this computer: {0}.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETIMPORT::T600255824"] = "Das Regelwerk wählt ein Profil vorab aus, das auf diesem Computer nicht vorhanden ist: {0}." UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETIMPORT::T600255824"] = "Das Regelwerk wählt ein Profil vorab aus, das auf diesem Rechner nicht vorhanden ist: {0}."
-- The pasted configuration contains an API key which is not encrypted, so it was left out. Please enter your API key before you save the provider. -- The pasted configuration contains an API key which is not encrypted, so it was left out. Please enter your API key before you save the provider.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETIMPORT::T639320638"] = "Die eingefügte Konfiguration enthält einen unverschlüsselten API-Schlüssel. Dieser wurde daher ausgelassen. Bitte geben Sie Ihren API-Schlüssel ein, bevor Sie den Anbieter speichern." UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETIMPORT::T639320638"] = "Die eingefügte Konfiguration enthält einen unverschlüsselten API-Schlüssel. Dieser wurde daher ausgelassen. Bitte geben Sie Ihren API-Schlüssel ein, bevor Sie den Anbieter speichern."
-- The chat template preselects a profile but does not allow profiles. Chats started with it ignore that profile.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETIMPORT::T769062029"] = "Die Chat-Vorlage wählt vorab ein Profil aus, erlaubt aber keine Profile. Mit ihr gestartete Chats ignorieren dieses Profil."
-- Expected a quoted string at character {0}. -- Expected a quoted string at character {0}.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETPARSER::T1235695745"] = "An Position {0} wurde ein Text in Anführungszeichen erwartet." UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETPARSER::T1235695745"] = "An Position {0} wurde ein Text in Anführungszeichen erwartet."
@@ -13404,9 +13584,36 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPES::T378481461"] = "Source Code
-- Document -- Document
UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPES::T4165204724"] = "Dokument" UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPES::T4165204724"] = "Dokument"
-- Image for AI models
UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPES::T791951937"] = "Bild für KI-Modelle"
-- Plugin archive -- Plugin archive
UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPES::T927001356"] = "Plugin-Archiv" UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPES::T927001356"] = "Plugin-Archiv"
-- AI Studio was not able to prepare the image '{0}' for the AI, so it was not sent.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::MODELIMAGEERRORCODEEXTENSIONS::T1345506748"] = "AI Studio konnte das Bild „{0}“ nicht für die KI vorbereiten und hat es daher nicht gesendet."
-- The image '{0}' could not be made small enough for the selected provider, so it was not sent.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::MODELIMAGEERRORCODEEXTENSIONS::T1463083567"] = "Das Bild „{0}“ konnte für den ausgewählten Anbieter nicht ausreichend verkleinert werden und wurde daher nicht gesendet."
-- The image '{0}' could not be opened and was not sent. When the image is stored on a network drive, the drive might be unavailable.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::MODELIMAGEERRORCODEEXTENSIONS::T2022724454"] = "Das Bild „{0}“ konnte nicht geöffnet und daher nicht gesendet werden. Wenn das Bild auf einem Netzlaufwerk gespeichert ist, ist das Laufwerk möglicherweise nicht verfügbar."
-- The image '{0}' does not exist anymore and was not sent.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::MODELIMAGEERRORCODEEXTENSIONS::T2330229240"] = "Das Bild „{0}“ existiert nicht mehr und wurde nicht gesendet."
-- The image '{0}' is not what its file extension claims, so it was not sent. Please correct its file extension.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::MODELIMAGEERRORCODEEXTENSIONS::T3457568536"] = "Das Bild „{0}“ hat nicht das Format, das seine Dateiendung angibt, und wurde daher nicht gesendet. Bitte korrigieren Sie die Dateiendung."
-- The image '{0}' could not be read and was not sent. It might be damaged or transferred incompletely.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::MODELIMAGEERRORCODEEXTENSIONS::T4029166607"] = "Das Bild „{0}“ konnte nicht gelesen und daher nicht gesendet werden. Möglicherweise ist es beschädigt oder unvollständig übertragen worden."
-- The image '{0}' has too many pixels for AI Studio to prepare it, so it was not sent. Please scale the image down first.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::MODELIMAGEERRORCODEEXTENSIONS::T567941882"] = "Das Bild „{0}“ hat zu viele Pixel, als dass AI Studio es vorbereiten könnte, und wurde daher nicht gesendet. Bitte verkleinern Sie das Bild zuerst."
-- The image '{0}' uses a format which AI Studio cannot give to an AI model, so it was not sent. Please convert the image to JPG or PNG.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::MODELIMAGEERRORCODEEXTENSIONS::T568830141"] = "Das Bild „{0}“ liegt in einem Format vor, das AI Studio nicht an ein KI-Modell übergeben kann, und wurde daher nicht gesendet. Bitte konvertieren Sie das Bild in JPG oder PNG."
-- Attempt to override instructions -- Attempt to override instructions
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SECURITY::PROMPTINJECTIONFINDINGCATEGORYEXTENSIONS::T161976090"] = "Versuch, Anweisungen zu überschreiben" UI_TEXT_CONTENT["AISTUDIO::TOOLS::SECURITY::PROMPTINJECTIONFINDINGCATEGORYEXTENSIONS::T161976090"] = "Versuch, Anweisungen zu überschreiben"
@@ -13650,6 +13857,9 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::DATASOURCEEMBEDDINGSERVICE::T1738200
-- The index of the data source '{0}' is missing. The data source stays out of your chats until its index was built anew. Use the repair action to start that. -- The index of the data source '{0}' is missing. The data source stays out of your chats until its index was built anew. Use the repair action to start that.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::DATASOURCEEMBEDDINGSERVICE::T204783307"] = "Der Index der Datenquelle „{0}“ fehlt. Die Datenquelle wird in Ihren Chats nicht verwendet, bis ihr Index neu erstellt wurde. Verwenden Sie die Aktion „Reparieren“, um dies zu starten." UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::DATASOURCEEMBEDDINGSERVICE::T204783307"] = "Der Index der Datenquelle „{0}“ fehlt. Die Datenquelle wird in Ihren Chats nicht verwendet, bis ihr Index neu erstellt wurde. Verwenden Sie die Aktion „Reparieren“, um dies zu starten."
-- The use of the embedding provider of this data source has been prohibited by your organization. Please select a different embedding provider in the settings of the data source.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::DATASOURCEEMBEDDINGSERVICE::T2092244746"] = "Die Verwendung des Einbettungsanbieters dieser Datenquelle wurde von Ihrer Organisation untersagt. Bitte wählen Sie in den Einstellungen der Datenquelle einen anderen Einbettungsanbieter."
-- The selected embedding provider is not allowed to index this data source. The data source asks for the confidence level '{0}', while the embedding provider has '{1}'. -- The selected embedding provider is not allowed to index this data source. The data source asks for the confidence level '{0}', while the embedding provider has '{1}'.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::DATASOURCEEMBEDDINGSERVICE::T2186533187"] = "Der ausgewählte Einbettungsanbieter darf diese Datenquelle nicht indexieren. Die Datenquelle erfordert das Vertrauensniveau „{0}“, während der Einbettungsanbieter „{1}“ hat." UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::DATASOURCEEMBEDDINGSERVICE::T2186533187"] = "Der ausgewählte Einbettungsanbieter darf diese Datenquelle nicht indexieren. Die Datenquelle erfordert das Vertrauensniveau „{0}“, während der Einbettungsanbieter „{1}“ hat."
@@ -13761,18 +13971,21 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::INDEXING::DOCUMENTRUNPROGRESS::T8042
-- The folder '{0}' could not be opened. Please check whether you are allowed to read it. Nothing was removed from the index. -- The folder '{0}' could not be opened. Please check whether you are allowed to read it. Nothing was removed from the index.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::INDEXING::FILESOURCEINDEXER::T1214987337"] = "Der Ordner „{0}“ konnte nicht geöffnet werden. Bitte prüfen Sie, ob Sie die erforderlichen Leserechte haben. Aus dem Index wurde nichts entfernt." UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::INDEXING::FILESOURCEINDEXER::T1214987337"] = "Der Ordner „{0}“ konnte nicht geöffnet werden. Bitte prüfen Sie, ob Sie die erforderlichen Leserechte haben. Aus dem Index wurde nichts entfernt."
-- The file '{0}' cannot be reached. Perhaps the drive it is on is not connected, or the file was moved or renamed. Nothing was removed from the index. Once the file is back, refresh this data source. When it is gone for good, remove this data source.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::INDEXING::FILESOURCEINDEXER::T1393648320"] = "Die Datei „{0}“ ist nicht erreichbar. Möglicherweise ist das Laufwerk, auf dem sie sich befindet, nicht verbunden, oder die Datei wurde verschoben oder umbenannt. Aus dem Index wurde nichts entfernt. Sobald die Datei wieder verfügbar ist, aktualisieren Sie diese Datenquelle. Wenn sie dauerhaft nicht mehr vorhanden ist, entfernen Sie diese Datenquelle."
-- The file '{0}' changed while it was being indexed. What was indexed of it is discarded, and the file is tried again during the next run. -- The file '{0}' changed while it was being indexed. What was indexed of it is discarded, and the file is tried again during the next run.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::INDEXING::FILESOURCEINDEXER::T1935191670"] = "Die Datei „{0}“ wurde während der Indexierung geändert. Die bereits indexierten Inhalte werden verworfen, und die Datei wird beim nächsten Durchlauf erneut verarbeitet." UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::INDEXING::FILESOURCEINDEXER::T1935191670"] = "Die Datei „{0}“ wurde während der Indexierung geändert. Die bereits indexierten Inhalte werden verworfen, und die Datei wird beim nächsten Durchlauf erneut verarbeitet."
-- The file '{0}' cannot be reached. Perhaps the drive it is on is not connected, or the file was moved or renamed. Nothing was removed from the index. If the file was moved or renamed, edit this data source to select its new location. Once the file is back, refresh this data source. When it is gone for good, remove this data source.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::INDEXING::FILESOURCEINDEXER::T2268993048"] = "Die Datei „{0}“ ist nicht erreichbar. Möglicherweise ist das Laufwerk, auf dem sie sich befindet, nicht verbunden, oder die Datei wurde verschoben oder umbenannt. Aus dem Index wurde nichts entfernt. Wenn die Datei verschoben oder umbenannt wurde, bearbeiten Sie diese Datenquelle und wählen Sie den neuen Speicherort der Datei aus. Sobald die Datei wieder verfügbar ist, aktualisieren Sie diese Datenquelle. Wenn sie dauerhaft nicht mehr vorhanden ist, entfernen Sie diese Datenquelle."
-- The folder '{0}' cannot be reached. Perhaps the drive it is on is not connected, or the folder was moved or renamed. Nothing was removed from the index. If the folder was moved or renamed, edit this data source to select its new location. Once the folder is back, refresh this data source.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::INDEXING::FILESOURCEINDEXER::T2692299965"] = "Der Ordner „{0}“ ist nicht erreichbar. Möglicherweise ist das Laufwerk, auf dem er sich befindet, nicht verbunden, oder der Ordner wurde verschoben oder umbenannt. Aus dem Index wurde nichts entfernt. Wenn der Ordner verschoben oder umbenannt wurde, bearbeiten Sie diese Datenquelle und wählen Sie den neuen Speicherort des Ordners aus. Sobald der Ordner wieder verfügbar ist, aktualisieren Sie diese Datenquelle."
-- The folder '{0}' currently appears empty. AI Studio kept the index of this data source, because the folder of a drive that is not connected can look empty. Connect the drive and refresh this data source. If you emptied the folder on purpose, remove this data source.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::INDEXING::FILESOURCEINDEXER::T2999277901"] = "Der Ordner „{0}“ scheint derzeit leer zu sein. AI Studio hat den Index dieser Datenquelle beibehalten, da ein Ordner auf einem nicht verbundenen Laufwerk leer erscheinen kann. Verbinden Sie das Laufwerk und aktualisieren Sie diese Datenquelle. Wenn Sie den Ordner absichtlich geleert haben, entfernen Sie diese Datenquelle."
-- The folder '{0}' could not be opened. Please check whether you are allowed to read it. -- The folder '{0}' could not be opened. Please check whether you are allowed to read it.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::INDEXING::FILESOURCEINDEXER::T3230000698"] = "Der Ordner „{0}“ konnte nicht geöffnet werden. Bitte prüfen Sie, ob Sie ihn lesen dürfen." UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::INDEXING::FILESOURCEINDEXER::T3230000698"] = "Der Ordner „{0}“ konnte nicht geöffnet werden. Bitte prüfen Sie, ob Sie ihn lesen dürfen."
-- The folder '{0}' cannot be reached. Perhaps the drive it is on is not connected, or the folder was moved or renamed. Nothing was removed from the index. Once the folder is back, refresh this data source.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::INDEXING::FILESOURCEINDEXER::T3722338131"] = "Der Ordner „{0}“ ist nicht erreichbar. Möglicherweise ist das Laufwerk, auf dem er sich befindet, nicht verbunden, oder der Ordner wurde verschoben oder umbenannt. Aus dem Index wurde nichts entfernt. Sobald der Ordner wieder verfügbar ist, aktualisieren Sie diese Datenquelle."
-- The file '{0}' could not be read. Please check whether you are allowed to read it. -- The file '{0}' could not be read. Please check whether you are allowed to read it.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::INDEXING::FILESOURCEINDEXER::T3924882233"] = "Die Datei „{0}“ konnte nicht gelesen werden. Bitte prüfen Sie, ob Sie berechtigt sind, sie zu lesen." UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::INDEXING::FILESOURCEINDEXER::T3924882233"] = "Die Datei „{0}“ konnte nicht gelesen werden. Bitte prüfen Sie, ob Sie berechtigt sind, sie zu lesen."
@@ -14577,6 +14790,9 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::VALIDATION::DATASOURCEVALIDATION::T2222598306"
-- Please select your security policy. -- Please select your security policy.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::VALIDATION::DATASOURCEVALIDATION::T2250909198"] = "Bitte wählen Sie Ihre Sicherheitsrichtlinie aus." UI_TEXT_CONTENT["AISTUDIO::TOOLS::VALIDATION::DATASOURCEVALIDATION::T2250909198"] = "Bitte wählen Sie Ihre Sicherheitsrichtlinie aus."
-- The use of this embedding provider has been prohibited by your organization. Please select a different embedding provider.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::VALIDATION::DATASOURCEVALIDATION::T2376506873"] = "Die Verwendung dieses Einbettungsanbieters wurde von Ihrer Organisation untersagt. Bitte wählen Sie einen anderen Einbettungsanbieter."
-- Please acknowledge that you are aware of the cloud transcription implications. -- Please acknowledge that you are aware of the cloud transcription implications.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::VALIDATION::DATASOURCEVALIDATION::T2818048408"] = "Bitte bestätigen Sie, dass Ihnen die Auswirkungen der Cloud-Transkription bewusst sind." UI_TEXT_CONTENT["AISTUDIO::TOOLS::VALIDATION::DATASOURCEVALIDATION::T2818048408"] = "Bitte bestätigen Sie, dass Ihnen die Auswirkungen der Cloud-Transkription bewusst sind."
@@ -14634,11 +14850,8 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::VALIDATION::DATASOURCEVALIDATION::T90753429"]
-- Please enter the secret necessary for authentication. -- Please enter the secret necessary for authentication.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::VALIDATION::DATASOURCEVALIDATION::T968385876"] = "Bitte geben Sie das für die Authentifizierung benötigte Geheimnis ein." UI_TEXT_CONTENT["AISTUDIO::TOOLS::VALIDATION::DATASOURCEVALIDATION::T968385876"] = "Bitte geben Sie das für die Authentifizierung benötigte Geheimnis ein."
-- Unsupported image format -- AI Studio cannot give {0} images to an AI model. Please convert the image to JPG or PNG.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::VALIDATION::FILEEXTENSIONVALIDATION::T1398282880"] = "Nicht unterstütztes Bildformat" UI_TEXT_CONTENT["AISTUDIO::TOOLS::VALIDATION::FILEEXTENSIONVALIDATION::T1578559061"] = "AI Studio kann keine Bilder im Format {0} an ein KI-Modell übergeben. Bitte konvertieren Sie das Bild in JPG oder PNG."
-- File has no extension
UI_TEXT_CONTENT["AISTUDIO::TOOLS::VALIDATION::FILEEXTENSIONVALIDATION::T1555980031"] = "Datei hat keine Erweiterung"
-- Audio files are not supported yet -- Audio files are not supported yet
UI_TEXT_CONTENT["AISTUDIO::TOOLS::VALIDATION::FILEEXTENSIONVALIDATION::T2919730669"] = "Audio-Dateien werden noch nicht unterstützt." UI_TEXT_CONTENT["AISTUDIO::TOOLS::VALIDATION::FILEEXTENSIONVALIDATION::T2919730669"] = "Audio-Dateien werden noch nicht unterstützt."
@@ -426,6 +426,9 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING
-- The selected folder does not exist. -- The selected folder does not exist.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T17705912"] = "The selected folder does not exist." UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T17705912"] = "The selected folder does not exist."
-- The image was no longer available while the model was asked about it.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T1805791865"] = "The image was no longer available while the model was asked about it."
-- Minimum pause between files -- Minimum pause between files
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T1829787634"] = "Minimum pause between files" UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T1829787634"] = "Minimum pause between files"
@@ -531,9 +534,15 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING
-- Was not able to write the result file: {0} -- Was not able to write the result file: {0}
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T2991295581"] = "Was not able to write the result file: {0}" UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T2991295581"] = "Was not able to write the result file: {0}"
-- Images in JPG, PNG, WebP, GIF, BMP, or TIFF format are given to the model as images, so you need a model that understands images. If the selected model cannot read images, remove the image patterns, such as *.jpg or *.png, before you start.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T3011193639"] = "Images in JPG, PNG, WebP, GIF, BMP, or TIFF format are given to the model as images, so you need a model that understands images. If the selected model cannot read images, remove the image patterns, such as *.jpg or *.png, before you start."
-- The batch run finished, but one file could not be processed. See the progress table and log for details. -- The batch run finished, but one file could not be processed. See the progress table and log for details.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T3201532790"] = "The batch run finished, but one file could not be processed. See the progress table and log for details." UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T3201532790"] = "The batch run finished, but one file could not be processed. See the progress table and log for details."
-- The selected model cannot read images, so the {0} images among the selected files will fail. All other files are processed as usual.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T324865195"] = "The selected model cannot read images, so the {0} images among the selected files will fail. All other files are processed as usual."
-- Maximum pause between files -- Maximum pause between files
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T3250003796"] = "Maximum pause between files" UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T3250003796"] = "Maximum pause between files"
@@ -552,6 +561,9 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING
-- Show Expert Settings -- Show Expert Settings
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T3361153305"] = "Show Expert Settings" UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T3361153305"] = "Show Expert Settings"
-- All selected files are images, but the selected model cannot read images. Select a model with image support.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T3365102967"] = "All selected files are images, but the selected model cannot read images. Select a model with image support."
-- Header of the result column (optional) -- Header of the result column (optional)
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T340994102"] = "Header of the result column (optional)" UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T340994102"] = "Header of the result column (optional)"
@@ -564,6 +576,9 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING
-- The file name of the CSV results table. We add the ending .csv when it is missing. When left empty, we use 'batch-results.csv'. -- The file name of the CSV results table. We add the ending .csv when it is missing. When left empty, we use 'batch-results.csv'.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T3439247329"] = "The file name of the CSV results table. We add the ending .csv when it is missing. When left empty, we use 'batch-results.csv'." UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T3439247329"] = "The file name of the CSV results table. We add the ending .csv when it is missing. When left empty, we use 'batch-results.csv'."
-- The selected model cannot read images. Select a model with image support to process this image.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T346986514"] = "The selected model cannot read images. Select a model with image support to process this image."
-- Document analysis policy -- Document analysis policy
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T3510564924"] = "Document analysis policy" UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T3510564924"] = "Document analysis policy"
@@ -678,6 +693,9 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING
-- 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. -- 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." 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."
-- The selected model cannot read images, so the image among the selected files will fail. All other files are processed as usual.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T9738678"] = "The selected model cannot read images, so the image among the selected files will fail. All other files are processed as usual."
-- Comma (,) -- Comma (,)
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::BATCHPROCESSINGCSVSEPARATOREXTENSIONS::T1676507543"] = "Comma (,)" UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::BATCHPROCESSINGCSVSEPARATOREXTENSIONS::T1676507543"] = "Comma (,)"
@@ -3627,6 +3645,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T1543974632"] = "The media
-- {0}, of which approx. {1} from tools -- {0}, of which approx. {1} from tools
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T1614399447"] = "{0}, of which approx. {1} from tools" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T1614399447"] = "{0}, of which approx. {1} from tools"
-- The use of this provider has been prohibited by your organization. Please select a different provider.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T1807387355"] = "The use of this provider has been prohibited by your organization. Please select a different provider."
-- Type your input here... -- Type your input here...
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T1849313532"] = "Type your input here..." UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T1849313532"] = "Type your input here..."
@@ -4122,9 +4143,6 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCEMANAGEMENT::T1084943026"] = "Yo
-- Automatic local data source refresh -- Automatic local data source refresh
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCEMANAGEMENT::T1208397349"] = "Automatic local data source refresh" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCEMANAGEMENT::T1208397349"] = "Automatic local data source refresh"
-- Edit Local Directory Data Source
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCEMANAGEMENT::T1215599168"] = "Edit Local Directory Data Source"
-- Refresh -- Refresh
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCEMANAGEMENT::T135637716"] = "Refresh" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCEMANAGEMENT::T135637716"] = "Refresh"
@@ -4152,12 +4170,6 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCEMANAGEMENT::T2067030738"] = "Ar
-- Local Directory Data Source Information -- Local Directory Data Source Information
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCEMANAGEMENT::T2146756020"] = "Local Directory Data Source Information" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCEMANAGEMENT::T2146756020"] = "Local Directory Data Source Information"
-- Edit ERI v1 Data Source
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCEMANAGEMENT::T221059217"] = "Edit ERI v1 Data Source"
-- Edit Local File Data Source
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCEMANAGEMENT::T2453292893"] = "Edit Local File Data Source"
-- Local data sources refresh when files change, mailboxes every 16 minutes. -- Local data sources refresh when files change, mailboxes every 16 minutes.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCEMANAGEMENT::T2511157475"] = "Local data sources refresh when files change, mailboxes every 16 minutes." UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCEMANAGEMENT::T2511157475"] = "Local data sources refresh when files change, mailboxes every 16 minutes."
@@ -4392,9 +4404,15 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DIRECTCHATLAUNCHERFORM::T1797236585"] = "
-- Workspace name (Optional) -- Workspace name (Optional)
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DIRECTCHATLAUNCHERFORM::T1873204484"] = "Workspace name (Optional)" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DIRECTCHATLAUNCHERFORM::T1873204484"] = "Workspace name (Optional)"
-- The chosen chat template preselects a provider of its own, and it wins over a selection made here as long as that provider can be used for chats.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DIRECTCHATLAUNCHERFORM::T1876510820"] = "The chosen chat template preselects a provider of its own, and it wins over a selection made here as long as that provider can be used for chats."
-- Use no profile -- Use no profile
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DIRECTCHATLAUNCHERFORM::T2205839602"] = "Use no profile" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DIRECTCHATLAUNCHERFORM::T2205839602"] = "Use no profile"
-- The chosen chat template keeps its chats in the workspace '{0}', so the tile opens its chat there instead. A workspace entered here wins over the one of the template.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DIRECTCHATLAUNCHERFORM::T2344103917"] = "The chosen chat template keeps its chats in the workspace '{0}', so the tile opens its chat there instead. A workspace entered here wins over the one of the template."
-- Existing workspace (Optional) -- Existing workspace (Optional)
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DIRECTCHATLAUNCHERFORM::T2364306588"] = "Existing workspace (Optional)" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DIRECTCHATLAUNCHERFORM::T2364306588"] = "Existing workspace (Optional)"
@@ -4416,6 +4434,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DIRECTCHATLAUNCHERFORM::T2901190527"] = "
-- Data sources (Optional) -- Data sources (Optional)
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DIRECTCHATLAUNCHERFORM::T3259309302"] = "Data sources (Optional)" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DIRECTCHATLAUNCHERFORM::T3259309302"] = "Data sources (Optional)"
-- The chosen chat template preselects a profile of its own, and it wins over a selection made here as long as that profile exists.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DIRECTCHATLAUNCHERFORM::T3419242912"] = "The chosen chat template preselects a profile of its own, and it wins over a selection made here as long as that profile exists."
-- Without a workspace, the tile opens a disappearing chat: it belongs to no workspace and is deleted according to your workspace maintenance settings. -- Without a workspace, the tile opens a disappearing chat: it belongs to no workspace and is deleted according to your workspace maintenance settings.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DIRECTCHATLAUNCHERFORM::T3611496116"] = "Without a workspace, the tile opens a disappearing chat: it belongs to no workspace and is deleted according to your workspace maintenance settings." UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DIRECTCHATLAUNCHERFORM::T3611496116"] = "Without a workspace, the tile opens a disappearing chat: it belongs to no workspace and is deleted according to your workspace maintenance settings."
@@ -4692,6 +4713,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::PROVIDERSELECTION::T2685487365"] = "Image
-- Speech input possible -- Speech input possible
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::PROVIDERSELECTION::T3005724142"] = "Speech input possible" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::PROVIDERSELECTION::T3005724142"] = "Speech input possible"
-- The use of all your LLM providers has been prohibited by your organization. Add a different provider in the app settings.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::PROVIDERSELECTION::T3506708164"] = "The use of all your LLM providers has been prohibited by your organization. Add a different provider in the app settings."
-- Uses reasoning (thinking) by default -- Uses reasoning (thinking) by default
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::PROVIDERSELECTION::T3891860124"] = "Uses reasoning (thinking) by default" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::PROVIDERSELECTION::T3891860124"] = "Uses reasoning (thinking) by default"
@@ -5262,6 +5286,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELCONFIDENCE::T45885
-- Not yet configured -- Not yet configured
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELCONFIDENCE::T48051324"] = "Not yet configured" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELCONFIDENCE::T48051324"] = "Not yet configured"
-- The use of this provider has been prohibited by your organization. Therefore, it is untrusted, and you cannot change its confidence level.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELCONFIDENCE::T489079668"] = "The use of this provider has been prohibited by your organization. Therefore, it is untrusted, and you cannot change its confidence level."
-- Do you want to always see how trustworthy your providers are? This way, you stay in control of which provider you send your data to. You can choose a common schema or configure the trust levels for each provider yourself. -- Do you want to always see how trustworthy your providers are? This way, you stay in control of which provider you send your data to. You can choose a common schema or configure the trust levels for each provider yourself.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELCONFIDENCE::T700839804"] = "Do you want to always see how trustworthy your providers are? This way, you stay in control of which provider you send your data to. You can choose a common schema or configure the trust levels for each provider yourself." UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELCONFIDENCE::T700839804"] = "Do you want to always see how trustworthy your providers are? This way, you stay in control of which provider you send your data to. You can choose a common schema or configure the trust levels for each provider yourself."
@@ -5280,6 +5307,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T13870
-- Delete -- Delete
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T1469573738"] = "Delete" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T1469573738"] = "Delete"
-- The use of this embedding provider has been prohibited by your organization.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T1509600117"] = "The use of this embedding provider has been prohibited by your organization."
-- Embed text -- Embed text
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T1644934561"] = "Embed text" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T1644934561"] = "Embed text"
@@ -5430,6 +5460,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T341592
-- Actions -- Actions
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T3865031940"] = "Actions" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T3865031940"] = "Actions"
-- The use of this provider has been prohibited by your organization.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T4229691938"] = "The use of this provider has been prohibited by your organization."
-- Delete LLM Provider -- Delete LLM Provider
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T4269256234"] = "Delete LLM Provider" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T4269256234"] = "Delete LLM Provider"
@@ -5493,6 +5526,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T16
-- Uses the provider-configured model -- Uses the provider-configured model
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T1760715963"] = "Uses the provider-configured model" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T1760715963"] = "Uses the provider-configured model"
-- The use of this transcription provider has been prohibited by your organization.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T2059509230"] = "The use of this transcription provider has been prohibited by your organization."
-- Add Transcription Provider -- Add Transcription Provider
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T2066315685"] = "Add Transcription Provider" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T2066315685"] = "Add Transcription Provider"
@@ -6180,12 +6216,21 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T1396308587"] = "The cha
-- The same goes for your data. A chat template may bring its own data source options, which includes leaving the choice of sources to the AI. Without them, those chats start with the data source options from your chat options. -- The same goes for your data. A chat template may bring its own data source options, which includes leaving the choice of sources to the AI. Without them, those chats start with the data source options from your chat options.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T1442266827"] = "The same goes for your data. A chat template may bring its own data source options, which includes leaving the choice of sources to the AI. Without them, those chats start with the data source options from your chat options." UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T1442266827"] = "The same goes for your data. A chat template may bring its own data source options, which includes leaving the choice of sources to the AI. Without them, those chats start with the data source options from your chat options."
-- Preselected provider
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T1469984996"] = "Preselected provider"
-- Please enter a name for the chat template. -- Please enter a name for the chat template.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T1548747185"] = "Please enter a name for the chat template." UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T1548747185"] = "Please enter a name for the chat template."
-- A chat template may decide which provider, and with it which model, a chat starts with. Without such a decision, those chats start with the provider they would start with anyway. The provider stays changeable in the chat, and it must meet the confidence level required for chats.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T171066641"] = "A chat template may decide which provider, and with it which model, a chat starts with. Without such a decision, those chats start with the provider they would start with anyway. The provider stays changeable in the chat, and it must meet the confidence level required for chats."
-- Yes, this template decides which data a chat starts with -- Yes, this template decides which data a chat starts with
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T17861006"] = "Yes, this template decides which data a chat starts with" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T17861006"] = "Yes, this template decides which data a chat starts with"
-- Preselected profile
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T178965494"] = "Preselected profile"
-- Enter the full path of a file on this computer, or remove the attachment. -- Enter the full path of a file on this computer, or remove the attachment.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T1798848159"] = "Enter the full path of a file on this computer, or remove the attachment." UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T1798848159"] = "Enter the full path of a file on this computer, or remove the attachment."
@@ -6195,12 +6240,21 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T1837026610"] = "Load pr
-- Update -- Update
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T1847791252"] = "Update" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T1847791252"] = "Update"
-- Yes, this template decides which profile a chat starts with
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T1865005833"] = "Yes, this template decides which profile a chat starts with"
-- The chat template name must not exceed 40 characters. -- The chat template name must not exceed 40 characters.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T204496403"] = "The chat template name must not exceed 40 characters." UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T204496403"] = "The chat template name must not exceed 40 characters."
-- Profile Usage -- Profile Usage
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2147062613"] = "Profile Usage" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2147062613"] = "Profile Usage"
-- Use no profile
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2205839602"] = "Use no profile"
-- Yes, chats with this template go into a workspace
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2205963533"] = "Yes, chats with this template go into a workspace"
-- Relink attachment: {0} -- Relink attachment: {0}
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2234793579"] = "Relink attachment: {0}" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2234793579"] = "Relink attachment: {0}"
@@ -6210,6 +6264,12 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2292424657"] = "Add mes
-- File Attachments -- File Attachments
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2294745309"] = "File Attachments" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2294745309"] = "File Attachments"
-- No, chats go where they would go anyway
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2355334446"] = "No, chats go where they would go anyway"
-- Existing workspace (Optional)
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2364306588"] = "Existing workspace (Optional)"
-- Role -- Role
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2418769465"] = "Role" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2418769465"] = "Role"
@@ -6222,6 +6282,12 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2499909372"] = "Tools"
-- What predefined user input do you want to use? -- What predefined user input do you want to use?
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2501284417"] = "What predefined user input do you want to use?" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2501284417"] = "What predefined user input do you want to use?"
-- Please enter the workspace for the chats of this template, or switch the preselection off.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2504276031"] = "Please enter the workspace for the chats of this template, or switch the preselection off."
-- Does this chat template preselect a provider?
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2541393632"] = "Does this chat template preselect a provider?"
-- Tell the AI your system prompt. -- Tell the AI your system prompt.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2552381261"] = "Tell the AI your system prompt." UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2552381261"] = "Tell the AI your system prompt."
@@ -6231,6 +6297,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2579080722"] = "No, pro
-- You might want to predefine a first message that will be copied into the user prompt, when you use this chat template. This message could for example be a blueprint for a structured message that this chat template is defined to work with. -- You might want to predefine a first message that will be copied into the user prompt, when you use this chat template. This message could for example be a blueprint for a structured message that this chat template is defined to work with.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2579208700"] = "You might want to predefine a first message that will be copied into the user prompt, when you use this chat template. This message could for example be a blueprint for a structured message that this chat template is defined to work with." UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2579208700"] = "You might want to predefine a first message that will be copied into the user prompt, when you use this chat template. This message could for example be a blueprint for a structured message that this chat template is defined to work with."
-- Does this chat template preselect a profile?
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2602340928"] = "Does this chat template preselect a profile?"
-- Predefined User Input -- Predefined User Input
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2607897066"] = "Predefined User Input" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2607897066"] = "Predefined User Input"
@@ -6246,9 +6315,15 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2719480499"] = "The mes
-- Chat Template Name -- Chat Template Name
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T275026390"] = "Chat Template Name" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T275026390"] = "Chat Template Name"
-- Workspaces are switched off in your workspace settings. While they are off, chats with this template belong to no workspace, and they do not move into a workspace when you switch workspaces on again. You can change this choice once workspaces are on.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2828180414"] = "Workspaces are switched off in your workspace settings. While they are off, chats with this template belong to no workspace, and they do not move into a workspace when you switch workspaces on again. You can change this choice once workspaces are on."
-- Please relink the attachment '{0}' to a file on this computer or remove it before you save. -- Please relink the attachment '{0}' to a file on this computer or remove it before you save.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2884684079"] = "Please relink the attachment '{0}' to a file on this computer or remove it before you save." UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2884684079"] = "Please relink the attachment '{0}' to a file on this computer or remove it before you save."
-- Workspace name
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T295876489"] = "Workspace name"
-- Commit Changes -- Commit Changes
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2996302517"] = "Commit Changes" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2996302517"] = "Commit Changes"
@@ -6264,21 +6339,42 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T3108503534"] = "You can
-- Are you unsure which system prompt to use? You might start with the default system prompt that AI Studio uses for all chats. -- Are you unsure which system prompt to use? You might start with the default system prompt that AI Studio uses for all chats.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T3127437308"] = "Are you unsure which system prompt to use? You might start with the default system prompt that AI Studio uses for all chats." UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T3127437308"] = "Are you unsure which system prompt to use? You might start with the default system prompt that AI Studio uses for all chats."
-- A chat template may also decide which profile a chat starts with, including starting without one. Without such a decision, those chats start with the profile they would start with anyway. The profile stays changeable in the chat.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T313529011"] = "A chat template may also decide which profile a chat starts with, including starting without one. Without such a decision, those chats start with the profile they would start with anyway. The profile stays changeable in the chat."
-- The profile this chat template preselects does not exist on this computer. Chats started with it keep their usual profile.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T3170335831"] = "The profile this chat template preselects does not exist on this computer. Chats started with it keep their usual profile."
-- Using some chat templates in tandem with profiles might cause issues. Therefore, you might prohibit the usage of profiles here. -- Using some chat templates in tandem with profiles might cause issues. Therefore, you might prohibit the usage of profiles here.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T3227981830"] = "Using some chat templates in tandem with profiles might cause issues. Therefore, you might prohibit the usage of profiles here." UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T3227981830"] = "Using some chat templates in tandem with profiles might cause issues. Therefore, you might prohibit the usage of profiles here."
-- Choose an existing workspace or enter a name that should be created when a chat with this template starts.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T3256439227"] = "Choose an existing workspace or enter a name that should be created when a chat with this template starts."
-- No, chats keep the data source options from your chat options -- No, chats keep the data source options from your chat options
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T3268470871"] = "No, chats keep the data source options from your chat options" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T3268470871"] = "No, chats keep the data source options from your chat options"
-- A chat template may decide which tools a chat starts with. Without such a decision, those chats start with the tools you chose as your default in the chat options. Deciding and then picking nothing is a different statement: such chats start with no tool at all, no matter what your default says. -- A chat template may decide which tools a chat starts with. Without such a decision, those chats start with the tools you chose as your default in the chat options. Deciding and then picking nothing is a different statement: such chats start with no tool at all, no matter what your default says.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T3329415439"] = "A chat template may decide which tools a chat starts with. Without such a decision, those chats start with the tools you chose as your default in the chat options. Deciding and then picking nothing is a different statement: such chats start with no tool at all, no matter what your default says." UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T3329415439"] = "A chat template may decide which tools a chat starts with. Without such a decision, those chats start with the tools you chose as your default in the chat options. Deciding and then picking nothing is a different statement: such chats start with no tool at all, no matter what your default says."
-- A chat template may keep its chats in a workspace of its own, which is created when it does not exist yet. This applies whenever a chat would otherwise be a disappearing chat, for example, when you start a new chat outside the workspaces or pick this template in a new chat. A chat you start in a workspace on purpose, for example, from the workspace list, stays in that workspace.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T3352941260"] = "A chat template may keep its chats in a workspace of its own, which is created when it does not exist yet. This applies whenever a chat would otherwise be a disappearing chat, for example, when you start a new chat outside the workspaces or pick this template in a new chat. A chat you start in a workspace on purpose, for example, from the workspace list, stays in that workspace."
-- Add a message -- Add a message
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T3372872324"] = "Add a message" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T3372872324"] = "Add a message"
-- Yes, this template decides which provider a chat starts with
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T3422337353"] = "Yes, this template decides which provider a chat starts with"
-- Close -- Close
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T3448155331"] = "Close" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T3448155331"] = "Close"
-- No, chats keep their usual profile
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T3465925873"] = "No, chats keep their usual profile"
-- The provider this chat template preselects does not exist on this computer or does not meet the confidence level required for chats. Chats started with it keep their usual provider.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T349281189"] = "The provider this chat template preselects does not exist on this computer or does not meet the confidence level required for chats. Chats started with it keep their usual provider."
-- Unsupported content type -- Unsupported content type
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T3570316759"] = "Unsupported content type" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T3570316759"] = "Unsupported content type"
@@ -6315,12 +6411,18 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T4051106111"] = "Use the
-- Tell the AI your predefined user input. -- Tell the AI your predefined user input.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T4052406705"] = "Tell the AI your predefined user input." UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T4052406705"] = "Tell the AI your predefined user input."
-- Does this chat template keep its chats in a workspace?
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T4057825866"] = "Does this chat template keep its chats in a workspace?"
-- Some attachments of this chat template are not on this computer. Please relink or remove them before you add the template. -- Some attachments of this chat template are not on this computer. Please relink or remove them before you add the template.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T4194591607"] = "Some attachments of this chat template are not on this computer. Please relink or remove them before you add the template." UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T4194591607"] = "Some attachments of this chat template are not on this computer. Please relink or remove them before you add the template."
-- Create your custom chat template to tailor the LLM's behavior for specific tasks or domains. Define a custom system prompt and provide an example conversation to design an AI experience perfectly suited to your requirements. -- Create your custom chat template to tailor the LLM's behavior for specific tasks or domains. Define a custom system prompt and provide an example conversation to design an AI experience perfectly suited to your requirements.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T4199560726"] = "Create your custom chat template to tailor the LLM's behavior for specific tasks or domains. Define a custom system prompt and provide an example conversation to design an AI experience perfectly suited to your requirements." UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T4199560726"] = "Create your custom chat template to tailor the LLM's behavior for specific tasks or domains. Define a custom system prompt and provide an example conversation to design an AI experience perfectly suited to your requirements."
-- Please choose the provider this chat template preselects, or switch the preselection off.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T442309864"] = "Please choose the provider this chat template preselects, or switch the preselection off."
-- Enter a message -- Enter a message
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T446374405"] = "Enter a message" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T446374405"] = "Enter a message"
@@ -6333,15 +6435,24 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T628396066"] = "System P
-- A chat started with this template begins with these data sources and options. All of it stays changeable in the chat itself. -- A chat started with this template begins with these data sources and options. All of it stays changeable in the chat itself.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T650711085"] = "A chat started with this template begins with these data sources and options. All of it stays changeable in the chat itself." UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T650711085"] = "A chat started with this template begins with these data sources and options. All of it stays changeable in the chat itself."
-- Workspace
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T658612054"] = "Workspace"
-- Remove this attachment -- Remove this attachment
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T70791272"] = "Remove this attachment" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T70791272"] = "Remove this attachment"
-- The selection stays changeable in the chat, and every tool still has to meet the confidence requirements of the provider in use. -- The selection stays changeable in the chat, and every tool still has to meet the confidence requirements of the provider in use.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T722788866"] = "The selection stays changeable in the chat, and every tool still has to meet the confidence requirements of the provider in use." UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T722788866"] = "The selection stays changeable in the chat, and every tool still has to meet the confidence requirements of the provider in use."
-- No, chats keep their usual provider
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T787085105"] = "No, chats keep their usual provider"
-- Allow the use of profiles together with this chat template? -- Allow the use of profiles together with this chat template?
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T823785464"] = "Allow the use of profiles together with this chat template?" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T823785464"] = "Allow the use of profiles together with this chat template?"
-- Provider
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T900237532"] = "Provider"
-- Cancel -- Cancel
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T900713019"] = "Cancel" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T900713019"] = "Cancel"
@@ -6807,9 +6918,6 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T2460883298"
-- Add -- Add
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T2646845972"] = "Add" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T2646845972"] = "Add"
-- The documents of this data source are already prepared, so its folder cannot be changed. Another folder holds other documents, which makes it another data source: please add one for it. The embedding provider below can be changed.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T2686150329"] = "The documents of this data source are already prepared, so its folder cannot be changed. Another folder holds other documents, which makes it another data source: please add one for it. The embedding provider below can be changed."
-- The embedding you selected runs locally or in your organization. Your data is not sent to the cloud. -- The embedding you selected runs locally or in your organization. Your data is not sent to the cloud.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T2814869210"] = "The embedding you selected runs locally or in your organization. Your data is not sent to the cloud." UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T2814869210"] = "The embedding you selected runs locally or in your organization. Your data is not sent to the cloud."
@@ -7068,9 +7176,6 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T787118522"] = "T
-- Data Source Name -- Data Source Name
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T813773421"] = "Data Source Name" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T813773421"] = "Data Source Name"
-- The documents of this data source are already prepared, so its file cannot be changed. Another file holds other content, which makes it another data source: please add one for it. The embedding provider below can be changed.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T890383764"] = "The documents of this data source are already prepared, so its file cannot be changed. Another file holds other content, which makes it another data source: please add one for it. The embedding provider below can be changed."
-- Cancel -- Cancel
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T900713019"] = "Cancel" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T900713019"] = "Cancel"
@@ -7548,6 +7653,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T1727440780"] = "Ho
-- Load -- Load
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T1756340745"] = "Load" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T1756340745"] = "Load"
-- The use of this provider has been prohibited by your organization. Please select a different provider.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T1807387355"] = "The use of this provider has been prohibited by your organization. Please select a different provider."
-- Update -- Update
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T1847791252"] = "Update" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T1847791252"] = "Update"
@@ -8073,6 +8181,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T1727440780"] = "Hostname"
-- Always on -- Always on
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T1761671861"] = "Always on" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T1761671861"] = "Always on"
-- The use of this provider has been prohibited by your organization. Please select a different provider.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T1807387355"] = "The use of this provider has been prohibited by your organization. Please select a different provider."
-- Reset -- Reset
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T180921696"] = "Reset" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T180921696"] = "Reset"
@@ -8580,6 +8691,33 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGASSISTANTBIAS::T6790
-- When enabled, you can preselect options. This is might be useful when you prefer a specific language or LLM model. -- When enabled, you can preselect options. This is might be useful when you prefer a specific language or LLM model.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGASSISTANTBIAS::T711745239"] = "When enabled, you can preselect options. This is might be useful when you prefer a specific language or LLM model." UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGASSISTANTBIAS::T711745239"] = "When enabled, you can preselect options. This is might be useful when you prefer a specific language or LLM model."
-- When enabled, the Assistant Builder starts with the defaults configured below. When disabled, it uses the app-wide default provider and preselects no output language.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGASSISTANTBUILDER::T1496952909"] = "When enabled, the Assistant Builder starts with the defaults configured below. When disabled, it uses the app-wide default provider and preselects no output language."
-- Preselect Assistant Builder options?
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGASSISTANTBUILDER::T2127606148"] = "Preselect Assistant Builder options?"
-- Default custom output language
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGASSISTANTBUILDER::T3360584376"] = "Default custom output language"
-- Close
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGASSISTANTBUILDER::T3448155331"] = "Close"
-- Default output language
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGASSISTANTBUILDER::T3652922567"] = "Default output language"
-- Assistant: Assistant Builder defaults
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGASSISTANTBUILDER::T3668274570"] = "Assistant: Assistant Builder defaults"
-- Assistant Builder options are preselected
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGASSISTANTBUILDER::T3962364100"] = "Assistant Builder options are preselected"
-- No Assistant Builder options are preselected
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGASSISTANTBUILDER::T4104323209"] = "No Assistant Builder options are preselected"
-- The language in which the assistants you build answer.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGASSISTANTBUILDER::T42126731"] = "The language in which the assistants you build answer."
-- Default minimum pause between files -- Default minimum pause between files
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T1008440099"] = "Default minimum pause between files" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T1008440099"] = "Default minimum pause between files"
@@ -8625,6 +8763,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T22
-- Hidden files and folders are included -- Hidden files and folders are included
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T2288334048"] = "Hidden files and folders are included" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T2288334048"] = "Hidden files and folders are included"
-- Separate multiple file patterns with a semicolon, e.g., *.pdf;*.docx. The standard patterns include all supported audio and video formats as well as images in JPG, PNG, WebP, GIF, BMP, and TIFF format.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T232588799"] = "Separate multiple file patterns with a semicolon, e.g., *.pdf;*.docx. The standard patterns include all supported audio and video formats as well as images in JPG, PNG, WebP, GIF, BMP, and TIFF format."
-- The lower end of the random pause interval. AI Studio never allows less than 6 seconds. -- 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." UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T237774509"] = "The lower end of the random pause interval. AI Studio never allows less than 6 seconds."
@@ -8637,9 +8778,6 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T24
-- When enabled, new batch runs start with the defaults configured below. -- 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." UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T2592677194"] = "When enabled, new batch runs start with the defaults configured below."
-- Separate multiple file patterns with a semicolon, e.g., *.pdf;*.docx. The standard patterns include all supported audio and video formats.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T2594325620"] = "Separate multiple file patterns with a semicolon, e.g., *.pdf;*.docx. The standard patterns include all supported audio and video formats."
-- Subfolders are included -- Subfolders are included
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T2607092632"] = "Subfolders are included" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T2607092632"] = "Subfolders are included"
@@ -9825,6 +9963,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T1727440780"] =
-- Load -- Load
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T1756340745"] = "Load" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T1756340745"] = "Load"
-- The use of this provider has been prohibited by your organization. Please select a different provider.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T1807387355"] = "The use of this provider has been prohibited by your organization. Please select a different provider."
-- Update -- Update
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T1847791252"] = "Update" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T1847791252"] = "Update"
@@ -10374,6 +10515,9 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::EMBEDDINGS::T819555436"] = "Failed mails: {0}"
-- Skipped -- Skipped
UI_TEXT_CONTENT["AISTUDIO::PAGES::EMBEDDINGS::T852993367"] = "Skipped" UI_TEXT_CONTENT["AISTUDIO::PAGES::EMBEDDINGS::T852993367"] = "Skipped"
-- Edit this data source
UI_TEXT_CONTENT["AISTUDIO::PAGES::EMBEDDINGS::T878700886"] = "Edit this data source"
-- Open the transcription settings -- Open the transcription settings
UI_TEXT_CONTENT["AISTUDIO::PAGES::EMBEDDINGS::T905182399"] = "Open the transcription settings" UI_TEXT_CONTENT["AISTUDIO::PAGES::EMBEDDINGS::T905182399"] = "Open the transcription settings"
@@ -11865,6 +12009,9 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::AIJOBS::AIJOBSERVICE::T1578005752"] = "The sel
-- The selected provider is not allowed for this chat. -- The selected provider is not allowed for this chat.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::AIJOBS::AIJOBSERVICE::T174545104"] = "The selected provider is not allowed for this chat." UI_TEXT_CONTENT["AISTUDIO::TOOLS::AIJOBS::AIJOBSERVICE::T174545104"] = "The selected provider is not allowed for this chat."
-- The use of this provider has been prohibited by your organization. Please select a different provider.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::AIJOBS::AIJOBSERVICE::T1807387355"] = "The use of this provider has been prohibited by your organization. Please select a different provider."
-- The AI job failed. The message is: '{0}' -- The AI job failed. The message is: '{0}'
UI_TEXT_CONTENT["AISTUDIO::TOOLS::AIJOBS::AIJOBSERVICE::T237448388"] = "The AI job failed. The message is: '{0}'" UI_TEXT_CONTENT["AISTUDIO::TOOLS::AIJOBS::AIJOBSERVICE::T237448388"] = "The AI job failed. The message is: '{0}'"
@@ -12210,18 +12357,39 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATABASES::VECTORSTORE::QDRANTEDGECLIENTIMPLEM
-- Qdrant Edge is not available. -- Qdrant Edge is not available.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATABASES::VECTORSTORE::QDRANTEDGECLIENTIMPLEMENTATION::T744445696"] = "Qdrant Edge is not available." UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATABASES::VECTORSTORE::QDRANTEDGECLIENTIMPLEMENTATION::T744445696"] = "Qdrant Edge is not available."
-- Edit Local Directory Data Source
UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATASOURCEEDITING::T1215599168"] = "Edit Local Directory Data Source"
-- Edit ERI v1 Data Source
UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATASOURCEEDITING::T221059217"] = "Edit ERI v1 Data Source"
-- Edit Local File Data Source
UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATASOURCEEDITING::T2453292893"] = "Edit Local File Data Source"
-- Every other file goes to your embedding provider once more, which takes a while with many files. Files missing at the new location are removed from the index, while your files on disk stay as they are.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATASOURCEREINDEXWARNING::T1555760974"] = "Every other file goes to your embedding provider once more, which takes a while with many files. Files missing at the new location are removed from the index, while your files on disk stay as they are."
-- AI Studio keeps what it has already prepared for every file it finds unchanged at the new location, that is, of the same type and with the same size and modification time. In a folder, such a file also has to keep its name and the subfolder it is in.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATASOURCEREINDEXWARNING::T2205693550"] = "AI Studio keeps what it has already prepared for every file it finds unchanged at the new location, that is, of the same type and with the same size and modification time. In a folder, such a file also has to keep its name and the subfolder it is in."
-- They keep answering keyword searches, but searching them by meaning stops working, and no further documents can be prepared for them. The ones which are already prepared stay tied to this provider as well, so you cannot simply move them to another one. -- They keep answering keyword searches, but searching them by meaning stops working, and no further documents can be prepared for them. The ones which are already prepared stay tied to this provider as well, so you cannot simply move them to another one.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATASOURCEREINDEXWARNING::T2343773457"] = "They keep answering keyword searches, but searching them by meaning stops working, and no further documents can be prepared for them. The ones which are already prepared stay tied to this provider as well, so you cannot simply move them to another one." UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATASOURCEREINDEXWARNING::T2343773457"] = "They keep answering keyword searches, but searching them by meaning stops working, and no further documents can be prepared for them. The ones which are already prepared stay tied to this provider as well, so you cannot simply move them to another one."
-- and {0} more. -- and {0} more.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATASOURCEREINDEXWARNING::T2519847121"] = "and {0} more." UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATASOURCEREINDEXWARNING::T2519847121"] = "and {0} more."
-- Your embedding provider runs in the cloud, so preparing files again costs money.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATASOURCEREINDEXWARNING::T2635623258"] = "Your embedding provider runs in the cloud, so preparing files again costs money."
-- This change makes the prepared documents of the following data sources unusable ({0}): -- This change makes the prepared documents of the following data sources unusable ({0}):
UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATASOURCEREINDEXWARNING::T3337378891"] = "This change makes the prepared documents of the following data sources unusable ({0}):" UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATASOURCEREINDEXWARNING::T3337378891"] = "This change makes the prepared documents of the following data sources unusable ({0}):"
-- Do you want to apply this change anyway? -- Do you want to apply this change anyway?
UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATASOURCEREINDEXWARNING::T3419411838"] = "Do you want to apply this change anyway?" UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATASOURCEREINDEXWARNING::T3419411838"] = "Do you want to apply this change anyway?"
-- New Location for This Data Source
UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATASOURCEREINDEXWARNING::T3462449568"] = "New Location for This Data Source"
-- Documents Will Be Prepared Again -- Documents Will Be Prepared Again
UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATASOURCEREINDEXWARNING::T737291513"] = "Documents Will Be Prepared Again" UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATASOURCEREINDEXWARNING::T737291513"] = "Documents Will Be Prepared Again"
@@ -12969,6 +13137,9 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::PLUGINASSISTANTSECUR
-- The token or password of the data source cannot be decrypted on this computer. Such data sources can only be imported where the encryption secret of your organization is configured. -- The token or password of the data source cannot be decrypted on this computer. Such data sources can only be imported where the encryption secret of your organization is configured.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETIMPORT::T186946778"] = "The token or password of the data source cannot be decrypted on this computer. Such data sources can only be imported where the encryption secret of your organization is configured." UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETIMPORT::T186946778"] = "The token or password of the data source cannot be decrypted on this computer. Such data sources can only be imported where the encryption secret of your organization is configured."
-- The chat template preselects the provider '{0}', which does not meet the confidence level required for chats. Chats started with it use your usual provider instead.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETIMPORT::T190892810"] = "The chat template preselects the provider '{0}', which does not meet the confidence level required for chats. Chats started with it use your usual provider instead."
-- Data sources which sign in with Kerberos cannot be imported yet. Please add them yourself. -- Data sources which sign in with Kerberos cannot be imported yet. Please add them yourself.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETIMPORT::T206419407"] = "Data sources which sign in with Kerberos cannot be imported yet. Please add them yourself." UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETIMPORT::T206419407"] = "Data sources which sign in with Kerberos cannot be imported yet. Please add them yourself."
@@ -12981,6 +13152,12 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETIMPORT::T260
-- The chat template preselects tools which are not available on this computer: {0}. -- The chat template preselects tools which are not available on this computer: {0}.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETIMPORT::T2693686582"] = "The chat template preselects tools which are not available on this computer: {0}." UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETIMPORT::T2693686582"] = "The chat template preselects tools which are not available on this computer: {0}."
-- The chat template preselects a profile which does not exist on this computer: {0}. Chats started with it use your usual profile instead.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETIMPORT::T2735390326"] = "The chat template preselects a profile which does not exist on this computer: {0}. Chats started with it use your usual profile instead."
-- The chat template preselects a provider which does not exist on this computer: {0}. Chats started with it use your usual provider instead.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETIMPORT::T3321789654"] = "The chat template preselects a provider which does not exist on this computer: {0}. Chats started with it use your usual provider instead."
-- The API key in the pasted configuration cannot be decrypted on this computer. Please enter your API key before you save the provider. -- The API key in the pasted configuration cannot be decrypted on this computer. Please enter your API key before you save the provider.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETIMPORT::T3589070261"] = "The API key in the pasted configuration cannot be decrypted on this computer. Please enter your API key before you save the provider." UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETIMPORT::T3589070261"] = "The API key in the pasted configuration cannot be decrypted on this computer. Please enter your API key before you save the provider."
@@ -13002,6 +13179,9 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETIMPORT::T600
-- The pasted configuration contains an API key which is not encrypted, so it was left out. Please enter your API key before you save the provider. -- The pasted configuration contains an API key which is not encrypted, so it was left out. Please enter your API key before you save the provider.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETIMPORT::T639320638"] = "The pasted configuration contains an API key which is not encrypted, so it was left out. Please enter your API key before you save the provider." UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETIMPORT::T639320638"] = "The pasted configuration contains an API key which is not encrypted, so it was left out. Please enter your API key before you save the provider."
-- The chat template preselects a profile but does not allow profiles. Chats started with it ignore that profile.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETIMPORT::T769062029"] = "The chat template preselects a profile but does not allow profiles. Chats started with it ignore that profile."
-- Expected a quoted string at character {0}. -- Expected a quoted string at character {0}.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETPARSER::T1235695745"] = "Expected a quoted string at character {0}." UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETPARSER::T1235695745"] = "Expected a quoted string at character {0}."
@@ -13404,9 +13584,36 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPES::T378481461"] = "Source like p
-- Document -- Document
UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPES::T4165204724"] = "Document" UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPES::T4165204724"] = "Document"
-- Image for AI models
UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPES::T791951937"] = "Image for AI models"
-- Plugin archive -- Plugin archive
UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPES::T927001356"] = "Plugin archive" UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPES::T927001356"] = "Plugin archive"
-- AI Studio was not able to prepare the image '{0}' for the AI, so it was not sent.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::MODELIMAGEERRORCODEEXTENSIONS::T1345506748"] = "AI Studio was not able to prepare the image '{0}' for the AI, so it was not sent."
-- The image '{0}' could not be made small enough for the selected provider, so it was not sent.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::MODELIMAGEERRORCODEEXTENSIONS::T1463083567"] = "The image '{0}' could not be made small enough for the selected provider, so it was not sent."
-- The image '{0}' could not be opened and was not sent. When the image is stored on a network drive, the drive might be unavailable.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::MODELIMAGEERRORCODEEXTENSIONS::T2022724454"] = "The image '{0}' could not be opened and was not sent. When the image is stored on a network drive, the drive might be unavailable."
-- The image '{0}' does not exist anymore and was not sent.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::MODELIMAGEERRORCODEEXTENSIONS::T2330229240"] = "The image '{0}' does not exist anymore and was not sent."
-- The image '{0}' is not what its file extension claims, so it was not sent. Please correct its file extension.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::MODELIMAGEERRORCODEEXTENSIONS::T3457568536"] = "The image '{0}' is not what its file extension claims, so it was not sent. Please correct its file extension."
-- The image '{0}' could not be read and was not sent. It might be damaged or transferred incompletely.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::MODELIMAGEERRORCODEEXTENSIONS::T4029166607"] = "The image '{0}' could not be read and was not sent. It might be damaged or transferred incompletely."
-- The image '{0}' has too many pixels for AI Studio to prepare it, so it was not sent. Please scale the image down first.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::MODELIMAGEERRORCODEEXTENSIONS::T567941882"] = "The image '{0}' has too many pixels for AI Studio to prepare it, so it was not sent. Please scale the image down first."
-- The image '{0}' uses a format which AI Studio cannot give to an AI model, so it was not sent. Please convert the image to JPG or PNG.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::MODELIMAGEERRORCODEEXTENSIONS::T568830141"] = "The image '{0}' uses a format which AI Studio cannot give to an AI model, so it was not sent. Please convert the image to JPG or PNG."
-- Attempt to override instructions -- Attempt to override instructions
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SECURITY::PROMPTINJECTIONFINDINGCATEGORYEXTENSIONS::T161976090"] = "Attempt to override instructions" UI_TEXT_CONTENT["AISTUDIO::TOOLS::SECURITY::PROMPTINJECTIONFINDINGCATEGORYEXTENSIONS::T161976090"] = "Attempt to override instructions"
@@ -13650,6 +13857,9 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::DATASOURCEEMBEDDINGSERVICE::T1738200
-- The index of the data source '{0}' is missing. The data source stays out of your chats until its index was built anew. Use the repair action to start that. -- The index of the data source '{0}' is missing. The data source stays out of your chats until its index was built anew. Use the repair action to start that.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::DATASOURCEEMBEDDINGSERVICE::T204783307"] = "The index of the data source '{0}' is missing. The data source stays out of your chats until its index was built anew. Use the repair action to start that." UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::DATASOURCEEMBEDDINGSERVICE::T204783307"] = "The index of the data source '{0}' is missing. The data source stays out of your chats until its index was built anew. Use the repair action to start that."
-- The use of the embedding provider of this data source has been prohibited by your organization. Please select a different embedding provider in the settings of the data source.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::DATASOURCEEMBEDDINGSERVICE::T2092244746"] = "The use of the embedding provider of this data source has been prohibited by your organization. Please select a different embedding provider in the settings of the data source."
-- The selected embedding provider is not allowed to index this data source. The data source asks for the confidence level '{0}', while the embedding provider has '{1}'. -- The selected embedding provider is not allowed to index this data source. The data source asks for the confidence level '{0}', while the embedding provider has '{1}'.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::DATASOURCEEMBEDDINGSERVICE::T2186533187"] = "The selected embedding provider is not allowed to index this data source. The data source asks for the confidence level '{0}', while the embedding provider has '{1}'." UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::DATASOURCEEMBEDDINGSERVICE::T2186533187"] = "The selected embedding provider is not allowed to index this data source. The data source asks for the confidence level '{0}', while the embedding provider has '{1}'."
@@ -13761,18 +13971,21 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::INDEXING::DOCUMENTRUNPROGRESS::T8042
-- The folder '{0}' could not be opened. Please check whether you are allowed to read it. Nothing was removed from the index. -- The folder '{0}' could not be opened. Please check whether you are allowed to read it. Nothing was removed from the index.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::INDEXING::FILESOURCEINDEXER::T1214987337"] = "The folder '{0}' could not be opened. Please check whether you are allowed to read it. Nothing was removed from the index." UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::INDEXING::FILESOURCEINDEXER::T1214987337"] = "The folder '{0}' could not be opened. Please check whether you are allowed to read it. Nothing was removed from the index."
-- The file '{0}' cannot be reached. Perhaps the drive it is on is not connected, or the file was moved or renamed. Nothing was removed from the index. Once the file is back, refresh this data source. When it is gone for good, remove this data source.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::INDEXING::FILESOURCEINDEXER::T1393648320"] = "The file '{0}' cannot be reached. Perhaps the drive it is on is not connected, or the file was moved or renamed. Nothing was removed from the index. Once the file is back, refresh this data source. When it is gone for good, remove this data source."
-- The file '{0}' changed while it was being indexed. What was indexed of it is discarded, and the file is tried again during the next run. -- The file '{0}' changed while it was being indexed. What was indexed of it is discarded, and the file is tried again during the next run.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::INDEXING::FILESOURCEINDEXER::T1935191670"] = "The file '{0}' changed while it was being indexed. What was indexed of it is discarded, and the file is tried again during the next run." UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::INDEXING::FILESOURCEINDEXER::T1935191670"] = "The file '{0}' changed while it was being indexed. What was indexed of it is discarded, and the file is tried again during the next run."
-- The file '{0}' cannot be reached. Perhaps the drive it is on is not connected, or the file was moved or renamed. Nothing was removed from the index. If the file was moved or renamed, edit this data source to select its new location. Once the file is back, refresh this data source. When it is gone for good, remove this data source.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::INDEXING::FILESOURCEINDEXER::T2268993048"] = "The file '{0}' cannot be reached. Perhaps the drive it is on is not connected, or the file was moved or renamed. Nothing was removed from the index. If the file was moved or renamed, edit this data source to select its new location. Once the file is back, refresh this data source. When it is gone for good, remove this data source."
-- The folder '{0}' cannot be reached. Perhaps the drive it is on is not connected, or the folder was moved or renamed. Nothing was removed from the index. If the folder was moved or renamed, edit this data source to select its new location. Once the folder is back, refresh this data source.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::INDEXING::FILESOURCEINDEXER::T2692299965"] = "The folder '{0}' cannot be reached. Perhaps the drive it is on is not connected, or the folder was moved or renamed. Nothing was removed from the index. If the folder was moved or renamed, edit this data source to select its new location. Once the folder is back, refresh this data source."
-- The folder '{0}' currently appears empty. AI Studio kept the index of this data source, because the folder of a drive that is not connected can look empty. Connect the drive and refresh this data source. If you emptied the folder on purpose, remove this data source.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::INDEXING::FILESOURCEINDEXER::T2999277901"] = "The folder '{0}' currently appears empty. AI Studio kept the index of this data source, because the folder of a drive that is not connected can look empty. Connect the drive and refresh this data source. If you emptied the folder on purpose, remove this data source."
-- The folder '{0}' could not be opened. Please check whether you are allowed to read it. -- The folder '{0}' could not be opened. Please check whether you are allowed to read it.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::INDEXING::FILESOURCEINDEXER::T3230000698"] = "The folder '{0}' could not be opened. Please check whether you are allowed to read it." UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::INDEXING::FILESOURCEINDEXER::T3230000698"] = "The folder '{0}' could not be opened. Please check whether you are allowed to read it."
-- The folder '{0}' cannot be reached. Perhaps the drive it is on is not connected, or the folder was moved or renamed. Nothing was removed from the index. Once the folder is back, refresh this data source.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::INDEXING::FILESOURCEINDEXER::T3722338131"] = "The folder '{0}' cannot be reached. Perhaps the drive it is on is not connected, or the folder was moved or renamed. Nothing was removed from the index. Once the folder is back, refresh this data source."
-- The file '{0}' could not be read. Please check whether you are allowed to read it. -- The file '{0}' could not be read. Please check whether you are allowed to read it.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::INDEXING::FILESOURCEINDEXER::T3924882233"] = "The file '{0}' could not be read. Please check whether you are allowed to read it." UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::INDEXING::FILESOURCEINDEXER::T3924882233"] = "The file '{0}' could not be read. Please check whether you are allowed to read it."
@@ -14577,6 +14790,9 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::VALIDATION::DATASOURCEVALIDATION::T2222598306"
-- Please select your security policy. -- Please select your security policy.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::VALIDATION::DATASOURCEVALIDATION::T2250909198"] = "Please select your security policy." UI_TEXT_CONTENT["AISTUDIO::TOOLS::VALIDATION::DATASOURCEVALIDATION::T2250909198"] = "Please select your security policy."
-- The use of this embedding provider has been prohibited by your organization. Please select a different embedding provider.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::VALIDATION::DATASOURCEVALIDATION::T2376506873"] = "The use of this embedding provider has been prohibited by your organization. Please select a different embedding provider."
-- Please acknowledge that you are aware of the cloud transcription implications. -- Please acknowledge that you are aware of the cloud transcription implications.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::VALIDATION::DATASOURCEVALIDATION::T2818048408"] = "Please acknowledge that you are aware of the cloud transcription implications." UI_TEXT_CONTENT["AISTUDIO::TOOLS::VALIDATION::DATASOURCEVALIDATION::T2818048408"] = "Please acknowledge that you are aware of the cloud transcription implications."
@@ -14634,11 +14850,8 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::VALIDATION::DATASOURCEVALIDATION::T90753429"]
-- Please enter the secret necessary for authentication. -- Please enter the secret necessary for authentication.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::VALIDATION::DATASOURCEVALIDATION::T968385876"] = "Please enter the secret necessary for authentication." UI_TEXT_CONTENT["AISTUDIO::TOOLS::VALIDATION::DATASOURCEVALIDATION::T968385876"] = "Please enter the secret necessary for authentication."
-- Unsupported image format -- AI Studio cannot give {0} images to an AI model. Please convert the image to JPG or PNG.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::VALIDATION::FILEEXTENSIONVALIDATION::T1398282880"] = "Unsupported image format" UI_TEXT_CONTENT["AISTUDIO::TOOLS::VALIDATION::FILEEXTENSIONVALIDATION::T1578559061"] = "AI Studio cannot give {0} images to an AI model. Please convert the image to JPG or PNG."
-- File has no extension
UI_TEXT_CONTENT["AISTUDIO::TOOLS::VALIDATION::FILEEXTENSIONVALIDATION::T1555980031"] = "File has no extension"
-- Audio files are not supported yet -- Audio files are not supported yet
UI_TEXT_CONTENT["AISTUDIO::TOOLS::VALIDATION::FILEEXTENSIONVALIDATION::T2919730669"] = "Audio files are not supported yet" UI_TEXT_CONTENT["AISTUDIO::TOOLS::VALIDATION::FILEEXTENSIONVALIDATION::T2919730669"] = "Audio files are not supported yet"
+1
View File
@@ -175,6 +175,7 @@ internal sealed class Program
builder.Services.AddSingleton<SettingsManager>(); builder.Services.AddSingleton<SettingsManager>();
builder.Services.AddSingleton<AIGeneratedContentDisclosureService>(); builder.Services.AddSingleton<AIGeneratedContentDisclosureService>();
builder.Services.AddSingleton<PromptInjectionGuardService>(); builder.Services.AddSingleton<PromptInjectionGuardService>();
builder.Services.AddSingleton<ModelImageService>();
builder.Services.AddSingleton<ToolSettingsService>(); builder.Services.AddSingleton<ToolSettingsService>();
builder.Services.AddSingleton<WebPageRetrievalService>(); builder.Services.AddSingleton<WebPageRetrievalService>();
builder.Services.AddSingleton<IToolImplementation, ReadWebPageTool>(); builder.Services.AddSingleton<IToolImplementation, ReadWebPageTool>();
@@ -59,18 +59,16 @@ public sealed class ProviderAnthropic() : BaseProvider(LLMProviders.ANTHROPIC, n
Text = text, Text = text,
}, },
// Anthropic-specific image sub-content: // Anthropic-specific image sub-content, which takes the base64 and its type apart:
async attachment => new SubContentImage image => new SubContentImage
{ {
Source = new SubContentBase64Image Source = new SubContentBase64Image
{ {
Data = await attachment.TryAsBase64(token: token) is (true, var base64Content) Data = image.Base64,
? base64Content MediaType = image.MimeType,
: string.Empty,
MediaType = attachment.DetermineMimeType(),
}
} }
},
token
); );
// //
@@ -288,6 +288,27 @@ public static class LLMProvidersExtensions
_ => false, _ => false,
}; };
/// <summary>
/// The largest image file, in bytes, which the provider accepts in a request, where it states one.
/// </summary>
/// <remarks>
/// The limit belongs to the API of the provider, not to the model: the same model may accept
/// larger images through another provider. What we actually send is asked through
/// IImageSourceExtensions.MaxImageSizeBytesFor, which also applies our own limit.
/// </remarks>
/// <param name="llmProvider">The provider to check.</param>
/// <returns>The size limit of one image file, or null when the provider states none we know of.</returns>
public static long? GetMaxImageSizeBytes(this LLMProviders llmProvider) => llmProvider switch
{
//
// Anthropic accepts at most 5 MiB (5,242,880 bytes) of base64 per image. Base64 needs four
// characters for every three bytes, which leaves 3,932,160 bytes for the image file itself:
//
LLMProviders.ANTHROPIC => 3_932_160,
_ => null,
};
/// <summary> /// <summary>
/// Creates a new provider instance based on the provider value. /// Creates a new provider instance based on the provider value.
/// </summary> /// </summary>
@@ -320,6 +341,16 @@ public static class LLMProvidersExtensions
private static IProvider CreateProvider(this LLMProviders provider, string instanceName, Host host, string hostname, HFInferenceProvider inferenceProvider, string configuredProviderId = "", string expertProviderApiParameter = "", bool isEnterpriseConfiguration = false, HFEndpointKind hfEndpointKind = HFEndpointKind.CHAT, ProviderCapabilityOverrides? capabilityOverrides = null, string tokenizerPath = "") private static IProvider CreateProvider(this LLMProviders provider, string instanceName, Host host, string hostname, HFInferenceProvider inferenceProvider, string configuredProviderId = "", string expertProviderApiParameter = "", bool isEnterpriseConfiguration = false, HFEndpointKind hfEndpointKind = HFEndpointKind.CHAT, ProviderCapabilityOverrides? capabilityOverrides = null, string tokenizerPath = "")
{ {
//
// Every provider instance comes from here, for LLMs, embeddings, and transcriptions alike.
// Whatever a caller forgot to check, no request reaches a provider the organization prohibited:
//
if (IsProhibitedByOrganization(provider))
{
LOGGER.LogWarning($"Refused to create the provider '{instanceName}' of '{provider}': the organization prohibited this provider.");
return new NoProvider();
}
try try
{ {
IProvider providerInstance = provider switch IProvider providerInstance = provider switch
@@ -362,6 +393,21 @@ public static class LLMProvidersExtensions
} }
} }
/// <summary>
/// Checks whether the organization prohibited the given provider.
/// </summary>
/// <remarks>
/// The settings come from Program.SERVICE_PROVIDER, because none of the callers of CreateProvider
/// hands them in. Tests which create providers without the services of the app have no settings
/// there, and nothing is prohibited for them.
/// </remarks>
private static bool IsProhibitedByOrganization(LLMProviders provider)
{
// Declared as never null, but it is null until the app has started its services:
// ReSharper disable once ConditionalAccessQualifierIsNonNullableAccordingToAPIContract
return Program.SERVICE_PROVIDER?.GetService<SettingsManager>() is { } settingsManager && settingsManager.IsProviderProhibited(provider);
}
public static string GetCreationURL(this LLMProviders provider) => provider switch public static string GetCreationURL(this LLMProviders provider) => provider switch
{ {
LLMProviders.OPEN_AI => "https://platform.openai.com/signup", LLMProviders.OPEN_AI => "https://platform.openai.com/signup",
@@ -113,15 +113,14 @@ public sealed class ProviderOpenAI() : BaseProvider(LLMProviders.OPEN_AI, new Ur
{ {
Text = text, Text = text,
}, },
async attachment => new SubContentImageUrlNested image => new SubContentImageUrlNested
{ {
ImageUrl = new SubContentImageUrlData ImageUrl = new SubContentImageUrlData
{ {
Url = await attachment.TryAsBase64(token: token) is (true, var base64Content) Url = image.ToDataUrl(),
? $"data:{attachment.DetermineMimeType()};base64,{base64Content}"
: string.Empty,
}, },
}); },
token);
return new ChatCompletionAPIRequest return new ChatCompletionAPIRequest
{ {
@@ -192,12 +191,11 @@ public sealed class ProviderOpenAI() : BaseProvider(LLMProviders.OPEN_AI, new Ur
{ {
Text = text, Text = text,
}, },
async attachment => new SubContentInputImage image => new SubContentInputImage
{ {
ImageUrl = await attachment.TryAsBase64(token: token) is (true, var base64Content) ImageUrl = image.ToDataUrl(),
? $"data:{attachment.DetermineMimeType()};base64,{base64Content}" },
: string.Empty, token);
});
var baseInput = new List<object> { systemPrompt }; var baseInput = new List<object> { systemPrompt };
baseInput.AddRange(messages); baseInput.AddRange(messages);
@@ -36,6 +36,29 @@ public static class HostExtensions
_ => "embeddings", _ => "embeddings",
}; };
/// <summary>
/// The largest image file, in bytes, which the engine accepts in a chat request, where we know of one.
/// </summary>
/// <remarks>
/// Like the limit of a cloud provider, see LLMProvidersExtensions.GetMaxImageSizeBytes, this
/// belongs to the API of the engine, not to the model. What we actually send is asked through
/// IImageSourceExtensions.MaxImageSizeBytesFor.
/// </remarks>
/// <param name="host">The engine.</param>
/// <returns>The size limit of one image file, or null when the engine states none we know of.</returns>
public static long? GetMaxImageSizeBytes(this Host host) => host switch
{
//
// llmman answers a chat request larger than 2 MiB with 413: its route /v1/chat/completions
// keeps the default limit of its web framework, read on 2026-10-22 at
// https://github.com/llmmanorg/llmman/blob/main/src/cmd/serve.rs. An image of 1 MiB takes
// about 1.33 MiB as base64, which leaves room for the text of the conversation:
//
Host.LLMMAN => 1_048_576,
_ => null,
};
public static bool IsChatSupported(this Host host) public static bool IsChatSupported(this Host host)
{ {
switch (host) switch (host)
+135 -11
View File
@@ -51,6 +51,46 @@ public record ChatTemplate(
/// </remarks> /// </remarks>
public DataSourceOptions? DataSourceOptions { get; init; } public DataSourceOptions? DataSourceOptions { get; init; }
/// <summary>
/// The profile this template preselects for a chat started with it.
/// </summary>
/// <remarks>
/// Null means the template says nothing about profiles, so the chat keeps the profile it would
/// start with anyway: the default of the chat settings, or the one the user picked. The ID of
/// Profile.NO_PROFILE is a statement of its own: such chats start without a profile, whatever
/// that default says.<br/><br/>
/// A preselection, not a limit: the user picks another profile in the chat as usual. It means
/// nothing for a template which does not allow profiles, and a profile which is gone leaves the
/// chat default in place.
/// </remarks>
public string? ProfileId { get; init; }
/// <summary>
/// The provider, and with it the model, this template preselects for a chat started with it.
/// </summary>
/// <remarks>
/// Null means the template says nothing, so the chat starts with the provider it would start
/// with anyway. A preselection, not a limit, and no way around the confidence levels either: a
/// provider which is gone, or which may not be used for chats, leaves the chat to start as if
/// the template said nothing.
/// </remarks>
public string? ProviderId { get; init; }
/// <summary>
/// The name of the workspace a chat started with this template is kept in.
/// </summary>
/// <remarks>
/// Null means the template says nothing, so the chat goes where it would go anyway. A name
/// rather than an ID: a workspace has an ID of its own on every computer, so a configuration
/// plugin has no other way to name one. Like for an assistant chat launcher, the workspace is
/// created when it does not exist yet; on the chat page only with the first message of the chat,
/// so that trying out a template creates no workspace.<br/><br/>
/// It only fills in where a chat would belong to no workspace otherwise. A chat started in a
/// workspace on purpose, for example from the workspace list, stays there, and so does every
/// chat while workspaces are switched off.
/// </remarks>
public string? WorkspaceName { get; init; }
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(ChatTemplate).Namespace, nameof(ChatTemplate)); private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(ChatTemplate).Namespace, nameof(ChatTemplate));
private static readonly ILogger<ChatTemplate> LOGGER = Program.LOGGER_FACTORY.CreateLogger<ChatTemplate>(); private static readonly ILogger<ChatTemplate> LOGGER = Program.LOGGER_FACTORY.CreateLogger<ChatTemplate>();
@@ -67,6 +107,9 @@ public record ChatTemplate(
AllowProfileUsage = true, AllowProfileUsage = true,
ToolIds = null, ToolIds = null,
DataSourceOptions = null, DataSourceOptions = null,
ProfileId = null,
ProviderId = null,
WorkspaceName = null,
EnterpriseConfigurationPluginId = Guid.Empty, EnterpriseConfigurationPluginId = Guid.Empty,
IsEnterpriseConfiguration = false, IsEnterpriseConfiguration = false,
}; };
@@ -81,6 +124,16 @@ public record ChatTemplate(
#endregion #endregion
/// <summary>
/// Whether this template starts its chats without a profile, whatever the default says.
/// </summary>
/// <remarks>
/// That is a statement of its own, made with the ID of the no-profile entry, see ProfileId. It
/// names no profile which could be missing on another computer. A method rather than a property,
/// so that it does not end up in the stored settings.
/// </remarks>
public bool StartsWithoutProfile() => string.Equals(this.ProfileId, Profile.NO_PROFILE.Id, StringComparison.OrdinalIgnoreCase);
/// <summary> /// <summary>
/// Gets the name of this chat template. If it is the NO_CHAT_TEMPLATE, it returns a localized string. /// Gets the name of this chat template. If it is the NO_CHAT_TEMPLATE, it returns a localized string.
/// </summary> /// </summary>
@@ -117,12 +170,12 @@ public record ChatTemplate(
/// <param name="chatTemplate">The chat template the launcher opens its chat with.</param> /// <param name="chatTemplate">The chat template the launcher opens its chat with.</param>
/// <param name="launcherToolIds">The tools the launcher names itself, or null when it names none.</param> /// <param name="launcherToolIds">The tools the launcher names itself, or null when it names none.</param>
/// <returns>The tools to start with — null when neither says anything, which leaves the chat default in place — and whether the launcher's own choice was dropped for it.</returns> /// <returns>The tools to start with — null when neither says anything, which leaves the chat default in place — and whether the launcher's own choice was dropped for it.</returns>
public static (IReadOnlyCollection<string>? ToolIds, bool LauncherChoiceDropped) ChooseToolIds(ChatTemplate chatTemplate, IReadOnlyCollection<string>? launcherToolIds) public static ChatTemplateToolChoice ChooseToolIds(ChatTemplate chatTemplate, IReadOnlyCollection<string>? launcherToolIds)
{ {
if (chatTemplate.ToolIds is not { } templateToolIds) if (chatTemplate.ToolIds is not { } templateToolIds)
return (launcherToolIds, false); return new(launcherToolIds, false);
return (templateToolIds, launcherToolIds is not null); return new(templateToolIds, launcherToolIds is not null);
} }
/// <summary> /// <summary>
@@ -137,12 +190,12 @@ public record ChatTemplate(
/// <param name="chatTemplate">The chat template the launcher opens its chat with.</param> /// <param name="chatTemplate">The chat template the launcher opens its chat with.</param>
/// <param name="launcherOptions">The options built from the data sources the launcher names, or null when it names none.</param> /// <param name="launcherOptions">The options built from the data sources the launcher names, or null when it names none.</param>
/// <returns>The options to start with — null when neither says anything, which leaves the chat default in place — and whether the launcher's own choice was dropped for them.</returns> /// <returns>The options to start with — null when neither says anything, which leaves the chat default in place — and whether the launcher's own choice was dropped for them.</returns>
public static (DataSourceOptions? Options, bool LauncherChoiceDropped) ChooseDataSourceOptions(ChatTemplate chatTemplate, DataSourceOptions? launcherOptions) public static ChatTemplateDataSourceChoice ChooseDataSourceOptions(ChatTemplate chatTemplate, DataSourceOptions? launcherOptions)
{ {
if (chatTemplate.DataSourceOptions is not { } templateOptions) if (chatTemplate.DataSourceOptions is not { } templateOptions)
return (launcherOptions, false); return new(launcherOptions, false);
return (templateOptions.CreateCopy(), launcherOptions is not null); return new(templateOptions.CreateCopy(), launcherOptions is not null);
} }
/// <summary> /// <summary>
@@ -202,6 +255,10 @@ public record ChatTemplate(
var fileAttachments = ParseFileAttachments(idx, table, pluginPath); var fileAttachments = ParseFileAttachments(idx, table, pluginPath);
var profileId = ParseOptionalId(idx, table, "ProfileId", allowEmpty: true);
if (profileId is not null && !allowProfileUsage)
LOGGER.LogWarning("The chat template {IdxChatTemplate} preselects a profile but does not allow profiles. Chats started with it ignore that profile.", idx);
template = new ChatTemplate template = new ChatTemplate
{ {
Num = 0, // will be set later by the PluginConfigurationObject Num = 0, // will be set later by the PluginConfigurationObject
@@ -214,6 +271,9 @@ public record ChatTemplate(
AllowProfileUsage = allowProfileUsage, AllowProfileUsage = allowProfileUsage,
ToolIds = ParseToolIds(idx, table), ToolIds = ParseToolIds(idx, table),
DataSourceOptions = ParseDataSourceOptions(idx, table), DataSourceOptions = ParseDataSourceOptions(idx, table),
ProfileId = profileId,
ProviderId = ParseOptionalId(idx, table, "ProviderId", allowEmpty: false),
WorkspaceName = ParseWorkspaceName(idx, table),
IsEnterpriseConfiguration = true, IsEnterpriseConfiguration = true,
EnterpriseConfigurationPluginId = configPluginId, EnterpriseConfigurationPluginId = configPluginId,
}; };
@@ -294,6 +354,43 @@ public record ChatTemplate(
return toolIds; return toolIds;
} }
/// <remarks>
/// The empty GUID is a statement for a profile — start without one — but names no provider at
/// all, hence the switch. Anything which is no GUID cannot point at a provider or profile and is
/// dropped, so that the template still works, with the chat defaults in its place.
/// </remarks>
private static string? ParseOptionalId(int idx, LuaTable table, string fieldName, bool allowEmpty)
{
if (!table.TryGetValue(fieldName, out var idValue))
return null;
if (!idValue.TryRead<string>(out var idText) || !Guid.TryParse(idText, out var id) || (!allowEmpty && id == Guid.Empty))
{
LOGGER.LogWarning("The {FieldName} of chat template {IdxChatTemplate} is not a valid ID and will be ignored.", fieldName, idx);
return null;
}
return id.ToString();
}
/// <remarks>
/// An empty name would mean "no workspace", which is what a template says by leaving the field
/// out. So it is dropped, with a warning, as is anything which is no text.
/// </remarks>
private static string? ParseWorkspaceName(int idx, LuaTable table)
{
if (!table.TryGetValue("WorkspaceName", out var workspaceNameValue))
return null;
if (!workspaceNameValue.TryRead<string>(out var workspaceName) || string.IsNullOrWhiteSpace(workspaceName))
{
LOGGER.LogWarning("The WorkspaceName of chat template {IdxChatTemplate} is not a valid name and will be ignored.", idx);
return null;
}
return workspaceName.Trim();
}
private static DataSourceOptions? ParseDataSourceOptions(int idx, LuaTable table) private static DataSourceOptions? ParseDataSourceOptions(int idx, LuaTable table)
{ {
if (!table.TryGetValue("DataSourceOptions", out var optionsValue) || !optionsValue.TryRead<LuaTable>(out var optionsTable)) if (!table.TryGetValue("DataSourceOptions", out var optionsValue) || !optionsValue.TryRead<LuaTable>(out var optionsTable))
@@ -449,22 +546,25 @@ public record ChatTemplate(
var fileAttachmentsLua = this.BuildFileAttachmentsLua(fileAttachmentPaths); var fileAttachmentsLua = this.BuildFileAttachmentsLua(fileAttachmentPaths);
// //
// Both of these may be absent entirely, because saying nothing about tools or data sources // These may be absent entirely, because saying nothing about the profile, the provider, the
// is a statement of its own. They therefore bring their own line break and indentation // workspace, the tools, or the data sources is a statement of its own. They therefore bring
// instead of sitting on a line of the template: // their own line break and indentation instead of sitting on a line of the template:
// //
var profileIdLua = BuildOptionalStringLua("ProfileId", this.ProfileId);
var providerIdLua = BuildOptionalStringLua("ProviderId", this.ProviderId);
var workspaceNameLua = BuildOptionalStringLua("WorkspaceName", this.WorkspaceName);
var toolIdsLua = this.BuildToolIdsLua(); var toolIdsLua = this.BuildToolIdsLua();
var dataSourceOptionsLua = this.BuildDataSourceOptionsLua(); var dataSourceOptionsLua = this.BuildDataSourceOptionsLua();
luaCode = $$""" luaCode = $$"""
{{this.BuildDataSourceIdNote()}}CONFIG["CHAT_TEMPLATES"][#CONFIG["CHAT_TEMPLATES"]+1] = { {{this.BuildProviderAndProfileIdNote()}}{{this.BuildDataSourceIdNote()}}CONFIG["CHAT_TEMPLATES"][#CONFIG["CHAT_TEMPLATES"]+1] = {
["Id"] = "{{LuaTools.EscapeLuaString(exportId)}}", ["Id"] = "{{LuaTools.EscapeLuaString(exportId)}}",
["Name"] = {{LuaTools.ToLuaStringLiteral(this.Name)}}, ["Name"] = {{LuaTools.ToLuaStringLiteral(this.Name)}},
["SystemPrompt"] = {{LuaTools.ToLuaStringLiteral(this.SystemPrompt)}}, ["SystemPrompt"] = {{LuaTools.ToLuaStringLiteral(this.SystemPrompt)}},
["PredefinedUserPrompt"] = {{LuaTools.ToLuaStringLiteral(this.PredefinedUserPrompt)}}, ["PredefinedUserPrompt"] = {{LuaTools.ToLuaStringLiteral(this.PredefinedUserPrompt)}},
["AllowProfileUsage"] = {{this.AllowProfileUsage.ToString().ToLowerInvariant()}}, ["AllowProfileUsage"] = {{this.AllowProfileUsage.ToString().ToLowerInvariant()}},
["FileAttachments"] = {{fileAttachmentsLua}}, ["FileAttachments"] = {{fileAttachmentsLua}},
["ExampleConversation"] = {{exampleConversationLua}},{{toolIdsLua}}{{dataSourceOptionsLua}} ["ExampleConversation"] = {{exampleConversationLua}},{{profileIdLua}}{{providerIdLua}}{{workspaceNameLua}}{{toolIdsLua}}{{dataSourceOptionsLua}}
} }
"""; """;
return true; return true;
@@ -574,6 +674,10 @@ public record ChatTemplate(
return true; return true;
} }
private static string BuildOptionalStringLua(string fieldName, string? value) => value is null
? string.Empty
: $"{Environment.NewLine} [\"{fieldName}\"] = {LuaTools.ToLuaStringLiteral(value)},";
/// <remarks> /// <remarks>
/// An empty set is written out as an empty table rather than being left out: the two say /// An empty set is written out as an empty table rather than being left out: the two say
/// different things, and dropping the line would turn "no tools at all" into "whatever the /// different things, and dropping the line would turn "no tools at all" into "whatever the
@@ -653,6 +757,26 @@ public record ChatTemplate(
"""; """;
} }
/// <remarks>
/// The same holds for the provider and the profile: a configuration plugin reaches only those
/// which it or another plugin defines, and the ones of this machine are mostly the user's own.
/// Starting without a profile names nothing, so it needs no note.
/// </remarks>
private string BuildProviderAndProfileIdNote()
{
var namesProfile = this.ProfileId is not null && !this.StartsWithoutProfile();
if (this.ProviderId is null && !namesProfile)
return string.Empty;
return """
-- The provider and profile IDs below are the ones of the machine this was exported from.
-- Please check them against your CONFIG["LLM_PROVIDERS"] and CONFIG["PROFILES"]: an ID
-- which resolves to nothing is ignored, and a chat with this template then starts with
-- the provider or profile it would start with anyway.
""";
}
private string BuildFileAttachmentsLua(IReadOnlyList<string>? fileAttachmentPaths) private string BuildFileAttachmentsLua(IReadOnlyList<string>? fileAttachmentPaths)
{ {
var paths = fileAttachmentPaths ?? this.FileAttachments.Select(attachment => attachment.FilePath).ToList(); var paths = fileAttachmentPaths ?? this.FileAttachments.Select(attachment => attachment.FilePath).ToList();
@@ -0,0 +1,16 @@
using AIStudio.Settings.DataModel;
namespace AIStudio.Settings;
/// <summary>
/// Whose data source options a chat started by a launcher begins with, decided by ChatTemplate.ChooseDataSourceOptions.
/// </summary>
/// <param name="Options">The options to start with, or null when neither side names any, which leaves the chat default in place.</param>
/// <param name="LauncherChoiceDropped">Whether the data sources the launcher names itself were dropped for the options of the chat template.</param>
public readonly record struct ChatTemplateDataSourceChoice(DataSourceOptions? Options, bool LauncherChoiceDropped)
{
/// <summary>
/// Neither side names any data sources, so the chat keeps its default options.
/// </summary>
public static readonly ChatTemplateDataSourceChoice NONE = new(null, false);
}
@@ -0,0 +1,14 @@
namespace AIStudio.Settings;
/// <summary>
/// Whose tools a chat started by a launcher begins with, decided by ChatTemplate.ChooseToolIds.
/// </summary>
/// <param name="ToolIds">The tools to start with, or null when neither side names any, which leaves the chat default in place.</param>
/// <param name="LauncherChoiceDropped">Whether the tools the launcher names itself were dropped for those of the chat template.</param>
public readonly record struct ChatTemplateToolChoice(IReadOnlyCollection<string>? ToolIds, bool LauncherChoiceDropped)
{
/// <summary>
/// Neither side names any tools, so the chat keeps its default.
/// </summary>
public static readonly ChatTemplateToolChoice NONE = new(null, false);
}
@@ -206,6 +206,11 @@ public sealed class Data
public DataSlideBuilder SlideBuilder { get; init; } = new(); public DataSlideBuilder SlideBuilder { get; init; } = new();
/// <summary>
/// Gets the managed Assistant Builder defaults.
/// </summary>
public DataAssistantBuilder AssistantBuilder { get; init; } = new(x => x.AssistantBuilder);
/// <summary> /// <summary>
/// Gets the managed Visual Briefing Assistant defaults. /// Gets the managed Visual Briefing Assistant defaults.
/// </summary> /// </summary>
@@ -1,5 +1,6 @@
using System.Linq.Expressions; using System.Linq.Expressions;
using AIStudio.Provider;
using AIStudio.Tools.PluginSystem; using AIStudio.Tools.PluginSystem;
namespace AIStudio.Settings.DataModel; namespace AIStudio.Settings.DataModel;
@@ -192,6 +193,17 @@ public sealed class DataApp(Expression<Func<Data, DataApp>>? configSelection = n
/// </summary> /// </summary>
public bool AllowUserToAddTranscriptionProvider { get; set; } = ManagedConfiguration.Register(configSelection, n => n.AllowUserToAddTranscriptionProvider, true); public bool AllowUserToAddTranscriptionProvider { get; set; } = ManagedConfiguration.Register(configSelection, n => n.AllowUserToAddTranscriptionProvider, true);
/// <summary>
/// The providers an organization prohibited, for LLMs, embeddings, and transcriptions alike.
/// </summary>
/// <remarks>
/// A prohibition names the operator of an API, not a family of models: prohibiting the API of a
/// model maker leaves its models reachable through a hub or a self-hosted server. Providers the
/// user set up before stay in the settings, so they are back once the prohibition ends, but
/// AI Studio neither offers nor contacts them, see SettingsManager.IsProviderProhibited.
/// </remarks>
public HashSet<LLMProviders> ProhibitedProviders { get; set; } = ManagedConfiguration.Register(configSelection, n => n.ProhibitedProviders, []);
/// <summary> /// <summary>
/// Should the user be allowed to add mailboxes? /// Should the user be allowed to add mailboxes?
/// </summary> /// </summary>
@@ -0,0 +1,44 @@
using System.Linq.Expressions;
using AIStudio.Provider;
namespace AIStudio.Settings.DataModel;
/// <summary>
/// Stores the default options for the Assistant Builder.
/// </summary>
/// <param name="configSelection">The managed-configuration selector.</param>
public sealed class DataAssistantBuilder(Expression<Func<Data, DataAssistantBuilder>>? configSelection = null)
{
/// <summary>
/// Initializes an unmanaged Assistant Builder settings instance.
/// </summary>
public DataAssistantBuilder() : this(null)
{
}
/// <summary>
/// Gets or sets whether Assistant Builder defaults are preselected.
/// </summary>
public bool PreselectOptions { get; set; } = ManagedConfiguration.Register(configSelection, value => value.PreselectOptions, true);
/// <summary>
/// Gets or sets the provider used by default. An empty value uses the app default.
/// </summary>
public string PreselectedProvider { get; set; } = ManagedConfiguration.Register(configSelection, value => value.PreselectedProvider, string.Empty);
/// <summary>
/// Gets or sets the default output language for generated assistants.
/// </summary>
public CommonLanguages PreselectedOutputLanguage { get; set; } = ManagedConfiguration.Register(configSelection, value => value.PreselectedOutputLanguage, CommonLanguages.AS_IS);
/// <summary>
/// Gets or sets the default custom output language.
/// </summary>
public string PreselectedOtherOutputLanguage { get; set; } = ManagedConfiguration.Register(configSelection, value => value.PreselectedOtherOutputLanguage, string.Empty);
/// <summary>
/// Gets or sets the minimum confidence a provider needs for building assistants.
/// </summary>
public ConfidenceLevel MinimumProviderConfidence { get; set; } = ManagedConfiguration.Register(configSelection, value => value.MinimumProviderConfidence, ConfidenceLevel.NONE);
}
@@ -11,7 +11,7 @@ namespace AIStudio.Settings.DataModel;
/// <param name="configSelection">The managed-configuration selector.</param> /// <param name="configSelection">The managed-configuration selector.</param>
public sealed class DataBatchProcessing(Expression<Func<Data, DataBatchProcessing>>? configSelection = null) public sealed class DataBatchProcessing(Expression<Func<Data, DataBatchProcessing>>? configSelection = null)
{ {
public const string DEFAULT_FILE_PATTERNS = "*.pdf;*.docx;*.pptx;*.xlsx;*.md;*.txt;*.mp3;*.wav;*.wave;*.aac;*.flac;*.ogg;*.opus;*.m4a;*.m4b;*.wma;*.alac;*.aif;*.aiff;*.caf;*.mp4;*.m4v;*.avi;*.mkv;*.mov;*.wmv;*.flv;*.webm"; public const string DEFAULT_FILE_PATTERNS = "*.pdf;*.docx;*.pptx;*.xlsx;*.md;*.txt;*.jpg;*.jpeg;*.png;*.webp;*.gif;*.bmp;*.tif;*.tiff;*.mp3;*.wav;*.wave;*.aac;*.flac;*.ogg;*.opus;*.m4a;*.m4b;*.wma;*.alac;*.aif;*.aiff;*.caf;*.mp4;*.m4v;*.avi;*.mkv;*.mov;*.wmv;*.flv;*.webm";
public const int MIN_DELAY_SECONDS = 6; public const int MIN_DELAY_SECONDS = 6;
public const int MAX_DELAY_SECONDS = 300; public const int MAX_DELAY_SECONDS = 300;
public const int DEFAULT_MIN_DELAY_SECONDS = 6; public const int DEFAULT_MIN_DELAY_SECONDS = 6;
@@ -581,6 +581,14 @@ public static partial class ManagedConfiguration
// Step 3 -- try to parse the string as the target type: // Step 3 -- try to parse the string as the target type:
if (EnumNames.TryParse(typeof(TValue), configuredLuaValueText, out var configuredEnum)) if (EnumNames.TryParse(typeof(TValue), configuredLuaValueText, out var configuredEnum))
set.Add((TValue)configuredEnum); set.Add((TValue)configuredEnum);
//
// Dropped, but not quietly: such a set may prohibit something, and a typo would
// otherwise leave it allowed without anybody noticing. A dry run comes before
// the actual one, so only the actual one reports it:
//
else if (!dryRun)
Log.LogWarning($"The configuration plugin '{configPluginId}' names '{configuredLuaValueText}' in the setting '{configMeta.SettingName}', which is no valid value. Ignoring it.");
} }
} }
@@ -299,7 +299,7 @@ public static partial class ManagedConfiguration
/// <summary> /// <summary>
/// Determines the configuration plugin which currently manages a setting, if any. /// Determines the configuration plugin which currently manages a setting, if any.
/// </summary> /// </summary>
private static Guid GetSettingOwner(ConfigMetaBase configMeta) internal static Guid GetSettingOwner(ConfigMetaBase configMeta)
{ {
if (configMeta.IsLocked && configMeta.LockedByConfigPluginId != Guid.Empty) if (configMeta.IsLocked && configMeta.LockedByConfigPluginId != Guid.Empty)
return configMeta.LockedByConfigPluginId; return configMeta.LockedByConfigPluginId;
@@ -81,10 +81,17 @@ public static partial class ProviderExtensions
/// wants to know whether an image may be sent has to accept both, which is why the question is asked /// wants to know whether an image may be sent has to accept both, which is why the question is asked
/// here instead of at each call site: attaching a file and validating an already attached file must /// here instead of at each call site: attaching a file and validating an already attached file must
/// never disagree about it. /// never disagree about it.
///
/// A limit of zero images takes the capability back. Zero is a real answer, e.g., from an operator
/// whose engine accepts no images at all, see ImageLimits; an unknown limit says nothing.
/// </remarks> /// </remarks>
/// <param name="provider">The configured provider.</param> /// <param name="provider">The configured provider.</param>
/// <returns><c>true</c> when the model accepts image input.</returns> /// <returns><c>true</c> when the model accepts image input.</returns>
public static bool SupportsImageInput(this Provider provider) => provider.GetModelProfile().HasAny(Capability.SINGLE_IMAGE_INPUT | Capability.MULTIPLE_IMAGE_INPUT); public static bool SupportsImageInput(this Provider provider)
{
var profile = provider.GetModelProfile();
return profile.HasAny(Capability.SINGLE_IMAGE_INPUT | Capability.MULTIPLE_IMAGE_INPUT) && profile.Images.MaxInOneMessage is not 0;
}
/// <summary> /// <summary>
/// Checks whether this model can be used for chatting. /// Checks whether this model can be used for chatting.
@@ -585,7 +585,7 @@ public sealed class SettingsManager
var minimumLevel = this.GetMinimumConfidenceLevel(component); var minimumLevel = this.GetMinimumConfidenceLevel(component);
// When there is only one provider, and it has a confidence level that is high enough, we return it: // When there is only one provider, and it has a confidence level that is high enough, we return it:
if (this.ConfigurationData.Providers.Count == 1 && this.ConfigurationData.Providers[0].UsedLLMProvider.GetConfidence(this).Level >= minimumLevel) if (this.ConfigurationData.Providers.Count == 1 && this.IsProviderUsable(this.ConfigurationData.Providers[0].UsedLLMProvider, minimumLevel))
return this.ConfigurationData.Providers[0]; return this.ConfigurationData.Providers[0];
// Is there a current provider with a sufficiently high confidence level? // Is there a current provider with a sufficiently high confidence level?
@@ -593,14 +593,14 @@ public sealed class SettingsManager
if (currentProviderId is not null && !string.IsNullOrWhiteSpace(currentProviderId)) if (currentProviderId is not null && !string.IsNullOrWhiteSpace(currentProviderId))
{ {
var currentProviderProbe = this.ConfigurationData.Providers.FirstOrDefault(x => x.Id == currentProviderId); var currentProviderProbe = this.ConfigurationData.Providers.FirstOrDefault(x => x.Id == currentProviderId);
if (currentProviderProbe is not null && currentProviderProbe.UsedLLMProvider.GetConfidence(this).Level >= minimumLevel) if (currentProviderProbe is not null && this.IsProviderUsable(currentProviderProbe.UsedLLMProvider, minimumLevel))
currentProvider = currentProviderProbe; currentProvider = currentProviderProbe;
} }
// Is there a component-preselected provider with a sufficiently high confidence level? // Is there a component-preselected provider with a sufficiently high confidence level?
var preselectedProvider = Provider.NONE; var preselectedProvider = Provider.NONE;
var preselectedProviderProbe = component.PreselectedProvider(this); var preselectedProviderProbe = component.PreselectedProvider(this);
if(preselectedProviderProbe != Provider.NONE && preselectedProviderProbe.UsedLLMProvider.GetConfidence(this).Level >= minimumLevel) if(preselectedProviderProbe != Provider.NONE && this.IsProviderUsable(preselectedProviderProbe.UsedLLMProvider, minimumLevel))
preselectedProvider = preselectedProviderProbe; preselectedProvider = preselectedProviderProbe;
// //
@@ -628,7 +628,7 @@ public sealed class SettingsManager
return preselectedProvider; return preselectedProvider;
// When there is an app-wide preselected provider, and it has a confidence level that is high enough, we return it: // When there is an app-wide preselected provider, and it has a confidence level that is high enough, we return it:
return this.ConfigurationData.Providers.FirstOrDefault(x => x.Id == this.ConfigurationData.App.PreselectedProvider && x.UsedLLMProvider.GetConfidence(this).Level >= minimumLevel) ?? Provider.NONE; return this.ConfigurationData.Providers.FirstOrDefault(x => x.Id == this.ConfigurationData.App.PreselectedProvider && this.IsProviderUsable(x.UsedLLMProvider, minimumLevel)) ?? Provider.NONE;
} }
public Provider GetChatProviderForLoadedChat(string? chatProviderId = null) public Provider GetChatProviderForLoadedChat(string? chatProviderId = null)
@@ -665,7 +665,7 @@ public sealed class SettingsManager
bool IsSelectableProvider(Provider provider) => bool IsSelectableProvider(Provider provider) =>
provider != Provider.NONE provider != Provider.NONE
&& provider.UsedLLMProvider != LLMProviders.NONE && provider.UsedLLMProvider != LLMProviders.NONE
&& provider.UsedLLMProvider.GetConfidence(this).Level >= minimumLevel; && this.IsProviderUsable(provider.UsedLLMProvider, minimumLevel);
} }
/// <summary> /// <summary>
@@ -684,7 +684,8 @@ public sealed class SettingsManager
/// <para> /// <para>
/// Whenever a provider is about to be used for an LLM request, do not use this method. Use /// Whenever a provider is about to be used for an LLM request, do not use this method. Use
/// GetConfidentProviders, GetPreselectedProvider, or GetChatProviderForLoadedChat instead, /// GetConfidentProviders, GetPreselectedProvider, or GetChatProviderForLoadedChat instead,
/// since they honor the confidence levels. /// since they honor the confidence levels and leave out the providers the organization
/// prohibited. This method returns those as well, so that the settings can mark them.
/// </para> /// </para>
/// <para> /// <para>
/// The returned list is a sorted copy of the provider list, ordered by the used LLM provider and /// The returned list is a sorted copy of the provider list, ordered by the used LLM provider and
@@ -706,9 +707,9 @@ public sealed class SettingsManager
/// <remarks> /// <remarks>
/// This method resolves a stored provider reference by its id. It applies neither the global /// This method resolves a stored provider reference by its id. It applies neither the global
/// minimum confidence level nor any component-specific minimum, so it returns the requested /// minimum confidence level nor any component-specific minimum, so it returns the requested
/// provider even when the user enforces a higher global minimum. Callers that intend to use the /// provider even when the user enforces a higher global minimum or the organization prohibited
/// returned provider for an LLM request must check it themselves through /// it. Callers that intend to use the returned provider for an LLM request must check it
/// IsProviderConfident or fall back to GetPreselectedProvider. /// themselves through IsProviderConfident or fall back to GetPreselectedProvider.
/// </remarks> /// </remarks>
/// <param name="providerId">The id of the provider to look up.</param> /// <param name="providerId">The id of the provider to look up.</param>
/// <returns>The provider, or <see cref="Provider.NONE"/> when no provider with that id exists.</returns> /// <returns>The provider, or <see cref="Provider.NONE"/> when no provider with that id exists.</returns>
@@ -741,6 +742,9 @@ public sealed class SettingsManager
/// <summary> /// <summary>
/// Checks whether the given provider satisfies the minimum confidence level of the given component. /// Checks whether the given provider satisfies the minimum confidence level of the given component.
/// </summary> /// </summary>
/// <remarks>
/// A provider the organization prohibited never does, see IsProviderUsable.
/// </remarks>
/// <param name="provider">The provider to check.</param> /// <param name="provider">The provider to check.</param>
/// <param name="component">The component for which the provider gets checked.</param> /// <param name="component">The component for which the provider gets checked.</param>
/// <param name="explicitMinimum">An explicit minimum level, which is applied when it is higher than the component's minimum.</param> /// <param name="explicitMinimum">An explicit minimum level, which is applied when it is higher than the component's minimum.</param>
@@ -750,12 +754,51 @@ public sealed class SettingsManager
if (provider.UsedLLMProvider is LLMProviders.NONE) if (provider.UsedLLMProvider is LLMProviders.NONE)
return false; return false;
return provider.UsedLLMProvider.GetConfidence(this).Level >= this.GetEffectiveMinimumConfidenceLevel(component, explicitMinimum); return this.IsProviderUsable(provider.UsedLLMProvider, this.GetEffectiveMinimumConfidenceLevel(component, explicitMinimum));
} }
/// <summary>
/// Checks whether the organization prohibited the given provider.
/// </summary>
/// <remarks>
/// A prohibition is a rule of its own, next to confidence and data-source security: no confidence
/// level and no trust by configuration makes a prohibited provider usable. It also makes the provider
/// untrusted, see GetConfiguredConfidenceLevel, but not the other way around. It names the operator
/// of an API, so a hub or a self-hosted server which serves the same models stays allowed.
/// </remarks>
/// <param name="provider">The provider to check, for LLMs, embeddings, and transcriptions alike.</param>
/// <returns>True, when the organization prohibited the provider. NONE is never prohibited.</returns>
public bool IsProviderProhibited(LLMProviders provider) => provider is not LLMProviders.NONE && this.ConfigurationData.App.ProhibitedProviders.Contains(provider);
/// <summary>
/// Checks whether the given provider may be used where the given minimum confidence level applies.
/// </summary>
/// <remarks>
/// The one check for every place which picks a provider: the organization did not prohibit it, and
/// its confidence level reaches the minimum. A prohibited provider is untrusted, see
/// GetConfiguredConfidenceLevel, but comparing the confidence level alone would still let it through
/// wherever no minimum is enforced, since even untrusted reaches the minimum NONE.
/// </remarks>
/// <param name="provider">The provider to check, for LLMs, embeddings, and transcriptions alike.</param>
/// <param name="minimumLevel">The minimum confidence level which applies.</param>
/// <returns>True, when the provider may be used, false otherwise.</returns>
public bool IsProviderUsable(LLMProviders provider, ConfidenceLevel minimumLevel) => !this.IsProviderProhibited(provider) && provider.GetConfidence(this).Level >= minimumLevel;
/// <summary>
/// Returns the prohibited providers which have a level of their own in the custom confidence scheme.
/// </summary>
/// <remarks>
/// The prohibition outranks these levels, see GetConfiguredConfidenceLevel.
/// </remarks>
/// <returns>The prohibited providers with a custom confidence level, in no particular order.</returns>
public IEnumerable<LLMProviders> GetProhibitedProvidersWithCustomConfidenceLevel() => this.ConfigurationData.Confidence.CustomConfidenceScheme.Keys.Where(this.IsProviderProhibited);
/// <summary> /// <summary>
/// Returns all providers that satisfy the minimum confidence level of the given component. /// Returns all providers that satisfy the minimum confidence level of the given component.
/// </summary> /// </summary>
/// <remarks>
/// The providers the organization prohibited are left out, see IsProviderUsable.
/// </remarks>
/// <param name="component">The component for which the providers get filtered.</param> /// <param name="component">The component for which the providers get filtered.</param>
/// <param name="explicitMinimum">An explicit minimum level, which is applied when it is higher than the component's minimum.</param> /// <param name="explicitMinimum">An explicit minimum level, which is applied when it is higher than the component's minimum.</param>
/// <returns>All providers the component may use, in the same order as GetAllProviders.</returns> /// <returns>All providers the component may use, in the same order as GetAllProviders.</returns>
@@ -763,7 +806,7 @@ public sealed class SettingsManager
{ {
var minimumLevel = this.GetEffectiveMinimumConfidenceLevel(component, explicitMinimum); var minimumLevel = this.GetEffectiveMinimumConfidenceLevel(component, explicitMinimum);
foreach (var provider in this.GetAllProviders()) foreach (var provider in this.GetAllProviders())
if (provider.UsedLLMProvider is not LLMProviders.NONE && provider.UsedLLMProvider.GetConfidence(this).Level >= minimumLevel) if (provider.UsedLLMProvider is not LLMProviders.NONE && this.IsProviderUsable(provider.UsedLLMProvider, minimumLevel))
yield return provider; yield return provider;
} }
@@ -877,6 +920,149 @@ public sealed class SettingsManager
return this.GetChatTemplateById(this.ConfigurationData.App.PreselectedChatTemplate); return this.GetChatTemplateById(this.ConfigurationData.App.PreselectedChatTemplate);
} }
/// <summary>
/// Returns the profile a chat template preselects for the chats started with it.
/// </summary>
/// <remarks>
/// Null whenever the template has nothing to say which could be used: it names no profile, it
/// does not allow profiles, or the profile it names is gone. The chat then keeps the profile it
/// would have without the template. Starting without a profile is an answer of its own, and it
/// comes back as Profile.NO_PROFILE.
/// </remarks>
/// <param name="chatTemplate">The chat template of the chat about to start.</param>
/// <returns>The profile to start with, or null to keep the usual one.</returns>
public Profile? GetChatTemplateProfile(ChatTemplate chatTemplate)
{
if (!chatTemplate.AllowProfileUsage || chatTemplate.ProfileId is null)
return null;
if (chatTemplate.StartsWithoutProfile())
return Profile.NO_PROFILE;
var profile = this.GetProfileById(chatTemplate.ProfileId);
return profile == Profile.NO_PROFILE ? null : profile;
}
/// <summary>
/// Returns the profile a chat started with the given chat template begins with.
/// </summary>
/// <remarks>
/// Null when the template says nothing about profiles, so the chat keeps the profile it has.
/// A template whose profile is gone starts its chats with the default profile of the chat
/// settings, which may be none — not with whatever profile the previous chat happened to use,
/// which would be a choice nobody made for this chat.
/// </remarks>
/// <param name="chatTemplate">The chat template of the chat about to start.</param>
/// <returns>The profile to start with, or null to keep the current one.</returns>
public Profile? GetChatTemplateStartProfile(ChatTemplate chatTemplate)
{
if (!chatTemplate.AllowProfileUsage || chatTemplate.ProfileId is null)
return null;
return this.GetChatTemplateProfile(chatTemplate) ?? this.GetPreselectedProfile(Tools.Components.CHAT);
}
/// <summary>
/// Returns the provider a chat template preselects for the chats started with it.
/// </summary>
/// <remarks>
/// The template is no way around the confidence levels: a provider which may not be used for
/// chats comes back as Provider.NONE, just like one which is gone or one the
/// template does not name at all. The chat then keeps the provider it would have without the
/// template.
/// </remarks>
/// <param name="chatTemplate">The chat template of the chat about to start.</param>
/// <returns>The provider to start with, or Provider.NONE to keep the usual one.</returns>
public Provider GetChatTemplateProvider(ChatTemplate chatTemplate) => this.GetChatTemplateProvider(chatTemplate.ProviderId);
/// <summary>
/// Returns the provider a chat template may preselect under the given ID.
/// </summary>
/// <remarks>
/// The one rule for the provider of a chat template: it exists and meets the confidence level
/// required for chats. The chat, assistant chat launchers, the chat template dialog, and the
/// import of a chat template all ask here, so that none of them accepts a provider another
/// one would leave out.
/// </remarks>
/// <param name="providerId">The ID the chat template names, or null when it names none.</param>
/// <returns>The provider, or Provider.NONE when the ID names no provider a chat may use.</returns>
public Provider GetChatTemplateProvider(string? providerId)
{
if (string.IsNullOrWhiteSpace(providerId))
return Provider.NONE;
var provider = this.GetProviderById(providerId);
return provider != Provider.NONE && this.IsProviderConfident(provider, Tools.Components.CHAT) ? provider : Provider.NONE;
}
/// <summary>
/// Returns the profile a chat begins with when it starts with the given chat template now.
/// </summary>
/// <remarks>
/// A template which names a profile decides, see GetChatTemplateStartProfile. One
/// which says nothing keeps the current profile, with one exception: when that profile is the one
/// the previous template preselected, the chat starts with the default profile of the chat
/// settings instead. Keeping it would carry the choice of another template over into this chat.
/// </remarks>
/// <param name="chatTemplate">The chat template of the chat about to start.</param>
/// <param name="previousChatTemplate">The chat template the current profile was chosen with; the same one when the template does not change.</param>
/// <param name="currentProfile">The profile the chat has right now.</param>
/// <returns>The profile to start with.</returns>
public Profile ChooseChatTemplateProfile(ChatTemplate chatTemplate, ChatTemplate previousChatTemplate, Profile currentProfile)
{
if (this.GetChatTemplateStartProfile(chatTemplate) is { } templateProfile)
return templateProfile;
if (this.GetChatTemplateStartProfile(previousChatTemplate) is { } previousTemplateProfile && previousTemplateProfile.Id == currentProfile.Id)
return this.GetPreselectedProfile(Tools.Components.CHAT);
return currentProfile;
}
/// <summary>
/// Returns the provider a chat begins with when it starts with the given chat template now.
/// </summary>
/// <remarks>
/// The same as for the profile, see ChooseChatTemplateProfile: a template which
/// names a usable provider decides, and the provider of the previous template is not carried
/// over. In its place comes the default provider of the chat settings. Without such a default,
/// the current provider stays, so that the chat does not lose its provider altogether.
/// </remarks>
/// <param name="chatTemplate">The chat template of the chat about to start.</param>
/// <param name="previousChatTemplate">The chat template the current provider was chosen with; the same one when the template does not change.</param>
/// <param name="currentProvider">The provider the chat has right now.</param>
/// <returns>The provider to start with.</returns>
public Provider ChooseChatTemplateProvider(ChatTemplate chatTemplate, ChatTemplate previousChatTemplate, Provider currentProvider)
{
var templateProvider = this.GetChatTemplateProvider(chatTemplate);
if (templateProvider != Provider.NONE)
return templateProvider;
var previousTemplateProvider = this.GetChatTemplateProvider(previousChatTemplate);
if (previousTemplateProvider == Provider.NONE || previousTemplateProvider.Id != currentProvider.Id)
return currentProvider;
var defaultProvider = this.GetPreselectedProvider(Tools.Components.CHAT);
return defaultProvider == Provider.NONE ? currentProvider : defaultProvider;
}
/// <summary>
/// Returns the name of the workspace a chat template keeps the chats started with it in.
/// </summary>
/// <remarks>
/// Null when the template names none, and also while workspaces are switched off: a chat then
/// belongs to no workspace, whatever its template says.
/// </remarks>
/// <param name="chatTemplate">The chat template of the chat about to start.</param>
/// <returns>The trimmed name of the workspace, or null to keep the chat where it would go anyway.</returns>
public string? GetChatTemplateWorkspaceName(ChatTemplate chatTemplate)
{
if (string.IsNullOrWhiteSpace(chatTemplate.WorkspaceName) || this.ConfigurationData.Workspace.StorageBehavior is WorkspaceStorageBehavior.DISABLE_WORKSPACES)
return null;
return chatTemplate.WorkspaceName.Trim();
}
public Profile GetProfileById(string? profileId) public Profile GetProfileById(string? profileId)
{ {
if (string.IsNullOrWhiteSpace(profileId)) if (string.IsNullOrWhiteSpace(profileId))
@@ -1010,11 +1196,25 @@ public sealed class SettingsManager
this.ConfigurationData.Tools.MinimumProviderConfidenceByToolId[settingsIds[0]] = confidenceLevel.ToString(); this.ConfigurationData.Tools.MinimumProviderConfidenceByToolId[settingsIds[0]] = confidenceLevel.ToString();
} }
/// <summary>
/// Returns the confidence level of the given provider.
/// </summary>
/// <remarks>
/// A prohibition by the organization comes first: a prohibited provider is always untrusted, no matter
/// what the confidence scheme says, and no matter which level the user or a configuration plugin chose
/// for it in the custom scheme. Those levels stay stored, so they apply again once the prohibition ends.
/// After that, the confidence scheme decides, and in the custom scheme, the level chosen for the provider.
/// </remarks>
/// <param name="llmProvider">The provider, for LLMs, embeddings, and transcriptions alike.</param>
/// <returns>The confidence level of the provider.</returns>
public ConfidenceLevel GetConfiguredConfidenceLevel(LLMProviders llmProvider) public ConfidenceLevel GetConfiguredConfidenceLevel(LLMProviders llmProvider)
{ {
if(llmProvider is LLMProviders.NONE) if(llmProvider is LLMProviders.NONE)
return ConfidenceLevel.NONE; return ConfidenceLevel.NONE;
if (this.IsProviderProhibited(llmProvider))
return ConfidenceLevel.UNTRUSTED;
switch (this.ConfigurationData.Confidence.ConfidenceScheme) switch (this.ConfigurationData.Confidence.ConfidenceScheme)
{ {
case ConfidenceSchemes.TRUST_ALL: case ConfidenceSchemes.TRUST_ALL:
@@ -255,6 +255,14 @@ public sealed class AIJobService(SettingsManager settingsManager, MessageBus mes
var provider = request.ProviderSettings.CreateProvider(); var provider = request.ProviderSettings.CreateProvider();
var chatThread = request.ChatThread; var chatThread = request.ChatThread;
// Checked on its own, so that the user learns what to change:
if (settingsManager.IsProviderProhibited(request.ProviderSettings.UsedLLMProvider))
{
logger.LogError("The provider of chat '{ChatId}' is prohibited by the organization. Skipping the AI process.", chatThread.ChatId);
await this.CompleteChatGenerationAsync(state, AIJobStatus.FAILED, TB("The use of this provider has been prohibited by your organization. Please select a different provider."));
return;
}
if (!chatThread.IsLLMProviderAllowed(provider)) if (!chatThread.IsLLMProviderAllowed(provider))
{ {
logger.LogError("The provider is not allowed for chat '{ChatId}' due to data security reasons. Skipping the AI process.", chatThread.ChatId); logger.LogError("The provider is not allowed for chat '{ChatId}' due to data security reasons. Skipping the AI process.", chatThread.ChatId);
@@ -152,6 +152,7 @@ public static class ComponentsExtensions
Components.BIAS_DAY_ASSISTANT => settingsManager.ConfigurationData.BiasOfTheDay.PreselectOptions ? settingsManager.ConfigurationData.BiasOfTheDay.MinimumProviderConfidence : default, Components.BIAS_DAY_ASSISTANT => settingsManager.ConfigurationData.BiasOfTheDay.PreselectOptions ? settingsManager.ConfigurationData.BiasOfTheDay.MinimumProviderConfidence : default,
Components.ERI_ASSISTANT => settingsManager.ConfigurationData.ERI.PreselectOptions ? settingsManager.ConfigurationData.ERI.MinimumProviderConfidence : default, Components.ERI_ASSISTANT => settingsManager.ConfigurationData.ERI.PreselectOptions ? settingsManager.ConfigurationData.ERI.MinimumProviderConfidence : default,
Components.SLIDE_BUILDER_ASSISTANT => settingsManager.ConfigurationData.SlideBuilder.PreselectOptions ? settingsManager.ConfigurationData.SlideBuilder.MinimumProviderConfidence : default, Components.SLIDE_BUILDER_ASSISTANT => settingsManager.ConfigurationData.SlideBuilder.PreselectOptions ? settingsManager.ConfigurationData.SlideBuilder.MinimumProviderConfidence : default,
Components.META_ASSISTANT => settingsManager.ConfigurationData.AssistantBuilder.PreselectOptions ? settingsManager.ConfigurationData.AssistantBuilder.MinimumProviderConfidence : default,
Components.VISUAL_BRIEFING_ASSISTANT => settingsManager.ConfigurationData.VisualBriefing.MinimumProviderConfidence, Components.VISUAL_BRIEFING_ASSISTANT => settingsManager.ConfigurationData.VisualBriefing.MinimumProviderConfidence,
// The minimum confidence for the Document Analysis Assistant is set per policy. // The minimum confidence for the Document Analysis Assistant is set per policy.
@@ -184,6 +185,7 @@ public static class ComponentsExtensions
Components.ERI_ASSISTANT => settingsManager.ConfigurationData.ERI.PreselectOptions ? settingsManager.GetProviderById(settingsManager.ConfigurationData.ERI.PreselectedProvider) : Settings.Provider.NONE, Components.ERI_ASSISTANT => settingsManager.ConfigurationData.ERI.PreselectOptions ? settingsManager.GetProviderById(settingsManager.ConfigurationData.ERI.PreselectedProvider) : Settings.Provider.NONE,
Components.I18N_ASSISTANT => settingsManager.ConfigurationData.I18N.PreselectOptions ? settingsManager.GetProviderById(settingsManager.ConfigurationData.I18N.PreselectedProvider) : Settings.Provider.NONE, Components.I18N_ASSISTANT => settingsManager.ConfigurationData.I18N.PreselectOptions ? settingsManager.GetProviderById(settingsManager.ConfigurationData.I18N.PreselectedProvider) : Settings.Provider.NONE,
Components.SLIDE_BUILDER_ASSISTANT => settingsManager.ConfigurationData.SlideBuilder.PreselectOptions ? settingsManager.GetProviderById(settingsManager.ConfigurationData.SlideBuilder.PreselectedProvider) : Settings.Provider.NONE, Components.SLIDE_BUILDER_ASSISTANT => settingsManager.ConfigurationData.SlideBuilder.PreselectOptions ? settingsManager.GetProviderById(settingsManager.ConfigurationData.SlideBuilder.PreselectedProvider) : Settings.Provider.NONE,
Components.META_ASSISTANT => settingsManager.ConfigurationData.AssistantBuilder.PreselectOptions ? settingsManager.GetProviderById(settingsManager.ConfigurationData.AssistantBuilder.PreselectedProvider) : Settings.Provider.NONE,
Components.VISUAL_BRIEFING_ASSISTANT => settingsManager.GetProviderById(settingsManager.ConfigurationData.VisualBriefing.PreselectedProvider), Components.VISUAL_BRIEFING_ASSISTANT => settingsManager.GetProviderById(settingsManager.ConfigurationData.VisualBriefing.PreselectedProvider),
// The Document Analysis Assistant does not have a preselected provider at the component level. // The Document Analysis Assistant does not have a preselected provider at the component level.
@@ -0,0 +1,115 @@
using AIStudio.Dialogs;
using AIStudio.Settings;
using AIStudio.Settings.DataModel;
using AIStudio.Tools.PluginSystem;
using AIStudio.Tools.Services;
using DialogOptions = AIStudio.Dialogs.DialogOptions;
namespace AIStudio.Tools;
/// <summary>
/// Opens the settings of a data source, and stores what the user changed in them.
/// </summary>
/// <remarks>
/// Kept here rather than in the places which offer it -- the data source table and the background
/// embeddings page, which leads to it when the file or the folder of a data source cannot be
/// reached -- so that saving a data source works the same way everywhere: the settings are stored,
/// and the data source is indexed right after. Mailboxes are kept in a list of their own and have
/// MailboxEditing instead.
/// </remarks>
public static class DataSourceEditing
{
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(DataSourceEditing).Namespace, nameof(DataSourceEditing));
/// <summary>
/// Whether the user may edit a data source.
/// </summary>
/// <param name="settingsManager">The settings, which hold the data source.</param>
/// <param name="dataSourceId">The id of the data source.</param>
/// <returns>False when the data source is gone or managed by the organization.</returns>
public static bool CanEdit(SettingsManager settingsManager, string dataSourceId) =>
settingsManager.ConfigurationData.DataSources.Any(dataSource => dataSource.Id == dataSourceId && !dataSource.IsEnterpriseConfiguration);
/// <summary>
/// Lets the user edit a data source, and stores and indexes it when they save.
/// </summary>
/// <remarks>
/// The caller tells the rest of the app that the configuration changed, as the sender of that message.
/// </remarks>
/// <param name="dialogService">The dialog service to show the settings with.</param>
/// <param name="settingsManager">The settings, which hold the data source.</param>
/// <param name="embeddingService">The service which indexes the data source.</param>
/// <param name="dataSourceId">The id of the data source.</param>
/// <returns>True when the user saved the data source. False when they cancelled, or the data source is gone or managed by the organization.</returns>
public static async Task<bool> EditAsync(IDialogService dialogService, SettingsManager settingsManager, DataSourceEmbeddingService embeddingService, string dataSourceId)
{
var dataSources = settingsManager.ConfigurationData.DataSources;
var storedDataSource = dataSources.Find(dataSource => dataSource.Id == dataSourceId);
if (storedDataSource is null || storedDataSource.IsEnterpriseConfiguration)
return false;
IDataSource? editedDataSource = null;
switch (storedDataSource)
{
case DataSourceLocalFile localFile:
var localFileDialogParameters = new DialogParameters<DataSourceLocalFileDialog>
{
{ x => x.IsEditing, true },
{ x => x.DataSource, localFile },
};
var localFileDialogReference = await dialogService.ShowAsync<DataSourceLocalFileDialog>(TB("Edit Local File Data Source"), localFileDialogParameters, DialogOptions.FULLSCREEN);
var localFileDialogResult = await localFileDialogReference.Result;
if (localFileDialogResult is null || localFileDialogResult.Canceled)
return false;
editedDataSource = (DataSourceLocalFile)localFileDialogResult.Data!;
break;
case DataSourceLocalDirectory localDirectory:
var localDirectoryDialogParameters = new DialogParameters<DataSourceLocalDirectoryDialog>
{
{ x => x.IsEditing, true },
{ x => x.DataSource, localDirectory },
};
var localDirectoryDialogReference = await dialogService.ShowAsync<DataSourceLocalDirectoryDialog>(TB("Edit Local Directory Data Source"), localDirectoryDialogParameters, DialogOptions.FULLSCREEN);
var localDirectoryDialogResult = await localDirectoryDialogReference.Result;
if (localDirectoryDialogResult is null || localDirectoryDialogResult.Canceled)
return false;
editedDataSource = (DataSourceLocalDirectory)localDirectoryDialogResult.Data!;
break;
case DataSourceERI_V1 eriDataSource:
var eriDialogParameters = new DialogParameters<DataSourceERI_V1Dialog>
{
{ x => x.IsEditing, true },
{ x => x.DataSource, eriDataSource },
};
var eriDialogReference = await dialogService.ShowAsync<DataSourceERI_V1Dialog>(TB("Edit ERI v1 Data Source"), eriDialogParameters, DialogOptions.FULLSCREEN);
var eriDialogResult = await eriDialogReference.Result;
if (eriDialogResult is null || eriDialogResult.Canceled)
return false;
editedDataSource = (DataSourceERI_V1)eriDialogResult.Data!;
break;
}
if (editedDataSource is null)
return false;
// Found anew, since the list may have changed while the dialog was open:
var position = dataSources.IndexOf(storedDataSource);
if (position < 0)
return false;
dataSources[position] = editedDataSource;
await settingsManager.StoreSettings();
await embeddingService.QueueDataSourceAsync(editedDataSource);
return true;
}
}
@@ -2,6 +2,7 @@ using System.Text;
using AIStudio.Dialogs; using AIStudio.Dialogs;
using AIStudio.Settings; using AIStudio.Settings;
using AIStudio.Settings.DataModel;
using AIStudio.Tools.PluginSystem; using AIStudio.Tools.PluginSystem;
using AIStudio.Tools.Services; using AIStudio.Tools.Services;
@@ -19,6 +20,10 @@ namespace AIStudio.Tools;
/// Nothing is asked when nothing is lost. A data source only reaches the question when the edit /// Nothing is asked when nothing is lost. A data source only reaches the question when the edit
/// really changes its embedding signature and when the index already holds something for it, so /// really changes its embedding signature and when the index already holds something for it, so
/// renaming an embedding provider or editing a data source nobody has indexed yet stays silent. /// renaming an embedding provider or editing a data source nobody has indexed yet stays silent.
/// A new location of a local file or folder has a question of its own, under the same condition of
/// a stored index: the signature stays, but every file which is not found unchanged at the new
/// location is prepared again. When the signature changes as well, only its question is asked,
/// since everything is prepared again anyway.
/// </remarks> /// </remarks>
public static class DataSourceReindexWarning public static class DataSourceReindexWarning
{ {
@@ -84,13 +89,62 @@ public static class DataSourceReindexWarning
DataSourceEmbeddingProviders.TryResolve(settingsManager, before, out var resolvedBeforeProvider); DataSourceEmbeddingProviders.TryResolve(settingsManager, before, out var resolvedBeforeProvider);
var beforeProvider = resolvedBeforeProvider ?? EmbeddingProvider.NONE; var beforeProvider = resolvedBeforeProvider ?? EmbeddingProvider.NONE;
if (!EmbeddingChangeImpact.AffectsStoredIndex(before, beforeProvider, after, afterProvider)) // Everything is prepared again, wherever the files are, so this question covers a new location as well:
return true; if (EmbeddingChangeImpact.AffectsStoredIndex(before, beforeProvider, after, afterProvider))
{
var affected = await embeddingService.GetDataSourcesWithStoredIndexAsync([after], token); var affected = await embeddingService.GetDataSourcesWithStoredIndexAsync([after], token);
return await ConfirmAsync(dialogService, affected, !afterProvider.IsSelfHosted); return await ConfirmAsync(dialogService, affected, !afterProvider.IsSelfHosted);
} }
if (!HasLocationChanged(before, after))
return true;
var indexed = await embeddingService.GetDataSourcesWithStoredIndexAsync([after], token);
return indexed.Count == 0 || await ConfirmNewLocationAsync(dialogService, !afterProvider.IsSelfHosted);
}
private static bool HasLocationChanged(IIndexedDataSource before, IIndexedDataSource after) => (before, after) switch
{
(DataSourceLocalDirectory oldFolder, DataSourceLocalDirectory newFolder) => !string.Equals(oldFolder.Path, newFolder.Path, StringComparison.Ordinal),
(DataSourceLocalFile oldFile, DataSourceLocalFile newFile) => !string.Equals(oldFile.FilePath, newFile.FilePath, StringComparison.Ordinal),
_ => false,
};
/// <summary>
/// Asks before a local file or folder with a stored index is saved with a new location.
/// </summary>
/// <remarks>
/// The conditions for keeping a file are the ones FileSourceIndexer.FindRelocatedFile checks.
/// </remarks>
/// <param name="dialogService">The dialog service to ask with.</param>
/// <param name="usesCloudEmbedding">Whether preparing a file again costs money.</param>
/// <returns>True when the edit may be saved.</returns>
private static async Task<bool> ConfirmNewLocationAsync(IDialogService dialogService, bool usesCloudEmbedding)
{
var body = new StringBuilder();
body.AppendLine(TB("AI Studio keeps what it has already prepared for every file it finds unchanged at the new location, that is, of the same type and with the same size and modification time. In a folder, such a file also has to keep its name and the subfolder it is in."));
body.AppendLine();
body.AppendLine(TB("Every other file goes to your embedding provider once more, which takes a while with many files. Files missing at the new location are removed from the index, while your files on disk stay as they are."));
if (usesCloudEmbedding)
{
body.AppendLine();
body.AppendLine(TB("Your embedding provider runs in the cloud, so preparing files again costs money."));
}
body.AppendLine();
body.AppendLine(TB("Do you want to apply this change anyway?"));
var dialogParameters = new DialogParameters<ConfirmDialog>
{
{ x => x.MarkdownBody, body.ToString() },
};
var dialogReference = await dialogService.ShowAsync<ConfirmDialog>(TB("New Location for This Data Source"), dialogParameters, Dialogs.DialogOptions.FULLSCREEN);
var dialogResult = await dialogReference.Result;
return dialogResult is not null && !dialogResult.Canceled;
}
/// <summary> /// <summary>
/// Names the data sources which would lose their embedding provider, for the deletion question. /// Names the data sources which would lose their embedding provider, for the deletion question.
/// </summary> /// </summary>
@@ -29,6 +29,11 @@ public abstract partial class IndexStoreClient(string name, string path) : Datab
public abstract Task UpsertFileAsync(string dataSourceId, EmbeddingStateFile file, CancellationToken token); public abstract Task UpsertFileAsync(string dataSourceId, EmbeddingStateFile file, CancellationToken token);
/// <summary>
/// Updates the location and fingerprint of a document, preserving its chunks and their ids.
/// </summary>
public abstract Task RelocateFileAsync(string dataSourceId, string parentFileId, string absolutePath, string relativePath, string fingerprint, CancellationToken token);
public abstract Task DeleteFileAsync(string dataSourceId, string filePath, CancellationToken token); public abstract Task DeleteFileAsync(string dataSourceId, string filePath, CancellationToken token);
public abstract Task UpsertPermanentFailureAsync(string dataSourceId, PermanentIndexingFailure failure, CancellationToken token); public abstract Task UpsertPermanentFailureAsync(string dataSourceId, PermanentIndexingFailure failure, CancellationToken token);
@@ -52,6 +52,9 @@ public sealed partial class NoIndexStoreClient(string name, string? unavailableR
public override Task UpsertFileAsync(string dataSourceId, EmbeddingStateFile file, CancellationToken token) => Task.CompletedTask; public override Task UpsertFileAsync(string dataSourceId, EmbeddingStateFile file, CancellationToken token) => Task.CompletedTask;
public override Task RelocateFileAsync(string dataSourceId, string parentFileId, string absolutePath, string relativePath, string fingerprint, CancellationToken token) =>
Task.FromException(new InvalidOperationException(unavailableReason ?? "The index of the data sources is not available."));
public override Task DeleteFileAsync(string dataSourceId, string filePath, CancellationToken token) => Task.CompletedTask; public override Task DeleteFileAsync(string dataSourceId, string filePath, CancellationToken token) => Task.CompletedTask;
public override Task UpsertPermanentFailureAsync(string dataSourceId, PermanentIndexingFailure failure, CancellationToken token) => Task.CompletedTask; public override Task UpsertPermanentFailureAsync(string dataSourceId, PermanentIndexingFailure failure, CancellationToken token) => Task.CompletedTask;
@@ -149,7 +149,9 @@ public sealed partial class SqliteIndexStoreClientImplementation(string name, st
file.LastWriteUtc, file.LastWriteUtc,
file.EmbeddedAtUtc, file.EmbeddedAtUtc,
file.ChunkCount, file.ChunkCount,
file.TranscriptKey); file.TranscriptKey,
file.RelativePath,
file.ParentFileId);
} }
var permanentFailures = await context.PermanentIndexingFailures var permanentFailures = await context.PermanentIndexingFailures
@@ -233,6 +235,18 @@ public sealed partial class SqliteIndexStoreClientImplementation(string name, st
await context.SaveChangesAsync(token); await context.SaveChangesAsync(token);
} }
public override async Task RelocateFileAsync(string dataSourceId, string parentFileId, string absolutePath, string relativePath, string fingerprint, CancellationToken token)
{
await using var context = this.CreateContext();
var file = await context.EmbeddedFiles.SingleAsync(file => file.DataSourceId == dataSourceId && file.ParentFileId == parentFileId, token);
// Keep the parent and chunk ids: moving a document does not change its stored vectors.
file.AbsolutePath = absolutePath;
file.RelativePath = relativePath;
file.FileName = Path.GetFileName(absolutePath);
file.Fingerprint = fingerprint;
await context.SaveChangesAsync(token);
}
public override async Task DeleteFileAsync(string dataSourceId, string filePath, CancellationToken token) public override async Task DeleteFileAsync(string dataSourceId, string filePath, CancellationToken token)
{ {
await using var context = this.CreateContext(); await using var context = this.CreateContext();
@@ -37,7 +37,10 @@ public sealed class NoVectorStoreClient(string name, string? unavailableReason,
public override Task<IReadOnlyList<VectorSearchResult>> SearchEmbeddingAsync(string storeName, IReadOnlyList<float> vector, int maxMatches, VectorSearchFilter filter, CancellationToken token) => public override Task<IReadOnlyList<VectorSearchResult>> SearchEmbeddingAsync(string storeName, IReadOnlyList<float> vector, int maxMatches, VectorSearchFilter filter, CancellationToken token) =>
Task.FromException<IReadOnlyList<VectorSearchResult>>(this.CreateUnavailableException()); Task.FromException<IReadOnlyList<VectorSearchResult>>(this.CreateUnavailableException());
public override Task DeleteEmbeddingByFile(string storeName, string filePath, CancellationToken token) => public override Task DeleteEmbeddingByFile(string storeName, string filePath, string parentFileId, CancellationToken token) =>
Task.FromException(this.CreateUnavailableException());
public override Task RelocateEmbeddingFileAsync(string storeName, string parentFileId, string absolutePath, string relativePath, string fingerprint, CancellationToken token) =>
Task.FromException(this.CreateUnavailableException()); Task.FromException(this.CreateUnavailableException());
public override Task OptimizeVectorStore(string storeName, CancellationToken token) => public override Task OptimizeVectorStore(string storeName, CancellationToken token) =>
@@ -27,6 +27,7 @@ public sealed class QdrantEdgeClientImplementation(
private const string INSERT_PATH = "/system/qdrant-edge/insert"; private const string INSERT_PATH = "/system/qdrant-edge/insert";
private const string SEARCH_PATH = "/system/qdrant-edge/search"; private const string SEARCH_PATH = "/system/qdrant-edge/search";
private const string DELETE_FILE_PATH = "/system/qdrant-edge/delete-file"; private const string DELETE_FILE_PATH = "/system/qdrant-edge/delete-file";
private const string RELOCATE_FILE_PATH = "/system/qdrant-edge/relocate-file";
private const string OPTIMIZE_PATH = "/system/qdrant-edge/optimize"; private const string OPTIMIZE_PATH = "/system/qdrant-edge/optimize";
private const string DELETE_STORE_PATH = "/system/qdrant-edge/delete-store"; private const string DELETE_STORE_PATH = "/system/qdrant-edge/delete-store";
private const string PREPARE_BACKUP_PATH = "/system/qdrant-edge/backup/prepare"; private const string PREPARE_BACKUP_PATH = "/system/qdrant-edge/backup/prepare";
@@ -156,8 +157,11 @@ public sealed class QdrantEdgeClientImplementation(
token) ?? []; token) ?? [];
} }
public override Task DeleteEmbeddingByFile(string storeName, string filePath, CancellationToken token) => public override Task DeleteEmbeddingByFile(string storeName, string filePath, string parentFileId, CancellationToken token) =>
rustService.ExecuteDatabaseOperation(DATABASE_NAME, DELETE_FILE_PATH, new DeleteEmbeddingByFileRequest(storeName, filePath), token); rustService.ExecuteDatabaseOperation(DATABASE_NAME, DELETE_FILE_PATH, new DeleteEmbeddingByFileRequest(storeName, filePath, parentFileId), token);
public override Task RelocateEmbeddingFileAsync(string storeName, string parentFileId, string absolutePath, string relativePath, string fingerprint, CancellationToken token) =>
rustService.ExecuteDatabaseOperation(DATABASE_NAME, RELOCATE_FILE_PATH, new RelocateEmbeddingFileRequest(storeName, parentFileId, absolutePath, Path.GetFileName(absolutePath), relativePath, fingerprint), token);
public override Task OptimizeVectorStore(string storeName, CancellationToken token) => public override Task OptimizeVectorStore(string storeName, CancellationToken token) =>
rustService.ExecuteDatabaseOperation(DATABASE_NAME, OPTIMIZE_PATH, new OptimizeVectorStoreRequest(storeName), token); rustService.ExecuteDatabaseOperation(DATABASE_NAME, OPTIMIZE_PATH, new OptimizeVectorStoreRequest(storeName), token);
@@ -194,7 +198,9 @@ public sealed class QdrantEdgeClientImplementation(
/// <param name="PointIds">The only points the search may return, or null for the whole store.</param> /// <param name="PointIds">The only points the search may return, or null for the whole store.</param>
private sealed record SearchEmbeddingRequest(string StoreName, IReadOnlyList<float> Vector, int MaxMatches, IReadOnlyList<string>? PointIds); private sealed record SearchEmbeddingRequest(string StoreName, IReadOnlyList<float> Vector, int MaxMatches, IReadOnlyList<string>? PointIds);
private sealed record DeleteEmbeddingByFileRequest(string StoreName, string FilePath); private sealed record DeleteEmbeddingByFileRequest(string StoreName, string FilePath, string ParentFileId);
private sealed record RelocateEmbeddingFileRequest(string StoreName, string ParentFileId, string AbsolutePath, string FileName, string RelativePath, string Fingerprint);
private sealed record OptimizeVectorStoreRequest(string StoreName); private sealed record OptimizeVectorStoreRequest(string StoreName);
@@ -27,7 +27,12 @@ public abstract class VectorStoreClient(string name, string path): DatabaseClien
/// <returns>The matches, best first, all of them inside the filter.</returns> /// <returns>The matches, best first, all of them inside the filter.</returns>
public abstract Task<IReadOnlyList<VectorSearchResult>> SearchEmbeddingAsync(string storeName, IReadOnlyList<float> vector, int maxMatches, VectorSearchFilter filter, CancellationToken token); public abstract Task<IReadOnlyList<VectorSearchResult>> SearchEmbeddingAsync(string storeName, IReadOnlyList<float> vector, int maxMatches, VectorSearchFilter filter, CancellationToken token);
public abstract Task DeleteEmbeddingByFile(string storeName, string filePath, CancellationToken token); public abstract Task DeleteEmbeddingByFile(string storeName, string filePath, string parentFileId, CancellationToken token);
/// <summary>
/// Updates the location of a document's points without changing their ids or vectors.
/// </summary>
public abstract Task RelocateEmbeddingFileAsync(string storeName, string parentFileId, string absolutePath, string relativePath, string fingerprint, CancellationToken token);
public abstract Task OptimizeVectorStore(string storeName, CancellationToken token); public abstract Task OptimizeVectorStore(string storeName, CancellationToken token);
@@ -43,7 +43,7 @@ public static class MailboxEditing
{ {
{ x => x.IsEditing, true }, { x => x.IsEditing, true },
{ x => x.DataSource, storedMailbox }, { x => x.DataSource, storedMailbox },
{ x => x.LockSource, await embeddingService.ShouldLockDataSourceOriginAsync(storedMailbox.Id) }, { x => x.LockSource, await embeddingService.ShouldLockMailboxOriginAsync(storedMailbox.Id) },
}; };
var dialogReference = await dialogService.ShowAsync<DataSourceMailboxDialog>(TB("Edit Mailbox"), dialogParameters, DialogOptions.FULLSCREEN); var dialogReference = await dialogService.ShowAsync<DataSourceMailboxDialog>(TB("Edit Mailbox"), dialogParameters, DialogOptions.FULLSCREEN);
+27 -1
View File
@@ -1,7 +1,7 @@
namespace AIStudio.Tools; namespace AIStudio.Tools;
/// <summary> /// <summary>
/// Shared rules for comparing file system paths. /// Shared rules for comparing and resolving file system paths.
/// </summary> /// </summary>
public static class PathTools public static class PathTools
{ {
@@ -42,4 +42,30 @@ public static class PathTools
return false; return false;
} }
} }
/// <summary>
/// Finds the place where a file dialog opens for a path which may no longer exist.
/// </summary>
/// <remarks>
/// Given a file or a folder which was moved or renamed, the native dialog cannot open its place
/// and falls back to wherever it was used last. The nearest folder which still exists is the
/// better start: the file or the folder was moved from there, so its new place is often close by.
/// A relative path, or a path of another operating system, e.g. from a backup, has no such folder.
/// </remarks>
/// <param name="path">The file or the folder the dialog should open at.</param>
/// <returns>The path itself when it exists, else its nearest parent folder which exists, or null when there is none.</returns>
public static string? FindNearestExistingPath(string path)
{
if (string.IsNullOrWhiteSpace(path) || !Path.IsPathFullyQualified(path))
return null;
if (File.Exists(path) || Directory.Exists(path))
return path;
for (var directory = Path.GetDirectoryName(path); !string.IsNullOrEmpty(directory); directory = Path.GetDirectoryName(directory))
if (Directory.Exists(directory))
return directory;
return null;
}
} }
@@ -148,6 +148,20 @@ public static class ConfigurationSnippetImport
if (missingChatToolIds.Count > 0) if (missingChatToolIds.Count > 0)
hints.Add(string.Format(TB("The chat template preselects tools which are not available on this computer: {0}."), string.Join(", ", missingChatToolIds))); hints.Add(string.Format(TB("The chat template preselects tools which are not available on this computer: {0}."), string.Join(", ", missingChatToolIds)));
if (template.ProviderId is not null)
{
var templateProvider = settingsManager.GetProviderById(template.ProviderId);
if (templateProvider == Settings.Provider.NONE)
hints.Add(string.Format(TB("The chat template preselects a provider which does not exist on this computer: {0}. Chats started with it use your usual provider instead."), template.ProviderId));
else if (settingsManager.GetChatTemplateProvider(template.ProviderId) == Settings.Provider.NONE)
hints.Add(string.Format(TB("The chat template preselects the provider '{0}', which does not meet the confidence level required for chats. Chats started with it use your usual provider instead."), templateProvider.InstanceName));
}
if (template.ProfileId is not null && !template.AllowProfileUsage)
hints.Add(TB("The chat template preselects a profile but does not allow profiles. Chats started with it ignore that profile."));
else if (template.ProfileId is not null && !template.StartsWithoutProfile() && settingsManager.GetProfileById(template.ProfileId) == Profile.NO_PROFILE)
hints.Add(string.Format(TB("The chat template preselects a profile which does not exist on this computer: {0}. Chats started with it use your usual profile instead."), template.ProfileId));
return result with { ConfigurationObject = template with { ToolIds = templateToolIds }, Hints = hints }; return result with { ConfigurationObject = template with { ToolIds = templateToolIds }, Hints = hints };
case DataDocumentAnalysisPolicy policy: case DataDocumentAnalysisPolicy policy:
@@ -331,6 +331,9 @@ public sealed class PluginConfiguration(bool isInternal, LuaState state, PluginT
// Config: hide some assistants? // Config: hide some assistants?
ManagedConfiguration.TryProcessConfiguration(x => x.App, x => x.HiddenAssistants, this.Id, settingsTable, dryRun); ManagedConfiguration.TryProcessConfiguration(x => x.App, x => x.HiddenAssistants, this.Id, settingsTable, dryRun);
// Config: prohibit some providers?
ManagedConfiguration.TryProcessConfiguration(x => x.App, x => x.ProhibitedProviders, this.Id, settingsTable, dryRun);
// Config: global voice recording shortcut // Config: global voice recording shortcut
ManagedConfiguration.TryProcessConfiguration(x => x.App, x => x.ShortcutVoiceRecording, this.Id, settingsTable, dryRun); ManagedConfiguration.TryProcessConfiguration(x => x.App, x => x.ShortcutVoiceRecording, this.Id, settingsTable, dryRun);
@@ -474,6 +477,13 @@ public sealed class PluginConfiguration(bool isInternal, LuaState state, PluginT
ManagedConfiguration.TryProcessConfiguration(x => x.VisualBriefing, x => x.OptimizeImages, this.Id, settingsTable, dryRun); ManagedConfiguration.TryProcessConfiguration(x => x.VisualBriefing, x => x.OptimizeImages, this.Id, settingsTable, dryRun);
ManagedConfiguration.TryProcessConfiguration(x => x.VisualBriefing, x => x.MinimumProviderConfidence, this.Id, settingsTable, dryRun); ManagedConfiguration.TryProcessConfiguration(x => x.VisualBriefing, x => x.MinimumProviderConfidence, this.Id, settingsTable, dryRun);
// Config: Assistant Builder defaults?
ManagedConfiguration.TryProcessConfiguration(x => x.AssistantBuilder, x => x.PreselectOptions, this.Id, settingsTable, dryRun);
ManagedConfiguration.TryProcessConfiguration(x => x.AssistantBuilder, x => x.PreselectedProvider, Guid.Empty, this.Id, settingsTable, dryRun);
ManagedConfiguration.TryProcessConfiguration(x => x.AssistantBuilder, x => x.PreselectedOutputLanguage, this.Id, settingsTable, dryRun);
ManagedConfiguration.TryProcessConfiguration(x => x.AssistantBuilder, x => x.PreselectedOtherOutputLanguage, this.Id, settingsTable, dryRun);
ManagedConfiguration.TryProcessConfiguration(x => x.AssistantBuilder, x => x.MinimumProviderConfidence, this.Id, settingsTable, dryRun);
// Config: transcription provider? // Config: transcription provider?
ManagedConfiguration.TryProcessConfiguration(x => x.App, x => x.UseTranscriptionProvider, Guid.Empty, this.Id, settingsTable, dryRun); ManagedConfiguration.TryProcessConfiguration(x => x.App, x => x.UseTranscriptionProvider, Guid.Empty, this.Id, settingsTable, dryRun);
@@ -333,6 +333,10 @@ public static partial class PluginFactory
if(ManagedConfiguration.CleanupLeftOverManagedConfigurations(AVAILABLE_PLUGINS, deployedEnterpriseConfigPluginIds)) if(ManagedConfiguration.CleanupLeftOverManagedConfigurations(AVAILABLE_PLUGINS, deployedEnterpriseConfigPluginIds))
wasConfigurationChanged = true; wasConfigurationChanged = true;
// Every configuration plugin has run and the left-overs are gone, so the result no longer
// depends on the order in which the plugins ran:
LogConfidenceLevelsOutrankedByProhibitions();
// //
// The enterprise approvals of all configuration plugins add up. Now that every plugin has // The enterprise approvals of all configuration plugins add up. Now that every plugin has
// contributed and the clean-up above has dropped the removed ones, we rebuild the effective // contributed and the clean-up above has dropped the removed ones, we rebuild the effective
@@ -365,6 +369,28 @@ public static partial class PluginFactory
FIRST_LOAD_COMPLETED.TrySetResult(); FIRST_LOAD_COMPLETED.TrySetResult();
} }
/// <summary>
/// Logs the confidence levels which a configuration plugin chose for prohibited providers.
/// </summary>
/// <remarks>
/// A prohibited provider is untrusted, whatever level the custom confidence scheme names for it, see
/// SettingsManager.GetConfiguredConfidenceLevel. Such a level contradicts the prohibition, so we tell
/// the organization that it is ignored. Levels the user chose are ignored the same way, without a log.
/// </remarks>
private static void LogConfidenceLevelsOutrankedByProhibitions()
{
if (!ManagedConfiguration.TryGet(x => x.Confidence, x => x.CustomConfidenceScheme, out var configMeta))
return;
var owningConfigPluginId = ManagedConfiguration.GetSettingOwner(configMeta);
if (owningConfigPluginId == Guid.Empty)
return;
var customConfidenceScheme = SettingsManagerAccess.ConfigurationData.Confidence.CustomConfidenceScheme;
foreach (var llmProvider in SettingsManagerAccess.GetProhibitedProvidersWithCustomConfidenceLevel())
LOG.LogWarning($"The configuration plugin '{owningConfigPluginId}' sets the confidence level '{customConfidenceScheme[llmProvider]}' for the provider '{llmProvider}' in the custom confidence scheme. Ignoring it: your organization prohibited this provider, so it is untrusted.");
}
/// <summary> /// <summary>
/// Determines the IDs of all configuration plugins which an organization deployed on this machine. /// Determines the IDs of all configuration plugins which an organization deployed on this machine.
/// </summary> /// </summary>
@@ -1,6 +1,5 @@
using System.Text; using System.Text;
using AIStudio.Chat;
using AIStudio.Tools.Security; using AIStudio.Tools.Security;
namespace AIStudio.Tools.RAG; namespace AIStudio.Tools.RAG;
+13 -1
View File
@@ -98,7 +98,19 @@ public static class FileTypes
// Media hierarchy // Media hierarchy
public static readonly FileTypeFilter IMAGE = FileTypeFilter.Leaf(TB("Image"), public static readonly FileTypeFilter IMAGE = FileTypeFilter.Leaf(TB("Image"),
"jpg", "jpeg", "png", "gif", "bmp", "tiff", "svg", "webp", "heic", "avif"); "jpg", "jpeg", "png", "gif", "bmp", "tif", "tiff", "svg", "webp", "heic", "avif");
/// <summary>
/// The image formats AI Studio can prepare for a model.
/// </summary>
/// <remarks>
/// These are the formats the runtime decodes before it sends an image to a model as JPEG or
/// PNG, see prepare_image_for_model in runtime/src/image.rs. Other images, such as HEIC, AVIF,
/// or SVG, cannot go to a model at all. The default file patterns of the batch processing
/// assistant list the same formats, see DataBatchProcessing.DEFAULT_FILE_PATTERNS.
/// </remarks>
public static readonly FileTypeFilter MODEL_IMAGE = FileTypeFilter.Leaf(TB("Image for AI models"),
"jpg", "jpeg", "png", "webp", "gif", "bmp", "tif", "tiff");
/// <summary> /// <summary>
/// Gets the prototype visual-asset image formats. /// Gets the prototype visual-asset image formats.
@@ -0,0 +1,63 @@
namespace AIStudio.Tools.Rust;
/// <summary>
/// Why the Rust runtime could not prepare an image for an AI model.
/// </summary>
/// <remarks>
/// UNKNOWN comes first on purpose. The converter for the enums of the runtime falls back to the
/// default value for a code it does not know, e.g., from a newer runtime, and such a code must
/// never read as a prepared image.
/// </remarks>
public enum ModelImageErrorCode
{
/// <summary>
/// The runtime answered with a code this version does not know, or it could not be asked at all.
/// </summary>
UNKNOWN,
/// <summary>
/// No failure happened: the image was prepared.
/// </summary>
NONE,
//
// Codes reported by the Rust runtime:
//
FILE_NOT_FOUND,
/// <summary>
/// The file exists, but could not be opened, e.g., for lack of permission.
/// </summary>
FILE_NOT_READABLE,
/// <summary>
/// AI Studio cannot decode this image format, e.g., HEIC, AVIF, or SVG.
/// </summary>
UNSUPPORTED_FORMAT,
/// <summary>
/// The content of the file does not match its file extension.
/// </summary>
CONTENT_MISMATCH,
/// <summary>
/// The file is damaged or uses a variant of its format which cannot be decoded.
/// </summary>
NOT_DECODABLE,
/// <summary>
/// Decoding the image would need more memory than the runtime allows.
/// </summary>
TOO_MANY_PIXELS,
/// <summary>
/// Not even the smallest size and lowest quality the runtime accepts fit into the byte budget.
/// </summary>
BUDGET_NOT_REACHABLE,
/// <summary>
/// Any failure in the runtime which does not carry a code of its own.
/// </summary>
INTERNAL,
}
@@ -0,0 +1,36 @@
using AIStudio.Tools.PluginSystem;
namespace AIStudio.Tools.Rust;
/// <summary>
/// Translates why an image could not be prepared for an AI model into user-facing text.
/// </summary>
/// <remarks>
/// The runtime writes the technical details into the log. The texts here name what the user can
/// act on, such as converting the image into another format.
/// </remarks>
public static class ModelImageErrorCodeExtensions
{
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(ModelImageErrorCodeExtensions).Namespace, nameof(ModelImageErrorCodeExtensions));
/// <summary>
/// Gets the localized message which explains why an image was not sent to the AI.
/// </summary>
/// <param name="code">Why the image could not be prepared.</param>
/// <param name="fileName">The name of the image file, as shown to the user.</param>
/// <returns>The localized message.</returns>
public static string ToUserMessage(this ModelImageErrorCode code, string fileName) => string.Format(ToUserMessageFormat(code), fileName);
private static string ToUserMessageFormat(ModelImageErrorCode code) => code switch
{
ModelImageErrorCode.FILE_NOT_FOUND => TB("The image '{0}' does not exist anymore and was not sent."),
ModelImageErrorCode.FILE_NOT_READABLE => TB("The image '{0}' could not be opened and was not sent. When the image is stored on a network drive, the drive might be unavailable."),
ModelImageErrorCode.UNSUPPORTED_FORMAT => TB("The image '{0}' uses a format which AI Studio cannot give to an AI model, so it was not sent. Please convert the image to JPG or PNG."),
ModelImageErrorCode.CONTENT_MISMATCH => TB("The image '{0}' is not what its file extension claims, so it was not sent. Please correct its file extension."),
ModelImageErrorCode.NOT_DECODABLE => TB("The image '{0}' could not be read and was not sent. It might be damaged or transferred incompletely."),
ModelImageErrorCode.TOO_MANY_PIXELS => TB("The image '{0}' has too many pixels for AI Studio to prepare it, so it was not sent. Please scale the image down first."),
ModelImageErrorCode.BUDGET_NOT_REACHABLE => TB("The image '{0}' could not be made small enough for the selected provider, so it was not sent."),
_ => TB("AI Studio was not able to prepare the image '{0}' for the AI, so it was not sent."),
};
}
@@ -0,0 +1,39 @@
namespace AIStudio.Tools.Rust;
/// <summary>
/// An image as the Rust runtime prepared it for an AI model, or why it could not.
/// </summary>
/// <remarks>
/// The base64 and its MIME type travel side by side, so a provider which wants a data URL builds
/// one and a provider which wants them apart, such as Anthropic, never parses one.
/// </remarks>
/// <param name="ErrorCode">NONE when the image was prepared; then the other values describe it.</param>
/// <param name="Base64">The encoded image as base64, without a data URI prefix.</param>
/// <param name="MimeType">The MIME type of the encoded image: image/jpeg or image/png.</param>
/// <param name="Width">The width of the prepared image in pixels.</param>
/// <param name="Height">The height of the prepared image in pixels.</param>
/// <param name="WasResized">Whether the runtime scaled the image down.</param>
public sealed record PreparedModelImage(ModelImageErrorCode ErrorCode, string Base64, string MimeType, uint Width, uint Height, bool WasResized)
{
/// <summary>
/// Whether the image can go to the model.
/// </summary>
/// <remarks>
/// The image itself has to be there as well: a success without one would become an empty image
/// in the request, which providers refuse along with the whole request.
/// </remarks>
public bool IsPrepared => this.ErrorCode is ModelImageErrorCode.NONE && !string.IsNullOrEmpty(this.Base64);
/// <summary>
/// The image as a data URL, as the OpenAI-compatible APIs take it.
/// </summary>
/// <returns>The data URL.</returns>
public string ToDataUrl() => $"data:{this.MimeType};base64,{this.Base64}";
/// <summary>
/// An image which could not be prepared.
/// </summary>
/// <param name="errorCode">Why it could not be prepared.</param>
/// <returns>The failed image.</returns>
public static PreparedModelImage Failed(ModelImageErrorCode errorCode) => new(errorCode, string.Empty, string.Empty, 0, 0, false);
}
@@ -0,0 +1,10 @@
using AIStudio.Settings.DataModel;
namespace AIStudio.Tools.Services;
/// <summary>
/// The data source options a chat started by an assistant chat launcher begins with.
/// </summary>
/// <param name="Options">The checked options of the chat, or null when the chosen data sources cannot be used.</param>
/// <param name="ErrorMessage">Why no chat can be created with the chosen data sources, or an empty string when it can.</param>
internal readonly record struct ChatLauncherDataSourcesResult(DataSourceOptions? Options, string ErrorMessage);
@@ -0,0 +1,10 @@
using AIStudio.Settings;
namespace AIStudio.Tools.Services;
/// <summary>
/// The profile a chat started by an assistant chat launcher begins with.
/// </summary>
/// <param name="Profile">The profile of the chat, or null when the profile the launcher names does not exist.</param>
/// <param name="ErrorMessage">Why no profile could be chosen, or an empty string when one was.</param>
internal readonly record struct ChatLauncherProfileResult(Profile? Profile, string ErrorMessage);
@@ -0,0 +1,11 @@
using ProviderSettings = AIStudio.Settings.Provider;
namespace AIStudio.Tools.Services;
/// <summary>
/// The provider a chat started by an assistant chat launcher begins with.
/// </summary>
/// <param name="Provider">The provider of the chat, or NONE when there is none to start with.</param>
/// <param name="IsExplicit">Whether the chat template or the launcher named this provider, as opposed to the chat defaults.</param>
/// <param name="ErrorMessage">Why the named provider cannot be used, or an empty string when it can.</param>
internal readonly record struct ChatLauncherProviderResult(ProviderSettings Provider, bool IsExplicit, string ErrorMessage);
@@ -0,0 +1,10 @@
using AIStudio.Settings;
namespace AIStudio.Tools.Services;
/// <summary>
/// The chat template a chat started by an assistant chat launcher begins with.
/// </summary>
/// <param name="ChatTemplate">The chat template of the chat, or null when the template the launcher names does not exist.</param>
/// <param name="ErrorMessage">Why no chat template could be chosen, or an empty string when one was.</param>
internal readonly record struct ChatLauncherTemplateResult(ChatTemplate? ChatTemplate, string ErrorMessage);
@@ -24,4 +24,9 @@ public enum DataSourceAttention
/// The organization allows only its own mail servers, and the mailbox is on another one. AI Studio does not connect to it until the organization allows the server. /// The organization allows only its own mail servers, and the mailbox is on another one. AI Studio does not connect to it until the organization allows the server.
/// </summary> /// </summary>
SERVER_NOT_ALLOWED, SERVER_NOT_ALLOWED,
/// <summary>
/// The file or the folder of a local data source cannot be reached, cannot be opened, or appears empty. AI Studio keeps the index until the file or the folder is back, the user selects its new location, or removes the data source.
/// </summary>
LOCATION_UNREACHABLE,
} }
@@ -1,7 +1,27 @@
namespace AIStudio.Tools.Services; namespace AIStudio.Tools.Services;
/// <summary>
/// What the index store holds for one data source, read at the start of a run.
/// </summary>
public sealed class DataSourceEmbeddingManifest public sealed class DataSourceEmbeddingManifest
{ {
/// <summary>
/// How the index compares the paths of the documents it stores, on every OS.
/// </summary>
/// <remarks>
/// Ignores case, unlike PathTools.COMPARER, which follows the file system. The index store keeps
/// the paths with the NOCASE collation and allows each path of a data source once, so two files
/// which differ in their case alone share one row there. Whatever the indexer keys or compares by
/// these paths has to agree with the store; telling the two files apart would let them replace
/// each other's row on every run.
/// </remarks>
public static readonly StringComparer PATH_COMPARER = StringComparer.OrdinalIgnoreCase;
/// <summary>
/// The comparison matching PATH_COMPARER, e.g., for prefixes.
/// </summary>
public const StringComparison PATH_COMPARISON = StringComparison.OrdinalIgnoreCase;
public string EmbeddingProviderId { get; set; } = string.Empty; public string EmbeddingProviderId { get; set; } = string.Empty;
public string EmbeddingSignature { get; set; } = string.Empty; public string EmbeddingSignature { get; set; } = string.Empty;
@@ -10,7 +30,7 @@ public sealed class DataSourceEmbeddingManifest
public int VectorSize { get; set; } public int VectorSize { get; set; }
public Dictionary<string, EmbeddedFileRecord> Files { get; init; } = new(StringComparer.OrdinalIgnoreCase); public Dictionary<string, EmbeddedFileRecord> Files { get; init; } = new(PATH_COMPARER);
/// <summary> /// <summary>
/// The files whose indexing began but never came to an end, by their absolute path. /// The files whose indexing began but never came to an end, by their absolute path.
@@ -21,7 +41,7 @@ public sealed class DataSourceEmbeddingManifest
/// meanwhile. Indexing such a file again replaces both. A file which is gone, or which the data /// meanwhile. Indexing such a file again replaces both. A file which is gone, or which the data
/// source no longer selects, is never indexed again, so the indexer has to remove them itself. /// source no longer selects, is never indexed again, so the indexer has to remove them itself.
/// </remarks> /// </remarks>
public HashSet<string> UnfinishedFiles { get; init; } = new(StringComparer.OrdinalIgnoreCase); public HashSet<string> UnfinishedFiles { get; init; } = new(PATH_COMPARER);
/// <summary> /// <summary>
/// The files whose indexing failed for a reason which lies in the file itself, keyed by their /// The files whose indexing failed for a reason which lies in the file itself, keyed by their
@@ -32,5 +52,5 @@ public sealed class DataSourceEmbeddingManifest
/// a folder holding hundreds of scanned documents without a text layer would be read again on /// a folder holding hundreds of scanned documents without a text layer would be read again on
/// every single run, with the outcome known in advance. /// every single run, with the outcome known in advance.
/// </remarks> /// </remarks>
public Dictionary<string, PermanentIndexingFailureRecord> PermanentFailures { get; init; } = new(StringComparer.OrdinalIgnoreCase); public Dictionary<string, PermanentIndexingFailureRecord> PermanentFailures { get; init; } = new(PATH_COMPARER);
} }
@@ -7,12 +7,30 @@ namespace AIStudio.Tools.Services;
internal static class DataSourceEmbeddingProviders internal static class DataSourceEmbeddingProviders
{ {
/// <summary>
/// Finds the embedding provider of a data source, when AI Studio may use it.
/// </summary>
/// <remarks>
/// A provider the organization prohibited counts as missing: nothing may be embedded with it,
/// neither the documents of the data source nor a query against its index.
/// </remarks>
public static bool TryResolve(SettingsManager settingsManager, IDataSourceBase dataSource, [NotNullWhen(true)] out EmbeddingProvider? embeddingProvider) public static bool TryResolve(SettingsManager settingsManager, IDataSourceBase dataSource, [NotNullWhen(true)] out EmbeddingProvider? embeddingProvider)
{ {
embeddingProvider = settingsManager.ConfigurationData.EmbeddingProviders.FirstOrDefault(provider => embeddingProvider = Find(settingsManager, dataSource);
return embeddingProvider is { UsedLLMProvider: not LLMProviders.NONE } && !settingsManager.IsProviderProhibited(embeddingProvider.UsedLLMProvider);
}
/// <summary>
/// Checks whether the organization prohibited the embedding provider of a data source.
/// </summary>
/// <remarks>
/// TryResolve fails for such a provider as well, but without the reason. The status of a data
/// source needs the reason to tell the user what to change.
/// </remarks>
public static bool IsProhibited(SettingsManager settingsManager, IDataSourceBase dataSource)
=> Find(settingsManager, dataSource) is { } embeddingProvider && settingsManager.IsProviderProhibited(embeddingProvider.UsedLLMProvider);
private static EmbeddingProvider? Find(SettingsManager settingsManager, IDataSourceBase dataSource) => settingsManager.ConfigurationData.EmbeddingProviders.FirstOrDefault(provider =>
dataSource is IIndexedDataSource indexedDataSource && dataSource is IIndexedDataSource indexedDataSource &&
provider.Id.Equals(indexedDataSource.EmbeddingId, StringComparison.OrdinalIgnoreCase)); provider.Id.Equals(indexedDataSource.EmbeddingId, StringComparison.OrdinalIgnoreCase));
return embeddingProvider != default && embeddingProvider.UsedLLMProvider is not LLMProviders.NONE;
}
} }
@@ -158,14 +158,18 @@ public sealed partial class DataSourceEmbeddingService
/// files which no longer belong to the data source, and the ones which now do have to wait for /// files which no longer belong to the data source, and the ones which now do have to wait for
/// the next run. /// the next run.
/// ///
/// Only a local folder chooses its documents by file patterns, exclusions, switches, and a size /// Local files and folders include their configured paths, so relocating one stops a run which
/// limit. For every other kind of data source, this is empty. /// still reads the old location. Folders also include their file selection. Neither changes the
/// embedding signature: unchanged documents keep their vectors.
/// </remarks> /// </remarks>
/// <param name="dataSource">The data source to index.</param> /// <param name="dataSource">The data source to index.</param>
/// <returns>The description of its selection, or an empty string.</returns> /// <returns>The description of its selection, or an empty string.</returns>
internal static string BuildSelectionSignature(IIndexedDataSource dataSource) => dataSource is DataSourceLocalDirectory directory internal static string BuildSelectionSignature(IIndexedDataSource dataSource) => dataSource switch
? FolderFileSelection.Of(directory).Describe() {
: string.Empty; DataSourceLocalDirectory directory => $"{directory.Path.Length}:{directory.Path}|{FolderFileSelection.Of(directory).Describe()}",
DataSourceLocalFile file => $"{file.FilePath.Length}:{file.FilePath}",
_ => string.Empty,
};
/// <summary> /// <summary>
/// Describes the indexing run a data source would get with a given embedding provider. /// Describes the indexing run a data source would get with a given embedding provider.
@@ -190,24 +190,20 @@ public sealed partial class DataSourceEmbeddingService(SettingsManager settingsM
} }
/// <summary> /// <summary>
/// Whether the file or folder a data source reads must stay as it is. /// Whether a mailbox's origin must stay as it is.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// Locked as soon as the index holds anything, because where a data source reads from is what it /// Locked as soon as the index holds anything: another server, account, or folder holds other
/// is: another folder is another data source, and the path reaches no signature, so swapping it /// mails, which makes it another data source. Local files and folders are not locked, since they
/// would leave the stored index describing documents nobody points at any more. /// may move, and the file indexer finds their documents again, cf. FileSourceIndexer.RelocateStoredFilesAsync.
/// /// The embedding provider is never locked; DataSourceReindexWarning asks what changing it costs.
/// The embedding provider used to be locked along with it and no longer is. It does reach the
/// signature, so changing it rebuilds the index cleanly -- and DataSourceReindexWarning asks
/// before it does. Locking it as well left a data source whose provider was deleted stuck on
/// keyword search for good, with no way back.
/// ///
/// Unclear counts as locked: an unavailable index database says nothing about what is stored. /// Unclear counts as locked: an unavailable index database says nothing about what is stored.
/// </remarks> /// </remarks>
/// <param name="dataSourceId">The data source to ask about.</param> /// <param name="dataSourceId">The mailbox to ask about.</param>
/// <param name="token">The cancellation token.</param> /// <param name="token">The cancellation token.</param>
/// <returns>True when the source must not be changed.</returns> /// <returns>True when the source must not be changed.</returns>
public async Task<bool> ShouldLockDataSourceOriginAsync(string dataSourceId, CancellationToken token = default) public async Task<bool> ShouldLockMailboxOriginAsync(string dataSourceId, CancellationToken token = default)
{ {
var indexStore = await databaseClientProvider.GetIndexStoreAsync(token); var indexStore = await databaseClientProvider.GetIndexStoreAsync(token);
if (!indexStore.IsAvailable) if (!indexStore.IsAvailable)
@@ -805,7 +801,9 @@ public sealed partial class DataSourceEmbeddingService(SettingsManager settingsM
if (!this.TryResolveEmbeddingProvider(dataSource, out var embeddingProvider)) if (!this.TryResolveEmbeddingProvider(dataSource, out var embeddingProvider))
{ {
token.ThrowIfCancellationRequested(); token.ThrowIfCancellationRequested();
this.UpsertStatus(this.GetFallbackStatus(dataSource, TB("The selected embedding provider is not available. Please check it in the settings."))); this.UpsertStatus(this.GetFallbackStatus(dataSource, DataSourceEmbeddingProviders.IsProhibited(settingsManager, dataSource)
? TB("The use of the embedding provider of this data source has been prohibited by your organization. Please select a different embedding provider in the settings of the data source.")
: TB("The selected embedding provider is not available. Please check it in the settings.")));
return null; return null;
} }
Loaded 100 of 153 files, more files were not shown because too many files have changed in this diff. Show more