Luxx/dashboard/src/utils/api.js

229 lines
6.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 (value) {
buffer += decoder.decode(value, { stream: true })
}
// 流结束时,先处理 buffer 中的剩余数据,再 break
if (done) {
// 处理 buffer 中剩余的数据
const lines = buffer.split('\n')
buffer = ''
let currentEvent = ''
for (const line of lines) {
if (line.startsWith('event: ')) {
currentEvent = line.slice(7).trim()
} else if (line.startsWith('data: ')) {
try {
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)
}
} catch (e) {
console.error('SSE parse error:', e, 'line:', line)
}
}
}
// 如果没有收到 done 事件,触发错误
if (!completed && onError) {
onError('stream ended without done event')
}
break
}
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: ')) {
try {
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)
}
} catch (e) {
// 忽略解析错误
}
}
}
}
// 流结束但没有收到 done 事件,才报错
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'),
listUsers: () => api.get('/auth/users'),
updateUserPermission: (userId, data) => api.put(`/auth/users/${userId}`, data)
}
// ============ 会话接口 ============
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,
thinking_enabled: data.thinking_enabled || false,
enabled_tools: data.enabled_tools || []
}, 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`)
}
// ============ Agent 接口 ============
export const agentsAPI = {
// 创建 Agent 任务
create: (data) => api.post('/agents/request', data),
// 获取任务状态
getTask: (taskId) => api.get(`/agents/task/${taskId}`),
// 取消任务
cancelTask: (taskId) => api.post(`/agents/task/${taskId}/cancel`),
// 列出用户的任务
listTasks: (params) => api.get('/agents/tasks', { params }),
// 删除任务
deleteTask: (taskId) => api.delete(`/agents/task/${taskId}`)
}
export default api