mirror of
https://github.com/basketikun/infinite-canvas.git
synced 2026-07-31 13:41:18 +08:00
88 lines
4.6 KiB
TypeScript
88 lines
4.6 KiB
TypeScript
import { useState, type PointerEvent as ReactPointerEvent } from "react";
|
|
import { Bot, PanelRightClose } from "lucide-react";
|
|
import { Button, Switch, Tooltip } from "antd";
|
|
import { motion } from "motion/react";
|
|
|
|
import { CanvasLocalAgentPanel } from "@/components/canvas/canvas-local-agent-panel";
|
|
import { canvasThemes } from "@/lib/canvas-theme";
|
|
import { CANVAS_AGENT_PANEL_MOTION_MS, useAgentStore } from "@/stores/use-agent-store";
|
|
import { useThemeStore } from "@/stores/use-theme-store";
|
|
|
|
const PANEL_MOTION_SECONDS = CANVAS_AGENT_PANEL_MOTION_MS / 1000;
|
|
|
|
export function AgentPanel() {
|
|
const theme = canvasThemes[useThemeStore((state) => state.theme)];
|
|
const width = useAgentStore((state) => state.width);
|
|
const [resizing, setResizing] = useState(false);
|
|
const panelMounted = useAgentStore((state) => state.panelMounted);
|
|
const panelOpen = useAgentStore((state) => state.panelOpen);
|
|
const panelClosing = useAgentStore((state) => state.panelClosing);
|
|
const confirmTools = useAgentStore((state) => state.confirmTools);
|
|
const setAgentState = useAgentStore((state) => state.setAgentState);
|
|
const closePanel = useAgentStore((state) => state.closePanel);
|
|
|
|
|
|
const startResize = (event: ReactPointerEvent<HTMLButtonElement>) => {
|
|
event.preventDefault();
|
|
const startX = event.clientX;
|
|
const startWidth = width;
|
|
let nextWidth = startWidth;
|
|
const onMove = (moveEvent: PointerEvent) => {
|
|
nextWidth = Math.min(760, Math.max(360, startWidth + startX - moveEvent.clientX));
|
|
setAgentState({ width: nextWidth });
|
|
};
|
|
const onUp = () => {
|
|
localStorage.setItem("canvas-agent-panel-width", String(nextWidth));
|
|
window.removeEventListener("pointermove", onMove);
|
|
window.removeEventListener("pointerup", onUp);
|
|
setResizing(false);
|
|
};
|
|
setResizing(true);
|
|
window.addEventListener("pointermove", onMove);
|
|
window.addEventListener("pointerup", onUp);
|
|
};
|
|
|
|
if (!panelMounted) return null;
|
|
|
|
return (
|
|
<motion.div
|
|
className="relative z-[70] flex h-full shrink-0"
|
|
initial={{ width: 0, opacity: 0 }}
|
|
animate={{ width: panelOpen ? width + 1 : 0, opacity: panelOpen ? 1 : 0 }}
|
|
transition={{ duration: resizing ? 0 : PANEL_MOTION_SECONDS, ease: [0.22, 1, 0.36, 1] }}
|
|
style={{ overflow: "clip", pointerEvents: panelClosing ? "none" : undefined }}
|
|
>
|
|
<motion.aside
|
|
className="relative flex h-full shrink-0 flex-col border-l"
|
|
initial={{ x: 48 }}
|
|
animate={{ x: panelClosing ? 28 : 0 }}
|
|
transition={{ duration: resizing ? 0 : PANEL_MOTION_SECONDS, ease: [0.22, 1, 0.36, 1] }}
|
|
style={{ width, background: theme.node.panel, borderColor: theme.node.stroke, color: theme.node.text }}
|
|
>
|
|
<button type="button" className="absolute inset-y-0 left-0 z-40 w-4 -translate-x-1/2 cursor-col-resize" onPointerDown={startResize} aria-label="调整右侧面板宽度" />
|
|
<header className="flex h-14 shrink-0 items-center justify-between border-b px-4" style={{ borderColor: theme.node.stroke }}>
|
|
<div className="flex min-w-0 items-center gap-2">
|
|
<span className="grid size-8 place-items-center rounded-lg">
|
|
<Bot className="size-4" />
|
|
</span>
|
|
<div className="min-w-0">
|
|
<div className="text-base font-semibold leading-5">Agent</div>
|
|
<div className="truncate text-xs" style={{ color: theme.node.muted }}>全站助手</div>
|
|
</div>
|
|
</div>
|
|
<div className="flex shrink-0 items-center gap-2">
|
|
<label className="flex items-center gap-1.5 text-xs" style={{ color: theme.node.muted }}>
|
|
<Switch size="small" checked={confirmTools} onChange={(confirmTools) => setAgentState({ confirmTools })} />
|
|
工具确认
|
|
</label>
|
|
<Tooltip title="收起对话">
|
|
<Button type="text" shape="circle" className="!h-8 !w-8 !min-w-8" style={{ color: theme.node.muted }} icon={<PanelRightClose className="size-4" />} onClick={closePanel} />
|
|
</Tooltip>
|
|
</div>
|
|
</header>
|
|
<CanvasLocalAgentPanel embedded />
|
|
</motion.aside>
|
|
</motion.div>
|
|
);
|
|
}
|