SpanDone commited on
Commit
a45fe55
·
0 Parent(s):

Initializing CareerPal on Hugging Face

Browse files
.gitattributes ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ *.7z filter=lfs diff=lfs merge=lfs -text
2
+ *.arrow filter=lfs diff=lfs merge=lfs -text
3
+ *.bin filter=lfs diff=lfs merge=lfs -text
4
+ *.bz2 filter=lfs diff=lfs merge=lfs -text
5
+ *.ckpt filter=lfs diff=lfs merge=lfs -text
6
+ *.ftz filter=lfs diff=lfs merge=lfs -text
7
+ *.gz filter=lfs diff=lfs merge=lfs -text
8
+ *.h5 filter=lfs diff=lfs merge=lfs -text
9
+ *.joblib filter=lfs diff=lfs merge=lfs -text
10
+ *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
+ *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
+ *.model filter=lfs diff=lfs merge=lfs -text
13
+ *.msgpack filter=lfs diff=lfs merge=lfs -text
14
+ *.npy filter=lfs diff=lfs merge=lfs -text
15
+ *.npz filter=lfs diff=lfs merge=lfs -text
16
+ *.onnx filter=lfs diff=lfs merge=lfs -text
17
+ *.ot filter=lfs diff=lfs merge=lfs -text
18
+ *.parquet filter=lfs diff=lfs merge=lfs -text
19
+ *.pb filter=lfs diff=lfs merge=lfs -text
20
+ *.pickle filter=lfs diff=lfs merge=lfs -text
21
+ *.pkl filter=lfs diff=lfs merge=lfs -text
22
+ *.pt filter=lfs diff=lfs merge=lfs -text
23
+ *.pth filter=lfs diff=lfs merge=lfs -text
24
+ *.rar filter=lfs diff=lfs merge=lfs -text
25
+ *.safetensors filter=lfs diff=lfs merge=lfs -text
26
+ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
+ *.tar.* filter=lfs diff=lfs merge=lfs -text
28
+ *.tar filter=lfs diff=lfs merge=lfs -text
29
+ *.tflite filter=lfs diff=lfs merge=lfs -text
30
+ *.tgz filter=lfs diff=lfs merge=lfs -text
31
+ *.wasm filter=lfs diff=lfs merge=lfs -text
32
+ *.xz filter=lfs diff=lfs merge=lfs -text
33
+ *.zip filter=lfs diff=lfs merge=lfs -text
34
+ *.zst filter=lfs diff=lfs merge=lfs -text
35
+ *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ *.pdf filter=lfs diff=lfs merge=lfs -text
37
+ *.png filter=lfs diff=lfs merge=lfs -text
.gitignore ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ venv/
2
+ __pycache__/
3
+ *.pyc
4
+ .env
Chat log saves/1.txt ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 2025-09-11 12:58:45 - Use pytorch device_name: cpu
2
+ 2025-09-11 12:58:45 - Load pretrained SentenceTransformer: sentence-transformers/all-MiniLM-L6-v2
3
+ 2025-09-11 12:58:51 - * Debugger is active!
4
+ 2025-09-11 12:58:51 - * Debugger PIN: 815-378-899
5
+ 2025-09-11 12:58:51 - 127.0.0.1 - - [11/Sep/2025 12:58:51] "GET / HTTP/1.1" 200 -
6
+ 2025-09-11 12:58:51 - 127.0.0.1 - - [11/Sep/2025 12:58:51] "GET /static/style.css HTTP/1.1" 304 -
7
+ 2025-09-11 12:58:51 - 127.0.0.1 - - [11/Sep/2025 12:58:51] "GET /static/logo_darktheme.png HTTP/1.1" 304 -
8
+ 2025-09-11 12:58:51 - 127.0.0.1 - - [11/Sep/2025 12:58:51] "GET /static/logo_lighttheme.png HTTP/1.1" 304 -
9
+ 2025-09-11 12:58:51 - 127.0.0.1 - - [11/Sep/2025 12:58:51] "GET /static/script.js HTTP/1.1" 304 -
10
+ 2025-09-11 12:58:51 - 127.0.0.1 - - [11/Sep/2025 12:58:51] "GET /static/bot_avatar.png HTTP/1.1" 304 -
11
+ 2025-09-11 12:59:17 - 127.0.0.1 - - [11/Sep/2025 12:59:17] "GET / HTTP/1.1" 200 -
12
+ 2025-09-11 12:59:17 - 127.0.0.1 - - [11/Sep/2025 12:59:17] "GET /static/script.js HTTP/1.1" 304 -
13
+ 2025-09-11 12:59:17 - 127.0.0.1 - - [11/Sep/2025 12:59:17] "GET /static/logo_darktheme.png HTTP/1.1" 304 -
14
+ 2025-09-11 12:59:17 - 127.0.0.1 - - [11/Sep/2025 12:59:17] "GET /static/logo_lighttheme.png HTTP/1.1" 304 -
15
+ 2025-09-11 12:59:17 - 127.0.0.1 - - [11/Sep/2025 12:59:17] "GET /static/style.css HTTP/1.1" 200 -
16
+ 2025-09-11 12:59:17 - 127.0.0.1 - - [11/Sep/2025 12:59:17] "GET /static/bot_avatar.png HTTP/1.1" 304 -
17
+ 2025-09-11 12:59:21 - 127.0.0.1 - - [11/Sep/2025 12:59:21] "GET /download_log HTTP/1.1" 200 -
18
+ 2025-09-11 12:59:31 - USER: start
19
+ 2025-09-11 12:59:31 - --- NEW SESSION STARTED ---
20
+ 2025-09-11 12:59:31 - BOT: \U0001f44b Hey there! Let�s get started. What was your academic stream after 10th? (Science, Commerce, Arts, etc.)
21
+ 2025-09-11 12:59:31 - 127.0.0.1 - - [11/Sep/2025 12:59:31] "POST /chat HTTP/1.1" 200 -
22
+ 2025-09-11 12:59:39 - USER: commerce
23
+ 2025-09-11 12:59:39 - BOT: \u2705 Okay, noted. Which subjects do you feel strongest in?
24
+ 2025-09-11 12:59:39 - 127.0.0.1 - - [11/Sep/2025 12:59:39] "POST /chat HTTP/1.1" 200 -
25
+ 2025-09-11 12:59:46 - USER: economics
26
+ 2025-09-11 12:59:46 - BOT: \U0001f525 Nice! Being strong in economics is a great asset. Which subjects do you find most difficult or uninteresting?
27
+ 2025-09-11 12:59:46 - 127.0.0.1 - - [11/Sep/2025 12:59:46] "POST /chat HTTP/1.1" 200 -
28
+ 2025-09-11 12:59:53 - USER: tally
29
+ 2025-09-11 12:59:53 - BOT: \U0001f44c Got it � I�ll stay away from careers heavy in tally. Do you learn better through practical work or theory/research?
30
+ 2025-09-11 12:59:53 - 127.0.0.1 - - [11/Sep/2025 12:59:53] "POST /chat HTTP/1.1" 200 -
31
+ 2025-09-11 13:00:01 - USER: theory
32
+ 2025-09-11 13:00:01 - BOT: \u2705 Okay, noted. What kind of work environment do you prefer? (e.g., office, outdoors, lab, on-the-move)
33
+ 2025-09-11 13:00:01 - 127.0.0.1 - - [11/Sep/2025 13:00:01] "POST /chat HTTP/1.1" 200 -
34
+ 2025-09-11 13:00:06 - USER: office
35
+ 2025-09-11 13:00:06 - BOT: \u2705 Okay, noted. Do you prefer working alone on focused tasks, or collaboratively in a team?
36
+ 2025-09-11 13:00:06 - 127.0.0.1 - - [11/Sep/2025 13:00:06] "POST /chat HTTP/1.1" 200 -
37
+ 2025-09-11 13:00:14 - USER: in a team
38
+ 2025-09-11 13:00:14 - BOT: \U0001f44d Okay, noting your preference for a 'in a team' work style. Outside academics, what activities or hobbies do you enjoy?
39
+ 2025-09-11 13:00:14 - 127.0.0.1 - - [11/Sep/2025 13:00:14] "POST /chat HTTP/1.1" 200 -
40
+ 2025-09-11 13:00:48 - USER: i like gaming
41
+ 2025-09-11 13:00:48 - BOT: \U0001f60e Cool! Enjoying gaming gives me clues about your personality. What topics or fields are you generally curious about? (e.g., technology, healthcare, finance, arts, environment)
42
+ 2025-09-11 13:00:48 - 127.0.0.1 - - [11/Sep/2025 13:00:48] "POST /chat HTTP/1.1" 200 -
43
+ 2025-09-11 13:01:04 - USER: i like finance
44
+ 2025-09-11 13:01:04 - BOT: \U0001f44d That's insightful! An interest in finance helps narrow down the options. What motivates your future most? (money, creativity, social impact, innovation, etc.)
45
+ 2025-09-11 13:01:04 - 127.0.0.1 - - [11/Sep/2025 13:01:04] "POST /chat HTTP/1.1" 200 -
46
+ 2025-09-11 13:01:19 - USER: money
47
+ 2025-09-11 13:01:24 - --- SESSION ENDED ---
48
+ 2025-09-11 13:01:24 - BOT: \U0001f680 Based on everything you shared, here are my top recommendations for you: \U0001f3af BA in Economics (Match: 112%) Studies the production, distribution, and consumption of goods and services. Suited for analytical thinkers with strong mathematical skills who are interested in finance, policy, and market behavior. \u2b50 Aligns with your interest in: finance . \u2728 Builds on your strengths in: economics. \U0001f9e9 Matches your personality traits: analytical, structured. \U0001f3af B.Com (General) (Match: 110%) A core commerce degree covering accounting, business law, and economics. Best suited for students with a knack for numbers and an interest in finance and corporate structures. \u2b50 Aligns with your interest in: finance . \u2728 Builds on your strengths in: economics. \U0001f9e9 Matches your personality traits: analytical, structured. \U0001f3af BMS (Bachelor of Management Studies) (Match: 101%) A comprehensive degree focusing on business, management, marketing, and leadership. It is ideal for aspiring entrepreneurs and managers with strong analytical and interpersonal skills. \u2b50 Aligns with your interest in: finance . \u2728 Builds on your strengths in: economics. \U0001f9e9 Matches your personality traits: analytical, structured. You can type 'start' again to restart the session with new answers.
49
+ 2025-09-11 13:01:24 - 127.0.0.1 - - [11/Sep/2025 13:01:24] "POST /chat HTTP/1.1" 200 -
Dockerfile ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Use an official Python runtime as a parent image
2
+ FROM python:3.9-slim
3
+
4
+ # Set the working directory in the container
5
+ WORKDIR /app
6
+
7
+ # Copy the requirements file into the container at /app
8
+ COPY requirements.txt .
9
+
10
+ # Install any needed packages specified in requirements.txt
11
+ RUN pip install --no-cache-dir -r requirements.txt
12
+
13
+ # Copy the rest of the application's code into the container at /app
14
+ COPY . .
15
+
16
+ # Expose the port that Gunicorn will run on
17
+ EXPOSE 7860
18
+
19
+ # Run the Gunicorn server
20
+ CMD ["gunicorn", "--bind", "0.0.0.0:7860", "app:app"]
README.md ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: CareerPal
3
+ emoji: 🦀
4
+ colorFrom: yellow
5
+ colorTo: purple
6
+ sdk: docker
7
+ pinned: false
8
+ ---
9
+
10
+ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
Required Data/24089440-pin-codes.pdf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:f6f017260958746938a353e79309ffde14331deda6494fa0fe6be363429ebcec
3
+ size 222659
Required Data/AndrewGreenResume.pdf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:7ef7b05c59df35030c6ccbd4fed9db388f29dd683c1c568fe97f2bfcd9ac852e
3
+ size 144793
Required Data/Professional CV Resume_20250626_095832_0000.pdf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:22af6f29b34745cbd232958ddd040e3aa8359792b4218528ee9439cc4c740ecf
3
+ size 51629
Required Data/Resume (2).pdf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:a32284e991166aad12ca12381e514269efb770a67122291a98c7048414a89948
3
+ size 30242
Required Data/Updated-All-College-List-with-Course-Detailss.pdf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:760e844c3ce2e500726d549bbdd8ee2857281ed05cee33d18f0a07d6d39ae167
3
+ size 4424828
app.py ADDED
@@ -0,0 +1,433 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import re
3
+ import logging
4
+ import math
5
+ import os
6
+ import docx
7
+ import fitz # PyMuPDF
8
+ from dotenv import load_dotenv
9
+ import google.generativeai as genai
10
+ from flask import Flask, render_template, request, jsonify, send_file
11
+ from sentence_transformers import SentenceTransformer, util
12
+ from rapidfuzz import fuzz, process
13
+ from urllib.parse import quote_plus
14
+
15
+ # --- Load environment variables and configure Gemini API ---
16
+ load_dotenv()
17
+ GEMINI_API_KEY = os.getenv("GEMINI_API_KEY")
18
+ genai.configure(api_key=GEMINI_API_KEY)
19
+
20
+ app = Flask(__name__)
21
+
22
+ # --- Setup Loggers ---
23
+ # On cloud platforms, we log to standard output, so we disable file logging.
24
+ logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(message)s', datefmt='%Y-%m-%d %H:%M:%S')
25
+
26
+ # --- Load Model & Data ---
27
+ model = SentenceTransformer("sentence-transformers/paraphrase-albert-small-v2")
28
+ def load_json(filename):
29
+ try:
30
+ with open(filename, "r", encoding="utf-8") as f:
31
+ return json.load(f)
32
+ except Exception as e:
33
+ logging.error(f"Error loading {filename}: {e}")
34
+ return [] if filename != "pincodes.json" else {}
35
+
36
+ COURSE_DATA = load_json("careers.json")
37
+ COLLEGE_DATA = load_json("colleges.json")
38
+ PINCODE_DATA = load_json("pincodes.json")
39
+
40
+ ALL_TAGS = set()
41
+ for course in COURSE_DATA:
42
+ tags_obj = course.get("tags", {})
43
+ if isinstance(tags_obj, dict):
44
+ for category_tags in tags_obj.values(): ALL_TAGS.update(category_tags)
45
+ ALL_TAGS.update(["theory", "research", "practical"])
46
+
47
+ # --- Constants & Dictionaries ---
48
+ QUESTIONS = { "stream": "What was your academic stream after 10th?", "subject_strengths": "Which subjects do you feel strongest in?", "subject_weaknesses": "Which subjects do you find most difficult?", "learning_style": "Do you learn better through practical work or theory/research?", "work_environment": "What kind of work environment do you prefer? (e.g., an office, a lab, outdoors, a workshop)", "team_preference": "Do you prefer working alone or collaboratively?", "interest_activities": "Outside academics, what hobbies do you enjoy?", "general_interests": "What topics or fields are you generally curious about?", "primary_driver": "What motivates your future most? (e.g., money, creativity, helping people, innovation, stability)" }
49
+ STOP_WORDS = {"a", "an", "and", "the", "in", "on", "for", "with", "i", "my", "is", "are", "like", "to", "of"}
50
+ TRAIT_KEYWORDS = { "analytical": ["math", "physics", "science", "data", "logic", "puzzles", "engineering", "theory", "research"], "creative": ["art", "design", "music", "writing", "media", "film", "painting"], "social": ["helping", "teaching", "volunteering", "communication", "people", "society", "healthcare", "environment"], "structured": ["commerce", "law", "management", "finance", "corporate", "office"], "hands_on": ["practical", "projects", "sports", "repair", "construction", "biology"], "collaborative": ["team", "teamwork", "collaboration", "people", "social"], "independent": ["alone", "independent", "self-directed", "focus", "quiet"], "field_work": ["outdoors", "on-the-move", "travel", "construction", "farming"], "lab_work": ["lab", "research", "science", "biotech", "forensic"] }
51
+
52
+ # --- Helper Functions ---
53
+ def preprocess_text(text):
54
+ text = text.lower()
55
+ text = re.sub(r'[.&,]', '', text)
56
+ text = re.sub(r'\b(in|and)\b', '', text)
57
+ return re.sub(r'\s+', ' ', text).strip()
58
+
59
+ def normalize_word(word):
60
+ if not ALL_TAGS: return word
61
+ best, score, _ = process.extractOne(word, ALL_TAGS, scorer=fuzz.ratio)
62
+ return best if score >= 85 else word
63
+
64
+ def parse_input(text):
65
+ words = re.split(r"[,\s]+", text.lower())
66
+ return {normalize_word(word) for word in words if word and word not in STOP_WORDS}
67
+
68
+ def build_user_profile(answers):
69
+ profile = {key: parse_input(value) for key, value in answers.items() if value}
70
+ all_keywords = set().union(*profile.values()) if profile else set()
71
+ persona = {trait for trait, kws in TRAIT_KEYWORDS.items() if not all_keywords.isdisjoint(kws)}
72
+ return profile, persona
73
+
74
+ def calculate_distance(pin1, pin2, pincode_data):
75
+ if pin1 not in pincode_data or pin2 not in pincode_data: return float('inf')
76
+ lat1, lon1 = pincode_data[pin1]['lat'], pincode_data[pin1]['lon']
77
+ lat2, lon2 = pincode_data[pin2]['lat'], pincode_data[pin2]['lon']
78
+ R, dLat, dLon = 6371, math.radians(lat2 - lat1), math.radians(lon2 - lon1)
79
+ a = math.sin(dLat / 2)**2 + math.cos(math.radians(lat1)) * math.cos(math.radians(lat2)) * math.sin(dLon / 2)**2
80
+ c = 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a))
81
+ return R * c
82
+
83
+ def find_nearby_colleges(course_name, user_pincode):
84
+ if user_pincode not in PINCODE_DATA:
85
+ return "<div class='college-card error-card'>Sorry, I don't have location data for that PIN code.</div>", []
86
+ colleges_with_course = []
87
+ for college in COLLEGE_DATA:
88
+ for offered_course in college.get('courses_offered', []):
89
+ if course_name.lower() == offered_course.lower():
90
+ colleges_with_course.append(college)
91
+ break
92
+ if not colleges_with_course:
93
+ return f"<div class='college-card error-card'>I couldn't find any colleges in my database offering <b>{course_name}</b>.</div>", []
94
+ nearby_colleges = sorted([(c, calculate_distance(user_pincode, c['pincode'], PINCODE_DATA)) for c in colleges_with_course], key=lambda x: x[1])
95
+ response_html = "<div class='college-card primary-card'>"
96
+ response_html += f"<h4>🎯 Top Matches for {course_name} near {user_pincode}</h4><ul>"
97
+ colleges_found = 0
98
+ for college, dist in nearby_colleges:
99
+ if dist <= 150:
100
+ colleges_found += 1
101
+ encoded_name = quote_plus(college['name'])
102
+ response_html += f"<li><a href='https://www.google.com/search?q={encoded_name}' target='_blank'><b>{college['name']}</b></a> ({college['pincode']})<br><small>Approx. {dist:.0f} km away</small></li>"
103
+ if colleges_found == 0:
104
+ return f"<div class='college-card error-card'>I couldn't find any colleges offering <b>{course_name}</b> within a 150km radius of your PIN code.</div>", []
105
+ response_html += "</ul></div>"
106
+ return response_html, nearby_colleges
107
+
108
+ def format_course_details(course):
109
+ details = "<div class='details-card'>"
110
+ details += f"<h3>🎓 {course.get('course', 'N/A')}</h3><p>{course.get('description', '')}</p>"
111
+ careers = course.get('possible_careers', [])
112
+ if careers: details += "<b>💼 Potential Career Paths:</b><ul>" + "".join(f"<li>{c}</li>" for c in careers) + "</ul>"
113
+ education = course.get('required_education', '')
114
+ if education: details += f"<p>✅ <b>Entry Requirements:</b> {education}</p>"
115
+ related = course.get('related_courses', [])
116
+ if related: details += "<b>📚 Key Subjects You'll Study:</b><ul>" + "".join(f"<li>{s}</li>" for s in related) + "</ul>"
117
+ details += "</div>"
118
+ return details
119
+
120
+ def format_comparison(courses):
121
+ if not courses: return "<div class='details-card'><p>I couldn't find any valid courses to compare. Please check the names and try again.</p></div>"
122
+ table_style = "width:100%;border-collapse:collapse;text-align:left;"
123
+ th_style = "border-bottom:2px solid #dee2e6;padding:12px;font-size:1rem;"
124
+ td_style = "border-bottom:1px solid #dee2e6;padding:12px;vertical-align:top;"
125
+ html = f"<div class='details-card'><table style='{table_style}'><thead><tr><th style='{th_style}'>Feature</th>"
126
+ for course in courses:
127
+ course_name = course.get('course')
128
+ html += f"<th style='{th_style}'><div class='clickable-card' data-action='quick_reply' data-value='{course_name}' style='padding:0; margin:0; text-align:left;'>{course_name}</div></th>"
129
+ html += "</tr></thead><tbody>"
130
+ html += f"<tr><td style='{td_style}'><b>💼 Careers</b></td>"
131
+ for course in courses: html += f"<td style='{td_style}'>{', '.join(course.get('possible_careers', ['N/A']))}</td>"
132
+ html += "</tr><tr><td style='{td_style}'><b>✅ Requirements</b></td>"
133
+ for course in courses: html += f"<td style='{td_style}'>{course.get('required_education', 'N/A')}</td>"
134
+ html += "</tr><tr><td style='{td_style}'><b>📚 Key Subjects</b></td>"
135
+ for course in courses: html += f"<td style='{td_style}'>{', '.join(course.get('related_courses', ['N/A']))}</td>"
136
+ html += "</tr><tr><td style='{td_style}'><b>🛠️ Core Skills</b></td>"
137
+ for course in courses:
138
+ skills = course.get('tags', {}).get('skills', ['N/A'])
139
+ html += f"<td style='{td_style}'>{', '.join(s.capitalize() for s in skills)}</td>"
140
+ html += "</tr></tbody></table>"
141
+ html += "<p style='font-size:0.85rem; text-align:center; margin-top:1rem; opacity:0.8;'>You can click on a course title in the table above for a detailed view.</p></div>"
142
+ return html
143
+
144
+ def get_recommendations(answers, courses):
145
+ profile, persona = build_user_profile(answers)
146
+ if not any(profile.values()): return "", []
147
+ user_profile_text = " ".join(set().union(*profile.values()))
148
+ user_emb = model.encode(user_profile_text, convert_to_tensor=True) if user_profile_text.strip() else None
149
+ if user_emb is None: return "", []
150
+ scored_courses = []
151
+ for course in courses:
152
+ tags_obj = course.get("tags", {})
153
+ course_tags = set().union(*tags_obj.values()) if isinstance(tags_obj, dict) else set()
154
+ rich_course_text = f"{course.get('course', '')} {course.get('description', '')} {' '.join(course.get('possible_careers', []))} {' '.join(course_tags)}"
155
+ course_emb = model.encode(rich_course_text, convert_to_tensor=True)
156
+ semantic_score = float(util.cos_sim(user_emb, course_emb)[0][0])
157
+ heuristic_score = sum([0.3 if profile.get("general_interests", set()).intersection(course_tags) else 0, 0.15 if profile.get("subject_strengths", set()).intersection(course_tags) else 0, 0.1 if profile.get("stream", set()).intersection(course_tags) else 0, 0.15 if persona.intersection(course_tags) else 0])
158
+ penalty_score = sum([len(profile.get("subject_weaknesses", set()).intersection(course_tags)) * 0.2, 0.15 if persona.intersection(course.get("anti_tags", [])) else 0])
159
+ final_score = (semantic_score + heuristic_score - penalty_score) * 100
160
+ if final_score > 20: scored_courses.append((final_score, course))
161
+ scored_courses.sort(key=lambda x: x[0], reverse=True)
162
+ top_courses = scored_courses[:3]
163
+ raw_recs = [course for _, course in top_courses]
164
+ if not raw_recs: return "🤔 I couldn’t find a strong match. Would you like to try again?", []
165
+ response_html = "<div class='recommendation-container'><h4>🚀 Here are my top recommendations for you:</h4>"
166
+ for i, (_, course_data) in enumerate(top_courses):
167
+ response_html += f"<div class='recommendation-card clickable-card' data-action='details' data-value='{i+1}'><p><b>{i+1}. {course_data.get('course')}</b><br>{course_data.get('description', '')}</p></div>"
168
+ response_html += f"<div class='recommendation-card clickable-card compare-card' data-action='compare' data-value='compare'><p><b>⚖️ Compare Courses</b></p></div></div>"
169
+ return response_html, raw_recs
170
+
171
+ def next_question(answers):
172
+ for key, q in QUESTIONS.items():
173
+ if key not in answers: return key, q
174
+ return None, None
175
+
176
+ # --- Resume Analyser Helper Functions ---
177
+ def extract_text_from_file(file):
178
+ text = ""
179
+ filename = file.filename.lower()
180
+ if filename.endswith('.pdf'):
181
+ pdf_document = fitz.open(stream=file.read(), filetype="pdf")
182
+ for page in pdf_document:
183
+ text += page.get_text()
184
+ pdf_document.close()
185
+ elif filename.endswith('.docx'):
186
+ doc = docx.Document(file)
187
+ for para in doc.paragraphs:
188
+ text += para.text + "\n"
189
+ return text
190
+
191
+ def analyze_resume_and_suggest_jobs(resume_text):
192
+ """Analyzes resume, gives a score, summary, and suggests job titles in one API call."""
193
+ if not GEMINI_API_KEY:
194
+ return "<div class='college-card error-card'>Error: Gemini API key is not configured.</div>"
195
+
196
+ prompt = f"""
197
+ You are an expert career coach. Analyze the following resume text.
198
+ Your response must be a single JSON object with four keys:
199
+ 1. "person_name": A string containing the full name of the candidate found in the resume. If no name is clear, return an empty string.
200
+ 2. "overall_score": An integer score out of 100 for the resume's quality.
201
+ 3. "summary": A brief, encouraging 1-2 sentence summary of the resume.
202
+ 4. "job_titles": A list of 3-5 specific job titles the candidate is well-suited for based on their skills and experience.
203
+
204
+ Do not add any text before or after the JSON object.
205
+
206
+ Resume Text to analyze:
207
+ ---
208
+ {resume_text}
209
+ ---
210
+ """
211
+ try:
212
+ model = genai.GenerativeModel('gemini-1.5-pro-latest')
213
+ response = model.generate_content(prompt)
214
+ json_match = re.search(r'\{.*\}', response.text, re.DOTALL)
215
+ if not json_match:
216
+ raise ValueError("Invalid JSON response from API")
217
+
218
+ feedback = json.loads(json_match.group(0))
219
+
220
+ name = feedback.get('person_name', '').strip()
221
+
222
+ html_response = "<div class='details-card'>"
223
+ if name:
224
+ html_response += f"<h3>📝 Resume Analysis for {name}</h3>"
225
+ else:
226
+ html_response += f"<h3>📝 Resume Analysis</h3>"
227
+
228
+ html_response += f"<p><b>Overall Score:</b> {feedback.get('overall_score', 'N/A')}/100</p>"
229
+ html_response += f"<p><b>Summary:</b> <i>{feedback.get('summary', '')}</i></p>"
230
+ html_response += "<b>🚀 Potential Job Roles:</b><ul>" + "".join(f"<li>{title}</li>" for title in feedback.get('job_titles', [])) + "</ul>"
231
+ html_response += "</div>"
232
+ return html_response
233
+
234
+ except Exception as e:
235
+ print(f"\n---!!! GEMINI API ERROR !!!---\n{e}\n-----------------------------\n")
236
+ logging.error(f"Gemini API Error: {e}")
237
+ error_message = ("Sorry, the analysis failed. This is often an API key issue. "
238
+ "Please check the terminal where you ran `python app.py` for the specific error message.")
239
+ return f"<div class='college-card error-card'>{error_message}</div>"
240
+
241
+ # --- Flask Routes ---
242
+ @app.route("/")
243
+ def index():
244
+ return render_template("index.html")
245
+
246
+ @app.route("/upload_resume", methods=["POST"])
247
+ def upload_resume():
248
+ if 'resume_file' not in request.files:
249
+ return jsonify({"error": "No file part"}), 400
250
+ file = request.files['resume_file']
251
+ if file.filename == '':
252
+ return jsonify({"error": "No selected file"}), 400
253
+
254
+ if file and (file.filename.lower().endswith('.pdf') or file.filename.lower().endswith('.docx')):
255
+ try:
256
+ resume_text = extract_text_from_file(file)
257
+ if not resume_text.strip():
258
+ return jsonify({"response": "<div class='college-card error-card'>The uploaded file seems to be empty.</div>"})
259
+
260
+ feedback_html = analyze_resume_and_suggest_jobs(resume_text)
261
+
262
+ return jsonify({"response": feedback_html})
263
+ except Exception as e:
264
+ logging.error(f"Resume Upload Error: {e}")
265
+ return jsonify({"response": "<div class='college-card error-card'>Sorry, an error occurred while processing your file.</div>"})
266
+
267
+ return jsonify({"error": "Invalid file type. Please upload a PDF or DOCX file."}), 400
268
+
269
+ @app.route("/chat", methods=["POST"])
270
+ def chat():
271
+ data = request.get_json()
272
+ msg = data.get("message", "").strip()
273
+ convo = data.get("conversation", {})
274
+ bot_response = ""
275
+
276
+ if not convo:
277
+ convo = {"state": "awaiting_initial_action", "answers": {}}
278
+ bot_response = "Welcome to CareerPal! You can type `start` to begin a personalized guidance session, or select a specific tool from the panel on the left."
279
+ logging.info("--- NEW SESSION INITIALIZED ---")
280
+ return jsonify({"response": bot_response, "conversation": convo})
281
+
282
+ current_state = convo.get("state", "awaiting_initial_action")
283
+ msg_lower = msg.lower()
284
+ logging.info(f"STATE: {current_state} | USER: {msg}")
285
+
286
+ feature_commands = ["personalized guidance", "compare courses", "college location finder", "resume analyser"]
287
+ if msg_lower in feature_commands:
288
+ current_state = "awaiting_initial_action"
289
+
290
+ if current_state == "awaiting_initial_action":
291
+ if msg_lower == "start" or msg_lower == "personalized guidance":
292
+ convo["state"] = "asking_questions"
293
+ convo["answers"] = {}
294
+ key, question = next_question(convo["answers"])
295
+ bot_response = f"Great, let's find your perfect career path! I'll ask a few questions to get started.<br><br>{question}"
296
+ elif msg_lower == "compare courses":
297
+ convo["state"] = "awaiting_compare_confirmation"
298
+ bot_response = "Do you have specific courses in mind to compare?<div class='quick-reply-container'><div class='quick-reply-button clickable-card' data-action='quick_reply' data-value='Yes'>Yes</div><div class='quick-reply-button clickable-card' data-action='quick_reply' data-value='No'>No</div></div>"
299
+ elif msg_lower == "college location finder":
300
+ convo["state"] = "awaiting_course_for_college_search"
301
+ bot_response = "Happy to help you find colleges! What is the name of the course you're interested in?"
302
+ elif msg_lower == "resume analyser":
303
+ convo["state"] = "awaiting_resume_upload"
304
+ bot_response = "Great! Please upload your resume (PDF or DOCX format) using the upload button below."
305
+ elif msg_lower == 'end chat':
306
+ bot_response = "Sure. Would you like to leave some feedback about your experience?<div class='quick-reply-container'><div class='quick-reply-button clickable-card' data-action='quick_reply' data-value='Yes'>👍 Yes</div><div class='quick-reply-button clickable-card' data-action='quick_reply' data-value='No'>👎 No</div></div>"
307
+ convo['state'] = 'awaiting_end_confirmation'
308
+ else:
309
+ bot_response = "Sorry, I didn't understand. You can type `start` or select a feature from the panel."
310
+
311
+ elif current_state == "asking_questions":
312
+ last_key, _ = next_question(convo["answers"])
313
+ if last_key:
314
+ convo["answers"][last_key] = msg
315
+ parsed_tags = parse_input(msg)
316
+ cleaned = ", ".join(tag.capitalize() for tag in parsed_tags) or msg
317
+ if last_key == "subject_weaknesses": bot_response = f"👌 Got it — I’ll stay away from careers heavy in {cleaned}. "
318
+ elif last_key == "subject_strengths": bot_response = f"🔥 Nice! Being strong in {cleaned} is a great asset. "
319
+ elif last_key == "interest_activities": bot_response = f"😎 Cool! Enjoying {cleaned} gives me clues about your personality. "
320
+ elif last_key == "general_interests": bot_response = f"👍 That's insightful! An interest in {cleaned} helps narrow down the options. "
321
+ else: bot_response = "✅ Okay, noted. "
322
+ next_key, next_q = next_question(convo["answers"])
323
+ if next_q:
324
+ bot_response += next_q
325
+ else:
326
+ bot_response, recs = get_recommendations(convo["answers"], COURSE_DATA)
327
+ if recs:
328
+ convo["last_recommendations"] = recs
329
+ convo["state"] = "awaiting_more_details"
330
+ bot_response += "<br>Click a course for more details, compare, or end the session."
331
+ bot_response += "<div class='quick-reply-container'><div class='quick-reply-button clickable-card' data-action='quick_reply' data-value='End Chat'>🚪 End Chat</div></div>"
332
+ else:
333
+ convo["state"] = "awaiting_initial_action"
334
+
335
+ elif current_state == "awaiting_compare_confirmation":
336
+ if msg_lower == 'yes':
337
+ convo["state"] = "awaiting_course_names_for_compare"
338
+ bot_response = "Please enter up to 3 course names, separated by commas."
339
+ else:
340
+ convo["state"] = "asking_questions"
341
+ convo["answers"] = {}
342
+ key, question = next_question(convo["answers"])
343
+ bot_response = f"No problem! Let's find some courses for you first.<br><br>{question}"
344
+
345
+ elif current_state == "awaiting_course_names_for_compare":
346
+ user_courses = [name.strip() for name in msg_lower.split(',')[:3]]
347
+ matched_courses = []
348
+ course_titles = [c['course'] for c in COURSE_DATA]
349
+ for user_course in user_courses:
350
+ best_match, score, _ = process.extractOne(user_course, course_titles, scorer=fuzz.token_set_ratio, processor=preprocess_text)
351
+ if score > 85:
352
+ matched_courses.append(next(c for c in COURSE_DATA if c['course'] == best_match))
353
+
354
+ bot_response = format_comparison(matched_courses)
355
+ convo["last_recommendations"] = matched_courses
356
+ convo["state"] = "awaiting_more_details"
357
+ bot_response += "<div class='quick-reply-container'><div class='quick-reply-button clickable-card' data-action='quick_reply' data-value='End Chat'>🚪 End Chat</div></div>"
358
+
359
+ elif current_state == "awaiting_course_for_college_search":
360
+ course_titles = [c['course'] for c in COURSE_DATA]
361
+ best_match, score, _ = process.extractOne(msg_lower, course_titles, scorer=fuzz.token_set_ratio, processor=preprocess_text)
362
+
363
+ if score > 85:
364
+ convo["course_for_college_search"] = best_match
365
+ convo["state"] = "awaiting_pincode"
366
+ bot_response = f"Okay, searching for colleges offering '<b>{best_match}</b>'. Please provide your 6-digit area PIN code."
367
+ else:
368
+ bot_response = "I couldn't find a clear match for that course. Could you please try rephrasing or be more specific?"
369
+ convo["state"] = "awaiting_course_for_college_search"
370
+
371
+ elif current_state == "awaiting_pincode":
372
+ if re.match(r"^\d{6}$", msg):
373
+ course_name = convo.get("course_for_college_search", "this course")
374
+ bot_response, _ = find_nearby_colleges(course_name, msg)
375
+ bot_response += "<div class='quick-reply-container'><div class='quick-reply-button clickable-card' data-action='quick_reply' data-value='College Location Finder'>🔎 Search Again</div><div class='quick-reply-button clickable-card' data-action='quick_reply' data-value='End Chat'>🚪 End Chat</div></div>"
376
+ convo["state"] = "awaiting_initial_action"
377
+ else:
378
+ bot_response = "That doesn't seem like a valid 6-digit PIN code. Please try again."
379
+
380
+ elif current_state == "awaiting_more_details":
381
+ recs = convo.get("last_recommendations", [])
382
+ if msg_lower == 'end chat':
383
+ bot_response = "Sure. Would you like to leave some feedback about your experience?<div class='quick-reply-container'><div class='quick-reply-button clickable-card' data-action='quick_reply' data-value='Yes'>👍 Yes</div><div class='quick-reply-button clickable-card' data-action='quick_reply' data-value='No'>👎 No</div></div>"
384
+ convo['state'] = 'awaiting_end_confirmation'
385
+ elif msg_lower == 'compare':
386
+ bot_response = format_comparison(recs)
387
+ bot_response += "<div class='quick-reply-container'><div class='quick-reply-button clickable-card' data-action='quick_reply' data-value='End Chat'>🚪 End Chat</div></div>"
388
+ else:
389
+ chosen_course = None
390
+ if msg.isdigit() and 1 <= int(msg) <= len(recs):
391
+ chosen_course = recs[int(msg) - 1]
392
+ else:
393
+ course_titles = [r.get('course', '') for r in recs]
394
+ best_match, score, _ = process.extractOne(msg, course_titles, scorer=fuzz.ratio)
395
+ if score > 70: chosen_course = next((r for r in recs if r.get('course') == best_match), None)
396
+
397
+ if chosen_course:
398
+ bot_response = format_course_details(chosen_course)
399
+ convo["course_for_college_search"] = chosen_course.get('course')
400
+ bot_response += "<br><br>Would you like to find nearby colleges for this course?<div class='quick-reply-container'><div class='quick-reply-button clickable-card' data-action='quick_reply' data-value='Yes'>👍 Yes</div><div class='quick-reply-button clickable-card' data-action='quick_reply' data-value='No'>👎 No</div></div>"
401
+ convo['state'] = 'awaiting_college_search_confirmation'
402
+ else:
403
+ bot_response = "Sorry, I didn't recognize that selection. Please choose an option from your recommendations."
404
+
405
+ elif current_state == 'awaiting_college_search_confirmation':
406
+ if 'yes' in msg_lower:
407
+ bot_response = "Great! Please provide your 6-digit PIN code."
408
+ convo['state'] = 'awaiting_pincode'
409
+ else:
410
+ bot_response = "No problem. You can explore other recommendations, compare courses, or select a new feature from the left panel."
411
+ bot_response += "<div class='quick-reply-container'><div class='quick-reply-button clickable-card' data-action='quick_reply' data-value='End Chat'>🚪 End Chat</div></div>"
412
+ convo['state'] = 'awaiting_more_details'
413
+
414
+ elif current_state == 'awaiting_end_confirmation':
415
+ if 'yes' in msg_lower:
416
+ bot_response = "I'd love to hear your thoughts. How was your experience?"
417
+ convo['state'] = 'awaiting_feedback'
418
+ else:
419
+ bot_response = "No problem! It was great helping you."
420
+ bot_response += "<div class='quick-reply-container'><div class='quick-reply-button clickable-card' data-action='restart'>🔄 Start Over</div></div>"
421
+ convo['state'] = 'session_ended'
422
+
423
+ elif current_state == 'awaiting_feedback':
424
+ feedback_logger.info(f"FEEDBACK: {msg}")
425
+ bot_response = "Thank you for your feedback!"
426
+ bot_response += "<div class='quick-reply-container'><div class='quick-reply-button clickable-card' data-action='restart'>🔄 Start Over</div></div>"
427
+ convo['state'] = 'session_ended'
428
+
429
+ logging.info(f"BOT: {re.sub('<[^<]+?>', ' ', bot_response).strip()}")
430
+ return jsonify({"response": bot_response, "conversation": convo})
431
+
432
+ if __name__ == "__main__":
433
+ app.run(debug=True)
build.sh ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env bash
2
+ # exit on error
3
+ set -o errexit
4
+
5
+ pip install -r requirements.txt
6
+
7
+ python -c "from sentence_transformers import SentenceTransformer; SentenceTransformer('sentence-transformers/all-MiniLM-L6-v2')"
careers.json ADDED
The diff for this file is too large to render. See raw diff
 
careers_cleaned.json ADDED
The diff for this file is too large to render. See raw diff
 
chat_log.txt ADDED
The diff for this file is too large to render. See raw diff
 
colleges.json ADDED
@@ -0,0 +1,525 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [
2
+ {
3
+ "name": "A. P. Shah Institute of Technology, Thane",
4
+ "pincode": "400615",
5
+ "courses_offered": [
6
+ "B.Tech in Computer Science Engineering (CSE)",
7
+ "B.Tech in Mechanical Engineering"
8
+ ]
9
+ },
10
+ {
11
+ "name": "Atharva College of Engineering, Malad",
12
+ "pincode": "400095",
13
+ "courses_offered": [
14
+ "B.Tech in Computer Science Engineering (CSE)",
15
+ "B.Tech in Information Technology (IT)",
16
+ "B.Tech in Electronics and Communication Engineering (ECE)"
17
+ ]
18
+ },
19
+ {
20
+ "name": "Bharati Vidyapeeth College of Engineering, Navi Mumbai",
21
+ "pincode": "400614",
22
+ "courses_offered": [
23
+ "B.Tech in Computer Science Engineering (CSE)",
24
+ "B.Tech in Chemical Engineering",
25
+ "B.Tech in Information Technology (IT)",
26
+ "B.Tech in Instrumentation Engineering"
27
+ ]
28
+ },
29
+ {
30
+ "name": "Bombay College of Pharmacy, Kalina",
31
+ "pincode": "400098",
32
+ "courses_offered": [
33
+ "Bachelor of Pharmacy (B.Pharm)"
34
+ ]
35
+ },
36
+ {
37
+ "name": "Datta Meghe College of Engineering, Airoli",
38
+ "pincode": "400708",
39
+ "courses_offered": [
40
+ "B.Tech in Computer Science Engineering (CSE)",
41
+ "B.Tech in Civil Engineering",
42
+ "B.Tech in Chemical Engineering"
43
+ ]
44
+ },
45
+ {
46
+ "name": "Don Bosco Institute of Technology, Kurla",
47
+ "pincode": "400070",
48
+ "courses_offered": [
49
+ "B.Tech in Computer Science Engineering (CSE)",
50
+ "B.Tech in Information Technology (IT)",
51
+ "B.Tech in Mechanical Engineering"
52
+ ]
53
+ },
54
+ {
55
+ "name": "Dwarkadas J. Sanghvi College of Engineering, Vile Parle",
56
+ "pincode": "400056",
57
+ "courses_offered": [
58
+ "B.Tech in Computer Science Engineering (CSE)",
59
+ "B.Tech in Information Technology (IT)",
60
+ "B.Tech in Artificial Intelligence & Machine Learning",
61
+ "B.Tech in Mechanical Engineering"
62
+ ]
63
+ },
64
+ {
65
+ "name": "Fr. C. Rodrigues Institute of Technology, Vashi",
66
+ "pincode": "400703",
67
+ "courses_offered": [
68
+ "B.Tech in Computer Science Engineering (CSE)",
69
+ "B.Tech in Mechanical Engineering",
70
+ "B.Tech in Electronics and Communication Engineering (ECE)"
71
+ ]
72
+ },
73
+ {
74
+ "name": "Government Law College, Churchgate",
75
+ "pincode": "400020",
76
+ "courses_offered": [
77
+ "BA LL.B. (Integrated)",
78
+ "Bachelor of Legislative Law (LLB - 3 year)"
79
+ ]
80
+ },
81
+ {
82
+ "name": "H.R. College of Commerce and Economics, Churchgate",
83
+ "pincode": "400020",
84
+ "courses_offered": [
85
+ "B.Com (General)",
86
+ "BMS (Bachelor of Management Studies)",
87
+ "BMM (Bachelor of Mass Media)",
88
+ "BBA in Digital Marketing"
89
+ ]
90
+ },
91
+ {
92
+ "name": "IIT Bombay, Powai",
93
+ "pincode": "400076",
94
+ "courses_offered": [
95
+ "B.Tech in Computer Science Engineering (CSE)",
96
+ "B.Tech in Mechanical Engineering",
97
+ "B.Tech in Electrical Engineering",
98
+ "B.Sc. in Physics",
99
+ "B.Tech in Chemical Engineering"
100
+ ]
101
+ },
102
+ {
103
+ "name": "Institute of Chemical Technology (ICT), Matunga",
104
+ "pincode": "400019",
105
+ "courses_offered": [
106
+ "B.Tech in Chemical Engineering",
107
+ "Bachelor of Pharmacy (B.Pharm)",
108
+ "B.Sc. in Food Technology"
109
+ ]
110
+ },
111
+ {
112
+ "name": "Jai Hind College, Churchgate",
113
+ "pincode": "400020",
114
+ "courses_offered": [
115
+ "B.Sc. in Computer Science",
116
+ "Bachelor of Business Administration (BBA)",
117
+ "BMM (Bachelor of Mass Media)",
118
+ "B.Voc in Retail Management"
119
+ ]
120
+ },
121
+ {
122
+ "name": "K. J. Somaiya College of Arts and Commerce, Vidyavihar",
123
+ "pincode": "400077",
124
+ "courses_offered": [
125
+ "B.Com (General)",
126
+ "BMS (Bachelor of Management Studies)",
127
+ "BA in Economics",
128
+ "BMM (Bachelor of Mass Media)"
129
+ ]
130
+ },
131
+ {
132
+ "name": "K. J. Somaiya College of Engineering, Vidyavihar",
133
+ "pincode": "400077",
134
+ "courses_offered": [
135
+ "B.Tech in Computer Science Engineering (CSE)",
136
+ "B.Tech in Information Technology (IT)",
137
+ "B.Tech in Robotics & Automation"
138
+ ]
139
+ },
140
+ {
141
+ "name": "KC College of Engineering, Thane",
142
+ "pincode": "400603",
143
+ "courses_offered": [
144
+ "B.Tech in Computer Science Engineering (CSE)",
145
+ "B.Tech in Information Technology (IT)"
146
+ ]
147
+ },
148
+ {
149
+ "name": "Kishinchand Chellaram College (KC College), Churchgate",
150
+ "pincode": "400020",
151
+ "courses_offered": [
152
+ "B.Com (General)",
153
+ "B.Sc. in Information Technology (IT)",
154
+ "BMM (Bachelor of Mass Media)"
155
+ ]
156
+ },
157
+ {
158
+ "name": "Lokmanya Tilak Municipal Medical College, Sion",
159
+ "pincode": "400022",
160
+ "courses_offered": [
161
+ "Bachelor of Physiotherapy (BPT)",
162
+ "Bachelor of Occupational Therapy (BOT)"
163
+ ]
164
+ },
165
+ {
166
+ "name": "Mithibai College of Arts, Vile Parle",
167
+ "pincode": "400056",
168
+ "courses_offered": [
169
+ "BMM (Bachelor of Mass Media)",
170
+ "Bachelor of Arts (BA) in Psychology",
171
+ "B.Com (General)",
172
+ "BMS (Bachelor of Management Studies)",
173
+ "BA in History"
174
+ ]
175
+ },
176
+ {
177
+ "name": "NMIMS (Deemed to be University), Vile Parle",
178
+ "pincode": "400056",
179
+ "courses_offered": [
180
+ "Bachelor of Business Administration (BBA)",
181
+ "BMS (Bachelor of Management Studies)",
182
+ "B.Com (General)",
183
+ "BA in Liberal Arts",
184
+ "BBA in Digital Marketing"
185
+ ]
186
+ },
187
+ {
188
+ "name": "Nagindas Khandwala College of Commerce, Arts & Management, Malad",
189
+ "pincode": "400064",
190
+ "courses_offered": [
191
+ "B.Com (General)",
192
+ "BMS (Bachelor of Management Studies)",
193
+ "B.Sc. in Information Technology (IT)"
194
+ ]
195
+ },
196
+ {
197
+ "name": "Narsee Monjee College of Commerce and Economics, Vile Parle",
198
+ "pincode": "400056",
199
+ "courses_offered": [
200
+ "B.Com (General)",
201
+ "BMS (Bachelor of Management Studies)"
202
+ ]
203
+ },
204
+ {
205
+ "name": "Patkar-Varde College, Goregaon",
206
+ "pincode": "400062",
207
+ "courses_offered": [
208
+ "B.Com (General)",
209
+ "BMS (Bachelor of Management Studies)",
210
+ "B.Sc. in Computer Science"
211
+ ]
212
+ },
213
+ {
214
+ "name": "Pillai College of Architecture, Panvel",
215
+ "pincode": "410206",
216
+ "courses_offered": [
217
+ "Bachelor of Architecture (B.Arch)"
218
+ ]
219
+ },
220
+ {
221
+ "name": "Pillai College of Arts, Commerce and Science, Panvel",
222
+ "pincode": "410206",
223
+ "courses_offered": [
224
+ "B.Com (General)",
225
+ "BMS (Bachelor of Management Studies)",
226
+ "BMM (Bachelor of Mass Media)"
227
+ ]
228
+ },
229
+ {
230
+ "name": "Pillai College of Engineering, Panvel",
231
+ "pincode": "410206",
232
+ "courses_offered": [
233
+ "B.Tech in Computer Science Engineering (CSE)",
234
+ "B.Tech in Mechanical Engineering",
235
+ "B.Tech in Robotics & Automation"
236
+ ]
237
+ },
238
+ {
239
+ "name": "Pravin Dalal School of Entrepreneurship (NMIMS), Vile Parle",
240
+ "pincode": "400056",
241
+ "courses_offered": [
242
+ "Bachelor of Business Administration (BBA)"
243
+ ]
244
+ },
245
+ {
246
+ "name": "R. A. Podar College of Commerce & Economics, Matunga",
247
+ "pincode": "400019",
248
+ "courses_offered": [
249
+ "B.Com (General)",
250
+ "BMS (Bachelor of Management Studies)"
251
+ ]
252
+ },
253
+ {
254
+ "name": "R. D. National College, Bandra",
255
+ "pincode": "400050",
256
+ "courses_offered": [
257
+ "B.Com (General)",
258
+ "BMS (Bachelor of Management Studies)"
259
+ ]
260
+ },
261
+ {
262
+ "name": "Ramnarain Ruia Autonomous College, Matunga",
263
+ "pincode": "400019",
264
+ "courses_offered": [
265
+ "B.Sc. in Physics",
266
+ "B.Sc. in Statistics",
267
+ "B.Sc. in Microbiology",
268
+ "BA in History"
269
+ ]
270
+ },
271
+ {
272
+ "name": "Ramniranjan Jhunjhunwala College, Ghatkopar",
273
+ "pincode": "400086",
274
+ "courses_offered": [
275
+ "B.Sc. in Data Science & Artificial Intelligence",
276
+ "B.Sc. in Computer Science",
277
+ "B.Com (General)",
278
+ "BMS (Bachelor of Management Studies)",
279
+ "B.Sc. in Biotechnology (B.Sc. Biotech)"
280
+ ]
281
+ },
282
+ {
283
+ "name": "Ramrao Adik Institute of Technology (RAIT), Nerul",
284
+ "pincode": "400706",
285
+ "courses_offered": [
286
+ "B.Tech in Computer Science Engineering (CSE)",
287
+ "B.Tech in Information Technology (IT)",
288
+ "B.Tech in Electronics and Communication Engineering (ECE)"
289
+ ]
290
+ },
291
+ {
292
+ "name": "Rizvi College of Arts, Science & Commerce, Bandra",
293
+ "pincode": "400050",
294
+ "courses_offered": [
295
+ "BMM (Bachelor of Mass Media)",
296
+ "BMS (Bachelor of Management Studies)",
297
+ "B.Sc. in Information Technology (IT)",
298
+ "Bachelor of Commerce (B.Com)"
299
+ ]
300
+ },
301
+ {
302
+ "name": "Rizvi College of Engineering, Bandra",
303
+ "pincode": "400050",
304
+ "courses_offered": [
305
+ "B.Tech in Civil Engineering",
306
+ "B.Tech in Mechanical Engineering",
307
+ "B.Tech in Biotechnology"
308
+ ]
309
+ },
310
+ {
311
+ "name": "SIES College of Arts, Science, and Commerce, Sion",
312
+ "pincode": "400022",
313
+ "courses_offered": [
314
+ "B.Sc. in Computer Science",
315
+ "B.Com (General)",
316
+ "BA in Economics"
317
+ ]
318
+ },
319
+ {
320
+ "name": "SIES College of Commerce & Economics, Sion",
321
+ "pincode": "400022",
322
+ "courses_offered": [
323
+ "Bachelor of Commerce (B.Com)",
324
+ "BMS (Bachelor of Management Studies)",
325
+ "B.Sc. in Information Technology (IT)"
326
+ ]
327
+ },
328
+ {
329
+ "name": "SIES Graduate School of Technology, Nerul",
330
+ "pincode": "400706",
331
+ "courses_offered": [
332
+ "B.Tech in Computer Science Engineering (CSE)",
333
+ "B.Tech in Information Technology (IT)",
334
+ "B.Tech in Robotics & Automation"
335
+ ]
336
+ },
337
+ {
338
+ "name": "SNDT Women's University, Churchgate",
339
+ "pincode": "400020",
340
+ "courses_offered": [
341
+ "Bachelor of Arts (BA) in Psychology",
342
+ "B.Des in Fashion Design",
343
+ "Bachelor of Library Science (B.Lib.Sc.)"
344
+ ]
345
+ },
346
+ {
347
+ "name": "Sardar Patel Institute of Technology (SPIT), Andheri",
348
+ "pincode": "400058",
349
+ "courses_offered": [
350
+ "B.Tech in Computer Science Engineering (CSE)",
351
+ "B.Tech in Information Technology (IT)",
352
+ "B.Tech in Electronics and Communication Engineering (ECE)"
353
+ ]
354
+ },
355
+ {
356
+ "name": "Sathaye College, Vile Parle",
357
+ "pincode": "400057",
358
+ "courses_offered": [
359
+ "B.Sc. in Physics",
360
+ "BA in History",
361
+ "B.Com (General)"
362
+ ]
363
+ },
364
+ {
365
+ "name": "Sir J. J. College of Architecture, Fort",
366
+ "pincode": "400001",
367
+ "courses_offered": [
368
+ "Bachelor of Architecture (B.Arch)"
369
+ ]
370
+ },
371
+ {
372
+ "name": "Sir J. J. Institute of Applied Art, Fort",
373
+ "pincode": "400001",
374
+ "courses_offered": [
375
+ "BFA in Applied Arts",
376
+ "Bachelor of Fine Arts (BFA)"
377
+ ]
378
+ },
379
+ {
380
+ "name": "Sophia College for Women, Breach Candy",
381
+ "pincode": "400026",
382
+ "courses_offered": [
383
+ "Bachelor of Arts (BA) in Psychology",
384
+ "B.Sc. in Microbiology",
385
+ "BMM (Bachelor of Mass Media)",
386
+ "B.Des in Fashion Communication"
387
+ ]
388
+ },
389
+ {
390
+ "name": "St. Andrew's College of Arts, Science and Commerce, Bandra",
391
+ "pincode": "400050",
392
+ "courses_offered": [
393
+ "B.Com (General)",
394
+ "Bachelor of Arts (BA) in Psychology",
395
+ "BMM (Bachelor of Mass Media)"
396
+ ]
397
+ },
398
+ {
399
+ "name": "St. Xavier's College (Autonomous), Fort",
400
+ "pincode": "400001",
401
+ "courses_offered": [
402
+ "BA in Economics",
403
+ "B.Sc. in Statistics",
404
+ "BMM (Bachelor of Mass Media)",
405
+ "B.Sc. in Geology",
406
+ "B.Sc. in Biotechnology (B.Sc. Biotech)"
407
+ ]
408
+ },
409
+ {
410
+ "name": "Sydenham College of Commerce and Economics, Churchgate",
411
+ "pincode": "400020",
412
+ "courses_offered": [
413
+ "Bachelor of Commerce (B.Com)",
414
+ "BMS (Bachelor of Management Studies)"
415
+ ]
416
+ },
417
+ {
418
+ "name": "Terna Engineering College, Nerul",
419
+ "pincode": "400706",
420
+ "courses_offered": [
421
+ "B.Tech in Computer Science Engineering (CSE)",
422
+ "B.Tech in Civil Engineering",
423
+ "B.Tech in Artificial Intelligence & Machine Learning"
424
+ ]
425
+ },
426
+ {
427
+ "name": "Thadomal Shahani Engineering College, Bandra",
428
+ "pincode": "400050",
429
+ "courses_offered": [
430
+ "B.Tech in Computer Science Engineering (CSE)",
431
+ "B.Tech in Information Technology (IT)",
432
+ "B.Tech in Artificial Intelligence & Machine Learning"
433
+ ]
434
+ },
435
+ {
436
+ "name": "Thakur College of Engineering and Technology, Kandivali",
437
+ "pincode": "400101",
438
+ "courses_offered": [
439
+ "B.Tech in Computer Science Engineering (CSE)",
440
+ "B.Tech in Information Technology (IT)",
441
+ "B.Tech in Artificial Intelligence & Machine Learning"
442
+ ]
443
+ },
444
+ {
445
+ "name": "Thakur College of Science and Commerce, Kandivali",
446
+ "pincode": "400101",
447
+ "courses_offered": [
448
+ "B.Sc. in Computer Science",
449
+ "B.Com (General)",
450
+ "BMS (Bachelor of Management Studies)"
451
+ ]
452
+ },
453
+ {
454
+ "name": "Usha Pravin Gandhi College of Arts, Science and Commerce, Vile Parle",
455
+ "pincode": "400056",
456
+ "courses_offered": [
457
+ "BMM (Bachelor of Mass Media)",
458
+ "B.Sc. in Information Technology (IT)"
459
+ ]
460
+ },
461
+ {
462
+ "name": "V.E.S. College of Arts, Science and Commerce, Chembur",
463
+ "pincode": "400071",
464
+ "courses_offered": [
465
+ "Bachelor of Arts (BA) in Psychology",
466
+ "Bachelor of Commerce (B.Com)",
467
+ "B.Sc. in Computer Science",
468
+ "B.Sc. in Information Technology (IT)",
469
+ "BMS (Bachelor of Management Studies)"
470
+ ]
471
+ },
472
+ {
473
+ "name": "Veermata Jijabai Technological Institute (VJTI), Matunga",
474
+ "pincode": "400019",
475
+ "courses_offered": [
476
+ "B.Tech in Computer Science Engineering (CSE)",
477
+ "B.Tech in Information Technology (IT)",
478
+ "B.Tech in Civil Engineering",
479
+ "B.Tech in Mechanical Engineering"
480
+ ]
481
+ },
482
+ {
483
+ "name": "Vidyalankar Institute of Technology, Wadala",
484
+ "pincode": "400037",
485
+ "courses_offered": [
486
+ "B.Tech in Computer Science Engineering (CSE)",
487
+ "B.Tech in Information Technology (IT)"
488
+ ]
489
+ },
490
+ {
491
+ "name": "Vivekanand Education Society's Institute of Technology (VESIT), Chembur",
492
+ "pincode": "400074",
493
+ "courses_offered": [
494
+ "B.Tech in Computer Science Engineering (CSE)",
495
+ "B.Tech in Information Technology (IT)",
496
+ "B.Tech in Robotics & Automation"
497
+ ]
498
+ },
499
+ {
500
+ "name": "Whistling Woods International, Goregaon",
501
+ "pincode": "400065",
502
+ "courses_offered": [
503
+ "B.Sc. in Animation & VFX",
504
+ "Bachelor in Film and Television Production",
505
+ "Diploma in Acting and Theatre"
506
+ ]
507
+ },
508
+ {
509
+ "name": "Wilson College, Chowpatty",
510
+ "pincode": "400007",
511
+ "courses_offered": [
512
+ "BMS (Bachelor of Management Studies)",
513
+ "BMM (Bachelor of Mass Media)",
514
+ "B.Sc. in Computer Science"
515
+ ]
516
+ },
517
+ {
518
+ "name": "Xavier Institute of Engineering, Mahim",
519
+ "pincode": "400016",
520
+ "courses_offered": [
521
+ "B.Tech in Computer Science Engineering (CSE)",
522
+ "B.Tech in Information Technology (IT)"
523
+ ]
524
+ }
525
+ ]
feedback.txt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ 2025-09-12 13:03:23 - FEEDBACK: Great, love it!!
2
+ 2025-09-12 18:50:30 - FEEDBACK: great
3
+ 2025-09-13 12:18:44 - FEEDBACK: Great
gunicorn ADDED
Binary file (3.69 kB). View file
 
pincodes.json ADDED
@@ -0,0 +1,130 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "400001": {"lat": 18.9220, "lon": 72.8319},
3
+ "400002": {"lat": 18.9482, "lon": 72.8315},
4
+ "400003": {"lat": 18.9663, "lon": 72.8393},
5
+ "400004": {"lat": 18.9632, "lon": 72.8223},
6
+ "400005": {"lat": 18.9100, "lon": 72.8169},
7
+ "400006": {"lat": 18.9560, "lon": 72.8161},
8
+ "400007": {"lat": 18.9576, "lon": 72.8129},
9
+ "400008": {"lat": 18.9734, "lon": 72.8314},
10
+ "400009": {"lat": 18.9792, "lon": 72.8357},
11
+ "400010": {"lat": 18.9904, "lon": 72.8385},
12
+ "400011": {"lat": 18.9800, "lon": 72.8273},
13
+ "400012": {"lat": 19.0069, "lon": 72.8427},
14
+ "400013": {"lat": 19.0021, "lon": 72.8530},
15
+ "400014": {"lat": 19.0195, "lon": 72.8420},
16
+ "400015": {"lat": 19.0202, "lon": 72.8312},
17
+ "400016": {"lat": 19.0234, "lon": 72.8425},
18
+ "400017": {"lat": 19.0354, "lon": 72.8617},
19
+ "400018": {"lat": 19.0136, "lon": 72.8202},
20
+ "400019": {"lat": 19.0213, "lon": 72.8559},
21
+ "400020": {"lat": 18.9333, "lon": 72.8257},
22
+ "400021": {"lat": 18.9388, "lon": 72.8378},
23
+ "400022": {"lat": 19.0445, "lon": 72.8682},
24
+ "400024": {"lat": 19.0556, "lon": 72.8781},
25
+ "400025": {"lat": 19.0121, "lon": 72.8253},
26
+ "400026": {"lat": 18.9599, "lon": 72.8095},
27
+ "400027": {"lat": 18.9840, "lon": 72.8436},
28
+ "400028": {"lat": 19.0250, "lon": 72.8305},
29
+ "400030": {"lat": 18.9950, "lon": 72.8203},
30
+ "400031": {"lat": 19.0264, "lon": 72.8672},
31
+ "400033": {"lat": 18.9868, "lon": 72.8491},
32
+ "400034": {"lat": 18.9740, "lon": 72.8174},
33
+ "400036": {"lat": 18.9529, "lon": 72.8049},
34
+ "400037": {"lat": 19.0345, "lon": 72.8763},
35
+ "400039": {"lat": 18.9152, "lon": 72.8291},
36
+ "400042": {"lat": 19.1417, "lon": 72.9378},
37
+ "400043": {"lat": 19.0333, "lon": 72.8450},
38
+ "400049": {"lat": 19.1083, "lon": 72.8275},
39
+ "400050": {"lat": 19.0547, "lon": 72.8405},
40
+ "400051": {"lat": 19.0652, "lon": 72.8587},
41
+ "400052": {"lat": 19.0768, "lon": 72.8415},
42
+ "400053": {"lat": 19.1102, "lon": 72.8594},
43
+ "400054": {"lat": 19.0837, "lon": 72.8322},
44
+ "400055": {"lat": 19.0760, "lon": 72.8777},
45
+ "400056": {"lat": 19.0884, "lon": 72.8373},
46
+ "400057": {"lat": 19.0829, "lon": 72.8465},
47
+ "400058": {"lat": 19.1235, "lon": 72.8285},
48
+ "400059": {"lat": 19.1432, "lon": 72.8690},
49
+ "400060": {"lat": 19.1554, "lon": 72.8521},
50
+ "400061": {"lat": 19.1293, "lon": 72.8090},
51
+ "400062": {"lat": 19.1678, "lon": 72.8436},
52
+ "400063": {"lat": 19.1695, "lon": 72.8295},
53
+ "400064": {"lat": 19.1864, "lon": 72.8398},
54
+ "400065": {"lat": 19.1695, "lon": 72.8295},
55
+ "400066": {"lat": 19.2062, "lon": 72.8711},
56
+ "400067": {"lat": 19.2086, "lon": 72.8436},
57
+ "400068": {"lat": 19.2555, "lon": 72.8529},
58
+ "400069": {"lat": 19.1430, "lon": 72.8488},
59
+ "400070": {"lat": 19.0729, "lon": 72.8942},
60
+ "400071": {"lat": 19.0435, "lon": 72.8890},
61
+ "400072": {"lat": 19.1085, "lon": 72.9082},
62
+ "400074": {"lat": 19.0468, "lon": 72.8841},
63
+ "400075": {"lat": 19.0818, "lon": 72.9150},
64
+ "400078": {"lat": 19.1285, "lon": 72.9238},
65
+ "400079": {"lat": 19.1008, "lon": 72.8447},
66
+ "400080": {"lat": 19.1722, "lon": 72.9425},
67
+ "400081": {"lat": 19.1833, "lon": 72.9510},
68
+ "400082": {"lat": 19.1601, "lon": 72.9460},
69
+ "400083": {"lat": 19.1002, "lon": 72.9262},
70
+ "400084": {"lat": 19.0885, "lon": 72.9220},
71
+ "400086": {"lat": 19.0851, "lon": 72.9092},
72
+ "400088": {"lat": 19.0558, "lon": 72.9090},
73
+ "400089": {"lat": 19.0343, "lon": 72.8953},
74
+ "400092": {"lat": 19.2270, "lon": 72.8494},
75
+ "400093": {"lat": 19.1309, "lon": 72.8791},
76
+ "400095": {"lat": 19.2007, "lon": 72.8340},
77
+ "400097": {"lat": 19.1912, "lon": 72.8760},
78
+ "400098": {"lat": 19.0818, "lon": 72.8643},
79
+ "400101": {"lat": 19.2288, "lon": 72.8568},
80
+ "400102": {"lat": 19.1517, "lon": 72.8190},
81
+ "400103": {"lat": 19.2458, "lon": 72.8270},
82
+ "400104": {"lat": 19.2555, "lon": 72.8529},
83
+ "400601": {"lat": 19.2183, "lon": 72.9781},
84
+ "400602": {"lat": 19.1944, "lon": 72.9757},
85
+ "400603": {"lat": 19.1764, "lon": 72.9689},
86
+ "400604": {"lat": 19.2294, "lon": 72.9730},
87
+ "400605": {"lat": 19.1578, "lon": 72.9698},
88
+ "400606": {"lat": 19.2560, "lon": 72.9702},
89
+ "400607": {"lat": 19.2882, "lon": 72.9805},
90
+ "400610": {"lat": 19.1500, "lon": 72.9900},
91
+ "400612": {"lat": 19.1983, "lon": 73.0416},
92
+ "400615": {"lat": 19.2711, "lon": 72.9734},
93
+ "400701": {"lat": 19.1235, "lon": 72.9930},
94
+ "400703": {"lat": 19.0734, "lon": 72.9991},
95
+ "400705": {"lat": 19.0730, "lon": 72.9972},
96
+ "400706": {"lat": 19.0330, "lon": 73.0181},
97
+ "400708": {"lat": 19.1175, "lon": 72.9868},
98
+ "400709": {"lat": 19.0880, "lon": 73.0135},
99
+ "400710": {"lat": 19.0200, "lon": 73.0600},
100
+ "401101": {"lat": 19.2898, "lon": 72.8538},
101
+ "401105": {"lat": 19.3000, "lon": 72.8700},
102
+ "401107": {"lat": 19.2825, "lon": 72.8690},
103
+ "401201": {"lat": 19.3879, "lon": 72.7533},
104
+ "401202": {"lat": 19.4623, "lon": 72.7933},
105
+ "401203": {"lat": 19.3833, "lon": 72.7917},
106
+ "401208": {"lat": 19.3200, "lon": 72.8300},
107
+ "401209": {"lat": 19.3500, "lon": 72.8500},
108
+ "401303": {"lat": 19.4414, "lon": 72.8252},
109
+ "401305": {"lat": 19.4172, "lon": 72.8211},
110
+ "401404": {"lat": 19.6891, "lon": 72.7958},
111
+ "410206": {"lat": 18.9952, "lon": 73.0805},
112
+ "410208": {"lat": 19.0311, "lon": 73.1207},
113
+ "410210": {"lat": 19.0371, "lon": 73.0645},
114
+ "421001": {"lat": 19.2200, "lon": 73.1800},
115
+ "421002": {"lat": 19.2230, "lon": 73.1610},
116
+ "421003": {"lat": 19.2132, "lon": 73.1850},
117
+ "421004": {"lat": 19.2155, "lon": 73.1970},
118
+ "421103": {"lat": 19.2430, "lon": 73.2081},
119
+ "421201": {"lat": 19.1764, "lon": 73.1111},
120
+ "421202": {"lat": 19.1600, "lon": 73.1100},
121
+ "421203": {"lat": 19.2032, "lon": 73.1368},
122
+ "421204": {"lat": 19.1868, "lon": 73.1500},
123
+ "421301": {"lat": 19.2312, "lon": 73.1895},
124
+ "421302": {"lat": 19.2941, "lon": 73.0799},
125
+ "421305": {"lat": 19.2882, "lon": 73.1255},
126
+ "421306": {"lat": 19.2023, "lon": 73.2355},
127
+ "421501": {"lat": 19.1678, "lon": 73.2509},
128
+ "421503": {"lat": 19.1970, "lon": 73.2872},
129
+ "421601": {"lat": 19.4580, "lon": 73.3235}
130
+ }
requirements.txt ADDED
Binary file (216 Bytes). View file
 
static/bot_avatar.png ADDED

Git LFS Details

  • SHA256: d86a4a6669a02fd917197abea198c2a17cb342441e3a63bc799f83fe669dc5ac
  • Pointer size: 131 Bytes
  • Size of remote file: 600 kB
static/logo_darktheme.png ADDED

Git LFS Details

  • SHA256: fb0a999bf8149b3edae88bdb45d19a014f20d921099ac6327e04496fd3389bae
  • Pointer size: 131 Bytes
  • Size of remote file: 127 kB
static/logo_lighttheme.png ADDED

Git LFS Details

  • SHA256: 57699f2a3f53e1c9e6c2f3e7d2fef7cf04fe36181dbe90fa13839e9753557562
  • Pointer size: 131 Bytes
  • Size of remote file: 129 kB
static/my_logo.png ADDED

Git LFS Details

  • SHA256: 74528d9c952f5b2ea271592a4925f9688a6a26a4e6e629d1e473d6bb94c9a84e
  • Pointer size: 131 Bytes
  • Size of remote file: 958 kB
static/script.js ADDED
@@ -0,0 +1,234 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ document.addEventListener("DOMContentLoaded", () => {
2
+ const chatForm = document.getElementById("chat-form");
3
+ const messageInput = document.getElementById("message-input");
4
+ const chatMessages = document.getElementById("chat-messages");
5
+ const themeToggle = document.getElementById("theme-toggle");
6
+ const sendButton = chatForm.querySelector("button[type='submit']");
7
+ const leftColumn = document.getElementById("left-column");
8
+ const uploadResumeButton = document.getElementById("upload-resume-button");
9
+ const resumeFileInput = document.getElementById("resume-file-input");
10
+ const refreshChatButton = document.getElementById("refresh-chat-button");
11
+ const menuToggle = document.getElementById("menu-toggle");
12
+
13
+ let conversationState = {};
14
+
15
+ const sendMessage = (messageText) => {
16
+ messageInput.value = messageText;
17
+ const submitEvent = new Event('submit', { bubbles: true, cancelable: true });
18
+ chatForm.dispatchEvent(submitEvent);
19
+ };
20
+
21
+ const uploadResumeFile = async (file) => {
22
+ if (!file) return;
23
+ if (file.size > 5 * 1024 * 1024) { // 5MB limit
24
+ addMessage("bot", "The selected file is too large. Please upload a file smaller than 5MB.");
25
+ return;
26
+ }
27
+
28
+ addMessage("user", `Uploading Resume: <i>${file.name}</i>`);
29
+ showTypingIndicator();
30
+
31
+ const formData = new FormData();
32
+ formData.append("resume_file", file);
33
+
34
+ try {
35
+ const response = await fetch("/upload_resume", {
36
+ method: "POST",
37
+ body: formData,
38
+ });
39
+
40
+ if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
41
+ const data = await response.json();
42
+
43
+ removeTypingIndicator();
44
+
45
+ if (data.response) {
46
+ let finalResponse = data.response;
47
+ finalResponse += "<div class='quick-reply-container'><div class='quick-reply-button clickable-card' data-action='quick_reply' data-value='Resume Analyser'>📄 Analyze Another Resume</div><div class='quick-reply-button clickable-card' data-action='quick_reply' data-value='End Chat'>🚪 End Chat</div></div>";
48
+ addMessage("bot", finalResponse);
49
+
50
+ conversationState.state = 'awaiting_initial_action';
51
+
52
+ } else if (data.error) {
53
+ addMessage("bot", `Error: ${data.error}`);
54
+ conversationState.state = 'awaiting_initial_action';
55
+ }
56
+ updatePlaceholder();
57
+
58
+ } catch (error) {
59
+ removeTypingIndicator();
60
+ console.error("Upload error:", error);
61
+ addMessage("bot", "Sorry, I encountered an error while uploading your resume.");
62
+ conversationState.state = 'awaiting_initial_action';
63
+ updatePlaceholder();
64
+ }
65
+ resumeFileInput.value = '';
66
+ };
67
+
68
+ chatMessages.addEventListener('click', (event) => {
69
+ const card = event.target.closest('.clickable-card');
70
+ if (!card) return;
71
+ const action = card.dataset.action;
72
+ const value = card.dataset.value;
73
+ if (action === 'details' || action === 'compare' || action === 'quick_reply') {
74
+ sendMessage(value);
75
+ const buttonContainer = card.closest('.quick-reply-container');
76
+ if (buttonContainer) buttonContainer.remove();
77
+ } else if (action === 'restart') {
78
+ location.reload();
79
+ }
80
+ });
81
+
82
+ leftColumn.addEventListener('click', (event) => {
83
+ const featureCard = event.target.closest('[data-action="feature_select"]');
84
+ if (!featureCard || conversationState.state === 'asking_questions') {
85
+ if (conversationState.state === 'asking_questions') {
86
+ const card = document.querySelector('.card');
87
+ card.style.transition = 'outline 0.1s ease-out';
88
+ card.style.outline = '2px solid #DB2777';
89
+ setTimeout(() => { card.style.outline = 'none'; }, 500);
90
+ }
91
+ return;
92
+ }
93
+ const featureName = featureCard.dataset.value;
94
+ sendMessage(featureName);
95
+ leftColumn.classList.remove('active');
96
+ });
97
+
98
+ const updatePlaceholder = () => {
99
+ const currentState = conversationState.state;
100
+ let shouldBeEnabled = true;
101
+ let placeholderText = "Send a message...";
102
+
103
+ if (currentState === 'session_ended') {
104
+ placeholderText = "Click 'Start Over' to begin a new session.";
105
+ shouldBeEnabled = false;
106
+ } else if (currentState === 'awaiting_initial_action') {
107
+ placeholderText = "Type 'start' or select a feature...";
108
+ } else if (currentState === 'awaiting_resume_upload') {
109
+ placeholderText = "Upload your resume using the upload button...";
110
+ shouldBeEnabled = false;
111
+ } else {
112
+ placeholderText = "Send a message...";
113
+ }
114
+
115
+ messageInput.placeholder = placeholderText;
116
+ messageInput.disabled = !shouldBeEnabled;
117
+ sendButton.disabled = !shouldBeEnabled;
118
+ uploadResumeButton.style.display = (currentState === 'awaiting_resume_upload') ? 'block' : 'none';
119
+
120
+ if (shouldBeEnabled) {
121
+ messageInput.focus();
122
+ }
123
+ };
124
+
125
+ const addMessage = (sender, message) => {
126
+ const messageElement = document.createElement("div");
127
+ messageElement.classList.add("message", `${sender}-message`);
128
+ if (sender === "bot") {
129
+ const avatarImg = document.createElement("img");
130
+ avatarImg.src = "/static/bot_avatar.png";
131
+ avatarImg.alt = "Bot Avatar";
132
+ avatarImg.className = "bot-avatar";
133
+ messageElement.appendChild(avatarImg);
134
+ }
135
+ const paragraph = document.createElement("p");
136
+ paragraph.innerHTML = message;
137
+ messageElement.appendChild(paragraph);
138
+ chatMessages.appendChild(messageElement);
139
+ // Removed automatic scrolling for bot messages
140
+ };
141
+
142
+ const showTypingIndicator = () => {
143
+ messageInput.disabled = true;
144
+ sendButton.disabled = true;
145
+ if (document.getElementById("typing-indicator")) return;
146
+ const indicatorElement = document.createElement("div");
147
+ indicatorElement.id = "typing-indicator";
148
+ indicatorElement.classList.add("message", "bot-message");
149
+ indicatorElement.innerHTML = `<img src="/static/bot_avatar.png" alt="Bot Avatar" class="bot-avatar"><div class="typing-indicator"><span></span><span></span><span></span></div>`;
150
+ chatMessages.appendChild(indicatorElement);
151
+ chatMessages.scrollTop = chatMessages.scrollHeight; // Scroll only for typing indicator
152
+ };
153
+
154
+ const removeTypingIndicator = () => {
155
+ const indicator = document.getElementById("typing-indicator");
156
+ if (indicator) indicator.remove();
157
+ };
158
+
159
+ const handleFormSubmit = async (event) => {
160
+ event.preventDefault();
161
+ const message = messageInput.value.trim();
162
+ if (!message) return;
163
+
164
+ addMessage("user", message);
165
+ chatMessages.scrollTop = chatMessages.scrollHeight;
166
+
167
+ messageInput.value = "";
168
+ showTypingIndicator();
169
+
170
+ try {
171
+ const response = await fetch("/chat", {
172
+ method: "POST",
173
+ headers: { "Content-Type": "application/json" },
174
+ body: JSON.stringify({ message: message, conversation: conversationState }),
175
+ });
176
+
177
+ if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
178
+ const data = await response.json();
179
+
180
+ setTimeout(() => {
181
+ removeTypingIndicator();
182
+ conversationState = data.conversation;
183
+ if (data.response) {
184
+ addMessage("bot", data.response);
185
+ }
186
+ updatePlaceholder();
187
+ }, 500);
188
+
189
+ } catch (error) {
190
+ removeTypingIndicator();
191
+ console.error("Fetch error:", error);
192
+ addMessage("bot", "Sorry, I'm having trouble connecting.");
193
+ updatePlaceholder();
194
+ }
195
+ };
196
+
197
+ const initializeChat = async () => {
198
+ try {
199
+ const response = await fetch("/chat", {
200
+ method: "POST",
201
+ headers: { "Content-Type": "application/json" },
202
+ body: JSON.stringify({ message: "", conversation: {} }),
203
+ });
204
+ const data = await response.json();
205
+ conversationState = data.conversation;
206
+ addMessage("bot", data.response);
207
+ updatePlaceholder();
208
+ } catch (error) {
209
+ console.error("Initialization error:", error);
210
+ addMessage("bot", "Sorry, I'm having trouble connecting.");
211
+ }
212
+ };
213
+
214
+ const handleThemeChange = () => {
215
+ document.body.className = themeToggle.checked ? 'dark' : 'light';
216
+ };
217
+
218
+ menuToggle.addEventListener('click', () => {
219
+ leftColumn.classList.toggle('active');
220
+ });
221
+
222
+ refreshChatButton.addEventListener('click', (event) => {
223
+ event.preventDefault();
224
+ location.reload();
225
+ });
226
+
227
+ chatForm.addEventListener("submit", handleFormSubmit);
228
+ themeToggle.addEventListener("change", handleThemeChange);
229
+ uploadResumeButton.addEventListener('click', () => resumeFileInput.click());
230
+ resumeFileInput.addEventListener('change', (event) => uploadResumeFile(event.target.files[0]));
231
+
232
+ document.body.className = 'light';
233
+ initializeChat();
234
+ });
static/style.css ADDED
@@ -0,0 +1,428 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* General Setup */
2
+ body {
3
+ font-family: 'Inter', sans-serif;
4
+ margin: 0;
5
+ transition: background-color 0.3s, color 0.3s;
6
+ -webkit-font-smoothing: antialiased;
7
+ -moz-osx-font-smoothing: grayscale;
8
+ }
9
+ .main-container {
10
+ display: flex;
11
+ height: 100vh;
12
+ padding: 2rem;
13
+ box-sizing: border-box;
14
+ gap: 2rem;
15
+ }
16
+
17
+ /* --- Left Column --- */
18
+ .left-column {
19
+ flex: 0.7;
20
+ display: flex;
21
+ flex-direction: column;
22
+ padding-right: 2rem;
23
+ border-right: 1px solid;
24
+ }
25
+ .logo-container {
26
+ width: 100%;
27
+ text-align: center;
28
+ margin-bottom: 1.5rem;
29
+ }
30
+ .logo {
31
+ max-width: 100%;
32
+ height: auto;
33
+ }
34
+ .intro-paragraph {
35
+ text-align: center;
36
+ font-size: 1rem;
37
+ line-height: 1.6;
38
+ font-weight: 400;
39
+ margin-bottom: 2rem;
40
+ }
41
+ .card {
42
+ padding: 1.5rem;
43
+ border-radius: 12px;
44
+ margin-bottom: 1.5rem;
45
+ border: 1px solid;
46
+ transition: box-shadow 0.3s ease-in-out, transform 0.2s ease-in-out;
47
+ }
48
+ .card:hover {
49
+ transform: translateY(-2px);
50
+ }
51
+ .card h2 {
52
+ margin-top: 0;
53
+ margin-bottom: 1.25rem;
54
+ font-size: 0.9rem;
55
+ font-weight: 600;
56
+ letter-spacing: 0.5px;
57
+ text-transform: uppercase;
58
+ }
59
+ .features-list {
60
+ list-style: none;
61
+ padding: 0;
62
+ margin: 0;
63
+ }
64
+ .features-list li {
65
+ display: flex;
66
+ align-items: center;
67
+ margin-bottom: 1rem;
68
+ font-size: 1rem;
69
+ font-weight: 500;
70
+ padding: 10px;
71
+ border-radius: 8px;
72
+ transition: background-color 0.2s ease-in-out;
73
+ }
74
+ .features-list li:hover {
75
+ cursor: pointer;
76
+ }
77
+ .features-list li:last-child {
78
+ margin-bottom: 0;
79
+ }
80
+ .features-list .icon {
81
+ display: inline-flex;
82
+ align-items: center;
83
+ justify-content: center;
84
+ margin-right: 1rem;
85
+ }
86
+ .disclaimer {
87
+ font-size: 0.8rem;
88
+ text-align: center;
89
+ margin-top: auto;
90
+ }
91
+ .setting-item {
92
+ display: flex;
93
+ justify-content: space-between;
94
+ align-items: center;
95
+ font-weight: 500;
96
+ }
97
+
98
+ /* --- Toggle Switch Styles --- */
99
+ .toggle-switch {
100
+ position: relative;
101
+ display: inline-block;
102
+ width: 44px;
103
+ height: 24px;
104
+ }
105
+ .toggle-switch input {
106
+ opacity: 0;
107
+ width: 0;
108
+ height: 0;
109
+ }
110
+ .slider {
111
+ position: absolute;
112
+ cursor: pointer;
113
+ top: 0; left: 0; right: 0; bottom: 0;
114
+ background-color: #ccc;
115
+ transition: .4s;
116
+ border-radius: 24px;
117
+ }
118
+ .slider:before {
119
+ position: absolute;
120
+ content: "";
121
+ height: 18px; width: 18px;
122
+ left: 3px; bottom: 3px;
123
+ background-color: white;
124
+ transition: .4s;
125
+ border-radius: 50%;
126
+ box-shadow: 0 1px 3px rgba(0,0,0,0.2);
127
+ }
128
+ input:checked + .slider:before {
129
+ transform: translateX(20px);
130
+ }
131
+
132
+ /* --- Right Column --- */
133
+ .right-column {
134
+ flex: 2.3;
135
+ }
136
+
137
+ /* Chat Window */
138
+ .chat-window {
139
+ height: 100%;
140
+ display: flex;
141
+ flex-direction: column;
142
+ border-radius: 16px;
143
+ box-shadow: 0 8px 30px rgba(0,0,0,0.08);
144
+ overflow: hidden;
145
+ }
146
+ .chat-header {
147
+ display: flex;
148
+ align-items: center;
149
+ padding: 0.75rem 1.5rem;
150
+ font-weight: 600;
151
+ font-family: 'Inter', sans-serif;
152
+ font-size: 1.2rem;
153
+ flex-shrink: 0;
154
+ }
155
+ .chat-messages {
156
+ flex-grow: 1;
157
+ padding: 1rem;
158
+ overflow-y: auto;
159
+ }
160
+ .chat-input-form {
161
+ display: flex;
162
+ padding: 1rem;
163
+ border-top: 1px solid;
164
+ flex-shrink: 0;
165
+ align-items: center;
166
+ }
167
+ #message-input {
168
+ flex-grow: 1;
169
+ border: 1px solid;
170
+ padding: 12px;
171
+ font-size: 1rem;
172
+ border-radius: 8px;
173
+ font-family: 'Inter', sans-serif;
174
+ }
175
+ #message-input:focus {
176
+ outline: none;
177
+ }
178
+ .chat-input-form button {
179
+ background: none;
180
+ border: none;
181
+ font-size: 1.5rem;
182
+ cursor: pointer;
183
+ transition: transform 0.2s ease-in-out;
184
+ padding-left: 1rem;
185
+ }
186
+ .chat-input-form button:hover {
187
+ transform: scale(1.1);
188
+ }
189
+
190
+ /* Message Bubbles */
191
+ .message {
192
+ margin-bottom: 1rem;
193
+ display: flex;
194
+ align-items: flex-end;
195
+ gap: 0.75rem;
196
+ }
197
+ .bot-avatar {
198
+ width: 40px;
199
+ height: 40px;
200
+ border-radius: 50%;
201
+ flex-shrink: 0;
202
+ }
203
+ .message p {
204
+ padding: 0.7rem 1.2rem;
205
+ border-radius: 18px;
206
+ max-width: 95%;
207
+ line-height: 1.6;
208
+ margin: 0;
209
+ }
210
+ .user-message { justify-content: flex-end; }
211
+ .bot-message { justify-content: flex-start; }
212
+ .user-message p { border-radius: 18px 18px 5px 18px; }
213
+ .bot-message p { border-radius: 18px 18px 18px 5px; }
214
+
215
+
216
+ /* Typing Indicator Styles */
217
+ .typing-indicator {
218
+ display: flex;
219
+ align-items: center;
220
+ padding: 0.7rem 1.2rem;
221
+ }
222
+ .typing-indicator span {
223
+ height: 8px;
224
+ width: 8px;
225
+ border-radius: 50%;
226
+ margin: 0 2px;
227
+ animation: bounce 1.4s infinite ease-in-out both;
228
+ }
229
+ .typing-indicator span:nth-child(1) { animation-delay: -0.32s; }
230
+ .typing-indicator span:nth-child(2) { animation-delay: -0.16s; }
231
+
232
+ @keyframes bounce {
233
+ 0%, 80%, 100% { transform: scale(0); } 40% { transform: scale(1.0); }
234
+ }
235
+
236
+
237
+ /* --- LIGHT THEME --- */
238
+ body.light {
239
+ background-color: #e9ecef;
240
+ color: #212529;
241
+ }
242
+ body.light .logo-dark { display: none; }
243
+ body.light .logo-light { display: block; margin: 0 auto; }
244
+ body.light .left-column { border-right-color: #ced4da; }
245
+ body.light .card { background-color: #ffffff; border-color: #dee2e6; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); }
246
+ body.light .features-list li:hover { background-color: #dee2e6; }
247
+ body.light .typing-indicator span { background-color: #868e96; }
248
+ body.light .chat-window { background: #FFFFFF; }
249
+ body.light .chat-header { background: #104E61; color: white; }
250
+ body.light .chat-input-form { border-top-color: #dee2e6; }
251
+ body.light #message-input { background: #ffffff; color: #495057; border-color: #ced4da; }
252
+ body.light .bot-message p { background: #D1D5DB; color: #212529; }
253
+ body.light .user-message p { background: #104E61; color: white; }
254
+ body.light .chat-input-form button { color: #104E61; }
255
+ body.light input:checked + .slider { background-color: #104E61; }
256
+
257
+
258
+ /* --- DARK THEME --- */
259
+ body.dark {
260
+ background-color: #121212;
261
+ color: #e9ecef;
262
+ }
263
+ body.dark .logo-dark { display: block; margin: 0 auto; }
264
+ body.dark .logo-light { display: none; }
265
+ body.dark .left-column { border-right-color: #343a40; }
266
+ body.dark .card { background-color: #1e1e1e; border-color: #343a40; box-shadow: 0 4px 20px rgba(0,0,0,0.2); }
267
+ body.dark .features-list li:hover { background-color: #2c2d30; }
268
+ body.dark .typing-indicator span { background-color: #adb5bd; }
269
+ body.dark .chat-window { background: #1e1e1e; }
270
+ body.dark .chat-header { background: #DB2777; color: white; }
271
+ body.dark .chat-input-form { border-top-color: #343a40; }
272
+ body.dark #message-input { background: #121212; color: #e9ecef; border-color: #343a40;}
273
+ body.dark .bot-message p { background: #343a40; color: #f8f9fa; }
274
+ body.dark .user-message p { background: #DB2777; color: white; }
275
+ body.dark .chat-input-form button { color: #DB2777; }
276
+ body.dark input:checked + .slider { background-color: #DB2777; }
277
+
278
+ /* --- Download Button Styles --- */
279
+ .setting-item { margin-bottom: 1rem; }
280
+ .setting-item:last-child { margin-bottom: 0; }
281
+ .download-button { text-decoration: none; font-weight: 500; font-size: 0.9rem; padding: 6px 14px; border-radius: 6px; transition: background-color 0.2s ease-in-out, color 0.2s ease-in-out; }
282
+ body.light .download-button { background-color: #e9ecef; color: #495057; border: 1px solid #dee2e6; }
283
+ body.light .download-button:hover { background-color: #dee2e6; }
284
+ body.dark .download-button { background-color: #343a40; color: #e9ecef; border: 1px solid #495057; }
285
+ body.dark .download-button:hover { background-color: #495057; }
286
+
287
+ /* --- V6+ Card & Link Styles --- */
288
+ .college-card a { text-decoration: none; color: inherit; font-weight: bold; }
289
+ .college-card a:hover { text-decoration: underline; }
290
+ .recommendation-container, .details-card, .college-card { border: 1px solid; border-radius: 12px; padding: 1rem 1.5rem; margin-top: 1rem; }
291
+ .recommendation-container h4, .details-card h3, .college-card h4 { margin-top: 0; margin-bottom: 1rem; font-size: 1.1rem; display: flex; align-items: center; gap: 0.5rem; }
292
+ .recommendation-card { padding: 1rem 0; border-bottom: 1px solid; }
293
+ .recommendation-container .recommendation-card:last-child { border-bottom: none; padding-bottom: 0; }
294
+ .college-card ul { list-style: none; padding-left: 0; margin: 0; }
295
+ .college-card li { padding: 0.75rem 0; border-bottom: 1px solid; }
296
+ .college-card li:last-child { border-bottom: none; padding-bottom: 0; }
297
+ .college-card li small { opacity: 0.7; }
298
+
299
+ /* Clickable Card Base Styles */
300
+ .clickable-card { cursor: pointer; transition: transform 0.2s ease-in-out, box-shadow 0.2s ease-in-out; }
301
+ .clickable-card:hover { transform: translateY(-3px); }
302
+ .compare-card {
303
+ text-align: center;
304
+ border-radius: 12px;
305
+ margin-top: 1rem;
306
+ padding: 1rem;
307
+ border-bottom: none;
308
+ }
309
+
310
+ /* Light Theme Cards */
311
+ body.light .recommendation-container, body.light .details-card, body.light .college-card { background-color: #f8f9fa; border-color: #dee2e6; }
312
+ body.light .recommendation-card, body.light .college-card li { border-color: #e9ecef; }
313
+ body.light .primary-card { border-left: 4px solid #104E61; }
314
+ body.light .fallback-card { border-left: 4px solid #6c757d; }
315
+ body.light .clickable-card:hover { box-shadow: 0 6px 15px rgba(0, 0, 0, 0.08); }
316
+ body.light .details-card a { color: #0056b3; }
317
+ body.light .compare-card { background-color: #e9ecef; }
318
+ body.light .compare-card:hover { background-color: #dee2e6; }
319
+
320
+ /* Dark Theme Cards */
321
+ body.dark .recommendation-container, body.dark .details-card, body.dark .college-card { background-color: #2c2d30; border-color: #343a40; }
322
+ body.dark .recommendation-card, body.dark .college-card li { border-color: #343a40; }
323
+ body.dark .primary-card { border-left: 4px solid #DB2777; }
324
+ body.dark .fallback-card { border-left: 4px solid #adb5bd; }
325
+ body.dark .compare-card { background-color: #343a40; }
326
+ body.dark .compare-card:hover { background-color: #3e444a; }
327
+ body.dark .clickable-card:hover { box-shadow: 0 6px 20px rgba(0,0,0,0.25); }
328
+ body.dark .details-card a { color: #7dd3fc; }
329
+
330
+ /* Quick Reply Buttons */
331
+ .quick-reply-container {
332
+ display: flex;
333
+ gap: 10px;
334
+ margin-top: 12px;
335
+ }
336
+ .quick-reply-button {
337
+ padding: 8px 16px;
338
+ border-radius: 18px;
339
+ font-weight: 500;
340
+ font-size: 0.9rem;
341
+ }
342
+ body.light .quick-reply-button {
343
+ background-color: #e9ecef;
344
+ border: 1px solid #dee2e6;
345
+ }
346
+ body.light .quick-reply-button:hover {
347
+ background-color: #dee2e6;
348
+ border-color: #ced4da;
349
+ }
350
+ body.dark .quick-reply-button {
351
+ background-color: #343a40;
352
+ border: 1px solid #495057;
353
+ }
354
+ body.dark .quick-reply-button:hover {
355
+ background-color: #495057;
356
+ border-color: #6c757d;
357
+ }
358
+
359
+ /* --- Mobile Responsiveness --- */
360
+
361
+ /* Hide the hamburger menu on desktop */
362
+ .menu-toggle-button {
363
+ display: none;
364
+ background: none;
365
+ border: none;
366
+ color: white;
367
+ cursor: pointer;
368
+ padding: 0 1rem 0 0;
369
+ }
370
+
371
+ @media (max-width: 768px) {
372
+ /* Show the hamburger menu on mobile */
373
+ .menu-toggle-button {
374
+ display: block;
375
+ }
376
+
377
+ .main-container {
378
+ flex-direction: column;
379
+ padding: 0;
380
+ gap: 0;
381
+ }
382
+
383
+ /* Make the left column a slide-out menu */
384
+ .left-column {
385
+ position: fixed;
386
+ top: 0;
387
+ left: 0;
388
+ width: 80%;
389
+ max-width: 300px;
390
+ height: 100%;
391
+ background-color: var(--background-color); /* Use theme variables */
392
+ z-index: 1000;
393
+ transform: translateX(-100%);
394
+ transition: transform 0.3s ease-in-out;
395
+ border-right: 1px solid;
396
+ padding: 1rem;
397
+ box-sizing: border-box;
398
+ overflow-y: auto;
399
+ }
400
+
401
+ /* This class will be toggled by JavaScript to show the menu */
402
+ .left-column.active {
403
+ transform: translateX(0);
404
+ }
405
+
406
+ .right-column {
407
+ width: 100%;
408
+ flex: 1; /* Allow it to take up remaining height */
409
+ }
410
+
411
+ .chat-window {
412
+ height: 100vh; /* Full screen height */
413
+ border-radius: 0;
414
+ box-shadow: none;
415
+ }
416
+
417
+ .chat-header {
418
+ border-radius: 0;
419
+ }
420
+ }
421
+
422
+ /* Use theme variables for the background of the left column on mobile */
423
+ body.light .left-column {
424
+ background-color: #e9ecef;
425
+ }
426
+ body.dark .left-column {
427
+ background-color: #121212;
428
+ }
templates/index.html ADDED
@@ -0,0 +1,102 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>CareerPal</title>
7
+ <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
8
+ <link rel="preconnect" href="https://fonts.googleapis.com">
9
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
10
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
11
+ </head>
12
+ <body>
13
+ <div class="main-container">
14
+ <div class="left-column" id="left-column">
15
+ <div class="logo-container">
16
+ <img src="{{ url_for('static', filename='logo_darktheme.png') }}" alt="CareerPal Logo" class="logo logo-dark">
17
+ <img src="{{ url_for('static', filename='logo_lighttheme.png') }}" alt="CareerPal Logo" class="logo logo-light">
18
+ </div>
19
+
20
+ <p class="intro-paragraph">
21
+ Your personal AI-powered career advisor. Discover, compare, and plan your professional journey with data-driven insights.
22
+ </p>
23
+
24
+ <div class="card">
25
+ <h2>Features:</h2>
26
+ <ul class="features-list">
27
+ <li data-action="feature_select" data-value="Personalized Guidance">
28
+ <span class="icon">
29
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><circle cx="12" cy="12" r="6"></circle><circle cx="12" cy="12" r="2"></circle></svg>
30
+ </span>
31
+ Personalized Guidance
32
+ </li>
33
+ <li data-action="feature_select" data-value="Compare Courses">
34
+ <span class="icon">
35
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="20" x2="18" y2="10"></line><line x1="12" y1="20" x2="12" y2="4"></line><line x1="6" y1="20" x2="6" y2="14"></line></svg>
36
+ </span>
37
+ Compare Courses
38
+ </li>
39
+ <li data-action="feature_select" data-value="College Location Finder">
40
+ <span class="icon">
41
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"></path><circle cx="12" cy="10" r="3"></circle></svg>
42
+ </span>
43
+ College Location Finder
44
+ </li>
45
+ <li data-action="feature_select" data-value="Resume Analyser">
46
+ <span class="icon">
47
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path><polyline points="14 2 14 8 20 8"></polyline><line x1="16" y1="13" x2="8" y2="13"></line><line x1="16" y1="17" x2="8" y2="17"></line><polyline points="10 9 9 9 8 9"></polyline></svg>
48
+ </span>
49
+ Resume Analyser
50
+ </li>
51
+ </ul>
52
+ </div>
53
+
54
+ <div class="card">
55
+ <h2>Settings:</h2>
56
+ <div class="setting-item">
57
+ <span>Dark Mode</span>
58
+ <label class="toggle-switch">
59
+ <input type="checkbox" id="theme-toggle">
60
+ <span class="slider"></span>
61
+ </label>
62
+ </div>
63
+ <div class="setting-item" style="margin-top: 1rem;">
64
+ <span>Refresh Chat</span>
65
+ <a href="#" id="refresh-chat-button" class="download-button">Restart</a>
66
+ </div>
67
+ </div>
68
+
69
+ <div class="disclaimer">
70
+ <p><strong>Disclaimer:</strong> AI-generated suggestions are for informational purposes only.</p>
71
+ </div>
72
+ </div>
73
+
74
+ <div class="right-column">
75
+ <div class="chat-window">
76
+ <div class="chat-header">
77
+ <button id="menu-toggle" class="menu-toggle-button">
78
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="3" y1="12" x2="21" y2="12"></line><line x1="3" y1="6" x2="21" y2="6"></line><line x1="3" y1="18" x2="21" y2="18"></line></svg>
79
+ </button>
80
+ <p>Chat with CareerPal</p>
81
+ </div>
82
+ <div class="chat-messages" id="chat-messages">
83
+ </div>
84
+ <form class="chat-input-form" id="chat-form">
85
+ <input type="file" id="resume-file-input" accept=".pdf,.docx" style="display: none;">
86
+ <button type="button" id="upload-resume-button" title="Upload Resume" style="padding: 0 0.75rem 0 0.5rem; display: flex; align-items: center; justify-content: center;">
87
+ <svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
88
+ <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
89
+ <polyline points="17 8 12 3 7 8"></polyline>
90
+ <line x1="12" y1="3" x2="12" y2="15"></line>
91
+ </svg>
92
+ </button>
93
+ <input type="text" id="message-input" placeholder="Type 'start' to begin..." autocomplete="off">
94
+ <button type="submit">➤</button>
95
+ </form>
96
+ </div>
97
+ </div>
98
+ </div>
99
+
100
+ <script src="{{ url_for('static', filename='script.js') }}"></script>
101
+ </body>
102
+ </html>