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