orderBy('model') ->paginate(20); return view('model-pricing.index', compact('modelPricing')); } /** * Show the form for creating a new model pricing */ public function create() { return view('model-pricing.create'); } /** * Store a newly created model pricing */ public function store(Request $request) { $validated = $request->validate([ 'provider' => 'required|string|max:50', 'model' => 'required|string|max:100', 'input_price_per_million' => 'required|numeric|min:0', 'output_price_per_million' => 'required|numeric|min:0', 'context_window' => 'nullable|integer|min:0', 'max_output_tokens' => 'nullable|integer|min:0', 'is_active' => 'boolean', 'effective_from' => 'nullable|date', 'effective_until' => 'nullable|date', 'notes' => 'nullable|string', ]); ModelPricing::create($validated); return redirect() ->route('model-pricing.index') ->with('success', 'Model pricing created successfully!'); } /** * Display the specified model pricing */ public function show(ModelPricing $modelPricing) { return view('model-pricing.show', compact('modelPricing')); } /** * Show the form for editing the specified model pricing */ public function edit(ModelPricing $modelPricing) { return view('model-pricing.edit', compact('modelPricing')); } /** * Update the specified model pricing */ public function update(Request $request, ModelPricing $modelPricing) { $validated = $request->validate([ 'provider' => 'required|string|max:50', 'model' => 'required|string|max:100', 'input_price_per_million' => 'required|numeric|min:0', 'output_price_per_million' => 'required|numeric|min:0', 'context_window' => 'nullable|integer|min:0', 'max_output_tokens' => 'nullable|integer|min:0', 'is_active' => 'boolean', 'effective_from' => 'nullable|date', 'effective_until' => 'nullable|date', 'notes' => 'nullable|string', ]); $modelPricing->update($validated); return redirect() ->route('model-pricing.index') ->with('success', 'Model pricing updated successfully!'); } /** * Remove the specified model pricing */ public function destroy(ModelPricing $modelPricing) { $modelPricing->delete(); return redirect() ->route('model-pricing.index') ->with('success', 'Model pricing deleted successfully!'); } /** * Show the cost calculator */ public function calculator() { $models = ModelPricing::where('is_active', true) ->orderBy('provider') ->orderBy('model') ->get(); return view('model-pricing.calculator', compact('models')); } /** * Calculate cost based on input */ public function calculate(Request $request) { $validated = $request->validate([ 'model_pricing_id' => 'required|exists:model_pricing,id', 'input_tokens' => 'required|integer|min:0', 'output_tokens' => 'required|integer|min:0', ]); $model = ModelPricing::findOrFail($validated['model_pricing_id']); $cost = $model->calculateCost( $validated['input_tokens'], $validated['output_tokens'] ); return response()->json([ 'provider' => $model->provider, 'model' => $model->model, 'input_tokens' => $validated['input_tokens'], 'output_tokens' => $validated['output_tokens'], 'total_tokens' => $validated['input_tokens'] + $validated['output_tokens'], 'cost' => $cost, 'cost_formatted' => '$' . number_format($cost, 6), ]); } /** * Show CSV import form */ public function importForm() { return view('model-pricing.import'); } /** * Import model pricing from CSV */ public function import(Request $request) { $request->validate([ 'csv_file' => 'required|file|mimes:csv,txt|max:2048', ]); $file = $request->file('csv_file'); $handle = fopen($file->getRealPath(), 'r'); $imported = 0; $updated = 0; $errors = []; // Skip header row fgetcsv($handle); while (($row = fgetcsv($handle)) !== false) { if (count($row) < 4) { continue; // Skip invalid rows } $provider = trim($row[0]); $model = trim($row[1]); $inputPrice = floatval($row[2]); $outputPrice = floatval($row[3]); if (empty($provider) || empty($model) || $inputPrice < 0 || $outputPrice < 0) { $errors[] = "Invalid data for model: {$provider}/{$model}"; continue; } $existing = ModelPricing::where('provider', $provider) ->where('model', $model) ->first(); if ($existing) { $existing->update([ 'input_price_per_million' => $inputPrice, 'output_price_per_million' => $outputPrice, ]); $updated++; } else { ModelPricing::create([ 'provider' => $provider, 'model' => $model, 'input_price_per_million' => $inputPrice, 'output_price_per_million' => $outputPrice, ]); $imported++; } } fclose($handle); $message = "Import completed! Created: {$imported}, Updated: {$updated}"; if (count($errors) > 0) { $message .= '. Errors: ' . implode(', ', array_slice($errors, 0, 5)); } return redirect() ->route('model-pricing.index') ->with('success', $message); } /** * Get available models from a provider */ public function getProviderModels(string $provider) { try { $models = []; switch($provider) { case 'openai': $models = $this->getOpenAIModels(); break; case 'anthropic': $models = $this->getAnthropicModels(); break; case 'deepseek': $models = $this->getDeepSeekModels(); break; case 'google': $models = $this->getGeminiModels(); break; case 'cohere': return response()->json([ 'success' => false, 'message' => 'Cohere does not provide a public models API. Please enter model names manually' ]); case 'mistral': $models = $this->getMistralModels(); break; default: return response()->json([ 'success' => false, 'message' => 'Provider not supported for automatic model fetching' ]); } return response()->json([ 'success' => true, 'models' => $models ]); } catch (\Exception $e) { return response()->json([ 'success' => false, 'message' => $e->getMessage() ], 500); } } /** * Fetch OpenAI models */ private function getOpenAIModels() { // Get API key from credentials $credential = \App\Models\UserProviderCredential::where('provider', 'openai') ->where('is_active', true) ->first(); if (!$credential || !$credential->api_key) { throw new \Exception('No active OpenAI credentials found. Please add credentials first.'); } $response = \Illuminate\Support\Facades\Http::withHeaders([ 'Authorization' => 'Bearer ' . $credential->api_key, ])->get('https://api.openai.com/v1/models'); if (!$response->successful()) { throw new \Exception('Failed to fetch OpenAI models: ' . $response->body()); } $data = $response->json(); $models = []; foreach ($data['data'] as $model) { if (isset($model['id'])) { $models[] = [ 'id' => $model['id'], 'name' => $model['id'], 'created' => $model['created'] ?? null, ]; } } // Sort by name usort($models, fn($a, $b) => strcmp($a['name'], $b['name'])); return $models; } /** * Fetch Anthropic models from API */ private function getAnthropicModels() { // Get API key from credentials $credential = \App\Models\UserProviderCredential::where('provider', 'anthropic') ->where('is_active', true) ->first(); if (!$credential || !$credential->api_key) { throw new \Exception('No active Anthropic credentials found. Please add credentials first.'); } $response = \Illuminate\Support\Facades\Http::withHeaders([ 'x-api-key' => $credential->api_key, 'anthropic-version' => '2023-06-01', ])->get('https://api.anthropic.com/v1/models'); if (!$response->successful()) { throw new \Exception('Failed to fetch Anthropic models: ' . $response->body()); } $data = $response->json(); $models = []; foreach ($data['data'] as $model) { if (isset($model['id'])) { $models[] = [ 'id' => $model['id'], 'name' => $model['display_name'] ?? $model['id'], 'created' => isset($model['created_at']) ? strtotime($model['created_at']) : null, ]; } } // Sort by name usort($models, fn($a, $b) => strcmp($a['name'], $b['name'])); return $models; } /** * Fetch DeepSeek models from API */ private function getDeepSeekModels() { // Get API key from credentials $credential = \App\Models\UserProviderCredential::where('provider', 'deepseek') ->where('is_active', true) ->first(); if (!$credential || !$credential->api_key) { throw new \Exception('No active DeepSeek credentials found. Please add credentials first.'); } // DeepSeek uses OpenAI-compatible API $response = \Illuminate\Support\Facades\Http::withHeaders([ 'Authorization' => 'Bearer ' . $credential->api_key, ])->get('https://api.deepseek.com/models'); if (!$response->successful()) { throw new \Exception('Failed to fetch DeepSeek models: ' . $response->body()); } $data = $response->json(); $models = []; foreach ($data['data'] as $model) { if (isset($model['id'])) { $models[] = [ 'id' => $model['id'], 'name' => $model['id'], 'created' => $model['created'] ?? null, ]; } } // Sort by name usort($models, fn($a, $b) => strcmp($a['name'], $b['name'])); return $models; } /** * Fetch Google Gemini models from API */ private function getGeminiModels() { // Get API key from credentials $credential = \App\Models\UserProviderCredential::where('provider', 'gemini') ->where('is_active', true) ->first(); if (!$credential || !$credential->api_key) { throw new \Exception('No active Google Gemini credentials found. Please add credentials first.'); } $models = []; $nextPageToken = null; // Fetch all pages do { $url = 'https://generativelanguage.googleapis.com/v1beta/models?key=' . $credential->api_key; if ($nextPageToken) { $url .= '&pageToken=' . $nextPageToken; } $response = \Illuminate\Support\Facades\Http::get($url); if (!$response->successful()) { throw new \Exception('Failed to fetch Gemini models: ' . $response->body()); } $data = $response->json(); foreach ($data['models'] as $model) { // Only include models that support generateContent if (isset($model['name']) && isset($model['supportedGenerationMethods']) && in_array('generateContent', $model['supportedGenerationMethods'])) { // Extract model ID from "models/gemini-xxx" format $modelId = str_replace('models/', '', $model['name']); $models[] = [ 'id' => $modelId, 'name' => $model['displayName'] ?? $modelId, 'created' => null, ]; } } $nextPageToken = $data['nextPageToken'] ?? null; } while ($nextPageToken); // Sort by name usort($models, fn($a, $b) => strcmp($a['name'], $b['name'])); return $models; } /** * Fetch Mistral AI models from API */ private function getMistralModels() { // Get API key from credentials $credential = \App\Models\UserProviderCredential::where('provider', 'mistral') ->where('is_active', true) ->first(); if (!$credential || !$credential->api_key) { throw new \Exception('No active Mistral AI credentials found. Please add credentials first.'); } $response = \Illuminate\Support\Facades\Http::withHeaders([ 'Authorization' => 'Bearer ' . $credential->api_key, ])->get('https://api.mistral.ai/v1/models'); if (!$response->successful()) { throw new \Exception('Failed to fetch Mistral models: ' . $response->body()); } $data = $response->json(); $models = []; $seenIds = []; // Track seen IDs to avoid duplicates (aliases) foreach ($data['data'] as $model) { // Skip deprecated models if (isset($model['deprecation']) && $model['deprecation']) { continue; } // Only include models that support chat completion if (isset($model['id']) && isset($model['capabilities']['completion_chat']) && $model['capabilities']['completion_chat'] === true && !isset($seenIds[$model['id']])) { $seenIds[$model['id']] = true; $models[] = [ 'id' => $model['id'], 'name' => $model['name'] ?? $model['id'], 'created' => $model['created'] ?? null, ]; } } // Sort by name usort($models, fn($a, $b) => strcmp($a['name'], $b['name'])); return $models; } }