@zodic/shared 0.0.272 → 0.0.273
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,432 +846,6 @@ 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
|
*/
|
|
@@ -1229,23 +854,35 @@ export class ConceptService {
|
|
|
1229
854
|
conceptSlug: Concept,
|
|
1230
855
|
combinationString: string
|
|
1231
856
|
): Promise<void> {
|
|
1232
|
-
const
|
|
857
|
+
const db = this.context.drizzle();
|
|
1233
858
|
const { width, height } = sizes['alchemy']['post4:5'];
|
|
1234
859
|
const { generations, conceptCombinations, concepts } = schema;
|
|
1235
860
|
|
|
1236
|
-
const kvKey = buildConceptKVKey(language, conceptSlug, combinationString);
|
|
1237
861
|
console.log(
|
|
1238
862
|
`🚀 Queuing image generation for concept: ${conceptSlug}, combination: ${combinationString}, language: ${language}`
|
|
1239
863
|
);
|
|
1240
864
|
|
|
1241
|
-
const
|
|
1242
|
-
|
|
865
|
+
const conceptEntries = await db
|
|
866
|
+
.select({
|
|
867
|
+
leonardoPrompt: conceptsData.leonardoPrompt,
|
|
868
|
+
})
|
|
869
|
+
.from(conceptsData)
|
|
870
|
+
.where(
|
|
871
|
+
and(
|
|
872
|
+
eq(conceptsData.conceptSlug, conceptSlug),
|
|
873
|
+
eq(conceptsData.combination, combinationString),
|
|
874
|
+
eq(conceptsData.language, language) // ✅ Fetch both languages
|
|
875
|
+
)
|
|
876
|
+
)
|
|
877
|
+
.limit(1);
|
|
878
|
+
|
|
879
|
+
if (!conceptEntries[0].leonardoPrompt) {
|
|
1243
880
|
throw new Error(
|
|
1244
881
|
`❌ Leonardo prompt must be populated before generating images for concept: ${conceptSlug}`
|
|
1245
882
|
);
|
|
1246
883
|
}
|
|
1247
884
|
|
|
1248
|
-
const conceptRecord = await
|
|
885
|
+
const conceptRecord = await db
|
|
1249
886
|
.select({
|
|
1250
887
|
id: concepts.id,
|
|
1251
888
|
planet1: concepts.planet1,
|
|
@@ -1278,7 +915,7 @@ export class ConceptService {
|
|
|
1278
915
|
`🔹 Extracted planet signs: ${planet1} -> ${planet1Sign}, ${planet2} -> ${planet2Sign}, ${planet3} -> ${planet3Sign}`
|
|
1279
916
|
);
|
|
1280
917
|
|
|
1281
|
-
let [conceptCombination] = await
|
|
918
|
+
let [conceptCombination] = await db
|
|
1282
919
|
.select({ id: conceptCombinations.id })
|
|
1283
920
|
.from(conceptCombinations)
|
|
1284
921
|
.where(
|
|
@@ -1294,7 +931,7 @@ export class ConceptService {
|
|
|
1294
931
|
`🔍 No existing conceptCombination found. Creating new entry for ${conceptSlug}:${combinationString}`
|
|
1295
932
|
);
|
|
1296
933
|
|
|
1297
|
-
const [insertedCombination] = await
|
|
934
|
+
const [insertedCombination] = await db
|
|
1298
935
|
.insert(conceptCombinations)
|
|
1299
936
|
.values({
|
|
1300
937
|
id: uuidv4(),
|
|
@@ -1338,7 +975,7 @@ export class ConceptService {
|
|
|
1338
975
|
const leonardoResponse = await this.context
|
|
1339
976
|
.api()
|
|
1340
977
|
.callLeonardo.generateImage({
|
|
1341
|
-
prompt:
|
|
978
|
+
prompt: conceptEntries[0].leonardoPrompt,
|
|
1342
979
|
width,
|
|
1343
980
|
height,
|
|
1344
981
|
...(controlNets.length > 0 ? { controlNets } : {}),
|
|
@@ -1354,7 +991,7 @@ export class ConceptService {
|
|
|
1354
991
|
|
|
1355
992
|
console.log(`✅ Leonardo Generation ID received: ${generationId}`);
|
|
1356
993
|
|
|
1357
|
-
await
|
|
994
|
+
await db.insert(generations).values({
|
|
1358
995
|
id: generationId,
|
|
1359
996
|
conceptCombinationId,
|
|
1360
997
|
type: 'concept_image',
|
|
@@ -1467,140 +1104,6 @@ export class ConceptService {
|
|
|
1467
1104
|
};
|
|
1468
1105
|
}
|
|
1469
1106
|
|
|
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
1107
|
async findDuplicateNamesForConcept(conceptSlug: Concept) {
|
|
1605
1108
|
console.log(
|
|
1606
1109
|
`🔍 Fetching duplicate names from KV_CONCEPT_CACHE for ${conceptSlug}...`
|