feat:AI 토큰 사용량 관리 화면 추가
- AiTokenUsageController (index, list) 생성 - AiTokenUsage 모델 생성 - React 기반 토큰 사용량 조회 페이지 (필터, 통계, 페이지네이션) - 라우트 추가 (system/ai-token-usage) - AiTokenUsageMenuSeeder 메뉴 시더 생성 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
123
app/Http/Controllers/System/AiTokenUsageController.php
Normal file
123
app/Http/Controllers/System/AiTokenUsageController.php
Normal file
@@ -0,0 +1,123 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\System;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\System\AiTokenUsage;
|
||||
use App\Models\Tenants\Tenant;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Response;
|
||||
use Illuminate\View\View;
|
||||
|
||||
class AiTokenUsageController extends Controller
|
||||
{
|
||||
public function index(Request $request): View|Response
|
||||
{
|
||||
if ($request->header('HX-Request')) {
|
||||
return response('', 200)->header('HX-Redirect', route('system.ai-token-usage.index'));
|
||||
}
|
||||
|
||||
return view('system.ai-token-usage.index');
|
||||
}
|
||||
|
||||
public function list(Request $request): JsonResponse
|
||||
{
|
||||
$perPage = $request->input('per_page', 20);
|
||||
$startDate = $request->input('start_date');
|
||||
$endDate = $request->input('end_date');
|
||||
$tenantId = $request->input('tenant_id');
|
||||
$menuName = $request->input('menu_name');
|
||||
|
||||
$query = AiTokenUsage::query()
|
||||
->orderByDesc('created_at');
|
||||
|
||||
if ($tenantId) {
|
||||
$query->where('tenant_id', $tenantId);
|
||||
}
|
||||
|
||||
if ($menuName) {
|
||||
$query->where('menu_name', $menuName);
|
||||
}
|
||||
|
||||
if ($startDate) {
|
||||
$query->whereDate('created_at', '>=', $startDate);
|
||||
}
|
||||
|
||||
if ($endDate) {
|
||||
$query->whereDate('created_at', '<=', $endDate);
|
||||
}
|
||||
|
||||
// 통계 (필터 조건 동일하게 적용)
|
||||
$statsQuery = clone $query;
|
||||
$stats = $statsQuery->selectRaw('
|
||||
COUNT(*) as total_count,
|
||||
SUM(prompt_tokens) as total_prompt_tokens,
|
||||
SUM(completion_tokens) as total_completion_tokens,
|
||||
SUM(total_tokens) as total_total_tokens,
|
||||
SUM(cost_usd) as total_cost_usd,
|
||||
SUM(cost_krw) as total_cost_krw
|
||||
')->first();
|
||||
|
||||
// 페이지네이션
|
||||
$records = $query->paginate($perPage);
|
||||
|
||||
// 테넌트 이름 매핑
|
||||
$tenantIds = $records->pluck('tenant_id')->unique();
|
||||
$tenants = Tenant::whereIn('id', $tenantIds)->pluck('company_name', 'id');
|
||||
|
||||
$data = $records->through(function ($item) use ($tenants) {
|
||||
return [
|
||||
'id' => $item->id,
|
||||
'tenant_id' => $item->tenant_id,
|
||||
'tenant_name' => $tenants[$item->tenant_id] ?? '-',
|
||||
'model' => $item->model,
|
||||
'menu_name' => $item->menu_name,
|
||||
'prompt_tokens' => $item->prompt_tokens,
|
||||
'completion_tokens' => $item->completion_tokens,
|
||||
'total_tokens' => $item->total_tokens,
|
||||
'cost_usd' => (float) $item->cost_usd,
|
||||
'cost_krw' => (float) $item->cost_krw,
|
||||
'request_id' => $item->request_id,
|
||||
'created_at' => $item->created_at->format('Y-m-d H:i:s'),
|
||||
];
|
||||
});
|
||||
|
||||
// 필터용 메뉴 목록
|
||||
$menuNames = AiTokenUsage::select('menu_name')
|
||||
->distinct()
|
||||
->orderBy('menu_name')
|
||||
->pluck('menu_name');
|
||||
|
||||
// 필터용 테넌트 목록
|
||||
$allTenantIds = AiTokenUsage::select('tenant_id')
|
||||
->distinct()
|
||||
->pluck('tenant_id');
|
||||
$allTenants = Tenant::whereIn('id', $allTenantIds)
|
||||
->orderBy('company_name')
|
||||
->get(['id', 'company_name']);
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'data' => $data->items(),
|
||||
'stats' => [
|
||||
'total_count' => (int) ($stats->total_count ?? 0),
|
||||
'total_prompt_tokens' => (int) ($stats->total_prompt_tokens ?? 0),
|
||||
'total_completion_tokens' => (int) ($stats->total_completion_tokens ?? 0),
|
||||
'total_total_tokens' => (int) ($stats->total_total_tokens ?? 0),
|
||||
'total_cost_usd' => round((float) ($stats->total_cost_usd ?? 0), 6),
|
||||
'total_cost_krw' => round((float) ($stats->total_cost_krw ?? 0), 2),
|
||||
],
|
||||
'filters' => [
|
||||
'menu_names' => $menuNames,
|
||||
'tenants' => $allTenants->map(fn ($t) => ['id' => $t->id, 'name' => $t->company_name]),
|
||||
],
|
||||
'pagination' => [
|
||||
'current_page' => $records->currentPage(),
|
||||
'last_page' => $records->lastPage(),
|
||||
'per_page' => $records->perPage(),
|
||||
'total' => $records->total(),
|
||||
],
|
||||
]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user