mirror of
https://github.com/R0m1k3/Socialflow.git
synced 2026-10-12 01:36:54 +02:00
perf(client): rafraîchir les données sans recharger la page
`staleTime: Infinity` combiné à `refetchOnWindowFocus: false` empêchait toute revalidation : une donnée chargée à la première visite restait affichée pour toute la session. Or le serveur agit de son côté — le scheduler publie chaque minute, les jetons se renouvellent en tâche de fond — sans que le navigateur en soit averti. D'où la nécessité de recharger la page pour voir quoi que ce soit. Les compteurs du tableau de bord étaient le cas le plus visible : /api/stats et /api/posts n'étaient invalidés par aucune mutation de l'application, et ne pouvaient donc plus bouger du tout. - Péremption courte (30 s) et rafraîchissement au retour sur l'onglet ou sur reconnexion, en remplacement du cache éternel. - /api/stats et /api/posts, qui dérivent de tout le reste, sont invalidés depuis le MutationCache après chaque mutation réussie : aucune mutation ne peut raisonnablement se souvenir de le faire, et aucune ne le faisait. Les mutations d'authentification en sont exclues, la session étant déjà détruite quand la déconnexion aboutit. - Les trois sondages à 3 s (historique desktop et mobile, reels en cours) devenaient inutiles hors traitement en cours. Ils ne s'emballent plus que lorsqu'il y a effectivement quelque chose à suivre — une publication dont l'heure est passée, une génération en route — et retombent à 30 s sinon. La page Historique rechargeait tout l'historique 20 fois par minute. - La liste des modèles OpenRouter, servie par un appel sortant, garde une péremption d'une heure pour ne pas être redemandée à chaque retour d'onglet. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qns6Gcpd95bCHWUxySh4Ew
This commit is contained in:
8 files changed
+75
-8
No files matched your search
@@ -38,6 +38,9 @@ export default function AiChat() {
|
||||
// Fetch available models from OpenRouter
|
||||
const { data: modelsData, isLoading: modelsLoading } = useQuery<{ data: OpenRouterModel[] }>({
|
||||
queryKey: ['/api/openrouter/models'],
|
||||
// Liste servie par un appel sortant vers OpenRouter : elle ne bouge pas d'une
|
||||
// heure à l'autre, inutile de la redemander à chaque retour sur l'onglet.
|
||||
staleTime: 60 * 60 * 1000,
|
||||
});
|
||||
|
||||
const availableModels = modelsData?.data || [];
|
||||
|
||||
@@ -24,7 +24,10 @@ interface OngoingReelsProps {
|
||||
export default function OngoingReels({ compact = false }: OngoingReelsProps) {
|
||||
const { data: ongoingPosts = [] } = useQuery<Post[]>({
|
||||
queryKey: ["/api/reels/ongoing"],
|
||||
refetchInterval: 3000, // Poll every 3 seconds
|
||||
// Cette route ne renvoie que les générations en cours : tant qu'elle est
|
||||
// vide il n'y a pas de barre de progression à animer. On garde une veille
|
||||
// lente, qui suffit à détecter une génération lancée depuis un autre écran.
|
||||
refetchInterval: (query) => ((query.state.data?.length ?? 0) > 0 ? 3000 : 30000),
|
||||
});
|
||||
|
||||
// Nothing to show — render nothing (not even a card)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Home, PlusCircle, Calendar, Images, Users, Bot, Clock, Settings, Database, UserCog, LogOut, ChevronLeft, ChevronRight, ChevronDown, Wand2, BarChart3, Video, Music, Shield } from "lucide-react";
|
||||
import { Link, useLocation } from "wouter";
|
||||
import { useQuery, useMutation } from "@tanstack/react-query";
|
||||
import { apiRequest, queryClient } from "@/lib/queryClient";
|
||||
import { apiRequest, AUTH_MUTATION, queryClient } from "@/lib/queryClient";
|
||||
import { useToast } from "@/hooks/use-toast";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Avatar, AvatarFallback } from "@/components/ui/avatar";
|
||||
@@ -52,6 +52,7 @@ export default function Sidebar({ onLinkClick }: SidebarProps = {}) {
|
||||
};
|
||||
|
||||
const logoutMutation = useMutation({
|
||||
mutationKey: [AUTH_MUTATION, "logout"],
|
||||
mutationFn: async () => {
|
||||
const res = await apiRequest("POST", "/api/auth/logout", {});
|
||||
return await res.json();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { QueryClient, QueryFunction } from "@tanstack/react-query";
|
||||
import { MutationCache, QueryClient, QueryFunction } from "@tanstack/react-query";
|
||||
|
||||
// Les routes d'authentification elles-mêmes ne doivent jamais déclencher
|
||||
// de redirection automatique (ex: mauvais mot de passe sur /login).
|
||||
@@ -88,13 +88,42 @@ export const getQueryFn: <T>(options: {
|
||||
return await res.json();
|
||||
};
|
||||
|
||||
/**
|
||||
* Marque les mutations d'authentification, exclues de l'invalidation globale
|
||||
* ci-dessous. À passer en `mutationKey` : `[AUTH_MUTATION, 'logout']`.
|
||||
*/
|
||||
export const AUTH_MUTATION = "auth";
|
||||
|
||||
export const queryClient = new QueryClient({
|
||||
// Les compteurs du tableau de bord et la liste des posts dérivent de tout le
|
||||
// reste : publier, supprimer un média, générer un texte… Aucune mutation ne
|
||||
// peut raisonnablement se souvenir de les invalider, et aucune ne le faisait :
|
||||
// ils restaient figés jusqu'au rechargement de la page. On les invalide donc
|
||||
// ici, une bonne fois, après chaque mutation réussie.
|
||||
mutationCache: new MutationCache({
|
||||
onSuccess: (_data, _variables, _context, mutation) => {
|
||||
// Sauf pour l'authentification : au moment où la déconnexion réussit, la
|
||||
// session est déjà détruite. Relancer des requêtes ici ne produirait que
|
||||
// des 401 et une redirection brutale, avant même que le cache soit vidé.
|
||||
if (mutation.options.mutationKey?.[0] === AUTH_MUTATION) return;
|
||||
|
||||
queryClient.invalidateQueries({ queryKey: ["/api/stats"] });
|
||||
queryClient.invalidateQueries({ queryKey: ["/api/posts"] });
|
||||
},
|
||||
}),
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
queryFn: getQueryFn({ on401: "throw" }),
|
||||
refetchInterval: false,
|
||||
refetchOnWindowFocus: false,
|
||||
staleTime: Infinity,
|
||||
// Le serveur agit de son côté — le scheduler publie toutes les minutes, le
|
||||
// renouvellement des jetons tourne en tâche de fond — sans que le
|
||||
// navigateur en soit averti. Avec `staleTime: Infinity`, un écran restait
|
||||
// sur le cache de sa première visite pour toute la session : d'où la
|
||||
// nécessité de recharger la page pour voir quoi que ce soit. Une
|
||||
// péremption courte, plus un rafraîchissement au retour sur l'onglet,
|
||||
// suffit à garder l'affichage juste sans marteler l'API.
|
||||
staleTime: 30_000,
|
||||
refetchOnWindowFocus: true,
|
||||
refetchOnReconnect: true,
|
||||
retry: false,
|
||||
},
|
||||
mutations: {
|
||||
|
||||
@@ -17,6 +17,15 @@ type ScheduledPostWithRelations = ScheduledPost & {
|
||||
page?: SocialPage;
|
||||
};
|
||||
|
||||
/** Publication dont l'heure est passée mais que le scheduler n'a pas encore traitée. */
|
||||
function isAwaitingPublication(scheduledPost: ScheduledPostWithRelations): boolean {
|
||||
return (
|
||||
!!scheduledPost.scheduledAt &&
|
||||
new Date(scheduledPost.scheduledAt) <= new Date() &&
|
||||
!scheduledPost.publishedAt
|
||||
);
|
||||
}
|
||||
|
||||
export default function History() {
|
||||
const [sidebarOpen, setSidebarOpen] = useState(false);
|
||||
const [previewModalOpen, setPreviewModalOpen] = useState(false);
|
||||
@@ -25,7 +34,11 @@ export default function History() {
|
||||
|
||||
const { data: scheduledPosts = [], isLoading } = useQuery<ScheduledPostWithRelations[]>({
|
||||
queryKey: ['/api/scheduled-posts'],
|
||||
refetchInterval: 3000,
|
||||
// Le scheduler publie en tâche de fond : on ne suit à la seconde que ce qui
|
||||
// est réellement en attente. Le reste du temps, une veille lente suffit —
|
||||
// sans quoi la page entière était rechargée 20 fois par minute pour rien.
|
||||
refetchInterval: (query) =>
|
||||
(query.state.data ?? []).some(isAwaitingPublication) ? 3000 : 30000,
|
||||
});
|
||||
|
||||
// Filter attempted posts (scheduled in the past)
|
||||
|
||||
@@ -19,13 +19,25 @@ import {
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
|
||||
/** Post dont le statut peut changer d'un instant à l'autre. */
|
||||
function isChanging(post: any): boolean {
|
||||
if (post?.generationStatus === 'pending' || post?.generationStatus === 'processing') {
|
||||
return true;
|
||||
}
|
||||
return post?.status === 'scheduled' && !!post?.scheduledFor && new Date(post.scheduledFor) <= new Date();
|
||||
}
|
||||
|
||||
export default function MobileHistory() {
|
||||
const [searchTerm, setSearchTerm] = useState("");
|
||||
|
||||
// Fetch posts history with polling to see status updates
|
||||
const { data: posts } = useQuery<any[]>({
|
||||
queryKey: ['/api/posts'],
|
||||
refetchInterval: 3000, // Poll every 3 seconds
|
||||
// Un post planifié pour la semaine prochaine ne bougera pas d'ici une
|
||||
// seconde : on ne suit de près que ce dont le statut peut basculer
|
||||
// maintenant (génération en cours, ou heure de publication dépassée).
|
||||
refetchInterval: (query) =>
|
||||
(query.state.data ?? []).some(isChanging) ? 3000 : 30000,
|
||||
});
|
||||
|
||||
const getStatusIcon = (status: string) => {
|
||||
|
||||
@@ -47,6 +47,9 @@ export default function SettingsMobile() {
|
||||
|
||||
const { data: openrouterModels, isLoading: modelsLoading } = useQuery({
|
||||
queryKey: ['/api/openrouter/models'],
|
||||
// Liste servie par un appel sortant vers OpenRouter : elle ne bouge pas d'une
|
||||
// heure à l'autre, inutile de la redemander à chaque retour sur l'onglet.
|
||||
staleTime: 60 * 60 * 1000,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -55,6 +55,9 @@ export default function Settings() {
|
||||
|
||||
const { data: openrouterModels, isLoading: modelsLoading } = useQuery({
|
||||
queryKey: ['/api/openrouter/models'],
|
||||
// Liste servie par un appel sortant vers OpenRouter : elle ne bouge pas d'une
|
||||
// heure à l'autre, inutile de la redemander à chaque retour sur l'onglet.
|
||||
staleTime: 60 * 60 * 1000,
|
||||
});
|
||||
|
||||
const { data: ffmpegConfig } = useQuery({
|
||||
|
||||
Reference in new issue
Block a user