wwf
12 小时以前 a1d7e81859f554f3a53680cc35f0f49bf1f77098
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
<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>
          <!-- 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
}>()
 
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>