Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions frontend/src/api/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,9 @@ export interface AiTrendDataPoint {
submission_id: number;
submission_time: string;
gpu_type: string;
user_id?: string;
user_name?: string;
model?: string;
}

export interface AiTrendTimeSeries {
Expand All @@ -218,3 +221,57 @@ export async function fetchAiTrend(leaderboardId: string): Promise<AiTrendRespon
const r = await res.json();
return r.data;
}

export interface UserTrendResponse {
leaderboard_id: number;
user_ids: string[];
time_series: AiTrendTimeSeries;
}

export async function fetchUserTrend(
leaderboardId: string,
userIds: string[]
): Promise<UserTrendResponse> {
if (!userIds || userIds.length === 0) {
throw new Error("At least one user ID is required");
}
const url = `/api/leaderboard/${leaderboardId}/user_trend?user_id=${userIds.join(",")}`;
const res = await fetch(url);
if (!res.ok) {
const json = await res.json();
const message = json?.message || "Unknown error";
throw new APIError(`Failed to fetch user trend: ${message}`, res.status);
}
const r = await res.json();
return r.data;
}

export interface UserSearchResult {
user_id: string;
username: string;
}

export interface SearchUsersResponse {
leaderboard_id: number;
users: UserSearchResult[];
}

export async function searchUsers(
leaderboardId: string,
query: string = "",
limit: number = 20
): Promise<SearchUsersResponse> {
const params = new URLSearchParams();
if (query) params.set("q", query);
if (limit) params.set("limit", limit.toString());

const url = `/api/leaderboard/${leaderboardId}/users?${params.toString()}`;
const res = await fetch(url);
if (!res.ok) {
const json = await res.json();
const message = json?.message || "Unknown error";
throw new APIError(`Failed to search users: ${message}`, res.status);
}
const r = await res.json();
return r.data;
}
7 changes: 7 additions & 0 deletions frontend/src/pages/leaderboard/Leaderboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { useAuthStore } from "../../lib/store/authStore";
import SubmissionHistorySection from "./components/submission-history/SubmissionHistorySection";
import LeaderboardSubmit from "./components/LeaderboardSubmit";
import AiTrendChart from "./components/AiTrendChart";
import UserTrendChart from "./components/UserTrendChart";
export const CardTitle = styled(Typography)(() => ({
fontSize: "1.5rem",
fontWeight: "bold",
Expand Down Expand Up @@ -263,6 +264,12 @@ export default function Leaderboard() {
<AiTrendChart leaderboardId={id!!} />
</CardContent>
</Card>
<Card sx={{ mt: 2 }}>
<CardContent>
<CardTitle fontWeight="bold">User Performance Trend</CardTitle>
<UserTrendChart leaderboardId={id!!} />
</CardContent>
</Card>
</TabPanel>
)}
</Box>
Expand Down
30 changes: 10 additions & 20 deletions frontend/src/pages/leaderboard/components/AiTrendChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,12 @@ function hashStringToColor(str: string): string {
let hash = 0;
for (let i = 0; i < str.length; i++) {
hash = str.charCodeAt(i) + ((hash << 5) - hash);
hash = hash & hash; // Convert to 32bit integer
hash = hash & hash;
}

// Generate HSL color with good saturation and lightness for visibility
const hue = Math.abs(hash) % 360;
const saturation = 65 + (Math.abs(hash >> 8) % 20); // 65-85%
const lightness = 45 + (Math.abs(hash >> 16) % 15); // 45-60%
const saturation = 65 + (Math.abs(hash >> 8) % 20);
const lightness = 45 + (Math.abs(hash >> 16) % 15);

return `hsl(${hue}, ${saturation}%, ${lightness}%)`;
}
Expand All @@ -38,20 +37,17 @@ export default function AiTrendChart({ leaderboardId }: AiTrendChartProps) {

useEffect(() => {
const loadData = async () => {
setLoading(true);
setError(null);
try {
setLoading(true);
setError(null);
const result = await fetchAiTrend(leaderboardId);
setData(result);
} catch (err) {
setError(
err instanceof Error ? err.message : "Failed to load AI trend data",
);
} catch (err: any) {
setError(err.message || "Failed to load data");
} finally {
setLoading(false);
}
};

loadData();
}, [leaderboardId]);

Expand Down Expand Up @@ -108,9 +104,7 @@ export default function AiTrendChart({ leaderboardId }: AiTrendChartProps) {
alignItems="center"
minHeight={400}
>
<Typography color="text.secondary">
No H100 AI data available
</Typography>
<Typography color="text.secondary">No H100 AI data available</Typography>
</Box>
);
}
Expand All @@ -124,7 +118,7 @@ export default function AiTrendChart({ leaderboardId }: AiTrendChartProps) {
const sortedData = [...dataPoints].sort(
(a, b) =>
new Date(a.submission_time).getTime() -
new Date(b.submission_time).getTime(),
new Date(b.submission_time).getTime()
);

series.push({
Expand Down Expand Up @@ -235,9 +229,5 @@ export default function AiTrendChart({ leaderboardId }: AiTrendChartProps) {
series,
};

return (
<Box>
<ReactECharts option={option} style={{ height: 500 }} />
</Box>
);
return <ReactECharts option={option} style={{ height: 500 }} />;
}
Loading
Loading