@runnerpro/backend 1.17.3 → 1.17.5
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.
|
@@ -10,9 +10,9 @@ 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
|
|
14
|
-
const
|
|
15
|
-
const
|
|
13
|
+
const index_1 = require("../prompt/index");
|
|
14
|
+
const index_2 = require("../db/index");
|
|
15
|
+
const index_3 = require("../err/index");
|
|
16
16
|
// ✅ PROMPTS PARA PROCESAMIENTO DE ARCHIVOS MULTIMEDIA
|
|
17
17
|
const AUDIO_TRANSCRIPTION_PROMPT = `Transcribe el audio de forma literal y completa en español.
|
|
18
18
|
Si el audio está en otro idioma, tradúcelo al español.
|
|
@@ -41,51 +41,15 @@ Si la imagen no está relacionada con deporte/fitness, descríbela brevemente.`;
|
|
|
41
41
|
* ```
|
|
42
42
|
*/
|
|
43
43
|
const transcribeAudio = (fileBuffer, mimetype) => __awaiter(void 0, void 0, void 0, function* () {
|
|
44
|
-
var _a, _b, _c, _d;
|
|
45
44
|
try {
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
|
|
45
|
+
const result = yield (0, index_1.sendPrompt)(AUDIO_TRANSCRIPTION_PROMPT, 'PRO', undefined, {
|
|
46
|
+
buffer: fileBuffer,
|
|
47
|
+
mimetype,
|
|
49
48
|
});
|
|
50
|
-
|
|
51
|
-
const generativeModel = vertex_ai.preview.getGenerativeModel({
|
|
52
|
-
model: 'gemini-2.5-flash',
|
|
53
|
-
generationConfig: {
|
|
54
|
-
temperature: 0.1, // Baja temperatura para transcripción precisa
|
|
55
|
-
topP: 0.8,
|
|
56
|
-
topK: 20,
|
|
57
|
-
},
|
|
58
|
-
});
|
|
59
|
-
const base64Audio = fileBuffer.toString('base64');
|
|
60
|
-
// @ts-ignore - Vertex AI types son complejos para contenido multimodal
|
|
61
|
-
const resp = yield generativeModel.generateContent({
|
|
62
|
-
contents: [
|
|
63
|
-
{
|
|
64
|
-
role: 'user',
|
|
65
|
-
parts: [
|
|
66
|
-
{
|
|
67
|
-
inlineData: {
|
|
68
|
-
mimeType: mimetype,
|
|
69
|
-
data: base64Audio,
|
|
70
|
-
},
|
|
71
|
-
},
|
|
72
|
-
{
|
|
73
|
-
text: AUDIO_TRANSCRIPTION_PROMPT,
|
|
74
|
-
},
|
|
75
|
-
],
|
|
76
|
-
},
|
|
77
|
-
],
|
|
78
|
-
});
|
|
79
|
-
const candidate = (_a = resp.response.candidates) === null || _a === void 0 ? void 0 : _a[0];
|
|
80
|
-
if (!candidate)
|
|
81
|
-
return '[Error al procesar audio]';
|
|
82
|
-
const parts = (_b = candidate.content) === null || _b === void 0 ? void 0 : _b.parts;
|
|
83
|
-
if (!parts || parts.length === 0)
|
|
84
|
-
return '[Error al procesar audio]';
|
|
85
|
-
return ((_d = (_c = parts[0]) === null || _c === void 0 ? void 0 : _c.text) === null || _d === void 0 ? void 0 : _d.trim()) || '[Audio vacío]';
|
|
49
|
+
return result || '[Audio vacío]';
|
|
86
50
|
}
|
|
87
51
|
catch (error) {
|
|
88
|
-
(0,
|
|
52
|
+
(0, index_3.err)(null, null, error, null);
|
|
89
53
|
return '[Error al transcribir audio]';
|
|
90
54
|
}
|
|
91
55
|
});
|
|
@@ -105,51 +69,15 @@ exports.transcribeAudio = transcribeAudio;
|
|
|
105
69
|
* ```
|
|
106
70
|
*/
|
|
107
71
|
const describeImage = (fileBuffer, mimetype) => __awaiter(void 0, void 0, void 0, function* () {
|
|
108
|
-
var _e, _f, _g, _h;
|
|
109
72
|
try {
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
});
|
|
114
|
-
// ⭐ Usamos gemini-2.5-flash para balance entre velocidad y precisión
|
|
115
|
-
const generativeModel = vertex_ai.preview.getGenerativeModel({
|
|
116
|
-
model: 'gemini-2.5-flash',
|
|
117
|
-
generationConfig: {
|
|
118
|
-
temperature: 0.3,
|
|
119
|
-
topP: 0.8,
|
|
120
|
-
topK: 20,
|
|
121
|
-
},
|
|
122
|
-
});
|
|
123
|
-
const base64Image = fileBuffer.toString('base64');
|
|
124
|
-
// @ts-ignore - Vertex AI types son complejos para contenido multimodal
|
|
125
|
-
const resp = yield generativeModel.generateContent({
|
|
126
|
-
contents: [
|
|
127
|
-
{
|
|
128
|
-
role: 'user',
|
|
129
|
-
parts: [
|
|
130
|
-
{
|
|
131
|
-
inlineData: {
|
|
132
|
-
mimeType: mimetype,
|
|
133
|
-
data: base64Image,
|
|
134
|
-
},
|
|
135
|
-
},
|
|
136
|
-
{
|
|
137
|
-
text: IMAGE_DESCRIPTION_PROMPT,
|
|
138
|
-
},
|
|
139
|
-
],
|
|
140
|
-
},
|
|
141
|
-
],
|
|
73
|
+
const result = yield (0, index_1.sendPrompt)(IMAGE_DESCRIPTION_PROMPT, 'PRO', undefined, {
|
|
74
|
+
buffer: fileBuffer,
|
|
75
|
+
mimetype,
|
|
142
76
|
});
|
|
143
|
-
|
|
144
|
-
if (!candidate)
|
|
145
|
-
return '[Error al procesar imagen]';
|
|
146
|
-
const parts = (_f = candidate.content) === null || _f === void 0 ? void 0 : _f.parts;
|
|
147
|
-
if (!parts || parts.length === 0)
|
|
148
|
-
return '[Error al procesar imagen]';
|
|
149
|
-
return ((_h = (_g = parts[0]) === null || _g === void 0 ? void 0 : _g.text) === null || _h === void 0 ? void 0 : _h.trim()) || '[Imagen no analizable]';
|
|
77
|
+
return result || '[Imagen no analizable]';
|
|
150
78
|
}
|
|
151
79
|
catch (error) {
|
|
152
|
-
(0,
|
|
80
|
+
(0, index_3.err)(null, null, error, null);
|
|
153
81
|
return '[Error al describir imagen]';
|
|
154
82
|
}
|
|
155
83
|
});
|
|
@@ -183,10 +111,10 @@ const processMediaFile = (idMessage, fileBuffer, mimetype) => __awaiter(void 0,
|
|
|
183
111
|
return;
|
|
184
112
|
}
|
|
185
113
|
// ✅ Guardar el resultado en la base de datos
|
|
186
|
-
yield (0,
|
|
114
|
+
yield (0, index_2.query)('UPDATE [CHAT MESSAGE] SET [FILE TEXT] = ? WHERE [ID] = ?', [fileText, idMessage]);
|
|
187
115
|
}
|
|
188
116
|
catch (error) {
|
|
189
|
-
(0,
|
|
117
|
+
(0, index_3.err)(null, null, error, null);
|
|
190
118
|
}
|
|
191
119
|
});
|
|
192
120
|
exports.processMediaFile = processMediaFile;
|
|
@@ -208,7 +136,7 @@ const reprocessRecentMedia = (bucket) => __awaiter(void 0, void 0, void 0, funct
|
|
|
208
136
|
let errors = 0;
|
|
209
137
|
try {
|
|
210
138
|
// ✅ Buscar mensajes de los últimos 2 días con MIMETYPE de audio/imagen y sin FILE TEXT
|
|
211
|
-
const messages = yield (0,
|
|
139
|
+
const messages = yield (0, index_2.query)(`SELECT "ID", "MIMETYPE" FROM "CHAT MESSAGE"
|
|
212
140
|
WHERE "DATE" >= NOW() - INTERVAL '2 days'
|
|
213
141
|
AND "FILE TEXT" IS NULL
|
|
214
142
|
AND ("MIMETYPE" LIKE 'audio/%' OR "MIMETYPE" LIKE 'image/%')
|
|
@@ -235,7 +163,7 @@ const reprocessRecentMedia = (bucket) => __awaiter(void 0, void 0, void 0, funct
|
|
|
235
163
|
fileText = yield describeImage(fileBuffer, message.mimetype);
|
|
236
164
|
}
|
|
237
165
|
// ⭐ Guardar en BD
|
|
238
|
-
yield (0,
|
|
166
|
+
yield (0, index_2.query)('UPDATE [CHAT MESSAGE] SET [FILE TEXT] = ? WHERE [ID] = ?', [fileText, message.id]);
|
|
239
167
|
// eslint-disable-next-line no-console
|
|
240
168
|
console.log(`[reprocessRecentMedia] ✅ Procesado mensaje ${message.id}: ${fileText.substring(0, 50)}...`);
|
|
241
169
|
processed++;
|
|
@@ -248,7 +176,7 @@ const reprocessRecentMedia = (bucket) => __awaiter(void 0, void 0, void 0, funct
|
|
|
248
176
|
}
|
|
249
177
|
}
|
|
250
178
|
catch (error) {
|
|
251
|
-
(0,
|
|
179
|
+
(0, index_3.err)(null, null, error, null);
|
|
252
180
|
}
|
|
253
181
|
return { processed, errors };
|
|
254
182
|
});
|
package/lib/cjs/prompt/index.js
CHANGED
|
@@ -11,36 +11,98 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
12
|
exports.sendPrompt = void 0;
|
|
13
13
|
const vertexai_1 = require("@google-cloud/vertexai");
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
/**
|
|
15
|
+
* Envía un prompt a Gemini (Vertex AI) y retorna la respuesta
|
|
16
|
+
* Soporta contenido multimodal (texto + audio/imagen)
|
|
17
|
+
*
|
|
18
|
+
* @param prompt - Texto del prompt a enviar
|
|
19
|
+
* @param model - Modelo a usar: 'LITE', 'FLASH' (default), 'PRO'
|
|
20
|
+
* @param systemPrompt - Instrucciones de sistema opcionales
|
|
21
|
+
* @param media - Contenido multimedia opcional (audio o imagen)
|
|
22
|
+
* @returns Promise<string> - Respuesta generada por la IA
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```typescript
|
|
26
|
+
* // Solo texto
|
|
27
|
+
* const response = await sendPrompt('Traduce esto al inglés: Hola mundo');
|
|
28
|
+
*
|
|
29
|
+
* // Con imagen
|
|
30
|
+
* const description = await sendPrompt(
|
|
31
|
+
* 'Describe esta imagen',
|
|
32
|
+
* 'FLASH',
|
|
33
|
+
* undefined,
|
|
34
|
+
* { buffer: imageBuffer, mimetype: 'image/jpeg' }
|
|
35
|
+
* );
|
|
36
|
+
*
|
|
37
|
+
* // Con audio
|
|
38
|
+
* const transcription = await sendPrompt(
|
|
39
|
+
* 'Transcribe este audio en español',
|
|
40
|
+
* 'FLASH',
|
|
41
|
+
* undefined,
|
|
42
|
+
* { buffer: audioBuffer, mimetype: 'audio/mpeg' }
|
|
43
|
+
* );
|
|
44
|
+
* ```
|
|
45
|
+
*/
|
|
46
|
+
function sendPrompt(prompt, model = 'FLASH', systemPrompt = undefined, media = undefined) {
|
|
47
|
+
var _a, _b, _c, _d;
|
|
16
48
|
return __awaiter(this, void 0, void 0, function* () {
|
|
17
49
|
// Inicializa el cliente de Vertex AI
|
|
18
50
|
const vertex_ai = new vertexai_1.VertexAI({
|
|
19
|
-
project: process.env.PROJECT_ID,
|
|
20
|
-
location: 'europe-west8',
|
|
51
|
+
project: process.env.PROJECT_ID,
|
|
52
|
+
location: 'europe-west8',
|
|
21
53
|
});
|
|
22
|
-
// Selecciona el modelo de Gemini
|
|
54
|
+
// Selecciona el modelo de Gemini
|
|
23
55
|
let modelGemini;
|
|
24
56
|
if (model === 'LITE')
|
|
25
57
|
modelGemini = 'gemini-2.5-flash-lite';
|
|
26
58
|
else if (model === 'PRO')
|
|
27
|
-
modelGemini = 'gemini-
|
|
59
|
+
modelGemini = 'gemini-3-pro-preview';
|
|
28
60
|
else
|
|
29
61
|
modelGemini = 'gemini-2.5-flash';
|
|
30
|
-
// Configura el modelo generativo
|
|
62
|
+
// Configura el modelo generativo
|
|
31
63
|
const generativeModel = vertex_ai.preview.getGenerativeModel({
|
|
32
64
|
model: modelGemini,
|
|
33
65
|
generationConfig: {
|
|
34
|
-
temperature: 0.3,
|
|
66
|
+
temperature: media ? 0.1 : 0.3, // Menor temperatura para transcripciones
|
|
35
67
|
topP: 0.8,
|
|
36
68
|
topK: 20,
|
|
37
69
|
},
|
|
38
|
-
systemInstruction: systemPrompt
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
70
|
+
systemInstruction: systemPrompt
|
|
71
|
+
? {
|
|
72
|
+
role: 'system',
|
|
73
|
+
parts: [{ text: systemPrompt }],
|
|
74
|
+
}
|
|
75
|
+
: undefined,
|
|
42
76
|
});
|
|
43
|
-
|
|
77
|
+
// ✅ Construir contenido según si hay media o no
|
|
78
|
+
let content;
|
|
79
|
+
if (media) {
|
|
80
|
+
// Contenido multimodal (audio/imagen + texto)
|
|
81
|
+
content = {
|
|
82
|
+
contents: [
|
|
83
|
+
{
|
|
84
|
+
role: 'user',
|
|
85
|
+
parts: [
|
|
86
|
+
{
|
|
87
|
+
inlineData: {
|
|
88
|
+
mimeType: media.mimetype,
|
|
89
|
+
data: media.buffer.toString('base64'),
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
text: prompt,
|
|
94
|
+
},
|
|
95
|
+
],
|
|
96
|
+
},
|
|
97
|
+
],
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
// Solo texto
|
|
102
|
+
content = prompt;
|
|
103
|
+
}
|
|
104
|
+
// @ts-ignore - Vertex AI types son complejos para contenido multimodal
|
|
105
|
+
const resp = yield generativeModel.generateContent(content);
|
|
44
106
|
// Early return si no hay candidates
|
|
45
107
|
const candidate = (_a = resp.response.candidates) === null || _a === void 0 ? void 0 : _a[0];
|
|
46
108
|
if (!candidate)
|
|
@@ -50,7 +112,7 @@ function sendPrompt(prompt, model = 'FLASH', systemPrompt = undefined) {
|
|
|
50
112
|
if (!parts || parts.length === 0)
|
|
51
113
|
return '';
|
|
52
114
|
// Return del texto si existe
|
|
53
|
-
return ((_c = parts[0]) === null || _c === void 0 ? void 0 : _c.text) || '';
|
|
115
|
+
return ((_d = (_c = parts[0]) === null || _c === void 0 ? void 0 : _c.text) === null || _d === void 0 ? void 0 : _d.trim()) || '';
|
|
54
116
|
});
|
|
55
117
|
}
|
|
56
118
|
exports.sendPrompt = sendPrompt;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/mediaProcessing/index.ts"],"names":[],"mappings":";AAqBA;;;;;;;;;;;;GAYG;AACH,QAAA,MAAM,eAAe,eAAsB,MAAM,YAAY,MAAM,KAAG,QAAQ,MAAM,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/mediaProcessing/index.ts"],"names":[],"mappings":";AAqBA;;;;;;;;;;;;GAYG;AACH,QAAA,MAAM,eAAe,eAAsB,MAAM,YAAY,MAAM,KAAG,QAAQ,MAAM,CAWnF,CAAC;AAEF;;;;;;;;;;;;;GAaG;AACH,QAAA,MAAM,aAAa,eAAsB,MAAM,YAAY,MAAM,KAAG,QAAQ,MAAM,CAWjF,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,3 +1,45 @@
|
|
|
1
|
-
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
/**
|
|
3
|
+
* Contenido multimedia opcional para enviar junto con el prompt
|
|
4
|
+
*/
|
|
5
|
+
interface MediaContent {
|
|
6
|
+
/** Buffer del archivo (audio o imagen) */
|
|
7
|
+
buffer: Buffer;
|
|
8
|
+
/** Tipo MIME del archivo (ej: 'audio/mpeg', 'image/jpeg') */
|
|
9
|
+
mimetype: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Envía un prompt a Gemini (Vertex AI) y retorna la respuesta
|
|
13
|
+
* Soporta contenido multimodal (texto + audio/imagen)
|
|
14
|
+
*
|
|
15
|
+
* @param prompt - Texto del prompt a enviar
|
|
16
|
+
* @param model - Modelo a usar: 'LITE', 'FLASH' (default), 'PRO'
|
|
17
|
+
* @param systemPrompt - Instrucciones de sistema opcionales
|
|
18
|
+
* @param media - Contenido multimedia opcional (audio o imagen)
|
|
19
|
+
* @returns Promise<string> - Respuesta generada por la IA
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```typescript
|
|
23
|
+
* // Solo texto
|
|
24
|
+
* const response = await sendPrompt('Traduce esto al inglés: Hola mundo');
|
|
25
|
+
*
|
|
26
|
+
* // Con imagen
|
|
27
|
+
* const description = await sendPrompt(
|
|
28
|
+
* 'Describe esta imagen',
|
|
29
|
+
* 'FLASH',
|
|
30
|
+
* undefined,
|
|
31
|
+
* { buffer: imageBuffer, mimetype: 'image/jpeg' }
|
|
32
|
+
* );
|
|
33
|
+
*
|
|
34
|
+
* // Con audio
|
|
35
|
+
* const transcription = await sendPrompt(
|
|
36
|
+
* 'Transcribe este audio en español',
|
|
37
|
+
* 'FLASH',
|
|
38
|
+
* undefined,
|
|
39
|
+
* { buffer: audioBuffer, mimetype: 'audio/mpeg' }
|
|
40
|
+
* );
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
declare function sendPrompt(prompt: string, model?: string, systemPrompt?: string | undefined, media?: MediaContent | undefined): Promise<string>;
|
|
2
44
|
export { sendPrompt };
|
|
3
45
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/prompt/index.ts"],"names":[],"mappings":"AAEA,iBAAe,UAAU,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/prompt/index.ts"],"names":[],"mappings":";AAEA;;GAEG;AACH,UAAU,YAAY;IACpB,0CAA0C;IAC1C,MAAM,EAAE,MAAM,CAAC;IACf,6DAA6D;IAC7D,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,iBAAe,UAAU,CACvB,MAAM,EAAE,MAAM,EACd,KAAK,GAAE,MAAgB,EACvB,YAAY,GAAE,MAAM,GAAG,SAAqB,EAC5C,KAAK,GAAE,YAAY,GAAG,SAAqB,mBAsE5C;AAED,OAAO,EAAE,UAAU,EAAE,CAAC"}
|