@zodic/shared 0.0.165 → 0.0.167
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/backend.ts
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './ConceptNameDO';
|
|
@@ -31,20 +31,15 @@ export class ConceptService {
|
|
|
31
31
|
|
|
32
32
|
const kvStore = this.context.kvConceptsStore();
|
|
33
33
|
const kvFailuresStore = this.context.kvConceptFailuresStore();
|
|
34
|
-
const kvNamesStore = this.context.kvConceptNamesStore(); // ✅ New
|
|
34
|
+
const kvNamesStore = this.context.kvConceptNamesStore(); // ✅ New Durable Object
|
|
35
35
|
const kvKeyEN = buildConceptKVKey('en-us', conceptSlug, combinationString);
|
|
36
36
|
const kvKeyPT = buildConceptKVKey('pt-br', conceptSlug, combinationString);
|
|
37
37
|
const failureKey = `failures:basic-info:${conceptSlug}:${combinationString}`;
|
|
38
38
|
|
|
39
|
-
const namesKeyEN = `${conceptSlug}:en-us`;
|
|
40
|
-
const namesKeyPT = `${conceptSlug}:pt-br`;
|
|
41
|
-
const reportKey = `report:${conceptSlug}`; // ✅ New tracking key
|
|
42
|
-
|
|
43
39
|
// ✅ Check if data already exists
|
|
44
40
|
if (!override) {
|
|
45
41
|
const existingEN = await this.getKVConcept(kvKeyEN);
|
|
46
42
|
const existingPT = await this.getKVConcept(kvKeyPT);
|
|
47
|
-
|
|
48
43
|
if (
|
|
49
44
|
existingEN.name &&
|
|
50
45
|
existingEN.description &&
|
|
@@ -56,7 +51,7 @@ export class ConceptService {
|
|
|
56
51
|
existingPT.poem.length > 0
|
|
57
52
|
) {
|
|
58
53
|
console.log(
|
|
59
|
-
`⚡ Basic info already exists for ${conceptSlug},
|
|
54
|
+
`⚡ Basic info already exists for ${conceptSlug}, skipping.`
|
|
60
55
|
);
|
|
61
56
|
return;
|
|
62
57
|
}
|
|
@@ -71,75 +66,82 @@ export class ConceptService {
|
|
|
71
66
|
attempts++;
|
|
72
67
|
console.log(`🔄 Attempt ${attempts} to generate basic info...`);
|
|
73
68
|
|
|
74
|
-
// ✅ Fetch the
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
)
|
|
78
|
-
let
|
|
79
|
-
|
|
80
|
-
)
|
|
69
|
+
// ✅ Fetch the latest name list **directly from Durable Object**
|
|
70
|
+
const response = await fetch(
|
|
71
|
+
`${this.context.env.API_BASE_URL}/concept-names/${conceptSlug}`
|
|
72
|
+
);
|
|
73
|
+
let allNamesEN: string[] = [];
|
|
74
|
+
let allNamesPT: string[] = [];
|
|
75
|
+
if (response.ok) {
|
|
76
|
+
const data = (await response.json()) as {
|
|
77
|
+
'en-us': string[];
|
|
78
|
+
'pt-br': string[];
|
|
79
|
+
};
|
|
80
|
+
allNamesEN = data['en-us'] || [];
|
|
81
|
+
allNamesPT = data['pt-br'] || [];
|
|
82
|
+
}
|
|
81
83
|
|
|
82
|
-
//
|
|
83
|
-
const recentNamesEN = allNamesEN.slice(-144);
|
|
84
|
+
const recentNamesEN = allNamesEN.slice(-144); // 🔥 Only pass the last 144 names
|
|
84
85
|
const recentNamesPT = allNamesPT.slice(-144);
|
|
85
86
|
|
|
86
|
-
// ✅
|
|
87
|
+
// ✅ Generate content ensuring uniqueness
|
|
87
88
|
const messages = this.context
|
|
88
89
|
.buildLLMMessages()
|
|
89
90
|
.generateConceptBasicInfo({
|
|
90
91
|
combination: combinationString,
|
|
91
92
|
conceptSlug,
|
|
92
|
-
existingNames: recentNamesEN,
|
|
93
|
+
existingNames: recentNamesEN,
|
|
93
94
|
});
|
|
94
95
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
96
|
+
let aiResponse = await this.context
|
|
97
|
+
.api()
|
|
98
|
+
.callTogether.single(messages, {});
|
|
99
|
+
if (!aiResponse) {
|
|
98
100
|
throw new Error(`❌ AI returned an empty response`);
|
|
99
101
|
}
|
|
100
102
|
|
|
101
103
|
phase = 'cleaning';
|
|
102
|
-
|
|
104
|
+
aiResponse = this.cleanAIResponse(aiResponse);
|
|
103
105
|
|
|
104
106
|
phase = 'parsing';
|
|
105
107
|
|
|
106
|
-
// ✅ Parse response for both languages
|
|
107
108
|
let { nameEN, descriptionEN, poemEN, namePT, descriptionPT, poemPT } =
|
|
108
|
-
this.parseBasicInfoResponse(
|
|
109
|
+
this.parseBasicInfoResponse(aiResponse, conceptSlug);
|
|
109
110
|
|
|
110
|
-
//
|
|
111
|
-
if (allNamesEN.includes(nameEN)) {
|
|
112
|
-
console.warn(
|
|
111
|
+
// ✅ Check if generated names are unique **right after parsing**
|
|
112
|
+
if (allNamesEN.includes(nameEN) || allNamesPT.includes(namePT)) {
|
|
113
|
+
console.warn(
|
|
114
|
+
`⚠️ Duplicate Name Detected: "${nameEN}" or "${namePT}"`
|
|
115
|
+
);
|
|
113
116
|
if (attempts >= maxAttempts) {
|
|
114
117
|
throw new Error(
|
|
115
|
-
`🚨 Could not generate a unique
|
|
118
|
+
`🚨 Could not generate a unique name after ${maxAttempts} attempts.`
|
|
116
119
|
);
|
|
117
120
|
}
|
|
118
|
-
console.log('🔁 Retrying due to duplicate
|
|
121
|
+
console.log('🔁 Retrying due to duplicate name...');
|
|
119
122
|
continue;
|
|
120
123
|
}
|
|
121
124
|
|
|
122
|
-
//
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
125
|
+
// ✅ **Immediately update Durable Object with the new name**
|
|
126
|
+
await fetch(
|
|
127
|
+
`${this.context.env.API_BASE_URL}/concept-names/${conceptSlug}/add`,
|
|
128
|
+
{
|
|
129
|
+
method: 'POST',
|
|
130
|
+
body: JSON.stringify({ language: 'en-us', name: nameEN }),
|
|
131
|
+
headers: { 'Content-Type': 'application/json' },
|
|
129
132
|
}
|
|
130
|
-
|
|
131
|
-
continue;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// ✅ Append new names to the full list
|
|
135
|
-
allNamesEN.push(nameEN);
|
|
136
|
-
allNamesPT.push(namePT);
|
|
133
|
+
);
|
|
137
134
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
135
|
+
await fetch(
|
|
136
|
+
`${this.context.env.API_BASE_URL}/concept-names/${conceptSlug}/add`,
|
|
137
|
+
{
|
|
138
|
+
method: 'POST',
|
|
139
|
+
body: JSON.stringify({ language: 'pt-br', name: namePT }),
|
|
140
|
+
headers: { 'Content-Type': 'application/json' },
|
|
141
|
+
}
|
|
142
|
+
);
|
|
141
143
|
|
|
142
|
-
//
|
|
144
|
+
// ✅ Store the generated basic info in KV
|
|
143
145
|
const conceptEN = await this.getKVConcept(kvKeyEN);
|
|
144
146
|
Object.assign(conceptEN, {
|
|
145
147
|
name: nameEN,
|
|
@@ -149,7 +151,6 @@ export class ConceptService {
|
|
|
149
151
|
});
|
|
150
152
|
await kvStore.put(kvKeyEN, JSON.stringify(conceptEN));
|
|
151
153
|
|
|
152
|
-
// 🇧🇷 Portuguese version
|
|
153
154
|
const conceptPT = await this.getKVConcept(kvKeyPT);
|
|
154
155
|
Object.assign(conceptPT, {
|
|
155
156
|
name: namePT,
|
|
@@ -160,19 +161,7 @@ export class ConceptService {
|
|
|
160
161
|
await kvStore.put(kvKeyPT, JSON.stringify(conceptPT));
|
|
161
162
|
|
|
162
163
|
console.log(
|
|
163
|
-
`✅
|
|
164
|
-
);
|
|
165
|
-
|
|
166
|
-
// ✅ Increment report tracking successful inserts
|
|
167
|
-
let currentCount = parseInt(
|
|
168
|
-
(await kvNamesStore.get(reportKey)) || '0',
|
|
169
|
-
10
|
|
170
|
-
);
|
|
171
|
-
currentCount += 1;
|
|
172
|
-
await kvNamesStore.put(reportKey, currentCount.toString());
|
|
173
|
-
|
|
174
|
-
console.log(
|
|
175
|
-
`📊 Updated progress for ${conceptSlug}: ${currentCount} successful inserts.`
|
|
164
|
+
`✅ Stored basic info for ${conceptSlug}, combination: ${combinationString}.`
|
|
176
165
|
);
|
|
177
166
|
|
|
178
167
|
return; // ✅ Exit loop if successful
|
|
@@ -181,29 +170,12 @@ export class ConceptService {
|
|
|
181
170
|
`❌ Attempt ${attempts} failed at phase: ${phase}`,
|
|
182
171
|
(error as Error).message
|
|
183
172
|
);
|
|
184
|
-
|
|
185
|
-
// ✅ Store failure details in KV for manual review
|
|
186
|
-
await kvFailuresStore.put(
|
|
187
|
-
failureKey,
|
|
188
|
-
JSON.stringify({
|
|
189
|
-
error: (error as Error).message,
|
|
190
|
-
attempt: attempts,
|
|
191
|
-
phase,
|
|
192
|
-
conceptSlug,
|
|
193
|
-
combinationString,
|
|
194
|
-
timestamp: new Date().toISOString(),
|
|
195
|
-
})
|
|
196
|
-
);
|
|
197
|
-
|
|
198
173
|
if (attempts >= maxAttempts) {
|
|
199
|
-
console.error(
|
|
200
|
-
`🚨 All ${maxAttempts} attempts failed during ${phase}. Logged failure.`
|
|
201
|
-
);
|
|
174
|
+
console.error(`🚨 All ${maxAttempts} attempts failed.`);
|
|
202
175
|
throw new Error(
|
|
203
|
-
`Failed to generate basic info after ${maxAttempts} attempts
|
|
176
|
+
`Failed to generate basic info after ${maxAttempts} attempts.`
|
|
204
177
|
);
|
|
205
178
|
}
|
|
206
|
-
|
|
207
179
|
console.log('🔁 Retrying...');
|
|
208
180
|
await new Promise((resolve) => setTimeout(resolve, 500)); // ⏳ Small delay before retrying
|
|
209
181
|
}
|
|
@@ -212,7 +184,7 @@ export class ConceptService {
|
|
|
212
184
|
|
|
213
185
|
private cleanAIResponse(response: string): string {
|
|
214
186
|
console.log('🧼 Cleaning AI Response...');
|
|
215
|
-
|
|
187
|
+
|
|
216
188
|
return response
|
|
217
189
|
.replace(/\*/g, '') // ✅ Remove asterisks
|
|
218
190
|
.replace(/#/g, '') // ✅ Remove hashtags
|
|
@@ -232,23 +204,23 @@ export class ConceptService {
|
|
|
232
204
|
poemPT: string[];
|
|
233
205
|
} {
|
|
234
206
|
console.log('📌 Parsing basic info response from ChatGPT:', response);
|
|
235
|
-
|
|
207
|
+
|
|
236
208
|
// ✅ More flexible regex to handle variations in formatting
|
|
237
209
|
const enMatch = response.match(
|
|
238
210
|
/EN:\s*(?:•\s*)?Name:\s*(.+?)\s*(?:•\s*)?Description:\s*([\s\S]+?)\s*(?:•\s*)?Poetic Passage:\s*([\s\S]+?)\s*(?=PT:|$)/
|
|
239
211
|
);
|
|
240
212
|
const ptMatch = response.match(
|
|
241
|
-
/PT:\s*(?:•\s*)?Nome:\s*(.+?)\s*(?:•\s*)?Descrição:\s*([\s\S]+?)\s*(?:•\s*)?Passagem Poética:\s*([\s\S]+)/
|
|
213
|
+
/PT:\s*(?:•\s*)?Nome:\s*(.+?)\s*(?:•\s*)?Descrição:\s*([\s\S]+?)\s*(?:•\s*)?Passagem Poética:\s*([\s\S]+)/
|
|
242
214
|
);
|
|
243
|
-
|
|
215
|
+
|
|
244
216
|
if (!enMatch || !ptMatch) {
|
|
245
217
|
console.error('❌ Invalid basic info response format:', response);
|
|
246
218
|
throw new Error('Invalid basic info response format');
|
|
247
219
|
}
|
|
248
|
-
|
|
220
|
+
|
|
249
221
|
// ✅ Function to clean text (removes leading/trailing spaces & unwanted symbols)
|
|
250
222
|
const cleanText = (text: string) => text.trim().replace(/\*/g, '');
|
|
251
|
-
|
|
223
|
+
|
|
252
224
|
// ✅ Parse and clean extracted content
|
|
253
225
|
const nameEN = cleanText(enMatch[1]);
|
|
254
226
|
const descriptionEN = cleanText(enMatch[2]);
|
|
@@ -256,37 +228,45 @@ export class ConceptService {
|
|
|
256
228
|
.trim()
|
|
257
229
|
.split(/\n+/)
|
|
258
230
|
.map((line) => cleanText(line));
|
|
259
|
-
|
|
231
|
+
|
|
260
232
|
const namePT = cleanText(ptMatch[1]);
|
|
261
233
|
const descriptionPT = cleanText(ptMatch[2]);
|
|
262
234
|
const poemPT = ptMatch[3]
|
|
263
235
|
.trim()
|
|
264
236
|
.split(/\n+/)
|
|
265
237
|
.map((line) => cleanText(line));
|
|
266
|
-
|
|
238
|
+
|
|
267
239
|
// ✅ Determine appropriate replacement text based on conceptSlug
|
|
268
|
-
const conceptPlaceholder = `this ${
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
240
|
+
const conceptPlaceholder = `this ${
|
|
241
|
+
conceptSlug.charAt(0).toUpperCase() + conceptSlug.slice(1)
|
|
242
|
+
}`;
|
|
243
|
+
const conceptPlaceholderCapitalized = `This ${
|
|
244
|
+
conceptSlug.charAt(0).toUpperCase() + conceptSlug.slice(1)
|
|
245
|
+
}`;
|
|
246
|
+
|
|
272
247
|
// ✅ Function to replace occurrences of the name in a text while handling capitalization
|
|
273
248
|
const replaceConceptName = (text: string, conceptName: string) => {
|
|
274
|
-
return text
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
249
|
+
return text
|
|
250
|
+
.replace(
|
|
251
|
+
new RegExp(`(?<=\\.\\s*|^)${conceptName}`, 'g'),
|
|
252
|
+
conceptPlaceholderCapitalized
|
|
253
|
+
)
|
|
254
|
+
.replace(new RegExp(conceptName, 'g'), conceptPlaceholder);
|
|
278
255
|
};
|
|
279
|
-
|
|
256
|
+
|
|
280
257
|
// ✅ Ensure the concept name is not repeated in the description
|
|
281
258
|
const cleanedDescriptionEN = replaceConceptName(descriptionEN, nameEN);
|
|
282
259
|
const cleanedDescriptionPT = replaceConceptName(descriptionPT, namePT);
|
|
283
|
-
|
|
260
|
+
|
|
284
261
|
// ✅ If the name appears in the poem, trigger a retry
|
|
285
|
-
if (
|
|
262
|
+
if (
|
|
263
|
+
poemEN.some((line) => line.includes(nameEN)) ||
|
|
264
|
+
poemPT.some((line) => line.includes(namePT))
|
|
265
|
+
) {
|
|
286
266
|
console.error('❌ Concept name detected in poem, triggering a retry.');
|
|
287
267
|
throw new Error('Concept name found in poem, regenerating response.');
|
|
288
268
|
}
|
|
289
|
-
|
|
269
|
+
|
|
290
270
|
console.log('✅ Successfully parsed and cleaned basic info:', {
|
|
291
271
|
nameEN,
|
|
292
272
|
descriptionEN: cleanedDescriptionEN,
|
|
@@ -295,7 +275,7 @@ export class ConceptService {
|
|
|
295
275
|
descriptionPT: cleanedDescriptionPT,
|
|
296
276
|
poemPT,
|
|
297
277
|
});
|
|
298
|
-
|
|
278
|
+
|
|
299
279
|
return {
|
|
300
280
|
nameEN,
|
|
301
281
|
descriptionEN: cleanedDescriptionEN,
|
|
@@ -317,31 +297,37 @@ export class ConceptService {
|
|
|
317
297
|
console.log(
|
|
318
298
|
`🚀 Generating Leonardo prompt for concept: ${conceptSlug}, combination: ${combinationString}`
|
|
319
299
|
);
|
|
320
|
-
|
|
300
|
+
|
|
321
301
|
// ✅ Build KV keys for both languages
|
|
322
302
|
const kvKeyEN = buildConceptKVKey('en-us', conceptSlug, combinationString);
|
|
323
303
|
const kvKeyPT = buildConceptKVKey('pt-br', conceptSlug, combinationString);
|
|
324
|
-
|
|
304
|
+
|
|
325
305
|
// ✅ Retrieve existing KV data
|
|
326
306
|
const conceptEN = await this.getKVConcept(kvKeyEN);
|
|
327
307
|
const conceptPT = await this.getKVConcept(kvKeyPT);
|
|
328
|
-
|
|
308
|
+
|
|
329
309
|
// ✅ Check if prompt already exists
|
|
330
310
|
if (!override && conceptEN.leonardoPrompt && conceptPT.leonardoPrompt) {
|
|
331
|
-
console.log(
|
|
311
|
+
console.log(
|
|
312
|
+
`⚡ Leonardo prompt already exists for ${conceptSlug}, skipping.`
|
|
313
|
+
);
|
|
332
314
|
return; // ✅ Skip regeneration
|
|
333
315
|
}
|
|
334
|
-
|
|
316
|
+
|
|
335
317
|
// ✅ Ensure basic info is present
|
|
336
318
|
if (
|
|
337
|
-
!conceptEN.name ||
|
|
338
|
-
!
|
|
319
|
+
!conceptEN.name ||
|
|
320
|
+
!conceptEN.description ||
|
|
321
|
+
!conceptEN.poem ||
|
|
322
|
+
!conceptPT.name ||
|
|
323
|
+
!conceptPT.description ||
|
|
324
|
+
!conceptPT.poem
|
|
339
325
|
) {
|
|
340
326
|
throw new Error(
|
|
341
327
|
`❌ Basic info must be populated before generating Leonardo prompt for concept: ${conceptSlug}`
|
|
342
328
|
);
|
|
343
329
|
}
|
|
344
|
-
|
|
330
|
+
|
|
345
331
|
// ✅ Generate prompt request
|
|
346
332
|
const messages = this.context
|
|
347
333
|
.buildLLMMessages()
|
|
@@ -349,7 +335,7 @@ export class ConceptService {
|
|
|
349
335
|
conceptSlug,
|
|
350
336
|
combination: combinationString,
|
|
351
337
|
});
|
|
352
|
-
|
|
338
|
+
|
|
353
339
|
// ✅ Call ChatGPT API
|
|
354
340
|
const response = await this.context.api().callTogether.single(messages, {});
|
|
355
341
|
if (!response) {
|
|
@@ -357,16 +343,16 @@ export class ConceptService {
|
|
|
357
343
|
`❌ Failed to generate Leonardo prompt for concept: ${conceptSlug}`
|
|
358
344
|
);
|
|
359
345
|
}
|
|
360
|
-
|
|
346
|
+
|
|
361
347
|
// ✅ Store the generated prompt in both languages
|
|
362
348
|
conceptEN.leonardoPrompt = response.trim();
|
|
363
349
|
conceptPT.leonardoPrompt = response.trim();
|
|
364
|
-
|
|
350
|
+
|
|
365
351
|
await Promise.all([
|
|
366
352
|
this.context.kvConceptsStore().put(kvKeyEN, JSON.stringify(conceptEN)),
|
|
367
353
|
this.context.kvConceptsStore().put(kvKeyPT, JSON.stringify(conceptPT)),
|
|
368
354
|
]);
|
|
369
|
-
|
|
355
|
+
|
|
370
356
|
console.log(
|
|
371
357
|
`✅ Leonardo prompt stored for concept: ${conceptSlug}, combination: ${combinationString}, in both languages.`
|
|
372
358
|
);
|
|
@@ -501,16 +487,19 @@ export class ConceptService {
|
|
|
501
487
|
structuredContentPT: StructuredConceptContent;
|
|
502
488
|
} {
|
|
503
489
|
console.log('📌 [START] Parsing structured content from ChatGPT response.');
|
|
504
|
-
|
|
490
|
+
|
|
505
491
|
// ✅ Step 1: Clean artifacts before processing
|
|
506
492
|
let cleanedResponse = response
|
|
507
493
|
.replace(/[*#•]/g, '') // Remove bullet points, bold markers, headings
|
|
508
494
|
.replace(/---+/g, '') // Remove dividers (like "---")
|
|
509
495
|
.replace(/\n\s*\n/g, '\n\n') // Normalize multiple line breaks
|
|
510
496
|
.trim();
|
|
511
|
-
|
|
512
|
-
console.log(
|
|
513
|
-
|
|
497
|
+
|
|
498
|
+
console.log(
|
|
499
|
+
'🔹 [CLEANED RESPONSE]:',
|
|
500
|
+
cleanedResponse.slice(0, 500) + '...'
|
|
501
|
+
);
|
|
502
|
+
|
|
514
503
|
const sectionsEN = [
|
|
515
504
|
'Core Identity',
|
|
516
505
|
'Strengths and Challenges',
|
|
@@ -518,7 +507,7 @@ export class ConceptService {
|
|
|
518
507
|
'Emotional Depth',
|
|
519
508
|
'Vision and Aspirations',
|
|
520
509
|
];
|
|
521
|
-
|
|
510
|
+
|
|
522
511
|
const sectionsPT = [
|
|
523
512
|
'Identidade Essencial',
|
|
524
513
|
'Forças e Desafios',
|
|
@@ -526,58 +515,76 @@ export class ConceptService {
|
|
|
526
515
|
'Profundidade Emocional',
|
|
527
516
|
'Visão e Aspirações',
|
|
528
517
|
];
|
|
529
|
-
|
|
518
|
+
|
|
530
519
|
// ✅ Step 2: Extract English and Portuguese content separately
|
|
531
520
|
const enMatches = cleanedResponse.match(/EN:\s*([\s\S]+?)\s*PT:/);
|
|
532
521
|
const ptMatches = cleanedResponse.match(/PT:\s*([\s\S]+)/);
|
|
533
|
-
|
|
522
|
+
|
|
534
523
|
if (!enMatches || !ptMatches) {
|
|
535
|
-
console.error(
|
|
524
|
+
console.error(
|
|
525
|
+
'❌ [ERROR] Missing English or Portuguese content in response.'
|
|
526
|
+
);
|
|
536
527
|
throw new Error('❌ Missing English or Portuguese content in response.');
|
|
537
528
|
}
|
|
538
|
-
|
|
529
|
+
|
|
539
530
|
const enContent = enMatches[1].trim();
|
|
540
531
|
const ptContent = ptMatches[1].trim();
|
|
541
|
-
|
|
542
|
-
console.log(
|
|
543
|
-
|
|
544
|
-
|
|
532
|
+
|
|
533
|
+
console.log(
|
|
534
|
+
'✅ [MATCH SUCCESS] Extracted English Content:',
|
|
535
|
+
enContent.slice(0, 500) + '...'
|
|
536
|
+
);
|
|
537
|
+
console.log(
|
|
538
|
+
'✅ [MATCH SUCCESS] Extracted Portuguese Content:',
|
|
539
|
+
ptContent.slice(0, 500) + '...'
|
|
540
|
+
);
|
|
541
|
+
|
|
545
542
|
// ✅ Step 3: Extract structured sections
|
|
546
543
|
function extractSections(text: string, sectionTitles: string[]) {
|
|
547
544
|
return sectionTitles.map((title) => {
|
|
548
545
|
console.log(`🔍 [PROCESSING] Extracting section: "${title}"`);
|
|
549
|
-
|
|
546
|
+
|
|
550
547
|
// ✅ Improved regex: Detects sections even if AI formatting changes
|
|
551
|
-
const regex = new RegExp(
|
|
548
|
+
const regex = new RegExp(
|
|
549
|
+
`\\d+\\.\\s*${title}:\\s*([\\s\\S]+?)(?=\\n\\d+\\.\\s*[A-Z]|$)`
|
|
550
|
+
);
|
|
552
551
|
const match = text.match(regex);
|
|
553
|
-
|
|
552
|
+
|
|
554
553
|
if (!match) {
|
|
555
554
|
console.warn(`⚠️ [WARNING] Missing section: "${title}"`);
|
|
556
555
|
return { type: 'section', title, content: ['❌ Section Missing'] };
|
|
557
556
|
}
|
|
558
|
-
|
|
557
|
+
|
|
559
558
|
// ✅ Split content into paragraphs and clean them
|
|
560
559
|
const paragraphs = match[1]
|
|
561
560
|
.trim()
|
|
562
561
|
.split(/\n{2,}/) // Handles cases where paragraphs are separated by multiple new lines
|
|
563
562
|
.map((p) => p.trim())
|
|
564
563
|
.filter((p) => p.length > 0); // Removes empty paragraphs
|
|
565
|
-
|
|
564
|
+
|
|
566
565
|
console.log(`✅ [EXTRACTED] "${title}" - Parsed Content:`, paragraphs);
|
|
567
|
-
|
|
566
|
+
|
|
568
567
|
return { type: 'section', title, content: paragraphs };
|
|
569
568
|
});
|
|
570
569
|
}
|
|
571
|
-
|
|
570
|
+
|
|
572
571
|
// ✅ Return parsed and cleaned structured content
|
|
573
572
|
const structuredContentEN = extractSections(enContent, sectionsEN);
|
|
574
573
|
const structuredContentPT = extractSections(ptContent, sectionsPT);
|
|
575
|
-
|
|
576
|
-
console.log(
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
574
|
+
|
|
575
|
+
console.log(
|
|
576
|
+
'🎯 [FINAL RESULT] Parsed English Content:',
|
|
577
|
+
JSON.stringify(structuredContentEN, null, 2)
|
|
578
|
+
);
|
|
579
|
+
console.log(
|
|
580
|
+
'🎯 [FINAL RESULT] Parsed Portuguese Content:',
|
|
581
|
+
JSON.stringify(structuredContentPT, null, 2)
|
|
582
|
+
);
|
|
583
|
+
|
|
584
|
+
console.log(
|
|
585
|
+
'✅ [COMPLETE] Structured content parsing finished successfully.'
|
|
586
|
+
);
|
|
587
|
+
|
|
581
588
|
return {
|
|
582
589
|
structuredContentEN,
|
|
583
590
|
structuredContentPT,
|
package/package.json
CHANGED
|
File without changes
|