feat: [additional] Notion 검색 기능 추가
- NotionService: Notion API 검색 + Gemini AI 답변 - AiConfig에 notion provider 추가 - 추가기능 > Notion 검색 채팅 UI
This commit is contained in:
@@ -48,7 +48,7 @@ public function store(Request $request): JsonResponse
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'name' => 'required|string|max:50',
|
||||
'provider' => 'required|string|in:gemini,claude,openai,gcs',
|
||||
'provider' => 'required|string|in:gemini,claude,openai,gcs,notion',
|
||||
'api_key' => 'nullable|string|max:255',
|
||||
'model' => 'nullable|string|max:100',
|
||||
'base_url' => 'nullable|string|max:255',
|
||||
@@ -108,7 +108,7 @@ public function update(Request $request, int $id): JsonResponse
|
||||
|
||||
$validated = $request->validate([
|
||||
'name' => 'required|string|max:50',
|
||||
'provider' => 'required|string|in:gemini,claude,openai,gcs',
|
||||
'provider' => 'required|string|in:gemini,claude,openai,gcs,notion',
|
||||
'api_key' => 'nullable|string|max:255',
|
||||
'model' => 'nullable|string|max:100',
|
||||
'base_url' => 'nullable|string|max:255',
|
||||
@@ -203,7 +203,7 @@ public function toggle(int $id): JsonResponse
|
||||
public function test(Request $request): JsonResponse
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'provider' => 'required|string|in:gemini,claude,openai',
|
||||
'provider' => 'required|string|in:gemini,claude,openai,notion',
|
||||
'api_key' => 'nullable|string',
|
||||
'model' => 'required|string',
|
||||
'base_url' => 'nullable|string',
|
||||
@@ -279,7 +279,7 @@ private function testGemini(string $baseUrl, string $model, string $apiKey): arr
|
||||
|
||||
return [
|
||||
'ok' => false,
|
||||
'error' => 'API 응답 오류: ' . $response->status(),
|
||||
'error' => 'API 응답 오류: '.$response->status(),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -297,19 +297,19 @@ private function testGeminiVertexAi(string $model, string $projectId, string $re
|
||||
return ['ok' => false, 'error' => '서비스 계정 파일 경로가 필요합니다.'];
|
||||
}
|
||||
|
||||
if (!file_exists($serviceAccountPath)) {
|
||||
if (! file_exists($serviceAccountPath)) {
|
||||
return ['ok' => false, 'error' => "서비스 계정 파일을 찾을 수 없습니다: {$serviceAccountPath}"];
|
||||
}
|
||||
|
||||
// 서비스 계정 JSON 로드
|
||||
$serviceAccount = json_decode(file_get_contents($serviceAccountPath), true);
|
||||
if (!$serviceAccount || empty($serviceAccount['client_email']) || empty($serviceAccount['private_key'])) {
|
||||
if (! $serviceAccount || empty($serviceAccount['client_email']) || empty($serviceAccount['private_key'])) {
|
||||
return ['ok' => false, 'error' => '서비스 계정 파일 형식이 올바르지 않습니다.'];
|
||||
}
|
||||
|
||||
// OAuth 토큰 획득
|
||||
$accessToken = $this->getVertexAiAccessToken($serviceAccount);
|
||||
if (!$accessToken) {
|
||||
if (! $accessToken) {
|
||||
return ['ok' => false, 'error' => 'OAuth 토큰 획득 실패. 서비스 계정 권한을 확인하세요.'];
|
||||
}
|
||||
|
||||
@@ -318,7 +318,7 @@ private function testGeminiVertexAi(string $model, string $projectId, string $re
|
||||
|
||||
$response = \Illuminate\Support\Facades\Http::timeout(30)
|
||||
->withHeaders([
|
||||
'Authorization' => 'Bearer ' . $accessToken,
|
||||
'Authorization' => 'Bearer '.$accessToken,
|
||||
'Content-Type' => 'application/json',
|
||||
])
|
||||
->post($url, [
|
||||
@@ -345,7 +345,7 @@ private function testGeminiVertexAi(string $model, string $projectId, string $re
|
||||
|
||||
// 상세 오류 메시지 추출
|
||||
$errorBody = $response->json();
|
||||
$errorMsg = $errorBody['error']['message'] ?? ('HTTP ' . $response->status());
|
||||
$errorMsg = $errorBody['error']['message'] ?? ('HTTP '.$response->status());
|
||||
|
||||
return [
|
||||
'ok' => false,
|
||||
@@ -369,16 +369,16 @@ private function getVertexAiAccessToken(array $serviceAccount): ?string
|
||||
]));
|
||||
|
||||
$privateKey = openssl_pkey_get_private($serviceAccount['private_key']);
|
||||
if (!$privateKey) {
|
||||
if (! $privateKey) {
|
||||
return null;
|
||||
}
|
||||
|
||||
openssl_sign($jwtHeader . '.' . $jwtClaim, $signature, $privateKey, OPENSSL_ALGO_SHA256);
|
||||
openssl_sign($jwtHeader.'.'.$jwtClaim, $signature, $privateKey, OPENSSL_ALGO_SHA256);
|
||||
if (PHP_VERSION_ID < 80000) {
|
||||
openssl_free_key($privateKey);
|
||||
}
|
||||
|
||||
$jwt = $jwtHeader . '.' . $jwtClaim . '.' . $this->base64UrlEncode($signature);
|
||||
$jwt = $jwtHeader.'.'.$jwtClaim.'.'.$this->base64UrlEncode($signature);
|
||||
|
||||
$response = \Illuminate\Support\Facades\Http::asForm()->post('https://oauth2.googleapis.com/token', [
|
||||
'grant_type' => 'urn:ietf:params:oauth:grant-type:jwt-bearer',
|
||||
@@ -408,13 +408,13 @@ public function testGcs(Request $request): JsonResponse
|
||||
$serviceAccount = null;
|
||||
|
||||
// 서비스 계정 로드 (JSON 직접 입력 또는 파일 경로)
|
||||
if (!empty($validated['service_account_json'])) {
|
||||
if (! empty($validated['service_account_json'])) {
|
||||
$serviceAccount = $validated['service_account_json'];
|
||||
} elseif (!empty($validated['service_account_path']) && file_exists($validated['service_account_path'])) {
|
||||
} elseif (! empty($validated['service_account_path']) && file_exists($validated['service_account_path'])) {
|
||||
$serviceAccount = json_decode(file_get_contents($validated['service_account_path']), true);
|
||||
}
|
||||
|
||||
if (!$serviceAccount) {
|
||||
if (! $serviceAccount) {
|
||||
return response()->json([
|
||||
'ok' => false,
|
||||
'error' => '서비스 계정 정보를 찾을 수 없습니다.',
|
||||
@@ -423,7 +423,7 @@ public function testGcs(Request $request): JsonResponse
|
||||
|
||||
// OAuth 토큰 획득
|
||||
$accessToken = $this->getGcsAccessToken($serviceAccount);
|
||||
if (!$accessToken) {
|
||||
if (! $accessToken) {
|
||||
return response()->json([
|
||||
'ok' => false,
|
||||
'error' => 'OAuth 토큰 획득 실패',
|
||||
@@ -432,7 +432,7 @@ public function testGcs(Request $request): JsonResponse
|
||||
|
||||
// 버킷 존재 확인
|
||||
$response = \Illuminate\Support\Facades\Http::timeout(10)
|
||||
->withHeaders(['Authorization' => 'Bearer ' . $accessToken])
|
||||
->withHeaders(['Authorization' => 'Bearer '.$accessToken])
|
||||
->get("https://storage.googleapis.com/storage/v1/b/{$bucketName}");
|
||||
|
||||
if ($response->successful()) {
|
||||
@@ -444,7 +444,7 @@ public function testGcs(Request $request): JsonResponse
|
||||
|
||||
return response()->json([
|
||||
'ok' => false,
|
||||
'error' => '버킷 접근 실패: ' . $response->status(),
|
||||
'error' => '버킷 접근 실패: '.$response->status(),
|
||||
]);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
@@ -467,24 +467,24 @@ private function getGcsAccessToken(array $serviceAccount): ?string
|
||||
'scope' => 'https://www.googleapis.com/auth/devstorage.full_control',
|
||||
'aud' => 'https://oauth2.googleapis.com/token',
|
||||
'exp' => $now + 3600,
|
||||
'iat' => $now
|
||||
'iat' => $now,
|
||||
]));
|
||||
|
||||
$privateKey = openssl_pkey_get_private($serviceAccount['private_key']);
|
||||
if (!$privateKey) {
|
||||
if (! $privateKey) {
|
||||
return null;
|
||||
}
|
||||
|
||||
openssl_sign($jwtHeader . '.' . $jwtClaim, $signature, $privateKey, OPENSSL_ALGO_SHA256);
|
||||
openssl_sign($jwtHeader.'.'.$jwtClaim, $signature, $privateKey, OPENSSL_ALGO_SHA256);
|
||||
if (PHP_VERSION_ID < 80000) {
|
||||
openssl_free_key($privateKey);
|
||||
}
|
||||
|
||||
$jwt = $jwtHeader . '.' . $jwtClaim . '.' . $this->base64UrlEncode($signature);
|
||||
$jwt = $jwtHeader.'.'.$jwtClaim.'.'.$this->base64UrlEncode($signature);
|
||||
|
||||
$response = \Illuminate\Support\Facades\Http::asForm()->post('https://oauth2.googleapis.com/token', [
|
||||
'grant_type' => 'urn:ietf:params:oauth:grant-type:jwt-bearer',
|
||||
'assertion' => $jwt
|
||||
'assertion' => $jwt,
|
||||
]);
|
||||
|
||||
if ($response->successful()) {
|
||||
|
||||
Reference in New Issue
Block a user