Browse Source

feat: 添加模块调用节点

jiaxing.liao 4 days ago
parent
commit
3e27babef5

+ 8 - 0
apps/web/src/i18n/locales/en-us.ts

@@ -1426,6 +1426,10 @@ export default {
 				postgresql: 'PostgreSQL'
 			}
 		},
+		moduleInvokeSetter: {
+			interfaceCode: 'Interface Code',
+			interfaceCodeRequired: 'Please enter the interface code'
+		},
 		questionClassifierSetter: {
 			classes: 'Categories',
 			addClass: '+ Add Category',
@@ -1506,6 +1510,10 @@ export default {
 			},
 			database: { displayName: 'Database Query', description: 'Query data through a database' },
 			code: { displayName: 'Code', description: 'Process data with code' },
+			'module-invoke': {
+				displayName: 'Module Invoke',
+				description: 'Invoke a module through interface code'
+			},
 			iteration: { displayName: 'Iteration', description: 'Iteration node' },
 			loop: { displayName: 'Loop', description: 'Loop node' },
 			'list-operator': { displayName: 'List Operations', description: 'List operation node' },

+ 5 - 0
apps/web/src/i18n/locales/zh-cn.ts

@@ -1305,6 +1305,10 @@ export default {
 				postgresql: 'PostgreSQL'
 			}
 		},
+		moduleInvokeSetter: {
+			interfaceCode: '接口代码',
+			interfaceCodeRequired: '请输入接口代码'
+		},
 		questionClassifierSetter: {
 			classes: '分类',
 			addClass: '+ 添加分类',
@@ -1393,6 +1397,7 @@ export default {
 			other: '其他'
 		},
 		meta: {
+			'module-invoke': { displayName: '模块调用', description: '通过接口代码调用模块能力' },
 			start: { displayName: '用户输入', description: '用户输入节点,用于接收用户输入' },
 			end: { displayName: '输出', description: '流程结束并输出节点' },
 			'http-request': { displayName: 'HTTP 请求', description: '通过 HTTP 请求获取数据' },

+ 1 - 0
apps/web/src/nodes/i18n.ts

@@ -8,6 +8,7 @@ const NODE_GROUP_KEYS: Record<string, 'start' | 'logic' | 'data' | 'other'> = {
 	database: 'data',
 	'database-query': 'data',
 	code: 'logic',
+	'module-invoke': 'data',
 	iteration: 'logic',
 	loop: 'logic',
 	'list-operator': 'data',

+ 3 - 1
apps/web/src/nodes/src/index.ts

@@ -11,6 +11,7 @@ import { questionClassifierNode } from './question-classifier'
 import { loopEndNode } from './loop-end'
 import { triggerScheduleNode } from './schedule'
 import { webhookNode } from './webhook'
+import { moduleInvokeNode } from './module-invoke'
 import { getNodeDisplayName } from '@/nodes/i18n'
 import type { INodeType } from '../Interface'
 
@@ -65,7 +66,8 @@ const nodes = [
 	iterationStartNode,
 	loopEndNode,
 	triggerScheduleNode,
-	webhookNode
+	webhookNode,
+	moduleInvokeNode
 ]
 
 const nodeMap = nodes.reduce(

+ 46 - 0
apps/web/src/nodes/src/module-invoke/index.ts

@@ -0,0 +1,46 @@
+import { NodeConnectionTypes, type INodeDataBaseSchema, type INodeType } from '../../Interface'
+import Setter from './setter.vue'
+import i18n from '@/i18n'
+import { getNodeDescription, getNodeDisplayName } from '@/nodes/i18n'
+
+export type ModuleInvokeData = INodeDataBaseSchema & {
+	interface_code: string
+}
+
+export const moduleInvokeNode: INodeType = {
+	version: ['1'],
+	displayName: getNodeDisplayName('module-invoke'),
+	name: 'module-invoke',
+	Setter,
+	description: getNodeDescription('module-invoke'),
+	group: 'data',
+	icon: 'lucide:blocks',
+	iconColor: '#f79009',
+	inputs: [NodeConnectionTypes.main],
+	outputs: [NodeConnectionTypes.main],
+	validate: (data: ModuleInvokeData) => {
+		return data?.interface_code?.trim()
+			? false
+			: i18n.t('pages.moduleInvokeSetter.interfaceCodeRequired')
+	},
+	// getSubtitle: (node: ModuleInvokeData | { data?: ModuleInvokeData }) => {
+	// 	const interfaceCode = node?.interface_code ?? node?.data?.interface_code ?? ''
+	// 	return interfaceCode.split('\n').find((line) => line.trim())?.trim() || ''
+	// },
+	schema: {
+		appAgentId: '',
+		parentId: '',
+		position: {
+			x: 20,
+			y: 30
+		},
+		width: 96,
+		height: 96,
+		selected: false,
+		nodeType: 'module-invoke',
+		zIndex: 1,
+		data: {
+			interface_code: ''
+		}
+	}
+}

+ 61 - 0
apps/web/src/nodes/src/module-invoke/setter.vue

@@ -0,0 +1,61 @@
+<template>
+	<el-scrollbar class="w-full box-border p-12px">
+		<div class="module-invoke-setter">
+			<section class="section-block">
+				<div class="section-title">{{ texts.interfaceCode }}</div>
+				<CodeEditor
+					v-model="formData.interface_code"
+					:tools="false"
+					:allow-change-language="false"
+					language="javascript"
+					:height="240"
+				/>
+			</section>
+		</div>
+	</el-scrollbar>
+</template>
+
+<script setup lang="ts">
+import { computed } from 'vue'
+
+import CodeEditor from '@/nodes/_base/CodeEditor.vue'
+import { useI18n } from '@/composables/useI18n'
+import { useSetterModel } from '../_shared/useSetterModel'
+
+import type { ModuleInvokeData } from './index'
+
+interface Emits {
+	(e: 'update', value: ModuleInvokeData): void
+}
+
+const props = defineProps<{
+	data: ModuleInvokeData
+}>()
+
+const emit = defineEmits<Emits>()
+const { t } = useI18n()
+const formData = useSetterModel<ModuleInvokeData>(props, emit)
+
+const texts = computed(() => ({
+	interfaceCode: t('pages.moduleInvokeSetter.interfaceCode')
+}))
+</script>
+
+<style scoped lang="less">
+.module-invoke-setter {
+	display: flex;
+	flex-direction: column;
+	gap: 16px;
+}
+
+.section-block {
+	padding-bottom: 16px;
+}
+
+.section-title {
+	margin-bottom: 12px;
+	font-size: 14px;
+	font-weight: 700;
+	color: #374151;
+}
+</style>

+ 0 - 1
packages/workflow/src/components/Canvas.vue

@@ -424,7 +424,6 @@ async function onNodesInitialized() {
 				maxZoom: 1
 			})
 		} else {
-			console.log('defaultViewport', defaultViewport.value)
 			await setViewport(defaultViewport.value)
 		}
 		loaded = true