169 lines
4.7 KiB
JavaScript
169 lines
4.7 KiB
JavaScript
import axios from 'axios'
|
|
|
|
// 创建 axios 实例
|
|
const api = axios.create({
|
|
baseURL: '/api',
|
|
timeout: 30000,
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
}
|
|
})
|
|
|
|
// 请求拦截器:添加认证 token
|
|
api.interceptors.request.use(
|
|
(config) => {
|
|
const token = localStorage.getItem('access_token')
|
|
if (token) {
|
|
config.headers.Authorization = `Bearer ${token}`
|
|
}
|
|
return config
|
|
},
|
|
(error) => {
|
|
return Promise.reject(error)
|
|
}
|
|
)
|
|
|
|
// 响应拦截器:处理通用错误
|
|
api.interceptors.response.use(
|
|
(response) => response.data,
|
|
(error) => {
|
|
if (error.response?.status === 401) {
|
|
localStorage.removeItem('access_token')
|
|
localStorage.removeItem('user')
|
|
window.location.href = '/auth'
|
|
}
|
|
return Promise.reject(error.response?.data || error.message)
|
|
}
|
|
)
|
|
|
|
/**
|
|
* SSE 流式请求处理器
|
|
* @param {string} url - API URL (不含 baseURL 前缀)
|
|
* @param {object} body - 请求体
|
|
* @param {object} callbacks - 事件回调: { onProcessStep, onDone, onError }
|
|
* @returns {{ abort: () => void }}
|
|
*/
|
|
export function createSSEStream(url, body, { onProcessStep, onDone, onError }) {
|
|
const token = localStorage.getItem('access_token')
|
|
const controller = new AbortController()
|
|
|
|
const promise = (async () => {
|
|
try {
|
|
const res = await fetch(`/api${url}`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Authorization': `Bearer ${token}`
|
|
},
|
|
body: JSON.stringify(body),
|
|
signal: controller.signal
|
|
})
|
|
|
|
if (!res.ok) {
|
|
const err = await res.json().catch(() => ({}))
|
|
throw new Error(err.message || `HTTP ${res.status}`)
|
|
}
|
|
|
|
const reader = res.body.getReader()
|
|
const decoder = new TextDecoder()
|
|
let buffer = ''
|
|
let completed = false
|
|
|
|
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() || ''
|
|
|
|
let currentEvent = ''
|
|
for (const line of lines) {
|
|
if (line.startsWith('event: ')) {
|
|
currentEvent = line.slice(7).trim()
|
|
} else if (line.startsWith('data: ')) {
|
|
const data = JSON.parse(line.slice(6))
|
|
if (currentEvent === 'process_step' && onProcessStep) {
|
|
onProcessStep(data.step)
|
|
} else if (currentEvent === 'done' && onDone) {
|
|
completed = true
|
|
onDone(data)
|
|
} else if (currentEvent === 'error' && onError) {
|
|
onError(data.content)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!completed && onError) {
|
|
onError('stream ended unexpectedly')
|
|
}
|
|
} catch (e) {
|
|
if (e.name !== 'AbortError' && onError) {
|
|
onError(e.message)
|
|
}
|
|
}
|
|
})()
|
|
|
|
promise.abort = () => controller.abort()
|
|
return promise
|
|
}
|
|
|
|
// ============ 认证接口 ============
|
|
|
|
export const authAPI = {
|
|
login: (data) => api.post('/auth/login', data),
|
|
register: (data) => api.post('/auth/register', data),
|
|
logout: () => api.post('/auth/logout'),
|
|
getMe: () => api.get('/auth/me')
|
|
}
|
|
|
|
// ============ 会话接口 ============
|
|
|
|
export const conversationsAPI = {
|
|
list: (params) => api.get('/conversations/', { params }),
|
|
create: (data) => api.post('/conversations/', data),
|
|
get: (id) => api.get(`/conversations/${id}`),
|
|
update: (id, data) => api.put(`/conversations/${id}`, data),
|
|
delete: (id) => api.delete(`/conversations/${id}`)
|
|
}
|
|
|
|
// ============ 消息接口 ============
|
|
|
|
export const messagesAPI = {
|
|
list: (conversationId, params) => api.get('/messages/', { params: { conversation_id: conversationId, ...params } }),
|
|
send: (data) => api.post('/messages/', data),
|
|
|
|
// 发送消息(流式)
|
|
sendStream: (data, callbacks) => {
|
|
return createSSEStream('/messages/stream', {
|
|
conversation_id: data.conversation_id,
|
|
content: data.content,
|
|
tools_enabled: callbacks.toolsEnabled !== false
|
|
}, callbacks)
|
|
},
|
|
|
|
delete: (id) => api.delete(`/messages/${id}`)
|
|
}
|
|
|
|
// ============ 工具接口 ============
|
|
|
|
export const toolsAPI = {
|
|
list: (params) => api.get('/tools/', { params }),
|
|
get: (name) => api.get(`/tools/${name}`),
|
|
execute: (name, data) => api.post(`/tools/${name}/execute`, data)
|
|
}
|
|
|
|
// ============ LLM Provider 接口 ============
|
|
|
|
export const providersAPI = {
|
|
list: () => api.get('/providers/'),
|
|
create: (data) => api.post('/providers/', data),
|
|
get: (id) => api.get(`/providers/${id}`),
|
|
update: (id, data) => api.put(`/providers/${id}`, data),
|
|
delete: (id) => api.delete(`/providers/${id}`),
|
|
test: (id) => api.post(`/providers/${id}/test`)
|
|
}
|
|
|
|
export default api
|