Chat bot code_9 Nov
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Graham Bishop Research Assistant Chatbot</title>
<script src="https://cdn.tailwindcss.com"></script>
<style>
/* New Brand Colors */
:root {
--color-primary: #0B2B60; /* Deep Navy Blue */
--color-accent: #D9A024; /* Muted Gold/Yellow */
--color-ai-bg: #F1F5F9; /* Light Gray/Blue */
}
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap');
body { font-family: 'Inter', sans-serif; background-color: #f7f7f7; }
.chatbot-container {
max-width: 400px;
height: 500px;
display: flex;
flex-direction: column;
box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1), 0 0 10px 0 rgba(0, 0, 0, 0.05);
border: 1px solid #e5e7eb;
}
.chat-header {
background-color: var(--color-primary); /* Deep Blue Header */
}
.chat-window {
overflow-y: auto;
flex-grow: 1;
padding: 1rem;
background-color: #ffffff;
scroll-behavior: smooth;
}
.message-bubble {
max-width: 85%;
padding: 0.75rem 1rem;
margin-bottom: 0.75rem;
border-radius: 1.5rem;
font-size: 0.875rem;
line-height: 1.4;
}
.user-message {
background-color: var(--color-primary); /* Deep Blue */
color: white;
margin-left: auto;
border-bottom-right-radius: 0.5rem;
}
.ai-message {
background-color: var(--color-ai-bg); /* Light Gray/Blue */
color: #1f2937;
margin-right: auto;
border-bottom-left-radius: 0.5rem;
}
.input-area {
border-top: 1px solid #e5e7eb;
}
.loader {
border: 4px solid #f3f3f3;
border-top: 4px solid var(--color-accent); /* Gold Accent */
border-radius: 50%;
width: 20px;
height: 20px;
animation: spin 1s linear infinite;
}
.themed-button {
background-color: var(--color-accent); /* Gold Accent */
color: var(--color-primary);
}
.themed-button:hover {
background-color: #C08C1E; /* Slightly darker gold on hover */
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
</style>
</head>
<body class="p-4 flex items-center justify-center min-h-screen">
<div class="chatbot-container rounded-xl w-full">
<!-- Header: Updated with Profile Picture Placeholder -->
<div class="chat-header p-4 rounded-t-xl text-white flex items-center justify-between space-x-3">
<!-- Profile Picture (Now updated with the provided URL) -->
<img id="profile-picture" src="https://lh3.googleusercontent.com/-vZT2ZIx_xoc/AAAAAAAAAAI/AAAAAAAAAAA/ALKGfkmL2mx1SNcox5KKD3PkydPT1H4zbQ/photo.jpg?sz=46"
onerror="this.onerror=null; this.src='https://placehold.co/40x40/0B2B60/ffffff?text=GB';"
alt="Graham Bishop Profile" class="w-10 h-10 rounded-full border-2 border-white object-cover">
<div class="flex-grow">
<div class="font-semibold text-lg">
GB Research Assistant
</div>
<div class="text-xs opacity-70">
EU Financial Markets Policy
</div>
</div>
</div>
<!-- Chat Window -->
<div id="chat-window" class="chat-window">
<div class="ai-message message-bubble rounded-tr-xl">
Hello. I am the automated research assistant for Graham Bishop. How can I help you find information on EU financial regulation today?
</div>
</div>
<!-- Input Area -->
<div class="input-area p-4 bg-white flex space-x-2">
<input type="text" id="user-input" placeholder="Ask a question..."
class="flex-grow p-2 border border-gray-300 rounded-full focus:ring-2 focus:ring-amber-500 focus:border-amber-500 outline-none transition duration-150"
onkeydown="if(event.key === 'Enter') sendMessage()">
<button id="send-button" onclick="sendMessage()"
class="themed-button text-white p-2 rounded-full w-10 h-10 flex items-center justify-center transition duration-150 disabled:bg-gray-400"
aria-label="Send Message">
<svg id="send-icon" class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14 5l7 7m0 0l-7 7m7-7H3"></path>
</svg>
<div id="loading-spinner" class="loader hidden"></div>
</button>
</div>
</div>
<script>
const chatWindow = document.getElementById('chat-window');
const userInput = document.getElementById('user-input');
const sendButton = document.getElementById('send-button');
const sendIcon = document.getElementById('send-icon');
const loadingSpinner = document.getElementById('loading-spinner');
// --- Configuration ---
const apiKey = ""; // API key is left empty as per instruction
const apiUrl = `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash-preview-09-2025:generateContent?key=${apiKey}`;
// Utility to display messages
function appendMessage(text, type) {
const bubble = document.createElement('div');
bubble.className = `message-bubble ${type === 'user' ? 'user-message rounded-tl-xl' : 'ai-message rounded-tr-xl'}`;
// Simple Markdown-like formatting for AI responses
const formattedText = text.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>');
bubble.innerHTML = formattedText;
chatWindow.appendChild(bubble);
chatWindow.scrollTop = chatWindow.scrollHeight;
}
// Exponential backoff retry logic
async function fetchWithRetry(url, options, maxRetries = 5) {
for (let i = 0; i < maxRetries; i++) {
try {
const response = await fetch(url, options);
if (!response.ok) {
// Throw error to trigger retry if rate-limited or transient failure
if (response.status === 429 || response.status >= 500) {
throw new Error(`Server error: ${response.status}`);
}
// For other errors (like 400 Bad Request), don't retry, just throw
throw new Error(`API Error: ${response.statusText}`);
}
return response;
} catch (error) {
// Only retry on specific server/rate limit errors
if (i < maxRetries - 1 && (error.message.includes('Server error: 429') || error.message.includes('Server error: 5'))) {
const delay = Math.pow(2, i) * 1000 + Math.random() * 1000; // 1s, 2s, 4s...
await new Promise(resolve => setTimeout(resolve, delay));
// console.warn(`Retrying after delay: ${delay / 1000}s`);
} else {
throw error;
}
}
}
throw new Error('Max retries exceeded.');
}
// Main function to send message to AI
async function sendMessage() {
const message = userInput.value.trim();
if (!message) return;
// 1. Display user message and clear input
appendMessage(message, 'user');
userInput.value = '';
// 2. Disable input and show loading
userInput.disabled = true;
sendButton.disabled = true;
sendIcon.classList.add('hidden');
loadingSpinner.classList.remove('hidden');
try {
// UPDATED: Persona for Graham Bishop's Research Assistant
const systemPrompt = "You are Graham Bishop's automated research assistant, specialized in European financial market regulation, policy, and economics. Your goal is to provide concise, factual, and professional summaries (under 50 words) based on the latest available information. Maintain a respectful and formal tone.";
const payload = {
contents: [{ parts: [{ text: message }] }],
tools: [{ "google_search": {} }], // Use search for grounding
systemInstruction: {
parts: [{ text: systemPrompt }]
},
};
const options = {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
};
const response = await fetchWithRetry(apiUrl, options);
const result = await response.json();
const aiText = result.candidates?.[0]?.content?.parts?.[0]?.text || "I apologize. I encountered a technical issue retrieving the required information. Please try rephrasing your request.";
// 3. Display AI message
appendMessage(aiText, 'ai');
} catch (error) {
console.error("Gemini API call failed:", error);
appendMessage("I apologize, but there seems to be a connection issue with the service. Kindly check your network or attempt your query later.", 'ai');
} finally {
// 4. Re-enable input and hide loading
userInput.disabled = false;
sendButton.disabled = false;
sendIcon.classList.remove('hidden');
loadingSpinner.classList.add('hidden');
userInput.focus();
}
}
</script>
</body>
</html>