# ============================================================================== # KU HMC Lab Chatbot # Author: Kyle VanSaun, kavansaun@ku.edu # Editors: # Last Updated: April 2026 # Description: A ChatBot that can # mimic human behavior # and saves participant # responses to Github. # Uses: Hugging Face as the Web host # Gradio for the User Interface # Python for the code # OpenAI for the ChatBot # Github to save data # ============================================================================== # INSTRUCTIONS for Chat Bot on line 92, Insert Repository name on line 39, Model type on line 150 # Imports import gradio as gr from openai import OpenAI import os from github import Github import uuid import time # Environment variables openai_key = os.getenv("OPENAI_KEY") github_key = os.getenv("GITHUB_KEY") # Check if openai key was given if not openai_key: raise gr.Error("OPENAI_KEY not set") client = OpenAI(api_key=openai_key) # Check if Github key was given g = Github(github_key) if github_key else None # Repo name, if you want to save to a github repository change RepositoryName to your repo name, name must be under quotations REPO_NAME = "RepositoryName" # Session data dictionary for each user user_dictionary = {} # Function to create or update the GitHub chat log def update_github_log(uid): if g: # Try to get repo try: repo = g.get_user().get_repo(REPO_NAME) filename = f"{uid}.txt" content = "" for msg in user_dictionary[uid]: role = msg["role"].capitalize() content += f"{role}: {msg['content']}\n" # Try to update the repo, otherwise create the repo try: file = repo.get_contents(filename) repo.update_file(file.path, "Update chat log", content, file.sha) except Exception: repo.create_file(filename, "Create session log", content) # If we didnt get the repo find and show the error except Exception as e: error_msg = str(e).lower() if "bad_credentials" in error_msg or "bad credentials" in error_msg: gr.Warning("The GitHub API Key provided is invalid.") elif "repository" in error_msg or "404" in error_msg: gr.Warning("Repo not found, Possible incorrect Repository name") else: gr.Warning(f"GitHub Error: {str(e)}") else: pass # Gradio user interface, status and progress made invisible with gr.Blocks(theme=gr.themes.Monochrome(), css="""footer {display:none !important;}[class*="status"] {display:none !important;}[class*="progress"] {display:none !important;}""") as chatblock: # Hidden session variables user_id = gr.Textbox(visible=False) notifier = gr.HTML(visible=False) # If you embed this in html, this notifier is so you can catch and use the user_id in your script or url. # Load user session @chatblock.load(outputs=[user_id, notifier]) def load_user(): global user_dictionary # Creating user ID new_id = str(uuid.uuid4()) print(f"Session started. User ID: {new_id}") # Print User ID user_dictionary[new_id] = [] # INSTRUCTIONS, instructions must be in quotations, \n means a new line to the AI (not needed). instructions = ( "You are a Chat Bot for the KU HMC Lab\n" "instruction...\n" "instruction...\n" ) # Add ID and instructions to users session data user_dictionary[new_id].append({ "role": "system", "content": instructions }) # Create a GitHub repository file to store the Data update_github_log(new_id) return new_id, f"" # User message function so we can show the messages separately def add_user_message(user_input, user_id): if not user_id: return [], "" if user_id not in user_dictionary: user_dictionary[user_id] = [] # Add messages to users session data user_dictionary[user_id].append({ "role": "user", "content": user_input }) # Format conversation formatted_chat = [ msg for msg in user_dictionary[user_id] if msg["role"] != "system" ] return formatted_chat, "" # Optional function to simulate the time it takes a "person" to read the users message, (amount of time reading, bubbles dont show up). def pause_before_typing(user_id): # Calculate reading time based on the length of the user's message if user_id and user_id in user_dictionary and len(user_dictionary[user_id]) > 0: user_msg = user_dictionary[user_id][-1]["content"] # Fast reading speed, caps between 1.0 and 3.0 seconds read_delay = max(1.0, min(len(user_msg) / 30, 3.0)) time.sleep(read_delay) else: time.sleep(1) # Generate Chat Bot response def predict_prompt(user_id): if not user_id or user_id not in user_dictionary: return [] # Try to get the response from OpenAI try: response = client.chat.completions.create( model="gpt-4o", # Model Type, must be under quotations, for example model="gpt-4o-mini". messages=user_dictionary[user_id] ) reply = response.choices[0].message.content # Optional delay for a "human" response time based on the length of the chatbot's reply (amount of time typing message, bubbles show up) typing_delay = max(1.0, min(len(reply) / 40, 5.0)) # Caps typing delay between 1.0 and 5.0 seconds time.sleep(typing_delay) # If we didnt get the response find and show the Error except Exception as e: error_msg = str(e).lower() # Catch invalid or expired API keys and show a error message if "invalid_api_key" in error_msg or "incorrect api key" in error_msg: gr.Warning("The OpenAI API Key provided is invalid or expired.") reply = "System Error: The OpenAI API Key is invalid." elif "ascii" in error_msg: gr.Warning("OPENAI_KEY was set incorrectly.") reply = "System Error: The OpenAI API Key is improperly formatted." else: gr.Warning(f"OpenAI Error: {str(e)}") reply = f"System Error: {str(e)}" # Add chatbot reply to session data user_dictionary[user_id].append({ "role": "assistant", "content": reply }) # Format conversation formatted_chat = [ msg for msg in user_dictionary[user_id] if msg["role"] != "system" ] # Save to GitHub update_github_log(user_id) return formatted_chat # User Interface components Chatbot = gr.Chatbot( type="messages", label="Anonymous User" # Participant User Name ) # User textbox for them to type in txt = gr.Textbox( show_label=False, placeholder="Please write your message and press Enter", container=False ) # Submits txt.submit( add_user_message, inputs=[txt, user_id], outputs=[Chatbot, txt], queue=False ).then( pause_before_typing, inputs=[user_id], outputs=None ).then( predict_prompt, inputs=[user_id], outputs=Chatbot ) # Lanch! chatblock.launch()