第一次提交
This commit is contained in:
493
frontend/src/components/WorkflowEditor/NodeTypes.ts
Normal file
493
frontend/src/components/WorkflowEditor/NodeTypes.ts
Normal file
@@ -0,0 +1,493 @@
|
||||
/**
|
||||
* 自定义节点类型定义
|
||||
*/
|
||||
import { defineComponent, h } from 'vue'
|
||||
import { Handle, Position } from '@vue-flow/core'
|
||||
|
||||
// 开始节点(只有输出)
|
||||
export const StartNode = defineComponent({
|
||||
name: 'StartNode',
|
||||
props: {
|
||||
data: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
setup(props, { attrs }) {
|
||||
const nodeClass = (attrs.class as string) || ''
|
||||
const executionClass = props.data?.executionClass || ''
|
||||
const allClasses = ['custom-node', 'start-node', nodeClass, executionClass].filter(Boolean).join(' ')
|
||||
return () => {
|
||||
// 根据执行状态动态设置样式
|
||||
const isExecuting = allClasses.includes('executing')
|
||||
const isExecuted = allClasses.includes('executed')
|
||||
const isFailed = allClasses.includes('failed')
|
||||
|
||||
const baseStyle: any = {
|
||||
padding: '8px 16px',
|
||||
borderRadius: '6px',
|
||||
background: '#67c23a',
|
||||
color: 'white',
|
||||
textAlign: 'center',
|
||||
minWidth: '100px',
|
||||
fontSize: '13px',
|
||||
fontWeight: '500',
|
||||
boxShadow: '0 2px 4px rgba(0,0,0,0.1)',
|
||||
position: 'relative',
|
||||
border: '2px solid transparent',
|
||||
transition: 'all 0.3s ease-in-out'
|
||||
}
|
||||
|
||||
// 执行状态样式(覆盖基础样式)
|
||||
if (isExecuting) {
|
||||
baseStyle.border = '3px solid #409eff'
|
||||
baseStyle.boxShadow = '0 0 0 3px rgba(64, 158, 255, 0.5), 0 0 20px rgba(64, 158, 255, 0.8)'
|
||||
baseStyle.transform = 'scale(1.05)'
|
||||
} else if (isExecuted) {
|
||||
baseStyle.border = '3px solid #67c23a'
|
||||
baseStyle.boxShadow = '0 0 0 3px rgba(103, 194, 58, 0.5), 0 2px 8px rgba(103, 194, 58, 0.3)'
|
||||
} else if (isFailed) {
|
||||
baseStyle.border = '3px solid #f56c6c'
|
||||
baseStyle.boxShadow = '0 0 0 3px rgba(245, 108, 108, 0.5), 0 2px 8px rgba(245, 108, 108, 0.3)'
|
||||
}
|
||||
|
||||
const errorMessage = props.data?.errorMessage
|
||||
|
||||
return h('div', {
|
||||
class: allClasses,
|
||||
style: baseStyle,
|
||||
title: isFailed && errorMessage ? errorMessage : undefined
|
||||
}, [
|
||||
h(Handle, {
|
||||
type: 'source',
|
||||
position: Position.Bottom,
|
||||
id: 'bottom',
|
||||
style: {
|
||||
background: '#67c23a',
|
||||
width: '8px',
|
||||
height: '8px'
|
||||
}
|
||||
}),
|
||||
h(Handle, {
|
||||
type: 'source',
|
||||
position: Position.Right,
|
||||
id: 'right',
|
||||
style: {
|
||||
background: '#67c23a',
|
||||
width: '8px',
|
||||
height: '8px'
|
||||
}
|
||||
}),
|
||||
h('div', {
|
||||
style: {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
gap: '4px'
|
||||
}
|
||||
}, [
|
||||
props.data.label || '开始',
|
||||
isFailed ? h('span', {
|
||||
style: {
|
||||
fontSize: '12px',
|
||||
marginLeft: '4px'
|
||||
}
|
||||
}, '❌') : null
|
||||
])
|
||||
])
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// LLM节点(有输入和输出)
|
||||
export const LLMNode = defineComponent({
|
||||
name: 'LLMNode',
|
||||
props: {
|
||||
data: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
setup(props, { attrs }) {
|
||||
const nodeClass = (attrs.class as string) || ''
|
||||
const executionClass = props.data?.executionClass || ''
|
||||
const allClasses = ['custom-node', 'llm-node', nodeClass, executionClass].filter(Boolean).join(' ')
|
||||
return () => {
|
||||
// 根据执行状态动态设置样式
|
||||
const isExecuting = allClasses.includes('executing')
|
||||
const isExecuted = allClasses.includes('executed')
|
||||
const isFailed = allClasses.includes('failed')
|
||||
|
||||
const baseStyle: any = {
|
||||
padding: '8px 16px',
|
||||
borderRadius: '6px',
|
||||
background: '#409eff',
|
||||
color: 'white',
|
||||
textAlign: 'center',
|
||||
minWidth: '100px',
|
||||
fontSize: '13px',
|
||||
fontWeight: '500',
|
||||
boxShadow: '0 2px 4px rgba(0,0,0,0.1)',
|
||||
position: 'relative',
|
||||
border: '2px solid transparent',
|
||||
transition: 'all 0.3s ease-in-out'
|
||||
}
|
||||
|
||||
// 执行状态样式(覆盖基础样式)
|
||||
if (isExecuting) {
|
||||
baseStyle.border = '3px solid #409eff'
|
||||
baseStyle.boxShadow = '0 0 0 3px rgba(64, 158, 255, 0.5), 0 0 20px rgba(64, 158, 255, 0.8)'
|
||||
baseStyle.transform = 'scale(1.05)'
|
||||
baseStyle.animation = 'pulse-blue 1.5s infinite'
|
||||
} else if (isExecuted) {
|
||||
baseStyle.border = '3px solid #67c23a'
|
||||
baseStyle.boxShadow = '0 0 0 3px rgba(103, 194, 58, 0.5), 0 2px 8px rgba(103, 194, 58, 0.3)'
|
||||
} else if (isFailed) {
|
||||
baseStyle.border = '3px solid #f56c6c'
|
||||
baseStyle.boxShadow = '0 0 0 3px rgba(245, 108, 108, 0.5), 0 2px 8px rgba(245, 108, 108, 0.3)'
|
||||
}
|
||||
|
||||
return h('div', {
|
||||
class: allClasses,
|
||||
style: baseStyle
|
||||
}, [
|
||||
h(Handle, {
|
||||
type: 'target',
|
||||
position: Position.Top,
|
||||
id: 'top',
|
||||
style: {
|
||||
background: '#409eff',
|
||||
width: '8px',
|
||||
height: '8px'
|
||||
}
|
||||
}),
|
||||
h(Handle, {
|
||||
type: 'target',
|
||||
position: Position.Left,
|
||||
id: 'left',
|
||||
style: {
|
||||
background: '#409eff',
|
||||
width: '8px',
|
||||
height: '8px'
|
||||
}
|
||||
}),
|
||||
h('div', {
|
||||
style: {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
gap: '4px'
|
||||
}
|
||||
}, [
|
||||
props.data.label || 'LLM',
|
||||
isFailed ? h('span', {
|
||||
style: {
|
||||
fontSize: '12px',
|
||||
marginLeft: '4px'
|
||||
}
|
||||
}, '❌') : null
|
||||
]),
|
||||
h(Handle, {
|
||||
type: 'source',
|
||||
position: Position.Bottom,
|
||||
id: 'bottom',
|
||||
style: {
|
||||
background: '#409eff',
|
||||
width: '8px',
|
||||
height: '8px'
|
||||
}
|
||||
}),
|
||||
h(Handle, {
|
||||
type: 'source',
|
||||
position: Position.Right,
|
||||
id: 'right',
|
||||
style: {
|
||||
background: '#409eff',
|
||||
width: '8px',
|
||||
height: '8px'
|
||||
}
|
||||
})
|
||||
])
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// 条件节点(有输入和两个输出:true/false)
|
||||
export const ConditionNode = defineComponent({
|
||||
name: 'ConditionNode',
|
||||
props: {
|
||||
data: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
setup(props, { attrs }) {
|
||||
const nodeClass = (attrs.class as string) || ''
|
||||
const executionClass = props.data?.executionClass || ''
|
||||
const allClasses = ['custom-node', 'condition-node', nodeClass, executionClass].filter(Boolean).join(' ')
|
||||
return () => {
|
||||
const isExecuting = allClasses.includes('executing')
|
||||
const isExecuted = allClasses.includes('executed')
|
||||
const isFailed = allClasses.includes('failed')
|
||||
|
||||
const baseStyle: any = {
|
||||
padding: '8px 16px',
|
||||
borderRadius: '6px',
|
||||
background: '#e6a23c',
|
||||
color: 'white',
|
||||
textAlign: 'center',
|
||||
minWidth: '100px',
|
||||
fontSize: '13px',
|
||||
fontWeight: '500',
|
||||
boxShadow: '0 2px 4px rgba(0,0,0,0.1)',
|
||||
position: 'relative',
|
||||
border: '2px solid transparent',
|
||||
transition: 'all 0.3s ease-in-out'
|
||||
}
|
||||
|
||||
if (isExecuting) {
|
||||
baseStyle.border = '3px solid #409eff'
|
||||
baseStyle.boxShadow = '0 0 0 3px rgba(64, 158, 255, 0.5), 0 0 20px rgba(64, 158, 255, 0.8)'
|
||||
baseStyle.transform = 'scale(1.05)'
|
||||
baseStyle.animation = 'pulse-blue 1.5s infinite'
|
||||
} else if (isExecuted) {
|
||||
baseStyle.border = '3px solid #67c23a'
|
||||
baseStyle.boxShadow = '0 0 0 3px rgba(103, 194, 58, 0.5), 0 2px 8px rgba(103, 194, 58, 0.3)'
|
||||
} else if (isFailed) {
|
||||
baseStyle.border = '3px solid #f56c6c'
|
||||
baseStyle.boxShadow = '0 0 0 3px rgba(245, 108, 108, 0.5), 0 2px 8px rgba(245, 108, 108, 0.3)'
|
||||
}
|
||||
|
||||
return h('div', {
|
||||
class: allClasses,
|
||||
style: baseStyle
|
||||
}, [
|
||||
h(Handle, {
|
||||
type: 'target',
|
||||
position: Position.Top,
|
||||
id: 'top',
|
||||
style: {
|
||||
background: '#e6a23c',
|
||||
width: '8px',
|
||||
height: '8px'
|
||||
}
|
||||
}),
|
||||
h(Handle, {
|
||||
type: 'target',
|
||||
position: Position.Left,
|
||||
id: 'left',
|
||||
style: {
|
||||
background: '#e6a23c',
|
||||
width: '8px',
|
||||
height: '8px'
|
||||
}
|
||||
}),
|
||||
props.data.label || '条件',
|
||||
h(Handle, {
|
||||
type: 'source',
|
||||
position: Position.Bottom,
|
||||
id: 'true',
|
||||
style: {
|
||||
background: '#67c23a',
|
||||
width: '8px',
|
||||
height: '8px',
|
||||
left: '30%'
|
||||
}
|
||||
}),
|
||||
h(Handle, {
|
||||
type: 'source',
|
||||
position: Position.Bottom,
|
||||
id: 'false',
|
||||
style: {
|
||||
background: '#f56c6c',
|
||||
width: '8px',
|
||||
height: '8px',
|
||||
right: '30%'
|
||||
}
|
||||
}),
|
||||
h(Handle, {
|
||||
type: 'source',
|
||||
position: Position.Right,
|
||||
id: 'right',
|
||||
style: {
|
||||
background: '#e6a23c',
|
||||
width: '8px',
|
||||
height: '8px'
|
||||
}
|
||||
})
|
||||
])
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// 结束节点(只有输入)
|
||||
export const EndNode = defineComponent({
|
||||
name: 'EndNode',
|
||||
props: {
|
||||
data: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
setup(props, { attrs }) {
|
||||
const nodeClass = (attrs.class as string) || ''
|
||||
const executionClass = props.data?.executionClass || ''
|
||||
const allClasses = ['custom-node', 'end-node', nodeClass, executionClass].filter(Boolean).join(' ')
|
||||
return () => {
|
||||
const isExecuting = allClasses.includes('executing')
|
||||
const isExecuted = allClasses.includes('executed')
|
||||
const isFailed = allClasses.includes('failed')
|
||||
|
||||
const baseStyle: any = {
|
||||
padding: '8px 16px',
|
||||
borderRadius: '6px',
|
||||
background: '#f56c6c',
|
||||
color: 'white',
|
||||
textAlign: 'center',
|
||||
minWidth: '100px',
|
||||
fontSize: '13px',
|
||||
fontWeight: '500',
|
||||
boxShadow: '0 2px 4px rgba(0,0,0,0.1)',
|
||||
position: 'relative',
|
||||
border: '2px solid transparent',
|
||||
transition: 'all 0.3s ease-in-out'
|
||||
}
|
||||
|
||||
if (isExecuting) {
|
||||
baseStyle.border = '3px solid #409eff'
|
||||
baseStyle.boxShadow = '0 0 0 3px rgba(64, 158, 255, 0.5), 0 0 20px rgba(64, 158, 255, 0.8)'
|
||||
baseStyle.transform = 'scale(1.05)'
|
||||
baseStyle.animation = 'pulse-blue 1.5s infinite'
|
||||
} else if (isExecuted) {
|
||||
baseStyle.border = '3px solid #67c23a'
|
||||
baseStyle.boxShadow = '0 0 0 3px rgba(103, 194, 58, 0.5), 0 2px 8px rgba(103, 194, 58, 0.3)'
|
||||
} else if (isFailed) {
|
||||
baseStyle.border = '3px solid #f56c6c'
|
||||
baseStyle.boxShadow = '0 0 0 3px rgba(245, 108, 108, 0.5), 0 2px 8px rgba(245, 108, 108, 0.3)'
|
||||
}
|
||||
|
||||
return h('div', {
|
||||
class: allClasses,
|
||||
style: baseStyle
|
||||
}, [
|
||||
h(Handle, {
|
||||
type: 'target',
|
||||
position: Position.Top,
|
||||
id: 'top',
|
||||
style: {
|
||||
background: '#f56c6c',
|
||||
width: '8px',
|
||||
height: '8px'
|
||||
}
|
||||
}),
|
||||
h(Handle, {
|
||||
type: 'target',
|
||||
position: Position.Left,
|
||||
id: 'left',
|
||||
style: {
|
||||
background: '#f56c6c',
|
||||
width: '8px',
|
||||
height: '8px'
|
||||
}
|
||||
}),
|
||||
props.data.label || '结束'
|
||||
])
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// 默认节点(有输入和输出)
|
||||
export const DefaultNode = defineComponent({
|
||||
name: 'DefaultNode',
|
||||
props: {
|
||||
data: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
setup(props, { attrs }) {
|
||||
const nodeClass = (attrs.class as string) || ''
|
||||
const executionClass = props.data?.executionClass || ''
|
||||
const allClasses = ['custom-node', 'default-node', nodeClass, executionClass].filter(Boolean).join(' ')
|
||||
return () => {
|
||||
const isExecuting = allClasses.includes('executing')
|
||||
const isExecuted = allClasses.includes('executed')
|
||||
const isFailed = allClasses.includes('failed')
|
||||
|
||||
const baseStyle: any = {
|
||||
padding: '8px 16px',
|
||||
borderRadius: '6px',
|
||||
background: '#909399',
|
||||
color: 'white',
|
||||
textAlign: 'center',
|
||||
minWidth: '100px',
|
||||
fontSize: '13px',
|
||||
fontWeight: '500',
|
||||
boxShadow: '0 2px 4px rgba(0,0,0,0.1)',
|
||||
position: 'relative',
|
||||
border: '2px solid transparent',
|
||||
transition: 'all 0.3s ease-in-out'
|
||||
}
|
||||
|
||||
if (isExecuting) {
|
||||
baseStyle.border = '3px solid #409eff'
|
||||
baseStyle.boxShadow = '0 0 0 3px rgba(64, 158, 255, 0.5), 0 0 20px rgba(64, 158, 255, 0.8)'
|
||||
baseStyle.transform = 'scale(1.05)'
|
||||
baseStyle.animation = 'pulse-blue 1.5s infinite'
|
||||
} else if (isExecuted) {
|
||||
baseStyle.border = '3px solid #67c23a'
|
||||
baseStyle.boxShadow = '0 0 0 3px rgba(103, 194, 58, 0.5), 0 2px 8px rgba(103, 194, 58, 0.3)'
|
||||
} else if (isFailed) {
|
||||
baseStyle.border = '3px solid #f56c6c'
|
||||
baseStyle.boxShadow = '0 0 0 3px rgba(245, 108, 108, 0.5), 0 2px 8px rgba(245, 108, 108, 0.3)'
|
||||
}
|
||||
|
||||
return h('div', {
|
||||
class: allClasses,
|
||||
style: baseStyle
|
||||
}, [
|
||||
h(Handle, {
|
||||
type: 'target',
|
||||
position: Position.Top,
|
||||
id: 'top',
|
||||
style: {
|
||||
background: '#909399',
|
||||
width: '8px',
|
||||
height: '8px'
|
||||
}
|
||||
}),
|
||||
h(Handle, {
|
||||
type: 'target',
|
||||
position: Position.Left,
|
||||
id: 'left',
|
||||
style: {
|
||||
background: '#909399',
|
||||
width: '8px',
|
||||
height: '8px'
|
||||
}
|
||||
}),
|
||||
props.data.label || '节点',
|
||||
h(Handle, {
|
||||
type: 'source',
|
||||
position: Position.Bottom,
|
||||
id: 'bottom',
|
||||
style: {
|
||||
background: '#909399',
|
||||
width: '8px',
|
||||
height: '8px'
|
||||
}
|
||||
}),
|
||||
h(Handle, {
|
||||
type: 'source',
|
||||
position: Position.Right,
|
||||
id: 'right',
|
||||
style: {
|
||||
background: '#909399',
|
||||
width: '8px',
|
||||
height: '8px'
|
||||
}
|
||||
})
|
||||
])
|
||||
}
|
||||
}
|
||||
})
|
||||
3087
frontend/src/components/WorkflowEditor/WorkflowEditor.vue
Normal file
3087
frontend/src/components/WorkflowEditor/WorkflowEditor.vue
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user