mihailik commited on
Commit
a79ad88
·
1 Parent(s): 3e8e6a4

Trying another one in chat.html

Browse files
Files changed (3) hide show
  1. chat.html +38 -0
  2. chat.js +27 -0
  3. index.html +64 -82
chat.html ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!doctype html>
2
+ <html lang="uk">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>Локальний Чат — каркас</title>
7
+ <style>
8
+ :root { color-scheme: light dark; }
9
+ * { box-sizing: border-box; }
10
+ body { margin: 0; font-family: system-ui, -apple-system, Segoe UI, Roboto, Ubuntu, Cantarell, Noto Sans, Arial; }
11
+ header { position: sticky; top: 0; padding: 12px 16px; border-bottom: 1px solid rgba(127,127,127,.25); backdrop-filter: blur(6px); }
12
+ main { padding: 16px; }
13
+ .row { display: flex; gap: 8px; }
14
+ input[type="text"] { flex: 1; padding: 10px 12px; border: 1px solid rgba(127,127,127,.35); border-radius: 10px; background: transparent; color: inherit; }
15
+ button { padding: 10px 14px; border-radius: 10px; border: 1px solid rgba(127,127,127,.35); background: transparent; color: inherit; cursor: pointer; }
16
+ button:disabled { opacity: .6; cursor: not-allowed; }
17
+ #messages { min-height: 40vh; padding-bottom: 12px; }
18
+ .msg { padding: 10px 12px; margin: 8px 0; border: 1px solid rgba(127,127,127,.25); border-radius: 12px; max-width: 70ch; }
19
+ .me { background: color-mix(in oklab, Canvas 92%, transparent); }
20
+ .bot { background: color-mix(in oklab, Canvas 85%, transparent); }
21
+ </style>
22
+ </head>
23
+ <body>
24
+ <header>
25
+ <strong>Локальний Чат (каркас)</strong>
26
+ <div style="opacity:.75">Цей файл підключає <code>chat.js</code> для логіки.</div>
27
+ </header>
28
+ <main>
29
+ <div id="messages"></div>
30
+ <div class="row">
31
+ <input id="prompt" type="text" placeholder="Напишіть повідомлення..." />
32
+ <button id="send">Надіслати</button>
33
+ </div>
34
+ </main>
35
+
36
+ <script src="./chat.js"></script>
37
+ </body>
38
+ </html>
chat.js ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ (function(){
2
+ const $ = (sel) => document.querySelector(sel);
3
+ const messages = $('#messages');
4
+ const input = $('#prompt');
5
+ const sendBtn = $('#send');
6
+
7
+ function push(role, text){
8
+ const div = document.createElement('div');
9
+ div.className = `msg ${role}`;
10
+ div.textContent = text;
11
+ messages.appendChild(div);
12
+ messages.scrollTop = messages.scrollHeight;
13
+ }
14
+
15
+ sendBtn?.addEventListener('click', () => {
16
+ const text = input.value.trim();
17
+ if (!text) return;
18
+ push('me', text);
19
+ input.value = '';
20
+ // Placeholder response to show wiring works.
21
+ setTimeout(() => push('bot', 'Це заглушка відповіді з chat.js. (Модель ще не підключено)'), 300);
22
+ });
23
+
24
+ input?.addEventListener('keydown', (e) => {
25
+ if (e.key === 'Enter') sendBtn?.click();
26
+ });
27
+ })();
index.html CHANGED
@@ -890,87 +890,68 @@ If you can answer the question directly with your existing knowledge or after us
890
 
891
  // --- Dynamic Trial Models Discovery (tokenless) ---
892
  async function discoverOpenSmallModels(maxModels = 6) {
893
- const collected = new Set();
894
- const results = [];
895
- const SEARCH_ENDPOINTS = [
896
- // Popular small-ish text gen models by downloads
897
- `${env.remoteURL}/api/models?pipeline_tag=text-generation&sort=downloads&direction=-1&limit=50`,
898
- // Fallback: general search for tiny / distil / mini / small
899
- `${env.remoteURL}/api/models?search=distil&limit=25`,
900
- `${env.remoteURL}/api/models?search=tinyllama&limit=25`,
901
- `${env.remoteURL}/api/models?search=phi-2&limit=25`,
902
- `${env.remoteURL}/api/models?search=qwen2.5-0.5b&limit=25`,
903
- `${env.remoteURL}/api/models?search=smol&limit=25`
904
  ];
905
- const nameAllowPatterns = [
906
- /distilgpt/i,
907
- /gpt2$/i,
908
- /tinyllama/i,
909
- /phi[-_]?1|phi[-_]?2/i,
910
- /qwen2?\.5?[-_]?0\.5b/i,
911
- /smol/i,
912
- /mini[-_]?llama/i
913
  ];
914
- function nameLooksSmall(id) {
915
- return nameAllowPatterns.some(rx => rx.test(id));
916
- }
917
- function quickHeuristicIsSmall(modelInfo) {
918
- // Basic gates first.
919
- if (modelInfo.private || modelInfo.gated || modelInfo.disabled) return false;
920
- const id = modelInfo.id || '';
921
- if (nameLooksSmall(id)) return true;
922
- // Try tags heuristics.
923
- const tags = modelInfo.tags || [];
924
- if (tags.some(t => /tiny|micro|mini|small|distil/.test(t))) return true;
925
- return false;
926
- }
927
- async function fetchJSON(url) {
928
  try {
929
- const r = await fetch(url, { headers: { 'Accept': 'application/json' } });
930
- if (!r.ok) throw new Error(r.status + ' ' + r.statusText);
931
- return await r.json();
932
  } catch (e) {
933
- appendDiagnostic('Discovery fetch fail: ' + url + ' :: ' + e.message);
934
  return null;
935
  }
936
  }
937
- for (const url of SEARCH_ENDPOINTS) {
938
- if (results.length >= maxModels) break;
939
- const data = await fetchJSON(url);
940
- if (!Array.isArray(data)) continue;
941
- for (const m of data) {
942
- if (results.length >= maxModels) break;
943
- const id = m.modelId || m.id; // API inconsistent keys
944
- if (!id || collected.has(id)) continue;
945
- if (!quickHeuristicIsSmall({ ...m, id })) continue;
946
- if (m.pipeline_tag && m.pipeline_tag !== 'text-generation') continue; // prefer text-generation
947
- collected.add(id);
948
- results.push(id);
 
 
 
 
949
  }
 
950
  }
951
- // Ensure some baseline fallbacks at end if discovery too small
952
- const FALLBACKS = ['Xenova/gpt2','Xenova/distilgpt2'];
953
- for (const f of FALLBACKS) if (!results.includes(f)) results.push(f);
954
- // Ensure fallbacks appear first (already pushed at end if missing; reorder)
955
- const ordered = FALLBACKS.filter(f=>results.includes(f)).concat(results.filter(r=>!FALLBACKS.includes(r)));
956
- return ordered.slice(0, maxModels);
957
  }
958
 
959
  trialModelsBtn.addEventListener('click', async () => {
960
- const trialResultsDiv = document.getElementById('trial-results');
961
- trialResultsDiv.style.display = 'block';
962
- trialResultsDiv.innerHTML = '<b>Discovering open small models (no token)...</b>';
963
- const TRIAL_PROMPT = 'Do planes fly higher than bees?';
964
  trialModelsBtn.disabled = true;
965
- const progressList = document.createElement('ul');
966
- progressList.style.fontSize = '0.7rem';
967
- trialResultsDiv.appendChild(progressList);
968
- const addProgress = (text) => {
969
- const li = document.createElement('li');
970
- li.textContent = text;
971
- progressList.appendChild(li);
972
- trialResultsDiv.scrollTop = trialResultsDiv.scrollHeight;
973
- };
974
  const yieldUI = async () => new Promise(r=>requestAnimationFrame(r));
975
  function withTimeout(promise, ms, label) {
976
  return Promise.race([
@@ -984,24 +965,24 @@ If you can answer the question directly with your existing knowledge or after us
984
  discovered = await discoverOpenSmallModels(10);
985
  } catch(e) {
986
  appendDiagnostic('Discovery error: ' + e.message);
 
987
  }
988
  if (!discovered.length) {
989
- addProgress('No models discovered dynamically. Using static fallbacks.');
990
  discovered = ['Xenova/gpt2','Xenova/distilgpt2'];
991
  }
992
- // Ensure baseline (gpt2 + distilgpt2) attempted first regardless of discovery order
993
  const baseline = ['Xenova/gpt2','Xenova/distilgpt2'];
994
  const ordered = baseline.concat(discovered.filter(m=>!baseline.includes(m)));
995
- // Limit total trials for responsiveness
996
  const MODELS = ordered.slice(0,6);
997
- addProgress('Models to try: ' + MODELS.join(', '));
998
  appendDiagnostic('Trial: Models -> ' + discovered.join(', '));
999
  const collected = [];
1000
  try {
1001
  for (const modelId of MODELS) {
1002
  let loadTime='-', genTime='-', snippet='', error=null;
1003
- let t0 = performance.now();
1004
- addProgress(`Loading ${modelId} ...`);
 
1005
  try {
1006
  const pipe = await withTimeout(pipeline('text-generation', modelId, { quantized: true }), 20000, 'load');
1007
  const t1 = performance.now();
@@ -1010,11 +991,12 @@ If you can answer the question directly with your existing knowledge or after us
1010
  loadTime = ((t1-t0)/1000).toFixed(2)+'s';
1011
  genTime = ((t2-t1)/1000).toFixed(2)+'s';
1012
  const full = Array.isArray(out) ? (out[0]?.generated_text||'') : (out.generated_text||'');
1013
- snippet = full.trim().slice(0,400) || '(empty)';
1014
- addProgress(`${modelId} ✓ load ${loadTime} gen ${genTime}`);
 
1015
  } catch(e) {
1016
  error = e?.message || String(e);
1017
- addProgress(`${modelId} ✗ ${error}`);
1018
  appendDiagnostic('Trial error '+modelId+': '+error);
1019
  }
1020
  collected.push({ model:modelId, loadTime, genTime, snippet, error });
@@ -1023,16 +1005,16 @@ If you can answer the question directly with your existing knowledge or after us
1023
  } finally {
1024
  trialModelsBtn.disabled = false;
1025
  }
1026
- let md = '### Trial Results (Dynamic, No Token)\n';
 
1027
  for (const r of collected) {
1028
  if (r.error) {
1029
- md += `- **${r.model}**: ERROR: ${r.error}\n`;
1030
  } else {
1031
- md += `- **${r.model}** (Load ${r.loadTime} / Gen ${r.genTime})\n Response: ${r.snippet.replace(/\n+/g,' ')}\n`;
1032
  }
1033
  }
1034
- appendMessage({ role:'system', content: md });
1035
- appendDiagnostic('Trial: dynamic markdown summary appended to chat.');
1036
  });
1037
 
1038
  // Event Listeners
 
890
 
891
  // --- Dynamic Trial Models Discovery (tokenless) ---
892
  async function discoverOpenSmallModels(maxModels = 6) {
893
+ // Curated base list of realistically loadable tokenless models published with ONNX/TFJS weights.
894
+ const CURATED = [
895
+ 'Xenova/gpt2',
896
+ 'Xenova/distilgpt2',
897
+ 'Xenova/phi-2',
898
+ 'Xenova/TinyLlama-1.1B-Chat-v1.0'
 
 
 
 
 
899
  ];
900
+ const archWhitelist = [
901
+ 'GPT2LMHeadModel',
902
+ 'PhiForCausalLM',
903
+ 'LlamaForCausalLM',
904
+ 'MistralForCausalLM',
905
+ 'TinyLlamaForCausalLM'
 
 
906
  ];
907
+ const accepted = [];
908
+ async function fetchConfig(modelId) {
909
+ const url = `${env.remoteURL}/${modelId}/resolve/main/config.json`;
 
 
 
 
 
 
 
 
 
 
 
910
  try {
911
+ const resp = await fetch(url, { headers:{ 'Accept':'application/json' } });
912
+ if (!resp.ok) throw new Error(resp.status+ ' ' + resp.statusText);
913
+ return await resp.json();
914
  } catch (e) {
915
+ appendDiagnostic('Config fail '+modelId+': '+e.message);
916
  return null;
917
  }
918
  }
919
+ for (const m of CURATED) {
920
+ if (accepted.length >= maxModels) break;
921
+ const cfg = await fetchConfig(m);
922
+ if (!cfg) continue;
923
+ const archs = cfg.architectures || [];
924
+ const ok = archs.some(a => archWhitelist.includes(a));
925
+ if (!ok) {
926
+ appendDiagnostic('Skip '+m+' (arch '+archs.join('/')+' not whitelisted)');
927
+ continue;
928
+ }
929
+ // Rough size gating: reject if hidden_size * n_layer heuristic too large (> ~4B tokens weight proxy)
930
+ const hs = cfg.hidden_size || cfg.n_embd || 0;
931
+ const nl = cfg.num_hidden_layers || cfg.n_layer || 0;
932
+ if (hs && nl && hs * nl > 20000) { // heuristic threshold
933
+ appendDiagnostic('Skip '+m+' (heuristic size too large hs*nl='+hs*nl+')');
934
+ continue;
935
  }
936
+ accepted.push(m);
937
  }
938
+ if (accepted.length === 0) {
939
+ appendDiagnostic('Discovery empty; using minimal fallback list.');
940
+ accepted.push('Xenova/gpt2','Xenova/distilgpt2');
941
+ }
942
+ return accepted.slice(0, maxModels);
 
943
  }
944
 
945
  trialModelsBtn.addEventListener('click', async () => {
 
 
 
 
946
  trialModelsBtn.disabled = true;
947
+ const TRIAL_PROMPT = 'Do planes fly higher than bees?';
948
+ // Create a live-updating system message
949
+ const liveHeader = '### Model Trials (live, no token)';
950
+ appendMessage({ role: 'system', content: liveHeader + '\nStarting discovery...' });
951
+ const liveEl = chatBox.lastElementChild; // system container
952
+ const lines = [liveHeader, 'Starting discovery...'];
953
+ const flush = () => { liveEl.textContent = lines.join('\n'); };
954
+ const addLine = (l) => { lines.push(l); flush(); };
 
955
  const yieldUI = async () => new Promise(r=>requestAnimationFrame(r));
956
  function withTimeout(promise, ms, label) {
957
  return Promise.race([
 
965
  discovered = await discoverOpenSmallModels(10);
966
  } catch(e) {
967
  appendDiagnostic('Discovery error: ' + e.message);
968
+ addLine('Discovery error: ' + e.message);
969
  }
970
  if (!discovered.length) {
971
+ addLine('No models discovered dynamically. Using static fallbacks.');
972
  discovered = ['Xenova/gpt2','Xenova/distilgpt2'];
973
  }
 
974
  const baseline = ['Xenova/gpt2','Xenova/distilgpt2'];
975
  const ordered = baseline.concat(discovered.filter(m=>!baseline.includes(m)));
 
976
  const MODELS = ordered.slice(0,6);
977
+ addLine('Models to try: ' + MODELS.join(', '));
978
  appendDiagnostic('Trial: Models -> ' + discovered.join(', '));
979
  const collected = [];
980
  try {
981
  for (const modelId of MODELS) {
982
  let loadTime='-', genTime='-', snippet='', error=null;
983
+ const t0 = performance.now();
984
+ addLine(`Loading ${modelId} ...`);
985
+ flush();
986
  try {
987
  const pipe = await withTimeout(pipeline('text-generation', modelId, { quantized: true }), 20000, 'load');
988
  const t1 = performance.now();
 
991
  loadTime = ((t1-t0)/1000).toFixed(2)+'s';
992
  genTime = ((t2-t1)/1000).toFixed(2)+'s';
993
  const full = Array.isArray(out) ? (out[0]?.generated_text||'') : (out.generated_text||'');
994
+ snippet = full.trim().slice(0,200).replace(/\n+/g,' ') || '(empty)';
995
+ addLine(`${modelId} ✓ load ${loadTime} gen ${genTime}`);
996
+ addLine(` → ${snippet}`);
997
  } catch(e) {
998
  error = e?.message || String(e);
999
+ addLine(`${modelId} ✗ ${error}`);
1000
  appendDiagnostic('Trial error '+modelId+': '+error);
1001
  }
1002
  collected.push({ model:modelId, loadTime, genTime, snippet, error });
 
1005
  } finally {
1006
  trialModelsBtn.disabled = false;
1007
  }
1008
+ addLine('');
1009
+ addLine('### Trial Summary');
1010
  for (const r of collected) {
1011
  if (r.error) {
1012
+ addLine(`- ${r.model}: ERROR ${r.error}`);
1013
  } else {
1014
+ addLine(`- ${r.model} (Load ${r.loadTime} / Gen ${r.genTime})`);
1015
  }
1016
  }
1017
+ appendDiagnostic('Trial: progress & summary streamed into chat message.');
 
1018
  });
1019
 
1020
  // Event Listeners