@dataclouder/nest-vertex 0.0.27 → 0.0.29

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.
@@ -17,16 +17,13 @@ export declare class ComfyUiController {
17
17
  constructor(comfyImageService: ComfyImageService, comfyVideoService: ComfyVideoService, generatedAssetService: GeneratedAssetService, httpService: HttpService, eventEmitter: EventEmitter2);
18
18
  generateImage(generateImageDto: ImageGenRequestAdapter): Promise<ImageGenAdapterResponse>;
19
19
  generateVideoFromLocalImage(positivePrompt: string, negativePrompt: string): Promise<{
20
- message: string;
21
- asset: import("../..").GeneratedAsset;
20
+ prompt_id: string;
22
21
  }>;
23
22
  generateVideoFromAsset(id: string): Promise<{
24
- message: string;
25
- asset: import("../..").GeneratedAsset;
23
+ prompt_id: string;
26
24
  }>;
27
25
  generateVideoFromUrl(videoUrlRequestDto: VideoUrlRequestDto): Promise<{
28
- message: string;
29
- asset: import("../..").GeneratedAsset;
26
+ prompt_id: string;
30
27
  }>;
31
28
  sse(): Observable<MessageEvent>;
32
29
  }
@@ -79,16 +79,7 @@ let ComfyUiController = ComfyUiController_1 = class ComfyUiController {
79
79
  if (!asset || !asset.assets?.firstFrame?.url) {
80
80
  throw new common_1.NotFoundException(`Asset with id ${id} not found or does not have a valid image url.`);
81
81
  }
82
- const imageUrl = asset.assets.firstFrame.url;
83
- const response = await this.httpService.axiosRef.get(imageUrl, { responseType: 'arraybuffer' });
84
- const imageBuffer = Buffer.from(response.data, 'binary');
85
- const videoRequest = {
86
- workflowPath: path.resolve('./comfy_workflows/video-15_seconds.json'),
87
- inputImage: imageBuffer,
88
- positivePrompt: 'Faces are smiling',
89
- negativePrompt: 'low quality, blurry, static',
90
- };
91
- const videoResponse = await this.comfyVideoService.runVideoGenerationFromWorkflow(videoRequest, id);
82
+ const videoResponse = await this.comfyVideoService.runVideoGenerationForAssetId(id);
92
83
  this.logger.log(`ComfyUI video generation started. Response: ${JSON.stringify(videoResponse)}`);
93
84
  return videoResponse;
94
85
  }
@@ -98,17 +89,13 @@ let ComfyUiController = ComfyUiController_1 = class ComfyUiController {
98
89
  }
99
90
  }
100
91
  async generateVideoFromUrl(videoUrlRequestDto) {
101
- const { url, positivePrompt, negativePrompt } = videoUrlRequestDto;
92
+ const { url } = videoUrlRequestDto;
102
93
  this.logger.log(`Received request to generate video from url: ${url}`);
103
94
  try {
104
95
  const newAsset = await this.generatedAssetService.save({
105
96
  assets: { firstFrame: { url } },
106
97
  });
107
- const videoResponse = await this.comfyVideoService.runVideoGenerationForAssetId({
108
- assetId: newAsset.id,
109
- positivePrompt,
110
- negativePrompt,
111
- });
98
+ const videoResponse = await this.comfyVideoService.runVideoGenerationForAssetId(newAsset.id);
112
99
  this.logger.log(`ComfyUI video generation started. Response: ${JSON.stringify(videoResponse)}`);
113
100
  return videoResponse;
114
101
  }
@@ -16,4 +16,7 @@ export interface VideoGenRequest {
16
16
  positivePrompt: string;
17
17
  negativePrompt: string;
18
18
  outputFilenamePrefix?: string;
19
+ width?: number;
20
+ height?: number;
21
+ seconds?: number;
19
22
  }
@@ -32,11 +32,11 @@ exports.comfyServicesProviders = [
32
32
  },
33
33
  {
34
34
  provide: comfy_video_service_1.ComfyVideoService,
35
- useFactory: (connectionService, httpService, generatedAssetService, cloudStorageService) => {
35
+ useFactory: (connectionService, httpService, generatedAssetService) => {
36
36
  if (!connectionService) {
37
37
  return null;
38
38
  }
39
- return new comfy_video_service_1.ComfyVideoService(connectionService, httpService, generatedAssetService, cloudStorageService);
39
+ return new comfy_video_service_1.ComfyVideoService(connectionService, generatedAssetService, httpService);
40
40
  },
41
41
  inject: [comfy_connection_service_1.ComfyConnectionService, axios_1.HttpService, generated_asset_service_1.GeneratedAssetService, nest_storage_1.CloudStorageService],
42
42
  },
@@ -29,6 +29,7 @@ export declare class ComfyConnectionService implements OnModuleInit, OnModuleDes
29
29
  queuePrompt(workflow: any): Promise<{
30
30
  prompt_id: string;
31
31
  }>;
32
+ uploadImage(imageBuffer: Buffer, filename: string): Promise<any>;
32
33
  queuePromptAndWait(workflow: any, timeoutMs?: number): Promise<any>;
33
34
  getAssetData(filename: string, subfolder: string, type: string): Promise<Buffer>;
34
35
  }
@@ -23,6 +23,7 @@ const rxjs_1 = require("rxjs");
23
23
  const generated_asset_service_1 = require("../../services/generated-asset.service");
24
24
  const nest_storage_1 = require("@dataclouder/nest-storage");
25
25
  const event_emitter_1 = require("@nestjs/event-emitter");
26
+ const FormData = require("form-data");
26
27
  let ComfyConnectionService = ComfyConnectionService_1 = class ComfyConnectionService {
27
28
  configService;
28
29
  httpService;
@@ -178,6 +179,16 @@ let ComfyConnectionService = ComfyConnectionService_1 = class ComfyConnectionSer
178
179
  };
179
180
  return (0, rxjs_1.firstValueFrom)(this.httpService.post(url, payload)).then((res) => res.data);
180
181
  }
182
+ async uploadImage(imageBuffer, filename) {
183
+ const url = `http://${this.comfyApiHost}/upload/image`;
184
+ const form = new FormData();
185
+ form.append('image', imageBuffer, { filename });
186
+ form.append('overwrite', 'true');
187
+ const response = await (0, rxjs_1.firstValueFrom)(this.httpService.post(url, form, {
188
+ headers: form.getHeaders(),
189
+ }));
190
+ return response.data;
191
+ }
181
192
  async queuePromptAndWait(workflow, timeoutMs = 300000) {
182
193
  const { prompt_id } = await this.queuePrompt(workflow);
183
194
  this.logger.log(`Queued prompt with ID: ${prompt_id}. Waiting for execution...`);
@@ -1,23 +1,18 @@
1
- import { RunVideoGenerationForAssetIdDto } from '../dto/video-generation.dto';
2
1
  import { ComfyConnectionService } from './comfy-connection.service';
3
- import { VideoGenRequest } from '../models/comfy.models';
4
- import { HttpService } from '@nestjs/axios';
5
2
  import { GeneratedAssetService } from '../../services/generated-asset.service';
6
- import { CloudStorageService } from '@dataclouder/nest-storage';
7
- import { GeneratedAsset } from '../../models/generated-asset.entity';
3
+ import { HttpService } from '@nestjs/axios';
4
+ import { VideoGenRequest } from '../models/comfy.models';
8
5
  export declare class ComfyVideoService {
9
- private readonly connectionService;
10
- private readonly httpService;
6
+ private readonly comfyConnectionService;
11
7
  private readonly generatedAssetService;
12
- private readonly cloudStorageService;
8
+ private readonly httpService;
13
9
  private readonly logger;
14
- constructor(connectionService: ComfyConnectionService, httpService: HttpService, generatedAssetService: GeneratedAssetService, cloudStorageService: CloudStorageService);
15
- runVideoGenerationFromWorkflow(request: VideoGenRequest, generatedAssetId?: string): Promise<{
16
- message: string;
17
- asset: GeneratedAsset;
10
+ constructor(comfyConnectionService: ComfyConnectionService, generatedAssetService: GeneratedAssetService, httpService: HttpService);
11
+ updateWorkflow(width: number, height: number, seconds: number): Promise<any>;
12
+ runVideoGenerationFromWorkflow(videoRequest: VideoGenRequest, assetId?: string): Promise<{
13
+ prompt_id: string;
18
14
  }>;
19
- runVideoGenerationForAssetId({ assetId, positivePrompt, negativePrompt, metadata, }: RunVideoGenerationForAssetIdDto): Promise<{
20
- message: string;
21
- asset: GeneratedAsset;
15
+ runVideoGenerationForAssetId(assetId: string, metadata?: any): Promise<{
16
+ prompt_id: string;
22
17
  }>;
23
18
  }
@@ -15,92 +15,68 @@ const common_1 = require("@nestjs/common");
15
15
  const fs = require("fs/promises");
16
16
  const path = require("path");
17
17
  const comfy_connection_service_1 = require("./comfy-connection.service");
18
- const axios_1 = require("@nestjs/axios");
19
18
  const generated_asset_service_1 = require("../../services/generated-asset.service");
20
- const nest_storage_1 = require("@dataclouder/nest-storage");
19
+ const axios_1 = require("@nestjs/axios");
21
20
  let ComfyVideoService = ComfyVideoService_1 = class ComfyVideoService {
22
- connectionService;
23
- httpService;
21
+ comfyConnectionService;
24
22
  generatedAssetService;
25
- cloudStorageService;
23
+ httpService;
26
24
  logger = new common_1.Logger(ComfyVideoService_1.name);
27
- constructor(connectionService, httpService, generatedAssetService, cloudStorageService) {
28
- this.connectionService = connectionService;
29
- this.httpService = httpService;
25
+ constructor(comfyConnectionService, generatedAssetService, httpService) {
26
+ this.comfyConnectionService = comfyConnectionService;
30
27
  this.generatedAssetService = generatedAssetService;
31
- this.cloudStorageService = cloudStorageService;
32
- if (!connectionService) {
33
- this.logger.warn('ComfyConnectionService not available. ComfyVideoService will not be initialized.');
34
- }
28
+ this.httpService = httpService;
35
29
  }
36
- async runVideoGenerationFromWorkflow(request, generatedAssetId = null) {
37
- this.logger.log(`Starting video generation with workflow: ${request.workflowPath} for asset ${generatedAssetId}`);
38
- let asset;
39
- try {
40
- asset = await this.generatedAssetService.findOne(generatedAssetId);
41
- if (!asset) {
42
- throw new Error('GeneratedAsset not found');
43
- }
44
- const host = this.connectionService.getComfyApiHost();
45
- const formData = new FormData();
46
- const uniqueFilename = `input_${Date.now()}.png`;
47
- formData.append('image', new Blob([request.inputImage]), uniqueFilename);
48
- formData.append('overwrite', 'true');
49
- const uploadResponse = await fetch(`http://${host}/upload/image`, {
50
- method: 'POST',
51
- body: formData,
52
- });
53
- if (!uploadResponse.ok) {
54
- throw new Error(`Failed to upload image: ${await uploadResponse.text()}`);
55
- }
56
- const uploadedImage = await uploadResponse.json();
57
- this.logger.log(`Successfully uploaded image: ${uploadedImage.name}`);
58
- const workflowJson = await fs.readFile(request.workflowPath, 'utf-8');
59
- const workflow = JSON.parse(workflowJson);
60
- workflow['6'].inputs.text = request.positivePrompt;
61
- workflow['7'].inputs.text = request.negativePrompt;
62
- workflow['62'].inputs.image = uploadedImage.name;
63
- if (request.outputFilenamePrefix) {
64
- workflow['90'].inputs.filename_prefix = request.outputFilenamePrefix;
65
- }
66
- const response = await this.connectionService.queuePrompt(workflow);
67
- this.logger.log(`Workflow queued, received prompt id: ${response.prompt_id}`);
68
- asset.operationId = response.prompt_id;
69
- await this.generatedAssetService.partialUpdate(asset.id, { operationId: response.prompt_id });
70
- return {
71
- message: 'Video generation workflow has been queued successfully.',
72
- asset,
73
- };
30
+ async updateWorkflow(width, height, seconds) {
31
+ const workflowPath = path.join(process.cwd(), 'comfy_workflows', 'video-15_seconds.json');
32
+ const workflowData = await fs.readFile(workflowPath, 'utf-8');
33
+ const workflow = JSON.parse(workflowData);
34
+ if (workflow['84']) {
35
+ workflow['84'].inputs.width = width;
36
+ workflow['84'].inputs.height = height;
74
37
  }
75
- catch (error) {
76
- this.logger.error('Error during video generation from workflow:', error.message || error, error.stack);
77
- if (asset) {
78
- asset.status = 'failed';
79
- await this.generatedAssetService.partialUpdate(asset.id, { status: 'failed' });
80
- }
81
- throw new common_1.InternalServerErrorException(`Failed to run video generation workflow: ${error.message}`);
38
+ if (workflow['63']) {
39
+ const lengthInFrames = seconds * 16 + 1;
40
+ workflow['63'].inputs.length = lengthInFrames;
82
41
  }
42
+ return workflow;
83
43
  }
84
- async runVideoGenerationForAssetId({ assetId, positivePrompt, negativePrompt, metadata, }) {
85
- this.logger.log(`Starting video generation for asset id: ${assetId}`);
86
- const asset = await this.generatedAssetService.findOne(assetId);
87
- if (!asset || !asset.assets?.firstFrame?.url) {
88
- console.log('No puede generar video si no hay una imagen...');
89
- throw new common_1.NotFoundException(`Asset with id ${assetId} not found or does not have a valid image url.`);
44
+ async runVideoGenerationFromWorkflow(videoRequest, assetId) {
45
+ const { inputImage, positivePrompt, negativePrompt } = videoRequest;
46
+ const workflow = await this.updateWorkflow(videoRequest.width, videoRequest.height, videoRequest.seconds);
47
+ workflow['6'].inputs.text = positivePrompt;
48
+ workflow['7'].inputs.text = negativePrompt;
49
+ const uploadResponse = await this.comfyConnectionService.uploadImage(inputImage, 'input_image.jpg');
50
+ const imageName = uploadResponse.name;
51
+ if (workflow['62']) {
52
+ workflow['62'].inputs.image = imageName;
53
+ }
54
+ const response = await this.comfyConnectionService.queuePrompt(workflow);
55
+ if (assetId) {
56
+ await this.generatedAssetService.update(assetId, { operationId: response.prompt_id });
90
57
  }
58
+ return response;
59
+ }
60
+ async runVideoGenerationForAssetId(assetId, metadata = null) {
61
+ const genAsset = await this.generatedAssetService.findOne(assetId);
91
62
  if (metadata) {
92
- asset.metadata = metadata;
93
- console.log('actualizando asset con metadata', metadata);
94
- await this.generatedAssetService.partialUpdate(asset.id, { metadata });
63
+ console.log('Updating asset metadata for future retrieval:', metadata);
64
+ await this.generatedAssetService.partialUpdate(assetId, metadata);
65
+ }
66
+ if (!genAsset || !genAsset.assets?.firstFrame?.url) {
67
+ throw new common_1.NotFoundException(`Asset with id ${assetId} not found or does not have a valid image url.`);
95
68
  }
96
- const imageUrl = asset.assets.firstFrame.url;
69
+ const imageUrl = genAsset.assets.firstFrame.url;
97
70
  const response = await this.httpService.axiosRef.get(imageUrl, { responseType: 'arraybuffer' });
98
71
  const imageBuffer = Buffer.from(response.data, 'binary');
99
72
  const videoRequest = {
100
- workflowPath: path.resolve('./comfy_workflows/video-15_seconds.json'),
73
+ workflowPath: path.join(process.cwd(), 'comfy_workflows', 'video-15_seconds.json'),
101
74
  inputImage: imageBuffer,
102
- positivePrompt: positivePrompt || 'Faces are smiling',
103
- negativePrompt: negativePrompt || 'low quality, blurry, static',
75
+ positivePrompt: genAsset.prompt || genAsset?.description || 'Random movement for video',
76
+ negativePrompt: 'low quality, blurry, static',
77
+ width: genAsset.request?.width || null,
78
+ height: genAsset.request?.height || null,
79
+ seconds: genAsset.request?.seconds || null,
104
80
  };
105
81
  return this.runVideoGenerationFromWorkflow(videoRequest, assetId);
106
82
  }
@@ -109,8 +85,7 @@ exports.ComfyVideoService = ComfyVideoService;
109
85
  exports.ComfyVideoService = ComfyVideoService = ComfyVideoService_1 = __decorate([
110
86
  (0, common_1.Injectable)(),
111
87
  __metadata("design:paramtypes", [comfy_connection_service_1.ComfyConnectionService,
112
- axios_1.HttpService,
113
88
  generated_asset_service_1.GeneratedAssetService,
114
- nest_storage_1.CloudStorageService])
89
+ axios_1.HttpService])
115
90
  ], ComfyVideoService);
116
91
  //# sourceMappingURL=comfy-video.service.js.map
@@ -90,10 +90,6 @@ exports.DefaultBestVoice = {
90
90
  female: 'es-US-Neural2-A',
91
91
  male: 'es-US-Studio-B',
92
92
  },
93
- it: {
94
- female: 'it-IT-Standard-E',
95
- male: 'it-IT-Neural2-F',
96
- },
97
93
  pt: {
98
94
  female: 'pt-BR-Neural2-C',
99
95
  male: 'pt-BR-Chirp3-HD-Fenrir',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dataclouder/nest-vertex",
3
- "version": "0.0.27",
3
+ "version": "0.0.29",
4
4
  "description": "NestJS Vertex AI library for Dataclouder",
5
5
  "author": "dataclouder",
6
6
  "license": "MIT",
@@ -1,10 +1,8 @@
1
1
  import { NestTtsService } from './vertex-tts.service';
2
- import { AudioGenRequestAdapter, AudioGenAdapterResponse } from '../models/adapter.models';
3
2
  import { google } from '@google-cloud/text-to-speech/build/protos/protos';
4
3
  export declare class AudioGenAdapterService {
5
4
  private nestTtsService;
6
5
  private readonly logger;
7
6
  constructor(nestTtsService: NestTtsService);
8
7
  listVoices(provider?: string, languageCode?: string): Promise<google.cloud.texttospeech.v1.IVoice[]>;
9
- generateAudio(audioDto: AudioGenRequestAdapter): Promise<AudioGenAdapterResponse>;
10
8
  }
@@ -28,20 +28,6 @@ let AudioGenAdapterService = AudioGenAdapterService_1 = class AudioGenAdapterSer
28
28
  return [];
29
29
  }
30
30
  }
31
- async generateAudio(audioDto) {
32
- if (audioDto.provider === 'google' || !audioDto.provider) {
33
- const audioBuffer = await this.nestTtsService.synthesizeSpeech(audioDto);
34
- return {
35
- audio: audioBuffer,
36
- mimeType: 'audio/mpeg',
37
- provider: 'google',
38
- };
39
- }
40
- else {
41
- this.logger.error(`Provider ${audioDto.provider} not supported for audio generation.`);
42
- throw new Error(`Provider ${audioDto.provider} not supported for audio generation.`);
43
- }
44
- }
45
31
  };
46
32
  exports.AudioGenAdapterService = AudioGenAdapterService;
47
33
  exports.AudioGenAdapterService = AudioGenAdapterService = AudioGenAdapterService_1 = __decorate([
@@ -138,7 +138,7 @@ let GeminiChatService = GeminiChatService_1 = class GeminiChatService {
138
138
  this.logger.error(`Gemini chat.sendMessage failed: Service Unavailable ${model} - ${balancedKey?.name} con clave ${balancedKey?.apiKey}`);
139
139
  await this.keyBalancer.recordFailedRequest(balancedKey.id, error, key_balancer_models_1.ModelType.LLM, model, 40);
140
140
  throw new nest_core_1.AppException({
141
- error_message: `Parece que fue demasiado con la misma clave, invalidar ${balancedKey?.name} clave ${balancedKey?.apiKey} por unos minutos.`,
141
+ error_message: `Problema de Google, No puede procesar la solicitud, invalidar ${balancedKey?.name} clave ${balancedKey?.apiKey} por unos segundos.`,
142
142
  explanation: error.message,
143
143
  });
144
144
  }
@@ -37,27 +37,32 @@ let ImageVertexService = ImageVertexService_1 = class ImageVertexService {
37
37
  async _getGenAiClient(model, tierType = null) {
38
38
  let apiKey;
39
39
  let keyId;
40
+ let availableKey = null;
40
41
  if (this.gemini_key) {
41
42
  apiKey = this.gemini_key;
42
43
  keyId = 'local';
43
44
  }
44
45
  else {
45
- const key = await this.keyBalancerApiService.getImageKey(model, tierType || 'tier 1');
46
- if (key?.error === 'RateLimited') {
46
+ availableKey = await this.keyBalancerApiService.getImageKey(model, tierType || 'tier 1');
47
+ if (availableKey === null) {
48
+ this.logger.error('KEY BALANCER IS DOWN: :::: No available API key from key balancer.');
49
+ }
50
+ if (availableKey?.error === 'RateLimited') {
47
51
  this.logger.error('No available API key from key balancer.');
48
52
  throw new Error('RateLimited');
49
53
  }
50
- if (!key || key?.error) {
54
+ if (!availableKey || availableKey?.error) {
51
55
  this.logger.error('No available API key from key balancer.');
52
56
  throw new Error('Not available API key');
53
57
  }
54
- apiKey = key.apiKey;
55
- keyId = key.id;
58
+ apiKey = availableKey.apiKey;
59
+ keyId = availableKey.id;
56
60
  }
57
61
  return {
58
62
  client: new genai_1.GoogleGenAI({ apiKey }),
59
63
  keyId,
60
64
  model,
65
+ availableKey,
61
66
  };
62
67
  }
63
68
  async startImageGeneration(generateImageDto) {
@@ -150,8 +155,23 @@ let ImageVertexService = ImageVertexService_1 = class ImageVertexService {
150
155
  if (error instanceof nest_core_1.AppException) {
151
156
  throw error;
152
157
  }
153
- this.logger.error(`startImageGenerationAdapter() Model: ${genAiClient.model} 🔑 KeyId: ${genAiClient.keyId} Error:`, error.message || error);
154
- throw error + ' 📝 ' + genAiClient.model + '🔑' + genAiClient.keyId;
158
+ if (error.name === 'ApiError') {
159
+ console.log(error);
160
+ error = JSON.parse(error.message);
161
+ console.log(error.error);
162
+ throw new nest_core_1.AppException({
163
+ error_message: `Error ${error?.error?.code} De Google ${error?.error?.message} con Model: ${genAiClient.model} 🔑 KeyId: ${genAiClient.availableKey.apiKey} Alias: ${genAiClient.availableKey.name} `,
164
+ explanation: JSON.stringify(error?.error?.details),
165
+ });
166
+ }
167
+ if (error.status === 400) {
168
+ throw new nest_core_1.AppException({
169
+ error_message: `Error 400, la llave probablemente no tiene permisos o saldo. Model: ${genAiClient.model} 🔑 KeyId: ${genAiClient.availableKey.apiKey} ${genAiClient.availableKey.name}`,
170
+ explanation: error.message,
171
+ });
172
+ }
173
+ this.logger.error(`startImageGenerationAdapter() Model: ${genAiClient.model} 🔑 KeyId: ${genAiClient.availableKey.apiKey} ${genAiClient.availableKey.name} Error:`, error.message || error);
174
+ throw error + ' 📝 ' + genAiClient.model + '🔑' + genAiClient.availableKey.apiKey + ' ' + genAiClient.availableKey.name;
155
175
  }
156
176
  }
157
177
  };