has('provider') && $request->provider) { $query->where('provider', $request->provider); } // Filter by user if ($request->has('user_id') && $request->user_id) { $query->where('user_id', $request->user_id); } // Filter by status if ($request->has('status')) { switch ($request->status) { case 'active': $query->where('is_active', true); break; case 'inactive': $query->where('is_active', false); break; } } // Search if ($request->has('search') && $request->search) { $query->whereHas('user', function ($q) use ($request) { $q->where('name', 'like', '%' . $request->search . '%') ->orWhere('email', 'like', '%' . $request->search . '%'); }); } // Sort $sortBy = $request->get('sort_by', 'created_at'); $sortOrder = $request->get('sort_order', 'desc'); $query->orderBy($sortBy, $sortOrder); $credentials = $query->paginate(20)->withQueryString(); // Get all users and providers for filters $users = User::orderBy('name')->get(); $providers = ['openai', 'anthropic', 'mistral', 'gemini', 'deepseek']; return view('admin.credentials.index', compact('credentials', 'users', 'providers')); } /** * Show the form for creating a new credential. */ public function create() { $users = User::orderBy('name')->get(); $providers = [ 'openai' => 'OpenAI', 'anthropic' => 'Anthropic (Claude)', 'mistral' => 'Mistral AI', 'gemini' => 'Google Gemini', 'deepseek' => 'DeepSeek' ]; return view('admin.credentials.create', compact('users', 'providers')); } /** * Store a newly created credential. */ public function store(Request $request) { $validated = $request->validate([ 'user_id' => 'required|exists:users,id', 'provider' => 'required|in:openai,anthropic,mistral,gemini,deepseek', 'api_key' => 'required|string|min:10', 'organization_id' => 'nullable|string|max:255', 'is_active' => 'boolean', ]); try { // Check for duplicate $existing = UserProviderCredential::where('user_id', $validated['user_id']) ->where('provider', $validated['provider']) ->first(); if ($existing) { return back() ->withInput() ->with('error', 'This user already has credentials for ' . ucfirst($validated['provider']) . '. Please edit the existing credentials instead.'); } // Create credential (encryption happens automatically in model) $credential = UserProviderCredential::create([ 'user_id' => $validated['user_id'], 'provider' => $validated['provider'], 'api_key' => $validated['api_key'], 'organization_id' => $validated['organization_id'] ?? null, 'is_active' => $validated['is_active'] ?? true, ]); return redirect()->route('admin.credentials.index') ->with('success', 'Provider credentials added successfully!'); } catch (\Exception $e) { Log::error('Failed to create provider credential', [ 'error' => $e->getMessage(), 'user_id' => $validated['user_id'], 'provider' => $validated['provider'] ]); return back() ->withInput() ->with('error', 'Failed to add credentials: ' . $e->getMessage()); } } /** * Display the specified credential. */ public function show(UserProviderCredential $credential) { $credential->load('user'); // Get usage statistics $stats = [ 'total_requests' => $credential->user->llmRequests() ->where('provider', $credential->provider) ->count(), 'total_cost' => $credential->user->llmRequests() ->where('provider', $credential->provider) ->sum('total_cost'), 'total_tokens' => $credential->user->llmRequests() ->where('provider', $credential->provider) ->sum('total_tokens'), 'last_30_days_requests' => $credential->user->llmRequests() ->where('provider', $credential->provider) ->where('created_at', '>=', now()->subDays(30)) ->count(), ]; return view('admin.credentials.show', compact('credential', 'stats')); } /** * Show the form for editing the specified credential. */ public function edit(UserProviderCredential $credential) { $credential->load('user'); $providers = [ 'openai' => 'OpenAI', 'anthropic' => 'Anthropic (Claude)', 'mistral' => 'Mistral AI', 'gemini' => 'Google Gemini', 'deepseek' => 'DeepSeek' ]; return view('admin.credentials.edit', compact('credential', 'providers')); } /** * Update the specified credential. */ public function update(Request $request, UserProviderCredential $credential) { $validated = $request->validate([ 'api_key' => 'nullable|string|min:10', 'organization_id' => 'nullable|string|max:255', 'is_active' => 'boolean', ]); try { // Only update API key if provided if (!empty($validated['api_key'])) { $credential->api_key = $validated['api_key']; } $credential->organization_id = $validated['organization_id'] ?? null; $credential->is_active = $validated['is_active'] ?? true; $credential->save(); return redirect()->route('admin.credentials.index') ->with('success', 'Provider credentials updated successfully!'); } catch (\Exception $e) { Log::error('Failed to update provider credential', [ 'error' => $e->getMessage(), 'credential_id' => $credential->id ]); return back() ->withInput() ->with('error', 'Failed to update credentials: ' . $e->getMessage()); } } /** * Remove the specified credential. */ public function destroy(UserProviderCredential $credential) { try { $provider = ucfirst($credential->provider); $userName = $credential->user->name; $credential->delete(); return redirect()->route('admin.credentials.index') ->with('success', "Provider credentials for {$provider} (User: {$userName}) deleted successfully!"); } catch (\Exception $e) { Log::error('Failed to delete provider credential', [ 'error' => $e->getMessage(), 'credential_id' => $credential->id ]); return back() ->with('error', 'Failed to delete credentials: ' . $e->getMessage()); } } /** * Test the API key validity. */ public function test(UserProviderCredential $credential) { try { $result = $this->testProviderApiKey($credential->provider, $credential->api_key); if ($result['success']) { return response()->json([ 'success' => true, 'message' => 'API key is valid and working!', 'details' => $result['details'] ?? null ]); } else { return response()->json([ 'success' => false, 'message' => $result['message'] ?? 'API key validation failed', 'error' => $result['error'] ?? null ], 400); } } catch (\Exception $e) { Log::error('Failed to test provider credential', [ 'error' => $e->getMessage(), 'credential_id' => $credential->id ]); return response()->json([ 'success' => false, 'message' => 'Test failed: ' . $e->getMessage() ], 500); } } /** * Test provider API key validity */ private function testProviderApiKey(string $provider, string $apiKey): array { switch ($provider) { case 'openai': return $this->testOpenAI($apiKey); case 'anthropic': return $this->testAnthropic($apiKey); case 'mistral': return $this->testMistral($apiKey); case 'gemini': return $this->testGemini($apiKey); case 'deepseek': return $this->testDeepSeek($apiKey); default: return [ 'success' => false, 'message' => 'Unsupported provider' ]; } } private function testOpenAI(string $apiKey): array { try { $response = Http::withHeaders([ 'Authorization' => 'Bearer ' . $apiKey, 'Content-Type' => 'application/json', ])->timeout(10)->post('https://api.openai.com/v1/chat/completions', [ 'model' => 'gpt-3.5-turbo', 'messages' => [ ['role' => 'user', 'content' => 'test'] ], 'max_tokens' => 5 ]); if ($response->successful()) { return [ 'success' => true, 'details' => 'Model: gpt-3.5-turbo accessible' ]; } return [ 'success' => false, 'message' => 'Invalid API key or insufficient permissions', 'error' => $response->body() ]; } catch (\Exception $e) { return [ 'success' => false, 'message' => 'Connection failed', 'error' => $e->getMessage() ]; } } private function testAnthropic(string $apiKey): array { try { $response = Http::withHeaders([ 'x-api-key' => $apiKey, 'anthropic-version' => '2023-06-01', 'Content-Type' => 'application/json', ])->timeout(10)->post('https://api.anthropic.com/v1/messages', [ 'model' => 'claude-3-haiku-20240307', 'max_tokens' => 10, 'messages' => [ ['role' => 'user', 'content' => 'test'] ] ]); if ($response->successful()) { return [ 'success' => true, 'details' => 'Model: Claude 3 Haiku accessible' ]; } return [ 'success' => false, 'message' => 'Invalid API key or insufficient permissions', 'error' => $response->body() ]; } catch (\Exception $e) { return [ 'success' => false, 'message' => 'Connection failed', 'error' => $e->getMessage() ]; } } private function testMistral(string $apiKey): array { try { $response = Http::withHeaders([ 'Authorization' => 'Bearer ' . $apiKey, 'Content-Type' => 'application/json', ])->timeout(10)->post('https://api.mistral.ai/v1/chat/completions', [ 'model' => 'mistral-tiny', 'messages' => [ ['role' => 'user', 'content' => 'test'] ], 'max_tokens' => 5 ]); if ($response->successful()) { return [ 'success' => true, 'details' => 'Model: Mistral Tiny accessible' ]; } return [ 'success' => false, 'message' => 'Invalid API key or insufficient permissions', 'error' => $response->body() ]; } catch (\Exception $e) { return [ 'success' => false, 'message' => 'Connection failed', 'error' => $e->getMessage() ]; } } private function testGemini(string $apiKey): array { try { $response = Http::timeout(10) ->post("https://generativelanguage.googleapis.com/v1beta/models/gemini-pro:generateContent?key={$apiKey}", [ 'contents' => [ ['parts' => [['text' => 'test']]] ] ]); if ($response->successful()) { return [ 'success' => true, 'details' => 'Model: Gemini Pro accessible' ]; } return [ 'success' => false, 'message' => 'Invalid API key or insufficient permissions', 'error' => $response->body() ]; } catch (\Exception $e) { return [ 'success' => false, 'message' => 'Connection failed', 'error' => $e->getMessage() ]; } } private function testDeepSeek(string $apiKey): array { try { $response = Http::withHeaders([ 'Authorization' => 'Bearer ' . $apiKey, 'Content-Type' => 'application/json', ])->timeout(10)->post('https://api.deepseek.com/v1/chat/completions', [ 'model' => 'deepseek-chat', 'messages' => [ ['role' => 'user', 'content' => 'test'] ], 'max_tokens' => 5 ]); if ($response->successful()) { return [ 'success' => true, 'details' => 'Model: DeepSeek Chat accessible' ]; } return [ 'success' => false, 'message' => 'Invalid API key or insufficient permissions', 'error' => $response->body() ]; } catch (\Exception $e) { return [ 'success' => false, 'message' => 'Connection failed', 'error' => $e->getMessage() ]; } } }