feat: 前端增加行内测试功能(场景绑定+接口工作流)
- 场景绑定表和接口工作流表操作列增加「测试」按钮 - 新增接口工作流测试对话框(流式+非流式) - API 层提取 fetchSseStream 共享辅助函数 - 新增 testEndpointRuntime 和 streamEndpointRuntime Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -97,10 +97,11 @@
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="150" fixed="right" align="center">
|
||||
<el-table-column label="操作" width="220" fixed="right" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-button link type="primary" @click="openEndpoint(row)">编辑</el-button>
|
||||
<el-button link type="danger" @click="removeEndpoint(row)">删除</el-button>
|
||||
<el-button link type="success" :disabled="row.isEnabled !== 1" @click="openEndpointTest(row)">测试</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
@@ -134,10 +135,11 @@
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="150" fixed="right" align="center">
|
||||
<el-table-column label="操作" width="220" fixed="right" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-button link type="primary" @click="openScene(row)">编辑</el-button>
|
||||
<el-button link type="danger" @click="removeScene(row)">删除</el-button>
|
||||
<el-button link type="success" @click="openSceneRuntimeTest(row)">测试</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
@@ -267,6 +269,37 @@
|
||||
<el-button type="primary" :loading="testing" @click="submitRuntimeTest">流式测试</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog v-model="endpointTestDialog" title="接口工作流测试" width="760px">
|
||||
<div class="endpoint-info">
|
||||
<span class="endpoint-label">接口名称:</span>
|
||||
<span>{{ endpointTestRow?.endpointName }}({{ endpointTestRow?.endpointCode }})</span>
|
||||
</div>
|
||||
<el-form label-width="110px" style="margin-top: 16px">
|
||||
<el-form-item label="入参 JSON">
|
||||
<el-input v-model="endpointInputsJson" type="textarea" :rows="6" placeholder="请输入 JSON 入参" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-alert
|
||||
v-if="endpointNonStreamResult"
|
||||
:type="endpointNonStreamResult.status === 'success' ? 'success' : 'error'"
|
||||
:title="endpointNonStreamResult.status === 'success' ? '非流式测试成功' : '非流式测试失败'"
|
||||
show-icon
|
||||
/>
|
||||
<pre v-if="endpointNonStreamResult" class="test-output">{{ endpointNonStreamResult.output || endpointNonStreamResult.errorMessage || '暂无输出' }}</pre>
|
||||
<el-alert
|
||||
v-if="endpointTestResult"
|
||||
:type="endpointTestResult.status === 'success' ? 'success' : 'error'"
|
||||
:title="endpointTestResult.status === 'success' ? '流式测试成功' : '流式测试失败'"
|
||||
show-icon
|
||||
/>
|
||||
<pre v-if="endpointTestResult" class="test-output">{{ endpointTestResult.output || endpointTestResult.errorMessage || '暂无输出' }}</pre>
|
||||
<template #footer>
|
||||
<el-button @click="endpointTestDialog = false">关闭</el-button>
|
||||
<el-button :loading="endpointTesting" @click="submitEndpointNonStreamTest">非流式测试</el-button>
|
||||
<el-button type="primary" :loading="endpointTesting" @click="submitEndpointStreamTest">流式测试</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -286,7 +319,9 @@ import {
|
||||
saveAiProvider,
|
||||
saveAiScene,
|
||||
streamAiRuntime,
|
||||
testAiRuntime
|
||||
streamEndpointRuntime,
|
||||
testAiRuntime,
|
||||
testEndpointRuntime
|
||||
} from '@/api/aiconfig'
|
||||
import type { AiCallLog, AiEndpointConfig, AiProvider, AiRuntimeTestResponse, AiSceneBinding } from '@/types/aiconfig'
|
||||
|
||||
@@ -306,6 +341,13 @@ const testResult = ref<AiRuntimeTestResponse | null>(null)
|
||||
const nonStreamResult = ref<AiRuntimeTestResponse | null>(null)
|
||||
const testInputsJson = ref('{\n "prompt": "请用一句中文回复测试成功。"\n}')
|
||||
|
||||
const endpointTestDialog = ref(false)
|
||||
const endpointTesting = ref(false)
|
||||
const endpointTestRow = ref<AiEndpointConfig | null>(null)
|
||||
const endpointTestResult = ref<AiRuntimeTestResponse | null>(null)
|
||||
const endpointNonStreamResult = ref<AiRuntimeTestResponse | null>(null)
|
||||
const endpointInputsJson = ref('{}')
|
||||
|
||||
const providerForm = reactive<AiProvider>(newProvider())
|
||||
const endpointForm = reactive<AiEndpointConfig>(newEndpoint())
|
||||
const sceneForm = reactive<AiSceneBinding>(newScene())
|
||||
@@ -414,6 +456,34 @@ function openRuntimeTest() {
|
||||
testDialog.value = true
|
||||
}
|
||||
|
||||
function openSceneRuntimeTest(row: AiSceneBinding) {
|
||||
testForm.sceneCode = row.sceneCode
|
||||
testResult.value = null
|
||||
nonStreamResult.value = null
|
||||
testDialog.value = true
|
||||
}
|
||||
|
||||
function openEndpointTest(row: AiEndpointConfig) {
|
||||
endpointTestRow.value = row
|
||||
if (row.defaultInputs) {
|
||||
try {
|
||||
const parsed = JSON.parse(row.defaultInputs)
|
||||
if (typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed)) {
|
||||
endpointInputsJson.value = JSON.stringify(parsed, null, 2)
|
||||
} else {
|
||||
endpointInputsJson.value = '{}'
|
||||
}
|
||||
} catch {
|
||||
endpointInputsJson.value = '{}'
|
||||
}
|
||||
} else {
|
||||
endpointInputsJson.value = '{}'
|
||||
}
|
||||
endpointTestResult.value = null
|
||||
endpointNonStreamResult.value = null
|
||||
endpointTestDialog.value = true
|
||||
}
|
||||
|
||||
async function submitProvider() {
|
||||
await saveAiProvider({ ...providerForm })
|
||||
providerDialog.value = false
|
||||
@@ -529,6 +599,78 @@ async function submitRuntimeTest() {
|
||||
}
|
||||
}
|
||||
|
||||
async function submitEndpointNonStreamTest() {
|
||||
if (!endpointTestRow.value) return
|
||||
let inputs: Record<string, any>
|
||||
try {
|
||||
inputs = JSON.parse(endpointInputsJson.value || '{}')
|
||||
} catch {
|
||||
ElMessage.error('入参 JSON 格式不正确')
|
||||
return
|
||||
}
|
||||
endpointTesting.value = true
|
||||
try {
|
||||
const res = await testEndpointRuntime({ endpointId: endpointTestRow.value.id!, inputs })
|
||||
endpointNonStreamResult.value = res.data as AiRuntimeTestResponse
|
||||
if (endpointNonStreamResult.value.status === 'success') {
|
||||
ElMessage.success('非流式测试成功')
|
||||
} else {
|
||||
ElMessage.error(`测试失败: ${endpointNonStreamResult.value.errorMessage || endpointNonStreamResult.value.errorCode}`)
|
||||
}
|
||||
await loadAll()
|
||||
} catch (error: any) {
|
||||
endpointNonStreamResult.value = {
|
||||
sceneCode: '',
|
||||
status: 'failed',
|
||||
errorMessage: error?.message || '非流式测试失败'
|
||||
} as AiRuntimeTestResponse
|
||||
ElMessage.error(error?.message || '非流式测试失败')
|
||||
} finally {
|
||||
endpointTesting.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function submitEndpointStreamTest() {
|
||||
if (!endpointTestRow.value) return
|
||||
let inputs: Record<string, any>
|
||||
try {
|
||||
inputs = JSON.parse(endpointInputsJson.value || '{}')
|
||||
} catch {
|
||||
ElMessage.error('入参 JSON 格式不正确')
|
||||
return
|
||||
}
|
||||
endpointTesting.value = true
|
||||
endpointTestResult.value = { sceneCode: '', status: 'success', output: '', streamChunks: 0, durationMs: 0 }
|
||||
const startedAt = Date.now()
|
||||
try {
|
||||
await streamEndpointRuntime({ endpointId: endpointTestRow.value.id!, inputs }, (event, output) => {
|
||||
if (!endpointTestResult.value) return
|
||||
endpointTestResult.value.output = output
|
||||
if (event.type === 'delta') {
|
||||
endpointTestResult.value.streamChunks = (endpointTestResult.value.streamChunks || 0) + 1
|
||||
}
|
||||
if (event.type === 'done') {
|
||||
endpointTestResult.value.status = 'success'
|
||||
}
|
||||
if (event.type === 'error') {
|
||||
endpointTestResult.value.status = 'failed'
|
||||
endpointTestResult.value.errorCode = event.code
|
||||
endpointTestResult.value.errorMessage = event.message
|
||||
}
|
||||
endpointTestResult.value.durationMs = Date.now() - startedAt
|
||||
})
|
||||
await loadAll()
|
||||
} catch (error: any) {
|
||||
if (endpointTestResult.value) {
|
||||
endpointTestResult.value.status = 'failed'
|
||||
endpointTestResult.value.errorMessage = error?.message || '流式测试失败'
|
||||
endpointTestResult.value.durationMs = Date.now() - startedAt
|
||||
}
|
||||
} finally {
|
||||
endpointTesting.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(loadAll)
|
||||
</script>
|
||||
|
||||
@@ -630,4 +772,18 @@ onMounted(loadAll)
|
||||
border-radius: var(--ls-radius-md);
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.endpoint-info {
|
||||
padding: 10px 14px;
|
||||
color: rgba(226, 232, 240, 0.85);
|
||||
background: rgba(0, 0, 0, 0.15);
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
border-radius: var(--ls-radius-md);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.endpoint-label {
|
||||
color: var(--ls-text-muted);
|
||||
margin-right: 6px;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user