Skip to content

Commit

Permalink
Merge pull request #3 from flo-bit/main
Browse files Browse the repository at this point in the history
add emotion analysis
  • Loading branch information
flo-bit authored Nov 19, 2024
2 parents 4603e0b + c62296e commit aa00276
Show file tree
Hide file tree
Showing 4 changed files with 17,395 additions and 55 deletions.
157 changes: 102 additions & 55 deletions src/routes/+page.svelte
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"> &rarr;</span>
</a>
</div>
</div>
</div>

</div>
135 changes: 135 additions & 0 deletions src/routes/emotions/+page.svelte
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" />
Loading

0 comments on commit aa00276

Please sign in to comment.