akhaliq HF Staff commited on
Commit
7525da2
·
verified ·
1 Parent(s): 9161cfa

Upload index.js with huggingface_hub

Browse files
Files changed (1) hide show
  1. index.js +363 -62
index.js CHANGED
@@ -1,76 +1,377 @@
1
- import { pipeline } from 'https://cdn.jsdelivr.net/npm/@huggingface/transformers@3.7.6';
 
 
 
 
 
 
 
 
 
 
 
 
2
 
3
- // Reference the elements that we will need
4
- const status = document.getElementById('status');
5
- const fileUpload = document.getElementById('upload');
6
- const imageContainer = document.getElementById('container');
7
- const example = document.getElementById('example');
 
 
 
 
 
 
 
 
 
 
 
8
 
9
- const EXAMPLE_URL = 'https://huggingface.co/datasets/Xenova/transformers.js-docs/resolve/main/city-streets.jpg';
 
 
 
 
 
 
 
 
 
 
10
 
11
- // Create a new object detection pipeline
12
- status.textContent = 'Loading model...';
13
- const detector = await pipeline('object-detection', 'Xenova/detr-resnet-50');
14
- status.textContent = 'Ready';
 
15
 
16
- example.addEventListener('click', (e) => {
17
- e.preventDefault();
18
- detect(EXAMPLE_URL);
19
- });
20
 
21
- fileUpload.addEventListener('change', function (e) {
22
- const file = e.target.files[0];
23
- if (!file) {
24
- return;
 
25
  }
26
 
27
- const reader = new FileReader();
 
 
 
 
 
 
 
 
 
 
 
 
28
 
29
- // Set up a callback when the file is loaded
30
- reader.onload = e2 => detect(e2.target.result);
 
 
 
 
 
 
31
 
32
- reader.readAsDataURL(file);
33
- });
34
 
 
 
 
 
 
 
 
 
 
35
 
36
- // Detect objects in the image
37
- async function detect(img) {
38
- imageContainer.innerHTML = '';
39
- imageContainer.style.backgroundImage = `url(${img})`;
 
 
40
 
41
- status.textContent = 'Analysing...';
42
- const output = await detector(img, {
43
- threshold: 0.5,
44
- percentage: true,
45
- });
46
- status.textContent = '';
47
- output.forEach(renderBox);
48
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
49
 
50
- // Render a bounding box and label on the image
51
- function renderBox({ box, label }) {
52
- const { xmax, xmin, ymax, ymin } = box;
53
-
54
- // Generate a random color for the box
55
- const color = '#' + Math.floor(Math.random() * 0xFFFFFF).toString(16).padStart(6, 0);
56
-
57
- // Draw the box
58
- const boxElement = document.createElement('div');
59
- boxElement.className = 'bounding-box';
60
- Object.assign(boxElement.style, {
61
- borderColor: color,
62
- left: 100 * xmin + '%',
63
- top: 100 * ymin + '%',
64
- width: 100 * (xmax - xmin) + '%',
65
- height: 100 * (ymax - ymin) + '%',
66
- })
67
-
68
- // Draw label
69
- const labelElement = document.createElement('span');
70
- labelElement.textContent = label;
71
- labelElement.className = 'bounding-box-label';
72
- labelElement.style.backgroundColor = color;
73
-
74
- boxElement.appendChild(labelElement);
75
- imageContainer.appendChild(boxElement);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
76
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ class GemmaChatbot {
2
+ constructor() {
3
+ this.generator = null;
4
+ this.isInitialized = false;
5
+ this.isGenerating = false;
6
+ this.currentDevice = 'cpu';
7
+ this.messages = [];
8
+ this.maxTokens = 1000;
9
+
10
+ this.initializeElements();
11
+ this.attachEventListeners();
12
+ this.initializeModel();
13
+ }
14
 
15
+ initializeElements() {
16
+ // Input elements
17
+ this.messageInput = document.getElementById('messageInput');
18
+ this.sendButton = document.getElementById('sendButton');
19
+ this.messagesContainer = document.getElementById('messagesContainer');
20
+ this.charCount = document.getElementById('charCount');
21
+
22
+ // UI elements
23
+ this.loadingOverlay = document.getElementById('loadingOverlay');
24
+ this.errorModal = document.getElementById('errorModal');
25
+ this.errorMessage = document.getElementById('errorMessage');
26
+ this.retryButton = document.getElementById('retryButton');
27
+ this.deviceToggle = document.getElementById('deviceToggle');
28
+ this.statusText = document.getElementById('statusText');
29
+ this.progressFill = document.getElementById('progressFill');
30
+ }
31
 
32
+ attachEventListeners() {
33
+ // Send message
34
+ this.sendButton.addEventListener('click', () => this.sendMessage());
35
+
36
+ // Enter key to send (Ctrl+Enter for new line)
37
+ this.messageInput.addEventListener('keydown', (e) => {
38
+ if (e.key === 'Enter' && !e.ctrlKey) {
39
+ e.preventDefault();
40
+ this.sendMessage();
41
+ }
42
+ });
43
 
44
+ // Auto-resize textarea
45
+ this.messageInput.addEventListener('input', () => {
46
+ this.autoResizeTextarea();
47
+ this.updateCharCount();
48
+ });
49
 
50
+ // Device toggle
51
+ this.deviceToggle.addEventListener('click', () => this.toggleDevice());
 
 
52
 
53
+ // Retry button
54
+ this.retryButton.addEventListener('click', () => this.retryInitialization());
55
+
56
+ // WebGPU support detection
57
+ this.checkWebGPUSupport();
58
  }
59
 
60
+ async checkWebGPUSupport() {
61
+ if ('gpu' in navigator) {
62
+ try {
63
+ const adapter = await navigator.gpu.requestAdapter();
64
+ if (adapter) {
65
+ this.statusText.textContent = 'Ready (GPU available)';
66
+ this.statusText.parentElement.classList.add('ready');
67
+ }
68
+ } catch (error) {
69
+ console.log('WebGPU not available');
70
+ }
71
+ }
72
+ }
73
 
74
+ async initializeModel() {
75
+ try {
76
+ this.updateProgress(10, 'Initializing transformers.js...');
77
+
78
+ // Wait for transformers to be available
79
+ while (!window.transformers) {
80
+ await new Promise(resolve => setTimeout(resolve, 100));
81
+ }
82
 
83
+ const { pipeline, TextStreamer } = window.transformers;
84
+ this.updateProgress(30, 'Loading Gemma model...');
85
 
86
+ // Create pipeline with error handling
87
+ this.generator = await pipeline(
88
+ 'text-generation',
89
+ 'onnx-community/gemma-3-270m-it-ONNX',
90
+ {
91
+ dtype: 'fp32',
92
+ device: this.currentDevice
93
+ }
94
+ );
95
 
96
+ this.updateProgress(90, 'Model loaded successfully!');
97
+
98
+ // Initialize with system message
99
+ this.messages = [
100
+ { role: 'system', content: 'You are a helpful assistant.' }
101
+ ];
102
 
103
+ this.isInitialized = true;
104
+ this.updateProgress(100, 'Ready!');
105
+
106
+ setTimeout(() => {
107
+ this.hideLoadingOverlay();
108
+ this.enableInput();
109
+ }, 500);
110
+
111
+ } catch (error) {
112
+ console.error('Model initialization failed:', error);
113
+ this.showError('Failed to initialize the AI model. Please check your internet connection and try again.');
114
+ }
115
+ }
116
+
117
+ async toggleDevice() {
118
+ if (this.isGenerating) return;
119
+
120
+ const options = this.deviceToggle.querySelectorAll('.device-option');
121
+ const statusCircle = this.statusText.parentElement.querySelector('i');
122
+
123
+ if (this.currentDevice === 'cpu') {
124
+ // Try to switch to GPU
125
+ if ('gpu' in navigator) {
126
+ try {
127
+ this.currentDevice = 'webgpu';
128
+ options[0].classList.remove('active');
129
+ options[1].classList.add('active');
130
+ this.statusText.textContent = 'Switching to GPU...';
131
+ statusCircle.style.color = '#ff9800';
132
+
133
+ // Reinitialize model with GPU
134
+ await this.reinitializeModel();
135
+
136
+ } catch (error) {
137
+ console.log('GPU initialization failed, staying on CPU');
138
+ this.currentDevice = 'cpu';
139
+ statusCircle.style.color = '#4caf50';
140
+ this.statusText.textContent = 'Ready (CPU)';
141
+ }
142
+ } else {
143
+ this.showToast('WebGPU not supported in this browser');
144
+ }
145
+ } else {
146
+ // Switch to CPU
147
+ this.currentDevice = 'cpu';
148
+ options[1].classList.remove('active');
149
+ options[0].classList.add('active');
150
+ this.statusText.textContent = 'Switching to CPU...';
151
+
152
+ await this.reinitializeModel();
153
+ }
154
+ }
155
+
156
+ async reinitializeModel() {
157
+ try {
158
+ this.isInitialized = false;
159
+ this.disableInput();
160
+
161
+ const { pipeline, TextStreamer } = window.transformers;
162
+
163
+ this.generator = await pipeline(
164
+ 'text-generation',
165
+ 'onnx-community/gemma-3-270m-it-ONNX',
166
+ {
167
+ dtype: 'fp32',
168
+ device: this.currentDevice
169
+ }
170
+ );
171
+
172
+ this.isInitialized = true;
173
+ this.enableInput();
174
+
175
+ const deviceName = this.currentDevice === 'gpu' ? 'GPU' : 'CPU';
176
+ this.statusText.textContent = `Ready (${deviceName})`;
177
+ this.showToast(`Switched to ${deviceName} execution`);
178
+
179
+ } catch (error) {
180
+ console.error('Model reinitialization failed:', error);
181
+ this.showError('Failed to switch execution device. Please try again.');
182
+ }
183
+ }
184
+
185
+ async sendMessage() {
186
+ if (!this.isInitialized || this.isGenerating) return;
187
+
188
+ const message = this.messageInput.value.trim();
189
+ if (!message) return;
190
+
191
+ // Add user message to UI
192
+ this.addMessage('user', message);
193
+
194
+ // Clear input
195
+ this.messageInput.value = '';
196
+ this.autoResizeTextarea();
197
+ this.updateCharCount();
198
+
199
+ // Add to conversation history
200
+ this.messages.push({ role: 'user', content: message });
201
+
202
+ // Generate response
203
+ await this.generateResponse();
204
+ }
205
+
206
+ async generateResponse() {
207
+ if (this.isGenerating) return;
208
+
209
+ this.isGenerating = true;
210
+ this.disableInput();
211
+
212
+ try {
213
+ // Add assistant message placeholder
214
+ const assistantMessageEl = this.addMessage('assistant', '');
215
+
216
+ // Create streaming response
217
+ const { pipeline, TextStreamer } = window.transformers;
218
+ const streamer = new TextStreamer(this.generator.tokenizer, {
219
+ skip_prompt: true,
220
+ skip_special_tokens: true,
221
+ callback_function: (text) => {
222
+ this.updateStreamingMessage(assistantMessageEl, text);
223
+ }
224
+ });
225
+
226
+ // Generate response with streaming
227
+ const output = await this.generator(this.messages, {
228
+ max_new_tokens: 512,
229
+ do_sample: false,
230
+ streamer: streamer
231
+ });
232
+
233
+ // Get final response
234
+ const finalResponse = output[0].generated_text.at(-1).content;
235
+
236
+ // Add final response to conversation
237
+ this.messages.push({ role: 'assistant', content: finalResponse });
238
+
239
+ } catch (error) {
240
+ console.error('Generation failed:', error);
241
+ this.updateStreamingMessage(assistantMessageEl, 'I apologize, but I encountered an error while generating a response. Please try again.');
242
+ this.showToast('Generation failed. Please try again.');
243
+ } finally {
244
+ this.isGenerating = false;
245
+ this.enableInput();
246
+ }
247
+ }
248
+
249
+ addMessage(role, content) {
250
+ const messageEl = document.createElement('div');
251
+ messageEl.className = `message ${role}`;
252
+
253
+ const avatar = role === 'user' ? 'fas fa-user' : 'fas fa-robot';
254
+ const roleName = role === 'user' ? 'You' : 'Gemma AI';
255
+
256
+ messageEl.innerHTML = `
257
+ <div class="message-avatar">
258
+ <i class="${avatar}"></i>
259
+ </div>
260
+ <div class="message-content">
261
+ <div class="message-header">
262
+ <span class="message-role">${roleName}</span>
263
+ </div>
264
+ <div class="message-text">${this.escapeHtml(content)}</div>
265
+ </div>
266
+ `;
267
+
268
+ this.messagesContainer.appendChild(messageEl);
269
+ this.scrollToBottom();
270
 
271
+ return messageEl;
272
+ }
273
+
274
+ updateStreamingMessage(messageEl, content) {
275
+ const messageText = messageEl.querySelector('.message-text');
276
+ messageText.innerHTML = this.escapeHtml(content);
277
+ this.scrollToBottom();
278
+ }
279
+
280
+ autoResizeTextarea() {
281
+ const textarea = this.messageInput;
282
+ textarea.style.height = 'auto';
283
+ textarea.style.height = Math.min(textarea.scrollHeight, 120) + 'px';
284
+ }
285
+
286
+ updateCharCount() {
287
+ const count = this.messageInput.value.length;
288
+ this.charCount.textContent = count;
289
+
290
+ if (count > this.maxTokens * 0.9) {
291
+ this.charCount.style.color = '#f44336';
292
+ } else if (count > this.maxTokens * 0.7) {
293
+ this.charCount.style.color = '#ff9800';
294
+ } else {
295
+ this.charCount.style.color = '';
296
+ }
297
+ }
298
+
299
+ enableInput() {
300
+ this.messageInput.disabled = false;
301
+ this.sendButton.disabled = false;
302
+ this.messageInput.placeholder = "Type your message here...";
303
+ }
304
+
305
+ disableInput() {
306
+ this.messageInput.disabled = true;
307
+ this.sendButton.disabled = true;
308
+ this.messageInput.placeholder = "AI is generating a response...";
309
+ }
310
+
311
+ scrollToBottom() {
312
+ this.messagesContainer.scrollTop = this.messagesContainer.scrollHeight;
313
+ }
314
+
315
+ escapeHtml(text) {
316
+ const div = document.createElement('div');
317
+ div.textContent = text;
318
+ return div.innerHTML;
319
+ }
320
+
321
+ updateProgress(percentage, status) {
322
+ this.progressFill.style.width = `${percentage}%`;
323
+ this.statusText.textContent = status;
324
+ }
325
+
326
+ hideLoadingOverlay() {
327
+ this.loadingOverlay.style.display = 'none';
328
+ }
329
+
330
+ showError(message) {
331
+ this.errorMessage.textContent = message;
332
+ this.errorModal.style.display = 'flex';
333
+ this.hideLoadingOverlay();
334
+ }
335
+
336
+ hideError() {
337
+ this.errorModal.style.display = 'none';
338
+ }
339
+
340
+ retryInitialization() {
341
+ this.hideError();
342
+ this.showLoadingOverlay();
343
+ this.initializeModel();
344
+ }
345
+
346
+ showLoadingOverlay() {
347
+ this.loadingOverlay.style.display = 'flex';
348
+ this.updateProgress(0, 'Initializing...');
349
+ }
350
+
351
+ showToast(message) {
352
+ const toast = document.createElement('div');
353
+ toast.className = 'toast';
354
+ toast.textContent = message;
355
+ document.body.appendChild(toast);
356
+
357
+ setTimeout(() => toast.classList.add('show'), 100);
358
+ setTimeout(() => {
359
+ toast.classList.remove('show');
360
+ setTimeout(() => document.body.removeChild(toast), 300);
361
+ }, 3000);
362
+ }
363
  }
364
+
365
+ // Initialize the chatbot when the page loads
366
+ document.addEventListener('DOMContentLoaded', () => {
367
+ new GemmaChatbot();
368
+ });
369
+
370
+ // Handle page visibility change
371
+ document.addEventListener('visibilitychange', () => {
372
+ if (document.hidden) {
373
+ // Page is hidden, can pause any ongoing operations
374
+ } else {
375
+ // Page is visible, resume operations
376
+ }
377
+ });