add promt enchancement
This commit is contained in:
parent
1fbc6daf1e
commit
7b6844d1f7
|
|
@ -0,0 +1,256 @@
|
||||||
|
# Prompting guide and strategies
|
||||||
|
|
||||||
|
Mastering Gemini 2.5 Flash Image Generation starts with one fundamental principle:
|
||||||
|
|
||||||
|
Describe the scene, don't just list keywords. The model's core strength is its deep language understanding. A narrative, descriptive paragraph will almost always produce a better, more coherent image than a list of disconnected words.
|
||||||
|
|
||||||
|
## Prompts for generating images
|
||||||
|
|
||||||
|
The following strategies will help you create effective prompts to generate exactly the images you're looking for.
|
||||||
|
|
||||||
|
### 1. Photorealistic scenes
|
||||||
|
|
||||||
|
For realistic images, use photography terms. Mention camera angles, lens types, lighting, and fine details to guide the model toward a photorealistic result.
|
||||||
|
|
||||||
|
template:
|
||||||
|
|
||||||
|
```
|
||||||
|
A photorealistic [shot type] of [subject], [action or expression], set in
|
||||||
|
[environment]. The scene is illuminated by [lighting description], creating
|
||||||
|
a [mood] atmosphere. Captured with a [camera/lens details], emphasizing
|
||||||
|
[key textures and details]. The image should be in a [aspect ratio] format.
|
||||||
|
```
|
||||||
|
|
||||||
|
example:
|
||||||
|
```
|
||||||
|
A photorealistic close-up portrait of an elderly Japanese ceramicist with
|
||||||
|
deep, sun-etched wrinkles and a warm, knowing smile. He is carefully
|
||||||
|
inspecting a freshly glazed tea bowl. The setting is his rustic,
|
||||||
|
sun-drenched workshop. The scene is illuminated by soft, golden hour light
|
||||||
|
streaming through a window, highlighting the fine texture of the clay.
|
||||||
|
Captured with an 85mm portrait lens, resulting in a soft, blurred background
|
||||||
|
(bokeh). The overall mood is serene and masterful. Vertical portrait
|
||||||
|
orientation.
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### 2. Stylized illustrations & stickers
|
||||||
|
To create stickers, icons, or assets, be explicit about the style and request a transparent background.
|
||||||
|
|
||||||
|
template:
|
||||||
|
|
||||||
|
```
|
||||||
|
A [style] sticker of a [subject], featuring [key characteristics] and a
|
||||||
|
[color palette]. The design should have [line style] and [shading style].
|
||||||
|
The background must be transparent.
|
||||||
|
```
|
||||||
|
|
||||||
|
example:
|
||||||
|
```
|
||||||
|
A kawaii-style sticker of a happy red panda wearing a tiny bamboo hat. It's
|
||||||
|
munching on a green bamboo leaf. The design features bold, clean outlines,
|
||||||
|
simple cel-shading, and a vibrant color palette. The background must be white.
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### 3. Accurate text in images
|
||||||
|
|
||||||
|
Gemini excels at rendering text. Be clear about the text, the font style (descriptively), and the overall design.
|
||||||
|
|
||||||
|
template:
|
||||||
|
```
|
||||||
|
Create a [image type] for [brand/concept] with the text "[text to render]"
|
||||||
|
in a [font style]. The design should be [style description], with a
|
||||||
|
[color scheme].
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
example:
|
||||||
|
```
|
||||||
|
Create a modern, minimalist logo for a coffee shop called 'The Daily Grind'.
|
||||||
|
The text should be in a clean, bold, sans-serif font. The design should
|
||||||
|
feature a simple, stylized icon of a a coffee bean seamlessly integrated
|
||||||
|
with the text. The color scheme is black and white.
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Product mockups & commercial photography
|
||||||
|
Perfect for creating clean, professional product shots for e-commerce, advertising, or branding.
|
||||||
|
|
||||||
|
|
||||||
|
template:
|
||||||
|
```
|
||||||
|
A high-resolution, studio-lit product photograph of a [product description]
|
||||||
|
on a [background surface/description]. The lighting is a [lighting setup,
|
||||||
|
e.g., three-point softbox setup] to [lighting purpose]. The camera angle is
|
||||||
|
a [angle type] to showcase [specific feature]. Ultra-realistic, with sharp
|
||||||
|
focus on [key detail]. [Aspect ratio].
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
example:
|
||||||
|
```
|
||||||
|
A high-resolution, studio-lit product photograph of a minimalist ceramic
|
||||||
|
coffee mug in matte black, presented on a polished concrete surface. The
|
||||||
|
lighting is a three-point softbox setup designed to create soft, diffused
|
||||||
|
highlights and eliminate harsh shadows. The camera angle is a slightly
|
||||||
|
elevated 45-degree shot to showcase its clean lines. Ultra-realistic, with
|
||||||
|
sharp focus on the steam rising from the coffee. Square image.
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. Minimalist & negative space design
|
||||||
|
|
||||||
|
Excellent for creating backgrounds for websites, presentations, or marketing materials where text will be overlaid.
|
||||||
|
|
||||||
|
template:
|
||||||
|
```
|
||||||
|
A minimalist composition featuring a single [subject] positioned in the
|
||||||
|
[bottom-right/top-left/etc.] of the frame. The background is a vast, empty
|
||||||
|
[color] canvas, creating significant negative space. Soft, subtle lighting.
|
||||||
|
[Aspect ratio].
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
example:
|
||||||
|
```
|
||||||
|
A minimalist composition featuring a single, delicate red maple leaf
|
||||||
|
positioned in the bottom-right of the frame. The background is a vast, empty
|
||||||
|
off-white canvas, creating significant negative space for text. Soft,
|
||||||
|
diffused lighting from the top left. Square image.
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6. Sequential art (Comic panel / Storyboard)`
|
||||||
|
|
||||||
|
Builds on character consistency and scene description to create panels for visual storytelling.
|
||||||
|
|
||||||
|
template:
|
||||||
|
```
|
||||||
|
A single comic book panel in a [art style] style. In the foreground,
|
||||||
|
[character description and action]. In the background, [setting details].
|
||||||
|
The panel has a [dialogue/caption box] with the text "[Text]". The lighting
|
||||||
|
creates a [mood] mood. [Aspect ratio].
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
example:
|
||||||
|
```
|
||||||
|
A single comic book panel in a gritty, noir art style with high-contrast
|
||||||
|
black and white inks. In the foreground, a detective in a trench coat stands
|
||||||
|
under a flickering streetlamp, rain soaking his shoulders. In the
|
||||||
|
background, the neon sign of a desolate bar reflects in a puddle. A caption
|
||||||
|
box at the top reads "The city was a tough place to keep secrets." The
|
||||||
|
lighting is harsh, creating a dramatic, somber mood. Landscape.
|
||||||
|
```
|
||||||
|
|
||||||
|
## Prompts for editing images
|
||||||
|
|
||||||
|
These examples show how to provide images alongside your text prompts for editing, composition, and style transfer.
|
||||||
|
|
||||||
|
### 1. Adding and removing elements
|
||||||
|
Provide an image and describe your change. The model will match the original image's style, lighting, and perspective.
|
||||||
|
|
||||||
|
|
||||||
|
template:
|
||||||
|
```
|
||||||
|
Using the provided image of [subject], please [add/remove/modify] [element]
|
||||||
|
to/from the scene. Ensure the change is [description of how the change should
|
||||||
|
integrate].
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
example:
|
||||||
|
```
|
||||||
|
"Using the provided image of my cat, please add a small, knitted wizard hat
|
||||||
|
on its head. Make it look like it's sitting comfortably and matches the soft
|
||||||
|
lighting of the photo."
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### 2. Inpainting (Semantic masking)
|
||||||
|
|
||||||
|
Conversationally define a "mask" to edit a specific part of an image while leaving the rest untouched.
|
||||||
|
|
||||||
|
|
||||||
|
template:
|
||||||
|
```
|
||||||
|
Using the provided image, change only the [specific element] to [new
|
||||||
|
element/description]. Keep everything else in the image exactly the same,
|
||||||
|
preserving the original style, lighting, and composition.
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
example:
|
||||||
|
```
|
||||||
|
"Using the provided image of a living room, change only the blue sofa to be
|
||||||
|
a vintage, brown leather chesterfield sofa. Keep the rest of the room,
|
||||||
|
including the pillows on the sofa and the lighting, unchanged."
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Style transfer
|
||||||
|
|
||||||
|
Provide an image and ask the model to recreate its content in a different artistic style.
|
||||||
|
|
||||||
|
|
||||||
|
template:
|
||||||
|
```
|
||||||
|
Transform the provided photograph of [subject] into the artistic style of [artist/art style]. Preserve the original composition but render it with [description of stylistic elements].
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
example:
|
||||||
|
```
|
||||||
|
"Transform the provided photograph of a modern city street at night into the artistic style of Vincent van Gogh's 'Starry Night'. Preserve the original composition of buildings and cars, but render all elements with swirling, impasto brushstrokes and a dramatic palette of deep blues and bright yellows."
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Advanced composition: Combining multiple images
|
||||||
|
|
||||||
|
Provide multiple images as context to create a new, composite scene. This is perfect for product mockups or creative collages.
|
||||||
|
|
||||||
|
|
||||||
|
template:
|
||||||
|
```
|
||||||
|
Create a new image by combining the elements from the provided images. Take
|
||||||
|
the [element from image 1] and place it with/on the [element from image 2].
|
||||||
|
The final image should be a [description of the final scene].
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
example:
|
||||||
|
```
|
||||||
|
"Create a professional e-commerce fashion photo. Take the blue floral dress
|
||||||
|
from the first image and let the woman from the second image wear it.
|
||||||
|
Generate a realistic, full-body shot of the woman wearing the dress, with
|
||||||
|
the lighting and shadows adjusted to match the outdoor environment."
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. High-fidelity detail preservation
|
||||||
|
|
||||||
|
To ensure critical details (like a face or logo) are preserved during an edit, describe them in great detail along with your edit request.
|
||||||
|
|
||||||
|
|
||||||
|
template:
|
||||||
|
```
|
||||||
|
Using the provided images, place [element from image 2] onto [element from
|
||||||
|
image 1]. Ensure that the features of [element from image 1] remain
|
||||||
|
completely unchanged. The added element should [description of how the
|
||||||
|
element should integrate].
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
example:
|
||||||
|
```
|
||||||
|
"Take the first image of the woman with brown hair, blue eyes, and a neutral
|
||||||
|
expression. Add the logo from the second image onto her black t-shirt.
|
||||||
|
Ensure the woman's face and features remain completely unchanged. The logo
|
||||||
|
should look like it's naturally printed on the fabric, following the folds
|
||||||
|
of the shirt."
|
||||||
|
```
|
||||||
|
|
||||||
|
## Best Practices
|
||||||
|
To elevate your results from good to great, incorporate these professional strategies into your workflow.
|
||||||
|
|
||||||
|
- Be Hyper-Specific: The more detail you provide, the more control you have. Instead of "fantasy armor," describe it: "ornate elven plate armor, etched with silver leaf patterns, with a high collar and pauldrons shaped like falcon wings."
|
||||||
|
- Provide Context and Intent: Explain the purpose of the image. The model's understanding of context will influence the final output. For example, "Create a logo for a high-end, minimalist skincare brand" will yield better results than just "Create a logo."
|
||||||
|
- Iterate and Refine: Don't expect a perfect image on the first try. Use the conversational nature of the model to make small changes. Follow up with prompts like, "That's great, but can you make the lighting a bit warmer?" or "Keep everything the same, but change the character's expression to be more serious."
|
||||||
|
- Use Step-by-Step Instructions: For complex scenes with many elements, break your prompt into steps. "First, create a background of a serene, misty forest at dawn. Then, in the foreground, add a moss-covered ancient stone altar. Finally, place a single, glowing sword on top of the altar."
|
||||||
|
- Use "Semantic Negative Prompts": Instead of saying "no cars," describe the desired scene positively: "an empty, deserted street with no signs of traffic."
|
||||||
|
- Control the Camera: Use photographic and cinematic language to control the composition. Terms like wide-angle shot, macro shot, low-angle perspective.
|
||||||
|
|
@ -18,7 +18,6 @@ module.exports = [
|
||||||
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
|
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
|
||||||
'@typescript-eslint/explicit-function-return-type': 'warn',
|
'@typescript-eslint/explicit-function-return-type': 'warn',
|
||||||
'@typescript-eslint/no-explicit-any': 'warn',
|
'@typescript-eslint/no-explicit-any': 'warn',
|
||||||
'@typescript-eslint/prefer-const': 'error',
|
|
||||||
'@typescript-eslint/no-var-requires': 'error',
|
'@typescript-eslint/no-var-requires': 'error',
|
||||||
'no-console': 'off', // Allow console for server logging
|
'no-console': 'off', // Allow console for server logging
|
||||||
'prefer-const': 'error',
|
'prefer-const': 'error',
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import cors from 'cors';
|
||||||
import { config } from 'dotenv';
|
import { config } from 'dotenv';
|
||||||
import { Config } from './types/api';
|
import { Config } from './types/api';
|
||||||
import { generateRouter } from './routes/generate';
|
import { generateRouter } from './routes/generate';
|
||||||
|
import { enhanceRouter } from './routes/enhance';
|
||||||
import { errorHandler, notFoundHandler } from './middleware/errorHandler';
|
import { errorHandler, notFoundHandler } from './middleware/errorHandler';
|
||||||
|
|
||||||
// Load environment variables
|
// Load environment variables
|
||||||
|
|
@ -61,7 +62,8 @@ export const createApp = (): Application => {
|
||||||
endpoints: {
|
endpoints: {
|
||||||
'GET /health': 'Health check',
|
'GET /health': 'Health check',
|
||||||
'GET /api/info': 'API information',
|
'GET /api/info': 'API information',
|
||||||
'POST /api/generate': 'Generate images from text prompt with optional reference images'
|
'POST /api/generate': 'Generate images from text prompt with optional reference images',
|
||||||
|
'POST /api/enhance': 'Enhance and optimize prompts for better image generation'
|
||||||
},
|
},
|
||||||
limits: {
|
limits: {
|
||||||
maxFileSize: `${appConfig.maxFileSize / (1024 * 1024)}MB`,
|
maxFileSize: `${appConfig.maxFileSize / (1024 * 1024)}MB`,
|
||||||
|
|
@ -76,6 +78,7 @@ export const createApp = (): Application => {
|
||||||
|
|
||||||
// Mount API routes
|
// Mount API routes
|
||||||
app.use('/api', generateRouter);
|
app.use('/api', generateRouter);
|
||||||
|
app.use('/api', enhanceRouter);
|
||||||
|
|
||||||
// Error handling middleware (must be last)
|
// Error handling middleware (must be last)
|
||||||
app.use(notFoundHandler);
|
app.use(notFoundHandler);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,116 @@
|
||||||
|
import { Request, Response } from "express";
|
||||||
|
import { PromptEnhancementService } from "../services/PromptEnhancementService";
|
||||||
|
import { EnhancedGenerateImageRequest } from "../types/api";
|
||||||
|
|
||||||
|
let promptEnhancementService: PromptEnhancementService | null = null;
|
||||||
|
|
||||||
|
interface RequestWithEnhancement extends Request {
|
||||||
|
body: EnhancedGenerateImageRequest;
|
||||||
|
enhancedPrompt?: string;
|
||||||
|
originalPrompt?: string;
|
||||||
|
enhancementMetadata?: {
|
||||||
|
detectedLanguage?: string;
|
||||||
|
appliedTemplate?: string;
|
||||||
|
enhancements: string[];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const autoEnhancePrompt = async (
|
||||||
|
req: RequestWithEnhancement,
|
||||||
|
_res: Response,
|
||||||
|
next: Function,
|
||||||
|
): Promise<void> => {
|
||||||
|
const timestamp = new Date().toISOString();
|
||||||
|
const requestId = req.requestId;
|
||||||
|
const { prompt, autoEnhance, enhancementOptions } = req.body;
|
||||||
|
|
||||||
|
if (!autoEnhance) {
|
||||||
|
console.log(
|
||||||
|
`[${timestamp}] [${requestId}] Auto-enhancement disabled, skipping`,
|
||||||
|
);
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
`[${timestamp}] [${requestId}] Auto-enhancement enabled, processing prompt`,
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!promptEnhancementService) {
|
||||||
|
const apiKey = process.env["GEMINI_API_KEY"];
|
||||||
|
if (!apiKey) {
|
||||||
|
console.error(
|
||||||
|
`[${timestamp}] [${requestId}] Cannot initialize prompt enhancement: Missing API key`,
|
||||||
|
);
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
promptEnhancementService = new PromptEnhancementService(apiKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await promptEnhancementService.enhancePrompt(
|
||||||
|
prompt,
|
||||||
|
enhancementOptions || {},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (result.success && result.enhancedPrompt) {
|
||||||
|
console.log(`[${timestamp}] [${requestId}] Prompt enhanced successfully`);
|
||||||
|
console.log(
|
||||||
|
`[${timestamp}] [${requestId}] Original: "${prompt.substring(0, 50)}..."`,
|
||||||
|
);
|
||||||
|
console.log(
|
||||||
|
`[${timestamp}] [${requestId}] Enhanced: "${result.enhancedPrompt.substring(0, 50)}..."`,
|
||||||
|
);
|
||||||
|
|
||||||
|
req.originalPrompt = prompt;
|
||||||
|
req.enhancedPrompt = result.enhancedPrompt;
|
||||||
|
req.enhancementMetadata = {
|
||||||
|
...(result.detectedLanguage && {
|
||||||
|
detectedLanguage: result.detectedLanguage,
|
||||||
|
}),
|
||||||
|
...(result.appliedTemplate && {
|
||||||
|
appliedTemplate: result.appliedTemplate,
|
||||||
|
}),
|
||||||
|
enhancements: result.metadata?.enhancements || [],
|
||||||
|
};
|
||||||
|
|
||||||
|
req.body.prompt = result.enhancedPrompt;
|
||||||
|
} else {
|
||||||
|
console.warn(
|
||||||
|
`[${timestamp}] [${requestId}] Prompt enhancement failed: ${result.error}`,
|
||||||
|
);
|
||||||
|
console.log(
|
||||||
|
`[${timestamp}] [${requestId}] Proceeding with original prompt`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(
|
||||||
|
`[${timestamp}] [${requestId}] Error during auto-enhancement:`,
|
||||||
|
error,
|
||||||
|
);
|
||||||
|
console.log(
|
||||||
|
`[${timestamp}] [${requestId}] Proceeding with original prompt`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
next();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const logEnhancementResult = (
|
||||||
|
req: RequestWithEnhancement,
|
||||||
|
_res: Response,
|
||||||
|
next: Function,
|
||||||
|
): void => {
|
||||||
|
if (req.enhancedPrompt && req.enhancementMetadata) {
|
||||||
|
const timestamp = new Date().toISOString();
|
||||||
|
const requestId = req.requestId;
|
||||||
|
|
||||||
|
console.log(`[${timestamp}] [${requestId}] Enhancement applied:`, {
|
||||||
|
detectedLanguage: req.enhancementMetadata.detectedLanguage,
|
||||||
|
appliedTemplate: req.enhancementMetadata.appliedTemplate,
|
||||||
|
enhancementsCount: req.enhancementMetadata.enhancements.length,
|
||||||
|
enhancements: req.enhancementMetadata.enhancements,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
next();
|
||||||
|
};
|
||||||
|
|
@ -35,12 +35,12 @@ const MAX_FILES = 3;
|
||||||
|
|
||||||
// Configure multer with limits and file filtering
|
// Configure multer with limits and file filtering
|
||||||
export const upload = multer({
|
export const upload = multer({
|
||||||
storage: storage,
|
storage,
|
||||||
limits: {
|
limits: {
|
||||||
fileSize: MAX_FILE_SIZE, // 5MB per file
|
fileSize: MAX_FILE_SIZE, // 5MB per file
|
||||||
files: MAX_FILES, // Maximum 3 files
|
files: MAX_FILES, // Maximum 3 files
|
||||||
},
|
},
|
||||||
fileFilter: fileFilter,
|
fileFilter,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Middleware for handling reference images
|
// Middleware for handling reference images
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@ export const validateGenerateRequest = (
|
||||||
next: NextFunction,
|
next: NextFunction,
|
||||||
): void | Response => {
|
): void | Response => {
|
||||||
const timestamp = new Date().toISOString();
|
const timestamp = new Date().toISOString();
|
||||||
const { prompt, filename } = req.body;
|
const { prompt, filename, autoEnhance, enhancementOptions } = req.body;
|
||||||
const errors: string[] = [];
|
const errors: string[] = [];
|
||||||
|
|
||||||
console.log(`[${timestamp}] [${req.requestId}] Validating generate request`);
|
console.log(`[${timestamp}] [${req.requestId}] Validating generate request`);
|
||||||
|
|
@ -72,6 +72,89 @@ export const validateGenerateRequest = (
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validate autoEnhance (optional boolean)
|
||||||
|
if (autoEnhance !== undefined && typeof autoEnhance !== "boolean") {
|
||||||
|
errors.push("autoEnhance must be a boolean");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate enhancementOptions (optional object)
|
||||||
|
if (enhancementOptions !== undefined) {
|
||||||
|
if (
|
||||||
|
typeof enhancementOptions !== "object" ||
|
||||||
|
Array.isArray(enhancementOptions)
|
||||||
|
) {
|
||||||
|
errors.push("enhancementOptions must be an object");
|
||||||
|
} else {
|
||||||
|
const {
|
||||||
|
imageStyle,
|
||||||
|
aspectRatio,
|
||||||
|
mood,
|
||||||
|
lighting,
|
||||||
|
cameraAngle,
|
||||||
|
negativePrompts,
|
||||||
|
} = enhancementOptions;
|
||||||
|
|
||||||
|
if (
|
||||||
|
imageStyle !== undefined &&
|
||||||
|
![
|
||||||
|
"photorealistic",
|
||||||
|
"illustration",
|
||||||
|
"minimalist",
|
||||||
|
"sticker",
|
||||||
|
"product",
|
||||||
|
"comic",
|
||||||
|
].includes(imageStyle)
|
||||||
|
) {
|
||||||
|
errors.push("Invalid imageStyle in enhancementOptions");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
aspectRatio !== undefined &&
|
||||||
|
!["square", "portrait", "landscape", "wide", "ultrawide"].includes(
|
||||||
|
aspectRatio,
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
errors.push("Invalid aspectRatio in enhancementOptions");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
mood !== undefined &&
|
||||||
|
(typeof mood !== "string" || mood.length > 100)
|
||||||
|
) {
|
||||||
|
errors.push("mood must be a string with max 100 characters");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
lighting !== undefined &&
|
||||||
|
(typeof lighting !== "string" || lighting.length > 100)
|
||||||
|
) {
|
||||||
|
errors.push("lighting must be a string with max 100 characters");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
cameraAngle !== undefined &&
|
||||||
|
(typeof cameraAngle !== "string" || cameraAngle.length > 100)
|
||||||
|
) {
|
||||||
|
errors.push("cameraAngle must be a string with max 100 characters");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (negativePrompts !== undefined) {
|
||||||
|
if (!Array.isArray(negativePrompts) || negativePrompts.length > 10) {
|
||||||
|
errors.push("negativePrompts must be an array with max 10 items");
|
||||||
|
} else {
|
||||||
|
for (const item of negativePrompts) {
|
||||||
|
if (typeof item !== "string" || item.length > 100) {
|
||||||
|
errors.push(
|
||||||
|
"Each negative prompt must be a string with max 100 characters",
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Check for XSS attempts in prompt
|
// Check for XSS attempts in prompt
|
||||||
const xssPatterns = [
|
const xssPatterns = [
|
||||||
/<script/i,
|
/<script/i,
|
||||||
|
|
@ -126,7 +209,7 @@ export const logRequestDetails = (
|
||||||
next: NextFunction,
|
next: NextFunction,
|
||||||
): void => {
|
): void => {
|
||||||
const timestamp = new Date().toISOString();
|
const timestamp = new Date().toISOString();
|
||||||
const { prompt, filename } = req.body;
|
const { prompt, filename, autoEnhance, enhancementOptions } = req.body;
|
||||||
const files = (req.files as Express.Multer.File[]) || [];
|
const files = (req.files as Express.Multer.File[]) || [];
|
||||||
|
|
||||||
console.log(`[${timestamp}] [${req.requestId}] === REQUEST DETAILS ===`);
|
console.log(`[${timestamp}] [${req.requestId}] === REQUEST DETAILS ===`);
|
||||||
|
|
@ -136,6 +219,15 @@ export const logRequestDetails = (
|
||||||
`[${timestamp}] [${req.requestId}] Prompt: "${prompt?.substring(0, 100)}${prompt?.length > 100 ? "..." : ""}"`,
|
`[${timestamp}] [${req.requestId}] Prompt: "${prompt?.substring(0, 100)}${prompt?.length > 100 ? "..." : ""}"`,
|
||||||
);
|
);
|
||||||
console.log(`[${timestamp}] [${req.requestId}] Filename: "${filename}"`);
|
console.log(`[${timestamp}] [${req.requestId}] Filename: "${filename}"`);
|
||||||
|
console.log(
|
||||||
|
`[${timestamp}] [${req.requestId}] Auto-enhance: ${autoEnhance || false}`,
|
||||||
|
);
|
||||||
|
if (enhancementOptions) {
|
||||||
|
console.log(
|
||||||
|
`[${timestamp}] [${req.requestId}] Enhancement options:`,
|
||||||
|
enhancementOptions,
|
||||||
|
);
|
||||||
|
}
|
||||||
console.log(
|
console.log(
|
||||||
`[${timestamp}] [${req.requestId}] Reference files: ${files.length}`,
|
`[${timestamp}] [${req.requestId}] Reference files: ${files.length}`,
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,188 @@
|
||||||
|
import { Request, Response, Router } from "express";
|
||||||
|
import type { Router as RouterType } from "express";
|
||||||
|
import { PromptEnhancementService } from "../services/PromptEnhancementService";
|
||||||
|
import { asyncHandler } from "../middleware/errorHandler";
|
||||||
|
import {
|
||||||
|
PromptEnhancementRequest,
|
||||||
|
PromptEnhancementResponse,
|
||||||
|
} from "../types/api";
|
||||||
|
import { body, validationResult } from "express-validator";
|
||||||
|
|
||||||
|
export const enhanceRouter: RouterType = Router();
|
||||||
|
|
||||||
|
let promptEnhancementService: PromptEnhancementService;
|
||||||
|
|
||||||
|
const validateEnhanceRequest = [
|
||||||
|
body("prompt")
|
||||||
|
.notEmpty()
|
||||||
|
.withMessage("Prompt is required")
|
||||||
|
.isLength({ min: 1, max: 5000 })
|
||||||
|
.withMessage("Prompt must be between 1 and 5000 characters")
|
||||||
|
.trim(),
|
||||||
|
|
||||||
|
body("options.imageStyle")
|
||||||
|
.optional()
|
||||||
|
.isIn([
|
||||||
|
"photorealistic",
|
||||||
|
"illustration",
|
||||||
|
"minimalist",
|
||||||
|
"sticker",
|
||||||
|
"product",
|
||||||
|
"comic",
|
||||||
|
])
|
||||||
|
.withMessage("Invalid image style"),
|
||||||
|
|
||||||
|
body("options.aspectRatio")
|
||||||
|
.optional()
|
||||||
|
.isIn(["square", "portrait", "landscape", "wide", "ultrawide"])
|
||||||
|
.withMessage("Invalid aspect ratio"),
|
||||||
|
|
||||||
|
body("options.mood")
|
||||||
|
.optional()
|
||||||
|
.isLength({ max: 100 })
|
||||||
|
.withMessage("Mood description too long")
|
||||||
|
.trim(),
|
||||||
|
|
||||||
|
body("options.lighting")
|
||||||
|
.optional()
|
||||||
|
.isLength({ max: 100 })
|
||||||
|
.withMessage("Lighting description too long")
|
||||||
|
.trim(),
|
||||||
|
|
||||||
|
body("options.cameraAngle")
|
||||||
|
.optional()
|
||||||
|
.isLength({ max: 100 })
|
||||||
|
.withMessage("Camera angle description too long")
|
||||||
|
.trim(),
|
||||||
|
|
||||||
|
body("options.outputFormat")
|
||||||
|
.optional()
|
||||||
|
.isIn(["text", "markdown", "detailed"])
|
||||||
|
.withMessage("Invalid output format"),
|
||||||
|
|
||||||
|
body("options.negativePrompts")
|
||||||
|
.optional()
|
||||||
|
.isArray({ max: 10 })
|
||||||
|
.withMessage("Too many negative prompts (max 10)"),
|
||||||
|
|
||||||
|
body("options.negativePrompts.*")
|
||||||
|
.optional()
|
||||||
|
.isLength({ max: 100 })
|
||||||
|
.withMessage("Negative prompt too long")
|
||||||
|
.trim(),
|
||||||
|
];
|
||||||
|
|
||||||
|
const logEnhanceRequest = (req: Request, _res: Response, next: Function) => {
|
||||||
|
const timestamp = new Date().toISOString();
|
||||||
|
const requestId = req.requestId;
|
||||||
|
const { prompt, options } = req.body as PromptEnhancementRequest;
|
||||||
|
|
||||||
|
console.log(`[${timestamp}] [${requestId}] POST /api/enhance`);
|
||||||
|
console.log(
|
||||||
|
`[${timestamp}] [${requestId}] Prompt length: ${prompt?.length || 0} characters`,
|
||||||
|
);
|
||||||
|
console.log(`[${timestamp}] [${requestId}] Options:`, options || "none");
|
||||||
|
|
||||||
|
next();
|
||||||
|
};
|
||||||
|
|
||||||
|
enhanceRouter.post(
|
||||||
|
"/enhance",
|
||||||
|
|
||||||
|
validateEnhanceRequest,
|
||||||
|
logEnhanceRequest,
|
||||||
|
|
||||||
|
asyncHandler(async (req: Request, res: Response) => {
|
||||||
|
if (!promptEnhancementService) {
|
||||||
|
const apiKey = process.env["GEMINI_API_KEY"];
|
||||||
|
if (!apiKey) {
|
||||||
|
return res.status(500).json({
|
||||||
|
success: false,
|
||||||
|
originalPrompt: "",
|
||||||
|
error: "Server configuration error: GEMINI_API_KEY not configured",
|
||||||
|
} as PromptEnhancementResponse);
|
||||||
|
}
|
||||||
|
promptEnhancementService = new PromptEnhancementService(apiKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
const errors = validationResult(req);
|
||||||
|
if (!errors.isEmpty()) {
|
||||||
|
console.log(
|
||||||
|
`[${new Date().toISOString()}] [${req.requestId}] Validation failed:`,
|
||||||
|
errors.array(),
|
||||||
|
);
|
||||||
|
return res.status(400).json({
|
||||||
|
success: false,
|
||||||
|
originalPrompt: req.body.prompt || "",
|
||||||
|
error: `Validation failed: ${errors
|
||||||
|
.array()
|
||||||
|
.map((e) => e.msg)
|
||||||
|
.join(", ")}`,
|
||||||
|
} as PromptEnhancementResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
const timestamp = new Date().toISOString();
|
||||||
|
const requestId = req.requestId;
|
||||||
|
const { prompt, options } = req.body as PromptEnhancementRequest;
|
||||||
|
|
||||||
|
console.log(`[${timestamp}] [${requestId}] Starting prompt enhancement`);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await promptEnhancementService.enhancePrompt(
|
||||||
|
prompt,
|
||||||
|
options || {},
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log(`[${timestamp}] [${requestId}] Enhancement completed:`, {
|
||||||
|
success: result.success,
|
||||||
|
detectedLanguage: result.detectedLanguage,
|
||||||
|
appliedTemplate: result.appliedTemplate,
|
||||||
|
enhancementsCount: result.metadata?.enhancements.length || 0,
|
||||||
|
hasError: !!result.error,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
const successResponse: PromptEnhancementResponse = {
|
||||||
|
success: true,
|
||||||
|
originalPrompt: result.originalPrompt,
|
||||||
|
enhancedPrompt: result.enhancedPrompt!,
|
||||||
|
...(result.detectedLanguage && {
|
||||||
|
detectedLanguage: result.detectedLanguage,
|
||||||
|
}),
|
||||||
|
...(result.appliedTemplate && {
|
||||||
|
appliedTemplate: result.appliedTemplate,
|
||||||
|
}),
|
||||||
|
...(result.metadata && { metadata: result.metadata }),
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log(`[${timestamp}] [${requestId}] Sending success response`);
|
||||||
|
return res.status(200).json(successResponse);
|
||||||
|
} else {
|
||||||
|
const errorResponse: PromptEnhancementResponse = {
|
||||||
|
success: false,
|
||||||
|
originalPrompt: result.originalPrompt,
|
||||||
|
error: result.error || "Unknown error occurred",
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
`[${timestamp}] [${requestId}] Sending error response: ${result.error}`,
|
||||||
|
);
|
||||||
|
return res.status(500).json(errorResponse);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(
|
||||||
|
`[${timestamp}] [${requestId}] Unhandled error in enhance endpoint:`,
|
||||||
|
error,
|
||||||
|
);
|
||||||
|
|
||||||
|
const errorResponse: PromptEnhancementResponse = {
|
||||||
|
success: false,
|
||||||
|
originalPrompt: prompt,
|
||||||
|
error:
|
||||||
|
error instanceof Error ? error.message : "Unknown error occurred",
|
||||||
|
};
|
||||||
|
|
||||||
|
return res.status(500).json(errorResponse);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
@ -9,6 +9,10 @@ import {
|
||||||
validateGenerateRequest,
|
validateGenerateRequest,
|
||||||
logRequestDetails,
|
logRequestDetails,
|
||||||
} from "../middleware/validation";
|
} from "../middleware/validation";
|
||||||
|
import {
|
||||||
|
autoEnhancePrompt,
|
||||||
|
logEnhancementResult,
|
||||||
|
} from "../middleware/promptEnhancement";
|
||||||
import { asyncHandler } from "../middleware/errorHandler";
|
import { asyncHandler } from "../middleware/errorHandler";
|
||||||
import { GenerateImageResponse } from "../types/api";
|
import { GenerateImageResponse } from "../types/api";
|
||||||
// Create router
|
// Create router
|
||||||
|
|
@ -30,6 +34,10 @@ generateRouter.post(
|
||||||
logRequestDetails,
|
logRequestDetails,
|
||||||
validateGenerateRequest,
|
validateGenerateRequest,
|
||||||
|
|
||||||
|
// Auto-enhancement middleware (optional)
|
||||||
|
autoEnhancePrompt,
|
||||||
|
logEnhancementResult,
|
||||||
|
|
||||||
// Main handler
|
// Main handler
|
||||||
asyncHandler(async (req: any, res: Response) => {
|
asyncHandler(async (req: any, res: Response) => {
|
||||||
// Initialize service if not already done
|
// Initialize service if not already done
|
||||||
|
|
@ -110,6 +118,15 @@ generateRouter.post(
|
||||||
...(result.description && { description: result.description }),
|
...(result.description && { description: result.description }),
|
||||||
model: result.model,
|
model: result.model,
|
||||||
generatedAt: timestamp,
|
generatedAt: timestamp,
|
||||||
|
...(req.enhancedPrompt && {
|
||||||
|
promptEnhancement: {
|
||||||
|
originalPrompt: req.originalPrompt,
|
||||||
|
enhancedPrompt: req.enhancedPrompt,
|
||||||
|
detectedLanguage: req.enhancementMetadata?.detectedLanguage,
|
||||||
|
appliedTemplate: req.enhancementMetadata?.appliedTemplate,
|
||||||
|
enhancements: req.enhancementMetadata?.enhancements || [],
|
||||||
|
},
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,274 @@
|
||||||
|
import { GoogleGenAI } from "@google/genai";
|
||||||
|
|
||||||
|
export interface PromptEnhancementOptions {
|
||||||
|
imageStyle?:
|
||||||
|
| "photorealistic"
|
||||||
|
| "illustration"
|
||||||
|
| "minimalist"
|
||||||
|
| "sticker"
|
||||||
|
| "product"
|
||||||
|
| "comic";
|
||||||
|
aspectRatio?: "square" | "portrait" | "landscape" | "wide" | "ultrawide";
|
||||||
|
mood?: string;
|
||||||
|
lighting?: string;
|
||||||
|
cameraAngle?: string;
|
||||||
|
outputFormat?: "text" | "markdown" | "detailed";
|
||||||
|
negativePrompts?: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PromptEnhancementResult {
|
||||||
|
success: boolean;
|
||||||
|
originalPrompt: string;
|
||||||
|
enhancedPrompt?: string;
|
||||||
|
detectedLanguage?: string;
|
||||||
|
appliedTemplate?: string;
|
||||||
|
metadata?: {
|
||||||
|
style?: string;
|
||||||
|
aspectRatio?: string;
|
||||||
|
enhancements: string[];
|
||||||
|
};
|
||||||
|
error?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class PromptEnhancementService {
|
||||||
|
private ai: GoogleGenAI;
|
||||||
|
private model = "gemini-2.5-flash";
|
||||||
|
|
||||||
|
constructor(apiKey: string) {
|
||||||
|
if (!apiKey) {
|
||||||
|
throw new Error("Gemini API key is required");
|
||||||
|
}
|
||||||
|
this.ai = new GoogleGenAI({ apiKey });
|
||||||
|
}
|
||||||
|
|
||||||
|
async enhancePrompt(
|
||||||
|
rawPrompt: string,
|
||||||
|
options: PromptEnhancementOptions = {},
|
||||||
|
): Promise<PromptEnhancementResult> {
|
||||||
|
const timestamp = new Date().toISOString();
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
`[${timestamp}] Starting prompt enhancement for: "${rawPrompt.substring(0, 50)}..."`,
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const systemPrompt = this.buildSystemPrompt(options);
|
||||||
|
const userPrompt = this.buildUserPrompt(rawPrompt, options);
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
`[${timestamp}] Making API request to Gemini 2.5 Flash for prompt enhancement...`,
|
||||||
|
);
|
||||||
|
|
||||||
|
const response = await this.ai.models.generateContent({
|
||||||
|
model: this.model,
|
||||||
|
config: { responseModalities: ["TEXT"] },
|
||||||
|
contents: [
|
||||||
|
{
|
||||||
|
role: "user" as const,
|
||||||
|
parts: [{ text: `${systemPrompt}\n\n${userPrompt}` }],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
if (
|
||||||
|
response.candidates &&
|
||||||
|
response.candidates[0] &&
|
||||||
|
response.candidates[0].content
|
||||||
|
) {
|
||||||
|
const content = response.candidates[0].content;
|
||||||
|
const enhancedText = content.parts?.[0]?.text || "";
|
||||||
|
|
||||||
|
console.log(`[${timestamp}] Enhanced prompt generated successfully`);
|
||||||
|
|
||||||
|
const result = this.parseEnhancedResponse(
|
||||||
|
enhancedText,
|
||||||
|
rawPrompt,
|
||||||
|
options,
|
||||||
|
);
|
||||||
|
|
||||||
|
const enhancementResult: PromptEnhancementResult = {
|
||||||
|
success: true,
|
||||||
|
originalPrompt: rawPrompt,
|
||||||
|
enhancedPrompt: result.enhancedPrompt,
|
||||||
|
...(result.detectedLanguage && {
|
||||||
|
detectedLanguage: result.detectedLanguage,
|
||||||
|
}),
|
||||||
|
...(result.appliedTemplate && {
|
||||||
|
appliedTemplate: result.appliedTemplate,
|
||||||
|
}),
|
||||||
|
metadata: {
|
||||||
|
...(options.imageStyle && { style: options.imageStyle }),
|
||||||
|
...(!options.imageStyle &&
|
||||||
|
result.detectedStyle && { style: result.detectedStyle }),
|
||||||
|
...(options.aspectRatio && { aspectRatio: options.aspectRatio }),
|
||||||
|
enhancements: result.enhancements,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
return enhancementResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
originalPrompt: rawPrompt,
|
||||||
|
error: "No enhanced prompt received from API",
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`[${timestamp}] Prompt enhancement failed:`, error);
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
originalPrompt: rawPrompt,
|
||||||
|
error: error instanceof Error ? error.message : "Enhancement failed",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private buildSystemPrompt(options: PromptEnhancementOptions): string {
|
||||||
|
const {
|
||||||
|
imageStyle,
|
||||||
|
aspectRatio,
|
||||||
|
mood,
|
||||||
|
lighting,
|
||||||
|
cameraAngle,
|
||||||
|
negativePrompts,
|
||||||
|
} = options;
|
||||||
|
|
||||||
|
return `You are an expert AI prompt engineer specializing in transforming rough, unstructured prompts into professional, detailed prompts for the Gemini Flash Image Generation model. Your goal is to follow these principles:
|
||||||
|
|
||||||
|
CORE PRINCIPLE: Describe the scene, don't just list keywords. Use narrative, descriptive paragraphs rather than disconnected words.
|
||||||
|
|
||||||
|
ENHANCEMENT GUIDELINES:
|
||||||
|
1. Transform any language into professional English
|
||||||
|
2. Be hyper-specific with details instead of vague descriptions
|
||||||
|
3. Use photography and cinematic language for composition control
|
||||||
|
4. Provide context and intent for better understanding
|
||||||
|
5. Apply the appropriate template based on the desired style
|
||||||
|
|
||||||
|
STYLE TEMPLATES:
|
||||||
|
- Photorealistic: Use photography terms (camera angles, lens types, lighting, fine details)
|
||||||
|
- Illustration: Specify art style, line work, color palette, shading technique
|
||||||
|
- Minimalist: Focus on negative space, simple composition, subtle elements
|
||||||
|
- Sticker: Emphasize style (kawaii, bold outlines, clean design), transparent background
|
||||||
|
- Product: Studio lighting setup, commercial photography terms, surfaces, angles
|
||||||
|
- Comic: Panel style, art technique, mood, dialogue/caption integration
|
||||||
|
|
||||||
|
TECHNICAL REQUIREMENTS:
|
||||||
|
${imageStyle ? `- Target Style: ${imageStyle}` : "- Auto-detect and apply appropriate style"}
|
||||||
|
${aspectRatio ? `- Aspect Ratio: ${aspectRatio}` : "- Use square format unless context suggests otherwise"}
|
||||||
|
${mood ? `- Mood: ${mood}` : ""}
|
||||||
|
${lighting ? `- Lighting: ${lighting}` : ""}
|
||||||
|
${cameraAngle ? `- Camera Angle: ${cameraAngle}` : ""}
|
||||||
|
${negativePrompts && negativePrompts.length > 0 ? `- Avoid: ${negativePrompts.join(", ")}` : ""}
|
||||||
|
|
||||||
|
RESPONSE FORMAT:
|
||||||
|
Provide only the enhanced prompt as a single, cohesive paragraph. Do not include explanations, metadata, or multiple options. The response should be ready to use directly for image generation.
|
||||||
|
|
||||||
|
Remember: More detail equals more control. Transform vague concepts into vivid, specific descriptions that guide the model toward the exact image envisioned.`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private buildUserPrompt(
|
||||||
|
rawPrompt: string,
|
||||||
|
options: PromptEnhancementOptions,
|
||||||
|
): string {
|
||||||
|
let prompt = `Transform this prompt into a professional image generation prompt: "${rawPrompt}"`;
|
||||||
|
|
||||||
|
if (options.imageStyle) {
|
||||||
|
prompt += `\n\nTarget style: ${options.imageStyle}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.aspectRatio) {
|
||||||
|
prompt += `\n\nAspect ratio: ${options.aspectRatio}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return prompt;
|
||||||
|
}
|
||||||
|
|
||||||
|
private parseEnhancedResponse(
|
||||||
|
enhancedText: string,
|
||||||
|
originalPrompt: string,
|
||||||
|
options: PromptEnhancementOptions,
|
||||||
|
): {
|
||||||
|
enhancedPrompt: string;
|
||||||
|
detectedLanguage?: string;
|
||||||
|
appliedTemplate?: string;
|
||||||
|
detectedStyle?: string;
|
||||||
|
enhancements: string[];
|
||||||
|
} {
|
||||||
|
const enhancements: string[] = [];
|
||||||
|
|
||||||
|
// Clean up the enhanced text
|
||||||
|
const enhancedPrompt = enhancedText.trim();
|
||||||
|
|
||||||
|
// Detect applied enhancements
|
||||||
|
if (enhancedPrompt.length > originalPrompt.length * 1.5) {
|
||||||
|
enhancements.push("Added detailed descriptions");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
enhancedPrompt.includes("photorealistic") ||
|
||||||
|
enhancedPrompt.includes("shot") ||
|
||||||
|
enhancedPrompt.includes("lens")
|
||||||
|
) {
|
||||||
|
enhancements.push("Applied photography terminology");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
enhancedPrompt.includes("lighting") ||
|
||||||
|
enhancedPrompt.includes("illuminated")
|
||||||
|
) {
|
||||||
|
enhancements.push("Enhanced lighting description");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
enhancedPrompt.includes("texture") ||
|
||||||
|
enhancedPrompt.includes("surface")
|
||||||
|
) {
|
||||||
|
enhancements.push("Added texture details");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to detect the language of original prompt (simple heuristic)
|
||||||
|
const detectedLanguage = this.detectLanguage(originalPrompt);
|
||||||
|
|
||||||
|
// Detect applied template based on content
|
||||||
|
let appliedTemplate = "general";
|
||||||
|
if (options.imageStyle) {
|
||||||
|
appliedTemplate = options.imageStyle;
|
||||||
|
} else if (
|
||||||
|
enhancedPrompt.includes("photorealistic") ||
|
||||||
|
enhancedPrompt.includes("camera")
|
||||||
|
) {
|
||||||
|
appliedTemplate = "photorealistic";
|
||||||
|
} else if (
|
||||||
|
enhancedPrompt.includes("sticker") ||
|
||||||
|
enhancedPrompt.includes("kawaii")
|
||||||
|
) {
|
||||||
|
appliedTemplate = "sticker";
|
||||||
|
} else if (
|
||||||
|
enhancedPrompt.includes("minimalist") ||
|
||||||
|
enhancedPrompt.includes("negative space")
|
||||||
|
) {
|
||||||
|
appliedTemplate = "minimalist";
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
enhancedPrompt,
|
||||||
|
detectedLanguage,
|
||||||
|
appliedTemplate,
|
||||||
|
detectedStyle: appliedTemplate,
|
||||||
|
enhancements,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private detectLanguage(text: string): string {
|
||||||
|
// Simple language detection heuristics
|
||||||
|
if (/[\u4e00-\u9fff]/.test(text)) return "Chinese";
|
||||||
|
if (/[\u3040-\u309f\u30a0-\u30ff]/.test(text)) return "Japanese";
|
||||||
|
if (/[\uac00-\ud7af]/.test(text)) return "Korean";
|
||||||
|
if (/[àáâãäåæçèéêëìíîïðñòóôõöøùúûüýþÿ]/.test(text))
|
||||||
|
return "Romance Language";
|
||||||
|
if (/[а-яё]/.test(text.toLowerCase())) return "Russian";
|
||||||
|
if (/[α-ωΑ-Ω]/.test(text)) return "Greek";
|
||||||
|
if (/[أ-ي]/.test(text)) return "Arabic";
|
||||||
|
if (/[א-ת]/.test(text)) return "Hebrew";
|
||||||
|
return "English";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -15,6 +15,13 @@ export interface GenerateImageResponse {
|
||||||
description?: string;
|
description?: string;
|
||||||
model: string;
|
model: string;
|
||||||
generatedAt: string;
|
generatedAt: string;
|
||||||
|
promptEnhancement?: {
|
||||||
|
originalPrompt: string;
|
||||||
|
enhancedPrompt: string;
|
||||||
|
detectedLanguage?: string;
|
||||||
|
appliedTemplate?: string;
|
||||||
|
enhancements: string[];
|
||||||
|
};
|
||||||
};
|
};
|
||||||
error?: string;
|
error?: string;
|
||||||
}
|
}
|
||||||
|
|
@ -55,6 +62,59 @@ export interface LogContext {
|
||||||
timestamp: string;
|
timestamp: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Prompt Enhancement types
|
||||||
|
export interface PromptEnhancementRequest {
|
||||||
|
prompt: string;
|
||||||
|
options?: {
|
||||||
|
imageStyle?:
|
||||||
|
| "photorealistic"
|
||||||
|
| "illustration"
|
||||||
|
| "minimalist"
|
||||||
|
| "sticker"
|
||||||
|
| "product"
|
||||||
|
| "comic";
|
||||||
|
aspectRatio?: "square" | "portrait" | "landscape" | "wide" | "ultrawide";
|
||||||
|
mood?: string;
|
||||||
|
lighting?: string;
|
||||||
|
cameraAngle?: string;
|
||||||
|
outputFormat?: "text" | "markdown" | "detailed";
|
||||||
|
negativePrompts?: string[];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PromptEnhancementResponse {
|
||||||
|
success: boolean;
|
||||||
|
originalPrompt: string;
|
||||||
|
enhancedPrompt?: string;
|
||||||
|
detectedLanguage?: string;
|
||||||
|
appliedTemplate?: string;
|
||||||
|
metadata?: {
|
||||||
|
style?: string;
|
||||||
|
aspectRatio?: string;
|
||||||
|
enhancements: string[];
|
||||||
|
};
|
||||||
|
error?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enhanced Generate Request (with auto-enhancement option)
|
||||||
|
export interface EnhancedGenerateImageRequest extends GenerateImageRequest {
|
||||||
|
autoEnhance?: boolean;
|
||||||
|
enhancementOptions?: {
|
||||||
|
imageStyle?:
|
||||||
|
| "photorealistic"
|
||||||
|
| "illustration"
|
||||||
|
| "minimalist"
|
||||||
|
| "sticker"
|
||||||
|
| "product"
|
||||||
|
| "comic";
|
||||||
|
aspectRatio?: "square" | "portrait" | "landscape" | "wide" | "ultrawide";
|
||||||
|
mood?: string;
|
||||||
|
lighting?: string;
|
||||||
|
cameraAngle?: string;
|
||||||
|
negativePrompts?: string[];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// Environment configuration
|
// Environment configuration
|
||||||
export interface Config {
|
export interface Config {
|
||||||
port: number;
|
port: number;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,127 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Test script for Banatie Prompt Enhancement API endpoints
|
||||||
|
BASE_URL="http://localhost:3000"
|
||||||
|
|
||||||
|
echo "🧪 Testing Banatie Prompt Enhancement API"
|
||||||
|
echo "========================================="
|
||||||
|
|
||||||
|
# Test 1: Health check first
|
||||||
|
echo "1. Testing health endpoint..."
|
||||||
|
curl -s "$BASE_URL/health" | jq '.status' || echo "Health endpoint failed"
|
||||||
|
echo -e "\n"
|
||||||
|
|
||||||
|
# Test 2: API info (should now include enhance endpoint)
|
||||||
|
echo "2. Testing API info endpoint..."
|
||||||
|
curl -s "$BASE_URL/api/info" | jq '.endpoints' || echo "API info endpoint failed"
|
||||||
|
echo -e "\n"
|
||||||
|
|
||||||
|
# Test 3: Basic prompt enhancement
|
||||||
|
echo "3. Testing basic prompt enhancement..."
|
||||||
|
curl -s -X POST "$BASE_URL/api/enhance" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"prompt": "cat"
|
||||||
|
}' | jq '.' || echo "Basic enhancement test failed"
|
||||||
|
echo -e "\n"
|
||||||
|
|
||||||
|
# Test 4: Prompt enhancement with options
|
||||||
|
echo "4. Testing prompt enhancement with options..."
|
||||||
|
curl -s -X POST "$BASE_URL/api/enhance" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"prompt": "beautiful sunset",
|
||||||
|
"options": {
|
||||||
|
"imageStyle": "photorealistic",
|
||||||
|
"aspectRatio": "landscape",
|
||||||
|
"mood": "serene",
|
||||||
|
"lighting": "golden hour"
|
||||||
|
}
|
||||||
|
}' | jq '.' || echo "Enhancement with options test failed"
|
||||||
|
echo -e "\n"
|
||||||
|
|
||||||
|
# Test 5: Multilingual prompt enhancement (Spanish)
|
||||||
|
echo "5. Testing Spanish prompt enhancement..."
|
||||||
|
curl -s -X POST "$BASE_URL/api/enhance" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"prompt": "un gato hermoso con ojos azules",
|
||||||
|
"options": {
|
||||||
|
"imageStyle": "illustration"
|
||||||
|
}
|
||||||
|
}' | jq '.' || echo "Spanish enhancement test failed"
|
||||||
|
echo -e "\n"
|
||||||
|
|
||||||
|
# Test 6: Multilingual prompt enhancement (Chinese)
|
||||||
|
echo "6. Testing Chinese prompt enhancement..."
|
||||||
|
curl -s -X POST "$BASE_URL/api/enhance" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"prompt": "美丽的山景",
|
||||||
|
"options": {
|
||||||
|
"imageStyle": "minimalist"
|
||||||
|
}
|
||||||
|
}' | jq '.' || echo "Chinese enhancement test failed"
|
||||||
|
echo -e "\n"
|
||||||
|
|
||||||
|
# Test 7: Sticker style prompt
|
||||||
|
echo "7. Testing sticker style enhancement..."
|
||||||
|
curl -s -X POST "$BASE_URL/api/enhance" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"prompt": "happy panda",
|
||||||
|
"options": {
|
||||||
|
"imageStyle": "sticker",
|
||||||
|
"negativePrompts": ["dark", "scary"]
|
||||||
|
}
|
||||||
|
}' | jq '.' || echo "Sticker style test failed"
|
||||||
|
echo -e "\n"
|
||||||
|
|
||||||
|
# Test 8: Validation error test (empty prompt)
|
||||||
|
echo "8. Testing validation error (empty prompt)..."
|
||||||
|
curl -s -X POST "$BASE_URL/api/enhance" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"prompt": ""
|
||||||
|
}' | jq '.' || echo "Validation error test failed"
|
||||||
|
echo -e "\n"
|
||||||
|
|
||||||
|
# Test 9: Validation error test (invalid image style)
|
||||||
|
echo "9. Testing validation error (invalid style)..."
|
||||||
|
curl -s -X POST "$BASE_URL/api/enhance" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"prompt": "test",
|
||||||
|
"options": {
|
||||||
|
"imageStyle": "invalid_style"
|
||||||
|
}
|
||||||
|
}' | jq '.' || echo "Invalid style test failed"
|
||||||
|
echo -e "\n"
|
||||||
|
|
||||||
|
# Test 10: Auto-enhancement in generate endpoint
|
||||||
|
echo "10. Testing auto-enhancement in generate endpoint..."
|
||||||
|
curl -s -X POST "$BASE_URL/api/generate" \
|
||||||
|
-F "prompt=gato bonito" \
|
||||||
|
-F "filename=test_autoenhance" \
|
||||||
|
-F "autoEnhance=true" \
|
||||||
|
-F "enhancementOptions[imageStyle]=photorealistic" | jq '.' || echo "Auto-enhancement test failed"
|
||||||
|
echo -e "\n"
|
||||||
|
|
||||||
|
# Test 11: Generate without auto-enhancement (normal behavior)
|
||||||
|
echo "11. Testing generate without auto-enhancement..."
|
||||||
|
curl -s -X POST "$BASE_URL/api/generate" \
|
||||||
|
-F "prompt=a simple cat" \
|
||||||
|
-F "filename=test_normal" | jq '.' || echo "Normal generate test failed"
|
||||||
|
echo -e "\n"
|
||||||
|
|
||||||
|
echo "✅ Enhancement API tests completed!"
|
||||||
|
echo ""
|
||||||
|
echo "📋 Test Summary:"
|
||||||
|
echo "- Basic enhancement functionality"
|
||||||
|
echo "- Enhancement with styling options"
|
||||||
|
echo "- Multilingual support (Spanish, Chinese)"
|
||||||
|
echo "- Different image styles (photorealistic, illustration, minimalist, sticker)"
|
||||||
|
echo "- Validation error handling"
|
||||||
|
echo "- Auto-enhancement integration with generate endpoint"
|
||||||
|
echo ""
|
||||||
|
echo "💡 To run with API key, ensure GEMINI_API_KEY is set in your environment"
|
||||||
Loading…
Reference in New Issue