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