ganna217 commited on
Commit
a254917
·
1 Parent(s): 1c2a247

Add Twitter Sentiment Analysis app files

Browse files
Files changed (7) hide show
  1. Dockerfile +12 -0
  2. app.py +92 -0
  3. best_model.h5 +3 -0
  4. requirements..txt +5 -0
  5. static/index.html +261 -0
  6. static/twitter_bird.png +0 -0
  7. tokenizer.pickle +3 -0
Dockerfile ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Dockerfile
2
+ FROM python:3.9-slim
3
+
4
+ WORKDIR /app
5
+
6
+ COPY . /app
7
+
8
+ RUN pip install -r requirements.txt
9
+
10
+ EXPOSE 8000
11
+
12
+ CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8000"]
app.py ADDED
@@ -0,0 +1,92 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # app.py
2
+ from fastapi import FastAPI, HTTPException
3
+ from pydantic import BaseModel
4
+ from contextlib import asynccontextmanager
5
+ import numpy as np
6
+ from tensorflow.keras.models import load_model
7
+ from tensorflow.keras.preprocessing.sequence import pad_sequences
8
+ import pickle
9
+ import os
10
+ from fastapi.middleware.cors import CORSMiddleware
11
+ from fastapi.staticfiles import StaticFiles
12
+ from fastapi.responses import FileResponse
13
+
14
+ # Global variables
15
+ model = None
16
+ tokenizer = None
17
+
18
+ # Lifespan event handler
19
+ @asynccontextmanager
20
+ async def lifespan(app: FastAPI):
21
+ global model, tokenizer
22
+
23
+ model_path = "best_model.h5"
24
+ tokenizer_path = "tokenizer.pickle"
25
+
26
+ if not os.path.exists(model_path):
27
+ raise FileNotFoundError(f"Model file not found at {model_path}")
28
+ if not os.path.exists(tokenizer_path):
29
+ raise FileNotFoundError(f"Tokenizer file not found at {tokenizer_path}")
30
+
31
+ model = load_model(model_path)
32
+ with open(tokenizer_path, 'rb') as handle:
33
+ tokenizer = pickle.load(handle)
34
+
35
+ yield
36
+
37
+ # Initialize FastAPI app
38
+ app = FastAPI(
39
+ title="Twitter Sentiment Analysis",
40
+ description="API and frontend for predicting sentiment of tweets",
41
+ version="1.0.0",
42
+ lifespan=lifespan
43
+ )
44
+
45
+ # Add CORS middleware
46
+ app.add_middleware(
47
+ CORSMiddleware,
48
+ allow_origins=["*"], # Allows all origins (adjust for production)
49
+ allow_credentials=True,
50
+ allow_methods=["*"],
51
+ allow_headers=["*"],
52
+ )
53
+
54
+ # Mount static files directory for serving index.html and twitter_bird.png
55
+ app.mount("/static", StaticFiles(directory="static"), name="static")
56
+
57
+ # Serve index.html at the root
58
+ @app.get("/")
59
+ async def serve_index():
60
+ return FileResponse("static/index.html")
61
+
62
+ # Define request body model
63
+ class TweetRequest(BaseModel):
64
+ text: str
65
+
66
+ # Define prediction function
67
+ def predict_sentiment(text: str):
68
+ sentiment_classes = ['Negative', 'Neutral', 'Positive']
69
+ max_len = 50
70
+
71
+ xt = tokenizer.texts_to_sequences([text])
72
+ xt = pad_sequences(xt, padding='post', maxlen=max_len)
73
+ yt = model.predict(xt).argmax(axis=1)
74
+
75
+ return sentiment_classes[yt[0]]
76
+
77
+ # Health check endpoint
78
+ @app.get("/health")
79
+ async def health_check():
80
+ return {"status": "healthy"}
81
+
82
+ # Prediction endpoint
83
+ @app.post("/predict")
84
+ async def predict(tweet: TweetRequest):
85
+ try:
86
+ prediction = predict_sentiment(tweet.text)
87
+ return {
88
+ "text": tweet.text,
89
+ "sentiment": prediction
90
+ }
91
+ except Exception as e:
92
+ raise HTTPException(status_code=500, detail=f"Prediction error: {str(e)}")
best_model.h5 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:bfb6052a2d0102a3b4648043ca94475004537a5e9fc3cb97433a29a9bb218115
3
+ size 1480912
requirements..txt ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ fastapi
2
+ uvicorn
3
+ tensorflow
4
+ numpy
5
+ pydantic
static/index.html ADDED
@@ -0,0 +1,261 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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>Twitter Sentiment Analysis</title>
7
+ <style>
8
+ /* General Styles */
9
+ body {
10
+ font-family: 'Segoe UI', Arial, sans-serif;
11
+ margin: 0;
12
+ padding: 20px;
13
+ background: linear-gradient(135deg, #e1f5fe 0%, #ffffff 100%); /* Light blue Twitter gradient */
14
+ min-height: 100vh;
15
+ display: flex;
16
+ flex-direction: column;
17
+ justify-content: center;
18
+ align-items: center;
19
+ }
20
+
21
+ /* Header with Twitter Bird */
22
+ .header {
23
+ display: flex;
24
+ align-items: center;
25
+ gap: 15px;
26
+ margin-bottom: 30px;
27
+ }
28
+
29
+ .header img {
30
+ width: 80px; /* Increased size */
31
+ height: 80px;
32
+ }
33
+
34
+ h2 {
35
+ color: #1da1f2; /* Twitter blue */
36
+ font-size: 2.8em;
37
+ text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.1);
38
+ margin: 0;
39
+ }
40
+
41
+ /* Card Container for Input */
42
+ .card {
43
+ background-color: white;
44
+ padding: 20px;
45
+ border-radius: 15px;
46
+ box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
47
+ width: 100%;
48
+ max-width: 600px;
49
+ margin-bottom: 20px;
50
+ }
51
+
52
+ .input-container {
53
+ display: flex;
54
+ gap: 15px;
55
+ align-items: center;
56
+ }
57
+
58
+ input[type="text"] {
59
+ flex: 1;
60
+ padding: 12px;
61
+ border: 2px solid #e0e0e0;
62
+ border-radius: 8px;
63
+ font-size: 1em;
64
+ outline: none;
65
+ transition: border-color 0.3s, box-shadow 0.3s;
66
+ }
67
+
68
+ input[type="text"]:focus {
69
+ border-color: #1da1f2; /* Twitter blue */
70
+ box-shadow: 0 0 5px rgba(29, 161, 242, 0.3);
71
+ }
72
+
73
+ button {
74
+ padding: 12px 25px;
75
+ background: linear-gradient(45deg, #1da1f2, #00acee); /* Twitter gradient */
76
+ color: white;
77
+ border: none;
78
+ border-radius: 8px;
79
+ cursor: pointer;
80
+ font-size: 1em;
81
+ display: flex;
82
+ align-items: center;
83
+ gap: 8px;
84
+ transition: background 0.3s, transform 0.2s;
85
+ }
86
+
87
+ button:hover {
88
+ background: linear-gradient(45deg, #00acee, #1da1f2);
89
+ transform: translateY(-2px);
90
+ }
91
+
92
+ button:disabled {
93
+ background: #cccccc;
94
+ cursor: not-allowed;
95
+ transform: none;
96
+ }
97
+
98
+ /* Spinner */
99
+ .spinner {
100
+ display: none;
101
+ width: 16px;
102
+ height: 16px;
103
+ border: 2px solid white;
104
+ border-top: 2px solid transparent;
105
+ border-radius: 50%;
106
+ animation: spin 1s linear infinite;
107
+ }
108
+
109
+ button:disabled .spinner {
110
+ display: inline-block;
111
+ }
112
+
113
+ button:disabled span {
114
+ display: none;
115
+ }
116
+
117
+ @keyframes spin {
118
+ 0% { transform: rotate(0deg); }
119
+ 100% { transform: rotate(360deg); }
120
+ }
121
+
122
+ /* Result Styles */
123
+ #result {
124
+ font-size: 1.6em;
125
+ min-height: 1.6em;
126
+ opacity: 0;
127
+ transition: opacity 0.5s ease-in-out;
128
+ }
129
+
130
+ #result.show {
131
+ opacity: 1;
132
+ }
133
+
134
+ #result b {
135
+ padding: 8px 15px;
136
+ border-radius: 8px;
137
+ display: inline-flex;
138
+ align-items: center;
139
+ }
140
+
141
+ #result.positive b {
142
+ color: white;
143
+ background-color: #4CAF50;
144
+ }
145
+
146
+ #result.negative b {
147
+ color: white;
148
+ background-color: #f44336;
149
+ }
150
+
151
+ #result.neutral b {
152
+ color: white;
153
+ background-color: #9e9e9e;
154
+ }
155
+
156
+ #result.loading {
157
+ color: #1da1f2;
158
+ font-style: italic;
159
+ }
160
+
161
+ /* Responsive Design */
162
+ @media (max-width: 480px) {
163
+ h2 {
164
+ font-size: 2em;
165
+ }
166
+
167
+ .header img {
168
+ width: 50px; /* Smaller logo on mobile */
169
+ height: 50px;
170
+ }
171
+
172
+ .card {
173
+ padding: 15px;
174
+ }
175
+
176
+ .input-container {
177
+ flex-direction: column;
178
+ gap: 10px;
179
+ }
180
+
181
+ input[type="text"] {
182
+ width: 100%;
183
+ }
184
+
185
+ button {
186
+ width: 100%;
187
+ }
188
+
189
+ #result {
190
+ font-size: 1.2em;
191
+ }
192
+ }
193
+ </style>
194
+ </head>
195
+ <body>
196
+ <div class="header">
197
+ <img src="/static/twitter_bird.png" alt="Twitter Bird">
198
+ <h2>Twitter Sentiment Analysis</h2>
199
+ </div>
200
+
201
+ <div class="card">
202
+ <div class="input-container">
203
+ <input type="text" id="textInput" placeholder="Enter your tweet...">
204
+ <button onclick="analyzeSentiment()">
205
+ <span>Analyze</span>
206
+ <div class="spinner"></div>
207
+ </button>
208
+ </div>
209
+ </div>
210
+
211
+ <div id="result"></div>
212
+
213
+ <script>
214
+ async function analyzeSentiment() {
215
+ let text = document.getElementById("textInput").value;
216
+ if (!text) {
217
+ alert("Please enter some text");
218
+ return;
219
+ }
220
+
221
+ // Show loading state
222
+ let resultDiv = document.getElementById("result");
223
+ resultDiv.className = "loading";
224
+ resultDiv.innerHTML = "Analyzing...";
225
+ resultDiv.classList.remove("show");
226
+
227
+ // Disable button during analysis
228
+ let button = document.querySelector("button");
229
+ button.disabled = true;
230
+
231
+ try {
232
+ let response = await fetch("http://127.0.0.1:8000/predict", {
233
+ method: "POST",
234
+ headers: {
235
+ "Content-Type": "application/json"
236
+ },
237
+ body: JSON.stringify({ text: text })
238
+ });
239
+
240
+ if (!response.ok) {
241
+ throw new Error("Network response was not ok");
242
+ }
243
+
244
+ let data = await response.json();
245
+ resultDiv.className = data.sentiment.toLowerCase();
246
+ resultDiv.innerHTML = `Prediction: <b>${data.sentiment}</b>`;
247
+ resultDiv.classList.add("show");
248
+ } catch (error) {
249
+ console.error("Error:", error);
250
+ resultDiv.className = "";
251
+ resultDiv.innerHTML = "Error analyzing sentiment";
252
+ resultDiv.classList.add("show");
253
+ } finally {
254
+ // Re-enable button
255
+ button.disabled = false;
256
+ }
257
+ }
258
+ </script>
259
+
260
+ </body>
261
+ </html>
static/twitter_bird.png ADDED
tokenizer.pickle ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:65b7d3a03ad1cb555befbc5b71b676df7ce3bef454b8744487f2833472ebfdfe
3
+ size 4994706