- URL 하드코딩 → .env APP_URL 기반 동적 URL로 변경 - DB 연결 하드코딩 → .env 기반으로 변경 - MySQL strict mode DATE 오류 수정
181 lines
6.6 KiB
PHP
181 lines
6.6 KiB
PHP
<?php
|
|
require_once($_SERVER['DOCUMENT_ROOT'] . "/session.php");
|
|
header('Content-Type: application/json; charset=utf-8');
|
|
|
|
// POST 요청만 처리
|
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
|
echo json_encode(['error' => 'Invalid request method']);
|
|
exit;
|
|
}
|
|
|
|
// JSON 입력 받기
|
|
$input = json_decode(file_get_contents('php://input'), true);
|
|
$userMessage = $input['message'] ?? '';
|
|
$history = $input['history'] ?? [];
|
|
|
|
if (empty($userMessage)) {
|
|
echo json_encode(['error' => 'Empty message']);
|
|
exit;
|
|
}
|
|
|
|
// API 키 설정
|
|
$notionApiKeyPath = $_SERVER['DOCUMENT_ROOT'] . "/apikey/notion.txt";
|
|
$googleApiKeyPath = $_SERVER['DOCUMENT_ROOT'] . "/apikey/google_vertex_api.txt";
|
|
|
|
if (!file_exists($notionApiKeyPath) || !file_exists($googleApiKeyPath)) {
|
|
echo json_encode(['reply' => "시스템 설정 오류: API 키 파일을 찾을 수 없습니다."]);
|
|
exit;
|
|
}
|
|
|
|
$notionApiKey = trim(file_get_contents($notionApiKeyPath));
|
|
$googleApiKey = trim(file_get_contents($googleApiKeyPath));
|
|
|
|
require_once 'notion_client.php';
|
|
|
|
try {
|
|
// 0. 검색어 정제 및 확장 (Query Refinement)
|
|
// 항상 Gemini를 이용해 오타 수정 및 Notion 검색에 적합한 키워드로 변환합니다.
|
|
$refineSystemInstruction = "You are a detailed search query generator for a Notion database.
|
|
Analyze the [Current Question] and [Conversation History] (if any).
|
|
1. Correct any typos (e.g., '프론트엔디' -> '프론트엔드').
|
|
2. Identify the core topic or entities.
|
|
3. IGNORE standard greetings (e.g., 'Hello', '운영자 문서 탐색').
|
|
4. Convert the intent into a precise search query likely to match Notion page titles or content.
|
|
RETURN ONLY THE SEARCH QUERY STRING. Do not explain.";
|
|
|
|
$historyText = "";
|
|
if (!empty($history)) {
|
|
foreach ($history as $msg) {
|
|
$role = $msg['role'] === 'user' ? "User" : "Assistant";
|
|
$text = is_array($msg['parts']) ? $msg['parts'][0]['text'] : $msg['parts'];
|
|
$historyText .= "$role: $text\n";
|
|
}
|
|
}
|
|
|
|
$refineData = [
|
|
'contents' => [
|
|
['parts' => [['text' => "Conversation History:\n$historyText\n\nCurrent Question: $userMessage"]]]
|
|
],
|
|
'systemInstruction' => [
|
|
'parts' => [['text' => $refineSystemInstruction]]
|
|
]
|
|
];
|
|
|
|
$ch = curl_init();
|
|
curl_setopt($ch, CURLOPT_URL, "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key=" . $googleApiKey);
|
|
curl_setopt($ch, CURLOPT_POST, true);
|
|
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
|
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
|
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($refineData));
|
|
$refineResponse = curl_exec($ch);
|
|
curl_close($ch);
|
|
|
|
$refineData = json_decode($refineResponse, true);
|
|
$refinedQuery = $userMessage; // 기본값
|
|
if (isset($refineData['candidates'][0]['content']['parts'][0]['text'])) {
|
|
$refinedQuery = trim($refineData['candidates'][0]['content']['parts'][0]['text']);
|
|
}
|
|
|
|
// 1. Notion 검색 및 컨텍스트 확보 (정제된 쿼리 사용)
|
|
$notion = new NotionClient($notionApiKey);
|
|
$searchResults = $notion->search($refinedQuery);
|
|
|
|
$context = "";
|
|
if ($searchResults && isset($searchResults['results'])) {
|
|
foreach ($searchResults['results'] as $page) {
|
|
$title = "제목 없음";
|
|
if (isset($page['properties']['Name']['title'][0]['plain_text'])) {
|
|
$title = $page['properties']['Name']['title'][0]['plain_text'];
|
|
} elseif (isset($page['properties']['title']['title'][0]['plain_text'])) {
|
|
$title = $page['properties']['title']['title'][0]['plain_text'];
|
|
}
|
|
|
|
$pageContent = $notion->getPageContent($page['id']);
|
|
$url = $page['url'] ?? '';
|
|
$context .= "문서 제목: [{$title}]\nURL: {$url}\n내용:\n{$pageContent}\n---\n";
|
|
}
|
|
}
|
|
|
|
if (empty($context)) {
|
|
$context = "관련된 내부 문서를 찾을 수 없습니다.";
|
|
}
|
|
|
|
// 2. Gemini API 호출
|
|
$url = "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key=" . $googleApiKey;
|
|
|
|
$headers = [
|
|
'Content-Type: application/json'
|
|
];
|
|
|
|
$systemInstruction = "You are a helpful, friendly, and professional customer support agent for 'codebridge-x.com'. Your tone should be polite and efficient, similar to Korean customer service standards. Use the provided [Context] to answer the user's question. If the context doesn't contain the answer, say you don't have that information in the internal documents. Reply in Korean.\n";
|
|
|
|
// 이력 포함
|
|
if (!empty($historyText)) {
|
|
$systemInstruction .= "\n[Conversation History]\n" . $historyText;
|
|
}
|
|
|
|
$systemInstruction .= "\nIMPORTANT: Even if you cannot find the direct answer in the Context, you MUST list the documents provided in the Context as '관련 문서' at the bottom.
|
|
|
|
If the document content is long or partial (ends with '...'), summarize the key points available and encourage the user to click the link for full details.
|
|
|
|
Format:
|
|
[Answer / Summary]
|
|
|
|
관련 문서:
|
|
- [문서 제목](URL)
|
|
|
|
[Context]\n" . $context;
|
|
|
|
$data = [
|
|
'contents' => [
|
|
[
|
|
'parts' => [
|
|
['text' => $userMessage]
|
|
]
|
|
]
|
|
],
|
|
'systemInstruction' => [
|
|
'parts' => [
|
|
['text' => $systemInstruction]
|
|
]
|
|
]
|
|
];
|
|
|
|
$ch = curl_init();
|
|
curl_setopt($ch, CURLOPT_URL, $url);
|
|
curl_setopt($ch, CURLOPT_POST, true);
|
|
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
|
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
|
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
|
|
|
|
$response = curl_exec($ch);
|
|
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
|
|
|
if (curl_errno($ch)) {
|
|
throw new Exception(curl_error($ch));
|
|
}
|
|
|
|
curl_close($ch);
|
|
|
|
if ($httpCode !== 200) {
|
|
throw new Exception("API Error: " . $response);
|
|
}
|
|
|
|
$responseData = json_decode($response, true);
|
|
$reply = $responseData['candidates'][0]['content']['parts'][0]['text'] ?? "죄송합니다. 답변을 생성하지 못했습니다.";
|
|
|
|
echo json_encode([
|
|
'reply' => $reply,
|
|
'debug' => [
|
|
'refinedQuery' => $refinedQuery,
|
|
'context' => $context,
|
|
'systemInstruction' => $systemInstruction,
|
|
'rawResponse' => $responseData
|
|
]
|
|
]);
|
|
|
|
} catch (Exception $e) {
|
|
echo json_encode(['reply' => "오류가 발생했습니다: " . $e->getMessage()]);
|
|
}
|
|
?>
|