@zodic/shared 0.0.272 → 0.0.274
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.
|
@@ -323,25 +323,56 @@ export class ConceptService {
|
|
|
323
323
|
conceptSlug: Concept,
|
|
324
324
|
combinationString: string,
|
|
325
325
|
override: boolean = false
|
|
326
|
-
): Promise<
|
|
326
|
+
): Promise<{ generatedPrompt: string }> {
|
|
327
327
|
console.log(
|
|
328
|
-
`🚀 Generating Leonardo prompt for concept: ${conceptSlug}, combination: ${combinationString}`
|
|
328
|
+
`🚀 Generating Leonardo prompt for concept: ${conceptSlug}, combination: ${combinationString}, override: ${override}`
|
|
329
329
|
);
|
|
330
330
|
|
|
331
|
-
// ✅
|
|
332
|
-
|
|
333
|
-
|
|
331
|
+
const db = drizzle(this.context.env.DB); // ✅ Initialize Drizzle with Cloudflare D1
|
|
332
|
+
|
|
333
|
+
// ✅ Fetch concept data from D1 for both languages
|
|
334
|
+
console.log(
|
|
335
|
+
`📡 Fetching concept data for ${conceptSlug}:${combinationString}`
|
|
336
|
+
);
|
|
337
|
+
|
|
338
|
+
const conceptEntries = await db
|
|
339
|
+
.select({
|
|
340
|
+
id: conceptsData.id,
|
|
341
|
+
language: conceptsData.language,
|
|
342
|
+
name: conceptsData.name,
|
|
343
|
+
description: conceptsData.description,
|
|
344
|
+
poem: conceptsData.poem,
|
|
345
|
+
leonardoPrompt: conceptsData.leonardoPrompt,
|
|
346
|
+
})
|
|
347
|
+
.from(conceptsData)
|
|
348
|
+
.where(
|
|
349
|
+
and(
|
|
350
|
+
eq(conceptsData.conceptSlug, conceptSlug),
|
|
351
|
+
eq(conceptsData.combination, combinationString),
|
|
352
|
+
inArray(conceptsData.language, ['en-us', 'pt-br']) // ✅ Fetch both languages
|
|
353
|
+
)
|
|
354
|
+
)
|
|
355
|
+
.execute();
|
|
356
|
+
|
|
357
|
+
if (conceptEntries.length !== 2) {
|
|
358
|
+
throw new Error(
|
|
359
|
+
`❌ Missing concept data for ${conceptSlug}:${combinationString}. Ensure basic info is populated first.`
|
|
360
|
+
);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
const conceptEN = conceptEntries.find((c) => c.language === 'en-us');
|
|
364
|
+
const conceptPT = conceptEntries.find((c) => c.language === 'pt-br');
|
|
334
365
|
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
366
|
+
if (!conceptEN || !conceptPT) {
|
|
367
|
+
throw new Error(`❌ Could not find both EN and PT versions.`);
|
|
368
|
+
}
|
|
338
369
|
|
|
339
|
-
// ✅ Check if prompt already exists
|
|
370
|
+
// ✅ Check if prompt already exists and skip if override is false
|
|
340
371
|
if (!override && conceptEN.leonardoPrompt && conceptPT.leonardoPrompt) {
|
|
341
372
|
console.log(
|
|
342
373
|
`⚡ Leonardo prompt already exists for ${conceptSlug}, skipping.`
|
|
343
374
|
);
|
|
344
|
-
return
|
|
375
|
+
return { generatedPrompt: conceptEN.leonardoPrompt };
|
|
345
376
|
}
|
|
346
377
|
|
|
347
378
|
// ✅ Ensure basic info is present
|
|
@@ -374,18 +405,38 @@ export class ConceptService {
|
|
|
374
405
|
);
|
|
375
406
|
}
|
|
376
407
|
|
|
377
|
-
|
|
378
|
-
conceptEN.leonardoPrompt = response.trim();
|
|
379
|
-
conceptPT.leonardoPrompt = response.trim();
|
|
408
|
+
const generatedPrompt = response.trim();
|
|
380
409
|
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
410
|
+
// ✅ Store the generated prompt in D1 for both languages
|
|
411
|
+
console.log(
|
|
412
|
+
`💾 Storing Leonardo prompt for ${conceptSlug}:${combinationString} in D1...`
|
|
413
|
+
);
|
|
414
|
+
await db.batch([
|
|
415
|
+
db
|
|
416
|
+
.update(conceptsData)
|
|
417
|
+
.set({ leonardoPrompt: generatedPrompt })
|
|
418
|
+
.where(
|
|
419
|
+
and(
|
|
420
|
+
eq(conceptsData.id, conceptEN.id),
|
|
421
|
+
eq(conceptsData.language, 'en-us')
|
|
422
|
+
)
|
|
423
|
+
),
|
|
424
|
+
db
|
|
425
|
+
.update(conceptsData)
|
|
426
|
+
.set({ leonardoPrompt: generatedPrompt })
|
|
427
|
+
.where(
|
|
428
|
+
and(
|
|
429
|
+
eq(conceptsData.id, conceptPT.id),
|
|
430
|
+
eq(conceptsData.language, 'pt-br')
|
|
431
|
+
)
|
|
432
|
+
),
|
|
384
433
|
]);
|
|
385
434
|
|
|
386
435
|
console.log(
|
|
387
436
|
`✅ Leonardo prompt stored for concept: ${conceptSlug}, combination: ${combinationString}, in both languages.`
|
|
388
437
|
);
|
|
438
|
+
|
|
439
|
+
return { generatedPrompt };
|
|
389
440
|
}
|
|
390
441
|
|
|
391
442
|
/**
|
|
@@ -795,457 +846,42 @@ export class ConceptService {
|
|
|
795
846
|
}
|
|
796
847
|
}
|
|
797
848
|
|
|
798
|
-
// async parseStructuredContent(
|
|
799
|
-
// conceptSlug: Concept,
|
|
800
|
-
// response: string
|
|
801
|
-
// ): Promise<{
|
|
802
|
-
// structuredContentEN: StructuredConceptContent;
|
|
803
|
-
// structuredContentPT: StructuredConceptContent;
|
|
804
|
-
// }> {
|
|
805
|
-
// console.log(
|
|
806
|
-
// `📌 [START] Parsing structured content for ${conceptSlug} from ChatGPT response.`
|
|
807
|
-
// );
|
|
808
|
-
|
|
809
|
-
// // ✅ Step 1: Clean artifacts before processing
|
|
810
|
-
// let cleanedResponse = response
|
|
811
|
-
// .replace(/[*#•]/g, '') // Remove bullet points, bold markers, headings
|
|
812
|
-
// .replace(/---+/g, '') // Remove dividers (like "---")
|
|
813
|
-
// .replace(/\n\s*\n/g, '\n\n') // Normalize multiple line breaks
|
|
814
|
-
// .replace(/\s+EN:\s*/g, '\nEN: ') // Ensure consistent "EN:" formatting
|
|
815
|
-
// .replace(/\s+PT:\s*/g, '\nPT: ') // Ensure consistent "PT:" formatting
|
|
816
|
-
// .trim();
|
|
817
|
-
|
|
818
|
-
// console.log(
|
|
819
|
-
// '🔹 [CLEANED RESPONSE]:',
|
|
820
|
-
// cleanedResponse.slice(0, 500) + '...'
|
|
821
|
-
// );
|
|
822
|
-
|
|
823
|
-
// try {
|
|
824
|
-
// // ✅ Step 2: Define Section Titles Per Concept
|
|
825
|
-
// const conceptSections: Record<string, { en: string[]; pt: string[] }> = {
|
|
826
|
-
// crown: {
|
|
827
|
-
// en: [
|
|
828
|
-
// 'Core Identity',
|
|
829
|
-
// 'Strengths and Challenges',
|
|
830
|
-
// 'Path to Fulfillment',
|
|
831
|
-
// 'Emotional Depth',
|
|
832
|
-
// 'Vision and Aspirations',
|
|
833
|
-
// ],
|
|
834
|
-
// pt: [
|
|
835
|
-
// 'Identidade Essencial',
|
|
836
|
-
// 'Forças e Desafios',
|
|
837
|
-
// 'Caminho para a Plenitude',
|
|
838
|
-
// 'Profundidade Emocional',
|
|
839
|
-
// 'Visão e Aspirações',
|
|
840
|
-
// ],
|
|
841
|
-
// },
|
|
842
|
-
// scepter: {
|
|
843
|
-
// en: [
|
|
844
|
-
// 'The Power of Expression',
|
|
845
|
-
// 'The Dance of Action and Reaction',
|
|
846
|
-
// 'Navigating Challenges',
|
|
847
|
-
// 'The Mirror of Relations',
|
|
848
|
-
// 'Impact on the World',
|
|
849
|
-
// ],
|
|
850
|
-
// pt: [
|
|
851
|
-
// 'O Poder da Expressão',
|
|
852
|
-
// 'A Dança da Ação e Reação',
|
|
853
|
-
// 'Navegando Desafios',
|
|
854
|
-
// 'O Espelho das Relações',
|
|
855
|
-
// 'Impacto no Mundo',
|
|
856
|
-
// ],
|
|
857
|
-
// },
|
|
858
|
-
// amulet: {
|
|
859
|
-
// en: [
|
|
860
|
-
// 'The Shield of Protection',
|
|
861
|
-
// 'The Strength Within',
|
|
862
|
-
// 'Nurturing Growth',
|
|
863
|
-
// 'The Cycle of Renewal',
|
|
864
|
-
// 'The Anchor of Wisdom',
|
|
865
|
-
// ],
|
|
866
|
-
// pt: [
|
|
867
|
-
// 'O Escudo da Proteção',
|
|
868
|
-
// 'A Força Interior',
|
|
869
|
-
// 'Cultivando o Crescimento',
|
|
870
|
-
// 'O Ciclo da Renovação',
|
|
871
|
-
// 'A Âncora da Sabedoria',
|
|
872
|
-
// ],
|
|
873
|
-
// },
|
|
874
|
-
// ring: {
|
|
875
|
-
// en: [
|
|
876
|
-
// 'Magnetic Pull',
|
|
877
|
-
// 'Hidden Desires',
|
|
878
|
-
// 'The Fated Dance',
|
|
879
|
-
// 'Unveiling the Shadows',
|
|
880
|
-
// 'Your Love Power',
|
|
881
|
-
// ],
|
|
882
|
-
// pt: [
|
|
883
|
-
// 'Força Magnética',
|
|
884
|
-
// 'Desejos Ocultos',
|
|
885
|
-
// 'A Dança do Destino',
|
|
886
|
-
// 'Revelando as Sombras',
|
|
887
|
-
// 'Seu Poder no Amor',
|
|
888
|
-
// ],
|
|
889
|
-
// },
|
|
890
|
-
// lantern: {
|
|
891
|
-
// en: [
|
|
892
|
-
// 'The Call to Awakening',
|
|
893
|
-
// 'Walking Through Shadows',
|
|
894
|
-
// 'The Inner Flame',
|
|
895
|
-
// 'Revelations and Truths',
|
|
896
|
-
// 'Becoming the Light',
|
|
897
|
-
// ],
|
|
898
|
-
// pt: [
|
|
899
|
-
// 'O Chamado para o Despertar',
|
|
900
|
-
// 'Caminhando Pelas Sombras',
|
|
901
|
-
// 'A Chama Interior',
|
|
902
|
-
// 'Revelações e Verdades',
|
|
903
|
-
// 'Tornando-se a Luz',
|
|
904
|
-
// ],
|
|
905
|
-
// },
|
|
906
|
-
// orb: {
|
|
907
|
-
// en: [
|
|
908
|
-
// 'The Path Unfolding',
|
|
909
|
-
// 'A Calling Beyond the Self',
|
|
910
|
-
// 'Turning Points of Fate',
|
|
911
|
-
// 'Mastering the Journey',
|
|
912
|
-
// 'Legacy and Impact',
|
|
913
|
-
// ],
|
|
914
|
-
// pt: [
|
|
915
|
-
// 'O Caminho que se Revela',
|
|
916
|
-
// 'Um Chamado Além de Si Mesmo',
|
|
917
|
-
// 'Pontos de Virada do Destino',
|
|
918
|
-
// 'Dominando a Jornada',
|
|
919
|
-
// 'Legado e Impacto',
|
|
920
|
-
// ],
|
|
921
|
-
// },
|
|
922
|
-
// };
|
|
923
|
-
|
|
924
|
-
// const sectionsEN = conceptSections[conceptSlug]?.en;
|
|
925
|
-
// const sectionsPT = conceptSections[conceptSlug]?.pt;
|
|
926
|
-
|
|
927
|
-
// if (!sectionsEN || !sectionsPT) {
|
|
928
|
-
// throw new Error(`Unknown concept: ${conceptSlug}`);
|
|
929
|
-
// }
|
|
930
|
-
|
|
931
|
-
// // ✅ Step 3: Detect if AI interleaved EN/PT sections
|
|
932
|
-
// const interleavedMatches = cleanedResponse.match(
|
|
933
|
-
// /(EN:\s*[\s\S]+?PT:\s*[\s\S]+?)(?=EN:|$)/
|
|
934
|
-
// );
|
|
935
|
-
// let extractedEN = '';
|
|
936
|
-
// let extractedPT = '';
|
|
937
|
-
|
|
938
|
-
// if (interleavedMatches) {
|
|
939
|
-
// console.warn(
|
|
940
|
-
// '⚠️ Detected interleaved EN/PT sections. Parsing accordingly.'
|
|
941
|
-
// );
|
|
942
|
-
// extractedEN = interleavedMatches
|
|
943
|
-
// .map(
|
|
944
|
-
// (pair) => pair.match(/EN:\s*([\s\S]+?)\s*PT:/)?.[1]?.trim() || ''
|
|
945
|
-
// )
|
|
946
|
-
// .join('\n\n');
|
|
947
|
-
// extractedPT = interleavedMatches
|
|
948
|
-
// .map((pair) => pair.match(/PT:\s*([\s\S]+)/)?.[1]?.trim() || '')
|
|
949
|
-
// .join('\n\n');
|
|
950
|
-
// } else {
|
|
951
|
-
// console.log(
|
|
952
|
-
// '✅ Standard format detected (EN block followed by PT block).'
|
|
953
|
-
// );
|
|
954
|
-
// extractedEN =
|
|
955
|
-
// cleanedResponse.match(/EN:\s*([\s\S]+?)\s*PT:/)?.[1]?.trim() || '';
|
|
956
|
-
// extractedPT =
|
|
957
|
-
// cleanedResponse.match(/PT:\s*([\s\S]+)/)?.[1]?.trim() || '';
|
|
958
|
-
// }
|
|
959
|
-
|
|
960
|
-
// console.log(
|
|
961
|
-
// '✅ [MATCH SUCCESS] Extracted English Content:',
|
|
962
|
-
// extractedEN.slice(0, 500) + '...'
|
|
963
|
-
// );
|
|
964
|
-
// console.log(
|
|
965
|
-
// '✅ [MATCH SUCCESS] Extracted Portuguese Content:',
|
|
966
|
-
// extractedPT.slice(0, 500) + '...'
|
|
967
|
-
// );
|
|
968
|
-
|
|
969
|
-
// // ✅ Step 4: Extract structured sections dynamically
|
|
970
|
-
// function extractSections(text: string, sectionTitles: string[]) {
|
|
971
|
-
// return sectionTitles.map((title) => {
|
|
972
|
-
// console.log(`🔍 [PROCESSING] Extracting section: "${title}"`);
|
|
973
|
-
|
|
974
|
-
// const regex = new RegExp(
|
|
975
|
-
// `\\d+\\.\\s*${title}:\\s*([\\s\\S]+?)(?=\\n\\d+\\.\\s*[A-Z]|$)`
|
|
976
|
-
// );
|
|
977
|
-
// const match = text.match(regex);
|
|
978
|
-
|
|
979
|
-
// if (!match) {
|
|
980
|
-
// throw new Error(`❌ Missing section: "${title}"`);
|
|
981
|
-
// }
|
|
982
|
-
|
|
983
|
-
// const paragraphs = match[1]
|
|
984
|
-
// .trim()
|
|
985
|
-
// .split(/\n{2,}/)
|
|
986
|
-
// .map((p) => p.trim())
|
|
987
|
-
// .filter((p) => p.length > 0);
|
|
988
|
-
|
|
989
|
-
// console.log(
|
|
990
|
-
// `✅ [EXTRACTED] "${title}" - Parsed Content:`,
|
|
991
|
-
// paragraphs
|
|
992
|
-
// );
|
|
993
|
-
// return { type: 'section', title, content: paragraphs };
|
|
994
|
-
// });
|
|
995
|
-
// }
|
|
996
|
-
|
|
997
|
-
// const structuredContentEN = extractSections(extractedEN, sectionsEN);
|
|
998
|
-
// const structuredContentPT = extractSections(extractedPT, sectionsPT);
|
|
999
|
-
|
|
1000
|
-
// console.log(
|
|
1001
|
-
// '🎯 [FINAL RESULT] Parsed English Content:',
|
|
1002
|
-
// JSON.stringify(structuredContentEN, null, 2)
|
|
1003
|
-
// );
|
|
1004
|
-
// console.log(
|
|
1005
|
-
// '🎯 [FINAL RESULT] Parsed Portuguese Content:',
|
|
1006
|
-
// JSON.stringify(structuredContentPT, null, 2)
|
|
1007
|
-
// );
|
|
1008
|
-
|
|
1009
|
-
// console.log(
|
|
1010
|
-
// '✅ [COMPLETE] Structured content parsing finished successfully.'
|
|
1011
|
-
// );
|
|
1012
|
-
|
|
1013
|
-
// return {
|
|
1014
|
-
// structuredContentEN,
|
|
1015
|
-
// structuredContentPT,
|
|
1016
|
-
// };
|
|
1017
|
-
// } catch (error) {
|
|
1018
|
-
// console.error(`❌ Parsing failed for ${conceptSlug}. Logging failure.`);
|
|
1019
|
-
|
|
1020
|
-
// // ✅ Store failure in KV for debugging
|
|
1021
|
-
// const kvFailuresStore = this.context.kvConceptFailuresStore();
|
|
1022
|
-
// const failureKey = `failures:content:parsing:${conceptSlug}:${new Date().toISOString()}`;
|
|
1023
|
-
|
|
1024
|
-
// await kvFailuresStore.put(
|
|
1025
|
-
// failureKey,
|
|
1026
|
-
// JSON.stringify({
|
|
1027
|
-
// error: (error as Error).message,
|
|
1028
|
-
// conceptSlug,
|
|
1029
|
-
// cleanedResponse,
|
|
1030
|
-
// timestamp: new Date().toISOString(),
|
|
1031
|
-
// })
|
|
1032
|
-
// );
|
|
1033
|
-
|
|
1034
|
-
// console.error(`🚨 Failure logged in KV: ${failureKey}`);
|
|
1035
|
-
// throw error;
|
|
1036
|
-
// }
|
|
1037
|
-
// }
|
|
1038
|
-
|
|
1039
|
-
// private parseStructuredContent(
|
|
1040
|
-
// conceptSlug: Concept,
|
|
1041
|
-
// response: string
|
|
1042
|
-
// ): {
|
|
1043
|
-
// structuredContentEN: StructuredConceptContent;
|
|
1044
|
-
// structuredContentPT: StructuredConceptContent;
|
|
1045
|
-
// } {
|
|
1046
|
-
// console.log(`📌 [START] Parsing structured content for ${conceptSlug} from ChatGPT response.`);
|
|
1047
|
-
|
|
1048
|
-
// // ✅ Step 1: Clean artifacts before processing
|
|
1049
|
-
// let cleanedResponse = response
|
|
1050
|
-
// .replace(/[*#•]/g, '') // Remove bullet points, bold markers, headings
|
|
1051
|
-
// .replace(/---+/g, '') // Remove dividers (like "---")
|
|
1052
|
-
// .replace(/\n\s*\n/g, '\n\n') // Normalize multiple line breaks
|
|
1053
|
-
// .replace(/\s+EN:\s*/g, '\nEN: ') // Ensure consistent "EN:" formatting
|
|
1054
|
-
// .replace(/\s+PT:\s*/g, '\nPT: ') // Ensure consistent "PT:" formatting
|
|
1055
|
-
// .trim();
|
|
1056
|
-
|
|
1057
|
-
// console.log('🔹 [CLEANED RESPONSE]:', cleanedResponse.slice(0, 500) + '...');
|
|
1058
|
-
|
|
1059
|
-
// // ✅ Step 2: Define Section Titles Per Concept
|
|
1060
|
-
// const conceptSections: Record<string, { en: string[]; pt: string[] }> = {
|
|
1061
|
-
// crown: {
|
|
1062
|
-
// en: [
|
|
1063
|
-
// 'Core Identity',
|
|
1064
|
-
// 'Strengths and Challenges',
|
|
1065
|
-
// 'Path to Fulfillment',
|
|
1066
|
-
// 'Emotional Depth',
|
|
1067
|
-
// 'Vision and Aspirations',
|
|
1068
|
-
// ],
|
|
1069
|
-
// pt: [
|
|
1070
|
-
// 'Identidade Essencial',
|
|
1071
|
-
// 'Forças e Desafios',
|
|
1072
|
-
// 'Caminho para a Plenitude',
|
|
1073
|
-
// 'Profundidade Emocional',
|
|
1074
|
-
// 'Visão e Aspirações',
|
|
1075
|
-
// ],
|
|
1076
|
-
// },
|
|
1077
|
-
// scepter: {
|
|
1078
|
-
// en: [
|
|
1079
|
-
// 'The Power of Expression',
|
|
1080
|
-
// 'The Dance of Action and Reaction',
|
|
1081
|
-
// 'Navigating Challenges',
|
|
1082
|
-
// 'The Mirror of Relationships',
|
|
1083
|
-
// 'Impact on the World',
|
|
1084
|
-
// ],
|
|
1085
|
-
// pt: [
|
|
1086
|
-
// 'O Poder da Expressão',
|
|
1087
|
-
// 'A Dança da Ação e Reação',
|
|
1088
|
-
// 'Navegando Desafios',
|
|
1089
|
-
// 'O Espelho dos Relacionamentos',
|
|
1090
|
-
// 'Impacto no Mundo',
|
|
1091
|
-
// ],
|
|
1092
|
-
// },
|
|
1093
|
-
// amulet: {
|
|
1094
|
-
// en: [
|
|
1095
|
-
// 'The Shield of Protection',
|
|
1096
|
-
// 'The Strength Within',
|
|
1097
|
-
// 'Nurturing Growth',
|
|
1098
|
-
// 'The Cycle of Renewal',
|
|
1099
|
-
// 'The Anchor of Wisdom',
|
|
1100
|
-
// ],
|
|
1101
|
-
// pt: [
|
|
1102
|
-
// 'O Escudo da Proteção',
|
|
1103
|
-
// 'A Força Interior',
|
|
1104
|
-
// 'Cultivando o Crescimento',
|
|
1105
|
-
// 'O Ciclo da Renovação',
|
|
1106
|
-
// 'A Âncora da Sabedoria',
|
|
1107
|
-
// ],
|
|
1108
|
-
// },
|
|
1109
|
-
// ring: {
|
|
1110
|
-
// en: [
|
|
1111
|
-
// 'Magnetic Pull',
|
|
1112
|
-
// 'Hidden Desires',
|
|
1113
|
-
// 'The Fated Dance',
|
|
1114
|
-
// 'Unveiling the Shadows',
|
|
1115
|
-
// 'Your Love Power',
|
|
1116
|
-
// ],
|
|
1117
|
-
// pt: [
|
|
1118
|
-
// 'Força Magnética',
|
|
1119
|
-
// 'Desejos Ocultos',
|
|
1120
|
-
// 'A Dança do Destino',
|
|
1121
|
-
// 'Revelando as Sombras',
|
|
1122
|
-
// 'Seu Poder no Amor',
|
|
1123
|
-
// ],
|
|
1124
|
-
// },
|
|
1125
|
-
// lantern: {
|
|
1126
|
-
// en: [
|
|
1127
|
-
// 'The Call to Awakening',
|
|
1128
|
-
// 'Walking Through Shadows',
|
|
1129
|
-
// 'The Inner Flame',
|
|
1130
|
-
// 'Revelations and Truths',
|
|
1131
|
-
// 'Becoming the Light',
|
|
1132
|
-
// ],
|
|
1133
|
-
// pt: [
|
|
1134
|
-
// 'O Chamado para o Despertar',
|
|
1135
|
-
// 'Caminhando Pelas Sombras',
|
|
1136
|
-
// 'A Chama Interior',
|
|
1137
|
-
// 'Revelações e Verdades',
|
|
1138
|
-
// 'Tornando-se a Luz',
|
|
1139
|
-
// ],
|
|
1140
|
-
// },
|
|
1141
|
-
// orb: {
|
|
1142
|
-
// en: [
|
|
1143
|
-
// 'The Path Unfolding',
|
|
1144
|
-
// 'A Calling Beyond the Self',
|
|
1145
|
-
// 'Turning Points of Fate',
|
|
1146
|
-
// 'Mastering the Journey',
|
|
1147
|
-
// 'Legacy and Impact',
|
|
1148
|
-
// ],
|
|
1149
|
-
// pt: [
|
|
1150
|
-
// 'O Caminho que se Revela',
|
|
1151
|
-
// 'Um Chamado Além de Si Mesmo',
|
|
1152
|
-
// 'Pontos de Virada do Destino',
|
|
1153
|
-
// 'Dominando a Jornada',
|
|
1154
|
-
// 'Legado e Impacto',
|
|
1155
|
-
// ],
|
|
1156
|
-
// },
|
|
1157
|
-
// };
|
|
1158
|
-
|
|
1159
|
-
// const sectionsEN = conceptSections[conceptSlug]?.en;
|
|
1160
|
-
// const sectionsPT = conceptSections[conceptSlug]?.pt;
|
|
1161
|
-
|
|
1162
|
-
// if (!sectionsEN || !sectionsPT) {
|
|
1163
|
-
// throw new Error(`❌ Unknown concept: ${conceptSlug}`);
|
|
1164
|
-
// }
|
|
1165
|
-
|
|
1166
|
-
// // ✅ Step 3: Extract English and Portuguese content separately
|
|
1167
|
-
// const enMatches = cleanedResponse.match(/EN:\s*([\s\S]+?)\s*PT:/);
|
|
1168
|
-
// const ptMatches = cleanedResponse.match(/PT:\s*([\s\S]+)/);
|
|
1169
|
-
|
|
1170
|
-
// if (!enMatches || !ptMatches) {
|
|
1171
|
-
// console.error('❌ [ERROR] Missing English or Portuguese content in response.');
|
|
1172
|
-
// throw new Error('❌ Missing English or Portuguese content in response.');
|
|
1173
|
-
// }
|
|
1174
|
-
|
|
1175
|
-
// let enContent = enMatches[1].trim();
|
|
1176
|
-
// let ptContent = ptMatches[1].trim();
|
|
1177
|
-
|
|
1178
|
-
// console.log('✅ [MATCH SUCCESS] Extracted English Content:', enContent.slice(0, 500) + '...');
|
|
1179
|
-
// console.log('✅ [MATCH SUCCESS] Extracted Portuguese Content:', ptContent.slice(0, 500) + '...');
|
|
1180
|
-
|
|
1181
|
-
// // ✅ Step 4: Debug **why** PT content has "EN:"
|
|
1182
|
-
// if (ptContent.includes('EN:')) {
|
|
1183
|
-
// console.warn('⚠️ PT content contains "EN:". Check if parsing was incorrect.');
|
|
1184
|
-
// }
|
|
1185
|
-
|
|
1186
|
-
// // ✅ Step 5: Extract structured sections
|
|
1187
|
-
// function extractSections(text: string, sectionTitles: string[]) {
|
|
1188
|
-
// return sectionTitles.map((title) => {
|
|
1189
|
-
// console.log(`🔍 [PROCESSING] Extracting section: "${title}"`);
|
|
1190
|
-
|
|
1191
|
-
// const regex = new RegExp(`\\d+\\.\\s*${title}:\\s*([\\s\\S]+?)(?=\\n\\d+\\.\\s*[A-Z]|$)`);
|
|
1192
|
-
// const match = text.match(regex);
|
|
1193
|
-
|
|
1194
|
-
// if (!match) {
|
|
1195
|
-
// console.warn(`⚠️ [WARNING] Missing section: "${title}"`);
|
|
1196
|
-
// return { type: 'section', title, content: ['❌ Section Missing'] };
|
|
1197
|
-
// }
|
|
1198
|
-
|
|
1199
|
-
// const paragraphs = match[1]
|
|
1200
|
-
// .trim()
|
|
1201
|
-
// .split(/\n{2,}/)
|
|
1202
|
-
// .map((p) => p.trim())
|
|
1203
|
-
// .filter((p) => p.length > 0);
|
|
1204
|
-
|
|
1205
|
-
// console.log(`✅ [EXTRACTED] "${title}" - Parsed Content:`, paragraphs);
|
|
1206
|
-
|
|
1207
|
-
// return { type: 'section', title, content: paragraphs };
|
|
1208
|
-
// });
|
|
1209
|
-
// }
|
|
1210
|
-
|
|
1211
|
-
// const structuredContentEN = extractSections(enContent, sectionsEN);
|
|
1212
|
-
// const structuredContentPT = extractSections(ptContent, sectionsPT);
|
|
1213
|
-
|
|
1214
|
-
// console.log('🎯 [FINAL RESULT] Parsed English Content:', JSON.stringify(structuredContentEN, null, 2));
|
|
1215
|
-
// console.log('🎯 [FINAL RESULT] Parsed Portuguese Content:', JSON.stringify(structuredContentPT, null, 2));
|
|
1216
|
-
|
|
1217
|
-
// console.log('✅ [COMPLETE] Structured content parsing finished successfully.');
|
|
1218
|
-
|
|
1219
|
-
// return {
|
|
1220
|
-
// structuredContentEN,
|
|
1221
|
-
// structuredContentPT,
|
|
1222
|
-
// };
|
|
1223
|
-
// }
|
|
1224
849
|
/**
|
|
1225
850
|
* Queue image generation for a concept.
|
|
1226
851
|
*/
|
|
1227
852
|
async generateImages(
|
|
1228
|
-
language: Languages,
|
|
1229
853
|
conceptSlug: Concept,
|
|
1230
854
|
combinationString: string
|
|
1231
855
|
): Promise<void> {
|
|
1232
|
-
const
|
|
856
|
+
const db = this.context.drizzle();
|
|
1233
857
|
const { width, height } = sizes['alchemy']['post4:5'];
|
|
1234
858
|
const { generations, conceptCombinations, concepts } = schema;
|
|
1235
859
|
|
|
1236
|
-
const kvKey = buildConceptKVKey(language, conceptSlug, combinationString);
|
|
1237
860
|
console.log(
|
|
1238
|
-
`🚀 Queuing image generation for concept: ${conceptSlug}, combination: ${combinationString}
|
|
861
|
+
`🚀 Queuing image generation for concept: ${conceptSlug}, combination: ${combinationString}`
|
|
1239
862
|
);
|
|
1240
863
|
|
|
1241
|
-
const
|
|
1242
|
-
|
|
864
|
+
const conceptEntries = await db
|
|
865
|
+
.select({
|
|
866
|
+
leonardoPrompt: conceptsData.leonardoPrompt,
|
|
867
|
+
})
|
|
868
|
+
.from(conceptsData)
|
|
869
|
+
.where(
|
|
870
|
+
and(
|
|
871
|
+
eq(conceptsData.conceptSlug, conceptSlug),
|
|
872
|
+
eq(conceptsData.combination, combinationString),
|
|
873
|
+
eq(conceptsData.language, "en-us") // ✅ Fetch both languages
|
|
874
|
+
)
|
|
875
|
+
)
|
|
876
|
+
.limit(1);
|
|
877
|
+
|
|
878
|
+
if (!conceptEntries[0].leonardoPrompt) {
|
|
1243
879
|
throw new Error(
|
|
1244
880
|
`❌ Leonardo prompt must be populated before generating images for concept: ${conceptSlug}`
|
|
1245
881
|
);
|
|
1246
882
|
}
|
|
1247
883
|
|
|
1248
|
-
const conceptRecord = await
|
|
884
|
+
const conceptRecord = await db
|
|
1249
885
|
.select({
|
|
1250
886
|
id: concepts.id,
|
|
1251
887
|
planet1: concepts.planet1,
|
|
@@ -1278,7 +914,7 @@ export class ConceptService {
|
|
|
1278
914
|
`🔹 Extracted planet signs: ${planet1} -> ${planet1Sign}, ${planet2} -> ${planet2Sign}, ${planet3} -> ${planet3Sign}`
|
|
1279
915
|
);
|
|
1280
916
|
|
|
1281
|
-
let [conceptCombination] = await
|
|
917
|
+
let [conceptCombination] = await db
|
|
1282
918
|
.select({ id: conceptCombinations.id })
|
|
1283
919
|
.from(conceptCombinations)
|
|
1284
920
|
.where(
|
|
@@ -1294,7 +930,7 @@ export class ConceptService {
|
|
|
1294
930
|
`🔍 No existing conceptCombination found. Creating new entry for ${conceptSlug}:${combinationString}`
|
|
1295
931
|
);
|
|
1296
932
|
|
|
1297
|
-
const [insertedCombination] = await
|
|
933
|
+
const [insertedCombination] = await db
|
|
1298
934
|
.insert(conceptCombinations)
|
|
1299
935
|
.values({
|
|
1300
936
|
id: uuidv4(),
|
|
@@ -1338,7 +974,7 @@ export class ConceptService {
|
|
|
1338
974
|
const leonardoResponse = await this.context
|
|
1339
975
|
.api()
|
|
1340
976
|
.callLeonardo.generateImage({
|
|
1341
|
-
prompt:
|
|
977
|
+
prompt: conceptEntries[0].leonardoPrompt,
|
|
1342
978
|
width,
|
|
1343
979
|
height,
|
|
1344
980
|
...(controlNets.length > 0 ? { controlNets } : {}),
|
|
@@ -1354,7 +990,7 @@ export class ConceptService {
|
|
|
1354
990
|
|
|
1355
991
|
console.log(`✅ Leonardo Generation ID received: ${generationId}`);
|
|
1356
992
|
|
|
1357
|
-
await
|
|
993
|
+
await db.insert(generations).values({
|
|
1358
994
|
id: generationId,
|
|
1359
995
|
conceptCombinationId,
|
|
1360
996
|
type: 'concept_image',
|
|
@@ -1467,140 +1103,6 @@ export class ConceptService {
|
|
|
1467
1103
|
};
|
|
1468
1104
|
}
|
|
1469
1105
|
|
|
1470
|
-
// async regenerateDuplicateNames(limit?: number) {
|
|
1471
|
-
// console.log(`🔄 Processing duplicate names for regeneration...`);
|
|
1472
|
-
|
|
1473
|
-
// const { duplicateEntries, usedNamesEN, usedNamesPT } =
|
|
1474
|
-
// await this.findDuplicateNamesAndUsedNames(limit);
|
|
1475
|
-
|
|
1476
|
-
// if (duplicateEntries.length === 0) {
|
|
1477
|
-
// console.log(`🎉 No duplicates found. Nothing to regenerate.`);
|
|
1478
|
-
// return { message: 'No duplicates detected.' };
|
|
1479
|
-
// }
|
|
1480
|
-
|
|
1481
|
-
// for (const { name: oldNameEN, combinations } of duplicateEntries) {
|
|
1482
|
-
// for (let i = 1; i < combinations.length; i++) {
|
|
1483
|
-
// const [_, lang, conceptSlug, combinationString] =
|
|
1484
|
-
// combinations[i].split(':');
|
|
1485
|
-
|
|
1486
|
-
// if (!lang || !conceptSlug || !combinationString) {
|
|
1487
|
-
// console.warn(`⚠️ Invalid KV key format: ${combinations[i]}`);
|
|
1488
|
-
// continue;
|
|
1489
|
-
// }
|
|
1490
|
-
|
|
1491
|
-
// const kvKeyEN = buildConceptKVKey(
|
|
1492
|
-
// 'en-us',
|
|
1493
|
-
// conceptSlug as Concept,
|
|
1494
|
-
// combinationString
|
|
1495
|
-
// );
|
|
1496
|
-
// const kvKeyPT = buildConceptKVKey(
|
|
1497
|
-
// 'pt-br',
|
|
1498
|
-
// conceptSlug as Concept,
|
|
1499
|
-
// combinationString
|
|
1500
|
-
// );
|
|
1501
|
-
|
|
1502
|
-
// const kvDataEN = (await this.context.env.KV_CONCEPTS.get(
|
|
1503
|
-
// kvKeyEN,
|
|
1504
|
-
// 'json'
|
|
1505
|
-
// )) as {
|
|
1506
|
-
// name: string;
|
|
1507
|
-
// description: string;
|
|
1508
|
-
// };
|
|
1509
|
-
// const kvDataPT = (await this.context.env.KV_CONCEPTS.get(
|
|
1510
|
-
// kvKeyPT,
|
|
1511
|
-
// 'json'
|
|
1512
|
-
// )) as {
|
|
1513
|
-
// name: string;
|
|
1514
|
-
// };
|
|
1515
|
-
|
|
1516
|
-
// console.log(
|
|
1517
|
-
// `🎭 Regenerating name for: ${kvKeyEN} (Old Name: "${oldNameEN}")`
|
|
1518
|
-
// );
|
|
1519
|
-
|
|
1520
|
-
// let newNameEN: string | null = null;
|
|
1521
|
-
// let newNamePT: string | null = null;
|
|
1522
|
-
// let attempts = 0;
|
|
1523
|
-
// const maxAttempts = 3;
|
|
1524
|
-
|
|
1525
|
-
// while (attempts < maxAttempts) {
|
|
1526
|
-
// attempts++;
|
|
1527
|
-
|
|
1528
|
-
// const messages = this.context
|
|
1529
|
-
// .buildLLMMessages()
|
|
1530
|
-
// .regenerateConceptName({
|
|
1531
|
-
// oldNameEN: kvDataEN.name,
|
|
1532
|
-
// description: kvDataEN.description,
|
|
1533
|
-
// usedNamesEN: Array.from(usedNamesEN), // ✅ Ensure array format
|
|
1534
|
-
// });
|
|
1535
|
-
|
|
1536
|
-
// const aiResponse = await this.context
|
|
1537
|
-
// .api()
|
|
1538
|
-
// .callTogether.single(messages, {});
|
|
1539
|
-
// if (!aiResponse) {
|
|
1540
|
-
// console.warn(
|
|
1541
|
-
// `⚠️ AI failed to generate a new name for ${kvKeyEN}, skipping.`
|
|
1542
|
-
// );
|
|
1543
|
-
// break;
|
|
1544
|
-
// }
|
|
1545
|
-
|
|
1546
|
-
// // ✅ Parse AI response to extract both EN and PT names
|
|
1547
|
-
// const [generatedEN, generatedPT] = aiResponse
|
|
1548
|
-
// .split('\n')
|
|
1549
|
-
// .map((s) => s.trim());
|
|
1550
|
-
|
|
1551
|
-
// // ✅ Validate that the generated names are not duplicates
|
|
1552
|
-
// if (!usedNamesEN.has(generatedEN) && !usedNamesPT.has(generatedPT)) {
|
|
1553
|
-
// newNameEN = generatedEN;
|
|
1554
|
-
// newNamePT = generatedPT;
|
|
1555
|
-
// break; // ✅ Found unique names, exit retry loop
|
|
1556
|
-
// }
|
|
1557
|
-
|
|
1558
|
-
// console.warn(
|
|
1559
|
-
// `⚠️ AI suggested a duplicate name: EN - "${generatedEN}", PT - "${generatedPT}". Retrying... (Attempt ${attempts}/${maxAttempts})`
|
|
1560
|
-
// );
|
|
1561
|
-
// }
|
|
1562
|
-
|
|
1563
|
-
// if (!newNameEN || !newNamePT) {
|
|
1564
|
-
// console.error(
|
|
1565
|
-
// `🚨 Failed to generate a unique name for ${kvKeyEN} after ${maxAttempts} attempts.`
|
|
1566
|
-
// );
|
|
1567
|
-
// continue;
|
|
1568
|
-
// }
|
|
1569
|
-
|
|
1570
|
-
// console.log(
|
|
1571
|
-
// `✅ Storing new names for ${kvKeyEN}: EN - "${newNameEN}", PT - "${newNamePT}"`
|
|
1572
|
-
// );
|
|
1573
|
-
|
|
1574
|
-
// // ✅ Update KV with new names (EN)
|
|
1575
|
-
// Object.assign(kvDataEN, { name: newNameEN });
|
|
1576
|
-
// await this.context.env.KV_CONCEPTS.put(
|
|
1577
|
-
// kvKeyEN,
|
|
1578
|
-
// JSON.stringify(kvDataEN)
|
|
1579
|
-
// );
|
|
1580
|
-
|
|
1581
|
-
// // ✅ Update KV with new names (PT)
|
|
1582
|
-
// Object.assign(kvDataPT, { name: newNamePT });
|
|
1583
|
-
// await this.context.env.KV_CONCEPTS.put(
|
|
1584
|
-
// kvKeyPT,
|
|
1585
|
-
// JSON.stringify(kvDataPT)
|
|
1586
|
-
// );
|
|
1587
|
-
|
|
1588
|
-
// // ✅ Add new names to the used names sets to prevent future reuse
|
|
1589
|
-
// usedNamesEN.add(newNameEN);
|
|
1590
|
-
// usedNamesPT.add(newNamePT);
|
|
1591
|
-
|
|
1592
|
-
// // ✅ Stop if limit is reached
|
|
1593
|
-
// if (limit && --limit <= 0) {
|
|
1594
|
-
// console.log(`🎯 Limit reached, stopping further processing.`);
|
|
1595
|
-
// return { message: 'Regeneration limit reached.' };
|
|
1596
|
-
// }
|
|
1597
|
-
// }
|
|
1598
|
-
// }
|
|
1599
|
-
|
|
1600
|
-
// console.log(`🎉 Duplicate names regenerated successfully.`);
|
|
1601
|
-
// return { message: 'Duplicate names processed and regenerated.' };
|
|
1602
|
-
// }
|
|
1603
|
-
|
|
1604
1106
|
async findDuplicateNamesForConcept(conceptSlug: Concept) {
|
|
1605
1107
|
console.log(
|
|
1606
1108
|
`🔍 Fetching duplicate names from KV_CONCEPT_CACHE for ${conceptSlug}...`
|
|
@@ -2515,4 +2017,81 @@ export class ConceptService {
|
|
|
2515
2017
|
console.log(`🎉 Parsing complete. Total reports: ${reports.length}`);
|
|
2516
2018
|
return reports;
|
|
2517
2019
|
}
|
|
2020
|
+
|
|
2021
|
+
async ensureConceptDataComplete(
|
|
2022
|
+
conceptSlug: Concept,
|
|
2023
|
+
combinationString: string
|
|
2024
|
+
): Promise<boolean> {
|
|
2025
|
+
const db = this.context.drizzle();
|
|
2026
|
+
console.log(
|
|
2027
|
+
`🔍 Ensuring concept data complete for ${conceptSlug}:${combinationString}`
|
|
2028
|
+
);
|
|
2029
|
+
|
|
2030
|
+
// ✅ Check concepts_data for completeness (English for simplicity)
|
|
2031
|
+
let conceptDataEntry = (
|
|
2032
|
+
await db
|
|
2033
|
+
.select({
|
|
2034
|
+
leonardoPrompt: conceptsData.leonardoPrompt,
|
|
2035
|
+
postImages: conceptsData.postImages,
|
|
2036
|
+
reelImages: conceptsData.reelImages,
|
|
2037
|
+
})
|
|
2038
|
+
.from(conceptsData)
|
|
2039
|
+
.where(
|
|
2040
|
+
and(
|
|
2041
|
+
eq(conceptsData.conceptSlug, conceptSlug),
|
|
2042
|
+
eq(conceptsData.combination, combinationString),
|
|
2043
|
+
eq(conceptsData.language, 'en-us')
|
|
2044
|
+
)
|
|
2045
|
+
)
|
|
2046
|
+
.limit(1)
|
|
2047
|
+
.execute()
|
|
2048
|
+
)[0];
|
|
2049
|
+
|
|
2050
|
+
const hasImages =
|
|
2051
|
+
conceptDataEntry?.postImages &&
|
|
2052
|
+
conceptDataEntry?.reelImages &&
|
|
2053
|
+
JSON.parse(conceptDataEntry.postImages || '[]').length > 0 &&
|
|
2054
|
+
JSON.parse(conceptDataEntry.reelImages || '[]').length > 0;
|
|
2055
|
+
|
|
2056
|
+
// ✅ Generate leonardoPrompt if missing
|
|
2057
|
+
if (!conceptDataEntry?.leonardoPrompt) {
|
|
2058
|
+
console.log(
|
|
2059
|
+
`🚀 Generating Leonardo prompt for ${conceptSlug}:${combinationString}`
|
|
2060
|
+
);
|
|
2061
|
+
await this.generatePrompt(conceptSlug, combinationString);
|
|
2062
|
+
// Re-fetch updated data
|
|
2063
|
+
conceptDataEntry = (
|
|
2064
|
+
await db
|
|
2065
|
+
.select({
|
|
2066
|
+
leonardoPrompt: conceptsData.leonardoPrompt,
|
|
2067
|
+
postImages: conceptsData.postImages,
|
|
2068
|
+
reelImages: conceptsData.reelImages,
|
|
2069
|
+
})
|
|
2070
|
+
.from(conceptsData)
|
|
2071
|
+
.where(
|
|
2072
|
+
and(
|
|
2073
|
+
eq(conceptsData.conceptSlug, conceptSlug),
|
|
2074
|
+
eq(conceptsData.combination, combinationString),
|
|
2075
|
+
eq(conceptsData.language, 'en-us')
|
|
2076
|
+
)
|
|
2077
|
+
)
|
|
2078
|
+
.limit(1)
|
|
2079
|
+
.execute()
|
|
2080
|
+
)[0];
|
|
2081
|
+
}
|
|
2082
|
+
|
|
2083
|
+
// ✅ Generate images if missing
|
|
2084
|
+
if (!hasImages) {
|
|
2085
|
+
console.log(
|
|
2086
|
+
`🖼️ Generating images for ${conceptSlug}:${combinationString}`
|
|
2087
|
+
);
|
|
2088
|
+
await this.generateImages(conceptSlug, combinationString);
|
|
2089
|
+
return false; // Images are being generated, not complete yet
|
|
2090
|
+
}
|
|
2091
|
+
|
|
2092
|
+
console.log(
|
|
2093
|
+
`✅ Concept data complete for ${conceptSlug}:${combinationString}`
|
|
2094
|
+
);
|
|
2095
|
+
return true; // Everything is ready
|
|
2096
|
+
}
|
|
2518
2097
|
}
|