Checkpoint frontend UI/UX overhaul and ingestion embed benchmark work
This commit is contained in:
+185
-1
@@ -1,5 +1,189 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import type { 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";
|
||||
|
||||
const INITIAL_SESSIONS: ChatSession[] = [
|
||||
{
|
||||
id: "session-1",
|
||||
title: "Tra cứu liều dùng Paracetamol",
|
||||
updatedAt: new Date().toISOString(),
|
||||
},
|
||||
{
|
||||
id: "session-2",
|
||||
title: "Chống chỉ định Amoxicillin",
|
||||
updatedAt: new Date().toISOString(),
|
||||
},
|
||||
];
|
||||
|
||||
export default function ChatPage() {
|
||||
return <ChatPanel className="max-w-2xl" />;
|
||||
const [sessions, setSessions] = useState<ChatSession[]>(INITIAL_SESSIONS);
|
||||
const [currentSessionId, setCurrentSessionId] = useState<string>("session-1");
|
||||
const [queryOverride, setQueryOverride] = useState<string | undefined>();
|
||||
|
||||
// Citation & Evidence Panel State
|
||||
const [citations, setCitations] = useState<Citation[]>([]);
|
||||
const [activeCitationIndex, setActiveCitationIndex] = useState<number | null>(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 = `session-${Date.now()}`;
|
||||
const newSession: ChatSession = {
|
||||
id: newId,
|
||||
title: "Phiên tra cứu mới",
|
||||
updatedAt: new Date().toISOString(),
|
||||
};
|
||||
setSessions((prev) => [newSession, ...prev]);
|
||||
setCurrentSessionId(newId);
|
||||
setQueryOverride(undefined);
|
||||
setCitations([]);
|
||||
setActiveCitationIndex(null);
|
||||
setShowMobileSidebar(false);
|
||||
};
|
||||
|
||||
const handleSelectSession = (id: string) => {
|
||||
setCurrentSessionId(id);
|
||||
setQueryOverride(undefined);
|
||||
setShowMobileSidebar(false);
|
||||
};
|
||||
|
||||
const handleDeleteSession = (id: string) => {
|
||||
setSessions((prev) => prev.filter((s) => s.id !== id));
|
||||
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(query);
|
||||
setShowMobileSidebar(false);
|
||||
};
|
||||
|
||||
const handleCitationClick = (citation: Citation, index: number) => {
|
||||
setActiveCitationIndex(index);
|
||||
setShowMobileEvidence(true);
|
||||
};
|
||||
|
||||
const handleCitationsLoaded = (newCitations: Citation[]) => {
|
||||
setCitations(newCitations);
|
||||
if (newCitations.length > 0) {
|
||||
setActiveCitationIndex(1);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-1 w-full h-[calc(100vh-6.5rem)] overflow-hidden bg-app relative">
|
||||
{/* Mobile Header Bar Controls */}
|
||||
<div className="lg:hidden absolute top-2 left-3 right-3 z-30 flex items-center justify-between pointer-events-none">
|
||||
<button
|
||||
onClick={() => setShowMobileSidebar(true)}
|
||||
aria-label="Mở danh mục phiên"
|
||||
className="pointer-events-auto flex items-center gap-1.5 px-3 py-1.5 rounded-full border border-border-subtle bg-surface/90 text-txt-primary text-xs font-semibold shadow-elevated backdrop-blur-md"
|
||||
>
|
||||
<Menu className="w-4 h-4 text-accent-primary" />
|
||||
<span>Lịch sử</span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={() => setShowMobileEvidence(!showMobileEvidence)}
|
||||
aria-label="Mở thanh bằng chứng"
|
||||
className="pointer-events-auto flex items-center gap-1.5 px-3 py-1.5 rounded-full border border-border-subtle bg-surface/90 text-txt-primary text-xs font-semibold shadow-elevated backdrop-blur-md"
|
||||
>
|
||||
<FileSearch className="w-4 h-4 text-accent-primary" />
|
||||
<span>Bằng chứng ({citations.length})</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Desktop Region 1: Navigation / Session Sidebar */}
|
||||
<Sidebar
|
||||
currentSessionId={currentSessionId}
|
||||
sessions={sessions}
|
||||
onSelectSession={handleSelectSession}
|
||||
onNewChat={handleNewChat}
|
||||
onDeleteSession={handleDeleteSession}
|
||||
onQuickQuery={handleQuickQuery}
|
||||
className="hidden lg:flex"
|
||||
/>
|
||||
|
||||
{/* Mobile Drawer Region 1 */}
|
||||
{showMobileSidebar && (
|
||||
<div className="fixed inset-0 z-50 flex lg:hidden">
|
||||
<div
|
||||
className="fixed inset-0 bg-bg-overlay backdrop-blur-sm"
|
||||
onClick={() => setShowMobileSidebar(false)}
|
||||
/>
|
||||
<div className="relative flex flex-col w-4/5 max-w-sm h-full bg-surface z-10 shadow-elevated animate-slide-up">
|
||||
<Sidebar
|
||||
currentSessionId={currentSessionId}
|
||||
sessions={sessions}
|
||||
onSelectSession={handleSelectSession}
|
||||
onNewChat={handleNewChat}
|
||||
onDeleteSession={handleDeleteSession}
|
||||
onQuickQuery={handleQuickQuery}
|
||||
className="w-full h-full border-r-0"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Desktop Region 2: Primary Answer Workspace */}
|
||||
<main className="flex-1 flex justify-center overflow-hidden relative">
|
||||
<ChatPanel
|
||||
key={`${currentSessionId}-${queryOverride}`}
|
||||
sessionId={currentSessionId}
|
||||
initialQuery={queryOverride}
|
||||
onCitationClick={handleCitationClick}
|
||||
onCitationsLoaded={handleCitationsLoaded}
|
||||
activeCitationIndex={activeCitationIndex}
|
||||
className="w-full max-w-4xl h-full"
|
||||
/>
|
||||
</main>
|
||||
|
||||
{/* Desktop Region 3: Right Evidence & Source Inspector Panel */}
|
||||
{showEvidenceDesktop && (
|
||||
<EvidencePanel
|
||||
citations={citations}
|
||||
activeCitationIndex={activeCitationIndex}
|
||||
onSelectCitation={(citation, index) => setActiveCitationIndex(index)}
|
||||
onClose={() => setShowEvidenceDesktop(false)}
|
||||
className="hidden lg:flex"
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Mobile Sheet Region 3: Evidence Bottom Sheet / Drawer */}
|
||||
{showMobileEvidence && (
|
||||
<div className="fixed inset-0 z-50 flex flex-col justify-end lg:hidden">
|
||||
<div
|
||||
className="fixed inset-0 bg-bg-overlay backdrop-blur-sm"
|
||||
onClick={() => setShowMobileEvidence(false)}
|
||||
/>
|
||||
<div className="relative flex flex-col w-full h-4/5 bg-surface rounded-t-3xl z-10 shadow-elevated overflow-hidden animate-slide-up">
|
||||
<EvidencePanel
|
||||
citations={citations}
|
||||
activeCitationIndex={activeCitationIndex}
|
||||
onSelectCitation={(citation, index) => {
|
||||
setActiveCitationIndex(index);
|
||||
}}
|
||||
onClose={() => setShowMobileEvidence(false)}
|
||||
className="w-full h-full border-l-0"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user