generate_and_upload_image
Generate an AI image from a text prompt and upload it directly to your Printify account for use in product designs. Specify file name and optional parameters like size, format, and model.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| prompt | Yes | Text prompt for image generation | |
| fileName | Yes | File name for the uploaded image | |
| model | No | Optional: Override the default model. Use get_defaults to see available models | |
| width | No | Image width in pixels | |
| height | No | Image height in pixels | |
| aspectRatio | No | Aspect ratio (e.g., '16:9', '4:3', '1:1'). If provided, overrides width and height | |
| outputFormat | No | Output format | png |
| safetyTolerance | No | Safety tolerance (0-6) | |
| seed | No | Random seed for reproducible generation | |
| numInferenceSteps | No | Number of inference steps | |
| guidanceScale | No | Guidance scale | |
| negativePrompt | No | Negative prompt | low quality, bad quality, sketches |
| promptUpsampling | No | Enable prompt upsampling (Flux 1.1 Pro only) | |
| outputQuality | No | Output quality 1-100 (Flux 1.1 Pro only) | |
| raw | No | Generate less processed, more natural-looking images (Flux 1.1 Pro Ultra only) | |
| imagePromptStrength | No | Image prompt strength 0-1 (Flux 1.1 Pro Ultra only) |
Implementation Reference
- src/index.ts:859-1244 (registration)Registration of the 'generate_and_upload_image' tool on the MCP server via server.tool(), including the full Zod schema for inputs and the async handler that orchestrates image generation via Replicate followed by upload to Printify.
// Generate and upload image tool - combines Replicate image generation with Printify upload server.tool( "generate_and_upload_image", { prompt: z.string().describe("Text prompt for image generation"), fileName: z.string().describe("File name for the uploaded image"), // Optional model override model: z.string().optional() .describe("Optional: Override the default model. Use get_defaults to see available models"), // Common parameters for both models width: z.number().optional().default(1024).describe("Image width in pixels"), height: z.number().optional().default(1024).describe("Image height in pixels"), aspectRatio: z.string().optional().describe("Aspect ratio (e.g., '16:9', '4:3', '1:1'). If provided, overrides width and height"), outputFormat: z.enum(["jpeg", "png", "webp"]).optional().default("png").describe("Output format"), safetyTolerance: z.number().optional().default(2).describe("Safety tolerance (0-6)"), seed: z.number().optional().describe("Random seed for reproducible generation"), numInferenceSteps: z.number().optional().default(25).describe("Number of inference steps"), guidanceScale: z.number().optional().default(7.5).describe("Guidance scale"), negativePrompt: z.string().optional().default("low quality, bad quality, sketches").describe("Negative prompt"), // Flux 1.1 Pro specific parameters promptUpsampling: z.boolean().optional() .describe("Enable prompt upsampling (Flux 1.1 Pro only)"), outputQuality: z.number().optional() .describe("Output quality 1-100 (Flux 1.1 Pro only)"), // Flux 1.1 Pro Ultra specific parameters raw: z.boolean().optional() .describe("Generate less processed, more natural-looking images (Flux 1.1 Pro Ultra only)"), imagePromptStrength: z.number().optional() .describe("Image prompt strength 0-1 (Flux 1.1 Pro Ultra only)") }, async ({ prompt, fileName, model, width, height, aspectRatio, outputFormat, safetyTolerance, seed, numInferenceSteps, guidanceScale, negativePrompt, promptUpsampling, outputQuality, raw, imagePromptStrength }): Promise<{ content: any[], isError?: boolean }> => { // Import the services const { generateImage } = await import('./services/image-generator.js'); const { formatSuccessResponse } = await import('./utils/error-handler.js'); // Check if clients are initialized if (!replicateClient) { return { content: [{ type: "text", text: "Replicate API client is not initialized. The REPLICATE_API_TOKEN environment variable may not be set." }], isError: true }; } if (!printifyClient) { return { content: [{ type: "text", text: "Printify API client is not initialized. The PRINTIFY_API_KEY environment variable may not be set." }], isError: true }; } // Check if we're using the Ultra model which requires ImgBB // Determine which model to use (user-specified or default) const modelToUse = model || replicateClient.getDefaultModel(); // Check if ImgBB API key is set when using Ultra model if (modelToUse.includes('flux-1.1-pro-ultra') && (!process.env.IMGBB_API_KEY || process.env.IMGBB_API_KEY === 'your-imgbb-api-key')) { return { content: [{ type: "text", text: `ERROR: The Flux 1.1 Pro Ultra model generates high-resolution images that are too large for direct base64 upload.\n\n` + `You MUST set the IMGBB_API_KEY environment variable when using this model.\n\n` + `Get a free API key from https://api.imgbb.com/ and add it to your .env file:\n` + `IMGBB_API_KEY=your_api_key_here` }], isError: true }; } // Check if a shop is selected const currentShop = printifyClient.getCurrentShop(); if (!currentShop) { return { content: [{ type: "text", text: "No shop is currently selected. Use the list_shops and switch_shop tools to select a shop." }], isError: true }; } console.log(`Starting generate_and_upload_image with prompt: ${prompt}`); // Get default parameters first const defaults = replicateClient.getAllDefaults(); // STEP 1: Generate the image with Replicate and process with Sharp // Start with defaults, then override with parameters from the tool call const generationResult = await generateImage( replicateClient, prompt, fileName, { // Start with defaults model: defaults.model, width: defaults.width, height: defaults.height, aspectRatio: defaults.aspectRatio, outputFormat: defaults.outputFormat, safetyTolerance: defaults.safetyTolerance, numInferenceSteps: defaults.numInferenceSteps, guidanceScale: defaults.guidanceScale, negativePrompt: defaults.negativePrompt, raw: defaults.raw, promptUpsampling: defaults.promptUpsampling, outputQuality: defaults.outputQuality, // Override with parameters from the tool call (if provided) ...(model !== undefined && { model }), ...(width !== undefined && { width }), ...(height !== undefined && { height }), ...(aspectRatio !== undefined && { aspectRatio }), ...(outputFormat !== undefined && { outputFormat }), ...(safetyTolerance !== undefined && { safetyTolerance }), ...(seed !== undefined && { seed }), ...(numInferenceSteps !== undefined && { numInferenceSteps }), ...(guidanceScale !== undefined && { guidanceScale }), ...(negativePrompt !== undefined && { negativePrompt }), ...(promptUpsampling !== undefined && { promptUpsampling }), ...(outputQuality !== undefined && { outputQuality }), ...(raw !== undefined && { raw }), ...(imagePromptStrength !== undefined && { imagePromptStrength }) } ); // If image generation failed, return the error if (!generationResult.success) { return generationResult.errorResponse as { content: any[], isError: boolean }; } const imageBuffer = generationResult.buffer; const mimeType = generationResult.mimeType; const finalFileName = generationResult.fileName; const usingModel = generationResult.model; // Make sure we have valid image data if (!imageBuffer) { return { content: [{ type: "text", text: "Failed to get valid image data from the image generator." }], isError: true }; } // STEP 2: Upload the processed image to Printify console.log(`Uploading processed image to Printify`); console.log(`Image buffer size: ${imageBuffer.length} bytes`); console.log(`MIME type: ${mimeType}`); console.log(`File name: ${finalFileName}`); // Prepare for upload to Printify const uploadDetails = [ `Preparing to upload image to Printify:`, `- File name: ${finalFileName}`, `- Image buffer size: ${imageBuffer?.length || 0} bytes`, `- MIME type: ${mimeType}`, `- Model used: ${usingModel}` ].join('\n'); // Save the base64 data to a debug file for inspection try { const fs = await import('fs'); const path = await import('path'); // Create a debug directory if it doesn't exist const debugDir = path.join(process.cwd(), 'debug'); if (!fs.existsSync(debugDir)) { fs.mkdirSync(debugDir, { recursive: true }); } // Save the base64 data to a file for debugging const debugFilePath = path.join(debugDir, `debug_${Date.now()}_${finalFileName}`); // Save buffer directly to debug file if (imageBuffer) { fs.writeFileSync(debugFilePath, imageBuffer); console.log(`Saved image data to debug file: ${debugFilePath}`); console.log(`Debug file size: ${imageBuffer.length} bytes`); } else { console.error('No image data to save for debugging'); } } catch (debugError) { console.error('Error saving debug file:', debugError); } // Validate input data if (!imageBuffer) { return { content: [{ type: "text", text: "Error: No image data available for upload" }], isError: true }; } if (!finalFileName) { return { content: [{ type: "text", text: "Error: No filename available for upload" }], isError: true }; } // STEP 1: Import required modules let axios; let FormData; try { axios = (await import('axios')).default; FormData = (await import('form-data')).default; } catch (importError: any) { return { content: [{ type: "text", text: `Error importing required modules: ${importError.message || String(importError)}` }], isError: true }; } // STEP 2: Prepare image for upload (either via ImgBB or direct base64) let imageUrl; let uploadMethod = "direct"; // Check if we're using the Ultra model const isUsingUltraModel = usingModel.includes('flux-1.1-pro-ultra'); // Check if ImgBB API key is set const imgbbApiKey = process.env.IMGBB_API_KEY; if (imgbbApiKey && imgbbApiKey !== 'your-imgbb-api-key') { // If ImgBB API key is set, use ImgBB to get a URL try { // Create form data for ImgBB const formData = new FormData(); // Convert buffer to base64 for ImgBB upload const base64Data = imageBuffer.toString('base64'); formData.append('image', base64Data); // Upload to ImgBB with the key as a query parameter const imgbbResponse = await axios.post( `https://api.imgbb.com/1/upload?key=${imgbbApiKey}`, formData ); // Get the image URL from ImgBB response imageUrl = imgbbResponse.data.data.url; uploadMethod = "imgbb"; // Log success console.log(`Successfully uploaded image to ImgBB. URL: ${imageUrl}`); } catch (imgbbError: any) { // Only fall back to direct upload if not using Ultra model if (isUsingUltraModel) { return { content: [{ type: "text", text: `Error uploading to ImgBB: ${imgbbError.message || String(imgbbError)}\n\n` + `When using the Ultra model, ImgBB upload is required and cannot be bypassed.\n\n` + `Response data: ${JSON.stringify(imgbbError.response?.data || {}, null, 2)}` }], isError: true }; } console.log(`Error uploading to ImgBB: ${imgbbError.message || String(imgbbError)}. Falling back to direct base64 upload.`); // Fall back to direct base64 upload for non-Ultra models uploadMethod = "direct"; } } else if (!isUsingUltraModel) { console.log("No ImgBB API key found. Using direct base64 upload."); } // STEP 4: Import Printify SDK let Printify; try { Printify = (await import('printify-sdk-js')).default; } catch (importError: any) { return { content: [{ type: "text", text: `Error importing Printify SDK: ${importError.message || String(importError)}\n\n` + `ImgBB URL: ${imageUrl}` }], isError: true }; } // STEP 5: Create Printify client let printifySDK; try { printifySDK = new Printify({ accessToken: process.env.PRINTIFY_API_KEY || '', shopId: printifyClient ? printifyClient.getCurrentShopId() || undefined : undefined }); // Log client creation console.log(`Created Printify client with shop ID: ${printifyClient?.getCurrentShopId() || 'undefined'}`); } catch (clientError: any) { return { content: [{ type: "text", text: `Error creating Printify client: ${clientError.message || String(clientError)}\n\n` + `ImgBB URL: ${imageUrl}` }], isError: true }; } // STEP 6: Upload the image to Printify let image; try { if (uploadMethod === "imgbb" && imageUrl) { // Upload using the URL from ImgBB image = await printifySDK.uploads.uploadImage({ file_name: finalFileName, url: imageUrl }); console.log(`Successfully uploaded image to Printify using ImgBB URL. Image ID: ${image.id}`); } else { // Direct base64 upload // Convert buffer to base64 for Printify direct upload const base64Data = imageBuffer.toString('base64'); image = await printifySDK.uploads.uploadImage({ file_name: finalFileName, contents: base64Data }); console.log(`Successfully uploaded image to Printify using direct base64. Image ID: ${image.id}`); } } catch (uploadError: any) { return { content: [{ type: "text", text: `Error uploading to Printify: ${uploadError.message || String(uploadError)}\n\n` + `Upload method: ${uploadMethod}${imageUrl ? `\nImgBB URL: ${imageUrl}` : ''}\n\n` + `Response data: ${JSON.stringify(uploadError.response?.data || {}, null, 2)}` }], isError: true }; } // STEP 7: Return success response const response = formatSuccessResponse( 'Image Generated and Uploaded Successfully', { Prompt: prompt, Model: usingModel.split('/')[1], 'Image ID': image.id, 'File Name': image.file_name, Dimensions: `${image.width}x${image.height}`, 'Preview URL': image.preview_url, 'Upload Method': uploadMethod === "imgbb" ? "ImgBB URL" : "Direct base64", ...(imageUrl ? { 'ImgBB URL': imageUrl } : {}), 'Upload Details': uploadDetails }, `You can now use this image ID (${image.id}) when creating a product.\n\n` + `**Example:**\n` + `\`\`\`json\n` + `"print_areas": {\n` + ` "front": { "position": "front", "imageId": "${image.id}" }\n` + `}\n` + `\`\`\`` ) as { content: any[], isError?: boolean }; return response; } ); - src/index.ts:862-892 (schema)Zod validation schema defining all input parameters for the generate_and_upload_image tool, including required prompt/fileName, common parameters with defaults, and model-specific parameters for Flux models.
{ prompt: z.string().describe("Text prompt for image generation"), fileName: z.string().describe("File name for the uploaded image"), // Optional model override model: z.string().optional() .describe("Optional: Override the default model. Use get_defaults to see available models"), // Common parameters for both models width: z.number().optional().default(1024).describe("Image width in pixels"), height: z.number().optional().default(1024).describe("Image height in pixels"), aspectRatio: z.string().optional().describe("Aspect ratio (e.g., '16:9', '4:3', '1:1'). If provided, overrides width and height"), outputFormat: z.enum(["jpeg", "png", "webp"]).optional().default("png").describe("Output format"), safetyTolerance: z.number().optional().default(2).describe("Safety tolerance (0-6)"), seed: z.number().optional().describe("Random seed for reproducible generation"), numInferenceSteps: z.number().optional().default(25).describe("Number of inference steps"), guidanceScale: z.number().optional().default(7.5).describe("Guidance scale"), negativePrompt: z.string().optional().default("low quality, bad quality, sketches").describe("Negative prompt"), // Flux 1.1 Pro specific parameters promptUpsampling: z.boolean().optional() .describe("Enable prompt upsampling (Flux 1.1 Pro only)"), outputQuality: z.number().optional() .describe("Output quality 1-100 (Flux 1.1 Pro only)"), // Flux 1.1 Pro Ultra specific parameters raw: z.boolean().optional() .describe("Generate less processed, more natural-looking images (Flux 1.1 Pro Ultra only)"), imagePromptStrength: z.number().optional() .describe("Image prompt strength 0-1 (Flux 1.1 Pro Ultra only)") }, - src/index.ts:893-1244 (handler)Handler function for generate_and_upload_image. Generates an image via Replicate (using the image-generator service), then uploads the processed buffer to Printify (either via ImgBB intermediate URL or direct base64 upload).
async ({ prompt, fileName, model, width, height, aspectRatio, outputFormat, safetyTolerance, seed, numInferenceSteps, guidanceScale, negativePrompt, promptUpsampling, outputQuality, raw, imagePromptStrength }): Promise<{ content: any[], isError?: boolean }> => { // Import the services const { generateImage } = await import('./services/image-generator.js'); const { formatSuccessResponse } = await import('./utils/error-handler.js'); // Check if clients are initialized if (!replicateClient) { return { content: [{ type: "text", text: "Replicate API client is not initialized. The REPLICATE_API_TOKEN environment variable may not be set." }], isError: true }; } if (!printifyClient) { return { content: [{ type: "text", text: "Printify API client is not initialized. The PRINTIFY_API_KEY environment variable may not be set." }], isError: true }; } // Check if we're using the Ultra model which requires ImgBB // Determine which model to use (user-specified or default) const modelToUse = model || replicateClient.getDefaultModel(); // Check if ImgBB API key is set when using Ultra model if (modelToUse.includes('flux-1.1-pro-ultra') && (!process.env.IMGBB_API_KEY || process.env.IMGBB_API_KEY === 'your-imgbb-api-key')) { return { content: [{ type: "text", text: `ERROR: The Flux 1.1 Pro Ultra model generates high-resolution images that are too large for direct base64 upload.\n\n` + `You MUST set the IMGBB_API_KEY environment variable when using this model.\n\n` + `Get a free API key from https://api.imgbb.com/ and add it to your .env file:\n` + `IMGBB_API_KEY=your_api_key_here` }], isError: true }; } // Check if a shop is selected const currentShop = printifyClient.getCurrentShop(); if (!currentShop) { return { content: [{ type: "text", text: "No shop is currently selected. Use the list_shops and switch_shop tools to select a shop." }], isError: true }; } console.log(`Starting generate_and_upload_image with prompt: ${prompt}`); // Get default parameters first const defaults = replicateClient.getAllDefaults(); // STEP 1: Generate the image with Replicate and process with Sharp // Start with defaults, then override with parameters from the tool call const generationResult = await generateImage( replicateClient, prompt, fileName, { // Start with defaults model: defaults.model, width: defaults.width, height: defaults.height, aspectRatio: defaults.aspectRatio, outputFormat: defaults.outputFormat, safetyTolerance: defaults.safetyTolerance, numInferenceSteps: defaults.numInferenceSteps, guidanceScale: defaults.guidanceScale, negativePrompt: defaults.negativePrompt, raw: defaults.raw, promptUpsampling: defaults.promptUpsampling, outputQuality: defaults.outputQuality, // Override with parameters from the tool call (if provided) ...(model !== undefined && { model }), ...(width !== undefined && { width }), ...(height !== undefined && { height }), ...(aspectRatio !== undefined && { aspectRatio }), ...(outputFormat !== undefined && { outputFormat }), ...(safetyTolerance !== undefined && { safetyTolerance }), ...(seed !== undefined && { seed }), ...(numInferenceSteps !== undefined && { numInferenceSteps }), ...(guidanceScale !== undefined && { guidanceScale }), ...(negativePrompt !== undefined && { negativePrompt }), ...(promptUpsampling !== undefined && { promptUpsampling }), ...(outputQuality !== undefined && { outputQuality }), ...(raw !== undefined && { raw }), ...(imagePromptStrength !== undefined && { imagePromptStrength }) } ); // If image generation failed, return the error if (!generationResult.success) { return generationResult.errorResponse as { content: any[], isError: boolean }; } const imageBuffer = generationResult.buffer; const mimeType = generationResult.mimeType; const finalFileName = generationResult.fileName; const usingModel = generationResult.model; // Make sure we have valid image data if (!imageBuffer) { return { content: [{ type: "text", text: "Failed to get valid image data from the image generator." }], isError: true }; } // STEP 2: Upload the processed image to Printify console.log(`Uploading processed image to Printify`); console.log(`Image buffer size: ${imageBuffer.length} bytes`); console.log(`MIME type: ${mimeType}`); console.log(`File name: ${finalFileName}`); // Prepare for upload to Printify const uploadDetails = [ `Preparing to upload image to Printify:`, `- File name: ${finalFileName}`, `- Image buffer size: ${imageBuffer?.length || 0} bytes`, `- MIME type: ${mimeType}`, `- Model used: ${usingModel}` ].join('\n'); // Save the base64 data to a debug file for inspection try { const fs = await import('fs'); const path = await import('path'); // Create a debug directory if it doesn't exist const debugDir = path.join(process.cwd(), 'debug'); if (!fs.existsSync(debugDir)) { fs.mkdirSync(debugDir, { recursive: true }); } // Save the base64 data to a file for debugging const debugFilePath = path.join(debugDir, `debug_${Date.now()}_${finalFileName}`); // Save buffer directly to debug file if (imageBuffer) { fs.writeFileSync(debugFilePath, imageBuffer); console.log(`Saved image data to debug file: ${debugFilePath}`); console.log(`Debug file size: ${imageBuffer.length} bytes`); } else { console.error('No image data to save for debugging'); } } catch (debugError) { console.error('Error saving debug file:', debugError); } // Validate input data if (!imageBuffer) { return { content: [{ type: "text", text: "Error: No image data available for upload" }], isError: true }; } if (!finalFileName) { return { content: [{ type: "text", text: "Error: No filename available for upload" }], isError: true }; } // STEP 1: Import required modules let axios; let FormData; try { axios = (await import('axios')).default; FormData = (await import('form-data')).default; } catch (importError: any) { return { content: [{ type: "text", text: `Error importing required modules: ${importError.message || String(importError)}` }], isError: true }; } // STEP 2: Prepare image for upload (either via ImgBB or direct base64) let imageUrl; let uploadMethod = "direct"; // Check if we're using the Ultra model const isUsingUltraModel = usingModel.includes('flux-1.1-pro-ultra'); // Check if ImgBB API key is set const imgbbApiKey = process.env.IMGBB_API_KEY; if (imgbbApiKey && imgbbApiKey !== 'your-imgbb-api-key') { // If ImgBB API key is set, use ImgBB to get a URL try { // Create form data for ImgBB const formData = new FormData(); // Convert buffer to base64 for ImgBB upload const base64Data = imageBuffer.toString('base64'); formData.append('image', base64Data); // Upload to ImgBB with the key as a query parameter const imgbbResponse = await axios.post( `https://api.imgbb.com/1/upload?key=${imgbbApiKey}`, formData ); // Get the image URL from ImgBB response imageUrl = imgbbResponse.data.data.url; uploadMethod = "imgbb"; // Log success console.log(`Successfully uploaded image to ImgBB. URL: ${imageUrl}`); } catch (imgbbError: any) { // Only fall back to direct upload if not using Ultra model if (isUsingUltraModel) { return { content: [{ type: "text", text: `Error uploading to ImgBB: ${imgbbError.message || String(imgbbError)}\n\n` + `When using the Ultra model, ImgBB upload is required and cannot be bypassed.\n\n` + `Response data: ${JSON.stringify(imgbbError.response?.data || {}, null, 2)}` }], isError: true }; } console.log(`Error uploading to ImgBB: ${imgbbError.message || String(imgbbError)}. Falling back to direct base64 upload.`); // Fall back to direct base64 upload for non-Ultra models uploadMethod = "direct"; } } else if (!isUsingUltraModel) { console.log("No ImgBB API key found. Using direct base64 upload."); } // STEP 4: Import Printify SDK let Printify; try { Printify = (await import('printify-sdk-js')).default; } catch (importError: any) { return { content: [{ type: "text", text: `Error importing Printify SDK: ${importError.message || String(importError)}\n\n` + `ImgBB URL: ${imageUrl}` }], isError: true }; } // STEP 5: Create Printify client let printifySDK; try { printifySDK = new Printify({ accessToken: process.env.PRINTIFY_API_KEY || '', shopId: printifyClient ? printifyClient.getCurrentShopId() || undefined : undefined }); // Log client creation console.log(`Created Printify client with shop ID: ${printifyClient?.getCurrentShopId() || 'undefined'}`); } catch (clientError: any) { return { content: [{ type: "text", text: `Error creating Printify client: ${clientError.message || String(clientError)}\n\n` + `ImgBB URL: ${imageUrl}` }], isError: true }; } // STEP 6: Upload the image to Printify let image; try { if (uploadMethod === "imgbb" && imageUrl) { // Upload using the URL from ImgBB image = await printifySDK.uploads.uploadImage({ file_name: finalFileName, url: imageUrl }); console.log(`Successfully uploaded image to Printify using ImgBB URL. Image ID: ${image.id}`); } else { // Direct base64 upload // Convert buffer to base64 for Printify direct upload const base64Data = imageBuffer.toString('base64'); image = await printifySDK.uploads.uploadImage({ file_name: finalFileName, contents: base64Data }); console.log(`Successfully uploaded image to Printify using direct base64. Image ID: ${image.id}`); } } catch (uploadError: any) { return { content: [{ type: "text", text: `Error uploading to Printify: ${uploadError.message || String(uploadError)}\n\n` + `Upload method: ${uploadMethod}${imageUrl ? `\nImgBB URL: ${imageUrl}` : ''}\n\n` + `Response data: ${JSON.stringify(uploadError.response?.data || {}, null, 2)}` }], isError: true }; } // STEP 7: Return success response const response = formatSuccessResponse( 'Image Generated and Uploaded Successfully', { Prompt: prompt, Model: usingModel.split('/')[1], 'Image ID': image.id, 'File Name': image.file_name, Dimensions: `${image.width}x${image.height}`, 'Preview URL': image.preview_url, 'Upload Method': uploadMethod === "imgbb" ? "ImgBB URL" : "Direct base64", ...(imageUrl ? { 'ImgBB URL': imageUrl } : {}), 'Upload Details': uploadDetails }, `You can now use this image ID (${image.id}) when creating a product.\n\n` + `**Example:**\n` + `\`\`\`json\n` + `"print_areas": {\n` + ` "front": { "position": "front", "imageId": "${image.id}" }\n` + `}\n` + `\`\`\`` ) as { content: any[], isError?: boolean }; return response; } ); - The generateImage helper function called by the handler. It generates an image via Replicate, processes it with Sharp for format conversion, and returns a buffer along with metadata.
export async function generateImage( replicateClient: ReplicateClient, prompt: string, fileName: string, options: any = {} ) { // No need to track files anymore since we're keeping everything in memory try { // Prepare options with proper naming for the API const modelOptions: any = {}; // Set aspect ratio or dimensions if (options.aspectRatio) { modelOptions.aspectRatio = options.aspectRatio; } else { // If no aspect ratio is provided, use width and height // These will be overridden by the defaults in the DefaultsManager if not provided modelOptions.width = options.width || 1024; modelOptions.height = options.height || 1024; } // Add common parameters if (options.numInferenceSteps) modelOptions.numInferenceSteps = options.numInferenceSteps; if (options.guidanceScale) modelOptions.guidanceScale = options.guidanceScale; if (options.negativePrompt) modelOptions.negativePrompt = options.negativePrompt; if (options.seed !== undefined) modelOptions.seed = options.seed; // Always set outputFormat, defaulting to png unless explicitly specified modelOptions.outputFormat = options.outputFormat || "png"; if (options.safetyTolerance !== undefined) modelOptions.safetyTolerance = options.safetyTolerance; // Add model-specific parameters if provided if (options.promptUpsampling !== undefined) modelOptions.promptUpsampling = options.promptUpsampling; if (options.outputQuality !== undefined) modelOptions.outputQuality = options.outputQuality; if (options.raw !== undefined) modelOptions.raw = options.raw; if (options.imagePromptStrength !== undefined) modelOptions.imagePromptStrength = options.imagePromptStrength; // Add model override if provided if (options.model) modelOptions.model = options.model; // Get the current default model for informational purposes const defaultModel = replicateClient.getDefaultModel(); const usingModel = options.model || defaultModel; console.log(`Using model: ${usingModel} (${options.model ? 'override' : 'default'})`); console.log(`Prompt: ${prompt}`); // STEP 1: Generate the image with Replicate console.log('Generating image with Replicate...'); const imageBuffer = await replicateClient.generateImage(prompt, modelOptions); console.log(`Image generated successfully, buffer size: ${imageBuffer.length} bytes`); // STEP 2: Process the image with Sharp console.log('Processing image with Sharp...'); // Get the output format from options (already defaulted to png earlier) const outputFormat = modelOptions.outputFormat; let mimeType: string; if (outputFormat === 'jpeg' || outputFormat === 'jpg') { mimeType = 'image/jpeg'; } else if (outputFormat === 'webp') { mimeType = 'image/webp'; } else { // Default to PNG mimeType = 'image/png'; } // Process with Sharp and get buffer directly let sharpInstance = sharp(imageBuffer); // Apply format-specific options if (outputFormat === 'png') { sharpInstance = sharpInstance.png({ quality: 100 }); } else if (outputFormat === 'jpeg' || outputFormat === 'jpg') { sharpInstance = sharpInstance.jpeg({ quality: 100 }); } else if (outputFormat === 'webp') { sharpInstance = sharpInstance.webp({ quality: 100 }); } // Get the processed image as a buffer const processedBuffer = await sharpInstance.toBuffer(); console.log(`Image processed successfully, buffer size: ${processedBuffer.length} bytes`); // Determine the final filename with extension const fileExtension = outputFormat === 'jpeg' ? 'jpg' : outputFormat; const finalFileName = fileName.endsWith(`.${fileExtension}`) ? fileName : `${fileName}.${fileExtension}`; // No need to clean up files since we're keeping everything in memory // Get dimensions from the Sharp metadata const metadata = await sharpInstance.metadata(); const dimensions = `${metadata.width}x${metadata.height}`; return { success: true, buffer: processedBuffer, mimeType, fileName: finalFileName, model: usingModel, dimensions }; } catch (error: any) { console.error('Error generating or processing image:', error); // No need to clean up files since we're keeping everything in memory // Get the current default model for informational purposes const defaultModel = replicateClient.getDefaultModel(); const usingModel = options.model || defaultModel; // Determine which step failed const errorStep = error.message.includes('Sharp') ? 'Image Processing' : 'Image Generation'; return { success: false, error, errorResponse: formatErrorResponse( error, errorStep, { Prompt: prompt, Model: usingModel.split('/')[1], Step: errorStep }, [ 'Check that your REPLICATE_API_TOKEN is valid', 'Try a different model using set-model', 'Try a more descriptive prompt', 'Try a different aspect ratio', ...(errorStep === 'Image Processing' ? [ 'Make sure Sharp is properly installed' ] : []) ] ) }; } } - src/utils/error-handler.ts:80-229 (helper)The formatSuccessResponse helper used by the handler to produce the final success output containing image ID, file name, preview URL, and instructions for using the image in product creation.
export function formatSuccessResponse( title: string, data: Record<string, any> = {}, additionalText: string = '' ) { let text = `✅ **${title}**\n\n`; // Add data information Object.entries(data).forEach(([key, value]) => { if (typeof value === 'string' && value.includes('"')) { text += `- **${key}**: ${value}\n`; } else if (typeof value === 'object') { text += `- **${key}**: ${JSON.stringify(value)}\n`; } else { text += `- **${key}**: "${value}"\n`; } }); // Add additional text if provided if (additionalText) { text += `\n${additionalText}`; } return { content: [{ type: "text", text }] }; }