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 <noreply@anthropic.com>
This commit is contained in:
@@ -1,3 +1,4 @@
|
|||||||
|
import asyncio
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
@@ -88,7 +89,7 @@ if PromptServer is not None:
|
|||||||
async def list_projects_proxy(request):
|
async def list_projects_proxy(request):
|
||||||
manager_url = request.query.get("url", "http://localhost:8080")
|
manager_url = request.query.get("url", "http://localhost:8080")
|
||||||
url = f"{manager_url.rstrip('/')}/api/projects"
|
url = f"{manager_url.rstrip('/')}/api/projects"
|
||||||
data = _fetch_json(url)
|
data = await asyncio.to_thread(_fetch_json, url)
|
||||||
return web.json_response(data)
|
return web.json_response(data)
|
||||||
|
|
||||||
@PromptServer.instance.routes.get("/json_manager/list_project_files")
|
@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")
|
manager_url = request.query.get("url", "http://localhost:8080")
|
||||||
project = urllib.parse.quote(request.query.get("project", ""), safe='')
|
project = urllib.parse.quote(request.query.get("project", ""), safe='')
|
||||||
url = f"{manager_url.rstrip('/')}/api/projects/{project}/files"
|
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)
|
return web.json_response(data)
|
||||||
|
|
||||||
@PromptServer.instance.routes.get("/json_manager/list_project_sequences")
|
@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='')
|
project = urllib.parse.quote(request.query.get("project", ""), safe='')
|
||||||
file_name = urllib.parse.quote(request.query.get("file", ""), safe='')
|
file_name = urllib.parse.quote(request.query.get("file", ""), safe='')
|
||||||
url = f"{manager_url.rstrip('/')}/api/projects/{project}/files/{file_name}/sequences"
|
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)
|
return web.json_response(data)
|
||||||
|
|
||||||
@PromptServer.instance.routes.get("/json_manager/get_project_keys")
|
@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"))
|
seq = int(request.query.get("seq", "1"))
|
||||||
except (ValueError, TypeError):
|
except (ValueError, TypeError):
|
||||||
seq = 1
|
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"):
|
if data.get("error") in ("http_error", "network_error", "parse_error"):
|
||||||
status = data.get("status", 502)
|
status = data.get("status", 502)
|
||||||
return web.json_response(data, status=status)
|
return web.json_response(data, status=status)
|
||||||
|
|||||||
@@ -267,7 +267,7 @@ def render_batch_processor(state: AppState):
|
|||||||
|
|
||||||
with ui.row().classes('q-mt-sm'):
|
with ui.row().classes('q-mt-sm'):
|
||||||
def add_empty():
|
def add_empty():
|
||||||
_add_sequence(DEFAULTS.copy())
|
_add_sequence(copy.deepcopy(DEFAULTS))
|
||||||
|
|
||||||
def add_from_source():
|
def add_from_source():
|
||||||
item = copy.deepcopy(DEFAULTS)
|
item = copy.deepcopy(DEFAULTS)
|
||||||
@@ -383,7 +383,7 @@ def _render_sequence_card(i, seq, batch_list, data, file_path, state,
|
|||||||
# --- Action row ---
|
# --- Action row ---
|
||||||
with ui.row().classes('w-full q-gutter-sm action-row'):
|
with ui.row().classes('w-full q-gutter-sm action-row'):
|
||||||
# Rename
|
# Rename
|
||||||
async def rename(idx=i, s=seq, exp=expansion):
|
async def rename(s=seq):
|
||||||
result = await ui.run_javascript(
|
result = await ui.run_javascript(
|
||||||
f'prompt("Rename sequence:", {json.dumps(s.get("name", ""))})',
|
f'prompt("Rename sequence:", {json.dumps(s.get("name", ""))})',
|
||||||
timeout=30.0,
|
timeout=30.0,
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
|
import sqlite3
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from nicegui import ui
|
from nicegui import ui
|
||||||
@@ -127,6 +128,9 @@ def render_projects_tab(state: AppState):
|
|||||||
state.config)
|
state.config)
|
||||||
ui.notify(f'Renamed to "{new_name}"', type='positive')
|
ui.notify(f'Renamed to "{new_name}"', type='positive')
|
||||||
render_project_list.refresh()
|
render_project_list.refresh()
|
||||||
|
except sqlite3.IntegrityError:
|
||||||
|
ui.notify(f'A project named "{new_name}" already exists',
|
||||||
|
type='warning')
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
ui.notify(f'Error: {e}', type='negative')
|
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:
|
if new_path and new_path.strip() and new_path.strip() != path:
|
||||||
new_path = new_path.strip()
|
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)
|
state.db.update_project_path(name, new_path)
|
||||||
ui.notify(f'Path updated to "{new_path}"', type='positive')
|
ui.notify(f'Path updated to "{new_path}"', type='positive')
|
||||||
render_project_list.refresh()
|
render_project_list.refresh()
|
||||||
|
|||||||
1
utils.py
1
utils.py
@@ -30,6 +30,7 @@ DEFAULTS = {
|
|||||||
"cfg": 1.5,
|
"cfg": 1.5,
|
||||||
|
|
||||||
# --- Settings ---
|
# --- Settings ---
|
||||||
|
"mode": 0,
|
||||||
"camera": "static",
|
"camera": "static",
|
||||||
"flf": 0.0,
|
"flf": 0.0,
|
||||||
|
|
||||||
|
|||||||
@@ -117,11 +117,11 @@ app.registerExtension({
|
|||||||
return;
|
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");
|
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");
|
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
|
// Slot 0 is always total_sequences (INT) — ensure it exists
|
||||||
if (this.outputs.length === 0 || this.outputs[0].name !== "total_sequences") {
|
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 okWidget = this.widgets?.find(w => w.name === "output_keys");
|
||||||
const otWidget = this.widgets?.find(w => w.name === "output_types");
|
const otWidget = this.widgets?.find(w => w.name === "output_types");
|
||||||
|
|
||||||
const keys = okWidget?.value
|
let keys = [];
|
||||||
? okWidget.value.split(",").filter(k => k.trim())
|
let types = [];
|
||||||
: [];
|
if (okWidget?.value) {
|
||||||
const types = otWidget?.value
|
try { keys = JSON.parse(okWidget.value); } catch (_) {
|
||||||
? otWidget.value.split(",")
|
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)
|
// Ensure slot 0 is total_sequences (INT)
|
||||||
if (this.outputs.length === 0 || this.outputs[0].name !== "total_sequences") {
|
if (this.outputs.length === 0 || this.outputs[0].name !== "total_sequences") {
|
||||||
|
|||||||
Reference in New Issue
Block a user