import axios from 'axios'; import type { DocumentNode, DocumentTreeNode, ModelSettings } from '@/stores/appStore'; // Конфигурация API const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8000/api'; // Типы export interface User { id: string; username: string; email: string; } export interface AuthResponse { access_token: string; token_type: string; user_id: string; username: string; } // Типы для конфигурации моделей export interface ModelInfo { id: string; name: string; description: string; context_length?: number; pricing?: string; } export interface ModelsResponse { provider: string; default_model: string; models: ModelInfo[]; } export type Provider = 'ollama' | 'openrouter'; // Типы для Project export interface Project { id: string; title: string; description: string; created_at: string; updated_at: string; } // Типы для Story Bible export interface Character { id: string; name: string; description: string; traits: string[]; appearance: string; role: 'protagonist' | 'antagonist' | 'supporting' | 'minor'; } export interface Location { id: string; name: string; description: string; atmosphere: string; type: 'city' | 'building' | 'nature' | 'interior' | 'other'; } export interface Event { id: string; title: string; description: string; chapter?: string; importance: 'major' | 'minor' | 'background'; } export interface StoryThread { id: string; title: string; description: string; status: 'active' | 'resolved' | 'abandoned'; related_characters: string[]; related_events: string[]; } export interface StoryBible { characters: Character[]; locations: Location[]; events: Event[]; threads: StoryThread[]; last_updated?: string; } export interface CleanupDuplicatesResponse { removed_characters: number; removed_locations: number; removed_events: number; message: string; } // Экспортируем все типы Story Bible export type { Character, Location, Event, StoryThread, StoryBible }; const apiClient = axios.create({ baseURL: API_BASE_URL, headers: { 'Content-Type': 'application/json', }, }); // Интерсептор для автоматического добавления токена apiClient.interceptors.request.use((config) => { const token = typeof window !== 'undefined' ? localStorage.getItem('auth_token') : null; if (token && config.headers) { config.headers.Authorization = `Bearer ${token}`; } return config; }); interface ApiProjectResponse { id: string; title: string; description: string; created_at: string; updated_at: string; } interface ApiProjectCreate { title: string; description: string; } // API функции для работы с проектами export const projectsApi = { /** * Получить все проекты */ async getAllProjects(): Promise { const response = await apiClient.get('/projects'); return response.data.map((p) => ({ ...p, created_at: p.created_at, updated_at: p.updated_at, })); }, /** * Создать новый проект */ async createProject(project: Omit): Promise { const response = await apiClient.post('/projects', project); return { ...response.data, created_at: response.data.created_at, updated_at: response.data.updated_at, }; }, /** * Обновить проект */ async updateProject( projectId: string, updates: Partial> ): Promise { const response = await apiClient.put(`/projects/${projectId}`, updates); return { ...response.data, created_at: response.data.created_at, updated_at: response.data.updated_at, }; }, /** * Удалить проект */ async deleteProject(projectId: string): Promise { await apiClient.delete(`/projects/${projectId}`); }, }; // API функции для работы с документами (иерархические) export const documentsApi = { /** * Получить документы в виде дерева */ async getDocumentsTree(projectId: string): Promise { const response = await apiClient.get( `/projects/${projectId}/documents/tree` ); return response.data; }, /** * Получить все документы (плоский список) */ async getProjectDocuments(projectId: string): Promise { const response = await apiClient.get( `/projects/${projectId}/documents` ); return response.data; }, /** * Получить документ по ID */ async getDocument(projectId: string, documentId: string): Promise { const response = await apiClient.get( `/projects/${projectId}/documents/${documentId}` ); return response.data; }, /** * Создать документ в проекте */ async createDocument( projectId: string, title: string, type: 'book' | 'chapter' | 'scene', parentId?: string, content: string = '' ): Promise { const response = await apiClient.post( `/projects/${projectId}/documents`, { title, type, parent_id: parentId, content, } ); return response.data; }, /** * Обновить документ (включая перенос) */ async updateDocument( projectId: string, documentId: string, updates: { title?: string; content?: string; parent_id?: string; order?: number; } ): Promise { const response = await apiClient.put( `/projects/${projectId}/documents/${documentId}`, updates ); return response.data; }, /** * Удалить документ с каскадом */ async deleteDocument(projectId: string, documentId: string): Promise { await apiClient.delete(`/projects/${projectId}/documents/${documentId}`); }, /** * Получить прямых детей узла */ async getChildren(projectId: string, parentId: string): Promise { const response = await apiClient.get( `/projects/${projectId}/documents/${parentId}/children` ); return response.data; }, // --- Legacy функции для обратной совместимости --- /** * Обновить контент документа (legacy) */ async updateDocumentContent( projectId: string, documentId: string, content: string ): Promise { const updatedDoc = await documentsApi.updateDocument(projectId, documentId, { content, }); return updatedDoc; }, }; // API функции для Story Bible export const storyBibleApi = { /** * Получить Story Bible проекта */ async getProjectStoryBible(projectId: string): Promise { const response = await apiClient.get(`/projects/${projectId}/bible`); return response.data; }, /** * Обновить Story Bible проекта */ async updateProjectStoryBible( projectId: string, bible: StoryBible ): Promise { const response = await apiClient.put(`/projects/${projectId}/bible`, bible); return response.data; }, /** * Очистить дубликаты в Story Bible */ async cleanupDuplicates(projectId: string): Promise { const response = await apiClient.post( `/projects/${projectId}/bible/cleanup-duplicates` ); return response.data; }, }; const apiClientFactory = () => apiClient; export default apiClientFactory; /** * Экспорт документа в выбранный формат (epub, fb2, pdf, docx) */ export async function exportDocument( projectId: string, documentId: string, format: 'epub' | 'fb2' | 'pdf' | 'docx' ): Promise { try { const response = await apiClient.post(`/export`, { project_id: projectId, document_id: documentId, format }, { responseType: 'blob' } ); // Создаем временную ссылку для скачивания const url = window.URL.createObjectURL(new Blob([response.data])); const link = document.createElement('a'); link.href = url; // Определяем расширение файла const extension = format === 'docx' ? 'docx' : format; link.setAttribute('download', `export.${extension}`); document.body.appendChild(link); link.click(); link.remove(); window.URL.revokeObjectURL(url); } catch (error) { if (axios.isAxiosError(error)) { throw new Error(`Export failed: ${error.response?.data?.detail || error.message}`); } throw error; } } /** * Получить список доступных моделей для провайдера */ export async function getAvailableModels(provider: Provider): Promise { try { const response = await apiClient.get(`/ai/models?provider=${provider}`); return response.data; } catch (error) { if (axios.isAxiosError(error)) { throw new Error(`Failed to fetch models: ${error.response?.data?.detail || error.message}`); } throw error; } } /** * Получить данные модели для конкретного endpoint (generation, background, analysis) */ export async function getModelForTask(taskType: keyof ModelSettings): Promise { try { // Получаем настройки из localStorage if (typeof window !== 'undefined') { const savedSettings = localStorage.getItem('modelSettings'); if (savedSettings) { const settings: ModelSettings = JSON.parse(savedSettings); const modelSetting = settings[taskType]; if (modelSetting) { const response = await apiClient.get( `/ai/models?provider=${modelSetting.provider}` ); // Обновляем default_model на выбранный пользователем return { ...response.data, default_model: modelSetting.model, }; } } } // Если нет настроек, используем дефолтные const response = await apiClient.get(`/ai/models?provider=ollama`); return response.data; } catch (error) { if (axios.isAxiosError(error)) { throw new Error(`Failed to fetch model for ${taskType}: ${error.response?.data?.detail || error.message}`); } throw error; } } // === ФУНКЦИИ ГЕНЕРАЦИИ ИЗОБРАЖЕНИЙ === export interface GenerateImageRequest { prompt: string; project_id: string; document_id: string; // ID документа (книги или главы) document_type: 'book' | 'chapter'; // Тип документа model?: string; size?: string; } export interface GenerateImageResponse { image_id: string; filename: string; path: string; prompt: string; model: string; data_url: string; // base64 изображение для немедленного отображения document_id: string; document_type: string; } /** * Сгенерировать изображение */ export async function generateImage(request: GenerateImageRequest): Promise { const response = await apiClient.post('/ai/generate-image', request); return response.data; } /** * Получить URL изображения */ export function getImageUrl(projectId: string, filename: string): string { const baseUrl = process.env.NEXT_PUBLIC_API_URL?.replace('/api', '') || 'http://localhost:8000'; return `${baseUrl}/api/images/${projectId}/${filename}`; } // === ФУНКЦИИ АВТОРИЗАЦИИ === /** * Войти в систему */ export async function login(username: string, password: string): Promise { const response = await apiClient.post('/auth/login', { username, password }); return response.data; } /** * Зарегистрировать нового пользователя */ export async function register(username: string, email: string, password: string): Promise { const response = await apiClient.post('/auth/register', { username, email, password }); return response.data; } /** * Получить текущего пользователя */ export async function fetchCurrentUser(): Promise { const response = await apiClient.get('/auth/me'); return response.data; }