feat: AI测试输出渲染Markdown/流式响应、Coze/Dify适配器优化
- 新增 MarkdownPreview 组件,支持 AI 测试输出 Markdown 渲染 - Coze 适配器优化:支持流式响应、工作流接口调用、SSE事件处理 - Dify 适配器优化:支持停止接口、流式聊天、SSE事件解析 - web-admin 添加 markdown-it 和 highlight.js 依赖 - AI 配置列表页面优化测试对话框输出显示 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -272,14 +272,20 @@
|
||||
:title="nonStreamResult.status === 'success' ? '非流式测试成功' : '非流式测试失败'"
|
||||
show-icon
|
||||
/>
|
||||
<pre v-if="nonStreamResult" class="test-output">{{ nonStreamResult.output || nonStreamResult.errorMessage || '暂无输出' }}</pre>
|
||||
<div v-if="nonStreamResult" class="test-output">
|
||||
<MarkdownPreview v-if="nonStreamResult.status === 'success'" :content="testOutput(nonStreamResult)" />
|
||||
<pre v-else class="plain-output">{{ testOutput(nonStreamResult) }}</pre>
|
||||
</div>
|
||||
<el-alert
|
||||
v-if="testResult"
|
||||
:type="testResult.status === 'success' ? 'success' : 'error'"
|
||||
:title="testResult.status === 'success' ? '流式测试成功' : '流式测试失败'"
|
||||
show-icon
|
||||
/>
|
||||
<pre v-if="testResult" class="test-output">{{ testResult.output || testResult.errorMessage || '暂无输出' }}</pre>
|
||||
<div v-if="testResult" ref="sceneOutputRef" class="test-output">
|
||||
<MarkdownPreview v-if="testResult.status === 'success'" :content="testOutput(testResult)" />
|
||||
<pre v-else class="plain-output">{{ testOutput(testResult) }}</pre>
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button @click="testDialog = false">关闭</el-button>
|
||||
<el-button :loading="testing" @click="submitNonStreamTest">非流式测试</el-button>
|
||||
@@ -313,14 +319,20 @@
|
||||
:title="endpointNonStreamResult.status === 'success' ? '非流式测试成功' : '非流式测试失败'"
|
||||
show-icon
|
||||
/>
|
||||
<pre v-if="endpointNonStreamResult" class="test-output">{{ endpointNonStreamResult.output || endpointNonStreamResult.errorMessage || '暂无输出' }}</pre>
|
||||
<div v-if="endpointNonStreamResult" class="test-output">
|
||||
<MarkdownPreview v-if="endpointNonStreamResult.status === 'success'" :content="testOutput(endpointNonStreamResult)" />
|
||||
<pre v-else class="plain-output">{{ testOutput(endpointNonStreamResult) }}</pre>
|
||||
</div>
|
||||
<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>
|
||||
<div v-if="endpointTestResult" ref="endpointOutputRef" class="test-output">
|
||||
<MarkdownPreview v-if="endpointTestResult.status === 'success'" :content="testOutput(endpointTestResult)" />
|
||||
<pre v-else class="plain-output">{{ testOutput(endpointTestResult) }}</pre>
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button @click="endpointTestDialog = false">关闭</el-button>
|
||||
<el-button :loading="endpointTesting" @click="submitEndpointNonStreamTest">非流式测试</el-button>
|
||||
@@ -331,9 +343,10 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, reactive, ref } from 'vue'
|
||||
import { computed, nextTick, onMounted, reactive, ref } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { Plus, Refresh, VideoPlay } from '@element-plus/icons-vue'
|
||||
import MarkdownPreview from '@/components/MarkdownPreview.vue'
|
||||
import {
|
||||
deleteAiEndpoint,
|
||||
deleteAiProvider,
|
||||
@@ -344,6 +357,7 @@ import {
|
||||
listAiEndpoints,
|
||||
listAiProviders,
|
||||
listAiScenes,
|
||||
normalizeAiText,
|
||||
saveAiEndpoint,
|
||||
saveAiProvider,
|
||||
saveAiScene,
|
||||
@@ -369,6 +383,7 @@ const testing = ref(false)
|
||||
const testResult = ref<AiRuntimeTestResponse | null>(null)
|
||||
const nonStreamResult = ref<AiRuntimeTestResponse | null>(null)
|
||||
const testInputsJson = ref('{\n "prompt": "请用一句中文回复测试成功。"\n}')
|
||||
const sceneOutputRef = ref<HTMLElement | null>(null)
|
||||
|
||||
const endpointTestDialog = ref(false)
|
||||
const endpointTesting = ref(false)
|
||||
@@ -377,6 +392,7 @@ const endpointTestResult = ref<AiRuntimeTestResponse | null>(null)
|
||||
const endpointNonStreamResult = ref<AiRuntimeTestResponse | null>(null)
|
||||
const endpointInputsJson = ref('{}')
|
||||
const endpointParamFields = ref<TestParamField[]>([])
|
||||
const endpointOutputRef = ref<HTMLElement | null>(null)
|
||||
|
||||
const paramDefinitions = ref<ParamDefinition[]>([])
|
||||
|
||||
@@ -448,6 +464,19 @@ function statusLabel(status?: string) {
|
||||
return status ? map[status] || status : '-'
|
||||
}
|
||||
|
||||
function testOutput(result?: AiRuntimeTestResponse | null) {
|
||||
return normalizeAiText(result?.output || result?.errorMessage || '暂无输出')
|
||||
}
|
||||
|
||||
function scrollOutput(target: { value: HTMLElement | null }) {
|
||||
nextTick(() => {
|
||||
const element = target.value
|
||||
if (element) {
|
||||
element.scrollTop = element.scrollHeight
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async function loadAll() {
|
||||
loading.value = true
|
||||
try {
|
||||
@@ -757,6 +786,7 @@ async function submitRuntimeTest() {
|
||||
await streamAiRuntime({ sceneCode: testForm.sceneCode, inputs }, (event, output) => {
|
||||
if (!testResult.value) return
|
||||
testResult.value.output = output
|
||||
scrollOutput(sceneOutputRef)
|
||||
if (event.type === 'delta') {
|
||||
testResult.value.streamChunks = (testResult.value.streamChunks || 0) + 1
|
||||
}
|
||||
@@ -831,6 +861,7 @@ async function submitEndpointStreamTest() {
|
||||
await streamEndpointRuntime({ endpointId: endpointTestRow.value.id!, inputs }, (event, output) => {
|
||||
if (!endpointTestResult.value) return
|
||||
endpointTestResult.value.output = output
|
||||
scrollOutput(endpointOutputRef)
|
||||
if (event.type === 'delta') {
|
||||
endpointTestResult.value.streamChunks = (endpointTestResult.value.streamChunks || 0) + 1
|
||||
}
|
||||
@@ -975,7 +1006,16 @@ onMounted(loadAll)
|
||||
background: rgba(0, 0, 0, 0.22);
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
border-radius: var(--ls-radius-md);
|
||||
}
|
||||
|
||||
.plain-output {
|
||||
margin: 0;
|
||||
color: rgba(226, 232, 240, 0.92);
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
||||
font-size: 13px;
|
||||
line-height: 1.7;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.endpoint-info {
|
||||
|
||||
Reference in New Issue
Block a user