mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-06-06 20:15:17 +02:00
Merge pull request #872 from MODSetter/dev
refactor: fixed firefox rendering issues
This commit is contained in:
commit
c74553219e
6 changed files with 55 additions and 279 deletions
|
|
@ -276,7 +276,7 @@ export default function NewChatPage() {
|
||||||
const initializeThread = useCallback(async () => {
|
const initializeThread = useCallback(async () => {
|
||||||
setIsInitializing(true);
|
setIsInitializing(true);
|
||||||
|
|
||||||
// Reset all state when switching between chats to prevent stale data
|
// Reset all state when switching between chats/search spaces to prevent stale data
|
||||||
setMessages([]);
|
setMessages([]);
|
||||||
setThreadId(null);
|
setThreadId(null);
|
||||||
setCurrentThread(null);
|
setCurrentThread(null);
|
||||||
|
|
@ -284,8 +284,8 @@ export default function NewChatPage() {
|
||||||
setMentionedDocuments([]);
|
setMentionedDocuments([]);
|
||||||
setSidebarDocuments([]);
|
setSidebarDocuments([]);
|
||||||
setMessageDocumentsMap({});
|
setMessageDocumentsMap({});
|
||||||
clearPlanOwnerRegistry(); // Reset plan ownership for new chat
|
clearPlanOwnerRegistry();
|
||||||
closeReportPanel(); // Close report panel when switching chats
|
closeReportPanel();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (urlChatId > 0) {
|
if (urlChatId > 0) {
|
||||||
|
|
@ -346,6 +346,7 @@ export default function NewChatPage() {
|
||||||
}
|
}
|
||||||
}, [
|
}, [
|
||||||
urlChatId,
|
urlChatId,
|
||||||
|
searchSpaceId,
|
||||||
setMessageDocumentsMap,
|
setMessageDocumentsMap,
|
||||||
setMentionedDocuments,
|
setMentionedDocuments,
|
||||||
setSidebarDocuments,
|
setSidebarDocuments,
|
||||||
|
|
@ -1671,7 +1672,7 @@ export default function NewChatPage() {
|
||||||
<DeleteGoogleDriveFileToolUI />
|
<DeleteGoogleDriveFileToolUI />
|
||||||
<SandboxExecuteToolUI />
|
<SandboxExecuteToolUI />
|
||||||
{/* <WriteTodosToolUI /> Disabled for now */}
|
{/* <WriteTodosToolUI /> Disabled for now */}
|
||||||
<div className="flex h-[calc(100dvh-64px)] overflow-hidden">
|
<div key={searchSpaceId} className="flex h-[calc(100dvh-64px)] overflow-hidden">
|
||||||
<div className="flex-1 flex flex-col min-w-0 overflow-hidden">
|
<div className="flex-1 flex flex-col min-w-0 overflow-hidden">
|
||||||
<Thread messageThinkingSteps={messageThinkingSteps} />
|
<Thread messageThinkingSteps={messageThinkingSteps} />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -220,14 +220,12 @@ const ThreadWelcome: FC = () => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="aui-thread-welcome-root mx-auto flex w-full max-w-(--thread-max-width) grow flex-col items-center px-4 relative">
|
<div className="aui-thread-welcome-root mx-auto flex w-full max-w-(--thread-max-width) grow flex-col items-center px-4 relative">
|
||||||
{/* Greeting positioned above the composer - fixed position */}
|
{/* Greeting positioned above the composer */}
|
||||||
<div className="aui-thread-welcome-message absolute bottom-[calc(50%+5rem)] left-0 right-0 flex flex-col items-center text-center">
|
<div className="aui-thread-welcome-message absolute bottom-[calc(50%+5rem)] left-0 right-0 flex flex-col items-center text-center">
|
||||||
<h1 className="aui-thread-welcome-message-inner fade-in slide-in-from-bottom-2 animate-in text-3xl md:text-5xl delay-100 duration-500 ease-out fill-mode-both">
|
<h1 className="aui-thread-welcome-message-inner text-3xl md:text-5xl">{greeting}</h1>
|
||||||
{greeting}
|
|
||||||
</h1>
|
|
||||||
</div>
|
</div>
|
||||||
{/* Composer - top edge fixed, expands downward only */}
|
{/* Composer - top edge fixed, expands downward only */}
|
||||||
<div className="fade-in slide-in-from-bottom-3 animate-in delay-200 duration-500 ease-out fill-mode-both w-full flex items-start justify-center absolute top-[calc(50%-3.5rem)] left-0 right-0">
|
<div className="w-full flex items-start justify-center absolute top-[calc(50%-3.5rem)] left-0 right-0">
|
||||||
<Composer />
|
<Composer />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,174 +1,10 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { IconBrandGithub } from "@tabler/icons-react";
|
import { IconBrandGithub } from "@tabler/icons-react";
|
||||||
import { StarIcon } from "lucide-react";
|
import { motion, useMotionValue, useSpring } from "motion/react";
|
||||||
import type { HTMLMotionProps, UseInViewOptions } from "motion/react";
|
|
||||||
import {
|
|
||||||
AnimatePresence,
|
|
||||||
motion,
|
|
||||||
useInView,
|
|
||||||
useMotionValue,
|
|
||||||
useSpring,
|
|
||||||
useTransform,
|
|
||||||
} from "motion/react";
|
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// Utilities
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
function getStrictContext<T>(name?: string) {
|
|
||||||
const Context = React.createContext<T | undefined>(undefined);
|
|
||||||
const Provider = ({ value, children }: { value: T; children?: React.ReactNode }) => (
|
|
||||||
<Context.Provider value={value}>{children}</Context.Provider>
|
|
||||||
);
|
|
||||||
const useSafeContext = () => {
|
|
||||||
const ctx = React.useContext(Context);
|
|
||||||
if (ctx === undefined) {
|
|
||||||
throw new Error(`useContext must be used within ${name ?? "a Provider"}`);
|
|
||||||
}
|
|
||||||
return ctx;
|
|
||||||
};
|
|
||||||
return [Provider, useSafeContext] as const;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface UseIsInViewOptions {
|
|
||||||
inView?: boolean;
|
|
||||||
inViewOnce?: boolean;
|
|
||||||
inViewMargin?: UseInViewOptions["margin"];
|
|
||||||
}
|
|
||||||
|
|
||||||
function useIsInView<T extends HTMLElement = HTMLElement>(
|
|
||||||
ref: React.Ref<T>,
|
|
||||||
options: UseIsInViewOptions = {}
|
|
||||||
) {
|
|
||||||
const { inView, inViewOnce = false, inViewMargin = "0px" } = options;
|
|
||||||
const localRef = React.useRef<T>(null);
|
|
||||||
React.useImperativeHandle(ref, () => localRef.current as T);
|
|
||||||
const inViewResult = useInView(localRef, {
|
|
||||||
once: inViewOnce,
|
|
||||||
margin: inViewMargin,
|
|
||||||
});
|
|
||||||
const isInView = !inView || inViewResult;
|
|
||||||
return { ref: localRef, isInView };
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// Particles (for star burst effect on completion)
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
type ParticlesContextType = { animate: boolean; isInView: boolean };
|
|
||||||
const [ParticlesProvider, useParticles] =
|
|
||||||
getStrictContext<ParticlesContextType>("ParticlesContext");
|
|
||||||
|
|
||||||
function Particles({
|
|
||||||
ref,
|
|
||||||
animate = true,
|
|
||||||
inView = false,
|
|
||||||
inViewMargin = "0px",
|
|
||||||
inViewOnce = true,
|
|
||||||
children,
|
|
||||||
style,
|
|
||||||
...props
|
|
||||||
}: Omit<HTMLMotionProps<"div">, "children"> & {
|
|
||||||
animate?: boolean;
|
|
||||||
children: React.ReactNode;
|
|
||||||
} & UseIsInViewOptions) {
|
|
||||||
const { ref: localRef, isInView } = useIsInView(ref as React.Ref<HTMLDivElement>, {
|
|
||||||
inView,
|
|
||||||
inViewOnce,
|
|
||||||
inViewMargin,
|
|
||||||
});
|
|
||||||
return (
|
|
||||||
<ParticlesProvider value={{ animate, isInView }}>
|
|
||||||
<motion.div ref={localRef} style={{ position: "relative", ...style }} {...props}>
|
|
||||||
{children}
|
|
||||||
</motion.div>
|
|
||||||
</ParticlesProvider>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function ParticlesEffect({
|
|
||||||
side = "top",
|
|
||||||
align = "center",
|
|
||||||
count = 6,
|
|
||||||
radius = 30,
|
|
||||||
spread = 360,
|
|
||||||
duration = 0.8,
|
|
||||||
holdDelay = 0.05,
|
|
||||||
sideOffset = 0,
|
|
||||||
alignOffset = 0,
|
|
||||||
delay = 0,
|
|
||||||
transition,
|
|
||||||
style,
|
|
||||||
...props
|
|
||||||
}: Omit<HTMLMotionProps<"div">, "children"> & {
|
|
||||||
side?: "top" | "bottom" | "left" | "right";
|
|
||||||
align?: "start" | "center" | "end";
|
|
||||||
count?: number;
|
|
||||||
radius?: number;
|
|
||||||
spread?: number;
|
|
||||||
duration?: number;
|
|
||||||
holdDelay?: number;
|
|
||||||
sideOffset?: number;
|
|
||||||
alignOffset?: number;
|
|
||||||
delay?: number;
|
|
||||||
}) {
|
|
||||||
const { animate, isInView } = useParticles();
|
|
||||||
const isVertical = side === "top" || side === "bottom";
|
|
||||||
const alignPct = align === "start" ? "0%" : align === "end" ? "100%" : "50%";
|
|
||||||
|
|
||||||
const top = isVertical
|
|
||||||
? side === "top"
|
|
||||||
? `calc(0% - ${sideOffset}px)`
|
|
||||||
: `calc(100% + ${sideOffset}px)`
|
|
||||||
: `calc(${alignPct} + ${alignOffset}px)`;
|
|
||||||
const left = isVertical
|
|
||||||
? `calc(${alignPct} + ${alignOffset}px)`
|
|
||||||
: side === "left"
|
|
||||||
? `calc(0% - ${sideOffset}px)`
|
|
||||||
: `calc(100% + ${sideOffset}px)`;
|
|
||||||
|
|
||||||
const containerStyle: React.CSSProperties = {
|
|
||||||
position: "absolute",
|
|
||||||
top,
|
|
||||||
left,
|
|
||||||
transform: "translate(-50%, -50%)",
|
|
||||||
};
|
|
||||||
const angleStep = (spread * (Math.PI / 180)) / Math.max(1, count - 1);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<AnimatePresence>
|
|
||||||
{animate &&
|
|
||||||
isInView &&
|
|
||||||
[...Array(count)].map((_, i) => {
|
|
||||||
const angle = i * angleStep;
|
|
||||||
const x = Math.cos(angle) * radius;
|
|
||||||
const y = Math.sin(angle) * radius;
|
|
||||||
return (
|
|
||||||
<motion.div
|
|
||||||
key={`particle-${angle}`}
|
|
||||||
style={{ ...containerStyle, ...style }}
|
|
||||||
initial={{ scale: 0, opacity: 0 }}
|
|
||||||
animate={{
|
|
||||||
x: `${x}px`,
|
|
||||||
y: `${y}px`,
|
|
||||||
scale: [0, 1, 0],
|
|
||||||
opacity: [0, 1, 0],
|
|
||||||
}}
|
|
||||||
transition={{
|
|
||||||
duration,
|
|
||||||
delay: delay + i * holdDelay,
|
|
||||||
ease: "easeOut",
|
|
||||||
...transition,
|
|
||||||
}}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</AnimatePresence>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Per-digit scrolling wheel
|
// Per-digit scrolling wheel
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
@ -409,11 +245,6 @@ function NavbarGitHubStars({
|
||||||
}: NavbarGitHubStarsProps) {
|
}: NavbarGitHubStarsProps) {
|
||||||
const [stars, setStars] = React.useState(0);
|
const [stars, setStars] = React.useState(0);
|
||||||
const [isLoading, setIsLoading] = React.useState(true);
|
const [isLoading, setIsLoading] = React.useState(true);
|
||||||
const [isCompleted, setIsCompleted] = React.useState(false);
|
|
||||||
|
|
||||||
const fillRaw = useMotionValue(0);
|
|
||||||
const fillSpring = useSpring(fillRaw, { stiffness: 12, damping: 14 });
|
|
||||||
const clipPath = useTransform(fillSpring, (v) => `inset(${100 - v * 100}% 0 0 0)`);
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
const abortController = new AbortController();
|
const abortController = new AbortController();
|
||||||
|
|
@ -424,7 +255,6 @@ function NavbarGitHubStars({
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
if (data && typeof data.stargazers_count === "number") {
|
if (data && typeof data.stargazers_count === "number") {
|
||||||
setStars(data.stargazers_count);
|
setStars(data.stargazers_count);
|
||||||
fillRaw.set(1);
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
|
|
@ -434,7 +264,7 @@ function NavbarGitHubStars({
|
||||||
})
|
})
|
||||||
.finally(() => setIsLoading(false));
|
.finally(() => setIsLoading(false));
|
||||||
return () => abortController.abort();
|
return () => abortController.abort();
|
||||||
}, [username, repo, fillRaw]);
|
}, [username, repo]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<a
|
<a
|
||||||
|
|
@ -442,38 +272,17 @@ function NavbarGitHubStars({
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className={cn(
|
className={cn(
|
||||||
"group flex items-center gap-2 rounded-full px-3 py-1.5 transition-colors",
|
"group flex items-center gap-1.5 rounded-full px-3 py-1.5 hover:bg-gray-100 dark:hover:bg-neutral-800 transition-colors",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<IconBrandGithub className="h-5 w-5 text-neutral-600 dark:text-neutral-300 shrink-0" />
|
<IconBrandGithub className="h-5 w-5 text-neutral-600 dark:text-neutral-300 shrink-0" />
|
||||||
<div className="flex items-center gap-1 rounded-md bg-neutral-100 dark:bg-neutral-800 group-hover:bg-neutral-200 dark:group-hover:bg-neutral-700 px-2 py-0.5 transition-colors">
|
<AnimatedStarCount
|
||||||
<AnimatedStarCount
|
value={isLoading ? 10000 : stars}
|
||||||
value={isLoading ? 10000 : stars}
|
itemSize={ITEM_SIZE}
|
||||||
itemSize={ITEM_SIZE}
|
isRolling={isLoading}
|
||||||
isRolling={isLoading}
|
className="text-sm font-semibold tabular-nums text-neutral-600 dark:text-neutral-400 group-hover:text-neutral-800 dark:group-hover:text-neutral-200 transition-colors"
|
||||||
className="text-sm font-semibold tabular-nums text-neutral-500 dark:text-neutral-400 group-hover:text-neutral-800 dark:group-hover:text-neutral-200 transition-colors"
|
/>
|
||||||
onComplete={() => setIsCompleted(true)}
|
|
||||||
/>
|
|
||||||
<Particles animate={isCompleted}>
|
|
||||||
<div className="relative size-4">
|
|
||||||
<StarIcon
|
|
||||||
aria-hidden="true"
|
|
||||||
className="absolute inset-0 size-4 fill-neutral-400 stroke-neutral-400 dark:fill-neutral-700 dark:stroke-neutral-700 group-hover:fill-neutral-600 group-hover:stroke-neutral-600 dark:group-hover:fill-neutral-300 dark:group-hover:stroke-neutral-300 transition-colors"
|
|
||||||
/>
|
|
||||||
<motion.div className="absolute inset-0" style={{ clipPath }}>
|
|
||||||
<StarIcon
|
|
||||||
aria-hidden="true"
|
|
||||||
className="size-4 fill-neutral-300 stroke-neutral-300 dark:fill-neutral-400 dark:stroke-neutral-400 group-hover:fill-neutral-500 group-hover:stroke-neutral-500 dark:group-hover:fill-neutral-200 dark:group-hover:stroke-neutral-200 transition-colors"
|
|
||||||
/>
|
|
||||||
</motion.div>
|
|
||||||
</div>
|
|
||||||
<ParticlesEffect
|
|
||||||
delay={0.3}
|
|
||||||
className="size-1 rounded-full bg-neutral-300 dark:bg-neutral-400"
|
|
||||||
/>
|
|
||||||
</Particles>
|
|
||||||
</div>
|
|
||||||
</a>
|
</a>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -262,6 +262,15 @@ export function LayoutDataProvider({ searchSpaceId, children }: LayoutDataProvid
|
||||||
}
|
}
|
||||||
}, [pendingNewChat, params?.chat_id, router, searchSpaceId, resetCurrentThread]);
|
}, [pendingNewChat, params?.chat_id, router, searchSpaceId, resetCurrentThread]);
|
||||||
|
|
||||||
|
// Reset transient slide-out panels when switching search spaces.
|
||||||
|
// Some browsers can retain overlay/backdrop state across route transitions.
|
||||||
|
useEffect(() => {
|
||||||
|
setIsInboxSidebarOpen(false);
|
||||||
|
setIsAllSharedChatsSidebarOpen(false);
|
||||||
|
setIsAllPrivateChatsSidebarOpen(false);
|
||||||
|
setIsAnnouncementsSidebarOpen(false);
|
||||||
|
}, [searchSpaceId]);
|
||||||
|
|
||||||
const searchSpaces: SearchSpace[] = useMemo(() => {
|
const searchSpaces: SearchSpace[] = useMemo(() => {
|
||||||
if (!searchSpacesData || !Array.isArray(searchSpacesData)) return [];
|
if (!searchSpacesData || !Array.isArray(searchSpacesData)) return [];
|
||||||
return searchSpacesData.map((space) => ({
|
return searchSpacesData.map((space) => ({
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
import { useAtom, useAtomValue, useSetAtom } from "jotai";
|
import { useAtom, useAtomValue, useSetAtom } from "jotai";
|
||||||
import { PanelRight, PanelRightClose } from "lucide-react";
|
import { PanelRight, PanelRightClose } from "lucide-react";
|
||||||
import { AnimatePresence, motion } from "motion/react";
|
|
||||||
import { startTransition, useEffect } from "react";
|
import { startTransition, useEffect } from "react";
|
||||||
import { closeReportPanelAtom, reportPanelAtom } from "@/atoms/chat/report-panel.atom";
|
import { closeReportPanelAtom, reportPanelAtom } from "@/atoms/chat/report-panel.atom";
|
||||||
import { documentsSidebarOpenAtom } from "@/atoms/documents/ui.atoms";
|
import { documentsSidebarOpenAtom } from "@/atoms/documents/ui.atoms";
|
||||||
|
|
@ -99,70 +98,35 @@ export function RightPanel({ documentsPanel }: RightPanelProps) {
|
||||||
const targetWidth = PANEL_WIDTHS[effectiveTab];
|
const targetWidth = PANEL_WIDTHS[effectiveTab];
|
||||||
const collapseButton = <CollapseButton onClick={() => setCollapsed(true)} />;
|
const collapseButton = <CollapseButton onClick={() => setCollapsed(true)} />;
|
||||||
|
|
||||||
const contentKey =
|
if (!isVisible) return null;
|
||||||
effectiveTab === "sources" && documentsOpen
|
|
||||||
? "sources"
|
|
||||||
: effectiveTab === "report" && reportOpen
|
|
||||||
? "report"
|
|
||||||
: null;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AnimatePresence>
|
<aside
|
||||||
{isVisible && (
|
style={{ width: targetWidth }}
|
||||||
<motion.aside
|
className="flex h-full shrink-0 flex-col border-l bg-background overflow-hidden transition-[width] duration-200 ease-out"
|
||||||
key="right-panel"
|
>
|
||||||
initial={{ width: 0, opacity: 0 }}
|
<div className="relative flex-1 min-h-0 overflow-hidden">
|
||||||
animate={{ width: targetWidth, opacity: 1 }}
|
{effectiveTab === "sources" && documentsOpen && documentsPanel && (
|
||||||
exit={{ width: 0, opacity: 0 }}
|
<div className="h-full">
|
||||||
transition={{
|
<DocumentsSidebar
|
||||||
width: { type: "spring", stiffness: 400, damping: 35, mass: 0.8 },
|
open={documentsPanel.open}
|
||||||
opacity: { duration: 0.2, ease: "easeOut" },
|
onOpenChange={documentsPanel.onOpenChange}
|
||||||
}}
|
embedded
|
||||||
style={{ willChange: "width, opacity", contain: "layout style" }}
|
headerAction={collapseButton}
|
||||||
className="flex h-full shrink-0 flex-col border-l bg-background overflow-hidden"
|
/>
|
||||||
>
|
|
||||||
<div className="relative flex-1 min-h-0 overflow-hidden">
|
|
||||||
<AnimatePresence mode="popLayout" initial={false}>
|
|
||||||
{contentKey === "sources" && documentsPanel && (
|
|
||||||
<motion.div
|
|
||||||
key="sources"
|
|
||||||
initial={{ opacity: 0, x: 8 }}
|
|
||||||
animate={{ opacity: 1, x: 0 }}
|
|
||||||
exit={{ opacity: 0, x: -8 }}
|
|
||||||
transition={{ duration: 0.15, ease: "easeOut" }}
|
|
||||||
className="h-full"
|
|
||||||
>
|
|
||||||
<DocumentsSidebar
|
|
||||||
open={documentsPanel.open}
|
|
||||||
onOpenChange={documentsPanel.onOpenChange}
|
|
||||||
embedded
|
|
||||||
headerAction={collapseButton}
|
|
||||||
/>
|
|
||||||
</motion.div>
|
|
||||||
)}
|
|
||||||
{contentKey === "report" && (
|
|
||||||
<motion.div
|
|
||||||
key="report"
|
|
||||||
initial={{ opacity: 0, x: 8 }}
|
|
||||||
animate={{ opacity: 1, x: 0 }}
|
|
||||||
exit={{ opacity: 0, x: -8 }}
|
|
||||||
transition={{ duration: 0.15, ease: "easeOut" }}
|
|
||||||
className="h-full"
|
|
||||||
>
|
|
||||||
<div className="flex h-full flex-col">
|
|
||||||
<ReportPanelContent
|
|
||||||
reportId={reportState.reportId!}
|
|
||||||
title={reportState.title || "Report"}
|
|
||||||
onClose={closeReport}
|
|
||||||
shareToken={reportState.shareToken}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</motion.div>
|
|
||||||
)}
|
|
||||||
</AnimatePresence>
|
|
||||||
</div>
|
</div>
|
||||||
</motion.aside>
|
)}
|
||||||
)}
|
{effectiveTab === "report" && reportOpen && (
|
||||||
</AnimatePresence>
|
<div className="h-full">
|
||||||
|
<ReportPanelContent
|
||||||
|
reportId={reportState.reportId!}
|
||||||
|
title={reportState.title || "Report"}
|
||||||
|
onClose={closeReport}
|
||||||
|
shareToken={reportState.shareToken}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</aside>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { motion } from "motion/react";
|
|
||||||
import { useMemo, useState } from "react";
|
import { useMemo, useState } from "react";
|
||||||
import { TooltipProvider } from "@/components/ui/tooltip";
|
import { TooltipProvider } from "@/components/ui/tooltip";
|
||||||
import type { InboxItem } from "@/hooks/use-inbox";
|
import type { InboxItem } from "@/hooks/use-inbox";
|
||||||
|
|
@ -308,17 +307,13 @@ export function LayoutShell({
|
||||||
isResizing={isResizing}
|
isResizing={isResizing}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<motion.main
|
<main className="flex-1 flex flex-col min-w-0">
|
||||||
layout={isResizing ? false : "position"}
|
|
||||||
style={{ contain: "inline-size" }}
|
|
||||||
className="flex-1 flex flex-col min-w-0"
|
|
||||||
>
|
|
||||||
<Header />
|
<Header />
|
||||||
|
|
||||||
<div className={cn("flex-1", isChatPage ? "overflow-hidden" : "overflow-auto")}>
|
<div className={cn("flex-1", isChatPage ? "overflow-hidden" : "overflow-auto")}>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
</motion.main>
|
</main>
|
||||||
|
|
||||||
{/* Right panel — tabbed Sources/Report (desktop only) */}
|
{/* Right panel — tabbed Sources/Report (desktop only) */}
|
||||||
{documentsPanel && (
|
{documentsPanel && (
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue