u-kuro commited on
Commit
5863ef1
·
1 Parent(s): 1775e8d

Refactor project structure: update .gitignore, switch from FastAPI to Flask, and enhance index.html styling

Browse files
Files changed (6) hide show
  1. .gitignore +3 -3
  2. Dockerfile +1 -1
  3. app.ipynb +4 -4
  4. app.py +7 -8
  5. requirements.txt +1 -2
  6. templates/index.html +119 -52
.gitignore CHANGED
@@ -1,3 +1,3 @@
1
- .env
2
- .vercel
3
- **/.ipynb_checkpoints/
 
1
+ **/.env
2
+ **/.ipynb_checkpoints
3
+ **/__pycache__
Dockerfile CHANGED
@@ -10,4 +10,4 @@ COPY --chown=user ./requirements.txt requirements.txt
10
  RUN pip install --no-cache-dir --user -r requirements.txt
11
 
12
  COPY --chown=user . /app
13
- CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860"]
 
10
  RUN pip install --no-cache-dir --user -r requirements.txt
11
 
12
  COPY --chown=user . /app
13
+ CMD ["gunicorn", "-w", "4", "-b", "0.0.0.0:7860", "app:app"]
app.ipynb CHANGED
@@ -40,12 +40,11 @@
40
  "MODEL_NAME = os.environ['MODEL_NAME']\n",
41
  "TOKEN = os.environ['HF_TOKEN']\n",
42
  "\n",
43
- "ismain = __name__ == '__main__'\n",
44
  "app = Flask(__name__)\n",
45
  "model = None\n",
46
  "tokenizer = None\n",
47
  "\n",
48
- "if ismain and (model is None or tokenizer is None):\n",
49
  " with app.app_context():\n",
50
  " print(\"Loading model...\")\n",
51
  " tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME, token=TOKEN)\n",
@@ -65,6 +64,7 @@
65
  " \n",
66
  " outputs = model(**encoding)\n",
67
  " _, predicted = torch.max(outputs.logits, 1)\n",
 
68
  " sentiment_score = int((predicted - 1).cpu().numpy()[0])\n",
69
  " \n",
70
  " return sentiment_score\n",
@@ -103,8 +103,8 @@
103
  "def home():\n",
104
  " return render_template(\"index.html\")\n",
105
  "\n",
106
- "if ismain:\n",
107
- " app.run(host=\"0.0.0.0\", port=7860)"
108
  ]
109
  },
110
  {
 
40
  "MODEL_NAME = os.environ['MODEL_NAME']\n",
41
  "TOKEN = os.environ['HF_TOKEN']\n",
42
  "\n",
 
43
  "app = Flask(__name__)\n",
44
  "model = None\n",
45
  "tokenizer = None\n",
46
  "\n",
47
+ "if model is None or tokenizer is None:\n",
48
  " with app.app_context():\n",
49
  " print(\"Loading model...\")\n",
50
  " tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME, token=TOKEN)\n",
 
64
  " \n",
65
  " outputs = model(**encoding)\n",
66
  " _, predicted = torch.max(outputs.logits, 1)\n",
67
+ " \n",
68
  " sentiment_score = int((predicted - 1).cpu().numpy()[0])\n",
69
  " \n",
70
  " return sentiment_score\n",
 
103
  "def home():\n",
104
  " return render_template(\"index.html\")\n",
105
  "\n",
106
+ "if __name__ == '__main__':\n",
107
+ " app.run(host=\"0.0.0.0\", port=7860, debug=True)"
108
  ]
109
  },
110
  {
app.py CHANGED
@@ -1,19 +1,17 @@
1
  import os, torch
2
- from flask import request, jsonify, render_template
3
  from transformers import AutoTokenizer, AutoModelForSequenceClassification
4
  from dotenv import load_dotenv
5
- from fastapi import FastAPI
6
 
7
  load_dotenv()
8
  MODEL_NAME = os.environ['MODEL_NAME']
9
  TOKEN = os.environ['HF_TOKEN']
10
 
11
- ismain = __name__ == '__main__'
12
- app = FastAPI()
13
  model = None
14
  tokenizer = None
15
 
16
- if ismain and (model is None or tokenizer is None):
17
  with app.app_context():
18
  print("Loading model...")
19
  tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME, token=TOKEN)
@@ -33,6 +31,7 @@ def get_sentiment_score(text):
33
 
34
  outputs = model(**encoding)
35
  _, predicted = torch.max(outputs.logits, 1)
 
36
  sentiment_score = int((predicted - 1).cpu().numpy()[0])
37
 
38
  return sentiment_score
@@ -50,7 +49,7 @@ def predict():
50
  try:
51
  data = request.json
52
  text = data.get('text', '').strip()
53
-
54
  if not text:
55
  return jsonify({'error': 'Please provide text to analyze'}), 400
56
 
@@ -68,8 +67,8 @@ def predict():
68
  return jsonify({'error': 'An error occurred during prediction'}), 500
69
 
70
  @app.route('/')
71
- def home(request):
72
  return render_template("index.html")
73
 
74
- if ismain:
75
  app.run(host="0.0.0.0", port=7860, debug=True)
 
1
  import os, torch
2
+ from flask import Flask, request, jsonify, render_template
3
  from transformers import AutoTokenizer, AutoModelForSequenceClassification
4
  from dotenv import load_dotenv
 
5
 
6
  load_dotenv()
7
  MODEL_NAME = os.environ['MODEL_NAME']
8
  TOKEN = os.environ['HF_TOKEN']
9
 
10
+ app = Flask(__name__)
 
11
  model = None
12
  tokenizer = None
13
 
14
+ if model is None or tokenizer is None:
15
  with app.app_context():
16
  print("Loading model...")
17
  tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME, token=TOKEN)
 
31
 
32
  outputs = model(**encoding)
33
  _, predicted = torch.max(outputs.logits, 1)
34
+
35
  sentiment_score = int((predicted - 1).cpu().numpy()[0])
36
 
37
  return sentiment_score
 
49
  try:
50
  data = request.json
51
  text = data.get('text', '').strip()
52
+
53
  if not text:
54
  return jsonify({'error': 'Please provide text to analyze'}), 400
55
 
 
67
  return jsonify({'error': 'An error occurred during prediction'}), 500
68
 
69
  @app.route('/')
70
+ def home():
71
  return render_template("index.html")
72
 
73
+ if __name__ == '__main__':
74
  app.run(host="0.0.0.0", port=7860, debug=True)
requirements.txt CHANGED
@@ -1,5 +1,4 @@
1
- fastapi
2
- uvicorn[standard]
3
  Flask
4
  transformers
5
  torch
 
1
+ gunicorn
 
2
  Flask
3
  transformers
4
  torch
templates/index.html CHANGED
@@ -22,6 +22,9 @@
22
  }
23
 
24
  .container {
 
 
 
25
  background: white;
26
  border-radius: 20px;
27
  box-shadow: 0 20px 40px rgba(0,0,0,0.1);
@@ -29,22 +32,28 @@
29
  max-width: 600px;
30
  width: 100%;
31
  text-align: center;
 
32
  }
33
 
34
- h1 {
35
  color: #333;
36
- margin-bottom: 30px;
37
  font-size: 2.5rem;
38
  font-weight: 700;
39
  }
40
 
41
  .subtitle {
42
  color: #666;
43
- margin-bottom: 30px;
44
  font-size: 1.1rem;
45
  }
 
 
 
 
 
 
 
46
 
47
- textarea {
48
  width: 100%;
49
  min-height: 120px;
50
  padding: 15px;
@@ -56,13 +65,13 @@
56
  transition: border-color 0.3s;
57
  }
58
 
59
- textarea:focus {
60
  outline: none;
61
  border-color: #667eea;
62
  box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
63
  }
64
 
65
- button {
66
  background: linear-gradient(135deg, #ba1b1b 0%, #314ed5 100%);
67
  color: white;
68
  border: none;
@@ -70,27 +79,52 @@
70
  font-size: 18px;
71
  border-radius: 50px;
72
  cursor: pointer;
73
- margin-top: 20px;
74
  transition: transform 0.2s, box-shadow 0.2s;
75
  font-weight: 600;
 
 
76
  }
77
 
78
- button:hover {
79
  transform: translateY(-2px);
80
  box-shadow: 0 10px 20px rgba(102, 126, 234, 0.3);
81
  }
82
 
83
- button:disabled {
84
  background: #ccc;
85
  cursor: not-allowed;
86
  transform: none;
87
  box-shadow: none;
88
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
89
 
90
  .loading {
91
- display: none;
92
- margin-top: 20px;
93
  color: #667eea;
 
 
 
94
  }
95
 
96
  .loading .spinner {
@@ -101,7 +135,6 @@
101
  border-top: 3px solid #667eea;
102
  border-radius: 50%;
103
  animation: spin 1s linear infinite;
104
- margin-right: 10px;
105
  }
106
 
107
  @keyframes spin {
@@ -110,10 +143,12 @@
110
  }
111
 
112
  .result {
113
- margin-top: 30px;
 
 
 
114
  padding: 20px;
115
  border-radius: 12px;
116
- display: none;
117
  }
118
 
119
  .result.positive {
@@ -133,7 +168,6 @@
133
 
134
  .result h3 {
135
  font-size: 1.5rem;
136
- margin-bottom: 10px;
137
  }
138
 
139
  .result p {
@@ -147,8 +181,6 @@
147
  color: #cc0000;
148
  padding: 15px;
149
  border-radius: 8px;
150
- margin-top: 20px;
151
- display: none;
152
  }
153
 
154
  @media (max-width: 600px) {
@@ -156,61 +188,87 @@
156
  padding: 30px 20px;
157
  }
158
 
159
- h1 {
160
  font-size: 2rem;
161
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
162
  }
163
  </style>
164
  </head>
165
  <body>
166
  <div class="container">
167
- <h1>🎭 Sentiment Analysis</h1>
168
  <p class="subtitle">Enter your text below to analyze its sentiment</p>
169
 
170
- <form id="sentimentForm">
171
- <textarea
172
- id="textInput"
 
173
  placeholder="Type your text here..."
174
  ></textarea>
175
 
176
- <button type="submit" id="analyzeBtn">
177
  Analyze Sentiment
178
  </button>
179
  </form>
180
 
181
- <div class="loading" id="loading">
182
- <div class="spinner"></div>
183
- <span>Analyzing sentiment...</span>
184
- </div>
185
-
186
- <div class="result" id="result">
187
- <h3 id="sentimentLabel"></h3>
188
- <p id="sentimentDescription"></p>
 
 
189
  </div>
190
-
191
- <div class="error" id="error"></div>
192
  </div>
193
 
194
  <script>
195
- const form = document.getElementById('sentimentForm');
196
- const textInput = document.getElementById('textInput');
197
- const analyzeBtn = document.getElementById('analyzeBtn');
198
  const loading = document.getElementById('loading');
199
  const result = document.getElementById('result');
200
  const error = document.getElementById('error');
201
 
202
- // Form submission
203
  form.addEventListener('submit', async function(e) {
204
  e.preventDefault();
205
 
 
 
 
 
206
  const text = textInput.value.trim();
207
  if (!text) {
208
  showError('Please enter some text to analyze');
209
  return;
210
  }
211
 
212
- showLoading();
213
-
214
  try {
215
  const response = await fetch('/predict', {
216
  method: 'POST',
@@ -228,28 +286,27 @@
228
  showError(data.error || 'An error occurred');
229
  }
230
 
231
- } catch (err) {
 
232
  showError('Network error. Please try again.');
233
  }
234
  });
235
 
236
  function showLoading() {
237
- analyzeBtn.disabled = true;
238
- loading.style.display = 'block';
239
- result.style.display = 'none';
240
- error.style.display = 'none';
241
  }
242
 
243
  function showResult(data) {
244
- analyzeBtn.disabled = false;
245
- loading.style.display = 'none';
246
 
247
  const sentimentLabel = document.getElementById('sentimentLabel');
248
  const sentimentDescription = document.getElementById('sentimentDescription');
249
 
250
  sentimentLabel.textContent = data.sentiment_label;
251
 
252
- // Set description based on sentiment
253
  let description = '';
254
  let className = '';
255
 
@@ -270,16 +327,26 @@
270
 
271
  sentimentDescription.textContent = description;
272
  result.className = 'result ' + className;
273
- result.style.display = 'block';
 
 
274
  }
275
 
276
  function showError(message) {
277
- analyzeBtn.disabled = false;
278
- loading.style.display = 'none';
279
- result.style.display = 'none';
280
 
281
  error.textContent = message;
282
- error.style.display = 'block';
 
 
 
 
 
 
 
 
 
 
283
  }
284
  </script>
285
  </body>
 
22
  }
23
 
24
  .container {
25
+ display: grid;
26
+ grid-template-columns: 1fr;
27
+ gap: 30px;
28
  background: white;
29
  border-radius: 20px;
30
  box-shadow: 0 20px 40px rgba(0,0,0,0.1);
 
32
  max-width: 600px;
33
  width: 100%;
34
  text-align: center;
35
+ user-select: none;
36
  }
37
 
38
+ .title {
39
  color: #333;
 
40
  font-size: 2.5rem;
41
  font-weight: 700;
42
  }
43
 
44
  .subtitle {
45
  color: #666;
 
46
  font-size: 1.1rem;
47
  }
48
+
49
+ .sentiment-form {
50
+ display: grid;
51
+ gap: 20px;
52
+ grid-template-rows: 1fr auto;
53
+ grid-template-columns: 1fr;
54
+ }
55
 
56
+ .text-input {
57
  width: 100%;
58
  min-height: 120px;
59
  padding: 15px;
 
65
  transition: border-color 0.3s;
66
  }
67
 
68
+ text-input:focus {
69
  outline: none;
70
  border-color: #667eea;
71
  box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
72
  }
73
 
74
+ .submit {
75
  background: linear-gradient(135deg, #ba1b1b 0%, #314ed5 100%);
76
  color: white;
77
  border: none;
 
79
  font-size: 18px;
80
  border-radius: 50px;
81
  cursor: pointer;
 
82
  transition: transform 0.2s, box-shadow 0.2s;
83
  font-weight: 600;
84
+ width: fit-content;
85
+ justify-self: center;
86
  }
87
 
88
+ .submit:hover {
89
  transform: translateY(-2px);
90
  box-shadow: 0 10px 20px rgba(102, 126, 234, 0.3);
91
  }
92
 
93
+ .submit:disabled {
94
  background: #ccc;
95
  cursor: not-allowed;
96
  transform: none;
97
  box-shadow: none;
98
  }
99
+
100
+ .result-container {
101
+ height: 100px;
102
+ position: relative;
103
+ }
104
+
105
+ .result-container > * {
106
+ position: absolute;
107
+ left: 50%;
108
+ top: 50%;
109
+ transform: translate(-50%, -50%);
110
+ width: 100%;
111
+ display: flex;
112
+ flex-direction: column;
113
+ justify-content: center;
114
+ align-items: center;
115
+ max-height: 100px;
116
+ overflow: clip;
117
+ }
118
+
119
+ .result-container:not(:has(> :not(.hide))) {
120
+ display: none !important;
121
+ }
122
 
123
  .loading {
 
 
124
  color: #667eea;
125
+ flex-direction: row;
126
+ height: 30px;
127
+ gap: 5px;
128
  }
129
 
130
  .loading .spinner {
 
135
  border-top: 3px solid #667eea;
136
  border-radius: 50%;
137
  animation: spin 1s linear infinite;
 
138
  }
139
 
140
  @keyframes spin {
 
143
  }
144
 
145
  .result {
146
+ position: absolute;
147
+ top: 50%;
148
+ left: 50%;
149
+ transform: translate(-50%, -50%);
150
  padding: 20px;
151
  border-radius: 12px;
 
152
  }
153
 
154
  .result.positive {
 
168
 
169
  .result h3 {
170
  font-size: 1.5rem;
 
171
  }
172
 
173
  .result p {
 
181
  color: #cc0000;
182
  padding: 15px;
183
  border-radius: 8px;
 
 
184
  }
185
 
186
  @media (max-width: 600px) {
 
188
  padding: 30px 20px;
189
  }
190
 
191
+ .title {
192
  font-size: 2rem;
193
  }
194
+
195
+ .subtitle {
196
+ font-size: 0.88rem;
197
+ }
198
+
199
+ .text-input {
200
+ font-size: 12.44px;
201
+ }
202
+
203
+ .submit {
204
+ font-size: 14px;
205
+ }
206
+
207
+ .sentimentLabel {
208
+ font-size: 1.2rem;
209
+ }
210
+
211
+ .sentimentDescription {
212
+ font-size: 0.88rem;
213
+ }
214
+ }
215
+
216
+ .hide {
217
+ display: none !important;
218
  }
219
  </style>
220
  </head>
221
  <body>
222
  <div class="container">
223
+ <h1 class="title">🎭 Sentiment Analysis</h1>
224
  <p class="subtitle">Enter your text below to analyze its sentiment</p>
225
 
226
+ <form class="sentiment-form" id="sentiment-form">
227
+ <textarea
228
+ class="text-input"
229
+ id="text-input"
230
  placeholder="Type your text here..."
231
  ></textarea>
232
 
233
+ <button class="submit" type="submit" id="submit-btn">
234
  Analyze Sentiment
235
  </button>
236
  </form>
237
 
238
+ <div class="result-container">
239
+ <div class="loading hide" id="loading">
240
+ <div class="spinner"></div>
241
+ <span>Analyzing sentiment...</span>
242
+ </div>
243
+ <div class="result hide" id="result">
244
+ <h3 id="sentimentLabel"></h3>
245
+ <p id="sentimentDescription"></p>
246
+ </div>
247
+ <div class="error hide" id="error"></div>
248
  </div>
 
 
249
  </div>
250
 
251
  <script>
252
+ const form = document.getElementById('sentiment-form');
253
+ const textInput = document.getElementById('text-input');
254
+ const analyzeBtn = document.getElementById('submit-btn');
255
  const loading = document.getElementById('loading');
256
  const result = document.getElementById('result');
257
  const error = document.getElementById('error');
258
 
 
259
  form.addEventListener('submit', async function(e) {
260
  e.preventDefault();
261
 
262
+ analyzeBtn.disabled = true;
263
+ textInput.focus();
264
+ showLoading();
265
+
266
  const text = textInput.value.trim();
267
  if (!text) {
268
  showError('Please enter some text to analyze');
269
  return;
270
  }
271
 
 
 
272
  try {
273
  const response = await fetch('/predict', {
274
  method: 'POST',
 
286
  showError(data.error || 'An error occurred');
287
  }
288
 
289
+ } catch (error) {
290
+ console.error(error);
291
  showError('Network error. Please try again.');
292
  }
293
  });
294
 
295
  function showLoading() {
296
+ hide(result);
297
+ hide(error);
298
+
299
+ show(loading);
300
  }
301
 
302
  function showResult(data) {
303
+ hide(loading);
 
304
 
305
  const sentimentLabel = document.getElementById('sentimentLabel');
306
  const sentimentDescription = document.getElementById('sentimentDescription');
307
 
308
  sentimentLabel.textContent = data.sentiment_label;
309
 
 
310
  let description = '';
311
  let className = '';
312
 
 
327
 
328
  sentimentDescription.textContent = description;
329
  result.className = 'result ' + className;
330
+ show(result);
331
+
332
+ analyzeBtn.disabled = false;
333
  }
334
 
335
  function showError(message) {
336
+ hide(loading);
 
 
337
 
338
  error.textContent = message;
339
+ show(error);
340
+
341
+ analyzeBtn.disabled = false;
342
+ }
343
+
344
+ function show(element) {
345
+ element.classList.remove('hide');
346
+ }
347
+
348
+ function hide(element) {
349
+ element.classList.add('hide');
350
  }
351
  </script>
352
  </body>