- gemini_model is now a saved config field (default: gemini-2.0-flash) - Config panel shows a text input to set the model name - probarGemini() reads the configured model and on error calls ListModels to show exactly which model names are available for the account's API key - GeminiIntentService and GeminiVisionService build the URL from config at runtime Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
231 lines
15 KiB
PHP
Executable File
231 lines
15 KiB
PHP
Executable File
<div class="max-w-2xl mx-auto space-y-6">
|
||
|
||
<div>
|
||
<h2 class="text-xl font-bold text-gray-800">Configuración del Chat</h2>
|
||
<p class="text-gray-500 text-sm">Parámetros generales del chatbot y del canal Telegram.</p>
|
||
</div>
|
||
|
||
{{-- Formulario --}}
|
||
<div class="bg-white rounded-2xl shadow border border-gray-100 p-6 space-y-5">
|
||
|
||
{{-- Token Telegram --}}
|
||
<div>
|
||
<label class="block text-sm font-semibold text-gray-700 mb-1">Token del bot de Telegram</label>
|
||
<p class="text-xs text-gray-400 mb-2">Obtenlo hablando con @BotFather en Telegram. Deja vacío si no usas Telegram.</p>
|
||
<input wire:model.defer="telegram_token" type="text"
|
||
placeholder="1234567890:ABCDefGhijKlmNoPqrStUVwxYZ"
|
||
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>
|
||
|
||
{{-- Webhook URL (solo lectura) --}}
|
||
<div>
|
||
<label class="block text-sm font-semibold text-gray-700 mb-1">URL del Webhook Telegram</label>
|
||
<p class="text-xs text-gray-400 mb-2">Esta es la URL que Telegram usará para enviarte los mensajes entrantes.</p>
|
||
<div class="flex gap-2">
|
||
<input type="text" readonly value="{{ url('/chat/webhook/telegram') }}"
|
||
class="flex-1 border border-gray-200 rounded-lg px-3 py-2 text-sm bg-gray-50 text-gray-600 font-mono">
|
||
<button wire:click="registrarWebhook"
|
||
class="flex-shrink-0 px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white text-sm font-semibold rounded-lg transition">
|
||
<span wire:loading.remove wire:target="registrarWebhook">Registrar Webhook</span>
|
||
<span wire:loading wire:target="registrarWebhook">Registrando...</span>
|
||
</button>
|
||
</div>
|
||
@if($webhookResult)
|
||
<p class="mt-2 text-sm {{ str_contains($webhookResult, '✅') ? 'text-emerald-600' : 'text-red-600' }}">
|
||
{{ $webhookResult }}
|
||
</p>
|
||
@endif
|
||
</div>
|
||
|
||
<hr class="border-gray-100">
|
||
|
||
{{-- Mensaje de bienvenida --}}
|
||
<div>
|
||
<label class="block text-sm font-semibold text-gray-700 mb-1">Mensaje de bienvenida <span class="text-red-500">*</span></label>
|
||
<p class="text-xs text-gray-400 mb-2">Primer mensaje cuando el bot no tiene menú configurado o cuando el usuario escribe "hola".</p>
|
||
<textarea wire:model.defer="mensaje_bienvenida" rows="3"
|
||
class="w-full border border-gray-300 rounded-lg px-3 py-2 text-sm focus:ring-2 focus:ring-emerald-500 outline-none resize-none"></textarea>
|
||
@error('mensaje_bienvenida') <span class="text-red-500 text-xs">{{ $message }}</span> @enderror
|
||
</div>
|
||
|
||
{{-- Mensaje de transferencia --}}
|
||
<div>
|
||
<label class="block text-sm font-semibold text-gray-700 mb-1">Mensaje al transferir a agente <span class="text-red-500">*</span></label>
|
||
<p class="text-xs text-gray-400 mb-2">Se envía al usuario cuando elige la opción de hablar con un agente.</p>
|
||
<textarea wire:model.defer="mensaje_transferencia" rows="3"
|
||
class="w-full border border-gray-300 rounded-lg px-3 py-2 text-sm focus:ring-2 focus:ring-emerald-500 outline-none resize-none"></textarea>
|
||
@error('mensaje_transferencia') <span class="text-red-500 text-xs">{{ $message }}</span> @enderror
|
||
</div>
|
||
|
||
<hr class="border-gray-100">
|
||
|
||
{{-- Gemini IA --}}
|
||
<div>
|
||
<h3 class="text-sm font-bold text-gray-700 mb-3">Inteligencia Artificial (Gemini)</h3>
|
||
<div class="space-y-4">
|
||
<div>
|
||
<label class="block text-sm font-semibold text-gray-700 mb-1">API Key de Gemini</label>
|
||
<p class="text-xs text-gray-400 mb-2">Obtén tu clave en aistudio.google.com</p>
|
||
<input wire:model.defer="gemini_api_key" type="password" placeholder="AIza..."
|
||
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">Modelo</label>
|
||
<p class="text-xs text-gray-400 mb-2">Usa "Probar conexión" para ver los modelos disponibles en tu cuenta si no sabes cuál poner.</p>
|
||
<input wire:model.defer="gemini_model" type="text" placeholder="gemini-2.0-flash"
|
||
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="gemini_habilitado" type="checkbox" value="1"
|
||
id="gemini_hab" class="w-4 h-4 accent-emerald-600">
|
||
<label for="gemini_hab" class="text-sm text-gray-700">
|
||
Habilitar IA para interpretar texto libre del usuario
|
||
</label>
|
||
</div>
|
||
<div>
|
||
<label class="block text-sm font-semibold text-gray-700 mb-1">Contexto extra para la IA (opcional)</label>
|
||
<textarea wire:model.defer="gemini_prompt_extra" rows="2"
|
||
class="w-full border border-gray-300 rounded-lg px-3 py-2 text-sm focus:ring-2 focus:ring-emerald-500 outline-none resize-none"
|
||
placeholder="Ej: Solo vendemos streaming. No ofrecemos juegos."></textarea>
|
||
</div>
|
||
<div>
|
||
<button wire:click="probarGemini"
|
||
class="flex items-center gap-2 px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white text-sm font-semibold rounded-lg transition">
|
||
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor">
|
||
<path stroke-linecap="round" stroke-linejoin="round" d="M9.813 15.904 9 18.75l-.813-2.846a4.5 4.5 0 0 0-3.09-3.09L2.25 12l2.846-.813a4.5 4.5 0 0 0 3.09-3.09L9 5.25l.813 2.846a4.5 4.5 0 0 0 3.09 3.09L15.75 12l-2.846.813a4.5 4.5 0 0 0-3.09 3.09Z" />
|
||
</svg>
|
||
<span wire:loading.remove wire:target="probarGemini">Probar conexión Gemini</span>
|
||
<span wire:loading wire:target="probarGemini">Probando...</span>
|
||
</button>
|
||
@if($geminiResult)
|
||
<p class="mt-2 text-sm {{ str_contains($geminiResult, '✅') ? 'text-emerald-600' : 'text-red-600' }}">
|
||
{{ $geminiResult }}
|
||
</p>
|
||
@endif
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<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>
|
||
<button wire:click="resetearWhisper"
|
||
class="flex items-center gap-2 px-4 py-2 bg-purple-600 hover:bg-purple-700 text-white text-sm font-semibold rounded-lg transition">
|
||
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor">
|
||
<path stroke-linecap="round" stroke-linejoin="round" d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0 3.181 3.183a8.25 8.25 0 0 0 13.803-3.7M4.031 9.865a8.25 8.25 0 0 1 13.803-3.7l3.181 3.182m0-4.991v4.99" />
|
||
</svg>
|
||
<span wire:loading.remove wire:target="resetearWhisper">Probar conexión Whisper</span>
|
||
<span wire:loading wire:target="resetearWhisper">Probando...</span>
|
||
</button>
|
||
@if($whisperResult)
|
||
<p class="mt-2 text-sm {{ str_contains($whisperResult, '✅') ? 'text-emerald-600' : 'text-red-600' }}">
|
||
{{ $whisperResult }}
|
||
</p>
|
||
@endif
|
||
</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>
|
||
<p class="text-xs text-gray-400 mb-4">La IA lee el comprobante y cruza los datos con los correos del IMAP configurado en la seccion WhatsApp.</p>
|
||
<div class="space-y-4">
|
||
<div class="flex items-center gap-3">
|
||
<input wire:model.defer="validacion_foto_habilitada" type="checkbox" value="1"
|
||
id="val_foto" class="w-4 h-4 accent-emerald-600">
|
||
<label for="val_foto" class="text-sm text-gray-700">Habilitar validacion de comprobante por foto</label>
|
||
</div>
|
||
<div>
|
||
<label class="block text-sm font-semibold text-gray-700 mb-1">Accion al confirmar pago</label>
|
||
<select wire:model.defer="validacion_accion_auto"
|
||
class="w-full border border-gray-300 rounded-lg px-3 py-2 text-sm focus:ring-2 focus:ring-emerald-500 outline-none">
|
||
<option value="solo_notificar">Solo notificar al asesor (manual)</option>
|
||
<option value="recargar_saldo">Acreditar saldo automaticamente</option>
|
||
</select>
|
||
</div>
|
||
<div>
|
||
<label class="block text-sm font-semibold text-gray-700 mb-1">Tolerancia de monto ($)</label>
|
||
<input wire:model.defer="validacion_monto_tolerancia" type="number" min="0"
|
||
class="w-32 border border-gray-300 rounded-lg px-3 py-2 text-sm focus:ring-2 focus:ring-emerald-500 outline-none">
|
||
<p class="text-xs text-gray-400 mt-1">Diferencia maxima permitida entre comprobante y correo. Recomendado: 0.</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<hr class="border-gray-100">
|
||
|
||
{{-- Bre-B --}}
|
||
<div>
|
||
<h3 class="text-sm font-bold text-gray-700 mb-1">Pago por Bre-B</h3>
|
||
<p class="text-xs text-gray-400 mb-4">Datos que se muestran al cliente cuando elige pagar por Bre-B. Deja la llave vacía para deshabilitar esta opción.</p>
|
||
<div class="space-y-4">
|
||
<div>
|
||
<label class="block text-sm font-semibold text-gray-700 mb-1">Banco / Billetera</label>
|
||
<input wire:model.defer="recarga_banco_nombre" type="text" placeholder="Ej: Bancolombia, Nequi, Daviplata"
|
||
class="w-full border border-gray-300 rounded-lg px-3 py-2 text-sm focus:ring-2 focus:ring-emerald-500 outline-none">
|
||
@error('recarga_banco_nombre') <span class="text-red-500 text-xs">{{ $message }}</span> @enderror
|
||
</div>
|
||
<div>
|
||
<label class="block text-sm font-semibold text-gray-700 mb-1">Llave Bre-B</label>
|
||
<input wire:model.defer="recarga_banco_llave" type="text" placeholder="Ej: 3001234567"
|
||
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">
|
||
@error('recarga_banco_llave') <span class="text-red-500 text-xs">{{ $message }}</span> @enderror
|
||
</div>
|
||
<div>
|
||
<label class="block text-sm font-semibold text-gray-700 mb-1">Titular de la cuenta</label>
|
||
<input wire:model.defer="recarga_banco_titular" type="text" placeholder="Nombre del titular"
|
||
class="w-full border border-gray-300 rounded-lg px-3 py-2 text-sm focus:ring-2 focus:ring-emerald-500 outline-none">
|
||
@error('recarga_banco_titular') <span class="text-red-500 text-xs">{{ $message }}</span> @enderror
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{{-- Guardar --}}
|
||
<div class="flex justify-end pt-2">
|
||
<button wire:click="guardar"
|
||
class="flex items-center gap-2 bg-emerald-600 hover:bg-emerald-700 text-white text-sm font-semibold px-6 py-2.5 rounded-lg transition shadow">
|
||
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor">
|
||
<path stroke-linecap="round" stroke-linejoin="round" d="M9 12.75 11.25 15 15 9.75M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z" />
|
||
</svg>
|
||
<span wire:loading.remove wire:target="guardar">Guardar configuracion</span>
|
||
<span wire:loading wire:target="guardar">Guardando...</span>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
{{-- Info de canales --}}
|
||
<div class="bg-blue-50 border border-blue-100 rounded-2xl p-5">
|
||
<h3 class="font-semibold text-blue-800 text-sm mb-2">ℹ️ Canales disponibles</h3>
|
||
<ul class="text-blue-700 text-sm space-y-1">
|
||
<li><strong>Web:</strong> Los visitantes acceden en <code class="bg-blue-100 px-1 rounded">{{ url('/chat') }}</code></li>
|
||
<li><strong>Telegram:</strong> Configurar el token y registrar el webhook para recibir mensajes de Telegram.</li>
|
||
</ul>
|
||
</div>
|
||
|
||
</div>
|