工作流动画效果
This commit is contained in:
@@ -143,7 +143,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, watch, nextTick } from 'vue'
|
||||
import { ref, computed, watch, nextTick, onUnmounted } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import {
|
||||
UserFilled,
|
||||
@@ -170,10 +170,15 @@ const props = defineProps<{
|
||||
nodeTestResult?: any
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
'execution-status': [status: any]
|
||||
}>()
|
||||
|
||||
const messages = ref<Message[]>([])
|
||||
const inputMessage = ref('')
|
||||
const loading = ref(false)
|
||||
const messagesContainer = ref<HTMLElement>()
|
||||
let pollingInterval: any = null
|
||||
|
||||
// 发送消息
|
||||
const handleSendMessage = async () => {
|
||||
@@ -208,8 +213,16 @@ const handleSendMessage = async () => {
|
||||
// 轮询执行状态
|
||||
const checkStatus = async () => {
|
||||
try {
|
||||
const statusResponse = await api.get(`/api/v1/executions/${execution.id}`)
|
||||
const exec = statusResponse.data
|
||||
// 获取详细执行状态(包含节点执行信息)
|
||||
const statusResponse = await api.get(`/api/v1/executions/${execution.id}/status`)
|
||||
const status = statusResponse.data
|
||||
|
||||
// 将执行状态传递给父组件,用于显示工作流动画
|
||||
emit('execution-status', status)
|
||||
|
||||
// 获取执行详情(用于提取输出结果)
|
||||
const execResponse = await api.get(`/api/v1/executions/${execution.id}`)
|
||||
const exec = execResponse.data
|
||||
|
||||
if (exec.status === 'completed') {
|
||||
// 提取Agent回复
|
||||
@@ -244,6 +257,16 @@ const handleSendMessage = async () => {
|
||||
|
||||
loading.value = false
|
||||
scrollToBottom()
|
||||
|
||||
// 延迟清除执行状态,让用户能看到最终的执行结果
|
||||
setTimeout(() => {
|
||||
emit('execution-status', null)
|
||||
}, 3000) // 3秒后清除
|
||||
|
||||
if (pollingInterval) {
|
||||
clearInterval(pollingInterval)
|
||||
pollingInterval = null
|
||||
}
|
||||
} else if (exec.status === 'failed') {
|
||||
messages.value.push({
|
||||
role: 'agent',
|
||||
@@ -252,9 +275,19 @@ const handleSendMessage = async () => {
|
||||
})
|
||||
loading.value = false
|
||||
scrollToBottom()
|
||||
|
||||
// 延迟清除执行状态,让用户能看到失败节点的状态
|
||||
setTimeout(() => {
|
||||
emit('execution-status', null)
|
||||
}, 5000) // 5秒后清除
|
||||
|
||||
if (pollingInterval) {
|
||||
clearInterval(pollingInterval)
|
||||
pollingInterval = null
|
||||
}
|
||||
} else {
|
||||
// 继续轮询
|
||||
setTimeout(checkStatus, 1000)
|
||||
// 继续轮询(pending 或 running 状态)
|
||||
// 不需要做任何操作,等待下次轮询
|
||||
}
|
||||
} catch (error: any) {
|
||||
messages.value.push({
|
||||
@@ -264,11 +297,21 @@ const handleSendMessage = async () => {
|
||||
})
|
||||
loading.value = false
|
||||
scrollToBottom()
|
||||
|
||||
// 清除执行状态
|
||||
emit('execution-status', null)
|
||||
|
||||
if (pollingInterval) {
|
||||
clearInterval(pollingInterval)
|
||||
pollingInterval = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 开始轮询
|
||||
setTimeout(checkStatus, 1000)
|
||||
// 使用 setInterval 进行轮询,每500毫秒检查一次(更频繁,能捕获快速执行的节点)
|
||||
pollingInterval = setInterval(checkStatus, 500)
|
||||
// 立即执行一次
|
||||
checkStatus()
|
||||
|
||||
} catch (error: any) {
|
||||
console.error('发送消息失败:', error)
|
||||
@@ -292,6 +335,13 @@ const handlePresetQuestion = (question: string) => {
|
||||
// 清空对话
|
||||
const handleClearChat = () => {
|
||||
messages.value = []
|
||||
// 清除执行状态
|
||||
emit('execution-status', null)
|
||||
// 清除轮询
|
||||
if (pollingInterval) {
|
||||
clearInterval(pollingInterval)
|
||||
pollingInterval = null
|
||||
}
|
||||
}
|
||||
|
||||
// 滚动到底部
|
||||
@@ -341,6 +391,16 @@ const handleCloseNodeTest = () => {
|
||||
watch(messages, () => {
|
||||
scrollToBottom()
|
||||
}, { deep: true })
|
||||
|
||||
// 组件卸载时清理轮询
|
||||
onUnmounted(() => {
|
||||
if (pollingInterval) {
|
||||
clearInterval(pollingInterval)
|
||||
pollingInterval = null
|
||||
}
|
||||
// 清除执行状态
|
||||
emit('execution-status', null)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -78,21 +78,82 @@ export const StartNode = defineComponent({
|
||||
height: '8px'
|
||||
}
|
||||
}),
|
||||
// 状态指示器(右上角)
|
||||
isExecuting ? h('div', {
|
||||
style: {
|
||||
position: 'absolute',
|
||||
top: '-8px',
|
||||
right: '-8px',
|
||||
width: '20px',
|
||||
height: '20px',
|
||||
background: '#409eff',
|
||||
borderRadius: '50%',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
boxShadow: '0 2px 8px rgba(64, 158, 255, 0.6)',
|
||||
zIndex: 3001, // 确保在模态框之上(Element Plus 模态框 z-index 通常是 2000-2100)
|
||||
}
|
||||
}, [
|
||||
h('div', {
|
||||
class: 'node-loading-spinner',
|
||||
style: {
|
||||
width: '12px',
|
||||
height: '12px',
|
||||
border: '2px solid rgba(255, 255, 255, 0.3)',
|
||||
borderTop: '2px solid white',
|
||||
borderRadius: '50%',
|
||||
animation: 'spin 0.8s linear infinite'
|
||||
}
|
||||
})
|
||||
]) : null,
|
||||
isExecuted ? h('div', {
|
||||
style: {
|
||||
position: 'absolute',
|
||||
top: '-8px',
|
||||
right: '-8px',
|
||||
width: '20px',
|
||||
height: '20px',
|
||||
background: '#67c23a',
|
||||
borderRadius: '50%',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
boxShadow: '0 2px 8px rgba(103, 194, 58, 0.6)',
|
||||
zIndex: 3001, // 确保在模态框之上(Element Plus 模态框 z-index 通常是 2000-2100)
|
||||
fontSize: '12px',
|
||||
color: 'white'
|
||||
}
|
||||
}, '✓') : null,
|
||||
isFailed ? h('div', {
|
||||
style: {
|
||||
position: 'absolute',
|
||||
top: '-8px',
|
||||
right: '-8px',
|
||||
width: '20px',
|
||||
height: '20px',
|
||||
background: '#f56c6c',
|
||||
borderRadius: '50%',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
boxShadow: '0 2px 8px rgba(245, 108, 108, 0.6)',
|
||||
zIndex: 3001, // 确保在模态框之上(Element Plus 模态框 z-index 通常是 2000-2100)
|
||||
fontSize: '12px',
|
||||
color: 'white'
|
||||
}
|
||||
}, '✕') : null,
|
||||
h('div', {
|
||||
style: {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
gap: '4px'
|
||||
gap: '4px',
|
||||
position: 'relative',
|
||||
zIndex: 1
|
||||
}
|
||||
}, [
|
||||
props.data.label || '开始',
|
||||
isFailed ? h('span', {
|
||||
style: {
|
||||
fontSize: '12px',
|
||||
marginLeft: '4px'
|
||||
}
|
||||
}, '❌') : null
|
||||
props.data.label || '开始'
|
||||
])
|
||||
])
|
||||
}
|
||||
@@ -171,21 +232,82 @@ export const LLMNode = defineComponent({
|
||||
height: '8px'
|
||||
}
|
||||
}),
|
||||
// 状态指示器(右上角)
|
||||
isExecuting ? h('div', {
|
||||
style: {
|
||||
position: 'absolute',
|
||||
top: '-8px',
|
||||
right: '-8px',
|
||||
width: '20px',
|
||||
height: '20px',
|
||||
background: '#409eff',
|
||||
borderRadius: '50%',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
boxShadow: '0 2px 8px rgba(64, 158, 255, 0.6)',
|
||||
zIndex: 3001, // 确保在模态框之上(Element Plus 模态框 z-index 通常是 2000-2100)
|
||||
}
|
||||
}, [
|
||||
h('div', {
|
||||
class: 'node-loading-spinner',
|
||||
style: {
|
||||
width: '12px',
|
||||
height: '12px',
|
||||
border: '2px solid rgba(255, 255, 255, 0.3)',
|
||||
borderTop: '2px solid white',
|
||||
borderRadius: '50%',
|
||||
animation: 'spin 0.8s linear infinite'
|
||||
}
|
||||
})
|
||||
]) : null,
|
||||
isExecuted ? h('div', {
|
||||
style: {
|
||||
position: 'absolute',
|
||||
top: '-8px',
|
||||
right: '-8px',
|
||||
width: '20px',
|
||||
height: '20px',
|
||||
background: '#67c23a',
|
||||
borderRadius: '50%',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
boxShadow: '0 2px 8px rgba(103, 194, 58, 0.6)',
|
||||
zIndex: 3001, // 确保在模态框之上(Element Plus 模态框 z-index 通常是 2000-2100)
|
||||
fontSize: '12px',
|
||||
color: 'white'
|
||||
}
|
||||
}, '✓') : null,
|
||||
isFailed ? h('div', {
|
||||
style: {
|
||||
position: 'absolute',
|
||||
top: '-8px',
|
||||
right: '-8px',
|
||||
width: '20px',
|
||||
height: '20px',
|
||||
background: '#f56c6c',
|
||||
borderRadius: '50%',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
boxShadow: '0 2px 8px rgba(245, 108, 108, 0.6)',
|
||||
zIndex: 3001, // 确保在模态框之上(Element Plus 模态框 z-index 通常是 2000-2100)
|
||||
fontSize: '12px',
|
||||
color: 'white'
|
||||
}
|
||||
}, '✕') : null,
|
||||
h('div', {
|
||||
style: {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
gap: '4px'
|
||||
gap: '4px',
|
||||
position: 'relative',
|
||||
zIndex: 1
|
||||
}
|
||||
}, [
|
||||
props.data.label || 'LLM',
|
||||
isFailed ? h('span', {
|
||||
style: {
|
||||
fontSize: '12px',
|
||||
marginLeft: '4px'
|
||||
}
|
||||
}, '❌') : null
|
||||
props.data.label || 'LLM'
|
||||
]),
|
||||
h(Handle, {
|
||||
type: 'source',
|
||||
@@ -282,7 +404,77 @@ export const ConditionNode = defineComponent({
|
||||
height: '8px'
|
||||
}
|
||||
}),
|
||||
props.data.label || '条件',
|
||||
// 状态指示器(右上角)
|
||||
isExecuting ? h('div', {
|
||||
style: {
|
||||
position: 'absolute',
|
||||
top: '-8px',
|
||||
right: '-8px',
|
||||
width: '20px',
|
||||
height: '20px',
|
||||
background: '#409eff',
|
||||
borderRadius: '50%',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
boxShadow: '0 2px 8px rgba(64, 158, 255, 0.6)',
|
||||
zIndex: 3001, // 确保在模态框之上(Element Plus 模态框 z-index 通常是 2000-2100)
|
||||
}
|
||||
}, [
|
||||
h('div', {
|
||||
class: 'node-loading-spinner',
|
||||
style: {
|
||||
width: '12px',
|
||||
height: '12px',
|
||||
border: '2px solid rgba(255, 255, 255, 0.3)',
|
||||
borderTop: '2px solid white',
|
||||
borderRadius: '50%',
|
||||
animation: 'spin 0.8s linear infinite'
|
||||
}
|
||||
})
|
||||
]) : null,
|
||||
isExecuted ? h('div', {
|
||||
style: {
|
||||
position: 'absolute',
|
||||
top: '-8px',
|
||||
right: '-8px',
|
||||
width: '20px',
|
||||
height: '20px',
|
||||
background: '#67c23a',
|
||||
borderRadius: '50%',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
boxShadow: '0 2px 8px rgba(103, 194, 58, 0.6)',
|
||||
zIndex: 3001, // 确保在模态框之上(Element Plus 模态框 z-index 通常是 2000-2100)
|
||||
fontSize: '12px',
|
||||
color: 'white'
|
||||
}
|
||||
}, '✓') : null,
|
||||
isFailed ? h('div', {
|
||||
style: {
|
||||
position: 'absolute',
|
||||
top: '-8px',
|
||||
right: '-8px',
|
||||
width: '20px',
|
||||
height: '20px',
|
||||
background: '#f56c6c',
|
||||
borderRadius: '50%',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
boxShadow: '0 2px 8px rgba(245, 108, 108, 0.6)',
|
||||
zIndex: 3001, // 确保在模态框之上(Element Plus 模态框 z-index 通常是 2000-2100)
|
||||
fontSize: '12px',
|
||||
color: 'white'
|
||||
}
|
||||
}, '✕') : null,
|
||||
h('div', {
|
||||
style: {
|
||||
position: 'relative',
|
||||
zIndex: 1
|
||||
}
|
||||
}, props.data.label || '条件'),
|
||||
h(Handle, {
|
||||
type: 'source',
|
||||
position: Position.Bottom,
|
||||
@@ -390,7 +582,77 @@ export const EndNode = defineComponent({
|
||||
height: '8px'
|
||||
}
|
||||
}),
|
||||
props.data.label || '结束'
|
||||
// 状态指示器(右上角)
|
||||
isExecuting ? h('div', {
|
||||
style: {
|
||||
position: 'absolute',
|
||||
top: '-8px',
|
||||
right: '-8px',
|
||||
width: '20px',
|
||||
height: '20px',
|
||||
background: '#409eff',
|
||||
borderRadius: '50%',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
boxShadow: '0 2px 8px rgba(64, 158, 255, 0.6)',
|
||||
zIndex: 3001, // 确保在模态框之上(Element Plus 模态框 z-index 通常是 2000-2100)
|
||||
}
|
||||
}, [
|
||||
h('div', {
|
||||
class: 'node-loading-spinner',
|
||||
style: {
|
||||
width: '12px',
|
||||
height: '12px',
|
||||
border: '2px solid rgba(255, 255, 255, 0.3)',
|
||||
borderTop: '2px solid white',
|
||||
borderRadius: '50%',
|
||||
animation: 'spin 0.8s linear infinite'
|
||||
}
|
||||
})
|
||||
]) : null,
|
||||
isExecuted ? h('div', {
|
||||
style: {
|
||||
position: 'absolute',
|
||||
top: '-8px',
|
||||
right: '-8px',
|
||||
width: '20px',
|
||||
height: '20px',
|
||||
background: '#67c23a',
|
||||
borderRadius: '50%',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
boxShadow: '0 2px 8px rgba(103, 194, 58, 0.6)',
|
||||
zIndex: 3001, // 确保在模态框之上(Element Plus 模态框 z-index 通常是 2000-2100)
|
||||
fontSize: '12px',
|
||||
color: 'white'
|
||||
}
|
||||
}, '✓') : null,
|
||||
isFailed ? h('div', {
|
||||
style: {
|
||||
position: 'absolute',
|
||||
top: '-8px',
|
||||
right: '-8px',
|
||||
width: '20px',
|
||||
height: '20px',
|
||||
background: '#f56c6c',
|
||||
borderRadius: '50%',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
boxShadow: '0 2px 8px rgba(245, 108, 108, 0.6)',
|
||||
zIndex: 3001, // 确保在模态框之上(Element Plus 模态框 z-index 通常是 2000-2100)
|
||||
fontSize: '12px',
|
||||
color: 'white'
|
||||
}
|
||||
}, '✕') : null,
|
||||
h('div', {
|
||||
style: {
|
||||
position: 'relative',
|
||||
zIndex: 1
|
||||
}
|
||||
}, props.data.label || '结束')
|
||||
])
|
||||
}
|
||||
}
|
||||
@@ -466,7 +728,77 @@ export const DefaultNode = defineComponent({
|
||||
height: '8px'
|
||||
}
|
||||
}),
|
||||
props.data.label || '节点',
|
||||
// 状态指示器(右上角)
|
||||
isExecuting ? h('div', {
|
||||
style: {
|
||||
position: 'absolute',
|
||||
top: '-8px',
|
||||
right: '-8px',
|
||||
width: '20px',
|
||||
height: '20px',
|
||||
background: '#409eff',
|
||||
borderRadius: '50%',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
boxShadow: '0 2px 8px rgba(64, 158, 255, 0.6)',
|
||||
zIndex: 3001, // 确保在模态框之上(Element Plus 模态框 z-index 通常是 2000-2100)
|
||||
}
|
||||
}, [
|
||||
h('div', {
|
||||
class: 'node-loading-spinner',
|
||||
style: {
|
||||
width: '12px',
|
||||
height: '12px',
|
||||
border: '2px solid rgba(255, 255, 255, 0.3)',
|
||||
borderTop: '2px solid white',
|
||||
borderRadius: '50%',
|
||||
animation: 'spin 0.8s linear infinite'
|
||||
}
|
||||
})
|
||||
]) : null,
|
||||
isExecuted ? h('div', {
|
||||
style: {
|
||||
position: 'absolute',
|
||||
top: '-8px',
|
||||
right: '-8px',
|
||||
width: '20px',
|
||||
height: '20px',
|
||||
background: '#67c23a',
|
||||
borderRadius: '50%',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
boxShadow: '0 2px 8px rgba(103, 194, 58, 0.6)',
|
||||
zIndex: 3001, // 确保在模态框之上(Element Plus 模态框 z-index 通常是 2000-2100)
|
||||
fontSize: '12px',
|
||||
color: 'white'
|
||||
}
|
||||
}, '✓') : null,
|
||||
isFailed ? h('div', {
|
||||
style: {
|
||||
position: 'absolute',
|
||||
top: '-8px',
|
||||
right: '-8px',
|
||||
width: '20px',
|
||||
height: '20px',
|
||||
background: '#f56c6c',
|
||||
borderRadius: '50%',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
boxShadow: '0 2px 8px rgba(245, 108, 108, 0.6)',
|
||||
zIndex: 3001, // 确保在模态框之上(Element Plus 模态框 z-index 通常是 2000-2100)
|
||||
fontSize: '12px',
|
||||
color: 'white'
|
||||
}
|
||||
}, '✕') : null,
|
||||
h('div', {
|
||||
style: {
|
||||
position: 'relative',
|
||||
zIndex: 1
|
||||
}
|
||||
}, props.data.label || '节点'),
|
||||
h(Handle, {
|
||||
type: 'source',
|
||||
position: Position.Bottom,
|
||||
|
||||
@@ -12,6 +12,10 @@
|
||||
</el-button>
|
||||
<el-button @click="handleRun">运行</el-button>
|
||||
<el-button @click="handleClear">清空</el-button>
|
||||
<el-button type="warning" @click="handleTestAnimation" :loading="testingAnimation">
|
||||
<el-icon><VideoPlay /></el-icon>
|
||||
{{ testingAnimation ? '测试中...' : '测试动画' }}
|
||||
</el-button>
|
||||
<el-button v-if="copiedNode" @click="handlePasteNodeFromButton" type="success">
|
||||
<el-icon><DocumentCopy /></el-icon>
|
||||
粘贴节点 (Ctrl+V)
|
||||
@@ -1003,6 +1007,37 @@
|
||||
</el-form-item>
|
||||
</template>
|
||||
|
||||
<!-- 结束节点配置 -->
|
||||
<template v-if="selectedNode.type === 'end' || selectedNode.type === 'output'">
|
||||
<el-form-item label="输出格式">
|
||||
<el-select v-model="selectedNode.data.output_format" placeholder="选择输出格式">
|
||||
<el-option label="纯文本(适合对话)" value="text" />
|
||||
<el-option label="JSON格式" value="json" />
|
||||
</el-select>
|
||||
<el-alert
|
||||
type="info"
|
||||
:closable="false"
|
||||
show-icon
|
||||
style="margin-top: 5px;"
|
||||
>
|
||||
<template #title>
|
||||
<div style="font-size: 12px;">
|
||||
<strong>纯文本</strong>:自动提取文本内容,适合对话场景<br/>
|
||||
<strong>JSON格式</strong>:保留完整数据结构,适合API调用
|
||||
</div>
|
||||
</template>
|
||||
</el-alert>
|
||||
</el-form-item>
|
||||
<el-form-item label="描述">
|
||||
<el-input
|
||||
v-model="selectedNode.data.description"
|
||||
type="textarea"
|
||||
:rows="2"
|
||||
placeholder="节点描述(可选)"
|
||||
/>
|
||||
</el-form-item>
|
||||
</template>
|
||||
|
||||
<!-- 定时任务节点配置 -->
|
||||
<template v-if="isScheduleNodeSelected">
|
||||
<el-form-item label="延迟类型">
|
||||
@@ -1314,6 +1349,8 @@ const handleManageTemplates = () => {
|
||||
const executedNodeIds = ref<Set<string>>(new Set())
|
||||
const runningNodeId = ref<string | null>(null)
|
||||
const failedNodeIds = ref<Set<string>>(new Set())
|
||||
const testingAnimation = ref(false)
|
||||
const testAnimationTimer = ref<number | null>(null)
|
||||
|
||||
// 运行对话框
|
||||
const runDialogVisible = ref(false)
|
||||
@@ -1331,7 +1368,8 @@ const {
|
||||
addEdges,
|
||||
removeNodes,
|
||||
removeEdges,
|
||||
updateNode,
|
||||
updateNode,
|
||||
updateEdge,
|
||||
screenToFlowCoordinate,
|
||||
zoomIn: vueFlowZoomIn,
|
||||
zoomOut: vueFlowZoomOut,
|
||||
@@ -1558,6 +1596,10 @@ const handleDrop = (event: DragEvent) => {
|
||||
body_type: 'text',
|
||||
attachments: []
|
||||
} : {}),
|
||||
// 结束节点默认配置
|
||||
...(nodeType === 'end' || nodeType === 'output' ? {
|
||||
output_format: 'text' // 默认纯文本格式,适合对话场景
|
||||
} : {}),
|
||||
// 消息队列节点默认配置
|
||||
...(isMQNode ? {
|
||||
queue_type: 'rabbitmq',
|
||||
@@ -2385,6 +2427,211 @@ const handleClear = () => {
|
||||
ElMessage.success('画布已清空')
|
||||
}
|
||||
|
||||
// 测试动画 - 依次执行所有节点,展示完整的工作流动画效果
|
||||
const handleTestAnimation = () => {
|
||||
// 清除之前的测试状态
|
||||
if (testAnimationTimer.value) {
|
||||
clearTimeout(testAnimationTimer.value)
|
||||
testAnimationTimer.value = null
|
||||
}
|
||||
|
||||
// 找到第一个开始节点
|
||||
const startNode = nodes.value.find(n => n.type === 'start' || n.data?.type === 'start' || n.id.startsWith('start'))
|
||||
|
||||
if (!startNode) {
|
||||
ElMessage.warning('未找到开始节点,请先添加一个开始节点')
|
||||
return
|
||||
}
|
||||
|
||||
console.log('[rjb] 🧪 开始测试动画,节点ID:', startNode.id)
|
||||
|
||||
// 清除所有节点的执行状态
|
||||
nodes.value.forEach(node => {
|
||||
const nodeClass = (node.class || '').replace(/\b(executing|executed|failed)\b/g, '').trim()
|
||||
if (nodeClass !== node.class) {
|
||||
updateNode(node.id, {
|
||||
class: nodeClass,
|
||||
data: { ...node.data, executionStatus: undefined, executionClass: undefined }
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
// 清除所有边的执行状态
|
||||
edges.value.forEach(edge => {
|
||||
const edgeClass = (edge.class || '').replace(/\b(edge-executing|edge-executed)\b/g, '').trim()
|
||||
if (edgeClass !== edge.class) {
|
||||
if (updateEdge) {
|
||||
updateEdge(edge.id, {
|
||||
class: edgeClass,
|
||||
style: {
|
||||
...edge.style,
|
||||
stroke: '#409eff',
|
||||
strokeWidth: 2.5
|
||||
},
|
||||
animated: false
|
||||
})
|
||||
} else {
|
||||
edge.class = edgeClass
|
||||
edge.animated = false
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// 使用广度优先搜索(BFS)找到所有节点的执行顺序
|
||||
const getExecutionOrder = (startNodeId: string): string[] => {
|
||||
const visited = new Set<string>()
|
||||
const order: string[] = []
|
||||
const queue: string[] = [startNodeId]
|
||||
|
||||
visited.add(startNodeId)
|
||||
|
||||
while (queue.length > 0) {
|
||||
const currentNodeId = queue.shift()!
|
||||
order.push(currentNodeId)
|
||||
|
||||
// 找到所有从当前节点出发的边
|
||||
const outgoingEdges = edges.value.filter(e => e.source === currentNodeId)
|
||||
for (const edge of outgoingEdges) {
|
||||
if (!visited.has(edge.target)) {
|
||||
visited.add(edge.target)
|
||||
queue.push(edge.target)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 如果还有未访问的节点(可能是孤立的),也加入顺序
|
||||
nodes.value.forEach(node => {
|
||||
if (!visited.has(node.id)) {
|
||||
order.push(node.id)
|
||||
}
|
||||
})
|
||||
|
||||
return order
|
||||
}
|
||||
|
||||
const executionOrder = getExecutionOrder(startNode.id)
|
||||
console.log('[rjb] 🧪 节点执行顺序:', executionOrder)
|
||||
|
||||
if (executionOrder.length === 0) {
|
||||
ElMessage.warning('未找到可执行的节点')
|
||||
return
|
||||
}
|
||||
|
||||
testingAnimation.value = true
|
||||
|
||||
// 依次执行每个节点
|
||||
const executeNodeAnimation = (nodeIndex: number) => {
|
||||
if (nodeIndex >= executionOrder.length) {
|
||||
// 所有节点执行完成
|
||||
testingAnimation.value = false
|
||||
testAnimationTimer.value = null
|
||||
ElMessage.success(`动画测试完成!共执行了 ${executionOrder.length} 个节点`)
|
||||
return
|
||||
}
|
||||
|
||||
const nodeId = executionOrder[nodeIndex]
|
||||
const node = nodes.value.find(n => n.id === nodeId)
|
||||
|
||||
if (!node) {
|
||||
// 如果节点不存在,跳过
|
||||
executeNodeAnimation(nodeIndex + 1)
|
||||
return
|
||||
}
|
||||
|
||||
console.log(`[rjb] 🧪 [${nodeIndex + 1}/${executionOrder.length}] 开始执行节点:`, nodeId)
|
||||
|
||||
// 第一步:标记为执行中
|
||||
const nodeClass = ((node.class || '').replace(/\b(executing|executed|failed)\b/g, '').trim() + ' executing').trim()
|
||||
const nodeData = { ...node.data, executionStatus: 'executing', executionClass: 'executing' }
|
||||
updateNode(node.id, {
|
||||
class: nodeClass,
|
||||
data: nodeData
|
||||
})
|
||||
console.log('[rjb] 🧪 ✅ 标记节点为执行中:', nodeId)
|
||||
|
||||
// 高亮连接到当前节点的边(从上游节点来的边)
|
||||
edges.value.forEach(edge => {
|
||||
if (edge.target === nodeId) {
|
||||
const edgeClass = ((edge.class || '').replace(/\b(edge-executing|edge-executed)\b/g, '').trim() + ' edge-executing').trim()
|
||||
try {
|
||||
if (updateEdge) {
|
||||
updateEdge(edge.id, {
|
||||
class: edgeClass,
|
||||
style: {
|
||||
...edge.style,
|
||||
stroke: '#409eff',
|
||||
strokeWidth: 3.5,
|
||||
strokeDasharray: '8,4'
|
||||
},
|
||||
animated: true
|
||||
})
|
||||
} else {
|
||||
edge.class = edgeClass
|
||||
edge.style = {
|
||||
...edge.style,
|
||||
stroke: '#409eff',
|
||||
strokeWidth: 3.5,
|
||||
strokeDasharray: '8,4'
|
||||
}
|
||||
edge.animated = true
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('[rjb] Failed to update edge:', e)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// 第二步:1.5秒后标记为已完成,然后执行下一个节点
|
||||
testAnimationTimer.value = window.setTimeout(() => {
|
||||
const executedNodeClass = ((node.class || '').replace(/\b(executing|executed|failed)\b/g, '').trim() + ' executed').trim()
|
||||
const executedNodeData = { ...node.data, executionStatus: 'executed', executionClass: 'executed' }
|
||||
updateNode(node.id, {
|
||||
class: executedNodeClass,
|
||||
data: executedNodeData
|
||||
})
|
||||
console.log('[rjb] 🧪 ✅ 标记节点为已完成:', nodeId)
|
||||
|
||||
// 更新连接到当前节点的边为已完成状态
|
||||
edges.value.forEach(edge => {
|
||||
if (edge.target === nodeId) {
|
||||
const edgeClass = ((edge.class || '').replace(/\b(edge-executing|edge-executed)\b/g, '').trim() + ' edge-executed').trim()
|
||||
try {
|
||||
if (updateEdge) {
|
||||
updateEdge(edge.id, {
|
||||
class: edgeClass,
|
||||
style: {
|
||||
...edge.style,
|
||||
stroke: '#67c23a',
|
||||
strokeWidth: 3,
|
||||
strokeDasharray: '0'
|
||||
},
|
||||
animated: false
|
||||
})
|
||||
} else {
|
||||
edge.class = edgeClass
|
||||
edge.style = {
|
||||
...edge.style,
|
||||
stroke: '#67c23a',
|
||||
strokeWidth: 3,
|
||||
strokeDasharray: '0'
|
||||
}
|
||||
edge.animated = false
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('[rjb] Failed to update edge:', e)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// 继续执行下一个节点
|
||||
executeNodeAnimation(nodeIndex + 1)
|
||||
}, 1500) // 每个节点执行1.5秒
|
||||
}
|
||||
|
||||
// 开始执行第一个节点
|
||||
executeNodeAnimation(0)
|
||||
}
|
||||
|
||||
// 监听节点和边的变化,检查是否有变更
|
||||
watch([nodes, edges], () => {
|
||||
if (lastSavedData.value) {
|
||||
@@ -2475,6 +2722,17 @@ watch(() => props.executionStatus, (newStatus, oldStatus) => {
|
||||
|
||||
const { current_node, executed_nodes, failed_nodes } = newStatus
|
||||
console.log('[rjb] Execution status - current:', current_node, 'executed:', executed_nodes, 'failed:', failed_nodes)
|
||||
console.log('[rjb] Execution status full:', JSON.stringify(newStatus, null, 2))
|
||||
|
||||
// 清除所有边的执行状态
|
||||
edges.value.forEach(edge => {
|
||||
if (edge.data) {
|
||||
delete edge.data.executionStatus
|
||||
}
|
||||
if (edge.class) {
|
||||
edge.class = edge.class.replace(/\b(edge-executing|edge-executed)\b/g, '').trim()
|
||||
}
|
||||
})
|
||||
|
||||
// 更新正在执行的节点
|
||||
if (current_node && current_node.node_id) {
|
||||
@@ -2486,7 +2744,49 @@ watch(() => props.executionStatus, (newStatus, oldStatus) => {
|
||||
class: nodeClass,
|
||||
data: nodeData
|
||||
})
|
||||
console.log('[rjb] ✅ Marking node as executing:', current_node.node_id, 'class:', nodeClass)
|
||||
console.log('[rjb] ✅ Marking node as executing:', current_node.node_id, 'class:', nodeClass, 'nodeData:', nodeData)
|
||||
|
||||
// 高亮连接到正在执行节点的边
|
||||
edges.value.forEach(edge => {
|
||||
if (edge.target === current_node.node_id) {
|
||||
const edgeClass = ((edge.class || '').replace(/\b(edge-executing|edge-executed)\b/g, '').trim() + ' edge-executing').trim()
|
||||
try {
|
||||
if (updateEdge) {
|
||||
updateEdge(edge.id, {
|
||||
class: edgeClass,
|
||||
style: {
|
||||
...edge.style,
|
||||
stroke: '#409eff',
|
||||
strokeWidth: 3.5,
|
||||
strokeDasharray: '8,4'
|
||||
},
|
||||
animated: true
|
||||
})
|
||||
} else {
|
||||
// 如果updateEdge不存在,直接修改(兼容旧版本)
|
||||
edge.class = edgeClass
|
||||
edge.style = {
|
||||
...edge.style,
|
||||
stroke: '#409eff',
|
||||
strokeWidth: 3.5,
|
||||
strokeDasharray: '8,4'
|
||||
}
|
||||
edge.animated = true
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('[rjb] Failed to update edge:', e)
|
||||
// 直接修改作为后备方案
|
||||
edge.class = edgeClass
|
||||
edge.style = {
|
||||
...edge.style,
|
||||
stroke: '#409eff',
|
||||
strokeWidth: 3.5,
|
||||
strokeDasharray: '8,4'
|
||||
}
|
||||
edge.animated = true
|
||||
}
|
||||
}
|
||||
})
|
||||
} else {
|
||||
console.warn('[rjb] ❌ Node not found:', current_node.node_id, 'available nodes:', nodes.value.map(n => n.id))
|
||||
}
|
||||
@@ -2505,6 +2805,36 @@ watch(() => props.executionStatus, (newStatus, oldStatus) => {
|
||||
data: nodeData
|
||||
})
|
||||
console.log('[rjb] ✅ Marking node as executed:', executedNode.node_id, 'class:', nodeClass)
|
||||
|
||||
// 高亮连接到已执行节点的边
|
||||
edges.value.forEach(edge => {
|
||||
if (edge.target === executedNode.node_id) {
|
||||
const edgeClass = ((edge.class || '').replace(/\b(edge-executing|edge-executed)\b/g, '').trim() + ' edge-executed').trim()
|
||||
try {
|
||||
updateEdge(edge.id, {
|
||||
class: edgeClass,
|
||||
style: {
|
||||
...edge.style,
|
||||
stroke: '#67c23a',
|
||||
strokeWidth: 3,
|
||||
strokeDasharray: '0'
|
||||
},
|
||||
animated: false
|
||||
})
|
||||
} catch (e) {
|
||||
console.warn('[rjb] Failed to update edge:', e)
|
||||
// 如果updateEdge不存在,直接修改(兼容旧版本)
|
||||
edge.class = edgeClass
|
||||
edge.style = {
|
||||
...edge.style,
|
||||
stroke: '#67c23a',
|
||||
strokeWidth: 3,
|
||||
strokeDasharray: '0'
|
||||
}
|
||||
edge.animated = false
|
||||
}
|
||||
}
|
||||
})
|
||||
} else {
|
||||
console.warn('[rjb] ❌ Executed node not found:', executedNode.node_id)
|
||||
}
|
||||
@@ -2719,6 +3049,12 @@ onUnmounted(() => {
|
||||
// 禁用自动保存
|
||||
disableAutoSave()
|
||||
|
||||
// 清理测试动画定时器
|
||||
if (testAnimationTimer.value) {
|
||||
clearTimeout(testAnimationTimer.value)
|
||||
testAnimationTimer.value = null
|
||||
}
|
||||
|
||||
// 断开协作连接
|
||||
if (collaboration) {
|
||||
collaboration.disconnect()
|
||||
@@ -2987,19 +3323,26 @@ onUnmounted(() => {
|
||||
box-shadow: 0 0 0 3px rgba(64, 158, 255, 0.5), 0 0 20px rgba(64, 158, 255, 0.8) !important;
|
||||
animation: pulse-blue 1.5s infinite !important;
|
||||
transform: scale(1.05) !important;
|
||||
z-index: 10 !important;
|
||||
z-index: 3000 !important; /* Element Plus 模态框 z-index 通常是 2000-2100,我们设置更高 */
|
||||
position: relative !important;
|
||||
}
|
||||
|
||||
.custom-node.executed {
|
||||
.custom-node.executed,
|
||||
.vue-flow__node.executed .custom-node,
|
||||
.vue-flow__node .custom-node.executed {
|
||||
border: 3px solid #67c23a !important;
|
||||
box-shadow: 0 0 0 3px rgba(103, 194, 58, 0.5), 0 2px 8px rgba(103, 194, 58, 0.3) !important;
|
||||
z-index: 5 !important;
|
||||
z-index: 2999 !important; /* 比执行中的节点稍低,但仍在模态框之上 */
|
||||
position: relative !important;
|
||||
}
|
||||
|
||||
.custom-node.failed {
|
||||
.custom-node.failed,
|
||||
.vue-flow__node.failed .custom-node,
|
||||
.vue-flow__node .custom-node.failed {
|
||||
border: 3px solid #f56c6c !important;
|
||||
box-shadow: 0 0 0 3px rgba(245, 108, 108, 0.5), 0 2px 8px rgba(245, 108, 108, 0.3) !important;
|
||||
z-index: 5 !important;
|
||||
z-index: 2999 !important; /* 比执行中的节点稍低,但仍在模态框之上 */
|
||||
position: relative !important;
|
||||
}
|
||||
|
||||
/* 确保 Vue Flow 节点容器也能应用样式 */
|
||||
@@ -3013,18 +3356,24 @@ onUnmounted(() => {
|
||||
.vue-flow__node .custom-node.executing {
|
||||
border-color: #409eff !important;
|
||||
box-shadow: 0 0 0 3px rgba(64, 158, 255, 0.5), 0 0 20px rgba(64, 158, 255, 0.8) !important;
|
||||
animation: pulse-blue 1.5s infinite !important;
|
||||
animation: pulse-blue 1.5s infinite, node-pulse 2s ease-in-out infinite !important;
|
||||
transform: scale(1.05) !important;
|
||||
z-index: 3000 !important;
|
||||
position: relative !important;
|
||||
}
|
||||
|
||||
.vue-flow__node .custom-node.executed {
|
||||
border-color: #67c23a !important;
|
||||
box-shadow: 0 0 0 3px rgba(103, 194, 58, 0.5), 0 2px 8px rgba(103, 194, 58, 0.3) !important;
|
||||
z-index: 2999 !important;
|
||||
position: relative !important;
|
||||
}
|
||||
|
||||
.vue-flow__node .custom-node.failed {
|
||||
border-color: #f56c6c !important;
|
||||
box-shadow: 0 0 0 3px rgba(245, 108, 108, 0.5), 0 2px 8px rgba(245, 108, 108, 0.3) !important;
|
||||
z-index: 2999 !important;
|
||||
position: relative !important;
|
||||
}
|
||||
|
||||
@keyframes pulse-blue {
|
||||
@@ -3039,6 +3388,86 @@ onUnmounted(() => {
|
||||
}
|
||||
}
|
||||
|
||||
/* 旋转动画 - 用于加载指示器 */
|
||||
@keyframes spin {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
/* 增强执行状态的动画效果 */
|
||||
.custom-node.executing {
|
||||
animation: pulse-blue 1.5s infinite, node-pulse 2s ease-in-out infinite !important;
|
||||
}
|
||||
|
||||
@keyframes node-pulse {
|
||||
0%, 100% {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
50% {
|
||||
transform: scale(1.08);
|
||||
}
|
||||
}
|
||||
|
||||
/* 执行成功时的闪烁效果 */
|
||||
.custom-node.executed {
|
||||
animation: success-flash 0.5s ease-out !important;
|
||||
}
|
||||
|
||||
@keyframes success-flash {
|
||||
0% {
|
||||
box-shadow: 0 0 0 0 rgba(103, 194, 58, 0.5), 0 0 20px rgba(103, 194, 58, 0.8);
|
||||
}
|
||||
50% {
|
||||
box-shadow: 0 0 0 8px rgba(103, 194, 58, 0.3), 0 0 30px rgba(103, 194, 58, 1);
|
||||
}
|
||||
100% {
|
||||
box-shadow: 0 0 0 3px rgba(103, 194, 58, 0.5), 0 2px 8px rgba(103, 194, 58, 0.3);
|
||||
}
|
||||
}
|
||||
|
||||
/* 执行失败时的闪烁效果 */
|
||||
.custom-node.failed {
|
||||
animation: error-shake 0.5s ease-out !important;
|
||||
}
|
||||
|
||||
@keyframes error-shake {
|
||||
0%, 100% {
|
||||
transform: translateX(0);
|
||||
}
|
||||
10%, 30%, 50%, 70%, 90% {
|
||||
transform: translateX(-3px);
|
||||
}
|
||||
20%, 40%, 60%, 80% {
|
||||
transform: translateX(3px);
|
||||
}
|
||||
}
|
||||
|
||||
/* 边的执行状态样式 */
|
||||
.vue-flow__edge.edge-executing .vue-flow__edge-path {
|
||||
stroke: #409eff !important;
|
||||
stroke-width: 3.5 !important;
|
||||
stroke-dasharray: 8,4 !important;
|
||||
animation: edge-flow 1.5s linear infinite !important;
|
||||
}
|
||||
|
||||
.vue-flow__edge.edge-executed .vue-flow__edge-path {
|
||||
stroke: #67c23a !important;
|
||||
stroke-width: 3 !important;
|
||||
}
|
||||
|
||||
@keyframes edge-flow {
|
||||
0% {
|
||||
stroke-dashoffset: 0;
|
||||
}
|
||||
100% {
|
||||
stroke-dashoffset: 12;
|
||||
}
|
||||
}
|
||||
|
||||
/* 全局样式 - 优化边的选中效果(类似 Dify) */
|
||||
.vue-flow__edge.selected .vue-flow__edge-path {
|
||||
stroke: #67c23a !important;
|
||||
|
||||
@@ -148,6 +148,19 @@ export const useAgentStore = defineStore('agent', () => {
|
||||
}
|
||||
}
|
||||
|
||||
// 复制Agent
|
||||
const duplicateAgent = async (id: string, name?: string) => {
|
||||
loading.value = true
|
||||
try {
|
||||
const requestData = name ? { name } : {}
|
||||
const response = await api.post(`/api/v1/agents/${id}/duplicate`, requestData)
|
||||
agents.value.unshift(response.data) // 添加到列表开头
|
||||
return response.data
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 设置当前Agent
|
||||
const setCurrentAgent = (agent: Agent | null) => {
|
||||
currentAgent.value = agent
|
||||
@@ -164,6 +177,7 @@ export const useAgentStore = defineStore('agent', () => {
|
||||
deleteAgent,
|
||||
deployAgent,
|
||||
stopAgent,
|
||||
duplicateAgent,
|
||||
setCurrentAgent
|
||||
}
|
||||
})
|
||||
|
||||
@@ -70,7 +70,7 @@
|
||||
{{ formatDate(row.created_at) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="300" fixed="right">
|
||||
<el-table-column label="操作" width="350" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button link type="primary" @click="handleEdit(row)">
|
||||
<el-icon><Edit /></el-icon>
|
||||
@@ -80,6 +80,10 @@
|
||||
<el-icon><Setting /></el-icon>
|
||||
设计
|
||||
</el-button>
|
||||
<el-button link type="info" @click="handleDuplicate(row)">
|
||||
<el-icon><CopyDocument /></el-icon>
|
||||
复制
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="row.status === 'draft' || row.status === 'stopped'"
|
||||
link
|
||||
@@ -177,7 +181,8 @@ import {
|
||||
Delete,
|
||||
Setting,
|
||||
VideoPlay,
|
||||
VideoPause
|
||||
VideoPause,
|
||||
CopyDocument
|
||||
} from '@element-plus/icons-vue'
|
||||
import { useAgentStore } from '@/stores/agent'
|
||||
import type { Agent } from '@/stores/agent'
|
||||
@@ -374,6 +379,34 @@ const handleStop = async (agent: Agent) => {
|
||||
}
|
||||
}
|
||||
|
||||
// 复制
|
||||
const handleDuplicate = async (agent: Agent) => {
|
||||
try {
|
||||
const { value } = await ElMessageBox.prompt(
|
||||
`请输入新Agent的名称(留空将自动生成)`,
|
||||
'复制Agent',
|
||||
{
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
inputPlaceholder: `留空将使用: ${agent.name} (副本)`,
|
||||
inputValidator: (val: string) => {
|
||||
if (val && val.length > 100) {
|
||||
return '名称长度不能超过100个字符'
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
)
|
||||
await agentStore.duplicateAgent(agent.id, value || undefined)
|
||||
ElMessage.success('复制成功')
|
||||
await loadAgents()
|
||||
} catch (error: any) {
|
||||
if (error !== 'cancel') {
|
||||
ElMessage.error(error.response?.data?.detail || '复制失败')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 删除
|
||||
const handleDelete = async (agent: Agent) => {
|
||||
try {
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
:workflow-id="undefined"
|
||||
:initial-nodes="initialNodes"
|
||||
:initial-edges="initialEdges"
|
||||
:execution-status="executionStatus"
|
||||
@save="handleSaveWorkflow"
|
||||
@node-test="handleNodeTest"
|
||||
/>
|
||||
@@ -37,6 +38,7 @@
|
||||
:opening-message="openingMessage"
|
||||
:preset-questions="presetQuestions"
|
||||
:node-test-result="nodeTestResult"
|
||||
@execution-status="handleChatExecutionStatus"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -200,6 +202,12 @@ const loadWorkflowData = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
// 处理聊天界面的执行状态更新
|
||||
const handleChatExecutionStatus = (status: any) => {
|
||||
console.log('[rjb] WorkflowDesigner received execution status from chat:', JSON.stringify(status, null, 2))
|
||||
executionStatus.value = status
|
||||
}
|
||||
|
||||
// 测试Agent
|
||||
const handleTestAgent = () => {
|
||||
testInput.value = '{}'
|
||||
@@ -214,8 +222,8 @@ const handleRunTest = async () => {
|
||||
testing.value = true
|
||||
testResult.value = null
|
||||
|
||||
// 轮询超时时间(60秒)
|
||||
const maxPollingTime = 60000
|
||||
// 轮询超时时间(5分钟,复杂工作流可能需要更长时间)
|
||||
const maxPollingTime = 5 * 60 * 1000 // 5分钟 = 300000毫秒
|
||||
const startTime = Date.now()
|
||||
let pollingInterval: any = null
|
||||
|
||||
|
||||
Reference in New Issue
Block a user