Files
titan3d_react_node_sqllite/src/lib/api/apiUtils.ts
T
2026-10-05 15:27:59 +05:30

120 lines
3.6 KiB
TypeScript

/**
* Utility functions for API requests with exponential backoff and polling.
*/
export const delay = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
export interface FetchOptions extends RequestInit {
maxRetries?: number;
baseDelayMs?: number;
}
export async function fetchWithRetry(url: string, options: FetchOptions = {}): Promise<Response> {
const { maxRetries = 3, baseDelayMs = 1000, ...fetchOptions } = options;
let retries = 0;
while (true) {
try {
const response = await fetch(url, fetchOptions);
if (response.ok) {
return response;
}
// Don't retry on 4xx errors (client errors)
if (response.status >= 400 && response.status < 500 && response.status !== 429) {
throw new Error(`HTTP Error: ${response.status} ${response.statusText}`);
}
// If it's a 429 (Rate Limit) or 5xx, we throw to trigger a retry
throw new Error(`HTTP Error: ${response.status} ${response.statusText}`);
} catch (error) {
if (retries >= maxRetries) {
throw error;
}
retries++;
// Exponential backoff with some jitter
const waitTime = (baseDelayMs * Math.pow(2, retries - 1)) + (Math.random() * 500);
console.warn(`Request failed, retrying in ${Math.round(waitTime)}ms... (Attempt ${retries}/${maxRetries})`);
await delay(waitTime);
}
}
}
export interface PollOptions {
intervalMs?: number;
maxAttempts?: number;
onProgress?: (progress: number) => void;
}
export async function pollStatus<T>(
pollFn: () => Promise<{ status: 'processing' | 'success' | 'failed', data?: T, progress?: number }>,
options: PollOptions = {}
): Promise<T> {
const { intervalMs = 2000, maxAttempts = 150, onProgress } = options; // Default to 5 minutes total polling
let attempts = 0;
while (attempts < maxAttempts) {
try {
const result = await pollFn();
if (result.status === 'success' && result.data) {
return result.data;
}
if (result.status === 'failed') {
throw new Error("Task failed during processing on the remote server.");
}
if (result.progress !== undefined && onProgress) {
onProgress(result.progress);
}
} catch (err) {
// If the poll request itself fails, we log it but keep trying until maxAttempts
console.warn("Polling request failed, will retry next interval:", err);
}
attempts++;
await delay(intervalMs);
}
throw new Error("Polling timeout exceeded.");
}
export async function editUVLayer(prompt: string, imageBase64: string, token: string, referenceImageBase64?: string): Promise<string> {
// Extract mime type if present in the data URI
let mimeType = 'image/png';
if (imageBase64.startsWith('data:')) {
const match = imageBase64.match(/^data:([^;]+);/);
if (match) {
mimeType = match[1];
}
}
let referenceMimeType;
if (referenceImageBase64 && referenceImageBase64.startsWith('data:')) {
const match = referenceImageBase64.match(/^data:([^;]+);/);
if (match) {
referenceMimeType = match[1];
}
}
const response = await fetch('/api/sparkplug/edit-layer', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
body: JSON.stringify({ prompt, imageBase64, mimeType, referenceImageBase64, referenceMimeType })
});
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(errorData.error || `HTTP error! status: ${response.status}`);
}
const data = await response.json();
if (data.error) throw new Error(data.error);
return data.imageUrl;
}