Implement enhanced UI with proper response display and feedback
Browse files- app.py +55 -84
- src/ui/chat_handler.py +133 -0
app.py
CHANGED
|
@@ -7,12 +7,13 @@ from datetime import datetime
|
|
| 7 |
from pathlib import Path
|
| 8 |
sys.path.append(str(Path(__file__).parent))
|
| 9 |
|
|
|
|
|
|
|
| 10 |
from utils.config import config
|
| 11 |
from core.session import session_manager
|
| 12 |
from core.memory import check_redis_health
|
| 13 |
from core.errors import translate_error
|
| 14 |
from core.personality import personality
|
| 15 |
-
from core.providers.ollama import OllamaProvider
|
| 16 |
import logging
|
| 17 |
|
| 18 |
# Set up logging
|
|
@@ -41,7 +42,7 @@ with st.sidebar:
|
|
| 41 |
# Model selection
|
| 42 |
model_options = {
|
| 43 |
"Mistral 7B (Local)": "mistral:latest",
|
| 44 |
-
"Llama 2 7B (Local)": "llama2:latest",
|
| 45 |
"OpenChat 3.5 (Local)": "openchat:latest"
|
| 46 |
}
|
| 47 |
selected_model_name = st.selectbox(
|
|
@@ -70,6 +71,7 @@ with st.sidebar:
|
|
| 70 |
|
| 71 |
if st.button("π‘ Test Connection"):
|
| 72 |
try:
|
|
|
|
| 73 |
ollama_provider = OllamaProvider(st.session_state.selected_model)
|
| 74 |
is_valid = ollama_provider.validate_model()
|
| 75 |
if is_valid:
|
|
@@ -81,12 +83,14 @@ with st.sidebar:
|
|
| 81 |
|
| 82 |
if st.button("ποΈ Clear History"):
|
| 83 |
st.session_state.messages = []
|
|
|
|
|
|
|
| 84 |
st.success("History cleared!")
|
| 85 |
|
| 86 |
st.divider()
|
| 87 |
|
| 88 |
-
# System Status
|
| 89 |
-
with st.expander("π System Status", expanded=
|
| 90 |
st.subheader("π Status")
|
| 91 |
|
| 92 |
# Ollama Status
|
|
@@ -100,11 +104,45 @@ with st.sidebar:
|
|
| 100 |
except:
|
| 101 |
st.info("π¦ Ollama: Unknown")
|
| 102 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 103 |
# Redis Status
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
|
|
|
|
|
|
|
|
|
|
| 108 |
|
| 109 |
st.divider()
|
| 110 |
|
|
@@ -124,88 +162,19 @@ if st.session_state.show_welcome:
|
|
| 124 |
st.markdown(greeting)
|
| 125 |
st.session_state.show_welcome = False
|
| 126 |
|
| 127 |
-
# Display
|
| 128 |
for message in st.session_state.messages:
|
| 129 |
with st.chat_message(message["role"]):
|
| 130 |
st.markdown(message["content"])
|
| 131 |
if "timestamp" in message:
|
| 132 |
-
|
|
|
|
| 133 |
|
| 134 |
-
# Chat input
|
| 135 |
-
user_input = st.chat_input("Type your message here...",
|
| 136 |
|
| 137 |
-
|
| 138 |
-
|
| 139 |
-
st.session_state.is_processing = True
|
| 140 |
-
|
| 141 |
-
# Display user message
|
| 142 |
-
with st.chat_message("user"):
|
| 143 |
-
st.markdown(user_input)
|
| 144 |
-
|
| 145 |
-
# Add to history
|
| 146 |
-
timestamp = datetime.now().strftime("%H:%M:%S")
|
| 147 |
-
st.session_state.messages.append({
|
| 148 |
-
"role": "user",
|
| 149 |
-
"content": user_input,
|
| 150 |
-
"timestamp": timestamp
|
| 151 |
-
})
|
| 152 |
-
|
| 153 |
-
# Process response
|
| 154 |
-
try:
|
| 155 |
-
# Get conversation history
|
| 156 |
-
user_session = session_manager.get_session("default_user")
|
| 157 |
-
conversation_history = user_session.get("conversation", []).copy()
|
| 158 |
-
conversation_history.append({"role": "user", "content": user_input})
|
| 159 |
-
|
| 160 |
-
# Generate response
|
| 161 |
-
try:
|
| 162 |
-
ollama_provider = OllamaProvider(st.session_state.selected_model)
|
| 163 |
-
ai_response = ollama_provider.generate(user_input, conversation_history)
|
| 164 |
-
|
| 165 |
-
if ai_response and ai_response.strip():
|
| 166 |
-
with st.chat_message("assistant"):
|
| 167 |
-
st.markdown(ai_response)
|
| 168 |
-
status = "β
Response received!"
|
| 169 |
-
else:
|
| 170 |
-
with st.chat_message("assistant"):
|
| 171 |
-
st.warning("β οΈ Received empty response")
|
| 172 |
-
ai_response = "I received your message but couldn't generate a proper response."
|
| 173 |
-
|
| 174 |
-
except Exception as e:
|
| 175 |
-
error_message = str(e)
|
| 176 |
-
with st.chat_message("assistant"):
|
| 177 |
-
st.error(f"β Error: {error_message[:100]}...")
|
| 178 |
-
ai_response = f"Error: {error_message[:100]}..."
|
| 179 |
-
|
| 180 |
-
# Save to session
|
| 181 |
-
if ai_response:
|
| 182 |
-
try:
|
| 183 |
-
conversation = user_session.get("conversation", []).copy()
|
| 184 |
-
conversation.append({"role": "user", "content": user_input})
|
| 185 |
-
conversation.append({"role": "assistant", "content": str(ai_response)})
|
| 186 |
-
session_manager.update_session("default_user", {"conversation": conversation})
|
| 187 |
-
|
| 188 |
-
st.session_state.messages.append({
|
| 189 |
-
"role": "assistant",
|
| 190 |
-
"content": str(ai_response),
|
| 191 |
-
"timestamp": timestamp
|
| 192 |
-
})
|
| 193 |
-
except Exception as session_error:
|
| 194 |
-
logger.error(f"Session update error: {session_error}")
|
| 195 |
-
|
| 196 |
-
except Exception as e:
|
| 197 |
-
error_msg = f"System error: {str(e)}"
|
| 198 |
-
logger.error(f"Processing error: {error_msg}")
|
| 199 |
-
with st.chat_message("assistant"):
|
| 200 |
-
st.error(error_msg)
|
| 201 |
-
st.session_state.messages.append({
|
| 202 |
-
"role": "assistant",
|
| 203 |
-
"content": error_msg,
|
| 204 |
-
"timestamp": timestamp
|
| 205 |
-
})
|
| 206 |
-
finally:
|
| 207 |
-
st.session_state.is_processing = False
|
| 208 |
-
st.experimental_rerun()
|
| 209 |
|
| 210 |
# About tab
|
| 211 |
st.divider()
|
|
@@ -220,11 +189,13 @@ with tab1:
|
|
| 220 |
- **Local AI processing** with Ollama models
|
| 221 |
- **Persistent memory** using Redis
|
| 222 |
- **Space-themed personality** for fun interactions
|
|
|
|
| 223 |
|
| 224 |
### π Cosmic Mode
|
| 225 |
When enabled, the AI responds with space-themed language and metaphors.
|
| 226 |
|
| 227 |
### π οΈ Technical Architecture
|
| 228 |
- **Primary model**: Ollama (local processing)
|
|
|
|
| 229 |
- **Memory system**: Redis-based session management
|
| 230 |
""")
|
|
|
|
| 7 |
from pathlib import Path
|
| 8 |
sys.path.append(str(Path(__file__).parent))
|
| 9 |
|
| 10 |
+
# Import our new handler
|
| 11 |
+
from src.ui.chat_handler import chat_handler
|
| 12 |
from utils.config import config
|
| 13 |
from core.session import session_manager
|
| 14 |
from core.memory import check_redis_health
|
| 15 |
from core.errors import translate_error
|
| 16 |
from core.personality import personality
|
|
|
|
| 17 |
import logging
|
| 18 |
|
| 19 |
# Set up logging
|
|
|
|
| 42 |
# Model selection
|
| 43 |
model_options = {
|
| 44 |
"Mistral 7B (Local)": "mistral:latest",
|
| 45 |
+
"Llama 2 7B (Local)": "llama2:latest",
|
| 46 |
"OpenChat 3.5 (Local)": "openchat:latest"
|
| 47 |
}
|
| 48 |
selected_model_name = st.selectbox(
|
|
|
|
| 71 |
|
| 72 |
if st.button("π‘ Test Connection"):
|
| 73 |
try:
|
| 74 |
+
from core.providers.ollama import OllamaProvider
|
| 75 |
ollama_provider = OllamaProvider(st.session_state.selected_model)
|
| 76 |
is_valid = ollama_provider.validate_model()
|
| 77 |
if is_valid:
|
|
|
|
| 83 |
|
| 84 |
if st.button("ποΈ Clear History"):
|
| 85 |
st.session_state.messages = []
|
| 86 |
+
# Also clear backend session
|
| 87 |
+
session_manager.clear_session("default_user")
|
| 88 |
st.success("History cleared!")
|
| 89 |
|
| 90 |
st.divider()
|
| 91 |
|
| 92 |
+
# System Status with enhanced HF monitoring
|
| 93 |
+
with st.expander("π System Status", expanded=True):
|
| 94 |
st.subheader("π Status")
|
| 95 |
|
| 96 |
# Ollama Status
|
|
|
|
| 104 |
except:
|
| 105 |
st.info("π¦ Ollama: Unknown")
|
| 106 |
|
| 107 |
+
# HF Endpoint Status (Enhanced)
|
| 108 |
+
try:
|
| 109 |
+
from src.services.hf_monitor import hf_monitor
|
| 110 |
+
status_message = hf_monitor.get_human_readable_status()
|
| 111 |
+
|
| 112 |
+
# Display appropriate status icon
|
| 113 |
+
if "π’" in status_message:
|
| 114 |
+
st.success(status_message)
|
| 115 |
+
elif "π‘" in status_message:
|
| 116 |
+
st.warning(status_message)
|
| 117 |
+
elif "π΄" in status_message or "β" in status_message:
|
| 118 |
+
st.error(status_message)
|
| 119 |
+
elif "β³" in status_message:
|
| 120 |
+
st.info(status_message)
|
| 121 |
+
else:
|
| 122 |
+
st.info(status_message)
|
| 123 |
+
|
| 124 |
+
# Add wake-up button if scaled to zero
|
| 125 |
+
if "scaled to zero" in status_message.lower():
|
| 126 |
+
if st.button("β‘ Wake Up HF Endpoint", key="wake_up_hf"):
|
| 127 |
+
with st.spinner("Waking up HF endpoint... This may take 2-4 minutes..."):
|
| 128 |
+
if hf_monitor.attempt_wake_up():
|
| 129 |
+
st.success("β
HF endpoint is waking up! Try your request again in a moment.")
|
| 130 |
+
time.sleep(2)
|
| 131 |
+
st.experimental_rerun()
|
| 132 |
+
else:
|
| 133 |
+
st.error("β Failed to wake up HF endpoint. Please try again.")
|
| 134 |
+
|
| 135 |
+
except Exception as e:
|
| 136 |
+
st.info(f"π€ HF Endpoint: Error checking status - {str(e)}")
|
| 137 |
+
|
| 138 |
# Redis Status
|
| 139 |
+
try:
|
| 140 |
+
if check_redis_health():
|
| 141 |
+
st.success("πΎ Redis: Connected")
|
| 142 |
+
else:
|
| 143 |
+
st.error("πΎ Redis: Disconnected")
|
| 144 |
+
except:
|
| 145 |
+
st.info("πΎ Redis: Unknown")
|
| 146 |
|
| 147 |
st.divider()
|
| 148 |
|
|
|
|
| 162 |
st.markdown(greeting)
|
| 163 |
st.session_state.show_welcome = False
|
| 164 |
|
| 165 |
+
# Display conversation history
|
| 166 |
for message in st.session_state.messages:
|
| 167 |
with st.chat_message(message["role"]):
|
| 168 |
st.markdown(message["content"])
|
| 169 |
if "timestamp" in message:
|
| 170 |
+
provider_info = f" (via {message.get('provider', 'unknown')})" if message["role"] == "assistant" else ""
|
| 171 |
+
st.caption(f"π {message['timestamp']}{provider_info}")
|
| 172 |
|
| 173 |
+
# Chat input with enhanced processing
|
| 174 |
+
user_input = st.chat_input("Type your message here...", key="chat_input")
|
| 175 |
|
| 176 |
+
if user_input:
|
| 177 |
+
chat_handler.process_user_message(user_input, selected_model_name)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 178 |
|
| 179 |
# About tab
|
| 180 |
st.divider()
|
|
|
|
| 189 |
- **Local AI processing** with Ollama models
|
| 190 |
- **Persistent memory** using Redis
|
| 191 |
- **Space-themed personality** for fun interactions
|
| 192 |
+
- **HF Endpoint integration** for advanced capabilities
|
| 193 |
|
| 194 |
### π Cosmic Mode
|
| 195 |
When enabled, the AI responds with space-themed language and metaphors.
|
| 196 |
|
| 197 |
### π οΈ Technical Architecture
|
| 198 |
- **Primary model**: Ollama (local processing)
|
| 199 |
+
- **Secondary model**: HF Endpoint (advanced processing)
|
| 200 |
- **Memory system**: Redis-based session management
|
| 201 |
""")
|
src/ui/chat_handler.py
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import streamlit as st
|
| 2 |
+
import time
|
| 3 |
+
import logging
|
| 4 |
+
from typing import Optional
|
| 5 |
+
from src.llm.factory import llm_factory, ProviderNotAvailableError
|
| 6 |
+
from src.services.hf_monitor import hf_monitor
|
| 7 |
+
from core.session import session_manager
|
| 8 |
+
|
| 9 |
+
logger = logging.getLogger(__name__)
|
| 10 |
+
|
| 11 |
+
class ChatHandler:
|
| 12 |
+
"""Handles chat interactions with better UI feedback"""
|
| 13 |
+
|
| 14 |
+
def __init__(self):
|
| 15 |
+
self.is_processing = False
|
| 16 |
+
|
| 17 |
+
def process_user_message(self, user_input: str, selected_model: str):
|
| 18 |
+
"""Process user message with enhanced UI feedback"""
|
| 19 |
+
if not user_input or not user_input.strip():
|
| 20 |
+
st.warning("Please enter a message")
|
| 21 |
+
return
|
| 22 |
+
|
| 23 |
+
if self.is_processing:
|
| 24 |
+
st.warning("Still processing previous request...")
|
| 25 |
+
return
|
| 26 |
+
|
| 27 |
+
self.is_processing = True
|
| 28 |
+
|
| 29 |
+
try:
|
| 30 |
+
# Show user message immediately
|
| 31 |
+
timestamp = time.strftime("%H:%M:%S")
|
| 32 |
+
with st.chat_message("user"):
|
| 33 |
+
st.markdown(user_input)
|
| 34 |
+
st.caption(f"π {timestamp}")
|
| 35 |
+
|
| 36 |
+
# Add to session history
|
| 37 |
+
st.session_state.messages.append({
|
| 38 |
+
"role": "user",
|
| 39 |
+
"content": user_input,
|
| 40 |
+
"timestamp": timestamp
|
| 41 |
+
})
|
| 42 |
+
|
| 43 |
+
# Show processing status
|
| 44 |
+
with st.chat_message("assistant"):
|
| 45 |
+
status_placeholder = st.empty()
|
| 46 |
+
response_placeholder = st.empty()
|
| 47 |
+
|
| 48 |
+
try:
|
| 49 |
+
# Determine provider based on model selection
|
| 50 |
+
provider_name = self._get_provider_for_model(selected_model)
|
| 51 |
+
status_placeholder.info(f"π Contacting {self._get_provider_display_name(provider_name)}...")
|
| 52 |
+
|
| 53 |
+
# Get response
|
| 54 |
+
response = self._get_ai_response(user_input, provider_name)
|
| 55 |
+
|
| 56 |
+
if response:
|
| 57 |
+
status_placeholder.success("β
Response received!")
|
| 58 |
+
response_placeholder.markdown(response)
|
| 59 |
+
|
| 60 |
+
# Add to session history
|
| 61 |
+
st.session_state.messages.append({
|
| 62 |
+
"role": "assistant",
|
| 63 |
+
"content": response,
|
| 64 |
+
"timestamp": time.strftime("%H:%M:%S"),
|
| 65 |
+
"provider": provider_name
|
| 66 |
+
})
|
| 67 |
+
else:
|
| 68 |
+
status_placeholder.error("β Empty response received")
|
| 69 |
+
response_placeholder.markdown("I received your message but couldn't generate a proper response.")
|
| 70 |
+
|
| 71 |
+
except ProviderNotAvailableError as e:
|
| 72 |
+
status_placeholder.error("β No AI providers available")
|
| 73 |
+
response_placeholder.markdown("No AI providers are configured. Please check your settings.")
|
| 74 |
+
logger.error(f"Provider not available: {e}")
|
| 75 |
+
|
| 76 |
+
except Exception as e:
|
| 77 |
+
status_placeholder.error(f"β Error: {str(e)[:100]}...")
|
| 78 |
+
response_placeholder.markdown(f"Sorry, I encountered an error: {str(e)[:100]}...")
|
| 79 |
+
logger.error(f"Chat processing error: {e}")
|
| 80 |
+
|
| 81 |
+
finally:
|
| 82 |
+
self.is_processing = False
|
| 83 |
+
time.sleep(0.1) # Small delay to ensure UI updates
|
| 84 |
+
# st.experimental_rerun() # Removed to prevent automatic rerun
|
| 85 |
+
|
| 86 |
+
def _get_provider_for_model(self, selected_model: str) -> str:
|
| 87 |
+
"""Determine which provider to use based on model selection"""
|
| 88 |
+
model_map = {
|
| 89 |
+
"Mistral 7B (Local)": "ollama",
|
| 90 |
+
"Llama 2 7B (Local)": "ollama",
|
| 91 |
+
"OpenChat 3.5 (Local)": "ollama"
|
| 92 |
+
}
|
| 93 |
+
return model_map.get(selected_model, "ollama")
|
| 94 |
+
|
| 95 |
+
def _get_provider_display_name(self, provider_name: str) -> str:
|
| 96 |
+
"""Get display name for provider"""
|
| 97 |
+
display_names = {
|
| 98 |
+
"ollama": "π¦ Ollama",
|
| 99 |
+
"huggingface": "π€ HF Endpoint"
|
| 100 |
+
}
|
| 101 |
+
return display_names.get(provider_name, provider_name)
|
| 102 |
+
|
| 103 |
+
def _get_ai_response(self, user_input: str, provider_name: str) -> Optional[str]:
|
| 104 |
+
"""Get AI response from specified provider"""
|
| 105 |
+
try:
|
| 106 |
+
# Get session and conversation history
|
| 107 |
+
user_session = session_manager.get_session("default_user")
|
| 108 |
+
conversation_history = user_session.get("conversation", []).copy()
|
| 109 |
+
|
| 110 |
+
# Add current user message
|
| 111 |
+
conversation_history.append({"role": "user", "content": user_input})
|
| 112 |
+
|
| 113 |
+
# Get provider
|
| 114 |
+
provider = llm_factory.get_provider(provider_name)
|
| 115 |
+
|
| 116 |
+
# Generate response
|
| 117 |
+
response = provider.generate(user_input, conversation_history)
|
| 118 |
+
|
| 119 |
+
# Update session with conversation
|
| 120 |
+
if response:
|
| 121 |
+
conversation = user_session.get("conversation", []).copy()
|
| 122 |
+
conversation.append({"role": "user", "content": user_input})
|
| 123 |
+
conversation.append({"role": "assistant", "content": response})
|
| 124 |
+
session_manager.update_session("default_user", {"conversation": conversation})
|
| 125 |
+
|
| 126 |
+
return response
|
| 127 |
+
|
| 128 |
+
except Exception as e:
|
| 129 |
+
logger.error(f"AI response generation failed: {e}")
|
| 130 |
+
raise
|
| 131 |
+
|
| 132 |
+
# Global instance
|
| 133 |
+
chat_handler = ChatHandler()
|