| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>Local LLM Chat</title> |
| <script src="https://cdn.tailwindcss.com"></script> |
| <script src="https://unpkg.com/feather-icons"></script> |
| <script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script> |
| <script src="https://cdn.jsdelivr.net/npm/animejs/lib/anime.iife.min.js"></script> |
| <style> |
| @keyframes fadeIn { |
| from { opacity: 0; transform: translateY(10px); } |
| to { opacity: 1; transform: translateY(0); } |
| } |
| .message-animation { |
| animation: fadeIn 0.3s ease-out forwards; |
| } |
| .chat-container { |
| height: calc(100vh - 160px); |
| } |
| .typing-indicator::after { |
| content: '...'; |
| animation: typing 1.5s infinite; |
| } |
| @keyframes typing { |
| 0% { content: '.'; } |
| 33% { content: '..'; } |
| 66% { content: '...'; } |
| } |
| </style> |
| </head> |
| <body class="bg-gray-50 text-gray-800 font-sans"> |
| <div class="max-w-4xl mx-auto h-screen flex flex-col"> |
| |
| <header class="bg-white shadow-sm py-4 px-6 flex items-center justify-between"> |
| <div class="flex items-center space-x-3"> |
| <div class="w-10 h-10 rounded-full bg-gradient-to-r from-purple-500 to-blue-500 flex items-center justify-center"> |
| <i data-feather="cpu" class="text-white"></i> |
| </div> |
| <h1 class="text-xl font-semibold">Local LLM Chat</h1> |
| </div> |
| <button class="p-2 rounded-full hover:bg-gray-100 transition-colors"> |
| <i data-feather="settings" class="text-gray-500"></i> |
| </button> |
| </header> |
|
|
| |
| <div class="chat-container overflow-y-auto p-6 space-y-4 flex-1"> |
| |
| <div class="flex justify-start" data-aos="fade-right"> |
| <div class="max-w-xs md:max-w-md lg:max-w-lg bg-white rounded-2xl p-4 shadow-sm"> |
| <div class="flex items-start space-x-2"> |
| <div class="w-8 h-8 rounded-full bg-gray-200 flex items-center justify-center"> |
| <i data-feather="cpu" class="text-gray-600 w-4 h-4"></i> |
| </div> |
| <div> |
| <p class="text-sm font-medium text-gray-700">Local LLM</p> |
| <p class="text-gray-600 mt-1">Hello! I'm your local AI assistant. How can I help you today?</p> |
| </div> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div class="flex justify-end" data-aos="fade-left"> |
| <div class="max-w-xs md:max-w-md lg:max-w-lg bg-blue-500 text-white rounded-2xl p-4 shadow-sm"> |
| <p>What can you do?</p> |
| </div> |
| </div> |
|
|
| |
| <div class="flex justify-start"> |
| <div class="max-w-xs md:max-w-md lg:max-w-lg bg-white rounded-2xl p-4 shadow-sm"> |
| <div class="flex items-start space-x-2"> |
| <div class="w-8 h-8 rounded-full bg-gray-200 flex items-center justify-center"> |
| <i data-feather="cpu" class="text-gray-600 w-4 h-4"></i> |
| </div> |
| <div> |
| <p class="text-sm font-medium text-gray-700">Local LLM</p> |
| <p class="text-gray-600 mt-1">I can answer questions, help with creative writing, summarize text, and much more - all running locally on your machine for privacy.</p> |
| </div> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div id="typing-indicator" class="flex justify-start hidden"> |
| <div class="max-w-xs md:max-w-md lg:max-w-lg bg-white rounded-2xl p-4 shadow-sm"> |
| <div class="flex items-start space-x-2"> |
| <div class="w-8 h-8 rounded-full bg-gray-200 flex items-center justify-center"> |
| <i data-feather="cpu" class="text-gray-600 w-4 h-4"></i> |
| </div> |
| <div> |
| <p class="text-sm font-medium text-gray-700">Local LLM</p> |
| <p class="text-gray-600 mt-1 typing-indicator">Thinking</p> |
| </div> |
| </div> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div class="bg-white border-t border-gray-200 p-4"> |
| <form id="chat-form" class="flex items-center space-x-2"> |
| <div class="flex-1 relative"> |
| <input |
| id="message-input" |
| type="text" |
| placeholder="Type your message..." |
| class="w-full px-4 py-3 rounded-full border border-gray-300 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all" |
| autocomplete="off" |
| > |
| <button type="button" class="absolute right-3 top-3 text-gray-400 hover:text-blue-500"> |
| <i data-feather="paperclip"></i> |
| </button> |
| </div> |
| <button |
| type="submit" |
| class="w-12 h-12 rounded-full bg-gradient-to-r from-purple-500 to-blue-500 text-white flex items-center justify-center hover:opacity-90 transition-opacity" |
| > |
| <i data-feather="send"></i> |
| </button> |
| </form> |
| <p class="text-xs text-gray-500 mt-2 text-center">Your conversations stay on your device</p> |
| </div> |
| </div> |
|
|
| <script> |
| feather.replace(); |
| |
| |
| const chatForm = document.getElementById('chat-form'); |
| const messageInput = document.getElementById('message-input'); |
| const chatContainer = document.querySelector('.chat-container'); |
| const typingIndicator = document.getElementById('typing-indicator'); |
| |
| chatForm.addEventListener('submit', function(e) { |
| e.preventDefault(); |
| const message = messageInput.value.trim(); |
| if (!message) return; |
| |
| |
| addMessage(message, 'user'); |
| messageInput.value = ''; |
| |
| |
| typingIndicator.classList.remove('hidden'); |
| chatContainer.scrollTop = chatContainer.scrollHeight; |
| |
| |
| setTimeout(() => { |
| typingIndicator.classList.add('hidden'); |
| addMessage(getRandomResponse(), 'ai'); |
| }, 1500 + Math.random() * 2000); |
| }); |
| |
| function addMessage(text, sender) { |
| const messageDiv = document.createElement('div'); |
| messageDiv.classList.add('flex', 'message-animation'); |
| |
| if (sender === 'user') { |
| messageDiv.classList.add('justify-end'); |
| messageDiv.innerHTML = ` |
| <div class="max-w-xs md:max-w-md lg:max-w-lg bg-blue-500 text-white rounded-2xl p-4 shadow-sm"> |
| <p>${text}</p> |
| </div> |
| `; |
| } else { |
| messageDiv.classList.add('justify-start'); |
| messageDiv.innerHTML = ` |
| <div class="max-w-xs md:max-w-md lg:max-w-lg bg-white rounded-2xl p-4 shadow-sm"> |
| <div class="flex items-start space-x-2"> |
| <div class="w-8 h-8 rounded-full bg-gray-200 flex items-center justify-center"> |
| <i data-feather="cpu" class="text-gray-600 w-4 h-4"></i> |
| </div> |
| <div> |
| <p class="text-sm font-medium text-gray-700">Local LLM</p> |
| <p class="text-gray-600 mt-1">${text}</p> |
| </div> |
| </div> |
| </div> |
| `; |
| } |
| |
| chatContainer.appendChild(messageDiv); |
| chatContainer.scrollTop = chatContainer.scrollHeight; |
| feather.replace(); |
| } |
| |
| function getRandomResponse() { |
| const responses = [ |
| "That's an interesting question. Based on my training data, I would say...", |
| "I can help with that. Here's what I know about this topic...", |
| "Great question! The answer depends on several factors...", |
| "I'm glad you asked. This is a complex topic, but in summary...", |
| "Let me think about that for a moment. My analysis suggests..." |
| ]; |
| return responses[Math.floor(Math.random() * responses.length)]; |
| } |
| </script> |
| </body> |
| </html> |
|
|