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}
|