@zodic/shared 0.0.314 → 0.0.316

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.
@@ -1,382 +1,281 @@
1
+ import { eq } from 'drizzle-orm';
1
2
  import { inject, injectable } from 'inversify';
2
3
  import 'reflect-metadata';
3
4
  import { schema } from '../..';
4
- import { Gender, VALID_GENDERS_ARRAY } from '../../types';
5
- import {
6
- KVArchetype,
7
- LeonardoGenerateImageResponse,
8
- sizes,
9
- } from '../../types/scopes/legacy';
10
- import { buildCosmicMirrorArchetypeKVKey } from '../../utils/KVKeysBuilders';
5
+ import { Gender } from '../../types';
11
6
  import { AppContext } from '../base/AppContext';
12
7
 
8
+ interface QueueMessage {
9
+ archetypeDataId: string;
10
+ combination: string;
11
+ gender: Gender;
12
+ language: string;
13
+ archetypeIndex: string;
14
+ }
15
+
13
16
  @injectable()
14
17
  export class LeonardoService {
15
18
  constructor(@inject(AppContext) private context: AppContext) {}
16
19
 
17
- /**
18
- * Processes and generates missing Leonardo prompts for archetypes.
19
- */
20
- async processArchetypesPrompts(crown: string): Promise<void> {
21
- console.log(`Processing Leonardo prompts for crown: ${crown}`);
22
-
23
- const promptsToGenerate: Array<{
24
- kvKey: string;
25
- name: string;
26
- visualDescription: string;
27
- }> = [];
28
-
29
- for (const gender of VALID_GENDERS_ARRAY) {
30
- for (let index = 1; index <= 3; index++) {
31
- const kvKey = buildCosmicMirrorArchetypeKVKey(
32
- 'en-us',
33
- crown,
34
- gender,
35
- index
36
- );
37
- console.log(`Fetching archetype from KV: ${kvKey}`);
38
-
39
- const kvData = await this.context
40
- .kvCosmicMirrorArchetypesStore()
41
- .get<KVArchetype>(kvKey, 'json');
42
- if (kvData && !kvData.leonardoPrompt) {
43
- promptsToGenerate.push({
44
- kvKey,
45
- name: kvData.name,
46
- visualDescription: kvData.visualDescription,
47
- });
48
- }
49
- }
50
- }
51
-
52
- if (promptsToGenerate.length === 0) {
53
- console.log('No archetype prompts need to be generated.');
54
- return;
55
- }
56
-
57
- console.log('Archetypes requiring prompt generation:', promptsToGenerate);
58
-
59
- const archetypes = promptsToGenerate.map(({ name, visualDescription }) => ({
60
- name,
61
- visualDescription,
62
- }));
63
- const leonardoPrompts =
64
- await this.generateCosmicMirrorArchetypesLeonardoPrompts(archetypes);
65
-
66
- if (leonardoPrompts.length !== archetypes.length) {
67
- throw new Error('Mismatch between archetypes and generated prompts.');
68
- }
69
-
70
- for (const [index, promptSet] of leonardoPrompts.entries()) {
71
- const { kvKey } = promptsToGenerate[index];
72
- console.log(`Updating KV with prompts for archetype: ${kvKey}`);
20
+ private async log(
21
+ level: 'info' | 'debug' | 'warn' | 'error',
22
+ message: string,
23
+ context: Record<string, any> = {}
24
+ ) {
25
+ const logId = `leonardo-service:${Date.now()}`;
26
+ const logMessage = `[${level.toUpperCase()}] ${message}`;
73
27
 
74
- const kvData = await this.context
75
- .kvCosmicMirrorArchetypesStore()
76
- .get<KVArchetype>(kvKey, 'json');
77
- if (!kvData) {
78
- console.warn(`KV data not found for key: ${kvKey}. Skipping update.`);
79
- continue;
80
- }
28
+ console[level](logMessage, context);
81
29
 
82
- kvData.leonardoPrompt = promptSet;
83
- await this.context
84
- .kvCosmicMirrorArchetypesStore()
85
- .put(kvKey, JSON.stringify(kvData));
30
+ const db = this.context.drizzle();
31
+ try {
32
+ await db
33
+ .insert(schema.logs)
34
+ .values({
35
+ id: logId,
36
+ level,
37
+ message,
38
+ context: JSON.stringify(context),
39
+ createdAt: new Date().getTime(),
40
+ })
41
+ .execute();
42
+ } catch (error) {
43
+ console.error('[ERROR] Failed to persist log to database:', {
44
+ error,
45
+ logId,
46
+ message,
47
+ context,
48
+ });
86
49
  }
87
-
88
- console.log(`Completed prompt processing for crown: ${crown}`);
89
50
  }
90
51
 
91
52
  /**
92
- * Queues image generation for archetypes if no images exist.
53
+ * Processes a message from ARCHETYPE_POPULATION_QUEUE to ensure the archetype has three images.
93
54
  */
94
- async queueImagesForArchetypes(crown: string, gender: Gender): Promise<void> {
95
- console.log(
96
- `Queuing image generation for crown: ${crown}, gender: ${gender}`
97
- );
98
-
99
- for (let index = 1; index <= 3; index++) {
100
- const kvKey = buildCosmicMirrorArchetypeKVKey(
101
- 'en-us',
102
- crown,
103
- gender,
104
- index
105
- );
106
- console.info(`Fetching KV for archetype: ${kvKey}`);
107
-
108
- const kvData = await this.context
109
- .kvCosmicMirrorArchetypesStore()
110
- .get<KVArchetype>(kvKey, 'json');
111
- if (!kvData || kvData.images.length >= 3) {
112
- console.info(
113
- `Skipping image generation for index ${index}, images already exist.`
114
- );
115
- continue;
116
- }
117
-
118
- console.info(`No images found. Queuing generation for index ${index}...`);
119
-
120
- const generationResponse = await this.queueImageGeneration(
121
- kvData.leonardoPrompt
122
- );
123
- const generationId = generationResponse.sdGenerationJob.generationId;
55
+ async processArchetypePopulation(message: QueueMessage): Promise<void> {
56
+ const { archetypeDataId, combination, gender, language, archetypeIndex } =
57
+ message;
58
+ await this.log('info', 'Processing archetype population', {
59
+ archetypeDataId,
60
+ combination,
61
+ gender,
62
+ language,
63
+ archetypeIndex,
64
+ });
124
65
 
125
- if (!generationId) {
126
- throw new Error('Leonardo generation failed to return a valid ID.');
127
- }
66
+ const db = this.context.drizzle();
67
+ const archetype = await db
68
+ .select()
69
+ .from(schema.archetypesData)
70
+ .where(eq(schema.archetypesData.id, archetypeDataId))
71
+ .limit(1)
72
+ .execute();
73
+
74
+ if (!archetype[0]) {
75
+ await this.log('error', 'Archetype not found', { archetypeDataId });
76
+ throw new Error(`Archetype not found: ${archetypeDataId}`);
77
+ }
128
78
 
129
- await this.context.drizzle().insert(schema.generations).values({
130
- id: generationId,
131
- archetypeIndex: index,
132
- conceptCombinationId: null,
133
- type: 'archetype_image',
134
- status: 'pending',
135
- createdAt: new Date(),
79
+ const images = JSON.parse(archetype[0].images || '[]');
80
+ if (images.length >= 3) {
81
+ await this.log('info', 'Archetype already has sufficient images', {
82
+ archetypeDataId,
83
+ imageCount: images.length,
136
84
  });
85
+ return;
86
+ }
137
87
 
138
- console.info(
139
- `Queued generation ${generationId} for archetype index ${index}`
88
+ if (!archetype[0].leonardoPrompt) {
89
+ await this.log('error', 'Missing Leonardo prompt for archetype', {
90
+ archetypeDataId,
91
+ });
92
+ throw new Error(
93
+ `Missing Leonardo prompt for archetype: ${archetypeDataId}`
140
94
  );
141
95
  }
142
- }
143
96
 
144
- /**
145
- * Generates Leonardo prompts for archetypes.
146
- */
147
- private async generateCosmicMirrorArchetypesLeonardoPrompts(
148
- archetypes: Array<{ name: string; visualDescription: string }>
149
- ): Promise<string[]> {
150
- console.log(`Generating Leonardo prompts for archetypes`);
97
+ await this.log('debug', 'Generating images for archetype', {
98
+ archetypeDataId,
99
+ prompt: archetype[0].leonardoPrompt,
100
+ });
151
101
 
152
- const messages = this.context
153
- .buildLLMMessages()
154
- .generateCosmicMirrorArchetypesLeonardoPrompts(archetypes);
155
- const response = await this.context.api().callChatGPT.single(messages, {});
102
+ const generationResponse = await this.context
103
+ .api()
104
+ .callLeonardo.generateImage({
105
+ prompt: archetype[0].leonardoPrompt,
106
+ width: 512,
107
+ height: 640,
108
+ quantity: 3 - images.length,
109
+ });
156
110
 
157
- if (!response) {
158
- throw new Error('Leonardo prompts not generated');
111
+ const generationId = generationResponse.sdGenerationJob.generationId;
112
+ if (!generationId) {
113
+ await this.log(
114
+ 'error',
115
+ 'Leonardo generation failed to return a valid ID',
116
+ { archetypeDataId }
117
+ );
118
+ throw new Error('Leonardo generation failed to return a valid ID');
159
119
  }
160
120
 
161
- console.log('Leonardo Prompts from API -> ', response);
121
+ await db.insert(schema.generations).values({
122
+ id: generationId,
123
+ archetypeIndex: parseInt(archetypeIndex),
124
+ archetypeDataId,
125
+ type: 'archetype_image',
126
+ status: 'pending',
127
+ gender,
128
+ createdAt: new Date(),
129
+ });
162
130
 
163
- return response
164
- .split('\n')
165
- .map((prompt) => prompt.trim())
166
- .filter(Boolean);
131
+ await this.log('info', 'Queued image generation', {
132
+ generationId,
133
+ archetypeDataId,
134
+ imagesToGenerate: 3 - images.length,
135
+ });
167
136
  }
168
137
 
169
138
  /**
170
- * Queues an image generation request to Leonardo.
139
+ * Uploads a user-provided image to Leonardo.ai.
171
140
  */
172
- private async queueImageGeneration(
173
- prompt: string
174
- ): Promise<LeonardoGenerateImageResponse> {
175
- console.log(`Queuing Leonardo image generation...`);
176
-
177
- const { width, height } = sizes['alchemy']['post4:5'];
178
-
179
- try {
180
- const response = await this.context
181
- .api()
182
- .callLeonardo.generateImage({ width, height, prompt });
183
- if (!response) {
184
- throw new Error('Leonardo image generation failed');
185
- }
186
-
187
- console.log('Leonardo Image Generation Response:', response);
188
- return response;
189
- } catch (error) {
190
- console.error('Error queuing Leonardo image generation:', error);
191
- throw error;
192
- }
193
- }
194
-
195
141
  async uploadImage(
196
142
  file: File
197
143
  ): Promise<{ uploadedImageId: string; uploadedImageUrl: string }> {
198
- const fileName = file.name;
199
- const fileExtension = fileName.split('.').pop()?.toLowerCase();
200
- const mimeType = `image/${fileExtension}`;
144
+ await this.log('info', 'Starting image upload', {
145
+ fileName: file.name,
146
+ fileSize: file.size,
147
+ });
201
148
 
202
- if (!fileExtension) {
203
- throw new Error('Invalid file extension');
204
- }
149
+ const fileExtension = file.name.split('.').pop()?.toLowerCase();
150
+ const mimeType = file.type || `image/${fileExtension}`;
205
151
 
206
- console.log('Starting uploadImage with details:', {
207
- fileName,
208
- fileExtension,
209
- mimeType,
210
- });
152
+ if (!fileExtension || !mimeType.startsWith('image/')) {
153
+ await this.log('error', 'Invalid file extension or type', {
154
+ fileName: file.name,
155
+ });
156
+ throw new Error('Invalid file extension or type');
157
+ }
211
158
 
212
159
  try {
213
- console.log('Requesting presigned URL...');
214
160
  const presignedUrlData = await this.context
215
161
  .api()
216
- .callLeonardo.generatePresignedUrl({
217
- extension: fileExtension,
218
- });
219
-
220
- if (
221
- !presignedUrlData.url ||
222
- !presignedUrlData.fields ||
223
- !presignedUrlData.id
224
- ) {
225
- throw new Error('Invalid presigned URL response from API');
226
- }
227
-
228
- console.log('Uploading file...');
229
- await this.uploadToPresignedUrl(
230
- presignedUrlData.url,
231
- presignedUrlData.fields,
232
- file
233
- );
162
+ .callLeonardo.generatePresignedUrl();
163
+ await this.context.api().callLeonardo.uploadImageToPresignedUrl(file, {
164
+ fields: presignedUrlData.fields,
165
+ url: presignedUrlData.url,
166
+ });
234
167
 
235
168
  const uploadedImageId = presignedUrlData.id;
169
+ // TODO: Replace hardcoded user ID with dynamic value from env or context
236
170
  const uploadedImageUrl = `https://cdn.leonardo.ai/users/b117a933-e5c9-45b2-96aa-4b619c2d74ba/initImages/${uploadedImageId}.${fileExtension}`;
237
171
 
238
- console.log('File uploaded successfully:', {
172
+ await this.log('info', 'Image uploaded successfully', {
239
173
  uploadedImageId,
240
174
  uploadedImageUrl,
241
175
  });
242
-
243
176
  return { uploadedImageId, uploadedImageUrl };
244
177
  } catch (error: any) {
245
- console.error('Error during image upload:', error.message);
178
+ await this.log('error', 'Error during image upload', {
179
+ message: error.message,
180
+ });
246
181
  throw new Error('Failed to upload image');
247
182
  }
248
183
  }
249
184
 
185
+ /**
186
+ * Uploads an image from a URL to Leonardo.ai.
187
+ */
250
188
  async uploadImageByUrl(
251
189
  imageUrl: string
252
190
  ): Promise<{ uploadedImageId: string; uploadedImageUrl: string }> {
253
- console.log('Starting uploadImageByUrl with imageUrl:', imageUrl);
191
+ await this.log('info', 'Starting image upload by URL', { imageUrl });
254
192
 
255
193
  try {
256
- console.log('Fetching image from URL...');
257
194
  const response = await fetch(imageUrl);
258
-
259
195
  if (!response.ok) {
260
- throw new Error(
261
- `Failed to fetch image from URL: ${response.statusText}`
262
- );
196
+ await this.log('error', 'Failed to fetch image from URL', {
197
+ status: response.status,
198
+ statusText: response.statusText,
199
+ });
200
+ throw new Error(`Failed to fetch image: ${response.statusText}`);
263
201
  }
264
202
 
265
203
  const blob = await response.blob();
266
204
  const fileExtension = imageUrl.split('.').pop()?.toLowerCase() || 'jpg';
267
205
  const mimeType = blob.type || `image/${fileExtension}`;
268
206
 
269
- if (!fileExtension || !mimeType.startsWith('image/')) {
270
- throw new Error('Invalid image URL or file type');
207
+ if (!mimeType.startsWith('image/')) {
208
+ await this.log('error', 'Invalid image type', { imageUrl, mimeType });
209
+ throw new Error('Invalid image type');
271
210
  }
272
211
 
273
- console.log('Image fetched successfully:', {
274
- fileExtension,
275
- mimeType,
212
+ // Explicitly use global File constructor to ensure Web API File
213
+ const file = new File([blob], `upload.${fileExtension}`, {
214
+ type: mimeType,
276
215
  });
277
216
 
278
- console.log('Requesting presigned URL...');
279
217
  const presignedUrlData = await this.context
280
218
  .api()
281
- .callLeonardo.generatePresignedUrl({
282
- extension: fileExtension,
219
+ .callLeonardo.generatePresignedUrl();
220
+ await this.context
221
+ .api()
222
+ .callLeonardo.uploadImageToPresignedUrl(file as File, {
223
+ fields: presignedUrlData.fields,
224
+ url: presignedUrlData.url,
283
225
  });
284
226
 
285
- if (
286
- !presignedUrlData.url ||
287
- !presignedUrlData.fields ||
288
- !presignedUrlData.id
289
- ) {
290
- throw new Error('Invalid presigned URL response from API');
291
- }
292
-
293
- console.log('Presigned URL obtained:', presignedUrlData);
294
-
295
- const file = new File([blob], `upload.${fileExtension}`, {
296
- type: mimeType,
297
- });
298
-
299
- console.log('Uploading file to presigned URL...');
300
- await this.uploadToPresignedUrl(
301
- presignedUrlData.url,
302
- presignedUrlData.fields,
303
- file as any
304
- );
305
-
306
227
  const uploadedImageId = presignedUrlData.id;
228
+ // TODO: Replace hardcoded user ID with dynamic value from env or context
307
229
  const uploadedImageUrl = `https://cdn.leonardo.ai/users/b117a933-e5c9-45b2-96aa-4b619c2d74ba/initImages/${uploadedImageId}.${fileExtension}`;
308
230
 
309
- console.log('Image uploaded successfully:', {
231
+ await this.log('info', 'Image uploaded successfully', {
310
232
  uploadedImageId,
311
233
  uploadedImageUrl,
312
234
  });
313
-
314
235
  return { uploadedImageId, uploadedImageUrl };
315
236
  } catch (error: any) {
316
- console.error('Error during uploadImageByUrl:', error.message);
237
+ await this.log('error', 'Error during uploadImageByUrl', {
238
+ message: error.message,
239
+ });
317
240
  throw new Error('Failed to upload image by URL');
318
241
  }
319
242
  }
320
243
 
321
- private async uploadToPresignedUrl(
322
- presignedUrl: string,
323
- fields: string,
324
- file: File
325
- ): Promise<void> {
326
- const parsedFields = JSON.parse(fields);
327
- const formData = new FormData();
328
-
329
- Object.entries(parsedFields).forEach(([key, value]) => {
330
- formData.append(key, value as string);
331
- });
332
- formData.append('file', file);
333
-
334
- console.log('Uploading with FormData:', Array.from(formData.entries()));
335
-
336
- const response = await fetch(presignedUrl, {
337
- method: 'POST',
338
- body: formData,
244
+ /**
245
+ * Personalizes a Leonardo prompt based on user-specific traits.
246
+ */
247
+ async personalizeCosmicMirrorLeonardoPrompt(params: {
248
+ leonardoPrompt: string;
249
+ traits: string;
250
+ }): Promise<string> {
251
+ const { leonardoPrompt, traits } = params;
252
+ await this.log('info', 'Personalizing Leonardo prompt', {
253
+ leonardoPrompt,
254
+ traits,
339
255
  });
340
256
 
341
- if (!response.ok) {
342
- const errorDetails = await response.text();
343
- console.error('Upload failed:', errorDetails);
344
- throw new Error(`Image upload failed: ${response.status}`);
345
- }
346
-
347
- console.log('Upload to presigned URL successful');
348
- }
257
+ const messages = this.context
258
+ .buildLLMMessages()
259
+ .personalizeCosmicMirrorLeonardoPrompt({ leonardoPrompt, traits });
349
260
 
350
- /**
351
- * Personalizes a Leonardo prompt based on user-specific traits.
352
- * @param params - Details for prompt personalization
353
- * @returns {Promise<string>} - The personalized prompt
354
- */
355
- async personalizeCosmicMirrorLeonardoPrompt(params: {
356
- leonardoPrompt: string;
357
- traits: string;
358
- }): Promise<string> {
359
- const { leonardoPrompt, traits } = params;
360
-
361
- const messages = this.context
362
- .buildLLMMessages()
363
- .personalizeCosmicMirrorLeonardoPrompt({ leonardoPrompt, traits });
364
-
365
- try {
366
- const personalizedPrompt = await this.context
367
- .api()
368
- .callChatGPT.single(messages, {});
261
+ try {
262
+ const personalizedPrompt = await this.context
263
+ .api()
264
+ .callChatGPT.single(messages, {});
265
+ if (!personalizedPrompt) {
266
+ await this.log('error', 'Failed to generate personalized prompt');
267
+ throw new Error('Failed to generate personalized prompt');
268
+ }
369
269
 
370
- if (!personalizedPrompt) {
371
- throw new Error('Failed to generate personalized prompt');
270
+ await this.log('info', 'Successfully personalized prompt', {
271
+ personalizedPrompt,
272
+ });
273
+ return personalizedPrompt.trim();
274
+ } catch (error: any) {
275
+ await this.log('error', 'Error personalizing Leonardo prompt', {
276
+ message: error.message,
277
+ });
278
+ throw error;
372
279
  }
373
-
374
- console.log('Personalized Leonardo Prompt:', personalizedPrompt);
375
-
376
- return personalizedPrompt.trim();
377
- } catch (error) {
378
- console.error('Error personalizing Leonardo prompt:', error);
379
- throw error;
380
280
  }
381
281
  }
382
- }