| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8" /> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"/> |
| <title>Mobile Chat App with Transformers.js</title> |
| <style> |
| body { |
| margin: 0; |
| font-family: sans-serif; |
| display: flex; |
| flex-direction: column; |
| height: 100vh; |
| background: #f5f5f5; |
| } |
| #model-select { |
| padding: 10px; |
| font-size: 1em; |
| width: 100%; |
| box-sizing: border-box; |
| } |
| #chat { |
| flex: 1; |
| overflow-y: auto; |
| padding: 10px; |
| background: #fff; |
| } |
| .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; |
| } |
| #send-btn { |
| padding: 10px; |
| font-size: 1em; |
| } |
| #status { |
| text-align: center; |
| padding: 5px; |
| font-size: 0.9em; |
| color: #555; |
| } |
| </style> |
| </head> |
| <body> |
| <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> |
| <div id="status">Select a model to begin</div> |
| <div id="chat"></div> |
| <div id="input-area"> |
| <input type="text" id="user-input" placeholder="Type a message..." /> |
| <button id="send-btn">Send</button> |
| </div> |
|
|
| <script type="module"> |
| import { pipeline } from 'https://cdn.jsdelivr.net/npm/@xenova/transformers@3.7.1/dist/transformers.min.js'; |
| |
| let worker = new Worker(URL.createObjectURL(new Blob([` |
| importScripts('https://cdn.jsdelivr.net/npm/@xenova/transformers@3.7.1/dist/transformers.min.js'); |
| |
| let chatPipeline = null; |
| let currentModel = null; |
| |
| self.onmessage = async (e) => { |
| const { type, data } = e.data; |
| try { |
| if (type === 'load') { |
| self.postMessage({ type: 'status', data: 'Loading model...' }); |
| chatPipeline = await window.pipeline('text-generation', data.model, { progress_callback: (x) => self.postMessage({ type: 'status', data: 'Progress: ' + x }) }); |
| currentModel = data.model; |
| self.postMessage({ type: 'status', data: 'Model loaded: ' + currentModel }); |
| } else if (type === 'generate') { |
| if (!chatPipeline) throw new Error('Model not loaded'); |
| self.postMessage({ type: 'status', data: 'Generating response...' }); |
| const output = await chatPipeline(data.text, { max_new_tokens: 100 }); |
| self.postMessage({ type: 'response', data: output[0].generated_text }); |
| self.postMessage({ type: 'status', data: 'Ready' }); |
| } |
| } catch (err) { |
| self.postMessage({ type: 'error', data: err.stack }); |
| } |
| }; |
| |
| self.onerror = (e) => { |
| self.postMessage({ type: 'error', data: e.message + '\\n' + e.filename + ':' + e.lineno }); |
| }; |
| `], { type: 'application/javascript' }))); |
| |
| const chat = document.getElementById('chat'); |
| const input = document.getElementById('user-input'); |
| const sendBtn = document.getElementById('send-btn'); |
| const modelSelect = document.getElementById('model-select'); |
| const status = document.getElementById('status'); |
| |
| let isBusy = false; |
| |
| function appendMessage(text, sender) { |
| const msg = document.createElement('div'); |
| msg.className = 'message ' + sender; |
| msg.textContent = text; |
| chat.appendChild(msg); |
| chat.scrollTop = chat.scrollHeight; |
| } |
| |
| function setStatus(text) { |
| status.textContent = text; |
| } |
| |
| function handleError(err) { |
| appendMessage('Error: ' + err, 'bot'); |
| setStatus('Error occurred'); |
| } |
| |
| window.onerror = (msg, src, line, col, err) => { |
| handleError(err?.stack || msg); |
| }; |
| |
| worker.onmessage = (e) => { |
| const { type, data } = e.data; |
| if (type === 'response') { |
| appendMessage(data, 'bot'); |
| isBusy = false; |
| } else if (type === 'status') { |
| setStatus(data); |
| } else if (type === 'error') { |
| handleError(data); |
| isBusy = false; |
| } |
| }; |
| |
| modelSelect.addEventListener('change', () => { |
| const model = modelSelect.value; |
| isBusy = true; |
| worker.postMessage({ type: 'load', data: { model } }); |
| }); |
| |
| sendBtn.addEventListener('click', () => { |
| if (isBusy) return; |
| const text = input.value.trim(); |
| if (!text) return; |
| appendMessage(text, 'user'); |
| input.value = ''; |
| isBusy = true; |
| worker.postMessage({ type: 'generate', data: { text } }); |
| }); |
| |
| input.addEventListener('keydown', (e) => { |
| if (e.key === 'Enter') sendBtn.click(); |
| }); |
| |
| |
| window.addEventListener('load', () => { |
| modelSelect.dispatchEvent(new Event('change')); |
| }); |
| </script> |
| </body> |
| </html> |
|
|