Restore AI workflow design nodes
This commit is contained in:
@@ -0,0 +1,432 @@
|
||||
"""
|
||||
应用设置节点
|
||||
|
||||
接收应用设置配置,展示给用户确认或编辑
|
||||
包括:应用配置、Logo配置、内置主题和布局
|
||||
"""
|
||||
import json
|
||||
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 AppSettingsNode(BaseNode):
|
||||
"""
|
||||
应用设置节点
|
||||
|
||||
接收应用设置配置,展示给用户确认或编辑
|
||||
包括:应用配置、Logo配置、内置主题和布局
|
||||
"""
|
||||
|
||||
node_type = 'app_settings'
|
||||
node_name = '应用设置'
|
||||
node_category = 'application'
|
||||
node_icon = 'settings'
|
||||
node_description = '配置应用设置,包括应用信息、Logo、主题和布局'
|
||||
|
||||
inputs = [
|
||||
{
|
||||
'name': 'app_name',
|
||||
'type': 'string',
|
||||
'description': '应用名称',
|
||||
'required': False,
|
||||
},
|
||||
{
|
||||
'name': 'home_path',
|
||||
'type': 'string',
|
||||
'description': '首页路径',
|
||||
'required': False,
|
||||
},
|
||||
{
|
||||
'name': 'logo_source',
|
||||
'type': 'string',
|
||||
'description': 'Logo图片URL或文件ID',
|
||||
'required': False,
|
||||
},
|
||||
{
|
||||
'name': 'theme_builtin_type',
|
||||
'type': 'string',
|
||||
'description': '内置主题类型',
|
||||
'required': False,
|
||||
},
|
||||
{
|
||||
'name': 'theme_color_primary',
|
||||
'type': 'string',
|
||||
'description': '主题主色调',
|
||||
'required': False,
|
||||
},
|
||||
{
|
||||
'name': 'app_layout',
|
||||
'type': 'string',
|
||||
'description': '应用布局类型',
|
||||
'required': False,
|
||||
},
|
||||
]
|
||||
|
||||
outputs = [
|
||||
{
|
||||
'name': 'settings',
|
||||
'type': 'object',
|
||||
'description': '应用设置配置',
|
||||
},
|
||||
{
|
||||
'name': 'confirmed',
|
||||
'type': 'boolean',
|
||||
'description': '是否已确认',
|
||||
},
|
||||
]
|
||||
|
||||
# 默认配置
|
||||
DEFAULT_SETTINGS = {
|
||||
'app': {
|
||||
'name': '',
|
||||
'locale': 'zh-CN',
|
||||
'dynamicTitle': True,
|
||||
'watermark': False,
|
||||
'watermarkContent': '',
|
||||
'enableCheckUpdates': True,
|
||||
'defaultHomePath': '/analytics',
|
||||
'enablePreferences': True,
|
||||
},
|
||||
'logo': {
|
||||
'enable': True,
|
||||
'source': '',
|
||||
'fit': 'contain',
|
||||
},
|
||||
'theme': {
|
||||
'colorPrimary': 'hsl(212 100% 45%)',
|
||||
'builtinType': 'default',
|
||||
'mode': 'light',
|
||||
'radius': '0.5',
|
||||
'semiDarkSidebar': False,
|
||||
'semiDarkHeader': False,
|
||||
},
|
||||
'sidebar': {
|
||||
'enable': True,
|
||||
'width': 230,
|
||||
'collapsed': False,
|
||||
'collapsedShowTitle': False,
|
||||
'autoActivateChild': False,
|
||||
'expandOnHover': True,
|
||||
'collapsedButton': True,
|
||||
'fixedButton': True,
|
||||
},
|
||||
'header': {
|
||||
'enable': True,
|
||||
'mode': 'fixed',
|
||||
'menuAlign': 'start',
|
||||
},
|
||||
'footer': {
|
||||
'enable': True,
|
||||
'fixed': True,
|
||||
},
|
||||
'copyright': {
|
||||
'enable': True,
|
||||
'companyName': '',
|
||||
'companySiteLink': '',
|
||||
'date': '',
|
||||
'icp': '',
|
||||
'icpLink': '',
|
||||
},
|
||||
'navigation': {
|
||||
'styleType': 'rounded',
|
||||
'split': True,
|
||||
'accordion': True,
|
||||
},
|
||||
'tabbar': {
|
||||
'enable': True,
|
||||
'showIcon': True,
|
||||
'showMore': True,
|
||||
'showMaximize': True,
|
||||
'persist': True,
|
||||
'draggable': True,
|
||||
'wheelable': True,
|
||||
'styleType': 'chrome',
|
||||
'maxCount': 0,
|
||||
'middleClickToClose': True,
|
||||
},
|
||||
'breadcrumb': {
|
||||
'enable': True,
|
||||
'showIcon': True,
|
||||
'showHome': True,
|
||||
'styleType': 'normal',
|
||||
'hideOnlyOne': False,
|
||||
},
|
||||
'transition': {
|
||||
'progress': True,
|
||||
'name': 'fade-slide',
|
||||
'loading': True,
|
||||
'enable': True,
|
||||
},
|
||||
'widget': {
|
||||
'globalSearch': True,
|
||||
'fullscreen': True,
|
||||
'languageToggle': True,
|
||||
'notification': True,
|
||||
'themeToggle': True,
|
||||
'sidebarToggle': True,
|
||||
'lockScreen': True,
|
||||
'refresh': True,
|
||||
},
|
||||
'shortcutKeys': {
|
||||
'enable': True,
|
||||
'globalSearch': True,
|
||||
'globalLogout': True,
|
||||
'globalLockScreen': True,
|
||||
},
|
||||
}
|
||||
|
||||
# 布局类型选项
|
||||
LAYOUT_OPTIONS = [
|
||||
{'label': '侧边导航', 'value': 'sidebar-nav'},
|
||||
{'label': '侧边混合导航', 'value': 'sidebar-mixed-nav'},
|
||||
{'label': '顶部导航', 'value': 'header-nav'},
|
||||
{'label': '混合导航', 'value': 'mixed-nav'},
|
||||
{'label': '全屏内容', 'value': 'full-content'},
|
||||
]
|
||||
|
||||
# 内置主题选项
|
||||
BUILTIN_THEME_OPTIONS = [
|
||||
{'label': '默认', 'value': 'default'},
|
||||
{'label': '紫罗兰', 'value': 'violet'},
|
||||
{'label': '粉红', 'value': 'pink'},
|
||||
{'label': '玫瑰', 'value': 'rose'},
|
||||
{'label': '天蓝', 'value': 'sky'},
|
||||
{'label': '青色', 'value': 'cyan'},
|
||||
{'label': '绿色', 'value': 'green'},
|
||||
{'label': '橙色', 'value': 'orange'},
|
||||
{'label': '黄色', 'value': 'yellow'},
|
||||
{'label': '锌色', 'value': 'zinc'},
|
||||
{'label': '中性', 'value': 'neutral'},
|
||||
{'label': '石板', 'value': 'slate'},
|
||||
{'label': '灰色', 'value': 'gray'},
|
||||
{'label': '深灰', 'value': 'deep-blue'},
|
||||
{'label': '深绿', 'value': 'deep-green'},
|
||||
]
|
||||
|
||||
def execute(self, context: NodeContext) -> NodeResult:
|
||||
"""执行节点"""
|
||||
try:
|
||||
# 检查是否有用户编辑的数据(从设计预览面板提交)
|
||||
user_input = context.variables.get('__user_input__')
|
||||
if user_input:
|
||||
edited_data = self._parse_user_edit(user_input)
|
||||
if edited_data:
|
||||
logger.info(f'AppSettingsNode - 使用用户编辑的数据')
|
||||
# 使用编辑后的数据,清除用户输入
|
||||
context.variables.pop('__user_input__', None)
|
||||
return self._build_result(edited_data, require_confirmation=False)
|
||||
|
||||
# 获取输入参数
|
||||
app_name = context.resolve_template(self.config.get('app_name', ''))
|
||||
home_path = context.resolve_template(self.config.get('home_path', ''))
|
||||
logo_source = context.resolve_template(self.config.get('logo_source', ''))
|
||||
theme_builtin_type = context.resolve_template(self.config.get('theme_builtin_type', 'default'))
|
||||
theme_color_primary = context.resolve_template(self.config.get('theme_color_primary', ''))
|
||||
app_layout = context.resolve_template(self.config.get('app_layout', 'sidebar-nav'))
|
||||
|
||||
logger.info(f'AppSettingsNode - 输入参数: app_name={app_name}, home_path={home_path}, layout={app_layout}')
|
||||
|
||||
# 构建设置数据(合并默认值和输入值)
|
||||
settings = self._build_settings(
|
||||
app_name=app_name,
|
||||
home_path=home_path,
|
||||
logo_source=logo_source,
|
||||
theme_builtin_type=theme_builtin_type,
|
||||
theme_color_primary=theme_color_primary,
|
||||
app_layout=app_layout,
|
||||
)
|
||||
|
||||
logger.info(f'AppSettingsNode - 输出设置配置')
|
||||
|
||||
# 检查是否需要确认(支持布尔值、字符串模式和变量引用)
|
||||
require_confirmation = self.resolve_require_confirmation(context, default=True)
|
||||
|
||||
return self._build_result(settings, require_confirmation)
|
||||
|
||||
except Exception as e:
|
||||
logger.exception(f'应用设置节点执行失败: {e}')
|
||||
return NodeResult(
|
||||
success=False,
|
||||
error=f'节点执行失败: {str(e)}',
|
||||
)
|
||||
|
||||
async def execute_async(self, context: NodeContext) -> NodeResult:
|
||||
"""异步执行节点(直接调用同步方法)"""
|
||||
return self.execute(context)
|
||||
|
||||
def _parse_user_edit(self, user_input: Any) -> Optional[Dict[str, Any]]:
|
||||
"""解析用户编辑的数据"""
|
||||
if isinstance(user_input, dict):
|
||||
# 检查是否包含设置字段
|
||||
if 'app' in user_input or 'theme' in user_input or 'logo' in user_input:
|
||||
return user_input
|
||||
return None
|
||||
|
||||
if isinstance(user_input, str):
|
||||
try:
|
||||
data = json.loads(user_input)
|
||||
if isinstance(data, dict) and ('app' in data or 'theme' in data or 'logo' in data):
|
||||
return data
|
||||
except (json.JSONDecodeError, TypeError):
|
||||
pass
|
||||
|
||||
return None
|
||||
|
||||
def _build_settings(
|
||||
self,
|
||||
app_name: str = '',
|
||||
home_path: str = '',
|
||||
logo_source: str = '',
|
||||
theme_builtin_type: str = 'default',
|
||||
theme_color_primary: str = '',
|
||||
app_layout: str = 'sidebar-nav',
|
||||
) -> Dict[str, Any]:
|
||||
"""构建设置配置"""
|
||||
import copy
|
||||
settings = copy.deepcopy(self.DEFAULT_SETTINGS)
|
||||
|
||||
# 应用配置
|
||||
if app_name:
|
||||
settings['app']['name'] = app_name
|
||||
|
||||
# 首页路径配置
|
||||
if home_path:
|
||||
settings['app']['defaultHomePath'] = home_path
|
||||
|
||||
# 布局配置
|
||||
if app_layout:
|
||||
settings['app']['layout'] = app_layout
|
||||
|
||||
# Logo配置
|
||||
if logo_source:
|
||||
settings['logo']['source'] = logo_source
|
||||
|
||||
# 主题配置
|
||||
if theme_builtin_type:
|
||||
settings['theme']['builtinType'] = theme_builtin_type
|
||||
if theme_color_primary:
|
||||
settings['theme']['colorPrimary'] = theme_color_primary
|
||||
|
||||
return settings
|
||||
|
||||
def _build_result(self, settings: Dict[str, Any], require_confirmation: bool = False) -> NodeResult:
|
||||
"""构建节点结果"""
|
||||
# 构建预览数据
|
||||
preview_data = {
|
||||
'type': 'app_settings',
|
||||
'title': '应用设置',
|
||||
'data': settings,
|
||||
'editable': True,
|
||||
'layoutOptions': self.LAYOUT_OPTIONS,
|
||||
'themeOptions': self.BUILTIN_THEME_OPTIONS,
|
||||
} if require_confirmation else None
|
||||
|
||||
return NodeResult(
|
||||
success=True,
|
||||
output=settings,
|
||||
output_variables={
|
||||
'settings': settings,
|
||||
'confirmed': not require_confirmation,
|
||||
},
|
||||
preview=preview_data,
|
||||
waiting_for_input=require_confirmation,
|
||||
waiting_config={
|
||||
'type': 'design_preview',
|
||||
'preview_type': 'app_settings',
|
||||
'title': '应用设置',
|
||||
'message': '请确认或编辑应用设置',
|
||||
'data': settings,
|
||||
'layoutOptions': self.LAYOUT_OPTIONS,
|
||||
'themeOptions': self.BUILTIN_THEME_OPTIONS,
|
||||
} if require_confirmation else {},
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def get_config_schema(cls) -> Dict[str, Any]:
|
||||
"""
|
||||
获取节点配置 Schema(供前端表单渲染)
|
||||
|
||||
Returns:
|
||||
配置 Schema
|
||||
"""
|
||||
return {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'app_name': {
|
||||
'type': 'string',
|
||||
'title': '应用名称',
|
||||
'description': '应用的显示名称,支持变量引用如 {{llm_output}}',
|
||||
'required': False,
|
||||
'x-component': 'Input',
|
||||
'x-component-props': {
|
||||
'placeholder': '请输入应用名称',
|
||||
},
|
||||
},
|
||||
'home_path': {
|
||||
'type': 'string',
|
||||
'title': '首页路径',
|
||||
'description': '应用首页路径,如 /dashboard 或 /analytics',
|
||||
'required': False,
|
||||
'x-component': 'Input',
|
||||
'x-component-props': {
|
||||
'placeholder': '如 /dashboard',
|
||||
},
|
||||
},
|
||||
'logo_source': {
|
||||
'type': 'string',
|
||||
'title': 'Logo图片',
|
||||
'description': 'Logo图片URL或文件ID,支持变量引用',
|
||||
'required': False,
|
||||
'x-component': 'Input',
|
||||
'x-component-props': {
|
||||
'placeholder': '请输入Logo图片URL',
|
||||
},
|
||||
},
|
||||
'theme_builtin_type': {
|
||||
'type': 'string',
|
||||
'title': '内置主题',
|
||||
'description': '选择内置主题类型',
|
||||
'default': 'default',
|
||||
'enum': ['default', 'violet', 'pink', 'rose', 'sky', 'cyan', 'green', 'orange', 'yellow', 'zinc', 'neutral', 'slate', 'gray', 'deep-blue', 'deep-green'],
|
||||
'x-component': 'Select',
|
||||
'x-component-props': {
|
||||
'placeholder': '请选择内置主题',
|
||||
},
|
||||
},
|
||||
'theme_color_primary': {
|
||||
'type': 'string',
|
||||
'title': '主题主色调',
|
||||
'description': '主题主色调,如 hsl(212 100% 45%)',
|
||||
'required': False,
|
||||
'x-component': 'Input',
|
||||
'x-component-props': {
|
||||
'placeholder': '如 hsl(212 100% 45%)',
|
||||
},
|
||||
},
|
||||
'app_layout': {
|
||||
'type': 'string',
|
||||
'title': '应用布局',
|
||||
'description': '应用布局类型',
|
||||
'default': 'sidebar-nav',
|
||||
'enum': ['sidebar-nav', 'sidebar-mixed-nav', 'header-nav', 'mixed-nav', 'full-content'],
|
||||
'x-component': 'Select',
|
||||
'x-component-props': {
|
||||
'placeholder': '请选择布局类型',
|
||||
},
|
||||
},
|
||||
'require_confirmation': {
|
||||
'type': 'boolean',
|
||||
'title': '需要确认',
|
||||
'description': '设置完成后是否暂停等待用户确认或编辑',
|
||||
'default': True,
|
||||
'x-component': 'Switch',
|
||||
},
|
||||
},
|
||||
'required': [],
|
||||
}
|
||||
Reference in New Issue
Block a user