TheOCEAN commited on
Commit
aa57b75
·
1 Parent(s): e6e5102

Add backend and frontend project files

Browse files
app.py CHANGED
@@ -1,101 +1,92 @@
1
- # -*- coding: utf-8 -*-
2
- """app.py
3
-
4
- Automatically generated by Colab.
5
-
6
- Original file is located at
7
- https://colab.research.google.com/drive/1E5d9dWFZwd3QoYrwG2SkR-slXGm2aMfL
8
- """
9
-
10
  import pickle
11
  import json
12
  import torch
13
- from fastapi import FastAPI
14
- from pydantic import BaseModel
15
  from transformers import BertTokenizer
 
 
16
 
17
  # 1. Initialize App and Load Assets
18
  # ==================================
19
- app = FastAPI(
20
- title="TEXI'OR Sentiment Analysis API",
21
- description="An API to classify text into different sentiments using a fine-tuned BERT model.",
22
- version="1.0"
23
- )
24
 
25
- # Pydantic model for input data validation
26
- class TextInput(BaseModel):
27
- text: str
28
 
29
- # Use CUDA if available, otherwise CPU
30
- device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
31
- print(f"✅ Using device: {device}")
 
 
 
 
32
 
33
- # Load the fine-tuned model and tokenizer
34
- try:
35
- with open('bert_sentiment_model (1).pkl', 'rb') as f:
36
- model = pickle.load(f)
37
- model.to(device)
38
- model.eval() # Set model to evaluation mode
39
- print("✅ Model 'bert_sentiment_model (1).pkl' loaded successfully.")
40
- except FileNotFoundError:
41
- print("❌ Model file not found. Make sure 'bert_sentiment_model (1).pkl' is uploaded.")
42
- model = None
43
 
44
  try:
45
- with open('label_dict.json', 'r') as f:
 
 
 
 
 
 
46
  label_dict = json.load(f)
47
- # Create an inverse mapping from index to label string (e.g., {0: "nocode"})
48
  idx2label = {int(v): k for k, v in label_dict.items()}
49
- print("✅ Label dictionary 'label_dict.json' loaded successfully.")
50
- except FileNotFoundError:
51
- print("❌ Label dictionary 'label_dict.json' not found.")
52
- idx2label = None
 
53
 
54
- tokenizer = BertTokenizer.from_pretrained('bert-base-uncased')
55
- print(" Tokenizer loaded successfully.")
56
 
57
 
58
  # 2. Define API Endpoints
59
  # ========================
60
- @app.get("/")
61
- def read_root():
62
- return {"message": "Welcome to the TEXI'OR API. Use the /predict endpoint to get a sentiment."}
63
-
64
- @app.post("/predict")
65
- def predict_sentiment(text_input: TextInput):
66
- """
67
- Predicts the sentiment of a given text.
68
- - Input: A JSON with a "text" field.
69
- - Output: A JSON with the predicted "sentiment" and "confidence" score.
70
- """
71
  if not all([model, idx2label, tokenizer]):
72
- return {"error": "API is not ready. A model, label, or tokenizer component failed to load."}
 
 
 
73
 
74
- text = text_input.text
 
75
 
76
- # Tokenize the input text
77
- inputs = tokenizer(
78
- text, padding=True, truncation=True, max_length=150, return_tensors='pt'
79
- )
80
 
81
- # Move tensors to the correct device
 
82
  input_ids = inputs['input_ids'].to(device)
83
  attention_mask = inputs['attention_mask'].to(device)
84
-
85
- # Get model predictions without calculating gradients
86
  with torch.no_grad():
87
  outputs = model(input_ids=input_ids, attention_mask=attention_mask)
88
 
89
- # Process the model output to get probabilities and the predicted class
90
  logits = outputs.logits
91
  probabilities = torch.nn.functional.softmax(logits, dim=-1)
92
  confidence, predicted_class_idx = torch.max(probabilities, dim=1)
93
-
94
- # Map the predicted index back to its string label
95
- predicted_label = idx2label.get(predicted_class_idx.item(), "Unknown Label")
96
  confidence_score = confidence.item()
97
 
98
- return {
99
  "sentiment": predicted_label,
100
  "confidence": round(confidence_score, 4)
101
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import pickle
2
  import json
3
  import torch
4
+ from flask import Flask, request, jsonify
5
+ # CORRECTED: Changed back to BertTokenizer to match your model
6
  from transformers import BertTokenizer
7
+ import io
8
+ from waitress import serve
9
 
10
  # 1. Initialize App and Load Assets
11
  # ==================================
12
+ app = Flask(__name__)
 
 
 
 
13
 
14
+ device = torch.device('cpu')
15
+ print("✅ Using device: cpu")
 
16
 
17
+ # Custom Unpickler to load a GPU-trained model onto a CPU
18
+ class CPU_Unpickler(pickle.Unpickler):
19
+ def find_class(self, module, name):
20
+ if module == 'torch.storage' and name == '_load_from_bytes':
21
+ return lambda b: torch.load(io.BytesIO(b), map_location='cpu')
22
+ else:
23
+ return super().find_class(module, name)
24
 
25
+ # Load model, dictionary, and tokenizer
26
+ model = None
27
+ idx2label = None
28
+ tokenizer = None
 
 
 
 
 
 
29
 
30
  try:
31
+ with open('bert_sentiment_model.pkl', 'rb') as f:
32
+ model = CPU_Unpickler(f).load()
33
+ model.to(device)
34
+ model.eval()
35
+ print("✅ Model 'bert_sentiment_model.pkl' loaded successfully.")
36
+
37
+ with open('Label_Dict.json', 'r') as f:
38
  label_dict = json.load(f)
 
39
  idx2label = {int(v): k for k, v in label_dict.items()}
40
+ print("✅ Label dictionary 'Label_Dict.json' loaded successfully.")
41
+
42
+ # CORRECTED: Use the original BERT tokenizer
43
+ tokenizer = BertTokenizer.from_pretrained('bert-base-uncased')
44
+ print("✅ Tokenizer loaded successfully.")
45
 
46
+ except Exception as e:
47
+ print(f" Error loading assets: {e}")
48
 
49
 
50
  # 2. Define API Endpoints
51
  # ========================
52
+ @app.route("/predict", methods=['POST'])
53
+ def predict_sentiment():
 
 
 
 
 
 
 
 
 
54
  if not all([model, idx2label, tokenizer]):
55
+ return jsonify({"error": "API is not ready. Check server logs."}), 500
56
+
57
+ if not request.is_json:
58
+ return jsonify({"error": "Request must be JSON"}), 400
59
 
60
+ data = request.get_json()
61
+ text = data.get("text")
62
 
63
+ if not text:
64
+ return jsonify({"error": "Missing 'text' field in request"}), 400
 
 
65
 
66
+ # Tokenize and predict
67
+ inputs = tokenizer(text, padding=True, truncation=True, max_length=150, return_tensors='pt')
68
  input_ids = inputs['input_ids'].to(device)
69
  attention_mask = inputs['attention_mask'].to(device)
70
+
 
71
  with torch.no_grad():
72
  outputs = model(input_ids=input_ids, attention_mask=attention_mask)
73
 
74
+ # Process output
75
  logits = outputs.logits
76
  probabilities = torch.nn.functional.softmax(logits, dim=-1)
77
  confidence, predicted_class_idx = torch.max(probabilities, dim=1)
78
+
79
+ predicted_label = idx2label.get(predicted_class_idx.item(), "Unknown")
 
80
  confidence_score = confidence.item()
81
 
82
+ return jsonify({
83
  "sentiment": predicted_label,
84
  "confidence": round(confidence_score, 4)
85
+ })
86
+
87
+ # 3. Run the Server
88
+ # ===================
89
+ if __name__ == "__main__":
90
+ print("🚀 Starting Flask server with Waitress...")
91
+ # Using 0.0.0.0 makes the server accessible on your local network
92
+ serve(app, host='0.0.0.0', port=5000)
requirements.txt CHANGED
@@ -1,14 +1,5 @@
1
- {
2
- "nocode": 0,
3
- "happy": 1,
4
- "not-relevant": 2,
5
- "angry": 3,
6
- "surprise": 4,
7
- "sad": 5,
8
- "happy|surprise": 6,
9
- "happy|sad": 7,
10
- "disgust|angry": 8,
11
- "disgust": 9,
12
- "sad|disgust": 10,
13
- "sad|angry": 11
14
- }
 
1
+ Flask
2
+ torch
3
+ transformers
4
+ scikit-learn
5
+ waitress
 
 
 
 
 
 
 
 
 
texior-app/.gitignore ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files
2
+
3
+ # dependencies
4
+ node_modules/
5
+
6
+ # Expo
7
+ .expo/
8
+ dist/
9
+ web-build/
10
+ expo-env.d.ts
11
+
12
+ # Native
13
+ .kotlin/
14
+ *.orig.*
15
+ *.jks
16
+ *.p8
17
+ *.p12
18
+ *.key
19
+ *.mobileprovision
20
+
21
+ # Metro
22
+ .metro-health-check*
23
+
24
+ # debug
25
+ npm-debug.*
26
+ yarn-debug.*
27
+ yarn-error.*
28
+
29
+ # macOS
30
+ .DS_Store
31
+ *.pem
32
+
33
+ # local env files
34
+ .env*.local
35
+
36
+ # typescript
37
+ *.tsbuildinfo
38
+
39
+ # generated native folders
40
+ /ios
41
+ /android
42
+ ==================================
43
+ Python & Flask Backend Specific
44
+ ==================================
45
+ Ignore the virtual environment folder
46
+ /texior-api/venv/
47
+
48
+ Ignore Python's compiled file cache
49
+ /texior-api/pycache/
50
+ /texior-api/*.pyc
51
+
52
+ ==================================
53
+ React Native & Expo Frontend Specific
54
+ ==================================
55
+ Ignore the massive node modules folder
56
+ /texior-app/node_modules/
57
+
58
+ Ignore local Expo configuration and cache
59
+ /texior-app/.expo/
60
+
61
+ Ignore log files
62
+ /texior-app/npm-debug.log
63
+ /texior-app/yarn-error.log
64
+
65
+ ==================================
66
+ General - IDE and OS Files
67
+ ==================================
68
+ Ignore VS Code editor settings
69
+ .vscode/
70
+
71
+ Ignore macOS generated files
72
+ .DS_Store
texior-app/App.js ADDED
@@ -0,0 +1,297 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { useState } from 'react';
2
+ import {
3
+ StyleSheet,
4
+ Text,
5
+ View,
6
+ TextInput,
7
+ TouchableOpacity,
8
+ ActivityIndicator,
9
+ Keyboard,
10
+ ScrollView,
11
+ SafeAreaView,
12
+ StatusBar,
13
+ Platform,
14
+ } from 'react-native';
15
+
16
+ const API_URL = "http://192.168.1.8:5000/predict"; // update if needed
17
+
18
+ export default function App() {
19
+ const [text, setText] = useState('');
20
+ const [sentiment, setSentiment] = useState(null);
21
+ const [confidence, setConfidence] = useState(null);
22
+ const [isLoading, setIsLoading] = useState(false);
23
+ const [error, setError] = useState(null);
24
+
25
+ const handlePredict = async () => {
26
+ if (text.trim() === '') {
27
+ setError('Please enter some text to analyze.');
28
+ return;
29
+ }
30
+
31
+ if (API_URL.includes("YOUR_COMPUTER_IP_ADDRESS")) {
32
+ setError("Please update the API_URL with your actual IP address.");
33
+ return;
34
+ }
35
+
36
+ Keyboard.dismiss();
37
+ setIsLoading(true);
38
+ setError(null);
39
+
40
+ try {
41
+ const response = await fetch(API_URL, {
42
+ method: 'POST',
43
+ headers: { 'Content-Type': 'application/json' },
44
+ body: JSON.stringify({ text }),
45
+ });
46
+
47
+ let data;
48
+ try {
49
+ data = await response.json();
50
+ } catch {
51
+ throw new Error('Invalid response from server. Expected JSON.');
52
+ }
53
+
54
+ if (response.ok) {
55
+ setSentiment(data.sentiment);
56
+ setConfidence(data.confidence);
57
+ // NOTE: we intentionally do NOT clear the input so text is retained
58
+ } else {
59
+ throw new Error(data.error || 'API returned an error.');
60
+ }
61
+ } catch (err) {
62
+ console.error(err);
63
+ setError(
64
+ 'Failed to connect to the API. Ensure your Flask server is running and your device is on the same Wi-Fi network.'
65
+ );
66
+ } finally {
67
+ setIsLoading(false);
68
+ }
69
+ };
70
+
71
+ const getSentimentStyle = (label) => {
72
+ switch (label?.toLowerCase()) {
73
+ case 'happy':
74
+ return { color: '#27ae60' };
75
+ case 'sad':
76
+ return { color: '#2980b9' };
77
+ case 'angry':
78
+ return { color: '#e74c3c' };
79
+ case 'surprise':
80
+ return { color: '#f1c40f' };
81
+ case 'disgust':
82
+ return { color: '#8e44ad' };
83
+ default:
84
+ return { color: '#7f8c8d' };
85
+ }
86
+ };
87
+
88
+ return (
89
+ <SafeAreaView style={styles.safeArea}>
90
+ <StatusBar
91
+ barStyle="dark-content"
92
+ backgroundColor={styles.safeArea.backgroundColor}
93
+ />
94
+ <ScrollView contentContainerStyle={styles.container}>
95
+ <View style={styles.header}>
96
+ <Text style={styles.title}>TEXI'OR</Text>
97
+ <Text style={styles.subtitle}>Mood & Sentiment Analyzer</Text>
98
+ </View>
99
+
100
+ <View style={styles.card}>
101
+ <TextInput
102
+ style={styles.input}
103
+ placeholder="How are you feeling today?"
104
+ placeholderTextColor="#9aa0a6"
105
+ value={text}
106
+ onChangeText={setText}
107
+ multiline
108
+ textAlignVertical="top"
109
+ />
110
+
111
+ <TouchableOpacity
112
+ style={[styles.button, isLoading && styles.buttonDisabled]}
113
+ onPress={handlePredict}
114
+ disabled={isLoading}
115
+ activeOpacity={0.85}
116
+ >
117
+ {isLoading ? (
118
+ <ActivityIndicator size="small" color="#fff" />
119
+ ) : (
120
+ <Text style={styles.buttonText}>Analyze Mood</Text>
121
+ )}
122
+ </TouchableOpacity>
123
+
124
+ {error ? <Text style={styles.errorText}>{error}</Text> : null}
125
+
126
+ {sentiment && Number.isFinite(confidence) && (
127
+ <View style={styles.resultContainer}>
128
+ <Text style={styles.resultLabel}>PREDICTED MOOD</Text>
129
+ <Text style={[styles.resultSentiment, getSentimentStyle(sentiment)]}>
130
+ {String(sentiment).toUpperCase()}
131
+ </Text>
132
+
133
+ <View style={styles.confidenceBarBackground}>
134
+ <View
135
+ style={[
136
+ styles.confidenceBarFill,
137
+ { width: `${Math.max(0, Math.min(100, (confidence * 100).toFixed(0)))}%` },
138
+ ]}
139
+ />
140
+ </View>
141
+
142
+ <Text style={styles.resultConfidence}>
143
+ {(Number.isFinite(confidence) ? (confidence * 100).toFixed(1) : '0.0')}% Confidence
144
+ </Text>
145
+ </View>
146
+ )}
147
+ </View>
148
+
149
+
150
+ </ScrollView>
151
+ </SafeAreaView>
152
+ );
153
+ }
154
+
155
+ const styles = StyleSheet.create({
156
+ safeArea: {
157
+ flex: 1,
158
+ backgroundColor: '#f4f6f8',
159
+ },
160
+ container: {
161
+ flexGrow: 1,
162
+ padding: 22,
163
+ alignItems: 'center',
164
+ justifyContent: 'center',
165
+ },
166
+ header: {
167
+ alignItems: 'center',
168
+ marginBottom: 18,
169
+ },
170
+ title: {
171
+ fontSize: 44,
172
+ fontWeight: '800',
173
+ color: '#243447',
174
+ letterSpacing: 1,
175
+ },
176
+ subtitle: {
177
+ fontSize: 16,
178
+ color: '#61707a',
179
+ marginTop: 6,
180
+ },
181
+
182
+ card: {
183
+ width: '100%',
184
+ backgroundColor: '#ffffff',
185
+ borderRadius: 16,
186
+ padding: 18,
187
+ // subtle shadow
188
+ ...Platform.select({
189
+ ios: {
190
+ shadowColor: '#000',
191
+ shadowOpacity: 0.06,
192
+ shadowOffset: { width: 0, height: 6 },
193
+ shadowRadius: 12,
194
+ },
195
+ android: {
196
+ elevation: 4,
197
+ },
198
+ }),
199
+ },
200
+
201
+ input: {
202
+ width: '100%',
203
+ minHeight: 120,
204
+ maxHeight: 220,
205
+ borderRadius: 12,
206
+ borderWidth: 1,
207
+ borderColor: '#e6ecef',
208
+ padding: 14,
209
+ fontSize: 16,
210
+ backgroundColor: '#fbfdff',
211
+ color: '#243447',
212
+ },
213
+
214
+ button: {
215
+ marginTop: 14,
216
+ backgroundColor: '#3276d6',
217
+ paddingVertical: 13,
218
+ borderRadius: 12,
219
+ alignItems: 'center',
220
+ justifyContent: 'center',
221
+ },
222
+ buttonDisabled: {
223
+ opacity: 0.78,
224
+ },
225
+ buttonText: {
226
+ color: '#fff',
227
+ fontWeight: '700',
228
+ fontSize: 16,
229
+ letterSpacing: 0.3,
230
+ },
231
+
232
+ errorText: {
233
+ marginTop: 12,
234
+ color: '#e04444',
235
+ fontSize: 14,
236
+ textAlign: 'center',
237
+ },
238
+
239
+ resultContainer: {
240
+ marginTop: 18,
241
+ alignItems: 'center',
242
+ width: '100%',
243
+ paddingVertical: 14,
244
+ paddingHorizontal: 10,
245
+ borderRadius: 12,
246
+ backgroundColor: '#fcfeff',
247
+ borderWidth: 1,
248
+ borderColor: '#e9f0f6',
249
+ },
250
+ resultLabel: {
251
+ fontSize: 12,
252
+ color: '#6b7780',
253
+ fontWeight: '600',
254
+ letterSpacing: 1,
255
+ },
256
+ resultSentiment: {
257
+ fontSize: 30,
258
+ marginTop: 8,
259
+ fontWeight: '800',
260
+ },
261
+
262
+ confidenceBarBackground: {
263
+ width: '92%',
264
+ height: 10,
265
+ borderRadius: 6,
266
+ backgroundColor: '#eef3f7',
267
+ marginTop: 12,
268
+ overflow: 'hidden',
269
+ },
270
+ confidenceBarFill: {
271
+ height: '100%',
272
+ backgroundColor: '#46a0ff',
273
+ },
274
+ resultConfidence: {
275
+ marginTop: 10,
276
+ fontSize: 15,
277
+ color: '#34495e',
278
+ fontWeight: '600',
279
+ },
280
+
281
+ footer: {
282
+ marginTop: 18,
283
+ alignItems: 'center',
284
+ },
285
+ footerText: {
286
+ color: '#8b99a2',
287
+ fontSize: 13,
288
+ },
289
+ code: {
290
+ fontFamily: Platform.select({ ios: 'Menlo', android: 'monospace' }),
291
+ backgroundColor: '#eef6ff',
292
+ paddingHorizontal: 6,
293
+ paddingVertical: 2,
294
+ borderRadius: 4,
295
+ color: '#2a6fdb',
296
+ },
297
+ });
texior-app/app.json ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "expo": {
3
+ "name": "texior-app",
4
+ "slug": "texior-app",
5
+ "version": "1.0.0",
6
+ "orientation": "portrait",
7
+ "icon": "./assets/icon.png",
8
+ "userInterfaceStyle": "light",
9
+ "newArchEnabled": true,
10
+ "splash": {
11
+ "image": "./assets/splash-icon.png",
12
+ "resizeMode": "contain",
13
+ "backgroundColor": "#ffffff"
14
+ },
15
+ "ios": {
16
+ "supportsTablet": true
17
+ },
18
+ "android": {
19
+ "adaptiveIcon": {
20
+ "foregroundImage": "./assets/adaptive-icon.png",
21
+ "backgroundColor": "#ffffff"
22
+ },
23
+ "edgeToEdgeEnabled": true
24
+ },
25
+ "web": {
26
+ "favicon": "./assets/favicon.png"
27
+ }
28
+ }
29
+ }
texior-app/assets/adaptive-icon.png ADDED
texior-app/assets/favicon.png ADDED
texior-app/assets/icon.png ADDED
texior-app/assets/splash-icon.png ADDED
texior-app/index.js ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ import { registerRootComponent } from 'expo';
2
+
3
+ import App from './App';
4
+
5
+ // registerRootComponent calls AppRegistry.registerComponent('main', () => App);
6
+ // It also ensures that whether you load the app in Expo Go or in a native build,
7
+ // the environment is set up appropriately
8
+ registerRootComponent(App);
texior-app/package-lock.json ADDED
The diff for this file is too large to render. See raw diff
 
texior-app/package.json ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "texior-app",
3
+ "license": "0BSD",
4
+ "version": "1.0.0",
5
+ "main": "index.js",
6
+ "scripts": {
7
+ "start": "expo start",
8
+ "android": "expo start --android",
9
+ "ios": "expo start --ios",
10
+ "web": "expo start --web"
11
+ },
12
+ "dependencies": {
13
+ "expo": "~54.0.12",
14
+ "expo-status-bar": "~3.0.8",
15
+ "react": "19.1.0",
16
+ "react-dom": "19.1.0",
17
+ "react-native": "0.81.4",
18
+ "react-native-linear-gradient": "^2.8.3",
19
+ "react-native-web": "^0.21.0"
20
+ },
21
+ "private": true
22
+ }