Skip to content

Commit ba99bf6

Browse files
Scaled down and converted images before sending them to AI models (#1101)
1 parent 1c8632b commit ba99bf6

33 files changed

Lines changed: 1549 additions & 263 deletions

‎app/MindWork AI Studio/Assistants/BatchProcessing/AssistantBatchProcessing.razor‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
</MudJustifiedText>
2727

2828
<MudJustifiedText Typo="Typo.body2" Class="mb-3">
29-
@T("Images in JPG, PNG, WebP, or GIF format are given directly to the model, so you need a model that understands images. If the selected model cannot read images, remove the image patterns, such as *.jpg or *.png, before you start.")
29+
@T("Images in JPG, PNG, WebP, GIF, BMP, or TIFF format are given to the model as images, so you need a model that understands images. If the selected model cannot read images, remove the image patterns, such as *.jpg or *.png, before you start.")
3030
</MudJustifiedText>
3131

3232
<MudStack Class="mb-3">

‎app/MindWork AI Studio/Assistants/BatchProcessing/AssistantBatchProcessing.razor.Content.cs‎

Lines changed: 22 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ public partial class AssistantBatchProcessing
2020
private async Task<BatchInput?> LoadInputContentAsync(BatchProcessingFileResult fileResult, CancellationToken token)
2121
{
2222
if (IsImage(fileResult.FilePath))
23-
return this.LoadImage(fileResult) is { } image ? new BatchInput(string.Empty, image) : null;
23+
return await this.LoadImageAsync(fileResult, token) is { } image ? new BatchInput(string.Empty, image) : null;
2424

2525
var text = IsTranscribableMedia(fileResult.FilePath)
2626
? await this.LoadMediaTranscriptAsync(fileResult, token)
@@ -30,13 +30,18 @@ public partial class AssistantBatchProcessing
3030
}
3131

3232
/// <summary>
33-
/// Checks an image before it goes to the model.
33+
/// Prepares an image for the model before the request is built.
3434
/// </summary>
3535
/// <remarks>
3636
/// The request itself would only skip an image it cannot send, with a message, and ask the model
3737
/// anyway. The model would then answer about nothing, and the file would count as done, so that
38-
/// continuing the run would skip it as well. A failed image is processed again when the run is
39-
/// continued, e.g., with a model that can read images.
38+
/// continuing the run would skip it as well. Preparing the image here lets it fail as a file
39+
/// instead, with the reason, e.g., an image format AI Studio cannot decode. The request then finds
40+
/// the prepared image in the cache of ModelImageService, so it is not prepared twice. A failed
41+
/// image is processed again when the run is continued, e.g., with a model that can read images.
42+
///
43+
/// How large an image may be needs no check of its own either: the preparation scales it down
44+
/// until it fits the limit of the provider, and fails only when even that is not enough.
4045
///
4146
/// How many images a model accepts needs no check of its own: every file gets a thread of its own,
4247
/// see CallAIAsync, so each request carries exactly one image, in every round with the model. A
@@ -49,39 +54,37 @@ public partial class AssistantBatchProcessing
4954
/// longer send data out, and without free address choice, its default, the Read Web Page tool
5055
/// refuses an address which appears only in the image.
5156
/// </remarks>
52-
private FileAttachmentImage? LoadImage(BatchProcessingFileResult fileResult)
57+
private async Task<FileAttachmentImage?> LoadImageAsync(BatchProcessingFileResult fileResult, CancellationToken token)
5358
{
5459
if (!this.ProviderSettings.SupportsImageInput())
5560
{
5661
this.FinishFileResult(fileResult, BatchProcessingFileStatus.FAILED, T("The selected model cannot read images. Select a model with image support to process this image."));
5762
return null;
5863
}
5964

60-
if (!FileTypes.IsAllowedPath(fileResult.FilePath, FileTypes.MODEL_IMAGE))
65+
PreparedModelImage prepared;
66+
FileAttachmentImage image;
67+
try
6168
{
62-
this.FinishFileResult(fileResult, BatchProcessingFileStatus.FAILED, T("This image format cannot be given to the model. Please convert the image to JPG, PNG, WebP, or GIF."));
63-
return null;
69+
image = new FileAttachmentImage(fileResult.FileName, fileResult.FilePath, new FileInfo(fileResult.FilePath).Length);
70+
prepared = await this.ModelImageService.PrepareAsync(image, this.ProviderSettings, token);
6471
}
65-
66-
long fileSize;
67-
try
72+
catch (OperationCanceledException)
6873
{
69-
fileSize = new FileInfo(fileResult.FilePath).Length;
74+
this.FinishFileResult(fileResult, BatchProcessingFileStatus.CANCELED, T("The batch run was canceled."));
75+
return null;
7076
}
7177
catch (Exception e)
7278
{
7379
this.FinishFileResult(fileResult, BatchProcessingFileStatus.FAILED, string.Format(T("Was not able to read the file: {0}"), e.Message), e);
7480
return null;
7581
}
7682

77-
var maxImageSizeBytes = IImageSourceExtensions.MaxImageSizeBytesFor(this.ProviderSettings.UsedLLMProvider);
78-
if (fileSize > maxImageSizeBytes)
79-
{
80-
this.FinishFileResult(fileResult, BatchProcessingFileStatus.FAILED, string.Format(T("The image is larger than {0} MB, which is the most the selected provider accepts for one image."), (maxImageSizeBytes / 1_000_000d).ToString("0.#", Tools.PluginSystem.I18N.I.Culture)));
81-
return null;
82-
}
83+
if (prepared.IsPrepared)
84+
return image;
8385

84-
return new FileAttachmentImage(fileResult.FileName, fileResult.FilePath, fileSize);
86+
this.FinishFileResult(fileResult, BatchProcessingFileStatus.FAILED, prepared.ErrorCode.ToUserMessage(fileResult.FileName));
87+
return null;
8588
}
8689

8790
private async Task<string?> LoadDocumentContentAsync(BatchProcessingFileResult fileResult, CancellationToken token)

‎app/MindWork AI Studio/Assistants/BatchProcessing/AssistantBatchProcessing.razor.Validation.cs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ private static bool IsTranscriptArtifact(string filePath)
180180

181181
//
182182
// A model that cannot read images would get the text of the request alone, without the
183-
// image, and answer about nothing. Such images fail one by one, see LoadImage, while the
183+
// image, and answer about nothing. Such images fail one by one, see LoadImageAsync, while the
184184
// documents next to them are processed. A run of nothing but images does not start at all:
185185
//
186186
if (files.All(IsImage) && !this.ProviderSettings.SupportsImageInput())

‎app/MindWork AI Studio/Assistants/BatchProcessing/AssistantBatchProcessing.razor.cs‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ public partial class AssistantBatchProcessing : AssistantBaseCore<SettingsDialog
1515
[Inject]
1616
private PandocAvailabilityService PandocAvailability { get; init; } = null!;
1717

18+
[Inject]
19+
private ModelImageService ModelImageService { get; init; } = null!;
20+
1821
private const string DEFAULT_OUTPUT_DIRECTORY_NAME = "ai-results";
1922
private const string DEFAULT_RESULTS_FILENAME = "batch-results.csv";
2023
private const string CSV_EXTENSION = ".csv";

‎app/MindWork AI Studio/Assistants/I18N/allTexts.lua‎

Lines changed: 31 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -532,8 +532,8 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING
532532
-- Was not able to write the result file: {0}
533533
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T2991295581"] = "Was not able to write the result file: {0}"
534534

535-
-- Images in JPG, PNG, WebP, or GIF format are given directly to the model, so you need a model that understands images. If the selected model cannot read images, remove the image patterns, such as *.jpg or *.png, before you start.
536-
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T3000909441"] = "Images in JPG, PNG, WebP, or GIF format are given directly to the model, so you need a model that understands images. If the selected model cannot read images, remove the image patterns, such as *.jpg or *.png, before you start."
535+
-- Images in JPG, PNG, WebP, GIF, BMP, or TIFF format are given to the model as images, so you need a model that understands images. If the selected model cannot read images, remove the image patterns, such as *.jpg or *.png, before you start.
536+
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T3011193639"] = "Images in JPG, PNG, WebP, GIF, BMP, or TIFF format are given to the model as images, so you need a model that understands images. If the selected model cannot read images, remove the image patterns, such as *.jpg or *.png, before you start."
537537

538538
-- The batch run finished, but one file could not be processed. See the progress table and log for details.
539539
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."
@@ -661,9 +661,6 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING
661661
-- Start batch processing
662662
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T50133258"] = "Start batch processing"
663663

664-
-- This image format cannot be given to the model. Please convert the image to JPG, PNG, WebP, or GIF.
665-
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T532496054"] = "This image format cannot be given to the model. Please convert the image to JPG, PNG, WebP, or GIF."
666-
667664
-- Was not able to read the results table of the previous run. Its completed documents cannot be restored and will be processed again.
668665
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T544244392"] = "Was not able to read the results table of the previous run. Its completed documents cannot be restored and will be processed again."
669666

@@ -688,9 +685,6 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING
688685
-- Include hidden files and folders?
689686
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T85084423"] = "Include hidden files and folders?"
690687

691-
-- The image is larger than {0} MB, which is the most the selected provider accepts for one image.
692-
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T87450623"] = "The image is larger than {0} MB, which is the most the selected provider accepts for one image."
693-
694688
-- Restore default
695689
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T913168756"] = "Restore default"
696690

@@ -8590,9 +8584,6 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGASSISTANTBIAS::T7117
85908584
-- Default minimum pause between files
85918585
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T1008440099"] = "Default minimum pause between files"
85928586

8593-
-- 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, and GIF format.
8594-
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T1038539663"] = "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, and GIF format."
8595-
85968587
-- Instructions
85978588
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T1221801316"] = "Instructions"
85988589

@@ -8635,6 +8626,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T22
86358626
-- Hidden files and folders are included
86368627
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T2288334048"] = "Hidden files and folders are included"
86378628

8629+
-- 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.
8630+
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T232588799"] = "Separate multiple file patterns with a semicolon, e.g., *.pdf;*.docx. The standard patterns include all supported audio and video formats as well as images in JPG, PNG, WebP, GIF, BMP, and TIFF format."
8631+
86388632
-- The lower end of the random pause interval. AI Studio never allows less than 6 seconds.
86398633
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T237774509"] = "The lower end of the random pause interval. AI Studio never allows less than 6 seconds."
86408634

@@ -13441,6 +13435,30 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPES::T791951937"] = "Image for AI
1344113435
-- Plugin archive
1344213436
UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPES::T927001356"] = "Plugin archive"
1344313437

13438+
-- AI Studio was not able to prepare the image '{0}' for the AI, so it was not sent.
13439+
UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::MODELIMAGEERRORCODEEXTENSIONS::T1345506748"] = "AI Studio was not able to prepare the image '{0}' for the AI, so it was not sent."
13440+
13441+
-- The image '{0}' could not be made small enough for the selected provider, so it was not sent.
13442+
UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::MODELIMAGEERRORCODEEXTENSIONS::T1463083567"] = "The image '{0}' could not be made small enough for the selected provider, so it was not sent."
13443+
13444+
-- The image '{0}' could not be opened and was not sent. When the image is stored on a network drive, the drive might be unavailable.
13445+
UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::MODELIMAGEERRORCODEEXTENSIONS::T2022724454"] = "The image '{0}' could not be opened and was not sent. When the image is stored on a network drive, the drive might be unavailable."
13446+
13447+
-- The image '{0}' does not exist anymore and was not sent.
13448+
UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::MODELIMAGEERRORCODEEXTENSIONS::T2330229240"] = "The image '{0}' does not exist anymore and was not sent."
13449+
13450+
-- The image '{0}' is not what its file extension claims, so it was not sent. Please correct its file extension.
13451+
UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::MODELIMAGEERRORCODEEXTENSIONS::T3457568536"] = "The image '{0}' is not what its file extension claims, so it was not sent. Please correct its file extension."
13452+
13453+
-- The image '{0}' could not be read and was not sent. It might be damaged or transferred incompletely.
13454+
UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::MODELIMAGEERRORCODEEXTENSIONS::T4029166607"] = "The image '{0}' could not be read and was not sent. It might be damaged or transferred incompletely."
13455+
13456+
-- The image '{0}' has too many pixels for AI Studio to prepare it, so it was not sent. Please scale the image down first.
13457+
UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::MODELIMAGEERRORCODEEXTENSIONS::T567941882"] = "The image '{0}' has too many pixels for AI Studio to prepare it, so it was not sent. Please scale the image down first."
13458+
13459+
-- The image '{0}' uses a format which AI Studio cannot give to an AI model, so it was not sent. Please convert the image to JPG or PNG.
13460+
UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::MODELIMAGEERRORCODEEXTENSIONS::T568830141"] = "The image '{0}' uses a format which AI Studio cannot give to an AI model, so it was not sent. Please convert the image to JPG or PNG."
13461+
1344413462
-- Attempt to override instructions
1344513463
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SECURITY::PROMPTINJECTIONFINDINGCATEGORYEXTENSIONS::T161976090"] = "Attempt to override instructions"
1344613464

@@ -14671,11 +14689,8 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::VALIDATION::DATASOURCEVALIDATION::T90753429"]
1467114689
-- Please enter the secret necessary for authentication.
1467214690
UI_TEXT_CONTENT["AISTUDIO::TOOLS::VALIDATION::DATASOURCEVALIDATION::T968385876"] = "Please enter the secret necessary for authentication."
1467314691

14674-
-- Unsupported image format
14675-
UI_TEXT_CONTENT["AISTUDIO::TOOLS::VALIDATION::FILEEXTENSIONVALIDATION::T1398282880"] = "Unsupported image format"
14676-
14677-
-- File has no extension
14678-
UI_TEXT_CONTENT["AISTUDIO::TOOLS::VALIDATION::FILEEXTENSIONVALIDATION::T1555980031"] = "File has no extension"
14692+
-- AI Studio cannot give {0} images to an AI model. Please convert the image to JPG or PNG.
14693+
UI_TEXT_CONTENT["AISTUDIO::TOOLS::VALIDATION::FILEEXTENSIONVALIDATION::T1578559061"] = "AI Studio cannot give {0} images to an AI model. Please convert the image to JPG or PNG."
1467914694

1468014695
-- Audio files are not supported yet
1468114696
UI_TEXT_CONTENT["AISTUDIO::TOOLS::VALIDATION::FILEEXTENSIONVALIDATION::T2919730669"] = "Audio files are not supported yet"

‎app/MindWork AI Studio/Chat/ContentImage.cs‎

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
using System.Text.Json.Serialization;
22

33
using AIStudio.Provider;
4-
using AIStudio.Tools.Validation;
54

65
namespace AIStudio.Chat;
76

@@ -53,23 +52,6 @@ public Task<ChatThread> CreateFromProviderAsync(IProvider provider, Model chatMo
5352

5453
#endregion
5554

56-
/// <summary>
57-
/// Creates a ContentImage from a local file path.
58-
/// </summary>
59-
/// <param name="filePath">The path to the image file.</param>
60-
/// <returns>A new ContentImage instance if the file is valid, null otherwise.</returns>
61-
public static async Task<ContentImage?> CreateFromFileAsync(string filePath)
62-
{
63-
if (!await FileExtensionValidation.IsImageExtensionValidWithNotifyAsync(filePath))
64-
return null;
65-
66-
return new ContentImage
67-
{
68-
SourceType = ContentImageSource.LOCAL_PATH,
69-
Source = filePath,
70-
};
71-
}
72-
7355
/// <summary>
7456
/// The type of the image source.
7557
/// </summary>

0 commit comments

Comments
 (0)