AI接口支持流式调用
This commit is contained in:
@@ -549,6 +549,28 @@
|
||||
<el-input v-model="testRequest.url" readonly />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="测试选项">
|
||||
<div class="test-options">
|
||||
<el-checkbox
|
||||
v-model="testOptions.useStream"
|
||||
@change="updateTestRequestBody"
|
||||
>
|
||||
启用流式响应
|
||||
</el-checkbox>
|
||||
<el-tooltip content="启用后将测试流式返回,可以看到AI逐步生成的响应内容" placement="top">
|
||||
<el-icon class="info-icon"><InfoFilled /></el-icon>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="测试消息">
|
||||
<el-input
|
||||
v-model="testOptions.testMessage"
|
||||
placeholder="输入测试消息内容"
|
||||
@input="updateTestRequestBody"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="请求头">
|
||||
<el-input
|
||||
v-model="testRequest.headers"
|
||||
@@ -562,14 +584,14 @@
|
||||
<el-input
|
||||
v-model="testRequest.body"
|
||||
type="textarea"
|
||||
:rows="12"
|
||||
:rows="10"
|
||||
placeholder="JSON格式的请求体"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handleTestRequest" :loading="testLoading">
|
||||
发送测试请求
|
||||
{{ testOptions.useStream ? '发送流式测试' : '发送测试请求' }}
|
||||
</el-button>
|
||||
<el-button @click="handleFormatRequest">格式化请求</el-button>
|
||||
<el-button @click="handleResetTest">重置</el-button>
|
||||
@@ -625,6 +647,7 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { ElMessage, ElMessageBox, type FormInstance, type FormRules } from 'element-plus'
|
||||
import { InfoFilled } from '@element-plus/icons-vue'
|
||||
import {
|
||||
getAiConfigPage,
|
||||
createAiConfig,
|
||||
@@ -746,6 +769,11 @@ const testResponse = reactive({
|
||||
body: ''
|
||||
})
|
||||
|
||||
const testOptions = reactive({
|
||||
useStream: false,
|
||||
testMessage: '你好,这是一个测试消息,请回复确认接口正常工作。'
|
||||
})
|
||||
|
||||
// 获取配置类型标签类型
|
||||
const getConfigTypeTagType = (type: string) => {
|
||||
const typeMap: Record<string, string> = {
|
||||
@@ -1078,11 +1106,11 @@ const initTestData = (config: AiConfig) => {
|
||||
const requestBody: any = {
|
||||
bot_id: config.botId || '',
|
||||
user_id: 'test_user_' + Date.now(),
|
||||
stream: false,
|
||||
stream: testOptions.useStream,
|
||||
additional_messages: [
|
||||
{
|
||||
role: 'user',
|
||||
content: '你好,这是一个测试消息,请回复确认接口正常工作。',
|
||||
content: testOptions.testMessage,
|
||||
content_type: 'text',
|
||||
type: 'question'
|
||||
}
|
||||
@@ -1113,6 +1141,20 @@ const initTestData = (config: AiConfig) => {
|
||||
testResponse.body = ''
|
||||
}
|
||||
|
||||
// 更新测试请求体
|
||||
const updateTestRequestBody = () => {
|
||||
if (!testConfig.value) return
|
||||
|
||||
try {
|
||||
const body = JSON.parse(testRequest.body)
|
||||
body.stream = testOptions.useStream
|
||||
body.additional_messages[0].content = testOptions.testMessage
|
||||
testRequest.body = JSON.stringify(body, null, 2)
|
||||
} catch (e) {
|
||||
console.warn('更新请求体失败:', e)
|
||||
}
|
||||
}
|
||||
|
||||
// 发送测试请求
|
||||
const handleTestRequest = async () => {
|
||||
if (!testConfig.value) return
|
||||
@@ -1138,39 +1180,15 @@ const handleTestRequest = async () => {
|
||||
return
|
||||
}
|
||||
|
||||
// 发送请求
|
||||
const response = await fetch(testRequest.url, {
|
||||
method: 'POST',
|
||||
headers: headers,
|
||||
body: JSON.stringify(body)
|
||||
})
|
||||
// 检查是否为流式请求
|
||||
const isStreamRequest = body.stream === true
|
||||
|
||||
// 获取响应头
|
||||
const responseHeaders: any = {}
|
||||
response.headers.forEach((value, key) => {
|
||||
responseHeaders[key] = value
|
||||
})
|
||||
|
||||
// 获取响应体
|
||||
const responseBody = await response.text()
|
||||
|
||||
// 更新响应数据
|
||||
testResponse.status = response.status
|
||||
testResponse.headers = JSON.stringify(responseHeaders, null, 2)
|
||||
testResponse.body = responseBody
|
||||
|
||||
// 尝试格式化响应体
|
||||
try {
|
||||
const jsonBody = JSON.parse(responseBody)
|
||||
testResponse.body = JSON.stringify(jsonBody, null, 2)
|
||||
} catch (e) {
|
||||
// 如果不是JSON格式,保持原样
|
||||
}
|
||||
|
||||
if (response.ok) {
|
||||
ElMessage.success('测试请求发送成功')
|
||||
if (isStreamRequest) {
|
||||
// 处理流式请求
|
||||
await handleStreamRequest(headers, body)
|
||||
} else {
|
||||
ElMessage.warning(`请求返回状态码: ${response.status}`)
|
||||
// 处理普通请求
|
||||
await handleNormalRequest(headers, body)
|
||||
}
|
||||
|
||||
} catch (error: any) {
|
||||
@@ -1190,6 +1208,165 @@ const handleTestRequest = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
// 处理普通请求
|
||||
const handleNormalRequest = async (headers: any, body: any) => {
|
||||
const response = await fetch(testRequest.url, {
|
||||
method: 'POST',
|
||||
headers: headers,
|
||||
body: JSON.stringify(body)
|
||||
})
|
||||
|
||||
// 获取响应头
|
||||
const responseHeaders: any = {}
|
||||
response.headers.forEach((value, key) => {
|
||||
responseHeaders[key] = value
|
||||
})
|
||||
|
||||
// 获取响应体
|
||||
const responseBody = await response.text()
|
||||
|
||||
// 更新响应数据
|
||||
testResponse.status = response.status
|
||||
testResponse.headers = JSON.stringify(responseHeaders, null, 2)
|
||||
testResponse.body = responseBody
|
||||
|
||||
// 尝试格式化响应体
|
||||
try {
|
||||
const jsonBody = JSON.parse(responseBody)
|
||||
testResponse.body = JSON.stringify(jsonBody, null, 2)
|
||||
} catch (e) {
|
||||
// 如果不是JSON格式,保持原样
|
||||
}
|
||||
|
||||
if (response.ok) {
|
||||
ElMessage.success('测试请求发送成功')
|
||||
} else {
|
||||
ElMessage.warning(`请求返回状态码: ${response.status}`)
|
||||
}
|
||||
}
|
||||
|
||||
// 处理流式请求
|
||||
const handleStreamRequest = async (headers: any, body: any) => {
|
||||
const response = await fetch(testRequest.url, {
|
||||
method: 'POST',
|
||||
headers: headers,
|
||||
body: JSON.stringify(body)
|
||||
})
|
||||
|
||||
// 获取响应头
|
||||
const responseHeaders: any = {}
|
||||
response.headers.forEach((value, key) => {
|
||||
responseHeaders[key] = value
|
||||
})
|
||||
|
||||
testResponse.status = response.status
|
||||
testResponse.headers = JSON.stringify(responseHeaders, null, 2)
|
||||
|
||||
if (!response.ok) {
|
||||
const errorBody = await response.text()
|
||||
testResponse.body = errorBody
|
||||
ElMessage.warning(`请求返回状态码: ${response.status}`)
|
||||
return
|
||||
}
|
||||
|
||||
if (!response.body) {
|
||||
testResponse.body = 'Error: 响应体为空'
|
||||
ElMessage.error('响应体为空')
|
||||
return
|
||||
}
|
||||
|
||||
// 处理流式响应
|
||||
const reader = response.body.getReader()
|
||||
const decoder = new TextDecoder()
|
||||
let streamContent = ''
|
||||
let chunks: string[] = []
|
||||
|
||||
// 清空响应体,准备接收流式数据
|
||||
testResponse.body = '正在接收流式数据...\n\n'
|
||||
|
||||
try {
|
||||
while (true) {
|
||||
const { done, value } = await reader.read()
|
||||
|
||||
if (done) {
|
||||
break
|
||||
}
|
||||
|
||||
// 解码数据块
|
||||
const chunk = decoder.decode(value, { stream: true })
|
||||
streamContent += chunk
|
||||
chunks.push(chunk)
|
||||
|
||||
// 实时更新响应体显示
|
||||
testResponse.body = `=== 流式响应数据 ===\n\n` +
|
||||
`接收到 ${chunks.length} 个数据块,总长度: ${streamContent.length} 字符\n\n` +
|
||||
`=== 原始数据流 ===\n${streamContent}\n\n` +
|
||||
`=== 解析后的数据 ===\n${parseStreamData(streamContent)}`
|
||||
}
|
||||
|
||||
ElMessage.success(`流式请求完成,共接收 ${chunks.length} 个数据块`)
|
||||
|
||||
} catch (streamError: any) {
|
||||
console.error('流式数据读取失败:', streamError)
|
||||
testResponse.body += `\n\n=== 流式读取错误 ===\n${streamError.message || streamError}`
|
||||
ElMessage.error('流式数据读取失败: ' + (streamError.message || streamError))
|
||||
} finally {
|
||||
reader.releaseLock()
|
||||
}
|
||||
}
|
||||
|
||||
// 解析流式数据
|
||||
const parseStreamData = (streamContent: string): string => {
|
||||
try {
|
||||
const lines = streamContent.split('\n')
|
||||
const parsedData: any[] = []
|
||||
let currentEvent = ''
|
||||
let currentData = ''
|
||||
|
||||
for (const line of lines) {
|
||||
if (line.startsWith('event:')) {
|
||||
currentEvent = line.substring(6).trim()
|
||||
} else if (line.startsWith('data:')) {
|
||||
currentData = line.substring(5).trim()
|
||||
|
||||
if (currentData === '[DONE]') {
|
||||
parsedData.push({
|
||||
event: currentEvent || 'done',
|
||||
data: '[DONE]',
|
||||
timestamp: new Date().toISOString()
|
||||
})
|
||||
} else if (currentData) {
|
||||
try {
|
||||
const jsonData = JSON.parse(currentData)
|
||||
parsedData.push({
|
||||
event: currentEvent || 'data',
|
||||
data: jsonData,
|
||||
timestamp: new Date().toISOString()
|
||||
})
|
||||
} catch (e) {
|
||||
parsedData.push({
|
||||
event: currentEvent || 'raw',
|
||||
data: currentData,
|
||||
timestamp: new Date().toISOString()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
currentEvent = ''
|
||||
currentData = ''
|
||||
} else if (line.trim() === '') {
|
||||
// 空行,重置状态
|
||||
currentEvent = ''
|
||||
currentData = ''
|
||||
}
|
||||
}
|
||||
|
||||
return JSON.stringify(parsedData, null, 2)
|
||||
} catch (e) {
|
||||
return `解析失败: ${e}\n\n原始内容:\n${streamContent}`
|
||||
}
|
||||
}
|
||||
|
||||
// 格式化请求
|
||||
const handleFormatRequest = () => {
|
||||
try {
|
||||
@@ -1260,6 +1437,8 @@ const handleTestDialogClose = () => {
|
||||
testResponse.status = null
|
||||
testResponse.headers = ''
|
||||
testResponse.body = ''
|
||||
testOptions.useStream = false
|
||||
testOptions.testMessage = '你好,这是一个测试消息,请回复确认接口正常工作。'
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
@@ -1321,6 +1500,17 @@ onMounted(() => {
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
|
||||
.test-options {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
|
||||
.info-icon {
|
||||
color: #909399;
|
||||
cursor: help;
|
||||
}
|
||||
}
|
||||
|
||||
.el-textarea {
|
||||
:deep(.el-textarea__inner) {
|
||||
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
||||
|
||||
Reference in New Issue
Block a user