Restore AI workflow design nodes
This commit is contained in:
@@ -0,0 +1,302 @@
|
||||
"""
|
||||
应用更新节点
|
||||
|
||||
执行应用配置的更新操作,将设置保存到数据库
|
||||
"""
|
||||
import json
|
||||
import logging
|
||||
from typing import Any, Dict, Optional
|
||||
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from ..base import BaseNode, NodeContext, NodeResult
|
||||
from ..registry import NodeRegistry
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@NodeRegistry.register
|
||||
class AppUpdateNode(BaseNode):
|
||||
"""
|
||||
应用更新节点
|
||||
|
||||
接收应用设置配置,执行实际的更新操作
|
||||
"""
|
||||
|
||||
node_type = 'app_update'
|
||||
node_name = '应用更新'
|
||||
node_category = 'application'
|
||||
node_icon = 'save'
|
||||
node_description = '执行应用配置的更新操作,将设置保存到数据库'
|
||||
|
||||
inputs = [
|
||||
{
|
||||
'name': 'settings',
|
||||
'type': 'object',
|
||||
'description': '应用设置配置(来自应用设置节点)',
|
||||
'required': True,
|
||||
},
|
||||
{
|
||||
'name': 'application_id',
|
||||
'type': 'string',
|
||||
'description': '应用ID,不传则更新主应用配置',
|
||||
'required': False,
|
||||
},
|
||||
]
|
||||
|
||||
outputs = [
|
||||
{
|
||||
'name': 'success',
|
||||
'type': 'boolean',
|
||||
'description': '更新是否成功',
|
||||
},
|
||||
{
|
||||
'name': 'config_id',
|
||||
'type': 'string',
|
||||
'description': '配置记录ID',
|
||||
},
|
||||
{
|
||||
'name': 'message',
|
||||
'type': 'string',
|
||||
'description': '更新结果消息',
|
||||
},
|
||||
]
|
||||
|
||||
async def execute_async(self, context: NodeContext) -> NodeResult:
|
||||
"""异步执行节点"""
|
||||
try:
|
||||
# 获取输入参数
|
||||
settings = self._get_settings(context)
|
||||
application_id = self._get_application_id(context)
|
||||
|
||||
if not settings:
|
||||
return NodeResult(
|
||||
success=False,
|
||||
error='缺少应用设置配置',
|
||||
)
|
||||
|
||||
logger.info(f'AppUpdateNode - 开始更新应用配置, application_id={application_id}')
|
||||
|
||||
# 获取数据库会话
|
||||
db: AsyncSession = context.db_session
|
||||
if not db:
|
||||
return NodeResult(
|
||||
success=False,
|
||||
error='数据库会话不可用',
|
||||
)
|
||||
|
||||
# 执行更新
|
||||
config_id = await self._update_preferences(db, settings, application_id)
|
||||
|
||||
if config_id:
|
||||
logger.info(f'AppUpdateNode - 应用配置更新成功, config_id={config_id}')
|
||||
return NodeResult(
|
||||
success=True,
|
||||
output={
|
||||
'success': True,
|
||||
'config_id': config_id,
|
||||
'message': '应用配置更新成功',
|
||||
},
|
||||
output_variables={
|
||||
'update_success': True,
|
||||
'config_id': config_id,
|
||||
'update_message': '应用配置更新成功',
|
||||
},
|
||||
)
|
||||
else:
|
||||
return NodeResult(
|
||||
success=False,
|
||||
error='应用配置更新失败',
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.exception(f'应用更新节点执行失败: {e}')
|
||||
return NodeResult(
|
||||
success=False,
|
||||
error=f'节点执行失败: {str(e)}',
|
||||
)
|
||||
|
||||
def execute(self, context: NodeContext) -> NodeResult:
|
||||
"""同步执行节点(不支持,需要数据库操作)"""
|
||||
return NodeResult(
|
||||
success=False,
|
||||
error='应用更新节点需要异步执行',
|
||||
)
|
||||
|
||||
def _get_settings(self, context: NodeContext) -> Optional[Dict[str, Any]]:
|
||||
"""获取应用设置配置"""
|
||||
import re
|
||||
|
||||
# 优先从配置中获取(支持变量引用)
|
||||
settings_config = self.config.get('settings', '')
|
||||
logger.info(f'AppUpdateNode - settings_config from config: {settings_config}')
|
||||
|
||||
if settings_config:
|
||||
# 检查是否是变量引用格式 {{node_id.key}}
|
||||
var_pattern = r'\{\{([^}]+)\}\}'
|
||||
match = re.match(var_pattern, settings_config.strip())
|
||||
|
||||
if match:
|
||||
# 是变量引用,直接从上下文获取对象
|
||||
var_ref = match.group(1).strip()
|
||||
logger.info(f'AppUpdateNode - 解析变量引用: {var_ref}')
|
||||
|
||||
# 解析 node_id.key 格式
|
||||
# 注意:node_id 可能包含连字符,如 app_settings-1769009671187
|
||||
# 所以只按最后一个点分割
|
||||
if '.' in var_ref:
|
||||
parts = var_ref.rsplit('.', 1) # 从右边分割,只分割一次
|
||||
node_id = parts[0]
|
||||
key = parts[1] if len(parts) > 1 else None
|
||||
|
||||
logger.info(f'AppUpdateNode - 查找节点输出: node_id={node_id}, key={key}')
|
||||
|
||||
# 从节点输出命名空间获取
|
||||
node_outputs = context.variables.get(f'_node_{node_id}')
|
||||
logger.info(f'AppUpdateNode - node_outputs type: {type(node_outputs)}, keys: {node_outputs.keys() if isinstance(node_outputs, dict) else "N/A"}')
|
||||
|
||||
if isinstance(node_outputs, dict) and key and key in node_outputs:
|
||||
value = node_outputs[key]
|
||||
logger.info(f'AppUpdateNode - 找到值, type: {type(value)}')
|
||||
if isinstance(value, dict):
|
||||
return value
|
||||
if isinstance(value, str):
|
||||
try:
|
||||
return json.loads(value)
|
||||
except (json.JSONDecodeError, TypeError):
|
||||
logger.warning(f'AppUpdateNode - JSON解析失败: {value[:100]}')
|
||||
pass
|
||||
else:
|
||||
logger.warning(f'AppUpdateNode - 未找到节点输出或key不存在')
|
||||
else:
|
||||
# 直接变量名
|
||||
value = context.variables.get(var_ref)
|
||||
logger.info(f'AppUpdateNode - 直接变量引用, type: {type(value)}')
|
||||
if isinstance(value, dict):
|
||||
return value
|
||||
if isinstance(value, str):
|
||||
try:
|
||||
return json.loads(value)
|
||||
except (json.JSONDecodeError, TypeError):
|
||||
pass
|
||||
else:
|
||||
# 不是变量引用,尝试解析为 JSON
|
||||
try:
|
||||
return json.loads(settings_config)
|
||||
except (json.JSONDecodeError, TypeError):
|
||||
pass
|
||||
|
||||
# 从上下文变量获取
|
||||
settings = context.variables.get('settings')
|
||||
logger.info(f'AppUpdateNode - 从上下文获取settings, type: {type(settings)}')
|
||||
if isinstance(settings, dict):
|
||||
return settings
|
||||
if isinstance(settings, str):
|
||||
try:
|
||||
return json.loads(settings)
|
||||
except (json.JSONDecodeError, TypeError):
|
||||
pass
|
||||
|
||||
logger.warning(f'AppUpdateNode - 未能获取有效的settings配置')
|
||||
return None
|
||||
|
||||
def _get_application_id(self, context: NodeContext) -> Optional[str]:
|
||||
"""获取应用ID"""
|
||||
# 优先从配置中获取
|
||||
app_id = self.config.get('application_id', '')
|
||||
if app_id:
|
||||
resolved = context.resolve_template(app_id)
|
||||
if resolved and resolved != 'main':
|
||||
return resolved
|
||||
|
||||
# 从上下文变量获取
|
||||
app_id = context.variables.get('application_id')
|
||||
if app_id and app_id != 'main':
|
||||
return app_id
|
||||
|
||||
return None
|
||||
|
||||
async def _update_preferences(
|
||||
self,
|
||||
db: AsyncSession,
|
||||
settings: Dict[str, Any],
|
||||
application_id: Optional[str] = None,
|
||||
) -> Optional[str]:
|
||||
"""更新偏好配置"""
|
||||
from core.ui_config.model import UIConfig
|
||||
|
||||
# 配置键:使用与前端 API 一致的键名
|
||||
config_key = 'frontend_preferences'
|
||||
|
||||
# 查找现有配置
|
||||
stmt = select(UIConfig).where(
|
||||
UIConfig.config_key == config_key,
|
||||
UIConfig.is_deleted == False,
|
||||
)
|
||||
|
||||
# 根据 application_id 过滤
|
||||
if application_id:
|
||||
stmt = stmt.where(UIConfig.application_id == application_id)
|
||||
else:
|
||||
stmt = stmt.where(UIConfig.application_id.is_(None))
|
||||
|
||||
result = await db.execute(stmt)
|
||||
config = result.scalar_one_or_none()
|
||||
|
||||
# 配置值
|
||||
config_value = json.dumps(settings, ensure_ascii=False)
|
||||
|
||||
if config:
|
||||
# 更新现有配置
|
||||
config.config_value = config_value
|
||||
await db.commit()
|
||||
await db.refresh(config)
|
||||
return config.id
|
||||
else:
|
||||
# 创建新配置
|
||||
new_config = UIConfig(
|
||||
application_id=application_id,
|
||||
config_key=config_key,
|
||||
config_value=config_value,
|
||||
config_type='preferences',
|
||||
description='前端UI偏好配置' if not application_id else '子应用UI偏好配置',
|
||||
status=True,
|
||||
sort=0,
|
||||
)
|
||||
db.add(new_config)
|
||||
await db.commit()
|
||||
await db.refresh(new_config)
|
||||
return new_config.id
|
||||
|
||||
@classmethod
|
||||
def get_config_schema(cls) -> Dict[str, Any]:
|
||||
"""
|
||||
获取节点配置 Schema(供前端表单渲染)
|
||||
"""
|
||||
return {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'settings': {
|
||||
'type': 'string',
|
||||
'title': '应用设置',
|
||||
'description': '应用设置配置,支持变量引用如 {{app_settings-1.settings}}',
|
||||
'required': True,
|
||||
'x-component': 'Input',
|
||||
'x-component-props': {
|
||||
'placeholder': '请输入应用设置或使用变量引用',
|
||||
},
|
||||
},
|
||||
'application_id': {
|
||||
'type': 'string',
|
||||
'title': '应用ID',
|
||||
'description': '目标应用ID,不填则更新主应用配置。支持变量引用如 {{application_id}}',
|
||||
'required': False,
|
||||
'x-component': 'Input',
|
||||
'x-component-props': {
|
||||
'placeholder': '留空则更新主应用配置',
|
||||
},
|
||||
},
|
||||
},
|
||||
'required': ['settings'],
|
||||
}
|
||||
Reference in New Issue
Block a user