// API client for stable-diffusion REST API // Type for server config injected by the server declare global { interface Window { __SERVER_CONFIG__?: { apiUrl: string; apiBasePath: string; host: string; port: number; }; } } // Get configuration from server-injected config or fallback to environment/defaults // This function is called at runtime to ensure __SERVER_CONFIG__ is available function getApiConfig() { if (typeof window !== 'undefined' && window.__SERVER_CONFIG__) { return { apiUrl: window.__SERVER_CONFIG__.apiUrl, apiBase: window.__SERVER_CONFIG__.apiBasePath, }; } // Fallback for development mode return { apiUrl: process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8080', apiBase: process.env.NEXT_PUBLIC_API_BASE_PATH || '/api', }; } export interface GenerationRequest { model?: string; prompt: string; negative_prompt?: string; width?: number; height?: number; steps?: number; cfg_scale?: number; seed?: string; sampling_method?: string; scheduler?: string; batch_count?: number; clip_skip?: number; strength?: number; control_strength?: number; } export interface JobInfo { id?: string; request_id?: string; status: 'pending' | 'processing' | 'completed' | 'failed' | 'cancelled' | 'queued'; progress?: number; result?: { images: string[]; }; error?: string; created_at?: string; updated_at?: string; message?: string; queue_position?: number; } export interface ModelInfo { id?: string; name: string; path?: string; type: string; size?: number; file_size?: number; file_size_mb?: number; sha256?: string | null; sha256_short?: string | null; loaded?: boolean; } export interface QueueStatus { active_generations: number; jobs: JobInfo[]; running: boolean; size: number; } class ApiClient { // Get base URL dynamically at runtime to ensure server config is loaded private getBaseUrl(): string { const { apiUrl, apiBase } = getApiConfig(); return `${apiUrl}${apiBase}`; } private async request( endpoint: string, options: RequestInit = {} ): Promise { const url = `${this.getBaseUrl()}${endpoint}`; const response = await fetch(url, { ...options, headers: { 'Content-Type': 'application/json', ...options.headers, }, }); if (!response.ok) { const errorData = await response.json().catch(() => ({ error: { message: response.statusText }, })); // Handle nested error structure: { error: { message: "..." } } const errorMessage = errorData.error?.message || errorData.message || errorData.error || 'API request failed'; throw new Error(errorMessage); } return response.json(); } // Generation endpoints async generateImage(params: GenerationRequest): Promise { return this.request('/generate/text2img', { method: 'POST', body: JSON.stringify(params), }); } async text2img(params: GenerationRequest): Promise { return this.request('/generate/text2img', { method: 'POST', body: JSON.stringify(params), }); } async img2img(params: GenerationRequest & { image: string }): Promise { return this.request('/generate/img2img', { method: 'POST', body: JSON.stringify(params), }); } // Job management async getJobStatus(jobId: string): Promise { return this.request(`/queue/job/${jobId}`); } async cancelJob(jobId: string): Promise { return this.request('/queue/cancel', { method: 'POST', body: JSON.stringify({ job_id: jobId }), }); } async getQueueStatus(): Promise { const response = await this.request<{ queue: QueueStatus }>('/queue/status'); return response.queue; } async clearQueue(): Promise { return this.request('/queue/clear', { method: 'POST', }); } // Model management async getModels(type?: string, loaded?: boolean): Promise { let endpoint = '/models'; const params = []; if (type && type !== 'loaded') params.push(`type=${type}`); if (type === 'loaded' || loaded) params.push('loaded=true'); // Request a high limit to get all models (default is 50) params.push('limit=1000'); if (params.length > 0) endpoint += '?' + params.join('&'); const response = await this.request<{ models: ModelInfo[] }>(endpoint); // Add id field based on sha256_short or name, and normalize size field return response.models.map(model => ({ ...model, id: model.sha256_short || model.name, size: model.file_size || model.size, path: model.path || model.name, })); } async getModelInfo(modelId: string): Promise { return this.request(`/models/${modelId}`); } async loadModel(modelId: string): Promise { return this.request(`/models/${modelId}/load`, { method: 'POST', }); } async unloadModel(modelId: string): Promise { return this.request(`/models/${modelId}/unload`, { method: 'POST', }); } async scanModels(): Promise { return this.request('/models/refresh', { method: 'POST', }); } async convertModel(modelName: string, quantizationType: string, outputPath?: string): Promise<{ request_id: string; message: string }> { return this.request<{ request_id: string; message: string }>('/models/convert', { method: 'POST', body: JSON.stringify({ model_name: modelName, quantization_type: quantizationType, output_path: outputPath, }), }); } // System endpoints async getHealth(): Promise<{ status: string }> { return this.request<{ status: string }>('/health'); } async getStatus(): Promise { return this.request('/status'); } async getSystemInfo(): Promise { return this.request('/system'); } async restartServer(): Promise<{ message: string }> { return this.request<{ message: string }>('/system/restart', { method: 'POST', body: JSON.stringify({}), }); } // Configuration endpoints async getSamplers(): Promise> { const response = await this.request<{ samplers: Array<{ name: string; description: string; recommended_steps: number }> }>('/samplers'); return response.samplers; } async getSchedulers(): Promise> { const response = await this.request<{ schedulers: Array<{ name: string; description: string }> }>('/schedulers'); return response.schedulers; } } export const apiClient = new ApiClient();