feat: add meta tags

This commit is contained in:
Oleg Proskurin 2025-10-08 00:03:08 +07:00
parent f63c89a991
commit 7c31644824
10 changed files with 114 additions and 8 deletions

View File

@ -147,6 +147,28 @@ export const validateTextToImageRequest = (
}
}
// Validate meta (optional object)
if (req.body.meta !== undefined) {
if (
typeof req.body.meta !== "object" ||
Array.isArray(req.body.meta)
) {
errors.push("meta must be an object");
} else if (req.body.meta.tags !== undefined) {
if (!Array.isArray(req.body.meta.tags)) {
errors.push("meta.tags must be an array");
} else {
// Validate each tag is a string
for (const tag of req.body.meta.tags) {
if (typeof tag !== "string") {
errors.push("Each tag in meta.tags must be a string");
break;
}
}
}
}
}
// Check for XSS attempts in prompt
const xssPatterns = [
/<script/i,

View File

@ -52,7 +52,10 @@ export const autoEnhancePrompt = async (
const result = await promptEnhancementService.enhancePrompt(
prompt,
enhancementOptions || {},
{
...enhancementOptions,
...(req.body.meta?.tags && { tags: req.body.meta.tags }),
},
);
if (result.success && result.enhancedPrompt) {

View File

@ -63,7 +63,7 @@ generateRouter.post(
const timestamp = new Date().toISOString();
const requestId = req.requestId;
const { prompt, filename, aspectRatio } = req.body;
const { prompt, filename, aspectRatio, meta } = req.body;
const files = (req.files as Express.Multer.File[]) || [];
// Extract org/project slugs from validated API key
@ -112,6 +112,7 @@ generateRouter.post(
orgId,
projectId,
...(referenceImages && { referenceImages }),
...(meta && { meta }),
});
// Log the result

View File

@ -54,7 +54,7 @@ textToImageRouter.post(
const timestamp = new Date().toISOString();
const requestId = req.requestId;
const { prompt, filename, aspectRatio } = req.body;
const { prompt, filename, aspectRatio, meta } = req.body;
// Extract org/project slugs from validated API key
const orgId = req.apiKey?.organizationSlug || undefined;
@ -76,6 +76,7 @@ textToImageRouter.post(
...(aspectRatio && { aspectRatio }),
orgId,
projectId,
...(meta && { meta }),
});
// Log the result

View File

@ -29,7 +29,7 @@ export class ImageGenService {
async generateImage(
options: ImageGenerationOptions,
): Promise<ImageGenerationResult> {
const { prompt, filename, referenceImages, aspectRatio, orgId, projectId } = options;
const { prompt, filename, referenceImages, aspectRatio, orgId, projectId, meta } = options;
// Use default values if not provided
const finalOrgId = orgId || process.env["DEFAULT_ORG_ID"] || "default";
@ -47,6 +47,7 @@ export class ImageGenService {
finalAspectRatio,
finalOrgId,
finalProjectId,
meta,
);
generatedData = aiResult.generatedData;
geminiParams = aiResult.geminiParams;
@ -126,6 +127,7 @@ export class ImageGenService {
aspectRatio: string,
orgId: string,
projectId: string,
meta?: { tags?: string[] },
): Promise<{
generatedData: GeneratedImageData;
geminiParams: GeminiParams;
@ -185,6 +187,7 @@ export class ImageGenService {
prompt,
model: this.primaryModel,
config,
...(meta && { meta }),
...(referenceImages &&
referenceImages.length > 0 && {
referenceImages: referenceImages.map((img) => ({

View File

@ -9,6 +9,7 @@ export interface PromptEnhancementOptions {
| "product"
| "comic"
| "general";
tags?: string[]; // Optional tags - accepted but not used yet
}
export interface PromptEnhancementResult {
@ -52,6 +53,9 @@ export class PromptEnhancementService {
`[${timestamp}] Starting prompt enhancement for: "${rawPrompt.substring(0, 50)}..."`,
);
console.log(`[${timestamp}] Using template: ${finalOptions.template}`);
if (finalOptions.tags && finalOptions.tags.length > 0) {
console.log(`[${timestamp}] Tags: ${finalOptions.tags.join(", ")}`);
}
try {
const systemPrompt = this.buildSystemPrompt(finalOptions);
@ -151,6 +155,8 @@ TECHNICAL REQUIREMENTS:
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.
CRITICAL: the prompt length MUST be under 2000 characters length. Contract the prompts if they're longer
Remember: More detail equals more control. Transform vague concepts into vivid, specific descriptions that guide the model toward the exact image envisioned.`;
}
@ -163,6 +169,10 @@ Remember: More detail equals more control. Transform vague concepts into vivid,
const selectedTemplate = options.template || "photorealistic";
prompt += `\n\nTarget template/style: ${selectedTemplate}`;
console.log(
"🚀 ~ PromptEnhancementService ~ buildUserPrompt ~ prompt:",
prompt,
);
return prompt;
}

View File

@ -1,4 +1,4 @@
import { writeFileSync, appendFileSync, existsSync, mkdirSync } from "fs";
import { writeFileSync, readFileSync, existsSync, mkdirSync } from "fs";
import { dirname } from "path";
export interface TTILogEntry {
@ -6,6 +6,9 @@ export interface TTILogEntry {
orgId: string;
projectId: string;
prompt: string;
meta?: {
tags?: string[];
};
referenceImages?: Array<{
mimetype: string;
size: number;
@ -65,15 +68,30 @@ export class TTILogger {
}
try {
const logEntry = this.formatLogEntry(entry);
appendFileSync(this.logFilePath, logEntry, { encoding: "utf-8" });
const newLogEntry = this.formatLogEntry(entry);
// Read existing content
const existingContent = existsSync(this.logFilePath)
? readFileSync(this.logFilePath, "utf-8")
: "# Text-to-Image Generation Log\n\n";
// Insert new entry AFTER header but BEFORE old entries
const headerEnd = existingContent.indexOf("\n\n") + 2;
const header = existingContent.slice(0, headerEnd);
const oldEntries = existingContent.slice(headerEnd);
writeFileSync(
this.logFilePath,
header + newLogEntry + oldEntries,
"utf-8",
);
} catch (error) {
console.error(`[TTILogger] Failed to write log entry:`, error);
}
}
private formatLogEntry(entry: TTILogEntry): string {
const { timestamp, orgId, projectId, prompt, referenceImages, model, config } = entry;
const { timestamp, orgId, projectId, prompt, meta, referenceImages, model, config } = entry;
// Format date from ISO timestamp
const date = new Date(timestamp);
@ -83,6 +101,11 @@ export class TTILogger {
logText += `${orgId}/${projectId}\n\n`;
logText += `**Prompt:** ${prompt}\n\n`;
// Add tags if present
if (meta?.tags && meta.tags.length > 0) {
logText += `**Tags:** ${meta.tags.join(", ")}\n\n`;
}
if (referenceImages && referenceImages.length > 0) {
logText += `**Reference Images:** ${referenceImages.length} image${referenceImages.length > 1 ? "s" : ""}\n`;
for (const img of referenceImages) {

View File

@ -21,6 +21,9 @@ export interface TextToImageRequest {
| "comic"
| "general"; // Defaults to "photorealistic"
};
meta?: {
tags?: string[]; // Optional array of tags for tracking/grouping (not stored, only logged)
};
}
export interface GenerateImageResponse {
@ -60,6 +63,9 @@ export interface ImageGenerationOptions {
orgId?: string;
projectId?: string;
userId?: string;
meta?: {
tags?: string[];
};
}
export interface ReferenceImage {
@ -155,6 +161,9 @@ export interface EnhancedGenerateImageRequest extends GenerateImageRequest {
| "comic"
| "general"; // Defaults to "photorealistic"
};
meta?: {
tags?: string[];
};
}
// Environment configuration

View File

@ -9,6 +9,11 @@ import { AdvancedOptionsModal, AdvancedOptionsData } from '@/components/demo/Adv
const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3000';
const API_KEY_STORAGE_KEY = 'banatie_demo_api_key';
// Generate random 6-character uppercase ID for pairing images
function generatePairId(): string {
return Math.random().toString(36).substring(2, 8).toUpperCase();
}
interface GenerationResult {
id: string;
timestamp: Date;
@ -39,6 +44,9 @@ interface GenerationResult {
};
enhancementOptions?: {
template?: string;
meta?: {
tags?: string[];
};
} & AdvancedOptionsData;
}
@ -201,6 +209,7 @@ export default function DemoTTIPage() {
setGenerationStartTime(startTime);
const resultId = Date.now().toString();
const pairId = generatePairId(); // NEW: Generate unique pair ID
const timestamp = new Date();
try {
@ -219,6 +228,9 @@ export default function DemoTTIPage() {
filename: `demo_${resultId}_left`,
aspectRatio,
autoEnhance: false, // Explicitly disable enhancement for left image
meta: {
tags: [pairId, 'simple'] // NEW: Pair ID + "simple" tag
}
}),
}),
fetch(`${API_BASE_URL}/api/text-to-image`, {
@ -235,6 +247,9 @@ export default function DemoTTIPage() {
enhancementOptions: {
template: template || 'photorealistic', // Only template parameter
},
meta: {
tags: [pairId, 'enhanced'] // NEW: Pair ID + "enhanced" tag
}
}),
}),
]);
@ -273,6 +288,9 @@ export default function DemoTTIPage() {
filename: `demo_${resultId}_left`,
aspectRatio,
autoEnhance: false,
meta: {
tags: [pairId, 'simple']
}
},
response: leftData,
geminiParams: leftData.data?.geminiParams || {},
@ -286,6 +304,9 @@ export default function DemoTTIPage() {
enhancementOptions: {
template: template || 'photorealistic',
},
meta: {
tags: [pairId, 'enhanced']
}
},
response: rightData,
geminiParams: rightData.data?.geminiParams || {},
@ -293,6 +314,9 @@ export default function DemoTTIPage() {
// Store enhancement options for display in inspect mode
enhancementOptions: {
template,
meta: {
tags: [pairId, 'enhanced']
}
},
};

View File

@ -356,6 +356,7 @@ Generate images from text prompts only using JSON payload. Simplified endpoint f
| `aspectRatio` | string | No | `"1:1"` | Image aspect ratio (`"1:1"`, `"2:3"`, `"3:2"`, `"3:4"`, `"4:3"`, `"4:5"`, `"5:4"`, `"9:16"`, `"16:9"`, `"21:9"`) |
| `autoEnhance` | boolean | No | `true` | Enable automatic prompt enhancement (set to `false` to use prompt as-is) |
| `enhancementOptions` | object | No | - | Enhancement configuration options |
| `meta` | object | No | - | Metadata for request tracking |
**Enhancement Options:**
@ -363,6 +364,12 @@ Generate images from text prompts only using JSON payload. Simplified endpoint f
|-------|------|----------|---------|-------------|
| `template` | string | No | `"photorealistic"` | Prompt engineering template: `"photorealistic"`, `"illustration"`, `"minimalist"`, `"sticker"`, `"product"`, `"comic"`, `"general"` |
**Meta Object:**
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `tags` | string[] | No | Array of string tags for tracking/grouping requests (not stored, only logged) |
**Example Request:**
```bash
curl -X POST http://localhost:3000/api/text-to-image \
@ -375,6 +382,9 @@ curl -X POST http://localhost:3000/api/text-to-image \
"autoEnhance": true,
"enhancementOptions": {
"template": "photorealistic"
},
"meta": {
"tags": ["demo", "sunset"]
}
}'
```