WorkflowDesign.vue 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250
  1. <template>
  2. <div class="relative" style="width: 100%; height: 700px">
  3. <Tinyflow
  4. v-if="workflowData"
  5. ref="tinyflowRef"
  6. :className="'custom-class'"
  7. :style="{ width: '100%', height: '100%' }"
  8. :data="workflowData"
  9. :provider="provider"
  10. />
  11. <div class="absolute top-30px right-30px">
  12. <el-button @click="testWorkflowModel" type="primary" v-hasPermi="['ai:workflow:test']">
  13. 测试
  14. </el-button>
  15. </div>
  16. <!-- 测试窗口 -->
  17. <el-drawer v-model="showTestDrawer" title="工作流测试" :modal="false">
  18. <fieldset>
  19. <legend class="ml-15px"><h3>运行参数配置</h3></legend>
  20. <div class="p-20px">
  21. <div
  22. class="flex justify-around mb-10px"
  23. v-for="(param, index) in params4Test"
  24. :key="index"
  25. >
  26. <el-select class="w-200px!" v-model="param.key" placeholder="参数名">
  27. <el-option
  28. v-for="(value, key) in paramsOfStartNode"
  29. :key="key"
  30. :label="value?.description || key"
  31. :value="key"
  32. :disabled="!!value?.disabled"
  33. />
  34. </el-select>
  35. <el-input class="w-200px!" v-model="param.value" placeholder="参数值" />
  36. <el-button type="danger" plain :icon="Delete" circle @click="removeParam(index)" />
  37. </div>
  38. <!-- TODO @lesan:是不是不用添加和删除参数,直接把必填和选填列出来,然后加上参数校验? -->
  39. <el-button type="primary" plain @click="addParam">添加参数</el-button>
  40. </div>
  41. </fieldset>
  42. <fieldset class="mt-20px bg-#f8f9fa">
  43. <legend class="ml-15px"><h3>运行结果</h3></legend>
  44. <div class="p-20px">
  45. <div v-if="loading"> <el-text type="primary">执行中...</el-text></div>
  46. <div v-else-if="error">
  47. <el-text type="danger">{{ error }}</el-text>
  48. </div>
  49. <pre v-else-if="testResult" class="result-content"
  50. >{{ JSON.stringify(testResult, null, 2) }}
  51. </pre>
  52. <div v-else> <el-text type="info">点击运行查看结果</el-text> </div>
  53. </div>
  54. </fieldset>
  55. <el-button class="mt-20px w-100%" size="large" type="success" @click="goRun">
  56. 运行流程
  57. </el-button>
  58. </el-drawer>
  59. </div>
  60. </template>
  61. <script setup lang="ts">
  62. import Tinyflow from '@/components/Tinyflow/Tinyflow.vue'
  63. import * as WorkflowApi from '@/api/ai/workflow'
  64. // TODO @lesan:要不使用 ICon 哪个组件哈
  65. import { Delete } from '@element-plus/icons-vue'
  66. defineProps<{
  67. provider: any
  68. }>()
  69. const tinyflowRef = ref()
  70. const workflowData = inject('workflowData') as Ref
  71. const showTestDrawer = ref(false)
  72. const params4Test = ref([])
  73. const paramsOfStartNode = ref({})
  74. const testResult = ref(null)
  75. const loading = ref(false)
  76. const error = ref(null)
  77. /** 展示工作流测试抽屉 */
  78. const testWorkflowModel = () => {
  79. showTestDrawer.value = !showTestDrawer.value
  80. }
  81. /** 运行流程 */
  82. const goRun = async () => {
  83. try {
  84. const val = tinyflowRef.value.getData()
  85. loading.value = true
  86. error.value = null
  87. testResult.value = null
  88. /// 查找start节点
  89. const startNode = getStartNode()
  90. // 获取参数定义
  91. const parameters = startNode.data?.parameters || []
  92. const paramDefinitions = {}
  93. parameters.forEach((param) => {
  94. paramDefinitions[param.name] = param.dataType
  95. })
  96. // 参数类型转换
  97. const convertedParams = {}
  98. for (const { key, value } of params4Test.value) {
  99. const paramKey = key.trim()
  100. if (!paramKey) continue
  101. let dataType = paramDefinitions[paramKey]
  102. if (!dataType) {
  103. dataType = 'String'
  104. }
  105. try {
  106. convertedParams[paramKey] = convertParamValue(value, dataType)
  107. } catch (e) {
  108. throw new Error(`参数 ${paramKey} 转换失败: ${e.message}`)
  109. }
  110. }
  111. const data = {
  112. graph: JSON.stringify(val),
  113. params: convertedParams
  114. }
  115. const response = await WorkflowApi.testWorkflow(data)
  116. testResult.value = response
  117. } catch (err) {
  118. error.value = err.response?.data?.message || '运行失败,请检查参数和网络连接'
  119. } finally {
  120. loading.value = false
  121. }
  122. }
  123. /** 监听测试抽屉的开启,获取开始节点参数列表 */
  124. watch(showTestDrawer, (value) => {
  125. if (!value) return
  126. /// 查找start节点
  127. const startNode = getStartNode()
  128. // 获取参数定义
  129. const parameters = startNode.data?.parameters || []
  130. const paramDefinitions = {}
  131. // 加入参数选项方便用户添加非必须参数
  132. parameters.forEach((param) => {
  133. paramDefinitions[param.name] = param
  134. })
  135. function mergeIfRequiredButNotSet(target) {
  136. let needPushList = []
  137. for (let key in paramDefinitions) {
  138. let param = paramDefinitions[key]
  139. if (param.required) {
  140. let item = target.find((item) => item.key === key)
  141. if (!item) {
  142. needPushList.push({ key: param.name, value: param.defaultValue || '' })
  143. }
  144. }
  145. }
  146. target.push(...needPushList)
  147. }
  148. // 自动装载需必填的参数
  149. mergeIfRequiredButNotSet(params4Test.value)
  150. paramsOfStartNode.value = paramDefinitions
  151. })
  152. /** 获取开始节点 */
  153. const getStartNode = () => {
  154. const val = tinyflowRef.value.getData()
  155. const startNode = val.nodes.find((node) => node.type === 'startNode')
  156. if (!startNode) {
  157. throw new Error('流程缺少开始节点')
  158. }
  159. return startNode
  160. }
  161. /** 添加参数项 */
  162. const addParam = () => {
  163. params4Test.value.push({ key: '', value: '' })
  164. }
  165. /** 删除参数项 */
  166. const removeParam = (index) => {
  167. params4Test.value.splice(index, 1)
  168. }
  169. /** 类型转换函数 */
  170. const convertParamValue = (value, dataType) => {
  171. if (value === '') return null // 空值处理
  172. switch (dataType) {
  173. case 'String':
  174. return String(value)
  175. case 'Number':
  176. const num = Number(value)
  177. if (isNaN(num)) throw new Error('非数字格式')
  178. return num
  179. case 'Boolean':
  180. if (value.toLowerCase() === 'true') return true
  181. if (value.toLowerCase() === 'false') return false
  182. throw new Error('必须为 true/false')
  183. case 'Object':
  184. case 'Array':
  185. try {
  186. return JSON.parse(value)
  187. } catch (e) {
  188. throw new Error(`JSON格式错误: ${e.message}`)
  189. }
  190. default:
  191. throw new Error(`不支持的类型: ${dataType}`)
  192. }
  193. }
  194. /** 表单校验 */
  195. const validate = async () => {
  196. try {
  197. // 获取最新的流程数据
  198. if (!workflowData.value) {
  199. throw new Error('请设计流程')
  200. }
  201. workflowData.value = tinyflowRef.value.getData()
  202. return true
  203. } catch (error) {
  204. throw error
  205. }
  206. }
  207. defineExpose({
  208. validate
  209. })
  210. </script>
  211. <style lang="css" scoped>
  212. .result-content {
  213. background: white;
  214. padding: 12px;
  215. border-radius: 4px;
  216. max-height: 300px;
  217. overflow: auto;
  218. font-family: Monaco, Consolas, monospace;
  219. font-size: 14px;
  220. line-height: 1.5;
  221. white-space: pre-wrap;
  222. }
  223. </style>