@activadee/n8n-nodes-gradio-client
Version:
n8n node for connecting to Gradio Spaces
179 lines (178 loc) • 8.47 kB
JavaScript
;
// Gradio Client Core Logic
Object.defineProperty(exports, "__esModule", { value: true });
exports.GradioClient = void 0;
const types_1 = require("./types");
const errors_1 = require("./errors");
const utils_1 = require("./utils");
class GradioClient {
// Advanced retry logic with exponential backoff
static async executeWithRetry(operation, maxRetries = 3, baseDelay = 1000) {
let lastError;
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
return await operation();
}
catch (error) {
lastError = error;
// Don't retry on certain error types
if (error instanceof errors_1.GradioError && !error.retryable) {
throw error;
}
if (attempt === maxRetries) {
throw lastError;
}
// Exponential backoff with jitter
const delay = baseDelay * Math.pow(2, attempt - 1) + Math.random() * 1000;
console.log(`Retry attempt ${attempt}/${maxRetries} after ${Math.round(delay)}ms delay`);
await new Promise(resolve => setTimeout(resolve, delay));
}
}
throw lastError;
}
// Configuration validation and API discovery
static async validateApiEndpoint(executeFunctions, spaceUrl, apiName, headers) {
try {
const cleanedUrl = (0, utils_1.cleanUrl)(spaceUrl);
const configUrl = `${cleanedUrl}/gradio_api/config`;
const configResponse = await executeFunctions.helpers.httpRequest({
method: 'GET',
url: configUrl,
headers,
returnFullResponse: true,
ignoreHttpStatusErrors: true,
});
if (configResponse.statusCode !== 200) {
return {
isValid: false,
error: `Unable to access space configuration (HTTP ${configResponse.statusCode})`
};
}
const config = configResponse.body;
// Check if endpoint exists in config
if (config.named_endpoints && config.named_endpoints[apiName]) {
return { isValid: true };
}
// Check unnamed endpoints
if (config.unnamed_endpoints && Array.isArray(config.unnamed_endpoints)) {
const endpointIndex = parseInt(apiName.replace('/api/predict_', ''));
if (!isNaN(endpointIndex) && endpointIndex < config.unnamed_endpoints.length) {
return { isValid: true };
}
}
return {
isValid: false,
error: `API endpoint '${apiName}' not found in space configuration`
};
}
catch (error) {
return {
isValid: false,
error: `Failed to validate API endpoint: ${error instanceof Error ? error.message : String(error)}`
};
}
}
// Official Gradio Client methods implementation with retry logic
static async predict(executeFunctions, spaceUrl, apiName, inputParameters, headers, timeout = 120, retryAttempts = 3) {
return GradioClient.executeWithRetry(async () => {
const cleanedUrl = (0, utils_1.cleanUrl)(spaceUrl);
const apiUrl = `${cleanedUrl}/gradio_api/call${apiName}`;
console.log('Gradio.predict() called:', { apiUrl, inputParameters });
// Step 1: Submit request with retry logic
const initialResponse = await executeFunctions.helpers.httpRequest({
method: 'POST',
url: apiUrl,
headers,
body: { data: inputParameters },
returnFullResponse: true,
});
if (initialResponse.statusCode !== 200) {
throw errors_1.GradioError.fromHttpError(initialResponse.statusCode, initialResponse.statusMessage);
}
const apiResponse = initialResponse.body;
const eventId = apiResponse.event_id;
if (!eventId) {
throw new errors_1.GradioError('No event_id received from Gradio API', types_1.GradioErrorType.API_ENDPOINT_ERROR, undefined, false);
}
// Step 2: Get result via streaming with adaptive timeout
const streamUrl = `${apiUrl}/${eventId}`;
const adaptiveTimeout = Math.max(timeout * 1000, 30000); // Minimum 30s for streaming
console.log(`Streaming from: ${streamUrl} (timeout: ${adaptiveTimeout}ms)`);
const streamResponse = await executeFunctions.helpers.httpRequest({
method: 'GET',
url: streamUrl,
headers,
timeout: adaptiveTimeout,
returnFullResponse: true,
});
if (streamResponse.statusCode !== 200) {
throw errors_1.GradioError.fromHttpError(streamResponse.statusCode, streamResponse.statusMessage);
}
return GradioClient.parseServerSentEvents(streamResponse.body);
}, retryAttempts);
}
static parseServerSentEvents(responseBody) {
const lines = responseBody.split('\n').filter((line) => line.trim());
let eventType = '';
let resultData = null;
let hasCompleteEvent = false;
for (const line of lines) {
console.log('Processing SSE line:', line);
// Parse event type
if (line.startsWith('event: ')) {
eventType = line.slice(7).trim();
console.log('Event type:', eventType);
if (eventType === 'complete') {
hasCompleteEvent = true;
}
continue;
}
// Parse data content
if (line.startsWith('data: ')) {
try {
const dataContent = line.slice(6).trim();
// Skip null data (heartbeat)
if (dataContent === 'null') {
console.log('Skipping heartbeat data');
continue;
}
// Enhanced error detection in streaming data
if (dataContent.includes('Session not found')) {
throw new errors_1.GradioError('Session expired or not found', types_1.GradioErrorType.STREAMING_ERROR, undefined, true);
}
if (dataContent.includes('Rate limit exceeded')) {
throw new errors_1.GradioError('Rate limit exceeded on streaming endpoint', types_1.GradioErrorType.RATE_LIMIT_ERROR, 429, true);
}
const data = JSON.parse(dataContent);
// Handle different event types
if (eventType === 'complete' && Array.isArray(data)) {
console.log('Found complete event with data:', data);
return data;
}
else if (eventType === 'error') {
throw new errors_1.GradioError(`Gradio API Error: ${JSON.stringify(data)}`, types_1.GradioErrorType.STREAMING_ERROR, undefined, false);
}
else if (Array.isArray(data) && hasCompleteEvent) {
// Fallback: array data after seeing complete event
console.log('Found array data after complete event:', data);
resultData = data;
}
}
catch (parseError) {
console.error('Failed to parse SSE data line:', line, 'Error:', parseError);
// Re-throw if it's our custom error
if (parseError instanceof errors_1.GradioError) {
throw parseError;
}
}
}
}
// Return result if we found complete event and data
if (hasCompleteEvent && resultData) {
console.log('Returning result data:', resultData);
return resultData;
}
throw new errors_1.GradioError('No valid result found in server-sent events stream', types_1.GradioErrorType.STREAMING_ERROR, undefined, false);
}
}
exports.GradioClient = GradioClient;