Files
voicebox/app/src/lib/api/client.ts
T

237 lines
6.6 KiB
TypeScript

import { useServerStore } from '@/stores/serverStore';
import type {
VoiceProfileCreate,
VoiceProfileResponse,
ProfileSampleResponse,
GenerationRequest,
GenerationResponse,
HistoryQuery,
HistoryListResponse,
HistoryResponse,
TranscriptionResponse,
HealthResponse,
ModelStatusListResponse,
ModelDownloadRequest,
ActiveTasksResponse,
} from './types';
class ApiClient {
private getBaseUrl(): string {
const serverUrl = useServerStore.getState().serverUrl;
return serverUrl;
}
private async request<T>(endpoint: string, options?: RequestInit): Promise<T> {
const url = `${this.getBaseUrl()}${endpoint}`;
const response = await fetch(url, {
...options,
headers: {
'Content-Type': 'application/json',
...options?.headers,
},
});
if (!response.ok) {
const error = await response.json().catch(() => ({
detail: response.statusText,
}));
throw new Error(error.detail || `HTTP error! status: ${response.status}`);
}
return response.json();
}
// Health
async getHealth(): Promise<HealthResponse> {
return this.request<HealthResponse>('/health');
}
// Profiles
async createProfile(data: VoiceProfileCreate): Promise<VoiceProfileResponse> {
return this.request<VoiceProfileResponse>('/profiles', {
method: 'POST',
body: JSON.stringify(data),
});
}
async listProfiles(): Promise<VoiceProfileResponse[]> {
return this.request<VoiceProfileResponse[]>('/profiles');
}
async getProfile(profileId: string): Promise<VoiceProfileResponse> {
return this.request<VoiceProfileResponse>(`/profiles/${profileId}`);
}
async updateProfile(profileId: string, data: VoiceProfileCreate): Promise<VoiceProfileResponse> {
return this.request<VoiceProfileResponse>(`/profiles/${profileId}`, {
method: 'PUT',
body: JSON.stringify(data),
});
}
async deleteProfile(profileId: string): Promise<void> {
await this.request<void>(`/profiles/${profileId}`, {
method: 'DELETE',
});
}
async addProfileSample(
profileId: string,
file: File,
referenceText: string,
): Promise<ProfileSampleResponse> {
const url = `${this.getBaseUrl()}/profiles/${profileId}/samples`;
const formData = new FormData();
formData.append('file', file);
formData.append('reference_text', referenceText);
const response = await fetch(url, {
method: 'POST',
body: formData,
});
if (!response.ok) {
const error = await response.json().catch(() => ({
detail: response.statusText,
}));
throw new Error(error.detail || `HTTP error! status: ${response.status}`);
}
return response.json();
}
async listProfileSamples(profileId: string): Promise<ProfileSampleResponse[]> {
return this.request<ProfileSampleResponse[]>(`/profiles/${profileId}/samples`);
}
async deleteProfileSample(sampleId: string): Promise<void> {
await this.request<void>(`/profiles/samples/${sampleId}`, {
method: 'DELETE',
});
}
async exportProfile(profileId: string): Promise<Blob> {
const url = `${this.getBaseUrl()}/profiles/${profileId}/export`;
const response = await fetch(url);
if (!response.ok) {
const error = await response.json().catch(() => ({
detail: response.statusText,
}));
throw new Error(error.detail || `HTTP error! status: ${response.status}`);
}
return response.blob();
}
async importProfile(file: File): Promise<VoiceProfileResponse> {
const url = `${this.getBaseUrl()}/profiles/import`;
const formData = new FormData();
formData.append('file', file);
const response = await fetch(url, {
method: 'POST',
body: formData,
});
if (!response.ok) {
const error = await response.json().catch(() => ({
detail: response.statusText,
}));
throw new Error(error.detail || `HTTP error! status: ${response.status}`);
}
return response.json();
}
// Generation
async generateSpeech(data: GenerationRequest): Promise<GenerationResponse> {
return this.request<GenerationResponse>('/generate', {
method: 'POST',
body: JSON.stringify(data),
});
}
// History
async listHistory(query?: HistoryQuery): Promise<HistoryListResponse> {
const params = new URLSearchParams();
if (query?.profile_id) params.append('profile_id', query.profile_id);
if (query?.search) params.append('search', query.search);
if (query?.limit) params.append('limit', query.limit.toString());
if (query?.offset) params.append('offset', query.offset.toString());
const queryString = params.toString();
const endpoint = queryString ? `/history?${queryString}` : '/history';
return this.request<HistoryListResponse>(endpoint);
}
async getGeneration(generationId: string): Promise<HistoryResponse> {
return this.request<HistoryResponse>(`/history/${generationId}`);
}
async deleteGeneration(generationId: string): Promise<void> {
await this.request<void>(`/history/${generationId}`, {
method: 'DELETE',
});
}
// Audio
getAudioUrl(audioId: string): string {
return `${this.getBaseUrl()}/audio/${audioId}`;
}
getSampleUrl(sampleId: string): string {
return `${this.getBaseUrl()}/samples/${sampleId}`;
}
// Transcription
async transcribeAudio(file: File, language?: 'en' | 'zh'): Promise<TranscriptionResponse> {
const formData = new FormData();
formData.append('file', file);
if (language) {
formData.append('language', language);
}
const url = `${this.getBaseUrl()}/transcribe`;
const response = await fetch(url, {
method: 'POST',
body: formData,
});
if (!response.ok) {
const error = await response.json().catch(() => ({
detail: response.statusText,
}));
throw new Error(error.detail || `HTTP error! status: ${response.status}`);
}
return response.json();
}
// Model Management
async getModelStatus(): Promise<ModelStatusListResponse> {
return this.request<ModelStatusListResponse>('/models/status');
}
async triggerModelDownload(modelName: string): Promise<{ message: string }> {
return this.request<{ message: string }>('/models/download', {
method: 'POST',
body: JSON.stringify({ model_name: modelName } as ModelDownloadRequest),
});
}
async deleteModel(modelName: string): Promise<{ message: string }> {
return this.request<{ message: string }>(`/models/${modelName}`, {
method: 'DELETE',
});
}
// Task Management
async getActiveTasks(): Promise<ActiveTasksResponse> {
return this.request<ActiveTasksResponse>('/tasks/active');
}
}
export const apiClient = new ApiClient();