Prathamesh1420 commited on
Commit
fa6331d
·
verified ·
1 Parent(s): 5f6b473

Create index.html

Browse files
Files changed (1) hide show
  1. index.html +1130 -0
index.html ADDED
@@ -0,0 +1,1130 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
9
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
10
+ <script>
11
+ tailwind.config = {
12
+ theme: {
13
+ extend: {
14
+ colors: {
15
+ industrial: {
16
+ 50: '#f0f9ff',
17
+ 100: '#e0f2fe',
18
+ 200: '#bae6fd',
19
+ 300: '#7dd3fc',
20
+ 400: '#38bdf8',
21
+ 500: '#0ea5e9',
22
+ 600: '#0284c7',
23
+ 700: '#0369a1',
24
+ 800: '#075985',
25
+ 900: '#0c4a6e',
26
+ },
27
+ danger: {
28
+ 500: '#ef4444',
29
+ 600: '#dc2626',
30
+ },
31
+ warning: {
32
+ 500: '#f59e0b',
33
+ 600: '#d97706',
34
+ },
35
+ success: {
36
+ 500: '#10b981',
37
+ 600: '#059669',
38
+ }
39
+ }
40
+ }
41
+ }
42
+ }
43
+ </script>
44
+ <style>
45
+ @import url('https://fonts.googleapis.com/css2?family=Roboto+Mono:wght@300;400;500;600;700&display=swap');
46
+
47
+ body {
48
+ font-family: 'Roboto Mono', monospace;
49
+ background: linear-gradient(135deg, #1a2a3a 0%, #0f172a 100%);
50
+ color: #e2e8f0;
51
+ min-height: 100vh;
52
+ }
53
+
54
+ .dashboard-grid {
55
+ display: grid;
56
+ grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
57
+ gap: 1.5rem;
58
+ }
59
+
60
+ .industrial-card {
61
+ background: rgba(15, 23, 42, 0.7);
62
+ border: 1px solid rgba(56, 189, 248, 0.2);
63
+ border-radius: 0.75rem;
64
+ backdrop-filter: blur(10px);
65
+ box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.3);
66
+ transition: all 0.3s ease;
67
+ }
68
+
69
+ .industrial-card:hover {
70
+ transform: translateY(-5px);
71
+ box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.3);
72
+ }
73
+
74
+ .camera-feed {
75
+ border: 2px dashed rgba(56, 189, 248, 0.5);
76
+ border-radius: 0.5rem;
77
+ background: rgba(15, 23, 42, 0.5);
78
+ }
79
+
80
+ .temperature-display {
81
+ font-size: 5rem;
82
+ font-weight: 700;
83
+ text-shadow: 0 0 10px rgba(56, 189, 248, 0.7);
84
+ transition: all 0.5s ease;
85
+ }
86
+
87
+ .status-indicator {
88
+ width: 12px;
89
+ height: 12px;
90
+ border-radius: 50%;
91
+ display: inline-block;
92
+ margin-right: 8px;
93
+ }
94
+
95
+ .status-normal { background-color: #10b981; }
96
+ .status-warning { background-color: #f59e0b; }
97
+ .status-danger { background-color: #ef4444; }
98
+
99
+ .history-item {
100
+ border-left: 3px solid #38bdf8;
101
+ transition: all 0.2s ease;
102
+ }
103
+
104
+ .history-item:hover {
105
+ background: rgba(56, 189, 248, 0.1);
106
+ transform: translateX(5px);
107
+ }
108
+
109
+ .gauge {
110
+ position: relative;
111
+ width: 200px;
112
+ height: 200px;
113
+ }
114
+
115
+ .gauge-circle {
116
+ fill: none;
117
+ stroke: rgba(30, 41, 59, 0.8);
118
+ stroke-width: 10;
119
+ }
120
+
121
+ .gauge-progress {
122
+ fill: none;
123
+ stroke: #38bdf8;
124
+ stroke-width: 10;
125
+ stroke-linecap: round;
126
+ transform: rotate(-90deg);
127
+ transform-origin: 50% 50%;
128
+ transition: stroke-dasharray 0.5s ease;
129
+ }
130
+
131
+ .pulse {
132
+ animation: pulse 2s infinite;
133
+ }
134
+
135
+ @keyframes pulse {
136
+ 0% { box-shadow: 0 0 0 0 rgba(56, 189, 248, 0.7); }
137
+ 70% { box-shadow: 0 0 0 10px rgba(56, 189, 248, 0); }
138
+ 100% { box-shadow: 0 0 0 0 rgba(56, 189, 248, 0); }
139
+ }
140
+
141
+ .glow {
142
+ text-shadow: 0 0 10px rgba(56, 189, 248, 0.7);
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
+ .modal-content {
158
+ background: #1e293b;
159
+ border-radius: 0.5rem;
160
+ padding: 1.5rem;
161
+ width: 90%;
162
+ max-width: 500px;
163
+ box-shadow: 0 10px 25px rgba(0, 0, 0, 0.5);
164
+ }
165
+ .modal-actions {
166
+ display: flex;
167
+ justify-content: flex-end;
168
+ gap: 0.75rem;
169
+ margin-top: 1rem;
170
+ }
171
+ </style>
172
+ </head>
173
+ <body class="min-h-screen p-4 md:p-8">
174
+ <div class="max-w-7xl mx-auto">
175
+ <!-- Header -->
176
+ <header class="flex flex-col md:flex-row justify-between items-center mb-8 md:mb-12">
177
+ <div class="flex items-center mb-4 md:mb-0">
178
+ <div class="bg-industrial-600 p-3 rounded-lg mr-4">
179
+ <i class="fas fa-industry text-3xl text-industrial-200"></i>
180
+ </div>
181
+ <div>
182
+ <h1 class="text-2xl md:text-3xl font-bold text-white">ThermoScan<span class="text-industrial-400">AI</span></h1>
183
+ <p class="text-industrial-300 text-sm">Industrial Machine Temperature Monitoring</p>
184
+ </div>
185
+ </div>
186
+
187
+ <div class="flex items-center space-x-4">
188
+ <div class="hidden md:block">
189
+ <div class="flex items-center">
190
+ <span class="status-indicator status-normal"></span>
191
+ <span class="text-industrial-300">System Status: <span class="text-success-500 font-medium">Operational</span></span>
192
+ </div>
193
+ <div class="text-xs text-industrial-400 mt-1">Connected to Gemini 1.5 Flash API</div>
194
+ </div>
195
+ <button id="settingsBtn" class="bg-industrial-600 hover:bg-industrial-500 text-white px-4 py-2 rounded-lg transition flex items-center">
196
+ <i class="fas fa-cog mr-2"></i> Settings
197
+ </button>
198
+ </div>
199
+ </header>
200
+
201
+ <div class="grid grid-cols-1 lg:grid-cols-3 gap-6 mb-8">
202
+ <!-- Main Camera Feed -->
203
+ <div class="lg:col-span-2">
204
+ <div class="industrial-card h-full">
205
+ <div class="p-4 border-b border-industrial-700 flex justify-between items-center">
206
+ <h2 class="text-xl font-bold text-white">Machine Camera Feed</h2>
207
+ <div class="flex space-x-2">
208
+ <button id="captureBtn" class="bg-industrial-500 hover:bg-industrial-400 text-white px-3 py-1 rounded text-sm flex items-center">
209
+ <i class="fas fa-camera mr-1"></i> Capture & Analyze
210
+ </button>
211
+ </div>
212
+ </div>
213
+ <div class="p-4">
214
+ <div class="camera-feed h-96 flex items-center justify-center relative">
215
+ <video id="cameraFeed" class="w-full h-full object-contain" autoplay playsinline></video>
216
+ <canvas id="captureCanvas" class="hidden"></canvas>
217
+ </div>
218
+
219
+ <div class="mt-4 text-center text-industrial-300 text-sm">
220
+ <i class="fas fa-microchip mr-1"></i> Using Gemini 2.5 Flash OCR
221
+ </div>
222
+ </div>
223
+ </div>
224
+ </div>
225
+
226
+ <!-- Temperature Dashboard -->
227
+ <div class="industrial-card">
228
+ <div class="p-4 border-b border-industrial-700">
229
+ <h2 class="text-xl font-bold text-white">Temperature Dashboard</h2>
230
+ </div>
231
+ <div class="p-4">
232
+ <div class="flex flex-col items-center mb-6">
233
+ <div class="text-industrial-300 mb-2">Current Temperature</div>
234
+ <div id="currentTemp" class="temperature-display text-industrial-200">--°C</div>
235
+ <div id="tempStatus" class="mt-2 px-3 py-1 rounded-full bg-industrial-700 text-industrial-300 text-sm">
236
+ <span class="status-indicator"></span> No data
237
+ </div>
238
+ </div>
239
+
240
+ <div class="grid grid-cols-2 gap-4 mb-6">
241
+ <div class="industrial-card bg-industrial-800 p-4 rounded-lg">
242
+ <div class="text-industrial-400 text-sm mb-1">Maximum</div>
243
+ <div id="maxTemp" class="text-2xl font-bold text-white">--°C</div>
244
+ </div>
245
+ <div class="industrial-card bg-industrial-800 p-4 rounded-lg">
246
+ <div class="text-industrial-400 text-sm mb-1">Minimum</div>
247
+ <div id="minTemp" class="text-2xl font-bold text-white">--°C</div>
248
+ </div>
249
+ </div>
250
+
251
+ <div class="flex justify-center mb-4">
252
+ <div class="gauge">
253
+ <svg width="200" height="200" viewBox="0 0 200 200">
254
+ <circle class="gauge-circle" cx="100" cy="100" r="90" />
255
+ <circle id="gaugeProgress" class="gauge-progress" cx="100" cy="100" r="90"
256
+ stroke-dasharray="0 565" />
257
+ </svg>
258
+ <div class="absolute inset-0 flex items-center justify-center">
259
+ <div id="gaugeValue" class="text-3xl font-bold text-white">--</div>
260
+ </div>
261
+ </div>
262
+ </div>
263
+
264
+ <div class="text-center text-industrial-400 text-sm">
265
+ <i class="fas fa-thermometer-half mr-1"></i> Normal Range: 20°C - 35°C
266
+ </div>
267
+ </div>
268
+ </div>
269
+ </div>
270
+
271
+ <!-- History Log -->
272
+ <div class="industrial-card mb-8">
273
+ <div class="p-4 border-b border-industrial-700">
274
+ <h2 class="text-xl font-bold text-white">Temperature History</h2>
275
+ </div>
276
+ <div class="p-4">
277
+ <div class="flex justify-between mb-4">
278
+ <div class="text-industrial-300">
279
+ Last 20 readings
280
+ </div>
281
+ <div class="flex space-x-2">
282
+ <button id="exportDataBtn" class="bg-industrial-700 hover:bg-industrial-600 text-white px-3 py-1 rounded text-sm">
283
+ <i class="fas fa-download mr-1"></i> Export Data
284
+ </button>
285
+ <button id="stopRecordingBtn" class="bg-danger-600 hover:bg-danger-500 text-white px-3 py-1 rounded text-sm ml-2 hidden">
286
+ <i class="fas fa-stop mr-1"></i> Stop Recording
287
+ </button>
288
+ </div>
289
+ </div>
290
+
291
+ <div class="overflow-x-auto">
292
+ <table class="min-w-full divide-y divide-industrial-700">
293
+ <thead>
294
+ <tr>
295
+ <th class="px-4 py-3 text-left text-xs font-medium text-industrial-400 uppercase tracking-wider">Timestamp</th>
296
+ <th class="px-4 py-3 text-left text-xs font-medium text-industrial-400 uppercase tracking-wider">Temperature</th>
297
+ <th class="px-4 py-3 text-left text-xs font-medium text-industrial-400 uppercase tracking-wider">Status</th>
298
+ <th class="px-4 py-3 text-left text-xs font-medium text-industrial-400 uppercase tracking-wider">Actions</th>
299
+ </tr>
300
+ </thead>
301
+ <tbody id="historyBody" class="divide-y divide-industrial-800">
302
+ <!-- History items will be added here dynamically -->
303
+ <tr>
304
+ <td colspan="4" class="px-4 py-8 text-center text-industrial-500">
305
+ No temperature data recorded yet
306
+ </td>
307
+ </tr>
308
+ </tbody>
309
+ </table>
310
+ </div>
311
+ </div>
312
+ </div>
313
+
314
+ <!-- Real-time Chart -->
315
+ <div class="industrial-card mb-8">
316
+ <div class="p-4 border-b border-industrial-700">
317
+ <h2 class="text-xl font-bold text-white">Temperature Trend</h2>
318
+ </div>
319
+ <div class="p-4">
320
+ <canvas id="liveChart" height="300"></canvas>
321
+ </div>
322
+ </div>
323
+
324
+ <!-- System Status -->
325
+ <div class="dashboard-grid mb-8">
326
+ <div class="industrial-card">
327
+ <div class="p-4 border-b border-industrial-700">
328
+ <h2 class="text-xl font-bold text-white">OCR Status</h2>
329
+ </div>
330
+ <div class="p-4">
331
+ <div class="flex items-center mb-4">
332
+ <div class="mr-4">
333
+ <div class="bg-industrial-700 rounded-full p-3">
334
+ <i class="fas fa-eye text-industrial-300 text-2xl"></i>
335
+ </div>
336
+ </div>
337
+ <div>
338
+ <div class="text-industrial-300">Gemini OCR Engine</div>
339
+ <div class="text-white font-bold text-lg">Operational</div>
340
+ </div>
341
+ </div>
342
+ <div class="bg-industrial-800 rounded-lg p-3">
343
+ <div class="text-industrial-400 text-sm mb-1">Last OCR Result</div>
344
+ <div id="lastOcrResult" class="text-industrial-200 font-mono">Waiting for first capture...</div>
345
+ </div>
346
+ </div>
347
+ </div>
348
+
349
+ <div class="industrial-card">
350
+ <div class="p-4 border-b border-industrial-700">
351
+ <h2 class="text-xl font-bold text-white">System Alerts</h2>
352
+ </div>
353
+ <div class="p-4">
354
+ <div class="flex items-center mb-4">
355
+ <div class="mr-4">
356
+ <div class="bg-industrial-700 rounded-full p-3">
357
+ <i class="fas fa-bell text-industrial-300 text-2xl"></i>
358
+ </div>
359
+ </div>
360
+ <div>
361
+ <div class="text-industrial-300">Alert Status</div>
362
+ <div class="text-white font-bold text-lg">No Active Alerts</div>
363
+ </div>
364
+ </div>
365
+ <div class="bg-industrial-800 rounded-lg p-3">
366
+ <div class="text-industrial-400 text-sm mb-1">Notification Settings</div>
367
+ <div class="text-industrial-200">Email alerts enabled for temperatures above 35°C</div>
368
+ </div>
369
+ </div>
370
+ </div>
371
+
372
+ <div class="industrial-card">
373
+ <div class="p-4 border-b border-industrial-700">
374
+ <h2 class="text-xl font-bold text-white">API Status</h2>
375
+ </div>
376
+ <div class="p-4">
377
+ <div class="flex items-center mb-4">
378
+ <div class="mr-4">
379
+ <div class="bg-industrial-700 rounded-full p-3">
380
+ <i class="fas fa-plug text-industrial-300 text-2xl"></i>
381
+ </div>
382
+ </div>
383
+ <div>
384
+ <div class="text-industrial-300">Gemini API</div>
385
+ <div class="text-white font-bold text-lg">Connected</div>
386
+ </div>
387
+ </div>
388
+ <div class="bg-industrial-800 rounded-lg p-3">
389
+ <div class="text-industrial-400 text-sm mb-1">Rate Limit Status</div>
390
+ <div class="text-industrial-200">10 requests/min available (1 used)</div>
391
+ </div>
392
+ </div>
393
+ </div>
394
+ </div>
395
+
396
+ <!-- Debug Console -->
397
+ <div class="industrial-card mb-8">
398
+ <div class="p-4 border-b border-industrial-700">
399
+ <h2 class="text-xl font-bold text-white">Debug Console</h2>
400
+ </div>
401
+ <div class="p-4">
402
+ <div id="debugConsole" class="bg-black text-green-400 font-mono text-sm p-4 rounded h-64 overflow-y-auto"></div>
403
+ </div>
404
+ </div>
405
+
406
+ <!-- Footer -->
407
+ <footer class="text-center text-industrial-500 text-sm pt-6 border-t border-industrial-800">
408
+ <p>ThermoScanAI - Industrial Machine Temperature Monitoring System | Using Gemini 1.5 Flash OCR</p>
409
+ <p class="mt-2">© 2023 Industrial AI Solutions. All rights reserved.</p>
410
+ </footer>
411
+ </div>
412
+
413
+ <script>
414
+ // DOM Elements
415
+ const cameraFeed = document.getElementById('cameraFeed');
416
+ const cameraPlaceholder = document.getElementById('cameraPlaceholder');
417
+ const captureCanvas = document.getElementById('captureCanvas');
418
+ const startBtn = document.getElementById('startBtn');
419
+ const stopBtn = document.getElementById('stopBtn');
420
+ const countdownEl = document.getElementById('countdown');
421
+ const currentTempEl = document.getElementById('currentTemp');
422
+ const maxTempEl = document.getElementById('maxTemp');
423
+ const minTempEl = document.getElementById('minTemp');
424
+ const tempStatusEl = document.getElementById('tempStatus');
425
+ const historyBody = document.getElementById('historyBody');
426
+ const gaugeProgress = document.getElementById('gaugeProgress');
427
+ const gaugeValue = document.getElementById('gaugeValue');
428
+ const lastOcrResult = document.getElementById('lastOcrResult');
429
+
430
+ // API Key Management
431
+ let apiKey = localStorage.getItem('geminiApiKey') || '';
432
+ const apiKeyModal = document.createElement('div');
433
+ apiKeyModal.className = 'fixed inset-0 bg-industrial-900 bg-opacity-90 flex items-center justify-center z-50 hidden';
434
+ apiKeyModal.innerHTML = `
435
+ <div class="bg-industrial-800 rounded-lg p-6 max-w-md w-full">
436
+ <h3 class="text-xl font-bold mb-4">Enter Gemini API Key</h3>
437
+ <input type="password" id="apiKeyInput" placeholder="Your Gemini API Key"
438
+ class="w-full bg-industrial-700 border border-industrial-600 rounded p-3 mb-4 text-white">
439
+ <div class="flex justify-end space-x-3">
440
+ <button id="cancelApiKey" class="px-4 py-2 rounded bg-industrial-600 hover:bg-industrial-500">
441
+ Cancel
442
+ </button>
443
+ <button id="saveApiKey" class="px-4 py-2 rounded bg-industrial-500 hover:bg-industrial-400">
444
+ Save Key
445
+ </button>
446
+ </div>
447
+ </div>
448
+ `;
449
+ document.body.appendChild(apiKeyModal);
450
+
451
+ // App State
452
+ let monitoringInterval;
453
+ let countdownInterval;
454
+ let countdown = 10;
455
+ let temperatureHistory = [];
456
+ let maxTemp = null;
457
+ let minTemp = null;
458
+ let stream = null;
459
+ let hasValidApiKey = false;
460
+
461
+ // Initialize the app
462
+ async function init() {
463
+ // Set up capture button
464
+ document.getElementById('captureBtn').addEventListener('click', captureAndProcess);
465
+ updateGauge(0);
466
+
467
+ // Initialize camera when user clicks capture for the first time
468
+ document.getElementById('captureBtn').addEventListener('click', async function firstCapture() {
469
+ try {
470
+ if (!stream) {
471
+ stream = await navigator.mediaDevices.getUserMedia({
472
+ video: {
473
+ facingMode: 'environment',
474
+ width: { ideal: 1280 },
475
+ height: { ideal: 720 }
476
+ }
477
+ });
478
+ cameraFeed.srcObject = stream;
479
+ }
480
+ } catch (err) {
481
+ console.error("Error accessing camera:", err);
482
+ cameraFeed.parentElement.innerHTML = `
483
+ <div class="text-center text-industrial-300 p-4">
484
+ <i class="fas fa-video-slash text-4xl mb-2"></i>
485
+ <p>Could not access camera. Please check permissions.</p>
486
+ <button onclick="window.location.reload()" class="mt-2 bg-industrial-600 hover:bg-industrial-500 text-white px-4 py-2 rounded-lg">
487
+ Try Again
488
+ </button>
489
+ </div>
490
+ `;
491
+ }
492
+ // Remove this event listener after first run
493
+ document.getElementById('captureBtn').removeEventListener('click', firstCapture);
494
+ }, { once: true });
495
+
496
+ // API Key management
497
+ document.getElementById('settingsBtn').addEventListener('click', () => {
498
+ apiKeyModal.classList.remove('hidden');
499
+ document.getElementById('apiKeyInput').value = apiKey;
500
+ });
501
+
502
+ document.getElementById('saveApiKey').addEventListener('click', () => {
503
+ apiKey = document.getElementById('apiKeyInput').value.trim();
504
+ localStorage.setItem('geminiApiKey', apiKey);
505
+ apiKeyModal.classList.add('hidden');
506
+ hasValidApiKey = apiKey.length > 0;
507
+ });
508
+
509
+ document.getElementById('cancelApiKey').addEventListener('click', () => {
510
+ apiKeyModal.classList.add('hidden');
511
+ });
512
+
513
+ hasValidApiKey = apiKey.length > 0;
514
+ }
515
+
516
+
517
+ // Update countdown display
518
+ function updateCountdown() {
519
+ countdownEl.textContent = countdown;
520
+
521
+ if (countdown <= 0) {
522
+ countdown = 10;
523
+ }
524
+
525
+ countdownInterval = setTimeout(() => {
526
+ countdown--;
527
+ updateCountdown();
528
+ }, 1000);
529
+ }
530
+
531
+ // Capture image and process
532
+ function captureAndProcess() {
533
+ if (!stream) return;
534
+
535
+ // Capture frame
536
+ const context = captureCanvas.getContext('2d');
537
+ captureCanvas.width = cameraFeed.videoWidth;
538
+ captureCanvas.height = cameraFeed.videoHeight;
539
+ context.drawImage(cameraFeed, 0, 0, captureCanvas.width, captureCanvas.height);
540
+
541
+ // Convert to base64 for API
542
+ const imageData = captureCanvas.toDataURL('image/jpeg').split(',')[1];
543
+
544
+ // Process with Gemini
545
+ processWithGemini(imageData);
546
+ }
547
+ // Start periodic capturing
548
+ function startPeriodicCapture(intervalSeconds = 10) {
549
+ logDebug(`Starting periodic capture every ${intervalSeconds} seconds`);
550
+ return setInterval(() => {
551
+ logDebug("Auto-capturing image...");
552
+ captureAndProcess();
553
+ }, intervalSeconds * 1000);
554
+ }
555
+ // Stop periodic capturing
556
+ function stopPeriodicCapture(intervalId) {
557
+ clearInterval(intervalId);
558
+ logDebug("Stopped periodic capture");
559
+ }
560
+
561
+ // Debug logging function
562
+ function logDebug(message) {
563
+ const debugConsole = document.getElementById('debugConsole');
564
+ const timestamp = new Date().toLocaleTimeString();
565
+ const logEntry = document.createElement('div');
566
+ logEntry.innerHTML = `[${timestamp}] ${message}`;
567
+ debugConsole.appendChild(logEntry);
568
+ debugConsole.scrollTop = debugConsole.scrollHeight;
569
+
570
+ // Also log to browser console
571
+ console.log(`[ThermoScan] ${message}`);
572
+
573
+ // Keep only last 100 messages
574
+ if (debugConsole.children.length > 100) {
575
+ debugConsole.removeChild(debugConsole.children[0]);
576
+ }
577
+ }
578
+ // Process with Gemini API
579
+ async function processWithGemini(imageData) {
580
+ if (!hasValidApiKey) {
581
+ const msg = "API Key not configured";
582
+ lastOcrResult.textContent = msg;
583
+ logDebug(msg);
584
+ currentTempEl.textContent = "--°C";
585
+ gaugeValue.textContent = "--";
586
+ return;
587
+ }
588
+ lastOcrResult.textContent = "Processing image...";
589
+ logDebug("Starting image processing with Gemini API");
590
+ logDebug(`Image data size: ${Math.round(imageData.length / 1024)}KB`);
591
+
592
+ try {
593
+ const apiUrl = `https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent?key=${apiKey}`;
594
+ logDebug(`Sending request to: ${apiUrl}`);
595
+
596
+ const requestBody = {
597
+ contents: [{
598
+ parts: [{
599
+ text: "Extract the numerical temperature value from this image. Only return the number, nothing else."
600
+ }, {
601
+ inlineData: {
602
+ mimeType: "image/jpeg",
603
+ data: imageData
604
+ }
605
+ }]
606
+ }]
607
+ };
608
+
609
+ logDebug("Request payload prepared");
610
+ logDebug(`Prompt: "${requestBody.contents[0].parts[0].text}"`);
611
+
612
+ const response = await fetch(apiUrl, {
613
+ method: 'POST',
614
+ headers: {
615
+ 'Content-Type': 'application/json',
616
+ },
617
+ body: JSON.stringify({
618
+ contents: [{
619
+ parts: [{
620
+ text: "Analyze this image of an industrial machine temperature display. Extract only the numerical temperature value. Return just the number with no additional text or symbols."
621
+ }, {
622
+ inlineData: {
623
+ mimeType: "image/jpeg",
624
+ data: imageData
625
+ }
626
+ }]
627
+ }],
628
+ generationConfig: {
629
+ temperature: 0.1,
630
+ topP: 0.1,
631
+ topK: 1
632
+ }
633
+ })
634
+ });
635
+ const data = await response.json();
636
+ logDebug(`API response status: ${response.status}`);
637
+ logDebug(`Full response: ${JSON.stringify(data, null, 2)}`);
638
+
639
+ if (data.candidates && data.candidates[0].content.parts[0].text) {
640
+ const result = data.candidates[0].content.parts[0].text;
641
+ logDebug(`Raw API response text: "${result}"`);
642
+ // More robust temperature parsing
643
+ let temperature;
644
+ const tempMatch = result.match(/-?\d+(\.\d+)?/);
645
+
646
+ if (tempMatch) {
647
+ temperature = parseFloat(tempMatch[0]);
648
+ // Handle cases where OCR might return values like "35 C" or "35C"
649
+ if (isNaN(temperature)) {
650
+ const cleaned = result.replace(/[^\d.-]/g, '');
651
+ temperature = parseFloat(cleaned);
652
+ }
653
+ const msg = `Detected temperature: ${temperature}°C`;
654
+ lastOcrResult.textContent = msg;
655
+ logDebug(msg);
656
+ updateTemperature(temperature);
657
+ } else {
658
+ lastOcrResult.textContent = "No temperature detected";
659
+ currentTempEl.textContent = "--°C";
660
+ gaugeValue.textContent = "--";
661
+ }
662
+ } else {
663
+ lastOcrResult.textContent = "Failed to process image";
664
+ currentTempEl.textContent = "--°C";
665
+ gaugeValue.textContent = "--";
666
+ }
667
+ } catch (error) {
668
+ console.error("Gemini API error:", error);
669
+ const msg = `API Error: ${error.message}`;
670
+ lastOcrResult.textContent = msg;
671
+ logDebug(msg);
672
+ currentTempEl.textContent = "--°C";
673
+ gaugeValue.textContent = "--";
674
+ }
675
+ }
676
+
677
+ // Update temperature display and history
678
+ let lastWebhookTimestamp = 0; // Stores last webhook call time
679
+ function updateTemperature(temp) {
680
+ currentTempEl.textContent = `${temp}°C`;
681
+ updateGauge(temp);
682
+ updateStatus(temp);
683
+
684
+ temperatureHistory.push({
685
+ temp: temp,
686
+ timestamp: new Date().toLocaleTimeString(),
687
+ status: getStatus(temp)
688
+ });
689
+
690
+ if (temperatureHistory.length > 20) {
691
+ temperatureHistory.shift();
692
+ }
693
+
694
+ if (maxTemp === null || temp > maxTemp) {
695
+ maxTemp = temp;
696
+ maxTempEl.textContent = `${maxTemp}°C`;
697
+ }
698
+
699
+ if (minTemp === null || temp < minTemp) {
700
+ minTemp = temp;
701
+ minTempEl.textContent = `${minTemp}°C`;
702
+ }
703
+
704
+ updateHistoryTable();
705
+ updateLiveChart(temp, new Date().toLocaleTimeString());
706
+
707
+ // Webhook Integration with improved reliability
708
+ const now = Date.now();
709
+ const criticalThreshold = 50; // Could be loaded from settings
710
+ const webhookCooldown = 30000; // 30 seconds
711
+
712
+ if (temp > criticalThreshold && now - lastWebhookTimestamp > webhookCooldown) {
713
+ lastWebhookTimestamp = now;
714
+ logDebug(`Triggering webhook for critical temperature: ${temp}°C`);
715
+
716
+ const sendWebhook = (attempt = 1) => {
717
+ const webhookUrl = 'https://n8n-1r4e.onrender.com/webhook-test/fb1185d8-51fc-4938-bd86-0222ceb2d3af';
718
+ const payload = {
719
+ temperature: temp,
720
+ timestamp: new Date().toISOString(),
721
+ status: getStatus(temp),
722
+ message: `⚠️ Critical temperature (${temp}°C) detected. Immediate action required.`,
723
+ deviceInfo: navigator.userAgent,
724
+ attempt: attempt
725
+ };
726
+
727
+ logDebug(`Sending webhook attempt ${attempt} to: ${webhookUrl}`);
728
+ logDebug(`Payload: ${JSON.stringify(payload)}`);
729
+
730
+ fetch(webhookUrl, {
731
+ method: 'POST',
732
+ headers: {
733
+ 'Content-Type': 'application/json',
734
+ 'X-ThermoScan-Signature': 'your-secret-key-here' // Add if your webhook requires auth
735
+ },
736
+ body: JSON.stringify(payload)
737
+ })
738
+ .then(async res => {
739
+ if (!res.ok) {
740
+ const errorText = await res.text();
741
+ throw new Error(`HTTP ${res.status}: ${errorText}`);
742
+ }
743
+ return res.json();
744
+ })
745
+ .then(data => {
746
+ logDebug(`Webhook successful. Response: ${JSON.stringify(data)}`);
747
+ showAlert('Critical temperature alert sent', 'success');
748
+ })
749
+ .catch(err => {
750
+ logDebug(`Webhook attempt ${attempt} failed: ${err.message}`);
751
+ if (attempt < 3) {
752
+ const retryDelay = 5000 * attempt;
753
+ logDebug(`Retrying in ${retryDelay/1000} seconds...`);
754
+ setTimeout(() => sendWebhook(attempt + 1), retryDelay);
755
+ } else {
756
+ logDebug('Max retry attempts reached');
757
+ showAlert('Failed to send critical alert after 3 attempts', 'error');
758
+ }
759
+ });
760
+ };
761
+
762
+ sendWebhook();
763
+ } else if (temp > criticalThreshold) {
764
+ const timeLeft = Math.ceil((webhookCooldown - (now - lastWebhookTimestamp)) / 1000);
765
+ logDebug(`Webhook cooldown active. ${timeLeft}s remaining until next alert can be sent`);
766
+ }
767
+ }
768
+
769
+
770
+ // Update gauge display
771
+ function updateGauge(temp) {
772
+ // Normalize temperature to gauge range (0-50°C)
773
+ const percentage = Math.min(Math.max((temp / 50) * 100, 0), 100);
774
+ const dashValue = (565 * percentage) / 100;
775
+
776
+ gaugeProgress.style.strokeDasharray = `${dashValue} 565`;
777
+ gaugeValue.textContent = `${temp}°C`;
778
+
779
+ // Update gauge color based on temperature
780
+ if (temp > 35) {
781
+ gaugeProgress.style.stroke = '#ef4444';
782
+ } else if (temp > 30) {
783
+ gaugeProgress.style.stroke = '#f59e0b';
784
+ } else {
785
+ gaugeProgress.style.stroke = '#38bdf8';
786
+ }
787
+ }
788
+
789
+ // Update temperature status
790
+ function updateStatus(temp) {
791
+ const statusIndicator = tempStatusEl.querySelector('.status-indicator');
792
+ statusIndicator.className = 'status-indicator';
793
+
794
+ if (temp > 35) {
795
+ tempStatusEl.innerHTML = '<span class="status-indicator status-danger"></span> CRITICAL TEMPERATURE';
796
+ tempStatusEl.className = 'mt-2 px-3 py-1 rounded-full bg-danger-900 text-danger-200 text-sm';
797
+ currentTempEl.classList.add('text-danger-500');
798
+ currentTempEl.classList.remove('text-industrial-200', 'text-warning-500');
799
+ } else if (temp > 30) {
800
+ tempStatusEl.innerHTML = '<span class="status-indicator status-warning"></span> HIGH TEMPERATURE';
801
+ tempStatusEl.className = 'mt-2 px-3 py-1 rounded-full bg-warning-900 text-warning-200 text-sm';
802
+ currentTempEl.classList.add('text-warning-500');
803
+ currentTempEl.classList.remove('text-industrial-200', 'text-danger-500');
804
+ } else {
805
+ tempStatusEl.innerHTML = '<span class="status-indicator status-normal"></span> NORMAL';
806
+ tempStatusEl.className = 'mt-2 px-3 py-1 rounded-full bg-industrial-800 text-industrial-300 text-sm';
807
+ currentTempEl.classList.add('text-industrial-200');
808
+ currentTempEl.classList.remove('text-warning-500', 'text-danger-500');
809
+ }
810
+ }
811
+
812
+ // Get status for history
813
+ function getStatus(temp) {
814
+ if (temp > 35) return 'critical';
815
+ if (temp > 30) return 'warning';
816
+ return 'normal';
817
+ }
818
+
819
+ // Update history table
820
+ function updateHistoryTable() {
821
+ if (temperatureHistory.length === 0) {
822
+ historyBody.innerHTML = `
823
+ <tr>
824
+ <td colspan="4" class="px-4 py-8 text-center text-industrial-500">
825
+ No temperature data recorded yet
826
+ </td>
827
+ </tr>
828
+ `;
829
+ return;
830
+ }
831
+
832
+ let historyHTML = '';
833
+ temperatureHistory.slice().reverse().forEach(reading => {
834
+ let statusClass = '';
835
+ let statusText = '';
836
+
837
+ switch(reading.status) {
838
+ case 'critical':
839
+ statusClass = 'text-danger-500';
840
+ statusText = 'Critical';
841
+ break;
842
+ case 'warning':
843
+ statusClass = 'text-warning-500';
844
+ statusText = 'Warning';
845
+ break;
846
+ default:
847
+ statusClass = 'text-success-500';
848
+ statusText = 'Normal';
849
+ }
850
+
851
+ historyHTML += `
852
+ <tr class="history-item">
853
+ <td class="px-4 py-3 whitespace-nowrap text-sm text-industrial-300">${reading.timestamp}</td>
854
+ <td class="px-4 py-3 whitespace-nowrap">
855
+ <div class="text-lg font-bold ${reading.status === 'critical' ? 'text-danger-500' : reading.status === 'warning' ? 'text-warning-500' : 'text-industrial-200'}">
856
+ ${reading.temp}°C
857
+ </div>
858
+ </td>
859
+ <td class="px-4 py-3 whitespace-nowrap">
860
+ <span class="${statusClass} font-medium">${statusText}</span>
861
+ </td>
862
+ <td class="px-4 py-3 whitespace-nowrap text-sm">
863
+ <button class="text-industrial-400 hover:text-industrial-300 mr-2" onclick="showTemperatureChart()">
864
+ <i class="fas fa-chart-line"></i>
865
+ </button>
866
+ <button class="text-industrial-400 hover:text-industrial-300">
867
+ <i class="fas fa-info-circle"></i>
868
+ </button>
869
+ </td>
870
+ </tr>
871
+ `;
872
+ });
873
+
874
+ historyBody.innerHTML = historyHTML;
875
+ }
876
+
877
+ // Export data as CSV
878
+ function exportData() {
879
+ if (temperatureHistory.length === 0) {
880
+ logDebug("No data to export");
881
+ return;
882
+ }
883
+ let csvContent = "Timestamp,Temperature,Status\n";
884
+ temperatureHistory.forEach(reading => {
885
+ csvContent += `${reading.timestamp},${reading.temp},${reading.status}\n`;
886
+ });
887
+ const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
888
+ const url = URL.createObjectURL(blob);
889
+ const link = document.createElement('a');
890
+ link.setAttribute('href', url);
891
+ link.setAttribute('download', `thermoscan_data_${new Date().toISOString().slice(0,10)}.csv`);
892
+ link.style.visibility = 'hidden';
893
+ document.body.appendChild(link);
894
+ link.click();
895
+ document.body.removeChild(link);
896
+ logDebug("Data exported as CSV");
897
+ }
898
+ // Chart functions
899
+ let temperatureChart = null;
900
+ function showTemperatureChart() {
901
+ const modal = document.getElementById('chartModal');
902
+ modal.classList.remove('hidden');
903
+
904
+ const ctx = document.getElementById('temperatureChart').getContext('2d');
905
+
906
+ // Destroy previous chart if exists
907
+ if (temperatureChart) {
908
+ temperatureChart.destroy();
909
+ }
910
+
911
+ // Prepare chart data
912
+ const labels = temperatureHistory.map(reading => reading.timestamp);
913
+ const data = temperatureHistory.map(reading => reading.temp);
914
+ const statusColors = temperatureHistory.map(reading => {
915
+ switch(reading.status) {
916
+ case 'critical': return '#ef4444';
917
+ case 'warning': return '#f59e0b';
918
+ default: return '#10b981';
919
+ }
920
+ });
921
+
922
+ temperatureChart = new Chart(ctx, {
923
+ type: 'line',
924
+ data: {
925
+ labels: labels,
926
+ datasets: [{
927
+ label: 'Temperature (°C)',
928
+ data: data,
929
+ borderColor: '#38bdf8',
930
+ backgroundColor: 'rgba(56, 189, 248, 0.1)',
931
+ borderWidth: 2,
932
+ pointBackgroundColor: statusColors,
933
+ pointRadius: 5,
934
+ pointHoverRadius: 7,
935
+ tension: 0.1,
936
+ fill: true
937
+ }]
938
+ },
939
+ options: {
940
+ responsive: true,
941
+ maintainAspectRatio: false,
942
+ scales: {
943
+ y: {
944
+ beginAtZero: false,
945
+ grid: {
946
+ color: 'rgba(30, 41, 59, 0.5)'
947
+ },
948
+ ticks: {
949
+ color: '#94a3b8'
950
+ }
951
+ },
952
+ x: {
953
+ grid: {
954
+ color: 'rgba(30, 41, 59, 0.5)'
955
+ },
956
+ ticks: {
957
+ color: '#94a3b8'
958
+ }
959
+ }
960
+ },
961
+ plugins: {
962
+ legend: {
963
+ labels: {
964
+ color: '#e2e8f0'
965
+ }
966
+ },
967
+ tooltip: {
968
+ backgroundColor: '#1e293b',
969
+ titleColor: '#e2e8f0',
970
+ bodyColor: '#e2e8f0',
971
+ borderColor: '#334155',
972
+ borderWidth: 1
973
+ }
974
+ }
975
+ }
976
+ });
977
+ }
978
+ function exportChartAsImage() {
979
+ if (!temperatureChart) return;
980
+
981
+ const link = document.createElement('a');
982
+ link.download = `temperature_chart_${new Date().toISOString().slice(0,10)}.png`;
983
+ link.href = temperatureChart.toBase64Image();
984
+ link.click();
985
+ }
986
+ // Chart variables
987
+ let liveChart = null;
988
+ let chartData = {
989
+ labels: [],
990
+ datasets: [{
991
+ label: 'Temperature (°C)',
992
+ data: [],
993
+ borderColor: '#38bdf8',
994
+ backgroundColor: 'rgba(56, 189, 248, 0.1)',
995
+ borderWidth: 2,
996
+ pointRadius: 3,
997
+ tension: 0.1,
998
+ fill: true
999
+ }]
1000
+ };
1001
+ // Initialize live chart
1002
+ function initLiveChart() {
1003
+ const ctx = document.getElementById('liveChart').getContext('2d');
1004
+ liveChart = new Chart(ctx, {
1005
+ type: 'line',
1006
+ data: chartData,
1007
+ options: {
1008
+ responsive: true,
1009
+ maintainAspectRatio: false,
1010
+ animation: {
1011
+ duration: 1000,
1012
+ easing: 'linear'
1013
+ },
1014
+ scales: {
1015
+ y: {
1016
+ beginAtZero: false,
1017
+ grid: {
1018
+ color: 'rgba(30, 41, 59, 0.5)'
1019
+ },
1020
+ ticks: {
1021
+ color: '#94a3b8'
1022
+ }
1023
+ },
1024
+ x: {
1025
+ grid: {
1026
+ color: 'rgba(30, 41, 59, 0.5)'
1027
+ },
1028
+ ticks: {
1029
+ color: '#94a3b8',
1030
+ maxRotation: 45,
1031
+ minRotation: 45
1032
+ }
1033
+ }
1034
+ },
1035
+ plugins: {
1036
+ legend: {
1037
+ labels: {
1038
+ color: '#e2e8f0'
1039
+ }
1040
+ },
1041
+ tooltip: {
1042
+ backgroundColor: '#1e293b',
1043
+ titleColor: '#e2e8f0',
1044
+ bodyColor: '#e2e8f0',
1045
+ borderColor: '#334155',
1046
+ borderWidth: 1
1047
+ }
1048
+ }
1049
+ }
1050
+ });
1051
+ }
1052
+ // Update live chart with new data
1053
+ function updateLiveChart(temp, timestamp) {
1054
+ // Add new data point
1055
+ chartData.labels.push(timestamp);
1056
+ chartData.datasets[0].data.push(temp);
1057
+
1058
+ // Keep only last 20 points
1059
+ if (chartData.labels.length > 20) {
1060
+ chartData.labels.shift();
1061
+ chartData.datasets[0].data.shift();
1062
+ }
1063
+
1064
+ // Update chart
1065
+ liveChart.update();
1066
+ }
1067
+ // Initialize the app when DOM is loaded
1068
+ document.addEventListener('DOMContentLoaded', () => {
1069
+ initLiveChart();
1070
+ init();
1071
+
1072
+ // Start/stop periodic capture
1073
+ let captureInterval;
1074
+ const captureBtn = document.getElementById('captureBtn');
1075
+ const stopRecordingBtn = document.getElementById('stopRecordingBtn');
1076
+
1077
+ captureBtn.addEventListener('click', () => {
1078
+ if (captureInterval) {
1079
+ stopPeriodicCapture(captureInterval);
1080
+ captureInterval = null;
1081
+ captureBtn.innerHTML = '<i class="fas fa-camera mr-1"></i> Capture & Analyze';
1082
+ stopRecordingBtn.classList.add('hidden');
1083
+ } else {
1084
+ captureInterval = startPeriodicCapture(10);
1085
+ captureBtn.innerHTML = '<i class="fas fa-pause mr-1"></i> Pause Recording';
1086
+ stopRecordingBtn.classList.remove('hidden');
1087
+ }
1088
+ });
1089
+ // Stop recording button
1090
+ stopRecordingBtn.addEventListener('click', () => {
1091
+ if (captureInterval) {
1092
+ stopPeriodicCapture(captureInterval);
1093
+ captureInterval = null;
1094
+ captureBtn.innerHTML = '<i class="fas fa-camera mr-1"></i> Capture & Analyze';
1095
+ stopRecordingBtn.classList.add('hidden');
1096
+ }
1097
+ });
1098
+ // Export data button
1099
+ document.getElementById('exportDataBtn').addEventListener('click', exportData);
1100
+
1101
+ // Chart modal events
1102
+ document.getElementById('closeChartModal').addEventListener('click', () => {
1103
+ document.getElementById('chartModal').classList.add('hidden');
1104
+ });
1105
+
1106
+ document.getElementById('exportChartBtn').addEventListener('click', exportChartAsImage);
1107
+ });
1108
+ </script>
1109
+
1110
+ <!-- Chart Modal -->
1111
+ <div id="chartModal" class="modal-overlay hidden">
1112
+ <div class="modal-content">
1113
+ <div class="flex justify-between items-center mb-4">
1114
+ <h3 class="text-xl font-bold">Temperature History Chart</h3>
1115
+ <button id="closeChartModal" class="text-industrial-400 hover:text-industrial-300">
1116
+ <i class="fas fa-times"></i>
1117
+ </button>
1118
+ </div>
1119
+ <div class="bg-industrial-800 p-4 rounded-lg">
1120
+ <canvas id="temperatureChart" height="300"></canvas>
1121
+ </div>
1122
+ <div class="modal-actions">
1123
+ <button id="exportChartBtn" class="bg-industrial-600 hover:bg-industrial-500 text-white px-4 py-2 rounded-lg">
1124
+ <i class="fas fa-download mr-2"></i> Export as Image
1125
+ </button>
1126
+ </div>
1127
+ </div>
1128
+ </div>
1129
+ <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>
1130
+ </html>