CognxSafeTrack commited on
Commit ·
9dfe8d2
1
Parent(s): 9cc0a90
fix(audit): add /v1/ai/transcribe endpoint + fix cross-package storage require in worker
Browse files- apps/api/src/routes/ai.ts +26 -0
- apps/whatsapp-worker/src/index.ts +10 -8
apps/api/src/routes/ai.ts
CHANGED
|
@@ -75,4 +75,30 @@ export async function aiRoutes(fastify: FastifyInstance) {
|
|
| 75 |
|
| 76 |
return { success: true, url: downloadUrl };
|
| 77 |
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 78 |
}
|
|
|
|
| 75 |
|
| 76 |
return { success: true, url: downloadUrl };
|
| 77 |
});
|
| 78 |
+
|
| 79 |
+
// 5. Transcribe audio (called by Railway worker after downloading media from Meta)
|
| 80 |
+
// Accepts: { audioBase64: string, filename: string }
|
| 81 |
+
// Returns: { text: string }
|
| 82 |
+
fastify.post('/transcribe', async (request, reply) => {
|
| 83 |
+
const bodySchema = z.object({
|
| 84 |
+
audioBase64: z.string(),
|
| 85 |
+
filename: z.string().default('message.ogg')
|
| 86 |
+
});
|
| 87 |
+
|
| 88 |
+
const { audioBase64, filename } = bodySchema.parse(request.body);
|
| 89 |
+
|
| 90 |
+
console.log(`[AI] Transcribing audio: ${filename}`);
|
| 91 |
+
|
| 92 |
+
const buffer = Buffer.from(audioBase64, 'base64');
|
| 93 |
+
|
| 94 |
+
try {
|
| 95 |
+
const text = await aiService.transcribeAudio(buffer, filename);
|
| 96 |
+
return { success: true, text };
|
| 97 |
+
} catch (err: any) {
|
| 98 |
+
if (err?.name === 'QuotaExceededError') {
|
| 99 |
+
return reply.code(429).send({ error: 'quota_exceeded', retryAfterMs: err.retryAfterMs });
|
| 100 |
+
}
|
| 101 |
+
throw err;
|
| 102 |
+
}
|
| 103 |
+
});
|
| 104 |
}
|
apps/whatsapp-worker/src/index.ts
CHANGED
|
@@ -109,14 +109,16 @@ const worker = new Worker('whatsapp-queue', async (job: Job) => {
|
|
| 109 |
try {
|
| 110 |
const { buffer } = await downloadMedia(mediaId, accessToken);
|
| 111 |
|
| 112 |
-
//
|
| 113 |
-
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
|
| 119 |
-
|
|
|
|
|
|
|
| 120 |
|
| 121 |
// Transcribe with Whisper via API
|
| 122 |
const API_URL = process.env.API_URL || 'http://localhost:3001';
|
|
|
|
| 109 |
try {
|
| 110 |
const { buffer } = await downloadMedia(mediaId, accessToken);
|
| 111 |
|
| 112 |
+
// Best-effort: store audio on R2 via the API (non-blocking, silent if fails)
|
| 113 |
+
// No direct require — that path doesn't exist in the worker Docker image
|
| 114 |
+
const API_URL_STORE = process.env.API_URL || 'http://localhost:3001';
|
| 115 |
+
const apiKeyStore = process.env.ADMIN_API_KEY || '';
|
| 116 |
+
fetch(`${API_URL_STORE}/v1/ai/store-audio`, {
|
| 117 |
+
method: 'POST',
|
| 118 |
+
headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${apiKeyStore}` },
|
| 119 |
+
body: JSON.stringify({ audioBase64: buffer.toString('base64'), mimeType, phone })
|
| 120 |
+
}).catch(() => { /* silent — audio archiving is optional */ });
|
| 121 |
+
|
| 122 |
|
| 123 |
// Transcribe with Whisper via API
|
| 124 |
const API_URL = process.env.API_URL || 'http://localhost:3001';
|