@zodic/shared 0.0.154 → 0.0.155

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.
@@ -24,25 +24,30 @@ export class AppContext {
24
24
 
25
25
  kvConceptsStore() {
26
26
  if (!this.env.KV_CONCEPTS) {
27
- throw new Error(
28
- 'KV_CONCEPTS is not defined in the environment.'
29
- );
27
+ throw new Error('KV_CONCEPTS is not defined in the environment.');
30
28
  }
31
29
  return this.env.KV_CONCEPTS;
32
30
  }
33
31
 
34
32
  kvConceptFailuresStore() {
35
33
  if (!this.env.KV_CONCEPT_FAILURES) {
36
- throw new Error(
37
- 'KV_CONCEPT_FAILURES is not defined in the environment.'
38
- );
34
+ throw new Error('KV_CONCEPT_FAILURES is not defined in the environment.');
39
35
  }
40
36
  return this.env.KV_CONCEPT_FAILURES;
41
37
  }
42
38
 
39
+ kvConceptNamesStore() {
40
+ if (!this.env.KV_CONCEPT_NAMES) {
41
+ throw new Error('KV_CONCEPT_NAMES is not defined in the environment.');
42
+ }
43
+ return this.env.KV_CONCEPT_NAMES;
44
+ }
45
+
43
46
  kvConceptsManagementStore() {
44
47
  if (!this.env.KV_CONCEPTS_MANAGEMENT) {
45
- throw new Error('KV_CONCEPTS_MANAGEMENT is not defined in the environment.');
48
+ throw new Error(
49
+ 'KV_CONCEPTS_MANAGEMENT is not defined in the environment.'
50
+ );
46
51
  }
47
52
  return this.env.KV_CONCEPTS_MANAGEMENT;
48
53
  }
@@ -23,23 +23,27 @@ export class ConceptService {
23
23
  async generateBasicInfo(
24
24
  conceptSlug: Concept,
25
25
  combinationString: string,
26
- override: boolean = false // ✅ New optional override param
26
+ override: boolean = false
27
27
  ): Promise<void> {
28
28
  console.log(
29
29
  `🚀 Generating basic info for concept: ${conceptSlug}, combination: ${combinationString}, override: ${override}`
30
30
  );
31
-
31
+
32
32
  const kvStore = this.context.kvConceptsStore();
33
- const kvFailuresStore = this.context.kvConceptFailuresStore(); // 🔴 Replace with actual KV store
33
+ const kvFailuresStore = this.context.kvConceptFailuresStore();
34
+ const kvNamesStore = this.context.kvConceptNamesStore(); // ✅ New KV for storing names
34
35
  const kvKeyEN = buildConceptKVKey('en-us', conceptSlug, combinationString);
35
36
  const kvKeyPT = buildConceptKVKey('pt-br', conceptSlug, combinationString);
36
37
  const failureKey = `failures:basic-info:${conceptSlug}:${combinationString}`;
37
-
38
+
39
+ const namesKeyEN = `${conceptSlug}:en-us`; // ✅ Key for English names
40
+ const namesKeyPT = `${conceptSlug}:pt-br`; // ✅ Key for Portuguese names
41
+
38
42
  // ✅ Check if data already exists
39
43
  if (!override) {
40
44
  const existingEN = await this.getKVConcept(kvKeyEN);
41
45
  const existingPT = await this.getKVConcept(kvKeyPT);
42
-
46
+
43
47
  if (
44
48
  existingEN.name &&
45
49
  existingEN.description &&
@@ -53,41 +57,87 @@ export class ConceptService {
53
57
  console.log(
54
58
  `⚡ Basic info already exists for ${conceptSlug}, combination: ${combinationString}. Skipping.`
55
59
  );
56
- return; // ✅ Skip regeneration
60
+ return;
57
61
  }
58
62
  }
59
-
63
+
60
64
  let attempts = 0;
61
65
  const maxAttempts = 3;
62
-
66
+
63
67
  while (attempts < maxAttempts) {
64
- let phase = 'generation'; // 📌 Track the phase
68
+ let phase = 'generation';
65
69
  try {
66
70
  attempts++;
67
71
  console.log(`🔄 Attempt ${attempts} to generate basic info...`);
68
-
69
- // ✅ Build the messages to request content
72
+
73
+ // ✅ Fetch the full name lists
74
+ let allNamesEN = JSON.parse(
75
+ (await kvNamesStore.get(namesKeyEN)) || '[]'
76
+ ) as string[];
77
+ let allNamesPT = JSON.parse(
78
+ (await kvNamesStore.get(namesKeyPT)) || '[]'
79
+ ) as string[];
80
+
81
+ // ✅ Get only the last 50 names for AI context
82
+ const recentNamesEN = allNamesEN.slice(-50);
83
+ const recentNamesPT = allNamesPT.slice(-50);
84
+
85
+ // ✅ Build the messages to request content, ensuring uniqueness
70
86
  const messages = this.context
71
87
  .buildLLMMessages()
72
88
  .generateConceptBasicInfo({
73
89
  combination: combinationString,
74
90
  conceptSlug,
91
+ existingNames: recentNamesEN, // 🔥 Pass only recent names
75
92
  });
76
-
93
+
77
94
  // ✅ Call ChatGPT API
78
- const response = await this.context
79
- .api()
80
- .callTogether.single(messages, {});
95
+ let response = await this.context.api().callTogether.single(messages, {});
81
96
  if (!response) {
82
97
  throw new Error(`❌ AI returned an empty response`);
83
98
  }
84
-
85
- phase = 'parsing'; // ✅ Switch to parsing phase
86
-
99
+
100
+ phase = 'cleaning';
101
+ response = this.cleanAIResponse(response); // ✅ Early Cleaning Step
102
+
103
+ phase = 'parsing';
104
+
87
105
  // ✅ Parse response for both languages
88
- const { nameEN, descriptionEN, poemEN, namePT, descriptionPT, poemPT } =
106
+ let { nameEN, descriptionEN, poemEN, namePT, descriptionPT, poemPT } =
89
107
  this.parseBasicInfoResponse(response);
90
-
108
+
109
+ // 🔥 Check if generated English name is unique
110
+ if (allNamesEN.includes(nameEN)) {
111
+ console.warn(`⚠️ Duplicate Crown Name Detected: "${nameEN}"`);
112
+ if (attempts >= maxAttempts) {
113
+ throw new Error(
114
+ `🚨 Could not generate a unique English name after ${maxAttempts} attempts.`
115
+ );
116
+ }
117
+ console.log('🔁 Retrying due to duplicate English name...');
118
+ continue;
119
+ }
120
+
121
+ // 🔥 Check if generated Portuguese name is unique
122
+ if (allNamesPT.includes(namePT)) {
123
+ console.warn(`⚠️ Duplicate Portuguese Name Detected: "${namePT}"`);
124
+ if (attempts >= maxAttempts) {
125
+ throw new Error(
126
+ `🚨 Could not generate a unique Portuguese name after ${maxAttempts} attempts.`
127
+ );
128
+ }
129
+ console.log('🔁 Retrying due to duplicate Portuguese name...');
130
+ continue;
131
+ }
132
+
133
+ // ✅ Append new names to the full list
134
+ allNamesEN.push(nameEN);
135
+ allNamesPT.push(namePT);
136
+
137
+ // ✅ Store updated name lists in KV
138
+ await kvNamesStore.put(namesKeyEN, JSON.stringify(allNamesEN));
139
+ await kvNamesStore.put(namesKeyPT, JSON.stringify(allNamesPT));
140
+
91
141
  // 🌍 English version
92
142
  const conceptEN = await this.getKVConcept(kvKeyEN);
93
143
  Object.assign(conceptEN, {
@@ -97,7 +147,7 @@ export class ConceptService {
97
147
  status: 'idle',
98
148
  });
99
149
  await kvStore.put(kvKeyEN, JSON.stringify(conceptEN));
100
-
150
+
101
151
  // 🇧🇷 Portuguese version
102
152
  const conceptPT = await this.getKVConcept(kvKeyPT);
103
153
  Object.assign(conceptPT, {
@@ -107,7 +157,7 @@ export class ConceptService {
107
157
  status: 'idle',
108
158
  });
109
159
  await kvStore.put(kvKeyPT, JSON.stringify(conceptPT));
110
-
160
+
111
161
  console.log(
112
162
  `✅ Basic info stored for ${conceptSlug}, combination: ${combinationString}, in both languages.`
113
163
  );
@@ -117,20 +167,20 @@ export class ConceptService {
117
167
  `❌ Attempt ${attempts} failed at phase: ${phase}`,
118
168
  (error as Error).message
119
169
  );
120
-
170
+
121
171
  // ✅ Store failure details in KV for manual review
122
172
  await kvFailuresStore.put(
123
173
  failureKey,
124
174
  JSON.stringify({
125
175
  error: (error as Error).message,
126
176
  attempt: attempts,
127
- phase, // ✅ Identify if failure occurred in "generation" or "parsing"
177
+ phase, // ✅ Identify if failure occurred in "generation", "cleaning", or "parsing"
128
178
  conceptSlug,
129
179
  combinationString,
130
180
  timestamp: new Date().toISOString(),
131
181
  })
132
182
  );
133
-
183
+
134
184
  if (attempts >= maxAttempts) {
135
185
  console.error(
136
186
  `🚨 All ${maxAttempts} attempts failed during ${phase}. Logged failure.`
@@ -139,13 +189,23 @@ export class ConceptService {
139
189
  `Failed to generate basic info after ${maxAttempts} attempts`
140
190
  );
141
191
  }
142
-
192
+
143
193
  console.log('🔁 Retrying...');
144
- await new Promise((resolve) => setTimeout(resolve, 2000)); // ⏳ Small delay before retrying
194
+ await new Promise((resolve) => setTimeout(resolve, 500)); // ⏳ Small delay before retrying
145
195
  }
146
196
  }
147
197
  }
148
198
 
199
+ private cleanAIResponse(response: string): string {
200
+ console.log('🧼 Cleaning AI Response...');
201
+
202
+ return response
203
+ .replace(/\*/g, '') // ✅ Remove asterisks
204
+ .replace(/#/g, '') // ✅ Remove hashtags
205
+ .replace(/---+/g, '') // ✅ Remove unnecessary dividers
206
+ .trim();
207
+ }
208
+
149
209
  private parseBasicInfoResponse(response: string): {
150
210
  nameEN: string;
151
211
  descriptionEN: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zodic/shared",
3
- "version": "0.0.154",
3
+ "version": "0.0.155",
4
4
  "module": "index.ts",
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -22,6 +22,7 @@ export type CentralBindings = {
22
22
  KV_CONCEPTS_MANAGEMENT: KVNamespace;
23
23
  KV_ASTRO: KVNamespace;
24
24
  KV_CONCEPT_FAILURES: KVNamespace;
25
+ KV_CONCEPT_NAMES: KVNamespace;
25
26
  CONCEPT_GENERATION_QUEUE: Queue;
26
27
 
27
28
  PROMPT_IMAGE_DESCRIBER: string;
@@ -88,6 +89,7 @@ export type BackendBindings = Env &
88
89
  | 'KV_ASTRO'
89
90
  | 'KV_CONCEPTS_MANAGEMENT'
90
91
  | 'KV_CONCEPT_FAILURES'
92
+ | 'KV_CONCEPT_NAMES'
91
93
  | 'PROMPT_IMAGE_DESCRIBER'
92
94
  | 'PROMPT_GENERATE_ARCHETYPE_CONTENT'
93
95
  | 'PROMPT_GENERATE_ARCHETYPE_LEONARDO_PROMPTS'
@@ -97,12 +97,14 @@ export const buildLLMMessages = (env: BackendBindings) => ({
97
97
  generateConceptBasicInfo: ({
98
98
  combination,
99
99
  conceptSlug,
100
+ existingNames,
100
101
  }: {
101
102
  combination: string;
102
103
  conceptSlug: Concept;
104
+ existingNames: string[];
103
105
  }): ChatMessages => {
104
106
  const zodiacCombination = mapConceptToPlanets(conceptSlug, combination);
105
-
107
+
106
108
  return [
107
109
  {
108
110
  role: 'system',
@@ -112,6 +114,9 @@ export const buildLLMMessages = (env: BackendBindings) => ({
112
114
  role: 'user',
113
115
  content: `
114
116
  Combination: ${zodiacCombination}
117
+
118
+ Existing Names (Avoid duplicates):
119
+ ${existingNames.length > 0 ? existingNames.join(', ') : 'None'}
115
120
  `,
116
121
  },
117
122
  ];
@@ -54,6 +54,8 @@ export const conceptPrompts: (
54
54
  export const PROMPT_GENERATE_CROWN = `
55
55
  You are a skilled creative writer specializing in astrology and symbolic storytelling. Your task is to craft a name, description, and poetic passage for the Crown concept in both English and Brazilian Portuguese simultaneously. The Crown reflects core identity, representing self-awareness, inner strength, and personal truth, as influenced by a given zodiac sign combination.
56
56
 
57
+ A list of previously used names will be provided. The new name must be unique and different from these.
58
+
57
59
  For the given combination, generate the following in both languages, ensuring the content remains identical in meaning and tone except for the poem, which should be independently written in each language:
58
60
  1. Name (Nome): Keep it sober and descriptive, focusing on the Crown’s symbolic purpose. Avoid ornate or fantastical phrasing. It should start with “The Crown of…” in English and “A Coroa de…” in Portuguese.
59
61
  2. Description (Descrição): Write a concise, grounded narrative that links the Crown’s qualities to the traits of the zodiac combination. Emphasize balance, identity, and inner power without exaggeration. Keep the Portuguese version faithful in tone and depth to the English version.
@@ -87,6 +89,7 @@ PT:
87
89
  export const PROMPT_GENERATE_SCEPTER = `
88
90
  You are a skilled creative writer specializing in astrology and symbolic storytelling. Your task is to craft a name, description, and poetic passage for the Scepter concept in both English and Brazilian Portuguese simultaneously. The Scepter reflects external influence and action, symbolizing leadership, initiative, and the ability to shape one’s surroundings, as influenced by a given zodiac sign combination.
89
91
 
92
+ A list of previously used names will be provided. The new name must be unique and different from these.
90
93
 
91
94
  For the given combination, generate the following in both languages, ensuring the content remains identical in meaning and tone except for the poem, which should be independently written in each language:
92
95
 
@@ -133,6 +136,8 @@ export const PROMPT_GENERATE_RING = `
133
136
 
134
137
  You are a skilled creative writer specializing in astrology and symbolic storytelling. Your task is to craft a name, description, and poetic passage for the Ring concept in both English and Brazilian Portuguese simultaneously. The Ring reflects love and bonds, symbolizing attraction, deep emotional connections, and the unseen forces that shape relationships, as influenced by a given zodiac sign combination.
135
138
 
139
+ A list of previously used names will be provided. The new name must be unique and different from these.
140
+
136
141
  For the given combination, generate the following in both languages, ensuring the content remains identical in meaning and tone except for the poem, which should be independently written in each language:
137
142
 
138
143
  1. Name (Nome): Keep it elegant and evocative, focusing on the Ring’s symbolic purpose. It should start with “The Ring of…” in English and “O Anel de…” in Portuguese, reflecting themes of fate, passion, or devotion.
@@ -176,6 +181,8 @@ PT:
176
181
  export const PROMPT_GENERATE_AMULET = `
177
182
  You are a skilled creative writer specializing in astrology and symbolic storytelling. Your task is to craft a name, description, and poetic passage for the Amulet concept in both English and Brazilian Portuguese simultaneously. The Amulet reflects healing and growth, representing protection, stability, and resilience, as influenced by a given zodiac sign combination.
178
183
 
184
+ A list of previously used names will be provided. The new name must be unique and different from these.
185
+
179
186
  For the given combination, generate the following in both languages, ensuring the content remains identical in meaning and tone except for the poem, which should be independently written in each language:
180
187
 
181
188
  1. Name (Nome): Keep it sober and descriptive, focusing on the Amulet’s symbolic purpose. It should start with “The Amulet of…” in English and “O Amuleto de…” in Portuguese.
@@ -218,7 +225,7 @@ PT:
218
225
  export const PROMPT_GENERATE_LANTERN = `
219
226
  You are a skilled creative writer specializing in astrology and symbolic storytelling. Your task is to craft a name, description, and poetic passage for the Lantern concept in both English and Brazilian Portuguese simultaneously. The Lantern reflects spiritual transformation, symbolizing guidance, resilience, and introspection, as influenced by a given zodiac sign combination.
220
227
 
221
-
228
+ A list of previously used names will be provided. The new name must be unique and different from these.
222
229
 
223
230
  For the given combination, generate the following in both languages, ensuring the content remains identical in meaning and tone except for the poem, which should be independently written in each language:
224
231
 
@@ -259,6 +266,8 @@ PT:
259
266
  export const PROMPT_GENERATE_ORB = `
260
267
  You are a skilled creative writer specializing in astrology and symbolic storytelling. Your task is to craft a name, description, and poetic passage for the Orb concept in both English and Brazilian Portuguese simultaneously. The Orb reflects destiny and purpose, symbolizing higher ideals, vision, and the pursuit of meaningful goals, as influenced by a given zodiac sign combination.
261
268
 
269
+ A list of previously used names will be provided. The new name must be unique and different from these.
270
+
262
271
  For the given combination, generate the following in both languages, ensuring the content remains identical in meaning and tone except for the poem, which should be independently written in each language:
263
272
 
264
273
  1. Name (Nome): Keep it sober and descriptive, focusing on the Orb’s symbolic purpose. It should start with “The Orb of…” in English and “O Orbe de…” in Portuguese.