From e4a389cc671b18bdedc1650c292d8e31bdd3a5b0 Mon Sep 17 00:00:00 2001 From: Manus Date: Mon, 17 Aug 2026 20:21:45 +0000 Subject: [PATCH] =?UTF-8?q?Checkpoint:=20Audit=20et=20refactoring=20:=20ce?= =?UTF-8?q?ntralisation=20robuste=20du=20suivi=20lu/non=20lu,=20suppressio?= =?UTF-8?q?n=20des=20captures=20SQL=20silencieuses,=20migration=20d?= =?UTF-8?q?=E2=80=99unicit=C3=A9=20avec=20d=C3=A9duplication=20des=20lectu?= =?UTF-8?q?res=20existantes,=20correction=20du=20reclassement=20AAP=20via?= =?UTF-8?q?=20classifyAap,=20nettoyage=20de=20cinq=20composants=20template?= =?UTF-8?q?=20non=20utilis=C3=A9s,=20nettoyage=20des=20lectures=20lors=20d?= =?UTF-8?q?es=20purges/fusions=20RSS=20et=20d=C3=A9coupage=20dynamique=20d?= =?UTF-8?q?u=20frontend.=2027=20tests=20Vitest,=20TypeScript=20et=20build?= =?UTF-8?q?=20de=20production=20valid=C3=A9s.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + client/public/__manus__/version.json | 4 +- client/src/App.tsx | 34 +- client/src/components/AIChatBox.tsx | 335 ---- client/src/components/DashboardLayout.tsx | 264 --- .../components/DashboardLayoutSkeleton.tsx | 46 - client/src/components/Map.tsx | 155 -- client/src/pages/ComponentShowcase.tsx | 1437 ----------------- drizzle/0012_fluffy_boomer.sql | 10 + drizzle/meta/0012_snapshot.json | 1061 ++++++++++++ drizzle/meta/_journal.json | 7 + drizzle/schema.ts | 22 +- server/articleReads.test.ts | 44 + server/db.ts | 120 +- server/routers.ts | 122 +- server/rssEngine.ts | 8 +- todo.md | 8 + vite.config.ts | 13 + 18 files changed, 1343 insertions(+), 2348 deletions(-) delete mode 100644 client/src/components/AIChatBox.tsx delete mode 100644 client/src/components/DashboardLayout.tsx delete mode 100644 client/src/components/DashboardLayoutSkeleton.tsx delete mode 100644 client/src/components/Map.tsx delete mode 100644 client/src/pages/ComponentShowcase.tsx create mode 100644 drizzle/0012_fluffy_boomer.sql create mode 100644 drizzle/meta/0012_snapshot.json create mode 100644 server/articleReads.test.ts diff --git a/.gitignore b/.gitignore index 24ab932..a252c1e 100644 --- a/.gitignore +++ b/.gitignore @@ -108,3 +108,4 @@ temp/ # Webdev artifacts (checkpoint zips, migrations, etc.) .webdev/ +.project-config.json diff --git a/client/public/__manus__/version.json b/client/public/__manus__/version.json index 98f2641..5444aa9 100644 --- a/client/public/__manus__/version.json +++ b/client/public/__manus__/version.json @@ -1,4 +1,4 @@ { - "version": "fd4e8bc0", - "timestamp": 1784275053491 + "timestamp": 1786998105509, + "version": "de5930f0" } \ No newline at end of file diff --git a/client/src/App.tsx b/client/src/App.tsx index 041d18c..c1648de 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -1,22 +1,26 @@ import { Toaster } from "@/components/ui/sonner"; import { TooltipProvider } from "@/components/ui/tooltip"; -import NotFound from "@/pages/NotFound"; +import { lazy, Suspense } from "react"; import { Route, Switch, Redirect } from "wouter"; import ErrorBoundary from "./components/ErrorBoundary"; import { ThemeProvider } from "./contexts/ThemeContext"; import { LocalAuthProvider, useLocalAuth } from "./contexts/LocalAuthContext"; import { AppLayout } from "./components/AppLayout"; -import Login from "./pages/Login"; -import VeilleDashboard from "./pages/VeilleDashboard"; -import AAPDashboard from "./pages/AAPDashboard"; -import Settings from "./pages/Settings"; -import UsersAdmin from "./pages/UsersAdmin"; -import ImportLogs from "./pages/ImportLogs"; -import BoiteAIdees from "@/pages/BoiteAIdees"; -import RssFeeds from "@/pages/RssFeeds"; -import AzureCallback from "@/pages/AzureCallback"; import { Loader2 } from "lucide-react"; +// Chaque page est chargée à la demande : la connexion reste rapide et les écrans +// d'administration lourds ne sont téléchargés que lorsqu'ils sont effectivement ouverts. +const NotFound = lazy(() => import("@/pages/NotFound")); +const Login = lazy(() => import("./pages/Login")); +const VeilleDashboard = lazy(() => import("./pages/VeilleDashboard")); +const AAPDashboard = lazy(() => import("./pages/AAPDashboard")); +const Settings = lazy(() => import("./pages/Settings")); +const UsersAdmin = lazy(() => import("./pages/UsersAdmin")); +const ImportLogs = lazy(() => import("./pages/ImportLogs")); +const BoiteAIdees = lazy(() => import("@/pages/BoiteAIdees")); +const RssFeeds = lazy(() => import("@/pages/RssFeeds")); +const AzureCallback = lazy(() => import("@/pages/AzureCallback")); + // ─── Guard d'authentification ───────────────────────────────────────────────── function AuthGuard({ children }: { children: React.ReactNode }) { @@ -150,7 +154,15 @@ function App() { - + + + + )} + > + + diff --git a/client/src/components/AIChatBox.tsx b/client/src/components/AIChatBox.tsx deleted file mode 100644 index 1c00871..0000000 --- a/client/src/components/AIChatBox.tsx +++ /dev/null @@ -1,335 +0,0 @@ -import { Button } from "@/components/ui/button"; -import { Textarea } from "@/components/ui/textarea"; -import { ScrollArea } from "@/components/ui/scroll-area"; -import { cn } from "@/lib/utils"; -import { Loader2, Send, User, Sparkles } from "lucide-react"; -import { useState, useEffect, useRef } from "react"; -import { Streamdown } from "streamdown"; - -/** - * Message type matching server-side LLM Message interface - */ -export type Message = { - role: "system" | "user" | "assistant"; - content: string; -}; - -export type AIChatBoxProps = { - /** - * Messages array to display in the chat. - * Should match the format used by invokeLLM on the server. - */ - messages: Message[]; - - /** - * Callback when user sends a message. - * Typically you'll call a tRPC mutation here to invoke the LLM. - */ - onSendMessage: (content: string) => void; - - /** - * Whether the AI is currently generating a response - */ - isLoading?: boolean; - - /** - * Placeholder text for the input field - */ - placeholder?: string; - - /** - * Custom className for the container - */ - className?: string; - - /** - * Height of the chat box (default: 600px) - */ - height?: string | number; - - /** - * Empty state message to display when no messages - */ - emptyStateMessage?: string; - - /** - * Suggested prompts to display in empty state - * Click to send directly - */ - suggestedPrompts?: string[]; -}; - -/** - * A ready-to-use AI chat box component that integrates with the LLM system. - * - * Features: - * - Matches server-side Message interface for seamless integration - * - Markdown rendering with Streamdown - * - Auto-scrolls to latest message - * - Loading states - * - Uses global theme colors from index.css - * - * @example - * ```tsx - * const ChatPage = () => { - * const [messages, setMessages] = useState([ - * { role: "system", content: "You are a helpful assistant." } - * ]); - * - * const chatMutation = trpc.ai.chat.useMutation({ - * onSuccess: (response) => { - * // Assuming your tRPC endpoint returns the AI response as a string - * setMessages(prev => [...prev, { - * role: "assistant", - * content: response - * }]); - * }, - * onError: (error) => { - * console.error("Chat error:", error); - * // Optionally show error message to user - * } - * }); - * - * const handleSend = (content: string) => { - * const newMessages = [...messages, { role: "user", content }]; - * setMessages(newMessages); - * chatMutation.mutate({ messages: newMessages }); - * }; - * - * return ( - * - * ); - * }; - * ``` - */ -export function AIChatBox({ - messages, - onSendMessage, - isLoading = false, - placeholder = "Type your message...", - className, - height = "600px", - emptyStateMessage = "Start a conversation with AI", - suggestedPrompts, -}: AIChatBoxProps) { - const [input, setInput] = useState(""); - const scrollAreaRef = useRef(null); - const containerRef = useRef(null); - const inputAreaRef = useRef(null); - const textareaRef = useRef(null); - - // Filter out system messages - const displayMessages = messages.filter((msg) => msg.role !== "system"); - - // Calculate min-height for last assistant message to push user message to top - const [minHeightForLastMessage, setMinHeightForLastMessage] = useState(0); - - useEffect(() => { - if (containerRef.current && inputAreaRef.current) { - const containerHeight = containerRef.current.offsetHeight; - const inputHeight = inputAreaRef.current.offsetHeight; - const scrollAreaHeight = containerHeight - inputHeight; - - // Reserve space for: - // - padding (p-4 = 32px top+bottom) - // - user message: 40px (item height) + 16px (margin-top from space-y-4) = 56px - // Note: margin-bottom is not counted because it naturally pushes the assistant message down - const userMessageReservedHeight = 56; - const calculatedHeight = scrollAreaHeight - 32 - userMessageReservedHeight; - - setMinHeightForLastMessage(Math.max(0, calculatedHeight)); - } - }, []); - - // Scroll to bottom helper function with smooth animation - const scrollToBottom = () => { - const viewport = scrollAreaRef.current?.querySelector( - '[data-radix-scroll-area-viewport]' - ) as HTMLDivElement; - - if (viewport) { - requestAnimationFrame(() => { - viewport.scrollTo({ - top: viewport.scrollHeight, - behavior: 'smooth' - }); - }); - } - }; - - const handleSubmit = (e: React.FormEvent) => { - e.preventDefault(); - const trimmedInput = input.trim(); - if (!trimmedInput || isLoading) return; - - onSendMessage(trimmedInput); - setInput(""); - - // Scroll immediately after sending - scrollToBottom(); - - // Keep focus on input - textareaRef.current?.focus(); - }; - - const handleKeyDown = (e: React.KeyboardEvent) => { - if (e.key === "Enter" && !e.shiftKey) { - e.preventDefault(); - handleSubmit(e); - } - }; - - return ( -
- {/* Messages Area */} -
- {displayMessages.length === 0 ? ( -
-
-
- -

{emptyStateMessage}

-
- - {suggestedPrompts && suggestedPrompts.length > 0 && ( -
- {suggestedPrompts.map((prompt, index) => ( - - ))} -
- )} -
-
- ) : ( - -
- {displayMessages.map((message, index) => { - // Apply min-height to last message only if NOT loading (when loading, the loading indicator gets it) - const isLastMessage = index === displayMessages.length - 1; - const shouldApplyMinHeight = - isLastMessage && !isLoading && minHeightForLastMessage > 0; - - return ( -
- {message.role === "assistant" && ( -
- -
- )} - -
- {message.role === "assistant" ? ( -
- {message.content} -
- ) : ( -

- {message.content} -

- )} -
- - {message.role === "user" && ( -
- -
- )} -
- ); - })} - - {isLoading && ( -
0 - ? { minHeight: `${minHeightForLastMessage}px` } - : undefined - } - > -
- -
-
- -
-
- )} -
-
- )} -
- - {/* Input Area */} -
-