|
import gradio as gr |
|
from openai import OpenAI |
|
import docx2txt |
|
import PyPDF2 |
|
import time |
|
import os |
|
API_KEY = os.environ.get("API_KEY") |
|
|
|
default_system_prompt = """ |
|
|
|
You are a friendly and supportive human-like assistant helping users explore their interests and potential career paths through a guided questionnaire. |
|
Ask the user each question one at a time, and allow them to respond either by choosing from given options or typing their own input. After receiving a reasonable response, continue to the next question until the survey is complete. |
|
At the end of the questionnaire, generate personalized career suggestions based on the user's responses. Your summary should: |
|
1. Briefly reflect the user's key interests, strengths, preferences and user's basic information. |
|
2. Brainstorm some unique ways of combinations of user's key interests, strengths, and preferences, and how these interdiscipline can be applied to their career trajectory. . |
|
3. Recommend 3 potential careers that align well with their profile. |
|
4. Suggest 5 university programs that match their interests and academic background and college characteristics preference. |
|
|
|
For each university/program: |
|
1. Include a brief description of the program, and college characteristics preference. |
|
2. Explain why it's a strong fit for the user. |
|
3. Sort the programs by acceptance rate (from low to high). |
|
4. Ensure at least one suggested school is realistic for guaranteed admission. |
|
|
|
Then ask follow up questions to encourage users to ask you more questions and more suggestions. Please encourage them to ask for a step-by-step road map for college application. Maybe also guide them to ask what other subjects that they should take in the future. |
|
When presenting the final recommendations, make your language friendly, encouraging, and logically consistent. Add a few well-placed emojis to keep the tone warm and engaging 😊🎓💡. |
|
|
|
🔧 SECTION 1: What Do You Enjoy Doing? |
|
(1/20) What are you mostly drawn to? |
|
A. Solving problems |
|
B. Doing experiments |
|
C. Analyzing data |
|
D. Creating or designing things |
|
|
|
(2/20) Do you like writing, drawing, designing, or performing? |
|
A. Very much |
|
B. Somewhat |
|
C. Not really |
|
D. Not at all |
|
|
|
(3/20) Do you enjoy helping people learn, grow, or feel better? |
|
A. Very much |
|
B. Somewhat |
|
C. Not really |
|
D. Not at all |
|
|
|
(4/20) Are you motivated by leading, persuading, or organizing people for a common goal? |
|
A. Very much |
|
B. Somewhat |
|
C. Not really |
|
D. Not at all |
|
|
|
(5/20) Do you enjoy working with data, numbers, or detailed processes? |
|
A. Very much |
|
B. Somewhat |
|
C. Not really |
|
D. Not at all |
|
|
|
|
|
💭 SECTION 2: What Kind of Work Feels Meaningful to You? |
|
(6/20) Think about a time you felt proud of your work — what were you doing? |
|
A. Finishing big projects |
|
B. Organizing an event |
|
C. Solving challenging problems |
|
D. Helping classmates |
|
|
|
(7/20) Do you prefer collaborating with others or working alone? |
|
A. Collaborating |
|
B. Working alone |
|
C. Balanced preference |
|
D. Depends on the task |
|
|
|
(8/20) Do you like structure and clear expectations, or do you thrive in flexibility and creativity? |
|
A. Strong structure |
|
B. Some structure |
|
C. Flexible, creative work |
|
D. Depends on context |
|
|
|
(9/20) What kind of praise or advice helps you do your best? |
|
A. Helpful tips and strategies |
|
B. Noticing my efforts |
|
C. Positive encouragement |
|
D. Quick and clear feedback |
|
|
|
(10/20) What kind of impact do you hope to make through your work? |
|
A. Empowering others through data |
|
B. Helping others learn and grow |
|
C. Solving problems that matter |
|
D. Creating or building something new |
|
|
|
🧬 SECTION 3: How Do You Make Decisions and Process Information? |
|
(11/20) What helps you decide? |
|
A. I think about the facts and what makes the most sense |
|
B. I think about how people feel and what’s fair |
|
C. I use both facts and empathy |
|
D. I trust my instincts and experience |
|
|
|
(12/20) How do you get energized? |
|
A. Talking with others and sharing ideas out loud |
|
B. Quiet reflection and solo thinking |
|
C. I enjoy both depending on the situation |
|
D. I prefer observing first, then contributing |
|
|
|
(13/20) Do you prefer a detailed plan or flexibility? |
|
A. I like having a clear, step-by-step plan |
|
B. I prefer to stay open and adapt as I go |
|
C. I like having a plan but can adapt if needed |
|
D. I thrive on spontaneity and last-minute ideas |
|
|
|
(14/20) How do you solve problems? |
|
A. I rely on past experience |
|
B. I imagine new solutions |
|
C. I do both—learn from the past and explore new ideas |
|
D. I ask others and collaborate to find the best answer |
|
|
|
🔎 SECTION 4: Your Strengths in Action |
|
(15/20) What compliments have you received? |
|
A. “You’re helpful” / “Great friend” |
|
B. “You explain things clearly” / “Good teacher” |
|
C. “You’re creative” / “You have great ideas” |
|
D. “You’re organized and reliable” |
|
|
|
(16/20) What tasks come naturally to you? |
|
A. Solving math problems |
|
B. Writing or storytelling |
|
C. Public speaking |
|
D. Organizing or planning tasks |
|
|
|
(17/20) What doesn’t feel like work to you? |
|
A. Creating (art, design, building) |
|
B. Solving puzzles or challenges |
|
C. Helping others |
|
D. Leading teams or projects |
|
|
|
(18/20) In a group project, what role fits you best? |
|
A. Leader |
|
B. Planner |
|
C. Researcher |
|
D. Presenter |
|
|
|
🧩 SECTION 5: Career Environment Preferences |
|
(19/20) What environments do you prefer? |
|
A. Fast-paced and competitive |
|
B. Calm and predictable |
|
C. Flexible and collaborative |
|
D. Structured and professional |
|
|
|
(20/20)Which motivates you more? |
|
A. Competing and achieving goals |
|
B. Supporting others and shared values |
|
C. Learning and growing |
|
D. Recognition and influence |
|
|
|
""" |
|
|
|
essay_system_prompt = """ |
|
You are a friendly and professional writing assistant. Your job is to guide the user step-by-step through writing a strong personal statement or optional essay. |
|
|
|
Intro: |
|
Hi there! Welcome to UniCue’s Writing Assistant. |
|
I’m Athena, your personal guide for crafting a standout university application essay. Let’s turn your ideas and experiences into a story that reflects you at your best. |
|
To get started, which type of essay are you working on? Personal Statement Or Optional Essays ? |
|
|
|
If Personal statements chosen: |
|
Great! Personal statements are your chance to show who you really are. |
|
From the info you shared, I see you're into swimming—awesome! That’s a powerful passion we can build on. |
|
Before we dive in, what tone do you want your essay to have? |
|
|
|
Warm and heartfelt |
|
Confident and ambitious |
|
Thoughtful and reflective |
|
Creative or playful |
|
|
|
If Optional essays chosen: |
|
Awesome! Optional essays are a great way to highlight different sides of your story. |
|
Which prompt are you focusing on? |
|
|
|
Why major/ Why school |
|
Overcoming Challenges |
|
Diversity or Community |
|
Creative or Open-Ended Essay |
|
""" |
|
|
|
essay_system_prompt = """ |
|
You are a friendly and professional writing assistant. Your job is to guide the user step-by-step through writing a strong personal statement or optional essay. |
|
|
|
Intro: |
|
Hi there! Welcome to UniCue’s Writing Assistant. |
|
I’m Athena, your personal guide for crafting a standout university application essay. Let’s turn your ideas and experiences into a story that reflects you at your best. |
|
To get started, which type of essay are you working on? Personal Statement Or Optional Essays ? |
|
|
|
If Personal statements chosen: |
|
Great! Personal statements are your chance to show who you really are. |
|
From the info you shared, I see you're into swimming—awesome! That’s a powerful passion we can build on. |
|
Before we dive in, what tone do you want your essay to have? |
|
|
|
Warm and heartfelt |
|
Confident and ambitious |
|
Thoughtful and reflective |
|
Creative or playful |
|
|
|
If Optional essays chosen: |
|
Awesome! Optional essays are a great way to highlight different sides of your story. |
|
Which prompt are you focusing on? |
|
|
|
Why major/ Why school |
|
Overcoming Challenges |
|
Diversity or Community |
|
Creative or Open-Ended Essay |
|
""" |
|
|
|
MODEL_NAME = "gpt-4o" |
|
DEFAULT_MAX_TOKENS = 2000 |
|
DEFAULT_TEMPERATURE = 0.7 |
|
DEFAULT_TOP_P = 0.95 |
|
|
|
def convert_to_chatbot_format(history): |
|
result = [] |
|
user_msg = None |
|
for msg in history: |
|
if msg["role"] == "user": |
|
user_msg = msg["content"] |
|
elif msg["role"] == "assistant": |
|
result.append([user_msg, msg["content"]]) |
|
user_msg = None |
|
return result |
|
|
|
def predict(message, history): |
|
client = OpenAI(api_key=API_KEY) |
|
messages = [{"role": "system", "content": default_system_prompt}] |
|
messages.extend(history if history else []) |
|
messages.append({"role": "user", "content": message}) |
|
|
|
start_time = time.time() |
|
response = client.chat.completions.create( |
|
model=MODEL_NAME, |
|
messages=messages, |
|
max_tokens=DEFAULT_MAX_TOKENS, |
|
temperature=DEFAULT_TEMPERATURE, |
|
top_p=DEFAULT_TOP_P, |
|
stream=True |
|
) |
|
|
|
full_message = "" |
|
first_chunk_time = None |
|
last_yield_time = None |
|
|
|
for chunk in response: |
|
if chunk.choices and chunk.choices[0].delta.content: |
|
if first_chunk_time is None: |
|
first_chunk_time = time.time() - start_time |
|
full_message += chunk.choices[0].delta.content |
|
current_time = time.time() |
|
if last_yield_time is None or (current_time - last_yield_time >= 0.25): |
|
chatbot_display = convert_to_chatbot_format(history + [ |
|
{"role": "user", "content": message}, |
|
{"role": "assistant", "content": full_message} |
|
]) |
|
yield chatbot_display, history |
|
last_yield_time = current_time |
|
|
|
if full_message: |
|
history.append({"role": "user", "content": message}) |
|
history.append({"role": "assistant", "content": full_message}) |
|
yield convert_to_chatbot_format(history), history |
|
|
|
def predict_mini(message, history): |
|
client = OpenAI(api_key=API_KEY) |
|
messages = [{"role": "system", "content": essay_system_prompt}] |
|
messages.extend(history if history else []) |
|
messages.append({"role": "user", "content": message}) |
|
|
|
start_time = time.time() |
|
response = client.chat.completions.create( |
|
model=MODEL_NAME, |
|
messages=messages, |
|
max_tokens=DEFAULT_MAX_TOKENS, |
|
temperature=DEFAULT_TEMPERATURE, |
|
top_p=DEFAULT_TOP_P, |
|
stream=True |
|
) |
|
|
|
full_message = "" |
|
first_chunk_time = None |
|
last_yield_time = None |
|
|
|
for chunk in response: |
|
if chunk.choices and chunk.choices[0].delta.content: |
|
if first_chunk_time is None: |
|
first_chunk_time = time.time() - start_time |
|
full_message += chunk.choices[0].delta.content |
|
current_time = time.time() |
|
if last_yield_time is None or (current_time - last_yield_time >= 0.25): |
|
chatbot_display = convert_to_chatbot_format(history + [ |
|
{"role": "user", "content": message}, |
|
{"role": "assistant", "content": full_message} |
|
]) |
|
yield chatbot_display, history |
|
last_yield_time = current_time |
|
|
|
if full_message: |
|
history.append({"role": "user", "content": message}) |
|
history.append({"role": "assistant", "content": full_message}) |
|
yield convert_to_chatbot_format(history), history |
|
|
|
def start_essay_intro(): |
|
intro_message = """Hi there! Welcome to UniCue’s Writing Assistant. |
|
I’m Athena, your personal guide for crafting a standout university application essay. Let’s turn your ideas and experiences into a story that reflects you at your best. |
|
To get started, which type of essay are you working on? Personal statement or optional essay?""" |
|
history = [{"role": "assistant", "content": intro_message}] |
|
return [[ "", intro_message ]], history |
|
|
|
|
|
def send_draft_to_chatbot(draft_text, history): |
|
prompt = f"Please review and provide feedback for the following personal statement:\n\n{draft_text}" |
|
yield from predict(prompt, history) |
|
|
|
|
|
def send_fixed_message(fixed_message): |
|
def inner(*args): |
|
history = args[0] |
|
yield from predict(fixed_message, history) |
|
|
|
return inner |
|
|
|
def stop_chat(): |
|
return [], [], "", "" |
|
|
|
def start_chat_fn(g, s, p, gen, sat, interests_list, dream_school): |
|
|
|
if interests_list: |
|
interest_str = f"You mentioned you're interested in subjects like {', '.join(interests_list)}." |
|
else: |
|
interest_str = "You haven't selected any favorite subjects yet." |
|
|
|
if sat: |
|
sat_str = f" You've also entered an SAT score of {sat}." |
|
else: |
|
sat_str = "" |
|
gender_str = f" and identify as {gen}" if gen != "Prefer not to say" else "" |
|
dream_str = f" Your dream school is {dream_school}." if dream_school else "" |
|
welcome = ( |
|
f"Welcome! I am the AI counselor Athena from UniCue! I know you're in grade {g}, from {s} in {p}{gender_str}. " |
|
f"""{interest_str}{sat_str}{dream_str} I will help you to find the perfect career path and interest for you! |
|
Let's begin with Section 1: What Do You Enjoy Doing? \n \n (1/20)What are you mostly drawn to? |
|
A. Solving problems |
|
B. Doing experiments |
|
C. Analyzing data |
|
D. Creating or designing things |
|
Please choose one of the options above. |
|
""" |
|
) |
|
return ( |
|
gr.update(visible=False), |
|
gr.update(visible=True), |
|
gr.update(visible=False), |
|
[["", welcome]], |
|
[{"role": "assistant", "content": welcome}] |
|
|
|
) |
|
|
|
def brainstorm_idea_message(history): |
|
user_prompt = "Can you help me brainstorm ideas for my personal statement?" |
|
history.append({"role": "user", "content": user_prompt}) |
|
yield from predict(user_prompt, history) |
|
|
|
def fix_structure_message(history): |
|
user_prompt = "Can you help me improve the structure of my personal statement?" |
|
history.append({"role": "user", "content": user_prompt}) |
|
yield from predict(user_prompt, history) |
|
|
|
def polish_writing_message(history): |
|
user_prompt = "Can you help polish and refine my draft?" |
|
history.append({"role": "user", "content": user_prompt}) |
|
yield from predict(user_prompt, history) |
|
|
|
def extract_text_from_file(file): |
|
if file is None: |
|
return gr.update() |
|
|
|
filepath = file.name |
|
text = "" |
|
|
|
if filepath.endswith(".pdf"): |
|
with open(filepath, "rb") as f: |
|
reader = PyPDF2.PdfReader(f) |
|
for page in reader.pages: |
|
page_text = page.extract_text() |
|
if page_text: |
|
text += page_text |
|
elif filepath.endswith(".docx"): |
|
text = docx2txt.process(filepath) |
|
|
|
return gr.update(value=text) |
|
|
|
def should_disable_add_button(school, program, rate): |
|
if not school.strip() or not program.strip() or not rate.strip(): |
|
return gr.update(interactive=False) |
|
return gr.update(interactive=True) |
|
|
|
with gr.Blocks(css=""" |
|
@import url('https://fonts.googleapis.com/css2?family=Fredoka:wght@700&display=swap'); |
|
|
|
body, .gradio-container { |
|
background-color: #fff8dc !important; |
|
} |
|
|
|
#title { |
|
text-align: center; |
|
margin-top: 40px; |
|
margin-bottom: 30px; |
|
font-size: 80px; |
|
font-family: 'Fredoka', sans-serif; |
|
font-weight: 700; |
|
} |
|
|
|
#title .gradient-text { |
|
background: linear-gradient(90deg, #ff6ec4, #7873f5, #4ac29a); |
|
background-clip: text; |
|
-webkit-background-clip: text; |
|
color: transparent; |
|
-webkit-text-fill-color: transparent; |
|
} |
|
|
|
button.small-colored { |
|
font-size: 14px !important; |
|
padding: 8px 12px !important; |
|
border-radius: 8px; |
|
font-weight: bold; |
|
background-color: #a8e6cf; |
|
color: #000000; |
|
border: none; |
|
margin-bottom: 10px; |
|
width: 100%; |
|
} |
|
|
|
footer { display: none !important; } |
|
|
|
#spinner { |
|
display: flex; |
|
flex-direction: column; |
|
align-items: center; |
|
justify-content: center; |
|
height: 300px; |
|
font-family: 'Fredoka', sans-serif; |
|
font-size: 20px; |
|
font-weight: bold; |
|
gap: 20px; |
|
} |
|
|
|
.spinner-circle { |
|
border: 6px solid #f3f3f3; |
|
border-top: 6px solid #ff6ec4; |
|
border-right: 6px solid #7873f5; |
|
border-bottom: 6px solid #4ac29a; |
|
border-radius: 50%; |
|
width: 60px; |
|
height: 60px; |
|
animation: spin 1s linear infinite; |
|
} |
|
|
|
.typing-loader { |
|
display: inline-block; |
|
} |
|
|
|
.typing-loader .loader-text { |
|
color: #b19cd9; |
|
font-size: 24px; |
|
} |
|
|
|
.typing-loader .dot { |
|
animation: blink 1.4s infinite both; |
|
font-size: 32px; |
|
font-weight: bold; |
|
color: #4ac29a; |
|
} |
|
|
|
.typing-loader .dot:nth-child(2) { |
|
animation-delay: 0.2s; |
|
} |
|
.typing-loader .dot:nth-child(3) { |
|
animation-delay: 0.4s; |
|
} |
|
|
|
@keyframes spin { |
|
0% { transform: rotate(0deg); } |
|
100% { transform: rotate(360deg); } |
|
} |
|
|
|
@keyframes blink { |
|
0% { opacity: 0.2; } |
|
20% { opacity: 1; } |
|
100% { opacity: 0.2; } |
|
} |
|
|
|
label { |
|
font-size: 18px !important; |
|
} |
|
input, select, textarea { |
|
font-size: 16px !important; |
|
} |
|
input[type="radio"] + span, select option { |
|
font-size: 14px !important; |
|
} |
|
|
|
div[data-testid="chatbot"] > div:first-child { |
|
font-size: 18px !important; |
|
font-weight: 600; |
|
color: #6a5acd; |
|
} |
|
|
|
div[data-testid="chatbot"] svg { |
|
display: none !important; |
|
} |
|
|
|
#subject_checkboxes input[type="checkbox"] + span { |
|
font-size: 14px !important; |
|
} |
|
|
|
#sidebar { |
|
background-color: #fff8f5; |
|
border-right: 2px solid #ffa500; |
|
padding-top: 20px; |
|
align-items: center; |
|
gap: 10px; |
|
height: 100%; |
|
} |
|
|
|
#interest-tab, |
|
#program-tab, |
|
#statement-tab { |
|
display: flex; |
|
flex-direction: column; |
|
align-items: center; |
|
background: none; |
|
justify-content: center; |
|
gap: 4px; /* 图标与文字间距 */ |
|
line-height: 1.2; |
|
} |
|
|
|
#interest-tab.selected, |
|
#program-tab.selected, |
|
#statement-tab.selected { |
|
color: #ff6600 !important; |
|
background-color: #fff2ec !important; |
|
border-left: 4px solid #ff6600 !important; |
|
} |
|
|
|
#school-card { |
|
background-color: #fdfdfd; |
|
border-radius: 12px; |
|
padding: 16px; |
|
box-shadow: 0px 1px 4px rgba(0,0,0,0.1); |
|
margin: 8px 0; |
|
} |
|
|
|
.card-grid { |
|
display: flex; |
|
flex-wrap: wrap; |
|
gap: 16px; |
|
margin-top: 20px; |
|
justify-content: flex-start; |
|
} |
|
|
|
.school-card { |
|
flex: 1 1 calc(50% - 16px); |
|
max-width: calc(50% - 16px); |
|
box-sizing: border-box; |
|
background-color: #ffffff; |
|
border-radius: 16px; |
|
box-shadow: 0 2px 6px rgba(0,0,0,0.1); |
|
padding: 16px; |
|
font-family: 'Fredoka', sans-serif; |
|
display: flex; |
|
flex-direction: column; |
|
justify-content: center; |
|
gap: 8px; |
|
} |
|
|
|
.school-card strong { |
|
font-size: 18px; |
|
font-weight: 700; |
|
color: #333333; |
|
} |
|
|
|
.school-card span { |
|
font-size: 16px; |
|
color: #444; |
|
} |
|
|
|
button.start-colored { |
|
font-size: 16px !important; |
|
padding: 12px 20px !important; |
|
border-radius: 10px; |
|
font-weight: bold; |
|
background: linear-gradient(90deg, #ff6ec4, #7873f5, #4ac29a); |
|
color: white; |
|
border: none; |
|
box-shadow: 0px 2px 6px rgba(0,0,0,0.2); |
|
transition: all 0.3s ease; |
|
} |
|
|
|
button.start-colored:hover { |
|
opacity: 0.9; |
|
transform: scale(1.02); |
|
} |
|
|
|
.send-btn { |
|
font-size: 18px !important; |
|
padding: 0 !important; |
|
width: 36px !important; |
|
height: 30px !important; |
|
min-width: 36px !important; |
|
min-height: 36px !important; |
|
background-color: #ffd6e7 !important; |
|
color: #333; |
|
border: none; |
|
border-radius: 6px; |
|
display: flex; |
|
align-items: center; |
|
justify-content: center; |
|
line-height: 1; |
|
} |
|
|
|
#upload-area { |
|
height: 100px; /* 你可以改成 80px、120px 等 */ |
|
border: 2px dashed #aaa; |
|
padding: 12px; |
|
display: flex; |
|
align-items: center; |
|
justify-content: center; |
|
font-size: 16px; |
|
background-color: #fafafa; |
|
} |
|
#upload-area:hover { |
|
background-color: #f0f8ff; |
|
border-color: #ff6600; |
|
cursor: pointer; |
|
} |
|
#upload-area span { |
|
display: none !important; |
|
} |
|
|
|
.bg-green { |
|
background-color: #99e699; |
|
} |
|
.bg-yellow { |
|
background-color: #ffd54d; |
|
} |
|
.bg-red { |
|
background-color: #ffb3b3; |
|
} |
|
|
|
.card-label { |
|
font-size: 16px; |
|
font-weight: bold; |
|
color: #333333; |
|
} |
|
|
|
.card-colon { |
|
font-size: 16px; |
|
font-weight: normal; |
|
margin-left: 2px; |
|
margin-right: 4px; |
|
color: #333333; |
|
} |
|
|
|
.card-value { |
|
font-size: 16px; |
|
color: #444444; |
|
font-family: "Roboto", "Inter", "Arial", sans-serif; |
|
} |
|
""") as demo: |
|
|
|
selected_tab = gr.State(value="interest") |
|
|
|
with gr.Row(): |
|
gr.Image( |
|
value="logo.png", |
|
show_label=False, |
|
show_download_button=False, |
|
height=150, |
|
width=150, |
|
container=False |
|
) |
|
|
|
with gr.Column(visible=True) as intro_page: |
|
grade = gr.Dropdown(choices=["9th and below", "10th", "11th", "12th"], label="Grade") |
|
school = gr.Textbox(label="High School") |
|
province = gr.Textbox(label="State") |
|
gender = gr.Radio(choices=["Female", "Male", "Other", "Prefer not to say"], label="Gender") |
|
sat_score = gr.Textbox(label="SAT Score (optional)", placeholder="e.g. 1450") |
|
interests = gr.CheckboxGroup( |
|
label="Subjects you’re interested in (optional)", |
|
choices=[ |
|
"Math", |
|
"Physics", |
|
"Chemistry", |
|
"Biology", |
|
"Geography", |
|
"History", |
|
"Literature", |
|
"Economics", |
|
"Politics", |
|
"Art", |
|
"Music", |
|
"Drama", |
|
"Engineering", |
|
"Psychology", |
|
"Philosophy", |
|
"Other" |
|
], |
|
|
|
elem_id="subject_checkboxes" |
|
) |
|
dream_school = gr.Textbox(label="Dream School (optional)", placeholder="e.g. Stanford University") |
|
start_button = gr.Button("🚀 Start Chat", elem_classes="start-colored") |
|
|
|
with gr.Column(visible=False) as loading_page: |
|
gr.HTML(""" |
|
<div id="spinner"> |
|
<div class="spinner-circle"></div> |
|
<div class="typing-loader"> |
|
<span class="loader-text">Preparing your personalized assistant</span> |
|
<span class="dot">.</span><span class="dot">.</span><span class="dot">.</span> |
|
</div> |
|
</div> |
|
""") |
|
|
|
with gr.Row(visible=False) as main_chat: |
|
with gr.Column(scale=0, min_width=80, elem_id="sidebar"): |
|
interest_tab = gr.Button("🎯 Interest", elem_id="interest-tab", elem_classes=["selected"]) |
|
program_tab = gr.Button("🎓 Program", elem_id="program-tab") |
|
statement_tab = gr.Button("📝 Essay", elem_id="statement-tab") |
|
|
|
with gr.Column(scale=4, elem_id="main-content") as main_area: |
|
|
|
with gr.Column(visible=True) as interest_page: |
|
with gr.Row(): |
|
with gr.Column(scale=4): |
|
chatbot = gr.Chatbot(label="👩🎓 AI counselor: Athena") |
|
with gr.Column(): |
|
with gr.Row(): |
|
send_btn1 = gr.Button("📨 A", elem_classes=["send-btn"]) |
|
send_btn2 = gr.Button("📨 B", elem_classes=["send-btn"]) |
|
send_btn3 = gr.Button("📨 C", elem_classes=["send-btn"]) |
|
send_btn4 = gr.Button("📨 D", elem_classes=["send-btn"]) |
|
|
|
msg = gr.Textbox( |
|
placeholder="Please input your answer as text if you think the options are not enough.", |
|
label=" Text Input ", |
|
lines=1, |
|
scale=16 |
|
) |
|
with gr.Row(): |
|
send_btn5 = gr.Button(" Get your Interest Cue! ", elem_classes=["send-btn"], scale=16) |
|
|
|
state = gr.State([]) |
|
msg.submit(fn=predict, inputs=[msg, state], outputs=[chatbot, state]).then( |
|
lambda: gr.update(value=""), inputs=[], outputs=[msg] |
|
) |
|
send_btn1.click(fn=send_fixed_message("I’d like to choose A."), inputs=[state], |
|
outputs=[chatbot, state]) |
|
send_btn2.click(fn=send_fixed_message("I’ll go with B."), inputs=[state], |
|
outputs=[chatbot, state]) |
|
send_btn3.click(fn=send_fixed_message("I choose C."), inputs=[state], |
|
outputs=[chatbot, state]) |
|
send_btn4.click(fn=send_fixed_message("My choice is D."), inputs=[state], |
|
outputs=[chatbot, state]) |
|
send_btn5.click(fn=send_fixed_message( |
|
"I finished the survey. Can you recommend some related University and programs?"), |
|
inputs=[state], outputs=[chatbot, state]) |
|
|
|
with gr.Column(visible=False) as program_page: |
|
gr.Markdown("## 🎓 Program Wishlist") |
|
|
|
with gr.Column(): |
|
with gr.Row(): |
|
school_name = gr.Textbox(label="University Name", placeholder="e.g. University of Pennsylvania") |
|
program_name = gr.Textbox(label="Program Name", placeholder="e.g. Computer and Information Science") |
|
acceptance_rate = gr.Textbox(label="Acceptance rate", placeholder="e.g. 65%") |
|
add_button = gr.Button("✚️ Add Program Card", elem_classes="start-colored", interactive=False) |
|
|
|
card_display = gr.HTML("") |
|
school_cards = gr.State([]) |
|
delete_trigger = gr.Textbox(visible=False, label=None, elem_id="delete_trigger") |
|
|
|
def delete_school_card(delete_index, cards): |
|
cards = cards or [] |
|
delete_index = int(delete_index) |
|
if 0 <= delete_index < len(cards): |
|
cards.pop(delete_index) |
|
|
|
html = "<div class='card-grid'>" |
|
for i, (s, p) in enumerate(cards): |
|
html += f""" |
|
<div class='school-card' id='card-{i}'> |
|
<div style="display: flex; justify-content: space-between; align-items: center;"> |
|
<strong>🏫 {s}</strong> |
|
<button class='delete-btn' data-index='{i}' style='background: none; border: none; font-size: 18px; cursor: pointer;'>❌</button> |
|
</div> |
|
<span>📘 {p}</span> |
|
</div> |
|
""" |
|
html += "</div>" |
|
return html, cards |
|
|
|
delete_trigger.change( |
|
fn=delete_school_card, |
|
inputs=[delete_trigger, school_cards], |
|
outputs=[card_display, school_cards] |
|
) |
|
|
|
|
|
def add_school_card(school, program, rate, cards): |
|
cards = cards or [] |
|
existing_entries = [(s.strip().lower(), p.strip().lower(), r.strip()) for s, p, r in cards] |
|
|
|
if (school.strip().lower(), program.strip().lower()) in [(s, p) for s, p, _ in existing_entries]: |
|
return gr.update(), cards |
|
|
|
cards.append((school, program, rate)) |
|
|
|
html = "<div class='card-grid'>" |
|
for i, (s, p, r) in enumerate(cards): |
|
try: |
|
rate_num = float(r.strip('%')) |
|
except: |
|
rate_num = 50 |
|
|
|
if rate_num > 80: |
|
bg_class = "bg-green" |
|
elif rate_num >= 40: |
|
bg_class = "bg-yellow" |
|
else: |
|
bg_class = "bg-red" |
|
|
|
html += f""" |
|
<div class='school-card {bg_class}' id='card-{i}'> |
|
<div> |
|
<span class="card-label">🏫 <strong>University</strong></span><span class="card-colon">:</span> |
|
<span class="card-value">{s}</span> |
|
</div> |
|
|
|
<div> |
|
<span class="card-label">📘 <strong>Program</strong></span><span class="card-colon">:</span> |
|
<span class="card-value">{p}</span> |
|
</div> |
|
<div> |
|
<span class="card-label">📊 <strong>Acceptance rate</strong></span><span class="card-colon">:</span> |
|
<span class="card-value">{r}</span> |
|
</div> |
|
|
|
</div> |
|
""" |
|
html += "</div>" |
|
return html, cards |
|
|
|
add_button.click( |
|
fn=add_school_card, |
|
inputs=[school_name, program_name, acceptance_rate, school_cards], |
|
outputs=[card_display, school_cards] |
|
) |
|
|
|
school_name.change(fn=should_disable_add_button, inputs=[school_name, program_name, acceptance_rate], |
|
outputs=add_button) |
|
program_name.change(fn=should_disable_add_button, inputs=[school_name, program_name, acceptance_rate], |
|
outputs=add_button) |
|
acceptance_rate.change(fn=should_disable_add_button, |
|
inputs=[school_name, program_name, acceptance_rate], outputs=add_button) |
|
|
|
with gr.Column(visible=False) as statement_page: |
|
gr.Markdown("## 📝 Personal Statement Workspace") |
|
|
|
with gr.Row(equal_height=True): |
|
with gr.Column(): |
|
upload_file = gr.File( |
|
label="📎", |
|
file_types=[".pdf", ".docx"], |
|
elem_id="upload-area" |
|
) |
|
|
|
|
|
user_draft = gr.Textbox( |
|
label="📄 Your Personal Statement Draft", |
|
lines=14, |
|
placeholder="Paste or write your draft here...", |
|
show_copy_button=True |
|
) |
|
send_draft_btn = gr.Button("✉️ Send Draft to Assistant", elem_classes="small-colored") |
|
|
|
upload_file.change( |
|
fn=extract_text_from_file, |
|
inputs=upload_file, |
|
outputs=user_draft |
|
) |
|
|
|
|
|
with gr.Column(): |
|
mini_chatbot = gr.Chatbot(label="💡 Writing Assistant") |
|
mini_input = gr.Textbox( |
|
placeholder="Ask for suggestions or improvements...", |
|
label="Message", |
|
lines=1 |
|
) |
|
mini_state = gr.State([]) |
|
|
|
send_draft_btn.click( |
|
fn=send_draft_to_chatbot, |
|
inputs=[user_draft, mini_state], |
|
outputs=[mini_chatbot, mini_state] |
|
) |
|
|
|
mini_input.submit(fn=predict_mini, inputs=[mini_input, mini_state], |
|
outputs=[mini_chatbot, mini_state]).then( |
|
lambda: gr.update(value=""), inputs=[], outputs=[mini_input] |
|
) |
|
|
|
with gr.Row(): |
|
brainstorm_btn = gr.Button("🧠 Brainstorm Ideas", elem_classes="small-colored") |
|
structure_btn = gr.Button("📚 Fix Structure", elem_classes="small-colored") |
|
polish_btn = gr.Button("✨ Polish Writing", elem_classes="small-colored") |
|
|
|
brainstorm_btn.click( |
|
fn=brainstorm_idea_message, |
|
inputs=[mini_state], |
|
outputs=[mini_chatbot, mini_state] |
|
) |
|
|
|
structure_btn.click( |
|
fn=fix_structure_message, |
|
inputs=[mini_state], |
|
outputs=[mini_chatbot, mini_state] |
|
) |
|
|
|
polish_btn.click( |
|
fn=polish_writing_message, |
|
inputs=[mini_state], |
|
outputs=[mini_chatbot, mini_state] |
|
) |
|
|
|
|
|
def switch_to_interest(): |
|
return ( |
|
gr.update(visible=True), |
|
gr.update(visible=False), |
|
gr.update(visible=False), |
|
gr.update(elem_classes=["selected"]), |
|
gr.update(elem_classes=[]), |
|
gr.update(elem_classes=[]), |
|
"interest" |
|
) |
|
|
|
|
|
def switch_to_program(): |
|
return ( |
|
gr.update(visible=False), |
|
gr.update(visible=True), |
|
gr.update(visible=False), |
|
gr.update(elem_classes=[]), |
|
gr.update(elem_classes=["selected"]), |
|
gr.update(elem_classes=[]), |
|
"program" |
|
) |
|
|
|
|
|
def switch_to_statement(): |
|
chatbot_display, history = start_essay_intro() |
|
return ( |
|
gr.update(visible=False), |
|
gr.update(visible=False), |
|
gr.update(visible=True), |
|
gr.update(elem_classes=[]), |
|
gr.update(elem_classes=[]), |
|
gr.update(elem_classes=["selected"]), |
|
"statement", |
|
chatbot_display, |
|
history |
|
) |
|
|
|
interest_tab.click( |
|
fn=switch_to_interest, |
|
inputs=[], |
|
outputs=[ |
|
interest_page, |
|
program_page, |
|
statement_page, |
|
interest_tab, |
|
program_tab, |
|
statement_tab, |
|
selected_tab |
|
] |
|
) |
|
|
|
program_tab.click( |
|
fn=switch_to_program, |
|
inputs=[], |
|
outputs=[ |
|
interest_page, |
|
program_page, |
|
statement_page, |
|
interest_tab, |
|
program_tab, |
|
statement_tab, |
|
selected_tab |
|
] |
|
) |
|
|
|
statement_tab.click( |
|
fn=switch_to_statement, |
|
inputs=[], |
|
outputs=[ |
|
interest_page, |
|
program_page, |
|
statement_page, |
|
interest_tab, |
|
program_tab, |
|
statement_tab, |
|
selected_tab, |
|
mini_chatbot, |
|
mini_state |
|
] |
|
) |
|
|
|
def loading_to_main(): |
|
time.sleep(1) |
|
return gr.update(visible=False), gr.update(visible=True) |
|
|
|
|
|
start_button.click( |
|
fn=start_chat_fn, |
|
inputs=[grade, school, province, gender, sat_score, interests, dream_school], |
|
outputs=[intro_page, loading_page, main_chat, chatbot, state] |
|
).then( |
|
fn=loading_to_main, |
|
inputs=[], |
|
outputs=[loading_page, main_chat] |
|
) |
|
|
|
demo.launch() |
|
|
|
gr.HTML(""" |
|
<script> |
|
document.addEventListener('click', function(event) { |
|
if (event.target && event.target.matches('.delete-btn')) { |
|
const index = event.target.getAttribute('data-index'); |
|
const wrapper = document.querySelector('[id^="delete_trigger"]'); |
|
const textarea = wrapper ? wrapper.querySelector('textarea') : null; |
|
if (textarea) { |
|
textarea.value = index; |
|
textarea.dispatchEvent(new Event('input', { bubbles: true })); |
|
} |
|
} |
|
}); |
|
</script> |
|
""") |