feat: add meta tags
This commit is contained in:
parent
f63c89a991
commit
7c31644824
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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) => ({
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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']
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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"]
|
||||
}
|
||||
}'
|
||||
```
|
||||
|
|
|
|||
Loading…
Reference in New Issue