d2l-ui / components /chat /AppShell.tsx
Berkkirik's picture
feat: streaming + multi-turn + view source + doc-scoped + UX fixes
df790cc
"use client";
import { Sidebar } from "./Sidebar";
import { TopBar } from "./TopBar";
import { Aurora } from "./Aurora";
import { useChatStore } from "@/lib/chatStore";
import { useBackendSync } from "@/lib/useBackendSync";
import clsx from "clsx";
import { usePathname } from "next/navigation";
import { useEffect } from "react";
export function AppShell({ children }: { children: React.ReactNode }) {
const sidebarOpen = useChatStore((s) => s.sidebarOpen);
const hydrated = useChatStore((s) => s.hydrated);
const pathname = usePathname();
const conversations = useChatStore((s) => s.conversations);
const activeId = useChatStore((s) => s.activeId);
const newConversation = useChatStore((s) => s.newConversation);
// Backend hydration + auto-save (debounced PUT to /conversations)
useBackendSync();
// Ensure there is always at least one conversation when on chat route.
// Wait until hydration completes so we don't create a placeholder before
// the remote state arrives (which would cause a flash + redundant save).
useEffect(() => {
if (pathname !== "/") return;
if (!hydrated) return;
const hasAny = Object.keys(conversations).length > 0;
const hasActive = activeId && conversations[activeId];
if (!hasAny || !hasActive) newConversation();
}, [pathname, hydrated, conversations, activeId, newConversation]);
return (
<div className="relative h-screen flex flex-col overflow-hidden">
{/* Atmospheric background — fixed, behind everything */}
<Aurora />
<TopBar />
<div className="relative z-10 flex-1 flex overflow-hidden min-h-0">
<Sidebar />
<main
className={clsx(
"flex-1 flex flex-col min-w-0 min-h-0 transition-[margin] duration-300 ease-atelier",
sidebarOpen ? "md:ml-0" : "md:ml-0"
)}
>
{children}
</main>
</div>
</div>
);
}