Improved the dialogs for local data sources and the Batch Processing assistant (#1096)

Co-authored-by: Thorsten Sommer <SommerEngineering@users.noreply.github.com>
This commit is contained in:
Paul KoudelkaandThorsten Sommer authored and GitHub committed 2026-10-27 19:57:04 +01:00
1 parent 149395c1a0
commit f7e6ceb915
45 files changed
+2514 -361

No files matched your search

@@ -3,7 +3,7 @@
@using AIStudio.Settings.DataModel
@using AIStudio.Tools.Rust
<MudText Typo="Typo.h5" Class="mb-3">
<MudText Typo="Typo.h6" Class="mb-3">
@T("Input")
</MudText>
@@ -18,8 +18,13 @@
</MudJustifiedText>
}
<MudTextField T="string" @bind-Text="@this.filePatterns" Validation="@this.ValidateFilePatterns" Immediate="@true" Disabled="@this.isProcessingBatch" Label="@T("File patterns")" HelperText="@T("Which files should be processed? Separate multiple patterns with a semicolon, e.g., *.pdf;*.docx. Use :documents: for every document and readable text file, :text: for readable text files only, or * for every file.")" AdornmentIcon="@Icons.Material.Filled.FilterAlt" Adornment="Adornment.Start" Variant="Variant.Outlined" Margin="Margin.Normal" UserAttributes="@USER_INPUT_ATTRIBUTES"/>
<RestoreDefaultButton Text="@T("Restore default patterns")" Disabled="@this.isProcessingBatch" OnClick="@this.RestoreDefaultFilePatterns" Class="mb-1"/>
<div class="mt-6">
<FilePatternSelection>
<AllowedContent>
<MudTextField T="string" @bind-Text="@this.filePatterns" Validation="@this.ValidateFilePatterns" Immediate="@true" Disabled="@this.isProcessingBatch" Label="@T("Files to include")" AdornmentIcon="@Icons.Material.Filled.Restore" Adornment="Adornment.End" OnAdornmentClick="@this.RestoreDefaultFilePatterns" AdornmentAriaLabel="@T("Restore default patterns")" Lines="1" AutoGrow="@true" MaxLines="@FilePatternSelection.MAX_FIELD_LINES" Class="pattern-text-field" Variant="Variant.Outlined" Margin="Margin.Dense" UserAttributes="@USER_INPUT_ATTRIBUTES"/>
</AllowedContent>
</FilePatternSelection>
</div>
<MudJustifiedText Typo="Typo.body2" Class="mb-3">
@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.")
@@ -35,20 +40,22 @@
</MudButton>
<MudDivider/>
<MudCollapse Expanded="@this.showExpertSettings" Class="@this.GetExpertStyles">
<MudText Typo="Typo.subtitle2" Class="mb-2">@T("Hidden files and folders")</MudText>
<MudTextSwitch Label="@T("Include hidden files and folders?")" Disabled="@this.isProcessingBatch" Value="@this.includeHiddenFilesAndFolders" ValueChanged="@(v => this.includeHiddenFilesAndFolders = v)" LabelOn="@T("Yes, process hidden files and folders as well")" LabelOff="@T("No, ignore hidden files and folders")"/>
<MudJustifiedText Typo="Typo.body2" Class="mb-3">
@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.")
</MudJustifiedText>
@* Without an icon: in a field of several lines, it floats in the middle of the text. *@
<MudTextField T="string" @bind-Text="@this.excludePatterns" Validation="@this.ValidateExcludePatterns" Immediate="@true" Disabled="@this.isProcessingBatch" Label="@T("Excluded files and folders")" Lines="@FileExclusions.DEFAULT_LINE_COUNT" AutoGrow="@true" MaxLines="@FileExclusions.MAX_LINE_COUNT" HelperText="@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.")" Variant="Variant.Outlined" Margin="Margin.Normal" UserAttributes="@USER_INPUT_ATTRIBUTES"/>
<RestoreDefaultButton Text="@T("Restore default")" Disabled="@this.isProcessingBatch" OnClick="@this.RestoreDefaultExcludePatterns" Class="mb-1"/>
<div class="mt-4">
<FileExclusionEditor @ref="@this.exclusionEditor" @bind-Text="@this.excludePatterns" Disabled="@this.isProcessingBatch" Label="@T("Excluded files and folders")" ResetButtonText="@T("Restore default")"/>
</div>
<MudText Typo="Typo.subtitle2" Class="mb-2 mt-4">@T("File size limit")</MudText>
<MudNumericField T="int" @bind-Value="@this.maxFileSizeMegabytes" Min="@FolderFileSelection.SMALLEST_MAX_FILE_SIZE_MEGABYTES" Immediate="@true" Disabled="@this.isProcessingBatch" Label="@T("Ignore files larger than")" HelperText="@T("Matching files above this size are left out, e.g., images of virtual machines.")" Adornment="Adornment.End" AdornmentText="MB" Variant="Variant.Outlined" Margin="Margin.Normal"/>
</MudCollapse>
</MudStack>
<MudText Typo="Typo.h5" Class="mb-3 mt-6">
<MudText Typo="Typo.h6" Class="mb-3 mt-6">
@T("Instructions")
</MudText>
@@ -134,7 +141,7 @@ else
}
}
<MudText Typo="Typo.h5" Class="mb-3 mt-6">
<MudText Typo="Typo.h6" Class="mb-3 mt-6">
@T("Output")
</MudText>
@@ -189,7 +196,7 @@ else
@T("We always write a semicolon-separated log named log.csv, which lists every document with its processing time, the model, the status, and the details of any error. When you start another run on the same output folder, we ask you whether to continue that run: documents which failed or are missing in the log are then processed again. When no output folder is selected, everything is written to the subfolder 'ai-results' within the input folder.")
</MudJustifiedText>
<MudText Typo="Typo.h5" Class="mb-3 mt-6">
<MudText Typo="Typo.h6" Class="mb-3 mt-6">
@T("Processing pace")
</MudText>
@@ -230,7 +237,7 @@ else
@if (this.fileResults.Count > 0)
{
<MudText Typo="Typo.h5" Class="mb-3 mt-6">
<MudText Typo="Typo.h6" Class="mb-3 mt-6">
@T("Progress")
</MudText>
@@ -18,8 +18,6 @@ public partial class AssistantBatchProcessing
private string? ValidateFilePatterns(string patterns) => FileNamePatterns.Validate(patterns);
private string? ValidateExcludePatterns(string patterns) => FileExclusions.Validate(patterns);
private string? ValidateCsvFileName(string fileName)
{
if (string.IsNullOrWhiteSpace(fileName))
@@ -135,9 +133,10 @@ public partial class AssistantBatchProcessing
//
// The exclusion list sits in the expert settings, which are folded away by default. Unfold
// them, so the user sees the message next to the list:
// them, so the user sees the message next to the list. Only the editor knows about an invalid
// draft, since it passes on valid lists only:
//
if (this.ValidateExcludePatterns(this.excludePatterns) is not null)
if (this.exclusionEditor is { IsValid: false })
this.showExpertSettings = true;
var instructionIssue = this.ValidateInstructionSource();
@@ -1,3 +1,4 @@
using AIStudio.Components;
using AIStudio.Dialogs.Settings;
using AIStudio.Provider;
using AIStudio.Settings.DataModel;
@@ -126,6 +127,7 @@ public partial class AssistantBatchProcessing : AssistantBaseCore<SettingsDialog
private readonly List<BatchProcessingFileResult> fileResults = [];
private readonly HashSet<string> usedResultFileNames = new(StringComparer.OrdinalIgnoreCase);
private FileExclusionEditor? exclusionEditor;
private bool showExpertSettings;
private bool isProcessingBatch;
private bool hasReportedWriteFailure;
@@ -171,8 +173,6 @@ public partial class AssistantBatchProcessing : AssistantBaseCore<SettingsDialog
private void RestoreDefaultFilePatterns() => this.filePatterns = DataBatchProcessing.DEFAULT_FILE_PATTERNS;
private void RestoreDefaultExcludePatterns() => this.excludePatterns = FileExclusions.DEFAULT_PATTERNS;
private void ToggleExpertSettings() => this.showExpertSettings = !this.showExpertSettings;
private string GetExpertStyles => this.showExpertSettings ? "border-2 border-dashed rounded pa-2" : string.Empty;
@@ -436,9 +436,15 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING
-- Please provide a file name without a path, e.g., my-results.csv
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T189587595"] = "Please provide a file name without a path, e.g., my-results.csv"
-- Hidden files and folders
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T1902969218"] = "Hidden files and folders"
-- Select the folder containing your documents
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T1926838679"] = "Select the folder containing your documents"
-- Files to include
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T2000156297"] = "Files to include"
-- Include subfolders?
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T2086334687"] = "Include subfolders?"
@@ -484,9 +490,6 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING
-- Was not able to read the existing transcript: {0}
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T2397111152"] = "Was not able to read the existing transcript: {0}"
-- File patterns
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T2460883298"] = "File patterns"
-- Load prompt from file
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T2474257795"] = "Load prompt from file"
@@ -538,6 +541,9 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING
-- 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."
-- File size limit
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T3225743883"] = "File size limit"
-- 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."
@@ -568,9 +574,6 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING
-- Processing pace
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T3428873429"] = "Processing pace"
-- Which files should be processed? Separate multiple patterns with a semicolon, e.g., *.pdf;*.docx. Use :documents: for every document and readable text file, :text: for readable text files only, or * for every file.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T3436936162"] = "Which files should be processed? Separate multiple patterns with a semicolon, e.g., *.pdf;*.docx. Use :documents: for every document and readable text file, :text: for readable text files only, or * for every file."
-- 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'."
@@ -607,9 +610,6 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING
-- Was not able to write '{0}'. Please make sure that the file is not opened in another application. The results of this run are incomplete on disk. The message is: '{1}'
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T3899869356"] = "Was not able to write '{0}'. Please make sure that the file is not opened in another application. The results of this run are incomplete on disk. The message is: '{1}'"
-- 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.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T3934601817"] = "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."
-- Select the file with your instructions
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T3943995624"] = "Select the file with your instructions"
@@ -4771,9 +4771,57 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::FILEACTIONS::T3596972550"] = "Open this w
-- Open the file in the program your system uses for it
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::FILEACTIONS::T4218155268"] = "Open the file in the program your system uses for it"
-- Exclusion help and examples
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::FILEEXCLUSIONEDITOR::T1309128010"] = "Exclusion help and examples"
-- Folders
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::FILEEXCLUSIONEDITOR::T1412427146"] = "Folders"
-- Excluded files and folders
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::FILEEXCLUSIONEDITOR::T286958948"] = "Excluded files and folders"
-- Files
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::FILEEXCLUSIONEDITOR::T3877444400"] = "Files"
-- 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/.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::FILEEXCLUSIONEDITOR::T499835724"] = "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/."
-- Restore default
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::FILEEXCLUSIONEDITOR::T913168756"] = "Restore default"
-- Saving '{0}'...
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::FILEEXPORTRUNNINGMESSAGE::T1063135893"] = "Saving '{0}'..."
-- Exclude folders ending in build, such as build or app-build
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::FILEPATTERNHELP::T1241240542"] = "Exclude folders ending in build, such as build or app-build"
-- Include readable text files, even those without an extension
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::FILEPATTERNHELP::T1482113736"] = "Include readable text files, even those without an extension"
-- File names starting with draft-
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::FILEPATTERNHELP::T1676890987"] = "File names starting with draft-"
-- Pattern examples
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::FILEPATTERNHELP::T2430822600"] = "Pattern examples"
-- Include all files, even audio and video
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::FILEPATTERNHELP::T2681459563"] = "Include all files, even audio and video"
-- Separate patterns with semicolons. Exclusions take priority over included files.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::FILEPATTERNHELP::T2728429336"] = "Separate patterns with semicolons. Exclusions take priority over included files."
-- PDF and Word files
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::FILEPATTERNHELP::T3467839719"] = "PDF and Word files"
-- Patterns match file names regardless of letter case. Folder paths are not supported.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::FILEPATTERNHELP::T3691425703"] = "Patterns match file names regardless of letter case. Folder paths are not supported."
-- Include all documents and readable text files
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::FILEPATTERNHELP::T967949655"] = "Include all documents and readable text files"
-- Pattern help and examples
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::FILEPATTERNSELECTION::T1369669520"] = "Pattern help and examples"
-- LLMs can make mistakes. Check important information.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::HALLUZINATIONREMINDER::T3528806904"] = "LLMs can make mistakes. Check important information."
@@ -4810,12 +4858,6 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::MANAGEDTOOLSWARNING::T3008114108"] = "Too
-- Some tools selected for this run are switched off in your tool settings and stay unused: {0}. Switch them on again in the app settings to use them.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::MANAGEDTOOLSWARNING::T3803950041"] = "Some tools selected for this run are switched off in your tool settings and stay unused: {0}. Switch them on again in the app settings to use them."
-- Your Pandoc installation meets the requirements.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::MANAGEPANDOCDEPENDENCY::T1167365374"] = "Your Pandoc installation meets the requirements."
-- Please wait while we check the availability of Pandoc.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::MANAGEPANDOCDEPENDENCY::T1336364299"] = "Please wait while we check the availability of Pandoc."
-- Pandoc Installation
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::MANAGEPANDOCDEPENDENCY::T185447014"] = "Pandoc Installation"
@@ -4828,9 +4870,6 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::MANAGEPANDOCDEPENDENCY::T3147551473"] = "
-- Update Pandoc
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::MANAGEPANDOCDEPENDENCY::T3249965383"] = "Update Pandoc"
-- Check your Pandoc installation
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::MANAGEPANDOCDEPENDENCY::T527187983"] = "Check your Pandoc installation"
-- Install Pandoc
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::MANAGEPANDOCDEPENDENCY::T986578435"] = "Install Pandoc"
@@ -7231,6 +7270,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCEERIV1USERNAMEPASSWORDEXPORTDIALOG:
-- Cancel
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCEERIV1USERNAMEPASSWORDEXPORTDIALOG::T900713019"] = "Cancel"
-- Folder
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T104168189"] = "Folder"
-- Hide Expert Settings
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T1108876344"] = "Hide Expert Settings"
@@ -7249,6 +7291,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T1311820297"
-- Selected base directory for this data source
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T1312296210"] = "Selected base directory for this data source"
-- MindWork AI Studio uses Pandoc to read HTML files.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T133018120"] = "MindWork AI Studio uses Pandoc to read HTML files."
-- No embedding selected
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T1359179968"] = "No embedding selected"
@@ -7279,23 +7324,32 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T1827669611"
-- Update
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T1847791252"] = "Update"
-- Hidden files and folders
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T1902969218"] = "Hidden files and folders"
-- Files to include
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T2000156297"] = "Files to include"
-- Data source
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T2034620186"] = "Data source"
-- Include subfolders?
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T2086334687"] = "Include subfolders?"
-- The overlap must be smaller than the effective token limit.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T2101951526"] = "The overlap must be smaller than the effective token limit."
-- Select the file patterns this data source should index.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T2244471011"] = "Select the file patterns this data source should index."
-- Required provider confidence level
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T236253137"] = "Required provider confidence level"
-- Please enter a token limit of at least 1.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T2406580478"] = "Please enter a token limit of at least 1."
-- File patterns
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T2460883298"] = "File patterns"
-- Search results
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T2499087427"] = "Search results"
-- Data sharing
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T2623330465"] = "Data sharing"
-- Add
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T2646845972"] = "Add"
@@ -7306,6 +7360,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T2814869210"
-- Embedding
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T2838542994"] = "Embedding"
-- Choose an embedding provider to make this data source searchable.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T2861534545"] = "Choose an embedding provider to make this data source searchable."
-- Excluded files and folders
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T286958948"] = "Excluded files and folders"
@@ -7315,18 +7372,21 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T2961294165"
-- 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.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T3017942214"] = "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."
-- Text chunking
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T3105524153"] = "Text chunking"
-- Audio and video files which this data source selects are skipped until you choose a transcription provider in the settings.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T3119708878"] = "Audio and video files which this data source selects are skipped until you choose a transcription provider in the settings."
-- File size limit
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T3225743883"] = "File size limit"
-- Please enter 0 or a positive overlap length.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T3242265813"] = "Please enter 0 or a positive overlap length."
-- Ignore files larger than
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T3353866230"] = "Ignore files larger than"
-- For some data types, such as Office files, MindWork AI Studio requires the open-source application Pandoc.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T3359366900"] = "For some data types, such as Office files, MindWork AI Studio requires the open-source application Pandoc."
-- Show Expert Settings
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T3361153305"] = "Show Expert Settings"
@@ -7339,21 +7399,21 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T3584816527"
-- This setting decides where a chat may still send data once the AI found something in this data source, so that its content does not end up in a web search, for example. Choose no restriction only for content anybody may read anyway.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T3738459730"] = "This setting decides where a chat may still send data once the AI found something in this data source, so that its content does not end up in a web search, for example. Choose no restriction only for content anybody may read anyway."
-- 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.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T3934601817"] = "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."
-- Only providers with this confidence level or higher may receive and process data from this data source.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T3755939304"] = "Only providers with this confidence level or higher may receive and process data from this data source."
-- In order for the AI to be able to determine the appropriate data at any time, you must choose an embedding provider.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T529871858"] = "In order for the AI to be able to determine the appropriate data at any time, you must choose an embedding provider."
-- Select the file patterns this data source should index. Audio and video files, e.g., *.mp3 or *.mp4, are transcribed by your transcription provider.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T4289641992"] = "Select the file patterns this data source should index. Audio and video files, e.g., *.mp3 or *.mp4, are transcribed by your transcription provider."
-- Select the base directory
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T562479068"] = "Select the base directory"
-- Which files should be indexed? Separate multiple patterns with a semicolon, e.g., *.txt;*.pdf;*.docx;.gitignore. Use :documents: for every document and readable text file, :text: for readable text files only, or * for every file. Audio and video files, e.g., *.mp3 or *.mp4, are transcribed by your transcription provider.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T599406584"] = "Which files should be indexed? Separate multiple patterns with a semicolon, e.g., *.txt;*.pdf;*.docx;.gitignore. Use :documents: for every document and readable text file, :text: for readable text files only, or * for every file. Audio and video files, e.g., *.mp3 or *.mp4, are transcribed by your transcription provider."
-- Yes, index hidden files and folders as well
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T607153623"] = "Yes, index hidden files and folders as well"
-- Restore default patterns
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T7425959"] = "Restore default patterns"
-- The data source token limit must not be larger than the embedding provider token limit ({0}).
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T787118522"] = "The data source token limit must not be larger than the embedding provider token limit ({0})."
@@ -7393,6 +7453,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYINFODIALOG::T1447415
-- No
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYINFODIALOG::T1642511898"] = "No"
-- the excluded folders
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYINFODIALOG::T1647237555"] = "the excluded folders"
-- Include subfolders
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYINFODIALOG::T1710166842"] = "Include subfolders"
@@ -7405,6 +7468,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYINFODIALOG::T1725856
-- The embedding runs locally or in your organization. Your data is not sent to the cloud.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYINFODIALOG::T1950544032"] = "The embedding runs locally or in your organization. Your data is not sent to the cloud."
-- Files to include
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYINFODIALOG::T2000156297"] = "Files to include"
-- the file patterns
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYINFODIALOG::T2013714981"] = "the file patterns"
@@ -7417,24 +7483,21 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYINFODIALOG::T2333573
-- Required provider confidence level
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYINFODIALOG::T236253137"] = "Required provider confidence level"
-- File patterns
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYINFODIALOG::T2460883298"] = "File patterns"
-- the maximum number of matches per query
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYINFODIALOG::T2479753122"] = "the maximum number of matches per query"
-- the total size of matching files
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYINFODIALOG::T2515807708"] = "the total size of matching files"
-- Excluded files
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYINFODIALOG::T2590452128"] = "Excluded files"
-- the description
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYINFODIALOG::T2658359966"] = "the description"
-- the data source name
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYINFODIALOG::T2717738728"] = "the data source name"
-- Excluded files and folders
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYINFODIALOG::T286958948"] = "Excluded files and folders"
-- The directory chosen for the data source does not exist anymore. Please edit the data source and correct the path.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYINFODIALOG::T2875614207"] = "The directory chosen for the data source does not exist anymore. Please edit the data source and correct the path."
@@ -7459,6 +7522,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYINFODIALOG::T3127438
-- Embedding name
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYINFODIALOG::T3153201045"] = "Embedding name"
-- Excluded folders
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYINFODIALOG::T3345276250"] = "Excluded folders"
-- Ignore files larger than
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYINFODIALOG::T3353866230"] = "Ignore files larger than"
@@ -7480,8 +7546,8 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYINFODIALOG::T3818527
-- Path
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYINFODIALOG::T3949388886"] = "Path"
-- the excluded files and folders
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYINFODIALOG::T4001527349"] = "the excluded files and folders"
-- the excluded files
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYINFODIALOG::T4083345389"] = "the excluded files"
-- The directory chosen for the data source exists.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYINFODIALOG::T445858624"] = "The directory chosen for the data source exists."
@@ -7498,6 +7564,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T1133561850"] = "
-- Default tokenizer
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T1220918127"] = "Default tokenizer"
-- MindWork AI Studio uses Pandoc to read HTML files.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T133018120"] = "MindWork AI Studio uses Pandoc to read HTML files."
-- No embedding selected
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T1359179968"] = "No embedding selected"
@@ -7519,6 +7588,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T1827669611"] = "
-- Update
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T1847791252"] = "Update"
-- Data source
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T2034620186"] = "Data source"
-- The overlap must be smaller than the effective token limit.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T2101951526"] = "The overlap must be smaller than the effective token limit."
@@ -7531,6 +7603,12 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T2406580478"] = "
-- This audio or video file is skipped, because your transcription provider does not meet the confidence level this data source requires.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T2468760910"] = "This audio or video file is skipped, because your transcription provider does not meet the confidence level this data source requires."
-- Search results
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T2499087427"] = "Search results"
-- Data sharing
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T2623330465"] = "Data sharing"
-- Add
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T2646845972"] = "Add"
@@ -7543,15 +7621,18 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T2838542994"] = "
-- Describe what data this file contains to help the AI select it.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T2859265837"] = "Describe what data this file contains to help the AI select it."
-- Choose an embedding provider to make this data source searchable.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T2861534545"] = "Choose an embedding provider to make this data source searchable."
-- Token limit
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T2961294165"] = "Token limit"
-- Text chunking
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T3105524153"] = "Text chunking"
-- Please enter 0 or a positive overlap length.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T3242265813"] = "Please enter 0 or a positive overlap length."
-- For some data types, such as Office files, MindWork AI Studio requires the open-source application Pandoc.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T3359366900"] = "For some data types, such as Office files, MindWork AI Studio requires the open-source application Pandoc."
-- Show Expert Settings
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T3361153305"] = "Show Expert Settings"
@@ -7564,11 +7645,14 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T3738459730"] = "
-- Select the file
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T3740148848"] = "Select the file"
-- Only providers with this confidence level or higher may receive and process data from this data source.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T3755939304"] = "Only providers with this confidence level or higher may receive and process data from this data source."
-- This audio or video file is skipped until you choose a transcription provider in the settings.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T4026150982"] = "This audio or video file is skipped until you choose a transcription provider in the settings."
-- In order for the AI to be able to determine the appropriate data at any time, you must choose an embedding provider.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T529871858"] = "In order for the AI to be able to determine the appropriate data at any time, you must choose an embedding provider."
-- File
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T723007075"] = "File"
-- The data source token limit must not be larger than the embedding provider token limit ({0}).
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T787118522"] = "The data source token limit must not be larger than the embedding provider token limit ({0})."
@@ -9151,9 +9235,15 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T17
-- Batch processing options are preselected
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T1893713430"] = "Batch processing options are preselected"
-- Hidden files and folders
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T1902969218"] = "Hidden files and folders"
-- Default custom column separator
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T19367494"] = "Default custom column separator"
-- Files to include
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T2000156297"] = "Files to include"
-- Default document analysis policy
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T2015391667"] = "Default document analysis policy"
@@ -9202,9 +9292,6 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T28
-- Please enter exactly one punctuation or symbol character. Letters, numbers, spaces, quotation marks, and line breaks cannot be used as CSV separators.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T2908365499"] = "Please enter exactly one punctuation or symbol character. Letters, numbers, spaces, quotation marks, and line breaks cannot be used as CSV separators."
-- Default file patterns
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T2909693903"] = "Default file patterns"
-- Only the selected folder is processed
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T2958253681"] = "Only the selected folder is processed"
@@ -9220,6 +9307,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T31
-- These instructions are applied to every document of a new batch run.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T3195548336"] = "These instructions are applied to every document of a new batch run."
-- File size limit
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T3225743883"] = "File size limit"
-- Ignore files larger than
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T3353866230"] = "Ignore files larger than"
@@ -9250,9 +9340,6 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T37
-- Default results table name
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T3816237687"] = "Default results table name"
-- 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.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T3934601817"] = "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."
-- Default Markdown instructions file
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T3967465682"] = "Default Markdown instructions file"
@@ -13156,6 +13243,12 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::EXTERNALHTTPCLIENTTIMEOUT::T599774443"] = "The
-- policy files
UI_TEXT_CONTENT["AISTUDIO::TOOLS::EXTERNALHTTPCLIENTTIMEOUT::T632340680"] = "policy files"
-- Enter folder patterns ending with / in 'Folders'. File patterns must not end with /.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::FILEEXCLUSIONS::T2019378403"] = "Enter folder patterns ending with / in 'Folders'. File patterns must not end with /."
-- Folder patterns must end with /, e.g., build/. Enter file patterns such as *.pdf in 'Files'.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::FILEEXCLUSIONS::T2695719308"] = "Folder patterns must end with /, e.g., build/. Enter file patterns such as *.pdf in 'Files'."
-- Selectors such as :text: cannot be excluded. Please use file name patterns such as *.min.js or folder names such as bin/.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::FILEEXCLUSIONS::T405437626"] = "Selectors such as :text: cannot be excluded. Please use file name patterns such as *.min.js or folder names such as bin/."
@@ -13384,9 +13477,6 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::FILENAMEPATTERNS::T2207479763"] = "One of the
-- Please provide at least one file pattern, e.g., :documents: or *.txt. Separate multiple patterns with a semicolon.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::FILENAMEPATTERNS::T3371062811"] = "Please provide at least one file pattern, e.g., :documents: or *.txt. Separate multiple patterns with a semicolon."
-- Please remove empty file patterns. Separate valid patterns with a single semicolon.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::FILENAMEPATTERNS::T368919579"] = "Please remove empty file patterns. Separate valid patterns with a single semicolon."
-- Please use only single asterisks as wildcards, e.g., *.txt or report-*.md.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::FILENAMEPATTERNS::T495100949"] = "Please use only single asterisks as wildcards, e.g., *.txt or report-*.md."
@@ -0,0 +1,3 @@
@inherits ConfigurationText
<FileExclusionEditor Text="@this.EditingText" TextChanged="@this.InternalUpdate" Label="@this.OptionDescription" Disabled="@this.IsDisabled" ShowHelp="@this.ShowHelp" ShowReset="@(this.ResetValue is not null)" ResetValue="@(this.ResetValue?.Invoke() ?? FileExclusions.DEFAULT_PATTERNS)" ResetButtonText="@this.ResetButtonText" ResetRequested="@this.ApplyTextAsync"/>
@@ -0,0 +1,17 @@
using Microsoft.AspNetCore.Components;
namespace AIStudio.Components;
/// <summary>
/// Reuses ConfigurationText's persistence, validation, reset, and organization locks with the
/// shared exclusion editor. The editor supplies its own outlined fields and heading.
/// </summary>
public partial class ConfigurationFileExclusions : ConfigurationText
{
[Parameter]
public bool ShowHelp { get; set; } = true;
protected override Variant Variant => Variant.Text;
protected override string Label => string.Empty;
}
@@ -4,7 +4,7 @@
{
<MudTextField
T="string"
Text="@this.Text()"
Text="@(this.ExpandToContent ? this.EditingText : this.Text())"
TextChanged="@this.InternalUpdate"
Disabled="@this.IsDisabled"
Adornment="@this.IconAdornment"
@@ -14,6 +14,7 @@
Lines="@this.NumLines"
AutoGrow="@this.AutoGrow"
MaxLines="@this.GetMaxLines"
Class="@this.InputClass"
Immediate="@true"
Validation="@this.Validation"
Underline="false"
@@ -25,20 +26,26 @@ else
<div>
<MudTextField
T="string"
Text="@this.Text()"
Text="@(this.ExpandToContent ? this.EditingText : this.Text())"
TextChanged="@this.InternalUpdate"
Disabled="@this.IsDisabled"
Adornment="@this.IconAdornment"
AdornmentIcon="@this.Icon"
Adornment="@(this.ResetButtonIconOnly ? Adornment.End : this.IconAdornment)"
AdornmentIcon="@(this.ResetButtonIconOnly ? Icons.Material.Filled.Restore : this.Icon)"
OnAdornmentClick="@this.ResetAdornmentClick"
AdornmentAriaLabel="@(this.ResetButtonIconOnly ? this.ResetButtonText : null)"
AdornmentColor="@this.IconColor"
UserAttributes="@SPELLCHECK_ATTRIBUTES"
Lines="@this.NumLines"
AutoGrow="@this.AutoGrow"
MaxLines="@this.GetMaxLines"
Class="@this.InputClass"
Immediate="@true"
Validation="@this.Validation"
Underline="false"
/>
<RestoreDefaultButton Text="@this.ResetButtonText" Disabled="@this.IsDisabled" OnClick="@this.ResetTextAsync"/>
@if (!this.ResetButtonIconOnly)
{
<RestoreDefaultButton Text="@this.ResetButtonText" Disabled="@this.IsDisabled" OnClick="@this.ResetTextAsync"/>
}
</div>
}
@@ -1,4 +1,5 @@
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;
using Timer = System.Timers.Timer;
@@ -24,6 +25,24 @@ public partial class ConfigurationText : ConfigurationBaseCore
[Parameter]
public string Icon { get; set; } = Icons.Material.Filled.Info;
/// <summary>
/// Removes the default bottom margin when the field is part of a compact group.
/// </summary>
[Parameter]
public bool Compact { get; set; }
/// <summary>
/// Wraps and grows with its text, including while the field is not focused, up to the lines of a
/// field for file patterns.
/// </summary>
/// <remarks>
/// Such a field shows the draft rather than the stored value. Otherwise, a reset which leaves the
/// stored value as it was would not reach the field, e.g., restoring the default while the field
/// shows a draft which the validation rejected.
/// </remarks>
[Parameter]
public bool ExpandToContent { get; set; }
/// <summary>
/// The color of the icon to use.
/// </summary>
@@ -54,6 +73,12 @@ public partial class ConfigurationText : ConfigurationBaseCore
[Parameter]
public string ResetButtonText { get; set; } = string.Empty;
/// <summary>
/// Uses an accessible reset icon inside the field.
/// </summary>
[Parameter]
public bool ResetButtonIconOnly { get; set; }
/// <summary>
/// Validates the configured text before it is stored.
/// </summary>
@@ -61,10 +86,14 @@ public partial class ConfigurationText : ConfigurationBaseCore
public Func<string, string?>? Validation { get; set; }
private string internalText = string.Empty;
private string? lastSourceText;
private readonly Timer timer = new(TimeSpan.FromMilliseconds(500))
{
AutoReset = false
};
protected string EditingText => this.internalText;
#region Overrides of ConfigurationBase
@@ -72,6 +101,8 @@ public partial class ConfigurationText : ConfigurationBaseCore
protected override bool Stretch => true;
protected override Variant Variant => Variant.Outlined;
protected override string MarginClass => this.Compact ? "mb-0" : base.MarginClass;
protected override string Label => this.OptionDescription;
@@ -83,15 +114,24 @@ public partial class ConfigurationText : ConfigurationBaseCore
protected override async Task OnParametersSetAsync()
{
this.internalText = this.Text();
var sourceText = this.Text();
// Re-rendering the parent must not replace a draft which is waiting for its debounce.
// A genuinely changed setting, e.g. from a configuration plugin, still replaces it.
if (this.IsDisabled || !string.Equals(sourceText, this.lastSourceText, StringComparison.Ordinal))
{
this.internalText = sourceText;
this.lastSourceText = sourceText;
}
await base.OnParametersSetAsync();
}
#endregion
private bool AutoGrow => this.NumLines > 1;
private bool AutoGrow => this.ExpandToContent || this.NumLines > 1;
private int GetMaxLines => this.AutoGrow ? this.MaxLines : 1;
private int GetMaxLines => this.ExpandToContent ? FilePatternSelection.MAX_FIELD_LINES : this.AutoGrow ? this.MaxLines : 1;
private string InputClass => this.ExpandToContent ? "pattern-text-field" : string.Empty;
//
// MudBlazor centers the icon vertically, so in a field of several lines it floats in the middle
@@ -99,29 +139,50 @@ public partial class ConfigurationText : ConfigurationBaseCore
//
private Adornment IconAdornment => this.NumLines == 1 ? Adornment.Start : Adornment.None;
private void InternalUpdate(string text)
protected void InternalUpdate(string text)
{
if (this.IsDisabled)
return;
this.timer.Stop();
this.internalText = text;
this.timer.Start();
}
private async Task ResetTextAsync()
private EventCallback<MouseEventArgs> ResetAdornmentClick => this.ResetButtonIconOnly
? EventCallback.Factory.Create<MouseEventArgs>(this, this.ResetTextAsync)
: default;
protected async Task ResetTextAsync()
{
if (this.ResetValue is null || this.IsDisabled)
return;
await this.ApplyTextAsync(this.ResetValue());
}
/// <summary>
/// Applies an explicit value immediately, including a reset of part of a compound setting.
/// </summary>
protected async Task ApplyTextAsync(string text)
{
if (this.IsDisabled)
return;
this.timer.Stop();
this.internalText = this.ResetValue();
this.internalText = text;
await this.OptionChanged(this.internalText);
}
private async Task OptionChanged(string updatedText)
protected async Task OptionChanged(string updatedText)
{
if (this.Validation?.Invoke(updatedText) is not null)
if (this.IsDisabled || this.Validation?.Invoke(updatedText) is not null)
return;
this.TextUpdate(updatedText);
// A save may finish after another keystroke. Remember the value it applied before
// awaiting storage, so its configuration-change notification cannot erase that keystroke.
this.lastSourceText = this.Text();
await this.SettingsManager.StoreSettings();
await this.InformAboutChange();
}
@@ -0,0 +1,25 @@
@inherits MSGComponentBase
<div class="file-exclusion-editor mb-3">
<MudStack Row="@true" AlignItems="AlignItems.Center" Spacing="1" Class="mb-1">
<MudText Typo="Typo.subtitle2" Class="flex-grow-1">@this.EditorLabel</MudText>
@if (this.ShowHelp)
{
<MudTooltip Text="@T("Exclusion help and examples")" RootStyle="display: inline-flex;">
<MudIconButton Icon="@Icons.Material.Outlined.HelpOutline" Color="@(this.showHelp ? Color.Info : Color.Default)" OnClick="@this.ToggleHelp" aria-label="@T("Exclusion help and examples")" aria-expanded="@(this.showHelp ? "true" : "false")"/>
</MudTooltip>
}
</MudStack>
<MudStack Spacing="1">
<MudTextField T="string" InputId="@this.folderInputId" Text="@this.state.FolderText" TextChanged="@this.ChangeFoldersAsync" Immediate="@true" Disabled="@this.Disabled" Label="@T("Folders")" Lines="1" AutoGrow="@true" MaxLines="@FilePatternSelection.MAX_FIELD_LINES" Class="pattern-text-field" Variant="Variant.Outlined" Margin="Margin.Dense" Validation="@FileExclusions.ValidateFolders" UserAttributes="@INPUT_ATTRIBUTES" Placeholder="node_modules/;bin/;cmake-build-*/" Adornment="@(this.ShowReset ? Adornment.End : Adornment.None)" AdornmentIcon="@Icons.Material.Filled.Restore" OnAdornmentClick="@(() => this.RestoreDefaultsAsync(true))" AdornmentAriaLabel="@this.FolderRestoreLabel"/>
<MudTextField T="string" InputId="@this.fileInputId" Text="@this.state.FileText" TextChanged="@this.ChangeFilesAsync" Immediate="@true" Disabled="@this.Disabled" Label="@T("Files")" Lines="1" AutoGrow="@true" MaxLines="@FilePatternSelection.MAX_FIELD_LINES" Class="pattern-text-field" Variant="Variant.Outlined" Margin="Margin.Dense" Validation="@FileExclusions.ValidateFiles" UserAttributes="@INPUT_ATTRIBUTES" Placeholder="*.min.js;*.webm;draft-*" Adornment="@(this.ShowReset ? Adornment.End : Adornment.None)" AdornmentIcon="@Icons.Material.Filled.Restore" OnAdornmentClick="@(() => this.RestoreDefaultsAsync(false))" AdornmentAriaLabel="@this.FileRestoreLabel"/>
</MudStack>
<MudText Typo="Typo.caption" Class="mud-text-secondary mt-1">@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/.")</MudText>
<MudCollapse Expanded="@(this.ShowHelp && this.showHelp)">
<div class="mt-2">
<FilePatternHelp ShowInclusionExamples="@false" ShowExclusionExamples="@true"/>
</div>
</MudCollapse>
</div>
@@ -0,0 +1,115 @@
using Microsoft.AspNetCore.Components;
namespace AIStudio.Components;
/// <summary>
/// A compact, two-group editor for the exclusion string shared by folder-based features.
/// </summary>
public partial class FileExclusionEditor : MSGComponentBase
{
private static readonly Dictionary<string, object?> INPUT_ATTRIBUTES = new()
{
["spellcheck"] = false,
["autocorrect"] = "off",
["autocapitalize"] = "off",
};
[Parameter]
public string? Text { get; set; } = string.Empty;
[Parameter]
public EventCallback<string> TextChanged { get; set; }
[Parameter]
public string Label { get; set; } = string.Empty;
[Parameter]
public bool Disabled { get; set; }
[Parameter]
public bool ShowReset { get; set; } = true;
[Parameter]
public bool ShowHelp { get; set; } = true;
[Parameter]
public string ResetValue { get; set; } = FileExclusions.DEFAULT_PATTERNS;
[Parameter]
public string ResetButtonText { get; set; } = string.Empty;
/// <summary>
/// Immediately persists the combined value after restoring just one of its groups.
/// Ordinary edits continue through TextChanged.
/// </summary>
[Parameter]
public EventCallback<string> ResetRequested { get; set; }
private readonly FileExclusionEditorState state = new();
private readonly string folderInputId = $"exclusion-folders-{Guid.NewGuid():N}";
private readonly string fileInputId = $"exclusion-files-{Guid.NewGuid():N}";
private bool showHelp;
/// <summary>
/// False while a field holds an invalid draft. The editor keeps such a draft to itself, so the
/// bound text is still the last valid one and cannot tell.
/// </summary>
public bool IsValid => this.state.IsValid;
private string EditorLabel => string.IsNullOrWhiteSpace(this.Label) ? T("Excluded files and folders") : this.Label;
private string RestoreLabel => string.IsNullOrWhiteSpace(this.ResetButtonText) ? T("Restore default") : this.ResetButtonText;
private string FolderRestoreLabel => $"{this.RestoreLabel}: {T("Folders")}";
private string FileRestoreLabel => $"{this.RestoreLabel}: {T("Files")}";
protected override void OnParametersSet()
{
this.state.Receive(this.Text, discardDraft: this.Disabled);
base.OnParametersSet();
}
protected Task ChangeFoldersAsync(string text) => this.ChangePatternsAsync(text, folders: true);
protected Task ChangeFilesAsync(string text) => this.ChangePatternsAsync(text, folders: false);
private async Task ChangePatternsAsync(string text, bool folders)
{
if (this.Disabled)
return;
var previousText = this.state.Text;
var wasValid = this.state.IsValid;
if (folders)
this.state.EditFolders(text);
else
this.state.EditFiles(text);
// Moving a rule to the correct field can keep the combined text identical. It must still
// publish the now-valid draft, because the previous, invalid arrangement was not sent.
if (this.state.IsValid && (!wasValid || !string.Equals(previousText, this.state.Text, StringComparison.Ordinal)))
await this.TextChanged.InvokeAsync(this.state.Text);
}
protected async Task RestoreDefaultsAsync(bool folders)
{
if (this.Disabled || !this.ShowReset)
return;
if (folders)
this.state.RestoreFolders(this.ResetValue);
else
this.state.RestoreFiles(this.ResetValue);
if (!this.state.IsValid)
return;
if (this.ResetRequested.HasDelegate)
await this.ResetRequested.InvokeAsync(this.state.Text);
else
await this.TextChanged.InvokeAsync(this.state.Text);
}
private void ToggleHelp() => this.showHelp = !this.showHelp;
}
@@ -0,0 +1,97 @@
namespace AIStudio.Components;
/// <summary>
/// Presents the existing exclusion string as two editable groups without losing the other group
/// or letting delayed parameter echoes overwrite newer edits.
/// </summary>
internal sealed class FileExclusionEditorState
{
private string? receivedText;
private readonly HashSet<string> pendingValues = new(StringComparer.Ordinal);
public string Text { get; private set; } = string.Empty;
public string FolderText { get; private set; } = string.Empty;
public string FileText { get; private set; } = string.Empty;
public bool IsValid => FileExclusions.IsValidFolders(this.FolderText) && FileExclusions.IsValidFiles(this.FileText);
/// <summary>
/// Takes over the text of the parent, which renders again after every published edit and passes
/// that edit back, possibly only after the user typed on. Such an echo must not replace the newer
/// draft; any other text does, e.g., a change by a configuration plugin.
/// </summary>
/// <param name="text">The text the parent passes in.</param>
/// <param name="discardDraft">Whether to take over the text in any case, e.g., while the editor is disabled.</param>
/// <returns>Whether the text replaced the draft.</returns>
public bool Receive(string? text, bool discardDraft = false)
{
text ??= string.Empty;
if (discardDraft)
{
var changed = !string.Equals(text, this.Text, StringComparison.Ordinal);
this.receivedText = text;
this.Reset(text);
return changed;
}
// The parent passes the same text with every render; only a different one counts:
if (string.Equals(text, this.receivedText, StringComparison.Ordinal))
return false;
this.receivedText = text;
// The parent caught up with the draft. Its renders arrive in order, so no older echo follows:
if (string.Equals(text, this.Text, StringComparison.Ordinal))
{
this.pendingValues.Clear();
return false;
}
// The echo of an earlier edit, which a later one replaced already:
if (this.pendingValues.Remove(text))
return false;
this.Reset(text);
return true;
}
public void Reset(string text)
{
this.pendingValues.Clear();
this.Text = text;
this.Regroup();
}
public void EditFolders(string? text)
{
this.FolderText = text ?? string.Empty;
this.UpdateText();
}
public void EditFiles(string? text)
{
this.FileText = text ?? string.Empty;
this.UpdateText();
}
public void RestoreFolders(string defaults) => this.EditFolders(FileExclusions.Group(defaults).Folders);
public void RestoreFiles(string defaults) => this.EditFiles(FileExclusions.Group(defaults).Files);
private void Regroup()
{
var groups = FileExclusions.Group(this.Text);
this.FolderText = groups.Folders;
this.FileText = groups.Files;
}
private void UpdateText()
{
this.Text = string.Join(FileExclusions.ENTRY_SEPARATOR, FileExclusions.Parse(this.FolderText)
.Concat(FileExclusions.Parse(this.FileText))
.Distinct(StringComparer.OrdinalIgnoreCase));
this.pendingValues.Add(this.Text);
}
}
@@ -0,0 +1,23 @@
@inherits MSGComponentBase
<MudPaper Outlined="@true" Class="pa-3" Elevation="0">
<MudText Typo="Typo.subtitle2">@T("Pattern examples")</MudText>
<MudText Typo="Typo.body2" Class="mud-text-secondary mb-2">@T("Separate patterns with semicolons. Exclusions take priority over included files.")</MudText>
<MudSimpleTable Dense="@true" Elevation="0" aria-label="@T("Pattern examples")">
<tbody>
<tr><td><code>*.pdf;*.docx</code></td><td>@T("PDF and Word files")</td></tr>
@if (this.ShowInclusionExamples)
{
<tr><td><code>:documents:</code></td><td>@T("Include all documents and readable text files")</td></tr>
<tr><td><code>*</code></td><td>@T("Include all files, even audio and video")</td></tr>
<tr><td><code>:text:</code></td><td>@T("Include readable text files, even those without an extension")</td></tr>
}
<tr><td><code>draft-*</code></td><td>@T("File names starting with draft-")</td></tr>
@if (this.ShowExclusionExamples)
{
<tr><td><code>*build/</code></td><td>@T("Exclude folders ending in build, such as build or app-build")</td></tr>
}
</tbody>
</MudSimpleTable>
<MudText Typo="Typo.caption" Class="mud-text-secondary mt-2">@T("Patterns match file names regardless of letter case. Folder paths are not supported.")</MudText>
</MudPaper>
@@ -0,0 +1,22 @@
using Microsoft.AspNetCore.Components;
namespace AIStudio.Components;
/// <summary>
/// Examples of file patterns, shared by the fields for files to include and for exclusions, so both
/// explain the patterns in the same words.
/// </summary>
public partial class FilePatternHelp : MSGComponentBase
{
/// <summary>
/// Shows the selectors and wildcards which only the files to include accept, such as :text:.
/// </summary>
[Parameter]
public bool ShowInclusionExamples { get; set; } = true;
/// <summary>
/// Shows an entry which excludes folders.
/// </summary>
[Parameter]
public bool ShowExclusionExamples { get; set; }
}
@@ -0,0 +1,27 @@
@inherits MSGComponentBase
<MudStack Spacing="1" Class="mb-3">
<div class="pattern-row">
<div class="pattern-row-main">
@this.AllowedContent
</div>
<MudTooltip Text="@T("Pattern help and examples")" RootStyle="display: inline-flex;">
<MudIconButton Icon="@Icons.Material.Outlined.HelpOutline" Color="@(this.showHelp ? Color.Info : Color.Default)" OnClick="@this.ToggleHelp" aria-label="@T("Pattern help and examples")" aria-expanded="@(this.showHelp ? "true" : "false")"/>
</MudTooltip>
</div>
@if (this.HasExclusions)
{
<div class="pattern-row">
<div class="pattern-row-main mt-3">
@this.ExcludedContent
</div>
</div>
}
<MudCollapse Expanded="@this.showHelp">
<div class="pattern-row">
<div class="pattern-row-main">
<FilePatternHelp ShowExclusionExamples="@this.HasExclusions"/>
</div>
</div>
</MudCollapse>
</MudStack>
@@ -0,0 +1,31 @@
using Microsoft.AspNetCore.Components;
namespace AIStudio.Components;
/// <summary>
/// A file-pattern field with shared, collapsible help and optional, always-visible exclusions.
/// Callers supply their existing fields, preserving validation and managed-configuration locks.
/// </summary>
public partial class FilePatternSelection : MSGComponentBase
{
/// <summary>
/// How many lines a field for file patterns or exclusions grows to. A longer list scrolls.
/// </summary>
public const int MAX_FIELD_LINES = 6;
[Parameter, EditorRequired]
public RenderFragment AllowedContent { get; set; } = null!;
/// <summary>
/// When supplied, shows the existing exclusion field below the inclusion field.
/// Both groups stay visible and use the same help control.
/// </summary>
[Parameter]
public RenderFragment? ExcludedContent { get; set; }
private bool HasExclusions => this.ExcludedContent is not null;
private bool showHelp;
private void ToggleHelp() => this.showHelp = !this.showHelp;
}
@@ -1,10 +1,13 @@
@inherits MSGComponentBase
<MudStack Row="false" Class="mb-3 pa-3 border-dashed border rounded-lg">
<MudJustifiedText Typo="Typo.body1">
@this.DetermineIntroText()
</MudJustifiedText>
<MudButton Color="@this.DetermineButtonColor()" Variant="Variant.Filled" OnClick="() => this.ShowPandocDialogAsync()">
@this.DetermineButtonText()
</MudButton>
</MudStack>
@if (ShouldShowInstallationPrompt(this.pandocInstallation))
{
<MudStack Row="false" Class="mb-3 pa-3 border-dashed border rounded-lg">
<MudJustifiedText Typo="Typo.body2">
@this.PromptText
</MudJustifiedText>
<MudButton Color="@this.ButtonColor" Variant="Variant.Filled" OnClick="() => this.ShowPandocDialogAsync()">
@this.ButtonText
</MudButton>
</MudStack>
}
@@ -7,6 +7,11 @@ using DialogOptions = AIStudio.Dialogs.DialogOptions;
namespace AIStudio.Components;
/// <summary>
/// Asks to install or update Pandoc where a feature needs it for some files only, such as the HTML files
/// of a local data source. It stays hidden while the check runs and once Pandoc is ready, so it does not
/// distract from the rest of the dialog.
/// </summary>
public partial class ManagePandocDependency : MSGComponentBase
{
[Parameter]
@@ -18,7 +23,10 @@ public partial class ManagePandocDependency : MSGComponentBase
[Inject]
private RustService RustService { get; init; } = null!;
private PandocInstallation pandocInstallation;
// Null means the silent availability check has not completed yet.
private PandocInstallation? pandocInstallation;
internal static bool ShouldShowInstallationPrompt(PandocInstallation? installation) => installation is { IsAvailable: false };
#region Overrides of MSGComponentBase
@@ -30,60 +38,20 @@ public partial class ManagePandocDependency : MSGComponentBase
#endregion
private Color DetermineButtonColor()
{
if (this.pandocInstallation == default)
return Color.Default;
switch (this.pandocInstallation)
{
case { CheckWasSuccessful: true, IsAvailable: true }:
return Color.Default;
case { CheckWasSuccessful: true, IsAvailable: false }:
return Color.Warning;
case { CheckWasSuccessful: false }:
return Color.Error;
}
}
private string DetermineButtonText()
{
if(this.pandocInstallation == default)
return T("Please wait while we check the availability of Pandoc.");
switch (this.pandocInstallation)
{
case { CheckWasSuccessful: true, IsAvailable: true }:
return T("Check your Pandoc installation");
case { CheckWasSuccessful: true, IsAvailable: false }:
return T("Update Pandoc");
case { CheckWasSuccessful: false }:
return T("Install Pandoc");
}
}
//
// The prompt shows up only for an installation which is not available, so it is either outdated,
// when the check found one, or missing:
//
private bool IsOutdated => this.pandocInstallation is { CheckWasSuccessful: true };
private Color ButtonColor => this.IsOutdated ? Color.Warning : Color.Error;
private string ButtonText => this.IsOutdated ? T("Update Pandoc") : T("Install Pandoc");
private string PromptText => this.IsOutdated
? $"{this.IntroText} {T("Your Pandoc installation is outdated. Please update it to the latest version to ensure compatibility with all features.")}"
: $"{this.IntroText} {T("Pandoc is not installed or not available. Please install it to use the features that require Pandoc.")}";
private string DetermineIntroText()
{
if (this.pandocInstallation == default)
return $"{this.IntroText} {T("Please wait while we check the availability of Pandoc.")}";
switch (this.pandocInstallation)
{
case { CheckWasSuccessful: true, IsAvailable: true }:
return $"{this.IntroText} {T("Your Pandoc installation meets the requirements.")}";
case { CheckWasSuccessful: true, IsAvailable: false }:
return $"{this.IntroText} {T("Your Pandoc installation is outdated. Please update it to the latest version to ensure compatibility with all features.")}";
case { CheckWasSuccessful: false }:
return $"{this.IntroText} {T("Pandoc is not installed or not available. Please install it to use the features that require Pandoc.")}";
}
}
private async Task ShowPandocDialogAsync()
{
var dialogReference = await this.DialogService.ShowAsync<PandocDialog>(T("Pandoc Installation"), DialogOptions.FULLSCREEN);
@@ -7,9 +7,11 @@
Text="@this.Value"
Variant="Variant.Outlined"
Margin="Margin.Dense"
Lines="3"
Lines="@this.Lines"
MaxLines="@this.MaxLines"
AutoGrow="@true"
Adornment="@(string.IsNullOrEmpty(this.Icon) ? Adornment.None : Adornment.Start)"
AdornmentIcon="@this.Icon"
Style="@this.GetColor()"
UserAttributes="@USER_INPUT_ATTRIBUTES" />
@@ -9,6 +9,9 @@ public partial class TextInfoLines : MSGComponentBase
[Parameter]
public string Label { get; set; } = string.Empty;
[Parameter]
public string Icon { get; set; } = string.Empty;
[Parameter]
public string Value { get; set; } = string.Empty;
@@ -18,6 +21,9 @@ public partial class TextInfoLines : MSGComponentBase
[Parameter]
public int MaxLines { get; set; } = 30;
[Parameter]
public int Lines { get; set; } = 3;
[Parameter]
public bool ShowingCopyButton { get; set; } = true;
@@ -10,12 +10,15 @@
<MudForm @ref="@this.form" @bind-IsValid="@this.dataIsValid" @bind-Errors="@this.dataIssues" FieldChanged="@this.RevalidateAfterFieldChange">
<DataSourceMissingEmbeddingWarning IsInDataSourceDialog="@true" EmbeddingProviderAdded="@this.SelectAddedEmbeddingProvider"/>
<MudText Typo="Typo.h6" Class="mb-3">
@T("Data source")
</MudText>
@* ReSharper disable once CSharpWarnings::CS8974 *@
<MudTextField
T="string"
@bind-Text="@this.dataName"
Label="@T("Data Source Name")"
Class="mb-6"
Class="mb-3"
MaxLength="@DataSourceValidation.MAX_NAME_LENGTH"
Counter="@DataSourceValidation.MAX_NAME_LENGTH"
Immediate="@true"
@@ -31,7 +34,7 @@
T="string"
@bind-Text="@this.dataDescription"
Label="@T("Description")"
Class="mb-6"
Class="mb-0"
MaxLength="200"
Counter="200"
Immediate="@true"
@@ -44,7 +47,10 @@
Lines="3"
/>
<MudJustifiedText Typo="Typo.body1" Class="mb-3">
<MudText Typo="Typo.h6" Class="mb-3 mt-6">
@T("Folder")
</MudText>
<MudJustifiedText Typo="Typo.body2" Class="mb-3">
@T("Select a root directory for this data source.")
</MudJustifiedText>
<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" />
@@ -57,28 +63,42 @@
LabelOff="@T("No, only index files in the selected folder")"
/>
<MudJustifiedText Typo="Typo.body1" Class="mb-3">
@T("Select the file patterns this data source should index.")
<MudText Typo="Typo.h6" Class="mb-3 mt-6">
@T("Files to include")
</MudText>
<MudJustifiedText Typo="Typo.body2" Class="mb-3">
@T("Select the file patterns this data source should index. Audio and video files, e.g., *.mp3 or *.mp4, are transcribed by your transcription provider.")
</MudJustifiedText>
<MudTextField
T="string"
@bind-Text="@this.dataFilePatterns"
Label="@T("File patterns")"
Class="mb-3"
Immediate="@true"
Validation="@this.dataSourceValidation.ValidateFilePatterns"
HelperText="@T("Which files should be indexed? Separate multiple patterns with a semicolon, e.g., *.txt;*.pdf;*.docx;.gitignore. Use :documents: for every document and readable text file, :text: for readable text files only, or * for every file. Audio and video files, e.g., *.mp3 or *.mp4, are transcribed by your transcription provider.")"
Adornment="Adornment.Start"
AdornmentIcon="@Icons.Material.Filled.FilterAlt"
AdornmentColor="Color.Info"
UserAttributes="@SPELLCHECK_ATTRIBUTES"
Variant="Variant.Outlined"
/>
<FilePatternSelection>
<AllowedContent>
<MudTextField
T="string"
@bind-Text="@this.dataFilePatterns"
Label="@T("Files to include")"
Immediate="@true"
Validation="@this.dataSourceValidation.ValidateFilePatterns"
Adornment="Adornment.End"
AdornmentIcon="@Icons.Material.Filled.Restore"
OnAdornmentClick="@this.RestoreDefaultFilePatterns"
AdornmentAriaLabel="@T("Restore default patterns")"
UserAttributes="@SPELLCHECK_ATTRIBUTES"
Variant="Variant.Outlined"
Margin="Margin.Dense"
Lines="1"
AutoGrow="@true"
MaxLines="@FilePatternSelection.MAX_FIELD_LINES"
Class="pattern-text-field"
/>
</AllowedContent>
</FilePatternSelection>
<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.")
</MudJustifiedText>
<MudText Typo="Typo.h6" Class="mb-3 mt-6">
@T("Embedding")
</MudText>
<MudText Typo="Typo.body2" Class="mb-3">
@T("Choose an embedding provider to make this data source searchable.")
</MudText>
<MudSelect @ref="@this.embeddingSelect" @bind-Value="@this.dataEmbeddingId" Label="@T("Embedding")" Class="mb-3" OpenIcon="@Icons.Material.Filled.ExpandMore" AdornmentColor="Color.Info" Adornment="Adornment.Start" Validation="@this.dataSourceValidation.ValidateEmbeddingProviderAccess">
@foreach (var provider in this.SettingsManager.GetAllEmbeddingProviders())
{
@@ -94,14 +114,17 @@
}
else if (!string.IsNullOrWhiteSpace(this.dataEmbeddingId))
{
<MudJustifiedText Typo="Typo.body1" Color="Color.Tertiary" Class="mb-3">
<MudJustifiedText Typo="Typo.body2" Color="Color.Tertiary" Class="mb-3">
@T("The embedding you selected runs locally or in your organization. Your data is not sent to the cloud.")
</MudJustifiedText>
}
<ManagePandocDependency IntroText="@T("For some data types, such as Office files, MindWork AI Studio requires the open-source application Pandoc.")"/>
<ManagePandocDependency IntroText="@T("MindWork AI Studio uses Pandoc to read HTML files.")"/>
<MudSelect @ref="@this.confidenceLevelSelect" @bind-Value="@this.dataConfidenceLevel" Text="@this.dataConfidenceLevel.GetName()" Label="@T("Required provider confidence level")" Class="mb-3" OpenIcon="@Icons.Material.Filled.ExpandMore" AdornmentColor="Color.Info" Adornment="Adornment.Start" Validation="@this.dataSourceValidation.ValidateDataSourceConfidenceLevel">
<MudText Typo="Typo.h6" Class="mb-3 mt-6">
@T("Data sharing")
</MudText>
<MudSelect @ref="@this.confidenceLevelSelect" @bind-Value="@this.dataConfidenceLevel" Text="@this.dataConfidenceLevel.GetName()" Label="@T("Required provider confidence level")" HelperText="@T("Only providers with this confidence level or higher may receive and process data from this data source.")" Class="mb-3" OpenIcon="@Icons.Material.Filled.ExpandMore" AdornmentColor="Color.Info" Adornment="Adornment.Start" Validation="@this.dataSourceValidation.ValidateDataSourceConfidenceLevel">
@foreach (var level in this.ConfidenceLevels)
{
<MudSelectItem Value="@level.Value">
@@ -125,6 +148,7 @@
</MudButton>
<MudDivider/>
<MudCollapse Expanded="@this.showExpertSettings" Class="@this.GetExpertStyles">
<MudText Typo="Typo.subtitle2" Class="mb-2">@T("Hidden files and folders")</MudText>
<MudTextSwitch
Label="@T("Include hidden files and folders?")"
Value="@this.dataIncludeHiddenFilesAndFolders"
@@ -135,21 +159,10 @@
<MudJustifiedText Typo="Typo.body2" Class="mb-3">
@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.")
</MudJustifiedText>
@* Without an icon: in a field of several lines, it floats in the middle of the text. *@
<MudTextField
T="string"
@bind-Text="@this.dataExcludePatterns"
Label="@T("Excluded files and folders")"
Lines="@FileExclusions.DEFAULT_LINE_COUNT"
AutoGrow="@true"
MaxLines="@FileExclusions.MAX_LINE_COUNT"
Immediate="@true"
Validation="@this.dataSourceValidation.ValidateExcludePatterns"
HelperText="@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.")"
UserAttributes="@SPELLCHECK_ATTRIBUTES"
Variant="Variant.Outlined"
/>
<RestoreDefaultButton Text="@T("Restore default")" OnClick="@this.RestoreDefaultExcludePatterns" Class="mb-3"/>
<div class="mt-4">
<FileExclusionEditor @bind-Text="@this.dataExcludePatterns" Label="@T("Excluded files and folders")" ResetButtonText="@T("Restore default")"/>
</div>
<MudText Typo="Typo.subtitle2" Class="mb-2 mt-4">@T("File size limit")</MudText>
<MudNumericField
T="int"
@bind-Value="@this.dataMaxFileSizeMegabytes"
@@ -161,6 +174,10 @@
Variant="Variant.Outlined"
Adornment="Adornment.End"
AdornmentText="MB"/>
<MudText Typo="Typo.subtitle2" Class="mb-2 mt-4">@T("Text chunking")</MudText>
<MudJustifiedText Typo="Typo.body2" Class="mb-3">
@T("Optional expert settings for how this data source is split before embedding.")
</MudJustifiedText>
@if (!string.IsNullOrWhiteSpace(this.dataEmbeddingId))
{
<MudTextField
@@ -174,9 +191,6 @@
AdornmentIcon="@Icons.Material.Filled.TextFields"
AdornmentColor="Color.Info"/>
}
<MudJustifiedText Typo="Typo.body1" Class="mb-3">
@T("Optional expert settings for how this data source is split before embedding.")
</MudJustifiedText>
<MudNumericField
@ref="@this.maxChunkTokenLengthField"
T="int"
@@ -204,6 +218,7 @@
Adornment="Adornment.Start"
AdornmentIcon="@Icons.Material.Filled.CompareArrows"
AdornmentColor="Color.Info"/>
<MudText Typo="Typo.subtitle2" Class="mb-2 mt-4">@T("Search results")</MudText>
<MudNumericField
T="ushort"
Min="10"
@@ -253,7 +253,7 @@ public partial class DataSourceLocalDirectoryDialog : MSGComponentBase
Path = this.dataPath,
FilePatterns = this.NormalizeFilePatterns(),
// Kept as typed, since the line breaks group the entries for the eye:
// Unlike the file patterns, no normalizing, since the exclusion editor passes on valid lists only:
ExcludePatterns = this.dataExcludePatterns,
MaxFileSizeMegabytes = this.dataMaxFileSizeMegabytes,
IncludeSubdirectories = this.dataIncludeSubdirectories,
@@ -301,7 +301,7 @@ public partial class DataSourceLocalDirectoryDialog : MSGComponentBase
private string NormalizeFilePatterns() => FileNamePatterns.Normalize(this.dataFilePatterns);
private void RestoreDefaultExcludePatterns() => this.dataExcludePatterns = FileExclusions.DEFAULT_PATTERNS;
private void RestoreDefaultFilePatterns() => this.dataFilePatterns = DataSourceLocalDirectory.DEFAULT_FILE_PATTERNS;
/// <summary>
/// Gives the fields which are checked against each other a fresh verdict: the embedding provider
@@ -12,8 +12,9 @@
<TextInfoLine Icon="@Icons.Material.Filled.FolderOpen" Label="@T("Path")" Value="@this.DataSource.Path" ClipboardTooltipSubject="@T("this path")"/>
<TextInfoLine Icon="@Icons.Material.Filled.AccountTree" Label="@T("Include subfolders")" Value="@(this.DataSource.IncludeSubdirectories ? T("Yes") : T("No"))" ClipboardTooltipSubject="@T("whether subfolders are included")"/>
<TextInfoLine Icon="@Icons.Material.Filled.VisibilityOff" Label="@T("Include hidden files and folders")" Value="@(this.DataSource.IncludeHiddenFilesAndFolders ? T("Yes") : T("No"))" ClipboardTooltipSubject="@T("whether hidden files and folders are included")"/>
<TextInfoLine Icon="@Icons.Material.Filled.FilterAlt" Label="@T("File patterns")" Value="@this.DataSource.EffectiveFilePatterns" ClipboardTooltipSubject="@T("the file patterns")"/>
<TextInfoLines Label="@T("Excluded files and folders")" Value="@this.DataSource.EffectiveExcludePatterns" ClipboardTooltipSubject="@T("the excluded files and folders")"/>
<TextInfoLines Icon="@Icons.Material.Filled.FilterAlt" Label="@T("Files to include")" Value="@this.DataSource.EffectiveFilePatterns" Lines="1" MaxLines="@FilePatternSelection.MAX_FIELD_LINES" ClipboardTooltipSubject="@T("the file patterns")"/>
<TextInfoLines Icon="@Icons.Material.Filled.FolderOff" Label="@T("Excluded folders")" Value="@this.Exclusions.Folders" Lines="1" MaxLines="@FilePatternSelection.MAX_FIELD_LINES" ClipboardTooltipSubject="@T("the excluded folders")"/>
<TextInfoLines Icon="@Icons.Material.Filled.FilterAltOff" Label="@T("Excluded files")" Value="@this.Exclusions.Files" Lines="1" MaxLines="@FilePatternSelection.MAX_FIELD_LINES" ClipboardTooltipSubject="@T("the excluded files")"/>
<TextInfoLine Icon="@Icons.Material.Filled.Straighten" Label="@T("Ignore files larger than")" Value="@FolderFileSelection.MegabytesToBytes(this.DataSource.EffectiveMaxFileSizeMegabytes).FileSize()" ClipboardTooltipSubject="@T("the size limit for files")"/>
@if (!this.IsDirectoryAvailable)
{
@@ -66,4 +67,4 @@
@T("Close")
</MudButton>
</DialogActions>
</MudDialog>
</MudDialog>
@@ -60,6 +60,8 @@ public partial class DataSourceLocalDirectoryInfoDialog : MSGComponentBase
private bool IsDirectoryAvailable => this.directoryInfo.Exists;
private FileExclusionGroups Exclusions => FileExclusions.Group(this.DataSource.EffectiveExcludePatterns);
/// <summary>
/// The level which applies, including what the organization demands.
/// </summary>
@@ -10,12 +10,15 @@
<MudForm @ref="@this.form" @bind-IsValid="@this.dataIsValid" @bind-Errors="@this.dataIssues" FieldChanged="@this.RevalidateAfterFieldChange">
<DataSourceMissingEmbeddingWarning IsInDataSourceDialog="@true" EmbeddingProviderAdded="@this.SelectAddedEmbeddingProvider"/>
<MudText Typo="Typo.h6" Class="mb-3">
@T("Data source")
</MudText>
@* ReSharper disable once CSharpWarnings::CS8974 *@
<MudTextField
T="string"
@bind-Text="@this.dataName"
Label="@T("Data Source Name")"
Class="mb-6"
Class="mb-3"
MaxLength="@DataSourceValidation.MAX_NAME_LENGTH"
Counter="@DataSourceValidation.MAX_NAME_LENGTH"
Immediate="@true"
@@ -31,7 +34,7 @@
T="string"
@bind-Text="@this.dataDescription"
Label="@T("Description")"
Class="mb-6"
Class="mb-0"
MaxLength="200"
Counter="200"
Immediate="@true"
@@ -44,14 +47,20 @@
Lines="3"
/>
<MudJustifiedText Typo="Typo.body1" Class="mb-3">
<MudText Typo="Typo.h6" Class="mb-3 mt-6">
@T("File")
</MudText>
<MudJustifiedText Typo="Typo.body2" 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.")
</MudJustifiedText>
<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" />
<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.")
</MudJustifiedText>
<MudText Typo="Typo.h6" Class="mb-3 mt-6">
@T("Embedding")
</MudText>
<MudText Typo="Typo.body2" Class="mb-3">
@T("Choose an embedding provider to make this data source searchable.")
</MudText>
<MudSelect @ref="@this.embeddingSelect" @bind-Value="@this.dataEmbeddingId" Label="@T("Embedding")" Class="mb-3" OpenIcon="@Icons.Material.Filled.ExpandMore" AdornmentColor="Color.Info" Adornment="Adornment.Start" Validation="@this.dataSourceValidation.ValidateEmbeddingProviderAccess">
@foreach (var provider in this.SettingsManager.GetAllEmbeddingProviders())
{
@@ -67,14 +76,17 @@
}
else if (!string.IsNullOrWhiteSpace(this.dataEmbeddingId))
{
<MudJustifiedText Typo="Typo.body1" Color="Color.Tertiary" Class="mb-3">
<MudJustifiedText Typo="Typo.body2" Color="Color.Tertiary" Class="mb-3">
@T("The embedding you selected runs locally or in your organization. Your data is not sent to the cloud.")
</MudJustifiedText>
}
<ManagePandocDependency IntroText="@T("For some data types, such as Office files, MindWork AI Studio requires the open-source application Pandoc.")"/>
<ManagePandocDependency IntroText="@T("MindWork AI Studio uses Pandoc to read HTML files.")"/>
<MudSelect @ref="@this.confidenceLevelSelect" @bind-Value="@this.dataConfidenceLevel" Text="@this.dataConfidenceLevel.GetName()" Label="@T("Required provider confidence level")" Class="mb-3" OpenIcon="@Icons.Material.Filled.ExpandMore" AdornmentColor="Color.Info" Adornment="Adornment.Start" Validation="@this.dataSourceValidation.ValidateDataSourceConfidenceLevel">
<MudText Typo="Typo.h6" Class="mb-3 mt-6">
@T("Data sharing")
</MudText>
<MudSelect @ref="@this.confidenceLevelSelect" @bind-Value="@this.dataConfidenceLevel" Text="@this.dataConfidenceLevel.GetName()" Label="@T("Required provider confidence level")" HelperText="@T("Only providers with this confidence level or higher may receive and process data from this data source.")" Class="mb-3" OpenIcon="@Icons.Material.Filled.ExpandMore" AdornmentColor="Color.Info" Adornment="Adornment.Start" Validation="@this.dataSourceValidation.ValidateDataSourceConfidenceLevel">
@foreach (var level in this.ConfidenceLevels)
{
<MudSelectItem Value="@level.Value">
@@ -98,6 +110,10 @@
</MudButton>
<MudDivider/>
<MudCollapse Expanded="@this.showExpertSettings" Class="@this.GetExpertStyles">
<MudText Typo="Typo.subtitle2" Class="mb-2">@T("Text chunking")</MudText>
<MudJustifiedText Typo="Typo.body2" Class="mb-3">
@T("Optional expert settings for how this data source is split before embedding.")
</MudJustifiedText>
@if (!string.IsNullOrWhiteSpace(this.dataEmbeddingId))
{
<MudTextField
@@ -111,9 +127,6 @@
AdornmentIcon="@Icons.Material.Filled.TextFields"
AdornmentColor="Color.Info"/>
}
<MudJustifiedText Typo="Typo.body1" Class="mb-3">
@T("Optional expert settings for how this data source is split before embedding.")
</MudJustifiedText>
<MudNumericField
@ref="@this.maxChunkTokenLengthField"
T="int"
@@ -141,6 +154,7 @@
Adornment="Adornment.Start"
AdornmentIcon="@Icons.Material.Filled.CompareArrows"
AdornmentColor="Color.Info"/>
<MudText Typo="Typo.subtitle2" Class="mb-2 mt-4">@T("Search results")</MudText>
<MudNumericField
T="ushort"
Min="10"
@@ -20,9 +20,18 @@
<MudText Typo="Typo.h6" Class="mb-3">@T("Input")</MudText>
<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"/>
<MudText Typo="Typo.subtitle2" Class="mb-2 mt-4">@T("Hidden files and folders")</MudText>
<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 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"/>
<MudText Typo="Typo.subtitle2" Class="mb-2 mt-4">@T("Files to include")</MudText>
<FilePatternSelection>
<AllowedContent>
<ConfigurationText Compact="@true" ExpandToContent="@true" OptionDescription="@T("Files to include")" 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")" ResetButtonIconOnly="@true" 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"/>
</AllowedContent>
<ExcludedContent>
<ConfigurationFileExclusions Compact="@true" ShowHelp="@false" OptionDescription="@T("Default excluded files and folders")" Disabled="@this.DefaultsDisabled" Text="@(() => this.SettingsManager.ConfigurationData.BatchProcessing.ExcludePatterns)" TextUpdate="@(value => this.SettingsManager.ConfigurationData.BatchProcessing.ExcludePatterns = value)" Validation="@FileExclusions.Validate" ResetValue="@(() => FileExclusions.DEFAULT_PATTERNS)" ResetButtonText="@T("Restore default")" IsLocked="() => ManagedConfiguration.TryGet(x => x.BatchProcessing, x => x.ExcludePatterns, out var meta) && meta.IsLocked"/>
</ExcludedContent>
</FilePatternSelection>
<MudText Typo="Typo.subtitle2" Class="mb-2 mt-4">@T("File size limit")</MudText>
<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"/>
<MudText Typo="Typo.h6" Class="mb-3 mt-6">@T("Instructions")</MudText>
@@ -438,9 +438,15 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING
-- Please provide a file name without a path, e.g., my-results.csv
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T189587595"] = "Bitte geben Sie einen Dateinamen ohne Pfad an, z. B. meine-ergebnisse.csv"
-- Hidden files and folders
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T1902969218"] = "Versteckte Dateien und Ordner"
-- Select the folder containing your documents
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T1926838679"] = "Wählen Sie den Ordner mit den Dokumenten aus"
-- Files to include
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T2000156297"] = "Einbezogene Dateien"
-- Include subfolders?
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T2086334687"] = "Unterordner einbeziehen?"
@@ -486,9 +492,6 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING
-- Was not able to read the existing transcript: {0}
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T2397111152"] = "Das vorhandene Transkript konnte nicht gelesen werden: {0}"
-- File patterns
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T2460883298"] = "Dateiendungen"
-- Load prompt from file
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T2474257795"] = "Prompt aus Datei laden"
@@ -540,6 +543,9 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING
-- 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."
-- File size limit
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T3225743883"] = "Maximale Dateigröße"
-- 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."
@@ -570,9 +576,6 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING
-- Processing pace
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T3428873429"] = "Verarbeitungsgeschwindigkeit"
-- Which files should be processed? Separate multiple patterns with a semicolon, e.g., *.pdf;*.docx. Use :documents: for every document and readable text file, :text: for readable text files only, or * for every file.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T3436936162"] = "Welche Dateien sollen verarbeitet werden? Trennen Sie mehrere Muster durch ein Semikolon, z. B. *.pdf;*.docx. Verwenden Sie :documents: für alle Dokumente und lesbaren Textdateien, :text: nur für lesbare Textdateien oder * für alle Dateien."
-- 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."
@@ -609,9 +612,6 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING
-- Was not able to write '{0}'. Please make sure that the file is not opened in another application. The results of this run are incomplete on disk. The message is: '{1}'
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T3899869356"] = "'{0}' konnte nicht geschrieben werden. Bitte stellen Sie sicher, dass die Datei nicht in einem anderen Programm geöffnet ist. Die Ergebnisse dieses Laufs sind auf der Festplatte unvollständig. Die Meldung lautet: '{1}'"
-- 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.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T3934601817"] = "Diese Dateien und Ordner werden auch dann ausgeschlossen, wenn ein Dateimuster auf sie zutrifft. Trennen Sie die Einträge durch ein Semikolon oder einen Zeilenumbruch. Ein Eintrag mit einem Schrägstrich am Ende schließt alle Ordner mit diesem Namen aus, z. B. node_modules/. Alle anderen Einträge beziehen sich auf Dateien, z. B. *.min.js."
-- Select the file with your instructions
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T3943995624"] = "Datei mit Ihren Anweisungen auswählen"
@@ -4354,7 +4354,7 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCEADDBUTTONS::T381231449"] = "Dat
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCEADDBUTTONS::T3859543867"] = "Postfach (IMAP)"
-- Add ERI v1 Data Source
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCEADDBUTTONS::T590005498"] = "ERI v1 Datenquelle hinzufügen"
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCEADDBUTTONS::T590005498"] = "ERI-v1-Datenquelle hinzufügen"
-- External Data (ERI-Server v1)
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCEADDBUTTONS::T774473996"] = "Externe Daten (ERI-Server v1)"
@@ -4459,7 +4459,7 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCEMANAGEMENT::T2352821039"] = "AI
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCEMANAGEMENT::T2511157475"] = "Lokale Datenquellen werden aktualisiert, wenn sich Dateien ändern, Postfächer alle 16 Minuten."
-- ERI v1 Data Source Information
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCEMANAGEMENT::T26243729"] = "ERI v1 Datenquellen-Informationen"
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCEMANAGEMENT::T26243729"] = "Informationen zur ERI-v1-Datenquelle"
-- Name
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCEMANAGEMENT::T266367750"] = "Name"
@@ -4773,9 +4773,57 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::FILEACTIONS::T3596972550"] = "Diese Webse
-- Open the file in the program your system uses for it
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::FILEACTIONS::T4218155268"] = "Datei in dem Programm öffnen, das Ihr System dafür verwendet"
-- Exclusion help and examples
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::FILEEXCLUSIONEDITOR::T1309128010"] = "Hilfe und Beispiele zu Ausschlussregeln"
-- Folders
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::FILEEXCLUSIONEDITOR::T1412427146"] = "Ordner"
-- Excluded files and folders
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::FILEEXCLUSIONEDITOR::T286958948"] = "Ausgeschlossene Dateien und Ordner"
-- Files
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::FILEEXCLUSIONEDITOR::T3877444400"] = "Dateien"
-- 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/.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::FILEEXCLUSIONEDITOR::T499835724"] = "Diese Dateien und Ordner werden auch dann ausgeschlossen, wenn ein Dateimuster auf sie zutrifft. Trennen Sie die Einträge durch ein Semikolon oder einen Zeilenumbruch. Ein Eintrag mit einem Schrägstrich am Ende schließt alle Ordner mit diesem Namen aus, z. B. node_modules/."
-- Restore default
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::FILEEXCLUSIONEDITOR::T913168756"] = "Standard wiederherstellen"
-- Saving '{0}'...
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::FILEEXPORTRUNNINGMESSAGE::T1063135893"] = "„{0}“ wird gespeichert …"
-- Exclude folders ending in build, such as build or app-build
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::FILEPATTERNHELP::T1241240542"] = "Ordner ausschließen, die auf „build“ enden, z. B. „build“ oder „app-build“"
-- Include readable text files, even those without an extension
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::FILEPATTERNHELP::T1482113736"] = "Lesbare Textdateien einbeziehen, auch solche ohne Dateiendung"
-- File names starting with draft-
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::FILEPATTERNHELP::T1676890987"] = "Dateinamen, die mit „draft-“ beginnen"
-- Pattern examples
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::FILEPATTERNHELP::T2430822600"] = "Beispiele für Dateimuster"
-- Include all files, even audio and video
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::FILEPATTERNHELP::T2681459563"] = "Alle Dateien einbeziehen, auch Audio- und Videodateien"
-- Separate patterns with semicolons. Exclusions take priority over included files.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::FILEPATTERNHELP::T2728429336"] = "Trennen Sie Muster durch Semikolons. Ausschlüsse haben Vorrang vor einbezogenen Dateien."
-- PDF and Word files
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::FILEPATTERNHELP::T3467839719"] = "PDF- und Word-Dateien"
-- Patterns match file names regardless of letter case. Folder paths are not supported.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::FILEPATTERNHELP::T3691425703"] = "Muster gelten für Dateinamen, unabhängig von Groß- und Kleinschreibung. Ordnerpfade werden nicht unterstützt."
-- Include all documents and readable text files
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::FILEPATTERNHELP::T967949655"] = "Alle Dokumente und lesbaren Textdateien einbeziehen"
-- Pattern help and examples
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::FILEPATTERNSELECTION::T1369669520"] = "Hilfe und Beispiele zu Dateimustern"
-- LLMs can make mistakes. Check important information.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::HALLUZINATIONREMINDER::T3528806904"] = "LLMs können Fehler machen. Überprüfen Sie wichtige Informationen."
@@ -4812,12 +4860,6 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::MANAGEDTOOLSWARNING::T3008114108"] = "Fü
-- Some tools selected for this run are switched off in your tool settings and stay unused: {0}. Switch them on again in the app settings to use them.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::MANAGEDTOOLSWARNING::T3803950041"] = "Einige der für diesen Durchlauf ausgewählten Werkzeuge sind in Ihren Werkzeugeinstellungen deaktiviert und bleiben daher ungenutzt: {0}. Aktivieren Sie sie in den App-Einstellungen wieder, um sie zu nutzen."
-- Your Pandoc installation meets the requirements.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::MANAGEPANDOCDEPENDENCY::T1167365374"] = "Ihre Pandoc-Installation erfüllt die Anforderungen."
-- Please wait while we check the availability of Pandoc.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::MANAGEPANDOCDEPENDENCY::T1336364299"] = "Bitte warten Sie, während wir die Verfügbarkeit von Pandoc überprüfen."
-- Pandoc Installation
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::MANAGEPANDOCDEPENDENCY::T185447014"] = "Pandoc-Installation"
@@ -4830,9 +4872,6 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::MANAGEPANDOCDEPENDENCY::T3147551473"] = "
-- Update Pandoc
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::MANAGEPANDOCDEPENDENCY::T3249965383"] = "Pandoc aktualisieren"
-- Check your Pandoc installation
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::MANAGEPANDOCDEPENDENCY::T527187983"] = "Überprüfen Sie Ihre Pandoc-Installation"
-- Install Pandoc
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::MANAGEPANDOCDEPENDENCY::T986578435"] = "Pandoc installieren"
@@ -7233,6 +7272,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCEERIV1USERNAMEPASSWORDEXPORTDIALOG:
-- Cancel
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCEERIV1USERNAMEPASSWORDEXPORTDIALOG::T900713019"] = "Abbrechen"
-- Folder
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T104168189"] = "Ordner"
-- Hide Expert Settings
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T1108876344"] = "Experten-Einstellungen ausblenden"
@@ -7251,6 +7293,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T1311820297"
-- Selected base directory for this data source
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T1312296210"] = "Ausgewähltes Stammverzeichnis für diese Datenquelle"
-- MindWork AI Studio uses Pandoc to read HTML files.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T133018120"] = "MindWork AI Studio verwendet Pandoc zum Lesen von HTML-Dateien."
-- No embedding selected
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T1359179968"] = "Keine Einbettung ausgewählt"
@@ -7281,23 +7326,32 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T1827669611"
-- Update
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T1847791252"] = "Aktualisieren"
-- Hidden files and folders
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T1902969218"] = "Versteckte Dateien und Ordner"
-- Files to include
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T2000156297"] = "Einbezogene Dateien"
-- Data source
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T2034620186"] = "Datenquelle"
-- Include subfolders?
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T2086334687"] = "Unterordner einbeziehen?"
-- The overlap must be smaller than the effective token limit.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T2101951526"] = "Die Überlappung muss kleiner sein als das effektive Token-Limit."
-- Select the file patterns this data source should index.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T2244471011"] = "Legen Sie mit Dateimustern fest, welche Dateien diese Datenquelle indexiert."
-- Required provider confidence level
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T236253137"] = "Erforderliches Vertrauensniveau des Anbieters"
-- Please enter a token limit of at least 1.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T2406580478"] = "Bitte geben Sie ein Token-Limit von mindestens 1 ein."
-- File patterns
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T2460883298"] = "Dateimuster"
-- Search results
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T2499087427"] = "Suchergebnisse"
-- Data sharing
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T2623330465"] = "Weitergabe der Daten"
-- Add
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T2646845972"] = "Hinzufügen"
@@ -7308,6 +7362,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T2814869210"
-- Embedding
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T2838542994"] = "Einbettung"
-- Choose an embedding provider to make this data source searchable.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T2861534545"] = "Wählen Sie einen Einbettungsanbieter aus, um diese Datenquelle durchsuchbar zu machen."
-- Excluded files and folders
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T286958948"] = "Ausgeschlossene Dateien und Ordner"
@@ -7317,18 +7374,21 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T2961294165"
-- 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.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T3017942214"] = "Audio- und Videodateien, die diese Datenquelle auswählt, werden übersprungen, weil Ihr Transkriptionsanbieter das Vertrauensniveau nicht erfüllt, das diese Datenquelle erfordert."
-- Text chunking
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T3105524153"] = "Aufteilung in Blöcke"
-- Audio and video files which this data source selects are skipped until you choose a transcription provider in the settings.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T3119708878"] = "Audio- und Videodateien, die diese Datenquelle auswählt, werden übersprungen, bis Sie in den Einstellungen einen Transkriptionsanbieter auswählen."
-- File size limit
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T3225743883"] = "Maximale Dateigröße"
-- Please enter 0 or a positive overlap length.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T3242265813"] = "Bitte geben Sie 0 oder eine positive Überlappungslänge ein."
-- Ignore files larger than
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T3353866230"] = "Dateien ignorieren, die größer sind als"
-- For some data types, such as Office files, MindWork AI Studio requires the open-source application Pandoc.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T3359366900"] = "Für einige Dateitypen, wie zum Beispiel Office-Dateien, benötigt MindWork AI Studio die Open-Source-Anwendung Pandoc."
-- Show Expert Settings
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T3361153305"] = "Experten-Einstellungen anzeigen"
@@ -7341,21 +7401,21 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T3584816527"
-- This setting decides where a chat may still send data once the AI found something in this data source, so that its content does not end up in a web search, for example. Choose no restriction only for content anybody may read anyway.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T3738459730"] = "Mit dieser Einstellung legen Sie fest, wohin ein Chat noch Daten senden darf, nachdem die KI etwas in dieser Datenquelle gefunden hat, damit deren Inhalte zum Beispiel nicht in einer Websuche landen. Wählen Sie „Keine Einschränkung“ nur für Inhalte, die ohnehin jeder lesen darf."
-- 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.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T3934601817"] = "Diese Dateien und Ordner werden auch dann ausgeschlossen, wenn ein Dateimuster auf sie zutrifft. Trennen Sie die Einträge durch ein Semikolon oder einen Zeilenumbruch. Ein Eintrag mit einem Schrägstrich am Ende schließt alle Ordner mit diesem Namen aus, z. B. node_modules/. Alle anderen Einträge beziehen sich auf Dateien, z. B. *.min.js."
-- Only providers with this confidence level or higher may receive and process data from this data source.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T3755939304"] = "Nur Anbieter mit mindestens diesem Vertrauensniveau dürfen Daten dieser Datenquelle empfangen und verarbeiten."
-- In order for the AI to be able to determine the appropriate data at any time, you must choose an embedding provider.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T529871858"] = "Damit die KI jederzeit die passenden Daten ermitteln kann, müssen Sie einen Einbettungsanbieter auswählen."
-- Select the file patterns this data source should index. Audio and video files, e.g., *.mp3 or *.mp4, are transcribed by your transcription provider.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T4289641992"] = "Wählen Sie die Dateimuster aus, die diese Datenquelle indexieren soll. Audio- und Videodateien, z. B. *.mp3 oder *.mp4, werden von Ihrem Transkriptionsanbieter transkribiert."
-- Select the base directory
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T562479068"] = "Wählen Sie das Stammverzeichnis aus"
-- Which files should be indexed? Separate multiple patterns with a semicolon, e.g., *.txt;*.pdf;*.docx;.gitignore. Use :documents: for every document and readable text file, :text: for readable text files only, or * for every file. Audio and video files, e.g., *.mp3 or *.mp4, are transcribed by your transcription provider.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T599406584"] = "Welche Dateien sollen indexiert werden? Trennen Sie mehrere Muster durch ein Semikolon, z. B. *.txt;*.pdf;*.docx;.gitignore. Verwenden Sie :documents: für alle Dokumente und lesbaren Textdateien, :text: nur für lesbare Textdateien oder * für alle Dateien. Audio- und Videodateien, z. B. *.mp3 oder *.mp4, werden von Ihrem Transkriptionsanbieter transkribiert."
-- Yes, index hidden files and folders as well
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T607153623"] = "Ja, auch versteckte Dateien und Ordner indexieren"
-- Restore default patterns
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T7425959"] = "Standardmuster wiederherstellen"
-- The data source token limit must not be larger than the embedding provider token limit ({0}).
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T787118522"] = "Das Token-Limit der Datenquelle darf nicht größer sein als das Token-Limit des Einbettungsanbieters ({0})."
@@ -7395,6 +7455,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYINFODIALOG::T1447415
-- No
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYINFODIALOG::T1642511898"] = "Nein"
-- the excluded folders
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYINFODIALOG::T1647237555"] = "die ausgeschlossenen Ordner"
-- Include subfolders
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYINFODIALOG::T1710166842"] = "Unterordner einbeziehen"
@@ -7407,6 +7470,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYINFODIALOG::T1725856
-- The embedding runs locally or in your organization. Your data is not sent to the cloud.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYINFODIALOG::T1950544032"] = "Die Einbettung läuft lokal oder in ihrer Organisation. Ihre Daten werden nicht in die Cloud gesendet."
-- Files to include
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYINFODIALOG::T2000156297"] = "Einbezogene Dateien"
-- the file patterns
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYINFODIALOG::T2013714981"] = "die Dateimuster"
@@ -7419,24 +7485,21 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYINFODIALOG::T2333573
-- Required provider confidence level
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYINFODIALOG::T236253137"] = "Erforderliches Vertrauensniveau des Anbieters"
-- File patterns
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYINFODIALOG::T2460883298"] = "Dateimuster"
-- the maximum number of matches per query
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYINFODIALOG::T2479753122"] = "die maximale Anzahl an Treffern pro Abfrage"
-- the total size of matching files
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYINFODIALOG::T2515807708"] = "die Gesamtgröße der passenden Dateien"
-- Excluded files
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYINFODIALOG::T2590452128"] = "Ausgeschlossene Dateien"
-- the description
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYINFODIALOG::T2658359966"] = "Die Beschreibung"
-- the data source name
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYINFODIALOG::T2717738728"] = "den Namen der Datenquelle"
-- Excluded files and folders
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYINFODIALOG::T286958948"] = "Ausgeschlossene Dateien und Ordner"
-- The directory chosen for the data source does not exist anymore. Please edit the data source and correct the path.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYINFODIALOG::T2875614207"] = "Das für die Datenquelle gewählte Verzeichnis existiert nicht mehr. Bitte bearbeiten Sie die Datenquelle und korrigieren Sie den Pfad."
@@ -7461,6 +7524,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYINFODIALOG::T3127438
-- Embedding name
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYINFODIALOG::T3153201045"] = "Name der Einbettung"
-- Excluded folders
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYINFODIALOG::T3345276250"] = "Ausgeschlossene Ordner"
-- Ignore files larger than
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYINFODIALOG::T3353866230"] = "Dateien ignorieren, die größer sind als"
@@ -7482,8 +7548,8 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYINFODIALOG::T3818527
-- Path
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYINFODIALOG::T3949388886"] = "Pfad"
-- the excluded files and folders
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYINFODIALOG::T4001527349"] = "die ausgeschlossenen Dateien und Ordner"
-- the excluded files
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYINFODIALOG::T4083345389"] = "die ausgeschlossenen Dateien"
-- The directory chosen for the data source exists.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYINFODIALOG::T445858624"] = "Das ausgewählte Verzeichnis für die Datenquelle ist vorhanden."
@@ -7500,6 +7566,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T1133561850"] = "
-- Default tokenizer
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T1220918127"] = "Standard-Tokenizer"
-- MindWork AI Studio uses Pandoc to read HTML files.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T133018120"] = "MindWork AI Studio verwendet Pandoc zum Lesen von HTML-Dateien."
-- No embedding selected
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T1359179968"] = "Keine Einbettung ausgewählt"
@@ -7521,6 +7590,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T1827669611"] = "
-- Update
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T1847791252"] = "Aktualisieren"
-- Data source
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T2034620186"] = "Datenquelle"
-- The overlap must be smaller than the effective token limit.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T2101951526"] = "Die Überlappung muss kleiner sein als das effektive Token-Limit."
@@ -7533,6 +7605,12 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T2406580478"] = "
-- This audio or video file is skipped, because your transcription provider does not meet the confidence level this data source requires.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T2468760910"] = "Diese Audio- oder Videodatei wird übersprungen, weil Ihr Transkriptionsanbieter das Vertrauensniveau nicht erfüllt, das diese Datenquelle erfordert."
-- Search results
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T2499087427"] = "Suchergebnisse"
-- Data sharing
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T2623330465"] = "Weitergabe der Daten"
-- Add
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T2646845972"] = "Hinzufügen"
@@ -7545,15 +7623,18 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T2838542994"] = "
-- Describe what data this file contains to help the AI select it.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T2859265837"] = "Beschreiben Sie, welche Daten diese Datei enthält, um der KI bei der Auswahl zu helfen."
-- Choose an embedding provider to make this data source searchable.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T2861534545"] = "Wählen Sie einen Einbettungsanbieter aus, um diese Datenquelle durchsuchbar zu machen."
-- Token limit
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T2961294165"] = "Token-Limit"
-- Text chunking
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T3105524153"] = "Aufteilung in Blöcke"
-- Please enter 0 or a positive overlap length.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T3242265813"] = "Bitte geben Sie 0 oder eine positive Überlappungslänge ein."
-- For some data types, such as Office files, MindWork AI Studio requires the open-source application Pandoc.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T3359366900"] = "Für einige Dateitypen, wie zum Beispiel Office-Dateien, benötigt MindWork AI Studio die Open-Source-Anwendung Pandoc."
-- Show Expert Settings
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T3361153305"] = "Experten-Einstellungen anzeigen"
@@ -7566,11 +7647,14 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T3738459730"] = "
-- Select the file
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T3740148848"] = "Datei auswählen"
-- Only providers with this confidence level or higher may receive and process data from this data source.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T3755939304"] = "Nur Anbieter mit mindestens diesem Vertrauensniveau dürfen Daten dieser Datenquelle empfangen und verarbeiten."
-- This audio or video file is skipped until you choose a transcription provider in the settings.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T4026150982"] = "Diese Audio- oder Videodatei wird übersprungen, bis Sie in den Einstellungen einen Transkriptionsanbieter auswählen."
-- In order for the AI to be able to determine the appropriate data at any time, you must choose an embedding provider.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T529871858"] = "Damit die KI jederzeit die passenden Daten ermitteln kann, müssen Sie einen Einbettungsanbieter auswählen."
-- File
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T723007075"] = "Datei"
-- The data source token limit must not be larger than the embedding provider token limit ({0}).
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T787118522"] = "Das Token-Limit der Datenquelle darf nicht größer sein als das Token-Limit des Einbettungsanbieters ({0})."
@@ -9153,9 +9237,15 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T17
-- Batch processing options are preselected
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T1893713430"] = "Optionen für die Stapelverarbeitung sind vorausgewählt"
-- Hidden files and folders
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T1902969218"] = "Versteckte Dateien und Ordner"
-- Default custom column separator
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T19367494"] = "Standardmäßiges benutzerdefiniertes Trennzeichen für Spalten"
-- Files to include
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T2000156297"] = "Einbezogene Dateien"
-- Default document analysis policy
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T2015391667"] = "Standardregelwerk für die Dokumentenanalyse"
@@ -9204,9 +9294,6 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T28
-- Please enter exactly one punctuation or symbol character. Letters, numbers, spaces, quotation marks, and line breaks cannot be used as CSV separators.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T2908365499"] = "Bitte geben Sie genau ein Satz- oder Sonderzeichen ein. Buchstaben, Zahlen, Leerzeichen, Anführungszeichen und Zeilenumbrüche können nicht als CSV-Trennzeichen verwendet werden."
-- Default file patterns
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T2909693903"] = "Standard-Dateimuster"
-- Only the selected folder is processed
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T2958253681"] = "Nur der ausgewählte Ordner wird verarbeitet."
@@ -9222,6 +9309,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T31
-- These instructions are applied to every document of a new batch run.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T3195548336"] = "Diese Anweisungen werden auf jedes Dokument eines neuen Stapelverarbeitungsdurchlaufs angewendet."
-- File size limit
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T3225743883"] = "Maximale Dateigröße"
-- Ignore files larger than
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T3353866230"] = "Dateien ignorieren, die größer sind als"
@@ -9252,9 +9342,6 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T37
-- Default results table name
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T3816237687"] = "Standardname der Ergebnistabelle"
-- 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.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T3934601817"] = "Diese Dateien und Ordner werden auch dann ausgeschlossen, wenn ein Dateimuster auf sie zutrifft. Trennen Sie die Einträge durch ein Semikolon oder einen Zeilenumbruch. Ein Eintrag mit einem Schrägstrich am Ende schließt alle Ordner mit diesem Namen aus, z. B. node_modules/. Alle anderen Einträge beziehen sich auf Dateien, z. B. *.min.js."
-- Default Markdown instructions file
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T3967465682"] = "Standarddatei für Markdown-Anweisungen"
@@ -12952,7 +13039,7 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATABASES::VECTORSTORE::QDRANTEDGECLIENTIMPLEM
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"
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"
@@ -13158,6 +13245,12 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::EXTERNALHTTPCLIENTTIMEOUT::T599774443"] = "Das
-- policy files
UI_TEXT_CONTENT["AISTUDIO::TOOLS::EXTERNALHTTPCLIENTTIMEOUT::T632340680"] = "Richtliniendateien"
-- Enter folder patterns ending with / in 'Folders'. File patterns must not end with /.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::FILEEXCLUSIONS::T2019378403"] = "Geben Sie Ordnermuster, die mit / enden, im Feld „Ordner“ ein. Dateimuster dürfen nicht mit / enden."
-- Folder patterns must end with /, e.g., build/. Enter file patterns such as *.pdf in 'Files'.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::FILEEXCLUSIONS::T2695719308"] = "Ordnermuster müssen mit / enden, z. B. build/. Geben Sie Dateimuster wie *.pdf im Feld „Dateien“ ein."
-- Selectors such as :text: cannot be excluded. Please use file name patterns such as *.min.js or folder names such as bin/.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::FILEEXCLUSIONS::T405437626"] = "Selektoren wie :text: können nicht ausgeschlossen werden. Bitte verwenden Sie Dateinamensmuster wie *.min.js oder Ordnernamen wie bin/."
@@ -13386,9 +13479,6 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::FILENAMEPATTERNS::T2207479763"] = "Eines der D
-- Please provide at least one file pattern, e.g., :documents: or *.txt. Separate multiple patterns with a semicolon.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::FILENAMEPATTERNS::T3371062811"] = "Bitte geben Sie mindestens ein Dateimuster an, z. B. :documents: oder *.txt. Trennen Sie mehrere Muster durch ein Semikolon."
-- Please remove empty file patterns. Separate valid patterns with a single semicolon.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::FILENAMEPATTERNS::T368919579"] = "Bitte entfernen Sie leere Dateimuster. Trennen Sie gültige Muster durch ein einzelnes Semikolon."
-- Please use only single asterisks as wildcards, e.g., *.txt or report-*.md.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::FILENAMEPATTERNS::T495100949"] = "Bitte verwenden Sie als Platzhalter nur einzelne Sternchen, z. B. *.txt oder report-*.md."
@@ -438,9 +438,15 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING
-- Please provide a file name without a path, e.g., my-results.csv
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T189587595"] = "Please provide a file name without a path, e.g., my-results.csv"
-- Hidden files and folders
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T1902969218"] = "Hidden files and folders"
-- Select the folder containing your documents
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T1926838679"] = "Select the folder containing your documents"
-- Files to include
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T2000156297"] = "Files to include"
-- Include subfolders?
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T2086334687"] = "Include subfolders?"
@@ -486,9 +492,6 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING
-- Was not able to read the existing transcript: {0}
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T2397111152"] = "Was not able to read the existing transcript: {0}"
-- File patterns
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T2460883298"] = "File patterns"
-- Load prompt from file
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T2474257795"] = "Load prompt from file"
@@ -540,6 +543,9 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING
-- 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."
-- File size limit
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T3225743883"] = "File size limit"
-- 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."
@@ -570,9 +576,6 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING
-- Processing pace
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T3428873429"] = "Processing pace"
-- Which files should be processed? Separate multiple patterns with a semicolon, e.g., *.pdf;*.docx. Use :documents: for every document and readable text file, :text: for readable text files only, or * for every file.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T3436936162"] = "Which files should be processed? Separate multiple patterns with a semicolon, e.g., *.pdf;*.docx. Use :documents: for every document and readable text file, :text: for readable text files only, or * for every file."
-- 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'."
@@ -609,9 +612,6 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING
-- Was not able to write '{0}'. Please make sure that the file is not opened in another application. The results of this run are incomplete on disk. The message is: '{1}'
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T3899869356"] = "Was not able to write '{0}'. Please make sure that the file is not opened in another application. The results of this run are incomplete on disk. The message is: '{1}'"
-- 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.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T3934601817"] = "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."
-- Select the file with your instructions
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T3943995624"] = "Select the file with your instructions"
@@ -4773,9 +4773,57 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::FILEACTIONS::T3596972550"] = "Open this w
-- Open the file in the program your system uses for it
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::FILEACTIONS::T4218155268"] = "Open the file in the program your system uses for it"
-- Exclusion help and examples
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::FILEEXCLUSIONEDITOR::T1309128010"] = "Exclusion help and examples"
-- Folders
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::FILEEXCLUSIONEDITOR::T1412427146"] = "Folders"
-- Excluded files and folders
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::FILEEXCLUSIONEDITOR::T286958948"] = "Excluded files and folders"
-- Files
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::FILEEXCLUSIONEDITOR::T3877444400"] = "Files"
-- 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/.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::FILEEXCLUSIONEDITOR::T499835724"] = "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/."
-- Restore default
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::FILEEXCLUSIONEDITOR::T913168756"] = "Restore default"
-- Saving '{0}'...
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::FILEEXPORTRUNNINGMESSAGE::T1063135893"] = "Saving '{0}'..."
-- Exclude folders ending in build, such as build or app-build
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::FILEPATTERNHELP::T1241240542"] = "Exclude folders ending in build, such as build or app-build"
-- Include readable text files, even those without an extension
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::FILEPATTERNHELP::T1482113736"] = "Include readable text files, even those without an extension"
-- File names starting with draft-
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::FILEPATTERNHELP::T1676890987"] = "File names starting with draft-"
-- Pattern examples
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::FILEPATTERNHELP::T2430822600"] = "Pattern examples"
-- Include all files, even audio and video
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::FILEPATTERNHELP::T2681459563"] = "Include all files, even audio and video"
-- Separate patterns with semicolons. Exclusions take priority over included files.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::FILEPATTERNHELP::T2728429336"] = "Separate patterns with semicolons. Exclusions take priority over included files."
-- PDF and Word files
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::FILEPATTERNHELP::T3467839719"] = "PDF and Word files"
-- Patterns match file names regardless of letter case. Folder paths are not supported.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::FILEPATTERNHELP::T3691425703"] = "Patterns match file names regardless of letter case. Folder paths are not supported."
-- Include all documents and readable text files
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::FILEPATTERNHELP::T967949655"] = "Include all documents and readable text files"
-- Pattern help and examples
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::FILEPATTERNSELECTION::T1369669520"] = "Pattern help and examples"
-- LLMs can make mistakes. Check important information.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::HALLUZINATIONREMINDER::T3528806904"] = "LLMs can make mistakes. Check important information."
@@ -4812,12 +4860,6 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::MANAGEDTOOLSWARNING::T3008114108"] = "Too
-- Some tools selected for this run are switched off in your tool settings and stay unused: {0}. Switch them on again in the app settings to use them.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::MANAGEDTOOLSWARNING::T3803950041"] = "Some tools selected for this run are switched off in your tool settings and stay unused: {0}. Switch them on again in the app settings to use them."
-- Your Pandoc installation meets the requirements.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::MANAGEPANDOCDEPENDENCY::T1167365374"] = "Your Pandoc installation meets the requirements."
-- Please wait while we check the availability of Pandoc.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::MANAGEPANDOCDEPENDENCY::T1336364299"] = "Please wait while we check the availability of Pandoc."
-- Pandoc Installation
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::MANAGEPANDOCDEPENDENCY::T185447014"] = "Pandoc Installation"
@@ -4830,9 +4872,6 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::MANAGEPANDOCDEPENDENCY::T3147551473"] = "
-- Update Pandoc
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::MANAGEPANDOCDEPENDENCY::T3249965383"] = "Update Pandoc"
-- Check your Pandoc installation
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::MANAGEPANDOCDEPENDENCY::T527187983"] = "Check your Pandoc installation"
-- Install Pandoc
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::MANAGEPANDOCDEPENDENCY::T986578435"] = "Install Pandoc"
@@ -7233,6 +7272,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCEERIV1USERNAMEPASSWORDEXPORTDIALOG:
-- Cancel
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCEERIV1USERNAMEPASSWORDEXPORTDIALOG::T900713019"] = "Cancel"
-- Folder
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T104168189"] = "Folder"
-- Hide Expert Settings
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T1108876344"] = "Hide Expert Settings"
@@ -7251,6 +7293,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T1311820297"
-- Selected base directory for this data source
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T1312296210"] = "Selected base directory for this data source"
-- MindWork AI Studio uses Pandoc to read HTML files.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T133018120"] = "MindWork AI Studio uses Pandoc to read HTML files."
-- No embedding selected
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T1359179968"] = "No embedding selected"
@@ -7281,23 +7326,32 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T1827669611"
-- Update
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T1847791252"] = "Update"
-- Hidden files and folders
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T1902969218"] = "Hidden files and folders"
-- Files to include
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T2000156297"] = "Files to include"
-- Data source
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T2034620186"] = "Data source"
-- Include subfolders?
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T2086334687"] = "Include subfolders?"
-- The overlap must be smaller than the effective token limit.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T2101951526"] = "The overlap must be smaller than the effective token limit."
-- Select the file patterns this data source should index.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T2244471011"] = "Select the file patterns this data source should index."
-- Required provider confidence level
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T236253137"] = "Required provider confidence level"
-- Please enter a token limit of at least 1.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T2406580478"] = "Please enter a token limit of at least 1."
-- File patterns
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T2460883298"] = "File patterns"
-- Search results
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T2499087427"] = "Search results"
-- Data sharing
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T2623330465"] = "Data sharing"
-- Add
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T2646845972"] = "Add"
@@ -7308,6 +7362,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T2814869210"
-- Embedding
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T2838542994"] = "Embedding"
-- Choose an embedding provider to make this data source searchable.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T2861534545"] = "Choose an embedding provider to make this data source searchable."
-- Excluded files and folders
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T286958948"] = "Excluded files and folders"
@@ -7317,18 +7374,21 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T2961294165"
-- 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.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T3017942214"] = "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."
-- Text chunking
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T3105524153"] = "Text chunking"
-- Audio and video files which this data source selects are skipped until you choose a transcription provider in the settings.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T3119708878"] = "Audio and video files which this data source selects are skipped until you choose a transcription provider in the settings."
-- File size limit
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T3225743883"] = "File size limit"
-- Please enter 0 or a positive overlap length.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T3242265813"] = "Please enter 0 or a positive overlap length."
-- Ignore files larger than
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T3353866230"] = "Ignore files larger than"
-- For some data types, such as Office files, MindWork AI Studio requires the open-source application Pandoc.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T3359366900"] = "For some data types, such as Office files, MindWork AI Studio requires the open-source application Pandoc."
-- Show Expert Settings
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T3361153305"] = "Show Expert Settings"
@@ -7341,21 +7401,21 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T3584816527"
-- This setting decides where a chat may still send data once the AI found something in this data source, so that its content does not end up in a web search, for example. Choose no restriction only for content anybody may read anyway.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T3738459730"] = "This setting decides where a chat may still send data once the AI found something in this data source, so that its content does not end up in a web search, for example. Choose no restriction only for content anybody may read anyway."
-- 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.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T3934601817"] = "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."
-- Only providers with this confidence level or higher may receive and process data from this data source.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T3755939304"] = "Only providers with this confidence level or higher may receive and process data from this data source."
-- In order for the AI to be able to determine the appropriate data at any time, you must choose an embedding provider.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T529871858"] = "In order for the AI to be able to determine the appropriate data at any time, you must choose an embedding provider."
-- Select the file patterns this data source should index. Audio and video files, e.g., *.mp3 or *.mp4, are transcribed by your transcription provider.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T4289641992"] = "Select the file patterns this data source should index. Audio and video files, e.g., *.mp3 or *.mp4, are transcribed by your transcription provider."
-- Select the base directory
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T562479068"] = "Select the base directory"
-- Which files should be indexed? Separate multiple patterns with a semicolon, e.g., *.txt;*.pdf;*.docx;.gitignore. Use :documents: for every document and readable text file, :text: for readable text files only, or * for every file. Audio and video files, e.g., *.mp3 or *.mp4, are transcribed by your transcription provider.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T599406584"] = "Which files should be indexed? Separate multiple patterns with a semicolon, e.g., *.txt;*.pdf;*.docx;.gitignore. Use :documents: for every document and readable text file, :text: for readable text files only, or * for every file. Audio and video files, e.g., *.mp3 or *.mp4, are transcribed by your transcription provider."
-- Yes, index hidden files and folders as well
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T607153623"] = "Yes, index hidden files and folders as well"
-- Restore default patterns
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T7425959"] = "Restore default patterns"
-- The data source token limit must not be larger than the embedding provider token limit ({0}).
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T787118522"] = "The data source token limit must not be larger than the embedding provider token limit ({0})."
@@ -7395,6 +7455,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYINFODIALOG::T1447415
-- No
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYINFODIALOG::T1642511898"] = "No"
-- the excluded folders
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYINFODIALOG::T1647237555"] = "the excluded folders"
-- Include subfolders
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYINFODIALOG::T1710166842"] = "Include subfolders"
@@ -7407,6 +7470,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYINFODIALOG::T1725856
-- The embedding runs locally or in your organization. Your data is not sent to the cloud.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYINFODIALOG::T1950544032"] = "The embedding runs locally or in your organization. Your data is not sent to the cloud."
-- Files to include
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYINFODIALOG::T2000156297"] = "Files to include"
-- the file patterns
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYINFODIALOG::T2013714981"] = "the file patterns"
@@ -7419,24 +7485,21 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYINFODIALOG::T2333573
-- Required provider confidence level
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYINFODIALOG::T236253137"] = "Required provider confidence level"
-- File patterns
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYINFODIALOG::T2460883298"] = "File patterns"
-- the maximum number of matches per query
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYINFODIALOG::T2479753122"] = "the maximum number of matches per query"
-- the total size of matching files
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYINFODIALOG::T2515807708"] = "the total size of matching files"
-- Excluded files
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYINFODIALOG::T2590452128"] = "Excluded files"
-- the description
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYINFODIALOG::T2658359966"] = "the description"
-- the data source name
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYINFODIALOG::T2717738728"] = "the data source name"
-- Excluded files and folders
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYINFODIALOG::T286958948"] = "Excluded files and folders"
-- The directory chosen for the data source does not exist anymore. Please edit the data source and correct the path.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYINFODIALOG::T2875614207"] = "The directory chosen for the data source does not exist anymore. Please edit the data source and correct the path."
@@ -7461,6 +7524,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYINFODIALOG::T3127438
-- Embedding name
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYINFODIALOG::T3153201045"] = "Embedding name"
-- Excluded folders
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYINFODIALOG::T3345276250"] = "Excluded folders"
-- Ignore files larger than
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYINFODIALOG::T3353866230"] = "Ignore files larger than"
@@ -7482,8 +7548,8 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYINFODIALOG::T3818527
-- Path
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYINFODIALOG::T3949388886"] = "Path"
-- the excluded files and folders
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYINFODIALOG::T4001527349"] = "the excluded files and folders"
-- the excluded files
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYINFODIALOG::T4083345389"] = "the excluded files"
-- The directory chosen for the data source exists.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYINFODIALOG::T445858624"] = "The directory chosen for the data source exists."
@@ -7500,6 +7566,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T1133561850"] = "
-- Default tokenizer
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T1220918127"] = "Default tokenizer"
-- MindWork AI Studio uses Pandoc to read HTML files.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T133018120"] = "MindWork AI Studio uses Pandoc to read HTML files."
-- No embedding selected
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T1359179968"] = "No embedding selected"
@@ -7521,6 +7590,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T1827669611"] = "
-- Update
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T1847791252"] = "Update"
-- Data source
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T2034620186"] = "Data source"
-- The overlap must be smaller than the effective token limit.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T2101951526"] = "The overlap must be smaller than the effective token limit."
@@ -7533,6 +7605,12 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T2406580478"] = "
-- This audio or video file is skipped, because your transcription provider does not meet the confidence level this data source requires.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T2468760910"] = "This audio or video file is skipped, because your transcription provider does not meet the confidence level this data source requires."
-- Search results
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T2499087427"] = "Search results"
-- Data sharing
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T2623330465"] = "Data sharing"
-- Add
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T2646845972"] = "Add"
@@ -7545,15 +7623,18 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T2838542994"] = "
-- Describe what data this file contains to help the AI select it.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T2859265837"] = "Describe what data this file contains to help the AI select it."
-- Choose an embedding provider to make this data source searchable.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T2861534545"] = "Choose an embedding provider to make this data source searchable."
-- Token limit
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T2961294165"] = "Token limit"
-- Text chunking
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T3105524153"] = "Text chunking"
-- Please enter 0 or a positive overlap length.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T3242265813"] = "Please enter 0 or a positive overlap length."
-- For some data types, such as Office files, MindWork AI Studio requires the open-source application Pandoc.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T3359366900"] = "For some data types, such as Office files, MindWork AI Studio requires the open-source application Pandoc."
-- Show Expert Settings
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T3361153305"] = "Show Expert Settings"
@@ -7566,11 +7647,14 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T3738459730"] = "
-- Select the file
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T3740148848"] = "Select the file"
-- Only providers with this confidence level or higher may receive and process data from this data source.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T3755939304"] = "Only providers with this confidence level or higher may receive and process data from this data source."
-- This audio or video file is skipped until you choose a transcription provider in the settings.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T4026150982"] = "This audio or video file is skipped until you choose a transcription provider in the settings."
-- In order for the AI to be able to determine the appropriate data at any time, you must choose an embedding provider.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T529871858"] = "In order for the AI to be able to determine the appropriate data at any time, you must choose an embedding provider."
-- File
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T723007075"] = "File"
-- The data source token limit must not be larger than the embedding provider token limit ({0}).
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T787118522"] = "The data source token limit must not be larger than the embedding provider token limit ({0})."
@@ -9153,9 +9237,15 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T17
-- Batch processing options are preselected
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T1893713430"] = "Batch processing options are preselected"
-- Hidden files and folders
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T1902969218"] = "Hidden files and folders"
-- Default custom column separator
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T19367494"] = "Default custom column separator"
-- Files to include
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T2000156297"] = "Files to include"
-- Default document analysis policy
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T2015391667"] = "Default document analysis policy"
@@ -9204,9 +9294,6 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T28
-- Please enter exactly one punctuation or symbol character. Letters, numbers, spaces, quotation marks, and line breaks cannot be used as CSV separators.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T2908365499"] = "Please enter exactly one punctuation or symbol character. Letters, numbers, spaces, quotation marks, and line breaks cannot be used as CSV separators."
-- Default file patterns
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T2909693903"] = "Default file patterns"
-- Only the selected folder is processed
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T2958253681"] = "Only the selected folder is processed"
@@ -9222,6 +9309,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T31
-- These instructions are applied to every document of a new batch run.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T3195548336"] = "These instructions are applied to every document of a new batch run."
-- File size limit
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T3225743883"] = "File size limit"
-- Ignore files larger than
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T3353866230"] = "Ignore files larger than"
@@ -9252,9 +9342,6 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T37
-- Default results table name
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T3816237687"] = "Default results table name"
-- 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.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T3934601817"] = "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."
-- Default Markdown instructions file
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T3967465682"] = "Default Markdown instructions file"
@@ -13158,6 +13245,12 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::EXTERNALHTTPCLIENTTIMEOUT::T599774443"] = "The
-- policy files
UI_TEXT_CONTENT["AISTUDIO::TOOLS::EXTERNALHTTPCLIENTTIMEOUT::T632340680"] = "policy files"
-- Enter folder patterns ending with / in 'Folders'. File patterns must not end with /.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::FILEEXCLUSIONS::T2019378403"] = "Enter folder patterns ending with / in 'Folders'. File patterns must not end with /."
-- Folder patterns must end with /, e.g., build/. Enter file patterns such as *.pdf in 'Files'.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::FILEEXCLUSIONS::T2695719308"] = "Folder patterns must end with /, e.g., build/. Enter file patterns such as *.pdf in 'Files'."
-- Selectors such as :text: cannot be excluded. Please use file name patterns such as *.min.js or folder names such as bin/.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::FILEEXCLUSIONS::T405437626"] = "Selectors such as :text: cannot be excluded. Please use file name patterns such as *.min.js or folder names such as bin/."
@@ -13386,9 +13479,6 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::FILENAMEPATTERNS::T2207479763"] = "One of the
-- Please provide at least one file pattern, e.g., :documents: or *.txt. Separate multiple patterns with a semicolon.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::FILENAMEPATTERNS::T3371062811"] = "Please provide at least one file pattern, e.g., :documents: or *.txt. Separate multiple patterns with a semicolon."
-- Please remove empty file patterns. Separate valid patterns with a single semicolon.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::FILENAMEPATTERNS::T368919579"] = "Please remove empty file patterns. Separate valid patterns with a single semicolon."
-- Please use only single asterisks as wildcards, e.g., *.txt or report-*.md.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::FILENAMEPATTERNS::T495100949"] = "Please use only single asterisks as wildcards, e.g., *.txt or report-*.md."
@@ -0,0 +1,8 @@
namespace AIStudio.Tools;
/// <summary>
/// A list of excluded files and folders, split into the entries for folders and those for files.
/// </summary>
/// <param name="Folders">The entries which name folders, e.g., bin/, joined into one list.</param>
/// <param name="Files">The entries which name files, e.g., *.min.js, joined into one list.</param>
internal readonly record struct FileExclusionGroups(string Folders, string Files);
+46 -15
View File
@@ -25,6 +25,11 @@ internal static class FileExclusions
/// </summary>
public const char FOLDER_SUFFIX = '/';
/// <summary>
/// What AI Studio joins entries with when it writes a list, e.g., after an edit in the exclusion editor.
/// </summary>
public const string ENTRY_SEPARATOR = "; ";
//
// Folders of dependencies and build outputs which are not hidden. Hidden ones, such as .git,
// .venv, or .gradle, are left out by the rule for hidden files and folders anyway. Names which
@@ -65,7 +70,9 @@ internal static class FileExclusions
private const string DISK_IMAGE_FILES = "*.vmdk; *.vdi; *.vhd; *.vhdx; *.qcow2; *.hdd; *.hds; *.iso; *.dmg; *.img";
/// <summary>
/// The list a data source or the batch processing assistant starts with, one group per line.
/// The list a data source or the batch processing assistant starts with. Its line breaks only
/// group the entries for the reader of this code: the exclusion editor shows folders and files in
/// fields of their own.
/// </summary>
public const string DEFAULT_PATTERNS =
DEPENDENCY_AND_BUILD_FOLDERS + "\n" +
@@ -77,17 +84,6 @@ internal static class FileExclusions
DATABASE_FILES + "\n" +
DISK_IMAGE_FILES;
/// <summary>
/// How many lines the default list has, so that a field shows all of it at once.
/// </summary>
public static readonly int DEFAULT_LINE_COUNT = DEFAULT_PATTERNS.Split('\n').Length;
/// <summary>
/// How many lines a field for the list grows to before it scrolls: the default list, with room for
/// a few entries of the user.
/// </summary>
public const int MAX_LINE_COUNT = 12;
private const string OFFICE_LOCK_FILE_PATTERN = "~$*";
/// <summary>
@@ -112,6 +108,8 @@ internal static class FileExclusions
PATH,
RECURSIVE_WILDCARD,
INVALID_CHARACTER,
FILE_IN_FOLDER_LIST,
FOLDER_IN_FILE_LIST,
}
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(FileExclusions).Namespace, nameof(FileExclusions));
@@ -124,20 +122,50 @@ internal static class FileExclusions
.Distinct(StringComparer.OrdinalIgnoreCase)
.ToArray();
/// <summary>
/// Splits a list into the entries for folders and those for files, as the exclusion editor and the
/// information about a data source show them.
/// </summary>
public static FileExclusionGroups Group(string? patterns)
{
var entries = Parse(patterns);
return new(
string.Join(ENTRY_SEPARATOR, entries.Where(IsFolderEntry)),
string.Join(ENTRY_SEPARATOR, entries.Where(entry => !IsFolderEntry(entry))));
}
public static bool IsValid(string? patterns) => GetValidationIssue(patterns) is ValidationIssue.NONE;
public static bool IsValidFolders(string? patterns) => GetValidationIssue(patterns, folders: true) is ValidationIssue.NONE;
public static bool IsValidFiles(string? patterns) => GetValidationIssue(patterns, folders: false) is ValidationIssue.NONE;
/// <summary>
/// Validates a list of excluded files and folders. An empty list is valid: it excludes nothing
/// beyond what is built in.
/// </summary>
public static string? Validate(string? patterns) => GetValidationIssue(patterns) switch
public static string? Validate(string? patterns) => DescribeValidationIssue(GetValidationIssue(patterns));
/// <summary>
/// Validates the folder field without guessing whether an entry without a slash was meant as a folder.
/// </summary>
public static string? ValidateFolders(string? patterns) => DescribeValidationIssue(GetValidationIssue(patterns, folders: true));
/// <summary>
/// Validates the file field, directing folder rules to their own field.
/// </summary>
public static string? ValidateFiles(string? patterns) => DescribeValidationIssue(GetValidationIssue(patterns, folders: false));
private static string? DescribeValidationIssue(ValidationIssue issue) => issue switch
{
ValidationIssue.NONE => null,
ValidationIssue.SELECTOR => TB("Selectors such as :text: cannot be excluded. Please use file name patterns such as *.min.js or folder names such as bin/."),
ValidationIssue.PATH => TB("Please write excluded folders as a name without a path, ending with a slash, e.g., node_modules/ or bin/. Such an entry excludes every folder of that name."),
ValidationIssue.RECURSIVE_WILDCARD => TB("Please use only single asterisks as wildcards, e.g., *.min.js or cmake-build-*/."),
ValidationIssue.INVALID_CHARACTER => TB("One of the excluded files or folders contains an invalid character."),
_ => throw new ArgumentOutOfRangeException(nameof(patterns)),
ValidationIssue.FILE_IN_FOLDER_LIST => TB("Folder patterns must end with /, e.g., build/. Enter file patterns such as *.pdf in 'Files'."),
ValidationIssue.FOLDER_IN_FILE_LIST => TB("Enter folder patterns ending with / in 'Folders'. File patterns must not end with /."),
_ => throw new ArgumentOutOfRangeException(nameof(issue)),
};
/// <summary>
@@ -174,7 +202,7 @@ internal static class FileExclusions
private static bool IsFolderEntry(string entry) => entry.EndsWith(FOLDER_SUFFIX);
private static ValidationIssue GetValidationIssue(string? patterns)
private static ValidationIssue GetValidationIssue(string? patterns, bool? folders = null)
{
foreach (var entry in Parse(patterns))
{
@@ -193,6 +221,9 @@ internal static class FileExclusions
if (name.IndexOfAny(FileNamePatterns.INVALID_PATTERN_CHARACTERS) >= 0)
return ValidationIssue.INVALID_CHARACTER;
if (folders is not null && IsFolderEntry(entry) != folders.Value)
return folders.Value ? ValidationIssue.FILE_IN_FOLDER_LIST : ValidationIssue.FOLDER_IN_FILE_LIST;
}
return ValidationIssue.NONE;
@@ -34,7 +34,6 @@ internal static class FileNamePatterns
{
NONE,
MISSING,
EMPTY_ENTRY,
UNKNOWN_SELECTOR,
RECURSIVE_WILDCARD,
PATH,
@@ -50,6 +49,9 @@ internal static class FileNamePatterns
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(FileNamePatterns).Namespace, nameof(FileNamePatterns));
/// <summary>
/// Splits the list, ignoring empty entries, including a trailing semicolon.
/// </summary>
public static string[] Parse(string? patterns) => (patterns ?? string.Empty)
.Split(';', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
.Distinct(StringComparer.OrdinalIgnoreCase)
@@ -124,7 +126,6 @@ internal static class FileNamePatterns
{
ValidationIssue.NONE => null,
ValidationIssue.MISSING => TB("Please provide at least one file pattern, e.g., :documents: or *.txt. Separate multiple patterns with a semicolon."),
ValidationIssue.EMPTY_ENTRY => TB("Please remove empty file patterns. Separate valid patterns with a single semicolon."),
ValidationIssue.UNKNOWN_SELECTOR => TB("One of the file patterns is an unknown selector. Please use :documents: or :text:, or a file name pattern such as *.txt."),
ValidationIssue.RECURSIVE_WILDCARD => TB("Please use only single asterisks as wildcards, e.g., *.txt or report-*.md."),
ValidationIssue.PATH => TB("Please use file name patterns without folder paths, e.g., *.txt or report-*.md."),
@@ -134,16 +135,12 @@ internal static class FileNamePatterns
private static ValidationIssue GetValidationIssue(string? patterns)
{
if (string.IsNullOrWhiteSpace(patterns))
var individualPatterns = Parse(patterns);
if (individualPatterns.Length == 0)
return ValidationIssue.MISSING;
var individualPatterns = patterns.Split(';');
if (individualPatterns.Any(string.IsNullOrWhiteSpace))
return ValidationIssue.EMPTY_ENTRY;
foreach (var patternEntry in individualPatterns)
foreach (var pattern in individualPatterns)
{
var pattern = patternEntry.Trim();
if (IsSelector(pattern))
continue;
+19 -6
View File
@@ -6,6 +6,8 @@ public static class LongExtensions
{
private static readonly string[] COUNT_SUFFIXES = ["k", "M", "B", "T"];
private static readonly string[] SIZE_UNITS = ["B", "kB", "MB", "GB", "TB"];
/// <summary>
/// Formats a count so that large numbers stay readable: 1456 becomes 1.46k, 4512900 becomes 4.51M.
/// </summary>
@@ -42,20 +44,31 @@ public static class LongExtensions
public static string CompactCount(this int count) => ((long)count).CompactCount();
/// <summary>
/// Formats the file size in a human-readable format.
/// Formats the file size in a human-readable format, e.g., 1.9 MB.
/// </summary>
/// <remarks>
/// Like CompactCount, the decimal separator follows the culture of the active language plugin.
/// </remarks>
/// <param name="sizeBytes">The size in bytes.</param>
/// <returns>The formatted file size.</returns>
public static string FileSize(this long sizeBytes)
public static string FileSize(this long sizeBytes) => sizeBytes.FileSize(I18N.I.Culture);
/// <summary>
/// Formats the file size in a human-readable format, with the decimal separator of the given culture.
/// </summary>
/// <param name="sizeBytes">The size in bytes.</param>
/// <param name="culture">The culture whose decimal separator to use.</param>
/// <returns>The formatted file size.</returns>
internal static string FileSize(this long sizeBytes, IFormatProvider culture)
{
string[] sizes = { "B", "kB", "MB", "GB", "TB" };
var order = 0;
while (sizeBytes >= 1024 && order < sizes.Length - 1)
double size = sizeBytes;
while (size >= 1024 && order < SIZE_UNITS.Length - 1)
{
order++;
sizeBytes /= 1024;
size /= 1024;
}
return $"{sizeBytes:0.##} {sizes[order]}";
return $"{size.ToString("0.##", culture)} {SIZE_UNITS[order]}";
}
}
@@ -168,8 +168,6 @@ public sealed class DataSourceValidation
public string? ValidateFilePatterns(string patterns) => FileNamePatterns.Validate(patterns);
public string? ValidateExcludePatterns(string patterns) => FileExclusions.Validate(patterns);
public string? ValidateEmbeddingId(string embeddingId)
{
if(string.IsNullOrWhiteSpace(embeddingId))
+26
View File
@@ -550,6 +550,32 @@ tr:has(> .provider-group-header) .mud-icon-button {
padding-bottom: 0.5rem;
}
/* A field for file patterns with its help button on the right, which keeps its width while the field grows. */
.pattern-row {
display: grid;
grid-template-columns: minmax(0, 1fr) 48px;
column-gap: 8px;
align-items: center;
}
/* The field takes the first column, and so does what follows below it, such as the help, which keeps them equally wide. */
.pattern-row-main {
grid-column: 1;
min-width: 0;
}
/*
* Pattern lists wrap and grow with their content, up to a limit beyond which they scroll. MudBlazor
* measures the height only on input, after a render with new text, and when the window changes its
* size. When the field gets narrower otherwise, e.g., in a dialog, more lines wrap than it is high,
* and MudBlazor hides the scrollbar below the limit. Allowing it always keeps every line reachable.
*/
.pattern-text-field textarea.mud-input-slot {
white-space: pre-wrap;
overflow-wrap: anywhere;
overflow-y: auto !important;
}
/*
* Dialogs whose footer carries buttons on the left as well, such as Add and Import next to Close.
* MudBlazor pads the content with 24px to the left and right, but the footer with only 8px, so the
@@ -49,7 +49,7 @@
- Improved local data sources for folders. You can now choose which files AI Studio indexes, for example, only PDF files, and whether it includes subfolders as well as hidden files and folders. By default, AI Studio now also indexes text files it did not recognize before, such as program code of any kind or a LICENSE file, so a Git repository makes a useful data source. Thanks, Paul Koudelka (`PaulKoudelka`), for this contribution.
- Improved local data sources for folders, which now leave out hidden files and folders as well as build output and downloaded dependencies, such as the folders `node_modules`, `bin`, and `obj`. You can change this in the expert settings of the data source. When an existing data source contains such files, AI Studio removes them from its index during the next update.
- Improved the background embeddings page, which now lists the files of a local data source that AI Studio left out, for example, images or files that are too large. It also shows which file types and folders lie outside what the data source selects.
- Improved the Batch Processing assistant, which now also processes images in JPG, PNG, WebP, GIF, BMP, and TIFF format when the selected model can read images. If you used the assistant before, your saved file patterns stay as they are: click "Restore default patterns" or add patterns such as `*.jpg;*.png` yourself.
- Improved the Batch Processing assistant, which now also processes images in JPG, PNG, WebP, GIF, BMP, and TIFF format when the selected model can read images. If you used the assistant before, your saved file patterns stay as they are: click the restore button at the end of the field "Files to include" or add patterns such as `*.jpg;*.png` yourself.
- Improved the Batch Processing assistant, which now leaves out hidden files and folders as well as build output and downloaded dependencies, such as the folder `node_modules`. You can change both in the expert settings of the assistant.
- Improved local data sources for folders and the Batch Processing assistant, which now leave out files larger than 1 GB, such as images of virtual machines. You can change this limit.
- Improved local data sources for files and folders. When you move or rename the file or folder, for example, to another drive or a new computer, you can now select its new location when you edit the data source. AI Studio keeps what it has already prepared for every unchanged file, so these files do not go to your embedding provider again. Thanks, Paul Koudelka (`PaulKoudelka`), for this contribution.
@@ -78,6 +78,7 @@
- Improved the export menu of answers with many tables or code blocks. From six of them on, the menu groups them by kind, so the formats for the entire answer stay in view. When an answer holds code blocks in different languages, the menu now names each block by its language, such as Python or Rust, instead of numbering them.
- Improved saving a single code block. When you point at a code block, whether in an answer or in one of your own messages, a save button now appears next to its copy button. You no longer have to scroll up to the export menu of a long answer, and you can save code you sent weeks ago, for example, to get back an earlier version of it.
- Improved the search field of the workspace list. It now tells you whether it searches only the names of your chats or their contents as well.
- Improved the dialogs for local data sources and the Batch Processing assistant, whose settings are now grouped into sections. A help button next to the file patterns shows you examples, and you enter the folders and the files to leave out in a field each. Thanks, Paul Koudelka (`PaulKoudelka`), for this contribution.
- Changed the banner on the start page to a fresh design that shows what AI Studio can do today.
- Changed when a chat you add to a workspace in the workspace list appears there. Like any other new chat, it now appears once there is something to keep, for example, your first message, instead of right away.
- Changed the name of the Slide Planner Assistant to Slide Assistant. With a PowerPoint template, it now creates the slides themselves, not just a plan for them.
@@ -152,6 +153,7 @@
- Fixed exporting a briefing of the Visual Briefing assistant into a file which another program keeps open or which cannot be written. AI Studio now tells you that the export failed.
- Fixed the German warning about tools which the chosen AI provider is not trusted enough to use. It named these tools as if they were the provider.
- Fixed the configuration of your organization being able to preset a format for the result files of the Batch Processing assistant which cannot hold an entire answer, such as a table. AI Studio now ignores such a format and keeps the one you chose.
- Fixed file sizes being rounded down to whole numbers, e.g., in the information about a local data source, where 1.9 MB appeared as 1 MB. They now show their decimals with the separator of your language.
- Updated the code contributions on the supporters page, which now thank everyone who has contributed code to AI Studio so far.
- Upgraded several libraries to improve security.
- Upgraded to Rust v1.99.1
@@ -0,0 +1,201 @@
using AIStudio.Components;
namespace AIStudio.Tests.Components;
public sealed class ConfigurationFileExclusionsTests
{
[Test]
public async Task AParentRefreshDoesNotReplaceAnUncommittedDraft()
{
var component = new TestableConfigurationFileExclusions();
try
{
await component.RefreshAsync();
component.Edit("*.new");
await component.RefreshAsync();
Assert.That(component.Draft, Is.EqualTo("*.new"));
}
finally
{
component.ReleaseResources();
}
}
[Test]
public async Task AnExternalSettingsChangeReplacesTheDraft()
{
var component = new TestableConfigurationFileExclusions();
try
{
await component.RefreshAsync();
component.Edit("*.new");
component.StoredText = "*.external";
await component.RefreshAsync();
Assert.That(component.Draft, Is.EqualTo("*.external"));
}
finally
{
component.ReleaseResources();
}
}
[TestCase(true, false)]
[TestCase(false, true)]
public async Task DisabledOrLockedFieldsRejectEdits(bool disabled, bool locked)
{
var component = new TestableConfigurationFileExclusions();
try
{
await component.RefreshAsync();
component.DisableField = disabled;
component.LockField = locked;
component.Edit("*.new");
Assert.That(component.Draft, Is.EqualTo("*.original"));
}
finally
{
component.ReleaseResources();
}
}
[TestCase(true, false)]
[TestCase(false, true)]
public async Task BecomingDisabledOrLockedBeforeAutosaveRejectsThePendingWrite(bool disabled, bool locked)
{
var component = new TestableConfigurationFileExclusions();
try
{
await component.RefreshAsync();
component.Edit("*.new");
component.DisableField = disabled;
component.LockField = locked;
await component.FlushAsync();
Assert.That(component.StoredText, Is.EqualTo("*.original"));
}
finally
{
component.ReleaseResources();
}
}
[Test]
public async Task LockingAFieldReplacesItsDraftWithTheStoredSetting()
{
var component = new TestableConfigurationFileExclusions();
try
{
await component.RefreshAsync();
component.Edit("*.new");
component.LockField = true;
await component.RefreshAsync();
Assert.That(component.Draft, Is.EqualTo("*.original"));
}
finally
{
component.ReleaseResources();
}
}
[Test]
public async Task InvalidRulesAreNotPersisted()
{
var component = new TestableConfigurationFileExclusions();
try
{
await component.RefreshAsync();
component.RejectEdits();
component.Edit(":text:");
await component.FlushAsync();
Assert.That(component.StoredText, Is.EqualTo("*.original"));
}
finally
{
component.ReleaseResources();
}
}
[TestCase(true, false)]
[TestCase(false, true)]
public async Task DisabledOrLockedFieldsRejectExplicitReplacement(bool disabled, bool locked)
{
var component = new TestableConfigurationFileExclusions();
try
{
await component.RefreshAsync();
component.DisableField = disabled;
component.LockField = locked;
await component.ReplaceAsync("bin/;*.tmp");
Assert.Multiple(() =>
{
Assert.That(component.Draft, Is.EqualTo("*.original"));
Assert.That(component.StoredText, Is.EqualTo("*.original"));
});
}
finally
{
component.ReleaseResources();
}
}
[Test]
public async Task ExplicitReplacementKeepsAnInvalidOtherGroupsDraftWithoutPersistingIt()
{
var component = new TestableConfigurationFileExclusions();
try
{
await component.RefreshAsync();
component.RejectEdits();
component.Edit("old/;:text:");
await component.ReplaceAsync("bin/;:text:");
Assert.Multiple(() =>
{
Assert.That(component.Draft, Is.EqualTo("bin/;:text:"));
Assert.That(component.StoredText, Is.EqualTo("*.original"));
});
}
finally
{
component.ReleaseResources();
}
}
private sealed class TestableConfigurationFileExclusions : ConfigurationFileExclusions
{
public string StoredText { get; set; } = "*.original";
public bool DisableField { get; set; }
public bool LockField { get; set; }
public string Draft => this.EditingText;
public TestableConfigurationFileExclusions()
{
this.Text = () => this.StoredText;
this.TextUpdate = text => this.StoredText = text;
this.Disabled = () => this.DisableField;
this.IsLocked = () => this.LockField;
}
public void Edit(string text) => this.InternalUpdate(text);
public void RejectEdits() => this.Validation = _ => "Invalid rule";
public Task RefreshAsync() => this.OnParametersSetAsync();
public Task FlushAsync() => this.OptionChanged(this.EditingText);
public Task ReplaceAsync(string text) => this.ApplyTextAsync(text);
// These state-only tests never register with a message bus or render the component.
public void ReleaseResources() => this.DisposeResources();
}
}
@@ -0,0 +1,377 @@
using AIStudio.Components;
using AIStudio.Tools;
namespace AIStudio.Tests.Components;
public sealed class FileExclusionEditorStateTests
{
[TestCase("")]
[TestCase(FileExclusions.DEFAULT_PATTERNS)]
[TestCase(" node_modules/ ; bin/\n*.min.js;draft-* ")]
public void OpeningTheEditorDoesNotRewriteTheStoredText(string text)
{
var state = new FileExclusionEditorState();
state.Receive(text);
Assert.That(state.Text, Is.EqualTo(text));
}
[TestCase("node_modules/;bin/;*.min.js;draft-*")]
[TestCase("node_modules/\nbin/\n*.min.js\ndraft-*")]
[TestCase("node_modules/\r\nbin/\r\n*.min.js\r\ndraft-*")]
[TestCase("node_modules/; bin/\r\n*.min.js; draft-*")]
public void BothGroupsDisplaySemicolonSeparatedPatternsWithoutRewritingStoredLists(string text)
{
var state = Create(text);
Assert.Multiple(() =>
{
Assert.That(state.FolderText, Is.EqualTo("node_modules/; bin/"));
Assert.That(state.FileText, Is.EqualTo("*.min.js; draft-*"));
Assert.That(state.Text, Is.EqualTo(text));
});
}
[Test]
public void EditingFoldersKeepsEveryFilePattern()
{
var state = Create("bin/;*.min.js;draft-*");
state.EditFolders("obj/\nbuild/");
Assert.Multiple(() =>
{
Assert.That(FileExclusions.Parse(state.Text), Is.EqualTo(new[] { "obj/", "build/", "*.min.js", "draft-*" }));
Assert.That(state.FileText, Is.EqualTo("*.min.js; draft-*"));
Assert.That(state.Text, Is.EqualTo("obj/; build/; *.min.js; draft-*"));
});
}
[Test]
public void EditingFilesKeepsEveryFolderPattern()
{
var state = Create("bin/;node_modules/;*.min.js");
state.EditFiles("*.wav;*.md");
Assert.Multiple(() =>
{
Assert.That(FileExclusions.Parse(state.Text), Is.EqualTo(new[] { "bin/", "node_modules/", "*.wav", "*.md" }));
Assert.That(state.FolderText, Is.EqualTo("bin/; node_modules/"));
Assert.That(state.Text, Is.EqualTo("bin/; node_modules/; *.wav; *.md"));
});
}
[TestCase("")]
[TestCase(" ")]
public void ClearingOneGroupKeepsTheOtherGroup(string emptyText)
{
var state = Create("bin/;*.min.js");
state.EditFolders(emptyText);
Assert.That(FileExclusions.Parse(state.Text), Is.EqualTo(new[] { "*.min.js" }));
state = Create("bin/;*.min.js");
state.EditFiles(emptyText);
Assert.That(FileExclusions.Parse(state.Text), Is.EqualTo(new[] { "bin/" }));
}
[Test]
public void ExplicitFolderRulesKeepTheirExistingMatchingRules()
{
var state = Create(string.Empty);
state.EditFolders("node_modules/;cmake-build-*/");
var patterns = FileExclusions.Parse(state.Text);
Assert.Multiple(() =>
{
Assert.That(patterns, Is.EqualTo(new[] { "node_modules/", "cmake-build-*/" }));
Assert.That(FileExclusions.IsValid(state.Text), Is.True);
Assert.That(state.IsValid, Is.True);
Assert.That(FileExclusions.ExcludesFolder("node_modules", patterns), Is.True);
Assert.That(FileExclusions.ExcludesFolder("cmake-build-debug", patterns), Is.True);
Assert.That(FileExclusions.ExcludesFile("node_modules", patterns), Is.False);
});
}
[TestCase(":text:")]
[TestCase("src/bin/")]
[TestCase("/")]
[TestCase("**/*.md")]
public void EditingTheOtherGroupNeverDropsAnInvalidStoredRule(string invalidRule)
{
var state = Create(invalidRule);
if (invalidRule.EndsWith('/'))
state.EditFiles("*.tmp");
else
state.EditFolders("bin/");
Assert.Multiple(() =>
{
Assert.That(FileExclusions.Parse(state.Text), Does.Contain(invalidRule));
Assert.That(FileExclusions.IsValid(state.Text), Is.False);
});
}
[TestCase(":text:")]
[TestCase(":documents:")]
public void FolderEditingDoesNotTurnAnInvalidSelectorIntoAValidLiteralFolder(string selector)
{
var state = Create(string.Empty);
state.EditFolders(selector);
Assert.Multiple(() =>
{
Assert.That(state.Text, Is.EqualTo(selector));
Assert.That(FileExclusions.IsValid(state.Text), Is.False);
});
}
[Test]
public void ExistingFolderNamesWhichResembleSelectorsArePreservedExactly()
{
var state = Create(":text:/;*.tmp");
state.EditFiles("*.bak");
Assert.That(FileExclusions.Parse(state.Text), Is.EqualTo(new[] { ":text:/", "*.bak" }));
}
[Test]
public void RestoringOneGroupDeduplicatesRulesWithoutLosingOtherRules()
{
var state = Create("custom/;*.TMP;*.tmp;Caddyfile");
state.RestoreFolders("node_modules/;NODE_MODULES/;*.default");
Assert.That(FileExclusions.Parse(state.Text), Is.EqualTo(new[] { "node_modules/", "*.TMP", "Caddyfile" }));
}
[TestCase(true)]
[TestCase(false)]
public void RestoringOneFieldPreservesTheOtherFieldsDraft(bool folders)
{
var state = Create("custom/;*.custom");
state.EditFolders("latest/; ");
state.EditFiles("*.latest; ");
const string DEFAULTS = "bin/;node_modules/;*.tmp;*.bak";
if (folders)
state.RestoreFolders(DEFAULTS);
else
state.RestoreFiles(DEFAULTS);
Assert.Multiple(() =>
{
Assert.That(state.FolderText, Is.EqualTo(folders ? "bin/; node_modules/" : "latest/; "));
Assert.That(state.FileText, Is.EqualTo(folders ? "*.latest; " : "*.tmp; *.bak"));
Assert.That(state.Text, Is.EqualTo(folders ? "bin/; node_modules/; *.latest" : "latest/; *.tmp; *.bak"));
});
}
[TestCase(true)]
[TestCase(false)]
public void EmptyDefaultsClearOnlyTheRestoredField(bool folders)
{
var state = Create("custom/;*.custom");
if (folders)
state.RestoreFolders(string.Empty);
else
state.RestoreFiles(string.Empty);
Assert.That(state.Text, Is.EqualTo(folders ? "*.custom" : "custom/"));
}
[TestCase(true, ":text:")]
[TestCase(false, "src/bin/")]
public void RestoringOneFieldPreservesInvalidRulesInTheOtherField(bool folders, string invalidRule)
{
var state = Create(invalidRule);
if (folders)
state.RestoreFolders(FileExclusions.DEFAULT_PATTERNS);
else
state.RestoreFiles(FileExclusions.DEFAULT_PATTERNS);
Assert.Multiple(() =>
{
Assert.That(FileExclusions.Parse(state.Text), Does.Contain(invalidRule));
Assert.That(FileExclusions.IsValid(state.Text), Is.False);
});
}
[Test]
public void EditingTheFileFieldDoesNotSilentlyMoveFolderRules()
{
var state = Create("bin/;*.min.js");
state.EditFiles("obj/;*.wav;draft-*");
Assert.Multiple(() =>
{
Assert.That(state.IsValid, Is.False);
Assert.That(state.Text, Is.EqualTo("bin/; obj/; *.wav; draft-*"));
Assert.That(state.FolderText, Is.EqualTo("bin/"));
Assert.That(state.FileText, Is.EqualTo("obj/;*.wav;draft-*"));
});
}
[TestCase(true)]
[TestCase(false)]
public void EditingBothVisibleFieldsPreservesBothDraftsAndAllRules(bool foldersFirst)
{
var state = Create("bin/;*.tmp");
string earlierValue;
if (foldersFirst)
{
state.EditFolders("obj/; build/; ");
earlierValue = state.Text;
state.EditFiles("*.wav; draft-*; ");
}
else
{
state.EditFiles("*.wav; draft-*; ");
earlierValue = state.Text;
state.EditFolders("obj/; build/; ");
}
state.Receive(earlierValue);
state.Receive(state.Text);
Assert.Multiple(() =>
{
Assert.That(state.FolderText, Is.EqualTo("obj/; build/; "));
Assert.That(state.FileText, Is.EqualTo("*.wav; draft-*; "));
Assert.That(state.Text, Is.EqualTo("obj/; build/; *.wav; draft-*"));
});
}
[Test]
public void ADebouncedEchoDoesNotOverwriteANewerDraft()
{
var state = Create("*.original");
state.EditFiles("*.first");
var earlierValue = state.Text;
state.EditFiles("*.latest");
state.Receive(earlierValue);
Assert.Multiple(() =>
{
Assert.That(state.Text, Is.EqualTo("*.latest"));
Assert.That(state.FileText, Is.EqualTo("*.latest"));
});
}
[Test]
public void AnUnchangedParentValueDoesNotDiscardAnUncommittedEdit()
{
var state = Create("*.original");
state.EditFiles("*.latest; ");
state.Receive("*.original");
Assert.That(state.FileText, Is.EqualTo("*.latest; "));
}
[Test]
public void ANewExternalValueReplacesBothGroups()
{
var state = Create("bin/;*.tmp");
state.EditFiles("*.latest");
state.Receive("policy/;*.secret");
Assert.Multiple(() =>
{
Assert.That(state.FolderText, Is.EqualTo("policy/"));
Assert.That(state.FileText, Is.EqualTo("*.secret"));
});
}
[Test]
public void ADisabledEditorShowsTheActualSettingEvenWhenItMatchesAnOlderDraft()
{
var state = Create("*.original");
state.EditFiles("*.earlier");
state.EditFiles("*.latest");
state.Receive("*.earlier", discardDraft: true);
Assert.That(state.FileText, Is.EqualTo("*.earlier"));
}
[Test]
public void ReformattingTheDefaultsDoesNotChangeTheSelectionSignature()
{
var state = Create(FileExclusions.DEFAULT_PATTERNS);
state.EditFiles(state.FileText);
var before = new FolderFileSelection(
["*"], FileExclusions.Parse(FileExclusions.DEFAULT_PATTERNS), true, false,
FolderFileSelection.DEFAULT_MAX_FILE_SIZE_MEGABYTES);
var after = new FolderFileSelection(
["*"], FileExclusions.Parse(state.Text), true, false,
FolderFileSelection.DEFAULT_MAX_FILE_SIZE_MEGABYTES);
Assert.That(after.Describe(), Is.EqualTo(before.Describe()));
}
[Test]
public void RestoringDefaultsReplacesBothGroupsAndKeepsTheExactDefaultString()
{
var state = Create("custom/;*.custom");
state.EditFolders(":text:");
state.Reset(FileExclusions.DEFAULT_PATTERNS);
Assert.Multiple(() =>
{
Assert.That(state.Text, Is.EqualTo(FileExclusions.DEFAULT_PATTERNS));
Assert.That(FileExclusions.IsValid(state.Text), Is.True);
Assert.That(state.FolderText, Does.Contain("node_modules/"));
Assert.That(state.FileText, Does.Contain("*.exe"));
Assert.That(state.FolderText, Does.Not.Contain("\n").And.Not.Contain("\r"));
Assert.That(state.FileText, Does.Not.Contain("\n").And.Not.Contain("\r"));
});
}
[Test]
public void EditingADefaultGroupPreservesAllRulesInTheOtherGroup()
{
var state = Create(FileExclusions.DEFAULT_PATTERNS);
state.EditFiles(state.FileText + "\n*.scratch");
Assert.That(
FileExclusions.Parse(state.Text),
Is.EquivalentTo(FileExclusions.Parse(FileExclusions.DEFAULT_PATTERNS).Append("*.scratch")));
}
[TestCase("*.pdf")]
[TestCase("Dockerfile")]
[TestCase("node_modules")]
public void WrongFieldDraftsStayVisibleWithoutAddingAFolderSuffix(string pattern)
{
var state = Create("bin/;*.tmp");
state.EditFolders(pattern);
Assert.Multiple(() =>
{
Assert.That(state.IsValid, Is.False);
Assert.That(state.FolderText, Is.EqualTo(pattern));
Assert.That(FileExclusions.Parse(state.Text), Does.Contain(pattern));
Assert.That(FileExclusions.Parse(state.Text), Does.Not.Contain(pattern + "/"));
Assert.That(state.FileText, Is.EqualTo("*.tmp"));
});
}
[Test]
public void FolderRulesInTheFileFieldStayVisibleAndInvalidAfterAParentEcho()
{
var state = Create("bin/;*.tmp");
state.EditFiles("*.bak");
var earlierValue = state.Text;
state.EditFiles("*.bak;obj/");
state.Receive(earlierValue);
Assert.Multiple(() =>
{
Assert.That(state.IsValid, Is.False);
Assert.That(state.FileText, Is.EqualTo("*.bak;obj/"));
Assert.That(state.FolderText, Is.EqualTo("bin/"));
});
}
private static FileExclusionEditorState Create(string text)
{
var state = new FileExclusionEditorState();
state.Receive(text);
return state;
}
}
@@ -0,0 +1,204 @@
using AIStudio.Components;
using Microsoft.AspNetCore.Components;
namespace AIStudio.Tests.Components;
public sealed class FileExclusionEditorTests
{
[TestCase(true, "*.pdf")]
[TestCase(true, "Dockerfile")]
[TestCase(true, "node_modules")]
[TestCase(false, "bin/")]
[TestCase(false, "*.pdf;obj/")]
[TestCase(true, ":text:")]
[TestCase(false, ":documents:")]
[TestCase(true, "src/bin/")]
[TestCase(false, "**/*.tmp")]
public async Task WrongFieldAndInvalidRulesAreNotPublished(bool folders, string patterns)
{
var published = new List<string>();
var editor = Create(published);
await editor.EditAsync(patterns, folders);
Assert.That(published, Is.Empty);
}
[Test]
public async Task EditingTheOtherFieldCannotPublishAnInvalidDraft()
{
var published = new List<string>();
var editor = Create(published);
await editor.EditAsync("*.pdf", folders: true);
await editor.EditAsync("*.bak", folders: false);
Assert.That(published, Is.Empty);
await editor.EditAsync("build/", folders: true);
Assert.That(published, Is.EqualTo(new[] { "build/; *.bak" }));
}
[Test]
public async Task MovingARuleToItsCorrectFieldPublishesItEvenIfTheCombinedTextDoesNotChange()
{
var published = new List<string>();
var editor = Create(published, "bin/");
await editor.EditAsync("bin/;*.pdf", folders: true);
await editor.EditAsync("*.pdf", folders: false);
Assert.That(published, Is.Empty);
await editor.EditAsync("bin/", folders: true);
Assert.That(published, Is.EqualTo(new[] { "bin/; *.pdf" }));
}
[TestCase(true, true)]
[TestCase(true, false)]
[TestCase(false, true)]
[TestCase(false, false)]
public async Task ResettingOneFieldCannotPersistInvalidRulesInTheOtherField(bool folders, bool explicitReset)
{
var published = new List<string>();
var editor = Create(published);
var resets = new List<string>();
if (explicitReset)
editor.SetParameters(new() { [nameof(FileExclusionEditor.ResetRequested)] = Collect(resets) });
await editor.EditAsync(folders ? "*.keep;wrong/" : "*.wrong", folders: !folders);
await editor.ResetAsync(folders);
Assert.Multiple(() =>
{
Assert.That(published, Is.Empty);
Assert.That(resets, Is.Empty);
});
await editor.EditAsync(folders ? "*.keep" : "keep/", folders: !folders);
Assert.That(published, Is.EqualTo(new[] { folders ? "bin/; *.keep" : "keep/; *.tmp" }));
}
[TestCase(true)]
[TestCase(false)]
public async Task ResettingTheInvalidFieldRestoresAValidValue(bool folders)
{
var published = new List<string>();
var editor = Create(published);
var resets = new List<string>();
editor.SetParameters(new() { [nameof(FileExclusionEditor.ResetRequested)] = Collect(resets) });
await editor.EditAsync(folders ? "*.wrong" : "wrong/", folders);
await editor.ResetAsync(folders);
Assert.Multiple(() =>
{
Assert.That(published, Is.Empty);
Assert.That(resets, Is.EqualTo(new[] { folders ? "bin/; *.custom" : "custom/; *.tmp" }));
});
}
[Test]
public async Task DisabledEditorRejectsEditsAndBothResetCallbacks()
{
var published = new List<string>();
var resets = new List<string>();
var editor = Create(published);
editor.SetParameters(new()
{
[nameof(FileExclusionEditor.Disabled)] = true,
[nameof(FileExclusionEditor.ResetRequested)] = Collect(resets),
});
await editor.EditAsync("changed/", folders: true);
await editor.EditAsync("*.changed", folders: false);
await editor.ResetAsync(folders: true);
await editor.ResetAsync(folders: false);
Assert.Multiple(() =>
{
Assert.That(published, Is.Empty);
Assert.That(resets, Is.Empty);
});
editor.SetParameters(new() { [nameof(FileExclusionEditor.ResetRequested)] = default(EventCallback<string>) });
await editor.ResetAsync(folders: true);
await editor.ResetAsync(folders: false);
Assert.That(published, Is.Empty);
// Returning to editing must reveal the unchanged source, not either attempted edit:
editor.SetParameters(new() { [nameof(FileExclusionEditor.Disabled)] = false });
await editor.EditAsync("*.other", folders: false);
Assert.That(published, Is.EqualTo(new[] { "custom/; *.other" }));
}
[Test]
public async Task AnInvalidDraftMakesTheEditorInvalidWhileTheBoundTextStaysValid()
{
var published = new List<string>();
var editor = Create(published);
Assert.That(editor.IsValid, Is.True);
await editor.EditAsync("*.wrong", folders: true);
Assert.Multiple(() =>
{
Assert.That(editor.IsValid, Is.False);
Assert.That(published, Is.Empty);
});
await editor.EditAsync("custom/", folders: true);
Assert.That(editor.IsValid, Is.True);
}
[Test]
public void AnInvalidReceivedListMakesTheEditorInvalid()
{
var editor = Create(new List<string>(), "**/*.tmp");
Assert.That(editor.IsValid, Is.False);
}
[Test]
public async Task HiddenResetButtonsCannotChangeTheRules()
{
var published = new List<string>();
var editor = Create(published);
editor.SetParameters(new() { [nameof(FileExclusionEditor.ShowReset)] = false });
await editor.ResetAsync(folders: true);
await editor.ResetAsync(folders: false);
Assert.That(published, Is.Empty);
}
private static TestableFileExclusionEditor Create(List<string> published, string initial = "custom/;*.custom")
{
var editor = new TestableFileExclusionEditor();
editor.SetParameters(new()
{
[nameof(FileExclusionEditor.Text)] = initial,
[nameof(FileExclusionEditor.ResetValue)] = "bin/;*.tmp",
[nameof(FileExclusionEditor.TextChanged)] = Collect(published),
});
return editor;
}
private static EventCallback<string> Collect(List<string> values) => EventCallback.Factory.Create<string>(new object(), values.Add);
private sealed class TestableFileExclusionEditor : FileExclusionEditor
{
//
// Sets the parameters the way a renderer would, which this test has none of, and then runs
// the step of the lifecycle which reacts to them:
//
public void SetParameters(Dictionary<string, object?> parameters)
{
ParameterView.FromDictionary(parameters).SetParameterProperties(this);
this.OnParametersSet();
}
public Task EditAsync(string patterns, bool folders) => folders ? this.ChangeFoldersAsync(patterns) : this.ChangeFilesAsync(patterns);
public Task ResetAsync(bool folders) => this.RestoreDefaultsAsync(folders);
}
}
@@ -0,0 +1,31 @@
using AIStudio.Components;
using AIStudio.Tools;
namespace AIStudio.Tests.Components;
public sealed class ManagePandocDependencyTests
{
[Test]
public void TheInstallationPromptStaysHiddenWhileAvailabilityIsChecked()
{
Assert.That(ManagePandocDependency.ShouldShowInstallationPrompt(null), Is.False);
}
[TestCase(true)]
[TestCase(false)]
public void AnAvailableInstallationNeedsNoPrompt(bool isLocal)
{
var installation = new PandocInstallation(true, string.Empty, true, "3.7.0.2", isLocal);
Assert.That(ManagePandocDependency.ShouldShowInstallationPrompt(installation), Is.False);
}
[TestCase(true)]
[TestCase(false)]
public void AnUnavailableOrOutdatedInstallationShowsThePrompt(bool checkWasSuccessful)
{
var installation = new PandocInstallation(checkWasSuccessful, "Unavailable", false, string.Empty, false);
Assert.That(ManagePandocDependency.ShouldShowInstallationPrompt(installation), Is.True);
}
}
+13 -1
View File
@@ -40,6 +40,18 @@ public sealed class EmbeddingSignatureTests
"Patterns change which files belong to the source, not how retained files are embedded; the indexing run removes and adds only the affected files.");
}
[Test]
public void ChangingDirectoryExclusionsKeepsTheStoredEmbeddingsOfRetainedFiles()
{
var allFiles = DataSource(ConfidenceLevel.LOW);
var filteredFiles = allFiles with { ExcludePatterns = "*.mp3;draft-*" };
Assert.That(
Signature(filteredFiles),
Is.EqualTo(Signature(allFiles)),
"Exclusions change which files belong to the source, not how retained files are embedded.");
}
[Test]
public void ChangingDirectoryRecursionKeepsTheStoredEmbeddingsOfRetainedFiles()
{
@@ -244,4 +256,4 @@ public sealed class EmbeddingSignatureTests
public int ChunkOverlapTokenLength { get; init; }
}
}
}
+79 -3
View File
@@ -4,7 +4,6 @@ using AIStudio.Settings;
using AIStudio.Settings.DataModel;
using AIStudio.Tools;
using AIStudio.Tools.Services;
using AIStudio.Tools.Validation;
namespace AIStudio.Tests.Tools;
@@ -22,7 +21,7 @@ public sealed class FileExclusionsTests
[TestCase(FileExclusions.DEFAULT_PATTERNS)]
public void ValidExclusionListsAreAccepted(string patterns)
{
Assert.That(new DataSourceValidation().ValidateExcludePatterns(patterns), Is.Null);
Assert.That(FileExclusions.Validate(patterns), Is.Null);
}
[TestCase("a/b/")]
@@ -33,7 +32,72 @@ public sealed class FileExclusionsTests
[TestCase("**/bin/")]
public void InvalidExclusionListsAreRejected(string patterns)
{
Assert.That(new DataSourceValidation().ValidateExcludePatterns(patterns), Is.Not.Null);
Assert.That(FileExclusions.Validate(patterns), Is.Not.Null);
}
[TestCase(null, true)]
[TestCase("", true)]
[TestCase(" ; \r\n", true)]
[TestCase("build/;cmake-build-*/", true)]
[TestCase(".env/;archive.v1/;*.vmwarevm/", true)]
[TestCase("*.pdf", false)]
[TestCase("Dockerfile", false)]
[TestCase("node_modules", false)]
[TestCase("build/;*.txt", false)]
public void FolderFieldRequiresExplicitFolderRules(string? patterns, bool valid)
{
Assert.Multiple(() =>
{
Assert.That(FileExclusions.IsValidFolders(patterns), Is.EqualTo(valid));
Assert.That(FileExclusions.ValidateFolders(patterns) is null, Is.EqualTo(valid));
});
}
[TestCase(null, true)]
[TestCase("", true)]
[TestCase(" ; \r\n", true)]
[TestCase("*.pdf;draft-*;report-?.md", true)]
[TestCase("Dockerfile;Caddyfile;.gitignore", true)]
[TestCase("build", true)]
[TestCase("build/", false)]
[TestCase("*.tmp;obj/", false)]
public void FileFieldRejectsFolderRulesButAcceptsExtensionlessFiles(string? patterns, bool valid)
{
Assert.Multiple(() =>
{
Assert.That(FileExclusions.IsValidFiles(patterns), Is.EqualTo(valid));
Assert.That(FileExclusions.ValidateFiles(patterns) is null, Is.EqualTo(valid));
});
}
[TestCase(":text:")]
[TestCase(":documents:")]
[TestCase("src/build/")]
[TestCase("**/*.tmp")]
[TestCase("/")]
[TestCase("./")]
public void BothFieldValidatorsReuseTheExistingSyntaxErrors(string patterns)
{
Assert.Multiple(() =>
{
Assert.That(FileExclusions.IsValidFolders(patterns), Is.False);
Assert.That(FileExclusions.IsValidFiles(patterns), Is.False);
Assert.That(FileExclusions.ValidateFolders(patterns), Is.EqualTo(FileExclusions.Validate(patterns)));
Assert.That(FileExclusions.ValidateFiles(patterns), Is.EqualTo(FileExclusions.Validate(patterns)));
});
}
[Test]
public void StoredListsStillAcceptBothKindsOfRulesTogether()
{
const string PATTERNS = "bin/;*.pdf;Dockerfile;.git/";
Assert.Multiple(() =>
{
Assert.That(FileExclusions.IsValid(PATTERNS), Is.True);
Assert.That(FileExclusions.Validate(PATTERNS), Is.Null);
Assert.That(FileExclusions.ExcludesFolder("bin", FileExclusions.Parse(PATTERNS)), Is.True);
Assert.That(FileExclusions.ExcludesFile("report.pdf", FileExclusions.Parse(PATTERNS)), Is.True);
});
}
[Test]
@@ -42,6 +106,18 @@ public sealed class FileExclusionsTests
Assert.That(FileExclusions.Parse(" bin/ ;obj/\r\n\r\n*.min.js\nBIN/ "), Is.EqualTo(new[] { "bin/", "obj/", "*.min.js" }));
}
[Test]
public void GroupingSplitsFoldersFromFilesAndDropsDuplicates()
{
var groups = FileExclusions.Group(" bin/ ;*.min.js\r\nobj/;BIN/;;\n*.MIN.js");
Assert.Multiple(() =>
{
Assert.That(groups.Folders, Is.EqualTo("bin/; obj/"));
Assert.That(groups.Files, Is.EqualTo("*.min.js"));
});
}
[Test]
public void SettingsFromBeforeTheListGetTheDefault()
{
@@ -0,0 +1,153 @@
using AIStudio.Tools;
namespace AIStudio.Tests.Tools;
public sealed class FilePatternExclusionTests
{
[TestCase("*", "*.mp3;*.webm", "recording.WEBM", false)]
[TestCase("*", "*.mp3;*.webm", "report.pdf", true)]
[TestCase("*.txt;*.pdf", "draft-*", "draft-report.pdf", false)]
[TestCase("*.txt;*.pdf", "draft-*", "report.pdf", true)]
[TestCase("*.txt", "*.mp3", "report.pdf", false)]
[TestCase("*", "Dockerfile;Caddyfile", "Dockerfile", false)]
[TestCase("*", ".env", ".env", false)]
[TestCase("*", "*", "report.pdf", false)]
[TestCase("*", "", "report.pdf", true)]
[TestCase("*", "*-draft-*", "report-draft-final.md", false)]
[TestCase("*", "draft-*", "draft-", false)]
[TestCase("*", "report-?.md", "report-a.md", false)]
[TestCase("*", "report-?.md", "report-ab.md", true)]
public void ExclusionsOverrideIncludesButNeverSelectOtherFiles(string include, string exclude, string fileName, bool expected)
{
var path = Path.Combine(Path.GetTempPath(), "nested", fileName);
var selection = new FolderFileSelection(
FileNamePatterns.Parse(include),
FileExclusions.Parse(exclude),
false,
false,
FolderFileSelection.DEFAULT_MAX_FILE_SIZE_MEGABYTES);
Assert.That(selection.Selects(path), Is.EqualTo(expected));
}
[TestCase("build", true)]
[TestCase("app-build", true)]
[TestCase("my-build", true)]
[TestCase("BUILD", true)]
[TestCase("builder", false)]
[TestCase("build-output", false)]
public void TheFolderExampleMatchesNamesEndingInBuildButNotFiles(string name, bool expected)
{
var patterns = FileExclusions.Parse("*build/");
var path = Path.Combine(Path.GetTempPath(), "test-data", name);
Assert.Multiple(() =>
{
Assert.That(FileExclusions.ExcludesFolder(path, patterns), Is.EqualTo(expected));
Assert.That(FileExclusions.ExcludesFile(path, patterns), Is.False);
});
}
[TestCase(false, "", true, false)]
[TestCase(true, "", true, true)]
[TestCase(false, ".gitignore", false, false)]
[TestCase(true, ".gitignore", false, false)]
[TestCase(false, ".hidden/", true, false)]
[TestCase(true, ".hidden/", true, false)]
public async Task ExplicitDotPatternsBypassOnlyTheHiddenFileRuleNotExclusionsOrHiddenFolders(
bool includeHidden, string exclusions, bool selectsRootFile, bool selectsNestedFile)
{
var directoryPath = Path.Combine(Path.GetTempPath(), $"ai-studio-dot-pattern-priority-{Guid.NewGuid():N}");
var hiddenDirectory = Path.Combine(directoryPath, ".hidden");
var rootFile = Path.Combine(directoryPath, ".gitignore");
var nestedFile = Path.Combine(hiddenDirectory, ".gitignore");
Directory.CreateDirectory(hiddenDirectory);
try
{
await File.WriteAllTextAsync(rootFile, "bin/");
await File.WriteAllTextAsync(nestedFile, "obj/");
var selection = new FolderFileSelection(
[".gitignore"], FileExclusions.Parse(exclusions), true, includeHidden,
FolderFileSelection.DEFAULT_MAX_FILE_SIZE_MEGABYTES);
var selectedFiles = selection.EnumerateCandidates(directoryPath).Where(selection.Selects).ToArray();
Assert.Multiple(() =>
{
Assert.That(selectedFiles.Contains(rootFile), Is.EqualTo(selectsRootFile));
Assert.That(selectedFiles.Contains(nestedFile), Is.EqualTo(selectsNestedFile));
Assert.That(selection.LeavesOut(directoryPath, rootFile), Is.EqualTo(!selectsRootFile));
Assert.That(selection.LeavesOut(directoryPath, nestedFile), Is.EqualTo(!selectsNestedFile));
});
}
finally
{
Directory.Delete(directoryPath, true);
}
}
[Test]
public void EnvironmentDefaultsExcludeVisibleEnvironmentFoldersRatherThanDotEnvFiles()
{
var defaults = FileExclusions.Parse(FileExclusions.DEFAULT_PATTERNS);
Assert.Multiple(() =>
{
Assert.That(FileExclusions.ExcludesFolder("env", defaults), Is.True);
Assert.That(FileExclusions.ExcludesFolder("venv", defaults), Is.True);
Assert.That(FileExclusions.ExcludesFile(".env", defaults), Is.False);
Assert.That(FileExclusions.ExcludesFolder(".env", defaults), Is.False,
"Hidden folders are handled by the hidden-file switch, not by the default exclusion list.");
});
}
[TestCase(null)]
[TestCase("")]
[TestCase(" ")]
public void ExclusionsMayBeEmptyWhileIncludesAreRequired(string? patterns)
{
Assert.Multiple(() =>
{
Assert.That(FileExclusions.Validate(patterns), Is.Null);
Assert.That(FileExclusions.IsValid(patterns), Is.True);
Assert.That(FileNamePatterns.IsValid(patterns), Is.False);
});
}
[TestCase("**/*.txt")]
[TestCase("folder/*.txt")]
public void OptionalExclusionsStillRejectMalformedRules(string patterns)
{
Assert.Multiple(() =>
{
Assert.That(FileExclusions.Validate(patterns), Is.Not.Null);
Assert.That(FileExclusions.IsValid(patterns), Is.False);
});
}
[Test]
public async Task TextSelectorsCanBeCombinedWithFilenameExclusions()
{
var directoryPath = Path.Combine(Path.GetTempPath(), $"ai-studio-exclusions-{Guid.NewGuid():N}");
Directory.CreateDirectory(directoryPath);
try
{
var terraform = Path.Combine(directoryPath, "main.tf");
var caddy = Path.Combine(directoryPath, "Caddyfile");
await File.WriteAllTextAsync(terraform, "resource example {}");
await File.WriteAllTextAsync(caddy, "localhost { respond hello }");
var selection = new FolderFileSelection(
[":text:"], ["*.tf"], false, false, FolderFileSelection.DEFAULT_MAX_FILE_SIZE_MEGABYTES);
Assert.Multiple(() =>
{
Assert.That(selection.Selects(terraform), Is.False);
Assert.That(selection.Selects(caddy), Is.True);
});
}
finally
{
Directory.Delete(directoryPath, true);
}
}
}
@@ -271,6 +271,32 @@ public sealed class FileSourceIndexerWatcherTests
Assert.That(FileSourceIndexer.IsSameWatchConfiguration(before, after), Is.False);
}
[Test]
public void ReorderingEquivalentExclusionsDoesNotRecreateTheWatcher()
{
var before = DefaultConfiguration(Path.GetTempPath()) with
{
ExcludePatterns = FileExclusions.Parse("*.mp3;draft-*"),
};
var after = before with { ExcludePatterns = FileExclusions.Parse("DRAFT-*;*.MP3;*.mp3") };
Assert.That(FileSourceIndexer.IsSameWatchConfiguration(before, after), Is.True);
}
[Test]
public void RenamingAnIncludedFileIntoAnExcludedNameStillRefreshesTheIndex()
{
var rootPath = Path.Combine(Path.GetTempPath(), $"ai-studio-excluded-rename-{Guid.NewGuid():N}");
var configuration = DefaultConfiguration(rootPath) with
{
FilePatterns = FileNamePatterns.Parse("*.md"),
ExcludePatterns = FileExclusions.Parse("draft-*"),
};
var renamed = new RenamedEventArgs(WatcherChangeTypes.Renamed, rootPath, "draft-report.md", "report.md");
Assert.That(FileSourceIndexer.IsRelevantWatcherEvent(configuration, renamed), Is.True);
}
[Test]
public void RelocatingToADirectoryWithDifferentCasingUsesTheLocalPathRules()
{
@@ -39,7 +39,8 @@ public sealed class LocalDirectoryFilePatternsTests
[TestCase(null)]
[TestCase("")]
[TestCase(" ")]
[TestCase("*.txt;")]
[TestCase(";")]
[TestCase(" ; ; ")]
[TestCase("folder/*.txt")]
public void InvalidStoredPatternsFallBackWithoutRemovingExistingDocuments(string? storedPatterns)
{
@@ -48,12 +49,60 @@ public sealed class LocalDirectoryFilePatternsTests
Assert.That(dataSource.EffectiveFilePatterns, Is.EqualTo(DataSourceLocalDirectory.DEFAULT_FILE_PATTERNS));
}
[Test]
public void BatchExclusionsSurviveSettingsSerialization()
{
var written = new DataBatchProcessing { ExcludePatterns = "*.mp3;*.webm;draft-*" };
var json = JsonSerializer.Serialize(written, SettingsManager.JSON_OPTIONS);
var read = JsonSerializer.Deserialize<DataBatchProcessing>(json, SettingsManager.JSON_OPTIONS);
Assert.That(read!.ExcludePatterns, Is.EqualTo(written.ExcludePatterns));
}
[Test]
public void PatternsAreSplitAndTrimmed()
{
Assert.That(FileNamePatterns.Parse(" *.txt ; *.tf ; .gitignore "), Is.EqualTo(new[] { "*.txt", "*.tf", ".gitignore" }));
}
[TestCase("*.txt;")]
[TestCase(";*.txt")]
[TestCase(";; *.txt ; ; ")]
public void EmptyEntriesAreIgnoredWithoutBroadeningTheSelection(string patterns)
{
var parsed = FileNamePatterns.Parse(patterns);
var dataSource = new DataSourceLocalDirectory { FilePatterns = patterns };
var selection = new FolderFileSelection(parsed, [], false, false, FolderFileSelection.DEFAULT_MAX_FILE_SIZE_MEGABYTES);
var canonical = new FolderFileSelection(["*.txt"], [], false, false, FolderFileSelection.DEFAULT_MAX_FILE_SIZE_MEGABYTES);
Assert.Multiple(() =>
{
Assert.That(FileNamePatterns.IsValid(patterns), Is.True);
Assert.That(FileNamePatterns.Validate(patterns), Is.Null);
Assert.That(parsed, Is.EqualTo(new[] { "*.txt" }));
Assert.That(dataSource.EffectiveFilePatterns, Is.EqualTo(patterns), "Empty entries must not trigger the default document selection.");
Assert.That(selection.Selects("report.txt"), Is.True);
Assert.That(selection.Selects("report.md"), Is.False);
Assert.That(selection.Selects("recording.wav"), Is.False);
Assert.That(selection.Describe(), Is.EqualTo(canonical.Describe()), "Empty entries must not change the indexing selection signature.");
});
}
[TestCase(null)]
[TestCase("")]
[TestCase(" ")]
[TestCase(";")]
[TestCase(" ; ;; ")]
public void IncludesStillRequireAtLeastOneNonEmptyPattern(string? patterns)
{
Assert.Multiple(() =>
{
Assert.That(FileNamePatterns.Parse(patterns), Is.Empty);
Assert.That(FileNamePatterns.IsValid(patterns), Is.False);
Assert.That(FileNamePatterns.Validate(patterns), Is.Not.Null);
});
}
[Test]
public void NormalizingPatternsRemovesCaseInsensitiveDuplicates()
{
@@ -75,6 +124,7 @@ public sealed class LocalDirectoryFilePatternsTests
{
Type = DataSourceType.LOCAL_DIRECTORY,
FilePatterns = "*.txt;*.tf;.gitignore",
ExcludePatterns = "secret-*;*.backup",
IncludeSubdirectories = false,
IncludeHiddenFilesAndFolders = true,
};
@@ -85,8 +135,10 @@ public sealed class LocalDirectoryFilePatternsTests
Assert.Multiple(() =>
{
Assert.That(json, Does.Not.Contain(nameof(DataSourceLocalDirectory.EffectiveFilePatterns)));
Assert.That(json, Does.Not.Contain(nameof(DataSourceLocalDirectory.EffectiveExcludePatterns)));
Assert.That(read, Is.TypeOf<DataSourceLocalDirectory>());
Assert.That(((DataSourceLocalDirectory)read!).FilePatterns, Is.EqualTo("*.txt;*.tf;.gitignore"));
Assert.That(((DataSourceLocalDirectory)read).ExcludePatterns, Is.EqualTo("secret-*;*.backup"));
Assert.That(((DataSourceLocalDirectory)read).IncludeSubdirectories, Is.False);
Assert.That(((DataSourceLocalDirectory)read).IncludeHiddenFilesAndFolders, Is.True);
});
@@ -419,17 +471,22 @@ public sealed class LocalDirectoryFilePatternsTests
[TestCase(":documents:")]
[TestCase(":DOCUMENTS:;*.tf")]
[TestCase("report-??.md")]
[TestCase("*.txt;")]
[TestCase(";*.txt;; *.tf ; ;")]
[TestCase(":documents:;;")]
public void DataSourceValidationAcceptsSimplePatterns(string patterns)
{
Assert.That(new DataSourceValidation().ValidateFilePatterns(patterns), Is.Null);
}
[TestCase("")]
[TestCase("*.txt;")]
[TestCase(" ; ;; ")]
[TestCase("**/*.txt")]
[TestCase("folder/*.txt")]
[TestCase(":document:")]
[TestCase("*.txt;:docs:")]
[TestCase(";*.txt;; :docs: ;")]
[TestCase("*.txt;;folder/*.md;")]
public void DataSourceValidationRejectsInvalidPatterns(string patterns)
{
Assert.That(new DataSourceValidation().ValidateFilePatterns(patterns), Is.Not.Null);
+25
View File
@@ -0,0 +1,25 @@
using System.Globalization;
using AIStudio.Tools;
namespace AIStudio.Tests.Tools;
public sealed class LongExtensionsTests
{
[TestCase(0L, "0 B")]
[TestCase(1_023L, "1023 B")]
[TestCase(1_536L, "1.5 kB")]
[TestCase(1_992_294L, "1.9 MB")]
[TestCase(1_073_741_824L, "1 GB")]
[TestCase(1_572_864_000L, "1.46 GB")]
public void FileSizesKeepTheirFraction(long sizeBytes, string expected)
{
Assert.That(sizeBytes.FileSize(CultureInfo.InvariantCulture), Is.EqualTo(expected));
}
[Test]
public void FileSizesUseTheDecimalSeparatorOfTheCulture()
{
Assert.That(1_992_294L.FileSize(CultureInfo.GetCultureInfo("de-DE")), Is.EqualTo("1,9 MB"));
}
}
+192
View File
@@ -0,0 +1,192 @@
using AIStudio.Settings;
using AIStudio.Settings.DataModel;
using AIStudio.Tools;
using AIStudio.Tools.Media;
using AIStudio.Tools.Services;
using AIStudio.Tools.Services.Indexing;
using Microsoft.Extensions.Logging.Abstractions;
namespace AIStudio.Tests.Tools;
/// <summary>
/// Checks combinations of media, transcripts, and ordinary Markdown against the current folder
/// selection rules. Transcript reuse exercises the shared implementation used by RAG and batch.
/// </summary>
public sealed class MediaFileSelectionTests
{
private const string TRANSCRIPT_TEXT = "# Transcript\nSpoken content from the recording.";
private string rootPath = string.Empty;
private string userFolder = string.Empty;
[SetUp]
public void CreateDirectory()
{
this.rootPath = Path.Combine(Path.GetTempPath(), $"ai-studio-media-selection-{Guid.NewGuid():N}");
this.userFolder = Path.Combine(this.rootPath, "user");
Directory.CreateDirectory(this.userFolder);
}
[TearDown]
public void DeleteDirectory() => Directory.Delete(this.rootPath, true);
[TestCase("*.wav", "*.md")]
[TestCase("*.wav;*.md", "*.md")]
[TestCase("*", "*.md")]
public async Task ExcludingMarkdownStillSelectsTheMedia(string includes, string excludes)
{
var mediaPath = await this.CreateMediaAndDocumentsAsync();
Assert.That(this.Discover(includes, excludes), Is.EqualTo(new[] { mediaPath }));
}
[TestCase("*.md")]
[TestCase("*.wav;*.md")]
[TestCase("*")]
public async Task ExcludingMediaKeepsOrdinaryMarkdownAndTheIndependentTranscript(string includes)
{
var mediaPath = await this.CreateMediaAndDocumentsAsync();
Assert.That(this.Discover(includes, "*.wav"), Is.EquivalentTo(new[]
{
MediaSidecarTranscripts.GetFilePath(mediaPath),
Path.Combine(this.userFolder, "notes.md"),
Path.Combine(this.userFolder, "readme.md"),
}));
}
[Test]
public async Task SelectingMediaAndMarkdownDoesNotProcessTheTranscriptTwice()
{
var mediaPath = await this.CreateMediaAndDocumentsAsync();
Assert.That(this.Discover("*.wav;*.md", string.Empty), Is.EquivalentTo(new[]
{
mediaPath,
Path.Combine(this.userFolder, "notes.md"),
Path.Combine(this.userFolder, "readme.md"),
}));
}
[TestCase(false)]
[TestCase(true)]
public async Task ExistingTranscriptsAreReusedWithoutAProviderOrARewrite(bool dataSourceTranscript)
{
var mediaPath = await this.CreateMediaAndDocumentsAsync();
IMediaTranscriptLocation location = dataSourceTranscript
? this.TranscriptStore()
: MediaSidecarTranscripts.INSTANCE;
var slot = await location.ResolveAsync(mediaPath, CancellationToken.None);
Directory.CreateDirectory(Path.GetDirectoryName(slot.Path)!);
await File.WriteAllTextAsync(slot.Path, TRANSCRIPT_TEXT);
var originalWriteTime = File.GetLastWriteTimeUtc(slot.Path);
var calls = 0;
Task<MediaTranscriptionResult> Transcribe(string _, TranscriptionProvider __, CancellationToken ___)
{
calls++;
throw new InvalidOperationException("An existing transcript must not ask a provider.");
}
for (var attempt = 0; attempt < 2; attempt++)
{
var result = await PersistentMediaTranscript.ReadOrCreateAsync(
location, mediaPath, null, Transcribe, NullLogger.Instance, CancellationToken.None);
Assert.Multiple(() =>
{
Assert.That(result.Status, Is.EqualTo(PersistentMediaTranscriptStatus.READY));
Assert.That(result.Reused, Is.True);
Assert.That(File.ReadAllText(result.TranscriptPath), Is.EqualTo(TRANSCRIPT_TEXT));
Assert.That(File.GetLastWriteTimeUtc(slot.Path), Is.EqualTo(originalWriteTime));
Assert.That(calls, Is.Zero);
});
}
}
[Test]
public async Task ChangedMediaDoesNotReuseAnOldDataSourceTranscriptEvenWithTheSameSizeAndWriteTime()
{
var mediaPath = await this.CreateMediaAndDocumentsAsync();
var store = this.TranscriptStore();
var originalSlot = await store.ResolveAsync(mediaPath, CancellationToken.None);
Directory.CreateDirectory(Path.GetDirectoryName(originalSlot.Path)!);
await File.WriteAllTextAsync(originalSlot.Path, TRANSCRIPT_TEXT);
var originalWriteTime = File.GetLastWriteTimeUtc(mediaPath);
await File.WriteAllBytesAsync(mediaPath, [4, 5, 6]);
File.SetLastWriteTimeUtc(mediaPath, originalWriteTime);
var changedSlot = await store.ResolveAsync(mediaPath, CancellationToken.None);
var result = await PersistentMediaTranscript.ReadOrCreateAsync(
store, mediaPath, null,
(_, _, _) => throw new InvalidOperationException("No provider was configured."),
NullLogger.Instance, CancellationToken.None);
Assert.Multiple(() =>
{
Assert.That(changedSlot.Key, Is.Not.EqualTo(originalSlot.Key));
Assert.That(result.Status, Is.EqualTo(PersistentMediaTranscriptStatus.NO_PROVIDER));
Assert.That(File.ReadAllText(originalSlot.Path), Is.EqualTo(TRANSCRIPT_TEXT));
});
}
[TestCase("*.wav", "*.md", false, false)]
[TestCase("*.md", "*.wav", true, true)]
[TestCase("*.wav;*.md", "", true, false)]
public async Task WatcherUsesTheSelectionOfTheMediaWhenDecidingAboutItsSidecar(
string includes, string excludes, bool markdownChangesMatter, bool transcriptChangesMatter)
{
var mediaPath = await this.CreateMediaAndDocumentsAsync();
var configuration = new FileSourceIndexer.DataSourceWatcherConfiguration(
this.userFolder,
false,
FilePatterns: FileNamePatterns.Parse(includes),
ExcludePatterns: FileExclusions.Parse(excludes));
Assert.Multiple(() =>
{
Assert.That(
FileSourceIndexer.IsRelevantWatcherPath(configuration, Path.Combine(this.userFolder, "notes.md"), WatcherChangeTypes.Changed),
Is.EqualTo(markdownChangesMatter));
Assert.That(
FileSourceIndexer.IsRelevantWatcherPath(configuration, MediaSidecarTranscripts.GetFilePath(mediaPath), WatcherChangeTypes.Changed),
Is.EqualTo(transcriptChangesMatter));
});
}
private async Task<string> CreateMediaAndDocumentsAsync()
{
var mediaPath = Path.Combine(this.userFolder, "test123.wav");
// These bytes cannot be decoded: reuse must find the transcript before asking a provider.
await File.WriteAllBytesAsync(mediaPath, [1, 2, 3]);
await File.WriteAllTextAsync(MediaSidecarTranscripts.GetFilePath(mediaPath), TRANSCRIPT_TEXT);
await File.WriteAllTextAsync(Path.Combine(this.userFolder, "notes.md"), "# Ordinary notes");
await File.WriteAllTextAsync(Path.Combine(this.userFolder, "readme.md"), "# Ordinary readme");
return mediaPath;
}
private IReadOnlyList<string> Discover(string includes, string excludes)
{
using var indexer = new FileSourceIndexer(null!, null!, null!, null!, NullLogger.Instance);
var result = indexer.GetInputFiles(new DataSourceLocalDirectory
{
Type = DataSourceType.LOCAL_DIRECTORY,
Path = this.userFolder,
FilePatterns = includes,
ExcludePatterns = excludes,
IncludeSubdirectories = false,
}, new DataSourceEmbeddingManifest(),
MediaTranscriptionAvailability.Allowed(new TranscriptionProvider { Id = "test-transcription-provider" }));
Assert.Multiple(() =>
{
Assert.That(result.Failures, Is.Empty);
Assert.That(result.IgnoredFiles, Is.Empty);
});
return result.Files.Select(file => file.FullName).ToArray();
}
private DataSourceTranscriptStore TranscriptStore() => new(
Path.Combine(this.rootPath, "data"), "d401ce54-9a42-4e78-85de-2402e07f7f7e");
}