Nipun Claude commited on
Commit
ab2f1d4
ยท
1 Parent(s): 11a5d91

Enhance VayuChat UI with professional improvements

Browse files

- Add enhanced processing indicators with contextual progress steps
- Implement quick action follow-up buttons after responses
- Improve error message display with better styling and user guidance
- Add response timestamps to assistant messages
- Optimize sidebar scrolling with custom scrollbar styling
- Complete follow-up prompt handling integration

All improvements maintain existing functionality while enhancing UX.

๐Ÿค– Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>

Files changed (1) hide show
  1. app.py +121 -16
app.py CHANGED
@@ -3,8 +3,8 @@ import os
3
  import json
4
  import pandas as pd
5
  import random
6
- from os.path import join
7
  from datetime import datetime
 
8
  from src import (
9
  preprocess_and_load_df,
10
  get_from_user,
@@ -62,6 +62,31 @@ st.markdown("""
62
  padding: 1rem;
63
  }
64
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
65
  /* Main title */
66
  .main-title {
67
  text-align: center;
@@ -676,22 +701,40 @@ def show_custom_response(response):
676
  import pandas as pd
677
  is_dataframe = isinstance(content, pd.DataFrame)
678
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
679
  # Assistant message with left alignment - reduced margins
680
- if not is_image_path and not is_dataframe:
681
  st.markdown(f"""
682
  <div style='display: flex; justify-content: flex-start; margin: 1rem 0;'>
683
  <div class='assistant-message'>
684
- <div class='assistant-info'>VayuChat</div>
685
  {content if isinstance(content, str) else str(content)}
686
  </div>
687
  </div>
688
  """, unsafe_allow_html=True)
689
  elif is_dataframe:
690
  # Display DataFrame with nice formatting
691
- st.markdown("""
692
  <div style='display: flex; justify-content: flex-start; margin: 1rem 0;'>
693
  <div class='assistant-message'>
694
- <div class='assistant-info'>VayuChat</div>
695
  Here are the results:
696
  </div>
697
  </div>
@@ -734,18 +777,29 @@ def show_custom_response(response):
734
  return {"is_image": False}
735
 
736
  def show_processing_indicator(model_name, question):
737
- """Show processing indicator with clear question and status"""
 
 
 
 
 
 
 
 
738
  st.markdown(f"""
739
  <div style='display: flex; justify-content: flex-start; margin: 1rem 0;'>
740
- <div class='processing-indicator'>
741
- <div style='font-size: 0.875rem; color: #6b7280; margin-bottom: 8px;'>๐Ÿค– VayuChat โ€ข Processing with {model_name}</div>
742
- <div style='background: rgba(255,255,255,0.9); padding: 0.75rem; border-radius: 8px; margin-bottom: 8px; border-left: 3px solid #3b82f6;'>
743
- <strong style='color: #1e293b;'>Your Question:</strong><br>
744
- <span style='color: #374151; font-size: 0.95rem;'>{question}</span>
745
- </div>
746
- <div style='display: flex; align-items: center; gap: 8px;'>
747
  <div style='width: 16px; height: 16px; border: 2px solid #3b82f6; border-top: 2px solid transparent; border-radius: 50%; animation: spin 1s linear infinite;'></div>
748
- <span style='color: #374151; font-style: italic;'>Analyzing data and generating response...</span>
 
 
 
 
 
 
 
 
749
  </div>
750
  </div>
751
  </div>
@@ -754,6 +808,10 @@ def show_processing_indicator(model_name, question):
754
  0% {{ transform: rotate(0deg); }}
755
  100% {{ transform: rotate(360deg); }}
756
  }}
 
 
 
 
757
  </style>
758
  """, unsafe_allow_html=True)
759
 
@@ -777,6 +835,45 @@ with chat_container:
777
  last_prompt = response.get("last_prompt", "")
778
  code = response.get("gen_code", "")
779
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
780
  if "feedback" in st.session_state.responses[response_id]:
781
  feedback_data = st.session_state.responses[response_id]["feedback"]
782
  st.markdown(f"""
@@ -842,6 +939,11 @@ prompt = st.chat_input("๐Ÿ’ฌ Ask about air quality trends, compare cities, or re
842
  if selected_prompt:
843
  prompt = selected_prompt
844
 
 
 
 
 
 
845
  # Handle new queries
846
  if prompt and not st.session_state.get("processing"):
847
  # Prevent duplicate processing
@@ -879,7 +981,8 @@ if st.session_state.get("processing"):
879
  "gen_code": "",
880
  "ex_code": "",
881
  "last_prompt": prompt,
882
- "error": "Invalid response format"
 
883
  }
884
 
885
  response.setdefault("role", "assistant")
@@ -888,6 +991,7 @@ if st.session_state.get("processing"):
888
  response.setdefault("ex_code", "")
889
  response.setdefault("last_prompt", prompt)
890
  response.setdefault("error", None)
 
891
 
892
  except Exception as e:
893
  response = {
@@ -896,7 +1000,8 @@ if st.session_state.get("processing"):
896
  "gen_code": "",
897
  "ex_code": "",
898
  "last_prompt": prompt,
899
- "error": str(e)
 
900
  }
901
 
902
  st.session_state.responses.append(response)
 
3
  import json
4
  import pandas as pd
5
  import random
 
6
  from datetime import datetime
7
+ from os.path import join
8
  from src import (
9
  preprocess_and_load_df,
10
  get_from_user,
 
62
  padding: 1rem;
63
  }
64
 
65
+ /* Optimize sidebar scrolling */
66
+ [data-testid="stSidebar"] > div:first-child {
67
+ height: 100vh;
68
+ overflow-y: auto;
69
+ padding-bottom: 2rem;
70
+ }
71
+
72
+ [data-testid="stSidebar"]::-webkit-scrollbar {
73
+ width: 6px;
74
+ }
75
+
76
+ [data-testid="stSidebar"]::-webkit-scrollbar-track {
77
+ background: #f1f1f1;
78
+ border-radius: 3px;
79
+ }
80
+
81
+ [data-testid="stSidebar"]::-webkit-scrollbar-thumb {
82
+ background: #c1c1c1;
83
+ border-radius: 3px;
84
+ }
85
+
86
+ [data-testid="stSidebar"]::-webkit-scrollbar-thumb:hover {
87
+ background: #a1a1a1;
88
+ }
89
+
90
  /* Main title */
91
  .main-title {
92
  text-align: center;
 
701
  import pandas as pd
702
  is_dataframe = isinstance(content, pd.DataFrame)
703
 
704
+ # Check for errors first and display them with special styling
705
+ error = response.get("error")
706
+ timestamp = response.get("timestamp", "")
707
+ timestamp_display = f" โ€ข {timestamp}" if timestamp else ""
708
+
709
+ if error:
710
+ st.markdown(f"""
711
+ <div style='display: flex; justify-content: flex-start; margin: 1rem 0;'>
712
+ <div class='assistant-message'>
713
+ <div class='assistant-info'>VayuChat{timestamp_display}</div>
714
+ <div class='error-message'>
715
+ โš ๏ธ <strong>Error:</strong> {error}
716
+ <br><br>
717
+ <em>๐Ÿ’ก Try rephrasing your question or being more specific about what you'd like to analyze.</em>
718
+ </div>
719
+ </div>
720
+ </div>
721
+ """, unsafe_allow_html=True)
722
  # Assistant message with left alignment - reduced margins
723
+ elif not is_image_path and not is_dataframe:
724
  st.markdown(f"""
725
  <div style='display: flex; justify-content: flex-start; margin: 1rem 0;'>
726
  <div class='assistant-message'>
727
+ <div class='assistant-info'>VayuChat{timestamp_display}</div>
728
  {content if isinstance(content, str) else str(content)}
729
  </div>
730
  </div>
731
  """, unsafe_allow_html=True)
732
  elif is_dataframe:
733
  # Display DataFrame with nice formatting
734
+ st.markdown(f"""
735
  <div style='display: flex; justify-content: flex-start; margin: 1rem 0;'>
736
  <div class='assistant-message'>
737
+ <div class='assistant-info'>VayuChat{timestamp_display}</div>
738
  Here are the results:
739
  </div>
740
  </div>
 
777
  return {"is_image": False}
778
 
779
  def show_processing_indicator(model_name, question):
780
+ """Show enhanced processing indicator with progress steps"""
781
+ # Estimate processing steps based on question type
782
+ if any(word in question.lower() for word in ["plot", "chart", "visualize", "show"]):
783
+ steps = ["๐Ÿ” Analyzing query", "๐Ÿ“Š Generating code", "๐Ÿ“ˆ Creating visualization"]
784
+ elif any(word in question.lower() for word in ["compare", "calculate", "rank"]):
785
+ steps = ["๐Ÿ” Analyzing query", "๐Ÿงฎ Processing calculations", "๐Ÿ“‹ Formatting results"]
786
+ else:
787
+ steps = ["๐Ÿ” Analyzing query", "๐Ÿค– Generating response", "โœจ Finalizing answer"]
788
+
789
  st.markdown(f"""
790
  <div style='display: flex; justify-content: flex-start; margin: 1rem 0;'>
791
+ <div class='processing-indicator' style='min-width: 400px;'>
792
+ <div style='display: flex; align-items: center; gap: 8px; margin-bottom: 12px;'>
 
 
 
 
 
793
  <div style='width: 16px; height: 16px; border: 2px solid #3b82f6; border-top: 2px solid transparent; border-radius: 50%; animation: spin 1s linear infinite;'></div>
794
+ <span style='font-weight: 600; color: #1e293b;'>๐Ÿค– VayuChat โ€ข {model_name}</span>
795
+ </div>
796
+
797
+ <div style='background: rgba(255,255,255,0.9); padding: 0.75rem; border-radius: 8px; margin-bottom: 12px; border-left: 3px solid #3b82f6;'>
798
+ <div style='color: #374151; font-size: 0.9rem; line-height: 1.4;'>{question}</div>
799
+ </div>
800
+
801
+ <div style='display: flex; flex-direction: column; gap: 4px;'>
802
+ {chr(10).join([f"<div style='display: flex; align-items: center; gap: 8px; color: #6b7280; font-size: 0.8rem;'><div style='width: 6px; height: 6px; background: #22c55e; border-radius: 50%; animation: pulse 1.5s infinite;'></div>{step}</div>" for step in steps])}
803
  </div>
804
  </div>
805
  </div>
 
808
  0% {{ transform: rotate(0deg); }}
809
  100% {{ transform: rotate(360deg); }}
810
  }}
811
+ @keyframes pulse {{
812
+ 0%, 100% {{ opacity: 0.4; transform: scale(0.8); }}
813
+ 50% {{ opacity: 1; transform: scale(1.2); }}
814
+ }}
815
  </style>
816
  """, unsafe_allow_html=True)
817
 
 
835
  last_prompt = response.get("last_prompt", "")
836
  code = response.get("gen_code", "")
837
 
838
+ # Add quick action buttons for follow-up questions (only if no error)
839
+ if not error and output:
840
+ st.markdown("---")
841
+ st.markdown("**๐Ÿ’ก Quick Follow-ups:**")
842
+
843
+ action_col1, action_col2, action_col3 = st.columns(3)
844
+
845
+ # Generate contextual follow-up suggestions based on the original question
846
+ original_question = last_prompt.lower()
847
+
848
+ with action_col1:
849
+ if "month" in original_question:
850
+ if st.button("๐Ÿ“ˆ Show yearly trend", key=f"action_yearly_{response_id}", use_container_width=True):
851
+ yearly_prompt = f"Plot yearly trend analysis for the same data as: {last_prompt}"
852
+ st.session_state.follow_up_prompt = yearly_prompt
853
+ st.rerun()
854
+ elif "city" in original_question:
855
+ if st.button("๐Ÿ™๏ธ Compare top 5 cities", key=f"action_compare_{response_id}", use_container_width=True):
856
+ compare_prompt = f"Compare top 5 cities based on: {last_prompt}"
857
+ st.session_state.follow_up_prompt = compare_prompt
858
+ st.rerun()
859
+ else:
860
+ if st.button("๐Ÿ“Š Show detailed breakdown", key=f"action_detail_{response_id}", use_container_width=True):
861
+ detail_prompt = f"Provide detailed breakdown of: {last_prompt}"
862
+ st.session_state.follow_up_prompt = detail_prompt
863
+ st.rerun()
864
+
865
+ with action_col2:
866
+ if st.button("๐ŸŒ Compare with WHO standards", key=f"action_who_{response_id}", use_container_width=True):
867
+ who_prompt = f"Compare the results from '{last_prompt}' with WHO air quality guidelines"
868
+ st.session_state.follow_up_prompt = who_prompt
869
+ st.rerun()
870
+
871
+ with action_col3:
872
+ if st.button("๐Ÿ“… Seasonal analysis", key=f"action_seasonal_{response_id}", use_container_width=True):
873
+ seasonal_prompt = f"Show seasonal patterns for: {last_prompt}"
874
+ st.session_state.follow_up_prompt = seasonal_prompt
875
+ st.rerun()
876
+
877
  if "feedback" in st.session_state.responses[response_id]:
878
  feedback_data = st.session_state.responses[response_id]["feedback"]
879
  st.markdown(f"""
 
939
  if selected_prompt:
940
  prompt = selected_prompt
941
 
942
+ # Handle follow-up prompts from quick action buttons
943
+ if st.session_state.get("follow_up_prompt") and not st.session_state.get("processing"):
944
+ prompt = st.session_state.follow_up_prompt
945
+ st.session_state.follow_up_prompt = None # Clear the follow-up prompt
946
+
947
  # Handle new queries
948
  if prompt and not st.session_state.get("processing"):
949
  # Prevent duplicate processing
 
981
  "gen_code": "",
982
  "ex_code": "",
983
  "last_prompt": prompt,
984
+ "error": "Invalid response format",
985
+ "timestamp": datetime.now().strftime("%H:%M")
986
  }
987
 
988
  response.setdefault("role", "assistant")
 
991
  response.setdefault("ex_code", "")
992
  response.setdefault("last_prompt", prompt)
993
  response.setdefault("error", None)
994
+ response.setdefault("timestamp", datetime.now().strftime("%H:%M"))
995
 
996
  except Exception as e:
997
  response = {
 
1000
  "gen_code": "",
1001
  "ex_code": "",
1002
  "last_prompt": prompt,
1003
+ "error": str(e),
1004
+ "timestamp": datetime.now().strftime("%H:%M")
1005
  }
1006
 
1007
  st.session_state.responses.append(response)