Rename project from any-llm to laravel-llm
- Remove old any-llm related files (Dockerfile, config.yml, web/, setup-laravel.sh) - Update README.md with new Laravel LLM Gateway documentation - Keep docker-compose.yml with laravel-llm container names - Clean project structure for Laravel-only implementation
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
<x-app-layout>
|
||||
<x-slot name="header">
|
||||
<h2 class="font-semibold text-xl text-gray-800 dark:text-gray-200 leading-tight">
|
||||
{{ __('Dashboard') }} - Any-LLM Gateway
|
||||
<h2 class="font-semibold text-xl text-gray-800 leading-tight">
|
||||
{{ __('Dashboard') }} - LLM Gateway
|
||||
</h2>
|
||||
</x-slot>
|
||||
|
||||
@@ -11,16 +11,16 @@
|
||||
<!-- Stats Cards -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
||||
<!-- Total Users -->
|
||||
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg">
|
||||
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
|
||||
<div class="p-6">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400">Total Users</p>
|
||||
<p class="text-3xl font-bold text-gray-900 dark:text-gray-100">
|
||||
<p class="text-sm text-gray-600">Total Users</p>
|
||||
<p class="text-3xl font-bold text-gray-900">
|
||||
{{ number_format($stats['total_users']) }}
|
||||
</p>
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400 mt-1">
|
||||
{{ $stats['active_users'] }} active, {{ $stats['blocked_users'] }} blocked
|
||||
<p class="text-xs text-gray-500 mt-1">
|
||||
{{ $stats['active_credentials'] }} active credentials
|
||||
</p>
|
||||
</div>
|
||||
<div class="text-blue-500">
|
||||
@@ -33,15 +33,15 @@
|
||||
</div>
|
||||
|
||||
<!-- Requests Today -->
|
||||
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg">
|
||||
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
|
||||
<div class="p-6">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400">Requests Today</p>
|
||||
<p class="text-3xl font-bold text-blue-600 dark:text-blue-400">
|
||||
<p class="text-sm text-gray-600">Requests Today</p>
|
||||
<p class="text-3xl font-bold text-blue-600">
|
||||
{{ number_format($stats['total_requests_today']) }}
|
||||
</p>
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400 mt-1">
|
||||
<p class="text-xs text-gray-500 mt-1">
|
||||
{{ number_format($stats['total_requests_month']) }} this month
|
||||
</p>
|
||||
</div>
|
||||
@@ -55,15 +55,15 @@
|
||||
</div>
|
||||
|
||||
<!-- Spend Today -->
|
||||
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg">
|
||||
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
|
||||
<div class="p-6">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400">Spend Today</p>
|
||||
<p class="text-3xl font-bold text-green-600 dark:text-green-400">
|
||||
<p class="text-sm text-gray-600">Spend Today</p>
|
||||
<p class="text-3xl font-bold text-green-600">
|
||||
${{ number_format($stats['total_spend_today'], 2) }}
|
||||
</p>
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400 mt-1">
|
||||
<p class="text-xs text-gray-500 mt-1">
|
||||
${{ number_format($stats['total_spend_month'], 2) }} this month
|
||||
</p>
|
||||
</div>
|
||||
@@ -77,16 +77,16 @@
|
||||
</div>
|
||||
|
||||
<!-- Tokens Today -->
|
||||
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg">
|
||||
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
|
||||
<div class="p-6">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400">Tokens Today</p>
|
||||
<p class="text-3xl font-bold text-purple-600 dark:text-purple-400">
|
||||
<p class="text-sm text-gray-600">Tokens Today</p>
|
||||
<p class="text-3xl font-bold text-purple-600">
|
||||
{{ number_format($stats['total_tokens_today']) }}
|
||||
</p>
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400 mt-1">
|
||||
Prompt + Completion
|
||||
<p class="text-xs text-gray-500 mt-1">
|
||||
Avg: ${{ number_format($stats['avg_cost_per_request'], 4) }}/req
|
||||
</p>
|
||||
</div>
|
||||
<div class="text-purple-500">
|
||||
@@ -100,9 +100,9 @@
|
||||
</div>
|
||||
|
||||
<!-- Usage Trend Chart -->
|
||||
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg">
|
||||
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
|
||||
<div class="p-6">
|
||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-4">
|
||||
<h3 class="text-lg font-semibold text-gray-900 mb-4">
|
||||
Usage Trend (Last 30 Days)
|
||||
</h3>
|
||||
<canvas id="usageChart" height="80"></canvas>
|
||||
@@ -112,9 +112,9 @@
|
||||
<!-- Provider Stats & Top Users -->
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
<!-- Provider Breakdown -->
|
||||
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg">
|
||||
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
|
||||
<div class="p-6">
|
||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-4">
|
||||
<h3 class="text-lg font-semibold text-gray-900 mb-4">
|
||||
Usage by Provider
|
||||
</h3>
|
||||
<canvas id="providerChart" height="250"></canvas>
|
||||
@@ -122,30 +122,33 @@
|
||||
</div>
|
||||
|
||||
<!-- Top Users -->
|
||||
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg">
|
||||
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
|
||||
<div class="p-6">
|
||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-4">
|
||||
<h3 class="text-lg font-semibold text-gray-900 mb-4">
|
||||
Top Users by Spend
|
||||
</h3>
|
||||
<div class="space-y-4">
|
||||
@forelse($topUsers as $user)
|
||||
<div class="flex items-center justify-between border-b border-gray-200 dark:border-gray-700 pb-3">
|
||||
<div class="flex items-center justify-between border-b border-gray-200 pb-3">
|
||||
<div class="flex-1">
|
||||
<p class="font-medium text-gray-900 dark:text-gray-100">
|
||||
{{ $user->alias ?? $user->user_id }}
|
||||
<p class="font-medium text-gray-900">
|
||||
{{ $user->name }}
|
||||
</p>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">
|
||||
{{ number_format($user->usage_logs_count ?? 0) }} requests
|
||||
<p class="text-sm text-gray-500">
|
||||
{{ number_format($user->llm_requests_count ?? 0) }} requests
|
||||
</p>
|
||||
</div>
|
||||
<div class="text-right">
|
||||
<p class="font-semibold text-green-600 dark:text-green-400">
|
||||
${{ number_format($user->usage_logs_sum_cost ?? 0, 2) }}
|
||||
<p class="font-semibold text-green-600">
|
||||
${{ number_format($user->total_cost ?? 0, 2) }}
|
||||
</p>
|
||||
<p class="text-xs text-gray-500">
|
||||
{{ number_format($user->total_tokens ?? 0) }} tokens
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@empty
|
||||
<p class="text-gray-500 dark:text-gray-400 text-center py-4">
|
||||
<p class="text-gray-500 text-center py-4">
|
||||
No usage data yet
|
||||
</p>
|
||||
@endforelse
|
||||
@@ -155,40 +158,52 @@
|
||||
</div>
|
||||
|
||||
<!-- Model Stats -->
|
||||
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg">
|
||||
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
|
||||
<div class="p-6">
|
||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-4">
|
||||
<h3 class="text-lg font-semibold text-gray-900 mb-4">
|
||||
Most Used Models
|
||||
</h3>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
|
||||
<thead class="bg-gray-50 dark:bg-gray-900">
|
||||
<table class="min-w-full divide-y divide-gray-200">
|
||||
<thead class="bg-gray-50">
|
||||
<tr>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Model</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Requests</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Tokens</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Cost</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Model</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Provider</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Requests</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Tokens</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Cost</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="bg-white dark:bg-gray-800 divide-y divide-gray-200 dark:divide-gray-700">
|
||||
<tbody class="bg-white divide-y divide-gray-200">
|
||||
@forelse($modelStats as $model)
|
||||
<tr>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900 dark:text-gray-100">
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">
|
||||
{{ $model->model }}
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-400">
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
||||
<span class="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium
|
||||
@if($model->provider == 'openai') bg-green-100 text-green-800
|
||||
@elseif($model->provider == 'anthropic') bg-purple-100 text-purple-800
|
||||
@elseif($model->provider == 'mistral') bg-blue-100 text-blue-800
|
||||
@elseif($model->provider == 'gemini') bg-yellow-100 text-yellow-800
|
||||
@else bg-gray-100 text-gray-800
|
||||
@endif">
|
||||
{{ ucfirst($model->provider) }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
||||
{{ number_format($model->count) }}
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-400">
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
||||
{{ number_format($model->tokens ?? 0) }}
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-400">
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
||||
${{ number_format($model->total_cost ?? 0, 4) }}
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="4" class="px-6 py-4 text-center text-gray-500 dark:text-gray-400">
|
||||
<td colspan="5" class="px-6 py-4 text-center text-gray-500">
|
||||
No usage data yet
|
||||
</td>
|
||||
</tr>
|
||||
@@ -245,12 +260,6 @@
|
||||
title: {
|
||||
display: true,
|
||||
text: 'Requests'
|
||||
},
|
||||
ticks: {
|
||||
color: getComputedStyle(document.documentElement).getPropertyValue('--tw-text-opacity') ? '#9CA3AF' : '#6B7280'
|
||||
},
|
||||
grid: {
|
||||
color: 'rgba(156, 163, 175, 0.1)'
|
||||
}
|
||||
},
|
||||
y1: {
|
||||
@@ -261,27 +270,9 @@
|
||||
display: true,
|
||||
text: 'Cost ($)'
|
||||
},
|
||||
ticks: {
|
||||
color: getComputedStyle(document.documentElement).getPropertyValue('--tw-text-opacity') ? '#9CA3AF' : '#6B7280'
|
||||
},
|
||||
grid: {
|
||||
drawOnChartArea: false,
|
||||
},
|
||||
},
|
||||
x: {
|
||||
ticks: {
|
||||
color: getComputedStyle(document.documentElement).getPropertyValue('--tw-text-opacity') ? '#9CA3AF' : '#6B7280'
|
||||
},
|
||||
grid: {
|
||||
color: 'rgba(156, 163, 175, 0.1)'
|
||||
}
|
||||
}
|
||||
},
|
||||
plugins: {
|
||||
legend: {
|
||||
labels: {
|
||||
color: getComputedStyle(document.documentElement).getPropertyValue('--tw-text-opacity') ? '#9CA3AF' : '#6B7280'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -292,19 +283,19 @@
|
||||
new Chart(providerCtx, {
|
||||
type: 'doughnut',
|
||||
data: {
|
||||
labels: @json($providerStats->pluck('provider')),
|
||||
labels: @json($providerStats->pluck('provider')->map(fn($p) => ucfirst($p))),
|
||||
datasets: [{
|
||||
data: @json($providerStats->pluck('count')),
|
||||
backgroundColor: [
|
||||
'rgba(59, 130, 246, 0.8)',
|
||||
'rgba(16, 185, 129, 0.8)',
|
||||
'rgba(249, 115, 22, 0.8)',
|
||||
'rgba(168, 85, 247, 0.8)',
|
||||
'rgba(236, 72, 153, 0.8)',
|
||||
'rgba(245, 158, 11, 0.8)',
|
||||
'rgba(34, 197, 94, 0.8)', // Green - OpenAI
|
||||
'rgba(168, 85, 247, 0.8)', // Purple - Anthropic
|
||||
'rgba(59, 130, 246, 0.8)', // Blue - Mistral
|
||||
'rgba(251, 191, 36, 0.8)', // Yellow - Gemini
|
||||
'rgba(236, 72, 153, 0.8)', // Pink - DeepSeek
|
||||
'rgba(249, 115, 22, 0.8)', // Orange
|
||||
],
|
||||
borderWidth: 2,
|
||||
borderColor: '#1f2937'
|
||||
borderColor: '#fff'
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
@@ -314,7 +305,6 @@
|
||||
legend: {
|
||||
position: 'bottom',
|
||||
labels: {
|
||||
color: getComputedStyle(document.documentElement).getPropertyValue('--tw-text-opacity') ? '#9CA3AF' : '#6B7280',
|
||||
padding: 15
|
||||
}
|
||||
},
|
||||
@@ -322,11 +312,10 @@
|
||||
callbacks: {
|
||||
label: function(context) {
|
||||
let label = context.label || '';
|
||||
if (label) {
|
||||
label += ': ';
|
||||
}
|
||||
label += context.parsed + ' requests';
|
||||
return label;
|
||||
let value = context.parsed || 0;
|
||||
let total = context.dataset.data.reduce((a, b) => a + b, 0);
|
||||
let percentage = ((value / total) * 100).toFixed(1);
|
||||
return label + ': ' + value + ' requests (' + percentage + '%)';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user