@zodic/shared 0.0.211 β†’ 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
  }
@@ -164,10 +203,10 @@ export class ConceptService {
164
203
  const enMatch = response.match(
165
204
  /EN:\s*(?:β€’\s*)?Name:\s*(.+?)\s*(?:β€’\s*)?Description:\s*([\s\S]+?)\s*(?:β€’\s*)?Poetic Passage:\s*([\s\S]+?)\s*(?=PT:|$)/
166
205
  );
167
-
206
+
168
207
  console.log('πŸ”Ž Attempting to match Portuguese section...');
169
208
  const ptMatch = response.match(
170
- /PT:\s*(?:β€’\s*)?Nome:\s*(.+?)\s*(?:β€’\s*)?DescriΓ§Γ£o:\s*([\s\S]+?)\s*(?:β€’\s*)?Passagem PoΓ©tica:\s*([\s\S]+)/
209
+ /PT:\s*(?:β€’\s*)?Nome:\s*(.+?)\s*(?:β€’\s*)?DescriΓ§Γ£o:\s*([\s\S]+?)\s*(?:β€’\s*)?Passagem PoΓ©tica:\s*([\s\S]+)/
171
210
  );
172
211
 
173
212
  if (!enMatch || !ptMatch) {
@@ -808,361 +847,319 @@ export class ConceptService {
808
847
  };
809
848
  }
810
849
 
811
- async regenerateDuplicateNames(limit?: number) {
812
- console.log(`πŸ”„ Processing duplicate names for regeneration...`);
813
-
814
- const { duplicateEntries, usedNamesEN, usedNamesPT } =
815
- await this.findDuplicateNamesAndUsedNames(limit);
816
-
817
- if (duplicateEntries.length === 0) {
818
- console.log(`πŸŽ‰ No duplicates found. Nothing to regenerate.`);
819
- return { message: 'No duplicates detected.' };
820
- }
821
-
822
- for (const { name: oldNameEN, combinations } of duplicateEntries) {
823
- for (let i = 1; i < combinations.length; i++) {
824
- const [_, lang, conceptSlug, combinationString] =
825
- combinations[i].split(':');
826
-
827
- if (!lang || !conceptSlug || !combinationString) {
828
- console.warn(`⚠️ Invalid KV key format: ${combinations[i]}`);
829
- continue;
830
- }
831
-
832
- const kvKeyEN = buildConceptKVKey(
833
- 'en-us',
834
- conceptSlug as Concept,
835
- combinationString
836
- );
837
- const kvKeyPT = buildConceptKVKey(
838
- 'pt-br',
839
- conceptSlug as Concept,
840
- combinationString
841
- );
842
-
843
- const kvDataEN = (await this.context.env.KV_CONCEPTS.get(
844
- kvKeyEN,
845
- 'json'
846
- )) as {
847
- name: string;
848
- description: string;
849
- };
850
- const kvDataPT = (await this.context.env.KV_CONCEPTS.get(
851
- kvKeyPT,
852
- 'json'
853
- )) as {
854
- name: string;
855
- };
856
-
857
- console.log(
858
- `🎭 Regenerating name for: ${kvKeyEN} (Old Name: "${oldNameEN}")`
859
- );
860
-
861
- let newNameEN: string | null = null;
862
- let newNamePT: string | null = null;
863
- let attempts = 0;
864
- const maxAttempts = 3;
865
-
866
- while (attempts < maxAttempts) {
867
- attempts++;
868
-
869
- const messages = this.context
870
- .buildLLMMessages()
871
- .regenerateConceptName({
872
- oldNameEN: kvDataEN.name,
873
- description: kvDataEN.description,
874
- usedNamesEN: Array.from(usedNamesEN), // βœ… Ensure array format
875
- });
876
-
877
- const aiResponse = await this.context
878
- .api()
879
- .callTogether.single(messages, {});
880
- if (!aiResponse) {
881
- console.warn(
882
- `⚠️ AI failed to generate a new name for ${kvKeyEN}, skipping.`
883
- );
884
- break;
885
- }
886
-
887
- // βœ… Parse AI response to extract both EN and PT names
888
- const [generatedEN, generatedPT] = aiResponse
889
- .split('\n')
890
- .map((s) => s.trim());
891
-
892
- // βœ… Validate that the generated names are not duplicates
893
- if (!usedNamesEN.has(generatedEN) && !usedNamesPT.has(generatedPT)) {
894
- newNameEN = generatedEN;
895
- newNamePT = generatedPT;
896
- break; // βœ… Found unique names, exit retry loop
897
- }
898
-
899
- console.warn(
900
- `⚠️ AI suggested a duplicate name: EN - "${generatedEN}", PT - "${generatedPT}". Retrying... (Attempt ${attempts}/${maxAttempts})`
901
- );
902
- }
903
-
904
- if (!newNameEN || !newNamePT) {
905
- console.error(
906
- `🚨 Failed to generate a unique name for ${kvKeyEN} after ${maxAttempts} attempts.`
907
- );
908
- continue;
909
- }
910
-
911
- console.log(
912
- `βœ… Storing new names for ${kvKeyEN}: EN - "${newNameEN}", PT - "${newNamePT}"`
913
- );
914
-
915
- // βœ… Update KV with new names (EN)
916
- Object.assign(kvDataEN, { name: newNameEN });
917
- await this.context.env.KV_CONCEPTS.put(
918
- kvKeyEN,
919
- JSON.stringify(kvDataEN)
920
- );
921
-
922
- // βœ… Update KV with new names (PT)
923
- Object.assign(kvDataPT, { name: newNamePT });
924
- await this.context.env.KV_CONCEPTS.put(
925
- kvKeyPT,
926
- JSON.stringify(kvDataPT)
927
- );
928
-
929
- // βœ… Add new names to the used names sets to prevent future reuse
930
- usedNamesEN.add(newNameEN);
931
- usedNamesPT.add(newNamePT);
932
-
933
- // βœ… Stop if limit is reached
934
- if (limit && --limit <= 0) {
935
- console.log(`🎯 Limit reached, stopping further processing.`);
936
- return { message: 'Regeneration limit reached.' };
937
- }
938
- }
939
- }
940
-
941
- console.log(`πŸŽ‰ Duplicate names regenerated successfully.`);
942
- return { message: 'Duplicate names processed and regenerated.' };
943
- }
944
-
945
- async findDuplicateNamesAndUsedNamesForConcept(
946
- conceptSlug: Concept,
947
- limit?: number
948
- ) {
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) {
949
985
  console.log(
950
- `πŸ” Scanning KV for duplicate names in concept: ${conceptSlug}...`
986
+ `πŸ” Fetching duplicate names from KV_CONCEPT_CACHE for ${conceptSlug}...`
951
987
  );
952
988
 
953
- const kvStore = this.context.env.KV_CONCEPTS;
954
- let cursor: string | undefined = undefined;
955
- let nameUsageMapEN: Record<string, string[]> = {}; // { enName: [combination1, combination2] }
956
- let usedNamesEN = new Set<string>(); // βœ… All used EN names
957
- let usedNamesPT = new Set<string>(); // βœ… All used PT names
958
- let totalKeysScanned = 0;
959
-
960
- do {
961
- const response: KVNamespaceListResult<KVConcept> = (await kvStore.list({
962
- prefix: `concepts:en-us:${conceptSlug}:`, // βœ… Fetch only keys for this concept
963
- cursor,
964
- })) as KVNamespaceListResult<KVConcept, string>;
965
-
966
- const {
967
- keys,
968
- cursor: newCursor,
969
- }: { keys: KVNamespaceListKey<KVConcept>[]; cursor?: string } = response;
970
-
971
- totalKeysScanned += keys.length;
972
-
973
- for (const key of keys) {
974
- const kvDataEN = (await kvStore.get(key.name, 'json')) as {
975
- name: string;
976
- };
977
- const kvKeyPT = key.name.replace(':en-us:', ':pt-br:'); // βœ… Get corresponding PT key
978
- const kvDataPT = (await kvStore.get(kvKeyPT, 'json')) as {
979
- name: string;
980
- };
989
+ const kvCacheStore = this.context.env.KV_CONCEPT_CACHE;
981
990
 
982
- if (kvDataEN?.name) {
983
- 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
+ )) || {};
984
997
 
985
- if (!nameUsageMapEN[kvDataEN.name]) {
986
- nameUsageMapEN[kvDataEN.name] = [];
987
- }
988
- nameUsageMapEN[kvDataEN.name].push(key.name); // Store the key for that name
989
- }
998
+ let duplicateKeys: string[] = [];
999
+ let usedNamesEN = new Set<string>();
1000
+ let usedNamesPT = new Set<string>();
990
1001
 
991
- if (kvDataPT?.name) {
992
- usedNamesPT.add(kvDataPT.name); // βœ… Store all used PT names
993
- }
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
994
1005
  }
1006
+ usedNamesEN.add(name);
1007
+ }
995
1008
 
996
- cursor = newCursor;
997
- } while (cursor && (!limit || totalKeysScanned < limit));
998
-
999
- // βœ… Find names used more than once (EN)
1000
- let duplicateENNames = Object.entries(nameUsageMapEN)
1001
- .filter(([_, combinations]) => combinations.length > 1)
1002
- .map(([name, combinations]) => ({
1003
- name,
1004
- combinations,
1005
- }));
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
+ )) || {};
1006
1015
 
1007
- // βœ… If limit is set, trim duplicates list
1008
- if (limit && duplicateENNames.length > limit) {
1009
- duplicateENNames = duplicateENNames.slice(0, limit);
1016
+ for (const name of Object.keys(nameMapPT)) {
1017
+ usedNamesPT.add(name);
1010
1018
  }
1011
1019
 
1012
1020
  console.log(
1013
- `βœ… Found ${duplicateENNames.length} duplicate EN names for concept "${conceptSlug}" out of ${totalKeysScanned} entries.`
1021
+ `βœ… Found ${duplicateKeys.length} duplicate keys for ${conceptSlug}.`
1014
1022
  );
1015
1023
 
1016
1024
  return {
1017
- duplicateEntries: duplicateENNames, // βœ… List of EN names that need regeneration
1018
- usedNamesEN, // βœ… Set of all unique EN names
1019
- 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
1020
1028
  };
1021
1029
  }
1022
1030
 
1023
- async regenerateDuplicateNamesForConcept(
1024
- conceptSlug: Concept,
1025
- limit?: number
1026
- ) {
1027
- console.log(`πŸ”„ Processing duplicate names for concept: ${conceptSlug}...`);
1028
-
1029
- const { duplicateEntries, usedNamesEN, usedNamesPT } =
1030
- await this.findDuplicateNamesAndUsedNamesForConcept(conceptSlug, limit);
1031
-
1032
- if (duplicateEntries.length === 0) {
1033
- console.log(
1034
- `πŸŽ‰ No duplicates found for concept: ${conceptSlug}. Nothing to regenerate.`
1035
- );
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}.`);
1036
1043
  return { message: `No duplicates detected for ${conceptSlug}.` };
1037
1044
  }
1038
-
1039
- for (const { name: oldNameEN, combinations } of duplicateEntries) {
1040
- for (let i = 1; i < combinations.length; i++) {
1041
- const [_, lang, concept, combinationString] =
1042
- combinations[i].split(':');
1043
-
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
1044
1098
  if (
1045
- !lang ||
1046
- !concept ||
1047
- !combinationString ||
1048
- concept !== conceptSlug
1099
+ usedNamesEN.has(generatedEN) ||
1100
+ usedNamesPT.has(generatedPT) ||
1101
+ recentDuplicates.has(generatedEN)
1049
1102
  ) {
1050
- console.warn(`⚠️ Invalid KV key format: ${combinations[i]}`);
1051
- continue;
1052
- }
1053
-
1054
- const kvKeyEN = buildConceptKVKey(
1055
- 'en-us',
1056
- concept as Concept,
1057
- combinationString
1058
- );
1059
- const kvKeyPT = buildConceptKVKey(
1060
- 'pt-br',
1061
- concept as Concept,
1062
- combinationString
1063
- );
1064
-
1065
- const kvDataEN = (await this.context.env.KV_CONCEPTS.get(
1066
- kvKeyEN,
1067
- 'json'
1068
- )) as {
1069
- name: string;
1070
- description: string;
1071
- };
1072
- const kvDataPT = (await this.context.env.KV_CONCEPTS.get(
1073
- kvKeyPT,
1074
- 'json'
1075
- )) as {
1076
- name: string;
1077
- };
1078
-
1079
- console.log(
1080
- `🎭 Regenerating name for: ${kvKeyEN} (Old Name: "${oldNameEN}")`
1081
- );
1082
-
1083
- let newNameEN: string | null = null;
1084
- let newNamePT: string | null = null;
1085
- let attempts = 0;
1086
- const maxAttempts = 3;
1087
-
1088
- while (attempts < maxAttempts) {
1089
- attempts++;
1090
-
1091
- const messages = this.context
1092
- .buildLLMMessages()
1093
- .regenerateConceptName({
1094
- oldNameEN: kvDataEN.name,
1095
- description: kvDataEN.description,
1096
- usedNamesEN: Array.from(usedNamesEN), // βœ… Ensure array format
1097
- });
1098
-
1099
- const aiResponse = await this.context
1100
- .api()
1101
- .callTogether.single(messages, {});
1102
- if (!aiResponse) {
1103
- console.warn(
1104
- `⚠️ AI failed to generate a new name for ${kvKeyEN}, skipping.`
1105
- );
1106
- break;
1107
- }
1108
-
1109
- // βœ… Parse AI response to extract both EN and PT names
1110
- const [generatedEN, generatedPT] = aiResponse
1111
- .split('\n')
1112
- .map((s) => s.trim());
1113
-
1114
- // βœ… Validate that the generated names are not duplicates
1115
- if (!usedNamesEN.has(generatedEN) && !usedNamesPT.has(generatedPT)) {
1116
- newNameEN = generatedEN;
1117
- newNamePT = generatedPT;
1118
- break; // βœ… Found unique names, exit retry loop
1119
- }
1120
-
1121
1103
  console.warn(
1122
- `⚠️ AI suggested a duplicate name: EN - "${generatedEN}", PT - "${generatedPT}". Retrying... (Attempt ${attempts}/${maxAttempts})`
1123
- );
1124
- }
1125
-
1126
- if (!newNameEN || !newNamePT) {
1127
- console.error(
1128
- `🚨 Failed to generate a unique name for ${kvKeyEN} after ${maxAttempts} attempts.`
1104
+ `⚠️ Duplicate detected: EN - "${generatedEN}", PT - "${generatedPT}". Retrying...`
1129
1105
  );
1106
+ recentDuplicates.add(generatedEN);
1107
+ recentDuplicates.add(generatedPT);
1130
1108
  continue;
1131
1109
  }
1132
-
1133
- console.log(
1134
- `βœ… Storing new names for ${kvKeyEN}: EN - "${newNameEN}", PT - "${newNamePT}"`
1135
- );
1136
-
1137
- // βœ… Update KV with new names (EN)
1138
- Object.assign(kvDataEN, { name: newNameEN });
1139
- await this.context.env.KV_CONCEPTS.put(
1140
- kvKeyEN,
1141
- JSON.stringify(kvDataEN)
1142
- );
1143
-
1144
- // βœ… Update KV with new names (PT)
1145
- Object.assign(kvDataPT, { name: newNamePT });
1146
- await this.context.env.KV_CONCEPTS.put(
1147
- kvKeyPT,
1148
- 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)
1149
1158
  );
1150
-
1151
- // βœ… Add new names to the used names sets to prevent future reuse
1152
- usedNamesEN.add(newNameEN);
1153
- usedNamesPT.add(newNamePT);
1154
-
1155
- // βœ… Stop if limit is reached
1156
- if (limit && --limit <= 0) {
1157
- console.log(`🎯 Limit reached, stopping further processing.`);
1158
- return { message: `Regeneration limit reached for ${conceptSlug}.` };
1159
- }
1160
1159
  }
1161
1160
  }
1162
-
1163
- console.log(
1164
- `πŸŽ‰ Duplicate names regenerated successfully for concept: ${conceptSlug}.`
1165
- );
1161
+
1162
+ console.log(`πŸŽ‰ [COMPLETE] Duplicate names regenerated successfully for concept: ${conceptSlug}.`);
1166
1163
  return {
1167
1164
  message: `Duplicate names processed and regenerated for ${conceptSlug}.`,
1168
1165
  };
@@ -1224,4 +1221,58 @@ export class ConceptService {
1224
1221
  message: `Cached ${totalKeysScanned} EN names for ${conceptSlug} in ${batchIndex} batches.`,
1225
1222
  };
1226
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
+ }
1227
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.211",
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
  ],