diff --git a/app/MindWork AI Studio/Assistants/I18N/allTexts.lua b/app/MindWork AI Studio/Assistants/I18N/allTexts.lua
index b8314057..a5a64ca5 100644
--- a/app/MindWork AI Studio/Assistants/I18N/allTexts.lua
+++ b/app/MindWork AI Studio/Assistants/I18N/allTexts.lua
@@ -6319,30 +6319,6 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::RAG::RAGPROCESSES::AISRCSELWITHRETCTXVAL::T304
-- AI source selection with AI retrieval context validation
UI_TEXT_CONTENT["AISTUDIO::TOOLS::RAG::RAGPROCESSES::AISRCSELWITHRETCTXVAL::T3775725978"] = "AI source selection with AI retrieval context validation"
--- Executable Files
-UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPEFILTER::T2217313358"] = "Executable Files"
-
--- All Source Code Files
-UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPEFILTER::T2460199369"] = "All Source Code Files"
-
--- All Audio Files
-UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPEFILTER::T2575722901"] = "All Audio Files"
-
--- All Video Files
-UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPEFILTER::T2850789856"] = "All Video Files"
-
--- PDF Files
-UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPEFILTER::T3108466742"] = "PDF Files"
-
--- All Image Files
-UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPEFILTER::T4086723714"] = "All Image Files"
-
--- Text Files
-UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPEFILTER::T639143005"] = "Text Files"
-
--- All Office Files
-UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPEFILTER::T709668067"] = "All Office Files"
-
-- Text
UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPES::T1041509726"] = "Text"
@@ -6352,6 +6328,12 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPES::T1063218378"] = "Office Files
-- Executable
UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPES::T1364437037"] = "Executable"
+-- Mail
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPES::T1399880782"] = "Mail"
+
+-- Source like
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPES::T1487238587"] = "Source like"
+
-- Image
UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPES::T1494001562"] = "Image"
@@ -6373,6 +6355,9 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPES::T2502277006"] = "Custom"
-- Media
UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPES::T3507473059"] = "Media"
+-- Source like prefix
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPES::T378481461"] = "Source like prefix"
+
-- Document
UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPES::T4165204724"] = "Document"
diff --git a/app/MindWork AI Studio/Chat/FileAttachment.cs b/app/MindWork AI Studio/Chat/FileAttachment.cs
index 42a696c0..558b7917 100644
--- a/app/MindWork AI Studio/Chat/FileAttachment.cs
+++ b/app/MindWork AI Studio/Chat/FileAttachment.cs
@@ -58,14 +58,11 @@ public record FileAttachment(FileAttachmentType Type, string FileName, string Fi
/// extracting the filename, and reading the file size.
///
/// The full path to the file.
- /// Optional: The allowed file types.
/// A FileAttachment instance with populated properties.
- public static FileAttachment FromPath(string filePath, FileType[]? allowedTypes=null)
+ public static FileAttachment FromPath(string filePath)
{
var fileName = Path.GetFileName(filePath);
var fileSize = File.Exists(filePath) ? new FileInfo(filePath).Length : 0;
- if (allowedTypes != null && !IsAllowed(filePath, allowedTypes))
- return new FileAttachment(FileAttachmentType.FORBIDDEN, fileName, filePath, fileSize);
var type = DetermineFileType(filePath);
return type switch
@@ -79,37 +76,23 @@ public record FileAttachment(FileAttachmentType Type, string FileName, string Fi
///
/// Determines the file attachment type based on the file extension.
- /// Uses centrally defined file types from .
+ /// Uses centrally defined file type filters from .
///
/// The file path to analyze.
/// The corresponding FileAttachmentType.
private static FileAttachmentType DetermineFileType(string filePath)
{
- var extension = Path.GetExtension(filePath).TrimStart('.').ToLowerInvariant();
+ if (FileTypes.IsAllowedPath(filePath, FileTypes.EXECUTABLES))
+ return FileAttachmentType.FORBIDDEN;
- // Check if it's an image file:
- if (FileTypes.OnlyAllowTypes(FileTypes.IMAGE).Contains(extension))
- {
+ if (FileTypes.IsAllowedPath(filePath, FileTypes.IMAGE))
return FileAttachmentType.IMAGE;
- }
- // Check if it's an audio file:
- if (FileTypes.OnlyAllowTypes(FileTypes.AUDIO).Contains(extension))
+ if (FileTypes.IsAllowedPath(filePath, FileTypes.AUDIO))
return FileAttachmentType.AUDIO;
- // Check if it's an allowed document file (PDF, Text, or Office):
- if (FileTypes.OnlyAllowTypes(FileTypes.DOCUMENT).Contains(extension))
- {
- return FileAttachmentType.DOCUMENT;
- }
-
- // All other file types are forbidden:
- return FileAttachmentType.FORBIDDEN;
+ return FileTypes.IsAllowedPath(filePath, FileTypes.DOCUMENT)
+ ? FileAttachmentType.DOCUMENT
+ : FileAttachmentType.FORBIDDEN;
}
-
- private static bool IsAllowed(string filePath, FileType[] allowedTypes)
- {
- var extension = Path.GetExtension(filePath).TrimStart('.').ToLowerInvariant();
- return FileTypes.OnlyAllowTypes(allowedTypes).Contains(extension);
- }
-}
\ No newline at end of file
+}
diff --git a/app/MindWork AI Studio/Components/AttachDocuments.razor.cs b/app/MindWork AI Studio/Components/AttachDocuments.razor.cs
index be83d51b..e4a0fe0f 100644
--- a/app/MindWork AI Studio/Components/AttachDocuments.razor.cs
+++ b/app/MindWork AI Studio/Components/AttachDocuments.razor.cs
@@ -48,9 +48,6 @@ public partial class AttachDocuments : MSGComponentBase
[Parameter]
public bool UseSmallForm { get; set; }
- [Parameter]
- public FileType[]? AllowedFileTypes { get; set; }
-
///
/// When true, validate media file types before attaching. Default is true. That means that
/// the user cannot attach unsupported media file types when the provider or model does not
@@ -184,7 +181,7 @@ public partial class AttachDocuments : MSGComponentBase
{
if(!await FileExtensionValidation.IsExtensionValidWithNotifyAsync(FileExtensionValidation.UseCase.ATTACHING_CONTENT, path, this.ValidateMediaFileTypes, this.Provider))
continue;
- this.DocumentPaths.Add(FileAttachment.FromPath(path, this.AllowedFileTypes));
+ this.DocumentPaths.Add(FileAttachment.FromPath(path));
}
await this.DocumentPathsChanged.InvokeAsync(this.DocumentPaths);
@@ -228,7 +225,7 @@ public partial class AttachDocuments : MSGComponentBase
if (!await FileExtensionValidation.IsExtensionValidWithNotifyAsync(FileExtensionValidation.UseCase.ATTACHING_CONTENT, selectedFilePath, this.ValidateMediaFileTypes, this.Provider))
continue;
- this.DocumentPaths.Add(FileAttachment.FromPath(selectedFilePath, this.AllowedFileTypes));
+ this.DocumentPaths.Add(FileAttachment.FromPath(selectedFilePath));
}
await this.DocumentPathsChanged.InvokeAsync(this.DocumentPaths);
diff --git a/app/MindWork AI Studio/Components/SelectFile.razor.cs b/app/MindWork AI Studio/Components/SelectFile.razor.cs
index c7b4dace..91c7a667 100644
--- a/app/MindWork AI Studio/Components/SelectFile.razor.cs
+++ b/app/MindWork AI Studio/Components/SelectFile.razor.cs
@@ -23,7 +23,7 @@ public partial class SelectFile : MSGComponentBase
public string FileDialogTitle { get; set; } = "Select File";
[Parameter]
- public FileType[]? Filter { get; set; }
+ public FileTypeFilter[]? Filter { get; set; }
[Parameter]
public Func Validation { get; set; } = _ => null;
diff --git a/app/MindWork AI Studio/Dialogs/EmbeddingProviderDialog.razor b/app/MindWork AI Studio/Dialogs/EmbeddingProviderDialog.razor
index 6e5a595b..85e6e6ef 100644
--- a/app/MindWork AI Studio/Dialogs/EmbeddingProviderDialog.razor
+++ b/app/MindWork AI Studio/Dialogs/EmbeddingProviderDialog.razor
@@ -1,6 +1,5 @@
@using AIStudio.Provider
@using AIStudio.Provider.SelfHosted
-@using AIStudio.Tools.Rust
@inherits MSGComponentBase
@@ -125,7 +124,6 @@
Validation="@this.providerValidation.ValidatingInstanceName"
UserAttributes="@SPELLCHECK_ATTRIBUTES"
/>
-
diff --git a/app/MindWork AI Studio/Dialogs/EmbeddingProviderDialog.razor.cs b/app/MindWork AI Studio/Dialogs/EmbeddingProviderDialog.razor.cs
index a3b66dbe..33e4db18 100644
--- a/app/MindWork AI Studio/Dialogs/EmbeddingProviderDialog.razor.cs
+++ b/app/MindWork AI Studio/Dialogs/EmbeddingProviderDialog.razor.cs
@@ -1,4 +1,3 @@
-using AIStudio.Chat;
using AIStudio.Components;
using AIStudio.Provider;
using AIStudio.Settings;
@@ -97,7 +96,6 @@ public partial class EmbeddingProviderDialog : MSGComponentBase, ISecretId
private readonly List availableModels = new();
private readonly Encryption encryption = Program.ENCRYPTION;
private readonly ProviderValidation providerValidation;
- private HashSet chatDocumentPaths = [];
public EmbeddingProviderDialog()
{
diff --git a/app/MindWork AI Studio/Plugins/languages/de-de-43065dbc-78d0-45b7-92be-f14c2926e2dc/plugin.lua b/app/MindWork AI Studio/Plugins/languages/de-de-43065dbc-78d0-45b7-92be-f14c2926e2dc/plugin.lua
index c518d439..09363a3f 100644
--- a/app/MindWork AI Studio/Plugins/languages/de-de-43065dbc-78d0-45b7-92be-f14c2926e2dc/plugin.lua
+++ b/app/MindWork AI Studio/Plugins/languages/de-de-43065dbc-78d0-45b7-92be-f14c2926e2dc/plugin.lua
@@ -6321,29 +6321,47 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::RAG::RAGPROCESSES::AISRCSELWITHRETCTXVAL::T304
-- AI-based data source selection with AI retrieval context validation
UI_TEXT_CONTENT["AISTUDIO::TOOLS::RAG::RAGPROCESSES::AISRCSELWITHRETCTXVAL::T3775725978"] = "KI-basierte Datenquellen-Auswahl mit Validierung des Abrufkontexts"
--- Executable Files
-UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPEFILTER::T2217313358"] = "Ausführbare Dateien"
+-- Text
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPES::T1041509726"] = "Text"
--- All Source Code Files
-UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPEFILTER::T2460199369"] = "Alle Quellcodedateien"
+-- Office Files
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPES::T1063218378"] = "Office-Dateien"
--- All Audio Files
-UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPEFILTER::T2575722901"] = "Alle Audiodateien"
+-- Executable
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPES::T1364437037"] = "Ausführbare Dateien"
--- All Video Files
-UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPEFILTER::T2850789856"] = "Alle Videodateien"
+-- Mail
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPES::T1399880782"] = "E-Mail"
--- PDF Files
-UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPEFILTER::T3108466742"] = "PDF-Dateien"
+-- Source like
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPES::T1487238587"] = "Source Code ähnlich"
--- All Image Files
-UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPEFILTER::T4086723714"] = "Alle Bilddateien"
+-- Image
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPES::T1494001562"] = "Bild"
--- Text Files
-UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPEFILTER::T639143005"] = "Textdateien"
+-- Video
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPES::T1533528076"] = "Video"
--- All Office Files
-UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPEFILTER::T709668067"] = "Alle Office-Dateien"
+-- Source Code
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPES::T1569048941"] = "Quellcode"
+
+-- Config
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPES::T1779622119"] = "Konfiguration"
+
+-- Audio
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPES::T2291602489"] = "Audio"
+
+-- Custom
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPES::T2502277006"] = "Benutzerdefiniert"
+
+-- Media
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPES::T3507473059"] = "Medien"
+
+-- Source like prefix
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPES::T378481461"] = "Source Code ähnlicher Prefix"
+
+-- Document
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPES::T4165204724"] = "Dokument"
-- Pandoc Installation
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PANDOCAVAILABILITYSERVICE::T185447014"] = "Pandoc-Installation"
diff --git a/app/MindWork AI Studio/Plugins/languages/en-us-97dfb1ba-50c4-4440-8dfa-6575daf543c8/plugin.lua b/app/MindWork AI Studio/Plugins/languages/en-us-97dfb1ba-50c4-4440-8dfa-6575daf543c8/plugin.lua
index a4fdfd5c..e9410700 100644
--- a/app/MindWork AI Studio/Plugins/languages/en-us-97dfb1ba-50c4-4440-8dfa-6575daf543c8/plugin.lua
+++ b/app/MindWork AI Studio/Plugins/languages/en-us-97dfb1ba-50c4-4440-8dfa-6575daf543c8/plugin.lua
@@ -6321,29 +6321,47 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::RAG::RAGPROCESSES::AISRCSELWITHRETCTXVAL::T304
-- AI-based data source selection with AI retrieval context validation
UI_TEXT_CONTENT["AISTUDIO::TOOLS::RAG::RAGPROCESSES::AISRCSELWITHRETCTXVAL::T3775725978"] = "AI-based data source selection with AI retrieval context validation"
--- Executable Files
-UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPEFILTER::T2217313358"] = "Executable Files"
+-- Text
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPES::T1041509726"] = "Text"
--- All Source Code Files
-UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPEFILTER::T2460199369"] = "All Source Code Files"
+-- Office Files
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPES::T1063218378"] = "Office Files"
--- All Audio Files
-UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPEFILTER::T2575722901"] = "All Audio Files"
+-- Executable
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPES::T1364437037"] = "Executable"
--- All Video Files
-UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPEFILTER::T2850789856"] = "All Video Files"
+-- Mail
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPES::T1399880782"] = "Mail"
--- PDF Files
-UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPEFILTER::T3108466742"] = "PDF Files"
+-- Source like
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPES::T1487238587"] = "Source like"
--- All Image Files
-UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPEFILTER::T4086723714"] = "All Image Files"
+-- Image
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPES::T1494001562"] = "Image"
--- Text Files
-UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPEFILTER::T639143005"] = "Text Files"
+-- Video
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPES::T1533528076"] = "Video"
--- All Office Files
-UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPEFILTER::T709668067"] = "All Office Files"
+-- Source Code
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPES::T1569048941"] = "Source Code"
+
+-- Config
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPES::T1779622119"] = "Config"
+
+-- Audio
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPES::T2291602489"] = "Audio"
+
+-- Custom
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPES::T2502277006"] = "Custom"
+
+-- Media
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPES::T3507473059"] = "Media"
+
+-- Source like prefix
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPES::T378481461"] = "Source like prefix"
+
+-- Document
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPES::T4165204724"] = "Document"
-- Pandoc Installation
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PANDOCAVAILABILITYSERVICE::T185447014"] = "Pandoc Installation"
diff --git a/app/MindWork AI Studio/Tools/Rust/FileType.cs b/app/MindWork AI Studio/Tools/Rust/FileType.cs
deleted file mode 100644
index c333a691..00000000
--- a/app/MindWork AI Studio/Tools/Rust/FileType.cs
+++ /dev/null
@@ -1,41 +0,0 @@
-namespace AIStudio.Tools.Rust;
-
-///
-/// Represents a file type that can optionally contain child file types.
-/// Use the static helpers , and to build readable trees.
-///
-/// Display name of the type (e.g., "Document").
-/// File extensions belonging to this type (without dot).
-/// Nested file types that are included when this type is selected.
-public sealed record FileType(string FilterName, string[] FilterExtensions, IReadOnlyList Children)
-{
- ///
- /// Factory for a leaf node.
- /// Example: FileType.Leaf(".NET", "cs", "razor")
- ///
- public static FileType Leaf(string name, params string[] extensions) =>
- new(name, extensions, []);
-
- ///
- /// Factory for a parent node that only has children.
- /// Example: FileType.Parent("Source Code", dotnet, java)
- ///
- public static FileType Parent(string name, params FileType[]? children) =>
- new(name, [], children ?? []);
-
- ///
- /// Factory for a composite node that has its own extensions in addition to children.
- ///
- public static FileType Composite(string name, string[] extensions, params FileType[] children) =>
- new(name, extensions, children);
-
- ///
- /// Collects all extensions for this type, including children.
- ///
- public IEnumerable FlattenExtensions()
- {
- return this.FilterExtensions
- .Concat(this.Children.SelectMany(child => child.FlattenExtensions()))
- .Distinct(StringComparer.OrdinalIgnoreCase);
- }
-}
\ No newline at end of file
diff --git a/app/MindWork AI Studio/Tools/Rust/FileTypeFilter.cs b/app/MindWork AI Studio/Tools/Rust/FileTypeFilter.cs
index 03232070..f4cd1c7e 100644
--- a/app/MindWork AI Studio/Tools/Rust/FileTypeFilter.cs
+++ b/app/MindWork AI Studio/Tools/Rust/FileTypeFilter.cs
@@ -1,80 +1,49 @@
-// ReSharper disable NotAccessedPositionalProperty.Global
-
-using AIStudio.Tools.PluginSystem;
-
namespace AIStudio.Tools.Rust;
///
-/// Represents a file type filter for file selection dialogs.
+/// Represents a file type that can optionally contain child file types.
+/// Use the static helpers , and to build readable trees.
///
-/// The name of the filter.
-/// The file extensions associated with the filter.
-public readonly record struct FileTypeFilter(string FilterName, string[] FilterExtensions)
+/// Display name of the type (e.g., "Document").
+/// File extensions belonging to this type (without dot).
+/// Nested file types that are included when this type is selected.
+public sealed record FileTypeFilter(string FilterName, string[] FilterExtensions, IReadOnlyList Children)
{
- private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(FileTypeFilter).Namespace, nameof(FileTypeFilter));
+ ///
+ /// Factory for a leaf node.
+ /// Example: FileType.Leaf(".NET", "cs", "razor")
+ ///
+ public static FileTypeFilter Leaf(string name, params string[] extensions) =>
+ new(name, extensions, []);
+
+ ///
+ /// Factory for a parent node that only has children.
+ /// Example: FileType.Parent("Source Code", dotnet, java)
+ ///
+ public static FileTypeFilter Parent(string name, params FileTypeFilter[]? children) =>
+ new(name, [], children ?? []);
+
+ ///
+ /// Factory for a composite node that has its own extensions in addition to children.
+ ///
+ public static FileTypeFilter Composite(string name, string[] extensions, params FileTypeFilter[] children) =>
+ new(name, extensions, children);
- public static FileTypeFilter PDF => new(TB("PDF Files"), ["pdf"]);
+ ///
+ /// Collects all extensions for this type, including children.
+ ///
+ public IEnumerable FlattenExtensions()
+ {
+ return this.FilterExtensions
+ .Concat(this.Children.SelectMany(child => child.FlattenExtensions()))
+ .Distinct(StringComparer.OrdinalIgnoreCase);
+ }
- public static FileTypeFilter Text => new(TB("Text Files"), ["txt", "md"]);
-
- public static FileTypeFilter AllOffice => new(TB("All Office Files"), ["docx", "xlsx", "pptx", "doc", "xls", "ppt", "pdf"]);
-
- public static FileTypeFilter AllImages => new(TB("All Image Files"), ["jpg", "jpeg", "png", "gif", "bmp", "tiff", "svg", "webp", "heic"]);
-
- public static FileTypeFilter AllVideos => new(TB("All Video Files"), ["mp4", "m4v", "avi", "mkv", "mov", "wmv", "flv", "webm"]);
-
- public static FileTypeFilter AllAudio => new(TB("All Audio Files"), ["mp3", "wav", "wave", "aac", "flac", "ogg", "m4a", "wma", "alac", "aiff", "m4b"]);
-
- public static FileTypeFilter AllSourceCode => new(TB("All Source Code Files"),
- [
- // .NET
- "cs", "vb", "fs", "razor", "aspx", "cshtml", "csproj",
-
- // Java:
- "java",
-
- // Python:
- "py",
-
- // JavaScript/TypeScript:
- "js", "ts",
-
- // C/C++:
- "c", "cpp", "h", "hpp",
-
- // Ruby:
- "rb",
-
- // Go:
- "go",
-
- // Rust:
- "rs",
-
- // Lua:
- "lua",
-
- // PHP:
- "php",
-
- // HTML/CSS:
- "html", "css",
-
- // Swift/Kotlin:
- "swift", "kt",
-
- // Shell scripts:
- "sh", "bash",
-
- // Logging files:
- "log",
-
- // JSON/YAML/XML:
- "json", "yaml", "yml", "xml",
-
- // Config files:
- "ini", "cfg", "toml", "plist",
- ]);
-
- public static FileTypeFilter Executables => new(TB("Executable Files"), ["exe", "app", "bin", "appimage"]);
+ public bool ContainsType(FileTypeFilter target)
+ {
+ if (this == target)
+ return true;
+
+ return this.Children.Any(child => child.ContainsType(target));
+ }
}
\ No newline at end of file
diff --git a/app/MindWork AI Studio/Tools/Rust/FileTypes.cs b/app/MindWork AI Studio/Tools/Rust/FileTypes.cs
index ff6fcd88..a6e8fd58 100644
--- a/app/MindWork AI Studio/Tools/Rust/FileTypes.cs
+++ b/app/MindWork AI Studio/Tools/Rust/FileTypes.cs
@@ -8,77 +8,123 @@ namespace AIStudio.Tools.Rust;
///
public static class FileTypes
{
- private static string TB(string fallbackEn) => I18N.I.T(fallbackEn, typeof(FileType).Namespace, nameof(FileType));
+ private static string TB(string fallbackEn) => I18N.I.T(fallbackEn, typeof(FileTypeFilter).Namespace, nameof(FileTypeFilter));
+
+ // Keep SOURCE_LIKE in the same leaf style as the other file types.
+ // These values are not sufficient for Dockerfile-style files without extensions,
+ // therefore IsAllowedSourceLikeFileName is still required for real matching.
+ public static readonly FileTypeFilter SOURCE_LIKE_FILE_NAMES = FileTypeFilter.Leaf(TB("Source like"),
+ "Dockerfile", "Containerfile", "Jenkinsfile", "Makefile", "GNUmakefile", "Procfile", "Vagrantfile",
+ "Tiltfile", "Justfile", "Brewfile", "Caddyfile", "Gemfile", "Podfile", "Fastfile", "Appfile", "Rakefile", "Dangerfile",
+ "BUILD", "WORKSPACE", "BUCK");
+
+ public static readonly FileTypeFilter SOURCE_LIKE_FILE_NAME_PREFIXES = FileTypeFilter.Leaf(TB("Source like prefix"),
+ "Dockerfile", "Containerfile", "Jenkinsfile", "Procfile", "Caddyfile");
// Source code hierarchy: SourceCode -> (.NET, Java, Python, Web, C/C++, Config, ...)
- public static readonly FileType DOTNET = FileType.Leaf(".NET", "cs", "razor", "vb", "fs", "aspx", "cshtml", "csproj");
- public static readonly FileType JAVA = FileType.Leaf("Java", "java");
- public static readonly FileType PYTHON = FileType.Leaf("Python", "py");
- public static readonly FileType JAVASCRIPT = FileType.Leaf("JavaScript/TypeScript", "js", "ts");
- public static readonly FileType CFAMILY = FileType.Leaf("C/C++", "c", "cpp", "h", "hpp");
- public static readonly FileType RUBY = FileType.Leaf("Ruby", "rb");
- public static readonly FileType GO = FileType.Leaf("Go", "go");
- public static readonly FileType RUST = FileType.Leaf("Rust", "rs");
- public static readonly FileType LUA = FileType.Leaf("Lua", "lua");
- public static readonly FileType PHP = FileType.Leaf("PHP", "php");
- public static readonly FileType WEB = FileType.Leaf("HTML/CSS", "html", "css");
- public static readonly FileType APP = FileType.Leaf("Swift/Kotlin", "swift", "kt");
- public static readonly FileType SHELL = FileType.Leaf("Shell", "sh", "bash", "zsh");
- public static readonly FileType LOG = FileType.Leaf("Log", "log");
- public static readonly FileType JSON = FileType.Leaf("JSON", "json");
- public static readonly FileType XML = FileType.Leaf("XML", "xml");
- public static readonly FileType YAML = FileType.Leaf("YAML", "yaml", "yml");
- public static readonly FileType CONFIG = FileType.Leaf(TB("Config"), "ini", "cfg", "toml", "plist");
-
- public static readonly FileType SOURCE_CODE = FileType.Parent(TB("Source Code"),
- DOTNET, JAVA, PYTHON, JAVASCRIPT, CFAMILY, RUBY, GO, RUST, LUA, PHP, WEB, APP, SHELL, LOG, JSON, XML, YAML, CONFIG);
+ public static readonly FileTypeFilter DOTNET = FileTypeFilter.Leaf(".NET", "cs", "razor", "vb", "fs", "aspx", "cshtml", "csproj");
+ public static readonly FileTypeFilter JAVA = FileTypeFilter.Leaf("Java", "java");
+ public static readonly FileTypeFilter PYTHON = FileTypeFilter.Leaf("Python", "py");
+ public static readonly FileTypeFilter JAVASCRIPT = FileTypeFilter.Leaf("JavaScript/TypeScript", "js", "ts");
+ public static readonly FileTypeFilter CFAMILY = FileTypeFilter.Leaf("C/C++", "c", "cpp", "h", "hpp");
+ public static readonly FileTypeFilter RUBY = FileTypeFilter.Leaf("Ruby", "rb");
+ public static readonly FileTypeFilter GO = FileTypeFilter.Leaf("Go", "go");
+ public static readonly FileTypeFilter RUST = FileTypeFilter.Leaf("Rust", "rs");
+ public static readonly FileTypeFilter LUA = FileTypeFilter.Leaf("Lua", "lua");
+ public static readonly FileTypeFilter PHP = FileTypeFilter.Leaf("PHP", "php");
+ public static readonly FileTypeFilter WEB = FileTypeFilter.Leaf("HTML/CSS", "html", "css");
+ public static readonly FileTypeFilter APP = FileTypeFilter.Leaf("Swift/Kotlin", "swift", "kt");
+ public static readonly FileTypeFilter SHELL = FileTypeFilter.Leaf("Shell", "sh", "bash", "zsh");
+ public static readonly FileTypeFilter LOG = FileTypeFilter.Leaf("Log", "log");
+ public static readonly FileTypeFilter JSON = FileTypeFilter.Leaf("JSON", "json");
+ public static readonly FileTypeFilter XML = FileTypeFilter.Leaf("XML", "xml");
+ public static readonly FileTypeFilter YAML = FileTypeFilter.Leaf("YAML", "yaml", "yml");
+ public static readonly FileTypeFilter CONFIG = FileTypeFilter.Leaf(TB("Config"), "ini", "cfg", "toml", "plist");
+
+ public static readonly FileTypeFilter SOURCE_CODE = FileTypeFilter.Parent(TB("Source Code"),
+ DOTNET, JAVA, PYTHON, JAVASCRIPT, CFAMILY, RUBY, GO, RUST, LUA, PHP, WEB, APP, SHELL, LOG, JSON, XML, YAML, CONFIG, SOURCE_LIKE_FILE_NAMES, SOURCE_LIKE_FILE_NAME_PREFIXES);
// Document hierarchy
- public static readonly FileType PDF = FileType.Leaf("PDF", "pdf");
- public static readonly FileType TEXT = FileType.Leaf(TB("Text"), "txt", "md");
- public static readonly FileType MS_WORD = FileType.Leaf("Microsoft Word", "docx");
- public static readonly FileType WORD = FileType.Composite("Word", ["docx"], MS_WORD);
- public static readonly FileType EXCEL = FileType.Leaf("Excel", "xls", "xlsx");
- public static readonly FileType POWER_POINT = FileType.Leaf("PowerPoint", "ppt", "pptx");
-
- public static readonly FileType OFFICE_FILES = FileType.Parent(TB("Office Files"),
+ public static readonly FileTypeFilter PDF = FileTypeFilter.Leaf("PDF", "pdf");
+ public static readonly FileTypeFilter TEXT = FileTypeFilter.Leaf(TB("Text"), "txt", "md");
+ public static readonly FileTypeFilter MS_WORD = FileTypeFilter.Leaf("Microsoft Word", "docx");
+ public static readonly FileTypeFilter WORD = FileTypeFilter.Composite("Word", ["doc"], MS_WORD);
+ public static readonly FileTypeFilter EXCEL = FileTypeFilter.Leaf("Excel", "xls", "xlsx");
+ public static readonly FileTypeFilter POWER_POINT = FileTypeFilter.Leaf("PowerPoint", "ppt", "pptx");
+ public static readonly FileTypeFilter MAIL = FileTypeFilter.Leaf(TB("Mail"), "eml", "msg", "mbox");
+
+ public static readonly FileTypeFilter OFFICE_FILES = FileTypeFilter.Parent(TB("Office Files"),
WORD, EXCEL, POWER_POINT, PDF);
- public static readonly FileType DOCUMENT = FileType.Parent(TB("Document"),
- TEXT, OFFICE_FILES, SOURCE_CODE);
+ public static readonly FileTypeFilter DOCUMENT = FileTypeFilter.Parent(TB("Document"),
+ TEXT, OFFICE_FILES, SOURCE_CODE, MAIL);
// Media hierarchy
- public static readonly FileType IMAGE = FileType.Leaf(TB("Image"),
+ public static readonly FileTypeFilter IMAGE = FileTypeFilter.Leaf(TB("Image"),
"jpg", "jpeg", "png", "gif", "bmp", "tiff", "svg", "webp", "heic");
- public static readonly FileType AUDIO = FileType.Leaf(TB("Audio"),
+ public static readonly FileTypeFilter AUDIO = FileTypeFilter.Leaf(TB("Audio"),
"mp3", "wav", "wave", "aac", "flac", "ogg", "m4a", "wma", "alac", "aiff", "m4b");
- public static readonly FileType VIDEO = FileType.Leaf(TB("Video"),
+ public static readonly FileTypeFilter VIDEO = FileTypeFilter.Leaf(TB("Video"),
"mp4", "m4v", "avi", "mkv", "mov", "wmv", "flv", "webm");
-
- public static readonly FileType MEDIA = FileType.Parent(TB("Media"), IMAGE, AUDIO, VIDEO);
+
+ public static readonly FileTypeFilter MEDIA = FileTypeFilter.Parent(TB("Media"), IMAGE, AUDIO, VIDEO);
// Other standalone types
- public static readonly FileType EXECUTABLES = FileType.Leaf(TB("Executable"), "exe", "app", "bin", "appimage");
+ public static readonly FileTypeFilter EXECUTABLES = FileTypeFilter.Leaf(TB("Executable"), "exe", "app", "bin", "appimage");
+
+ public static FileTypeFilter? AsOneFileType(params FileTypeFilter[]? types)
+ {
+ if (types == null || types.Length == 0)
+ return null;
+
+ if (types.Length == 1) return types[0];
- ///
- /// Builds a distinct, lower-cased list of extensions allowed for the provided types.
- /// Accepts both composite types (e.g., Document) and leaves (e.g., Pdf).
- ///
- public static string[] OnlyAllowTypes(params FileType[] types)
+ return FileTypeFilter.Composite(TB("Custom"), OnlyAllowTypes(types));
+ }
+
+ public static string[] OnlyAllowTypes(params FileTypeFilter[] types)
{
if (types.Length == 0)
return [];
return types
+ .Where(t => t != SOURCE_LIKE_FILE_NAMES && t != SOURCE_LIKE_FILE_NAME_PREFIXES)
.SelectMany(t => t.FlattenExtensions())
.Select(ext => ext.ToLowerInvariant())
.Distinct(StringComparer.OrdinalIgnoreCase)
.ToArray();
}
- public static FileType? AsOneFileType(params FileType[]? types)
+ ///
+ /// Validates a file path against the provided filters.
+ /// Supports extension-based matching and source-like file names (e.g. Dockerfile).
+ ///
+ public static bool IsAllowedPath(string filePath, params FileTypeFilter[]? types)
{
- if (types == null || types.Length == 0)
- return null;
- return FileType.Composite(TB("Custom"), OnlyAllowTypes(types));
+ if (types == null || types.Length == 0 || string.IsNullOrWhiteSpace(filePath))
+ return false;
+
+ var extension = Path.GetExtension(filePath).TrimStart('.');
+ if (!string.IsNullOrWhiteSpace(extension))
+ {
+ if (OnlyAllowTypes(types).Contains(extension, StringComparer.OrdinalIgnoreCase))
+ return true;
+ }
+
+ var fileName = Path.GetFileName(filePath);
+ if (string.IsNullOrWhiteSpace(fileName))
+ {
+ return false;
+ }
+
+ if (types.Any(t => t.ContainsType(SOURCE_LIKE_FILE_NAMES)))
+ {
+ if (SOURCE_LIKE_FILE_NAMES.FilterExtensions.Contains(fileName)) return true;
+ }
+
+ if (types.Any(t => t.ContainsType(SOURCE_LIKE_FILE_NAME_PREFIXES))){
+ if (SOURCE_LIKE_FILE_NAME_PREFIXES.FilterExtensions.Any(prefix => fileName.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))) return true;
+ }
+
+ return false;
}
}
diff --git a/app/MindWork AI Studio/Tools/Rust/SaveFileOptions.cs b/app/MindWork AI Studio/Tools/Rust/SaveFileOptions.cs
index f1300ac1..107e581a 100644
--- a/app/MindWork AI Studio/Tools/Rust/SaveFileOptions.cs
+++ b/app/MindWork AI Studio/Tools/Rust/SaveFileOptions.cs
@@ -6,5 +6,5 @@ public class SaveFileOptions
public PreviousFile? PreviousFile { get; init; }
- public FileType? Filter { get; init; }
+ public FileTypeFilter? Filter { get; init; }
}
\ No newline at end of file
diff --git a/app/MindWork AI Studio/Tools/Rust/SelectFileOptions.cs b/app/MindWork AI Studio/Tools/Rust/SelectFileOptions.cs
index fac7d5f4..28d16809 100644
--- a/app/MindWork AI Studio/Tools/Rust/SelectFileOptions.cs
+++ b/app/MindWork AI Studio/Tools/Rust/SelectFileOptions.cs
@@ -6,5 +6,5 @@ public sealed class SelectFileOptions
public PreviousFile? PreviousFile { get; init; }
- public FileType? Filter { get; init; }
+ public FileTypeFilter? Filter { get; init; }
}
\ No newline at end of file
diff --git a/app/MindWork AI Studio/Tools/Services/RustService.FileSystem.cs b/app/MindWork AI Studio/Tools/Services/RustService.FileSystem.cs
index c55b6a8b..76519eb2 100644
--- a/app/MindWork AI Studio/Tools/Services/RustService.FileSystem.cs
+++ b/app/MindWork AI Studio/Tools/Services/RustService.FileSystem.cs
@@ -17,7 +17,7 @@ public sealed partial class RustService
return await result.Content.ReadFromJsonAsync(this.jsonRustSerializerOptions);
}
- public async Task SelectFile(string title, FileType[]? filter = null, string? initialFile = null)
+ public async Task SelectFile(string title, FileTypeFilter[]? filter = null, string? initialFile = null)
{
var payload = new SelectFileOptions
{
@@ -36,7 +36,7 @@ public sealed partial class RustService
return await result.Content.ReadFromJsonAsync(this.jsonRustSerializerOptions);
}
- public async Task SelectFiles(string title, FileType[]? filter = null, string? initialFile = null)
+ public async Task SelectFiles(string title, FileTypeFilter[]? filter = null, string? initialFile = null)
{
var payload = new SelectFileOptions
{
@@ -63,7 +63,7 @@ public sealed partial class RustService
/// An optional initial file path to pre-fill in the dialog.
/// A object containing information about whether the user canceled the
/// operation and whether the select operation was successful.
- public async Task SaveFile(string title, FileType[]? filter = null, string? initialFile = null)
+ public async Task SaveFile(string title, FileTypeFilter[]? filter = null, string? initialFile = null)
{
var payload = new SaveFileOptions
{
diff --git a/app/MindWork AI Studio/Tools/Validation/FileExtensionValidation.cs b/app/MindWork AI Studio/Tools/Validation/FileExtensionValidation.cs
index a9a87367..d38a8c08 100644
--- a/app/MindWork AI Studio/Tools/Validation/FileExtensionValidation.cs
+++ b/app/MindWork AI Studio/Tools/Validation/FileExtensionValidation.cs
@@ -43,8 +43,7 @@ public static class FileExtensionValidation
/// True if valid, false if invalid (error/warning already sent via MessageBus).
public static async Task IsExtensionValidWithNotifyAsync(UseCase useCae, string filePath, bool validateMediaFileTypes = true, Settings.Provider? provider = null)
{
- var ext = Path.GetExtension(filePath).TrimStart('.').ToLowerInvariant();
- if(FileTypes.EXECUTABLES.FlattenExtensions().Contains(ext))
+ if (FileTypes.IsAllowedPath(filePath, FileTypes.EXECUTABLES))
{
await MessageBus.INSTANCE.SendError(new(
Icons.Material.Filled.AppBlocking,
@@ -53,7 +52,7 @@ public static class FileExtensionValidation
}
var capabilities = provider?.GetModelCapabilities() ?? new();
- if (FileTypes.IMAGE.FlattenExtensions().Contains(ext))
+ if (FileTypes.IsAllowedPath(filePath, FileTypes.IMAGE))
{
switch (useCae)
{
@@ -88,7 +87,7 @@ public static class FileExtensionValidation
}
}
- if(FileTypes.VIDEO.FlattenExtensions().Contains(ext))
+ if (FileTypes.IsAllowedPath(filePath, FileTypes.VIDEO))
{
await MessageBus.INSTANCE.SendWarning(new(
Icons.Material.Filled.FeaturedVideo,
@@ -96,7 +95,7 @@ public static class FileExtensionValidation
return false;
}
- if(FileTypes.AUDIO.FlattenExtensions().Contains(ext))
+ if (FileTypes.IsAllowedPath(filePath, FileTypes.AUDIO))
{
await MessageBus.INSTANCE.SendWarning(new(
Icons.Material.Filled.AudioFile,
@@ -123,7 +122,7 @@ public static class FileExtensionValidation
return false;
}
- if (FileTypes.IMAGE.FlattenExtensions().Any(x => x.Equals(ext, StringComparison.OrdinalIgnoreCase)))
+ if (FileTypes.IsAllowedPath(filePath, FileTypes.IMAGE))
{
await MessageBus.INSTANCE.SendError(new(
Icons.Material.Filled.ImageNotSupported,