feat: improve workflow observability and lighten admin frontend
This commit is contained in:
@@ -226,6 +226,7 @@ def _make_execution_log_entry(
|
|||||||
**extra: Any,
|
**extra: Any,
|
||||||
) -> dict:
|
) -> dict:
|
||||||
extra.setdefault('timestamp', datetime.now().isoformat())
|
extra.setdefault('timestamp', datetime.now().isoformat())
|
||||||
|
events = extra.pop('events', None)
|
||||||
metadata = dict(extra.pop('metadata', {}) or {})
|
metadata = dict(extra.pop('metadata', {}) or {})
|
||||||
event_type = _execution_log_event_type(extra.get('status'))
|
event_type = _execution_log_event_type(extra.get('status'))
|
||||||
collaboration = _node_collaboration_metadata(node_map, node_id, node_type)
|
collaboration = _node_collaboration_metadata(node_map, node_id, node_type)
|
||||||
@@ -249,19 +250,106 @@ def _make_execution_log_entry(
|
|||||||
error=extra.get('error'),
|
error=extra.get('error'),
|
||||||
)
|
)
|
||||||
metadata['communication']['timestamp'] = extra.get('timestamp')
|
metadata['communication']['timestamp'] = extra.get('timestamp')
|
||||||
return {
|
entry = {
|
||||||
'node_id': node_id,
|
'node_id': node_id,
|
||||||
'node_type': node_type,
|
'node_type': node_type,
|
||||||
'node_label': _node_label_from_map(node_map, node_id),
|
'node_label': _node_label_from_map(node_map, node_id),
|
||||||
'metadata': metadata,
|
'metadata': metadata,
|
||||||
**extra,
|
**extra,
|
||||||
}
|
}
|
||||||
|
if events:
|
||||||
|
entry['events'] = events
|
||||||
|
return entry
|
||||||
|
|
||||||
|
|
||||||
|
def _event_log_status(event_type: str, event: Dict[str, Any]) -> str:
|
||||||
|
if event_type == 'error':
|
||||||
|
return 'failed'
|
||||||
|
if event_type == 'waiting_input':
|
||||||
|
return 'waiting'
|
||||||
|
if event_type in ('start', 'node_start', 'parallel_start'):
|
||||||
|
return 'running'
|
||||||
|
status = event.get('status') or 'completed'
|
||||||
|
if status == 'success':
|
||||||
|
return 'completed'
|
||||||
|
if status == 'failed':
|
||||||
|
return 'failed'
|
||||||
|
return status
|
||||||
|
|
||||||
|
|
||||||
|
def _make_stream_event_log_entry(event: Dict[str, Any]) -> Optional[dict]:
|
||||||
|
event_type = event.get('type')
|
||||||
|
if not event_type or event_type == 'llm_chunk':
|
||||||
|
return None
|
||||||
|
|
||||||
|
event_data = copy.deepcopy(event)
|
||||||
|
timestamp = event_data.get('timestamp') or datetime.now().isoformat()
|
||||||
|
event_data.setdefault('timestamp', timestamp)
|
||||||
|
communication = dict(event_data.get('communication') or {})
|
||||||
|
communication.setdefault('event', event_type)
|
||||||
|
communication.setdefault('timestamp', timestamp)
|
||||||
|
|
||||||
|
node_id = str(event_data.get('node_id') or event_data.get('run_id') or 'workflow')
|
||||||
|
node_type = str(event_data.get('node_type') or 'workflow')
|
||||||
|
node_label = (
|
||||||
|
event_data.get('node_label')
|
||||||
|
or event_data.get('workflow_name')
|
||||||
|
or communication.get('message')
|
||||||
|
or event_type
|
||||||
|
)
|
||||||
|
output = event_data.get('outputs')
|
||||||
|
if output is None:
|
||||||
|
output = event_data.get('content') or event_data.get('message')
|
||||||
|
error = event_data.get('error_message') or event_data.get('error')
|
||||||
|
|
||||||
|
return {
|
||||||
|
'type': event_type,
|
||||||
|
'node_id': node_id,
|
||||||
|
'node_type': node_type,
|
||||||
|
'node_label': str(node_label),
|
||||||
|
'status': _event_log_status(event_type, event_data),
|
||||||
|
'timestamp': timestamp,
|
||||||
|
'elapsed_time': event_data.get('elapsed_time'),
|
||||||
|
'tokens_used': event_data.get('tokens_used') or 0,
|
||||||
|
'output': output,
|
||||||
|
'error': error,
|
||||||
|
'event': event_data,
|
||||||
|
'events': [event_data],
|
||||||
|
'metadata': {
|
||||||
|
'stream_event': True,
|
||||||
|
'collaboration': event_data.get('collaboration') or {},
|
||||||
|
'communication': communication,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _log_timestamp(log: Dict[str, Any]) -> str:
|
||||||
|
metadata = log.get('metadata') or {}
|
||||||
|
communication = metadata.get('communication') or {}
|
||||||
|
return log.get('timestamp') or communication.get('timestamp') or ''
|
||||||
|
|
||||||
|
|
||||||
|
def _merge_execution_logs(node_logs: List[dict], event_logs: List[dict]) -> List[dict]:
|
||||||
|
base_logs = [
|
||||||
|
log for log in (node_logs or [])
|
||||||
|
if not (log.get('metadata') or {}).get('stream_event')
|
||||||
|
]
|
||||||
|
merged = base_logs + list(event_logs or [])
|
||||||
|
return sorted(
|
||||||
|
merged,
|
||||||
|
key=lambda item: (_log_timestamp(item), 0 if item.get('metadata', {}).get('stream_event') else 1),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def _node_result_metadata(result: NodeResult) -> dict:
|
def _node_result_metadata(result: NodeResult) -> dict:
|
||||||
return dict(result.metadata or {})
|
return dict(result.metadata or {})
|
||||||
|
|
||||||
|
|
||||||
|
def _node_result_events(result: NodeResult) -> List[Dict[str, Any]]:
|
||||||
|
events = getattr(result, 'events', None) or []
|
||||||
|
return copy.deepcopy(events) if events else []
|
||||||
|
|
||||||
|
|
||||||
def _event_collaboration_metadata(
|
def _event_collaboration_metadata(
|
||||||
node_map: Dict[str, Any],
|
node_map: Dict[str, Any],
|
||||||
node_id: str,
|
node_id: str,
|
||||||
@@ -842,6 +930,7 @@ class AIWorkflowService:
|
|||||||
elapsed_time=elapsed,
|
elapsed_time=elapsed,
|
||||||
tokens_used=result.tokens_used,
|
tokens_used=result.tokens_used,
|
||||||
metadata=_node_result_metadata(result),
|
metadata=_node_result_metadata(result),
|
||||||
|
events=_node_result_events(result),
|
||||||
inputs=copy.deepcopy(node_inputs),
|
inputs=copy.deepcopy(node_inputs),
|
||||||
target_node_id=target_node_id,
|
target_node_id=target_node_id,
|
||||||
))
|
))
|
||||||
@@ -1024,6 +1113,7 @@ class AIWorkflowService:
|
|||||||
elapsed_time=elapsed,
|
elapsed_time=elapsed,
|
||||||
tokens_used=result.tokens_used,
|
tokens_used=result.tokens_used,
|
||||||
metadata=_node_result_metadata(result),
|
metadata=_node_result_metadata(result),
|
||||||
|
events=_node_result_events(result),
|
||||||
inputs=copy.deepcopy(node_inputs),
|
inputs=copy.deepcopy(node_inputs),
|
||||||
branch=branch_id,
|
branch=branch_id,
|
||||||
branch_label=branch_labels.get(branch_id, branch_id),
|
branch_label=branch_labels.get(branch_id, branch_id),
|
||||||
@@ -1252,6 +1342,7 @@ class AIWorkflowService:
|
|||||||
elapsed_time=elapsed,
|
elapsed_time=elapsed,
|
||||||
tokens_used=result.tokens_used,
|
tokens_used=result.tokens_used,
|
||||||
metadata=_node_result_metadata(result),
|
metadata=_node_result_metadata(result),
|
||||||
|
events=_node_result_events(result),
|
||||||
branch=branch_id,
|
branch=branch_id,
|
||||||
branch_label=branch_labels.get(branch_id, branch_id),
|
branch_label=branch_labels.get(branch_id, branch_id),
|
||||||
))
|
))
|
||||||
@@ -1545,6 +1636,7 @@ class AIWorkflowService:
|
|||||||
elapsed_time=elapsed,
|
elapsed_time=elapsed,
|
||||||
tokens_used=result.tokens_used,
|
tokens_used=result.tokens_used,
|
||||||
metadata=_node_result_metadata(result),
|
metadata=_node_result_metadata(result),
|
||||||
|
events=_node_result_events(result),
|
||||||
loop_iteration=iteration,
|
loop_iteration=iteration,
|
||||||
))
|
))
|
||||||
|
|
||||||
@@ -1593,7 +1685,10 @@ class AIWorkflowService:
|
|||||||
current_id,
|
current_id,
|
||||||
node_type,
|
node_type,
|
||||||
status='waiting',
|
status='waiting',
|
||||||
|
output=result.output,
|
||||||
elapsed_time=elapsed,
|
elapsed_time=elapsed,
|
||||||
|
metadata=_node_result_metadata(result),
|
||||||
|
events=_node_result_events(result),
|
||||||
loop_iteration=iteration,
|
loop_iteration=iteration,
|
||||||
))
|
))
|
||||||
|
|
||||||
@@ -1733,15 +1828,20 @@ class AIWorkflowService:
|
|||||||
)
|
)
|
||||||
self._db.add(run)
|
self._db.add(run)
|
||||||
await self._db.flush()
|
await self._db.flush()
|
||||||
|
event_logs: List[dict] = []
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# 使用异步生成器执行工作流
|
# 使用异步生成器执行工作流
|
||||||
async for event in self._execute_workflow_stream(workflow, inputs, run, conversation_history, use_draft):
|
async for event in self._execute_workflow_stream(workflow, inputs, run, conversation_history, use_draft):
|
||||||
yield event
|
yield event
|
||||||
|
event_log = _make_stream_event_log_entry(event)
|
||||||
|
if event_log:
|
||||||
|
event_logs.append(event_log)
|
||||||
|
run.execution_log = _merge_execution_logs(run.execution_log or [], event_logs)
|
||||||
|
|
||||||
# 在关键事件后提交数据库状态
|
# 在关键事件后提交数据库状态
|
||||||
event_type = event.get('type')
|
event_type = event.get('type')
|
||||||
if event_type in ('node_complete', 'waiting_input', 'complete', 'error'):
|
if event_log or event_type in ('node_complete', 'waiting_input', 'complete', 'error'):
|
||||||
await self._db.commit()
|
await self._db.commit()
|
||||||
|
|
||||||
# 更新运行记录
|
# 更新运行记录
|
||||||
@@ -1771,13 +1871,19 @@ class AIWorkflowService:
|
|||||||
await self._db.commit()
|
await self._db.commit()
|
||||||
|
|
||||||
# 发送错误事件给前端
|
# 发送错误事件给前端
|
||||||
yield {
|
error_event = {
|
||||||
'type': 'error',
|
'type': 'error',
|
||||||
'message': str(e),
|
'message': str(e),
|
||||||
'error_message': str(e),
|
'error_message': str(e),
|
||||||
'run_id': run.id,
|
'run_id': run.id,
|
||||||
'workflow_id': workflow.id,
|
'workflow_id': workflow.id,
|
||||||
}
|
}
|
||||||
|
error_log = _make_stream_event_log_entry(error_event)
|
||||||
|
if error_log:
|
||||||
|
event_logs.append(error_log)
|
||||||
|
run.execution_log = _merge_execution_logs(run.execution_log or [], event_logs)
|
||||||
|
await self._db.commit()
|
||||||
|
yield error_event
|
||||||
|
|
||||||
async def _execute_workflow_stream(
|
async def _execute_workflow_stream(
|
||||||
self,
|
self,
|
||||||
@@ -2026,6 +2132,7 @@ class AIWorkflowService:
|
|||||||
output=result.output,
|
output=result.output,
|
||||||
elapsed_time=elapsed,
|
elapsed_time=elapsed,
|
||||||
metadata=_node_result_metadata(result),
|
metadata=_node_result_metadata(result),
|
||||||
|
events=_node_result_events(result),
|
||||||
inputs=copy.deepcopy(node_inputs),
|
inputs=copy.deepcopy(node_inputs),
|
||||||
target_node_id=target_node_id,
|
target_node_id=target_node_id,
|
||||||
)
|
)
|
||||||
@@ -2067,6 +2174,7 @@ class AIWorkflowService:
|
|||||||
elapsed_time=elapsed,
|
elapsed_time=elapsed,
|
||||||
tokens_used=result.tokens_used,
|
tokens_used=result.tokens_used,
|
||||||
metadata=_node_result_metadata(result),
|
metadata=_node_result_metadata(result),
|
||||||
|
events=_node_result_events(result),
|
||||||
inputs=copy.deepcopy(node_inputs),
|
inputs=copy.deepcopy(node_inputs),
|
||||||
target_node_id=target_node_id,
|
target_node_id=target_node_id,
|
||||||
)
|
)
|
||||||
@@ -2557,6 +2665,7 @@ class AIWorkflowService:
|
|||||||
output=result.output,
|
output=result.output,
|
||||||
elapsed_time=elapsed,
|
elapsed_time=elapsed,
|
||||||
metadata=_node_result_metadata(result),
|
metadata=_node_result_metadata(result),
|
||||||
|
events=_node_result_events(result),
|
||||||
target_node_id=target_node_id,
|
target_node_id=target_node_id,
|
||||||
)
|
)
|
||||||
logs.append(log_entry)
|
logs.append(log_entry)
|
||||||
@@ -2595,6 +2704,7 @@ class AIWorkflowService:
|
|||||||
elapsed_time=elapsed,
|
elapsed_time=elapsed,
|
||||||
tokens_used=result.tokens_used,
|
tokens_used=result.tokens_used,
|
||||||
metadata=_node_result_metadata(result),
|
metadata=_node_result_metadata(result),
|
||||||
|
events=_node_result_events(result),
|
||||||
target_node_id=target_node_id,
|
target_node_id=target_node_id,
|
||||||
)
|
)
|
||||||
logs.append(log_entry)
|
logs.append(log_entry)
|
||||||
|
|||||||
@@ -271,6 +271,14 @@ export interface WorkflowListItem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 工作流运行记录 */
|
/** 工作流运行记录 */
|
||||||
|
export interface WorkflowRunEvent {
|
||||||
|
type?: string;
|
||||||
|
content?: string;
|
||||||
|
message?: string;
|
||||||
|
message_type?: string;
|
||||||
|
[key: string]: any;
|
||||||
|
}
|
||||||
|
|
||||||
export interface ExecutionLogEntry {
|
export interface ExecutionLogEntry {
|
||||||
node_id: string;
|
node_id: string;
|
||||||
node_type: string;
|
node_type: string;
|
||||||
@@ -285,6 +293,7 @@ export interface ExecutionLogEntry {
|
|||||||
branch?: string;
|
branch?: string;
|
||||||
branch_label?: string;
|
branch_label?: string;
|
||||||
metadata?: Record<string, any>;
|
metadata?: Record<string, any>;
|
||||||
|
events?: WorkflowRunEvent[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface WorkflowRunListItem {
|
export interface WorkflowRunListItem {
|
||||||
|
|||||||
@@ -1,22 +1,3 @@
|
|||||||
export * from '../online-dev/form-data-api';
|
|
||||||
export * from '../online-dev/form-manager';
|
|
||||||
// 注意: login-log 模块请直接导入以避免 PaginatedResponse 类型冲突
|
|
||||||
// import { getLoginLogListApi, ... } from '#/api/core/login-log';
|
|
||||||
// 注意: ai-platform 模块请直接导入以避免类型冲突
|
|
||||||
// import { ... } from '#/api/core/ai-platform';
|
|
||||||
export * from '../online-dev/screen-design';
|
|
||||||
export * from './application';
|
|
||||||
export * from './auth';
|
export * from './auth';
|
||||||
export * from './data-source';
|
|
||||||
export * from './database-connection';
|
|
||||||
export * from './database-manager';
|
|
||||||
export * from './dept';
|
|
||||||
export * from './dict';
|
|
||||||
export * from './menu';
|
|
||||||
export * from './oauth';
|
export * from './oauth';
|
||||||
export * from './permission';
|
|
||||||
export * from './post';
|
|
||||||
export * from './role';
|
|
||||||
export * from './scheduler';
|
|
||||||
export * from './server-monitor';
|
|
||||||
export * from './user';
|
export * from './user';
|
||||||
|
|||||||
@@ -22,7 +22,33 @@ import zhTwLocale from 'element-plus/es/locale/lang/zh-tw';
|
|||||||
|
|
||||||
const elementLocale = ref<Language>(defaultLocale);
|
const elementLocale = ref<Language>(defaultLocale);
|
||||||
|
|
||||||
const modules = import.meta.glob('./langs/**/*.json');
|
const modules = import.meta.glob([
|
||||||
|
'./langs/**/account-settings.json',
|
||||||
|
'./langs/**/ai-platform.json',
|
||||||
|
'./langs/**/announcement.json',
|
||||||
|
'./langs/**/authentication.json',
|
||||||
|
'./langs/**/chat.json',
|
||||||
|
'./langs/**/common.json',
|
||||||
|
'./langs/**/core.json',
|
||||||
|
'./langs/**/database-connection.json',
|
||||||
|
'./langs/**/database-manager.json',
|
||||||
|
'./langs/**/dept.json',
|
||||||
|
'./langs/**/dict.json',
|
||||||
|
'./langs/**/file-manager.json',
|
||||||
|
'./langs/**/menu-title.json',
|
||||||
|
'./langs/**/menu.json',
|
||||||
|
'./langs/**/message.json',
|
||||||
|
'./langs/**/page.json',
|
||||||
|
'./langs/**/permission.json',
|
||||||
|
'./langs/**/post.json',
|
||||||
|
'./langs/**/role.json',
|
||||||
|
'./langs/**/system.json',
|
||||||
|
'./langs/**/system-config.json',
|
||||||
|
'./langs/**/ui.json',
|
||||||
|
'./langs/**/user-avatar.json',
|
||||||
|
'./langs/**/user.json',
|
||||||
|
'./langs/**/workflow.json',
|
||||||
|
]);
|
||||||
|
|
||||||
const localesMap = loadLocalesMapFromDir(
|
const localesMap = loadLocalesMapFromDir(
|
||||||
/\.\/langs\/([^/]+)\/(.*)\.json$/,
|
/\.\/langs\/([^/]+)\/(.*)\.json$/,
|
||||||
|
|||||||
@@ -175,27 +175,13 @@ async function generateAccess(options: GenerateMenuAndRoutesOptions) {
|
|||||||
'../views/_core/agent-chat/**/*.vue',
|
'../views/_core/agent-chat/**/*.vue',
|
||||||
'../views/_core/announcement/index.vue',
|
'../views/_core/announcement/index.vue',
|
||||||
'../views/_core/announcement/list.vue',
|
'../views/_core/announcement/list.vue',
|
||||||
'../views/_core/application/index.vue',
|
|
||||||
'../views/_core/authentication/login.vue',
|
'../views/_core/authentication/login.vue',
|
||||||
'../views/_core/chat/**/*.vue',
|
|
||||||
'../views/_core/database-monitor/**/*.vue',
|
|
||||||
'../views/_core/dept/index.vue',
|
|
||||||
'../views/_core/dict/index.vue',
|
|
||||||
'../views/_core/fallback/**/*.vue',
|
'../views/_core/fallback/**/*.vue',
|
||||||
'../views/_core/file-manager/index.vue',
|
|
||||||
'../views/_core/file-preview/index.vue',
|
|
||||||
'../views/_core/login-log/index.vue',
|
|
||||||
'../views/_core/menu/index.vue',
|
'../views/_core/menu/index.vue',
|
||||||
'../views/_core/message/index.vue',
|
'../views/_core/message/index.vue',
|
||||||
'../views/_core/org-chart/index.vue',
|
|
||||||
'../views/_core/page-render/index.vue',
|
'../views/_core/page-render/index.vue',
|
||||||
'../views/_core/permission/index.vue',
|
'../views/_core/permission/index.vue',
|
||||||
'../views/_core/post/index.vue',
|
|
||||||
'../views/_core/role/index.vue',
|
'../views/_core/role/index.vue',
|
||||||
'../views/_core/redis-monitor/**/*.vue',
|
|
||||||
'../views/_core/server-monitor/index.vue',
|
|
||||||
'../views/_core/system-config/index.vue',
|
|
||||||
'../views/_core/ui-config/index.vue',
|
|
||||||
'../views/_core/user/index.vue',
|
'../views/_core/user/index.vue',
|
||||||
'../views/ai-platform/agent/editor/index.vue',
|
'../views/ai-platform/agent/editor/index.vue',
|
||||||
'../views/ai-platform/agent/index.vue',
|
'../views/ai-platform/agent/index.vue',
|
||||||
|
|||||||
@@ -1,50 +1,42 @@
|
|||||||
export const LIGHT_MENU_NAMES = new Set([
|
export const LIGHT_MENU_NAMES = new Set([
|
||||||
'AccountSettings',
|
'AccountSettings',
|
||||||
'Application',
|
|
||||||
'ControlCenter',
|
|
||||||
'AIAgent',
|
'AIAgent',
|
||||||
|
'AIAgentEditor',
|
||||||
|
'AgentChat',
|
||||||
'AIKnowledgeDetail',
|
'AIKnowledgeDetail',
|
||||||
|
'AIKnowledgeLegacyDetailRedirect',
|
||||||
|
'AIKnowledgeLegacyRedirect',
|
||||||
'AIModelConfig',
|
'AIModelConfig',
|
||||||
'AIPlatform',
|
'AIPlatform',
|
||||||
'AIWorkflow',
|
'AIWorkflow',
|
||||||
|
'AIWorkflowEditor',
|
||||||
'AIWorkflowRuns',
|
'AIWorkflowRuns',
|
||||||
'AnnouncementList',
|
'AnnouncementList',
|
||||||
'AnnouncementManage',
|
'AnnouncementManage',
|
||||||
'CodexAgentChat',
|
'CodexAgentChat',
|
||||||
'FileManager',
|
|
||||||
'Message',
|
'Message',
|
||||||
'MessageCenter',
|
'MessageCenter',
|
||||||
'MessageList',
|
'MessageList',
|
||||||
'KnowledgeBase',
|
'KnowledgeBase',
|
||||||
'LoginLog',
|
|
||||||
'OrgNode',
|
|
||||||
'DatabaseMonitor',
|
|
||||||
'RedisMonitor',
|
|
||||||
'ServerMonitor',
|
|
||||||
'StartChat',
|
|
||||||
'SystemDept',
|
|
||||||
'SystemDict',
|
|
||||||
'SystemFileManager',
|
|
||||||
'SystemLoginLog',
|
|
||||||
'SystemManagement',
|
'SystemManagement',
|
||||||
'SystemOrgChart',
|
|
||||||
'SystemTools',
|
|
||||||
'SystemConfig',
|
|
||||||
'SystemConfigManager',
|
|
||||||
'SystemMonitoring',
|
|
||||||
'SystemMenu',
|
'SystemMenu',
|
||||||
'SystemPermission',
|
'SystemPermission',
|
||||||
'SystemPost',
|
|
||||||
'SystemRole',
|
'SystemRole',
|
||||||
'UIConfig',
|
'PageRenderMainHome',
|
||||||
'UIConfigManager',
|
'SubAppAIAgentEditor',
|
||||||
|
'SubAppAgentChat',
|
||||||
|
'SubAppAIKnowledgeDetail',
|
||||||
|
'SubAppAIKnowledgeLegacyDetailRedirect',
|
||||||
|
'SubAppAIKnowledgeLegacyRedirect',
|
||||||
|
'SubAppAIModelConfig',
|
||||||
|
'SubAppAIWorkflowEditor',
|
||||||
|
'SubAppAIWorkflowRuns',
|
||||||
'UserManagement',
|
'UserManagement',
|
||||||
'userCenter',
|
'userCenter',
|
||||||
'userSettings',
|
'userSettings',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const LIGHT_ROUTE_PATH_PREFIXES = [
|
const LIGHT_ROUTE_PATH_PREFIXES = [
|
||||||
'/control-center',
|
|
||||||
'/ai-platform/agent',
|
'/ai-platform/agent',
|
||||||
'/ai-platform/knowledge',
|
'/ai-platform/knowledge',
|
||||||
'/ai-platform/knowledge-base',
|
'/ai-platform/knowledge-base',
|
||||||
@@ -52,30 +44,14 @@ const LIGHT_ROUTE_PATH_PREFIXES = [
|
|||||||
'/ai-platform/workflow',
|
'/ai-platform/workflow',
|
||||||
'/ai-platform/workflow-runs',
|
'/ai-platform/workflow-runs',
|
||||||
'/agent-chat',
|
'/agent-chat',
|
||||||
'/application',
|
|
||||||
'/chat',
|
|
||||||
'/message/list',
|
'/message/list',
|
||||||
'/message/announcement',
|
'/message/announcement',
|
||||||
'/message/announcement-list',
|
'/message/announcement-list',
|
||||||
'/page-render/main_home',
|
'/page-render/main_home',
|
||||||
'/monitor',
|
|
||||||
'/core/ui-config',
|
|
||||||
'/system-config',
|
|
||||||
'/system/dept',
|
|
||||||
'/system/dict',
|
|
||||||
'/system/file-manager',
|
|
||||||
'/system/login-log',
|
|
||||||
'/system/org-chart',
|
|
||||||
'/system/post',
|
|
||||||
'/system/config',
|
|
||||||
'/system/menu',
|
'/system/menu',
|
||||||
'/system/new-permission',
|
'/system/new-permission',
|
||||||
'/system/role',
|
'/system/role',
|
||||||
'/system/ui-config',
|
|
||||||
'/system/user',
|
'/system/user',
|
||||||
'/tool/dict',
|
|
||||||
'/tool/file',
|
|
||||||
'/log/login-log',
|
|
||||||
'/user-center',
|
'/user-center',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { defineAsyncComponent, onMounted, ref, watch } from 'vue';
|
import { computed, defineAsyncComponent, onMounted, ref, watch } from 'vue';
|
||||||
import { useRoute } from 'vue-router';
|
import { useRoute, useRouter } from 'vue-router';
|
||||||
|
|
||||||
|
import { IconifyIcon } from '@vben/icons';
|
||||||
import { ElEmpty, ElMessage, ElScrollbar } from 'element-plus';
|
import { ElEmpty, ElMessage, ElScrollbar } from 'element-plus';
|
||||||
|
|
||||||
import { getPageByCodeApi } from '#/api/core/page-manager';
|
import { getPageByCodeApi } from '#/api/core/page-manager';
|
||||||
@@ -9,6 +10,7 @@ import { getPageByCodeApi } from '#/api/core/page-manager';
|
|||||||
defineOptions({ name: 'PageRender' });
|
defineOptions({ name: 'PageRender' });
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
|
const router = useRouter();
|
||||||
const DashboardRenderer = defineAsyncComponent(
|
const DashboardRenderer = defineAsyncComponent(
|
||||||
() => import('#/components/dashboard-design/DashboardRenderer.vue'),
|
() => import('#/components/dashboard-design/DashboardRenderer.vue'),
|
||||||
);
|
);
|
||||||
@@ -127,9 +129,30 @@ const opsHomeMenus = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: 'lucide:sliders-horizontal',
|
icon: 'lucide:sliders-horizontal',
|
||||||
id: 'ui-config',
|
id: 'model-config',
|
||||||
path: '/system/ui-config',
|
path: '/ai-platform/model',
|
||||||
title: '界面配置',
|
title: '模型配置',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const homeSections = [
|
||||||
|
{
|
||||||
|
description: '智能体、流程、知识库、模型和运行历史',
|
||||||
|
id: 'ai',
|
||||||
|
menus: aiHomeMenus,
|
||||||
|
title: 'AI 协作',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: '用户、角色、菜单、权限和公告消息',
|
||||||
|
id: 'admin',
|
||||||
|
menus: adminHomeMenus,
|
||||||
|
title: '基础管理',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: '需求分析、协作流程、可观测性和模型配置',
|
||||||
|
id: 'ops',
|
||||||
|
menus: opsHomeMenus,
|
||||||
|
title: '协作运维',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -231,6 +254,12 @@ function getPageCode(): string {
|
|||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isMainHome = computed(() => getPageCode() === 'main_home');
|
||||||
|
|
||||||
|
function goTo(path: string) {
|
||||||
|
router.push(path);
|
||||||
|
}
|
||||||
|
|
||||||
async function loadPageData() {
|
async function loadPageData() {
|
||||||
const code = getPageCode();
|
const code = getPageCode();
|
||||||
if (!code) {
|
if (!code) {
|
||||||
@@ -238,6 +267,12 @@ async function loadPageData() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (code === 'main_home') {
|
||||||
|
pageConfig.value = '';
|
||||||
|
loading.value = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
try {
|
try {
|
||||||
const page = await getPageByCodeApi(code);
|
const page = await getPageByCodeApi(code);
|
||||||
@@ -269,7 +304,62 @@ watch(
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<ElScrollbar class="rounded-[8px] py-3">
|
<ElScrollbar class="rounded-[8px] py-3">
|
||||||
<div v-loading="loading" class="h-[calc(100vh-120px)] rounded-[8px] px-3">
|
<div
|
||||||
|
v-if="isMainHome"
|
||||||
|
class="min-h-[calc(100vh-120px)] rounded-[8px] px-3 pb-6"
|
||||||
|
>
|
||||||
|
<div class="mb-4 px-1">
|
||||||
|
<h1 class="text-foreground text-xl font-semibold">AI Agent Admin</h1>
|
||||||
|
<p class="text-muted-foreground mt-1 text-sm">
|
||||||
|
面向智能体协作的轻量后台
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid gap-3 xl:grid-cols-3">
|
||||||
|
<section
|
||||||
|
v-for="section in homeSections"
|
||||||
|
:key="section.id"
|
||||||
|
class="border-border bg-card rounded-[8px] border p-4"
|
||||||
|
>
|
||||||
|
<div class="mb-4">
|
||||||
|
<h2 class="text-foreground text-sm font-semibold">
|
||||||
|
{{ section.title }}
|
||||||
|
</h2>
|
||||||
|
<p class="text-muted-foreground mt-1 text-xs">
|
||||||
|
{{ section.description }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-2 gap-2">
|
||||||
|
<button
|
||||||
|
v-for="item in section.menus"
|
||||||
|
:key="item.id"
|
||||||
|
class="border-border hover:border-primary/70 hover:bg-accent group flex min-h-20 flex-col items-start justify-between rounded-[8px] border p-3 text-left transition-colors"
|
||||||
|
type="button"
|
||||||
|
@click="goTo(item.path)"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="flex size-8 items-center justify-center rounded-[6px] text-white shadow-sm"
|
||||||
|
:style="{ background: item.color || item.bgColor }"
|
||||||
|
>
|
||||||
|
<IconifyIcon :icon="item.icon" class="size-4" />
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="text-foreground group-hover:text-primary mt-3 text-sm font-medium"
|
||||||
|
>
|
||||||
|
{{ item.title }}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-else
|
||||||
|
v-loading="loading"
|
||||||
|
class="h-[calc(100vh-120px)] rounded-[8px] px-3"
|
||||||
|
>
|
||||||
<DashboardRenderer v-if="pageConfig" :config="pageConfig" />
|
<DashboardRenderer v-if="pageConfig" :config="pageConfig" />
|
||||||
<ElEmpty v-else-if="!loading" description="暂无页面配置" />
|
<ElEmpty v-else-if="!loading" description="暂无页面配置" />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,9 +1,18 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { ExecutionLogEntry } from '#/api/ai-platform/ai-platform';
|
import type {
|
||||||
|
ExecutionLogEntry,
|
||||||
|
WorkflowRunEvent,
|
||||||
|
} from '#/api/ai-platform/ai-platform';
|
||||||
|
|
||||||
import { $t } from '@vben/locales';
|
import { $t } from '@vben/locales';
|
||||||
|
|
||||||
import { ElEmpty, ElScrollbar, ElTag, ElTimeline, ElTimelineItem } from 'element-plus';
|
import {
|
||||||
|
ElEmpty,
|
||||||
|
ElScrollbar,
|
||||||
|
ElTag,
|
||||||
|
ElTimeline,
|
||||||
|
ElTimelineItem,
|
||||||
|
} from 'element-plus';
|
||||||
|
|
||||||
import RunStatusTag from './RunStatusTag.vue';
|
import RunStatusTag from './RunStatusTag.vue';
|
||||||
|
|
||||||
@@ -31,9 +40,14 @@ function stringifyBrief(value: any, maxLength = 180) {
|
|||||||
return text.length > maxLength ? `${text.slice(0, maxLength)}...` : text;
|
return text.length > maxLength ? `${text.slice(0, maxLength)}...` : text;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getCommunication(log: ExecutionLogEntry) {
|
||||||
|
return log.metadata?.communication || {};
|
||||||
|
}
|
||||||
|
|
||||||
function getMetaItems(log: ExecutionLogEntry) {
|
function getMetaItems(log: ExecutionLogEntry) {
|
||||||
const metadata = log.metadata || {};
|
const metadata = log.metadata || {};
|
||||||
const items: Array<{ label: string; type?: 'info' | 'primary' | 'success' | 'warning' }> = [];
|
const items: Array<{ label: string; type?: 'info' | 'primary' | 'success' | 'warning' }> = [];
|
||||||
|
const communication = getCommunication(log);
|
||||||
|
|
||||||
if (log.branch_label || log.branch) {
|
if (log.branch_label || log.branch) {
|
||||||
items.push({ label: `分支: ${log.branch_label || log.branch}`, type: 'primary' });
|
items.push({ label: `分支: ${log.branch_label || log.branch}`, type: 'primary' });
|
||||||
@@ -41,9 +55,15 @@ function getMetaItems(log: ExecutionLogEntry) {
|
|||||||
if (metadata.subflow_name) {
|
if (metadata.subflow_name) {
|
||||||
items.push({ label: `子流程: ${metadata.subflow_name}`, type: 'success' });
|
items.push({ label: `子流程: ${metadata.subflow_name}`, type: 'success' });
|
||||||
}
|
}
|
||||||
|
if (metadata.model || metadata.model_id) {
|
||||||
|
items.push({ label: `模型: ${metadata.model || metadata.model_id}`, type: 'info' });
|
||||||
|
}
|
||||||
if (log.tokens_used) {
|
if (log.tokens_used) {
|
||||||
items.push({ label: `Token ${log.tokens_used}`, type: 'info' });
|
items.push({ label: `Token ${log.tokens_used}`, type: 'info' });
|
||||||
}
|
}
|
||||||
|
if (communication.channel) {
|
||||||
|
items.push({ label: getChannelLabel(communication.channel), type: 'info' });
|
||||||
|
}
|
||||||
if (log.status === 'waiting') {
|
if (log.status === 'waiting') {
|
||||||
items.push({ label: '等待用户输入', type: 'warning' });
|
items.push({ label: '等待用户输入', type: 'warning' });
|
||||||
}
|
}
|
||||||
@@ -51,12 +71,63 @@ function getMetaItems(log: ExecutionLogEntry) {
|
|||||||
return items;
|
return items;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getChannelLabel(channel: string) {
|
||||||
|
const labels: Record<string, string> = {
|
||||||
|
human_input: '人机协作',
|
||||||
|
parallel_branch: '并行分支',
|
||||||
|
subflow: '子流程',
|
||||||
|
workflow_edge: '流程传递',
|
||||||
|
};
|
||||||
|
return labels[channel] || channel;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getEventTypeLabel(type?: string) {
|
||||||
|
const labels: Record<string, string> = {
|
||||||
|
answer: '回复',
|
||||||
|
complete: '完成',
|
||||||
|
error: '错误',
|
||||||
|
llm_chunk: 'LLM 输出',
|
||||||
|
message: '消息',
|
||||||
|
node_complete: '节点完成',
|
||||||
|
node_event: '节点事件',
|
||||||
|
node_start: '节点开始',
|
||||||
|
parallel_complete: '并行完成',
|
||||||
|
parallel_start: '并行开始',
|
||||||
|
start: '开始',
|
||||||
|
waiting_input: '等待输入',
|
||||||
|
};
|
||||||
|
return labels[type || ''] || type || '事件';
|
||||||
|
}
|
||||||
|
|
||||||
|
function getEventSummary(event: WorkflowRunEvent) {
|
||||||
|
if (event.content) return event.content;
|
||||||
|
if (event.message) return event.message;
|
||||||
|
if (event.error_message) return event.error_message;
|
||||||
|
if (event.error) return event.error;
|
||||||
|
if (event.event) return getEventSummary(event.event);
|
||||||
|
return stringifyBrief(event, 140);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLogEvents(log: ExecutionLogEntry): WorkflowRunEvent[] {
|
||||||
|
const directEvents = Array.isArray(log.events) ? log.events : [];
|
||||||
|
const metadataEvents = Array.isArray(log.metadata?.events)
|
||||||
|
? log.metadata.events
|
||||||
|
: [];
|
||||||
|
return [...directEvents, ...metadataEvents].slice(0, 4);
|
||||||
|
}
|
||||||
|
|
||||||
function getLogDetail(log: ExecutionLogEntry) {
|
function getLogDetail(log: ExecutionLogEntry) {
|
||||||
|
const communication = getCommunication(log);
|
||||||
if (log.error) return log.error;
|
if (log.error) return log.error;
|
||||||
if (log.status === 'waiting') {
|
if (log.status === 'waiting') {
|
||||||
return stringifyBrief(log.metadata?.waiting_config || log.metadata?.config || log.output);
|
return stringifyBrief(log.metadata?.waiting_config || log.metadata?.config || log.output);
|
||||||
}
|
}
|
||||||
return stringifyBrief(log.output || log.metadata?.branch_results || log.metadata?.result);
|
return stringifyBrief(
|
||||||
|
log.output
|
||||||
|
|| log.metadata?.branch_results
|
||||||
|
|| log.metadata?.result
|
||||||
|
|| communication.message,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -104,6 +175,26 @@ function getLogDetail(log: ExecutionLogEntry) {
|
|||||||
>
|
>
|
||||||
{{ getLogDetail(log) }}
|
{{ getLogDetail(log) }}
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="getLogEvents(log).length"
|
||||||
|
class="bg-muted/40 border-border mt-2 rounded border px-2 py-1.5"
|
||||||
|
>
|
||||||
|
<div class="text-muted-foreground mb-1 text-xs font-medium">
|
||||||
|
运行事件
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-for="(event, eventIndex) in getLogEvents(log)"
|
||||||
|
:key="`${log.node_id}-event-${eventIndex}`"
|
||||||
|
class="text-muted-foreground flex gap-2 text-xs"
|
||||||
|
>
|
||||||
|
<span class="shrink-0 font-medium">
|
||||||
|
{{ getEventTypeLabel(event.type || event.event?.type) }}
|
||||||
|
</span>
|
||||||
|
<span class="line-clamp-2 min-w-0 whitespace-pre-wrap">
|
||||||
|
{{ getEventSummary(event) }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</button>
|
</button>
|
||||||
</ElTimelineItem>
|
</ElTimelineItem>
|
||||||
</ElTimeline>
|
</ElTimeline>
|
||||||
|
|||||||
@@ -54,79 +54,12 @@ const components: Record<string, any> = {
|
|||||||
),
|
),
|
||||||
// 循环节点
|
// 循环节点
|
||||||
loop: defineAsyncComponent(() => import('../panels/LoopPanel.vue')),
|
loop: defineAsyncComponent(() => import('../panels/LoopPanel.vue')),
|
||||||
// 表单节点
|
|
||||||
form_basic_info: defineAsyncComponent(
|
|
||||||
() => import('../panels/FormBasicInfoPanel.vue'),
|
|
||||||
),
|
|
||||||
form_database_design: defineAsyncComponent(
|
|
||||||
() => import('../panels/FormDatabaseDesignPanel.vue'),
|
|
||||||
),
|
|
||||||
form_database_create: defineAsyncComponent(
|
|
||||||
() => import('../panels/FormDatabaseCreatePanel.vue'),
|
|
||||||
),
|
|
||||||
form_ui_design: defineAsyncComponent(
|
|
||||||
() => import('../panels/FormUIDesignPanel.vue'),
|
|
||||||
),
|
|
||||||
form_list_design: defineAsyncComponent(
|
|
||||||
() => import('../panels/FormListDesignPanel.vue'),
|
|
||||||
),
|
|
||||||
form_create: defineAsyncComponent(
|
|
||||||
() => import('../panels/FormCreatePanel.vue'),
|
|
||||||
),
|
|
||||||
form_publish: defineAsyncComponent(
|
|
||||||
() => import('../panels/FormPublishPanel.vue'),
|
|
||||||
),
|
|
||||||
// 应用节点
|
|
||||||
app_create: defineAsyncComponent(
|
|
||||||
() => import('../panels/AppCreatePanel.vue'),
|
|
||||||
),
|
|
||||||
app_design: defineAsyncComponent(
|
|
||||||
() => import('../panels/AppDesignPanel.vue'),
|
|
||||||
),
|
|
||||||
app_settings: defineAsyncComponent(
|
|
||||||
() => import('../panels/AppSettingsPanel.vue'),
|
|
||||||
),
|
|
||||||
app_update: defineAsyncComponent(
|
|
||||||
() => import('../panels/AppUpdatePanel.vue'),
|
|
||||||
),
|
|
||||||
// 仪表盘节点
|
|
||||||
dashboard_basic_info: defineAsyncComponent(
|
|
||||||
() => import('../panels/DashboardBasicInfoPanel.vue'),
|
|
||||||
),
|
|
||||||
dashboard_design: defineAsyncComponent(
|
|
||||||
() => import('../panels/DashboardDesignPanel.vue'),
|
|
||||||
),
|
|
||||||
dashboard_create: defineAsyncComponent(
|
|
||||||
() => import('../panels/DashboardCreatePanel.vue'),
|
|
||||||
),
|
|
||||||
dashboard_publish: defineAsyncComponent(
|
|
||||||
() => import('../panels/DashboardPublishPanel.vue'),
|
|
||||||
),
|
|
||||||
// 系统总结节点
|
// 系统总结节点
|
||||||
system_summary: defineAsyncComponent(
|
system_summary: defineAsyncComponent(
|
||||||
() => import('../panels/SystemSummaryPanel.vue'),
|
() => import('../panels/SystemSummaryPanel.vue'),
|
||||||
),
|
),
|
||||||
// 子流程节点
|
// 子流程节点
|
||||||
subflow: defineAsyncComponent(() => import('../panels/SubflowPanel.vue')),
|
subflow: defineAsyncComponent(() => import('../panels/SubflowPanel.vue')),
|
||||||
// 表单数据节点
|
|
||||||
form_data_create: defineAsyncComponent(
|
|
||||||
() => import('../panels/FormDataPanel.vue'),
|
|
||||||
),
|
|
||||||
form_data_read: defineAsyncComponent(
|
|
||||||
() => import('../panels/FormDataPanel.vue'),
|
|
||||||
),
|
|
||||||
form_data_update: defineAsyncComponent(
|
|
||||||
() => import('../panels/FormDataPanel.vue'),
|
|
||||||
),
|
|
||||||
form_data_delete: defineAsyncComponent(
|
|
||||||
() => import('../panels/FormDataPanel.vue'),
|
|
||||||
),
|
|
||||||
form_data_list: defineAsyncComponent(
|
|
||||||
() => import('../panels/FormDataPanel.vue'),
|
|
||||||
),
|
|
||||||
form_schema_to_llm: defineAsyncComponent(
|
|
||||||
() => import('../panels/FormDataPanel.vue'),
|
|
||||||
),
|
|
||||||
// Text-to-SQL 节点
|
// Text-to-SQL 节点
|
||||||
text_to_sql: defineAsyncComponent(
|
text_to_sql: defineAsyncComponent(
|
||||||
() => import('../panels/TextToSqlPanel.vue'),
|
() => import('../panels/TextToSqlPanel.vue'),
|
||||||
|
|||||||
@@ -27,14 +27,11 @@ import {
|
|||||||
Database,
|
Database,
|
||||||
DatabaseBackup,
|
DatabaseBackup,
|
||||||
DatabaseZap,
|
DatabaseZap,
|
||||||
FilePlus,
|
|
||||||
FileText,
|
|
||||||
Fullscreen,
|
Fullscreen,
|
||||||
GitBranch,
|
GitBranch,
|
||||||
GitFork,
|
GitFork,
|
||||||
Globe,
|
Globe,
|
||||||
HelpCircle,
|
HelpCircle,
|
||||||
LayoutDashboard,
|
|
||||||
LayoutTemplate,
|
LayoutTemplate,
|
||||||
ListChecks,
|
ListChecks,
|
||||||
Map,
|
Map,
|
||||||
@@ -45,13 +42,9 @@ import {
|
|||||||
Plus,
|
Plus,
|
||||||
Redo2,
|
Redo2,
|
||||||
RefreshCw,
|
RefreshCw,
|
||||||
Save,
|
|
||||||
Search,
|
Search,
|
||||||
Send,
|
|
||||||
Settings,
|
|
||||||
Snowflake,
|
Snowflake,
|
||||||
Square,
|
Square,
|
||||||
TableProperties,
|
|
||||||
Trash2,
|
Trash2,
|
||||||
Undo2,
|
Undo2,
|
||||||
Upload,
|
Upload,
|
||||||
@@ -84,32 +77,16 @@ import ContextMenu from './components/ContextMenu.vue';
|
|||||||
import Panel from './components/Panel.vue';
|
import Panel from './components/Panel.vue';
|
||||||
import VersionHistoryPanel from './components/VersionHistoryPanel.vue';
|
import VersionHistoryPanel from './components/VersionHistoryPanel.vue';
|
||||||
import AddButtonEdge from './edges/AddButtonEdge.vue';
|
import AddButtonEdge from './edges/AddButtonEdge.vue';
|
||||||
import AppCreateNode from './nodes/AppCreateNode.vue';
|
|
||||||
import AppDesignNode from './nodes/AppDesignNode.vue';
|
|
||||||
import AppSettingsNode from './nodes/AppSettingsNode.vue';
|
|
||||||
import AppUpdateNode from './nodes/AppUpdateNode.vue';
|
|
||||||
import ChoiceNode from './nodes/ChoiceNode.vue';
|
import ChoiceNode from './nodes/ChoiceNode.vue';
|
||||||
import CodeNode from './nodes/CodeNode.vue';
|
import CodeNode from './nodes/CodeNode.vue';
|
||||||
import ConditionNode from './nodes/ConditionNode.vue';
|
import ConditionNode from './nodes/ConditionNode.vue';
|
||||||
import ConfirmNode from './nodes/ConfirmNode.vue';
|
import ConfirmNode from './nodes/ConfirmNode.vue';
|
||||||
import DashboardBasicInfoNode from './nodes/DashboardBasicInfoNode.vue';
|
|
||||||
import DashboardCreateNode from './nodes/DashboardCreateNode.vue';
|
|
||||||
import DashboardDesignNode from './nodes/DashboardDesignNode.vue';
|
|
||||||
import DashboardPublishNode from './nodes/DashboardPublishNode.vue';
|
|
||||||
import DbDeleteNode from './nodes/DbDeleteNode.vue';
|
import DbDeleteNode from './nodes/DbDeleteNode.vue';
|
||||||
import DbInsertNode from './nodes/DbInsertNode.vue';
|
import DbInsertNode from './nodes/DbInsertNode.vue';
|
||||||
import DbQueryNode from './nodes/DbQueryNode.vue';
|
import DbQueryNode from './nodes/DbQueryNode.vue';
|
||||||
import DbSqlNode from './nodes/DbSqlNode.vue';
|
import DbSqlNode from './nodes/DbSqlNode.vue';
|
||||||
import DbUpdateNode from './nodes/DbUpdateNode.vue';
|
import DbUpdateNode from './nodes/DbUpdateNode.vue';
|
||||||
import EndNode from './nodes/EndNode.vue';
|
import EndNode from './nodes/EndNode.vue';
|
||||||
import FormBasicInfoNode from './nodes/FormBasicInfoNode.vue';
|
|
||||||
import FormCreateNode from './nodes/FormCreateNode.vue';
|
|
||||||
import FormDatabaseCreateNode from './nodes/FormDatabaseCreateNode.vue';
|
|
||||||
import FormDatabaseDesignNode from './nodes/FormDatabaseDesignNode.vue';
|
|
||||||
import FormDataNode from './nodes/FormDataNode.vue';
|
|
||||||
import FormListDesignNode from './nodes/FormListDesignNode.vue';
|
|
||||||
import FormPublishNode from './nodes/FormPublishNode.vue';
|
|
||||||
import FormUIDesignNode from './nodes/FormUIDesignNode.vue';
|
|
||||||
import HttpNode from './nodes/HttpNode.vue';
|
import HttpNode from './nodes/HttpNode.vue';
|
||||||
import IntentNode from './nodes/IntentNode.vue';
|
import IntentNode from './nodes/IntentNode.vue';
|
||||||
import KnowledgeRetrievalNode from './nodes/KnowledgeRetrievalNode.vue';
|
import KnowledgeRetrievalNode from './nodes/KnowledgeRetrievalNode.vue';
|
||||||
@@ -184,35 +161,10 @@ const nodeTypes = {
|
|||||||
snowflake_cortex_analyst: markRaw(SnowflakeCortexAnalystNode),
|
snowflake_cortex_analyst: markRaw(SnowflakeCortexAnalystNode),
|
||||||
// 循环节点
|
// 循环节点
|
||||||
loop: markRaw(LoopNode),
|
loop: markRaw(LoopNode),
|
||||||
// 表单节点
|
|
||||||
form_basic_info: markRaw(FormBasicInfoNode),
|
|
||||||
form_database_design: markRaw(FormDatabaseDesignNode),
|
|
||||||
form_database_create: markRaw(FormDatabaseCreateNode),
|
|
||||||
form_ui_design: markRaw(FormUIDesignNode),
|
|
||||||
form_list_design: markRaw(FormListDesignNode),
|
|
||||||
form_create: markRaw(FormCreateNode),
|
|
||||||
form_publish: markRaw(FormPublishNode),
|
|
||||||
// 应用管理节点
|
|
||||||
app_create: markRaw(AppCreateNode),
|
|
||||||
app_design: markRaw(AppDesignNode),
|
|
||||||
app_settings: markRaw(AppSettingsNode),
|
|
||||||
app_update: markRaw(AppUpdateNode),
|
|
||||||
// 仪表盘节点
|
|
||||||
dashboard_basic_info: markRaw(DashboardBasicInfoNode),
|
|
||||||
dashboard_design: markRaw(DashboardDesignNode),
|
|
||||||
dashboard_create: markRaw(DashboardCreateNode),
|
|
||||||
dashboard_publish: markRaw(DashboardPublishNode),
|
|
||||||
// 系统总结节点
|
// 系统总结节点
|
||||||
system_summary: markRaw(SystemSummaryNode),
|
system_summary: markRaw(SystemSummaryNode),
|
||||||
// 子流程节点
|
// 子流程节点
|
||||||
subflow: markRaw(SubflowNode),
|
subflow: markRaw(SubflowNode),
|
||||||
// 表单数据节点
|
|
||||||
form_data_create: markRaw(FormDataNode),
|
|
||||||
form_data_read: markRaw(FormDataNode),
|
|
||||||
form_data_update: markRaw(FormDataNode),
|
|
||||||
form_data_delete: markRaw(FormDataNode),
|
|
||||||
form_data_list: markRaw(FormDataNode),
|
|
||||||
form_schema_to_llm: markRaw(FormDataNode),
|
|
||||||
// Text-to-SQL 节点
|
// Text-to-SQL 节点
|
||||||
text_to_sql: markRaw(TextToSqlNode),
|
text_to_sql: markRaw(TextToSqlNode),
|
||||||
// 知识库节点
|
// 知识库节点
|
||||||
@@ -390,94 +342,6 @@ const nodeCategories = computed(() => [
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: $t('ai-platform.workflow.editor.categories.formDesign'),
|
|
||||||
nodes: [
|
|
||||||
{
|
|
||||||
type: 'form_basic_info',
|
|
||||||
label: getNodeLabel('form_basic_info'),
|
|
||||||
icon: 'FileText',
|
|
||||||
color: 'purple',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'form_database_design',
|
|
||||||
label: getNodeLabel('form_database_design'),
|
|
||||||
icon: 'Database',
|
|
||||||
color: 'purple',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'form_database_create',
|
|
||||||
label: getNodeLabel('form_database_create'),
|
|
||||||
icon: 'DatabaseZap',
|
|
||||||
color: 'purple',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'form_ui_design',
|
|
||||||
label: getNodeLabel('form_ui_design'),
|
|
||||||
icon: 'LayoutTemplate',
|
|
||||||
color: 'purple',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'form_list_design',
|
|
||||||
label: getNodeLabel('form_list_design'),
|
|
||||||
icon: 'TableProperties',
|
|
||||||
color: 'purple',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'form_create',
|
|
||||||
label: getNodeLabel('form_create'),
|
|
||||||
icon: 'FilePlus',
|
|
||||||
color: 'purple',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'form_publish',
|
|
||||||
label: getNodeLabel('form_publish'),
|
|
||||||
icon: 'Send',
|
|
||||||
color: 'purple',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: $t('ai-platform.workflow.editor.categories.formData'),
|
|
||||||
nodes: [
|
|
||||||
{
|
|
||||||
type: 'form_data_create',
|
|
||||||
label: getNodeLabel('form_data_create'),
|
|
||||||
icon: 'FilePlus',
|
|
||||||
color: 'green',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'form_data_read',
|
|
||||||
label: getNodeLabel('form_data_read'),
|
|
||||||
icon: 'FileText',
|
|
||||||
color: 'blue',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'form_data_update',
|
|
||||||
label: getNodeLabel('form_data_update'),
|
|
||||||
icon: 'FileText',
|
|
||||||
color: 'orange',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'form_data_delete',
|
|
||||||
label: getNodeLabel('form_data_delete'),
|
|
||||||
icon: 'Trash2',
|
|
||||||
color: 'red',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'form_data_list',
|
|
||||||
label: getNodeLabel('form_data_list'),
|
|
||||||
icon: 'Search',
|
|
||||||
color: 'blue',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'form_schema_to_llm',
|
|
||||||
label: getNodeLabel('form_schema_to_llm'),
|
|
||||||
icon: 'Code',
|
|
||||||
color: 'purple',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: $t('ai-platform.workflow.editor.categories.dialog'),
|
name: $t('ai-platform.workflow.editor.categories.dialog'),
|
||||||
nodes: [
|
nodes: [
|
||||||
@@ -587,6 +451,12 @@ const nodeCategories = computed(() => [
|
|||||||
icon: 'Workflow',
|
icon: 'Workflow',
|
||||||
color: 'purple',
|
color: 'purple',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
type: 'system_summary',
|
||||||
|
label: getNodeLabel('system_summary'),
|
||||||
|
icon: 'ClipboardCheck',
|
||||||
|
color: 'purple',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
type: 'end',
|
type: 'end',
|
||||||
label: getNodeLabel('end'),
|
label: getNodeLabel('end'),
|
||||||
@@ -612,72 +482,6 @@ const nodeCategories = computed(() => [
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: $t('ai-platform.workflow.editor.categories.appManage'),
|
|
||||||
nodes: [
|
|
||||||
{
|
|
||||||
type: 'app_create',
|
|
||||||
label: getNodeLabel('app_create'),
|
|
||||||
icon: 'LayoutTemplate',
|
|
||||||
color: 'indigo',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'app_design',
|
|
||||||
label: getNodeLabel('app_design'),
|
|
||||||
icon: 'LayoutDashboard',
|
|
||||||
color: 'indigo',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'app_settings',
|
|
||||||
label: getNodeLabel('app_settings'),
|
|
||||||
icon: 'Settings',
|
|
||||||
color: 'indigo',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'app_update',
|
|
||||||
label: getNodeLabel('app_update'),
|
|
||||||
icon: 'Save',
|
|
||||||
color: 'indigo',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: $t('ai-platform.workflow.editor.categories.dashboard'),
|
|
||||||
icon: 'LayoutDashboard',
|
|
||||||
color: 'cyan',
|
|
||||||
nodes: [
|
|
||||||
{
|
|
||||||
type: 'dashboard_basic_info',
|
|
||||||
label: getNodeLabel('dashboard_basic_info'),
|
|
||||||
icon: 'FileText',
|
|
||||||
color: 'cyan',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'dashboard_design',
|
|
||||||
label: getNodeLabel('dashboard_design'),
|
|
||||||
icon: 'LayoutDashboard',
|
|
||||||
color: 'cyan',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'dashboard_create',
|
|
||||||
label: getNodeLabel('dashboard_create'),
|
|
||||||
icon: 'FilePlus',
|
|
||||||
color: 'cyan',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'dashboard_publish',
|
|
||||||
label: getNodeLabel('dashboard_publish'),
|
|
||||||
icon: 'Upload',
|
|
||||||
color: 'cyan',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'system_summary',
|
|
||||||
label: getNodeLabel('system_summary'),
|
|
||||||
icon: 'ClipboardCheck',
|
|
||||||
color: 'cyan',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: $t('ai-platform.workflow.editor.categories.knowledge'),
|
name: $t('ai-platform.workflow.editor.categories.knowledge'),
|
||||||
nodes: [
|
nodes: [
|
||||||
@@ -720,17 +524,6 @@ const iconComponents: Record<string, any> = {
|
|||||||
// 循环节点图标
|
// 循环节点图标
|
||||||
RefreshCw,
|
RefreshCw,
|
||||||
CornerDownLeft,
|
CornerDownLeft,
|
||||||
// 表单节点图标
|
|
||||||
FileText,
|
|
||||||
TableProperties,
|
|
||||||
Send,
|
|
||||||
// 应用节点图标
|
|
||||||
LayoutDashboard,
|
|
||||||
Settings,
|
|
||||||
Save,
|
|
||||||
// 仪表盘节点图标
|
|
||||||
FilePlus,
|
|
||||||
Upload,
|
|
||||||
// 系统总结节点图标
|
// 系统总结节点图标
|
||||||
ClipboardCheck,
|
ClipboardCheck,
|
||||||
// 子流程节点图标
|
// 子流程节点图标
|
||||||
@@ -741,21 +534,7 @@ const iconComponents: Record<string, any> = {
|
|||||||
|
|
||||||
// 过滤后的节点分类
|
// 过滤后的节点分类
|
||||||
const filteredNodeCategories = computed(() => {
|
const filteredNodeCategories = computed(() => {
|
||||||
// 首先根据工作流类型过滤分类
|
const categories = nodeCategories.value;
|
||||||
let categories = nodeCategories.value;
|
|
||||||
|
|
||||||
// 如果是通用类型,排除表单设计、应用管理、仪表盘分组
|
|
||||||
if (workflow.value?.workflow_type === 'general') {
|
|
||||||
const excludeNames = new Set([
|
|
||||||
$t('ai-platform.workflow.editor.categories.appManage'),
|
|
||||||
$t('ai-platform.workflow.editor.categories.dashboard'),
|
|
||||||
$t('ai-platform.workflow.editor.categories.formDesign'),
|
|
||||||
]);
|
|
||||||
categories = nodeCategories.value.filter(
|
|
||||||
(category: { name: string; nodes: any[] }) =>
|
|
||||||
!excludeNames.has(category.name),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 再根据搜索关键词过滤
|
// 再根据搜索关键词过滤
|
||||||
if (!nodeSearchQuery.value.trim()) {
|
if (!nodeSearchQuery.value.trim()) {
|
||||||
|
|||||||
@@ -1,32 +1,16 @@
|
|||||||
import { markRaw } from 'vue';
|
import { markRaw } from 'vue';
|
||||||
|
|
||||||
import AddButtonEdge from '../editor/edges/AddButtonEdge.vue';
|
import AddButtonEdge from '../editor/edges/AddButtonEdge.vue';
|
||||||
import AppCreateNode from '../editor/nodes/AppCreateNode.vue';
|
|
||||||
import AppDesignNode from '../editor/nodes/AppDesignNode.vue';
|
|
||||||
import AppSettingsNode from '../editor/nodes/AppSettingsNode.vue';
|
|
||||||
import AppUpdateNode from '../editor/nodes/AppUpdateNode.vue';
|
|
||||||
import ChoiceNode from '../editor/nodes/ChoiceNode.vue';
|
import ChoiceNode from '../editor/nodes/ChoiceNode.vue';
|
||||||
import CodeNode from '../editor/nodes/CodeNode.vue';
|
import CodeNode from '../editor/nodes/CodeNode.vue';
|
||||||
import ConditionNode from '../editor/nodes/ConditionNode.vue';
|
import ConditionNode from '../editor/nodes/ConditionNode.vue';
|
||||||
import ConfirmNode from '../editor/nodes/ConfirmNode.vue';
|
import ConfirmNode from '../editor/nodes/ConfirmNode.vue';
|
||||||
import DashboardBasicInfoNode from '../editor/nodes/DashboardBasicInfoNode.vue';
|
|
||||||
import DashboardCreateNode from '../editor/nodes/DashboardCreateNode.vue';
|
|
||||||
import DashboardDesignNode from '../editor/nodes/DashboardDesignNode.vue';
|
|
||||||
import DashboardPublishNode from '../editor/nodes/DashboardPublishNode.vue';
|
|
||||||
import DbDeleteNode from '../editor/nodes/DbDeleteNode.vue';
|
import DbDeleteNode from '../editor/nodes/DbDeleteNode.vue';
|
||||||
import DbInsertNode from '../editor/nodes/DbInsertNode.vue';
|
import DbInsertNode from '../editor/nodes/DbInsertNode.vue';
|
||||||
import DbQueryNode from '../editor/nodes/DbQueryNode.vue';
|
import DbQueryNode from '../editor/nodes/DbQueryNode.vue';
|
||||||
import DbSqlNode from '../editor/nodes/DbSqlNode.vue';
|
import DbSqlNode from '../editor/nodes/DbSqlNode.vue';
|
||||||
import DbUpdateNode from '../editor/nodes/DbUpdateNode.vue';
|
import DbUpdateNode from '../editor/nodes/DbUpdateNode.vue';
|
||||||
import EndNode from '../editor/nodes/EndNode.vue';
|
import EndNode from '../editor/nodes/EndNode.vue';
|
||||||
import FormBasicInfoNode from '../editor/nodes/FormBasicInfoNode.vue';
|
|
||||||
import FormCreateNode from '../editor/nodes/FormCreateNode.vue';
|
|
||||||
import FormDatabaseCreateNode from '../editor/nodes/FormDatabaseCreateNode.vue';
|
|
||||||
import FormDatabaseDesignNode from '../editor/nodes/FormDatabaseDesignNode.vue';
|
|
||||||
import FormDataNode from '../editor/nodes/FormDataNode.vue';
|
|
||||||
import FormListDesignNode from '../editor/nodes/FormListDesignNode.vue';
|
|
||||||
import FormPublishNode from '../editor/nodes/FormPublishNode.vue';
|
|
||||||
import FormUIDesignNode from '../editor/nodes/FormUIDesignNode.vue';
|
|
||||||
import HttpNode from '../editor/nodes/HttpNode.vue';
|
import HttpNode from '../editor/nodes/HttpNode.vue';
|
||||||
import IntentNode from '../editor/nodes/IntentNode.vue';
|
import IntentNode from '../editor/nodes/IntentNode.vue';
|
||||||
import KnowledgeRetrievalNode from '../editor/nodes/KnowledgeRetrievalNode.vue';
|
import KnowledgeRetrievalNode from '../editor/nodes/KnowledgeRetrievalNode.vue';
|
||||||
@@ -67,29 +51,8 @@ export const workflowNodeTypes = {
|
|||||||
snowflake_cortex_llm: markRaw(SnowflakeCortexLLMNode),
|
snowflake_cortex_llm: markRaw(SnowflakeCortexLLMNode),
|
||||||
snowflake_cortex_analyst: markRaw(SnowflakeCortexAnalystNode),
|
snowflake_cortex_analyst: markRaw(SnowflakeCortexAnalystNode),
|
||||||
loop: markRaw(LoopNode),
|
loop: markRaw(LoopNode),
|
||||||
form_basic_info: markRaw(FormBasicInfoNode),
|
|
||||||
form_database_design: markRaw(FormDatabaseDesignNode),
|
|
||||||
form_database_create: markRaw(FormDatabaseCreateNode),
|
|
||||||
form_ui_design: markRaw(FormUIDesignNode),
|
|
||||||
form_list_design: markRaw(FormListDesignNode),
|
|
||||||
form_create: markRaw(FormCreateNode),
|
|
||||||
form_publish: markRaw(FormPublishNode),
|
|
||||||
app_create: markRaw(AppCreateNode),
|
|
||||||
app_design: markRaw(AppDesignNode),
|
|
||||||
app_settings: markRaw(AppSettingsNode),
|
|
||||||
app_update: markRaw(AppUpdateNode),
|
|
||||||
dashboard_basic_info: markRaw(DashboardBasicInfoNode),
|
|
||||||
dashboard_design: markRaw(DashboardDesignNode),
|
|
||||||
dashboard_create: markRaw(DashboardCreateNode),
|
|
||||||
dashboard_publish: markRaw(DashboardPublishNode),
|
|
||||||
system_summary: markRaw(SystemSummaryNode),
|
system_summary: markRaw(SystemSummaryNode),
|
||||||
subflow: markRaw(SubflowNode),
|
subflow: markRaw(SubflowNode),
|
||||||
form_data_create: markRaw(FormDataNode),
|
|
||||||
form_data_read: markRaw(FormDataNode),
|
|
||||||
form_data_update: markRaw(FormDataNode),
|
|
||||||
form_data_delete: markRaw(FormDataNode),
|
|
||||||
form_data_list: markRaw(FormDataNode),
|
|
||||||
form_schema_to_llm: markRaw(FormDataNode),
|
|
||||||
text_to_sql: markRaw(TextToSqlNode),
|
text_to_sql: markRaw(TextToSqlNode),
|
||||||
knowledge_retrieval: markRaw(KnowledgeRetrievalNode),
|
knowledge_retrieval: markRaw(KnowledgeRetrievalNode),
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user