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, PromptWorkshopListResponse, PromptWorkshopItem, PromptSubmission, PromptSubmissionCreate, MCPPlugin, MCPPluginCreate, MCPPluginUpdate, MCPTestResult, MCPTool, MCPToolCallRequest, MCPToolCallResponse, APIKeyPreset, PresetCreateRequest, PresetUpdateRequest, PresetListResponse, ChapterPlanItem, BookImportTask, BookImportPreview, BookImportApplyPayload, BookImportCreateTaskPayload, BookImportResult, BookImportRetryResult, BatchAnalysisStatusResponse, BatchAnalyzeUnanalyzedRequest, BatchAnalyzeUnanalyzedResponse, } 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: { const backendDetail = data?.detail || data?.message; const unauthenticatedDetails = [ '未登录', '需要登录', '未登录或用户ID缺失', '未登录,无法刷新会话', ]; const isUnauthenticated = unauthenticatedDetails.includes(backendDetail); errorMessage = backendDetail || '登录状态已失效,请重新登录'; if (isUnauthenticated && window.location.pathname !== '/login') { window.location.href = '/login'; } break; } case 403: errorMessage = data?.detail || '没有权限访问'; 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 }), emailLogin: (payload: import('../types').EmailLoginPayload) => api.post('/auth/email/login', payload), sendEmailCode: (payload: import('../types').EmailSendCodePayload) => api.post('/auth/email/send-code', payload), emailRegister: (payload: import('../types').EmailRegisterPayload) => api.post('/auth/email/register', payload), resetEmailPassword: (payload: import('../types').EmailResetPasswordPayload) => api.post('/auth/email/reset-password', payload), 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; temperature?: number; max_tokens?: number }) => api.post; error?: string; error_type?: string; suggestions?: string[]; }>('/settings/test', params), testCoverConnection: (params: { cover_api_provider: string; cover_api_key: string; cover_api_base_url?: string; cover_image_model: string }) => api.post('/settings/cover/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`), setChapterAnalysisPresetSelection: (presetId?: string) => api.put('/settings/presets/usage/chapter-analysis', { preset_id: presetId || null, }), createPresetFromCurrent: (name: string, description?: string) => api.post('/settings/presets/from-current', null, { params: { name, description } }), getSystemSMTPSettings: () => api.get('/settings/system/smtp'), updateSystemSMTPSettings: (data: import('../types').SystemSMTPSettingsUpdate) => api.put('/settings/system/smtp', data), testSystemSMTPSettings: (data: { to_email: string }) => api.post('/settings/system/smtp/test', data), }; 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}`), generateCover: (id: string, overwrite: boolean = true) => api.post(`/projects/${id}/cover/generate`, { overwrite }), downloadCover: async (id: string, filename?: string) => { const response = await axios.get(`/api/projects/${id}/cover/download`, { responseType: 'blob', withCredentials: true, }); const contentDisposition = response.headers['content-disposition']; let finalFilename = filename || 'novel-cover.png'; if (contentDisposition) { const utf8Match = /filename\*=UTF-8''(.+)/.exec(contentDisposition); const basicMatch = /filename="?([^";]+)"?/.exec(contentDisposition); if (utf8Match?.[1]) { finalFilename = decodeURIComponent(utf8Match[1]); } else if (basicMatch?.[1]) { finalFilename = basicMatch[1]; } } const url = window.URL.createObjectURL(new Blob([response.data])); const link = document.createElement('a'); link.href = url; link.setAttribute('download', finalFilename); document.body.appendChild(link); link.click(); link.remove(); window.URL.revokeObjectURL(url); }, exportProject: (id: string) => { window.open(`/api/projects/${id}/export`, '_blank'); }, // 导出项目数据为JSON exportProjectData: async (id: string, options: { include_generation_history?: boolean; include_writing_styles?: boolean; include_careers?: boolean; include_memories?: boolean; include_plot_analysis?: 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 bookImportApi = { createTask: (params: BookImportCreateTaskPayload) => { const formData = new FormData(); formData.append('file', params.file); const tailChapterCount = params.tail_chapter_count ?? 10; formData.append('extract_mode', params.extract_mode ?? 'tail'); formData.append('tail_chapter_count', String(tailChapterCount)); return api.post( '/book-import/tasks', formData, { headers: { 'Content-Type': 'multipart/form-data' } } ); }, getTaskStatus: (taskId: string) => api.get(`/book-import/tasks/${taskId}`), getPreview: (taskId: string) => api.get(`/book-import/tasks/${taskId}/preview`), applyImport: (taskId: string, payload: BookImportApplyPayload) => api.post(`/book-import/tasks/${taskId}/apply`, payload), applyImportStream: ( taskId: string, payload: BookImportApplyPayload, options?: SSEClientOptions, ) => ssePost( `/api/book-import/tasks/${taskId}/apply-stream`, payload, options, ), retryFailedStepsStream: ( taskId: string, steps: string[], options?: SSEClientOptions, ) => ssePost( `/api/book-import/tasks/${taskId}/retry-stream`, { steps }, options, ), cancelTask: (taskId: string) => api.delete(`/book-import/tasks/${taskId}`), }; 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), // 获取大纲关联的章节 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}`).then(res => res.items), 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}`).then(res => res.items), 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`), getBatchAnalysisStatuses: (projectId: string, chapterIds?: string[]) => api.post(`/chapters/project/${projectId}/analysis/statuses`, { chapter_ids: chapterIds && chapterIds.length > 0 ? chapterIds : undefined, }), batchAnalyzeUnanalyzed: (projectId: string, data?: BatchAnalyzeUnanalyzedRequest) => api.post(`/chapters/project/${projectId}/analysis/analyze-unanalyzed`, { chapter_ids: data?.chapter_ids && data.chapter_ids.length > 0 ? data.chapter_ids : undefined, }), // 章节重新生成相关 getRegenerationTasks: (chapterId: string, limit?: number) => api.get; }>(`/chapters/${chapterId}/regeneration/tasks`, { params: { limit } }), // 局部重写相关 partialRegenerateStream: ( chapterId: string, data: { selected_text: string; start_position: number; end_position: number; user_instructions: string; context_chars?: number; style_id?: number; length_mode?: 'similar' | 'expand' | 'condense' | 'custom'; target_word_count?: number; }, options?: SSEClientOptions ) => ssePost<{ new_text: string; word_count: number; original_word_count: number; start_position: number; end_position: number; }>( `/api/chapters/${chapterId}/partial-regenerate-stream`, data, options ), applyPartialRegenerate: (chapterId: string, data: { new_text: string; start_position: number; end_position: number; }) => api.post(`/chapters/${chapterId}/apply-partial-regenerate`, data), }; 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 promptWorkshopApi = { // 检查服务状态 getStatus: () => api.get('/prompt-workshop/status'), // 获取工坊提示词列表 getItems: (params?: { category?: string; search?: string; tags?: string; sort?: 'newest' | 'popular' | 'downloads'; page?: number; limit?: number; }) => api.get('/prompt-workshop/items', { params }), // 获取单个提示词 getItem: (itemId: string) => api.get(`/prompt-workshop/items/${itemId}`), // 导入到本地 importItem: (itemId: string, customName?: string) => api.post( `/prompt-workshop/items/${itemId}/import`, { custom_name: customName } ), // 点赞 toggleLike: (itemId: string) => api.post( `/prompt-workshop/items/${itemId}/like` ), // 提交提示词 submit: (data: PromptSubmissionCreate) => api.post('/prompt-workshop/submit', data), // 我的提交 getMySubmissions: (status?: string) => api.get( '/prompt-workshop/my-submissions', { params: { status } } ), // 撤回提交(pending状态) withdrawSubmission: (submissionId: string) => api.delete(`/prompt-workshop/submissions/${submissionId}`), // 删除提交记录(所有状态,需要 force=true) deleteSubmission: (submissionId: string) => api.delete(`/prompt-workshop/submissions/${submissionId}`, { params: { force: true } }), // ========== 管理员 API(仅服务端模式可用) ========== // 获取待审核列表 adminGetSubmissions: (params?: { status?: string; source?: string; page?: number; limit?: number }) => api.get('/prompt-workshop/admin/submissions', { params }), // 审核提交 adminReviewSubmission: (submissionId: string, data: { action: 'approve' | 'reject'; review_note?: string; category?: string; tags?: string[] }) => api.post( `/prompt-workshop/admin/submissions/${submissionId}/review`, data ), // 添加官方提示词 adminCreateItem: (data: { name: string; description?: string; prompt_content: string; category: string; tags?: string[] }) => api.post('/prompt-workshop/admin/items', data), // 编辑提示词 adminUpdateItem: (itemId: string, data: { name?: string; description?: string; prompt_content?: string; category?: string; tags?: string[]; status?: string }) => api.put(`/prompt-workshop/admin/items/${itemId}`, data), // 删除提示词 adminDeleteItem: (itemId: string) => api.delete(`/prompt-workshop/admin/items/${itemId}`), // 获取统计数据 adminGetStats: () => api.get('/prompt-workshop/admin/stats'), }; 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}`), }; // 伏笔管理API export const foreshadowApi = { // 获取项目伏笔列表 getProjectForeshadows: (projectId: string, params?: { status?: string; category?: string; source_type?: string; is_long_term?: boolean; page?: number; limit?: number; }) => api.get( `/foreshadows/projects/${projectId}`, { params } ), // 获取伏笔统计 getForeshadowStats: (projectId: string, currentChapter?: number) => api.get( `/foreshadows/projects/${projectId}/stats`, { params: { current_chapter: currentChapter } } ), // 获取章节伏笔上下文 getChapterContext: (projectId: string, chapterNumber: number, params?: { include_pending?: boolean; include_overdue?: boolean; lookahead?: number; }) => api.get( `/foreshadows/projects/${projectId}/context/${chapterNumber}`, { params } ), // 获取待回收伏笔 getPendingResolveForeshadows: (projectId: string, currentChapter: number, lookahead?: number) => api.get( `/foreshadows/projects/${projectId}/pending-resolve`, { params: { current_chapter: currentChapter, lookahead } } ), // 获取单个伏笔 getForeshadow: (foreshadowId: string) => api.get(`/foreshadows/${foreshadowId}`), // 创建伏笔 createForeshadow: (data: import('../types').ForeshadowCreate) => api.post('/foreshadows', data), // 更新伏笔 updateForeshadow: (foreshadowId: string, data: import('../types').ForeshadowUpdate) => api.put(`/foreshadows/${foreshadowId}`, data), // 删除伏笔 deleteForeshadow: (foreshadowId: string) => api.delete(`/foreshadows/${foreshadowId}`), // 标记伏笔为已埋入 plantForeshadow: (foreshadowId: string, data: import('../types').PlantForeshadowRequest) => api.post(`/foreshadows/${foreshadowId}/plant`, data), // 标记伏笔为已回收 resolveForeshadow: (foreshadowId: string, data: import('../types').ResolveForeshadowRequest) => api.post(`/foreshadows/${foreshadowId}/resolve`, data), // 标记伏笔为已废弃 abandonForeshadow: (foreshadowId: string, reason?: string) => api.post( `/foreshadows/${foreshadowId}/abandon`, null, { params: { reason } } ), // 从分析结果同步伏笔 syncFromAnalysis: (projectId: string, data: import('../types').SyncFromAnalysisRequest) => api.post( `/foreshadows/projects/${projectId}/sync-from-analysis`, data ), };