|  | <!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> | 
					
						
						|  |  |