@zodic/shared 0.0.210 β†’ 0.0.212

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.
@@ -29,67 +29,80 @@ export class ConceptService {
29
29
  combinationString: string,
30
30
  override: boolean = false
31
31
  ): Promise<void> {
32
- console.log(`πŸš€ Generating basic info for concept: ${conceptSlug}, combination: ${combinationString}, override: ${override}`);
33
-
32
+ console.log(
33
+ `πŸš€ Generating basic info for concept: ${conceptSlug}, combination: ${combinationString}, override: ${override}`
34
+ );
35
+
34
36
  const kvStore = this.context.kvConceptsStore();
35
37
  const kvFailuresStore = this.context.kvConceptFailuresStore();
36
38
  const kvKeyEN = buildConceptKVKey('en-us', conceptSlug, combinationString);
37
39
  const kvKeyPT = buildConceptKVKey('pt-br', conceptSlug, combinationString);
38
-
40
+
39
41
  // βœ… Use Durable Object stub
40
42
  const id = this.context.env.CONCEPT_NAMES_DO.idFromName(conceptSlug);
41
43
  const stub = this.context.env.CONCEPT_NAMES_DO.get(id);
42
-
44
+
43
45
  console.log(`πŸ“‘ Fetching existing KV data for ${conceptSlug}...`);
44
46
  const existingEN = await this.getKVConcept(kvKeyEN);
45
47
  const existingPT = await this.getKVConcept(kvKeyPT);
46
-
48
+
47
49
  if (!override && existingEN.name && existingPT.name) {
48
50
  console.log(`⚑ Basic info already exists for ${conceptSlug}, skipping.`);
49
51
  return;
50
52
  }
51
-
53
+
52
54
  let attempts = 0;
53
55
  const maxAttempts = 3;
54
56
  const MAX_NAME_LENGTH = 44;
55
-
57
+
56
58
  while (attempts < maxAttempts) {
57
59
  let phase = 'generation';
58
60
  try {
59
61
  attempts++;
60
- console.log(`πŸ”„ Attempt ${attempts} to generate basic info for ${conceptSlug}...`);
61
-
62
+ console.log(
63
+ `πŸ”„ Attempt ${attempts} to generate basic info for ${conceptSlug}...`
64
+ );
65
+
62
66
  let allNamesEN: string[] = [];
63
67
  let allNamesPT: string[] = [];
64
68
  const response = await stub.fetch(`https://internal/names`);
65
-
69
+
66
70
  if (response.ok) {
67
- const data = (await response.json()) as { 'en-us': string[]; 'pt-br': string[] };
71
+ const data = (await response.json()) as {
72
+ 'en-us': string[];
73
+ 'pt-br': string[];
74
+ };
68
75
  allNamesEN = data['en-us'] || [];
69
76
  allNamesPT = data['pt-br'] || [];
70
77
  }
71
-
78
+
72
79
  console.log(`✏️ Generating new name...`);
73
- const messages = this.context.buildLLMMessages().generateConceptBasicInfo({
74
- combination: combinationString,
75
- conceptSlug,
76
- existingNames: allNamesEN.length > 100 ? allNamesEN.slice(-100) : allNamesEN,
77
- });
78
-
79
- let aiResponse = await this.context.api().callTogether.single(messages, {});
80
+ const messages = this.context
81
+ .buildLLMMessages()
82
+ .generateConceptBasicInfo({
83
+ combination: combinationString,
84
+ conceptSlug,
85
+ existingNames:
86
+ allNamesEN.length > 100 ? allNamesEN.slice(-100) : allNamesEN,
87
+ });
88
+
89
+ let aiResponse = await this.context
90
+ .api()
91
+ .callTogether.single(messages, {});
80
92
  if (!aiResponse) throw new Error(`❌ AI returned an empty response`);
81
-
93
+
82
94
  phase = 'cleaning';
83
95
  aiResponse = this.cleanAIResponse(aiResponse);
84
96
 
85
- console.log("!-- aiResponse -> ", aiResponse)
86
- console.log("!-- aiResponse length -> ", aiResponse.length)
87
-
97
+ console.log('!-- aiResponse -> ', aiResponse);
98
+ console.log('!-- aiResponse length -> ', aiResponse.length);
99
+
88
100
  phase = 'parsing';
89
- let { nameEN, descriptionEN, poemEN, namePT, descriptionPT, poemPT } = this.parseBasicInfoResponse(aiResponse, conceptSlug);
90
-
101
+ let { nameEN, descriptionEN, poemEN, namePT, descriptionPT, poemPT } =
102
+ this.parseBasicInfoResponse(aiResponse, conceptSlug);
103
+
91
104
  console.log(`🎭 Generated names: EN - "${nameEN}", PT - "${namePT}"`);
92
-
105
+
93
106
  // βœ… Forcefully trim the name to exactly 5 words if it's too long
94
107
  const enforceWordLimit = (name: string): string => {
95
108
  const words = name.split(/\s+/);
@@ -98,40 +111,66 @@ export class ConceptService {
98
111
  }
99
112
  return name;
100
113
  };
101
-
114
+
102
115
  nameEN = enforceWordLimit(nameEN);
103
116
  namePT = enforceWordLimit(namePT);
104
-
117
+
105
118
  console.log(`βœ‚οΈ Trimmed names: EN - "${nameEN}", PT - "${namePT}"`);
106
-
119
+
107
120
  // βœ… Check uniqueness before storing
108
121
  if (allNamesEN.includes(nameEN) || allNamesPT.includes(namePT)) {
109
- console.warn(`⚠️ Duplicate Name Detected: "${nameEN}" or "${namePT}"`);
122
+ console.warn(
123
+ `⚠️ Duplicate Name Detected: "${nameEN}" or "${namePT}"`
124
+ );
110
125
  if (attempts >= maxAttempts) {
111
- console.log(`🚨 Max attempts reached. Storing name despite duplicate.`);
126
+ console.log(
127
+ `🚨 Max attempts reached. Storing name despite duplicate.`
128
+ );
112
129
  await kvFailuresStore.put(
113
130
  `failures:duplicates:${conceptSlug}:${combinationString}`,
114
- JSON.stringify({ nameEN, namePT, attempts, conceptSlug, combinationString, timestamp: new Date().toISOString() })
131
+ JSON.stringify({
132
+ nameEN,
133
+ namePT,
134
+ attempts,
135
+ conceptSlug,
136
+ combinationString,
137
+ timestamp: new Date().toISOString(),
138
+ })
115
139
  );
116
140
  break;
117
141
  }
118
142
  continue;
119
143
  }
120
-
144
+
121
145
  console.log(`πŸ“ Storing names in Durable Object...`);
122
146
  // await stub.fetch(`https://internal/add-name`, { method: 'POST', body: JSON.stringify({ language: 'en-us', name: nameEN }), headers: { 'Content-Type': 'application/json' } });
123
147
  // await stub.fetch(`https://internal/add-name`, { method: 'POST', body: JSON.stringify({ language: 'pt-br', name: namePT }), headers: { 'Content-Type': 'application/json' } });
124
-
125
- Object.assign(existingEN, { name: nameEN, description: descriptionEN, poem: poemEN, status: 'idle' });
148
+
149
+ Object.assign(existingEN, {
150
+ name: nameEN,
151
+ description: descriptionEN,
152
+ poem: poemEN,
153
+ status: 'idle',
154
+ });
126
155
  await kvStore.put(kvKeyEN, JSON.stringify(existingEN));
127
-
128
- Object.assign(existingPT, { name: namePT, description: descriptionPT, poem: poemPT, status: 'idle' });
156
+
157
+ Object.assign(existingPT, {
158
+ name: namePT,
159
+ description: descriptionPT,
160
+ poem: poemPT,
161
+ status: 'idle',
162
+ });
129
163
  await kvStore.put(kvKeyPT, JSON.stringify(existingPT));
130
-
131
- console.log(`βœ… Stored basic info for ${conceptSlug}, combination: ${combinationString}.`);
164
+
165
+ console.log(
166
+ `βœ… Stored basic info for ${conceptSlug}, combination: ${combinationString}.`
167
+ );
132
168
  return;
133
169
  } catch (error) {
134
- console.error(`❌ Attempt ${attempts} failed at phase: ${phase}`, error);
170
+ console.error(
171
+ `❌ Attempt ${attempts} failed at phase: ${phase}`,
172
+ error
173
+ );
135
174
  }
136
175
  }
137
176
  }
@@ -157,18 +196,22 @@ export class ConceptService {
157
196
  descriptionPT: string;
158
197
  poemPT: string[];
159
198
  } {
160
- console.log('πŸ“Œ Parsing basic info response from ChatGPT:', response);
199
+ console.log('πŸ“Œ Received AI response for parsing:', response);
161
200
 
162
201
  // βœ… More flexible regex to handle variations in formatting
202
+ console.log('πŸ”Ž Attempting to match English section...');
163
203
  const enMatch = response.match(
164
204
  /EN:\s*(?:β€’\s*)?Name:\s*(.+?)\s*(?:β€’\s*)?Description:\s*([\s\S]+?)\s*(?:β€’\s*)?Poetic Passage:\s*([\s\S]+?)\s*(?=PT:|$)/
165
205
  );
206
+
207
+ console.log('πŸ”Ž Attempting to match Portuguese section...');
166
208
  const ptMatch = response.match(
167
209
  /PT:\s*(?:β€’\s*)?Nome:\s*(.+?)\s*(?:β€’\s*)?DescriΓ§Γ£o:\s*([\s\S]+?)\s*(?:β€’\s*)?Passagem PoΓ©tica:\s*([\s\S]+)/
168
210
  );
169
211
 
170
212
  if (!enMatch || !ptMatch) {
171
213
  console.error('❌ Invalid basic info response format:', response);
214
+ console.log('❌ Regex match results:', { enMatch, ptMatch });
172
215
  throw new Error('Invalid basic info response format');
173
216
  }
174
217
 
@@ -176,14 +219,15 @@ export class ConceptService {
176
219
  const cleanText = (text: string) => text.trim().replace(/\*/g, '');
177
220
 
178
221
  // βœ… Parse and clean extracted content
222
+ console.log('βœ‚οΈ Extracting and cleaning English content...');
179
223
  const nameEN = cleanText(enMatch[1]);
180
224
  const descriptionEN = cleanText(enMatch[2]);
181
225
  const poemEN = enMatch[3]
182
226
  .trim()
183
227
  .split(/\n+/)
184
228
  .map((line) => cleanText(line));
185
-
186
229
 
230
+ console.log('βœ‚οΈ Extracting and cleaning Portuguese content...');
187
231
  const namePT = cleanText(ptMatch[1]);
188
232
  const descriptionPT = cleanText(ptMatch[2]);
189
233
  const poemPT = ptMatch[3]
@@ -191,6 +235,15 @@ export class ConceptService {
191
235
  .split(/\n+/)
192
236
  .map((line) => cleanText(line));
193
237
 
238
+ console.log('πŸ“Œ Extracted values before processing:', {
239
+ nameEN,
240
+ descriptionEN,
241
+ poemEN,
242
+ namePT,
243
+ descriptionPT,
244
+ poemPT,
245
+ });
246
+
194
247
  // βœ… Determine appropriate replacement text based on conceptSlug
195
248
  const conceptPlaceholder = `this ${
196
249
  conceptSlug.charAt(0).toUpperCase() + conceptSlug.slice(1)
@@ -210,15 +263,18 @@ export class ConceptService {
210
263
  };
211
264
 
212
265
  // βœ… Ensure the concept name is not repeated in the description
266
+ console.log('πŸ”„ Replacing concept name in descriptions...');
213
267
  const cleanedDescriptionEN = replaceConceptName(descriptionEN, nameEN);
214
268
  const cleanedDescriptionPT = replaceConceptName(descriptionPT, namePT);
215
269
 
216
270
  // βœ… If the name appears in the poem, trigger a retry
271
+ console.log('πŸ” Checking for concept name in poems...');
217
272
  if (
218
273
  poemEN.some((line) => line.includes(nameEN)) ||
219
274
  poemPT.some((line) => line.includes(namePT))
220
275
  ) {
221
276
  console.error('❌ Concept name detected in poem, triggering a retry.');
277
+ console.log('❌ Problematic lines:', { poemEN, poemPT });
222
278
  throw new Error('Concept name found in poem, regenerating response.');
223
279
  }
224
280
 
@@ -791,361 +847,319 @@ export class ConceptService {
791
847
  };
792
848
  }
793
849
 
794
- async regenerateDuplicateNames(limit?: number) {
795
- console.log(`πŸ”„ Processing duplicate names for regeneration...`);
796
-
797
- const { duplicateEntries, usedNamesEN, usedNamesPT } =
798
- await this.findDuplicateNamesAndUsedNames(limit);
799
-
800
- if (duplicateEntries.length === 0) {
801
- console.log(`πŸŽ‰ No duplicates found. Nothing to regenerate.`);
802
- return { message: 'No duplicates detected.' };
803
- }
804
-
805
- for (const { name: oldNameEN, combinations } of duplicateEntries) {
806
- for (let i = 1; i < combinations.length; i++) {
807
- const [_, lang, conceptSlug, combinationString] =
808
- combinations[i].split(':');
809
-
810
- if (!lang || !conceptSlug || !combinationString) {
811
- console.warn(`⚠️ Invalid KV key format: ${combinations[i]}`);
812
- continue;
813
- }
814
-
815
- const kvKeyEN = buildConceptKVKey(
816
- 'en-us',
817
- conceptSlug as Concept,
818
- combinationString
819
- );
820
- const kvKeyPT = buildConceptKVKey(
821
- 'pt-br',
822
- conceptSlug as Concept,
823
- combinationString
824
- );
825
-
826
- const kvDataEN = (await this.context.env.KV_CONCEPTS.get(
827
- kvKeyEN,
828
- 'json'
829
- )) as {
830
- name: string;
831
- description: string;
832
- };
833
- const kvDataPT = (await this.context.env.KV_CONCEPTS.get(
834
- kvKeyPT,
835
- 'json'
836
- )) as {
837
- name: string;
838
- };
839
-
840
- console.log(
841
- `🎭 Regenerating name for: ${kvKeyEN} (Old Name: "${oldNameEN}")`
842
- );
843
-
844
- let newNameEN: string | null = null;
845
- let newNamePT: string | null = null;
846
- let attempts = 0;
847
- const maxAttempts = 3;
848
-
849
- while (attempts < maxAttempts) {
850
- attempts++;
851
-
852
- const messages = this.context
853
- .buildLLMMessages()
854
- .regenerateConceptName({
855
- oldNameEN: kvDataEN.name,
856
- description: kvDataEN.description,
857
- usedNamesEN: Array.from(usedNamesEN), // βœ… Ensure array format
858
- });
859
-
860
- const aiResponse = await this.context
861
- .api()
862
- .callTogether.single(messages, {});
863
- if (!aiResponse) {
864
- console.warn(
865
- `⚠️ AI failed to generate a new name for ${kvKeyEN}, skipping.`
866
- );
867
- break;
868
- }
869
-
870
- // βœ… Parse AI response to extract both EN and PT names
871
- const [generatedEN, generatedPT] = aiResponse
872
- .split('\n')
873
- .map((s) => s.trim());
874
-
875
- // βœ… Validate that the generated names are not duplicates
876
- if (!usedNamesEN.has(generatedEN) && !usedNamesPT.has(generatedPT)) {
877
- newNameEN = generatedEN;
878
- newNamePT = generatedPT;
879
- break; // βœ… Found unique names, exit retry loop
880
- }
881
-
882
- console.warn(
883
- `⚠️ AI suggested a duplicate name: EN - "${generatedEN}", PT - "${generatedPT}". Retrying... (Attempt ${attempts}/${maxAttempts})`
884
- );
885
- }
886
-
887
- if (!newNameEN || !newNamePT) {
888
- console.error(
889
- `🚨 Failed to generate a unique name for ${kvKeyEN} after ${maxAttempts} attempts.`
890
- );
891
- continue;
892
- }
893
-
894
- console.log(
895
- `βœ… Storing new names for ${kvKeyEN}: EN - "${newNameEN}", PT - "${newNamePT}"`
896
- );
897
-
898
- // βœ… Update KV with new names (EN)
899
- Object.assign(kvDataEN, { name: newNameEN });
900
- await this.context.env.KV_CONCEPTS.put(
901
- kvKeyEN,
902
- JSON.stringify(kvDataEN)
903
- );
904
-
905
- // βœ… Update KV with new names (PT)
906
- Object.assign(kvDataPT, { name: newNamePT });
907
- await this.context.env.KV_CONCEPTS.put(
908
- kvKeyPT,
909
- JSON.stringify(kvDataPT)
910
- );
911
-
912
- // βœ… Add new names to the used names sets to prevent future reuse
913
- usedNamesEN.add(newNameEN);
914
- usedNamesPT.add(newNamePT);
915
-
916
- // βœ… Stop if limit is reached
917
- if (limit && --limit <= 0) {
918
- console.log(`🎯 Limit reached, stopping further processing.`);
919
- return { message: 'Regeneration limit reached.' };
920
- }
921
- }
922
- }
923
-
924
- console.log(`πŸŽ‰ Duplicate names regenerated successfully.`);
925
- return { message: 'Duplicate names processed and regenerated.' };
926
- }
927
-
928
- async findDuplicateNamesAndUsedNamesForConcept(
929
- conceptSlug: Concept,
930
- limit?: number
931
- ) {
850
+ // async regenerateDuplicateNames(limit?: number) {
851
+ // console.log(`πŸ”„ Processing duplicate names for regeneration...`);
852
+
853
+ // const { duplicateEntries, usedNamesEN, usedNamesPT } =
854
+ // await this.findDuplicateNamesAndUsedNames(limit);
855
+
856
+ // if (duplicateEntries.length === 0) {
857
+ // console.log(`πŸŽ‰ No duplicates found. Nothing to regenerate.`);
858
+ // return { message: 'No duplicates detected.' };
859
+ // }
860
+
861
+ // for (const { name: oldNameEN, combinations } of duplicateEntries) {
862
+ // for (let i = 1; i < combinations.length; i++) {
863
+ // const [_, lang, conceptSlug, combinationString] =
864
+ // combinations[i].split(':');
865
+
866
+ // if (!lang || !conceptSlug || !combinationString) {
867
+ // console.warn(`⚠️ Invalid KV key format: ${combinations[i]}`);
868
+ // continue;
869
+ // }
870
+
871
+ // const kvKeyEN = buildConceptKVKey(
872
+ // 'en-us',
873
+ // conceptSlug as Concept,
874
+ // combinationString
875
+ // );
876
+ // const kvKeyPT = buildConceptKVKey(
877
+ // 'pt-br',
878
+ // conceptSlug as Concept,
879
+ // combinationString
880
+ // );
881
+
882
+ // const kvDataEN = (await this.context.env.KV_CONCEPTS.get(
883
+ // kvKeyEN,
884
+ // 'json'
885
+ // )) as {
886
+ // name: string;
887
+ // description: string;
888
+ // };
889
+ // const kvDataPT = (await this.context.env.KV_CONCEPTS.get(
890
+ // kvKeyPT,
891
+ // 'json'
892
+ // )) as {
893
+ // name: string;
894
+ // };
895
+
896
+ // console.log(
897
+ // `🎭 Regenerating name for: ${kvKeyEN} (Old Name: "${oldNameEN}")`
898
+ // );
899
+
900
+ // let newNameEN: string | null = null;
901
+ // let newNamePT: string | null = null;
902
+ // let attempts = 0;
903
+ // const maxAttempts = 3;
904
+
905
+ // while (attempts < maxAttempts) {
906
+ // attempts++;
907
+
908
+ // const messages = this.context
909
+ // .buildLLMMessages()
910
+ // .regenerateConceptName({
911
+ // oldNameEN: kvDataEN.name,
912
+ // description: kvDataEN.description,
913
+ // usedNamesEN: Array.from(usedNamesEN), // βœ… Ensure array format
914
+ // });
915
+
916
+ // const aiResponse = await this.context
917
+ // .api()
918
+ // .callTogether.single(messages, {});
919
+ // if (!aiResponse) {
920
+ // console.warn(
921
+ // `⚠️ AI failed to generate a new name for ${kvKeyEN}, skipping.`
922
+ // );
923
+ // break;
924
+ // }
925
+
926
+ // // βœ… Parse AI response to extract both EN and PT names
927
+ // const [generatedEN, generatedPT] = aiResponse
928
+ // .split('\n')
929
+ // .map((s) => s.trim());
930
+
931
+ // // βœ… Validate that the generated names are not duplicates
932
+ // if (!usedNamesEN.has(generatedEN) && !usedNamesPT.has(generatedPT)) {
933
+ // newNameEN = generatedEN;
934
+ // newNamePT = generatedPT;
935
+ // break; // βœ… Found unique names, exit retry loop
936
+ // }
937
+
938
+ // console.warn(
939
+ // `⚠️ AI suggested a duplicate name: EN - "${generatedEN}", PT - "${generatedPT}". Retrying... (Attempt ${attempts}/${maxAttempts})`
940
+ // );
941
+ // }
942
+
943
+ // if (!newNameEN || !newNamePT) {
944
+ // console.error(
945
+ // `🚨 Failed to generate a unique name for ${kvKeyEN} after ${maxAttempts} attempts.`
946
+ // );
947
+ // continue;
948
+ // }
949
+
950
+ // console.log(
951
+ // `βœ… Storing new names for ${kvKeyEN}: EN - "${newNameEN}", PT - "${newNamePT}"`
952
+ // );
953
+
954
+ // // βœ… Update KV with new names (EN)
955
+ // Object.assign(kvDataEN, { name: newNameEN });
956
+ // await this.context.env.KV_CONCEPTS.put(
957
+ // kvKeyEN,
958
+ // JSON.stringify(kvDataEN)
959
+ // );
960
+
961
+ // // βœ… Update KV with new names (PT)
962
+ // Object.assign(kvDataPT, { name: newNamePT });
963
+ // await this.context.env.KV_CONCEPTS.put(
964
+ // kvKeyPT,
965
+ // JSON.stringify(kvDataPT)
966
+ // );
967
+
968
+ // // βœ… Add new names to the used names sets to prevent future reuse
969
+ // usedNamesEN.add(newNameEN);
970
+ // usedNamesPT.add(newNamePT);
971
+
972
+ // // βœ… Stop if limit is reached
973
+ // if (limit && --limit <= 0) {
974
+ // console.log(`🎯 Limit reached, stopping further processing.`);
975
+ // return { message: 'Regeneration limit reached.' };
976
+ // }
977
+ // }
978
+ // }
979
+
980
+ // console.log(`πŸŽ‰ Duplicate names regenerated successfully.`);
981
+ // return { message: 'Duplicate names processed and regenerated.' };
982
+ // }
983
+
984
+ async findDuplicateNamesForConcept(conceptSlug: Concept) {
932
985
  console.log(
933
- `πŸ” Scanning KV for duplicate names in concept: ${conceptSlug}...`
986
+ `πŸ” Fetching duplicate names from KV_CONCEPT_CACHE for ${conceptSlug}...`
934
987
  );
935
988
 
936
- const kvStore = this.context.env.KV_CONCEPTS;
937
- let cursor: string | undefined = undefined;
938
- let nameUsageMapEN: Record<string, string[]> = {}; // { enName: [combination1, combination2] }
939
- let usedNamesEN = new Set<string>(); // βœ… All used EN names
940
- let usedNamesPT = new Set<string>(); // βœ… All used PT names
941
- let totalKeysScanned = 0;
942
-
943
- do {
944
- const response: KVNamespaceListResult<KVConcept> = (await kvStore.list({
945
- prefix: `concepts:en-us:${conceptSlug}:`, // βœ… Fetch only keys for this concept
946
- cursor,
947
- })) as KVNamespaceListResult<KVConcept, string>;
948
-
949
- const {
950
- keys,
951
- cursor: newCursor,
952
- }: { keys: KVNamespaceListKey<KVConcept>[]; cursor?: string } = response;
953
-
954
- totalKeysScanned += keys.length;
955
-
956
- for (const key of keys) {
957
- const kvDataEN = (await kvStore.get(key.name, 'json')) as {
958
- name: string;
959
- };
960
- const kvKeyPT = key.name.replace(':en-us:', ':pt-br:'); // βœ… Get corresponding PT key
961
- const kvDataPT = (await kvStore.get(kvKeyPT, 'json')) as {
962
- name: string;
963
- };
989
+ const kvCacheStore = this.context.env.KV_CONCEPT_CACHE;
964
990
 
965
- if (kvDataEN?.name) {
966
- usedNamesEN.add(kvDataEN.name); // βœ… Store all used EN names
991
+ // βœ… Fetch the cached concept name mappings
992
+ const nameMap: Record<string, string[]> =
993
+ (await kvCacheStore.get(
994
+ `cache:concepts:en-us:${conceptSlug}:0`,
995
+ 'json'
996
+ )) || {};
967
997
 
968
- if (!nameUsageMapEN[kvDataEN.name]) {
969
- nameUsageMapEN[kvDataEN.name] = [];
970
- }
971
- nameUsageMapEN[kvDataEN.name].push(key.name); // Store the key for that name
972
- }
998
+ let duplicateKeys: string[] = [];
999
+ let usedNamesEN = new Set<string>();
1000
+ let usedNamesPT = new Set<string>();
973
1001
 
974
- if (kvDataPT?.name) {
975
- usedNamesPT.add(kvDataPT.name); // βœ… Store all used PT names
976
- }
1002
+ for (const [name, keys] of Object.entries(nameMap)) {
1003
+ if (keys.length > 1) {
1004
+ duplicateKeys.push(...keys.slice(1)); // βœ… Store all but the first occurrence
977
1005
  }
1006
+ usedNamesEN.add(name);
1007
+ }
978
1008
 
979
- cursor = newCursor;
980
- } while (cursor && (!limit || totalKeysScanned < limit));
1009
+ // βœ… Fetch PT-BR version from cache
1010
+ const nameMapPT: Record<string, string[]> =
1011
+ (await kvCacheStore.get(
1012
+ `cache:concepts:pt-br:${conceptSlug}:0`,
1013
+ 'json'
1014
+ )) || {};
981
1015
 
982
- // βœ… Find names used more than once (EN)
983
- let duplicateENNames = Object.entries(nameUsageMapEN)
984
- .filter(([_, combinations]) => combinations.length > 1)
985
- .map(([name, combinations]) => ({
986
- name,
987
- combinations,
988
- }));
989
-
990
- // βœ… If limit is set, trim duplicates list
991
- if (limit && duplicateENNames.length > limit) {
992
- duplicateENNames = duplicateENNames.slice(0, limit);
1016
+ for (const name of Object.keys(nameMapPT)) {
1017
+ usedNamesPT.add(name);
993
1018
  }
994
1019
 
995
1020
  console.log(
996
- `βœ… Found ${duplicateENNames.length} duplicate EN names for concept "${conceptSlug}" out of ${totalKeysScanned} entries.`
1021
+ `βœ… Found ${duplicateKeys.length} duplicate keys for ${conceptSlug}.`
997
1022
  );
998
1023
 
999
1024
  return {
1000
- duplicateEntries: duplicateENNames, // βœ… List of EN names that need regeneration
1001
- usedNamesEN, // βœ… Set of all unique EN names
1002
- usedNamesPT, // βœ… Set of all unique PT names
1025
+ duplicateKeys, // βœ… List of keys that need new names
1026
+ usedNamesEN, // βœ… Set of all used EN names
1027
+ usedNamesPT, // βœ… Set of all used PT names
1003
1028
  };
1004
1029
  }
1005
1030
 
1006
- async regenerateDuplicateNamesForConcept(
1007
- conceptSlug: Concept,
1008
- limit?: number
1009
- ) {
1010
- console.log(`πŸ”„ Processing duplicate names for concept: ${conceptSlug}...`);
1011
-
1012
- const { duplicateEntries, usedNamesEN, usedNamesPT } =
1013
- await this.findDuplicateNamesAndUsedNamesForConcept(conceptSlug, limit);
1014
-
1015
- if (duplicateEntries.length === 0) {
1016
- console.log(
1017
- `πŸŽ‰ No duplicates found for concept: ${conceptSlug}. Nothing to regenerate.`
1018
- );
1031
+ async regenerateDuplicateNamesForConcept(conceptSlug: Concept) {
1032
+ console.log(`πŸ”„ [START] Regenerating duplicate names for concept: ${conceptSlug}`);
1033
+
1034
+ const kvCacheStore = this.context.env.KV_CONCEPT_CACHE;
1035
+ const kvStore = this.context.env.KV_CONCEPTS;
1036
+
1037
+ // βœ… Get duplicate keys and used names
1038
+ const { duplicateKeys, usedNamesEN, usedNamesPT } =
1039
+ await this.findDuplicateNamesForConcept(conceptSlug);
1040
+
1041
+ if (duplicateKeys.length === 0) {
1042
+ console.log(`πŸŽ‰ No duplicates found for ${conceptSlug}.`);
1019
1043
  return { message: `No duplicates detected for ${conceptSlug}.` };
1020
1044
  }
1021
-
1022
- for (const { name: oldNameEN, combinations } of duplicateEntries) {
1023
- for (let i = 1; i < combinations.length; i++) {
1024
- const [_, lang, concept, combinationString] =
1025
- combinations[i].split(':');
1026
-
1045
+
1046
+ let updatedKeys: string[] = [];
1047
+ let recentDuplicates = new Set<string>(); // βœ… Track newly generated names dynamically
1048
+
1049
+ console.log(`πŸ” Found ${duplicateKeys.length} duplicate keys to process.`);
1050
+
1051
+ for (const kvKeyEN of duplicateKeys) {
1052
+ const kvKeyPT = kvKeyEN.replace(':en-us:', ':pt-br:');
1053
+
1054
+ const kvDataEN = (await kvStore.get(kvKeyEN, 'json')) as {
1055
+ name: string;
1056
+ description: string;
1057
+ };
1058
+ const kvDataPT = (await kvStore.get(kvKeyPT, 'json')) as { name: string };
1059
+
1060
+ if (!kvDataEN || !kvDataEN.name) {
1061
+ console.warn(`⚠️ Missing data for key: ${kvKeyEN}, skipping.`);
1062
+ continue;
1063
+ }
1064
+
1065
+ console.log(`🎭 Processing duplicate: ${kvKeyEN} (Old Name: "${kvDataEN.name}")`);
1066
+
1067
+ let newNameEN: string | null = null;
1068
+ let newNamePT: string | null = null;
1069
+ let attempts = 0;
1070
+ const maxAttempts = 3;
1071
+
1072
+ while (attempts < maxAttempts) {
1073
+ attempts++;
1074
+
1075
+ console.log(`πŸ“‘ Attempt ${attempts}/${maxAttempts} - Requesting AI for new name...`);
1076
+
1077
+ // βœ… Generate a new name, passing dynamically tracked duplicates
1078
+ const messages = this.context.buildLLMMessages().regenerateConceptName({
1079
+ conceptSlug,
1080
+ oldNameEN: kvDataEN.name,
1081
+ description: kvDataEN.description,
1082
+ recentNames: Array.from(recentDuplicates), // βœ… Prevent reusing recent names
1083
+ });
1084
+
1085
+ const aiResponse = await this.context.api().callTogether.single(messages, {});
1086
+
1087
+ if (!aiResponse) {
1088
+ console.warn(`⚠️ AI failed to generate a new name for ${kvKeyEN}, skipping.`);
1089
+ break;
1090
+ }
1091
+
1092
+ // βœ… Parse AI response
1093
+ const [generatedEN, generatedPT] = aiResponse.split('\n').map((s) => s.trim());
1094
+
1095
+ console.log(`🎨 AI Response: EN - "${generatedEN}", PT - "${generatedPT}"`);
1096
+
1097
+ // βœ… Check if the name already exists
1027
1098
  if (
1028
- !lang ||
1029
- !concept ||
1030
- !combinationString ||
1031
- concept !== conceptSlug
1099
+ usedNamesEN.has(generatedEN) ||
1100
+ usedNamesPT.has(generatedPT) ||
1101
+ recentDuplicates.has(generatedEN)
1032
1102
  ) {
1033
- console.warn(`⚠️ Invalid KV key format: ${combinations[i]}`);
1034
- continue;
1035
- }
1036
-
1037
- const kvKeyEN = buildConceptKVKey(
1038
- 'en-us',
1039
- concept as Concept,
1040
- combinationString
1041
- );
1042
- const kvKeyPT = buildConceptKVKey(
1043
- 'pt-br',
1044
- concept as Concept,
1045
- combinationString
1046
- );
1047
-
1048
- const kvDataEN = (await this.context.env.KV_CONCEPTS.get(
1049
- kvKeyEN,
1050
- 'json'
1051
- )) as {
1052
- name: string;
1053
- description: string;
1054
- };
1055
- const kvDataPT = (await this.context.env.KV_CONCEPTS.get(
1056
- kvKeyPT,
1057
- 'json'
1058
- )) as {
1059
- name: string;
1060
- };
1061
-
1062
- console.log(
1063
- `🎭 Regenerating name for: ${kvKeyEN} (Old Name: "${oldNameEN}")`
1064
- );
1065
-
1066
- let newNameEN: string | null = null;
1067
- let newNamePT: string | null = null;
1068
- let attempts = 0;
1069
- const maxAttempts = 3;
1070
-
1071
- while (attempts < maxAttempts) {
1072
- attempts++;
1073
-
1074
- const messages = this.context
1075
- .buildLLMMessages()
1076
- .regenerateConceptName({
1077
- oldNameEN: kvDataEN.name,
1078
- description: kvDataEN.description,
1079
- usedNamesEN: Array.from(usedNamesEN), // βœ… Ensure array format
1080
- });
1081
-
1082
- const aiResponse = await this.context
1083
- .api()
1084
- .callTogether.single(messages, {});
1085
- if (!aiResponse) {
1086
- console.warn(
1087
- `⚠️ AI failed to generate a new name for ${kvKeyEN}, skipping.`
1088
- );
1089
- break;
1090
- }
1091
-
1092
- // βœ… Parse AI response to extract both EN and PT names
1093
- const [generatedEN, generatedPT] = aiResponse
1094
- .split('\n')
1095
- .map((s) => s.trim());
1096
-
1097
- // βœ… Validate that the generated names are not duplicates
1098
- if (!usedNamesEN.has(generatedEN) && !usedNamesPT.has(generatedPT)) {
1099
- newNameEN = generatedEN;
1100
- newNamePT = generatedPT;
1101
- break; // βœ… Found unique names, exit retry loop
1102
- }
1103
-
1104
1103
  console.warn(
1105
- `⚠️ AI suggested a duplicate name: EN - "${generatedEN}", PT - "${generatedPT}". Retrying... (Attempt ${attempts}/${maxAttempts})`
1106
- );
1107
- }
1108
-
1109
- if (!newNameEN || !newNamePT) {
1110
- console.error(
1111
- `🚨 Failed to generate a unique name for ${kvKeyEN} after ${maxAttempts} attempts.`
1104
+ `⚠️ Duplicate detected: EN - "${generatedEN}", PT - "${generatedPT}". Retrying...`
1112
1105
  );
1106
+ recentDuplicates.add(generatedEN);
1107
+ recentDuplicates.add(generatedPT);
1113
1108
  continue;
1114
1109
  }
1115
-
1116
- console.log(
1117
- `βœ… Storing new names for ${kvKeyEN}: EN - "${newNameEN}", PT - "${newNamePT}"`
1118
- );
1119
-
1120
- // βœ… Update KV with new names (EN)
1121
- Object.assign(kvDataEN, { name: newNameEN });
1122
- await this.context.env.KV_CONCEPTS.put(
1123
- kvKeyEN,
1124
- JSON.stringify(kvDataEN)
1125
- );
1126
-
1127
- // βœ… Update KV with new names (PT)
1128
- Object.assign(kvDataPT, { name: newNamePT });
1129
- await this.context.env.KV_CONCEPTS.put(
1130
- kvKeyPT,
1131
- JSON.stringify(kvDataPT)
1110
+
1111
+ newNameEN = generatedEN;
1112
+ newNamePT = generatedPT;
1113
+ recentDuplicates.add(newNameEN);
1114
+ recentDuplicates.add(newNamePT);
1115
+ break; // βœ… Found unique names, exit retry loop
1116
+ }
1117
+
1118
+ if (!newNameEN || !newNamePT) {
1119
+ console.error(`🚨 Failed to generate a unique name for ${kvKeyEN} after ${maxAttempts} attempts.`);
1120
+ continue;
1121
+ }
1122
+
1123
+ console.log(`βœ… Storing new names: EN - "${newNameEN}", PT - "${newNamePT}"`);
1124
+
1125
+ // βœ… Update KV with new names (EN)
1126
+ Object.assign(kvDataEN, { name: newNameEN });
1127
+ await kvStore.put(kvKeyEN, JSON.stringify(kvDataEN));
1128
+
1129
+ // βœ… Update KV with new names (PT)
1130
+ Object.assign(kvDataPT, { name: newNamePT });
1131
+ await kvStore.put(kvKeyPT, JSON.stringify(kvDataPT));
1132
+
1133
+ // βœ… Add new names to used names sets
1134
+ usedNamesEN.add(newNameEN);
1135
+ usedNamesPT.add(newNamePT);
1136
+
1137
+ // βœ… Collect successfully processed keys for cache update
1138
+ updatedKeys.push(kvKeyEN);
1139
+ }
1140
+
1141
+ // βœ… Remove successfully updated keys from duplicate cache
1142
+ if (updatedKeys.length > 0) {
1143
+ let duplicateKeysList: string[] =
1144
+ (await kvCacheStore.get(`cache:duplicates:${conceptSlug}`, 'json')) || [];
1145
+
1146
+ console.log(`πŸ—‘οΈ Removing ${updatedKeys.length} resolved duplicates from cache.`);
1147
+
1148
+ // βœ… Remove processed keys
1149
+ duplicateKeysList = duplicateKeysList.filter((key) => !updatedKeys.includes(key));
1150
+
1151
+ if (duplicateKeysList.length === 0) {
1152
+ console.log(`βœ… All duplicates resolved for ${conceptSlug}, deleting cache entry.`);
1153
+ await kvCacheStore.delete(`cache:duplicates:${conceptSlug}`);
1154
+ } else {
1155
+ await kvCacheStore.put(
1156
+ `cache:duplicates:${conceptSlug}`,
1157
+ JSON.stringify(duplicateKeysList)
1132
1158
  );
1133
-
1134
- // βœ… Add new names to the used names sets to prevent future reuse
1135
- usedNamesEN.add(newNameEN);
1136
- usedNamesPT.add(newNamePT);
1137
-
1138
- // βœ… Stop if limit is reached
1139
- if (limit && --limit <= 0) {
1140
- console.log(`🎯 Limit reached, stopping further processing.`);
1141
- return { message: `Regeneration limit reached for ${conceptSlug}.` };
1142
- }
1143
1159
  }
1144
1160
  }
1145
-
1146
- console.log(
1147
- `πŸŽ‰ Duplicate names regenerated successfully for concept: ${conceptSlug}.`
1148
- );
1161
+
1162
+ console.log(`πŸŽ‰ [COMPLETE] Duplicate names regenerated successfully for concept: ${conceptSlug}.`);
1149
1163
  return {
1150
1164
  message: `Duplicate names processed and regenerated for ${conceptSlug}.`,
1151
1165
  };
@@ -1207,4 +1221,58 @@ export class ConceptService {
1207
1221
  message: `Cached ${totalKeysScanned} EN names for ${conceptSlug} in ${batchIndex} batches.`,
1208
1222
  };
1209
1223
  }
1224
+
1225
+ async cacheDuplicateConcepts(conceptSlug: Concept) {
1226
+ console.log(`πŸ” Identifying duplicate entries for concept: ${conceptSlug}`);
1227
+
1228
+ const kvCacheStore = this.context.env.KV_CONCEPT_CACHE;
1229
+ let cursor: string | undefined = undefined;
1230
+ let batchIndex = 0;
1231
+ let duplicateKeys: string[] = [];
1232
+
1233
+ do {
1234
+ const batchKey = `cache:concepts:en-us:${conceptSlug}:${batchIndex}`;
1235
+ const batchData = await kvCacheStore.get<{ [name: string]: string[] }>(
1236
+ batchKey,
1237
+ 'json'
1238
+ );
1239
+
1240
+ if (!batchData) {
1241
+ console.log(`⚠️ No data found for batch: ${batchKey}, stopping.`);
1242
+ break;
1243
+ }
1244
+
1245
+ console.log(
1246
+ `πŸ“¦ Processing batch ${batchIndex} with ${
1247
+ Object.keys(batchData).length
1248
+ } names`
1249
+ );
1250
+
1251
+ for (const [name, keys] of Object.entries(batchData)) {
1252
+ if (keys.length > 1) {
1253
+ // Collect duplicate entries (all but the first one)
1254
+ duplicateKeys.push(...keys.slice(1));
1255
+ }
1256
+ }
1257
+
1258
+ batchIndex++;
1259
+ } while (true);
1260
+
1261
+ if (duplicateKeys.length === 0) {
1262
+ console.log(`βœ… No duplicates found for ${conceptSlug}.`);
1263
+ return;
1264
+ }
1265
+
1266
+ console.log(
1267
+ `🚨 Found ${duplicateKeys.length} duplicate entries for ${conceptSlug}`
1268
+ );
1269
+
1270
+ // Store the duplicate keys list in KV
1271
+ await kvCacheStore.put(
1272
+ `cache:duplicates:${conceptSlug}`,
1273
+ JSON.stringify(duplicateKeys)
1274
+ );
1275
+
1276
+ console.log(`βœ… Stored duplicate keys for ${conceptSlug} in cache.`);
1277
+ }
1210
1278
  }
@@ -66,6 +66,17 @@ export class ConceptWorkflow {
66
66
  }
67
67
  }
68
68
 
69
+ async processDuplicateNames(conceptSlug: Concept) {
70
+ console.log(`πŸ” Initiating duplicate name resolution for ${conceptSlug}`);
71
+
72
+ try {
73
+ await this.conceptService.regenerateDuplicateNamesForConcept(conceptSlug);
74
+ console.log(`βœ… Duplicate names resolved for ${conceptSlug}`);
75
+ } catch (error) {
76
+ console.error(`❌ Error resolving duplicates for ${conceptSlug}:`, error);
77
+ }
78
+ }
79
+
69
80
  async processBatch(
70
81
  conceptSlug: Concept,
71
82
  combinations: string[],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zodic/shared",
3
- "version": "0.0.210",
3
+ "version": "0.0.212",
4
4
  "module": "index.ts",
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -183,42 +183,56 @@ export const buildLLMMessages = (env: BackendBindings) => ({
183
183
  regenerateConceptName: ({
184
184
  oldNameEN,
185
185
  description,
186
- usedNamesEN,
186
+ conceptSlug,
187
+ recentNames = [],
187
188
  }: {
188
189
  oldNameEN: string;
189
190
  description: string;
190
- usedNamesEN: string[];
191
+ conceptSlug: string;
192
+ recentNames?: string[];
191
193
  }): ChatMessages => [
192
194
  {
193
195
  role: 'system',
194
196
  content: `
195
- You are an expert in creating mystical and evocative names for spiritual artifacts.
196
-
197
- ## Instructions:
198
- - The name "${oldNameEN}" is already in use. Generate a new unique name that preserves the essence but avoids duplication.
199
- - Keep it concise (≀5 words) and memorable.
200
- - Ensure the name feels symbolic and mystical, fitting within the theme of arcane wisdom.
201
- - Do NOT use astrological signs or zodiac references.
202
- - Provide a natural Portuguese translation of the new name.
203
- - Avoid these names: ${
204
- usedNamesEN.length ? usedNamesEN.join(', ') : 'None'
205
- }.
206
-
207
- ## Output Format:
208
- EN: [new unique name]
209
- PT: [Portuguese translation]
197
+ You are an expert in mystical and symbolic naming. Your task is to generate a new, unique name for a given concept while ensuring it aligns with its description.
198
+
199
+ Guidelines:
200
+ β€’ The name must have exactly six words.
201
+ β€’ It must start with "The ${conceptSlug} of" in English and "O ${conceptSlug} de" in Portuguese.
202
+ β€’ The last three words should be distinct, meaningful, and thematically relevant.
203
+ β€’ It should reflect the concept’s core essence such as protection, transformation, or resilience.
204
+ β€’ Avoid generic, redundant, or overly abstract terms.
205
+ β€’ Ensure it does not resemble the previous name.
206
+ β€’ The name must not match any of the recently generated names.
207
+
208
+ Input:
209
+ β€’ Concept Name: ${conceptSlug}
210
+ β€’ Previous Name Already Used: ${oldNameEN}
211
+ β€’ Recently Generated Names: ${
212
+ recentNames.length > 0 ? recentNames.join(', ') : 'None'
213
+ }
214
+ β€’ Description: ${description}
215
+
216
+ Output Format:
217
+
218
+ EN:
219
+ β€’ New Name: The ${conceptSlug} of [New Symbolic Concept]
220
+
221
+ PT:
222
+ β€’ Novo Nome: O ${conceptSlug} de [Novo Conceito SimbΓ³lico]
223
+
224
+ No explanations or additional text. Return only the names.
210
225
  `,
211
226
  },
212
227
  {
213
228
  role: 'user',
214
229
  content: `
215
- The current name "${oldNameEN}" has been found to be a duplicate.
216
-
217
- ### Description:
218
- ${description}
219
-
220
- Generate a unique and evocative English name while maintaining the essence of the concept.
221
- Also, provide a natural Portuguese translation of the new name.
230
+ β€’ Concept Name: ${conceptSlug}
231
+ β€’ Previous Name Already Used: ${oldNameEN}
232
+ β€’ Recently Generated Names: ${
233
+ recentNames.length > 0 ? recentNames.join(', ') : 'None'
234
+ }
235
+ β€’ Description: ${description}
222
236
  `,
223
237
  },
224
238
  ],