@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<void> {
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
- // ✅ Build KV keys for both languages
332
- const kvKeyEN = buildConceptKVKey('en-us', conceptSlug, combinationString);
333
- const kvKeyPT = buildConceptKVKey('pt-br', conceptSlug, combinationString);
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
- // Retrieve existing KV data
336
- const conceptEN = await this.getKVConcept(kvKeyEN);
337
- const conceptPT = await this.getKVConcept(kvKeyPT);
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; // Skip regeneration
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
- // Store the generated prompt in both languages
378
- conceptEN.leonardoPrompt = response.trim();
379
- conceptPT.leonardoPrompt = response.trim();
408
+ const generatedPrompt = response.trim();
380
409
 
381
- await Promise.all([
382
- this.context.kvConceptsStore().put(kvKeyEN, JSON.stringify(conceptEN)),
383
- this.context.kvConceptsStore().put(kvKeyPT, JSON.stringify(conceptPT)),
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 drizzle = this.context.drizzle();
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}, language: ${language}`
861
+ `🚀 Queuing image generation for concept: ${conceptSlug}, combination: ${combinationString}`
1239
862
  );
1240
863
 
1241
- const concept = await this.getKVConcept(kvKey);
1242
- if (!concept.leonardoPrompt) {
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 drizzle
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 drizzle
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 drizzle
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: concept.leonardoPrompt,
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 drizzle.insert(generations).values({
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
  }