api.ts 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237
  1. // API client for stable-diffusion REST API
  2. // Type for server config injected by the server
  3. declare global {
  4. interface Window {
  5. __SERVER_CONFIG__?: {
  6. apiUrl: string;
  7. apiBasePath: string;
  8. host: string;
  9. port: number;
  10. };
  11. }
  12. }
  13. // Get configuration from server-injected config or fallback to environment/defaults
  14. // This function is called at runtime to ensure __SERVER_CONFIG__ is available
  15. function getApiConfig() {
  16. if (typeof window !== 'undefined' && window.__SERVER_CONFIG__) {
  17. return {
  18. apiUrl: window.__SERVER_CONFIG__.apiUrl,
  19. apiBase: window.__SERVER_CONFIG__.apiBasePath,
  20. };
  21. }
  22. // Fallback for development mode
  23. return {
  24. apiUrl: process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8080',
  25. apiBase: process.env.NEXT_PUBLIC_API_BASE_PATH || '/api',
  26. };
  27. }
  28. export interface GenerationRequest {
  29. model?: string;
  30. prompt: string;
  31. negative_prompt?: string;
  32. width?: number;
  33. height?: number;
  34. steps?: number;
  35. cfg_scale?: number;
  36. seed?: string;
  37. sampling_method?: string;
  38. scheduler?: string;
  39. batch_count?: number;
  40. clip_skip?: number;
  41. strength?: number;
  42. control_strength?: number;
  43. }
  44. export interface JobInfo {
  45. id?: string;
  46. request_id?: string;
  47. status: 'pending' | 'processing' | 'completed' | 'failed' | 'cancelled' | 'queued';
  48. progress?: number;
  49. result?: {
  50. images: string[];
  51. };
  52. error?: string;
  53. created_at?: string;
  54. updated_at?: string;
  55. message?: string;
  56. queue_position?: number;
  57. }
  58. export interface ModelInfo {
  59. id?: string;
  60. name: string;
  61. path?: string;
  62. type: string;
  63. size?: number;
  64. file_size?: number;
  65. file_size_mb?: number;
  66. sha256?: string | null;
  67. sha256_short?: string | null;
  68. loaded?: boolean;
  69. }
  70. export interface QueueStatus {
  71. active_generations: number;
  72. jobs: JobInfo[];
  73. running: boolean;
  74. size: number;
  75. }
  76. class ApiClient {
  77. // Get base URL dynamically at runtime to ensure server config is loaded
  78. private getBaseUrl(): string {
  79. const { apiUrl, apiBase } = getApiConfig();
  80. return `${apiUrl}${apiBase}`;
  81. }
  82. private async request<T>(
  83. endpoint: string,
  84. options: RequestInit = {}
  85. ): Promise<T> {
  86. const url = `${this.getBaseUrl()}${endpoint}`;
  87. const response = await fetch(url, {
  88. ...options,
  89. headers: {
  90. 'Content-Type': 'application/json',
  91. ...options.headers,
  92. },
  93. });
  94. if (!response.ok) {
  95. const error = await response.json().catch(() => ({
  96. error: response.statusText,
  97. }));
  98. throw new Error(error.error || 'API request failed');
  99. }
  100. return response.json();
  101. }
  102. // Generation endpoints
  103. async generateImage(params: GenerationRequest): Promise<JobInfo> {
  104. return this.request<JobInfo>('/generate/text2img', {
  105. method: 'POST',
  106. body: JSON.stringify(params),
  107. });
  108. }
  109. async text2img(params: GenerationRequest): Promise<JobInfo> {
  110. return this.request<JobInfo>('/generate/text2img', {
  111. method: 'POST',
  112. body: JSON.stringify(params),
  113. });
  114. }
  115. async img2img(params: GenerationRequest & { image: string }): Promise<JobInfo> {
  116. return this.request<JobInfo>('/generate/img2img', {
  117. method: 'POST',
  118. body: JSON.stringify(params),
  119. });
  120. }
  121. // Job management
  122. async getJobStatus(jobId: string): Promise<JobInfo> {
  123. return this.request<JobInfo>(`/queue/job/${jobId}`);
  124. }
  125. async cancelJob(jobId: string): Promise<void> {
  126. return this.request<void>('/queue/cancel', {
  127. method: 'POST',
  128. body: JSON.stringify({ job_id: jobId }),
  129. });
  130. }
  131. async getQueueStatus(): Promise<QueueStatus> {
  132. const response = await this.request<{ queue: QueueStatus }>('/queue/status');
  133. return response.queue;
  134. }
  135. async clearQueue(): Promise<void> {
  136. return this.request<void>('/queue/clear', {
  137. method: 'POST',
  138. });
  139. }
  140. // Model management
  141. async getModels(type?: string, loaded?: boolean): Promise<ModelInfo[]> {
  142. let endpoint = '/models';
  143. const params = [];
  144. if (type && type !== 'loaded') params.push(`type=${type}`);
  145. if (type === 'loaded' || loaded) params.push('loaded=true');
  146. // Request a high limit to get all models (default is 50)
  147. params.push('limit=1000');
  148. if (params.length > 0) endpoint += '?' + params.join('&');
  149. const response = await this.request<{ models: ModelInfo[] }>(endpoint);
  150. // Add id field based on sha256_short or name, and normalize size field
  151. return response.models.map(model => ({
  152. ...model,
  153. id: model.sha256_short || model.name,
  154. size: model.file_size || model.size,
  155. path: model.path || model.name,
  156. }));
  157. }
  158. async getModelInfo(modelId: string): Promise<ModelInfo> {
  159. return this.request<ModelInfo>(`/models/${modelId}`);
  160. }
  161. async loadModel(modelId: string): Promise<void> {
  162. return this.request<void>(`/models/${modelId}/load`, {
  163. method: 'POST',
  164. });
  165. }
  166. async unloadModel(modelId: string): Promise<void> {
  167. return this.request<void>(`/models/${modelId}/unload`, {
  168. method: 'POST',
  169. });
  170. }
  171. async scanModels(): Promise<void> {
  172. return this.request<void>('/models/refresh', {
  173. method: 'POST',
  174. });
  175. }
  176. async convertModel(modelName: string, quantizationType: string, outputPath?: string): Promise<{ request_id: string; message: string }> {
  177. return this.request<{ request_id: string; message: string }>('/models/convert', {
  178. method: 'POST',
  179. body: JSON.stringify({
  180. model_name: modelName,
  181. quantization_type: quantizationType,
  182. output_path: outputPath,
  183. }),
  184. });
  185. }
  186. // System endpoints
  187. async getHealth(): Promise<{ status: string }> {
  188. return this.request<{ status: string }>('/health');
  189. }
  190. async getStatus(): Promise<any> {
  191. return this.request<any>('/status');
  192. }
  193. async getSystemInfo(): Promise<any> {
  194. return this.request<any>('/system');
  195. }
  196. // Configuration endpoints
  197. async getSamplers(): Promise<Array<{ name: string; description: string; recommended_steps: number }>> {
  198. const response = await this.request<{ samplers: Array<{ name: string; description: string; recommended_steps: number }> }>('/samplers');
  199. return response.samplers;
  200. }
  201. async getSchedulers(): Promise<Array<{ name: string; description: string }>> {
  202. const response = await this.request<{ schedulers: Array<{ name: string; description: string }> }>('/schedulers');
  203. return response.schedulers;
  204. }
  205. }
  206. export const apiClient = new ApiClient();