Files
writer-ide/frontend/src/lib/api.ts
T
admin 46af290db3
Build and Push Docker Images / build-and-push (push) Failing after 3m33s
initial commit
2026-11-07 16:39:43 +03:00

472 lines
13 KiB
TypeScript

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<Project[]> {
const response = await apiClient.get<ApiProjectResponse[]>('/projects');
return response.data.map((p) => ({
...p,
created_at: p.created_at,
updated_at: p.updated_at,
}));
},
/**
* Создать новый проект
*/
async createProject(project: Omit<ApiProjectCreate, 'id'>): Promise<Project> {
const response = await apiClient.post<ApiProjectResponse>('/projects', project);
return {
...response.data,
created_at: response.data.created_at,
updated_at: response.data.updated_at,
};
},
/**
* Обновить проект
*/
async updateProject(
projectId: string,
updates: Partial<Omit<ApiProjectCreate, 'id'>>
): Promise<Project> {
const response = await apiClient.put<ApiProjectResponse>(`/projects/${projectId}`, updates);
return {
...response.data,
created_at: response.data.created_at,
updated_at: response.data.updated_at,
};
},
/**
* Удалить проект
*/
async deleteProject(projectId: string): Promise<void> {
await apiClient.delete(`/projects/${projectId}`);
},
};
// API функции для работы с документами (иерархические)
export const documentsApi = {
/**
* Получить документы в виде дерева
*/
async getDocumentsTree(projectId: string): Promise<DocumentTreeNode[]> {
const response = await apiClient.get<DocumentTreeNode[]>(
`/projects/${projectId}/documents/tree`
);
return response.data;
},
/**
* Получить все документы (плоский список)
*/
async getProjectDocuments(projectId: string): Promise<DocumentNode[]> {
const response = await apiClient.get<DocumentNode[]>(
`/projects/${projectId}/documents`
);
return response.data;
},
/**
* Получить документ по ID
*/
async getDocument(projectId: string, documentId: string): Promise<DocumentNode> {
const response = await apiClient.get<DocumentNode>(
`/projects/${projectId}/documents/${documentId}`
);
return response.data;
},
/**
* Создать документ в проекте
*/
async createDocument(
projectId: string,
title: string,
type: 'book' | 'chapter' | 'scene',
parentId?: string,
content: string = ''
): Promise<DocumentNode> {
const response = await apiClient.post<DocumentNode>(
`/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<DocumentNode> {
const response = await apiClient.put<DocumentNode>(
`/projects/${projectId}/documents/${documentId}`,
updates
);
return response.data;
},
/**
* Удалить документ с каскадом
*/
async deleteDocument(projectId: string, documentId: string): Promise<void> {
await apiClient.delete(`/projects/${projectId}/documents/${documentId}`);
},
/**
* Получить прямых детей узла
*/
async getChildren(projectId: string, parentId: string): Promise<DocumentNode[]> {
const response = await apiClient.get<DocumentNode[]>(
`/projects/${projectId}/documents/${parentId}/children`
);
return response.data;
},
// --- Legacy функции для обратной совместимости ---
/**
* Обновить контент документа (legacy)
*/
async updateDocumentContent(
projectId: string,
documentId: string,
content: string
): Promise<DocumentNode> {
const updatedDoc = await documentsApi.updateDocument(projectId, documentId, {
content,
});
return updatedDoc;
},
};
// API функции для Story Bible
export const storyBibleApi = {
/**
* Получить Story Bible проекта
*/
async getProjectStoryBible(projectId: string): Promise<StoryBible> {
const response = await apiClient.get<StoryBible>(`/projects/${projectId}/bible`);
return response.data;
},
/**
* Обновить Story Bible проекта
*/
async updateProjectStoryBible(
projectId: string,
bible: StoryBible
): Promise<StoryBible> {
const response = await apiClient.put<StoryBible>(`/projects/${projectId}/bible`, bible);
return response.data;
},
/**
* Очистить дубликаты в Story Bible
*/
async cleanupDuplicates(projectId: string): Promise<CleanupDuplicatesResponse> {
const response = await apiClient.post<CleanupDuplicatesResponse>(
`/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<void> {
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<ModelsResponse> {
try {
const response = await apiClient.get<ModelsResponse>(`/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<ModelsResponse> {
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<ModelsResponse>(
`/ai/models?provider=${modelSetting.provider}`
);
// Обновляем default_model на выбранный пользователем
return {
...response.data,
default_model: modelSetting.model,
};
}
}
}
// Если нет настроек, используем дефолтные
const response = await apiClient.get<ModelsResponse>(`/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<GenerateImageResponse> {
const response = await apiClient.post<GenerateImageResponse>('/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<AuthResponse> {
const response = await apiClient.post<AuthResponse>('/auth/login', { username, password });
return response.data;
}
/**
* Зарегистрировать нового пользователя
*/
export async function register(username: string, email: string, password: string): Promise<AuthResponse> {
const response = await apiClient.post<AuthResponse>('/auth/register', { username, email, password });
return response.data;
}
/**
* Получить текущего пользователя
*/
export async function fetchCurrentUser(): Promise<User> {
const response = await apiClient.get<User>('/auth/me');
return response.data;
}