Files
deardiary/frontend/src/lib/api.ts

308 lines
7.9 KiB
TypeScript

const API_BASE = '/api/v1';
interface ApiResponse<T> {
data: T | null;
error: { code: string; message: string } | null;
}
class ApiClient {
private apiKey: string | null = null;
setApiKey(key: string) {
this.apiKey = key;
localStorage.setItem('apiKey', key);
}
getApiKey(): string | null {
if (this.apiKey) return this.apiKey;
this.apiKey = localStorage.getItem('apiKey');
return this.apiKey;
}
clearApiKey() {
this.apiKey = null;
localStorage.removeItem('apiKey');
}
private async request<T>(
method: string,
path: string,
body?: unknown
): Promise<ApiResponse<T>> {
const headers: Record<string, string> = {
'Content-Type': 'application/json',
};
if (this.getApiKey()) {
headers['Authorization'] = `Bearer ${this.getApiKey()}`;
}
const response = await fetch(`${API_BASE}${path}`, {
method,
headers,
body: body ? JSON.stringify(body) : undefined,
});
return response.json() as Promise<ApiResponse<T>>;
}
async login(email: string, password: string) {
return this.request<{ token: string; userId: string }>('POST', '/auth/login', { email, password });
}
async createApiKey(name: string, token: string) {
const headers: Record<string, string> = {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`,
};
const response = await fetch(`${API_BASE}/auth/api-key`, {
method: 'POST',
headers,
body: JSON.stringify({ name }),
});
return response.json() as Promise<ApiResponse<{ apiKey: string }>>;
}
async getDays() {
return this.request<Array<{ date: string; eventCount: number; hasJournal: boolean; journalTitle?: string; journalExcerpt?: string }>>('GET', '/days');
}
async getDay(date: string) {
return this.request<{ date: string; events: Event[]; journal: Journal | null }>('GET', `/days/${date}`);
}
async deleteDay(date: string) {
return this.request<{ deleted: boolean }>('DELETE', `/days/${date}`);
}
async createEvent(date: string, type: string, content: string, metadata?: object, location?: { latitude: number; longitude: number; placeName?: string }) {
return this.request<Event>('POST', '/events', { date, type, content, metadata, ...location });
}
async updateEvent(id: string, content: string, metadata?: object) {
return this.request<Event>('PUT', `/events/${id}`, { content, metadata });
}
async deleteEvent(id: string) {
return this.request<{ deleted: boolean }>('DELETE', `/events/${id}`);
}
async deleteJournal(date: string) {
return this.request<{ deleted: boolean }>('DELETE', `/journal/${date}`);
}
async uploadPhoto(eventId: string, file: File) {
const formData = new FormData();
formData.append('file', file);
const headers: Record<string, string> = {};
if (this.getApiKey()) {
headers['Authorization'] = `Bearer ${this.getApiKey()}`;
}
const response = await fetch(`${API_BASE}/events/${eventId}/photo`, {
method: 'POST',
headers,
body: formData,
});
return response.json() as Promise<ApiResponse<{ mediaPath: string }>>;
}
async uploadVoice(eventId: string, file: File) {
const formData = new FormData();
formData.append('file', file);
const headers: Record<string, string> = {};
if (this.getApiKey()) {
headers['Authorization'] = `Bearer ${this.getApiKey()}`;
}
const response = await fetch(`${API_BASE}/events/${eventId}/voice`, {
method: 'POST',
headers,
body: formData,
});
return response.json() as Promise<ApiResponse<{ mediaPath: string }>>;
}
async generateJournal(date: string, instructions?: string) {
return this.request<{ journal: Journal; task: Task }>('POST', `/journal/generate/${date}`, { instructions: instructions || '' });
}
async getJournal(date: string) {
return this.request<Journal>('GET', `/journal/${date}`);
}
async getJournals(page: number = 1, limit: number = 10) {
return this.request<{ journals: Journal[]; total: number; page: number; limit: number; totalPages: number }>('GET', `/journals?page=${page}&limit=${limit}`);
}
async getJournalTasks(date: string) {
return this.request<Task[]>('GET', `/journal/${date}/tasks`);
}
async getTask(taskId: string) {
return this.request<Task>('GET', `/tasks/${taskId}`);
}
async getSettings() {
return this.request<Settings>('GET', '/settings');
}
async search(query: string) {
return this.request<{ journals: Array<{ date: string; title: string; excerpt: string }>; events: Array<{ date: string; type: string; content: string }> }>('GET', `/search?q=${encodeURIComponent(query)}`);
}
async updateSettings(settings: Partial<Settings>) {
return this.request<Settings>('PUT', '/settings', settings);
}
async exportData() {
return this.request<ExportData>('GET', '/export');
}
async importData(data: ExportData) {
return this.request<ImportResult>('POST', '/import', data);
}
async deleteAccount() {
return this.request<{ deleted: boolean }>('DELETE', '/account');
}
async resetAccount() {
return this.request<{ reset: boolean }>('POST', '/account/reset');
}
async changePassword(currentPassword: string, newPassword: string) {
return this.request<{ changed: boolean }>('POST', '/account/password', { currentPassword, newPassword });
}
async register(email: string, password: string) {
return this.request<{ apiKey: string; userId: string }>('POST', '/auth/register', { email, password });
}
}
export interface Event {
id: string;
date: string;
type: string;
content: string;
mediaPath?: string;
metadata?: string;
latitude?: number;
longitude?: number;
placeName?: string;
createdAt: string;
}
export interface Journal {
id: string;
date: string;
title?: string;
content: string;
eventCount: number;
generatedAt: string;
}
export interface Task {
id: string;
journalId: string;
type: string;
status: 'pending' | 'completed' | 'failed';
provider: string;
model?: string;
prompt?: string;
request?: string;
response?: string;
error?: string;
title?: string | null;
createdAt: string;
completedAt?: string;
}
export interface Settings {
aiProvider: 'openai' | 'anthropic' | 'ollama' | 'lmstudio' | 'groq' | 'xai' | 'custom';
aiApiKey?: string;
aiModel: string;
aiBaseUrl?: string;
journalPrompt: string;
language: string;
timezone: string;
journalContextDays: number;
useSystemDefault: boolean;
providerSettings?: Record<string, {
apiKey?: string;
model?: string;
baseUrl?: string;
headers?: Record<string, string>;
parameters?: Record<string, unknown>;
}>;
}
export interface ExportData {
version: string;
exportedAt: string;
settings: {
aiProvider: string;
aiApiKey?: string;
aiModel?: string;
aiBaseUrl?: string;
journalPrompt?: string;
language?: string;
timezone?: string;
providerSettings?: string;
journalContextDays?: number;
};
events: Array<{
id: string;
date: string;
type: string;
content: string;
mediaPath?: string;
metadata?: string;
latitude?: number;
longitude?: number;
placeName?: string;
createdAt: string;
}>;
journals: Array<{
id: string;
date: string;
title?: string;
content: string;
eventCount: number;
generatedAt: string;
}>;
tasks: Array<{
id: string;
journalId: string;
date: string;
type: string;
status: string;
provider: string;
model?: string;
prompt?: string;
request?: string;
response?: string;
error?: string;
title?: string;
createdAt: string;
completedAt?: string;
}>;
}
export interface ImportResult {
compatible: boolean;
importedEvents: number;
importedJournals: number;
importedTasks: number;
skippedEvents: number;
skippedJournals: number;
totalEvents: number;
totalJournals: number;
totalTasks: number;
warning?: string;
}
export const api = new ApiClient();