ahmadsanafarooq commited on
Commit
abd20b2
·
verified ·
1 Parent(s): f774800

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +582 -217
app.py CHANGED
@@ -587,7 +587,7 @@ def show_auth_page():
587
  # Re-adding the original Empathetic Chatbot feature from previous response for completeness
588
  st.markdown("""
589
  <div class="feature-item">
590
- <span class="icon-img">💬</span> <h3>Empathetic Chatbot</h3>
591
  <p>Connect with an AI that listens and understands your feelings.</p>
592
  </div>
593
  """, unsafe_allow_html=True)
@@ -785,40 +785,330 @@ def is_crisis(text):
785
 
786
  def show_admin_dashboard():
787
  """Admin dashboard for monitoring users and app usage"""
788
- st.set_page_config(page_title="DilBot Admin Dashboard", page_icon="👑")
789
- # --- PASTE YOUR CUSTOM CSS HERE FOR THE ADMIN DASHBOARD ---
 
790
  st.markdown("""
791
  <style>
792
- /* Example: Styling for the admin dashboard */
793
  .stApp {
794
- background-color: #ffffff; /* Clean white background */
 
 
 
 
795
  }
796
- /* Metric styling */
797
- [data-testid="stMetricValue"] {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
798
  font-size: 2em;
799
- font-weight: bold;
800
- color: #4CAF50; /* Green color for metrics */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
801
  }
802
  [data-testid="stMetricLabel"] {
803
- color: #555;
 
 
 
804
  }
805
- /* Table row styling (for expanders) */
806
- .stExpander {
807
- border: 1px solid #eee;
808
- border-radius: 8px;
809
- margin-bottom: 10px;
810
- box-shadow: 0 2px 5px rgba(0,0,0,0.05);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
811
  }
812
  </style>
813
  """, unsafe_allow_html=True)
814
  # --- END CUSTOM CSS ---
 
815
  # Header
816
  col1, col2 = st.columns([4, 1])
817
  with col1:
818
- st.title("👑 DilBot Admin Dashboard")
819
- st.markdown("Monitor users, conversations, and app analytics")
820
  with col2:
821
- if st.button("Logout", key="admin_logout"):
 
822
  st.session_state.authenticated = False
823
  st.session_state.username = None
824
  st.session_state.is_admin = False
@@ -831,223 +1121,298 @@ def show_admin_dashboard():
831
  stats = get_admin_stats()
832
 
833
  # Overview metrics
834
- st.header("📊 Overview")
835
- col1, col2, col3, col4 = st.columns(4)
836
-
837
- with col1:
838
- st.metric("Total Users", stats["total_users"])
839
- with col2:
840
- st.metric("Active Users", stats["active_users"])
841
- with col3:
842
- st.metric("New Today", stats["users_today"])
843
- with col4:
844
- st.metric("Total Conversations", stats["total_conversations"])
 
845
 
846
  # User registration trend
847
- st.header("📈 User Registration Trend")
848
- if stats["user_details"]:
849
- # Create registration data
850
- reg_data = {}
851
- for user in stats["user_details"]:
852
- date = user["joined"]
853
- reg_data[date] = reg_data.get(date, 0) + 1
854
-
855
- chart_data = [{"date": date, "registrations": count} for date, count in sorted(reg_data.items())]
856
-
857
- if chart_data:
858
- chart = alt.Chart(alt.Data(values=chart_data)).mark_line(point=True).encode(
859
- x=alt.X('date:T', title='Date'),
860
- y=alt.Y('registrations:Q', title='New Registrations'),
861
- tooltip=['date:T', 'registrations:Q']
862
- ).properties(
863
- width=700,
864
- height=300,
865
- title="Daily User Registrations"
866
- )
867
- st.altair_chart(chart, use_container_width=True)
 
 
 
 
 
868
 
869
  # Detailed user table
870
- st.header("👥 User Details")
871
-
872
- # Search and filter
873
- col1, col2 = st.columns([2, 1])
874
- with col1:
875
- search_term = st.text_input("🔍 Search users", placeholder="Search by username or email")
876
- with col2:
877
- min_conversations = st.number_input("Min conversations", min_value=0, value=0)
878
-
879
- # Filter users
880
- filtered_users = stats["user_details"]
881
- if search_term:
882
- filtered_users = [u for u in filtered_users if
883
- search_term.lower() in u["username"].lower() or
884
- search_term.lower() in u["email"].lower()]
885
-
886
- if min_conversations > 0:
887
- filtered_users = [u for u in filtered_users if u["conversations"] >= min_conversations]
888
-
889
- # Display user table
890
- if filtered_users:
891
- for user in filtered_users:
892
- with st.expander(f"👤 {user['username']} ({user['conversations']} conversations)"):
893
- col1, col2 = st.columns(2)
894
-
895
- with col1:
896
- st.write(f"**Email:** {user['email']}")
897
- st.write(f"**Joined:** {user['joined']}")
898
- st.write(f"**Last Activity:** {user['last_activity']}")
899
-
900
- with col2:
901
- st.write(f"**Conversations:** {user['conversations']}")
902
- st.write(f"**Most Common Emotion:** {user['most_common_emotion']}")
903
 
904
- # Show emotion breakdown
905
- if user['emotions_breakdown']:
906
- st.write("**Emotion Breakdown:**")
907
- for emotion, count in user['emotions_breakdown'].items():
908
- st.write(f" - {emotion.capitalize()}: {count}")
909
-
910
- # Quick actions
911
- col1, col2, col3 = st.columns(3)
912
- with col1:
913
- if st.button(f"View Journal", key=f"view_{user['username']}"):
914
- # Show user's recent conversations
915
- user_journal = load_user_journal(user['username'])
916
- if user_journal:
917
- st.subheader(f"Recent conversations for {user['username']}")
918
- for entry in user_journal[-5:]:
919
- st.text_area(
920
- f"{entry['date']} - {entry['emotion'].capitalize()}",
921
- f"User: {entry['user_input']}\nDilBot: {entry['response']}",
922
- height=100,
923
- disabled=True
924
- )
925
  else:
926
- st.info("No conversations found")
927
-
928
- with col2:
929
- reset_key = f"reset_{user['username']}"
930
- confirm_key = f"confirm_{user['username']}"
931
 
932
- if st.button(f"Reset Data", key=reset_key):
933
- st.session_state[confirm_key] = True # Flag to show confirmation
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
934
 
935
- if st.session_state.get(confirm_key, False):
936
- st.warning(f"Are you sure you want to reset data for {user['username']}?")
937
- if st.button(f"Yes, Reset {user['username']}", key=f"confirm_reset_{user['username']}"):
938
- # Clear user's journal
939
- journal_path = get_user_file_path(user['username'], "journal.json")
940
- if os.path.exists(journal_path):
941
- os.remove(journal_path)
942
-
943
- log_admin_activity("User Data Reset", f"Reset data for {user['username']}")
944
- st.success(f"Data reset for {user['username']}")
945
- st.session_state[confirm_key] = False # Reset confirmation flag
946
- st.rerun()
947
- if st.button(f"Cancel", key=f"cancel_reset_{user['username']}"):
948
- st.session_state[confirm_key] = False # Cancel confirmation
949
- st.rerun()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
950
 
951
- else:
952
- st.info("No users found matching your criteria")
953
 
954
  # System Analytics
955
- st.header("🔧 System Analytics")
956
-
957
- col1, col2 = st.columns(2)
958
-
959
- with col1:
960
- st.subheader("Emotion Distribution (All Users)")
961
- # Aggregate all emotions
962
- all_emotions = {}
963
- for user in stats["user_details"]:
964
- for emotion, count in user['emotions_breakdown'].items():
965
- all_emotions[emotion] = all_emotions.get(emotion, 0) + count
966
 
967
- if all_emotions:
968
- emotion_chart_data = [{"emotion": emotion.capitalize(), "count": count}
969
- for emotion, count in all_emotions.items()]
 
 
 
 
970
 
971
- emotion_chart = alt.Chart(alt.Data(values=emotion_chart_data)).mark_bar().encode(
972
- x=alt.X('emotion:N', title='Emotion'),
973
- y=alt.Y('count:Q', title='Frequency'),
974
- color=alt.Color('emotion:N', legend=None),
975
- tooltip=['emotion:N', 'count:Q']
976
- ).properties(
977
- width=400,
978
- height=300,
979
- title="Overall Emotion Distribution"
980
- )
981
- st.altair_chart(emotion_chart, use_container_width=True)
982
-
983
- with col2:
984
- st.subheader("User Activity Levels")
985
- activity_levels = {"Inactive (0)": 0, "Light (1-5)": 0, "Moderate (6-20)": 0, "Heavy (21+)": 0}
986
-
987
- for user in stats["user_details"]:
988
- conv_count = user["conversations"]
989
- if conv_count == 0:
990
- activity_levels["Inactive (0)"] += 1
991
- elif conv_count <= 5:
992
- activity_levels["Light (1-5)"] += 1
993
- elif conv_count <= 20:
994
- activity_levels["Moderate (6-20)"] += 1
995
  else:
996
- activity_levels["Heavy (21+)"] += 1
997
 
998
- activity_data = [{"level": level, "users": count} for level, count in activity_levels.items()]
999
-
1000
- activity_chart = alt.Chart(alt.Data(values=activity_data)).mark_arc().encode(
1001
- theta=alt.Theta('users:Q'),
1002
- color=alt.Color('level:N', title="Activity Level"),
1003
- tooltip=['level:N', 'users:Q']
1004
- ).properties(
1005
- width=300,
1006
- height=300,
1007
- title="User Activity Distribution"
1008
- )
1009
- st.altair_chart(activity_chart, use_container_width=True)
1010
-
1011
- # Admin logs
1012
- st.header("📋 Admin Activity Logs")
1013
- admin_logs = get_admin_logs()
1014
-
1015
- if admin_logs:
1016
- # Show recent admin activities
1017
- for log_entry in reversed(admin_logs[-10:]): # Last 10 activities
1018
- timestamp = datetime.datetime.fromisoformat(log_entry["timestamp"])
1019
- st.text(f"{timestamp.strftime('%Y-%m-%d %H:%M:%S')} - {log_entry['action']}: {log_entry['details']}")
1020
- else:
1021
- st.info("No admin activities logged yet")
1022
-
1023
- # Export functionality
1024
- st.header("Data Export")
1025
- col1, col2 = st.columns(2)
1026
-
1027
- with col1:
1028
- if st.button("Export User Data (JSON)"):
1029
- export_data = {
1030
- "export_timestamp": str(datetime.datetime.now()),
1031
- "statistics": stats,
1032
- "admin_logs": admin_logs
1033
- }
1034
 
1035
- st.download_button(
1036
- label="Download User Data",
1037
- data=json.dumps(export_data, indent=4),
1038
- file_name=f"dilbot_export_{datetime.date.today()}.json",
1039
- mime="application/json"
1040
- )
1041
 
1042
- log_admin_activity("Data Export", "Exported user data")
 
 
 
 
 
 
 
 
 
 
 
 
1043
 
1044
- with col2:
1045
- if st.button("Clear Admin Logs"):
1046
- admin_log_path = "data/admin_log.json"
1047
- if os.path.exists(admin_log_path):
1048
- os.remove(admin_log_path)
1049
- st.success("Admin logs cleared!")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1050
  st.rerun()
 
 
 
 
1051
 
1052
  def speak(text, username):
1053
  """Generate and play audio response"""
 
587
  # Re-adding the original Empathetic Chatbot feature from previous response for completeness
588
  st.markdown("""
589
  <div class="feature-item">
590
+ <span class="icon-img"></span> <h3>Empathetic Chatbot</h3>
591
  <p>Connect with an AI that listens and understands your feelings.</p>
592
  </div>
593
  """, unsafe_allow_html=True)
 
785
 
786
  def show_admin_dashboard():
787
  """Admin dashboard for monitoring users and app usage"""
788
+ st.set_page_config(page_title="DilBot Admin Dashboard", page_icon="👑", layout="wide")
789
+
790
+ # --- ENHANCED CUSTOM CSS FOR ADMIN DASHBOARD (Consistent with main app) ---
791
  st.markdown("""
792
  <style>
793
+ /* Global Styles & Background (Consistent with main app) */
794
  .stApp {
795
+ background: linear-gradient(to bottom right, #f8f9fa, #e9ecef);
796
+ color: #343a40;
797
+ font-family: 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
798
+ -webkit-font-smoothing: antialiased;
799
+ padding-top: 2rem;
800
  }
801
+
802
+ /* Streamlit's main block container for content centering and width */
803
+ .main .block-container {
804
+ max-width: 1200px;
805
+ padding-top: 2rem;
806
+ padding-bottom: 2rem;
807
+ padding-left: 1rem;
808
+ padding-right: 1rem;
809
+ }
810
+
811
+ /* Hide Streamlit's header/footer if you want a fully custom layout */
812
+ .stApp > header {
813
+ display: none;
814
+ }
815
+ .stApp > footer {
816
+ display: none;
817
+ }
818
+
819
+ /* Header & Titles (Consistent with main app) */
820
+ h1, h2, h3, h4, h5, h6 {
821
+ color: #212529;
822
+ margin-top: 1.8rem;
823
+ margin-bottom: 0.9rem;
824
+ font-weight: 700;
825
+ }
826
+ h1 {
827
+ font-size: 2.8em;
828
+ font-weight: 800;
829
+ color: #5d6dbe;
830
+ letter-spacing: -0.8px;
831
+ text-shadow: 1px 1px 2px rgba(0,0,0,0.05);
832
+ }
833
+ .header-tagline { /* Added for general sub-titles */
834
+ font-size: 1.2em;
835
+ color: #6c757d;
836
+ margin-top: -0.5rem;
837
+ margin-bottom: 2.5rem;
838
+ font-weight: 400;
839
+ }
840
+ h2 {
841
  font-size: 2em;
842
+ font-weight: 700;
843
+ border-bottom: 2px solid #e9ecef;
844
+ padding-bottom: 0.7rem;
845
+ margin-bottom: 2rem;
846
+ color: #343a40;
847
+ }
848
+ h3 {
849
+ font-size: 1.6em;
850
+ font-weight: 600;
851
+ color: #495057;
852
+ margin-top: 2rem;
853
+ margin-bottom: 1.2rem;
854
+ }
855
+ h4 { /* Added for sub-sections */
856
+ font-size: 1.3em;
857
+ font-weight: 600;
858
+ color: #343a40;
859
+ margin-top: 1.5rem;
860
+ margin-bottom: 1rem;
861
+ }
862
+ h5 {
863
+ font-size: 1.1em;
864
+ font-weight: 600;
865
+ color: #495057;
866
+ margin-bottom: 1rem;
867
+ }
868
+
869
+ /* Metrics (Consistent with main app) */
870
+ [data-testid="stMetric"] {
871
+ background-color: #ffffff;
872
+ border: 1px solid #dee2e6;
873
+ border-radius: 12px;
874
+ padding: 20px;
875
+ box-shadow: 0 8px 20px rgba(0,0,0,0.08);
876
+ text-align: center;
877
+ margin-bottom: 1.5rem;
878
+ transition: transform 0.2s ease-in-out, box-shadow 0.2s ease-in-out;
879
+ }
880
+ [data-testid="stMetric"]:hover {
881
+ transform: translateY(-5px);
882
+ box-shadow: 0 12px 30px rgba(0,0,0,0.12);
883
  }
884
  [data-testid="stMetricLabel"] {
885
+ font-size: 1em;
886
+ color: #6c757d;
887
+ margin-bottom: 8px;
888
+ font-weight: 500;
889
  }
890
+ [data-testid="stMetricValue"] { /* Overriding the admin-specific green with main app blue */
891
+ font-size: 2.8em;
892
+ font-weight: 800;
893
+ color: #5d6dbe; /* Main app's primary blue */
894
+ }
895
+
896
+ /* Buttons (Consistent with main app) */
897
+ .stButton>button {
898
+ background-color: #5d6dbe;
899
+ color: white;
900
+ border: none;
901
+ border-radius: 10px;
902
+ padding: 12px 25px;
903
+ font-size: 1.1em;
904
+ font-weight: bold;
905
+ transition: background-color 0.3s ease, transform 0.2s ease, box-shadow 0.3s ease;
906
+ box-shadow: 0 5px 15px rgba(0,0,0,0.1);
907
+ cursor: pointer;
908
+ letter-spacing: 0.5px;
909
+ }
910
+ .stButton>button:hover {
911
+ background-color: #4a5c9d;
912
+ transform: translateY(-3px);
913
+ box-shadow: 0 8px 20px rgba(0,0,0,0.15);
914
+ }
915
+ .stButton>button:active {
916
+ background-color: #3b4b80;
917
+ transform: translateY(0);
918
+ box-shadow: 0 3px 10px rgba(0,0,0,0.1);
919
+ }
920
+ /* Logout button specific style */
921
+ [key="admin_logout"] > button {
922
+ background-color: #dc3545;
923
+ box-shadow: 0 5px 15px rgba(220,53,69,0.2);
924
+ }
925
+ [key="admin_logout"] > button:hover {
926
+ background-color: #c82333;
927
+ box-shadow: 0 8px 20px rgba(220,53,69,0.3);
928
+ }
929
+ /* "Back to Main App" button style */
930
+ [key="admin_back_btn"] > button {
931
+ background-color: #6c757d; /* Grey for back button */
932
+ box-shadow: 0 5px 15px rgba(108,117,125,0.2);
933
+ }
934
+ [key="admin_back_btn"] > button:hover {
935
+ background-color: #5a6268;
936
+ box-shadow: 0 8px 20px rgba(108,117,125,0.3);
937
+ }
938
+ /* Reset Data buttons for users */
939
+ .stButton > button[kind="secondary"] { /* Target buttons that are not primary, e.g., Reset/Confirm */
940
+ background-color: #ffc107; /* Warning yellow */
941
+ color: #343a40; /* Dark text on yellow */
942
+ border: none;
943
+ }
944
+ .stButton > button[kind="secondary"]:hover {
945
+ background-color: #e0a800; /* Darker yellow on hover */
946
+ transform: translateY(-2px);
947
+ }
948
+ .stButton > button[key*="confirm_reset_"] {
949
+ background-color: #dc3545; /* Danger red for confirm reset */
950
+ color: white;
951
+ }
952
+ .stButton > button[key*="confirm_reset_"]:hover {
953
+ background-color: #c82333;
954
+ }
955
+ .stButton > button[key*="cancel_reset_"] {
956
+ background-color: #6c757d; /* Grey for cancel */
957
+ color: white;
958
+ }
959
+ .stButton > button[key*="cancel_reset_"]:hover {
960
+ background-color: #5a6268;
961
+ }
962
+
963
+
964
+ /* Text Inputs and Text Areas (Consistent with main app) */
965
+ .stTextInput>div>div>input, .stTextArea>div>div>textarea, .stNumberInput>div>div>input {
966
+ border-radius: 10px;
967
+ border: 1px solid #ced4da;
968
+ padding: 14px 18px;
969
+ font-size: 1.05em;
970
+ color: #343a40;
971
+ background-color: #fcfcfc;
972
+ box-shadow: inset 0 2px 5px rgba(0,0,0,0.03);
973
+ transition: border-color 0.3s ease, box-shadow 0.3s ease;
974
+ }
975
+ .stTextInput>div>div>input:focus, .stTextArea>div>div>textarea:focus, .stNumberInput>div>div>input:focus {
976
+ border-color: #5d6dbe;
977
+ box-shadow: 0 0 0 0.25rem rgba(93,109,190,0.25);
978
+ outline: none;
979
+ }
980
+ .stTextInput>label, .stTextArea>label, .stNumberInput>label {
981
+ font-weight: 600;
982
+ color: #495057;
983
+ margin-bottom: 0.6rem;
984
+ }
985
+
986
+ /* Information, Success, Error, Warning Boxes (Consistent with main app) */
987
+ [data-testid="stAlert"] {
988
+ border-radius: 10px;
989
+ padding: 18px 25px;
990
+ margin-bottom: 1.8rem;
991
+ font-size: 1.05em;
992
+ line-height: 1.6;
993
+ text-align: left;
994
+ box-shadow: 0 2px 10px rgba(0,0,0,0.05);
995
+ }
996
+ [data-testid="stAlert"] .streamlit-warning {
997
+ background-color: #fef7e0; color: #7a5f00; border-left: 6px solid #ffcc00;
998
+ }
999
+ [data-testid="stAlert"] .streamlit-success {
1000
+ background-color: #e6ffe6;
1001
+ border-left: 6px solid #4CAF50;
1002
+ color: black !important;
1003
+ }
1004
+ [data-testid="stAlert"] .streamlit-success p,
1005
+ [data-testid="stAlert"] .streamlit-success span,
1006
+ [data-testid="stAlert"] .streamlit-success div,
1007
+ [data-testid="stAlert"] .streamlit-success strong {
1008
+ color: black !important;
1009
+ -webkit-text-fill-color: black !important;
1010
+ opacity: 1 !important;
1011
+ }
1012
+ [data-testid="stAlert"] .streamlit-error {
1013
+ background-color: #ffe6e6; color: #8c0a0a; border-left: 6px solid #e74c3c;
1014
+ }
1015
+ .stInfo { /* Ensure st.info is also styled consistently */
1016
+ background-color: #e6f7ff;
1017
+ border-left: 6px solid #64b5f6;
1018
+ color: black !important;
1019
+ border-radius: 10px;
1020
+ padding: 18px 25px;
1021
+ margin-top: 2rem;
1022
+ font-style: italic;
1023
+ font-size: 1.05em;
1024
+ box-shadow: 0 2px 10px rgba(0,0,0,0.05);
1025
+ }
1026
+ .stInfo p, .stInfo span, .stInfo strong, .stInfo div {
1027
+ color: black !important;
1028
+ -webkit-text-fill-color: black !important;
1029
+ opacity: 1 !important;
1030
+ }
1031
+
1032
+
1033
+ /* Container for sections (Consistent with main app) */
1034
+ .stContainer {
1035
+ border-radius: 12px;
1036
+ box-shadow: 0 8px 20px rgba(0,0,0,0.08);
1037
+ border: 1px solid #e0e0e0;
1038
+ padding: 2rem;
1039
+ margin-bottom: 2.5rem;
1040
+ background-color: #ffffff;
1041
+ }
1042
+ .stContainer.has-border {
1043
+ border: 1px solid #e0e0e0;
1044
+ }
1045
+
1046
+ /* Expander styling (Consistent with main app) */
1047
+ .streamlit-expanderHeader {
1048
+ background-color: #ffffff;
1049
+ border: 1px solid #e0e0e0;
1050
+ border-radius: 10px;
1051
+ padding: 12px 18px;
1052
+ margin-bottom: 0.8rem;
1053
+ cursor: pointer;
1054
+ transition: background-color 0.2s ease, box-shadow 0.2s ease;
1055
+ box-shadow: 0 4px 10px rgba(0,0,0,0.05);
1056
+ }
1057
+ .streamlit-expanderHeader:hover {
1058
+ background-color: #f7f9fb;
1059
+ box-shadow: 0 6px 15px rgba(0,0,0,0.08);
1060
+ }
1061
+ .streamlit-expanderHeader > div > div > p {
1062
+ font-weight: 600;
1063
+ color: #343a40;
1064
+ font-size: 1.05em;
1065
+ }
1066
+ .streamlit-expanderContent {
1067
+ background-color: #f8f9fa;
1068
+ border-left: 4px solid #ced4da;
1069
+ padding: 15px 20px;
1070
+ border-bottom-left-radius: 10px;
1071
+ border-bottom-right-radius: 10px;
1072
+ margin-top: -10px;
1073
+ box-shadow: inset 0 2px 5px rgba(0,0,0,0.03);
1074
+ line-height: 1.6;
1075
+ }
1076
+ /* Ensure text within expander content is black */
1077
+ .streamlit-expanderContent p,
1078
+ .streamlit-expanderContent span,
1079
+ .streamlit-expanderContent div,
1080
+ .streamlit-expanderContent strong {
1081
+ color: black !important;
1082
+ -webkit-text-fill-color: black !important;
1083
+ opacity: 1 !important;
1084
+ }
1085
+
1086
+ /* Table styling (for dataframes) */
1087
+ .stDataFrame {
1088
+ border: 1px solid #e9ecef;
1089
+ border-radius: 10px;
1090
+ overflow: hidden; /* Ensures rounded corners */
1091
+ box-shadow: 0 4px 10px rgba(0,0,0,0.05);
1092
+ }
1093
+
1094
+ /* General text color in case Streamlit overrides */
1095
+ div[data-testid="stVerticalBlock"] div > p,
1096
+ div[data-testid="stHorizontalBlock"] div > p,
1097
+ div[data-testid="stText"] p {
1098
+ color: #343a40 !important;
1099
  }
1100
  </style>
1101
  """, unsafe_allow_html=True)
1102
  # --- END CUSTOM CSS ---
1103
+
1104
  # Header
1105
  col1, col2 = st.columns([4, 1])
1106
  with col1:
1107
+ st.markdown(f"<h1>👑 DilBot Admin Dashboard</h1>", unsafe_allow_html=True)
1108
+ st.markdown("<p class='header-tagline'>Monitor users, conversations, and app analytics</p>", unsafe_allow_html=True)
1109
  with col2:
1110
+ st.markdown("<div style='height: 40px;'></div>", unsafe_allow_html=True) # Spacer
1111
+ if st.button("Logout", key="admin_logout", use_container_width=True):
1112
  st.session_state.authenticated = False
1113
  st.session_state.username = None
1114
  st.session_state.is_admin = False
 
1121
  stats = get_admin_stats()
1122
 
1123
  # Overview metrics
1124
+ st.markdown("<h2>📊 Overview</h2>", unsafe_allow_html=True)
1125
+ with st.container(border=True): # Wrap metrics in a container for styling
1126
+ col1, col2, col3, col4 = st.columns(4)
1127
+
1128
+ with col1:
1129
+ st.metric("Total Users", stats["total_users"])
1130
+ with col2:
1131
+ st.metric("Active Users", stats["active_users"])
1132
+ with col3:
1133
+ st.metric("New Today", stats["users_today"])
1134
+ with col4:
1135
+ st.metric("Total Conversations", stats["total_conversations"])
1136
 
1137
  # User registration trend
1138
+ st.markdown("<h2>📈 User Registration Trend</h2>", unsafe_allow_html=True)
1139
+ with st.container(border=True): # Wrap chart in a container
1140
+ if stats["user_details"]:
1141
+ # Create registration data
1142
+ reg_data = {}
1143
+ for user in stats["user_details"]:
1144
+ date = user["joined"]
1145
+ reg_data[date] = reg_data.get(date, 0) + 1
1146
+
1147
+ # Convert to DataFrame for Altair compatibility and sorting
1148
+ chart_data_list = [{"date": date, "registrations": count} for date, count in reg_data.items()]
1149
+ chart_df = pd.DataFrame(chart_data_list).sort_values("date")
1150
+
1151
+ if not chart_df.empty:
1152
+ chart = alt.Chart(chart_df).mark_line(point=True).encode(
1153
+ x=alt.X('date:T', title='Date'),
1154
+ y=alt.Y('registrations:Q', title='New Registrations'),
1155
+ tooltip=[alt.Tooltip('date:T', title='Date'), 'registrations:Q']
1156
+ ).properties(
1157
+ title="Daily User Registrations"
1158
+ ).interactive() # Make interactive for zoom/pan
1159
+ st.altair_chart(chart, use_container_width=True)
1160
+ else:
1161
+ st.info("No registration data available to display trend.")
1162
+ else:
1163
+ st.info("No user data available to display registration trend.")
1164
 
1165
  # Detailed user table
1166
+ st.markdown("<h2>👥 User Details</h2>", unsafe_allow_html=True)
1167
+ with st.container(border=True): # Wrap user details section in a container
1168
+ # Search and filter
1169
+ col1, col2 = st.columns([2, 1])
1170
+ with col1:
1171
+ search_term = st.text_input("🔍 Search users", placeholder="Search by username or email", label_visibility="visible")
1172
+ with col2:
1173
+ min_conversations = st.number_input("Min conversations", min_value=0, value=0, label_visibility="visible")
1174
+
1175
+ # Filter users
1176
+ filtered_users = stats["user_details"]
1177
+ if search_term:
1178
+ filtered_users = [u for u in filtered_users if
1179
+ search_term.lower() in u["username"].lower() or
1180
+ search_term.lower() in u["email"].lower()]
1181
+
1182
+ if min_conversations > 0:
1183
+ filtered_users = [u for u in filtered_users if u["conversations"] >= min_conversations]
1184
+
1185
+ # Display user table
1186
+ if filtered_users:
1187
+ for user in filtered_users:
1188
+ with st.expander(f"👤 **{user['username']}** ({user['conversations']} conversations)"): # Bold username
1189
+ col1_detail, col2_detail = st.columns(2) # Renamed columns to avoid conflict
 
 
 
 
 
 
 
 
 
1190
 
1191
+ with col1_detail:
1192
+ st.write(f"**Email:** {user['email']}")
1193
+ st.write(f"**Joined:** {user['joined']}")
1194
+ st.write(f"**Last Activity:** {user['last_activity']}")
1195
+
1196
+ with col2_detail:
1197
+ st.write(f"**Conversations:** {user['conversations']}")
1198
+ st.write(f"**Most Common Emotion:** {user['most_common_emotion'].capitalize()}") # Capitalize emotion
1199
+
1200
+ # Show emotion breakdown
1201
+ if user['emotions_breakdown']:
1202
+ st.write("**Emotion Breakdown:**")
1203
+ # Convert to DataFrame for a nicer table display
1204
+ emotions_df = pd.DataFrame([{"Emotion": e.capitalize(), "Count": c} for e, c in user['emotions_breakdown'].items()])
1205
+ st.dataframe(emotions_df, hide_index=True, use_container_width=True)
 
 
 
 
 
 
1206
  else:
1207
+ st.info("No emotion data for this user.")
 
 
 
 
1208
 
1209
+ st.markdown("---") # Separator for actions
1210
+ # Quick actions
1211
+ col1_actions, col2_actions, col3_actions = st.columns(3) # Renamed columns
1212
+ with col1_actions:
1213
+ if st.button(f"View Journal", key=f"view_{user['username']}", use_container_width=True):
1214
+ # Show user's recent conversations
1215
+ user_journal = load_user_journal(user['username'])
1216
+ if user_journal:
1217
+ st.subheader(f"Recent conversations for {user['username']}")
1218
+ for entry in user_journal[-5:]: # Last 5 activities
1219
+ st.text_area(
1220
+ f"{entry['date']} - {entry['emotion'].capitalize()} ({round(entry['confidence'] * 100)}% confidence)",
1221
+ f"User: {entry['user_input']}\nDilBot: {entry['response']}",
1222
+ height=100,
1223
+ disabled=True,
1224
+ key=f"journal_entry_{user['username']}_{entry['date']}_{hash(entry['user_input'])}" # Unique key for text_area
1225
+ )
1226
+ else:
1227
+ st.info("No conversations found for this user.")
1228
 
1229
+ with col2_actions:
1230
+ reset_key = f"reset_{user['username']}"
1231
+ confirm_key = f"confirm_{user['username']}"
1232
+
1233
+ # Use a persistent state for confirmation
1234
+ if confirm_key not in st.session_state:
1235
+ st.session_state[confirm_key] = False
1236
+
1237
+ if st.button(f"Reset Data", key=reset_key, use_container_width=True):
1238
+ st.session_state[confirm_key] = True # Flag to show confirmation
1239
+ st.rerun() # Rerun to display confirmation message/buttons
1240
+
1241
+ if st.session_state.get(confirm_key, False):
1242
+ st.warning(f"Are you sure you want to reset ALL data for {user['username']}? This action is irreversible.")
1243
+ col_confirm_yes, col_confirm_no = st.columns(2)
1244
+ with col_confirm_yes:
1245
+ if st.button(f"Yes, Reset", key=f"confirm_reset_{user['username']}", use_container_width=True):
1246
+ # Clear user's journal
1247
+ journal_path = get_user_file_path(user['username'], "journal.json")
1248
+ if os.path.exists(journal_path):
1249
+ os.remove(journal_path)
1250
+ # Also remove vectorstore if it exists
1251
+ vectorstore_path = get_user_file_path(user['username'], "faiss_index")
1252
+ if os.path.exists(vectorstore_path):
1253
+ import shutil
1254
+ shutil.rmtree(vectorstore_path) # Remove directory for FAISS
1255
+
1256
+ log_admin_activity("User Data Reset", f"Reset data for {user['username']}")
1257
+ st.success(f"Data reset for {user['username']}!")
1258
+ st.session_state[confirm_key] = False # Reset confirmation flag
1259
+ st.rerun()
1260
+ with col_confirm_no:
1261
+ if st.button(f"Cancel", key=f"cancel_reset_{user['username']}", use_container_width=True):
1262
+ st.session_state[confirm_key] = False # Cancel confirmation
1263
+ st.info(f"Reset for {user['username']} cancelled.")
1264
+ st.rerun() # Rerun to hide confirmation buttons
1265
+
1266
+ with col3_actions:
1267
+ # Placeholder for other actions like "Deactivate User"
1268
+ st.button("Deactivate User (WIP)", key=f"deactivate_{user['username']}", disabled=True, use_container_width=True)
1269
 
1270
+ else:
1271
+ st.info("No users found matching your criteria.")
1272
 
1273
  # System Analytics
1274
+ st.markdown("<h2>🔧 System Analytics</h2>", unsafe_allow_html=True)
1275
+ with st.container(border=True): # Wrap system analytics in a container
1276
+ col1_analytics, col2_analytics = st.columns(2) # Renamed columns
 
 
 
 
 
 
 
 
1277
 
1278
+ with col1_analytics:
1279
+ st.markdown("<h4>Emotion Distribution (All Users)</h4>", unsafe_allow_html=True)
1280
+ # Aggregate all emotions
1281
+ all_emotions = {}
1282
+ for user in stats["user_details"]:
1283
+ for emotion, count in user['emotions_breakdown'].items():
1284
+ all_emotions[emotion] = all_emotions.get(emotion, 0) + count
1285
 
1286
+ if all_emotions:
1287
+ emotion_chart_data = [{"emotion": emotion.capitalize(), "count": count}
1288
+ for emotion, count in all_emotions.items()]
1289
+
1290
+ emotion_chart = alt.Chart(pd.DataFrame(emotion_chart_data)).mark_bar().encode(
1291
+ x=alt.X('emotion:N', title='Emotion', sort='-y'), # Sort by count descending
1292
+ y=alt.Y('count:Q', title='Frequency'),
1293
+ color=alt.Color('emotion:N', legend=None, scale=alt.Scale(
1294
+ range=['#4CAF50', '#FFC107', '#E74C3C', '#3498DB', '#9B59B6', '#1ABC9C', '#FF5733'])), # More colors
1295
+ tooltip=['emotion:N', 'count:Q']
1296
+ ).properties(
1297
+ title="Overall Emotion Distribution"
1298
+ ).interactive()
1299
+ st.altair_chart(emotion_chart, use_container_width=True)
 
 
 
 
 
 
 
 
 
 
1300
  else:
1301
+ st.info("No emotion data available for overall analysis.")
1302
 
1303
+ with col2_analytics:
1304
+ st.markdown("<h4>User Activity Levels</h4>", unsafe_allow_html=True)
1305
+ activity_levels = {"Inactive (0)": 0, "Light (1-5)": 0, "Moderate (6-20)": 0, "Heavy (21+)": 0}
1306
+
1307
+ for user in stats["user_details"]:
1308
+ conv_count = user["conversations"]
1309
+ if conv_count == 0:
1310
+ activity_levels["Inactive (0)"] += 1
1311
+ elif conv_count <= 5:
1312
+ activity_levels["Light (1-5)"] += 1
1313
+ elif conv_count <= 20:
1314
+ activity_levels["Moderate (6-20)"] += 1
1315
+ else:
1316
+ activity_levels["Heavy (21+)"] += 1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1317
 
1318
+ activity_data = [{"level": level, "users": count} for level, count in activity_levels.items()]
 
 
 
 
 
1319
 
1320
+ if activity_data:
1321
+ activity_chart = alt.Chart(pd.DataFrame(activity_data)).mark_arc(outerRadius=120, innerRadius=80).encode( # Donut chart
1322
+ theta=alt.Theta('users:Q'),
1323
+ color=alt.Color('level:N', title="Activity Level", scale=alt.Scale(
1324
+ range=['#6c757d', '#64b5f6', '#5d6dbe', '#4a5c9d'])), # Consistent color scheme
1325
+ order=alt.Order('users:Q', sort='descending'),
1326
+ tooltip=['level:N', 'users:Q']
1327
+ ).properties(
1328
+ title="User Activity Distribution"
1329
+ ).interactive()
1330
+ st.altair_chart(activity_chart, use_container_width=True)
1331
+ else:
1332
+ st.info("No user activity data to display.")
1333
 
1334
+ # Admin logs
1335
+ st.markdown("<h2>📋 Admin Activity Logs</h2>", unsafe_allow_html=True)
1336
+ with st.container(border=True): # Wrap admin logs in a container
1337
+ admin_logs = get_admin_logs()
1338
+
1339
+ if admin_logs:
1340
+ st.subheader("Recent Admin Activities (Last 10)")
1341
+ # Display logs in a more readable format, perhaps a table or structured text
1342
+ for log_entry in reversed(admin_logs[-10:]): # Last 10 activities, reversed to show newest first
1343
+ timestamp = datetime.datetime.fromisoformat(log_entry["timestamp"])
1344
+ st.markdown(f"**{timestamp.strftime('%Y-%m-%d %H:%M:%S')}** - **{log_entry['action']}**: `{log_entry['details']}`")
1345
+ st.markdown("---") # Small separator
1346
+ else:
1347
+ st.info("No admin activities logged yet.")
1348
+
1349
+ # Data Export and Admin Log Clearing
1350
+ st.markdown("<h2>💾 Data Management</h2>", unsafe_allow_html=True)
1351
+ with st.container(border=True): # Wrap data export in a container
1352
+ col1_export, col2_export = st.columns(2) # Renamed columns
1353
+
1354
+ with col1_export:
1355
+ st.markdown("<h4>Export Application Data</h4>", unsafe_allow_html=True)
1356
+ if st.button("Export All Data (JSON)", key="export_data_btn", use_container_width=True):
1357
+ export_data = {
1358
+ "export_timestamp": str(datetime.datetime.now()),
1359
+ "statistics": stats,
1360
+ "admin_logs": admin_logs
1361
+ }
1362
+
1363
+ st.download_button(
1364
+ label="Download Exported Data",
1365
+ data=json.dumps(export_data, indent=4),
1366
+ file_name=f"dilbot_admin_export_{datetime.date.today().isoformat()}.json",
1367
+ mime="application/json",
1368
+ key="download_export_btn",
1369
+ use_container_width=True
1370
+ )
1371
+
1372
+ log_admin_activity("Data Export", "Initiated data export")
1373
+ st.success("Data export ready for download!")
1374
+
1375
+ with col2_export:
1376
+ st.markdown("<h4>Clear Admin Activity Logs</h4>", unsafe_allow_html=True)
1377
+ # Add a confirmation step for clearing logs
1378
+ clear_log_confirm_key = "clear_log_confirm"
1379
+ if clear_log_confirm_key not in st.session_state:
1380
+ st.session_state[clear_log_confirm_key] = False
1381
+
1382
+ if st.button("Clear Admin Logs", key="clear_admin_logs_btn", use_container_width=True):
1383
+ st.session_state[clear_log_confirm_key] = True
1384
+ st.rerun()
1385
+
1386
+ if st.session_state.get(clear_log_confirm_key, False):
1387
+ st.warning("Are you sure you want to clear ALL admin logs? This action is irreversible.")
1388
+ col_clear_yes, col_clear_no = st.columns(2)
1389
+ with col_clear_yes:
1390
+ if st.button("Yes, Clear Logs", key="confirm_clear_logs_btn", use_container_width=True):
1391
+ admin_log_path = "data/admin_log.json"
1392
+ if os.path.exists(admin_log_path):
1393
+ os.remove(admin_log_path)
1394
+ st.success("Admin logs cleared successfully!")
1395
+ log_admin_activity("Admin Logs Cleared", "All admin activity logs were cleared")
1396
+ st.session_state[clear_log_confirm_key] = False
1397
+ st.rerun()
1398
+ with col_clear_no:
1399
+ if st.button("Cancel", key="cancel_clear_logs_btn", use_container_width=True):
1400
+ st.session_state[clear_log_confirm_key] = False
1401
+ st.info("Clearing admin logs cancelled.")
1402
+ st.rerun()
1403
+
1404
+ st.markdown("---")
1405
+ # Back to main app button (aligned left in a container)
1406
+ with st.container():
1407
+ st.markdown("<div style='display: flex; justify-content: flex-start;'>", unsafe_allow_html=True)
1408
+ if st.button("⬅️ Back to Main App", key="admin_back_btn"):
1409
+ st.session_state.authenticated = True # Keep admin logged in
1410
+ st.session_state.is_admin = False # Temporarily set to False to show main app
1411
  st.rerun()
1412
+ st.markdown("</div>", unsafe_allow_html=True)
1413
+
1414
+
1415
+ st.markdown("<p class='footer-caption'>DilBot Admin Panel | Powered by Streamlit</p>", unsafe_allow_html=True)
1416
 
1417
  def speak(text, username):
1418
  """Generate and play audio response"""