import json from datetime import datetime from time import monotonic from aiohttp import web from .logger import log def _ok(**kwargs): return web.json_response({"status": "success", **kwargs}) def _err(msg, status=400): return web.json_response({"status": "error", "message": msg}, status=status) class WorkflowManagerAPI: def __init__(self, manager): self.manager = manager self._recent_auto_saves = {} async def _body(self, request): try: return await request.json() except Exception: return {} def _find_existing_workflow_id(self, name, group_id): matches = [] for workflow_id, workflow in self.manager.get_all_workflows().items(): if workflow.get("name") == name and workflow.get("group") == group_id: matches.append((workflow_id, workflow)) if not matches: return None matches.sort( key=lambda match: ( match[1].get("modified_at") or match[1].get("created_at") or "", match[0], ), reverse=True, ) if len(matches) > 1: ids = ", ".join(workflow_id[:8] for workflow_id, _ in matches) log(f"[yeban-WM] Detected duplicate workflow name/group while saving {name}: {ids}") return matches[0][0] async def list_workflows(self, request): try: return _ok( workflows=self.manager.get_all_workflows(), groups=self.manager.get_all_groups(), ) except Exception as e: return _err(str(e), 500) async def save_workflow(self, request): try: d = await self._body(request) wf_data = d.get("workflow_data") if isinstance(wf_data, str): wf_data = json.loads(wf_data) name = d.get("workflow_name") group_id = d.get("group_id") or None group_label = group_id or "\u6839\u76ee\u5f55" is_auto_save = d.get("auto_save") is not False if not name: return _err("workflow_name required", 400) now = datetime.now().strftime("%H:%M:%S") existing_id = self._find_existing_workflow_id(name, group_id) if existing_id: if is_auto_save: last_saved_at = self._recent_auto_saves.get(existing_id, 0) if monotonic() - last_saved_at < 10: log(f"[yeban-WM] [{now}] \u5df2\u62e6\u622a\u91cd\u590d\u81ea\u52a8\u4fdd\u5b58: {name} -> {group_label} | id: {existing_id[:8]}...") return _ok(workflow_id=existing_id, throttled=True) current_data = self.manager.load_workflow(existing_id) if current_data == wf_data: if is_auto_save: self._recent_auto_saves[existing_id] = monotonic() log(f"[yeban-WM] [{now}] \u8df3\u8fc7\u672a\u53d8\u5316\u7684\u4fdd\u5b58: {name} -> {group_label} | id: {existing_id[:8]}...") return _ok(workflow_id=existing_id, unchanged=True) self.manager.save_version(existing_id) log(f"[yeban-WM] [{now}] \u5df2\u521b\u5efa\u7248\u672c\u5feb\u7167: {name}") wf_id = self.manager.save_workflow(wf_data, name, group_id) if existing_id: log(f"[yeban-WM] [{now}] \u5df2\u8986\u76d6: {name} -> {group_label} | id: {wf_id[:8]}...") else: log(f"[yeban-WM] [{now}] \u5df2\u4fdd\u5b58: {name} -> {group_label} | id: {wf_id[:8]}...") if is_auto_save: self._recent_auto_saves[wf_id] = monotonic() return _ok(workflow_id=wf_id) except Exception as e: log(f"[yeban-WM] \u4fdd\u5b58\u5931\u8d25: {e}") return _err(str(e), 500) async def load_workflow(self, request): try: d = await self._body(request) wf_id = d.get("workflow_id") if not wf_id: return _err("workflow_id required", 400) data = self.manager.load_workflow(wf_id) if data is None: return _err("Workflow not found", 404) return _ok(data=data) except Exception as e: return _err(str(e), 500) async def delete_workflow(self, request): try: d = await self._body(request) result = self.manager.delete_workflow(d.get("workflow_id")) return _ok() if result else _err("Workflow not found", 404) except Exception as e: return _err(str(e), 500) async def rename_workflow(self, request): try: d = await self._body(request) result = self.manager.rename_workflow(d.get("workflow_id"), d.get("new_name")) return _ok(workflow_id=result) if result else _err("Workflow not found", 404) except Exception as e: return _err(str(e), 500) async def move_workflow(self, request): try: d = await self._body(request) result = self.manager.move_workflow( d.get("workflow_id"), d.get("target_group_id") or None, ) return _ok(workflow_id=result) if result else _err("Workflow not found", 404) except Exception as e: return _err(str(e), 500) async def copy_workflow(self, request): try: d = await self._body(request) result = self.manager.copy_workflow( d.get("workflow_id"), d.get("target_group_id") or None, d.get("new_name") or None, ) return _ok(workflow_id=result) if result else _err("Workflow not found", 404) except Exception as e: return _err(str(e), 500) async def create_group(self, request): try: d = await self._body(request) name = d.get("name") if not name: return _err("name required", 400) group_id = self.manager.create_group(name, d.get("parent_group_id") or None) return _ok(group_id=group_id) except Exception as e: return _err(str(e), 500) async def delete_group(self, request): try: d = await self._body(request) result = self.manager.delete_group(d.get("group_id")) return _ok() if result else _err("Group not found", 404) except Exception as e: return _err(str(e), 500) async def rename_group(self, request): try: d = await self._body(request) result = self.manager.rename_group(d.get("group_id"), d.get("new_name")) return _ok(group_id=result) if result else _err("Group not found", 404) except Exception as e: return _err(str(e), 500) async def save_thumbnail(self, request): try: d = await self._body(request) result = self.manager.save_thumbnail(d.get("workflow_id"), d.get("data_url")) return _ok() if result else _err("Workflow not found", 404) except Exception as e: return _err(str(e), 500) async def get_thumbnail(self, request): try: wf_id = request.match_info.get("workflow_id") path = self.manager.get_thumbnail_path(wf_id) if path is None: return web.Response(status=404) return web.FileResponse(path) except Exception as e: return _err(str(e), 500) async def list_versions(self, request): try: d = await self._body(request) versions = self.manager.list_versions(d.get("workflow_id")) return _ok(versions=versions) except Exception as e: return _err(str(e), 500) async def restore_version(self, request): try: d = await self._body(request) result = self.manager.restore_version( d.get("workflow_id"), d.get("version_filename"), ) return _ok() if result else _err("Version not found", 404) except Exception as e: return _err(str(e), 500) async def search_workflows(self, request): try: d = await self._body(request) results = self.manager.search_workflows(d.get("query", "")) return _ok(results=results) except Exception as e: return _err(str(e), 500) async def toggle_star(self, request): try: d = await self._body(request) result = self.manager.toggle_star(d.get("workflow_id")) if result is None: return _err("Workflow not found", 404) return _ok(starred=result) except Exception as e: return _err(str(e), 500) async def update_tags(self, request): try: d = await self._body(request) result = self.manager.update_tags(d.get("workflow_id"), d.get("tags", [])) return _ok() if result else _err("Workflow not found", 404) except Exception as e: return _err(str(e), 500) async def backup_workflow(self, request): try: d = await self._body(request) result = self.manager.backup_workflow(d.get("workflow_id")) return _ok(backup_path=result) if result else _err("Workflow not found", 404) except Exception as e: return _err(str(e), 500) async def import_workflow(self, request): try: d = await self._body(request) result = self.manager.import_workflow( d.get("import_path"), d.get("workflow_name"), d.get("group_id") or None, ) return _ok(workflow_id=result) if result else _err("Import failed", 500) except Exception as e: return _err(str(e), 500) async def get_settings(self, request): try: return _ok(settings=self.manager.get_settings()) except Exception as e: return _err(str(e), 500) async def update_settings(self, request): try: d = await self._body(request) settings = self.manager.update_settings( backend_logging_enabled=d.get("backend_logging_enabled") ) return _ok(settings=settings) except Exception as e: return _err(str(e), 500)