增加AI页面在其它页面的展示

This commit is contained in:
allen2fuc
2026-05-08 18:15:52 +08:00
parent 9817ecca33
commit 68f06344dc
9 changed files with 334 additions and 15 deletions
+47 -6
View File
@@ -6,6 +6,8 @@
- Inactive: muted icon + label
============================================================ */
import { cn } from "@/lib/utils";
import { useAIDrawer } from "@/contexts/AIDrawerContext";
import { useIsLgUp } from "@/hooks/useIsLgUp";
import {
Activity,
Bot,
@@ -31,6 +33,8 @@ const NAV_ITEMS: NavItem[] = [
export default function BottomNav() {
const [location] = useLocation();
const isLgUp = useIsLgUp();
const { open: aiDrawerOpen, setOpen: setAiDrawerOpen } = useAIDrawer();
return (
<nav
@@ -46,14 +50,16 @@ export default function BottomNav() {
>
<div className="flex items-stretch justify-around">
{NAV_ITEMS.map((item) => {
const isActive = location === item.path ||
(item.path === "/" && location === "/") ||
(item.path !== "/" && location.startsWith(item.path));
const isActive =
item.path === "/ai"
? location === "/ai" || (isLgUp && aiDrawerOpen)
: location === item.path ||
(item.path === "/" && location === "/") ||
(item.path !== "/" && location.startsWith(item.path));
const Icon = item.icon;
return (
<Link key={item.path} href={item.path}>
<div className="flex flex-col items-center justify-center relative pt-2.5 pb-1.5 px-3">
const inner = (
<div className="flex flex-col items-center justify-center relative pt-2.5 pb-1.5 px-3">
{/* Active indicator bar */}
<div
className="absolute top-0 left-1/2 -translate-x-1/2 rounded-b-full transition-all duration-300"
@@ -99,6 +105,41 @@ export default function BottomNav() {
{item.label}
</span>
</div>
);
if (item.path === "/ai") {
if (!isLgUp) {
return (
<Link key={item.path} href={item.path}>
{inner}
</Link>
);
}
return (
<div
key={item.path}
role="button"
tabIndex={0}
className="cursor-pointer select-none"
onClick={() => {
if (location === "/ai") return;
setAiDrawerOpen((o) => !o);
}}
onKeyDown={(e) => {
if (e.key !== "Enter" && e.key !== " ") return;
e.preventDefault();
if (location === "/ai") return;
setAiDrawerOpen((o) => !o);
}}
>
{inner}
</div>
);
}
return (
<Link key={item.path} href={item.path}>
{inner}
</Link>
);
})}
+121
View File
@@ -0,0 +1,121 @@
import AIPage from "@/pages/AIPage";
import { useAIDrawer } from "@/contexts/AIDrawerContext";
import { useIsLgUp } from "@/hooks/useIsLgUp";
import { cn } from "@/lib/utils";
import { useLocation } from "wouter";
import {
useCallback,
useEffect,
useRef,
type ReactNode,
type RefObject,
} from "react";
function SplitDivider({
containerRef,
onResize,
}: {
containerRef: RefObject<HTMLDivElement | null>;
onResize: (aiPercent: number) => void;
}) {
const dragging = useRef(false);
const onMove = useCallback(
(e: MouseEvent) => {
if (!dragging.current || !containerRef.current) return;
const r = containerRef.current.getBoundingClientRect();
const w = r.width;
if (w <= 0) return;
const aiPct = ((r.right - e.clientX) / w) * 100;
onResize(aiPct);
},
[containerRef, onResize],
);
useEffect(() => {
const onUp = () => {
dragging.current = false;
};
window.addEventListener("mouseup", onUp);
window.addEventListener("mousemove", onMove);
return () => {
window.removeEventListener("mouseup", onUp);
window.removeEventListener("mousemove", onMove);
};
}, [onMove]);
return (
<div
className={cn(
"group relative z-10 w-2 shrink-0 cursor-col-resize bg-transparent",
"hover:bg-[#00FFF6]/10",
)}
onMouseDown={() => {
dragging.current = true;
}}
role="separator"
aria-orientation="vertical"
aria-label="Resize panels"
>
<div className="absolute inset-y-0 left-1/2 w-px -translate-x-1/2 bg-white/15 transition-colors group-hover:bg-[#00FFF6]/45" />
</div>
);
}
/**
* lg+: optional side-by-side AI panel with draggable divider. Mobile unchanged (router only).
*/
export default function DesktopAIShell({ children }: { children: ReactNode }) {
const isLgUp = useIsLgUp();
const [location] = useLocation();
const { open, setOpen, splitPercent, setSplitPercent } = useAIDrawer();
const containerRef = useRef<HTMLDivElement>(null);
const showSplit = Boolean(isLgUp && open && location !== "/ai");
useEffect(() => {
if (location === "/ai" && isLgUp) setOpen(false);
}, [location, isLgUp, setOpen]);
return (
<div
ref={containerRef}
className={cn(
"w-full min-w-0",
showSplit
? "flex h-dvh max-h-dvh flex-row overflow-hidden"
: "min-h-dvh",
)}
>
<div
className={cn(
"min-w-0",
showSplit
? "h-full min-h-0 overflow-y-auto overflow-x-hidden"
: "min-h-dvh overflow-x-hidden",
)}
style={
showSplit
? { flex: `${100 - splitPercent} 1 0%`, minWidth: 0 }
: { width: "100%" }
}
>
{children}
</div>
{showSplit && (
<>
<SplitDivider
containerRef={containerRef}
onResize={(aiPct) => setSplitPercent(aiPct)}
/>
<div
className="flex h-full min-h-0 min-w-0 flex-col overflow-hidden border-l border-white/[0.08] bg-[#0d1117]"
style={{ flex: `${splitPercent} 1 0%` }}
>
<AIPage variant="split" onClose={() => setOpen(false)} />
</div>
</>
)}
</div>
);
}