diff --git a/website/package-lock.json b/website/package-lock.json index b215f733..3f3d1870 100644 --- a/website/package-lock.json +++ b/website/package-lock.json @@ -41,6 +41,7 @@ "react-dom": "18.2.0", "react-feature-flags": "^1.0.0", "react-icons": "^4.7.1", + "react-table": "^7.8.0", "sharp": "^0.31.3", "swr": "^2.0.0", "tailwindcss": "^3.2.4", @@ -32625,6 +32626,18 @@ } } }, + "node_modules/react-table": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/react-table/-/react-table-7.8.0.tgz", + "integrity": "sha512-hNaz4ygkZO4bESeFfnfOft73iBUj8K5oKi1EcSHPAibEydfsX2MyU6Z8KCr3mv3C9Kqqh71U+DhZkFvibbnPbA==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^16.8.3 || ^17.0.0-0 || ^18.0.0" + } + }, "node_modules/read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", @@ -61993,6 +62006,12 @@ "tslib": "^2.0.0" } }, + "react-table": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/react-table/-/react-table-7.8.0.tgz", + "integrity": "sha512-hNaz4ygkZO4bESeFfnfOft73iBUj8K5oKi1EcSHPAibEydfsX2MyU6Z8KCr3mv3C9Kqqh71U+DhZkFvibbnPbA==", + "requires": {} + }, "read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", diff --git a/website/package.json b/website/package.json index dd0ca93a..855feb1d 100644 --- a/website/package.json +++ b/website/package.json @@ -58,6 +58,7 @@ "react-dom": "18.2.0", "react-feature-flags": "^1.0.0", "react-icons": "^4.7.1", + "react-table": "^7.8.0", "sharp": "^0.31.3", "swr": "^2.0.0", "tailwindcss": "^3.2.4", diff --git a/website/src/components/LeaderboardGridCell/LeaderboardGridCell.tsx b/website/src/components/LeaderboardGridCell/LeaderboardGridCell.tsx index 51dd877e..35d83a75 100644 --- a/website/src/components/LeaderboardGridCell/LeaderboardGridCell.tsx +++ b/website/src/components/LeaderboardGridCell/LeaderboardGridCell.tsx @@ -1,97 +1,73 @@ -import { Avatar, Box, Grid, GridItem, Text, useColorModeValue } from "@chakra-ui/react"; -import { FiChevronDown } from "react-icons/fi"; +import { Table, TableContainer, Tbody, Td, Th, Thead, Tr, useColorModeValue } from "@chakra-ui/react"; +import React from "react"; +import { useTable } from "react-table"; import { get } from "src/lib/api"; -import useSWR from "swr"; +import { LeaderboardEntity, LeaderboardTimeFrame } from "src/types/Leaderboard"; +import useSWRImmutable from "swr/immutable"; + +const columns = [ + { + Header: "Rank", + accessor: (item: LeaderboardEntity, rowIndex: number) => "#" + (item.user_rank + 1), + style: { width: "90px" }, + }, + { + Header: "Score", + accessor: "leader_score", + style: { width: "90px" }, + }, + { + Header: "User", + accessor: "display_name", + }, +]; /** * Presents a grid of leaderboard entries with more detailed information. */ -const LeaderboardGridCell = () => { - const { data: leaderboardEntries } = useSWR("/api/leaderboard", get); +const LeaderboardGridCell = ({ timeFrame }: { timeFrame: LeaderboardTimeFrame }) => { + const { data } = useSWRImmutable(`/api/leaderboard?time_frame=${timeFrame}`, get, { + fallbackData: [], + revalidateOnMount: true, + }); const backgroundColor = useColorModeValue("white", "gray.800"); - const columns = `repeat(${FILTER.length}, 1fr)`; + + const { getTableProps, getTableBodyProps, headerGroups, rows, prepareRow } = useTable({ columns, data }); return ( - <> - - - {FILTER.map(({ title, GridItemProps }, index) => ( - - - - {title} - - - - - + + + + {headerGroups.map((headerGroup, idx) => ( + + {headerGroup.headers.map((column) => ( + + ))} + ))} - - - - {leaderboardEntries?.map(({ display_name, ranking, score }, index) => ( - - - - - {display_name} - - - - - {ranking} - - - - {score} - - {/* - - {item.medal} - - */} - - ))} - - + + + + {rows.map((row) => { + prepareRow(row); + return ( + + {row.cells.map((cell, idx) => { + return ( + + ); + })} + + ); + })} + +
+ {column.render("Header")} +
+ {cell.render("Cell")} +
+
); }; -/** - * Specifies the table headers in the grid. - */ -const FILTER = [ - { - title: "User", - isActive: false, - GridItemProps: { justifyContent: "start" }, - }, - { - title: "Rank", - isActive: false, - GridItemProps: { justifyContent: "center" }, - }, - { - title: "Score", - isActive: false, - GridItemProps: { justifyContent: "center" }, - }, - /* - { - title: "Medal", - isActive: false, - GridItemProps: { justifyContent: "center" }, - }, - */ -]; - export { LeaderboardGridCell }; diff --git a/website/src/lib/oasst_api_client.ts b/website/src/lib/oasst_api_client.ts index 43ca6c49..948bf320 100644 --- a/website/src/lib/oasst_api_client.ts +++ b/website/src/lib/oasst_api_client.ts @@ -1,5 +1,6 @@ import { JWT } from "next-auth/jwt"; import type { Message } from "src/types/Conversation"; +import { LeaderboardReply, LeaderboardTimeFrame } from "src/types/Leaderboard"; import type { BackendUser } from "src/types/Users"; export class OasstError { @@ -187,8 +188,8 @@ export class OasstApiClient { /** * Returns the current leaderboard ranking. */ - async fetch_leaderboard(): Promise { - return this.get(`/api/v1/experimental/leaderboards/create/assistant`); + async fetch_leaderboard(time_frame: LeaderboardTimeFrame): Promise { + return this.get(`/api/v1/leaderboards/${time_frame}`); } } diff --git a/website/src/pages/api/leaderboard.ts b/website/src/pages/api/leaderboard.ts index d2a591a8..10a9a3a7 100644 --- a/website/src/pages/api/leaderboard.ts +++ b/website/src/pages/api/leaderboard.ts @@ -1,18 +1,14 @@ import { withoutRole } from "src/lib/auth"; import { oasstApiClient } from "src/lib/oasst_api_client"; +import { LeaderboardTimeFrame } from "src/types/Leaderboard"; /** * Returns the set of valid labels that can be applied to messages. */ const handler = withoutRole("banned", async (req, res) => { - const { leaderboard } = await oasstApiClient.fetch_leaderboard(); - res.status(200).json( - leaderboard.map(({ display_name, ranking, score }) => ({ - display_name, - ranking, - score, - })) - ); + const time_frame = req.query.time_frame as LeaderboardTimeFrame; + const { leaderboard } = await oasstApiClient.fetch_leaderboard(time_frame); + res.status(200).json(leaderboard); }); export default handler; diff --git a/website/src/pages/leaderboard.tsx b/website/src/pages/leaderboard.tsx index ef2c4529..131778c9 100644 --- a/website/src/pages/leaderboard.tsx +++ b/website/src/pages/leaderboard.tsx @@ -1,7 +1,8 @@ -import { Box, Heading } from "@chakra-ui/react"; +import { Box, Heading, Tabs, TabList, TabPanels, Tab, TabPanel } from "@chakra-ui/react"; import Head from "next/head"; import { getDashboardLayout } from "src/components/Layout"; import { LeaderboardGridCell } from "src/components/LeaderboardGridCell"; +import { LeaderboardTimeFrame } from "src/types/Leaderboard"; const Leaderboard = () => { return ( @@ -14,7 +15,29 @@ const Leaderboard = () => { Leaderboard - + + + Daily + Weekly + Monthly + Overall + + + + + + + + + + + + + + + + + ); diff --git a/website/src/types/Leaderboard.ts b/website/src/types/Leaderboard.ts index ea3c9dae..2e72fafc 100644 --- a/website/src/types/Leaderboard.ts +++ b/website/src/types/Leaderboard.ts @@ -3,3 +3,40 @@ export interface LeaderboardEntry { ranking: number; score: number; } + +export const enum LeaderboardTimeFrame { + day = "day", + week = "week", + month = "month", + total = "total", +} +export interface LeaderboardReply { + time_frame: LeaderboardTimeFrame; + leaderboard: LeaderboardEntity[]; +} + +export interface LeaderboardEntity { + user_rank: number; + user_id: string; + username: string; + auth_method: string; + display_name: string; + leader_score: number; + base_date: string; + modified_date: string; + prompts: number; + replies_assistant: number; + replies_prompter: number; + labels_simple: number; + labels_full: number; + rankings_total: number; + rankings_good: number; + accepted_prompts: number; + accepted_replies_assistant: number; + accepted_replies_prompter: number; + reply_ranked_1: number; + reply_ranked_2: number; + reply_ranked_3: number; + streak_last_day_date: number | null; + streak_days: number | null; +}