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