import streamlit as st import google.generativeai as genai import os import PyPDF2 from audio_recorder_streamlit import audio_recorder import speech_recognition as sr import matplotlib.pyplot as plt from matplotlib.patches import FancyBboxPatch from reportlab.lib.pagesizes import A4 from reportlab.lib import colors from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Image as RLImage, HRFlowable from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle from reportlab.lib.enums import TA_CENTER import io import json import re st.set_page_config( page_title="AI Study Abroad Suite", page_icon="๐", layout="wide" ) api_key = os.getenv("GEMINI_API_KEY") language = st.sidebar.selectbox( "๐ Select Language", ["ุงูุนุฑุจูุฉ", "English", "Franรงais", "Espaรฑol", "Deutsch"] ) theme_mode = st.sidebar.radio( "๐จ Theme Mode", ["Dark", "Light"] ) if theme_mode == "Dark": bg = "#1f1f1f"; text = "white" chat_user_bg = "#2d4a6e"; chat_bot_bg = "#2a2a2a" chat_border = "#444"; input_bg = "#2a2a2a" else: bg = "#ffffff"; text = "black" chat_user_bg = "#d1e8ff"; chat_bot_bg = "#f0f0f0" chat_border = "#ddd"; input_bg = "#f9f9f9" st.markdown( f""" """, unsafe_allow_html=True ) st.title("๐ AI Study Abroad Assistant & Mentorship Suite") st.sidebar.markdown("---") st.sidebar.subheader("๐งฎ GPA Converter (ู ุนุงุฏู ุงูุฏุฑุฌุงุช)") gpa_system = st.sidebar.selectbox("System", ["Percentage (100%)", "US GPA (4.0)", "UK Honours"]) user_grade = st.sidebar.number_input("Your Grade", min_value=0.0, max_value=100.0, value=85.0 if gpa_system == "Percentage (100%)" else 3.5) converted_gpa_us = 0.0 converted_gpa_de = 0.0 if gpa_system == "Percentage (100%)": converted_gpa_us = (user_grade / 100) * 4.0 converted_gpa_de = max(1.0, min(5.0, 1.0 + 3.0 * (100.0 - user_grade) / (100.0 - 50.0))) elif gpa_system == "US GPA (4.0)": converted_gpa_us = user_grade converted_gpa_de = max(1.0, min(5.0, 1.0 + 3.0 * (4.0 - user_grade) / (4.0 - 2.0))) else: converted_gpa_us = 3.7 converted_gpa_de = 1.5 st.sidebar.markdown(f"๐บ๐ธ **US GPA Equivalent:** {converted_gpa_us:.2f} / 4.0") st.sidebar.markdown(f"๐ฉ๐ช **German Grade Equivalent:** {converted_gpa_de:.2f} *(1.0 is best)*") if "interview_started" not in st.session_state: st.session_state.interview_started = False if "interview_history" not in st.session_state: st.session_state.interview_history = [] if "current_question" not in st.session_state: st.session_state.current_question = "" if "question_count" not in st.session_state: st.session_state.question_count = 0 if "study_plan" not in st.session_state: st.session_state.study_plan = "" if "country_info" not in st.session_state: st.session_state.country_info = "" if "cv_text_extracted" not in st.session_state: st.session_state.cv_text_extracted = "" if "chat_history" not in st.session_state: st.session_state.chat_history = [] def clean_text_for_pdf(text): text = re.sub(r'[^\x00-\x7F]+', ' ', text) text = re.sub(r'\*\*(.+?)\*\*', r'\1', text) text = re.sub(r'\*(.+?)\*', r'\1', text) text = re.sub(r'#{1,6}\s*', '', text) text = re.sub(r'^\s*[\*\-]\s+', 'โข ', text, flags=re.MULTILINE) return text.strip() def generate_roadmap(plan_text, major, degree, goal, model): parse_prompt = f"""Based on this study plan: {plan_text} Extract courses and websites organized by level (Beginner, Intermediate, Advanced). Maximum 3 items per level. Each item must be structured exactly like this string sample: "Course Name - Platform" Return ONLY a valid JSON object structure, no markdown codeblocks, no extra words: {{ "Beginner": ["Course Name - Platform", "Course Name - Platform"], "Intermediate": ["Course Name - Platform"], "Advanced": ["Course Name - Platform"] }}""" try: parse_response = model.generate_content(parse_prompt) json_str = re.search(r'\{.*\}', parse_response.text, re.DOTALL).group() levels = json.loads(json_str) except: levels = { "Beginner": [f"Intro to {major or 'Field'} - Coursera", "Foundations Course - edX"], "Intermediate": [f"Core {major or 'Field'} Concepts - Udemy"], "Advanced": [f"Advanced {major or 'Field'} System - MIT OCW"] } level_styles = { "Beginner": {"bg": "#eff6ff", "border": "#3b82f6", "title": "#1e3a8a", "node": "#dbeafe"}, "Intermediate": {"bg": "#fef3c7", "border": "#f59e0b", "title": "#78350f", "node": "#fef3c7"}, "Advanced": {"bg": "#ecfdf5", "border": "#10b981", "title": "#064e3b", "node": "#d1fae5"}, } NODE_W = 6.2 NODE_H = 0.75 PAD = 0.6 total_h = 1.0 + (len(levels) * 2.8) fig, ax = plt.subplots(figsize=(9.5, max(6, total_h))) fig.patch.set_facecolor('#f8fafc') ax.set_facecolor('#f8fafc') ax.axis('off') ax.set_xlim(0, 10) ax.set_ylim(0, total_h) y_cursor = total_h - 0.8 prev_arrow_y = None for level, courses in levels.items(): s = level_styles.get(level, level_styles["Advanced"]) box_h = len(courses) * (NODE_H + 0.3) + 0.8 if prev_arrow_y is not None: ax.annotate("", xy=(5, y_cursor + 0.05), xytext=(5, prev_arrow_y), arrowprops=dict(arrowstyle="-|>", color="#94a3b8", lw=2, mutation_scale=14)) big_box = FancyBboxPatch( (PAD, y_cursor - box_h), 10 - 2*PAD, box_h, boxstyle="round,pad=0.1", linewidth=1.5, edgecolor=s["border"], facecolor=s["bg"], zorder=1 ) ax.add_patch(big_box) ax.text(5, y_cursor - 0.3, level.upper(), ha='center', va='center', fontweight='bold', color=s["title"], fontsize=12, zorder=3) node_y_top = y_cursor - 0.75 for i, course in enumerate(courses): ny = node_y_top - i * (NODE_H + 0.3) node_box = FancyBboxPatch( (5 - NODE_W/2, ny - NODE_H/2), NODE_W, NODE_H, boxstyle="round,pad=0.05", linewidth=1, edgecolor=s["border"], facecolor="#ffffff", zorder=2 ) ax.add_patch(node_box) parts = course.split(' - ', 1) if len(parts) == 2: clean_title = re.sub(r'[^\x00-\x7F]+', '', parts[0])[:50] clean_platform = re.sub(r'[^\x00-\x7F]+', '', parts[1])[:35] ax.text(5, ny + 0.14, clean_title, ha='center', va='center', fontsize=9, fontweight='bold', color="#1e293b", zorder=3) ax.text(5, ny - 0.14, clean_platform, ha='center', va='center', fontsize=8, color="#64748b", fontweight='600', zorder=3) else: clean_course = re.sub(r'[^\x00-\x7F]+', '', course)[:55] ax.text(5, ny, clean_course, ha='center', va='center', fontsize=9, color="#1e293b", zorder=3) prev_arrow_y = y_cursor - box_h - 0.05 y_cursor -= box_h + 0.5 fig.suptitle(f"Study Roadmap โข {major or 'General'} | {degree} | {goal or 'Global'}", fontsize=12, fontweight='bold', color='#0f172a', y=0.99) buf = io.BytesIO() plt.savefig(buf, format='png', bbox_inches='tight', dpi=160, facecolor='#f8fafc') buf.seek(0) plt.close() return buf def generate_pdf_summary(plan_text, country_info, major, degree, budget, goal, roadmap_buf=None): buffer = io.BytesIO() doc = SimpleDocTemplate(buffer, pagesize=A4, rightMargin=45, leftMargin=45, topMargin=45, bottomMargin=45) styles = getSampleStyleSheet() title_style = ParagraphStyle('DocTitle', fontSize=24, fontName='Helvetica-Bold', textColor=colors.HexColor('#0f172a'), spaceAfter=4, alignment=TA_CENTER) subtitle_style = ParagraphStyle('DocSub', fontSize=11, fontName='Helvetica', textColor=colors.HexColor('#475569'), spaceAfter=14, alignment=TA_CENTER) section_style = ParagraphStyle('DocSec', fontSize=14, fontName='Helvetica-Bold', textColor=colors.HexColor('#1e3a8a'), spaceBefore=12, spaceAfter=6) body_style = ParagraphStyle('DocBody', fontSize=10, fontName='Helvetica', textColor=colors.HexColor('#334155'), spaceAfter=4, leading=14) bullet_style = ParagraphStyle('DocBullet', fontSize=10, fontName='Helvetica', textColor=colors.HexColor('#334155'), spaceAfter=4, leftIndent=15, leading=14) story = [ Paragraph("Study Abroad Plan Document", title_style), Paragraph(f"{major or 'N/A'} | {degree} | {goal or 'Global'}", subtitle_style), HRFlowable(width="100%", thickness=2, color=colors.HexColor('#1e3a8a'), spaceAfter=10), Paragraph("Student Profile Summary", section_style), Paragraph(f"โข Field of Study: {major or 'Not specified'}", bullet_style), Paragraph(f"โข Degree Objective: {degree}", bullet_style), Paragraph(f"โข Financial Budget Context: {budget or 'Not specified'}", bullet_style), Paragraph(f"โข Target Country: {goal or 'Not specified'}", bullet_style), Spacer(1, 6), Paragraph("Academic Consultation Strategy", section_style) ] for line in plan_text.split('\n'): if line.strip(): clean_line = clean_text_for_pdf(line.strip()) current_style = bullet_style if clean_line.startswith('โข') else body_style story.append(Paragraph(clean_line, current_style)) if country_info: story.append(Spacer(1, 6)) story.append(Paragraph(f"Destination Insights: {goal}", section_style)) for line in country_info.split('\n'): if line.strip(): clean_line = clean_text_for_pdf(line.strip()) story.append(Paragraph(clean_line, body_style)) if roadmap_buf: story.append(Spacer(1, 10)) story.append(HRFlowable(width="100%", thickness=1, color=colors.HexColor('#e2e8f0'), spaceAfter=10)) story.append(Paragraph("Visual Learning Map Layout", section_style)) roadmap_buf.seek(0) story.append(RLImage(roadmap_buf, width=460, height=280)) doc.build(story) buffer.seek(0) return buffer tab_plan, tab_interview, tab_sop, tab_ats = st.tabs([ "๐ Study Plan & Advisor", "๐ AI Mock Interview", "โ๏ธ AI SOP Builder", "๐ CV ATS Scorer" ]) with tab_plan: major = st.text_input("๐ฏ Field (ุงูุชุฎุตุต)", key="plan_major") degree = st.selectbox("๐ Degree (ุงูุฏุฑุฌุฉ)", ["Bachelor", "Master", "PhD"], key="plan_degree") budget = st.text_input("๐ฐ Budget (ุงูู ูุฒุงููุฉ)", key="plan_budget") goal = st.text_input("๐ Country (ุงูุฏููุฉ)", key="plan_goal") st.markdown("### Upload CV (PDF)") uploaded_pdf = st.file_uploader("Upload PDF", type=["pdf"], key="plan_pdf") if uploaded_pdf: reader = PyPDF2.PdfReader(uploaded_pdf) extracted = "" for page in reader.pages: t = page.extract_text() if t: extracted += t st.session_state.cv_text_extracted = extracted st.success("PDF Loaded into System Memory โ ") st.markdown("### ๐ค Voice Input") audio = audio_recorder(key="plan_audio") voice_text = "" if audio: with open("audio.wav", "wb") as f: f.write(audio) r = sr.Recognizer() try: with sr.AudioFile("audio.wav") as source: audio_data = r.record(source) voice_text = r.recognize_google(audio_data) st.success("Voice Converted โ ") st.write(voice_text) except Exception as e: st.error(f"Voice Error: {e}") if st.button("Generate Plan ๐", key="btn_gen_plan"): if not api_key: st.error("Missing API Key.") else: try: genai.configure(api_key=api_key) model = genai.GenerativeModel( model_name="gemini-3.1-flash-lite", system_instruction="You are an absolute authority on international university applications, visa requirements, and higher education academic advice. You must refuse to handle non-educational requests." ) lang_inst = "Answer only in English without complex emojis" if language == "English" else f"Respond in {language} without complex emojis" prompt = f"""{lang_inst}CRITICAL GUARDRAIL: If the student data context contains completely non-academic queries or dangerous content, explicitly decline to respond. Create a comprehensive study plan: Major: {major}, Degree: {degree}, Budget: {budget}, Destination: {goal} CV Context: {st.session_state.cv_text_extracted} | Voice Note Transcribed: {voice_text} Include recommended Universities, available Scholarships, and Online developmental courses.""" with st.spinner("Generating Study Plan..."): response = model.generate_content(prompt) st.session_state.study_plan = response.text st.success("Plan Generated Successfully!") st.write(st.session_state.study_plan) if goal: country_prompt = f"Give a short structured factual brief about studying, culture and cost of living in {goal}. {lang_inst}" country_resp = model.generate_content(country_prompt) st.session_state.country_info = country_resp.text st.markdown("---") st.subheader(f"๐ About {goal}") st.write(st.session_state.country_info) st.markdown("---") with st.spinner("Assembling your Roadmap layout..."): roadmap_buf = generate_roadmap(st.session_state.study_plan, major, degree, goal, model) col_pdf, col_map = st.columns(2) with col_pdf: st.markdown("#### ๐ PDF Document Export") pdf_buf = generate_pdf_summary(st.session_state.study_plan, st.session_state.country_info, major, degree, budget, goal, roadmap_buf) st.download_button("๐ Download PDF Report", data=pdf_buf, file_name="Study_Plan.pdf", mime="application/pdf") with col_map: st.markdown("#### ๐บ๏ธ Course Pathway Flowchart") st.image(roadmap_buf, caption="Your Study Roadmap Pathway") except Exception as e: st.error(f"Execution Error: {e}") st.markdown("---") st.subheader("๐ฌ Chat with your Study Advisor") chat_html = "".join(f'