Files
2026-06-09 21:18:33 +08:00

433 lines
14 KiB
Python

"""
应用设置节点
接收应用设置配置,展示给用户确认或编辑
包括:应用配置、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': [],
}