| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015 |
- <template>
- <div class="w-full h-full flex flex-col">
- <div
- class="h-60px shrink-0 border-b border-b-solid border-gray-200 flex items-center justify-between px-12px"
- >
- <div class="left flex items-center gap-4">
- <el-breadcrumb separator="/" class="flex items-center">
- <el-breadcrumb-item>Workspace</el-breadcrumb-item>
- <el-breadcrumb-item>
- <Input ref="inputRef" v-model="workflow.name" variant="borderless" />
- </el-breadcrumb-item>
- </el-breadcrumb>
- <div class="flex gap-2" v-show="!showTagInput" @click="showTagInput = true">
- <el-tag type="info" v-for="tag in workflow.tags" :key="tag" :disable-transitions="false">
- {{ tag }}
- </el-tag>
- </div>
- <el-input-tag
- v-show="showTagInput"
- v-model="workflow.tags"
- placeholder="按回车键添加标签"
- aria-label="按回车键添加标签"
- :max="5"
- @blur="showTagInput = false"
- />
- <IconButton
- v-if="!workflow.tags?.length && !showTagInput"
- icon="iconoir:plus"
- type="primary"
- link
- @click="showTagInput = true"
- >标签</IconButton
- >
- </div>
- <div class="right flex items-center gap-2">
- <el-button type="default" size="small">发布</el-button>
- <IconButton icon="lucide:history" type="default" link></IconButton>
- <el-dropdown placement="bottom-end" popper-class="w-120px">
- <IconButton icon="fluent-mdl2:more" type="default" link></IconButton>
- <template #dropdown>
- <el-dropdown-item>描述</el-dropdown-item>
- <el-dropdown-item>复用</el-dropdown-item>
- <el-dropdown-item @click="handleRename">重命名</el-dropdown-item>
- <el-dropdown-item divided @click="handleDelete">删除</el-dropdown-item>
- </template>
- </el-dropdown>
- </div>
- </div>
- <el-splitter layout="vertical" class="flex-1">
- <el-splitter-panel>
- <div class="h-full w-full" ref="workflowWrapperRef" @drop="onDrop">
- <Workflow
- ref="workflowRef"
- :id="workflow?.id"
- :workflow="workflowWithExecutionState"
- :nodeMap="nodeMap"
- @click:node="handleSelectNode"
- @dblclick:node="handleNodeClick"
- @create:node="handleNodeCreate"
- @create:connection:end="onCreateConnection"
- @drag-and-drop="handleDrop"
- @run:node="handleRunNode"
- @update:nodes:position="handleUpdateNodesPosition"
- @update:node:attrs="handleUpdateNodeProps"
- @delete:node="handleDeleteNode"
- @delete:connection="handleDeleteEdge"
- @dragover="onDragOver"
- @dragleave="onDragLeave"
- @create:connection:cancelled="onConnectionOpenNodeLibary"
- @click:connection:add="handleClickConectionAdd"
- class="bg-#f5f5f5"
- >
- <Toolbar
- @create:node="handleNodeCreate"
- @run="handleRunSelectedNode"
- :env-vars="workflow?.env_variables || []"
- @change-env-vars="handleChangeEnvVars"
- />
- </Workflow>
- </div>
- <RunWorkflow v-model:visible="runVisible" @run="handleRunSelectedNode" />
- <Setter
- :id="nodeID"
- :workflow="workflow!"
- @update:node:data="handleUpdateNode"
- @run-node="handleRunNode"
- v-model:visible="setterVisible"
- />
- <el-popover
- :visible="showNodeLibary"
- width="360px"
- virtual-triggering
- :show-arrow="false"
- :append-to="workflowWrapperRef"
- :virtual-ref="libaryRefferenceRef"
- >
- <NodeLibary @add-node="handleNodeCreate" @mouseleave="onHideNodeLibary" />
- </el-popover>
- </el-splitter-panel>
- <el-splitter-panel v-model:size.lazy="footerHeight" :min="32">
- <EditorFooter @toggle="handleFooterToggle" />
- </el-splitter-panel>
- </el-splitter>
- </div>
- </template>
- <script setup lang="ts">
- import { computed, ref, inject, type CSSProperties, onBeforeUnmount, watch, nextTick } from 'vue'
- import { useRoute, useRouter } from 'vue-router'
- import { agent } from '@repo/api-service'
- import RunWorkflow from '@/components/RunWorkflow/index.vue'
- import EditorFooter from '@/features/editorFooter/index.vue'
- import NodeLibary from '@/features/nodeLibary/index.vue'
- import Toolbar from '@/features/toolbar/index.vue'
- import Setter from '@/features/setter/index.vue'
- import { IconButton, Input } from '@repo/ui'
- import { nodeMap } from '@/nodes'
- import { Workflow, useDragAndDrop } from '@repo/workflow'
- import { dayjs, ElMessage, ElMessageBox } from 'element-plus'
- import { useRunnerStore } from '@/store/modules/runner.store'
- import type {
- IWorkflow,
- XYPosition,
- Connection,
- ConnectStartEvent,
- IWorkflowNode,
- CanvasExecutionStatus
- } from '@repo/workflow'
- const layout = inject<{ setMainStyle: (style: CSSProperties) => void }>('layout')
- layout?.setMainStyle({
- padding: '0px'
- })
- const footerHeight = ref(32)
- const route = useRoute()
- const router = useRouter()
- const id = route.params?.id as string
- const runnerStore = useRunnerStore()
- const workflowWrapperRef = ref<HTMLElement>()
- const showNodeLibary = ref(false)
- const libaryRefferenceRef = ref<HTMLElement>()
- const peddingHandlePayload = ref<{
- by?: 'node' | 'edge'
- handle?: ConnectStartEvent
- position: XYPosition
- event?: MouseEvent
- parentId?: string
- connection?: Connection
- }>()
- const projectMap = JSON.parse(localStorage.getItem(`workflow-map`) || '{}') as Record<
- string,
- IWorkflow
- >
- const showTagInput = ref(false)
- const workflow = ref<IWorkflow>(
- projectMap?.[id]
- ? projectMap[id]
- : {
- id,
- name: 'workflow_1',
- created: dayjs().format('MM 月 DD 日'),
- nodes: [],
- edges: []
- }
- )
- const mapNodeExecutionStatus = (status?: string): CanvasExecutionStatus => {
- if (status === 'running') {
- return 'running'
- }
- if (status === 'success') {
- return 'success'
- }
- if (status === 'failed') {
- return 'warning'
- }
- return 'idle'
- }
- const MIN_NODE_RUNNING_EFFECT_MS = 500
- const displayNodeExecutionStatus = ref<Record<string, CanvasExecutionStatus>>({})
- const runningStatusStartedAt = new Map<string, number>()
- const pendingNodeStatusTimers = new Map<string, number>()
- const clearPendingNodeStatusTimer = (nodeId: string) => {
- const timer = pendingNodeStatusTimers.get(nodeId)
- if (timer) {
- window.clearTimeout(timer)
- pendingNodeStatusTimers.delete(nodeId)
- }
- }
- const applyDisplayedNodeStatus = (nodeId: string, status: CanvasExecutionStatus) => {
- if (status === 'idle') {
- delete displayNodeExecutionStatus.value[nodeId]
- return
- }
- displayNodeExecutionStatus.value[nodeId] = status
- }
- const syncDisplayedNodeStatus = (nodeId: string, nextStatus: CanvasExecutionStatus) => {
- const currentStatus = displayNodeExecutionStatus.value[nodeId] || 'idle'
- if (nextStatus === 'running') {
- clearPendingNodeStatusTimer(nodeId)
- runningStatusStartedAt.set(nodeId, Date.now())
- applyDisplayedNodeStatus(nodeId, 'running')
- return
- }
- const runningStartedAt = runningStatusStartedAt.get(nodeId)
- const shouldKeepRunning =
- currentStatus === 'running' &&
- typeof runningStartedAt === 'number' &&
- Date.now() - runningStartedAt < MIN_NODE_RUNNING_EFFECT_MS
- if (shouldKeepRunning) {
- clearPendingNodeStatusTimer(nodeId)
- const delay = MIN_NODE_RUNNING_EFFECT_MS - (Date.now() - runningStartedAt)
- const timer = window.setTimeout(() => {
- applyDisplayedNodeStatus(nodeId, nextStatus)
- runningStatusStartedAt.delete(nodeId)
- pendingNodeStatusTimers.delete(nodeId)
- }, delay)
- pendingNodeStatusTimers.set(nodeId, timer)
- return
- }
- clearPendingNodeStatusTimer(nodeId)
- applyDisplayedNodeStatus(nodeId, nextStatus)
- runningStatusStartedAt.delete(nodeId)
- }
- const resetDisplayedNodeStatuses = () => {
- pendingNodeStatusTimers.forEach((timer) => window.clearTimeout(timer))
- pendingNodeStatusTimers.clear()
- runningStatusStartedAt.clear()
- displayNodeExecutionStatus.value = {}
- }
- watch(
- () => runnerStore.currentRunnerKey,
- () => {
- resetDisplayedNodeStatuses()
- }
- )
- watch(
- () => runnerStore.nodes.map((item) => ({ nodeId: item.nodeId, status: item.status })),
- (nodeStates) => {
- const activeNodeIds = new Set(nodeStates.map((item) => item.nodeId))
- nodeStates.forEach((item) => {
- syncDisplayedNodeStatus(item.nodeId, mapNodeExecutionStatus(item.status))
- })
- Object.keys(displayNodeExecutionStatus.value).forEach((nodeId) => {
- if (!activeNodeIds.has(nodeId)) {
- syncDisplayedNodeStatus(nodeId, 'idle')
- }
- })
- },
- { immediate: true, deep: true }
- )
- const workflowWithExecutionState = computed<IWorkflow>(() => {
- const baseWorkflow = workflow.value
- const runnerNodeStatusMap = new Map(Object.entries(displayNodeExecutionStatus.value))
- return {
- ...baseWorkflow,
- nodes: (baseWorkflow.nodes || []).map((node) => {
- const executionStatus = runnerNodeStatusMap.get(node.id) || 'idle'
- return {
- ...node,
- executionStatus,
- data: {
- ...(node.data || {}),
- executionStatus
- }
- }
- })
- }
- })
- 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 { onDragOver, onDrop, onDragLeave } = useDragAndDrop({
- id,
- addNodes: (node) => {
- handleNodeCreate(node)
- }
- })
- const normalizeNodeType = (node: any) => {
- const sourceNodeType = node?.nodeType || node?.data?.nodeType || node?.data?.type || node?.type
- return sourceNodeType || 'code'
- }
- 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 (response?.code === 0 && response?.error) {
- ElMessage.error(response.error)
- return false
- }
- if (errorMessage) {
- ElMessage.error(errorMessage)
- }
- return false
- }
- const buildUpdateNodePayload = (node: any) => {
- return {
- ...node,
- 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: node.data?.nodeType || node.nodeType,
- zIndex: node.zIndex ?? 1
- }
- }
- const toWorkflowNode = (node: any) => {
- const normalizedNodeType = normalizeNodeType(node)
- const schema = nodeMap[normalizedNodeType]?.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: false,
- data: {
- ...(schema?.data || {}),
- ...(node?.data || {}),
- id: node.id,
- position,
- width,
- height,
- nodeType: normalizedNodeType
- }
- }
- }
- const normalizeEdgeEndpoints = (
- edge: { source: string; target: string },
- nodes: Array<{ id: string; parentId?: string }>
- ) => {
- const sourceNode = nodes.find((node) => node.id === edge.source)
- const targetNode = nodes.find((node) => node.id === edge.target)
- const sourceParentId = sourceNode?.parentId || ''
- const targetParentId = targetNode?.parentId || ''
- if (sourceParentId && sourceParentId !== targetParentId) {
- return {
- source: sourceParentId,
- target: edge.target
- }
- }
- if (targetParentId && targetParentId !== sourceParentId) {
- return {
- source: edge.source,
- target: targetParentId
- }
- }
- return edge
- }
- const toWorkflowEdge = (
- edge: any,
- index: number,
- nodes: Array<{ id: string; parentId?: string }> = []
- ) => {
- if (!edge || typeof edge !== 'object' || !edge.source || !edge.target) {
- return null
- }
- const normalizedEdge = normalizeEdgeEndpoints(edge, nodes)
- return {
- ...edge,
- ...normalizedEdge,
- sourceHandle: edge.sourceHandle === 'source' ? `${edge.source}-source` : edge.sourceHandle,
- targetHandle: edge.targetHandle === 'target' ? `${edge.target}-target` : edge.targetHandle,
- id: edge.id || `edge-${normalizedEdge.source}-${normalizedEdge.target}-${index}`,
- type: 'canvas-edge',
- data: edge.data || {}
- }
- }
- const isPendingCreate = (node: any) => {
- return !!(node as any)?.__pendingCreate
- }
- const loadAgentWorkflow = async (agentId: string) => {
- if (!agentId) return
- isHydrating.value = true
- try {
- const response = await agent.postAgentGetAgentInfo({ id: agentId })
- const result = response?.result
- if (!response?.isSuccess || !result) {
- throw new Error('获取智能体信息失败')
- }
- const normalizedNodes = (result.nodes || []).map(toWorkflowNode)
- workflow.value = {
- ...(result as unknown as IWorkflow),
- nodes: normalizedNodes,
- edges: (result.edges || []).map((edge: any, index: number) =>
- toWorkflowEdge(edge, index, normalizedNodes)
- )
- }
- 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.postAgentDoEditAgent({
- data: workflow.value
- })
- handleApiResult(response, '智能体已保存', '保存智能体失败')
- } catch (error) {
- console.error('saveAgentMeta error', error)
- ElMessage.error('保存智能体失败')
- }
- }
- const saveAgentVariables = async () => {
- if (!workflow.value?.id) return
- try {
- const response = await agent.postAgentDoSaveAgentVariables({
- 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[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,
- async (id) => {
- if (id) {
- await loadAgentWorkflow(id as string)
- }
- },
- { immediate: true }
- )
- /**
- * Editor
- */
- const handleFooterToggle = (open: boolean) => {
- footerHeight.value = open ? 200 : 32
- }
- /**
- * Workflow
- */
- const nodeID = ref('')
- const setterVisible = ref(false)
- const runVisible = ref(false)
- const pendingSetterInit = new Set<string>()
- const workflowRef = ref<InstanceType<typeof Workflow>>()
- 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.postAgentDoExecute({
- appAgentId: workflow.value.id,
- start_node_id: nodeID.value,
- is_debugger: true,
- responseType: 'ws',
- params: {}
- })
- const agentRunnerKey = response?.result
- if (agentRunnerKey) {
- runnerStore.startRunner(agentRunnerKey)
- }
- 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.postAgentDoExecute({
- appAgentId: workflow.value.id,
- start_node_id: id,
- is_debugger: true,
- responseType: 'ws',
- params: {}
- })
- const agentRunnerKey = response?.result
- if (agentRunnerKey) {
- runnerStore.startRunner(agentRunnerKey)
- }
- // handleApiResult(response, '已提交运行节点', '运行节点失败')
- } catch (error) {
- console.error('postDoTestNodeRunner error', error)
- ElMessage.error('运行节点失败')
- }
- }
- const handleNodeCreate = (value: { type: string; position?: XYPosition } | string) => {
- // TODO: 处理注释节点
- if (typeof value === 'string') {
- if (value === 'stickyNote') {
- workflow.value?.nodes.push({
- appAgentId: workflow.value.id,
- type: 'canvas-node',
- zIndex: -1,
- nodeType: 'stickyNote',
- position: { x: 600, y: 300 },
- id: 'stickyNote',
- name: '注释',
- remark: '',
- data: {
- id: '',
- version: ['1.0.0'],
- inputs: [],
- outputs: [],
- position: { x: 600, y: 300 },
- nodeType: 'stickyNote',
- content: '注释内容,可以使用 **Markdown** 语法进行格式化, 双击进入编辑。',
- width: 400,
- height: 200,
- color: '#fff5d6'
- }
- })
- }
- return
- }
- const nodeToAdd = nodeMap[value.type]?.schema
- const viewport = workflowRef.value?.getVueFlow()?.viewport
- // 如果存在对应节点则添加
- if (nodeToAdd) {
- const newNodeParam: any = {
- ...nodeToAdd,
- appAgentId: workflow.value?.id || '',
- position: value.position
- }
- // 获取当前画布的中心点
- if (newNodeParam.position && viewport) {
- newNodeParam.position = {
- // 计算当前中心点坐标(相对于画布)
- x: (-viewport.value.x + window.innerWidth / 2) / viewport.value.zoom,
- y: (-viewport.value.y + window.innerHeight / 2) / viewport.value.zoom
- }
- }
- // 需要连接前一个节点
- if (peddingHandlePayload.value) {
- const { position, handle, parentId } = peddingHandlePayload.value
- newNodeParam.position = position
- newNodeParam.prevNodeId = handle?.nodeId
- newNodeParam.parentId = parentId
- // 条件句柄
- if (handle?.handleId?.includes('_')) {
- newNodeParam.nodeHandleId = handle?.handleId
- }
- }
- if (!newNodeParam.position) {
- newNodeParam.position = nodeToAdd.position
- }
- // 根据连线添加节点
- if (peddingHandlePayload.value?.by === 'edge' && peddingHandlePayload.value?.connection) {
- const { connection } = peddingHandlePayload.value
- const params = {
- edgeId: connection.id,
- newNode: newNodeParam
- }
- agent.postAgentDoNewAgentNodeWithEdge(params).then(async (response) => {
- if (handleApiResult(response, '节点已添加', '新增节点失败')) {
- await loadAgentWorkflow(workflow.value.id)
- }
- })
- onHideNodeLibary()
- return
- }
- onHideNodeLibary()
- agent
- .postAgentDoNewAgentNode(newNodeParam)
- .then(async (response) => {
- if (handleApiResult(response, '节点已添加', '新增节点失败')) {
- await loadAgentWorkflow(workflow.value.id)
- }
- })
- .catch((error) => {
- console.error('postDoNewAgentNode error', error)
- ElMessage.error('新增节点失败')
- })
- }
- }
- const handleNodeClick = (id: string, _position: XYPosition) => {
- nodeID.value = id
- pendingSetterInit.add(id)
- setterVisible.value = true
- }
- const handleDrop = (position: XYPosition, event: DragEvent) => {
- const type = event.dataTransfer?.getData('application/x-node-type')
- if (!type) return
- handleNodeCreate({ type, position })
- }
- /**
- * 修改工作流名称
- */
- const handleRename = () => {
- inputRef.value?.focus()
- inputRef.value?.select()
- }
- /**
- * 删除工作流
- */
- const handleDelete = () => {
- ElMessageBox.confirm('确定要删除吗?', '提示', {
- confirmButtonText: '确定',
- cancelButtonText: '取消',
- type: 'warning'
- }).then(() => {
- localStorage.removeItem(`project_${id}`)
- router.push('/')
- })
- }
- /**
- * 创建连线
- */
- const normalizeConnectionEndpoints = (connection: Connection) => {
- return normalizeEdgeEndpoints(connection, workflow.value?.nodes || [])
- }
- const onCreateConnection = async (connection: Connection) => {
- const { sourceHandle } = connection
- const { source, target } = normalizeConnectionEndpoints(connection)
- const params: {
- appAgentId: string
- source: string
- target: string
- zIndex: number
- sourceHandle?: string
- } = {
- appAgentId: workflow.value.id,
- source,
- target,
- zIndex: 1
- }
- // 需要传sourceHandle的情况是中间带"_"
- if (sourceHandle && sourceHandle.includes('_')) {
- params.sourceHandle = sourceHandle
- }
- // 如果是source条件节点else的sourceHandle模式就是当前节点id
- // if (sourceHandle && sourceHandle.endsWith('-else')) {
- // params.sourceHandle = source
- // }
- if (!workflow.value?.edges.some((edge) => edge.source === source && edge.target === target)) {
- const response = await agent.postAgentDoNewEdge(params)
- if (handleApiResult(response, '连线已创建', '连线创建失败')) {
- await loadAgentWorkflow(workflow.value.id)
- }
- }
- }
- /**
- * 移动位置
- */
- const handleUpdateNodesPosition = (events: { id: string; position: XYPosition }[]) => {
- events?.forEach(({ id, position }) => {
- const node = workflow.value?.nodes.find((node) => node.id === id)
- if (node && !isPendingCreate(node)) {
- if (node.position?.x === position.x && node.position?.y === position.y) {
- return
- }
- node.position = position
- agent
- .postAgentDoUpdateAgentNode(buildUpdateNodePayload(node))
- .then((response) => {
- handleApiResult(response, undefined, '更新节点失败')
- })
- .catch((error) => {
- console.error('postDoUpdateAgentNode error', error)
- })
- }
- })
- }
- /**
- * 点击选中节点
- * @param id
- * @param position
- */
- const handleSelectNode = (id: string) => {
- workflow.value?.nodes.forEach((node) => {
- node.selected = false
- })
- const node = workflow.value?.nodes.find((node) => node.id === id)
- if (node) {
- node.selected = true
- }
- }
- /**
- * 修改节点数据
- */
- const handleUpdateNode = (node: IWorkflowNode) => {
- if (node && !isPendingCreate(node)) {
- if (pendingSetterInit.has(id)) {
- pendingSetterInit.delete(id)
- return
- }
- if (node.nodeType === 'if-else') {
- const cases = node.data?.cases || []
- const offsetHeight = (cases.length > 1 ? cases.length - 1 : 0) * 32
- node.height = 96 + offsetHeight
- }
- agent
- .postAgentDoUpdateAgentNode(buildUpdateNodePayload(node))
- .then((response) => {
- handleApiResult(response, undefined, '更新节点失败')
- })
- .catch((error) => {
- console.error('postDoUpdateAgentNode error', error)
- })
- }
- }
- /**
- * 修改节点属性
- */
- const handleUpdateNodeProps = (id: string, attrs: Record<string, unknown>) => {
- const node = workflow.value?.nodes.find((node) => node.id === id)
- 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 {
- Object.assign(node, attrs)
- }
- agent
- .postAgentDoUpdateAgentNode(buildUpdateNodePayload(node))
- .then((response) => {
- handleApiResult(response, undefined, '更新节点失败')
- })
- .catch((error) => {
- console.error('postDoUpdateAgentNode error', error)
- })
- }
- }
- /**
- * 删除节点
- */
- const handleDeleteNode = async (id: string) => {
- const index = workflow.value.nodes.findIndex((node) => node.id === id)
- if (index != -1) {
- ElMessageBox.confirm('确定要删除吗?', '提示', {
- confirmButtonText: '确定',
- cancelButtonText: '取消',
- type: 'warning'
- }).then(async () => {
- await agent.postAgentDoDeleteAgentNode({
- id: id
- })
- await loadAgentWorkflow(workflow.value.id)
- })
- }
- }
- /**
- * 删除连线
- */
- const handleDeleteEdge = async (connection: Connection) => {
- if (connection.id) {
- ElMessageBox.confirm('确定要删除吗?', '提示', {
- confirmButtonText: '确定',
- cancelButtonText: '取消',
- type: 'warning'
- }).then(async () => {
- await agent.postAgentDoDeleteEdge({
- id: connection.id
- })
- await loadAgentWorkflow(workflow.value.id)
- })
- }
- }
- /**
- * 修改环境变量
- */
- const handleChangeEnvVars = async (
- envVars: {
- name: string
- value: string
- type: 'string' | 'number' | 'boolean' | 'object' | 'array'
- }[]
- ) => {
- const response = await agent.postAgentDoSaveAgentVariables({
- appAgentId: workflow.value.id,
- conversation_variables: [],
- env_variables: envVars
- })
- handleApiResult(response, '环境变量已保存', '保存环境变量失败')
- await loadAgentWorkflow(workflow.value.id)
- }
- /**
- * 连线取消时
- */
- const onConnectionOpenNodeLibary = async (payload: {
- handle: ConnectStartEvent
- position: XYPosition
- event: MouseEvent
- parentId?: string
- }) => {
- await nextTick()
- const { handle } = payload
- libaryRefferenceRef.value = handle.event?.target as HTMLElement
- showNodeLibary.value = true
- peddingHandlePayload.value = payload
- }
- /**
- * 节点库隐藏时
- */
- const onHideNodeLibary = () => {
- peddingHandlePayload.value = undefined
- libaryRefferenceRef.value = undefined
- showNodeLibary.value = false
- }
- /**
- * 连线添加按钮触发
- */
- const handleClickConectionAdd = (connection: Connection) => {
- const el = document.querySelector(`[edge-add-btn="${connection.id}"]`) as HTMLElement
- const screenToFlowCoordinate = workflowRef.value?.getVueFlow()?.screenToFlowCoordinate
- if (el && screenToFlowCoordinate) {
- const rect = el.getBoundingClientRect()
- const position = screenToFlowCoordinate({
- x: rect.left,
- y: rect.top
- })
- libaryRefferenceRef.value = el
- showNodeLibary.value = true
- peddingHandlePayload.value = {
- by: 'edge',
- position,
- connection
- }
- }
- }
- onBeforeUnmount(() => {
- if (saveAgentTimer.value) window.clearTimeout(saveAgentTimer.value)
- if (saveVarsTimer.value) window.clearTimeout(saveVarsTimer.value)
- resetDisplayedNodeStatuses()
- layout?.setMainStyle({})
- })
- </script>
|