fix: flow creation
This commit is contained in:
parent
3cd7eb316d
commit
6235736f4f
|
|
@ -1,5 +1,5 @@
|
||||||
import { randomUUID } from 'crypto';
|
import { randomUUID } from 'crypto';
|
||||||
import { eq, desc, count } from 'drizzle-orm';
|
import { eq, desc, count, and, isNull, inArray } from 'drizzle-orm';
|
||||||
import { db } from '@/db';
|
import { db } from '@/db';
|
||||||
import { generations, flows, images } from '@banatie/database';
|
import { generations, flows, images } from '@banatie/database';
|
||||||
import type {
|
import type {
|
||||||
|
|
@ -58,17 +58,42 @@ export class GenerationService {
|
||||||
// Merge: manual references first, then auto-detected (remove duplicates)
|
// Merge: manual references first, then auto-detected (remove duplicates)
|
||||||
const allReferences = Array.from(new Set([...manualReferences, ...autoDetectedAliases]));
|
const allReferences = Array.from(new Set([...manualReferences, ...autoDetectedAliases]));
|
||||||
|
|
||||||
// FlowId logic (Section 10.1):
|
// FlowId logic (Section 10.1 - UPDATED FOR LAZY PATTERN):
|
||||||
// - If undefined (not provided) → generate new UUID
|
// - If undefined → generate UUID for pendingFlowId, flowId = null (lazy)
|
||||||
// - If null (explicitly null) → keep null
|
// - If null → flowId = null, pendingFlowId = null (explicitly no flow)
|
||||||
// - If string (specific value) → use that value
|
// - If string → flowId = string, pendingFlowId = null (use provided, create if needed)
|
||||||
let finalFlowId: string | null;
|
let finalFlowId: string | null;
|
||||||
|
let pendingFlowId: string | null = null;
|
||||||
|
|
||||||
if (params.flowId === undefined) {
|
if (params.flowId === undefined) {
|
||||||
finalFlowId = randomUUID();
|
// Lazy pattern: defer flow creation until needed
|
||||||
} else if (params.flowId === null) {
|
pendingFlowId = randomUUID();
|
||||||
finalFlowId = null;
|
finalFlowId = null;
|
||||||
|
} else if (params.flowId === null) {
|
||||||
|
// Explicitly no flow
|
||||||
|
finalFlowId = null;
|
||||||
|
pendingFlowId = null;
|
||||||
} else {
|
} else {
|
||||||
|
// Specific flowId provided - ensure flow exists (eager creation)
|
||||||
finalFlowId = params.flowId;
|
finalFlowId = params.flowId;
|
||||||
|
pendingFlowId = null;
|
||||||
|
|
||||||
|
// 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: {},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Link any pending generations to this new flow
|
||||||
|
await this.linkPendingGenerationsToFlow(finalFlowId, params.projectId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prompt semantics (Section 2.1):
|
// Prompt semantics (Section 2.1):
|
||||||
|
|
@ -80,6 +105,7 @@ export class GenerationService {
|
||||||
const generationRecord: NewGeneration = {
|
const generationRecord: NewGeneration = {
|
||||||
projectId: params.projectId,
|
projectId: params.projectId,
|
||||||
flowId: finalFlowId,
|
flowId: finalFlowId,
|
||||||
|
pendingFlowId: pendingFlowId,
|
||||||
apiKeyId: params.apiKeyId,
|
apiKeyId: params.apiKeyId,
|
||||||
status: 'pending',
|
status: 'pending',
|
||||||
prompt: usedPrompt, // Prompt actually used for generation
|
prompt: usedPrompt, // Prompt actually used for generation
|
||||||
|
|
@ -159,29 +185,41 @@ export class GenerationService {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Eager flow creation if flowAlias is provided (Section 4.2)
|
// Eager flow creation if flowAlias is provided (Section 4.2)
|
||||||
if (params.flowAlias && finalFlowId) {
|
if (params.flowAlias) {
|
||||||
|
// If we have pendingFlowId, create flow and link pending generations
|
||||||
|
const flowIdToUse = pendingFlowId || finalFlowId;
|
||||||
|
|
||||||
|
if (!flowIdToUse) {
|
||||||
|
throw new Error('Cannot create flow: no flowId available');
|
||||||
|
}
|
||||||
|
|
||||||
// Check if flow exists, create if not
|
// Check if flow exists, create if not
|
||||||
const existingFlow = await db.query.flows.findFirst({
|
const existingFlow = await db.query.flows.findFirst({
|
||||||
where: eq(flows.id, finalFlowId),
|
where: eq(flows.id, flowIdToUse),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!existingFlow) {
|
if (!existingFlow) {
|
||||||
await db.insert(flows).values({
|
await db.insert(flows).values({
|
||||||
id: finalFlowId,
|
id: flowIdToUse,
|
||||||
projectId: params.projectId,
|
projectId: params.projectId,
|
||||||
aliases: {},
|
aliases: {},
|
||||||
meta: {},
|
meta: {},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Link any pending generations to this new flow
|
||||||
|
await this.linkPendingGenerationsToFlow(flowIdToUse, params.projectId);
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.assignFlowAlias(finalFlowId, params.flowAlias, imageRecord.id);
|
await this.assignFlowAlias(flowIdToUse, params.flowAlias, imageRecord.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (finalFlowId) {
|
// Update flow timestamp if flow was created (either from finalFlowId or pendingFlowId converted to flow)
|
||||||
|
const actualFlowId = finalFlowId || (pendingFlowId && params.flowAlias ? pendingFlowId : null);
|
||||||
|
if (actualFlowId) {
|
||||||
await db
|
await db
|
||||||
.update(flows)
|
.update(flows)
|
||||||
.set({ updatedAt: new Date() })
|
.set({ updatedAt: new Date() })
|
||||||
.where(eq(flows.id, finalFlowId));
|
.where(eq(flows.id, actualFlowId));
|
||||||
}
|
}
|
||||||
|
|
||||||
const processingTime = Date.now() - startTime;
|
const processingTime = Date.now() - startTime;
|
||||||
|
|
@ -278,6 +316,56 @@ export class GenerationService {
|
||||||
.where(eq(flows.id, flowId));
|
.where(eq(flows.id, flowId));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async linkPendingGenerationsToFlow(
|
||||||
|
flowId: string,
|
||||||
|
projectId: string
|
||||||
|
): Promise<void> {
|
||||||
|
// Find all generations with pendingFlowId matching this flowId
|
||||||
|
const pendingGens = await db.query.generations.findMany({
|
||||||
|
where: and(
|
||||||
|
eq(generations.pendingFlowId, flowId),
|
||||||
|
eq(generations.projectId, projectId)
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (pendingGens.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update generations: set flowId and clear pendingFlowId
|
||||||
|
await db
|
||||||
|
.update(generations)
|
||||||
|
.set({
|
||||||
|
flowId: flowId,
|
||||||
|
pendingFlowId: null,
|
||||||
|
updatedAt: new Date(),
|
||||||
|
})
|
||||||
|
.where(
|
||||||
|
and(
|
||||||
|
eq(generations.pendingFlowId, flowId),
|
||||||
|
eq(generations.projectId, projectId)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Also update associated images to have the flowId
|
||||||
|
const generationIds = pendingGens.map(g => g.id);
|
||||||
|
if (generationIds.length > 0) {
|
||||||
|
await db
|
||||||
|
.update(images)
|
||||||
|
.set({
|
||||||
|
flowId: flowId,
|
||||||
|
updatedAt: new Date(),
|
||||||
|
})
|
||||||
|
.where(
|
||||||
|
and(
|
||||||
|
eq(images.projectId, projectId),
|
||||||
|
isNull(images.flowId),
|
||||||
|
inArray(images.generationId, generationIds)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async updateStatus(
|
private async updateStatus(
|
||||||
id: string,
|
id: string,
|
||||||
status: 'pending' | 'processing' | 'success' | 'failed',
|
status: 'pending' | 'processing' | 'success' | 'failed',
|
||||||
|
|
|
||||||
|
|
@ -245,7 +245,7 @@ export interface ErrorResponse {
|
||||||
export const toGenerationResponse = (gen: GenerationWithRelations): GenerationResponse => ({
|
export const toGenerationResponse = (gen: GenerationWithRelations): GenerationResponse => ({
|
||||||
id: gen.id,
|
id: gen.id,
|
||||||
projectId: gen.projectId,
|
projectId: gen.projectId,
|
||||||
flowId: gen.flowId,
|
flowId: gen.flowId ?? gen.pendingFlowId ?? null, // Return actual flowId or pendingFlowId for client
|
||||||
prompt: gen.prompt, // Prompt actually used
|
prompt: gen.prompt, // Prompt actually used
|
||||||
originalPrompt: gen.originalPrompt, // User's original (null if not enhanced)
|
originalPrompt: gen.originalPrompt, // User's original (null if not enhanced)
|
||||||
aspectRatio: gen.aspectRatio,
|
aspectRatio: gen.aspectRatio,
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,7 @@ export const generations = pgTable(
|
||||||
.notNull()
|
.notNull()
|
||||||
.references(() => projects.id, { onDelete: 'cascade' }),
|
.references(() => projects.id, { onDelete: 'cascade' }),
|
||||||
flowId: uuid('flow_id').references(() => flows.id, { onDelete: 'set null' }),
|
flowId: uuid('flow_id').references(() => flows.id, { onDelete: 'set null' }),
|
||||||
|
pendingFlowId: text('pending_flow_id'), // Temporary UUID for lazy flow pattern
|
||||||
apiKeyId: uuid('api_key_id').references(() => apiKeys.id, { onDelete: 'set null' }),
|
apiKeyId: uuid('api_key_id').references(() => apiKeys.id, { onDelete: 'set null' }),
|
||||||
|
|
||||||
// Status
|
// Status
|
||||||
|
|
@ -127,6 +128,11 @@ export const generations = pgTable(
|
||||||
.on(table.flowId, table.createdAt.desc())
|
.on(table.flowId, table.createdAt.desc())
|
||||||
.where(sql`${table.flowId} IS NOT NULL`),
|
.where(sql`${table.flowId} IS NOT NULL`),
|
||||||
|
|
||||||
|
// Index for pending flow-scoped generations (partial index)
|
||||||
|
pendingFlowIdx: index('idx_generations_pending_flow')
|
||||||
|
.on(table.pendingFlowId, table.createdAt.desc())
|
||||||
|
.where(sql`${table.pendingFlowId} IS NOT NULL`),
|
||||||
|
|
||||||
// Index for output image lookup
|
// Index for output image lookup
|
||||||
outputIdx: index('idx_generations_output').on(table.outputImageId),
|
outputIdx: index('idx_generations_output').on(table.outputImageId),
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
export const config = {
|
export const config = {
|
||||||
// API Configuration
|
// API Configuration
|
||||||
baseURL: 'http://localhost:3000',
|
baseURL: 'http://localhost:3000',
|
||||||
apiKey: 'bnt_71e7e16732ac5e21f597edc56e99e8c3696e713552ec9d1f44dfeffb2ef7c495',
|
apiKey: 'bnt_727d2f4f72bd03ed96da5278bb971a00cb0a2454d4d70f9748b5c39f3f69d88d',
|
||||||
|
|
||||||
// Paths
|
// Paths
|
||||||
resultsDir: '../../results',
|
resultsDir: '../../results',
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue