File size: 9,242 Bytes
71a1c43
2b9aa0c
 
 
71a1c43
2b9aa0c
 
 
71a1c43
 
2b9aa0c
71a1c43
2b9aa0c
71a1c43
 
2b9aa0c
71a1c43
 
2b9aa0c
71a1c43
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2b9aa0c
 
 
 
71a1c43
2b9aa0c
71a1c43
 
 
 
 
2b9aa0c
 
 
 
71a1c43
 
 
2b9aa0c
71a1c43
2b9aa0c
71a1c43
 
2b9aa0c
 
 
 
 
 
71a1c43
 
 
 
2b9aa0c
 
71a1c43
2b9aa0c
71a1c43
2b9aa0c
 
 
 
 
 
 
 
 
 
 
71a1c43
2b9aa0c
 
 
71a1c43
2b9aa0c
 
 
 
71a1c43
 
2b9aa0c
 
71a1c43
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2b9aa0c
 
71a1c43
 
 
2b9aa0c
71a1c43
 
2b9aa0c
71a1c43
2b9aa0c
 
71a1c43
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
# /home/user/app/pages/2_Consult.py
import streamlit as st
from langchain_core.messages import HumanMessage, AIMessage, SystemMessage, ToolMessage
from datetime import datetime
from typing import List, Optional # Corrected import for List and Optional

from config.settings import settings
from agent import get_agent_executor
from models import ChatMessage, ChatSession, User # User might not be needed directly if ID is used
from models.db import get_session_context # Or from models import get_session_context
from services.logger import app_logger
from services.metrics import log_consultation_start # Assuming this function exists

# Page config typically in app.py
# st.set_page_config(page_title=f"Consult - {settings.APP_TITLE}", layout="wide")

# --- Authentication Check ---
if not st.session_state.get("authenticated_user_id"):
    st.warning("Please log in to access the consultation page.")
    try:
        st.switch_page("app.py")
    except st.errors.StreamlitAPIException as e:
        if "st.switch_page can only be called when running in MPA mode" in str(e):
            app_logger.warning("Consult: Running in single-page mode or st.switch_page issue. Stopping script.")
            st.info("Please navigate to the main login page.")
        else:
            app_logger.error(f"Consult: Error during st.switch_page: {e}")
            st.error("Redirection error. Please go to the login page manually.")
    st.stop()

# Get authenticated user's ID and username
authenticated_user_id = st.session_state.get("authenticated_user_id")
authenticated_username = st.session_state.get("authenticated_username", "User")
app_logger.info(f"User {authenticated_username} (ID: {authenticated_user_id}) accessed Consult page.")

# --- Initialize Agent ---
try:
    agent_executor = get_agent_executor()
except ValueError as e: # Handles missing API key or other init issues
    st.error(f"Could not initialize AI Agent: {e}")
    app_logger.critical(f"AI Agent initialization failed: {e}", exc_info=True)
    st.stop()
except Exception as e:
    st.error(f"An unexpected error occurred while initializing the AI Agent: {e}")
    app_logger.critical(f"Unexpected AI Agent initialization error: {e}", exc_info=True)
    st.stop()


# --- Helper Functions ---
@st.cache_data(ttl=60) # Short cache for chat history to avoid constant DB hits on reruns
def load_chat_history_for_agent(session_id: int) -> List: # Type hint for return
    """Loads chat history from DB for the current session, formatted for LangChain agent."""
    messages = []
    app_logger.debug(f"Loading agent chat history for session_id: {session_id}")
    with get_session_context() as db:
        # If using SQLModel: from sqlmodel import select
        # db_messages = db.exec(select(ChatMessage).where(ChatMessage.session_id == session_id).order_by(ChatMessage.timestamp)).all()
        db_messages = db.query(ChatMessage).filter(ChatMessage.session_id == session_id).order_by(ChatMessage.timestamp).all()
        for msg in db_messages:
            if msg.role == "user":
                messages.append(HumanMessage(content=msg.content))
            elif msg.role == "assistant":
                messages.append(AIMessage(content=msg.content))
            elif msg.role == "tool" and hasattr(msg, 'tool_call_id') and msg.tool_call_id: # Ensure tool_call_id exists
                 messages.append(ToolMessage(content=msg.content, tool_call_id=str(msg.tool_call_id))) # Cast to str just in case
            # Add other roles if necessary
    app_logger.debug(f"Loaded {len(messages)} messages for agent history for session {session_id}.")
    return messages

def save_chat_message_to_db(session_id: int, role: str, content: str, tool_call_id: Optional[str]=None, tool_name: Optional[str]=None):
    """Saves a chat message to the database."""
    app_logger.debug(f"Saving message to DB for session {session_id}: Role={role}, Content snippet='{content[:50]}...'")
    with get_session_context() as db:
        chat_message = ChatMessage(
            session_id=session_id,
            role=role,
            content=content,
            timestamp=datetime.utcnow(),
            tool_call_id=tool_call_id,
            tool_name=tool_name
        )
        db.add(chat_message)
        db.commit()
        app_logger.info(f"Message saved to DB for session {session_id}. Role: {role}.")

# --- Page Logic ---
st.title("AI Consultation Room")
st.markdown(f"Interacting as: **{authenticated_username}**")

chat_session_id = st.session_state.get("current_chat_session_id")

if not chat_session_id:
    st.error("No active chat session ID found in session state. This might happen if you logged in before this feature was fully active. Please try logging out and logging back in.")
    app_logger.error(f"User {authenticated_username} (ID: {authenticated_user_id}) on Consult page with no current_chat_session_id.")
    st.stop()

# Initialize agent's chat history if not already present for this session_id
# We use a more specific key for agent_chat_history to handle session changes
agent_history_key = f"agent_chat_history_{chat_session_id}"
if agent_history_key not in st.session_state:
    st.session_state[agent_history_key] = load_chat_history_for_agent(chat_session_id)
    if not st.session_state[agent_history_key]: # If no history, maybe add a system greeting
        try:
            log_consultation_start(user_id=authenticated_user_id, session_id=chat_session_id)
        except Exception as e:
            app_logger.warning(f"Failed to log consultation start: {e}")
        initial_ai_message_content = "Hello! I am your AI Health Navigator. How can I assist you today?"
        st.session_state[agent_history_key].append(AIMessage(content=initial_ai_message_content))
        save_chat_message_to_db(chat_session_id, "assistant", initial_ai_message_content)
        app_logger.info(f"Initialized new consultation for session {chat_session_id} with a greeting.")


# Display chat messages for UI (always fetch fresh from DB for UI consistency)
# This ensures UI reflects what's actually in the DB.
with st.container(): # Use a container for the chat display area
    with get_session_context() as db:
        # If using SQLModel: from sqlmodel import select
        # ui_messages = db.exec(select(ChatMessage).where(ChatMessage.session_id == chat_session_id).order_by(ChatMessage.timestamp)).all()
        ui_messages = db.query(ChatMessage).filter(ChatMessage.session_id == chat_session_id).order_by(ChatMessage.timestamp).all()
        for msg in ui_messages:
            avatar = "πŸ§‘β€βš•οΈ" if msg.role == "assistant" else "πŸ‘€"
            if msg.role == "tool": avatar = "πŸ› οΈ"

            with st.chat_message(msg.role, avatar=avatar):
                st.markdown(msg.content)

# Chat input
if prompt := st.chat_input("Ask the AI... (e.g., 'What is hypertension?' or 'Suggest diagnostic tests for chest pain')"):
    # Add user message to UI immediately (optimistic update)
    with st.chat_message("user", avatar="πŸ‘€"):
        st.markdown(prompt)
    # Save user message to DB
    save_chat_message_to_db(chat_session_id, "user", prompt)
    # Add to agent's history (LangChain format)
    st.session_state[agent_history_key].append(HumanMessage(content=prompt))

    # Get AI response
    with st.chat_message("assistant", avatar="πŸ§‘β€βš•οΈ"): # Prepare AI's chat message bubble
        with st.spinner("AI is thinking..."):
            try:
                response = agent_executor.invoke({
                    "input": prompt,
                    "chat_history": st.session_state[agent_history_key] # Pass the current agent history
                })
                ai_response_content = response.get('output', "No output from AI.")
                if not isinstance(ai_response_content, str): # Ensure it's a string
                    ai_response_content = str(ai_response_content)

                st.markdown(ai_response_content) # Display AI response in UI
                save_chat_message_to_db(chat_session_id, "assistant", ai_response_content) # Save to DB
                st.session_state[agent_history_key].append(AIMessage(content=ai_response_content)) # Add to agent's history

            except Exception as e:
                app_logger.error(f"Error during agent invocation for session {chat_session_id}: {e}", exc_info=True)
                error_message_user = f"Sorry, I encountered an error and could not process your request. Please try again or rephrase. (Error: {type(e).__name__})"
                st.error(error_message_user) # Display error in the AI's bubble
                # Save a generic error message to DB for the assistant's turn
                save_chat_message_to_db(chat_session_id, "assistant", f"Error processing request: {type(e).__name__}")
                # Add error representation to agent history so it's aware
                st.session_state[agent_history_key].append(AIMessage(content=f"Observed internal error: {type(e).__name__}"))

    # A full st.rerun() can be a bit disruptive if not needed.
    # Streamlit's chat_input and context managers usually handle updates well.
    # If messages aren't appearing correctly, a targeted rerun might be useful.
    # st.rerun()