"""工作流页面路由 + REST API。 - `/workflows` 工作流库列表页,返回主 SPA 入口,由前端 bootstrapRoute 识别路径 - `/workflow/` 工作流编辑器页,同上 - `/api/workflows` 系列:工作流 CRUD(WORKFLOW.md 落盘,见 modules/workflow_manager.py) """ from __future__ import annotations from flask import Blueprint, current_app, jsonify, request from modules.workflow_manager import ( delete_workflow, list_workflows, load_workflow, save_workflow, ) from server.auth_helpers import api_login_required, login_required from server.gateway_auth import api_login_or_host_token_required from server.context import with_terminal from modules.i18n import tr workflow_page_bp = Blueprint("workflow_page", __name__) @workflow_page_bp.route("/workflows") @login_required def workflow_library_page(): """工作流库入口,返回与 /new 相同的 SPA index.html。""" return current_app.send_static_file("index.html") @workflow_page_bp.route("/workflow/") @login_required def workflow_editor_page(name: str): """工作流编辑器入口,返回 SPA index.html 让前端路由处理。""" return current_app.send_static_file("index.html") # ---------------------------------------------------------------- REST API @workflow_page_bp.route("/api/workflows", methods=["GET"]) @api_login_or_host_token_required @with_terminal def api_list_workflows(terminal, workspace, username): """工作流列表(内置 + 用户库双源合并,仅元信息)。""" try: return jsonify({"workflows": list_workflows(workspace.data_dir)}) except Exception as exc: return jsonify({"error": tr("workflow_page.list_failed", error=exc)}), 500 @workflow_page_bp.route("/api/workflows/", methods=["GET"]) @api_login_required @with_terminal def api_load_workflow(name: str, terminal, workspace, username): """加载完整工作流定义(用户库优先,其次内置)。""" try: return jsonify({"workflow": load_workflow(name, workspace.data_dir)}) except FileNotFoundError: return jsonify({"error": tr("workflow_page.workflow_not_found", name=name)}), 404 except ValueError as exc: return jsonify({"error": str(exc)}), 400 @workflow_page_bp.route("/api/workflows/", methods=["PUT"]) @api_login_required @with_terminal def api_save_workflow(name: str, terminal, workspace, username): """保存工作流到用户库(原子写;结构 error 级校验不过则 400)。""" data = request.get_json(silent=True) if not isinstance(data, dict) or not isinstance(data.get("workflow"), dict): return jsonify({"error": tr("workflow_page.missing_workflow_object")}), 400 wf = dict(data["workflow"]) wf["name"] = name # 名称以 URL 为准 try: save_workflow(wf, workspace.data_dir) return jsonify({"ok": True}) except ValueError as exc: return jsonify({"error": str(exc)}), 400 except OSError as exc: return jsonify({"error": tr("workflow_page.write_file_failed", error=exc)}), 500 @workflow_page_bp.route("/api/workflows/", methods=["DELETE"]) @api_login_required @with_terminal def api_delete_workflow(name: str, terminal, workspace, username): """删除用户库中的工作流(内置示例不可删)。""" try: delete_workflow(name, workspace.data_dir) return jsonify({"ok": True}) except FileNotFoundError: return jsonify({"error": tr("workflow_page.workflow_not_found", name=name)}), 404 except ValueError as exc: return jsonify({"error": str(exc)}), 400 except OSError as exc: return jsonify({"error": tr("workflow_page.delete_failed", error=exc)}), 500