From 497e6b06fbf85753bcae5c2b8856aaba5b600c23 Mon Sep 17 00:00:00 2001 From: Ethanfel Date: Sun, 1 Mar 2026 20:29:24 +0100 Subject: [PATCH] Fix 7 bugs: async proxies, mode default, JS key serialization, validation - Use asyncio.to_thread for proxy endpoints to avoid blocking event loop - Add mode to DEFAULTS so it doesn't silently insert 0 - Use JSON serialization for keys in project_dynamic.js (with comma fallback) - Validate path exists in change_path, friendly error on duplicate rename - Remove unused exp param from rename closure - Use deepcopy for DEFAULTS consistently Co-Authored-By: Claude Opus 4.6 --- project_loader.py | 9 +++++---- tab_batch_ng.py | 4 ++-- tab_projects_ng.py | 7 +++++++ utils.py | 1 + web/project_dynamic.js | 24 +++++++++++++++--------- 5 files changed, 30 insertions(+), 15 deletions(-) diff --git a/project_loader.py b/project_loader.py index a3be21f..581da09 100644 --- a/project_loader.py +++ b/project_loader.py @@ -1,3 +1,4 @@ +import asyncio import json import logging import urllib.parse @@ -88,7 +89,7 @@ if PromptServer is not None: async def list_projects_proxy(request): manager_url = request.query.get("url", "http://localhost:8080") url = f"{manager_url.rstrip('/')}/api/projects" - data = _fetch_json(url) + data = await asyncio.to_thread(_fetch_json, url) return web.json_response(data) @PromptServer.instance.routes.get("/json_manager/list_project_files") @@ -96,7 +97,7 @@ if PromptServer is not None: manager_url = request.query.get("url", "http://localhost:8080") project = urllib.parse.quote(request.query.get("project", ""), safe='') url = f"{manager_url.rstrip('/')}/api/projects/{project}/files" - data = _fetch_json(url) + data = await asyncio.to_thread(_fetch_json, url) return web.json_response(data) @PromptServer.instance.routes.get("/json_manager/list_project_sequences") @@ -105,7 +106,7 @@ if PromptServer is not None: project = urllib.parse.quote(request.query.get("project", ""), safe='') file_name = urllib.parse.quote(request.query.get("file", ""), safe='') url = f"{manager_url.rstrip('/')}/api/projects/{project}/files/{file_name}/sequences" - data = _fetch_json(url) + data = await asyncio.to_thread(_fetch_json, url) return web.json_response(data) @PromptServer.instance.routes.get("/json_manager/get_project_keys") @@ -117,7 +118,7 @@ if PromptServer is not None: seq = int(request.query.get("seq", "1")) except (ValueError, TypeError): seq = 1 - data = _fetch_keys(manager_url, project, file_name, seq) + data = await asyncio.to_thread(_fetch_keys, manager_url, project, file_name, seq) if data.get("error") in ("http_error", "network_error", "parse_error"): status = data.get("status", 502) return web.json_response(data, status=status) diff --git a/tab_batch_ng.py b/tab_batch_ng.py index 71e992f..a88c3cf 100644 --- a/tab_batch_ng.py +++ b/tab_batch_ng.py @@ -267,7 +267,7 @@ def render_batch_processor(state: AppState): with ui.row().classes('q-mt-sm'): def add_empty(): - _add_sequence(DEFAULTS.copy()) + _add_sequence(copy.deepcopy(DEFAULTS)) def add_from_source(): item = copy.deepcopy(DEFAULTS) @@ -383,7 +383,7 @@ def _render_sequence_card(i, seq, batch_list, data, file_path, state, # --- Action row --- with ui.row().classes('w-full q-gutter-sm action-row'): # Rename - async def rename(idx=i, s=seq, exp=expansion): + async def rename(s=seq): result = await ui.run_javascript( f'prompt("Rename sequence:", {json.dumps(s.get("name", ""))})', timeout=30.0, diff --git a/tab_projects_ng.py b/tab_projects_ng.py index 8652ed0..5212a84 100644 --- a/tab_projects_ng.py +++ b/tab_projects_ng.py @@ -1,5 +1,6 @@ import json import logging +import sqlite3 from pathlib import Path from nicegui import ui @@ -127,6 +128,9 @@ def render_projects_tab(state: AppState): state.config) ui.notify(f'Renamed to "{new_name}"', type='positive') render_project_list.refresh() + except sqlite3.IntegrityError: + ui.notify(f'A project named "{new_name}" already exists', + type='warning') except Exception as e: ui.notify(f'Error: {e}', type='negative') @@ -140,6 +144,9 @@ def render_projects_tab(state: AppState): ) if new_path and new_path.strip() and new_path.strip() != path: new_path = new_path.strip() + if not Path(new_path).is_dir(): + ui.notify(f'Warning: "{new_path}" does not exist', + type='warning') state.db.update_project_path(name, new_path) ui.notify(f'Path updated to "{new_path}"', type='positive') render_project_list.refresh() diff --git a/utils.py b/utils.py index af80c60..634f07f 100644 --- a/utils.py +++ b/utils.py @@ -30,6 +30,7 @@ DEFAULTS = { "cfg": 1.5, # --- Settings --- + "mode": 0, "camera": "static", "flf": 0.0, diff --git a/web/project_dynamic.js b/web/project_dynamic.js index fa43360..0100382 100644 --- a/web/project_dynamic.js +++ b/web/project_dynamic.js @@ -117,11 +117,11 @@ app.registerExtension({ return; } - // Store keys and types in hidden widgets for persistence (comma-separated) + // Store keys and types in hidden widgets for persistence (JSON) const okWidget = this.widgets?.find(w => w.name === "output_keys"); - if (okWidget) okWidget.value = keys.join(","); + if (okWidget) okWidget.value = JSON.stringify(keys); const otWidget = this.widgets?.find(w => w.name === "output_types"); - if (otWidget) otWidget.value = types.join(","); + if (otWidget) otWidget.value = JSON.stringify(types); // Slot 0 is always total_sequences (INT) — ensure it exists if (this.outputs.length === 0 || this.outputs[0].name !== "total_sequences") { @@ -198,12 +198,18 @@ app.registerExtension({ const okWidget = this.widgets?.find(w => w.name === "output_keys"); const otWidget = this.widgets?.find(w => w.name === "output_types"); - const keys = okWidget?.value - ? okWidget.value.split(",").filter(k => k.trim()) - : []; - const types = otWidget?.value - ? otWidget.value.split(",") - : []; + let keys = []; + let types = []; + if (okWidget?.value) { + try { keys = JSON.parse(okWidget.value); } catch (_) { + keys = okWidget.value.split(",").filter(k => k.trim()); + } + } + if (otWidget?.value) { + try { types = JSON.parse(otWidget.value); } catch (_) { + types = otWidget.value.split(","); + } + } // Ensure slot 0 is total_sequences (INT) if (this.outputs.length === 0 || this.outputs[0].name !== "total_sequences") {