@zodic/shared 0.0.145 β†’ 0.0.147

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.
@@ -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 { KVConcept, sizes, StructuredConceptContent } from '../../types/scopes/legacy';
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("en-us", conceptSlug, combinationString);
31
- const kvKeyPT = buildConceptKVKey("pt-br", conceptSlug, combinationString);
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 && existingEN.description && existingEN.poem &&
41
- existingPT.name && existingPT.description && existingPT.poem
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(`⚑ Basic info already exists for ${conceptSlug}, combination: ${combinationString}. Skipping.`);
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 = "generation"; // πŸ“Œ Track the 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.buildLLMMessages().generateConceptBasicInfo({
59
- combination: combinationString,
60
- conceptSlug,
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.api().callChatGPT.single(messages, {});
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 = "parsing"; // βœ… Switch to parsing 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: "idle",
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: "idle",
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(`❌ Attempt ${attempts} failed at phase: ${phase}`, (error as Error).message);
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(failureKey, JSON.stringify({
105
- error: (error as Error).message,
106
- attempt: attempts,
107
- phase, // βœ… Identify if failure occurred in "generation" or "parsing"
108
- conceptSlug,
109
- combinationString,
110
- timestamp: new Date().toISOString(),
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(`🚨 All ${maxAttempts} attempts failed during ${phase}. Logged failure.`);
115
- throw new Error(`Failed to generate basic info after ${maxAttempts} attempts`);
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("πŸ” Retrying...");
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].trim().split(/\n+/).map(line => line.trim()); // βœ… Split into array
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].trim().split(/\n+/).map(line => line.trim()); // βœ… Split into array
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
 
@@ -167,40 +198,65 @@ export class ConceptService {
167
198
  * Generate the Leonardo prompt for a concept.
168
199
  */
169
200
  async generatePrompt(
170
- language: Languages,
171
201
  conceptSlug: Concept,
172
- combinationString: string
202
+ combinationString: string,
203
+ override: boolean = false
173
204
  ): Promise<void> {
174
- const kvKey = buildConceptKVKey(language, conceptSlug, combinationString);
175
205
  console.log(
176
- `Generating Leonardo prompt for concept: ${conceptSlug}, combination: ${combinationString}, language: ${language}`
206
+ `πŸš€ Generating Leonardo prompt for concept: ${conceptSlug}, combination: ${combinationString}`
177
207
  );
178
-
179
- const concept = await this.getKVConcept(kvKey);
180
- if (!concept.name || !concept.description || !concept.poem) {
208
+
209
+ // βœ… Build KV keys for both languages
210
+ const kvKeyEN = buildConceptKVKey('en-us', conceptSlug, combinationString);
211
+ const kvKeyPT = buildConceptKVKey('pt-br', conceptSlug, combinationString);
212
+
213
+ // βœ… Retrieve existing KV data
214
+ const conceptEN = await this.getKVConcept(kvKeyEN);
215
+ const conceptPT = await this.getKVConcept(kvKeyPT);
216
+
217
+ // βœ… Check if prompt already exists
218
+ if (!override && conceptEN.leonardoPrompt && conceptPT.leonardoPrompt) {
219
+ console.log(`⚑ Leonardo prompt already exists for ${conceptSlug}, skipping.`);
220
+ return; // βœ… Skip regeneration
221
+ }
222
+
223
+ // βœ… Ensure basic info is present
224
+ if (
225
+ !conceptEN.name || !conceptEN.description || !conceptEN.poem ||
226
+ !conceptPT.name || !conceptPT.description || !conceptPT.poem
227
+ ) {
181
228
  throw new Error(
182
- `Basic info must be populated before generating prompt for concept: ${conceptSlug}`
229
+ `❌ Basic info must be populated before generating Leonardo prompt for concept: ${conceptSlug}`
183
230
  );
184
231
  }
185
-
232
+
233
+ // βœ… Generate prompt request
186
234
  const messages = this.context
187
235
  .buildLLMMessages()
188
236
  .generateConceptLeonardoPrompt({
189
237
  conceptSlug,
190
238
  combination: combinationString,
191
239
  });
192
-
240
+
241
+ // βœ… Call ChatGPT API
193
242
  const response = await this.context.api().callChatGPT.single(messages, {});
194
243
  if (!response) {
195
244
  throw new Error(
196
- `Failed to generate Leonardo prompt for concept: ${conceptSlug}`
245
+ `❌ Failed to generate Leonardo prompt for concept: ${conceptSlug}`
197
246
  );
198
247
  }
199
-
200
- concept.leonardoPrompt = response.trim();
201
- await this.context.kvConceptsStore().put(kvKey, JSON.stringify(concept));
248
+
249
+ // βœ… Store the generated prompt in both languages
250
+ conceptEN.leonardoPrompt = response.trim();
251
+ conceptPT.leonardoPrompt = response.trim();
252
+
253
+ await Promise.all([
254
+ this.context.kvConceptsStore().put(kvKeyEN, JSON.stringify(conceptEN)),
255
+ this.context.kvConceptsStore().put(kvKeyPT, JSON.stringify(conceptPT)),
256
+ ]);
257
+
202
258
  console.log(
203
- `Leonardo prompt stored for concept: ${conceptSlug}, combination: ${combinationString}, language: ${language}`
259
+ `βœ… Leonardo prompt stored for concept: ${conceptSlug}, combination: ${combinationString}, in both languages.`
204
260
  );
205
261
  }
206
262
 
@@ -215,93 +271,114 @@ export class ConceptService {
215
271
  console.log(
216
272
  `πŸš€ Generating content for concept: ${conceptSlug}, combination: ${combinationString}, override: ${override}`
217
273
  );
218
-
274
+
219
275
  const kvStore = this.context.kvConceptsStore();
220
276
  const kvFailuresStore = this.context.kvConceptFailuresStore(); // πŸ”΄ Replace with actual KV store
221
- const kvKeyEN = buildConceptKVKey("en-us", conceptSlug, combinationString);
222
- const kvKeyPT = buildConceptKVKey("pt-br", conceptSlug, combinationString);
277
+ const kvKeyEN = buildConceptKVKey('en-us', conceptSlug, combinationString);
278
+ const kvKeyPT = buildConceptKVKey('pt-br', conceptSlug, combinationString);
223
279
  const failureKey = `failures:content:${conceptSlug}:${combinationString}`;
224
-
280
+
225
281
  // βœ… Ensure basic info is available before generating content
226
282
  const conceptEN = await this.getKVConcept(kvKeyEN);
227
283
  const conceptPT = await this.getKVConcept(kvKeyPT);
228
-
284
+
229
285
  if (
230
- !conceptEN.name || !conceptEN.description || !conceptEN.poem ||
231
- !conceptPT.name || !conceptPT.description || !conceptPT.poem
286
+ !conceptEN.name ||
287
+ !conceptEN.description ||
288
+ !conceptEN.poem ||
289
+ !conceptPT.name ||
290
+ !conceptPT.description ||
291
+ !conceptPT.poem
232
292
  ) {
233
293
  throw new Error(
234
294
  `❌ Basic info must be populated before generating content for ${conceptSlug}`
235
295
  );
236
296
  }
237
-
238
- if (!override && conceptEN.content?.length > 0 && conceptPT.content?.length > 0) {
297
+
298
+ if (
299
+ !override &&
300
+ conceptEN.content?.length > 0 &&
301
+ conceptPT.content?.length > 0
302
+ ) {
239
303
  console.log(`⚑ Content already exists for ${conceptSlug}, skipping.`);
240
304
  return; // βœ… Skip regeneration
241
305
  }
242
-
306
+
243
307
  let attempts = 0;
244
308
  const maxAttempts = 3;
245
-
309
+
246
310
  while (attempts < maxAttempts) {
247
- let phase = "generation"; // πŸ“Œ Track phase
311
+ let phase = 'generation'; // πŸ“Œ Track phase
248
312
  try {
249
313
  attempts++;
250
314
  console.log(`πŸ”„ Attempt ${attempts} to generate content...`);
251
-
315
+
252
316
  // βœ… Build messages for LLM
253
- const messages = this.context.buildLLMMessages().generateConceptContent({
254
- conceptSlug,
255
- combination: combinationString,
256
- name: conceptEN.name, // Use English name since both languages match in meaning
257
- description: conceptEN.description,
258
- poem: conceptEN.poem,
259
- });
260
-
317
+ const messages = this.context
318
+ .buildLLMMessages()
319
+ .generateConceptContent({
320
+ conceptSlug,
321
+ combination: combinationString,
322
+ name: conceptEN.name, // Use English name since both languages match in meaning
323
+ description: conceptEN.description,
324
+ poem: conceptEN.poem,
325
+ });
326
+
261
327
  // βœ… Call ChatGPT API
262
- const response = await this.context.api().callChatGPT.single(messages, {});
328
+ const response = await this.context
329
+ .api()
330
+ .callChatGPT.single(messages, {});
263
331
  if (!response) {
264
332
  throw new Error(`❌ AI returned an empty response`);
265
333
  }
266
-
267
- phase = "parsing"; // βœ… Switch to parsing phase
268
-
334
+
335
+ phase = 'parsing'; // βœ… Switch to parsing phase
336
+
269
337
  // βœ… Parse structured content for both languages
270
338
  const { structuredContentEN, structuredContentPT } =
271
339
  this.parseStructuredContent(response);
272
-
340
+
273
341
  // 🌍 Store English content
274
342
  Object.assign(conceptEN, { content: structuredContentEN });
275
343
  await kvStore.put(kvKeyEN, JSON.stringify(conceptEN));
276
-
344
+
277
345
  // πŸ‡§πŸ‡· Store Portuguese content
278
346
  Object.assign(conceptPT, { content: structuredContentPT });
279
347
  await kvStore.put(kvKeyPT, JSON.stringify(conceptPT));
280
-
348
+
281
349
  console.log(
282
350
  `βœ… Structured content stored for ${conceptSlug}, combination: ${combinationString}, in both languages.`
283
351
  );
284
352
  return; // βœ… Exit loop if successful
285
-
286
353
  } catch (error) {
287
- console.error(`❌ Attempt ${attempts} failed at phase: ${phase}`, (error as Error).message);
288
-
354
+ console.error(
355
+ `❌ Attempt ${attempts} failed at phase: ${phase}`,
356
+ (error as Error).message
357
+ );
358
+
289
359
  // βœ… Store failure details in KV for manual review
290
- await kvFailuresStore.put(failureKey, JSON.stringify({
291
- error: (error as Error).message,
292
- attempt: attempts,
293
- phase, // βœ… Identify failure phase
294
- conceptSlug,
295
- combinationString,
296
- timestamp: new Date().toISOString(),
297
- }));
298
-
360
+ await kvFailuresStore.put(
361
+ failureKey,
362
+ JSON.stringify({
363
+ error: (error as Error).message,
364
+ attempt: attempts,
365
+ phase, // βœ… Identify failure phase
366
+ conceptSlug,
367
+ combinationString,
368
+ timestamp: new Date().toISOString(),
369
+ })
370
+ );
371
+
299
372
  if (attempts >= maxAttempts) {
300
- console.error(`🚨 All ${maxAttempts} attempts failed at phase ${phase}. Logged failure.`);
301
- throw new Error(`Failed to generate content after ${maxAttempts} attempts`);
373
+ console.error(
374
+ `🚨 All ${maxAttempts} attempts failed at phase ${phase}. Logged failure.`
375
+ );
376
+ throw new Error(
377
+ `Failed to generate content after ${maxAttempts} attempts`
378
+ );
302
379
  }
303
-
304
- console.log("πŸ” Retrying...");
380
+
381
+ console.log('πŸ” Retrying...');
305
382
  await new Promise((resolve) => setTimeout(resolve, 2000)); // ⏳ Small delay before retrying
306
383
  }
307
384
  }
@@ -311,51 +388,57 @@ export class ConceptService {
311
388
  structuredContentEN: StructuredConceptContent;
312
389
  structuredContentPT: StructuredConceptContent;
313
390
  } {
314
- console.log('πŸ“Œ Parsing structured content from ChatGPT response:', response);
315
-
391
+ console.log(
392
+ 'πŸ“Œ Parsing structured content from ChatGPT response:',
393
+ response
394
+ );
395
+
316
396
  const sections = [
317
- "Core Identity",
318
- "Strengths and Challenges",
319
- "Path to Fulfillment",
320
- "Emotional Depth",
321
- "Vision and Aspirations"
397
+ 'Core Identity',
398
+ 'Strengths and Challenges',
399
+ 'Path to Fulfillment',
400
+ 'Emotional Depth',
401
+ 'Vision and Aspirations',
322
402
  ];
323
-
403
+
324
404
  const sectionsPT = [
325
- "Identidade Essencial",
326
- "ForΓ§as e Desafios",
327
- "Caminho para a Plenitude",
328
- "Profundidade Emocional",
329
- "VisΓ£o e AspiraΓ§Γ΅es"
405
+ 'Identidade Essencial',
406
+ 'ForΓ§as e Desafios',
407
+ 'Caminho para a Plenitude',
408
+ 'Profundidade Emocional',
409
+ 'VisΓ£o e AspiraΓ§Γ΅es',
330
410
  ];
331
-
411
+
332
412
  // βœ… Match English and Portuguese content separately
333
413
  const enMatches = response.match(/EN:\s*([\s\S]+?)\s*PT:/);
334
414
  const ptMatches = response.match(/PT:\s*([\s\S]+)/);
335
-
415
+
336
416
  if (!enMatches || !ptMatches) {
337
- throw new Error("❌ Missing English or Portuguese content in response.");
417
+ throw new Error('❌ Missing English or Portuguese content in response.');
338
418
  }
339
-
419
+
340
420
  const enContent = enMatches[1].trim();
341
421
  const ptContent = ptMatches[1].trim();
342
-
422
+
343
423
  function extractSections(text: string, sectionTitles: string[]) {
344
424
  return sectionTitles.map((title) => {
345
425
  const regex = new RegExp(`${title}:\\s*([\\s\\S]+?)(?=\\n\\d|$)`);
346
426
  const match = text.match(regex);
347
-
427
+
348
428
  if (!match) {
349
- return { type: "section", title, content: ["❌ Section Missing"] };
429
+ return { type: 'section', title, content: ['❌ Section Missing'] };
350
430
  }
351
-
431
+
352
432
  // βœ… Split content into paragraphs
353
- const paragraphs = match[1].trim().split(/\n\n+/).map((p) => p.trim());
354
-
355
- return { type: "section", title, content: paragraphs };
433
+ const paragraphs = match[1]
434
+ .trim()
435
+ .split(/\n\n+/)
436
+ .map((p) => p.trim());
437
+
438
+ return { type: 'section', title, content: paragraphs };
356
439
  });
357
440
  }
358
-
441
+
359
442
  return {
360
443
  structuredContentEN: extractSections(enContent, sections),
361
444
  structuredContentPT: extractSections(ptContent, sectionsPT),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zodic/shared",
3
- "version": "0.0.145",
3
+ "version": "0.0.147",
4
4
  "module": "index.ts",
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -122,18 +122,22 @@ export const buildLLMMessages = (env: BackendBindings) => ({
122
122
  }: {
123
123
  combination: string;
124
124
  conceptSlug: Concept;
125
- }): ChatMessages => [
126
- {
127
- role: 'system',
128
- content: conceptPrompts(env)['leonardoPrompt'][conceptSlug],
129
- },
130
- {
131
- role: 'user',
132
- content: `
133
- Combination: ${combination}
125
+ }): ChatMessages => {
126
+ const zodiacCombination = mapConceptToPlanets(conceptSlug, combination);
127
+
128
+ return [
129
+ {
130
+ role: 'system',
131
+ content: conceptPrompts(env)['leonardoPrompt'][conceptSlug],
132
+ },
133
+ {
134
+ role: 'user',
135
+ content: `
136
+ Combination: ${zodiacCombination}
134
137
  `,
135
- },
136
- ],
138
+ },
139
+ ];
140
+ },
137
141
  generateConceptContent: ({
138
142
  name,
139
143
  description,