UNPKG

@solspace/strapi-plugin-imagiterate

Version:

Prompt an AI to iterate on uploaded images and save them to Strapi's backend.

218 lines (182 loc) 5.25 kB
import fs from "fs/promises"; import path from "path"; import mime from "mime-types"; import { v4 as uuidv4 } from "uuid"; import { validate as uuidValidate } from "uuid"; import Replicate from "replicate"; const iterate = ({ strapi }) => ({ async refineImage(ctx) { const { documentId, prompt, url, token } = ctx.request.body; // Do we have a document id? if (!documentId) { return { error: { status: 400, name: "MissingDocumentId", message: "Please provide a document id.", }, }; } // Do we have a prompt? if (!prompt) { return { error: { status: 400, name: "MissingPrompt", message: "Please provide a prompt to guide the AI in processing your image.", }, }; } // Do we have an image url? if (!url) { return { error: { status: 400, name: "MissingImageUrl", message: "Please provide an image url for the AI to process.", }, }; } // Do we have a token? This helps us secure each api call once an image iteration is started by a user if (!token) { return { error: { status: 400, name: "MissingToken", message: "Please provide a valid token.", }, }; } if (!uuidValidate(token)) { return { error: { status: 400, name: "InvalidTokenFormat", message: "Please provide a token in a valid format.", }, }; } // Query let imageDocument = await strapi .documents("plugin::imagiterate.imagiterate") .findOne({ documentId, populate: ["images"], }); if (imageDocument.error) return imageDocument; if (token != imageDocument.token) { return { error: { status: 400, name: "InvalidToken", message: "Please provide a valid token.", }, }; } // Instantiate Replicate const { replicate, model } = getReplicate(); if (replicate.error) return replicate; // Now send it to Replicate for processing. const input = { input_image: url, prompt, }; const output = await replicate.run(model, { input }); // Error? if (output.error) return output; // Return image url const blob = await output.blob(); // Upload const newUploadedFile = await uploadBlob(blob); if (newUploadedFile.error) return newUploadedFile; // Merge new image into images array const mergedImages = [ ...(imageDocument.images || []).map((img) => img.id), newUploadedFile[0].id, ]; // Query const update = await strapi .documents("plugin::imagiterate.imagiterate") .update({ documentId, data: { images: mergedImages, }, }); if (update.error) return update; const replicateUrl = await output.url(); // Normalize to a plain string const resultUrl = typeof replicateUrl === "string" ? replicateUrl : replicateUrl.href; return { ...imageDocument, url: resultUrl, alt: "Alt text", prompt }; }, }); // Service loader const getUploadService = () => { return strapi.plugin("upload").service("upload"); }; // Replicate api loader for clean error handling const getReplicate = () => { const token = strapi.plugin("imagiterate").config("replicateApiToken") || null; const model = strapi.plugin("imagiterate").config("replicateAiModel") || null; if (!token) { return { error: { status: 400, name: "MissingReplicateToken", message: "Please provide a valid API token for the Replicate AI service.", }, }; } if (!model) { return { error: { status: 400, name: "MissingReplicateApiModel", message: "Please provide a valid model for the Replicate AI service.", }, }; } const replicate = new Replicate({ auth: token, }); return { replicate, model }; }; async function uploadImage(file) { // Instantiate our service const uploadService = getUploadService(); const uploadedFile = await uploadService.upload({ data: {}, files: file, }); return uploadedFile; } async function uploadBlob(blob) { // Convert blob → buffer const buffer = Buffer.from(await blob.arrayBuffer()); // Resolve file extension from MIME type const mimeType = blob.type || "application/octet-stream"; const ext = mime.extension(mimeType) || "jpg"; // Write buffer to a temp file const strapiPath = strapi.config.get("server.dirs.public"); const fileName = `/uploads/replicate-${Date.now()}.${ext}`; const filePath = path.join(strapiPath, fileName); await fs.writeFile(filePath, buffer); // Build the file object Strapi expects const stats = await fs.stat(filePath); const file = { filepath: filePath, originalFilename: "replicate-ai-file", mimetype: mimeType, size: stats.size, }; // Call Strapi’s upload service const uploadedFile = await uploadImage(file); // Clean up temp file await fs.unlink(filePath); return uploadedFile; } export default iterate;