header('HX-Request')) { return response('', 200)->header('HX-Redirect', route('system.ai-config.index')); } $configs = AiConfig::orderBy('provider') ->orderByDesc('is_active') ->orderBy('name') ->get(); return view('system.ai-config.index', compact('configs')); } /** * AI 설정 저장 */ public function store(Request $request): JsonResponse { $validated = $request->validate([ 'name' => 'required|string|max:50', 'provider' => 'required|string|in:gemini,claude,openai', 'api_key' => 'required|string|max:255', 'model' => 'required|string|max:100', 'base_url' => 'nullable|string|max:255|url', 'description' => 'nullable|string', 'is_active' => 'boolean', ]); // 활성화 시 동일 provider의 다른 설정 비활성화 if ($validated['is_active'] ?? false) { AiConfig::where('provider', $validated['provider']) ->update(['is_active' => false]); } $config = AiConfig::create($validated); return response()->json([ 'ok' => true, 'message' => '저장되었습니다.', 'data' => $config, ]); } /** * AI 설정 수정 */ public function update(Request $request, int $id): JsonResponse { $config = AiConfig::findOrFail($id); $validated = $request->validate([ 'name' => 'required|string|max:50', 'provider' => 'required|string|in:gemini,claude,openai', 'api_key' => 'required|string|max:255', 'model' => 'required|string|max:100', 'base_url' => 'nullable|string|max:255', 'description' => 'nullable|string', 'is_active' => 'boolean', ]); // 활성화 시 동일 provider의 다른 설정 비활성화 if ($validated['is_active'] ?? false) { AiConfig::where('provider', $validated['provider']) ->where('id', '!=', $id) ->update(['is_active' => false]); } $config->update($validated); return response()->json([ 'ok' => true, 'message' => '수정되었습니다.', 'data' => $config->fresh(), ]); } /** * AI 설정 삭제 */ public function destroy(int $id): JsonResponse { $config = AiConfig::findOrFail($id); $config->delete(); return response()->json([ 'ok' => true, 'message' => '삭제되었습니다.', ]); } /** * AI 설정 활성화/비활성화 토글 */ public function toggle(int $id): JsonResponse { $config = AiConfig::findOrFail($id); if (! $config->is_active) { // 활성화 시 동일 provider의 다른 설정 비활성화 AiConfig::where('provider', $config->provider) ->where('id', '!=', $id) ->update(['is_active' => false]); } $config->update(['is_active' => ! $config->is_active]); return response()->json([ 'ok' => true, 'message' => $config->is_active ? '활성화되었습니다.' : '비활성화되었습니다.', 'data' => $config->fresh(), ]); } /** * API 연결 테스트 */ public function test(Request $request): JsonResponse { $validated = $request->validate([ 'provider' => 'required|string|in:gemini,claude,openai', 'api_key' => 'required|string', 'model' => 'required|string', 'base_url' => 'nullable|string', ]); try { $provider = $validated['provider']; $apiKey = $validated['api_key']; $model = $validated['model']; $baseUrl = $validated['base_url'] ?? AiConfig::DEFAULT_BASE_URLS[$provider]; if ($provider === 'gemini') { $result = $this->testGemini($baseUrl, $model, $apiKey); } else { return response()->json([ 'ok' => false, 'error' => '아직 지원하지 않는 provider입니다.', ]); } return response()->json($result); } catch (\Exception $e) { return response()->json([ 'ok' => false, 'error' => $e->getMessage(), ]); } } /** * Gemini API 테스트 */ private function testGemini(string $baseUrl, string $model, string $apiKey): array { $url = "{$baseUrl}/models/{$model}:generateContent?key={$apiKey}"; $response = \Illuminate\Support\Facades\Http::timeout(10)->post($url, [ 'contents' => [ [ 'parts' => [ ['text' => '안녕하세요. 테스트입니다. "OK"라고만 응답해주세요.'], ], ], ], 'generationConfig' => [ 'temperature' => 0, 'maxOutputTokens' => 10, ], ]); if ($response->successful()) { return [ 'ok' => true, 'message' => '연결 테스트 성공', ]; } return [ 'ok' => false, 'error' => 'API 응답 오류: ' . $response->status(), ]; } }