tfrere commited on
Commit
29519d8
·
1 Parent(s): 171ef76

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
- // Afficher les données pour le debug
113
- console.log("Checking votes for model:", {
114
- model_name: model.name,
115
- precision: model.precision,
116
- revision: model.revision,
117
- votes_data: votesData,
118
- });
119
 
120
- // Parcourir toutes les configurations pour trouver celle qui correspond
 
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
- return config.count;
 
127
  }
128
  }
129
- return 0;
 
 
 
 
130
  };
131
 
132
  const sortModels = (models) => {
@@ -151,22 +155,57 @@ function VoteModelPage() {
151
  });
152
  };
153
 
154
- // Fetch user's votes and models together
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
155
  useEffect(() => {
156
  const fetchData = async () => {
157
  try {
158
- setLoadingModels(true);
 
 
 
159
  setError(null);
160
 
161
- // Fetch user votes only if authenticated
162
- let votedModels = new Set();
163
- if (isAuthenticated && user) {
164
- const userVotesResponse = await fetch(
165
- `/api/votes/user/${user.username}`
166
- );
167
- if (!userVotesResponse.ok) {
168
- throw new Error("Failed to fetch user votes");
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
- const votesData = await votesResponse.json();
209
- return {
210
- ...model,
211
- votes: getConfigVotes(votesData, model),
212
- votes_by_config: votesData.votes_by_config || {},
213
- wait_time: formatWaitTime(model.submission_time),
214
- hasVoted: votedModels.has(getModelUniqueId(model)),
215
- };
216
- } catch (err) {
217
- console.error(`Error fetching votes for ${model.name}:`, err);
218
- return {
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
- // Sort models
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
230
  const sortedModels = sortModels(modelsWithVotes);
231
- setPendingModels(sortedModels);
 
 
 
 
 
 
 
 
 
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
- // Set loading state for this specific model
249
- setLoadingVotes((prev) => ({ ...prev, [getModelUniqueId(model)]: true }));
 
 
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
- [getModelUniqueId(model)]: false,
313
  }));
314
  }
315
  };
316
 
317
- if (loading) {
 
 
 
 
 
 
 
318
  return (
319
  <Box
320
  sx={{
@@ -716,12 +769,12 @@ function VoteModelPage() {
716
  </Typography>
717
  </Stack>
718
  <Button
719
- variant={model.hasVoted ? "contained" : "outlined"}
720
  size={isMobile ? "medium" : "large"}
721
  onClick={() => handleVote(model)}
722
  disabled={
723
  !isAuthenticated ||
724
- model.hasVoted ||
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.hasVoted
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.hasVoted ? (
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}