File size: 10,279 Bytes
ade3749 4cc2671 ade3749 88b24b5 ade3749 4cc2671 9d94507 4cc2671 9d94507 4cc2671 9d94507 4cc2671 9d94507 4cc2671 ade3749 953becc ade3749 88b24b5 953becc d3318b4 953becc ade3749 1634265 ade3749 db047b3 f025340 9d94507 db047b3 f025340 4cc2671 1634265 ade3749 8e40310 1634265 ade3749 2f774df 0ab8a85 2f774df 0ab8a85 2f774df 0ab8a85 2f774df ade3749 96e08a2 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 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 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 |
"""
This module handles the chat display components and rendering.
"""
import streamlit as st
import os
import re
from utils import has_meaningful_content, remove_reasoning_and_sources, clean_explanation, get_image_base64
from session_state import get_full_history
def get_avatars():
"""
Get avatar images for the user and assistant.
Returns:
tuple: (user_avatar, assistant_avatar) - URLs or emoji fallbacks
"""
# Define paths to your company logo images
user_logo_path = "src/assets/icon3.png"
assistant_logo_path = "src/assets/logo.png"
# Get base64 encoded images or use default emoji as fallback
user_logo_base64 = get_image_base64(user_logo_path)
assistant_logo_base64 = get_image_base64(assistant_logo_path)
# Create avatar URLs with base64 data
user_avatar = f"data:image/png;base64,{user_logo_base64}" if user_logo_base64 else "π€"
assistant_avatar = f"data:image/png;base64,{assistant_logo_base64}" if assistant_logo_base64 else "π€"
return user_avatar, assistant_avatar
def extract_follow_up_questions(content):
"""
Extract follow-up questions from the main content.
Args:
content (str): The message content
Returns:
str: Extracted follow-up questions or empty string if none found
"""
# Look for various forms of follow-up questions sections
patterns = [
r'(?i)follow[ -]?up questions:?\s*(.*?)(?=\n+\s*(?:#{1,3}|reasoning:|sources:|\Z))',
r'(?i)#{1,3}\s*follow[ -]?up questions:?\s*(.*?)(?=\n+\s*(?:#{1,3}|reasoning:|sources:|\Z))',
r'(?i)important questions to ask:?\s*(.*?)(?=\n+\s*(?:#{1,3}|reasoning:|sources:|\Z))',
r'(?i)clarifying questions:?\s*(.*?)(?=\n+\s*(?:#{1,3}|reasoning:|sources:|\Z))',
r'(?i)additional questions:?\s*(.*?)(?=\n+\s*(?:#{1,3}|reasoning:|sources:|\Z))',
r'(?i)please clarify(?:[ a-z]+)?:?\s*(.*?)(?=\n+\s*(?:#{1,3}|reasoning:|sources:|\Z))',
r'(?i)additional information needed:?\s*(.*?)(?=\n+\s*(?:#{1,3}|reasoning:|sources:|\Z))'
]
# Try each pattern until we find a match
for pattern in patterns:
follow_up_match = re.search(pattern, content, re.DOTALL)
if follow_up_match:
follow_up_text = follow_up_match.group(1).strip()
# Check if there's introductory text before a list
intro_text = ""
questions = []
# Look for introductory text before bullet points or numbered list
intro_match = re.match(r'(.*?)(?=\n\s*[-β’*]|\n\s*\d+\.)', follow_up_text, re.DOTALL)
if intro_match and intro_match.group(1).strip():
intro_text = intro_match.group(1).strip()
# Handle different list formats
if re.search(r'\n\s*[-β’*]', follow_up_text):
# Extract bullet points
bullet_points = re.findall(r'(?:\n|\A)\s*[-β’*]\s*(.*?)(?=\n\s*[-β’*]|\Z)', follow_up_text, re.DOTALL)
if bullet_points:
questions = [point.strip() for point in bullet_points if point.strip()]
elif re.search(r'\n\s*\d+\.', follow_up_text):
# Extract numbered points
numbered_points = re.findall(r'(?:\n|\A)\s*\d+\.\s*(.*?)(?=\n\s*\d+\.|\Z)', follow_up_text, re.DOTALL)
if numbered_points:
questions = [point.strip() for point in numbered_points if point.strip()]
else:
# If no specific formatting, try to split by lines
lines = follow_up_text.split('\n')
questions = [line.strip() for line in lines if line.strip()]
# If intro text was found, remove it from questions
if intro_text and questions and questions[0] == intro_text:
questions = questions[1:]
# Format output, preserving intro text
result = ""
if intro_text:
result += f"{intro_text}\n\n"
# Format the questions as a numbered list
if questions:
for i, question in enumerate(questions):
# Clean up any existing numbers or bullets
clean_question = re.sub(r'^\s*(?:\d+\.|\-|\β’|\*)\s*', '', question)
result += f"{i+1}. {clean_question}\n"
else:
# If no structured questions found, just use the whole text
result += follow_up_text
return result.strip()
return ""
def display_chat_history():
"""
Display the chat history from the database.
"""
user_avatar, assistant_avatar = get_avatars()
# Get full history from database
history = get_full_history()
# Display all messages instead of limiting to a fixed number
display_history = history
for message in display_history:
if message["role"] == "user":
# Right-aligned container for user messages
with st.container():
col1, col2 = st.columns([2, 10])
with col2:
with st.chat_message("user", avatar=user_avatar):
st.write(message["content"])
else:
# Left-aligned container for assistant messages
with st.container():
col1, col2 = st.columns([10, 2])
with col1:
with st.chat_message("assistant", avatar=assistant_avatar):
# Display the response text without reasoning or sources sections
cleaned_response = remove_reasoning_and_sources(message["content"])
st.markdown(cleaned_response)
# Use follow-up questions from the message object directly
if message.get("follow_up_questions") and message["follow_up_questions"].strip():
print(f"Found follow-up questions in message: {message['follow_up_questions']}")
with st.expander("Additional Questions"):
st.markdown(message["follow_up_questions"])
else:
print(f"No follow-up questions found in message keys: {list(message.keys())}")
# Only display the explanation in an expander if it exists AND has actual content
if message.get("explanation") and has_meaningful_content(message.get("explanation")):
# Clean up the explanation text
cleaned_explanation = clean_explanation(message["explanation"])
# Additional cleaning to remove any source information from reasoning
# Remove any sources/references sections
cleaned_explanation = re.sub(r'(?i)(\n+\s*sources:|\n+\s*references:|\n+\s*\*{0,2}sources\*{0,2}:?|\n+\s*\*{0,2}references\*{0,2}:?|\n+\s*#{1,3}\s*sources|\n+\s*#{1,3}\s*references).*', '', cleaned_explanation, flags=re.DOTALL)
# Remove any ## Sources heading and content
cleaned_explanation = re.sub(r'#{1,3}\s+Sources.*', '', cleaned_explanation, flags=re.DOTALL)
with st.expander("Show Reasoning"):
st.markdown(cleaned_explanation)
# Display sources in a separate expander if evidence is available
if message.get("evidence") and len(message.get("evidence", [])) > 0:
with st.expander("Show Sources"):
st.markdown("**Medical sources used:**")
for i, source in enumerate(message.get("evidence", [])):
title = source.get("title", "No title") # Fallback if citation is missing
url = source.get("url", "#")
source_type = source.get("source_type", "Unknown Source")
is_open_access = source.get("is_open_access", False)
access_icon = "π " if is_open_access else ""
citation_text = source.get('citation', title).strip()
markdown_string = (
f"{i+1}. {access_icon}{citation_text}\n"
f" [[Access Article]]({url}) - *{source_type}*"
)
st.markdown(markdown_string)
st.markdown("---")
st.markdown("**Legend:** π = Open Access (full text available)")
def show_typing_indicator():
"""
Display a typing indicator when the system is processing a response.
"""
if st.session_state.processing:
user_avatar, assistant_avatar = get_avatars()
with st.container():
col1, col2 = st.columns([10, 2])
with col1:
with st.chat_message("assistant", avatar=assistant_avatar):
st.markdown("""
<div class="typing-indicator">
<span></span>
<span></span>
<span></span>
</div>
<p style="margin: 0; color: rgba(255,255,255,0.6) !important; font-size: 0.8rem;">Analyzing your query...</p>
""", unsafe_allow_html=True)
def display_legal_disclaimer():
"""
Display the legal disclaimer at the bottom of the page.
"""
st.markdown("""
<div class="footer-text">
For informational purposes only. Not a substitute for professional medical advice.
</div>
""", unsafe_allow_html=True)
|