feat: AI端点测试动态参数表单、接口工作流行内测试、本地开发环境改为线上域名
- 后端新增 /ai/endpoint/test 和 /ai/endpoint/stream 接口,支持直接端点测试 - 前端增加行内测试功能(场景绑定+接口工作流) - 测试对话框增加动态参数表单和参数定义编辑 - 支持 _meta 格式的默认输入参数处理 - web、web-admin 本地开发环境 API 调用改为线上域名 https://lifescript.happylifeos.com Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -218,6 +218,7 @@
|
||||
<el-option label="多行" value="textarea" />
|
||||
<el-option label="数字" value="number" />
|
||||
<el-option label="开关" value="boolean" />
|
||||
<el-option label="JSON" value="json" />
|
||||
</el-select>
|
||||
<el-input v-model="param.defaultValue" placeholder="默认值" style="width: 100px" />
|
||||
<el-checkbox v-model="param.required">必填</el-checkbox>
|
||||
@@ -298,6 +299,7 @@
|
||||
<el-input v-if="field.type === 'textarea'" v-model="field.value" type="textarea" :rows="3" :placeholder="field.name" @input="syncEndpointJsonFromFields" />
|
||||
<el-input-number v-if="field.type === 'number'" v-model="field.value" :placeholder="field.name" @change="syncEndpointJsonFromFields" style="width: 100%" />
|
||||
<el-switch v-if="field.type === 'boolean'" v-model="field.value" @change="syncEndpointJsonFromFields" />
|
||||
<el-input v-if="field.type === 'json'" v-model="field.value" type="textarea" :rows="4" :placeholder="field.placeholder || field.name" @input="syncEndpointJsonFromFields" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-collapse style="margin-top: 12px">
|
||||
@@ -336,6 +338,8 @@ import {
|
||||
deleteAiEndpoint,
|
||||
deleteAiProvider,
|
||||
deleteAiScene,
|
||||
getEndpointTestTemplate,
|
||||
getSceneTestTemplate,
|
||||
listAiCallLogs,
|
||||
listAiEndpoints,
|
||||
listAiProviders,
|
||||
@@ -348,7 +352,7 @@ import {
|
||||
testAiRuntime,
|
||||
testEndpointRuntime
|
||||
} from '@/api/aiconfig'
|
||||
import type { AiCallLog, AiEndpointConfig, AiProvider, AiRuntimeTestResponse, AiSceneBinding, TestParamField, ParamDefinition } from '@/types/aiconfig'
|
||||
import type { AiCallLog, AiEndpointConfig, AiProvider, AiRuntimeTestResponse, AiSceneBinding, TestParamField, ParamDefinition, AiTestTemplateResponse } from '@/types/aiconfig'
|
||||
|
||||
const activeTab = ref('providers')
|
||||
const loading = ref(false)
|
||||
@@ -478,27 +482,67 @@ function openScene(row?: AiSceneBinding) {
|
||||
sceneDialog.value = true
|
||||
}
|
||||
|
||||
function openRuntimeTest() {
|
||||
async function openRuntimeTest() {
|
||||
testForm.sceneCode = scenes.value.find(item => item.isEnabled === 1 && item.endpointId)?.sceneCode || scenes.value[0]?.sceneCode || ''
|
||||
testResult.value = null
|
||||
nonStreamResult.value = null
|
||||
await loadSceneTemplate(testForm.sceneCode)
|
||||
testDialog.value = true
|
||||
}
|
||||
|
||||
function openSceneRuntimeTest(row: AiSceneBinding) {
|
||||
async function openSceneRuntimeTest(row: AiSceneBinding) {
|
||||
testForm.sceneCode = row.sceneCode
|
||||
testResult.value = null
|
||||
nonStreamResult.value = null
|
||||
await loadSceneTemplate(row.sceneCode)
|
||||
testDialog.value = true
|
||||
}
|
||||
|
||||
function openEndpointTest(row: AiEndpointConfig) {
|
||||
async function openEndpointTest(row: AiEndpointConfig) {
|
||||
if (!row.id) return
|
||||
endpointTestRow.value = row
|
||||
endpointParamFields.value = parseParamFields(row.defaultInputs)
|
||||
endpointInputsJson.value = buildInputsJson(endpointParamFields.value)
|
||||
endpointTestResult.value = null
|
||||
endpointNonStreamResult.value = null
|
||||
endpointTestDialog.value = true
|
||||
endpointTesting.value = true
|
||||
try {
|
||||
const res = await getEndpointTestTemplate(row.id!)
|
||||
applyEndpointTemplate(res.data as AiTestTemplateResponse)
|
||||
} catch (error: any) {
|
||||
ElMessage.warning(error?.message || '测试样例加载失败,已使用本地默认参数')
|
||||
} finally {
|
||||
endpointTesting.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function loadSceneTemplate(sceneCode: string) {
|
||||
if (!sceneCode) {
|
||||
testInputsJson.value = '{\n "prompt": "请用一句中文回复测试成功。"\n}'
|
||||
return
|
||||
}
|
||||
try {
|
||||
const res = await getSceneTestTemplate(sceneCode)
|
||||
const template = res.data as AiTestTemplateResponse
|
||||
testInputsJson.value = JSON.stringify(template.inputs || {}, null, 2)
|
||||
} catch (error: any) {
|
||||
testInputsJson.value = '{\n "prompt": "请用一句中文回复测试成功。"\n}'
|
||||
ElMessage.warning(error?.message || '场景测试样例加载失败,已使用通用测试参数')
|
||||
}
|
||||
}
|
||||
|
||||
function applyEndpointTemplate(template: AiTestTemplateResponse) {
|
||||
const inputs = template.inputs || {}
|
||||
endpointInputsJson.value = JSON.stringify(inputs, null, 2)
|
||||
endpointParamFields.value = (template.paramFields || []).map(field => ({
|
||||
...field,
|
||||
value: field.type === 'json'
|
||||
? JSON.stringify(field.value ?? {}, null, 2)
|
||||
: field.value,
|
||||
defaultValue: field.value,
|
||||
required: Boolean(field.required)
|
||||
}))
|
||||
}
|
||||
|
||||
function parseParamFields(defaultInputs?: string): TestParamField[] {
|
||||
@@ -533,16 +577,28 @@ function parseParamFields(defaultInputs?: string): TestParamField[] {
|
||||
}
|
||||
}
|
||||
|
||||
function inferParamType(val: any): 'string' | 'textarea' | 'number' | 'boolean' {
|
||||
function inferParamType(val: any): 'string' | 'textarea' | 'number' | 'boolean' | 'json' {
|
||||
if (typeof val === 'number') return 'number'
|
||||
if (typeof val === 'boolean') return 'boolean'
|
||||
if (val && typeof val === 'object') return 'json'
|
||||
if (typeof val === 'string' && val.length > 80) return 'textarea'
|
||||
return 'string'
|
||||
}
|
||||
|
||||
function buildInputsJson(fields: TestParamField[]): string {
|
||||
const obj: Record<string, any> = {}
|
||||
fields.forEach(f => { obj[f.name] = f.value })
|
||||
fields.forEach(f => {
|
||||
if (!f.name) return
|
||||
if (f.type === 'json') {
|
||||
try {
|
||||
obj[f.name] = typeof f.value === 'string' ? JSON.parse(f.value || '{}') : f.value
|
||||
} catch {
|
||||
obj[f.name] = f.value
|
||||
}
|
||||
} else {
|
||||
obj[f.name] = f.value
|
||||
}
|
||||
})
|
||||
return JSON.stringify(obj, null, 2)
|
||||
}
|
||||
|
||||
@@ -555,7 +611,9 @@ function syncEndpointFieldsFromJson() {
|
||||
const parsed = JSON.parse(endpointInputsJson.value || '{}')
|
||||
endpointParamFields.value.forEach(field => {
|
||||
if (field.name in parsed) {
|
||||
field.value = parsed[field.name]
|
||||
field.value = field.type === 'json'
|
||||
? JSON.stringify(parsed[field.name] ?? {}, null, 2)
|
||||
: parsed[field.name]
|
||||
}
|
||||
})
|
||||
} catch { /* ignore parse errors */ }
|
||||
@@ -726,6 +784,7 @@ async function submitRuntimeTest() {
|
||||
|
||||
async function submitEndpointNonStreamTest() {
|
||||
if (!endpointTestRow.value) return
|
||||
if (!validateEndpointFields()) return
|
||||
let inputs: Record<string, any>
|
||||
try {
|
||||
inputs = JSON.parse(endpointInputsJson.value || '{}')
|
||||
@@ -757,6 +816,7 @@ async function submitEndpointNonStreamTest() {
|
||||
|
||||
async function submitEndpointStreamTest() {
|
||||
if (!endpointTestRow.value) return
|
||||
if (!validateEndpointFields()) return
|
||||
let inputs: Record<string, any>
|
||||
try {
|
||||
inputs = JSON.parse(endpointInputsJson.value || '{}')
|
||||
@@ -796,6 +856,26 @@ async function submitEndpointStreamTest() {
|
||||
}
|
||||
}
|
||||
|
||||
function validateEndpointFields() {
|
||||
for (const field of endpointParamFields.value) {
|
||||
if (!field.required) continue
|
||||
const value = field.value
|
||||
if (value === null || value === undefined || String(value).trim() === '') {
|
||||
ElMessage.error(`请填写必填参数:${field.label || field.name}`)
|
||||
return false
|
||||
}
|
||||
}
|
||||
for (const field of endpointParamFields.value.filter(item => item.type === 'json')) {
|
||||
try {
|
||||
JSON.parse(field.value || '{}')
|
||||
} catch {
|
||||
ElMessage.error(`参数 ${field.label || field.name} 不是有效 JSON`)
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
onMounted(loadAll)
|
||||
</script>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user