pksaheb commited on
Commit
533f20b
·
verified ·
1 Parent(s): c7e9002

camera feed is not visible - Initial Deployment

Browse files
Files changed (2) hide show
  1. README.md +7 -5
  2. index.html +735 -19
README.md CHANGED
@@ -1,10 +1,12 @@
1
  ---
2
- title: Temperature Monitoring
3
- emoji: 🏃
4
- colorFrom: indigo
5
- colorTo: indigo
6
  sdk: static
7
  pinned: false
 
 
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
1
  ---
2
+ title: temperature-monitoring
3
+ emoji: 🐳
4
+ colorFrom: gray
5
+ colorTo: blue
6
  sdk: static
7
  pinned: false
8
+ tags:
9
+ - deepsite
10
  ---
11
 
12
+ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
index.html CHANGED
@@ -1,19 +1,735 @@
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
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>ThermoScan AI | Industrial Temperature Monitoring</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
9
+ <script>
10
+ tailwind.config = {
11
+ theme: {
12
+ extend: {
13
+ colors: {
14
+ industrial: {
15
+ 50: '#f0f9ff',
16
+ 100: '#e0f2fe',
17
+ 200: '#bae6fd',
18
+ 300: '#7dd3fc',
19
+ 400: '#38bdf8',
20
+ 500: '#0ea5e9',
21
+ 600: '#0284c7',
22
+ 700: '#0369a1',
23
+ 800: '#075985',
24
+ 900: '#0c4a6e',
25
+ },
26
+ danger: {
27
+ 500: '#ef4444',
28
+ 600: '#dc2626',
29
+ },
30
+ warning: {
31
+ 500: '#f59e0b',
32
+ 600: '#d97706',
33
+ },
34
+ success: {
35
+ 500: '#10b981',
36
+ 600: '#059669',
37
+ }
38
+ }
39
+ }
40
+ }
41
+ }
42
+ </script>
43
+ <style>
44
+ @import url('https://fonts.googleapis.com/css2?family=Roboto+Mono:wght@300;400;500;600;700&display=swap');
45
+
46
+ body {
47
+ font-family: 'Roboto Mono', monospace;
48
+ background: linear-gradient(135deg, #1a2a3a 0%, #0f172a 100%);
49
+ color: #e2e8f0;
50
+ min-height: 100vh;
51
+ }
52
+
53
+ .dashboard-grid {
54
+ display: grid;
55
+ grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
56
+ gap: 1.5rem;
57
+ }
58
+
59
+ .industrial-card {
60
+ background: rgba(15, 23, 42, 0.7);
61
+ border: 1px solid rgba(56, 189, 248, 0.2);
62
+ border-radius: 0.75rem;
63
+ backdrop-filter: blur(10px);
64
+ box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.3);
65
+ transition: all 0.3s ease;
66
+ }
67
+
68
+ .industrial-card:hover {
69
+ transform: translateY(-5px);
70
+ box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.3);
71
+ }
72
+
73
+ .camera-feed {
74
+ border: 2px dashed rgba(56, 189, 248, 0.5);
75
+ border-radius: 0.5rem;
76
+ background: rgba(15, 23, 42, 0.5);
77
+ }
78
+
79
+ .temperature-display {
80
+ font-size: 5rem;
81
+ font-weight: 700;
82
+ text-shadow: 0 0 10px rgba(56, 189, 248, 0.7);
83
+ transition: all 0.5s ease;
84
+ }
85
+
86
+ .status-indicator {
87
+ width: 12px;
88
+ height: 12px;
89
+ border-radius: 50%;
90
+ display: inline-block;
91
+ margin-right: 8px;
92
+ }
93
+
94
+ .status-normal { background-color: #10b981; }
95
+ .status-warning { background-color: #f59e0b; }
96
+ .status-danger { background-color: #ef4444; }
97
+
98
+ .history-item {
99
+ border-left: 3px solid #38bdf8;
100
+ transition: all 0.2s ease;
101
+ }
102
+
103
+ .history-item:hover {
104
+ background: rgba(56, 189, 248, 0.1);
105
+ transform: translateX(5px);
106
+ }
107
+
108
+ .gauge {
109
+ position: relative;
110
+ width: 200px;
111
+ height: 200px;
112
+ }
113
+
114
+ .gauge-circle {
115
+ fill: none;
116
+ stroke: rgba(30, 41, 59, 0.8);
117
+ stroke-width: 10;
118
+ }
119
+
120
+ .gauge-progress {
121
+ fill: none;
122
+ stroke: #38bdf8;
123
+ stroke-width: 10;
124
+ stroke-linecap: round;
125
+ transform: rotate(-90deg);
126
+ transform-origin: 50% 50%;
127
+ transition: stroke-dasharray 0.5s ease;
128
+ }
129
+
130
+ .pulse {
131
+ animation: pulse 2s infinite;
132
+ }
133
+
134
+ @keyframes pulse {
135
+ 0% { box-shadow: 0 0 0 0 rgba(56, 189, 248, 0.7); }
136
+ 70% { box-shadow: 0 0 0 10px rgba(56, 189, 248, 0); }
137
+ 100% { box-shadow: 0 0 0 0 rgba(56, 189, 248, 0); }
138
+ }
139
+
140
+ .glow {
141
+ text-shadow: 0 0 10px rgba(56, 189, 248, 0.7);
142
+ }
143
+
144
+ /* Modal styles */
145
+ .modal-overlay {
146
+ position: fixed;
147
+ top: 0;
148
+ left: 0;
149
+ right: 0;
150
+ bottom: 0;
151
+ background: rgba(15, 23, 42, 0.9);
152
+ display: flex;
153
+ align-items: center;
154
+ justify-content: center;
155
+ z-index: 1000;
156
+ }
157
+
158
+ .modal-content {
159
+ background: #1e293b;
160
+ border-radius: 0.5rem;
161
+ padding: 1.5rem;
162
+ width: 90%;
163
+ max-width: 500px;
164
+ box-shadow: 0 10px 25px rgba(0, 0, 0, 0.5);
165
+ }
166
+
167
+ .modal-actions {
168
+ display: flex;
169
+ justify-content: flex-end;
170
+ gap: 0.75rem;
171
+ margin-top: 1rem;
172
+ }
173
+ </style>
174
+ </head>
175
+ <body class="min-h-screen p-4 md:p-8">
176
+ <div class="max-w-7xl mx-auto">
177
+ <!-- Header -->
178
+ <header class="flex flex-col md:flex-row justify-between items-center mb-8 md:mb-12">
179
+ <div class="flex items-center mb-4 md:mb-0">
180
+ <div class="bg-industrial-600 p-3 rounded-lg mr-4">
181
+ <i class="fas fa-industry text-3xl text-industrial-200"></i>
182
+ </div>
183
+ <div>
184
+ <h1 class="text-2xl md:text-3xl font-bold text-white">ThermoScan<span class="text-industrial-400">AI</span></h1>
185
+ <p class="text-industrial-300 text-sm">Industrial Machine Temperature Monitoring</p>
186
+ </div>
187
+ </div>
188
+
189
+ <div class="flex items-center space-x-4">
190
+ <div class="hidden md:block">
191
+ <div class="flex items-center">
192
+ <span class="status-indicator status-normal"></span>
193
+ <span class="text-industrial-300">System Status: <span class="text-success-500 font-medium">Operational</span></span>
194
+ </div>
195
+ <div class="text-xs text-industrial-400 mt-1">Connected to Gemini 2.5 Flash API</div>
196
+ </div>
197
+ <button id="settingsBtn" class="bg-industrial-600 hover:bg-industrial-500 text-white px-4 py-2 rounded-lg transition flex items-center">
198
+ <i class="fas fa-cog mr-2"></i> Settings
199
+ </button>
200
+ </div>
201
+ </header>
202
+
203
+ <div class="grid grid-cols-1 lg:grid-cols-3 gap-6 mb-8">
204
+ <!-- Main Camera Feed -->
205
+ <div class="lg:col-span-2">
206
+ <div class="industrial-card h-full">
207
+ <div class="p-4 border-b border-industrial-700 flex justify-between items-center">
208
+ <h2 class="text-xl font-bold text-white">Machine Camera Feed</h2>
209
+ <div class="flex space-x-2">
210
+ <button id="captureBtn" class="bg-industrial-500 hover:bg-industrial-400 text-white px-3 py-1 rounded text-sm flex items-center">
211
+ <i class="fas fa-camera mr-1"></i> Capture & Analyze
212
+ </button>
213
+ </div>
214
+ </div>
215
+ <div class="p-4">
216
+ <div class="camera-feed h-96 flex items-center justify-center relative">
217
+ <video id="cameraFeed" class="w-full h-full object-contain" autoplay playsinline></video>
218
+ <canvas id="captureCanvas" class="hidden"></canvas>
219
+ </div>
220
+
221
+ <div class="mt-4 text-center text-industrial-300 text-sm">
222
+ <i class="fas fa-microchip mr-1"></i> Using Gemini 2.5 Flash OCR
223
+ </div>
224
+ </div>
225
+ </div>
226
+ </div>
227
+
228
+ <!-- Temperature Dashboard -->
229
+ <div class="industrial-card">
230
+ <div class="p-4 border-b border-industrial-700">
231
+ <h2 class="text-xl font-bold text-white">Temperature Dashboard</h2>
232
+ </div>
233
+ <div class="p-4">
234
+ <div class="flex flex-col items-center mb-6">
235
+ <div class="text-industrial-300 mb-2">Current Temperature</div>
236
+ <div id="currentTemp" class="temperature-display text-industrial-200">--°C</div>
237
+ <div id="tempStatus" class="mt-2 px-3 py-1 rounded-full bg-industrial-700 text-industrial-300 text-sm">
238
+ <span class="status-indicator"></span> No data
239
+ </div>
240
+ </div>
241
+
242
+ <div class="grid grid-cols-2 gap-4 mb-6">
243
+ <div class="industrial-card bg-industrial-800 p-4 rounded-lg">
244
+ <div class="text-industrial-400 text-sm mb-1">Maximum</div>
245
+ <div id="maxTemp" class="text-2xl font-bold text-white">--°C</div>
246
+ </div>
247
+ <div class="industrial-card bg-industrial-800 p-4 rounded-lg">
248
+ <div class="text-industrial-400 text-sm mb-1">Minimum</div>
249
+ <div id="minTemp" class="text-2xl font-bold text-white">--°C</div>
250
+ </div>
251
+ </div>
252
+
253
+ <div class="flex justify-center mb-4">
254
+ <div class="gauge">
255
+ <svg width="200" height="200" viewBox="0 0 200 200">
256
+ <circle class="gauge-circle" cx="100" cy="100" r="90" />
257
+ <circle id="gaugeProgress" class="gauge-progress" cx="100" cy="100" r="90"
258
+ stroke-dasharray="0 565" />
259
+ </svg>
260
+ <div class="absolute inset-0 flex items-center justify-center">
261
+ <div id="gaugeValue" class="text-3xl font-bold text-white">--</div>
262
+ </div>
263
+ </div>
264
+ </div>
265
+
266
+ <div class="text-center text-industrial-400 text-sm">
267
+ <i class="fas fa-thermometer-half mr-1"></i> Normal Range: 20°C - 35°C
268
+ </div>
269
+ </div>
270
+ </div>
271
+ </div>
272
+
273
+ <!-- History Log -->
274
+ <div class="industrial-card mb-8">
275
+ <div class="p-4 border-b border-industrial-700">
276
+ <h2 class="text-xl font-bold text-white">Temperature History</h2>
277
+ </div>
278
+ <div class="p-4">
279
+ <div class="flex justify-between mb-4">
280
+ <div class="text-industrial-300">
281
+ Last 20 readings
282
+ </div>
283
+ <div class="flex space-x-2">
284
+ <button class="bg-industrial-700 hover:bg-industrial-600 text-white px-3 py-1 rounded text-sm">
285
+ <i class="fas fa-download mr-1"></i> Export Data
286
+ </button>
287
+ </div>
288
+ </div>
289
+
290
+ <div class="overflow-x-auto">
291
+ <table class="min-w-full divide-y divide-industrial-700">
292
+ <thead>
293
+ <tr>
294
+ <th class="px-4 py-3 text-left text-xs font-medium text-industrial-400 uppercase tracking-wider">Timestamp</th>
295
+ <th class="px-4 py-3 text-left text-xs font-medium text-industrial-400 uppercase tracking-wider">Temperature</th>
296
+ <th class="px-4 py-3 text-left text-xs font-medium text-industrial-400 uppercase tracking-wider">Status</th>
297
+ <th class="px-4 py-3 text-left text-xs font-medium text-industrial-400 uppercase tracking-wider">Actions</th>
298
+ </tr>
299
+ </thead>
300
+ <tbody id="historyBody" class="divide-y divide-industrial-800">
301
+ <!-- History items will be added here dynamically -->
302
+ <tr>
303
+ <td colspan="4" class="px-4 py-8 text-center text-industrial-500">
304
+ No temperature data recorded yet
305
+ </td>
306
+ </tr>
307
+ </tbody>
308
+ </table>
309
+ </div>
310
+ </div>
311
+ </div>
312
+
313
+ <!-- System Status -->
314
+ <div class="dashboard-grid mb-8">
315
+ <div class="industrial-card">
316
+ <div class="p-4 border-b border-industrial-700">
317
+ <h2 class="text-xl font-bold text-white">OCR Status</h2>
318
+ </div>
319
+ <div class="p-4">
320
+ <div class="flex items-center mb-4">
321
+ <div class="mr-4">
322
+ <div class="bg-industrial-700 rounded-full p-3">
323
+ <i class="fas fa-eye text-industrial-300 text-2xl"></i>
324
+ </div>
325
+ </div>
326
+ <div>
327
+ <div class="text-industrial-300">Gemini OCR Engine</div>
328
+ <div class="text-white font-bold text-lg">Operational</div>
329
+ </div>
330
+ </div>
331
+ <div class="bg-industrial-800 rounded-lg p-3">
332
+ <div class="text-industrial-400 text-sm mb-1">Last OCR Result</div>
333
+ <div id="lastOcrResult" class="text-industrial-200 font-mono">Waiting for first capture...</div>
334
+ </div>
335
+ </div>
336
+ </div>
337
+
338
+ <div class="industrial-card">
339
+ <div class="p-4 border-b border-industrial-700">
340
+ <h2 class="text-xl font-bold text-white">System Alerts</h2>
341
+ </div>
342
+ <div class="p-4">
343
+ <div class="flex items-center mb-4">
344
+ <div class="mr-4">
345
+ <div class="bg-industrial-700 rounded-full p-3">
346
+ <i class="fas fa-bell text-industrial-300 text-2xl"></i>
347
+ </div>
348
+ </div>
349
+ <div>
350
+ <div class="text-industrial-300">Alert Status</div>
351
+ <div class="text-white font-bold text-lg">No Active Alerts</div>
352
+ </div>
353
+ </div>
354
+ <div class="bg-industrial-800 rounded-lg p-3">
355
+ <div class="text-industrial-400 text-sm mb-1">Notification Settings</div>
356
+ <div class="text-industrial-200">Email alerts enabled for temperatures above 35°C</div>
357
+ </div>
358
+ </div>
359
+ </div>
360
+
361
+ <div class="industrial-card">
362
+ <div class="p-4 border-b border-industrial-700">
363
+ <h2 class="text-xl font-bold text-white">API Status</h2>
364
+ </div>
365
+ <div class="p-4">
366
+ <div class="flex items-center mb-4">
367
+ <div class="mr-4">
368
+ <div class="bg-industrial-700 rounded-full p-3">
369
+ <i class="fas fa-plug text-industrial-300 text-2xl"></i>
370
+ </div>
371
+ </div>
372
+ <div>
373
+ <div class="text-industrial-300">Gemini API</div>
374
+ <div class="text-white font-bold text-lg">Connected</div>
375
+ </div>
376
+ </div>
377
+ <div class="bg-industrial-800 rounded-lg p-3">
378
+ <div class="text-industrial-400 text-sm mb-1">Rate Limit Status</div>
379
+ <div class="text-industrial-200">10 requests/min available (1 used)</div>
380
+ </div>
381
+ </div>
382
+ </div>
383
+ </div>
384
+
385
+ <!-- Footer -->
386
+ <footer class="text-center text-industrial-500 text-sm pt-6 border-t border-industrial-800">
387
+ <p>ThermoScanAI - Industrial Machine Temperature Monitoring System | Using Gemini 2.5 Flash OCR</p>
388
+ <p class="mt-2">© 2023 Industrial AI Solutions. All rights reserved.</p>
389
+ </footer>
390
+ </div>
391
+
392
+ <script>
393
+ // DOM Elements
394
+ const cameraFeed = document.getElementById('cameraFeed');
395
+ const cameraPlaceholder = document.getElementById('cameraPlaceholder');
396
+ const captureCanvas = document.getElementById('captureCanvas');
397
+ const startBtn = document.getElementById('startBtn');
398
+ const stopBtn = document.getElementById('stopBtn');
399
+ const countdownEl = document.getElementById('countdown');
400
+ const currentTempEl = document.getElementById('currentTemp');
401
+ const maxTempEl = document.getElementById('maxTemp');
402
+ const minTempEl = document.getElementById('minTemp');
403
+ const tempStatusEl = document.getElementById('tempStatus');
404
+ const historyBody = document.getElementById('historyBody');
405
+ const gaugeProgress = document.getElementById('gaugeProgress');
406
+ const gaugeValue = document.getElementById('gaugeValue');
407
+ const lastOcrResult = document.getElementById('lastOcrResult');
408
+
409
+ // API Key Management
410
+ let apiKey = localStorage.getItem('geminiApiKey') || '';
411
+ const apiKeyModal = document.createElement('div');
412
+ apiKeyModal.className = 'fixed inset-0 bg-industrial-900 bg-opacity-90 flex items-center justify-center z-50 hidden';
413
+ apiKeyModal.innerHTML = `
414
+ <div class="bg-industrial-800 rounded-lg p-6 max-w-md w-full">
415
+ <h3 class="text-xl font-bold mb-4">Enter Gemini API Key</h3>
416
+ <input type="password" id="apiKeyInput" placeholder="Your Gemini API Key"
417
+ class="w-full bg-industrial-700 border border-industrial-600 rounded p-3 mb-4 text-white">
418
+ <div class="flex justify-end space-x-3">
419
+ <button id="cancelApiKey" class="px-4 py-2 rounded bg-industrial-600 hover:bg-industrial-500">
420
+ Cancel
421
+ </button>
422
+ <button id="saveApiKey" class="px-4 py-2 rounded bg-industrial-500 hover:bg-industrial-400">
423
+ Save Key
424
+ </button>
425
+ </div>
426
+ </div>
427
+ `;
428
+ document.body.appendChild(apiKeyModal);
429
+
430
+ // App State
431
+ let monitoringInterval;
432
+ let countdownInterval;
433
+ let countdown = 10;
434
+ let temperatureHistory = [];
435
+ let maxTemp = null;
436
+ let minTemp = null;
437
+ let stream = null;
438
+ let hasValidApiKey = false;
439
+
440
+ // Initialize the app
441
+ async function init() {
442
+ // Set up capture button
443
+ document.getElementById('captureBtn').addEventListener('click', captureAndProcess);
444
+ updateGauge(0);
445
+
446
+ // Initialize camera when user clicks capture for the first time
447
+ document.getElementById('captureBtn').addEventListener('click', async function firstCapture() {
448
+ try {
449
+ if (!stream) {
450
+ stream = await navigator.mediaDevices.getUserMedia({
451
+ video: {
452
+ facingMode: 'environment',
453
+ width: { ideal: 1280 },
454
+ height: { ideal: 720 }
455
+ }
456
+ });
457
+ cameraFeed.srcObject = stream;
458
+ }
459
+ } catch (err) {
460
+ console.error("Error accessing camera:", err);
461
+ cameraFeed.parentElement.innerHTML = `
462
+ <div class="text-center text-industrial-300 p-4">
463
+ <i class="fas fa-video-slash text-4xl mb-2"></i>
464
+ <p>Could not access camera. Please check permissions.</p>
465
+ <button onclick="window.location.reload()" class="mt-2 bg-industrial-600 hover:bg-industrial-500 text-white px-4 py-2 rounded-lg">
466
+ Try Again
467
+ </button>
468
+ </div>
469
+ `;
470
+ }
471
+ // Remove this event listener after first run
472
+ document.getElementById('captureBtn').removeEventListener('click', firstCapture);
473
+ }, { once: true });
474
+
475
+ // API Key management
476
+ document.getElementById('settingsBtn').addEventListener('click', () => {
477
+ apiKeyModal.classList.remove('hidden');
478
+ document.getElementById('apiKeyInput').value = apiKey;
479
+ });
480
+
481
+ document.getElementById('saveApiKey').addEventListener('click', () => {
482
+ apiKey = document.getElementById('apiKeyInput').value.trim();
483
+ localStorage.setItem('geminiApiKey', apiKey);
484
+ apiKeyModal.classList.add('hidden');
485
+ hasValidApiKey = apiKey.length > 0;
486
+ });
487
+
488
+ document.getElementById('cancelApiKey').addEventListener('click', () => {
489
+ apiKeyModal.classList.add('hidden');
490
+ });
491
+
492
+ hasValidApiKey = apiKey.length > 0;
493
+ }
494
+
495
+
496
+ // Update countdown display
497
+ function updateCountdown() {
498
+ countdownEl.textContent = countdown;
499
+
500
+ if (countdown <= 0) {
501
+ countdown = 10;
502
+ }
503
+
504
+ countdownInterval = setTimeout(() => {
505
+ countdown--;
506
+ updateCountdown();
507
+ }, 1000);
508
+ }
509
+
510
+ // Capture image and process
511
+ function captureAndProcess() {
512
+ if (!stream) return;
513
+
514
+ // Capture frame
515
+ const context = captureCanvas.getContext('2d');
516
+ captureCanvas.width = cameraFeed.videoWidth;
517
+ captureCanvas.height = cameraFeed.videoHeight;
518
+ context.drawImage(cameraFeed, 0, 0, captureCanvas.width, captureCanvas.height);
519
+
520
+ // Convert to base64 for API
521
+ const imageData = captureCanvas.toDataURL('image/jpeg').split(',')[1];
522
+
523
+ // Process with Gemini
524
+ processWithGemini(imageData);
525
+ }
526
+
527
+ // Process with Gemini API
528
+ async function processWithGemini(imageData) {
529
+ if (!hasValidApiKey) {
530
+ lastOcrResult.textContent = "API Key not configured";
531
+ currentTempEl.textContent = "--°C";
532
+ gaugeValue.textContent = "--";
533
+ return;
534
+ }
535
+
536
+ lastOcrResult.textContent = "Processing image...";
537
+
538
+ try {
539
+ const response = await fetch(`https://generativelanguage.googleapis.com/v1beta/models/gemini-pro-vision:generateContent?key=${apiKey}`, {
540
+ method: 'POST',
541
+ headers: {
542
+ 'Content-Type': 'application/json',
543
+ },
544
+ body: JSON.stringify({
545
+ contents: [{
546
+ parts: [{
547
+ text: "Extract the numerical temperature value from this image. Only return the number, nothing else."
548
+ }, {
549
+ inlineData: {
550
+ mimeType: "image/jpeg",
551
+ data: imageData
552
+ }
553
+ }]
554
+ }]
555
+ })
556
+ });
557
+
558
+ const data = await response.json();
559
+
560
+ if (data.candidates && data.candidates[0].content.parts[0].text) {
561
+ const result = data.candidates[0].content.parts[0].text;
562
+ const tempMatch = result.match(/\d+(\.\d+)?/);
563
+
564
+ if (tempMatch) {
565
+ const temperature = parseFloat(tempMatch[0]);
566
+ lastOcrResult.textContent = `Detected temperature: ${temperature}°C`;
567
+ updateTemperature(temperature);
568
+ } else {
569
+ lastOcrResult.textContent = "No temperature detected";
570
+ currentTempEl.textContent = "--°C";
571
+ gaugeValue.textContent = "--";
572
+ }
573
+ } else {
574
+ lastOcrResult.textContent = "Failed to process image";
575
+ currentTempEl.textContent = "--°C";
576
+ gaugeValue.textContent = "--";
577
+ }
578
+ } catch (error) {
579
+ console.error("Gemini API error:", error);
580
+ lastOcrResult.textContent = "API Error - Check console";
581
+ currentTempEl.textContent = "--°C";
582
+ gaugeValue.textContent = "--";
583
+ }
584
+ }
585
+
586
+ // Update temperature display and history
587
+ function updateTemperature(temp) {
588
+ // Update current temperature
589
+ currentTempEl.textContent = `${temp}°C`;
590
+
591
+ // Update gauge
592
+ updateGauge(temp);
593
+
594
+ // Update status
595
+ updateStatus(temp);
596
+
597
+ // Update history
598
+ temperatureHistory.push({
599
+ temp: temp,
600
+ timestamp: new Date().toLocaleTimeString(),
601
+ status: getStatus(temp)
602
+ });
603
+
604
+ // Keep only last 20 readings
605
+ if (temperatureHistory.length > 20) {
606
+ temperatureHistory.shift();
607
+ }
608
+
609
+ // Update min/max
610
+ if (maxTemp === null || temp > maxTemp) {
611
+ maxTemp = temp;
612
+ maxTempEl.textContent = `${maxTemp}°C`;
613
+ }
614
+
615
+ if (minTemp === null || temp < minTemp) {
616
+ minTemp = temp;
617
+ minTempEl.textContent = `${minTemp}°C`;
618
+ }
619
+
620
+ // Update history table
621
+ updateHistoryTable();
622
+ }
623
+
624
+ // Update gauge display
625
+ function updateGauge(temp) {
626
+ // Normalize temperature to gauge range (0-50°C)
627
+ const percentage = Math.min(Math.max((temp / 50) * 100, 0), 100);
628
+ const dashValue = (565 * percentage) / 100;
629
+
630
+ gaugeProgress.style.strokeDasharray = `${dashValue} 565`;
631
+ gaugeValue.textContent = `${temp}°C`;
632
+
633
+ // Update gauge color based on temperature
634
+ if (temp > 35) {
635
+ gaugeProgress.style.stroke = '#ef4444';
636
+ } else if (temp > 30) {
637
+ gaugeProgress.style.stroke = '#f59e0b';
638
+ } else {
639
+ gaugeProgress.style.stroke = '#38bdf8';
640
+ }
641
+ }
642
+
643
+ // Update temperature status
644
+ function updateStatus(temp) {
645
+ const statusIndicator = tempStatusEl.querySelector('.status-indicator');
646
+ statusIndicator.className = 'status-indicator';
647
+
648
+ if (temp > 35) {
649
+ tempStatusEl.innerHTML = '<span class="status-indicator status-danger"></span> CRITICAL TEMPERATURE';
650
+ tempStatusEl.className = 'mt-2 px-3 py-1 rounded-full bg-danger-900 text-danger-200 text-sm';
651
+ currentTempEl.classList.add('text-danger-500');
652
+ currentTempEl.classList.remove('text-industrial-200', 'text-warning-500');
653
+ } else if (temp > 30) {
654
+ tempStatusEl.innerHTML = '<span class="status-indicator status-warning"></span> HIGH TEMPERATURE';
655
+ tempStatusEl.className = 'mt-2 px-3 py-1 rounded-full bg-warning-900 text-warning-200 text-sm';
656
+ currentTempEl.classList.add('text-warning-500');
657
+ currentTempEl.classList.remove('text-industrial-200', 'text-danger-500');
658
+ } else {
659
+ tempStatusEl.innerHTML = '<span class="status-indicator status-normal"></span> NORMAL';
660
+ tempStatusEl.className = 'mt-2 px-3 py-1 rounded-full bg-industrial-800 text-industrial-300 text-sm';
661
+ currentTempEl.classList.add('text-industrial-200');
662
+ currentTempEl.classList.remove('text-warning-500', 'text-danger-500');
663
+ }
664
+ }
665
+
666
+ // Get status for history
667
+ function getStatus(temp) {
668
+ if (temp > 35) return 'critical';
669
+ if (temp > 30) return 'warning';
670
+ return 'normal';
671
+ }
672
+
673
+ // Update history table
674
+ function updateHistoryTable() {
675
+ if (temperatureHistory.length === 0) {
676
+ historyBody.innerHTML = `
677
+ <tr>
678
+ <td colspan="4" class="px-4 py-8 text-center text-industrial-500">
679
+ No temperature data recorded yet
680
+ </td>
681
+ </tr>
682
+ `;
683
+ return;
684
+ }
685
+
686
+ let historyHTML = '';
687
+ temperatureHistory.slice().reverse().forEach(reading => {
688
+ let statusClass = '';
689
+ let statusText = '';
690
+
691
+ switch(reading.status) {
692
+ case 'critical':
693
+ statusClass = 'text-danger-500';
694
+ statusText = 'Critical';
695
+ break;
696
+ case 'warning':
697
+ statusClass = 'text-warning-500';
698
+ statusText = 'Warning';
699
+ break;
700
+ default:
701
+ statusClass = 'text-success-500';
702
+ statusText = 'Normal';
703
+ }
704
+
705
+ historyHTML += `
706
+ <tr class="history-item">
707
+ <td class="px-4 py-3 whitespace-nowrap text-sm text-industrial-300">${reading.timestamp}</td>
708
+ <td class="px-4 py-3 whitespace-nowrap">
709
+ <div class="text-lg font-bold ${reading.status === 'critical' ? 'text-danger-500' : reading.status === 'warning' ? 'text-warning-500' : 'text-industrial-200'}">
710
+ ${reading.temp}°C
711
+ </div>
712
+ </td>
713
+ <td class="px-4 py-3 whitespace-nowrap">
714
+ <span class="${statusClass} font-medium">${statusText}</span>
715
+ </td>
716
+ <td class="px-4 py-3 whitespace-nowrap text-sm">
717
+ <button class="text-industrial-400 hover:text-industrial-300 mr-2">
718
+ <i class="fas fa-chart-line"></i>
719
+ </button>
720
+ <button class="text-industrial-400 hover:text-industrial-300">
721
+ <i class="fas fa-info-circle"></i>
722
+ </button>
723
+ </td>
724
+ </tr>
725
+ `;
726
+ });
727
+
728
+ historyBody.innerHTML = historyHTML;
729
+ }
730
+
731
+ // Initialize the app when DOM is loaded
732
+ document.addEventListener('DOMContentLoaded', init);
733
+ </script>
734
+ <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=pksaheb/temperature-monitoring" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
735
+ </html>