@zodic/shared 0.0.155 → 0.0.157
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 +81 -34
- package/package.json +1 -1
- package/utils/conceptPrompts.ts +6 -24
|
@@ -28,22 +28,23 @@ export class ConceptService {
|
|
|
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
33
|
const kvFailuresStore = this.context.kvConceptFailuresStore();
|
|
34
34
|
const kvNamesStore = this.context.kvConceptNamesStore(); // ✅ New KV for storing names
|
|
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
|
-
|
|
39
|
-
const namesKeyEN = `${conceptSlug}:en-us`;
|
|
40
|
-
const namesKeyPT = `${conceptSlug}:pt-br`;
|
|
41
|
-
|
|
38
|
+
|
|
39
|
+
const namesKeyEN = `${conceptSlug}:en-us`;
|
|
40
|
+
const namesKeyPT = `${conceptSlug}:pt-br`;
|
|
41
|
+
const reportKey = `report:${conceptSlug}`; // ✅ New tracking key
|
|
42
|
+
|
|
42
43
|
// ✅ Check if data already exists
|
|
43
44
|
if (!override) {
|
|
44
45
|
const existingEN = await this.getKVConcept(kvKeyEN);
|
|
45
46
|
const existingPT = await this.getKVConcept(kvKeyPT);
|
|
46
|
-
|
|
47
|
+
|
|
47
48
|
if (
|
|
48
49
|
existingEN.name &&
|
|
49
50
|
existingEN.description &&
|
|
@@ -60,16 +61,16 @@ export class ConceptService {
|
|
|
60
61
|
return;
|
|
61
62
|
}
|
|
62
63
|
}
|
|
63
|
-
|
|
64
|
+
|
|
64
65
|
let attempts = 0;
|
|
65
66
|
const maxAttempts = 3;
|
|
66
|
-
|
|
67
|
+
|
|
67
68
|
while (attempts < maxAttempts) {
|
|
68
69
|
let phase = 'generation';
|
|
69
70
|
try {
|
|
70
71
|
attempts++;
|
|
71
72
|
console.log(`🔄 Attempt ${attempts} to generate basic info...`);
|
|
72
|
-
|
|
73
|
+
|
|
73
74
|
// ✅ Fetch the full name lists
|
|
74
75
|
let allNamesEN = JSON.parse(
|
|
75
76
|
(await kvNamesStore.get(namesKeyEN)) || '[]'
|
|
@@ -77,11 +78,11 @@ export class ConceptService {
|
|
|
77
78
|
let allNamesPT = JSON.parse(
|
|
78
79
|
(await kvNamesStore.get(namesKeyPT)) || '[]'
|
|
79
80
|
) as string[];
|
|
80
|
-
|
|
81
|
+
|
|
81
82
|
// ✅ Get only the last 50 names for AI context
|
|
82
83
|
const recentNamesEN = allNamesEN.slice(-50);
|
|
83
84
|
const recentNamesPT = allNamesPT.slice(-50);
|
|
84
|
-
|
|
85
|
+
|
|
85
86
|
// ✅ Build the messages to request content, ensuring uniqueness
|
|
86
87
|
const messages = this.context
|
|
87
88
|
.buildLLMMessages()
|
|
@@ -90,22 +91,22 @@ export class ConceptService {
|
|
|
90
91
|
conceptSlug,
|
|
91
92
|
existingNames: recentNamesEN, // 🔥 Pass only recent names
|
|
92
93
|
});
|
|
93
|
-
|
|
94
|
+
|
|
94
95
|
// ✅ Call ChatGPT API
|
|
95
96
|
let response = await this.context.api().callTogether.single(messages, {});
|
|
96
97
|
if (!response) {
|
|
97
98
|
throw new Error(`❌ AI returned an empty response`);
|
|
98
99
|
}
|
|
99
|
-
|
|
100
|
+
|
|
100
101
|
phase = 'cleaning';
|
|
101
|
-
response = this.cleanAIResponse(response);
|
|
102
|
-
|
|
102
|
+
response = this.cleanAIResponse(response);
|
|
103
|
+
|
|
103
104
|
phase = 'parsing';
|
|
104
|
-
|
|
105
|
+
|
|
105
106
|
// ✅ Parse response for both languages
|
|
106
107
|
let { nameEN, descriptionEN, poemEN, namePT, descriptionPT, poemPT } =
|
|
107
|
-
this.parseBasicInfoResponse(response);
|
|
108
|
-
|
|
108
|
+
this.parseBasicInfoResponse(response, conceptSlug);
|
|
109
|
+
|
|
109
110
|
// 🔥 Check if generated English name is unique
|
|
110
111
|
if (allNamesEN.includes(nameEN)) {
|
|
111
112
|
console.warn(`⚠️ Duplicate Crown Name Detected: "${nameEN}"`);
|
|
@@ -117,7 +118,7 @@ export class ConceptService {
|
|
|
117
118
|
console.log('🔁 Retrying due to duplicate English name...');
|
|
118
119
|
continue;
|
|
119
120
|
}
|
|
120
|
-
|
|
121
|
+
|
|
121
122
|
// 🔥 Check if generated Portuguese name is unique
|
|
122
123
|
if (allNamesPT.includes(namePT)) {
|
|
123
124
|
console.warn(`⚠️ Duplicate Portuguese Name Detected: "${namePT}"`);
|
|
@@ -129,15 +130,15 @@ export class ConceptService {
|
|
|
129
130
|
console.log('🔁 Retrying due to duplicate Portuguese name...');
|
|
130
131
|
continue;
|
|
131
132
|
}
|
|
132
|
-
|
|
133
|
+
|
|
133
134
|
// ✅ Append new names to the full list
|
|
134
135
|
allNamesEN.push(nameEN);
|
|
135
136
|
allNamesPT.push(namePT);
|
|
136
|
-
|
|
137
|
+
|
|
137
138
|
// ✅ Store updated name lists in KV
|
|
138
139
|
await kvNamesStore.put(namesKeyEN, JSON.stringify(allNamesEN));
|
|
139
140
|
await kvNamesStore.put(namesKeyPT, JSON.stringify(allNamesPT));
|
|
140
|
-
|
|
141
|
+
|
|
141
142
|
// 🌍 English version
|
|
142
143
|
const conceptEN = await this.getKVConcept(kvKeyEN);
|
|
143
144
|
Object.assign(conceptEN, {
|
|
@@ -147,7 +148,7 @@ export class ConceptService {
|
|
|
147
148
|
status: 'idle',
|
|
148
149
|
});
|
|
149
150
|
await kvStore.put(kvKeyEN, JSON.stringify(conceptEN));
|
|
150
|
-
|
|
151
|
+
|
|
151
152
|
// 🇧🇷 Portuguese version
|
|
152
153
|
const conceptPT = await this.getKVConcept(kvKeyPT);
|
|
153
154
|
Object.assign(conceptPT, {
|
|
@@ -157,30 +158,43 @@ export class ConceptService {
|
|
|
157
158
|
status: 'idle',
|
|
158
159
|
});
|
|
159
160
|
await kvStore.put(kvKeyPT, JSON.stringify(conceptPT));
|
|
160
|
-
|
|
161
|
+
|
|
161
162
|
console.log(
|
|
162
163
|
`✅ Basic info stored for ${conceptSlug}, combination: ${combinationString}, in both languages.`
|
|
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.`
|
|
176
|
+
);
|
|
177
|
+
|
|
164
178
|
return; // ✅ Exit loop if successful
|
|
165
179
|
} catch (error) {
|
|
166
180
|
console.error(
|
|
167
181
|
`❌ Attempt ${attempts} failed at phase: ${phase}`,
|
|
168
182
|
(error as Error).message
|
|
169
183
|
);
|
|
170
|
-
|
|
184
|
+
|
|
171
185
|
// ✅ Store failure details in KV for manual review
|
|
172
186
|
await kvFailuresStore.put(
|
|
173
187
|
failureKey,
|
|
174
188
|
JSON.stringify({
|
|
175
189
|
error: (error as Error).message,
|
|
176
190
|
attempt: attempts,
|
|
177
|
-
phase,
|
|
191
|
+
phase,
|
|
178
192
|
conceptSlug,
|
|
179
193
|
combinationString,
|
|
180
194
|
timestamp: new Date().toISOString(),
|
|
181
195
|
})
|
|
182
196
|
);
|
|
183
|
-
|
|
197
|
+
|
|
184
198
|
if (attempts >= maxAttempts) {
|
|
185
199
|
console.error(
|
|
186
200
|
`🚨 All ${maxAttempts} attempts failed during ${phase}. Logged failure.`
|
|
@@ -189,7 +203,7 @@ export class ConceptService {
|
|
|
189
203
|
`Failed to generate basic info after ${maxAttempts} attempts`
|
|
190
204
|
);
|
|
191
205
|
}
|
|
192
|
-
|
|
206
|
+
|
|
193
207
|
console.log('🔁 Retrying...');
|
|
194
208
|
await new Promise((resolve) => setTimeout(resolve, 500)); // ⏳ Small delay before retrying
|
|
195
209
|
}
|
|
@@ -206,7 +220,10 @@ export class ConceptService {
|
|
|
206
220
|
.trim();
|
|
207
221
|
}
|
|
208
222
|
|
|
209
|
-
private parseBasicInfoResponse(
|
|
223
|
+
private parseBasicInfoResponse(
|
|
224
|
+
response: string,
|
|
225
|
+
conceptSlug: Concept
|
|
226
|
+
): {
|
|
210
227
|
nameEN: string;
|
|
211
228
|
descriptionEN: string;
|
|
212
229
|
poemEN: string[];
|
|
@@ -237,25 +254,55 @@ export class ConceptService {
|
|
|
237
254
|
const poemEN = enMatch[3]
|
|
238
255
|
.trim()
|
|
239
256
|
.split(/\n+/)
|
|
240
|
-
.map((line) => cleanText(line));
|
|
257
|
+
.map((line) => cleanText(line));
|
|
241
258
|
|
|
242
259
|
const namePT = cleanText(ptMatch[1]);
|
|
243
260
|
const descriptionPT = cleanText(ptMatch[2]);
|
|
244
261
|
const poemPT = ptMatch[3]
|
|
245
262
|
.trim()
|
|
246
263
|
.split(/\n+/)
|
|
247
|
-
.map((line) => cleanText(line));
|
|
264
|
+
.map((line) => cleanText(line));
|
|
265
|
+
|
|
266
|
+
// ✅ Determine appropriate replacement text based on conceptSlug
|
|
267
|
+
const conceptPlaceholder = `this ${conceptSlug.charAt(0).toUpperCase() + conceptSlug.slice(1)}`;
|
|
268
|
+
const conceptPlaceholderCapitalized =
|
|
269
|
+
`This ${conceptSlug.charAt(0).toUpperCase() + conceptSlug.slice(1)}`;
|
|
270
|
+
|
|
271
|
+
// ✅ Function to replace occurrences of the name in a text while handling capitalization
|
|
272
|
+
const replaceConceptName = (text: string, conceptName: string) => {
|
|
273
|
+
return text.replace(
|
|
274
|
+
new RegExp(`(?<=\\.\\s*|^)${conceptName}`, 'g'),
|
|
275
|
+
conceptPlaceholderCapitalized
|
|
276
|
+
).replace(new RegExp(conceptName, 'g'), conceptPlaceholder);
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
// ✅ Ensure the concept name is not repeated in the description
|
|
280
|
+
const cleanedDescriptionEN = replaceConceptName(descriptionEN, nameEN);
|
|
281
|
+
const cleanedDescriptionPT = replaceConceptName(descriptionPT, namePT);
|
|
282
|
+
|
|
283
|
+
// ✅ If the name appears in the poem, trigger a retry
|
|
284
|
+
if (poemEN.some((line) => line.includes(nameEN)) || poemPT.some((line) => line.includes(namePT))) {
|
|
285
|
+
console.error('❌ Concept name detected in poem, triggering a retry.');
|
|
286
|
+
throw new Error('Concept name found in poem, regenerating response.');
|
|
287
|
+
}
|
|
248
288
|
|
|
249
289
|
console.log('✅ Successfully parsed and cleaned basic info:', {
|
|
250
290
|
nameEN,
|
|
251
|
-
descriptionEN,
|
|
291
|
+
descriptionEN: cleanedDescriptionEN,
|
|
252
292
|
poemEN,
|
|
253
293
|
namePT,
|
|
254
|
-
descriptionPT,
|
|
294
|
+
descriptionPT: cleanedDescriptionPT,
|
|
255
295
|
poemPT,
|
|
256
296
|
});
|
|
257
297
|
|
|
258
|
-
return {
|
|
298
|
+
return {
|
|
299
|
+
nameEN,
|
|
300
|
+
descriptionEN: cleanedDescriptionEN,
|
|
301
|
+
poemEN,
|
|
302
|
+
namePT,
|
|
303
|
+
descriptionPT: cleanedDescriptionPT,
|
|
304
|
+
poemPT,
|
|
305
|
+
};
|
|
259
306
|
}
|
|
260
307
|
|
|
261
308
|
/**
|
package/package.json
CHANGED
package/utils/conceptPrompts.ts
CHANGED
|
@@ -66,6 +66,7 @@ For the given combination, generate the following in both languages, ensuring th
|
|
|
66
66
|
|
|
67
67
|
Rules & Formatting:
|
|
68
68
|
• Do not mention zodiac signs or planets explicitly.
|
|
69
|
+
• Do not mention the Crown’s name in the description or poem.
|
|
69
70
|
• Ensure both descriptions are structurally and emotionally equivalent.
|
|
70
71
|
• Write the poem freely in each language to maximize impact, avoiding direct translation.
|
|
71
72
|
• Use rich, symbolic language while keeping it accessible and inspiring.
|
|
@@ -99,22 +100,16 @@ For the given combination, generate the following in both languages, ensuring th
|
|
|
99
100
|
|
|
100
101
|
3. Poetic Passage (Passagem Poética): Create two separate and independent poetic passages—one in English, one in Portuguese. Each should reflect the same essence but be uniquely crafted to best suit the flow and artistic qualities of its respective language. The passage should be long and evocative, 4 poetic lines, ensuring it deeply resonates and immerses the reader in a sense of confidence, power, and determination.
|
|
101
102
|
|
|
102
|
-
|
|
103
103
|
• Do not translate the poem directly—let each version flourish naturally in its own language.
|
|
104
|
-
|
|
105
104
|
• Ensure the passage flows rhythmically, evoking strength and vision.
|
|
106
|
-
|
|
107
105
|
• Avoid mentioning the Scepter itself or using the word “scepter.”
|
|
108
106
|
|
|
109
|
-
|
|
110
107
|
Rules & Formatting:
|
|
111
108
|
|
|
112
109
|
• Do not mention zodiac signs or planets explicitly.
|
|
113
|
-
|
|
110
|
+
• Do not mention the Scepter’s name in the description or poem.
|
|
114
111
|
• Ensure both descriptions are structurally and emotionally equivalent.
|
|
115
|
-
|
|
116
112
|
• Write the poem freely in each language to maximize impact, avoiding direct translation.
|
|
117
|
-
|
|
118
113
|
• Use rich, symbolic language while keeping it accessible and inspiring.
|
|
119
114
|
|
|
120
115
|
Response Format:
|
|
@@ -147,20 +142,16 @@ For the given combination, generate the following in both languages, ensuring th
|
|
|
147
142
|
3. Poetic Passage (Passagem Poética): Create two separate and independent poetic passages—one in English, one in Portuguese. Each should reflect the same essence but be uniquely crafted to best suit the flow and artistic qualities of its respective language. The passage should be long and evocative, 4 poetic lines, ensuring it deeply resonates and immerses the reader in a sense of longing, connection, and destiny.
|
|
148
143
|
|
|
149
144
|
• Do not translate the poem directly—let each version flourish naturally in its own language.
|
|
150
|
-
|
|
151
145
|
• Ensure the passage flows rhythmically, evoking deep emotion and intimacy.
|
|
152
|
-
|
|
153
146
|
• Avoid mentioning the Ring itself or using the word “ring.”
|
|
154
147
|
|
|
155
148
|
|
|
156
149
|
Rules & Formatting:
|
|
157
150
|
|
|
158
151
|
• Do not mention zodiac signs or planets explicitly.
|
|
159
|
-
|
|
152
|
+
• Do not mention the Ring’s name in the description or poem.
|
|
160
153
|
• Ensure both descriptions are structurally and emotionally equivalent.
|
|
161
|
-
|
|
162
154
|
• Write the poem freely in each language to maximize impact, avoiding direct translation.
|
|
163
|
-
|
|
164
155
|
• Use rich, symbolic language while keeping it accessible and inspiring.
|
|
165
156
|
|
|
166
157
|
Response Format:
|
|
@@ -192,19 +183,15 @@ For the given combination, generate the following in both languages, ensuring th
|
|
|
192
183
|
3. Poetic Passage (Passagem Poética): Create two separate and independent poetic passages—one in English, one in Portuguese. Each should reflect the same essence but be uniquely crafted to best suit the flow and artistic qualities of its respective language. The passage should be long and evocative, 4 poetic lines, ensuring it deeply resonates and immerses the reader in a sense of renewal, strength, and inner harmony.
|
|
193
184
|
|
|
194
185
|
• Do not translate the poem directly—let each version flourish naturally in its own language.
|
|
195
|
-
|
|
196
186
|
• Ensure the passage flows rhythmically, evoking protection, resilience, and healing.
|
|
197
|
-
|
|
198
187
|
• Avoid mentioning the Amulet itself or using the word “amulet.”
|
|
199
188
|
|
|
200
189
|
Rules & Formatting:
|
|
201
190
|
|
|
202
191
|
• Do not mention zodiac signs or planets explicitly.
|
|
203
|
-
|
|
192
|
+
• Do not mention the Amulet’s name in the description or poem.
|
|
204
193
|
• Ensure both descriptions are structurally and emotionally equivalent.
|
|
205
|
-
|
|
206
194
|
• Write the poem freely in each language to maximize impact, avoiding direct translation.
|
|
207
|
-
|
|
208
195
|
• Use rich, symbolic language while keeping it accessible and inspiring.
|
|
209
196
|
|
|
210
197
|
Response Format:
|
|
@@ -236,14 +223,13 @@ For the given combination, generate the following in both languages, ensuring th
|
|
|
236
223
|
3. Poetic Passage (Passagem Poética): Create two separate and independent poetic passages—one in English, one in Portuguese. Each should reflect the same essence but be uniquely crafted to best suit the flow and artistic qualities of its respective language. The passage should be long and evocative, 4 poetic lines, ensuring it deeply resonates and immerses the reader in a sense of mystery, guidance, and inner awakening.
|
|
237
224
|
|
|
238
225
|
• Do not translate the poem directly—let each version flourish naturally in its own language.
|
|
239
|
-
|
|
240
226
|
• Ensure the passage flows rhythmically, evoking transformation and introspection.
|
|
241
|
-
|
|
242
227
|
• Avoid mentioning the Lantern itself or using the word “lantern.”
|
|
243
228
|
|
|
244
229
|
Rules & Formatting:
|
|
245
230
|
|
|
246
231
|
• Do not mention zodiac signs or planets explicitly.
|
|
232
|
+
• Do not mention the Lantern’s name in the description or poem.
|
|
247
233
|
• Ensure both descriptions are structurally and emotionally equivalent.
|
|
248
234
|
• Write the poem freely in each language to maximize impact, avoiding direct translation.
|
|
249
235
|
• Use rich, symbolic language while keeping it accessible and inspiring.
|
|
@@ -277,19 +263,15 @@ For the given combination, generate the following in both languages, ensuring th
|
|
|
277
263
|
3. Poetic Passage (Passagem Poética): Create two separate and independent poetic passages—one in English, one in Portuguese. Each should reflect the same essence but be uniquely crafted to best suit the flow and artistic qualities of its respective language. The passage should be long and evocative, 4 poetic lines, ensuring it deeply resonates and immerses the reader in a sense of aspiration, vision, and transcendence.
|
|
278
264
|
|
|
279
265
|
• Do not translate the poem directly—let each version flourish naturally in its own language.
|
|
280
|
-
|
|
281
266
|
• Ensure the passage flows rhythmically, evoking inspiration and a higher calling.
|
|
282
|
-
|
|
283
267
|
• Avoid mentioning the Orb itself or using the word “orb.”
|
|
284
268
|
|
|
285
269
|
Rules & Formatting:
|
|
286
270
|
|
|
287
271
|
• Do not mention zodiac signs or planets explicitly.
|
|
288
|
-
|
|
272
|
+
• Do not mention the Orb’s name in the description or poem.
|
|
289
273
|
• Ensure both descriptions are structurally and emotionally equivalent.
|
|
290
|
-
|
|
291
274
|
• Write the poem freely in each language to maximize impact, avoiding direct translation.
|
|
292
|
-
|
|
293
275
|
• Use rich, symbolic language while keeping it accessible and inspiring.
|
|
294
276
|
|
|
295
277
|
Response Format:
|