@zodic/shared 0.0.226 → 0.0.228

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.
@@ -2,7 +2,7 @@ import {
2
2
  KVNamespaceListKey,
3
3
  KVNamespaceListResult,
4
4
  } from '@cloudflare/workers-types';
5
- import { and, eq, sql } from 'drizzle-orm';
5
+ import { and, eq, inArray, sql } from 'drizzle-orm';
6
6
  import { drizzle } from 'drizzle-orm/d1';
7
7
  import { inject, injectable } from 'inversify';
8
8
  import 'reflect-metadata';
@@ -377,22 +377,52 @@ export class ConceptService {
377
377
  async generateContent(
378
378
  conceptSlug: Concept,
379
379
  combinationString: string,
380
- override: boolean = false // ✅ New optional override parameter
380
+ override: boolean = false // ✅ Optional override flag
381
381
  ): Promise<void> {
382
382
  console.log(
383
383
  `🚀 Generating content for concept: ${conceptSlug}, combination: ${combinationString}, override: ${override}`
384
384
  );
385
385
 
386
- const kvStore = this.context.kvConceptsStore();
387
- const kvFailuresStore = this.context.kvConceptFailuresStore(); // 🔴 Replace with actual KV store
388
- const kvKeyEN = buildConceptKVKey('en-us', conceptSlug, combinationString);
389
- const kvKeyPT = buildConceptKVKey('pt-br', conceptSlug, combinationString);
390
- const failureKey = `failures:content:${conceptSlug}:${combinationString}`;
386
+ const db = drizzle(this.context.env.DB); // ✅ Initialize Drizzle with Cloudflare D1
391
387
 
392
- // ✅ Ensure basic info is available before generating content
393
- const conceptEN = await this.getKVConcept(kvKeyEN);
394
- const conceptPT = await this.getKVConcept(kvKeyPT);
388
+ // ✅ Fetch concept data from D1
389
+ console.log(
390
+ `📡 Fetching concept data for ${conceptSlug}:${combinationString}`
391
+ );
392
+
393
+ const conceptEntries = await db
394
+ .select({
395
+ id: conceptsData.id,
396
+ language: conceptsData.language,
397
+ name: conceptsData.name,
398
+ description: conceptsData.description,
399
+ poem: conceptsData.poem,
400
+ content: conceptsData.content,
401
+ })
402
+ .from(conceptsData)
403
+ .where(
404
+ and(
405
+ eq(conceptsData.conceptSlug, conceptSlug),
406
+ eq(conceptsData.combination, combinationString),
407
+ inArray(conceptsData.language, ['en-us', 'pt-br']) // ✅ Fetch both languages
408
+ )
409
+ )
410
+ .execute();
411
+
412
+ if (conceptEntries.length !== 2) {
413
+ throw new Error(
414
+ `❌ Missing concept data for ${conceptSlug}:${combinationString}. Ensure basic info is populated first.`
415
+ );
416
+ }
417
+
418
+ const conceptEN = conceptEntries.find((c) => c.language === 'en-us');
419
+ const conceptPT = conceptEntries.find((c) => c.language === 'pt-br');
395
420
 
421
+ if (!conceptEN || !conceptPT) {
422
+ throw new Error(`❌ Could not find both EN and PT versions.`);
423
+ }
424
+
425
+ // ✅ Ensure basic info is available before generating content
396
426
  if (
397
427
  !conceptEN.name ||
398
428
  !conceptEN.description ||
@@ -408,8 +438,8 @@ export class ConceptService {
408
438
 
409
439
  if (
410
440
  !override &&
411
- conceptEN.content?.length > 0 &&
412
- conceptPT.content?.length > 0
441
+ Array.isArray(conceptEN?.content) && conceptEN.content.length > 0 &&
442
+ Array.isArray(conceptPT?.content) && conceptPT.content.length > 0
413
443
  ) {
414
444
  console.log(`⚡ Content already exists for ${conceptSlug}, skipping.`);
415
445
  return; // ✅ Skip regeneration
@@ -432,7 +462,6 @@ export class ConceptService {
432
462
  combination: combinationString,
433
463
  name: conceptEN.name, // Use English name since both languages match in meaning
434
464
  description: conceptEN.description,
435
- poem: conceptEN.poem,
436
465
  });
437
466
 
438
467
  // ✅ Call ChatGPT API
@@ -447,18 +476,39 @@ export class ConceptService {
447
476
 
448
477
  // ✅ Parse structured content for both languages
449
478
  const { structuredContentEN, structuredContentPT } =
450
- this.parseStructuredContent(response);
479
+ this.parseStructuredContent(conceptSlug, response);
451
480
 
452
- // 🌍 Store English content
453
- Object.assign(conceptEN, { content: structuredContentEN });
454
- await kvStore.put(kvKeyEN, JSON.stringify(conceptEN));
481
+ // Store content in D1
482
+ console.log(
483
+ `💾 Storing structured content for ${conceptSlug}:${combinationString} in D1...`
484
+ );
455
485
 
456
- // 🇧🇷 Store Portuguese content
457
- Object.assign(conceptPT, { content: structuredContentPT });
458
- await kvStore.put(kvKeyPT, JSON.stringify(conceptPT));
486
+ await db.transaction(async (tx) => {
487
+ await tx
488
+ .update(conceptsData)
489
+ .set({ content: JSON.stringify(structuredContentEN) })
490
+ .where(
491
+ and(
492
+ eq(conceptsData.id, conceptEN.id),
493
+ eq(conceptsData.language, 'en-us')
494
+ )
495
+ )
496
+ .execute();
497
+
498
+ await tx
499
+ .update(conceptsData)
500
+ .set({ content: JSON.stringify(structuredContentPT) })
501
+ .where(
502
+ and(
503
+ eq(conceptsData.id, conceptPT.id),
504
+ eq(conceptsData.language, 'pt-br')
505
+ )
506
+ )
507
+ .execute();
508
+ });
459
509
 
460
510
  console.log(
461
- `✅ Structured content stored for ${conceptSlug}, combination: ${combinationString}, in both languages.`
511
+ `✅ Structured content stored successfully for ${conceptSlug}:${combinationString}.`
462
512
  );
463
513
  return; // ✅ Exit loop if successful
464
514
  } catch (error) {
@@ -468,7 +518,9 @@ export class ConceptService {
468
518
  );
469
519
 
470
520
  // ✅ Store failure details in KV for manual review
471
- await kvFailuresStore.put(
521
+ const failureKey = `failures:content:${conceptSlug}:${combinationString}`;
522
+
523
+ await this.context.kvConceptFailuresStore().put(
472
524
  failureKey,
473
525
  JSON.stringify({
474
526
  error: (error as Error).message,
@@ -495,11 +547,16 @@ export class ConceptService {
495
547
  }
496
548
  }
497
549
 
498
- private parseStructuredContent(response: string): {
550
+ private parseStructuredContent(
551
+ conceptSlug: Concept,
552
+ response: string
553
+ ): {
499
554
  structuredContentEN: StructuredConceptContent;
500
555
  structuredContentPT: StructuredConceptContent;
501
556
  } {
502
- console.log('📌 [START] Parsing structured content from ChatGPT response.');
557
+ console.log(
558
+ `📌 [START] Parsing structured content for ${conceptSlug} from ChatGPT response.`
559
+ );
503
560
 
504
561
  // ✅ Step 1: Clean artifacts before processing
505
562
  let cleanedResponse = response
@@ -513,23 +570,114 @@ export class ConceptService {
513
570
  cleanedResponse.slice(0, 500) + '...'
514
571
  );
515
572
 
516
- const sectionsEN = [
517
- 'Core Identity',
518
- 'Strengths and Challenges',
519
- 'Path to Fulfillment',
520
- 'Emotional Depth',
521
- 'Vision and Aspirations',
522
- ];
523
-
524
- const sectionsPT = [
525
- 'Identidade Essencial',
526
- 'Forças e Desafios',
527
- 'Caminho para a Plenitude',
528
- 'Profundidade Emocional',
529
- 'Visão e Aspirações',
530
- ];
531
-
532
- // ✅ Step 2: Extract English and Portuguese content separately
573
+ // Step 2: Define Section Titles Per Concept
574
+ const conceptSections: Record<string, { en: string[]; pt: string[] }> = {
575
+ crown: {
576
+ en: [
577
+ 'Core Identity',
578
+ 'Strengths and Challenges',
579
+ 'Path to Fulfillment',
580
+ 'Emotional Depth',
581
+ 'Vision and Aspirations',
582
+ ],
583
+ pt: [
584
+ 'Identidade Essencial',
585
+ 'Forças e Desafios',
586
+ 'Caminho para a Plenitude',
587
+ 'Profundidade Emocional',
588
+ 'Visão e Aspirações',
589
+ ],
590
+ },
591
+ scepter: {
592
+ en: [
593
+ 'The Power of Expression',
594
+ 'The Dance of Action and Reaction',
595
+ 'Navigating Challenges',
596
+ 'The Mirror of Relationships',
597
+ 'Impact on the World',
598
+ ],
599
+ pt: [
600
+ 'O Poder da Expressão',
601
+ 'A Dança da Ação e Reação',
602
+ 'Navegando Desafios',
603
+ 'O Espelho dos Relacionamentos',
604
+ 'Impacto no Mundo',
605
+ ],
606
+ },
607
+ amulet: {
608
+ en: [
609
+ 'The Shield of Protection',
610
+ 'The Strength Within',
611
+ 'Nurturing Growth',
612
+ 'The Cycle of Renewal',
613
+ 'The Anchor of Wisdom',
614
+ ],
615
+ pt: [
616
+ 'O Escudo da Proteção',
617
+ 'A Força Interior',
618
+ 'Cultivando o Crescimento',
619
+ 'O Ciclo da Renovação',
620
+ 'A Âncora da Sabedoria',
621
+ ],
622
+ },
623
+ ring: {
624
+ en: [
625
+ 'Magnetic Pull',
626
+ 'Hidden Desires',
627
+ 'The Fated Dance',
628
+ 'Unveiling the Shadows',
629
+ 'Your Love Power',
630
+ ],
631
+ pt: [
632
+ 'Força Magnética',
633
+ 'Desejos Ocultos',
634
+ 'A Dança do Destino',
635
+ 'Revelando as Sombras',
636
+ 'Seu Poder no Amor',
637
+ ],
638
+ },
639
+ lantern: {
640
+ en: [
641
+ 'The Call to Awakening',
642
+ 'Walking Through Shadows',
643
+ 'The Inner Flame',
644
+ 'Revelations and Truths',
645
+ 'Becoming the Light',
646
+ ],
647
+ pt: [
648
+ 'O Chamado para o Despertar',
649
+ 'Caminhando Pelas Sombras',
650
+ 'A Chama Interior',
651
+ 'Revelações e Verdades',
652
+ 'Tornando-se a Luz',
653
+ ],
654
+ },
655
+ orb: {
656
+ en: [
657
+ 'The Path Unfolding',
658
+ 'A Calling Beyond the Self',
659
+ 'Turning Points of Fate',
660
+ 'Mastering the Journey',
661
+ 'Legacy and Impact',
662
+ ],
663
+ pt: [
664
+ 'O Caminho que se Revela',
665
+ 'Um Chamado Além de Si Mesmo',
666
+ 'Pontos de Virada do Destino',
667
+ 'Dominando a Jornada',
668
+ 'Legado e Impacto',
669
+ ],
670
+ },
671
+ };
672
+
673
+ const sectionsEN = conceptSections[conceptSlug]?.en;
674
+ const sectionsPT = conceptSections[conceptSlug]?.pt;
675
+
676
+ if (!sectionsEN || !sectionsPT) {
677
+ throw new Error(`❌ Unknown concept: ${conceptSlug}`);
678
+ }
679
+
680
+ // ✅ Step 3: Extract English and Portuguese content separately
533
681
  const enMatches = cleanedResponse.match(/EN:\s*([\s\S]+?)\s*PT:/);
534
682
  const ptMatches = cleanedResponse.match(/PT:\s*([\s\S]+)/);
535
683
 
@@ -552,7 +700,7 @@ export class ConceptService {
552
700
  ptContent.slice(0, 500) + '...'
553
701
  );
554
702
 
555
- // ✅ Step 3: Extract structured sections
703
+ // ✅ Step 4: Extract structured sections
556
704
  function extractSections(text: string, sectionTitles: string[]) {
557
705
  return sectionTitles.map((title) => {
558
706
  console.log(`🔍 [PROCESSING] Extracting section: "${title}"`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zodic/shared",
3
- "version": "0.0.226",
3
+ "version": "0.0.228",
4
4
  "module": "index.ts",
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -146,18 +146,15 @@ export const buildLLMMessages = (env: BackendBindings) => ({
146
146
  generateConceptContent: ({
147
147
  name,
148
148
  description,
149
- poem,
150
149
  combination,
151
150
  conceptSlug,
152
151
  }: {
153
152
  name: string;
154
153
  description: string;
155
- poem: string[];
156
154
  conceptSlug: Concept;
157
155
  combination: string;
158
156
  }): ChatMessages => {
159
157
  const zodiacCombination = mapConceptToPlanets(conceptSlug, combination);
160
- const formattedPoem = poem.map((line) => `${line}`).join('\n');
161
158
 
162
159
  return [
163
160
  {
@@ -171,8 +168,6 @@ export const buildLLMMessages = (env: BackendBindings) => ({
171
168
  - Combination: ${zodiacCombination}
172
169
  - Name: ${name}
173
170
  - Description: ${description}
174
- - Poem:
175
- ${formattedPoem}
176
171
 
177
172
  Generate additional content to elaborate on this concept for frontend display.
178
173
  `,
@@ -246,7 +241,11 @@ export const buildLLMMessages = (env: BackendBindings) => ({
246
241
  ];
247
242
  },
248
243
 
249
- translateENConceptName: ({ englishName }: { englishName: string }): ChatMessages => [
244
+ translateENConceptName: ({
245
+ englishName,
246
+ }: {
247
+ englishName: string;
248
+ }): ChatMessages => [
250
249
  {
251
250
  role: 'system',
252
251
  content: `