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, OutlineExpansionRequest, OutlineExpansionResponse, BatchOutlineExpansionRequest, BatchOutlineExpansionResponse, Character, CharacterUpdate, Chapter, ChapterCreate, ChapterUpdate, GenerateOutlineRequest, GenerateCharacterRequest, PolishTextRequest, GenerateCharactersResponse, GenerateOutlineResponse, Settings, SettingsUpdate, WritingStyle, WritingStyleCreate, WritingStyleUpdate, PresetStyle, WritingStyleListResponse, MCPPlugin, MCPPluginCreate, MCPPluginUpdate, MCPTestResult, MCPTool, MCPToolCallRequest, MCPToolCallResponse, APIKeyPreset, PresetCreateRequest, PresetUpdateRequest, PresetListResponse, ChapterPlanItem, } from '../types'; interface MCPPluginSimpleCreate { config_json: string; enabled: boolean; } 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 }), bindAccountLogin: (username: string, password: string) => api.post('/auth/bind/login', { username, password }), getLinuxDOAuthUrl: () => api.get('/auth/linuxdo/url'), getCurrentUser: () => api.get('/auth/user'), getPasswordStatus: () => api.get('/auth/password/status'), setPassword: (password: string) => api.post('/auth/password/set', { password }), initializePassword: (password: string) => api.post('/auth/password/initialize', { password }), refreshSession: () => api.post('/auth/refresh'), 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}`), resetPassword: (userId: string, newPassword?: string) => api.post('/users/reset-password', { user_id: userId, new_password: newPassword }), }; export const settingsApi = { getSettings: () => api.get('/settings'), saveSettings: (data: SettingsUpdate) => api.post('/settings', data), updateSettings: (data: SettingsUpdate) => api.put('/settings', data), deleteSettings: () => api.delete('/settings'), getAvailableModels: (params: { api_key: string; api_base_url: string; provider: string }) => api.get; count?: number }>('/settings/models', { params }), testApiConnection: (params: { api_key: string; api_base_url: string; provider: string; llm_model: string }) => api.post; error?: string; error_type?: string; suggestions?: string[]; }>('/settings/test', params), checkFunctionCalling: (params: { api_key: string; api_base_url: string; provider: string; llm_model: string }) => api.post; response_preview?: string; error?: string; error_type?: string; suggestions?: string[]; }>('/settings/check-function-calling', params), // API配置预设管理 getPresets: () => api.get('/settings/presets'), createPreset: (data: PresetCreateRequest) => api.post('/settings/presets', data), updatePreset: (presetId: string, data: PresetUpdateRequest) => api.put(`/settings/presets/${presetId}`, data), deletePreset: (presetId: string) => api.delete(`/settings/presets/${presetId}`), activatePreset: (presetId: string) => api.post(`/settings/presets/${presetId}/activate`), testPreset: (presetId: string) => api.post; error?: string; error_type?: string; suggestions?: string[]; }>(`/settings/presets/${presetId}/test`), createPresetFromCurrent: (name: string, description?: string) => api.post('/settings/presets/from-current', null, { params: { name, description } }), }; 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'); }, // 导出项目数据为JSON exportProjectData: async (id: string, options: { include_generation_history?: boolean; include_writing_styles?: boolean }) => { const response = await axios.post( `/api/projects/${id}/export-data`, options, { responseType: 'blob', headers: { 'Content-Type': 'application/json', }, } ); // 从响应头获取文件名 const contentDisposition = response.headers['content-disposition']; let filename = 'project_export.json'; if (contentDisposition) { const matches = /filename\*=UTF-8''(.+)/.exec(contentDisposition); if (matches && matches[1]) { filename = decodeURIComponent(matches[1]); } } // 创建下载链接 const url = window.URL.createObjectURL(new Blob([response.data])); const link = document.createElement('a'); link.href = url; link.setAttribute('download', filename); document.body.appendChild(link); link.click(); link.remove(); window.URL.revokeObjectURL(url); }, // 验证导入文件 validateImportFile: (file: File) => { const formData = new FormData(); formData.append('file', file); return api.post; errors: string[]; warnings: string[]; }>('/projects/validate-import', formData, { headers: { 'Content-Type': 'multipart/form-data' }, }); }, // 导入项目 importProject: (file: File) => { const formData = new FormData(); formData.append('file', file); return api.post; warnings: string[]; }>('/projects/import', formData, { headers: { 'Content-Type': 'multipart/form-data' }, }); }, }; 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), // 预测续写所需角色 predictCharacters: (data: { project_id: string; start_chapter: number; chapter_count: number; plot_stage: string; story_direction?: string; enable_mcp: boolean; }) => api.post; }>; }>('/outlines/predict-characters', data), // 获取大纲关联的章节 getOutlineChapters: (outlineId: string) => api.get; expansion_plans: Array<{ sub_index: number; title: string; plot_summary: string; key_events: string[]; character_focus: string[]; emotional_tone: string; narrative_goal: string; conflict_type: string; estimated_words: number; scenes?: Array<{ location: string; characters: string[]; purpose: string; }> | null; }> | null; }>(`/outlines/${outlineId}/chapters`), // 单个大纲展开为多章 expandOutline: (outlineId: string, data: OutlineExpansionRequest) => api.post(`/outlines/${outlineId}/expand`, data), // 根据已有规划创建章节(避免重复AI调用) createChaptersFromPlans: (outlineId: string, chapterPlans: ChapterPlanItem[]) => api.post; }>(`/outlines/${outlineId}/create-chapters-from-plans`, { chapter_plans: chapterPlans }), // 批量展开大纲 batchExpandOutlines: (data: BatchOutlineExpansionRequest) => api.post('/outlines/batch-expand', data), }; export const characterApi = { getCharacters: (projectId: string) => api.get(`/characters/project/${projectId}`), getCharacter: (id: string) => api.get(`/characters/${id}`), createCharacter: (data: { project_id: string; name: string; age?: string; gender?: string; is_organization?: boolean; role_type?: string; personality?: string; background?: string; appearance?: string; relationships?: string; organization_type?: string; organization_purpose?: string; organization_members?: string; traits?: string; avatar_url?: string; power_level?: number; location?: string; motto?: string; color?: string; }) => api.post('/characters', data), 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), // 导出角色/组织 exportCharacters: async (characterIds: string[]) => { const response = await axios.post( '/api/characters/export', { character_ids: characterIds }, { responseType: 'blob', headers: { 'Content-Type': 'application/json', }, } ); // 从响应头获取文件名 const contentDisposition = response.headers['content-disposition']; let filename = 'characters_export.json'; if (contentDisposition) { const matches = /filename=(.+)/.exec(contentDisposition); if (matches && matches[1]) { filename = matches[1]; } } // 创建下载链接 const url = window.URL.createObjectURL(new Blob([response.data])); const link = document.createElement('a'); link.href = url; link.setAttribute('download', filename); document.body.appendChild(link); link.click(); link.remove(); window.URL.revokeObjectURL(url); }, // 验证导入文件 validateImportCharacters: (file: File) => { const formData = new FormData(); formData.append('file', file); return api.post('/characters/validate-import', formData, { headers: { 'Content-Type': 'multipart/form-data' }, }); }, // 导入角色/组织 importCharacters: (projectId: string, file: File) => { const formData = new FormData(); formData.append('file', file); return api.post(`/characters/import?project_id=${projectId}`, formData, { headers: { 'Content-Type': 'multipart/form-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`), // 章节重新生成相关 getRegenerationTasks: (chapterId: string, limit?: number) => api.get; }>(`/chapters/${chapterId}/regeneration/tasks`, { params: { limit } }), }; export const writingStyleApi = { // 获取预设风格列表 getPresetStyles: () => api.get('/writing-styles/presets/list'), // 获取用户的所有风格(新接口) getUserStyles: () => api.get('/writing-styles/user'), // 获取项目的所有风格(保留向后兼容) getProjectStyles: (projectId: string) => api.get(`/writing-styles/project/${projectId}`), // 创建新风格(基于预设或自定义) createStyle: (data: WritingStyleCreate) => api.post('/writing-styles', data), // 更新风格 updateStyle: (styleId: number, data: WritingStyleUpdate) => api.put(`/writing-styles/${styleId}`, data), // 删除风格 deleteStyle: (styleId: number) => api.delete(`/writing-styles/${styleId}`), // 设置默认风格 setDefaultStyle: (styleId: number, projectId: string) => api.post(`/writing-styles/${styleId}/set-default`, { project_id: projectId }), // 为项目初始化默认风格(如果没有任何风格) initializeDefaultStyles: (projectId: string) => api.post(`/writing-styles/project/${projectId}/initialize`, {}), }; export const polishApi = { polishText: (data: PolishTextRequest) => api.post('/polish', data), polishBatch: (texts: string[]) => api.post('/polish/batch', { texts }), }; export const inspirationApi = { // 生成选项建议 generateOptions: (data: { step: 'title' | 'description' | 'theme' | 'genre'; context: { title?: string; description?: string; theme?: string; }; }) => api.post('/inspiration/generate-options', data), // 基于用户反馈重新生成选项(新增) refineOptions: (data: { step: 'title' | 'description' | 'theme' | 'genre'; context: { initial_idea?: string; title?: string; description?: string; theme?: string; }; feedback: string; previous_options?: string[]; }) => api.post('/inspiration/refine-options', data), // 智能补全缺失信息 quickGenerate: (data: { title?: string; description?: string; theme?: string; genre?: string | string[]; }) => api.post('/inspiration/quick-generate', data), }; 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; outline_mode?: 'one-to-one' | 'one-to-many'; // 添加大纲模式参数 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 ), generateCareerSystemStream: ( data: { project_id: string; provider?: string; model?: string; }, options?: SSEClientOptions ) => ssePost<{ project_id: string; main_careers_count: number; sub_careers_count: number; main_careers: string[]; sub_careers: string[]; }>( '/api/wizard-stream/career-system', 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 ), }; export const mcpPluginApi = { // 获取所有插件 getPlugins: () => api.get('/mcp/plugins'), // 获取单个插件 getPlugin: (id: string) => api.get(`/mcp/plugins/${id}`), // 创建插件 createPlugin: (data: MCPPluginCreate) => api.post('/mcp/plugins', data), // 简化创建插件(通过标准MCP配置JSON) createPluginSimple: (data: MCPPluginSimpleCreate) => api.post('/mcp/plugins/simple', data), // 更新插件 updatePlugin: (id: string, data: MCPPluginUpdate) => api.put(`/mcp/plugins/${id}`, data), // 删除插件 deletePlugin: (id: string) => api.delete(`/mcp/plugins/${id}`), // 启用/禁用插件 togglePlugin: (id: string, enabled: boolean) => api.post(`/mcp/plugins/${id}/toggle`, null, { params: { enabled } }), // 测试插件连接 testPlugin: (id: string) => api.post(`/mcp/plugins/${id}/test`), // 获取插件工具列表 getPluginTools: (id: string) => api.get(`/mcp/plugins/${id}/tools`), // 调用工具 callTool: (data: MCPToolCallRequest) => api.post('/mcp/call', data), }; // 管理员API export const adminApi = { // 获取用户列表 getUsers: () => api.get('/admin/users'), // 添加用户 createUser: (data: { username: string; display_name: string; password?: string; avatar_url?: string; trust_level?: number; is_admin?: boolean; }) => api.post('/admin/users', data), // 编辑用户 updateUser: (userId: string, data: { display_name?: string; avatar_url?: string; trust_level?: number; }) => api.put(`/admin/users/${userId}`, data), // 切换用户状态(启用/禁用) toggleUserStatus: (userId: string, isActive: boolean) => api.post(`/admin/users/${userId}/toggle-status`, { is_active: isActive }), // 重置密码 resetPassword: (userId: string, newPassword?: string) => api.post(`/admin/users/${userId}/reset-password`, { new_password: newPassword }), // 删除用户 deleteUser: (userId: string) => api.delete(`/admin/users/${userId}`), };