From f572428a87940bf9e2a524215d6f398e94c62fab Mon Sep 17 00:00:00 2001 From: Oleg Proskurin Date: Mon, 29 Sep 2025 22:49:32 +0700 Subject: [PATCH] fix: image generation --- .../src/services/ImageGenService.ts | 90 ++++++------------- .../src/services/MinioStorageService.ts | 6 +- .../src/services/StorageFactory.ts | 15 ---- .../src/services/StorageService.ts | 36 ++++---- 4 files changed, 48 insertions(+), 99 deletions(-) diff --git a/apps/api-service/src/services/ImageGenService.ts b/apps/api-service/src/services/ImageGenService.ts index 0ca8393..caba69f 100644 --- a/apps/api-service/src/services/ImageGenService.ts +++ b/apps/api-service/src/services/ImageGenService.ts @@ -8,6 +8,7 @@ import { ReferenceImage, } from "../types/api"; import { StorageFactory } from "./StorageFactory"; +import { UploadResult } from "./StorageService"; export class ImageGenService { private ai: GoogleGenAI; @@ -27,20 +28,18 @@ export class ImageGenService { async generateImage( options: ImageGenerationOptions, ): Promise { - const { prompt, filename, referenceImages, orgId, projectId, userId } = options; + const { prompt, filename, referenceImages, orgId, projectId, userId } = + options; const timestamp = new Date().toISOString(); // Use default values if not provided - const finalOrgId = orgId || process.env['DEFAULT_ORG_ID'] || 'default'; - const finalProjectId = projectId || process.env['DEFAULT_PROJECT_ID'] || 'main'; - const finalUserId = userId || process.env['DEFAULT_USER_ID'] || 'system'; + const finalOrgId = orgId || process.env["DEFAULT_ORG_ID"] || "default"; + const finalProjectId = + projectId || process.env["DEFAULT_PROJECT_ID"] || "main"; + const finalUserId = userId || process.env["DEFAULT_USER_ID"] || "system"; - console.log( - `[${timestamp}] Starting image generation: "${prompt.substring(0, 50)}..." for ${finalOrgId}/${finalProjectId}`, - ); try { - // First try the primary model (Nano Banana) const result = await this.tryGeneration({ model: this.primaryModel, config: { responseModalities: ["IMAGE", "TEXT"] }, @@ -50,17 +49,13 @@ export class ImageGenService { projectId: finalProjectId, userId: finalUserId, ...(referenceImages && { referenceImages }), - modelName: "Nano Banana", + modelName: "Primary Model", }); if (result.success) { return result; } - // Fallback to Imagen 4 - console.log( - `[${new Date().toISOString()}] Primary model failed, trying fallback (Imagen 4)...`, - ); return await this.tryGeneration({ model: this.fallbackModel, @@ -71,13 +66,9 @@ export class ImageGenService { projectId: finalProjectId, userId: finalUserId, ...(referenceImages && { referenceImages }), - modelName: "Imagen 4", + modelName: "Fallback Model", }); } catch (error) { - console.error( - `[${new Date().toISOString()}] Image generation failed:`, - error, - ); return { success: false, model: "none", @@ -87,9 +78,6 @@ export class ImageGenService { } } - /** - * Try generation with a specific model - */ private async tryGeneration(params: { model: string; config: { responseModalities: string[] }; @@ -101,18 +89,22 @@ export class ImageGenService { referenceImages?: ReferenceImage[]; modelName: string; }): Promise { - const { model, config, prompt, filename, orgId, projectId, userId, referenceImages, modelName } = - params; + const { + model, + config, + prompt, + filename, + orgId, + projectId, + userId, + referenceImages, + modelName, + } = params; try { - // Build content parts for the API request const contentParts: any[] = []; - // Add reference images if provided if (referenceImages && referenceImages.length > 0) { - console.log( - `[${new Date().toISOString()}] Adding ${referenceImages.length} reference image(s)`, - ); for (const refImage of referenceImages) { contentParts.push({ @@ -124,7 +116,6 @@ export class ImageGenService { } } - // Add the text prompt contentParts.push({ text: prompt, }); @@ -136,9 +127,6 @@ export class ImageGenService { }, ]; - console.log( - `[${new Date().toISOString()}] Making API request to ${modelName} (${model})...`, - ); const response = await this.ai.models.generateContent({ model, @@ -146,9 +134,6 @@ export class ImageGenService { contents, }); - console.log( - `[${new Date().toISOString()}] Response received from ${modelName}`, - ); if ( response.candidates && @@ -157,7 +142,7 @@ export class ImageGenService { ) { const content = response.candidates[0].content; let generatedDescription = ""; - let uploadResult = null; + let uploadResult: UploadResult | null = null; for (let index = 0; index < (content.parts?.length || 0); index++) { const part = content.parts?.[index]; @@ -168,36 +153,30 @@ export class ImageGenService { part.inlineData.mimeType || "", ); const finalFilename = `${filename}.${fileExtension}`; - const contentType = part.inlineData.mimeType || `image/${fileExtension}`; + const contentType = + part.inlineData.mimeType || `image/${fileExtension}`; - console.log( - `[${new Date().toISOString()}] Uploading image to MinIO: ${finalFilename}`, - ); const buffer = Buffer.from(part.inlineData.data || "", "base64"); - // Upload to MinIO storage const storageService = StorageFactory.getInstance(); - uploadResult = await storageService.uploadFile( + const result = (await storageService).uploadFile( orgId, projectId, - 'generated', + "generated", finalFilename, buffer, - contentType + contentType, ); - console.log( - `[${new Date().toISOString()}] Image uploaded successfully: ${uploadResult.path}`, - ); + uploadResult = await result; + } else if (part.text) { generatedDescription = part.text; - console.log( - `[${new Date().toISOString()}] Generated description: ${part.text.substring(0, 100)}...`, - ); } } + if (uploadResult && uploadResult.success) { return { success: true, @@ -216,10 +195,6 @@ export class ImageGenService { error: "No image data received from API", }; } catch (error) { - console.error( - `[${new Date().toISOString()}] ${modelName} generation failed:`, - error, - ); return { success: false, model: modelName, @@ -228,10 +203,6 @@ export class ImageGenService { } } - - /** - * Validate reference images - */ static validateReferenceImages(files: Express.Multer.File[]): { valid: boolean; error?: string; @@ -262,9 +233,6 @@ export class ImageGenService { return { valid: true }; } - /** - * Convert Express.Multer.File[] to ReferenceImage[] - */ static convertFilesToReferenceImages( files: Express.Multer.File[], ): ReferenceImage[] { diff --git a/apps/api-service/src/services/MinioStorageService.ts b/apps/api-service/src/services/MinioStorageService.ts index d64824f..dea9434 100644 --- a/apps/api-service/src/services/MinioStorageService.ts +++ b/apps/api-service/src/services/MinioStorageService.ts @@ -312,7 +312,6 @@ export class MinioStorageService implements StorageService { createdAt: obj.lastModified || new Date() }); } catch (error) { - console.error(`Error processing file ${obj.name}:`, error); } }); @@ -328,7 +327,6 @@ export class MinioStorageService implements StorageService { filename: string; } | null { try { - // Key format: banatie/orgId/projectId/category/year-month/filename const match = key.match(/^banatie\/([^/]+)\/([^/]+)\/(uploads|generated|references)\/[^/]+\/(.+)$/); if (!match) { @@ -352,7 +350,6 @@ export class MinioStorageService implements StorageService { } } - // MISSING METHODS FROM INTERFACE async fileExists( orgId: string, @@ -376,7 +373,7 @@ export class MinioStorageService implements StorageService { category: 'uploads' | 'generated' | 'references', prefix?: string ): Promise { - this.validateFilePath(orgId, projectId, category, 'dummy.txt'); // Validate path components + this.validateFilePath(orgId, projectId, category, 'dummy.txt'); const basePath = `${orgId}/${projectId}/${category}/`; const searchPrefix = prefix ? `${basePath}${prefix}` : basePath; @@ -406,7 +403,6 @@ export class MinioStorageService implements StorageService { path: obj.name }); } catch (error) { - console.error(`Error processing file ${obj.name}:`, error); } }); diff --git a/apps/api-service/src/services/StorageFactory.ts b/apps/api-service/src/services/StorageFactory.ts index 244f3b5..9bbb502 100644 --- a/apps/api-service/src/services/StorageFactory.ts +++ b/apps/api-service/src/services/StorageFactory.ts @@ -25,13 +25,11 @@ export class StorageFactory { } } - // Synchronous version for backward compatibility (with graceful degradation) static getInstanceSync(): StorageService { if (!this.instance) { try { this.instance = this.createStorageService(); } catch (error) { - console.error('Failed to create storage service:', error); throw new Error('Storage service unavailable. Please check MinIO configuration.'); } } @@ -45,18 +43,14 @@ export class StorageFactory { for (let attempt = 1; attempt <= maxRetries; attempt++) { try { - console.log(`Attempting to create storage service (attempt ${attempt}/${maxRetries})`); const service = this.createStorageService(); - // Test the connection by checking if bucket exists await service.bucketExists(); - console.log('Storage service created successfully'); return service; } catch (error) { - console.error(`Storage service creation attempt ${attempt} failed:`, error); if (attempt === maxRetries) { throw new Error( @@ -65,9 +59,7 @@ export class StorageFactory { ); } - // Exponential backoff const delay = baseDelay * Math.pow(2, attempt - 1); - console.log(`Waiting ${delay}ms before retry...`); await this.sleep(delay); } } @@ -98,11 +90,6 @@ export class StorageFactory { ); } - console.log(`Initializing MinIO Storage Service:`); - console.log(` Endpoint: ${endpoint}`); - console.log(` Bucket: ${bucketName}`); - console.log(` SSL: ${useSSL}`); - console.log(` Public URL: ${publicUrl}`); return new MinioStorageService( endpoint, @@ -118,12 +105,10 @@ export class StorageFactory { throw new Error(`Unsupported storage type: ${storageType}`); } } catch (error) { - console.error('Error creating storage service:', error); throw error; } } - // Reset instance for testing static resetInstance(): void { this.instance = null; this.initializationPromise = null; diff --git a/apps/api-service/src/services/StorageService.ts b/apps/api-service/src/services/StorageService.ts index 6d22a00..f5f4d49 100644 --- a/apps/api-service/src/services/StorageService.ts +++ b/apps/api-service/src/services/StorageService.ts @@ -1,4 +1,4 @@ -import { Readable } from 'stream'; +import { Readable } from "stream"; export interface FileMetadata { filename: string; @@ -42,10 +42,10 @@ export interface StorageService { uploadFile( orgId: string, projectId: string, - category: 'uploads' | 'generated' | 'references', + category: "uploads" | "generated" | "references", filename: string, buffer: Buffer, - contentType: string + contentType: string, ): Promise; /** @@ -58,8 +58,8 @@ export interface StorageService { downloadFile( orgId: string, projectId: string, - category: 'uploads' | 'generated' | 'references', - filename: string + category: "uploads" | "generated" | "references", + filename: string, ): Promise; /** @@ -72,8 +72,8 @@ export interface StorageService { streamFile( orgId: string, projectId: string, - category: 'uploads' | 'generated' | 'references', - filename: string + category: "uploads" | "generated" | "references", + filename: string, ): Promise; /** @@ -87,9 +87,9 @@ export interface StorageService { getPresignedDownloadUrl( orgId: string, projectId: string, - category: 'uploads' | 'generated' | 'references', + category: "uploads" | "generated" | "references", filename: string, - expirySeconds: number + expirySeconds: number, ): Promise; /** @@ -104,10 +104,10 @@ export interface StorageService { getPresignedUploadUrl( orgId: string, projectId: string, - category: 'uploads' | 'generated' | 'references', + category: "uploads" | "generated" | "references", filename: string, expirySeconds: number, - contentType: string + contentType: string, ): Promise; /** @@ -120,8 +120,8 @@ export interface StorageService { listFiles( orgId: string, projectId: string, - category: 'uploads' | 'generated' | 'references', - prefix?: string + category: "uploads" | "generated" | "references", + prefix?: string, ): Promise; /** @@ -134,8 +134,8 @@ export interface StorageService { deleteFile( orgId: string, projectId: string, - category: 'uploads' | 'generated' | 'references', - filename: string + category: "uploads" | "generated" | "references", + filename: string, ): Promise; /** @@ -148,7 +148,7 @@ export interface StorageService { fileExists( orgId: string, projectId: string, - category: 'uploads' | 'generated' | 'references', - filename: string + category: "uploads" | "generated" | "references", + filename: string, ): Promise; -} \ No newline at end of file +}