mirror of
https://github.com/wassname/Open-Assistant.git
synced 2026-07-21 12:20:08 +08:00
Merge pull request #903 from notmd/leaderboard_enhancement
Show more stats in leaderboard table
This commit is contained in:
Generated
-19
@@ -46,7 +46,6 @@
|
||||
"react-feature-flags": "^1.0.0",
|
||||
"react-hook-form": "^7.42.1",
|
||||
"react-i18next": "^12.1.4",
|
||||
"react-table": "^7.8.0",
|
||||
"sharp": "^0.31.3",
|
||||
"swr": "^2.0.0",
|
||||
"tailwindcss": "^3.2.4",
|
||||
@@ -32756,18 +32755,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"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",
|
||||
@@ -62197,12 +62184,6 @@
|
||||
"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",
|
||||
|
||||
@@ -63,7 +63,6 @@
|
||||
"react-feature-flags": "^1.0.0",
|
||||
"react-hook-form": "^7.42.1",
|
||||
"react-i18next": "^12.1.4",
|
||||
"react-table": "^7.8.0",
|
||||
"sharp": "^0.31.3",
|
||||
"swr": "^2.0.0",
|
||||
"tailwindcss": "^3.2.4",
|
||||
|
||||
@@ -7,5 +7,8 @@
|
||||
"rank": "Rank",
|
||||
"score": "Score",
|
||||
"user": "User",
|
||||
"weekly": "Weekly"
|
||||
"weekly": "Weekly",
|
||||
"prompt": "Prompts",
|
||||
"reply": "Replies",
|
||||
"label": "Labels"
|
||||
}
|
||||
|
||||
+8
-14
@@ -1,11 +1,9 @@
|
||||
import { Box, Link, Text, useColorModeValue } from "@chakra-ui/react";
|
||||
import { Card, CardBody, Link, Text } from "@chakra-ui/react";
|
||||
import NextLink from "next/link";
|
||||
import { LeaderboardGridCell } from "src/components/LeaderboardGridCell";
|
||||
import { LeaderboardTable } from "src/components/LeaderboardTable";
|
||||
import { LeaderboardTimeFrame } from "src/types/Leaderboard";
|
||||
|
||||
export function LeaderboardTable() {
|
||||
const backgroundColor = useColorModeValue("white", "gray.700");
|
||||
const accentColor = useColorModeValue("gray.200", "gray.900");
|
||||
export function LeaderboardWidget() {
|
||||
return (
|
||||
<main className="h-fit col-span-3">
|
||||
<div className="flex flex-col gap-4">
|
||||
@@ -17,15 +15,11 @@ export function LeaderboardTable() {
|
||||
</Text>
|
||||
</Link>
|
||||
</div>
|
||||
<Box
|
||||
backgroundColor={backgroundColor}
|
||||
boxShadow="base"
|
||||
dropShadow={accentColor}
|
||||
borderRadius="xl"
|
||||
className="p-6 shadow-sm"
|
||||
>
|
||||
<LeaderboardGridCell timeFrame={LeaderboardTimeFrame.day} />
|
||||
</Box>
|
||||
<Card>
|
||||
<CardBody>
|
||||
<LeaderboardTable timeFrame={LeaderboardTimeFrame.day} limit={5} />
|
||||
</CardBody>
|
||||
</Card>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
@@ -1,3 +1,3 @@
|
||||
export { LeaderboardTable } from "./LeaderboardTable";
|
||||
export { LeaderboardWidget } from "./LeaderboardWidget";
|
||||
export { TaskOption } from "./TaskOption";
|
||||
export { WelcomeCard } from "./WelcomeCard";
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Card,
|
||||
CardBody,
|
||||
Flex,
|
||||
FormControl,
|
||||
FormLabel,
|
||||
@@ -49,6 +47,7 @@ export type DataTableProps<T> = {
|
||||
onFilterChange?: (items: FilterItem[]) => void;
|
||||
disableNext?: boolean;
|
||||
disablePrevious?: boolean;
|
||||
disablePagination?: boolean;
|
||||
};
|
||||
|
||||
export const DataTable = <T,>({
|
||||
@@ -61,6 +60,7 @@ export const DataTable = <T,>({
|
||||
onFilterChange,
|
||||
disableNext,
|
||||
disablePrevious,
|
||||
disablePagination,
|
||||
}: DataTableProps<T>) => {
|
||||
const { getHeaderGroups, getRowModel } = useReactTable<T>({
|
||||
data,
|
||||
@@ -79,8 +79,8 @@ export const DataTable = <T,>({
|
||||
onFilterChange(newValues);
|
||||
};
|
||||
return (
|
||||
<Card>
|
||||
<CardBody>
|
||||
<>
|
||||
{!disablePagination && (
|
||||
<Flex mb="2">
|
||||
<Button onClick={onPreviousClick} disabled={disablePrevious}>
|
||||
Previous
|
||||
@@ -90,42 +90,42 @@ export const DataTable = <T,>({
|
||||
Next
|
||||
</Button>
|
||||
</Flex>
|
||||
<TableContainer>
|
||||
<Table variant="simple">
|
||||
<TableCaption>{caption}</TableCaption>
|
||||
<Thead>
|
||||
{getHeaderGroups().map((headerGroup) => (
|
||||
<Tr key={headerGroup.id}>
|
||||
{headerGroup.headers.map((header) => (
|
||||
<Th key={header.id}>
|
||||
<Box display="flex" alignItems="center">
|
||||
{header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext())}
|
||||
{(header.column.columnDef as DataTableColumnDef<T>).filterable && (
|
||||
<FilterModal
|
||||
value={filterValues.find((value) => value.id === header.id)?.value ?? ""}
|
||||
onChange={(value) => handleFilterChange({ id: header.id, value })}
|
||||
label={flexRender(header.column.columnDef.header, header.getContext())}
|
||||
></FilterModal>
|
||||
)}
|
||||
</Box>
|
||||
</Th>
|
||||
))}
|
||||
</Tr>
|
||||
))}
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{getRowModel().rows.map((row) => (
|
||||
<Tr key={row.id}>
|
||||
{row.getVisibleCells().map((cell) => (
|
||||
<Td key={cell.id}>{flexRender(cell.column.columnDef.cell, cell.getContext())}</Td>
|
||||
))}
|
||||
</Tr>
|
||||
))}
|
||||
</Tbody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</CardBody>
|
||||
</Card>
|
||||
)}
|
||||
<TableContainer>
|
||||
<Table variant="simple">
|
||||
<TableCaption>{caption}</TableCaption>
|
||||
<Thead>
|
||||
{getHeaderGroups().map((headerGroup) => (
|
||||
<Tr key={headerGroup.id}>
|
||||
{headerGroup.headers.map((header) => (
|
||||
<Th key={header.id}>
|
||||
<Box display="flex" alignItems="center">
|
||||
{header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext())}
|
||||
{(header.column.columnDef as DataTableColumnDef<T>).filterable && (
|
||||
<FilterModal
|
||||
value={filterValues.find((value) => value.id === header.id)?.value ?? ""}
|
||||
onChange={(value) => handleFilterChange({ id: header.id, value })}
|
||||
label={flexRender(header.column.columnDef.header, header.getContext())}
|
||||
></FilterModal>
|
||||
)}
|
||||
</Box>
|
||||
</Th>
|
||||
))}
|
||||
</Tr>
|
||||
))}
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{getRowModel().rows.map((row) => (
|
||||
<Tr key={row.id}>
|
||||
{row.getVisibleCells().map((cell) => (
|
||||
<Td key={cell.id}>{flexRender(cell.column.columnDef.cell, cell.getContext())}</Td>
|
||||
))}
|
||||
</Tr>
|
||||
))}
|
||||
</Tbody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,90 +0,0 @@
|
||||
import { Table, TableContainer, Tbody, Td, Text, Th, Thead, Tr, useColorModeValue } from "@chakra-ui/react";
|
||||
import { useTranslation } from "next-i18next";
|
||||
import React, { useMemo } from "react";
|
||||
import { useTable } from "react-table";
|
||||
import { get } from "src/lib/api";
|
||||
import { LeaderboardReply, LeaderboardTimeFrame } from "src/types/Leaderboard";
|
||||
import useSWRImmutable from "swr/immutable";
|
||||
|
||||
const getColumns = (t) => [
|
||||
{
|
||||
Header: t("rank"),
|
||||
accessor: "rank",
|
||||
style: { width: "90px" },
|
||||
},
|
||||
{
|
||||
Header: t("score"),
|
||||
accessor: "leader_score",
|
||||
style: { width: "90px" },
|
||||
},
|
||||
{
|
||||
Header: t("user"),
|
||||
accessor: "display_name",
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* Presents a grid of leaderboard entries with more detailed information.
|
||||
*/
|
||||
const LeaderboardGridCell = ({ timeFrame }: { timeFrame: LeaderboardTimeFrame }) => {
|
||||
const { t } = useTranslation(["leaderboard", "common"]);
|
||||
const { data: reply } = useSWRImmutable<LeaderboardReply>(`/api/leaderboard?time_frame=${timeFrame}`, get, {
|
||||
revalidateOnMount: true,
|
||||
});
|
||||
|
||||
const columns = useMemo(() => getColumns(t), [t]);
|
||||
|
||||
const { getTableProps, getTableBodyProps, headerGroups, rows, prepareRow } = useTable({
|
||||
columns,
|
||||
data: reply?.leaderboard ?? [],
|
||||
});
|
||||
|
||||
const backgroundColor = useColorModeValue("white", "gray.800");
|
||||
|
||||
const lastUpdated = useMemo(() => {
|
||||
const val = new Date(reply?.last_updated);
|
||||
return t("last_updated_at", { val, formatParams: { val: { dateStyle: "full", timeStyle: "short" } } });
|
||||
}, [t, reply?.last_updated]);
|
||||
|
||||
if (!reply) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<TableContainer>
|
||||
<Table {...getTableProps()}>
|
||||
<Thead bg={backgroundColor}>
|
||||
{headerGroups.map((headerGroup, idx) => (
|
||||
<Tr key={idx} {...headerGroup.getHeaderGroupProps()}>
|
||||
{headerGroup.headers.map((column) => (
|
||||
<Th {...column.getHeaderProps([{ style: column.style }])} key={column.id}>
|
||||
{column.render("Header")}
|
||||
</Th>
|
||||
))}
|
||||
</Tr>
|
||||
))}
|
||||
</Thead>
|
||||
|
||||
<Tbody {...getTableBodyProps()}>
|
||||
{rows.map((row) => {
|
||||
prepareRow(row);
|
||||
return (
|
||||
<Tr key={row.id} {...row.getRowProps()}>
|
||||
{row.cells.map((cell, idx) => {
|
||||
return (
|
||||
<Td key={row.id + idx} {...cell.getCellProps([{ style: cell.column.style }])}>
|
||||
{cell.render("Cell")}
|
||||
</Td>
|
||||
);
|
||||
})}
|
||||
</Tr>
|
||||
);
|
||||
})}
|
||||
</Tbody>
|
||||
</Table>
|
||||
<Text p="2">{lastUpdated}</Text>
|
||||
</TableContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export { LeaderboardGridCell };
|
||||
@@ -1 +0,0 @@
|
||||
export * from "./LeaderboardGridCell";
|
||||
@@ -0,0 +1,64 @@
|
||||
import { CircularProgress } from "@chakra-ui/react";
|
||||
import { createColumnHelper } from "@tanstack/react-table";
|
||||
import { useTranslation } from "next-i18next";
|
||||
import React, { useMemo } from "react";
|
||||
import { get } from "src/lib/api";
|
||||
import { LeaderboardEntity, LeaderboardReply, LeaderboardTimeFrame } from "src/types/Leaderboard";
|
||||
import useSWRImmutable from "swr/immutable";
|
||||
|
||||
import { DataTable } from "../DataTable";
|
||||
|
||||
const columnHelper = createColumnHelper<LeaderboardEntity>();
|
||||
|
||||
/**
|
||||
* Presents a grid of leaderboard entries with more detailed information.
|
||||
*/
|
||||
export const LeaderboardTable = ({ timeFrame, limit }: { timeFrame: LeaderboardTimeFrame; limit: number }) => {
|
||||
const { t } = useTranslation("leaderboard");
|
||||
const {
|
||||
data: reply,
|
||||
isLoading,
|
||||
error,
|
||||
} = useSWRImmutable<LeaderboardReply>(`/api/leaderboard?time_frame=${timeFrame}&limit=${limit}`, get, {
|
||||
revalidateOnMount: true,
|
||||
});
|
||||
|
||||
const columns = useMemo(
|
||||
() => [
|
||||
columnHelper.accessor("rank", {
|
||||
header: t("rank"),
|
||||
}),
|
||||
columnHelper.accessor("display_name", {
|
||||
header: t("user"),
|
||||
}),
|
||||
columnHelper.accessor("leader_score", {
|
||||
header: t("score"),
|
||||
}),
|
||||
columnHelper.accessor("prompts", {
|
||||
header: t("prompt"),
|
||||
}),
|
||||
columnHelper.accessor((row) => row.replies_assistant + row.replies_prompter, {
|
||||
header: t("reply"),
|
||||
}),
|
||||
columnHelper.accessor((row) => row.labels_full + row.labels_simple, {
|
||||
header: t("label"),
|
||||
}),
|
||||
],
|
||||
[t]
|
||||
);
|
||||
|
||||
const lastUpdated = useMemo(() => {
|
||||
const val = new Date(reply?.last_updated);
|
||||
return t("last_updated_at", { val, formatParams: { val: { dateStyle: "full", timeStyle: "short" } } });
|
||||
}, [t, reply?.last_updated]);
|
||||
|
||||
if (isLoading) {
|
||||
return <CircularProgress isIndeterminate></CircularProgress>;
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return <span>Unable to load leaderboard</span>;
|
||||
}
|
||||
|
||||
return <DataTable data={reply.leaderboard} columns={columns} caption={lastUpdated} disablePagination></DataTable>;
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./LeaderboardTable";
|
||||
@@ -1,4 +1,4 @@
|
||||
import { IconButton } from "@chakra-ui/react";
|
||||
import { Card, CardBody, IconButton } from "@chakra-ui/react";
|
||||
import { createColumnHelper } from "@tanstack/react-table";
|
||||
import { Pencil } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
@@ -90,19 +90,21 @@ export const UserTable = memo(function UserTable() {
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<DataTable
|
||||
data={data?.items || []}
|
||||
columns={columns}
|
||||
caption="Users"
|
||||
onNextClick={toNextPage}
|
||||
onPreviousClick={toPreviousPage}
|
||||
disableNext={!data?.next}
|
||||
disablePrevious={!data?.prev}
|
||||
filterValues={filterValues}
|
||||
onFilterChange={handleFilterValuesChange}
|
||||
></DataTable>
|
||||
{error && "Unable to load users."}
|
||||
</>
|
||||
<Card>
|
||||
<CardBody>
|
||||
<DataTable
|
||||
data={data?.items || []}
|
||||
columns={columns}
|
||||
caption="Users"
|
||||
onNextClick={toNextPage}
|
||||
onPreviousClick={toPreviousPage}
|
||||
disableNext={!data?.next}
|
||||
disablePrevious={!data?.prev}
|
||||
filterValues={filterValues}
|
||||
onFilterChange={handleFilterValuesChange}
|
||||
></DataTable>
|
||||
{error && "Unable to load users."}
|
||||
</CardBody>
|
||||
</Card>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -262,8 +262,14 @@ export class OasstApiClient {
|
||||
/**
|
||||
* Returns the current leaderboard ranking.
|
||||
*/
|
||||
async fetch_leaderboard(time_frame: LeaderboardTimeFrame): Promise<LeaderboardReply> {
|
||||
return this.get(`/api/v1/leaderboards/${time_frame}`);
|
||||
async fetch_leaderboard(
|
||||
time_frame: LeaderboardTimeFrame,
|
||||
{ limit = 20 }: { limit?: number }
|
||||
): Promise<LeaderboardReply> {
|
||||
const params = new URLSearchParams({
|
||||
limit: limit.toString(),
|
||||
});
|
||||
return this.get(`/api/v1/leaderboards/${time_frame}?${params.toString()}`);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -7,7 +7,7 @@ import { LeaderboardTimeFrame } from "src/types/Leaderboard";
|
||||
*/
|
||||
const handler = withoutRole("banned", async (req, res) => {
|
||||
const time_frame = (req.query.time_frame as LeaderboardTimeFrame) ?? LeaderboardTimeFrame.day;
|
||||
const info = await oasstApiClient.fetch_leaderboard(time_frame);
|
||||
const info = await oasstApiClient.fetch_leaderboard(time_frame, { limit: req.query.limit as unknown as number });
|
||||
res.status(200).json(info);
|
||||
});
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Flex } from "@chakra-ui/react";
|
||||
import Head from "next/head";
|
||||
import { useTranslation } from "next-i18next";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { LeaderboardTable, TaskOption, WelcomeCard } from "src/components/Dashboard";
|
||||
import { LeaderboardWidget, TaskOption, WelcomeCard } from "src/components/Dashboard";
|
||||
import { getDashboardLayout } from "src/components/Layout";
|
||||
import { TaskCategory } from "src/components/Tasks/TaskTypes";
|
||||
import { get } from "src/lib/api";
|
||||
@@ -42,7 +42,7 @@ const Dashboard = () => {
|
||||
<Flex direction="column" gap="10">
|
||||
<WelcomeCard />
|
||||
<TaskOption content={availableTaskTypes} />
|
||||
<LeaderboardTable />
|
||||
<LeaderboardWidget />
|
||||
</Flex>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { Box, Heading, Tab, TabList, TabPanel, TabPanels, Tabs } from "@chakra-ui/react";
|
||||
import { Box, Card, CardBody, Heading, Tab, TabList, TabPanel, TabPanels, Tabs } from "@chakra-ui/react";
|
||||
import Head from "next/head";
|
||||
import { useTranslation } from "next-i18next";
|
||||
import { getDashboardLayout } from "src/components/Layout";
|
||||
import { LeaderboardGridCell } from "src/components/LeaderboardGridCell";
|
||||
export { getDefaultStaticProps as getStaticProps } from "src/lib/default_static_props";
|
||||
import { LeaderboardTable } from "src/components/LeaderboardTable";
|
||||
import { LeaderboardTimeFrame } from "src/types/Leaderboard";
|
||||
|
||||
const Leaderboard = () => {
|
||||
@@ -18,29 +18,33 @@ const Leaderboard = () => {
|
||||
<Heading fontSize="2xl" fontWeight="bold" pb="4">
|
||||
{t("leaderboard:leaderboard")}
|
||||
</Heading>
|
||||
<Tabs isFitted isLazy>
|
||||
<TabList>
|
||||
<Tab>{t("leaderboard:daily")}</Tab>
|
||||
<Tab>{t("leaderboard:weekly")}</Tab>
|
||||
<Tab>{t("leaderboard:monthly")}</Tab>
|
||||
<Tab>{t("leaderboard:overall")}</Tab>
|
||||
</TabList>
|
||||
<Card>
|
||||
<CardBody>
|
||||
<Tabs isFitted isLazy>
|
||||
<TabList>
|
||||
<Tab>{t("leaderboard:daily")}</Tab>
|
||||
<Tab>{t("leaderboard:weekly")}</Tab>
|
||||
<Tab>{t("leaderboard:monthly")}</Tab>
|
||||
<Tab>{t("leaderboard:overall")}</Tab>
|
||||
</TabList>
|
||||
|
||||
<TabPanels>
|
||||
<TabPanel p="0">
|
||||
<LeaderboardGridCell timeFrame={LeaderboardTimeFrame.day} />
|
||||
</TabPanel>
|
||||
<TabPanel p="0">
|
||||
<LeaderboardGridCell timeFrame={LeaderboardTimeFrame.week} />
|
||||
</TabPanel>
|
||||
<TabPanel p="0">
|
||||
<LeaderboardGridCell timeFrame={LeaderboardTimeFrame.month} />
|
||||
</TabPanel>
|
||||
<TabPanel p="0">
|
||||
<LeaderboardGridCell timeFrame={LeaderboardTimeFrame.total} />
|
||||
</TabPanel>
|
||||
</TabPanels>
|
||||
</Tabs>
|
||||
<TabPanels>
|
||||
<TabPanel p="0">
|
||||
<LeaderboardTable timeFrame={LeaderboardTimeFrame.day} limit={20} />
|
||||
</TabPanel>
|
||||
<TabPanel p="0">
|
||||
<LeaderboardTable timeFrame={LeaderboardTimeFrame.week} limit={20} />
|
||||
</TabPanel>
|
||||
<TabPanel p="0">
|
||||
<LeaderboardTable timeFrame={LeaderboardTimeFrame.month} limit={20} />
|
||||
</TabPanel>
|
||||
<TabPanel p="0">
|
||||
<LeaderboardTable timeFrame={LeaderboardTimeFrame.total} limit={20} />
|
||||
</TabPanel>
|
||||
</TabPanels>
|
||||
</Tabs>
|
||||
</CardBody>
|
||||
</Card>
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user