@zodic/shared 0.0.196 → 0.0.197

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.
@@ -1,3 +1,7 @@
1
+ import {
2
+ KVNamespaceListKey,
3
+ KVNamespaceListResult,
4
+ } from '@cloudflare/workers-types';
1
5
  import { and, eq } from 'drizzle-orm';
2
6
  import { inject, injectable } from 'inversify';
3
7
  import 'reflect-metadata';
@@ -28,47 +32,54 @@ export class ConceptService {
28
32
  console.log(
29
33
  `🚀 Generating basic info for concept: ${conceptSlug}, combination: ${combinationString}, override: ${override}`
30
34
  );
31
-
35
+
32
36
  const kvStore = this.context.kvConceptsStore();
33
37
  const kvFailuresStore = this.context.kvConceptFailuresStore();
34
38
  const kvKeyEN = buildConceptKVKey('en-us', conceptSlug, combinationString);
35
39
  const kvKeyPT = buildConceptKVKey('pt-br', conceptSlug, combinationString);
36
-
40
+
37
41
  // ✅ Use Durable Object stub
38
42
  const id = this.context.env.CONCEPT_NAMES_DO.idFromName(conceptSlug);
39
43
  const stub = this.context.env.CONCEPT_NAMES_DO.get(id);
40
-
44
+
41
45
  console.log(`📡 Fetching existing KV data for ${conceptSlug}...`);
42
46
  const existingEN = await this.getKVConcept(kvKeyEN);
43
47
  const existingPT = await this.getKVConcept(kvKeyPT);
44
-
48
+
45
49
  // ✅ IMMEDIATE SKIP if already exists and override is false
46
50
  if (!override && existingEN.name && existingPT.name) {
47
51
  console.log(`⚡ Basic info already exists for ${conceptSlug}, skipping.`);
48
52
  return;
49
53
  }
50
-
54
+
51
55
  let attempts = 0;
52
56
  const maxAttempts = 3;
53
-
57
+
54
58
  while (attempts < maxAttempts) {
55
59
  let phase = 'generation';
56
60
  try {
57
61
  attempts++;
58
- console.log(`🔄 Attempt ${attempts} to generate basic info for ${conceptSlug}...`);
59
-
62
+ console.log(
63
+ `🔄 Attempt ${attempts} to generate basic info for ${conceptSlug}...`
64
+ );
65
+
60
66
  // ✅ Fetch the latest name list from Durable Object
61
- console.log(`📡 Fetching names from Durable Object for ${conceptSlug}...`);
67
+ console.log(
68
+ `📡 Fetching names from Durable Object for ${conceptSlug}...`
69
+ );
62
70
  let allNamesEN: string[] = [];
63
71
  let allNamesPT: string[] = [];
64
72
  const response = await stub.fetch(`https://internal/names`);
65
-
73
+
66
74
  if (response.ok) {
67
- const data = (await response.json()) as { 'en-us': string[]; 'pt-br': string[] };
75
+ const data = (await response.json()) as {
76
+ 'en-us': string[];
77
+ 'pt-br': string[];
78
+ };
68
79
  allNamesEN = data['en-us'] || [];
69
80
  allNamesPT = data['pt-br'] || [];
70
81
  }
71
-
82
+
72
83
  console.log(`✏️ Generating new name...`);
73
84
  const messages = this.context
74
85
  .buildLLMMessages()
@@ -77,49 +88,60 @@ export class ConceptService {
77
88
  conceptSlug,
78
89
  existingNames: allNamesEN.slice(-40),
79
90
  });
80
-
81
- let aiResponse = await this.context.api().callTogether.single(messages, {});
91
+
92
+ let aiResponse = await this.context
93
+ .api()
94
+ .callTogether.single(messages, {});
82
95
  if (!aiResponse) throw new Error(`❌ AI returned an empty response`);
83
-
96
+
84
97
  phase = 'cleaning';
85
98
  aiResponse = this.cleanAIResponse(aiResponse);
86
-
99
+
87
100
  phase = 'parsing';
88
101
  let { nameEN, descriptionEN, poemEN, namePT, descriptionPT, poemPT } =
89
102
  this.parseBasicInfoResponse(aiResponse, conceptSlug);
90
-
103
+
91
104
  console.log(`🎭 Generated names: EN - "${nameEN}", PT - "${namePT}"`);
92
-
105
+
93
106
  // ✅ **Check name length and retry if too long**
94
107
  const MAX_NAME_LENGTH = 44; // Adjust if needed
95
- if (nameEN.length > MAX_NAME_LENGTH || namePT.length > MAX_NAME_LENGTH) {
96
- console.warn(`⚠️ Name too long: "${nameEN}" | "${namePT}". Retrying...`);
97
-
108
+ if (
109
+ nameEN.length > MAX_NAME_LENGTH ||
110
+ namePT.length > MAX_NAME_LENGTH
111
+ ) {
112
+ console.warn(
113
+ `⚠️ Name too long: "${nameEN}" | "${namePT}". Retrying...`
114
+ );
115
+
98
116
  // ✅ **Register name in Durable Object as blocked**
99
117
  await stub.fetch(`https://internal/add-name`, {
100
118
  method: 'POST',
101
119
  body: JSON.stringify({ language: 'en-us', name: nameEN }),
102
120
  headers: { 'Content-Type': 'application/json' },
103
121
  });
104
-
122
+
105
123
  await stub.fetch(`https://internal/add-name`, {
106
124
  method: 'POST',
107
125
  body: JSON.stringify({ language: 'pt-br', name: namePT }),
108
126
  headers: { 'Content-Type': 'application/json' },
109
127
  });
110
-
128
+
111
129
  console.log(`✅ Added long name to DO blocklist, retrying...`);
112
130
  continue;
113
131
  }
114
-
132
+
115
133
  // ✅ Check uniqueness before storing
116
134
  if (allNamesEN.includes(nameEN) || allNamesPT.includes(namePT)) {
117
- console.warn(`⚠️ Duplicate Name Detected: "${nameEN}" or "${namePT}"`);
118
-
135
+ console.warn(
136
+ `⚠️ Duplicate Name Detected: "${nameEN}" or "${namePT}"`
137
+ );
138
+
119
139
  // ✅ **On third attempt, store the name anyway & register in kvFailures**
120
140
  if (attempts >= maxAttempts) {
121
- console.log(`🚨 Max attempts reached. Storing name despite duplicate.`);
122
-
141
+ console.log(
142
+ `🚨 Max attempts reached. Storing name despite duplicate.`
143
+ );
144
+
123
145
  await kvFailuresStore.put(
124
146
  `failures:duplicates:${conceptSlug}:${combinationString}`,
125
147
  JSON.stringify({
@@ -131,15 +153,17 @@ export class ConceptService {
131
153
  timestamp: new Date().toISOString(),
132
154
  })
133
155
  );
134
-
135
- console.log(`✅ Stored duplicate name after max retries: ${nameEN}, ${namePT}`);
156
+
157
+ console.log(
158
+ `✅ Stored duplicate name after max retries: ${nameEN}, ${namePT}`
159
+ );
136
160
  break;
137
161
  }
138
-
162
+
139
163
  console.log('🔁 Retrying due to duplicate name...');
140
164
  continue;
141
165
  }
142
-
166
+
143
167
  console.log(`📝 Storing names in Durable Object...`);
144
168
  // ✅ **Immediately update Durable Object with the new name**
145
169
  await stub.fetch(`https://internal/add-name`, {
@@ -147,13 +171,13 @@ export class ConceptService {
147
171
  body: JSON.stringify({ language: 'en-us', name: nameEN }),
148
172
  headers: { 'Content-Type': 'application/json' },
149
173
  });
150
-
174
+
151
175
  await stub.fetch(`https://internal/add-name`, {
152
176
  method: 'POST',
153
177
  body: JSON.stringify({ language: 'pt-br', name: namePT }),
154
178
  headers: { 'Content-Type': 'application/json' },
155
179
  });
156
-
180
+
157
181
  // ✅ Store the generated basic info in KV
158
182
  Object.assign(existingEN, {
159
183
  name: nameEN,
@@ -162,7 +186,7 @@ export class ConceptService {
162
186
  status: 'idle',
163
187
  });
164
188
  await kvStore.put(kvKeyEN, JSON.stringify(existingEN));
165
-
189
+
166
190
  Object.assign(existingPT, {
167
191
  name: namePT,
168
192
  description: descriptionPT,
@@ -170,11 +194,16 @@ export class ConceptService {
170
194
  status: 'idle',
171
195
  });
172
196
  await kvStore.put(kvKeyPT, JSON.stringify(existingPT));
173
-
174
- console.log(`✅ Stored basic info for ${conceptSlug}, combination: ${combinationString}.`);
197
+
198
+ console.log(
199
+ `✅ Stored basic info for ${conceptSlug}, combination: ${combinationString}.`
200
+ );
175
201
  return;
176
202
  } catch (error) {
177
- console.error(`❌ Attempt ${attempts} failed at phase: ${phase}`, (error as Error).message);
203
+ console.error(
204
+ `❌ Attempt ${attempts} failed at phase: ${phase}`,
205
+ (error as Error).message
206
+ );
178
207
  }
179
208
  }
180
209
  }
@@ -762,4 +791,434 @@ export class ConceptService {
762
791
  status: 'idle',
763
792
  };
764
793
  }
794
+
795
+ private async findDuplicateNamesAndUsedNames(limit?: number) {
796
+ console.log(`🔍 Scanning KV for duplicate names...`);
797
+
798
+ const kvStore = this.context.env.KV_CONCEPTS;
799
+ let cursor: string | undefined = undefined;
800
+ let nameUsageMapEN: Record<string, string[]> = {}; // { enName: [combination1, combination2] }
801
+ let usedNamesEN = new Set<string>(); // ✅ All used EN names
802
+ let usedNamesPT = new Set<string>(); // ✅ All used PT names
803
+ let totalKeysScanned = 0;
804
+
805
+ do {
806
+ const response: KVNamespaceListResult<KVConcept> = (await kvStore.list({
807
+ prefix: 'concepts:',
808
+ cursor,
809
+ })) as KVNamespaceListResult<KVConcept, string>;
810
+
811
+ const {
812
+ keys,
813
+ cursor: newCursor,
814
+ }: { keys: KVNamespaceListKey<KVConcept>[]; cursor?: string } = response;
815
+
816
+ totalKeysScanned += keys.length;
817
+
818
+ for (const key of keys) {
819
+ const kvData = (await kvStore.get(key.name, 'json')) as {
820
+ name: string;
821
+ ptName?: string;
822
+ };
823
+
824
+ if (kvData?.name) {
825
+ usedNamesEN.add(kvData.name); // ✅ Store all used EN names
826
+
827
+ if (!nameUsageMapEN[kvData.name]) {
828
+ nameUsageMapEN[kvData.name] = [];
829
+ }
830
+ nameUsageMapEN[kvData.name].push(key.name); // Store the key for that name
831
+ }
832
+
833
+ if (kvData?.ptName) {
834
+ usedNamesPT.add(kvData.ptName); // ✅ Store all used PT names
835
+ }
836
+ }
837
+
838
+ cursor = newCursor;
839
+ } while (cursor && (!limit || totalKeysScanned < limit));
840
+
841
+ // ✅ Find names used more than once (EN)
842
+ let duplicateENNames = Object.entries(nameUsageMapEN)
843
+ .filter(([_, combinations]) => combinations.length > 1)
844
+ .map(([name, combinations]) => ({
845
+ name,
846
+ combinations,
847
+ }));
848
+
849
+ // ✅ If limit is set, trim duplicates list
850
+ if (limit && duplicateENNames.length > limit) {
851
+ duplicateENNames = duplicateENNames.slice(0, limit);
852
+ }
853
+
854
+ console.log(
855
+ `✅ Found ${duplicateENNames.length} duplicate EN names out of ${totalKeysScanned} entries.`
856
+ );
857
+
858
+ return {
859
+ duplicateEntries: duplicateENNames, // ✅ List of EN names that need regeneration
860
+ usedNamesEN, // ✅ Set of all unique EN names
861
+ usedNamesPT, // ✅ Set of all unique PT names
862
+ };
863
+ }
864
+
865
+ async regenerateDuplicateNames(limit?: number) {
866
+ console.log(`🔄 Processing duplicate names for regeneration...`);
867
+
868
+ const { duplicateEntries, usedNamesEN, usedNamesPT } =
869
+ await this.findDuplicateNamesAndUsedNames(limit);
870
+
871
+ if (duplicateEntries.length === 0) {
872
+ console.log(`🎉 No duplicates found. Nothing to regenerate.`);
873
+ return { message: 'No duplicates detected.' };
874
+ }
875
+
876
+ for (const { name: oldNameEN, combinations } of duplicateEntries) {
877
+ for (let i = 1; i < combinations.length; i++) {
878
+ const [_, lang, conceptSlug, combinationString] =
879
+ combinations[i].split(':');
880
+
881
+ if (!lang || !conceptSlug || !combinationString) {
882
+ console.warn(`⚠️ Invalid KV key format: ${combinations[i]}`);
883
+ continue;
884
+ }
885
+
886
+ const kvKeyEN = buildConceptKVKey(
887
+ 'en-us',
888
+ conceptSlug as Concept,
889
+ combinationString
890
+ );
891
+ const kvKeyPT = buildConceptKVKey(
892
+ 'pt-br',
893
+ conceptSlug as Concept,
894
+ combinationString
895
+ );
896
+
897
+ const kvDataEN = (await this.context.env.KV_CONCEPTS.get(
898
+ kvKeyEN,
899
+ 'json'
900
+ )) as {
901
+ name: string;
902
+ description: string;
903
+ };
904
+ const kvDataPT = (await this.context.env.KV_CONCEPTS.get(
905
+ kvKeyPT,
906
+ 'json'
907
+ )) as {
908
+ name: string;
909
+ };
910
+
911
+ console.log(
912
+ `🎭 Regenerating name for: ${kvKeyEN} (Old Name: "${oldNameEN}")`
913
+ );
914
+
915
+ let newNameEN: string | null = null;
916
+ let newNamePT: string | null = null;
917
+ let attempts = 0;
918
+ const maxAttempts = 3;
919
+
920
+ while (attempts < maxAttempts) {
921
+ attempts++;
922
+
923
+ const messages = this.context
924
+ .buildLLMMessages()
925
+ .regenerateConceptName({
926
+ oldNameEN: kvDataEN.name,
927
+ description: kvDataEN.description,
928
+ usedNamesEN: Array.from(usedNamesEN), // ✅ Ensure array format
929
+ });
930
+
931
+ const aiResponse = await this.context
932
+ .api()
933
+ .callTogether.single(messages, {});
934
+ if (!aiResponse) {
935
+ console.warn(
936
+ `⚠️ AI failed to generate a new name for ${kvKeyEN}, skipping.`
937
+ );
938
+ break;
939
+ }
940
+
941
+ // ✅ Parse AI response to extract both EN and PT names
942
+ const [generatedEN, generatedPT] = aiResponse
943
+ .split('\n')
944
+ .map((s) => s.trim());
945
+
946
+ // ✅ Validate that the generated names are not duplicates
947
+ if (!usedNamesEN.has(generatedEN) && !usedNamesPT.has(generatedPT)) {
948
+ newNameEN = generatedEN;
949
+ newNamePT = generatedPT;
950
+ break; // ✅ Found unique names, exit retry loop
951
+ }
952
+
953
+ console.warn(
954
+ `⚠️ AI suggested a duplicate name: EN - "${generatedEN}", PT - "${generatedPT}". Retrying... (Attempt ${attempts}/${maxAttempts})`
955
+ );
956
+ }
957
+
958
+ if (!newNameEN || !newNamePT) {
959
+ console.error(
960
+ `🚨 Failed to generate a unique name for ${kvKeyEN} after ${maxAttempts} attempts.`
961
+ );
962
+ continue;
963
+ }
964
+
965
+ console.log(
966
+ `✅ Storing new names for ${kvKeyEN}: EN - "${newNameEN}", PT - "${newNamePT}"`
967
+ );
968
+
969
+ // ✅ Update KV with new names (EN)
970
+ Object.assign(kvDataEN, { name: newNameEN });
971
+ await this.context.env.KV_CONCEPTS.put(
972
+ kvKeyEN,
973
+ JSON.stringify(kvDataEN)
974
+ );
975
+
976
+ // ✅ Update KV with new names (PT)
977
+ Object.assign(kvDataPT, { name: newNamePT });
978
+ await this.context.env.KV_CONCEPTS.put(
979
+ kvKeyPT,
980
+ JSON.stringify(kvDataPT)
981
+ );
982
+
983
+ // ✅ Add new names to the used names sets to prevent future reuse
984
+ usedNamesEN.add(newNameEN);
985
+ usedNamesPT.add(newNamePT);
986
+
987
+ // ✅ Stop if limit is reached
988
+ if (limit && --limit <= 0) {
989
+ console.log(`🎯 Limit reached, stopping further processing.`);
990
+ return { message: 'Regeneration limit reached.' };
991
+ }
992
+ }
993
+ }
994
+
995
+ console.log(`🎉 Duplicate names regenerated successfully.`);
996
+ return { message: 'Duplicate names processed and regenerated.' };
997
+ }
998
+
999
+ private async findDuplicateNamesAndUsedNamesForConcept(
1000
+ conceptSlug: Concept,
1001
+ limit?: number
1002
+ ) {
1003
+ console.log(
1004
+ `🔍 Scanning KV for duplicate names in concept: ${conceptSlug}...`
1005
+ );
1006
+
1007
+ const kvStore = this.context.env.KV_CONCEPTS;
1008
+ let cursor: string | undefined = undefined;
1009
+ let nameUsageMapEN: Record<string, string[]> = {}; // { enName: [combination1, combination2] }
1010
+ let usedNamesEN = new Set<string>(); // ✅ All used EN names
1011
+ let usedNamesPT = new Set<string>(); // ✅ All used PT names
1012
+ let totalKeysScanned = 0;
1013
+
1014
+ do {
1015
+ const response: KVNamespaceListResult<KVConcept> = (await kvStore.list({
1016
+ prefix: `concepts:en-us:${conceptSlug}:`, // ✅ Fetch only keys for this concept
1017
+ cursor,
1018
+ })) as KVNamespaceListResult<KVConcept, string>;
1019
+
1020
+ const {
1021
+ keys,
1022
+ cursor: newCursor,
1023
+ }: { keys: KVNamespaceListKey<KVConcept>[]; cursor?: string } = response;
1024
+
1025
+ totalKeysScanned += keys.length;
1026
+
1027
+ for (const key of keys) {
1028
+ const kvDataEN = (await kvStore.get(key.name, 'json')) as {
1029
+ name: string;
1030
+ };
1031
+ const kvKeyPT = key.name.replace(':en-us:', ':pt-br:'); // ✅ Get corresponding PT key
1032
+ const kvDataPT = (await kvStore.get(kvKeyPT, 'json')) as {
1033
+ name: string;
1034
+ };
1035
+
1036
+ if (kvDataEN?.name) {
1037
+ usedNamesEN.add(kvDataEN.name); // ✅ Store all used EN names
1038
+
1039
+ if (!nameUsageMapEN[kvDataEN.name]) {
1040
+ nameUsageMapEN[kvDataEN.name] = [];
1041
+ }
1042
+ nameUsageMapEN[kvDataEN.name].push(key.name); // Store the key for that name
1043
+ }
1044
+
1045
+ if (kvDataPT?.name) {
1046
+ usedNamesPT.add(kvDataPT.name); // ✅ Store all used PT names
1047
+ }
1048
+ }
1049
+
1050
+ cursor = newCursor;
1051
+ } while (cursor && (!limit || totalKeysScanned < limit));
1052
+
1053
+ // ✅ Find names used more than once (EN)
1054
+ let duplicateENNames = Object.entries(nameUsageMapEN)
1055
+ .filter(([_, combinations]) => combinations.length > 1)
1056
+ .map(([name, combinations]) => ({
1057
+ name,
1058
+ combinations,
1059
+ }));
1060
+
1061
+ // ✅ If limit is set, trim duplicates list
1062
+ if (limit && duplicateENNames.length > limit) {
1063
+ duplicateENNames = duplicateENNames.slice(0, limit);
1064
+ }
1065
+
1066
+ console.log(
1067
+ `✅ Found ${duplicateENNames.length} duplicate EN names for concept "${conceptSlug}" out of ${totalKeysScanned} entries.`
1068
+ );
1069
+
1070
+ return {
1071
+ duplicateEntries: duplicateENNames, // ✅ List of EN names that need regeneration
1072
+ usedNamesEN, // ✅ Set of all unique EN names
1073
+ usedNamesPT, // ✅ Set of all unique PT names
1074
+ };
1075
+ }
1076
+
1077
+ async regenerateDuplicateNamesForConcept(
1078
+ conceptSlug: Concept,
1079
+ limit?: number
1080
+ ) {
1081
+ console.log(`🔄 Processing duplicate names for concept: ${conceptSlug}...`);
1082
+
1083
+ const { duplicateEntries, usedNamesEN, usedNamesPT } =
1084
+ await this.findDuplicateNamesAndUsedNamesForConcept(conceptSlug, limit);
1085
+
1086
+ if (duplicateEntries.length === 0) {
1087
+ console.log(
1088
+ `🎉 No duplicates found for concept: ${conceptSlug}. Nothing to regenerate.`
1089
+ );
1090
+ return { message: `No duplicates detected for ${conceptSlug}.` };
1091
+ }
1092
+
1093
+ for (const { name: oldNameEN, combinations } of duplicateEntries) {
1094
+ for (let i = 1; i < combinations.length; i++) {
1095
+ const [_, lang, concept, combinationString] =
1096
+ combinations[i].split(':');
1097
+
1098
+ if (
1099
+ !lang ||
1100
+ !concept ||
1101
+ !combinationString ||
1102
+ concept !== conceptSlug
1103
+ ) {
1104
+ console.warn(`⚠️ Invalid KV key format: ${combinations[i]}`);
1105
+ continue;
1106
+ }
1107
+
1108
+ const kvKeyEN = buildConceptKVKey(
1109
+ 'en-us',
1110
+ concept as Concept,
1111
+ combinationString
1112
+ );
1113
+ const kvKeyPT = buildConceptKVKey(
1114
+ 'pt-br',
1115
+ concept as Concept,
1116
+ combinationString
1117
+ );
1118
+
1119
+ const kvDataEN = (await this.context.env.KV_CONCEPTS.get(
1120
+ kvKeyEN,
1121
+ 'json'
1122
+ )) as {
1123
+ name: string;
1124
+ description: string;
1125
+ };
1126
+ const kvDataPT = (await this.context.env.KV_CONCEPTS.get(
1127
+ kvKeyPT,
1128
+ 'json'
1129
+ )) as {
1130
+ name: string;
1131
+ };
1132
+
1133
+ console.log(
1134
+ `🎭 Regenerating name for: ${kvKeyEN} (Old Name: "${oldNameEN}")`
1135
+ );
1136
+
1137
+ let newNameEN: string | null = null;
1138
+ let newNamePT: string | null = null;
1139
+ let attempts = 0;
1140
+ const maxAttempts = 3;
1141
+
1142
+ while (attempts < maxAttempts) {
1143
+ attempts++;
1144
+
1145
+ const messages = this.context
1146
+ .buildLLMMessages()
1147
+ .regenerateConceptName({
1148
+ oldNameEN: kvDataEN.name,
1149
+ description: kvDataEN.description,
1150
+ usedNamesEN: Array.from(usedNamesEN), // ✅ Ensure array format
1151
+ });
1152
+
1153
+ const aiResponse = await this.context
1154
+ .api()
1155
+ .callTogether.single(messages, {});
1156
+ if (!aiResponse) {
1157
+ console.warn(
1158
+ `⚠️ AI failed to generate a new name for ${kvKeyEN}, skipping.`
1159
+ );
1160
+ break;
1161
+ }
1162
+
1163
+ // ✅ Parse AI response to extract both EN and PT names
1164
+ const [generatedEN, generatedPT] = aiResponse
1165
+ .split('\n')
1166
+ .map((s) => s.trim());
1167
+
1168
+ // ✅ Validate that the generated names are not duplicates
1169
+ if (!usedNamesEN.has(generatedEN) && !usedNamesPT.has(generatedPT)) {
1170
+ newNameEN = generatedEN;
1171
+ newNamePT = generatedPT;
1172
+ break; // ✅ Found unique names, exit retry loop
1173
+ }
1174
+
1175
+ console.warn(
1176
+ `⚠️ AI suggested a duplicate name: EN - "${generatedEN}", PT - "${generatedPT}". Retrying... (Attempt ${attempts}/${maxAttempts})`
1177
+ );
1178
+ }
1179
+
1180
+ if (!newNameEN || !newNamePT) {
1181
+ console.error(
1182
+ `🚨 Failed to generate a unique name for ${kvKeyEN} after ${maxAttempts} attempts.`
1183
+ );
1184
+ continue;
1185
+ }
1186
+
1187
+ console.log(
1188
+ `✅ Storing new names for ${kvKeyEN}: EN - "${newNameEN}", PT - "${newNamePT}"`
1189
+ );
1190
+
1191
+ // ✅ Update KV with new names (EN)
1192
+ Object.assign(kvDataEN, { name: newNameEN });
1193
+ await this.context.env.KV_CONCEPTS.put(
1194
+ kvKeyEN,
1195
+ JSON.stringify(kvDataEN)
1196
+ );
1197
+
1198
+ // ✅ Update KV with new names (PT)
1199
+ Object.assign(kvDataPT, { name: newNamePT });
1200
+ await this.context.env.KV_CONCEPTS.put(
1201
+ kvKeyPT,
1202
+ JSON.stringify(kvDataPT)
1203
+ );
1204
+
1205
+ // ✅ Add new names to the used names sets to prevent future reuse
1206
+ usedNamesEN.add(newNameEN);
1207
+ usedNamesPT.add(newNamePT);
1208
+
1209
+ // ✅ Stop if limit is reached
1210
+ if (limit && --limit <= 0) {
1211
+ console.log(`🎯 Limit reached, stopping further processing.`);
1212
+ return { message: `Regeneration limit reached for ${conceptSlug}.` };
1213
+ }
1214
+ }
1215
+ }
1216
+
1217
+ console.log(
1218
+ `🎉 Duplicate names regenerated successfully for concept: ${conceptSlug}.`
1219
+ );
1220
+ return {
1221
+ message: `Duplicate names processed and regenerated for ${conceptSlug}.`,
1222
+ };
1223
+ }
765
1224
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zodic/shared",
3
- "version": "0.0.196",
3
+ "version": "0.0.197",
4
4
  "module": "index.ts",
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -104,7 +104,7 @@ export const buildLLMMessages = (env: BackendBindings) => ({
104
104
  existingNames: string[];
105
105
  }): ChatMessages => {
106
106
  const zodiacCombination = mapConceptToPlanets(conceptSlug, combination);
107
-
107
+
108
108
  return [
109
109
  {
110
110
  role: 'system',
@@ -179,4 +179,47 @@ export const buildLLMMessages = (env: BackendBindings) => ({
179
179
  },
180
180
  ];
181
181
  },
182
+
183
+ regenerateConceptName: ({
184
+ oldNameEN,
185
+ description,
186
+ usedNamesEN,
187
+ }: {
188
+ oldNameEN: string;
189
+ description: string;
190
+ usedNamesEN: string[];
191
+ }): ChatMessages => [
192
+ {
193
+ role: 'system',
194
+ 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]
210
+ `,
211
+ },
212
+ {
213
+ role: 'user',
214
+ 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.
222
+ `,
223
+ },
224
+ ],
182
225
  });