194 lines
7.4 KiB
TypeScript
194 lines
7.4 KiB
TypeScript
"use client";
|
|
import React, { useState, useEffect } from 'react';
|
|
//import top_tags from "@/data/top_tags.json";
|
|
import positions from "@/data/positions.json";
|
|
import server_matrix from "@/data/server_matrix.json";
|
|
import server_names from "@/data/server_names.json";
|
|
import tag_names from "@/data/tag_names.json";
|
|
|
|
interface Tag {
|
|
index: number;
|
|
tag: string;
|
|
}
|
|
// Read from data/top_tags.json FILE
|
|
//const tags: Tag[] =
|
|
|
|
const selected_tags = [
|
|
"politics", "gardening", "art", "nature", "pride", "cycling", "climate",
|
|
"programming", "dogs", "privacy", "cats", "gaming", "education",
|
|
"science", "music", "movies", "food", "books", "lgbtq", "python",
|
|
"emacs", "gay", "trans", "furry", "photography", "cooking", "literature",
|
|
"television"
|
|
];
|
|
|
|
|
|
function dotProduct(a: number[], b: number[]): number {
|
|
return a.map((x, i) => x * b[i]).reduce((sum, current) => sum + current, 0);
|
|
}
|
|
|
|
function magnitude(arr: number[]): number {
|
|
return Math.sqrt(arr.map(x => x * x).reduce((sum, current) => sum + current, 0));
|
|
}
|
|
|
|
function cosineSimilarity(arr1: number[], arr2: number[]): number {
|
|
if (arr1.length !== arr2.length) {
|
|
throw new Error("Arrays must have the same length");
|
|
}
|
|
|
|
const dotProd = dotProduct(arr1, arr2);
|
|
const magnitudeProd = magnitude(arr1) * magnitude(arr2);
|
|
if (magnitudeProd === 0) {
|
|
return 0;
|
|
}
|
|
return dotProd / magnitudeProd;
|
|
}
|
|
|
|
function averageOfArrays(arr: number[][]): number[] {
|
|
// Get the length of the first sub-array
|
|
const length = arr[0].length;
|
|
|
|
// Initialize an array to store the sums
|
|
const sums = Array(length).fill(0);
|
|
|
|
// Loop over each sub-array
|
|
for (let i = 0; i < arr.length; i++) {
|
|
// Loop over each element in the sub-array
|
|
for (let j = 0; j < arr[i].length; j++) {
|
|
// Add the element to the corresponding sum
|
|
sums[j] += arr[i][j];
|
|
}
|
|
}
|
|
|
|
// Divide each sum by the number of arrays to get the average
|
|
const averages = sums.map(sum => sum / arr.length);
|
|
|
|
return averages;
|
|
}
|
|
|
|
|
|
|
|
const TagSelector: React.FC = () => {
|
|
//const top_tags: Tag[] = [];
|
|
const all_tags: Tag[] = tag_names.map((tag: string, index: number) => ({index, tag}))
|
|
const tags: Tag[] = all_tags.filter((tag: Tag) => selected_tags.includes(tag.tag));
|
|
//top_tags.filter((tag) => selected_tags.includes(tag.tag));
|
|
// State to keep track of selected tag IDs.
|
|
const [selectedTagIds, setSelectedTagIds] = useState<number[]>([]);
|
|
|
|
const [suggestedTagIds, setSuggestedTagIds] = useState<number[]>([]);
|
|
|
|
const [topServerIds, setTopServerIds] = useState<number[]>([]);
|
|
|
|
// Function to handle tag selection toggling.
|
|
const toggleTag = (tagId: number) => {
|
|
setSelectedTagIds((currentTagIds) =>
|
|
currentTagIds.includes(tagId)
|
|
? currentTagIds.filter((id) => id !== tagId)
|
|
: [...currentTagIds, tagId],
|
|
)
|
|
};
|
|
|
|
function find_most_similar_tags(all_tags: Tag[], selectedTagIds: number[], positions: number[][]) {
|
|
let most_similar: Tag[] = [];
|
|
// get the average position of all selected tags
|
|
if (selectedTagIds.length > 0) {
|
|
// loop through all selected tags and get their positions
|
|
const selected_positions = selectedTagIds.map((tagId) => positions[tagId]);
|
|
for (let i = 0; i < selected_positions.length; i++) {
|
|
let tag_similarity = positions.map((row) => cosineSimilarity(selected_positions[i], row));
|
|
let tag_rank = tag_similarity.map((similarity, index) => ({index, similarity})).sort((a, b) => b.similarity - a.similarity).map((item, index) => ({index: item.index, similarity: item.similarity, name: tag_names[item.index]}));
|
|
console.log(tag_rank.slice(0, 10));
|
|
let topServerIds = tag_rank.slice(0, 10).map((item) => item.index);
|
|
for (let tagId of all_tags.filter((tag) => topServerIds.includes(tag.index))) {
|
|
if (!selectedTagIds.includes(tagId.index) && !most_similar.includes(tagId)) {
|
|
most_similar.push(tagId);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return most_similar;
|
|
}
|
|
|
|
useEffect(() => {
|
|
// get the average position of all selected tags
|
|
if (selectedTagIds.length > 0) {
|
|
let selected_positions = selectedTagIds.map((tagId) => positions[tagId]);
|
|
// get the average of selected positions
|
|
let average_position = averageOfArrays(selected_positions);
|
|
// loop through each row of the server_matrix and calculate the cosine similarity
|
|
const server_similarity = server_matrix.map((row) => cosineSimilarity(average_position, row));
|
|
const server_rank = server_similarity.map((similarity, index) => ({index, similarity})).sort((a, b) => b.similarity - a.similarity).map((item, index) => ({index: item.index, similarity: item.similarity, name: server_names[item.index]}));
|
|
setTopServerIds(server_rank.slice(0, 10).map((item) => item.index));
|
|
|
|
// Find the most similar tags among all tags
|
|
const tag_similarity = all_tags.map((tag) => ({t: tag, sim: cosineSimilarity(average_position, positions[tag.index])})).sort((a, b) => b.sim - a.sim);
|
|
const most_similar = find_most_similar_tags(all_tags, selectedTagIds, positions);
|
|
//tag_similarity.slice(0, 50).map((item) => item.t);
|
|
console.log(most_similar);
|
|
setSuggestedTagIds(most_similar.map((tag) => tag.index));
|
|
console.log(suggestedTagIds);
|
|
}
|
|
}, [selectedTagIds]);
|
|
|
|
return (
|
|
<div>
|
|
<div className="flex flex-wrap gap-2">
|
|
<div>
|
|
<h3>Selected</h3>
|
|
{all_tags.filter((tag) => selectedTagIds.includes(tag.index)).map((tag) => (
|
|
<button
|
|
key={tag.index}
|
|
onClick={() => toggleTag(tag.index)}
|
|
className={`px-3 py-1 rounded-full text-sm ${
|
|
selectedTagIds.includes(tag.index) ? 'bg-blue-500 text-white' : 'bg-gray-200 text-gray-800'
|
|
} transition-colors duration-300 ease-in-out focus:outline-none focus:ring-2 focus:ring-blue-400 focus:ring-opacity-50`}
|
|
>
|
|
{tag.tag}
|
|
</button>
|
|
))}
|
|
</div>
|
|
<div>
|
|
<h3>Categories</h3>
|
|
{tags.filter((tag) => !selectedTagIds.includes(tag.index)).map((tag) => (
|
|
<button
|
|
key={tag.index}
|
|
onClick={() => toggleTag(tag.index)}
|
|
className={`px-3 py-1 rounded-full text-sm bg-gray-200 text-gray-800 transition-colors duration-300 ease-in-out focus:outline-none focus:ring-2 focus:ring-blue-400 focus:ring-opacity-50`}
|
|
>
|
|
{tag.tag}
|
|
</button>
|
|
))}
|
|
</div>
|
|
<div>
|
|
<h3>Suggested Tags ({suggestedTagIds.filter((tag) => !selectedTagIds.includes(tag)).length})</h3>
|
|
{all_tags.filter((tag) => suggestedTagIds.includes(tag.index)).filter((tag) => !selectedTagIds.includes(tag.index)).map((tag) => (
|
|
<button
|
|
key={tag.index}
|
|
onClick={() => toggleTag(tag.index)}
|
|
className={`px-3 py-1 rounded-full text-sm bg-gray-200 text-gray-800 transition-colors duration-300 ease-in-out focus:outline-none focus:ring-2 focus:ring-blue-400 focus:ring-opacity-50`}
|
|
>
|
|
{tag.tag}
|
|
</button>
|
|
))}
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<ul>
|
|
{topServerIds.map((id) => (
|
|
<li key={id}>{server_names[id]}</li>
|
|
))}
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
|
|
export default function Home() {
|
|
return (
|
|
<main className="flex min-h-screen flex-col items-center justify-between p-24">
|
|
<TagSelector />
|
|
</main>
|
|
);
|
|
}
|