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]>
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 |
-
|
| 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
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 738 |
st.markdown(f"""
|
| 739 |
<div style='display: flex; justify-content: flex-start; margin: 1rem 0;'>
|
| 740 |
-
<div class='processing-indicator'>
|
| 741 |
-
<div style='
|
| 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='
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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)
|