Files
yudao-ui-admin-vue3/src/views/ai/workflow/form/WorkflowDesign.vue

249 lines
6.7 KiB
Vue

<template>
<div class="relative" style="width: 100%; height: 700px">
<Tinyflow
v-if="workflowData"
ref="tinyflowRef"
:className="'custom-class'"
:style="{ width: '100%', height: '100%' }"
:data="workflowData"
:provider="provider"
/>
<div class="absolute top-30px right-30px">
<el-button @click="testWorkflowModel" type="primary" v-hasPermi="['ai:workflow:test']">
测试
</el-button>
</div>
<!-- 测试窗口 -->
<el-drawer v-model="showTestDrawer" title="工作流测试" :modal="false">
<fieldset>
<legend class="ml-15px"><h3>运行参数配置</h3></legend>
<div class="p-20px">
<div
class="flex justify-around mb-10px"
v-for="(param, index) in params4Test"
:key="index"
>
<el-select class="w-200px!" v-model="param.key" placeholder="参数名">
<el-option
v-for="(value, key) in paramsOfStartNode"
:key="key"
:label="value?.description || key"
:value="key"
:disabled="!!value?.disabled"
/>
</el-select>
<el-input class="w-200px!" v-model="param.value" placeholder="参数值" />
<el-button type="danger" plain :icon="Delete" circle @click="removeParam(index)" />
</div>
<el-button type="primary" plain @click="addParam">添加参数</el-button>
</div>
</fieldset>
<fieldset class="mt-20px bg-#f8f9fa">
<legend class="ml-15px"><h3>运行结果</h3></legend>
<div class="p-20px">
<div v-if="loading"> <el-text type="primary">执行中...</el-text></div>
<div v-else-if="error">
<el-text type="danger">{{ error }}</el-text>
</div>
<pre v-else-if="testResult" class="result-content"
>{{ JSON.stringify(testResult, null, 2) }}
</pre>
<div v-else> <el-text type="info">点击运行查看结果</el-text> </div>
</div>
</fieldset>
<el-button class="mt-20px w-100%" size="large" type="success" @click="goRun"
>运行流程</el-button
>
</el-drawer>
</div>
</template>
<script setup lang="ts">
import Tinyflow from '@/components/Tinyflow/Tinyflow.vue'
import * as WorkflowApi from '@/api/ai/workflow'
import { Delete } from '@element-plus/icons-vue'
defineProps<{
provider: any
}>()
const tinyflowRef = ref()
const workflowData = inject('workflowData') as Ref
const showTestDrawer = ref(false)
const params4Test = ref([])
const paramsOfStartNode = ref({})
const testResult = ref(null)
const loading = ref(false)
const error = ref(null)
/** 展示工作流测试抽屉 */
const testWorkflowModel = () => {
showTestDrawer.value = !showTestDrawer.value
}
/** 运行流程 */
const goRun = async () => {
try {
const val = tinyflowRef.value.getData()
loading.value = true
error.value = null
testResult.value = null
/// 查找start节点
const startNode = getStartNode()
// 获取参数定义
const parameters = startNode.data?.parameters || []
const paramDefinitions = {}
parameters.forEach((param) => {
paramDefinitions[param.name] = param.dataType
})
// 参数类型转换
const convertedParams = {}
for (const { key, value } of params4Test.value) {
const paramKey = key.trim()
if (!paramKey) continue
let dataType = paramDefinitions[paramKey]
if (!dataType) {
dataType = 'String'
}
try {
convertedParams[paramKey] = convertParamValue(value, dataType)
} catch (e) {
throw new Error(`参数 ${paramKey} 转换失败: ${e.message}`)
}
}
const data = {
graph: JSON.stringify(val),
params: convertedParams
}
const response = await WorkflowApi.testWorkflow(data)
testResult.value = response
} catch (err) {
error.value = err.response?.data?.message || '运行失败,请检查参数和网络连接'
} finally {
loading.value = false
}
}
/** 监听测试抽屉的开启,获取开始节点参数列表 */
watch(showTestDrawer, (value) => {
if (!value) return
/// 查找start节点
const startNode = getStartNode()
// 获取参数定义
const parameters = startNode.data?.parameters || []
const paramDefinitions = {}
// 加入参数选项方便用户添加非必须参数
parameters.forEach((param) => {
paramDefinitions[param.name] = param
})
function mergeIfRequiredButNotSet(target) {
let needPushList = []
for (let key in paramDefinitions) {
let param = paramDefinitions[key]
if (param.required) {
let item = target.find((item) => item.key === key)
if (!item) {
needPushList.push({ key: param.name, value: param.defaultValue || '' })
}
}
}
target.push(...needPushList)
}
// 自动装载需必填的参数
mergeIfRequiredButNotSet(params4Test.value)
paramsOfStartNode.value = paramDefinitions
})
/** 获取开始节点 */
const getStartNode = () => {
const val = tinyflowRef.value.getData()
const startNode = val.nodes.find((node) => node.type === 'startNode')
if (!startNode) {
throw new Error('流程缺少开始节点')
}
return startNode
}
/** 添加参数项 */
const addParam = () => {
params4Test.value.push({ key: '', value: '' })
}
/** 删除参数项 */
const removeParam = (index) => {
params4Test.value.splice(index, 1)
}
/** 类型转换函数 */
const convertParamValue = (value, dataType) => {
if (value === '') return null // 空值处理
switch (dataType) {
case 'String':
return String(value)
case 'Number':
const num = Number(value)
if (isNaN(num)) throw new Error('非数字格式')
return num
case 'Boolean':
if (value.toLowerCase() === 'true') return true
if (value.toLowerCase() === 'false') return false
throw new Error('必须为 true/false')
case 'Object':
case 'Array':
try {
return JSON.parse(value)
} catch (e) {
throw new Error(`JSON格式错误: ${e.message}`)
}
default:
throw new Error(`不支持的类型: ${dataType}`)
}
}
/** 表单校验 */
const validate = async () => {
try {
// 获取最新的流程数据
if (!workflowData.value) {
throw new Error('请设计流程')
}
workflowData.value = tinyflowRef.value.getData()
return true
} catch (error) {
throw error
}
}
defineExpose({
validate
})
</script>
<style lang="css" scoped>
.result-content {
background: white;
padding: 12px;
border-radius: 4px;
max-height: 300px;
overflow: auto;
font-family: Monaco, Consolas, monospace;
font-size: 14px;
line-height: 1.5;
white-space: pre-wrap;
}
</style>