| | 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> |
| | """) |