diff --git a/Homepage.py b/Homepage.py index 425c855..8b38fbf 100644 --- a/Homepage.py +++ b/Homepage.py @@ -1,76 +1,19 @@ import streamlit as st from streamlit_option_menu import option_menu from app_utils import switch_page -from initialization import initialize_session_state, embedding, resume_reader -from prompts.prompts import templates -from typing import Literal -from dataclasses import dataclass import streamlit as st -from speech_recognition.openai_whisper import save_wav_file, transcribe -from langchain.callbacks import get_openai_callback -from aws.synthesize_speech import synthesize_speech -from IPython.display import Audio -from dataclasses import dataclass -import base64 -from typing import Literal -from audio_recorder_streamlit import audio_recorder -from initialization import initialize_session_state -@dataclass -class Message: - """Class for keeping track of interview history.""" - origin: Literal["human", "ai"] - message: str -def autoplay_audio(file_path: str): - def update_audio(): - global global_audio_md - with open(file_path, "rb") as f: - data = f.read() - b64 = base64.b64encode(data).decode() - global_audio_md = f""" - - """ - def update_markdown(audio_md): - st.markdown(audio_md, unsafe_allow_html=True) - update_audio() - update_markdown(global_audio_md) - -def answer_call_back(): - with get_openai_callback() as cb: - # user input - human_answer = st.session_state.answer - # transcribe audio - save_wav_file("temp/audio.wav", human_answer) - try: - input = transcribe("temp/audio.wav") - # save human_answer to history - st.session_state.history.append( - Message("human", input) - ) - # OpenAI answer and save to history - llm_answer = st.session_state.screen.run(input) - # speech synthesis and speak out - audio_file_path = synthesize_speech(llm_answer) - # create audio widget with autoplay - audio_widget = Audio(audio_file_path, autoplay=True) - # save audio data to history - st.session_state.history.append( - Message("ai", llm_answer) - ) - st.session_state.token_count += cb.total_tokens - return audio_widget - except: - st.session_state.history.append(Message("ai", "Sorry, I didn't get that. Please try again.")) +from PIL import Image # ———————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— -st.set_page_config(page_title = "🤖 AI Interviewer", layout = "centered") -home_title = "🤖 AI Interviewer" +im = Image.open("icon.png") +st.set_page_config(page_title = "AI Interviewer", layout = "centered",page_icon=im) + +home_title = "AI Interviewer" home_introduction = "Welcome to AI Interviewer, empowering your interview preparation with generative AI." with st.sidebar: - st.markdown('🤖 AI Interviewer - V0.1.2') + st.markdown('AI Interviewer - V0.1.2') st.markdown(""" #### Let's contact: [Haoxiang Jia](https://www.linkedin.com/in/haoxiang-jia/) @@ -94,8 +37,9 @@ def answer_call_back(): "", unsafe_allow_html=True ) - +st.image(im, width=100) st.markdown(f"""# {home_title} Beta""",unsafe_allow_html=True) + st.markdown("""\n""") #st.markdown("#### Greetings") st.markdown("Welcome to AI Interviewer! 👏AI Interviewer is a generative AI powered tool that provides you with realistic interview experience. " @@ -116,7 +60,8 @@ def answer_call_back(): st.info(""" 📚In this session, the AI Interviewer will assess your technical skills as they relate to the job description. - Press the microphone to start answering. - - Each Interview will take 10 to 15 mins. + - Each Interview will take 10 to 15 mins. + - To start a new session, just refresh the page. - Start introduce yourself and enjoy! """) if st.button("Start Interview!"): switch_page("Professional Screen") @@ -126,7 +71,8 @@ def answer_call_back(): st.info(""" 📚In this session, the AI Interviewer will review your resume and discuss your past experiences. - Press the microphone to start answering. - - Each Interview will take 10 to 15 mins. + - Each Interview will take 10 to 15 mins. + - To start a new session, just refresh the page. - Start introduce yourself and enjoy! """ ) if st.button("Start Interview!"): @@ -137,7 +83,8 @@ def answer_call_back(): st.info(""" 📚In this session, the AI Interviewer will assess your soft skills as they relate to the job description. - Press the microphone to start answering. - - Each Interview will take 10 to 15 mins. + - Each Interview will take 10 to 15 mins. + - To start a new session, just refresh the page. - Start introduce yourself and enjoy! """) if st.button("Start Interview!"): diff --git a/icon.png b/icon.png new file mode 100644 index 0000000..a3f7ead Binary files /dev/null and b/icon.png differ diff --git a/images/icon.png b/images/icon.png deleted file mode 100644 index 36d4e01..0000000 Binary files a/images/icon.png and /dev/null differ diff --git a/pages/Behavioral Screen.py b/pages/Behavioral Screen.py index 0f6479a..7f35e84 100644 --- a/pages/Behavioral Screen.py +++ b/pages/Behavioral Screen.py @@ -65,7 +65,6 @@ def initialize_session_state(): st.session_state.bq_docserch = save_vector(jd) if "bq_retriever" not in st.session_state: st.session_state.bq_retriever = st.session_state.bq_docserch.as_retriever(search_type="similarity") - if "bq_chain_type_kwargs" not in st.session_state: Behavioral_Prompt = PromptTemplate(input_variables=["context", "question"], template=templates.behavioral_template) @@ -73,6 +72,8 @@ def initialize_session_state(): # interview history if "history" not in st.session_state: st.session_state.history = [] + st.session_state.history.append(Message("ai", "Hello there! I am your interviewer today. I will access your soft skills through a series of questions. Let's get started! Please start by saying hello or introducing yourself.")) + # token count if "token_count" not in st.session_state: st.session_state.token_count = 0 @@ -95,7 +96,7 @@ def initialize_session_state(): PROMPT = PromptTemplate( input_variables=["history", "input"], template="""I want you to act as an interviewer strictly following the guideline in the current conversation. - + Candidate has no idea what the guideline is. Ask me questions and wait for my answers. Do not write explanations. Ask question like a real person, only one question at a time. Do not ask the same question. @@ -128,18 +129,40 @@ def answer_call_back(): # user input human_answer = st.session_state.answer # transcribe audio - save_wav_file("temp/audio.wav", human_answer) - try: - input = transcribe("temp/audio.wav") - # save human_answer to history + if voice: + save_wav_file("temp/audio.wav", human_answer) + try: + input = transcribe("temp/audio.wav") + # save human_answer to history + st.session_state.history.append( + Message("human", input) + ) + # OpenAI answer and save to history + llm_answer = st.session_state.conversation.run(input) + # speech synthesis and speak out + audio_file_path = synthesize_speech(llm_answer) + # create audio widget with autoplay + audio_widget = Audio(audio_file_path, autoplay=True) + # save audio data to history + st.session_state.history.append( + Message("ai", llm_answer) + ) + st.session_state.token_count += cb.total_tokens + return audio_widget + except: + st.session_state.history.append(Message("ai", "Sorry, I didn't get that. Please try again.")) + else: + input = human_answer st.session_state.history.append( Message("human", input) ) # OpenAI answer and save to history llm_answer = st.session_state.conversation.run(input) + # OpenAI answer and save to history + llm_answer = st.session_state.conversation.run(input) # speech synthesis and speak out audio_file_path = synthesize_speech(llm_answer) - # 创建自动播放的音频部件 + # create audio widget with autoplay audio_widget = Audio(audio_file_path, autoplay=True) # save audio data to history st.session_state.history.append( @@ -147,9 +170,6 @@ def answer_call_back(): ) st.session_state.token_count += cb.total_tokens return audio_widget - except: - st.session_state.history.append(Message("ai", "Sorry, I didn't get that. Please try again.")) - ### ———————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— if jd: @@ -168,23 +188,27 @@ def answer_call_back(): # keep interview else: with answer_placeholder: - answer = audio_recorder(pause_threshold=2.5, sample_rate=44100) + voice: bool = st.checkbox("I would like to speak with AI Interviewer!") + if voice: + answer = audio_recorder(pause_threshold=2.5, sample_rate=44100) + else: + answer = st.chat_input("Your answer") if answer: st.session_state['answer'] = answer audio = answer_call_back() - else: - st.write("Please speak into the microphone to answer the question.") with chat_placeholder: + auto_play = st.checkbox("Let AI interviewer speak!") + if auto_play: + try: + st.write(audio) + except: + pass for answer in st.session_state.history: if answer: if answer.origin == 'ai': with st.chat_message("assistant"): st.write(answer.message) - try: - st.write(audio) - except: - pass else: with st.chat_message("user"): st.write(answer.message) diff --git a/pages/Professional Screen.py b/pages/Professional Screen.py index 9550699..359fff7 100644 --- a/pages/Professional Screen.py +++ b/pages/Professional Screen.py @@ -32,16 +32,12 @@ def load_lottiefile(filepath: str): ### —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— @dataclass class Message: - """class for keeping track of interview history.""" - origin: Literal["human", "ai"] message: str def save_vector(text): - """embeddings""" - text_splitter = NLTKTextSplitter() texts = text_splitter.split_text(text) # Create emebeddings @@ -64,13 +60,16 @@ def initialize_session_state(): # interview history if "jd_history" not in st.session_state: st.session_state.jd_history = [] + st.session_state.jd_history.append(Message("ai", + "Hello, Welcome to the interview. I am your interviewer today. I will ask you professional questions regarding the job description you submitted." + "Please start by introducting a little bit about yourself.")) # token count if "token_count" not in st.session_state: st.session_state.token_count = 0 if "jd_guideline" not in st.session_state: llm = ChatOpenAI( model_name = "gpt-3.5-turbo", - temperature = 0.6,) + temperature = 0.8,) st.session_state.jd_guideline = RetrievalQA.from_chain_type( llm=llm, chain_type_kwargs=st.session_state.jd_chain_type_kwargs, chain_type='stuff', @@ -82,11 +81,11 @@ def initialize_session_state(): temperature=0.8, ) PROMPT = PromptTemplate( input_variables=["history", "input"], - template="""I want you to act as an interviewer strictly following the guideline in the current conversation. + template="""I want you to act as a human interviewer strictly following the guideline in the current conversation. - Ask me questions and wait for my answers like a real person. + Ask me questions and wait for my answers. Do not write explanations. - Ask question like a real person, only one question at a time. + only one question at a time. Do not ask the same question. Do not repeat the question. Do ask follow-up questions if necessary. @@ -106,7 +105,7 @@ def initialize_session_state(): if 'jd_feedback' not in st.session_state: llm = ChatOpenAI( model_name="gpt-3.5-turbo", - temperature=0.5, ) + temperature=0.8, ) st.session_state.jd_feedback = ConversationChain( prompt=PromptTemplate(input_variables=["history", "input"], template=templates.feedback_template), llm=llm, @@ -118,11 +117,32 @@ def answer_call_back(): with get_openai_callback() as cb: # user input human_answer = st.session_state.answer - # transcribe audio - save_wav_file("temp/audio.wav", human_answer) - try: - input = transcribe("temp/audio.wav") - # save human_answer to history + if voice: + # transcribe audio + save_wav_file("temp/audio.wav", human_answer) + try: + input = transcribe("temp/audio.wav") + # save human_answer to history + st.session_state.jd_history.append( + Message("human", input) + ) + # OpenAI answer and save to history + llm_answer = st.session_state.jd_screen.run(input) + # speech synthesis + audio_file_path = synthesize_speech(llm_answer) + st.session_state.audio_file_path = audio_file_path + # 创建自动播放的音频部件 + audio_widget = Audio(audio_file_path, autoplay=True) + # save audio data to history + st.session_state.jd_history.append( + Message("ai", llm_answer) + ) + st.session_state.token_count += cb.total_tokens + return audio_widget + except: + st.session_state.jd_history.append(Message("ai", "Sorry, I didn't get that. Please try again.")) + else: + input = human_answer st.session_state.jd_history.append( Message("human", input) ) @@ -139,8 +159,6 @@ def answer_call_back(): ) st.session_state.token_count += cb.total_tokens return audio_widget - except: - st.session_state.jd_history.append(Message("ai", "Sorry, I didn't get that. Please try again.")) ### —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— # sumitted job description @@ -158,25 +176,31 @@ def answer_call_back(): st.stop() else: with answer_placeholder: - answer = audio_recorder(pause_threshold = 2.5, sample_rate = 44100) + voice: bool = st.checkbox("I would like to speak with AI Interviewer") + if voice: + answer = audio_recorder(pause_threshold = 2.5, sample_rate = 44100) + else: + answer = st.chat_input("Your answer") if answer: st.session_state['answer'] = answer audio = answer_call_back() - else: - st.write("Please speak into the microphone to answer the question.") + with chat_placeholder: + auto_play = st.checkbox("Let AI interviewer speak!") + if auto_play: + try: + st.write(audio) + except: + pass for answer in st.session_state.jd_history: #if answer: if answer.origin == 'ai': with st.chat_message("assistant"): st.write(answer.message) - try: - st.write(audio) - except: - pass else: with st.chat_message("user"): st.write(answer.message) + credit_card_placeholder.caption(f""" Used {st.session_state.token_count} tokens \n Progress: {int(len(st.session_state.jd_history) / 30 * 100)}% completed.""") diff --git a/pages/Resume Screen.py b/pages/Resume Screen.py index 28be86c..52e4da9 100644 --- a/pages/Resume Screen.py +++ b/pages/Resume Screen.py @@ -1,5 +1,4 @@ # langchain: https://python.langchain.com/ -import time from dataclasses import dataclass import streamlit as st from speech_recognition.openai_whisper import save_wav_file, transcribe @@ -15,7 +14,6 @@ from langchain.embeddings import OpenAIEmbeddings from langchain.vectorstores import FAISS from langchain.text_splitter import NLTKTextSplitter -import nltk from PyPDF2 import PdfReader from prompts.prompt_selector import prompt_sector from streamlit_lottie import st_lottie @@ -23,19 +21,13 @@ from IPython.display import Audio ### —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— - -try: - nltk.data.find('tokenizers/punkt') -except LookupError: - nltk.download('punkt') - def load_lottiefile(filepath: str): with open(filepath, "r") as f: return json.load(f) st_lottie(load_lottiefile("images/welcome.json"), speed=1, reverse=False, loop=True, quality="high", height=300) -position = st.selectbox("#### Select the position you are applying for", ["Data Analyst", "Software Engineer", "Marketing"]) -resume = st.file_uploader("#### Upload your resume", type=["pdf"]) +position = st.selectbox("Select the position you are applying for", ["Data Analyst", "Software Engineer", "Marketing"]) +resume = st.file_uploader("Upload your resume", type=["pdf"]) ### —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— @dataclass @@ -45,25 +37,20 @@ class Message: message: str def save_vector(resume): - pdf_reader = PdfReader(resume) - text = "" for page in pdf_reader.pages: text += page.extract_text() - # Split the document into chunks text_splitter = NLTKTextSplitter() texts = text_splitter.split_text(text) text_splitter = NLTKTextSplitter() texts = text_splitter.split_text(text) - embeddings = OpenAIEmbeddings() docsearch = FAISS.from_texts(texts, embeddings) return docsearch def initialize_session_state(): - # convert resume to embeddings if 'docsearch' not in st.session_state: st.session_state.docserch = save_vector(resume) @@ -76,6 +63,7 @@ def initialize_session_state(): # interview history if "resume_history" not in st.session_state: st.session_state.resume_history = [] + st.session_state.resume_history.append(Message(origin="ai", message="Hello, I am your interivewer today. I will ask you some questions regarding your resume and your experience. Please start by saying hello or introducing yourself.")) # token count if "token_count" not in st.session_state: st.session_state.token_count = 0 @@ -122,11 +110,9 @@ def initialize_session_state(): st.session_state.resume_screen = ConversationChain(prompt=PROMPT, llm = llm, memory = st.session_state.resume_memory) # llm chain for generating feedback if "resume_feedback" not in st.session_state: - llm = ChatOpenAI( model_name="gpt-3.5-turbo", temperature=0.5,) - st.session_state.resume_feedback = ConversationChain( prompt=PromptTemplate(input_variables=["history","input"], template=templates.feedback_template), llm=llm, @@ -134,37 +120,54 @@ def initialize_session_state(): ) def answer_call_back(): - with get_openai_callback() as cb: # user input human_answer = st.session_state.answer - # transcribe audio - save_wav_file("temp/audio.wav", human_answer) - try: - input = transcribe("temp/audio.wav") - # save human input to history + if voice: + # transcribe audio + save_wav_file("temp/audio.wav", human_answer) + try: + input = transcribe("temp/audio.wav") + # save human input to history + st.session_state.resume_history.append( + Message("human", input) + ) + # GPT Interviewer output and save to history + llm_answer = st.session_state.resume_screen.run(input) + # speech synthesis and speak out + audio_file_path = synthesize_speech(llm_answer) + + st.session_state.audio_file_path = audio_file_path + # create audio widget with autoplay + audio_widget = Audio(audio_file_path, autoplay=True) + + # save audio data to history + st.session_state.resume_history.append( + Message("ai", llm_answer) + ) + st.session_state.token_count += cb.total_tokens + + return audio_widget + except: + st.session_state.resume_history.append(Message("ai", "Sorry, I didn't get that. Please try again.")) + else: + input = human_answer st.session_state.resume_history.append( Message("human", input) ) - # GPT Interviewer output and save to history llm_answer = st.session_state.resume_screen.run(input) # speech synthesis and speak out audio_file_path = synthesize_speech(llm_answer) - st.session_state.audio_file_path = audio_file_path - # 创建自动播放的音频部件 + # create audio widget with auto play audio_widget = Audio(audio_file_path, autoplay=True) - # save audio data to history st.session_state.resume_history.append( Message("ai", llm_answer) ) st.session_state.token_count += cb.total_tokens - return audio_widget - except: - st.session_state.resume_history.append(Message("ai", "Sorry, I didn't get that. Please try again.")) ### —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— @@ -184,26 +187,31 @@ def answer_call_back(): st.stop() else: with answer_placeholder: - answer = audio_recorder(pause_threshold=2, sample_rate=44100) + voice: bool = st.checkbox("I would like to speak with AI Interviewer!") + if voice: + answer = audio_recorder(pause_threshold=2, sample_rate=44100) + else: + answer = st.chat_input("Your answer") if answer: st.session_state['answer'] = answer audio_widget = answer_call_back() - else: - st.write("Please speak into the microphone to answer the question.") with chat_placeholder: + auto_play = st.checkbox("Let AI interviewer speak!") + if auto_play: + try: + st.write(audio_widget) + except: + pass for answer in st.session_state.resume_history: if answer: if answer.origin == 'ai': with st.chat_message("assistant"): st.write(answer.message) - try: - st.write(audio_widget) - except: - pass else: with st.chat_message("user"): st.write(answer.message) + credit_card_placeholder.caption(f""" Used {st.session_state.token_count} tokens \n Progress: {int(len(st.session_state.resume_history) / 30 * 100)}% completed.""") diff --git a/requirements.txt b/requirements.txt index c91c3b0..17defe0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,7 +2,7 @@ langchain PyPDF2 openai wave -streamlit +streamlit==1.24.0 tiktoken nltk #azure-cognitiveservices-speech diff --git a/temp/audio.wav b/temp/audio.wav index 7d1599f..c98be16 100644 Binary files a/temp/audio.wav and b/temp/audio.wav differ