-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #3 from flo-bit/main
add emotion analysis
- Loading branch information
Showing
4 changed files
with
17,395 additions
and
55 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,66 +1,113 @@ | ||
<script> | ||
import Credit from "$lib/Credit.svelte"; | ||
import Credit from '$lib/Credit.svelte'; | ||
</script> | ||
|
||
<Credit showAll={false} /> | ||
|
||
<div class=""> | ||
<div class="mx-auto max-w-7xl px-4 py-16 sm:px-6 sm:py-24 lg:px-8"> | ||
<div class="sm:flex sm:items-baseline sm:justify-between"> | ||
<h2 class="text-2xl font-bold tracking-tight text-gray-50">Bluesky Visualizers</h2> | ||
|
||
</div> | ||
|
||
<div class="mt-6 grid grid-cols-1 gap-y-6 sm:grid-cols-2 sm:grid-rows-2 sm:gap-x-6 lg:gap-8"> | ||
<div class="group relative aspect-[2/1] overflow-hidden rounded-lg sm:row-span-2 sm:aspect-square border border-gray-900"> | ||
<img src="/bluesky-visualizers/trending.webp" alt="" class="absolute size-full object-cover group-hover:opacity-75 transition-opacity duration-75"> | ||
<div aria-hidden="true" class="absolute inset-0 bg-gradient-to-b from-transparent to-black opacity-80"></div> | ||
<div class="absolute inset-0 flex items-end p-6"> | ||
<div> | ||
<h3 class="font-semibold text-white"> | ||
<a href="/bluesky-visualizers/trending"> | ||
<span class="absolute inset-0"></span> | ||
Trending Hashtags | ||
</a> | ||
</h3> | ||
</div> | ||
</div> | ||
<div class="sm:flex sm:items-baseline sm:justify-between"> | ||
<h2 class="text-2xl font-bold tracking-tight text-gray-50">Bluesky Visualizers</h2> | ||
</div> | ||
<div class="group relative aspect-[2/1] overflow-hidden rounded-lg sm:aspect-auto border border-gray-900"> | ||
<img src="/bluesky-visualizers/wordcloud.webp" alt="" class="absolute size-full object-cover group-hover:opacity-75 transition-opacity duration-75"> | ||
<div aria-hidden="true" class="absolute inset-0 bg-gradient-to-b from-transparent to-black opacity-80"></div> | ||
<div class="absolute inset-0 flex items-end p-6"> | ||
<div> | ||
<h3 class="font-semibold text-white"> | ||
<a href="/bluesky-visualizers/wordcloud"> | ||
<span class="absolute inset-0"></span> | ||
Wordcloud | ||
</a> | ||
</h3> | ||
|
||
<div class="mt-6 grid grid-cols-1 gap-y-6 sm:grid-cols-2 sm:grid-rows-2 sm:gap-x-6 lg:gap-8"> | ||
<div class="flex flex-col gap-8"> | ||
<div | ||
class="group relative aspect-[2/1] overflow-hidden rounded-lg border border-gray-900 sm:row-span-2 sm:aspect-square" | ||
> | ||
<img | ||
src="/bluesky-visualizers/trending.webp" | ||
alt="" | ||
class="absolute size-full object-cover transition-opacity duration-75 group-hover:opacity-75" | ||
/> | ||
<div | ||
aria-hidden="true" | ||
class="absolute inset-0 bg-gradient-to-b from-transparent to-black opacity-80" | ||
></div> | ||
<div class="absolute inset-0 flex items-end p-6"> | ||
<div> | ||
<h3 class="font-semibold text-white"> | ||
<a href="/bluesky-visualizers/trending"> | ||
<span class="absolute inset-0"></span> | ||
Trending Hashtags | ||
</a> | ||
</h3> | ||
</div> | ||
</div> | ||
</div> | ||
|
||
<div | ||
class="group relative aspect-[2/1] overflow-hidden rounded-lg border border-gray-900 sm:aspect-[2/1]" | ||
> | ||
<img | ||
src="/bluesky-visualizers/emotions.webp" | ||
alt="" | ||
class="absolute size-full object-cover transition-opacity duration-75 group-hover:opacity-75" | ||
/> | ||
<div | ||
aria-hidden="true" | ||
class="absolute inset-0 bg-gradient-to-b from-transparent to-black opacity-80" | ||
></div> | ||
<div class="absolute inset-0 flex items-end p-6"> | ||
<div> | ||
<h3 class="font-semibold text-white"> | ||
<a href="/bluesky-visualizers/emotions"> | ||
<span class="absolute inset-0"></span> | ||
Emotions | ||
</a> | ||
</h3> | ||
</div> | ||
</div> | ||
</div> | ||
</div> | ||
</div> | ||
</div> | ||
<div class="group relative aspect-[2/1] overflow-hidden rounded-lg sm:aspect-auto border border-gray-900"> | ||
<img src="/bluesky-visualizers/particles.webp" alt="" class="absolute size-full object-cover group-hover:opacity-75 transition-opacity duration-75"> | ||
<div aria-hidden="true" class="absolute inset-0 bg-gradient-to-b from-transparent to-black opacity-80"></div> | ||
<div class="absolute inset-0 flex items-end p-6"> | ||
<div> | ||
<h3 class="font-semibold text-white"> | ||
<a href="/bluesky-visualizers/particles"> | ||
<span class="absolute inset-0"></span> | ||
Particles | ||
</a> | ||
</h3> | ||
<div class="flex flex-col gap-8"> | ||
<div | ||
class="group relative aspect-[2/1] overflow-hidden rounded-lg border border-gray-900 sm:aspect-[4/3]" | ||
> | ||
<img | ||
src="/bluesky-visualizers/wordcloud.webp" | ||
alt="" | ||
class="absolute size-full object-cover transition-opacity duration-75 group-hover:opacity-75" | ||
/> | ||
<div | ||
aria-hidden="true" | ||
class="absolute inset-0 bg-gradient-to-b from-transparent to-black opacity-80" | ||
></div> | ||
<div class="absolute inset-0 flex items-end p-6"> | ||
<div> | ||
<h3 class="font-semibold text-white"> | ||
<a href="/bluesky-visualizers/wordcloud"> | ||
<span class="absolute inset-0"></span> | ||
Wordcloud | ||
</a> | ||
</h3> | ||
</div> | ||
</div> | ||
</div> | ||
<div | ||
class="group relative aspect-[2/1] overflow-hidden rounded-lg border border-gray-900 sm:aspect-[4/3]" | ||
> | ||
<img | ||
src="/bluesky-visualizers/particles.webp" | ||
alt="" | ||
class="absolute size-full object-cover transition-opacity duration-75 group-hover:opacity-75" | ||
/> | ||
<div | ||
aria-hidden="true" | ||
class="absolute inset-0 bg-gradient-to-b from-transparent to-black opacity-80" | ||
></div> | ||
<div class="absolute inset-0 flex items-end p-6"> | ||
<div> | ||
<h3 class="font-semibold text-white"> | ||
<a href="/bluesky-visualizers/particles"> | ||
<span class="absolute inset-0"></span> | ||
Particles | ||
</a> | ||
</h3> | ||
</div> | ||
</div> | ||
</div> | ||
</div> | ||
</div> | ||
</div> | ||
</div> | ||
|
||
<div class="mt-6 sm:hidden"> | ||
<a href="#" class="block text-sm font-semibold text-indigo-600 hover:text-indigo-500"> | ||
Browse all categories | ||
<span aria-hidden="true"> →</span> | ||
</a> | ||
</div> | ||
</div> | ||
</div> | ||
|
||
</div> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
<script lang="ts"> | ||
import { onMount } from 'svelte'; | ||
import emotionLexicon from './emotion_lexicon.json'; | ||
import Credit from '$lib/Credit.svelte'; | ||
let lexicon: Record<string, string[]> = emotionLexicon; | ||
function tokenize(text: string) { | ||
// Simple tokenizer that converts text to lowercase and splits on non-word characters | ||
return text.toLowerCase().match(/\b\w+\b/g); | ||
} | ||
function analyzeEmotion(tweet: string) { | ||
const words = tokenize(tweet); | ||
const emotions: Record<string, number> = {}; | ||
if (!words) return {}; | ||
words.forEach((word: string) => { | ||
if (lexicon[word]) { | ||
lexicon[word].forEach((emotion) => { | ||
emotions[emotion] = (emotions[emotion] || 0) + 1; | ||
}); | ||
} | ||
}); | ||
return emotions; | ||
} | ||
let allEmotions: Record<string, number> = {}; | ||
$: total = Object.values(allEmotions).reduce((acc, curr) => acc + curr, 0) as number; | ||
let posts = 0; | ||
const emotionEmojis: Record<string, string> = { | ||
anger: '😠', | ||
anticipation: '🤔', | ||
disgust: '🤢', | ||
fear: '😨', | ||
joy: '😊', | ||
sadness: '😢', | ||
surprise: '😲', | ||
trust: '🤝' | ||
}; | ||
onMount(() => { | ||
const url = | ||
'wss://jetstream2.us-east.bsky.network/subscribe?wantedCollections=app.bsky.feed.post'; | ||
// WebSocket logic | ||
const ws = new WebSocket(url); | ||
ws.onopen = () => { | ||
console.log('Connected to BlueSky WebSocket'); | ||
}; | ||
ws.onmessage = (event) => { | ||
const json = JSON.parse(event.data); | ||
if (json.kind === 'account') console.log(json); | ||
if ( | ||
json.kind === 'commit' && | ||
json.commit.collection === 'app.bsky.feed.post' && | ||
json.commit.operation === 'create' && | ||
json.commit.record.text && | ||
(json.commit.record.langs?.includes('en') || !json.commit.record.langs) | ||
) { | ||
let emotions = analyzeEmotion(json.commit.record.text); | ||
for (const emotion in emotions) { | ||
allEmotions[emotion] = (allEmotions[emotion] || 0) + emotions[emotion]; | ||
} | ||
posts++; | ||
} | ||
}; | ||
ws.onerror = (error) => { | ||
console.error('WebSocket error:', error); | ||
}; | ||
ws.onclose = () => { | ||
console.log('WebSocket connection closed'); | ||
}; | ||
}); | ||
</script> | ||
|
||
{#if total > 10} | ||
<div class="max-w-4xl mx-auto py-24 px-4"> | ||
<div class="inline-flex items-center gap-2 text-2xl w-full justify-center font-bold text-white"> | ||
realtime emotions on | ||
<span class="sr-only"> | ||
BlueSky | ||
</span> | ||
<svg | ||
fill="currentColor" | ||
xmlns="http://www.w3.org/2000/svg" | ||
viewBox="-40 -40 680 620" | ||
version="1.1" | ||
class="size-7 fill-cyan-400" | ||
> | ||
<path | ||
d="m135.72 44.03c66.496 49.921 138.02 151.14 164.28 205.46 26.262-54.316 97.782-155.54 164.28-205.46 47.98-36.021 125.72-63.892 125.72 24.795 0 17.712-10.155 148.79-16.111 170.07-20.703 73.984-96.144 92.854-163.25 81.433 117.3 19.964 147.14 86.092 82.697 152.22-122.39 125.59-175.91-31.511-189.63-71.766-2.514-7.3797-3.6904-10.832-3.7077-7.8964-0.0174-2.9357-1.1937 0.51669-3.7077 7.8964-13.714 40.255-67.233 197.36-189.63 71.766-64.444-66.128-34.605-132.26 82.697-152.22-67.108 11.421-142.55-7.4491-163.25-81.433-5.9562-21.282-16.111-152.36-16.111-170.07 0-88.687 77.742-60.816 125.72-24.795z" | ||
></path> | ||
</svg> | ||
</div> | ||
<div class="mt-8 flex w-full flex-col gap-2 text-white"> | ||
{#each Object.entries(allEmotions) as [emotion, count]} | ||
<div class="grid grid-cols-5 items-center gap-2"> | ||
<div class="col-span-1 flex flex-col items-center"> | ||
<span class="text-4xl">{emotionEmojis[emotion]}</span> | ||
<span class="text-xs font-semibold">{emotion}</span> | ||
</div> | ||
<div | ||
class="relative col-span-4 h-full w-full flex items-center" | ||
> | ||
<div class="h-fit w-full overflow-hidden rounded-2xl border border-cyan-500 relative py-0.5"> | ||
<span class="ml-4 font-semibold text-xs">{Math.floor((count / total) * 100)}%</span> | ||
<div | ||
style="width: {(count / total) * 100}%" | ||
class="absolute bottom-0 left-0 top-0 -z-10 h-full w-full bg-cyan-700" | ||
></div> | ||
</div> | ||
</div> | ||
</div> | ||
{/each} | ||
</div> | ||
<div class="text-sm text-gray-400 text-end mt-4 max-w-lg ml-auto"> | ||
analized {posts} posts, | ||
using the <a class="text-cyan-400 hover:text-cyan-500 font-semibold" href="https://saifmohammad.com/WebPages/NRC-Emotion-Lexicon.htm" target="_blank"> | ||
NRC Word-Emotion Association Lexicon</a>, this is not any kind of scientific analysis, just a fun experiment. | ||
</div> | ||
</div> | ||
{/if} | ||
|
||
|
||
<Credit name="emotions" /> |
Oops, something went wrong.