Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8" /> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0" /> | |
| <title>Sentiment Twitter</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <link | |
| rel="stylesheet" | |
| href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" | |
| /> | |
| <style> | |
| .sentiment-positive { | |
| background-color: rgba(74, 222, 128, 0.2); | |
| border-left: 4px solid rgb(74, 222, 128); | |
| } | |
| .sentiment-negative { | |
| background-color: rgba(248, 113, 113, 0.2); | |
| border-left: 4px solid rgb(248, 113, 113); | |
| } | |
| .sentiment-neutral { | |
| background-color: rgba(156, 163, 175, 0.2); | |
| border-left: 4px solid rgb(156, 163, 175); | |
| } | |
| .pulse { | |
| animation: pulse 2s infinite; | |
| } | |
| @keyframes pulse { | |
| 0% { | |
| opacity: 1; | |
| } | |
| 50% { | |
| opacity: 0.5; | |
| } | |
| 100% { | |
| opacity: 1; | |
| } | |
| } | |
| </style> | |
| </head> | |
| <body class="bg-gray-50 min-h-screen"> | |
| <div class="max-w-2xl mx-auto"> | |
| <!-- Header --> | |
| <header | |
| class="sticky top-0 z-10 bg-white bg-opacity-90 backdrop-blur-sm border-b border-gray-200 p-4" | |
| > | |
| <div class="flex items-center justify-between"> | |
| <div class="flex items-center space-x-2"> | |
| <i class="fas fa-feather-alt text-blue-500 text-2xl"></i> | |
| <h1 class="text-xl font-bold text-gray-800">Sentiment Twitter</h1> | |
| </div> | |
| <div class="flex items-center space-x-4"> | |
| <button class="p-2 rounded-full hover:bg-gray-100"> | |
| <i class="fas fa-bell text-gray-600"></i> | |
| </button> | |
| <div | |
| class="w-8 h-8 rounded-full bg-blue-500 flex items-center justify-center text-white font-semibold" | |
| > | |
| U | |
| </div> | |
| </div> | |
| </div> | |
| </header> | |
| <!-- Post Creation --> | |
| <div class="bg-white p-4 border-b border-gray-200"> | |
| <div class="flex space-x-3"> | |
| <div class="flex-shrink-0"> | |
| <div | |
| class="w-10 h-10 rounded-full bg-blue-500 flex items-center justify-center text-white font-semibold" | |
| > | |
| U | |
| </div> | |
| </div> | |
| <div class="flex-1"> | |
| <textarea | |
| id="postContent" | |
| class="w-full border-0 focus:ring-0 resize-none text-lg placeholder-gray-500 tracking-wide min-h-[50px]" | |
| placeholder="What's happening?" | |
| rows="2" | |
| ></textarea> | |
| <div | |
| class="flex items-center justify-between pt-2 border-t border-gray-200" | |
| > | |
| <div class="flex space-x-1"> | |
| <button class="p-2 rounded-full text-blue-500 hover:bg-blue-50"> | |
| <i class="far fa-image"></i> | |
| </button> | |
| <button class="p-2 rounded-full text-blue-500 hover:bg-blue-50"> | |
| <i class="far fa-smile"></i> | |
| </button> | |
| <button class="p-2 rounded-full text-blue-500 hover:bg-blue-50"> | |
| <i class="far fa-calendar-alt"></i> | |
| </button> | |
| </div> | |
| <button | |
| id="postButton" | |
| class="bg-blue-500 hover:bg-blue-600 text-white px-4 py-1.5 rounded-full font-semibold disabled:opacity-50 disabled:cursor-not-allowed" | |
| disabled | |
| > | |
| Post | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Posts Feed --> | |
| <div id="postsContainer" class="divide-y divide-gray-200"> | |
| <!-- Sample posts will be added here dynamically --> | |
| <div class="p-4 sentiment-positive"> | |
| <div class="flex space-x-3"> | |
| <div class="flex-shrink-0"> | |
| <div | |
| class="w-10 h-10 rounded-full bg-purple-500 flex items-center justify-center text-white font-semibold" | |
| > | |
| A | |
| </div> | |
| </div> | |
| <div class="flex-1 min-w-0"> | |
| <div class="flex items-center text-sm"> | |
| <span class="font-bold text-gray-900 truncate">Alice</span> | |
| <span class="ml-1 text-gray-500 truncate">@alice · 2h</span> | |
| </div> | |
| <p class="text-gray-800 text-base mt-1"> | |
| Just had the best day ever! The weather is perfect and I got a | |
| promotion at work! 🎉 | |
| </p> | |
| <div class="mt-2 flex items-center justify-between"> | |
| <div class="flex space-x-4 text-gray-500"> | |
| <button | |
| class="flex items-center space-x-1 hover:text-blue-500" | |
| > | |
| <i class="far fa-comment"></i> | |
| <span>12</span> | |
| </button> | |
| <button | |
| class="flex items-center space-x-1 hover:text-green-500" | |
| > | |
| <i class="fas fa-retweet"></i> | |
| <span>5</span> | |
| </button> | |
| <button | |
| class="flex items-center space-x-1 hover:text-red-500" | |
| > | |
| <i class="far fa-heart"></i> | |
| <span>24</span> | |
| </button> | |
| </div> | |
| <div | |
| class="flex items-center space-x-1 text-sm bg-green-100 text-green-800 px-2 py-0.5 rounded-full" | |
| > | |
| <i class="fas fa-smile"></i> | |
| <span>Positive</span> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="p-4 sentiment-negative"> | |
| <div class="flex space-x-3"> | |
| <div class="flex-shrink-0"> | |
| <div | |
| class="w-10 h-10 rounded-full bg-red-500 flex items-center justify-center text-white font-semibold" | |
| > | |
| B | |
| </div> | |
| </div> | |
| <div class="flex-1 min-w-0"> | |
| <div class="flex items-center text-sm"> | |
| <span class="font-bold text-gray-900 truncate">Bob</span> | |
| <span class="ml-1 text-gray-500 truncate">@bob · 4h</span> | |
| </div> | |
| <p class="text-gray-800 text-base mt-1"> | |
| I can't believe how terrible the service was at that restaurant. | |
| Never going back there again! 😡 | |
| </p> | |
| <div class="mt-2 flex items-center justify-between"> | |
| <div class="flex space-x-4 text-gray-500"> | |
| <button | |
| class="flex items-center space-x-1 hover:text-blue-500" | |
| > | |
| <i class="far fa-comment"></i> | |
| <span>8</span> | |
| </button> | |
| <button | |
| class="flex items-center space-x-1 hover:text-green-500" | |
| > | |
| <i class="fas fa-retweet"></i> | |
| <span>2</span> | |
| </button> | |
| <button | |
| class="flex items-center space-x-1 hover:text-red-500" | |
| > | |
| <i class="far fa-heart"></i> | |
| <span>15</span> | |
| </button> | |
| </div> | |
| <div | |
| class="flex items-center space-x-1 text-sm bg-red-100 text-red-800 px-2 py-0.5 rounded-full" | |
| > | |
| <i class="fas fa-frown"></i> | |
| <span>Negative</span> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="p-4 sentiment-neutral"> | |
| <div class="flex space-x-3"> | |
| <div class="flex-shrink-0"> | |
| <div | |
| class="w-10 h-10 rounded-full bg-gray-500 flex items-center justify-center text-white font-semibold" | |
| > | |
| C | |
| </div> | |
| </div> | |
| <div class="flex-1 min-w-0"> | |
| <div class="flex items-center text-sm"> | |
| <span class="font-bold text-gray-900 truncate">Charlie</span> | |
| <span class="ml-1 text-gray-500 truncate">@charlie · 6h</span> | |
| </div> | |
| <p class="text-gray-800 text-base mt-1"> | |
| The meeting today was scheduled for 2pm. We discussed the | |
| quarterly reports. | |
| </p> | |
| <div class="mt-2 flex items-center justify-between"> | |
| <div class="flex space-x-4 text-gray-500"> | |
| <button | |
| class="flex items-center space-x-1 hover:text-blue-500" | |
| > | |
| <i class="far fa-comment"></i> | |
| <span>3</span> | |
| </button> | |
| <button | |
| class="flex items-center space-x-1 hover:text-green-500" | |
| > | |
| <i class="fas fa-retweet"></i> | |
| <span>1</span> | |
| </button> | |
| <button | |
| class="flex items-center space-x-1 hover:text-red-500" | |
| > | |
| <i class="far fa-heart"></i> | |
| <span>7</span> | |
| </button> | |
| </div> | |
| <div | |
| class="flex items-center space-x-1 text-sm bg-gray-100 text-gray-800 px-2 py-0.5 rounded-full" | |
| > | |
| <i class="fas fa-meh"></i> | |
| <span>Neutral</span> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Loading indicator --> | |
| <div id="loadingIndicator" class="p-4 hidden"> | |
| <div class="flex justify-center"> | |
| <div | |
| class="w-8 h-8 border-4 border-blue-500 border-t-transparent rounded-full animate-spin" | |
| ></div> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| document.addEventListener("DOMContentLoaded", function () { | |
| const postContent = document.getElementById("postContent"); | |
| const postButton = document.getElementById("postButton"); | |
| const postsContainer = document.getElementById("postsContainer"); | |
| const loadingIndicator = document.getElementById("loadingIndicator"); | |
| // Enable/disable post button based on content | |
| postContent.addEventListener("input", function () { | |
| postButton.disabled = this.value.trim().length === 0; | |
| }); | |
| // Handle post submission | |
| postButton.addEventListener("click", function () { | |
| const content = postContent.value.trim(); | |
| if (content.length === 0) return; | |
| // Show loading indicator | |
| loadingIndicator.classList.remove("hidden"); | |
| // Simulate API call delay for sentiment analysis | |
| setTimeout(() => { | |
| // Create new post | |
| createPost(content); | |
| // Clear input and disable button | |
| postContent.value = ""; | |
| postButton.disabled = true; | |
| // Hide loading indicator | |
| loadingIndicator.classList.add("hidden"); | |
| }, 1500); | |
| }); | |
| // Function to create a new post with sentiment analysis | |
| function createPost(content) { | |
| // Simulate BERT sentiment analysis (in a real app, this would be an API call) | |
| const sentiment = analyzeSentiment(content); | |
| // Create post element | |
| const postElement = document.createElement("div"); | |
| postElement.className = `p-4 sentiment-${sentiment.class}`; | |
| // Generate random user data for demo | |
| const randomName = getRandomName(); | |
| const randomInitial = randomName.charAt(0); | |
| const randomColor = getRandomColor(); | |
| // Post HTML | |
| postElement.innerHTML = ` | |
| <div class="flex space-x-3"> | |
| <div class="flex-shrink-0"> | |
| <div class="w-10 h-10 rounded-full ${randomColor} flex items-center justify-center text-white font-semibold"> | |
| ${randomInitial} | |
| </div> | |
| </div> | |
| <div class="flex-1 min-w-0"> | |
| <div class="flex items-center text-sm"> | |
| <span class="font-bold text-gray-900 truncate">${randomName}</span> | |
| <span class="ml-1 text-gray-500 truncate">@${randomName.toLowerCase()} · just now</span> | |
| </div> | |
| <p class="text-gray-800 text-base mt-1">${content}</p> | |
| <div class="mt-2 flex items-center justify-between"> | |
| <div class="flex space-x-4 text-gray-500"> | |
| <button class="flex items-center space-x-1 hover:text-blue-500"> | |
| <i class="far fa-comment"></i> | |
| <span>0</span> | |
| </button> | |
| <button class="flex items-center space-x-1 hover:text-green-500"> | |
| <i class="fas fa-retweet"></i> | |
| <span>0</span> | |
| </button> | |
| <button class="flex items-center space-x-1 hover:text-red-500"> | |
| <i class="far fa-heart"></i> | |
| <span>0</span> | |
| </button> | |
| </div> | |
| <div class="flex items-center space-x-1 text-sm ${getSentimentBadgeClass( | |
| sentiment.class | |
| )}"> | |
| <i class="${getSentimentIcon( | |
| sentiment.class | |
| )}"></i> | |
| <span>${sentiment.label}</span> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| `; | |
| // Add to the top of the posts container | |
| postsContainer.prepend(postElement); | |
| // Scroll to the new post | |
| postElement.scrollIntoView({ behavior: "smooth" }); | |
| } | |
| // Simulated BERT sentiment analysis function | |
| function analyzeSentiment(text) { | |
| // This is a simplified simulation - in a real app, you'd call your BERT model API | |
| const positiveWords = [ | |
| "happy", | |
| "great", | |
| "awesome", | |
| "fantastic", | |
| "amazing", | |
| "love", | |
| "best", | |
| "perfect", | |
| "wonderful", | |
| "excellent", | |
| ]; | |
| const negativeWords = [ | |
| "bad", | |
| "terrible", | |
| "awful", | |
| "hate", | |
| "worst", | |
| "angry", | |
| "sad", | |
| "disappointed", | |
| "frustrated", | |
| "annoyed", | |
| ]; | |
| const lowerText = text.toLowerCase(); | |
| let positiveScore = 0; | |
| let negativeScore = 0; | |
| positiveWords.forEach((word) => { | |
| if (lowerText.includes(word)) positiveScore++; | |
| }); | |
| negativeWords.forEach((word) => { | |
| if (lowerText.includes(word)) negativeScore++; | |
| }); | |
| // Determine sentiment based on scores | |
| if (positiveScore > negativeScore) { | |
| return { class: "positive", label: "Positive" }; | |
| } else if (negativeScore > positiveScore) { | |
| return { class: "negative", label: "Negative" }; | |
| } else { | |
| return { class: "neutral", label: "Neutral" }; | |
| } | |
| } | |
| // Helper functions | |
| function getRandomName() { | |
| const names = [ | |
| "David", | |
| "Emma", | |
| "Frank", | |
| "Grace", | |
| "Henry", | |
| "Isabella", | |
| "Jack", | |
| "Katherine", | |
| "Liam", | |
| "Mia", | |
| ]; | |
| return names[Math.floor(Math.random() * names.length)]; | |
| } | |
| function getRandomColor() { | |
| const colors = [ | |
| "bg-blue-500", | |
| "bg-purple-500", | |
| "bg-pink-500", | |
| "bg-red-500", | |
| "bg-orange-500", | |
| "bg-yellow-500", | |
| "bg-green-500", | |
| "bg-teal-500", | |
| "bg-cyan-500", | |
| ]; | |
| return colors[Math.floor(Math.random() * colors.length)]; | |
| } | |
| function getSentimentBadgeClass(sentiment) { | |
| switch (sentiment) { | |
| case "positive": | |
| return "bg-green-100 text-green-800"; | |
| case "negative": | |
| return "bg-red-100 text-red-800"; | |
| default: | |
| return "bg-gray-100 text-gray-800"; | |
| } | |
| } | |
| function getSentimentIcon(sentiment) { | |
| switch (sentiment) { | |
| case "positive": | |
| return "fas fa-smile"; | |
| case "negative": | |
| return "fas fa-frown"; | |
| default: | |
| return "fas fa-meh"; | |
| } | |
| } | |
| }); | |
| </script> | |
| </body> | |
| </html> | |