feat: add aspect ratio
This commit is contained in:
parent
620a2a9caa
commit
585b446ca5
|
|
@ -16,6 +16,20 @@ const VALIDATION_RULES = {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Valid aspect ratios supported by Gemini SDK
|
||||||
|
const VALID_ASPECT_RATIOS = [
|
||||||
|
"1:1", // Square (1024x1024)
|
||||||
|
"2:3", // Portrait (832x1248)
|
||||||
|
"3:2", // Landscape (1248x832)
|
||||||
|
"3:4", // Portrait (864x1184)
|
||||||
|
"4:3", // Landscape (1184x864)
|
||||||
|
"4:5", // Portrait (896x1152)
|
||||||
|
"5:4", // Landscape (1152x896)
|
||||||
|
"9:16", // Vertical (768x1344)
|
||||||
|
"16:9", // Widescreen (1344x768)
|
||||||
|
"21:9", // Ultrawide (1536x672)
|
||||||
|
] as const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate the text-to-image JSON request
|
* Validate the text-to-image JSON request
|
||||||
*/
|
*/
|
||||||
|
|
@ -25,7 +39,7 @@ export const validateTextToImageRequest = (
|
||||||
next: NextFunction,
|
next: NextFunction,
|
||||||
): void | Response => {
|
): void | Response => {
|
||||||
const timestamp = new Date().toISOString();
|
const timestamp = new Date().toISOString();
|
||||||
const { prompt, filename, autoEnhance, enhancementOptions } = req.body;
|
const { prompt, filename, aspectRatio, autoEnhance, enhancementOptions } = req.body;
|
||||||
const errors: string[] = [];
|
const errors: string[] = [];
|
||||||
|
|
||||||
console.log(
|
console.log(
|
||||||
|
|
@ -73,6 +87,17 @@ export const validateTextToImageRequest = (
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validate aspectRatio (optional, defaults to "1:1")
|
||||||
|
if (aspectRatio !== undefined) {
|
||||||
|
if (typeof aspectRatio !== "string") {
|
||||||
|
errors.push("aspectRatio must be a string");
|
||||||
|
} else if (!VALID_ASPECT_RATIOS.includes(aspectRatio as any)) {
|
||||||
|
errors.push(
|
||||||
|
`Invalid aspectRatio. Must be one of: ${VALID_ASPECT_RATIOS.join(", ")}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Validate autoEnhance (optional boolean)
|
// Validate autoEnhance (optional boolean)
|
||||||
if (autoEnhance !== undefined && typeof autoEnhance !== "boolean") {
|
if (autoEnhance !== undefined && typeof autoEnhance !== "boolean") {
|
||||||
errors.push("autoEnhance must be a boolean");
|
errors.push("autoEnhance must be a boolean");
|
||||||
|
|
@ -111,11 +136,11 @@ export const validateTextToImageRequest = (
|
||||||
|
|
||||||
if (
|
if (
|
||||||
aspectRatio !== undefined &&
|
aspectRatio !== undefined &&
|
||||||
!["square", "portrait", "landscape", "wide", "ultrawide"].includes(
|
!VALID_ASPECT_RATIOS.includes(aspectRatio as any)
|
||||||
aspectRatio,
|
|
||||||
)
|
|
||||||
) {
|
) {
|
||||||
errors.push("Invalid aspectRatio in enhancementOptions");
|
errors.push(
|
||||||
|
`Invalid aspectRatio. Must be one of: ${VALID_ASPECT_RATIOS.join(", ")}`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
|
|
||||||
|
|
@ -63,7 +63,7 @@ generateRouter.post(
|
||||||
|
|
||||||
const timestamp = new Date().toISOString();
|
const timestamp = new Date().toISOString();
|
||||||
const requestId = req.requestId;
|
const requestId = req.requestId;
|
||||||
const { prompt, filename } = req.body;
|
const { prompt, filename, aspectRatio } = req.body;
|
||||||
const files = (req.files as Express.Multer.File[]) || [];
|
const files = (req.files as Express.Multer.File[]) || [];
|
||||||
|
|
||||||
// Extract org/project slugs from validated API key
|
// Extract org/project slugs from validated API key
|
||||||
|
|
@ -108,6 +108,7 @@ generateRouter.post(
|
||||||
const result = await imageGenService.generateImage({
|
const result = await imageGenService.generateImage({
|
||||||
prompt,
|
prompt,
|
||||||
filename,
|
filename,
|
||||||
|
...(aspectRatio && { aspectRatio }),
|
||||||
orgId,
|
orgId,
|
||||||
projectId,
|
projectId,
|
||||||
...(referenceImages && { referenceImages }),
|
...(referenceImages && { referenceImages }),
|
||||||
|
|
|
||||||
|
|
@ -54,7 +54,7 @@ textToImageRouter.post(
|
||||||
|
|
||||||
const timestamp = new Date().toISOString();
|
const timestamp = new Date().toISOString();
|
||||||
const requestId = req.requestId;
|
const requestId = req.requestId;
|
||||||
const { prompt, filename } = req.body;
|
const { prompt, filename, aspectRatio } = req.body;
|
||||||
|
|
||||||
// Extract org/project slugs from validated API key
|
// Extract org/project slugs from validated API key
|
||||||
const orgId = req.apiKey?.organizationSlug || undefined;
|
const orgId = req.apiKey?.organizationSlug || undefined;
|
||||||
|
|
@ -73,6 +73,7 @@ textToImageRouter.post(
|
||||||
const result = await imageGenService.generateImage({
|
const result = await imageGenService.generateImage({
|
||||||
prompt,
|
prompt,
|
||||||
filename,
|
filename,
|
||||||
|
...(aspectRatio && { aspectRatio }),
|
||||||
orgId,
|
orgId,
|
||||||
projectId,
|
projectId,
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -29,12 +29,13 @@ export class ImageGenService {
|
||||||
async generateImage(
|
async generateImage(
|
||||||
options: ImageGenerationOptions,
|
options: ImageGenerationOptions,
|
||||||
): Promise<ImageGenerationResult> {
|
): Promise<ImageGenerationResult> {
|
||||||
const { prompt, filename, referenceImages, orgId, projectId } = options;
|
const { prompt, filename, referenceImages, aspectRatio, orgId, projectId } = options;
|
||||||
|
|
||||||
// Use default values if not provided
|
// Use default values if not provided
|
||||||
const finalOrgId = orgId || process.env["DEFAULT_ORG_ID"] || "default";
|
const finalOrgId = orgId || process.env["DEFAULT_ORG_ID"] || "default";
|
||||||
const finalProjectId =
|
const finalProjectId =
|
||||||
projectId || process.env["DEFAULT_PROJECT_ID"] || "main";
|
projectId || process.env["DEFAULT_PROJECT_ID"] || "main";
|
||||||
|
const finalAspectRatio = aspectRatio || "1:1"; // Default to square
|
||||||
|
|
||||||
// Step 1: Generate image from Gemini AI
|
// Step 1: Generate image from Gemini AI
|
||||||
let generatedData: GeneratedImageData;
|
let generatedData: GeneratedImageData;
|
||||||
|
|
@ -43,6 +44,7 @@ export class ImageGenService {
|
||||||
const aiResult = await this.generateImageWithAI(
|
const aiResult = await this.generateImageWithAI(
|
||||||
prompt,
|
prompt,
|
||||||
referenceImages,
|
referenceImages,
|
||||||
|
finalAspectRatio,
|
||||||
finalOrgId,
|
finalOrgId,
|
||||||
finalProjectId,
|
finalProjectId,
|
||||||
);
|
);
|
||||||
|
|
@ -121,6 +123,7 @@ export class ImageGenService {
|
||||||
private async generateImageWithAI(
|
private async generateImageWithAI(
|
||||||
prompt: string,
|
prompt: string,
|
||||||
referenceImages: ReferenceImage[] | undefined,
|
referenceImages: ReferenceImage[] | undefined,
|
||||||
|
aspectRatio: string,
|
||||||
orgId: string,
|
orgId: string,
|
||||||
projectId: string,
|
projectId: string,
|
||||||
): Promise<{
|
): Promise<{
|
||||||
|
|
@ -155,7 +158,12 @@ export class ImageGenService {
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const config = { responseModalities: ["IMAGE", "TEXT"] };
|
const config = {
|
||||||
|
responseModalities: ["IMAGE", "TEXT"],
|
||||||
|
imageConfig: {
|
||||||
|
aspectRatio,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
// Capture Gemini SDK parameters for debugging
|
// Capture Gemini SDK parameters for debugging
|
||||||
const geminiParams: GeminiParams = {
|
const geminiParams: GeminiParams = {
|
||||||
|
|
|
||||||
|
|
@ -59,6 +59,7 @@ export interface ImageGenerationOptions {
|
||||||
prompt: string;
|
prompt: string;
|
||||||
filename: string;
|
filename: string;
|
||||||
referenceImages?: ReferenceImage[];
|
referenceImages?: ReferenceImage[];
|
||||||
|
aspectRatio?: string;
|
||||||
orgId?: string;
|
orgId?: string;
|
||||||
projectId?: string;
|
projectId?: string;
|
||||||
userId?: string;
|
userId?: string;
|
||||||
|
|
@ -74,6 +75,9 @@ export interface GeminiParams {
|
||||||
model: string;
|
model: string;
|
||||||
config: {
|
config: {
|
||||||
responseModalities: string[];
|
responseModalities: string[];
|
||||||
|
imageConfig?: {
|
||||||
|
aspectRatio?: string;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
contentsStructure: {
|
contentsStructure: {
|
||||||
role: string;
|
role: string;
|
||||||
|
|
|
||||||
|
|
@ -64,7 +64,7 @@ export default function DemoTTIPage() {
|
||||||
const [generationError, setGenerationError] = useState('');
|
const [generationError, setGenerationError] = useState('');
|
||||||
|
|
||||||
// Enhancement Options State
|
// Enhancement Options State
|
||||||
const [aspectRatio, setAspectRatio] = useState('');
|
const [aspectRatio, setAspectRatio] = useState('1:1');
|
||||||
const [imageStyle, setImageStyle] = useState('');
|
const [imageStyle, setImageStyle] = useState('');
|
||||||
const [advancedOptions, setAdvancedOptions] = useState<AdvancedOptionsData>({});
|
const [advancedOptions, setAdvancedOptions] = useState<AdvancedOptionsData>({});
|
||||||
const [showAdvancedModal, setShowAdvancedModal] = useState(false);
|
const [showAdvancedModal, setShowAdvancedModal] = useState(false);
|
||||||
|
|
@ -241,6 +241,7 @@ export default function DemoTTIPage() {
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
prompt: prompt.trim(),
|
prompt: prompt.trim(),
|
||||||
filename: `demo_${resultId}_left`,
|
filename: `demo_${resultId}_left`,
|
||||||
|
aspectRatio,
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
fetch(`${API_BASE_URL}/api/text-to-image`, {
|
fetch(`${API_BASE_URL}/api/text-to-image`, {
|
||||||
|
|
@ -252,6 +253,7 @@ export default function DemoTTIPage() {
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
prompt: prompt.trim(),
|
prompt: prompt.trim(),
|
||||||
filename: `demo_${resultId}_right`,
|
filename: `demo_${resultId}_right`,
|
||||||
|
aspectRatio,
|
||||||
autoEnhance: true,
|
autoEnhance: true,
|
||||||
...(hasEnhancementOptions && {
|
...(hasEnhancementOptions && {
|
||||||
enhancementOptions: rightEnhancementOptions
|
enhancementOptions: rightEnhancementOptions
|
||||||
|
|
@ -488,12 +490,12 @@ export default function DemoTTIPage() {
|
||||||
disabled={!apiKeyValidated || generating}
|
disabled={!apiKeyValidated || generating}
|
||||||
className="w-full px-3 py-2 text-sm bg-slate-800 border border-slate-700 rounded-lg text-white focus:outline-none focus:ring-2 focus:ring-amber-500 focus:border-transparent disabled:opacity-50 disabled:cursor-not-allowed"
|
className="w-full px-3 py-2 text-sm bg-slate-800 border border-slate-700 rounded-lg text-white focus:outline-none focus:ring-2 focus:ring-amber-500 focus:border-transparent disabled:opacity-50 disabled:cursor-not-allowed"
|
||||||
>
|
>
|
||||||
<option value="">Auto</option>
|
<option value="1:1">Square (1:1)</option>
|
||||||
<option value="square">Square (1:1)</option>
|
<option value="3:4">Portrait (3:4)</option>
|
||||||
<option value="portrait">Portrait (3:4)</option>
|
<option value="4:3">Landscape (4:3)</option>
|
||||||
<option value="landscape">Landscape (4:3)</option>
|
<option value="9:16">Vertical (9:16)</option>
|
||||||
<option value="wide">Wide (16:9)</option>
|
<option value="16:9">Widescreen (16:9)</option>
|
||||||
<option value="ultrawide">Ultrawide (21:9)</option>
|
<option value="21:9">Ultrawide (21:9)</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue