@runnerpro/backend 1.17.7 → 1.17.11

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.
@@ -158,6 +158,8 @@ const deleteConversationMessage = (req, res, { isClient }) => __awaiter(void 0,
158
158
  const editConversationMessage = (req, res, { isClient }) => __awaiter(void 0, void 0, void 0, function* () {
159
159
  const { id } = req.params;
160
160
  const { text, idWorkout: idWorkoutBody } = req.body;
161
+ // ⭐ Verificar si el campo idWorkout fue enviado explícitamente en el body
162
+ const hasIdWorkoutInBody = 'idWorkout' in req.body;
161
163
  if (!(yield canEditOrDeleteMessage({ idMessage: id, isClient, userid: req.session.userid })))
162
164
  return res.send({ status: 'ok' });
163
165
  const [message] = yield (0, index_1.query)('SELECT [ID CLIENTE], [ID WORKOUT] FROM [CHAT MESSAGE] WHERE [ID] = ?', [id]);
@@ -177,8 +179,8 @@ const editConversationMessage = (req, res, { isClient }) => __awaiter(void 0, vo
177
179
  }
178
180
  // Si el workout no existe o no pertenece al cliente, se mantiene el idWorkout actual (o null)
179
181
  }
180
- else if (idWorkoutBody === null && !isClient) {
181
- // Permitir quitar el workout si se envía explícitamente null
182
+ else if (hasIdWorkoutInBody && !idWorkoutBody && !isClient) {
183
+ // Permitir quitar el workout si se envía explícitamente el campo con valor falsy (null, undefined, '', 0)
182
184
  idWorkout = null;
183
185
  }
184
186
  yield (0, index_1.query)('UPDATE [CHAT MESSAGE] SET [TEXT] = ?, [TEXT PREFERRED LANGUAGE] = ?, [ID WORKOUT] = ?, [EDITADO] = TRUE WHERE [ID] = ?', [
@@ -490,7 +492,7 @@ const sendFile = (req, res, { sendNotification, firebaseMessaging, isClient, buc
490
492
  });
491
493
  yield updateSenderView({ userid, idCliente, idMessage: idFile });
492
494
  }
493
- // Procesar archivo multimedia en background (transcripción de audio / descripción de imagen)
495
+ // Procesar archivo multimedia en background (transcripción de audio / descripción de imagen)
494
496
  if (req.file.mimetype.startsWith('audio/') || req.file.mimetype.startsWith('image/')) {
495
497
  (0, mediaProcessing_1.processMediaFile)(idFile, fileData, req.file.mimetype);
496
498
  }
@@ -10,24 +10,37 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
12
  exports.reprocessRecentMedia = exports.processMediaFile = exports.describeImage = exports.transcribeAudio = void 0;
13
+ const speech_1 = require("@google-cloud/speech");
13
14
  const index_1 = require("../prompt/index");
14
15
  const index_2 = require("../db/index");
15
16
  const index_3 = require("../err/index");
16
- // ✅ PROMPTS PARA PROCESAMIENTO DE ARCHIVOS MULTIMEDIA
17
- const AUDIO_TRANSCRIPTION_PROMPT = `Transcribe el audio de forma literal y completa en español.
18
- Si el audio está en otro idioma, tradúcelo al español.
19
- Devuelve ÚNICAMENTE la transcripción, sin comentarios adicionales.
20
- Si no puedes entender el audio o está vacío, responde: "[Audio no reconocible]"`;
17
+ // ✅ PROMPT PARA DESCRIPCIÓN DE IMÁGENES (Gemini)
21
18
  const IMAGE_DESCRIPTION_PROMPT = `Describe esta imagen de forma directa y completa en un solo párrafo continuo, sin introducciones ni listas.
22
19
  Si la imagen es de contenido deportivo (captura de Garmin/Strava/reloj deportivo, foto de lesión/dolor, gráfica de entrenamiento, selfie deportivo, foto de equipamiento, datos de entrenamiento, etc.), incluye el tipo de imagen, los datos relevantes si es una captura (ritmo, distancia, frecuencia cardíaca, zonas, etc.), y cualquier contexto útil para entender el estado del atleta o su entrenamiento.
23
20
  Si la imagen NO es deportiva, describe su contenido de forma general pero útil para entender el contexto de la conversación.
24
21
  Devuelve ÚNICAMENTE la descripción en español, sin comentarios adicionales ni formato de lista. Máximo 300 palabras.`;
22
+ // ✅ Mapeo de MIME types a encoding de Speech-to-Text
23
+ const AUDIO_ENCODING_MAP = {
24
+ 'audio/mpeg': 'MP3',
25
+ 'audio/mp3': 'MP3',
26
+ 'audio/wav': 'LINEAR16',
27
+ 'audio/wave': 'LINEAR16',
28
+ 'audio/x-wav': 'LINEAR16',
29
+ 'audio/ogg': 'OGG_OPUS',
30
+ 'audio/webm': 'WEBM_OPUS',
31
+ 'audio/flac': 'FLAC',
32
+ 'audio/x-flac': 'FLAC',
33
+ 'audio/mp4': 'MP3',
34
+ 'audio/m4a': 'MP3',
35
+ 'audio/aac': 'MP3',
36
+ };
25
37
  /**
26
- * Transcribe un archivo de audio usando Gemini (Vertex AI)
38
+ * Transcribe un archivo de audio usando Google Cloud Speech-to-Text
39
+ * Servicio especializado en reconocimiento de voz con soporte para 125+ idiomas
27
40
  *
28
41
  * @param fileBuffer - Buffer del archivo de audio
29
42
  * @param mimetype - Tipo MIME del archivo (ej: 'audio/mpeg', 'audio/wav')
30
- * @returns Promise<string> - Transcripción del audio
43
+ * @returns Promise<string> - Transcripción del audio en español
31
44
  *
32
45
  * @example
33
46
  * ```typescript
@@ -36,16 +49,37 @@ Devuelve ÚNICAMENTE la descripción en español, sin comentarios adicionales ni
36
49
  * ```
37
50
  */
38
51
  const transcribeAudio = (fileBuffer, mimetype) => __awaiter(void 0, void 0, void 0, function* () {
52
+ var _a;
39
53
  try {
40
- const result = yield (0, index_1.sendPrompt)(AUDIO_TRANSCRIPTION_PROMPT, 'PRO', undefined, {
41
- buffer: fileBuffer,
42
- mimetype,
54
+ const speechClient = new speech_1.SpeechClient();
55
+ // Determinar el encoding del audio
56
+ const encoding = AUDIO_ENCODING_MAP[mimetype] || 'MP3';
57
+ // @ts-ignore - Tipos de Speech-to-Text son complejos
58
+ const [response] = yield speechClient.recognize({
59
+ audio: {
60
+ content: fileBuffer.toString('base64'),
61
+ },
62
+ config: {
63
+ // @ts-ignore - El encoding se determina dinámicamente según el mimetype
64
+ encoding: encoding,
65
+ sampleRateHertz: 16000,
66
+ languageCode: 'es-ES', // Español de España
67
+ alternativeLanguageCodes: ['es-MX', 'es-AR', 'en-US'], // Alternativas: México, Argentina, Inglés
68
+ enableAutomaticPunctuation: true, // Puntuación automática
69
+ model: 'latest_long', // Modelo más reciente para audios largos
70
+ useEnhanced: true, // Modelo mejorado
71
+ },
43
72
  });
44
- return result || '[Audio vacío]';
73
+ // Concatenar todas las transcripciones
74
+ const transcription = (_a = response.results) === null || _a === void 0 ? void 0 : _a.map((result) => { var _a, _b; return (_b = (_a = result.alternatives) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.transcript; }).filter(Boolean).join(' ').trim();
75
+ if (!transcription) {
76
+ return '[Audio sin contenido reconocible]';
77
+ }
78
+ return transcription;
45
79
  }
46
80
  catch (error) {
47
81
  // eslint-disable-next-line no-console
48
- console.error('[transcribeAudio] Error:', (error === null || error === void 0 ? void 0 : error.message) || error);
82
+ console.error('[transcribeAudio] Error Speech-to-Text:', (error === null || error === void 0 ? void 0 : error.message) || error);
49
83
  (0, index_3.err)(null, null, error, null);
50
84
  return '[Error al transcribir audio]';
51
85
  }
@@ -83,7 +117,8 @@ const describeImage = (fileBuffer, mimetype) => __awaiter(void 0, void 0, void 0
83
117
  exports.describeImage = describeImage;
84
118
  /**
85
119
  * Procesa un archivo multimedia (audio o imagen) y guarda el resultado en la base de datos
86
- * Esta función se ejecuta de forma asíncrona después de guardar el mensaje
120
+ * - Audio: usa Google Cloud Speech-to-Text
121
+ * - Imagen: usa Gemini (Vertex AI)
87
122
  *
88
123
  * @param idMessage - ID del mensaje en CHAT MESSAGE
89
124
  * @param fileBuffer - Buffer del archivo
@@ -56,7 +56,7 @@ function sendPrompt(prompt, model = 'FLASH', systemPrompt = undefined, media = u
56
56
  if (model === 'LITE')
57
57
  modelGemini = 'gemini-2.5-flash-lite';
58
58
  else if (model === 'PRO')
59
- modelGemini = 'gemini-3-pro-preview'; // Modelo más potente y estable
59
+ modelGemini = 'gemini-2.5-pro'; // Modelo más potente y estable
60
60
  else
61
61
  modelGemini = 'gemini-2.5-flash';
62
62
  // Configura el modelo generativo
@@ -1 +1 @@
1
- {"version":3,"file":"conversation.d.ts","sourceRoot":"","sources":["../../../../../src/chat/api/conversation.ts"],"names":[],"mappings":"AAmBA,QAAA,MAAM,iBAAiB,0BAA2B,GAAG,SA0BpD,CAAC;AA0EF;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,QAAA,MAAM,uBAAuB,qCAAkC,GAAG,iBAqCjE,CAAC;AAmGF;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,QAAA,MAAM,WAAW,0EAAuE,GAAG,kBAkE1F,CAAC;AAEF,QAAA,MAAM,gBAAgB;;;;mBAqBrB,CAAC;AA8RF,OAAO,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,WAAW,EAAE,uBAAuB,EAAE,CAAC"}
1
+ {"version":3,"file":"conversation.d.ts","sourceRoot":"","sources":["../../../../../src/chat/api/conversation.ts"],"names":[],"mappings":"AAmBA,QAAA,MAAM,iBAAiB,0BAA2B,GAAG,SA0BpD,CAAC;AA0EF;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,QAAA,MAAM,uBAAuB,qCAAkC,GAAG,iBAwCjE,CAAC;AAmGF;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,QAAA,MAAM,WAAW,0EAAuE,GAAG,kBAkE1F,CAAC;AAEF,QAAA,MAAM,gBAAgB;;;;mBAqBrB,CAAC;AA8RF,OAAO,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,WAAW,EAAE,uBAAuB,EAAE,CAAC"}
@@ -1,10 +1,11 @@
1
1
  /// <reference types="node" />
2
2
  /**
3
- * Transcribe un archivo de audio usando Gemini (Vertex AI)
3
+ * Transcribe un archivo de audio usando Google Cloud Speech-to-Text
4
+ * Servicio especializado en reconocimiento de voz con soporte para 125+ idiomas
4
5
  *
5
6
  * @param fileBuffer - Buffer del archivo de audio
6
7
  * @param mimetype - Tipo MIME del archivo (ej: 'audio/mpeg', 'audio/wav')
7
- * @returns Promise<string> - Transcripción del audio
8
+ * @returns Promise<string> - Transcripción del audio en español
8
9
  *
9
10
  * @example
10
11
  * ```typescript
@@ -30,7 +31,8 @@ declare const transcribeAudio: (fileBuffer: Buffer, mimetype: string) => Promise
30
31
  declare const describeImage: (fileBuffer: Buffer, mimetype: string) => Promise<string>;
31
32
  /**
32
33
  * Procesa un archivo multimedia (audio o imagen) y guarda el resultado en la base de datos
33
- * Esta función se ejecuta de forma asíncrona después de guardar el mensaje
34
+ * - Audio: usa Google Cloud Speech-to-Text
35
+ * - Imagen: usa Gemini (Vertex AI)
34
36
  *
35
37
  * @param idMessage - ID del mensaje en CHAT MESSAGE
36
38
  * @param fileBuffer - Buffer del archivo
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/mediaProcessing/index.ts"],"names":[],"mappings":";AAgBA;;;;;;;;;;;;GAYG;AACH,QAAA,MAAM,eAAe,eAAsB,MAAM,YAAY,MAAM,KAAG,QAAQ,MAAM,CAanF,CAAC;AAEF;;;;;;;;;;;;;GAaG;AACH,QAAA,MAAM,aAAa,eAAsB,MAAM,YAAY,MAAM,KAAG,QAAQ,MAAM,CAajF,CAAC;AAEF;;;;;;;;;;;;;;GAcG;AACH,QAAA,MAAM,gBAAgB,cAAqB,MAAM,cAAc,MAAM,YAAY,MAAM,KAAG,QAAQ,IAAI,CAkBrG,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,QAAA,MAAM,oBAAoB,WAAkB,GAAG;eAAwB,MAAM;YAAU,MAAM;EAwD5F,CAAC;AAEF,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/mediaProcessing/index.ts"],"names":[],"mappings":";AA2BA;;;;;;;;;;;;;GAaG;AACH,QAAA,MAAM,eAAe,eAAsB,MAAM,YAAY,MAAM,KAAG,QAAQ,MAAM,CA0CnF,CAAC;AAEF;;;;;;;;;;;;;GAaG;AACH,QAAA,MAAM,aAAa,eAAsB,MAAM,YAAY,MAAM,KAAG,QAAQ,MAAM,CAajF,CAAC;AAEF;;;;;;;;;;;;;;;GAeG;AACH,QAAA,MAAM,gBAAgB,cAAqB,MAAM,cAAc,MAAM,YAAY,MAAM,KAAG,QAAQ,IAAI,CAkBrG,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,QAAA,MAAM,oBAAoB,WAAkB,GAAG;eAAwB,MAAM;YAAU,MAAM;EAwD5F,CAAC;AAEF,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@runnerpro/backend",
3
- "version": "1.17.7",
3
+ "version": "1.17.11",
4
4
  "description": "A collection of common backend functions",
5
5
  "exports": {
6
6
  ".": "./lib/cjs/index.js"
@@ -64,6 +64,7 @@
64
64
  "uuidv4": "^6.2.13"
65
65
  },
66
66
  "dependencies": {
67
+ "@google-cloud/speech": "^7.2.1",
67
68
  "@google-cloud/storage": "^7.11.3",
68
69
  "@google-cloud/translate": "^8.3.0",
69
70
  "@google-cloud/vertexai": "^1.4.0",