feat: switch chatbot from Gemini to Groq (llama-3.3-70b, 14400 RPD free)
This commit is contained in:
1 parent
6727361c6e
commit
0c0977a220
4 files changed
+68
-82
No files matched your search
+2
-2
@@ -11,8 +11,8 @@ RECAPTCHA_SECRET_KEY=your-recaptcha-secret-key-here
|
||||
# Contact Email (used in FormSubmit action)
|
||||
CONTACT_EMAIL=your-email@gmail.com
|
||||
|
||||
# gemini api key
|
||||
GEMINI_API_KEY=your-gemin-api-key
|
||||
# Groq AI API Key (free: 14,400 req/day — get yours at https://console.groq.com)
|
||||
GROQ_API_KEY=your-groq-api-key-here
|
||||
|
||||
|
||||
# Rate Limiting (requests per hour)
|
||||
|
||||
+37
-50
@@ -25,10 +25,11 @@ $_SESSION['chat_requests'][] = $now;
|
||||
// Load DB connection which also loads environment variables
|
||||
require_once __DIR__ . '/../config/db.php';
|
||||
|
||||
$apiKey = $_ENV['GEMINI_API_KEY'] ?? getenv('GEMINI_API_KEY') ?? '';
|
||||
// Load Groq API key (free tier: 14,400 req/day)
|
||||
$apiKey = $_ENV['GROQ_API_KEY'] ?? getenv('GROQ_API_KEY') ?? '';
|
||||
if (empty($apiKey)) {
|
||||
http_response_code(500);
|
||||
echo json_encode(['error' => 'API Key configuration is missing. Please configure GEMINI_API_KEY in your .env file.']);
|
||||
echo json_encode(['error' => 'API Key configuration is missing. Please set GROQ_API_KEY in your .env file.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
@@ -121,49 +122,32 @@ $systemPrompt .= "3. If asked about contact options or hiring, point them to his
|
||||
$systemPrompt .= "4. Do not make up facts or external projects not listed in the context. If you don't know the answer, politely invite the user to contact Cris directly.\n";
|
||||
$systemPrompt .= "5. Format your output using simple markdown (like bolding or bullet points) to make it readable.\n";
|
||||
|
||||
// Map chat history
|
||||
// Map chat history and prepend system instructions to the first user message
|
||||
$contents = [];
|
||||
$firstMsgPrepended = false;
|
||||
|
||||
if (isset($inputData['history']) && is_array($inputData['history']) && !empty($inputData['history'])) {
|
||||
foreach ($inputData['history'] as $index => $msg) {
|
||||
if (isset($msg['role']) && isset($msg['text'])) {
|
||||
$role = ($msg['role'] === 'user') ? 'user' : 'model';
|
||||
$text = $msg['text'];
|
||||
|
||||
// Prepend system prompt to the first user message in history
|
||||
if (!$firstMsgPrepended && $role === 'user') {
|
||||
$text = "System Context & Instructions:\n" . $systemPrompt . "\n\nUser Message: " . $text;
|
||||
$firstMsgPrepended = true;
|
||||
}
|
||||
|
||||
$contents[] = [
|
||||
'role' => $role,
|
||||
'parts' => [['text' => $text]]
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add the current user message (prepend system instructions if history was empty)
|
||||
$currentUserText = $userMessage;
|
||||
if (!$firstMsgPrepended) {
|
||||
$currentUserText = "System Context & Instructions:\n" . $systemPrompt . "\n\nUser Message: " . $userMessage;
|
||||
}
|
||||
|
||||
$contents[] = [
|
||||
'role' => 'user',
|
||||
'parts' => [['text' => $currentUserText]]
|
||||
// Build Groq messages array (OpenAI-compatible format)
|
||||
// System prompt goes in its own 'system' role message
|
||||
$messages = [
|
||||
['role' => 'system', 'content' => $systemPrompt]
|
||||
];
|
||||
|
||||
// Setup request body without systemInstruction key
|
||||
// Append chat history
|
||||
if (isset($inputData['history']) && is_array($inputData['history']) && !empty($inputData['history'])) {
|
||||
foreach ($inputData['history'] as $msg) {
|
||||
if (isset($msg['role']) && isset($msg['text'])) {
|
||||
$role = ($msg['role'] === 'user') ? 'user' : 'assistant';
|
||||
$messages[] = ['role' => $role, 'content' => $msg['text']];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add the current user message
|
||||
$messages[] = ['role' => 'user', 'content' => $userMessage];
|
||||
|
||||
// Groq request payload (OpenAI-compatible)
|
||||
$requestPayload = [
|
||||
'contents' => $contents,
|
||||
'generationConfig' => [
|
||||
'model' => 'llama-3.3-70b-versatile', // 14,400 RPD free
|
||||
'messages' => $messages,
|
||||
'temperature' => 0.7,
|
||||
'maxOutputTokens' => 800
|
||||
]
|
||||
'max_tokens' => 800,
|
||||
'stream' => false
|
||||
];
|
||||
|
||||
// Guard: cURL must be available
|
||||
@@ -173,15 +157,18 @@ if (!function_exists('curl_init')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
// Call Gemini API via cURL
|
||||
// gemini-2.0-flash-lite: highest free-tier quota (1,500 req/day)
|
||||
$url = "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash-lite:generateContent?key=" . urlencode($apiKey);
|
||||
// Call Groq API via cURL (OpenAI-compatible endpoint)
|
||||
// Model: llama-3.3-70b-versatile — free tier, 14,400 req/day
|
||||
$url = 'https://api.groq.com/openai/v1/chat/completions';
|
||||
|
||||
$ch = curl_init($url);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_POST, true);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($requestPayload));
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
||||
'Content-Type: application/json',
|
||||
'Authorization: Bearer ' . $apiKey
|
||||
]);
|
||||
curl_setopt($ch, CURLOPT_TIMEOUT, 15);
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
|
||||
|
||||
@@ -200,18 +187,18 @@ curl_close($ch);
|
||||
if ($httpCode !== 200) {
|
||||
http_response_code($httpCode);
|
||||
$errorDetails = json_decode($response, true);
|
||||
$errMsg = isset($errorDetails['error']['message']) ? $errorDetails['error']['message'] : 'Failed to retrieve response from Gemini.';
|
||||
echo json_encode(['error' => $errMsg, 'raw' => $response]);
|
||||
$errMsg = isset($errorDetails['error']['message']) ? $errorDetails['error']['message'] : 'Failed to retrieve response from Groq.';
|
||||
echo json_encode(['error' => $errMsg]);
|
||||
exit;
|
||||
}
|
||||
|
||||
$responseData = json_decode($response, true);
|
||||
$reply = '';
|
||||
if (isset($responseData['candidates'][0]['content']['parts'][0]['text'])) {
|
||||
$reply = $responseData['candidates'][0]['content']['parts'][0]['text'];
|
||||
if (isset($responseData['choices'][0]['message']['content'])) {
|
||||
$reply = $responseData['choices'][0]['message']['content'];
|
||||
} else {
|
||||
http_response_code(502);
|
||||
echo json_encode(['error' => 'Invalid response structure from Gemini API.', 'raw' => $responseData]);
|
||||
echo json_encode(['error' => 'Invalid response structure from Groq API.', 'raw' => $responseData]);
|
||||
exit;
|
||||
}
|
||||
|
||||
|
||||
+27
-28
@@ -75,41 +75,40 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
console.warn("Backend API call failed, attempting client-side fallback. Details:", error);
|
||||
}
|
||||
|
||||
// CLIENT-SIDE FALLBACK (e.g. for InfinityFree or local cert errors)
|
||||
// CLIENT-SIDE FALLBACK: use Groq directly (OpenAI-compatible, 14,400 RPD free)
|
||||
if (!replySuccess) {
|
||||
const config = window.chatbotConfig;
|
||||
if (config && config.apiKey && config.systemPrompt) {
|
||||
if (config && config.groqApiKey && config.systemPrompt) {
|
||||
try {
|
||||
// Structure payload for Gemini client-side and inject systemInstructions in first user message
|
||||
let firstMsgPrepended = false;
|
||||
const contents = chatHistory.map(msg => {
|
||||
let text = msg.text;
|
||||
const role = msg.role === 'user' ? 'user' : 'model';
|
||||
if (!firstMsgPrepended && role === 'user') {
|
||||
text = `System Context & Instructions:\n${config.systemPrompt}\n\nUser Message: ${text}`;
|
||||
firstMsgPrepended = true;
|
||||
}
|
||||
return {
|
||||
role: role,
|
||||
parts: [{ text: text }]
|
||||
};
|
||||
// Build OpenAI-compatible messages array
|
||||
const messages = [
|
||||
{ role: "system", content: config.systemPrompt }
|
||||
];
|
||||
|
||||
// Append history (excluding the latest user message which we add separately)
|
||||
chatHistory.slice(0, -1).forEach(msg => {
|
||||
messages.push({
|
||||
role: msg.role === "user" ? "user" : "assistant",
|
||||
content: msg.text
|
||||
});
|
||||
});
|
||||
|
||||
// Add current user message
|
||||
messages.push({ role: "user", content: message });
|
||||
|
||||
const requestPayload = {
|
||||
contents: contents,
|
||||
generationConfig: {
|
||||
model: "llama-3.3-70b-versatile", // 14,400 RPD free
|
||||
messages: messages,
|
||||
temperature: 0.7,
|
||||
maxOutputTokens: 800
|
||||
}
|
||||
max_tokens: 800,
|
||||
stream: false
|
||||
};
|
||||
|
||||
// gemini-2.0-flash-lite: highest free-tier quota (1,500 req/day)
|
||||
const url = `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash-lite:generateContent?key=${encodeURIComponent(config.apiKey)}`;
|
||||
|
||||
const response = await fetch(url, {
|
||||
const response = await fetch("https://api.groq.com/openai/v1/chat/completions", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": `Bearer ${config.groqApiKey}`
|
||||
},
|
||||
body: JSON.stringify(requestPayload)
|
||||
});
|
||||
@@ -117,8 +116,8 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
const data = await response.json();
|
||||
removeTypingIndicator(typingIndicator);
|
||||
|
||||
if (response.ok && data.candidates && data.candidates[0].content.parts[0].text) {
|
||||
const botReply = data.candidates[0].content.parts[0].text;
|
||||
if (response.ok && data.choices && data.choices[0].message.content) {
|
||||
const botReply = data.choices[0].message.content;
|
||||
addMessage(botReply, "bot-msg");
|
||||
chatHistory.push({ role: "model", text: botReply });
|
||||
replySuccess = true;
|
||||
@@ -129,11 +128,11 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
} catch (fallbackError) {
|
||||
removeTypingIndicator(typingIndicator);
|
||||
addMessage("Unable to connect to assistant. Please check your internet connection.", "bot-msg error-msg");
|
||||
console.error("Direct API Fallback Error:", fallbackError);
|
||||
console.error("Groq Fallback Error:", fallbackError);
|
||||
}
|
||||
} else {
|
||||
removeTypingIndicator(typingIndicator);
|
||||
addMessage("API request blocked by host firewall and fallback config is missing.", "bot-msg error-msg");
|
||||
addMessage("Chatbot is temporarily unavailable. Please try again later.", "bot-msg error-msg");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -80,13 +80,13 @@ $systemPrompt .= "3. If asked about contact options or hiring, point them to his
|
||||
$systemPrompt .= "4. Do not make up facts or external projects not listed in the context. If you don't know the answer, politely invite the user to contact Cris directly.\n";
|
||||
$systemPrompt .= "5. Format your output using simple markdown (like bolding or bullet points) to make it readable.\n";
|
||||
|
||||
$apiKey = $_ENV['GEMINI_API_KEY'] ?? getenv('GEMINI_API_KEY') ?? '';
|
||||
$apiKey = $_ENV['GROQ_API_KEY'] ?? getenv('GROQ_API_KEY') ?? '';
|
||||
?>
|
||||
|
||||
<!-- Pass DB configuration context directly to javascript to allow direct API requests on restricted hosting environments (like InfinityFree) -->
|
||||
<script>
|
||||
window.chatbotConfig = {
|
||||
apiKey: <?php echo json_encode($apiKey); ?>,
|
||||
groqApiKey: <?php echo json_encode($apiKey); ?>,
|
||||
systemPrompt: <?php echo json_encode($systemPrompt); ?>
|
||||
};
|
||||
</script>
|
||||
|
||||
Reference in new issue
Block a user