api.ts 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439
  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. // Add auth token if available
  88. const token = typeof window !== 'undefined' ? localStorage.getItem('auth_token') : null;
  89. const headers = {
  90. 'Content-Type': 'application/json',
  91. ...(token && { 'Authorization': `Bearer ${token}` }),
  92. ...options.headers,
  93. };
  94. const response = await fetch(url, {
  95. ...options,
  96. headers,
  97. });
  98. if (!response.ok) {
  99. const errorData = await response.json().catch(() => ({
  100. error: { message: response.statusText },
  101. }));
  102. // Handle nested error structure: { error: { message: "..." } }
  103. const errorMessage =
  104. errorData.error?.message ||
  105. errorData.message ||
  106. errorData.error ||
  107. 'API request failed';
  108. throw new Error(errorMessage);
  109. }
  110. return response.json();
  111. }
  112. // Generation endpoints
  113. async generateImage(params: GenerationRequest): Promise<JobInfo> {
  114. return this.request<JobInfo>('/generate/text2img', {
  115. method: 'POST',
  116. body: JSON.stringify(params),
  117. });
  118. }
  119. async text2img(params: GenerationRequest): Promise<JobInfo> {
  120. return this.request<JobInfo>('/generate/text2img', {
  121. method: 'POST',
  122. body: JSON.stringify(params),
  123. });
  124. }
  125. async img2img(params: GenerationRequest & { image: string }): Promise<JobInfo> {
  126. return this.request<JobInfo>('/generate/img2img', {
  127. method: 'POST',
  128. body: JSON.stringify(params),
  129. });
  130. }
  131. // Job management
  132. async getJobStatus(jobId: string): Promise<JobInfo> {
  133. return this.request<JobInfo>(`/queue/job/${jobId}`);
  134. }
  135. async cancelJob(jobId: string): Promise<void> {
  136. return this.request<void>('/queue/cancel', {
  137. method: 'POST',
  138. body: JSON.stringify({ job_id: jobId }),
  139. });
  140. }
  141. async getQueueStatus(): Promise<QueueStatus> {
  142. const response = await this.request<{ queue: QueueStatus }>('/queue/status');
  143. return response.queue;
  144. }
  145. async clearQueue(): Promise<void> {
  146. return this.request<void>('/queue/clear', {
  147. method: 'POST',
  148. });
  149. }
  150. // Model management
  151. async getModels(type?: string, loaded?: boolean): Promise<ModelInfo[]> {
  152. let endpoint = '/models';
  153. const params = [];
  154. if (type && type !== 'loaded') params.push(`type=${type}`);
  155. if (type === 'loaded' || loaded) params.push('loaded=true');
  156. // Request a high limit to get all models (default is 50)
  157. params.push('limit=1000');
  158. if (params.length > 0) endpoint += '?' + params.join('&');
  159. const response = await this.request<{ models: ModelInfo[] }>(endpoint);
  160. // Add id field based on sha256_short or name, and normalize size field
  161. return response.models.map(model => ({
  162. ...model,
  163. id: model.sha256_short || model.name,
  164. size: model.file_size || model.size,
  165. path: model.path || model.name,
  166. }));
  167. }
  168. async getModelInfo(modelId: string): Promise<ModelInfo> {
  169. return this.request<ModelInfo>(`/models/${modelId}`);
  170. }
  171. async loadModel(modelId: string): Promise<void> {
  172. return this.request<void>(`/models/${modelId}/load`, {
  173. method: 'POST',
  174. });
  175. }
  176. async unloadModel(modelId: string): Promise<void> {
  177. return this.request<void>(`/models/${modelId}/unload`, {
  178. method: 'POST',
  179. });
  180. }
  181. async scanModels(): Promise<void> {
  182. return this.request<void>('/models/refresh', {
  183. method: 'POST',
  184. });
  185. }
  186. async convertModel(modelName: string, quantizationType: string, outputPath?: string): Promise<{ request_id: string; message: string }> {
  187. return this.request<{ request_id: string; message: string }>('/models/convert', {
  188. method: 'POST',
  189. body: JSON.stringify({
  190. model_name: modelName,
  191. quantization_type: quantizationType,
  192. output_path: outputPath,
  193. }),
  194. });
  195. }
  196. // System endpoints
  197. async getHealth(): Promise<{ status: string }> {
  198. return this.request<{ status: string }>('/health');
  199. }
  200. async getStatus(): Promise<any> {
  201. return this.request<any>('/status');
  202. }
  203. async getSystemInfo(): Promise<any> {
  204. return this.request<any>('/system');
  205. }
  206. async restartServer(): Promise<{ message: string }> {
  207. return this.request<{ message: string }>('/system/restart', {
  208. method: 'POST',
  209. body: JSON.stringify({}),
  210. });
  211. }
  212. // Configuration endpoints
  213. async getSamplers(): Promise<Array<{ name: string; description: string; recommended_steps: number }>> {
  214. const response = await this.request<{ samplers: Array<{ name: string; description: string; recommended_steps: number }> }>('/samplers');
  215. return response.samplers;
  216. }
  217. async getSchedulers(): Promise<Array<{ name: string; description: string }>> {
  218. const response = await this.request<{ schedulers: Array<{ name: string; description: string }> }>('/schedulers');
  219. return response.schedulers;
  220. }
  221. }
  222. // Generic API request function for authentication
  223. export async function apiRequest(
  224. endpoint: string,
  225. options: RequestInit = {}
  226. ): Promise<Response> {
  227. const { apiUrl, apiBase } = getApiConfig();
  228. const url = `${apiUrl}${apiBase}${endpoint}`;
  229. // Add auth token if available
  230. const token = typeof window !== 'undefined' ? localStorage.getItem('auth_token') : null;
  231. const headers = {
  232. 'Content-Type': 'application/json',
  233. ...(token && { 'Authorization': `Bearer ${token}` }),
  234. ...options.headers,
  235. };
  236. return fetch(url, {
  237. ...options,
  238. headers,
  239. });
  240. }
  241. // Authentication API endpoints
  242. export const authApi = {
  243. async login(username: string, password: string) {
  244. const response = await apiRequest('/auth/login', {
  245. method: 'POST',
  246. body: JSON.stringify({ username, password }),
  247. });
  248. if (!response.ok) {
  249. const error = await response.json().catch(() => ({ message: 'Login failed' }));
  250. throw new Error(error.message || 'Login failed');
  251. }
  252. return response.json();
  253. },
  254. async validateToken(token: string) {
  255. const response = await apiRequest('/auth/validate', {
  256. headers: { 'Authorization': `Bearer ${token}` },
  257. });
  258. if (!response.ok) {
  259. throw new Error('Token validation failed');
  260. }
  261. return response.json();
  262. },
  263. async refreshToken() {
  264. const response = await apiRequest('/auth/refresh', {
  265. method: 'POST',
  266. });
  267. if (!response.ok) {
  268. throw new Error('Token refresh failed');
  269. }
  270. return response.json();
  271. },
  272. async logout() {
  273. await apiRequest('/auth/logout', {
  274. method: 'POST',
  275. });
  276. },
  277. async getCurrentUser() {
  278. const response = await apiRequest('/auth/me');
  279. if (!response.ok) {
  280. throw new Error('Failed to get current user');
  281. }
  282. return response.json();
  283. },
  284. async changePassword(oldPassword: string, newPassword: string) {
  285. const response = await apiRequest('/auth/change-password', {
  286. method: 'POST',
  287. body: JSON.stringify({ old_password: oldPassword, new_password: newPassword }),
  288. });
  289. if (!response.ok) {
  290. const error = await response.json().catch(() => ({ message: 'Password change failed' }));
  291. throw new Error(error.message || 'Password change failed');
  292. }
  293. return response.json();
  294. },
  295. // API Key management
  296. async getApiKeys() {
  297. const response = await apiRequest('/auth/api-keys');
  298. if (!response.ok) {
  299. throw new Error('Failed to get API keys');
  300. }
  301. return response.json();
  302. },
  303. async createApiKey(name: string, scopes?: string[]) {
  304. const response = await apiRequest('/auth/api-keys', {
  305. method: 'POST',
  306. body: JSON.stringify({ name, scopes }),
  307. });
  308. if (!response.ok) {
  309. const error = await response.json().catch(() => ({ message: 'Failed to create API key' }));
  310. throw new Error(error.message || 'Failed to create API key');
  311. }
  312. return response.json();
  313. },
  314. async deleteApiKey(keyId: string) {
  315. const response = await apiRequest(`/auth/api-keys/${keyId}`, {
  316. method: 'DELETE',
  317. });
  318. if (!response.ok) {
  319. throw new Error('Failed to delete API key');
  320. }
  321. return response.json();
  322. },
  323. // User management (admin only)
  324. async getUsers() {
  325. const response = await apiRequest('/auth/users');
  326. if (!response.ok) {
  327. throw new Error('Failed to get users');
  328. }
  329. return response.json();
  330. },
  331. async createUser(userData: { username: string; email?: string; password: string; role?: string }) {
  332. const response = await apiRequest('/auth/users', {
  333. method: 'POST',
  334. body: JSON.stringify(userData),
  335. });
  336. if (!response.ok) {
  337. const error = await response.json().catch(() => ({ message: 'Failed to create user' }));
  338. throw new Error(error.message || 'Failed to create user');
  339. }
  340. return response.json();
  341. },
  342. async updateUser(userId: string, userData: { email?: string; role?: string; active?: boolean }) {
  343. const response = await apiRequest(`/auth/users/${userId}`, {
  344. method: 'PUT',
  345. body: JSON.stringify(userData),
  346. });
  347. if (!response.ok) {
  348. const error = await response.json().catch(() => ({ message: 'Failed to update user' }));
  349. throw new Error(error.message || 'Failed to update user');
  350. }
  351. return response.json();
  352. },
  353. async deleteUser(userId: string) {
  354. const response = await apiRequest(`/auth/users/${userId}`, {
  355. method: 'DELETE',
  356. });
  357. if (!response.ok) {
  358. throw new Error('Failed to delete user');
  359. }
  360. return response.json();
  361. }
  362. };
  363. export const apiClient = new ApiClient();