feat: Implementiere umfassende RESTful API für LLM Gateway
Fügt 21 neue API-Endpoints in 4 Phasen hinzu:
Phase 1 - Foundation (Provider & Models):
- GET /api/providers - Liste aller Provider
- GET /api/providers/{provider} - Provider-Details
- GET /api/models - Liste aller Models mit Filtering/Sorting
- GET /api/models/{provider}/{model} - Model-Details
Phase 2 - Core Features (Credentials, Budget, Pricing):
- GET/POST/PUT/DELETE /api/credentials - Credential-Management
- POST /api/credentials/{id}/test - Connection Testing
- GET /api/budget - Budget-Status mit Projektionen
- GET /api/budget/history - Budget-Historie
- GET /api/pricing - Model-Pricing-Listen
- GET /api/pricing/calculator - Kosten-Kalkulator
- GET /api/pricing/compare - Preis-Vergleich
Phase 3 - Analytics (Usage Statistics):
- GET /api/usage/summary - Umfassende Statistiken
- GET /api/usage/requests - Request-History mit Pagination
- GET /api/usage/requests/{id} - Request-Details
- GET /api/usage/charts - Chart-Daten (4 Typen)
Phase 4 - Account (Account Info & Activity):
- GET /api/account - User-Informationen
- GET /api/account/activity - Activity-Log
Features:
- Vollständige Scramble/Swagger-Dokumentation
- Consistent Error-Handling
- API-Key Authentication
- Filtering, Sorting, Pagination
- Budget-Tracking mit Alerts
- Provider-Breakdown
- Performance-Metriken
- Chart-Ready-Data
Controller erstellt:
- ProviderController
- ModelController
- CredentialController
- BudgetController
- PricingController
- UsageController
- AccountController
Dokumentation:
- API_KONZEPT.md - Vollständiges API-Konzept
- API_IMPLEMENTATION_STATUS.txt - Implementation-Tracking
- API_IMPLEMENTATION_SUMMARY.md - Zusammenfassung und Workflows
This commit is contained in:
636
laravel-app/app/Http/Controllers/Api/UsageController.php
Normal file
636
laravel-app/app/Http/Controllers/Api/UsageController.php
Normal file
@@ -0,0 +1,636 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\LlmRequest;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class UsageController extends Controller
|
||||
{
|
||||
/**
|
||||
* Get usage summary statistics
|
||||
*
|
||||
* Returns comprehensive usage statistics for the authenticated user,
|
||||
* including requests, tokens, costs, and breakdowns by provider and model.
|
||||
*
|
||||
* ## Query Parameters
|
||||
*
|
||||
* - `period` (optional) - Time period: today, week, month, all (default: month)
|
||||
* - `provider` (optional) - Filter by provider
|
||||
*
|
||||
* ## Example Response
|
||||
*
|
||||
* ```json
|
||||
* {
|
||||
* "data": {
|
||||
* "period": "month",
|
||||
* "period_start": "2025-11-01T00:00:00Z",
|
||||
* "period_end": "2025-11-30T23:59:59Z",
|
||||
* "summary": {
|
||||
* "total_requests": 1250,
|
||||
* "successful_requests": 1235,
|
||||
* "failed_requests": 15,
|
||||
* "success_rate": 98.8,
|
||||
* "total_tokens": 2500000,
|
||||
* "prompt_tokens": 1800000,
|
||||
* "completion_tokens": 700000,
|
||||
* "total_cost": 45.67,
|
||||
* "avg_cost_per_request": 0.0365,
|
||||
* "avg_tokens_per_request": 2000,
|
||||
* "avg_response_time_ms": 1450
|
||||
* },
|
||||
* "by_provider": [...],
|
||||
* "by_model": [...],
|
||||
* "top_hours": [...]
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @tags Usage
|
||||
*
|
||||
* @param Request $request
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function summary(Request $request): JsonResponse
|
||||
{
|
||||
$validator = Validator::make($request->all(), [
|
||||
'period' => 'sometimes|string|in:today,week,month,all',
|
||||
'provider' => 'sometimes|string|in:openai,anthropic,gemini,deepseek,mistral',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return response()->json([
|
||||
'error' => [
|
||||
'code' => 'validation_error',
|
||||
'message' => 'Invalid query parameters',
|
||||
'status' => 422,
|
||||
'details' => $validator->errors(),
|
||||
],
|
||||
], 422);
|
||||
}
|
||||
|
||||
$user = $request->user();
|
||||
$period = $request->input('period', 'month');
|
||||
|
||||
// Calculate date range
|
||||
$dateRange = $this->getDateRange($period);
|
||||
|
||||
// Base query
|
||||
$query = LlmRequest::where('gateway_user_id', $user->user_id)
|
||||
->where('created_at', '>=', $dateRange['start']);
|
||||
|
||||
if ($dateRange['end']) {
|
||||
$query->where('created_at', '<=', $dateRange['end']);
|
||||
}
|
||||
|
||||
// Apply provider filter
|
||||
if ($request->has('provider')) {
|
||||
$query->where('provider', $request->input('provider'));
|
||||
}
|
||||
|
||||
// Get summary statistics
|
||||
$summary = $query->selectRaw('
|
||||
COUNT(*) as total_requests,
|
||||
SUM(CASE WHEN status = "success" THEN 1 ELSE 0 END) as successful_requests,
|
||||
SUM(CASE WHEN status != "success" THEN 1 ELSE 0 END) as failed_requests,
|
||||
SUM(prompt_tokens) as prompt_tokens,
|
||||
SUM(completion_tokens) as completion_tokens,
|
||||
SUM(total_tokens) as total_tokens,
|
||||
SUM(total_cost) as total_cost,
|
||||
AVG(total_tokens) as avg_tokens_per_request,
|
||||
AVG(total_cost) as avg_cost_per_request,
|
||||
AVG(response_time_ms) as avg_response_time_ms
|
||||
')->first();
|
||||
|
||||
$successRate = $summary->total_requests > 0
|
||||
? ($summary->successful_requests / $summary->total_requests) * 100
|
||||
: 0;
|
||||
|
||||
// Get breakdown by provider
|
||||
$byProvider = LlmRequest::where('gateway_user_id', $user->user_id)
|
||||
->where('created_at', '>=', $dateRange['start'])
|
||||
->where('status', 'success')
|
||||
->select(
|
||||
'provider',
|
||||
DB::raw('COUNT(*) as requests'),
|
||||
DB::raw('SUM(total_tokens) as tokens'),
|
||||
DB::raw('SUM(total_cost) as cost'),
|
||||
DB::raw('AVG(response_time_ms) as avg_response_time_ms')
|
||||
)
|
||||
->groupBy('provider')
|
||||
->orderByDesc('requests')
|
||||
->get()
|
||||
->map(function ($item) use ($summary) {
|
||||
$successRate = LlmRequest::where('gateway_user_id', request()->user()->user_id)
|
||||
->where('provider', $item->provider)
|
||||
->where('created_at', '>=', $this->getDateRange(request()->input('period', 'month'))['start'])
|
||||
->selectRaw('
|
||||
COUNT(*) as total,
|
||||
SUM(CASE WHEN status = "success" THEN 1 ELSE 0 END) as successful
|
||||
')
|
||||
->first();
|
||||
|
||||
$rate = $successRate->total > 0 ? ($successRate->successful / $successRate->total) * 100 : 0;
|
||||
|
||||
return [
|
||||
'provider' => $item->provider,
|
||||
'provider_name' => $this->getProviderName($item->provider),
|
||||
'requests' => $item->requests,
|
||||
'tokens' => $item->tokens,
|
||||
'cost' => round($item->cost, 4),
|
||||
'avg_response_time_ms' => round($item->avg_response_time_ms),
|
||||
'success_rate' => round($rate, 1),
|
||||
];
|
||||
});
|
||||
|
||||
// Get breakdown by model (top 10)
|
||||
$byModel = LlmRequest::where('gateway_user_id', $user->user_id)
|
||||
->where('created_at', '>=', $dateRange['start'])
|
||||
->where('status', 'success')
|
||||
->select(
|
||||
'model',
|
||||
'provider',
|
||||
DB::raw('COUNT(*) as requests'),
|
||||
DB::raw('SUM(total_tokens) as tokens'),
|
||||
DB::raw('SUM(total_cost) as cost'),
|
||||
DB::raw('AVG(total_tokens) as avg_tokens_per_request')
|
||||
)
|
||||
->groupBy('model', 'provider')
|
||||
->orderByDesc('requests')
|
||||
->limit(10)
|
||||
->get()
|
||||
->map(function ($item) {
|
||||
return [
|
||||
'model' => $item->model,
|
||||
'provider' => $item->provider,
|
||||
'requests' => $item->requests,
|
||||
'tokens' => $item->tokens,
|
||||
'cost' => round($item->cost, 4),
|
||||
'avg_tokens_per_request' => round($item->avg_tokens_per_request),
|
||||
];
|
||||
});
|
||||
|
||||
// Get top hours
|
||||
$topHours = LlmRequest::where('gateway_user_id', $user->user_id)
|
||||
->where('created_at', '>=', $dateRange['start'])
|
||||
->where('status', 'success')
|
||||
->select(
|
||||
DB::raw('HOUR(created_at) as hour'),
|
||||
DB::raw('COUNT(*) as requests'),
|
||||
DB::raw('SUM(total_cost) as cost')
|
||||
)
|
||||
->groupBy('hour')
|
||||
->orderByDesc('requests')
|
||||
->limit(5)
|
||||
->get()
|
||||
->map(function ($item) {
|
||||
return [
|
||||
'hour' => $item->hour,
|
||||
'requests' => $item->requests,
|
||||
'cost' => round($item->cost, 4),
|
||||
];
|
||||
});
|
||||
|
||||
return response()->json([
|
||||
'data' => [
|
||||
'period' => $period,
|
||||
'period_start' => $dateRange['start']->toIso8601String(),
|
||||
'period_end' => $dateRange['end']?->toIso8601String() ?? now()->toIso8601String(),
|
||||
'summary' => [
|
||||
'total_requests' => $summary->total_requests ?? 0,
|
||||
'successful_requests' => $summary->successful_requests ?? 0,
|
||||
'failed_requests' => $summary->failed_requests ?? 0,
|
||||
'success_rate' => round($successRate, 1),
|
||||
'total_tokens' => $summary->total_tokens ?? 0,
|
||||
'prompt_tokens' => $summary->prompt_tokens ?? 0,
|
||||
'completion_tokens' => $summary->completion_tokens ?? 0,
|
||||
'total_cost' => round($summary->total_cost ?? 0, 4),
|
||||
'avg_cost_per_request' => round($summary->avg_cost_per_request ?? 0, 6),
|
||||
'avg_tokens_per_request' => round($summary->avg_tokens_per_request ?? 0),
|
||||
'avg_response_time_ms' => round($summary->avg_response_time_ms ?? 0),
|
||||
],
|
||||
'by_provider' => $byProvider,
|
||||
'by_model' => $byModel,
|
||||
'top_hours' => $topHours,
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get list of individual requests
|
||||
*
|
||||
* Returns paginated list of requests with filtering and sorting options.
|
||||
*
|
||||
* ## Query Parameters
|
||||
*
|
||||
* - `page` (optional) - Page number (default: 1)
|
||||
* - `per_page` (optional) - Items per page (default: 20, max: 100)
|
||||
* - `provider` (optional) - Filter by provider
|
||||
* - `model` (optional) - Filter by model
|
||||
* - `status` (optional) - Filter by status: success, failed, all (default: all)
|
||||
* - `date_from` (optional) - From date (ISO 8601)
|
||||
* - `date_to` (optional) - To date (ISO 8601)
|
||||
* - `sort` (optional) - Sort field: created_at, cost, tokens, response_time (default: -created_at)
|
||||
*
|
||||
* @tags Usage
|
||||
*
|
||||
* @param Request $request
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function requests(Request $request): JsonResponse
|
||||
{
|
||||
$validator = Validator::make($request->all(), [
|
||||
'page' => 'sometimes|integer|min:1',
|
||||
'per_page' => 'sometimes|integer|min:1|max:100',
|
||||
'provider' => 'sometimes|string|in:openai,anthropic,gemini,deepseek,mistral',
|
||||
'model' => 'sometimes|string',
|
||||
'status' => 'sometimes|string|in:success,failed,all',
|
||||
'date_from' => 'sometimes|date',
|
||||
'date_to' => 'sometimes|date',
|
||||
'sort' => 'sometimes|string|in:created_at,-created_at,cost,-cost,tokens,-tokens,response_time,-response_time',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return response()->json([
|
||||
'error' => [
|
||||
'code' => 'validation_error',
|
||||
'message' => 'Invalid query parameters',
|
||||
'status' => 422,
|
||||
'details' => $validator->errors(),
|
||||
],
|
||||
], 422);
|
||||
}
|
||||
|
||||
$user = $request->user();
|
||||
$perPage = $request->input('per_page', 20);
|
||||
|
||||
// Build query
|
||||
$query = LlmRequest::where('gateway_user_id', $user->user_id);
|
||||
|
||||
// Apply filters
|
||||
if ($request->has('provider')) {
|
||||
$query->where('provider', $request->input('provider'));
|
||||
}
|
||||
|
||||
if ($request->has('model')) {
|
||||
$query->where('model', $request->input('model'));
|
||||
}
|
||||
|
||||
$status = $request->input('status', 'all');
|
||||
if ($status === 'success') {
|
||||
$query->where('status', 'success');
|
||||
} elseif ($status === 'failed') {
|
||||
$query->where('status', '!=', 'success');
|
||||
}
|
||||
|
||||
if ($request->has('date_from')) {
|
||||
$query->where('created_at', '>=', $request->input('date_from'));
|
||||
}
|
||||
|
||||
if ($request->has('date_to')) {
|
||||
$query->where('created_at', '<=', $request->input('date_to'));
|
||||
}
|
||||
|
||||
// Apply sorting
|
||||
$sort = $request->input('sort', '-created_at');
|
||||
$sortField = ltrim($sort, '-');
|
||||
$sortDirection = str_starts_with($sort, '-') ? 'desc' : 'asc';
|
||||
$query->orderBy($sortField, $sortDirection);
|
||||
|
||||
// Get summary for filtered results
|
||||
$summary = $query->clone()->selectRaw('
|
||||
SUM(total_cost) as total_cost,
|
||||
SUM(total_tokens) as total_tokens,
|
||||
AVG(response_time_ms) as avg_response_time_ms
|
||||
')->first();
|
||||
|
||||
// Paginate
|
||||
$paginated = $query->paginate($perPage);
|
||||
|
||||
$data = $paginated->map(function ($request) {
|
||||
return [
|
||||
'id' => $request->request_id,
|
||||
'provider' => $request->provider,
|
||||
'model' => $request->model,
|
||||
'status' => $request->status,
|
||||
'prompt_tokens' => $request->prompt_tokens,
|
||||
'completion_tokens' => $request->completion_tokens,
|
||||
'total_tokens' => $request->total_tokens,
|
||||
'input_cost' => round($request->prompt_tokens * ($request->input_price_per_token ?? 0), 6),
|
||||
'output_cost' => round($request->completion_tokens * ($request->output_price_per_token ?? 0), 6),
|
||||
'total_cost' => round($request->total_cost, 6),
|
||||
'response_time_ms' => $request->response_time_ms,
|
||||
'created_at' => $request->created_at->toIso8601String(),
|
||||
];
|
||||
});
|
||||
|
||||
return response()->json([
|
||||
'data' => $data,
|
||||
'meta' => [
|
||||
'current_page' => $paginated->currentPage(),
|
||||
'per_page' => $paginated->perPage(),
|
||||
'total' => $paginated->total(),
|
||||
'total_pages' => $paginated->lastPage(),
|
||||
'has_more' => $paginated->hasMorePages(),
|
||||
],
|
||||
'links' => [
|
||||
'first' => $paginated->url(1),
|
||||
'last' => $paginated->url($paginated->lastPage()),
|
||||
'prev' => $paginated->previousPageUrl(),
|
||||
'next' => $paginated->nextPageUrl(),
|
||||
],
|
||||
'summary' => [
|
||||
'total_cost' => round($summary->total_cost ?? 0, 4),
|
||||
'total_tokens' => $summary->total_tokens ?? 0,
|
||||
'avg_response_time_ms' => round($summary->avg_response_time_ms ?? 0),
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get details of a specific request
|
||||
*
|
||||
* Returns complete information about a single request including
|
||||
* full request and response data.
|
||||
*
|
||||
* @tags Usage
|
||||
*
|
||||
* @param Request $request
|
||||
* @param string $id
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function show(Request $request, string $id): JsonResponse
|
||||
{
|
||||
$user = $request->user();
|
||||
|
||||
$llmRequest = LlmRequest::where('gateway_user_id', $user->user_id)
|
||||
->where('request_id', $id)
|
||||
->first();
|
||||
|
||||
if (!$llmRequest) {
|
||||
return response()->json([
|
||||
'error' => [
|
||||
'code' => 'not_found',
|
||||
'message' => 'Request not found',
|
||||
'status' => 404,
|
||||
],
|
||||
], 404);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'data' => [
|
||||
'id' => $llmRequest->request_id,
|
||||
'gateway_user_id' => $llmRequest->gateway_user_id,
|
||||
'provider' => $llmRequest->provider,
|
||||
'provider_name' => $this->getProviderName($llmRequest->provider),
|
||||
'model' => $llmRequest->model,
|
||||
'status' => $llmRequest->status,
|
||||
'request' => $llmRequest->request_data,
|
||||
'response' => $llmRequest->response_data,
|
||||
'usage' => [
|
||||
'prompt_tokens' => $llmRequest->prompt_tokens,
|
||||
'completion_tokens' => $llmRequest->completion_tokens,
|
||||
'total_tokens' => $llmRequest->total_tokens,
|
||||
],
|
||||
'cost' => [
|
||||
'input_cost' => round($llmRequest->prompt_tokens * ($llmRequest->input_price_per_token ?? 0), 6),
|
||||
'output_cost' => round($llmRequest->completion_tokens * ($llmRequest->output_price_per_token ?? 0), 6),
|
||||
'total_cost' => round($llmRequest->total_cost, 6),
|
||||
'currency' => 'USD',
|
||||
],
|
||||
'performance' => [
|
||||
'response_time_ms' => $llmRequest->response_time_ms,
|
||||
],
|
||||
'metadata' => [
|
||||
'ip_address' => $llmRequest->ip_address,
|
||||
'user_agent' => $llmRequest->user_agent,
|
||||
],
|
||||
'created_at' => $llmRequest->created_at->toIso8601String(),
|
||||
'completed_at' => $llmRequest->created_at->addMilliseconds($llmRequest->response_time_ms)->toIso8601String(),
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get chart data for visualizations
|
||||
*
|
||||
* Returns data formatted for chart visualizations.
|
||||
*
|
||||
* ## Query Parameters
|
||||
*
|
||||
* - `type` (required) - Chart type: daily_cost, provider_distribution, model_usage, hourly_pattern
|
||||
* - `days` (optional) - Number of days to look back (default: 30)
|
||||
*
|
||||
* @tags Usage
|
||||
*
|
||||
* @param Request $request
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function charts(Request $request): JsonResponse
|
||||
{
|
||||
$validator = Validator::make($request->all(), [
|
||||
'type' => 'required|string|in:daily_cost,provider_distribution,model_usage,hourly_pattern',
|
||||
'days' => 'sometimes|integer|min:1|max:365',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return response()->json([
|
||||
'error' => [
|
||||
'code' => 'validation_error',
|
||||
'message' => 'Invalid query parameters',
|
||||
'status' => 422,
|
||||
'details' => $validator->errors(),
|
||||
],
|
||||
], 422);
|
||||
}
|
||||
|
||||
$user = $request->user();
|
||||
$type = $request->input('type');
|
||||
$days = $request->input('days', 30);
|
||||
$startDate = now()->subDays($days);
|
||||
|
||||
$chartData = match ($type) {
|
||||
'daily_cost' => $this->getDailyCostChart($user, $startDate),
|
||||
'provider_distribution' => $this->getProviderDistributionChart($user, $startDate),
|
||||
'model_usage' => $this->getModelUsageChart($user, $startDate),
|
||||
'hourly_pattern' => $this->getHourlyPatternChart($user, $startDate),
|
||||
};
|
||||
|
||||
return response()->json([
|
||||
'data' => $chartData,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate date range for period
|
||||
*/
|
||||
private function getDateRange(string $period): array
|
||||
{
|
||||
$now = now();
|
||||
|
||||
return match ($period) {
|
||||
'today' => [
|
||||
'start' => $now->copy()->startOfDay(),
|
||||
'end' => $now->copy()->endOfDay(),
|
||||
],
|
||||
'week' => [
|
||||
'start' => $now->copy()->startOfWeek(),
|
||||
'end' => $now->copy()->endOfWeek(),
|
||||
],
|
||||
'month' => [
|
||||
'start' => $now->copy()->startOfMonth(),
|
||||
'end' => $now->copy()->endOfMonth(),
|
||||
],
|
||||
'all' => [
|
||||
'start' => $now->copy()->subYears(10), // 10 years back
|
||||
'end' => null,
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get daily cost chart data
|
||||
*/
|
||||
private function getDailyCostChart($user, $startDate): array
|
||||
{
|
||||
$dailyData = LlmRequest::where('gateway_user_id', $user->user_id)
|
||||
->where('created_at', '>=', $startDate)
|
||||
->where('status', 'success')
|
||||
->select(
|
||||
DB::raw('DATE(created_at) as date'),
|
||||
DB::raw('SUM(total_cost) as cost'),
|
||||
DB::raw('COUNT(*) as requests')
|
||||
)
|
||||
->groupBy('date')
|
||||
->orderBy('date')
|
||||
->get();
|
||||
|
||||
return [
|
||||
'type' => 'daily_cost',
|
||||
'labels' => $dailyData->pluck('date')->toArray(),
|
||||
'datasets' => [
|
||||
[
|
||||
'label' => 'Daily Cost',
|
||||
'data' => $dailyData->pluck('cost')->map(fn($v) => round($v, 4))->toArray(),
|
||||
'backgroundColor' => 'rgba(59, 130, 246, 0.5)',
|
||||
'borderColor' => 'rgba(59, 130, 246, 1)',
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get provider distribution chart data
|
||||
*/
|
||||
private function getProviderDistributionChart($user, $startDate): array
|
||||
{
|
||||
$providerData = LlmRequest::where('gateway_user_id', $user->user_id)
|
||||
->where('created_at', '>=', $startDate)
|
||||
->where('status', 'success')
|
||||
->select('provider', DB::raw('SUM(total_cost) as cost'))
|
||||
->groupBy('provider')
|
||||
->orderByDesc('cost')
|
||||
->get();
|
||||
|
||||
return [
|
||||
'type' => 'provider_distribution',
|
||||
'labels' => $providerData->pluck('provider')->map(fn($p) => $this->getProviderName($p))->toArray(),
|
||||
'datasets' => [
|
||||
[
|
||||
'label' => 'Cost by Provider',
|
||||
'data' => $providerData->pluck('cost')->map(fn($v) => round($v, 4))->toArray(),
|
||||
'backgroundColor' => [
|
||||
'rgba(59, 130, 246, 0.8)',
|
||||
'rgba(239, 68, 68, 0.8)',
|
||||
'rgba(34, 197, 94, 0.8)',
|
||||
'rgba(251, 146, 60, 0.8)',
|
||||
'rgba(168, 85, 247, 0.8)',
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get model usage chart data
|
||||
*/
|
||||
private function getModelUsageChart($user, $startDate): array
|
||||
{
|
||||
$modelData = LlmRequest::where('gateway_user_id', $user->user_id)
|
||||
->where('created_at', '>=', $startDate)
|
||||
->where('status', 'success')
|
||||
->select('model', DB::raw('COUNT(*) as requests'))
|
||||
->groupBy('model')
|
||||
->orderByDesc('requests')
|
||||
->limit(10)
|
||||
->get();
|
||||
|
||||
return [
|
||||
'type' => 'model_usage',
|
||||
'labels' => $modelData->pluck('model')->toArray(),
|
||||
'datasets' => [
|
||||
[
|
||||
'label' => 'Requests by Model',
|
||||
'data' => $modelData->pluck('requests')->toArray(),
|
||||
'backgroundColor' => 'rgba(59, 130, 246, 0.5)',
|
||||
'borderColor' => 'rgba(59, 130, 246, 1)',
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get hourly pattern chart data
|
||||
*/
|
||||
private function getHourlyPatternChart($user, $startDate): array
|
||||
{
|
||||
$hourlyData = LlmRequest::where('gateway_user_id', $user->user_id)
|
||||
->where('created_at', '>=', $startDate)
|
||||
->where('status', 'success')
|
||||
->select(
|
||||
DB::raw('HOUR(created_at) as hour'),
|
||||
DB::raw('COUNT(*) as requests')
|
||||
)
|
||||
->groupBy('hour')
|
||||
->orderBy('hour')
|
||||
->get();
|
||||
|
||||
// Fill missing hours with 0
|
||||
$allHours = collect(range(0, 23))->map(function ($hour) use ($hourlyData) {
|
||||
$data = $hourlyData->firstWhere('hour', $hour);
|
||||
return $data?->requests ?? 0;
|
||||
});
|
||||
|
||||
return [
|
||||
'type' => 'hourly_pattern',
|
||||
'labels' => range(0, 23),
|
||||
'datasets' => [
|
||||
[
|
||||
'label' => 'Requests by Hour',
|
||||
'data' => $allHours->toArray(),
|
||||
'backgroundColor' => 'rgba(59, 130, 246, 0.5)',
|
||||
'borderColor' => 'rgba(59, 130, 246, 1)',
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get human-readable provider name
|
||||
*/
|
||||
private function getProviderName(string $provider): string
|
||||
{
|
||||
return match ($provider) {
|
||||
'openai' => 'OpenAI',
|
||||
'anthropic' => 'Anthropic',
|
||||
'gemini' => 'Google Gemini',
|
||||
'deepseek' => 'DeepSeek',
|
||||
'mistral' => 'Mistral AI',
|
||||
default => ucfirst($provider),
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user