- OcrExtractionService: registra cada llamada en ai_usage_logs (servicio=ocr, costo=5 COP/foto) y ahora tambien loggea cuando SI funciona, no solo cuando falla (antes era imposible confirmar por log que el OCR corrio). - GeminiVisionService: el log decia siempre "[Gemini Vision]" sin importar si la llamada fue de imagen o de texto post-OCR, haciendo imposible distinguir cual camino se uso realmente. Ahora usa el servicio real. - GeminiVisionService: agrega thinkingConfig.thinkingBudget=0 (igual que GeminiIntentService) para que el modelo no gaste tokens narrando su razonamiento en texto plano antes del JSON final -- se estaba colando ese razonamiento en la respuesta y comiendose el presupuesto de tokens. - GeminiIntentService: resultado ya no queda forzado a 'ok'; ahora filtra bloques 'thought' igual que vision, y guarda tokens/raw en el detalle para poder diagnosticar sin acceso al servidor. - ShowAiLogs: card y filtro de costo OCR en el dashboard de consumo. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
106 lines
3.6 KiB
PHP
Executable File
106 lines
3.6 KiB
PHP
Executable File
<?php
|
|
|
|
namespace App\Http\Livewire\Chat;
|
|
|
|
use App\Models\AiUsageLog;
|
|
use Carbon\Carbon;
|
|
use Livewire\Component;
|
|
use Livewire\WithPagination;
|
|
|
|
class ShowAiLogs extends Component
|
|
{
|
|
use WithPagination;
|
|
|
|
public string $filtroServicio = '';
|
|
public string $filtroCanal = '';
|
|
public string $filtroFechaDesde = '';
|
|
public string $filtroFechaHasta = '';
|
|
public string $filtroMes = '';
|
|
|
|
protected $queryString = [
|
|
'filtroServicio', 'filtroCanal',
|
|
'filtroFechaDesde', 'filtroFechaHasta',
|
|
'filtroMes',
|
|
];
|
|
|
|
public function updatingFiltroServicio(): void { $this->resetPage(); }
|
|
public function updatingFiltroCanal(): void { $this->resetPage(); }
|
|
public function updatingFiltroFechaDesde(): void { $this->resetPage(); }
|
|
public function updatingFiltroFechaHasta(): void { $this->resetPage(); }
|
|
public function updatingFiltroMes(): void { $this->resetPage(); }
|
|
|
|
public function updatedFiltroMes(): void
|
|
{
|
|
if ($this->filtroMes) {
|
|
$this->filtroFechaDesde = '';
|
|
$this->filtroFechaHasta = '';
|
|
}
|
|
}
|
|
|
|
public function updatedFiltroFechaDesde(): void
|
|
{
|
|
if ($this->filtroFechaDesde) $this->filtroMes = '';
|
|
}
|
|
|
|
public function updatedFiltroFechaHasta(): void
|
|
{
|
|
if ($this->filtroFechaHasta) $this->filtroMes = '';
|
|
}
|
|
|
|
private function applyFiltros($query): void
|
|
{
|
|
if ($this->filtroServicio) {
|
|
$query->where('servicio', $this->filtroServicio);
|
|
}
|
|
if ($this->filtroCanal) {
|
|
$query->where('canal', $this->filtroCanal);
|
|
}
|
|
if ($this->filtroMes) {
|
|
$inicio = Carbon::createFromFormat('Y-m', $this->filtroMes)->startOfMonth();
|
|
$fin = $inicio->copy()->endOfMonth();
|
|
$query->whereBetween('created_at', [$inicio, $fin]);
|
|
} else {
|
|
if ($this->filtroFechaDesde) {
|
|
$query->whereDate('created_at', '>=', $this->filtroFechaDesde);
|
|
}
|
|
if ($this->filtroFechaHasta) {
|
|
$query->whereDate('created_at', '<=', $this->filtroFechaHasta);
|
|
}
|
|
}
|
|
}
|
|
|
|
public function render()
|
|
{
|
|
$query = AiUsageLog::query()->latest();
|
|
$this->applyFiltros($query);
|
|
$logs = $query->paginate(30);
|
|
|
|
$resumenQuery = AiUsageLog::query();
|
|
$this->applyFiltros($resumenQuery);
|
|
|
|
$resumen = $resumenQuery->selectRaw("
|
|
COUNT(*) as total_llamadas,
|
|
SUM(CASE WHEN resultado = 'ok' THEN 1 ELSE 0 END) as total_ok,
|
|
SUM(CASE WHEN resultado = 'error' THEN 1 ELSE 0 END) as total_errores,
|
|
SUM(input_tokens) as total_input_tokens,
|
|
SUM(output_tokens) as total_output_tokens,
|
|
SUM(audio_segundos) as total_segundos_whisper,
|
|
SUM(CASE WHEN servicio = 'whisper' THEN costo ELSE 0 END) as costo_whisper,
|
|
SUM(CASE WHEN servicio = 'ocr' THEN 1 ELSE 0 END) as total_fotos_ocr,
|
|
SUM(CASE WHEN servicio = 'ocr' THEN costo ELSE 0 END) as costo_ocr,
|
|
SUM((COALESCE(input_tokens,0) + COALESCE(output_tokens,0)) / 1000000.0 * 20000) as costo_gemini,
|
|
AVG(tiempo_ms) as avg_tiempo_ms
|
|
")->first();
|
|
|
|
$meses = collect(range(0, 12))->map(function ($i) {
|
|
$d = now()->subMonths($i);
|
|
return [
|
|
'value' => $d->format('Y-m'),
|
|
'label' => ucfirst($d->locale('es')->isoFormat('MMMM YYYY')),
|
|
];
|
|
});
|
|
|
|
return view('livewire.chat.show-ai-logs', compact('logs', 'resumen', 'meses'));
|
|
}
|
|
}
|