@zodic/shared 0.0.145 β 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 -120
- 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,93 +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
|
-
if (
|
|
272
|
+
|
|
273
|
+
if (
|
|
274
|
+
!override &&
|
|
275
|
+
conceptEN.content?.length > 0 &&
|
|
276
|
+
conceptPT.content?.length > 0
|
|
277
|
+
) {
|
|
239
278
|
console.log(`β‘ Content already exists for ${conceptSlug}, skipping.`);
|
|
240
279
|
return; // β
Skip regeneration
|
|
241
280
|
}
|
|
242
|
-
|
|
281
|
+
|
|
243
282
|
let attempts = 0;
|
|
244
283
|
const maxAttempts = 3;
|
|
245
|
-
|
|
284
|
+
|
|
246
285
|
while (attempts < maxAttempts) {
|
|
247
|
-
let phase =
|
|
286
|
+
let phase = 'generation'; // π Track phase
|
|
248
287
|
try {
|
|
249
288
|
attempts++;
|
|
250
289
|
console.log(`π Attempt ${attempts} to generate content...`);
|
|
251
|
-
|
|
290
|
+
|
|
252
291
|
// β
Build messages for LLM
|
|
253
|
-
const messages = this.context
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
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
|
+
|
|
261
302
|
// β
Call ChatGPT API
|
|
262
|
-
const response = await this.context
|
|
303
|
+
const response = await this.context
|
|
304
|
+
.api()
|
|
305
|
+
.callChatGPT.single(messages, {});
|
|
263
306
|
if (!response) {
|
|
264
307
|
throw new Error(`β AI returned an empty response`);
|
|
265
308
|
}
|
|
266
|
-
|
|
267
|
-
phase =
|
|
268
|
-
|
|
309
|
+
|
|
310
|
+
phase = 'parsing'; // β
Switch to parsing phase
|
|
311
|
+
|
|
269
312
|
// β
Parse structured content for both languages
|
|
270
313
|
const { structuredContentEN, structuredContentPT } =
|
|
271
314
|
this.parseStructuredContent(response);
|
|
272
|
-
|
|
315
|
+
|
|
273
316
|
// π Store English content
|
|
274
317
|
Object.assign(conceptEN, { content: structuredContentEN });
|
|
275
318
|
await kvStore.put(kvKeyEN, JSON.stringify(conceptEN));
|
|
276
|
-
|
|
319
|
+
|
|
277
320
|
// π§π· Store Portuguese content
|
|
278
321
|
Object.assign(conceptPT, { content: structuredContentPT });
|
|
279
322
|
await kvStore.put(kvKeyPT, JSON.stringify(conceptPT));
|
|
280
|
-
|
|
323
|
+
|
|
281
324
|
console.log(
|
|
282
325
|
`β
Structured content stored for ${conceptSlug}, combination: ${combinationString}, in both languages.`
|
|
283
326
|
);
|
|
284
327
|
return; // β
Exit loop if successful
|
|
285
|
-
|
|
286
328
|
} catch (error) {
|
|
287
|
-
console.error(
|
|
288
|
-
|
|
329
|
+
console.error(
|
|
330
|
+
`β Attempt ${attempts} failed at phase: ${phase}`,
|
|
331
|
+
(error as Error).message
|
|
332
|
+
);
|
|
333
|
+
|
|
289
334
|
// β
Store failure details in KV for manual review
|
|
290
|
-
await kvFailuresStore.put(
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
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
|
+
|
|
299
347
|
if (attempts >= maxAttempts) {
|
|
300
|
-
console.error(
|
|
301
|
-
|
|
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
|
+
);
|
|
302
354
|
}
|
|
303
|
-
|
|
304
|
-
console.log(
|
|
355
|
+
|
|
356
|
+
console.log('π Retrying...');
|
|
305
357
|
await new Promise((resolve) => setTimeout(resolve, 2000)); // β³ Small delay before retrying
|
|
306
358
|
}
|
|
307
359
|
}
|
|
@@ -311,51 +363,57 @@ export class ConceptService {
|
|
|
311
363
|
structuredContentEN: StructuredConceptContent;
|
|
312
364
|
structuredContentPT: StructuredConceptContent;
|
|
313
365
|
} {
|
|
314
|
-
console.log(
|
|
315
|
-
|
|
366
|
+
console.log(
|
|
367
|
+
'π Parsing structured content from ChatGPT response:',
|
|
368
|
+
response
|
|
369
|
+
);
|
|
370
|
+
|
|
316
371
|
const sections = [
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
372
|
+
'Core Identity',
|
|
373
|
+
'Strengths and Challenges',
|
|
374
|
+
'Path to Fulfillment',
|
|
375
|
+
'Emotional Depth',
|
|
376
|
+
'Vision and Aspirations',
|
|
322
377
|
];
|
|
323
|
-
|
|
378
|
+
|
|
324
379
|
const sectionsPT = [
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
380
|
+
'Identidade Essencial',
|
|
381
|
+
'ForΓ§as e Desafios',
|
|
382
|
+
'Caminho para a Plenitude',
|
|
383
|
+
'Profundidade Emocional',
|
|
384
|
+
'VisΓ£o e AspiraΓ§Γ΅es',
|
|
330
385
|
];
|
|
331
|
-
|
|
386
|
+
|
|
332
387
|
// β
Match English and Portuguese content separately
|
|
333
388
|
const enMatches = response.match(/EN:\s*([\s\S]+?)\s*PT:/);
|
|
334
389
|
const ptMatches = response.match(/PT:\s*([\s\S]+)/);
|
|
335
|
-
|
|
390
|
+
|
|
336
391
|
if (!enMatches || !ptMatches) {
|
|
337
|
-
throw new Error(
|
|
392
|
+
throw new Error('β Missing English or Portuguese content in response.');
|
|
338
393
|
}
|
|
339
|
-
|
|
394
|
+
|
|
340
395
|
const enContent = enMatches[1].trim();
|
|
341
396
|
const ptContent = ptMatches[1].trim();
|
|
342
|
-
|
|
397
|
+
|
|
343
398
|
function extractSections(text: string, sectionTitles: string[]) {
|
|
344
399
|
return sectionTitles.map((title) => {
|
|
345
400
|
const regex = new RegExp(`${title}:\\s*([\\s\\S]+?)(?=\\n\\d|$)`);
|
|
346
401
|
const match = text.match(regex);
|
|
347
|
-
|
|
402
|
+
|
|
348
403
|
if (!match) {
|
|
349
|
-
return { type:
|
|
404
|
+
return { type: 'section', title, content: ['β Section Missing'] };
|
|
350
405
|
}
|
|
351
|
-
|
|
406
|
+
|
|
352
407
|
// β
Split content into paragraphs
|
|
353
|
-
const paragraphs = match[1]
|
|
354
|
-
|
|
355
|
-
|
|
408
|
+
const paragraphs = match[1]
|
|
409
|
+
.trim()
|
|
410
|
+
.split(/\n\n+/)
|
|
411
|
+
.map((p) => p.trim());
|
|
412
|
+
|
|
413
|
+
return { type: 'section', title, content: paragraphs };
|
|
356
414
|
});
|
|
357
415
|
}
|
|
358
|
-
|
|
416
|
+
|
|
359
417
|
return {
|
|
360
418
|
structuredContentEN: extractSections(enContent, sections),
|
|
361
419
|
structuredContentPT: extractSections(ptContent, sectionsPT),
|