"use client"; import { useEffect, useState } from "react"; import type { ChatMessage, Citation } from "@duoc-thu/shared-types"; import { ChatPanel } from "./_components/ChatPanel"; import { Sidebar, ChatSession } from "./_components/Sidebar"; import { EvidencePanel } from "./_components/EvidencePanel"; import { MessageSquare, FileSearch, Menu, X, Layers } from "lucide-react"; import { cn } from "@duoc-thu/ui"; function createSessionId() { return `session-${crypto.randomUUID()}`; } export default function ChatPage() { const [sessions, setSessions] = useState([]); const [currentSessionId, setCurrentSessionId] = useState(""); const [messagesBySession, setMessagesBySession] = useState>({}); const [queryOverride, setQueryOverride] = useState<{ text: string; token: number } | null>(null); useEffect(() => { const id = createSessionId(); setSessions([{ id, title: "Phiên tra cứu mới", updatedAt: new Date().toISOString() }]); setMessagesBySession({ [id]: [] }); setCurrentSessionId(id); }, []); // Citation & Evidence Panel State const [citations, setCitations] = useState([]); const [activeCitationIndex, setActiveCitationIndex] = useState(null); // Responsive Drawer Toggles for Mobile/Tablet const [showMobileSidebar, setShowMobileSidebar] = useState(false); const [showMobileEvidence, setShowMobileEvidence] = useState(false); const [showEvidenceDesktop, setShowEvidenceDesktop] = useState(true); const handleNewChat = () => { const newId = createSessionId(); const newSession: ChatSession = { id: newId, title: "Phiên tra cứu mới", updatedAt: new Date().toISOString(), }; setSessions((prev) => [newSession, ...prev]); setMessagesBySession((prev) => ({ ...prev, [newId]: [] })); setCurrentSessionId(newId); setQueryOverride(null); setCitations([]); setActiveCitationIndex(null); setShowMobileSidebar(false); }; const handleSelectSession = (id: string) => { setCurrentSessionId(id); setQueryOverride(null); setCitations([]); setActiveCitationIndex(null); setShowMobileSidebar(false); }; const handleDeleteSession = (id: string) => { setSessions((prev) => prev.filter((s) => s.id !== id)); setMessagesBySession((prev) => { const next = { ...prev }; delete next[id]; return next; }); if (currentSessionId === id) { const remaining = sessions.filter((s) => s.id !== id); if (remaining.length > 0) { setCurrentSessionId(remaining[0].id); } else { handleNewChat(); } } }; const handleQuickQuery = (query: string) => { setQueryOverride({ text: query, token: Date.now() }); setShowMobileSidebar(false); }; const currentMessages = messagesBySession[currentSessionId] ?? []; const setCurrentMessages: React.Dispatch> = (update) => { const sessionId = currentSessionId; setMessagesBySession((prev) => { const existing = prev[sessionId] ?? []; const next = typeof update === "function" ? update(existing) : update; return { ...prev, [sessionId]: next }; }); }; if (!currentSessionId) { return
Đang tạo phiên tra cứu an toàn...
; } const handleCitationClick = (citation: Citation, index: number, allCitations: Citation[]) => { // Found live 2026-08-07: this used to only set the index into whatever // `citations` array was last loaded (i.e. the MOST RECENT answer's), so // clicking [1] on an older message showed a LATER message's unrelated // drug in the evidence panel (reported live: clicking Omeprazol's own // citation showed Kanamycin). The clicked message's own citation list // must replace the panel's state, not just the index into a stale one. setCitations(allCitations); setActiveCitationIndex(index); setShowMobileEvidence(true); }; const handleCitationsLoaded = (newCitations: Citation[]) => { setCitations(newCitations); // Deliberately NOT auto-activating citation 1 here (removed // 2026-08-07): this used to fire the beam connector line + card // highlight on every single answer, unprompted, and — since // `CitationBeamOverlay` only recomputes its coordinates on window // resize/scroll, not on the content reflow a just-arrived answer // itself causes — the line frequently ended up pointing at stale // positions, i.e. exactly the "dây trích dẫn dính lung tung" (messy // citation wire) reported live. The beam/highlight now only appears // when the user actually clicks a citation, at which point the // coordinates are computed fresh. }; return (
{/* Mobile Header Bar Controls */}
{/* Desktop Region 1: Navigation / Session Sidebar */} {/* Mobile Drawer Region 1 */} {showMobileSidebar && (
setShowMobileSidebar(false)} />
)} {/* Desktop Region 2: Primary Answer Workspace */}
{/* Desktop Region 3: Right Evidence & Source Inspector Panel */} {showEvidenceDesktop && ( setActiveCitationIndex(index)} onClose={() => setShowEvidenceDesktop(false)} className="hidden lg:flex" /> )} {/* Mobile Sheet Region 3: Evidence Bottom Sheet / Drawer */} {showMobileEvidence && (
setShowMobileEvidence(false)} />
{ setActiveCitationIndex(index); }} onClose={() => setShowMobileEvidence(false)} className="w-full h-full border-l-0" />
)}
); }