Skip to content

Commit f5be836

Browse files
committed
add new dropdown
1 parent 9f30c36 commit f5be836

File tree

8 files changed

+1403
-167
lines changed

8 files changed

+1403
-167
lines changed

flow/api_handlers.py

Lines changed: 205 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
1-
import json
21
import shutil
3-
import subprocess
4-
import sys
52
import os
6-
import stat
73
import re
84
import base64
5+
import json
6+
import hashlib
7+
import urllib.parse
98
from pathlib import Path
109
from aiohttp import web
11-
from typing import Dict, Any
10+
from typing import Any
1211
from io import BytesIO
1312
from PIL import Image
1413

@@ -18,6 +17,207 @@
1817
SAFE_FOLDER_NAME_REGEX, ALLOWED_EXTENSIONS, CUSTOM_THEMES_DIR, FLOWS_CONFIG_FILE
1918
)
2019

20+
DATA_DIR = Path(__file__).parent / "data"
21+
PREVIEWS_REGISTRY_DIR = DATA_DIR / "model_previews_registry"
22+
PREVIEWS_IMAGES_DIR = DATA_DIR / "model_previews"
23+
24+
def ensure_data_folders():
25+
try:
26+
DATA_DIR.mkdir(parents=True, exist_ok=True)
27+
PREVIEWS_REGISTRY_DIR.mkdir(parents=True, exist_ok=True)
28+
PREVIEWS_IMAGES_DIR.mkdir(parents=True, exist_ok=True)
29+
except Exception as e:
30+
logger.error(f"{FLOWMSG}: Could not create data dirs: {e}")
31+
32+
def pathToKey(model_path: str) -> str:
33+
return model_path.replace('\\', '/')
34+
35+
def get_filename_only(model_path: str) -> str:
36+
fwd = pathToKey(model_path)
37+
return os.path.basename(fwd)
38+
39+
def get_preview_id(model_path: str) -> str:
40+
filename = get_filename_only(model_path)
41+
h = hashlib.sha1(filename.encode('utf-8')).hexdigest()
42+
return h[:16]
43+
44+
def get_preview_paths(preview_id: str):
45+
sub1 = preview_id[0]
46+
sub2 = preview_id[:2]
47+
registry_json = PREVIEWS_REGISTRY_DIR / sub1 / sub2 / f"{preview_id}.json"
48+
image_folder = PREVIEWS_IMAGES_DIR / sub1 / sub2 / preview_id
49+
return registry_json, image_folder
50+
51+
async def set_model_preview_handler(request: web.Request) -> web.Response:
52+
try:
53+
ensure_data_folders()
54+
data = await request.json()
55+
rawPath = data.get("modelPath")
56+
base64_data = data.get("base64Data")
57+
if not rawPath or not base64_data:
58+
return web.Response(status=400, text="Missing 'modelPath' or 'base64Data'")
59+
60+
pid = get_preview_id(rawPath)
61+
registry_json, image_folder = get_preview_paths(pid)
62+
63+
match = re.match(r"data:(image/\w+);base64,(.+)", base64_data)
64+
if not match:
65+
return web.Response(status=400, text="Invalid data URL format")
66+
67+
mime_type = match.group(1)
68+
encoded = match.group(2)
69+
try:
70+
raw_image = base64.b64decode(encoded)
71+
except:
72+
return web.Response(status=400, text="Error decoding base64 image")
73+
74+
registry_json.parent.mkdir(parents=True, exist_ok=True)
75+
image_folder.mkdir(parents=True, exist_ok=True)
76+
77+
full_path = image_folder / "full.jpg"
78+
with full_path.open("wb") as f:
79+
f.write(raw_image)
80+
81+
thumb_path = image_folder / "thumbnail.jpg"
82+
img = Image.open(BytesIO(raw_image))
83+
w_percent = 128.0 / float(img.size[0])
84+
h_new = int(float(img.size[1]) * w_percent)
85+
img = img.convert("RGB").resize((128, h_new), Image.Resampling.LANCZOS)
86+
img.save(thumb_path, format="JPEG")
87+
88+
reg_data = {
89+
"modelPath": rawPath,
90+
"previewId": pid,
91+
"timestamp": int(os.path.getmtime(full_path)),
92+
"mime_type": mime_type
93+
}
94+
with registry_json.open("w", encoding="utf-8") as jf:
95+
json.dump(reg_data, jf, indent=2)
96+
97+
return web.json_response({"status": "success", "previewId": pid})
98+
99+
except Exception as e:
100+
logger.error(f"{FLOWMSG}: Error in set_model_preview_handler: {e}")
101+
return web.Response(status=500, text=str(e))
102+
103+
async def clear_model_preview_handler(request: web.Request) -> web.Response:
104+
try:
105+
ensure_data_folders()
106+
rawPath = request.query.get("modelPath", None)
107+
if not rawPath:
108+
return web.Response(status=400, text="Missing 'modelPath'")
109+
110+
pid = get_preview_id(rawPath)
111+
registry_json, image_folder = get_preview_paths(pid)
112+
113+
if registry_json.exists():
114+
registry_json.unlink()
115+
if image_folder.exists() and image_folder.is_dir():
116+
shutil.rmtree(image_folder)
117+
118+
return web.json_response({"status": "success", "previewId": pid})
119+
except Exception as e:
120+
logger.error(f"{FLOWMSG}: Error in clear_model_preview_handler: {e}")
121+
return web.Response(status=500, text=str(e))
122+
123+
async def list_model_previews_handler(request: web.Request) -> web.Response:
124+
try:
125+
ensure_data_folders()
126+
result_map = {}
127+
128+
if request.method == 'POST':
129+
data = await request.json()
130+
raw_paths = data.get('paths', [])
131+
if not isinstance(raw_paths, list):
132+
return web.Response(status=400, text="Invalid JSON: 'paths' must be an array")
133+
134+
for rp in raw_paths:
135+
rp = urllib.parse.unquote(rp).strip()
136+
if not rp:
137+
continue
138+
pid = get_preview_id(rp)
139+
_, image_folder = get_preview_paths(pid)
140+
thumb = image_folder / "thumbnail.jpg"
141+
if thumb.exists():
142+
with thumb.open("rb") as tf:
143+
b = tf.read()
144+
b64 = base64.b64encode(b).decode("utf-8")
145+
data_url = f"data:image/jpeg;base64,{b64}"
146+
result_map[rp] = data_url
147+
148+
return web.json_response(result_map)
149+
150+
paths_param = request.rel_url.query.get('paths', None)
151+
if paths_param:
152+
raw_split = paths_param.split(',')
153+
for rp in raw_split:
154+
rp = rp.strip()
155+
if not rp:
156+
continue
157+
rp = urllib.parse.unquote(rp)
158+
pid = get_preview_id(rp)
159+
_, image_folder = get_preview_paths(pid)
160+
thumb = image_folder / "thumbnail.jpg"
161+
if thumb.exists():
162+
with thumb.open("rb") as tf:
163+
b = tf.read()
164+
b64 = base64.b64encode(b).decode("utf-8")
165+
result_map[rp] = f"data:image/jpeg;base64,{b64}"
166+
return web.json_response(result_map)
167+
168+
for root, dirs, files in os.walk(PREVIEWS_REGISTRY_DIR):
169+
for filename in files:
170+
if filename.endswith(".json"):
171+
regp = Path(root) / filename
172+
try:
173+
with regp.open("r", encoding="utf-8") as f:
174+
reg_data = json.load(f)
175+
mp = reg_data.get("modelPath")
176+
pid = reg_data.get("previewId")
177+
if not mp or not pid:
178+
continue
179+
180+
_, folder = get_preview_paths(pid)
181+
thumb = folder / "thumbnail.jpg"
182+
if thumb.exists():
183+
with thumb.open("rb") as tf:
184+
b = tf.read()
185+
b64 = base64.b64encode(b).decode("utf-8")
186+
result_map[mp] = f"data:image/jpeg;base64,{b64}"
187+
except Exception as ex:
188+
logger.error(f"{FLOWMSG}: Error reading registry {regp}: {ex}")
189+
continue
190+
191+
return web.json_response(result_map)
192+
193+
except Exception as e:
194+
logger.error(f"{FLOWMSG}: Error in list_model_previews_handler: {e}")
195+
return web.Response(status=500, text=str(e))
196+
197+
async def get_model_preview_handler(request: web.Request) -> web.Response:
198+
try:
199+
ensure_data_folders()
200+
rawPath = request.query.get("modelPath", None)
201+
if not rawPath:
202+
return web.Response(status=400, text="Missing 'modelPath'")
203+
204+
pid = get_preview_id(rawPath)
205+
_, image_folder = get_preview_paths(pid)
206+
207+
thumb = image_folder / "thumbnail.jpg"
208+
if thumb.exists():
209+
with thumb.open("rb") as tf:
210+
b = tf.read()
211+
b64 = base64.b64encode(b).decode("utf-8")
212+
data_url = f"data:image/jpeg;base64,{b64}"
213+
return web.json_response({rawPath: data_url})
214+
else:
215+
return web.Response(status=404, text="Preview not found")
216+
217+
except Exception as e:
218+
logger.error(f"{FLOWMSG}: Error in get_model_preview_handler: {e}")
219+
return web.Response(status=500, text=str(e))
220+
21221
async def apps_handler(request: web.Request) -> web.Response:
22222
return web.json_response(APP_CONFIGS)
23223

flow/constants.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from pathlib import Path
44
import re
55
APP_NAME = "Flow"
6-
APP_VERSION = "0.4.9"
6+
APP_VERSION = "0.5.0"
77
FLOWMSG = f"\033[38;5;129mFlow - {APP_VERSION}\033[0m"
88
APP_CONFIGS = []
99

@@ -58,7 +58,7 @@
5858
"afl_pulid_flux_GGUF",
5959
"afl_reactor"
6060
"5otvy-cogvideox-orbit-left-lora",
61-
"umbi9-hunyuan-text-to-video"
61+
"umbi9-hunyuan-text-to-video",
6262
]
6363

6464
NODE_CLASS_MAPPINGS = {}

flow/flow_manager.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,11 @@
1111
apps_handler, extension_node_map_handler,
1212
install_package_handler, update_package_handler, uninstall_package_handler,
1313
installed_custom_nodes_handler, preview_flow_handler,
14-
reset_preview_handler, create_flow_handler, update_flow_handler, delete_flow_handler
14+
reset_preview_handler, create_flow_handler, update_flow_handler, delete_flow_handler,
15+
set_model_preview_handler,
16+
clear_model_preview_handler,
17+
list_model_previews_handler,
18+
get_model_preview_handler
1519
)
1620

1721
class FlowManager:
@@ -68,6 +72,10 @@ def _setup_api_routes(app: web.Application) -> None:
6872
(f'/flow/api/create-flow', 'POST', create_flow_handler),
6973
(f'/flow/api/update-flow', 'POST', update_flow_handler),
7074
(f'/flow/api/delete-flow', 'DELETE', delete_flow_handler),
75+
(f'/flow/api/model-preview', 'POST', set_model_preview_handler),
76+
(f'/flow/api/model-preview', 'DELETE', clear_model_preview_handler),
77+
(f'/flow/api/model-previews', 'POST', list_model_previews_handler),
78+
(f'/flow/api/model-preview', 'GET', get_model_preview_handler),
7179
]
7280

7381
for path, method, handler in api_routes:
@@ -95,4 +103,6 @@ def _load_config(conf_file: Path) -> Dict[str, Any]:
95103
return {}
96104
except Exception as e:
97105
logger.error(f"{FLOWMSG}: Error loading config from {conf_file}: {e}")
98-
return {}
106+
return {}
107+
108+

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[project]
22
name = "comfyui-disty-flow"
33
description = "Flow is a custom node designed to provide a more user-friendly interface for ComfyUI by acting as an alternative user interface for running workflows. It is not a replacement for workflow creation.\nFlow is currently in the early stages of development, so expect bugs and ongoing feature enhancements. With your support and feedback, Flow will settle into a steady stream."
4-
version = "0.4.9"
4+
version = "0.5.0"
55
license = {file = "LICENSE"}
66

77
[project.urls]

0 commit comments

Comments
 (0)