using System.Text.Json.Serialization; using AIStudio.Tools.Rust; namespace AIStudio.Chat; /// /// Represents an immutable file attachment with details about its type, name, path, and size. /// /// The type of the file attachment. /// The name of the file, including extension. /// The full path to the file, including the filename and extension. /// The size of the file in bytes. [JsonPolymorphic(TypeDiscriminatorPropertyName = "$type")] [JsonDerivedType(typeof(FileAttachment), typeDiscriminator: "file")] [JsonDerivedType(typeof(FileAttachmentImage), typeDiscriminator: "image")] public record FileAttachment(FileAttachmentType Type, string FileName, string FilePath, long FileSizeBytes) { /// /// Gets a value indicating whether the file type is forbidden and should not be attached. /// /// /// The state is determined once during construction and does not change. /// public bool IsForbidden { get; } = Type == FileAttachmentType.FORBIDDEN; /// /// Gets a value indicating whether the file type is valid and allowed to be attached. /// /// /// The state is determined once during construction and does not change. /// public bool IsValid { get; } = Type != FileAttachmentType.FORBIDDEN; /// /// Gets a value indicating whether the file type is an image. /// /// /// The state is determined once during construction and does not change. /// public bool IsImage { get; } = Type == FileAttachmentType.IMAGE; /// /// Gets the file path for loading the file from the web browser-side (Blazor). /// public string FilePathAsUrl { get; } = FileHandler.CreateFileUrl(FilePath); /// /// Gets a value indicating whether the file still exists on the file system. /// /// /// This property checks the file system each time it is accessed. /// public bool Exists => File.Exists(this.FilePath); /// /// Creates a FileAttachment from a file path by automatically determining the type, /// 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) { 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 { FileAttachmentType.DOCUMENT => new FileAttachment(type, fileName, filePath, fileSize), FileAttachmentType.IMAGE => new FileAttachmentImage(fileName, filePath, fileSize), _ => new FileAttachment(type, fileName, filePath, fileSize), }; } /// /// Determines the file attachment type based on the file extension. /// Uses centrally defined file types from . /// /// The file path to analyze. /// The corresponding FileAttachmentType. private static FileAttachmentType DetermineFileType(string filePath) { var extension = Path.GetExtension(filePath).TrimStart('.').ToLowerInvariant(); // Check if it's an image file: if (FileTypes.OnlyAllowTypes(FileTypes.IMAGE).Contains(extension)) { return FileAttachmentType.IMAGE; } // Check if it's an audio file: if (FileTypes.OnlyAllowTypes(FileTypes.AUDIO).Contains(extension)) 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; } private static bool IsAllowed(string filePath, FileType[] allowedTypes) { var extension = Path.GetExtension(filePath).TrimStart('.').ToLowerInvariant(); return FileTypes.OnlyAllowTypes(allowedTypes).Contains(extension); } }