mirror of
https://gitee.com/mateos/mateclaw.git
synced 2026-09-13 11:13:43 +08:00
373 lines
16 KiB
TypeScript
373 lines
16 KiB
TypeScript
import axios from 'axios'
|
||
import { handleAuthFailure, updateTokenFromHeader } from '@/utils/auth'
|
||
|
||
// Axios 实例
|
||
export const http = axios.create({
|
||
baseURL: '/api/v1',
|
||
timeout: 30000,
|
||
})
|
||
|
||
// 请求拦截器:注入 Token
|
||
http.interceptors.request.use((config) => {
|
||
const token = localStorage.getItem('token')
|
||
if (token) {
|
||
config.headers.Authorization = `Bearer ${token}`
|
||
}
|
||
return config
|
||
})
|
||
|
||
// 响应拦截器:适配后端 R<T> { code, msg, data } 格式
|
||
http.interceptors.response.use(
|
||
(res) => {
|
||
// 滑动窗口续期:后端在 Token 接近过期时通过响应头下发新 Token
|
||
updateTokenFromHeader(res.headers)
|
||
|
||
const data = res.data
|
||
// 后端统一响应格式 R<T>: { code: number, msg: string, data: T }
|
||
if (data && typeof data === 'object' && 'code' in data) {
|
||
if (data.code === 200) return data
|
||
if (data.code === 401 || data.code === 403) {
|
||
handleAuthFailure()
|
||
return Promise.reject(new Error(data.msg || 'Unauthorized'))
|
||
}
|
||
return Promise.reject(new Error(data.msg || 'Request failed'))
|
||
}
|
||
return data
|
||
},
|
||
(err) => {
|
||
if (err.response?.status === 401 || err.response?.status === 403) {
|
||
handleAuthFailure()
|
||
}
|
||
return Promise.reject(err.response?.data?.msg || err.message)
|
||
}
|
||
)
|
||
|
||
// ==================== 受保护文件访问 ====================
|
||
|
||
/**
|
||
* 使用 JWT 认证 fetch 受保护文件,返回 Blob。
|
||
* 用于 <img> 和 <a download> 无法自动携带 Authorization header 的场景。
|
||
* 使用原生 fetch(不走 axios),避免 baseURL 拼接和 R<T> 拦截器干扰。
|
||
*/
|
||
export async function fetchAuthenticatedBlob(fileUrl: string): Promise<Blob> {
|
||
const token = localStorage.getItem('token')
|
||
const headers: Record<string, string> = {}
|
||
if (token) headers.Authorization = `Bearer ${token}`
|
||
const response = await fetch(fileUrl, { headers })
|
||
if (!response.ok) throw new Error(`Fetch failed: ${response.status}`)
|
||
return response.blob()
|
||
}
|
||
|
||
// ==================== Auth ====================
|
||
export const authApi = {
|
||
login: (data: { username: string; password: string }) =>
|
||
http.post('/auth/login', data),
|
||
listUsers: () => http.get('/auth/users'),
|
||
createUser: (data: any) => http.post('/auth/users', data),
|
||
}
|
||
|
||
// ==================== Agent ====================
|
||
export const agentApi = {
|
||
list: () => http.get('/agents'),
|
||
get: (id: string | number) => http.get(`/agents/${id}`),
|
||
create: (data: any) => http.post('/agents', data),
|
||
update: (id: string | number, data: any) => http.put(`/agents/${id}`, data),
|
||
delete: (id: string | number) => http.delete(`/agents/${id}`),
|
||
chat: (id: string | number, data: any) => http.post(`/agents/${id}/chat`, data),
|
||
execute: (id: string | number, data: any) => http.post(`/agents/${id}/execute`, data),
|
||
getState: (id: string | number) => http.get(`/agents/${id}/state`),
|
||
}
|
||
|
||
// ==================== Chat ====================
|
||
export const chatApi = {
|
||
uploadFile: async (conversationId: string, file: File) => {
|
||
const formData = new FormData()
|
||
formData.append('file', file)
|
||
formData.append('conversationId', conversationId)
|
||
return http.post('/chat/upload', formData, {
|
||
headers: {
|
||
'Content-Type': 'multipart/form-data',
|
||
},
|
||
})
|
||
},
|
||
stream: (data: any, signal?: AbortSignal) => {
|
||
const headers: Record<string, string> = {
|
||
Accept: 'text/event-stream',
|
||
'Content-Type': 'application/json',
|
||
'Cache-Control': 'no-cache',
|
||
}
|
||
const token = localStorage.getItem('token')
|
||
if (token) {
|
||
headers.Authorization = `Bearer ${token}`
|
||
}
|
||
return fetch('/api/v1/chat/stream', {
|
||
method: 'POST',
|
||
headers,
|
||
body: JSON.stringify(data),
|
||
signal,
|
||
})
|
||
},
|
||
stop: (conversationId: string) =>
|
||
http.post<{ stopped: boolean }>(`/chat/${conversationId}/stop`),
|
||
approve: (conversationId: string, data: { pendingId: string; decision: string }) =>
|
||
http.post(`/chat/${conversationId}/approve`, data),
|
||
getPendingApprovals: (conversationId: string) =>
|
||
http.get(`/chat/${conversationId}/pending-approvals`),
|
||
}
|
||
|
||
// ==================== Conversation ====================
|
||
export const conversationApi = {
|
||
list: () => http.get('/conversations'),
|
||
listMessages: (conversationId: string) =>
|
||
http.get(`/conversations/${conversationId}/messages`),
|
||
getStatus: (conversationId: string) =>
|
||
http.get(`/conversations/${conversationId}/status`),
|
||
delete: (conversationId: string) =>
|
||
http.delete(`/conversations/${conversationId}`),
|
||
clearMessages: (conversationId: string) =>
|
||
http.delete(`/conversations/${conversationId}/messages`),
|
||
}
|
||
|
||
// ==================== Skill ====================
|
||
export const skillApi = {
|
||
list: () => http.get('/skills'),
|
||
listEnabled: () => http.get('/skills/enabled'),
|
||
get: (id: string | number) => http.get(`/skills/${id}`),
|
||
create: (data: any) => http.post('/skills', data),
|
||
update: (id: string | number, data: any) => http.put(`/skills/${id}`, data),
|
||
delete: (id: string | number) => http.delete(`/skills/${id}`),
|
||
toggle: (id: string | number, enabled: boolean) =>
|
||
http.put(`/skills/${id}/toggle?enabled=${enabled}`),
|
||
getActiveSkills: () => http.get('/skills/runtime/active'),
|
||
getRuntimeStatus: () => http.get('/skills/runtime/status'),
|
||
refreshRuntime: () => http.post('/skills/runtime/refresh'),
|
||
exportWorkspace: (id: string | number) => http.post(`/skills/${id}/export-workspace`),
|
||
getWorkspaceInfo: (id: string | number) => http.get(`/skills/${id}/workspace`),
|
||
}
|
||
|
||
// ==================== Skill Install ====================
|
||
export const skillInstallApi = {
|
||
searchHub: (q: string, limit = 20) =>
|
||
http.get('/skills/install/hub/search', { params: { q, limit } }),
|
||
startInstall: (data: { bundleUrl: string; version?: string; enable?: boolean; targetName?: string; overwrite?: boolean }) =>
|
||
http.post('/skills/install/start', data),
|
||
getStatus: (taskId: string) =>
|
||
http.get(`/skills/install/status/${taskId}`),
|
||
cancelInstall: (taskId: string) =>
|
||
http.post(`/skills/install/cancel/${taskId}`),
|
||
uninstall: (skillName: string) =>
|
||
http.delete(`/skills/install/${skillName}`),
|
||
}
|
||
|
||
// ==================== Datasource ====================
|
||
export const datasourceApi = {
|
||
list: () => http.get('/datasources'),
|
||
get: (id: string | number) => http.get(`/datasources/${id}`),
|
||
create: (data: any) => http.post('/datasources', data),
|
||
update: (id: string | number, data: any) => http.put(`/datasources/${id}`, data),
|
||
delete: (id: string | number) => http.delete(`/datasources/${id}`),
|
||
toggle: (id: string | number, enabled: boolean) =>
|
||
http.put(`/datasources/${id}/toggle?enabled=${enabled}`),
|
||
test: (id: string | number) => http.post(`/datasources/${id}/test`),
|
||
}
|
||
|
||
// ==================== Tool ====================
|
||
export const toolApi = {
|
||
list: () => http.get('/tools'),
|
||
listEnabled: () => http.get('/tools/enabled'),
|
||
get: (id: string | number) => http.get(`/tools/${id}`),
|
||
create: (data: any) => http.post('/tools', data),
|
||
update: (id: string | number, data: any) => http.put(`/tools/${id}`, data),
|
||
delete: (id: string | number) => http.delete(`/tools/${id}`),
|
||
toggle: (id: string | number, enabled: boolean) =>
|
||
http.put(`/tools/${id}/toggle?enabled=${enabled}`),
|
||
}
|
||
|
||
// ==================== Channel ====================
|
||
export const channelApi = {
|
||
list: () => http.get('/channels'),
|
||
get: (id: string | number) => http.get(`/channels/${id}`),
|
||
create: (data: any) => http.post('/channels', data),
|
||
update: (id: string | number, data: any) => http.put(`/channels/${id}`, data),
|
||
delete: (id: string | number) => http.delete(`/channels/${id}`),
|
||
toggle: (id: string | number, enabled: boolean) =>
|
||
http.put(`/channels/${id}/toggle?enabled=${enabled}`),
|
||
status: () => http.get('/channels/status'),
|
||
// 微信 iLink Bot QR 码登录
|
||
weixinQrcode: () => http.get('/channels/webhook/weixin/qrcode'),
|
||
weixinQrcodeStatus: (qrcode: string) =>
|
||
http.get(`/channels/webhook/weixin/qrcode/status?qrcode=${encodeURIComponent(qrcode)}`),
|
||
}
|
||
|
||
// ==================== MCP Server ====================
|
||
export const mcpApi = {
|
||
list: () => http.get('/mcp/servers'),
|
||
get: (id: string | number) => http.get(`/mcp/servers/${id}`),
|
||
create: (data: any) => http.post('/mcp/servers', data),
|
||
update: (id: string | number, data: any) => http.put(`/mcp/servers/${id}`, data),
|
||
delete: (id: string | number) => http.delete(`/mcp/servers/${id}`),
|
||
toggle: (id: string | number, enabled: boolean) =>
|
||
http.put(`/mcp/servers/${id}/toggle?enabled=${enabled}`),
|
||
test: (id: string | number) => http.post(`/mcp/servers/${id}/test`),
|
||
refresh: () => http.post('/mcp/servers/refresh'),
|
||
}
|
||
|
||
// ==================== Plan ====================
|
||
export const planApi = {
|
||
listByAgent: (agentId: string) => http.get(`/plans?agentId=${agentId}`),
|
||
get: (id: string | number) => http.get(`/plans/${id}`),
|
||
}
|
||
|
||
// ==================== Model ====================
|
||
export const modelApi = {
|
||
listProviders: () => http.get('/models'),
|
||
listEnabled: () => http.get('/models/enabled'),
|
||
get: (id: string | number) => http.get(`/models/${id}`),
|
||
getDefault: () => http.get('/models/default'),
|
||
create: (data: any) => http.post('/models', data),
|
||
update: (id: string | number, data: any) => http.put(`/models/${id}`, data),
|
||
delete: (id: string | number) => http.delete(`/models/${id}`),
|
||
setDefault: (id: string | number) => http.post(`/models/${id}/default`),
|
||
updateProviderConfig: (providerId: string, data: any) =>
|
||
http.put(`/models/${providerId}/config`, data),
|
||
createCustomProvider: (data: any) => http.post('/models/custom-providers', data),
|
||
deleteCustomProvider: (providerId: string) =>
|
||
http.delete(`/models/custom-providers/${providerId}`),
|
||
addProviderModel: (providerId: string, data: any) =>
|
||
http.post(`/models/${providerId}/models`, data),
|
||
removeProviderModel: (providerId: string, modelId: string) =>
|
||
http.delete(`/models/${providerId}/models/${encodeURIComponent(modelId)}`),
|
||
getActive: () => http.get('/models/active'),
|
||
setActive: (data: { providerId: string; model: string }) =>
|
||
http.put('/models/active', data),
|
||
// 模型发现与连接测试
|
||
discoverModels: (providerId: string) =>
|
||
http.post(`/models/${providerId}/discover`),
|
||
applyDiscoveredModels: (providerId: string, modelIds: string[]) =>
|
||
http.post(`/models/${providerId}/discover/apply`, { modelIds }),
|
||
testConnection: (providerId: string) =>
|
||
http.post(`/models/${providerId}/test-connection`),
|
||
testModel: (providerId: string, modelId: string) =>
|
||
http.post(`/models/${providerId}/models/${encodeURIComponent(modelId)}/test`),
|
||
}
|
||
|
||
// ==================== OAuth ====================
|
||
export const oauthApi = {
|
||
authorize: () => http.get('/oauth/openai/authorize'),
|
||
status: () => http.get('/oauth/openai/status'),
|
||
refresh: () => http.post('/oauth/openai/refresh'),
|
||
revoke: () => http.delete('/oauth/openai/revoke'),
|
||
}
|
||
|
||
// ==================== Settings ====================
|
||
export const settingsApi = {
|
||
get: () => http.get('/settings'),
|
||
update: (data: any) => http.put('/settings', data),
|
||
getLanguage: () => http.get('/settings/language'),
|
||
updateLanguage: (language: string) => http.put('/settings/language', { language }),
|
||
}
|
||
|
||
// ==================== Workspace ====================
|
||
const encodeFilePath = (filename: string) =>
|
||
filename.split('/').map(encodeURIComponent).join('/')
|
||
|
||
export const workspaceApi = {
|
||
listFiles: (agentId: string | number) =>
|
||
http.get(`/agents/${agentId}/workspace/files`),
|
||
getFile: (agentId: string | number, filename: string) =>
|
||
http.get(`/agents/${agentId}/workspace/files/${encodeFilePath(filename)}`),
|
||
saveFile: (agentId: string | number, filename: string, content: string) =>
|
||
http.put(`/agents/${agentId}/workspace/files/${encodeFilePath(filename)}`, { content }),
|
||
deleteFile: (agentId: string | number, filename: string) =>
|
||
http.delete(`/agents/${agentId}/workspace/files/${encodeFilePath(filename)}`),
|
||
getPromptFiles: (agentId: string | number) =>
|
||
http.get(`/agents/${agentId}/workspace/prompt-files`),
|
||
setPromptFiles: (agentId: string | number, files: string[]) =>
|
||
http.put(`/agents/${agentId}/workspace/prompt-files`, { files }),
|
||
}
|
||
|
||
// ==================== Security ====================
|
||
export const securityApi = {
|
||
getGuardConfig: () => http.get('/security/guard/config'),
|
||
updateGuardConfig: (data: any) => http.put('/security/guard/config', data),
|
||
getFileGuardConfig: () => http.get('/security/guard/config/file-guard'),
|
||
updateFileGuardConfig: (data: any) => http.put('/security/guard/config/file-guard', data),
|
||
listRules: (params?: any) => http.get('/security/guard/rules', { params }),
|
||
listBuiltinRules: (params?: any) => http.get('/security/guard/rules/builtin', { params }),
|
||
createRule: (data: any) => http.post('/security/guard/rules', data),
|
||
updateRule: (ruleId: string, data: any) => http.put(`/security/guard/rules/${ruleId}`, data),
|
||
toggleRule: (ruleId: string, enabled: boolean) =>
|
||
http.put(`/security/guard/rules/${ruleId}/toggle?enabled=${enabled}`),
|
||
deleteRule: (ruleId: string) => http.delete(`/security/guard/rules/${ruleId}`),
|
||
listAuditLogs: (params?: any) => http.get('/security/audit/logs', { params }),
|
||
getAuditStats: () => http.get('/security/audit/stats'),
|
||
listApprovals: (params?: any) => http.get('/security/approvals', { params }),
|
||
}
|
||
|
||
// ==================== Token Usage ====================
|
||
export const tokenUsageApi = {
|
||
getSummary: (params?: { startDate?: string; endDate?: string; modelName?: string; providerId?: string }) =>
|
||
http.get('/token-usage', { params }),
|
||
}
|
||
|
||
// ==================== CronJob ====================
|
||
export const cronJobApi = {
|
||
list: () => http.get('/cron-jobs'),
|
||
get: (id: string | number) => http.get(`/cron-jobs/${id}`),
|
||
create: (data: any) => http.post('/cron-jobs', data),
|
||
update: (id: string | number, data: any) => http.put(`/cron-jobs/${id}`, data),
|
||
delete: (id: string | number) => http.delete(`/cron-jobs/${id}`),
|
||
toggle: (id: string | number, enabled: boolean) =>
|
||
http.put(`/cron-jobs/${id}/toggle`, null, { params: { enabled } }),
|
||
runNow: (id: string | number) => http.post(`/cron-jobs/${id}/run`),
|
||
}
|
||
|
||
// ==================== Wiki Knowledge Base ====================
|
||
export const wikiApi = {
|
||
// Knowledge Base
|
||
listKBs: () => http.get('/wiki/knowledge-bases'),
|
||
getKB: (id: number) => http.get(`/wiki/knowledge-bases/${id}`),
|
||
listKBsByAgent: (agentId: number) => http.get(`/wiki/knowledge-bases/agent/${agentId}`),
|
||
createKB: (data: { name: string; description?: string; agentId?: number }) =>
|
||
http.post('/wiki/knowledge-bases', data),
|
||
updateKB: (id: number, data: { name?: string; description?: string; agentId?: number }) =>
|
||
http.put(`/wiki/knowledge-bases/${id}`, data),
|
||
deleteKB: (id: number) => http.delete(`/wiki/knowledge-bases/${id}`),
|
||
getConfig: (id: number) => http.get(`/wiki/knowledge-bases/${id}/config`),
|
||
updateConfig: (id: number, content: string) =>
|
||
http.put(`/wiki/knowledge-bases/${id}/config`, { content }),
|
||
|
||
// Directory Scan
|
||
setSourceDirectory: (id: number, path: string) =>
|
||
http.put(`/wiki/knowledge-bases/${id}/source-directory`, { path }),
|
||
scanDirectory: (id: number) => http.post(`/wiki/knowledge-bases/${id}/scan`),
|
||
|
||
// Raw Materials
|
||
listRaw: (kbId: number) => http.get(`/wiki/knowledge-bases/${kbId}/raw`),
|
||
addRawText: (kbId: number, data: { title: string; content: string }) =>
|
||
http.post(`/wiki/knowledge-bases/${kbId}/raw/text`, data),
|
||
uploadRaw: (kbId: number, formData: FormData) =>
|
||
http.post(`/wiki/knowledge-bases/${kbId}/raw/upload`, formData, {
|
||
headers: { 'Content-Type': 'multipart/form-data' },
|
||
}),
|
||
deleteRaw: (kbId: number, rawId: number) =>
|
||
http.delete(`/wiki/knowledge-bases/${kbId}/raw/${rawId}`),
|
||
reprocessRaw: (kbId: number, rawId: number) =>
|
||
http.post(`/wiki/knowledge-bases/${kbId}/raw/${rawId}/reprocess`),
|
||
|
||
// Wiki Pages
|
||
listPages: (kbId: number) => http.get(`/wiki/knowledge-bases/${kbId}/pages`),
|
||
getPage: (kbId: number, slug: string) =>
|
||
http.get(`/wiki/knowledge-bases/${kbId}/pages/${encodeURIComponent(slug)}`),
|
||
updatePage: (kbId: number, slug: string, content: string) =>
|
||
http.put(`/wiki/knowledge-bases/${kbId}/pages/${encodeURIComponent(slug)}`, { content }),
|
||
deletePage: (kbId: number, slug: string) =>
|
||
http.delete(`/wiki/knowledge-bases/${kbId}/pages/${encodeURIComponent(slug)}`),
|
||
getBacklinks: (kbId: number, slug: string) =>
|
||
http.get(`/wiki/knowledge-bases/${kbId}/pages/${encodeURIComponent(slug)}/backlinks`),
|
||
|
||
// Processing
|
||
processKB: (kbId: number) => http.post(`/wiki/knowledge-bases/${kbId}/process`),
|
||
getProcessingStatus: (kbId: number) => http.get(`/wiki/knowledge-bases/${kbId}/processing-status`),
|
||
}
|