|
<!doctype html> |
|
<html lang="uk"> |
|
|
|
<head> |
|
<meta charset="utf-8" /> |
|
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" /> |
|
<title>Локальний “Hello World” чат • Transformers.js</title> |
|
<style> |
|
:root { |
|
color-scheme: light dark; |
|
--bg: Canvas; |
|
--fg: CanvasText; |
|
--muted: color-mix(in oklab, var(--fg) 60%, transparent); |
|
--border: color-mix(in oklab, var(--fg) 25%, transparent); |
|
--bubble-me: color-mix(in oklab, var(--bg) 94%, var(--fg) 6%); |
|
--bubble-bot: color-mix(in oklab, var(--bg) 88%, var(--fg) 12%); |
|
--accent: #2f7bfd; |
|
} |
|
|
|
* { |
|
box-sizing: border-box; |
|
} |
|
|
|
html, |
|
body { |
|
height: 100%; |
|
} |
|
|
|
body { |
|
margin: 0; |
|
font-family: system-ui, -apple-system, "Segoe UI", Roboto, Ubuntu, Cantarell, "Noto Sans", Arial, "Apple Color Emoji", "Segoe UI Emoji"; |
|
background: var(--bg); |
|
color: var(--fg); |
|
line-height: 1.35; |
|
} |
|
|
|
header { |
|
position: sticky; |
|
top: 0; |
|
z-index: 10; |
|
padding: 12px 16px; |
|
border-bottom: 1px solid var(--border); |
|
backdrop-filter: blur(6px); |
|
background: color-mix(in oklab, var(--bg) 88%, transparent); |
|
} |
|
|
|
header .row { |
|
display: flex; |
|
gap: 8px; |
|
align-items: center; |
|
flex-wrap: wrap; |
|
} |
|
|
|
header h1 { |
|
font-size: 1.05rem; |
|
margin: 0; |
|
margin-right: auto; |
|
} |
|
|
|
select, |
|
button, |
|
input, |
|
textarea { |
|
font: inherit; |
|
color: inherit; |
|
background: transparent; |
|
border: 1px solid var(--border); |
|
border-radius: 10px; |
|
padding: 8px 10px; |
|
} |
|
|
|
select { |
|
min-width: 270px; |
|
} |
|
|
|
.muted { |
|
color: var(--muted); |
|
font-size: .92rem; |
|
} |
|
|
|
main { |
|
display: grid; |
|
grid-template-rows: 1fr auto; |
|
height: calc(100dvh - 64px); |
|
} |
|
|
|
#messages { |
|
overflow-y: auto; |
|
padding: 14px 12px 8px; |
|
scroll-behavior: smooth; |
|
} |
|
|
|
.msg { |
|
max-width: 72ch; |
|
border: 1px solid var(--border); |
|
padding: 10px 12px; |
|
border-radius: 14px; |
|
margin: 8px 0; |
|
word-wrap: break-word; |
|
white-space: pre-wrap; |
|
} |
|
|
|
.msg.me { |
|
background: var(--bubble-me); |
|
margin-left: auto; |
|
} |
|
|
|
.msg.bot { |
|
background: var(--bubble-bot); |
|
margin-right: auto; |
|
} |
|
|
|
.msg.sys { |
|
font-size: .9rem; |
|
border-style: dashed; |
|
color: var(--muted); |
|
} |
|
.error-details { |
|
font-size: .88em; |
|
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; |
|
background: color-mix(in oklab, var(--bg) 96%, var(--fg) 4%); |
|
color: var(--muted); |
|
margin: 6px 0 0 0; |
|
padding: 7px 10px; |
|
border-radius: 7px; |
|
white-space: pre-wrap; |
|
overflow-x: auto; |
|
} |
|
|
|
form#composer { |
|
position: sticky; |
|
bottom: 0; |
|
padding: 10px 12px 14px; |
|
border-top: 1px solid var(--border); |
|
background: color-mix(in oklab, var(--bg) 94%, transparent); |
|
} |
|
|
|
.composer-row { |
|
display: grid; |
|
grid-template-columns: 1fr auto; |
|
gap: 8px; |
|
align-items: end; |
|
} |
|
|
|
textarea#prompt { |
|
width: 100%; |
|
min-height: 44px; |
|
max-height: 35dvh; |
|
resize: vertical; |
|
padding: 10px 12px; |
|
} |
|
|
|
button#send { |
|
background: color-mix(in oklab, var(--bg) 92%, transparent); |
|
color: inherit; |
|
border: 1px solid var(--border); |
|
padding: 10px 14px; |
|
border-radius: 12px; |
|
cursor: pointer; |
|
} |
|
|
|
button:disabled { |
|
opacity: .6; |
|
cursor: not-allowed; |
|
} |
|
|
|
.status { |
|
display: flex; |
|
gap: 8px; |
|
align-items: center; |
|
padding: 6px 0 2px; |
|
} |
|
|
|
.dot { |
|
width: 8px; |
|
height: 8px; |
|
border-radius: 50%; |
|
background: var(--border); |
|
box-shadow: 0 0 0 0 var(--border); |
|
animation: pulse 1.2s infinite ease-in-out; |
|
} |
|
|
|
@keyframes pulse { |
|
0% { |
|
transform: scale(1); |
|
box-shadow: 0 0 0 0 color-mix(in oklab, var(--accent) 22%, transparent); |
|
} |
|
|
|
70% { |
|
transform: scale(1.1); |
|
box-shadow: 0 0 0 6px transparent; |
|
} |
|
|
|
100% { |
|
transform: scale(1); |
|
box-shadow: 0 0 0 0 transparent; |
|
} |
|
} |
|
|
|
|
|
@media (min-width: 720px) { |
|
main { |
|
height: calc(100vh - 64px); |
|
} |
|
} |
|
</style> |
|
</head> |
|
|
|
<body> |
|
<header> |
|
<div class="row"> |
|
<h1>Локальний чат: 1</h1> |
|
<label for="model" class="muted" style="display:none">Модель</label> |
|
<select id="model" title="Оберіть модель (Xenova)"> |
|
<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 (text2text)</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> |
|
<div id="status" class="status muted"> |
|
<span class="dot" aria-hidden="true"></span> |
|
<span id="status-text">Готово</span> |
|
</div> |
|
</header> |
|
|
|
<main> |
|
<div id="messages" aria-live="polite" aria-busy="false"> |
|
<div class="msg sys">Підказка: спершу спробуйте маленькі моделі (наприклад, distilgpt2 або t5-small). Великі |
|
(7B/8B) можуть не поміститися в памʼять браузера.</div> |
|
</div> |
|
|
|
<form id="composer" autocomplete="off"> |
|
<div class="composer-row"> |
|
<textarea id="prompt" placeholder="Напишіть повідомлення…" required></textarea> |
|
<button id="send" type="submit">Надіслати</button> |
|
</div> |
|
</form> |
|
</main> |
|
|
|
|
|
<script type="module" src="chat-full.js"></script> |
|
</body> |
|
|
|
|