Sharathhebbar24 commited on
Commit
0509f82
·
1 Parent(s): f154c9d

Sentiment Analysis

Browse files
.gitignore ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2
+
3
+ # dependencies
4
+ /node_modules
5
+ /.pnp
6
+ .pnp.js
7
+
8
+ # testing
9
+ /coverage
10
+
11
+ # production
12
+ /build
13
+
14
+ # misc
15
+ .DS_Store
16
+ .env.local
17
+ .env.development.local
18
+ .env.test.local
19
+ .env.production.local
20
+
21
+ npm-debug.log*
22
+ yarn-debug.log*
23
+ yarn-error.log*
Dockerfile ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Use an official Node.js runtime as a parent image
2
+ FROM node:16
3
+
4
+ # Set the working directory
5
+ WORKDIR /app
6
+
7
+ # Copy package.json and install dependencies
8
+ COPY package.json .
9
+ RUN npm install
10
+
11
+ # Copy the rest of the application code
12
+ COPY . .
13
+
14
+ # Build the React app
15
+ RUN npm run build
16
+
17
+ # Use an Nginx image to serve the built app
18
+ FROM nginx:alpine
19
+ COPY --from=0 /app/build /usr/share/nginx/html
20
+
21
+ # Expose the port Nginx runs on
22
+ EXPOSE 80
23
+
24
+ # Start Nginx
25
+ CMD ["nginx", "-g", "daemon off;"]
package-lock.json ADDED
The diff for this file is too large to render. See raw diff
 
package.json ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "sentimental-analysis",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "dependencies": {
6
+ "@testing-library/dom": "^10.4.0",
7
+ "@testing-library/jest-dom": "^6.6.3",
8
+ "@testing-library/react": "^16.3.0",
9
+ "@testing-library/user-event": "^13.5.0",
10
+ "react": "^19.1.0",
11
+ "react-dom": "^19.1.0",
12
+ "react-scripts": "5.0.1",
13
+ "web-vitals": "^2.1.4",
14
+ "@mui/icons-material": "^7.0.2",
15
+ "@mui/material": "^7.0.2",
16
+ "wouter": "^3.3.5",
17
+ "@emotion/react": "^11.14.0",
18
+ "@emotion/styled": "^11.14.0"
19
+ },
20
+ "scripts": {
21
+ "start": "react-scripts start",
22
+ "build": "react-scripts build",
23
+ "test": "react-scripts test",
24
+ "eject": "react-scripts eject"
25
+ },
26
+ "eslintConfig": {
27
+ "extends": [
28
+ "react-app",
29
+ "react-app/jest"
30
+ ]
31
+ },
32
+ "browserslist": {
33
+ "production": [
34
+ ">0.2%",
35
+ "not dead",
36
+ "not op_mini all"
37
+ ],
38
+ "development": [
39
+ "last 1 chrome version",
40
+ "last 1 firefox version",
41
+ "last 1 safari version"
42
+ ]
43
+ }
44
+ }
public/favicon.ico ADDED
public/index.html ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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" />
6
+ <meta name="theme-color" content="#000000" />
7
+ <meta name="description" content="Movie Review Sentiment Analysis Tool" />
8
+ <title>Movie Review Sentiment Analysis</title>
9
+ </head>
10
+ <body>
11
+ <noscript>You need to enable JavaScript to run this app.</noscript>
12
+ <div id="root"></div>
13
+ </body>
14
+ </html>
public/logo192.png ADDED
public/logo512.png ADDED
public/manifest.json ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "short_name": "React App",
3
+ "name": "Create React App Sample",
4
+ "icons": [
5
+ {
6
+ "src": "favicon.ico",
7
+ "sizes": "64x64 32x32 24x24 16x16",
8
+ "type": "image/x-icon"
9
+ },
10
+ {
11
+ "src": "logo192.png",
12
+ "type": "image/png",
13
+ "sizes": "192x192"
14
+ },
15
+ {
16
+ "src": "logo512.png",
17
+ "type": "image/png",
18
+ "sizes": "512x512"
19
+ }
20
+ ],
21
+ "start_url": ".",
22
+ "display": "standalone",
23
+ "theme_color": "#000000",
24
+ "background_color": "#ffffff"
25
+ }
public/robots.txt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ # https://www.robotstxt.org/robotstxt.html
2
+ User-agent: *
3
+ Disallow:
src/App.css ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .App {
2
+ text-align: center;
3
+ }
4
+
5
+ .App-logo {
6
+ height: 40vmin;
7
+ pointer-events: none;
8
+ }
9
+
10
+ @media (prefers-reduced-motion: no-preference) {
11
+ .App-logo {
12
+ animation: App-logo-spin infinite 20s linear;
13
+ }
14
+ }
15
+
16
+ .App-header {
17
+ background-color: #282c34;
18
+ min-height: 100vh;
19
+ display: flex;
20
+ flex-direction: column;
21
+ align-items: center;
22
+ justify-content: center;
23
+ font-size: calc(10px + 2vmin);
24
+ color: white;
25
+ }
26
+
27
+ .App-link {
28
+ color: #61dafb;
29
+ }
30
+
31
+ @keyframes App-logo-spin {
32
+ from {
33
+ transform: rotate(0deg);
34
+ }
35
+ to {
36
+ transform: rotate(360deg);
37
+ }
38
+ }
src/App.js ADDED
@@ -0,0 +1,86 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React from 'react';
2
+ import { Route, Switch } from 'wouter';
3
+ import { CssBaseline, Box } from '@mui/material';
4
+ import { ThemeProvider, createTheme } from '@mui/material/styles';
5
+ import Home from './pages/Home';
6
+ import NotFound from './pages/not-found';
7
+ import Header from './components/Header';
8
+ import Footer from './components/Footer';
9
+
10
+ // Create a global theme with movie-themed colors
11
+ const theme = createTheme({
12
+ palette: {
13
+ primary: {
14
+ main: '#2C3E50', // Navy blue
15
+ contrastText: '#ffffff',
16
+ },
17
+ secondary: {
18
+ main: '#E74C3C', // Review red
19
+ contrastText: '#ffffff',
20
+ },
21
+ success: {
22
+ main: '#2ECC71', // Positive green
23
+ contrastText: '#ffffff',
24
+ },
25
+ warning: {
26
+ main: '#F1C40F', // Neutral yellow
27
+ contrastText: '#ffffff',
28
+ },
29
+ background: {
30
+ default: '#F8F9FA',
31
+ paper: '#ffffff',
32
+ },
33
+ },
34
+ typography: {
35
+ fontFamily: '"Roboto", "Helvetica", "Arial", sans-serif',
36
+ h1: {
37
+ fontSize: '2.5rem',
38
+ fontWeight: 700,
39
+ marginBottom: '1rem',
40
+ },
41
+ h2: {
42
+ fontSize: '1.75rem',
43
+ fontWeight: 600,
44
+ marginBottom: '0.75rem',
45
+ },
46
+ h3: {
47
+ fontSize: '1.5rem',
48
+ fontWeight: 600,
49
+ marginBottom: '0.5rem',
50
+ },
51
+ },
52
+ shape: {
53
+ borderRadius: 8,
54
+ },
55
+ spacing: 8,
56
+ });
57
+
58
+ function Router() {
59
+ return (
60
+ <Switch>
61
+ <Route path="/" component={Home} />
62
+ <Route component={NotFound} />
63
+ </Switch>
64
+ );
65
+ }
66
+
67
+ function App() {
68
+ return (
69
+ <ThemeProvider theme={theme}>
70
+ <CssBaseline />
71
+ <Box sx={{
72
+ display: 'flex',
73
+ flexDirection: 'column',
74
+ minHeight: '100vh'
75
+ }}>
76
+ <Header />
77
+ <Box sx={{ flex: 1 }}>
78
+ <Router />
79
+ </Box>
80
+ <Footer />
81
+ </Box>
82
+ </ThemeProvider>
83
+ );
84
+ }
85
+
86
+ export default App;
src/App.test.js ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ import { render, screen } from '@testing-library/react';
2
+ import App from './App';
3
+
4
+ test('renders learn react link', () => {
5
+ render(<App />);
6
+ const linkElement = screen.getByText(/learn react/i);
7
+ expect(linkElement).toBeInTheDocument();
8
+ });
src/components/AboutSection.js ADDED
@@ -0,0 +1,119 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React from 'react';
2
+ import { Box, Typography, Paper, Chip } from '@mui/material';
3
+ import ThumbUpIcon from '@mui/icons-material/ThumbUp';
4
+ import ThumbDownIcon from '@mui/icons-material/ThumbDown';
5
+ import RemoveIcon from '@mui/icons-material/Remove';
6
+ import RateReviewIcon from '@mui/icons-material/RateReview';
7
+ import AutoAwesomeIcon from '@mui/icons-material/AutoAwesome';
8
+
9
+ function AboutSection() {
10
+ return (
11
+ <Paper
12
+ elevation={0}
13
+ sx={{
14
+ p: 3,
15
+ mb: 4,
16
+ borderRadius: 2,
17
+ bgcolor: 'background.paper',
18
+ border: '1px solid rgba(0,0,0,0.08)'
19
+ }}
20
+ >
21
+ <Typography variant="h4" component="h2" gutterBottom fontWeight="500">
22
+ How It Works
23
+ </Typography>
24
+
25
+ <Typography variant="body1" paragraph>
26
+ This tool uses artificial intelligence to analyze the sentiment of movie reviews.
27
+ Simply enter or paste a movie review, and our AI will classify it as positive, negative, or neutral,
28
+ along with a confidence score and detailed analysis.
29
+ </Typography>
30
+
31
+ <Box sx={{ display: 'flex', flexDirection: { xs: 'column', md: 'row' }, gap: 3, mt: 1 }}>
32
+ <Box sx={{
33
+ display: 'flex',
34
+ flexDirection: 'column',
35
+ alignItems: 'center',
36
+ textAlign: 'center',
37
+ p: 2,
38
+ height: '100%',
39
+ bgcolor: '#f8f9fa',
40
+ borderRadius: 2,
41
+ flex: 1
42
+ }}>
43
+ <RateReviewIcon sx={{ fontSize: 40, color: 'primary.main', mb: 1 }} />
44
+ <Typography variant="h6" gutterBottom>
45
+ Enter Your Review
46
+ </Typography>
47
+ <Typography variant="body2" color="text.secondary">
48
+ Copy and paste a movie review or write your own in the text area.
49
+ </Typography>
50
+ </Box>
51
+
52
+ <Box sx={{
53
+ display: 'flex',
54
+ flexDirection: 'column',
55
+ alignItems: 'center',
56
+ textAlign: 'center',
57
+ p: 2,
58
+ height: '100%',
59
+ bgcolor: '#f8f9fa',
60
+ borderRadius: 2,
61
+ flex: 1
62
+ }}>
63
+ <AutoAwesomeIcon sx={{ fontSize: 40, color: 'primary.main', mb: 1 }} />
64
+ <Typography variant="h6" gutterBottom>
65
+ AI Analysis
66
+ </Typography>
67
+ <Typography variant="body2" color="text.secondary">
68
+ Our AI model analyzes the text to determine the sentiment and confidence level.
69
+ </Typography>
70
+ </Box>
71
+
72
+ <Box sx={{
73
+ display: 'flex',
74
+ flexDirection: 'column',
75
+ alignItems: 'center',
76
+ textAlign: 'center',
77
+ p: 2,
78
+ height: '100%',
79
+ bgcolor: '#f8f9fa',
80
+ borderRadius: 2,
81
+ flex: 1
82
+ }}>
83
+ <Box sx={{ display: 'flex', gap: 1, mb: 1 }}>
84
+ <Chip
85
+ icon={<ThumbUpIcon />}
86
+ label="Positive"
87
+ size="small"
88
+ sx={{ bgcolor: 'success.main', color: 'white' }}
89
+ />
90
+ <Chip
91
+ icon={<ThumbDownIcon />}
92
+ label="Negative"
93
+ size="small"
94
+ sx={{ bgcolor: 'secondary.main', color: 'white' }}
95
+ />
96
+ <Chip
97
+ icon={<RemoveIcon />}
98
+ label="Neutral"
99
+ size="small"
100
+ sx={{ bgcolor: 'warning.main', color: 'white' }}
101
+ />
102
+ </Box>
103
+ <Typography variant="h6" gutterBottom>
104
+ View Results
105
+ </Typography>
106
+ <Typography variant="body2" color="text.secondary">
107
+ See the sentiment classification, confidence score, and detailed reasoning.
108
+ </Typography>
109
+ </Box>
110
+ </Box>
111
+
112
+ <Typography variant="body2" color="text.secondary" sx={{ mt: 3, fontStyle: 'italic' }}>
113
+ This application uses Cohere's large language model API to perform sentiment analysis.
114
+ </Typography>
115
+ </Paper>
116
+ );
117
+ }
118
+
119
+ export default AboutSection;
src/components/Footer.js ADDED
@@ -0,0 +1,70 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React from 'react';
2
+ import { Box, Typography, Container, Link, Divider } from '@mui/material';
3
+
4
+ function Footer() {
5
+ return (
6
+ <Box
7
+ component="footer"
8
+ sx={{
9
+ py: 3,
10
+ mt: 4,
11
+ bgcolor: '#f5f5f5'
12
+ }}
13
+ >
14
+ <Divider />
15
+ <Container maxWidth="lg">
16
+ <Box sx={{
17
+ display: 'flex',
18
+ flexDirection: { xs: 'column', md: 'row' },
19
+ justifyContent: 'space-between',
20
+ alignItems: 'center',
21
+ textAlign: { xs: 'center', md: 'left' },
22
+ mt: 2
23
+ }}>
24
+ <Typography variant="body2" color="text.secondary">
25
+ © {new Date().getFullYear()} Movie Review Sentiment Analysis
26
+ </Typography>
27
+
28
+ <Box sx={{
29
+ display: 'flex',
30
+ gap: 2,
31
+ mt: { xs: 2, md: 0 }
32
+ }}>
33
+ <Link
34
+ href="https://cohere.com/"
35
+ target="_blank"
36
+ rel="noopener noreferrer"
37
+ color="inherit"
38
+ underline="hover"
39
+ >
40
+ Powered by Cohere AI
41
+ </Link>
42
+ <Link
43
+ href="#"
44
+ color="inherit"
45
+ underline="hover"
46
+ >
47
+ About
48
+ </Link>
49
+ <Link
50
+ href="#"
51
+ color="inherit"
52
+ underline="hover"
53
+ >
54
+ Terms
55
+ </Link>
56
+ <Link
57
+ href="#"
58
+ color="inherit"
59
+ underline="hover"
60
+ >
61
+ Privacy
62
+ </Link>
63
+ </Box>
64
+ </Box>
65
+ </Container>
66
+ </Box>
67
+ );
68
+ }
69
+
70
+ export default Footer;
src/components/Header.js ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React from 'react';
2
+ import { AppBar, Toolbar, Typography, Box, Container } from '@mui/material';
3
+ import MovieIcon from '@mui/icons-material/Movie';
4
+ import { Link } from 'wouter';
5
+
6
+ function Header() {
7
+ return (
8
+ <AppBar position="static" sx={{ backgroundColor: '#2C3E50', boxShadow: 2, mb: 2 }}>
9
+ <Container maxWidth="lg">
10
+ <Toolbar sx={{ justifyContent: 'space-between' }}>
11
+ <Box sx={{ display: 'flex', alignItems: 'center' }}>
12
+ <MovieIcon sx={{ mr: 1, fontSize: 28 }} />
13
+ <Typography
14
+ variant="h6"
15
+ component={Link}
16
+ to="/"
17
+ sx={{
18
+ textDecoration: 'none',
19
+ color: 'white',
20
+ fontWeight: 'bold',
21
+ cursor: 'pointer'
22
+ }}
23
+ >
24
+ Movie Review Analyzer
25
+ </Typography>
26
+ </Box>
27
+ </Toolbar>
28
+ </Container>
29
+ </AppBar>
30
+ );
31
+ }
32
+
33
+ export default Header;
src/components/PreviousAnalysis.js ADDED
@@ -0,0 +1,99 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React from 'react';
2
+ import {
3
+ Box,
4
+ Typography,
5
+ List,
6
+ ListItem,
7
+ ListItemButton,
8
+ ListItemText,
9
+ ListItemIcon,
10
+ Chip,
11
+ Divider
12
+ } from '@mui/material';
13
+ import ThumbUpIcon from '@mui/icons-material/ThumbUp';
14
+ import ThumbDownIcon from '@mui/icons-material/ThumbDown';
15
+ import RemoveIcon from '@mui/icons-material/Remove';
16
+ import HistoryIcon from '@mui/icons-material/History';
17
+
18
+ function PreviousAnalyses({ analyses, onViewAnalysis }) {
19
+ if (!analyses.length) {
20
+ return (
21
+ <Box sx={{ textAlign: 'center', py: 4 }}>
22
+ <HistoryIcon sx={{ fontSize: 40, color: 'text.secondary', mb: 2 }} />
23
+ <Typography variant="body1" color="textSecondary">
24
+ Previous analyses will appear here
25
+ </Typography>
26
+ <Typography variant="body2" color="textSecondary">
27
+ Analyze your first review to get started
28
+ </Typography>
29
+ </Box>
30
+ );
31
+ }
32
+
33
+ return (
34
+ <List sx={{ width: '100%', maxHeight: '400px', overflow: 'auto' }}>
35
+ {analyses.map((analysis, index) => {
36
+ const getSentimentIcon = (sentiment) => {
37
+ switch (sentiment) {
38
+ case 'positive': return <ThumbUpIcon sx={{ color: 'success.main' }} />;
39
+ case 'negative': return <ThumbDownIcon sx={{ color: 'secondary.main' }} />;
40
+ default: return <RemoveIcon sx={{ color: 'warning.main' }} />;
41
+ }
42
+ };
43
+
44
+ const truncateText = (text, maxLength = 50) => {
45
+ return text.length > maxLength
46
+ ? `${text.substring(0, maxLength)}...`
47
+ : text;
48
+ };
49
+ console.log(analysis)
50
+
51
+ return (
52
+ <React.Fragment key={analysis.id || index}>
53
+ <ListItem disablePadding sx={{ mb: 1 }}>
54
+ <ListItemButton
55
+ sx={{
56
+ border: '1px solid',
57
+ borderColor: 'divider',
58
+ borderRadius: 1,
59
+ "&:hover": {
60
+ bgcolor: 'action.hover',
61
+ }
62
+ }}
63
+ >
64
+ <ListItemIcon>
65
+ {getSentimentIcon(analysis.sentiment)}
66
+ </ListItemIcon>
67
+ <ListItemText
68
+ primary={truncateText(analysis.text)}
69
+ secondary={
70
+ <Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mt: 0.5 }}>
71
+ <Chip
72
+ label={`${analysis.sentiment}`}
73
+ size="small"
74
+ sx={{
75
+ bgcolor: analysis.sentiment === 'positive' ? 'success.light' :
76
+ analysis.sentiment === 'negative' ? 'secondary.light' :
77
+ 'warning.light',
78
+ color: '#fff',
79
+ fontWeight: 'medium',
80
+ fontSize: '0.7rem'
81
+ }}
82
+ />
83
+ <Typography variant="caption">
84
+ {analysis.createdAt ? new Date(analysis.createdAt).toLocaleTimeString() : 'Just now'}
85
+ </Typography>
86
+ </Box>
87
+ }
88
+ />
89
+ </ListItemButton>
90
+ </ListItem>
91
+ {index < analyses.length - 1 && <Divider variant="fullWidth" component="li" />}
92
+ </React.Fragment>
93
+ );
94
+ })}
95
+ </List>
96
+ );
97
+ }
98
+
99
+ export default PreviousAnalyses;
src/components/ReviewInput.js ADDED
@@ -0,0 +1,85 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React from 'react';
2
+ import { Box, TextField, Button, Typography, CircularProgress } from '@mui/material';
3
+ import RateReviewIcon from '@mui/icons-material/RateReview';
4
+ import { analyzeSentiment } from '../lib/api';
5
+
6
+ function ReviewInput({
7
+ currentReview,
8
+ setCurrentReview,
9
+ isLoading,
10
+ setIsLoading,
11
+ setHasError,
12
+ setErrorMessage,
13
+ setAnalysisResult,
14
+ addToPreviousAnalyses
15
+ }) {
16
+ const handleReviewChange = (e) => {
17
+ setCurrentReview(e.target.value);
18
+ };
19
+
20
+ const handleSubmit = async (e) => {
21
+ e.preventDefault();
22
+
23
+ if (!currentReview) {
24
+ setHasError(true);
25
+ setErrorMessage('Please enter a review to analyze.');
26
+ return;
27
+ }
28
+
29
+ setIsLoading(true);
30
+ setHasError(false);
31
+ setErrorMessage('');
32
+
33
+ try {
34
+ const result = await analyzeSentiment(currentReview);
35
+ setAnalysisResult(result);
36
+ addToPreviousAnalyses({ ...result, text: currentReview });
37
+ } catch (error) {
38
+ console.error('Error analyzing sentiment:', error);
39
+ setHasError(true);
40
+ setErrorMessage(
41
+ error.message || 'An error occurred while analyzing the review. Please try again.'
42
+ );
43
+ } finally {
44
+ setIsLoading(false);
45
+ }
46
+ };
47
+
48
+ return (
49
+ <Box component="form" onSubmit={handleSubmit} noValidate>
50
+ <Typography variant="h2" component="h2" gutterBottom>
51
+ Enter Movie Review
52
+ </Typography>
53
+
54
+ <TextField
55
+ fullWidth
56
+ variant="outlined"
57
+ multiline
58
+ rows={6}
59
+ placeholder="Type or paste a movie review here..."
60
+ value={currentReview}
61
+ onChange={handleReviewChange}
62
+ sx={{ mb: 2 }}
63
+ disabled={isLoading}
64
+ />
65
+
66
+ <Button
67
+ variant="contained"
68
+ color="primary"
69
+ startIcon={isLoading ? <CircularProgress size={20} color="inherit" /> : <RateReviewIcon />}
70
+ type="submit"
71
+ disabled={isLoading || !currentReview}
72
+ sx={{
73
+ px: 4,
74
+ py: 1.5,
75
+ fontSize: '1rem',
76
+ fontWeight: 'medium'
77
+ }}
78
+ >
79
+ {isLoading ? 'Analyzing...' : 'Analyze Sentiment'}
80
+ </Button>
81
+ </Box>
82
+ );
83
+ }
84
+
85
+ export default ReviewInput;
src/components/SentimentResult.js ADDED
@@ -0,0 +1,124 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React from 'react';
2
+ import { Box, Typography, Button, Paper, Chip, CircularProgress } from '@mui/material';
3
+ import ThumbUpIcon from '@mui/icons-material/ThumbUp';
4
+ import ThumbDownIcon from '@mui/icons-material/ThumbDown';
5
+ import RemoveIcon from '@mui/icons-material/Remove';
6
+ import ArrowBackIcon from '@mui/icons-material/ArrowBack';
7
+
8
+ function SentimentResult({ result, onNewAnalysis, currentReview }) {
9
+ const sentimentColor =
10
+ result.sentiment === 'positive' ? 'success.main' :
11
+ result.sentiment === 'negative' ? 'secondary.main' :
12
+ 'warning.main';
13
+
14
+ const SentimentIcon =
15
+ result.sentiment === 'positive' ? ThumbUpIcon :
16
+ result.sentiment === 'negative' ? ThumbDownIcon :
17
+ RemoveIcon;
18
+
19
+ return (
20
+ <Box>
21
+ <Typography variant="h2" component="h2" gutterBottom>
22
+ Sentiment Analysis Result
23
+ </Typography>
24
+
25
+ <Box sx={{
26
+ display: 'flex',
27
+ flexDirection: { xs: 'column', sm: 'row' },
28
+ alignItems: 'center',
29
+ gap: 2,
30
+ mb: 3
31
+ }}>
32
+ <Box sx={{
33
+ display: 'flex',
34
+ alignItems: 'center',
35
+ gap: 1
36
+ }}>
37
+ <Typography variant="h3" component="span">
38
+ Sentiment:
39
+ </Typography>
40
+ <Chip
41
+ icon={<SentimentIcon />}
42
+ label={result.sentiment.charAt(0).toUpperCase() + result.sentiment.slice(1)}
43
+ sx={{
44
+ bgcolor: sentimentColor,
45
+ color: 'white',
46
+ fontWeight: 'bold',
47
+ fontSize: '1rem',
48
+ px: 1,
49
+ py: 2.5
50
+ }}
51
+ />
52
+ </Box>
53
+
54
+ <Box sx={{
55
+ display: 'flex',
56
+ alignItems: 'center',
57
+ gap: 1
58
+ }}>
59
+ <Typography variant="h3" component="span">
60
+ Confidence:
61
+ </Typography>
62
+ <Box sx={{ position: 'relative', display: 'inline-flex' }}>
63
+ <CircularProgress
64
+ variant="determinate"
65
+ value={result.confidence}
66
+ size={60}
67
+ thickness={5}
68
+ sx={{ color: sentimentColor }}
69
+ />
70
+ <Box
71
+ sx={{
72
+ top: 0,
73
+ left: 0,
74
+ bottom: 0,
75
+ right: 0,
76
+ position: 'absolute',
77
+ display: 'flex',
78
+ alignItems: 'center',
79
+ justifyContent: 'center',
80
+ }}
81
+ >
82
+ <Typography variant="body1" component="div" fontWeight="bold">
83
+ {`${Math.round(result.confidence)}%`}
84
+ </Typography>
85
+ </Box>
86
+ </Box>
87
+ </Box>
88
+ </Box>
89
+
90
+ {result.reasoning && (
91
+ <Paper variant="outlined" sx={{ p: 2, mb: 3, bgcolor: 'background.default' }}>
92
+ <Typography variant="h6" gutterBottom>
93
+ Analysis Details:
94
+ </Typography>
95
+ <Typography variant="body1">
96
+ {result.reasoning}
97
+ </Typography>
98
+ </Paper>
99
+ )}
100
+
101
+ <Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
102
+ <Paper variant="outlined" sx={{ p: 2, flex: 1, mr: 2 }}>
103
+ <Typography variant="body2" color="text.secondary">
104
+ Original Review Text:
105
+ </Typography>
106
+ <Typography variant="body1" sx={{ mt: 1 }}>
107
+ {currentReview}
108
+ </Typography>
109
+ </Paper>
110
+
111
+ <Button
112
+ variant="outlined"
113
+ color="primary"
114
+ startIcon={<ArrowBackIcon />}
115
+ onClick={onNewAnalysis}
116
+ >
117
+ New Analysis
118
+ </Button>
119
+ </Box>
120
+ </Box>
121
+ );
122
+ }
123
+
124
+ export default SentimentResult;
src/index.css ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ body {
2
+ margin: 0;
3
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
4
+ 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
5
+ sans-serif;
6
+ -webkit-font-smoothing: antialiased;
7
+ -moz-osx-font-smoothing: grayscale;
8
+ }
9
+
10
+ code {
11
+ font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
12
+ monospace;
13
+ }
src/index.js ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React from 'react';
2
+ import ReactDOM from 'react-dom/client';
3
+ import './index.css';
4
+ import App from './App';
5
+ import reportWebVitals from './reportWebVitals';
6
+
7
+ const root = ReactDOM.createRoot(document.getElementById('root'));
8
+ root.render(
9
+ <React.StrictMode>
10
+ <App />
11
+ </React.StrictMode>
12
+ );
13
+
14
+ // If you want to start measuring performance in your app, pass a function
15
+ // to log results (for example: reportWebVitals(console.log))
16
+ // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
17
+ reportWebVitals();
src/lib/api.js ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Makes a request to the sentiment analysis API endpoint
3
+ * @param {string} reviewText - The review text to analyze
4
+ * @returns {Promise<Object>} - The sentiment analysis result
5
+ */
6
+ export async function analyzeSentiment(reviewText) {
7
+ try {
8
+ const response = await fetch(
9
+ 'https://chethanmsrit-movie-sentiment-analysis.hf.space/sentiment', {
10
+ method: 'POST',
11
+ headers: {
12
+ 'Content-Type': 'application/json',
13
+ },
14
+ body: JSON.stringify({ reviewText }),
15
+ });
16
+
17
+ if (!response.ok) {
18
+ const errorData = await response.json();
19
+ throw new Error(errorData.message || 'Failed to analyze sentiment');
20
+ }
21
+
22
+ return await response.json();
23
+ } catch (error) {
24
+ console.error('Error analyzing sentiment:', error);
25
+ throw error;
26
+ }
27
+ }
src/logo.svg ADDED
src/pages/Home.js ADDED
@@ -0,0 +1,91 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { useState } from 'react';
2
+ import { Container, Typography, Box, Paper, Divider } from '@mui/material';
3
+ import ReviewInput from '../components/ReviewInput';
4
+ import SentimentResult from '../components/SentimentResult';
5
+ import PreviousAnalyses from '../components/PreviousAnalysis';
6
+ import AboutSection from '../components/AboutSection';
7
+
8
+ export default function Home() {
9
+ const [currentReview, setCurrentReview] = useState('');
10
+ const [isLoading, setIsLoading] = useState(false);
11
+ const [hasError, setHasError] = useState(false);
12
+ const [errorMessage, setErrorMessage] = useState('');
13
+ const [analysisResult, setAnalysisResult] = useState(null);
14
+ const [previousAnalyses, setPreviousAnalyses] = useState([]);
15
+
16
+ const handleViewPreviousAnalysis = (analysis) => {
17
+ setAnalysisResult(null);
18
+ setCurrentReview(analysis);
19
+ };
20
+
21
+ const addToPreviousAnalyses = (analysis) => {
22
+ // Add unique ID if it doesn't exist
23
+ const analysisWithId = analysis.id
24
+ ? analysis
25
+ : { ...analysis, id: Date.now(), createdAt: new Date() };
26
+
27
+ // Add to the beginning of the array to show newest first
28
+ setPreviousAnalyses(prev => [analysisWithId, ...prev]);
29
+ };
30
+
31
+ return (
32
+ <Container maxWidth="lg" sx={{ py: 4 }}>
33
+ <Box mb={4} textAlign="center">
34
+ <Typography variant="h1" component="h1" gutterBottom>
35
+ Movie Review Sentiment Analysis
36
+ </Typography>
37
+ <Typography variant="h6" color="textSecondary">
38
+ Enter a movie review to analyze its sentiment using AI
39
+ </Typography>
40
+ </Box>
41
+
42
+ <AboutSection />
43
+
44
+ <Box sx={{ display: 'flex', flexDirection: { xs: 'column', md: 'row' }, gap: 3 }}>
45
+ <Box sx={{ flex: { xs: '1', md: '2' } }}>
46
+ <Paper elevation={2} sx={{ p: 3, mb: { xs: 3, md: 0 } }}>
47
+ {!analysisResult ? (
48
+ <ReviewInput
49
+ currentReview={currentReview}
50
+ setCurrentReview={setCurrentReview}
51
+ isLoading={isLoading}
52
+ setIsLoading={setIsLoading}
53
+ setHasError={setHasError}
54
+ setErrorMessage={setErrorMessage}
55
+ setAnalysisResult={setAnalysisResult}
56
+ addToPreviousAnalyses={addToPreviousAnalyses}
57
+ />
58
+ ) : (
59
+ <SentimentResult
60
+ result={analysisResult}
61
+ onNewAnalysis={() => setAnalysisResult(null)}
62
+ currentReview={currentReview}
63
+ />
64
+ )}
65
+
66
+ {hasError && (
67
+ <Box sx={{ mt: 2, p: 2, bgcolor: 'error.light', color: 'error.contrastText', borderRadius: 1 }}>
68
+ <Typography variant="body1">
69
+ {errorMessage || 'An error occurred while analyzing the review. Please try again.'}
70
+ </Typography>
71
+ </Box>
72
+ )}
73
+ </Paper>
74
+ </Box>
75
+
76
+ <Box sx={{ flex: { xs: '1', md: '1' } }}>
77
+ <Paper elevation={2} sx={{ p: 3, mb: { xs: 3, md: 0 } }}>
78
+ <Typography variant="h2" component="h2" gutterBottom>
79
+ Previous Analyses
80
+ </Typography>
81
+ <Divider sx={{ mb: 2 }} />
82
+ <PreviousAnalyses
83
+ analyses={previousAnalyses}
84
+ onViewAnalysis={handleViewPreviousAnalysis}
85
+ />
86
+ </Paper>
87
+ </Box>
88
+ </Box>
89
+ </Container>
90
+ );
91
+ }
src/pages/not-found.js ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React from 'react';
2
+ import { Box, Typography, Button, Container } from '@mui/material';
3
+ import { Link } from 'wouter';
4
+ import ErrorOutlineIcon from '@mui/icons-material/ErrorOutline';
5
+ import HomeIcon from '@mui/icons-material/Home';
6
+
7
+ export default function NotFound() {
8
+ return (
9
+ <Container maxWidth="md">
10
+ <Box
11
+ sx={{
12
+ display: 'flex',
13
+ flexDirection: 'column',
14
+ alignItems: 'center',
15
+ justifyContent: 'center',
16
+ minHeight: '100vh',
17
+ textAlign: 'center',
18
+ py: 4
19
+ }}
20
+ >
21
+ <ErrorOutlineIcon sx={{ fontSize: 100, color: 'error.main', mb: 2 }} />
22
+
23
+ <Typography variant="h2" component="h1" gutterBottom>
24
+ Page Not Found
25
+ </Typography>
26
+
27
+ <Typography variant="h5" color="textSecondary" paragraph>
28
+ The page you're looking for doesn't exist or has been moved.
29
+ </Typography>
30
+
31
+ <Button
32
+ component={Link}
33
+ to="/"
34
+ variant="contained"
35
+ color="primary"
36
+ startIcon={<HomeIcon />}
37
+ sx={{ mt: 3 }}
38
+ >
39
+ Go to Home
40
+ </Button>
41
+ </Box>
42
+ </Container>
43
+ );
44
+ }
src/reportWebVitals.js ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const reportWebVitals = onPerfEntry => {
2
+ if (onPerfEntry && onPerfEntry instanceof Function) {
3
+ import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
4
+ getCLS(onPerfEntry);
5
+ getFID(onPerfEntry);
6
+ getFCP(onPerfEntry);
7
+ getLCP(onPerfEntry);
8
+ getTTFB(onPerfEntry);
9
+ });
10
+ }
11
+ };
12
+
13
+ export default reportWebVitals;
src/setupTests.js ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ // jest-dom adds custom jest matchers for asserting on DOM nodes.
2
+ // allows you to do things like:
3
+ // expect(element).toHaveTextContent(/react/i)
4
+ // learn more: https://github.com/testing-library/jest-dom
5
+ import '@testing-library/jest-dom';