Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 35 additions & 13 deletions BackendSchema.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using SixLabors.ImageSharp.Processing;
using SwarmUI.Media;
using SwarmUI.Utils;
using SwarmUI.Media;
using Image = SwarmUI.Utils.Image;
Expand Down Expand Up @@ -67,10 +68,15 @@ public static string CompressImageForVision(MediaContent media, string targetFor
}
try
{
ImageFile image = ImageFile.FromDataString($"data:{media.MediaType};base64,{media.Data}");
// Skip compression for videos etc..
if (image.Type.MetaType != MediaMetaType.Image)
Image image = Image.FromDataString($"data:{media.MediaType};base64,{media.Data}") as SwarmUI.Utils.Image;
Copy link

Copilot AI Dec 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The redundant cast as SwarmUI.Utils.Image is unnecessary since Image.FromDataString() already returns a SwarmUI.Utils.Image type (as defined by the using alias on line 4). This cast will always succeed or return null, making the null check on line 71 potentially misleading about the actual failure mode.

Suggested change
Image image = Image.FromDataString($"data:{media.MediaType};base64,{media.Data}") as SwarmUI.Utils.Image;
Image image = Image.FromDataString($"data:{media.MediaType};base64,{media.Data}");

Copilot uses AI. Check for mistakes.
if (image == null)
{
Logs.Error("Failed to parse image data.");
return media.Data;
}
if (image.Type.MetaType != MediaMetaType.Image && image.Type.MetaType != MediaMetaType.Animation)
{
Logs.Error("Media type is not supported for compression.");
return media.Data;
}
ISImage img = image.ToIS;
Expand All @@ -82,13 +88,18 @@ public static string CompressImageForVision(MediaContent media, string targetFor
int newHeight = (int)(img.Height * scaleFactor);
img.Mutate(i => i.Resize(newWidth, newHeight));
}
// Set compression quality based on format TODO: This needs to be tested and adjusted
int quality = targetFormat == "PNG" ? 60 : 40;
ImageFile tempImage = new Image(ImageFile.ISImgToPngBytes(img), image.Type);
ImageFile compressedImage = tempImage.ConvertTo(targetFormat, quality: quality);
// Return just the base64 data (without the data:image/webp;base64, prefix)
var imageFile = new Image(img) as SwarmUI.Media.ImageFile;
if (imageFile == null)
{
Logs.Error("Failed to convert image to ImageFile.");
return media.Data;
}
var compressedImage = imageFile.ConvertTo(targetFormat, quality: quality);
Comment on lines +92 to +98
Copy link

Copilot AI Dec 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This cast from Image to SwarmUI.Media.ImageFile appears problematic. Based on line 4, Image is aliased to SwarmUI.Utils.Image, not SwarmUI.Media.ImageFile. This cast will likely always fail and return null, causing the compression to always fall back to returning the original uncompressed data. The previous code created a new Image and called ConvertTo directly on it, which was the correct approach.

Suggested change
var imageFile = new Image(img) as SwarmUI.Media.ImageFile;
if (imageFile == null)
{
Logs.Error("Failed to convert image to ImageFile.");
return media.Data;
}
var compressedImage = imageFile.ConvertTo(targetFormat, quality: quality);
var compressedImage = image.ConvertTo(targetFormat, quality: quality);

Copilot uses AI. Check for mistakes.
return compressedImage.AsBase64;

}

catch (Exception ex)
{
Logs.Error($"Failed to compress image: {ex.Message}");
Expand Down Expand Up @@ -124,7 +135,11 @@ private static object OllamaRequestBody(MessageContent content, string model, Me
messages = messages.ToArray(),
stream = false,
keep_alive = content.KeepAlive,
options
options = new
{
temperature = 1.0,
top_p = 0.9,
}
};
}
messages.Add(new { role = "user", content = content.Text });
Expand All @@ -134,7 +149,11 @@ private static object OllamaRequestBody(MessageContent content, string model, Me
messages = messages.ToArray(),
stream = false,
keep_alive = content.KeepAlive,
options
options = new
{
temperature = 1.0,
top_p = 0.9,
}
};
}

Expand Down Expand Up @@ -190,7 +209,9 @@ private static object OpenAICompatibleRequestBody(MessageContent content, string
model,
messages = messages.ToArray(),
max_tokens = 1000,
temperature = 1.0,
temperature = 1.1,
min_p = 0.05,
top_p = 0.95,
stream = false
};
}
Expand All @@ -213,10 +234,11 @@ private static object OpenAICompatibleRequestBody(MessageContent content, string
{
model,
messages = messages.ToArray(),
temperature = 1.0,
temperature = 1.1,
max_tokens = 1000,
top_p = 0.9,
stream = false
min_p = 0.05,
stream = false,
};
}

Expand Down Expand Up @@ -270,4 +292,4 @@ private static object AnthropicRequestBody(MessageContent content, string model,
max_tokens = 1024
};
}
}
}
22 changes: 15 additions & 7 deletions WebAPI/LLMAPICalls.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,15 @@ namespace Hartsy.Extensions.MagicPromptExtension.WebAPI;

public class LLMAPICalls : MagicPromptAPI
{
protected static readonly HttpClient HttpClient = NetworkBackendUtils.MakeHttpClient();
// Set HttpClient timeout to 120 seconds
protected static readonly HttpClient HttpClient = CreateHttpClientWithTimeout();

private static HttpClient CreateHttpClientWithTimeout()
{
var client = NetworkBackendUtils.MakeHttpClient();
client.Timeout = TimeSpan.FromSeconds(120);
return client;
}

/// <summary>Fetches available models from the LLM API endpoint.</summary>
/// <returns>A JSON object containing the models or an error message.</returns>
Expand Down Expand Up @@ -418,19 +426,19 @@ public static async Task<JObject> MagicPromptPhoneHome(JObject requestData, Sess
{
// Typically thrown for validation issues (e.g., Grok vision requires direct JPG/PNG URLs)
Logs.Error($"Request build error for {backend}: {ex.Message}");
return CreateErrorResponse(ErrorHandler.FormatErrorMessage(ErrorType.UnsupportedParameterImage, ex.Message, backend));
return CreateErrorResponse(ErrorHandler.FormatErrorMessage(ErrorType.ValidationError, ex.Message, backend));
}
string jsonContent = JsonSerializer.Serialize(requestBody);
request.Content = new StringContent(jsonContent, Encoding.UTF8, "application/json");
try
{
// Send request and handle response
// Detailed diagnostics to help trace hanging requests
Logs.Debug($"[MagicPrompt] Sending request | backend={backend} | type={(messageType == MessageType.Vision ? "vision" : "chat")} | endpoint={endpoint} | model={modelId}");
// Use KeepAlive (if provided) as a soft hint for timeout.
// Increase defaults and caps, especially for Ollama on slower machines.
int cap = backend == "ollama" ? 300 : 180; // maximum allowed timeout (seconds)
int defaultTimeout = backend == "ollama" ? 120 : 60; // default timeout when KeepAlive is not set
// Increase defaults and caps, especially for Ollama on slower machines and vision requests.
// Vision/image captioning requests get significantly longer timeouts due to image processing overhead.
bool isVisionRequest = messageType == MessageType.Vision;
int cap = backend == "ollama" ? 300 : (isVisionRequest ? 240 : 180); // maximum allowed timeout (seconds)
int defaultTimeout = backend == "ollama" ? 120 : (isVisionRequest ? 120 : 60); // default timeout when KeepAlive is not set
int timeoutSec = messageContent.KeepAlive.HasValue && messageContent.KeepAlive.Value > 0
? Math.Min(messageContent.KeepAlive.Value, cap)
: defaultTimeout;
Expand Down