Download app.js from alkasima/Hakkillo: direct link, hf CLI and curl.
- Browser
- Download file 5.59 kB
-
https://huggingface.co/spaces/alkasima/Hakkillo/resolve/main/app.js
- Command line
-
hf download hf://spaces/alkasima/Hakkillo/app.js
-
curl -L -o app.js https://huggingface.co/spaces/alkasima/Hakkillo/resolve/main/app.js
5.59 kB
| import { feedbackText } from './feedback.mjs'; | |
| const $ = (id) => document.getElementById(id); | |
| const worker = new Worker(new URL('./worker.js', import.meta.url), { type: 'module' }); | |
| let busy = false; | |
| let loaded = false; | |
| let history = []; | |
| let responseNode; | |
| let pendingExchange; | |
| let lastExchange; | |
| let modelMetadata; | |
| function setBusy(value) { | |
| busy = value; | |
| $('prompt').disabled = value || !loaded; | |
| $('send').disabled = value || !loaded; | |
| $('clear').disabled = value || !loaded; | |
| $('load').disabled = value; | |
| $('copy-feedback').disabled = value || !lastExchange; | |
| document.querySelectorAll('.suggestion').forEach((button) => { button.disabled = value || !loaded; }); | |
| } | |
| function message(role, text) { | |
| const element = document.createElement('div'); | |
| element.className = `message ${role}`; | |
| const speaker = document.createElement('span'); | |
| speaker.className = 'speaker'; | |
| speaker.textContent = role === 'user' ? 'You' : 'Hakkillo'; | |
| const content = document.createElement('span'); | |
| content.textContent = text; | |
| element.append(speaker, content); | |
| $('messages').append(element); | |
| element.scrollIntoView({ block: 'nearest' }); | |
| return content; | |
| } | |
| function fail(error) { | |
| $('status').textContent = error; | |
| $('progress').hidden = true; | |
| if (history.at(-1)?.role === 'user') history.pop(); | |
| if (responseNode && !responseNode.textContent) responseNode.textContent = 'The model could not finish this reply.'; | |
| pendingExchange = null; | |
| setBusy(false); | |
| } | |
| $('load').addEventListener('click', () => { | |
| setBusy(true); | |
| $('status').textContent = 'Preparing your model download…'; | |
| $('progress').hidden = false; | |
| worker.postMessage({ type: 'load' }); | |
| }); | |
| worker.addEventListener('message', ({ data }) => { | |
| if (data.type === 'progress') { | |
| $('status').textContent = data.message; | |
| if (data.percent != null) $('progress').value = data.percent; | |
| } else if (data.type === 'ready') { | |
| loaded = true; | |
| modelMetadata = data.metadata; | |
| $('welcome').hidden = true; | |
| $('progress').hidden = true; | |
| $('status').textContent = 'Model ready · messages stay in this browser'; | |
| $('prompt').placeholder = 'Ask a short question in English…'; | |
| setBusy(false); | |
| $('prompt').focus(); | |
| } else if (data.type === 'token') { | |
| responseNode.textContent += data.text; | |
| $('messages').scrollTop = $('messages').scrollHeight; | |
| } else if (data.type === 'answer') { | |
| responseNode.textContent = data.text || 'I could not produce an answer. Try a shorter question.'; | |
| history.push({ role: 'assistant', content: data.text }); | |
| history = history.slice(-4); | |
| lastExchange = { ...pendingExchange, response: data.text }; | |
| pendingExchange = null; | |
| $('feedback-panel').hidden = false; | |
| $('status').textContent = `Answered in ${data.seconds}s · experimental model`; | |
| setBusy(false); | |
| $('prompt').focus(); | |
| } else if (data.type === 'error') { | |
| fail(`Could not complete this request: ${data.message}. If loading failed, reload the page to retry.`); | |
| } | |
| }); | |
| worker.addEventListener('error', () => fail('The model worker could not start. Check your connection and reload this page.')); | |
| $('chat-form').addEventListener('submit', (event) => { | |
| event.preventDefault(); | |
| const text = $('prompt').value.trim(); | |
| if (!loaded || busy || !text) return; | |
| setBusy(true); | |
| $('feedback-panel').hidden = true; | |
| $('feedback-status').textContent = ''; | |
| $('feedback-fallback').hidden = true; | |
| $('status').textContent = 'Thinking on your CPU…'; | |
| message('user', text); | |
| responseNode = message('assistant', ''); | |
| const starter = [...document.querySelectorAll('.suggestion')].find((button) => button.dataset.prompt === text); | |
| pendingExchange = { prompt: text, context: history.map((turn) => ({ ...turn })), case_id: starter?.dataset.case || null }; | |
| history.push({ role: 'user', content: text }); | |
| $('prompt').value = ''; | |
| worker.postMessage({ type: 'generate', messages: history }); | |
| }); | |
| $('prompt').addEventListener('keydown', (event) => { | |
| if (event.key === 'Enter' && !event.shiftKey) { | |
| event.preventDefault(); | |
| $('chat-form').requestSubmit(); | |
| } | |
| }); | |
| function clearConversation() { | |
| history = []; | |
| responseNode = null; | |
| pendingExchange = null; | |
| lastExchange = null; | |
| $('messages').replaceChildren(); | |
| $('prompt').value = ''; | |
| $('feedback-panel').hidden = true; | |
| $('feedback-fallback').hidden = true; | |
| $('feedback-fallback').value = ''; | |
| $('feedback-status').textContent = ''; | |
| $('status').textContent = 'New conversation · model stays loaded'; | |
| } | |
| $('clear').addEventListener('click', clearConversation); | |
| document.querySelectorAll('.suggestion').forEach((button) => button.addEventListener('click', () => { | |
| if (busy || !loaded) return; | |
| clearConversation(); | |
| $('prompt').value = button.dataset.prompt; | |
| $('chat-form').requestSubmit(); | |
| })); | |
| $('copy-feedback').addEventListener('click', async () => { | |
| if (!lastExchange || !modelMetadata || busy) return; | |
| const text = feedbackText(lastExchange, modelMetadata); | |
| try { | |
| await navigator.clipboard.writeText(text); | |
| $('feedback-status').textContent = 'Copied. Review the text before posting it.'; | |
| } catch { | |
| $('feedback-fallback').hidden = false; | |
| $('feedback-fallback').value = text; | |
| $('feedback-fallback').focus(); | |
| $('feedback-fallback').select(); | |
| $('feedback-status').textContent = 'Copy the selected text manually, then review it before posting.'; | |
| } | |
| }); | |
| fetch('./model.json').then((response) => response.json()).then((config) => { | |
| if (config.download_mb) $('download-size').textContent = `${config.download_mb} MB`; | |
| }).catch(() => {}); | |