Javedalam commited on
Commit
881e3d2
·
verified ·
1 Parent(s): b8753ac

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +217 -18
index.html CHANGED
@@ -1,19 +1,218 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  </html>
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <meta charset="utf-8" />
4
+ <meta name="viewport" content="width=device-width,initial-scale=1" />
5
+ <title>Local AI (JS only)</title>
6
+ <style>
7
+ body { font-family: system-ui, sans-serif; max-width: 820px; margin: 24px auto; padding: 0 12px; }
8
+ textarea { width: 100%; min-height: 140px; }
9
+ button, select { padding: 10px 14px; margin: 6px 6px 0 0; }
10
+ .row { margin: 16px 0; }
11
+ #log { white-space: pre-wrap; font: 13px/1.4 monospace; background:#f6f6f6; padding:12px; border-radius:8px; }
12
+ #out { min-height: 48px; }
13
+ </style>
14
+
15
+ <h2>Local AI on your device (JS only)</h2>
16
+
17
+ <div class="row">
18
+ <label>Task:
19
+ <select id="task">
20
+ <option value="sentiment">Sentiment (DistilBERT)</option>
21
+ <option value="summarize">Summarize (T5-small)</option>
22
+ <option value="whisper">Transcribe (Whisper tiny.en)</option>
23
+ </select>
24
+ </label>
25
+ <button id="initBtn">Load model</button>
26
+ </div>
27
+
28
+ <div class="row" id="textRow">
29
+ <textarea id="text" placeholder="Type or paste text…"></textarea>
30
+ <button id="runBtn" disabled>Run</button>
31
+ </div>
32
+
33
+ <div class="row" id="audioRow" style="display:none">
34
+ <button id="recBtn" disabled>🎙️ Start / Stop Recording</button>
35
+ <button id="transcribeBtn" disabled>Transcribe</button>
36
+ </div>
37
+
38
+ <h3>Output</h3>
39
+ <div id="out"></div>
40
+
41
+ <h3>Log</h3>
42
+ <div id="log"></div>
43
+
44
+ <script type="module">
45
+ import { pipeline, read_audio } from "https://cdn.jsdelivr.net/npm/@xenova/transformers";
46
+
47
+ const $ = (id) => document.getElementById(id);
48
+ const log = (s) => $('log').textContent += s + "\n";
49
+ const out = (html) => $('out').innerHTML = html;
50
+
51
+ let runner = null;
52
+
53
+ // --- Recording state ---
54
+ let audioCtx = null, processor = null, inputNode = null, stream = null;
55
+ let pcmChunks = []; // Float32 chunks
56
+ let wavBlob = null; // built on stop
57
+
58
+ function enableTextUI(en) { $('runBtn').disabled = !en; }
59
+ function enableAudioUI(en) { $('recBtn').disabled = !en; $('transcribeBtn').disabled = true; }
60
+ function toggleTaskUI() {
61
+ const isWhisper = ($('task').value === 'whisper');
62
+ $('textRow').style.display = isWhisper ? 'none' : '';
63
+ $('audioRow').style.display = isWhisper ? '' : 'none';
64
+ $('log').textContent = ''; out('');
65
+ enableTextUI(false); enableAudioUI(false);
66
+ }
67
+ $('task').addEventListener('change', toggleTaskUI);
68
+ toggleTaskUI();
69
+
70
+ // --- Robust progress (0–1 or 0–100) ---
71
+ let lastPct = -1, lastTime = 0;
72
+ function progressLogger(p) {
73
+ let pct = null;
74
+ if (p && typeof p.progress === 'number') pct = (p.progress <= 1 ? p.progress * 100 : p.progress);
75
+ else if (p && p.loaded && p.total) pct = (p.loaded / p.total) * 100;
76
+ if (pct == null) return;
77
+ pct = Math.max(0, Math.min(100, Math.round(pct)));
78
+ const now = performance.now();
79
+ if (pct !== lastPct && (now - lastTime > 120)) { log(`Download: ${pct}%`); lastPct = pct; lastTime = now; }
80
+ }
81
+
82
+ // --- Load model ---
83
+ $('initBtn').onclick = async () => {
84
+ try {
85
+ $('log').textContent = ''; out('');
86
+ runner = null; enableTextUI(false); enableAudioUI(false);
87
+ lastPct = -1; lastTime = 0;
88
+
89
+ const task = $('task').value;
90
+ log('Loading… (first time may download model to cache)');
91
+
92
+ if (task === 'sentiment') {
93
+ runner = await pipeline("text-classification",
94
+ "Xenova/distilbert-base-uncased-finetuned-sst-2-english",
95
+ { progress_callback: progressLogger });
96
+ log('Model ready ✅'); enableTextUI(true);
97
+
98
+ } else if (task === 'summarize') {
99
+ runner = await pipeline("summarization", "Xenova/t5-small",
100
+ { progress_callback: progressLogger });
101
+ log('Model ready ✅'); enableTextUI(true);
102
+
103
+ } else {
104
+ runner = await pipeline("automatic-speech-recognition",
105
+ "Xenova/whisper-tiny.en",
106
+ { progress_callback: progressLogger, chunk_length_s: 15, stride_length_s: 2 });
107
+ log('Model ready ✅'); enableAudioUI(true);
108
+ }
109
+ } catch (e) {
110
+ log('Error loading model: ' + (e?.message ?? e));
111
+ }
112
+ };
113
+
114
+ // --- Run text tasks ---
115
+ $('runBtn').onclick = async () => {
116
+ if (!runner) return log('Load a model first.');
117
+ const task = $('task').value, txt = $('text').value.trim();
118
+ if (!txt) return out('<i>Enter some text.</i>');
119
+ out('Running…');
120
+ try {
121
+ if (task === 'sentiment') {
122
+ const res = await runner(txt);
123
+ out(`<pre>${JSON.stringify(res, null, 2)}</pre>`);
124
+ } else {
125
+ // T5: "summarize: " + chunking
126
+ const MAX = 2000; const chunks = [];
127
+ for (let i = 0; i < txt.length; i += MAX) chunks.push(txt.slice(i, i + MAX));
128
+ const parts = [];
129
+ for (const c of chunks) {
130
+ const r = await runner(`summarize: ${c}`, { max_new_tokens: 120 });
131
+ parts.push(Array.isArray(r) ? r[0]?.summary_text : r?.summary_text);
132
+ }
133
+ out(`<div><b>Summary:</b><br>${parts.join(' ')}</div>`);
134
+ }
135
+ } catch (e) { out(''); log('Run error: ' + (e?.message ?? e)); }
136
+ };
137
+
138
+ // --- Record PCM, then encode WAV (so read_audio can decode) ---
139
+ $('recBtn').onclick = async () => {
140
+ try {
141
+ if (processor) {
142
+ // Stop recording
143
+ processor.disconnect(); inputNode.disconnect();
144
+ if (stream) stream.getTracks().forEach(t => t.stop());
145
+ const rate = audioCtx.sampleRate;
146
+ wavBlob = encodeWAV(pcmChunks, rate); // audio/wav
147
+ // reset
148
+ pcmChunks = [];
149
+ if (audioCtx) { try { await audioCtx.close(); } catch(_){} }
150
+ audioCtx = null; processor = null; inputNode = null; stream = null;
151
+
152
+ $('recBtn').textContent = '🎙️ Start / Stop Recording';
153
+ $('transcribeBtn').disabled = false;
154
+ out('Recording stopped. Tap Transcribe.');
155
+ return;
156
+ }
157
+
158
+ // Start
159
+ stream = await navigator.mediaDevices.getUserMedia({ audio: true });
160
+ audioCtx = new (window.AudioContext || window.webkitAudioContext)();
161
+ inputNode = audioCtx.createMediaStreamSource(stream);
162
+ processor = audioCtx.createScriptProcessor(4096, 1, 1);
163
+ processor.onaudioprocess = e => {
164
+ const ch = e.inputBuffer.getChannelData(0);
165
+ pcmChunks.push(new Float32Array(ch)); // copy
166
+ };
167
+ inputNode.connect(processor);
168
+ processor.connect(audioCtx.destination);
169
+
170
+ $('recBtn').textContent = '⏹️ Stop';
171
+ $('transcribeBtn').disabled = true;
172
+ out('Recording… speak now.');
173
+ } catch (e) {
174
+ log('Mic error (use http://localhost & allow mic): ' + e.name + ' - ' + e.message);
175
+ }
176
+ };
177
+
178
+ function encodeWAV(chunks, sampleRate) {
179
+ const length = chunks.reduce((a, b) => a + b.length, 0);
180
+ const buffer = new ArrayBuffer(44 + length * 2);
181
+ const view = new DataView(buffer);
182
+ const write = (o, s) => { for (let i = 0; i < s.length; i++) view.setUint8(o+i, s.charCodeAt(i)); };
183
+ write(0, 'RIFF'); view.setUint32(4, 36 + length * 2, true); write(8, 'WAVE'); write(12, 'fmt ');
184
+ view.setUint32(16, 16, true); view.setUint16(20, 1, true); view.setUint16(22, 1, true);
185
+ view.setUint32(24, sampleRate, true); view.setUint32(28, sampleRate * 2, true);
186
+ view.setUint16(32, 2, true); view.setUint16(34, 16, true); write(36, 'data'); view.setUint32(40, length * 2, true);
187
+ let offset = 44;
188
+ for (const chunk of chunks) for (let i = 0; i < chunk.length; i++, offset += 2) {
189
+ const s = Math.max(-1, Math.min(1, chunk[i]));
190
+ view.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7FFF, true);
191
+ }
192
+ return new Blob([buffer], { type: 'audio/wav' });
193
+ }
194
+
195
+ // --- Transcribe (read_audio(URL, 16000) → Float32Array → pipeline) ---
196
+ $('transcribeBtn').onclick = async () => {
197
+ if (!runner) return log('Load Whisper tiny.en first.');
198
+ if (!wavBlob) return log('No audio recorded.');
199
+ out('Transcribing…');
200
+ try {
201
+ const url = URL.createObjectURL(wavBlob);
202
+ const audio = await read_audio(url, 16000); // returns Float32Array at 16kHz
203
+ URL.revokeObjectURL(url);
204
+ const result = await runner(audio);
205
+ out(`<div><b>Transcript:</b><br>${result.text}</div>`);
206
+ } catch (e) {
207
+ out(''); log('ASR error: ' + (e?.message ?? e));
208
+ } finally {
209
+ wavBlob = null; $('transcribeBtn').disabled = true;
210
+ }
211
+ };
212
+
213
+ // Env info
214
+ log('Secure origin required for mic: use http://localhost');
215
+ log('WebGPU available: ' + (!!navigator.gpu));
216
+ if ('deviceMemory' in navigator) log('deviceMemory (GB bucket): ' + navigator.deviceMemory);
217
+ </script>
218
  </html>