@zodic/shared 0.0.144 β 0.0.146
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 +178 -121
- package/package.json +1 -1
|
@@ -4,7 +4,11 @@ import 'reflect-metadata';
|
|
|
4
4
|
import { v4 as uuidv4 } from 'uuid';
|
|
5
5
|
import { schema } from '../..';
|
|
6
6
|
import { Concept, ControlNetConfig, Languages } from '../../types';
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
KVConcept,
|
|
9
|
+
sizes,
|
|
10
|
+
StructuredConceptContent,
|
|
11
|
+
} from '../../types/scopes/legacy';
|
|
8
12
|
import { leonardoInitImages } from '../../utils/initImages';
|
|
9
13
|
import { buildConceptKVKey } from '../../utils/KVKeysBuilders';
|
|
10
14
|
import { AppContext } from '../base/AppContext';
|
|
@@ -24,98 +28,119 @@ export class ConceptService {
|
|
|
24
28
|
console.log(
|
|
25
29
|
`π Generating basic info for concept: ${conceptSlug}, combination: ${combinationString}, override: ${override}`
|
|
26
30
|
);
|
|
27
|
-
|
|
31
|
+
|
|
28
32
|
const kvStore = this.context.kvConceptsStore();
|
|
29
33
|
const kvFailuresStore = this.context.kvConceptFailuresStore(); // π΄ Replace with actual KV store
|
|
30
|
-
const kvKeyEN = buildConceptKVKey(
|
|
31
|
-
const kvKeyPT = buildConceptKVKey(
|
|
34
|
+
const kvKeyEN = buildConceptKVKey('en-us', conceptSlug, combinationString);
|
|
35
|
+
const kvKeyPT = buildConceptKVKey('pt-br', conceptSlug, combinationString);
|
|
32
36
|
const failureKey = `failures:basic-info:${conceptSlug}:${combinationString}`;
|
|
33
|
-
|
|
37
|
+
|
|
34
38
|
// β
Check if data already exists
|
|
35
39
|
if (!override) {
|
|
36
40
|
const existingEN = await this.getKVConcept(kvKeyEN);
|
|
37
41
|
const existingPT = await this.getKVConcept(kvKeyPT);
|
|
38
|
-
|
|
42
|
+
|
|
39
43
|
if (
|
|
40
|
-
existingEN.name &&
|
|
41
|
-
|
|
44
|
+
existingEN.name &&
|
|
45
|
+
existingEN.description &&
|
|
46
|
+
Array.isArray(existingEN.poem) &&
|
|
47
|
+
existingEN.poem.length > 0 &&
|
|
48
|
+
existingPT.name &&
|
|
49
|
+
existingPT.description &&
|
|
50
|
+
Array.isArray(existingPT.poem) &&
|
|
51
|
+
existingPT.poem.length > 0
|
|
42
52
|
) {
|
|
43
|
-
console.log(
|
|
53
|
+
console.log(
|
|
54
|
+
`β‘ Basic info already exists for ${conceptSlug}, combination: ${combinationString}. Skipping.`
|
|
55
|
+
);
|
|
44
56
|
return; // β
Skip regeneration
|
|
45
57
|
}
|
|
46
58
|
}
|
|
47
|
-
|
|
59
|
+
|
|
48
60
|
let attempts = 0;
|
|
49
61
|
const maxAttempts = 3;
|
|
50
|
-
|
|
62
|
+
|
|
51
63
|
while (attempts < maxAttempts) {
|
|
52
|
-
let phase =
|
|
64
|
+
let phase = 'generation'; // π Track the phase
|
|
53
65
|
try {
|
|
54
66
|
attempts++;
|
|
55
67
|
console.log(`π Attempt ${attempts} to generate basic info...`);
|
|
56
|
-
|
|
68
|
+
|
|
57
69
|
// β
Build the messages to request content
|
|
58
|
-
const messages = this.context
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
70
|
+
const messages = this.context
|
|
71
|
+
.buildLLMMessages()
|
|
72
|
+
.generateConceptBasicInfo({
|
|
73
|
+
combination: combinationString,
|
|
74
|
+
conceptSlug,
|
|
75
|
+
});
|
|
76
|
+
|
|
63
77
|
// β
Call ChatGPT API
|
|
64
|
-
const response = await this.context
|
|
78
|
+
const response = await this.context
|
|
79
|
+
.api()
|
|
80
|
+
.callChatGPT.single(messages, {});
|
|
65
81
|
if (!response) {
|
|
66
82
|
throw new Error(`β AI returned an empty response`);
|
|
67
83
|
}
|
|
68
|
-
|
|
69
|
-
phase =
|
|
70
|
-
|
|
84
|
+
|
|
85
|
+
phase = 'parsing'; // β
Switch to parsing phase
|
|
86
|
+
|
|
71
87
|
// β
Parse response for both languages
|
|
72
88
|
const { nameEN, descriptionEN, poemEN, namePT, descriptionPT, poemPT } =
|
|
73
89
|
this.parseBasicInfoResponse(response);
|
|
74
|
-
|
|
90
|
+
|
|
75
91
|
// π English version
|
|
76
92
|
const conceptEN = await this.getKVConcept(kvKeyEN);
|
|
77
93
|
Object.assign(conceptEN, {
|
|
78
94
|
name: nameEN,
|
|
79
95
|
description: descriptionEN,
|
|
80
96
|
poem: poemEN,
|
|
81
|
-
status:
|
|
97
|
+
status: 'idle',
|
|
82
98
|
});
|
|
83
99
|
await kvStore.put(kvKeyEN, JSON.stringify(conceptEN));
|
|
84
|
-
|
|
100
|
+
|
|
85
101
|
// π§π· Portuguese version
|
|
86
102
|
const conceptPT = await this.getKVConcept(kvKeyPT);
|
|
87
103
|
Object.assign(conceptPT, {
|
|
88
104
|
name: namePT,
|
|
89
105
|
description: descriptionPT,
|
|
90
106
|
poem: poemPT,
|
|
91
|
-
status:
|
|
107
|
+
status: 'idle',
|
|
92
108
|
});
|
|
93
109
|
await kvStore.put(kvKeyPT, JSON.stringify(conceptPT));
|
|
94
|
-
|
|
110
|
+
|
|
95
111
|
console.log(
|
|
96
112
|
`β
Basic info stored for ${conceptSlug}, combination: ${combinationString}, in both languages.`
|
|
97
113
|
);
|
|
98
114
|
return; // β
Exit loop if successful
|
|
99
|
-
|
|
100
115
|
} catch (error) {
|
|
101
|
-
console.error(
|
|
102
|
-
|
|
116
|
+
console.error(
|
|
117
|
+
`β Attempt ${attempts} failed at phase: ${phase}`,
|
|
118
|
+
(error as Error).message
|
|
119
|
+
);
|
|
120
|
+
|
|
103
121
|
// β
Store failure details in KV for manual review
|
|
104
|
-
await kvFailuresStore.put(
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
122
|
+
await kvFailuresStore.put(
|
|
123
|
+
failureKey,
|
|
124
|
+
JSON.stringify({
|
|
125
|
+
error: (error as Error).message,
|
|
126
|
+
attempt: attempts,
|
|
127
|
+
phase, // β
Identify if failure occurred in "generation" or "parsing"
|
|
128
|
+
conceptSlug,
|
|
129
|
+
combinationString,
|
|
130
|
+
timestamp: new Date().toISOString(),
|
|
131
|
+
})
|
|
132
|
+
);
|
|
133
|
+
|
|
113
134
|
if (attempts >= maxAttempts) {
|
|
114
|
-
console.error(
|
|
115
|
-
|
|
135
|
+
console.error(
|
|
136
|
+
`π¨ All ${maxAttempts} attempts failed during ${phase}. Logged failure.`
|
|
137
|
+
);
|
|
138
|
+
throw new Error(
|
|
139
|
+
`Failed to generate basic info after ${maxAttempts} attempts`
|
|
140
|
+
);
|
|
116
141
|
}
|
|
117
|
-
|
|
118
|
-
console.log(
|
|
142
|
+
|
|
143
|
+
console.log('π Retrying...');
|
|
119
144
|
await new Promise((resolve) => setTimeout(resolve, 2000)); // β³ Small delay before retrying
|
|
120
145
|
}
|
|
121
146
|
}
|
|
@@ -130,27 +155,33 @@ export class ConceptService {
|
|
|
130
155
|
poemPT: string[];
|
|
131
156
|
} {
|
|
132
157
|
console.log('π Parsing basic info response from ChatGPT:', response);
|
|
133
|
-
|
|
158
|
+
|
|
134
159
|
const enMatch = response.match(
|
|
135
160
|
/EN:\s*β’\s*Name:\s*(.+?)\s*β’\s*Description:\s*([\s\S]+?)\s*β’\s*Poetic Passage:\s*([\s\S]+?)\s*(?=PT:|$)/
|
|
136
161
|
);
|
|
137
162
|
const ptMatch = response.match(
|
|
138
163
|
/PT:\s*β’\s*Nome:\s*(.+?)\s*β’\s*DescriΓ§Γ£o:\s*([\s\S]+?)\s*β’\s*Passagem PoΓ©tica:\s*([\s\S]+)/
|
|
139
164
|
);
|
|
140
|
-
|
|
165
|
+
|
|
141
166
|
if (!enMatch || !ptMatch) {
|
|
142
167
|
console.error('β Invalid basic info response format:', response);
|
|
143
168
|
throw new Error('Invalid basic info response format');
|
|
144
169
|
}
|
|
145
|
-
|
|
170
|
+
|
|
146
171
|
const nameEN = enMatch[1].trim();
|
|
147
172
|
const descriptionEN = enMatch[2].trim();
|
|
148
|
-
const poemEN = enMatch[3]
|
|
149
|
-
|
|
173
|
+
const poemEN = enMatch[3]
|
|
174
|
+
.trim()
|
|
175
|
+
.split(/\n+/)
|
|
176
|
+
.map((line) => line.trim()); // β
Split into array
|
|
177
|
+
|
|
150
178
|
const namePT = ptMatch[1].trim();
|
|
151
179
|
const descriptionPT = ptMatch[2].trim();
|
|
152
|
-
const poemPT = ptMatch[3]
|
|
153
|
-
|
|
180
|
+
const poemPT = ptMatch[3]
|
|
181
|
+
.trim()
|
|
182
|
+
.split(/\n+/)
|
|
183
|
+
.map((line) => line.trim()); // β
Split into array
|
|
184
|
+
|
|
154
185
|
console.log('β
Successfully parsed basic info:', {
|
|
155
186
|
nameEN,
|
|
156
187
|
descriptionEN,
|
|
@@ -159,7 +190,7 @@ export class ConceptService {
|
|
|
159
190
|
descriptionPT,
|
|
160
191
|
poemPT,
|
|
161
192
|
});
|
|
162
|
-
|
|
193
|
+
|
|
163
194
|
return { nameEN, descriptionEN, poemEN, namePT, descriptionPT, poemPT };
|
|
164
195
|
}
|
|
165
196
|
|
|
@@ -215,94 +246,114 @@ export class ConceptService {
|
|
|
215
246
|
console.log(
|
|
216
247
|
`π Generating content for concept: ${conceptSlug}, combination: ${combinationString}, override: ${override}`
|
|
217
248
|
);
|
|
218
|
-
|
|
249
|
+
|
|
219
250
|
const kvStore = this.context.kvConceptsStore();
|
|
220
251
|
const kvFailuresStore = this.context.kvConceptFailuresStore(); // π΄ Replace with actual KV store
|
|
221
|
-
const kvKeyEN = buildConceptKVKey(
|
|
222
|
-
const kvKeyPT = buildConceptKVKey(
|
|
252
|
+
const kvKeyEN = buildConceptKVKey('en-us', conceptSlug, combinationString);
|
|
253
|
+
const kvKeyPT = buildConceptKVKey('pt-br', conceptSlug, combinationString);
|
|
223
254
|
const failureKey = `failures:content:${conceptSlug}:${combinationString}`;
|
|
224
|
-
|
|
255
|
+
|
|
225
256
|
// β
Ensure basic info is available before generating content
|
|
226
257
|
const conceptEN = await this.getKVConcept(kvKeyEN);
|
|
227
258
|
const conceptPT = await this.getKVConcept(kvKeyPT);
|
|
228
|
-
|
|
259
|
+
|
|
229
260
|
if (
|
|
230
|
-
!conceptEN.name ||
|
|
231
|
-
!
|
|
261
|
+
!conceptEN.name ||
|
|
262
|
+
!conceptEN.description ||
|
|
263
|
+
!conceptEN.poem ||
|
|
264
|
+
!conceptPT.name ||
|
|
265
|
+
!conceptPT.description ||
|
|
266
|
+
!conceptPT.poem
|
|
232
267
|
) {
|
|
233
268
|
throw new Error(
|
|
234
269
|
`β Basic info must be populated before generating content for ${conceptSlug}`
|
|
235
270
|
);
|
|
236
271
|
}
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
272
|
+
|
|
273
|
+
if (
|
|
274
|
+
!override &&
|
|
275
|
+
conceptEN.content?.length > 0 &&
|
|
276
|
+
conceptPT.content?.length > 0
|
|
277
|
+
) {
|
|
240
278
|
console.log(`β‘ Content already exists for ${conceptSlug}, skipping.`);
|
|
241
279
|
return; // β
Skip regeneration
|
|
242
280
|
}
|
|
243
|
-
|
|
281
|
+
|
|
244
282
|
let attempts = 0;
|
|
245
283
|
const maxAttempts = 3;
|
|
246
|
-
|
|
284
|
+
|
|
247
285
|
while (attempts < maxAttempts) {
|
|
248
|
-
let phase =
|
|
286
|
+
let phase = 'generation'; // π Track phase
|
|
249
287
|
try {
|
|
250
288
|
attempts++;
|
|
251
289
|
console.log(`π Attempt ${attempts} to generate content...`);
|
|
252
|
-
|
|
290
|
+
|
|
253
291
|
// β
Build messages for LLM
|
|
254
|
-
const messages = this.context
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
292
|
+
const messages = this.context
|
|
293
|
+
.buildLLMMessages()
|
|
294
|
+
.generateConceptContent({
|
|
295
|
+
conceptSlug,
|
|
296
|
+
combination: combinationString,
|
|
297
|
+
name: conceptEN.name, // Use English name since both languages match in meaning
|
|
298
|
+
description: conceptEN.description,
|
|
299
|
+
poem: conceptEN.poem,
|
|
300
|
+
});
|
|
301
|
+
|
|
262
302
|
// β
Call ChatGPT API
|
|
263
|
-
const response = await this.context
|
|
303
|
+
const response = await this.context
|
|
304
|
+
.api()
|
|
305
|
+
.callChatGPT.single(messages, {});
|
|
264
306
|
if (!response) {
|
|
265
307
|
throw new Error(`β AI returned an empty response`);
|
|
266
308
|
}
|
|
267
|
-
|
|
268
|
-
phase =
|
|
269
|
-
|
|
309
|
+
|
|
310
|
+
phase = 'parsing'; // β
Switch to parsing phase
|
|
311
|
+
|
|
270
312
|
// β
Parse structured content for both languages
|
|
271
313
|
const { structuredContentEN, structuredContentPT } =
|
|
272
314
|
this.parseStructuredContent(response);
|
|
273
|
-
|
|
315
|
+
|
|
274
316
|
// π Store English content
|
|
275
317
|
Object.assign(conceptEN, { content: structuredContentEN });
|
|
276
318
|
await kvStore.put(kvKeyEN, JSON.stringify(conceptEN));
|
|
277
|
-
|
|
319
|
+
|
|
278
320
|
// π§π· Store Portuguese content
|
|
279
321
|
Object.assign(conceptPT, { content: structuredContentPT });
|
|
280
322
|
await kvStore.put(kvKeyPT, JSON.stringify(conceptPT));
|
|
281
|
-
|
|
323
|
+
|
|
282
324
|
console.log(
|
|
283
325
|
`β
Structured content stored for ${conceptSlug}, combination: ${combinationString}, in both languages.`
|
|
284
326
|
);
|
|
285
327
|
return; // β
Exit loop if successful
|
|
286
|
-
|
|
287
328
|
} catch (error) {
|
|
288
|
-
console.error(
|
|
289
|
-
|
|
329
|
+
console.error(
|
|
330
|
+
`β Attempt ${attempts} failed at phase: ${phase}`,
|
|
331
|
+
(error as Error).message
|
|
332
|
+
);
|
|
333
|
+
|
|
290
334
|
// β
Store failure details in KV for manual review
|
|
291
|
-
await kvFailuresStore.put(
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
335
|
+
await kvFailuresStore.put(
|
|
336
|
+
failureKey,
|
|
337
|
+
JSON.stringify({
|
|
338
|
+
error: (error as Error).message,
|
|
339
|
+
attempt: attempts,
|
|
340
|
+
phase, // β
Identify failure phase
|
|
341
|
+
conceptSlug,
|
|
342
|
+
combinationString,
|
|
343
|
+
timestamp: new Date().toISOString(),
|
|
344
|
+
})
|
|
345
|
+
);
|
|
346
|
+
|
|
300
347
|
if (attempts >= maxAttempts) {
|
|
301
|
-
console.error(
|
|
302
|
-
|
|
348
|
+
console.error(
|
|
349
|
+
`π¨ All ${maxAttempts} attempts failed at phase ${phase}. Logged failure.`
|
|
350
|
+
);
|
|
351
|
+
throw new Error(
|
|
352
|
+
`Failed to generate content after ${maxAttempts} attempts`
|
|
353
|
+
);
|
|
303
354
|
}
|
|
304
|
-
|
|
305
|
-
console.log(
|
|
355
|
+
|
|
356
|
+
console.log('π Retrying...');
|
|
306
357
|
await new Promise((resolve) => setTimeout(resolve, 2000)); // β³ Small delay before retrying
|
|
307
358
|
}
|
|
308
359
|
}
|
|
@@ -312,51 +363,57 @@ export class ConceptService {
|
|
|
312
363
|
structuredContentEN: StructuredConceptContent;
|
|
313
364
|
structuredContentPT: StructuredConceptContent;
|
|
314
365
|
} {
|
|
315
|
-
console.log(
|
|
316
|
-
|
|
366
|
+
console.log(
|
|
367
|
+
'π Parsing structured content from ChatGPT response:',
|
|
368
|
+
response
|
|
369
|
+
);
|
|
370
|
+
|
|
317
371
|
const sections = [
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
372
|
+
'Core Identity',
|
|
373
|
+
'Strengths and Challenges',
|
|
374
|
+
'Path to Fulfillment',
|
|
375
|
+
'Emotional Depth',
|
|
376
|
+
'Vision and Aspirations',
|
|
323
377
|
];
|
|
324
|
-
|
|
378
|
+
|
|
325
379
|
const sectionsPT = [
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
380
|
+
'Identidade Essencial',
|
|
381
|
+
'ForΓ§as e Desafios',
|
|
382
|
+
'Caminho para a Plenitude',
|
|
383
|
+
'Profundidade Emocional',
|
|
384
|
+
'VisΓ£o e AspiraΓ§Γ΅es',
|
|
331
385
|
];
|
|
332
|
-
|
|
386
|
+
|
|
333
387
|
// β
Match English and Portuguese content separately
|
|
334
388
|
const enMatches = response.match(/EN:\s*([\s\S]+?)\s*PT:/);
|
|
335
389
|
const ptMatches = response.match(/PT:\s*([\s\S]+)/);
|
|
336
|
-
|
|
390
|
+
|
|
337
391
|
if (!enMatches || !ptMatches) {
|
|
338
|
-
throw new Error(
|
|
392
|
+
throw new Error('β Missing English or Portuguese content in response.');
|
|
339
393
|
}
|
|
340
|
-
|
|
394
|
+
|
|
341
395
|
const enContent = enMatches[1].trim();
|
|
342
396
|
const ptContent = ptMatches[1].trim();
|
|
343
|
-
|
|
397
|
+
|
|
344
398
|
function extractSections(text: string, sectionTitles: string[]) {
|
|
345
399
|
return sectionTitles.map((title) => {
|
|
346
400
|
const regex = new RegExp(`${title}:\\s*([\\s\\S]+?)(?=\\n\\d|$)`);
|
|
347
401
|
const match = text.match(regex);
|
|
348
|
-
|
|
402
|
+
|
|
349
403
|
if (!match) {
|
|
350
|
-
return { type:
|
|
404
|
+
return { type: 'section', title, content: ['β Section Missing'] };
|
|
351
405
|
}
|
|
352
|
-
|
|
406
|
+
|
|
353
407
|
// β
Split content into paragraphs
|
|
354
|
-
const paragraphs = match[1]
|
|
355
|
-
|
|
356
|
-
|
|
408
|
+
const paragraphs = match[1]
|
|
409
|
+
.trim()
|
|
410
|
+
.split(/\n\n+/)
|
|
411
|
+
.map((p) => p.trim());
|
|
412
|
+
|
|
413
|
+
return { type: 'section', title, content: paragraphs };
|
|
357
414
|
});
|
|
358
415
|
}
|
|
359
|
-
|
|
416
|
+
|
|
360
417
|
return {
|
|
361
418
|
structuredContentEN: extractSections(enContent, sections),
|
|
362
419
|
structuredContentPT: extractSections(ptContent, sectionsPT),
|