Commit
·
a45fe55
0
Parent(s):
Initializing CareerPal on Hugging Face
Browse files- .gitattributes +37 -0
- .gitignore +4 -0
- Chat log saves/1.txt +49 -0
- Dockerfile +20 -0
- README.md +10 -0
- Required Data/24089440-pin-codes.pdf +3 -0
- Required Data/AndrewGreenResume.pdf +3 -0
- Required Data/Professional CV Resume_20250626_095832_0000.pdf +3 -0
- Required Data/Resume (2).pdf +3 -0
- Required Data/Updated-All-College-List-with-Course-Detailss.pdf +3 -0
- app.py +433 -0
- build.sh +7 -0
- careers.json +0 -0
- careers_cleaned.json +0 -0
- chat_log.txt +0 -0
- colleges.json +525 -0
- feedback.txt +3 -0
- gunicorn +0 -0
- pincodes.json +130 -0
- requirements.txt +0 -0
- static/bot_avatar.png +3 -0
- static/logo_darktheme.png +3 -0
- static/logo_lighttheme.png +3 -0
- static/my_logo.png +3 -0
- static/script.js +234 -0
- static/style.css +428 -0
- templates/index.html +102 -0
.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] "[36mGET /static/style.css HTTP/1.1[0m" 304 -
|
| 7 |
+
2025-09-11 12:58:51 - 127.0.0.1 - - [11/Sep/2025 12:58:51] "[36mGET /static/logo_darktheme.png HTTP/1.1[0m" 304 -
|
| 8 |
+
2025-09-11 12:58:51 - 127.0.0.1 - - [11/Sep/2025 12:58:51] "[36mGET /static/logo_lighttheme.png HTTP/1.1[0m" 304 -
|
| 9 |
+
2025-09-11 12:58:51 - 127.0.0.1 - - [11/Sep/2025 12:58:51] "[36mGET /static/script.js HTTP/1.1[0m" 304 -
|
| 10 |
+
2025-09-11 12:58:51 - 127.0.0.1 - - [11/Sep/2025 12:58:51] "[36mGET /static/bot_avatar.png HTTP/1.1[0m" 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] "[36mGET /static/script.js HTTP/1.1[0m" 304 -
|
| 13 |
+
2025-09-11 12:59:17 - 127.0.0.1 - - [11/Sep/2025 12:59:17] "[36mGET /static/logo_darktheme.png HTTP/1.1[0m" 304 -
|
| 14 |
+
2025-09-11 12:59:17 - 127.0.0.1 - - [11/Sep/2025 12:59:17] "[36mGET /static/logo_lighttheme.png HTTP/1.1[0m" 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] "[36mGET /static/bot_avatar.png HTTP/1.1[0m" 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
|
static/logo_darktheme.png
ADDED
|
Git LFS Details
|
static/logo_lighttheme.png
ADDED
|
Git LFS Details
|
static/my_logo.png
ADDED
|
Git LFS Details
|
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>
|