From 1727110b46a0b5187b1548224dc4700077214f98 Mon Sep 17 00:00:00 2001 From: zhangtao <9480807882@qq.com> Date: Thu, 26 Feb 2026 00:33:57 +0800 Subject: [PATCH] =?UTF-8?q?refactor(workflow):=20=E7=A7=BB=E9=99=A4?= =?UTF-8?q?=E5=B7=A5=E4=BD=9C=E6=B5=81=E7=BC=96=E6=8E=92=E5=8A=9F=E8=83=BD?= =?UTF-8?q?=E5=8F=8A=E7=9B=B8=E5=85=B3=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 删除前后端工作流编排功能的所有相关代码,包括模型、控制器、服务、API、前端组件等 更新依赖版本并移除不再需要的依赖包 调整系统菜单配置移除工作流相关菜单项 --- backend/app/core/ap_scheduler.py | 451 ++++++++- backend/app/plugin/module_task/__init__.py | 2 - .../plugin/module_task/workflow/__init__.py | 1 - .../plugin/module_task/workflow/controller.py | 211 ----- .../app/plugin/module_task/workflow/crud.py | 152 ---- .../app/plugin/module_task/workflow/engine.py | 397 -------- .../app/plugin/module_task/workflow/model.py | 30 - .../app/plugin/module_task/workflow/schema.py | 151 ---- .../plugin/module_task/workflow/service.py | 208 ----- backend/app/scripts/data/sys_menu.json | 154 ---- backend/pyproject.toml | 4 +- backend/requirements.txt | 4 +- backend/uv.lock | 127 +-- frontend/package.json | 4 - frontend/src/api/module_task/workflow.ts | 112 --- frontend/src/composables/index.ts | 6 - frontend/src/composables/task/useNodeDrag.ts | 119 --- .../src/composables/task/useNodeOperations.ts | 164 ---- .../src/composables/task/usePerformance.ts | 31 - .../composables/task/useWorkflowHistory.ts | 106 --- .../workflow/components/DynamicNode.vue | 174 ---- .../workflow/components/EdgeConfigPanel.vue | 167 ---- .../workflow/components/NodeConfigPanel.vue | 217 ----- .../components/WorkflowDesignDrawer.vue | 854 ------------------ .../src/views/module_task/workflow/index.vue | 376 -------- .../src/views/module_task/workflow/types.ts | 162 ---- 26 files changed, 487 insertions(+), 3897 deletions(-) delete mode 100644 backend/app/plugin/module_task/workflow/__init__.py delete mode 100644 backend/app/plugin/module_task/workflow/controller.py delete mode 100644 backend/app/plugin/module_task/workflow/crud.py delete mode 100644 backend/app/plugin/module_task/workflow/engine.py delete mode 100644 backend/app/plugin/module_task/workflow/model.py delete mode 100644 backend/app/plugin/module_task/workflow/schema.py delete mode 100644 backend/app/plugin/module_task/workflow/service.py delete mode 100644 frontend/src/api/module_task/workflow.ts delete mode 100644 frontend/src/composables/task/useNodeDrag.ts delete mode 100644 frontend/src/composables/task/useNodeOperations.ts delete mode 100644 frontend/src/composables/task/usePerformance.ts delete mode 100644 frontend/src/composables/task/useWorkflowHistory.ts delete mode 100644 frontend/src/views/module_task/workflow/components/DynamicNode.vue delete mode 100644 frontend/src/views/module_task/workflow/components/EdgeConfigPanel.vue delete mode 100644 frontend/src/views/module_task/workflow/components/NodeConfigPanel.vue delete mode 100644 frontend/src/views/module_task/workflow/components/WorkflowDesignDrawer.vue delete mode 100644 frontend/src/views/module_task/workflow/index.vue delete mode 100644 frontend/src/views/module_task/workflow/types.ts diff --git a/backend/app/core/ap_scheduler.py b/backend/app/core/ap_scheduler.py index 0c6f4d20..aacfa79b 100644 --- a/backend/app/core/ap_scheduler.py +++ b/backend/app/core/ap_scheduler.py @@ -1,16 +1,32 @@ import json +from collections.abc import Callable from datetime import datetime from typing import Any from apscheduler.events import ( EVENT_ALL, + EVENT_ALL_JOBS_REMOVED, + EVENT_EXECUTOR_ADDED, + EVENT_EXECUTOR_REMOVED, + EVENT_JOB_ADDED, EVENT_JOB_ERROR, EVENT_JOB_EXECUTED, + EVENT_JOB_MAX_INSTANCES, EVENT_JOB_MISSED, + EVENT_JOB_MODIFIED, EVENT_JOB_REMOVED, EVENT_JOB_SUBMITTED, + EVENT_JOBSTORE_ADDED, + EVENT_JOBSTORE_REMOVED, + EVENT_SCHEDULER_PAUSED, + EVENT_SCHEDULER_RESUMED, + EVENT_SCHEDULER_SHUTDOWN, + EVENT_SCHEDULER_START, + EVENT_SCHEDULER_STARTED, JobEvent, JobExecutionEvent, + JobSubmissionEvent, + SchedulerEvent, ) from apscheduler.executors.asyncio import AsyncIOExecutor from apscheduler.executors.pool import ProcessPoolExecutor, ThreadPoolExecutor @@ -51,7 +67,7 @@ scheduler.configure( }, job_defaults={ "coalesce": True, - "max_instances": 1, + "max_instances": 5, }, timezone="Asia/Shanghai", ) @@ -71,43 +87,404 @@ class SchedulerUtil: 每次执行都创建新记录,保留所有历史执行记录 """ try: - if not hasattr(event, "job_id"): - return + # 事件处理器映射 + event_handlers: dict[int, Callable] = { + # 调度器事件 + EVENT_SCHEDULER_STARTED: cls._handle_scheduler_started, + EVENT_SCHEDULER_START: cls._handle_scheduler_started, + EVENT_SCHEDULER_SHUTDOWN: cls._handle_scheduler_shutdown, + EVENT_SCHEDULER_PAUSED: cls._handle_scheduler_paused, + EVENT_SCHEDULER_RESUMED: cls._handle_scheduler_resumed, + # 执行器事件 + EVENT_EXECUTOR_ADDED: cls._handle_executor_added, + EVENT_EXECUTOR_REMOVED: cls._handle_executor_removed, + # JobStore 事件 + EVENT_JOBSTORE_ADDED: cls._handle_jobstore_added, + EVENT_JOBSTORE_REMOVED: cls._handle_jobstore_removed, + EVENT_ALL_JOBS_REMOVED: cls._handle_all_jobs_removed, + # 任务事件 + EVENT_JOB_ADDED: cls._handle_job_added, + EVENT_JOB_REMOVED: cls._handle_job_removed, + EVENT_JOB_MODIFIED: cls._handle_job_modified, + EVENT_JOB_SUBMITTED: cls._handle_job_submitted, + EVENT_JOB_EXECUTED: cls._handle_job_executed, + EVENT_JOB_ERROR: cls._handle_job_error, + EVENT_JOB_MISSED: cls._handle_job_missed, + EVENT_JOB_MAX_INSTANCES: cls._handle_job_max_instances, + } - job_id = str(event.job_id) + # 处理事件 + if event.code in event_handlers: + handler = event_handlers[event.code] + handler(event) + else: + # 处理其他事件 + cls._handle_other_event(event) - if event.code == EVENT_JOB_SUBMITTED: - job = cls.get_job(job_id=job_id) - cls._create_job_log( - job_id=job_id, - job_name=job.name if job else None, - trigger_type=cls._get_trigger_type(job_id), - status="running", - ) - elif event.code == EVENT_JOB_EXECUTED: - retval = getattr(event, "retval", None) - cls._update_latest_job_log( - job_id=job_id, - status="success", - result=str(retval) if retval else None, - ) - elif event.code == EVENT_JOB_ERROR: - exception = getattr(event, "exception", None) - cls._update_latest_job_log( - job_id=job_id, - status="failed", - error=str(exception) if exception else "未知错误", - ) - elif event.code == EVENT_JOB_MISSED: - cls._update_latest_job_log( - job_id=job_id, - status="timeout", - error="任务错过执行时间", - ) - elif event.code == EVENT_JOB_REMOVED: - cls._update_job_log_on_removed(job_id=job_id) except Exception as e: - log.error(f"处理任务执行事件失败: {e!s}") + log.error(f"处理任务执行事件失败: {e!s}", exc_info=True) + + @classmethod + def _handle_job_submitted(cls, event: JobSubmissionEvent) -> None: + """ + 处理任务提交事件 + """ + job_id = str(event.job_id) + job = cls.get_job(job_id=job_id) + + if job: + log.info(f"任务 {job_id} ({job.name}) 已提交执行") + + # 创建执行日志 + cls._create_job_log( + job_id=job_id, + job_name=job.name, + trigger_type=cls._get_trigger_type(job_id), + status="running", + ) + else: + log.warning(f"任务 {job_id} 提交执行,但未找到任务信息") + + @classmethod + def _handle_job_executed(cls, event: JobExecutionEvent) -> None: + """ + 处理任务执行成功事件 + """ + job_id = str(event.job_id) + retval = getattr(event, "retval", None) + scheduled_run_time = getattr(event, "scheduled_run_time", None) + + log.info(f"任务 {job_id} 执行成功") + if retval: + log.debug(f"任务 {job_id} 返回值: {retval}") + if scheduled_run_time: + log.debug(f"任务 {job_id} 计划执行时间: {scheduled_run_time}") + + # 更新执行日志 + cls._update_latest_job_log( + job_id=job_id, + status="success", + result=str(retval) if retval else None, + ) + + @classmethod + def _handle_job_error(cls, event: JobExecutionEvent) -> None: + """ + 处理任务执行失败事件 + """ + job_id = str(event.job_id) + exception = getattr(event, "exception", None) + traceback = getattr(event, "traceback", None) + scheduled_run_time = getattr(event, "scheduled_run_time", None) + + log.error(f"任务 {job_id} 执行失败: {exception!s}") + if traceback: + log.debug(f"任务 {job_id} 错误堆栈: {traceback}") + if scheduled_run_time: + log.debug(f"任务 {job_id} 计划执行时间: {scheduled_run_time}") + + # 更新执行日志 + cls._update_latest_job_log( + job_id=job_id, + status="failed", + result="failed", + error=str(exception) if exception else "未知错误", + ) + + @classmethod + def _handle_job_missed(cls, event: JobEvent) -> None: + """ + 处理任务错过执行时间事件 + """ + job_id = str(event.job_id) + job = cls.get_job(job_id=job_id) + + log.warning(f"任务 {job_id} 错过执行时间") + if job: + log.debug(f"任务 {job_id} ({job.name}) 错过执行") + + # 更新执行日志 + cls._update_latest_job_log( + job_id=job_id, + status="timeout", + result="timeout", + error="任务错过执行时间", + ) + + @classmethod + def _handle_job_removed(cls, event: JobEvent) -> None: + """ + 处理任务被移除事件 + """ + job_id = str(event.job_id) + jobstore = getattr(event, "jobstore", "unknown") + + log.info(f"任务 {job_id} 从 {jobstore} 存储器中移除") + + # 更新执行日志 + cls._update_job_log_on_removed(job_id=job_id) + + @classmethod + def _handle_job_added(cls, event: JobEvent) -> None: + """ + 处理任务添加事件 + """ + job_id = str(event.job_id) + jobstore = event.jobstore + job = cls.get_job(job_id=job_id) + + if job: + log.info(f"任务 {job_id} ({job.name}) 已添加到 {jobstore} 存储器") + else: + log.info(f"任务 {job_id} 已添加到 {jobstore} 存储器") + + @classmethod + def _handle_job_modified(cls, event: JobEvent) -> None: + """ + 处理任务修改事件 + """ + job_id = str(event.job_id) + jobstore = event.jobstore + job = cls.get_job(job_id=job_id) + + if job: + log.info(f"任务 {job_id} ({job.name}) 已在 {jobstore} 存储器中修改") + else: + log.info(f"任务 {job_id} 已在 {jobstore} 存储器中修改") + + @classmethod + def _handle_scheduler_started(cls, event: SchedulerEvent) -> None: + """ + 处理调度器启动事件 + """ + log.info("调度器已启动") + cls._update_scheduler_status("running") + + @classmethod + def _handle_scheduler_shutdown(cls, event: SchedulerEvent) -> None: + """ + 处理调度器关闭事件 + """ + log.info("调度器已关闭") + cls._update_scheduler_status("stopped") + + @classmethod + def _handle_scheduler_paused(cls, event: SchedulerEvent) -> None: + """ + 处理调度器暂停事件 + """ + log.info("调度器已暂停") + cls._update_scheduler_status("paused") + + @classmethod + def _handle_scheduler_resumed(cls, event: SchedulerEvent) -> None: + """ + 处理调度器恢复事件 + """ + log.info("调度器已恢复运行") + cls._update_scheduler_status("running") + + @classmethod + def _handle_executor_added(cls, event: SchedulerEvent) -> None: + """ + 处理执行器添加事件 + """ + alias = event.alias + if alias: + log.info(f"执行器 {alias} 已添加到调度器") + cls._update_executor_info(alias, "added") + else: + log.warning("执行器添加事件,但别名为空") + + @classmethod + def _handle_executor_removed(cls, event: SchedulerEvent) -> None: + """ + 处理执行器移除事件 + """ + alias = event.alias + if alias: + log.info(f"执行器 {alias} 已从调度器中移除") + cls._update_executor_info(alias, "removed") + else: + log.warning("执行器移除事件,但别名为空") + + @classmethod + def _handle_jobstore_added(cls, event: SchedulerEvent) -> None: + """ + 处理 JobStore 添加事件 + """ + alias = event.alias + if alias: + log.info(f"JobStore {alias} 已添加到调度器") + cls._update_jobstore_info(alias, "added") + else: + log.warning("JobStore 添加事件,但别名为空") + + @classmethod + def _handle_jobstore_removed(cls, event: SchedulerEvent) -> None: + """ + 处理 JobStore 移除事件 + """ + alias = event.alias + if alias: + log.info(f"JobStore {alias} 已从调度器中移除") + cls._update_jobstore_info(alias, "removed") + else: + log.warning("JobStore 移除事件,但别名为空") + + @classmethod + def _handle_all_jobs_removed(cls, event: SchedulerEvent) -> None: + """ + 处理所有任务移除事件 + """ + log.info("所有任务已从调度器中移除") + cls._clear_all_job_logs() + + @classmethod + def _handle_job_max_instances(cls, event: JobEvent) -> None: + """ + 处理任务达到最大实例数事件 + """ + job_id = str(event.job_id) + log.warning(f"任务 {job_id} 已达到最大实例数限制,无法启动新实例") + + @classmethod + def _handle_other_event(cls, event: SchedulerEvent | JobEvent | JobExecutionEvent | JobSubmissionEvent) -> None: + """ + 处理其他事件 + """ + event_code = event.code + event_type = type(event).__name__ + log.debug(f"收到未处理的事件: {event_type} (code: {event_code})") + + @classmethod + def _update_scheduler_status(cls, status: str) -> None: + """ + 更新调度器状态到系统参数 + + 参数: + - status (str): 调度器状态 (running/stopped/paused) + """ + try: + from sqlalchemy.orm import Session + + from app.api.v1.module_system.params.model import ParamsModel + + with Session(engine) as session: + param = session.query(ParamsModel).filter(ParamsModel.config_key == "scheduler_status").first() + if param: + param.config_value = status + else: + param = ParamsModel( + config_name="调度器状态", + config_key="scheduler_status", + config_value=status, + config_type=True, + ) + session.add(param) + session.commit() + log.debug(f"调度器状态已更新: {status}") + except Exception as e: + log.error(f"更新调度器状态失败: {e!s}", exc_info=True) + + @classmethod + def _update_executor_info(cls, alias: str | None, action: str) -> None: + """ + 更新执行器信息到系统参数 + + 参数: + - alias (str | None): 执行器别名 + - action (str): 操作 (added/removed) + """ + if not alias: + log.warning("执行器别名为空,跳过更新") + return + + try: + from sqlalchemy.orm import Session + + from app.api.v1.module_system.params.model import ParamsModel + + key = f"executor_{alias}" + with Session(engine) as session: + param = session.query(ParamsModel).filter(ParamsModel.config_key == key).first() + if action == "added": + if param: + param.config_value = "active" + else: + param = ParamsModel( + config_name=f"执行器 {alias}", + config_key=key, + config_value="active", + config_type=True, + ) + session.add(param) + log.debug(f"执行器 {alias} 已标记为活跃") + elif action == "removed": + if param: + param.config_value = "inactive" + log.debug(f"执行器 {alias} 已标记为非活跃") + session.commit() + except Exception as e: + log.error(f"更新执行器信息失败: {e!s}", exc_info=True) + + @classmethod + def _update_jobstore_info(cls, alias: str | None, action: str) -> None: + """ + 更新 JobStore 信息到系统参数 + + 参数: + - alias (str | None): JobStore 别名 + - action (str): 操作 (added/removed) + """ + if not alias: + log.warning("JobStore 别名为空,跳过更新") + return + + try: + from sqlalchemy.orm import Session + + from app.api.v1.module_system.params.model import ParamsModel + + key = f"jobstore_{alias}" + with Session(engine) as session: + param = session.query(ParamsModel).filter(ParamsModel.config_key == key).first() + if action == "added": + if param: + param.config_value = "active" + else: + param = ParamsModel( + config_name=f"JobStore {alias}", + config_key=key, + config_value="active", + config_type=True, + ) + session.add(param) + log.debug(f"JobStore {alias} 已标记为活跃") + elif action == "removed": + if param: + param.config_value = "inactive" + log.debug(f"JobStore {alias} 已标记为非活跃") + session.commit() + except Exception as e: + log.error(f"更新 JobStore 信息失败: {e!s}", exc_info=True) + + @classmethod + def _clear_all_job_logs(cls) -> None: + """ + 清空所有任务日志 + """ + try: + from sqlalchemy.orm import Session + + from app.plugin.module_task.job.model import JobModel + + with Session(engine) as session: + session.query(JobModel).delete() + session.commit() + log.info("所有任务日志已清空") + except Exception as e: + log.error(f"清空任务日志失败: {e!s}", exc_info=True) @classmethod def _get_trigger_type(cls, job_id: str) -> str: @@ -297,7 +674,7 @@ class SchedulerUtil: with Session(engine) as session: job_log = ( session.query(JobModel) - .filter(JobModel.job_id == job_id, JobModel.status.in_(["pending", "running"])) + .filter(JobModel.job_id == job_id, JobModel.status.in_(['pending', 'running'])) .order_by(JobModel.created_time.desc()) .first() ) @@ -367,7 +744,7 @@ class SchedulerUtil: .first() ) if job_log: - if job_log.trigger_type in ["date", "manual"]: + if job_log.trigger_type in ("date", "manual"): return job_log.status = "cancelled" session.commit() diff --git a/backend/app/plugin/module_task/__init__.py b/backend/app/plugin/module_task/__init__.py index ae92242d..f35f7ba3 100644 --- a/backend/app/plugin/module_task/__init__.py +++ b/backend/app/plugin/module_task/__init__.py @@ -1,9 +1,7 @@ from .job import controller as job_controller from .node import controller as node_controller -from .workflow import controller as workflow_controller __all__ = [ "job_controller", "node_controller", - "workflow_controller", ] diff --git a/backend/app/plugin/module_task/workflow/__init__.py b/backend/app/plugin/module_task/workflow/__init__.py deleted file mode 100644 index 8b137891..00000000 --- a/backend/app/plugin/module_task/workflow/__init__.py +++ /dev/null @@ -1 +0,0 @@ - diff --git a/backend/app/plugin/module_task/workflow/controller.py b/backend/app/plugin/module_task/workflow/controller.py deleted file mode 100644 index f8729f22..00000000 --- a/backend/app/plugin/module_task/workflow/controller.py +++ /dev/null @@ -1,211 +0,0 @@ -from typing import Annotated - -from fastapi import APIRouter, Body, Depends, Path -from fastapi.responses import JSONResponse - -from app.api.v1.module_system.auth.schema import AuthSchema -from app.common.response import ResponseSchema, SuccessResponse -from app.core.base_params import PaginationQueryParam -from app.core.dependencies import AuthPermission -from app.core.logger import log -from app.core.router_class import OperationLogRoute - -from .schema import ( - WorkflowCreateSchema, - WorkflowExecuteResultSchema, - WorkflowExecuteSchema, - WorkflowOutSchema, - WorkflowPublishSchema, - WorkflowQueryParam, - WorkflowUpdateSchema, -) -from .service import WorkflowService - -WorkflowRouter = APIRouter(route_class=OperationLogRoute, prefix="/workflow", tags=["工作流模块"]) - - -@WorkflowRouter.get( - "/detail/{id}", - summary="获取工作流详情", - description="获取工作流详情", - response_model=ResponseSchema[WorkflowOutSchema], -) -async def get_obj_detail_controller( - id: Annotated[int, Path(description="工作流ID")], - auth: Annotated[AuthSchema, Depends(AuthPermission(["module_task:workflow:detail"]))], -) -> JSONResponse: - """ - 获取工作流详情 - - 参数: - - id (int): 工作流ID - - auth (AuthSchema): 认证信息模型 - - 返回: - - JSONResponse: 包含工作流详情的JSON响应 - """ - result_dict = await WorkflowService.detail_service(id=id, auth=auth) - log.info(f"获取工作流详情成功 {id}") - return SuccessResponse(data=result_dict, msg="获取工作流详情成功") - - -@WorkflowRouter.get( - "/list", - summary="查询工作流列表", - description="查询工作流列表", - response_model=ResponseSchema[list[WorkflowOutSchema]], -) -async def get_obj_list_controller( - page: Annotated[PaginationQueryParam, Depends()], - search: Annotated[WorkflowQueryParam, Depends()], - auth: Annotated[AuthSchema, Depends(AuthPermission(["module_task:workflow:query"]))], -) -> JSONResponse: - """ - 查询工作流列表 - - 参数: - - page (PaginationQueryParam): 分页查询参数 - - search (WorkflowQueryParam): 查询参数 - - auth (AuthSchema): 认证信息模型 - - 返回: - - JSONResponse: 包含工作流列表分页信息的JSON响应 - """ - result_dict = await WorkflowService.page_service( - auth=auth, - page_no=page.page_no, - page_size=page.page_size, - search=search, - order_by=page.order_by, - ) - log.info("查询工作流列表成功") - return SuccessResponse(data=result_dict, msg="查询工作流列表成功") - - -@WorkflowRouter.post( - "/create", - summary="创建工作流", - description="创建工作流", - response_model=ResponseSchema[WorkflowOutSchema], -) -async def create_obj_controller( - data: WorkflowCreateSchema, - auth: Annotated[AuthSchema, Depends(AuthPermission(["module_task:workflow:create"]))], -) -> JSONResponse: - """ - 创建工作流 - - 参数: - - data (WorkflowCreateSchema): 工作流创建模型 - - auth (AuthSchema): 认证信息模型 - - 返回: - - JSONResponse: 包含创建工作流详情的JSON响应 - """ - result_dict = await WorkflowService.create_service(auth=auth, data=data) - log.info(f"创建工作流成功: {result_dict.get('name')}") - return SuccessResponse(data=result_dict, msg="创建工作流成功") - - -@WorkflowRouter.put( - "/update/{id}", - summary="修改工作流", - description="修改工作流", - response_model=ResponseSchema[WorkflowOutSchema], -) -async def update_obj_controller( - data: WorkflowUpdateSchema, - id: Annotated[int, Path(description="工作流ID")], - auth: Annotated[AuthSchema, Depends(AuthPermission(["module_task:workflow:update"]))], -) -> JSONResponse: - """ - 修改工作流 - - 参数: - - data (WorkflowUpdateSchema): 工作流更新模型 - - id (int): 工作流ID - - auth (AuthSchema): 认证信息模型 - - 返回: - - JSONResponse: 包含修改工作流详情的JSON响应 - """ - result_dict = await WorkflowService.update_service(auth=auth, id=id, data=data) - log.info(f"修改工作流成功: {result_dict.get('name')}") - return SuccessResponse(data=result_dict, msg="修改工作流成功") - - -@WorkflowRouter.delete( - "/delete", - summary="删除工作流", - description="删除工作流", - response_model=ResponseSchema[None], -) -async def delete_obj_controller( - ids: Annotated[list[int], Body(description="ID列表")], - auth: Annotated[AuthSchema, Depends(AuthPermission(["module_task:workflow:delete"]))], -) -> JSONResponse: - """ - 删除工作流 - - 参数: - - ids (list[int]): 工作流ID列表 - - auth (AuthSchema): 认证信息模型 - - 返回: - - JSONResponse: 包含删除工作流详情的JSON响应 - """ - await WorkflowService.delete_service(auth=auth, ids=ids) - log.info(f"删除工作流成功: {ids}") - return SuccessResponse(msg="删除工作流成功") - - -@WorkflowRouter.post( - "/publish/{id}", - summary="发布工作流", - description="发布工作流", - response_model=ResponseSchema[WorkflowOutSchema], -) -async def publish_obj_controller( - id: Annotated[int, Path(description="工作流ID")], - data: WorkflowPublishSchema, - auth: Annotated[AuthSchema, Depends(AuthPermission(["module_task:workflow:publish"]))], -) -> JSONResponse: - """ - 发布工作流 - - 参数: - - id (int): 工作流ID - - data (WorkflowPublishSchema): 发布模型 - - auth (AuthSchema): 认证信息模型 - - 返回: - - JSONResponse: 包含发布工作流详情的JSON响应 - """ - result_dict = await WorkflowService.publish_service(auth=auth, id=id, data=data) - log.info(f"发布工作流成功: {result_dict.get('name')}") - return SuccessResponse(data=result_dict, msg="发布工作流成功") - - -@WorkflowRouter.post( - "/execute", - summary="执行工作流", - description="执行工作流", - response_model=ResponseSchema[WorkflowExecuteResultSchema], -) -async def execute_obj_controller( - data: WorkflowExecuteSchema, - auth: Annotated[AuthSchema, Depends(AuthPermission(["module_task:workflow:execute"]))], -) -> JSONResponse: - """ - 执行工作流 - - 参数: - - data (WorkflowExecuteSchema): 执行模型 - - auth (AuthSchema): 认证信息模型 - - 返回: - - JSONResponse: 包含执行结果的JSON响应 - """ - result_dict = await WorkflowService.execute_service(auth=auth, data=data) - log.info(f"执行工作流成功: {result_dict.get('workflow_id')}") - return SuccessResponse(data=result_dict, msg="执行工作流成功") diff --git a/backend/app/plugin/module_task/workflow/crud.py b/backend/app/plugin/module_task/workflow/crud.py deleted file mode 100644 index b38e9921..00000000 --- a/backend/app/plugin/module_task/workflow/crud.py +++ /dev/null @@ -1,152 +0,0 @@ -from collections.abc import Sequence - -from app.api.v1.module_system.auth.schema import AuthSchema -from app.core.base_crud import CRUDBase - -from .model import WorkflowModel -from .schema import ( - WorkflowCreateSchema, - WorkflowOutSchema, - WorkflowUpdateSchema, -) - - -class WorkflowCRUD(CRUDBase[WorkflowModel, WorkflowCreateSchema, WorkflowUpdateSchema]): - """工作流数据层""" - - def __init__(self, auth: AuthSchema) -> None: - """ - 初始化CRUD数据层 - - 参数: - - auth (AuthSchema): 认证信息模型 - """ - super().__init__(model=WorkflowModel, auth=auth) - - async def get_by_id_crud(self, id: int, preload: list[str] | None = None) -> WorkflowModel | None: - """ - 详情 - - 参数: - - id (int): 工作流ID - - preload (list[str] | None): 预加载关系,未提供时使用模型默认项 - - 返回: - - WorkflowModel | None: 工作流模型实例或None - """ - return await self.get(id=id, preload=preload) - - async def get_by_code_crud(self, code: str, preload: list[str] | None = None) -> WorkflowModel | None: - """ - 根据编码获取工作流 - - 参数: - - code (str): 工作流编码 - - preload (list[str] | None): 预加载关系 - - 返回: - - WorkflowModel | None: 工作流模型实例或None - """ - return await self.get(code=code, preload=preload) - - async def list_crud( - self, - search: dict | None = None, - order_by: list[dict] | None = None, - preload: list[str] | None = None, - ) -> Sequence[WorkflowModel]: - """ - 列表查询 - - 参数: - - search (dict | None): 查询参数 - - order_by (list[dict] | None): 排序参数 - - preload (list[str] | None): 预加载关系,未提供时使用模型默认项 - - 返回: - - Sequence[WorkflowModel]: 工作流模型实例序列 - """ - return await self.list(search=search, order_by=order_by, preload=preload) - - async def create_crud(self, data: WorkflowCreateSchema) -> WorkflowModel | None: - """ - 创建 - - 参数: - - data (WorkflowCreateSchema): 工作流创建模型 - - 返回: - - WorkflowModel | None: 工作流模型实例或None - """ - return await self.create(data=data) - - async def update_crud(self, id: int, data: WorkflowUpdateSchema) -> WorkflowModel | None: - """ - 更新 - - 参数: - - id (int): 工作流ID - - data (WorkflowUpdateSchema): 工作流更新模型 - - 返回: - - WorkflowModel | None: 工作流模型实例或None - """ - return await self.update(id=id, data=data) - - async def delete_crud(self, ids: list[int]) -> None: - """ - 批量删除 - - 参数: - - ids (List[int]): 工作流ID列表 - - 返回: - - None - """ - return await self.delete(ids=ids) - - async def set_available_crud(self, ids: list[int], status: str) -> None: - """ - 批量设置可用状态 - - 参数: - - ids (list[int]): 工作流ID列表 - - status (str): 可用状态 - - 返回: - - None - """ - return await self.set(ids=ids, status=status) - - async def page_crud( - self, - offset: int, - limit: int, - order_by: list[dict] | None = None, - search: dict | None = None, - preload: list | None = None, - ) -> dict: - """ - 分页查询 - - 参数: - - offset (int): 偏移量 - - limit (int): 每页数量 - - order_by (list[dict] | None): 排序参数 - - search (dict | None): 查询参数 - - preload (list | None): 预加载关系,未提供时使用模型默认项 - - 返回: - - dict: 分页数据 - """ - order_by_list = order_by or [{"id": "asc"}] - search_dict = search or {} - - return await self.page( - offset=offset, - limit=limit, - order_by=order_by_list, - search=search_dict, - out_schema=WorkflowOutSchema, - preload=preload, - ) diff --git a/backend/app/plugin/module_task/workflow/engine.py b/backend/app/plugin/module_task/workflow/engine.py deleted file mode 100644 index 29fb663a..00000000 --- a/backend/app/plugin/module_task/workflow/engine.py +++ /dev/null @@ -1,397 +0,0 @@ -import asyncio -import json -from collections import defaultdict -from collections.abc import Callable -from datetime import datetime -from typing import Any - -from sqlalchemy import select - -from app.core.database import async_db_session -from app.core.logger import log -from app.plugin.module_task.node.model import NodeModel -from app.plugin.module_task.workflow.model import WorkflowModel - - -class WorkflowRunLogModel: - pass - - -class NodeExecutionContext: - def __init__( - self, - node_id: str, - node_type: str, - args: str | None = None, - kwargs: str | None = None, - variables: dict | None = None, - input_data: Any = None, - ): - self.node_id = node_id - self.node_type = node_type - self.args = args - self.kwargs = kwargs - self.variables = variables or {} - self.input_data = input_data - self.output_data: Any = None - self.status: str = "pending" - self.error: str | None = None - self.start_time: datetime | None = None - self.end_time: datetime | None = None - - -class WorkflowExecutionContext: - def __init__( - self, - workflow_id: int, - workflow_name: str, - variables: dict | None = None, - business_key: str | None = None, - ): - self.workflow_id = workflow_id - self.workflow_name = workflow_name - self.variables = variables or {} - self.business_key = business_key - self.node_contexts: dict[str, NodeExecutionContext] = {} - self.status: str = "pending" - self.start_time: datetime | None = None - self.end_time: datetime | None = None - self.current_node: str | None = None - - -class WorkflowEngine: - """ - 工作流执行引擎 - - 功能: - 1. 解析工作流节点和边,构建执行图 - 2. 按拓扑顺序执行节点 - 3. 支持条件分支和并行执行 - 4. 记录执行日志 - """ - - _node_handlers: dict[str, Callable] = {} - - @classmethod - def register_handler(cls, node_type: str, handler: Callable) -> None: - """ - 注册节点处理器 - - 参数: - - node_type: 节点类型编码 - - handler: 处理函数,签名为 async (context: NodeExecutionContext) -> Any - """ - cls._node_handlers[node_type] = handler - - @classmethod - async def execute(cls, workflow: WorkflowModel, variables: dict | None = None, business_key: str | None = None) -> WorkflowExecutionContext: - """ - 执行工作流 - - 参数: - - workflow: 工作流模型实例 - - variables: 流程变量 - - business_key: 业务键 - - 返回: - - WorkflowExecutionContext: 执行上下文 - """ - context = WorkflowExecutionContext( - workflow_id=workflow.id, - workflow_name=workflow.name, - variables=variables or {}, - business_key=business_key, - ) - - context.start_time = datetime.now() - context.status = "running" - - try: - nodes = workflow.nodes if isinstance(workflow.nodes, list) else [] - edges = workflow.edges if isinstance(workflow.edges, list) else [] - - if not nodes: - context.status = "completed" - context.end_time = datetime.now() - return context - - node_map = {n.get("id"): n for n in nodes} - execution_order = cls._build_execution_order(nodes, edges) - - node_type_map = await cls._load_node_types([n.get("type") for n in nodes if n.get("type")]) - - for node_id in execution_order: - node_data = node_map.get(node_id) - if not node_data: - continue - - node_type_code = node_data.get("type") - node_args = node_data.get("data", {}).get("args", "") - node_kwargs = node_data.get("data", {}).get("kwargs", "{}") - - input_data = cls._collect_input_data(node_id, edges, context) - - node_context = NodeExecutionContext( - node_id=node_id, - node_type=node_type_code, - args=node_args, - kwargs=node_kwargs, - variables=context.variables, - input_data=input_data, - ) - context.node_contexts[node_id] = node_context - context.current_node = node_id - - node_type_info = node_type_map.get(node_type_code) - await cls._execute_node(node_context, node_type_info) - - if node_context.status == "failed": - context.status = "failed" - context.end_time = datetime.now() - return context - - if node_context.output_data: - context.variables[node_id] = node_context.output_data - - context.status = "completed" - except Exception as e: - log.error(f"工作流 {workflow.id} 执行失败: {e!s}") - context.status = "failed" - finally: - context.end_time = datetime.now() - context.current_node = None - - return context - - @classmethod - def _build_execution_order(cls, nodes: list, edges: list) -> list[str]: - """ - 构建节点执行顺序(拓扑排序) - - 参数: - - nodes: 节点列表 - - edges: 边列表 - - 返回: - - list[str]: 节点ID执行顺序 - """ - in_degree = defaultdict(int) - adjacency = defaultdict(list) - node_ids = {n.get("id") for n in nodes} - - for edge in edges: - source = edge.get("source") - target = edge.get("target") - if source in node_ids and target in node_ids: - adjacency[source].append(target) - in_degree[target] += 1 - - queue = [nid for nid in node_ids if in_degree[nid] == 0] - result = [] - - while queue: - node_id = queue.pop(0) - result.append(node_id) - - for neighbor in adjacency[node_id]: - in_degree[neighbor] -= 1 - if in_degree[neighbor] == 0: - queue.append(neighbor) - - for nid in node_ids: - if nid not in result: - result.append(nid) - - return result - - @classmethod - async def _load_node_types(cls, type_codes: list[str]) -> dict[str, NodeModel]: - """ - 加载节点类型定义 - - 参数: - - type_codes: 节点类型编码列表 - - 返回: - - dict[str, NodeModel]: 节点类型映射 - """ - if not type_codes: - return {} - - async with async_db_session() as session: - result = await session.execute(select(NodeModel).where(NodeModel.code.in_(type_codes))) - node_types = result.scalars().all() - return {nt.code: nt for nt in node_types} - - @classmethod - def _collect_input_data(cls, node_id: str, edges: list, context: WorkflowExecutionContext) -> dict: - """ - 收集节点输入数据(从上游节点的输出) - - 参数: - - node_id: 当前节点ID - - edges: 边列表 - - context: 执行上下文 - - 返回: - - dict: 输入数据 - """ - input_data = {} - for edge in edges: - if edge.get("target") == node_id: - source_id = edge.get("source") - source_context = context.node_contexts.get(source_id) - if source_context and source_context.output_data: - edge_label = edge.get("label", "default") - input_data[edge_label] = source_context.output_data - return input_data - - @classmethod - async def _execute_node(cls, context: NodeExecutionContext, node_type_info: NodeModel | None) -> None: - """ - 执行单个节点 - - 参数: - - context: 节点执行上下文 - - node_type_info: 节点类型定义 - """ - context.start_time = datetime.now() - context.status = "running" - - try: - handler = cls._node_handlers.get(context.node_type) - - if handler: - context.output_data = await handler(context) - elif node_type_info and node_type_info.func: - context.output_data = await cls._execute_code_block( - node_type_info.func, - node_type_info.args, - node_type_info.kwargs, - context, - ) - else: - log.warning(f"节点 {context.node_id} 没有注册处理器或代码块,跳过执行") - context.output_data = None - - context.status = "completed" - except Exception as e: - log.error(f"节点 {context.node_id} 执行失败: {e!s}") - context.status = "failed" - context.error = str(e) - finally: - context.end_time = datetime.now() - - @classmethod - async def _execute_code_block( - cls, - code_block: str, - args: str | None, - kwargs: str | None, - context: NodeExecutionContext, - ) -> Any: - """ - 执行代码块 - - 参数: - - code_block: 代码块 - - args: 位置参数 - - kwargs: 关键字参数 - - context: 节点执行上下文 - - 返回: - - Any: 执行结果 - """ - if not code_block or not code_block.strip(): - return None - - local_vars = { - "context": context, - "variables": context.variables, - "input_data": context.input_data, - } - - exec(code_block, {"__builtins__": __builtins__}, local_vars) - - handler = local_vars.get("handler") - if handler and callable(handler): - job_args = [] - if args: - args_str = str(args).strip() - if args_str: - job_args = [arg.strip() for arg in args_str.split(",") if arg.strip()] - - job_kwargs = {} - if kwargs: - kwargs_str = str(kwargs).strip() - if kwargs_str: - try: - job_kwargs = json.loads(kwargs_str) - except json.JSONDecodeError: - pass - - result = handler(*job_args, **job_kwargs) - if asyncio.iscoroutine(result): - return await result - return result - - return local_vars.get("result") - - -def register_builtin_handlers(): - """ - 注册内置节点处理器 - """ - - async def input_handler(context: NodeExecutionContext) -> Any: - return context.variables - - async def output_handler(context: NodeExecutionContext) -> Any: - return context.input_data - - async def condition_handler(context: NodeExecutionContext) -> Any: - try: - kwargs_str = str(context.kwargs or "{}").strip() - kwargs_data = json.loads(kwargs_str) if kwargs_str else {} - condition_expr = kwargs_data.get("condition", "True") - result = eval(condition_expr, {"__builtins__": __builtins__}, context.variables) - return {"condition_result": bool(result)} - except Exception as e: - log.error(f"条件表达式执行失败: {e!s}") - return {"condition_result": False} - - async def http_request_handler(context: NodeExecutionContext) -> Any: - import httpx - - kwargs_str = str(context.kwargs or "{}").strip() - kwargs_data = json.loads(kwargs_str) if kwargs_str else {} - - url = kwargs_data.get("url") - method = kwargs_data.get("method", "GET").upper() - headers = kwargs_data.get("headers", {}) - body = kwargs_data.get("body") - - if not url: - raise ValueError("HTTP请求节点缺少URL配置") - - async with httpx.AsyncClient() as client: - response = await client.request( - method=method, - url=url, - headers=headers, - json=body if body else None, - timeout=30.0, - ) - return { - "status_code": response.status_code, - "body": response.text, - "headers": dict(response.headers), - } - - WorkflowEngine.register_handler("input", input_handler) - WorkflowEngine.register_handler("output", output_handler) - WorkflowEngine.register_handler("condition", condition_handler) - WorkflowEngine.register_handler("http_request", http_request_handler) - - -register_builtin_handlers() diff --git a/backend/app/plugin/module_task/workflow/model.py b/backend/app/plugin/module_task/workflow/model.py deleted file mode 100644 index 3604be95..00000000 --- a/backend/app/plugin/module_task/workflow/model.py +++ /dev/null @@ -1,30 +0,0 @@ -import enum - -from sqlalchemy import JSON, String -from sqlalchemy.orm import Mapped, mapped_column - -from app.core.base_model import ModelMixin, UserMixin - - -class WorkflowStatusEnum(enum.Enum): - """流程状态枚举""" - - DRAFT = "draft" - PUBLISHED = "published" - ARCHIVED = "archived" - - -class WorkflowModel(ModelMixin, UserMixin): - """ - 工作流模型 - 通用流程编排引擎 - """ - - __tablename__: str = "task_workflow" - __table_args__: dict[str, str] = {"comment": "工作流表"} - __loader_options__: list[str] = ["created_by", "updated_by"] - - name: Mapped[str] = mapped_column(String(128), nullable=False, index=True, comment="流程名称") - code: Mapped[str] = mapped_column(String(64), nullable=False, unique=True, index=True, comment="流程编码") - status: Mapped[str] = mapped_column(String(32), default=WorkflowStatusEnum.DRAFT.value, nullable=False, index=True, comment="流程状态") - nodes: Mapped[list] = mapped_column(JSON, nullable=False, default=list, comment="节点数据(JSON格式)") - edges: Mapped[list] = mapped_column(JSON, nullable=False, default=list, comment="连线数据(JSON格式)") diff --git a/backend/app/plugin/module_task/workflow/schema.py b/backend/app/plugin/module_task/workflow/schema.py deleted file mode 100644 index 2c04eedd..00000000 --- a/backend/app/plugin/module_task/workflow/schema.py +++ /dev/null @@ -1,151 +0,0 @@ -from dataclasses import dataclass - -from fastapi import Query -from pydantic import ( - BaseModel, - ConfigDict, - Field, - field_validator, - model_validator, -) - -from app.common.enums import QueueEnum -from app.core.base_schema import BaseSchema, UserBySchema -from app.core.validator import DateTimeStr - - -class WorkflowCreateSchema(BaseModel): - """创建工作流模型""" - - name: str = Field(..., description="流程名称", min_length=2, max_length=128) - code: str = Field(..., description="流程编码", min_length=2, max_length=64) - status: str = Field(default="draft", description="流程状态(draft:草稿, published:已发布, archived:已归档)") - description: str | None = Field(default=None, description="流程描述") - nodes: list = Field(default_factory=list, description="节点数据(JSON格式)") - edges: list = Field(default_factory=list, description="连线数据(JSON格式)") - - @field_validator("code") - @classmethod - def validate_code(cls, v: str) -> str: - """验证流程编码格式""" - v = v.strip() - if not v: - raise ValueError("流程编码不能为空") - if not v.replace("-", "_").replace("_", "").isalnum(): - raise ValueError("流程编码只能包含字母、数字、下划线和中划线") - return v - - @field_validator("status") - @classmethod - def validate_status(cls, v: str) -> str: - """验证流程状态""" - valid_statuses = ["draft", "published", "archived"] - if v not in valid_statuses: - raise ValueError(f"流程状态必须为: {', '.join(valid_statuses)}") - return v - - @model_validator(mode="after") - def _after_validation(self): - """核心业务规则校验""" - if self.description and len(self.description) > 1000: - raise ValueError("流程描述长度不能超过1000个字符") - return self - - -class WorkflowUpdateSchema(BaseModel): - """更新工作流模型""" - - name: str | None = Field(default=None, description="流程名称", min_length=2, max_length=128) - code: str | None = Field(default=None, description="流程编码", min_length=2, max_length=64) - status: str | None = Field(default=None, description="流程状态") - description: str | None = Field(default=None, description="流程描述") - nodes: list | None = Field(default=None, description="节点数据(JSON格式)") - edges: list | None = Field(default=None, description="连线数据(JSON格式)") - - @field_validator("status") - @classmethod - def validate_status(cls, v: str | None) -> str | None: - """验证流程状态""" - if v is not None: - valid_statuses = ["draft", "published", "archived"] - if v not in valid_statuses: - raise ValueError(f"流程状态必须为: {', '.join(valid_statuses)}") - return v - - @model_validator(mode="after") - def _after_validation(self): - """核心业务规则校验""" - if self.description and len(self.description) > 1000: - raise ValueError("流程描述长度不能超过1000个字符") - return self - - -class WorkflowOutSchema(WorkflowUpdateSchema, BaseSchema, UserBySchema): - """工作流响应模型""" - - model_config = ConfigDict(from_attributes=True) - - -class WorkflowPublishSchema(BaseModel): - """发布工作流模型""" - - pass - - -@dataclass -class WorkflowQueryParam: - """工作流查询参数""" - - def __init__( - self, - name: str | None = Query(None, description="流程名称"), - code: str | None = Query(None, description="流程编码"), - status: str | None = Query(None, description="流程状态"), - created_time: list[DateTimeStr] | None = Query( - None, - description="创建时间范围", - examples=["2025-01-01 00:00:00", "2025-12-31 23:59:59"], - ), - updated_time: list[DateTimeStr] | None = Query( - None, - description="更新时间范围", - examples=["2025-01-01 00:00:00", "2025-12-31 23:59:59"], - ), - created_id: int | None = Query(None, description="创建人"), - updated_id: int | None = Query(None, description="更新人"), - ) -> None: - if name: - self.name = (QueueEnum.like.value, name) - if code: - self.code = (QueueEnum.like.value, code) - if status: - self.status = (QueueEnum.eq.value, status) - if created_time and len(created_time) == 2: - self.created_time = (QueueEnum.between.value, (created_time[0], created_time[1])) - if updated_time and len(updated_time) == 2: - self.updated_time = (QueueEnum.between.value, (updated_time[0], updated_time[1])) - if created_id: - self.created_id = (QueueEnum.eq.value, created_id) - if updated_id: - self.updated_id = (QueueEnum.eq.value, updated_id) - - -class WorkflowExecuteSchema(BaseModel): - """执行工作流模型""" - - workflow_id: int = Field(..., description="工作流ID") - variables: dict = Field(default_factory=dict, description="流程变量(JSON格式)") - business_key: str | None = Field(default=None, description="业务键") - job_id: int | None = Field(default=None, description="关联的定时任务ID") - - -class WorkflowExecuteResultSchema(BaseModel): - """工作流执行结果模型""" - - workflow_id: int = Field(..., description="工作流ID") - workflow_name: str = Field(..., description="工作流名称") - status: str = Field(..., description="执行状态") - start_time: str | None = Field(default=None, description="开始时间") - end_time: str | None = Field(default=None, description="结束时间") - variables: dict = Field(default_factory=dict, description="流程变量") - node_results: dict = Field(default_factory=dict, description="节点执行结果") diff --git a/backend/app/plugin/module_task/workflow/service.py b/backend/app/plugin/module_task/workflow/service.py deleted file mode 100644 index f2c706bb..00000000 --- a/backend/app/plugin/module_task/workflow/service.py +++ /dev/null @@ -1,208 +0,0 @@ - -from app.api.v1.module_system.auth.schema import AuthSchema -from app.core.exceptions import CustomException - -from .crud import WorkflowCRUD -from .engine import WorkflowEngine -from .schema import ( - WorkflowCreateSchema, - WorkflowExecuteSchema, - WorkflowOutSchema, - WorkflowPublishSchema, - WorkflowQueryParam, - WorkflowUpdateSchema, -) - - -class WorkflowService: - """ - 工作流管理模块服务层 - """ - - @classmethod - async def detail_service(cls, auth: AuthSchema, id: int) -> dict: - """ - 详情 - - 参数: - - auth (AuthSchema): 认证信息模型 - - id (int): 工作流ID - - 返回: - - dict: 工作流模型实例字典 - """ - obj = await WorkflowCRUD(auth).get_by_id_crud(id=id) - if not obj: - raise CustomException(msg="该工作流不存在") - return WorkflowOutSchema.model_validate(obj).model_dump() - - @classmethod - async def page_service( - cls, - auth: AuthSchema, - page_no: int, - page_size: int, - search: WorkflowQueryParam | None = None, - order_by: list[dict[str, str]] | None = None, - ) -> dict: - """ - 分页查询 - - 参数: - - auth (AuthSchema): 认证信息模型 - - page_no (int): 页码 - - page_size (int): 每页数量 - - search (WorkflowQueryParam | None): 查询参数 - - order_by (list[dict[str, str]] | None): 排序参数 - - 返回: - - dict: 分页数据 - """ - search_dict = search.__dict__ if search else {} - order_by_list = order_by or [{"id": "asc"}] - offset = (page_no - 1) * page_size - - result = await WorkflowCRUD(auth).page_crud( - offset=offset, - limit=page_size, - order_by=order_by_list, - search=search_dict, - ) - return result - - @classmethod - async def create_service(cls, auth: AuthSchema, data: WorkflowCreateSchema) -> dict: - """ - 创建 - - 参数: - - auth (AuthSchema): 认证信息模型 - - data (WorkflowCreateSchema): 工作流创建模型 - - 返回: - - dict: 工作流模型实例字典 - """ - obj = await WorkflowCRUD(auth).get_by_code_crud(code=data.code) - if obj: - raise CustomException(msg="创建失败,流程编码已存在") - - obj = await WorkflowCRUD(auth).create_crud(data=data) - return WorkflowOutSchema.model_validate(obj).model_dump() - - @classmethod - async def update_service(cls, auth: AuthSchema, id: int, data: WorkflowUpdateSchema) -> dict: - """ - 更新 - - 参数: - - auth (AuthSchema): 认证信息模型 - - id (int): 工作流ID - - data (WorkflowUpdateSchema): 工作流更新模型 - - 返回: - - dict: 工作流模型实例字典 - """ - obj = await WorkflowCRUD(auth).get_by_id_crud(id=id) - if not obj: - raise CustomException(msg="更新失败,该工作流不存在") - - if data.code: - exist_obj = await WorkflowCRUD(auth).get_by_code_crud(code=data.code) - if exist_obj and exist_obj.id != id: - raise CustomException(msg="更新失败,流程编码重复") - - obj = await WorkflowCRUD(auth).update_crud(id=id, data=data) - return WorkflowOutSchema.model_validate(obj).model_dump() - - @classmethod - async def delete_service(cls, auth: AuthSchema, ids: list[int]) -> None: - """ - 删除 - - 参数: - - auth (AuthSchema): 认证信息模型 - - ids (list[int]): 工作流ID列表 - - 返回: - - None - """ - if len(ids) < 1: - raise CustomException(msg="删除失败,删除对象不能为空") - - obj_list = await WorkflowCRUD(auth).list_crud(search={"id": (None, ids)}) - - if len(obj_list) != len(ids): - existing_ids = {obj.id for obj in obj_list} - missing_ids = set(ids) - existing_ids - raise CustomException(msg=f"删除失败,ID为{missing_ids}的工作流不存在") - - await WorkflowCRUD(auth).delete_crud(ids=ids) - - @classmethod - async def publish_service(cls, auth: AuthSchema, id: int, data: WorkflowPublishSchema) -> dict: - """ - 发布工作流 - - 参数: - - auth (AuthSchema): 认证信息模型 - - id (int): 工作流ID - - data (WorkflowPublishSchema): 发布模型 - - 返回: - - dict: 工作流模型实例字典 - """ - obj = await WorkflowCRUD(auth).get_by_id_crud(id=id) - if not obj: - raise CustomException(msg="发布失败,该工作流不存在") - - if obj.status == "published": - raise CustomException(msg="发布失败,该工作流已发布") - - update_data = WorkflowUpdateSchema( - status="published", - ) - - obj = await WorkflowCRUD(auth).update_crud(id=id, data=update_data) - return WorkflowOutSchema.model_validate(obj).model_dump() - - @classmethod - async def execute_service(cls, auth: AuthSchema, data: WorkflowExecuteSchema) -> dict: - """ - 执行工作流 - - 参数: - - auth (AuthSchema): 认证信息模型 - - data (WorkflowExecuteSchema): 执行模型 - - 返回: - - dict: 执行结果 - """ - workflow = await WorkflowCRUD(auth).get_by_id_crud(id=data.workflow_id) - if not workflow: - raise CustomException(msg="工作流不存在") - - if workflow.status != "published": - raise CustomException(msg="工作流未发布,无法执行") - - context = await WorkflowEngine.execute( - workflow=workflow, - variables=data.variables, - business_key=data.business_key, - ) - - return { - "workflow_id": data.workflow_id, - "workflow_name": workflow.name, - "status": context.status, - "start_time": context.start_time.isoformat() if context.start_time else None, - "end_time": context.end_time.isoformat() if context.end_time else None, - "variables": context.variables, - "node_results": { - node_id: { - "status": ctx.status, - "output": ctx.output_data, - "error": ctx.error, - } - for node_id, ctx in context.node_contexts.items() - }, - } diff --git a/backend/app/scripts/data/sys_menu.json b/backend/app/scripts/data/sys_menu.json index 08f16d6a..9e85de80 100644 --- a/backend/app/scripts/data/sys_menu.json +++ b/backend/app/scripts/data/sys_menu.json @@ -2805,160 +2805,6 @@ "description": "查询节点" } ] - }, - { - "name": "流程编排", - "type": 2, - "icon": "el-icon-Share", - "order": 3, - "permission": "module_task:workflow:query", - "route_name": "Workflow", - "route_path": "/task/workflow", - "component_path": "module_task/workflow/index", - "status": "0", - "keep_alive": true, - "hidden": false, - "always_show": false, - "title": "流程编排", - "params": null, - "affix": false, - "redirect": null, - "description": "流程编排", - "children": [ - { - "name": "创建工作流", - "type": 3, - "icon": null, - "order": 1, - "permission": "module_task:workflow:create", - "route_name": null, - "route_path": null, - "component_path": null, - "status": "0", - "keep_alive": true, - "hidden": false, - "always_show": false, - "title": "创建工作流", - "params": null, - "affix": false, - "redirect": null, - "description": "创建工作流" - }, - { - "name": "修改工作流", - "type": 3, - "icon": null, - "order": 2, - "permission": "module_task:workflow:update", - "route_name": null, - "route_path": null, - "component_path": null, - "status": "0", - "keep_alive": true, - "hidden": false, - "always_show": false, - "title": "修改工作流", - "params": null, - "affix": false, - "redirect": null, - "description": "修改工作流" - }, - { - "name": "删除工作流", - "type": 3, - "icon": null, - "order": 3, - "permission": "module_task:workflow:delete", - "route_name": null, - "route_path": null, - "component_path": null, - "status": "0", - "keep_alive": true, - "hidden": false, - "always_show": false, - "title": "删除工作流", - "params": null, - "affix": false, - "redirect": null, - "description": "删除工作流" - }, - { - "name": "发布工作流", - "type": 3, - "icon": null, - "order": 4, - "permission": "module_task:workflow:publish", - "route_name": null, - "route_path": null, - "component_path": null, - "status": "0", - "keep_alive": true, - "hidden": false, - "always_show": false, - "title": "发布工作流", - "params": null, - "affix": false, - "redirect": null, - "description": "发布工作流" - }, - { - "name": "执行工作流", - "type": 3, - "icon": null, - "order": 5, - "permission": "module_task:workflow:execute", - "route_name": null, - "route_path": null, - "component_path": null, - "status": "0", - "keep_alive": true, - "hidden": false, - "always_show": false, - "title": "执行工作流", - "params": null, - "affix": false, - "redirect": null, - "description": "执行工作流" - }, - { - "name": "详情工作流", - "type": 3, - "icon": null, - "order": 6, - "permission": "module_task:workflow:detail", - "route_name": null, - "route_path": null, - "component_path": null, - "status": "0", - "keep_alive": true, - "hidden": false, - "always_show": false, - "title": "详情工作流", - "params": null, - "affix": false, - "redirect": null, - "description": "详情工作流" - }, - { - "name": "查询工作流", - "type": 3, - "icon": null, - "order": 7, - "permission": "module_task:workflow:query", - "route_name": null, - "route_path": null, - "component_path": null, - "status": "0", - "keep_alive": true, - "hidden": false, - "always_show": false, - "title": "查询工作流", - "params": null, - "affix": false, - "redirect": null, - "description": "查询工作流" - } - ] } ] }, diff --git a/backend/pyproject.toml b/backend/pyproject.toml index 9a204f8e..6184a721 100644 --- a/backend/pyproject.toml +++ b/backend/pyproject.toml @@ -47,10 +47,10 @@ dependencies = [ "ruff==0.14.13", "sqlalchemy==2.0.45", # 数据库ORM "sqlglot[rs]==27.8.0", # sql 解析 - "typer==0.9.0", # 命令行工具 + "typer==0.19.0", # 命令行工具 "user-agents==2.2.0", # 获取用户UA "uvicorn==0.30.6", # uvicorn web 框架 - "websockets==14.2", # websocket 框架 + "websockets==16.0", # websocket 框架 ] [dependency-groups] diff --git a/backend/requirements.txt b/backend/requirements.txt index 9ebc2616..3d5b390b 100755 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -3,11 +3,11 @@ APScheduler==3.11.0 # 定时任务 bleach==6.3.0 # XSS 过滤 fastapi==0.115.2 # fastapi 框架 Jinja2==3.1.6 # 模板引擎 -typer==0.9.0 # 命令行工具 +typer==0.19.0 # 命令行工具 click==8.1.7 # 命令行参数解析 uvicorn==0.30.6 # uvicorn web 框架 gunicorn==23.0.0 # 协程框架 -websockets==14.2 # websocket 框架 +websockets==16.0 # websocket 框架 httpx==0.27.2 # HTTP 客户端 croniter==6.0.0 # 实现cron表达式验证和解析执行计划 pandas==2.2.2 # 数据处理 diff --git a/backend/uv.lock b/backend/uv.lock index 154e4016..efc1b7bc 100644 --- a/backend/uv.lock +++ b/backend/uv.lock @@ -286,10 +286,10 @@ requires-dist = [ { name = "ruff", specifier = "==0.14.13" }, { name = "sqlalchemy", specifier = "==2.0.45" }, { name = "sqlglot", extras = ["rs"], specifier = "==27.8.0" }, - { name = "typer", specifier = "==0.9.0" }, + { name = "typer", specifier = "==0.19.0" }, { name = "user-agents", specifier = "==2.2.0" }, { name = "uvicorn", specifier = "==0.30.6" }, - { name = "websockets", specifier = "==14.2" }, + { name = "websockets", specifier = "==16.0" }, ] [package.metadata.requires-dev] @@ -3672,15 +3672,17 @@ wheels = [ [[package]] name = "typer" -version = "0.9.0" +version = "0.19.0" source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } dependencies = [ { name = "click" }, + { name = "rich" }, + { name = "shellingham" }, { name = "typing-extensions" }, ] -sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5b/49/39f10d0f75886439ab3dac889f14f8ad511982a754e382c9b6ca895b29e9/typer-0.9.0.tar.gz", hash = "sha256:50922fd79aea2f4751a8e0408ff10d2662bd0c8bbfa84755a699f3bada2978b2", size = 273985, upload-time = "2023-05-02T05:20:57.63Z" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/40/48/b648a9e38df4fa0b5e935767845038daa68b270a0dcc87a8f5b042f187bf/typer-0.19.0.tar.gz", hash = "sha256:f290580f52c60222574d4261fb26e316204558be4add913ee53e4680f16cd6d2", size = 104759, upload-time = "2025-09-20T08:07:43.208Z" } wheels = [ - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/bf/0e/c68adf10adda05f28a6ed7b9f4cd7b8e07f641b44af88ba72d9c89e4de7a/typer-0.9.0-py3-none-any.whl", hash = "sha256:5d96d986a21493606a358cae4461bd8cdf83cbf33a5aa950ae629ca3b51467ee", size = 45861, upload-time = "2023-05-02T05:20:55.675Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/30/5c/3e5e443718ca54ab01a746ceaaea9575af312a4e456426406002994e7c43/typer-0.19.0-py3-none-any.whl", hash = "sha256:3c6abeb8f01b97b9be84b8dd134956b4c15a3eef0e11382c537dc083886f908f", size = 46877, upload-time = "2025-09-20T08:07:40.536Z" }, ] [[package]] @@ -4000,61 +4002,70 @@ wheels = [ [[package]] name = "websockets" -version = "14.2" +version = "16.0" source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } -sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/94/54/8359678c726243d19fae38ca14a334e740782336c9f19700858c4eb64a1e/websockets-14.2.tar.gz", hash = "sha256:5059ed9c54945efb321f097084b4c7e52c246f2c869815876a69d1efc4ad6eb5", size = 164394, upload-time = "2025-01-19T21:00:56.431Z" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/04/24/4b2031d72e840ce4c1ccb255f693b15c334757fc50023e4db9537080b8c4/websockets-16.0.tar.gz", hash = "sha256:5f6261a5e56e8d5c42a4497b364ea24d94d9563e8fbd44e78ac40879c60179b5", size = 179346, upload-time = "2026-01-10T09:23:47.181Z" } wheels = [ - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/28/fa/76607eb7dcec27b2d18d63f60a32e60e2b8629780f343bb83a4dbb9f4350/websockets-14.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e8179f95323b9ab1c11723e5d91a89403903f7b001828161b480a7810b334885", size = 163089, upload-time = "2025-01-19T20:58:43.399Z" }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9e/00/ad2246b5030575b79e7af0721810fdaecaf94c4b2625842ef7a756fa06dd/websockets-14.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0d8c3e2cdb38f31d8bd7d9d28908005f6fa9def3324edb9bf336d7e4266fd397", size = 160741, upload-time = "2025-01-19T20:58:45.309Z" }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/72/f7/60f10924d333a28a1ff3fcdec85acf226281331bdabe9ad74947e1b7fc0a/websockets-14.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:714a9b682deb4339d39ffa674f7b674230227d981a37d5d174a4a83e3978a610", size = 160996, upload-time = "2025-01-19T20:58:47.563Z" }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/63/7c/c655789cf78648c01ac6ecbe2d6c18f91b75bdc263ffee4d08ce628d12f0/websockets-14.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2e53c72052f2596fb792a7acd9704cbc549bf70fcde8a99e899311455974ca3", size = 169974, upload-time = "2025-01-19T20:58:51.023Z" }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/fb/5b/013ed8b4611857ac92ac631079c08d9715b388bd1d88ec62e245f87a39df/websockets-14.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e3fbd68850c837e57373d95c8fe352203a512b6e49eaae4c2f4088ef8cf21980", size = 168985, upload-time = "2025-01-19T20:58:52.698Z" }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/cd/33/aa3e32fd0df213a5a442310754fe3f89dd87a0b8e5b4e11e0991dd3bcc50/websockets-14.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b27ece32f63150c268593d5fdb82819584831a83a3f5809b7521df0685cd5d8", size = 169297, upload-time = "2025-01-19T20:58:54.898Z" }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/93/17/dae0174883d6399f57853ac44abf5f228eaba86d98d160f390ffabc19b6e/websockets-14.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4daa0faea5424d8713142b33825fff03c736f781690d90652d2c8b053345b0e7", size = 169677, upload-time = "2025-01-19T20:58:56.36Z" }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/42/e2/0375af7ac00169b98647c804651c515054b34977b6c1354f1458e4116c1e/websockets-14.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:bc63cee8596a6ec84d9753fd0fcfa0452ee12f317afe4beae6b157f0070c6c7f", size = 169089, upload-time = "2025-01-19T20:58:58.824Z" }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/73/8d/80f71d2a351a44b602859af65261d3dde3a0ce4e76cf9383738a949e0cc3/websockets-14.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7a570862c325af2111343cc9b0257b7119b904823c675b22d4ac547163088d0d", size = 169026, upload-time = "2025-01-19T20:59:01.089Z" }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/48/97/173b1fa6052223e52bb4054a141433ad74931d94c575e04b654200b98ca4/websockets-14.2-cp310-cp310-win32.whl", hash = "sha256:75862126b3d2d505e895893e3deac0a9339ce750bd27b4ba515f008b5acf832d", size = 163967, upload-time = "2025-01-19T20:59:02.662Z" }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c0/5b/2fcf60f38252a4562b28b66077e0d2b48f91fef645d5f78874cd1dec807b/websockets-14.2-cp310-cp310-win_amd64.whl", hash = "sha256:cc45afb9c9b2dc0852d5c8b5321759cf825f82a31bfaf506b65bf4668c96f8b2", size = 164413, upload-time = "2025-01-19T20:59:05.071Z" }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/15/b6/504695fb9a33df0ca56d157f5985660b5fc5b4bf8c78f121578d2d653392/websockets-14.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3bdc8c692c866ce5fefcaf07d2b55c91d6922ac397e031ef9b774e5b9ea42166", size = 163088, upload-time = "2025-01-19T20:59:06.435Z" }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/81/26/ebfb8f6abe963c795122439c6433c4ae1e061aaedfc7eff32d09394afbae/websockets-14.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c93215fac5dadc63e51bcc6dceca72e72267c11def401d6668622b47675b097f", size = 160745, upload-time = "2025-01-19T20:59:09.109Z" }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a1/c6/1435ad6f6dcbff80bb95e8986704c3174da8866ddb751184046f5c139ef6/websockets-14.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1c9b6535c0e2cf8a6bf938064fb754aaceb1e6a4a51a80d884cd5db569886910", size = 160995, upload-time = "2025-01-19T20:59:12.816Z" }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/96/63/900c27cfe8be1a1f2433fc77cd46771cf26ba57e6bdc7cf9e63644a61863/websockets-14.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a52a6d7cf6938e04e9dceb949d35fbdf58ac14deea26e685ab6368e73744e4c", size = 170543, upload-time = "2025-01-19T20:59:15.026Z" }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/00/8b/bec2bdba92af0762d42d4410593c1d7d28e9bfd952c97a3729df603dc6ea/websockets-14.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9f05702e93203a6ff5226e21d9b40c037761b2cfb637187c9802c10f58e40473", size = 169546, upload-time = "2025-01-19T20:59:17.156Z" }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6b/a9/37531cb5b994f12a57dec3da2200ef7aadffef82d888a4c29a0d781568e4/websockets-14.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22441c81a6748a53bfcb98951d58d1af0661ab47a536af08920d129b4d1c3473", size = 169911, upload-time = "2025-01-19T20:59:18.623Z" }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/60/d5/a6eadba2ed9f7e65d677fec539ab14a9b83de2b484ab5fe15d3d6d208c28/websockets-14.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:efd9b868d78b194790e6236d9cbc46d68aba4b75b22497eb4ab64fa640c3af56", size = 170183, upload-time = "2025-01-19T20:59:20.743Z" }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/76/57/a338ccb00d1df881c1d1ee1f2a20c9c1b5b29b51e9e0191ee515d254fea6/websockets-14.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:1a5a20d5843886d34ff8c57424cc65a1deda4375729cbca4cb6b3353f3ce4142", size = 169623, upload-time = "2025-01-19T20:59:22.286Z" }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/64/22/e5f7c33db0cb2c1d03b79fd60d189a1da044e2661f5fd01d629451e1db89/websockets-14.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:34277a29f5303d54ec6468fb525d99c99938607bc96b8d72d675dee2b9f5bf1d", size = 169583, upload-time = "2025-01-19T20:59:23.656Z" }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/aa/2e/2b4662237060063a22e5fc40d46300a07142afe30302b634b4eebd717c07/websockets-14.2-cp311-cp311-win32.whl", hash = "sha256:02687db35dbc7d25fd541a602b5f8e451a238ffa033030b172ff86a93cb5dc2a", size = 163969, upload-time = "2025-01-19T20:59:26.004Z" }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/94/a5/0cda64e1851e73fc1ecdae6f42487babb06e55cb2f0dc8904b81d8ef6857/websockets-14.2-cp311-cp311-win_amd64.whl", hash = "sha256:862e9967b46c07d4dcd2532e9e8e3c2825e004ffbf91a5ef9dde519ee2effb0b", size = 164408, upload-time = "2025-01-19T20:59:28.105Z" }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c1/81/04f7a397653dc8bec94ddc071f34833e8b99b13ef1a3804c149d59f92c18/websockets-14.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1f20522e624d7ffbdbe259c6b6a65d73c895045f76a93719aa10cd93b3de100c", size = 163096, upload-time = "2025-01-19T20:59:29.763Z" }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ec/c5/de30e88557e4d70988ed4d2eabd73fd3e1e52456b9f3a4e9564d86353b6d/websockets-14.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:647b573f7d3ada919fd60e64d533409a79dcf1ea21daeb4542d1d996519ca967", size = 160758, upload-time = "2025-01-19T20:59:32.095Z" }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e5/8c/d130d668781f2c77d106c007b6c6c1d9db68239107c41ba109f09e6c218a/websockets-14.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6af99a38e49f66be5a64b1e890208ad026cda49355661549c507152113049990", size = 160995, upload-time = "2025-01-19T20:59:33.527Z" }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a6/bc/f6678a0ff17246df4f06765e22fc9d98d1b11a258cc50c5968b33d6742a1/websockets-14.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:091ab63dfc8cea748cc22c1db2814eadb77ccbf82829bac6b2fbe3401d548eda", size = 170815, upload-time = "2025-01-19T20:59:35.837Z" }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d8/b2/8070cb970c2e4122a6ef38bc5b203415fd46460e025652e1ee3f2f43a9a3/websockets-14.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b374e8953ad477d17e4851cdc66d83fdc2db88d9e73abf755c94510ebddceb95", size = 169759, upload-time = "2025-01-19T20:59:38.216Z" }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/81/da/72f7caabd94652e6eb7e92ed2d3da818626e70b4f2b15a854ef60bf501ec/websockets-14.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a39d7eceeea35db85b85e1169011bb4321c32e673920ae9c1b6e0978590012a3", size = 170178, upload-time = "2025-01-19T20:59:40.423Z" }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/31/e0/812725b6deca8afd3a08a2e81b3c4c120c17f68c9b84522a520b816cda58/websockets-14.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0a6f3efd47ffd0d12080594f434faf1cd2549b31e54870b8470b28cc1d3817d9", size = 170453, upload-time = "2025-01-19T20:59:41.996Z" }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/66/d3/8275dbc231e5ba9bb0c4f93144394b4194402a7a0c8ffaca5307a58ab5e3/websockets-14.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:065ce275e7c4ffb42cb738dd6b20726ac26ac9ad0a2a48e33ca632351a737267", size = 169830, upload-time = "2025-01-19T20:59:44.669Z" }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a3/ae/e7d1a56755ae15ad5a94e80dd490ad09e345365199600b2629b18ee37bc7/websockets-14.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e9d0e53530ba7b8b5e389c02282f9d2aa47581514bd6049d3a7cffe1385cf5fe", size = 169824, upload-time = "2025-01-19T20:59:46.932Z" }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b6/32/88ccdd63cb261e77b882e706108d072e4f1c839ed723bf91a3e1f216bf60/websockets-14.2-cp312-cp312-win32.whl", hash = "sha256:20e6dd0984d7ca3037afcb4494e48c74ffb51e8013cac71cf607fffe11df7205", size = 163981, upload-time = "2025-01-19T20:59:49.228Z" }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b3/7d/32cdb77990b3bdc34a306e0a0f73a1275221e9a66d869f6ff833c95b56ef/websockets-14.2-cp312-cp312-win_amd64.whl", hash = "sha256:44bba1a956c2c9d268bdcdf234d5e5ff4c9b6dc3e300545cbe99af59dda9dcce", size = 164421, upload-time = "2025-01-19T20:59:50.674Z" }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/82/94/4f9b55099a4603ac53c2912e1f043d6c49d23e94dd82a9ce1eb554a90215/websockets-14.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6f1372e511c7409a542291bce92d6c83320e02c9cf392223272287ce55bc224e", size = 163102, upload-time = "2025-01-19T20:59:52.177Z" }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8e/b7/7484905215627909d9a79ae07070057afe477433fdacb59bf608ce86365a/websockets-14.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4da98b72009836179bb596a92297b1a61bb5a830c0e483a7d0766d45070a08ad", size = 160766, upload-time = "2025-01-19T20:59:54.368Z" }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a3/a4/edb62efc84adb61883c7d2c6ad65181cb087c64252138e12d655989eec05/websockets-14.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8a86a269759026d2bde227652b87be79f8a734e582debf64c9d302faa1e9f03", size = 160998, upload-time = "2025-01-19T20:59:56.671Z" }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f5/79/036d320dc894b96af14eac2529967a6fc8b74f03b83c487e7a0e9043d842/websockets-14.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:86cf1aaeca909bf6815ea714d5c5736c8d6dd3a13770e885aafe062ecbd04f1f", size = 170780, upload-time = "2025-01-19T20:59:58.085Z" }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/63/75/5737d21ee4dd7e4b9d487ee044af24a935e36a9ff1e1419d684feedcba71/websockets-14.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9b0f6c3ba3b1240f602ebb3971d45b02cc12bd1845466dd783496b3b05783a5", size = 169717, upload-time = "2025-01-19T20:59:59.545Z" }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/2c/3c/bf9b2c396ed86a0b4a92ff4cdaee09753d3ee389be738e92b9bbd0330b64/websockets-14.2-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:669c3e101c246aa85bc8534e495952e2ca208bd87994650b90a23d745902db9a", size = 170155, upload-time = "2025-01-19T21:00:01.887Z" }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/75/2d/83a5aca7247a655b1da5eb0ee73413abd5c3a57fc8b92915805e6033359d/websockets-14.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:eabdb28b972f3729348e632ab08f2a7b616c7e53d5414c12108c29972e655b20", size = 170495, upload-time = "2025-01-19T21:00:04.064Z" }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/79/dd/699238a92761e2f943885e091486378813ac8f43e3c84990bc394c2be93e/websockets-14.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2066dc4cbcc19f32c12a5a0e8cc1b7ac734e5b64ac0a325ff8353451c4b15ef2", size = 169880, upload-time = "2025-01-19T21:00:05.695Z" }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c8/c9/67a8f08923cf55ce61aadda72089e3ed4353a95a3a4bc8bf42082810e580/websockets-14.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ab95d357cd471df61873dadf66dd05dd4709cae001dd6342edafc8dc6382f307", size = 169856, upload-time = "2025-01-19T21:00:07.192Z" }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/17/b1/1ffdb2680c64e9c3921d99db460546194c40d4acbef999a18c37aa4d58a3/websockets-14.2-cp313-cp313-win32.whl", hash = "sha256:a9e72fb63e5f3feacdcf5b4ff53199ec8c18d66e325c34ee4c551ca748623bbc", size = 163974, upload-time = "2025-01-19T21:00:08.698Z" }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/14/13/8b7fc4cb551b9cfd9890f0fd66e53c18a06240319915533b033a56a3d520/websockets-14.2-cp313-cp313-win_amd64.whl", hash = "sha256:b439ea828c4ba99bb3176dc8d9b933392a2413c0f6b149fdcba48393f573377f", size = 164420, upload-time = "2025-01-19T21:00:10.182Z" }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/10/3d/91d3d2bb1325cd83e8e2c02d0262c7d4426dc8fa0831ef1aa4d6bf2041af/websockets-14.2-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:d7d9cafbccba46e768be8a8ad4635fa3eae1ffac4c6e7cb4eb276ba41297ed29", size = 160773, upload-time = "2025-01-19T21:00:32.225Z" }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/33/7c/cdedadfef7381939577858b1b5718a4ab073adbb584e429dd9d9dc9bfe16/websockets-14.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:c76193c1c044bd1e9b3316dcc34b174bbf9664598791e6fb606d8d29000e070c", size = 161007, upload-time = "2025-01-19T21:00:33.784Z" }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ca/35/7a20a3c450b27c04e50fbbfc3dfb161ed8e827b2a26ae31c4b59b018b8c6/websockets-14.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fd475a974d5352390baf865309fe37dec6831aafc3014ffac1eea99e84e83fc2", size = 162264, upload-time = "2025-01-19T21:00:35.255Z" }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e8/9c/e3f9600564b0c813f2448375cf28b47dc42c514344faed3a05d71fb527f9/websockets-14.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2c6c0097a41968b2e2b54ed3424739aab0b762ca92af2379f152c1aef0187e1c", size = 161873, upload-time = "2025-01-19T21:00:37.377Z" }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3f/37/260f189b16b2b8290d6ae80c9f96d8b34692cf1bb3475df54c38d3deb57d/websockets-14.2-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d7ff794c8b36bc402f2e07c0b2ceb4a2424147ed4785ff03e2a7af03711d60a", size = 161818, upload-time = "2025-01-19T21:00:38.952Z" }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ff/1e/e47dedac8bf7140e59aa6a679e850c4df9610ae844d71b6015263ddea37b/websockets-14.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:dec254fcabc7bd488dab64846f588fc5b6fe0d78f641180030f8ea27b76d72c3", size = 164465, upload-time = "2025-01-19T21:00:40.456Z" }, - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/7b/c8/d529f8a32ce40d98309f4470780631e971a5a842b60aec864833b3615786/websockets-14.2-py3-none-any.whl", hash = "sha256:7a6ceec4ea84469f15cf15807a747e9efe57e369c384fa86e022b3bea679b79b", size = 157416, upload-time = "2025-01-19T21:00:54.843Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/20/74/221f58decd852f4b59cc3354cccaf87e8ef695fede361d03dc9a7396573b/websockets-16.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:04cdd5d2d1dacbad0a7bf36ccbcd3ccd5a30ee188f2560b7a62a30d14107b31a", size = 177343, upload-time = "2026-01-10T09:22:21.28Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/19/0f/22ef6107ee52ab7f0b710d55d36f5a5d3ef19e8a205541a6d7ffa7994e5a/websockets-16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8ff32bb86522a9e5e31439a58addbb0166f0204d64066fb955265c4e214160f0", size = 175021, upload-time = "2026-01-10T09:22:22.696Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/10/40/904a4cb30d9b61c0e278899bf36342e9b0208eb3c470324a9ecbaac2a30f/websockets-16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:583b7c42688636f930688d712885cf1531326ee05effd982028212ccc13e5957", size = 175320, upload-time = "2026-01-10T09:22:23.94Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9d/2f/4b3ca7e106bc608744b1cdae041e005e446124bebb037b18799c2d356864/websockets-16.0-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7d837379b647c0c4c2355c2499723f82f1635fd2c26510e1f587d89bc2199e72", size = 183815, upload-time = "2026-01-10T09:22:25.469Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/86/26/d40eaa2a46d4302becec8d15b0fc5e45bdde05191e7628405a19cf491ccd/websockets-16.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:df57afc692e517a85e65b72e165356ed1df12386ecb879ad5693be08fac65dde", size = 185054, upload-time = "2026-01-10T09:22:27.101Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b0/ba/6500a0efc94f7373ee8fefa8c271acdfd4dca8bd49a90d4be7ccabfc397e/websockets-16.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:2b9f1e0d69bc60a4a87349d50c09a037a2607918746f07de04df9e43252c77a3", size = 184565, upload-time = "2026-01-10T09:22:28.293Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/04/b4/96bf2cee7c8d8102389374a2616200574f5f01128d1082f44102140344cc/websockets-16.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:335c23addf3d5e6a8633f9f8eda77efad001671e80b95c491dd0924587ece0b3", size = 183848, upload-time = "2026-01-10T09:22:30.394Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/02/8e/81f40fb00fd125357814e8c3025738fc4ffc3da4b6b4a4472a82ba304b41/websockets-16.0-cp310-cp310-win32.whl", hash = "sha256:37b31c1623c6605e4c00d466c9d633f9b812ea430c11c8a278774a1fde1acfa9", size = 178249, upload-time = "2026-01-10T09:22:32.083Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b4/5f/7e40efe8df57db9b91c88a43690ac66f7b7aa73a11aa6a66b927e44f26fa/websockets-16.0-cp310-cp310-win_amd64.whl", hash = "sha256:8e1dab317b6e77424356e11e99a432b7cb2f3ec8c5ab4dabbcee6add48f72b35", size = 178685, upload-time = "2026-01-10T09:22:33.345Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f2/db/de907251b4ff46ae804ad0409809504153b3f30984daf82a1d84a9875830/websockets-16.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:31a52addea25187bde0797a97d6fc3d2f92b6f72a9370792d65a6e84615ac8a8", size = 177340, upload-time = "2026-01-10T09:22:34.539Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f3/fa/abe89019d8d8815c8781e90d697dec52523fb8ebe308bf11664e8de1877e/websockets-16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:417b28978cdccab24f46400586d128366313e8a96312e4b9362a4af504f3bbad", size = 175022, upload-time = "2026-01-10T09:22:36.332Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/58/5d/88ea17ed1ded2079358b40d31d48abe90a73c9e5819dbcde1606e991e2ad/websockets-16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:af80d74d4edfa3cb9ed973a0a5ba2b2a549371f8a741e0800cb07becdd20f23d", size = 175319, upload-time = "2026-01-10T09:22:37.602Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d2/ae/0ee92b33087a33632f37a635e11e1d99d429d3d323329675a6022312aac2/websockets-16.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:08d7af67b64d29823fed316505a89b86705f2b7981c07848fb5e3ea3020c1abe", size = 184631, upload-time = "2026-01-10T09:22:38.789Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c8/c5/27178df583b6c5b31b29f526ba2da5e2f864ecc79c99dae630a85d68c304/websockets-16.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7be95cfb0a4dae143eaed2bcba8ac23f4892d8971311f1b06f3c6b78952ee70b", size = 185870, upload-time = "2026-01-10T09:22:39.893Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/87/05/536652aa84ddc1c018dbb7e2c4cbcd0db884580bf8e95aece7593fde526f/websockets-16.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d6297ce39ce5c2e6feb13c1a996a2ded3b6832155fcfc920265c76f24c7cceb5", size = 185361, upload-time = "2026-01-10T09:22:41.016Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6d/e2/d5332c90da12b1e01f06fb1b85c50cfc489783076547415bf9f0a659ec19/websockets-16.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1c1b30e4f497b0b354057f3467f56244c603a79c0d1dafce1d16c283c25f6e64", size = 184615, upload-time = "2026-01-10T09:22:42.442Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/77/fb/d3f9576691cae9253b51555f841bc6600bf0a983a461c79500ace5a5b364/websockets-16.0-cp311-cp311-win32.whl", hash = "sha256:5f451484aeb5cafee1ccf789b1b66f535409d038c56966d6101740c1614b86c6", size = 178246, upload-time = "2026-01-10T09:22:43.654Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/54/67/eaff76b3dbaf18dcddabc3b8c1dba50b483761cccff67793897945b37408/websockets-16.0-cp311-cp311-win_amd64.whl", hash = "sha256:8d7f0659570eefb578dacde98e24fb60af35350193e4f56e11190787bee77dac", size = 178684, upload-time = "2026-01-10T09:22:44.941Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/84/7b/bac442e6b96c9d25092695578dda82403c77936104b5682307bd4deb1ad4/websockets-16.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:71c989cbf3254fbd5e84d3bff31e4da39c43f884e64f2551d14bb3c186230f00", size = 177365, upload-time = "2026-01-10T09:22:46.787Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b0/fe/136ccece61bd690d9c1f715baaeefd953bb2360134de73519d5df19d29ca/websockets-16.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:8b6e209ffee39ff1b6d0fa7bfef6de950c60dfb91b8fcead17da4ee539121a79", size = 175038, upload-time = "2026-01-10T09:22:47.999Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/40/1e/9771421ac2286eaab95b8575b0cb701ae3663abf8b5e1f64f1fd90d0a673/websockets-16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:86890e837d61574c92a97496d590968b23c2ef0aeb8a9bc9421d174cd378ae39", size = 175328, upload-time = "2026-01-10T09:22:49.809Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/18/29/71729b4671f21e1eaa5d6573031ab810ad2936c8175f03f97f3ff164c802/websockets-16.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9b5aca38b67492ef518a8ab76851862488a478602229112c4b0d58d63a7a4d5c", size = 184915, upload-time = "2026-01-10T09:22:51.071Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/97/bb/21c36b7dbbafc85d2d480cd65df02a1dc93bf76d97147605a8e27ff9409d/websockets-16.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e0334872c0a37b606418ac52f6ab9cfd17317ac26365f7f65e203e2d0d0d359f", size = 186152, upload-time = "2026-01-10T09:22:52.224Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/4a/34/9bf8df0c0cf88fa7bfe36678dc7b02970c9a7d5e065a3099292db87b1be2/websockets-16.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a0b31e0b424cc6b5a04b8838bbaec1688834b2383256688cf47eb97412531da1", size = 185583, upload-time = "2026-01-10T09:22:53.443Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/47/88/4dd516068e1a3d6ab3c7c183288404cd424a9a02d585efbac226cb61ff2d/websockets-16.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:485c49116d0af10ac698623c513c1cc01c9446c058a4e61e3bf6c19dff7335a2", size = 184880, upload-time = "2026-01-10T09:22:55.033Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/91/d6/7d4553ad4bf1c0421e1ebd4b18de5d9098383b5caa1d937b63df8d04b565/websockets-16.0-cp312-cp312-win32.whl", hash = "sha256:eaded469f5e5b7294e2bdca0ab06becb6756ea86894a47806456089298813c89", size = 178261, upload-time = "2026-01-10T09:22:56.251Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c3/f0/f3a17365441ed1c27f850a80b2bc680a0fa9505d733fe152fdf5e98c1c0b/websockets-16.0-cp312-cp312-win_amd64.whl", hash = "sha256:5569417dc80977fc8c2d43a86f78e0a5a22fee17565d78621b6bb264a115d4ea", size = 178693, upload-time = "2026-01-10T09:22:57.478Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/cc/9c/baa8456050d1c1b08dd0ec7346026668cbc6f145ab4e314d707bb845bf0d/websockets-16.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:878b336ac47938b474c8f982ac2f7266a540adc3fa4ad74ae96fea9823a02cc9", size = 177364, upload-time = "2026-01-10T09:22:59.333Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/7e/0c/8811fc53e9bcff68fe7de2bcbe75116a8d959ac699a3200f4847a8925210/websockets-16.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:52a0fec0e6c8d9a784c2c78276a48a2bdf099e4ccc2a4cad53b27718dbfd0230", size = 175039, upload-time = "2026-01-10T09:23:01.171Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/aa/82/39a5f910cb99ec0b59e482971238c845af9220d3ab9fa76dd9162cda9d62/websockets-16.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e6578ed5b6981005df1860a56e3617f14a6c307e6a71b4fff8c48fdc50f3ed2c", size = 175323, upload-time = "2026-01-10T09:23:02.341Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/bd/28/0a25ee5342eb5d5f297d992a77e56892ecb65e7854c7898fb7d35e9b33bd/websockets-16.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:95724e638f0f9c350bb1c2b0a7ad0e83d9cc0c9259f3ea94e40d7b02a2179ae5", size = 184975, upload-time = "2026-01-10T09:23:03.756Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f9/66/27ea52741752f5107c2e41fda05e8395a682a1e11c4e592a809a90c6a506/websockets-16.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c0204dc62a89dc9d50d682412c10b3542d748260d743500a85c13cd1ee4bde82", size = 186203, upload-time = "2026-01-10T09:23:05.01Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/37/e5/8e32857371406a757816a2b471939d51c463509be73fa538216ea52b792a/websockets-16.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:52ac480f44d32970d66763115edea932f1c5b1312de36df06d6b219f6741eed8", size = 185653, upload-time = "2026-01-10T09:23:06.301Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9b/67/f926bac29882894669368dc73f4da900fcdf47955d0a0185d60103df5737/websockets-16.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6e5a82b677f8f6f59e8dfc34ec06ca6b5b48bc4fcda346acd093694cc2c24d8f", size = 184920, upload-time = "2026-01-10T09:23:07.492Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3c/a1/3d6ccdcd125b0a42a311bcd15a7f705d688f73b2a22d8cf1c0875d35d34a/websockets-16.0-cp313-cp313-win32.whl", hash = "sha256:abf050a199613f64c886ea10f38b47770a65154dc37181bfaff70c160f45315a", size = 178255, upload-time = "2026-01-10T09:23:09.245Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6b/ae/90366304d7c2ce80f9b826096a9e9048b4bb760e44d3b873bb272cba696b/websockets-16.0-cp313-cp313-win_amd64.whl", hash = "sha256:3425ac5cf448801335d6fdc7ae1eb22072055417a96cc6b31b3861f455fbc156", size = 178689, upload-time = "2026-01-10T09:23:10.483Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f3/1d/e88022630271f5bd349ed82417136281931e558d628dd52c4d8621b4a0b2/websockets-16.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8cc451a50f2aee53042ac52d2d053d08bf89bcb31ae799cb4487587661c038a0", size = 177406, upload-time = "2026-01-10T09:23:12.178Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f2/78/e63be1bf0724eeb4616efb1ae1c9044f7c3953b7957799abb5915bffd38e/websockets-16.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:daa3b6ff70a9241cf6c7fc9e949d41232d9d7d26fd3522b1ad2b4d62487e9904", size = 175085, upload-time = "2026-01-10T09:23:13.511Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/bb/f4/d3c9220d818ee955ae390cf319a7c7a467beceb24f05ee7aaaa2414345ba/websockets-16.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:fd3cb4adb94a2a6e2b7c0d8d05cb94e6f1c81a0cf9dc2694fb65c7e8d94c42e4", size = 175328, upload-time = "2026-01-10T09:23:14.727Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/63/bc/d3e208028de777087e6fb2b122051a6ff7bbcca0d6df9d9c2bf1dd869ae9/websockets-16.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:781caf5e8eee67f663126490c2f96f40906594cb86b408a703630f95550a8c3e", size = 185044, upload-time = "2026-01-10T09:23:15.939Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ad/6e/9a0927ac24bd33a0a9af834d89e0abc7cfd8e13bed17a86407a66773cc0e/websockets-16.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:caab51a72c51973ca21fa8a18bd8165e1a0183f1ac7066a182ff27107b71e1a4", size = 186279, upload-time = "2026-01-10T09:23:17.148Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b9/ca/bf1c68440d7a868180e11be653c85959502efd3a709323230314fda6e0b3/websockets-16.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:19c4dc84098e523fd63711e563077d39e90ec6702aff4b5d9e344a60cb3c0cb1", size = 185711, upload-time = "2026-01-10T09:23:18.372Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c4/f8/fdc34643a989561f217bb477cbc47a3a07212cbda91c0e4389c43c296ebf/websockets-16.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:a5e18a238a2b2249c9a9235466b90e96ae4795672598a58772dd806edc7ac6d3", size = 184982, upload-time = "2026-01-10T09:23:19.652Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/dd/d1/574fa27e233764dbac9c52730d63fcf2823b16f0856b3329fc6268d6ae4f/websockets-16.0-cp314-cp314-win32.whl", hash = "sha256:a069d734c4a043182729edd3e9f247c3b2a4035415a9172fd0f1b71658a320a8", size = 177915, upload-time = "2026-01-10T09:23:21.458Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8a/f1/ae6b937bf3126b5134ce1f482365fde31a357c784ac51852978768b5eff4/websockets-16.0-cp314-cp314-win_amd64.whl", hash = "sha256:c0ee0e63f23914732c6d7e0cce24915c48f3f1512ec1d079ed01fc629dab269d", size = 178381, upload-time = "2026-01-10T09:23:22.715Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/06/9b/f791d1db48403e1f0a27577a6beb37afae94254a8c6f08be4a23e4930bc0/websockets-16.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:a35539cacc3febb22b8f4d4a99cc79b104226a756aa7400adc722e83b0d03244", size = 177737, upload-time = "2026-01-10T09:23:24.523Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/bd/40/53ad02341fa33b3ce489023f635367a4ac98b73570102ad2cdd770dacc9a/websockets-16.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:b784ca5de850f4ce93ec85d3269d24d4c82f22b7212023c974c401d4980ebc5e", size = 175268, upload-time = "2026-01-10T09:23:25.781Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/74/9b/6158d4e459b984f949dcbbb0c5d270154c7618e11c01029b9bbd1bb4c4f9/websockets-16.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:569d01a4e7fba956c5ae4fc988f0d4e187900f5497ce46339c996dbf24f17641", size = 175486, upload-time = "2026-01-10T09:23:27.033Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e5/2d/7583b30208b639c8090206f95073646c2c9ffd66f44df967981a64f849ad/websockets-16.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:50f23cdd8343b984957e4077839841146f67a3d31ab0d00e6b824e74c5b2f6e8", size = 185331, upload-time = "2026-01-10T09:23:28.259Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/45/b0/cce3784eb519b7b5ad680d14b9673a31ab8dcb7aad8b64d81709d2430aa8/websockets-16.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:152284a83a00c59b759697b7f9e9cddf4e3c7861dd0d964b472b70f78f89e80e", size = 186501, upload-time = "2026-01-10T09:23:29.449Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/19/60/b8ebe4c7e89fb5f6cdf080623c9d92789a53636950f7abacfc33fe2b3135/websockets-16.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:bc59589ab64b0022385f429b94697348a6a234e8ce22544e3681b2e9331b5944", size = 186062, upload-time = "2026-01-10T09:23:31.368Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/88/a8/a080593f89b0138b6cba1b28f8df5673b5506f72879322288b031337c0b8/websockets-16.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:32da954ffa2814258030e5a57bc73a3635463238e797c7375dc8091327434206", size = 185356, upload-time = "2026-01-10T09:23:32.627Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c2/b6/b9afed2afadddaf5ebb2afa801abf4b0868f42f8539bfe4b071b5266c9fe/websockets-16.0-cp314-cp314t-win32.whl", hash = "sha256:5a4b4cc550cb665dd8a47f868c8d04c8230f857363ad3c9caf7a0c3bf8c61ca6", size = 178085, upload-time = "2026-01-10T09:23:33.816Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9f/3e/28135a24e384493fa804216b79a6a6759a38cc4ff59118787b9fb693df93/websockets-16.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b14dc141ed6d2dde437cddb216004bcac6a1df0935d79656387bd41632ba0bbd", size = 178531, upload-time = "2026-01-10T09:23:35.016Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/72/07/c98a68571dcf256e74f1f816b8cc5eae6eb2d3d5cfa44d37f801619d9166/websockets-16.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:349f83cd6c9a415428ee1005cadb5c2c56f4389bc06a9af16103c3bc3dcc8b7d", size = 174947, upload-time = "2026-01-10T09:23:36.166Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/7e/52/93e166a81e0305b33fe416338be92ae863563fe7bce446b0f687b9df5aea/websockets-16.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:4a1aba3340a8dca8db6eb5a7986157f52eb9e436b74813764241981ca4888f03", size = 175260, upload-time = "2026-01-10T09:23:37.409Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/56/0c/2dbf513bafd24889d33de2ff0368190a0e69f37bcfa19009ef819fe4d507/websockets-16.0-pp311-pypy311_pp73-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f4a32d1bd841d4bcbffdcb3d2ce50c09c3909fbead375ab28d0181af89fd04da", size = 176071, upload-time = "2026-01-10T09:23:39.158Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a5/8f/aea9c71cc92bf9b6cc0f7f70df8f0b420636b6c96ef4feee1e16f80f75dd/websockets-16.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0298d07ee155e2e9fda5be8a9042200dd2e3bb0b8a38482156576f863a9d457c", size = 176968, upload-time = "2026-01-10T09:23:41.031Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9a/3f/f70e03f40ffc9a30d817eef7da1be72ee4956ba8d7255c399a01b135902a/websockets-16.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:a653aea902e0324b52f1613332ddf50b00c06fdaf7e92624fbf8c77c78fa5767", size = 178735, upload-time = "2026-01-10T09:23:42.259Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6f/28/258ebab549c2bf3e64d2b0217b973467394a9cea8c42f70418ca2c5d0d2e/websockets-16.0-py3-none-any.whl", hash = "sha256:1637db62fad1dc833276dded54215f2c7fa46912301a24bd94d45d46a011ceec", size = 171598, upload-time = "2026-01-10T09:23:45.395Z" }, ] [[package]] diff --git a/frontend/package.json b/frontend/package.json index 770ae007..d426ecf8 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -63,10 +63,6 @@ }, "dependencies": { "@element-plus/icons-vue": "^2.3.1", - "@vue-flow/background": "^1.3.2", - "@vue-flow/controls": "^1.1.3", - "@vue-flow/core": "^1.48.1", - "@vue-flow/minimap": "^1.5.4", "@vueuse/core": "^13.5.0", "@wangeditor-next/editor": "^5.6.49", "@wangeditor-next/editor-for-vue": "^5.1.14", diff --git a/frontend/src/api/module_task/workflow.ts b/frontend/src/api/module_task/workflow.ts deleted file mode 100644 index 3c4201cf..00000000 --- a/frontend/src/api/module_task/workflow.ts +++ /dev/null @@ -1,112 +0,0 @@ -import request from "@/utils/request"; - -const API_PATH = "/task/workflow"; - -const WorkflowAPI = { - getWorkflowList(query: WorkflowPageQuery) { - return request>>({ - url: `${API_PATH}/list`, - method: "get", - params: query, - }); - }, - - getWorkflowDetail(query: number) { - return request>({ - url: `${API_PATH}/detail/${query}`, - method: "get", - }); - }, - - createWorkflow(body: WorkflowForm) { - return request>({ - url: `${API_PATH}/create`, - method: "post", - data: body, - }); - }, - - updateWorkflow(id: number, body: WorkflowForm) { - return request>({ - url: `${API_PATH}/update/${id}`, - method: "put", - data: body, - }); - }, - - deleteWorkflow(body: number[]) { - return request({ - url: `${API_PATH}/delete`, - method: "delete", - data: body, - }); - }, - - publishWorkflow(id: number, body: WorkflowPublishForm) { - return request>({ - url: `${API_PATH}/publish/${id}`, - method: "post", - data: body, - }); - }, - - executeWorkflow(body: WorkflowExecuteForm) { - return request>({ - url: `${API_PATH}/execute`, - method: "post", - data: body, - }); - }, -}; - -export default WorkflowAPI; -export { WorkflowAPI }; - -export interface WorkflowPageQuery extends PageQuery { - name?: string; - code?: string; - status?: string; - created_time?: string[]; - updated_time?: string[]; - created_id?: number; - updated_id?: number; -} - -export interface WorkflowTable extends BaseType { - name?: string; - code?: string; - status?: string; - description?: string; - nodes?: any[]; - edges?: any[]; - created_by?: CommonType; - updated_by?: CommonType; -} - -export interface WorkflowForm extends BaseFormType { - name?: string; - code?: string; - status?: string; - description?: string; - nodes?: any[]; - edges?: any[]; -} - -export interface WorkflowPublishForm {} - -export interface WorkflowExecuteForm { - workflow_id: number; - variables?: Record; - business_key?: string; - job_id?: number; -} - -export interface WorkflowExecuteResult { - workflow_id: number; - workflow_name: string; - status: string; - start_time?: string; - end_time?: string; - variables?: Record; - node_results?: Record; -} diff --git a/frontend/src/composables/index.ts b/frontend/src/composables/index.ts index 99d083f1..9dec97e5 100644 --- a/frontend/src/composables/index.ts +++ b/frontend/src/composables/index.ts @@ -2,9 +2,3 @@ // AI 相关 export { useAiAction } from "./ai/useAiAction"; export type { UseAiActionOptions, AiActionHandler } from "./ai/useAiAction"; - -// 任务相关 -export { useDebounce, useThrottle } from "./task/usePerformance"; -export { useNodeDrag } from "./task/useNodeDrag"; -export { useNodeOperations } from "./task/useNodeOperations"; -export { useWorkflowHistory } from "./task/useWorkflowHistory"; diff --git a/frontend/src/composables/task/useNodeDrag.ts b/frontend/src/composables/task/useNodeDrag.ts deleted file mode 100644 index cfec5398..00000000 --- a/frontend/src/composables/task/useNodeDrag.ts +++ /dev/null @@ -1,119 +0,0 @@ -import { Ref, ref } from "vue"; - -interface DragItem { - id: string; - data: { - label: string; - type: string; - args?: string; - kwargs?: string; - nodeId?: number; - category?: string; - }; - type: string; - position: { x: number; y: number }; - class?: string; -} - -interface NodeItem { - id?: number; - type: string; - name: string; - icon?: string; - color?: string; - class?: string; - args?: string; - kwargs?: string; -} - -interface Coordinate { - x: number; - y: number; -} - -interface ScreenToFlowCoordinate { - (coordinate: Coordinate): Coordinate; -} - -interface OnNodesInitialized { - (callback: () => void): { off: () => void }; -} - -interface UpdateNode { - (nodeId: string, updater: (node: any) => any): void; -} - -interface AddNodes { - (nodes: any): void; -} - -export function useNodeDrag() { - const dragItem: Ref = ref(null); - - function onDragStart(event: DragEvent, item: NodeItem) { - dragItem.value = { - id: `node-${Date.now()}`, - data: { - label: item.name, - type: item.type, - args: item.args || "", - kwargs: item.kwargs || "{}", - nodeId: item.id, - category: (item as any).category, - }, - type: item.type, - position: { x: 0, y: 0 }, - class: item.class || "light", - }; - } - - function onDragEnd() { - dragItem.value = null; - } - - function onDragOver(event: DragEvent) { - event.preventDefault(); - } - - function onDrop( - event: DragEvent, - screenToFlowCoordinate: ScreenToFlowCoordinate, - onNodesInitialized: OnNodesInitialized, - updateNode: UpdateNode, - addNodes: AddNodes - ) { - if (!dragItem.value) return; - - const position = screenToFlowCoordinate({ - x: event.clientX, - y: event.clientY, - }); - - const newNode = { - ...dragItem.value, - position, - }; - - const { off } = onNodesInitialized(() => { - updateNode(dragItem.value?.id || "", (node: any) => ({ - position: { - x: node.position.x - node.dimensions.width / 2, - y: node.position.y - node.dimensions.height / 2, - }, - })); - - off(); - }); - - dragItem.value = null; - addNodes(newNode); - } - - return { - dragItem, - onDragStart, - onDragEnd, - onDragOver, - onDrop, - }; -} diff --git a/frontend/src/composables/task/useNodeOperations.ts b/frontend/src/composables/task/useNodeOperations.ts deleted file mode 100644 index 263c4b2f..00000000 --- a/frontend/src/composables/task/useNodeOperations.ts +++ /dev/null @@ -1,164 +0,0 @@ -import { Ref, ref } from "vue"; - -interface WorkflowNode { - id: string; - type: string; - position: { x: number; y: number }; - data: any; - [key: string]: any; -} - -interface WorkflowEdge { - id: string; - source: string; - target: string; - label?: string; - type?: string; - animated?: boolean; - style?: any; - data?: any; - [key: string]: any; -} - -interface EdgeData { - label: string; - type: string; - animated: boolean; - color: string; - strokeWidth: number; - condition?: string; - description?: string; -} - -interface GetNodes { - (): WorkflowNode[]; -} - -interface SetNodes { - (nodes: WorkflowNode[]): void; -} - -interface GetEdges { - (): WorkflowEdge[]; -} - -interface SetEdges { - (edges: WorkflowEdge[]): void; -} - -interface AddNodes { - (nodes: WorkflowNode | WorkflowNode[]): void; -} - -export function useNodeOperations() { - const clipboard: Ref = ref(null); - - function copyNode(node: WorkflowNode | null): boolean { - if (node?.id) { - clipboard.value = JSON.parse(JSON.stringify(node)); - return true; - } - return false; - } - - function pasteNode(addNodes: AddNodes): WorkflowNode | null { - if (clipboard.value) { - const newNode: WorkflowNode = { - ...clipboard.value, - id: `node-${Date.now()}`, - position: { - x: clipboard.value.position.x + 50, - y: clipboard.value.position.y + 50, - }, - }; - addNodes(newNode); - return newNode; - } - return null; - } - - function deleteNode( - nodeId: string, - getNodes: GetNodes, - setNodes: SetNodes, - getEdges: GetEdges, - setEdges: SetEdges - ): boolean { - if (!nodeId) return false; - - setNodes(getNodes().filter((n) => n.id !== nodeId)); - setEdges(getEdges().filter((e) => e.source !== nodeId && e.target !== nodeId)); - return true; - } - - function updateNodeData( - nodeId: string, - data: any, - getNodes: GetNodes, - setNodes: SetNodes - ): boolean { - if (!nodeId) return false; - - const allNodes = getNodes(); - const targetNode = allNodes.find((n) => n.id === nodeId); - if (!targetNode) return false; - - setNodes([ - ...allNodes.filter((n) => n.id !== nodeId), - { - ...targetNode, - data: { ...targetNode.data, ...data }, - }, - ]); - return true; - } - - function deleteEdge(edgeId: string, getEdges: GetEdges, setEdges: SetEdges): boolean { - if (!edgeId) return false; - - setEdges(getEdges().filter((e) => e.id !== edgeId)); - return true; - } - - function updateEdgeData( - edgeId: string, - data: EdgeData, - getEdges: GetEdges, - setEdges: SetEdges - ): boolean { - if (!edgeId) return false; - - const allEdges = getEdges(); - const targetEdge = allEdges.find((e) => e.id === edgeId); - if (!targetEdge) return false; - - setEdges([ - ...allEdges.filter((e) => e.id !== edgeId), - { - ...targetEdge, - label: data.label, - type: data.type, - animated: data.animated, - style: { - stroke: data.color, - strokeWidth: data.strokeWidth, - }, - data: { - condition: data.condition, - description: data.description, - }, - }, - ]); - return true; - } - - return { - clipboard, - copyNode, - pasteNode, - deleteNode, - updateNodeData, - deleteEdge, - updateEdgeData, - }; -} diff --git a/frontend/src/composables/task/usePerformance.ts b/frontend/src/composables/task/usePerformance.ts deleted file mode 100644 index 929ce469..00000000 --- a/frontend/src/composables/task/usePerformance.ts +++ /dev/null @@ -1,31 +0,0 @@ -export function useDebounce any>( - fn: T, - delay: number = 300 -): (...args: Parameters) => void { - let timeoutId: NodeJS.Timeout | null = null; - - return function (this: any, ...args: Parameters) { - if (timeoutId) { - clearTimeout(timeoutId); - } - - timeoutId = setTimeout(() => { - fn.apply(this, args); - }, delay); - }; -} - -export function useThrottle any>( - fn: T, - delay: number = 300 -): (...args: Parameters) => void { - let lastCall: number = 0; - - return function (this: any, ...args: Parameters) { - const now = Date.now(); - if (now - lastCall >= delay) { - lastCall = now; - fn.apply(this, args); - } - }; -} diff --git a/frontend/src/composables/task/useWorkflowHistory.ts b/frontend/src/composables/task/useWorkflowHistory.ts deleted file mode 100644 index d7d65a72..00000000 --- a/frontend/src/composables/task/useWorkflowHistory.ts +++ /dev/null @@ -1,106 +0,0 @@ -import { Ref, ref } from "vue"; - -interface WorkflowNode { - id: string; - type: string; - position: { x: number; y: number }; - data: any; - [key: string]: any; -} - -interface WorkflowEdge { - id: string; - source: string; - target: string; - label?: string; - type?: string; - animated?: boolean; - style?: any; - data?: any; - [key: string]: any; -} - -interface WorkflowState { - nodes: WorkflowNode[]; - edges: WorkflowEdge[]; -} - -export function useWorkflowHistory(maxHistory = 50) { - const history: Ref = ref([]); - const historyIndex: Ref = ref(-1); - - function saveToHistory(nodes: WorkflowNode[], edges: WorkflowEdge[]) { - const state: WorkflowState = { - nodes: JSON.parse(JSON.stringify(nodes)), - edges: JSON.parse(JSON.stringify(edges)), - }; - - if (historyIndex.value < history.value.length - 1) { - history.value = history.value.slice(0, historyIndex.value + 1); - } - - history.value.push(state); - - if (history.value.length > maxHistory) { - history.value.shift(); - } else { - historyIndex.value++; - } - } - - function undo(): WorkflowState | null { - if (historyIndex.value > 0) { - historyIndex.value--; - const state = history.value[historyIndex.value]; - return { - nodes: JSON.parse(JSON.stringify(state.nodes)), - edges: JSON.parse(JSON.stringify(state.edges)), - }; - } - return null; - } - - function redo(): WorkflowState | null { - if (historyIndex.value < history.value.length - 1) { - historyIndex.value++; - const state = history.value[historyIndex.value]; - return { - nodes: JSON.parse(JSON.stringify(state.nodes)), - edges: JSON.parse(JSON.stringify(state.edges)), - }; - } - return null; - } - - function clear() { - history.value = []; - historyIndex.value = -1; - } - - function canUndo(): boolean { - return historyIndex.value > 0; - } - - function canRedo(): boolean { - return historyIndex.value < history.value.length - 1; - } - - function getCurrentState(): WorkflowState | null { - if (historyIndex.value >= 0 && historyIndex.value < history.value.length) { - return history.value[historyIndex.value]; - } - return null; - } - - return { - history, - historyIndex, - saveToHistory, - undo, - redo, - clear, - canUndo, - canRedo, - getCurrentState, - }; -} diff --git a/frontend/src/views/module_task/workflow/components/DynamicNode.vue b/frontend/src/views/module_task/workflow/components/DynamicNode.vue deleted file mode 100644 index 1c0add3c..00000000 --- a/frontend/src/views/module_task/workflow/components/DynamicNode.vue +++ /dev/null @@ -1,174 +0,0 @@ - - - - - diff --git a/frontend/src/views/module_task/workflow/components/EdgeConfigPanel.vue b/frontend/src/views/module_task/workflow/components/EdgeConfigPanel.vue deleted file mode 100644 index 624ba551..00000000 --- a/frontend/src/views/module_task/workflow/components/EdgeConfigPanel.vue +++ /dev/null @@ -1,167 +0,0 @@ - - - - - diff --git a/frontend/src/views/module_task/workflow/components/NodeConfigPanel.vue b/frontend/src/views/module_task/workflow/components/NodeConfigPanel.vue deleted file mode 100644 index b8d1c140..00000000 --- a/frontend/src/views/module_task/workflow/components/NodeConfigPanel.vue +++ /dev/null @@ -1,217 +0,0 @@ - - - - - diff --git a/frontend/src/views/module_task/workflow/components/WorkflowDesignDrawer.vue b/frontend/src/views/module_task/workflow/components/WorkflowDesignDrawer.vue deleted file mode 100644 index b2282d27..00000000 --- a/frontend/src/views/module_task/workflow/components/WorkflowDesignDrawer.vue +++ /dev/null @@ -1,854 +0,0 @@ - - - - - diff --git a/frontend/src/views/module_task/workflow/index.vue b/frontend/src/views/module_task/workflow/index.vue deleted file mode 100644 index c18c157d..00000000 --- a/frontend/src/views/module_task/workflow/index.vue +++ /dev/null @@ -1,376 +0,0 @@ - - - - - diff --git a/frontend/src/views/module_task/workflow/types.ts b/frontend/src/views/module_task/workflow/types.ts deleted file mode 100644 index 1330c3c0..00000000 --- a/frontend/src/views/module_task/workflow/types.ts +++ /dev/null @@ -1,162 +0,0 @@ -import type { CSSProperties } from "vue"; - -export type NodeType = - | "input" - | "output" - | "trigger" - | "action" - | "condition" - | "control" - | "integration" - | "custom"; - -export type EdgeType = "default" | "straight" | "step" | "smoothstep" | "bezier"; - -export type HandlePosition = "left" | "right" | "top" | "bottom"; - -export interface NodeConfigSchema { - type: string; - properties: Record; -} - -export interface PropertySchema { - type: "string" | "number" | "boolean" | "select" | "textarea" | "json" | "code"; - label: string; - description?: string; - default?: any; - required?: boolean; - options?: Array<{ label: string; value: any }>; - placeholder?: string; - validation?: { - min?: number; - max?: number; - pattern?: string; - }; -} - -export interface DynamicNodeData { - label: string; - nodeTypeCode: string; - config: Record; - description?: string; -} - -export interface Node { - id: string; - position: { x: number; y: number }; - type?: NodeType; - data?: DynamicNodeData; - label?: string; - style?: CSSProperties; - class?: string | string[]; - sourcePosition?: HandlePosition; - targetPosition?: HandlePosition; - hidden?: boolean; - selected?: boolean; - draggable?: boolean; - connectable?: boolean; - deletable?: boolean; - selectable?: boolean; - focusable?: boolean; - dragHandle?: string; - extent?: "parent" | [number, number] | [[number, number], [number, number]]; - parentNode?: string; - expandParent?: boolean; - zIndex?: number; -} - -export interface Edge { - id?: string; - source: string; - target: string; - sourceHandle?: string; - targetHandle?: string; - type?: EdgeType; - label?: string; - labelStyle?: CSSProperties; - labelShowBg?: boolean; - labelBgStyle?: CSSProperties; - labelBgPadding?: [number, number]; - labelBgBorderRadius?: number; - style?: CSSProperties; - class?: string | string[]; - animated?: boolean; - hidden?: boolean; - selected?: boolean; - deletable?: boolean; - selectable?: boolean; - focusable?: boolean; - updatable?: boolean | "source" | "target"; - markerStart?: Marker | string; - markerEnd?: Marker | string; - pathOptions?: { - offset?: number; - borderRadius?: number; - curvature?: number; - }; - interactionWidth?: number; -} - -export interface Marker { - type: "arrow" | "arrowclosed"; - color?: string; - width?: number; - height?: number; - orient?: "auto" | "auto-start-reverse"; -} - -export interface WorkflowTemplate { - id: string; - name: string; - description?: string; - nodes: Node[]; - edges: Edge[]; -} - -export interface WorkflowStats { - totalNodes: number; - totalEdges: number; - nodeTypes: Record; -} - -export interface NodeConfig { - id: string; - type: NodeType; - data: DynamicNodeData; -} - -export interface EdgeConfig { - id: string; - source: string; - target: string; - label?: string; - type?: EdgeType; - animated?: boolean; -} - -export interface NodeTypeDefinition { - code: string; - name: string; - category: "trigger" | "action" | "condition" | "control" | "integration" | "custom"; - description?: string; - icon?: string; - color?: string; - configSchema: NodeConfigSchema; - inputSchema?: Record; - outputSchema?: Record; - handler: string; - isSystem: boolean; - isActive: boolean; - sortOrder: number; -} - -export interface NodeTemplate { - id: string; - nodeTypeCode: string; - name: string; - description?: string; - defaultConfig: Record; - isPublic: boolean; - tags?: string[]; - thumbnail?: string; -}