|
|
@@ -55,15 +55,16 @@
|
|
|
@create:connection="onCreateConnection"
|
|
|
@drop="handleDrop"
|
|
|
@run="handleRunWorkflow"
|
|
|
+ @run:node="handleRunNode"
|
|
|
@update:nodes:position="handleUpdateNodesPosition"
|
|
|
@update:node:attrs="handleUpdateNodeProps"
|
|
|
@delete:node="handleDeleteNode"
|
|
|
@delete:connection="handleDeleteEdge"
|
|
|
class="bg-#f5f5f5"
|
|
|
>
|
|
|
- <Toolbar @create:node="handleNodeCreate" />
|
|
|
+ <Toolbar @create:node="handleNodeCreate" @run="handleRunSelectedNode" />
|
|
|
</Workflow>
|
|
|
- <RunWorkflow v-model:visible="runVisible" />
|
|
|
+ <RunWorkflow v-model:visible="runVisible" @run="handleRunSelectedNode" />
|
|
|
<Setter
|
|
|
:id="nodeID"
|
|
|
:workflow="workflow!"
|
|
|
@@ -80,7 +81,7 @@
|
|
|
</template>
|
|
|
|
|
|
<script setup lang="ts">
|
|
|
-import { ref, inject, type CSSProperties, onBeforeUnmount, watch } from 'vue'
|
|
|
+import { ref, inject, type CSSProperties, onBeforeUnmount, watch, nextTick } from 'vue'
|
|
|
import { startNode, endNode, httpNode, conditionNode, databaseNode, codeNode } from '@repo/nodes'
|
|
|
import { Workflow, type IWorkflow, type XYPosition, type Connection } from '@repo/workflow'
|
|
|
import { v4 as uuid } from 'uuid'
|
|
|
@@ -95,7 +96,7 @@ import Toolbar from '@/features/toolbar/index.vue'
|
|
|
import { IconButton, Input } from '@repo/ui'
|
|
|
|
|
|
import type { SourceType } from '@repo/nodes'
|
|
|
-import { dayjs, ElMessageBox } from 'element-plus'
|
|
|
+import { dayjs, ElMessage, ElMessageBox } from 'element-plus'
|
|
|
|
|
|
const layout = inject<{ setMainStyle: (style: CSSProperties) => void }>('layout')
|
|
|
|
|
|
@@ -103,10 +104,6 @@ layout?.setMainStyle({
|
|
|
padding: '0px'
|
|
|
})
|
|
|
|
|
|
-agent.postGetAgentInfo({
|
|
|
- id: 'b3a4aabb-a6b8-47f3-8a32-f45930f7d7b8'
|
|
|
-})
|
|
|
-
|
|
|
const footerHeight = ref(32)
|
|
|
const route = useRoute()
|
|
|
const router = useRouter()
|
|
|
@@ -130,36 +127,349 @@ const workflow = ref<IWorkflow>(
|
|
|
}
|
|
|
)
|
|
|
const inputRef = ref<InstanceType<typeof Input>>()
|
|
|
+const saveAgentTimer = ref<number | undefined>(undefined)
|
|
|
+const saveVarsTimer = ref<number | undefined>(undefined)
|
|
|
+const isHydrating = ref(false)
|
|
|
+const notifyTimestamps = new Map<string, number>()
|
|
|
+
|
|
|
+const nodeTypeMap: Record<string, string> = {
|
|
|
+ 'http-request': 'http-request',
|
|
|
+ 'if-else': 'condition',
|
|
|
+ condition: 'condition',
|
|
|
+ code: 'code',
|
|
|
+ database: 'database',
|
|
|
+ start: 'start',
|
|
|
+ end: 'end'
|
|
|
+}
|
|
|
+
|
|
|
+const nodeSchemaMap: Record<string, any> = {
|
|
|
+ start: startNode.schema,
|
|
|
+ end: endNode.schema,
|
|
|
+ 'http-request': httpNode.schema,
|
|
|
+ condition: conditionNode.schema,
|
|
|
+ code: codeNode.schema,
|
|
|
+ database: databaseNode.schema
|
|
|
+}
|
|
|
+
|
|
|
+const normalizeNodeType = (node: any) => {
|
|
|
+ const sourceNodeType = node?.nodeType || node?.data?.nodeType || node?.data?.type || node?.type
|
|
|
+ return nodeTypeMap[sourceNodeType] || sourceNodeType || 'code'
|
|
|
+}
|
|
|
+
|
|
|
+type AgentNodeType = 'custom' | 'start' | 'end' | 'condition' | 'task' | 'http-request'
|
|
|
+
|
|
|
+const toApiNodeType = (nodeType?: string): AgentNodeType => {
|
|
|
+ if (!nodeType) return 'custom'
|
|
|
+ return ['start', 'end', 'condition', 'http-request'].includes(nodeType)
|
|
|
+ ? (nodeType as AgentNodeType)
|
|
|
+ : 'custom'
|
|
|
+}
|
|
|
+
|
|
|
+const toApiNodeData = (nodeData: any) => {
|
|
|
+ if (nodeData?.nodeType !== 'http-request') {
|
|
|
+ return { ...(nodeData || {}) }
|
|
|
+ }
|
|
|
+
|
|
|
+ const bodyType = nodeData?.bodyType || 'json'
|
|
|
+ const bodyValue = nodeData?.body
|
|
|
+ const bodyData = Array.isArray(bodyValue)
|
|
|
+ ? bodyValue.map((item: any) => ({
|
|
|
+ key: item?.key ?? '',
|
|
|
+ value: item?.value ?? '',
|
|
|
+ type: item?.type ?? 'text'
|
|
|
+ }))
|
|
|
+ : [
|
|
|
+ {
|
|
|
+ key: '',
|
|
|
+ value: typeof bodyValue === 'string' ? bodyValue : '',
|
|
|
+ type: 'text'
|
|
|
+ }
|
|
|
+ ]
|
|
|
+
|
|
|
+ return {
|
|
|
+ ...(nodeData || {}),
|
|
|
+ method: (nodeData?.method || 'get').toLowerCase(),
|
|
|
+ ssl_verify: nodeData?.verifySSL ?? nodeData?.ssl_verify ?? true,
|
|
|
+ headers: undefined,
|
|
|
+ heads: (nodeData?.headers || []).map((item: any) => ({
|
|
|
+ name: item?.key ?? '',
|
|
|
+ value: item?.value ?? ''
|
|
|
+ })),
|
|
|
+ timeout_config: {
|
|
|
+ max_connect_timeout: nodeData?.timeoutConfig?.connect ?? 0,
|
|
|
+ max_read_timeout: nodeData?.timeoutConfig?.read ?? 0,
|
|
|
+ max_write_timeout: nodeData?.timeoutConfig?.write ?? 0
|
|
|
+ },
|
|
|
+ retry_config: {
|
|
|
+ max_retries: nodeData?.errorConfig?.max_retry ?? 3,
|
|
|
+ retry_enabled: nodeData?.errorConfig?.retry ?? false,
|
|
|
+ retry_interval: nodeData?.errorConfig?.retry_delay ?? 100
|
|
|
+ },
|
|
|
+ body: {
|
|
|
+ type: bodyType,
|
|
|
+ data: bodyData
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const notifySuccess = (key: string, message: string, cooldown = 1500) => {
|
|
|
+ const now = Date.now()
|
|
|
+ const last = notifyTimestamps.get(key) || 0
|
|
|
+ if (now - last < cooldown) return
|
|
|
+
|
|
|
+ notifyTimestamps.set(key, now)
|
|
|
+ ElMessage.success(message)
|
|
|
+}
|
|
|
+
|
|
|
+const handleApiResult = (response: any, successMessage?: string, errorMessage?: string) => {
|
|
|
+ if (response?.isSuccess) {
|
|
|
+ if (successMessage) {
|
|
|
+ notifySuccess(successMessage, successMessage)
|
|
|
+ }
|
|
|
+ return true
|
|
|
+ }
|
|
|
+ if (errorMessage) {
|
|
|
+ ElMessage.error(errorMessage)
|
|
|
+ }
|
|
|
+ return false
|
|
|
+}
|
|
|
+
|
|
|
+const buildUpdateNodePayload = (node: any) => {
|
|
|
+ return {
|
|
|
+ id: node.id,
|
|
|
+ appAgentId: workflow.value.id,
|
|
|
+ parentId: node.parentId || node.data?.parentId || '',
|
|
|
+ position: node.position || { x: 20, y: 30 },
|
|
|
+ width: node.width ?? node.data?.width ?? 96,
|
|
|
+ height: node.height ?? node.data?.height ?? 96,
|
|
|
+ selected: !!node.selected,
|
|
|
+ nodeType: toApiNodeType(node.data?.nodeType || node.nodeType),
|
|
|
+ zIndex: node.zIndex ?? 1,
|
|
|
+ data: toApiNodeData(node.data)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const toWorkflowNode = (node: any) => {
|
|
|
+ const normalizedNodeType = normalizeNodeType(node)
|
|
|
+ const schema = nodeSchemaMap[normalizedNodeType] || codeNode.schema
|
|
|
+ const position = node?.position || schema.position || { x: 20, y: 30 }
|
|
|
+ const width = node?.width ?? schema.width ?? 96
|
|
|
+ const height = node?.height ?? schema.height ?? 96
|
|
|
+
|
|
|
+ return {
|
|
|
+ ...schema,
|
|
|
+ ...node,
|
|
|
+ id: node.id,
|
|
|
+ type: 'canvas-node',
|
|
|
+ position,
|
|
|
+ width,
|
|
|
+ height,
|
|
|
+ zIndex: node?.zIndex ?? schema.zIndex ?? 1,
|
|
|
+ selected: !!node?.selected,
|
|
|
+ data: {
|
|
|
+ ...(schema.data || {}),
|
|
|
+ ...(node?.data || {}),
|
|
|
+ id: node.id,
|
|
|
+ position,
|
|
|
+ width,
|
|
|
+ height,
|
|
|
+ nodeType: normalizedNodeType
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const resolveOverlapPositions = (nodes: any[]) => {
|
|
|
+ const positionCountMap = new Map<string, number>()
|
|
|
+ const gapX = 220
|
|
|
+ const gapY = 140
|
|
|
+
|
|
|
+ return nodes.map((node) => {
|
|
|
+ const x = Number(node?.position?.x ?? 20)
|
|
|
+ const y = Number(node?.position?.y ?? 30)
|
|
|
+ const key = `${x},${y}`
|
|
|
+ const currentCount = positionCountMap.get(key) ?? 0
|
|
|
+ positionCountMap.set(key, currentCount + 1)
|
|
|
+
|
|
|
+ if (currentCount === 0) {
|
|
|
+ return node
|
|
|
+ }
|
|
|
+
|
|
|
+ const row = Math.floor(currentCount / 4)
|
|
|
+ const col = currentCount % 4
|
|
|
+ const nextPosition = {
|
|
|
+ x: x + col * gapX,
|
|
|
+ y: y + row * gapY
|
|
|
+ }
|
|
|
+
|
|
|
+ return {
|
|
|
+ ...node,
|
|
|
+ position: nextPosition,
|
|
|
+ data: {
|
|
|
+ ...(node?.data || {}),
|
|
|
+ position: nextPosition
|
|
|
+ }
|
|
|
+ }
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+const toWorkflowEdge = (edge: any, index: number) => {
|
|
|
+ if (!edge || typeof edge !== 'object' || !edge.source || !edge.target) {
|
|
|
+ return null
|
|
|
+ }
|
|
|
+
|
|
|
+ return {
|
|
|
+ ...edge,
|
|
|
+ id: edge.id || `edge-${edge.source}-${edge.target}-${index}`,
|
|
|
+ type: 'canvas-edge',
|
|
|
+ data: edge.data || {}
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const isPendingCreate = (node: any) => {
|
|
|
+ return !!(node as any)?.__pendingCreate
|
|
|
+}
|
|
|
+
|
|
|
+const isEqualNodeData = (current: any, next: any) => {
|
|
|
+ try {
|
|
|
+ return JSON.stringify(current ?? {}) === JSON.stringify(next ?? {})
|
|
|
+ } catch {
|
|
|
+ return current === next
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const loadAgentWorkflow = async (agentId: string) => {
|
|
|
+ if (!agentId) return
|
|
|
+ isHydrating.value = true
|
|
|
+
|
|
|
+ try {
|
|
|
+ const response = await agent.postGetAgentInfo({ id: agentId })
|
|
|
+ const result = response?.result
|
|
|
+ if (!response?.isSuccess || !result) {
|
|
|
+ throw new Error('获取智能体信息失败')
|
|
|
+ }
|
|
|
+
|
|
|
+ const mappedNodes = (result.nodes || []).map(toWorkflowNode)
|
|
|
+ const positionedNodes = resolveOverlapPositions(mappedNodes)
|
|
|
+
|
|
|
+ workflow.value = {
|
|
|
+ id: result.id || agentId,
|
|
|
+ name: result.name || 'workflow_1',
|
|
|
+ created: dayjs().format('MM 月 DD 日'),
|
|
|
+ nodes: positionedNodes,
|
|
|
+ edges: (result.edges || []).map(toWorkflowEdge).filter(Boolean),
|
|
|
+ tags: workflow.value?.tags || [],
|
|
|
+ conversation_variables: result.conversation_variables || [],
|
|
|
+ env_variables: result.env_variables || [],
|
|
|
+ profilePhoto: result.profilePhoto,
|
|
|
+ viewPort: result.viewPort
|
|
|
+ }
|
|
|
+ await nextTick()
|
|
|
+ } catch (error) {
|
|
|
+ console.error('loadAgentWorkflow error', error)
|
|
|
+ ElMessage.error('加载智能体流程失败')
|
|
|
+ } finally {
|
|
|
+ isHydrating.value = false
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const saveAgentMeta = async () => {
|
|
|
+ if (!workflow.value?.id) return
|
|
|
+
|
|
|
+ try {
|
|
|
+ const response = await agent.postDoEditAgent({
|
|
|
+ data: {
|
|
|
+ id: workflow.value.id,
|
|
|
+ name: workflow.value.name,
|
|
|
+ tags: workflow.value.tags || [],
|
|
|
+ description: workflow.value.description || '',
|
|
|
+ remark: workflow.value.description || '',
|
|
|
+ profilePhoto: workflow.value.profilePhoto,
|
|
|
+ viewPort: workflow.value.viewPort
|
|
|
+ }
|
|
|
+ })
|
|
|
+ handleApiResult(response, '智能体已保存', '保存智能体失败')
|
|
|
+ } catch (error) {
|
|
|
+ console.error('saveAgentMeta error', error)
|
|
|
+ ElMessage.error('保存智能体失败')
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const saveAgentVariables = async () => {
|
|
|
+ if (!workflow.value?.id) return
|
|
|
+
|
|
|
+ try {
|
|
|
+ const response = await agent.postDoSaveAgentVariables({
|
|
|
+ appAgentId: workflow.value.id,
|
|
|
+ conversation_variables: workflow.value.conversation_variables || [],
|
|
|
+ env_variables: (workflow.value.env_variables || []).map((item: any) => ({
|
|
|
+ name: item?.name || '',
|
|
|
+ value: item?.value ?? '',
|
|
|
+ type: item?.type || 'string'
|
|
|
+ }))
|
|
|
+ })
|
|
|
+ handleApiResult(response, '变量已保存', '保存变量失败')
|
|
|
+ } catch (error) {
|
|
|
+ console.error('saveAgentVariables error', error)
|
|
|
+ ElMessage.error('保存变量失败')
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const scheduleSaveAgentMeta = () => {
|
|
|
+ if (saveAgentTimer.value) window.clearTimeout(saveAgentTimer.value)
|
|
|
+ if (!workflow.value?.id) return
|
|
|
+
|
|
|
+ saveAgentTimer.value = window.setTimeout(() => {
|
|
|
+ saveAgentMeta()
|
|
|
+ }, 600)
|
|
|
+}
|
|
|
+
|
|
|
+const scheduleSaveAgentVariables = () => {
|
|
|
+ if (saveVarsTimer.value) window.clearTimeout(saveVarsTimer.value)
|
|
|
+ if (!workflow.value?.id) return
|
|
|
+
|
|
|
+ saveVarsTimer.value = window.setTimeout(() => {
|
|
|
+ saveAgentVariables()
|
|
|
+ }, 600)
|
|
|
+}
|
|
|
|
|
|
watch(
|
|
|
() => workflow.value,
|
|
|
(workflow) => {
|
|
|
- projectMap[id] = workflow
|
|
|
+ projectMap[workflow.id] = workflow
|
|
|
localStorage.setItem(`workflow-map`, JSON.stringify(projectMap))
|
|
|
},
|
|
|
{ deep: true }
|
|
|
)
|
|
|
|
|
|
+watch(
|
|
|
+ () => [workflow.value?.name, workflow.value?.description, workflow.value?.tags],
|
|
|
+ () => {
|
|
|
+ if (isHydrating.value) return
|
|
|
+ scheduleSaveAgentMeta()
|
|
|
+ },
|
|
|
+ { deep: true }
|
|
|
+)
|
|
|
+
|
|
|
+watch(
|
|
|
+ () => [workflow.value?.conversation_variables, workflow.value?.env_variables],
|
|
|
+ () => {
|
|
|
+ if (isHydrating.value) return
|
|
|
+ scheduleSaveAgentVariables()
|
|
|
+ },
|
|
|
+ { deep: true }
|
|
|
+)
|
|
|
+
|
|
|
/**
|
|
|
* 监听路由参数变化
|
|
|
*/
|
|
|
watch(
|
|
|
() => route.params?.id,
|
|
|
- (newId) => {
|
|
|
+ async (newId) => {
|
|
|
if (newId) {
|
|
|
- const projectMap = JSON.parse(localStorage.getItem(`workflow-map`) || '{}') as Record<
|
|
|
- string,
|
|
|
- IWorkflow
|
|
|
- >
|
|
|
- workflow.value = projectMap[newId as string] ?? {
|
|
|
- id: newId as string,
|
|
|
- name: 'workflow_1',
|
|
|
- created: dayjs().format('MM 月 DD 日'),
|
|
|
- nodes: [],
|
|
|
- edges: []
|
|
|
- }
|
|
|
+ await loadAgentWorkflow('b3a4aabb-a6b8-47f3-8a32-f45930f7d7b8' as string)
|
|
|
}
|
|
|
- }
|
|
|
+ },
|
|
|
+ { immediate: true }
|
|
|
)
|
|
|
/**
|
|
|
* Editor
|
|
|
@@ -174,9 +484,59 @@ const handleFooterToggle = (open: boolean) => {
|
|
|
const nodeID = ref('')
|
|
|
const setterVisible = ref(false)
|
|
|
const runVisible = ref(false)
|
|
|
+const pendingSetterInit = new Set<string>()
|
|
|
const handleRunWorkflow = () => {
|
|
|
runVisible.value = true
|
|
|
}
|
|
|
+
|
|
|
+const handleRunSelectedNode = async () => {
|
|
|
+ if (!workflow.value?.id) {
|
|
|
+ ElMessage.warning('请先选择需要运行的节点')
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!nodeID.value) {
|
|
|
+ const selectedNode = workflow.value?.nodes?.find((node) => (node as any)?.selected)
|
|
|
+ if (selectedNode?.id) {
|
|
|
+ nodeID.value = selectedNode.id
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!nodeID.value) {
|
|
|
+ ElMessage.warning('请选择需要测试的节点')
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ const response = await agent.postDoTestNodeRunner({
|
|
|
+ appAgentId: workflow.value.id,
|
|
|
+ nodeIds: [nodeID.value]
|
|
|
+ })
|
|
|
+ runVisible.value = false
|
|
|
+ handleApiResult(response, '已提交节点测试', '节点测试失败')
|
|
|
+ } catch (error) {
|
|
|
+ console.error('postDoTestNodeRunner error', error)
|
|
|
+ ElMessage.error('节点测试失败')
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const handleRunNode = async (id: string) => {
|
|
|
+ if (!workflow.value?.id) {
|
|
|
+ ElMessage.warning('请先选择需要运行的节点')
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ const response = await agent.postDoTestNodeRunner({
|
|
|
+ appAgentId: workflow.value.id,
|
|
|
+ nodeIds: [id]
|
|
|
+ })
|
|
|
+ handleApiResult(response, '已提交节点测试', '节点测试失败')
|
|
|
+ } catch (error) {
|
|
|
+ console.error('postDoTestNodeRunner error', error)
|
|
|
+ ElMessage.error('节点测试失败')
|
|
|
+ }
|
|
|
+}
|
|
|
const handleNodeCreate = (value: SourceType | string) => {
|
|
|
const id = uuid()
|
|
|
if (typeof value === 'string') {
|
|
|
@@ -215,20 +575,44 @@ const handleNodeCreate = (value: SourceType | string) => {
|
|
|
|
|
|
// 如果存在对应节点则添加
|
|
|
if (nodeToAdd) {
|
|
|
- workflow.value?.nodes.push({
|
|
|
+ const newNode = {
|
|
|
...nodeToAdd,
|
|
|
type: 'canvas-node',
|
|
|
data: {
|
|
|
...nodeToAdd,
|
|
|
id
|
|
|
},
|
|
|
+ __pendingCreate: true,
|
|
|
id
|
|
|
- })
|
|
|
+ }
|
|
|
+ workflow.value?.nodes.push(newNode)
|
|
|
+
|
|
|
+ agent
|
|
|
+ .postDoNewAgentNode({
|
|
|
+ appAgentId: workflow.value.id,
|
|
|
+ position: newNode.position,
|
|
|
+ width: newNode.width,
|
|
|
+ height: newNode.height,
|
|
|
+ selected: !!newNode.selected,
|
|
|
+ nodeType: toApiNodeType(newNode.data?.nodeType),
|
|
|
+ zIndex: newNode.zIndex ?? 1,
|
|
|
+ parentId: newNode.parentId || ''
|
|
|
+ })
|
|
|
+ .then(async (response) => {
|
|
|
+ if (handleApiResult(response, '节点已添加', '新增节点失败')) {
|
|
|
+ await loadAgentWorkflow(workflow.value.id)
|
|
|
+ }
|
|
|
+ })
|
|
|
+ .catch((error) => {
|
|
|
+ console.error('postDoNewAgentNode error', error)
|
|
|
+ ElMessage.error('新增节点失败')
|
|
|
+ })
|
|
|
}
|
|
|
console.log(workflow.value?.nodes, 'workflow.nodes')
|
|
|
}
|
|
|
-const handleNodeClick = (id: string, position: XYPosition) => {
|
|
|
+const handleNodeClick = (id: string, _position: XYPosition) => {
|
|
|
nodeID.value = id
|
|
|
+ pendingSetterInit.add(id)
|
|
|
setterVisible.value = true
|
|
|
}
|
|
|
|
|
|
@@ -282,8 +666,19 @@ const onCreateConnection = (connection: Connection) => {
|
|
|
const handleUpdateNodesPosition = (events: { id: string; position: XYPosition }[]) => {
|
|
|
events?.forEach(({ id, position }) => {
|
|
|
const node = workflow.value?.nodes.find((node) => node.id === id)
|
|
|
- if (node) {
|
|
|
+ if (node && !isPendingCreate(node)) {
|
|
|
+ if (node.position?.x === position.x && node.position?.y === position.y) {
|
|
|
+ return
|
|
|
+ }
|
|
|
node.position = position
|
|
|
+ agent
|
|
|
+ .postDoUpdateAgentNode(buildUpdateNodePayload(node))
|
|
|
+ .then((response) => {
|
|
|
+ handleApiResult(response, undefined, '更新节点失败')
|
|
|
+ })
|
|
|
+ .catch((error) => {
|
|
|
+ console.error('postDoUpdateAgentNode error', error)
|
|
|
+ })
|
|
|
}
|
|
|
})
|
|
|
}
|
|
|
@@ -293,11 +688,27 @@ const handleUpdateNodesPosition = (events: { id: string; position: XYPosition }[
|
|
|
*/
|
|
|
const hangleUpdateNodeData = (id: string, data: any) => {
|
|
|
const node = workflow.value?.nodes.find((node) => node.id === id)
|
|
|
- if (node) {
|
|
|
- node.data = {
|
|
|
+ if (node && !isPendingCreate(node)) {
|
|
|
+ if (pendingSetterInit.has(id)) {
|
|
|
+ pendingSetterInit.delete(id)
|
|
|
+ return
|
|
|
+ }
|
|
|
+ const nextData = {
|
|
|
...node.data,
|
|
|
...data
|
|
|
}
|
|
|
+ if (isEqualNodeData(node.data, nextData)) {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ node.data = nextData
|
|
|
+ agent
|
|
|
+ .postDoUpdateAgentNode(buildUpdateNodePayload(node))
|
|
|
+ .then((response) => {
|
|
|
+ handleApiResult(response, undefined, '更新节点失败')
|
|
|
+ })
|
|
|
+ .catch((error) => {
|
|
|
+ console.error('postDoUpdateAgentNode error', error)
|
|
|
+ })
|
|
|
}
|
|
|
console.log('hangleUpdateNodeData', id, data)
|
|
|
}
|
|
|
@@ -307,7 +718,12 @@ const hangleUpdateNodeData = (id: string, data: any) => {
|
|
|
*/
|
|
|
const handleUpdateNodeProps = (id: string, attrs: Record<string, unknown>) => {
|
|
|
const node = workflow.value?.nodes.find((node) => node.id === id)
|
|
|
- if (node) {
|
|
|
+ if (node && !isPendingCreate(node)) {
|
|
|
+ const keys = Object.keys(attrs || {})
|
|
|
+ const meaningfulKeys = keys.filter((key) => !['selected', 'dragging'].includes(key))
|
|
|
+ if (meaningfulKeys.length === 0) {
|
|
|
+ return
|
|
|
+ }
|
|
|
if (node.data?.nodeType === 'stickyNote') {
|
|
|
Object.assign(node.data, attrs)
|
|
|
} else {
|
|
|
@@ -341,6 +757,8 @@ const handleDeleteEdge = (connection: Connection) => {
|
|
|
}
|
|
|
|
|
|
onBeforeUnmount(() => {
|
|
|
+ if (saveAgentTimer.value) window.clearTimeout(saveAgentTimer.value)
|
|
|
+ if (saveVarsTimer.value) window.clearTimeout(saveVarsTimer.value)
|
|
|
layout?.setMainStyle({})
|
|
|
})
|
|
|
</script>
|