This commit is contained in:
14651
2025-08-14 15:43:24 +08:00
parent 253825ab0c
commit e66713b51f
19 changed files with 1685 additions and 26 deletions

110
src/utils/ai_stream.js Normal file
View File

@@ -0,0 +1,110 @@
import { getToken } from '@/utils/auth'
// 使用环境变量配置基础URL
const BASE_URL = process.env.VUE_APP_API_BASE_URL || 'http://localhost:8088'
/**
* 创建聊天流式连接
* @param {Object} params 请求参数
* @param {string} params.prompt 用户输入
* @param {string} params.userId 用户ID
* @param {string} params.userName 用户名
* @param {string} [params.conversationId] 会话ID
* @returns {Object} 包含stream和cancel方法的对象
*/
export function createChatStream(params) {
const requestId = `req-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`
const url = `${BASE_URL}/aitutor/aichat/stream`
const token = getToken()
if (!token) {
throw new Error('请先登录')
}
const controller = new AbortController()
const fetchPromise = fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`,
'X-Request-ID': requestId
},
body: JSON.stringify({
query: params.prompt,
user_id: params.userId,
user_name: params.userName,
user_token: params.user_token || '123',
user_role: 'student',
conversation_id: params.conversationId || null,
}),
signal: controller.signal
})
.then(response => {
if (!response.ok) {
throw new Error(`HTTP ${response.status}`)
}
if (!response.body) {
throw new Error('Response body is null')
}
return {
reader: response.body.getReader(),
decoder: new TextDecoder('utf-8')
}
})
return {
stream: fetchPromise,
cancel: (reason) => {
if (!controller.signal.aborted) {
controller.abort(reason)
}
}
}
}
/**
* 处理流式响应
* @param {ReadableStreamDefaultReader} reader 读取器
* @param {TextDecoder} decoder 文本解码器
* @param {Function} onMessage 消息回调
* @param {Function} onError 错误回调
* @param {Function} onComplete 完成回调
*/
export async function processStream(reader, decoder, { onMessage, onError, onComplete }) {
let buffer = ''
try {
while (true) {
const { done, value } = await reader.read()
if (done) break
buffer += decoder.decode(value, { stream: true })
const lines = buffer.split('\n')
buffer = lines.pop() || ''
for (const line of lines) {
if (!line.trim()) continue
try {
const data = JSON.parse(line)
if (typeof onMessage === 'function') {
onMessage(data)
}
} catch (e) {
console.warn('解析消息失败:', line, e)
}
}
}
if (typeof onComplete === 'function') {
onComplete()
}
} catch (error) {
if (error.name !== 'AbortError' && typeof onError === 'function') {
onError(error)
}
} finally {
reader.releaseLock()
}
}