vancyferns commited on
Commit
2a89678
·
1 Parent(s): d0ed4e5

added textModel files to github repo

Browse files
.gitignore DELETED
@@ -1,207 +0,0 @@
1
- # Byte-compiled / optimized / DLL files
2
- __pycache__/
3
- *.py[codz]
4
- *$py.class
5
-
6
- # C extensions
7
- *.so
8
-
9
- # Distribution / packaging
10
- .Python
11
- build/
12
- develop-eggs/
13
- dist/
14
- downloads/
15
- eggs/
16
- .eggs/
17
- lib/
18
- lib64/
19
- parts/
20
- sdist/
21
- var/
22
- wheels/
23
- share/python-wheels/
24
- *.egg-info/
25
- .installed.cfg
26
- *.egg
27
- MANIFEST
28
-
29
- # PyInstaller
30
- # Usually these files are written by a python script from a template
31
- # before PyInstaller builds the exe, so as to inject date/other infos into it.
32
- *.manifest
33
- *.spec
34
-
35
- # Installer logs
36
- pip-log.txt
37
- pip-delete-this-directory.txt
38
-
39
- # Unit test / coverage reports
40
- htmlcov/
41
- .tox/
42
- .nox/
43
- .coverage
44
- .coverage.*
45
- .cache
46
- nosetests.xml
47
- coverage.xml
48
- *.cover
49
- *.py.cover
50
- .hypothesis/
51
- .pytest_cache/
52
- cover/
53
-
54
- # Translations
55
- *.mo
56
- *.pot
57
-
58
- # Django stuff:
59
- *.log
60
- local_settings.py
61
- db.sqlite3
62
- db.sqlite3-journal
63
-
64
- # Flask stuff:
65
- instance/
66
- .webassets-cache
67
-
68
- # Scrapy stuff:
69
- .scrapy
70
-
71
- # Sphinx documentation
72
- docs/_build/
73
-
74
- # PyBuilder
75
- .pybuilder/
76
- target/
77
-
78
- # Jupyter Notebook
79
- .ipynb_checkpoints
80
-
81
- # IPython
82
- profile_default/
83
- ipython_config.py
84
-
85
- # pyenv
86
- # For a library or package, you might want to ignore these files since the code is
87
- # intended to run in multiple environments; otherwise, check them in:
88
- # .python-version
89
-
90
- # pipenv
91
- # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
92
- # However, in case of collaboration, if having platform-specific dependencies or dependencies
93
- # having no cross-platform support, pipenv may install dependencies that don't work, or not
94
- # install all needed dependencies.
95
- #Pipfile.lock
96
-
97
- # UV
98
- # Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
99
- # This is especially recommended for binary packages to ensure reproducibility, and is more
100
- # commonly ignored for libraries.
101
- #uv.lock
102
-
103
- # poetry
104
- # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
105
- # This is especially recommended for binary packages to ensure reproducibility, and is more
106
- # commonly ignored for libraries.
107
- # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
108
- #poetry.lock
109
- #poetry.toml
110
-
111
- # pdm
112
- # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
113
- # pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
114
- # https://pdm-project.org/en/latest/usage/project/#working-with-version-control
115
- #pdm.lock
116
- #pdm.toml
117
- .pdm-python
118
- .pdm-build/
119
-
120
- # pixi
121
- # Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
122
- #pixi.lock
123
- # Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
124
- # in the .venv directory. It is recommended not to include this directory in version control.
125
- .pixi
126
-
127
- # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
128
- __pypackages__/
129
-
130
- # Celery stuff
131
- celerybeat-schedule
132
- celerybeat.pid
133
-
134
- # SageMath parsed files
135
- *.sage.py
136
-
137
- # Environments
138
- .env
139
- .envrc
140
- .venv
141
- env/
142
- venv/
143
- ENV/
144
- env.bak/
145
- venv.bak/
146
-
147
- # Spyder project settings
148
- .spyderproject
149
- .spyproject
150
-
151
- # Rope project settings
152
- .ropeproject
153
-
154
- # mkdocs documentation
155
- /site
156
-
157
- # mypy
158
- .mypy_cache/
159
- .dmypy.json
160
- dmypy.json
161
-
162
- # Pyre type checker
163
- .pyre/
164
-
165
- # pytype static type analyzer
166
- .pytype/
167
-
168
- # Cython debug symbols
169
- cython_debug/
170
-
171
- # PyCharm
172
- # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
173
- # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
174
- # and can be added to the global gitignore or merged into this file. For a more nuclear
175
- # option (not recommended) you can uncomment the following to ignore the entire idea folder.
176
- #.idea/
177
-
178
- # Abstra
179
- # Abstra is an AI-powered process automation framework.
180
- # Ignore directories containing user credentials, local state, and settings.
181
- # Learn more at https://abstra.io/docs
182
- .abstra/
183
-
184
- # Visual Studio Code
185
- # Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
186
- # that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
187
- # and can be added to the global gitignore or merged into this file. However, if you prefer,
188
- # you could uncomment the following to ignore the entire vscode folder
189
- # .vscode/
190
-
191
- # Ruff stuff:
192
- .ruff_cache/
193
-
194
- # PyPI configuration file
195
- .pypirc
196
-
197
- # Cursor
198
- # Cursor is an AI-powered code editor. `.cursorignore` specifies files/directories to
199
- # exclude from AI features like autocomplete and code analysis. Recommended for sensitive data
200
- # refer to https://docs.cursor.com/context/ignore-files
201
- .cursorignore
202
- .cursorindexingignore
203
-
204
- # Marimo
205
- marimo/_static/
206
- marimo/_lsp/
207
- __marimo__/
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Dockerfile ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Use official Python image
2
+ FROM python:3.9-slim
3
+
4
+ # Create a non-root user
5
+ RUN useradd -m -u 1000 user
6
+ USER user
7
+ ENV PATH="/home/user/.local/bin:$PATH"
8
+
9
+ # Set working directory
10
+ WORKDIR /app
11
+
12
+ # Copy requirements and install
13
+ COPY --chown=user ./requirements.txt requirements.txt
14
+ RUN pip install --no-cache-dir --upgrade -r requirements.txt
15
+
16
+ # Copy the textModel folder contents into /app
17
+ COPY --chown=user ./textModel/ .
18
+
19
+ # Expose the port your Flask app will run on
20
+ EXPOSE 7860
21
+
22
+ # Command to run Flask app in Hugging Face Spaces
23
+ ENV FLASK_APP=app.py
24
+ ENV FLASK_RUN_HOST=0.0.0.0
25
+ ENV FLASK_RUN_PORT=7860
26
+
27
+ CMD ["flask", "run"]
28
+
README.md CHANGED
@@ -1,9 +0,0 @@
1
- ---
2
- title: Moodify TextModel
3
- emoji: 🐠
4
- colorFrom: gray
5
- colorTo: pink
6
- sdk: docker
7
- pinned: false
8
- ---
9
- # moodify_textModel
 
 
 
 
 
 
 
 
 
 
textModel/README.md ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Text Emotion Detection API
2
+
3
+ This is a Flask-based API that uses a pre-trained transformer model to detect emotions from text.
4
+
5
+ ## How to Run
6
+
7
+ ### 1. Prerequisites
8
+
9
+ Make sure you have Python 3 and `pip` installed on your system.
10
+
11
+ ### 2. Create a Virtual Environment (Recommended)
12
+
13
+ It's a good practice to create a virtual environment to manage project dependencies.
14
+
15
+ ```bash
16
+ python3 -m venv venv
17
+ source venv/bin/activate
18
+ ```
19
+
20
+ ### 3. Install Dependencies
21
+
22
+ Install the required Python packages using `pip`:
23
+
24
+ ```bash
25
+ pip install -r requirements.txt
26
+ ```
27
+
28
+ ### 4. Run the Application
29
+
30
+ Once the dependencies are installed, you can run the Flask application:
31
+
32
+ ```bash
33
+ #first run the fix_nlkt.py file
34
+ python fix_nltk.py
35
+ #once this is succesfull run app.py now you can run /client code in seperate terminal with npm run dev and go to emotionQuestionnare
36
+
37
+ python app.py
38
+ ```
39
+
textModel/app.py ADDED
@@ -0,0 +1,540 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import logging
2
+ from flask import Flask, request, jsonify
3
+ from flask_cors import CORS
4
+ from transformers import pipeline
5
+ import torch
6
+ from pymongo import MongoClient
7
+ from pymongo.errors import ConnectionFailure
8
+ import random
9
+ import certifi
10
+ from textblob import TextBlob
11
+
12
+ # --- Set up logging ---
13
+ logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
14
+ logger = logging.getLogger(__name__)
15
+
16
+ # --- Database Connection ---
17
+ MONGO_URI = "mongodb+srv://soniyavitkar2712:soniya_27@cluster0.slai2ew.mongodb.net/?retryWrites=true&w=majority&appName=Cluster0"
18
+ client = None
19
+ db = None
20
+ songs_collection = None
21
+
22
+ try:
23
+ logger.info("Attempting to connect to MongoDB Atlas...")
24
+ # Use certifi to provide the SSL certificate
25
+ ca = certifi.where()
26
+ client = MongoClient(MONGO_URI, serverSelectionTimeoutMS=5000, tlsCAFile=ca)
27
+ # The ismaster command is cheap and does not require auth.
28
+ client.admin.command('ismaster')
29
+ db = client["moodify_db"]
30
+ songs_collection = db["songs_by_emotion"]
31
+ logger.info(f"Successfully connected to MongoDB. Using database: '{db.name}' and collection: '{songs_collection.name}'")
32
+ except ConnectionFailure as e:
33
+ logger.error(f"MongoDB connection failed. Please check your MONGO_URI and network access. Error: {e}")
34
+ # Exit if we can't connect to the DB
35
+ exit()
36
+ except Exception as e:
37
+ logger.error(f"An unexpected error occurred during DB initialization: {e}")
38
+ exit()
39
+
40
+
41
+ app = Flask(__name__)
42
+ CORS(app)
43
+
44
+ # --- Model & Configuration ---
45
+ emotion_classifier = None
46
+ device = "cuda" if torch.cuda.is_available() else "cpu"
47
+
48
+ EMOTION_MAP = {
49
+ 'joy': 'happy',
50
+ 'sadness': 'sad',
51
+ 'anger': 'angry',
52
+ 'surprise': 'surprised',
53
+ 'neutral': 'neutral',
54
+ }
55
+
56
+ def initialize_model():
57
+ """Initializes the pre-trained emotion classification model."""
58
+ global emotion_classifier
59
+ try:
60
+ model_name = "j-hartmann/emotion-english-distilroberta-base"
61
+ logger.info(f"Loading model: {model_name} on device: {device}")
62
+
63
+ emotion_classifier = pipeline(
64
+ "text-classification",
65
+ model=model_name,
66
+ tokenizer=model_name,
67
+ device=0 if device == "cuda" else -1,
68
+ top_k=None,
69
+ max_length=512,
70
+ truncation=True
71
+ )
72
+ logger.info("Model loaded successfully!")
73
+ return True
74
+ except Exception as e:
75
+ logger.error(f"Fatal error loading model: {e}")
76
+ emotion_classifier = None
77
+ return False
78
+
79
+ def combine_responses(responses):
80
+ """Combine multiple text inputs into one."""
81
+ if not responses:
82
+ return ""
83
+ valid_responses = [resp.strip() for resp in responses if resp and resp.strip()]
84
+ combined_text = " . ".join(valid_responses)
85
+ words = combined_text.split()
86
+ if len(words) > 400:
87
+ combined_text = " ".join(words[:400])
88
+ return combined_text
89
+
90
+ def correct_spelling(text):
91
+ """Corrects spelling mistakes in the input text using TextBlob."""
92
+ if not text:
93
+ return ""
94
+ try:
95
+ # Create a TextBlob object and call the correct() method
96
+ corrected_blob = TextBlob(text).correct()
97
+ return str(corrected_blob)
98
+ except Exception as e:
99
+ logger.error(f"Error during spelling correction: {e}")
100
+ # Fallback to original text if correction fails
101
+ return text
102
+
103
+ def fetch_songs_by_emotion(emotion, limit=20):
104
+ """Fetch songs from MongoDB based on emotion with enhanced logging."""
105
+ try:
106
+ query_filter = {"emotion": emotion}
107
+ logger.info(f"Executing MongoDB find with filter: {query_filter}")
108
+
109
+ songs = list(songs_collection.find(query_filter, {"_id": 0}).limit(limit))
110
+
111
+ if not songs:
112
+ logger.warning(f"Query returned 0 songs for filter: {query_filter}")
113
+ case_insensitive_filter = {"emotion": {"$regex": f"^{emotion}$", "$options": "i"}}
114
+ case_insensitive_count = songs_collection.count_documents(case_insensitive_filter)
115
+ if case_insensitive_count > 0:
116
+ logger.warning(f"Hint: Found {case_insensitive_count} songs with case-insensitive match. Check for capitalization issues (e.g., 'Happy' vs 'happy').")
117
+ return []
118
+
119
+ logger.info(f"Query successfully found {len(songs)} songs for emotion: '{emotion}'")
120
+ random.shuffle(songs)
121
+ return songs
122
+ except Exception as e:
123
+ logger.error(f"Error during MongoDB query for emotion '{emotion}': {e}")
124
+ return []
125
+
126
+ def process_emotion_predictions(text):
127
+ """Analyzes text, filters for relevant emotions, maps them, and returns sorted results."""
128
+ raw_predictions = emotion_classifier(text)
129
+
130
+ mapped_predictions = []
131
+ for pred in raw_predictions[0]:
132
+ raw_emotion = pred['label'].lower()
133
+ if raw_emotion in EMOTION_MAP:
134
+ mapped_predictions.append({
135
+ 'emotion': EMOTION_MAP[raw_emotion],
136
+ 'confidence': round(pred['score'], 4)
137
+ })
138
+
139
+ # --- MODIFICATION START ---
140
+ # If no emotions from the EMOTION_MAP are found, fallback to 'neutral'.
141
+ if not mapped_predictions:
142
+ logger.warning(f"No mapped emotions found in predictions. Falling back to 'neutral'.")
143
+ return [{'emotion': 'neutral', 'confidence': 1.0}]
144
+ # --- END MODIFICATION ---
145
+
146
+ mapped_predictions.sort(key=lambda x: x['confidence'], reverse=True)
147
+ return mapped_predictions
148
+
149
+
150
+ @app.route('/health', methods=['GET'])
151
+ def health_check():
152
+ """Health check endpoint for server, model, and database status."""
153
+ try:
154
+ client.admin.command('ping')
155
+ db_status = "connected"
156
+ db_info = f"Using database '{db.name}' with {songs_collection.count_documents({})} songs."
157
+ except Exception as e:
158
+ db_status = "disconnected"
159
+ db_info = str(e)
160
+
161
+ return jsonify({
162
+ 'status': 'healthy',
163
+ 'model_status': "loaded" if emotion_classifier else "not loaded",
164
+ 'device': device,
165
+ 'database_status': db_status,
166
+ 'database_info': db_info
167
+ })
168
+
169
+ @app.route('/predict', methods=['POST'])
170
+ def predict_emotion():
171
+ """Predict emotion, return all relevant emotion scores, and provide songs."""
172
+ if not emotion_classifier:
173
+ return jsonify({'error': 'Model is not available. Please try again later.'}), 503
174
+
175
+ try:
176
+ data = request.get_json()
177
+ if not data or 'responses' not in data:
178
+ return jsonify({'error': 'Invalid input. Provide "responses" field in JSON.'}), 400
179
+
180
+ original_text = combine_responses(data.get('responses', []))
181
+ if not original_text.strip():
182
+ return jsonify({'error': 'Input text is empty after processing.'}), 400
183
+
184
+ logger.info(f"Original text received: '{original_text}'")
185
+ corrected_text = correct_spelling(original_text)
186
+ logger.info(f"Text after spell correction: '{corrected_text}'")
187
+
188
+ final_emotions = process_emotion_predictions(corrected_text)
189
+
190
+ # This check is now effectively redundant due to the fallback, but safe to keep.
191
+ if not final_emotions:
192
+ return jsonify({'error': 'Could not determine a relevant emotion from the provided text.'}), 400
193
+
194
+ primary_emotion_obj = final_emotions[0]
195
+ primary_emotion = primary_emotion_obj['emotion']
196
+
197
+ songs = fetch_songs_by_emotion(primary_emotion)
198
+
199
+ return jsonify({
200
+ 'primary_emotion': primary_emotion,
201
+ 'confidence': primary_emotion_obj['confidence'],
202
+ 'all_emotions': final_emotions,
203
+ 'original_text_preview': original_text[:150] + ('...' if len(original_text) > 150 else ''),
204
+ 'corrected_text_preview': corrected_text[:150] + ('...' if len(corrected_text) > 150 else ''),
205
+ 'songs': songs,
206
+ 'songs_count': len(songs)
207
+ })
208
+
209
+ except Exception as e:
210
+ logger.error(f"Error in prediction endpoint: {e}")
211
+ return jsonify({'error': f'Prediction failed: {str(e)}'}), 500
212
+
213
+ @app.route('/text_emotion/predict', methods=['POST'])
214
+ def predict_emotion_text():
215
+ if not emotion_classifier:
216
+ return jsonify({'error': 'Model is not available. Please try again later.'}), 503
217
+ try:
218
+ data = request.get_json()
219
+ if not data or 'responses' not in data:
220
+ return jsonify({'error': 'Invalid input. Provide "responses" field in JSON.'}), 400
221
+
222
+ original_text = combine_responses(data.get('responses', []))
223
+ if not original_text.strip():
224
+ return jsonify({'error': 'Input text is empty after processing.'}), 400
225
+
226
+ logger.info(f"Original text received: '{original_text}'")
227
+ corrected_text = correct_spelling(original_text)
228
+ logger.info(f"Text after spell correction: '{corrected_text}'")
229
+
230
+ final_emotions = process_emotion_predictions(corrected_text)
231
+
232
+ # This check is now effectively redundant due to the fallback, but safe to keep.
233
+ if not final_emotions:
234
+ return jsonify({'error': 'Could not determine a relevant emotion from the provided text.'}), 400
235
+ primary_emotion_obj = final_emotions[0]
236
+
237
+ return jsonify({
238
+ 'primary_emotion': primary_emotion_obj['emotion'],
239
+ 'confidence': primary_emotion_obj['confidence'],
240
+ 'all_emotions': final_emotions,
241
+ 'original_text_preview': original_text[:150] + ('...' if len(original_text) > 150 else ''),
242
+ 'corrected_text_preview': corrected_text[:150] + ('...' if len(corrected_text) > 150 else '')
243
+ })
244
+ except Exception as e:
245
+ logger.error(f"Error in text_emotion prediction: {e}")
246
+ return jsonify({'error': f'Prediction failed: {str(e)}'}), 500
247
+
248
+ @app.route('/songs/<emotion>', methods=['GET'])
249
+ def get_songs_by_emotion(emotion):
250
+ limit = request.args.get('limit', 20, type=int)
251
+ songs = fetch_songs_by_emotion(emotion.lower(), limit)
252
+ return jsonify({'emotion': emotion, 'songs': songs, 'count': len(songs)})
253
+
254
+ @app.route('/songs/all', methods=['GET'])
255
+ def get_all_emotions():
256
+ try:
257
+ emotions = sorted(songs_collection.distinct("emotion"))
258
+ emotion_counts = {emo: songs_collection.count_documents({"emotion": emo}) for emo in emotions}
259
+ return jsonify({'emotions': emotions, 'emotion_counts': emotion_counts})
260
+ except Exception as e:
261
+ logger.error(f"Error fetching all emotions: {e}")
262
+ return jsonify({'error': f'Failed to fetch emotions: {str(e)}'}), 500
263
+
264
+ if __name__ == '__main__':
265
+ logger.info("Starting Emotion Detection API...")
266
+ if emotion_classifier or initialize_model():
267
+ app.run(debug=True, host='0.0.0.0', port=5001)
268
+ else:
269
+ logger.error("Could not start the server because the model failed to initialize.")
270
+
271
+ # import logging
272
+ # from flask import Flask, request, jsonify
273
+ # from flask_cors import CORS
274
+ # from transformers import pipeline
275
+ # import torch
276
+ # from pymongo import MongoClient
277
+ # from pymongo.errors import ConnectionFailure
278
+ # import random
279
+ # import certifi
280
+ # from textblob import TextBlob # --- NEW ---
281
+
282
+ # # --- Set up logging ---
283
+ # logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
284
+ # logger = logging.getLogger(__name__)
285
+
286
+ # # --- Database Connection ---
287
+ # MONGO_URI = "mongodb+srv://soniyavitkar2712:soniya_27@cluster0.slai2ew.mongodb.net/?retryWrites=true&w=majority&appName=Cluster0"
288
+ # client = None
289
+ # db = None
290
+ # songs_collection = None
291
+
292
+ # try:
293
+ # logger.info("Attempting to connect to MongoDB Atlas...")
294
+ # # Use certifi to provide the SSL certificate
295
+ # ca = certifi.where()
296
+ # client = MongoClient(MONGO_URI, serverSelectionTimeoutMS=5000, tlsCAFile=ca)
297
+ # # The ismaster command is cheap and does not require auth.
298
+ # client.admin.command('ismaster')
299
+ # db = client["moodify_db"]
300
+ # songs_collection = db["songs_by_emotion"]
301
+ # logger.info(f"Successfully connected to MongoDB. Using database: '{db.name}' and collection: '{songs_collection.name}'")
302
+ # except ConnectionFailure as e:
303
+ # logger.error(f"MongoDB connection failed. Please check your MONGO_URI and network access. Error: {e}")
304
+ # # Exit if we can't connect to the DB
305
+ # exit()
306
+ # except Exception as e:
307
+ # logger.error(f"An unexpected error occurred during DB initialization: {e}")
308
+ # exit()
309
+
310
+
311
+ # app = Flask(__name__)
312
+ # CORS(app)
313
+
314
+ # # --- Model & Configuration ---
315
+ # emotion_classifier = None
316
+ # device = "cuda" if torch.cuda.is_available() else "cpu"
317
+
318
+ # EMOTION_MAP = {
319
+ # 'joy': 'happy',
320
+ # 'sadness': 'sad',
321
+ # 'anger': 'angry',
322
+ # 'surprise': 'surprised',
323
+ # 'neutral': 'neutral',
324
+ # }
325
+
326
+ # def initialize_model():
327
+ # """Initializes the pre-trained emotion classification model."""
328
+ # global emotion_classifier
329
+ # try:
330
+ # model_name = "j-hartmann/emotion-english-distilroberta-base"
331
+ # logger.info(f"Loading model: {model_name} on device: {device}")
332
+
333
+ # emotion_classifier = pipeline(
334
+ # "text-classification",
335
+ # model=model_name,
336
+ # tokenizer=model_name,
337
+ # device=0 if device == "cuda" else -1,
338
+ # top_k=None,
339
+ # max_length=512,
340
+ # truncation=True
341
+ # )
342
+ # logger.info("Model loaded successfully!")
343
+ # return True
344
+ # except Exception as e:
345
+ # logger.error(f"Fatal error loading model: {e}")
346
+ # emotion_classifier = None
347
+ # return False
348
+
349
+ # def combine_responses(responses):
350
+ # """Combine multiple text inputs into one."""
351
+ # if not responses:
352
+ # return ""
353
+ # valid_responses = [resp.strip() for resp in responses if resp and resp.strip()]
354
+ # combined_text = " . ".join(valid_responses)
355
+ # words = combined_text.split()
356
+ # if len(words) > 400:
357
+ # combined_text = " ".join(words[:400])
358
+ # return combined_text
359
+
360
+ # # --- NEW: Function to correct spelling ---
361
+ # def correct_spelling(text):
362
+ # """Corrects spelling mistakes in the input text using TextBlob."""
363
+ # if not text:
364
+ # return ""
365
+ # try:
366
+ # # Create a TextBlob object and call the correct() method
367
+ # corrected_blob = TextBlob(text).correct()
368
+ # return str(corrected_blob)
369
+ # except Exception as e:
370
+ # logger.error(f"Error during spelling correction: {e}")
371
+ # # Fallback to original text if correction fails
372
+ # return text
373
+
374
+ # def fetch_songs_by_emotion(emotion, limit=20):
375
+ # """Fetch songs from MongoDB based on emotion with enhanced logging."""
376
+ # try:
377
+ # query_filter = {"emotion": emotion}
378
+ # logger.info(f"Executing MongoDB find with filter: {query_filter}")
379
+
380
+ # songs = list(songs_collection.find(query_filter, {"_id": 0}).limit(limit))
381
+
382
+ # if not songs:
383
+ # logger.warning(f"Query returned 0 songs for filter: {query_filter}")
384
+ # case_insensitive_filter = {"emotion": {"$regex": f"^{emotion}$", "$options": "i"}}
385
+ # case_insensitive_count = songs_collection.count_documents(case_insensitive_filter)
386
+ # if case_insensitive_count > 0:
387
+ # logger.warning(f"Hint: Found {case_insensitive_count} songs with case-insensitive match. Check for capitalization issues (e.g., 'Happy' vs 'happy').")
388
+ # return []
389
+
390
+ # logger.info(f"Query successfully found {len(songs)} songs for emotion: '{emotion}'")
391
+ # random.shuffle(songs)
392
+ # return songs
393
+ # except Exception as e:
394
+ # logger.error(f"Error during MongoDB query for emotion '{emotion}': {e}")
395
+ # return []
396
+
397
+ # def process_emotion_predictions(text):
398
+ # """Analyzes text, filters for relevant emotions, maps them, and returns sorted results."""
399
+ # raw_predictions = emotion_classifier(text)
400
+
401
+ # mapped_predictions = []
402
+ # for pred in raw_predictions[0]:
403
+ # raw_emotion = pred['label'].lower()
404
+ # if raw_emotion in EMOTION_MAP:
405
+ # mapped_predictions.append({
406
+ # 'emotion': EMOTION_MAP[raw_emotion],
407
+ # 'confidence': round(pred['score'], 4)
408
+ # })
409
+
410
+ # if not mapped_predictions:
411
+ # return None
412
+
413
+ # mapped_predictions.sort(key=lambda x: x['confidence'], reverse=True)
414
+ # return mapped_predictions
415
+
416
+
417
+ # @app.route('/health', methods=['GET'])
418
+ # def health_check():
419
+ # """Health check endpoint for server, model, and database status."""
420
+ # try:
421
+ # client.admin.command('ping')
422
+ # db_status = "connected"
423
+ # db_info = f"Using database '{db.name}' with {songs_collection.count_documents({})} songs."
424
+ # except Exception as e:
425
+ # db_status = "disconnected"
426
+ # db_info = str(e)
427
+
428
+ # return jsonify({
429
+ # 'status': 'healthy',
430
+ # 'model_status': "loaded" if emotion_classifier else "not loaded",
431
+ # 'device': device,
432
+ # 'database_status': db_status,
433
+ # 'database_info': db_info
434
+ # })
435
+
436
+ # @app.route('/predict', methods=['POST'])
437
+ # def predict_emotion():
438
+ # """Predict emotion, return all relevant emotion scores, and provide songs."""
439
+ # if not emotion_classifier:
440
+ # return jsonify({'error': 'Model is not available. Please try again later.'}), 503
441
+
442
+ # try:
443
+ # data = request.get_json()
444
+ # if not data or 'responses' not in data:
445
+ # return jsonify({'error': 'Invalid input. Provide "responses" field in JSON.'}), 400
446
+
447
+ # original_text = combine_responses(data.get('responses', []))
448
+ # if not original_text.strip():
449
+ # return jsonify({'error': 'Input text is empty after processing.'}), 400
450
+
451
+ # # --- MODIFIED: Add spelling correction step ---
452
+ # logger.info(f"Original text received: '{original_text}'")
453
+ # corrected_text = correct_spelling(original_text)
454
+ # logger.info(f"Text after spell correction: '{corrected_text}'")
455
+
456
+ # final_emotions = process_emotion_predictions(corrected_text)
457
+ # # --- END MODIFICATION ---
458
+
459
+ # if not final_emotions:
460
+ # return jsonify({'error': 'Could not determine a relevant emotion from the provided text.'}), 400
461
+
462
+ # primary_emotion_obj = final_emotions[0]
463
+ # primary_emotion = primary_emotion_obj['emotion']
464
+
465
+ # songs = fetch_songs_by_emotion(primary_emotion)
466
+
467
+ # # --- MODIFIED: Add corrected text to the response for clarity ---
468
+ # return jsonify({
469
+ # 'primary_emotion': primary_emotion,
470
+ # 'confidence': primary_emotion_obj['confidence'],
471
+ # 'all_emotions': final_emotions,
472
+ # 'original_text_preview': original_text[:150] + ('...' if len(original_text) > 150 else ''),
473
+ # 'corrected_text_preview': corrected_text[:150] + ('...' if len(corrected_text) > 150 else ''),
474
+ # 'songs': songs,
475
+ # 'songs_count': len(songs)
476
+ # })
477
+
478
+ # except Exception as e:
479
+ # logger.error(f"Error in prediction endpoint: {e}")
480
+ # return jsonify({'error': f'Prediction failed: {str(e)}'}), 500
481
+
482
+ # @app.route('/text_emotion/predict', methods=['POST'])
483
+ # def predict_emotion_text():
484
+ # if not emotion_classifier:
485
+ # return jsonify({'error': 'Model is not available. Please try again later.'}), 503
486
+ # try:
487
+ # data = request.get_json()
488
+ # if not data or 'responses' not in data:
489
+ # return jsonify({'error': 'Invalid input. Provide "responses" field in JSON.'}), 400
490
+
491
+ # original_text = combine_responses(data.get('responses', []))
492
+ # if not original_text.strip():
493
+ # return jsonify({'error': 'Input text is empty after processing.'}), 400
494
+
495
+ # # --- MODIFIED: Add spelling correction step ---
496
+ # logger.info(f"Original text received: '{original_text}'")
497
+ # corrected_text = correct_spelling(original_text)
498
+ # logger.info(f"Text after spell correction: '{corrected_text}'")
499
+
500
+ # final_emotions = process_emotion_predictions(corrected_text)
501
+ # # --- END MODIFICATION ---
502
+
503
+ # if not final_emotions:
504
+ # return jsonify({'error': 'Could not determine a relevant emotion from the provided text.'}), 400
505
+ # primary_emotion_obj = final_emotions[0]
506
+
507
+ # # --- MODIFIED: Add corrected text to the response for clarity ---
508
+ # return jsonify({
509
+ # 'primary_emotion': primary_emotion_obj['emotion'],
510
+ # 'confidence': primary_emotion_obj['confidence'],
511
+ # 'all_emotions': final_emotions,
512
+ # 'original_text_preview': original_text[:150] + ('...' if len(original_text) > 150 else ''),
513
+ # 'corrected_text_preview': corrected_text[:150] + ('...' if len(corrected_text) > 150 else '')
514
+ # })
515
+ # except Exception as e:
516
+ # logger.error(f"Error in text_emotion prediction: {e}")
517
+ # return jsonify({'error': f'Prediction failed: {str(e)}'}), 500
518
+
519
+ # @app.route('/songs/<emotion>', methods=['GET'])
520
+ # def get_songs_by_emotion(emotion):
521
+ # limit = request.args.get('limit', 20, type=int)
522
+ # songs = fetch_songs_by_emotion(emotion.lower(), limit)
523
+ # return jsonify({'emotion': emotion, 'songs': songs, 'count': len(songs)})
524
+
525
+ # @app.route('/songs/all', methods=['GET'])
526
+ # def get_all_emotions():
527
+ # try:
528
+ # emotions = sorted(songs_collection.distinct("emotion"))
529
+ # emotion_counts = {emo: songs_collection.count_documents({"emotion": emo}) for emo in emotions}
530
+ # return jsonify({'emotions': emotions, 'emotion_counts': emotion_counts})
531
+ # except Exception as e:
532
+ # logger.error(f"Error fetching all emotions: {e}")
533
+ # return jsonify({'error': f'Failed to fetch emotions: {str(e)}'}), 500
534
+
535
+ # if __name__ == '__main__':
536
+ # logger.info("Starting Emotion Detection API...")
537
+ # if emotion_classifier or initialize_model():
538
+ # app.run(debug=True, host='0.0.0.0', port=5001)
539
+ # else:
540
+ # logger.error("Could not start the server because the model failed to initialize.")
textModel/checkMongo.py ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pymongo import MongoClient
2
+ import certifi
3
+ import os
4
+
5
+ # MongoDB connection string
6
+ MONGO_URI = os.getenv("MONGO_URI")
7
+ if not MONGO_URI:
8
+ raise ValueError("MONGO_URI environment variable is not set")
9
+
10
+ # Connect using certifi's CA bundle
11
+ client = MongoClient(MONGO_URI, tlsCAFile=certifi.where())
12
+
13
+ # Access database and collection
14
+ db = client["moodify_db"]
15
+ collection = db["songs_by_emotion"]
16
+
17
+ # Function to fetch songs by emotion
18
+ def get_songs_by_emotion(emotion):
19
+ results = collection.find({"emotion": emotion})
20
+ return list(results)
21
+
22
+ # Main
23
+ if __name__ == "__main__":
24
+ emotion = input("Enter an emotion (e.g. happy, sad, angry): ")
25
+ songs = get_songs_by_emotion(emotion)
26
+ for song in songs:
27
+ print(song)
textModel/fix_nltk.py ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Run this script to download required NLTK data
2
+ import nltk
3
+ import ssl
4
+
5
+ # Handle SSL certificate issues if they occur
6
+ try:
7
+ _create_unverified_https_context = ssl._create_unverified_context
8
+ except AttributeError:
9
+ pass
10
+ else:
11
+ ssl._create_default_https_context = _create_unverified_https_context
12
+
13
+ # Download required NLTK data
14
+ print("Downloading NLTK data...")
15
+
16
+ try:
17
+ # Download the newer punkt_tab tokenizer
18
+ nltk.download('punkt_tab')
19
+ print("✓ punkt_tab downloaded successfully")
20
+ except:
21
+ print("Failed to download punkt_tab, trying punkt...")
22
+ try:
23
+ nltk.download('punkt')
24
+ print("✓ punkt downloaded successfully")
25
+ except:
26
+ print("Failed to download punkt tokenizer")
27
+
28
+ try:
29
+ # Download stopwords
30
+ nltk.download('stopwords')
31
+ print("✓ stopwords downloaded successfully")
32
+ except:
33
+ print("Failed to download stopwords")
34
+
35
+ print("NLTK setup complete!")
36
+
37
+ # Test the downloads
38
+ try:
39
+ from nltk.tokenize import sent_tokenize
40
+ from nltk.corpus import stopwords
41
+
42
+ test_text = "Hello world. This is a test."
43
+ sentences = sent_tokenize(test_text)
44
+ stop_words = set(stopwords.words('english'))
45
+
46
+ print(f"\nTesting tokenization:")
47
+ print(f"Input: {test_text}")
48
+ print(f"Sentences: {sentences}")
49
+ print(f"Number of English stopwords: {len(stop_words)}")
50
+ print("\n✓ All NLTK components working correctly!")
51
+
52
+ except Exception as e:
53
+ print(f"\n❌ Error testing NLTK components: {e}")
54
+ print("You may need to run the downloads manually in Python:")
55
+ print(">>> import nltk")
56
+ print(">>> nltk.download('punkt_tab')")
57
+ print(">>> nltk.download('stopwords')")
textModel/requirements.txt ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ flask==2.3.3
2
+ flask-cors==4.0.0
3
+ torch>=1.9.0
4
+ transformers>=4.20.0
5
+ nltk>=3.8
6
+ numpy>=1.21.0
7
+ scipy>=1.7.0
8
+ scikit-learn>=1.0.0
9
+ requests>=2.25.0
10
+ tqdm>=4.62.0
11
+ cloudinary
12
+ gunicorn
13
+ flask-pymongo
14
+ os