| | <!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-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-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"> |
| | |
| | <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> |
| |
|
| | |
| | <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"> |
| | |
| | </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; |
| | |
| | this.initializeElements(); |
| | this.bindEvents(); |
| | this.loadFromStorage(); |
| | } |
| | |
| | initializeElements() { |
| | |
| | this.loginPage = document.getElementById('loginPage'); |
| | this.loginForm = document.getElementById('loginForm'); |
| | this.apiKeyInput = document.getElementById('apiKey'); |
| | this.loginError = document.getElementById('loginError'); |
| | |
| | |
| | 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() { |
| | |
| | this.loginForm.addEventListener('submit', (e) => this.handleLogin(e)); |
| | |
| | |
| | this.sendBtn.addEventListener('click', () => this.sendMessage()); |
| | this.chatInput.addEventListener('keypress', (e) => { |
| | if (e.key === 'Enter' && !e.shiftKey) { |
| | e.preventDefault(); |
| | this.sendMessage(); |
| | } |
| | }); |
| | |
| | |
| | this.chatInput.addEventListener('input', () => { |
| | this.chatInput.style.height = 'auto'; |
| | this.chatInput.style.height = Math.min(this.chatInput.scrollHeight, 120) + 'px'; |
| | }); |
| | |
| | |
| | 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); |
| | }); |
| | |
| | |
| | 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; |
| | |
| | |
| | this.addMessage('user', message); |
| | this.chatInput.value = ''; |
| | this.chatInput.style.height = 'auto'; |
| | |
| | |
| | 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); |
| | |
| | |
| | if (this.typingIndicator && this.typingIndicator.parentNode === this.chatMessages) { |
| | this.chatMessages.insertBefore(messageDiv, this.typingIndicator); |
| | } else { |
| | |
| | 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) { |
| | 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 || []; |
| | |
| | |
| | this.populateModelSelect(); |
| | this.toolToggle.classList.toggle('active', this.toolsEnabled); |
| | |
| | |
| | this.verifySession(); |
| | } |
| | } catch (error) { |
| | console.error('Error loading from storage:', error); |
| | localStorage.removeItem('chatbot_data'); |
| | } |
| | } |
| | } |
| | |
| | async verifySession() { |
| | try { |
| | |
| | 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 { |
| | |
| | localStorage.removeItem('chatbot_data'); |
| | this.sessionId = ''; |
| | } |
| | } catch (error) { |
| | |
| | localStorage.removeItem('chatbot_data'); |
| | this.sessionId = ''; |
| | } |
| | } |
| | } |
| | |
| | |
| | document.addEventListener('DOMContentLoaded', () => { |
| | new ChatBot(); |
| | }); |
| | </script> |
| | </body> |
| | </html> |
| |
|