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(), ], ]); } public function pricingList(): JsonResponse { $configs = AiPricingConfig::orderBy('id')->get(); return response()->json([ 'success' => true, 'data' => $configs->map(fn ($c) => [ 'id' => $c->id, 'provider' => $c->provider, 'model_name' => $c->model_name, 'input_price_per_million' => (float) $c->input_price_per_million, 'output_price_per_million' => (float) $c->output_price_per_million, 'unit_price' => (float) $c->unit_price, 'unit_description' => $c->unit_description, 'exchange_rate' => (float) $c->exchange_rate, 'is_active' => $c->is_active, 'description' => $c->description, ]), ]); } public function pricingUpdate(Request $request): JsonResponse { $validated = $request->validate([ 'configs' => 'required|array', 'configs.*.id' => 'required|integer|exists:ai_pricing_configs,id', 'configs.*.model_name' => 'required|string|max:100', 'configs.*.input_price_per_million' => 'required|numeric|min:0', 'configs.*.output_price_per_million' => 'required|numeric|min:0', 'configs.*.unit_price' => 'required|numeric|min:0', 'configs.*.exchange_rate' => 'required|numeric|min:0', 'configs.*.description' => 'nullable|string|max:255', ]); foreach ($validated['configs'] as $item) { AiPricingConfig::where('id', $item['id'])->update([ 'model_name' => $item['model_name'], 'input_price_per_million' => $item['input_price_per_million'], 'output_price_per_million' => $item['output_price_per_million'], 'unit_price' => $item['unit_price'], 'exchange_rate' => $item['exchange_rate'], 'description' => $item['description'] ?? null, ]); } AiPricingConfig::clearCache(); return response()->json([ 'success' => true, 'message' => '단가 설정이 저장되었습니다.', ]); } }