Skip to content

Commit 7f6eae4

Browse files
committed
indirection for model preview icons
1 parent e123211 commit 7f6eae4

File tree

6 files changed

+99
-8
lines changed

6 files changed

+99
-8
lines changed

Diff for: docs/API.md

+1
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ The following `GET` routes are also available:
7272
- `/fonts/*.woff2`, `/imgs/*.jpg`, `/imgs/*.png`, `/favicon.ico` - Various asset files for the interface.
7373
- `/View/*.*` - Returns saved outputs. By default usually in a format like `/View/(user)/raw/(year-month-day)/(file).(ext)`, but is user-customizable.
7474
- `/Output/*.*` - legacy output call format. Do not use.
75+
- `/ViewSpecial/*.*` - Returns special data views. Notable example, `/ViewSpecial/Stable-Diffusion/OfficialStableDiffusion/sd_xl_base_1.0.safetensors` gets the preview icon for that model.
7576
- `/Audio/*.*` - Returns saved user audio files (in `Data/Audio`).
7677
- `/ExtensionFile/(extension)/*.*` - gets a web asset file from an extension.
7778
- `/ComfyBackendDirect/*.*` - direct pass-through to a comfy instance, if the [ComfyUI Backend Extension](/src/BuiltinExtensions/ComfyUIBackend/README.md) is in use.

Diff for: src/Core/WebServer.cs

+67
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using Microsoft.Extensions.Hosting;
88
using Microsoft.Extensions.Logging;
99
using Microsoft.Extensions.Primitives;
10+
using Newtonsoft.Json.Linq;
1011
using SwarmUI.Accounts;
1112
using SwarmUI.Text2Image;
1213
using SwarmUI.Utils;
@@ -239,6 +240,7 @@ public void Prep()
239240
WebApp.Map("/API/{*Call}", API.HandleAsyncRequest);
240241
WebApp.MapGet("/Output/{*Path}", ViewOutput);
241242
WebApp.MapGet("/View/{*Path}", ViewOutput);
243+
WebApp.MapGet("/ViewSpecial/{*Path}", ViewSpecial);
242244
WebApp.MapGet("/ExtensionFile/{*f}", ViewExtensionScript);
243245
WebApp.MapGet("/Audio/{*f}", ViewAudio);
244246
timer.Check("[Web] core maps");
@@ -552,4 +554,69 @@ public async Task ViewOutput(HttpContext context)
552554
await context.Response.Body.WriteAsync(data, Program.GlobalProgramCancel);
553555
await context.Response.CompleteAsync();
554556
}
557+
558+
/// <summary>Web route for viewing special images (eg model icons).</summary>
559+
public async Task ViewSpecial(HttpContext context)
560+
{
561+
string path = context.Request.Path.ToString();
562+
path = Uri.UnescapeDataString(path).Replace('\\', '/').Trim('/');
563+
while (path.Contains("//"))
564+
{
565+
path = path.Replace("//", "/");
566+
}
567+
(string subtype, string name) = path.After("ViewSpecial/").BeforeAndAfter('/');
568+
User user = GetUserFor(context);
569+
if (user is null)
570+
{
571+
await context.YieldJsonOutput(null, 400, Utilities.ErrorObj("invalid or unauthorized", "invalid_user"));
572+
return;
573+
}
574+
async Task yieldResult(string imageData)
575+
{
576+
Image img = Image.FromDataString(imageData);
577+
context.Response.ContentType = img.MimeType();
578+
context.Response.StatusCode = 200;
579+
context.Response.ContentLength = img.ImageData.Length;
580+
context.Response.Headers.CacheControl = $"private, max-age=2";
581+
await context.Response.Body.WriteAsync(img.ImageData, Program.GlobalProgramCancel);
582+
await context.Response.CompleteAsync();
583+
}
584+
if (!user.IsAllowedModel(name))
585+
{
586+
Logs.Verbose($"Not showing user '{user.UserID}' sub-type '{subtype}' model image '{name}': user restriction");
587+
}
588+
else
589+
{
590+
if (subtype == "Wildcards")
591+
{
592+
WildcardsHelper.Wildcard card = WildcardsHelper.GetWildcard(name);
593+
if (card is not null && card.Image.StartsWithFast("data:"))
594+
{
595+
await yieldResult(card.Image);
596+
return;
597+
}
598+
}
599+
if (Program.T2IModelSets.TryGetValue(subtype, out T2IModelHandler handler))
600+
{
601+
if (handler.Models.TryGetValue(name + ".safetensors", out T2IModel model) || handler.Models.TryGetValue(name, out model))
602+
{
603+
if (model.Metadata?.PreviewImage?.StartsWithFast("data:") ?? false)
604+
{
605+
await yieldResult(model.Metadata.PreviewImage);
606+
return;
607+
}
608+
}
609+
else if (ModelsAPI.InternalExtraModels(subtype).TryGetValue(name + ".safetensors", out JObject remoteModel) || ModelsAPI.InternalExtraModels(subtype).TryGetValue(name, out remoteModel))
610+
{
611+
if (remoteModel.TryGetValue("preview_image", out JToken previewImg) && previewImg.ToString().StartsWithFast("data:"))
612+
{
613+
await yieldResult(previewImg.ToString());
614+
return;
615+
}
616+
}
617+
}
618+
Logs.Verbose($"Not showing user '{user.UserID}' sub-type '{subtype}' model image '{name}': not found");
619+
}
620+
await context.YieldJsonOutput(null, 404, Utilities.ErrorObj("404, file not found.", "file_not_found"));
621+
}
555622
}

Diff for: src/Text2Image/T2IModel.cs

+8-2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using Newtonsoft.Json.Linq;
44
using SwarmUI.Core;
55
using SwarmUI.Utils;
6+
using SwarmUI.WebAPI;
67
using System.IO;
78
using System.Security.Cryptography;
89

@@ -265,15 +266,20 @@ void HandleResave(string path)
265266
}
266267

267268
/// <summary>Gets a networkable copy of this model's data.</summary>
268-
public JObject ToNetObject(string prefix = "")
269+
public JObject ToNetObject(string prefix = "", bool dataImgs = true)
269270
{
271+
string previewImg = PreviewImage;
272+
if (!dataImgs && previewImg is not null && previewImg.StartsWithFast("data:"))
273+
{
274+
previewImg = $"/ViewSpecial/{Handler.ModelType}/{name}?editid={ModelsAPI.ModelEditID}";
275+
}
270276
return new JObject()
271277
{
272278
[$"{prefix}name"] = Name,
273279
[$"{prefix}title"] = Metadata?.Title,
274280
[$"{prefix}author"] = Metadata?.Author,
275281
[$"{prefix}description"] = Description,
276-
[$"{prefix}preview_image"] = PreviewImage,
282+
[$"{prefix}preview_image"] = previewImg,
277283
[$"{prefix}loaded"] = AnyBackendsHaveLoaded,
278284
[$"{prefix}architecture"] = ModelClass?.ID,
279285
[$"{prefix}class"] = ModelClass?.Name,

Diff for: src/Utils/WildcardsHelper.cs

+8-2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using FreneticUtilities.FreneticToolkit;
33
using Newtonsoft.Json.Linq;
44
using SwarmUI.Core;
5+
using SwarmUI.WebAPI;
56
using System.IO;
67

78
namespace SwarmUI.Utils;
@@ -23,14 +24,19 @@ public class Wildcard
2324

2425
public long TimeCreated;
2526

26-
public JObject GetNetObject()
27+
public JObject GetNetObject(bool dataImgs = true)
2728
{
29+
string previewImg = Image ?? "imgs/model_placeholder.jpg";
30+
if (!dataImgs && previewImg is not null && previewImg.StartsWithFast("data:"))
31+
{
32+
previewImg = $"/ViewSpecial/Wildcards/{Name}?editid={ModelsAPI.ModelEditID}";
33+
}
2834
return new()
2935
{
3036
["name"] = Name,
3137
["options"] = JArray.FromObject(Options),
3238
["raw"] = Raw,
33-
["image"] = Image ?? "imgs/model_placeholder.jpg"
39+
["image"] = previewImg
3440
};
3541
}
3642
}

Diff for: src/WebAPI/ModelsAPI.cs

+14-3
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ namespace SwarmUI.WebAPI;
1515
[API.APIClass("API routes related to handling models (including loras, wildcards, etc).")]
1616
public static class ModelsAPI
1717
{
18+
public static long ModelEditID = 0;
19+
1820
public static void Register()
1921
{
2022
API.RegisterAPICall(ListModels, false, Permissions.FundamentalModelAccess);
@@ -198,7 +200,7 @@ bool tryMatch(string name)
198200
if (tryMatch(file))
199201
{
200202
WildcardsHelper.Wildcard card = WildcardsHelper.GetWildcard(file);
201-
files.Add(new(card.Name, card.Name.AfterLast('/'), card.TimeCreated, card.TimeModified, card.GetNetObject()));
203+
files.Add(new(card.Name, card.Name.AfterLast('/'), card.TimeCreated, card.TimeModified, card.GetNetObject(false)));
202204
if (files.Count > sanityCap)
203205
{
204206
break;
@@ -212,7 +214,7 @@ bool tryMatch(string name)
212214
{
213215
if (tryMatch(possible.Name))
214216
{
215-
files.Add(new(possible.Name, possible.Title, possible.Metadata?.TimeCreated ?? long.MaxValue, possible.Metadata?.TimeModified ?? long.MaxValue, possible.ToNetObject()));
217+
files.Add(new(possible.Name, possible.Title, possible.Metadata?.TimeCreated ?? long.MaxValue, possible.Metadata?.TimeModified ?? long.MaxValue, possible.ToNetObject(dataImgs: false)));
216218
if (files.Count > sanityCap)
217219
{
218220
break;
@@ -226,7 +228,13 @@ bool tryMatch(string name)
226228
{
227229
if (tryMatch(name))
228230
{
229-
files.Add(new(name, name.AfterLast('/'), long.MaxValue, long.MaxValue, possible));
231+
JObject toAdd = possible;
232+
if (toAdd.TryGetValue("preview_image", out JToken previewImg) && previewImg.ToString().StartsWith("data:"))
233+
{
234+
toAdd = toAdd.DeepClone() as JObject;
235+
toAdd["preview_image"] = $"/ViewSpecial/{subtype}/{name}";
236+
}
237+
files.Add(new(name, name.AfterLast('/'), long.MaxValue, long.MaxValue, toAdd));
230238
if (files.Count > sanityCap)
231239
{
232240
break;
@@ -403,6 +411,7 @@ public static async Task<JObject> EditWildcard(Session session,
403411
File.WriteAllBytes($"{WildcardsHelper.Folder}/{card}.jpg", img.ImageData);
404412
WildcardsHelper.WildcardFiles[card] = new WildcardsHelper.Wildcard() { Name = card };
405413
}
414+
Interlocked.Increment(ref ModelEditID);
406415
return new JObject() { ["success"] = true };
407416
}
408417

@@ -473,6 +482,7 @@ public static async Task<JObject> EditModelMetadata(Session session,
473482
}
474483
handler.ResetMetadataFrom(actualModel);
475484
_ = Utilities.RunCheckedTask(() => actualModel.ResaveModel());
485+
Interlocked.Increment(ref ModelEditID);
476486
return new JObject() { ["success"] = true };
477487
}
478488

@@ -804,6 +814,7 @@ void doMoveNow(string oldPath)
804814
doMoveNow(altPath);
805815
}
806816
}
817+
Interlocked.Increment(ref ModelEditID);
807818
return new JObject() { ["success"] = true };
808819
}
809820
}

Diff for: src/wwwroot/js/genpage/models.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ function save_edit_wildcard() {
102102
}
103103
genericRequest('EditWildcard', data, resData => {
104104
wildcardsBrowser.browser.refresh();
105-
if (card.name != data.card) {
105+
if (card.name && card.name != data.card) {
106106
genericRequest('DeleteWildcard', { card: card.name }, data => {});
107107
}
108108
});

0 commit comments

Comments
 (0)