mahmdshafee commited on
Commit
ac96138
Β·
verified Β·
1 Parent(s): 282dc4a

Update app/frontend/src/App.jsx

Browse files
Files changed (1) hide show
  1. app/frontend/src/App.jsx +384 -384
app/frontend/src/App.jsx CHANGED
@@ -1,385 +1,385 @@
1
- import React, { useState, useEffect, useRef } from 'react';
2
- import { Send, Loader2, Heart, Frown, Smile, Zap, Meh, AlertCircle, TrendingUp, Moon, Sun } from 'lucide-react';
3
- import MovieCarousel from './components/MovieCarousel';
4
-
5
- const EmotionDetector = () => {
6
- const [text, setText] = useState('');
7
- const [result, setResult] = useState(null);
8
- const [movies, setMovies] = useState(null);
9
- const [loading, setLoading] = useState(false);
10
- const [error, setError] = useState(null);
11
- const [apiHealth, setApiHealth] = useState(null);
12
- const [isDark, setIsDark] = useState(true);
13
- const textareaRef = useRef(null);
14
-
15
- // API endpoint - change this to your deployed backend URL
16
- const API_URL = 'http://localhost:8000';
17
-
18
- // Emotion configurations with colors and icons
19
- const emotionConfig = {
20
- joy: { color: '#FFD700', gradient: 'from-yellow-400 to-amber-500', icon: Smile, emoji: '😊' },
21
- love: { color: '#FF69B4', gradient: 'from-pink-400 to-rose-500', icon: Heart, emoji: '❀️' },
22
- surprise: { color: '#9370DB', gradient: 'from-purple-400 to-violet-500', icon: Zap, emoji: '😲' },
23
- neutral: { color: '#94A3B8', gradient: 'from-slate-400 to-gray-500', icon: Meh, emoji: '😐' },
24
- sadness: { color: '#4682B4', gradient: 'from-blue-400 to-cyan-600', icon: Frown, emoji: '😒' },
25
- anger: { color: '#DC143C', gradient: 'from-red-500 to-orange-600', icon: AlertCircle, emoji: '😠' },
26
- fear: { color: '#8B4513', gradient: 'from-amber-700 to-orange-800', icon: TrendingUp, emoji: '😨' }
27
- };
28
-
29
- // Check API health on mount
30
- useEffect(() => {
31
- checkHealth();
32
- }, []);
33
-
34
- const checkHealth = async () => {
35
- try {
36
- const response = await fetch(`${API_URL}/health`);
37
- if (response.ok) {
38
- const data = await response.json();
39
- setApiHealth(data);
40
- }
41
- } catch (err) {
42
- console.error('Health check failed:', err);
43
- }
44
- };
45
-
46
- const handleSubmit = async (e) => {
47
- e.preventDefault();
48
-
49
- if (!text.trim()) {
50
- setError('Please enter some text');
51
- return;
52
- }
53
-
54
- if (text.length > 5000) {
55
- setError('Text too long (max 5000 characters)');
56
- return;
57
- }
58
-
59
- setLoading(true);
60
- setError(null);
61
- setResult(null);
62
- setMovies(null);
63
-
64
- try {
65
- // Call the new /recommendations endpoint
66
- const response = await fetch(`${API_URL}/recommendations`, {
67
- method: 'POST',
68
- headers: {
69
- 'Content-Type': 'application/json',
70
- },
71
- body: JSON.stringify({ text: text.trim() }),
72
- });
73
-
74
- if (!response.ok) {
75
- const errorData = await response.json();
76
- throw new Error(errorData.detail || 'Prediction failed');
77
- }
78
-
79
- const data = await response.json();
80
- setResult({
81
- emotion: data.emotion,
82
- confidence: data.confidence,
83
- });
84
- setMovies(data.recommendations);
85
-
86
- } catch (err) {
87
- setError(err.message || 'Failed to connect to server');
88
- console.error('Prediction error:', err);
89
- } finally {
90
- setLoading(false);
91
- }
92
- };
93
-
94
- const handleTextChange = (e) => {
95
- setText(e.target.value);
96
- setError(null);
97
- };
98
-
99
- const handleClear = () => {
100
- setText('');
101
- setResult(null);
102
- setMovies(null);
103
- setError(null);
104
- textareaRef.current?.focus();
105
- };
106
-
107
- const tryExample = (exampleText) => {
108
- setText(exampleText);
109
- setError(null);
110
- setResult(null);
111
- setMovies(null);
112
- };
113
-
114
- const exampleTexts = [
115
- "I'm so excited about my vacation next week!",
116
- "This situation makes me really frustrated and angry.",
117
- "I miss my family so much, feeling lonely today.",
118
- "You mean everything to me, I love you.",
119
- "I'm worried about the exam results tomorrow.",
120
- "Just another regular day at work.",
121
- "Wow! I can't believe this just happened!"
122
- ];
123
-
124
- // Dark theme: Blade Runner 2049 - Neon pink/purple cyberpunk
125
- // Light theme: Joker 2019 - Neon orange suit inspired
126
- const bgLight = 'bg-gradient-to-br from-orange-50 via-amber-50 to-red-50';
127
- const bgDark = 'bg-gradient-to-br from-slate-950 via-purple-950 to-slate-950';
128
-
129
- const headerLight = 'bg-gradient-to-r from-orange-500 via-red-500 to-orange-600 border-orange-400';
130
- const headerDark = 'bg-gradient-to-r from-fuchsia-900 via-purple-900 to-violet-900 border-fuchsia-600';
131
-
132
- const cardLight = 'bg-white border-orange-400 shadow-lg';
133
- const cardDark = 'bg-slate-900/80 border-fuchsia-500 shadow-2xl shadow-fuchsia-500/20';
134
-
135
- const sortedEmotions = result
136
- ? Object.entries(result.all_probabilities || {}).sort((a, b) => b[1] - a[1])
137
- : [];
138
-
139
- return (
140
- <div className={`min-h-screen transition-colors duration-300 ${isDark ? bgDark : bgLight}`}>
141
- {/* Header */}
142
- <div className={`border-b backdrop-blur-sm shadow-md ${isDark ? headerDark : headerLight}`}>
143
- <div className="max-w-6xl mx-auto px-4 py-4 sm:px-6 lg:px-8">
144
- <div className="flex items-center justify-between">
145
- <div className="flex items-center space-x-3">
146
- <div className={`w-10 h-10 rounded-xl flex items-center justify-center ${isDark ? 'bg-gradient-to-br from-fuchsia-500 to-purple-600' : 'bg-gradient-to-br from-orange-500 to-red-600'}`}>
147
- <Heart className="w-6 h-6 text-white" />
148
- </div>
149
- <div>
150
- <h1 className={`text-2xl font-bold ${isDark ? 'text-transparent bg-clip-text bg-gradient-to-r from-fuchsia-400 to-cyan-400' : 'text-transparent bg-clip-text bg-gradient-to-r from-orange-600 to-red-600'}`}>
151
- MoodFlix
152
- </h1>
153
- <p className={`text-xs ${isDark ? 'text-fuchsia-300' : 'text-orange-700'}`}>Emotion-based Movie Recommendations</p>
154
- </div>
155
- </div>
156
-
157
- <div className="flex items-center space-x-4">
158
- {apiHealth && (
159
- <div className={`hidden sm:flex items-center space-x-2 px-3 py-1 rounded-full border ${isDark ? 'bg-cyan-950/50 border-cyan-600' : 'bg-orange-50 border-orange-400'}`}>
160
- <div className="w-2 h-2 bg-green-500 rounded-full animate-pulse"></div>
161
- <span className={`text-xs font-medium ${isDark ? 'text-cyan-400' : 'text-orange-700'}`}>
162
- API Online
163
- </span>
164
- </div>
165
- )}
166
-
167
- {/* Theme Toggle */}
168
- <button
169
- onClick={() => setIsDark(!isDark)}
170
- className={`p-2 rounded-lg transition-all ${isDark ? 'bg-fuchsia-900/50 hover:bg-fuchsia-800 text-cyan-300 border border-fuchsia-600' : 'bg-orange-300 hover:bg-orange-400 text-gray-900 border border-orange-500'}`}
171
- >
172
- {isDark ? <Sun className="w-5 h-5" /> : <Moon className="w-5 h-5" />}
173
- </button>
174
- </div>
175
- </div>
176
- </div>
177
- </div>
178
-
179
- {/* Main Content */}
180
- <div className="max-w-6xl mx-auto px-4 py-6 sm:px-6 lg:px-8 sm:py-8">
181
- <div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
182
-
183
- {/* Left Column - Input */}
184
- <div className="lg:col-span-2 space-y-6">
185
- {/* Input Card */}
186
- <div className={`rounded-2xl border-2 overflow-hidden ${isDark ? cardDark : cardLight}`}>
187
- <div className="p-6">
188
- <h2 className={`text-lg font-semibold mb-4 ${isDark ? 'text-white' : 'text-gray-900'}`}>
189
- How are you feeling?
190
- </h2>
191
-
192
- <form onSubmit={handleSubmit} className="space-y-4">
193
- <div className="relative">
194
- <textarea
195
- ref={textareaRef}
196
- value={text}
197
- onChange={handleTextChange}
198
- placeholder="Share your thoughts or feelings... (e.g., 'I'm feeling amazing today!')"
199
- className={`w-full h-32 sm:h-40 px-4 py-3 border-2 rounded-xl focus:ring-4 transition-all resize-none ${
200
- isDark
201
- ? 'bg-slate-800 border-fuchsia-500 focus:border-cyan-400 focus:ring-fuchsia-500/20 text-white placeholder-slate-500'
202
- : 'bg-white border-orange-400 focus:border-red-500 focus:ring-orange-200 text-gray-900 placeholder-gray-400'
203
- }`}
204
- disabled={loading}
205
- />
206
- <div className={`absolute bottom-3 right-3 text-xs ${isDark ? 'text-gray-500' : 'text-gray-500'}`}>
207
- {text.length} / 5000
208
- </div>
209
- </div>
210
-
211
- {error && (
212
- <div className={`flex items-center space-x-2 p-3 border rounded-lg ${isDark ? 'bg-red-950/50 border-red-600' : 'bg-red-50 border-red-300'}`}>
213
- <AlertCircle className={`w-4 h-4 flex-shrink-0 ${isDark ? 'text-red-400' : 'text-red-600'}`} />
214
- <p className={`text-sm ${isDark ? 'text-red-300' : 'text-red-700'}`}>{error}</p>
215
- </div>
216
- )}
217
-
218
- <div className="flex flex-col sm:flex-row gap-3">
219
- <button
220
- type="submit"
221
- disabled={loading || !text.trim()}
222
- className={`flex-1 flex items-center justify-center space-x-2 px-6 py-3 rounded-xl font-medium shadow-lg hover:shadow-xl transform hover:scale-[1.02] transition-all disabled:opacity-50 disabled:cursor-not-allowed disabled:transform-none ${
223
- isDark
224
- ? 'bg-gradient-to-r from-fuchsia-600 to-purple-600 text-white hover:from-fuchsia-700 hover:to-purple-700 border border-fuchsia-500'
225
- : 'bg-gradient-to-r from-orange-500 to-red-500 text-white hover:from-orange-600 hover:to-red-600 border border-orange-400'
226
- }`}
227
- >
228
- {loading ? (
229
- <>
230
- <Loader2 className="w-5 h-5 animate-spin" />
231
- <span>Analyzing & Finding Movies...</span>
232
- </>
233
- ) : (
234
- <>
235
- <Send className="w-5 h-5" />
236
- <span>Detect & Suggest</span>
237
- </>
238
- )}
239
- </button>
240
-
241
- <button
242
- type="button"
243
- onClick={handleClear}
244
- disabled={loading}
245
- className={`px-6 py-3 rounded-xl font-medium transition-colors disabled:opacity-50 ${
246
- isDark
247
- ? 'bg-slate-800 text-slate-200 hover:bg-slate-700 border border-fuchsia-500'
248
- : 'bg-orange-200 text-gray-800 hover:bg-orange-300 border border-orange-400'
249
- }`}
250
- >
251
- Clear
252
- </button>
253
- </div>
254
- </form>
255
- </div>
256
- </div>
257
-
258
- {/* Results Card */}
259
- {result && (
260
- <div className={`rounded-2xl border-2 overflow-hidden animate-fadeIn ${isDark ? cardDark : cardLight}`}>
261
- <div className="p-6 space-y-6">
262
- {/* Primary Emotion */}
263
- <div>
264
- <h2 className={`text-lg font-semibold mb-4 ${isDark ? 'text-white' : 'text-gray-900'}`}>
265
- Your Emotion
266
- </h2>
267
- <div className={`relative p-6 rounded-xl bg-gradient-to-r ${emotionConfig[result.emotion].gradient} overflow-hidden`}>
268
- <div className="absolute top-0 right-0 text-8xl opacity-10">
269
- {emotionConfig[result.emotion].emoji}
270
- </div>
271
- <div className="relative">
272
- <div className="flex items-center space-x-3 mb-2">
273
- {React.createElement(emotionConfig[result.emotion].icon, {
274
- className: "w-8 h-8 text-white"
275
- })}
276
- <h3 className="text-3xl font-bold text-white capitalize">
277
- {result.emotion}
278
- </h3>
279
- </div>
280
- <p className="text-white/90 text-lg">
281
- Confidence: {(result.confidence * 100).toFixed(1)}%
282
- </p>
283
- </div>
284
- </div>
285
- </div>
286
- </div>
287
- </div>
288
- )}
289
-
290
- {/* Movies Carousels */}
291
- {movies && movies.length > 0 && (
292
- <div className="space-y-4 animate-fadeIn">
293
- <h2 className={`text-2xl font-bold ${isDark ? 'text-white' : 'text-gray-900'}`}>
294
- Movies For You
295
- </h2>
296
- {movies.map((genreMovies, idx) => (
297
- <MovieCarousel
298
- key={idx}
299
- genre={genreMovies.genre}
300
- movies={genreMovies.movies}
301
- isDark={isDark}
302
- />
303
- ))}
304
- </div>
305
- )}
306
- </div>
307
-
308
- {/* Right Column - Examples & Info */}
309
- <div className="space-y-6">
310
- {/* Try Examples */}
311
- <div className={`rounded-2xl border-2 p-6 overflow-hidden ${isDark ? cardDark : cardLight}`}>
312
- <h2 className={`text-lg font-semibold mb-4 ${isDark ? 'text-white' : 'text-gray-900'}`}>
313
- Try Examples
314
- </h2>
315
- <div className="space-y-2">
316
- {exampleTexts.map((example, idx) => (
317
- <button
318
- key={idx}
319
- onClick={() => tryExample(example)}
320
- disabled={loading}
321
- className={`w-full text-left px-3 py-2 text-sm rounded-lg transition-colors disabled:opacity-50 border ${
322
- isDark
323
- ? 'text-slate-300 hover:bg-fuchsia-900/30 hover:text-fuchsia-200 border-fuchsia-600'
324
- : 'text-gray-700 hover:bg-orange-100 hover:text-orange-700 border-orange-300'
325
- }`}
326
- >
327
- "{example.length > 60 ? example.slice(0, 60) + '...' : example}"
328
- </button>
329
- ))}
330
- </div>
331
- </div>
332
-
333
- {/* Emotion Legend */}
334
- <div className={`rounded-2xl border-2 p-6 overflow-hidden ${isDark ? cardDark : cardLight}`}>
335
- <h2 className={`text-lg font-semibold mb-4 ${isDark ? 'text-white' : 'text-gray-900'}`}>
336
- Emotions We Detect
337
- </h2>
338
- <div className="space-y-3">
339
- {Object.entries(emotionConfig).map(([emotion, config]) => (
340
- <div key={emotion} className="flex items-center space-x-3">
341
- <div className={`w-10 h-10 rounded-lg bg-gradient-to-r ${config.gradient} flex items-center justify-center text-xl`}>
342
- {config.emoji}
343
- </div>
344
- <div>
345
- <p className={`font-medium capitalize ${isDark ? 'text-white' : 'text-gray-900'}`}>
346
- {emotion}
347
- </p>
348
- </div>
349
- </div>
350
- ))}
351
- </div>
352
- </div>
353
-
354
- {/* Info Card */}
355
- <div className={`rounded-2xl border-2 p-6 text-white overflow-hidden ${isDark ? 'bg-gradient-to-br from-fuchsia-900 to-purple-900 border-fuchsia-600' : 'bg-gradient-to-br from-orange-500 to-red-600 border-orange-400'}`}>
356
- <h3 className="font-semibold mb-2">About MoodFlix</h3>
357
- <p className="text-sm text-white/90">
358
- AI-powered emotion detection meets cinema. Share your feelings and discover movies that match your mood. Powered by DeBERTa v3 and TMDB.
359
- </p>
360
- </div>
361
- </div>
362
- </div>
363
- </div>
364
-
365
- <style jsx>{`
366
- @keyframes fadeIn {
367
- from {
368
- opacity: 0;
369
- transform: translateY(10px);
370
- }
371
- to {
372
- opacity: 1;
373
- transform: translateY(0);
374
- }
375
- }
376
-
377
- .animate-fadeIn {
378
- animation: fadeIn 0.3s ease-out;
379
- }
380
- `}</style>
381
- </div>
382
- );
383
- };
384
-
385
  export default EmotionDetector;
 
1
+ import React, { useState, useEffect, useRef } from 'react';
2
+ import { Send, Loader2, Heart, Frown, Smile, Zap, Meh, AlertCircle, TrendingUp, Moon, Sun } from 'lucide-react';
3
+ import MovieCarousel from './components/MovieCarousel';
4
+
5
+ const EmotionDetector = () => {
6
+ const [text, setText] = useState('');
7
+ const [result, setResult] = useState(null);
8
+ const [movies, setMovies] = useState(null);
9
+ const [loading, setLoading] = useState(false);
10
+ const [error, setError] = useState(null);
11
+ const [apiHealth, setApiHealth] = useState(null);
12
+ const [isDark, setIsDark] = useState(true);
13
+ const textareaRef = useRef(null);
14
+
15
+ // API endpoint - change this to your deployed backend URL
16
+ const API_URL = import.meta.env.VITE_API_BASE_URL || 'http://localhost:8000';
17
+
18
+ // Emotion configurations with colors and icons
19
+ const emotionConfig = {
20
+ joy: { color: '#FFD700', gradient: 'from-yellow-400 to-amber-500', icon: Smile, emoji: '😊' },
21
+ love: { color: '#FF69B4', gradient: 'from-pink-400 to-rose-500', icon: Heart, emoji: '❀️' },
22
+ surprise: { color: '#9370DB', gradient: 'from-purple-400 to-violet-500', icon: Zap, emoji: '😲' },
23
+ neutral: { color: '#94A3B8', gradient: 'from-slate-400 to-gray-500', icon: Meh, emoji: '😐' },
24
+ sadness: { color: '#4682B4', gradient: 'from-blue-400 to-cyan-600', icon: Frown, emoji: '😒' },
25
+ anger: { color: '#DC143C', gradient: 'from-red-500 to-orange-600', icon: AlertCircle, emoji: '😠' },
26
+ fear: { color: '#8B4513', gradient: 'from-amber-700 to-orange-800', icon: TrendingUp, emoji: '😨' }
27
+ };
28
+
29
+ // Check API health on mount
30
+ useEffect(() => {
31
+ checkHealth();
32
+ }, []);
33
+
34
+ const checkHealth = async () => {
35
+ try {
36
+ const response = await fetch(`${API_URL}/health`);
37
+ if (response.ok) {
38
+ const data = await response.json();
39
+ setApiHealth(data);
40
+ }
41
+ } catch (err) {
42
+ console.error('Health check failed:', err);
43
+ }
44
+ };
45
+
46
+ const handleSubmit = async (e) => {
47
+ e.preventDefault();
48
+
49
+ if (!text.trim()) {
50
+ setError('Please enter some text');
51
+ return;
52
+ }
53
+
54
+ if (text.length > 5000) {
55
+ setError('Text too long (max 5000 characters)');
56
+ return;
57
+ }
58
+
59
+ setLoading(true);
60
+ setError(null);
61
+ setResult(null);
62
+ setMovies(null);
63
+
64
+ try {
65
+ // Call the new /recommendations endpoint
66
+ const response = await fetch(`${API_URL}/recommendations`, {
67
+ method: 'POST',
68
+ headers: {
69
+ 'Content-Type': 'application/json',
70
+ },
71
+ body: JSON.stringify({ text: text.trim() }),
72
+ });
73
+
74
+ if (!response.ok) {
75
+ const errorData = await response.json();
76
+ throw new Error(errorData.detail || 'Prediction failed');
77
+ }
78
+
79
+ const data = await response.json();
80
+ setResult({
81
+ emotion: data.emotion,
82
+ confidence: data.confidence,
83
+ });
84
+ setMovies(data.recommendations);
85
+
86
+ } catch (err) {
87
+ setError(err.message || 'Failed to connect to server');
88
+ console.error('Prediction error:', err);
89
+ } finally {
90
+ setLoading(false);
91
+ }
92
+ };
93
+
94
+ const handleTextChange = (e) => {
95
+ setText(e.target.value);
96
+ setError(null);
97
+ };
98
+
99
+ const handleClear = () => {
100
+ setText('');
101
+ setResult(null);
102
+ setMovies(null);
103
+ setError(null);
104
+ textareaRef.current?.focus();
105
+ };
106
+
107
+ const tryExample = (exampleText) => {
108
+ setText(exampleText);
109
+ setError(null);
110
+ setResult(null);
111
+ setMovies(null);
112
+ };
113
+
114
+ const exampleTexts = [
115
+ "I'm so excited about my vacation next week!",
116
+ "This situation makes me really frustrated and angry.",
117
+ "I miss my family so much, feeling lonely today.",
118
+ "You mean everything to me, I love you.",
119
+ "I'm worried about the exam results tomorrow.",
120
+ "Just another regular day at work.",
121
+ "Wow! I can't believe this just happened!"
122
+ ];
123
+
124
+ // Dark theme: Blade Runner 2049 - Neon pink/purple cyberpunk
125
+ // Light theme: Joker 2019 - Neon orange suit inspired
126
+ const bgLight = 'bg-gradient-to-br from-orange-50 via-amber-50 to-red-50';
127
+ const bgDark = 'bg-gradient-to-br from-slate-950 via-purple-950 to-slate-950';
128
+
129
+ const headerLight = 'bg-gradient-to-r from-orange-500 via-red-500 to-orange-600 border-orange-400';
130
+ const headerDark = 'bg-gradient-to-r from-fuchsia-900 via-purple-900 to-violet-900 border-fuchsia-600';
131
+
132
+ const cardLight = 'bg-white border-orange-400 shadow-lg';
133
+ const cardDark = 'bg-slate-900/80 border-fuchsia-500 shadow-2xl shadow-fuchsia-500/20';
134
+
135
+ const sortedEmotions = result
136
+ ? Object.entries(result.all_probabilities || {}).sort((a, b) => b[1] - a[1])
137
+ : [];
138
+
139
+ return (
140
+ <div className={`min-h-screen transition-colors duration-300 ${isDark ? bgDark : bgLight}`}>
141
+ {/* Header */}
142
+ <div className={`border-b backdrop-blur-sm shadow-md ${isDark ? headerDark : headerLight}`}>
143
+ <div className="max-w-6xl mx-auto px-4 py-4 sm:px-6 lg:px-8">
144
+ <div className="flex items-center justify-between">
145
+ <div className="flex items-center space-x-3">
146
+ <div className={`w-10 h-10 rounded-xl flex items-center justify-center ${isDark ? 'bg-gradient-to-br from-fuchsia-500 to-purple-600' : 'bg-gradient-to-br from-orange-500 to-red-600'}`}>
147
+ <Heart className="w-6 h-6 text-white" />
148
+ </div>
149
+ <div>
150
+ <h1 className={`text-2xl font-bold ${isDark ? 'text-transparent bg-clip-text bg-gradient-to-r from-fuchsia-400 to-cyan-400' : 'text-transparent bg-clip-text bg-gradient-to-r from-orange-600 to-red-600'}`}>
151
+ MoodFlix
152
+ </h1>
153
+ <p className={`text-xs ${isDark ? 'text-fuchsia-300' : 'text-orange-700'}`}>Emotion-based Movie Recommendations</p>
154
+ </div>
155
+ </div>
156
+
157
+ <div className="flex items-center space-x-4">
158
+ {apiHealth && (
159
+ <div className={`hidden sm:flex items-center space-x-2 px-3 py-1 rounded-full border ${isDark ? 'bg-cyan-950/50 border-cyan-600' : 'bg-orange-50 border-orange-400'}`}>
160
+ <div className="w-2 h-2 bg-green-500 rounded-full animate-pulse"></div>
161
+ <span className={`text-xs font-medium ${isDark ? 'text-cyan-400' : 'text-orange-700'}`}>
162
+ API Online
163
+ </span>
164
+ </div>
165
+ )}
166
+
167
+ {/* Theme Toggle */}
168
+ <button
169
+ onClick={() => setIsDark(!isDark)}
170
+ className={`p-2 rounded-lg transition-all ${isDark ? 'bg-fuchsia-900/50 hover:bg-fuchsia-800 text-cyan-300 border border-fuchsia-600' : 'bg-orange-300 hover:bg-orange-400 text-gray-900 border border-orange-500'}`}
171
+ >
172
+ {isDark ? <Sun className="w-5 h-5" /> : <Moon className="w-5 h-5" />}
173
+ </button>
174
+ </div>
175
+ </div>
176
+ </div>
177
+ </div>
178
+
179
+ {/* Main Content */}
180
+ <div className="max-w-6xl mx-auto px-4 py-6 sm:px-6 lg:px-8 sm:py-8">
181
+ <div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
182
+
183
+ {/* Left Column - Input */}
184
+ <div className="lg:col-span-2 space-y-6">
185
+ {/* Input Card */}
186
+ <div className={`rounded-2xl border-2 overflow-hidden ${isDark ? cardDark : cardLight}`}>
187
+ <div className="p-6">
188
+ <h2 className={`text-lg font-semibold mb-4 ${isDark ? 'text-white' : 'text-gray-900'}`}>
189
+ How are you feeling?
190
+ </h2>
191
+
192
+ <form onSubmit={handleSubmit} className="space-y-4">
193
+ <div className="relative">
194
+ <textarea
195
+ ref={textareaRef}
196
+ value={text}
197
+ onChange={handleTextChange}
198
+ placeholder="Share your thoughts or feelings... (e.g., 'I'm feeling amazing today!')"
199
+ className={`w-full h-32 sm:h-40 px-4 py-3 border-2 rounded-xl focus:ring-4 transition-all resize-none ${
200
+ isDark
201
+ ? 'bg-slate-800 border-fuchsia-500 focus:border-cyan-400 focus:ring-fuchsia-500/20 text-white placeholder-slate-500'
202
+ : 'bg-white border-orange-400 focus:border-red-500 focus:ring-orange-200 text-gray-900 placeholder-gray-400'
203
+ }`}
204
+ disabled={loading}
205
+ />
206
+ <div className={`absolute bottom-3 right-3 text-xs ${isDark ? 'text-gray-500' : 'text-gray-500'}`}>
207
+ {text.length} / 5000
208
+ </div>
209
+ </div>
210
+
211
+ {error && (
212
+ <div className={`flex items-center space-x-2 p-3 border rounded-lg ${isDark ? 'bg-red-950/50 border-red-600' : 'bg-red-50 border-red-300'}`}>
213
+ <AlertCircle className={`w-4 h-4 flex-shrink-0 ${isDark ? 'text-red-400' : 'text-red-600'}`} />
214
+ <p className={`text-sm ${isDark ? 'text-red-300' : 'text-red-700'}`}>{error}</p>
215
+ </div>
216
+ )}
217
+
218
+ <div className="flex flex-col sm:flex-row gap-3">
219
+ <button
220
+ type="submit"
221
+ disabled={loading || !text.trim()}
222
+ className={`flex-1 flex items-center justify-center space-x-2 px-6 py-3 rounded-xl font-medium shadow-lg hover:shadow-xl transform hover:scale-[1.02] transition-all disabled:opacity-50 disabled:cursor-not-allowed disabled:transform-none ${
223
+ isDark
224
+ ? 'bg-gradient-to-r from-fuchsia-600 to-purple-600 text-white hover:from-fuchsia-700 hover:to-purple-700 border border-fuchsia-500'
225
+ : 'bg-gradient-to-r from-orange-500 to-red-500 text-white hover:from-orange-600 hover:to-red-600 border border-orange-400'
226
+ }`}
227
+ >
228
+ {loading ? (
229
+ <>
230
+ <Loader2 className="w-5 h-5 animate-spin" />
231
+ <span>Analyzing & Finding Movies...</span>
232
+ </>
233
+ ) : (
234
+ <>
235
+ <Send className="w-5 h-5" />
236
+ <span>Detect & Suggest</span>
237
+ </>
238
+ )}
239
+ </button>
240
+
241
+ <button
242
+ type="button"
243
+ onClick={handleClear}
244
+ disabled={loading}
245
+ className={`px-6 py-3 rounded-xl font-medium transition-colors disabled:opacity-50 ${
246
+ isDark
247
+ ? 'bg-slate-800 text-slate-200 hover:bg-slate-700 border border-fuchsia-500'
248
+ : 'bg-orange-200 text-gray-800 hover:bg-orange-300 border border-orange-400'
249
+ }`}
250
+ >
251
+ Clear
252
+ </button>
253
+ </div>
254
+ </form>
255
+ </div>
256
+ </div>
257
+
258
+ {/* Results Card */}
259
+ {result && (
260
+ <div className={`rounded-2xl border-2 overflow-hidden animate-fadeIn ${isDark ? cardDark : cardLight}`}>
261
+ <div className="p-6 space-y-6">
262
+ {/* Primary Emotion */}
263
+ <div>
264
+ <h2 className={`text-lg font-semibold mb-4 ${isDark ? 'text-white' : 'text-gray-900'}`}>
265
+ Your Emotion
266
+ </h2>
267
+ <div className={`relative p-6 rounded-xl bg-gradient-to-r ${emotionConfig[result.emotion].gradient} overflow-hidden`}>
268
+ <div className="absolute top-0 right-0 text-8xl opacity-10">
269
+ {emotionConfig[result.emotion].emoji}
270
+ </div>
271
+ <div className="relative">
272
+ <div className="flex items-center space-x-3 mb-2">
273
+ {React.createElement(emotionConfig[result.emotion].icon, {
274
+ className: "w-8 h-8 text-white"
275
+ })}
276
+ <h3 className="text-3xl font-bold text-white capitalize">
277
+ {result.emotion}
278
+ </h3>
279
+ </div>
280
+ <p className="text-white/90 text-lg">
281
+ Confidence: {(result.confidence * 100).toFixed(1)}%
282
+ </p>
283
+ </div>
284
+ </div>
285
+ </div>
286
+ </div>
287
+ </div>
288
+ )}
289
+
290
+ {/* Movies Carousels */}
291
+ {movies && movies.length > 0 && (
292
+ <div className="space-y-4 animate-fadeIn">
293
+ <h2 className={`text-2xl font-bold ${isDark ? 'text-white' : 'text-gray-900'}`}>
294
+ Movies For You
295
+ </h2>
296
+ {movies.map((genreMovies, idx) => (
297
+ <MovieCarousel
298
+ key={idx}
299
+ genre={genreMovies.genre}
300
+ movies={genreMovies.movies}
301
+ isDark={isDark}
302
+ />
303
+ ))}
304
+ </div>
305
+ )}
306
+ </div>
307
+
308
+ {/* Right Column - Examples & Info */}
309
+ <div className="space-y-6">
310
+ {/* Try Examples */}
311
+ <div className={`rounded-2xl border-2 p-6 overflow-hidden ${isDark ? cardDark : cardLight}`}>
312
+ <h2 className={`text-lg font-semibold mb-4 ${isDark ? 'text-white' : 'text-gray-900'}`}>
313
+ Try Examples
314
+ </h2>
315
+ <div className="space-y-2">
316
+ {exampleTexts.map((example, idx) => (
317
+ <button
318
+ key={idx}
319
+ onClick={() => tryExample(example)}
320
+ disabled={loading}
321
+ className={`w-full text-left px-3 py-2 text-sm rounded-lg transition-colors disabled:opacity-50 border ${
322
+ isDark
323
+ ? 'text-slate-300 hover:bg-fuchsia-900/30 hover:text-fuchsia-200 border-fuchsia-600'
324
+ : 'text-gray-700 hover:bg-orange-100 hover:text-orange-700 border-orange-300'
325
+ }`}
326
+ >
327
+ "{example.length > 60 ? example.slice(0, 60) + '...' : example}"
328
+ </button>
329
+ ))}
330
+ </div>
331
+ </div>
332
+
333
+ {/* Emotion Legend */}
334
+ <div className={`rounded-2xl border-2 p-6 overflow-hidden ${isDark ? cardDark : cardLight}`}>
335
+ <h2 className={`text-lg font-semibold mb-4 ${isDark ? 'text-white' : 'text-gray-900'}`}>
336
+ Emotions We Detect
337
+ </h2>
338
+ <div className="space-y-3">
339
+ {Object.entries(emotionConfig).map(([emotion, config]) => (
340
+ <div key={emotion} className="flex items-center space-x-3">
341
+ <div className={`w-10 h-10 rounded-lg bg-gradient-to-r ${config.gradient} flex items-center justify-center text-xl`}>
342
+ {config.emoji}
343
+ </div>
344
+ <div>
345
+ <p className={`font-medium capitalize ${isDark ? 'text-white' : 'text-gray-900'}`}>
346
+ {emotion}
347
+ </p>
348
+ </div>
349
+ </div>
350
+ ))}
351
+ </div>
352
+ </div>
353
+
354
+ {/* Info Card */}
355
+ <div className={`rounded-2xl border-2 p-6 text-white overflow-hidden ${isDark ? 'bg-gradient-to-br from-fuchsia-900 to-purple-900 border-fuchsia-600' : 'bg-gradient-to-br from-orange-500 to-red-600 border-orange-400'}`}>
356
+ <h3 className="font-semibold mb-2">About MoodFlix</h3>
357
+ <p className="text-sm text-white/90">
358
+ AI-powered emotion detection meets cinema. Share your feelings and discover movies that match your mood. Powered by DeBERTa v3 and TMDB.
359
+ </p>
360
+ </div>
361
+ </div>
362
+ </div>
363
+ </div>
364
+
365
+ <style jsx>{`
366
+ @keyframes fadeIn {
367
+ from {
368
+ opacity: 0;
369
+ transform: translateY(10px);
370
+ }
371
+ to {
372
+ opacity: 1;
373
+ transform: translateY(0);
374
+ }
375
+ }
376
+
377
+ .animate-fadeIn {
378
+ animation: fadeIn 0.3s ease-out;
379
+ }
380
+ `}</style>
381
+ </div>
382
+ );
383
+ };
384
+
385
  export default EmotionDetector;