Restore AI workflow design nodes
This commit is contained in:
@@ -0,0 +1,258 @@
|
||||
"""
|
||||
应用创建节点
|
||||
|
||||
创建一个新的应用(子应用)
|
||||
"""
|
||||
import logging
|
||||
from typing import Any, Dict, Optional
|
||||
|
||||
from ..base import BaseNode, NodeContext, NodeResult
|
||||
from ..registry import NodeRegistry
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@NodeRegistry.register
|
||||
class AppCreateNode(BaseNode):
|
||||
"""
|
||||
应用创建节点
|
||||
|
||||
创建一个新的应用(低代码平台的顶层容器)
|
||||
"""
|
||||
|
||||
node_type = 'app_create'
|
||||
node_name = '创建应用'
|
||||
node_category = 'application'
|
||||
node_icon = 'app-window'
|
||||
node_description = '创建一个新的应用(子应用)'
|
||||
|
||||
inputs = [
|
||||
{
|
||||
'name': 'name',
|
||||
'type': 'string',
|
||||
'description': '应用名称',
|
||||
'required': True,
|
||||
},
|
||||
{
|
||||
'name': 'code',
|
||||
'type': 'string',
|
||||
'description': '应用编码(唯一标识,用于URL路由,只能包含字母、数字、下划线和连字符,必须以字母开头)',
|
||||
'required': True,
|
||||
},
|
||||
{
|
||||
'name': 'description',
|
||||
'type': 'string',
|
||||
'description': '应用描述',
|
||||
'required': False,
|
||||
},
|
||||
{
|
||||
'name': 'icon',
|
||||
'type': 'string',
|
||||
'description': '应用图标',
|
||||
'required': False,
|
||||
},
|
||||
{
|
||||
'name': 'app_type',
|
||||
'type': 'string',
|
||||
'description': '应用类型: form-表单应用, workflow-流程应用, dashboard-数据应用, screen-大屏应用, mixed-混合应用',
|
||||
'required': False,
|
||||
},
|
||||
]
|
||||
|
||||
outputs = [
|
||||
{
|
||||
'name': 'app_id',
|
||||
'type': 'string',
|
||||
'description': '创建的应用ID',
|
||||
},
|
||||
{
|
||||
'name': 'app_code',
|
||||
'type': 'string',
|
||||
'description': '应用编码',
|
||||
},
|
||||
{
|
||||
'name': 'app_name',
|
||||
'type': 'string',
|
||||
'description': '应用名称',
|
||||
},
|
||||
{
|
||||
'name': 'success',
|
||||
'type': 'boolean',
|
||||
'description': '是否创建成功',
|
||||
},
|
||||
]
|
||||
|
||||
def execute(self, context: NodeContext) -> NodeResult:
|
||||
"""执行节点(同步方法,通过运行异步方法实现)"""
|
||||
import asyncio
|
||||
|
||||
loop = asyncio.get_event_loop()
|
||||
if loop.is_running():
|
||||
import concurrent.futures
|
||||
with concurrent.futures.ThreadPoolExecutor() as executor:
|
||||
future = executor.submit(asyncio.run, self.execute_async(context))
|
||||
return future.result()
|
||||
else:
|
||||
return loop.run_until_complete(self.execute_async(context))
|
||||
|
||||
async def execute_async(self, context: NodeContext) -> NodeResult:
|
||||
"""异步执行节点"""
|
||||
try:
|
||||
# 获取输入参数并解析变量引用
|
||||
name = context.resolve_template(self.config.get('name', ''))
|
||||
code_base = context.resolve_template(self.config.get('code', ''))
|
||||
description = context.resolve_template(self.config.get('description', ''))
|
||||
icon = context.resolve_template(self.config.get('icon', ''))
|
||||
app_type = context.resolve_template(self.config.get('app_type', 'mixed'))
|
||||
|
||||
# 配置选项
|
||||
update_if_exists = self.config.get('update_if_exists', False)
|
||||
|
||||
logger.info(f'AppCreateNode - 开始创建应用: name={name}, code={code_base}')
|
||||
|
||||
# 验证必要参数
|
||||
if not name:
|
||||
return NodeResult(
|
||||
success=False,
|
||||
error='应用名称不能为空',
|
||||
)
|
||||
|
||||
if not code_base:
|
||||
return NodeResult(
|
||||
success=False,
|
||||
error='应用编码不能为空',
|
||||
)
|
||||
|
||||
# 验证编码格式(字母开头,只能包含字母、数字、下划线和连字符)
|
||||
import re
|
||||
if not re.match(r'^[a-zA-Z][a-zA-Z0-9_-]*$', code_base):
|
||||
return NodeResult(
|
||||
success=False,
|
||||
error='应用编码格式不正确,必须以字母开头,只能包含字母、数字、下划线和连字符',
|
||||
)
|
||||
|
||||
# 导入服务和数据库会话
|
||||
from app.database import AsyncSessionLocal
|
||||
from core.application.service import ApplicationService
|
||||
from core.application.schema import ApplicationCreate, ApplicationUpdate
|
||||
|
||||
async with AsyncSessionLocal() as db:
|
||||
# 生成唯一的 code(如果已存在则自动添加后缀)
|
||||
code = await self._get_unique_code(db, code_base, ApplicationService)
|
||||
logger.info(f'AppCreateNode - 使用 code: {code} (原始: {code_base})')
|
||||
|
||||
# 检查应用编码是否已存在(理论上不应该存在,因为已经生成了唯一 code)
|
||||
existing_app = await ApplicationService.get_by_code(db, code)
|
||||
|
||||
if existing_app:
|
||||
if update_if_exists:
|
||||
# 更新已存在的应用
|
||||
update_data = ApplicationUpdate(
|
||||
name=name,
|
||||
description=description or None,
|
||||
icon=icon or None,
|
||||
app_type=app_type or None,
|
||||
)
|
||||
updated_app = await ApplicationService.update(
|
||||
db,
|
||||
record_id=existing_app.id,
|
||||
data=update_data,
|
||||
auto_commit=True
|
||||
)
|
||||
|
||||
if updated_app:
|
||||
logger.info(f'AppCreateNode - 应用已存在,更新成功: id={updated_app.id}')
|
||||
return NodeResult(
|
||||
success=True,
|
||||
output=f'应用 {name} 更新成功',
|
||||
output_variables={
|
||||
'app_id': updated_app.id,
|
||||
'app_code': updated_app.code,
|
||||
'app_name': updated_app.name,
|
||||
'success': True,
|
||||
}
|
||||
)
|
||||
else:
|
||||
return NodeResult(
|
||||
success=False,
|
||||
error=f'更新应用失败',
|
||||
)
|
||||
else:
|
||||
# 应用已存在,返回已存在的应用信息
|
||||
logger.info(f'AppCreateNode - 应用编码已存在: code={code}, id={existing_app.id}')
|
||||
return NodeResult(
|
||||
success=True,
|
||||
output=f'应用编码 {code} 已存在',
|
||||
output_variables={
|
||||
'app_id': existing_app.id,
|
||||
'app_code': existing_app.code,
|
||||
'app_name': existing_app.name,
|
||||
'success': False, # 标记为未创建新应用
|
||||
}
|
||||
)
|
||||
|
||||
# 创建新应用
|
||||
create_data = ApplicationCreate(
|
||||
name=name,
|
||||
code=code,
|
||||
description=description or '',
|
||||
icon=icon or '',
|
||||
app_type=app_type or 'mixed',
|
||||
)
|
||||
|
||||
new_app = await ApplicationService.create(db, data=create_data, auto_commit=True)
|
||||
|
||||
if new_app:
|
||||
logger.info(f'AppCreateNode - 应用创建成功: id={new_app.id}, code={new_app.code}')
|
||||
return NodeResult(
|
||||
success=True,
|
||||
output=f'应用 {name} 创建成功',
|
||||
output_variables={
|
||||
'app_id': new_app.id,
|
||||
'app_code': new_app.code,
|
||||
'app_name': new_app.name,
|
||||
'success': True,
|
||||
}
|
||||
)
|
||||
else:
|
||||
return NodeResult(
|
||||
success=False,
|
||||
error='创建应用失败',
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.exception(f'AppCreateNode - 执行异常: {e}')
|
||||
return NodeResult(
|
||||
success=False,
|
||||
error=f'创建应用异常: {str(e)}',
|
||||
)
|
||||
|
||||
async def _get_unique_code(self, db, base_code: str, service) -> str:
|
||||
"""
|
||||
生成唯一的 code,如果已存在则自动添加数字后缀
|
||||
|
||||
Args:
|
||||
db: 数据库会话
|
||||
base_code: 基础 code
|
||||
service: 服务类(需要有 get_by_code 方法)
|
||||
|
||||
Returns:
|
||||
唯一的 code
|
||||
"""
|
||||
code = base_code
|
||||
counter = 1
|
||||
|
||||
while True:
|
||||
try:
|
||||
existing = await service.get_by_code(db, code)
|
||||
if not existing:
|
||||
# code 不存在,可以使用
|
||||
break
|
||||
# code 已存在,添加后缀
|
||||
code = f"{base_code}_{counter}"
|
||||
counter += 1
|
||||
except Exception:
|
||||
# 查询出错,认为 code 可用
|
||||
break
|
||||
|
||||
return code
|
||||
Reference in New Issue
Block a user