Pārlūkot izejas kodu

fix: 修改自定义节点问题,调整发布节点选择框

jiaxing.liao 2 nedēļas atpakaļ
vecāks
revīzija
f2b02d67bb

+ 5 - 2
apps/web/src/features/nodeLibary/index.vue

@@ -106,7 +106,7 @@ const materials = computed<NodeGroup[]>(() => {
 			groupMap.get(groupId)!.nodes.push({
 				id: node.name,
 				type: node.schema.nodeType,
-				name: getNodeDisplayName(node.schema.nodeType),
+				name: node.displayName,
 				description: getNodeDescription(node.schema.nodeType),
 				icon: node.icon,
 				isImageIcon: isImageIcon(node.icon),
@@ -115,7 +115,9 @@ const materials = computed<NodeGroup[]>(() => {
 			})
 		})
 
-	return GROUP_ORDER.filter((groupId) => groupMap.has(groupId)).map((groupId) => groupMap.get(groupId)!)
+	return GROUP_ORDER.filter((groupId) => groupMap.has(groupId)).map(
+		(groupId) => groupMap.get(groupId)!
+	)
 })
 
 const activeGroup = ref('')
@@ -170,6 +172,7 @@ const onAddNode = (value: NodeItem) => {
 						<div class="flex flex-col">
 							<span>{{ item.name }}</span>
 							<span v-if="item.description" class="desc">{{ item.description }}</span>
+							<span v-if="item.raw?.author" class="desc">作者: {{ item.raw?.author }}</span>
 						</div>
 					</li>
 				</ul>

+ 1 - 1
apps/web/src/nodes/_base/PromptEditor/index.vue

@@ -204,7 +204,7 @@ function onChange(editorState: any) {
 	display: inline-flex;
 	align-items: center;
 	margin: 0 2px;
-	vertical-align: baseline;
+	vertical-align: middle;
 	position: relative;
 	top: -1px;
 	line-height: 1;

+ 1 - 1
apps/web/src/nodes/_base/VarInput.vue

@@ -1,5 +1,5 @@
 <template>
-	<div>
+	<div class="w-full">
 		<PromptEditor v-model="modelValue" v-bind="$attrs" />
 	</div>
 </template>

+ 4 - 4
apps/web/src/nodes/custom/CustomNodeSetter.vue

@@ -3,6 +3,7 @@ import { computed, watch } from 'vue'
 import NodeRuntimeConfig from '@/nodes/_base/NodeRuntimeConfig.vue'
 import { useSetterModel } from '@/nodes/src/_shared/useSetterModel'
 import SecretInput from '@/features/secretInput/SecretInput.vue'
+import VarInput from '../_base/VarInput.vue'
 import { nodeMap } from '../src'
 
 interface CustomNodeOption {
@@ -123,7 +124,7 @@ const setParameterValue = (parameter: CustomNodeParameter, value: unknown) => {
 					:title="parameter.description || 'Edge-link output, no value input needed'"
 				/> -->
 
-				<el-input
+				<VarInput
 					v-if="normalizeType(parameter.type) === 'text-input'"
 					:model-value="getParameterValue(parameter)"
 					:placeholder="parameter.placeholder || ''"
@@ -137,12 +138,11 @@ const setParameterValue = (parameter: CustomNodeParameter, value: unknown) => {
 					@update:model-value="setParameterValue(parameter, $event)"
 				/>
 
-				<el-input
+				<VarInput
 					v-else-if="normalizeType(parameter.type) === 'text-area'"
 					:model-value="getParameterValue(parameter)"
 					:placeholder="parameter.placeholder || ''"
-					type="textarea"
-					:autosize="{ minRows: 3, maxRows: 8 }"
+					:rows="3"
 					@update:model-value="setParameterValue(parameter, $event)"
 				/>
 

+ 20 - 92
apps/web/src/nodes/custom/parser.ts

@@ -3,12 +3,12 @@ import { getNodeDisplayName } from '@/nodes/i18n'
 import CustomNodeSetter from './CustomNodeSetter.vue'
 
 interface CustomIdentity {
-	type?: string
-	name?: string
-	label?: string
+	type: string
+	name: string
+	label: string
 	description?: string
 	icon40?: string
-	author?: string
+	author: string
 }
 
 interface CustomOption {
@@ -31,7 +31,7 @@ interface CustomParameter {
 }
 
 export interface CustomNodeSource {
-	identity?: CustomIdentity
+	identity: CustomIdentity
 	parameters?: CustomParameter[]
 	[key: string]: unknown
 }
@@ -41,33 +41,6 @@ const DEFAULT_NODE_ICON_COLOR = '#7c3aed'
 
 const toArray = <T>(value: T[] | undefined): T[] => (Array.isArray(value) ? value : [])
 
-const normalizeNodeType = (raw: CustomNodeSource) => {
-	const type =
-		raw.identity?.type ||
-		raw.identity?.name ||
-		(typeof raw.nodeType === 'string' ? raw.nodeType : '') ||
-		(typeof raw.type === 'string' ? raw.type : '')
-	return type?.trim?.() || ''
-}
-
-const normalizeNodeName = (raw: CustomNodeSource, nodeType: string) => {
-	const name = raw.identity?.name || nodeType
-	return name?.trim?.() || nodeType
-}
-
-const normalizeDisplayName = (raw: CustomNodeSource, nodeType: string) => {
-	return (
-		raw.identity?.label?.trim?.() ||
-		raw.identity?.name?.trim?.() ||
-		getNodeDisplayName(nodeType) ||
-		nodeType
-	)
-}
-
-const normalizeDescription = (raw: CustomNodeSource) => {
-	return raw.identity?.description?.trim?.() || ''
-}
-
 const resolveCustomIcon = (icon40?: string) => {
 	const raw = `${icon40 || ''}`.trim()
 	if (!raw) {
@@ -87,65 +60,19 @@ const resolveCustomIcon = (icon40?: string) => {
 	return raw
 }
 
-const getFormType = (type?: string) => {
-	const raw = `${type || ''}`.toLowerCase()
-	if (['text-input', 'text', 'string', 'input'].includes(raw)) return 'string'
-	if (['secret-input', 'password'].includes(raw)) return 'string'
-	if (['text-area', 'textarea'].includes(raw)) return 'string'
-	if (['number', 'int', 'integer', 'float', 'double'].includes(raw)) return 'number'
-	if (['checkbox', 'switch', 'boolean', 'bool'].includes(raw)) return 'boolean'
-	if (['select', 'enum'].includes(raw)) return 'string'
-	return 'string'
-}
-
 const isEdgeLinkParameter = (parameter: CustomParameter) => {
 	return `${parameter.type || ''}`.toLowerCase() === 'edg-link'
 }
 
-const toNodeVariableType = (type?: string): NodeVariable['type'] => {
-	const value = getFormType(type)
-	if (value === 'number') return 'number'
-	if (value === 'boolean') return 'boolean'
-	return 'string'
-}
-
-const buildDefaultParameters = (parameters: CustomParameter[]) => {
-	const defaults: Record<string, unknown> = {}
-
-	for (const parameter of parameters) {
-		if (!parameter.name) continue
-		if (parameter.defaultValue !== undefined) {
-			defaults[parameter.name] = parameter.defaultValue
-			continue
-		}
-
-		if (getFormType(parameter.type) === 'boolean') {
-			defaults[parameter.name] = false
-			continue
-		}
-
-		defaults[parameter.name] = ''
-	}
-
-	return defaults
-}
-
-const buildOutputVariables = (parameters: CustomParameter[]): NodeVariable[] => {
-	return parameters
-		.filter((parameter) => parameter.name && !isEdgeLinkParameter(parameter))
-		.map((parameter) => ({
-			name: parameter.name as string,
-			describe: parameter.label || parameter.description || '',
-			type: toNodeVariableType(parameter.type)
-		}))
-}
-
-const buildEdgeLinkPorts = (parameters: CustomParameter[]): EndpointType[] => {
+const buildEdgeLinkPorts = (
+	parameters: CustomParameter[],
+	data: Record<string, any>
+): EndpointType[] => {
 	return parameters
 		.filter((parameter) => parameter.name && isEdgeLinkParameter(parameter))
 		.map(
 			(parameter): EndpointType => ({
-				id: parameter.name as string,
+				id: data?.[parameter.name as string],
 				type: 'port',
 				label: parameter.label || parameter.name || ''
 			})
@@ -153,27 +80,29 @@ const buildEdgeLinkPorts = (parameters: CustomParameter[]): EndpointType[] => {
 }
 
 export const toCustomNodeType = (raw: CustomNodeSource): INodeType | null => {
-	const nodeType = normalizeNodeType(raw)
+	const nodeType = raw.identity.type
 	if (!nodeType) return null
+	console.log(111, raw)
 
 	const parameters = toArray(raw.parameters)
-	const displayName = normalizeDisplayName(raw, nodeType)
-	const nodeName = normalizeNodeName(raw, nodeType)
-	const edgeLinkPorts = buildEdgeLinkPorts(parameters)
-	const outputs: EndpointType[] = ['main', ...edgeLinkPorts]
+
+	const outputs = (data: Record<string, any>) => {
+		return ['main', ...buildEdgeLinkPorts(parameters, data)]
+	}
 
 	return {
 		version: ['1'],
 		group: 'custom',
-		name: nodeName,
-		displayName,
-		description: normalizeDescription(raw),
+		name: nodeType,
+		displayName: raw.identity.label,
+		description: raw.identity?.description,
 		icon: resolveCustomIcon(raw.identity?.icon40),
 		iconColor: DEFAULT_NODE_ICON_COLOR,
 		Setter: CustomNodeSetter,
 		inputs: ['main'],
 		outputs,
 		parameters,
+		author: raw.identity.author,
 		schema: {
 			appAgentId: '',
 			parentId: '',
@@ -186,7 +115,6 @@ export const toCustomNodeType = (raw: CustomNodeSource): INodeType | null => {
 			selected: false,
 			nodeType,
 			zIndex: 1,
-
 			data: {}
 		}
 	}

+ 3 - 0
apps/web/src/nodes/src/workflow-approval/index.ts

@@ -60,6 +60,9 @@ export const workflowApprovalNode: INodeType = {
 
 		return ports
 	},
+	validate: (data: WorkflowApprovalData) => {
+		return !!data?.workflowCode ? false : '流程编码为空'
+	},
 	getSubtitle: (data: WorkflowApprovalData) => {
 		return data?.workflowCode ? `workflow: ${data.workflowCode}` : ''
 	},

+ 35 - 16
apps/web/src/views/editor/PublishBtn.vue

@@ -1,22 +1,38 @@
 <template>
 	<el-popover
 		ref="popoverRef"
-		width="240px"
+		width="300px"
 		trigger="click"
-		:disabled="publishNodes.length <= 1 || isPublishing"
+		placement="bottom-end"
+		:disabled="isPublishing"
 	>
-		<div v-if="publishNodes.length > 1" class="publish-selector">
+		<div class="publish-selector">
 			<div class="publish-selector__title">{{ t('pages.editor.selectPublishNode') }}</div>
-			<button
-				v-for="node in publishNodes"
-				:key="node.id"
-				type="button"
-				class="publish-selector__item"
-				@click="handleSelectPublishNode(node.id)"
-			>
-				<span class="publish-selector__name">{{ node.name }}</span>
-				<span class="publish-selector__type">{{ getNodeDisplayName(node.nodeType) }}</span>
-			</button>
+			<div class="flex items-center gap-2">
+				<el-input v-model="search" placeholder="搜索" />
+			</div>
+			<el-divider class="my-0" />
+			<el-scrollbar height="280px">
+				<button
+					v-for="node in publishNodes"
+					:key="node.id"
+					type="button"
+					class="publish-selector__item"
+					@click="handleSelectPublishNode(node.id)"
+				>
+					<span class="flex items-center gap-1">
+						<Icon
+							:icon="nodeMap[node.nodeType]?.icon!"
+							:color="nodeMap[node.nodeType]?.iconColor"
+							:size="18"
+						/>
+						<span class="publish-selector__name">{{ node.name }}</span>
+					</span>
+
+					<span class="publish-selector__type">{{ nodeMap[node.nodeType]?.displayName }}</span>
+				</button>
+				<el-empty v-if="!publishNodes.length" :image-size="120" description="无节点数据" />
+			</el-scrollbar>
 		</div>
 		<template #reference>
 			<el-button
@@ -44,7 +60,7 @@ import { Icon } from '@repo/ui'
 import type { IWorkflow } from '@repo/workflow'
 
 import { useI18n } from '@/composables/useI18n'
-import { getNodeDisplayName } from '@/nodes/i18n'
+import { nodeMap } from '@/nodes'
 
 const props = defineProps<{
 	workflow: IWorkflow
@@ -57,6 +73,7 @@ const emit = defineEmits<{
 const { t } = useI18n()
 const popoverRef = ref<PopoverInstance>()
 const isPublishing = ref(false)
+const search = ref('')
 
 // const PUBLISHABLE_NODE_TYPES = ['start', 'trigger-schedule', 'trigger-webhook']
 
@@ -69,11 +86,12 @@ const publishNodes = computed(() => {
 			// 	const nodeType = (node as any)?.nodeType || (node as any)?.data?.nodeType
 			// 	return PUBLISHABLE_NODE_TYPES.includes(nodeType || '')
 			// })
+			.filter((node) => node.name?.includes(search.value))
 			.map((node) => {
 				const nodeType = ((node as any)?.nodeType || (node as any)?.data?.nodeType || '') as string
 				return {
 					id: node.id,
-					name: node.name || node.data?.title || getNodeDisplayName(nodeType) || nodeType,
+					name: node.name || nodeType,
 					nodeType
 				}
 			})
@@ -156,6 +174,7 @@ const handleSelectPublishNode = (id: string) => {
 	background: #fff;
 	cursor: pointer;
 	text-align: left;
+	margin-bottom: 8px;
 }
 
 .publish-selector__item:hover {
@@ -165,7 +184,7 @@ const handleSelectPublishNode = (id: string) => {
 
 .publish-selector__name {
 	font-size: 13px;
-	font-weight: 600;
+	font-weight: 500;
 	color: #344054;
 }