@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.
@@ -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
- for (const lang of ['en-us', 'pt-br']) {
302
- const descriptionField =
303
- lang === 'en-us'
304
- ? parsedDescVirtues[i].descriptionEN
305
- : gender === 'male'
306
- ? parsedDescVirtues[i].descriptionPTM
307
- : parsedDescVirtues[i].descriptionPTF;
308
-
309
- const id = `${combinationString}:${gender}:${index}${
310
- lang === 'pt-br' ? ':pt' : ''
311
- }`;
312
- await this.log('debug', `Updating description and virtues for ${id}`, {
313
- description: descriptionField,
314
- virtues: parsedDescVirtues[i].virtues,
315
- });
316
-
317
- await db
318
- .update(schema.archetypesData)
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
- .execute();
331
-
332
- await this.log('info', `Updated description and virtues for ${id}`);
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
- private parseDescriptionAndVirtuesResponse(response: string) {
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').filter((line) => line.trim());
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 virtues: string[] = [];
425
+ let virtuesEN: string[] = [];
426
+ let virtuesPT: string[] = [];
371
427
  let currentField = '';
372
-
373
- for (const line of lines) {
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
- 'debug',
395
- `Extracted descriptionPTF for entry ${entryIndex + 1}`,
396
- { descriptionPTF }
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
- currentField = 'virtues';
400
- virtues =
401
- line
402
- .split('• Virtues:')[1]
403
- ?.split(',')
404
- .map((v) => v.trim())
405
- .filter((v) => v) || [];
406
- this.log('debug', `Extracted virtues for entry ${entryIndex + 1}`, {
407
- virtues,
408
- });
409
- } else if (currentField && currentField !== 'virtues') {
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.trim();
412
- this.log(
413
- 'debug',
414
- `Appended to descriptionEN for entry ${entryIndex + 1}`,
415
- { descriptionEN }
416
- );
417
- }
418
- if (currentField === 'descriptionPTM') {
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
- if (
438
- !descriptionEN ||
439
- !descriptionPTM ||
440
- !descriptionPTF ||
441
- virtues.length !== 3
442
- ) {
443
- this.log(
444
- 'warn',
445
- `Malformed description and virtues response for entry ${
446
- entryIndex + 1
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, virtues };
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: Array<{ malePrompt: string; femalePrompt: string }> = [];
834
-
835
- for (let i = 0; i < lines.length; i += 2) {
836
- const maleLine = lines[i];
837
- const femaleLine = lines[i + 1];
838
-
839
- const malePrompt = maleLine.startsWith(`${Math.floor(i / 2) + 1}.m`)
840
- ? maleLine.split(`${Math.floor(i / 2) + 1}.m`)[1]?.trim()
841
- : '';
842
- const femalePrompt = femaleLine.startsWith(`${Math.floor(i / 2) + 1}.f`)
843
- ? femaleLine.split(`${Math.floor(i / 2) + 1}.f`)[1]?.trim()
844
- : '';
845
-
846
- prompts.push({ malePrompt, femalePrompt });
847
- this.log(
848
- 'debug',
849
- `Parsed prompts for archetype ${Math.floor(i / 2) + 1}`,
850
- { malePrompt, femalePrompt }
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
- 'warn',
856
- `Malformed Leonardo prompt for archetype ${Math.floor(i / 2) + 1}`,
857
- {
858
- malePrompt,
859
- femalePrompt,
860
- maleLine,
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, combination, gender, language, archetypeIndex } =
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 = JSON.parse(archetype[0].images || '[]');
80
- if (images.length >= 3) {
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: images.length,
96
+ imageCount: parsedImages.length,
84
97
  });
85
98
  return;
86
99
  }
87
-
88
- if (!archetype[0].leonardoPrompt) {
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: archetype[0].leonardoPrompt,
112
+ prompt: leonardoPrompt,
100
113
  });
101
-
114
+
102
115
  const generationResponse = await this.context
103
116
  .api()
104
117
  .callLeonardo.generateImage({
105
- prompt: archetype[0].leonardoPrompt,
118
+ prompt: leonardoPrompt,
106
119
  width: 512,
107
120
  height: 640,
108
- quantity: 3 - images.length,
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 - images.length,
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
- eq(schema.generations.archetypeDataId, combinationString)
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zodic/shared",
3
- "version": "0.0.316",
3
+ "version": "0.0.318",
4
4
  "module": "index.ts",
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -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
- 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.
215
-
216
- For each archetype, generate two prompts:
217
- - One for a male character
218
- - One for a female character
219
-
220
- Instructions:
221
- - Focus primarily on the character design—their appearance, clothing, and demeanor. This is more important than the background or setting.
222
- - 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.
223
- - The character must reflect the symbolic and mythic tone of the archetype name and description.
224
- - Make sure male characters have short, cropped, or bald hair. Never long.
225
- - Female characters may have long, flowing, or styled hair if it suits the archetype.
226
- - The outfit should be rich in fantasy elements: armor, cloaks, robes, accessories. Avoid modern or casual clothing.
227
- - The character must always be facing the camera, with a clear, visible face.
228
- - Rendering instructions must include: 8K, HDR, Unreal Engine, photorealism, cinematic lighting, full body, fantasy aesthetic.
229
-
230
- Each prompt must:
231
- - Be under 1200 characters (including spaces).
232
- - Clearly distinguish between the male and female versions only by adapting gender-specific features.
233
- - Maintain identical structure, setting, and tone between both versions.
234
-
235
- Output Format
236
-
237
- 1.m
238
- [Prompt for the first archetype made for a male character]
239
-
240
- 1.f
241
- [Prompt for the first archetype made for a female character (same as the male prompt but adapted for gender-specific traits)]
242
-
243
- 2.m
244
- [Prompt for the second archetype made for a male character]
245
-
246
- 2.f
247
- [Prompt for the second archetype made for a female character]
248
-
249
- 3.m
250
- [Prompt for the third archetype made for a male character]
251
-
252
- 3.f
253
- [Prompt for the third archetype made for a female character]
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
  },