@zodic/shared 0.0.200 → 0.0.202
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 +44 -125
- package/package.json +1 -1
- package/utils/conceptPrompts.ts +50 -2
|
@@ -29,181 +29,100 @@ export class ConceptService {
|
|
|
29
29
|
combinationString: string,
|
|
30
30
|
override: boolean = false
|
|
31
31
|
): Promise<void> {
|
|
32
|
-
console.log(
|
|
33
|
-
|
|
34
|
-
);
|
|
35
|
-
|
|
32
|
+
console.log(`🚀 Generating basic info for concept: ${conceptSlug}, combination: ${combinationString}, override: ${override}`);
|
|
33
|
+
|
|
36
34
|
const kvStore = this.context.kvConceptsStore();
|
|
37
35
|
const kvFailuresStore = this.context.kvConceptFailuresStore();
|
|
38
36
|
const kvKeyEN = buildConceptKVKey('en-us', conceptSlug, combinationString);
|
|
39
37
|
const kvKeyPT = buildConceptKVKey('pt-br', conceptSlug, combinationString);
|
|
40
|
-
|
|
38
|
+
|
|
41
39
|
// ✅ Use Durable Object stub
|
|
42
40
|
const id = this.context.env.CONCEPT_NAMES_DO.idFromName(conceptSlug);
|
|
43
41
|
const stub = this.context.env.CONCEPT_NAMES_DO.get(id);
|
|
44
|
-
|
|
42
|
+
|
|
45
43
|
console.log(`📡 Fetching existing KV data for ${conceptSlug}...`);
|
|
46
44
|
const existingEN = await this.getKVConcept(kvKeyEN);
|
|
47
45
|
const existingPT = await this.getKVConcept(kvKeyPT);
|
|
48
|
-
|
|
49
|
-
// ✅ IMMEDIATE SKIP if already exists and override is false
|
|
46
|
+
|
|
50
47
|
if (!override && existingEN.name && existingPT.name) {
|
|
51
48
|
console.log(`⚡ Basic info already exists for ${conceptSlug}, skipping.`);
|
|
52
49
|
return;
|
|
53
50
|
}
|
|
54
|
-
|
|
51
|
+
|
|
55
52
|
let attempts = 0;
|
|
56
53
|
const maxAttempts = 3;
|
|
57
|
-
|
|
54
|
+
|
|
58
55
|
while (attempts < maxAttempts) {
|
|
59
56
|
let phase = 'generation';
|
|
60
57
|
try {
|
|
61
58
|
attempts++;
|
|
62
|
-
console.log(
|
|
63
|
-
|
|
64
|
-
);
|
|
65
|
-
|
|
66
|
-
// ✅ Fetch the latest name list from Durable Object
|
|
67
|
-
console.log(
|
|
68
|
-
`📡 Fetching names from Durable Object for ${conceptSlug}...`
|
|
69
|
-
);
|
|
59
|
+
console.log(`🔄 Attempt ${attempts} to generate basic info for ${conceptSlug}...`);
|
|
60
|
+
|
|
70
61
|
let allNamesEN: string[] = [];
|
|
71
62
|
let allNamesPT: string[] = [];
|
|
72
63
|
const response = await stub.fetch(`https://internal/names`);
|
|
73
|
-
|
|
64
|
+
|
|
74
65
|
if (response.ok) {
|
|
75
|
-
const data = (await response.json()) as {
|
|
76
|
-
'en-us': string[];
|
|
77
|
-
'pt-br': string[];
|
|
78
|
-
};
|
|
66
|
+
const data = (await response.json()) as { 'en-us': string[]; 'pt-br': string[] };
|
|
79
67
|
allNamesEN = data['en-us'] || [];
|
|
80
68
|
allNamesPT = data['pt-br'] || [];
|
|
81
69
|
}
|
|
82
|
-
|
|
70
|
+
|
|
83
71
|
console.log(`✏️ Generating new name...`);
|
|
84
|
-
const messages = this.context
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
let aiResponse = await this.context
|
|
93
|
-
.api()
|
|
94
|
-
.callTogether.single(messages, {});
|
|
72
|
+
const messages = this.context.buildLLMMessages().generateConceptBasicInfo({
|
|
73
|
+
combination: combinationString,
|
|
74
|
+
conceptSlug,
|
|
75
|
+
existingNames: allNamesEN.length > 100 ? allNamesEN.slice(-100) : allNamesEN,
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
let aiResponse = await this.context.api().callTogether.single(messages, {});
|
|
95
79
|
if (!aiResponse) throw new Error(`❌ AI returned an empty response`);
|
|
96
|
-
|
|
80
|
+
|
|
97
81
|
phase = 'cleaning';
|
|
98
82
|
aiResponse = this.cleanAIResponse(aiResponse);
|
|
99
|
-
|
|
83
|
+
|
|
100
84
|
phase = 'parsing';
|
|
101
|
-
let { nameEN, descriptionEN, poemEN, namePT, descriptionPT, poemPT } =
|
|
102
|
-
|
|
103
|
-
|
|
85
|
+
let { nameEN, descriptionEN, poemEN, namePT, descriptionPT, poemPT } = this.parseBasicInfoResponse(aiResponse, conceptSlug);
|
|
86
|
+
|
|
104
87
|
console.log(`🎭 Generated names: EN - "${nameEN}", PT - "${namePT}"`);
|
|
105
|
-
|
|
106
|
-
// ✅
|
|
107
|
-
const MAX_NAME_LENGTH = 44;
|
|
108
|
-
if (
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
console.warn(
|
|
113
|
-
`⚠️ Name too long: "${nameEN}" | "${namePT}". Retrying...`
|
|
114
|
-
);
|
|
115
|
-
|
|
116
|
-
// ✅ **Register name in Durable Object as blocked**
|
|
117
|
-
await stub.fetch(`https://internal/add-name`, {
|
|
118
|
-
method: 'POST',
|
|
119
|
-
body: JSON.stringify({ language: 'en-us', name: nameEN }),
|
|
120
|
-
headers: { 'Content-Type': 'application/json' },
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
await stub.fetch(`https://internal/add-name`, {
|
|
124
|
-
method: 'POST',
|
|
125
|
-
body: JSON.stringify({ language: 'pt-br', name: namePT }),
|
|
126
|
-
headers: { 'Content-Type': 'application/json' },
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
console.log(`✅ Added long name to DO blocklist, retrying...`);
|
|
88
|
+
|
|
89
|
+
// ✅ Name length check
|
|
90
|
+
const MAX_NAME_LENGTH = 44;
|
|
91
|
+
if (nameEN.length > MAX_NAME_LENGTH || namePT.length > MAX_NAME_LENGTH) {
|
|
92
|
+
console.warn(`⚠️ Name too long: "${nameEN}" | "${namePT}". Retrying...`);
|
|
93
|
+
allNamesEN.push(nameEN);
|
|
94
|
+
allNamesPT.push(namePT);
|
|
130
95
|
continue;
|
|
131
96
|
}
|
|
132
|
-
|
|
97
|
+
|
|
133
98
|
// ✅ Check uniqueness before storing
|
|
134
99
|
if (allNamesEN.includes(nameEN) || allNamesPT.includes(namePT)) {
|
|
135
|
-
console.warn(
|
|
136
|
-
`⚠️ Duplicate Name Detected: "${nameEN}" or "${namePT}"`
|
|
137
|
-
);
|
|
138
|
-
|
|
139
|
-
// ✅ **On third attempt, store the name anyway & register in kvFailures**
|
|
100
|
+
console.warn(`⚠️ Duplicate Name Detected: "${nameEN}" or "${namePT}"`);
|
|
140
101
|
if (attempts >= maxAttempts) {
|
|
141
|
-
console.log(
|
|
142
|
-
`🚨 Max attempts reached. Storing name despite duplicate.`
|
|
143
|
-
);
|
|
144
|
-
|
|
102
|
+
console.log(`🚨 Max attempts reached. Storing name despite duplicate.`);
|
|
145
103
|
await kvFailuresStore.put(
|
|
146
104
|
`failures:duplicates:${conceptSlug}:${combinationString}`,
|
|
147
|
-
JSON.stringify({
|
|
148
|
-
nameEN,
|
|
149
|
-
namePT,
|
|
150
|
-
attempts,
|
|
151
|
-
conceptSlug,
|
|
152
|
-
combinationString,
|
|
153
|
-
timestamp: new Date().toISOString(),
|
|
154
|
-
})
|
|
155
|
-
);
|
|
156
|
-
|
|
157
|
-
console.log(
|
|
158
|
-
`✅ Stored duplicate name after max retries: ${nameEN}, ${namePT}`
|
|
105
|
+
JSON.stringify({ nameEN, namePT, attempts, conceptSlug, combinationString, timestamp: new Date().toISOString() })
|
|
159
106
|
);
|
|
160
107
|
break;
|
|
161
108
|
}
|
|
162
|
-
|
|
163
|
-
console.log('🔁 Retrying due to duplicate name...');
|
|
164
109
|
continue;
|
|
165
110
|
}
|
|
166
|
-
|
|
111
|
+
|
|
167
112
|
console.log(`📝 Storing names in Durable Object...`);
|
|
168
|
-
|
|
169
|
-
await stub.fetch(`https://internal/add-name`, {
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
headers: { 'Content-Type': 'application/json' },
|
|
173
|
-
});
|
|
174
|
-
|
|
175
|
-
await stub.fetch(`https://internal/add-name`, {
|
|
176
|
-
method: 'POST',
|
|
177
|
-
body: JSON.stringify({ language: 'pt-br', name: namePT }),
|
|
178
|
-
headers: { 'Content-Type': 'application/json' },
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
// ✅ Store the generated basic info in KV
|
|
182
|
-
Object.assign(existingEN, {
|
|
183
|
-
name: nameEN,
|
|
184
|
-
description: descriptionEN,
|
|
185
|
-
poem: poemEN,
|
|
186
|
-
status: 'idle',
|
|
187
|
-
});
|
|
113
|
+
await stub.fetch(`https://internal/add-name`, { method: 'POST', body: JSON.stringify({ language: 'en-us', name: nameEN }), headers: { 'Content-Type': 'application/json' } });
|
|
114
|
+
await stub.fetch(`https://internal/add-name`, { method: 'POST', body: JSON.stringify({ language: 'pt-br', name: namePT }), headers: { 'Content-Type': 'application/json' } });
|
|
115
|
+
|
|
116
|
+
Object.assign(existingEN, { name: nameEN, description: descriptionEN, poem: poemEN, status: 'idle' });
|
|
188
117
|
await kvStore.put(kvKeyEN, JSON.stringify(existingEN));
|
|
189
|
-
|
|
190
|
-
Object.assign(existingPT, {
|
|
191
|
-
name: namePT,
|
|
192
|
-
description: descriptionPT,
|
|
193
|
-
poem: poemPT,
|
|
194
|
-
status: 'idle',
|
|
195
|
-
});
|
|
118
|
+
|
|
119
|
+
Object.assign(existingPT, { name: namePT, description: descriptionPT, poem: poemPT, status: 'idle' });
|
|
196
120
|
await kvStore.put(kvKeyPT, JSON.stringify(existingPT));
|
|
197
|
-
|
|
198
|
-
console.log(
|
|
199
|
-
`✅ Stored basic info for ${conceptSlug}, combination: ${combinationString}.`
|
|
200
|
-
);
|
|
121
|
+
|
|
122
|
+
console.log(`✅ Stored basic info for ${conceptSlug}, combination: ${combinationString}.`);
|
|
201
123
|
return;
|
|
202
124
|
} catch (error) {
|
|
203
|
-
console.error(
|
|
204
|
-
`❌ Attempt ${attempts} failed at phase: ${phase}`,
|
|
205
|
-
(error as Error).message
|
|
206
|
-
);
|
|
125
|
+
console.error(`❌ Attempt ${attempts} failed at phase: ${phase}`, error);
|
|
207
126
|
}
|
|
208
127
|
}
|
|
209
128
|
}
|
package/package.json
CHANGED
package/utils/conceptPrompts.ts
CHANGED
|
@@ -7,7 +7,7 @@ export const conceptPrompts: (
|
|
|
7
7
|
crown: PROMPT_GENERATE_CROWN,
|
|
8
8
|
scepter: PROMPT_GENERATE_SCEPTER,
|
|
9
9
|
ring: PROMPT_GENERATE_RING,
|
|
10
|
-
amulet:
|
|
10
|
+
amulet: PROMPT_GENERATE_AMULET_BASIC_INFO,
|
|
11
11
|
lantern: PROMPT_GENERATE_LANTERN,
|
|
12
12
|
orb: PROMPT_GENERATE_ORB,
|
|
13
13
|
},
|
|
@@ -176,7 +176,7 @@ A list of previously used names will be provided. The new name must be unique an
|
|
|
176
176
|
|
|
177
177
|
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:
|
|
178
178
|
|
|
179
|
-
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.
|
|
179
|
+
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. It must not have more than 5 words.
|
|
180
180
|
|
|
181
181
|
2. Description (Descrição): Write a concise, grounded narrative that links the Amulet’s qualities to the traits of the zodiac combination. Emphasize protection, nurturing energy, and personal growth. Keep the Portuguese version faithful in tone and depth to the English version.
|
|
182
182
|
|
|
@@ -209,6 +209,54 @@ PT:
|
|
|
209
209
|
• Passagem Poética:[4 poetic lines uniquely written in Portuguese]
|
|
210
210
|
`;
|
|
211
211
|
|
|
212
|
+
export const PROMPT_GENERATE_AMULET_BASIC_INFO = `
|
|
213
|
+
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.
|
|
214
|
+
|
|
215
|
+
A list of previously used names will be provided. The new name must be unique and different from these.
|
|
216
|
+
|
|
217
|
+
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:
|
|
218
|
+
|
|
219
|
+
1. Name (Nome)
|
|
220
|
+
- The name must contain exactly 5 words.
|
|
221
|
+
- It must start with "The Amulet of…" in English and "O Amuleto de…" in Portuguese.
|
|
222
|
+
- Choose words that emphasize protection, stability, healing, and resilience.
|
|
223
|
+
- Keep the name sober, symbolic, and not overly long or ornate.
|
|
224
|
+
- Avoid redundancy and ensure each word adds value.
|
|
225
|
+
|
|
226
|
+
2. Description (Descrição)
|
|
227
|
+
- Write a concise and grounded narrative that links the Amulet’s qualities to the traits of the zodiac combination.
|
|
228
|
+
- Emphasize protection, nurturing energy, and personal growth.
|
|
229
|
+
- Keep the Portuguese version faithful in tone and depth to the English version.
|
|
230
|
+
|
|
231
|
+
3. Poetic Passage (Passagem Poética)
|
|
232
|
+
- Create two separate and independent poetic passages—one in English, one in Portuguese.
|
|
233
|
+
- The passage should be 4 poetic lines long.
|
|
234
|
+
- It must evoke protection, resilience, and healing.
|
|
235
|
+
- Do not translate the poem directly—let each version flourish naturally in its own language.
|
|
236
|
+
- The passage should flow rhythmically and be immersive.
|
|
237
|
+
- Do not mention the Amulet directly in the poem.
|
|
238
|
+
|
|
239
|
+
Rules and Restrictions:
|
|
240
|
+
- Do not mention zodiac signs or planets.
|
|
241
|
+
- Do not include the Amulet’s name in the description or poem.
|
|
242
|
+
- Ensure both descriptions are structurally and emotionally equivalent.
|
|
243
|
+
- The poem must be free-flowing and symbolic, avoiding direct translation.
|
|
244
|
+
|
|
245
|
+
Response Format:
|
|
246
|
+
|
|
247
|
+
EN:
|
|
248
|
+
• Name: The Amulet of [Symbolic Concept]
|
|
249
|
+
• Description: [Concise and grounded explanation]
|
|
250
|
+
• Poetic Passage:
|
|
251
|
+
[4 poetic lines uniquely written in English]
|
|
252
|
+
|
|
253
|
+
PT:
|
|
254
|
+
• Nome: O Amuleto de [Conceito Simbólico]
|
|
255
|
+
• Descrição: [Explicação concisa e bem fundamentada]
|
|
256
|
+
• Passagem Poética:
|
|
257
|
+
[4 poetic lines uniquely written in Portuguese]
|
|
258
|
+
`;
|
|
259
|
+
|
|
212
260
|
export const PROMPT_GENERATE_LANTERN = `
|
|
213
261
|
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.
|
|
214
262
|
|