feat: [ai-config] Notion API 설정 UI 추가
- AI 설정 페이지에 Notion 섹션 추가 (추가/수정/삭제) - AiConfig에 API_SERVICE_PROVIDERS 상수 분리 - Notion 전용 모달 (API 키, API 버전, 활성화)
This commit is contained in:
@@ -34,10 +34,18 @@ public function index(Request $request): View|Response
|
||||
->orderBy('name')
|
||||
->get();
|
||||
|
||||
// API 서비스 설정 (notion)
|
||||
$apiServiceConfigs = AiConfig::whereIn('provider', AiConfig::API_SERVICE_PROVIDERS)
|
||||
->orderBy('provider')
|
||||
->orderByDesc('is_active')
|
||||
->orderBy('name')
|
||||
->get();
|
||||
|
||||
return view('system.ai-config.index', [
|
||||
'configs' => $aiConfigs,
|
||||
'aiConfigs' => $aiConfigs,
|
||||
'storageConfigs' => $storageConfigs,
|
||||
'apiServiceConfigs' => $apiServiceConfigs,
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -69,13 +69,18 @@ class AiConfig extends Model
|
||||
/**
|
||||
* AI Provider 목록 (GCS 제외)
|
||||
*/
|
||||
public const AI_PROVIDERS = ['gemini', 'claude', 'openai', 'notion'];
|
||||
public const AI_PROVIDERS = ['gemini', 'claude', 'openai'];
|
||||
|
||||
/**
|
||||
* 스토리지 Provider 목록
|
||||
*/
|
||||
public const STORAGE_PROVIDERS = ['gcs'];
|
||||
|
||||
/**
|
||||
* API 서비스 Provider 목록
|
||||
*/
|
||||
public const API_SERVICE_PROVIDERS = ['notion'];
|
||||
|
||||
/**
|
||||
* 활성화된 Gemini 설정 조회
|
||||
*/
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
.provider-gemini { background: #e8f0fe; color: #1a73e8; }
|
||||
.provider-claude { background: #fef3e8; color: #d97706; }
|
||||
.provider-openai { background: #e8f8e8; color: #16a34a; }
|
||||
.provider-notion { background: #f3f0ff; color: #7c3aed; }
|
||||
|
||||
.status-badge {
|
||||
display: inline-flex;
|
||||
@@ -239,6 +240,79 @@
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- API 서비스 설정 (Notion) 섹션 -->
|
||||
<div>
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<div class="flex items-center gap-2">
|
||||
<svg class="w-5 h-5 text-purple-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
|
||||
</svg>
|
||||
<h2 class="text-lg font-semibold text-gray-800">API 서비스 설정 (Notion)</h2>
|
||||
</div>
|
||||
<button type="button" onclick="openNotionModal()" class="px-3 py-1.5 bg-purple-600 hover:bg-purple-700 text-white text-sm rounded-lg transition inline-flex items-center gap-1.5">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" />
|
||||
</svg>
|
||||
Notion 설정 추가
|
||||
</button>
|
||||
</div>
|
||||
<!-- Notion 설정 목록 -->
|
||||
<div id="notion-config-list" class="space-y-4">
|
||||
@forelse($apiServiceConfigs as $config)
|
||||
<div class="config-card" data-id="{{ $config->id }}">
|
||||
<div class="flex items-start justify-between">
|
||||
<div class="flex-1">
|
||||
<div class="flex items-center gap-3 mb-2">
|
||||
<h3 class="font-semibold text-lg text-gray-800">{{ $config->name }}</h3>
|
||||
<span class="provider-badge provider-notion">Notion</span>
|
||||
<span class="status-badge {{ $config->is_active ? 'status-active' : 'status-inactive' }}">
|
||||
{{ $config->status_label }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="text-sm text-gray-600 space-y-1">
|
||||
<p><span class="font-medium">API 버전:</span> {{ $config->model }}</p>
|
||||
<p><span class="font-medium">API 키:</span> {{ $config->masked_api_key }}</p>
|
||||
@if($config->description)
|
||||
<p><span class="font-medium">설명:</span> {{ $config->description }}</p>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<button type="button" onclick="toggleConfig({{ $config->id }})" class="px-3 py-1.5 text-sm {{ $config->is_active ? 'bg-yellow-100 hover:bg-yellow-200 text-yellow-700' : 'bg-green-100 hover:bg-green-200 text-green-700' }} rounded-lg transition">
|
||||
{{ $config->is_active ? '비활성화' : '활성화' }}
|
||||
</button>
|
||||
<button type="button" data-config='@json($config)' onclick="editNotionConfig(this)" class="px-3 py-1.5 text-sm bg-blue-100 hover:bg-blue-200 text-blue-700 rounded-lg transition">
|
||||
수정
|
||||
</button>
|
||||
<button type="button" onclick="deleteConfig({{ $config->id }}, '{{ $config->name }}')" class="px-3 py-1.5 text-sm bg-red-100 hover:bg-red-200 text-red-700 rounded-lg transition">
|
||||
삭제
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@empty
|
||||
<div class="text-center py-12 bg-white rounded-lg shadow-sm">
|
||||
<svg class="w-12 h-12 mx-auto text-gray-300 mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
|
||||
</svg>
|
||||
<p class="text-gray-500">등록된 Notion 설정이 없습니다.</p>
|
||||
<p class="text-sm text-gray-400 mt-1">'Notion 설정 추가' 버튼을 클릭하여 Notion API를 등록하세요.</p>
|
||||
</div>
|
||||
@endforelse
|
||||
</div>
|
||||
|
||||
<!-- Notion 사용 안내 -->
|
||||
<div class="mt-6 bg-purple-50 border border-purple-200 rounded-lg p-4">
|
||||
<h3 class="font-medium text-purple-800 mb-2">Notion API 사용 안내</h3>
|
||||
<ul class="text-sm text-purple-700 space-y-1">
|
||||
<li>Notion 검색 기능(추가기능 > Notion 검색)에서 사용됩니다.</li>
|
||||
<li><a href="https://www.notion.so/my-integrations" target="_blank" class="underline font-medium">Notion Integrations</a>에서 Internal Integration을 생성하고 API 키를 발급받으세요.</li>
|
||||
<li>검색할 페이지/데이터베이스에 Integration을 연결(Share)해야 합니다.</li>
|
||||
<li>Gemini AI 설정이 활성화되어 있어야 검색어 정제 및 AI 답변이 동작합니다.</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- GCS 추가/수정 모달 -->
|
||||
@@ -312,6 +386,60 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Notion 추가/수정 모달 -->
|
||||
<div id="notion-modal" class="modal-overlay hidden">
|
||||
<div class="modal-content">
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<h2 id="notion-modal-title" class="text-xl font-bold text-gray-800">Notion 설정 추가</h2>
|
||||
<button type="button" onclick="closeNotionModal()" class="text-gray-400 hover:text-gray-600">
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<form id="notion-form" class="space-y-4">
|
||||
<input type="hidden" id="notion-config-id" value="">
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">설정 이름 <span class="text-red-500">*</span></label>
|
||||
<input type="text" id="notion-name" required placeholder="예: Notion Production" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-purple-500">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">API 키 <span class="text-red-500">*</span></label>
|
||||
<input type="password" id="notion-api-key" required placeholder="ntn_... 또는 secret_..." class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-purple-500">
|
||||
<p class="mt-1 text-xs text-gray-500">Notion Integrations에서 발급받은 Internal Integration Token</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">API 버전</label>
|
||||
<input type="text" id="notion-api-version" placeholder="2025-09-03" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-purple-500">
|
||||
<p class="mt-1 text-xs text-gray-500">기본값: 2025-09-03 (비워두면 기본값 사용)</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">설명 (선택)</label>
|
||||
<textarea id="notion-description" rows="2" placeholder="설정에 대한 설명" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-purple-500"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center">
|
||||
<input type="checkbox" id="notion-is-active" class="w-4 h-4 text-purple-600 border-gray-300 rounded focus:ring-purple-500">
|
||||
<label for="notion-is-active" class="ml-2 text-sm text-gray-700">활성화 (기존 Notion 설정은 비활성화됩니다)</label>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end gap-3 pt-4 border-t">
|
||||
<button type="button" onclick="closeNotionModal()" class="px-4 py-2 border border-gray-300 text-gray-700 rounded-lg hover:bg-gray-50 transition">
|
||||
취소
|
||||
</button>
|
||||
<button type="submit" class="px-4 py-2 bg-purple-600 hover:bg-purple-700 text-white rounded-lg transition">
|
||||
저장
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 추가/수정 모달 -->
|
||||
<div id="config-modal" class="modal-overlay hidden">
|
||||
<div class="modal-content">
|
||||
@@ -912,6 +1040,97 @@ function toggleGcsAuthType(type) {
|
||||
}
|
||||
}
|
||||
|
||||
// === Notion 설정 관련 함수들 ===
|
||||
|
||||
// Notion 모달 열기
|
||||
window.openNotionModal = function(config) {
|
||||
const modal = document.getElementById('notion-modal');
|
||||
const title = document.getElementById('notion-modal-title');
|
||||
|
||||
if (config) {
|
||||
title.textContent = 'Notion 설정 수정';
|
||||
document.getElementById('notion-config-id').value = config.id;
|
||||
document.getElementById('notion-name').value = config.name;
|
||||
document.getElementById('notion-api-key').value = config.api_key || '';
|
||||
document.getElementById('notion-api-version').value = config.model || '';
|
||||
document.getElementById('notion-description').value = config.description || '';
|
||||
document.getElementById('notion-is-active').checked = config.is_active;
|
||||
} else {
|
||||
title.textContent = 'Notion 설정 추가';
|
||||
document.getElementById('notion-form').reset();
|
||||
document.getElementById('notion-config-id').value = '';
|
||||
}
|
||||
|
||||
modal.classList.remove('hidden');
|
||||
};
|
||||
|
||||
// Notion 모달 닫기
|
||||
window.closeNotionModal = function() {
|
||||
document.getElementById('notion-modal').classList.add('hidden');
|
||||
};
|
||||
|
||||
// Notion 수정
|
||||
window.editNotionConfig = function(btn) {
|
||||
try {
|
||||
const config = JSON.parse(btn.dataset.config);
|
||||
window.openNotionModal(config);
|
||||
} catch (e) {
|
||||
console.error('Config parse error:', e);
|
||||
showToast('설정 데이터를 불러올 수 없습니다.', 'error');
|
||||
}
|
||||
};
|
||||
|
||||
// Notion 폼 제출
|
||||
async function handleNotionFormSubmit(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const id = document.getElementById('notion-config-id').value;
|
||||
const apiKey = document.getElementById('notion-api-key').value;
|
||||
|
||||
if (!apiKey) {
|
||||
showToast('API 키를 입력해주세요.', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
const data = {
|
||||
provider: 'notion',
|
||||
name: document.getElementById('notion-name').value,
|
||||
api_key: apiKey,
|
||||
model: document.getElementById('notion-api-version').value || '2025-09-03',
|
||||
description: document.getElementById('notion-description').value || null,
|
||||
is_active: document.getElementById('notion-is-active').checked,
|
||||
options: {},
|
||||
};
|
||||
|
||||
try {
|
||||
const url = id
|
||||
? `{{ url('system/ai-config') }}/${id}`
|
||||
: '{{ route("system.ai-config.store") }}';
|
||||
const method = id ? 'PUT' : 'POST';
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: method,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRF-TOKEN': csrfToken
|
||||
},
|
||||
body: JSON.stringify(data)
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (result.ok) {
|
||||
showToast(result.message, 'success');
|
||||
window.closeNotionModal();
|
||||
location.reload();
|
||||
} else {
|
||||
showToast(result.message || '저장 실패', 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
showToast('저장 중 오류가 발생했습니다.', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// DOM 로드 후 이벤트 리스너 등록
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// 페이지 로드 시 모달 강제 닫기
|
||||
@@ -923,6 +1142,10 @@ function toggleGcsAuthType(type) {
|
||||
if (gcsModal) {
|
||||
gcsModal.classList.add('hidden');
|
||||
}
|
||||
const notionModal = document.getElementById('notion-modal');
|
||||
if (notionModal) {
|
||||
notionModal.classList.add('hidden');
|
||||
}
|
||||
|
||||
// Provider 변경 시 기본 모델 업데이트 및 UI 전환
|
||||
const providerEl = document.getElementById('config-provider');
|
||||
@@ -969,6 +1192,12 @@ function toggleGcsAuthType(type) {
|
||||
});
|
||||
}
|
||||
|
||||
// Notion 폼 제출
|
||||
const notionFormEl = document.getElementById('notion-form');
|
||||
if (notionFormEl) {
|
||||
notionFormEl.addEventListener('submit', handleNotionFormSubmit);
|
||||
}
|
||||
|
||||
// 모달 외부 클릭 시 닫지 않음 (의도치 않은 닫힘 방지)
|
||||
// 닫기 버튼이나 취소 버튼으로만 닫을 수 있음
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user