Danielchris145 commited on
Commit
034681f
·
verified ·
1 Parent(s): f5fe8f3

Automated deployment via API

Browse files
Files changed (3) hide show
  1. app.py +180 -1
  2. static/js/main.js +140 -25
  3. templates/index.html +80 -0
app.py CHANGED
@@ -14,9 +14,10 @@ import time
14
  import warnings
15
 
16
  # Flask & Socket.IO
17
- from flask import Flask, render_template, request, jsonify, send_from_directory
18
  from flask_socketio import SocketIO, emit
19
  from flask_cors import CORS
 
20
 
21
  # Machine Learning
22
  from sklearn.preprocessing import StandardScaler
@@ -442,6 +443,184 @@ def generate_ai_response(query):
442
  logger.error(f"❌ Response generation error: {e}")
443
  return f"System operational. Temp: {current_temp:.1f}°C, Energy: {energy:.1f} kWh. How can I assist?"
444
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
445
  @app.route('/api/chat', methods=['POST'])
446
  def chat():
447
  """AI Chat interface with fail-safe return"""
 
14
  import warnings
15
 
16
  # Flask & Socket.IO
17
+ from flask import Flask, render_template, request, jsonify, send_from_directory, Response
18
  from flask_socketio import SocketIO, emit
19
  from flask_cors import CORS
20
+ import queue
21
 
22
  # Machine Learning
23
  from sklearn.preprocessing import StandardScaler
 
443
  logger.error(f"❌ Response generation error: {e}")
444
  return f"System operational. Temp: {current_temp:.1f}°C, Energy: {energy:.1f} kWh. How can I assist?"
445
 
446
+
447
+ def generate_streaming_response(query):
448
+ """Generator that yields response chunks for streaming"""
449
+ full_response = generate_ai_response(query)
450
+
451
+ # Split into words for natural streaming effect
452
+ words = full_response.split(' ')
453
+
454
+ for i, word in enumerate(words):
455
+ # Add space before word (except first)
456
+ if i > 0:
457
+ yield ' '
458
+ yield word
459
+ time.sleep(0.03) # 30ms delay between words for natural typing effect
460
+
461
+
462
+ @app.route('/api/chat/stream', methods=['POST'])
463
+ def chat_stream():
464
+ """Streaming chat endpoint using Server-Sent Events"""
465
+ try:
466
+ data = request.json
467
+ query = data.get('query', '').strip()
468
+
469
+ if not query:
470
+ return jsonify({'error': 'No query provided'}), 400
471
+
472
+ def generate():
473
+ full_response = ""
474
+ for chunk in generate_streaming_response(query):
475
+ full_response += chunk
476
+ # SSE format
477
+ yield f"data: {json.dumps({'chunk': chunk, 'done': False})}\n\n"
478
+
479
+ # Final message with complete response
480
+ yield f"data: {json.dumps({'chunk': '', 'done': True, 'full_response': full_response})}\n\n"
481
+
482
+ # Save to chat history
483
+ app_state.chat_history.append({
484
+ 'user': query,
485
+ 'bot': full_response,
486
+ 'timestamp': datetime.now().isoformat()
487
+ })
488
+
489
+ return Response(
490
+ generate(),
491
+ mimetype='text/event-stream',
492
+ headers={
493
+ 'Cache-Control': 'no-cache',
494
+ 'Connection': 'keep-alive',
495
+ 'X-Accel-Buffering': 'no'
496
+ }
497
+ )
498
+ except Exception as e:
499
+ logger.error(f"❌ Stream error: {e}")
500
+ return jsonify({'error': str(e)}), 500
501
+
502
+
503
+ @app.route('/api/predict/stream', methods=['GET'])
504
+ def predict_stream():
505
+ """Real-time streaming predictions via SSE"""
506
+ def generate():
507
+ while True:
508
+ try:
509
+ with app_state.lock:
510
+ features = quantum_engine.generate_features(
511
+ app_state.temp_history,
512
+ app_state.energy_history
513
+ )
514
+ predicted_temp = quantum_engine.predict_temperature(features)
515
+ anomaly_risk, is_anomaly = quantum_engine.detect_anomaly(
516
+ features,
517
+ app_state.current_temp
518
+ )
519
+
520
+ prediction_data = {
521
+ 'current_temp': round(app_state.get_temperature(), 2),
522
+ 'predicted_temp': round(predicted_temp, 2),
523
+ 'energy': round(app_state.get_energy(), 2),
524
+ 'anomaly_risk': round(anomaly_risk, 4),
525
+ 'is_anomaly': is_anomaly,
526
+ 'timestamp': datetime.now().isoformat(),
527
+ 'confidence': 0.9738
528
+ }
529
+
530
+ yield f"data: {json.dumps(prediction_data)}\n\n"
531
+ time.sleep(2) # Send prediction every 2 seconds
532
+
533
+ except GeneratorExit:
534
+ break
535
+ except Exception as e:
536
+ logger.error(f"❌ Prediction stream error: {e}")
537
+ time.sleep(2)
538
+
539
+ return Response(
540
+ generate(),
541
+ mimetype='text/event-stream',
542
+ headers={
543
+ 'Cache-Control': 'no-cache',
544
+ 'Connection': 'keep-alive',
545
+ 'X-Accel-Buffering': 'no'
546
+ }
547
+ )
548
+
549
+ @app.route('/api/predict', methods=['POST'])
550
+ def predict():
551
+ """Predict next temperature using Quantum ML"""
552
+ try:
553
+ with app_state.lock:
554
+ features = quantum_engine.generate_features(app_state.temp_history, app_state.energy_history)
555
+ predicted_temp = quantum_engine.predict_temperature(features)
556
+
557
+ return jsonify({
558
+ 'predicted_temp': round(predicted_temp, 2),
559
+ 'current_temp': round(app_state.get_temperature(), 2),
560
+ 'confidence': 0.9738,
561
+ 'model': 'Quantum Superposition Ensemble',
562
+ 'timestamp': datetime.now().isoformat()
563
+ })
564
+ except Exception as e:
565
+ logger.error(f"❌ Prediction error: {e}")
566
+ return jsonify({'error': str(e)}), 500
567
+
568
+
569
+ @app.route('/api/anomaly', methods=['GET'])
570
+ def check_anomaly():
571
+ """Check for anomalies"""
572
+ try:
573
+ with app_state.lock:
574
+ features = quantum_engine.generate_features(app_state.temp_history, app_state.energy_history)
575
+ anomaly_risk, is_anomaly = quantum_engine.detect_anomaly(features, app_state.current_temp)
576
+
577
+ app_state.anomaly_risk = anomaly_risk
578
+ app_state.is_anomaly = is_anomaly
579
+
580
+ return jsonify({
581
+ 'anomaly_score': round(anomaly_risk, 4),
582
+ 'is_anomaly': is_anomaly,
583
+ 'current_temp': round(app_state.get_temperature(), 2),
584
+ 'quantum_risk': round(anomaly_risk, 4),
585
+ 'confidence': 0.9660,
586
+ 'model': 'Quantum Entanglement Detection',
587
+ 'timestamp': datetime.now().isoformat()
588
+ })
589
+ except Exception as e:
590
+ logger.error(f"❌ Anomaly check error: {e}")
591
+ return jsonify({'error': str(e)}), 500
592
+
593
+
594
+ @app.route('/api/energy_status', methods=['GET'])
595
+ def energy_status():
596
+ """Get energy status and savings"""
597
+ try:
598
+ optimal_energy = CONFIG['OPTIMAL_ENERGY']
599
+ current_energy = app_state.get_energy()
600
+
601
+ # Calculate savings
602
+ if current_energy > 0:
603
+ savings_pct = ((optimal_energy - current_energy) / optimal_energy) * 100
604
+ else:
605
+ savings_pct = 0
606
+
607
+ # Annual ROI
608
+ roi_annual = int((savings_pct / 100) * 150)
609
+
610
+ return jsonify({
611
+ 'current_energy': round(current_energy, 2),
612
+ 'optimal_energy': optimal_energy,
613
+ 'savings_pct': round(savings_pct, 2),
614
+ 'status': 'GOOD' if savings_pct > 5 else 'OPTIMIZE',
615
+ 'roi_annual': roi_annual,
616
+ 'model': 'Quantum Energy Optimizer',
617
+ 'timestamp': datetime.now().isoformat()
618
+ })
619
+ except Exception as e:
620
+ logger.error(f"❌ Energy status error: {e}")
621
+ return jsonify({'error': str(e)}), 500
622
+
623
+
624
  @app.route('/api/chat', methods=['POST'])
625
  def chat():
626
  """AI Chat interface with fail-safe return"""
static/js/main.js CHANGED
@@ -58,12 +58,17 @@ const AppState = {
58
  // API Endpoints
59
  const API_ENDPOINTS = {
60
  chat: '/api/chat',
 
61
  predict: '/api/predict',
 
62
  anomaly: '/api/anomaly',
63
  energy: '/api/energy_status',
64
  status: '/api/status',
65
  };
66
 
 
 
 
67
  // ============================================================================
68
  // 2. INITIALIZATION - DOCUMENT READY
69
  // ============================================================================
@@ -621,29 +626,58 @@ async function sendChatMessage() {
621
  const sendBtn = document.querySelector('.btn-send');
622
  if (sendBtn) {
623
  sendBtn.disabled = true;
624
- sendBtn.textContent = 'Sending...';
625
  }
626
 
 
 
 
 
627
  try {
628
- console.log('💬 Sending chat message...');
629
 
630
- const response = await fetch(API_ENDPOINTS.chat, {
631
  method: 'POST',
632
  headers: { 'Content-Type': 'application/json' },
633
  body: JSON.stringify({ query: message })
634
  });
635
 
636
- if (!response.ok) throw new Error('Chat failed');
637
 
638
- const data = await response.json();
 
 
639
 
640
- addChatMessage('bot', data.response);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
641
 
642
- console.log(`🤖 Response: ${data.response}`);
643
 
644
  } catch (error) {
645
  console.error('❌ Chat error:', error);
646
- addChatMessage('bot', '❌ Error processing request');
647
  showNotification('Failed to send message', 'danger');
648
  } finally {
649
  if (sendBtn) {
@@ -654,6 +688,33 @@ async function sendChatMessage() {
654
  }
655
  }
656
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
657
  function addChatMessage(type, text) {
658
  const container = document.getElementById('chat-messages');
659
  if (!container) return;
@@ -688,35 +749,89 @@ function handleChatKeypress(e) {
688
  function startLiveUpdates() {
689
  console.log('⏰ Starting live update intervals...');
690
 
691
- // Predictions: Every 10 seconds
692
- setInterval(() => {
693
- if (AppState.isConnected) {
694
- fetchAndUpdatePredictions();
695
- }
696
- }, AppState.updateIntervals.prediction);
697
 
698
- // Energy: Every 20 seconds
699
  setInterval(() => {
700
  if (AppState.isConnected) {
701
  fetchAndUpdateEnergy();
702
  }
703
  }, AppState.updateIntervals.energy);
704
 
705
- // Anomalies: Every 15 seconds
706
- setInterval(() => {
707
- if (AppState.isConnected) {
708
- fetchAndUpdateAnomalies();
709
- }
710
- }, AppState.updateIntervals.anomaly);
711
-
712
  // Initial fetch
713
  setTimeout(() => {
714
- fetchAndUpdatePredictions();
715
  fetchAndUpdateEnergy();
716
- fetchAndUpdateAnomalies();
717
  }, 1000);
718
 
719
- console.log('✅ Live updates started');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
720
  }
721
 
722
  // ============================================================================
 
58
  // API Endpoints
59
  const API_ENDPOINTS = {
60
  chat: '/api/chat',
61
+ chatStream: '/api/chat/stream',
62
  predict: '/api/predict',
63
+ predictStream: '/api/predict/stream',
64
  anomaly: '/api/anomaly',
65
  energy: '/api/energy_status',
66
  status: '/api/status',
67
  };
68
 
69
+ // Streaming prediction connection
70
+ let predictionEventSource = null;
71
+
72
  // ============================================================================
73
  // 2. INITIALIZATION - DOCUMENT READY
74
  // ============================================================================
 
626
  const sendBtn = document.querySelector('.btn-send');
627
  if (sendBtn) {
628
  sendBtn.disabled = true;
629
+ sendBtn.textContent = '⏳ Thinking...';
630
  }
631
 
632
+ // Create streaming bot message container
633
+ const botMessageId = 'bot-msg-' + Date.now();
634
+ addStreamingMessage(botMessageId);
635
+
636
  try {
637
+ console.log('💬 Starting streaming chat...');
638
 
639
+ const response = await fetch('/api/chat/stream', {
640
  method: 'POST',
641
  headers: { 'Content-Type': 'application/json' },
642
  body: JSON.stringify({ query: message })
643
  });
644
 
645
+ if (!response.ok) throw new Error('Chat stream failed');
646
 
647
+ const reader = response.body.getReader();
648
+ const decoder = new TextDecoder();
649
+ let fullResponse = '';
650
 
651
+ while (true) {
652
+ const { done, value } = await reader.read();
653
+ if (done) break;
654
+
655
+ const chunk = decoder.decode(value);
656
+ const lines = chunk.split('\n');
657
+
658
+ for (const line of lines) {
659
+ if (line.startsWith('data: ')) {
660
+ try {
661
+ const data = JSON.parse(line.slice(6));
662
+ if (data.chunk) {
663
+ fullResponse += data.chunk;
664
+ updateStreamingMessage(botMessageId, fullResponse);
665
+ }
666
+ if (data.done) {
667
+ console.log('✅ Stream complete');
668
+ }
669
+ } catch (e) {
670
+ // Skip invalid JSON
671
+ }
672
+ }
673
+ }
674
+ }
675
 
676
+ console.log(`🤖 Response: ${fullResponse}`);
677
 
678
  } catch (error) {
679
  console.error('❌ Chat error:', error);
680
+ updateStreamingMessage(botMessageId, '❌ Error processing request. Please try again.');
681
  showNotification('Failed to send message', 'danger');
682
  } finally {
683
  if (sendBtn) {
 
688
  }
689
  }
690
 
691
+ function addStreamingMessage(messageId) {
692
+ const container = document.getElementById('chat-messages');
693
+ if (!container) return;
694
+
695
+ const messageDiv = document.createElement('div');
696
+ messageDiv.className = 'message agent';
697
+ messageDiv.id = messageId;
698
+ messageDiv.innerHTML = `<strong>🤖 Forge AI:</strong><br><span class="streaming-text"><span class="cursor">▊</span></span>`;
699
+
700
+ container.appendChild(messageDiv);
701
+ container.scrollTop = container.scrollHeight;
702
+ }
703
+
704
+ function updateStreamingMessage(messageId, text) {
705
+ const messageDiv = document.getElementById(messageId);
706
+ if (!messageDiv) return;
707
+
708
+ // Replace newlines with <br> for HTML display
709
+ const htmlText = text.replace(/\n/g, '<br>').replace(/•/g, '&bull;');
710
+ messageDiv.innerHTML = `<strong>🤖 Forge AI:</strong><br><span class="streaming-text">${htmlText}<span class="cursor">▊</span></span>`;
711
+
712
+ const container = document.getElementById('chat-messages');
713
+ if (container) {
714
+ container.scrollTop = container.scrollHeight;
715
+ }
716
+ }
717
+
718
  function addChatMessage(type, text) {
719
  const container = document.getElementById('chat-messages');
720
  if (!container) return;
 
749
  function startLiveUpdates() {
750
  console.log('⏰ Starting live update intervals...');
751
 
752
+ // Start streaming predictions (SSE)
753
+ startStreamingPredictions();
 
 
 
 
754
 
755
+ // Energy: Every 20 seconds (fallback)
756
  setInterval(() => {
757
  if (AppState.isConnected) {
758
  fetchAndUpdateEnergy();
759
  }
760
  }, AppState.updateIntervals.energy);
761
 
 
 
 
 
 
 
 
762
  // Initial fetch
763
  setTimeout(() => {
 
764
  fetchAndUpdateEnergy();
 
765
  }, 1000);
766
 
767
+ console.log('✅ Live updates started with streaming predictions');
768
+ }
769
+
770
+ function startStreamingPredictions() {
771
+ console.log('🔮 Starting streaming predictions...');
772
+
773
+ // Close existing connection if any
774
+ if (predictionEventSource) {
775
+ predictionEventSource.close();
776
+ }
777
+
778
+ predictionEventSource = new EventSource(API_ENDPOINTS.predictStream);
779
+
780
+ predictionEventSource.onopen = () => {
781
+ console.log('✅ Prediction stream connected');
782
+ showNotification('🔮 Real-time predictions active', 'success');
783
+ };
784
+
785
+ predictionEventSource.onmessage = (event) => {
786
+ try {
787
+ const data = JSON.parse(event.data);
788
+
789
+ // Update state
790
+ AppState.currentTemp = data.current_temp;
791
+ AppState.predictedTemp = data.predicted_temp;
792
+ AppState.currentEnergy = data.energy;
793
+ AppState.anomalyRisk = data.anomaly_risk;
794
+ AppState.isAnomaly = data.is_anomaly;
795
+
796
+ // Update UI with streaming data
797
+ updatePredictionDisplay(data.predicted_temp, data.confidence);
798
+ updateQuantumRiskMeter(data.anomaly_risk);
799
+ updateAnomalyIndicator(data.is_anomaly, data.anomaly_risk);
800
+
801
+ // Update anomaly status
802
+ updateAnomalyStatus(data.is_anomaly, data.anomaly_risk, data.anomaly_risk);
803
+
804
+ // Alerts for high risk
805
+ if (data.is_anomaly || data.anomaly_risk > 0.7) {
806
+ showQuantumAlert(data.current_temp, data.anomaly_risk);
807
+ }
808
+
809
+ console.log(`📊 Stream: Temp=${data.current_temp}°C, Pred=${data.predicted_temp}°C, Risk=${(data.anomaly_risk*100).toFixed(1)}%`);
810
+
811
+ } catch (e) {
812
+ console.error('❌ Parse error:', e);
813
+ }
814
+ };
815
+
816
+ predictionEventSource.onerror = (error) => {
817
+ console.error('❌ Prediction stream error:', error);
818
+
819
+ // Reconnect after 5 seconds
820
+ setTimeout(() => {
821
+ if (AppState.isConnected) {
822
+ console.log('🔄 Reconnecting prediction stream...');
823
+ startStreamingPredictions();
824
+ }
825
+ }, 5000);
826
+ };
827
+ }
828
+
829
+ function stopStreamingPredictions() {
830
+ if (predictionEventSource) {
831
+ predictionEventSource.close();
832
+ predictionEventSource = null;
833
+ console.log('⏹️ Prediction stream stopped');
834
+ }
835
  }
836
 
837
  // ============================================================================
templates/index.html CHANGED
@@ -797,6 +797,86 @@
797
  font-size: 2rem;
798
  }
799
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
800
  </style>
801
  </head>
802
 
 
797
  font-size: 2rem;
798
  }
799
  }
800
+
801
+ /* ============================================================================
802
+ STREAMING CHAT CURSOR ANIMATION
803
+ ============================================================================ */
804
+
805
+ .streaming-text {
806
+ display: inline;
807
+ }
808
+
809
+ .cursor {
810
+ display: inline-block;
811
+ color: var(--cyber-blue);
812
+ animation: blink 0.7s infinite;
813
+ font-weight: bold;
814
+ margin-left: 2px;
815
+ }
816
+
817
+ @keyframes blink {
818
+ 0%, 50% { opacity: 1; }
819
+ 51%, 100% { opacity: 0; }
820
+ }
821
+
822
+ .message.agent .streaming-text {
823
+ white-space: pre-wrap;
824
+ }
825
+
826
+ /* Streaming indicator */
827
+ .streaming-indicator {
828
+ display: inline-flex;
829
+ align-items: center;
830
+ gap: 4px;
831
+ margin-left: 8px;
832
+ }
833
+
834
+ .streaming-indicator .dot {
835
+ width: 6px;
836
+ height: 6px;
837
+ background: var(--cyber-blue);
838
+ border-radius: 50%;
839
+ animation: pulse-dot 1.4s infinite ease-in-out;
840
+ }
841
+
842
+ .streaming-indicator .dot:nth-child(1) { animation-delay: 0s; }
843
+ .streaming-indicator .dot:nth-child(2) { animation-delay: 0.2s; }
844
+ .streaming-indicator .dot:nth-child(3) { animation-delay: 0.4s; }
845
+
846
+ @keyframes pulse-dot {
847
+ 0%, 80%, 100% { transform: scale(0.6); opacity: 0.5; }
848
+ 40% { transform: scale(1); opacity: 1; }
849
+ }
850
+
851
+ /* Real-time prediction badge */
852
+ .live-badge {
853
+ display: inline-flex;
854
+ align-items: center;
855
+ gap: 6px;
856
+ background: rgba(57, 255, 20, 0.15);
857
+ border: 1px solid var(--neon-green);
858
+ padding: 4px 12px;
859
+ border-radius: 20px;
860
+ font-size: 0.75rem;
861
+ font-family: var(--font-tech);
862
+ color: var(--neon-green);
863
+ text-transform: uppercase;
864
+ letter-spacing: 1px;
865
+ }
866
+
867
+ .live-badge::before {
868
+ content: '';
869
+ width: 8px;
870
+ height: 8px;
871
+ background: var(--neon-green);
872
+ border-radius: 50%;
873
+ animation: pulse-live 1.5s infinite;
874
+ }
875
+
876
+ @keyframes pulse-live {
877
+ 0%, 100% { box-shadow: 0 0 0 0 rgba(57, 255, 20, 0.7); }
878
+ 50% { box-shadow: 0 0 0 8px rgba(57, 255, 20, 0); }
879
+ }
880
  </style>
881
  </head>
882