|
|
@@ -13,11 +13,58 @@
|
|
|
测试
|
|
|
</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>
|
|
|
+ <!-- TODO @lesan:是不是不用添加和删除参数,直接把必填和选填列出来,然后加上参数校验? -->
|
|
|
+ <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'
|
|
|
+// TODO @lesan:要不使用 ICon 哪个组件哈
|
|
|
+import { Delete } from '@element-plus/icons-vue'
|
|
|
|
|
|
defineProps<{
|
|
|
provider: any
|
|
|
@@ -25,9 +72,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 +234,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>
|