Quellcode durchsuchen

!771 feat: 完善AI工作流运行测试(通义千问)
Merge pull request !771 from Lesan/feature/ai-workflow

芋道源码 vor 10 Monaten
Ursprung
Commit
bc817f85f8

+ 2 - 2
src/api/ai/workflow/index.ts

@@ -20,6 +20,6 @@ export const deleteWorkflow = async (id) => {
   return await request.delete({ url: '/ai/workflow/delete?id=' + id })
 }
 
-export const updateWorkflowModel = async (data) => {
-  return await request.put({ url: '/ai/workflow/updateWorkflowModel', data })
+export const testWorkflow = async (data) => {
+  return await request.post({ url: '/ai/workflow/test', data })
 }

+ 200 - 1
src/views/ai/workflow/form/WorkflowDesign.vue

@@ -13,11 +13,56 @@
         测试
       </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
@@ -25,9 +70,149 @@ defineProps<{
 
 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 = () => {
-  // TODO @lesan 测试
+  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}`)
+  }
 }
 
 /** 表单校验 */
@@ -47,3 +232,17 @@ 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>

+ 2 - 2
src/views/ai/workflow/form/index.vue

@@ -73,7 +73,7 @@ import { CommonStatusEnum } from '@/utils/constants'
 import * as WorkflowApi from '@/api/ai/workflow'
 import BasicInfo from './BasicInfo.vue'
 import WorkflowDesign from './WorkflowDesign.vue'
-import { ApiKeyApi } from '@/api/ai/model/apiKey'
+import { ModelApi } from '@/api/ai/model/model'
 
 const router = useRouter()
 const { delView } = useTagsViewStore()
@@ -118,7 +118,7 @@ const initData = async () => {
     workflowData.value = JSON.parse(formData.value.graph)
   }
 
-  const apiKeys = await ApiKeyApi.getApiKeySimpleList()
+  const apiKeys = await ModelApi.getModelSimpleList(1)
   provider.value = {
     llm: () =>
       apiKeys.map(({ id, name }) => ({