// routes/api/ai-images/[id] import { define } from "../../../utils.ts"; import { ImageFile } from "../../../utils/types.ts"; import { OpenAI } from "@openai"; import { getLocalBlobUrl, uploadToLocalBlobStorage, } from "../../../utils/blobStorage.ts"; import { consumeCreateToken, isCreateTokenValid, } from "../../../utils/tokenEvaluator.ts"; import { createCorsPreflightResponse, getIslandCorsHeaders, isIslandFetchRequestAllowed, withCors, } from "../../../utils/cors.ts"; // Initialize OpenAI client const openai = new OpenAI({ apiKey: "sk-proj-i17SN2KSk10UAORJz3zfT3BlbkFJVxk8D561NlXMN4SZ4IUj", }); export const handler = define.handlers({ async OPTIONS(ctx) { const url = new URL(ctx.req.url); if (!isIslandFetchRequestAllowed(url)) { return new Response("Not Found", { status: 404 }); } const corsHeaders = getIslandCorsHeaders( ctx.req.headers.get("Origin"), ctx.req.headers.get("Access-Control-Request-Headers"), ); return createCorsPreflightResponse(ctx.req, corsHeaders); }, async GET(ctx) { const url = new URL(ctx.req.url); if (!isIslandFetchRequestAllowed(url)) { return new Response("Not Found", { status: 404 }); } const corsHeaders = getIslandCorsHeaders( ctx.req.headers.get("Origin"), ctx.req.headers.get("Access-Control-Request-Headers"), ); const respond = (body: BodyInit | null, init: ResponseInit) => withCors(new Response(body, init), corsHeaders); const tokenIdParam = url.searchParams.get("create_token_id"); const stateIdParam = url.searchParams.get("state_id"); if (!tokenIdParam || !stateIdParam) { return respond( JSON.stringify({ error: "Authentication or create token required" }), { status: 401, headers: { "Content-Type": "application/json" } }, ); } const token = await isCreateTokenValid( tokenIdParam, stateIdParam, ); if (!token) { return respond( JSON.stringify({ error: "Invalid or expired create token" }), { status: 401, headers: { "Content-Type": "application/json" } }, ); } const id = decodeURIComponent(ctx.params.id); if (!id || id.length === 0) { return respond("ID is required", { status: 400 }); } try { const response = await openai.images.generate({ model: "gpt-image-1", prompt: 'Not in writing. Generate the photo that is a person or thing with action of: "' + id + '"', n: 1, quality: "low", size: "1024x1024", } as any); //console.log("response",response.data); const b64 = response?.data?.[0]?.b64_json ?? ""; if (b64 && typeof b64 === "string" && b64.length > 0) { //const imageResponse = await fetch(imageUrl); //const blob = await imageResponse.blob(); const baseName = sanitizeImageFileName(id); const imageBlob = base64ToBlob(b64, "image/png"); const uploadResult = await uploadToLocalBlobStorage( baseName, imageBlob, "image", { useTimestamp: true, defaultExtension: ".png", }, ); if (!uploadResult.success) { throw new Error(`Failed to save AI image: ${uploadResult.error}`); } const finalFileName = uploadResult.fileName || baseName; const signedUrl = await getLocalBlobUrl( finalFileName, "image", token.user_id, token.state_id, ); const imageData: ImageFile = { image_text: id, image_file: id, image_data: signedUrl, }; if (tokenIdParam) { await consumeCreateToken(tokenIdParam); } return respond(JSON.stringify(imageData), { status: 200, headers: { "Content-Type": "application/json", "Cache-Control": "no-cache, no-store, must-revalidate", "Pragma": "no-cache", "Expires": "0", }, }); } else { return respond( JSON.stringify({ error: "No image data returned from image generation", }), { status: 502, headers: { "Content-Type": "application/json", }, }, ); } } catch (error) { console.error("Error generating image:", error); } //console.error("Error fetching quiz:", error); return respond( JSON.stringify({ error: "Failed to fetch image data" }), { status: 500, headers: { "Content-Type": "application/json", }, }, ); }, }); function base64ToBlob(base64: string, mimeType: string): Blob { const binaryString = atob(base64); const bytes = new Uint8Array(binaryString.length); for (let i = 0; i < binaryString.length; i++) { bytes[i] = binaryString.charCodeAt(i); } return new Blob([bytes], { type: mimeType }); } function sanitizeImageFileName(fileName: string) { if (!fileName) { return "image"; } // // Step 1: Ensure the file name ends with .png // if (!fileName.toLowerCase().endsWith('.png')) { // fileName += '.png'; // } // // Step 2: Remove the path if present (keep only the file name) // fileName = fileName.split('/').pop().split('\\').pop(); // Step 3: Replace spaces and other problematic characters fileName = fileName .replace(/[^a-zA-Z0-9_.-]/g, "_") // Replace invalid chars with underscore .replace(/_{2,}/g, "_") // Replace multiple underscores with single .replace(/^[._-]+|[._-]+$/g, "") // Remove leading/trailing ._- .toLowerCase(); // Convert to lowercase // Step 4: Ensure the file name is not empty // if (fileName === '.png') { // fileName = 'image.png'; // } // Step 5: Truncate if the file name is too long (optional) const maxLength = 100; // Maximum file name length if (fileName.length > maxLength) { const extension = ".png"; fileName = fileName.slice(0, maxLength - extension.length) + extension; } return fileName; }