mihailik commited on
Commit
a19df13
·
1 Parent(s): 4bec26a

More ES6 imports.

Browse files
Files changed (1) hide show
  1. chat-full.js +151 -167
chat-full.js CHANGED
@@ -1,181 +1,165 @@
1
- (() => {
2
- const $ = (s) => document.querySelector(s);
3
- const messagesEl = $("#messages");
4
- const statusEl = $("#status");
5
- const statusText = $("#status-text");
6
- const modelSel = $("#model");
7
- const promptEl = $("#prompt");
8
- const sendBtn = $("#send");
9
- const form = $("#composer");
10
-
11
- // Режими/стан
12
- let busy = false;
13
- let currentModel = modelSel.value || "Xenova/distilgpt2";
14
- const cache = new Map(); // model -> { pipe, task }
15
- const chatHistory = []; // { role: 'user'|'assistant', content: string }
16
-
17
- // Зручні хелпери UI
18
- function setStatus(text, isBusy) {
19
- statusText.textContent = text;
20
- messagesEl.setAttribute("aria-busy", isBusy ? "true" : "false");
21
- busy = !!isBusy;
22
- promptEl.disabled = !!isBusy;
23
- sendBtn.disabled = !!isBusy;
24
- modelSel.disabled = !!isBusy;
25
- }
26
- function pushMsg(role, text, details) {
27
- const div = document.createElement("div");
28
- div.className = `msg ${role}`;
29
- if (role === 'sys' && details) {
30
- // Показати повідомлення з розгортанням stack trace
31
- div.innerHTML = `<span>${text}</span><pre class="error-details">${escapeHtml(details)}</pre>`;
32
- } else {
33
- div.textContent = text;
34
- }
35
- messagesEl.appendChild(div);
36
- messagesEl.scrollTop = messagesEl.scrollHeight;
37
- }
38
-
39
- // Захист від XSS у stack trace
40
- function escapeHtml(str) {
41
- return String(str).replace(/[&<>"']/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]));
42
- }
43
 
44
- // Визначення задачі для моделі
45
- function taskForModel(model) {
46
- if ((model || "").toLowerCase().includes("t5")) return "text2text-generation";
47
- return "text-generation";
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
48
  }
49
-
50
- // Побудова простого prompt’а (мінімально універсальний)
51
- function buildPrompt(model, history) {
52
- const task = taskForModel(model);
53
- const lastUser = history.slice().reverse().find(m => m.role === "user");
54
- const userText = lastUser ? lastUser.content : "";
55
-
56
- if (task === "text2text-generation") {
57
- // T5 зазвичай у форматі інструкції
58
- return `Instruction: Answer briefly.\nInput: ${userText}\nOutput:`;
59
- } else {
60
- // GPT-подібні моделі простий діалоговий патерн
61
- const sys = "You are a helpful assistant. Answer briefly.";
62
- const turns = history.map(m => (m.role === "user" ? `User: ${m.content}` : `Assistant: ${m.content}`));
63
- return `${sys}\n${turns.join("\n")}\nAssistant:`;
64
- }
 
 
 
 
 
 
 
 
 
 
 
65
  }
66
-
67
- // Завантаження/кешування пайплайну
68
- async function ensurePipeline(model) {
69
- if (cache.has(model)) return cache.get(model);
70
- const task = taskForModel(model);
71
- setStatus(`Завантаження моделі (${task})…`, true);
72
- try {
73
- // Перевірка наявності Transformers.js
74
- if (!window.transformers || typeof window.transformers.pipeline !== 'function') {
75
- const msg = "Transformers.js не завантажено або недоступно. Перевірте підключення скрипта (має бути перед chat-full.js).";
76
- const err = new Error(msg);
77
- setStatus("Помилка завантаження моделі", false);
78
- pushMsg("sys", `Помилка при завантаженні '${model}': ${msg}`,
79
- err.stack);
80
- throw err;
81
- }
82
- // window.transformers.env.backends.onnx.wasm.wasmPaths = ...
83
- const pipe = await window.transformers.pipeline(task, model);
84
- const entry = { pipe, task };
85
- cache.set(model, entry);
86
- setStatus("Готово", false);
87
- return entry;
88
- } catch (err) {
89
  setStatus("Помилка завантаження моделі", false);
90
- pushMsg("sys", `Помилка при завантаженні '${model}': ${String(err && err.message || err)}`,
91
- err && err.stack ? err.stack : undefined);
92
  throw err;
93
  }
 
 
 
 
 
 
 
 
 
 
94
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
95
 
96
- async function generateAndReply() {
97
- const model = currentModel;
98
- const { pipe, task } = await ensurePipeline(model);
99
- const prompt = buildPrompt(model, chatHistory);
100
-
101
- setStatus("Генерація відповіді…", true);
102
- try {
103
- // Параметри генерації обрані консервативно для мобільних пристроїв
104
- const genOpts = {
105
- max_new_tokens: 64,
106
- temperature: 0.8,
107
- top_p: 0.9,
108
- do_sample: true
109
- };
110
- const out = await pipe(prompt, genOpts);
111
-
112
- // Узгодження виходу для різних пайплайнів
113
- let fullText = "";
114
- if (Array.isArray(out) && out.length) {
115
- // text-generation: [{ generated_text }]
116
- // text2text-generation: [{ generated_text }]
117
- fullText = out[0].generated_text ?? out[0].summary_text ?? String(out[0].text || "");
118
- } else if (typeof out === "string") {
119
- fullText = out;
120
- } else {
121
- fullText = JSON.stringify(out);
122
- }
123
-
124
- // Для text-generation відповідь часто містить prompt + продовження — відтягуємо лише продовження
125
- let reply = fullText;
126
- if (task === "text-generation" && fullText.startsWith(prompt)) {
127
- reply = fullText.slice(prompt.length);
128
- }
129
-
130
- reply = (reply || "").trim();
131
- if (!reply) reply = "(порожня відповідь)";
132
-
133
- chatHistory.push({ role: "assistant", content: reply });
134
- pushMsg("bot", reply);
135
- } catch (err) {
136
- pushMsg("sys", `Помилка генерації: ${String(err && err.message || err)}`,
137
- err && err.stack ? err.stack : undefined);
138
- } finally {
139
- setStatus("Готово", false);
140
  }
 
 
 
 
 
 
 
 
 
 
 
141
  }
 
 
 
 
 
 
142
 
143
- // Обробники подій
144
- modelSel.addEventListener("change", async () => {
145
- currentModel = modelSel.value;
146
- // Не завантажуємо модель негайно — лише коли треба згенерувати.
147
- // Але повідомимо користувача:
148
- pushMsg("sys", `Обрано модель: ${currentModel}`);
149
- });
150
-
151
- form.addEventListener("submit", async (e) => {
152
- e.preventDefault();
153
- if (busy) return;
154
- const text = (promptEl.value || "").trim();
155
- if (!text) return;
156
-
157
- chatHistory.push({ role: "user", content: text });
158
- pushMsg("me", text);
159
-
160
- promptEl.value = "";
161
- await generateAndReply();
162
- promptEl.focus();
163
- });
164
-
165
- // Стартовий стан
166
  promptEl.focus();
 
167
 
168
- // Глобальний обробник помилок
169
- window.onerror = function (msg, url, line, col, error) {
170
- let details = '';
171
- if (error && error.stack) {
172
- details = error.stack;
173
- } else {
174
- details = `${msg} at ${url}:${line}:${col}`;
175
- }
176
- pushMsg('sys', `global unhandled: ${msg}`, details);
177
- // Не перериваємо стандартну поведінку
178
- return false;
179
- };
180
 
181
- })();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
 
2
+ import * as transformers from 'https://cdn.jsdelivr.net/npm/@xenova/transformers/+esm';
3
+
4
+ const $ = (s) => document.querySelector(s);
5
+ const messagesEl = $("#messages");
6
+ const statusEl = $("#status");
7
+ const statusText = $("#status-text");
8
+ const modelSel = $("#model");
9
+ const promptEl = $("#prompt");
10
+ const sendBtn = $("#send");
11
+ const form = $("#composer");
12
+
13
+ // Режими/стан
14
+ let busy = false;
15
+ let currentModel = modelSel.value || "Xenova/distilgpt2";
16
+ const cache = new Map(); // model -> { pipe, task }
17
+ const chatHistory = []; // { role: 'user'|'assistant', content: string }
18
+
19
+ // Зручні хелпери UI
20
+ function setStatus(text, isBusy) {
21
+ statusText.textContent = text;
22
+ messagesEl.setAttribute("aria-busy", isBusy ? "true" : "false");
23
+ busy = !!isBusy;
24
+ promptEl.disabled = !!isBusy;
25
+ sendBtn.disabled = !!isBusy;
26
+ modelSel.disabled = !!isBusy;
27
+ }
28
+ function pushMsg(role, text, details) {
29
+ const div = document.createElement("div");
30
+ div.className = `msg ${role}`;
31
+ if (role === 'sys' && details) {
32
+ div.innerHTML = `<span>${text}</span><pre class="error-details">${escapeHtml(details)}</pre>`;
33
+ } else {
34
+ div.textContent = text;
35
  }
36
+ messagesEl.appendChild(div);
37
+ messagesEl.scrollTop = messagesEl.scrollHeight;
38
+ }
39
+
40
+ // Захист від XSS у stack trace
41
+ function escapeHtml(str) {
42
+ return String(str).replace(/[&<>"']/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]));
43
+ }
44
+
45
+ // Визначення задачі для моделі
46
+ function taskForModel(model) {
47
+ if ((model || "").toLowerCase().includes("t5")) return "text2text-generation";
48
+ return "text-generation";
49
+ }
50
+
51
+ // Побудова простого prompt’а (мінімально універсальний)
52
+ function buildPrompt(model, history) {
53
+ const task = taskForModel(model);
54
+ const lastUser = history.slice().reverse().find(m => m.role === "user");
55
+ const userText = lastUser ? lastUser.content : "";
56
+
57
+ if (task === "text2text-generation") {
58
+ return `Instruction: Answer briefly.\nInput: ${userText}\nOutput:`;
59
+ } else {
60
+ const sys = "You are a helpful assistant. Answer briefly.";
61
+ const turns = history.map(m => (m.role === "user" ? `User: ${m.content}` : `Assistant: ${m.content}`));
62
+ return `${sys}\n${turns.join("\n")}\nAssistant:`;
63
  }
64
+ }
65
+
66
+ // Завантаження/кешування пайплайну
67
+ async function ensurePipeline(model) {
68
+ if (cache.has(model)) return cache.get(model);
69
+ const task = taskForModel(model);
70
+ setStatus(`Завантаження моделі (${task})…`, true);
71
+ try {
72
+ if (!transformers || typeof transformers.pipeline !== 'function') {
73
+ const msg = "Transformers.js (ESM) не завантажено або недоступно. Перевірте імпорт.";
74
+ const err = new Error(msg);
 
 
 
 
 
 
 
 
 
 
 
 
75
  setStatus("Помилка завантаження моделі", false);
76
+ pushMsg("sys", `Помилка при завантаженні '${model}': ${msg}`,
77
+ err.stack);
78
  throw err;
79
  }
80
+ const pipe = await transformers.pipeline(task, model);
81
+ const entry = { pipe, task };
82
+ cache.set(model, entry);
83
+ setStatus("Готово", false);
84
+ return entry;
85
+ } catch (err) {
86
+ setStatus("Помилка завантаження моделі", false);
87
+ pushMsg("sys", `Помилка при завантаженні '${model}': ${String(err && err.message || err)}`,
88
+ err && err.stack ? err.stack : undefined);
89
+ throw err;
90
  }
91
+ }
92
+
93
+ async function generateAndReply() {
94
+ const model = currentModel;
95
+ const { pipe, task } = await ensurePipeline(model);
96
+ const prompt = buildPrompt(model, chatHistory);
97
+
98
+ setStatus("Генерація відповіді…", true);
99
+ try {
100
+ const genOpts = {
101
+ max_new_tokens: 64,
102
+ temperature: 0.8,
103
+ top_p: 0.9,
104
+ do_sample: true
105
+ };
106
+ const out = await pipe(prompt, genOpts);
107
+
108
+ let fullText = "";
109
+ if (Array.isArray(out) && out.length) {
110
+ fullText = out[0].generated_text ?? out[0].summary_text ?? String(out[0].text || "");
111
+ } else if (typeof out === "string") {
112
+ fullText = out;
113
+ } else {
114
+ fullText = JSON.stringify(out);
115
+ }
116
 
117
+ let reply = fullText;
118
+ if (task === "text-generation" && fullText.startsWith(prompt)) {
119
+ reply = fullText.slice(prompt.length);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
120
  }
121
+
122
+ reply = (reply || "").trim();
123
+ if (!reply) reply = "(порожня відповідь)";
124
+
125
+ chatHistory.push({ role: "assistant", content: reply });
126
+ pushMsg("bot", reply);
127
+ } catch (err) {
128
+ pushMsg("sys", `Помилка генерації: ${String(err && err.message || err)}`,
129
+ err && err.stack ? err.stack : undefined);
130
+ } finally {
131
+ setStatus("Готово", false);
132
  }
133
+ }
134
+
135
+ modelSel.addEventListener("change", async () => {
136
+ currentModel = modelSel.value;
137
+ pushMsg("sys", `Обрано модель: ${currentModel}`);
138
+ });
139
 
140
+ form.addEventListener("submit", async (e) => {
141
+ e.preventDefault();
142
+ if (busy) return;
143
+ const text = (promptEl.value || "").trim();
144
+ if (!text) return;
145
+
146
+ chatHistory.push({ role: "user", content: text });
147
+ pushMsg("me", text);
148
+
149
+ promptEl.value = "";
150
+ await generateAndReply();
 
 
 
 
 
 
 
 
 
 
 
 
151
  promptEl.focus();
152
+ });
153
 
154
+ promptEl.focus();
 
 
 
 
 
 
 
 
 
 
 
155
 
156
+ window.onerror = function (msg, url, line, col, error) {
157
+ let details = '';
158
+ if (error && error.stack) {
159
+ details = error.stack;
160
+ } else {
161
+ details = `${msg} at ${url}:${line}:${col}`;
162
+ }
163
+ pushMsg('sys', `global unhandled: ${msg}`, details);
164
+ return false;
165
+ };