keefereuther commited on
Commit
9c43c64
·
verified ·
1 Parent(s): 19acbf6

Upload app.py

Browse files
Files changed (1) hide show
  1. app.py +306 -0
app.py ADDED
@@ -0,0 +1,306 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ############################################################################################################
2
+ # Importing Libraries
3
+
4
+ import streamlit as st
5
+ import pandas as pd
6
+ import random
7
+ import os
8
+ import time
9
+ import base64
10
+ import logging
11
+ import io
12
+ import config
13
+ from openai import OpenAI
14
+
15
+ # Set up logging
16
+ logging.basicConfig(filename='app.log', level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s', datefmt='%Y-%m-%d %H:%M:%S')
17
+
18
+ ############################################################################################################
19
+ # Streamlit app layout
20
+
21
+ # Set the page to wide or centered mode
22
+ st.set_page_config(layout="wide", initial_sidebar_state="expanded")
23
+
24
+ # Load the terms file into a DataFrame
25
+ df = pd.read_csv(config.default_terms_csv)
26
+
27
+ # Streamlit app layout
28
+ st.title(config.app_title)
29
+ st.markdown(config.intro_para)
30
+ st.caption(config.app_author)
31
+
32
+ # API Key Input in sidebar
33
+
34
+ with st.sidebar:
35
+ st.header("Configuration")
36
+
37
+ # API Key Input Field
38
+ api_key = st.text_input(
39
+ "OpenAI API Key",
40
+ type="password",
41
+ key="api_key",
42
+ help="This is the API key for your OpenAI account. You can find it [here](https://platform.openai.com/api-keys)."
43
+ )
44
+ if api_key:
45
+ st.session_state["OPENAI_API_KEY"] = api_key
46
+ else:
47
+ st.warning("Please provide your OpenAI API key to enable chat functionality.")
48
+
49
+ st.sidebar.title(config.sidebar_title)
50
+
51
+ with st.sidebar:
52
+ with st.expander("Click here for instructions."):
53
+ st.write(config.sidebar_instructions)
54
+
55
+
56
+ # File Uploader in sidebar
57
+
58
+ # Load terms from a CSV file
59
+ def load_terms(file_input):
60
+ try:
61
+ if isinstance(file_input, str):
62
+ data = pd.read_csv(file_input)
63
+ else:
64
+ data = pd.read_csv(io.StringIO(file_input.read().decode('utf-8')))
65
+ return data
66
+ except Exception as e:
67
+ st.error(f"An error occurred while loading the file: {str(e)}")
68
+ logging.exception(f"Error loading file: {e}")
69
+
70
+ # Function to create a download link for a file
71
+ def create_download_link(file_path, file_name):
72
+ try:
73
+ with open(file_path, "rb") as file:
74
+ file_content = file.read()
75
+ encoded_content = base64.b64encode(file_content).decode("utf-8")
76
+ download_link = f'<a href="data:file/csv;base64,{encoded_content}" download="{file_name}">Download {file_name}</a>'
77
+ return download_link
78
+ except FileNotFoundError:
79
+ error_message = f"The file {file_name} was not found."
80
+ st.error(error_message)
81
+ logging.exception(error_message)
82
+ except Exception as e:
83
+ error_message = f"An error occurred: {str(e)}"
84
+ st.error(error_message)
85
+ logging.exception(error_message)
86
+
87
+ # Function to extract the first column values
88
+ def get_first_column_values(df):
89
+ if not df.empty:
90
+ return df.iloc[:, 0].tolist()
91
+ else:
92
+ return []
93
+
94
+ # Download link for the template file
95
+ template_file_path = config.default_terms_csv
96
+
97
+ # File Uploader
98
+ uploaded_file = st.sidebar.file_uploader(" ", type=["csv"])
99
+ if uploaded_file is not None:
100
+ logging.info(f"File uploaded: {uploaded_file.name}")
101
+ st.session_state.uploaded_file = uploaded_file
102
+
103
+ # Load terms from the file
104
+ if 'uploaded_file' in st.session_state and st.session_state.uploaded_file is not None:
105
+ terms = load_terms(st.session_state.uploaded_file)
106
+ else:
107
+ terms = load_terms(template_file_path)
108
+
109
+ # Extract first column values
110
+ term_list = get_first_column_values(terms)
111
+
112
+ st.sidebar.markdown(create_download_link(template_file_path, "terms.csv"), unsafe_allow_html=True)
113
+
114
+ # line break in the sidebar
115
+ st.sidebar.markdown('<hr>', unsafe_allow_html=True)
116
+
117
+ ############################################################################################################
118
+ # Term Selection and session state
119
+
120
+ # Initialize the session state variables for selected term, context, and display messages
121
+ if 'selected_term' not in st.session_state:
122
+ st.session_state.selected_term = None
123
+ if 'selected_context' not in st.session_state:
124
+ st.session_state.selected_context = None
125
+ if 'display_messages' not in st.session_state:
126
+ st.session_state.display_messages = []
127
+
128
+ # Initialize session states for the selected term, counter, and display flag
129
+ if 'display_term' not in st.session_state:
130
+ st.session_state.display_term = False
131
+ if 'initial_message_displayed' not in st.session_state:
132
+ st.session_state.initial_message_displayed = False
133
+
134
+ # Initialize state to track the previously selected term
135
+ if 'old_term' not in st.session_state:
136
+ st.session_state.old_term = None
137
+
138
+ # Dropdown menu for selecting a term
139
+ selected_term = st.selectbox('**SELECT FROM THE DROPDOWN MENU**', term_list)
140
+
141
+ if selected_term:
142
+ # If a new term is selected (including first time selection), reset or show the message
143
+ if selected_term != st.session_state.old_term:
144
+ user_message = f"What is one thing you know or want to know about '{selected_term}'? Let's have a conversation! I love to ask follow-up questions. Feel free to answer those or ask any other course relevant question."
145
+ st.session_state["display_messages"].append({"role": "user", "content": user_message})
146
+ # Update old_term in session state
147
+ st.session_state.old_term = selected_term
148
+
149
+ selected_context = terms.loc[terms['TERM'] == selected_term, 'CONTEXT'].values[0]
150
+ st.session_state.selected_term = selected_term
151
+ st.session_state.selected_context = selected_context
152
+ st.session_state.display_term = True
153
+
154
+ # Update the prompt for the API
155
+ updated_prompt = config.term_prompt(st.session_state.selected_term, st.session_state.selected_context, term_list)
156
+
157
+ else:
158
+ # If nothing is selected or the selection is cleared, reset the old_term
159
+ st.session_state.old_term = None
160
+
161
+ # Display the selected term and its context
162
+ if st.session_state.display_term and st.session_state.selected_term:
163
+ st.header(st.session_state.selected_term)
164
+
165
+ with st.expander("INSTRUCTIONS FOR STUDENTS:"):
166
+ st.markdown(config.instructions)
167
+ with st.expander("**INSTRUCTORS**: For a look at the current terms file driving the interaction, click here:"):
168
+ st.markdown("This is the terms.csv file that drives the interaction. You can edit this file to change the terms and context that the chatbot uses. You may add any term or phrase. You may leave the context blank if you prefer or you can add anything relevant that the GPT does not normally know about the term. This may include relevant learning objectives, course examples, notable scientists, assessment dates, syllabus information, etc.")
169
+ st.table(df)
170
+ with st.expander("**INSTRUCTORS**: For a look at the prompt driving the chatbot, click here:"):
171
+ prompt_text = config.term_prompt(st.session_state.selected_term, st.session_state.selected_context, term_list)
172
+ st.markdown(prompt_text)
173
+
174
+ ############################################################################################################
175
+ # ChatGPT
176
+
177
+ # Initialize the OpenAI client
178
+ if "OPENAI_API_KEY" in st.session_state:
179
+ client = OpenAI(api_key=st.session_state["OPENAI_API_KEY"])
180
+ else:
181
+ client = None
182
+
183
+ # Initialize the session state variables if they don't exist
184
+ if "openai_model" not in st.session_state:
185
+ st.session_state["openai_model"] = config.ai_model
186
+
187
+ if "display_messages" not in st.session_state:
188
+ st.session_state.display_messages = []
189
+
190
+ # Update initial_context with the latest selected term and context
191
+ if st.session_state.get('selected_term') and st.session_state.get('selected_context'):
192
+ updated_prompt = config.term_prompt(st.session_state.selected_term, st.session_state.selected_context, term_list)
193
+ # Replace the initial context in display_messages with the updated prompt
194
+ if st.session_state.display_messages:
195
+ st.session_state.display_messages[0]["content"] = updated_prompt
196
+ else:
197
+ st.session_state.display_messages = [{"role": "system", "content": updated_prompt}]
198
+
199
+ # Get user input
200
+ prompt = st.chat_input("What do you know? What do you want to know?")
201
+
202
+ # Input for new messages
203
+ if prompt:
204
+ # Ensure the initial context is in the session state, add the user's message
205
+ if not st.session_state["display_messages"]:
206
+ st.session_state["display_messages"].append({"role": "system", "content": updated_prompt})
207
+ st.session_state["display_messages"].append({"role": "user", "content": prompt})
208
+
209
+ # Function to reset all chat-related session state
210
+ def reset_chat_history():
211
+ st.session_state["display_messages"] = []
212
+ # Reset other chat-related session states if they exist
213
+ if 'selected_term' in st.session_state:
214
+ st.session_state.selected_term = None
215
+ if 'selected_context' in st.session_state:
216
+ st.session_state.selected_context = None
217
+ if 'display_term' in st.session_state:
218
+ st.session_state.display_term = False
219
+ st.rerun()
220
+
221
+ # Main chat container
222
+ with st.container(height=400, border=True):
223
+ # Display chat history in reverse order including new messages
224
+ for message in st.session_state["display_messages"][1:]:
225
+ if message["role"] == "user":
226
+ with st.chat_message("user"):
227
+ st.markdown(message["content"])
228
+ else:
229
+ with st.chat_message("assistant"):
230
+ st.markdown(message["content"])
231
+
232
+ # Generate assistant's response and add it to the messages
233
+ if prompt:
234
+ if client:
235
+ with st.chat_message("assistant"):
236
+ try:
237
+ stream = client.chat.completions.create(
238
+ model=st.session_state["openai_model"],
239
+ messages=[
240
+ {"role": m["role"], "content": m["content"]}
241
+ for m in st.session_state["display_messages"]
242
+ ],
243
+ stream=True,
244
+ temperature=config.temperature,
245
+ max_tokens=config.max_tokens,
246
+ frequency_penalty=config.frequency_penalty,
247
+ presence_penalty=config.presence_penalty,
248
+ )
249
+ response = st.write_stream(stream)
250
+ # Append the full response to the session state for display
251
+ st.session_state["display_messages"].append(
252
+ {"role": "assistant", "content": response}
253
+ )
254
+ logging.info(f"User prompt: {prompt}") # Log user prompts
255
+ logging.info(f"Assistant response: {response}") # Log assistant responses
256
+ except Exception as e:
257
+ st.error(f"An error occurred: {str(e)}")
258
+ logging.exception(f"Error generating response: {e}") # Log errors
259
+ else:
260
+ st.warning("Please provide an OpenAI API key to enable the chat functionality.")
261
+
262
+ # Add Clear Chat History button between container and warning message
263
+ if st.button("Clear Chat History"):
264
+ reset_chat_history()
265
+ logging.info("Chat history cleared") # Log when chat history is cleared
266
+
267
+ st.markdown(config.warning_message, unsafe_allow_html=True)
268
+
269
+ ############################################################################################################
270
+
271
+ # Resources and About Sections in the Sidebar
272
+
273
+ st.sidebar.title("Resources")
274
+
275
+ for resource in config.resources:
276
+ with st.sidebar:
277
+ with st.sidebar:
278
+ with st.expander(resource["title"]):
279
+ st.markdown(f"Description: {resource['description']}")
280
+ if "url" in resource:
281
+ st.markdown(f"[{resource['title']}]({resource['url']})")
282
+ if "file_path" in resource:
283
+ file_path = resource["file_path"]
284
+ if os.path.exists(file_path):
285
+ with open(file_path, "rb") as file:
286
+ file_bytes = file.read()
287
+ with st.spinner(f"Loading {resource['title']}..."):
288
+ st.download_button(
289
+ label=resource["title"],
290
+ data=file_bytes,
291
+ file_name=os.path.basename(file_path),
292
+ mime="application/octet-stream",
293
+ help=resource["description"],
294
+ )
295
+ else:
296
+ st.warning(f"File not found: {file_path}")
297
+
298
+ # Footer
299
+ with st.sidebar:
300
+ st.markdown("---")
301
+
302
+ st.title("About")
303
+
304
+ # Using the config objects in your Streamlit app
305
+ st.markdown(config.app_creation_message, unsafe_allow_html=True)
306
+ st.markdown(config.app_repo_license_message, unsafe_allow_html=True)