HarbourSOFT commited on
Commit
c58fb03
·
verified ·
1 Parent(s): 8f84bc2

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +108 -50
index.html CHANGED
@@ -2,11 +2,27 @@
2
  <html lang="en">
3
  <head>
4
  <meta charset="UTF-8" />
5
- <meta name="viewport" content="width=device-width,initial-scale=1" />
 
 
6
  <title>Qwen2.5‑Coder‑3B‑Instruct — WebGPU (ONNX)</title>
 
7
  <style>
8
- :root{--bg:#0b1021;--panel:#0e1430;--text:#e9eeff;--muted:#9fb3ff;--accent:#7aa2ff;--ok:#2ecc71;--warn:#f39c12;--err:#e74c3c;--radius:14px;--border:1px solid rgba(255,255,255,.08)}
9
- *{box-sizing:border-box}body{margin:0;background:radial-gradient(1200px 800px at 10% -10%,#1a2252 0%,transparent 60%),radial-gradient(1200px 800px at 110% 10%,#1b2d61 0%,transparent 60%),var(--bg);color:var(--text);font:16px/1.6 system-ui,-apple-system,Segoe UI,Roboto,Inter,Arial,sans-serif;padding:20px}
 
 
 
 
 
 
 
 
 
 
 
 
 
10
  header{display:flex;gap:16px;align-items:center;justify-content:space-between;padding:14px 16px;border-radius:var(--radius);background:linear-gradient(180deg,rgba(255,255,255,.06),rgba(255,255,255,.02));border:var(--border)}
11
  h1{margin:0;font-size:22px}
12
  .muted{color:var(--muted);font-size:12px}
@@ -29,13 +45,16 @@
29
  .examples{display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:10px;margin-top:10px}
30
  .examples button{justify-content:flex-start}
31
  .toggle{display:flex;align-items:center;gap:8px}
 
 
 
32
  </style>
33
  </head>
34
  <body>
35
  <header>
36
  <div>
37
  <h1>Qwen2.5‑Coder‑3B‑Instruct — WebGPU (ONNX)</h1>
38
- <div class="muted">Pure front‑end · Transformers.js + ONNX Runtime Web · WebGPU forced</div>
39
  </div>
40
  <div class="row">
41
  <button id="load">Load model</button>
@@ -46,7 +65,7 @@
46
 
47
  <div class="card">
48
  <div class="row" style="margin-bottom:8px">
49
- <span class="chip warn">First run will download ~2.1 GB (q4, smallest ONNX). Desktop Chrome/Edge recommended.</span>
50
  </div>
51
 
52
  <!-- Confirmation step -->
@@ -58,17 +77,21 @@
58
  </div>
59
  </div>
60
 
 
61
  <div id="loading" style="display:none">
62
- <div class="row">
63
  <progress id="progress" value="0" max="100"></progress>
64
  <span id="progress-label" class="muted">0%</span>
65
  </div>
66
- <div class="muted">WebGPU requires HTTPS or localhost. Chrome/Edge supported by default; Safari/Firefox may need experimental flags.</div>
 
 
67
  </div>
68
 
 
69
  <div id="ui" style="display:none">
70
  <input id="sys" type="text" placeholder="System prompt (optional): You are a senior coding assistant. Keep answers concise." />
71
- <div id="chat"></div>
72
  <textarea id="prompt" placeholder="Ask something (you can paste code)…"></textarea>
73
  <div class="row">
74
  <button id="send">Send</button>
@@ -86,14 +109,14 @@
86
 
87
  <div class="card">
88
  <h3 style="margin:6px 0">Debug log</h3>
89
- <div id="log"></div>
90
  </div>
91
 
92
  <script type="module">
93
  // ================= Configuration =================
94
  const MODEL_ID = 'onnx-community/Qwen2.5-Coder-3B-Instruct';
95
- const CDN_PRIMARY = 'https://cdn.jsdelivr.net/npm/@huggingface/transformers@3.0.0';
96
- const CDN_FALLBACK = 'https://cdn.jsdelivr.net/npm/@xenova/transformers@2.17.2';
97
 
98
  // Prefer WebGPU; gracefully fall back if unavailable
99
  const device = ('gpu' in navigator) ? 'webgpu' : 'auto';
@@ -101,7 +124,7 @@
101
  // Smallest first: q4 → q4f16 → int8 → fp16
102
  const options = {
103
  device, // prefer WebGPU; fallback to auto if not available
104
- dtype: 'q4', // smallest ONNX weights (model_q4.onnx + .onnx_data)
105
  progress_callback: setProgress,
106
  };
107
 
@@ -182,26 +205,35 @@ def f(nums):
182
  const exWrap=$('#examples');
183
  const autoRun=$('#autorun');
184
 
185
- let pipe=null, cancelled=false, abortGen=null;
 
 
 
186
 
187
  // ================= Utils =================
188
  function log(...a){
189
  const s = a.map(x => (typeof x === 'string' ? x : JSON.stringify(x))).join(' ');
190
- logDiv.textContent += s + '
191
- ';
192
  logDiv.scrollTop = logDiv.scrollHeight;
193
  console.log('[LOG]', ...a);
194
  }
195
  function chip(kind,text){ statusChip.className='chip '+(kind||''); statusChip.textContent=text; statusChip.style.display='inline-flex'; }
196
  function clearChip(){ statusChip.style.display='none'; }
197
- function setProgress(evt){ if(evt?.status==='progress'){ const pct=Math.round(Math.max(0,Math.min(100,evt.progress||0))); pbar.value=pct; plabel.textContent=`${pct}% ${evt?.name||evt?.file||''}`.trim(); } else if(evt?.status){ log(`status: ${evt.status} ${evt?.name||evt?.file||''}`); } }
 
 
 
 
 
 
 
198
 
199
  window.addEventListener('error',e=>log('window.error:',e.message,e.filename,`${e.lineno}:${e.colno}`));
200
  window.addEventListener('unhandledrejection',e=>log('unhandledrejection:',e.reason?.message||e.reason));
201
 
202
  async function importTransformers(){
203
- try{ log('Importing library:',CDN_PRIMARY); return await import(CDN_PRIMARY); }
204
- catch(e){ log('Primary failed, fallback:',CDN_FALLBACK,e?.message); return await import(CDN_FALLBACK); }
205
  }
206
 
207
  function renderExamples(){
@@ -235,7 +267,9 @@ def f(nums):
235
  cancelled=false; confirmBox.style.display='none';
236
  cancelBtn.style.display='inline-block';
237
  loading.style.display='block'; pbar.value=0; plabel.textContent='0%';
238
- const { pipeline } = await importTransformers();
 
 
239
  if (device !== 'webgpu') {
240
  log('WebGPU not detected; falling back to device=auto (WASM/CPU).');
241
  chip('warn','WebGPU not detected — using auto');
@@ -243,7 +277,12 @@ def f(nums):
243
  log('WebGPU detected.');
244
  }
245
  log('Transformers.js loaded. Creating text-generation pipeline…');
 
246
  pipe = await pipeline('text-generation', MODEL_ID, options);
 
 
 
 
247
  if(cancelled) log('Note: cancel only resets UI and cannot interrupt underlying downloads.');
248
  loading.style.display='none'; cancelBtn.style.display='none'; ui.style.display='block';
249
  chip('ok','Model ready'); setTimeout(clearChip,1200);
@@ -258,49 +297,68 @@ def f(nums):
258
  cancelBtn.addEventListener('click',()=>{ cancelled=true; loadBtn.style.display='inline-block'; cancelBtn.style.display='none'; loading.style.display='none'; log('Canceled (UI only).'); });
259
  clearBtn.addEventListener('click',()=>{ logDiv.textContent=''; });
260
 
261
- function addMsg(text, me=false){ const d=document.createElement('div'); d.className='msg '+(me?'me':'bot'); d.textContent=text; chatDiv.appendChild(d); chatDiv.scrollTop=chatDiv.scrollHeight; return d; }
 
 
 
262
 
263
  sendBtn.addEventListener('click', async ()=>{
264
  try{
265
- if(!pipe) return chip('warn','Model not loaded');
266
  const user=promptEl.value.trim(); if(!user) return; promptEl.value='';
267
  const sys=sysEl.value.trim();
268
  addMsg(user,true);
269
  const botEl=addMsg('…');
270
- stopBtn.disabled=false; chip('', 'Generating…');
271
-
272
- let outText='';
273
- abortGen = new AbortController();
274
- const sysPrefix = sys ? `[SYSTEM]
275
- ${sys}
276
-
277
- ` : '';
278
- const promptText = `${sysPrefix}[USER]
279
- ${user}
280
-
281
- [ASSISTANT]
282
- `;
283
- const gen = await pipe(
284
- promptText,
285
- {
286
- max_new_tokens: 256,
287
- temperature: 0.7,
288
- top_p: 0.9,
289
- repetition_penalty: 1.05,
290
- callback_function: (x)=>{ outText += x; botEl.textContent = outText; },
291
- signal: abortGen.signal,
292
- }
293
- );
294
- if(typeof gen?.[0]?.generated_text === 'string' && !outText){ botEl.textContent = gen[0].generated_text; }
 
 
 
 
 
 
 
 
295
  chip('ok','Done'); setTimeout(clearChip, 1200);
296
  }catch(err){
297
- if(err?.name==='AbortError'){ chip('warn','Stopped'); setTimeout(clearChip,1200); return; }
298
  log('❌ Generation error:', err?.message||err);
299
  chip('err','Generation failed');
300
- }finally{ stopBtn.disabled=true; abortGen=null; }
 
 
301
  });
302
 
303
- stopBtn.addEventListener('click',()=>{ try{ abortGen?.abort(); }catch{} });
 
 
 
 
 
 
304
  </script>
 
305
  </body>
306
- </html>
 
2
  <html lang="en">
3
  <head>
4
  <meta charset="UTF-8" />
5
+ <meta
6
+ name="viewport"
7
+ content="width=device-width,initial-scale=1,viewport-fit=cover" />
8
  <title>Qwen2.5‑Coder‑3B‑Instruct — WebGPU (ONNX)</title>
9
+ <meta name="color-scheme" content="dark light" />
10
  <style>
11
+ :root{
12
+ --bg:#0b1021; --panel:#0e1430; --text:#e9eeff; --muted:#9fb3ff; --accent:#7aa2ff;
13
+ --ok:#2ecc71; --warn:#f39c12; --err:#e74c3c; --radius:14px; --border:1px solid rgba(255,255,255,.08)
14
+ }
15
+ *{box-sizing:border-box}
16
+ body{
17
+ margin:0;
18
+ background:
19
+ radial-gradient(1200px 800px at 10% -10%,#1a2252 0%,transparent 60%),
20
+ radial-gradient(1200px 800px at 110% 10%,#1b2d61 0%,transparent 60%),
21
+ var(--bg);
22
+ color:var(--text);
23
+ font:16px/1.6 system-ui,-apple-system,Segoe UI,Roboto,Inter,Arial,sans-serif;
24
+ padding:20px
25
+ }
26
  header{display:flex;gap:16px;align-items:center;justify-content:space-between;padding:14px 16px;border-radius:var(--radius);background:linear-gradient(180deg,rgba(255,255,255,.06),rgba(255,255,255,.02));border:var(--border)}
27
  h1{margin:0;font-size:22px}
28
  .muted{color:var(--muted);font-size:12px}
 
45
  .examples{display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:10px;margin-top:10px}
46
  .examples button{justify-content:flex-start}
47
  .toggle{display:flex;align-items:center;gap:8px}
48
+ progress{width:220px;height:10px}
49
+ a.link{color:var(--accent);text-decoration:none}
50
+ a.link:hover{text-decoration:underline}
51
  </style>
52
  </head>
53
  <body>
54
  <header>
55
  <div>
56
  <h1>Qwen2.5‑Coder‑3B‑Instruct — WebGPU (ONNX)</h1>
57
+ <div class="muted">Pure front‑end · Transformers.js v3 + ONNX Runtime Web · WebGPU preferred</div>
58
  </div>
59
  <div class="row">
60
  <button id="load">Load model</button>
 
65
 
66
  <div class="card">
67
  <div class="row" style="margin-bottom:8px">
68
+ <span class="chip warn">First run will download ~2.1 GB (q4 ONNX weights). Desktop Chrome/Edge recommended. HTTPS or localhost required for WebGPU.</span>
69
  </div>
70
 
71
  <!-- Confirmation step -->
 
77
  </div>
78
  </div>
79
 
80
+ <!-- Loading / progress -->
81
  <div id="loading" style="display:none">
82
+ <div class="row" style="align-items:center">
83
  <progress id="progress" value="0" max="100"></progress>
84
  <span id="progress-label" class="muted">0%</span>
85
  </div>
86
+ <div class="muted">
87
+ WebGPU works on secure contexts (HTTPS/localhost). Safari/Firefox may need experimental flags; fallback is WASM/CPU.
88
+ </div>
89
  </div>
90
 
91
+ <!-- Chat UI -->
92
  <div id="ui" style="display:none">
93
  <input id="sys" type="text" placeholder="System prompt (optional): You are a senior coding assistant. Keep answers concise." />
94
+ <div id="chat" aria-live="polite" aria-busy="false"></div>
95
  <textarea id="prompt" placeholder="Ask something (you can paste code)…"></textarea>
96
  <div class="row">
97
  <button id="send">Send</button>
 
109
 
110
  <div class="card">
111
  <h3 style="margin:6px 0">Debug log</h3>
112
+ <div id="log" role="log" aria-live="polite"></div>
113
  </div>
114
 
115
  <script type="module">
116
  // ================= Configuration =================
117
  const MODEL_ID = 'onnx-community/Qwen2.5-Coder-3B-Instruct';
118
+ // 固定到 v3 稳定版(包含 TextStreamer / InterruptableStoppingCriteria / WebGPU 支持)
119
+ const TRANSFORMERS_CDN = 'https://cdn.jsdelivr.net/npm/@huggingface/transformers@3.0.0';
120
 
121
  // Prefer WebGPU; gracefully fall back if unavailable
122
  const device = ('gpu' in navigator) ? 'webgpu' : 'auto';
 
124
  // Smallest first: q4 → q4f16 → int8 → fp16
125
  const options = {
126
  device, // prefer WebGPU; fallback to auto if not available
127
+ dtype: 'q4', // maps to onnx/model_q4.onnx + model_q4.onnx_data
128
  progress_callback: setProgress,
129
  };
130
 
 
205
  const exWrap=$('#examples');
206
  const autoRun=$('#autorun');
207
 
208
+ let pipe=null;
209
+ let cancelled=false;
210
+ let stopping=null; // InterruptableStoppingCriteria
211
+ let currentStreamer=null; // TextStreamer
212
 
213
  // ================= Utils =================
214
  function log(...a){
215
  const s = a.map(x => (typeof x === 'string' ? x : JSON.stringify(x))).join(' ');
216
+ logDiv.textContent += s + '\n'; // ✅ 修复:原代码这里是非法的跨行字符串
 
217
  logDiv.scrollTop = logDiv.scrollHeight;
218
  console.log('[LOG]', ...a);
219
  }
220
  function chip(kind,text){ statusChip.className='chip '+(kind||''); statusChip.textContent=text; statusChip.style.display='inline-flex'; }
221
  function clearChip(){ statusChip.style.display='none'; }
222
+ function setProgress(evt){
223
+ if(evt?.status==='progress'){
224
+ const pct=Math.round(Math.max(0,Math.min(100,evt.progress||0)));
225
+ pbar.value=pct; plabel.textContent=`${pct}% ${evt?.name||evt?.file||''}`.trim();
226
+ } else if(evt?.status){
227
+ log(`status: ${evt.status} ${evt?.name||evt?.file||''}`);
228
+ }
229
+ }
230
 
231
  window.addEventListener('error',e=>log('window.error:',e.message,e.filename,`${e.lineno}:${e.colno}`));
232
  window.addEventListener('unhandledrejection',e=>log('unhandledrejection:',e.reason?.message||e.reason));
233
 
234
  async function importTransformers(){
235
+ log('Importing library:',TRANSFORMERS_CDN);
236
+ return await import(TRANSFORMERS_CDN);
237
  }
238
 
239
  function renderExamples(){
 
267
  cancelled=false; confirmBox.style.display='none';
268
  cancelBtn.style.display='inline-block';
269
  loading.style.display='block'; pbar.value=0; plabel.textContent='0%';
270
+
271
+ const { pipeline, TextStreamer, InterruptableStoppingCriteria } = await importTransformers();
272
+
273
  if (device !== 'webgpu') {
274
  log('WebGPU not detected; falling back to device=auto (WASM/CPU).');
275
  chip('warn','WebGPU not detected — using auto');
 
277
  log('WebGPU detected.');
278
  }
279
  log('Transformers.js loaded. Creating text-generation pipeline…');
280
+
281
  pipe = await pipeline('text-generation', MODEL_ID, options);
282
+ stopping = new InterruptableStoppingCriteria();
283
+ // 暴露 streamer 构造器供 send 使用
284
+ window.__hf = { TextStreamer };
285
+
286
  if(cancelled) log('Note: cancel only resets UI and cannot interrupt underlying downloads.');
287
  loading.style.display='none'; cancelBtn.style.display='none'; ui.style.display='block';
288
  chip('ok','Model ready'); setTimeout(clearChip,1200);
 
297
  cancelBtn.addEventListener('click',()=>{ cancelled=true; loadBtn.style.display='inline-block'; cancelBtn.style.display='none'; loading.style.display='none'; log('Canceled (UI only).'); });
298
  clearBtn.addEventListener('click',()=>{ logDiv.textContent=''; });
299
 
300
+ function addMsg(text, me=false){
301
+ const d=document.createElement('div'); d.className='msg '+(me?'me':'bot'); d.textContent=text;
302
+ chatDiv.appendChild(d); chatDiv.scrollTop=chatDiv.scrollHeight; return d;
303
+ }
304
 
305
  sendBtn.addEventListener('click', async ()=>{
306
  try{
307
+ if(!pipe){ chip('warn','Model not loaded'); return; }
308
  const user=promptEl.value.trim(); if(!user) return; promptEl.value='';
309
  const sys=sysEl.value.trim();
310
  addMsg(user,true);
311
  const botEl=addMsg('…');
312
+ stopBtn.disabled=false; chip('', 'Generating…'); chatDiv.setAttribute('aria-busy','true');
313
+
314
+ // v3 推荐的“消息数组”(Chat 格式)
315
+ const messages = [];
316
+ if (sys) messages.push({ role: 'system', content: sys });
317
+ messages.push({ role: 'user', content: user });
318
+
319
+ const { TextStreamer } = window.__hf || {};
320
+ let outText = '';
321
+ currentStreamer = new TextStreamer(pipe.tokenizer, {
322
+ skip_prompt: true,
323
+ callback_function: (chunk) => { outText += chunk; botEl.textContent = outText; }
324
+ });
325
+
326
+ // 可中断
327
+ stopping?.reset?.();
328
+
329
+ const out = await pipe(messages, {
330
+ max_new_tokens: 256,
331
+ temperature: 0.7,
332
+ top_p: 0.9,
333
+ repetition_penalty: 1.05,
334
+ streamer: currentStreamer,
335
+ stopping_criteria: stopping
336
+ });
337
+
338
+ // 若未走流式,兜底一次性写入
339
+ if (!outText && Array.isArray(out) && out[0] && out[0].generated_text) {
340
+ botEl.textContent = (typeof out[0].generated_text === 'string')
341
+ ? out[0].generated_text
342
+ : JSON.stringify(out[0].generated_text);
343
+ }
344
+
345
  chip('ok','Done'); setTimeout(clearChip, 1200);
346
  }catch(err){
 
347
  log('❌ Generation error:', err?.message||err);
348
  chip('err','Generation failed');
349
+ }finally{
350
+ stopBtn.disabled=true; currentStreamer=null; chatDiv.setAttribute('aria-busy','false');
351
+ }
352
  });
353
 
354
+ // Stop:使用 InterruptableStoppingCriteria 即时终止生成
355
+ stopBtn.addEventListener('click',()=>{
356
+ try{
357
+ stopping?.interrupt?.();
358
+ chip('warn','Stopped'); setTimeout(clearChip,1200);
359
+ }catch{}
360
+ });
361
  </script>
362
+
363
  </body>
364
+ </html>