| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8" /> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"/> |
| <title>AI Chat App</title> |
| <style> |
| body { |
| margin: 0; |
| font-family: sans-serif; |
| display: flex; |
| flex-direction: column; |
| height: 100vh; |
| background: #f5f5f5; |
| } |
| header { |
| padding: 10px; |
| background: #333; |
| color: white; |
| text-align: center; |
| } |
| #model-select { |
| width: 100%; |
| padding: 10px; |
| font-size: 1em; |
| border: none; |
| outline: none; |
| } |
| #chat { |
| flex: 1; |
| overflow-y: auto; |
| padding: 10px; |
| background: white; |
| display: flex; |
| flex-direction: column; |
| } |
| .message { |
| margin: 10px 0; |
| padding: 10px; |
| border-radius: 5px; |
| max-width: 80%; |
| word-wrap: break-word; |
| } |
| .user { |
| background: #d1e7dd; |
| align-self: flex-end; |
| } |
| .bot { |
| background: #f8d7da; |
| align-self: flex-start; |
| } |
| #input-area { |
| display: flex; |
| padding: 10px; |
| background: #eee; |
| } |
| #user-input { |
| flex: 1; |
| padding: 10px; |
| font-size: 1em; |
| border: 1px solid #ccc; |
| border-radius: 5px; |
| } |
| #send-btn { |
| padding: 10px 20px; |
| margin-left: 10px; |
| font-size: 1em; |
| border: none; |
| background: #333; |
| color: white; |
| border-radius: 5px; |
| cursor: pointer; |
| } |
| #send-btn:disabled { |
| background: #999; |
| cursor: not-allowed; |
| } |
| </style> |
| </head> |
| <body> |
| <header> |
| <h1>AI Chat App</h1> |
| <select id="model-select"> |
| <option value="Xenova/distilgpt2">Xenova/distilgpt2</option> |
| <option value="Xenova/phi-3-mini-4k-instruct">Xenova/phi-3-mini-4k-instruct</option> |
| <option value="Xenova/t5-small">Xenova/t5-small</option> |
| <option value="Xenova/gemma-2b-it">Xenova/gemma-2b-it</option> |
| <option value="Xenova/llama-3-8b-instruct">Xenova/llama-3-8b-instruct</option> |
| <option value="Xenova/Mistral-7B-Instruct-v0.2">Xenova/Mistral-7B-Instruct-v0.2</option> |
| </select> |
| </header> |
| <div id="chat"></div> |
| <div id="input-area"> |
| <input type="text" id="user-input" placeholder="Type your message..." /> |
| <button id="send-btn">Send</button> |
| </div> |
|
|
| <script type="module"> |
| const chat = document.getElementById('chat'); |
| const input = document.getElementById('user-input'); |
| const sendBtn = document.getElementById('send-btn'); |
| const modelSelect = document.getElementById('model-select'); |
| |
| let busy = false; |
| let worker; |
| |
| function appendMessage(text, sender) { |
| const div = document.createElement('div'); |
| div.className = `message ${sender}`; |
| div.textContent = text; |
| chat.appendChild(div); |
| chat.scrollTop = chat.scrollHeight; |
| } |
| |
| function setBusy(state) { |
| busy = state; |
| sendBtn.disabled = state; |
| input.disabled = state; |
| } |
| |
| function createWorker(modelName) { |
| if (worker) worker.terminate(); |
| |
| const workerCode = ` |
| import { pipeline } from 'https://cdn.jsdelivr.net/npm/@xenova/transformers@3.7.1/dist/transformers.min.js'; |
| |
| let generator = null; |
| |
| self.onmessage = async (e) => { |
| const { type, message } = e.data; |
| try { |
| if (type === 'load') { |
| self.postMessage({ type: 'status', message: 'Loading model: ' + message }); |
| generator = await pipeline('text-generation', message); |
| self.postMessage({ type: 'ready' }); |
| } else if (type === 'generate') { |
| if (!generator) throw new Error('Model not loaded'); |
| const output = await generator(message, { max_new_tokens: 100 }); |
| self.postMessage({ type: 'response', message: output[0].generated_text }); |
| } |
| } catch (err) { |
| self.postMessage({ type: 'error', message: err.stack }); |
| } |
| }; |
| `; |
| |
| const blob = new Blob([workerCode], { type: 'application/javascript' }); |
| worker = new Worker(URL.createObjectURL(blob), { type: 'module' }); |
| |
| worker.onmessage = (e) => { |
| const { type, message } = e.data; |
| if (type === 'status') { |
| appendMessage(message, 'bot'); |
| } else if (type === 'ready') { |
| appendMessage('Model loaded and ready!', 'bot'); |
| setBusy(false); |
| } else if (type === 'response') { |
| appendMessage(message, 'bot'); |
| setBusy(false); |
| } else if (type === 'error') { |
| appendMessage('Error: ' + message, 'bot'); |
| setBusy(false); |
| } |
| }; |
| |
| setBusy(true); |
| worker.postMessage({ type: 'load', message: modelName }); |
| } |
| |
| modelSelect.addEventListener('change', () => { |
| const selected = modelSelect.value; |
| createWorker(selected); |
| }); |
| |
| sendBtn.addEventListener('click', () => { |
| const text = input.value.trim(); |
| if (!text || busy) return; |
| appendMessage(text, 'user'); |
| input.value = ''; |
| setBusy(true); |
| worker.postMessage({ type: 'generate', message: text }); |
| }); |
| |
| |
| createWorker(modelSelect.value); |
| </script> |
| </body> |
| </html> |
|
|