| <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 }); | |
| }); | |
| // Initial load | |
| createWorker(modelSelect.value); | |
| </script> | |
| </body> | |
| </html> | |