Add Whisper voice transcription to Telegram bot
- handleVoice() downloads Telegram audio and transcribes via Whisper API - transcribeAudio() calls Whisper with Basic Auth, handles json response - TelegramWebhookController now routes message.voice and message.audio - Whisper URL, token and toggle configurable from admin config panel - Transcribed text echoed back to user then processed as normal text intent Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
ad9d1761d2
commit
f39f3d4045
@@ -29,6 +29,18 @@ class TelegramWebhookController extends Controller
|
||||
return response()->json(['ok' => true]);
|
||||
}
|
||||
|
||||
// Voice / audio messages
|
||||
if (isset($update['message']['voice']) || isset($update['message']['audio'])) {
|
||||
$chatId = (string) $update['message']['chat']['id'];
|
||||
$fileId = $update['message']['voice']['file_id'] ?? $update['message']['audio']['file_id'];
|
||||
$nombre = trim(
|
||||
($update['message']['from']['first_name'] ?? '') . ' ' .
|
||||
($update['message']['from']['last_name'] ?? '')
|
||||
);
|
||||
$bot->handleVoice($chatId, $fileId, $nombre);
|
||||
return response()->json(['ok' => true]);
|
||||
}
|
||||
|
||||
// Text messages
|
||||
if (isset($update['message']['text'])) {
|
||||
$chatId = (string) $update['message']['chat']['id'];
|
||||
|
||||
@@ -27,6 +27,11 @@ class ShowConfiguracionChat extends Component
|
||||
public string $validacion_accion_auto = 'solo_notificar';
|
||||
public string $validacion_monto_tolerancia = '0';
|
||||
|
||||
// ── Whisper STT ───────────────────────────────────────────
|
||||
public string $whisper_url = '';
|
||||
public string $whisper_token = '';
|
||||
public string $whisper_habilitado = '0';
|
||||
|
||||
// ── Bre-B ─────────────────────────────────────────────────
|
||||
public string $recarga_banco_nombre = '';
|
||||
public string $recarga_banco_llave = '';
|
||||
@@ -38,6 +43,7 @@ class ShowConfiguracionChat extends Component
|
||||
'telegram_token', 'mensaje_bienvenida', 'mensaje_transferencia',
|
||||
'gemini_api_key', 'gemini_habilitado', 'gemini_prompt_extra',
|
||||
'validacion_foto_habilitada', 'validacion_accion_auto', 'validacion_monto_tolerancia',
|
||||
'whisper_url', 'whisper_token', 'whisper_habilitado',
|
||||
'recarga_banco_nombre', 'recarga_banco_llave', 'recarga_banco_titular',
|
||||
];
|
||||
|
||||
@@ -68,6 +74,7 @@ class ShowConfiguracionChat extends Component
|
||||
'telegram_token', 'mensaje_bienvenida', 'mensaje_transferencia',
|
||||
'gemini_api_key', 'gemini_habilitado', 'gemini_prompt_extra',
|
||||
'validacion_foto_habilitada', 'validacion_accion_auto', 'validacion_monto_tolerancia',
|
||||
'whisper_url', 'whisper_token', 'whisper_habilitado',
|
||||
'recarga_banco_nombre', 'recarga_banco_llave', 'recarga_banco_titular',
|
||||
];
|
||||
|
||||
|
||||
@@ -196,6 +196,71 @@ class TelegramBotService
|
||||
}
|
||||
}
|
||||
|
||||
public function handleVoice(string $chatId, string $fileId, string $nombre = ''): void
|
||||
{
|
||||
$state = $this->getState($chatId);
|
||||
$this->convId = $state['conv_id'] ?? null;
|
||||
|
||||
if (ChatConfig::get('whisper_habilitado', '0') !== '1') {
|
||||
$this->send($chatId, "🎤 Recibí tu audio, pero la transcripción de voz no está habilitada. Por favor escribe tu mensaje.");
|
||||
return;
|
||||
}
|
||||
|
||||
$this->send($chatId, "🎤 Transcribiendo tu audio...");
|
||||
|
||||
try {
|
||||
$audioData = $this->downloadTelegramFile($fileId);
|
||||
if (! $audioData) {
|
||||
$this->send($chatId, "No pude descargar el audio. Intenta de nuevo o escribe tu mensaje.");
|
||||
return;
|
||||
}
|
||||
|
||||
$texto = $this->transcribeAudio($audioData['content']);
|
||||
if (! $texto) {
|
||||
$this->send($chatId, "No pude entender el audio. Intenta de nuevo o escribe tu mensaje.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Confirmar lo que se escuchó y procesar como texto normal
|
||||
$this->send($chatId, "🎙️ Escuché: _\"{$texto}\"_");
|
||||
$this->handleText($chatId, $texto, $nombre);
|
||||
|
||||
} catch (\Throwable $e) {
|
||||
Log::warning('[TelegramBot] handleVoice error: ' . $e->getMessage());
|
||||
$this->send($chatId, "Error procesando el audio. Escribe tu mensaje.");
|
||||
}
|
||||
}
|
||||
|
||||
private function transcribeAudio(string $audioContent): ?string
|
||||
{
|
||||
$whisperUrl = ChatConfig::get('whisper_url');
|
||||
$whisperToken = ChatConfig::get('whisper_token');
|
||||
|
||||
if (! $whisperUrl) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
$response = Http::timeout(30)
|
||||
->withBasicAuth('whisper', $whisperToken)
|
||||
->attach('audio_file', $audioContent, 'audio.ogg')
|
||||
->post($whisperUrl, ['response_format' => 'json']);
|
||||
|
||||
if (! $response->successful()) {
|
||||
Log::warning('[TelegramBot] Whisper error: ' . $response->body());
|
||||
return null;
|
||||
}
|
||||
|
||||
// Whisper devuelve {"text": "..."} o {"transcription": "..."}
|
||||
$json = $response->json();
|
||||
return trim($json['text'] ?? $json['transcription'] ?? '');
|
||||
|
||||
} catch (\Throwable $e) {
|
||||
Log::warning('[TelegramBot] Whisper exception: ' . $e->getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Auth flow ────────────────────────────────────────────
|
||||
|
||||
private function askEmail(string $chatId): void
|
||||
|
||||
@@ -87,6 +87,33 @@
|
||||
|
||||
<hr class="border-gray-100">
|
||||
|
||||
{{-- Whisper STT --}}
|
||||
<div>
|
||||
<h3 class="text-sm font-bold text-gray-700 mb-3">Transcripción de voz (Whisper)</h3>
|
||||
<p class="text-xs text-gray-400 mb-4">Cuando un usuario envíe un audio en Telegram, se transcribirá con Whisper y el texto pasará al sistema de intención como si lo hubiera escrito.</p>
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
<label class="block text-sm font-semibold text-gray-700 mb-1">URL del servidor Whisper</label>
|
||||
<input wire:model.defer="whisper_url" type="text" placeholder="https://whisper.u-s.app/asr"
|
||||
class="w-full border border-gray-300 rounded-lg px-3 py-2 text-sm focus:ring-2 focus:ring-emerald-500 outline-none font-mono">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-semibold text-gray-700 mb-1">Token (contraseña Basic Auth)</label>
|
||||
<input wire:model.defer="whisper_token" type="password" placeholder="Token del servidor Whisper"
|
||||
class="w-full border border-gray-300 rounded-lg px-3 py-2 text-sm focus:ring-2 focus:ring-emerald-500 outline-none font-mono">
|
||||
</div>
|
||||
<div class="flex items-center gap-3">
|
||||
<input wire:model.defer="whisper_habilitado" type="checkbox" value="1"
|
||||
id="whisper_hab" class="w-4 h-4 accent-emerald-600">
|
||||
<label for="whisper_hab" class="text-sm text-gray-700">
|
||||
Habilitar transcripción de audios en Telegram
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr class="border-gray-100">
|
||||
|
||||
{{-- Validacion de pagos --}}
|
||||
<div>
|
||||
<h3 class="text-sm font-bold text-gray-700 mb-1">Validacion de pagos por foto + correo IMAP</h3>
|
||||
|
||||
Reference in New Issue
Block a user