Files
hack/tools/scripts/llm-key-hunter/results/_cache/content/adakash26434_tankaadhikaricv.json
T

1 line
43 KiB
JSON

{"repo": "adakash26434/tankaadhikaricv", "files": {"ai-chat.php": {"79694732fa3e0c0fab2788808eecdc4255645a57": {"t": 1785746153.83879, "n": 13248, "h": "cb8761535e5d2cfc", "c": "<?php\n/**\n * AI Chat Endpoint\n *\n * Receives a question, builds a context from PUBLIC portfolio data,\n * and sends it to the configured AI provider (ChatGPT/Gemini/DeepSeek).\n *\n * SECURITY: Only public portfolio data is sent to AI.\n * Admin credentials, messages, passwords are NEVER sent.\n */\n\nrequire_once __DIR__ . '/db.php';\n\nheader('Content-Type: application/json');\nheader('X-Content-Type-Options: nosniff');\n\nsession_start();\n\n// ── Rate limiting (simple) ────────────────────────────────────────────────────\n$rateKey = 'ai_chat_' . ($_SERVER['REMOTE_ADDR'] ?? 'unknown');\n$now = time();\nif (!isset($_SESSION[$rateKey])) $_SESSION[$rateKey] = ['count' => 0, 'reset' => $now + 60];\nif ($now > $_SESSION[$rateKey]['reset']) {\n $_SESSION[$rateKey] = ['count' => 0, 'reset' => $now + 60];\n}\nif ($_SESSION[$rateKey]['count'] > 10) {\n http_response_code(429);\n die(json_encode(['error' => 'Too many requests. Please wait a minute.']));\n}\n$_SESSION[$rateKey]['count']++;\n\n// ── Get settings ─────────────────────────────────────────────────────────────\ntry {\n $pdo = getDB();\n\n // Ensure table exists\n $pdo->exec(\"CREATE TABLE IF NOT EXISTS ai_settings (\n id INT AUTO_INCREMENT PRIMARY KEY,\n provider VARCHAR(20) DEFAULT 'openai',\n openai_key VARCHAR(255) DEFAULT '',\n gemini_key VARCHAR(255) DEFAULT '',\n deepseek_key VARCHAR(255) DEFAULT '',\n system_prompt TEXT,\n enabled TINYINT(1) DEFAULT 0,\n created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,\n updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP\n ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4\");\n\n $stmt = $pdo->query(\"SELECT * FROM ai_settings LIMIT 1\");\n $settings = $stmt->fetch() ?: [];\n\n // If no row, insert one\n if (!$settings) {\n $pdo->exec(\"INSERT INTO ai_settings (id) VALUES (1)\");\n $stmt = $pdo->query(\"SELECT * FROM ai_settings LIMIT 1\");\n $settings = $stmt->fetch() ?: [];\n }\n} catch (Exception $e) {\n error_log('[AI Chat] DB error: ' . $e->getMessage());\n die(json_encode(['error' => 'Database unavailable.']));\n}\n\n// Check if AI is enabled\nif (empty($settings['enabled'])) {\n die(json_encode(['error' => 'AI chat is currently disabled.']));\n}\n\n// ── Validate input ────────────────────────────────────────────────────────────\n$input = json_decode(file_get_contents('php://input'), true);\n$question = trim($input['question'] ?? '');\nif (strlen($question) < 2 || strlen($question) > 500) {\n die(json_encode(['error' => 'Please ask a question between 2-500 characters.']));\n}\n\n// Block prompt injection attempts\nif (preg_match('/(system|prompt injection|ignore previous|disregard)/i', $question)) {\n die(json_encode(['error' => 'Invalid question.']));\n}\n\n// ── Build PUBLIC portfolio context (SECURE — no admin data) ───────────────────\n$contextParts = [];\n\ntry {\n // Profile — public info only (no admin credentials)\n $profile = dbRow(\"SELECT full_name, title, bio, email, phone, location, company, company_url, role FROM profile LIMIT 1\");\n if ($profile) {\n $contextParts[] = \"PORTFOLIO OWNER:\\nName: {$profile['full_name']}\\nTitle: {$profile['title']}\\nBio: {$profile['bio']}\\nRole: {$profile['role']}\\nCompany: {$profile['company']}\\nLocation: {$profile['location']}\\nContact: {$profile['email']} | {$profile['phone']}\";\n }\n\n // Experience\n $exp = dbRows(\"SELECT company, role, period, description FROM experience ORDER BY sort_order, id\");\n if ($exp) {\n $expList = [];\n foreach ($exp as $e) {\n $expList[] = \"- {$e['role']} at {$e['company']} ({$e['period']}): {$e['description']}\";\n }\n $contextParts[] = \"WORK EXPERIENCE:\\n\" . implode(\"\\n\", $expList);\n }\n\n // Education\n $edu = dbRows(\"SELECT degree_code, degree_name, institution, period FROM education ORDER BY sort_order, id\");\n if ($edu) {\n $eduList = [];\n foreach ($edu as $e) {\n $code = $e['degree_code'] ? \"{$e['degree_code']} — \" : '';\n $eduList[] = \"- {$code}{$e['degree_name']} from {$e['institution']} ({$e['period']})\";\n }\n $contextParts[] = \"EDUCATION:\\n\" . implode(\"\\n\", $eduList);\n }\n\n // Skills\n $skills = dbRows(\"SELECT name, level, category FROM skills ORDER BY sort_order, id\");\n if ($skills) {\n $byCat = [];\n foreach ($skills as $s) { $byCat[$s['category']][] = \"{$s['name']} ({$s['level']}%)\"; }\n $skillParts = [];\n foreach ($byCat as $cat => $items) { $skillParts[] = \"$cat: \" . implode(', ', $items); }\n $contextParts[] = \"SKILLS:\\n\" . implode(\"\\n\", $skillParts);\n }\n\n // Projects\n $projects = dbRows(\"SELECT title, subtitle, description, url, tags FROM projects ORDER BY sort_order, id\");\n if ($projects) {\n $projList = [];\n foreach ($projects as $p) {\n $tags = $p['tags'] ? \" [Tags: {$p['tags']}]\" : \"\";\n $link = $p['url'] ? \" — {$p['url']}\" : \"\";\n $projList[] = \"- {$p['title']}\" . ($p['subtitle'] ? \" ({$p['subtitle']})\" : \"\") . \": {$p['description']}{$tags}{$link}\";\n }\n $contextParts[] = \"PROJECTS:\\n\" . implode(\"\\n\", $projList);\n }\n\n // Awards\n $awards = dbRows(\"SELECT title, organization, year, description FROM awards ORDER BY sort_order, id\");\n if ($awards) {\n $awardList = [];\n foreach ($awards as $a) {\n $desc = $a['description'] ? \" — {$a['description']}\" : \"\";\n $awardList[] = \"- {$a['title']} by {$a['organization']} ({$a['year']}){$desc}\";\n }\n $contextParts[] = \"AWARDS & RECOGNITION:\\n\" . implode(\"\\n\", $awardList);\n }\n\n // Training\n $training = dbRows(\"SELECT name, organizer, year, certificate_url FROM training ORDER BY sort_order, id\");\n if ($training) {\n $trainList = [];\n foreach ($training as $t) {\n $org = $t['organizer'] ? \" by {$t['organizer']}\" : \"\";\n $trainList[] = \"- {$t['name']}{$org} ({$t['year']})\";\n }\n $contextParts[] = \"TRAINING & CERTIFICATIONS:\\n\" . implode(\"\\n\", $trainList);\n }\n\n // Research\n $research = dbRows(\"SELECT title, journal, year, description FROM research ORDER BY sort_order, id\");\n if ($research) {\n $resList = [];\n foreach ($research as $r) { $resList[] = \"- \\\"{$r['title']}\\\" in {$r['journal']} ({$r['year']})\"; }\n $contextParts[] = \"RESEARCH PUBLICATIONS:\\n\" . implode(\"\\n\", $resList);\n }\n\n // Services (includes Digital Services with pricing)\n try {\n $services = dbRows(\"SELECT name, description, is_pricing, price, price_unit, features FROM services_about ORDER BY sort_order, id\");\n if ($services) {\n $servList = [];\n foreach ($services as $s) {\n $pricing = (!empty($s['is_pricing']) && !empty($s['price'])) ? \" — {$s['price']} {$s['price_unit']}\" : \"\";\n $servList[] = \"- {$s['name']}{$pricing}: {$s['description']}\";\n }\n $contextParts[] = \"SERVICES OFFERED:\\n\" . implode(\"\\n\", $servList);\n }\n } catch (Exception $e) {\n // Fallback: query just name/description if new columns don't exist yet\n try {\n $services = dbRows(\"SELECT name, description FROM services_about ORDER BY sort_order, id\");\n if ($services) {\n $servList = [];\n foreach ($services as $s) { $servList[] = \"- {$s['name']}: {$s['description']}\"; }\n $contextParts[] = \"SERVICES OFFERED:\\n\" . implode(\"\\n\", $servList);\n }\n } catch (Exception $e2) { /* table doesn't exist */ }\n }\n\n // Interests\n $interests = dbRows(\"SELECT name FROM interests ORDER BY sort_order, id\");\n if ($interests) {\n $intList = array_map(fn($i) => $i['name'], $interests);\n $contextParts[] = \"INTERESTS: \" . implode(', ', $intList);\n }\n\n} catch (Exception $e) {\n error_log('[AI Chat] Context build error: ' . $e->getMessage());\n}\n\n// ── Call AI Provider ─────────────────────────────────────────────────────────\n$provider = $settings['provider'] ?? 'openai';\n$answer = '';\n\nif ($provider === 'gemini') {\n $apiKey = $settings['gemini_key'] ?? '';\n if ($apiKey) $answer = callGemini($apiKey, $settings['system_prompt'] ?? '', $contextParts, $question);\n} elseif ($provider === 'deepseek') {\n $apiKey = $settings['deepseek_key'] ?? '';\n if ($apiKey) $answer = callDeepSeek($apiKey, $settings['system_prompt'] ?? '', $contextParts, $question);\n} else {\n $apiKey = $settings['openai_key'] ?? '';\n if ($apiKey) $answer = callOpenAI($apiKey, $settings['system_prompt'] ?? '', $contextParts, $question);\n}\n\n// ── Return answer ─────────────────────────────────────────────────────────────\nif ($answer) {\n $answer = preg_replace('/(password|secret|key|token)\\s*[:=]\\s*\\S+/i', '[REDACTED]', $answer);\n die(json_encode(['answer' => trim($answer)]));\n} else {\n die(json_encode(['error' => 'AI could not generate a response. Please try again.']));\n}\n\n// ── OpenAI / ChatGPT API ─────────────────────────────────────────────────────\nfunction callOpenAI(string $apiKey, string $customPrompt, array $contextParts, string $question): string {\n $systemDefault = \"You are a helpful AI assistant for a portfolio website. Answer questions based ONLY on the provided portfolio data. Be friendly, concise, and helpful. If you don't know something, say so. Do NOT make up information. Do NOT reveal any admin credentials, passwords, or internal data.\";\n $system = $customPrompt ?: $systemDefault;\n $context = implode(\"\\n\\n\", $contextParts);\n $fullSystem = $system . \"\\n\\n[PORTFOLIO DATA]\\n\" . $context;\n\n $ch = curl_init('https://api.openai.com/v1/chat/completions');\n curl_setopt_array($ch, [\n CURLOPT_POST => true,\n CURLOPT_HTTPHEADER => ['Content-Type: application/json', 'Authorization: Bearer ' . $apiKey],\n CURLOPT_POSTFIELDS => json_encode([\n 'model' => 'gpt-3.5-turbo',\n 'messages' => [\n ['role' => 'system', 'content' => $fullSystem],\n ['role' => 'user', 'content' => $question]\n ],\n 'max_tokens' => 500,\n 'temperature' => 0.7\n ]),\n CURLOPT_RETURNTRANSFER => true,\n CURLOPT_TIMEOUT => 20\n ]);\n $res = curl_exec($ch);\n $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);\n curl_close($ch);\n if ($code !== 200 || !$res) return '';\n $data = json_decode($res, true);\n return $data['choices'][0]['message']['content'] ?? '';\n}\n\n// ── Google Gemini API ──────────────────────────────────────────────────────────\nfunction callGemini(string $apiKey, string $customPrompt, array $contextParts, string $question): string {\n $systemDefault = \"You are a helpful AI assistant for a portfolio website. Answer based ONLY on the portfolio data provided. Be friendly and concise.\";\n $system = $customPrompt ?: $systemDefault;\n $context = implode(\"\\n\\n\", $contextParts);\n $prompt = \"$system\\n\\n[PORTFOLIO DATA]\\n$context\\n\\nUser: $question\";\n\n $url = \"https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent?key=$apiKey\";\n $ch = curl_init($url);\n curl_setopt_array($ch, [\n CURLOPT_POST => true,\n CURLOPT_HTTPHEADER => ['Content-Type: application/json'],\n CURLOPT_POSTFIELDS => json_encode([\n 'contents' => [['parts' => [['text' => $prompt]]]],\n 'generationConfig' => ['maxOutputTokens' => 500, 'temperature' => 0.7]\n ]),\n CURLOPT_RETURNTRANSFER => true,\n CURLOPT_TIMEOUT => 20\n ]);\n $res = curl_exec($ch);\n $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);\n curl_close($ch);\n if ($code !== 200 || !$res) return '';\n $data = json_decode($res, true);\n return $data['candidates'][0]['content']['parts'][0]['text'] ?? '';\n}\n\n// ── DeepSeek API ─────────────────────────────────────────────────────────────\nfunction callDeepSeek(string $apiKey, string $customPrompt, array $contextParts, string $question): string {\n $systemDefault = \"You are a helpful AI assistant for a portfolio website. Answer questions based ONLY on the provided portfolio data. Be friendly, concise, and helpful.\";\n $system = $customPrompt ?: $systemDefault;\n $context = implode(\"\\n\\n\", $contextParts);\n $fullSystem = $system . \"\\n\\n[PORTFOLIO DATA]\\n\" . $context;\n\n $ch = curl_init('https://api.deepseek.com/chat/completions');\n curl_setopt_array($ch, [\n CURLOPT_POST => true,\n CURLOPT_HTTPHEADER => ['Content-Type: application/json', 'Authorization: Bearer ' . $apiKey],\n CURLOPT_POSTFIELDS => json_encode([\n 'model' => 'deepseek-chat',\n 'messages' => [\n ['role' => 'system', 'content' => $fullSystem],\n ['role' => 'user', 'content' => $question]\n ],\n 'max_tokens' => 500,\n 'temperature' => 0.7\n ]),\n CURLOPT_RETURNTRANSFER => true,\n CURLOPT_TIMEOUT => 20\n ]);\n $res = curl_exec($ch);\n $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);\n curl_close($ch);\n if ($code !== 200 || !$res) return '';\n $data = json_decode($res, true);\n return $data['choices'][0]['message']['content'] ?? '';\n}\n\n"}, "a05a8a36d1f89fbfe94601c321ac88d575501618": {"t": 1785760240.9672623, "n": 13248, "h": "cb8761535e5d2cfc", "c": "<?php\n/**\n * AI Chat Endpoint\n *\n * Receives a question, builds a context from PUBLIC portfolio data,\n * and sends it to the configured AI provider (ChatGPT/Gemini/DeepSeek).\n *\n * SECURITY: Only public portfolio data is sent to AI.\n * Admin credentials, messages, passwords are NEVER sent.\n */\n\nrequire_once __DIR__ . '/db.php';\n\nheader('Content-Type: application/json');\nheader('X-Content-Type-Options: nosniff');\n\nsession_start();\n\n// ── Rate limiting (simple) ────────────────────────────────────────────────────\n$rateKey = 'ai_chat_' . ($_SERVER['REMOTE_ADDR'] ?? 'unknown');\n$now = time();\nif (!isset($_SESSION[$rateKey])) $_SESSION[$rateKey] = ['count' => 0, 'reset' => $now + 60];\nif ($now > $_SESSION[$rateKey]['reset']) {\n $_SESSION[$rateKey] = ['count' => 0, 'reset' => $now + 60];\n}\nif ($_SESSION[$rateKey]['count'] > 10) {\n http_response_code(429);\n die(json_encode(['error' => 'Too many requests. Please wait a minute.']));\n}\n$_SESSION[$rateKey]['count']++;\n\n// ── Get settings ─────────────────────────────────────────────────────────────\ntry {\n $pdo = getDB();\n\n // Ensure table exists\n $pdo->exec(\"CREATE TABLE IF NOT EXISTS ai_settings (\n id INT AUTO_INCREMENT PRIMARY KEY,\n provider VARCHAR(20) DEFAULT 'openai',\n openai_key VARCHAR(255) DEFAULT '',\n gemini_key VARCHAR(255) DEFAULT '',\n deepseek_key VARCHAR(255) DEFAULT '',\n system_prompt TEXT,\n enabled TINYINT(1) DEFAULT 0,\n created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,\n updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP\n ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4\");\n\n $stmt = $pdo->query(\"SELECT * FROM ai_settings LIMIT 1\");\n $settings = $stmt->fetch() ?: [];\n\n // If no row, insert one\n if (!$settings) {\n $pdo->exec(\"INSERT INTO ai_settings (id) VALUES (1)\");\n $stmt = $pdo->query(\"SELECT * FROM ai_settings LIMIT 1\");\n $settings = $stmt->fetch() ?: [];\n }\n} catch (Exception $e) {\n error_log('[AI Chat] DB error: ' . $e->getMessage());\n die(json_encode(['error' => 'Database unavailable.']));\n}\n\n// Check if AI is enabled\nif (empty($settings['enabled'])) {\n die(json_encode(['error' => 'AI chat is currently disabled.']));\n}\n\n// ── Validate input ────────────────────────────────────────────────────────────\n$input = json_decode(file_get_contents('php://input'), true);\n$question = trim($input['question'] ?? '');\nif (strlen($question) < 2 || strlen($question) > 500) {\n die(json_encode(['error' => 'Please ask a question between 2-500 characters.']));\n}\n\n// Block prompt injection attempts\nif (preg_match('/(system|prompt injection|ignore previous|disregard)/i', $question)) {\n die(json_encode(['error' => 'Invalid question.']));\n}\n\n// ── Build PUBLIC portfolio context (SECURE — no admin data) ───────────────────\n$contextParts = [];\n\ntry {\n // Profile — public info only (no admin credentials)\n $profile = dbRow(\"SELECT full_name, title, bio, email, phone, location, company, company_url, role FROM profile LIMIT 1\");\n if ($profile) {\n $contextParts[] = \"PORTFOLIO OWNER:\\nName: {$profile['full_name']}\\nTitle: {$profile['title']}\\nBio: {$profile['bio']}\\nRole: {$profile['role']}\\nCompany: {$profile['company']}\\nLocation: {$profile['location']}\\nContact: {$profile['email']} | {$profile['phone']}\";\n }\n\n // Experience\n $exp = dbRows(\"SELECT company, role, period, description FROM experience ORDER BY sort_order, id\");\n if ($exp) {\n $expList = [];\n foreach ($exp as $e) {\n $expList[] = \"- {$e['role']} at {$e['company']} ({$e['period']}): {$e['description']}\";\n }\n $contextParts[] = \"WORK EXPERIENCE:\\n\" . implode(\"\\n\", $expList);\n }\n\n // Education\n $edu = dbRows(\"SELECT degree_code, degree_name, institution, period FROM education ORDER BY sort_order, id\");\n if ($edu) {\n $eduList = [];\n foreach ($edu as $e) {\n $code = $e['degree_code'] ? \"{$e['degree_code']} — \" : '';\n $eduList[] = \"- {$code}{$e['degree_name']} from {$e['institution']} ({$e['period']})\";\n }\n $contextParts[] = \"EDUCATION:\\n\" . implode(\"\\n\", $eduList);\n }\n\n // Skills\n $skills = dbRows(\"SELECT name, level, category FROM skills ORDER BY sort_order, id\");\n if ($skills) {\n $byCat = [];\n foreach ($skills as $s) { $byCat[$s['category']][] = \"{$s['name']} ({$s['level']}%)\"; }\n $skillParts = [];\n foreach ($byCat as $cat => $items) { $skillParts[] = \"$cat: \" . implode(', ', $items); }\n $contextParts[] = \"SKILLS:\\n\" . implode(\"\\n\", $skillParts);\n }\n\n // Projects\n $projects = dbRows(\"SELECT title, subtitle, description, url, tags FROM projects ORDER BY sort_order, id\");\n if ($projects) {\n $projList = [];\n foreach ($projects as $p) {\n $tags = $p['tags'] ? \" [Tags: {$p['tags']}]\" : \"\";\n $link = $p['url'] ? \" — {$p['url']}\" : \"\";\n $projList[] = \"- {$p['title']}\" . ($p['subtitle'] ? \" ({$p['subtitle']})\" : \"\") . \": {$p['description']}{$tags}{$link}\";\n }\n $contextParts[] = \"PROJECTS:\\n\" . implode(\"\\n\", $projList);\n }\n\n // Awards\n $awards = dbRows(\"SELECT title, organization, year, description FROM awards ORDER BY sort_order, id\");\n if ($awards) {\n $awardList = [];\n foreach ($awards as $a) {\n $desc = $a['description'] ? \" — {$a['description']}\" : \"\";\n $awardList[] = \"- {$a['title']} by {$a['organization']} ({$a['year']}){$desc}\";\n }\n $contextParts[] = \"AWARDS & RECOGNITION:\\n\" . implode(\"\\n\", $awardList);\n }\n\n // Training\n $training = dbRows(\"SELECT name, organizer, year, certificate_url FROM training ORDER BY sort_order, id\");\n if ($training) {\n $trainList = [];\n foreach ($training as $t) {\n $org = $t['organizer'] ? \" by {$t['organizer']}\" : \"\";\n $trainList[] = \"- {$t['name']}{$org} ({$t['year']})\";\n }\n $contextParts[] = \"TRAINING & CERTIFICATIONS:\\n\" . implode(\"\\n\", $trainList);\n }\n\n // Research\n $research = dbRows(\"SELECT title, journal, year, description FROM research ORDER BY sort_order, id\");\n if ($research) {\n $resList = [];\n foreach ($research as $r) { $resList[] = \"- \\\"{$r['title']}\\\" in {$r['journal']} ({$r['year']})\"; }\n $contextParts[] = \"RESEARCH PUBLICATIONS:\\n\" . implode(\"\\n\", $resList);\n }\n\n // Services (includes Digital Services with pricing)\n try {\n $services = dbRows(\"SELECT name, description, is_pricing, price, price_unit, features FROM services_about ORDER BY sort_order, id\");\n if ($services) {\n $servList = [];\n foreach ($services as $s) {\n $pricing = (!empty($s['is_pricing']) && !empty($s['price'])) ? \" — {$s['price']} {$s['price_unit']}\" : \"\";\n $servList[] = \"- {$s['name']}{$pricing}: {$s['description']}\";\n }\n $contextParts[] = \"SERVICES OFFERED:\\n\" . implode(\"\\n\", $servList);\n }\n } catch (Exception $e) {\n // Fallback: query just name/description if new columns don't exist yet\n try {\n $services = dbRows(\"SELECT name, description FROM services_about ORDER BY sort_order, id\");\n if ($services) {\n $servList = [];\n foreach ($services as $s) { $servList[] = \"- {$s['name']}: {$s['description']}\"; }\n $contextParts[] = \"SERVICES OFFERED:\\n\" . implode(\"\\n\", $servList);\n }\n } catch (Exception $e2) { /* table doesn't exist */ }\n }\n\n // Interests\n $interests = dbRows(\"SELECT name FROM interests ORDER BY sort_order, id\");\n if ($interests) {\n $intList = array_map(fn($i) => $i['name'], $interests);\n $contextParts[] = \"INTERESTS: \" . implode(', ', $intList);\n }\n\n} catch (Exception $e) {\n error_log('[AI Chat] Context build error: ' . $e->getMessage());\n}\n\n// ── Call AI Provider ─────────────────────────────────────────────────────────\n$provider = $settings['provider'] ?? 'openai';\n$answer = '';\n\nif ($provider === 'gemini') {\n $apiKey = $settings['gemini_key'] ?? '';\n if ($apiKey) $answer = callGemini($apiKey, $settings['system_prompt'] ?? '', $contextParts, $question);\n} elseif ($provider === 'deepseek') {\n $apiKey = $settings['deepseek_key'] ?? '';\n if ($apiKey) $answer = callDeepSeek($apiKey, $settings['system_prompt'] ?? '', $contextParts, $question);\n} else {\n $apiKey = $settings['openai_key'] ?? '';\n if ($apiKey) $answer = callOpenAI($apiKey, $settings['system_prompt'] ?? '', $contextParts, $question);\n}\n\n// ── Return answer ─────────────────────────────────────────────────────────────\nif ($answer) {\n $answer = preg_replace('/(password|secret|key|token)\\s*[:=]\\s*\\S+/i', '[REDACTED]', $answer);\n die(json_encode(['answer' => trim($answer)]));\n} else {\n die(json_encode(['error' => 'AI could not generate a response. Please try again.']));\n}\n\n// ── OpenAI / ChatGPT API ─────────────────────────────────────────────────────\nfunction callOpenAI(string $apiKey, string $customPrompt, array $contextParts, string $question): string {\n $systemDefault = \"You are a helpful AI assistant for a portfolio website. Answer questions based ONLY on the provided portfolio data. Be friendly, concise, and helpful. If you don't know something, say so. Do NOT make up information. Do NOT reveal any admin credentials, passwords, or internal data.\";\n $system = $customPrompt ?: $systemDefault;\n $context = implode(\"\\n\\n\", $contextParts);\n $fullSystem = $system . \"\\n\\n[PORTFOLIO DATA]\\n\" . $context;\n\n $ch = curl_init('https://api.openai.com/v1/chat/completions');\n curl_setopt_array($ch, [\n CURLOPT_POST => true,\n CURLOPT_HTTPHEADER => ['Content-Type: application/json', 'Authorization: Bearer ' . $apiKey],\n CURLOPT_POSTFIELDS => json_encode([\n 'model' => 'gpt-3.5-turbo',\n 'messages' => [\n ['role' => 'system', 'content' => $fullSystem],\n ['role' => 'user', 'content' => $question]\n ],\n 'max_tokens' => 500,\n 'temperature' => 0.7\n ]),\n CURLOPT_RETURNTRANSFER => true,\n CURLOPT_TIMEOUT => 20\n ]);\n $res = curl_exec($ch);\n $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);\n curl_close($ch);\n if ($code !== 200 || !$res) return '';\n $data = json_decode($res, true);\n return $data['choices'][0]['message']['content'] ?? '';\n}\n\n// ── Google Gemini API ──────────────────────────────────────────────────────────\nfunction callGemini(string $apiKey, string $customPrompt, array $contextParts, string $question): string {\n $systemDefault = \"You are a helpful AI assistant for a portfolio website. Answer based ONLY on the portfolio data provided. Be friendly and concise.\";\n $system = $customPrompt ?: $systemDefault;\n $context = implode(\"\\n\\n\", $contextParts);\n $prompt = \"$system\\n\\n[PORTFOLIO DATA]\\n$context\\n\\nUser: $question\";\n\n $url = \"https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent?key=$apiKey\";\n $ch = curl_init($url);\n curl_setopt_array($ch, [\n CURLOPT_POST => true,\n CURLOPT_HTTPHEADER => ['Content-Type: application/json'],\n CURLOPT_POSTFIELDS => json_encode([\n 'contents' => [['parts' => [['text' => $prompt]]]],\n 'generationConfig' => ['maxOutputTokens' => 500, 'temperature' => 0.7]\n ]),\n CURLOPT_RETURNTRANSFER => true,\n CURLOPT_TIMEOUT => 20\n ]);\n $res = curl_exec($ch);\n $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);\n curl_close($ch);\n if ($code !== 200 || !$res) return '';\n $data = json_decode($res, true);\n return $data['candidates'][0]['content']['parts'][0]['text'] ?? '';\n}\n\n// ── DeepSeek API ─────────────────────────────────────────────────────────────\nfunction callDeepSeek(string $apiKey, string $customPrompt, array $contextParts, string $question): string {\n $systemDefault = \"You are a helpful AI assistant for a portfolio website. Answer questions based ONLY on the provided portfolio data. Be friendly, concise, and helpful.\";\n $system = $customPrompt ?: $systemDefault;\n $context = implode(\"\\n\\n\", $contextParts);\n $fullSystem = $system . \"\\n\\n[PORTFOLIO DATA]\\n\" . $context;\n\n $ch = curl_init('https://api.deepseek.com/chat/completions');\n curl_setopt_array($ch, [\n CURLOPT_POST => true,\n CURLOPT_HTTPHEADER => ['Content-Type: application/json', 'Authorization: Bearer ' . $apiKey],\n CURLOPT_POSTFIELDS => json_encode([\n 'model' => 'deepseek-chat',\n 'messages' => [\n ['role' => 'system', 'content' => $fullSystem],\n ['role' => 'user', 'content' => $question]\n ],\n 'max_tokens' => 500,\n 'temperature' => 0.7\n ]),\n CURLOPT_RETURNTRANSFER => true,\n CURLOPT_TIMEOUT => 20\n ]);\n $res = curl_exec($ch);\n $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);\n curl_close($ch);\n if ($code !== 200 || !$res) return '';\n $data = json_decode($res, true);\n return $data['choices'][0]['message']['content'] ?? '';\n}\n\n"}, "98633863ac9369c57c9f712421a6f8e3f177e638": {"t": 1785760242.8497705, "n": 12425, "h": "381f3319d9a2a1d7", "c": "<?php\n/**\n * AI Chat Endpoint\n *\n * Receives a question, builds a context from PUBLIC portfolio data,\n * and sends it to the configured AI provider (ChatGPT/Gemini/DeepSeek).\n *\n * SECURITY: Only public portfolio data is sent to AI.\n * Admin credentials, messages, passwords are NEVER sent.\n */\n\nrequire_once __DIR__ . '/db.php';\n\nheader('Content-Type: application/json');\nheader('X-Content-Type-Options: nosniff');\n\nsession_start();\n\n// ── Rate limiting (simple) ────────────────────────────────────────────────────\n$rateKey = 'ai_chat_' . ($_SERVER['REMOTE_ADDR'] ?? 'unknown');\n$now = time();\nif (!isset($_SESSION[$rateKey])) $_SESSION[$rateKey] = ['count' => 0, 'reset' => $now + 60];\nif ($now > $_SESSION[$rateKey]['reset']) {\n $_SESSION[$rateKey] = ['count' => 0, 'reset' => $now + 60];\n}\nif ($_SESSION[$rateKey]['count'] > 10) {\n http_response_code(429);\n die(json_encode(['error' => 'Too many requests. Please wait a minute.']));\n}\n$_SESSION[$rateKey]['count']++;\n\n// ── Get settings ─────────────────────────────────────────────────────────────\ntry {\n $pdo = getDB();\n\n // Ensure table exists\n $pdo->exec(\"CREATE TABLE IF NOT EXISTS ai_settings (\n id INT AUTO_INCREMENT PRIMARY KEY,\n provider VARCHAR(20) DEFAULT 'openai',\n openai_key VARCHAR(255) DEFAULT '',\n gemini_key VARCHAR(255) DEFAULT '',\n deepseek_key VARCHAR(255) DEFAULT '',\n system_prompt TEXT,\n enabled TINYINT(1) DEFAULT 0,\n created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,\n updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP\n ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4\");\n\n $stmt = $pdo->query(\"SELECT * FROM ai_settings LIMIT 1\");\n $settings = $stmt->fetch() ?: [];\n\n // If no row, insert one\n if (!$settings) {\n $pdo->exec(\"INSERT INTO ai_settings (id) VALUES (1)\");\n $stmt = $pdo->query(\"SELECT * FROM ai_settings LIMIT 1\");\n $settings = $stmt->fetch() ?: [];\n }\n} catch (Exception $e) {\n error_log('[AI Chat] DB error: ' . $e->getMessage());\n die(json_encode(['error' => 'Database unavailable.']));\n}\n\n// Check if AI is enabled\nif (empty($settings['enabled'])) {\n die(json_encode(['error' => 'AI chat is currently disabled.']));\n}\n\n// ── Validate input ────────────────────────────────────────────────────────────\n$input = json_decode(file_get_contents('php://input'), true);\n$question = trim($input['question'] ?? '');\nif (strlen($question) < 2 || strlen($question) > 500) {\n die(json_encode(['error' => 'Please ask a question between 2-500 characters.']));\n}\n\n// Block prompt injection attempts\nif (preg_match('/(system|prompt injection|ignore previous|disregard)/i', $question)) {\n die(json_encode(['error' => 'Invalid question.']));\n}\n\n// ── Build PUBLIC portfolio context (SECURE — no admin data) ───────────────────\n$contextParts = [];\n\ntry {\n // Profile — public info only (no admin credentials)\n $profile = dbRow(\"SELECT full_name, title, bio, email, phone, location, company, company_url, role FROM profile LIMIT 1\");\n if ($profile) {\n $contextParts[] = \"PORTFOLIO OWNER:\\nName: {$profile['full_name']}\\nTitle: {$profile['title']}\\nBio: {$profile['bio']}\\nRole: {$profile['role']}\\nCompany: {$profile['company']}\\nLocation: {$profile['location']}\\nContact: {$profile['email']} | {$profile['phone']}\";\n }\n\n // Experience\n $exp = dbRows(\"SELECT title, company, location, start_date, end_date, description FROM experience ORDER BY sort_order, id\");\n if ($exp) {\n $expList = [];\n foreach ($exp as $e) {\n $dates = \"{$e['start_date']} - \" . ($e['end_date'] ?: 'Present');\n $expList[] = \"- {$e['title']} at {$e['company']} ({$dates}): {$e['description']}\";\n }\n $contextParts[] = \"WORK EXPERIENCE:\\n\" . implode(\"\\n\", $expList);\n }\n\n // Education\n $edu = dbRows(\"SELECT degree, institution, field_of_study, start_year, end_year FROM education ORDER BY sort_order, id\");\n if ($edu) {\n $eduList = [];\n foreach ($edu as $e) {\n $years = \"{$e['start_year']} - \" . ($e['end_year'] ?: 'Present');\n $eduList[] = \"- {$e['degree']} in {$e['field_of_study']} from {$e['institution']} ({$years})\";\n }\n $contextParts[] = \"EDUCATION:\\n\" . implode(\"\\n\", $eduList);\n }\n\n // Skills\n $skills = dbRows(\"SELECT name, level, category FROM skills ORDER BY sort_order, id\");\n if ($skills) {\n $byCat = [];\n foreach ($skills as $s) { $byCat[$s['category']][] = \"{$s['name']} ({$s['level']}%)\"; }\n $skillParts = [];\n foreach ($byCat as $cat => $items) { $skillParts[] = \"$cat: \" . implode(', ', $items); }\n $contextParts[] = \"SKILLS:\\n\" . implode(\"\\n\", $skillParts);\n }\n\n // Projects\n $projects = dbRows(\"SELECT title, description, url, technologies FROM projects ORDER BY sort_order, id\");\n if ($projects) {\n $projList = [];\n foreach ($projects as $p) {\n $tech = $p['technologies'] ? \" [Tech: {$p['technologies']}]\" : \"\";\n $link = $p['url'] ? \" — {$p['url']}\" : \"\";\n $projList[] = \"- {$p['title']}: {$p['description']}{$tech}{$link}\";\n }\n $contextParts[] = \"PROJECTS:\\n\" . implode(\"\\n\", $projList);\n }\n\n // Awards\n $awards = dbRows(\"SELECT title, organization, year, description FROM awards ORDER BY sort_order, id\");\n if ($awards) {\n $awardList = [];\n foreach ($awards as $a) {\n $desc = $a['description'] ? \" — {$a['description']}\" : \"\";\n $awardList[] = \"- {$a['title']} by {$a['organization']} ({$a['year']}){$desc}\";\n }\n $contextParts[] = \"AWARDS & RECOGNITION:\\n\" . implode(\"\\n\", $awardList);\n }\n\n // Training\n $training = dbRows(\"SELECT title, institution, year FROM training ORDER BY sort_order, id\");\n if ($training) {\n $trainList = [];\n foreach ($training as $t) { $trainList[] = \"- {$t['title']} from {$t['institution']} ({$t['year']})\"; }\n $contextParts[] = \"TRAINING & CERTIFICATIONS:\\n\" . implode(\"\\n\", $trainList);\n }\n\n // Research\n $research = dbRows(\"SELECT title, journal, year, description FROM research ORDER BY sort_order, id\");\n if ($research) {\n $resList = [];\n foreach ($research as $r) { $resList[] = \"- \\\"{$r['title']}\\\" in {$r['journal']} ({$r['year']})\"; }\n $contextParts[] = \"RESEARCH PUBLICATIONS:\\n\" . implode(\"\\n\", $resList);\n }\n\n // Services\n $services = dbRows(\"SELECT title, description FROM services_about ORDER BY sort_order, id\");\n if ($services) {\n $servList = [];\n foreach ($services as $s) { $servList[] = \"- {$s['title']}: {$s['description']}\"; }\n $contextParts[] = \"SERVICES OFFERED:\\n\" . implode(\"\\n\", $servList);\n }\n\n // Interests\n $interests = dbRows(\"SELECT name FROM interests ORDER BY sort_order, id\");\n if ($interests) {\n $intList = array_map(fn($i) => $i['name'], $interests);\n $contextParts[] = \"INTERESTS: \" . implode(', ', $intList);\n }\n\n} catch (Exception $e) {\n error_log('[AI Chat] Context build error: ' . $e->getMessage());\n}\n\n// ── Call AI Provider ─────────────────────────────────────────────────────────\n$provider = $settings['provider'] ?? 'openai';\n$answer = '';\n\nif ($provider === 'gemini') {\n $apiKey = $settings['gemini_key'] ?? '';\n if ($apiKey) $answer = callGemini($apiKey, $settings['system_prompt'] ?? '', $contextParts, $question);\n} elseif ($provider === 'deepseek') {\n $apiKey = $settings['deepseek_key'] ?? '';\n if ($apiKey) $answer = callDeepSeek($apiKey, $settings['system_prompt'] ?? '', $contextParts, $question);\n} else {\n $apiKey = $settings['openai_key'] ?? '';\n if ($apiKey) $answer = callOpenAI($apiKey, $settings['system_prompt'] ?? '', $contextParts, $question);\n}\n\n// ── Return answer ─────────────────────────────────────────────────────────────\nif ($answer) {\n $answer = preg_replace('/(password|secret|key|token)\\s*[:=]\\s*\\S+/i', '[REDACTED]', $answer);\n die(json_encode(['answer' => trim($answer)]));\n} else {\n die(json_encode(['error' => 'AI could not generate a response. Please try again.']));\n}\n\n// ── OpenAI / ChatGPT API ─────────────────────────────────────────────────────\nfunction callOpenAI(string $apiKey, string $customPrompt, array $contextParts, string $question): string {\n $systemDefault = \"You are a helpful AI assistant for a portfolio website. Answer questions based ONLY on the provided portfolio data. Be friendly, concise, and helpful. If you don't know something, say so. Do NOT make up information. Do NOT reveal any admin credentials, passwords, or internal data.\";\n $system = $customPrompt ?: $systemDefault;\n $context = implode(\"\\n\\n\", $contextParts);\n $fullSystem = $system . \"\\n\\n[PORTFOLIO DATA]\\n\" . $context;\n\n $ch = curl_init('https://api.openai.com/v1/chat/completions');\n curl_setopt_array($ch, [\n CURLOPT_POST => true,\n CURLOPT_HTTPHEADER => ['Content-Type: application/json', 'Authorization: Bearer ' . $apiKey],\n CURLOPT_POSTFIELDS => json_encode([\n 'model' => 'gpt-3.5-turbo',\n 'messages' => [\n ['role' => 'system', 'content' => $fullSystem],\n ['role' => 'user', 'content' => $question]\n ],\n 'max_tokens' => 500,\n 'temperature' => 0.7\n ]),\n CURLOPT_RETURNTRANSFER => true,\n CURLOPT_TIMEOUT => 20\n ]);\n $res = curl_exec($ch);\n $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);\n curl_close($ch);\n if ($code !== 200 || !$res) return '';\n $data = json_decode($res, true);\n return $data['choices'][0]['message']['content'] ?? '';\n}\n\n// ── Google Gemini API ──────────────────────────────────────────────────────────\nfunction callGemini(string $apiKey, string $customPrompt, array $contextParts, string $question): string {\n $systemDefault = \"You are a helpful AI assistant for a portfolio website. Answer based ONLY on the portfolio data provided. Be friendly and concise.\";\n $system = $customPrompt ?: $systemDefault;\n $context = implode(\"\\n\\n\", $contextParts);\n $prompt = \"$system\\n\\n[PORTFOLIO DATA]\\n$context\\n\\nUser: $question\";\n\n $url = \"https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent?key=$apiKey\";\n $ch = curl_init($url);\n curl_setopt_array($ch, [\n CURLOPT_POST => true,\n CURLOPT_HTTPHEADER => ['Content-Type: application/json'],\n CURLOPT_POSTFIELDS => json_encode([\n 'contents' => [['parts' => [['text' => $prompt]]]],\n 'generationConfig' => ['maxOutputTokens' => 500, 'temperature' => 0.7]\n ]),\n CURLOPT_RETURNTRANSFER => true,\n CURLOPT_TIMEOUT => 20\n ]);\n $res = curl_exec($ch);\n $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);\n curl_close($ch);\n if ($code !== 200 || !$res) return '';\n $data = json_decode($res, true);\n return $data['candidates'][0]['content']['parts'][0]['text'] ?? '';\n}\n\n// ── DeepSeek API ─────────────────────────────────────────────────────────────\nfunction callDeepSeek(string $apiKey, string $customPrompt, array $contextParts, string $question): string {\n $systemDefault = \"You are a helpful AI assistant for a portfolio website. Answer questions based ONLY on the provided portfolio data. Be friendly, concise, and helpful.\";\n $system = $customPrompt ?: $systemDefault;\n $context = implode(\"\\n\\n\", $contextParts);\n $fullSystem = $system . \"\\n\\n[PORTFOLIO DATA]\\n\" . $context;\n\n $ch = curl_init('https://api.deepseek.com/chat/completions');\n curl_setopt_array($ch, [\n CURLOPT_POST => true,\n CURLOPT_HTTPHEADER => ['Content-Type: application/json', 'Authorization: Bearer ' . $apiKey],\n CURLOPT_POSTFIELDS => json_encode([\n 'model' => 'deepseek-chat',\n 'messages' => [\n ['role' => 'system', 'content' => $fullSystem],\n ['role' => 'user', 'content' => $question]\n ],\n 'max_tokens' => 500,\n 'temperature' => 0.7\n ]),\n CURLOPT_RETURNTRANSFER => true,\n CURLOPT_TIMEOUT => 20\n ]);\n $res = curl_exec($ch);\n $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);\n curl_close($ch);\n if ($code !== 200 || !$res) return '';\n $data = json_decode($res, true);\n return $data['choices'][0]['message']['content'] ?? '';\n}\n\n"}}}}