snapai
Version:
AI-powered icon generation CLI for mobile app developers
97 lines (96 loc) • 4.62 kB
JavaScript
import OpenAI from "openai";
import { ConfigService } from "./config.js";
export class OpenAIService {
static async getClient() {
const apiKey = await ConfigService.get("openai_api_key");
if (!apiKey) {
throw new Error("OpenAI API key not configured. Run: snapai config --api-key YOUR_KEY");
}
return new OpenAI({
apiKey: apiKey,
});
}
static async generateIcon(options) {
const client = await this.getClient();
const { prompt, model = "gpt-image-1", size = "1024x1024", quality = "auto", background = "auto", outputFormat = "png", numImages = 1, moderation = "auto", rawPrompt = false, } = options;
this.validateModelParameters(model, size, quality, numImages, background, outputFormat, moderation);
const finalPrompt = rawPrompt
? prompt
: `Create a full-bleed ${size} px iOS app icon: ${prompt}.Use crisp, minimal design with vibrant colors. Add a subtle inner bevel for gentle depth; no hard shadows or outlines. Center the design with comfortable breathing room from the edges. Solid, light-neutral background. IMPORTANT: Fill the entire canvas edge-to-edge with the design, no padding, no margins. Design elements should be centered with appropriate spacing from edges but the background must cover 100% of the canvas. Add subtle depth with inner highlights, avoid hard shadows. Clean, minimal, Apple-style design. No borders, frames, or rounded corners.`;
const requestParams = {
model,
prompt: finalPrompt,
n: numImages,
size: size,
};
if (model === "gpt-image-1") {
requestParams.quality = this.mapQualityForGptImage1(quality);
requestParams.background = background;
requestParams.output_format = outputFormat;
requestParams.moderation = moderation;
}
else if (model === "dall-e-3") {
requestParams.quality = quality === "hd" ? "hd" : "standard";
requestParams.n = 1;
}
else if (model === "dall-e-2") {
requestParams.quality = "standard";
}
const response = await client.images.generate(requestParams);
if (!response.data || response.data.length === 0) {
throw new Error("Failed to generate image");
}
return response.data.map((img) => {
if (!img.b64_json) {
throw new Error("Failed to get base64 data from generated image");
}
return img.b64_json;
});
}
static validateModelParameters(model, size, quality, numImages, background, outputFormat, moderation) {
const validSizes = {
"dall-e-2": ["256x256", "512x512", "1024x1024"],
"dall-e-3": ["1024x1024", "1792x1024", "1024x1792"],
"gpt-image-1": ["1024x1024", "1536x1024", "1024x1536", "auto"],
};
if (!validSizes[model]?.includes(size)) {
throw new Error(`Invalid size "${size}" for model "${model}". Valid sizes: ${validSizes[model]?.join(", ")}`);
}
const validQualities = {
"dall-e-2": ["standard"],
"dall-e-3": ["standard", "hd"],
"gpt-image-1": ["auto", "high", "medium", "low"],
};
if (!validQualities[model]?.includes(quality)) {
throw new Error(`Invalid quality "${quality}" for model "${model}". Valid qualities: ${validQualities[model]?.join(", ")}`);
}
if (model === "dall-e-3" && numImages > 1) {
throw new Error("dall-e-3 only supports generating 1 image at a time");
}
if (numImages < 1 || numImages > 10) {
throw new Error("Number of images must be between 1 and 10");
}
if (model !== "gpt-image-1") {
if (background !== "auto") {
throw new Error(`Background parameter is only supported for gpt-image-1 model`);
}
if (outputFormat !== "png") {
throw new Error(`Output format parameter is only supported for gpt-image-1 model`);
}
if (moderation !== "auto") {
throw new Error(`Moderation parameter is only supported for gpt-image-1 model`);
}
}
}
static mapQualityForGptImage1(quality) {
const qualityMap = {
auto: "auto",
high: "high",
medium: "medium",
low: "low",
hd: "high",
standard: "medium",
};
return qualityMap[quality] || "auto";
}
}