feat: phase 1 - parameter renames, auto-detection, and flowId logic
**Parameter Renames (Section 1.1):** - Rename `assignAlias` → `alias` in CreateGenerationRequest - Rename `assignFlowAlias` → `flowAlias` (changed from Record<string, string> to string) - Rename `flowAliases` → `flowAlias` in UploadImageRequest - Update all route handlers and service methods to use new names - Simplify flowAlias logic to assign single alias string to output image **Reference Image Auto-Detection (Section 1.2):** - Add `extractAliasesFromPrompt()` function with regex pattern: /(?:^|\s)(@[\w-]+)/g - Make `referenceImages` parameter optional - Auto-detect aliases from prompt text and merge with manual references - Manual references have priority (listed first), then auto-detected - Remove duplicates while preserving order - Invalid aliases are silently skipped (validated with isValidAliasFormat) **FlowId Response Logic (Section 10.1):** - If `flowId: undefined` (not provided) → generate new UUID, return in response - If `flowId: null` (explicitly null) → keep null, don't generate - If `flowId: "uuid"` (specific value) → use provided value - Eager flow creation when `flowAlias` is provided (create flow immediately in DB) **Generation Modification Endpoint (Section 9):** - Add `PUT /api/v1/generations/:id` endpoint - Modifiable fields: prompt, aspectRatio, flowId, meta - Non-generative params (flowId, meta) → update DB only - Generative params (prompt, aspectRatio) → update DB + trigger regeneration - FlowId management: null to detach, UUID to attach/change (with eager creation) - Regeneration updates existing image (same ID, same MinIO path) **Type Definitions:** - Update CreateGenerationParams interface with new parameter names - Add UpdateGenerationRequest interface - Add extractAliasesFromPrompt export to validators index **Documentation:** - Update REST API examples with new parameter names **Technical Notes:** - All Phase 1 changes are backward compatible at the data layer - TypeScript strict mode passes (no new errors introduced) - Pre-existing TypeScript errors in middleware and other routes remain unchanged 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
ed3931a2bd
commit
647f66db7a
|
|
@ -41,8 +41,8 @@ generationsRouter.post(
|
||||||
referenceImages,
|
referenceImages,
|
||||||
aspectRatio,
|
aspectRatio,
|
||||||
flowId,
|
flowId,
|
||||||
assignAlias,
|
alias,
|
||||||
assignFlowAlias,
|
flowAlias,
|
||||||
autoEnhance,
|
autoEnhance,
|
||||||
meta,
|
meta,
|
||||||
} = req.body;
|
} = req.body;
|
||||||
|
|
@ -68,8 +68,8 @@ generationsRouter.post(
|
||||||
referenceImages,
|
referenceImages,
|
||||||
aspectRatio,
|
aspectRatio,
|
||||||
flowId,
|
flowId,
|
||||||
assignAlias,
|
alias,
|
||||||
assignFlowAlias,
|
flowAlias,
|
||||||
autoEnhance,
|
autoEnhance,
|
||||||
meta,
|
meta,
|
||||||
requestId: req.requestId,
|
requestId: req.requestId,
|
||||||
|
|
@ -158,6 +158,58 @@ generationsRouter.get(
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PUT /api/v1/generations/:id
|
||||||
|
* Update generation parameters (prompt, aspectRatio, flowId, meta)
|
||||||
|
* Generative parameters (prompt, aspectRatio) trigger automatic regeneration
|
||||||
|
*/
|
||||||
|
generationsRouter.put(
|
||||||
|
'/:id',
|
||||||
|
validateApiKey,
|
||||||
|
requireProjectKey,
|
||||||
|
rateLimitByApiKey,
|
||||||
|
asyncHandler(async (req: any, res: Response<GetGenerationResponse>) => {
|
||||||
|
const service = getGenerationService();
|
||||||
|
const { id } = req.params;
|
||||||
|
const { prompt, aspectRatio, flowId, meta } = req.body;
|
||||||
|
|
||||||
|
const original = await service.getById(id);
|
||||||
|
if (!original) {
|
||||||
|
res.status(404).json({
|
||||||
|
success: false,
|
||||||
|
error: {
|
||||||
|
message: 'Generation not found',
|
||||||
|
code: 'GENERATION_NOT_FOUND',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (original.projectId !== req.apiKey.projectId) {
|
||||||
|
res.status(404).json({
|
||||||
|
success: false,
|
||||||
|
error: {
|
||||||
|
message: 'Generation not found',
|
||||||
|
code: 'GENERATION_NOT_FOUND',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const updated = await service.update(id, {
|
||||||
|
prompt,
|
||||||
|
aspectRatio,
|
||||||
|
flowId,
|
||||||
|
meta,
|
||||||
|
});
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
success: true,
|
||||||
|
data: toGenerationResponse(updated),
|
||||||
|
});
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* POST /api/v1/generations/:id/retry
|
* POST /api/v1/generations/:id/retry
|
||||||
* Retry a failed generation
|
* Retry a failed generation
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { randomUUID } from 'crypto';
|
||||||
import { eq, desc, count } from 'drizzle-orm';
|
import { eq, desc, count } from 'drizzle-orm';
|
||||||
import { db } from '@/db';
|
import { db } from '@/db';
|
||||||
import { generations, flows } from '@banatie/database';
|
import { generations, flows } from '@banatie/database';
|
||||||
|
|
@ -13,6 +14,7 @@ import { ImageGenService } from '../ImageGenService';
|
||||||
import { StorageFactory } from '../StorageFactory';
|
import { StorageFactory } from '../StorageFactory';
|
||||||
import { buildWhereClause, buildEqCondition } from '@/utils/helpers';
|
import { buildWhereClause, buildEqCondition } from '@/utils/helpers';
|
||||||
import { ERROR_MESSAGES, GENERATION_LIMITS } from '@/utils/constants';
|
import { ERROR_MESSAGES, GENERATION_LIMITS } from '@/utils/constants';
|
||||||
|
import { extractAliasesFromPrompt } from '@/utils/validators';
|
||||||
import type { ReferenceImage } from '@/types/api';
|
import type { ReferenceImage } from '@/types/api';
|
||||||
|
|
||||||
export interface CreateGenerationParams {
|
export interface CreateGenerationParams {
|
||||||
|
|
@ -22,8 +24,8 @@ export interface CreateGenerationParams {
|
||||||
referenceImages?: string[] | undefined; // Aliases to resolve
|
referenceImages?: string[] | undefined; // Aliases to resolve
|
||||||
aspectRatio?: string | undefined;
|
aspectRatio?: string | undefined;
|
||||||
flowId?: string | undefined;
|
flowId?: string | undefined;
|
||||||
assignAlias?: string | undefined;
|
alias?: string | undefined;
|
||||||
assignFlowAlias?: Record<string, string> | undefined;
|
flowAlias?: string | undefined;
|
||||||
autoEnhance?: boolean | undefined;
|
autoEnhance?: boolean | undefined;
|
||||||
enhancedPrompt?: string | undefined;
|
enhancedPrompt?: string | undefined;
|
||||||
meta?: Record<string, unknown> | undefined;
|
meta?: Record<string, unknown> | undefined;
|
||||||
|
|
@ -49,9 +51,29 @@ export class GenerationService {
|
||||||
async create(params: CreateGenerationParams): Promise<GenerationWithRelations> {
|
async create(params: CreateGenerationParams): Promise<GenerationWithRelations> {
|
||||||
const startTime = Date.now();
|
const startTime = Date.now();
|
||||||
|
|
||||||
|
// Auto-detect aliases from prompt and merge with manual references
|
||||||
|
const autoDetectedAliases = extractAliasesFromPrompt(params.prompt);
|
||||||
|
const manualReferences = params.referenceImages || [];
|
||||||
|
|
||||||
|
// Merge: manual references first, then auto-detected (remove duplicates)
|
||||||
|
const allReferences = Array.from(new Set([...manualReferences, ...autoDetectedAliases]));
|
||||||
|
|
||||||
|
// FlowId logic (Section 10.1):
|
||||||
|
// - If undefined (not provided) → generate new UUID
|
||||||
|
// - If null (explicitly null) → keep null
|
||||||
|
// - If string (specific value) → use that value
|
||||||
|
let finalFlowId: string | null;
|
||||||
|
if (params.flowId === undefined) {
|
||||||
|
finalFlowId = randomUUID();
|
||||||
|
} else if (params.flowId === null) {
|
||||||
|
finalFlowId = null;
|
||||||
|
} else {
|
||||||
|
finalFlowId = params.flowId;
|
||||||
|
}
|
||||||
|
|
||||||
const generationRecord: NewGeneration = {
|
const generationRecord: NewGeneration = {
|
||||||
projectId: params.projectId,
|
projectId: params.projectId,
|
||||||
flowId: params.flowId || null,
|
flowId: finalFlowId,
|
||||||
apiKeyId: params.apiKeyId,
|
apiKeyId: params.apiKeyId,
|
||||||
status: 'pending',
|
status: 'pending',
|
||||||
originalPrompt: params.prompt,
|
originalPrompt: params.prompt,
|
||||||
|
|
@ -77,9 +99,9 @@ export class GenerationService {
|
||||||
let referenceImageBuffers: ReferenceImage[] = [];
|
let referenceImageBuffers: ReferenceImage[] = [];
|
||||||
let referencedImagesMetadata: Array<{ imageId: string; alias: string }> = [];
|
let referencedImagesMetadata: Array<{ imageId: string; alias: string }> = [];
|
||||||
|
|
||||||
if (params.referenceImages && params.referenceImages.length > 0) {
|
if (allReferences.length > 0) {
|
||||||
const resolved = await this.resolveReferenceImages(
|
const resolved = await this.resolveReferenceImages(
|
||||||
params.referenceImages,
|
allReferences,
|
||||||
params.projectId,
|
params.projectId,
|
||||||
params.flowId
|
params.flowId
|
||||||
);
|
);
|
||||||
|
|
@ -117,7 +139,7 @@ export class GenerationService {
|
||||||
|
|
||||||
const imageRecord = await this.imageService.create({
|
const imageRecord = await this.imageService.create({
|
||||||
projectId: params.projectId,
|
projectId: params.projectId,
|
||||||
flowId: params.flowId || null,
|
flowId: finalFlowId,
|
||||||
generationId: generation.id,
|
generationId: generation.id,
|
||||||
apiKeyId: params.apiKeyId,
|
apiKeyId: params.apiKeyId,
|
||||||
storageKey,
|
storageKey,
|
||||||
|
|
@ -126,19 +148,34 @@ export class GenerationService {
|
||||||
fileSize: genResult.size || 0,
|
fileSize: genResult.size || 0,
|
||||||
fileHash,
|
fileHash,
|
||||||
source: 'generated',
|
source: 'generated',
|
||||||
alias: params.assignAlias || null,
|
alias: params.alias || null,
|
||||||
meta: params.meta || {},
|
meta: params.meta || {},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (params.assignFlowAlias && params.flowId) {
|
// Eager flow creation if flowAlias is provided (Section 4.2)
|
||||||
await this.assignFlowAliases(params.flowId, params.assignFlowAlias, imageRecord.id);
|
if (params.flowAlias && finalFlowId) {
|
||||||
|
// Check if flow exists, create if not
|
||||||
|
const existingFlow = await db.query.flows.findFirst({
|
||||||
|
where: eq(flows.id, finalFlowId),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!existingFlow) {
|
||||||
|
await db.insert(flows).values({
|
||||||
|
id: finalFlowId,
|
||||||
|
projectId: params.projectId,
|
||||||
|
aliases: {},
|
||||||
|
meta: {},
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (params.flowId) {
|
await this.assignFlowAlias(finalFlowId, params.flowAlias, imageRecord.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (finalFlowId) {
|
||||||
await db
|
await db
|
||||||
.update(flows)
|
.update(flows)
|
||||||
.set({ updatedAt: new Date() })
|
.set({ updatedAt: new Date() })
|
||||||
.where(eq(flows.id, params.flowId));
|
.where(eq(flows.id, finalFlowId));
|
||||||
}
|
}
|
||||||
|
|
||||||
const processingTime = Date.now() - startTime;
|
const processingTime = Date.now() - startTime;
|
||||||
|
|
@ -210,9 +247,9 @@ export class GenerationService {
|
||||||
return { buffers, metadata };
|
return { buffers, metadata };
|
||||||
}
|
}
|
||||||
|
|
||||||
private async assignFlowAliases(
|
private async assignFlowAlias(
|
||||||
flowId: string,
|
flowId: string,
|
||||||
flowAliases: Record<string, string>,
|
flowAlias: string,
|
||||||
imageId: string
|
imageId: string
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const flow = await db.query.flows.findFirst({
|
const flow = await db.query.flows.findFirst({
|
||||||
|
|
@ -226,11 +263,8 @@ export class GenerationService {
|
||||||
const currentAliases = (flow.aliases as Record<string, string>) || {};
|
const currentAliases = (flow.aliases as Record<string, string>) || {};
|
||||||
const updatedAliases = { ...currentAliases };
|
const updatedAliases = { ...currentAliases };
|
||||||
|
|
||||||
for (const [alias, value] of Object.entries(flowAliases)) {
|
// Assign the flow alias to the image
|
||||||
if (value === '@output' || value === imageId) {
|
updatedAliases[flowAlias] = imageId;
|
||||||
updatedAliases[alias] = imageId;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await db
|
await db
|
||||||
.update(flows)
|
.update(flows)
|
||||||
|
|
@ -364,6 +398,107 @@ export class GenerationService {
|
||||||
return newGeneration;
|
return newGeneration;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async update(
|
||||||
|
id: string,
|
||||||
|
updates: {
|
||||||
|
prompt?: string;
|
||||||
|
aspectRatio?: string;
|
||||||
|
flowId?: string | null;
|
||||||
|
meta?: Record<string, unknown>;
|
||||||
|
}
|
||||||
|
): Promise<GenerationWithRelations> {
|
||||||
|
const generation = await this.getById(id);
|
||||||
|
if (!generation) {
|
||||||
|
throw new Error(ERROR_MESSAGES.GENERATION_NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if generative parameters changed (prompt or aspectRatio)
|
||||||
|
const shouldRegenerate =
|
||||||
|
(updates.prompt !== undefined && updates.prompt !== generation.originalPrompt) ||
|
||||||
|
(updates.aspectRatio !== undefined && updates.aspectRatio !== generation.aspectRatio);
|
||||||
|
|
||||||
|
// Handle flowId change (Section 9.2)
|
||||||
|
if (updates.flowId !== undefined && updates.flowId !== null) {
|
||||||
|
// If flowId provided and not null, create flow if it doesn't exist (eager creation)
|
||||||
|
const existingFlow = await db.query.flows.findFirst({
|
||||||
|
where: eq(flows.id, updates.flowId),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!existingFlow) {
|
||||||
|
await db.insert(flows).values({
|
||||||
|
id: updates.flowId,
|
||||||
|
projectId: generation.projectId,
|
||||||
|
aliases: {},
|
||||||
|
meta: {},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update database fields
|
||||||
|
const updateData: Partial<NewGeneration> = {};
|
||||||
|
if (updates.prompt !== undefined) {
|
||||||
|
updateData.originalPrompt = updates.prompt;
|
||||||
|
}
|
||||||
|
if (updates.aspectRatio !== undefined) {
|
||||||
|
updateData.aspectRatio = updates.aspectRatio;
|
||||||
|
}
|
||||||
|
if (updates.flowId !== undefined) {
|
||||||
|
updateData.flowId = updates.flowId;
|
||||||
|
}
|
||||||
|
if (updates.meta !== undefined) {
|
||||||
|
updateData.meta = updates.meta;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Object.keys(updateData).length > 0) {
|
||||||
|
await db
|
||||||
|
.update(generations)
|
||||||
|
.set({ ...updateData, updatedAt: new Date() })
|
||||||
|
.where(eq(generations.id, id));
|
||||||
|
}
|
||||||
|
|
||||||
|
// If generative parameters changed, trigger regeneration
|
||||||
|
if (shouldRegenerate && generation.outputImageId) {
|
||||||
|
// Update status to processing
|
||||||
|
await this.updateStatus(id, 'processing');
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Use updated prompt/aspectRatio or fall back to existing
|
||||||
|
const promptToUse = updates.prompt || generation.originalPrompt;
|
||||||
|
const aspectRatioToUse = updates.aspectRatio || generation.aspectRatio || GENERATION_LIMITS.DEFAULT_ASPECT_RATIO;
|
||||||
|
|
||||||
|
// Regenerate image
|
||||||
|
const genResult = await this.imageGenService.generateImage({
|
||||||
|
prompt: promptToUse,
|
||||||
|
filename: `gen_${id}`,
|
||||||
|
referenceImages: [],
|
||||||
|
aspectRatio: aspectRatioToUse,
|
||||||
|
orgId: 'default',
|
||||||
|
projectId: generation.projectId,
|
||||||
|
meta: updates.meta || generation.meta || {},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!genResult.success) {
|
||||||
|
await this.updateStatus(id, 'failed', {
|
||||||
|
errorMessage: genResult.error || 'Regeneration failed',
|
||||||
|
});
|
||||||
|
throw new Error(genResult.error || 'Regeneration failed');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: Physical file in MinIO is overwritten by ImageGenService
|
||||||
|
// TODO: Update fileSize and other metadata when ImageService.update() supports it
|
||||||
|
|
||||||
|
await this.updateStatus(id, 'success');
|
||||||
|
} catch (error) {
|
||||||
|
await this.updateStatus(id, 'failed', {
|
||||||
|
errorMessage: error instanceof Error ? error.message : 'Unknown error',
|
||||||
|
});
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return await this.getByIdWithRelations(id);
|
||||||
|
}
|
||||||
|
|
||||||
async delete(id: string): Promise<void> {
|
async delete(id: string): Promise<void> {
|
||||||
const generation = await this.getById(id);
|
const generation = await this.getById(id);
|
||||||
if (!generation) {
|
if (!generation) {
|
||||||
|
|
|
||||||
|
|
@ -9,8 +9,8 @@ export interface CreateGenerationRequest {
|
||||||
referenceImages?: string[]; // Array of aliases to resolve
|
referenceImages?: string[]; // Array of aliases to resolve
|
||||||
aspectRatio?: string; // e.g., "1:1", "16:9", "3:2", "9:16"
|
aspectRatio?: string; // e.g., "1:1", "16:9", "3:2", "9:16"
|
||||||
flowId?: string;
|
flowId?: string;
|
||||||
assignAlias?: string; // Alias to assign to generated image
|
alias?: string; // Alias to assign to generated image
|
||||||
assignFlowAlias?: Record<string, string>; // Flow-scoped aliases to assign
|
flowAlias?: string; // Flow-scoped alias to assign
|
||||||
autoEnhance?: boolean;
|
autoEnhance?: boolean;
|
||||||
enhancementOptions?: {
|
enhancementOptions?: {
|
||||||
template?: 'photorealistic' | 'illustration' | 'minimalist' | 'sticker' | 'product' | 'comic' | 'general';
|
template?: 'photorealistic' | 'illustration' | 'minimalist' | 'sticker' | 'product' | 'comic' | 'general';
|
||||||
|
|
@ -31,6 +31,13 @@ export interface RetryGenerationRequest {
|
||||||
aspectRatio?: string; // Optional: override original aspect ratio
|
aspectRatio?: string; // Optional: override original aspect ratio
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface UpdateGenerationRequest {
|
||||||
|
prompt?: string; // Change prompt (triggers regeneration)
|
||||||
|
aspectRatio?: string; // Change aspect ratio (triggers regeneration)
|
||||||
|
flowId?: string | null; // Change/remove/add flow association (null to detach)
|
||||||
|
meta?: Record<string, unknown>; // Update metadata
|
||||||
|
}
|
||||||
|
|
||||||
// ========================================
|
// ========================================
|
||||||
// IMAGE ENDPOINTS
|
// IMAGE ENDPOINTS
|
||||||
// ========================================
|
// ========================================
|
||||||
|
|
@ -38,7 +45,7 @@ export interface RetryGenerationRequest {
|
||||||
export interface UploadImageRequest {
|
export interface UploadImageRequest {
|
||||||
alias?: string; // Project-scoped alias
|
alias?: string; // Project-scoped alias
|
||||||
flowId?: string;
|
flowId?: string;
|
||||||
flowAliases?: Record<string, string>; // Flow-scoped aliases
|
flowAlias?: string; // Flow-scoped alias
|
||||||
meta?: Record<string, unknown>;
|
meta?: Record<string, unknown>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,8 @@ import {
|
||||||
ALIAS_PATTERN,
|
ALIAS_PATTERN,
|
||||||
ALIAS_MAX_LENGTH,
|
ALIAS_MAX_LENGTH,
|
||||||
isReservedAlias,
|
isReservedAlias,
|
||||||
isTechnicalAlias
|
isTechnicalAlias,
|
||||||
|
isValidAliasFormat
|
||||||
} from '../constants/aliases';
|
} from '../constants/aliases';
|
||||||
import { ERROR_MESSAGES, ERROR_CODES } from '../constants/errors';
|
import { ERROR_MESSAGES, ERROR_CODES } from '../constants/errors';
|
||||||
|
|
||||||
|
|
@ -97,3 +98,31 @@ export const validateTechnicalAliasWithFlow = (
|
||||||
|
|
||||||
return { valid: true };
|
return { valid: true };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract all aliases from a prompt text
|
||||||
|
* Pattern: space followed by @ followed by alphanumeric, dash, or underscore
|
||||||
|
* Example: "Create image based on @hero and @background" -> ["@hero", "@background"]
|
||||||
|
*/
|
||||||
|
export const extractAliasesFromPrompt = (prompt: string): string[] => {
|
||||||
|
if (!prompt || typeof prompt !== 'string') {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pattern: space then @ then word characters (including dash and underscore)
|
||||||
|
// Also match @ at the beginning of the string
|
||||||
|
const aliasPattern = /(?:^|\s)(@[\w-]+)/g;
|
||||||
|
const matches: string[] = [];
|
||||||
|
let match;
|
||||||
|
|
||||||
|
while ((match = aliasPattern.exec(prompt)) !== null) {
|
||||||
|
const alias = match[1]!;
|
||||||
|
// Validate format and max length
|
||||||
|
if (isValidAliasFormat(alias)) {
|
||||||
|
matches.push(alias);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove duplicates while preserving order
|
||||||
|
return Array.from(new Set(matches));
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -14,8 +14,8 @@ X-API-Key: {{apiKey}}
|
||||||
{
|
{
|
||||||
"prompt": "A majestic eagle soaring over snow-capped mountains",
|
"prompt": "A majestic eagle soaring over snow-capped mountains",
|
||||||
"aspectRatio": "16:9",
|
"aspectRatio": "16:9",
|
||||||
"assignAlias": "@eagle-hero",
|
"alias": "@eagle-hero",
|
||||||
"assignFlowAlias": "@hero",
|
"flowAlias": "@hero",
|
||||||
"autoEnhance": true,
|
"autoEnhance": true,
|
||||||
"meta": {
|
"meta": {
|
||||||
"tags": ["demo", "nature"]
|
"tags": ["demo", "nature"]
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue