Refactored file attachments (#608)
Build and Release / Read metadata (push) Has been cancelled
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-apple-darwin, osx-arm64, macos-latest, aarch64-apple-darwin, dmg updater) (push) Has been cancelled
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-pc-windows-msvc.exe, win-arm64, windows-latest, aarch64-pc-windows-msvc, nsis updater) (push) Has been cancelled
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-unknown-linux-gnu, linux-arm64, ubuntu-22.04-arm, aarch64-unknown-linux-gnu, appimage deb updater) (push) Has been cancelled
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-apple-darwin, osx-x64, macos-latest, x86_64-apple-darwin, dmg updater) (push) Has been cancelled
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-pc-windows-msvc.exe, win-x64, windows-latest, x86_64-pc-windows-msvc, nsis updater) (push) Has been cancelled
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-unknown-linux-gnu, linux-x64, ubuntu-22.04, x86_64-unknown-linux-gnu, appimage deb updater) (push) Has been cancelled
Build and Release / Prepare & create release (push) Has been cancelled
Build and Release / Publish release (push) Has been cancelled

This commit is contained in:
Thorsten Sommer authored and GitHub committed 2025-12-28 16:50:36 +01:00
1 parent 4be5002088
commit ed4c7d215a
26 files changed
+188 -76

No files matched your search

+1 -1
View File
@@ -32,7 +32,7 @@ public sealed class ContentImage : IContent, IImageSource
public List<Source> Sources { get; set; } = [];
/// <inheritdoc />
public List<string> FileAttachments { get; set; } = [];
public List<FileAttachment> FileAttachments { get; set; } = [];
/// <inheritdoc />
public Task<ChatThread> CreateFromProviderAsync(IProvider provider, Model chatModel, IContent? lastUserPrompt, ChatThread? chatChatThread, CancellationToken token = default)
+21 -15
View File
@@ -42,7 +42,7 @@ public sealed class ContentText : IContent
public List<Source> Sources { get; set; } = [];
/// <inheritdoc />
public List<string> FileAttachments { get; set; } = [];
public List<FileAttachment> FileAttachments { get; set; } = [];
/// <inheritdoc />
public async Task<ChatThread> CreateFromProviderAsync(IProvider provider, Model chatModel, IContent? lastUserPrompt, ChatThread? chatThread, CancellationToken token = default)
@@ -149,24 +149,24 @@ public sealed class ContentText : IContent
#endregion
public async Task<string> PrepareContentForAI()
public async Task<string> PrepareTextContentForAI()
{
var sb = new StringBuilder();
sb.AppendLine(this.Text);
if(this.FileAttachments.Count > 0)
{
// Filter out files that no longer exist
var existingFiles = this.FileAttachments.Where(File.Exists).ToList();
// Get the list of existing documents:
var existingDocuments = this.FileAttachments.Where(x => x.Type is FileAttachmentType.DOCUMENT && x.Exists).ToList();
// Log warning for missing files
var missingFiles = this.FileAttachments.Except(existingFiles).ToList();
if (missingFiles.Count > 0)
foreach (var missingFile in missingFiles)
LOGGER.LogWarning("File attachment no longer exists and will be skipped: '{MissingFile}'", missingFile);
// Only proceed if there are existing files
if (existingFiles.Count > 0)
// Log warning for missing files:
var missingDocuments = this.FileAttachments.Except(existingDocuments).Where(x => x.Type is FileAttachmentType.DOCUMENT).ToList();
if (missingDocuments.Count > 0)
foreach (var missingDocument in missingDocuments)
LOGGER.LogWarning("File attachment no longer exists and will be skipped: '{MissingDocument}'.", missingDocument.FilePath);
// Only proceed if there are existing, allowed documents:
if (existingDocuments.Count > 0)
{
// Check Pandoc availability once before processing file attachments
var pandocState = await Pandoc.CheckAvailabilityAsync(Program.RUST_SERVICE, showMessages: true, showSuccessMessage: false);
@@ -179,14 +179,20 @@ public sealed class ContentText : IContent
{
sb.AppendLine();
sb.AppendLine("The following files are attached to this message:");
foreach(var file in existingFiles)
foreach(var document in existingDocuments)
{
if (document.IsForbidden)
{
LOGGER.LogWarning("File attachment '{FilePath}' has a forbidden file type and will be skipped.", document.FilePath);
continue;
}
sb.AppendLine();
sb.AppendLine("---------------------------------------");
sb.AppendLine($"File path: {file}");
sb.AppendLine($"File path: {document.FilePath}");
sb.AppendLine("File content:");
sb.AppendLine("````");
sb.AppendLine(await Program.RUST_SERVICE.ReadArbitraryFileData(file, int.MaxValue));
sb.AppendLine(await Program.RUST_SERVICE.ReadArbitraryFileData(document.FilePath, int.MaxValue));
sb.AppendLine("````");
}
}
@@ -0,0 +1,71 @@
using AIStudio.Tools.Rust;
namespace AIStudio.Chat;
/// <summary>
/// Represents an immutable file attachment with details about its type, name, path, and size.
/// </summary>
/// <param name="Type">The type of the file attachment.</param>
/// <param name="FileName">The name of the file, including extension.</param>
/// <param name="FilePath">The full path to the file, including the filename and extension.</param>
/// <param name="FileSizeBytes">The size of the file in bytes.</param>
public readonly record struct FileAttachment(FileAttachmentType Type, string FileName, string FilePath, long FileSizeBytes)
{
/// <summary>
/// Gets a value indicating whether the file still exists on the file system.
/// </summary>
public bool Exists => File.Exists(this.FilePath);
/// <summary>
/// Gets a value indicating whether the file type is forbidden and should not be attached.
/// </summary>
public bool IsForbidden => this.Type == FileAttachmentType.FORBIDDEN;
/// <summary>
/// Gets a value indicating whether the file type is valid and allowed to be attached.
/// </summary>
public bool IsValid => this.Type != FileAttachmentType.FORBIDDEN;
/// <summary>
/// Creates a FileAttachment from a file path by automatically determining the type,
/// extracting the filename, and reading the file size.
/// </summary>
/// <param name="filePath">The full path to the file.</param>
/// <returns>A FileAttachment instance with populated properties.</returns>
public static FileAttachment FromPath(string filePath)
{
var fileName = Path.GetFileName(filePath);
var fileSize = File.Exists(filePath) ? new FileInfo(filePath).Length : 0;
var type = DetermineFileType(filePath);
return new FileAttachment(type, fileName, filePath, fileSize);
}
/// <summary>
/// Determines the file attachment type based on the file extension.
/// Uses centrally defined file type filters from <see cref="FileTypeFilter"/>.
/// </summary>
/// <param name="filePath">The file path to analyze.</param>
/// <returns>The corresponding FileAttachmentType.</returns>
private static FileAttachmentType DetermineFileType(string filePath)
{
var extension = Path.GetExtension(filePath).TrimStart('.').ToLowerInvariant();
// Check if it's an image file:
if (FileTypeFilter.AllImages.FilterExtensions.Contains(extension))
return FileAttachmentType.IMAGE;
// Check if it's an audio file:
if (FileTypeFilter.AllAudio.FilterExtensions.Contains(extension))
return FileAttachmentType.AUDIO;
// Check if it's an allowed document file (PDF, Text, or Office):
if (FileTypeFilter.PDF.FilterExtensions.Contains(extension) ||
FileTypeFilter.Text.FilterExtensions.Contains(extension) ||
FileTypeFilter.AllOffice.FilterExtensions.Contains(extension))
return FileAttachmentType.DOCUMENT;
// All other file types are forbidden:
return FileAttachmentType.FORBIDDEN;
}
}
@@ -0,0 +1,27 @@
namespace AIStudio.Chat;
/// <summary>
/// Represents different types of file attachments.
/// </summary>
public enum FileAttachmentType
{
/// <summary>
/// Document file types, such as .pdf, .docx, .txt, etc.
/// </summary>
DOCUMENT,
/// <summary>
/// All image file types, such as .jpg, .png, .gif, etc.
/// </summary>
IMAGE,
/// <summary>
/// All audio file types, such as .mp3, .wav, .aac, etc.
/// </summary>
AUDIO,
/// <summary>
/// Forbidden file types that should not be attached, such as executables.
/// </summary>
FORBIDDEN,
}
+2 -2
View File
@@ -50,10 +50,10 @@ public interface IContent
/// <summary>
/// Represents a collection of file attachments associated with the content.
/// This property contains a list of file paths that are appended
/// This property contains a list of file attachments that are appended
/// to the content to provide additional context or resources.
/// </summary>
public List<string> FileAttachments { get; set; }
public List<FileAttachment> FileAttachments { get; set; }
/// <summary>
/// Uses the provider to create the content.