@zodic/shared 0.0.315 → 0.0.317

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.
@@ -1,431 +1,103 @@
1
- import { and, eq, sql } from 'drizzle-orm';
1
+ import { and, eq } from 'drizzle-orm';
2
2
  import { inject, injectable } from 'inversify';
3
- import { ChatMessages, Composition, schema } from '../..';
4
- import { KVArchetype, ZodiacSignSlug } from '../../types/scopes/legacy';
3
+ import { ChatMessages, schema } from '../..';
4
+ import { Gender } from '../../types';
5
+ import { ZodiacSignSlug } from '../../types/scopes/legacy';
5
6
  import { generateArchetypePrompt } from '../../utils/archetypeNamesMessages';
6
- import { duplicatedArchetypeNamePrompt } from '../../utils/duplicatedArchetypeNamePrompt';
7
- import { buildCosmicMirrorArchetypeKVKey } from '../../utils/KVKeysBuilders';
8
7
  import { AppContext } from '../base';
9
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
+
10
22
  @injectable()
11
23
  export class ArchetypeService {
12
24
  constructor(@inject(AppContext) private context: AppContext) {}
13
25
 
14
- async generateBasicInfo(combinationString: string): Promise<void> {
15
- console.log(
16
- `Generating basic info for archetypes of combination: ${combinationString}`
17
- );
18
-
19
- const [sun, ascendant, moon] = combinationString.split('-');
20
-
21
- const messages = this.context
22
- .buildLLMMessages()
23
- .generateCosmicMirrorArchetypeBasicInfo({ sun, ascendant, moon });
24
-
25
- const response = await this.context.api().callChatGPT.single(messages, {});
26
-
27
- if (!response) {
28
- throw new Error(
29
- `Failed to generate basic info for archetypes: ${combinationString}`
30
- );
31
- }
32
-
33
- const archetypes = this.parseArchetypeResponse(response);
34
-
35
- await Promise.all(
36
- archetypes.map(async (archetype, index) => {
37
- const archetypeIndex = index + 1;
38
-
39
- const maleKey = buildCosmicMirrorArchetypeKVKey(
40
- 'en-us',
41
- combinationString,
42
- 'male',
43
- archetypeIndex
44
- );
45
- const femaleKey = buildCosmicMirrorArchetypeKVKey(
46
- 'en-us',
47
- combinationString,
48
- 'female',
49
- archetypeIndex
50
- );
51
- const nonBinaryKey = buildCosmicMirrorArchetypeKVKey(
52
- 'en-us',
53
- combinationString,
54
- 'non-binary',
55
- archetypeIndex
56
- );
57
-
58
- const kvDataMale: KVArchetype = {
59
- name: archetype.name,
60
- description: archetype.descriptionMale,
61
- visualDescription: archetype.visualRepresentation,
62
- content: '',
63
- leonardoPrompt: '',
64
- virtues: archetype.virtues,
65
- images: [],
66
- status: 'idle',
67
- };
68
-
69
- const kvDataFemale: KVArchetype = {
70
- name: archetype.name,
71
- description: archetype.descriptionFemale,
72
- visualDescription: archetype.visualRepresentation,
73
- content: '',
74
- leonardoPrompt: '',
75
- virtues: archetype.virtues,
76
- images: [],
77
- status: 'idle',
78
- };
79
-
80
- await Promise.all([
81
- this.context
82
- .kvCosmicMirrorArchetypesStore()
83
- .put(maleKey, JSON.stringify(kvDataMale)),
84
- this.context
85
- .kvCosmicMirrorArchetypesStore()
86
- .put(femaleKey, JSON.stringify(kvDataFemale)),
87
- this.context
88
- .kvCosmicMirrorArchetypesStore()
89
- .put(nonBinaryKey, JSON.stringify(kvDataFemale)),
90
- ]);
91
- })
92
- );
93
-
94
- console.log(
95
- `Basic info stored for archetypes of combination: ${combinationString}`
96
- );
97
- }
98
-
99
- private parseArchetypeResponse(response: string): Array<{
100
- name: string;
101
- descriptionMale: string;
102
- descriptionFemale: string;
103
- visualRepresentation: string;
104
- virtues: string[];
105
- }> {
106
- console.log('Parsing archetype response from ChatGPT');
107
-
108
- const archetypes: Array<{
109
- name: string;
110
- descriptionMale: string;
111
- descriptionFemale: string;
112
- visualRepresentation: string;
113
- virtues: string[];
114
- }> = [];
115
-
116
- const entries = response.split(/\n\d+\.\n/).filter(Boolean);
117
-
118
- for (const entry of entries) {
119
- const nameMatch = entry.match(/- Name: (.+)/);
120
- const descriptionMaleMatch = entry.match(
121
- /- Narrative Description \(Male\): (.+)/
122
- );
123
- const descriptionFemaleMatch = entry.match(
124
- /- Narrative Description \(Female\): (.+)/
125
- );
126
- const visualRepresentationMatch = entry.match(
127
- /- Visual Representation: (.+)/
128
- );
129
- const virtuesMatch = entry.match(/- Virtues: (.+)/);
130
-
131
- if (
132
- nameMatch &&
133
- descriptionMaleMatch &&
134
- descriptionFemaleMatch &&
135
- visualRepresentationMatch &&
136
- virtuesMatch
137
- ) {
138
- archetypes.push({
139
- name: nameMatch[1].trim(),
140
- descriptionMale: descriptionMaleMatch[1].trim(),
141
- descriptionFemale: descriptionFemaleMatch[1].trim(),
142
- visualRepresentation: visualRepresentationMatch[1].trim(),
143
- virtues: virtuesMatch[1].split(',').map((virtue) => virtue.trim()),
144
- });
145
- }
146
- }
147
-
148
- return archetypes;
149
- }
150
-
151
- async generatePrompts(combinationString: string): Promise<void> {
152
- console.log(
153
- `Generating Leonardo.ai prompts for archetypes of: ${combinationString}`
154
- );
155
-
156
- const kvStore = this.context.kvCosmicMirrorArchetypesStore();
157
-
158
- const kvKeys = [1, 2, 3].map((index) =>
159
- buildCosmicMirrorArchetypeKVKey('en-us', combinationString, 'male', index)
160
- );
161
-
162
- const archetypeEntries = await Promise.all(
163
- kvKeys.map((key) => kvStore.get<KVArchetype>(key))
164
- );
165
-
166
- if (archetypeEntries.some((entry) => !entry)) {
167
- throw new Error(
168
- `Missing archetype data for combination: ${combinationString}`
169
- );
170
- }
171
-
172
- const messages = this.context
173
- .buildLLMMessages()
174
- .generateCosmicMirrorArchetypesLeonardoPrompts(
175
- archetypeEntries.map((entry) => ({
176
- name: entry!.name,
177
- visualDescription: entry!.visualDescription, // Using visual description for prompt
178
- }))
179
- );
180
-
181
- const response = await this.context.api().callChatGPT.single(messages, {});
182
- if (!response) {
183
- throw new Error(
184
- `Failed to generate Leonardo prompts for: ${combinationString}`
185
- );
186
- }
187
-
188
- const parsedPrompts = this.parseLeonardoPromptResponse(response);
189
-
190
- await Promise.all(
191
- parsedPrompts.map(async (promptData, index) => {
192
- const archetypeIndex = index + 1;
193
-
194
- const maleKey = buildCosmicMirrorArchetypeKVKey(
195
- 'en-us',
196
- combinationString,
197
- 'male',
198
- archetypeIndex
199
- );
200
- const femaleKey = buildCosmicMirrorArchetypeKVKey(
201
- 'en-us',
202
- combinationString,
203
- 'female',
204
- archetypeIndex
205
- );
206
- const nonBinaryKey = buildCosmicMirrorArchetypeKVKey(
207
- 'en-us',
208
- combinationString,
209
- 'non-binary',
210
- archetypeIndex
211
- );
212
-
213
- const updateKV = async (key: string, updatedPrompt: string) => {
214
- const existingEntry = await kvStore.get<KVArchetype>(key);
215
- if (existingEntry) {
216
- await kvStore.put(
217
- key,
218
- JSON.stringify({
219
- ...existingEntry,
220
- leonardoPrompt: updatedPrompt,
221
- })
222
- );
223
- }
224
- };
225
-
226
- await Promise.all([
227
- updateKV(maleKey, promptData.malePrompt),
228
- updateKV(femaleKey, promptData.femalePrompt),
229
- updateKV(nonBinaryKey, promptData.nonBinaryPrompt),
230
- ]);
231
- })
232
- );
233
-
234
- console.log(
235
- `Leonardo prompts stored for archetypes of: ${combinationString}`
236
- );
237
- }
238
-
239
- private parseLeonardoPromptResponse(response: string): Array<{
240
- malePrompt: string;
241
- femalePrompt: string;
242
- nonBinaryPrompt: string;
243
- }> {
244
- console.log('Parsing Leonardo prompt response from ChatGPT');
245
-
246
- const parsedPrompts: Array<{
247
- malePrompt: string;
248
- femalePrompt: string;
249
- nonBinaryPrompt: string;
250
- }> = [];
251
-
252
- const entries = response.split(/\n\d+\.\w{1,2}\n/).filter(Boolean);
253
-
254
- for (let i = 0; i < entries.length; i += 3) {
255
- const malePrompt = entries[i]?.trim();
256
- const femalePrompt = entries[i + 1]?.trim();
257
- const nonBinaryPrompt = entries[i + 2]?.trim();
26
+ private async log(
27
+ level: 'info' | 'debug' | 'warn' | 'error',
28
+ message: string,
29
+ context: Record<string, any> = {}
30
+ ) {
31
+ const logId = `archetype-service:${Date.now()}`;
32
+ const logMessage = `[${level.toUpperCase()}] ${message}`;
258
33
 
259
- if (malePrompt && femalePrompt && nonBinaryPrompt) {
260
- parsedPrompts.push({ malePrompt, femalePrompt, nonBinaryPrompt });
261
- }
34
+ console[level](logMessage, context);
35
+ const db = this.context.drizzle();
36
+ try {
37
+ await db
38
+ .insert(schema.logs)
39
+ .values({
40
+ id: logId,
41
+ level,
42
+ message,
43
+ context: JSON.stringify(context),
44
+ createdAt: new Date().getTime(),
45
+ })
46
+ .execute();
47
+ } catch (error) {
48
+ console.error('[ERROR] Failed to persist log to database:', {
49
+ error,
50
+ logId,
51
+ message,
52
+ context,
53
+ });
262
54
  }
263
-
264
- return parsedPrompts;
265
- }
266
-
267
- async generateImages(combinationString: string): Promise<void> {
268
- const kvBaseKey = `archetypes:${combinationString}`;
269
- console.log(
270
- `Generating images for archetypes of combination: ${combinationString}`
271
- );
272
-
273
- await Promise.all(
274
- [1, 2, 3].map(async (index) => {
275
- const kvKey = `${kvBaseKey}:${index}`;
276
- const archetype = await this.context
277
- .kvCosmicMirrorArchetypesStore()
278
- .get<KVArchetype>(kvKey, 'json');
279
- if (!archetype || !archetype.leonardoPrompt) {
280
- throw new Error(
281
- `Missing Leonardo prompt for archetype ${index} of combination: ${combinationString}`
282
- );
283
- }
284
- // Add image generation logic
285
- })
286
- );
287
55
  }
288
56
 
57
+ // Replace generateArchetypeNames and generateArchetypeNamesBatch
289
58
  async generateArchetypeNames(
290
- combination: string,
59
+ combinationString: string,
291
60
  overrideExisting: boolean = false,
292
- indexesToGenerate?: number[]
61
+ indexesToGenerate: number[] = [1, 2, 3]
293
62
  ) {
294
- const db = this.context.drizzle();
295
- console.log(
296
- `🚀 [Single] Starting generation for combination: ${combination} | Override: ${overrideExisting}`
297
- );
63
+ await this.log('info', 'Starting generateArchetypeNames', {
64
+ combinationString,
65
+ overrideExisting,
66
+ indexesToGenerate,
67
+ });
298
68
 
299
- const [sun, ascendant, moon] = combination.split('-') as ZodiacSignSlug[];
69
+ const [sun, ascendant, moon] = combinationString.split(
70
+ '-'
71
+ ) as ZodiacSignSlug[];
300
72
  const prompt = generateArchetypePrompt([
301
- {
302
- sun,
303
- ascendant,
304
- moon,
305
- indexesToGenerate: indexesToGenerate ?? [1, 2, 3],
306
- },
73
+ { sun, ascendant, moon, indexesToGenerate },
307
74
  ]);
308
75
  const messages: ChatMessages = [{ role: 'user', content: prompt }];
309
- const response = await this.context.api().callTogether.single(messages, {});
310
76
 
77
+ await this.log('debug', 'Calling API to generate archetype names', {
78
+ messages,
79
+ });
80
+ const response = await this.context.api().callTogether.single(messages, {});
311
81
  if (!response) {
312
- console.error(`❌ [Single] No response for: ${combination}`);
313
- return;
82
+ await this.log('error', 'No response for archetype names generation', {
83
+ combinationString,
84
+ });
85
+ throw new Error(
86
+ `Failed to generate archetype names for: ${combinationString}`
87
+ );
314
88
  }
89
+ await this.log('info', 'Received archetype names response', {
90
+ responseLength: response.length,
91
+ });
315
92
 
316
93
  const { english: englishNames, portuguese: portugueseVariants } =
317
94
  this.parseArchetypeNameBlocks(response);
318
-
319
- async function isEnglishNameDuplicate(name: string): Promise<boolean> {
320
- const result = await db
321
- .select({ name: schema.archetypesData.name })
322
- .from(schema.archetypesData)
323
- .where(
324
- and(
325
- eq(schema.archetypesData.language, 'en-us'),
326
- eq(schema.archetypesData.name, name)
327
- )
328
- )
329
- .limit(1)
330
- .execute();
331
-
332
- return result.length > 0;
333
- }
334
-
335
- await Promise.all(
336
- englishNames.map(async (entry, i) => {
337
- const index = (i + 1).toString();
338
- const ptVariant = portugueseVariants[i];
339
-
340
- if (await isEnglishNameDuplicate(entry.name)) {
341
- console.warn(`⚠️ [Single] Duplicate name skipped: ${entry.name}`);
342
- return;
343
- }
344
-
345
- for (const gender of ['male', 'female']) {
346
- const enId = `${combination}:${gender}:${index}`;
347
- const ptId = `${combination}:${gender}:${index}:pt`;
348
- const ptName = gender === 'female' ? ptVariant.fem : ptVariant.masc;
349
-
350
- await db
351
- .insert(schema.archetypesData)
352
- .values({
353
- id: enId,
354
- combination,
355
- gender,
356
- archetypeIndex: index,
357
- language: 'en-us',
358
- name: entry.name,
359
- essenceLine: entry.essenceLine,
360
- status: 'idle',
361
- })
362
- .onConflictDoUpdate({
363
- target: [schema.archetypesData.id],
364
- set: {
365
- name: entry.name,
366
- essenceLine: entry.essenceLine,
367
- updatedAt: new Date().getTime(),
368
- },
369
- });
370
-
371
- await db
372
- .insert(schema.archetypesData)
373
- .values({
374
- id: ptId,
375
- combination,
376
- gender,
377
- archetypeIndex: index,
378
- language: 'pt-br',
379
- name: ptName,
380
- essenceLine: ptVariant.essenceLine,
381
- status: 'idle',
382
- })
383
- .onConflictDoUpdate({
384
- target: [schema.archetypesData.id],
385
- set: {
386
- name: ptName,
387
- essenceLine: ptVariant.essenceLine,
388
- updatedAt: new Date().getTime(),
389
- },
390
- });
391
-
392
- console.log(
393
- `✅ [Single] Saved archetype ${index} (${gender}) for ${combination}`
394
- );
395
- }
396
- })
397
- );
398
-
399
- console.log(`🏁 [Single] Done generating names for ${combination}`);
400
- }
401
-
402
- async generateArchetypeNamesBatch(
403
- entries: Array<{ combination: string }>,
404
- overrideExisting: boolean = false
405
- ) {
406
- const db = this.context.drizzle();
407
- console.log(
408
- `🚀 [Batch] Starting generation for ${entries.length} combinations | Override: ${overrideExisting}`
409
- );
410
-
411
- const prompts = entries.map(({ combination }) => {
412
- const [sun, ascendant, moon] = combination.split('-') as ZodiacSignSlug[];
413
- return { sun, ascendant, moon };
95
+ await this.log('debug', 'Parsed archetype names', {
96
+ englishNames,
97
+ portugueseVariants,
414
98
  });
415
99
 
416
- const prompt = generateArchetypePrompt(prompts);
417
- const messages: ChatMessages = [{ role: 'user', content: prompt }];
418
- const response = await this.context.api().callTogether.single(messages, {});
419
-
420
- if (!response) {
421
- console.error(`❌ [Batch] No response from model`);
422
- return;
423
- }
424
-
425
- const blocks = response
426
- .split(/Composition \d+/)
427
- .slice(1)
428
- .map((b) => b.trim());
100
+ const db = this.context.drizzle();
429
101
 
430
102
  async function isEnglishNameDuplicate(name: string): Promise<boolean> {
431
103
  const result = await db
@@ -439,967 +111,861 @@ export class ArchetypeService {
439
111
  )
440
112
  .limit(1)
441
113
  .execute();
442
-
443
114
  return result.length > 0;
444
115
  }
445
116
 
446
- for (let i = 0; i < entries.length; i++) {
447
- const { combination } = entries[i];
448
- const block = blocks[i];
449
- console.log(`📦 [Batch] Processing: ${combination}`);
450
-
451
- const { english, portuguese } = this.parseArchetypeNameBlocks(block);
117
+ for (let i = 0; i < englishNames.length; i++) {
118
+ const index = (i + 1).toString();
119
+ const entry = englishNames[i];
120
+ const ptVariant = portugueseVariants[i];
452
121
 
453
- // Check if parsing produced the expected number of entries
454
- if (english.length !== 3 || portuguese.length !== 3) {
455
- console.error(
456
- `[Batch] Parsing failed for ${combination}. Expected 3 entries, got English: ${english.length}, Portuguese: ${portuguese.length}`
122
+ if (!indexesToGenerate.includes(parseInt(index))) {
123
+ await this.log(
124
+ 'debug',
125
+ `Skipping index ${index} as it is not in indexesToGenerate`,
126
+ { index, indexesToGenerate }
457
127
  );
458
- continue; // Skip this combination if parsing failed
128
+ continue;
459
129
  }
460
130
 
461
- for (let j = 0; j < 3; j++) {
462
- const index = (j + 1).toString();
463
- const englishEntry = english[j];
464
- const ptEntry = portuguese[j];
465
-
466
- // Skip if either entry is missing (shouldn't happen with the above check, but added for safety)
467
- if (!englishEntry || !ptEntry) {
468
- console.warn(
469
- `[Batch] Skipping index ${index} for ${combination} due to missing parsed data. English: ${JSON.stringify(
470
- englishEntry
471
- )}, Portuguese: ${JSON.stringify(ptEntry)}`
472
- );
473
- continue;
474
- }
475
-
476
- // if (await isEnglishNameDuplicate(englishEntry.name)) {
477
- // console.warn(
478
- // `⚠️ [Batch] Duplicate name skipped: ${englishEntry.name}`
479
- // );
480
- // continue;
481
- // }
482
-
483
- for (const gender of ['male', 'female']) {
484
- const enId = `${combination}:${gender}:${index}`;
485
- const ptId = `${combination}:${gender}:${index}:pt`;
486
- const ptName = gender === 'female' ? ptEntry.fem : ptEntry.masc;
131
+ if (await isEnglishNameDuplicate(entry.name)) {
132
+ await this.log('warn', `Duplicate name skipped: ${entry.name}`, {
133
+ combinationString,
134
+ index,
135
+ });
136
+ continue;
137
+ }
487
138
 
488
- await db
489
- .insert(schema.archetypesData)
490
- .values({
491
- id: enId,
492
- combination,
493
- gender,
494
- archetypeIndex: index,
495
- language: 'en-us',
496
- name: englishEntry.name,
497
- essenceLine: englishEntry.essenceLine,
498
- status: 'idle',
499
- })
500
- .onConflictDoUpdate({
501
- target: [schema.archetypesData.id],
502
- set: {
503
- name: englishEntry.name,
504
- essenceLine: englishEntry.essenceLine,
505
- updatedAt: new Date().getTime(),
506
- },
507
- });
139
+ for (const gender of ['male', 'female']) {
140
+ for (const lang of ['en-us', 'pt-br']) {
141
+ const id = `${combinationString}:${gender}:${index}${
142
+ lang === 'pt-br' ? ':pt' : ''
143
+ }`;
144
+ const name =
145
+ lang === 'en-us'
146
+ ? entry.name
147
+ : gender === 'female'
148
+ ? ptVariant.fem
149
+ : ptVariant.masc;
150
+ const essenceLine =
151
+ lang === 'en-us' ? entry.essenceLine : ptVariant.essenceLine;
152
+
153
+ await this.log('debug', `Saving archetype name for ${id}`, {
154
+ name,
155
+ essenceLine,
156
+ });
508
157
 
509
158
  await db
510
159
  .insert(schema.archetypesData)
511
160
  .values({
512
- id: ptId,
513
- combination,
161
+ id,
162
+ combination: combinationString,
514
163
  gender,
515
164
  archetypeIndex: index,
516
- language: 'pt-br',
517
- name: ptName,
518
- essenceLine: ptEntry.essenceLine,
165
+ language: lang,
166
+ name,
167
+ essenceLine,
519
168
  status: 'idle',
520
169
  })
521
170
  .onConflictDoUpdate({
522
171
  target: [schema.archetypesData.id],
523
172
  set: {
524
- name: ptName,
525
- essenceLine: ptEntry.essenceLine,
173
+ name,
174
+ essenceLine,
526
175
  updatedAt: new Date().getTime(),
527
176
  },
528
177
  });
529
178
 
530
- console.log(
531
- `✅ [Batch] Saved archetype ${index} (${gender}) for ${combination}`
179
+ await this.log(
180
+ 'info',
181
+ `Saved archetype ${index} (${gender}, ${lang}) for ${combinationString}`
532
182
  );
533
183
  }
534
184
  }
535
-
536
- console.log(`🏁 [Batch] Finished combination: ${combination}`);
537
- }
538
-
539
- console.log(
540
- `🎉 [Batch] Completed processing for all ${entries.length} combinations`
541
- );
542
- }
543
-
544
- async findIncompleteCombinations(): Promise<
545
- Array<{ combination: string; count: number }>
546
- > {
547
- const db = this.context.drizzle();
548
-
549
- const result = await db
550
- .select({
551
- combination: schema.archetypesData.combination,
552
- count: sql<number>`count(*)`.as('count'),
553
- })
554
- .from(schema.archetypesData)
555
- .groupBy(schema.archetypesData.combination)
556
- .having(sql`count(*) < 12`)
557
- .limit(400)
558
- .execute();
559
-
560
- const typedResult = result.map((row) => ({
561
- combination: row.combination,
562
- count: Number(row.count),
563
- }));
564
-
565
- console.log(`🔍 Found ${typedResult.length} incomplete combinations`);
566
- return typedResult;
567
- }
568
-
569
- async regenerateMissingArchetypeNames(overrideExisting: boolean = false) {
570
- const incomplete = await this.findIncompleteCombinations();
571
- console.log(
572
- `🔁 Starting regeneration for ${incomplete.length} combinations`
573
- );
574
-
575
- for (const { combination, count } of incomplete) {
576
- const indexesToGenerate: number[] = [];
577
-
578
- const db = this.context.drizzle();
579
- const existing = await db
580
- .select({
581
- archetypeIndex: schema.archetypesData.archetypeIndex,
582
- language: schema.archetypesData.language,
583
- gender: schema.archetypesData.gender,
584
- })
585
- .from(schema.archetypesData)
586
- .where(eq(schema.archetypesData.combination, combination))
587
- .execute();
588
-
589
- for (let i = 1; i <= 3; i++) {
590
- const hasAll =
591
- existing.filter((e) => e.archetypeIndex === i.toString()).length ===
592
- 4;
593
- if (!hasAll) indexesToGenerate.push(i);
594
- }
595
-
596
- if (indexesToGenerate.length > 0) {
597
- console.log(
598
- `🧩 Regenerating indexes ${indexesToGenerate.join(
599
- ','
600
- )} for ${combination}`
601
- );
602
- await this.generateArchetypeNames(
603
- combination,
604
- overrideExisting,
605
- indexesToGenerate
606
- );
607
- }
608
185
  }
609
186
 
610
- console.log(`✅ Done regenerating missing archetype names`);
187
+ await this.log('info', 'Completed generateArchetypeNames', {
188
+ combinationString,
189
+ });
611
190
  }
612
191
 
613
- async regenerateMissingArchetypeNamesBatch(compositions: Composition[]) {
614
- if (compositions.length === 0) {
615
- console.log('✅ [Batch] No combinations provided for regeneration');
616
- return;
617
- }
618
-
619
- console.log(
620
- `🔁 [Batch] Starting regeneration for ${compositions.length} combinations`
621
- );
622
-
623
- const prompt = generateArchetypePrompt(compositions);
624
- const messages: ChatMessages = [{ role: 'user', content: prompt }];
625
- const response = await this.context.api().callTogether.single(messages, {});
626
-
627
- if (!response) {
628
- console.error(`❌ [Batch] No response from model during regeneration`);
629
- return;
630
- }
631
-
632
- // Clean up the response by removing unexpected suffixes
633
- const cleanedResponse = response
634
- .replace(/###|---\s*###|-\s*###/g, '')
635
- .trim();
636
-
637
- const blocks = cleanedResponse
638
- .split(/Composition \d+/)
639
- .slice(1)
640
- .map((b) => b.trim());
192
+ private parseArchetypeNameBlocks(response: string): {
193
+ english: Array<{ name: string; essenceLine: string }>;
194
+ portuguese: Array<{ masc: string; fem: string; essenceLine: string }>;
195
+ } {
196
+ this.log('debug', 'Starting parseArchetypeNameBlocks', {
197
+ responseLength: response.length,
198
+ });
641
199
 
642
- for (let i = 0; i < compositions.length; i++) {
643
- const comp = compositions[i];
644
- const combination = [comp.sun, comp.ascendant, comp.moon].join('-');
645
- const block = blocks[i];
646
- const indexes = comp.indexesToGenerate ?? [1, 2, 3];
200
+ const blocks = response.split(/\n\d+\.\n/).filter(Boolean);
201
+ this.log('debug', 'Split response into blocks', {
202
+ blocksCount: blocks.length,
203
+ });
647
204
 
648
- console.log(`🔄 [Batch] Processing: ${combination}`);
205
+ const english: Array<{ name: string; essenceLine: string }> = [];
206
+ const portuguese: Array<{
207
+ masc: string;
208
+ fem: string;
209
+ essenceLine: string;
210
+ }> = [];
649
211
 
650
- const { english, portuguese } = this.parseArchetypeNameBlocks(block);
212
+ for (const block of blocks) {
213
+ const nameMatch = block.match(/- Name: (.+)/);
214
+ const essenceLineMatch = block.match(/- Essence Line: (.+)/);
215
+ const ptMascMatch = block.match(/- Portuguese \(Masc\): (.+)/);
216
+ const ptFemMatch = block.match(/- Portuguese \(Fem\): (.+)/);
217
+ const ptEssenceLineMatch = block.match(/- Portuguese Essence Line: (.+)/);
651
218
 
652
219
  if (
653
- english.length !== indexes.length ||
654
- portuguese.length !== indexes.length
220
+ nameMatch &&
221
+ essenceLineMatch &&
222
+ ptMascMatch &&
223
+ ptFemMatch &&
224
+ ptEssenceLineMatch
655
225
  ) {
656
- console.error(`❌ [Batch] Parsing failed for: ${combination}`);
657
- await this.context
658
- .drizzle()
659
- .insert(schema.archetypeNameDumps)
660
- .values({
661
- id: `${combination}:${Date.now()}`,
662
- combination,
663
- rawText: block,
664
- parsedSuccessfully: 0,
665
- createdAt: Date.now(),
666
- });
667
- continue;
668
- }
669
-
670
- for (const index of indexes) {
671
- const idx = index - 1;
672
- const englishEntry = english[idx];
673
- const ptEntry = portuguese[idx];
674
-
675
- if (!englishEntry || !ptEntry) {
676
- await this.context
677
- .drizzle()
678
- .insert(schema.archetypeNameDumps)
679
- .values({
680
- id: `${combination}:${index}:${Date.now()}`,
681
- combination,
682
- rawText: block,
683
- parsedSuccessfully: 0,
684
- createdAt: Date.now(),
685
- });
686
- console.warn(
687
- `⚠️ [Batch] Skipping index ${index} for ${combination} due to missing parsed block`
688
- );
689
- continue;
690
- }
691
-
692
- for (const gender of ['male', 'female']) {
693
- const enId = `${combination}:${gender}:${index}`;
694
- const ptId = `${combination}:${gender}:${index}:pt`;
695
- const ptName = gender === 'female' ? ptEntry.fem : ptEntry.masc;
696
-
697
- await this.context
698
- .drizzle()
699
- .insert(schema.archetypesData)
700
- .values({
701
- id: enId,
702
- combination,
703
- gender,
704
- archetypeIndex: index.toString(),
705
- language: 'en-us',
706
- name: englishEntry.name,
707
- essenceLine: englishEntry.essenceLine,
708
- status: 'idle',
709
- })
710
- .onConflictDoUpdate({
711
- target: [schema.archetypesData.id],
712
- set: {
713
- name: englishEntry.name,
714
- essenceLine: englishEntry.essenceLine,
715
- updatedAt: new Date().getTime(),
716
- },
717
- });
718
-
719
- await this.context
720
- .drizzle()
721
- .insert(schema.archetypesData)
722
- .values({
723
- id: ptId,
724
- combination,
725
- gender,
726
- archetypeIndex: index.toString(),
727
- language: 'pt-br',
728
- name: ptName,
729
- essenceLine: ptEntry.essenceLine,
730
- status: 'idle',
731
- })
732
- .onConflictDoUpdate({
733
- target: [schema.archetypesData.id],
734
- set: {
735
- name: ptName,
736
- essenceLine: ptEntry.essenceLine,
737
- updatedAt: new Date().getTime(),
738
- },
739
- });
740
-
741
- console.log(
742
- `✅ [Batch] Saved archetype ${index} (${gender}) for ${combination}`
743
- );
744
- }
226
+ english.push({
227
+ name: nameMatch[1].trim(),
228
+ essenceLine: essenceLineMatch[1].trim(),
229
+ });
230
+ portuguese.push({
231
+ masc: ptMascMatch[1].trim(),
232
+ fem: ptFemMatch[1].trim(),
233
+ essenceLine: ptEssenceLineMatch[1].trim(),
234
+ });
235
+ } else {
236
+ this.log('warn', 'Malformed archetype name block', { block });
745
237
  }
746
-
747
- console.log(`🏁 [Batch] Finished combination: ${combination}`);
748
238
  }
749
239
 
750
- console.log(
751
- `🎉 [Batch] Completed regenerating ${compositions.length} combinations`
752
- );
240
+ this.log('info', 'Completed parseArchetypeNameBlocks', {
241
+ english,
242
+ portuguese,
243
+ });
244
+ return { english, portuguese };
753
245
  }
754
246
 
755
- async regenerateDuplicateArchetypeNamesBatch(
756
- entriesByCombination: Record<string, any[]>
247
+ async generateDescriptionsAndVirtues(
248
+ combinationString: string,
249
+ gender: Gender,
250
+ language: string
757
251
  ) {
252
+ await this.log('info', 'Starting generateDescriptionsAndVirtues', {
253
+ combinationString,
254
+ gender,
255
+ language,
256
+ });
257
+
758
258
  const db = this.context.drizzle();
759
- console.log(
760
- `[RegenerateDuplicatesBatch] Processing batch with ${
761
- Object.keys(entriesByCombination).length
762
- } combinations`
763
- );
764
-
765
- // Process each combination in the batch
766
- for (const [combination, entries] of Object.entries(entriesByCombination)) {
767
- console.log(
768
- `[RegenerateDuplicatesBatch] Processing combination: ${combination}`
259
+ const archetypes = await db
260
+ .select()
261
+ .from(schema.archetypesData)
262
+ .where(
263
+ and(
264
+ eq(schema.archetypesData.combination, combinationString),
265
+ eq(schema.archetypesData.gender, gender),
266
+ eq(schema.archetypesData.language, 'en-us')
267
+ )
268
+ )
269
+ .execute();
270
+
271
+ await this.log('info', 'Fetched archetypes for description generation', {
272
+ archetypesCount: archetypes.length,
273
+ names: archetypes.map((a) => a.name),
274
+ });
275
+
276
+ const descVirtueMessages = this.context
277
+ .buildLLMMessages()
278
+ .generateCosmicMirrorDescriptionAndVirtues({
279
+ combination: combinationString,
280
+ names: [archetypes[0].name, archetypes[1].name, archetypes[2].name],
281
+ essenceLines: [
282
+ archetypes[0].essenceLine,
283
+ archetypes[1].essenceLine,
284
+ archetypes[2].essenceLine,
285
+ ],
286
+ });
287
+
288
+ await this.log('debug', 'Calling API for descriptions and virtues', {
289
+ messages: descVirtueMessages,
290
+ });
291
+ const descVirtueResponse = await this.context
292
+ .api()
293
+ .callTogether.single(descVirtueMessages, {});
294
+ if (!descVirtueResponse) {
295
+ await this.log('error', 'No response for descriptions and virtues', {
296
+ combinationString,
297
+ });
298
+ throw new Error(
299
+ `Failed to generate descriptions and virtues for: ${combinationString}`
769
300
  );
770
-
771
- // Fetch all existing English names for this combination (across all archetypeIndex, gender, language)
772
- const existingNamesResult = await db
773
- .selectDistinct({ name: schema.archetypesData.name })
774
- .from(schema.archetypesData)
301
+ }
302
+ await this.log('info', 'Received descriptions and virtues response', {
303
+ responseLength: descVirtueResponse.length,
304
+ });
305
+
306
+ const parsedDescVirtues = this.parseDescriptionAndVirtuesResponse(descVirtueResponse);
307
+ await this.log('info', 'Parsed descriptions and virtues', {
308
+ parsedDescVirtues,
309
+ });
310
+
311
+ for (let i = 0; i < 3; i++) {
312
+ const index = (i + 1).toString();
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
+ })
775
327
  .where(
776
328
  and(
777
- eq(schema.archetypesData.combination, combination),
329
+ eq(schema.archetypesData.id, enId),
778
330
  eq(schema.archetypesData.language, 'en-us')
779
331
  )
780
332
  )
781
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)`);
380
+ }
381
+
382
+ await this.log('info', 'Completed generateDescriptionsAndVirtues', {
383
+ combinationString,
384
+ gender,
385
+ language,
386
+ });
387
+ return parsedDescVirtues;
388
+ }
782
389
 
783
- let existingNames = existingNamesResult.map((r) => r.name);
784
- console.log(
785
- `[RegenerateDuplicatesBatch] Existing names for ${combination}: ${existingNames.join(
786
- ', '
787
- )}`
788
- );
789
-
790
- // Process each entry with the duplicate name in this combination
791
- for (const entry of entries) {
792
- const { archetypeIndex, essenceLine } = entry;
793
- console.log(
794
- `[RegenerateDuplicatesBatch] Regenerating name for ${combination} (archetypeIndex: ${archetypeIndex})`
795
- );
796
-
797
- let newName: {
798
- name_en: string;
799
- name_pt_male: string;
800
- name_pt_female: string;
801
- } | null = null;
802
- let attempts = 0;
803
- const maxAttempts = 5;
804
-
805
- // Generate a new name until a unique one is found
806
- while (attempts < maxAttempts) {
807
- attempts++;
808
- console.log(
809
- `[RegenerateDuplicatesBatch] Attempt ${attempts} to generate a new name for ${combination} (archetypeIndex: ${archetypeIndex})`
810
- );
811
-
812
- // Build the prompt
813
- const prompt = duplicatedArchetypeNamePrompt({
814
- combination,
815
- essenceLine: essenceLine || 'A unique and meaningful archetype', // Fallback for null essenceLine
816
- existingNames,
817
- });
818
-
819
- // Send the request to the AI
820
- const messages: ChatMessages = [{ role: 'user', content: prompt }];
821
- const response = await this.context
822
- .api()
823
- .callTogether.single(messages, {});
824
-
825
- if (!response) {
826
- console.error(
827
- `[RegenerateDuplicatesBatch] No response from model for ${combination} (archetypeIndex: ${archetypeIndex})`
828
- );
829
- continue;
830
- }
831
-
832
- // Parse the response
833
- try {
834
- newName = JSON.parse(response);
835
- console.log(
836
- `[RegenerateDuplicatesBatch] Generated new name: ${JSON.stringify(
837
- newName,
838
- null,
839
- 2
840
- )}`
841
- );
842
-
843
- // Validate the response format
844
- if (
845
- !newName ||
846
- !newName.name_en ||
847
- !newName.name_pt_male ||
848
- !newName.name_pt_female
849
- ) {
850
- console.error(
851
- `[RegenerateDuplicatesBatch] Invalid response format for ${combination} (archetypeIndex: ${archetypeIndex}): ${response}`
852
- );
853
- newName = null;
854
- continue;
855
- }
856
- } catch (error) {
857
- console.error(
858
- `[RegenerateDuplicatesBatch] Failed to parse AI response for ${combination} (archetypeIndex: ${archetypeIndex}): ${response}`,
859
- error
860
- );
861
- continue;
862
- }
863
-
864
- // Check if the new name is unique (globally across all combinations)
865
- const isDuplicate = await db
866
- .select({ name: schema.archetypesData.name })
867
- .from(schema.archetypesData)
868
- .where(
869
- and(
870
- eq(schema.archetypesData.language, 'en-us'),
871
- eq(schema.archetypesData.name, newName.name_en)
872
- )
873
- )
874
- .limit(1)
875
- .execute();
876
-
877
- if (isDuplicate.length > 0) {
878
- console.warn(
879
- `[RegenerateDuplicatesBatch] Generated name "${newName.name_en}" is already used. Adding to existing names and retrying.`
880
- );
881
- existingNames.push(newName.name_en);
882
- newName = null;
883
- continue;
884
- }
885
-
886
- // If we reach here, the name is unique
887
- break;
888
- }
889
390
 
890
- if (!newName) {
891
- console.error(
892
- `[RegenerateDuplicatesBatch] Failed to generate a unique name for ${combination} (archetypeIndex: ${archetypeIndex}) after ${maxAttempts} attempts`
893
- );
894
- continue;
391
+ private parseDescriptionAndVirtuesResponse(response: string): ParsedDescriptionAndVirtues[] {
392
+ this.log('debug', 'Starting parseDescriptionAndVirtuesResponse', {
393
+ responseLength: response.length,
394
+ });
395
+
396
+ const entries = response
397
+ .split(/---/)
398
+ .map((block) => block.trim())
399
+ .filter((block) => block.length > 0);
400
+
401
+ this.log('debug', 'Split response into entries', {
402
+ entriesCount: entries.length,
403
+ });
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
+
413
+ const result = entries.map((entry, entryIndex) => {
414
+ this.log('debug', `Processing entry ${entryIndex + 1}`, { entry });
415
+
416
+ const lines = entry.split('\n').map((line) => line.trim()).filter((line) => line);
417
+ this.log('debug', `Split entry ${entryIndex + 1} into lines`, {
418
+ linesCount: lines.length,
419
+ lines,
420
+ });
421
+
422
+ let descriptionEN = '';
423
+ let descriptionPTM = '';
424
+ let descriptionPTF = '';
425
+ let virtuesEN: string[] = [];
426
+ let virtuesPT: string[] = [];
427
+ let currentField = '';
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
895
437
  }
896
-
897
- // Update the database with the new names
898
- for (const gender of ['male', 'female']) {
899
- const enId = `${combination}:${gender}:${archetypeIndex}`;
900
- const ptId = `${combination}:${gender}:${archetypeIndex}:pt`;
901
-
902
- // Update English entry
903
- await db
904
- .update(schema.archetypesData)
905
- .set({
906
- name: newName.name_en,
907
- updatedAt: new Date().getTime(),
908
- })
909
- .where(
910
- and(
911
- eq(schema.archetypesData.id, enId),
912
- eq(schema.archetypesData.language, 'en-us')
913
- )
914
- )
915
- .execute();
916
-
917
- // Update Portuguese entry
918
- const ptName =
919
- gender === 'female' ? newName.name_pt_female : newName.name_pt_male;
920
- await db
921
- .update(schema.archetypesData)
922
- .set({
923
- name: ptName,
924
- updatedAt: new Date().getTime(),
925
- })
926
- .where(
927
- and(
928
- eq(schema.archetypesData.id, ptId),
929
- eq(schema.archetypesData.language, 'pt-br')
930
- )
931
- )
932
- .execute();
933
-
934
- console.log(
935
- `[RegenerateDuplicatesBatch] Updated ${combination} (archetypeIndex: ${archetypeIndex}, gender: ${gender}) with new name - English: ${newName.name_en}, Portuguese: ${ptName}`
936
- );
438
+
439
+ if (line.startsWith('• Description EN:')) {
440
+ currentField = 'descriptionEN';
441
+ descriptionEN = line.split('• Description EN:')[1]?.trim() || '';
442
+ this.log('debug', `Extracted descriptionEN for entry ${entryIndex + 1}`, { descriptionEN });
443
+ } else if (line.startsWith('• Description PT-M:')) {
444
+ currentField = 'descriptionPTM';
445
+ descriptionPTM = line.split('• Description PT-M:')[1]?.trim() || '';
446
+ this.log('debug', `Extracted descriptionPTM for entry ${entryIndex + 1}`, { descriptionPTM });
447
+ } else if (line.startsWith('• Description PT-F:')) {
448
+ currentField = 'descriptionPTF';
449
+ descriptionPTF = line.split('• Description PT-F:')[1]?.trim() || '';
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 });
469
+ } else if (line.startsWith('• 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')) {
490
+ if (currentField === 'descriptionEN') {
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 });
499
+ }
937
500
  }
938
501
  }
939
- }
940
-
941
- console.log(`✅ [RegenerateDuplicatesBatch] Finished processing batch`);
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`);
514
+ }
515
+
516
+ return { descriptionEN, descriptionPTM, descriptionPTF, virtuesEN, virtuesPT };
517
+ });
518
+
519
+ this.log('info', 'Completed parseDescriptionAndVirtuesResponse', { result });
520
+ return result;
942
521
  }
943
522
 
944
- async recycleArchetypeNameDumpsBatch(
945
- compositions: Composition[]
946
- ): Promise<void> {
947
- console.log('🔁 [Recycle] Starting recycling of archetype name dumps');
523
+ async generateContent(
524
+ combinationString: string,
525
+ gender: Gender,
526
+ language: string,
527
+ descriptions: Array<{ descriptionEN: string }>
528
+ ) {
529
+ await this.log('info', 'Starting generateContent', {
530
+ combinationString,
531
+ gender,
532
+ language,
533
+ });
948
534
 
949
- if (compositions.length === 0) {
950
- console.log('✅ [Recycle] No compositions provided for recycling');
951
- return;
952
- }
535
+ const db = this.context.drizzle();
536
+ const archetypes = await db
537
+ .select()
538
+ .from(schema.archetypesData)
539
+ .where(
540
+ and(
541
+ eq(schema.archetypesData.combination, combinationString),
542
+ eq(schema.archetypesData.gender, gender),
543
+ eq(schema.archetypesData.language, 'en-us')
544
+ )
545
+ )
546
+ .execute();
547
+
548
+ await this.log('info', 'Fetched archetypes for content generation', {
549
+ archetypesCount: archetypes.length,
550
+ names: archetypes.map((a) => a.name),
551
+ });
953
552
 
954
- console.log(
955
- `📦 [Recycle] Total compositions to recycle: ${compositions.length}`
956
- );
553
+ const contentResults = [];
554
+ for (let i = 0; i < 3; i++) {
555
+ await this.log('debug', `Generating content for archetype ${i + 1}`, {
556
+ name: archetypes[i].name,
557
+ });
558
+ const contentMessages = this.context
559
+ .buildLLMMessages()
560
+ .generateCosmicMirrorArchetypeContent({
561
+ combination: combinationString,
562
+ name: archetypes[i].name,
563
+ description: descriptions[i].descriptionEN,
564
+ });
957
565
 
958
- // Step 1: Process each composition
959
- const db = this.context.drizzle();
960
- for (const comp of compositions) {
961
- const combination = [comp.sun, comp.ascendant, comp.moon].join('-');
962
- const indexes = comp.indexesToGenerate ?? [1, 2, 3];
963
-
964
- console.log(
965
- `🔄 [Recycle] Processing combination: ${combination} with indexes: [${indexes.join(
966
- ', '
967
- )}]`
566
+ await this.log('debug', `Calling API for content of archetype ${i + 1}`, {
567
+ messages: contentMessages,
568
+ });
569
+ const contentResponse = await this.context
570
+ .api()
571
+ .callTogether.single(contentMessages, {});
572
+ if (!contentResponse) {
573
+ await this.log(
574
+ 'error',
575
+ `No response for content of archetype ${i + 1}`,
576
+ { combinationString, archetype: archetypes[i].name }
577
+ );
578
+ throw new Error(
579
+ `Failed to generate content for archetype ${
580
+ i + 1
581
+ } of: ${combinationString}`
582
+ );
583
+ }
584
+ await this.log(
585
+ 'info',
586
+ `Received content response for archetype ${i + 1}`,
587
+ { responseLength: contentResponse.length }
968
588
  );
969
589
 
970
- // Fetch all dump entries for this combination
971
- const dumpEntries = await db
972
- .select({
973
- id: schema.archetypeNameDumps.id,
974
- rawText: schema.archetypeNameDumps.rawText,
975
- })
976
- .from(schema.archetypeNameDumps)
977
- .where(eq(schema.archetypeNameDumps.combination, combination))
978
- .execute();
979
-
980
- console.log(
981
- `[Recycle] Retrieved ${
982
- dumpEntries.length
983
- } dump entries for ${combination}: ${JSON.stringify(
984
- dumpEntries,
985
- null,
986
- 2
987
- )}`
590
+ await this.log(
591
+ 'debug',
592
+ `Parsing content response for archetype ${i + 1}`
988
593
  );
594
+ const parsedContent = this.parseContentResponse(contentResponse);
595
+ contentResults.push(parsedContent);
596
+ await this.log('info', `Parsed content for archetype ${i + 1}`, {
597
+ parsedContent,
598
+ });
599
+
600
+ const index = (i + 1).toString();
601
+ for (const lang of ['en-us', 'pt-br']) {
602
+ const contentLangIndex =
603
+ lang === 'en-us' ? 0 : gender === 'male' ? 1 : 2;
604
+ const content = [
605
+ {
606
+ section: 'voiceOfTheSoul',
607
+ text: parsedContent[contentLangIndex].voiceOfTheSoul,
608
+ },
609
+ {
610
+ section: 'giftsYouBear',
611
+ text: parsedContent[contentLangIndex].giftsYouBear,
612
+ },
613
+ {
614
+ section: 'shadowsYouFace',
615
+ text: parsedContent[contentLangIndex].shadowsYouFace,
616
+ },
617
+ {
618
+ section: 'rhythmOfYourDays',
619
+ text: parsedContent[contentLangIndex].rhythmOfYourDays,
620
+ },
621
+ {
622
+ section: 'tiesThatBind',
623
+ text: parsedContent[contentLangIndex].tiesThatBind,
624
+ },
625
+ {
626
+ section: 'lightWithin',
627
+ text: parsedContent[contentLangIndex].lightWithin,
628
+ },
629
+ ];
630
+
631
+ const id = `${combinationString}:${gender}:${index}${
632
+ lang === 'pt-br' ? ':pt' : ''
633
+ }`;
634
+ await this.log('debug', `Updating content for ${id}`, { content });
635
+
636
+ await db
637
+ .update(schema.archetypesData)
638
+ .set({
639
+ content: JSON.stringify(content),
640
+ updatedAt: new Date().getTime(),
641
+ })
642
+ .where(
643
+ and(
644
+ eq(schema.archetypesData.id, id),
645
+ eq(schema.archetypesData.language, lang)
646
+ )
647
+ )
648
+ .execute();
989
649
 
990
- if (dumpEntries.length === 0) {
991
- console.log(
992
- `⚠️ [Recycle] No dump entries found for combination: ${combination}`
993
- );
994
- continue;
650
+ await this.log('info', `Updated content for ${id}`);
995
651
  }
652
+ }
996
653
 
997
- // Step 2: Process each missing index
998
- for (const index of indexes) {
999
- console.log(`[Recycle] Processing index ${index} for ${combination}`);
1000
-
1001
- // Find a dump entry that contains the specific index
1002
- let rawText: string | null = null;
1003
- let selectedEntryId: string | null = null;
1004
- for (const entry of dumpEntries) {
1005
- if (entry.rawText && entry.rawText.includes(`-EN ${index}.`)) {
1006
- rawText = entry.rawText;
1007
- selectedEntryId = entry.id;
1008
- console.log(
1009
- `[Recycle] Selected dump entry for index ${index} with id ${selectedEntryId}: ${rawText}`
1010
- );
1011
- break;
1012
- }
1013
- }
654
+ await this.log('info', 'Completed generateContent', {
655
+ combinationString,
656
+ gender,
657
+ language,
658
+ });
659
+ return contentResults;
660
+ }
1014
661
 
1015
- if (!rawText) {
1016
- console.log(
1017
- `⚠️ [Recycle] No dump entry contains index ${index} for combination: ${combination}`
1018
- );
1019
- continue;
1020
- }
662
+ private parseContentResponse(response: string) {
663
+ this.log('debug', 'Starting parseContentResponse', {
664
+ responseLength: response.length,
665
+ });
1021
666
 
1022
- // Clean up the raw text
1023
- console.log(
1024
- `[Recycle] Cleaning rawText for index ${index}: ${rawText}`
1025
- );
1026
- const cleanedText = rawText
1027
- .replace(/###|---\s*###|-\s*###/g, '')
1028
- .trim();
1029
- console.log(
1030
- `[Recycle] Cleaned text for index ${index}: ${cleanedText}`
1031
- );
667
+ const sections = response
668
+ .split(/---/)
669
+ .map((block) => block.trim())
670
+ .filter((block) => block.length > 0);
671
+ this.log('debug', 'Split response into sections', {
672
+ sectionsCount: sections.length,
673
+ });
1032
674
 
1033
- // Extract the specific index sections
1034
- const enSectionMatch = cleanedText.match(
1035
- new RegExp(`-EN ${index}\\.\\s*[^-]*(?=-EN|-PT|$)`, 's')
1036
- );
1037
- const ptSectionMatch = cleanedText.match(
1038
- new RegExp(`-PT ${index}\\.\\s*[^-]*(?=-EN|-PT|$)`, 's')
1039
- );
675
+ const result = sections.map((section, sectionIndex) => {
676
+ this.log('debug', `Processing section ${sectionIndex + 1}`, { section });
1040
677
 
1041
- console.log(
1042
- `[Recycle] Extracted sections for index ${index} - enSectionMatch: ${JSON.stringify(
1043
- enSectionMatch
1044
- )}, ptSectionMatch: ${JSON.stringify(ptSectionMatch)}`
1045
- );
678
+ const lines = section.split('\n').filter((line) => line.trim());
679
+ this.log('debug', `Split section ${sectionIndex + 1} into lines`, {
680
+ linesCount: lines.length,
681
+ lines,
682
+ });
1046
683
 
684
+ const content: Record<string, string> = {};
685
+ let currentSection = '';
686
+ let currentText: string[] = [];
687
+
688
+ for (const line of lines) {
1047
689
  if (
1048
- !enSectionMatch ||
1049
- !ptSectionMatch ||
1050
- !enSectionMatch[0] ||
1051
- !ptSectionMatch[0]
690
+ line.startsWith('EN:') ||
691
+ line.startsWith('PT-M:') ||
692
+ line.startsWith('PT-F:')
1052
693
  ) {
1053
- console.log(
1054
- `⚠️ [Recycle] Could not extract index ${index} sections for combination: ${combination}. enSectionMatch: ${JSON.stringify(
1055
- enSectionMatch
1056
- )}, ptSectionMatch: ${JSON.stringify(ptSectionMatch)}`
694
+ this.log(
695
+ 'debug',
696
+ `Skipping language header in section ${sectionIndex + 1}`,
697
+ { line }
1057
698
  );
1058
699
  continue;
1059
700
  }
1060
701
 
1061
- // Combine the extracted sections into a block for parsing
1062
- const block = `-EN\n${enSectionMatch[0]
1063
- .replace(/-EN\s*/, '')
1064
- .trim()}\n\n-PT\n${ptSectionMatch[0].replace(/-PT\s*/, '').trim()}`;
1065
- console.log(`[Recycle] Constructed block for index ${index}: ${block}`);
1066
-
1067
- // Step 3: Parse the block
1068
- const { english, portuguese } = this.parseArchetypeNameBlocks(block);
1069
- console.log(
1070
- `[Recycle] Parsed results for index ${index} - English: ${JSON.stringify(
1071
- english,
1072
- null,
1073
- 2
1074
- )}, Portuguese: ${JSON.stringify(portuguese, null, 2)}`
1075
- );
1076
-
1077
- if (english.length === 0 || portuguese.length === 0) {
1078
- console.error(
1079
- `❌ [Recycle] Parsing failed for index ${index} of combination: ${combination}. Block: ${block}`
1080
- );
1081
- if (block) {
1082
- await this.context
1083
- .drizzle()
1084
- .insert(schema.archetypeNameDumps)
1085
- .values({
1086
- id: `${combination}:${index}:${Date.now()}`,
1087
- combination,
1088
- rawText: block,
1089
- parsedSuccessfully: 0,
1090
- createdAt: Date.now(),
1091
- });
1092
- console.log(
1093
- `[Recycle] Logged failed parse to archetype_name_dumps for index ${index} of ${combination}`
1094
- );
1095
- } else {
1096
- console.error(
1097
- `❌ [Recycle] Cannot log failed parse to archetype_name_dumps: block is undefined`
702
+ if (line.startsWith('#')) {
703
+ if (currentSection && currentText.length > 0) {
704
+ const key = currentSection
705
+ .toLowerCase()
706
+ .replace(/\s+/g, '')
707
+ .replace(/([A-Z])/g, (match) => match.toLowerCase());
708
+ content[key] = currentText.join(' ').trim();
709
+ this.log(
710
+ 'debug',
711
+ `Extracted section ${currentSection} in section ${
712
+ sectionIndex + 1
713
+ }`,
714
+ { key, text: content[key] }
1098
715
  );
1099
716
  }
1100
- continue;
1101
- }
1102
-
1103
- const englishEntry = english[0];
1104
- const ptEntry = portuguese[0];
1105
-
1106
- console.log(
1107
- `[Recycle] Extracted entries for index ${index} - englishEntry: ${JSON.stringify(
1108
- englishEntry,
1109
- null,
1110
- 2
1111
- )}, ptEntry: ${JSON.stringify(ptEntry, null, 2)}`
1112
- );
1113
-
1114
- if (!englishEntry || !ptEntry) {
1115
- console.warn(
1116
- `⚠️ [Recycle] Skipping index ${index} for ${combination} due to missing parsed data. englishEntry: ${JSON.stringify(
1117
- englishEntry
1118
- )}, ptEntry: ${JSON.stringify(ptEntry)}`
717
+ currentSection = line.replace('# ', '');
718
+ currentText = [];
719
+ this.log(
720
+ 'debug',
721
+ `Starting new section ${currentSection} in section ${
722
+ sectionIndex + 1
723
+ }`
1119
724
  );
1120
- continue;
725
+ } else {
726
+ currentText.push(line);
1121
727
  }
728
+ }
1122
729
 
1123
- // Step 4: Insert the parsed data into archetypes_data
1124
- for (const gender of ['male', 'female']) {
1125
- const enId = `${combination}:${gender}:${index}`;
1126
- const ptId = `${combination}:${gender}:${index}:pt`;
1127
- const ptName = gender === 'female' ? ptEntry.fem : ptEntry.masc;
1128
-
1129
- console.log(
1130
- `[Recycle] Inserting English entry for ${combination}, index ${index}, gender ${gender}: id=${enId}, name=${englishEntry.name}, essenceLine=${englishEntry.essenceLine}`
1131
- );
1132
- await this.context
1133
- .drizzle()
1134
- .insert(schema.archetypesData)
1135
- .values({
1136
- id: enId,
1137
- combination,
1138
- gender,
1139
- archetypeIndex: index.toString(),
1140
- language: 'en-us',
1141
- name: englishEntry.name,
1142
- essenceLine: englishEntry.essenceLine,
1143
- status: 'idle',
1144
- })
1145
- .onConflictDoUpdate({
1146
- target: [schema.archetypesData.id],
1147
- set: {
1148
- name: englishEntry.name,
1149
- essenceLine: englishEntry.essenceLine,
1150
- updatedAt: new Date().getTime(),
1151
- },
1152
- });
1153
-
1154
- console.log(
1155
- `[Recycle] Inserting Portuguese entry for ${combination}, index ${index}, gender ${gender}: id=${ptId}, name=${ptName}, essenceLine=${ptEntry.essenceLine}`
1156
- );
1157
- await this.context
1158
- .drizzle()
1159
- .insert(schema.archetypesData)
1160
- .values({
1161
- id: ptId,
1162
- combination,
1163
- gender,
1164
- archetypeIndex: index.toString(),
1165
- language: 'pt-br',
1166
- name: ptName,
1167
- essenceLine: ptEntry.essenceLine,
1168
- status: 'idle',
1169
- })
1170
- .onConflictDoUpdate({
1171
- target: [schema.archetypesData.id],
1172
- set: {
1173
- name: ptName,
1174
- essenceLine: ptEntry.essenceLine,
1175
- updatedAt: new Date().getTime(),
1176
- },
1177
- });
730
+ if (currentSection && currentText.length > 0) {
731
+ const key = currentSection
732
+ .toLowerCase()
733
+ .replace(/\s+/g, '')
734
+ .replace(/([A-Z])/g, (match) => match.toLowerCase());
735
+ content[key] = currentText.join(' ').trim();
736
+ this.log(
737
+ 'debug',
738
+ `Extracted final section ${currentSection} in section ${
739
+ sectionIndex + 1
740
+ }`,
741
+ { key, text: content[key] }
742
+ );
743
+ }
1178
744
 
1179
- // Delete the processed dump entry
1180
- if (selectedEntryId) {
1181
- console.log(
1182
- `[Recycle] Deleting dump entry with id ${selectedEntryId} for ${combination}, index ${index}`
1183
- );
1184
- await db
1185
- .delete(schema.archetypeNameDumps)
1186
- .where(eq(schema.archetypeNameDumps.id, selectedEntryId));
1187
- console.log(
1188
- `[Recycle] Successfully deleted dump entry with id ${selectedEntryId}`
1189
- );
1190
- } else {
1191
- console.warn(
1192
- `[Recycle] Could not delete dump entry for ${combination}, index ${index}: selectedEntryId is null`
1193
- );
745
+ const parsedContent = {
746
+ voiceOfTheSoul:
747
+ content['thevoiceofthesoul'] || content['avozdaalma'] || '',
748
+ giftsYouBear:
749
+ content['thegiftsyoubear'] || content['osdonsquevocêcarrega'] || '',
750
+ shadowsYouFace:
751
+ content['theshadowsyouface'] || content['assombrasqueenfrenta'] || '',
752
+ rhythmOfYourDays:
753
+ content['therhythmofyourdays'] || content['oritmodosseusdias'] || '',
754
+ tiesThatBind:
755
+ content['thetiesthatbind'] || content['oslaçosqueteconectam'] || '',
756
+ lightWithin: content['thelightwithin'] || content['aluzinterior'] || '',
757
+ };
758
+
759
+ if (Object.values(parsedContent).some((value) => !value)) {
760
+ this.log(
761
+ 'warn',
762
+ `Malformed content response for section ${sectionIndex + 1}`,
763
+ {
764
+ parsedContent,
765
+ response: section,
1194
766
  }
1195
-
1196
- console.log(
1197
- `✅ [Recycle] Saved archetype ${index} (${gender}) for ${combination}`
1198
- );
1199
- }
767
+ );
1200
768
  }
1201
769
 
1202
- console.log(`🏁 [Recycle] Finished combination: ${combination}`);
1203
- }
770
+ return parsedContent;
771
+ });
1204
772
 
1205
- console.log(
1206
- `🎉 [Recycle] Completed recycling for ${compositions.length} combinations`
1207
- );
773
+ this.log('info', 'Completed parseContentResponse', { result });
774
+ return result;
1208
775
  }
1209
776
 
1210
- async fetchMissingArchetypeCompositions(): Promise<Composition[]> {
777
+ async generateLeonardoPrompts(
778
+ combinationString: string,
779
+ gender: Gender,
780
+ language: string,
781
+ descriptions: Array<{ descriptionEN: string }>
782
+ ) {
783
+ await this.log('info', 'Starting generateLeonardoPrompts', {
784
+ combinationString,
785
+ gender,
786
+ language,
787
+ });
788
+
1211
789
  const db = this.context.drizzle();
1212
- const incomplete = await this.findIncompleteCombinations();
1213
- console.log(
1214
- `🔎 [Fetch] Retrieved ${incomplete.length} incomplete combinations`
1215
- );
1216
- const limited = incomplete.slice(0, 400);
1217
-
1218
- const compositions: Composition[] = [];
1219
-
1220
- for (const { combination } of limited) {
1221
- console.log(`🔍 [Fetch] Processing combination: ${combination}`);
1222
- const [sun, ascendant, moon] = combination.split('-') as ZodiacSignSlug[];
1223
-
1224
- const existing = await db
1225
- .select({
1226
- archetypeIndex: schema.archetypesData.archetypeIndex,
1227
- language: schema.archetypesData.language,
1228
- gender: schema.archetypesData.gender,
1229
- })
1230
- .from(schema.archetypesData)
1231
- .where(eq(schema.archetypesData.combination, combination))
1232
- .execute();
790
+ const archetypes = await db
791
+ .select()
792
+ .from(schema.archetypesData)
793
+ .where(
794
+ and(
795
+ eq(schema.archetypesData.combination, combinationString),
796
+ eq(schema.archetypesData.gender, gender),
797
+ eq(schema.archetypesData.language, 'en-us')
798
+ )
799
+ )
800
+ .execute();
1233
801
 
1234
- const indexesToGenerate: number[] = [];
802
+ await this.log('info', 'Fetched archetypes for Leonardo prompts', {
803
+ archetypesCount: archetypes.length,
804
+ names: archetypes.map((a) => a.name),
805
+ });
1235
806
 
1236
- for (let i = 1; i <= 3; i++) {
1237
- const hasAll =
1238
- existing.filter((e) => e.archetypeIndex === i.toString()).length ===
1239
- 4;
1240
- if (!hasAll) indexesToGenerate.push(i);
1241
- }
807
+ const promptMessages = this.context
808
+ .buildLLMMessages()
809
+ .generateCosmicMirrorArchetypeLeonardoPrompts(
810
+ archetypes.map((arc, idx) => ({
811
+ name: arc.name,
812
+ description: descriptions[idx].descriptionEN,
813
+ }))
814
+ );
1242
815
 
1243
- if (indexesToGenerate.length > 0) {
1244
- compositions.push({
1245
- sun,
1246
- ascendant,
1247
- moon,
1248
- indexesToGenerate,
1249
- });
1250
- console.log(
1251
- `✅ [Fetch] Will regenerate indexes [${indexesToGenerate.join(
1252
- ', '
1253
- )}] for ${combination}`
1254
- );
1255
- }
816
+ await this.log('debug', 'Calling API for Leonardo prompts', {
817
+ messages: promptMessages,
818
+ });
819
+ const promptResponse = await this.context
820
+ .api()
821
+ .callTogether.single(promptMessages, {});
822
+ if (!promptResponse) {
823
+ await this.log('error', 'No response for Leonardo prompts', {
824
+ combinationString,
825
+ });
826
+ throw new Error(
827
+ `Failed to generate Leonardo prompts for: ${combinationString}`
828
+ );
1256
829
  }
830
+ await this.log('info', 'Received Leonardo prompts response', {
831
+ responseLength: promptResponse.length,
832
+ });
1257
833
 
1258
- console.log(
1259
- `📦 [Fetch] Total compositions to regenerate (limited to 400): ${compositions.length}`
1260
- );
834
+ const parsedPrompts = this.parseLeonardoPromptResponse(promptResponse);
835
+ await this.log('info', 'Parsed Leonardo prompts', { parsedPrompts });
836
+
837
+ for (let i = 0; i < 3; i++) {
838
+ const index = (i + 1).toString();
839
+ const leonardoPrompt =
840
+ gender === 'male'
841
+ ? parsedPrompts[i].malePrompt
842
+ : parsedPrompts[i].femalePrompt;
843
+
844
+ for (const lang of ['en-us', 'pt-br']) {
845
+ const id = `${combinationString}:${gender}:${index}${
846
+ lang === 'pt-br' ? ':pt' : ''
847
+ }`;
848
+ await this.log('debug', `Updating Leonardo prompt for ${id}`, {
849
+ leonardoPrompt,
850
+ });
1261
851
 
1262
- return compositions;
1263
- }
852
+ await db
853
+ .update(schema.archetypesData)
854
+ .set({
855
+ leonardoPrompt,
856
+ updatedAt: new Date().getTime(),
857
+ })
858
+ .where(
859
+ and(
860
+ eq(schema.archetypesData.id, id),
861
+ eq(schema.archetypesData.language, lang)
862
+ )
863
+ )
864
+ .execute();
1264
865
 
1265
- parseArchetypeNameBlocks(block: string): {
1266
- english: any[];
1267
- portuguese: any[];
1268
- } {
1269
- console.log(`[Parse] Starting parsing of block: ${block}`);
1270
-
1271
- // Split the block into sections based on -EN and -PT markers
1272
- const sections = block.split(/^-EN|^-PT/m).filter((s) => s.trim());
1273
- console.log(
1274
- `[Parse] Split block into ${sections.length} sections: ${JSON.stringify(
1275
- sections,
1276
- null,
1277
- 2
1278
- )}`
1279
- );
1280
-
1281
- const english: any[] = [];
1282
- const portuguese: any[] = [];
1283
-
1284
- // Track whether we're in an EN or PT section
1285
- let currentLang: 'EN' | 'PT' | null = null;
1286
-
1287
- for (let i = 0; i < block.length; i++) {
1288
- if (block.startsWith('-EN', i)) {
1289
- currentLang = 'EN';
1290
- i += 3; // Skip past "-EN"
1291
- } else if (block.startsWith('-PT', i)) {
1292
- currentLang = 'PT';
1293
- i += 3; // Skip past "-PT"
866
+ await this.log('info', `Updated Leonardo prompt for ${id}`);
1294
867
  }
1295
868
  }
1296
869
 
1297
- // Process each section
1298
- for (let i = 0; i < sections.length; i++) {
1299
- const section = sections[i].trim();
1300
- console.log(`[Parse] Processing section: ${section}`);
1301
-
1302
- // Determine the language based on the section's position relative to -EN and -PT markers
1303
- const lang = i === 0 ? 'EN' : 'PT'; // First section after -EN, second after -PT
1304
-
1305
- // Split the section into individual entries based on numbered markers (1., 2., 3.)
1306
- const entries = section.split(/\n(?=\d+\.\s*)/).filter((e) => e.trim());
1307
- console.log(
1308
- `[Parse] Split section into ${entries.length} entries: ${JSON.stringify(
1309
- entries,
1310
- null,
1311
- 2
1312
- )}`
1313
- );
1314
-
1315
- for (const entry of entries) {
1316
- const lines = entry.split('\n').filter((l) => l.trim());
1317
- console.log(
1318
- `[Parse] Extracted ${lines.length} lines from entry: ${JSON.stringify(
1319
- lines,
1320
- null,
1321
- 2
1322
- )}`
1323
- );
870
+ await this.log('info', 'Completed generateLeonardoPrompts', {
871
+ combinationString,
872
+ gender,
873
+ language,
874
+ });
875
+ return parsedPrompts;
876
+ }
1324
877
 
1325
- if (lang === 'EN') {
1326
- const nameMatch = lines
1327
- .find((l) => l.includes('• Name:'))
1328
- ?.split('• Name:')[1]
1329
- ?.trim();
1330
- console.log(`[Parse] English nameMatch: ${nameMatch}`);
1331
-
1332
- const essenceMatch = lines
1333
- .find((l) => l.includes('• Essence:'))
1334
- ?.split('• Essence:')[1]
1335
- ?.trim();
1336
- console.log(`[Parse] English essenceMatch: ${essenceMatch}`);
1337
-
1338
- if (nameMatch && essenceMatch) {
1339
- const englishEntry = { name: nameMatch, essenceLine: essenceMatch };
1340
- english.push(englishEntry);
1341
- console.log(
1342
- `[Parse] Added English entry: ${JSON.stringify(
1343
- englishEntry,
1344
- null,
1345
- 2
1346
- )}`
1347
- );
1348
- } else {
1349
- console.warn(
1350
- `[Parse] Skipping English entry due to missing fields. nameMatch: ${nameMatch}, essenceMatch: ${essenceMatch}`
1351
- );
1352
- }
1353
- } else if (lang === 'PT') {
1354
- const mascMatch = lines
1355
- .find((l) => l.includes('• Masculino:'))
1356
- ?.split('• Masculino:')[1]
1357
- ?.trim();
1358
- console.log(`[Parse] Portuguese mascMatch: ${mascMatch}`);
1359
-
1360
- const femMatch = lines
1361
- .find((l) => l.includes('• Feminino:'))
1362
- ?.split('• Feminino:')[1]
1363
- ?.trim();
1364
- console.log(`[Parse] Portuguese femMatch: ${femMatch}`);
1365
-
1366
- const essenceMatch = lines
1367
- .find((l) => l.includes('• Essência:'))
1368
- ?.split('• Essência:')[1]
1369
- ?.trim();
1370
- console.log(`[Parse] Portuguese essenceMatch: ${essenceMatch}`);
1371
-
1372
- if (mascMatch && femMatch && essenceMatch) {
1373
- const ptEntry = {
1374
- masc: mascMatch,
1375
- fem: femMatch,
1376
- essenceLine: essenceMatch,
1377
- };
1378
- portuguese.push(ptEntry);
1379
- console.log(
1380
- `[Parse] Added Portuguese entry: ${JSON.stringify(
1381
- ptEntry,
1382
- null,
1383
- 2
1384
- )}`
1385
- );
1386
- } else {
1387
- console.warn(
1388
- `[Parse] Skipping Portuguese entry due to missing fields. mascMatch: ${mascMatch}, femMatch: ${femMatch}, essenceMatch: ${essenceMatch}`
1389
- );
1390
- }
1391
- }
878
+ private parseLeonardoPromptResponse(response: string): LeonardoPrompt[] {
879
+ this.log('debug', 'Starting parseLeonardoPromptResponse', {
880
+ responseLength: response.length,
881
+ });
882
+
883
+ const lines = response.split('\n').filter((line) => line.trim());
884
+ this.log('debug', 'Split response into lines', {
885
+ linesCount: lines.length,
886
+ lines,
887
+ });
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
+
917
+ if (!malePrompt || !femalePrompt) {
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}`);
1392
925
  }
926
+
927
+ prompts.push({ malePrompt, femalePrompt });
928
+ this.log('debug', `Parsed prompts for archetype ${currentArchetype}`, { malePrompt, femalePrompt });
1393
929
  }
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
+
939
+ this.log('info', 'Completed parseLeonardoPromptResponse', { prompts });
940
+ return prompts;
941
+ }
1394
942
 
1395
- console.log(
1396
- `[Parse] Final parsed results - English: ${JSON.stringify(
1397
- english,
1398
- null,
1399
- 2
1400
- )}, Portuguese: ${JSON.stringify(portuguese, null, 2)}`
1401
- );
1402
-
1403
- return { english, portuguese };
943
+ async fetchArchetypesFromDB(
944
+ combinationString: string,
945
+ gender: Gender,
946
+ language: string
947
+ ) {
948
+ await this.log('debug', 'Executing fetchArchetypesFromDB', {
949
+ combinationString,
950
+ gender,
951
+ language,
952
+ });
953
+ const db = this.context.drizzle();
954
+ const result = await db
955
+ .select()
956
+ .from(schema.archetypesData)
957
+ .where(
958
+ and(
959
+ eq(schema.archetypesData.combination, combinationString),
960
+ eq(schema.archetypesData.gender, gender),
961
+ eq(schema.archetypesData.language, language)
962
+ )
963
+ )
964
+ .execute();
965
+ await this.log('debug', 'fetchArchetypesFromDB result', {
966
+ resultCount: result.length,
967
+ result,
968
+ });
969
+ return result;
1404
970
  }
1405
971
  }