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