@zodic/shared 0.0.358 → 0.0.359
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -0,0 +1,631 @@
|
|
|
1
|
+
import { and, eq } from 'drizzle-orm';
|
|
2
|
+
import { inject, injectable } from 'inversify';
|
|
3
|
+
import { v4 } from 'uuid';
|
|
4
|
+
import { ImageDescriberService } from './ImageDescriberService';
|
|
5
|
+
import { AppContext } from '../base';
|
|
6
|
+
import { LeonardoService } from './LeonardoService';
|
|
7
|
+
import { ArtifactList, schema } from '../..';
|
|
8
|
+
import { sizes } from '../../types/scopes/legacy';
|
|
9
|
+
import { users } from '../../db/schema';
|
|
10
|
+
|
|
11
|
+
@injectable()
|
|
12
|
+
export class ArtifactService {
|
|
13
|
+
constructor(
|
|
14
|
+
@inject(AppContext) private context: AppContext,
|
|
15
|
+
@inject(LeonardoService) private leonardoService: LeonardoService,
|
|
16
|
+
@inject(ImageDescriberService)
|
|
17
|
+
private imageDescriberService: ImageDescriberService
|
|
18
|
+
) {}
|
|
19
|
+
|
|
20
|
+
private async log(
|
|
21
|
+
level: 'info' | 'debug' | 'warn' | 'error',
|
|
22
|
+
message: string,
|
|
23
|
+
context: Record<string, any> = {}
|
|
24
|
+
) {
|
|
25
|
+
const logId = `artifact-service:${Date.now()}`;
|
|
26
|
+
const logMessage = `[${level.toUpperCase()}] ${message}`;
|
|
27
|
+
|
|
28
|
+
console[level](logMessage, context);
|
|
29
|
+
const db = this.context.drizzle();
|
|
30
|
+
try {
|
|
31
|
+
await db
|
|
32
|
+
.insert(schema.logs)
|
|
33
|
+
.values({
|
|
34
|
+
id: logId,
|
|
35
|
+
level,
|
|
36
|
+
message,
|
|
37
|
+
context: JSON.stringify(context),
|
|
38
|
+
createdAt: new Date().getTime(),
|
|
39
|
+
})
|
|
40
|
+
.execute();
|
|
41
|
+
} catch (error) {
|
|
42
|
+
console.error('[ERROR] Failed to persist log to database:', {
|
|
43
|
+
error,
|
|
44
|
+
logId,
|
|
45
|
+
message,
|
|
46
|
+
rawContext: context,
|
|
47
|
+
stringifiedContext: JSON.stringify(context),
|
|
48
|
+
createdAt: new Date().getTime(),
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async generateCosmicMirror(
|
|
54
|
+
artifactId: string,
|
|
55
|
+
userId: string,
|
|
56
|
+
generatedImageId: string,
|
|
57
|
+
archetypeDataId: string
|
|
58
|
+
) {
|
|
59
|
+
const { height, width } = sizes['alchemy']['post4:5'];
|
|
60
|
+
|
|
61
|
+
await this.log('info', 'Starting cosmic mirror generation', {
|
|
62
|
+
artifactId,
|
|
63
|
+
userId,
|
|
64
|
+
generatedImageId,
|
|
65
|
+
archetypeDataId,
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
const drizzle = this.context.drizzle();
|
|
69
|
+
const {
|
|
70
|
+
artifacts,
|
|
71
|
+
userArtifacts,
|
|
72
|
+
generations,
|
|
73
|
+
userConcepts,
|
|
74
|
+
cosmicMirrorImages,
|
|
75
|
+
} = schema;
|
|
76
|
+
|
|
77
|
+
try {
|
|
78
|
+
// Fetch the artifact and its linked conceptId first
|
|
79
|
+
const [artifact] = await drizzle
|
|
80
|
+
.select({
|
|
81
|
+
id: artifacts.id,
|
|
82
|
+
conceptId: artifacts.conceptId,
|
|
83
|
+
})
|
|
84
|
+
.from(artifacts)
|
|
85
|
+
.where(eq(artifacts.slug, ArtifactList.COSMIC_MIRROR))
|
|
86
|
+
.limit(1);
|
|
87
|
+
|
|
88
|
+
if (!artifact || !artifact.id || !artifact.conceptId) {
|
|
89
|
+
return {
|
|
90
|
+
status: 'error',
|
|
91
|
+
error: 'Artifact or linked concept not found. Check registration.',
|
|
92
|
+
statusCode: 500,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Check for existing cosmic mirror artifact
|
|
97
|
+
const [existingArtifact] = await drizzle
|
|
98
|
+
.select()
|
|
99
|
+
.from(userArtifacts)
|
|
100
|
+
.where(
|
|
101
|
+
and(
|
|
102
|
+
eq(userArtifacts.userId, userId),
|
|
103
|
+
eq(userArtifacts.artifactId, artifact.id)
|
|
104
|
+
)
|
|
105
|
+
)
|
|
106
|
+
.limit(1);
|
|
107
|
+
|
|
108
|
+
if (existingArtifact) {
|
|
109
|
+
const artifactAge =
|
|
110
|
+
Date.now() - new Date(existingArtifact.createdAt).getTime();
|
|
111
|
+
const twentyMinutesMs = 20 * 60 * 1000;
|
|
112
|
+
|
|
113
|
+
if (
|
|
114
|
+
existingArtifact.status === 'pending' &&
|
|
115
|
+
artifactAge < twentyMinutesMs
|
|
116
|
+
) {
|
|
117
|
+
return {
|
|
118
|
+
status: 'error',
|
|
119
|
+
error:
|
|
120
|
+
'You already have a cosmic mirror being generated. Please wait for it to complete.',
|
|
121
|
+
statusCode: 400,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Check D1 for existing images
|
|
126
|
+
const existingImages = await drizzle
|
|
127
|
+
.select()
|
|
128
|
+
.from(cosmicMirrorImages)
|
|
129
|
+
.where(eq(cosmicMirrorImages.userArtifactId, existingArtifact.id));
|
|
130
|
+
|
|
131
|
+
if (existingImages.length >= 6 && artifactAge >= twentyMinutesMs) {
|
|
132
|
+
// If we have 6 images and artifact is old, trigger faceswaps
|
|
133
|
+
return await this.retryStaleCosmicMirror(existingArtifact.id);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Clean up old artifact if incomplete
|
|
137
|
+
if (artifactAge >= twentyMinutesMs) {
|
|
138
|
+
await this.cleanupStaleCosmicMirror(existingArtifact.id);
|
|
139
|
+
} else {
|
|
140
|
+
return {
|
|
141
|
+
status: 'error',
|
|
142
|
+
error: 'You can only have one cosmic mirror artifact at this time.',
|
|
143
|
+
statusCode: 400,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Fetch user information
|
|
149
|
+
const [user] = await drizzle
|
|
150
|
+
.select({
|
|
151
|
+
userPhotoId: users.userPhotoId,
|
|
152
|
+
userPhotoUrl: users.userPhotoUrl,
|
|
153
|
+
userGender: users.gender,
|
|
154
|
+
profileImage: users.profileImage,
|
|
155
|
+
})
|
|
156
|
+
.from(users)
|
|
157
|
+
.where(eq(users.id, userId))
|
|
158
|
+
.limit(1);
|
|
159
|
+
|
|
160
|
+
if (!user) {
|
|
161
|
+
return {
|
|
162
|
+
status: 'error',
|
|
163
|
+
error: 'User not found',
|
|
164
|
+
statusCode: 400,
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
let userPhotoId = user.userPhotoId;
|
|
169
|
+
let userPhotoUrl = user.userPhotoUrl;
|
|
170
|
+
|
|
171
|
+
// Upload profileImage to Leonardo if userPhotoId or userPhotoUrl is missing
|
|
172
|
+
if (!userPhotoId || !userPhotoUrl) {
|
|
173
|
+
if (!user.profileImage) {
|
|
174
|
+
return {
|
|
175
|
+
status: 'error',
|
|
176
|
+
error:
|
|
177
|
+
'User profile image not found. Please upload an image first.',
|
|
178
|
+
statusCode: 400,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
console.info(
|
|
183
|
+
`Uploading user profile image to Leonardo: ${user.profileImage}`
|
|
184
|
+
);
|
|
185
|
+
|
|
186
|
+
const uploadResponse = await this.leonardoService.uploadImageByUrl(
|
|
187
|
+
user.profileImage
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
if (!uploadResponse) {
|
|
191
|
+
return {
|
|
192
|
+
status: 'error',
|
|
193
|
+
error: 'Failed to upload user profile image to Leonardo',
|
|
194
|
+
statusCode: 500,
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
userPhotoId = uploadResponse.uploadedImageId;
|
|
199
|
+
userPhotoUrl = uploadResponse.uploadedImageUrl;
|
|
200
|
+
|
|
201
|
+
console.info('User profile image uploaded to Leonardo:', {
|
|
202
|
+
userPhotoId,
|
|
203
|
+
userPhotoUrl,
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
// Update user record with new photo data
|
|
207
|
+
await drizzle
|
|
208
|
+
.update(users)
|
|
209
|
+
.set({
|
|
210
|
+
userPhotoId,
|
|
211
|
+
userPhotoUrl,
|
|
212
|
+
updatedAt: new Date(),
|
|
213
|
+
})
|
|
214
|
+
.where(eq(users.id, userId));
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (!user.userGender) {
|
|
218
|
+
return {
|
|
219
|
+
status: 'error',
|
|
220
|
+
error: 'User gender not found. Please define a gender first.',
|
|
221
|
+
statusCode: 400,
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Fetch the user's concept with combinationId
|
|
226
|
+
const [userConcept] = await drizzle
|
|
227
|
+
.select({
|
|
228
|
+
id: userConcepts.id,
|
|
229
|
+
conceptCombinationId: userConcepts.conceptCombinationId,
|
|
230
|
+
})
|
|
231
|
+
.from(userConcepts)
|
|
232
|
+
.where(
|
|
233
|
+
and(
|
|
234
|
+
eq(userConcepts.userId, userId),
|
|
235
|
+
eq(userConcepts.conceptId, artifact.conceptId)
|
|
236
|
+
)
|
|
237
|
+
)
|
|
238
|
+
.limit(1);
|
|
239
|
+
|
|
240
|
+
if (!userConcept) {
|
|
241
|
+
return {
|
|
242
|
+
status: 'error',
|
|
243
|
+
error: 'User concept not found. Please generate the concept first.',
|
|
244
|
+
statusCode: 400,
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Get the archetype data record
|
|
249
|
+
const [archetypeData] = await drizzle
|
|
250
|
+
.select({
|
|
251
|
+
id: schema.archetypesData.id,
|
|
252
|
+
leonardoPrompt: schema.archetypesData.leonardoPrompt,
|
|
253
|
+
})
|
|
254
|
+
.from(schema.archetypesData)
|
|
255
|
+
.where(eq(schema.archetypesData.id, archetypeDataId))
|
|
256
|
+
.limit(1);
|
|
257
|
+
|
|
258
|
+
if (!archetypeData) {
|
|
259
|
+
return {
|
|
260
|
+
status: 'error',
|
|
261
|
+
error: 'Archetype data not found for this combination and gender.',
|
|
262
|
+
statusCode: 404,
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
if (!archetypeData.leonardoPrompt) {
|
|
267
|
+
return {
|
|
268
|
+
status: 'error',
|
|
269
|
+
error: `Archetype data is incomplete. Leonardo Prompt not found for ArchetypeDataId: ${archetypeData}`,
|
|
270
|
+
statusCode: 404,
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Create the user artifact with new schema
|
|
275
|
+
const userArtifactId = v4();
|
|
276
|
+
await drizzle.insert(userArtifacts).values({
|
|
277
|
+
id: userArtifactId,
|
|
278
|
+
userId,
|
|
279
|
+
artifactId: artifact.id,
|
|
280
|
+
userConceptId: userConcept.id,
|
|
281
|
+
conceptId: artifact.conceptId,
|
|
282
|
+
archetypeDataId: archetypeData.id,
|
|
283
|
+
postImages: '[]',
|
|
284
|
+
status: 'pending',
|
|
285
|
+
createdAt: new Date(),
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
// Describe the user photo
|
|
289
|
+
const traits = await this.imageDescriberService.describeImage({
|
|
290
|
+
imageUrl: userPhotoUrl,
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
// Personalize the Leonardo prompt
|
|
294
|
+
const personalizedPrompt =
|
|
295
|
+
await this.leonardoService.personalizeCosmicMirrorLeonardoPrompt({
|
|
296
|
+
leonardoPrompt: archetypeData.leonardoPrompt,
|
|
297
|
+
traits,
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
// First generation - with both control nets (content + character)
|
|
301
|
+
const generationResponse1 = await this.context
|
|
302
|
+
.api()
|
|
303
|
+
.callLeonardo.generateImage({
|
|
304
|
+
prompt: personalizedPrompt,
|
|
305
|
+
width,
|
|
306
|
+
height,
|
|
307
|
+
quantity: 3,
|
|
308
|
+
controlNets: [
|
|
309
|
+
{
|
|
310
|
+
initImageId: userPhotoId,
|
|
311
|
+
initImageType: 'UPLOADED',
|
|
312
|
+
preprocessorId: 133, // Character Reference
|
|
313
|
+
strengthType: 'High',
|
|
314
|
+
},
|
|
315
|
+
{
|
|
316
|
+
initImageId: generatedImageId,
|
|
317
|
+
initImageType: 'GENERATED',
|
|
318
|
+
preprocessorId: 100, // Content Reference
|
|
319
|
+
strengthType: 'Low',
|
|
320
|
+
},
|
|
321
|
+
],
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
if (
|
|
325
|
+
!generationResponse1 ||
|
|
326
|
+
!generationResponse1.sdGenerationJob ||
|
|
327
|
+
!generationResponse1.sdGenerationJob.generationId
|
|
328
|
+
) {
|
|
329
|
+
console.error('First Leonardo generation response is incorrect.');
|
|
330
|
+
return {
|
|
331
|
+
status: 'error',
|
|
332
|
+
error: 'First Leonardo generation failed.',
|
|
333
|
+
statusCode: 500,
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
const generationId1 = generationResponse1.sdGenerationJob.generationId;
|
|
338
|
+
await drizzle.insert(generations).values({
|
|
339
|
+
id: generationId1,
|
|
340
|
+
userId,
|
|
341
|
+
artifactId: artifact.id,
|
|
342
|
+
userArtifactId,
|
|
343
|
+
type: ArtifactList.COSMIC_MIRROR,
|
|
344
|
+
status: 'pending',
|
|
345
|
+
createdAt: new Date(),
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
// Second generation - with only character control net
|
|
349
|
+
const generationResponse2 = await this.context
|
|
350
|
+
.api()
|
|
351
|
+
.callLeonardo.generateImage({
|
|
352
|
+
prompt: personalizedPrompt,
|
|
353
|
+
width,
|
|
354
|
+
height,
|
|
355
|
+
quantity: 3,
|
|
356
|
+
controlNets: [
|
|
357
|
+
{
|
|
358
|
+
initImageId: userPhotoId,
|
|
359
|
+
initImageType: 'UPLOADED',
|
|
360
|
+
preprocessorId: 133, // Character Reference
|
|
361
|
+
strengthType: 'High',
|
|
362
|
+
},
|
|
363
|
+
],
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
if (
|
|
367
|
+
!generationResponse2 ||
|
|
368
|
+
!generationResponse2.sdGenerationJob ||
|
|
369
|
+
!generationResponse2.sdGenerationJob.generationId
|
|
370
|
+
) {
|
|
371
|
+
console.error('Second Leonardo generation response is incorrect.');
|
|
372
|
+
return {
|
|
373
|
+
status: 'error',
|
|
374
|
+
error: 'Second Leonardo generation failed.',
|
|
375
|
+
statusCode: 500,
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
const generationId2 = generationResponse2.sdGenerationJob.generationId;
|
|
380
|
+
await drizzle.insert(generations).values({
|
|
381
|
+
id: generationId2,
|
|
382
|
+
userId,
|
|
383
|
+
artifactId: artifact.id,
|
|
384
|
+
userArtifactId,
|
|
385
|
+
type: ArtifactList.COSMIC_MIRROR,
|
|
386
|
+
status: 'pending',
|
|
387
|
+
createdAt: new Date(),
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
return {
|
|
391
|
+
status: 'success',
|
|
392
|
+
data: {
|
|
393
|
+
generations: {
|
|
394
|
+
contentCharacter: generationResponse1,
|
|
395
|
+
characterOnly: generationResponse2,
|
|
396
|
+
},
|
|
397
|
+
userArtifactId,
|
|
398
|
+
},
|
|
399
|
+
statusCode: 200,
|
|
400
|
+
};
|
|
401
|
+
} catch (error) {
|
|
402
|
+
console.error('Error generating user artifact:', error);
|
|
403
|
+
return {
|
|
404
|
+
status: 'error',
|
|
405
|
+
error: 'Failed to generate user artifact',
|
|
406
|
+
statusCode: 500,
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
async cleanupStaleCosmicMirror(userArtifactId: string) {
|
|
412
|
+
const drizzle = this.context.drizzle();
|
|
413
|
+
const { cosmicMirrorImages, generations, userArtifacts } = schema;
|
|
414
|
+
|
|
415
|
+
// Delete images from cosmicMirrorImages
|
|
416
|
+
await drizzle
|
|
417
|
+
.delete(cosmicMirrorImages)
|
|
418
|
+
.where(eq(cosmicMirrorImages.userArtifactId, userArtifactId));
|
|
419
|
+
|
|
420
|
+
// Delete associated generations
|
|
421
|
+
await drizzle
|
|
422
|
+
.delete(generations)
|
|
423
|
+
.where(eq(generations.userArtifactId, userArtifactId));
|
|
424
|
+
|
|
425
|
+
// Delete the user artifact
|
|
426
|
+
await drizzle
|
|
427
|
+
.delete(userArtifacts)
|
|
428
|
+
.where(eq(userArtifacts.id, userArtifactId));
|
|
429
|
+
|
|
430
|
+
await this.log('info', 'Cleaned up stale cosmic mirror artifact', {
|
|
431
|
+
userArtifactId,
|
|
432
|
+
});
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
async retryStaleCosmicMirror(userArtifactId: string): Promise<any> {
|
|
436
|
+
const drizzle = this.context.drizzle();
|
|
437
|
+
const { users, artifactFaceswap, cosmicMirrorImages, userArtifacts } =
|
|
438
|
+
schema;
|
|
439
|
+
|
|
440
|
+
console.log(
|
|
441
|
+
`Starting retryStaleCosmicMirror for userArtifactId: ${userArtifactId}`
|
|
442
|
+
);
|
|
443
|
+
|
|
444
|
+
// Fetch userId from userArtifacts
|
|
445
|
+
console.log('Fetching user artifact to get userId...');
|
|
446
|
+
const [artifact] = await drizzle
|
|
447
|
+
.select({ userId: userArtifacts.userId })
|
|
448
|
+
.from(userArtifacts)
|
|
449
|
+
.where(eq(userArtifacts.id, userArtifactId))
|
|
450
|
+
.limit(1);
|
|
451
|
+
|
|
452
|
+
if (!artifact || !artifact.userId) {
|
|
453
|
+
console.error('User artifact or userId not found');
|
|
454
|
+
return {
|
|
455
|
+
status: 'error',
|
|
456
|
+
error: 'User artifact or userId not found',
|
|
457
|
+
statusCode: 400,
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
const userId = artifact.userId;
|
|
462
|
+
console.log(`Fetched userId: ${userId}`);
|
|
463
|
+
|
|
464
|
+
// Fetch user photo
|
|
465
|
+
console.log('Fetching user photo from database...');
|
|
466
|
+
const [user] = await drizzle
|
|
467
|
+
.select({
|
|
468
|
+
userPhotoUrl: users.userPhotoUrl,
|
|
469
|
+
userProfileImage: users.profileImage,
|
|
470
|
+
})
|
|
471
|
+
.from(users)
|
|
472
|
+
.where(eq(users.id, userId))
|
|
473
|
+
.limit(1);
|
|
474
|
+
|
|
475
|
+
console.log('User fetch result:', user);
|
|
476
|
+
|
|
477
|
+
const sourceImage = user?.userPhotoUrl || user?.userProfileImage;
|
|
478
|
+
if (!sourceImage) {
|
|
479
|
+
console.error('No user photo found for faceswap');
|
|
480
|
+
return {
|
|
481
|
+
status: 'error',
|
|
482
|
+
error: 'No user photo found for faceswap',
|
|
483
|
+
statusCode: 400,
|
|
484
|
+
};
|
|
485
|
+
}
|
|
486
|
+
console.log(`Source image URL: ${sourceImage}`);
|
|
487
|
+
|
|
488
|
+
// Get all images from D1
|
|
489
|
+
console.log('Fetching images from D1 cosmicMirrorImages table...');
|
|
490
|
+
const images = await drizzle
|
|
491
|
+
.select()
|
|
492
|
+
.from(cosmicMirrorImages)
|
|
493
|
+
.where(eq(cosmicMirrorImages.userArtifactId, userArtifactId));
|
|
494
|
+
|
|
495
|
+
console.log('Images retrieved from D1:', images);
|
|
496
|
+
|
|
497
|
+
if (images.length === 0) {
|
|
498
|
+
console.warn('No images found in D1 for this userArtifactId');
|
|
499
|
+
return {
|
|
500
|
+
status: 'error',
|
|
501
|
+
error: 'No images found in D1 for faceswap',
|
|
502
|
+
statusCode: 400,
|
|
503
|
+
};
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
// Process faceswaps for all images
|
|
507
|
+
let faceswapCount = 0;
|
|
508
|
+
for (const image of images) {
|
|
509
|
+
console.log(`Processing image with leonardoId: ${image.leonardoId}`);
|
|
510
|
+
|
|
511
|
+
if (!image.url) {
|
|
512
|
+
console.warn(
|
|
513
|
+
`Image with leonardoId ${image.leonardoId} does not contain a URL:`,
|
|
514
|
+
image
|
|
515
|
+
);
|
|
516
|
+
continue;
|
|
517
|
+
}
|
|
518
|
+
console.log(
|
|
519
|
+
`Target image URL for leonardoId ${image.leonardoId}: ${image.url}`
|
|
520
|
+
);
|
|
521
|
+
|
|
522
|
+
console.log(
|
|
523
|
+
`Calling faceSwap API for source: ${sourceImage}, target: ${image.url}`
|
|
524
|
+
);
|
|
525
|
+
let faceSwapResponse: any;
|
|
526
|
+
try {
|
|
527
|
+
// Add a timeout for the FaceSwap API call (8 seconds as specified)
|
|
528
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
529
|
+
setTimeout(
|
|
530
|
+
() =>
|
|
531
|
+
reject(new Error('FaceSwap API call timed out after 8 seconds')),
|
|
532
|
+
8000
|
|
533
|
+
);
|
|
534
|
+
});
|
|
535
|
+
|
|
536
|
+
faceSwapResponse = await Promise.race([
|
|
537
|
+
this.context.api().callPiApi.faceSwap({
|
|
538
|
+
sourceImageUrl: sourceImage,
|
|
539
|
+
targetImageUrl: image.url,
|
|
540
|
+
}),
|
|
541
|
+
timeoutPromise,
|
|
542
|
+
]);
|
|
543
|
+
|
|
544
|
+
console.log(
|
|
545
|
+
`faceSwap response for leonardoId ${image.leonardoId}:`,
|
|
546
|
+
faceSwapResponse
|
|
547
|
+
);
|
|
548
|
+
} catch (err) {
|
|
549
|
+
console.error(
|
|
550
|
+
`faceSwap API call failed for leonardoId ${image.leonardoId}:`,
|
|
551
|
+
err
|
|
552
|
+
);
|
|
553
|
+
continue;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
if (!faceSwapResponse || !faceSwapResponse.taskId) {
|
|
557
|
+
console.error(
|
|
558
|
+
`faceSwap response for leonardoId ${image.leonardoId} does not contain a taskId:`,
|
|
559
|
+
faceSwapResponse
|
|
560
|
+
);
|
|
561
|
+
continue;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
// Create faceswap record
|
|
565
|
+
console.log(
|
|
566
|
+
`Creating artifactFaceswap record for taskId: ${faceSwapResponse.taskId}`
|
|
567
|
+
);
|
|
568
|
+
try {
|
|
569
|
+
await drizzle.insert(artifactFaceswap).values({
|
|
570
|
+
id: faceSwapResponse.taskId,
|
|
571
|
+
userArtifactId,
|
|
572
|
+
status: 'pending',
|
|
573
|
+
createdAt: new Date(),
|
|
574
|
+
updatedAt: new Date(),
|
|
575
|
+
});
|
|
576
|
+
console.log(
|
|
577
|
+
`Successfully created artifactFaceswap record for taskId: ${faceSwapResponse.taskId}`
|
|
578
|
+
);
|
|
579
|
+
faceswapCount++;
|
|
580
|
+
} catch (err) {
|
|
581
|
+
console.error(
|
|
582
|
+
`Failed to create artifactFaceswap record for taskId: ${faceSwapResponse.taskId}:`,
|
|
583
|
+
err
|
|
584
|
+
);
|
|
585
|
+
continue;
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
if (faceswapCount === 0) {
|
|
590
|
+
console.error('No artifactFaceswap records were created');
|
|
591
|
+
return {
|
|
592
|
+
status: 'error',
|
|
593
|
+
error: 'Failed to create any faceswap records',
|
|
594
|
+
statusCode: 500,
|
|
595
|
+
};
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
// Update user artifact status
|
|
599
|
+
console.log(
|
|
600
|
+
`Updating userArtifacts status to 'faceswap' for userArtifactId: ${userArtifactId}`
|
|
601
|
+
);
|
|
602
|
+
try {
|
|
603
|
+
await drizzle
|
|
604
|
+
.update(userArtifacts)
|
|
605
|
+
.set({
|
|
606
|
+
status: 'faceswap',
|
|
607
|
+
updatedAt: new Date(),
|
|
608
|
+
})
|
|
609
|
+
.where(eq(userArtifacts.id, userArtifactId));
|
|
610
|
+
console.log('Successfully updated userArtifacts status');
|
|
611
|
+
} catch (err) {
|
|
612
|
+
console.error('Failed to update userArtifacts status:', err);
|
|
613
|
+
return {
|
|
614
|
+
status: 'error',
|
|
615
|
+
error: 'Failed to update user artifact status',
|
|
616
|
+
statusCode: 500,
|
|
617
|
+
};
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
console.log(
|
|
621
|
+
`retryStaleCosmicMirror completed successfully. Created ${faceswapCount} faceswap records.`
|
|
622
|
+
);
|
|
623
|
+
return {
|
|
624
|
+
status: 'success',
|
|
625
|
+
data: {
|
|
626
|
+
message: 'Restarted faceswap process for existing cosmic mirror images',
|
|
627
|
+
},
|
|
628
|
+
statusCode: 200,
|
|
629
|
+
};
|
|
630
|
+
}
|
|
631
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { inject, injectable } from 'inversify';
|
|
2
|
+
import 'reflect-metadata';
|
|
3
|
+
import { AppContext } from '../base';
|
|
4
|
+
|
|
5
|
+
@injectable()
|
|
6
|
+
export class ImageDescriberService {
|
|
7
|
+
constructor(@inject(AppContext) private context: AppContext) {}
|
|
8
|
+
|
|
9
|
+
async describeImage({ imageUrl }: { imageUrl: string }): Promise<string> {
|
|
10
|
+
try {
|
|
11
|
+
const response = await this.context.api().callTogether.single(
|
|
12
|
+
[
|
|
13
|
+
{
|
|
14
|
+
role: 'user',
|
|
15
|
+
content: [
|
|
16
|
+
{
|
|
17
|
+
type: 'text',
|
|
18
|
+
text: 'Describe the following traits of the person in the picture:\n- Hair color, size and style\n- Skin color\n- Eye color\n\nReturn the result in the following format:\n\nHair: hair description\nSkin: skin description\nEye: eye color',
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
type: 'image_url',
|
|
22
|
+
image_url: {
|
|
23
|
+
url: imageUrl,
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
],
|
|
27
|
+
},
|
|
28
|
+
],
|
|
29
|
+
{
|
|
30
|
+
model: 'meta-llama/Llama-3.2-11B-Vision-Instruct-Turbo',
|
|
31
|
+
temperature: 0.3,
|
|
32
|
+
}
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
if (!response) {
|
|
36
|
+
throw new Error('Failed to generate image description');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return response;
|
|
40
|
+
} catch (error) {
|
|
41
|
+
console.error('Error describing image:', error);
|
|
42
|
+
throw error;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
package/app/services/index.ts
CHANGED