Prathamesh1420 commited on
Commit
ee8b196
·
verified ·
1 Parent(s): c6fc75a

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +346 -420
index.html CHANGED
@@ -171,6 +171,23 @@
171
  gap: 0.75rem;
172
  margin-top: 1rem;
173
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
174
  </style>
175
  </head>
176
  <body class="min-h-screen p-4 md:p-8">
@@ -193,7 +210,7 @@
193
  <span class="status-indicator status-normal"></span>
194
  <span class="text-industrial-300">System Status: <span class="text-success-500 font-medium">Operational</span></span>
195
  </div>
196
- <div class="text-xs text-industrial-400 mt-1">Connected to Gemini 1.5 Flash API</div>
197
  </div>
198
  <button id="settingsBtn" class="bg-industrial-600 hover:bg-industrial-500 text-white px-4 py-2 rounded-lg transition flex items-center">
199
  <i class="fas fa-cog mr-2"></i> Settings
@@ -208,6 +225,10 @@
208
  <div class="p-4 border-b border-industrial-700 flex justify-between items-center">
209
  <h2 class="text-xl font-bold text-white">Machine Camera Feed</h2>
210
  <div class="flex space-x-2">
 
 
 
 
211
  <button id="captureBtn" class="bg-industrial-500 hover:bg-industrial-400 text-white px-3 py-1 rounded text-sm flex items-center">
212
  <i class="fas fa-camera mr-1"></i> Capture & Analyze
213
  </button>
@@ -217,10 +238,16 @@
217
  <div class="camera-feed h-96 flex items-center justify-center relative">
218
  <video id="cameraFeed" class="w-full h-full object-contain" autoplay playsinline></video>
219
  <canvas id="captureCanvas" class="hidden"></canvas>
 
 
 
 
 
 
220
  </div>
221
 
222
  <div class="mt-4 text-center text-industrial-300 text-sm">
223
- <i class="fas fa-microchip mr-1"></i> Using Gemini 2.5 Flash OCR
224
  </div>
225
  </div>
226
  </div>
@@ -298,13 +325,14 @@
298
  <th class="px-4 py-3 text-left text-xs font-medium text-industrial-400 uppercase tracking-wider">Timestamp</th>
299
  <th class="px-4 py-3 text-left text-xs font-medium text-industrial-400 uppercase tracking-wider">Temperature</th>
300
  <th class="px-4 py-3 text-left text-xs font-medium text-industrial-400 uppercase tracking-wider">Status</th>
 
301
  <th class="px-4 py-3 text-left text-xs font-medium text-industrial-400 uppercase tracking-wider">Actions</th>
302
  </tr>
303
  </thead>
304
  <tbody id="historyBody" class="divide-y divide-industrial-800">
305
  <!-- History items will be added here dynamically -->
306
  <tr>
307
- <td colspan="4" class="px-4 py-8 text-center text-industrial-500">
308
  No temperature data recorded yet
309
  </td>
310
  </tr>
@@ -328,22 +356,22 @@
328
  <div class="dashboard-grid mb-8">
329
  <div class="industrial-card">
330
  <div class="p-4 border-b border-industrial-700">
331
- <h2 class="text-xl font-bold text-white">OCR Status</h2>
332
  </div>
333
  <div class="p-4">
334
  <div class="flex items-center mb-4">
335
  <div class="mr-4">
336
  <div class="bg-industrial-700 rounded-full p-3">
337
- <i class="fas fa-eye text-industrial-300 text-2xl"></i>
338
  </div>
339
  </div>
340
  <div>
341
- <div class="text-industrial-300">Gemini OCR Engine</div>
342
- <div class="text-white font-bold text-lg">Operational</div>
343
  </div>
344
  </div>
345
  <div class="bg-industrial-800 rounded-lg p-3">
346
- <div class="text-industrial-400 text-sm mb-1">Last OCR Result</div>
347
  <div id="lastOcrResult" class="text-industrial-200 font-mono">Waiting for first capture...</div>
348
  </div>
349
  </div>
@@ -384,13 +412,13 @@
384
  </div>
385
  </div>
386
  <div>
387
- <div class="text-industrial-300">Gemini API</div>
388
- <div class="text-white font-bold text-lg">Connected</div>
389
  </div>
390
  </div>
391
  <div class="bg-industrial-800 rounded-lg p-3">
392
- <div class="text-industrial-400 text-sm mb-1">Rate Limit Status</div>
393
- <div class="text-industrial-200">10 requests/min available (1 used)</div>
394
  </div>
395
  </div>
396
  </div>
@@ -408,19 +436,66 @@
408
 
409
  <!-- Footer -->
410
  <footer class="text-center text-industrial-500 text-sm pt-6 border-t border-industrial-800">
411
- <p>ThermoScanAI - Industrial Machine Temperature Monitoring System | Using Gemini 1.5 Flash OCR</p>
412
  <p class="mt-2">© 2023 Industrial AI Solutions. All rights reserved.</p>
413
  </footer>
414
  </div>
415
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
416
  <script>
417
  // DOM Elements
418
  const cameraFeed = document.getElementById('cameraFeed');
419
- const cameraPlaceholder = document.getElementById('cameraPlaceholder');
420
  const captureCanvas = document.getElementById('captureCanvas');
421
- const startBtn = document.getElementById('startBtn');
422
- const stopBtn = document.getElementById('stopBtn');
423
- const countdownEl = document.getElementById('countdown');
424
  const currentTempEl = document.getElementById('currentTemp');
425
  const maxTempEl = document.getElementById('maxTemp');
426
  const minTempEl = document.getElementById('minTemp');
@@ -429,42 +504,62 @@
429
  const gaugeProgress = document.getElementById('gaugeProgress');
430
  const gaugeValue = document.getElementById('gaugeValue');
431
  const lastOcrResult = document.getElementById('lastOcrResult');
 
 
 
 
 
 
432
 
433
  // API Key Management
434
- let apiKey = localStorage.getItem('geminiApiKey') || '';
435
- const apiKeyModal = document.createElement('div');
436
- apiKeyModal.className = 'fixed inset-0 bg-industrial-900 bg-opacity-90 flex items-center justify-center z-50 hidden';
437
- apiKeyModal.innerHTML = `
438
- <div class="bg-industrial-800 rounded-lg p-6 max-w-md w-full">
439
- <h3 class="text-xl font-bold mb-4">Enter Gemini API Key</h3>
440
- <input type="password" id="apiKeyInput" placeholder="Your Gemini API Key"
441
- class="w-full bg-industrial-700 border border-industrial-600 rounded p-3 mb-4 text-white">
442
- <div class="flex justify-end space-x-3">
443
- <button id="cancelApiKey" class="px-4 py-2 rounded bg-industrial-600 hover:bg-industrial-500">
444
- Cancel
445
- </button>
446
- <button id="saveApiKey" class="px-4 py-2 rounded bg-industrial-500 hover:bg-industrial-400">
447
- Save Key
448
- </button>
449
- </div>
450
- </div>
451
- `;
452
- document.body.appendChild(apiKeyModal);
453
 
454
  // App State
455
  let monitoringInterval;
456
- let countdownInterval;
457
- let countdown = 10;
458
  let temperatureHistory = [];
459
  let maxTemp = null;
460
  let minTemp = null;
461
  let stream = null;
462
- let hasValidApiKey = false;
463
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
464
  // Initialize the app
465
  async function init() {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
466
  // Set up capture button
467
  document.getElementById('captureBtn').addEventListener('click', captureAndProcess);
 
 
468
  updateGauge(0);
469
 
470
  // Initialize camera when user clicks capture for the first time
@@ -496,45 +591,97 @@
496
  document.getElementById('captureBtn').removeEventListener('click', firstCapture);
497
  }, { once: true });
498
 
499
- // API Key management
500
  document.getElementById('settingsBtn').addEventListener('click', () => {
501
- apiKeyModal.classList.remove('hidden');
502
- document.getElementById('apiKeyInput').value = apiKey;
503
  });
504
 
505
- document.getElementById('saveApiKey').addEventListener('click', () => {
506
- apiKey = document.getElementById('apiKeyInput').value.trim();
507
- localStorage.setItem('geminiApiKey', apiKey);
508
- apiKeyModal.classList.add('hidden');
509
- hasValidApiKey = apiKey.length > 0;
510
  });
511
 
512
- document.getElementById('cancelApiKey').addEventListener('click', () => {
513
- apiKeyModal.classList.add('hidden');
514
  });
515
 
516
- hasValidApiKey = apiKey.length > 0;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
517
  }
518
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
519
 
520
- // Update countdown display
521
- function updateCountdown() {
522
- countdownEl.textContent = countdown;
 
523
 
524
- if (countdown <= 0) {
525
- countdown = 10;
 
 
 
 
526
  }
527
-
528
- countdownInterval = setTimeout(() => {
529
- countdown--;
530
- updateCountdown();
531
- }, 1000);
532
  }
533
 
534
  // Capture image and process
535
  function captureAndProcess() {
536
  if (!stream) return;
537
 
 
 
 
 
538
  // Capture frame
539
  const context = captureCanvas.getContext('2d');
540
  captureCanvas.width = cameraFeed.videoWidth;
@@ -544,8 +691,12 @@
544
  // Convert to base64 for API
545
  const imageData = captureCanvas.toDataURL('image/jpeg').split(',')[1];
546
 
547
- // Process with Gemini
548
- processWithGemini(imageData);
 
 
 
 
549
  }
550
 
551
  // Start periodic capturing
@@ -572,9 +723,6 @@
572
  debugConsole.appendChild(logEntry);
573
  debugConsole.scrollTop = debugConsole.scrollHeight;
574
 
575
- // Also log to browser console
576
- console.log(`[ThermoScan] ${message}`);
577
-
578
  // Keep only last 100 messages
579
  if (debugConsole.children.length > 100) {
580
  debugConsole.removeChild(debugConsole.children[0]);
@@ -583,38 +731,21 @@
583
 
584
  // Process with Gemini API
585
  async function processWithGemini(imageData) {
586
- if (!hasValidApiKey) {
587
- const msg = "API Key not configured";
588
  lastOcrResult.textContent = msg;
589
  logDebug(msg);
590
  currentTempEl.textContent = "--°C";
591
  gaugeValue.textContent = "--";
 
592
  return;
593
  }
594
 
595
- lastOcrResult.textContent = "Processing image...";
596
  logDebug("Starting image processing with Gemini API");
597
- logDebug(`Image data size: ${Math.round(imageData.length / 1024)}KB`);
598
 
599
  try {
600
- const apiUrl = `https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent?key=${apiKey}`;
601
- logDebug(`Sending request to: ${apiUrl}`);
602
-
603
- const requestBody = {
604
- contents: [{
605
- parts: [{
606
- text: "Extract the numerical temperature value from this image. Only return the number, nothing else."
607
- }, {
608
- inlineData: {
609
- mimeType: "image/jpeg",
610
- data: imageData
611
- }
612
- }]
613
- }]
614
- };
615
-
616
- logDebug("Request payload prepared");
617
- logDebug(`Prompt: "${requestBody.contents[0].parts[0].text}"`);
618
 
619
  const response = await fetch(apiUrl, {
620
  method: 'POST',
@@ -641,27 +772,19 @@
641
  });
642
 
643
  const data = await response.json();
644
- logDebug(`API response status: ${response.status}`);
645
- logDebug(`Full response: ${JSON.stringify(data, null, 2)}`);
646
 
647
  if (data.candidates && data.candidates[0].content.parts[0].text) {
648
  const result = data.candidates[0].content.parts[0].text;
649
- logDebug(`Raw API response text: "${result}"`);
650
- // More robust temperature parsing
651
- let temperature;
652
- const tempMatch = result.match(/-?\d+(\.\d+)?/);
653
 
 
 
654
  if (tempMatch) {
655
- temperature = parseFloat(tempMatch[0]);
656
- // Handle cases where OCR might return values like "35 C" or "35C"
657
- if (isNaN(temperature)) {
658
- const cleaned = result.replace(/[^\d.-]/g, '');
659
- temperature = parseFloat(cleaned);
660
- }
661
- const msg = `Detected temperature: ${temperature}°C`;
662
  lastOcrResult.textContent = msg;
663
  logDebug(msg);
664
- updateTemperature(temperature);
665
  } else {
666
  lastOcrResult.textContent = "No temperature detected";
667
  currentTempEl.textContent = "--°C";
@@ -674,26 +797,116 @@
674
  }
675
  } catch (error) {
676
  console.error("Gemini API error:", error);
677
- const msg = `API Error: ${error.message}`;
678
  lastOcrResult.textContent = msg;
679
  logDebug(msg);
680
  currentTempEl.textContent = "--°C";
681
  gaugeValue.textContent = "--";
 
 
682
  }
683
  }
684
 
685
- // Update temperature display and history
686
- let lastWebhookTimestamp = 0; // Stores last webhook call time
 
 
 
 
 
 
 
 
 
687
 
688
- function updateTemperature(temp) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
689
  currentTempEl.textContent = `${temp}°C`;
690
  updateGauge(temp);
691
  updateStatus(temp);
692
 
 
 
 
693
  temperatureHistory.push({
694
  temp: temp,
695
- timestamp: new Date().toLocaleTimeString(),
696
- status: getStatus(temp)
 
697
  });
698
 
699
  if (temperatureHistory.length > 20) {
@@ -711,94 +924,20 @@
711
  }
712
 
713
  updateHistoryTable();
714
- updateLiveChart(temp, new Date().toLocaleTimeString());
715
-
716
- // Webhook Integration with improved reliability
717
- const now = Date.now();
718
- const criticalThreshold = 50;
719
- const webhookCooldown = 30000;
720
 
721
- if (temp > criticalThreshold && now - lastWebhookTimestamp > webhookCooldown) {
722
- lastWebhookTimestamp = now;
723
- logDebug(`Triggering webhook for critical temperature: ${temp}°C`);
724
-
725
- // Generate attachments
726
- const csvData = generateCSV();
727
- const chartImage = generateChartImage();
728
-
729
- const sendWebhook = (attempt = 1) => {
730
- const webhookUrl = 'https://n8n-1r4e.onrender.com/webhook-test/2af1157d-c7d7-4e57-86c8-577c5a005f40';
731
-
732
- try {
733
- const formData = new FormData();
734
-
735
- // Add JSON payload
736
- formData.append('payload', JSON.stringify({
737
- temperature: temp,
738
- timestamp: new Date().toISOString(),
739
- status: getStatus(temp),
740
- message: `⚠️ Critical temperature (${temp}°C) detected`,
741
- deviceInfo: navigator.userAgent,
742
- attempt: attempt
743
- }));
744
-
745
- // Add CSV file if available
746
- if (csvData) {
747
- const csvBlob = new Blob([csvData], { type: 'text/csv' });
748
- formData.append('temperature_data', csvBlob, 'temperature_data.csv');
749
- }
750
-
751
- // Add chart image if available
752
- if (chartImage) {
753
- const chartBlob = dataURLtoBlob(chartImage);
754
- formData.append('temperature_chart', chartBlob, 'temperature_chart.png');
755
- }
756
-
757
- logDebug(`Sending webhook attempt ${attempt}...`);
758
-
759
- fetch(webhookUrl, {
760
- method: 'POST',
761
- body: formData
762
- })
763
- .then(async res => {
764
- if (!res.ok) {
765
- const errorText = await res.text();
766
- throw new Error(`HTTP ${res.status}: ${errorText}`);
767
- }
768
- return res.json();
769
- })
770
- .then(data => {
771
- logDebug(`Webhook successful: ${JSON.stringify(data)}`);
772
- showAlert('Critical alert sent successfully', 'success');
773
- })
774
- .catch(err => {
775
- logDebug(`Webhook attempt ${attempt} failed: ${err.message}`);
776
- if (attempt < 3) {
777
- const retryDelay = 5000 * attempt;
778
- logDebug(`Retrying in ${retryDelay/1000} seconds...`);
779
- setTimeout(() => sendWebhook(attempt + 1), retryDelay);
780
- } else {
781
- showAlert('Failed to send critical alert', 'error');
782
- }
783
- });
784
- } catch (error) {
785
- logDebug(`Webhook preparation error: ${error.message}`);
786
- if (attempt < 3) {
787
- setTimeout(() => sendWebhook(attempt + 1), 5000 * attempt);
788
- }
789
- }
790
- };
791
-
792
- sendWebhook();
793
- }
794
- else if (temp > criticalThreshold) {
795
- const timeLeft = Math.ceil((webhookCooldown - (now - lastWebhookTimestamp)) / 1000);
796
- logDebug(`Webhook cooldown active. ${timeLeft}s remaining until next alert can be sent`);
797
  }
798
-
799
  }
800
-
801
-
802
 
803
  // Update gauge display
804
  function updateGauge(temp) {
@@ -810,9 +949,9 @@
810
  gaugeValue.textContent = `${temp}°C`;
811
 
812
  // Update gauge color based on temperature
813
- if (temp > 35) {
814
  gaugeProgress.style.stroke = '#ef4444';
815
- } else if (temp > 30) {
816
  gaugeProgress.style.stroke = '#f59e0b';
817
  } else {
818
  gaugeProgress.style.stroke = '#38bdf8';
@@ -824,12 +963,12 @@
824
  const statusIndicator = tempStatusEl.querySelector('.status-indicator');
825
  statusIndicator.className = 'status-indicator';
826
 
827
- if (temp > 35) {
828
  tempStatusEl.innerHTML = '<span class="status-indicator status-danger"></span> CRITICAL TEMPERATURE';
829
  tempStatusEl.className = 'mt-2 px-3 py-1 rounded-full bg-danger-900 text-danger-200 text-sm';
830
  currentTempEl.classList.add('text-danger-500');
831
  currentTempEl.classList.remove('text-industrial-200', 'text-warning-500');
832
- } else if (temp > 30) {
833
  tempStatusEl.innerHTML = '<span class="status-indicator status-warning"></span> HIGH TEMPERATURE';
834
  tempStatusEl.className = 'mt-2 px-3 py-1 rounded-full bg-warning-900 text-warning-200 text-sm';
835
  currentTempEl.classList.add('text-warning-500');
@@ -841,59 +980,11 @@
841
  currentTempEl.classList.remove('text-warning-500', 'text-danger-500');
842
  }
843
  }
844
- // Generate CSV data from temperature history
845
- function generateCSV() {
846
- if (temperatureHistory.length === 0) return null;
847
-
848
- let csvContent = "Timestamp,Temperature (°C),Status\n";
849
- temperatureHistory.forEach(reading => {
850
- csvContent += `${reading.timestamp},${reading.temp},${reading.status}\n`;
851
- });
852
-
853
- return csvContent;
854
- }
855
-
856
- // Generate chart image as base64
857
- function generateChartImage() {
858
- if (!liveChart) return null;
859
- return liveChart.toBase64Image();
860
- }
861
 
862
- // Convert data URL to Blob for file upload
863
- function dataURLtoBlob(dataURL) {
864
- const arr = dataURL.split(',');
865
- const mime = arr[0].match(/:(.*?);/)[1];
866
- const bstr = atob(arr[1]);
867
- let n = bstr.length;
868
- const u8arr = new Uint8Array(n);
869
-
870
- while (n--) {
871
- u8arr[n] = bstr.charCodeAt(n);
872
- }
873
-
874
- return new Blob([u8arr], { type: mime });
875
- }
876
-
877
- // Show alert notification
878
- function showAlert(message, type = 'info') {
879
- const alertEl = document.createElement('div');
880
- alertEl.className = `fixed top-4 right-4 p-4 rounded-lg z-50 ${
881
- type === 'error' ? 'bg-danger-600' :
882
- type === 'success' ? 'bg-success-600' : 'bg-industrial-600'
883
- } text-white shadow-lg`;
884
- alertEl.textContent = message;
885
-
886
- document.body.appendChild(alertEl);
887
-
888
- setTimeout(() => {
889
- alertEl.classList.add('opacity-0', 'transition-opacity', 'duration-300');
890
- setTimeout(() => alertEl.remove(), 300);
891
- }, 5000);
892
- }
893
  // Get status for history
894
  function getStatus(temp) {
895
- if (temp > 35) return 'critical';
896
- if (temp > 30) return 'warning';
897
  return 'normal';
898
  }
899
 
@@ -902,7 +993,7 @@
902
  if (temperatureHistory.length === 0) {
903
  historyBody.innerHTML = `
904
  <tr>
905
- <td colspan="4" class="px-4 py-8 text-center text-industrial-500">
906
  No temperature data recorded yet
907
  </td>
908
  </tr>
@@ -940,6 +1031,9 @@
940
  <td class="px-4 py-3 whitespace-nowrap">
941
  <span class="${statusClass} font-medium">${statusText}</span>
942
  </td>
 
 
 
943
  <td class="px-4 py-3 whitespace-nowrap text-sm">
944
  <button class="text-industrial-400 hover:text-industrial-300 mr-2" onclick="showTemperatureChart()">
945
  <i class="fas fa-chart-line"></i>
@@ -962,9 +1056,9 @@
962
  return;
963
  }
964
 
965
- let csvContent = "Timestamp,Temperature,Status\n";
966
  temperatureHistory.forEach(reading => {
967
- csvContent += `${reading.timestamp},${reading.temp},${reading.status}\n`;
968
  });
969
 
970
  const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
@@ -979,113 +1073,6 @@
979
  logDebug("Data exported as CSV");
980
  }
981
 
982
- // Chart functions
983
- let temperatureChart = null;
984
-
985
- function showTemperatureChart() {
986
- const modal = document.getElementById('chartModal');
987
- modal.classList.remove('hidden');
988
-
989
- const ctx = document.getElementById('temperatureChart').getContext('2d');
990
-
991
- // Destroy previous chart if exists
992
- if (temperatureChart) {
993
- temperatureChart.destroy();
994
- }
995
-
996
- // Prepare chart data
997
- const labels = temperatureHistory.map(reading => reading.timestamp);
998
- const data = temperatureHistory.map(reading => reading.temp);
999
- const statusColors = temperatureHistory.map(reading => {
1000
- switch(reading.status) {
1001
- case 'critical': return '#ef4444';
1002
- case 'warning': return '#f59e0b';
1003
- default: return '#10b981';
1004
- }
1005
- });
1006
-
1007
- temperatureChart = new Chart(ctx, {
1008
- type: 'line',
1009
- data: {
1010
- labels: labels,
1011
- datasets: [{
1012
- label: 'Temperature (°C)',
1013
- data: data,
1014
- borderColor: '#38bdf8',
1015
- backgroundColor: 'rgba(56, 189, 248, 0.1)',
1016
- borderWidth: 2,
1017
- pointBackgroundColor: statusColors,
1018
- pointRadius: 5,
1019
- pointHoverRadius: 7,
1020
- tension: 0.1,
1021
- fill: true
1022
- }]
1023
- },
1024
- options: {
1025
- responsive: true,
1026
- maintainAspectRatio: false,
1027
- scales: {
1028
- y: {
1029
- beginAtZero: false,
1030
- grid: {
1031
- color: 'rgba(30, 41, 59, 0.5)'
1032
- },
1033
- ticks: {
1034
- color: '#94a3b8'
1035
- }
1036
- },
1037
- x: {
1038
- grid: {
1039
- color: 'rgba(30, 41, 59, 0.5)'
1040
- },
1041
- ticks: {
1042
- color: '#94a3b8'
1043
- }
1044
- }
1045
- },
1046
- plugins: {
1047
- legend: {
1048
- labels: {
1049
- color: '#e2e8f0'
1050
- }
1051
- },
1052
- tooltip: {
1053
- backgroundColor: '#1e293b',
1054
- titleColor: '#e2e8f0',
1055
- bodyColor: '#e2e8f0',
1056
- borderColor: '#334155',
1057
- borderWidth: 1
1058
- }
1059
- }
1060
- }
1061
- });
1062
- }
1063
-
1064
- function exportChartAsImage() {
1065
- if (!temperatureChart) return;
1066
-
1067
- const link = document.createElement('a');
1068
- link.download = `temperature_chart_${new Date().toISOString().slice(0,10)}.png`;
1069
- link.href = temperatureChart.toBase64Image();
1070
- link.click();
1071
- }
1072
-
1073
- // Chart variables
1074
- let liveChart = null;
1075
- let chartData = {
1076
- labels: [],
1077
- datasets: [{
1078
- label: 'Temperature (°C)',
1079
- data: [],
1080
- borderColor: '#38bdf8',
1081
- backgroundColor: 'rgba(56, 189, 248, 0.1)',
1082
- borderWidth: 2,
1083
- pointRadius: 3,
1084
- tension: 0.1,
1085
- fill: true
1086
- }]
1087
- };
1088
-
1089
  // Initialize live chart
1090
  function initLiveChart() {
1091
  const ctx = document.getElementById('liveChart').getContext('2d');
@@ -1155,68 +1142,7 @@
1155
  }
1156
 
1157
  // Initialize the app when DOM is loaded
1158
- document.addEventListener('DOMContentLoaded', () => {
1159
- initLiveChart();
1160
- init();
1161
-
1162
- // Start/stop periodic capture
1163
- let captureInterval;
1164
- const captureBtn = document.getElementById('captureBtn');
1165
- const stopRecordingBtn = document.getElementById('stopRecordingBtn');
1166
-
1167
- captureBtn.addEventListener('click', () => {
1168
- if (captureInterval) {
1169
- stopPeriodicCapture(captureInterval);
1170
- captureInterval = null;
1171
- captureBtn.innerHTML = '<i class="fas fa-camera mr-1"></i> Capture & Analyze';
1172
- stopRecordingBtn.classList.add('hidden');
1173
- } else {
1174
- captureInterval = startPeriodicCapture(10);
1175
- captureBtn.innerHTML = '<i class="fas fa-pause mr-1"></i> Pause Recording';
1176
- stopRecordingBtn.classList.remove('hidden');
1177
- }
1178
- });
1179
-
1180
- // Stop recording button
1181
- stopRecordingBtn.addEventListener('click', () => {
1182
- if (captureInterval) {
1183
- stopPeriodicCapture(captureInterval);
1184
- captureInterval = null;
1185
- captureBtn.innerHTML = '<i class="fas fa-camera mr-1"></i> Capture & Analyze';
1186
- stopRecordingBtn.classList.add('hidden');
1187
- }
1188
- });
1189
-
1190
- // Export data button
1191
- document.getElementById('exportDataBtn').addEventListener('click', exportData);
1192
-
1193
- // Chart modal events
1194
- document.getElementById('closeChartModal').addEventListener('click', () => {
1195
- document.getElementById('chartModal').classList.add('hidden');
1196
- });
1197
-
1198
- document.getElementById('exportChartBtn').addEventListener('click', exportChartAsImage);
1199
- });
1200
  </script>
1201
-
1202
- <!-- Chart Modal -->
1203
- <div id="chartModal" class="modal-overlay hidden">
1204
- <div class="modal-content">
1205
- <div class="flex justify-between items-center mb-4">
1206
- <h3 class="text-xl font-bold">Temperature History Chart</h3>
1207
- <button id="closeChartModal" class="text-industrial-400 hover:text-industrial-300">
1208
- <i class="fas fa-times"></i>
1209
- </button>
1210
- </div>
1211
- <div class="bg-industrial-800 p-4 rounded-lg">
1212
- <canvas id="temperatureChart" height="300"></canvas>
1213
- </div>
1214
- <div class="modal-actions">
1215
- <button id="exportChartBtn" class="bg-industrial-600 hover:bg-industrial-500 text-white px-4 py-2 rounded-lg">
1216
- <i class="fas fa-download mr-2"></i> Export as Image
1217
- </button>
1218
- </div>
1219
- </div>
1220
- </div>
1221
- <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>
1222
  </html>
 
171
  gap: 0.75rem;
172
  margin-top: 1rem;
173
  }
174
+
175
+ /* Loading spinner */
176
+ .spinner {
177
+ width: 24px;
178
+ height: 24px;
179
+ border: 3px solid rgba(255,255,255,0.3);
180
+ border-radius: 50%;
181
+ border-top-color: #38bdf8;
182
+ animation: spin 1s ease-in-out infinite;
183
+ display: inline-block;
184
+ vertical-align: middle;
185
+ margin-right: 8px;
186
+ }
187
+
188
+ @keyframes spin {
189
+ to { transform: rotate(360deg); }
190
+ }
191
  </style>
192
  </head>
193
  <body class="min-h-screen p-4 md:p-8">
 
210
  <span class="status-indicator status-normal"></span>
211
  <span class="text-industrial-300">System Status: <span class="text-success-500 font-medium">Operational</span></span>
212
  </div>
213
+ <div class="text-xs text-industrial-400 mt-1">Multi-Model Temperature Analysis</div>
214
  </div>
215
  <button id="settingsBtn" class="bg-industrial-600 hover:bg-industrial-500 text-white px-4 py-2 rounded-lg transition flex items-center">
216
  <i class="fas fa-cog mr-2"></i> Settings
 
225
  <div class="p-4 border-b border-industrial-700 flex justify-between items-center">
226
  <h2 class="text-xl font-bold text-white">Machine Camera Feed</h2>
227
  <div class="flex space-x-2">
228
+ <select id="modelSelector" class="bg-industrial-700 border border-industrial-600 text-white rounded px-3 py-1 text-sm">
229
+ <option value="gemini">Gemini 1.5 Flash</option>
230
+ <option value="qwen">Qwen2.5-VL-7B-Instruct</option>
231
+ </select>
232
  <button id="captureBtn" class="bg-industrial-500 hover:bg-industrial-400 text-white px-3 py-1 rounded text-sm flex items-center">
233
  <i class="fas fa-camera mr-1"></i> Capture & Analyze
234
  </button>
 
238
  <div class="camera-feed h-96 flex items-center justify-center relative">
239
  <video id="cameraFeed" class="w-full h-full object-contain" autoplay playsinline></video>
240
  <canvas id="captureCanvas" class="hidden"></canvas>
241
+ <div id="processingOverlay" class="absolute inset-0 bg-black bg-opacity-70 flex items-center justify-center hidden">
242
+ <div class="text-center">
243
+ <div class="spinner"></div>
244
+ <p class="text-industrial-200 mt-2">Processing with <span id="processingModel">Gemini</span>...</p>
245
+ </div>
246
+ </div>
247
  </div>
248
 
249
  <div class="mt-4 text-center text-industrial-300 text-sm">
250
+ <i class="fas fa-microchip mr-1"></i> Current Model: <span id="currentModelDisplay">Gemini 1.5 Flash</span>
251
  </div>
252
  </div>
253
  </div>
 
325
  <th class="px-4 py-3 text-left text-xs font-medium text-industrial-400 uppercase tracking-wider">Timestamp</th>
326
  <th class="px-4 py-3 text-left text-xs font-medium text-industrial-400 uppercase tracking-wider">Temperature</th>
327
  <th class="px-4 py-3 text-left text-xs font-medium text-industrial-400 uppercase tracking-wider">Status</th>
328
+ <th class="px-4 py-3 text-left text-xs font-medium text-industrial-400 uppercase tracking-wider">Model</th>
329
  <th class="px-4 py-3 text-left text-xs font-medium text-industrial-400 uppercase tracking-wider">Actions</th>
330
  </tr>
331
  </thead>
332
  <tbody id="historyBody" class="divide-y divide-industrial-800">
333
  <!-- History items will be added here dynamically -->
334
  <tr>
335
+ <td colspan="5" class="px-4 py-8 text-center text-industrial-500">
336
  No temperature data recorded yet
337
  </td>
338
  </tr>
 
356
  <div class="dashboard-grid mb-8">
357
  <div class="industrial-card">
358
  <div class="p-4 border-b border-industrial-700">
359
+ <h2 class="text-xl font-bold text-white">Model Status</h2>
360
  </div>
361
  <div class="p-4">
362
  <div class="flex items-center mb-4">
363
  <div class="mr-4">
364
  <div class="bg-industrial-700 rounded-full p-3">
365
+ <i class="fas fa-robot text-industrial-300 text-2xl"></i>
366
  </div>
367
  </div>
368
  <div>
369
+ <div class="text-industrial-300">Current Model</div>
370
+ <div id="activeModelStatus" class="text-white font-bold text-lg">Gemini 1.5 Flash</div>
371
  </div>
372
  </div>
373
  <div class="bg-industrial-800 rounded-lg p-3">
374
+ <div class="text-industrial-400 text-sm mb-1">Last Analysis Result</div>
375
  <div id="lastOcrResult" class="text-industrial-200 font-mono">Waiting for first capture...</div>
376
  </div>
377
  </div>
 
412
  </div>
413
  </div>
414
  <div>
415
+ <div class="text-industrial-300">Model API</div>
416
+ <div id="apiStatus" class="text-white font-bold text-lg">Ready</div>
417
  </div>
418
  </div>
419
  <div class="bg-industrial-800 rounded-lg p-3">
420
+ <div class="text-industrial-400 text-sm mb-1">Model Performance</div>
421
+ <div id="modelPerformance" class="text-industrial-200">Gemini: Fast | Qwen: High Accuracy</div>
422
  </div>
423
  </div>
424
  </div>
 
436
 
437
  <!-- Footer -->
438
  <footer class="text-center text-industrial-500 text-sm pt-6 border-t border-industrial-800">
439
+ <p>ThermoScanAI - Industrial Machine Temperature Monitoring System | Multi-Model Analysis</p>
440
  <p class="mt-2">© 2023 Industrial AI Solutions. All rights reserved.</p>
441
  </footer>
442
  </div>
443
 
444
+ <!-- Settings Modal -->
445
+ <div id="settingsModal" class="modal-overlay hidden">
446
+ <div class="modal-content">
447
+ <div class="flex justify-between items-center mb-4">
448
+ <h3 class="text-xl font-bold">System Settings</h3>
449
+ <button id="closeSettingsModal" class="text-industrial-400 hover:text-industrial-300">
450
+ <i class="fas fa-times"></i>
451
+ </button>
452
+ </div>
453
+
454
+ <div class="space-y-4">
455
+ <div>
456
+ <label class="block text-industrial-300 mb-2">Gemini API Key</label>
457
+ <input type="password" id="geminiApiKey" placeholder="Your Gemini API Key"
458
+ class="w-full bg-industrial-700 border border-industrial-600 rounded p-3 mb-2 text-white">
459
+ </div>
460
+
461
+ <div>
462
+ <label class="block text-industrial-300 mb-2">Hugging Face API Key (for Qwen)</label>
463
+ <input type="password" id="huggingfaceApiKey" placeholder="Your Hugging Face API Key"
464
+ class="w-full bg-industrial-700 border border-industrial-600 rounded p-3 mb-2 text-white">
465
+ </div>
466
+
467
+ <div>
468
+ <label class="block text-industrial-300 mb-2">Temperature Thresholds</label>
469
+ <div class="grid grid-cols-2 gap-4">
470
+ <div>
471
+ <label class="block text-xs text-industrial-400 mb-1">Warning Threshold (°C)</label>
472
+ <input type="number" id="warningThreshold" value="30"
473
+ class="w-full bg-industrial-700 border border-industrial-600 rounded p-2 text-white">
474
+ </div>
475
+ <div>
476
+ <label class="block text-xs text-industrial-400 mb-1">Critical Threshold (°C)</label>
477
+ <input type="number" id="criticalThreshold" value="35"
478
+ class="w-full bg-industrial-700 border border-industrial-600 rounded p-2 text-white">
479
+ </div>
480
+ </div>
481
+ </div>
482
+ </div>
483
+
484
+ <div class="modal-actions">
485
+ <button id="cancelSettings" class="px-4 py-2 rounded bg-industrial-600 hover:bg-industrial-500">
486
+ Cancel
487
+ </button>
488
+ <button id="saveSettings" class="px-4 py-2 rounded bg-industrial-500 hover:bg-industrial-400">
489
+ Save Settings
490
+ </button>
491
+ </div>
492
+ </div>
493
+ </div>
494
+
495
  <script>
496
  // DOM Elements
497
  const cameraFeed = document.getElementById('cameraFeed');
 
498
  const captureCanvas = document.getElementById('captureCanvas');
 
 
 
499
  const currentTempEl = document.getElementById('currentTemp');
500
  const maxTempEl = document.getElementById('maxTemp');
501
  const minTempEl = document.getElementById('minTemp');
 
504
  const gaugeProgress = document.getElementById('gaugeProgress');
505
  const gaugeValue = document.getElementById('gaugeValue');
506
  const lastOcrResult = document.getElementById('lastOcrResult');
507
+ const modelSelector = document.getElementById('modelSelector');
508
+ const currentModelDisplay = document.getElementById('currentModelDisplay');
509
+ const activeModelStatus = document.getElementById('activeModelStatus');
510
+ const processingOverlay = document.getElementById('processingOverlay');
511
+ const processingModel = document.getElementById('processingModel');
512
+ const apiStatus = document.getElementById('apiStatus');
513
 
514
  // API Key Management
515
+ let geminiApiKey = localStorage.getItem('geminiApiKey') || '';
516
+ let huggingfaceApiKey = localStorage.getItem('huggingfaceApiKey') || '';
517
+ let warningThreshold = localStorage.getItem('warningThreshold') || 30;
518
+ let criticalThreshold = localStorage.getItem('criticalThreshold') || 35;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
519
 
520
  // App State
521
  let monitoringInterval;
 
 
522
  let temperatureHistory = [];
523
  let maxTemp = null;
524
  let minTemp = null;
525
  let stream = null;
526
+ let currentModel = 'gemini'; // Default model
527
+ let liveChart = null;
528
+ let chartData = {
529
+ labels: [],
530
+ datasets: [{
531
+ label: 'Temperature (°C)',
532
+ data: [],
533
+ borderColor: '#38bdf8',
534
+ backgroundColor: 'rgba(56, 189, 248, 0.1)',
535
+ borderWidth: 2,
536
+ pointRadius: 3,
537
+ tension: 0.1,
538
+ fill: true
539
+ }]
540
+ };
541
+
542
  // Initialize the app
543
  async function init() {
544
+ // Load saved settings
545
+ document.getElementById('geminiApiKey').value = geminiApiKey;
546
+ document.getElementById('huggingfaceApiKey').value = huggingfaceApiKey;
547
+ document.getElementById('warningThreshold').value = warningThreshold;
548
+ document.getElementById('criticalThreshold').value = criticalThreshold;
549
+
550
+ // Set up model selector
551
+ modelSelector.addEventListener('change', function() {
552
+ currentModel = this.value;
553
+ const modelName = this.options[this.selectedIndex].text;
554
+ currentModelDisplay.textContent = modelName;
555
+ activeModelStatus.textContent = modelName;
556
+ logDebug(`Switched to ${modelName} model`);
557
+ });
558
+
559
  // Set up capture button
560
  document.getElementById('captureBtn').addEventListener('click', captureAndProcess);
561
+
562
+ // Initialize gauge
563
  updateGauge(0);
564
 
565
  // Initialize camera when user clicks capture for the first time
 
591
  document.getElementById('captureBtn').removeEventListener('click', firstCapture);
592
  }, { once: true });
593
 
594
+ // Settings modal
595
  document.getElementById('settingsBtn').addEventListener('click', () => {
596
+ document.getElementById('settingsModal').classList.remove('hidden');
 
597
  });
598
 
599
+ document.getElementById('closeSettingsModal').addEventListener('click', () => {
600
+ document.getElementById('settingsModal').classList.add('hidden');
 
 
 
601
  });
602
 
603
+ document.getElementById('cancelSettings').addEventListener('click', () => {
604
+ document.getElementById('settingsModal').classList.add('hidden');
605
  });
606
 
607
+ document.getElementById('saveSettings').addEventListener('click', saveSettings);
608
+
609
+ // Initialize chart
610
+ initLiveChart();
611
+
612
+ // Export data button
613
+ document.getElementById('exportDataBtn').addEventListener('click', exportData);
614
+
615
+ // Start/stop periodic capture
616
+ let captureInterval;
617
+ const captureBtn = document.getElementById('captureBtn');
618
+ const stopRecordingBtn = document.getElementById('stopRecordingBtn');
619
+
620
+ captureBtn.addEventListener('click', () => {
621
+ if (captureInterval) {
622
+ stopPeriodicCapture(captureInterval);
623
+ captureInterval = null;
624
+ captureBtn.innerHTML = '<i class="fas fa-camera mr-1"></i> Capture & Analyze';
625
+ stopRecordingBtn.classList.add('hidden');
626
+ } else {
627
+ captureInterval = startPeriodicCapture(10);
628
+ captureBtn.innerHTML = '<i class="fas fa-pause mr-1"></i> Pause Recording';
629
+ stopRecordingBtn.classList.remove('hidden');
630
+ }
631
+ });
632
+
633
+ stopRecordingBtn.addEventListener('click', () => {
634
+ if (captureInterval) {
635
+ stopPeriodicCapture(captureInterval);
636
+ captureInterval = null;
637
+ captureBtn.innerHTML = '<i class="fas fa-camera mr-1"></i> Capture & Analyze';
638
+ stopRecordingBtn.classList.add('hidden');
639
+ }
640
+ });
641
+
642
+ // Update API status
643
+ updateApiStatus();
644
  }
645
 
646
+ // Save settings
647
+ function saveSettings() {
648
+ geminiApiKey = document.getElementById('geminiApiKey').value.trim();
649
+ huggingfaceApiKey = document.getElementById('huggingfaceApiKey').value.trim();
650
+ warningThreshold = parseInt(document.getElementById('warningThreshold').value) || 30;
651
+ criticalThreshold = parseInt(document.getElementById('criticalThreshold').value) || 35;
652
+
653
+ localStorage.setItem('geminiApiKey', geminiApiKey);
654
+ localStorage.setItem('huggingfaceApiKey', huggingfaceApiKey);
655
+ localStorage.setItem('warningThreshold', warningThreshold);
656
+ localStorage.setItem('criticalThreshold', criticalThreshold);
657
+
658
+ document.getElementById('settingsModal').classList.add('hidden');
659
+ logDebug("Settings saved");
660
+ updateApiStatus();
661
+ }
662
 
663
+ // Update API status display
664
+ function updateApiStatus() {
665
+ const geminiReady = geminiApiKey.length > 0;
666
+ const qwenReady = huggingfaceApiKey.length > 0;
667
 
668
+ if (currentModel === 'gemini') {
669
+ apiStatus.textContent = geminiReady ? 'Ready' : 'API Key Required';
670
+ apiStatus.className = geminiReady ? 'text-success-500 font-bold text-lg' : 'text-warning-500 font-bold text-lg';
671
+ } else {
672
+ apiStatus.textContent = qwenReady ? 'Ready' : 'API Key Required';
673
+ apiStatus.className = qwenReady ? 'text-success-500 font-bold text-lg' : 'text-warning-500 font-bold text-lg';
674
  }
 
 
 
 
 
675
  }
676
 
677
  // Capture image and process
678
  function captureAndProcess() {
679
  if (!stream) return;
680
 
681
+ // Show processing overlay
682
+ processingOverlay.classList.remove('hidden');
683
+ processingModel.textContent = currentModel === 'gemini' ? 'Gemini' : 'Qwen';
684
+
685
  // Capture frame
686
  const context = captureCanvas.getContext('2d');
687
  captureCanvas.width = cameraFeed.videoWidth;
 
691
  // Convert to base64 for API
692
  const imageData = captureCanvas.toDataURL('image/jpeg').split(',')[1];
693
 
694
+ // Process with selected model
695
+ if (currentModel === 'gemini') {
696
+ processWithGemini(imageData);
697
+ } else {
698
+ processWithQwen(imageData);
699
+ }
700
  }
701
 
702
  // Start periodic capturing
 
723
  debugConsole.appendChild(logEntry);
724
  debugConsole.scrollTop = debugConsole.scrollHeight;
725
 
 
 
 
726
  // Keep only last 100 messages
727
  if (debugConsole.children.length > 100) {
728
  debugConsole.removeChild(debugConsole.children[0]);
 
731
 
732
  // Process with Gemini API
733
  async function processWithGemini(imageData) {
734
+ if (!geminiApiKey) {
735
+ const msg = "Gemini API Key not configured";
736
  lastOcrResult.textContent = msg;
737
  logDebug(msg);
738
  currentTempEl.textContent = "--°C";
739
  gaugeValue.textContent = "--";
740
+ processingOverlay.classList.add('hidden');
741
  return;
742
  }
743
 
744
+ lastOcrResult.textContent = "Processing image with Gemini...";
745
  logDebug("Starting image processing with Gemini API");
 
746
 
747
  try {
748
+ const apiUrl = `https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent?key=${geminiApiKey}`;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
749
 
750
  const response = await fetch(apiUrl, {
751
  method: 'POST',
 
772
  });
773
 
774
  const data = await response.json();
 
 
775
 
776
  if (data.candidates && data.candidates[0].content.parts[0].text) {
777
  const result = data.candidates[0].content.parts[0].text;
778
+ logDebug(`Gemini response: "${result}"`);
 
 
 
779
 
780
+ // Parse temperature
781
+ const tempMatch = result.match(/-?\d+(\.\d+)?/);
782
  if (tempMatch) {
783
+ const temperature = parseFloat(tempMatch[0]);
784
+ const msg = `Gemini detected temperature: ${temperature}°C`;
 
 
 
 
 
785
  lastOcrResult.textContent = msg;
786
  logDebug(msg);
787
+ updateTemperature(temperature, 'gemini');
788
  } else {
789
  lastOcrResult.textContent = "No temperature detected";
790
  currentTempEl.textContent = "--°C";
 
797
  }
798
  } catch (error) {
799
  console.error("Gemini API error:", error);
800
+ const msg = `Gemini Error: ${error.message}`;
801
  lastOcrResult.textContent = msg;
802
  logDebug(msg);
803
  currentTempEl.textContent = "--°C";
804
  gaugeValue.textContent = "--";
805
+ } finally {
806
+ processingOverlay.classList.add('hidden');
807
  }
808
  }
809
 
810
+ // Process with Qwen API
811
+ async function processWithQwen(imageData) {
812
+ if (!huggingfaceApiKey) {
813
+ const msg = "Hugging Face API Key not configured";
814
+ lastOcrResult.textContent = msg;
815
+ logDebug(msg);
816
+ currentTempEl.textContent = "--°C";
817
+ gaugeValue.textContent = "--";
818
+ processingOverlay.classList.add('hidden');
819
+ return;
820
+ }
821
 
822
+ lastOcrResult.textContent = "Processing image with Qwen...";
823
+ logDebug("Starting image processing with Qwen API");
824
+
825
+ try {
826
+ const API_URL = "https://api-inference.huggingface.co/models/Qwen/Qwen2.5-VL-7B-Instruct";
827
+ const headers = {
828
+ "Authorization": `Bearer ${huggingfaceApiKey}`,
829
+ "Content-Type": "application/json"
830
+ };
831
+
832
+ const payload = {
833
+ "inputs": {
834
+ "role": "user",
835
+ "content": [
836
+ {
837
+ "type": "image",
838
+ "src": `data:image/jpeg;base64,${imageData}`
839
+ },
840
+ {
841
+ "type": "text",
842
+ "text": "Extract the numerical temperature value from this industrial machine display. Only return the number, nothing else."
843
+ }
844
+ ]
845
+ }
846
+ };
847
+
848
+ logDebug("Sending request to Qwen API...");
849
+ const response = await fetch(API_URL, {
850
+ method: "POST",
851
+ headers: headers,
852
+ body: JSON.stringify(payload)
853
+ });
854
+
855
+ if (!response.ok) {
856
+ throw new Error(`HTTP error! status: ${response.status}`);
857
+ }
858
+
859
+ const data = await response.json();
860
+ logDebug(`Qwen response: ${JSON.stringify(data)}`);
861
+
862
+ if (data && data[0] && data[0].generated_text) {
863
+ const result = data[0].generated_text;
864
+ logDebug(`Qwen raw response: "${result}"`);
865
+
866
+ // Parse temperature
867
+ const tempMatch = result.match(/-?\d+(\.\d+)?/);
868
+ if (tempMatch) {
869
+ const temperature = parseFloat(tempMatch[0]);
870
+ const msg = `Qwen detected temperature: ${temperature}°C`;
871
+ lastOcrResult.textContent = msg;
872
+ logDebug(msg);
873
+ updateTemperature(temperature, 'qwen');
874
+ } else {
875
+ lastOcrResult.textContent = "No temperature detected";
876
+ currentTempEl.textContent = "--°C";
877
+ gaugeValue.textContent = "--";
878
+ }
879
+ } else {
880
+ lastOcrResult.textContent = "Failed to process image";
881
+ currentTempEl.textContent = "--°C";
882
+ gaugeValue.textContent = "--";
883
+ }
884
+ } catch (error) {
885
+ console.error("Qwen API error:", error);
886
+ const msg = `Qwen Error: ${error.message}`;
887
+ lastOcrResult.textContent = msg;
888
+ logDebug(msg);
889
+ currentTempEl.textContent = "--°C";
890
+ gaugeValue.textContent = "--";
891
+ } finally {
892
+ processingOverlay.classList.add('hidden');
893
+ }
894
+ }
895
+
896
+ // Update temperature display and history
897
+ function updateTemperature(temp, model) {
898
  currentTempEl.textContent = `${temp}°C`;
899
  updateGauge(temp);
900
  updateStatus(temp);
901
 
902
+ const timestamp = new Date().toLocaleTimeString();
903
+ const status = getStatus(temp);
904
+
905
  temperatureHistory.push({
906
  temp: temp,
907
+ timestamp: timestamp,
908
+ status: status,
909
+ model: model === 'gemini' ? 'Gemini' : 'Qwen'
910
  });
911
 
912
  if (temperatureHistory.length > 20) {
 
924
  }
925
 
926
  updateHistoryTable();
927
+ updateLiveChart(temp, timestamp);
 
 
 
 
 
928
 
929
+ // Check for critical temperature
930
+ checkCriticalTemperature(temp);
931
+ }
932
+
933
+ // Check for critical temperature and trigger alerts
934
+ function checkCriticalTemperature(temp) {
935
+ if (temp > criticalThreshold) {
936
+ logDebug(`Critical temperature detected: ${temp}°C (threshold: ${criticalThreshold}°C)`);
937
+ // In a real application, you would trigger alerts here
938
+ // For demo purposes, we'll just log it
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
939
  }
 
940
  }
 
 
941
 
942
  // Update gauge display
943
  function updateGauge(temp) {
 
949
  gaugeValue.textContent = `${temp}°C`;
950
 
951
  // Update gauge color based on temperature
952
+ if (temp > criticalThreshold) {
953
  gaugeProgress.style.stroke = '#ef4444';
954
+ } else if (temp > warningThreshold) {
955
  gaugeProgress.style.stroke = '#f59e0b';
956
  } else {
957
  gaugeProgress.style.stroke = '#38bdf8';
 
963
  const statusIndicator = tempStatusEl.querySelector('.status-indicator');
964
  statusIndicator.className = 'status-indicator';
965
 
966
+ if (temp > criticalThreshold) {
967
  tempStatusEl.innerHTML = '<span class="status-indicator status-danger"></span> CRITICAL TEMPERATURE';
968
  tempStatusEl.className = 'mt-2 px-3 py-1 rounded-full bg-danger-900 text-danger-200 text-sm';
969
  currentTempEl.classList.add('text-danger-500');
970
  currentTempEl.classList.remove('text-industrial-200', 'text-warning-500');
971
+ } else if (temp > warningThreshold) {
972
  tempStatusEl.innerHTML = '<span class="status-indicator status-warning"></span> HIGH TEMPERATURE';
973
  tempStatusEl.className = 'mt-2 px-3 py-1 rounded-full bg-warning-900 text-warning-200 text-sm';
974
  currentTempEl.classList.add('text-warning-500');
 
980
  currentTempEl.classList.remove('text-warning-500', 'text-danger-500');
981
  }
982
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
983
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
984
  // Get status for history
985
  function getStatus(temp) {
986
+ if (temp > criticalThreshold) return 'critical';
987
+ if (temp > warningThreshold) return 'warning';
988
  return 'normal';
989
  }
990
 
 
993
  if (temperatureHistory.length === 0) {
994
  historyBody.innerHTML = `
995
  <tr>
996
+ <td colspan="5" class="px-4 py-8 text-center text-industrial-500">
997
  No temperature data recorded yet
998
  </td>
999
  </tr>
 
1031
  <td class="px-4 py-3 whitespace-nowrap">
1032
  <span class="${statusClass} font-medium">${statusText}</span>
1033
  </td>
1034
+ <td class="px-4 py-3 whitespace-nowrap text-sm text-industrial-400">
1035
+ ${reading.model}
1036
+ </td>
1037
  <td class="px-4 py-3 whitespace-nowrap text-sm">
1038
  <button class="text-industrial-400 hover:text-industrial-300 mr-2" onclick="showTemperatureChart()">
1039
  <i class="fas fa-chart-line"></i>
 
1056
  return;
1057
  }
1058
 
1059
+ let csvContent = "Timestamp,Temperature,Status,Model\n";
1060
  temperatureHistory.forEach(reading => {
1061
+ csvContent += `${reading.timestamp},${reading.temp},${reading.status},${reading.model}\n`;
1062
  });
1063
 
1064
  const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
 
1073
  logDebug("Data exported as CSV");
1074
  }
1075
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1076
  // Initialize live chart
1077
  function initLiveChart() {
1078
  const ctx = document.getElementById('liveChart').getContext('2d');
 
1142
  }
1143
 
1144
  // Initialize the app when DOM is loaded
1145
+ document.addEventListener('DOMContentLoaded', init);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1146
  </script>
1147
+ </body>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1148
  </html>