Spaces:
Runtime error
Runtime error
| <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> | |