sedzhin commited on
Commit
064b9d3
·
verified ·
1 Parent(s): 2140a52

Upload 3 files

Browse files
Files changed (3) hide show
  1. app.js +174 -0
  2. index.html +285 -18
  3. reviews_test.tsv +0 -0
app.js ADDED
@@ -0,0 +1,174 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Global variables
2
+ let reviews = [];
3
+ let apiToken = '';
4
+
5
+ // DOM elements
6
+ const analyzeBtn = document.getElementById('analyze-btn');
7
+ const reviewText = document.getElementById('review-text');
8
+ const sentimentResult = document.getElementById('sentiment-result');
9
+ const loadingElement = document.querySelector('.loading');
10
+ const errorElement = document.getElementById('error-message');
11
+ const apiTokenInput = document.getElementById('api-token');
12
+
13
+ // Initialize the app
14
+ document.addEventListener('DOMContentLoaded', function() {
15
+ // Load the TSV file (Papa Parse 활성화)
16
+ loadReviews();
17
+
18
+ // Set up event listeners
19
+ analyzeBtn.addEventListener('click', analyzeRandomReview);
20
+ apiTokenInput.addEventListener('change', saveApiToken);
21
+
22
+ // Load saved API token if exists
23
+ const savedToken = localStorage.getItem('hfApiToken');
24
+ if (savedToken) {
25
+ apiTokenInput.value = savedToken;
26
+ apiToken = savedToken;
27
+ }
28
+ });
29
+
30
+ // Load and parse the TSV file using Papa Parse
31
+ function loadReviews() {
32
+ fetch('reviews_test.tsv')
33
+ .then(response => {
34
+ if (!response.ok) throw new Error('Failed to load TSV file');
35
+ return response.text();
36
+ })
37
+ .then(tsvData => {
38
+ Papa.parse(tsvData, {
39
+ header: true,
40
+ delimiter: '\t',
41
+ complete: (results) => {
42
+ reviews = results.data
43
+ .map(row => row.text)
44
+ .filter(text => text && text.trim() !== '');
45
+ console.log('Loaded', reviews.length, 'reviews');
46
+ },
47
+ error: (error) => {
48
+ console.error('TSV parse error:', error);
49
+ showError('Failed to parse TSV file: ' + error.message);
50
+ }
51
+ });
52
+ })
53
+ .catch(error => {
54
+ console.error('TSV load error:', error);
55
+ showError('Failed to load TSV file: ' + error.message);
56
+ });
57
+ }
58
+
59
+ // Save API token to localStorage
60
+ function saveApiToken() {
61
+ apiToken = apiTokenInput.value.trim();
62
+ if (apiToken) {
63
+ localStorage.setItem('hfApiToken', apiToken);
64
+ } else {
65
+ localStorage.removeItem('hfApiToken');
66
+ }
67
+ }
68
+
69
+ // Analyze a random review
70
+ function analyzeRandomReview() {
71
+ hideError();
72
+
73
+ if (reviews.length === 0) {
74
+ showError('No reviews available. Please try again later.');
75
+ return;
76
+ }
77
+
78
+ const selectedReview = reviews[Math.floor(Math.random() * reviews.length)];
79
+
80
+ // Display the review
81
+ reviewText.textContent = selectedReview;
82
+
83
+ // Show loading state
84
+ loadingElement.style.display = 'block';
85
+ analyzeBtn.disabled = true;
86
+ sentimentResult.innerHTML = ''; // Reset previous result
87
+ sentimentResult.className = 'sentiment-result'; // Reset classes
88
+
89
+ // Call Hugging Face API
90
+ analyzeSentiment(selectedReview)
91
+ .then(result => displaySentiment(result))
92
+ .catch(error => {
93
+ console.error('Error:', error);
94
+ showError('Failed to analyze sentiment: ' + error.message);
95
+ })
96
+ .finally(() => {
97
+ loadingElement.style.display = 'none';
98
+ analyzeBtn.disabled = false;
99
+ });
100
+ }
101
+
102
+ // Call Hugging Face API for sentiment analysis
103
+ async function analyzeSentiment(text) {
104
+ const response = await fetch(
105
+ 'https://api-inference.huggingface.co/models/siebert/sentiment-roberta-large-english',
106
+ {
107
+ headers: {
108
+ Authorization: apiToken ? `Bearer ${apiToken}` : undefined,
109
+ 'Content-Type': 'application/json'
110
+ },
111
+ method: 'POST',
112
+ body: JSON.stringify({ inputs: text }),
113
+ }
114
+ );
115
+
116
+ if (!response.ok) {
117
+ throw new Error(`API error: ${response.status} ${response.statusText}`);
118
+ }
119
+
120
+ const result = await response.json();
121
+ return result;
122
+ }
123
+
124
+ // Display sentiment result
125
+ function displaySentiment(result) {
126
+ // Default to neutral if we can't parse the result
127
+ let sentiment = 'neutral';
128
+ let score = 0.5;
129
+ let label = 'NEUTRAL';
130
+
131
+ // Parse the API response (format: [[{label: 'POSITIVE', score: 0.99}]])
132
+ if (Array.isArray(result) && result.length > 0 && Array.isArray(result[0]) && result[0].length > 0) {
133
+ const sentimentData = result[0][0];
134
+ label = sentimentData.label?.toUpperCase() || 'NEUTRAL';
135
+ score = sentimentData.score ?? 0.5;
136
+
137
+ // Determine sentiment
138
+ if (label === 'POSITIVE' && score > 0.5) {
139
+ sentiment = 'positive';
140
+ } else if (label === 'NEGATIVE' && score > 0.5) {
141
+ sentiment = 'negative';
142
+ }
143
+ }
144
+
145
+ // Update UI
146
+ sentimentResult.classList.add(sentiment);
147
+ sentimentResult.innerHTML = `
148
+ <i class="fas ${getSentimentIcon(sentiment)} icon"></i>
149
+ <span>${label} (${(score * 100).toFixed(1)}% confidence)</span>
150
+ `;
151
+ }
152
+
153
+ // Get appropriate icon for sentiment
154
+ function getSentimentIcon(sentiment) {
155
+ switch(sentiment) {
156
+ case 'positive':
157
+ return 'fa-thumbs-up';
158
+ case 'negative':
159
+ return 'fa-thumbs-down';
160
+ default:
161
+ return 'fa-question-circle';
162
+ }
163
+ }
164
+
165
+ // Show error message
166
+ function showError(message) {
167
+ errorElement.textContent = message;
168
+ errorElement.style.display = 'block';
169
+ }
170
+
171
+ // Hide error message
172
+ function hideError() {
173
+ errorElement.style.display = 'none';
174
+ }
index.html CHANGED
@@ -1,19 +1,286 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  </html>
 
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>Review Sentiment Analyzer</title>
7
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
8
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/PapaParse/5.3.2/papaparse.min.js"></script>
9
+ <style>
10
+ * {
11
+ box-sizing: border-box;
12
+ margin: 0;
13
+ padding: 0;
14
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
15
+ }
16
+
17
+ body {
18
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
19
+ color: #333;
20
+ min-height: 100vh;
21
+ padding: 20px;
22
+ display: flex;
23
+ flex-direction: column;
24
+ align-items: center;
25
+ }
26
+
27
+ .container {
28
+ background-color: rgba(255, 255, 255, 0.95);
29
+ border-radius: 15px;
30
+ box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
31
+ width: 90%;
32
+ max-width: 800px;
33
+ padding: 30px;
34
+ margin: 20px 0;
35
+ }
36
+
37
+ h1 {
38
+ text-align: center;
39
+ color: #4a4a4a;
40
+ margin-bottom: 20px;
41
+ font-size: 2.2rem;
42
+ }
43
+
44
+ .description {
45
+ text-align: center;
46
+ margin-bottom: 30px;
47
+ color: #666;
48
+ line-height: 1.6;
49
+ }
50
+
51
+ .api-section {
52
+ margin-bottom: 25px;
53
+ padding: 20px;
54
+ background-color: #f8f9fa;
55
+ border-radius: 10px;
56
+ border-left: 4px solid #667eea;
57
+ }
58
+
59
+ .api-section h2 {
60
+ margin-bottom: 15px;
61
+ color: #4a4a4a;
62
+ font-size: 1.4rem;
63
+ }
64
+
65
+ .api-input {
66
+ display: flex;
67
+ flex-direction: column;
68
+ gap: 10px;
69
+ }
70
+
71
+ input[type="text"] {
72
+ padding: 12px 15px;
73
+ border: 1px solid #ddd;
74
+ border-radius: 8px;
75
+ font-size: 16px;
76
+ transition: border-color 0.3s;
77
+ }
78
+
79
+ input[type="text"]:focus {
80
+ border-color: #667eea;
81
+ outline: none;
82
+ box-shadow: 0 0 0 2px rgba(102, 126, 234, 0.2);
83
+ }
84
+
85
+ button {
86
+ background: linear-gradient(to right, #667eea, #764ba2);
87
+ color: white;
88
+ border: none;
89
+ padding: 12px 20px;
90
+ border-radius: 8px;
91
+ cursor: pointer;
92
+ font-size: 16px;
93
+ font-weight: 600;
94
+ transition: transform 0.2s, box-shadow 0.2s;
95
+ margin-top: 10px;
96
+ }
97
+
98
+ button:hover {
99
+ transform: translateY(-2px);
100
+ box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2);
101
+ }
102
+
103
+ button:active {
104
+ transform: translateY(0);
105
+ }
106
+
107
+ .review-section {
108
+ margin-bottom: 25px;
109
+ }
110
+
111
+ .review-card {
112
+ background-color: white;
113
+ border-radius: 10px;
114
+ padding: 20px;
115
+ box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
116
+ margin-bottom: 20px;
117
+ }
118
+
119
+ .review-text {
120
+ line-height: 1.6;
121
+ margin-bottom: 15px;
122
+ font-size: 16px;
123
+ color: #444;
124
+ }
125
+
126
+ .sentiment-result {
127
+ display: flex;
128
+ align-items: center;
129
+ justify-content: center;
130
+ gap: 15px;
131
+ padding: 15px;
132
+ border-radius: 10px;
133
+ margin-top: 20px;
134
+ background-color: #f8f9fa;
135
+ font-weight: bold;
136
+ font-size: 18px;
137
+ }
138
+
139
+ .positive {
140
+ color: #28a745;
141
+ border: 2px solid #28a745;
142
+ }
143
+
144
+ .negative {
145
+ color: #dc3545;
146
+ border: 2px solid #dc3545;
147
+ }
148
+
149
+ .neutral {
150
+ color: #ffc107;
151
+ border: 2px solid #ffc107;
152
+ }
153
+
154
+ .icon {
155
+ font-size: 24px;
156
+ }
157
+
158
+ .loading {
159
+ display: none;
160
+ text-align: center;
161
+ margin: 20px 0;
162
+ }
163
+
164
+ .spinner {
165
+ border: 4px solid rgba(0, 0, 0, 0.1);
166
+ border-left: 4px solid #667eea;
167
+ border-radius: 50%;
168
+ width: 30px;
169
+ height: 30px;
170
+ animation: spin 1s linear infinite;
171
+ margin: 0 auto;
172
+ }
173
+
174
+ @keyframes spin {
175
+ 0% { transform: rotate(0deg); }
176
+ 100% { transform: rotate(360deg); }
177
+ }
178
+
179
+ .error {
180
+ color: #dc3545;
181
+ padding: 10px;
182
+ border-radius: 5px;
183
+ background-color: #f8d7da;
184
+ border: 1px solid #f5c6cb;
185
+ margin: 10px 0;
186
+ display: none;
187
+ }
188
+
189
+ .instructions {
190
+ margin-top: 30px;
191
+ padding: 20px;
192
+ background-color: #f8f9fa;
193
+ border-radius: 10px;
194
+ font-size: 14px;
195
+ line-height: 1.6;
196
+ }
197
+
198
+ .instructions h3 {
199
+ margin-bottom: 10px;
200
+ color: #4a4a4a;
201
+ }
202
+
203
+ .instructions ol {
204
+ padding-left: 20px;
205
+ }
206
+
207
+ .instructions li {
208
+ margin-bottom: 8px;
209
+ }
210
+
211
+ footer {
212
+ text-align: center;
213
+ margin-top: 30px;
214
+ color: rgba(255, 255, 255, 0.8);
215
+ font-size: 14px;
216
+ }
217
+
218
+ @media (max-width: 600px) {
219
+ .container {
220
+ width: 95%;
221
+ padding: 20px;
222
+ }
223
+
224
+ h1 {
225
+ font-size: 1.8rem;
226
+ }
227
+
228
+ button {
229
+ width: 100%;
230
+ }
231
+ }
232
+ </style>
233
+ </head>
234
+ <body>
235
+ <div class="container">
236
+ <h1>Review Sentiment Analyzer</h1>
237
+ <p class="description">Analyze the sentiment of product reviews using Hugging Face's AI model</p>
238
+
239
+ <div class="api-section">
240
+ <h2>Hugging Face API Setup</h2>
241
+ <div class="api-input">
242
+ <label for="api-token">Enter your Hugging Face API Token:</label>
243
+ <input type="text" id="api-token" placeholder="hf_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx">
244
+ <p><small>Your token is stored only in your browser and is never sent to our servers.</small></p>
245
+ </div>
246
+ </div>
247
+
248
+ <div class="review-section">
249
+ <button id="analyze-btn">Analyze Random Review</button>
250
+
251
+ <div class="loading">
252
+ <div class="spinner"></div>
253
+ <p>Analyzing sentiment...</p>
254
+ </div>
255
+
256
+ <div class="error" id="error-message"></div>
257
+
258
+ <div class="review-card">
259
+ <h3>Selected Review:</h3>
260
+ <p class="review-text" id="review-text">Click the button above to analyze a random review</p>
261
+ </div>
262
+
263
+ <div class="sentiment-result" id="sentiment-result">
264
+ <i class="fas fa-question-circle icon"></i>
265
+ <span>Sentiment will appear here</span>
266
+ </div>
267
+ </div>
268
+
269
+ <div class="instructions">
270
+ <h3>How to get a Hugging Face API Token:</h3>
271
+ <ol>
272
+ <li>Go to <a href="https://huggingface.co/" target="_blank">huggingface.co</a> and create a free account</li>
273
+ <li>Navigate to your profile settings and access tokens section</li>
274
+ <li>Generate a new token with read permissions</li>
275
+ <li>Copy the token and paste it in the field above</li>
276
+ </ol>
277
+ </div>
278
+ </div>
279
+
280
+ <footer>
281
+ <p>This app uses the distilbert-base-uncased-finetuned-sst-2-english model from Hugging Face</p>
282
+ </footer>
283
+
284
+ <script src="app.js"></script>
285
+ </body>
286
  </html>
reviews_test.tsv ADDED
The diff for this file is too large to render. See raw diff