@zodic/shared 0.0.316 → 0.0.318
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.
- package/app/services/ArchetypeService.ts +231 -159
- package/app/services/LeonardoService.ts +37 -24
- package/app/workflow/ArchetypeWorkflow.ts +17 -18
- package/package.json +1 -1
- package/utils/buildMessages.ts +73 -58
|
@@ -6,6 +6,19 @@ import { ZodiacSignSlug } from '../../types/scopes/legacy';
|
|
|
6
6
|
import { generateArchetypePrompt } from '../../utils/archetypeNamesMessages';
|
|
7
7
|
import { AppContext } from '../base';
|
|
8
8
|
|
|
9
|
+
interface ParsedDescriptionAndVirtues {
|
|
10
|
+
descriptionEN: string;
|
|
11
|
+
descriptionPTM: string;
|
|
12
|
+
descriptionPTF: string;
|
|
13
|
+
virtuesEN: string[];
|
|
14
|
+
virtuesPT: string[];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface LeonardoPrompt {
|
|
18
|
+
malePrompt: string;
|
|
19
|
+
femalePrompt: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
9
22
|
@injectable()
|
|
10
23
|
export class ArchetypeService {
|
|
11
24
|
constructor(@inject(AppContext) private context: AppContext) {}
|
|
@@ -241,7 +254,7 @@ export class ArchetypeService {
|
|
|
241
254
|
gender,
|
|
242
255
|
language,
|
|
243
256
|
});
|
|
244
|
-
|
|
257
|
+
|
|
245
258
|
const db = this.context.drizzle();
|
|
246
259
|
const archetypes = await db
|
|
247
260
|
.select()
|
|
@@ -254,12 +267,12 @@ export class ArchetypeService {
|
|
|
254
267
|
)
|
|
255
268
|
)
|
|
256
269
|
.execute();
|
|
257
|
-
|
|
270
|
+
|
|
258
271
|
await this.log('info', 'Fetched archetypes for description generation', {
|
|
259
272
|
archetypesCount: archetypes.length,
|
|
260
273
|
names: archetypes.map((a) => a.name),
|
|
261
274
|
});
|
|
262
|
-
|
|
275
|
+
|
|
263
276
|
const descVirtueMessages = this.context
|
|
264
277
|
.buildLLMMessages()
|
|
265
278
|
.generateCosmicMirrorDescriptionAndVirtues({
|
|
@@ -271,7 +284,7 @@ export class ArchetypeService {
|
|
|
271
284
|
archetypes[2].essenceLine,
|
|
272
285
|
],
|
|
273
286
|
});
|
|
274
|
-
|
|
287
|
+
|
|
275
288
|
await this.log('debug', 'Calling API for descriptions and virtues', {
|
|
276
289
|
messages: descVirtueMessages,
|
|
277
290
|
});
|
|
@@ -289,50 +302,83 @@ export class ArchetypeService {
|
|
|
289
302
|
await this.log('info', 'Received descriptions and virtues response', {
|
|
290
303
|
responseLength: descVirtueResponse.length,
|
|
291
304
|
});
|
|
292
|
-
|
|
293
|
-
const parsedDescVirtues =
|
|
294
|
-
this.parseDescriptionAndVirtuesResponse(descVirtueResponse);
|
|
305
|
+
|
|
306
|
+
const parsedDescVirtues = this.parseDescriptionAndVirtuesResponse(descVirtueResponse);
|
|
295
307
|
await this.log('info', 'Parsed descriptions and virtues', {
|
|
296
308
|
parsedDescVirtues,
|
|
297
309
|
});
|
|
298
|
-
|
|
310
|
+
|
|
299
311
|
for (let i = 0; i < 3; i++) {
|
|
300
312
|
const index = (i + 1).toString();
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
.set({
|
|
320
|
-
description: descriptionField,
|
|
321
|
-
virtues: JSON.stringify(parsedDescVirtues[i].virtues),
|
|
322
|
-
updatedAt: new Date().getTime(),
|
|
323
|
-
})
|
|
324
|
-
.where(
|
|
325
|
-
and(
|
|
326
|
-
eq(schema.archetypesData.id, id),
|
|
327
|
-
eq(schema.archetypesData.language, lang)
|
|
328
|
-
)
|
|
313
|
+
|
|
314
|
+
// Update English entry (en-us)
|
|
315
|
+
const enId = `${combinationString}:${gender}:${index}`;
|
|
316
|
+
await this.log('debug', `Updating description and virtues for ${enId}`, {
|
|
317
|
+
description: parsedDescVirtues[i].descriptionEN,
|
|
318
|
+
virtues: parsedDescVirtues[i].virtuesEN,
|
|
319
|
+
});
|
|
320
|
+
await db
|
|
321
|
+
.update(schema.archetypesData)
|
|
322
|
+
.set({
|
|
323
|
+
description: parsedDescVirtues[i].descriptionEN,
|
|
324
|
+
virtues: JSON.stringify(parsedDescVirtues[i].virtuesEN),
|
|
325
|
+
updatedAt: new Date().getTime(),
|
|
326
|
+
})
|
|
327
|
+
.where(
|
|
328
|
+
and(
|
|
329
|
+
eq(schema.archetypesData.id, enId),
|
|
330
|
+
eq(schema.archetypesData.language, 'en-us')
|
|
329
331
|
)
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
332
|
+
)
|
|
333
|
+
.execute();
|
|
334
|
+
await this.log('info', `Updated description and virtues for ${enId}`);
|
|
335
|
+
|
|
336
|
+
// Update Portuguese entries (pt-br) for both male and female
|
|
337
|
+
const ptIdMale = `${combinationString}:${gender}:${index}:pt`;
|
|
338
|
+
await this.log('debug', `Updating description and virtues for ${ptIdMale} (male)`, {
|
|
339
|
+
description: parsedDescVirtues[i].descriptionPTM,
|
|
340
|
+
virtues: parsedDescVirtues[i].virtuesPT,
|
|
341
|
+
});
|
|
342
|
+
await db
|
|
343
|
+
.update(schema.archetypesData)
|
|
344
|
+
.set({
|
|
345
|
+
description: parsedDescVirtues[i].descriptionPTM,
|
|
346
|
+
virtues: JSON.stringify(parsedDescVirtues[i].virtuesPT),
|
|
347
|
+
updatedAt: new Date().getTime(),
|
|
348
|
+
})
|
|
349
|
+
.where(
|
|
350
|
+
and(
|
|
351
|
+
eq(schema.archetypesData.id, ptIdMale),
|
|
352
|
+
eq(schema.archetypesData.language, 'pt-br'),
|
|
353
|
+
eq(schema.archetypesData.gender, 'male')
|
|
354
|
+
)
|
|
355
|
+
)
|
|
356
|
+
.execute();
|
|
357
|
+
await this.log('info', `Updated description and virtues for ${ptIdMale} (male)`);
|
|
358
|
+
|
|
359
|
+
const ptIdFemale = `${combinationString}:${gender}:${index}:pt`;
|
|
360
|
+
await this.log('debug', `Updating description and virtues for ${ptIdFemale} (female)`, {
|
|
361
|
+
description: parsedDescVirtues[i].descriptionPTF,
|
|
362
|
+
virtues: parsedDescVirtues[i].virtuesPT,
|
|
363
|
+
});
|
|
364
|
+
await db
|
|
365
|
+
.update(schema.archetypesData)
|
|
366
|
+
.set({
|
|
367
|
+
description: parsedDescVirtues[i].descriptionPTF,
|
|
368
|
+
virtues: JSON.stringify(parsedDescVirtues[i].virtuesPT),
|
|
369
|
+
updatedAt: new Date().getTime(),
|
|
370
|
+
})
|
|
371
|
+
.where(
|
|
372
|
+
and(
|
|
373
|
+
eq(schema.archetypesData.id, ptIdFemale),
|
|
374
|
+
eq(schema.archetypesData.language, 'pt-br'),
|
|
375
|
+
eq(schema.archetypesData.gender, 'female')
|
|
376
|
+
)
|
|
377
|
+
)
|
|
378
|
+
.execute();
|
|
379
|
+
await this.log('info', `Updated description and virtues for ${ptIdFemale} (female)`);
|
|
334
380
|
}
|
|
335
|
-
|
|
381
|
+
|
|
336
382
|
await this.log('info', 'Completed generateDescriptionsAndVirtues', {
|
|
337
383
|
combinationString,
|
|
338
384
|
gender,
|
|
@@ -341,126 +387,136 @@ export class ArchetypeService {
|
|
|
341
387
|
return parsedDescVirtues;
|
|
342
388
|
}
|
|
343
389
|
|
|
344
|
-
|
|
390
|
+
|
|
391
|
+
private parseDescriptionAndVirtuesResponse(response: string): ParsedDescriptionAndVirtues[] {
|
|
345
392
|
this.log('debug', 'Starting parseDescriptionAndVirtuesResponse', {
|
|
346
393
|
responseLength: response.length,
|
|
347
394
|
});
|
|
348
|
-
|
|
395
|
+
|
|
349
396
|
const entries = response
|
|
350
397
|
.split(/---/)
|
|
351
398
|
.map((block) => block.trim())
|
|
352
399
|
.filter((block) => block.length > 0);
|
|
353
|
-
|
|
400
|
+
|
|
354
401
|
this.log('debug', 'Split response into entries', {
|
|
355
402
|
entriesCount: entries.length,
|
|
356
403
|
});
|
|
357
|
-
|
|
404
|
+
|
|
405
|
+
if (entries.length !== 3) {
|
|
406
|
+
this.log('error', 'Expected exactly 3 archetype entries', {
|
|
407
|
+
entriesCount: entries.length,
|
|
408
|
+
response,
|
|
409
|
+
});
|
|
410
|
+
throw new Error(`Expected exactly 3 archetype entries, but got ${entries.length}`);
|
|
411
|
+
}
|
|
412
|
+
|
|
358
413
|
const result = entries.map((entry, entryIndex) => {
|
|
359
414
|
this.log('debug', `Processing entry ${entryIndex + 1}`, { entry });
|
|
360
|
-
|
|
361
|
-
const lines = entry.split('\n').
|
|
415
|
+
|
|
416
|
+
const lines = entry.split('\n').map((line) => line.trim()).filter((line) => line);
|
|
362
417
|
this.log('debug', `Split entry ${entryIndex + 1} into lines`, {
|
|
363
418
|
linesCount: lines.length,
|
|
364
419
|
lines,
|
|
365
420
|
});
|
|
366
|
-
|
|
421
|
+
|
|
367
422
|
let descriptionEN = '';
|
|
368
423
|
let descriptionPTM = '';
|
|
369
424
|
let descriptionPTF = '';
|
|
370
|
-
let
|
|
425
|
+
let virtuesEN: string[] = [];
|
|
426
|
+
let virtuesPT: string[] = [];
|
|
371
427
|
let currentField = '';
|
|
372
|
-
|
|
373
|
-
for (
|
|
428
|
+
|
|
429
|
+
for (let i = 0; i < lines.length; i++) {
|
|
430
|
+
let line = lines[i];
|
|
431
|
+
|
|
432
|
+
// Remove markdown bold (**...**) from field names
|
|
433
|
+
line = line.replace(/\*\*(.*?)\*\*/g, '$1');
|
|
434
|
+
|
|
435
|
+
if (line.startsWith('1.') || line.startsWith('2.') || line.startsWith('3.')) {
|
|
436
|
+
continue; // Skip entry number lines
|
|
437
|
+
}
|
|
438
|
+
|
|
374
439
|
if (line.startsWith('• Description EN:')) {
|
|
375
440
|
currentField = 'descriptionEN';
|
|
376
441
|
descriptionEN = line.split('• Description EN:')[1]?.trim() || '';
|
|
377
|
-
this.log(
|
|
378
|
-
'debug',
|
|
379
|
-
`Extracted descriptionEN for entry ${entryIndex + 1}`,
|
|
380
|
-
{ descriptionEN }
|
|
381
|
-
);
|
|
442
|
+
this.log('debug', `Extracted descriptionEN for entry ${entryIndex + 1}`, { descriptionEN });
|
|
382
443
|
} else if (line.startsWith('• Description PT-M:')) {
|
|
383
444
|
currentField = 'descriptionPTM';
|
|
384
445
|
descriptionPTM = line.split('• Description PT-M:')[1]?.trim() || '';
|
|
385
|
-
this.log(
|
|
386
|
-
'debug',
|
|
387
|
-
`Extracted descriptionPTM for entry ${entryIndex + 1}`,
|
|
388
|
-
{ descriptionPTM }
|
|
389
|
-
);
|
|
446
|
+
this.log('debug', `Extracted descriptionPTM for entry ${entryIndex + 1}`, { descriptionPTM });
|
|
390
447
|
} else if (line.startsWith('• Description PT-F:')) {
|
|
391
448
|
currentField = 'descriptionPTF';
|
|
392
449
|
descriptionPTF = line.split('• Description PT-F:')[1]?.trim() || '';
|
|
393
|
-
this.log(
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
450
|
+
this.log('debug', `Extracted descriptionPTF for entry ${entryIndex + 1}`, { descriptionPTF });
|
|
451
|
+
} else if (line.startsWith('• Virtues EN:')) {
|
|
452
|
+
currentField = 'virtuesEN';
|
|
453
|
+
virtuesEN = line
|
|
454
|
+
.split('• Virtues EN:')[1]
|
|
455
|
+
?.split(',')
|
|
456
|
+
.map((v) => v.trim())
|
|
457
|
+
.filter((v) => v)
|
|
458
|
+
.slice(0, 3);
|
|
459
|
+
this.log('debug', `Extracted virtuesEN for entry ${entryIndex + 1}`, { virtuesEN });
|
|
460
|
+
} else if (line.startsWith('• Virtues PT:')) {
|
|
461
|
+
currentField = 'virtuesPT';
|
|
462
|
+
virtuesPT = line
|
|
463
|
+
.split('• Virtues PT:')[1]
|
|
464
|
+
?.split(',')
|
|
465
|
+
.map((v) => v.trim())
|
|
466
|
+
.filter((v) => v)
|
|
467
|
+
.slice(0, 3);
|
|
468
|
+
this.log('debug', `Extracted virtuesPT for entry ${entryIndex + 1}`, { virtuesPT });
|
|
398
469
|
} else if (line.startsWith('• Virtues:')) {
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
470
|
+
// Fallback for older response format: use English virtues and attempt to translate
|
|
471
|
+
currentField = 'virtuesEN';
|
|
472
|
+
virtuesEN = line
|
|
473
|
+
.split('• Virtues:')[1]
|
|
474
|
+
?.split(',')
|
|
475
|
+
.map((v) => v.trim())
|
|
476
|
+
.filter((v) => v)
|
|
477
|
+
.slice(0, 3);
|
|
478
|
+
this.log('debug', `Extracted virtuesEN (fallback) for entry ${entryIndex + 1}`, { virtuesEN });
|
|
479
|
+
|
|
480
|
+
// Simple translation mapping for common virtues (as a fallback)
|
|
481
|
+
const virtueTranslations: { [key: string]: string } = {
|
|
482
|
+
Harmony: 'Harmonia',
|
|
483
|
+
Intuition: 'Intuição',
|
|
484
|
+
Grace: 'Graça',
|
|
485
|
+
// Add more translations as needed
|
|
486
|
+
};
|
|
487
|
+
virtuesPT = virtuesEN.map((v) => virtueTranslations[v] || v); // Fallback to English if no translation
|
|
488
|
+
this.log('debug', `Generated virtuesPT (fallback) for entry ${entryIndex + 1}`, { virtuesPT });
|
|
489
|
+
} else if (currentField && !currentField.startsWith('virtues')) {
|
|
410
490
|
if (currentField === 'descriptionEN') {
|
|
411
|
-
descriptionEN += ' ' + line
|
|
412
|
-
this.log(
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
descriptionPTM += ' ' + line.trim();
|
|
420
|
-
this.log(
|
|
421
|
-
'debug',
|
|
422
|
-
`Appended to descriptionPTM for entry ${entryIndex + 1}`,
|
|
423
|
-
{ descriptionPTM }
|
|
424
|
-
);
|
|
425
|
-
}
|
|
426
|
-
if (currentField === 'descriptionPTF') {
|
|
427
|
-
descriptionPTF += ' ' + line.trim();
|
|
428
|
-
this.log(
|
|
429
|
-
'debug',
|
|
430
|
-
`Appended to descriptionPTF for entry ${entryIndex + 1}`,
|
|
431
|
-
{ descriptionPTF }
|
|
432
|
-
);
|
|
491
|
+
descriptionEN += ' ' + line;
|
|
492
|
+
this.log('debug', `Appended to descriptionEN for entry ${entryIndex + 1}`, { descriptionEN });
|
|
493
|
+
} else if (currentField === 'descriptionPTM') {
|
|
494
|
+
descriptionPTM += ' ' + line;
|
|
495
|
+
this.log('debug', `Appended to descriptionPTM for entry ${entryIndex + 1}`, { descriptionPTM });
|
|
496
|
+
} else if (currentField === 'descriptionPTF') {
|
|
497
|
+
descriptionPTF += ' ' + line;
|
|
498
|
+
this.log('debug', `Appended to descriptionPTF for entry ${entryIndex + 1}`, { descriptionPTF });
|
|
433
499
|
}
|
|
434
500
|
}
|
|
435
501
|
}
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
{
|
|
449
|
-
descriptionEN,
|
|
450
|
-
descriptionPTM,
|
|
451
|
-
descriptionPTF,
|
|
452
|
-
virtues,
|
|
453
|
-
response: entry,
|
|
454
|
-
}
|
|
455
|
-
);
|
|
502
|
+
|
|
503
|
+
// Validate the parsed data
|
|
504
|
+
if (!descriptionEN || !descriptionPTM || !descriptionPTF || virtuesEN.length !== 3 || virtuesPT.length !== 3) {
|
|
505
|
+
this.log('warn', `Malformed description and virtues response for entry ${entryIndex + 1}`, {
|
|
506
|
+
descriptionEN,
|
|
507
|
+
descriptionPTM,
|
|
508
|
+
descriptionPTF,
|
|
509
|
+
virtuesEN,
|
|
510
|
+
virtuesPT,
|
|
511
|
+
response: entry,
|
|
512
|
+
});
|
|
513
|
+
throw new Error(`Malformed response for entry ${entryIndex + 1}: Missing required fields`);
|
|
456
514
|
}
|
|
457
|
-
|
|
458
|
-
return { descriptionEN, descriptionPTM, descriptionPTF,
|
|
459
|
-
});
|
|
460
|
-
|
|
461
|
-
this.log('info', 'Completed parseDescriptionAndVirtuesResponse', {
|
|
462
|
-
result,
|
|
515
|
+
|
|
516
|
+
return { descriptionEN, descriptionPTM, descriptionPTF, virtuesEN, virtuesPT };
|
|
463
517
|
});
|
|
518
|
+
|
|
519
|
+
this.log('info', 'Completed parseDescriptionAndVirtuesResponse', { result });
|
|
464
520
|
return result;
|
|
465
521
|
}
|
|
466
522
|
|
|
@@ -819,51 +875,67 @@ export class ArchetypeService {
|
|
|
819
875
|
return parsedPrompts;
|
|
820
876
|
}
|
|
821
877
|
|
|
822
|
-
private parseLeonardoPromptResponse(response: string) {
|
|
878
|
+
private parseLeonardoPromptResponse(response: string): LeonardoPrompt[] {
|
|
823
879
|
this.log('debug', 'Starting parseLeonardoPromptResponse', {
|
|
824
880
|
responseLength: response.length,
|
|
825
881
|
});
|
|
826
|
-
|
|
882
|
+
|
|
827
883
|
const lines = response.split('\n').filter((line) => line.trim());
|
|
828
884
|
this.log('debug', 'Split response into lines', {
|
|
829
885
|
linesCount: lines.length,
|
|
830
886
|
lines,
|
|
831
887
|
});
|
|
832
|
-
|
|
833
|
-
const prompts:
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
const
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
'
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
888
|
+
|
|
889
|
+
const prompts: LeonardoPrompt[] = [];
|
|
890
|
+
let currentArchetype = 0;
|
|
891
|
+
|
|
892
|
+
for (let i = 0; i < lines.length; i += 4) {
|
|
893
|
+
// Expect pairs of label and prompt: "1.m", male prompt, "1.f", female prompt
|
|
894
|
+
const maleLabel = lines[i]; // "1.m"
|
|
895
|
+
const malePromptLine = lines[i + 1]; // Male prompt text
|
|
896
|
+
const femaleLabel = lines[i + 2]; // "1.f"
|
|
897
|
+
const femalePromptLine = lines[i + 3]; // Female prompt text
|
|
898
|
+
|
|
899
|
+
currentArchetype++;
|
|
900
|
+
const expectedMaleLabel = `${currentArchetype}.m`;
|
|
901
|
+
const expectedFemaleLabel = `${currentArchetype}.f`;
|
|
902
|
+
|
|
903
|
+
if (!maleLabel?.startsWith(expectedMaleLabel) || !femaleLabel?.startsWith(expectedFemaleLabel)) {
|
|
904
|
+
this.log('warn', `Malformed Leonardo prompt format for archetype ${currentArchetype}`, {
|
|
905
|
+
maleLabel,
|
|
906
|
+
malePromptLine,
|
|
907
|
+
femaleLabel,
|
|
908
|
+
femalePromptLine,
|
|
909
|
+
lines,
|
|
910
|
+
});
|
|
911
|
+
throw new Error(`Expected ${expectedMaleLabel} and ${expectedFemaleLabel} at lines ${i} and ${i + 2}`);
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
const malePrompt = malePromptLine?.trim() || '';
|
|
915
|
+
const femalePrompt = femalePromptLine?.trim() || '';
|
|
916
|
+
|
|
853
917
|
if (!malePrompt || !femalePrompt) {
|
|
854
|
-
this.log(
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
femaleLine,
|
|
862
|
-
}
|
|
863
|
-
);
|
|
918
|
+
this.log('warn', `Empty Leonardo prompt for archetype ${currentArchetype}`, {
|
|
919
|
+
malePrompt,
|
|
920
|
+
femalePrompt,
|
|
921
|
+
malePromptLine,
|
|
922
|
+
femalePromptLine,
|
|
923
|
+
});
|
|
924
|
+
throw new Error(`Empty prompt for archetype ${currentArchetype}`);
|
|
864
925
|
}
|
|
926
|
+
|
|
927
|
+
prompts.push({ malePrompt, femalePrompt });
|
|
928
|
+
this.log('debug', `Parsed prompts for archetype ${currentArchetype}`, { malePrompt, femalePrompt });
|
|
865
929
|
}
|
|
866
|
-
|
|
930
|
+
|
|
931
|
+
if (prompts.length !== 3) {
|
|
932
|
+
this.log('error', 'Expected exactly 3 archetype prompts', {
|
|
933
|
+
promptsCount: prompts.length,
|
|
934
|
+
response,
|
|
935
|
+
});
|
|
936
|
+
throw new Error(`Expected exactly 3 archetype prompts, but got ${prompts.length}`);
|
|
937
|
+
}
|
|
938
|
+
|
|
867
939
|
this.log('info', 'Completed parseLeonardoPromptResponse', { prompts });
|
|
868
940
|
return prompts;
|
|
869
941
|
}
|
|
@@ -53,39 +53,52 @@ export class LeonardoService {
|
|
|
53
53
|
* Processes a message from ARCHETYPE_POPULATION_QUEUE to ensure the archetype has three images.
|
|
54
54
|
*/
|
|
55
55
|
async processArchetypePopulation(message: QueueMessage): Promise<void> {
|
|
56
|
-
const { archetypeDataId
|
|
57
|
-
message;
|
|
56
|
+
const { archetypeDataId } = message;
|
|
58
57
|
await this.log('info', 'Processing archetype population', {
|
|
59
58
|
archetypeDataId,
|
|
60
|
-
combination,
|
|
61
|
-
gender,
|
|
62
|
-
language,
|
|
63
|
-
archetypeIndex,
|
|
64
59
|
});
|
|
65
|
-
|
|
60
|
+
|
|
66
61
|
const db = this.context.drizzle();
|
|
67
62
|
const archetype = await db
|
|
68
|
-
.select(
|
|
63
|
+
.select({
|
|
64
|
+
id: schema.archetypesData.id,
|
|
65
|
+
combination: schema.archetypesData.combination,
|
|
66
|
+
gender: schema.archetypesData.gender,
|
|
67
|
+
language: schema.archetypesData.language,
|
|
68
|
+
archetypeIndex: schema.archetypesData.archetypeIndex,
|
|
69
|
+
leonardoPrompt: schema.archetypesData.leonardoPrompt,
|
|
70
|
+
images: schema.archetypesData.images,
|
|
71
|
+
})
|
|
69
72
|
.from(schema.archetypesData)
|
|
70
73
|
.where(eq(schema.archetypesData.id, archetypeDataId))
|
|
71
74
|
.limit(1)
|
|
72
75
|
.execute();
|
|
73
|
-
|
|
76
|
+
|
|
74
77
|
if (!archetype[0]) {
|
|
75
78
|
await this.log('error', 'Archetype not found', { archetypeDataId });
|
|
76
79
|
throw new Error(`Archetype not found: ${archetypeDataId}`);
|
|
77
80
|
}
|
|
78
|
-
|
|
79
|
-
const images =
|
|
80
|
-
|
|
81
|
+
|
|
82
|
+
const { combination, gender, language, archetypeIndex, leonardoPrompt, images } = archetype[0];
|
|
83
|
+
|
|
84
|
+
await this.log('info', 'Fetched archetype details', {
|
|
85
|
+
archetypeDataId,
|
|
86
|
+
combination,
|
|
87
|
+
gender,
|
|
88
|
+
language,
|
|
89
|
+
archetypeIndex,
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
const parsedImages = JSON.parse(images || '[]');
|
|
93
|
+
if (parsedImages.length >= 3) {
|
|
81
94
|
await this.log('info', 'Archetype already has sufficient images', {
|
|
82
95
|
archetypeDataId,
|
|
83
|
-
imageCount:
|
|
96
|
+
imageCount: parsedImages.length,
|
|
84
97
|
});
|
|
85
98
|
return;
|
|
86
99
|
}
|
|
87
|
-
|
|
88
|
-
if (!
|
|
100
|
+
|
|
101
|
+
if (!leonardoPrompt) {
|
|
89
102
|
await this.log('error', 'Missing Leonardo prompt for archetype', {
|
|
90
103
|
archetypeDataId,
|
|
91
104
|
});
|
|
@@ -93,21 +106,21 @@ export class LeonardoService {
|
|
|
93
106
|
`Missing Leonardo prompt for archetype: ${archetypeDataId}`
|
|
94
107
|
);
|
|
95
108
|
}
|
|
96
|
-
|
|
109
|
+
|
|
97
110
|
await this.log('debug', 'Generating images for archetype', {
|
|
98
111
|
archetypeDataId,
|
|
99
|
-
prompt:
|
|
112
|
+
prompt: leonardoPrompt,
|
|
100
113
|
});
|
|
101
|
-
|
|
114
|
+
|
|
102
115
|
const generationResponse = await this.context
|
|
103
116
|
.api()
|
|
104
117
|
.callLeonardo.generateImage({
|
|
105
|
-
prompt:
|
|
118
|
+
prompt: leonardoPrompt,
|
|
106
119
|
width: 512,
|
|
107
120
|
height: 640,
|
|
108
|
-
quantity: 3 -
|
|
121
|
+
quantity: 3 - parsedImages.length,
|
|
109
122
|
});
|
|
110
|
-
|
|
123
|
+
|
|
111
124
|
const generationId = generationResponse.sdGenerationJob.generationId;
|
|
112
125
|
if (!generationId) {
|
|
113
126
|
await this.log(
|
|
@@ -117,7 +130,7 @@ export class LeonardoService {
|
|
|
117
130
|
);
|
|
118
131
|
throw new Error('Leonardo generation failed to return a valid ID');
|
|
119
132
|
}
|
|
120
|
-
|
|
133
|
+
|
|
121
134
|
await db.insert(schema.generations).values({
|
|
122
135
|
id: generationId,
|
|
123
136
|
archetypeIndex: parseInt(archetypeIndex),
|
|
@@ -127,11 +140,11 @@ export class LeonardoService {
|
|
|
127
140
|
gender,
|
|
128
141
|
createdAt: new Date(),
|
|
129
142
|
});
|
|
130
|
-
|
|
143
|
+
|
|
131
144
|
await this.log('info', 'Queued image generation', {
|
|
132
145
|
generationId,
|
|
133
146
|
archetypeDataId,
|
|
134
|
-
imagesToGenerate: 3 -
|
|
147
|
+
imagesToGenerate: 3 - parsedImages.length,
|
|
135
148
|
});
|
|
136
149
|
}
|
|
137
150
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { and, eq } from 'drizzle-orm';
|
|
1
|
+
import { and, eq, inArray } from 'drizzle-orm';
|
|
2
2
|
import { inject, injectable } from 'inversify';
|
|
3
3
|
import { schema } from '../..';
|
|
4
4
|
import { Gender } from '../../types';
|
|
@@ -55,7 +55,7 @@ export class ArchetypeWorkflow {
|
|
|
55
55
|
gender,
|
|
56
56
|
language,
|
|
57
57
|
});
|
|
58
|
-
|
|
58
|
+
|
|
59
59
|
// Step 1: Check if archetypes exist, if not, generate names
|
|
60
60
|
let archetypes = await this.archetypeService.fetchArchetypesFromDB(
|
|
61
61
|
combinationString,
|
|
@@ -79,7 +79,7 @@ export class ArchetypeWorkflow {
|
|
|
79
79
|
archetypesCount: archetypes.length,
|
|
80
80
|
archetypeIds: archetypes.map((a) => a.id),
|
|
81
81
|
});
|
|
82
|
-
|
|
82
|
+
|
|
83
83
|
// Step 2: Check if any archetypes are already being processed (images are generating)
|
|
84
84
|
const db = this.context.drizzle();
|
|
85
85
|
await this.log('debug', 'Checking for in-progress image generations', {
|
|
@@ -93,11 +93,14 @@ export class ArchetypeWorkflow {
|
|
|
93
93
|
and(
|
|
94
94
|
eq(schema.generations.status, 'pending'),
|
|
95
95
|
eq(schema.generations.gender, gender),
|
|
96
|
-
|
|
96
|
+
inArray(
|
|
97
|
+
schema.generations.archetypeDataId,
|
|
98
|
+
archetypes.map((a) => a.id)
|
|
99
|
+
)
|
|
97
100
|
)
|
|
98
101
|
)
|
|
99
102
|
.execute();
|
|
100
|
-
|
|
103
|
+
|
|
101
104
|
if (inProgressGenerations.length > 0) {
|
|
102
105
|
await this.log(
|
|
103
106
|
'warn',
|
|
@@ -113,7 +116,7 @@ export class ArchetypeWorkflow {
|
|
|
113
116
|
combinationString,
|
|
114
117
|
gender,
|
|
115
118
|
});
|
|
116
|
-
|
|
119
|
+
|
|
117
120
|
// Step 3: Generate missing textual content (description, virtues, content, leonardoPrompt)
|
|
118
121
|
const archetypesNeedingText = archetypes.filter(
|
|
119
122
|
(arc) =>
|
|
@@ -122,7 +125,7 @@ export class ArchetypeWorkflow {
|
|
|
122
125
|
arc.content === '[]' ||
|
|
123
126
|
!arc.leonardoPrompt
|
|
124
127
|
);
|
|
125
|
-
|
|
128
|
+
|
|
126
129
|
if (archetypesNeedingText.length > 0) {
|
|
127
130
|
await this.log('info', 'Generating missing textual content', {
|
|
128
131
|
combinationString,
|
|
@@ -131,7 +134,7 @@ export class ArchetypeWorkflow {
|
|
|
131
134
|
archetypesNeedingTextCount: archetypesNeedingText.length,
|
|
132
135
|
archetypeIds: archetypesNeedingText.map((a) => a.id),
|
|
133
136
|
});
|
|
134
|
-
|
|
137
|
+
|
|
135
138
|
const descVirtues =
|
|
136
139
|
await this.archetypeService.generateDescriptionsAndVirtues(
|
|
137
140
|
combinationString,
|
|
@@ -157,7 +160,7 @@ export class ArchetypeWorkflow {
|
|
|
157
160
|
language,
|
|
158
161
|
});
|
|
159
162
|
}
|
|
160
|
-
|
|
163
|
+
|
|
161
164
|
// Refetch archetypes after generating textual content
|
|
162
165
|
const updatedArchetypes = await this.archetypeService.fetchArchetypesFromDB(
|
|
163
166
|
combinationString,
|
|
@@ -168,12 +171,12 @@ export class ArchetypeWorkflow {
|
|
|
168
171
|
updatedArchetypesCount: updatedArchetypes.length,
|
|
169
172
|
archetypeIds: updatedArchetypes.map((a) => a.id),
|
|
170
173
|
});
|
|
171
|
-
|
|
174
|
+
|
|
172
175
|
// Step 4: Queue image generation if needed
|
|
173
176
|
const archetypesNeedingImages = updatedArchetypes.filter(
|
|
174
177
|
(arc) => JSON.parse(arc.images).length < 3
|
|
175
178
|
);
|
|
176
|
-
|
|
179
|
+
|
|
177
180
|
if (archetypesNeedingImages.length > 0) {
|
|
178
181
|
await this.log(
|
|
179
182
|
'info',
|
|
@@ -183,14 +186,10 @@ export class ArchetypeWorkflow {
|
|
|
183
186
|
archetypeIds: archetypesNeedingImages.map((a) => a.id),
|
|
184
187
|
}
|
|
185
188
|
);
|
|
186
|
-
|
|
189
|
+
|
|
187
190
|
const batch = archetypesNeedingImages.map((arc) => ({
|
|
188
191
|
body: {
|
|
189
192
|
archetypeDataId: arc.id,
|
|
190
|
-
combination: combinationString,
|
|
191
|
-
gender,
|
|
192
|
-
language,
|
|
193
|
-
archetypeIndex: arc.archetypeIndex,
|
|
194
193
|
},
|
|
195
194
|
}));
|
|
196
195
|
await this.log('debug', 'Sending batch to ARCHETYPE_POPULATION_QUEUE', {
|
|
@@ -201,10 +200,10 @@ export class ArchetypeWorkflow {
|
|
|
201
200
|
await this.log('info', 'Successfully queued image generation', {
|
|
202
201
|
batchCount: batch.length,
|
|
203
202
|
});
|
|
204
|
-
|
|
203
|
+
|
|
205
204
|
return { message: 'Images are being processed, please try again later.' };
|
|
206
205
|
}
|
|
207
|
-
|
|
206
|
+
|
|
208
207
|
await this.log('info', 'Archetypes fully processed and ready', {
|
|
209
208
|
updatedArchetypes,
|
|
210
209
|
});
|
package/package.json
CHANGED
package/utils/buildMessages.ts
CHANGED
|
@@ -34,38 +34,52 @@ export const buildLLMMessages = (env: BackendBindings) => ({
|
|
|
34
34
|
• Portuguese — masculine version
|
|
35
35
|
• Portuguese — feminine version
|
|
36
36
|
|
|
37
|
-
2. Three symbolic **Virtues** for each archetype
|
|
37
|
+
2. Three symbolic **Virtues** for each archetype, in both English and Portuguese:
|
|
38
|
+
• English Virtues (e.g., Harmony, Intuition, Grace)
|
|
39
|
+
• Portuguese Virtues (e.g., Harmonia, Intuição, Graça)
|
|
38
40
|
|
|
39
41
|
—
|
|
40
42
|
|
|
41
|
-
✳️ Use the following format exactly for the response:
|
|
43
|
+
✳️ Use the following format exactly for the response. Ensure each section is separated by a line break, and virtues are comma-separated on a single line. Do not use markdown (e.g., **bold**) in the field names:
|
|
42
44
|
|
|
43
45
|
---
|
|
44
46
|
|
|
45
47
|
1.
|
|
46
48
|
|
|
47
|
-
• Description EN:
|
|
48
|
-
• Description PT-M:
|
|
49
|
-
• Description PT-F:
|
|
50
|
-
• Virtues: Virtue1, Virtue2, Virtue3
|
|
49
|
+
• Description EN: [Description in English]
|
|
50
|
+
• Description PT-M: [Description in Portuguese, masculine]
|
|
51
|
+
• Description PT-F: [Description in Portuguese, feminine]
|
|
52
|
+
• Virtues EN: Virtue1, Virtue2, Virtue3
|
|
53
|
+
• Virtues PT: Virtude1, Virtude2, Virtude3
|
|
51
54
|
|
|
52
55
|
---
|
|
53
56
|
|
|
54
57
|
2.
|
|
55
58
|
|
|
56
|
-
• Description EN:
|
|
57
|
-
• Description PT-M:
|
|
58
|
-
• Description PT-F:
|
|
59
|
-
• Virtues: Virtue1, Virtue2, Virtue3
|
|
59
|
+
• Description EN: [Description in English]
|
|
60
|
+
• Description PT-M: [Description in Portuguese, masculine]
|
|
61
|
+
• Description PT-F: [Description in Portuguese, feminine]
|
|
62
|
+
• Virtues EN: Virtue1, Virtue2, Virtue3
|
|
63
|
+
• Virtues PT: Virtude1, Virtude2, Virtude3
|
|
60
64
|
|
|
61
65
|
---
|
|
62
66
|
|
|
63
67
|
3.
|
|
64
68
|
|
|
65
|
-
• Description EN:
|
|
66
|
-
• Description PT-M:
|
|
67
|
-
• Description PT-F:
|
|
68
|
-
• Virtues: Virtue1, Virtue2, Virtue3
|
|
69
|
+
• Description EN: [Description in English]
|
|
70
|
+
• Description PT-M: [Description in Portuguese, masculine]
|
|
71
|
+
• Description PT-F: [Description in Portuguese, feminine]
|
|
72
|
+
• Virtues EN: Virtue1, Virtue2, Virtue3
|
|
73
|
+
• Virtues PT: Virtude1, Virtude2, Virtude3
|
|
74
|
+
|
|
75
|
+
—
|
|
76
|
+
|
|
77
|
+
✳️ Rules:
|
|
78
|
+
- Do not include extra text outside the specified format.
|
|
79
|
+
- Do not use markdown (e.g., **bold**, *italic*) in field names like "Description EN" or "Virtues EN".
|
|
80
|
+
- Ensure each description is 80–100 words.
|
|
81
|
+
- Virtues must be exactly three per archetype, comma-separated, on a single line.
|
|
82
|
+
- Use proper grammar and avoid ambiguity in translations.
|
|
69
83
|
`,
|
|
70
84
|
},
|
|
71
85
|
{
|
|
@@ -211,56 +225,57 @@ export const buildLLMMessages = (env: BackendBindings) => ({
|
|
|
211
225
|
{
|
|
212
226
|
role: 'system',
|
|
213
227
|
content: `
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
228
|
+
You are an expert in fantasy image generation. Your task is to transform archetype names and symbolic descriptions into highly detailed prompts for generating photorealistic fantasy characters using Leonardo.ai.
|
|
229
|
+
|
|
230
|
+
For each archetype, generate two prompts:
|
|
231
|
+
- One for a male character
|
|
232
|
+
- One for a female character
|
|
233
|
+
|
|
234
|
+
Instructions:
|
|
235
|
+
- Focus primarily on the character design—their appearance, clothing, and demeanor. This is more important than the background or setting.
|
|
236
|
+
- The setting should remain fantasy-inspired, but avoid over-specification. General hints like “enchanted realm” or “mystical battlefield” are enough unless the archetype demands more.
|
|
237
|
+
- The character must reflect the symbolic and mythic tone of the archetype name and description.
|
|
238
|
+
- Make sure male characters have short, cropped, or bald hair. Never long.
|
|
239
|
+
- Female characters may have long, flowing, or styled hair if it suits the archetype.
|
|
240
|
+
- The outfit should be rich in fantasy elements: armor, cloaks, robes, accessories. Avoid modern or casual clothing.
|
|
241
|
+
- The character must always be facing the camera, with a clear, visible face.
|
|
242
|
+
- Rendering instructions must include: 8K, HDR, Unreal Engine, photorealism, cinematic lighting, full body, fantasy aesthetic.
|
|
243
|
+
|
|
244
|
+
Each prompt must:
|
|
245
|
+
- Be under 1200 characters (including spaces).
|
|
246
|
+
- Clearly distinguish between the male and female versions only by adapting gender-specific features.
|
|
247
|
+
- Maintain identical structure, setting, and tone between both versions.
|
|
248
|
+
- Be written on a single line (no line breaks within the prompt text).
|
|
249
|
+
|
|
250
|
+
Output Format (each prompt on a new line, no extra spaces or lines):
|
|
251
|
+
|
|
252
|
+
1.m
|
|
253
|
+
[Prompt for the first archetype made for a male character]
|
|
254
|
+
1.f
|
|
255
|
+
[Prompt for the first archetype made for a female character (same as the male prompt but adapted for gender-specific traits)]
|
|
256
|
+
2.m
|
|
257
|
+
[Prompt for the second archetype made for a male character]
|
|
258
|
+
2.f
|
|
259
|
+
[Prompt for the second archetype made for a female character]
|
|
260
|
+
3.m
|
|
261
|
+
[Prompt for the third archetype made for a male character]
|
|
262
|
+
3.f
|
|
263
|
+
[Prompt for the third archetype made for a female character]
|
|
264
|
+
|
|
265
|
+
Rules:
|
|
266
|
+
- Do not include extra text, spaces, or line breaks outside the specified format.
|
|
267
|
+
- Each prompt must be a single line.
|
|
268
|
+
- Ensure exactly 3 archetypes are provided, with both male and female prompts for each.
|
|
269
|
+
`,
|
|
255
270
|
},
|
|
256
271
|
{
|
|
257
272
|
role: 'user',
|
|
258
273
|
content: archetypes
|
|
259
274
|
.map((a, idx) =>
|
|
260
275
|
`
|
|
261
|
-
${idx + 1}.
|
|
262
|
-
- Name: ${a.name}
|
|
263
|
-
- Description: ${a.description}`.trim()
|
|
276
|
+
${idx + 1}.
|
|
277
|
+
- Name: ${a.name}
|
|
278
|
+
- Description: ${a.description}`.trim()
|
|
264
279
|
)
|
|
265
280
|
.join('\n\n'),
|
|
266
281
|
},
|