gardarjuto commited on
Commit
3aaaddd
·
1 Parent(s): d4577f4

removed unused files

Browse files
frontend/src/components/shared/AuthContainer.js DELETED
@@ -1,168 +0,0 @@
1
- import React from "react";
2
- import {
3
- Box,
4
- Typography,
5
- Button,
6
- Chip,
7
- Stack,
8
- Paper,
9
- CircularProgress,
10
- useTheme,
11
- useMediaQuery,
12
- } from "@mui/material";
13
- import HFLogo from "../Logo/HFLogo";
14
- import { useAuth } from "../../hooks/useAuth";
15
- import LogoutIcon from "@mui/icons-material/Logout";
16
- import { useNavigate } from "react-router-dom";
17
-
18
- function AuthContainer({ actionText = "DO_ACTION" }) {
19
- const { isAuthenticated, user, login, logout, loading } = useAuth();
20
- const navigate = useNavigate();
21
- const theme = useTheme();
22
- const isMobile = useMediaQuery(theme.breakpoints.down("sm"));
23
-
24
- const handleLogout = () => {
25
- if (isAuthenticated && logout) {
26
- logout();
27
- navigate("/", { replace: true });
28
- window.location.reload();
29
- }
30
- };
31
-
32
- if (loading) {
33
- return (
34
- <Paper
35
- elevation={0}
36
- sx={{
37
- p: 3,
38
- mb: 4,
39
- border: "1px solid",
40
- borderColor: "grey.300",
41
- display: "flex",
42
- flexDirection: "column",
43
- alignItems: "center",
44
- gap: 2,
45
- }}
46
- >
47
- <CircularProgress size={24} />
48
- </Paper>
49
- );
50
- }
51
-
52
- if (!isAuthenticated) {
53
- return (
54
- <Paper
55
- elevation={0}
56
- sx={{
57
- p: 3,
58
- mb: 4,
59
- border: "1px solid",
60
- borderColor: "grey.300",
61
- display: "flex",
62
- flexDirection: "column",
63
- alignItems: "center",
64
- gap: 2,
65
- }}
66
- >
67
- <Typography variant="h6" align="center">
68
- Login to {actionText}
69
- </Typography>
70
- <Typography
71
- variant="body2"
72
- color="text.secondary"
73
- align="center"
74
- sx={{
75
- px: isMobile ? 2 : 0,
76
- }}
77
- >
78
- You need to be logged in with your Hugging Face account to{" "}
79
- {actionText.toLowerCase()}
80
- </Typography>
81
- <Button
82
- variant="contained"
83
- onClick={login}
84
- startIcon={
85
- <Box
86
- sx={{
87
- width: 20,
88
- height: 20,
89
- display: "flex",
90
- alignItems: "center",
91
- }}
92
- >
93
- <HFLogo />
94
- </Box>
95
- }
96
- sx={{
97
- textTransform: "none",
98
- fontWeight: 600,
99
- py: 1,
100
- px: 2,
101
- width: isMobile ? "100%" : "auto",
102
- }}
103
- >
104
- Sign in with Hugging Face
105
- </Button>
106
- </Paper>
107
- );
108
- }
109
-
110
- return (
111
- <Paper
112
- elevation={0}
113
- sx={{ p: 2, border: "1px solid", borderColor: "grey.300", mb: 4 }}
114
- >
115
- <Stack
116
- direction={isMobile ? "column" : "row"}
117
- spacing={2}
118
- alignItems={isMobile ? "stretch" : "center"}
119
- justifyContent="space-between"
120
- >
121
- <Stack
122
- direction={isMobile ? "column" : "row"}
123
- spacing={1}
124
- alignItems={isMobile ? "stretch" : "center"}
125
- sx={{ width: "100%" }}
126
- >
127
- <Typography
128
- variant="body1"
129
- align={isMobile ? "center" : "left"}
130
- sx={{ mb: isMobile ? 1 : 0 }}
131
- >
132
- Connected as <strong>{user?.username}</strong>
133
- </Typography>
134
- <Chip
135
- label={`Ready to ${actionText}`}
136
- color="success"
137
- size="small"
138
- variant="outlined"
139
- sx={{
140
- width: isMobile ? "100%" : "auto",
141
- height: isMobile ? 32 : 24,
142
- "& .MuiChip-label": {
143
- px: isMobile ? 2 : 1,
144
- },
145
- }}
146
- />
147
- </Stack>
148
- <Button
149
- variant="contained"
150
- onClick={handleLogout}
151
- endIcon={<LogoutIcon />}
152
- color="primary"
153
- sx={{
154
- minWidth: 120,
155
- height: 36,
156
- textTransform: "none",
157
- fontSize: "0.9375rem",
158
- width: isMobile ? "100%" : "auto",
159
- }}
160
- >
161
- Logout
162
- </Button>
163
- </Stack>
164
- </Paper>
165
- );
166
- }
167
-
168
- export default AuthContainer;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
frontend/src/config/auth.js DELETED
@@ -1,7 +0,0 @@
1
- export const HF_CONFIG = {
2
- CLIENT_ID: "18fe6b93-6921-444c-9a20-5c22c578f2d8",
3
- STORAGE_KEY: "hf_oauth",
4
- SCOPE: "openid profile",
5
- PROD_URL: "https://open-llm-leaderboard-open-llm-leaderboard.hf.space",
6
- DEV_URL: "http://localhost:7860"
7
- };
 
 
 
 
 
 
 
 
frontend/src/hooks/useAuth.js DELETED
@@ -1,173 +0,0 @@
1
- import { useState, useEffect } from "react";
2
- import { useLocation, useNavigate } from "react-router-dom";
3
- import { oauthLoginUrl, oauthHandleRedirectIfPresent } from "@huggingface/hub";
4
- import { HF_CONFIG } from "../config/auth";
5
-
6
- async function fetchUserInfo(token) {
7
- const response = await fetch("https://huggingface.co/api/whoami-v2", {
8
- headers: {
9
- Authorization: `Bearer ${token}`,
10
- },
11
- });
12
- if (!response.ok) {
13
- throw new Error("Failed to fetch user info");
14
- }
15
- return response.json();
16
- }
17
-
18
- export function useAuth() {
19
- const [isAuthenticated, setIsAuthenticated] = useState(false);
20
- const [user, setUser] = useState(null);
21
- const [loading, setLoading] = useState(true);
22
- const [error, setError] = useState(null);
23
- const location = useLocation();
24
- const navigate = useNavigate();
25
-
26
- // Initialisation de l'authentification
27
- useEffect(() => {
28
- let mounted = true;
29
- const initAuth = async () => {
30
- try {
31
- console.group("Auth Initialization");
32
- setLoading(true);
33
-
34
- // Vérifier s'il y a une redirection OAuth d'abord
35
- let oauthResult = await oauthHandleRedirectIfPresent();
36
-
37
- // Si pas de redirection, vérifier le localStorage
38
- if (!oauthResult) {
39
- const storedAuth = localStorage.getItem(HF_CONFIG.STORAGE_KEY);
40
- if (storedAuth) {
41
- try {
42
- oauthResult = JSON.parse(storedAuth);
43
- console.log("Found existing auth");
44
- const userInfo = await fetchUserInfo(oauthResult.access_token);
45
- if (mounted) {
46
- setIsAuthenticated(true);
47
- setUser({
48
- username: userInfo.name,
49
- token: oauthResult.access_token,
50
- });
51
- }
52
- } catch (err) {
53
- console.log("Invalid stored auth data, clearing...", err);
54
- localStorage.removeItem(HF_CONFIG.STORAGE_KEY);
55
- if (mounted) {
56
- setIsAuthenticated(false);
57
- setUser(null);
58
- }
59
- }
60
- }
61
- } else {
62
- console.log("Processing OAuth redirect");
63
- const token = oauthResult.accessToken;
64
- const userInfo = await fetchUserInfo(token);
65
-
66
- const authData = {
67
- access_token: token,
68
- username: userInfo.name,
69
- };
70
-
71
- localStorage.setItem(HF_CONFIG.STORAGE_KEY, JSON.stringify(authData));
72
-
73
- if (mounted) {
74
- setIsAuthenticated(true);
75
- setUser({
76
- username: userInfo.name,
77
- token: token,
78
- });
79
- }
80
-
81
- // Rediriger vers la page d'origine
82
- const returnTo = localStorage.getItem("auth_return_to");
83
- if (returnTo) {
84
- navigate(returnTo);
85
- localStorage.removeItem("auth_return_to");
86
- }
87
- }
88
- } catch (err) {
89
- console.error("Auth initialization error:", err);
90
- if (mounted) {
91
- setError(err.message);
92
- setIsAuthenticated(false);
93
- setUser(null);
94
- }
95
- } finally {
96
- if (mounted) {
97
- setLoading(false);
98
- }
99
- console.groupEnd();
100
- }
101
- };
102
-
103
- initAuth();
104
-
105
- return () => {
106
- mounted = false;
107
- };
108
- }, [navigate, location.pathname]);
109
-
110
- const login = async () => {
111
- try {
112
- console.group("Login Process");
113
- setLoading(true);
114
-
115
- // Sauvegarder la route actuelle pour la redirection post-auth
116
- const currentRoute = window.location.hash.replace("#", "") || "/";
117
- localStorage.setItem("auth_return_to", currentRoute);
118
-
119
- // Déterminer l'URL de redirection en fonction de l'environnement
120
- const redirectUrl =
121
- window.location.hostname === "localhost" ||
122
- window.location.hostname === "127.0.0.1"
123
- ? HF_CONFIG.DEV_URL
124
- : HF_CONFIG.PROD_URL;
125
-
126
- console.log("Using redirect URL:", redirectUrl);
127
-
128
- // Générer l'URL de login et rediriger
129
- const loginUrl = await oauthLoginUrl({
130
- clientId: HF_CONFIG.CLIENT_ID,
131
- redirectUrl,
132
- scope: HF_CONFIG.SCOPE,
133
- });
134
-
135
- window.location.href = loginUrl + "&prompt=consent";
136
-
137
- console.groupEnd();
138
- } catch (err) {
139
- console.error("Login error:", err);
140
- setError(err.message);
141
- setLoading(false);
142
- console.groupEnd();
143
- }
144
- };
145
-
146
- const logout = () => {
147
- console.group("Logout Process");
148
- setLoading(true);
149
- try {
150
- console.log("Clearing auth data...");
151
- localStorage.removeItem(HF_CONFIG.STORAGE_KEY);
152
- localStorage.removeItem("auth_return_to");
153
- setIsAuthenticated(false);
154
- setUser(null);
155
- console.log("Logged out successfully");
156
- } catch (err) {
157
- console.error("Logout error:", err);
158
- setError(err.message);
159
- } finally {
160
- setLoading(false);
161
- console.groupEnd();
162
- }
163
- };
164
-
165
- return {
166
- isAuthenticated,
167
- user,
168
- loading,
169
- error,
170
- login,
171
- logout,
172
- };
173
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
frontend/src/pages/AddModelPage/AddModelPage.js DELETED
@@ -1,51 +0,0 @@
1
- import React from "react";
2
- import { Box, CircularProgress } from "@mui/material";
3
- import { useAuth } from "../../hooks/useAuth";
4
- import PageHeader from "../../components/shared/PageHeader";
5
- import EvaluationQueues from "./components/EvaluationQueues/EvaluationQueues";
6
- import ModelSubmissionForm from "./components/ModelSubmissionForm/ModelSubmissionForm";
7
- import SubmissionGuide from "./components/SubmissionGuide/SubmissionGuide";
8
- import SubmissionLimitChecker from "./components/SubmissionLimitChecker/SubmissionLimitChecker";
9
-
10
- function AddModelPage() {
11
- const { isAuthenticated, loading, user } = useAuth();
12
-
13
- if (loading) {
14
- return (
15
- <Box
16
- sx={{
17
- display: "flex",
18
- justifyContent: "center",
19
- alignItems: "center",
20
- height: "100vh",
21
- }}
22
- >
23
- <CircularProgress />
24
- </Box>
25
- );
26
- }
27
-
28
- return (
29
- <Box sx={{ width: "100%", maxWidth: 1200, margin: "0 auto", py: 4, px: 0 }}>
30
- <PageHeader
31
- title="Submit a Model for Evaluation"
32
- subtitle={
33
- <>
34
- Add <span style={{ fontWeight: 600 }}>your</span> model to the Open
35
- LLM Leaderboard
36
- </>
37
- }
38
- />
39
-
40
- <SubmissionGuide />
41
-
42
- <SubmissionLimitChecker user={user}>
43
- <ModelSubmissionForm user={user} isAuthenticated={isAuthenticated} />
44
- </SubmissionLimitChecker>
45
-
46
- <EvaluationQueues defaultExpanded={false} />
47
- </Box>
48
- );
49
- }
50
-
51
- export default AddModelPage;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
frontend/src/pages/AddModelPage/components/EvaluationQueues/EvaluationQueues.js DELETED
@@ -1,787 +0,0 @@
1
- import React, { useState, useEffect, useRef } from "react";
2
- import {
3
- Box,
4
- Typography,
5
- Table,
6
- TableBody,
7
- TableCell,
8
- TableContainer,
9
- TableHead,
10
- TableRow,
11
- Chip,
12
- Link,
13
- CircularProgress,
14
- Alert,
15
- Accordion,
16
- AccordionSummary,
17
- AccordionDetails,
18
- Stack,
19
- Tooltip,
20
- useTheme,
21
- useMediaQuery,
22
- } from "@mui/material";
23
- import AccessTimeIcon from "@mui/icons-material/AccessTime";
24
- import CheckCircleIcon from "@mui/icons-material/CheckCircle";
25
- import PendingIcon from "@mui/icons-material/Pending";
26
- import AutorenewIcon from "@mui/icons-material/Autorenew";
27
- import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
28
- import OpenInNewIcon from "@mui/icons-material/OpenInNew";
29
- import { useVirtualizer } from "@tanstack/react-virtual";
30
-
31
- // Function to format wait time
32
- const formatWaitTime = (waitTimeStr) => {
33
- const seconds = parseFloat(waitTimeStr.replace("s", ""));
34
-
35
- if (seconds < 60) {
36
- return "just now";
37
- }
38
-
39
- const minutes = Math.floor(seconds / 60);
40
- if (minutes < 60) {
41
- return `${minutes}m ago`;
42
- }
43
-
44
- const hours = Math.floor(minutes / 60);
45
- if (hours < 24) {
46
- return `${hours}h ago`;
47
- }
48
-
49
- const days = Math.floor(hours / 24);
50
- return `${days}d ago`;
51
- };
52
-
53
- // Column definitions with their properties
54
- const columns = [
55
- {
56
- id: "model",
57
- label: "Model",
58
- width: "35%",
59
- align: "left",
60
- },
61
- {
62
- id: "submitter",
63
- label: "Submitted by",
64
- width: "15%",
65
- align: "left",
66
- },
67
- {
68
- id: "wait_time",
69
- label: "Submitted",
70
- width: "12%",
71
- align: "center",
72
- },
73
- {
74
- id: "precision",
75
- label: "Precision",
76
- width: "13%",
77
- align: "center",
78
- },
79
- {
80
- id: "revision",
81
- label: "Revision",
82
- width: "12%",
83
- align: "center",
84
- },
85
- {
86
- id: "status",
87
- label: "Status",
88
- width: "13%",
89
- align: "center",
90
- },
91
- ];
92
-
93
- const StatusChip = ({ status }) => {
94
- const statusConfig = {
95
- finished: {
96
- icon: <CheckCircleIcon />,
97
- label: "Completed",
98
- color: "success",
99
- },
100
- evaluating: {
101
- icon: <AutorenewIcon />,
102
- label: "Evaluating",
103
- color: "warning",
104
- },
105
- pending: { icon: <PendingIcon />, label: "Pending", color: "info" },
106
- };
107
-
108
- const config = statusConfig[status] || statusConfig.pending;
109
-
110
- return (
111
- <Chip
112
- icon={config.icon}
113
- label={config.label}
114
- color={config.color}
115
- size="small"
116
- variant="outlined"
117
- />
118
- );
119
- };
120
-
121
- const ModelTable = ({ models, emptyMessage, status }) => {
122
- const parentRef = useRef(null);
123
- const rowVirtualizer = useVirtualizer({
124
- count: models.length,
125
- getScrollElement: () => parentRef.current,
126
- estimateSize: () => 53,
127
- overscan: 5,
128
- });
129
-
130
- if (models.length === 0) {
131
- return (
132
- <Typography variant="body2" color="text.secondary" sx={{ p: 2 }}>
133
- {emptyMessage}
134
- </Typography>
135
- );
136
- }
137
-
138
- return (
139
- <TableContainer
140
- ref={parentRef}
141
- sx={{
142
- maxHeight: 400,
143
- "&::-webkit-scrollbar": {
144
- width: 8,
145
- height: 8,
146
- },
147
- "&::-webkit-scrollbar-track": {
148
- backgroundColor: "action.hover",
149
- borderRadius: 4,
150
- },
151
- "&::-webkit-scrollbar-thumb": {
152
- backgroundColor: "action.selected",
153
- borderRadius: 4,
154
- "&:hover": {
155
- backgroundColor: "action.focus",
156
- },
157
- },
158
- }}
159
- >
160
- <Table size="small" stickyHeader sx={{ tableLayout: "fixed" }}>
161
- <colgroup>
162
- {columns.map((column) => (
163
- <col key={column.id} style={{ width: column.width }} />
164
- ))}
165
- </colgroup>
166
- <TableHead>
167
- <TableRow>
168
- {columns.map((column, index) => (
169
- <TableCell
170
- key={column.id}
171
- align={column.align}
172
- sx={{
173
- backgroundColor: "background.paper",
174
- fontWeight: 600,
175
- borderBottom: "2px solid",
176
- borderColor: "divider",
177
- borderRight:
178
- index < columns.length - 1 ? "1px solid" : "none",
179
- borderRightColor: "divider",
180
- whiteSpace: "nowrap",
181
- overflow: "hidden",
182
- textOverflow: "ellipsis",
183
- padding: "12px 16px",
184
- }}
185
- >
186
- {column.label}
187
- </TableCell>
188
- ))}
189
- </TableRow>
190
- </TableHead>
191
- <TableBody>
192
- <TableRow>
193
- <TableCell
194
- style={{
195
- height: `${rowVirtualizer.getTotalSize()}px`,
196
- padding: 0,
197
- position: "relative",
198
- width: "100%",
199
- height: `${rowVirtualizer.getTotalSize()}px`,
200
- }}
201
- colSpan={columns.length}
202
- >
203
- <>
204
- {rowVirtualizer.getVirtualItems().map((virtualRow) => {
205
- const model = models[virtualRow.index];
206
- const waitTime = formatWaitTime(model.wait_time);
207
-
208
- return (
209
- <TableRow
210
- key={virtualRow.index}
211
- style={{
212
- position: "absolute",
213
- top: 0,
214
- left: 0,
215
- width: "100%",
216
- height: `${virtualRow.size}px`,
217
- transform: `translateY(${virtualRow.start}px)`,
218
- backgroundColor: "background.paper",
219
- display: "flex",
220
- }}
221
- hover
222
- >
223
- <TableCell
224
- sx={{
225
- flex: `0 0 ${columns[0].width}`,
226
- padding: "12px 16px",
227
- overflow: "hidden",
228
- textOverflow: "ellipsis",
229
- whiteSpace: "nowrap",
230
- borderRight: "1px solid",
231
- borderRightColor: "divider",
232
- display: "flex",
233
- alignItems: "center",
234
- }}
235
- >
236
- <Link
237
- href={`https://huggingface.co/${model.name}`}
238
- target="_blank"
239
- rel="noopener noreferrer"
240
- sx={{
241
- textDecoration: "none",
242
- overflow: "hidden",
243
- textOverflow: "ellipsis",
244
- whiteSpace: "nowrap",
245
- display: "flex",
246
- alignItems: "center",
247
- gap: 0.5,
248
- "& .MuiSvgIcon-root": {
249
- fontSize: "1rem",
250
- opacity: 0.6,
251
- },
252
- }}
253
- >
254
- {model.name}
255
- <OpenInNewIcon />
256
- </Link>
257
- </TableCell>
258
- <TableCell
259
- sx={{
260
- flex: `0 0 ${columns[1].width}`,
261
- padding: "12px 16px",
262
- overflow: "hidden",
263
- textOverflow: "ellipsis",
264
- whiteSpace: "nowrap",
265
- borderRight: "1px solid",
266
- borderRightColor: "divider",
267
- display: "flex",
268
- alignItems: "center",
269
- }}
270
- >
271
- {model.submitter}
272
- </TableCell>
273
- <TableCell
274
- align={columns[2].align}
275
- sx={{
276
- flex: `0 0 ${columns[2].width}`,
277
- padding: "12px 16px",
278
- borderRight: "1px solid",
279
- borderRightColor: "divider",
280
- display: "flex",
281
- alignItems: "center",
282
- justifyContent: "center",
283
- }}
284
- >
285
- <Tooltip title={model.wait_time} arrow placement="top">
286
- <Typography
287
- variant="body2"
288
- color="text.secondary"
289
- sx={{
290
- display: "flex",
291
- alignItems: "center",
292
- justifyContent: "center",
293
- gap: 0.5,
294
- }}
295
- >
296
- <AccessTimeIcon sx={{ fontSize: "0.9rem" }} />
297
- {waitTime}
298
- </Typography>
299
- </Tooltip>
300
- </TableCell>
301
- <TableCell
302
- align={columns[3].align}
303
- sx={{
304
- flex: `0 0 ${columns[3].width}`,
305
- padding: "12px 16px",
306
- borderRight: "1px solid",
307
- borderRightColor: "divider",
308
- display: "flex",
309
- alignItems: "center",
310
- justifyContent: "center",
311
- }}
312
- >
313
- <Typography variant="body2" color="text.secondary">
314
- {model.precision}
315
- </Typography>
316
- </TableCell>
317
- <TableCell
318
- align={columns[4].align}
319
- sx={{
320
- flex: `0 0 ${columns[4].width}`,
321
- padding: "12px 16px",
322
- fontFamily: "monospace",
323
- borderRight: "1px solid",
324
- borderRightColor: "divider",
325
- display: "flex",
326
- alignItems: "center",
327
- justifyContent: "center",
328
- }}
329
- >
330
- {model.revision.substring(0, 7)}
331
- </TableCell>
332
- <TableCell
333
- align={columns[5].align}
334
- sx={{
335
- flex: `0 0 ${columns[5].width}`,
336
- padding: "12px 16px",
337
- display: "flex",
338
- alignItems: "center",
339
- justifyContent: "center",
340
- }}
341
- >
342
- <StatusChip status={status} />
343
- </TableCell>
344
- </TableRow>
345
- );
346
- })}
347
- </>
348
- </TableCell>
349
- </TableRow>
350
- </TableBody>
351
- </Table>
352
- </TableContainer>
353
- );
354
- };
355
-
356
- const QueueAccordion = ({
357
- title,
358
- models,
359
- status,
360
- emptyMessage,
361
- expanded,
362
- onChange,
363
- loading,
364
- }) => {
365
- const theme = useTheme();
366
- const isMobile = useMediaQuery(theme.breakpoints.down("sm"));
367
-
368
- return (
369
- <Accordion
370
- expanded={expanded}
371
- onChange={onChange}
372
- disabled={loading}
373
- sx={{
374
- "&:before": { display: "none" },
375
- boxShadow: "none",
376
- border: "none",
377
- }}
378
- >
379
- <AccordionSummary
380
- expandIcon={<ExpandMoreIcon />}
381
- sx={{
382
- px: { xs: 2, sm: 3 },
383
- py: { xs: 1.5, sm: 2 },
384
- alignItems: { xs: "flex-start", sm: "center" },
385
- "& .MuiAccordionSummary-expandIconWrapper": {
386
- marginTop: { xs: "4px", sm: 0 },
387
- },
388
- }}
389
- >
390
- <Stack
391
- direction={{ xs: "column", sm: "row" }}
392
- spacing={{ xs: 1, sm: 2 }}
393
- alignItems={{ xs: "flex-start", sm: "center" }}
394
- sx={{ width: "100%" }}
395
- >
396
- <Typography
397
- sx={{
398
- fontSize: { xs: "0.95rem", sm: "1rem" },
399
- fontWeight: 500,
400
- }}
401
- >
402
- {title}
403
- </Typography>
404
- <Stack
405
- direction={{ xs: "column", sm: "row" }}
406
- spacing={1}
407
- alignItems={{ xs: "stretch", sm: "center" }}
408
- sx={{
409
- ml: { xs: 0, sm: "auto" },
410
- width: { xs: "100%", sm: "auto" },
411
- }}
412
- >
413
- <Chip
414
- label={models.length}
415
- size={isMobile ? "small" : "medium"}
416
- color={
417
- status === "finished"
418
- ? "success"
419
- : status === "evaluating"
420
- ? "warning"
421
- : "info"
422
- }
423
- variant="outlined"
424
- sx={(theme) => ({
425
- borderWidth: 2,
426
- fontWeight: 600,
427
- fontSize: { xs: "0.75rem", sm: "0.875rem" },
428
- height: { xs: "24px", sm: "32px" },
429
- width: { xs: "100%", sm: "auto" },
430
- bgcolor:
431
- status === "finished"
432
- ? theme.palette.success[100]
433
- : status === "evaluating"
434
- ? theme.palette.warning[100]
435
- : theme.palette.info[100],
436
- borderColor:
437
- status === "finished"
438
- ? theme.palette.success[400]
439
- : status === "evaluating"
440
- ? theme.palette.warning[400]
441
- : theme.palette.info[400],
442
- color:
443
- status === "finished"
444
- ? theme.palette.success[700]
445
- : status === "evaluating"
446
- ? theme.palette.warning[700]
447
- : theme.palette.info[700],
448
- "& .MuiChip-label": {
449
- px: { xs: 1, sm: 1.2 },
450
- width: "100%",
451
- },
452
- "&:hover": {
453
- bgcolor:
454
- status === "finished"
455
- ? theme.palette.success[200]
456
- : status === "evaluating"
457
- ? theme.palette.warning[200]
458
- : theme.palette.info[200],
459
- },
460
- })}
461
- />
462
- {loading && (
463
- <CircularProgress
464
- size={isMobile ? 14 : 16}
465
- color="inherit"
466
- sx={{ opacity: 0.5 }}
467
- />
468
- )}
469
- </Stack>
470
- </Stack>
471
- </AccordionSummary>
472
- <AccordionDetails sx={{ p: { xs: 1, sm: 2 } }}>
473
- <Box
474
- sx={{
475
- border: "1px solid",
476
- borderColor: "grey.200",
477
- borderRadius: 1,
478
- overflow: "hidden",
479
- }}
480
- >
481
- <ModelTable
482
- models={models}
483
- emptyMessage={emptyMessage}
484
- status={status}
485
- />
486
- </Box>
487
- </AccordionDetails>
488
- </Accordion>
489
- );
490
- };
491
-
492
- const EvaluationQueues = ({ defaultExpanded = true }) => {
493
- const [expanded, setExpanded] = useState(defaultExpanded);
494
- const [expandedQueues, setExpandedQueues] = useState(new Set());
495
- const [models, setModels] = useState({
496
- pending: [],
497
- evaluating: [],
498
- finished: [],
499
- });
500
- const [loading, setLoading] = useState(true);
501
- const [error, setError] = useState(null);
502
- const theme = useTheme();
503
- const isMobile = useMediaQuery(theme.breakpoints.down("sm"));
504
-
505
- useEffect(() => {
506
- const fetchModels = async () => {
507
- try {
508
- const response = await fetch("/api/models/status");
509
- if (!response.ok) {
510
- throw new Error("Failed to fetch models");
511
- }
512
- const data = await response.json();
513
-
514
- // Sort models by submission date (most recent first)
515
- const sortByDate = (models) => {
516
- return [...models].sort((a, b) => {
517
- const dateA = new Date(a.submission_time);
518
- const dateB = new Date(b.submission_time);
519
- return dateB - dateA;
520
- });
521
- };
522
-
523
- setModels({
524
- finished: sortByDate(data.finished),
525
- evaluating: sortByDate(data.evaluating),
526
- pending: sortByDate(data.pending),
527
- });
528
- } catch (err) {
529
- setError(err.message);
530
- } finally {
531
- setLoading(false);
532
- }
533
- };
534
-
535
- fetchModels();
536
- const interval = setInterval(fetchModels, 30000);
537
- return () => clearInterval(interval);
538
- }, []);
539
-
540
- const handleMainAccordionChange = (panel) => (event, isExpanded) => {
541
- setExpanded(isExpanded ? panel : false);
542
- };
543
-
544
- const handleQueueAccordionChange = (queueName) => (event, isExpanded) => {
545
- setExpandedQueues((prev) => {
546
- const newSet = new Set(prev);
547
- if (isExpanded) {
548
- newSet.add(queueName);
549
- } else {
550
- newSet.delete(queueName);
551
- }
552
- return newSet;
553
- });
554
- };
555
-
556
- if (error) {
557
- return (
558
- <Alert severity="error" sx={{ mb: 2 }}>
559
- {error}
560
- </Alert>
561
- );
562
- }
563
-
564
- return (
565
- <Accordion
566
- expanded={expanded === "main"}
567
- onChange={handleMainAccordionChange("main")}
568
- disabled={loading}
569
- elevation={0}
570
- sx={{
571
- mb: 3,
572
- boxShadow: "none",
573
- border: "1px solid",
574
- borderColor: "divider",
575
- borderRadius: "8px !important",
576
- "&:before": {
577
- display: "none",
578
- },
579
- "&.Mui-disabled": {
580
- backgroundColor: "rgba(0, 0, 0, 0.03)",
581
- opacity: 0.9,
582
- },
583
- "& .MuiAccordionSummary-root": {
584
- minHeight: { xs: 56, sm: 64 },
585
- bgcolor: "background.paper",
586
- borderRadius: "8px",
587
- alignItems: { xs: "flex-start", sm: "center" },
588
- "&.Mui-expanded": {
589
- minHeight: { xs: 56, sm: 64 },
590
- borderRadius: "8px 8px 0 0",
591
- },
592
- },
593
- "& .MuiAccordionSummary-content": {
594
- m: 0,
595
- "&.Mui-expanded": {
596
- m: 0,
597
- },
598
- },
599
- }}
600
- >
601
- <AccordionSummary
602
- expandIcon={<ExpandMoreIcon />}
603
- sx={{
604
- px: { xs: 2, sm: 3 },
605
- "& .MuiAccordionSummary-expandIconWrapper": {
606
- color: "text.secondary",
607
- transform: "rotate(0deg)",
608
- transition: "transform 150ms",
609
- marginTop: { xs: "4px", sm: 0 },
610
- "&.Mui-expanded": {
611
- transform: "rotate(180deg)",
612
- },
613
- },
614
- }}
615
- >
616
- <Stack
617
- direction={{ xs: "column", sm: "row" }}
618
- spacing={{ xs: 1, sm: 2 }}
619
- alignItems={{ xs: "flex-start", sm: "center" }}
620
- sx={{ width: "100%" }}
621
- >
622
- <Typography
623
- variant="h6"
624
- sx={{
625
- fontWeight: 600,
626
- color: "text.primary",
627
- letterSpacing: "-0.01em",
628
- fontSize: { xs: "1.1rem", sm: "1.25rem" },
629
- }}
630
- >
631
- Evaluation Status
632
- </Typography>
633
- {!loading && (
634
- <Stack
635
- direction={{ xs: "column", sm: "row" }}
636
- spacing={1}
637
- sx={{
638
- transition: "opacity 0.2s",
639
- ".Mui-expanded &": {
640
- opacity: 0,
641
- height: 0,
642
- m: 0,
643
- overflow: "hidden",
644
- },
645
- width: { xs: "100%", sm: "auto" },
646
- alignItems: { xs: "stretch", sm: "center" },
647
- }}
648
- >
649
- <Chip
650
- label={`${models.pending.length} In Queue`}
651
- size={isMobile ? "small" : "medium"}
652
- color="info"
653
- variant="outlined"
654
- sx={{
655
- borderWidth: 2,
656
- fontWeight: 600,
657
- fontSize: { xs: "0.75rem", sm: "0.875rem" },
658
- height: { xs: "24px", sm: "32px" },
659
- bgcolor: "info.100",
660
- borderColor: "info.400",
661
- color: "info.700",
662
- width: { xs: "100%", sm: "auto" },
663
- "& .MuiChip-label": {
664
- px: { xs: 1, sm: 1.2 },
665
- width: "100%",
666
- display: "flex",
667
- justifyContent: "center",
668
- },
669
- "&:hover": {
670
- bgcolor: "info.200",
671
- },
672
- }}
673
- />
674
- <Chip
675
- label={`${models.evaluating.length} Evaluating`}
676
- size={isMobile ? "small" : "medium"}
677
- color="warning"
678
- variant="outlined"
679
- sx={{
680
- borderWidth: 2,
681
- fontWeight: 600,
682
- fontSize: { xs: "0.75rem", sm: "0.875rem" },
683
- height: { xs: "24px", sm: "32px" },
684
- bgcolor: "warning.100",
685
- borderColor: "warning.400",
686
- color: "warning.700",
687
- width: { xs: "100%", sm: "auto" },
688
- "& .MuiChip-label": {
689
- px: { xs: 1, sm: 1.2 },
690
- width: "100%",
691
- display: "flex",
692
- justifyContent: "center",
693
- },
694
- "&:hover": {
695
- bgcolor: "warning.200",
696
- },
697
- }}
698
- />
699
- <Chip
700
- label={`${models.finished.length} Evaluated`}
701
- size={isMobile ? "small" : "medium"}
702
- color="success"
703
- variant="outlined"
704
- sx={{
705
- borderWidth: 2,
706
- fontWeight: 600,
707
- fontSize: { xs: "0.75rem", sm: "0.875rem" },
708
- height: { xs: "24px", sm: "32px" },
709
- bgcolor: "success.100",
710
- borderColor: "success.400",
711
- color: "success.700",
712
- width: { xs: "100%", sm: "auto" },
713
- "& .MuiChip-label": {
714
- px: { xs: 1, sm: 1.2 },
715
- width: "100%",
716
- display: "flex",
717
- justifyContent: "center",
718
- },
719
- "&:hover": {
720
- bgcolor: "success.200",
721
- },
722
- }}
723
- />
724
- </Stack>
725
- )}
726
- {loading && (
727
- <CircularProgress
728
- size={isMobile ? 18 : 20}
729
- sx={{
730
- color: "primary.main",
731
- }}
732
- />
733
- )}
734
- </Stack>
735
- </AccordionSummary>
736
- <AccordionDetails sx={{ p: 0 }}>
737
- {loading ? (
738
- <Box
739
- sx={{
740
- display: "flex",
741
- justifyContent: "center",
742
- alignItems: "center",
743
- minHeight: 200,
744
- width: "100%",
745
- }}
746
- >
747
- <CircularProgress />
748
- </Box>
749
- ) : (
750
- <>
751
- <QueueAccordion
752
- title="Models in queue"
753
- models={models.pending}
754
- status="pending"
755
- emptyMessage="No models in queue"
756
- expanded={expandedQueues.has("pending")}
757
- onChange={handleQueueAccordionChange("pending")}
758
- loading={loading}
759
- />
760
-
761
- <QueueAccordion
762
- title="Models being evaluated"
763
- models={models.evaluating}
764
- status="evaluating"
765
- emptyMessage="No models currently being evaluated"
766
- expanded={expandedQueues.has("evaluating")}
767
- onChange={handleQueueAccordionChange("evaluating")}
768
- loading={loading}
769
- />
770
-
771
- <QueueAccordion
772
- title="Recently evaluated models"
773
- models={models.finished}
774
- status="finished"
775
- emptyMessage="No models have been evaluated recently"
776
- expanded={expandedQueues.has("finished")}
777
- onChange={handleQueueAccordionChange("finished")}
778
- loading={loading}
779
- />
780
- </>
781
- )}
782
- </AccordionDetails>
783
- </Accordion>
784
- );
785
- };
786
-
787
- export default EvaluationQueues;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
frontend/src/pages/AddModelPage/components/ModelSubmissionForm/ModelSubmissionForm.js DELETED
@@ -1,599 +0,0 @@
1
- import React, { useState } from "react";
2
- import {
3
- Box,
4
- Paper,
5
- Typography,
6
- TextField,
7
- Button,
8
- FormControl,
9
- InputLabel,
10
- Select,
11
- MenuItem,
12
- FormControlLabel,
13
- Switch,
14
- Stack,
15
- Grid,
16
- CircularProgress,
17
- Alert,
18
- } from "@mui/material";
19
- import RocketLaunchIcon from "@mui/icons-material/RocketLaunch";
20
- import CheckCircleOutlineIcon from "@mui/icons-material/CheckCircleOutline";
21
- import { alpha } from "@mui/material/styles";
22
- import InfoIconWithTooltip from "../../../../components/shared/InfoIconWithTooltip";
23
- import { MODEL_TYPES } from "../../../../pages/LeaderboardPage/components/Leaderboard/constants/modelTypes";
24
- import { SUBMISSION_PRECISIONS } from "../../../../pages/LeaderboardPage/components/Leaderboard/constants/defaults";
25
- import AuthContainer from "../../../../components/shared/AuthContainer";
26
-
27
- const WEIGHT_TYPES = [
28
- { value: "Original", label: "Original" },
29
- { value: "Delta", label: "Delta" },
30
- { value: "Adapter", label: "Adapter" },
31
- ];
32
-
33
- const HELP_TEXTS = {
34
- modelName: (
35
- <Box sx={{ p: 1 }}>
36
- <Typography variant="subtitle2" sx={{ fontWeight: 600, mb: 0.5 }}>
37
- Model Name on Hugging Face Hub
38
- </Typography>
39
- <Typography variant="body2" sx={{ opacity: 0.9, lineHeight: 1.4 }}>
40
- Your model must be public and loadable with AutoClasses without
41
- trust_remote_code. The model should be in Safetensors format for better
42
- safety and loading performance. Example: mistralai/Mistral-7B-v0.1
43
- </Typography>
44
- </Box>
45
- ),
46
- revision: (
47
- <Box sx={{ p: 1 }}>
48
- <Typography variant="subtitle2" sx={{ fontWeight: 600, mb: 0.5 }}>
49
- Model Revision
50
- </Typography>
51
- <Typography variant="body2" sx={{ opacity: 0.9, lineHeight: 1.4 }}>
52
- Git branch, tag or commit hash. The evaluation will be strictly tied to
53
- this specific commit to ensure consistency. Make sure this version is
54
- stable and contains all necessary files.
55
- </Typography>
56
- </Box>
57
- ),
58
- modelType: (
59
- <Box sx={{ p: 1 }}>
60
- <Typography variant="subtitle2" sx={{ fontWeight: 600, mb: 0.5 }}>
61
- Model Category
62
- </Typography>
63
- <Typography variant="body2" sx={{ opacity: 0.9, lineHeight: 1.4 }}>
64
- 🟢 Pretrained: Base models trained on text using masked modeling 🟩
65
- Continuously Pretrained: Extended training on additional corpus 🔶
66
- Fine-tuned: Domain-specific optimization 💬 Chat: Models using RLHF,
67
- DPO, or IFT for conversation 🤝 Merge: Combined weights without
68
- additional training 🌸 Multimodal: Handles multiple input types
69
- </Typography>
70
- </Box>
71
- ),
72
- baseModel: (
73
- <Box sx={{ p: 1 }}>
74
- <Typography variant="subtitle2" sx={{ fontWeight: 600, mb: 0.5 }}>
75
- Base Model Reference
76
- </Typography>
77
- <Typography variant="body2" sx={{ opacity: 0.9, lineHeight: 1.4 }}>
78
- Required for delta weights or adapters. This information is used to
79
- identify the original model and calculate the total parameter count by
80
- combining base model and adapter/delta parameters.
81
- </Typography>
82
- </Box>
83
- ),
84
- precision: (
85
- <Box sx={{ p: 1 }}>
86
- <Typography variant="subtitle2" sx={{ fontWeight: 600, mb: 0.5 }}>
87
- Model Precision
88
- </Typography>
89
- <Typography variant="body2" sx={{ opacity: 0.9, lineHeight: 1.4 }}>
90
- Size limits vary by precision: • FP16/BF16: up to 100B parameters •
91
- 8-bit: up to 280B parameters (2x) • 4-bit: up to 560B parameters (4x)
92
- Choose carefully as incorrect precision can cause evaluation errors.
93
- </Typography>
94
- </Box>
95
- ),
96
- weightsType: (
97
- <Box sx={{ p: 1 }}>
98
- <Typography variant="subtitle2" sx={{ fontWeight: 600, mb: 0.5 }}>
99
- Weights Format
100
- </Typography>
101
- <Typography variant="body2" sx={{ opacity: 0.9, lineHeight: 1.4 }}>
102
- Original: Complete model weights in safetensors format Delta: Weight
103
- differences from base model (requires base model for size calculation)
104
- Adapter: Lightweight fine-tuning layers (requires base model for size
105
- calculation)
106
- </Typography>
107
- </Box>
108
- ),
109
- chatTemplate: (
110
- <Box sx={{ p: 1 }}>
111
- <Typography variant="subtitle2" sx={{ fontWeight: 600, mb: 0.5 }}>
112
- Chat Template Support
113
- </Typography>
114
- <Typography variant="body2" sx={{ opacity: 0.9, lineHeight: 1.4 }}>
115
- Activates automatically for chat models. It uses the standardized
116
- Hugging Face chat template for consistent prompt formatting during
117
- evaluation. Required for models using RLHF, DPO, or instruction
118
- fine-tuning.
119
- </Typography>
120
- </Box>
121
- ),
122
- };
123
-
124
- // Convert MODEL_TYPES to format expected by Select component
125
- const modelTypeOptions = Object.entries(MODEL_TYPES).map(
126
- ([value, { icon, label }]) => ({
127
- value,
128
- label: `${icon} ${label}`,
129
- })
130
- );
131
-
132
- function ModelSubmissionForm({ user, isAuthenticated }) {
133
- const [formData, setFormData] = useState({
134
- modelName: "",
135
- revision: "main",
136
- modelType: "fine-tuned",
137
- isChatModel: false,
138
- useChatTemplate: false,
139
- precision: "float16",
140
- weightsType: "Original",
141
- baseModel: "",
142
- });
143
- const [error, setError] = useState(null);
144
- const [submitting, setSubmitting] = useState(false);
145
- const [success, setSuccess] = useState(false);
146
- const [submittedData, setSubmittedData] = useState(null);
147
-
148
- const handleChange = (event) => {
149
- const { name, value, checked } = event.target;
150
- setFormData((prev) => ({
151
- ...prev,
152
- [name]: event.target.type === "checkbox" ? checked : value,
153
- }));
154
- };
155
-
156
- const handleSubmit = async (e) => {
157
- e.preventDefault();
158
- setError(null);
159
- setSubmitting(true);
160
-
161
- try {
162
- const response = await fetch("/api/models/submit", {
163
- method: "POST",
164
- headers: {
165
- "Content-Type": "application/json",
166
- },
167
- body: JSON.stringify({
168
- model_id: formData.modelName,
169
- revision: formData.revision,
170
- model_type: formData.modelType,
171
- precision: formData.precision,
172
- weight_type: formData.weightsType,
173
- base_model: formData.baseModel,
174
- use_chat_template: formData.useChatTemplate,
175
- user_id: user.username,
176
- }),
177
- });
178
-
179
- if (!response.ok) {
180
- const error = await response.json();
181
- throw new Error(error.detail || "Failed to submit model");
182
- }
183
-
184
- setSubmittedData(formData);
185
- setSuccess(true);
186
- } catch (error) {
187
- setError(error.message);
188
- } finally {
189
- setSubmitting(false);
190
- }
191
- };
192
-
193
- if (success && submittedData) {
194
- return (
195
- <Paper
196
- variant="outlined"
197
- sx={(theme) => ({
198
- p: 6,
199
- mb: 3,
200
- bgcolor: alpha(theme.palette.success.main, 0.05),
201
- borderColor: alpha(theme.palette.success.main, 0.2),
202
- })}
203
- >
204
- <Stack spacing={3}>
205
- <Stack direction="row" spacing={2} alignItems="center">
206
- <CheckCircleOutlineIcon color="success" sx={{ fontSize: 28 }} />
207
- <Typography
208
- variant="h5"
209
- sx={{ fontWeight: 600, color: "success.800" }}
210
- >
211
- Model submitted successfully!
212
- </Typography>
213
- </Stack>
214
-
215
- <Typography variant="body1">
216
- Your model <strong>{submittedData.modelName}</strong> has been added
217
- to the evaluation queue with the following parameters:
218
- </Typography>
219
-
220
- <Paper
221
- variant="outlined"
222
- sx={{
223
- p: 2,
224
- borderColor: "divider",
225
- }}
226
- >
227
- <Stack spacing={1.5}>
228
- <Stack direction="row" spacing={2}>
229
- <Typography
230
- variant="body2"
231
- color="text.secondary"
232
- sx={{ width: 120 }}
233
- >
234
- Model:
235
- </Typography>
236
- <Typography variant="body2" sx={{ fontFamily: "monospace" }}>
237
- {submittedData.modelName}
238
- </Typography>
239
- </Stack>
240
- <Stack direction="row" spacing={2}>
241
- <Typography
242
- variant="body2"
243
- color="text.secondary"
244
- sx={{ width: 120 }}
245
- >
246
- Type:
247
- </Typography>
248
- <Typography variant="body2">
249
- {submittedData.modelType}
250
- </Typography>
251
- </Stack>
252
- <Stack direction="row" spacing={2}>
253
- <Typography
254
- variant="body2"
255
- color="text.secondary"
256
- sx={{ width: 120 }}
257
- >
258
- Revision:
259
- </Typography>
260
- <Typography variant="body2" sx={{ fontFamily: "monospace" }}>
261
- {submittedData.revision}
262
- </Typography>
263
- </Stack>
264
- <Stack direction="row" spacing={2}>
265
- <Typography
266
- variant="body2"
267
- color="text.secondary"
268
- sx={{ width: 120 }}
269
- >
270
- Precision:
271
- </Typography>
272
- <Typography variant="body2">
273
- {submittedData.precision}
274
- </Typography>
275
- </Stack>
276
- <Stack direction="row" spacing={2}>
277
- <Typography
278
- variant="body2"
279
- color="text.secondary"
280
- sx={{ width: 120 }}
281
- >
282
- Weight type:
283
- </Typography>
284
- <Typography variant="body2">
285
- {submittedData.weightsType}
286
- </Typography>
287
- </Stack>
288
- {submittedData.baseModel && (
289
- <Stack direction="row" spacing={2}>
290
- <Typography
291
- variant="body2"
292
- color="text.secondary"
293
- sx={{ width: 120 }}
294
- >
295
- Base model:
296
- </Typography>
297
- <Typography variant="body2">
298
- {submittedData.baseModel}
299
- </Typography>
300
- </Stack>
301
- )}
302
- <Stack direction="row" spacing={2}>
303
- <Typography
304
- variant="body2"
305
- color="text.secondary"
306
- sx={{ width: 120 }}
307
- >
308
- Chat template:
309
- </Typography>
310
- <Typography variant="body2">
311
- {submittedData.useChatTemplate ? "Yes" : "No"}
312
- </Typography>
313
- </Stack>
314
- </Stack>
315
- </Paper>
316
-
317
- <Typography variant="body2" color="text.secondary">
318
- An automatic upvote has been added to your model to help with
319
- prioritization.
320
- </Typography>
321
-
322
- <Stack direction="row" spacing={2}>
323
- <Button
324
- variant="outlined"
325
- size="large"
326
- onClick={() => {
327
- setSuccess(false);
328
- setSubmittedData(null);
329
- setFormData({
330
- modelName: "",
331
- revision: "main",
332
- modelType: "fine-tuned",
333
- isChatModel: false,
334
- useChatTemplate: false,
335
- precision: "float16",
336
- weightsType: "Original",
337
- baseModel: "",
338
- });
339
- }}
340
- >
341
- Submit another model
342
- </Button>
343
- </Stack>
344
- </Stack>
345
- </Paper>
346
- );
347
- }
348
-
349
- return (
350
- <>
351
- {error && (
352
- <Alert severity="error" sx={{ mb: 2 }}>
353
- {error}
354
- </Alert>
355
- )}
356
- <AuthContainer actionText="submit a model" />
357
- {isAuthenticated && (
358
- <Paper
359
- elevation={0}
360
- component="form"
361
- onSubmit={handleSubmit}
362
- sx={{
363
- p: 0,
364
- border: "1px solid",
365
- borderColor: "grey.300",
366
- mb: 3,
367
- overflow: "hidden",
368
- }}
369
- >
370
- {/* Header */}
371
- <Box
372
- sx={{
373
- px: 3,
374
- py: 2,
375
- borderBottom: "1px solid",
376
- borderColor: (theme) =>
377
- theme.palette.mode === "dark"
378
- ? alpha(theme.palette.divider, 0.1)
379
- : "grey.200",
380
- bgcolor: (theme) =>
381
- theme.palette.mode === "dark"
382
- ? alpha(theme.palette.background.paper, 0.5)
383
- : "grey.50",
384
- }}
385
- >
386
- <Typography
387
- variant="h6"
388
- sx={{ fontWeight: 600, color: "text.primary" }}
389
- >
390
- Model Submission Form
391
- </Typography>
392
- </Box>
393
-
394
- {/* Form Content */}
395
- <Box sx={{ p: 3 }}>
396
- <Grid container spacing={3}>
397
- {/* Model Information */}
398
- <Grid item xs={12}>
399
- <Stack direction="row" spacing={1} alignItems="center">
400
- <Typography variant="h6">Model Information</Typography>
401
- <InfoIconWithTooltip tooltip={HELP_TEXTS.modelName} />
402
- </Stack>
403
- </Grid>
404
-
405
- <Grid item xs={12} sm={8}>
406
- <TextField
407
- required
408
- fullWidth
409
- name="modelName"
410
- label="Model Name"
411
- placeholder="organization/model-name"
412
- value={formData.modelName}
413
- onChange={handleChange}
414
- helperText="Example: meta-llama/Llama-3.2-1B"
415
- InputProps={{
416
- endAdornment: (
417
- <InfoIconWithTooltip tooltip={HELP_TEXTS.modelName} />
418
- ),
419
- }}
420
- />
421
- </Grid>
422
-
423
- <Grid item xs={12} sm={4}>
424
- <TextField
425
- fullWidth
426
- name="revision"
427
- label="Revision commit"
428
- value={formData.revision}
429
- onChange={handleChange}
430
- helperText="Default: main"
431
- InputProps={{
432
- endAdornment: (
433
- <InfoIconWithTooltip tooltip={HELP_TEXTS.revision} />
434
- ),
435
- }}
436
- />
437
- </Grid>
438
-
439
- {/* Model Configuration */}
440
- <Grid item xs={12}>
441
- <Stack direction="row" spacing={1} alignItems="center">
442
- <Typography variant="h6">Model Configuration</Typography>
443
- </Stack>
444
- </Grid>
445
-
446
- <Grid item xs={12} sm={6}>
447
- <FormControl fullWidth>
448
- <InputLabel>Model Type</InputLabel>
449
- <Select
450
- name="modelType"
451
- value={formData.modelType}
452
- onChange={handleChange}
453
- label="Model Type"
454
- endAdornment={
455
- <InfoIconWithTooltip
456
- tooltip={HELP_TEXTS.modelType}
457
- sx={{ mr: 2 }}
458
- />
459
- }
460
- >
461
- {modelTypeOptions.map((type) => (
462
- <MenuItem key={type.value} value={type.value}>
463
- {type.label}
464
- </MenuItem>
465
- ))}
466
- </Select>
467
- </FormControl>
468
- </Grid>
469
-
470
- <Grid item xs={12} sm={6}>
471
- <Stack
472
- direction="row"
473
- spacing={2}
474
- alignItems="center"
475
- sx={{ height: "100%" }}
476
- >
477
- <FormControlLabel
478
- control={
479
- <Switch
480
- name="useChatTemplate"
481
- checked={formData.useChatTemplate}
482
- onChange={handleChange}
483
- />
484
- }
485
- label="Use Chat Template"
486
- />
487
- <InfoIconWithTooltip tooltip={HELP_TEXTS.chatTemplate} />
488
- </Stack>
489
- </Grid>
490
-
491
- <Grid item xs={12} sm={6}>
492
- <FormControl fullWidth>
493
- <InputLabel>Precision</InputLabel>
494
- <Select
495
- name="precision"
496
- value={formData.precision}
497
- onChange={handleChange}
498
- label="Precision"
499
- endAdornment={
500
- <InfoIconWithTooltip
501
- tooltip={HELP_TEXTS.precision}
502
- sx={{ mr: 2 }}
503
- />
504
- }
505
- >
506
- {SUBMISSION_PRECISIONS.map((option) => (
507
- <MenuItem key={option.value} value={option.value}>
508
- {option.label}
509
- </MenuItem>
510
- ))}
511
- </Select>
512
- </FormControl>
513
- </Grid>
514
-
515
- <Grid item xs={12} sm={6}>
516
- <FormControl fullWidth>
517
- <InputLabel>Weights Type</InputLabel>
518
- <Select
519
- name="weightsType"
520
- value={formData.weightsType}
521
- onChange={handleChange}
522
- label="Weights Type"
523
- endAdornment={
524
- <InfoIconWithTooltip
525
- tooltip={HELP_TEXTS.weightsType}
526
- sx={{ mr: 2 }}
527
- />
528
- }
529
- >
530
- {WEIGHT_TYPES.map((type) => (
531
- <MenuItem key={type.value} value={type.value}>
532
- {type.label}
533
- </MenuItem>
534
- ))}
535
- </Select>
536
- </FormControl>
537
- </Grid>
538
-
539
- {formData.weightsType !== "Original" && (
540
- <Grid item xs={12}>
541
- <TextField
542
- fullWidth
543
- required={
544
- formData.weightsType === "Delta" ||
545
- formData.weightsType === "Adapter"
546
- }
547
- name="baseModel"
548
- label="Base Model"
549
- value={formData.baseModel}
550
- onChange={handleChange}
551
- InputProps={{
552
- endAdornment: (
553
- <InfoIconWithTooltip tooltip={HELP_TEXTS.baseModel} />
554
- ),
555
- }}
556
- />
557
- </Grid>
558
- )}
559
-
560
- {/* Submit Button */}
561
- <Grid item xs={12}>
562
- <Box
563
- sx={{
564
- display: "flex",
565
- justifyContent: "space-between",
566
- alignItems: "center",
567
- mt: 2,
568
- }}
569
- >
570
- <Typography variant="body2" color="text.secondary">
571
- All fields marked with * are required
572
- </Typography>
573
- <Button
574
- type="submit"
575
- variant="contained"
576
- disabled={submitting}
577
- endIcon={submitting ? null : <RocketLaunchIcon />}
578
- sx={{
579
- minWidth: 120,
580
- position: "relative",
581
- }}
582
- >
583
- {submitting ? (
584
- <CircularProgress size={24} color="inherit" />
585
- ) : (
586
- "Submit"
587
- )}
588
- </Button>
589
- </Box>
590
- </Grid>
591
- </Grid>
592
- </Box>
593
- </Paper>
594
- )}
595
- </>
596
- );
597
- }
598
-
599
- export default ModelSubmissionForm;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
frontend/src/pages/AddModelPage/components/SubmissionGuide/SubmissionGuide.js DELETED
@@ -1,274 +0,0 @@
1
- import React, { useState, useEffect } from "react";
2
- import { useLocation, useNavigate } from "react-router-dom";
3
- import { Box, Paper, Typography, Button, Stack, Collapse } from "@mui/material";
4
- import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
5
-
6
- const DocLink = ({ href, children }) => (
7
- <Button
8
- variant="text"
9
- size="small"
10
- href={href}
11
- target="_blank"
12
- sx={{
13
- fontFamily: "monospace",
14
- textTransform: "none",
15
- color: "primary.main",
16
- fontSize: "0.875rem",
17
- p: 0,
18
- minWidth: "auto",
19
- justifyContent: "flex-start",
20
- "&:hover": {
21
- color: "primary.dark",
22
- backgroundColor: "transparent",
23
- textDecoration: "underline",
24
- },
25
- }}
26
- >
27
- {children} →
28
- </Button>
29
- );
30
-
31
- const StepNumber = ({ number }) => (
32
- <Box
33
- sx={{
34
- width: 32,
35
- height: 32,
36
- borderRadius: "50%",
37
- display: "flex",
38
- alignItems: "center",
39
- justifyContent: "center",
40
- border: "1px solid",
41
- borderColor: "primary.main",
42
- color: "primary.main",
43
- fontSize: "0.875rem",
44
- fontWeight: 600,
45
- flexShrink: 0,
46
- bgcolor: "transparent",
47
- }}
48
- >
49
- {number}
50
- </Box>
51
- );
52
-
53
- const TUTORIAL_STEPS = [
54
- {
55
- title: "Model Information",
56
- content: (
57
- <Stack spacing={2}>
58
- <Typography variant="body2" color="text.secondary">
59
- Your model should be <strong>public</strong> on the Hub and follow the{" "}
60
- <strong>username/model-id</strong> format (e.g.
61
- mistralai/Mistral-7B-v0.1). Specify the <strong>revision</strong>{" "}
62
- (commit hash or branch) and <strong>model type</strong>.
63
- </Typography>
64
- <DocLink href="https://huggingface.co/docs/hub/models-uploading">
65
- Model uploading guide
66
- </DocLink>
67
- </Stack>
68
- ),
69
- },
70
- {
71
- title: "Technical Details",
72
- content: (
73
- <Stack spacing={2}>
74
- <Typography variant="body2" color="text.secondary">
75
- Make sure your model can be <strong>loaded locally</strong> before
76
- submitting:
77
- </Typography>
78
- <Box
79
- sx={{
80
- p: 2,
81
- bgcolor: (theme) =>
82
- theme.palette.mode === "dark" ? "grey.50" : "grey.900",
83
- borderRadius: 1,
84
- "& pre": {
85
- m: 0,
86
- p: 0,
87
- fontFamily: "monospace",
88
- fontSize: "0.875rem",
89
- color: (theme) =>
90
- theme.palette.mode === "dark" ? "grey.900" : "grey.50",
91
- },
92
- }}
93
- >
94
- <pre>
95
- {`from transformers import AutoConfig, AutoModel, AutoTokenizer
96
-
97
- config = AutoConfig.from_pretrained("your-username/your-model", revision="main")
98
- model = AutoModel.from_pretrained("your-username/your-model", revision="main")
99
- tokenizer = AutoTokenizer.from_pretrained("your-username/your-model", revision="main")`}
100
- </pre>
101
- </Box>
102
- <DocLink href="https://huggingface.co/docs/transformers/installation">
103
- Transformers documentation
104
- </DocLink>
105
- </Stack>
106
- ),
107
- },
108
- {
109
- title: "License Requirements",
110
- content: (
111
- <Stack spacing={2}>
112
- <Typography variant="body2" color="text.secondary">
113
- A <strong>license tag</strong> is required.{" "}
114
- <strong>Open licenses</strong> (Apache, MIT, etc) are strongly
115
- recommended.
116
- </Typography>
117
- <DocLink href="https://huggingface.co/docs/hub/repositories-licenses">
118
- About model licenses
119
- </DocLink>
120
- </Stack>
121
- ),
122
- },
123
- {
124
- title: "Model Card Requirements",
125
- content: (
126
- <Stack spacing={2}>
127
- <Typography variant="body2" color="text.secondary">
128
- Your model card must include: <strong>architecture</strong>,{" "}
129
- <strong>training details</strong>,{" "}
130
- <strong>dataset information</strong>, intended use, limitations, and{" "}
131
- <strong>performance metrics</strong>.
132
- </Typography>
133
- <DocLink href="https://huggingface.co/docs/hub/model-cards">
134
- Model cards guide
135
- </DocLink>
136
- </Stack>
137
- ),
138
- },
139
- {
140
- title: "Final Checklist",
141
- content: (
142
- <Stack spacing={2}>
143
- <Typography variant="body2" color="text.secondary">
144
- Ensure your model is <strong>public</strong>, uses{" "}
145
- <strong>safetensors</strong> format, has a{" "}
146
- <strong>license tag</strong>, and <strong>loads correctly</strong>{" "}
147
- with the provided code.
148
- </Typography>
149
- <DocLink href="https://huggingface.co/docs/hub/repositories-getting-started">
150
- Sharing best practices
151
- </DocLink>
152
- </Stack>
153
- ),
154
- },
155
- ];
156
-
157
- function SubmissionGuide() {
158
- const location = useLocation();
159
- const navigate = useNavigate();
160
-
161
- // Initialize state directly with URL value
162
- const initialExpanded = !new URLSearchParams(location.search).get("guide");
163
- const [expanded, setExpanded] = useState(initialExpanded);
164
-
165
- // Sync expanded state with URL changes after initial render
166
- useEffect(() => {
167
- const guideOpen = !new URLSearchParams(location.search).get("guide");
168
- if (guideOpen !== expanded) {
169
- setExpanded(guideOpen);
170
- }
171
- }, [location.search, expanded]);
172
-
173
- const handleAccordionChange = () => {
174
- const newExpanded = !expanded;
175
- setExpanded(newExpanded);
176
- const params = new URLSearchParams(location.search);
177
- if (newExpanded) {
178
- params.delete("guide");
179
- } else {
180
- params.set("guide", "closed");
181
- }
182
- navigate({ search: params.toString() }, { replace: true });
183
- };
184
-
185
- return (
186
- <Paper
187
- elevation={0}
188
- sx={{
189
- mb: 3,
190
- borderRadius: "8px !important",
191
- border: "1px solid",
192
- borderColor: (theme) =>
193
- theme.palette.mode === "dark" ? "grey.800" : "grey.200",
194
- overflow: "hidden",
195
- }}
196
- >
197
- <Box
198
- onClick={handleAccordionChange}
199
- sx={{
200
- p: 2,
201
- display: "flex",
202
- alignItems: "center",
203
- justifyContent: "space-between",
204
- cursor: "pointer",
205
- bgcolor: (theme) =>
206
- theme.palette.mode === "dark" ? "grey.900" : "grey.50",
207
- borderBottom: "1px solid",
208
- borderColor: (theme) =>
209
- expanded
210
- ? theme.palette.mode === "dark"
211
- ? "grey.800"
212
- : "grey.200"
213
- : "transparent",
214
- }}
215
- >
216
- <Typography
217
- variant="h6"
218
- sx={{ fontWeight: 600, color: "text.primary" }}
219
- >
220
- Submission Guide
221
- </Typography>
222
- <ExpandMoreIcon
223
- sx={{
224
- transform: expanded ? "rotate(180deg)" : "rotate(0deg)",
225
- transition: "transform 0.3s",
226
- }}
227
- />
228
- </Box>
229
- <Collapse in={expanded} appear={false}>
230
- <Box sx={{ py: 4 }}>
231
- <Stack spacing={4}>
232
- {TUTORIAL_STEPS.map((step, index) => (
233
- <Box key={step.title}>
234
- <Stack spacing={3}>
235
- <Stack
236
- direction="row"
237
- spacing={2}
238
- alignItems="center"
239
- sx={{ px: 4 }}
240
- >
241
- <StepNumber number={index + 1} />
242
- <Typography
243
- variant="subtitle1"
244
- sx={{
245
- fontWeight: 600,
246
- color: "text.primary",
247
- letterSpacing: "-0.01em",
248
- }}
249
- >
250
- {step.title}
251
- </Typography>
252
- </Stack>
253
- <Box sx={{ px: 4, pl: 7 }}>{step.content}</Box>
254
- </Stack>
255
- {index < TUTORIAL_STEPS.length - 1 && (
256
- <Box
257
- sx={{
258
- mt: 4,
259
- borderTop: "1px solid",
260
- borderColor: (theme) =>
261
- theme.palette.mode === "dark" ? "grey.800" : "grey.100",
262
- }}
263
- />
264
- )}
265
- </Box>
266
- ))}
267
- </Stack>
268
- </Box>
269
- </Collapse>
270
- </Paper>
271
- );
272
- }
273
-
274
- export default SubmissionGuide;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
frontend/src/pages/AddModelPage/components/SubmissionLimitChecker/SubmissionLimitChecker.js DELETED
@@ -1,85 +0,0 @@
1
- import React, { useState, useEffect } from "react";
2
- import { Alert, Box, CircularProgress } from "@mui/material";
3
-
4
- const MAX_SUBMISSIONS_PER_WEEK = 10;
5
-
6
- function SubmissionLimitChecker({ user, children }) {
7
- const [loading, setLoading] = useState(true);
8
- const [reachedLimit, setReachedLimit] = useState(false);
9
- const [error, setError] = useState(false);
10
-
11
- useEffect(() => {
12
- const checkSubmissionLimit = async () => {
13
- if (!user?.username) {
14
- setLoading(false);
15
- return;
16
- }
17
-
18
- try {
19
- const response = await fetch(
20
- `/api/models/organization/${user.username}/submissions?days=7`
21
- );
22
- if (!response.ok) {
23
- throw new Error("Failed to fetch submission data");
24
- }
25
-
26
- const submissions = await response.json();
27
- console.log(`Recent submissions for ${user.username}:`, submissions);
28
- setReachedLimit(submissions.length >= MAX_SUBMISSIONS_PER_WEEK);
29
- setError(false);
30
- } catch (error) {
31
- console.error("Error checking submission limit:", error);
32
- setError(true);
33
- } finally {
34
- setLoading(false);
35
- }
36
- };
37
-
38
- checkSubmissionLimit();
39
- }, [user?.username]);
40
-
41
- if (loading) {
42
- return (
43
- <Box sx={{ display: "flex", justifyContent: "center", py: 4 }}>
44
- <CircularProgress />
45
- </Box>
46
- );
47
- }
48
-
49
- if (error) {
50
- return (
51
- <Alert
52
- severity="error"
53
- sx={{
54
- mb: 3,
55
- "& .MuiAlert-message": {
56
- fontSize: "1rem",
57
- },
58
- }}
59
- >
60
- Unable to verify submission limits. Please try again in a few minutes.
61
- </Alert>
62
- );
63
- }
64
-
65
- if (reachedLimit) {
66
- return (
67
- <Alert
68
- severity="warning"
69
- sx={{
70
- mb: 3,
71
- "& .MuiAlert-message": {
72
- fontSize: "1rem",
73
- },
74
- }}
75
- >
76
- For fairness reasons, you cannot submit more than{" "}
77
- {MAX_SUBMISSIONS_PER_WEEK} models per week. Please try again later.
78
- </Alert>
79
- );
80
- }
81
-
82
- return children;
83
- }
84
-
85
- export default SubmissionLimitChecker;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
frontend/src/pages/VoteModelPage/VoteModelPage.js DELETED
@@ -1,896 +0,0 @@
1
- import React, { useState, useEffect } from "react";
2
- import {
3
- Box,
4
- Typography,
5
- Paper,
6
- Button,
7
- Alert,
8
- List,
9
- ListItem,
10
- CircularProgress,
11
- Chip,
12
- Divider,
13
- IconButton,
14
- Stack,
15
- Link,
16
- useTheme,
17
- useMediaQuery,
18
- } from "@mui/material";
19
- import AccessTimeIcon from "@mui/icons-material/AccessTime";
20
- import PersonIcon from "@mui/icons-material/Person";
21
- import OpenInNewIcon from "@mui/icons-material/OpenInNew";
22
- import HowToVoteIcon from "@mui/icons-material/HowToVote";
23
- import { useAuth } from "../../hooks/useAuth";
24
- import PageHeader from "../../components/shared/PageHeader";
25
- import AuthContainer from "../../components/shared/AuthContainer";
26
- import { alpha } from "@mui/material/styles";
27
- import CheckIcon from "@mui/icons-material/Check";
28
-
29
- const NoModelsToVote = () => (
30
- <Box
31
- sx={{
32
- display: "flex",
33
- flexDirection: "column",
34
- alignItems: "center",
35
- justifyContent: "center",
36
- py: 8,
37
- textAlign: "center",
38
- }}
39
- >
40
- <HowToVoteIcon
41
- sx={{
42
- fontSize: 100,
43
- color: "grey.300",
44
- mb: 3,
45
- }}
46
- />
47
- <Typography
48
- variant="h4"
49
- component="h2"
50
- sx={{
51
- fontWeight: "bold",
52
- color: "grey.700",
53
- mb: 2,
54
- }}
55
- >
56
- No Models to Vote
57
- </Typography>
58
- <Typography
59
- variant="body1"
60
- sx={{
61
- color: "grey.600",
62
- maxWidth: 450,
63
- mx: "auto",
64
- }}
65
- >
66
- There are currently no models waiting for votes.
67
- <br />
68
- Check back later!
69
- </Typography>
70
- </Box>
71
- );
72
-
73
- const LOCAL_STORAGE_KEY = "pending_votes";
74
-
75
- function VoteModelPage() {
76
- const { isAuthenticated, user, loading: authLoading } = useAuth();
77
- const [pendingModels, setPendingModels] = useState([]);
78
- const [loadingModels, setLoadingModels] = useState(true);
79
- const [error, setError] = useState(null);
80
- const [userVotes, setUserVotes] = useState(new Set());
81
- const [loadingVotes, setLoadingVotes] = useState({});
82
- const [localVotes, setLocalVotes] = useState(new Set());
83
- const theme = useTheme();
84
- const isMobile = useMediaQuery(theme.breakpoints.down("sm"));
85
-
86
- // Create a unique identifier for a model
87
- const getModelUniqueId = (model) => {
88
- return `${model.name}_${model.precision}_${model.revision}`;
89
- };
90
-
91
- const formatWaitTime = (submissionTime) => {
92
- if (!submissionTime) return "N/A";
93
-
94
- const now = new Date();
95
- const submitted = new Date(submissionTime);
96
- const diffInHours = Math.floor((now - submitted) / (1000 * 60 * 60));
97
-
98
- // Less than 24 hours: show in hours
99
- if (diffInHours < 24) {
100
- return `${diffInHours}h`;
101
- }
102
-
103
- // Less than 7 days: show in days
104
- const diffInDays = Math.floor(diffInHours / 24);
105
- if (diffInDays < 7) {
106
- return `${diffInDays}d`;
107
- }
108
-
109
- // More than 7 days: show in weeks
110
- const diffInWeeks = Math.floor(diffInDays / 7);
111
- return `${diffInWeeks}w`;
112
- };
113
-
114
- const getConfigVotes = (votesData, model) => {
115
- // Créer l'identifiant unique du modèle
116
- const modelUniqueId = getModelUniqueId(model);
117
-
118
- // Compter les votes du serveur
119
- let serverVotes = 0;
120
- for (const [key, config] of Object.entries(votesData.votes_by_config)) {
121
- if (
122
- config.precision === model.precision &&
123
- config.revision === model.revision
124
- ) {
125
- serverVotes = config.count;
126
- break;
127
- }
128
- }
129
-
130
- // Ajouter les votes en attente du localStorage
131
- const pendingVote = localVotes.has(modelUniqueId) ? 1 : 0;
132
-
133
- return serverVotes + pendingVote;
134
- };
135
-
136
- const sortModels = (models) => {
137
- // Trier d'abord par nombre de votes décroissant, puis par soumission de l'utilisateur
138
- return [...models].sort((a, b) => {
139
- // Comparer d'abord le nombre de votes
140
- if (b.votes !== a.votes) {
141
- return b.votes - a.votes;
142
- }
143
-
144
- // Si l'utilisateur est connecté, mettre ses modèles en priorité
145
- if (user) {
146
- const aIsUserModel = a.submitter === user.username;
147
- const bIsUserModel = b.submitter === user.username;
148
-
149
- if (aIsUserModel && !bIsUserModel) return -1;
150
- if (!aIsUserModel && bIsUserModel) return 1;
151
- }
152
-
153
- // Si égalité, trier par date de soumission (le plus récent d'abord)
154
- return new Date(b.submission_time) - new Date(a.submission_time);
155
- });
156
- };
157
-
158
- // Add this function to handle localStorage
159
- const updateLocalVotes = (modelUniqueId, action = "add") => {
160
- const storedVotes = JSON.parse(
161
- localStorage.getItem(LOCAL_STORAGE_KEY) || "[]"
162
- );
163
- if (action === "add") {
164
- if (!storedVotes.includes(modelUniqueId)) {
165
- storedVotes.push(modelUniqueId);
166
- }
167
- } else {
168
- const index = storedVotes.indexOf(modelUniqueId);
169
- if (index > -1) {
170
- storedVotes.splice(index, 1);
171
- }
172
- }
173
- localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(storedVotes));
174
- setLocalVotes(new Set(storedVotes));
175
- };
176
-
177
- useEffect(() => {
178
- const fetchData = async () => {
179
- try {
180
- // Ne pas afficher le loading si on a déjà des données
181
- if (pendingModels.length === 0) {
182
- setLoadingModels(true);
183
- }
184
- setError(null);
185
-
186
- // Charger d'abord les votes en attente du localStorage
187
- const storedVotes = JSON.parse(
188
- localStorage.getItem(LOCAL_STORAGE_KEY) || "[]"
189
- );
190
- const localVotesSet = new Set(storedVotes);
191
-
192
- // Préparer toutes les requêtes en parallèle
193
- const [pendingModelsResponse, userVotesResponse] = await Promise.all([
194
- fetch("/api/models/pending"),
195
- isAuthenticated && user
196
- ? fetch(`/api/votes/user/${user.username}`)
197
- : Promise.resolve(null),
198
- ]);
199
-
200
- if (!pendingModelsResponse.ok) {
201
- throw new Error("Failed to fetch pending models");
202
- }
203
-
204
- const modelsData = await pendingModelsResponse.json();
205
- const votedModels = new Set();
206
-
207
- // Traiter les votes de l'utilisateur si connecté
208
- if (userVotesResponse && userVotesResponse.ok) {
209
- const votesData = await userVotesResponse.json();
210
- const userVotes = Array.isArray(votesData) ? votesData : [];
211
-
212
- userVotes.forEach((vote) => {
213
- const uniqueId = `${vote.model}_${vote.precision || "unknown"}_${
214
- vote.revision || "main"
215
- }`;
216
- votedModels.add(uniqueId);
217
- if (localVotesSet.has(uniqueId)) {
218
- localVotesSet.delete(uniqueId);
219
- updateLocalVotes(uniqueId, "remove");
220
- }
221
- });
222
- }
223
-
224
- // Préparer et exécuter toutes les requêtes de votes en une seule fois
225
- const modelVotesResponses = await Promise.all(
226
- modelsData.map((model) => {
227
- const [provider, modelName] = model.name.split("/");
228
- return fetch(`/api/votes/model/${provider}/${modelName}`)
229
- .then((response) =>
230
- response.ok
231
- ? response.json()
232
- : { total_votes: 0, votes_by_config: {} }
233
- )
234
- .catch(() => ({ total_votes: 0, votes_by_config: {} }));
235
- })
236
- );
237
-
238
- // Construire les modèles avec toutes les données
239
- const modelsWithVotes = modelsData.map((model, index) => {
240
- const votesData = modelVotesResponses[index];
241
- const modelUniqueId = getModelUniqueId(model);
242
- const isVotedByUser =
243
- votedModels.has(modelUniqueId) || localVotesSet.has(modelUniqueId);
244
-
245
- return {
246
- ...model,
247
- votes: getConfigVotes(
248
- {
249
- ...votesData,
250
- votes_by_config: votesData.votes_by_config || {},
251
- },
252
- model
253
- ),
254
- votes_by_config: votesData.votes_by_config || {},
255
- wait_time: formatWaitTime(model.submission_time),
256
- hasVoted: isVotedByUser,
257
- };
258
- });
259
-
260
- // Mettre à jour tous les états en une seule fois
261
- const sortedModels = sortModels(modelsWithVotes);
262
-
263
- // Batch updates
264
- const updates = () => {
265
- setPendingModels(sortedModels);
266
- setUserVotes(votedModels);
267
- setLocalVotes(localVotesSet);
268
- setLoadingModels(false);
269
- };
270
-
271
- updates();
272
- } catch (err) {
273
- console.error("Error fetching data:", err);
274
- setError(err.message);
275
- setLoadingModels(false);
276
- }
277
- };
278
-
279
- fetchData();
280
- }, [isAuthenticated, user]);
281
-
282
- // Modify the handleVote function
283
- const handleVote = async (model) => {
284
- if (!isAuthenticated) return;
285
-
286
- const modelUniqueId = getModelUniqueId(model);
287
-
288
- try {
289
- setError(null);
290
- setLoadingVotes((prev) => ({ ...prev, [modelUniqueId]: true }));
291
-
292
- // Add to localStorage immediately
293
- updateLocalVotes(modelUniqueId, "add");
294
-
295
- // Encode model name for URL
296
- const encodedModelName = encodeURIComponent(model.name);
297
-
298
- const response = await fetch(
299
- `/api/votes/${encodedModelName}?vote_type=up&user_id=${user.username}`,
300
- {
301
- method: "POST",
302
- headers: {
303
- "Content-Type": "application/json",
304
- },
305
- body: JSON.stringify({
306
- precision: model.precision,
307
- revision: model.revision,
308
- }),
309
- }
310
- );
311
-
312
- if (!response.ok) {
313
- // If the request fails, remove from localStorage
314
- updateLocalVotes(modelUniqueId, "remove");
315
- throw new Error("Failed to submit vote");
316
- }
317
-
318
- // Refresh votes for this model with cache bypass
319
- const [provider, modelName] = model.name.split("/");
320
- const timestamp = Date.now();
321
- const votesResponse = await fetch(
322
- `/api/votes/model/${provider}/${modelName}?nocache=${timestamp}`
323
- );
324
-
325
- if (!votesResponse.ok) {
326
- throw new Error("Failed to fetch updated votes");
327
- }
328
-
329
- const votesData = await votesResponse.json();
330
- console.log(`Updated votes for ${model.name}:`, votesData); // Debug log
331
-
332
- // Update model and resort the list
333
- setPendingModels((models) => {
334
- const updatedModels = models.map((m) =>
335
- getModelUniqueId(m) === getModelUniqueId(model)
336
- ? {
337
- ...m,
338
- votes: getConfigVotes(votesData, m),
339
- votes_by_config: votesData.votes_by_config || {},
340
- hasVoted: true,
341
- }
342
- : m
343
- );
344
- const sortedModels = sortModels(updatedModels);
345
- console.log("Updated and sorted models:", sortedModels); // Debug log
346
- return sortedModels;
347
- });
348
-
349
- // Update user votes with unique ID
350
- setUserVotes((prev) => new Set([...prev, getModelUniqueId(model)]));
351
- } catch (err) {
352
- console.error("Error voting:", err);
353
- setError(err.message);
354
- } finally {
355
- // Clear loading state for this model
356
- setLoadingVotes((prev) => ({
357
- ...prev,
358
- [modelUniqueId]: false,
359
- }));
360
- }
361
- };
362
-
363
- // Modify the rendering logic to consider both server and local votes
364
- // Inside the map function where you render models
365
- const isVoted = (model) => {
366
- const modelUniqueId = getModelUniqueId(model);
367
- return userVotes.has(modelUniqueId) || localVotes.has(modelUniqueId);
368
- };
369
-
370
- if (authLoading || (loadingModels && pendingModels.length === 0)) {
371
- return (
372
- <Box
373
- sx={{
374
- display: "flex",
375
- justifyContent: "center",
376
- alignItems: "center",
377
- height: "100vh",
378
- }}
379
- >
380
- <CircularProgress />
381
- </Box>
382
- );
383
- }
384
-
385
- return (
386
- <Box
387
- sx={{
388
- width: "100%",
389
- maxWidth: 1200,
390
- margin: "0 auto",
391
- py: 4,
392
- px: 0,
393
- }}
394
- >
395
- <PageHeader
396
- title="Vote for the Next Models"
397
- subtitle={
398
- <>
399
- Help us <span style={{ fontWeight: 600 }}>prioritize</span> which
400
- models to evaluate next
401
- </>
402
- }
403
- />
404
-
405
- {error && (
406
- <Alert severity="error" sx={{ mb: 2 }}>
407
- {error}
408
- </Alert>
409
- )}
410
-
411
- {/* Auth Status */}
412
- {/* <Box sx={{ mb: 3 }}>
413
- {isAuthenticated ? (
414
- <Paper
415
- elevation={0}
416
- sx={{ p: 2, border: "1px solid", borderColor: "grey.300" }}
417
- >
418
- <Stack
419
- direction="row"
420
- spacing={2}
421
- alignItems="center"
422
- justifyContent="space-between"
423
- >
424
- <Stack direction="row" spacing={1} alignItems="center">
425
- <Typography variant="body1">
426
- Connected as <strong>{user?.username}</strong>
427
- </Typography>
428
- <Chip
429
- label="Ready to vote"
430
- color="success"
431
- size="small"
432
- variant="outlined"
433
- />
434
- </Stack>
435
- <LogoutButton />
436
- </Stack>
437
- </Paper>
438
- ) : (
439
- <Paper
440
- elevation={0}
441
- sx={{
442
- p: 3,
443
- border: "1px solid",
444
- borderColor: "grey.300",
445
- display: "flex",
446
- flexDirection: "column",
447
- alignItems: "center",
448
- gap: 2,
449
- }}
450
- >
451
- <Typography variant="h6" align="center">
452
- Login to Vote
453
- </Typography>
454
- <Typography variant="body2" color="text.secondary" align="center">
455
- You need to be logged in with your Hugging Face account to vote
456
- for models
457
- </Typography>
458
- <AuthBlock />
459
- </Paper>
460
- )}
461
- </Box> */}
462
- <AuthContainer actionText="vote for models" />
463
-
464
- {/* Models List */}
465
- <Paper
466
- elevation={0}
467
- sx={{
468
- border: "1px solid",
469
- borderColor: "grey.300",
470
- borderRadius: 1,
471
- overflow: "hidden",
472
- minHeight: 400,
473
- }}
474
- >
475
- {/* Header - Always visible */}
476
- <Box
477
- sx={{
478
- px: 3,
479
- py: 2,
480
- borderBottom: "1px solid",
481
- borderColor: (theme) =>
482
- theme.palette.mode === "dark"
483
- ? alpha(theme.palette.divider, 0.1)
484
- : "grey.200",
485
- bgcolor: (theme) =>
486
- theme.palette.mode === "dark"
487
- ? alpha(theme.palette.background.paper, 0.5)
488
- : "grey.50",
489
- }}
490
- >
491
- <Typography
492
- variant="h6"
493
- sx={{ fontWeight: 600, color: "text.primary" }}
494
- >
495
- Models Pending Evaluation
496
- </Typography>
497
- </Box>
498
-
499
- {/* Table Header */}
500
- <Box
501
- sx={{
502
- px: 3,
503
- py: 1.5,
504
- borderBottom: "1px solid",
505
- borderColor: "divider",
506
- bgcolor: "background.paper",
507
- display: { xs: "none", sm: "grid" },
508
- gridTemplateColumns: "1fr 200px 160px",
509
- gap: 3,
510
- alignItems: "center",
511
- }}
512
- >
513
- <Box>
514
- <Typography variant="subtitle2" color="text.secondary">
515
- Model
516
- </Typography>
517
- </Box>
518
- <Box sx={{ textAlign: "right" }}>
519
- <Typography variant="subtitle2" color="text.secondary">
520
- Votes
521
- </Typography>
522
- </Box>
523
- <Box sx={{ textAlign: "right" }}>
524
- <Typography variant="subtitle2" color="text.secondary">
525
- Priority
526
- </Typography>
527
- </Box>
528
- </Box>
529
-
530
- {/* Content */}
531
- {loadingModels ? (
532
- <Box
533
- sx={{
534
- display: "flex",
535
- justifyContent: "center",
536
- alignItems: "center",
537
- height: "200px",
538
- width: "100%",
539
- bgcolor: "background.paper",
540
- }}
541
- >
542
- <CircularProgress />
543
- </Box>
544
- ) : pendingModels.length === 0 && !loadingModels ? (
545
- <NoModelsToVote />
546
- ) : (
547
- <List sx={{ p: 0, bgcolor: "background.paper" }}>
548
- {pendingModels.map((model, index) => {
549
- const isTopThree = index < 3;
550
- return (
551
- <React.Fragment key={getModelUniqueId(model)}>
552
- {index > 0 && <Divider />}
553
- <ListItem
554
- sx={{
555
- py: 2.5,
556
- px: 3,
557
- display: "grid",
558
- gridTemplateColumns: { xs: "1fr", sm: "1fr 200px 160px" },
559
- gap: { xs: 2, sm: 3 },
560
- alignItems: "start",
561
- position: "relative",
562
- "&:hover": {
563
- bgcolor: "action.hover",
564
- },
565
- }}
566
- >
567
- {/* Left side - Model info */}
568
- <Box>
569
- <Stack spacing={1}>
570
- {/* Model name and link */}
571
- <Stack
572
- direction={{ xs: "column", sm: "row" }}
573
- spacing={1}
574
- alignItems={{ xs: "stretch", sm: "center" }}
575
- >
576
- <Stack
577
- direction="row"
578
- spacing={1}
579
- alignItems="center"
580
- sx={{ flexGrow: 1 }}
581
- >
582
- <Link
583
- href={`https://huggingface.co/${model.name}`}
584
- target="_blank"
585
- rel="noopener noreferrer"
586
- sx={{
587
- textDecoration: "none",
588
- color: "primary.main",
589
- fontWeight: 500,
590
- "&:hover": {
591
- textDecoration: "underline",
592
- },
593
- fontSize: { xs: "0.9rem", sm: "inherit" },
594
- wordBreak: "break-word",
595
- }}
596
- >
597
- {model.name}
598
- </Link>
599
- <IconButton
600
- size="small"
601
- href={`https://huggingface.co/${model.name}`}
602
- target="_blank"
603
- rel="noopener noreferrer"
604
- sx={{
605
- ml: 0.5,
606
- p: 0.5,
607
- color: "action.active",
608
- "&:hover": {
609
- color: "primary.main",
610
- },
611
- }}
612
- >
613
- <OpenInNewIcon sx={{ fontSize: "1rem" }} />
614
- </IconButton>
615
- </Stack>
616
- <Stack
617
- direction="row"
618
- spacing={1}
619
- sx={{
620
- width: { xs: "100%", sm: "auto" },
621
- justifyContent: {
622
- xs: "flex-start",
623
- sm: "flex-end",
624
- },
625
- flexWrap: "wrap",
626
- gap: 1,
627
- }}
628
- >
629
- <Chip
630
- label={model.precision}
631
- size="small"
632
- variant="outlined"
633
- sx={{
634
- borderColor: "grey.300",
635
- bgcolor: "grey.50",
636
- "& .MuiChip-label": {
637
- fontSize: "0.75rem",
638
- fontWeight: 600,
639
- color: "text.secondary",
640
- },
641
- }}
642
- />
643
- <Chip
644
- label={`rev: ${model.revision.slice(0, 7)}`}
645
- size="small"
646
- variant="outlined"
647
- sx={{
648
- borderColor: "grey.300",
649
- bgcolor: "grey.50",
650
- "& .MuiChip-label": {
651
- fontSize: "0.75rem",
652
- fontWeight: 600,
653
- color: "text.secondary",
654
- },
655
- }}
656
- />
657
- </Stack>
658
- </Stack>
659
- {/* Metadata row */}
660
- <Stack
661
- direction={{ xs: "column", sm: "row" }}
662
- spacing={{ xs: 1, sm: 2 }}
663
- alignItems={{ xs: "flex-start", sm: "center" }}
664
- >
665
- <Stack
666
- direction="row"
667
- spacing={0.5}
668
- alignItems="center"
669
- >
670
- <AccessTimeIcon
671
- sx={{
672
- fontSize: "0.875rem",
673
- color: "text.secondary",
674
- }}
675
- />
676
- <Typography variant="body2" color="text.secondary">
677
- {model.wait_time}
678
- </Typography>
679
- </Stack>
680
- <Stack
681
- direction="row"
682
- spacing={0.5}
683
- alignItems="center"
684
- >
685
- <PersonIcon
686
- sx={{
687
- fontSize: "0.875rem",
688
- color: "text.secondary",
689
- }}
690
- />
691
- <Typography variant="body2" color="text.secondary">
692
- {model.submitter}
693
- </Typography>
694
- </Stack>
695
- </Stack>
696
- </Stack>
697
- </Box>
698
-
699
- {/* Vote Column */}
700
- <Box
701
- sx={{
702
- textAlign: { xs: "left", sm: "right" },
703
- mt: { xs: 2, sm: 0 },
704
- }}
705
- >
706
- <Stack
707
- direction={{ xs: "row", sm: "row" }}
708
- spacing={2.5}
709
- justifyContent={{ xs: "space-between", sm: "flex-end" }}
710
- alignItems="center"
711
- >
712
- <Stack
713
- alignItems={{ xs: "flex-start", sm: "center" }}
714
- sx={{
715
- minWidth: { xs: "auto", sm: "90px" },
716
- }}
717
- >
718
- <Typography
719
- variant="h4"
720
- component="div"
721
- sx={{
722
- fontWeight: 700,
723
- lineHeight: 1,
724
- fontSize: { xs: "1.75rem", sm: "2rem" },
725
- display: "flex",
726
- alignItems: "center",
727
- justifyContent: "center",
728
- }}
729
- >
730
- <Typography
731
- component="span"
732
- sx={{
733
- fontSize: { xs: "1.25rem", sm: "1.5rem" },
734
- fontWeight: 600,
735
- color: "primary.main",
736
- lineHeight: 1,
737
- mr: 0.5,
738
- mt: "-2px",
739
- }}
740
- >
741
- +
742
- </Typography>
743
- <Typography
744
- component="span"
745
- sx={{
746
- color:
747
- model.votes === 0
748
- ? "text.primary"
749
- : "primary.main",
750
- fontWeight: 700,
751
- lineHeight: 1,
752
- }}
753
- >
754
- {model.votes > 999 ? "999" : model.votes}
755
- </Typography>
756
- </Typography>
757
- <Typography
758
- variant="caption"
759
- sx={{
760
- color: "text.secondary",
761
- fontWeight: 500,
762
- mt: 0.5,
763
- textTransform: "uppercase",
764
- letterSpacing: "0.05em",
765
- fontSize: "0.75rem",
766
- }}
767
- >
768
- votes
769
- </Typography>
770
- </Stack>
771
- <Button
772
- variant={isVoted(model) ? "contained" : "outlined"}
773
- size={isMobile ? "medium" : "large"}
774
- onClick={() => handleVote(model)}
775
- disabled={
776
- !isAuthenticated ||
777
- isVoted(model) ||
778
- loadingVotes[getModelUniqueId(model)]
779
- }
780
- color="primary"
781
- sx={{
782
- minWidth: { xs: "80px", sm: "100px" },
783
- height: { xs: "36px", sm: "40px" },
784
- textTransform: "none",
785
- fontWeight: 600,
786
- fontSize: { xs: "0.875rem", sm: "0.95rem" },
787
- ...(isVoted(model)
788
- ? {
789
- bgcolor: "primary.main",
790
- "&:hover": {
791
- bgcolor: "primary.dark",
792
- },
793
- "&.Mui-disabled": {
794
- bgcolor: "primary.main",
795
- color: "white",
796
- opacity: 0.7,
797
- },
798
- }
799
- : {
800
- borderWidth: 2,
801
- "&:hover": {
802
- borderWidth: 2,
803
- },
804
- }),
805
- }}
806
- >
807
- {loadingVotes[getModelUniqueId(model)] ? (
808
- <CircularProgress size={20} color="inherit" />
809
- ) : isVoted(model) ? (
810
- <Stack
811
- direction="row"
812
- spacing={0.5}
813
- alignItems="center"
814
- >
815
- <CheckIcon sx={{ fontSize: "1.2rem" }} />
816
- <span>Voted</span>
817
- </Stack>
818
- ) : (
819
- "Vote"
820
- )}
821
- </Button>
822
- </Stack>
823
- </Box>
824
-
825
- {/* Priority Column */}
826
- <Box
827
- sx={{
828
- textAlign: { xs: "left", sm: "right" },
829
- mt: { xs: 2, sm: 0 },
830
- display: { xs: "none", sm: "block" },
831
- }}
832
- >
833
- <Chip
834
- label={
835
- <Stack
836
- direction="row"
837
- spacing={0.5}
838
- alignItems="center"
839
- >
840
- {isTopThree && (
841
- <Typography
842
- variant="body2"
843
- sx={{
844
- fontWeight: 600,
845
- color: isTopThree
846
- ? "primary.main"
847
- : "text.primary",
848
- letterSpacing: "0.02em",
849
- }}
850
- >
851
- HIGH
852
- </Typography>
853
- )}
854
- <Typography
855
- variant="body2"
856
- sx={{
857
- fontWeight: 600,
858
- color: isTopThree
859
- ? "primary.main"
860
- : "text.secondary",
861
- letterSpacing: "0.02em",
862
- }}
863
- >
864
- #{index + 1}
865
- </Typography>
866
- </Stack>
867
- }
868
- size="medium"
869
- variant={isTopThree ? "filled" : "outlined"}
870
- sx={{
871
- height: 36,
872
- minWidth: "100px",
873
- bgcolor: isTopThree
874
- ? (theme) => alpha(theme.palette.primary.main, 0.1)
875
- : "transparent",
876
- borderColor: isTopThree ? "primary.main" : "grey.300",
877
- borderWidth: 2,
878
- "& .MuiChip-label": {
879
- px: 2,
880
- fontSize: "0.95rem",
881
- },
882
- }}
883
- />
884
- </Box>
885
- </ListItem>
886
- </React.Fragment>
887
- );
888
- })}
889
- </List>
890
- )}
891
- </Paper>
892
- </Box>
893
- );
894
- }
895
-
896
- export default VoteModelPage;