diff --git a/py/routes/handlers/misc_handlers.py b/py/routes/handlers/misc_handlers.py index 63100897..a44884ca 100644 --- a/py/routes/handlers/misc_handlers.py +++ b/py/routes/handlers/misc_handlers.py @@ -1784,6 +1784,124 @@ class LoraCodeHandler: logger.error("Failed to update lora code: %s", exc, exc_info=True) return web.json_response({"success": False, "error": str(exc)}, status=500) + async def get_update_lora_code(self, request: web.Request) -> web.Response: + """GET version of update_lora_code — reads parameters from query string. + + Query params: + lora_code (required) — the LoRA syntax to send + mode (optional) — "append" (default) or "replace" + node_id (repeatable) — target node id(s), e.g. node_id=3&node_id=5 + node_ids (optional) — JSON-encoded array for complex references with graph_id: + [{"node_id":3,"graph_id":"g1"}, ...] + """ + try: + node_ids_raw = request.query.get("node_ids") + node_id_list = request.query.getall("node_id", []) + lora_code = request.query.get("lora_code", "") + mode = request.query.get("mode", "append") + + if not lora_code: + return web.json_response( + {"success": False, "error": "Missing lora_code parameter"}, + status=400, + ) + + node_ids = None + if node_ids_raw: + try: + node_ids = json.loads(node_ids_raw) + except (json.JSONDecodeError, TypeError): + return web.json_response( + {"success": False, "error": "node_ids must be a valid JSON array"}, + status=400, + ) + if not isinstance(node_ids, list) or not node_ids: + return web.json_response( + {"success": False, "error": "node_ids must be a non-empty JSON array"}, + status=400, + ) + elif node_id_list: + node_ids = node_id_list + + results = [] + if node_ids is None: + try: + self._prompt_server.instance.send_sync( + "lora_code_update", + {"id": -1, "lora_code": lora_code, "mode": mode}, + ) + results.append({"node_id": "broadcast", "success": True}) + except Exception as exc: # pragma: no cover - defensive logging + logger.error("Error broadcasting lora code: %s", exc) + results.append( + {"node_id": "broadcast", "success": False, "error": str(exc)} + ) + else: + for entry in node_ids: + node_identifier = entry + graph_identifier = None + if isinstance(entry, dict): + node_identifier = entry.get("node_id") + graph_identifier = entry.get("graph_id") + + if node_identifier is None: + results.append( + { + "node_id": node_identifier, + "graph_id": graph_identifier, + "success": False, + "error": "Missing node_id parameter", + } + ) + continue + + try: + parsed_node_id = int(node_identifier) + except (TypeError, ValueError): + parsed_node_id = node_identifier + + payload = { + "id": parsed_node_id, + "lora_code": lora_code, + "mode": mode, + } + + if graph_identifier is not None: + payload["graph_id"] = str(graph_identifier) + + try: + self._prompt_server.instance.send_sync( + "lora_code_update", + payload, + ) + results.append( + { + "node_id": parsed_node_id, + "graph_id": payload.get("graph_id"), + "success": True, + } + ) + except Exception as exc: # pragma: no cover - defensive logging + logger.error( + "Error sending lora code to node %s (graph %s): %s", + parsed_node_id, + graph_identifier, + exc, + ) + results.append( + { + "node_id": parsed_node_id, + "graph_id": payload.get("graph_id"), + "success": False, + "error": str(exc), + } + ) + + return web.json_response({"success": True, "results": results}) + except Exception as exc: # pragma: no cover - defensive logging + logger.error("Failed to update lora code (GET): %s", exc, exc_info=True) + return web.json_response({"success": False, "error": str(exc)}, status=500) + class TrainedWordsHandler: async def get_trained_words(self, request: web.Request) -> web.Response: @@ -3431,6 +3549,130 @@ class NodeRegistryHandler: logger.error("Failed to update node widget: %s", exc, exc_info=True) return web.json_response({"success": False, "error": str(exc)}, status=500) + async def get_update_node_widget(self, request: web.Request) -> web.Response: + """GET version of update_node_widget — reads parameters from query string. + + Query params: + widget_name (optional) — the widget name to update (required unless action is set) + action (optional) — alternative action, e.g. "inject_text" (required unless widget_name is set) + value (required) — the value to set + mode (optional) — "replace" (default) or "append" + node_id (repeatable) — target node id(s), e.g. node_id=3&node_id=5 + node_ids (optional) — JSON-encoded array for complex references: + [{"node_id":3,"graph_id":"g1"}, ...] + """ + try: + widget_name = request.query.get("widget_name") + action = request.query.get("action") + value = request.query.get("value") + mode = request.query.get("mode", "replace") + node_ids_raw = request.query.get("node_ids") + node_id_list = request.query.getall("node_id", []) + + if not action and (not isinstance(widget_name, str) or not widget_name): + return web.json_response( + { + "success": False, + "error": "Missing parameter: provide either 'action' or 'widget_name'", + }, + status=400, + ) + + if not isinstance(value, str) or not value: + return web.json_response( + {"success": False, "error": "Missing value parameter"}, status=400 + ) + + node_ids = None + if node_ids_raw: + try: + node_ids = json.loads(node_ids_raw) + except (json.JSONDecodeError, TypeError): + return web.json_response( + {"success": False, "error": "node_ids must be a valid JSON array"}, + status=400, + ) + if not isinstance(node_ids, list) or not node_ids: + return web.json_response( + {"success": False, "error": "node_ids must be a non-empty JSON array"}, + status=400, + ) + elif node_id_list: + node_ids = node_id_list + + if not isinstance(node_ids, list) or not node_ids: + return web.json_response( + {"success": False, "error": "node_ids must be a non-empty list"}, + status=400, + ) + + results = [] + for entry in node_ids: + node_identifier = entry + graph_identifier = None + if isinstance(entry, dict): + node_identifier = entry.get("node_id") + graph_identifier = entry.get("graph_id") + + if node_identifier is None: + results.append( + { + "node_id": node_identifier, + "graph_id": graph_identifier, + "success": False, + "error": "Missing node_id parameter", + } + ) + continue + + try: + parsed_node_id = int(node_identifier) + except (TypeError, ValueError): + parsed_node_id = node_identifier + + payload: dict = { + "id": parsed_node_id, + "value": value, + "mode": mode, + } + if action: + payload["action"] = action + if widget_name: + payload["widget_name"] = widget_name + + if graph_identifier is not None: + payload["graph_id"] = str(graph_identifier) + + try: + self._prompt_server.instance.send_sync("lm_widget_update", payload) + results.append( + { + "node_id": parsed_node_id, + "graph_id": payload.get("graph_id"), + "success": True, + } + ) + except Exception as exc: # pragma: no cover - defensive logging + logger.error( + "Error sending widget update to node %s (graph %s): %s", + parsed_node_id, + graph_identifier, + exc, + ) + results.append( + { + "node_id": parsed_node_id, + "graph_id": payload.get("graph_id"), + "success": False, + "error": str(exc), + } + ) + + return web.json_response({"success": True, "results": results}) + except Exception as exc: # pragma: no cover - defensive logging + logger.error("Failed to update node widget (GET): %s", exc, exc_info=True) + return web.json_response({"success": False, "error": str(exc)}, status=500) + class MiscHandlerSet: """Aggregate handlers into a lookup compatible with the registrar.""" @@ -3497,10 +3739,12 @@ class MiscHandlerSet: "update_usage_stats": self.usage_stats.update_usage_stats, "get_usage_stats": self.usage_stats.get_usage_stats, "update_lora_code": self.lora_code.update_lora_code, + "get_update_lora_code": self.lora_code.get_update_lora_code, "get_trained_words": self.trained_words.get_trained_words, "get_model_example_files": self.model_examples.get_model_example_files, "register_nodes": self.node_registry.register_nodes, "update_node_widget": self.node_registry.update_node_widget, + "get_update_node_widget": self.node_registry.get_update_node_widget, "get_registry": self.node_registry.get_registry, "check_model_exists": self.model_library.check_model_exists, "check_models_exist": self.model_library.check_models_exist, diff --git a/py/routes/misc_route_registrar.py b/py/routes/misc_route_registrar.py index b6a74738..5ba051e1 100644 --- a/py/routes/misc_route_registrar.py +++ b/py/routes/misc_route_registrar.py @@ -39,10 +39,12 @@ MISC_ROUTE_DEFINITIONS: tuple[RouteDefinition, ...] = ( RouteDefinition("POST", "/api/lm/update-usage-stats", "update_usage_stats"), RouteDefinition("GET", "/api/lm/get-usage-stats", "get_usage_stats"), RouteDefinition("POST", "/api/lm/update-lora-code", "update_lora_code"), + RouteDefinition("GET", "/api/lm/update-lora-code", "get_update_lora_code"), RouteDefinition("GET", "/api/lm/trained-words", "get_trained_words"), RouteDefinition("GET", "/api/lm/model-example-files", "get_model_example_files"), RouteDefinition("POST", "/api/lm/register-nodes", "register_nodes"), RouteDefinition("POST", "/api/lm/update-node-widget", "update_node_widget"), + RouteDefinition("GET", "/api/lm/update-node-widget", "get_update_node_widget"), RouteDefinition("GET", "/api/lm/get-registry", "get_registry"), RouteDefinition("GET", "/api/lm/check-model-exists", "check_model_exists"), RouteDefinition("GET", "/api/lm/check-models-exist", "check_models_exist"),