keefereuther commited on
Commit
d9f0974
·
verified ·
1 Parent(s): c6e6d74

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +386 -199
app.py CHANGED
@@ -1,78 +1,246 @@
1
  ############################################################################################################
2
- # Importing Libraries
 
 
 
 
3
 
4
- import streamlit as st
5
- import hmac
6
- import pandas as pd
7
- import random
8
- import os
9
- import time
10
- import base64
11
- import logging
12
- import io
13
- import config
14
- from openai import OpenAI
15
 
16
- # Set up logging
17
  logging.basicConfig(filename='app.log', level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s', datefmt='%Y-%m-%d %H:%M:%S')
18
 
19
  ############################################################################################################
20
- # Password protection
 
21
 
22
- def check_credentials():
23
- """Returns `True` if the user had the correct username and password."""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
24
 
 
 
 
 
 
 
25
  def credentials_entered():
26
- """Checks whether the entered username and password are correct."""
27
  if (
28
  hmac.compare_digest(st.session_state["username"], st.secrets["username"]) and
29
  hmac.compare_digest(st.session_state["password"], st.secrets["password"])
30
  ):
31
  st.session_state["credentials_correct"] = True
32
- del st.session_state["username"] # Don't store the username.
33
- del st.session_state["password"] # Don't store the password.
34
  else:
35
  st.session_state["credentials_correct"] = False
36
 
37
- # Return True if the credentials are validated.
38
  if st.session_state.get("credentials_correct", False):
39
  return True
40
 
41
- # Show input for username and password.
42
  st.text_input("Username", on_change=credentials_entered, key="username")
43
  st.text_input("Password", type="password", on_change=credentials_entered, key="password")
44
 
 
45
  if "credentials_correct" in st.session_state:
46
- logging.warning("Invalid login attempt") # Log invalid login attempts
47
  return False
48
 
 
49
  if not check_credentials():
50
- st.stop() # Do not continue if check_credentials is not True.
51
  else:
52
  logging.info("Successful login") # Log successful logins
53
 
54
  ############################################################################################################
55
- # Streamlit app layout
56
-
57
- # Set the page to wide or centered mode
58
- st.set_page_config(
59
- layout="wide",
60
- initial_sidebar_state="collapsed" # ✨ keeps sidebar closed on first load
61
- )
62
-
63
- # Load the terms file into a DataFrame
64
- df = pd.read_csv(config.default_terms_csv)
65
-
66
- # Streamlit app layout
67
- st.title(config.app_title)
68
- st.markdown(config.intro_para)
69
- st.caption(config.app_author)
70
-
71
  ############################################################################################################
72
- # Loading Terms
 
 
73
 
74
  def load_terms(file_path):
75
- """Loads the CSV from the directory."""
 
 
 
 
 
 
 
76
  try:
77
  return pd.read_csv(file_path)
78
  except Exception as e:
@@ -80,195 +248,214 @@ def load_terms(file_path):
80
  logging.exception(f"Error loading file: {e}")
81
  return pd.DataFrame()
82
 
83
- # Function to extract the first column values
84
  def get_first_column_values(local_df):
 
 
 
 
 
 
 
 
85
  if not local_df.empty:
86
  return local_df.iloc[:, 0].tolist()
87
  else:
88
  return []
89
 
90
-
91
- ############################################################################################################
92
- # Prepare terms (no user file uploader anymore—only config.default_terms_csv)
93
-
94
  terms = load_terms(config.default_terms_csv)
95
  term_list = get_first_column_values(terms)
96
 
97
-
98
  ############################################################################################################
99
- # Term Selection and session state
100
-
101
- # Initialize the session state variables for selected term, context, and display messages
102
- if 'selected_term' not in st.session_state:
103
- st.session_state.selected_term = None
104
- if 'selected_context' not in st.session_state:
105
- st.session_state.selected_context = None
106
- if 'display_messages' not in st.session_state:
107
- st.session_state.display_messages = []
108
-
109
- # Initialize session states for the selected term, counter, and display flag
110
- if 'display_term' not in st.session_state:
111
- st.session_state.display_term = False
112
- if 'initial_message_displayed' not in st.session_state:
113
- st.session_state.initial_message_displayed = False
114
-
115
- # Initialize state to track the previously selected term
116
- if 'old_term' not in st.session_state:
117
- st.session_state.old_term = None
118
 
119
- # Dropdown menu for selecting a term
120
- selected_term = st.selectbox('**SELECT FROM THE DROPDOWN MENU**', term_list)
121
 
122
- if selected_term:
123
- # If a new term is selected (including first time selection), reset or show the message
124
- if selected_term != st.session_state.old_term:
125
- user_message = f"What is one thing you know about '{selected_term}'? What do you want to know about it? This could include a definition, examples, misconceptions, associations with other course terms, opinions, etc."
126
- st.session_state["display_messages"].append({"role": "user", "content": user_message})
127
- # Update old_term in session state
128
- st.session_state.old_term = selected_term
129
 
130
- selected_context = terms.loc[terms['TERM'] == selected_term, 'CONTEXT'].values[0]
131
- st.session_state.selected_term = selected_term
132
- st.session_state.selected_context = selected_context
133
- st.session_state.display_term = True
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
134
 
135
- # Update the prompt for the API
136
- updated_prompt = config.term_prompt(st.session_state.selected_term, st.session_state.selected_context, term_list)
 
137
 
138
- else:
139
- # If nothing is selected or the selection is cleared, reset the old_term
140
- st.session_state.old_term = None
141
-
142
- # Display the selected term and its context
143
- if st.session_state.display_term and st.session_state.selected_term:
144
- st.header(st.session_state.selected_term)
145
-
146
- with st.expander("INSTRUCTIONS FOR STUDENTS:"):
147
- st.markdown(config.instructions)
148
-
149
- ############################################################################################################
150
- # ChatGPT
151
- # Initialize the OpenAI client
152
- client = OpenAI(api_key=st.secrets["OPENAI_API_KEY"])
153
-
154
- # Initialize the session state variables if they don't exist
155
- if "openai_model" not in st.session_state:
156
- st.session_state["openai_model"] = config.ai_model
157
-
158
- if "display_messages" not in st.session_state:
159
- st.session_state.display_messages = []
160
-
161
- # Update initial_context with the latest selected term and context
162
- if st.session_state.get('selected_term') and st.session_state.get('selected_context'):
163
- updated_prompt = config.term_prompt(st.session_state.selected_term, st.session_state.selected_context, term_list)
164
- # Replace the initial context in display_messages with the updated prompt
165
- if st.session_state.display_messages:
166
- st.session_state.display_messages[0]["content"] = updated_prompt
167
- else:
168
- st.session_state.display_messages = [{"role": "system", "content": updated_prompt}]
169
-
170
- # Get user input
171
- prompt = st.chat_input("What do you know? What do you want to know?")
172
-
173
- # Input for new messages
174
- if prompt:
175
- # Ensure the initial context is in the session state, add the user's message
176
- if not st.session_state["display_messages"]:
177
- st.session_state["display_messages"].append(initial_context)
178
- st.session_state["display_messages"].append({"role": "user", "content": prompt})
179
-
180
- # Function to reset all chat-related session state
181
- def reset_chat_history():
182
- st.session_state["display_messages"] = []
183
- # Reset other chat-related session states if they exist
184
- if 'selected_term' in st.session_state:
185
- st.session_state.selected_term = None
186
- if 'selected_context' in st.session_state:
187
- st.session_state.selected_context = None
188
- if 'display_term' in st.session_state:
189
- st.session_state.display_term = False
190
- st.rerun()
191
-
192
- # Main chat container
193
- with st.container(height=400, border=True):
194
- # Display chat history in reverse order including new messages
195
- for message in st.session_state["display_messages"][1:]:
196
- if message["role"] == "user":
197
- with st.chat_message("user"):
198
- st.markdown(message["content"])
199
- else:
200
- with st.chat_message("assistant"):
201
- st.markdown(message["content"])
202
-
203
- # Generate assistant's response and add it to the messages
204
- if prompt:
205
- with st.chat_message("assistant"):
206
  try:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
207
  stream = client.chat.completions.create(
208
  model=st.session_state["openai_model"],
209
- messages=[
210
- {"role": m["role"], "content": m["content"]}
211
- for m in st.session_state["display_messages"]
212
- ],
213
  stream=True,
214
  temperature=config.temperature,
215
  max_tokens=config.max_tokens,
216
  frequency_penalty=config.frequency_penalty,
217
  presence_penalty=config.presence_penalty,
218
  )
219
- response = st.write_stream(stream)
220
- # Append the full response to the session state for display
221
- st.session_state["display_messages"].append(
222
- {"role": "assistant", "content": response}
223
- )
224
- logging.info(f"User prompt: {prompt}") # Log user prompts
225
- logging.info(f"Assistant response: {response}") # Log assistant responses
 
 
226
  except Exception as e:
 
227
  st.error(f"An error occurred: {str(e)}")
228
- logging.exception(f"Error generating response: {e}") # Log errors
 
 
 
229
 
230
- # Add Clear Chat History button between container and warning message
231
- if st.button("Clear Chat History"):
232
- reset_chat_history()
233
- logging.info("Chat history cleared") # Log when chat history is cleared
 
 
 
234
 
235
- st.markdown(config.warning_message, unsafe_allow_html=True)
 
 
 
 
236
 
237
- ############################################################################################################
 
238
 
239
- # Resources and About Sections in the Sidebar
 
 
240
 
 
241
  st.sidebar.title("Resources")
242
 
 
243
  for resource in config.resources:
244
- with st.sidebar:
245
- with st.sidebar:
246
- with st.expander(resource["title"]):
247
- st.markdown(f"Description: {resource['description']}")
248
- if "url" in resource:
249
- st.markdown(f"[{resource['title']}]({resource['url']})")
250
- if "file_path" in resource:
251
- file_path = resource["file_path"]
252
- if os.path.exists(file_path):
253
- with open(file_path, "rb") as file:
254
- file_bytes = file.read()
255
- with st.spinner(f"Loading {resource['title']}..."):
256
- st.download_button(
257
- label=resource["title"],
258
- data=file_bytes,
259
- file_name=os.path.basename(file_path),
260
- mime="application/octet-stream",
261
- help=resource["description"],
262
- )
263
- else:
264
- st.warning(f"File not found: {file_path}")
265
-
266
- # Footer
267
- with st.sidebar:
268
- st.markdown("---")
269
-
270
- st.title("About")
271
-
272
- # Using the config objects in your Streamlit app
273
- st.markdown(config.app_creation_message, unsafe_allow_html=True)
274
- st.markdown(config.app_repo_license_message, unsafe_allow_html=True)
 
1
  ############################################################################################################
2
+ # Importing Libraries - Core dependencies for the Schema Study App
3
+ #
4
+ # This app helps students learn biology concepts through interactive conversations
5
+ # with an AI tutor, guided by course-specific terms and schemas.
6
+ ############################################################################################################
7
 
8
+ import streamlit as st # Web app framework
9
+ import hmac # Secure password validation
10
+ import pandas as pd # Data handling
11
+ import os # File operations
12
+ import logging # Logging functionality
13
+ import config # Local configuration module
14
+ from openai import OpenAI # OpenAI API client
 
 
 
 
15
 
16
+ # Set up logging to track app activity
17
  logging.basicConfig(filename='app.log', level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s', datefmt='%Y-%m-%d %H:%M:%S')
18
 
19
  ############################################################################################################
20
+ # Theme Configuration - UI appearance and layout settings
21
+ ############################################################################################################
22
 
23
+ # Set page layout, icon, and meta information
24
+ st.set_page_config(
25
+ layout="wide",
26
+ initial_sidebar_state="collapsed",
27
+ page_title="Schema Study - BILD 5",
28
+ page_icon="📚",
29
+ menu_items={
30
+ 'Get Help': 'https://keefereuther.com',
31
+ 'Report a bug': "mailto:kdreuther@ucsd.edu",
32
+ 'About': "# Schema Study\n An AI-enhanced study app for biology students."
33
+ }
34
+ )
35
+
36
+ # Apply custom CSS for better visual appearance
37
+ st.markdown("""
38
+ <style>
39
+ /* Overall app styling */
40
+ .main .block-container {
41
+ padding-top: 1rem;
42
+ }
43
+
44
+ /* Chat container styling */
45
+ .stChatMessage {
46
+ padding: 1rem;
47
+ border-radius: 0.5rem;
48
+ margin-bottom: 1rem;
49
+ border: 1px solid rgba(38, 70, 83, 0.1);
50
+ }
51
+
52
+ /* User message styling */
53
+ .stChatMessage[data-testid="user"] {
54
+ background-color: rgba(231, 111, 81, 0.1);
55
+ color: #264653;
56
+ border-left: 4px solid #E76F51;
57
+ }
58
+
59
+ /* Assistant message styling */
60
+ .stChatMessage[data-testid="assistant"] {
61
+ background-color: rgba(42, 157, 143, 0.1);
62
+ color: #264653;
63
+ border-left: 4px solid #2A9D8F;
64
+ }
65
+
66
+ /* Success message styling */
67
+ .stSuccess {
68
+ background-color: rgba(42, 157, 143, 0.2);
69
+ border-left: 4px solid #2A9D8F;
70
+ color: #264653;
71
+ }
72
+
73
+ /* Warning message styling */
74
+ .stWarning {
75
+ background-color: rgba(233, 196, 106, 0.2);
76
+ border-left: 4px solid #E9C46A;
77
+ color: #264653;
78
+ }
79
+
80
+ /* Error message styling */
81
+ .stError {
82
+ background-color: rgba(231, 111, 81, 0.2);
83
+ border-left: 4px solid #E76F51;
84
+ color: #264653;
85
+ }
86
+
87
+ /* Chat input box styling */
88
+ .stChatInputContainer {
89
+ background-color: rgba(42, 157, 143, 0.05);
90
+ border-radius: 0.5rem;
91
+ padding: 0.5rem;
92
+ }
93
+
94
+ .stTextInput input {
95
+ border: 1px solid #2A9D8F !important;
96
+ }
97
+
98
+ /* Template button styling */
99
+ .stButton button {
100
+ border: 1px solid #2A9D8F !important;
101
+ border-radius: 6px !important;
102
+ background-color: rgba(42, 157, 143, 0.1) !important;
103
+ color: #264653 !important;
104
+ font-weight: 500 !important;
105
+ box-shadow: none !important;
106
+ width: 100%;
107
+ text-align: center;
108
+ transition: all 0.2s ease;
109
+ }
110
+
111
+ /* Button hover effect */
112
+ .stButton button:hover {
113
+ background-color: rgba(42, 157, 143, 0.2) !important;
114
+ border-color: #2A9D8F !important;
115
+ transform: translateY(-1px);
116
+ box-shadow: 0 2px 4px rgba(42, 157, 143, 0.2) !important;
117
+ }
118
+
119
+ /* Specific button for clear chat */
120
+ [data-testid="baseButton-secondary"] {
121
+ border-color: #F4A261 !important;
122
+ background-color: rgba(244, 162, 97, 0.1) !important;
123
+ color: #264653 !important;
124
+ }
125
+
126
+ [data-testid="baseButton-secondary"]:hover {
127
+ background-color: rgba(244, 162, 97, 0.2) !important;
128
+ border-color: #F4A261 !important;
129
+ }
130
+
131
+ /* Sidebar styling */
132
+ .st-emotion-cache-16txtl3 {
133
+ background-color: rgba(38, 70, 83, 0.03);
134
+ }
135
+
136
+ /* Header and subheader styling */
137
+ h1, h2, h3 {
138
+ color: #264653;
139
+ }
140
+
141
+ /* Expander styling */
142
+ .streamlit-expanderHeader {
143
+ background-color: rgba(233, 196, 106, 0.1);
144
+ border-radius: 4px;
145
+ border: none;
146
+ color: #264653;
147
+ }
148
+
149
+ .streamlit-expanderHeader:hover {
150
+ background-color: rgba(233, 196, 106, 0.2);
151
+ }
152
+
153
+ /* Selectbox styling */
154
+ .stSelectbox label {
155
+ color: #264653;
156
+ }
157
+
158
+ .stSelectbox div[data-baseweb="select"] > div {
159
+ background-color: rgba(42, 157, 143, 0.05);
160
+ border-color: #2A9D8F;
161
+ }
162
+ </style>
163
+ """, unsafe_allow_html=True)
164
+
165
+ ############################################################################################################
166
+ # Initialize all session state variables - Persistent data between app reruns
167
+ ############################################################################################################
168
+
169
+ # Initialize core session state variables if they don't exist
170
+ if 'selected_term' not in st.session_state:
171
+ st.session_state.selected_term = None # Currently selected term
172
+ if 'selected_context' not in st.session_state:
173
+ st.session_state.selected_context = None # Context for the selected term
174
+ if 'display_messages' not in st.session_state:
175
+ st.session_state.display_messages = [] # Chat history
176
+ if 'display_term' not in st.session_state:
177
+ st.session_state.display_term = False # Whether to display the term
178
+ if 'initial_message_displayed' not in st.session_state:
179
+ st.session_state.initial_message_displayed = False # Initial message flag
180
+ if 'old_term' not in st.session_state:
181
+ st.session_state.old_term = None # Previously selected term
182
+ if 'seen_terms' not in st.session_state:
183
+ st.session_state.seen_terms = set() # Set of viewed terms
184
+ if 'openai_model' not in st.session_state:
185
+ st.session_state.openai_model = config.ai_model # AI model being used
186
+
187
+ ############################################################################################################
188
+ # Password protection - User authentication functionality
189
+ ############################################################################################################
190
 
191
+ def check_credentials():
192
+ """Verifies username and password against stored credentials.
193
+
194
+ Returns:
195
+ bool: True if credentials are valid, False otherwise.
196
+ """
197
  def credentials_entered():
198
+ """Callback for when credentials are entered by the user."""
199
  if (
200
  hmac.compare_digest(st.session_state["username"], st.secrets["username"]) and
201
  hmac.compare_digest(st.session_state["password"], st.secrets["password"])
202
  ):
203
  st.session_state["credentials_correct"] = True
204
+ del st.session_state["username"] # Remove for security
205
+ del st.session_state["password"] # Remove for security
206
  else:
207
  st.session_state["credentials_correct"] = False
208
 
209
+ # Return True if already validated in this session
210
  if st.session_state.get("credentials_correct", False):
211
  return True
212
 
213
+ # Display login form
214
  st.text_input("Username", on_change=credentials_entered, key="username")
215
  st.text_input("Password", type="password", on_change=credentials_entered, key="password")
216
 
217
+ # Log invalid login attempts for security monitoring
218
  if "credentials_correct" in st.session_state:
219
+ logging.warning("Invalid login attempt")
220
  return False
221
 
222
+ # Validate user credentials before proceeding
223
  if not check_credentials():
224
+ st.stop() # Stop execution if authentication fails
225
  else:
226
  logging.info("Successful login") # Log successful logins
227
 
228
  ############################################################################################################
229
+ # Loading Terms - Data loading and preparation functions
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
230
  ############################################################################################################
231
+
232
+ # Load the terms file
233
+ terms = pd.read_csv(config.default_terms_csv)
234
 
235
  def load_terms(file_path):
236
+ """Loads terms from a CSV file containing terms and definitions.
237
+
238
+ Args:
239
+ file_path (str): Path to the CSV file.
240
+
241
+ Returns:
242
+ DataFrame: Loaded terms data or empty DataFrame on error.
243
+ """
244
  try:
245
  return pd.read_csv(file_path)
246
  except Exception as e:
 
248
  logging.exception(f"Error loading file: {e}")
249
  return pd.DataFrame()
250
 
 
251
  def get_first_column_values(local_df):
252
+ """Extracts values from the first column of a DataFrame.
253
+
254
+ Args:
255
+ local_df (DataFrame): DataFrame containing terms.
256
+
257
+ Returns:
258
+ list: List of terms from the first column.
259
+ """
260
  if not local_df.empty:
261
  return local_df.iloc[:, 0].tolist()
262
  else:
263
  return []
264
 
265
+ # Prepare terms for the app
 
 
 
266
  terms = load_terms(config.default_terms_csv)
267
  term_list = get_first_column_values(terms)
268
 
 
269
  ############################################################################################################
270
+ # Streamlit app layout - Main UI components and interaction logic
271
+ ############################################################################################################
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
272
 
273
+ # Create two columns with a 1:2 ratio for the main layout
274
+ left_col, right_col = st.columns([1, 2])
275
 
276
+ # Left column for app info, term selection, current term, prompt templates, and instructions
277
+ with left_col:
278
+ # App header
279
+ st.header(config.app_title)
280
+ st.markdown("---")
 
 
281
 
282
+ # Term selection dropdown
283
+ selected_term = st.selectbox('**SELECT FROM THE DROPDOWN MENU**', term_list)
284
+ if selected_term:
285
+ # Display selected term
286
+ st.markdown(f"### {selected_term}")
287
+
288
+ # Handle new term selection
289
+ if selected_term != st.session_state.old_term:
290
+ # Get context for the selected term
291
+ term_context = terms[terms.iloc[:, 0] == selected_term].iloc[:, 1].values[0] if not terms.empty else ""
292
+
293
+ # Update session state
294
+ st.session_state.selected_context = term_context
295
+ st.session_state.selected_term = selected_term
296
+
297
+ # Create initial user message
298
+ user_message = f"What is one thing you know about '{selected_term}'? What do you want to know about it? This could include a definition, examples, misconceptions, associations with other course terms, opinions, etc. You may also choose one of the template buttons on the left to help you get started."
299
+ st.session_state["display_messages"].append({"role": "user", "content": user_message})
300
+
301
+ # Save current term as previous term
302
+ st.session_state.old_term = selected_term
303
+ st.rerun()
304
+
305
+ # Template buttons section
306
+ st.markdown("**Prompt Templates:**")
307
 
308
+ # Calculate layout for template buttons (3 per row)
309
+ buttons_per_row = 3
310
+ num_rows = (len(config.prompt_templates) + buttons_per_row - 1) // buttons_per_row
311
 
312
+ # Emoji mapping for templates
313
+ template_emojis = {
314
+ "Misconception Check": "❓",
315
+ "Two Truths & a Lie": "🎮",
316
+ "Connect Terms": "🔄",
317
+ "Schema Map": "🗺️",
318
+ "Create a Study Plan": "📚"
319
+ }
320
+
321
+ # Create rows of template buttons
322
+ for row in range(num_rows):
323
+ # Create columns for this row
324
+ start_idx = row * buttons_per_row
325
+ end_idx = min(start_idx + buttons_per_row, len(config.prompt_templates))
326
+ btn_cols = st.columns(end_idx - start_idx)
327
+
328
+ # Add buttons for this row
329
+ for i, template in enumerate(config.prompt_templates[start_idx:end_idx]):
330
+ template_name = template["name"]
331
+ # Add emoji to template name
332
+ button_text = f"{template_emojis.get(template_name, '')} {template_name}"
333
+
334
+ # Create button and handle click
335
+ if btn_cols[i].button(button_text, key=f"btn_{row}_{i}"):
336
+ # Format template with appropriate variables
337
+ if "term_list" in template["template"]:
338
+ formatted_content = template["template"].format(term=selected_term, term_list=term_list)
339
+ else:
340
+ formatted_content = template["template"].format(term=selected_term)
341
+
342
+ # Add to chat messages and trigger LLM response
343
+ st.session_state.display_messages.append({"role": "user", "content": formatted_content})
344
+ st.session_state["trigger_llm"] = True
345
+ st.rerun()
346
+
347
+ # Instructions for students
348
+ with st.expander("INSTRUCTIONS FOR STUDENTS:"):
349
+ st.markdown(config.instructions)
350
+
351
+ # Right column for chat window and input
352
+ with right_col:
353
+ # Main chat container - displays conversation history
354
+ with st.container(height=450, border=True):
355
+ # Display previous chat messages
356
+ for message in st.session_state["display_messages"][1:]:
357
+ if message["role"] == "user":
358
+ with st.chat_message("user"):
359
+ st.markdown(message["content"])
360
+ else:
361
+ with st.chat_message("assistant"):
362
+ st.markdown(message["content"])
363
+
364
+ # Generate and display AI response when triggered
365
+ if st.session_state.get("trigger_llm", False):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
366
  try:
367
+ # Initialize OpenAI client
368
+ client = OpenAI(api_key=st.secrets["OPENAI_API_KEY"])
369
+
370
+ # Generate system message with term context
371
+ system_message = config.term_prompt(
372
+ selected_term=st.session_state.selected_term,
373
+ selected_context=st.session_state.selected_context,
374
+ term_list=term_list
375
+ )
376
+
377
+ # Prepare messages for API call
378
+ messages = [{"role": "system", "content": system_message}] + [
379
+ {"role": m["role"], "content": m["content"]}
380
+ for m in st.session_state["display_messages"]
381
+ ]
382
+
383
+ # Create streaming completion
384
  stream = client.chat.completions.create(
385
  model=st.session_state["openai_model"],
386
+ messages=messages,
 
 
 
387
  stream=True,
388
  temperature=config.temperature,
389
  max_tokens=config.max_tokens,
390
  frequency_penalty=config.frequency_penalty,
391
  presence_penalty=config.presence_penalty,
392
  )
393
+
394
+ # Display streaming response
395
+ with st.chat_message("assistant"):
396
+ response = st.write_stream(stream)
397
+ # Save response to chat history
398
+ st.session_state["display_messages"].append({"role": "assistant", "content": response})
399
+ # Log the exchange
400
+ logging.info(f"User prompt: {st.session_state['display_messages'][-2]['content']}")
401
+ logging.info(f"Assistant response: {response}")
402
  except Exception as e:
403
+ # Handle errors
404
  st.error(f"An error occurred: {str(e)}")
405
+ logging.exception(f"Error generating response: {e}")
406
+
407
+ # Reset trigger flag after response is generated
408
+ st.session_state["trigger_llm"] = False
409
 
410
+ # Chat input field
411
+ prompt = st.chat_input("What do you know? What do you want to know?")
412
+ if prompt:
413
+ # Add user message to chat and trigger LLM response
414
+ st.session_state.display_messages.append({"role": "user", "content": prompt})
415
+ st.session_state["trigger_llm"] = True
416
+ st.rerun()
417
 
418
+ # Clear chat history button
419
+ if st.button("Clear Chat History"):
420
+ st.session_state["display_messages"] = []
421
+ st.session_state["trigger_llm"] = False
422
+ st.rerun()
423
 
424
+ # Warning message about AI limitations
425
+ st.markdown(config.warning_message, unsafe_allow_html=True)
426
 
427
+ ############################################################################################################
428
+ # Sidebar Content - Resources and information
429
+ ############################################################################################################
430
 
431
+ # Resources section
432
  st.sidebar.title("Resources")
433
 
434
+ # Display each resource
435
  for resource in config.resources:
436
+ st.sidebar.markdown(f"### {resource['title']}")
437
+ st.sidebar.markdown(resource['description'])
438
+
439
+ # Add URL link if available
440
+ if "url" in resource:
441
+ st.sidebar.markdown(f"[Open Link]({resource['url']})")
442
+
443
+ # Add file download button if available
444
+ if "file_path" in resource:
445
+ with open(resource["file_path"], "rb") as file:
446
+ btn = st.sidebar.download_button(
447
+ label=f"Download {resource['title']}",
448
+ data=file,
449
+ file_name=os.path.basename(resource["file_path"]),
450
+ mime="application/pdf"
451
+ )
452
+
453
+ # Separator between resources
454
+ st.sidebar.markdown("---")
455
+
456
+ # About section
457
+ st.sidebar.markdown("### About")
458
+ st.sidebar.markdown(config.app_creation_message)
459
+ st.sidebar.markdown("---")
460
+ st.sidebar.markdown(config.app_repo_license_message)
461
+ st.sidebar.markdown("---")