junior-sheer/recommender/app/witch/page.tsx
2024-05-03 18:07:01 -05:00

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>
);
}