diff --git a/app.py b/app.py index aa23bcdd8..b53447a9c 100644 --- a/app.py +++ b/app.py @@ -15,6 +15,8 @@ page_label = nv() if page_label == "Homepage": pg.home() +elif page_label == "BuffBot": + pg.buffbot() elif page_label == "Outstanding Members": pg.outstanding_members() @@ -33,14 +35,16 @@ elif page_label == "Join Us": # block of PythonX lessons elif page_label == "About PythonX": pg.pythonx_homepage() -elif page_label == "Lesson1": - pg.pythonx_lesson1() -elif page_label == "Lesson2": - pg.pythonx_lesson2() -elif page_label == "Lesson3": - pg.pythonx_lesson3() -elif page_label == "Lesson4": - pg.pythonx_lesson4() +elif page_label == "Introduction": + pg.pythonx_introduction() +elif page_label == "WordCloud": + pg.pythonx_wordcloud() +elif page_label == "Finance": + pg.pythonx_finance() +elif page_label == "GeoMap": + pg.pythonx_geomap() +elif page_label == "BuffBot": + pg.pythonx_buffbot() # block of CIS Tech Challenge Event elif page_label == "CIS Tech Challenge": diff --git a/app_config.json b/app_config.json new file mode 100644 index 000000000..045835810 --- /dev/null +++ b/app_config.json @@ -0,0 +1,16 @@ +{ + "deepseek":{ + "api_url": "https://api.deepseek.com", + "api_key": "sk-12165b127043441697a8940918e207ac" + }, + + "ollama":{ + "api_url": "http://localhost:11434/v1", + "api_key": "ollama" + }, + + "send_email": { + "sender_email": "noreply@buffteks.org", + "password": "cidm4360fall2024@*" + } +} \ No newline at end of file diff --git a/chatbot_config.json b/chatbot_config.json deleted file mode 100644 index ddaef19c7..000000000 --- a/chatbot_config.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "api_url": "https://api.deepseek.com", - "api_key": "sk-12165b127043441697a8940918e207ac", - "ollama_api_url": "http://localhost:11434/v1", - "ollama_api_key": "ollama" -} \ No newline at end of file diff --git a/deepseek_chatbot.py b/deepseek_chatbot.py index 7cdf87e45..0113cf52a 100644 --- a/deepseek_chatbot.py +++ b/deepseek_chatbot.py @@ -12,7 +12,7 @@ with st.expander("See Source Code"): st.code(f.read(), language="python") # Load API credentials from config.json -with open('chatbot_config.json') as config_file: +with open('app_config.json') as config_file: config = json.load(config_file) openai_api_base_url = config["api_url"] openai_api_key = config["api_key"] diff --git a/llama_chatbot.py b/llama_chatbot.py index b51b43523..989925550 100644 --- a/llama_chatbot.py +++ b/llama_chatbot.py @@ -15,14 +15,14 @@ st.markdown("

BuffBot🦬

", # st.subheader() st.info("Powered by llama3.2:1b model via [Ollama](https://ollama.com/library/llama3.2:1b)!") with st.expander("See Source Code"): - with open(__file__, "r") as f: - st.code(f.read(), language="python") + with open(__file__, "r") as f: + st.code(f.read(), language="python") # Load API credentials from config.json -with open('chatbot_config.json') as config_file: +with open('app_config.json') as config_file: config = json.load(config_file) - api_base_url = config["ollama_api_url"] - api_key = config["ollama_api_key"] + api_base_url = config["ollama"]["api_url"] + api_key = config["ollama"]["api_key"] client = OpenAI(api_key=api_key, base_url=api_base_url) diff --git a/new_members.json b/new_members.json new file mode 100644 index 000000000..ab4200988 --- /dev/null +++ b/new_members.json @@ -0,0 +1,3 @@ +[ + +] \ No newline at end of file diff --git a/webpages/SendEmail.py b/webpages/SendEmail.py index 035cc6948..67dfd9836 100644 --- a/webpages/SendEmail.py +++ b/webpages/SendEmail.py @@ -106,7 +106,9 @@ def send_email(sender_email, password, to_email, subject): server.starttls() # Enable TLS encryption for secure connection server.login(sender_email, password) # Log in with the sender's email and password server.send_message(message) # Send the email message + print("Email sent successfully!") + except Exception as e: # Catch and display any exceptions that occur during the sending process print(f"Failed to send email: {str(e)}") diff --git a/webpages/__init__.py b/webpages/__init__.py index 4dc3667e9..30ecd37ad 100644 --- a/webpages/__init__.py +++ b/webpages/__init__.py @@ -6,10 +6,11 @@ from .join_us import join_us from .testing import testing from .reference import reference from .pythonx_lessons_pages.pythonx_homepage import pythonx_homepage -from .pythonx_lessons_pages.pythonx_lesson1 import pythonx_lesson1 -from .pythonx_lessons_pages.pythonx_lesson2 import pythonx_lesson2 -from .pythonx_lessons_pages.pythonx_lesson3 import pythonx_lesson3 -from .pythonx_lessons_pages.pythonx_lesson4 import pythonx_lesson4 +from .pythonx_lessons_pages.pythonx_introduction import pythonx_introduction +from .buff_bot import buffbot +from .pythonx_lessons_pages.pythonx_finance import pythonx_finance +from .pythonx_lessons_pages.pythonx_geomap import pythonx_geomap +from .pythonx_lessons_pages.pythonx_wordcloud import pythonx_wordcloud from .outstanding_members import outstanding_members from .cis_tech_challenge_pages.cis_tech_challenge_homepage import cis_tech_challenge_homepage from .coreteks_pages.coreteks_homepage import coreteks_homepage diff --git a/webpages/__pycache__/SendEmail.cpython-312.pyc b/webpages/__pycache__/SendEmail.cpython-312.pyc index 85d777c1d..9d086e0bf 100644 Binary files a/webpages/__pycache__/SendEmail.cpython-312.pyc and b/webpages/__pycache__/SendEmail.cpython-312.pyc differ diff --git a/webpages/__pycache__/__init__.cpython-312.pyc b/webpages/__pycache__/__init__.cpython-312.pyc index 348dc8202..993fc751f 100644 Binary files a/webpages/__pycache__/__init__.cpython-312.pyc and b/webpages/__pycache__/__init__.cpython-312.pyc differ diff --git a/webpages/__pycache__/buff_bot.cpython-312.pyc b/webpages/__pycache__/buff_bot.cpython-312.pyc new file mode 100644 index 000000000..056d479c4 Binary files /dev/null and b/webpages/__pycache__/buff_bot.cpython-312.pyc differ diff --git a/webpages/__pycache__/home.cpython-312.pyc b/webpages/__pycache__/home.cpython-312.pyc index 72181dcae..8591948ab 100644 Binary files a/webpages/__pycache__/home.cpython-312.pyc and b/webpages/__pycache__/home.cpython-312.pyc differ diff --git a/webpages/__pycache__/join_us.cpython-312.pyc b/webpages/__pycache__/join_us.cpython-312.pyc index 6f80101f4..2078f6648 100644 Binary files a/webpages/__pycache__/join_us.cpython-312.pyc and b/webpages/__pycache__/join_us.cpython-312.pyc differ diff --git a/webpages/__pycache__/navigation.cpython-312.pyc b/webpages/__pycache__/navigation.cpython-312.pyc index b38aef41c..dc8e3cec9 100644 Binary files a/webpages/__pycache__/navigation.cpython-312.pyc and b/webpages/__pycache__/navigation.cpython-312.pyc differ diff --git a/webpages/__pycache__/outstanding_members.cpython-312.pyc b/webpages/__pycache__/outstanding_members.cpython-312.pyc index a61b9a970..600e97888 100644 Binary files a/webpages/__pycache__/outstanding_members.cpython-312.pyc and b/webpages/__pycache__/outstanding_members.cpython-312.pyc differ diff --git a/webpages/buff_bot.py b/webpages/buff_bot.py new file mode 100644 index 000000000..2ac01ee80 --- /dev/null +++ b/webpages/buff_bot.py @@ -0,0 +1,119 @@ +import streamlit as st +from openai import OpenAI # OpenAI compatibility +import json +# reference: +# - Use OpenAI to connect Ollama: https://ollama.com/blog/openai-compatibility +# - Build Chatbot with streamlit: https://streamlit.io/generative-ai +# - Ollama docker: https://hub.docker.com/r/ollama/ollama +# - [TBD] Finetune: https://docs.loopin.network/tutorials/LLM/llama3-finetune + + +# Clear chat history +def clear_chat(): + st.session_state.messages = [] + st.toast("Chat Cleaned", icon="🧹") + +def buffbot(): + # Set up the Streamlit app + st.markdown("

BuffBot🦬

", unsafe_allow_html=True) + st.markdown("
Your friendly AI chatbot powered by LLM! 🤖
", unsafe_allow_html=True) + # Display info and source code + with st.expander("See Source Code"): + with open(__file__, "r", encoding="utf-8") as f: + st.code(f.read(), language="python") + + # Select AI model for chatbot + model_options = ["llama3.2:1b", "deepseek-chat", ] + # on_change callback to clear chat history when model is changed + selected_model = st.selectbox("**👉 Please select a model to start**", model_options, on_change=clear_chat) + + # Initialize session state to store chat history and message count + if "messages" not in st.session_state: + st.session_state.messages = [] + # Initialize message count + if "message_count" not in st.session_state: + st.session_state.message_count = 0 + + # Load API credentials from config.json + # the config file contains the API key and base URL for the selected model + """ + { + "deepseek":{ + "api_url": "https://api.deepseek.com", + "api_key": "YOUR_API_KEY" + }, + + "ollama":{ + "api_url": "http://localhost:11434/v1", + "api_key": "ollama" + } + } + """ + # The API key and base URL are loaded based on the selected model + with open('app_config.json') as config_file: + config = json.load(config_file) + # deepseek-chat model, online API + if selected_model == "deepseek-chat": + api_base_url = config["deepseek"]["api_url"] + api_key = config["deepseek"]["api_key"] + st.info("Powered by the online [DeepSeek](https://www.deepseek.com/) API!\ + Just a heads up, you have 10 messages to use.") + # Set the maximum number of user messages + MAX_USER_MESSAGES = 10 + + # llama3.2:1b model, local API + if selected_model == "llama3.2:1b": + api_base_url = config["ollama"]["api_url"] + api_key = config["ollama"]["api_key"] + st.info("Powered by local llama3.2:1b model via [Ollama](https://ollama.com/library/llama3.2:1b)!\ + Just a heads up, you have 100 messages to use.") + MAX_USER_MESSAGES = 100 + + # Initialize OpenAI client to connect with the selected model API + client = OpenAI(api_key=api_key, base_url=api_base_url) + + # print welcome message + with st.chat_message("assistant", avatar="🦬"): + st.markdown("Welcome to BuffBot! What Can I Do for You Today?🌞") + + # Display chat history with different avatars for user and AI assistant + for message in st.session_state.messages: + if message["role"] == "user": + avatar="🤠" + else: + avatar="🦬" + with st.chat_message(message["role"], avatar=avatar): + st.markdown(message["content"]) + + # Get user input + if prompt := st.chat_input("Type your message here..."): + # Add user message to chat history + st.session_state.messages.append({"role": "user", "content": prompt}) + # Display user message with cowboy avatar + with st.chat_message("user", avatar="🤠"): + st.markdown(prompt) + + # Generate reply + with st.chat_message("assistant", avatar="🦬"): + with st.spinner('Thinking...'): + # Call the selected model API to generate a response + stream = client.chat.completions.create( + model=selected_model, + messages=[ + {"role": m["role"], "content": m["content"]} + for m in st.session_state.messages + ], + stream=True, # stream the response + ) + # Display the response from the model API + response = st.write_stream(stream) + # Add the AI assistant response to the chat history + st.session_state.messages.append({"role": "assistant", "content": response}) + + # Clear chat history + if st.button("Clear Chat"): + clear_chat() + st.rerun() + + + diff --git a/webpages/coreteks_pages/__pycache__/coreteks_homepage.cpython-312.pyc b/webpages/coreteks_pages/__pycache__/coreteks_homepage.cpython-312.pyc index 4f67dc5c7..f3b25c25b 100644 Binary files a/webpages/coreteks_pages/__pycache__/coreteks_homepage.cpython-312.pyc and b/webpages/coreteks_pages/__pycache__/coreteks_homepage.cpython-312.pyc differ diff --git a/webpages/coreteks_pages/coreteks_homepage.py b/webpages/coreteks_pages/coreteks_homepage.py index 03d980e8d..d24545bc7 100644 --- a/webpages/coreteks_pages/coreteks_homepage.py +++ b/webpages/coreteks_pages/coreteks_homepage.py @@ -27,7 +27,7 @@ def coreteks_homepage(): # for linux talk st.subheader(" :two: Linux Security and Web Server Setup") - st.image("./images/11-19-2024-CaddyAndLinux.jpg", width= 500) + st.image("./images/11-19-2024-CaddyAndLinux.jpg", width= 700) st.link_button(label="Watch Video Recording", use_container_width=True, type="primary", url="https://wtamu0-my.sharepoint.com/:v:/g/personal/czhang_wtamu_edu/EdgZHrnkqihNrOZGYAT6O2MBSRBOBMv3czD_uIE21KgsWw?nav=eyJyZWZlcnJhbEluZm8iOnsicmVmZXJyYWxBcHAiOiJPbmVEcml2ZUZvckJ1c2luZXNzIiwicmVmZXJyYWxBcHBQbGF0Zm9ybSI6IldlYiIsInJlZmVycmFsTW9kZSI6InZpZXciLCJyZWZlcnJhbFZpZXciOiJNeUZpbGVzTGlua0NvcHkifX0&e=obIoyY", ) st.link_button(label="Download PDF Instruction", use_container_width=True, type="primary", url="https://wtamu0-my.sharepoint.com/:b:/g/personal/czhang_wtamu_edu/Eaygqoc_FqxNgYj3BlEOXhsBzOj-BWTbNKwkpJEYSDBwgA?e=K7qxSU", ) st.divider() diff --git a/webpages/home.py b/webpages/home.py index 02d9e33da..422bb9530 100644 --- a/webpages/home.py +++ b/webpages/home.py @@ -1,4 +1,136 @@ import streamlit as st +from .join_us import join_us def home(): - st.html("./webpages/buffteks.html") \ No newline at end of file + + # Custom CSS to style the page + st.markdown(""" + + """, unsafe_allow_html=True) + + # Header + st.markdown(""" +
+

BuffTeks Student Organization

+

Building Skills, Crafting Code, Bridging Communities

+
+ """, unsafe_allow_html=True) + + + # Our Mission + st.markdown(""" +
+

Our Mission

+

Empower members with advanced software development knowledge, foster new skills and technologies in a collaborative and engaging community.

+
+ """, unsafe_allow_html=True) + + # Our Activities + st.markdown(""" +
+

Our Activities

+ +
+ """, unsafe_allow_html=True) + + # Faculty Advisors + st.markdown(""" +
+

Faculty Advisors

+
+
+
+ Dr. Jeffry Babb +
+ Dr. Jeffry Babb
+ BuffTeks Founder
+
+
+
+ Dr. Carl Zhang +
+ Dr. Carl Zhang
+ czhang@wtamu.edu +
+
+
+ Mr. Kareem Dana +
+ Mr. Kareem Dana
+ kdana@wtamu.edu +
+
+
+
+
+ """, unsafe_allow_html=True) diff --git a/webpages/join_us.py b/webpages/join_us.py index 76deec215..3399ae917 100644 --- a/webpages/join_us.py +++ b/webpages/join_us.py @@ -1,50 +1,103 @@ import streamlit as st from .SendEmail import send_email import time +import json +import re +# reset all inputs +def clear_text(): + st.session_state["full_name"] = "" + st.session_state["email"] = "" + st.session_state["stu_major"] = None + st.session_state["other_major"] = "" + st.session_state["stu_year"] = None + st.session_state["skills_selection"] = [] + st.session_state["other_skill"] = "" +def is_valid_email(email): + pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$' + return re.match(pattern, email) + +@st.dialog("Review Your Information") +def review(review_info): + st.write(review_info) + confirm_btn = st.button("Confirm and Send Email", on_click=clear_text) + if confirm_btn: + # save new member info to json file + try: + with open('new_members.json', 'r+') as file: + data = json.load(file) + data.append(review_info) + file.seek(0) + json.dump(data, file, indent=4) + except FileNotFoundError: + with open('new_members.json', 'w') as file: + json.dump([refiew_info], file, indent=4) + + # load email config from app_config.json + with open('app_config.json') as config_file: + config = json.load(config_file) + email_config = config["send_email"] + + # send email to new member + with st.spinner('📧 Sending Email...'): + send_email( + sender_email=email_config["sender_email"], + password=email_config["password"], + to_email=review_info["Email"], + subject=f"Welcome to join BuffTeks, {review_info["Full Name"]}!" + ) + time.sleep(3) + st.success("🎉Thanks for submitting your information, you will receive an invitation email shortly. \ + \nPlease contact Dr.Zhang (czhang@wtamu.edu) if you need any help.") + + progress_text = "⏱️ Window will be closed in 10 seconds..." + my_bar = st.progress(0, text=progress_text) + for percent_complete in range(99): + time.sleep(0.1) + my_bar.progress(percent_complete + 1, text=progress_text) + time.sleep(1) + st.rerun() + + def join_us(): st.title("Join Us") st.write("Thank you for your interest in the BuffTeks student organization! Please share your information below, \ and our Faculty Advisor will send you an invitation to join our Discord channel. \ If you have any questions, feel free to contact our Faculty Advisor, Dr. Carl Zhang, at czhang@wtamu.edu.") - full_name = st.text_input("**Full Name**") - wt_email = st.text_input("**WT Email**") + full_name = st.text_input("**Full Name**", key="full_name") + email = st.text_input("**Email**", key="email", placeholder="WT email is preferred") major_list = ["Accounting", "Computer Infromation Systems", "Economics", "Finance", "General Business", "Management", "Marketing", "Other"] - stu_major = st.radio("**Major/Field of Study**",major_list) + stu_major = st.radio("**Major/Field of Study**",major_list, key="stu_major") if stu_major == "Other": - other_major = st.text_input("Please specify the other major") + other_major = st.text_input("Please specify the other major", key="other_major") stu_major = other_major year_of_study_list = ["Freshman", "Sophomore", "Junior", "Senior", "Graduate"] - stu_year = st.radio("**Year of Study**", year_of_study_list) + stu_year = st.radio("**Year of Study**", year_of_study_list, key="stu_year") skills_options = ["Programming", "Cybersecurity", "Web Development", "Mobile App Development", "machine Learning/AI", "Data Analysis", "Other"] skills_selection = st.multiselect( label= "**Technical Skills You Are Interested In (Please check all that apply)**", options = skills_options, - placeholder = "Choose all that apply") + placeholder = "Choose all that apply", + key="skills_selection") if "Other" in skills_selection: - other_skill = st.text_input("**Please specify the other skills**") + other_skill = st.text_input("**Please specify the other skills**", key="other_skill") skills_selection.remove("Other") - skills_selection.append(f"Other ({other_skill})") + skills_selection.append(f"{other_skill}") next_btn = st.button("Next") - if next_btn: - if "wtamu.edu" not in wt_email: - st.warning("Please input your WT Email address to receive invitation") - with st.spinner('Sending Email...'): - send_email(sender_email="noreply@buffteks.org", password="cidm4360fall2024@*", to_email=wt_email, subject=f"Welcome to join ButtTeks, {full_name}!") - st.balloons() - st.success("Thanks for submitting your information, you will receive an invitation email shortly. \n Please contact Dr.Zhang (czhang@wtamu.edu) if you need any help.") - -# @st.dialog("Check Email") -# def confirm_email(wt_email, full_name): -# st.write(f"Please double-check your **WT Email** in order to receive our invitation: \n {wt_email}") -# if st.button("Confirm & Submit"): -# send_email(sender_email="noreply@buffteks.org", password="cidm4360fall2024@*", to_email=wt_email, subject=f"Welcome to join ButtTeks, {full_name}!") -# st.rerun() -# return True - \ No newline at end of file + if not is_valid_email(email): + st.error("Please enter a valid email address.") + return + st.session_state.review_info = { + "Full Name": full_name, + "Email": email, + "Major/Field of Study": stu_major, + "Year of Study": stu_year, + "Technical Skills": skills_selection + } + review(review_info=st.session_state.review_info) diff --git a/webpages/navigation.py b/webpages/navigation.py index ed6086d71..9242b6ede 100644 --- a/webpages/navigation.py +++ b/webpages/navigation.py @@ -13,23 +13,25 @@ def navigation_bar(): page_label = sac.menu([ sac.MenuItem('Homepage', icon='house'), + sac.MenuItem('BuffBot', icon='robot'), sac.MenuItem('Outstanding Members', icon='award'), + sac.MenuItem("Join Us", icon='person-add'), sac.MenuItem('BuffTeks Project', icon='bi bi-laptop'), - sac.MenuItem('BuffTeks Event', icon='calendar-event', children=[ - sac.MenuItem('CIS Tech Challenge', icon='bi bi-trophy'), - ]), sac.MenuItem('BuffTeks Classroom', icon='book', children=[ sac.MenuItem('About Classroom', icon='question-circle'), sac.MenuItem('PythonX', icon='bi bi-filetype-py', children=[ sac.MenuItem('About PythonX', icon='question-circle'), - sac.MenuItem('Lesson1', icon='1-square'), - sac.MenuItem('Lesson2', icon='2-square'), - sac.MenuItem('Lesson3', icon='3-square'), - sac.MenuItem('Lesson4', icon='4-square'), + sac.MenuItem('Introduction', icon='1-square'), + sac.MenuItem('WordCloud', icon='2-square'), + sac.MenuItem('Finance', icon='3-square'), + sac.MenuItem('GeoMap', icon='4-square'), + # sac.MenuItem('BuffBot', icon='5-square'), ]), sac.MenuItem('CoreTeks', icon='bi bi-tools'), ]), - sac.MenuItem("Join Us", icon='person-add'), + sac.MenuItem('BuffTeks Event', icon='calendar-event', children=[ + sac.MenuItem('CIS Tech Challenge', icon='bi bi-trophy'), + ]), # sac.MenuItem("Testing", icon='fingerprint'), # sac.MenuItem(type='divider'), # sac.MenuItem('Link', type='group', children=[ diff --git a/webpages/pythonx_lessons_pages/__pycache__/pythonx_buffbot.cpython-312.pyc b/webpages/pythonx_lessons_pages/__pycache__/pythonx_buffbot.cpython-312.pyc new file mode 100644 index 000000000..ee1f2c4b4 Binary files /dev/null and b/webpages/pythonx_lessons_pages/__pycache__/pythonx_buffbot.cpython-312.pyc differ diff --git a/webpages/pythonx_lessons_pages/__pycache__/pythonx_finance.cpython-312.pyc b/webpages/pythonx_lessons_pages/__pycache__/pythonx_finance.cpython-312.pyc new file mode 100644 index 000000000..224f64aab Binary files /dev/null and b/webpages/pythonx_lessons_pages/__pycache__/pythonx_finance.cpython-312.pyc differ diff --git a/webpages/pythonx_lessons_pages/__pycache__/pythonx_geomap.cpython-312.pyc b/webpages/pythonx_lessons_pages/__pycache__/pythonx_geomap.cpython-312.pyc new file mode 100644 index 000000000..30426ed8e Binary files /dev/null and b/webpages/pythonx_lessons_pages/__pycache__/pythonx_geomap.cpython-312.pyc differ diff --git a/webpages/pythonx_lessons_pages/__pycache__/pythonx_introduction.cpython-312.pyc b/webpages/pythonx_lessons_pages/__pycache__/pythonx_introduction.cpython-312.pyc new file mode 100644 index 000000000..79cf90f16 Binary files /dev/null and b/webpages/pythonx_lessons_pages/__pycache__/pythonx_introduction.cpython-312.pyc differ diff --git a/webpages/pythonx_lessons_pages/__pycache__/pythonx_wordcloud.cpython-312.pyc b/webpages/pythonx_lessons_pages/__pycache__/pythonx_wordcloud.cpython-312.pyc new file mode 100644 index 000000000..64d900663 Binary files /dev/null and b/webpages/pythonx_lessons_pages/__pycache__/pythonx_wordcloud.cpython-312.pyc differ diff --git a/webpages/pythonx_lessons_pages/pythonx_lesson3.py b/webpages/pythonx_lessons_pages/pythonx_finance.py similarity index 99% rename from webpages/pythonx_lessons_pages/pythonx_lesson3.py rename to webpages/pythonx_lessons_pages/pythonx_finance.py index bdbaff617..213087ce3 100644 --- a/webpages/pythonx_lessons_pages/pythonx_lesson3.py +++ b/webpages/pythonx_lessons_pages/pythonx_finance.py @@ -4,7 +4,7 @@ import yfinance as yf import plotly.express as px from webpages import code_editor as ce -def pythonx_lesson3(): +def pythonx_finance(): st.title("Lesson 3: Collecting Stock Data Through API") st.header(":one: What is API") diff --git a/webpages/pythonx_lessons_pages/pythonx_lesson4.py b/webpages/pythonx_lessons_pages/pythonx_geomap.py similarity index 63% rename from webpages/pythonx_lessons_pages/pythonx_lesson4.py rename to webpages/pythonx_lessons_pages/pythonx_geomap.py index 48240dd02..181d800e0 100644 --- a/webpages/pythonx_lessons_pages/pythonx_lesson4.py +++ b/webpages/pythonx_lessons_pages/pythonx_geomap.py @@ -1,17 +1,17 @@ - import streamlit as st -import pandas as pd -import folium -from streamlit_folium import folium_static -from geopy.geocoders import Nominatim -import sqlite3 -from datetime import datetime -from folium.features import CustomIcon +import pandas as pd # package for database connection +import folium # package for creating maps +from streamlit_folium import folium_static # package for displaying maps on streamlit +from geopy.geocoders import Nominatim # package for geolocation conversion +import sqlite3 # package for database connection +from datetime import datetime # package for timestamp +from folium.features import CustomIcon # package for custom icons on map -def pythonx_lesson4(): +def pythonx_geomap(): # Initialize the database conn = sqlite3.connect('./files/student_locations.db') + # Create a table to store student locations c = conn.cursor() c.execute('''CREATE TABLE IF NOT EXISTS students (id INTEGER PRIMARY KEY AUTOINCREMENT, @@ -24,16 +24,40 @@ def pythonx_lesson4(): conn.commit() - st.title('Where are Members From') - + st.title("Lesson 4: Geographical Data Visualization") + st.markdown(""" + This lesson demonstrates how to handle geolocation data and visualize it using maps in Python. + + The main functionalities include: + + - **User Input for Location**: Users can input their city and state or city and country through a form. + + - **Geolocation Processing**: The input location is processed to obtain latitude and longitude coordinates. + + - **Data Storage**: The processed location data is saved to a [SQLite](https://www.geeksforgeeks.org/python-sqlite/) database. + + - **Map Visualization**: All stored student locations are displayed on an interactive map. + + - **Statistics Display**: The code also provides statistics on the total number of students, unique cities, and unique countries, along with the top 5 cities. + """) + + # Display the source code + with st.expander("See Source Code"): + with open(__file__, "r", encoding="utf-8") as f: + st.code(f.read(), language="python") + + st.subheader("Student Location Tracker") # Input form for student location with st.form("student_form"): input_city = st.text_input("Enter your City, State (e.g.: Amarillo,TX), or City, Country (Toronto, Canada):") submitted = st.form_submit_button("Submit") - if submitted: + if submitted: + # converting addresses (like "Mountain View, CA") into geographic + # coordinates (like latitude 37.423021 and longitude -122.083739) lat, lon, city, state, country = get_location(input_city) + # Save the location data to the database if lat and lon: save_student(conn, city, state, country, lat, lon) st.success(f"Location saved: {city}, {country}") @@ -66,7 +90,7 @@ def pythonx_lesson4(): conn.close() - +# convert address to latitude and longitude def get_location(city): geolocator = Nominatim(user_agent="student_location_app") try: @@ -82,7 +106,7 @@ def get_location(city): st.write(e) return None, None, None, None, None - +# save student location to database def save_student(conn, city, state, country, lat, lon): c = conn.cursor() timestamp = datetime.now() @@ -90,11 +114,14 @@ def save_student(conn, city, state, country, lat, lon): (city, state, country, lat, lon, timestamp)) conn.commit() +# get all student locations from database def get_all_students(conn): df = pd.read_sql_query("SELECT * from students", conn) return df +# create map with student locations def create_map(df): + # Create a map centered at the US m = folium.Map(location=[41.2706, -97.1749], zoom_start=4) # Group by city and count occurrences @@ -104,6 +131,7 @@ def create_map(df): max_count = city_counts['count'].max() min_size, max_size = 5, 20 # min and max marker sizes + # Add markers for each city for _, row in city_counts.iterrows(): # Create a custom icon icon = CustomIcon( @@ -112,7 +140,7 @@ def create_map(df): icon_anchor=(15, 30), # Adjust anchor point if needed popup_anchor=(0, -30) # Adjust popup anchor if needed ) - + # Calculate marker size based on count folium.Marker( location=[row['latitude'], row['longitude']], popup=f"{row['city']}
{row['count']}", diff --git a/webpages/pythonx_lessons_pages/pythonx_lesson1.py b/webpages/pythonx_lessons_pages/pythonx_introduction.py similarity index 99% rename from webpages/pythonx_lessons_pages/pythonx_lesson1.py rename to webpages/pythonx_lessons_pages/pythonx_introduction.py index 485ac3e6f..969f9ea1a 100644 --- a/webpages/pythonx_lessons_pages/pythonx_lesson1.py +++ b/webpages/pythonx_lessons_pages/pythonx_introduction.py @@ -3,7 +3,7 @@ from webpages import code_editor as ce -def pythonx_lesson1(): +def pythonx_introduction(): st.title("Lesson 1: Introduction to Python") # Lesson1-Part1: what is programming st.header(":one: From Idea to Program") diff --git a/webpages/pythonx_lessons_pages/pythonx_lesson2.py b/webpages/pythonx_lessons_pages/pythonx_wordcloud.py similarity index 99% rename from webpages/pythonx_lessons_pages/pythonx_lesson2.py rename to webpages/pythonx_lessons_pages/pythonx_wordcloud.py index c6aece0ee..5439e3b86 100644 --- a/webpages/pythonx_lessons_pages/pythonx_lesson2.py +++ b/webpages/pythonx_lessons_pages/pythonx_wordcloud.py @@ -4,7 +4,7 @@ from wordcloud import WordCloud import matplotlib.pyplot as plt -def pythonx_lesson2(): +def pythonx_wordcloud(): st.title("Lesson 2: Create WordClouds in Python") st.header(":one: What is WordClouds") st.markdown("""