@zodic/shared 0.0.220 → 0.0.222

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.
@@ -3,10 +3,12 @@ import {
3
3
  KVNamespaceListResult,
4
4
  } from '@cloudflare/workers-types';
5
5
  import { and, eq, sql } from 'drizzle-orm';
6
+ import { drizzle } from 'drizzle-orm/d1';
6
7
  import { inject, injectable } from 'inversify';
7
8
  import 'reflect-metadata';
8
9
  import { v4 as uuidv4 } from 'uuid';
9
10
  import { schema } from '../..';
11
+ import { conceptsData } from '../../db/schema';
10
12
  import { Concept, ControlNetConfig, Languages } from '../../types';
11
13
  import {
12
14
  KVConcept,
@@ -16,8 +18,6 @@ import {
16
18
  import { leonardoInitImages } from '../../utils/initImages';
17
19
  import { buildConceptKVKey } from '../../utils/KVKeysBuilders';
18
20
  import { AppContext } from '../base/AppContext';
19
- import { drizzle } from 'drizzle-orm/d1';
20
- import { conceptsData } from '../../db/schema';
21
21
 
22
22
  @injectable()
23
23
  export class ConceptService {
@@ -1030,11 +1030,16 @@ export class ConceptService {
1030
1030
  };
1031
1031
  }
1032
1032
 
1033
- async regenerateDuplicateNamesForConcept(conceptSlug: Concept, maxEntries?: number) {
1034
- console.log(`🔄 [START] Regenerating duplicate names for concept: ${conceptSlug}...`);
1035
-
1033
+ async regenerateDuplicateNamesForConcept(
1034
+ conceptSlug: Concept,
1035
+ maxEntries?: number
1036
+ ) {
1037
+ console.log(
1038
+ `🔄 [START] Regenerating duplicate names for concept: ${conceptSlug}...`
1039
+ );
1040
+
1036
1041
  const db = drizzle(this.context.env.DB);
1037
-
1042
+
1038
1043
  // ✅ Step 1: Fetch duplicate entries filtered by conceptSlug
1039
1044
  const duplicateEntries = await db
1040
1045
  .select({
@@ -1057,119 +1062,178 @@ export class ConceptService {
1057
1062
  )
1058
1063
  .limit(maxEntries || 50) // ✅ Apply optional limit
1059
1064
  .execute();
1060
-
1065
+
1061
1066
  if (!duplicateEntries.length) {
1062
1067
  console.log(`🎉 No duplicates found for ${conceptSlug}.`);
1063
1068
  return { message: `No duplicates detected for ${conceptSlug}.` };
1064
1069
  }
1065
-
1066
- console.log(`🔍 Found ${duplicateEntries.length} duplicate entries to process.`);
1067
-
1070
+
1071
+ console.log(
1072
+ `🔍 Found ${duplicateEntries.length} duplicate entries to process.`
1073
+ );
1074
+
1075
+ // ✅ Group duplicates by name
1076
+ const duplicateGroups = new Map<string, typeof duplicateEntries>();
1077
+ for (const entry of duplicateEntries) {
1078
+ if (!duplicateGroups.has(entry.name)) {
1079
+ duplicateGroups.set(entry.name, []);
1080
+ }
1081
+ duplicateGroups.get(entry.name)!.push(entry);
1082
+ }
1083
+
1068
1084
  let recentDuplicates = new Set<string>(); // ✅ Track newly generated names
1069
1085
  let updatedEntries = 0;
1070
-
1071
- for (const entry of duplicateEntries) {
1072
- const { id, name, description, combination } = entry;
1073
-
1074
- console.log(`🎭 Processing duplicate ID ${id}: "${name}"`);
1075
-
1076
- let newNameEN: string | null = null;
1077
- let newNamePT: string | null = null;
1078
- let attempts = 0;
1079
- const maxAttempts = 3;
1080
-
1081
- while (attempts < maxAttempts) {
1082
- attempts++;
1083
-
1084
- console.log(`📡 Attempt ${attempts}/${maxAttempts} - Requesting AI for new name...`);
1085
-
1086
- // Generate a new name, passing dynamically tracked duplicates
1087
- const messages = this.context.buildLLMMessages().regenerateConceptName({
1088
- conceptSlug,
1089
- oldNameEN: name,
1090
- description,
1091
- recentNames: Array.from(recentDuplicates),
1092
- });
1093
-
1094
- const aiResponse = await this.context.api().callTogether.single(messages, {});
1095
-
1096
- if (!aiResponse) {
1097
- console.warn(`⚠️ AI failed to generate a new name for ID ${id}, skipping.`);
1098
- break;
1099
- }
1100
-
1101
- console.log(`📝 Raw AI Response:\n${aiResponse}`);
1102
-
1103
- // ✅ Mapping for concept names with proper PT articles
1104
- const conceptMapping = {
1105
- amulet: { en: "Amulet", pt: "Amuleto", articlePT: "O" },
1106
- crown: { en: "Crown", pt: "Coroa", articlePT: "A" },
1107
- scepter: { en: "Scepter", pt: "Cetro", articlePT: "O" },
1108
- lantern: { en: "Lantern", pt: "Candeia", articlePT: "A" },
1109
- ring: { en: "Ring", pt: "Anel", articlePT: "O" },
1110
- orb: { en: "Orb", pt: "Orbe", articlePT: "O" },
1111
- };
1112
-
1113
- const { en: conceptEN, pt: conceptPT, articlePT } = conceptMapping[conceptSlug] || {
1114
- en: conceptSlug,
1115
- pt: conceptSlug,
1116
- articlePT: "O", // Default to "O" if concept is missing
1117
- };
1118
-
1119
- // ✅ Strict regex matching for English and Portuguese
1120
- const enMatch = aiResponse.match(new RegExp(`EN:\\s*The ${conceptEN} of (.+)`));
1121
- const ptMatch = aiResponse.match(new RegExp(`PT:\\s*${articlePT} ${conceptPT} de (.+)`));
1122
-
1123
- if (!enMatch || !ptMatch) {
1124
- console.error(`❌ Invalid AI response format for ID ${id}, retrying...`);
1125
- continue;
1086
+
1087
+ for (const [duplicateName, entries] of duplicateGroups.entries()) {
1088
+ console.log(
1089
+ `🔄 Processing duplicate group: "${duplicateName}" with ${entries.length} occurrences`
1090
+ );
1091
+
1092
+ // Leave the first entry unchanged and only process the extras
1093
+ const [keepEntry, ...entriesToProcess] = entries;
1094
+ console.log(`✅ Keeping original name for ID: ${keepEntry.id}`);
1095
+
1096
+ for (const entry of entriesToProcess) {
1097
+ const { id, description, combination } = entry;
1098
+
1099
+ console.log(`🎭 Processing duplicate ID ${id}: "${duplicateName}"`);
1100
+
1101
+ let newNameEN: string | null = null;
1102
+ let newNamePT: string | null = null;
1103
+ let attempts = 0;
1104
+ const maxAttempts = 3;
1105
+
1106
+ while (attempts < maxAttempts) {
1107
+ attempts++;
1108
+
1109
+ console.log(
1110
+ `📡 Attempt ${attempts}/${maxAttempts} - Requesting AI for new name...`
1111
+ );
1112
+
1113
+ // Generate a new name, passing dynamically tracked duplicates
1114
+ const messages = this.context
1115
+ .buildLLMMessages()
1116
+ .regenerateConceptName({
1117
+ conceptSlug,
1118
+ oldNameEN: duplicateName,
1119
+ description,
1120
+ recentNames: Array.from(recentDuplicates),
1121
+ });
1122
+
1123
+ const aiResponse = await this.context
1124
+ .api()
1125
+ .callTogether.single(messages, {});
1126
+
1127
+ if (!aiResponse) {
1128
+ console.warn(
1129
+ `⚠️ AI failed to generate a new name for ID ${id}, skipping.`
1130
+ );
1131
+ break;
1132
+ }
1133
+
1134
+ console.log(`📝 Raw AI Response:\n${aiResponse}`);
1135
+
1136
+ // Mapping for concept names with proper PT articles
1137
+ const conceptMapping = {
1138
+ amulet: { en: 'Amulet', pt: 'Amuleto', articlePT: 'O' },
1139
+ crown: { en: 'Crown', pt: 'Coroa', articlePT: 'A' },
1140
+ scepter: { en: 'Scepter', pt: 'Cetro', articlePT: 'O' },
1141
+ lantern: { en: 'Lantern', pt: 'Candeia', articlePT: 'A' },
1142
+ ring: { en: 'Ring', pt: 'Anel', articlePT: 'O' },
1143
+ orb: { en: 'Orb', pt: 'Orbe', articlePT: 'O' },
1144
+ };
1145
+
1146
+ const {
1147
+ en: conceptEN,
1148
+ pt: conceptPT,
1149
+ articlePT,
1150
+ } = conceptMapping[conceptSlug] || {
1151
+ en: conceptSlug,
1152
+ pt: conceptSlug,
1153
+ articlePT: 'O', // Default to "O" if concept is missing
1154
+ };
1155
+
1156
+ // ✅ Strict regex matching for English and Portuguese
1157
+ const enMatch = aiResponse.match(
1158
+ new RegExp(`EN:\\s*The ${conceptEN} of (.+)`)
1159
+ );
1160
+ const ptMatch = aiResponse.match(
1161
+ new RegExp(`PT:\\s*${articlePT} ${conceptPT} de (.+)`)
1162
+ );
1163
+
1164
+ if (!enMatch || !ptMatch) {
1165
+ console.error(
1166
+ `❌ Invalid AI response format for ID ${id}, retrying...`
1167
+ );
1168
+ continue;
1169
+ }
1170
+
1171
+ newNameEN = `The ${conceptEN} of ${enMatch[1].trim()}`;
1172
+ newNamePT = `${articlePT} ${conceptPT} de ${ptMatch[1].trim()}`;
1173
+
1174
+ console.log(
1175
+ `🎨 Extracted Names: EN - "${newNameEN}", PT - "${newNamePT}"`
1176
+ );
1177
+
1178
+ // ✅ Check if the new name is already in use in D1 before updating
1179
+ const existingEntry = await db
1180
+ .select({ id: conceptsData.id })
1181
+ .from(conceptsData)
1182
+ .where(and(eq(conceptsData.name, newNameEN)))
1183
+ .execute();
1184
+
1185
+ if (existingEntry.length > 0 || recentDuplicates.has(newNameEN)) {
1186
+ console.warn(`⚠️ Duplicate detected: "${newNameEN}", retrying...`);
1187
+ continue;
1188
+ }
1189
+
1190
+ recentDuplicates.add(newNameEN);
1191
+ break; // ✅ Found a valid new name
1126
1192
  }
1127
-
1128
- newNameEN = `The ${conceptEN} of ${enMatch[1].trim()}`;
1129
- newNamePT = `${articlePT} ${conceptPT} de ${ptMatch[1].trim()}`;
1130
-
1131
- console.log(`🎨 Extracted Names: EN - "${newNameEN}", PT - "${newNamePT}"`);
1132
-
1133
- // ✅ Check for duplicate avoidance
1134
- if (recentDuplicates.has(newNameEN)) {
1135
- console.warn(`⚠️ Duplicate detected: "${newNameEN}", retrying...`);
1193
+
1194
+ if (!newNameEN || !newNamePT) {
1195
+ console.error(
1196
+ `🚨 Failed to generate a unique name for ID ${id} after ${maxAttempts} attempts.`
1197
+ );
1136
1198
  continue;
1137
1199
  }
1138
-
1139
- recentDuplicates.add(newNameEN);
1140
- break; // Found a valid new name
1141
- }
1142
-
1143
- if (!newNameEN || !newNamePT) {
1144
- console.error(`🚨 Failed to generate a unique name for ID ${id} after ${maxAttempts} attempts.`);
1145
- continue;
1200
+
1201
+ console.log(
1202
+ `✅ Updating names: EN - "${newNameEN}", PT - "${newNamePT}"`
1203
+ );
1204
+
1205
+ // Update English name using the ID
1206
+ await db
1207
+ .update(conceptsData)
1208
+ .set({ name: newNameEN })
1209
+ .where(
1210
+ and(eq(conceptsData.id, id), eq(conceptsData.language, 'en-us'))
1211
+ )
1212
+ .execute();
1213
+
1214
+ // ✅ Convert English ID to Portuguese ID by replacing `:en-us:` with `:pt-br:`
1215
+ const ptId = id.replace(':en-us:', ':pt-br:');
1216
+
1217
+ // ✅ Update Portuguese name using the derived PT ID
1218
+ await db
1219
+ .update(conceptsData)
1220
+ .set({ name: newNamePT })
1221
+ .where(
1222
+ and(eq(conceptsData.id, ptId), eq(conceptsData.language, 'pt-br'))
1223
+ )
1224
+ .execute();
1225
+
1226
+ console.log(
1227
+ `📝 Updated IDs: EN (${id}) -> "${newNameEN}", PT (${ptId}) -> "${newNamePT}"`
1228
+ );
1229
+
1230
+ updatedEntries++;
1146
1231
  }
1147
-
1148
- console.log(`✅ Updating names: EN - "${newNameEN}", PT - "${newNamePT}"`);
1149
-
1150
- // ✅ Update English name using the ID
1151
- await db
1152
- .update(conceptsData)
1153
- .set({ name: newNameEN })
1154
- .where(and(eq(conceptsData.id, id), eq(conceptsData.language, 'en-us')))
1155
- .execute();
1156
-
1157
- // ✅ Convert English ID to Portuguese ID by replacing `:en-us:` with `:pt-br:`
1158
- const ptId = id.replace(':en-us:', ':pt-br:');
1159
-
1160
- // ✅ Update Portuguese name using the derived PT ID
1161
- await db
1162
- .update(conceptsData)
1163
- .set({ name: newNamePT })
1164
- .where(and(eq(conceptsData.id, ptId), eq(conceptsData.language, 'pt-br')))
1165
- .execute();
1166
-
1167
- console.log(`📝 Updated IDs: EN (${id}) -> "${newNameEN}", PT (${ptId}) -> "${newNamePT}"`);
1168
-
1169
- updatedEntries++;
1170
1232
  }
1171
-
1172
- console.log(`🎉 [COMPLETE] Regenerated ${updatedEntries} duplicate names for concept: ${conceptSlug}.`);
1233
+
1234
+ console.log(
1235
+ `🎉 [COMPLETE] Regenerated ${updatedEntries} duplicate names for concept: ${conceptSlug}.`
1236
+ );
1173
1237
  return {
1174
1238
  message: `Duplicate names processed and regenerated for ${conceptSlug}.`,
1175
1239
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zodic/shared",
3
- "version": "0.0.220",
3
+ "version": "0.0.222",
4
4
  "module": "index.ts",
5
5
  "type": "module",
6
6
  "publishConfig": {