GeminiMCPChatbot / index.html
om4r932's picture
Translate into English
7087e54 verified
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ChatBot Gemini</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Google Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
}
.container {
width: 100%;
max-width: 1200px;
height: 100vh;
background: white;
border-radius: 16px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
overflow: hidden;
display: flex;
flex-direction: column;
}
/* Login Page Styles */
.login-container {
display: flex;
align-items: center;
justify-content: center;
height: 100vh;
background: white;
}
.login-form {
background: white;
padding: 48px;
border-radius: 16px;
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.1);
width: 100%;
max-width: 400px;
text-align: center;
}
.login-form h1 {
color: #1a73e8;
font-size: 32px;
font-weight: 400;
margin-bottom: 8px;
}
.login-form p {
color: #5f6368;
font-size: 16px;
margin-bottom: 32px;
}
.input-group {
margin-bottom: 24px;
text-align: left;
}
.input-group label {
display: block;
color: #202124;
font-size: 14px;
font-weight: 500;
margin-bottom: 8px;
}
.input-group input {
width: 100%;
padding: 16px;
border: 2px solid #dadce0;
border-radius: 8px;
font-size: 16px;
transition: all 0.2s ease;
background: #fafafa;
}
.input-group input:focus {
outline: none;
border-color: #1a73e8;
background: white;
box-shadow: 0 0 0 3px rgba(26, 115, 232, 0.1);
}
.btn-primary {
background: #1a73e8;
color: white;
border: none;
padding: 16px 32px;
border-radius: 8px;
font-size: 16px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s ease;
width: 100%;
}
.btn-primary:hover {
background: #1557b0;
box-shadow: 0 2px 8px rgba(26, 115, 232, 0.3);
}
/* Chat Interface Styles */
.chat-container {
display: none;
height: 100vh;
flex-direction: column;
}
.chat-header {
background: #1a73e8;
color: white;
padding: 16px 24px;
display: flex;
align-items: center;
justify-content: space-between;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.header-left {
display: flex;
align-items: center;
gap: 16px;
}
.header-title {
font-size: 20px;
font-weight: 500;
}
.header-controls {
display: flex;
align-items: center;
gap: 16px;
}
.model-select {
background: rgba(255, 255, 255, 0.1);
border: 1px solid rgba(255, 255, 255, 0.2);
color: white;
padding: 8px 12px;
border-radius: 8px;
font-size: 14px;
}
.model-select option {
background: #1a73e8;
color: white;
}
.toggle-container {
display: flex;
align-items: center;
gap: 8px;
}
.toggle {
position: relative;
width: 48px;
height: 24px;
background: rgba(255, 255, 255, 0.2);
border-radius: 12px;
cursor: pointer;
transition: all 0.3s ease;
}
.toggle.active {
background: #34a853;
}
.toggle-slider {
position: absolute;
top: 2px;
left: 2px;
width: 20px;
height: 20px;
background: white;
border-radius: 50%;
transition: all 0.3s ease;
}
.toggle.active .toggle-slider {
transform: translateX(24px);
}
.logout-btn {
background: rgba(255, 255, 255, 0.1);
border: 1px solid rgba(255, 255, 255, 0.2);
color: white;
padding: 8px 16px;
border-radius: 8px;
cursor: pointer;
font-size: 14px;
transition: all 0.2s ease;
}
.logout-btn:hover {
background: rgba(255, 255, 255, 0.2);
}
.chat-messages {
flex: 1;
padding: 24px;
overflow-y: auto;
background: #f8f9fa;
display: flex;
flex-direction: column;
gap: 16px;
}
.message {
display: flex;
gap: 12px;
max-width: 80%;
animation: slideIn 0.3s ease;
}
.message.user {
align-self: flex-end;
flex-direction: row-reverse;
}
.message-avatar {
width: 40px;
height: 40px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-weight: 500;
font-size: 14px;
flex-shrink: 0;
}
.message.user .message-avatar {
background: #1a73e8;
color: white;
}
.message.assistant .message-avatar {
background: #34a853;
color: white;
}
.message-content {
background: white;
padding: 16px;
border-radius: 16px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
line-height: 1.5;
word-wrap: break-word;
}
.message.user .message-content {
background: #1a73e8;
color: white;
}
.chat-input-container {
padding: 24px;
background: white;
border-top: 1px solid #e0e0e0;
}
.chat-input-wrapper {
display: flex;
gap: 12px;
align-items: flex-end;
}
.chat-input {
flex: 1;
padding: 16px;
border: 2px solid #dadce0;
border-radius: 24px;
font-size: 16px;
resize: none;
min-height: 24px;
max-height: 120px;
transition: all 0.2s ease;
}
.chat-input:focus {
outline: none;
border-color: #1a73e8;
box-shadow: 0 0 0 3px rgba(26, 115, 232, 0.1);
}
.send-btn {
background: #1a73e8;
color: white;
border: none;
width: 48px;
height: 48px;
border-radius: 50%;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.2s ease;
flex-shrink: 0;
}
.send-btn:hover {
background: #1557b0;
transform: scale(1.05);
}
.send-btn:disabled {
background: #dadce0;
cursor: not-allowed;
transform: none;
}
.typing-indicator {
display: none;
align-items: center;
gap: 12px;
max-width: 80%;
animation: slideIn 0.3s ease;
}
.typing-dots {
background: white;
padding: 16px;
border-radius: 16px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
display: flex;
gap: 4px;
}
.typing-dot {
width: 8px;
height: 8px;
border-radius: 50%;
background: #dadce0;
animation: typing 1.4s infinite ease-in-out;
}
.typing-dot:nth-child(2) {
animation-delay: 0.2s;
}
.typing-dot:nth-child(3) {
animation-delay: 0.4s;
}
@keyframes slideIn {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes typing {
0%, 60%, 100% {
transform: scale(1);
background: #dadce0;
}
30% {
transform: scale(1.2);
background: #1a73e8;
}
}
.error-message {
background: #fce8e6;
color: #d93025;
padding: 12px 16px;
border-radius: 8px;
margin-bottom: 16px;
border-left: 4px solid #d93025;
}
@media (max-width: 768px) {
.container {
border-radius: 0;
height: 100vh;
}
.login-form {
padding: 32px 24px;
margin: 16px;
}
.header-controls {
flex-direction: column;
gap: 8px;
align-items: flex-end;
}
.message {
max-width: 90%;
}
.chat-input-container {
padding: 16px;
}
}
</style>
</head>
<body>
<div class="container">
<!-- Login Page -->
<div id="loginPage" class="login-container">
<div class="login-form">
<h1>ChatBot Gemini</h1>
<p>Login with your API key</p>
<div id="loginError"></div>
<form id="loginForm">
<div class="input-group">
<label for="apiKey">API Key</label>
<input type="password" id="apiKey" placeholder="Votre clé API..." required>
</div>
<button type="submit" class="btn-primary">Log In</button>
</form>
</div>
</div>
<!-- Chat Interface -->
<div id="chatPage" class="chat-container">
<div class="chat-header">
<div class="header-left">
<div class="header-title">ChatBot Gemini</div>
</div>
<div class="header-controls">
<select id="modelSelect" class="model-select">
<!-- Models will be populated dynamically -->
</select>
<div class="toggle-container">
<span style="font-size: 14px;">Tools</span>
<div id="toolToggle" class="toggle active">
<div class="toggle-slider"></div>
</div>
</div>
<button id="logoutBtn" class="logout-btn">Log Out</button>
</div>
</div>
<div id="chatMessages" class="chat-messages">
<div class="message assistant">
<div class="message-avatar">AI</div>
<div class="message-content">
Hi ! I'm your AI assistant. How can I help you today ?
</div>
</div>
</div>
<div class="typing-indicator" id="typingIndicator">
<div class="message-avatar" style="background: #34a853; color: white;">AI</div>
<div class="typing-dots">
<div class="typing-dot"></div>
<div class="typing-dot"></div>
<div class="typing-dot"></div>
</div>
</div>
<div class="chat-input-container">
<div class="chat-input-wrapper">
<textarea id="chatInput" class="chat-input" placeholder="Tapez votre message..." rows="1"></textarea>
<button id="sendBtn" class="send-btn">
<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
<path d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z"/>
</svg>
</button>
</div>
</div>
</div>
</div>
<script>
class ChatBot {
constructor() {
this.sessionId = '';
this.currentModel = 'llama-3.3-70b-versatile';
this.toolsEnabled = true;
this.isTyping = false;
this.availableModels = [];
this.baseUrl = window.location.origin; // Utilise l'URL de votre serveur FastAPI
this.initializeElements();
this.bindEvents();
this.loadFromStorage();
}
initializeElements() {
// Login elements
this.loginPage = document.getElementById('loginPage');
this.loginForm = document.getElementById('loginForm');
this.apiKeyInput = document.getElementById('apiKey');
this.loginError = document.getElementById('loginError');
// Chat elements
this.chatPage = document.getElementById('chatPage');
this.chatMessages = document.getElementById('chatMessages');
this.chatInput = document.getElementById('chatInput');
this.sendBtn = document.getElementById('sendBtn');
this.modelSelect = document.getElementById('modelSelect');
this.toolToggle = document.getElementById('toolToggle');
this.logoutBtn = document.getElementById('logoutBtn');
this.typingIndicator = document.getElementById('typingIndicator');
}
bindEvents() {
// Login events
this.loginForm.addEventListener('submit', (e) => this.handleLogin(e));
// Chat events
this.sendBtn.addEventListener('click', () => this.sendMessage());
this.chatInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
this.sendMessage();
}
});
// Auto-resize textarea
this.chatInput.addEventListener('input', () => {
this.chatInput.style.height = 'auto';
this.chatInput.style.height = Math.min(this.chatInput.scrollHeight, 120) + 'px';
});
// Header controls
this.modelSelect.addEventListener('change', (e) => {
this.currentModel = e.target.value;
this.saveToStorage();
});
this.toolToggle.addEventListener('click', () => {
this.toolsEnabled = !this.toolsEnabled;
this.toolToggle.classList.toggle('active', this.toolsEnabled);
this.saveToStorage();
});
this.logoutBtn.addEventListener('click', () => this.logout());
}
async handleLogin(e) {
e.preventDefault();
const apiKey = this.apiKeyInput.value.trim();
if (!apiKey) {
this.showLoginError('Please enter a valid API key');
return;
}
try {
const response = await fetch(`${this.baseUrl}/init`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
api_key: apiKey
})
});
const data = await response.json();
if (data.success) {
this.sessionId = data.session_id;
this.availableModels = data.models || [];
this.populateModelSelect();
this.saveToStorage();
this.showChatInterface();
this.loginError.innerHTML = '';
} else {
this.showLoginError(data.error || 'Connection error.');
}
} catch (error) {
this.showLoginError('Server connection error. Please retry.');
console.error('Login error:', error);
}
}
populateModelSelect() {
this.modelSelect.innerHTML = '';
this.availableModels.forEach(model => {
const option = document.createElement('option');
option.value = model;
option.textContent = model;
this.modelSelect.appendChild(option);
});
// Set default model if available
if (this.availableModels.includes(this.currentModel)) {
this.modelSelect.value = this.currentModel;
} else if (this.availableModels.length > 0) {
this.currentModel = this.availableModels[0];
this.modelSelect.value = this.currentModel;
}
}
showLoginError(message) {
this.loginError.innerHTML = `<div class="error-message">${message}</div>`;
}
showChatInterface() {
this.loginPage.style.display = 'none';
this.chatPage.style.display = 'flex';
this.chatInput.focus();
}
async logout() {
try {
if (this.sessionId) {
await fetch(`${this.baseUrl}/logout`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
session_id: this.sessionId
})
});
}
} catch (error) {
console.error('Logout error:', error);
}
this.sessionId = '';
this.availableModels = [];
localStorage.removeItem('chatbot_data');
this.chatPage.style.display = 'none';
this.loginPage.style.display = 'flex';
this.apiKeyInput.value = '';
this.clearMessages();
}
async sendMessage() {
const message = this.chatInput.value.trim();
if (!message || this.isTyping || !this.sessionId) return;
// Add user message
this.addMessage('user', message);
this.chatInput.value = '';
this.chatInput.style.height = 'auto';
// Show typing indicator
this.showTyping(true);
try {
const response = await fetch(`${this.baseUrl}/chat`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
session_id: this.sessionId,
query: message,
tool_use: this.toolsEnabled,
model: this.currentModel
})
});
const data = await response.json();
this.showTyping(false);
if (data.error) {
this.addMessage('assistant', `Erreur: ${data.error}`);
} else {
this.addMessage('assistant', data.output);
}
} catch (error) {
this.showTyping(false);
this.addMessage('assistant', `Erreur de connexion: ${error.message}`);
console.error('Chat error:', error);
}
this.saveToStorage();
}
addMessage(role, content) {
const messageDiv = document.createElement('div');
messageDiv.className = `message ${role}`;
const avatar = document.createElement('div');
avatar.className = 'message-avatar';
avatar.textContent = role === 'user' ? 'U' : 'AI';
const messageContent = document.createElement('div');
messageContent.className = 'message-content';
messageContent.textContent = content;
messageDiv.appendChild(avatar);
messageDiv.appendChild(messageContent);
// Vérifier si typingIndicator est bien un enfant de chatMessages
if (this.typingIndicator && this.typingIndicator.parentNode === this.chatMessages) {
this.chatMessages.insertBefore(messageDiv, this.typingIndicator);
} else {
// Si typingIndicator n'est pas présent ou pas un enfant, ajouter à la fin
this.chatMessages.appendChild(messageDiv);
}
this.scrollToBottom();
}
showTyping(show) {
this.isTyping = show;
this.typingIndicator.style.display = show ? 'flex' : 'none';
this.sendBtn.disabled = show;
this.scrollToBottom();
}
scrollToBottom() {
setTimeout(() => {
this.chatMessages.scrollTop = this.chatMessages.scrollHeight;
}, 100);
}
clearMessages() {
const messages = this.chatMessages.querySelectorAll('.message');
messages.forEach((msg, index) => {
if (index > 0) { // Keep welcome message
msg.remove();
}
});
}
saveToStorage() {
const data = {
sessionId: this.sessionId,
currentModel: this.currentModel,
toolsEnabled: this.toolsEnabled,
availableModels: this.availableModels
};
localStorage.setItem('chatbot_data', JSON.stringify(data));
}
loadFromStorage() {
const saved = localStorage.getItem('chatbot_data');
if (saved) {
try {
const data = JSON.parse(saved);
if (data.sessionId) {
this.sessionId = data.sessionId;
this.currentModel = data.currentModel || 'models/gemini-2.0-flash';
this.toolsEnabled = data.toolsEnabled !== undefined ? data.toolsEnabled : true;
this.availableModels = data.availableModels || [];
// Update UI
this.populateModelSelect();
this.toolToggle.classList.toggle('active', this.toolsEnabled);
// Verify session is still valid
this.verifySession();
}
} catch (error) {
console.error('Error loading from storage:', error);
localStorage.removeItem('chatbot_data');
}
}
}
async verifySession() {
try {
// Test if session is still valid by making a simple request
const response = await fetch(`${this.baseUrl}/chat`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
session_id: this.sessionId,
query: 'test',
tool_use: false,
model: this.currentModel
})
});
if (response.ok) {
this.showChatInterface();
} else {
// Session invalid, clear storage and show login
localStorage.removeItem('chatbot_data');
this.sessionId = '';
}
} catch (error) {
// Connection error, clear session
localStorage.removeItem('chatbot_data');
this.sessionId = '';
}
}
}
// Initialize the chatbot when the page loads
document.addEventListener('DOMContentLoaded', () => {
new ChatBot();
});
</script>
</body>
</html>