Update app/streamlit_app.py
Browse filesCritical Issues in Original streamlit_app.py:
Hardcoded paths that may not exist
No error handling for API failures
Limited file upload validation
No batch processing capabilities
Basic sidebar with minimal functionality
No analytics or visualization features
No system monitoring capabilities
No session state management
Observational Fix:
Added comprehensive error handling and validation for all inputs
Added robust API client with connection testing and timeout handling
Added advanced file upload validation with multiple format support
Added batch processing capabilities with progress tracking
Added comprehensive analytics with interactive visualizations
Added system monitoring with health checks and resource usage
Added session state management for prediction history
Added auto-refresh functionality and real-time status updates
Added custom model training interface with progress tracking
- app/streamlit_app.py +846 -129
|
@@ -1,5 +1,3 @@
|
|
| 1 |
-
# app/streamlit_app.py
|
| 2 |
-
|
| 3 |
import streamlit as st
|
| 4 |
import requests
|
| 5 |
import json
|
|
@@ -8,144 +6,863 @@ import altair as alt
|
|
| 8 |
import time
|
| 9 |
import subprocess
|
| 10 |
import sys
|
| 11 |
-
from pathlib import Path
|
| 12 |
import os
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 13 |
|
| 14 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
| 15 |
sys.path.append(str(Path(__file__).resolve().parent.parent))
|
| 16 |
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 43 |
try:
|
| 44 |
-
response =
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 55 |
|
| 56 |
-
#
|
| 57 |
-
|
| 58 |
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
else:
|
| 67 |
-
st.success("β
File looks good. Starting training...")
|
| 68 |
|
| 69 |
-
|
| 70 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 71 |
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
|
|
|
|
|
|
|
|
|
| 79 |
|
| 80 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 81 |
result = subprocess.run(
|
| 82 |
-
[sys.executable, "
|
| 83 |
-
capture_output=True,
|
|
|
|
|
|
|
| 84 |
)
|
| 85 |
-
|
| 86 |
if result.returncode == 0:
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
"test_accuracy": round(acc, 4),
|
| 92 |
-
"timestamp": time.strftime("%Y-%m-%dT%H:%M:%S")
|
| 93 |
-
}
|
| 94 |
-
with open(METADATA_PATH, "w") as f:
|
| 95 |
-
json.dump(metadata, f, indent=2)
|
| 96 |
-
status_text.text("π Training complete!")
|
| 97 |
-
st.success(f"New model trained with accuracy: {acc:.4f}")
|
| 98 |
else:
|
| 99 |
-
st.error("
|
| 100 |
-
st.
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
st.
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
st.
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
|
| 113 |
-
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
if ACTIVITY_LOG_PATH.exists():
|
| 118 |
-
with open(ACTIVITY_LOG_PATH) as f:
|
| 119 |
-
activity_log = json.load(f)
|
| 120 |
-
for entry in reversed(activity_log[-5:]):
|
| 121 |
-
st.sidebar.text(f"{entry['timestamp']} - {entry['event']}")
|
| 122 |
-
else:
|
| 123 |
-
st.sidebar.info("No recent logs found.")
|
| 124 |
-
|
| 125 |
-
# ---- Drift Chart ----
|
| 126 |
-
st.sidebar.header("π Drift Monitoring")
|
| 127 |
-
if DRIFT_LOG_PATH.exists():
|
| 128 |
-
drift_df = pd.read_json(DRIFT_LOG_PATH)
|
| 129 |
-
drift_df["timestamp"] = pd.to_datetime(drift_df["timestamp"])
|
| 130 |
-
drift_df["status"] = drift_df["drift_detected"].map({True: "Drift", False: "Stable"})
|
| 131 |
-
|
| 132 |
-
chart = alt.Chart(drift_df).mark_line(point=True).encode(
|
| 133 |
-
x="timestamp:T",
|
| 134 |
-
y=alt.Y("test_accuracy:Q", title="Test Accuracy"),
|
| 135 |
-
color="status:N",
|
| 136 |
-
tooltip=["timestamp", "test_accuracy", "status"]
|
| 137 |
-
).properties(title="Model Performance & Drift", height=250)
|
| 138 |
-
|
| 139 |
-
st.sidebar.altair_chart(chart, use_container_width=True)
|
| 140 |
-
else:
|
| 141 |
-
st.sidebar.info("No drift data available.")
|
| 142 |
-
|
| 143 |
-
|
| 144 |
-
# ---- Tmp Folder Explorer ----
|
| 145 |
-
st.sidebar.header("ποΈ /tmp Explorer")
|
| 146 |
-
if st.sidebar.button("Refresh /tmp View"):
|
| 147 |
-
tmp_files = []
|
| 148 |
-
for root, dirs, files in os.walk("/tmp"):
|
| 149 |
-
for f in files:
|
| 150 |
-
tmp_files.append(os.path.relpath(os.path.join(root, f), "/tmp"))
|
| 151 |
-
st.sidebar.write(tmp_files)
|
|
|
|
|
|
|
|
|
|
| 1 |
import streamlit as st
|
| 2 |
import requests
|
| 3 |
import json
|
|
|
|
| 6 |
import time
|
| 7 |
import subprocess
|
| 8 |
import sys
|
|
|
|
| 9 |
import os
|
| 10 |
+
import logging
|
| 11 |
+
from pathlib import Path
|
| 12 |
+
from datetime import datetime, timedelta
|
| 13 |
+
from typing import Dict, List, Optional, Any
|
| 14 |
+
import plotly.express as px
|
| 15 |
+
import plotly.graph_objects as go
|
| 16 |
+
from plotly.subplots import make_subplots
|
| 17 |
+
import hashlib
|
| 18 |
+
import io
|
| 19 |
|
| 20 |
+
# Configure logging
|
| 21 |
+
logging.basicConfig(level=logging.INFO)
|
| 22 |
+
logger = logging.getLogger(__name__)
|
| 23 |
+
|
| 24 |
+
# Add root to sys.path for imports
|
| 25 |
sys.path.append(str(Path(__file__).resolve().parent.parent))
|
| 26 |
|
| 27 |
+
class StreamlitAppManager:
|
| 28 |
+
"""Manages Streamlit application state and functionality"""
|
| 29 |
+
|
| 30 |
+
def __init__(self):
|
| 31 |
+
self.setup_config()
|
| 32 |
+
self.setup_paths()
|
| 33 |
+
self.setup_api_client()
|
| 34 |
+
self.initialize_session_state()
|
| 35 |
+
|
| 36 |
+
def setup_config(self):
|
| 37 |
+
"""Setup application configuration"""
|
| 38 |
+
self.config = {
|
| 39 |
+
'api_url': "http://localhost:8000",
|
| 40 |
+
'max_upload_size': 10 * 1024 * 1024, # 10MB
|
| 41 |
+
'supported_file_types': ['csv', 'txt', 'json'],
|
| 42 |
+
'max_text_length': 10000,
|
| 43 |
+
'prediction_timeout': 30,
|
| 44 |
+
'refresh_interval': 60,
|
| 45 |
+
'max_batch_size': 10
|
| 46 |
+
}
|
| 47 |
+
|
| 48 |
+
def setup_paths(self):
|
| 49 |
+
"""Setup file paths"""
|
| 50 |
+
self.paths = {
|
| 51 |
+
'custom_data': Path("/tmp/custom_upload.csv"),
|
| 52 |
+
'metadata': Path("/tmp/metadata.json"),
|
| 53 |
+
'activity_log': Path("/tmp/activity_log.json"),
|
| 54 |
+
'drift_log': Path("/tmp/logs/monitoring_log.json"),
|
| 55 |
+
'prediction_log': Path("/tmp/prediction_log.json"),
|
| 56 |
+
'scheduler_log': Path("/tmp/logs/scheduler_execution.json"),
|
| 57 |
+
'error_log': Path("/tmp/logs/scheduler_errors.json")
|
| 58 |
+
}
|
| 59 |
+
|
| 60 |
+
def setup_api_client(self):
|
| 61 |
+
"""Setup API client with error handling"""
|
| 62 |
+
self.session = requests.Session()
|
| 63 |
+
self.session.timeout = self.config['prediction_timeout']
|
| 64 |
+
|
| 65 |
+
# Test API connection
|
| 66 |
+
self.api_available = self.test_api_connection()
|
| 67 |
+
|
| 68 |
+
def test_api_connection(self) -> bool:
|
| 69 |
+
"""Test API connection"""
|
| 70 |
try:
|
| 71 |
+
response = self.session.get(f"{self.config['api_url']}/health", timeout=5)
|
| 72 |
+
return response.status_code == 200
|
| 73 |
+
except:
|
| 74 |
+
return False
|
| 75 |
+
|
| 76 |
+
def initialize_session_state(self):
|
| 77 |
+
"""Initialize Streamlit session state"""
|
| 78 |
+
if 'prediction_history' not in st.session_state:
|
| 79 |
+
st.session_state.prediction_history = []
|
| 80 |
+
|
| 81 |
+
if 'upload_history' not in st.session_state:
|
| 82 |
+
st.session_state.upload_history = []
|
| 83 |
+
|
| 84 |
+
if 'last_refresh' not in st.session_state:
|
| 85 |
+
st.session_state.last_refresh = datetime.now()
|
| 86 |
+
|
| 87 |
+
if 'auto_refresh' not in st.session_state:
|
| 88 |
+
st.session_state.auto_refresh = False
|
| 89 |
|
| 90 |
+
# Initialize app manager
|
| 91 |
+
app_manager = StreamlitAppManager()
|
| 92 |
|
| 93 |
+
# Page configuration
|
| 94 |
+
st.set_page_config(
|
| 95 |
+
page_title="Fake News Detection System",
|
| 96 |
+
page_icon="π°",
|
| 97 |
+
layout="wide",
|
| 98 |
+
initial_sidebar_state="expanded"
|
| 99 |
+
)
|
|
|
|
|
|
|
| 100 |
|
| 101 |
+
# Custom CSS for better styling
|
| 102 |
+
st.markdown("""
|
| 103 |
+
<style>
|
| 104 |
+
.main-header {
|
| 105 |
+
font-size: 3rem;
|
| 106 |
+
font-weight: bold;
|
| 107 |
+
text-align: center;
|
| 108 |
+
color: #1f77b4;
|
| 109 |
+
margin-bottom: 2rem;
|
| 110 |
+
}
|
| 111 |
+
|
| 112 |
+
.metric-card {
|
| 113 |
+
background-color: #f0f2f6;
|
| 114 |
+
padding: 1rem;
|
| 115 |
+
border-radius: 0.5rem;
|
| 116 |
+
border-left: 4px solid #1f77b4;
|
| 117 |
+
}
|
| 118 |
+
|
| 119 |
+
.success-message {
|
| 120 |
+
background-color: #d4edda;
|
| 121 |
+
color: #155724;
|
| 122 |
+
padding: 1rem;
|
| 123 |
+
border-radius: 0.5rem;
|
| 124 |
+
border: 1px solid #c3e6cb;
|
| 125 |
+
}
|
| 126 |
+
|
| 127 |
+
.warning-message {
|
| 128 |
+
background-color: #fff3cd;
|
| 129 |
+
color: #856404;
|
| 130 |
+
padding: 1rem;
|
| 131 |
+
border-radius: 0.5rem;
|
| 132 |
+
border: 1px solid #ffeaa7;
|
| 133 |
+
}
|
| 134 |
+
|
| 135 |
+
.error-message {
|
| 136 |
+
background-color: #f8d7da;
|
| 137 |
+
color: #721c24;
|
| 138 |
+
padding: 1rem;
|
| 139 |
+
border-radius: 0.5rem;
|
| 140 |
+
border: 1px solid #f5c6cb;
|
| 141 |
+
}
|
| 142 |
+
</style>
|
| 143 |
+
""", unsafe_allow_html=True)
|
| 144 |
|
| 145 |
+
def load_json_file(file_path: Path, default: Any = None) -> Any:
|
| 146 |
+
"""Safely load JSON file with error handling"""
|
| 147 |
+
try:
|
| 148 |
+
if file_path.exists():
|
| 149 |
+
with open(file_path, 'r') as f:
|
| 150 |
+
return json.load(f)
|
| 151 |
+
return default or {}
|
| 152 |
+
except Exception as e:
|
| 153 |
+
logger.error(f"Failed to load {file_path}: {e}")
|
| 154 |
+
return default or {}
|
| 155 |
|
| 156 |
+
def save_prediction_to_history(text: str, prediction: str, confidence: float):
|
| 157 |
+
"""Save prediction to session history"""
|
| 158 |
+
prediction_entry = {
|
| 159 |
+
'timestamp': datetime.now().isoformat(),
|
| 160 |
+
'text': text[:100] + "..." if len(text) > 100 else text,
|
| 161 |
+
'prediction': prediction,
|
| 162 |
+
'confidence': confidence,
|
| 163 |
+
'text_length': len(text)
|
| 164 |
+
}
|
| 165 |
+
|
| 166 |
+
st.session_state.prediction_history.append(prediction_entry)
|
| 167 |
+
|
| 168 |
+
# Keep only last 50 predictions
|
| 169 |
+
if len(st.session_state.prediction_history) > 50:
|
| 170 |
+
st.session_state.prediction_history = st.session_state.prediction_history[-50:]
|
| 171 |
+
|
| 172 |
+
def make_prediction_request(text: str) -> Dict[str, Any]:
|
| 173 |
+
"""Make prediction request to API"""
|
| 174 |
+
try:
|
| 175 |
+
if not app_manager.api_available:
|
| 176 |
+
return {'error': 'API is not available'}
|
| 177 |
+
|
| 178 |
+
response = app_manager.session.post(
|
| 179 |
+
f"{app_manager.config['api_url']}/predict",
|
| 180 |
+
json={"text": text},
|
| 181 |
+
timeout=app_manager.config['prediction_timeout']
|
| 182 |
+
)
|
| 183 |
+
|
| 184 |
+
if response.status_code == 200:
|
| 185 |
+
return response.json()
|
| 186 |
+
else:
|
| 187 |
+
return {'error': f'API Error: {response.status_code} - {response.text}'}
|
| 188 |
+
|
| 189 |
+
except requests.exceptions.Timeout:
|
| 190 |
+
return {'error': 'Request timed out. Please try again.'}
|
| 191 |
+
except requests.exceptions.ConnectionError:
|
| 192 |
+
return {'error': 'Cannot connect to prediction service.'}
|
| 193 |
+
except Exception as e:
|
| 194 |
+
return {'error': f'Unexpected error: {str(e)}'}
|
| 195 |
+
|
| 196 |
+
def validate_text_input(text: str) -> tuple[bool, str]:
|
| 197 |
+
"""Validate text input"""
|
| 198 |
+
if not text or not text.strip():
|
| 199 |
+
return False, "Please enter some text to analyze."
|
| 200 |
+
|
| 201 |
+
if len(text) < 10:
|
| 202 |
+
return False, "Text must be at least 10 characters long."
|
| 203 |
+
|
| 204 |
+
if len(text) > app_manager.config['max_text_length']:
|
| 205 |
+
return False, f"Text must be less than {app_manager.config['max_text_length']} characters."
|
| 206 |
+
|
| 207 |
+
# Check for suspicious content
|
| 208 |
+
suspicious_patterns = ['<script', 'javascript:', 'data:']
|
| 209 |
+
if any(pattern in text.lower() for pattern in suspicious_patterns):
|
| 210 |
+
return False, "Text contains suspicious content."
|
| 211 |
+
|
| 212 |
+
return True, "Valid"
|
| 213 |
+
|
| 214 |
+
def create_confidence_gauge(confidence: float, prediction: str):
|
| 215 |
+
"""Create confidence gauge visualization"""
|
| 216 |
+
fig = go.Figure(go.Indicator(
|
| 217 |
+
mode = "gauge+number+delta",
|
| 218 |
+
value = confidence * 100,
|
| 219 |
+
domain = {'x': [0, 1], 'y': [0, 1]},
|
| 220 |
+
title = {'text': f"Confidence: {prediction}"},
|
| 221 |
+
delta = {'reference': 50},
|
| 222 |
+
gauge = {
|
| 223 |
+
'axis': {'range': [None, 100]},
|
| 224 |
+
'bar': {'color': "red" if prediction == "Fake" else "green"},
|
| 225 |
+
'steps': [
|
| 226 |
+
{'range': [0, 50], 'color': "lightgray"},
|
| 227 |
+
{'range': [50, 80], 'color': "yellow"},
|
| 228 |
+
{'range': [80, 100], 'color': "lightgreen"}
|
| 229 |
+
],
|
| 230 |
+
'threshold': {
|
| 231 |
+
'line': {'color': "black", 'width': 4},
|
| 232 |
+
'thickness': 0.75,
|
| 233 |
+
'value': 90
|
| 234 |
+
}
|
| 235 |
+
}
|
| 236 |
+
))
|
| 237 |
+
|
| 238 |
+
fig.update_layout(height=300)
|
| 239 |
+
return fig
|
| 240 |
+
|
| 241 |
+
def create_prediction_history_chart():
|
| 242 |
+
"""Create prediction history visualization"""
|
| 243 |
+
if not st.session_state.prediction_history:
|
| 244 |
+
return None
|
| 245 |
+
|
| 246 |
+
df = pd.DataFrame(st.session_state.prediction_history)
|
| 247 |
+
df['timestamp'] = pd.to_datetime(df['timestamp'])
|
| 248 |
+
df['confidence_percent'] = df['confidence'] * 100
|
| 249 |
+
|
| 250 |
+
fig = px.scatter(
|
| 251 |
+
df,
|
| 252 |
+
x='timestamp',
|
| 253 |
+
y='confidence_percent',
|
| 254 |
+
color='prediction',
|
| 255 |
+
size='text_length',
|
| 256 |
+
hover_data=['text'],
|
| 257 |
+
title="Prediction History",
|
| 258 |
+
labels={'confidence_percent': 'Confidence (%)', 'timestamp': 'Time'}
|
| 259 |
+
)
|
| 260 |
+
|
| 261 |
+
fig.update_layout(height=400)
|
| 262 |
+
return fig
|
| 263 |
+
|
| 264 |
+
# Main application
|
| 265 |
+
def main():
|
| 266 |
+
"""Main Streamlit application"""
|
| 267 |
+
|
| 268 |
+
# Header
|
| 269 |
+
st.markdown('<h1 class="main-header">π° Fake News Detection System</h1>', unsafe_allow_html=True)
|
| 270 |
+
|
| 271 |
+
# API Status indicator
|
| 272 |
+
col1, col2, col3 = st.columns([1, 2, 1])
|
| 273 |
+
with col2:
|
| 274 |
+
if app_manager.api_available:
|
| 275 |
+
st.markdown('<div class="success-message">π’ API Service: Online</div>', unsafe_allow_html=True)
|
| 276 |
+
else:
|
| 277 |
+
st.markdown('<div class="error-message">π΄ API Service: Offline</div>', unsafe_allow_html=True)
|
| 278 |
+
|
| 279 |
+
# Main content area
|
| 280 |
+
tab1, tab2, tab3, tab4, tab5 = st.tabs([
|
| 281 |
+
"π Prediction",
|
| 282 |
+
"π Batch Analysis",
|
| 283 |
+
"π Analytics",
|
| 284 |
+
"π― Model Training",
|
| 285 |
+
"βοΈ System Status"
|
| 286 |
+
])
|
| 287 |
+
|
| 288 |
+
# Tab 1: Individual Prediction
|
| 289 |
+
with tab1:
|
| 290 |
+
st.header("Single Text Analysis")
|
| 291 |
+
|
| 292 |
+
# Input methods
|
| 293 |
+
input_method = st.radio(
|
| 294 |
+
"Choose input method:",
|
| 295 |
+
["Type Text", "Upload File"],
|
| 296 |
+
horizontal=True
|
| 297 |
+
)
|
| 298 |
+
|
| 299 |
+
user_text = ""
|
| 300 |
+
|
| 301 |
+
if input_method == "Type Text":
|
| 302 |
+
user_text = st.text_area(
|
| 303 |
+
"Enter news article text:",
|
| 304 |
+
height=200,
|
| 305 |
+
placeholder="Paste or type the news article you want to analyze..."
|
| 306 |
+
)
|
| 307 |
+
|
| 308 |
+
else: # Upload File
|
| 309 |
+
uploaded_file = st.file_uploader(
|
| 310 |
+
"Upload text file:",
|
| 311 |
+
type=['txt', 'csv'],
|
| 312 |
+
help="Upload a text file containing the article to analyze"
|
| 313 |
+
)
|
| 314 |
+
|
| 315 |
+
if uploaded_file:
|
| 316 |
+
try:
|
| 317 |
+
if uploaded_file.type == "text/plain":
|
| 318 |
+
user_text = str(uploaded_file.read(), "utf-8")
|
| 319 |
+
elif uploaded_file.type == "text/csv":
|
| 320 |
+
df = pd.read_csv(uploaded_file)
|
| 321 |
+
if 'text' in df.columns:
|
| 322 |
+
user_text = df['text'].iloc[0] if len(df) > 0 else ""
|
| 323 |
+
else:
|
| 324 |
+
st.error("CSV file must contain a 'text' column")
|
| 325 |
+
|
| 326 |
+
st.success(f"File uploaded successfully! ({len(user_text)} characters)")
|
| 327 |
+
|
| 328 |
+
except Exception as e:
|
| 329 |
+
st.error(f"Error reading file: {e}")
|
| 330 |
+
|
| 331 |
+
# Prediction section
|
| 332 |
+
col1, col2 = st.columns([3, 1])
|
| 333 |
+
|
| 334 |
+
with col1:
|
| 335 |
+
if st.button("π§ Analyze Text", type="primary", use_container_width=True):
|
| 336 |
+
if user_text:
|
| 337 |
+
# Validate input
|
| 338 |
+
is_valid, validation_message = validate_text_input(user_text)
|
| 339 |
+
|
| 340 |
+
if not is_valid:
|
| 341 |
+
st.error(validation_message)
|
| 342 |
+
else:
|
| 343 |
+
# Show progress
|
| 344 |
+
with st.spinner("Analyzing text..."):
|
| 345 |
+
result = make_prediction_request(user_text)
|
| 346 |
+
|
| 347 |
+
if 'error' in result:
|
| 348 |
+
st.error(f"β {result['error']}")
|
| 349 |
+
else:
|
| 350 |
+
# Display results
|
| 351 |
+
prediction = result['prediction']
|
| 352 |
+
confidence = result['confidence']
|
| 353 |
+
|
| 354 |
+
# Save to history
|
| 355 |
+
save_prediction_to_history(user_text, prediction, confidence)
|
| 356 |
+
|
| 357 |
+
# Results display
|
| 358 |
+
col_result1, col_result2 = st.columns(2)
|
| 359 |
+
|
| 360 |
+
with col_result1:
|
| 361 |
+
if prediction == "Fake":
|
| 362 |
+
st.markdown(f"""
|
| 363 |
+
<div class="error-message">
|
| 364 |
+
<h3>π¨ Prediction: FAKE NEWS</h3>
|
| 365 |
+
<p>Confidence: {confidence:.2%}</p>
|
| 366 |
+
</div>
|
| 367 |
+
""", unsafe_allow_html=True)
|
| 368 |
+
else:
|
| 369 |
+
st.markdown(f"""
|
| 370 |
+
<div class="success-message">
|
| 371 |
+
<h3>β
Prediction: REAL NEWS</h3>
|
| 372 |
+
<p>Confidence: {confidence:.2%}</p>
|
| 373 |
+
</div>
|
| 374 |
+
""", unsafe_allow_html=True)
|
| 375 |
+
|
| 376 |
+
with col_result2:
|
| 377 |
+
# Confidence gauge
|
| 378 |
+
fig_gauge = create_confidence_gauge(confidence, prediction)
|
| 379 |
+
st.plotly_chart(fig_gauge, use_container_width=True)
|
| 380 |
+
|
| 381 |
+
# Additional information
|
| 382 |
+
with st.expander("π Analysis Details"):
|
| 383 |
+
st.json({
|
| 384 |
+
"model_version": result.get('model_version', 'Unknown'),
|
| 385 |
+
"processing_time": f"{result.get('processing_time', 0):.3f} seconds",
|
| 386 |
+
"timestamp": result.get('timestamp', ''),
|
| 387 |
+
"text_length": len(user_text),
|
| 388 |
+
"word_count": len(user_text.split())
|
| 389 |
+
})
|
| 390 |
+
else:
|
| 391 |
+
st.warning("Please enter text to analyze.")
|
| 392 |
+
|
| 393 |
+
with col2:
|
| 394 |
+
if st.button("π Clear Text", use_container_width=True):
|
| 395 |
+
st.rerun()
|
| 396 |
+
|
| 397 |
+
# Tab 2: Batch Analysis
|
| 398 |
+
with tab2:
|
| 399 |
+
st.header("Batch Text Analysis")
|
| 400 |
+
|
| 401 |
+
# File upload for batch processing
|
| 402 |
+
batch_file = st.file_uploader(
|
| 403 |
+
"Upload CSV file for batch analysis:",
|
| 404 |
+
type=['csv'],
|
| 405 |
+
help="CSV file should contain a 'text' column with articles to analyze"
|
| 406 |
+
)
|
| 407 |
+
|
| 408 |
+
if batch_file:
|
| 409 |
+
try:
|
| 410 |
+
df = pd.read_csv(batch_file)
|
| 411 |
+
|
| 412 |
+
if 'text' not in df.columns:
|
| 413 |
+
st.error("CSV file must contain a 'text' column")
|
| 414 |
+
else:
|
| 415 |
+
st.success(f"File loaded: {len(df)} articles found")
|
| 416 |
+
|
| 417 |
+
# Preview data
|
| 418 |
+
st.subheader("Data Preview")
|
| 419 |
+
st.dataframe(df.head(10))
|
| 420 |
+
|
| 421 |
+
# Batch processing
|
| 422 |
+
if st.button("π Process Batch", type="primary"):
|
| 423 |
+
if len(df) > app_manager.config['max_batch_size']:
|
| 424 |
+
st.warning(f"Only processing first {app_manager.config['max_batch_size']} articles")
|
| 425 |
+
df = df.head(app_manager.config['max_batch_size'])
|
| 426 |
+
|
| 427 |
+
progress_bar = st.progress(0)
|
| 428 |
+
status_text = st.empty()
|
| 429 |
+
results = []
|
| 430 |
+
|
| 431 |
+
for i, row in df.iterrows():
|
| 432 |
+
status_text.text(f"Processing article {i+1}/{len(df)}...")
|
| 433 |
+
progress_bar.progress((i + 1) / len(df))
|
| 434 |
+
|
| 435 |
+
result = make_prediction_request(row['text'])
|
| 436 |
+
|
| 437 |
+
if 'error' not in result:
|
| 438 |
+
results.append({
|
| 439 |
+
'text': row['text'][:100] + "...",
|
| 440 |
+
'prediction': result['prediction'],
|
| 441 |
+
'confidence': result['confidence'],
|
| 442 |
+
'processing_time': result.get('processing_time', 0)
|
| 443 |
+
})
|
| 444 |
+
else:
|
| 445 |
+
results.append({
|
| 446 |
+
'text': row['text'][:100] + "...",
|
| 447 |
+
'prediction': 'Error',
|
| 448 |
+
'confidence': 0,
|
| 449 |
+
'processing_time': 0
|
| 450 |
+
})
|
| 451 |
+
|
| 452 |
+
# Display results
|
| 453 |
+
results_df = pd.DataFrame(results)
|
| 454 |
+
|
| 455 |
+
# Summary statistics
|
| 456 |
+
col1, col2, col3, col4 = st.columns(4)
|
| 457 |
+
|
| 458 |
+
with col1:
|
| 459 |
+
st.metric("Total Processed", len(results_df))
|
| 460 |
+
|
| 461 |
+
with col2:
|
| 462 |
+
fake_count = len(results_df[results_df['prediction'] == 'Fake'])
|
| 463 |
+
st.metric("Fake News", fake_count)
|
| 464 |
+
|
| 465 |
+
with col3:
|
| 466 |
+
real_count = len(results_df[results_df['prediction'] == 'Real'])
|
| 467 |
+
st.metric("Real News", real_count)
|
| 468 |
+
|
| 469 |
+
with col4:
|
| 470 |
+
avg_confidence = results_df['confidence'].mean()
|
| 471 |
+
st.metric("Avg Confidence", f"{avg_confidence:.2%}")
|
| 472 |
+
|
| 473 |
+
# Results visualization
|
| 474 |
+
if len(results_df) > 0:
|
| 475 |
+
fig = px.histogram(
|
| 476 |
+
results_df,
|
| 477 |
+
x='prediction',
|
| 478 |
+
color='prediction',
|
| 479 |
+
title="Batch Analysis Results"
|
| 480 |
+
)
|
| 481 |
+
st.plotly_chart(fig, use_container_width=True)
|
| 482 |
+
|
| 483 |
+
# Download results
|
| 484 |
+
csv_buffer = io.StringIO()
|
| 485 |
+
results_df.to_csv(csv_buffer, index=False)
|
| 486 |
+
|
| 487 |
+
st.download_button(
|
| 488 |
+
label="π₯ Download Results",
|
| 489 |
+
data=csv_buffer.getvalue(),
|
| 490 |
+
file_name=f"batch_analysis_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv",
|
| 491 |
+
mime="text/csv"
|
| 492 |
+
)
|
| 493 |
+
|
| 494 |
+
except Exception as e:
|
| 495 |
+
st.error(f"Error processing file: {e}")
|
| 496 |
+
|
| 497 |
+
# Tab 3: Analytics
|
| 498 |
+
with tab3:
|
| 499 |
+
st.header("System Analytics")
|
| 500 |
+
|
| 501 |
+
# Prediction history
|
| 502 |
+
if st.session_state.prediction_history:
|
| 503 |
+
st.subheader("Recent Predictions")
|
| 504 |
+
|
| 505 |
+
# History chart
|
| 506 |
+
fig_history = create_prediction_history_chart()
|
| 507 |
+
if fig_history:
|
| 508 |
+
st.plotly_chart(fig_history, use_container_width=True)
|
| 509 |
+
|
| 510 |
+
# History table
|
| 511 |
+
history_df = pd.DataFrame(st.session_state.prediction_history)
|
| 512 |
+
st.dataframe(history_df.tail(20), use_container_width=True)
|
| 513 |
+
|
| 514 |
+
else:
|
| 515 |
+
st.info("No prediction history available. Make some predictions to see analytics.")
|
| 516 |
+
|
| 517 |
+
# System metrics
|
| 518 |
+
st.subheader("System Metrics")
|
| 519 |
+
|
| 520 |
+
# Load various log files for analytics
|
| 521 |
+
try:
|
| 522 |
+
# API health check
|
| 523 |
+
if app_manager.api_available:
|
| 524 |
+
response = app_manager.session.get(f"{app_manager.config['api_url']}/metrics")
|
| 525 |
+
if response.status_code == 200:
|
| 526 |
+
metrics = response.json()
|
| 527 |
+
|
| 528 |
+
col1, col2, col3, col4 = st.columns(4)
|
| 529 |
+
|
| 530 |
+
with col1:
|
| 531 |
+
st.metric("Total API Requests", metrics.get('total_requests', 0))
|
| 532 |
+
|
| 533 |
+
with col2:
|
| 534 |
+
st.metric("Unique Clients", metrics.get('unique_clients', 0))
|
| 535 |
+
|
| 536 |
+
with col3:
|
| 537 |
+
st.metric("Model Version", metrics.get('model_version', 'Unknown'))
|
| 538 |
+
|
| 539 |
+
with col4:
|
| 540 |
+
status = metrics.get('model_health', 'unknown')
|
| 541 |
+
st.metric("Model Status", status)
|
| 542 |
+
|
| 543 |
+
except Exception as e:
|
| 544 |
+
st.warning(f"Could not load API metrics: {e}")
|
| 545 |
+
|
| 546 |
+
# Tab 4: Model Training
|
| 547 |
+
with tab4:
|
| 548 |
+
st.header("Custom Model Training")
|
| 549 |
+
|
| 550 |
+
st.info("Upload your own dataset to retrain the model with custom data.")
|
| 551 |
+
|
| 552 |
+
# File upload for training
|
| 553 |
+
training_file = st.file_uploader(
|
| 554 |
+
"Upload training dataset (CSV):",
|
| 555 |
+
type=['csv'],
|
| 556 |
+
help="CSV file should contain 'text' and 'label' columns (label: 0=Real, 1=Fake)"
|
| 557 |
+
)
|
| 558 |
+
|
| 559 |
+
if training_file:
|
| 560 |
+
try:
|
| 561 |
+
df_train = pd.read_csv(training_file)
|
| 562 |
+
|
| 563 |
+
required_columns = ['text', 'label']
|
| 564 |
+
missing_columns = [col for col in required_columns if col not in df_train.columns]
|
| 565 |
+
|
| 566 |
+
if missing_columns:
|
| 567 |
+
st.error(f"Missing required columns: {missing_columns}")
|
| 568 |
+
else:
|
| 569 |
+
st.success(f"Training file loaded: {len(df_train)} samples")
|
| 570 |
+
|
| 571 |
+
# Data validation
|
| 572 |
+
label_counts = df_train['label'].value_counts()
|
| 573 |
+
|
| 574 |
+
col1, col2 = st.columns(2)
|
| 575 |
+
|
| 576 |
+
with col1:
|
| 577 |
+
st.subheader("Dataset Overview")
|
| 578 |
+
st.write(f"Total samples: {len(df_train)}")
|
| 579 |
+
st.write(f"Real news (0): {label_counts.get(0, 0)}")
|
| 580 |
+
st.write(f"Fake news (1): {label_counts.get(1, 0)}")
|
| 581 |
+
|
| 582 |
+
with col2:
|
| 583 |
+
# Label distribution chart
|
| 584 |
+
fig_labels = px.pie(
|
| 585 |
+
values=label_counts.values,
|
| 586 |
+
names=['Real', 'Fake'],
|
| 587 |
+
title="Label Distribution"
|
| 588 |
+
)
|
| 589 |
+
st.plotly_chart(fig_labels)
|
| 590 |
+
|
| 591 |
+
# Training options
|
| 592 |
+
st.subheader("Training Configuration")
|
| 593 |
+
|
| 594 |
+
col1, col2 = st.columns(2)
|
| 595 |
+
|
| 596 |
+
with col1:
|
| 597 |
+
test_size = st.slider("Test Size", 0.1, 0.4, 0.2, 0.05)
|
| 598 |
+
max_features = st.number_input("Max Features", 1000, 20000, 10000, 1000)
|
| 599 |
+
|
| 600 |
+
with col2:
|
| 601 |
+
cross_validation = st.checkbox("Cross Validation", value=True)
|
| 602 |
+
hyperparameter_tuning = st.checkbox("Hyperparameter Tuning", value=False)
|
| 603 |
+
|
| 604 |
+
# Start training
|
| 605 |
+
if st.button("πββοΈ Start Training", type="primary"):
|
| 606 |
+
# Save training data
|
| 607 |
+
app_manager.paths['custom_data'].parent.mkdir(parents=True, exist_ok=True)
|
| 608 |
+
df_train.to_csv(app_manager.paths['custom_data'], index=False)
|
| 609 |
+
|
| 610 |
+
# Progress simulation
|
| 611 |
+
progress_bar = st.progress(0)
|
| 612 |
+
status_text = st.empty()
|
| 613 |
+
|
| 614 |
+
training_steps = [
|
| 615 |
+
"Preprocessing data...",
|
| 616 |
+
"Splitting dataset...",
|
| 617 |
+
"Training model...",
|
| 618 |
+
"Evaluating performance...",
|
| 619 |
+
"Saving model..."
|
| 620 |
+
]
|
| 621 |
+
|
| 622 |
+
for i, step in enumerate(training_steps):
|
| 623 |
+
status_text.text(step)
|
| 624 |
+
progress_bar.progress((i + 1) / len(training_steps))
|
| 625 |
+
time.sleep(2) # Simulate processing time
|
| 626 |
+
|
| 627 |
+
# Run actual training
|
| 628 |
+
try:
|
| 629 |
+
result = subprocess.run(
|
| 630 |
+
[sys.executable, "model/train.py",
|
| 631 |
+
"--data_path", str(app_manager.paths['custom_data'])],
|
| 632 |
+
capture_output=True,
|
| 633 |
+
text=True,
|
| 634 |
+
timeout=300
|
| 635 |
+
)
|
| 636 |
+
|
| 637 |
+
if result.returncode == 0:
|
| 638 |
+
st.success("π Training completed successfully!")
|
| 639 |
+
|
| 640 |
+
# Try to extract accuracy from output
|
| 641 |
+
try:
|
| 642 |
+
output_lines = result.stdout.strip().split('\n')
|
| 643 |
+
for line in output_lines:
|
| 644 |
+
if 'accuracy' in line.lower():
|
| 645 |
+
st.info(f"Model performance: {line}")
|
| 646 |
+
except:
|
| 647 |
+
pass
|
| 648 |
+
|
| 649 |
+
# Reload API model
|
| 650 |
+
if app_manager.api_available:
|
| 651 |
+
try:
|
| 652 |
+
reload_response = app_manager.session.post(
|
| 653 |
+
f"{app_manager.config['api_url']}/model/reload"
|
| 654 |
+
)
|
| 655 |
+
if reload_response.status_code == 200:
|
| 656 |
+
st.success("β
Model reloaded in API successfully!")
|
| 657 |
+
except:
|
| 658 |
+
st.warning("β οΈ Model trained but API reload failed")
|
| 659 |
+
|
| 660 |
+
else:
|
| 661 |
+
st.error(f"Training failed: {result.stderr}")
|
| 662 |
+
|
| 663 |
+
except subprocess.TimeoutExpired:
|
| 664 |
+
st.error("Training timed out. Please try with a smaller dataset.")
|
| 665 |
+
except Exception as e:
|
| 666 |
+
st.error(f"Training error: {e}")
|
| 667 |
+
|
| 668 |
+
except Exception as e:
|
| 669 |
+
st.error(f"Error loading training file: {e}")
|
| 670 |
+
|
| 671 |
+
# Tab 5: System Status
|
| 672 |
+
with tab5:
|
| 673 |
+
render_system_status()
|
| 674 |
+
|
| 675 |
+
def render_system_status():
|
| 676 |
+
"""Render system status tab"""
|
| 677 |
+
st.header("System Status & Monitoring")
|
| 678 |
+
|
| 679 |
+
# Auto-refresh toggle
|
| 680 |
+
col1, col2 = st.columns([1, 4])
|
| 681 |
+
with col1:
|
| 682 |
+
st.session_state.auto_refresh = st.checkbox("Auto Refresh", value=st.session_state.auto_refresh)
|
| 683 |
+
|
| 684 |
+
with col2:
|
| 685 |
+
if st.button("π Refresh Now"):
|
| 686 |
+
st.session_state.last_refresh = datetime.now()
|
| 687 |
+
st.rerun()
|
| 688 |
+
|
| 689 |
+
# System health overview
|
| 690 |
+
st.subheader("π₯ System Health")
|
| 691 |
+
|
| 692 |
+
if app_manager.api_available:
|
| 693 |
+
try:
|
| 694 |
+
health_response = app_manager.session.get(f"{app_manager.config['api_url']}/health")
|
| 695 |
+
if health_response.status_code == 200:
|
| 696 |
+
health_data = health_response.json()
|
| 697 |
+
|
| 698 |
+
# Overall status
|
| 699 |
+
overall_status = health_data.get('status', 'unknown')
|
| 700 |
+
if overall_status == 'healthy':
|
| 701 |
+
st.success("π’ System Status: Healthy")
|
| 702 |
+
else:
|
| 703 |
+
st.error("π΄ System Status: Unhealthy")
|
| 704 |
+
|
| 705 |
+
# Detailed health metrics
|
| 706 |
+
col1, col2, col3 = st.columns(3)
|
| 707 |
+
|
| 708 |
+
with col1:
|
| 709 |
+
st.subheader("π€ Model Health")
|
| 710 |
+
model_health = health_data.get('model_health', {})
|
| 711 |
+
|
| 712 |
+
for key, value in model_health.items():
|
| 713 |
+
if key != 'test_prediction':
|
| 714 |
+
st.write(f"**{key.replace('_', ' ').title()}:** {value}")
|
| 715 |
+
|
| 716 |
+
with col2:
|
| 717 |
+
st.subheader("π» System Resources")
|
| 718 |
+
system_health = health_data.get('system_health', {})
|
| 719 |
+
|
| 720 |
+
for key, value in system_health.items():
|
| 721 |
+
if isinstance(value, (int, float)):
|
| 722 |
+
st.metric(key.replace('_', ' ').title(), f"{value:.1f}%")
|
| 723 |
+
|
| 724 |
+
with col3:
|
| 725 |
+
st.subheader("π API Health")
|
| 726 |
+
api_health = health_data.get('api_health', {})
|
| 727 |
+
|
| 728 |
+
for key, value in api_health.items():
|
| 729 |
+
st.write(f"**{key.replace('_', ' ').title()}:** {value}")
|
| 730 |
+
|
| 731 |
+
except Exception as e:
|
| 732 |
+
st.error(f"Failed to get health status: {e}")
|
| 733 |
+
|
| 734 |
+
else:
|
| 735 |
+
st.error("π΄ API Service is not available")
|
| 736 |
+
|
| 737 |
+
# Model information
|
| 738 |
+
st.subheader("π― Model Information")
|
| 739 |
+
|
| 740 |
+
metadata = load_json_file(app_manager.paths['metadata'], {})
|
| 741 |
+
if metadata:
|
| 742 |
+
col1, col2 = st.columns(2)
|
| 743 |
+
|
| 744 |
+
with col1:
|
| 745 |
+
for key in ['model_version', 'test_accuracy', 'test_f1', 'model_type']:
|
| 746 |
+
if key in metadata:
|
| 747 |
+
display_key = key.replace('_', ' ').title()
|
| 748 |
+
value = metadata[key]
|
| 749 |
+
if isinstance(value, float):
|
| 750 |
+
st.metric(display_key, f"{value:.4f}")
|
| 751 |
+
else:
|
| 752 |
+
st.metric(display_key, str(value))
|
| 753 |
+
|
| 754 |
+
with col2:
|
| 755 |
+
for key in ['train_size', 'timestamp', 'data_version']:
|
| 756 |
+
if key in metadata:
|
| 757 |
+
display_key = key.replace('_', ' ').title()
|
| 758 |
+
value = metadata[key]
|
| 759 |
+
if key == 'timestamp':
|
| 760 |
+
try:
|
| 761 |
+
dt = datetime.fromisoformat(value.replace('Z', '+00:00'))
|
| 762 |
+
value = dt.strftime('%Y-%m-%d %H:%M:%S')
|
| 763 |
+
except:
|
| 764 |
+
pass
|
| 765 |
+
st.write(f"**{display_key}:** {value}")
|
| 766 |
+
|
| 767 |
+
else:
|
| 768 |
+
st.warning("No model metadata available")
|
| 769 |
+
|
| 770 |
+
# Recent activity
|
| 771 |
+
st.subheader("π Recent Activity")
|
| 772 |
+
|
| 773 |
+
activity_log = load_json_file(app_manager.paths['activity_log'], [])
|
| 774 |
+
if activity_log:
|
| 775 |
+
recent_activities = activity_log[-10:] if len(activity_log) > 10 else activity_log
|
| 776 |
+
|
| 777 |
+
for entry in reversed(recent_activities):
|
| 778 |
+
timestamp = entry.get('timestamp', 'Unknown')
|
| 779 |
+
event = entry.get('event', 'Unknown event')
|
| 780 |
+
level = entry.get('level', 'INFO')
|
| 781 |
+
|
| 782 |
+
if level == 'ERROR':
|
| 783 |
+
st.error(f"π΄ {timestamp} - {event}")
|
| 784 |
+
elif level == 'WARNING':
|
| 785 |
+
st.warning(f"π‘ {timestamp} - {event}")
|
| 786 |
+
else:
|
| 787 |
+
st.info(f"π΅ {timestamp} - {event}")
|
| 788 |
+
|
| 789 |
+
else:
|
| 790 |
+
st.info("No recent activity logs found")
|
| 791 |
+
|
| 792 |
+
# File system status
|
| 793 |
+
st.subheader("π File System Status")
|
| 794 |
+
|
| 795 |
+
critical_files = [
|
| 796 |
+
("/tmp/model.pkl", "Main Model"),
|
| 797 |
+
("/tmp/vectorizer.pkl", "Vectorizer"),
|
| 798 |
+
("/tmp/data/combined_dataset.csv", "Training Dataset"),
|
| 799 |
+
("/tmp/metadata.json", "Model Metadata")
|
| 800 |
+
]
|
| 801 |
+
|
| 802 |
+
col1, col2 = st.columns(2)
|
| 803 |
+
|
| 804 |
+
with col1:
|
| 805 |
+
st.write("**Critical Files:**")
|
| 806 |
+
for file_path, description in critical_files:
|
| 807 |
+
if Path(file_path).exists():
|
| 808 |
+
st.success(f"β
{description}")
|
| 809 |
+
else:
|
| 810 |
+
st.error(f"β {description}")
|
| 811 |
+
|
| 812 |
+
with col2:
|
| 813 |
+
# Disk usage information
|
| 814 |
+
try:
|
| 815 |
+
import shutil
|
| 816 |
+
total, used, free = shutil.disk_usage("/tmp")
|
| 817 |
+
|
| 818 |
+
st.write("**Disk Usage (/tmp):**")
|
| 819 |
+
st.write(f"Total: {total // (1024**3)} GB")
|
| 820 |
+
st.write(f"Used: {used // (1024**3)} GB")
|
| 821 |
+
st.write(f"Free: {free // (1024**3)} GB")
|
| 822 |
+
|
| 823 |
+
usage_percent = (used / total) * 100
|
| 824 |
+
if usage_percent > 90:
|
| 825 |
+
st.error(f"β οΈ Disk usage: {usage_percent:.1f}%")
|
| 826 |
+
elif usage_percent > 75:
|
| 827 |
+
st.warning(f"β οΈ Disk usage: {usage_percent:.1f}%")
|
| 828 |
+
else:
|
| 829 |
+
st.success(f"β
Disk usage: {usage_percent:.1f}%")
|
| 830 |
+
|
| 831 |
+
except Exception as e:
|
| 832 |
+
st.error(f"Cannot check disk usage: {e}")
|
| 833 |
+
|
| 834 |
+
# Initialize system button
|
| 835 |
+
if st.button("π§ Initialize System", help="Run system initialization if components are missing"):
|
| 836 |
+
with st.spinner("Running system initialization..."):
|
| 837 |
+
try:
|
| 838 |
result = subprocess.run(
|
| 839 |
+
[sys.executable, "/app/initialize_system.py"],
|
| 840 |
+
capture_output=True,
|
| 841 |
+
text=True,
|
| 842 |
+
timeout=300
|
| 843 |
)
|
| 844 |
+
|
| 845 |
if result.returncode == 0:
|
| 846 |
+
st.success("β
System initialization completed successfully!")
|
| 847 |
+
st.code(result.stdout)
|
| 848 |
+
time.sleep(2)
|
| 849 |
+
st.rerun()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 850 |
else:
|
| 851 |
+
st.error("β System initialization failed")
|
| 852 |
+
st.code(result.stderr)
|
| 853 |
+
|
| 854 |
+
except subprocess.TimeoutExpired:
|
| 855 |
+
st.error("β° Initialization timed out")
|
| 856 |
+
except Exception as e:
|
| 857 |
+
st.error(f"β Initialization error: {e}")
|
| 858 |
+
|
| 859 |
+
# Auto-refresh logic
|
| 860 |
+
if st.session_state.auto_refresh:
|
| 861 |
+
time_since_refresh = datetime.now() - st.session_state.last_refresh
|
| 862 |
+
if time_since_refresh > timedelta(seconds=app_manager.config['refresh_interval']):
|
| 863 |
+
st.session_state.last_refresh = datetime.now()
|
| 864 |
+
st.rerun()
|
| 865 |
+
|
| 866 |
+
# Run main application
|
| 867 |
+
if __name__ == "__main__":
|
| 868 |
+
main()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|