@zodic/shared 0.0.314 → 0.0.316

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,90 @@
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
 
10
9
  @injectable()
11
10
  export class ArchetypeService {
12
11
  constructor(@inject(AppContext) private context: AppContext) {}
13
12
 
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();
13
+ private async log(
14
+ level: 'info' | 'debug' | 'warn' | 'error',
15
+ message: string,
16
+ context: Record<string, any> = {}
17
+ ) {
18
+ const logId = `archetype-service:${Date.now()}`;
19
+ const logMessage = `[${level.toUpperCase()}] ${message}`;
258
20
 
259
- if (malePrompt && femalePrompt && nonBinaryPrompt) {
260
- parsedPrompts.push({ malePrompt, femalePrompt, nonBinaryPrompt });
261
- }
21
+ console[level](logMessage, context);
22
+ const db = this.context.drizzle();
23
+ try {
24
+ await db
25
+ .insert(schema.logs)
26
+ .values({
27
+ id: logId,
28
+ level,
29
+ message,
30
+ context: JSON.stringify(context),
31
+ createdAt: new Date().getTime(),
32
+ })
33
+ .execute();
34
+ } catch (error) {
35
+ console.error('[ERROR] Failed to persist log to database:', {
36
+ error,
37
+ logId,
38
+ message,
39
+ context,
40
+ });
262
41
  }
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
42
  }
288
43
 
44
+ // Replace generateArchetypeNames and generateArchetypeNamesBatch
289
45
  async generateArchetypeNames(
290
- combination: string,
46
+ combinationString: string,
291
47
  overrideExisting: boolean = false,
292
- indexesToGenerate?: number[]
48
+ indexesToGenerate: number[] = [1, 2, 3]
293
49
  ) {
294
- const db = this.context.drizzle();
295
- console.log(
296
- `🚀 [Single] Starting generation for combination: ${combination} | Override: ${overrideExisting}`
297
- );
50
+ await this.log('info', 'Starting generateArchetypeNames', {
51
+ combinationString,
52
+ overrideExisting,
53
+ indexesToGenerate,
54
+ });
298
55
 
299
- const [sun, ascendant, moon] = combination.split('-') as ZodiacSignSlug[];
56
+ const [sun, ascendant, moon] = combinationString.split(
57
+ '-'
58
+ ) as ZodiacSignSlug[];
300
59
  const prompt = generateArchetypePrompt([
301
- {
302
- sun,
303
- ascendant,
304
- moon,
305
- indexesToGenerate: indexesToGenerate ?? [1, 2, 3],
306
- },
60
+ { sun, ascendant, moon, indexesToGenerate },
307
61
  ]);
308
62
  const messages: ChatMessages = [{ role: 'user', content: prompt }];
309
- const response = await this.context.api().callTogether.single(messages, {});
310
63
 
64
+ await this.log('debug', 'Calling API to generate archetype names', {
65
+ messages,
66
+ });
67
+ const response = await this.context.api().callTogether.single(messages, {});
311
68
  if (!response) {
312
- console.error(`❌ [Single] No response for: ${combination}`);
313
- return;
69
+ await this.log('error', 'No response for archetype names generation', {
70
+ combinationString,
71
+ });
72
+ throw new Error(
73
+ `Failed to generate archetype names for: ${combinationString}`
74
+ );
314
75
  }
76
+ await this.log('info', 'Received archetype names response', {
77
+ responseLength: response.length,
78
+ });
315
79
 
316
80
  const { english: englishNames, portuguese: portugueseVariants } =
317
81
  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 };
82
+ await this.log('debug', 'Parsed archetype names', {
83
+ englishNames,
84
+ portugueseVariants,
414
85
  });
415
86
 
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());
87
+ const db = this.context.drizzle();
429
88
 
430
89
  async function isEnglishNameDuplicate(name: string): Promise<boolean> {
431
90
  const result = await db
@@ -439,967 +98,802 @@ export class ArchetypeService {
439
98
  )
440
99
  .limit(1)
441
100
  .execute();
442
-
443
101
  return result.length > 0;
444
102
  }
445
103
 
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}`);
104
+ for (let i = 0; i < englishNames.length; i++) {
105
+ const index = (i + 1).toString();
106
+ const entry = englishNames[i];
107
+ const ptVariant = portugueseVariants[i];
450
108
 
451
- const { english, portuguese } = this.parseArchetypeNameBlocks(block);
452
-
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}`
109
+ if (!indexesToGenerate.includes(parseInt(index))) {
110
+ await this.log(
111
+ 'debug',
112
+ `Skipping index ${index} as it is not in indexesToGenerate`,
113
+ { index, indexesToGenerate }
457
114
  );
458
- continue; // Skip this combination if parsing failed
115
+ continue;
459
116
  }
460
117
 
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;
118
+ if (await isEnglishNameDuplicate(entry.name)) {
119
+ await this.log('warn', `Duplicate name skipped: ${entry.name}`, {
120
+ combinationString,
121
+ index,
122
+ });
123
+ continue;
124
+ }
487
125
 
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
- });
126
+ for (const gender of ['male', 'female']) {
127
+ for (const lang of ['en-us', 'pt-br']) {
128
+ const id = `${combinationString}:${gender}:${index}${
129
+ lang === 'pt-br' ? ':pt' : ''
130
+ }`;
131
+ const name =
132
+ lang === 'en-us'
133
+ ? entry.name
134
+ : gender === 'female'
135
+ ? ptVariant.fem
136
+ : ptVariant.masc;
137
+ const essenceLine =
138
+ lang === 'en-us' ? entry.essenceLine : ptVariant.essenceLine;
139
+
140
+ await this.log('debug', `Saving archetype name for ${id}`, {
141
+ name,
142
+ essenceLine,
143
+ });
508
144
 
509
145
  await db
510
146
  .insert(schema.archetypesData)
511
147
  .values({
512
- id: ptId,
513
- combination,
148
+ id,
149
+ combination: combinationString,
514
150
  gender,
515
151
  archetypeIndex: index,
516
- language: 'pt-br',
517
- name: ptName,
518
- essenceLine: ptEntry.essenceLine,
152
+ language: lang,
153
+ name,
154
+ essenceLine,
519
155
  status: 'idle',
520
156
  })
521
157
  .onConflictDoUpdate({
522
158
  target: [schema.archetypesData.id],
523
159
  set: {
524
- name: ptName,
525
- essenceLine: ptEntry.essenceLine,
160
+ name,
161
+ essenceLine,
526
162
  updatedAt: new Date().getTime(),
527
163
  },
528
164
  });
529
165
 
530
- console.log(
531
- `✅ [Batch] Saved archetype ${index} (${gender}) for ${combination}`
166
+ await this.log(
167
+ 'info',
168
+ `Saved archetype ${index} (${gender}, ${lang}) for ${combinationString}`
532
169
  );
533
170
  }
534
171
  }
535
-
536
- console.log(`🏁 [Batch] Finished combination: ${combination}`);
537
172
  }
538
173
 
539
- console.log(
540
- `🎉 [Batch] Completed processing for all ${entries.length} combinations`
541
- );
174
+ await this.log('info', 'Completed generateArchetypeNames', {
175
+ combinationString,
176
+ });
542
177
  }
543
178
 
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
- }));
179
+ private parseArchetypeNameBlocks(response: string): {
180
+ english: Array<{ name: string; essenceLine: string }>;
181
+ portuguese: Array<{ masc: string; fem: string; essenceLine: string }>;
182
+ } {
183
+ this.log('debug', 'Starting parseArchetypeNameBlocks', {
184
+ responseLength: response.length,
185
+ });
564
186
 
565
- console.log(`🔍 Found ${typedResult.length} incomplete combinations`);
566
- return typedResult;
567
- }
187
+ const blocks = response.split(/\n\d+\.\n/).filter(Boolean);
188
+ this.log('debug', 'Split response into blocks', {
189
+ blocksCount: blocks.length,
190
+ });
568
191
 
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();
192
+ const english: Array<{ name: string; essenceLine: string }> = [];
193
+ const portuguese: Array<{
194
+ masc: string;
195
+ fem: string;
196
+ essenceLine: string;
197
+ }> = [];
588
198
 
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
- }
199
+ for (const block of blocks) {
200
+ const nameMatch = block.match(/- Name: (.+)/);
201
+ const essenceLineMatch = block.match(/- Essence Line: (.+)/);
202
+ const ptMascMatch = block.match(/- Portuguese \(Masc\): (.+)/);
203
+ const ptFemMatch = block.match(/- Portuguese \(Fem\): (.+)/);
204
+ const ptEssenceLineMatch = block.match(/- Portuguese Essence Line: (.+)/);
595
205
 
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
- );
206
+ if (
207
+ nameMatch &&
208
+ essenceLineMatch &&
209
+ ptMascMatch &&
210
+ ptFemMatch &&
211
+ ptEssenceLineMatch
212
+ ) {
213
+ english.push({
214
+ name: nameMatch[1].trim(),
215
+ essenceLine: essenceLineMatch[1].trim(),
216
+ });
217
+ portuguese.push({
218
+ masc: ptMascMatch[1].trim(),
219
+ fem: ptFemMatch[1].trim(),
220
+ essenceLine: ptEssenceLineMatch[1].trim(),
221
+ });
222
+ } else {
223
+ this.log('warn', 'Malformed archetype name block', { block });
607
224
  }
608
225
  }
609
226
 
610
- console.log(`✅ Done regenerating missing archetype names`);
227
+ this.log('info', 'Completed parseArchetypeNameBlocks', {
228
+ english,
229
+ portuguese,
230
+ });
231
+ return { english, portuguese };
611
232
  }
612
233
 
613
- async regenerateMissingArchetypeNamesBatch(compositions: Composition[]) {
614
- if (compositions.length === 0) {
615
- console.log('✅ [Batch] No combinations provided for regeneration');
616
- return;
617
- }
234
+ async generateDescriptionsAndVirtues(
235
+ combinationString: string,
236
+ gender: Gender,
237
+ language: string
238
+ ) {
239
+ await this.log('info', 'Starting generateDescriptionsAndVirtues', {
240
+ combinationString,
241
+ gender,
242
+ language,
243
+ });
618
244
 
619
- console.log(
620
- `🔁 [Batch] Starting regeneration for ${compositions.length} combinations`
621
- );
245
+ const db = this.context.drizzle();
246
+ const archetypes = await db
247
+ .select()
248
+ .from(schema.archetypesData)
249
+ .where(
250
+ and(
251
+ eq(schema.archetypesData.combination, combinationString),
252
+ eq(schema.archetypesData.gender, gender),
253
+ eq(schema.archetypesData.language, 'en-us')
254
+ )
255
+ )
256
+ .execute();
622
257
 
623
- const prompt = generateArchetypePrompt(compositions);
624
- const messages: ChatMessages = [{ role: 'user', content: prompt }];
625
- const response = await this.context.api().callTogether.single(messages, {});
258
+ await this.log('info', 'Fetched archetypes for description generation', {
259
+ archetypesCount: archetypes.length,
260
+ names: archetypes.map((a) => a.name),
261
+ });
626
262
 
627
- if (!response) {
628
- console.error(`❌ [Batch] No response from model during regeneration`);
629
- return;
263
+ const descVirtueMessages = this.context
264
+ .buildLLMMessages()
265
+ .generateCosmicMirrorDescriptionAndVirtues({
266
+ combination: combinationString,
267
+ names: [archetypes[0].name, archetypes[1].name, archetypes[2].name],
268
+ essenceLines: [
269
+ archetypes[0].essenceLine,
270
+ archetypes[1].essenceLine,
271
+ archetypes[2].essenceLine,
272
+ ],
273
+ });
274
+
275
+ await this.log('debug', 'Calling API for descriptions and virtues', {
276
+ messages: descVirtueMessages,
277
+ });
278
+ const descVirtueResponse = await this.context
279
+ .api()
280
+ .callTogether.single(descVirtueMessages, {});
281
+ if (!descVirtueResponse) {
282
+ await this.log('error', 'No response for descriptions and virtues', {
283
+ combinationString,
284
+ });
285
+ throw new Error(
286
+ `Failed to generate descriptions and virtues for: ${combinationString}`
287
+ );
630
288
  }
289
+ await this.log('info', 'Received descriptions and virtues response', {
290
+ responseLength: descVirtueResponse.length,
291
+ });
631
292
 
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());
641
-
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];
647
-
648
- console.log(`🔄 [Batch] Processing: ${combination}`);
649
-
650
- const { english, portuguese } = this.parseArchetypeNameBlocks(block);
651
-
652
- if (
653
- english.length !== indexes.length ||
654
- portuguese.length !== indexes.length
655
- ) {
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;
293
+ const parsedDescVirtues =
294
+ this.parseDescriptionAndVirtuesResponse(descVirtueResponse);
295
+ await this.log('info', 'Parsed descriptions and virtues', {
296
+ parsedDescVirtues,
297
+ });
696
298
 
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
- });
299
+ for (let i = 0; i < 3; i++) {
300
+ 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
+ });
718
316
 
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
- });
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
+ )
329
+ )
330
+ .execute();
740
331
 
741
- console.log(
742
- `✅ [Batch] Saved archetype ${index} (${gender}) for ${combination}`
743
- );
744
- }
332
+ await this.log('info', `Updated description and virtues for ${id}`);
745
333
  }
746
-
747
- console.log(`🏁 [Batch] Finished combination: ${combination}`);
748
334
  }
749
335
 
750
- console.log(
751
- `🎉 [Batch] Completed regenerating ${compositions.length} combinations`
752
- );
336
+ await this.log('info', 'Completed generateDescriptionsAndVirtues', {
337
+ combinationString,
338
+ gender,
339
+ language,
340
+ });
341
+ return parsedDescVirtues;
753
342
  }
754
343
 
755
- async regenerateDuplicateArchetypeNamesBatch(
756
- entriesByCombination: Record<string, any[]>
757
- ) {
758
- 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}`
769
- );
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)
775
- .where(
776
- and(
777
- eq(schema.archetypesData.combination, combination),
778
- eq(schema.archetypesData.language, 'en-us')
779
- )
780
- )
781
- .execute();
344
+ private parseDescriptionAndVirtuesResponse(response: string) {
345
+ this.log('debug', 'Starting parseDescriptionAndVirtuesResponse', {
346
+ responseLength: response.length,
347
+ });
782
348
 
783
- let existingNames = existingNamesResult.map((r) => r.name);
784
- console.log(
785
- `[RegenerateDuplicatesBatch] Existing names for ${combination}: ${existingNames.join(
786
- ', '
787
- )}`
788
- );
349
+ const entries = response
350
+ .split(/---/)
351
+ .map((block) => block.trim())
352
+ .filter((block) => block.length > 0);
789
353
 
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
- );
354
+ this.log('debug', 'Split response into entries', {
355
+ entriesCount: entries.length,
356
+ });
796
357
 
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})`
358
+ const result = entries.map((entry, entryIndex) => {
359
+ this.log('debug', `Processing entry ${entryIndex + 1}`, { entry });
360
+
361
+ const lines = entry.split('\n').filter((line) => line.trim());
362
+ this.log('debug', `Split entry ${entryIndex + 1} into lines`, {
363
+ linesCount: lines.length,
364
+ lines,
365
+ });
366
+
367
+ let descriptionEN = '';
368
+ let descriptionPTM = '';
369
+ let descriptionPTF = '';
370
+ let virtues: string[] = [];
371
+ let currentField = '';
372
+
373
+ for (const line of lines) {
374
+ if (line.startsWith('• Description EN:')) {
375
+ currentField = 'descriptionEN';
376
+ descriptionEN = line.split('• Description EN:')[1]?.trim() || '';
377
+ this.log(
378
+ 'debug',
379
+ `Extracted descriptionEN for entry ${entryIndex + 1}`,
380
+ { descriptionEN }
810
381
  );
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,
382
+ } else if (line.startsWith('• Description PT-M:')) {
383
+ currentField = 'descriptionPTM';
384
+ descriptionPTM = line.split('• Description PT-M:')[1]?.trim() || '';
385
+ this.log(
386
+ 'debug',
387
+ `Extracted descriptionPTM for entry ${entryIndex + 1}`,
388
+ { descriptionPTM }
389
+ );
390
+ } else if (line.startsWith('• Description PT-F:')) {
391
+ currentField = 'descriptionPTF';
392
+ descriptionPTF = line.split('• Description PT-F:')[1]?.trim() || '';
393
+ this.log(
394
+ 'debug',
395
+ `Extracted descriptionPTF for entry ${entryIndex + 1}`,
396
+ { descriptionPTF }
397
+ );
398
+ } 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,
817
408
  });
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})`
409
+ } else if (currentField && currentField !== 'virtues') {
410
+ if (currentField === 'descriptionEN') {
411
+ descriptionEN += ' ' + line.trim();
412
+ this.log(
413
+ 'debug',
414
+ `Appended to descriptionEN for entry ${entryIndex + 1}`,
415
+ { descriptionEN }
828
416
  );
829
- continue;
830
417
  }
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
- )}`
418
+ if (currentField === 'descriptionPTM') {
419
+ descriptionPTM += ' ' + line.trim();
420
+ this.log(
421
+ 'debug',
422
+ `Appended to descriptionPTM for entry ${entryIndex + 1}`,
423
+ { descriptionPTM }
841
424
  );
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
425
  }
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.`
426
+ if (currentField === 'descriptionPTF') {
427
+ descriptionPTF += ' ' + line.trim();
428
+ this.log(
429
+ 'debug',
430
+ `Appended to descriptionPTF for entry ${entryIndex + 1}`,
431
+ { descriptionPTF }
880
432
  );
881
- existingNames.push(newName.name_en);
882
- newName = null;
883
- continue;
884
433
  }
885
-
886
- // If we reach here, the name is unique
887
- break;
888
434
  }
435
+ }
889
436
 
890
- if (!newName) {
891
- console.error(
892
- `[RegenerateDuplicatesBatch] Failed to generate a unique name for ${combination} (archetypeIndex: ${archetypeIndex}) after ${maxAttempts} attempts`
893
- );
894
- continue;
895
- }
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
- );
937
- }
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
+ );
938
456
  }
939
- }
940
457
 
941
- console.log(`✅ [RegenerateDuplicatesBatch] Finished processing batch`);
458
+ return { descriptionEN, descriptionPTM, descriptionPTF, virtues };
459
+ });
460
+
461
+ this.log('info', 'Completed parseDescriptionAndVirtuesResponse', {
462
+ result,
463
+ });
464
+ return result;
942
465
  }
943
466
 
944
- async recycleArchetypeNameDumpsBatch(
945
- compositions: Composition[]
946
- ): Promise<void> {
947
- console.log('🔁 [Recycle] Starting recycling of archetype name dumps');
467
+ async generateContent(
468
+ combinationString: string,
469
+ gender: Gender,
470
+ language: string,
471
+ descriptions: Array<{ descriptionEN: string }>
472
+ ) {
473
+ await this.log('info', 'Starting generateContent', {
474
+ combinationString,
475
+ gender,
476
+ language,
477
+ });
948
478
 
949
- if (compositions.length === 0) {
950
- console.log('✅ [Recycle] No compositions provided for recycling');
951
- return;
952
- }
479
+ const db = this.context.drizzle();
480
+ const archetypes = await db
481
+ .select()
482
+ .from(schema.archetypesData)
483
+ .where(
484
+ and(
485
+ eq(schema.archetypesData.combination, combinationString),
486
+ eq(schema.archetypesData.gender, gender),
487
+ eq(schema.archetypesData.language, 'en-us')
488
+ )
489
+ )
490
+ .execute();
953
491
 
954
- console.log(
955
- `📦 [Recycle] Total compositions to recycle: ${compositions.length}`
956
- );
492
+ await this.log('info', 'Fetched archetypes for content generation', {
493
+ archetypesCount: archetypes.length,
494
+ names: archetypes.map((a) => a.name),
495
+ });
957
496
 
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
- )}]`
968
- );
497
+ const contentResults = [];
498
+ for (let i = 0; i < 3; i++) {
499
+ await this.log('debug', `Generating content for archetype ${i + 1}`, {
500
+ name: archetypes[i].name,
501
+ });
502
+ const contentMessages = this.context
503
+ .buildLLMMessages()
504
+ .generateCosmicMirrorArchetypeContent({
505
+ combination: combinationString,
506
+ name: archetypes[i].name,
507
+ description: descriptions[i].descriptionEN,
508
+ });
969
509
 
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();
510
+ await this.log('debug', `Calling API for content of archetype ${i + 1}`, {
511
+ messages: contentMessages,
512
+ });
513
+ const contentResponse = await this.context
514
+ .api()
515
+ .callTogether.single(contentMessages, {});
516
+ if (!contentResponse) {
517
+ await this.log(
518
+ 'error',
519
+ `No response for content of archetype ${i + 1}`,
520
+ { combinationString, archetype: archetypes[i].name }
521
+ );
522
+ throw new Error(
523
+ `Failed to generate content for archetype ${
524
+ i + 1
525
+ } of: ${combinationString}`
526
+ );
527
+ }
528
+ await this.log(
529
+ 'info',
530
+ `Received content response for archetype ${i + 1}`,
531
+ { responseLength: contentResponse.length }
532
+ );
979
533
 
980
- console.log(
981
- `[Recycle] Retrieved ${
982
- dumpEntries.length
983
- } dump entries for ${combination}: ${JSON.stringify(
984
- dumpEntries,
985
- null,
986
- 2
987
- )}`
534
+ await this.log(
535
+ 'debug',
536
+ `Parsing content response for archetype ${i + 1}`
988
537
  );
538
+ const parsedContent = this.parseContentResponse(contentResponse);
539
+ contentResults.push(parsedContent);
540
+ await this.log('info', `Parsed content for archetype ${i + 1}`, {
541
+ parsedContent,
542
+ });
543
+
544
+ const index = (i + 1).toString();
545
+ for (const lang of ['en-us', 'pt-br']) {
546
+ const contentLangIndex =
547
+ lang === 'en-us' ? 0 : gender === 'male' ? 1 : 2;
548
+ const content = [
549
+ {
550
+ section: 'voiceOfTheSoul',
551
+ text: parsedContent[contentLangIndex].voiceOfTheSoul,
552
+ },
553
+ {
554
+ section: 'giftsYouBear',
555
+ text: parsedContent[contentLangIndex].giftsYouBear,
556
+ },
557
+ {
558
+ section: 'shadowsYouFace',
559
+ text: parsedContent[contentLangIndex].shadowsYouFace,
560
+ },
561
+ {
562
+ section: 'rhythmOfYourDays',
563
+ text: parsedContent[contentLangIndex].rhythmOfYourDays,
564
+ },
565
+ {
566
+ section: 'tiesThatBind',
567
+ text: parsedContent[contentLangIndex].tiesThatBind,
568
+ },
569
+ {
570
+ section: 'lightWithin',
571
+ text: parsedContent[contentLangIndex].lightWithin,
572
+ },
573
+ ];
574
+
575
+ const id = `${combinationString}:${gender}:${index}${
576
+ lang === 'pt-br' ? ':pt' : ''
577
+ }`;
578
+ await this.log('debug', `Updating content for ${id}`, { content });
579
+
580
+ await db
581
+ .update(schema.archetypesData)
582
+ .set({
583
+ content: JSON.stringify(content),
584
+ updatedAt: new Date().getTime(),
585
+ })
586
+ .where(
587
+ and(
588
+ eq(schema.archetypesData.id, id),
589
+ eq(schema.archetypesData.language, lang)
590
+ )
591
+ )
592
+ .execute();
989
593
 
990
- if (dumpEntries.length === 0) {
991
- console.log(
992
- `⚠️ [Recycle] No dump entries found for combination: ${combination}`
993
- );
994
- continue;
594
+ await this.log('info', `Updated content for ${id}`);
995
595
  }
596
+ }
996
597
 
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
- }
598
+ await this.log('info', 'Completed generateContent', {
599
+ combinationString,
600
+ gender,
601
+ language,
602
+ });
603
+ return contentResults;
604
+ }
1014
605
 
1015
- if (!rawText) {
1016
- console.log(
1017
- `⚠️ [Recycle] No dump entry contains index ${index} for combination: ${combination}`
1018
- );
1019
- continue;
1020
- }
606
+ private parseContentResponse(response: string) {
607
+ this.log('debug', 'Starting parseContentResponse', {
608
+ responseLength: response.length,
609
+ });
1021
610
 
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
- );
611
+ const sections = response
612
+ .split(/---/)
613
+ .map((block) => block.trim())
614
+ .filter((block) => block.length > 0);
615
+ this.log('debug', 'Split response into sections', {
616
+ sectionsCount: sections.length,
617
+ });
1032
618
 
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
- );
619
+ const result = sections.map((section, sectionIndex) => {
620
+ this.log('debug', `Processing section ${sectionIndex + 1}`, { section });
1040
621
 
1041
- console.log(
1042
- `[Recycle] Extracted sections for index ${index} - enSectionMatch: ${JSON.stringify(
1043
- enSectionMatch
1044
- )}, ptSectionMatch: ${JSON.stringify(ptSectionMatch)}`
1045
- );
622
+ const lines = section.split('\n').filter((line) => line.trim());
623
+ this.log('debug', `Split section ${sectionIndex + 1} into lines`, {
624
+ linesCount: lines.length,
625
+ lines,
626
+ });
627
+
628
+ const content: Record<string, string> = {};
629
+ let currentSection = '';
630
+ let currentText: string[] = [];
1046
631
 
632
+ for (const line of lines) {
1047
633
  if (
1048
- !enSectionMatch ||
1049
- !ptSectionMatch ||
1050
- !enSectionMatch[0] ||
1051
- !ptSectionMatch[0]
634
+ line.startsWith('EN:') ||
635
+ line.startsWith('PT-M:') ||
636
+ line.startsWith('PT-F:')
1052
637
  ) {
1053
- console.log(
1054
- `⚠️ [Recycle] Could not extract index ${index} sections for combination: ${combination}. enSectionMatch: ${JSON.stringify(
1055
- enSectionMatch
1056
- )}, ptSectionMatch: ${JSON.stringify(ptSectionMatch)}`
638
+ this.log(
639
+ 'debug',
640
+ `Skipping language header in section ${sectionIndex + 1}`,
641
+ { line }
1057
642
  );
1058
643
  continue;
1059
644
  }
1060
645
 
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`
646
+ if (line.startsWith('#')) {
647
+ if (currentSection && currentText.length > 0) {
648
+ const key = currentSection
649
+ .toLowerCase()
650
+ .replace(/\s+/g, '')
651
+ .replace(/([A-Z])/g, (match) => match.toLowerCase());
652
+ content[key] = currentText.join(' ').trim();
653
+ this.log(
654
+ 'debug',
655
+ `Extracted section ${currentSection} in section ${
656
+ sectionIndex + 1
657
+ }`,
658
+ { key, text: content[key] }
1098
659
  );
1099
660
  }
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)}`
661
+ currentSection = line.replace('# ', '');
662
+ currentText = [];
663
+ this.log(
664
+ 'debug',
665
+ `Starting new section ${currentSection} in section ${
666
+ sectionIndex + 1
667
+ }`
1119
668
  );
1120
- continue;
669
+ } else {
670
+ currentText.push(line);
1121
671
  }
672
+ }
1122
673
 
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
- });
674
+ if (currentSection && currentText.length > 0) {
675
+ const key = currentSection
676
+ .toLowerCase()
677
+ .replace(/\s+/g, '')
678
+ .replace(/([A-Z])/g, (match) => match.toLowerCase());
679
+ content[key] = currentText.join(' ').trim();
680
+ this.log(
681
+ 'debug',
682
+ `Extracted final section ${currentSection} in section ${
683
+ sectionIndex + 1
684
+ }`,
685
+ { key, text: content[key] }
686
+ );
687
+ }
1178
688
 
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
- );
689
+ const parsedContent = {
690
+ voiceOfTheSoul:
691
+ content['thevoiceofthesoul'] || content['avozdaalma'] || '',
692
+ giftsYouBear:
693
+ content['thegiftsyoubear'] || content['osdonsquevocêcarrega'] || '',
694
+ shadowsYouFace:
695
+ content['theshadowsyouface'] || content['assombrasqueenfrenta'] || '',
696
+ rhythmOfYourDays:
697
+ content['therhythmofyourdays'] || content['oritmodosseusdias'] || '',
698
+ tiesThatBind:
699
+ content['thetiesthatbind'] || content['oslaçosqueteconectam'] || '',
700
+ lightWithin: content['thelightwithin'] || content['aluzinterior'] || '',
701
+ };
702
+
703
+ if (Object.values(parsedContent).some((value) => !value)) {
704
+ this.log(
705
+ 'warn',
706
+ `Malformed content response for section ${sectionIndex + 1}`,
707
+ {
708
+ parsedContent,
709
+ response: section,
1194
710
  }
1195
-
1196
- console.log(
1197
- `✅ [Recycle] Saved archetype ${index} (${gender}) for ${combination}`
1198
- );
1199
- }
711
+ );
1200
712
  }
1201
713
 
1202
- console.log(`🏁 [Recycle] Finished combination: ${combination}`);
1203
- }
714
+ return parsedContent;
715
+ });
1204
716
 
1205
- console.log(
1206
- `🎉 [Recycle] Completed recycling for ${compositions.length} combinations`
1207
- );
717
+ this.log('info', 'Completed parseContentResponse', { result });
718
+ return result;
1208
719
  }
1209
720
 
1210
- async fetchMissingArchetypeCompositions(): Promise<Composition[]> {
721
+ async generateLeonardoPrompts(
722
+ combinationString: string,
723
+ gender: Gender,
724
+ language: string,
725
+ descriptions: Array<{ descriptionEN: string }>
726
+ ) {
727
+ await this.log('info', 'Starting generateLeonardoPrompts', {
728
+ combinationString,
729
+ gender,
730
+ language,
731
+ });
732
+
1211
733
  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();
734
+ const archetypes = await db
735
+ .select()
736
+ .from(schema.archetypesData)
737
+ .where(
738
+ and(
739
+ eq(schema.archetypesData.combination, combinationString),
740
+ eq(schema.archetypesData.gender, gender),
741
+ eq(schema.archetypesData.language, 'en-us')
742
+ )
743
+ )
744
+ .execute();
1233
745
 
1234
- const indexesToGenerate: number[] = [];
746
+ await this.log('info', 'Fetched archetypes for Leonardo prompts', {
747
+ archetypesCount: archetypes.length,
748
+ names: archetypes.map((a) => a.name),
749
+ });
1235
750
 
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
- }
751
+ const promptMessages = this.context
752
+ .buildLLMMessages()
753
+ .generateCosmicMirrorArchetypeLeonardoPrompts(
754
+ archetypes.map((arc, idx) => ({
755
+ name: arc.name,
756
+ description: descriptions[idx].descriptionEN,
757
+ }))
758
+ );
1242
759
 
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
- }
760
+ await this.log('debug', 'Calling API for Leonardo prompts', {
761
+ messages: promptMessages,
762
+ });
763
+ const promptResponse = await this.context
764
+ .api()
765
+ .callTogether.single(promptMessages, {});
766
+ if (!promptResponse) {
767
+ await this.log('error', 'No response for Leonardo prompts', {
768
+ combinationString,
769
+ });
770
+ throw new Error(
771
+ `Failed to generate Leonardo prompts for: ${combinationString}`
772
+ );
1256
773
  }
774
+ await this.log('info', 'Received Leonardo prompts response', {
775
+ responseLength: promptResponse.length,
776
+ });
1257
777
 
1258
- console.log(
1259
- `📦 [Fetch] Total compositions to regenerate (limited to 400): ${compositions.length}`
1260
- );
778
+ const parsedPrompts = this.parseLeonardoPromptResponse(promptResponse);
779
+ await this.log('info', 'Parsed Leonardo prompts', { parsedPrompts });
780
+
781
+ for (let i = 0; i < 3; i++) {
782
+ const index = (i + 1).toString();
783
+ const leonardoPrompt =
784
+ gender === 'male'
785
+ ? parsedPrompts[i].malePrompt
786
+ : parsedPrompts[i].femalePrompt;
787
+
788
+ for (const lang of ['en-us', 'pt-br']) {
789
+ const id = `${combinationString}:${gender}:${index}${
790
+ lang === 'pt-br' ? ':pt' : ''
791
+ }`;
792
+ await this.log('debug', `Updating Leonardo prompt for ${id}`, {
793
+ leonardoPrompt,
794
+ });
1261
795
 
1262
- return compositions;
1263
- }
796
+ await db
797
+ .update(schema.archetypesData)
798
+ .set({
799
+ leonardoPrompt,
800
+ updatedAt: new Date().getTime(),
801
+ })
802
+ .where(
803
+ and(
804
+ eq(schema.archetypesData.id, id),
805
+ eq(schema.archetypesData.language, lang)
806
+ )
807
+ )
808
+ .execute();
1264
809
 
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"
810
+ await this.log('info', `Updated Leonardo prompt for ${id}`);
1294
811
  }
1295
812
  }
1296
813
 
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
- );
814
+ await this.log('info', 'Completed generateLeonardoPrompts', {
815
+ combinationString,
816
+ gender,
817
+ language,
818
+ });
819
+ return parsedPrompts;
820
+ }
1314
821
 
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
- );
822
+ private parseLeonardoPromptResponse(response: string) {
823
+ this.log('debug', 'Starting parseLeonardoPromptResponse', {
824
+ responseLength: response.length,
825
+ });
1324
826
 
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
- );
827
+ const lines = response.split('\n').filter((line) => line.trim());
828
+ this.log('debug', 'Split response into lines', {
829
+ linesCount: lines.length,
830
+ lines,
831
+ });
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
+
853
+ 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,
1390
862
  }
1391
- }
863
+ );
1392
864
  }
1393
865
  }
1394
866
 
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
- );
867
+ this.log('info', 'Completed parseLeonardoPromptResponse', { prompts });
868
+ return prompts;
869
+ }
1402
870
 
1403
- return { english, portuguese };
871
+ async fetchArchetypesFromDB(
872
+ combinationString: string,
873
+ gender: Gender,
874
+ language: string
875
+ ) {
876
+ await this.log('debug', 'Executing fetchArchetypesFromDB', {
877
+ combinationString,
878
+ gender,
879
+ language,
880
+ });
881
+ const db = this.context.drizzle();
882
+ const result = await db
883
+ .select()
884
+ .from(schema.archetypesData)
885
+ .where(
886
+ and(
887
+ eq(schema.archetypesData.combination, combinationString),
888
+ eq(schema.archetypesData.gender, gender),
889
+ eq(schema.archetypesData.language, language)
890
+ )
891
+ )
892
+ .execute();
893
+ await this.log('debug', 'fetchArchetypesFromDB result', {
894
+ resultCount: result.length,
895
+ result,
896
+ });
897
+ return result;
1404
898
  }
1405
899
  }