marsadh commited on
Commit
5091c87
·
verified ·
1 Parent(s): a4fa263

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +51 -38
index.html CHANGED
@@ -23,7 +23,7 @@
23
  display: inline-block;
24
  animation: loading 1.5s infinite;
25
  }
26
-
27
  @keyframes loading {
28
  0% { content: ''; }
29
  25% { content: '.'; }
@@ -31,11 +31,11 @@
31
  75% { content: '...'; }
32
  100% { content: ''; }
33
  }
34
-
35
  .drag-over {
36
  @apply border-gray-900 bg-gray-50;
37
  }
38
-
39
  @keyframes slideIn {
40
  from {
41
  transform: translateX(100%);
@@ -46,20 +46,19 @@
46
  opacity: 1;
47
  }
48
  }
49
-
50
  .notification {
51
  animation: slideIn 0.3s ease;
52
  }
53
-
54
  textarea {
55
  text-align: left !important;
56
  direction: ltr !important;
57
  }
58
-
59
  #questionInput {
60
  text-align: left !important;
61
  }
62
-
63
  </style>
64
  </head>
65
  <body class="min-h-screen bg-white text-gray-900 font-inter-tight antialiased">
@@ -77,7 +76,8 @@
77
 
78
  <!-- Upload Section -->
79
  <div class="mb-12">
80
- <div id="dropZone" class="border-2 border-dashed border-gray-300 rounded-xl p-12 text-center cursor-pointer transition-all duration-200 hover:border-gray-900 hover:bg-gray-50 bg-gray-25">
 
81
  <div id="dropContent" class="space-y-4">
82
  <div class="mx-auto w-12 h-12 text-gray-400">
83
  <svg fill="none" stroke="currentColor" viewBox="0 0 24 24" class="w-full h-full">
@@ -89,32 +89,41 @@
89
  <p class="text-gray-500 text-sm">Supports JPG, PNG, GIF up to 10MB</p>
90
  </div>
91
  </div>
92
-
93
- <div id="imagePreview" class="hidden space-y-4">
94
- <img id="previewImg" class="max-w-full max-h-64 mx-auto rounded-lg shadow-sm border border-gray-200" alt="Preview">
95
- <div class="space-y-2">
96
- <p class="text-sm text-gray-600 font-medium" id="fileName"></p>
97
- <button id="removeImage" class="inline-flex items-center px-3 py-1.5 border border-gray-300 rounded-md text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 hover:border-gray-400 transition-colors duration-200">
98
- Remove
99
- </button>
100
- </div>
 
 
 
 
101
  </div>
102
  </div>
103
-
104
  <input type="file" id="fileInput" accept="image/*" class="hidden">
105
  </div>
106
 
107
  <!-- Question Section -->
108
  <div class="mb-12 space-y-6">
109
  <div>
110
- <textarea
111
- id="questionInput"
112
  rows="4"
113
  class="w-full px-4 py-3 border border-gray-300 rounded-lg resize-none focus:ring-2 focus:ring-gray-900 focus:border-transparent text-base placeholder-gray-500 transition-all duration-200 text-left"
114
  placeholder="What would you like to know about this image?"
115
  ></textarea>
116
  </div>
117
-
 
 
 
 
 
118
  <!-- Results Section -->
119
  <div id="resultsSection" class="hidden mb-12">
120
  <div class="border border-gray-200 rounded-xl p-8 bg-gray-50">
@@ -123,12 +132,12 @@
123
  <div class="text-xs font-semibold text-gray-500 uppercase tracking-wider mb-2">Question</div>
124
  <div class="text-gray-900 leading-relaxed" id="questionDisplay"></div>
125
  </div>
126
-
127
  <div>
128
  <div class="text-xs font-semibold text-gray-500 uppercase tracking-wider mb-2">Answer</div>
129
  <div class="text-gray-900 leading-relaxed text-base" id="answerDisplay"></div>
130
  </div>
131
-
132
  <div class="flex justify-between items-center pt-4 border-t border-gray-200 text-xs text-gray-500">
133
  <span>ID: <span id="requestId" class="font-mono"></span></span>
134
  <span id="responseTime" class="font-medium"></span>
@@ -154,6 +163,7 @@
154
  class VisionApp {
155
  constructor() {
156
  // API key embedded in the app
 
157
  this.apiKey = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJrZXlfaWQiOiJkMjc1N2QwNS04OGRjLTQ4YjUtOGVjNC05Y2M1ODYxYjBmOGMiLCJvcmdfaWQiOiIyUlJuZUVNMGVEblVRWFdjbDZKa3M3Y01vaWdTMmJ2aCIsImlhdCI6MTc1ODYyNzkzNywidmVyIjoxfQ.-Wqw1RXeev4ERwl18R9fefMzvOvSBVMvbVWiR3E-BOE';
158
  this.currentImage = null;
159
  this.initializeElements();
@@ -167,8 +177,7 @@
167
  dropContent: document.getElementById('dropContent'),
168
  imagePreview: document.getElementById('imagePreview'),
169
  previewImg: document.getElementById('previewImg'),
170
- fileName: document.getElementById('fileName'),
171
- removeImageBtn: document.getElementById('removeImage'),
172
  questionInput: document.getElementById('questionInput'),
173
  analyzeBtn: document.getElementById('analyzeBtn'),
174
  analyzeText: document.getElementById('analyzeText'),
@@ -187,12 +196,15 @@
187
  this.elements.dropZone.addEventListener('dragleave', this.handleDragLeave.bind(this));
188
  this.elements.dropZone.addEventListener('drop', this.handleDrop.bind(this));
189
  this.elements.fileInput.addEventListener('change', this.handleFileSelect.bind(this));
190
- this.elements.removeImageBtn.addEventListener('click', () => this.removeImage());
 
 
 
191
 
192
  // Question events
193
  this.elements.questionInput.addEventListener('input', () => this.updateAnalyzeButton());
194
  this.elements.analyzeBtn.addEventListener('click', () => this.analyzeImage());
195
-
196
 
197
  handleDragOver(e) {
198
  e.preventDefault();
@@ -242,7 +254,7 @@
242
 
243
  showImagePreview(file) {
244
  this.elements.previewImg.src = this.currentImage;
245
- this.elements.fileName.textContent = file.name;
246
  this.elements.dropContent.classList.add('hidden');
247
  this.elements.imagePreview.classList.remove('hidden');
248
  this.updateAnalyzeButton();
@@ -259,7 +271,7 @@
259
  updateAnalyzeButton() {
260
  const hasImage = this.currentImage !== null;
261
  const hasQuestion = this.elements.questionInput.value.trim().length > 0;
262
-
263
  this.elements.analyzeBtn.disabled = !(hasImage && hasQuestion);
264
  }
265
 
@@ -284,7 +296,7 @@
284
 
285
  this.showResults(question, result.answer, result.request_id, responseTime);
286
  this.showNotification('Analysis completed successfully', 'success');
287
-
288
  } catch (error) {
289
  console.error('Analysis error:', error);
290
  this.showNotification(`Analysis failed: ${error.message}`, 'error');
@@ -301,6 +313,7 @@
301
  stream: false
302
  };
303
 
 
304
  const response = await fetch('https://api.moondream.ai/v1/query', {
305
  method: 'POST',
306
  headers: {
@@ -327,7 +340,7 @@
327
  answer: result.result || result.answer || 'No answer received',
328
  request_id: result.request_id || result.id || `req_${Date.now()}`
329
  };
330
-
331
  } catch (error) {
332
  throw new Error(`API request failed: ${error.message}`);
333
  }
@@ -349,23 +362,23 @@
349
  this.elements.requestId.textContent = requestId;
350
  this.elements.responseTime.textContent = `${responseTime}s`;
351
  this.elements.resultsSection.classList.remove('hidden');
352
-
353
  // Smooth scroll to results
354
- this.elements.resultsSection.scrollIntoView({
355
- behavior: 'smooth',
356
- block: 'nearest'
357
  });
358
  }
359
 
360
  showNotification(message, type = 'info') {
361
  const notification = document.createElement('div');
362
  const bgColor = type === 'error' ? 'bg-red-600' : type === 'success' ? 'bg-green-600' : 'bg-gray-900';
363
-
364
  notification.className = `notification fixed top-4 right-4 ${bgColor} text-white px-4 py-3 rounded-lg shadow-lg z-50 text-sm font-medium`;
365
  notification.textContent = message;
366
-
367
  document.body.appendChild(notification);
368
-
369
  setTimeout(() => {
370
  notification.remove();
371
  }, 4000);
 
23
  display: inline-block;
24
  animation: loading 1.5s infinite;
25
  }
26
+
27
  @keyframes loading {
28
  0% { content: ''; }
29
  25% { content: '.'; }
 
31
  75% { content: '...'; }
32
  100% { content: ''; }
33
  }
34
+
35
  .drag-over {
36
  @apply border-gray-900 bg-gray-50;
37
  }
38
+
39
  @keyframes slideIn {
40
  from {
41
  transform: translateX(100%);
 
46
  opacity: 1;
47
  }
48
  }
49
+
50
  .notification {
51
  animation: slideIn 0.3s ease;
52
  }
53
+
54
  textarea {
55
  text-align: left !important;
56
  direction: ltr !important;
57
  }
58
+
59
  #questionInput {
60
  text-align: left !important;
61
  }
 
62
  </style>
63
  </head>
64
  <body class="min-h-screen bg-white text-gray-900 font-inter-tight antialiased">
 
76
 
77
  <!-- Upload Section -->
78
  <div class="mb-12">
79
+ <!-- Modified dropZone: relative for icon positioning, fixed height -->
80
+ <div id="dropZone" class="border-2 border-dashed border-gray-300 rounded-xl p-12 text-center cursor-pointer transition-all duration-200 hover:border-gray-900 hover:bg-gray-50 bg-gray-25 h-[300px] relative flex items-center justify-center">
81
  <div id="dropContent" class="space-y-4">
82
  <div class="mx-auto w-12 h-12 text-gray-400">
83
  <svg fill="none" stroke="currentColor" viewBox="0 0 24 24" class="w-full h-full">
 
89
  <p class="text-gray-500 text-sm">Supports JPG, PNG, GIF up to 10MB</p>
90
  </div>
91
  </div>
92
+
93
+ <!-- Modified imagePreview: relative for icon positioning, flex column, full height/width -->
94
+ <div id="imagePreview" class="hidden absolute inset-0 flex-col">
95
+ <!-- Modified previewImg: object-contain to fit without cropping -->
96
+ <img id="previewImg" class="w-full h-full object-contain object-center" alt="Preview">
97
+
98
+ <!-- Cross Icon Button: absolute top-right -->
99
+ <button id="removeImage" class="absolute top-3 right-3 p-1.5 rounded-full bg-white/70 hover:bg-white/100 shadow-sm border border-gray-300 text-gray-700 hover:text-gray-900 transition-colors duration-200" aria-label="Remove image">
100
+ <!-- Heroicons X Mark Icon -->
101
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5">
102
+ <path stroke-linecap="round" stroke-linejoin="round" d="M6 18 18 6M6 6l12 12" />
103
+ </svg>
104
+ </button>
105
  </div>
106
  </div>
107
+
108
  <input type="file" id="fileInput" accept="image/*" class="hidden">
109
  </div>
110
 
111
  <!-- Question Section -->
112
  <div class="mb-12 space-y-6">
113
  <div>
114
+ <textarea
115
+ id="questionInput"
116
  rows="4"
117
  class="w-full px-4 py-3 border border-gray-300 rounded-lg resize-none focus:ring-2 focus:ring-gray-900 focus:border-transparent text-base placeholder-gray-500 transition-all duration-200 text-left"
118
  placeholder="What would you like to know about this image?"
119
  ></textarea>
120
  </div>
121
+
122
+ <button id="analyzeBtn" disabled class="w-full bg-gray-900 text-white px-6 py-4 rounded-lg font-medium text-base hover:bg-gray-800 disabled:bg-gray-300 disabled:text-gray-500 disabled:cursor-not-allowed transition-all duration-200 focus:ring-2 focus:ring-gray-900 focus:ring-offset-2">
123
+ <span id="analyzeText">Analyze Image</span>
124
+ </button>
125
+ </div>
126
+
127
  <!-- Results Section -->
128
  <div id="resultsSection" class="hidden mb-12">
129
  <div class="border border-gray-200 rounded-xl p-8 bg-gray-50">
 
132
  <div class="text-xs font-semibold text-gray-500 uppercase tracking-wider mb-2">Question</div>
133
  <div class="text-gray-900 leading-relaxed" id="questionDisplay"></div>
134
  </div>
135
+
136
  <div>
137
  <div class="text-xs font-semibold text-gray-500 uppercase tracking-wider mb-2">Answer</div>
138
  <div class="text-gray-900 leading-relaxed text-base" id="answerDisplay"></div>
139
  </div>
140
+
141
  <div class="flex justify-between items-center pt-4 border-t border-gray-200 text-xs text-gray-500">
142
  <span>ID: <span id="requestId" class="font-mono"></span></span>
143
  <span id="responseTime" class="font-medium"></span>
 
163
  class VisionApp {
164
  constructor() {
165
  // API key embedded in the app
166
+ // Note: Exposing API keys in client-side code is a security risk.
167
  this.apiKey = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJrZXlfaWQiOiJkMjc1N2QwNS04OGRjLTQ4YjUtOGVjNC05Y2M1ODYxYjBmOGMiLCJvcmdfaWQiOiIyUlJuZUVNMGVEblVRWFdjbDZKa3M3Y01vaWdTMmJ2aCIsImlhdCI6MTc1ODYyNzkzNywidmVyIjoxfQ.-Wqw1RXeev4ERwl18R9fefMzvOvSBVMvbVWiR3E-BOE';
168
  this.currentImage = null;
169
  this.initializeElements();
 
177
  dropContent: document.getElementById('dropContent'),
178
  imagePreview: document.getElementById('imagePreview'),
179
  previewImg: document.getElementById('previewImg'),
180
+ removeImageBtn: document.getElementById('removeImage'), // Updated reference
 
181
  questionInput: document.getElementById('questionInput'),
182
  analyzeBtn: document.getElementById('analyzeBtn'),
183
  analyzeText: document.getElementById('analyzeText'),
 
196
  this.elements.dropZone.addEventListener('dragleave', this.handleDragLeave.bind(this));
197
  this.elements.dropZone.addEventListener('drop', this.handleDrop.bind(this));
198
  this.elements.fileInput.addEventListener('change', this.handleFileSelect.bind(this));
199
+ this.elements.removeImageBtn.addEventListener('click', (e) => {
200
+ e.stopPropagation(); // Prevent triggering drop zone click
201
+ this.removeImage();
202
+ });
203
 
204
  // Question events
205
  this.elements.questionInput.addEventListener('input', () => this.updateAnalyzeButton());
206
  this.elements.analyzeBtn.addEventListener('click', () => this.analyzeImage());
207
+ }
208
 
209
  handleDragOver(e) {
210
  e.preventDefault();
 
254
 
255
  showImagePreview(file) {
256
  this.elements.previewImg.src = this.currentImage;
257
+ // this.elements.fileName.textContent = file.name; // Removed filename display
258
  this.elements.dropContent.classList.add('hidden');
259
  this.elements.imagePreview.classList.remove('hidden');
260
  this.updateAnalyzeButton();
 
271
  updateAnalyzeButton() {
272
  const hasImage = this.currentImage !== null;
273
  const hasQuestion = this.elements.questionInput.value.trim().length > 0;
274
+
275
  this.elements.analyzeBtn.disabled = !(hasImage && hasQuestion);
276
  }
277
 
 
296
 
297
  this.showResults(question, result.answer, result.request_id, responseTime);
298
  this.showNotification('Analysis completed successfully', 'success');
299
+
300
  } catch (error) {
301
  console.error('Analysis error:', error);
302
  this.showNotification(`Analysis failed: ${error.message}`, 'error');
 
313
  stream: false
314
  };
315
 
316
+ // Fixed the extra space in the URL
317
  const response = await fetch('https://api.moondream.ai/v1/query', {
318
  method: 'POST',
319
  headers: {
 
340
  answer: result.result || result.answer || 'No answer received',
341
  request_id: result.request_id || result.id || `req_${Date.now()}`
342
  };
343
+
344
  } catch (error) {
345
  throw new Error(`API request failed: ${error.message}`);
346
  }
 
362
  this.elements.requestId.textContent = requestId;
363
  this.elements.responseTime.textContent = `${responseTime}s`;
364
  this.elements.resultsSection.classList.remove('hidden');
365
+
366
  // Smooth scroll to results
367
+ this.elements.resultsSection.scrollIntoView({
368
+ behavior: 'smooth',
369
+ block: 'nearest'
370
  });
371
  }
372
 
373
  showNotification(message, type = 'info') {
374
  const notification = document.createElement('div');
375
  const bgColor = type === 'error' ? 'bg-red-600' : type === 'success' ? 'bg-green-600' : 'bg-gray-900';
376
+
377
  notification.className = `notification fixed top-4 right-4 ${bgColor} text-white px-4 py-3 rounded-lg shadow-lg z-50 text-sm font-medium`;
378
  notification.textContent = message;
379
+
380
  document.body.appendChild(notification);
381
+
382
  setTimeout(() => {
383
  notification.remove();
384
  }, 4000);