AI弹窗
This commit is contained in:
54
src/utils/ai_request.js
Normal file
54
src/utils/ai_request.js
Normal file
@@ -0,0 +1,54 @@
|
||||
import axios from 'axios'
|
||||
import { getToken } from './auth'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { showToast } from '@/utils/toast' // 请替换为你的Toast组件
|
||||
|
||||
// 创建axios实例
|
||||
const service = axios.create({
|
||||
baseURL: process.env.VUE_APP_API_BASE_URL || 'http://localhost:8088/aitutor/aichat',
|
||||
timeout: 15000,
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
})
|
||||
|
||||
// 请求拦截器
|
||||
service.interceptors.request.use(
|
||||
config => {
|
||||
// 从本地存储获取token
|
||||
const token = getToken()
|
||||
if (token) {
|
||||
config.headers.Authorization = `Bearer ${token}`
|
||||
}
|
||||
return config
|
||||
},
|
||||
error => {
|
||||
return Promise.reject(error)
|
||||
}
|
||||
)
|
||||
|
||||
// 响应拦截器
|
||||
service.interceptors.response.use(
|
||||
response => {
|
||||
// 对响应数据做处理
|
||||
return response.data
|
||||
},
|
||||
error => {
|
||||
const router = useRouter()
|
||||
|
||||
// 处理401未授权
|
||||
if (error.response?.status === 401) {
|
||||
showToast('登录已过期,请重新登录', 'error')
|
||||
router.push('/login')
|
||||
}
|
||||
|
||||
// 处理其他错误状态码
|
||||
if (error.response?.status === 500) {
|
||||
showToast('服务器错误,请稍后再试', 'error')
|
||||
}
|
||||
|
||||
return Promise.reject(error)
|
||||
}
|
||||
)
|
||||
|
||||
export default service
|
110
src/utils/ai_stream.js
Normal file
110
src/utils/ai_stream.js
Normal 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()
|
||||
}
|
||||
}
|
48
src/utils/toast.js
Normal file
48
src/utils/toast.js
Normal file
@@ -0,0 +1,48 @@
|
||||
import { Message } from 'element-ui'
|
||||
|
||||
/**
|
||||
* 显示Toast消息
|
||||
* @param {string} message - 消息内容
|
||||
* @param {string} type - 消息类型: 'success', 'warning', 'info', 'error'
|
||||
* @param {number} duration - 显示时长,默认3000ms
|
||||
*/
|
||||
export function showToast(message, type = 'info', duration = 3000) {
|
||||
Message({
|
||||
message,
|
||||
type,
|
||||
duration,
|
||||
showClose: true
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示成功消息
|
||||
* @param {string} message - 消息内容
|
||||
*/
|
||||
export function showSuccess(message) {
|
||||
showToast(message, 'success')
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示错误消息
|
||||
* @param {string} message - 消息内容
|
||||
*/
|
||||
export function showError(message) {
|
||||
showToast(message, 'error')
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示警告消息
|
||||
* @param {string} message - 消息内容
|
||||
*/
|
||||
export function showWarning(message) {
|
||||
showToast(message, 'warning')
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示信息消息
|
||||
* @param {string} message - 消息内容
|
||||
*/
|
||||
export function showInfo(message) {
|
||||
showToast(message, 'info')
|
||||
}
|
Reference in New Issue
Block a user