import axios from 'axios'; import { message } from 'antd'; import { ssePost } from '../utils/sseClient'; import type { SSEClientOptions } from '../utils/sseClient'; import type { User, AuthUrlResponse, Project, ProjectCreate, ProjectUpdate, WorldBuildingResponse, Outline, OutlineCreate, OutlineUpdate, OutlineReorderRequest, Character, CharacterUpdate, Chapter, ChapterCreate, ChapterUpdate, GenerateOutlineRequest, GenerateCharacterRequest, PolishTextRequest, GenerateCharactersResponse, GenerateOutlineResponse, } from '../types'; const api = axios.create({ baseURL: '/api', timeout: 120000, headers: { 'Content-Type': 'application/json', }, withCredentials: true, }); api.interceptors.request.use( (config) => { return config; }, (error) => { return Promise.reject(error); } ); api.interceptors.response.use( (response) => { return response.data; }, (error) => { let errorMessage = '请求失败'; if (error.response) { const status = error.response.status; const data = error.response.data; switch (status) { case 400: errorMessage = data?.detail || '请求参数错误'; break; case 401: errorMessage = '未授权,请先登录'; if (window.location.pathname !== '/login') { window.location.href = '/login'; } break; case 403: errorMessage = '没有权限访问'; break; case 404: errorMessage = data?.detail || '请求的资源不存在'; break; case 422: errorMessage = data?.detail || '请求参数验证失败'; if (data?.errors) { console.error('验证错误详情:', data.errors); } break; case 500: errorMessage = data?.detail || '服务器内部错误'; break; case 503: errorMessage = '服务暂时不可用,请稍后重试'; break; default: errorMessage = data?.detail || data?.message || `请求失败 (${status})`; } } else if (error.request) { errorMessage = '网络错误,请检查网络连接'; } else { errorMessage = error.message || '请求失败'; } message.error(errorMessage); console.error('API Error:', errorMessage, error); return Promise.reject(error); } ); export const authApi = { getAuthConfig: () => api.get('/auth/config'), localLogin: (username: string, password: string) => api.post('/auth/local/login', { username, password }), getLinuxDOAuthUrl: () => api.get('/auth/linuxdo/url'), getCurrentUser: () => api.get('/auth/user'), logout: () => api.post('/auth/logout'), }; export const userApi = { getCurrentUser: () => api.get('/users/current'), listUsers: () => api.get('/users'), setAdmin: (userId: string, isAdmin: boolean) => api.post('/users/set-admin', { user_id: userId, is_admin: isAdmin }), deleteUser: (userId: string) => api.delete(`/users/${userId}`), getUser: (userId: string) => api.get(`/users/${userId}`), }; export const projectApi = { getProjects: () => api.get('/projects'), getProject: (id: string) => api.get(`/projects/${id}`), createProject: (data: ProjectCreate) => api.post('/projects', data), updateProject: (id: string, data: ProjectUpdate) => api.put(`/projects/${id}`, data), deleteProject: (id: string) => api.delete(`/projects/${id}`), exportProject: (id: string) => { window.open(`/api/projects/${id}/export`, '_blank'); }, }; export const outlineApi = { getOutlines: (projectId: string) => api.get(`/outlines/project/${projectId}`).then(res => res.items), getOutline: (id: string) => api.get(`/outlines/${id}`), createOutline: (data: OutlineCreate) => api.post('/outlines', data), updateOutline: (id: string, data: OutlineUpdate) => api.put(`/outlines/${id}`, data), deleteOutline: (id: string) => api.delete(`/outlines/${id}`), reorderOutlines: (data: OutlineReorderRequest) => api.post('/outlines/reorder', data), generateOutline: (data: GenerateOutlineRequest) => api.post('/outlines/generate', data).then(res => res.items), }; export const characterApi = { getCharacters: (projectId: string) => api.get(`/characters/project/${projectId}`), getCharacter: (id: string) => api.get(`/characters/${id}`), updateCharacter: (id: string, data: CharacterUpdate) => api.put(`/characters/${id}`, data), deleteCharacter: (id: string) => api.delete(`/characters/${id}`), generateCharacter: (data: GenerateCharacterRequest) => api.post('/characters/generate', data), }; export const chapterApi = { getChapters: (projectId: string) => api.get(`/chapters/project/${projectId}`), getChapter: (id: string) => api.get(`/chapters/${id}`), createChapter: (data: ChapterCreate) => api.post('/chapters', data), updateChapter: (id: string, data: ChapterUpdate) => api.put(`/chapters/${id}`, data), deleteChapter: (id: string) => api.delete(`/chapters/${id}`), checkCanGenerate: (chapterId: string) => api.get(`/chapters/${chapterId}/can-generate`), generateChapterContent: (chapterId: string) => api.post(`/chapters/${chapterId}/generate`, {}), }; export const polishApi = { polishText: (data: PolishTextRequest) => api.post('/polish', data), polishBatch: (texts: string[]) => api.post('/polish/batch', { texts }), }; export default api; export const wizardStreamApi = { generateWorldBuildingStream: ( data: { title: string; description: string; theme: string; genre: string | string[]; narrative_perspective?: string; target_words?: number; chapter_count?: number; character_count?: number; provider?: string; model?: string; }, options?: SSEClientOptions ) => ssePost( '/api/wizard-stream/world-building', data, options ), generateCharactersStream: ( data: { project_id: string; count?: number; world_context?: Record; theme?: string; genre?: string; requirements?: string; provider?: string; model?: string; }, options?: SSEClientOptions ) => ssePost( '/api/wizard-stream/characters', data, options ), generateCompleteOutlineStream: ( data: { project_id: string; chapter_count: number; narrative_perspective: string; target_words?: number; requirements?: string; provider?: string; model?: string; }, options?: SSEClientOptions ) => ssePost( '/api/wizard-stream/outline', data, options ), updateWorldBuildingStream: ( projectId: string, data: { time_period?: string; location?: string; atmosphere?: string; rules?: string; }, options?: SSEClientOptions ) => ssePost( `/api/wizard-stream/world-building/${projectId}`, data, options ), regenerateWorldBuildingStream: ( projectId: string, data?: { provider?: string; model?: string; }, options?: SSEClientOptions ) => ssePost( `/api/wizard-stream/world-building/${projectId}/regenerate`, data || {}, options ), cleanupWizardDataStream: ( projectId: string, options?: SSEClientOptions ) => ssePost<{ message: string; deleted: { characters: number; outlines: number; chapters: number } }>( `/api/wizard-stream/cleanup/${projectId}`, {}, options ), };