import OpenAI from "openai"; import * as fs from "fs"; import * as path from "path"; import { StoryConfig } from "./config"; let openaiClient: OpenAI | null = null; function getOpenAI(): OpenAI { if (!openaiClient) { openaiClient = new OpenAI({ apiKey: process.env.OPENAI_API_KEY }); } return openaiClient; } const allowedSizesValues = [ "256x256", "512x512", "1024x1024", "1536x1024", "1024x1536", "1792x1024", "1024x1792", ] as const; type AllowedSize = (typeof allowedSizesValues)[number]; function pickImageSize(resolution?: string): AllowedSize { if (!resolution) return "1024x1024"; const match = resolution.match(/^(\d+)x(\d+)$/); if (!match) return "1024x1024"; const width = parseInt(match[1], 10); const height = parseInt(match[2], 10); if (!Number.isFinite(width) || !Number.isFinite(height)) return "1024x1024"; if (width === height) return "1024x1024"; const landscapeCandidates: AllowedSize[] = ["1536x1024", "1792x1024"]; const portraitCandidates: AllowedSize[] = ["1024x1536", "1024x1792"]; return width > height ? landscapeCandidates[0] : portraitCandidates[0]; } export async function generateImage( storyName: string, storyConfig: StoryConfig, chunk: string, chunkIndex: number, imageIndex: number ): Promise { const imagePath = path.join("stories", storyName, "images", `chunk_${chunkIndex}_img${imageIndex}.png`); const prompt = `${(storyConfig.config.image_style_prompts || "").trim()} Illustration for the following passage: "${chunk.slice(0, 500)}"`; const size = pickImageSize(storyConfig.config.export_settings?.resolution); const response = await getOpenAI().images.generate({ model: "dall-e-3", prompt, n: 1, size, response_format: "b64_json", }); if (!response.data?.[0].b64_json) { throw new Error("Image data not found in response"); } const imageBase64 = response.data[0].b64_json; const imageBuffer = Buffer.from(imageBase64, "base64"); fs.writeFileSync(imagePath, imageBuffer); return imagePath; }