Spaces:
Running
on
CPU Upgrade
Running
on
CPU Upgrade
add localstorage for pending votes
Browse files
frontend/src/pages/AddModelPage/components/EvaluationQueues/EvaluationQueues.js
CHANGED
@@ -644,10 +644,6 @@ const EvaluationQueues = ({ defaultExpanded = true }) => {
|
|
644 |
},
|
645 |
width: { xs: "100%", sm: "auto" },
|
646 |
alignItems: { xs: "stretch", sm: "center" },
|
647 |
-
mb: { xs: 1, sm: 0 },
|
648 |
-
".Mui-expanded &": {
|
649 |
-
mb: 0,
|
650 |
-
},
|
651 |
}}
|
652 |
>
|
653 |
<Chip
|
|
|
644 |
},
|
645 |
width: { xs: "100%", sm: "auto" },
|
646 |
alignItems: { xs: "stretch", sm: "center" },
|
|
|
|
|
|
|
|
|
647 |
}}
|
648 |
>
|
649 |
<Chip
|
frontend/src/pages/VoteModelPage/VoteModelPage.js
CHANGED
@@ -70,13 +70,16 @@ const NoModelsToVote = () => (
|
|
70 |
</Box>
|
71 |
);
|
72 |
|
|
|
|
|
73 |
function VoteModelPage() {
|
74 |
-
const { isAuthenticated, user, loading } = useAuth();
|
75 |
const [pendingModels, setPendingModels] = useState([]);
|
76 |
const [loadingModels, setLoadingModels] = useState(true);
|
77 |
const [error, setError] = useState(null);
|
78 |
const [userVotes, setUserVotes] = useState(new Set());
|
79 |
const [loadingVotes, setLoadingVotes] = useState({});
|
|
|
80 |
const theme = useTheme();
|
81 |
const isMobile = useMediaQuery(theme.breakpoints.down("sm"));
|
82 |
|
@@ -109,24 +112,25 @@ function VoteModelPage() {
|
|
109 |
};
|
110 |
|
111 |
const getConfigVotes = (votesData, model) => {
|
112 |
-
//
|
113 |
-
|
114 |
-
model_name: model.name,
|
115 |
-
precision: model.precision,
|
116 |
-
revision: model.revision,
|
117 |
-
votes_data: votesData,
|
118 |
-
});
|
119 |
|
120 |
-
//
|
|
|
121 |
for (const [key, config] of Object.entries(votesData.votes_by_config)) {
|
122 |
if (
|
123 |
config.precision === model.precision &&
|
124 |
config.revision === model.revision
|
125 |
) {
|
126 |
-
|
|
|
127 |
}
|
128 |
}
|
129 |
-
|
|
|
|
|
|
|
|
|
130 |
};
|
131 |
|
132 |
const sortModels = (models) => {
|
@@ -151,22 +155,57 @@ function VoteModelPage() {
|
|
151 |
});
|
152 |
};
|
153 |
|
154 |
-
//
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
155 |
useEffect(() => {
|
156 |
const fetchData = async () => {
|
157 |
try {
|
158 |
-
|
|
|
|
|
|
|
159 |
setError(null);
|
160 |
|
161 |
-
//
|
162 |
-
|
163 |
-
|
164 |
-
|
165 |
-
|
166 |
-
|
167 |
-
|
168 |
-
|
169 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
170 |
const votesData = await userVotesResponse.json();
|
171 |
const userVotes = Array.isArray(votesData) ? votesData : [];
|
172 |
|
@@ -175,64 +214,64 @@ function VoteModelPage() {
|
|
175 |
vote.revision || "main"
|
176 |
}`;
|
177 |
votedModels.add(uniqueId);
|
|
|
|
|
|
|
|
|
178 |
});
|
179 |
}
|
180 |
-
setUserVotes(votedModels);
|
181 |
-
|
182 |
-
// Fetch pending models
|
183 |
-
const pendingModelsResponse = await fetch("/api/models/pending");
|
184 |
-
if (!pendingModelsResponse.ok) {
|
185 |
-
throw new Error("Failed to fetch pending models");
|
186 |
-
}
|
187 |
-
const modelsData = await pendingModelsResponse.json();
|
188 |
-
|
189 |
-
// Fetch votes for each model
|
190 |
-
const modelsWithVotes = await Promise.all(
|
191 |
-
modelsData.map(async (model) => {
|
192 |
-
try {
|
193 |
-
const [provider, modelName] = model.name.split("/");
|
194 |
-
const votesResponse = await fetch(
|
195 |
-
`/api/votes/model/${provider}/${modelName}`
|
196 |
-
);
|
197 |
-
|
198 |
-
if (!votesResponse.ok) {
|
199 |
-
return {
|
200 |
-
...model,
|
201 |
-
votes: 0,
|
202 |
-
votes_by_config: {},
|
203 |
-
wait_time: formatWaitTime(model.submission_time),
|
204 |
-
hasVoted: votedModels.has(getModelUniqueId(model)),
|
205 |
-
};
|
206 |
-
}
|
207 |
|
208 |
-
|
209 |
-
|
210 |
-
|
211 |
-
|
212 |
-
|
213 |
-
|
214 |
-
|
215 |
-
|
216 |
-
|
217 |
-
|
218 |
-
|
219 |
-
...model,
|
220 |
-
votes: 0,
|
221 |
-
votes_by_config: {},
|
222 |
-
wait_time: formatWaitTime(model.submission_time),
|
223 |
-
hasVoted: votedModels.has(getModelUniqueId(model)),
|
224 |
-
};
|
225 |
-
}
|
226 |
})
|
227 |
);
|
228 |
|
229 |
-
//
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
230 |
const sortedModels = sortModels(modelsWithVotes);
|
231 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
232 |
} catch (err) {
|
233 |
console.error("Error fetching data:", err);
|
234 |
setError(err.message);
|
235 |
-
} finally {
|
236 |
setLoadingModels(false);
|
237 |
}
|
238 |
};
|
@@ -240,13 +279,18 @@ function VoteModelPage() {
|
|
240 |
fetchData();
|
241 |
}, [isAuthenticated, user]);
|
242 |
|
|
|
243 |
const handleVote = async (model) => {
|
244 |
if (!isAuthenticated) return;
|
245 |
|
|
|
|
|
246 |
try {
|
247 |
setError(null);
|
248 |
-
|
249 |
-
|
|
|
|
|
250 |
|
251 |
// Encode model name for URL
|
252 |
const encodedModelName = encodeURIComponent(model.name);
|
@@ -266,6 +310,8 @@ function VoteModelPage() {
|
|
266 |
);
|
267 |
|
268 |
if (!response.ok) {
|
|
|
|
|
269 |
throw new Error("Failed to submit vote");
|
270 |
}
|
271 |
|
@@ -309,12 +355,19 @@ function VoteModelPage() {
|
|
309 |
// Clear loading state for this model
|
310 |
setLoadingVotes((prev) => ({
|
311 |
...prev,
|
312 |
-
[
|
313 |
}));
|
314 |
}
|
315 |
};
|
316 |
|
317 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
318 |
return (
|
319 |
<Box
|
320 |
sx={{
|
@@ -716,12 +769,12 @@ function VoteModelPage() {
|
|
716 |
</Typography>
|
717 |
</Stack>
|
718 |
<Button
|
719 |
-
variant={model
|
720 |
size={isMobile ? "medium" : "large"}
|
721 |
onClick={() => handleVote(model)}
|
722 |
disabled={
|
723 |
!isAuthenticated ||
|
724 |
-
model
|
725 |
loadingVotes[getModelUniqueId(model)]
|
726 |
}
|
727 |
color="primary"
|
@@ -731,7 +784,7 @@ function VoteModelPage() {
|
|
731 |
textTransform: "none",
|
732 |
fontWeight: 600,
|
733 |
fontSize: { xs: "0.875rem", sm: "0.95rem" },
|
734 |
-
...(model
|
735 |
? {
|
736 |
bgcolor: "primary.main",
|
737 |
"&:hover": {
|
@@ -753,7 +806,7 @@ function VoteModelPage() {
|
|
753 |
>
|
754 |
{loadingVotes[getModelUniqueId(model)] ? (
|
755 |
<CircularProgress size={20} color="inherit" />
|
756 |
-
) : model
|
757 |
<Stack
|
758 |
direction="row"
|
759 |
spacing={0.5}
|
|
|
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 |
|
|
|
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) => {
|
|
|
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 |
|
|
|
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 |
};
|
|
|
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);
|
|
|
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 |
|
|
|
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={{
|
|
|
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"
|
|
|
784 |
textTransform: "none",
|
785 |
fontWeight: 600,
|
786 |
fontSize: { xs: "0.875rem", sm: "0.95rem" },
|
787 |
+
...(isVoted(model)
|
788 |
? {
|
789 |
bgcolor: "primary.main",
|
790 |
"&:hover": {
|
|
|
806 |
>
|
807 |
{loadingVotes[getModelUniqueId(model)] ? (
|
808 |
<CircularProgress size={20} color="inherit" />
|
809 |
+
) : isVoted(model) ? (
|
810 |
<Stack
|
811 |
direction="row"
|
812 |
spacing={0.5}
|