From 5783e0cbbdcde24edad2abff7904388baa455b86 Mon Sep 17 00:00:00 2001 From: ammar Date: Thu, 30 Nov 2023 21:16:06 -0500 Subject: [PATCH 01/27] - can upload class level files to blob storage - files would go into class/ - todo: module management --- Scripts/__fileupload.py | 152 ++++++++++++---------------------------- Scripts/__sidebar.py | 45 ++++++++---- Scripts/azblob.py | 71 +++++++++++++++++++ Scripts/sessionvars.py | 14 +++- requirements.txt | 1 + temp.ipynb | 72 +++++++++++++++++++ 6 files changed, 231 insertions(+), 124 deletions(-) create mode 100644 Scripts/azblob.py create mode 100644 temp.ipynb diff --git a/Scripts/__fileupload.py b/Scripts/__fileupload.py index c127d41..3c725ec 100644 --- a/Scripts/__fileupload.py +++ b/Scripts/__fileupload.py @@ -1,128 +1,64 @@ ''' This file contains the file upload functionality for the dashboard page ''' -import openai import os -import re import streamlit as st from io import BytesIO -from typing import Tuple, List -from dotenv import load_dotenv -from Scripts import azsqldb, sessionvars -from langchain.docstore.document import Document -from langchain.embeddings.openai import OpenAIEmbeddings -from langchain.vectorstores.azuresearch import AzureSearch -from langchain.text_splitter import RecursiveCharacterTextSplitter -from pypdf import PdfReader +from dotenv import load_dotenv, find_dotenv +from Scripts import azsqldb, sessionvars, azblob as azb +from random import randint -BASEDIR = os.path.abspath(os.path.dirname(os.path.dirname(__file__))) -load_dotenv(os.path.join(BASEDIR, '.env')) +load_dotenv(find_dotenv()) sessionvars.initialize_session_vars() -def create_class_index(): - """ - Create the index for the class if it doesn't exist - """ - # Replace spaces with dashes and convert to lowercase - index_name = st.session_state.class_info['class_name'].replace(" ", "-").lower() - # Replace all non-alphanumeric characters (except dashes) with empty strings - index_name = re.sub(r'[^a-z0-9-]', '', index_name) - # Remove leading and trailing dashes - index_name = index_name.strip('-') - # Create the index in Azure Cognitive Search, and update the database - # Initialize our embedding model - embeddings=OpenAIEmbeddings(openai_api_key=os.getenv('OPENAI_API_KEY'), - model="text-embedding-ada-002", - chunk_size=1000) - # Set our Azure Search - acs = AzureSearch(azure_search_endpoint=os.getenv('AZURE_AI_SEARCH_ENDPOINT'), - azure_search_key=os.getenv('AZURE_AI_SEARCH_API_KEY'), - index_name=index_name, - embedding_function=embeddings.embed_query) - # Update the database with the new class index name - azsqldb.update_class(st.session_state.sqlcursor, - st.session_state.class_info['class_id'], - 'index_name', - index_name) - st.session_state.class_info['index_name'] = index_name +if st.session_state.user_info['role'] == 'teacher': + help_text = 'Upload your class materials here!' +else: + help_text = 'Upload your notes here!' - return index_name - -def parse_pdf(file, filename: str) -> Tuple[List[str], str]: - pdf = PdfReader(file) - output = [] - for page in pdf.pages: - text = page.extract_text() - text = re.sub(r"(\w+)-\n(\w+)", r"\1\2", text) - text = re.sub(r"(? List[Document]: - if isinstance(text, str): - text = [text] - page_docs = [Document(page_content=page) for page in text] - for i, doc in enumerate(page_docs): - doc.metadata["page"] = i + 1 +def upload_class_file(): + if st.session_state.show_upload_file: - doc_chunks = [] - for doc in page_docs: - text_splitter = RecursiveCharacterTextSplitter( - chunk_size=4000, - separators=["\n\n", "\n", ".", "!", "?", ",", " ", ""], - chunk_overlap=0, - ) - chunks = text_splitter.split_text(doc.page_content) - for i, chunk in enumerate(chunks): - doc = Document( - page_content=chunk, metadata={"page": doc.metadata["page"], "chunk": i} - ) - doc.metadata["source"] = f"{doc.metadata['page']}-{doc.metadata['chunk']}" - doc.metadata["filename"] = filename # Add filename to metadata - doc_chunks.append(doc) - return doc_chunks + files = st.file_uploader("Upload files relevant to the class as a whole ie syllabus, schedule, etc", + accept_multiple_files=True, + help=help_text, + key = st.session_state.upload_key) + if st.button("Submit", key='class_upload_submit'): + # Display a warning if the user hasn't uploaded a file + if not files: + st.warning("Please upload a file first!") + else: + with st.spinner("Uploading your files..."): + for file in files: + file_stream = BytesIO(file.getvalue()) + blob_name = st.session_state.class_info['class_name'] + '/' + file.name + azb.upload_file_to_blob(file_stream, blob_name) + + # Reset the file uploader widget + st.session_state.upload_key = str(randint(1000, 1000000)) -def upload_file(): +def upload_module_file(): if st.button("Upload File"): - st.session_state.show_upload_file = not st.session_state.show_upload_file + st.session_state.show_upload_file2 = not st.session_state.show_upload_file2 - if st.session_state.show_upload_file: - pdf_files = st.file_uploader("Upload your files here", + if st.session_state.show_upload_file2: + + files = st.file_uploader("Upload files relevant to the lesson", accept_multiple_files=True, - help="Only .pdf files only please", - type='pdf') + help=help_text, + key = st.session_state.upload_key_2) if st.button("Submit"): # Display a warning if the user hasn't uploaded a file - if not pdf_files: + if not files: st.warning("Please upload a file first!") - - with st.spinner("Uploading your files..."): - pdf_names = [file.name for file in pdf_files] # get the names for each file - - # Create the index if it doesn't exist - if st.session_state.class_info['index_name'] is None: - index_name = create_class_index() - - # Upload the document to Azure Cognitive Search - #1 Parse the PDF - documents = [] - for pdf_file, pdf_name in zip(pdf_files, pdf_names): - text, filename = parse_pdf(pdf_file, pdf_name) - documents = documents + text_to_docs(text, filename) - st.write(f"Here is the documents: {documents}") - #2 Upload the documents to Azure Cognitive Search - embeddings=OpenAIEmbeddings(openai_api_key=os.getenv('OPENAI_API_KEY'), - model="text-embedding-ada-002", - chunk_size=1000) - - acs = AzureSearch(azure_search_endpoint=os.getenv('AZURE_AI_SEARCH_ENDPOINT'), - azure_search_key=os.getenv('AZURE_AI_SEARCH_API_KEY'), - index_name=st.session_state.class_info['index_name'], - embedding_function=embeddings.embed_query) - - acs.add_documents(documents=documents) - - \ No newline at end of file + else: + with st.spinner("Uploading your files..."): + for file in files: + file_stream = BytesIO(file.getvalue()) + blob_name = st.session_state.class_info['class_name'] + '/' + file.name + azb.upload_file_to_blob(file_stream, blob_name) + + # Reset the file uploader widget + st.session_state.upload_key_2 = str(randint(1000, 1000000)) \ No newline at end of file diff --git a/Scripts/__sidebar.py b/Scripts/__sidebar.py index 4994d40..8300a9f 100644 --- a/Scripts/__sidebar.py +++ b/Scripts/__sidebar.py @@ -50,29 +50,44 @@ def teacher_sidebar(): st.selectbox("Select class:", ["No classes available"]) st.write("You haven't created any classes yet.") - # Button to create a new class - if st.button("Create a new class"): - st.session_state.show_new_class_input = not st.session_state.show_new_class_input + col1, col2 = st.columns([1,1]) + with col1: + # Button to create a new class + if st.button("Create a new class"): + st.session_state.show_new_class_input = not st.session_state.show_new_class_input + st.session_state.show_upload_file = False + + with col2: + # Button to upload class level files + if st.button("Upload File", key='class_upload'): + st.session_state.show_upload_file = not st.session_state.show_upload_file ## Upload class files + st.session_state.show_new_class_input = False + + + # Block to create a new class if st.session_state.show_new_class_input: # Input field and button for new class creation - if st.session_state.show_new_class_input: - new_class_name = st.text_input("Enter the name for the new class") - if st.button("Submit New Class"): - if new_class_name: - azsqldb.new_class(st.session_state.user_info['user_id'], st.session_state.sqlcursor, new_class_name) - class_data = fetch_class_data() # Refresh the class data - st.session_state.selected_class_name = new_class_name # Update the selected class name - st.session_state.show_new_class_input = False # Hide the input fields after submission - st.experimental_rerun() # Rerun the script to reflect the changes - + new_class_name = st.text_input("Enter the name for the new class") + if st.button("Submit New Class"): + if new_class_name: + azsqldb.new_class(st.session_state.user_info['user_id'], st.session_state.sqlcursor, new_class_name) + class_data = fetch_class_data() # Refresh the class data + st.session_state.selected_class_name = new_class_name # Update the selected class name + st.session_state.show_new_class_input = False # Hide the input fields after submission + st.experimental_rerun() # Rerun the script to reflect the changes + + # Block to upload class level files + if st.session_state.show_upload_file: + fu.upload_class_file() + ### Faq functions st.sidebar.title("FAQs") fq.teacher_faqs() #file upload st.sidebar.title("Upload Files") - fu.upload_file() + fu.upload_module_file() #schedule st.sidebar.title("Manage Assignments") @@ -129,7 +144,7 @@ def student_sidebar(): #file upload st.sidebar.title("Upload Files") - fu.upload_file() + fu.upload_module_file() #schedule st.sidebar.title("Upcoming Assignments") diff --git a/Scripts/azblob.py b/Scripts/azblob.py new file mode 100644 index 0000000..125cff8 --- /dev/null +++ b/Scripts/azblob.py @@ -0,0 +1,71 @@ +# **************************************************************************** # +# # +# ::: :::::::: # +# azblob.py :+: :+: :+: # +# +:+ +:+ +:+ # +# By: ammar syed ali 1\u001b[0m blob_service_client \u001b[39m=\u001b[39m BlobServiceClient\u001b[39m.\u001b[39mfrom_connection_string(os\u001b[39m.\u001b[39mgetenv(\u001b[39m\"\u001b[39m\u001b[39mAZURE_STORAGE_CONNECTION_STRING\u001b[39m\u001b[39m\"\u001b[39m))\n", + "\u001b[1;31mNameError\u001b[0m: name 'os' is not defined" + ] + } + ], + "source": [ + "blob_service_client = BlobServiceClient.from_connection_string(os.getenv(\"AZURE_STORAGE_CONNECTION_STRING\"))\n" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": ".venv", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.6" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} From 9cdc9812d1ec8b76916dc73f377576f1a9b84132 Mon Sep 17 00:00:00 2001 From: ammar Date: Fri, 1 Dec 2023 09:13:45 -0500 Subject: [PATCH 02/27] - splitting the class management from sidebar - teacher class functions complete --- Scripts/__classmanager.py | 49 +++++++++++++++++++++++++++++++++++++++ Scripts/__fileupload.py | 36 ++++++++++++++-------------- Scripts/__sidebar.py | 30 ++++-------------------- 3 files changed, 70 insertions(+), 45 deletions(-) create mode 100644 Scripts/__classmanager.py diff --git a/Scripts/__classmanager.py b/Scripts/__classmanager.py new file mode 100644 index 0000000..e2027bf --- /dev/null +++ b/Scripts/__classmanager.py @@ -0,0 +1,49 @@ +# **************************************************************************** # +# # +# ::: :::::::: # +# __classmanager.py :+: :+: :+: # +# +:+ +:+ +:+ # +# By: ammar syed ali Date: Fri, 1 Dec 2023 09:38:47 -0500 Subject: [PATCH 03/27] - student class functions in separate module - messed around with the button sizes --- Scripts/__classmanager.py | 22 ++++++++++++++++++++-- Scripts/__sidebar.py | 32 +++----------------------------- 2 files changed, 23 insertions(+), 31 deletions(-) diff --git a/Scripts/__classmanager.py b/Scripts/__classmanager.py index e2027bf..3087507 100644 --- a/Scripts/__classmanager.py +++ b/Scripts/__classmanager.py @@ -28,14 +28,18 @@ def show_class(): # Select box for choosing the class selected_class_name = st.selectbox("Select class:", list(class_data.keys()), index=list(class_data.keys()).index(st.session_state.selected_class_name) if st.session_state.selected_class_name in class_data else 0) st.session_state.selected_class_name = selected_class_name - # Display the class code for the selected class + # Save the class code for the selected class if selected_class_name: selected_class_info = class_data[selected_class_name] st.session_state.class_info = selected_class_info st.write(f"Class Code: {selected_class_info['class_code']}") else: st.selectbox("Select class:", ["No classes available"]) - st.write("You haven't created any classes yet.") + # if-else to show different messages for teachers and students with no classes + if st.session_state.user_info['role'] == 'teacher': + st.write("You haven't created any classes yet.") + else: + st.write("You are not enrolled in any classes yet.") def create_new_class(): # Input field and button for new class creation @@ -46,4 +50,18 @@ def create_new_class(): class_data = fetch_class_data() # Refresh the class data st.session_state.selected_class_name = new_class_name # Update the selected class name st.session_state.show_new_class_input = False # Hide the input fields after submission + st.experimental_rerun() # Rerun the script to reflect the changes + +def join_class(): + # Input field and button for joining a new class + new_class_code = st.text_input("Enter the class code") + join_class_button = st.button("Join Class", use_container_width= True) + # Block to handle form submission + if join_class_button and new_class_code: + join_message = azsqldb.join_class(st.session_state.user_info['user_id'], st.session_state.sqlcursor, new_class_code) + st.warning(join_message) + # Handle the a successful class join + if join_message == "You have successfully joined the class!": + class_data = fetch_class_data() # Refresh the class data + st.session_state.selected_class_name = list(class_data.keys())[-1] # Update the selected class name to the newly joined class st.experimental_rerun() # Rerun the script to reflect the changes \ No newline at end of file diff --git a/Scripts/__sidebar.py b/Scripts/__sidebar.py index acfa756..ded7b17 100644 --- a/Scripts/__sidebar.py +++ b/Scripts/__sidebar.py @@ -71,12 +71,8 @@ def teacher_sidebar(): st.sidebar.title("Manage Assignments") sc.teacher_schedule() - - def student_sidebar(): - # Fetch class data - class_data = fetch_class_data() # Sidebar for class selection and new class joining with st.sidebar: st.write(""" @@ -86,36 +82,14 @@ def student_sidebar(): - **Upload Files**: Upload your notes, outlines, etc. 📚 """) st.sidebar.title("Manage Classes") - if class_data: - # Select box for choosing the class - selected_class_name = st.selectbox("Select class:", list(class_data.keys()), index=list(class_data.keys()).index(st.session_state.selected_class_name) if st.session_state.selected_class_name in class_data else 0) - st.session_state.selected_class_name = selected_class_name - # Display the class info for the selected class - if selected_class_name: - selected_class_info = class_data[selected_class_name] - st.session_state.class_info = selected_class_info - else: - st.selectbox("Select class:", ["No classes available"]) - st.write("You are not enrolled in any classes yet.") + cm.show_class() + # Button to join a new class if st.button("Join a new class"): st.session_state.show_join_class_input = not st.session_state.show_join_class_input if st.session_state.show_join_class_input: - # Input field and button for joining a new class - if st.session_state.show_join_class_input: - new_class_code = st.text_input("Enter the class code") - join_class_button = st.button("Join Class") - # Block to handle form submission - if join_class_button and new_class_code: - join_message = azsqldb.join_class(st.session_state.user_info['user_id'], st.session_state.sqlcursor, new_class_code) - st.warning(join_message) - # Handle the a successful class join - if join_message == "You have successfully joined the class!": - class_data = fetch_class_data() # Refresh the class data - st.session_state.selected_class_name = list(class_data.keys())[-1] # Update the selected class name to the newly joined class - st.experimental_rerun() # Rerun the script to reflect the changes - + cm.join_class() st.sidebar.title("FAQs") fq.student_faqs() From 2693cfce9ede9f4ed7500918c449950f82e27db3 Mon Sep 17 00:00:00 2001 From: ammar Date: Fri, 1 Dec 2023 09:45:06 -0500 Subject: [PATCH 04/27] - DDL for modules table --- SQL Scripts/Objects.sql | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/SQL Scripts/Objects.sql b/SQL Scripts/Objects.sql index b74622e..5afae8c 100644 --- a/SQL Scripts/Objects.sql +++ b/SQL Scripts/Objects.sql @@ -34,3 +34,11 @@ CREATE TABLE STUDYBUDDY.FAQs ( CONSTRAINT FK_FAQs_Class FOREIGN KEY (class_id) REFERENCES master.STUDYBUDDY.Classes(class_id), CONSTRAINT FK_FAQs_User FOREIGN KEY (user_id) REFERENCES STUDYBUDDY.Users(user_id) ); + +CREATE TABLE master.STUDYBUDDY.Modules ( + module_id int IDENTITY(1,1) NOT NULL, + module_name nvarchar(100) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL, + class_id int NOT NULL, + CONSTRAINT PK__Modules__ModuleID PRIMARY KEY (module_id), + CONSTRAINT FK_ClassModule FOREIGN KEY (class_id) REFERENCES master.STUDYBUDDY.Classes(class_id) +); \ No newline at end of file From 5c5b60468e6a5d16fa9ad13cefb112ce4c97658c Mon Sep 17 00:00:00 2001 From: ammar Date: Fri, 1 Dec 2023 11:40:41 -0500 Subject: [PATCH 05/27] - teacher can delete modules now --- Scripts/__modules.py | 73 ++++++++++++++++++++++++++++++++++++++++++ Scripts/__sidebar.py | 26 +++++++++++---- Scripts/azsqldb.py | 46 +++++++++++++++++++++++++- Scripts/sessionvars.py | 18 +++++++++++ 4 files changed, 156 insertions(+), 7 deletions(-) create mode 100644 Scripts/__modules.py diff --git a/Scripts/__modules.py b/Scripts/__modules.py new file mode 100644 index 0000000..ed39f3e --- /dev/null +++ b/Scripts/__modules.py @@ -0,0 +1,73 @@ +# **************************************************************************** # +# # +# ::: :::::::: # +# __modules.py :+: :+: :+: # +# +:+ +:+ +:+ # +# By: ammar syed ali Date: Sat, 2 Dec 2023 13:59:48 -0500 Subject: [PATCH 06/27] - made the UI a little nicer - add and delete modules - new modules and classes require learning outcomes - file uploads to azure blob at the class and module level --- Scripts/__classmanager.py | 21 +++++++++++------ Scripts/__fileupload.py | 48 +++++++++++++++++++-------------------- Scripts/__modules.py | 11 ++++++--- Scripts/__sidebar.py | 45 ++++++++++++++++++++++++++---------- Scripts/azsqldb.py | 12 +++++----- Scripts/sessionvars.py | 4 ++-- 6 files changed, 87 insertions(+), 54 deletions(-) diff --git a/Scripts/__classmanager.py b/Scripts/__classmanager.py index 3087507..bda0de0 100644 --- a/Scripts/__classmanager.py +++ b/Scripts/__classmanager.py @@ -24,15 +24,17 @@ def show_class(): # Fetch class data class_data = fetch_class_data() + col1, col2 = st.columns([1.4,1]) + if class_data: # Select box for choosing the class - selected_class_name = st.selectbox("Select class:", list(class_data.keys()), index=list(class_data.keys()).index(st.session_state.selected_class_name) if st.session_state.selected_class_name in class_data else 0) + selected_class_name = col1.selectbox("Select class:", list(class_data.keys()), index=list(class_data.keys()).index(st.session_state.selected_class_name) if st.session_state.selected_class_name in class_data else 0, label_visibility='hidden') st.session_state.selected_class_name = selected_class_name # Save the class code for the selected class if selected_class_name: selected_class_info = class_data[selected_class_name] st.session_state.class_info = selected_class_info - st.write(f"Class Code: {selected_class_info['class_code']}") + col2.text_input(label="Class Code", label_visibility='hidden', value= f"Code: {selected_class_info['class_code']}", max_chars=0, key='class_code', disabled=True) else: st.selectbox("Select class:", ["No classes available"]) # if-else to show different messages for teachers and students with no classes @@ -41,16 +43,21 @@ def show_class(): else: st.write("You are not enrolled in any classes yet.") + def create_new_class(): - # Input field and button for new class creation - new_class_name = st.text_input("Enter the name for the new class") - if st.button("Submit New Class"): - if new_class_name: - azsqldb.new_class(st.session_state.user_info['user_id'], st.session_state.sqlcursor, new_class_name) + + new_class_name = st.text_input("Enter the name for the new class", "Name?", label_visibility='hidden') + new_class_learning_outcomes = st.text_area("Enter the learning outcomes for the new class", "Enter the learning outcomes for the new class", label_visibility='hidden') + + if st.button("Submit Class"): + if new_class_name and new_class_learning_outcomes: + azsqldb.new_class(st.session_state.user_info['user_id'], st.session_state.sqlcursor, new_class_name, new_class_learning_outcomes) class_data = fetch_class_data() # Refresh the class data st.session_state.selected_class_name = new_class_name # Update the selected class name st.session_state.show_new_class_input = False # Hide the input fields after submission st.experimental_rerun() # Rerun the script to reflect the changes + else: + st.warning('Both class name and learning outcomes must be populated.') def join_class(): # Input field and button for joining a new class diff --git a/Scripts/__fileupload.py b/Scripts/__fileupload.py index b4188fd..38bccfc 100644 --- a/Scripts/__fileupload.py +++ b/Scripts/__fileupload.py @@ -16,7 +16,7 @@ if st.session_state.user_info['role'] == 'teacher': help_text = 'Upload your class materials here!' else: - help_text = 'Upload your notes here!' + help_text = 'Any files, like notes or outlines' def upload_class_file(): files = st.file_uploader("Upload files relevant to the class as a whole ie syllabus, schedule, etc", @@ -35,28 +35,28 @@ def upload_class_file(): azb.upload_file_to_blob(file_stream, blob_name) # Reset the file uploader widget - st.session_state.upload_key = str(randint(1000, 1000000)) + st.session_state.upload_key = str(randint(0, 1000000)) + st.rerun() def upload_module_file(): - if st.button("Upload File"): - st.session_state.show_upload_file2 = not st.session_state.show_upload_file2 - - if st.session_state.show_upload_file2: - - files = st.file_uploader("Upload files relevant to the lesson", - accept_multiple_files=True, - help=help_text, - key = st.session_state.upload_key_2) - if st.button("Submit"): - # Display a warning if the user hasn't uploaded a file - if not files: - st.warning("Please upload a file first!") - else: - with st.spinner("Uploading your files..."): - for file in files: - file_stream = BytesIO(file.getvalue()) - blob_name = st.session_state.class_info['class_name'] + '/' + file.name - azb.upload_file_to_blob(file_stream, blob_name) - - # Reset the file uploader widget - st.session_state.upload_key_2 = str(randint(1000, 1000000)) \ No newline at end of file + files = st.file_uploader("Upload files your files for this module", + accept_multiple_files=True, + help=help_text, + key = "fufuf" + st.session_state.upload_key_2) + if st.button("Submit"): + # Display a warning if the user hasn't uploaded a file + if not files: + st.warning("Please upload a file first!") + else: + with st.spinner("Uploading your files..."): + for file in files: + file_stream = BytesIO(file.getvalue()) + if st.session_state.user_info['role'] == 'teacher': + blob_name = st.session_state.class_info['class_name'] + '/' + st.session_state.selected_module_name + '/' + file.name + else: + blob_name = st.session_state.class_info['class_name'] + '/' + st.session_state.selected_module_name + '/STUDENT_NOTES/' + st.session_state.user_info['username'] + '/' + file.name + azb.upload_file_to_blob(file_stream, blob_name) + + # Reset the file uploader widget + st.session_state.upload_key_2 = str(randint(1000001, 10000000)) + st.rerun() diff --git a/Scripts/__modules.py b/Scripts/__modules.py index ed39f3e..7d74402 100644 --- a/Scripts/__modules.py +++ b/Scripts/__modules.py @@ -25,7 +25,7 @@ def show_module(): if module_data: # Select box for choosing the module - selected_module_name = st.selectbox("Select module:", list(module_data.keys()), index=list(module_data.keys()).index(st.session_state.selected_module_name) if st.session_state.selected_module_name in module_data else 0) + selected_module_name = st.selectbox("Select module:", list(module_data.keys()), index=list(module_data.keys()).index(st.session_state.selected_module_name) if st.session_state.selected_module_name in module_data else 0,label_visibility='hidden') st.session_state.selected_module_name = selected_module_name # Save the module info for the selected module if selected_module_name: @@ -37,10 +37,12 @@ def show_module(): def create_new_module(): # Input field and button for new module creation new_module_name = st.text_input("Enter the name for the new module") + new_module_learning_outcomes = st.text_area("Enter the learning outcomes for the new class", "Enter the learning outcomes for the new class", label_visibility='hidden') + if st.button("Submit New Module"): - if new_module_name: + if new_module_name and new_module_learning_outcomes: # Call the function to add a new module to the database - azsqldb.new_module(st.session_state.class_info['class_id'], new_module_name, st.session_state.sqlcursor) + azsqldb.new_module(st.session_state.class_info['class_id'], new_module_name, new_module_learning_outcomes, st.session_state.sqlcursor) # Optionally, fetch the updated module data to refresh the page or to show the updated list # module_data = fetch_module_data() @@ -51,6 +53,9 @@ def create_new_module(): st.session_state.selected_module_name = new_module_name # Update the selected class name st.session_state.new_module_toggle = False # Hide the input fields after submission st.experimental_rerun() # Rerun the script to reflect the changes + else: + st.warning('Both module name and learning outcomes must be populated.') + def delete_module(): # Fetch module data for the selected class diff --git a/Scripts/__sidebar.py b/Scripts/__sidebar.py index b8c2419..efdbabb 100644 --- a/Scripts/__sidebar.py +++ b/Scripts/__sidebar.py @@ -28,7 +28,7 @@ def teacher_sidebar(): - **Upload Files**: Upload class materials, assignments, and other resources. 📚 """) ## Class management - st.sidebar.title("Manage Classes") + st.sidebar.title("Class") cm.show_class() col1, col2 = st.columns([1,1]) @@ -54,33 +54,45 @@ def teacher_sidebar(): if st.session_state.show_upload_file: fu.upload_class_file() - #### Module management + #################################### + # Module management st.sidebar.title("Modules") md.show_module() - col3, col4 = st.columns([1,1]) + col3, col4, col5 = st.columns([1,1,1]) with col3: if st.button("Create a new module"): st.session_state.new_module_toggle = not st.session_state.new_module_toggle + st.session_state.delete_module_toggle = False + st.session_state.show_upload_file2 = False + with col4: if st.button("Delete a module"): st.session_state.delete_module_toggle = not st.session_state.delete_module_toggle + st.session_state.new_module_toggle = False + st.session_state.show_upload_file2 = False + + with col5: + if st.button("Upload File", key='module_upload'): + st.session_state.show_upload_file2 = not st.session_state.show_upload_file2 + st.session_state.new_module_toggle = False + st.session_state.delete_module_toggle = False if st.session_state.new_module_toggle: md.create_new_module() - if st.session_state.delete_module_toggle: + if st.session_state.delete_module_toggle: md.delete_module() + + if st.session_state.show_upload_file2: + fu.upload_module_file() + #################################### ### Faq functions st.sidebar.title("FAQs") fq.teacher_faqs() - #file upload - st.sidebar.title("Upload Files") - fu.upload_module_file() - #schedule st.sidebar.title("Manage Assignments") sc.teacher_schedule() @@ -104,13 +116,22 @@ def student_sidebar(): if st.session_state.show_join_class_input: cm.join_class() + + + ################################ + # Modules + st.sidebar.title("Modules") + md.show_module() + + if st.button("Upload File", key='module_upload'): + st.session_state.show_upload_file2 = not st.session_state.show_upload_file2 + + if st.session_state.show_upload_file2: + fu.upload_module_file() st.sidebar.title("FAQs") fq.student_faqs() - - #file upload - st.sidebar.title("Upload Files") - fu.upload_module_file() + #schedule st.sidebar.title("Upcoming Assignments") diff --git a/Scripts/azsqldb.py b/Scripts/azsqldb.py index 71b318d..6b94fcc 100644 --- a/Scripts/azsqldb.py +++ b/Scripts/azsqldb.py @@ -138,7 +138,7 @@ def get_classes(user_id, role, sqlcursor): } # classes["Anam's Class"]['class_id'] would return 1]""" -def new_class(user_id, sqlcursor, class_name): +def new_class(user_id, sqlcursor, class_name, learnig_outcomes): """ Create a new class """ @@ -146,7 +146,7 @@ def new_class(user_id, sqlcursor, class_name): class_code = ''.join(random.choices('0123456789ABCDEF', k=6)) # Execute a SQL query to insert the new class - sqlcursor.execute("INSERT INTO master.STUDYBUDDY.classes (class_name, class_code, teacher_id) VALUES (?, ?, ?)", (class_name, class_code, user_id)) + sqlcursor.execute("INSERT INTO master.STUDYBUDDY.classes (class_name, class_code, teacher_id, LearningOutcomes) VALUES (?, ?, ?, ?)", (class_name, class_code, user_id, learnig_outcomes)) # Commit the transaction sqlcursor.connection.commit() @@ -315,15 +315,15 @@ def get_modules(class_id, sqlcursor): return module_info_mapping -def new_module(class_id, module_name, sqlcursor): +def new_module(class_id, module_name, learning_outcome, sqlcursor): """ Create a new module for a specific class. """ # Execute a SQL query to insert the new module sqlcursor.execute(""" - INSERT INTO master.STUDYBUDDY.Modules (class_id, module_name) - VALUES (?, ?) - """, (class_id, module_name)) + INSERT INTO master.STUDYBUDDY.Modules (class_id, module_name, LearningOutcomes) + VALUES (?, ?, ?) + """, (class_id, module_name, learning_outcome)) # Commit the transaction sqlcursor.connection.commit() diff --git a/Scripts/sessionvars.py b/Scripts/sessionvars.py index d596fe8..8100a7e 100644 --- a/Scripts/sessionvars.py +++ b/Scripts/sessionvars.py @@ -72,11 +72,11 @@ def initialize_session_vars(): # Initialize the upload counter in session state if 'upload_key' not in st.session_state: - st.session_state.upload_key = str(randint(1000, 1000000)) + st.session_state.upload_key = str(randint(0, 1000000)) # Initialize the upload counter in session state if 'upload_key_2' not in st.session_state: - st.session_state.upload_key_2 = str(randint(1000, 1000000)) + st.session_state.upload_key_2 = str(randint(1000001, 10000000)) #### # Module vars From 667a5215d3b179072f6cfa03bfb82dc8e34c11b2 Mon Sep 17 00:00:00 2001 From: ammar Date: Sat, 2 Dec 2023 16:36:20 -0500 Subject: [PATCH 07/27] - a simple widget to let the user choose context - the user can select which modules to chat about --- Scripts/__chatscreen.py | 60 +++++++++++++++++++++++++++++++++++++++++ Scripts/sessionvars.py | 7 +++++ app.py | 16 ++++++++--- 3 files changed, 79 insertions(+), 4 deletions(-) create mode 100644 Scripts/__chatscreen.py diff --git a/Scripts/__chatscreen.py b/Scripts/__chatscreen.py new file mode 100644 index 0000000..154b718 --- /dev/null +++ b/Scripts/__chatscreen.py @@ -0,0 +1,60 @@ +# **************************************************************************** # +# # +# ::: :::::::: # +# __chatscreen.py :+: :+: :+: # +# +:+ +:+ +:+ # +# By: ammar syed ali Date: Sun, 3 Dec 2023 03:08:47 -0500 Subject: [PATCH 08/27] - context selection widget can find blobs - only blobs in selected contexts appear --- Scripts/__fileupload.py | 4 ++-- Scripts/__modules.py | 2 +- Scripts/azblob.py | 46 ++++++++++++++++++++++++----------------- Scripts/sessionvars.py | 6 ++++++ app.py | 13 +++++++++++- 5 files changed, 48 insertions(+), 23 deletions(-) diff --git a/Scripts/__fileupload.py b/Scripts/__fileupload.py index 38bccfc..1d33ef5 100644 --- a/Scripts/__fileupload.py +++ b/Scripts/__fileupload.py @@ -58,5 +58,5 @@ def upload_module_file(): azb.upload_file_to_blob(file_stream, blob_name) # Reset the file uploader widget - st.session_state.upload_key_2 = str(randint(1000001, 10000000)) - st.rerun() + st.session_state.upload_key_2 = str(randint(1000001, 10000000)) + st.rerun() diff --git a/Scripts/__modules.py b/Scripts/__modules.py index 7d74402..0e2b5d4 100644 --- a/Scripts/__modules.py +++ b/Scripts/__modules.py @@ -37,7 +37,7 @@ def show_module(): def create_new_module(): # Input field and button for new module creation new_module_name = st.text_input("Enter the name for the new module") - new_module_learning_outcomes = st.text_area("Enter the learning outcomes for the new class", "Enter the learning outcomes for the new class", label_visibility='hidden') + new_module_learning_outcomes = st.text_area("Enter the learning outcomes for the module", "Enter the learning outcomes for the new class", label_visibility='hidden') if st.button("Submit New Module"): if new_module_name and new_module_learning_outcomes: diff --git a/Scripts/azblob.py b/Scripts/azblob.py index 125cff8..a92cbcd 100644 --- a/Scripts/azblob.py +++ b/Scripts/azblob.py @@ -10,6 +10,7 @@ # # # **************************************************************************** # import streamlit as st +import pandas as pd from dotenv import load_dotenv, find_dotenv from azure.storage.blob import BlobServiceClient, BlobClient, ContainerClient from azure.core.exceptions import ResourceExistsError @@ -45,27 +46,34 @@ def upload_file_to_blob(file, blob_name): st.error(f"Error occurred while uploading file: {e}") -# Streamlit UI to upload files -def main(): - st.title("Upload Files to Azure Blob Storage") - - # Connection string and container name (ensure these are secure and not hard-coded in production) +def get_class_and_module_files(class_name): + # Retrieve environment variables connection_string = os.getenv("AZURE_STORAGE_CONNECTION_STRING") container_name = os.getenv("AZURE_CONTAINER") - # File uploader widget - uploaded_files = st.file_uploader('file_uplaods', accept_multiple_files=True, label_visibility='hidden') + # Initialize BlobServiceClient + blob_service_client = BlobServiceClient.from_connection_string(connection_string) + container_client = blob_service_client.get_container_client(container_name) + + # List to store all blob paths + all_blobs = [] + + # Define the root folder for the class + class_folder = f"{class_name}/" + + # Get all blobs within the class folder + blobs = container_client.list_blobs(name_starts_with=class_folder) + for blob in blobs: + all_blobs.append(blob.name) - if st.button("Submit"): - # Handle file upload - if uploaded_files: - for uploaded_file in uploaded_files: - # Create a file-like object - file_stream = BytesIO(uploaded_file.getvalue()) - blob_name = 'test/' + uploaded_file.name - # Call the upload function - upload_file_to_blob(file_stream, container_name, blob_name, connection_string) - st.success(f"Uploaded {blob_name}") + # Initialize DataFrame + df = pd.DataFrame(columns=['full_path', 'module_name', 'student_name']) -if __name__ == "__main__": - main() \ No newline at end of file + # Process each file path + for path in all_blobs: + parts = path.split('/') + module_name = parts[1] if len(parts) > 1 and "." not in parts[1] else None + student_name = parts[3] if len(parts) > 3 and "STUDENT_NOTES" in parts else None + df = pd.concat([df, pd.DataFrame({'full_path': path, 'module_name': module_name, 'student_name': student_name}, index=[0])], ignore_index=True) + + st.session_state.blobs_df = df \ No newline at end of file diff --git a/Scripts/sessionvars.py b/Scripts/sessionvars.py index c10809e..5692ef8 100644 --- a/Scripts/sessionvars.py +++ b/Scripts/sessionvars.py @@ -102,3 +102,9 @@ def initialize_session_vars(): #### chat screen vars if 'context_selection_toggle' not in st.session_state: st.session_state.context_selection_toggle = True + + if 'blobs_df' not in st.session_state: + st.session_state.blobs_df = None + + if 'blobs_to_retrieve' not in st.session_state: + st.session_state.blobs_to_retrieve = None diff --git a/app.py b/app.py index 60e9b65..875804e 100644 --- a/app.py +++ b/app.py @@ -1,6 +1,12 @@ -from Scripts import azsqldb, sessionvars, __login as lg, __sidebar as sb, __chatscreen as cs +from Scripts import azsqldb +from Scripts import sessionvars +from Scripts import __login as lg +from Scripts import __sidebar as sb +from Scripts import __chatscreen as cs +from Scripts import azblob as azb import streamlit as st from markdownlit import mdlit +import pandas as pd sessionvars.initialize_session_vars() @@ -44,4 +50,9 @@ if st.session_state.selected_modules not in [None, []]: st.write(f"Chatting about: {st.session_state.selected_modules}") + azb.get_class_and_module_files('BUS5000') + st.session_state.blobs_to_retrieve = st.session_state.blobs_df[st.session_state.blobs_df['module_name'].isin(st.session_state.selected_modules)] + ######################### + #st.dataframe(st.session_state.blobs_to_retrieve) + From c01bb0136e219368d7eed6f1d5dc96f03b514c1a Mon Sep 17 00:00:00 2001 From: ammar Date: Sun, 3 Dec 2023 03:32:42 -0500 Subject: [PATCH 09/27] - class level files always included in context --- Scripts/azblob.py | 5 +++- app.py | 4 +-- temp.ipynb | 68 ++++++++++++++++++++++++----------------------- 3 files changed, 41 insertions(+), 36 deletions(-) diff --git a/Scripts/azblob.py b/Scripts/azblob.py index a92cbcd..da4284b 100644 --- a/Scripts/azblob.py +++ b/Scripts/azblob.py @@ -74,6 +74,9 @@ def get_class_and_module_files(class_name): parts = path.split('/') module_name = parts[1] if len(parts) > 1 and "." not in parts[1] else None student_name = parts[3] if len(parts) > 3 and "STUDENT_NOTES" in parts else None + if len(parts) > 1 and "." in parts[1]: + module_name = "CLASS_LEVEL" df = pd.concat([df, pd.DataFrame({'full_path': path, 'module_name': module_name, 'student_name': student_name}, index=[0])], ignore_index=True) - + ## when a fileis a the class level make the module name CLASS_LEVEL + ## filter the data fram checking the blob path for two st.session_state.blobs_df = df \ No newline at end of file diff --git a/app.py b/app.py index 875804e..587f4b3 100644 --- a/app.py +++ b/app.py @@ -51,8 +51,8 @@ st.write(f"Chatting about: {st.session_state.selected_modules}") azb.get_class_and_module_files('BUS5000') - st.session_state.blobs_to_retrieve = st.session_state.blobs_df[st.session_state.blobs_df['module_name'].isin(st.session_state.selected_modules)] + st.session_state.blobs_to_retrieve = st.session_state.blobs_df[st.session_state.blobs_df['module_name'].isin(st.session_state.selected_modules + ['CLASS_LEVEL'])] ######################### - #st.dataframe(st.session_state.blobs_to_retrieve) + st.dataframe(st.session_state.blobs_to_retrieve) diff --git a/temp.ipynb b/temp.ipynb index c3630bd..046ea04 100644 --- a/temp.ipynb +++ b/temp.ipynb @@ -4,47 +4,49 @@ "cell_type": "code", "execution_count": 3, "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "True" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ - "import streamlit as st\n", - "from dotenv import load_dotenv, find_dotenv\n", "from azure.storage.blob import BlobServiceClient, BlobClient, ContainerClient\n", - "from io import BytesIO\n", - "import os\n", - "\n", - "load_dotenv(find_dotenv())" + "import openai\n", + "from dotenv import load_dotenv, find_dotenv\n", + "import os" ] }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 5, "metadata": {}, - "outputs": [ - { - "ename": "NameError", - "evalue": "name 'os' is not defined", - "output_type": "error", - "traceback": [ - "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[1;31mNameError\u001b[0m Traceback (most recent call last)", - "\u001b[1;32mc:\\Users\\ammar\\Desktop\\StudyBuddy\\temp.ipynb Cell 2\u001b[0m line \u001b[0;36m1\n\u001b[1;32m----> 1\u001b[0m blob_service_client \u001b[39m=\u001b[39m BlobServiceClient\u001b[39m.\u001b[39mfrom_connection_string(os\u001b[39m.\u001b[39mgetenv(\u001b[39m\"\u001b[39m\u001b[39mAZURE_STORAGE_CONNECTION_STRING\u001b[39m\u001b[39m\"\u001b[39m))\n", - "\u001b[1;31mNameError\u001b[0m: name 'os' is not defined" - ] - } - ], + "outputs": [], "source": [ - "blob_service_client = BlobServiceClient.from_connection_string(os.getenv(\"AZURE_STORAGE_CONNECTION_STRING\"))\n" + "blob_service_client = BlobServiceClient.from_connection_string(os.getenv(\"AZURE_STORAGE_CONNECTION_STRING\"))\n", + "container_client = blob_service_client.get_container_client(os.getenv(\"AZURE_CONTAINER\"))\n", + "openai.api_key = os.getenv(\"OPENAI_API_KEY\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "blob_paths = [\n", + "'BUS5000/Introduction/10dayMBA - Intro.pdf',\n", + "'BUS5000/Introduction/STUDENT_NOTES/s1/s1w0.docx',\n", + "'BUS5000/Introduction/STUDENT_NOTES/s2/s2w0.docx'\n", + "]\n", + "# List to store file objects\n", + "uploaded_files = []\n", + "\n", + "for blob_path in blob_paths:\n", + " # Download the file from Azure Blob\n", + " blob_client = container_client.get_blob_client(blob_path)\n", + " with open(blob_path, \"wb\") as download_file:\n", + " download_file.write(blob_client.download_blob().readall())\n", + "\n", + " # Upload the file to OpenAI\n", + " with open(blob_path, \"rb\") as file:\n", + " response = openai.File.create(file=file, purpose=\"fine-tune\")\n", + " uploaded_files.append(response)" ] } ], From 19bd10968043abc16571f531b1eef8f1e2d13294 Mon Sep 17 00:00:00 2001 From: ammar Date: Sun, 3 Dec 2023 04:23:55 -0500 Subject: [PATCH 10/27] - successfully added files to the open ai endpoint - deleted the same files --- requirements.txt | 2 +- temp.ipynb | 24 +++++++++++++++++++----- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/requirements.txt b/requirements.txt index 21e15a5..f9576d2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -openai +openai==1.3.7 langchain faiss-cpu pypdf diff --git a/temp.ipynb b/temp.ipynb index 046ea04..661719c 100644 --- a/temp.ipynb +++ b/temp.ipynb @@ -2,30 +2,34 @@ "cells": [ { "cell_type": "code", - "execution_count": 3, + "execution_count": 12, "metadata": {}, "outputs": [], "source": [ "from azure.storage.blob import BlobServiceClient, BlobClient, ContainerClient\n", "import openai\n", + "from openai import OpenAI\n", "from dotenv import load_dotenv, find_dotenv\n", "import os" ] }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 13, "metadata": {}, "outputs": [], "source": [ "blob_service_client = BlobServiceClient.from_connection_string(os.getenv(\"AZURE_STORAGE_CONNECTION_STRING\"))\n", "container_client = blob_service_client.get_container_client(os.getenv(\"AZURE_CONTAINER\"))\n", - "openai.api_key = os.getenv(\"OPENAI_API_KEY\")" + "\n", + "client = OpenAI(\n", + " api_key=os.getenv(\"OPENAI_API_KEY\"),\n", + ")" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 15, "metadata": {}, "outputs": [], "source": [ @@ -45,9 +49,19 @@ "\n", " # Upload the file to OpenAI\n", " with open(blob_path, \"rb\") as file:\n", - " response = openai.File.create(file=file, purpose=\"fine-tune\")\n", + " response = client.files.create(file=file, purpose=\"assistants\")\n", " uploaded_files.append(response)" ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": {}, + "outputs": [], + "source": [ + "for file in uploaded_files:\n", + " client.files.delete(file.id)\n" + ] } ], "metadata": { From edcdf7a730a06c61c49ecb4747c47ea90ac86431 Mon Sep 17 00:00:00 2001 From: ammar Date: Sun, 3 Dec 2023 13:20:35 -0500 Subject: [PATCH 11/27] - create a system prompt to prep study buddy - prompt gives studybuddy the context of the class and selected modules --- Scripts/__chatscreen.py | 26 ++++++++++++++++++++++++-- Scripts/azblob.py | 3 +++ Scripts/azsqldb.py | 33 +++++++++++++++++++++++++++++++++ Scripts/sessionvars.py | 27 ++++++++++++++++++++++++++- app.py | 11 ++++++++--- temp.ipynb | 25 ++++++++++++++++++------- 6 files changed, 112 insertions(+), 13 deletions(-) diff --git a/Scripts/__chatscreen.py b/Scripts/__chatscreen.py index 154b718..a2d6f7c 100644 --- a/Scripts/__chatscreen.py +++ b/Scripts/__chatscreen.py @@ -10,7 +10,8 @@ # # # **************************************************************************** # import streamlit as st -from Scripts import azsqldb, sessionvars +import openai +from Scripts import azsqldb, sessionvars, azblob as azb sessionvars.initialize_session_vars() @@ -57,4 +58,25 @@ def context_selection(): else: st.session_state.selected_modules = selected_modules st.session_state.context_selection_toggle = False - st.experimental_rerun() \ No newline at end of file + st.experimental_rerun() + +def initialize_chat(): + module_learning_outcomes, class_learning_outcomes = azsqldb.get_learning_outcomes( + st.session_state.class_info['class_id'], + st.session_state.selected_modules, + st.session_state.sqlcursor) + + initial_prompt = f""" + INITIAL PROMPT +You're chatting with {st.session_state.user_info['username']}\n +Their role is : {st.session_state.user_info['role']}\n +The class is : {st.session_state.class_info['class_name']}\n +The class learning outcomes are:\n {class_learning_outcomes}\n +You are going to discuss the following modules:\n +""" + + for module, outcome in module_learning_outcomes.items(): + initial_prompt += f" -Module: {module}\n\n" + initial_prompt += f" -Learning outcomes: {outcome}\n\n" + + return initial_prompt diff --git a/Scripts/azblob.py b/Scripts/azblob.py index da4284b..49c3de0 100644 --- a/Scripts/azblob.py +++ b/Scripts/azblob.py @@ -47,6 +47,9 @@ def upload_file_to_blob(file, blob_name): def get_class_and_module_files(class_name): + ''' + Outputs a function that returns a dataframe of all files in the class and module folders + ''' # Retrieve environment variables connection_string = os.getenv("AZURE_STORAGE_CONNECTION_STRING") container_name = os.getenv("AZURE_CONTAINER") diff --git a/Scripts/azsqldb.py b/Scripts/azsqldb.py index 6b94fcc..98ce73e 100644 --- a/Scripts/azsqldb.py +++ b/Scripts/azsqldb.py @@ -338,3 +338,36 @@ def delete_module(module_id, sqlcursor): """, (module_id,)) # Commit the transaction sqlcursor.connection.commit() + +def get_learning_outcomes(class_id, selected_modules, sqlcursor): + """ + Get the learning outcomes for the selected modules. + Returns a dictionary mapping module names to their learning outcomes and class information. + """ + # Execute a SQL query to get the learning outcomes for the selected modules + sqlcursor.execute(""" + SELECT module_name, LearningOutcomes + FROM master.STUDYBUDDY.Modules + WHERE class_id = ? + AND module_name IN ({}) + """.format(','.join('?' * len(selected_modules))), (class_id, *selected_modules)) + # Fetch all records from the query + learning_outcome_records = sqlcursor.fetchall() + # Create a dictionary mapping module names to their learning outcomes + learning_outcomes = {} + for record in learning_outcome_records: + module_name = record[0] + learning_outcome = record[1] + learning_outcomes[module_name] = learning_outcome + + # Execute a SQL query to get the class information + sqlcursor.execute(""" + SELECT class_name, LearningOutcomes + FROM master.STUDYBUDDY.Classes + WHERE class_id = ? + """, (class_id,)) + # Fetch the record from the query + class_record = sqlcursor.fetchone() + class_learning_outcomes = class_record[1] + + return learning_outcomes, class_learning_outcomes diff --git a/Scripts/sessionvars.py b/Scripts/sessionvars.py index 5692ef8..932114c 100644 --- a/Scripts/sessionvars.py +++ b/Scripts/sessionvars.py @@ -12,10 +12,16 @@ """ Initialize session variables for the app """ - +from openai import OpenAI import streamlit as st +import os +from dotenv import load_dotenv, find_dotenv from Scripts import azsqldb from random import randint +import uuid + + +load_dotenv(find_dotenv()) def initialize_session_vars(): ''' @@ -108,3 +114,22 @@ def initialize_session_vars(): if 'blobs_to_retrieve' not in st.session_state: st.session_state.blobs_to_retrieve = None + + if 'ai_client' not in st.session_state: + st.session_state.ai_client = OpenAI( + api_key = os.getenv("OPENAI_API_KEY") + ) + if "session_id" not in st.session_state: # Used to identify each session + st.session_state.session_id = str(uuid.uuid4()) + + if "run" not in st.session_state: # Stores the run state of the assistant + st.session_state.run = {"status": None} + + if "messages" not in st.session_state: # Stores the messages of the assistant + st.session_state.messages = [] + + if "retry_error" not in st.session_state: # Used for error handling + st.session_state.retry_error = 0 + + if 'studybuddy' not in st.session_state: # Used to store the studybuddy object + st.session_state.studybuddy = st.session_state.ai_client.beta.assistants.retrieve(os.getenv("OPENAI_ASSISTANT")) \ No newline at end of file diff --git a/app.py b/app.py index 587f4b3..d6557cd 100644 --- a/app.py +++ b/app.py @@ -46,13 +46,18 @@ if st.session_state.context_selection_toggle: cs.context_selection() - # Show the select modules + # blob runs only after context has been selected if st.session_state.selected_modules not in [None, []]: - st.write(f"Chatting about: {st.session_state.selected_modules}") + col4, col5 = st.columns([1,1]) + col4.write(f"Chatting about: {st.session_state.selected_modules}") + col5.write(f"Current session: {st.session_state.session_id}") + azb.get_class_and_module_files('BUS5000') st.session_state.blobs_to_retrieve = st.session_state.blobs_df[st.session_state.blobs_df['module_name'].isin(st.session_state.selected_modules + ['CLASS_LEVEL'])] ######################### - st.dataframe(st.session_state.blobs_to_retrieve) + #st.dataframe(st.session_state.blobs_to_retrieve) + + st.write(cs.initialize_chat()) diff --git a/temp.ipynb b/temp.ipynb index 661719c..3a01a3b 100644 --- a/temp.ipynb +++ b/temp.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "code", - "execution_count": 12, + "execution_count": 19, "metadata": {}, "outputs": [], "source": [ @@ -15,7 +15,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 20, "metadata": {}, "outputs": [], "source": [ @@ -29,10 +29,14 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 24, "metadata": {}, "outputs": [], "source": [ + "staging_dir = '/path/to/your/project/staging'\n", + "os.makedirs(staging_dir, exist_ok=True)\n", + "\n", + "\n", "blob_paths = [\n", "'BUS5000/Introduction/10dayMBA - Intro.pdf',\n", "'BUS5000/Introduction/STUDENT_NOTES/s1/s1w0.docx',\n", @@ -42,24 +46,31 @@ "uploaded_files = []\n", "\n", "for blob_path in blob_paths:\n", + " # Adjust the path to save in the staging directory\n", + " staging_path = os.path.join(staging_dir, os.path.basename(blob_path))\n", + "\n", " # Download the file from Azure Blob\n", " blob_client = container_client.get_blob_client(blob_path)\n", - " with open(blob_path, \"wb\") as download_file:\n", + " with open(staging_path, \"wb\") as download_file:\n", " download_file.write(blob_client.download_blob().readall())\n", "\n", " # Upload the file to OpenAI\n", - " with open(blob_path, \"rb\") as file:\n", + " with open(staging_path, \"rb\") as file:\n", " response = client.files.create(file=file, purpose=\"assistants\")\n", - " uploaded_files.append(response)" + " uploaded_files.append(response)\n", + "\n", + " # Delete the file from the staging directory\n", + " os.remove(staging_path)" ] }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 26, "metadata": {}, "outputs": [], "source": [ "for file in uploaded_files:\n", + " #print(file.id + '-' + file.filename)\n", " client.files.delete(file.id)\n" ] } From 11927d1de1b55db9095d1f737609c60b97d5a22a Mon Sep 17 00:00:00 2001 From: ammar Date: Sun, 3 Dec 2023 14:40:21 -0500 Subject: [PATCH 12/27] - blobs uploaded to openai stored in bin - each blob's openai fileid is returned as a list - temp notebook has the function to clear files from openai --- .bin/files.json | 1 + Scripts/__chatscreen.py | 66 ++++++++++++++++++++++++++ app.py | 4 +- staging/full_path | 0 temp.ipynb | 102 ++++++++++++++++++++++++++++++++++++---- 5 files changed, 161 insertions(+), 12 deletions(-) create mode 100644 .bin/files.json create mode 100644 staging/full_path diff --git a/.bin/files.json b/.bin/files.json new file mode 100644 index 0000000..ccae948 --- /dev/null +++ b/.bin/files.json @@ -0,0 +1 @@ +[{"id": "file-02HGHfO3ldqVFpEHGcQbd076", "bytes": 2190194, "created_at": 1701632335, "filename": "10dayMBA - Intro.pdf", "object": "file", "purpose": "assistants", "status": "processed", "status_details": null}, {"id": "file-wx30m8K4G9Isz1Jl2gt1XeGz", "bytes": 12631, "created_at": 1701632337, "filename": "s1w0.docx", "object": "file", "purpose": "assistants", "status": "processed", "status_details": null}, {"id": "file-6BIM37pvj6iCQaxCjbSS0MLi", "bytes": 12567, "created_at": 1701632338, "filename": "s2w0.docx", "object": "file", "purpose": "assistants", "status": "processed", "status_details": null}] \ No newline at end of file diff --git a/Scripts/__chatscreen.py b/Scripts/__chatscreen.py index a2d6f7c..d6b20c6 100644 --- a/Scripts/__chatscreen.py +++ b/Scripts/__chatscreen.py @@ -10,11 +10,30 @@ # # # **************************************************************************** # import streamlit as st +import os import openai +from azure.storage.blob import BlobServiceClient, BlobClient, ContainerClient from Scripts import azsqldb, sessionvars, azblob as azb +import json +from dotenv import load_dotenv, find_dotenv + +load_dotenv(find_dotenv()) sessionvars.initialize_session_vars() +# Function to convert FileObject to a serializable dictionary +def file_object_to_dict(file_obj): + return { + 'id': file_obj.id, + 'bytes': file_obj.bytes, + 'created_at': file_obj.created_at, + 'filename': file_obj.filename, + 'object': file_obj.object, + 'purpose': file_obj.purpose, + 'status': file_obj.status, + 'status_details': file_obj.status_details + } + def context_selection(): """ A widget that allows the user to select what context the chatbot has access to @@ -80,3 +99,50 @@ def initialize_chat(): initial_prompt += f" -Learning outcomes: {outcome}\n\n" return initial_prompt + + +def upload_files_ai(blob_paths): + blob_service_client = BlobServiceClient.from_connection_string(os.getenv("AZURE_STORAGE_CONNECTION_STRING")) + container_client = blob_service_client.get_container_client(os.getenv("AZURE_CONTAINER")) + client = st.session_state.ai_client + + # Base directory (assuming this script is in the /scripts subdirectory) + base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + + # Paths + staging_dir = os.path.join(base_dir, 'staging') + json_path = os.path.join(base_dir, '.bin', 'files.json') + + # Ensure directories exist + os.makedirs(staging_dir, exist_ok=True) + os.makedirs(os.path.dirname(json_path), exist_ok=True) + + # List to store file objects + uploaded_files = [] + + for blob_path in blob_paths: + # Adjust the path to save in the staging directory + staging_path = os.path.join(staging_dir, os.path.basename(blob_path)) + + # Download the file from Azure Blob + blob_client = container_client.get_blob_client(blob_path) + with open(staging_path, "wb") as download_file: + download_file.write(blob_client.download_blob().readall()) + + # Upload the file to OpenAI + with open(staging_path, "rb") as file: + response = client.files.create(file=file, purpose="assistants") + uploaded_files.append(response) + + # Delete the file from the staging directory + os.remove(staging_path) + + # Convert each FileObject in the list to a dictionary + file_dicts = [file_object_to_dict(file_obj) for file_obj in uploaded_files] + + # Save the list of dictionaries as a JSON list + with open(json_path, 'w') as json_file: + json.dump(file_dicts, json_file) + + # Return this list of file ids + return [file_obj.id for file_obj in uploaded_files] \ No newline at end of file diff --git a/app.py b/app.py index d6557cd..4749ddf 100644 --- a/app.py +++ b/app.py @@ -56,8 +56,8 @@ azb.get_class_and_module_files('BUS5000') st.session_state.blobs_to_retrieve = st.session_state.blobs_df[st.session_state.blobs_df['module_name'].isin(st.session_state.selected_modules + ['CLASS_LEVEL'])] ######################### - #st.dataframe(st.session_state.blobs_to_retrieve) + st.dataframe(st.session_state.blobs_to_retrieve) + st.write(cs.upload_files_ai(st.session_state.blobs_to_retrieve['full_path'])) - st.write(cs.initialize_chat()) diff --git a/staging/full_path b/staging/full_path new file mode 100644 index 0000000..e69de29 diff --git a/temp.ipynb b/temp.ipynb index 3a01a3b..d10a138 100644 --- a/temp.ipynb +++ b/temp.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "code", - "execution_count": 19, + "execution_count": 9, "metadata": {}, "outputs": [], "source": [ @@ -10,12 +10,13 @@ "import openai\n", "from openai import OpenAI\n", "from dotenv import load_dotenv, find_dotenv\n", - "import os" + "import os\n", + "import json" ] }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 10, "metadata": {}, "outputs": [], "source": [ @@ -29,11 +30,11 @@ }, { "cell_type": "code", - "execution_count": 24, + "execution_count": 22, "metadata": {}, "outputs": [], "source": [ - "staging_dir = '/path/to/your/project/staging'\n", + "staging_dir = '/staging'\n", "os.makedirs(staging_dir, exist_ok=True)\n", "\n", "\n", @@ -60,18 +61,99 @@ " uploaded_files.append(response)\n", "\n", " # Delete the file from the staging directory\n", - " os.remove(staging_path)" + " os.remove(staging_path)\n", + "\n", + "# Function to convert FileObject to a serializable dictionary\n", + "def file_object_to_dict(file_obj):\n", + " return {\n", + " 'id': file_obj.id,\n", + " 'bytes': file_obj.bytes,\n", + " 'created_at': file_obj.created_at,\n", + " 'filename': file_obj.filename,\n", + " 'object': file_obj.object,\n", + " 'purpose': file_obj.purpose,\n", + " 'status': file_obj.status,\n", + " 'status_details': file_obj.status_details\n", + " }\n", + "\n", + "# Convert each FileObject in the list to a dictionary\n", + "file_dicts = [file_object_to_dict(file_obj) for file_obj in uploaded_files]\n", + "\n", + "# Save the list of dictionaries as a JSON list\n", + "json_path = './.bin/files.json'\n", + "with open(json_path, 'w') as json_file:\n", + " json.dump(file_dicts, json_file)" ] }, { "cell_type": "code", - "execution_count": 26, + "execution_count": 19, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "SyncPage[FileObject](data=[], object='list', has_more=False)\n" + ] + } + ], + "source": [ + "print(client.files.list())" + ] + }, + { + "cell_type": "code", + "execution_count": 21, "metadata": {}, "outputs": [], "source": [ - "for file in uploaded_files:\n", - " #print(file.id + '-' + file.filename)\n", - " client.files.delete(file.id)\n" + "def delete_files_from_json():\n", + " json_path = './.bin/files.json'\n", + " # Load the file data from the JSON file\n", + " with open(json_path, 'r') as json_file:\n", + " file_data = json.load(json_file)\n", + "\n", + " # Iterate through each file in the list\n", + " for file_info in file_data:\n", + " file_id = file_info['id']\n", + " # Delete the file using OpenAI API and print the status\n", + " client.files.delete(file_id)\n", + " \n", + " # Open the file in write mode and close it to clear its contents\n", + " with open(json_path, 'w') as file:\n", + " pass # Opening in 'w' mode and closing will clear the file\n", + "\n", + "delete_files_from_json()" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "['file-1I7bhX1YyIhKCJ7ddouuPy60', 'file-oxcDEGJLNutupSASmnV2vM3w', 'file-fQZCpJfuH5J6TQ6O6Qpt3Zxn']\n" + ] + } + ], + "source": [ + "json_path = './.bin/files.json'\n", + "\n", + "fileids = []\n", + "\n", + "# Load the file data from the JSON file\n", + "with open(json_path, 'r') as json_file:\n", + " file_data = json.load(json_file)\n", + " # Iterate through each file in the list\n", + " for file_info in file_data:\n", + " file_id = file_info['id']\n", + " fileids.append(file_id)\n", + " \n", + "print(fileids)" ] } ], From adab1e0e8a2658abae9083500d219b7f342ef1f1 Mon Sep 17 00:00:00 2001 From: ammar Date: Sun, 3 Dec 2023 15:23:10 -0500 Subject: [PATCH 13/27] - files get attached to assistant - only the required files get attached - openai files cleared out prior to chatting --- .bin/files.json | 2 +- Scripts/__chatscreen.py | 24 +++++------------------- Scripts/sessionvars.py | 5 ++++- app.py | 15 +++++++++++++-- 4 files changed, 23 insertions(+), 23 deletions(-) diff --git a/.bin/files.json b/.bin/files.json index ccae948..222a766 100644 --- a/.bin/files.json +++ b/.bin/files.json @@ -1 +1 @@ -[{"id": "file-02HGHfO3ldqVFpEHGcQbd076", "bytes": 2190194, "created_at": 1701632335, "filename": "10dayMBA - Intro.pdf", "object": "file", "purpose": "assistants", "status": "processed", "status_details": null}, {"id": "file-wx30m8K4G9Isz1Jl2gt1XeGz", "bytes": 12631, "created_at": 1701632337, "filename": "s1w0.docx", "object": "file", "purpose": "assistants", "status": "processed", "status_details": null}, {"id": "file-6BIM37pvj6iCQaxCjbSS0MLi", "bytes": 12567, "created_at": 1701632338, "filename": "s2w0.docx", "object": "file", "purpose": "assistants", "status": "processed", "status_details": null}] \ No newline at end of file +[{"id": "file-gwWAHylwSsbgiGtgueJXDQy7", "bytes": 22887384, "created_at": 1701634508, "filename": "10dayMBA - 1.pdf", "object": "file", "purpose": "assistants", "status": "processed", "status_details": null}, {"id": "file-F1Up2lgzKDRblggN0NBpBxjE", "bytes": 13464, "created_at": 1701634510, "filename": "s1w1 notes.docx", "object": "file", "purpose": "assistants", "status": "processed", "status_details": null}, {"id": "file-NUzY5dlGyYfneoeUhKgwPnRt", "bytes": 13875, "created_at": 1701634512, "filename": "s2w1 notes.docx", "object": "file", "purpose": "assistants", "status": "processed", "status_details": null}] \ No newline at end of file diff --git a/Scripts/__chatscreen.py b/Scripts/__chatscreen.py index d6b20c6..936b5f9 100644 --- a/Scripts/__chatscreen.py +++ b/Scripts/__chatscreen.py @@ -21,18 +21,11 @@ sessionvars.initialize_session_vars() -# Function to convert FileObject to a serializable dictionary -def file_object_to_dict(file_obj): - return { - 'id': file_obj.id, - 'bytes': file_obj.bytes, - 'created_at': file_obj.created_at, - 'filename': file_obj.filename, - 'object': file_obj.object, - 'purpose': file_obj.purpose, - 'status': file_obj.status, - 'status_details': file_obj.status_details - } +def delete_files_from_openai(): + files = st.session_state.ai_client.files.list() + for file in files: + file_id = file.id + st.session_state.ai_client.files.delete(file_id) def context_selection(): """ @@ -136,13 +129,6 @@ def upload_files_ai(blob_paths): # Delete the file from the staging directory os.remove(staging_path) - - # Convert each FileObject in the list to a dictionary - file_dicts = [file_object_to_dict(file_obj) for file_obj in uploaded_files] - - # Save the list of dictionaries as a JSON list - with open(json_path, 'w') as json_file: - json.dump(file_dicts, json_file) # Return this list of file ids return [file_obj.id for file_obj in uploaded_files] \ No newline at end of file diff --git a/Scripts/sessionvars.py b/Scripts/sessionvars.py index 932114c..5184037 100644 --- a/Scripts/sessionvars.py +++ b/Scripts/sessionvars.py @@ -132,4 +132,7 @@ def initialize_session_vars(): st.session_state.retry_error = 0 if 'studybuddy' not in st.session_state: # Used to store the studybuddy object - st.session_state.studybuddy = st.session_state.ai_client.beta.assistants.retrieve(os.getenv("OPENAI_ASSISTANT")) \ No newline at end of file + st.session_state.studybuddy = st.session_state.ai_client.beta.assistants.retrieve(os.getenv("OPENAI_ASSISTANT")) + + if 'openai_fileids' not in st.session_state: + st.session_state.openai_fileids = [] \ No newline at end of file diff --git a/app.py b/app.py index 4749ddf..4f5a1d3 100644 --- a/app.py +++ b/app.py @@ -7,9 +7,15 @@ import streamlit as st from markdownlit import mdlit import pandas as pd +import os +from dotenv import load_dotenv, find_dotenv + +load_dotenv(find_dotenv()) sessionvars.initialize_session_vars() +cs.delete_files_from_openai() + custom_width = 250 # Assuming the image is in the same directory as your script @@ -56,8 +62,13 @@ azb.get_class_and_module_files('BUS5000') st.session_state.blobs_to_retrieve = st.session_state.blobs_df[st.session_state.blobs_df['module_name'].isin(st.session_state.selected_modules + ['CLASS_LEVEL'])] ######################### - st.dataframe(st.session_state.blobs_to_retrieve) - st.write(cs.upload_files_ai(st.session_state.blobs_to_retrieve['full_path'])) + #st.dataframe(st.session_state.blobs_to_retrieve) + st.session_state.openai_fileids = cs.upload_files_ai(st.session_state.blobs_to_retrieve['full_path']) + + st.session_state.studybuddy = st.session_state.ai_client.beta.assistants.update( + assistant_id = os.getenv("OPENAI_ASSISTANT"), + file_ids = st.session_state.openai_fileids + ) From 7e9fbf8b5ad088174806ca3360cb46c5e9ae8322 Mon Sep 17 00:00:00 2001 From: ammar Date: Sun, 3 Dec 2023 15:23:42 -0500 Subject: [PATCH 14/27] - saved temp file --- temp.ipynb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/temp.ipynb b/temp.ipynb index d10a138..30b15dd 100644 --- a/temp.ipynb +++ b/temp.ipynb @@ -108,7 +108,7 @@ "metadata": {}, "outputs": [], "source": [ - "def delete_files_from_json():\n", + "def delete_files_from_openai():\n", " json_path = './.bin/files.json'\n", " # Load the file data from the JSON file\n", " with open(json_path, 'r') as json_file:\n", @@ -118,7 +118,7 @@ " for file_info in file_data:\n", " file_id = file_info['id']\n", " # Delete the file using OpenAI API and print the status\n", - " client.files.delete(file_id)\n", + " st.session_state.ai_client.files.delete(file_id)\n", " \n", " # Open the file in write mode and close it to clear its contents\n", " with open(json_path, 'w') as file:\n", From e9314b1b4125971bccc966ed6c82c52f86e145b4 Mon Sep 17 00:00:00 2001 From: ammar Date: Sun, 3 Dec 2023 15:31:40 -0500 Subject: [PATCH 15/27] - file clean up logic change - now operates on the assistant level --- Scripts/__chatscreen.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/Scripts/__chatscreen.py b/Scripts/__chatscreen.py index 936b5f9..8875a86 100644 --- a/Scripts/__chatscreen.py +++ b/Scripts/__chatscreen.py @@ -22,10 +22,19 @@ sessionvars.initialize_session_vars() def delete_files_from_openai(): - files = st.session_state.ai_client.files.list() + files = st.session_state.ai_client.beta.assistants.files.list( + assistant_id=os.getenv("OPENAI_ASSISTANT") + ) + + if not files: # Check if files list is empty + return + + for file in files: file_id = file.id - st.session_state.ai_client.files.delete(file_id) + st.session_state.ai_client.beta.assistants.files.delete( + assistant_id=os.getenv("OPENAI_ASSISTANT"), + file_id=file_id) def context_selection(): """ From 488ba3e5b88fc0bd5841121bf6f758cd9b68ac4e Mon Sep 17 00:00:00 2001 From: ammar Date: Sun, 3 Dec 2023 20:26:32 -0500 Subject: [PATCH 16/27] - IT WORKDS --- Scripts/__chatscreen.py | 27 ++++++-- Scripts/sessionvars.py | 16 +++-- app.py | 135 ++++++++++++++++++++++++++++++++++++---- temp.ipynb | 119 ++++++++++++++++++----------------- testing_chat.py | 107 +++++++++++++++++++++++++++++++ 5 files changed, 325 insertions(+), 79 deletions(-) create mode 100644 testing_chat.py diff --git a/Scripts/__chatscreen.py b/Scripts/__chatscreen.py index 8875a86..8cdc1e1 100644 --- a/Scripts/__chatscreen.py +++ b/Scripts/__chatscreen.py @@ -22,19 +22,36 @@ sessionvars.initialize_session_vars() def delete_files_from_openai(): + files = st.session_state.ai_client.files.list() + # Check if there are no files + if not files.data: + pass # End the function if there are no files + + # If there are files, proceed with deletion + for file in files.data: + file_id = file.id + st.session_state.ai_client.files.delete( + file_id=file_id + ) + print(f"Deleted file {file_id}") + files = st.session_state.ai_client.beta.assistants.files.list( assistant_id=os.getenv("OPENAI_ASSISTANT") ) - if not files: # Check if files list is empty - return + # Check if there are no files + if not files.data: + pass - - for file in files: + # If there are files, proceed with deletion + for file in files.data: file_id = file.id st.session_state.ai_client.beta.assistants.files.delete( assistant_id=os.getenv("OPENAI_ASSISTANT"), - file_id=file_id) + file_id=file_id + ) + print(f"Deleted file {file_id}") + def context_selection(): """ diff --git a/Scripts/sessionvars.py b/Scripts/sessionvars.py index 5184037..e622005 100644 --- a/Scripts/sessionvars.py +++ b/Scripts/sessionvars.py @@ -130,9 +130,15 @@ def initialize_session_vars(): if "retry_error" not in st.session_state: # Used for error handling st.session_state.retry_error = 0 - - if 'studybuddy' not in st.session_state: # Used to store the studybuddy object - st.session_state.studybuddy = st.session_state.ai_client.beta.assistants.retrieve(os.getenv("OPENAI_ASSISTANT")) - + if 'openai_fileids' not in st.session_state: - st.session_state.openai_fileids = [] \ No newline at end of file + st.session_state.openai_fileids = [] + + if 'initialized' not in st.session_state: + st.session_state.initialized = False + + if 'cleanup' not in st.session_state: + st.session_state.cleanup = False + + if 'uploaded_to_openai' not in st.session_state: + st.session_state.uploaded_to_openai = False \ No newline at end of file diff --git a/app.py b/app.py index 4f5a1d3..eaae0f1 100644 --- a/app.py +++ b/app.py @@ -7,14 +7,24 @@ import streamlit as st from markdownlit import mdlit import pandas as pd -import os +import os +import time from dotenv import load_dotenv, find_dotenv load_dotenv(find_dotenv()) sessionvars.initialize_session_vars() -cs.delete_files_from_openai() +print("TOP OF SCRIPT+++++++++++++++++++++++++++++++++++++++++++++++") +if hasattr(st.session_state.run, 'id'): + print(f"Current run id: {st.session_state.run.id}") + print(f"Current run status: {st.session_state.run.status}") + +if st.session_state.cleanup == False: + print("Cleaning up files from OpenAI") + cs.delete_files_from_openai() + print("DONE") + st.session_state.cleanup = True custom_width = 250 @@ -52,23 +62,122 @@ if st.session_state.context_selection_toggle: cs.context_selection() - # blob runs only after context has been selected + # block runs only after context has been selected if st.session_state.selected_modules not in [None, []]: col4, col5 = st.columns([1,1]) col4.write(f"Chatting about: {st.session_state.selected_modules}") col5.write(f"Current session: {st.session_state.session_id}") - azb.get_class_and_module_files('BUS5000') + # Get all the class and module files + azb.get_class_and_module_files(st.session_state.class_info['class_name']) + # Retrieve only the selected modules' files st.session_state.blobs_to_retrieve = st.session_state.blobs_df[st.session_state.blobs_df['module_name'].isin(st.session_state.selected_modules + ['CLASS_LEVEL'])] ######################### #st.dataframe(st.session_state.blobs_to_retrieve) - st.session_state.openai_fileids = cs.upload_files_ai(st.session_state.blobs_to_retrieve['full_path']) - - st.session_state.studybuddy = st.session_state.ai_client.beta.assistants.update( - assistant_id = os.getenv("OPENAI_ASSISTANT"), - file_ids = st.session_state.openai_fileids - ) - - - + # Store the openai file ids of all the files uploaded to the assistant + if st.session_state.uploaded_to_openai == False: # To ensure this only happens once + st.session_state.openai_fileids = cs.upload_files_ai(st.session_state.blobs_to_retrieve['full_path']) + st.session_state.uploaded_to_openai = True + # Initialize the assistant + if "studybuddy" not in st.session_state: + st.session_state.studybuddy = st.session_state.ai_client.beta.assistants.retrieve(os.getenv('OPENAI_ASSISTANT')) + + # Create a new thread for this session + st.session_state.thread = st.session_state.ai_client.beta.threads.create( + metadata={ + 'session_id': st.session_state.session_id, + } + ) + # If the run is completed, display the messages + elif hasattr(st.session_state.run, 'status') and st.session_state.run.status == "completed": + # Retrieve the list of messages + st.session_state.messages = st.session_state.ai_client.beta.threads.messages.list( + thread_id=st.session_state.thread.id + ) + # Display sources + for thread_message in st.session_state.messages.data: + for message_content in thread_message.content: + # Access the actual text content + message_content = message_content.text + annotations = message_content.annotations + citations = [] + + # Iterate over the annotations and add footnotes + for index, annotation in enumerate(annotations): + # Replace the text with a footnote + message_content.value = message_content.value.replace(annotation.text, f' [{index}]') + + # Gather citations based on annotation attributes + if (file_citation := getattr(annotation, 'file_citation', None)): + cited_file = st.session_state.ai_client.files.retrieve(file_citation.file_id) + citations.append(f'[{index}] {file_citation.quote} from {cited_file.filename}') + elif (file_path := getattr(annotation, 'file_path', None)): + cited_file = st.session_state.ai_client.files.retrieve(file_path.file_id) + citations.append(f'[{index}] Click to download {cited_file.filename}') + # Note: File download functionality not implemented above for brevity + + # Add footnotes to the end of the message before displaying to user + message_content.value += '\n' + '\n'.join(citations) + # Display messages + for message in reversed(st.session_state.messages.data): + if message.role in ["user", "assistant"]: + with st.chat_message(message.role): + for content_part in message.content: + message_text = content_part.text.value + st.markdown(message_text) + + if prompt := st.chat_input("How can I help you?"): + with st.chat_message('user'): + st.write(prompt) + + # Add message to the thread + st.session_state.messages = st.session_state.ai_client.beta.threads.messages.create( + thread_id=st.session_state.thread.id, + role="user", + content=prompt + ) + # Do a run to process the messages in the thread + st.session_state.run = st.session_state.ai_client.beta.threads.runs.create( + thread_id=st.session_state.thread.id, + assistant_id=st.session_state.studybuddy.id, + ) + print(f"Current run id: {st.session_state.run.id}") + if st.session_state.retry_error < 3: + time.sleep(1) # Wait 1 second before checking run status + st.rerun() + # Check if 'run' object has 'status' attribute + if hasattr(st.session_state.run, 'status'): + # Handle the 'running' status + if st.session_state.run.status == "running": + with st.chat_message('assistant'): + st.write("Thinking ......") + if st.session_state.retry_error < 3: + time.sleep(5) # Short delay to prevent immediate rerun, adjust as needed + st.rerun() + + # Handle the 'failed' status + elif st.session_state.run.status == "failed": + st.session_state.retry_error += 1 + with st.chat_message('assistant'): + if st.session_state.retry_error < 3: + st.write("Run failed, retrying ......") + time.sleep(5) # Longer delay before retrying + st.rerun() + else: + st.error("FAILED: The OpenAI API is currently processing too many requests. Please try again later ......") + + # Handle any status that is not 'completed' + elif st.session_state.run.status != "completed": + print("""# Handle any status that is not 'completed' + elif st.session_state.run.status != "completed":""") + print(f"Current run status: {st.session_state.run.status}") + print(f"Current run id: {st.session_state.run.id}") + # Attempt to retrieve the run again, possibly redundant if there's no other status but 'running' or 'failed' + st.session_state.run = st.session_state.ai_client.beta.threads.runs.retrieve( + thread_id=st.session_state.thread.id, + run_id=st.session_state.run.id, + ) + if st.session_state.retry_error < 3: + time.sleep(3) + st.rerun() \ No newline at end of file diff --git a/temp.ipynb b/temp.ipynb index 30b15dd..854472c 100644 --- a/temp.ipynb +++ b/temp.ipynb @@ -2,21 +2,34 @@ "cells": [ { "cell_type": "code", - "execution_count": 9, + "execution_count": 18, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 18, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "from azure.storage.blob import BlobServiceClient, BlobClient, ContainerClient\n", "import openai\n", "from openai import OpenAI\n", "from dotenv import load_dotenv, find_dotenv\n", "import os\n", - "import json" + "import json\n", + "\n", + "load_dotenv(find_dotenv())" ] }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 19, "metadata": {}, "outputs": [], "source": [ @@ -25,7 +38,7 @@ "\n", "client = OpenAI(\n", " api_key=os.getenv(\"OPENAI_API_KEY\"),\n", - ")" + ")\n" ] }, { @@ -87,73 +100,67 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 22, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "SyncPage[FileObject](data=[], object='list', has_more=False)\n" + "Deleted file file-183J5qW4YR5swyMd67rIPLFA\n", + "Deleted file file-2iMor5j5BBN8jYLou1rvNgl2\n", + "Deleted file file-Rz4NLaIi5bdhsDUeDwy674r4\n", + "Deleted file file-hOIlgRLDBy7aEH3Jgu3mjb9B\n", + "Deleted file file-lGUQHlgWAKeozDd08feZcRtM\n", + "Deleted file file-9IJfZpAfX39gZw2iT3CpHYtk\n", + "Deleted file file-y5wq3UbFHy1YZdaLjY0W9AIH\n", + "Deleted file file-l4cV4p8PMxxdPb5utucARJ0r\n", + "Deleted file file-RBs8iX0qsVwxEKFIivKTmPBe\n", + "Deleted file file-nmf9Fi5S15uvasWHijN5vvXK\n", + "Deleted file file-y5aek5I2heiKhBRQH0YmCIAS\n", + "Deleted file file-qCpLcxbvBROxCG1ehFSZY5Oh\n", + "Deleted file file-xezeLthSxhG1tlH6F9tZa6mf\n", + "Deleted file file-lwXRv8ScOAwOdvkwGxmuanaY\n", + "Deleted file file-exAi161HW9VCQoV8tqVigC1O\n", + "ending..\n" ] } ], - "source": [ - "print(client.files.list())" - ] - }, - { - "cell_type": "code", - "execution_count": 21, - "metadata": {}, - "outputs": [], "source": [ "def delete_files_from_openai():\n", - " json_path = './.bin/files.json'\n", - " # Load the file data from the JSON file\n", - " with open(json_path, 'r') as json_file:\n", - " file_data = json.load(json_file)\n", + " files = client.files.list()\n", + " # Check if there are no files\n", + " if not files.data:\n", + " print(\"ending..\")\n", + " return # End the function if there are no files\n", "\n", - " # Iterate through each file in the list\n", - " for file_info in file_data:\n", - " file_id = file_info['id']\n", - " # Delete the file using OpenAI API and print the status\n", - " st.session_state.ai_client.files.delete(file_id)\n", + " # If there are files, proceed with deletion\n", + " for file in files.data:\n", + " file_id = file.id\n", + " client.files.delete(\n", + " file_id=file_id\n", + " ) \n", + " print(f\"Deleted file {file_id}\")\n", " \n", - " # Open the file in write mode and close it to clear its contents\n", - " with open(json_path, 'w') as file:\n", - " pass # Opening in 'w' mode and closing will clear the file\n", - "\n", - "delete_files_from_json()" - ] - }, - { - "cell_type": "code", - "execution_count": 26, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "['file-1I7bhX1YyIhKCJ7ddouuPy60', 'file-oxcDEGJLNutupSASmnV2vM3w', 'file-fQZCpJfuH5J6TQ6O6Qpt3Zxn']\n" - ] - } - ], - "source": [ - "json_path = './.bin/files.json'\n", + " files = client.beta.assistants.files.list(\n", + " assistant_id=os.getenv(\"OPENAI_ASSISTANT\")\n", + " )\n", "\n", - "fileids = []\n", + " # Check if there are no files\n", + " if not files.data:\n", + " print(\"ending..\")\n", + " return # End the function if there are no files\n", "\n", - "# Load the file data from the JSON file\n", - "with open(json_path, 'r') as json_file:\n", - " file_data = json.load(json_file)\n", - " # Iterate through each file in the list\n", - " for file_info in file_data:\n", - " file_id = file_info['id']\n", - " fileids.append(file_id)\n", + " # If there are files, proceed with deletion\n", + " for file in files.data:\n", + " file_id = file.id\n", + " client.beta.assistants.files.delete(\n", + " assistant_id=os.getenv(\"OPENAI_ASSISTANT\"),\n", + " file_id=file_id\n", + " )\n", " \n", - "print(fileids)" + "# Call the function to execute\n", + "delete_files_from_openai()\n" ] } ], diff --git a/testing_chat.py b/testing_chat.py new file mode 100644 index 0000000..2f05306 --- /dev/null +++ b/testing_chat.py @@ -0,0 +1,107 @@ +import streamlit as st +import openai +import uuid +import time +from dotenv import load_dotenv, find_dotenv +import os + +load_dotenv(find_dotenv()) + +from openai import OpenAI +client = OpenAI(api_key=os.getenv("OPENAI_API_KEY")) + +if "studybuddy" not in st.session_state: + st.session_state.assistant = openai.beta.assistants.retrieve(st.secrets["OPENAI_ASSISTANT"]) + + # Create a new thread for this session + st.session_state.thread = client.beta.threads.create( + metadata={ + 'session_id': st.session_state.session_id, + } + ) +# If the run is completed, display the messages +elif hasattr(st.session_state.run, 'status') and st.session_state.run.status == "completed": + # Retrieve the list of messages + st.session_state.messages = client.beta.threads.messages.list( + thread_id=st.session_state.thread.id + ) +# Display sources + for thread_message in st.session_state.messages.data: + for message_content in thread_message.content: + # Access the actual text content + message_content = message_content.text + annotations = message_content.annotations + citations = [] + + # Iterate over the annotations and add footnotes + for index, annotation in enumerate(annotations): + # Replace the text with a footnote + message_content.value = message_content.value.replace(annotation.text, f' [{index}]') + + # Gather citations based on annotation attributes + if (file_citation := getattr(annotation, 'file_citation', None)): + cited_file = client.files.retrieve(file_citation.file_id) + citations.append(f'[{index}] {file_citation.quote} from {cited_file.filename}') + elif (file_path := getattr(annotation, 'file_path', None)): + cited_file = client.files.retrieve(file_path.file_id) + citations.append(f'[{index}] Click to download {cited_file.filename}') + # Note: File download functionality not implemented above for brevity + + # Add footnotes to the end of the message before displaying to user + message_content.value += '\n' + '\n'.join(citations) +# Display messages + for message in reversed(st.session_state.messages.data): + if message.role in ["user", "assistant"]: + with st.chat_message(message.role): + for content_part in message.content: + message_text = content_part.text.value + st.markdown(message_text) +if prompt := st.chat_input("How can I help you?"): + with st.chat_message('user'): + st.write(prompt) + + # Add message to the thread + st.session_state.messages = client.beta.threads.messages.create( + thread_id=st.session_state.thread.id, + role="user", + content=prompt + ) +# Do a run to process the messages in the thread + st.session_state.run = client.beta.threads.runs.create( + thread_id=st.session_state.thread.id, + assistant_id=st.session_state.assistant.id, + ) + if st.session_state.retry_error < 3: + time.sleep(1) # Wait 1 second before checking run status + st.rerun() +# Check if 'run' object has 'status' attribute +if hasattr(st.session_state.run, 'status'): + # Handle the 'running' status + if st.session_state.run.status == "running": + with st.chat_message('assistant'): + st.write("Thinking ......") + if st.session_state.retry_error < 3: + time.sleep(1) # Short delay to prevent immediate rerun, adjust as needed + st.rerun() + + # Handle the 'failed' status + elif st.session_state.run.status == "failed": + st.session_state.retry_error += 1 + with st.chat_message('assistant'): + if st.session_state.retry_error < 3: + st.write("Run failed, retrying ......") + time.sleep(3) # Longer delay before retrying + st.rerun() + else: + st.error("FAILED: The OpenAI API is currently processing too many requests. Please try again later ......") + + # Handle any status that is not 'completed' + elif st.session_state.run.status != "completed": + # Attempt to retrieve the run again, possibly redundant if there's no other status but 'running' or 'failed' + st.session_state.run = client.beta.threads.runs.retrieve( + thread_id=st.session_state.thread.id, + run_id=st.session_state.run.id, + ) + if st.session_state.retry_error < 3: + time.sleep(3) + st.rerun() \ No newline at end of file From 7b68859ef2a69553abfc2b00b878a4efa3ea8d63 Mon Sep 17 00:00:00 2001 From: ammar Date: Sun, 3 Dec 2023 21:33:57 -0500 Subject: [PATCH 17/27] - feature complete --- Scripts/__chatscreen.py | 49 +++++++++++++++++++++++++++-------------- app.py | 30 ++++++++++++++++--------- 2 files changed, 53 insertions(+), 26 deletions(-) diff --git a/Scripts/__chatscreen.py b/Scripts/__chatscreen.py index 8cdc1e1..277e0a6 100644 --- a/Scripts/__chatscreen.py +++ b/Scripts/__chatscreen.py @@ -22,35 +22,50 @@ sessionvars.initialize_session_vars() def delete_files_from_openai(): + total_files = 0 + progress_value = 0 + + # First list of files files = st.session_state.ai_client.files.list() - # Check if there are no files - if not files.data: - pass # End the function if there are no files + total_files += len(files.data) if files.data else 0 - # If there are files, proceed with deletion - for file in files.data: - file_id = file.id - st.session_state.ai_client.files.delete( - file_id=file_id - ) - print(f"Deleted file {file_id}") - - files = st.session_state.ai_client.beta.assistants.files.list( + # Second list of files + assistant_files = st.session_state.ai_client.beta.assistants.files.list( assistant_id=os.getenv("OPENAI_ASSISTANT") ) + total_files += len(assistant_files.data) if assistant_files.data else 0 + + # Create a progress bar + progress_bar = st.progress(0, text='Getting ready to study :)!') - # Check if there are no files - if not files.data: - pass + # Function to update progress + def update_progress(): + nonlocal progress_value + progress_value += 1 + progress_bar.progress(progress_value / total_files) - # If there are files, proceed with deletion + # Delete files from the first list for file in files.data: + file_id = file.id + st.session_state.ai_client.files.delete(file_id=file_id) + print(f"Deleted file {file_id}") + update_progress() + + # Delete files from the second list + for file in assistant_files.data: file_id = file.id st.session_state.ai_client.beta.assistants.files.delete( assistant_id=os.getenv("OPENAI_ASSISTANT"), file_id=file_id ) print(f"Deleted file {file_id}") + update_progress() + + # Complete the progress bar if there were no files + if total_files == 0: + progress_bar.progress(1) + progress_bar.empty() + def context_selection(): @@ -117,6 +132,8 @@ def initialize_chat(): initial_prompt += f" -Module: {module}\n\n" initial_prompt += f" -Learning outcomes: {outcome}\n\n" + initial_prompt += f"Here is info on the files you recieved:\n\n{st.session_state.blobs_to_retrieve}" + return initial_prompt diff --git a/app.py b/app.py index eaae0f1..be61ae2 100644 --- a/app.py +++ b/app.py @@ -15,10 +15,6 @@ sessionvars.initialize_session_vars() -print("TOP OF SCRIPT+++++++++++++++++++++++++++++++++++++++++++++++") -if hasattr(st.session_state.run, 'id'): - print(f"Current run id: {st.session_state.run.id}") - print(f"Current run status: {st.session_state.run.status}") if st.session_state.cleanup == False: print("Cleaning up files from OpenAI") @@ -82,7 +78,10 @@ # Initialize the assistant if "studybuddy" not in st.session_state: st.session_state.studybuddy = st.session_state.ai_client.beta.assistants.retrieve(os.getenv('OPENAI_ASSISTANT')) - + st.session_state.studybuddy = st.session_state.ai_client.beta.assistants.update( + assistant_id=st.session_state.studybuddy.id, + file_ids=st.session_state.openai_fileids + ) # Create a new thread for this session st.session_state.thread = st.session_state.ai_client.beta.threads.create( metadata={ @@ -122,12 +121,23 @@ # Display messages for message in reversed(st.session_state.messages.data): if message.role in ["user", "assistant"]: - with st.chat_message(message.role): - for content_part in message.content: - message_text = content_part.text.value - st.markdown(message_text) + for content_part in message.content: + message_text = content_part.text.value + # Check if the message contains the specified phrase + if " INITIAL PROMPT " not in message_text: + with st.chat_message(message.role): + st.markdown(message_text) + else: + # Optionally, you can print a message to the console for debugging + print("Skipped a message containing the initial prompt info.") - if prompt := st.chat_input("How can I help you?"): + if st.session_state.initialized == False: + prompt = cs.initialize_chat() + st.session_state.initialized = True + else: + prompt = st.chat_input("How can I help you?") + + if prompt: with st.chat_message('user'): st.write(prompt) From 5448f9eec5233cf097e69191ec6abe9857ce3f57 Mon Sep 17 00:00:00 2001 From: ammar Date: Sun, 3 Dec 2023 21:50:45 -0500 Subject: [PATCH 18/27] - removed re --- requirements.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index f9576d2..95156fa 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,6 +11,5 @@ azure-search-documents==11.4.0b8 azure-storage-blob azure-identity PyPDF2 -re io markdownlit \ No newline at end of file From 2870b1b7d46db96e2bb85d541a8d535ad02b09f7 Mon Sep 17 00:00:00 2001 From: ammar Date: Sun, 3 Dec 2023 21:59:01 -0500 Subject: [PATCH 19/27] - requirements clean up --- requirements.txt | 4 ---- 1 file changed, 4 deletions(-) diff --git a/requirements.txt b/requirements.txt index 95156fa..4bddaa3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,4 @@ openai==1.3.7 -langchain -faiss-cpu -pypdf tiktoken python-dotenv pyodbc @@ -10,6 +7,5 @@ wget azure-search-documents==11.4.0b8 azure-storage-blob azure-identity -PyPDF2 io markdownlit \ No newline at end of file From e8fdf64af7a74bbd23b574a7318e0d1bbdfafc65 Mon Sep 17 00:00:00 2001 From: ammar Date: Mon, 4 Dec 2023 19:25:49 -0500 Subject: [PATCH 20/27] - updated requirements.txt --- requirements.txt | 16 +++++++--------- temp.ipynb | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 9 deletions(-) diff --git a/requirements.txt b/requirements.txt index 4bddaa3..f6e8182 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,11 +1,9 @@ openai==1.3.7 -tiktoken -python-dotenv -pyodbc -streamlit -wget +python-dotenv==1.0.0 +pyodbc==5.0.1 +streamlit==1.28.1 +wget==3.2 azure-search-documents==11.4.0b8 -azure-storage-blob -azure-identity -io -markdownlit \ No newline at end of file +azure-storage-blob==12.19.0 +azure-identity==1.15.0 +tiktoken==0.5.1 diff --git a/temp.ipynb b/temp.ipynb index 854472c..1ad62c7 100644 --- a/temp.ipynb +++ b/temp.ipynb @@ -162,6 +162,52 @@ "# Call the function to execute\n", "delete_files_from_openai()\n" ] + }, + { + "cell_type": "code", + "execution_count": 25, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "openai==1.3.7\n", + "python-dotenv==1.0.0\n", + "pyodbc==5.0.1\n", + "streamlit==1.28.1\n", + "wget==3.2\n", + "azure-search-documents==11.4.0b8\n", + "azure-storage-blob==12.19.0\n", + "azure-identitytiktoken not found\n" + ] + } + ], + "source": [ + "import pkg_resources\n", + "\n", + "# List of your packages\n", + "packages = [\n", + " 'openai', \n", + " 'python-dotenv', \n", + " 'pyodbc', \n", + " 'streamlit', \n", + " 'wget', \n", + " 'azure-search-documents', \n", + " 'azure-storage-blob', \n", + " 'azure-identity'\n", + "]\n", + "\n", + "for package in packages:\n", + " try:\n", + " version = pkg_resources.get_distribution(package).version\n", + " print(f\"{package}=={version}\")\n", + " except pkg_resources.DistributionNotFound:\n", + " print(f\"{package} not found\")\n", + "\n", + "# If you want to save this to a file, you can redirect the output to 'requirements.txt'\n", + "# Run this script with 'python script_name.py > requirements.txt' in your terminal\n" + ] } ], "metadata": { From fd16bdd79eeeefdc522b33a943d05cbcd67afa6f Mon Sep 17 00:00:00 2001 From: Ammar Syed Ali <60233164+ammarali0416@users.noreply.github.com> Date: Mon, 4 Dec 2023 19:36:49 -0500 Subject: [PATCH 21/27] Add or update the Azure App Service build and deployment workflow config --- .../development_timetostudybuddy.yml | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 .github/workflows/development_timetostudybuddy.yml diff --git a/.github/workflows/development_timetostudybuddy.yml b/.github/workflows/development_timetostudybuddy.yml new file mode 100644 index 0000000..d4275e7 --- /dev/null +++ b/.github/workflows/development_timetostudybuddy.yml @@ -0,0 +1,67 @@ +# Docs for the Azure Web Apps Deploy action: https://github.com/Azure/webapps-deploy +# More GitHub Actions for Azure: https://github.com/Azure/actions +# More info on Python, GitHub Actions, and Azure App Service: https://aka.ms/python-webapps-actions + +name: Build and deploy Python app to Azure Web App - timetostudybuddy + +on: + push: + branches: + - development + workflow_dispatch: + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Set up Python version + uses: actions/setup-python@v1 + with: + python-version: '3.9' + + - name: Create and start virtual environment + run: | + python -m venv venv + source venv/bin/activate + + - name: Install dependencies + run: pip install -r requirements.txt + + # Optional: Add step to run tests here (PyTest, Django test suites, etc.) + - name: Zip artifact for deployment + run: zip release.zip ./* -r + + - name: Upload artifact for deployment jobs + uses: actions/upload-artifact@v3 + with: + name: python-app + path: | + release.zip + !venv/ + + deploy: + runs-on: ubuntu-latest + needs: build + environment: + name: 'Production' + url: ${{ steps.deploy-to-webapp.outputs.webapp-url }} + + steps: + - name: Download artifact from build job + uses: actions/download-artifact@v3 + with: + name: python-app + + - name: Unzip artifact for deployment + run: unzip release.zip + + - name: 'Deploy to Azure Web App' + uses: azure/webapps-deploy@v2 + id: deploy-to-webapp + with: + app-name: 'timetostudybuddy' + slot-name: 'Production' + publish-profile: ${{ secrets.AZUREAPPSERVICE_PUBLISHPROFILE_55AB58D01B2644DE951E25FDE92530B2 }} From 55215477eda6f1e3105075718a3b070a1cee06cf Mon Sep 17 00:00:00 2001 From: ammar Date: Mon, 4 Dec 2023 21:05:26 -0500 Subject: [PATCH 22/27] - edited requirements file --- requirements.txt | 1 + temp.ipynb | 7 ++++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/requirements.txt b/requirements.txt index f6e8182..65cdebb 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,3 +7,4 @@ azure-search-documents==11.4.0b8 azure-storage-blob==12.19.0 azure-identity==1.15.0 tiktoken==0.5.1 +markdownlit==0.0.7 \ No newline at end of file diff --git a/temp.ipynb b/temp.ipynb index 1ad62c7..035d76d 100644 --- a/temp.ipynb +++ b/temp.ipynb @@ -165,7 +165,7 @@ }, { "cell_type": "code", - "execution_count": 25, + "execution_count": 26, "metadata": {}, "outputs": [ { @@ -179,7 +179,7 @@ "wget==3.2\n", "azure-search-documents==11.4.0b8\n", "azure-storage-blob==12.19.0\n", - "azure-identitytiktoken not found\n" + "azure-identity==1.15.0\n" ] } ], @@ -195,7 +195,8 @@ " 'wget', \n", " 'azure-search-documents', \n", " 'azure-storage-blob', \n", - " 'azure-identity'\n", + " 'azure-identity',\n", + " 'markdownlit'\n", "]\n", "\n", "for package in packages:\n", From a935691996560ae701d604d742bf52aa721c4552 Mon Sep 17 00:00:00 2001 From: aanam20 Date: Mon, 4 Dec 2023 21:45:51 -0500 Subject: [PATCH 23/27] Added descriptions for each button on sidebar --- Scripts/__modules.py | 2 +- Scripts/__sidebar.py | 82 ++++++++++++++++++++++++++++++++++++++++---- app.py | 1 - 3 files changed, 76 insertions(+), 9 deletions(-) diff --git a/Scripts/__modules.py b/Scripts/__modules.py index 0e2b5d4..9c6cdb1 100644 --- a/Scripts/__modules.py +++ b/Scripts/__modules.py @@ -37,7 +37,7 @@ def show_module(): def create_new_module(): # Input field and button for new module creation new_module_name = st.text_input("Enter the name for the new module") - new_module_learning_outcomes = st.text_area("Enter the learning outcomes for the module", "Enter the learning outcomes for the new class", label_visibility='hidden') + new_module_learning_outcomes = st.text_area("Enter the learning outcomes for the module", "Enter the learning outcomes for the module", label_visibility='hidden') if st.button("Submit New Module"): if new_module_name and new_module_learning_outcomes: diff --git a/Scripts/__sidebar.py b/Scripts/__sidebar.py index efdbabb..5839a00 100644 --- a/Scripts/__sidebar.py +++ b/Scripts/__sidebar.py @@ -23,12 +23,23 @@ def teacher_sidebar(): with st.sidebar: st.write(""" Here's a quick guide to the buttons you'll find on this page: - - **FAQ**: View and answer students' questions. 🎓 - - **Schedule**: Use this to view and manage the class schedule. 🗓️ - - **Upload Files**: Upload class materials, assignments, and other resources. 📚 - """) + - **Class**: Navigate through classes. + - **Modules**: Upload class materials, assignments, and other resources. 📚 + - **FAQs**: View and answer students' questions. 🎓 + - **Manage Assignments**: Use this to view and manage the class's tasks. 🗓️ + """) ## Class management st.sidebar.title("Class") + + class_description = """ + **The Class feature allows the instructor to:** + - Create new classes. + - Upload educational materials pertaining to the class as a whole. + - Auto generate class code to enable students to join their class. + """ + + st.write(class_description) + cm.show_class() col1, col2 = st.columns([1,1]) @@ -57,6 +68,15 @@ def teacher_sidebar(): #################################### # Module management st.sidebar.title("Modules") + + module_description = """ + **This functionality assists the educator in structuring their course by:** + - Creating and deleting distinct modules within classes. + - Uploading specific educational materials within each module. + - Enabling students to pose targeted questions related to each module through the Study Buddy chat box. + """ + + st.write(module_description) md.show_module() col3, col4, col5 = st.columns([1,1,1]) @@ -91,10 +111,31 @@ def teacher_sidebar(): #################################### ### Faq functions st.sidebar.title("FAQs") + + FAQs_description = """ + **The FAQ feature empowers the instructor to:** + - Curate a set of frequently asked questions relevant to the class. + - Display this FAQ repository prominently in the student interface. + - Offer comprehensive answers to address any queries about the course. + - Answer questions directly sent by students. + """ + + st.write(FAQs_description) + fq.teacher_faqs() #schedule st.sidebar.title("Manage Assignments") + + manage_description = """ + **The Managing Assignments function allows teachers to:** + - Generate tasks with due dates corresponding to the selected class. + - Make these tasks visible on the student interface. + - Enable students to mark them as completed. + """ + + st.write(manage_description) + sc.teacher_schedule() @@ -103,11 +144,18 @@ def student_sidebar(): with st.sidebar: st.write(""" Here's a quick guide to the buttons you'll find on this page: - - **FAQ**: View FAQs or ask a new one. 🎓 - - **Schedule**: Use this to view and manage the class schedule. 🗓️ - - **Upload Files**: Upload your notes, outlines, etc. 📚 + - **Manage Classes**: Navigate through courses. + - **Modules**: Upload your notes to a specified class module. 📚 + - **FAQ**: View FAQs or create a new one. 🎓 + - **Upcoming Assignments**: Use this to view and manage the class's assignments. 🗓️ """) st.sidebar.title("Manage Classes") + manage_classes_description = """ + **The Manage Class feature allows the student to:** + - View and select their classes from a list. + - Join a new class using a class code provided by the instructor. + """ + st.write(manage_classes_description) cm.show_class() # Button to join a new class @@ -121,6 +169,13 @@ def student_sidebar(): ################################ # Modules st.sidebar.title("Modules") + modules_description = """ + **This Modules functionality allows the student to:** + - Navigate the modules in their selected course. + - Upload files pertaining to modules within the selected course which can then be used by the Study Buddy chat bot. + """ + + st.write(modules_description) md.show_module() if st.button("Upload File", key='module_upload'): @@ -130,9 +185,22 @@ def student_sidebar(): fu.upload_module_file() st.sidebar.title("FAQs") + faqs_description = """ + **The FAQ feature empowers the student to:** + - Access and explore the FAQ repository, featuring questions deemed useful by the teacher. + - Directly pose questions to the teacher. + """ + st.write(faqs_description) fq.student_faqs() #schedule st.sidebar.title("Upcoming Assignments") + upcoming_assignments_description = """ + **The Upcoming Assignments function allows the student to:** + - Review tasks assigned by the instructor along with their respective due dates. + - Utilize the 'Done' button upon completing an assignment for organizational purposes, and the accomplished tasks will be displayed in the 'Completed Tasks' section. + """ + st.write(upcoming_assignments_description) + sc.student_schedule() diff --git a/app.py b/app.py index be61ae2..1d276e2 100644 --- a/app.py +++ b/app.py @@ -35,7 +35,6 @@ st.image(logo_path, width= custom_width) st.subheader("An Intelligent Education App", ) - # Display the login container # This block defining what the app does when the user_id value is equal to None if not st.session_state.user_info['user_id']: From 101f2314a4f4111c1639ea0f2df0219fdb1fc952 Mon Sep 17 00:00:00 2001 From: ammar Date: Tue, 5 Dec 2023 15:36:05 -0500 Subject: [PATCH 24/27] - clean up on the teacher side bar buttons - added some more instructions on the sign up and login page --- Scripts/__classmanager.py | 2 +- Scripts/__fileupload.py | 4 ++-- Scripts/__login.py | 5 +++-- Scripts/__modules.py | 4 ++-- Scripts/__sidebar.py | 17 +++++++++++------ app.py | 1 + 6 files changed, 20 insertions(+), 13 deletions(-) diff --git a/Scripts/__classmanager.py b/Scripts/__classmanager.py index bda0de0..8beb35d 100644 --- a/Scripts/__classmanager.py +++ b/Scripts/__classmanager.py @@ -49,7 +49,7 @@ def create_new_class(): new_class_name = st.text_input("Enter the name for the new class", "Name?", label_visibility='hidden') new_class_learning_outcomes = st.text_area("Enter the learning outcomes for the new class", "Enter the learning outcomes for the new class", label_visibility='hidden') - if st.button("Submit Class"): + if st.button("Submit", use_container_width=True): if new_class_name and new_class_learning_outcomes: azsqldb.new_class(st.session_state.user_info['user_id'], st.session_state.sqlcursor, new_class_name, new_class_learning_outcomes) class_data = fetch_class_data() # Refresh the class data diff --git a/Scripts/__fileupload.py b/Scripts/__fileupload.py index 1d33ef5..bb9ab9c 100644 --- a/Scripts/__fileupload.py +++ b/Scripts/__fileupload.py @@ -23,7 +23,7 @@ def upload_class_file(): accept_multiple_files=True, help=help_text, key = st.session_state.upload_key) - if st.button("Submit", key='class_upload_submit'): + if st.button("Submit", key='class_upload_submit', use_container_width=True): # Display a warning if the user hasn't uploaded a file if not files: st.warning("Please upload a file first!") @@ -43,7 +43,7 @@ def upload_module_file(): accept_multiple_files=True, help=help_text, key = "fufuf" + st.session_state.upload_key_2) - if st.button("Submit"): + if st.button("Submit", use_container_width=True): # Display a warning if the user hasn't uploaded a file if not files: st.warning("Please upload a file first!") diff --git a/Scripts/__login.py b/Scripts/__login.py index 7d6a861..bf9c4f6 100644 --- a/Scripts/__login.py +++ b/Scripts/__login.py @@ -16,13 +16,14 @@ def signup(): st.subheader("Sign Up") - + st.info("Please fill in the details below to create a new account.") # User details input new_username = st.text_input("Create a new username") new_password = st.text_input("Create a password", type="password") email = st.text_input("Enter your email") school = st.text_input("Enter your school") + st.info("Students must have a class code to join classes. Only teachers can create classes") # Role selection using radio buttons role = st.radio("Select your role", ["student", "teacher"]) @@ -52,7 +53,7 @@ def login(): def LoginContainer(): # Main application # Create a dropdown to select action (Sign Up or Log In) - + st.info("Trying to demo the app?\n\n Use pre-made accounts found here: https://github.com/ammarali0416/StudyBuddy") selected_action = st.selectbox("Select an action:", ["Log In", "Sign Up"], key="login_selectbox") if selected_action == "Sign Up": diff --git a/Scripts/__modules.py b/Scripts/__modules.py index 9c6cdb1..de4431e 100644 --- a/Scripts/__modules.py +++ b/Scripts/__modules.py @@ -39,7 +39,7 @@ def create_new_module(): new_module_name = st.text_input("Enter the name for the new module") new_module_learning_outcomes = st.text_area("Enter the learning outcomes for the module", "Enter the learning outcomes for the module", label_visibility='hidden') - if st.button("Submit New Module"): + if st.button("Submit New Module", use_container_width=True): if new_module_name and new_module_learning_outcomes: # Call the function to add a new module to the database azsqldb.new_module(st.session_state.class_info['class_id'], new_module_name, new_module_learning_outcomes, st.session_state.sqlcursor) @@ -68,7 +68,7 @@ def delete_module(): module_id = module_data[selected_module_name] # Assuming module_data maps module names to their ids # Button to delete the selected module - if st.button("Delete Module"): + if st.button("Delete Module", use_container_width=True): # Call the function to delete the module from the database azsqldb.delete_module(module_id, st.session_state.sqlcursor) st.session_state.delete_module_toggle = False # Hide the input fields after submission diff --git a/Scripts/__sidebar.py b/Scripts/__sidebar.py index 5839a00..5aa3304 100644 --- a/Scripts/__sidebar.py +++ b/Scripts/__sidebar.py @@ -21,13 +21,16 @@ def teacher_sidebar(): # Sidebar for class selection and new class creation with st.sidebar: + st.sidebar.title(f"Welcome, {st.session_state.user_info['username']}!") + st.write(""" Here's a quick guide to the buttons you'll find on this page: - **Class**: Navigate through classes. - **Modules**: Upload class materials, assignments, and other resources. 📚 - **FAQs**: View and answer students' questions. 🎓 - **Manage Assignments**: Use this to view and manage the class's tasks. 🗓️ - """) + + Clicking on a button toggles the corresponding function.""") ## Class management st.sidebar.title("Class") @@ -42,17 +45,17 @@ def teacher_sidebar(): cm.show_class() - col1, col2 = st.columns([1,1]) + col1, col2 = st.columns([1.4,1]) with col1: # Button to create a new class - if st.button("Create a new class"): + if st.button("New Class", use_container_width=True): st.session_state.show_new_class_input = not st.session_state.show_new_class_input st.session_state.show_upload_file = False with col2: # Button to upload class level files - if st.button("Upload File", key='class_upload'): + if st.button("Upload File", key='class_upload', use_container_width=True): st.session_state.show_upload_file = not st.session_state.show_upload_file ## Upload class files st.session_state.show_new_class_input = False @@ -82,13 +85,13 @@ def teacher_sidebar(): col3, col4, col5 = st.columns([1,1,1]) with col3: - if st.button("Create a new module"): + if st.button("New module"): st.session_state.new_module_toggle = not st.session_state.new_module_toggle st.session_state.delete_module_toggle = False st.session_state.show_upload_file2 = False with col4: - if st.button("Delete a module"): + if st.button("Delete module"): st.session_state.delete_module_toggle = not st.session_state.delete_module_toggle st.session_state.new_module_toggle = False st.session_state.show_upload_file2 = False @@ -142,6 +145,8 @@ def teacher_sidebar(): def student_sidebar(): # Sidebar for class selection and new class joining with st.sidebar: + st.sidebar.title(f"Welcome, {st.session_state.user_info['username']}!") + st.write(""" Here's a quick guide to the buttons you'll find on this page: - **Manage Classes**: Navigate through courses. diff --git a/app.py b/app.py index 1d276e2..be61ae2 100644 --- a/app.py +++ b/app.py @@ -35,6 +35,7 @@ st.image(logo_path, width= custom_width) st.subheader("An Intelligent Education App", ) + # Display the login container # This block defining what the app does when the user_id value is equal to None if not st.session_state.user_info['user_id']: From 37c5c03cd17fbfd7ae5903876334540cb0ab71ab Mon Sep 17 00:00:00 2001 From: ammar Date: Tue, 5 Dec 2023 15:46:10 -0500 Subject: [PATCH 25/27] - student side bar cleaned up - log out button resets the app, cleaning up files in openai too --- Scripts/__sidebar.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/Scripts/__sidebar.py b/Scripts/__sidebar.py index 5aa3304..f0da5c5 100644 --- a/Scripts/__sidebar.py +++ b/Scripts/__sidebar.py @@ -141,6 +141,11 @@ def teacher_sidebar(): sc.teacher_schedule() + if st.sidebar.button("Reset Chat", use_container_width=True, ): + st.session_state.user_info['user_id'] = None + st.session_state.cleanup = False + st.experimental_rerun() + def student_sidebar(): # Sidebar for class selection and new class joining @@ -153,7 +158,8 @@ def student_sidebar(): - **Modules**: Upload your notes to a specified class module. 📚 - **FAQ**: View FAQs or create a new one. 🎓 - **Upcoming Assignments**: Use this to view and manage the class's assignments. 🗓️ - """) + + Clicking on a button toggles the corresponding function.""") st.sidebar.title("Manage Classes") manage_classes_description = """ **The Manage Class feature allows the student to:** @@ -163,8 +169,10 @@ def student_sidebar(): st.write(manage_classes_description) cm.show_class() + col111, col222 = st.columns([1.4,1]) + # Button to join a new class - if st.button("Join a new class"): + if col111.button("Join a new class", use_container_width=True): st.session_state.show_join_class_input = not st.session_state.show_join_class_input if st.session_state.show_join_class_input: @@ -183,7 +191,7 @@ def student_sidebar(): st.write(modules_description) md.show_module() - if st.button("Upload File", key='module_upload'): + if st.button("Upload File", key='module_upload', use_container_width=True): st.session_state.show_upload_file2 = not st.session_state.show_upload_file2 if st.session_state.show_upload_file2: @@ -209,3 +217,8 @@ def student_sidebar(): st.write(upcoming_assignments_description) sc.student_schedule() + + if st.sidebar.button("Reset Chat", use_container_width=True, ): + st.session_state.user_info['user_id'] = None + st.session_state.cleanup = False + st.experimental_rerun() From 0f50ac6252727cb75cf425f5729003381bb56ff4 Mon Sep 17 00:00:00 2001 From: ammar Date: Tue, 5 Dec 2023 19:26:10 -0500 Subject: [PATCH 26/27] - decreased wait time sot imporve performance - initial prompt now includes faqs - sidebar modified for faq changes - new function to return usernames and faqs --- Scripts/__chatscreen.py | 6 +++- Scripts/__faqs.py | 77 ++++++++++++++++++++++++++--------------- Scripts/__sidebar.py | 6 +++- Scripts/azsqldb.py | 41 ++++++++++++++++++++++ app.py | 4 +-- 5 files changed, 103 insertions(+), 31 deletions(-) diff --git a/Scripts/__chatscreen.py b/Scripts/__chatscreen.py index 277e0a6..4efcb1c 100644 --- a/Scripts/__chatscreen.py +++ b/Scripts/__chatscreen.py @@ -119,6 +119,8 @@ def initialize_chat(): st.session_state.selected_modules, st.session_state.sqlcursor) + faq_df = azsqldb.get_questions_usernames(st.session_state.class_info['class_id'], st.session_state.sqlcursor) + initial_prompt = f""" INITIAL PROMPT You're chatting with {st.session_state.user_info['username']}\n @@ -132,7 +134,9 @@ def initialize_chat(): initial_prompt += f" -Module: {module}\n\n" initial_prompt += f" -Learning outcomes: {outcome}\n\n" - initial_prompt += f"Here is info on the files you recieved:\n\n{st.session_state.blobs_to_retrieve}" + initial_prompt += f"Here is info on the files you recieved:\n\n{st.session_state.blobs_to_retrieve} \n\n" + + initial_prompt += f"Here are the FAQs for this class:\n\n{faq_df}" return initial_prompt diff --git a/Scripts/__faqs.py b/Scripts/__faqs.py index 45533cd..67ba4b0 100644 --- a/Scripts/__faqs.py +++ b/Scripts/__faqs.py @@ -15,16 +15,17 @@ import streamlit as st +import time from Scripts import azsqldb, sessionvars def teacher_faqs(): - if st.button("FAQs"): + if st.button("FAQs", use_container_width=True): st.session_state.show_faqs = not st.session_state.show_faqs # Toggle the show_faqs state if st.session_state.show_faqs: # Fetch FAQs as a DataFrame faq_df = azsqldb.get_questions(st.session_state.class_info['class_id'], st.session_state.sqlcursor) - + st.info("Edit this table to answer, edit, add or delete questions.") if not faq_df.empty: st.info("Edit this table to answer, edit, add or delete questions.") # Display the DataFrame with an editable interface @@ -37,7 +38,8 @@ def teacher_faqs(): 'faq_id': None, 'class_id': None, 'user_id': None}, - hide_index=True) + hide_index=True, + use_container_width=True) if st.button('Publish'): with st.spinner('Updating FAQs...'): @@ -49,29 +51,50 @@ def teacher_faqs(): st.write("No FAQs available for this class.") def student_faqs(): - if st.button("FAQs"): - st.session_state.show_faqs = not st.session_state.show_faqs # Toggle the show_faqs state - - if st.session_state.show_faqs: - faq_df = azsqldb.get_questions(st.session_state.class_info['class_id'], st.session_state.sqlcursor) + faq_df = azsqldb.get_questions(st.session_state.class_info['class_id'], st.session_state.sqlcursor) + st.dataframe(faq_df[['question', 'answer']], + use_container_width=True, + hide_index=True, + column_config={ + 'question': 'Question', + 'answer': 'Answer' + }) + question = st.text_input("Ask a question", key="FAQ question for students") + if st.button("Ask Question"): + - if not faq_df.empty: - # Display the DataFrame with an editable interface - st.info("Add new rows to the table to ask a new question.") - edited_data = st.data_editor(key="FAQ Editor", - data=faq_df[['question', 'answer', 'faq_id', 'class_id', 'user_id']], - num_rows='dynamic', - disabled=['faq_id', 'class_id', 'user_id', 'answer'], - column_config={'question': 'Question', - 'answer': 'Answer', - 'faq_id': None, - 'class_id': None, - 'user_id': None}, - hide_index=True) - if st.button('Ask question'): - with st.spinner('Updating FAQs...'): - # Check for changes and update - azsqldb.update_faqs(faq_df, edited_data, st.session_state.sqlcursor) - st.success("FAQs updated successfully!") + if question == "" or question == None: + st.warning("Please enter a question.") else: - st.write("No FAQs available for this class.") + azsqldb.ask_question(st.session_state.user_info['user_id'], + st.session_state.class_info['class_id'], + question, + st.session_state.sqlcursor) + st.success("Question submitted successfully!") + time.sleep(3) + st.session_state.show_faqs = False + st.rerun() + +# if st.session_state.show_faqs: +# faq_df = azsqldb.get_questions(st.session_state.class_info['class_id'], st.session_state.sqlcursor) +# +# if not faq_df.empty: +# # Display the DataFrame with an editable interface +# st.info("Add new rows to the table to ask a new question.") +# edited_data = st.data_editor(key="FAQ Editor", +# data=faq_df[['question', 'answer', 'faq_id', 'class_id', 'user_id']], +# num_rows='dynamic', +# disabled=['faq_id', 'class_id', 'user_id', 'answer'], +# column_config={'question': 'Question', +# 'answer': 'Answer', +# 'faq_id': None, +# 'class_id': None, +# 'user_id': None}, +# hide_index=True) +# if st.button('Ask question'): +# with st.spinner('Updating FAQs...'): +# # Check for changes and update +# azsqldb.update_faqs(faq_df, edited_data, st.session_state.sqlcursor) +# st.success("FAQs updated successfully!") +# else: +# st.write("No FAQs available for this class.") diff --git a/Scripts/__sidebar.py b/Scripts/__sidebar.py index f0da5c5..799b549 100644 --- a/Scripts/__sidebar.py +++ b/Scripts/__sidebar.py @@ -204,7 +204,11 @@ def student_sidebar(): - Directly pose questions to the teacher. """ st.write(faqs_description) - fq.student_faqs() + if st.button("FAQs", use_container_width=True): + st.session_state.show_faqs = not st.session_state.show_faqs + + if st.session_state.show_faqs: + fq.student_faqs() #schedule diff --git a/Scripts/azsqldb.py b/Scripts/azsqldb.py index 98ce73e..cf65e8d 100644 --- a/Scripts/azsqldb.py +++ b/Scripts/azsqldb.py @@ -206,6 +206,36 @@ def get_questions(class_id, sqlcursor): df = pd.DataFrame(data) return df +def get_questions_usernames(class_id, sqlcursor): + """ + Get all the questions and answers for a particular class + and return them as a Pandas DataFrame. + Instead of the user_id, return the username. + """ + # Execute a SQL query to get all the questions for the provided class_id + sqlcursor.execute("""SELECT b.username, a.question, a.answer + FROM master.STUDYBUDDY.FAQs a + LEFT JOIN master.STUDYBUDDY.Users b ON + a.user_id = b.user_id + WHERE class_id = ?""", (class_id,)) + + # Fetch all the records returned by the query + question_records = sqlcursor.fetchall() + + # Create a list of dictionaries for each record + data = [] + for record in question_records: + data.append({ + 'username': record[0], + 'question': record[1], + 'answer': record[2] + }) + + # Create and return a DataFrame from the list of dictionaries + df = pd.DataFrame(data) + return df + + def update_faqs(original_df, edited_df, sqlcursor): # Separate new questions (with None in faq_id) new_questions = edited_df[edited_df['faq_id'].isnull()] @@ -279,6 +309,17 @@ def update_faqs(original_df, edited_df, sqlcursor): # Commit the changes after deletion sqlcursor.connection.commit() +def ask_question(user_id, class_id, question, sqlcursor): + """ + Ask a question in a particular class. + """ + # Execute a SQL query to insert the new question + sqlcursor.execute("INSERT INTO master.STUDYBUDDY.FAQs (class_id, user_id, question) VALUES (?, ?, ?)", (class_id, user_id, question)) + # Commit the transaction + + sqlcursor.connection.commit() + + def update_class(sqlcursor, class_id, field, new_value): """ Update the class table with the new value for the provided field diff --git a/app.py b/app.py index be61ae2..a0ab5f6 100644 --- a/app.py +++ b/app.py @@ -163,7 +163,7 @@ with st.chat_message('assistant'): st.write("Thinking ......") if st.session_state.retry_error < 3: - time.sleep(5) # Short delay to prevent immediate rerun, adjust as needed + time.sleep(2) # Short delay to prevent immediate rerun, adjust as needed st.rerun() # Handle the 'failed' status @@ -189,5 +189,5 @@ run_id=st.session_state.run.id, ) if st.session_state.retry_error < 3: - time.sleep(3) + time.sleep(2) st.rerun() \ No newline at end of file From aaec53ad0f9d1c01a359157b3bf963ae22fa1f54 Mon Sep 17 00:00:00 2001 From: ammar Date: Tue, 5 Dec 2023 20:04:36 -0500 Subject: [PATCH 27/27] - made note that assignments is a future feature --- Scripts/__sidebar.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Scripts/__sidebar.py b/Scripts/__sidebar.py index 799b549..b8a0523 100644 --- a/Scripts/__sidebar.py +++ b/Scripts/__sidebar.py @@ -132,6 +132,7 @@ def teacher_sidebar(): manage_description = """ **The Managing Assignments function allows teachers to:** + - This is a future feature we plan to release in the next version of Study Buddy. - Generate tasks with due dates corresponding to the selected class. - Make these tasks visible on the student interface. - Enable students to mark them as completed.