@zodic/shared 0.0.225 → 0.0.227
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/app/services/ConceptService.ts +321 -116
- package/package.json +1 -1
- package/utils/buildMessages.ts +5 -6
|
@@ -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 // ✅
|
|
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
|
|
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
|
-
// ✅
|
|
393
|
-
|
|
394
|
-
|
|
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');
|
|
420
|
+
|
|
421
|
+
if (!conceptEN || !conceptPT) {
|
|
422
|
+
throw new Error(`❌ Could not find both EN and PT versions.`);
|
|
423
|
+
}
|
|
395
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
|
|
412
|
-
conceptPT
|
|
441
|
+
conceptEN?.content!.length > 0 &&
|
|
442
|
+
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
|
-
//
|
|
453
|
-
|
|
454
|
-
|
|
481
|
+
// ✅ Store content in D1
|
|
482
|
+
console.log(
|
|
483
|
+
`💾 Storing structured content for ${conceptSlug}:${combinationString} in D1...`
|
|
484
|
+
);
|
|
455
485
|
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
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}
|
|
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
|
-
|
|
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(
|
|
550
|
+
private parseStructuredContent(
|
|
551
|
+
conceptSlug: Concept,
|
|
552
|
+
response: string
|
|
553
|
+
): {
|
|
499
554
|
structuredContentEN: StructuredConceptContent;
|
|
500
555
|
structuredContentPT: StructuredConceptContent;
|
|
501
556
|
} {
|
|
502
|
-
console.log(
|
|
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
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
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
|
|
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}"`);
|
|
@@ -1034,27 +1182,29 @@ export class ConceptService {
|
|
|
1034
1182
|
conceptSlug: Concept,
|
|
1035
1183
|
maxEntries?: number
|
|
1036
1184
|
) {
|
|
1037
|
-
console.log(
|
|
1185
|
+
console.log(
|
|
1186
|
+
`🔄 [START] Regenerating duplicate names for concept: ${conceptSlug}...`
|
|
1187
|
+
);
|
|
1038
1188
|
const startTime = Date.now();
|
|
1039
|
-
|
|
1189
|
+
|
|
1040
1190
|
const db = drizzle(this.context.env.DB);
|
|
1041
|
-
|
|
1191
|
+
|
|
1042
1192
|
// ✅ Step 1: Fetch duplicate entries filtered by conceptSlug
|
|
1043
1193
|
console.log(`📡 Fetching duplicate entries for concept: ${conceptSlug}...`);
|
|
1044
|
-
|
|
1194
|
+
|
|
1045
1195
|
const duplicateEntries = await db
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1196
|
+
.select({
|
|
1197
|
+
id: conceptsData.id,
|
|
1198
|
+
name: conceptsData.name,
|
|
1199
|
+
description: conceptsData.description,
|
|
1200
|
+
combination: conceptsData.combination,
|
|
1201
|
+
})
|
|
1202
|
+
.from(conceptsData)
|
|
1203
|
+
.where(
|
|
1204
|
+
and(
|
|
1205
|
+
eq(conceptsData.language, 'en-us'), // ✅ English only
|
|
1206
|
+
eq(conceptsData.conceptSlug, conceptSlug), // ✅ Filter by concept
|
|
1207
|
+
sql`
|
|
1058
1208
|
name IN (
|
|
1059
1209
|
SELECT name
|
|
1060
1210
|
FROM concepts_data
|
|
@@ -1063,19 +1213,24 @@ export class ConceptService {
|
|
|
1063
1213
|
HAVING COUNT(*) > 1
|
|
1064
1214
|
)
|
|
1065
1215
|
`
|
|
1216
|
+
)
|
|
1066
1217
|
)
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1218
|
+
.orderBy(conceptsData.name) // ✅ Order by name for better grouping
|
|
1219
|
+
.limit(maxEntries || 50) // ✅ Apply optional limit
|
|
1220
|
+
.execute();
|
|
1221
|
+
|
|
1072
1222
|
if (!duplicateEntries.length) {
|
|
1073
1223
|
console.log(`🎉 No duplicates found for ${conceptSlug}.`);
|
|
1074
|
-
return {
|
|
1224
|
+
return {
|
|
1225
|
+
message: `No duplicates detected for ${conceptSlug}.`,
|
|
1226
|
+
remainingDuplicates: 0,
|
|
1227
|
+
};
|
|
1075
1228
|
}
|
|
1076
|
-
|
|
1077
|
-
console.log(
|
|
1078
|
-
|
|
1229
|
+
|
|
1230
|
+
console.log(
|
|
1231
|
+
`🔍 Found ${duplicateEntries.length} duplicate entries to process.`
|
|
1232
|
+
);
|
|
1233
|
+
|
|
1079
1234
|
// ✅ Group duplicates by name
|
|
1080
1235
|
const duplicateGroups = new Map<string, typeof duplicateEntries>();
|
|
1081
1236
|
for (const entry of duplicateEntries) {
|
|
@@ -1084,50 +1239,60 @@ export class ConceptService {
|
|
|
1084
1239
|
}
|
|
1085
1240
|
duplicateGroups.get(entry.name)!.push(entry);
|
|
1086
1241
|
}
|
|
1087
|
-
|
|
1242
|
+
|
|
1088
1243
|
let recentDuplicates = new Set<string>(); // ✅ Track newly generated names
|
|
1089
1244
|
let updatedEntries = 0;
|
|
1090
1245
|
let skippedEntries = 0;
|
|
1091
1246
|
let failedEntries = 0;
|
|
1092
|
-
|
|
1247
|
+
|
|
1093
1248
|
for (const [duplicateName, entries] of duplicateGroups.entries()) {
|
|
1094
|
-
console.log(
|
|
1095
|
-
|
|
1249
|
+
console.log(
|
|
1250
|
+
`🔄 Processing duplicate group: "${duplicateName}" with ${entries.length} occurrences`
|
|
1251
|
+
);
|
|
1252
|
+
|
|
1096
1253
|
// ✅ Leave the first entry unchanged and only process the extras
|
|
1097
1254
|
const [keepEntry, ...entriesToProcess] = entries;
|
|
1098
1255
|
console.log(`✅ Keeping original name for ID: ${keepEntry.id}`);
|
|
1099
|
-
|
|
1256
|
+
|
|
1100
1257
|
for (const entry of entriesToProcess) {
|
|
1101
1258
|
const { id, description, combination } = entry;
|
|
1102
|
-
|
|
1259
|
+
|
|
1103
1260
|
console.log(`🎭 Processing duplicate ID ${id}: "${duplicateName}"`);
|
|
1104
|
-
|
|
1261
|
+
|
|
1105
1262
|
let newNameEN: string | null = null;
|
|
1106
1263
|
let newNamePT: string | null = null;
|
|
1107
1264
|
let attempts = 0;
|
|
1108
1265
|
const maxAttempts = 3;
|
|
1109
|
-
|
|
1266
|
+
|
|
1110
1267
|
while (attempts < maxAttempts) {
|
|
1111
1268
|
attempts++;
|
|
1112
|
-
console.log(
|
|
1113
|
-
|
|
1269
|
+
console.log(
|
|
1270
|
+
`📡 Attempt ${attempts}/${maxAttempts} - Requesting AI for a new name...`
|
|
1271
|
+
);
|
|
1272
|
+
|
|
1114
1273
|
// ✅ Generate a new name, passing dynamically tracked duplicates
|
|
1115
|
-
const messages = this.context
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1274
|
+
const messages = this.context
|
|
1275
|
+
.buildLLMMessages()
|
|
1276
|
+
.regenerateConceptName({
|
|
1277
|
+
conceptSlug,
|
|
1278
|
+
oldNameEN: duplicateName,
|
|
1279
|
+
description,
|
|
1280
|
+
recentNames: Array.from(recentDuplicates),
|
|
1281
|
+
});
|
|
1282
|
+
|
|
1283
|
+
const aiResponse = await this.context
|
|
1284
|
+
.api()
|
|
1285
|
+
.callTogether.single(messages, {});
|
|
1286
|
+
|
|
1124
1287
|
if (!aiResponse) {
|
|
1125
|
-
console.warn(
|
|
1288
|
+
console.warn(
|
|
1289
|
+
`⚠️ AI failed to generate a new name for ID ${id}, skipping.`
|
|
1290
|
+
);
|
|
1126
1291
|
break;
|
|
1127
1292
|
}
|
|
1128
|
-
|
|
1293
|
+
|
|
1129
1294
|
console.log(`📝 Raw AI Response:\n${aiResponse}`);
|
|
1130
|
-
|
|
1295
|
+
|
|
1131
1296
|
// ✅ Mapping for concept names with proper PT articles
|
|
1132
1297
|
const conceptMapping = {
|
|
1133
1298
|
amulet: { en: 'Amulet', pt: 'Amuleto', articlePT: 'O' },
|
|
@@ -1137,7 +1302,7 @@ export class ConceptService {
|
|
|
1137
1302
|
ring: { en: 'Ring', pt: 'Anel', articlePT: 'O' },
|
|
1138
1303
|
orb: { en: 'Orb', pt: 'Orbe', articlePT: 'O' },
|
|
1139
1304
|
};
|
|
1140
|
-
|
|
1305
|
+
|
|
1141
1306
|
const {
|
|
1142
1307
|
en: conceptEN,
|
|
1143
1308
|
pt: conceptPT,
|
|
@@ -1147,80 +1312,120 @@ export class ConceptService {
|
|
|
1147
1312
|
pt: conceptSlug,
|
|
1148
1313
|
articlePT: 'O', // Default to "O" if concept is missing
|
|
1149
1314
|
};
|
|
1150
|
-
|
|
1315
|
+
|
|
1151
1316
|
// ✅ Strict regex matching for English and Portuguese
|
|
1152
|
-
const enMatch = aiResponse.match(
|
|
1153
|
-
|
|
1154
|
-
|
|
1317
|
+
const enMatch = aiResponse.match(
|
|
1318
|
+
new RegExp(`EN:\\s*The ${conceptEN} of (.+)`)
|
|
1319
|
+
);
|
|
1320
|
+
const ptMatch = aiResponse.match(
|
|
1321
|
+
new RegExp(`PT:\\s*${articlePT} ${conceptPT} de (.+)`)
|
|
1322
|
+
);
|
|
1323
|
+
|
|
1155
1324
|
if (!enMatch || !ptMatch) {
|
|
1156
|
-
console.error(
|
|
1325
|
+
console.error(
|
|
1326
|
+
`❌ Invalid AI response format for ID ${id}, retrying...`
|
|
1327
|
+
);
|
|
1157
1328
|
continue;
|
|
1158
1329
|
}
|
|
1159
|
-
|
|
1330
|
+
|
|
1160
1331
|
newNameEN = `The ${conceptEN} of ${enMatch[1].trim()}`;
|
|
1161
1332
|
newNamePT = `${articlePT} ${conceptPT} de ${ptMatch[1].trim()}`;
|
|
1162
|
-
|
|
1163
|
-
console.log(
|
|
1164
|
-
|
|
1333
|
+
|
|
1334
|
+
console.log(
|
|
1335
|
+
`🎨 Extracted Names: EN - "${newNameEN}", PT - "${newNamePT}"`
|
|
1336
|
+
);
|
|
1337
|
+
|
|
1165
1338
|
// ✅ Check if the new name is already in use in D1 before updating
|
|
1166
1339
|
const existingEntry = await db
|
|
1167
1340
|
.select({ id: conceptsData.id })
|
|
1168
1341
|
.from(conceptsData)
|
|
1169
1342
|
.where(eq(conceptsData.name, newNameEN))
|
|
1170
1343
|
.execute();
|
|
1171
|
-
|
|
1344
|
+
|
|
1172
1345
|
if (existingEntry.length > 0 || recentDuplicates.has(newNameEN)) {
|
|
1173
1346
|
console.warn(`⚠️ Duplicate detected: "${newNameEN}", retrying...`);
|
|
1174
1347
|
continue;
|
|
1175
1348
|
}
|
|
1176
|
-
|
|
1349
|
+
|
|
1177
1350
|
recentDuplicates.add(newNameEN);
|
|
1178
1351
|
break; // ✅ Found a valid new name
|
|
1179
1352
|
}
|
|
1180
|
-
|
|
1353
|
+
|
|
1181
1354
|
if (!newNameEN || !newNamePT) {
|
|
1182
|
-
console.error(
|
|
1355
|
+
console.error(
|
|
1356
|
+
`🚨 Failed to generate a unique name for ID ${id} after ${maxAttempts} attempts.`
|
|
1357
|
+
);
|
|
1183
1358
|
failedEntries++;
|
|
1184
1359
|
continue;
|
|
1185
1360
|
}
|
|
1186
|
-
|
|
1187
|
-
console.log(
|
|
1188
|
-
|
|
1361
|
+
|
|
1362
|
+
console.log(
|
|
1363
|
+
`✅ Updating names: EN - "${newNameEN}", PT - "${newNamePT}"`
|
|
1364
|
+
);
|
|
1365
|
+
|
|
1189
1366
|
// ✅ Update English name using the ID
|
|
1190
1367
|
await db
|
|
1191
1368
|
.update(conceptsData)
|
|
1192
1369
|
.set({ name: newNameEN })
|
|
1193
|
-
.where(
|
|
1370
|
+
.where(
|
|
1371
|
+
and(eq(conceptsData.id, id), eq(conceptsData.language, 'en-us'))
|
|
1372
|
+
)
|
|
1194
1373
|
.execute();
|
|
1195
|
-
|
|
1374
|
+
|
|
1196
1375
|
// ✅ Convert English ID to Portuguese ID by replacing `:en-us:` with `:pt-br:`
|
|
1197
1376
|
const ptId = id.replace(':en-us:', ':pt-br:');
|
|
1198
|
-
|
|
1377
|
+
|
|
1199
1378
|
// ✅ Update Portuguese name using the derived PT ID
|
|
1200
1379
|
await db
|
|
1201
1380
|
.update(conceptsData)
|
|
1202
1381
|
.set({ name: newNamePT })
|
|
1203
|
-
.where(
|
|
1382
|
+
.where(
|
|
1383
|
+
and(eq(conceptsData.id, ptId), eq(conceptsData.language, 'pt-br'))
|
|
1384
|
+
)
|
|
1204
1385
|
.execute();
|
|
1205
|
-
|
|
1206
|
-
console.log(
|
|
1207
|
-
|
|
1386
|
+
|
|
1387
|
+
console.log(
|
|
1388
|
+
`📝 Updated IDs: EN (${id}) -> "${newNameEN}", PT (${ptId}) -> "${newNamePT}"`
|
|
1389
|
+
);
|
|
1390
|
+
|
|
1208
1391
|
updatedEntries++;
|
|
1209
1392
|
}
|
|
1210
1393
|
}
|
|
1211
|
-
|
|
1394
|
+
|
|
1212
1395
|
const elapsedTime = ((Date.now() - startTime) / 1000).toFixed(2);
|
|
1213
|
-
|
|
1396
|
+
|
|
1214
1397
|
console.log(`🎉 [COMPLETE] Regeneration finished in ${elapsedTime}s.`);
|
|
1215
1398
|
console.log(`✅ Successfully updated: ${updatedEntries}`);
|
|
1216
1399
|
console.log(`⚠️ Skipped (already unique): ${skippedEntries}`);
|
|
1217
1400
|
console.log(`🚨 Failed attempts: ${failedEntries}`);
|
|
1218
|
-
|
|
1401
|
+
|
|
1402
|
+
const remainingDuplicates = await db
|
|
1403
|
+
.select({ count: sql<number>`COUNT(*)` }) // ✅ Count only duplicates
|
|
1404
|
+
.from(conceptsData)
|
|
1405
|
+
.where(
|
|
1406
|
+
and(
|
|
1407
|
+
eq(conceptsData.conceptSlug, conceptSlug), // ✅ Filter by concept
|
|
1408
|
+
sql`name IN (
|
|
1409
|
+
SELECT name FROM concepts_data
|
|
1410
|
+
WHERE concept_slug = ${conceptSlug}
|
|
1411
|
+
GROUP BY name
|
|
1412
|
+
HAVING COUNT(*) > 1
|
|
1413
|
+
)`
|
|
1414
|
+
)
|
|
1415
|
+
)
|
|
1416
|
+
.execute()
|
|
1417
|
+
.then((res) => res[0]?.count || 0);
|
|
1418
|
+
|
|
1419
|
+
console.log(
|
|
1420
|
+
`🔄 Remaining duplicates for ${conceptSlug}: ${remainingDuplicates}`
|
|
1421
|
+
);
|
|
1422
|
+
|
|
1219
1423
|
return {
|
|
1220
1424
|
message: `Duplicate names processed and regenerated for ${conceptSlug}.`,
|
|
1221
1425
|
updated: updatedEntries,
|
|
1222
1426
|
skipped: skippedEntries,
|
|
1223
1427
|
failed: failedEntries,
|
|
1428
|
+
remainingDuplicates,
|
|
1224
1429
|
elapsedTime,
|
|
1225
1430
|
};
|
|
1226
1431
|
}
|
package/package.json
CHANGED
package/utils/buildMessages.ts
CHANGED
|
@@ -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: ({
|
|
244
|
+
translateENConceptName: ({
|
|
245
|
+
englishName,
|
|
246
|
+
}: {
|
|
247
|
+
englishName: string;
|
|
248
|
+
}): ChatMessages => [
|
|
250
249
|
{
|
|
251
250
|
role: 'system',
|
|
252
251
|
content: `
|