Updated the Sidebar component to include an optional className property in the icon type definition for better styling flexibility. In the AssistantPage, introduced new FastGptForm and DifyForm types, updated the assistant type options to reflect their availability, and enhanced the state management for creating and editing assistants. Improved the user experience by refining the view handling logic and adding dedicated forms for FastGPT and Dify configurations.
330 lines
11 KiB
TypeScript
330 lines
11 KiB
TypeScript
"use client";
|
|
|
|
import {
|
|
Bot,
|
|
Boxes,
|
|
Brain,
|
|
ChevronLeft,
|
|
ChevronRight,
|
|
Clock3,
|
|
Database,
|
|
Wrench,
|
|
Home,
|
|
PlayCircle,
|
|
Video,
|
|
Workflow,
|
|
} from "lucide-react";
|
|
import type { NavKey } from "./AppShell";
|
|
|
|
type SidebarProps = {
|
|
active: NavKey;
|
|
collapsed: boolean;
|
|
onNavigate: (key: NavKey) => void;
|
|
onToggle: () => void;
|
|
};
|
|
|
|
const mainItems: Array<{
|
|
key: NavKey;
|
|
label: string;
|
|
icon: React.ComponentType<{ size?: number; className?: string }>;
|
|
}> = [
|
|
{ key: "home", label: "首页", icon: Home },
|
|
{ key: "test", label: "测试助手", icon: PlayCircle },
|
|
];
|
|
|
|
const componentSubItems: Array<{
|
|
key: NavKey;
|
|
label: string;
|
|
icon: React.ComponentType<{ size?: number; className?: string }>;
|
|
}> = [
|
|
{ key: "components-models", label: "模型资源", icon: Brain },
|
|
{ key: "components-knowledge", label: "知识库", icon: Database },
|
|
{ key: "components-tools", label: "工具资源", icon: Wrench },
|
|
];
|
|
|
|
const monitorSubItems: Array<{
|
|
key: NavKey;
|
|
label: string;
|
|
icon: React.ComponentType<{ size?: number; className?: string }>;
|
|
}> = [
|
|
{ key: "history", label: "历史记录", icon: Clock3 },
|
|
{ key: "dashboard", label: "数据看板", icon: Database },
|
|
];
|
|
|
|
export function Sidebar({
|
|
active,
|
|
collapsed,
|
|
onNavigate,
|
|
onToggle,
|
|
}: SidebarProps) {
|
|
const assistantActive = active === "assistants";
|
|
|
|
const componentActive =
|
|
active === "components-models" || active === "components-knowledge" || active === "components-tools";
|
|
|
|
const monitorActive = monitorSubItems.some((item) => item.key === active);
|
|
|
|
return (
|
|
<aside
|
|
className={[
|
|
"flex shrink-0 flex-col overflow-hidden border-r border-sidebar-border bg-sidebar transition-[width] duration-300 ease-[cubic-bezier(0.22,1,0.36,1)] will-change-[width]",
|
|
collapsed ? "w-[76px]" : "w-[252px]",
|
|
].join(" ")}
|
|
>
|
|
<div className="shrink-0 flex h-16 items-center gap-3 border-b border-sidebar-border px-5 transition-[padding] duration-300 ease-[cubic-bezier(0.22,1,0.36,1)]">
|
|
<div
|
|
className="relative flex h-11 w-11 shrink-0 items-center justify-center rounded-2xl text-on-primary shadow-sm"
|
|
style={{
|
|
backgroundColor: "var(--primary)",
|
|
backgroundImage:
|
|
"radial-gradient(circle at 30% 20%, color-mix(in srgb, var(--gradient-sky) 70%, transparent), transparent 60%), radial-gradient(circle at 80% 90%, color-mix(in srgb, var(--gradient-lavender) 65%, transparent), transparent 55%)",
|
|
}}
|
|
>
|
|
<Video size={22} style={{ color: "var(--primary-foreground)" }} />
|
|
</div>
|
|
|
|
<div
|
|
className={[
|
|
"min-w-0 overflow-hidden transition-all duration-300 ease-[cubic-bezier(0.22,1,0.36,1)]",
|
|
collapsed ? "w-0 opacity-0 -translate-x-2" : "w-[140px] opacity-100 translate-x-0",
|
|
].join(" ")}
|
|
>
|
|
<div className="truncate font-display text-base text-foreground">
|
|
AI 视频助手
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<nav className="min-h-0 flex-1 overflow-y-auto overflow-x-hidden px-3 py-5 pr-2 [scrollbar-width:thin] [scrollbar-color:var(--hairline-strong)_transparent]">
|
|
<div className="space-y-1">
|
|
<NavButton
|
|
active={active === "home"}
|
|
collapsed={collapsed}
|
|
icon={Home}
|
|
label="首页"
|
|
onClick={() => onNavigate("home")}
|
|
/>
|
|
<div className="pt-2">
|
|
<NavButton
|
|
active={assistantActive}
|
|
collapsed={collapsed}
|
|
icon={Bot}
|
|
label="创建助手"
|
|
onClick={() => onNavigate("assistants")}
|
|
/>
|
|
</div>
|
|
|
|
<div className="pt-2">
|
|
{collapsed ? (
|
|
<div
|
|
className="flex h-8 items-center justify-center"
|
|
aria-hidden="true"
|
|
title="组件库"
|
|
>
|
|
<span className="h-px w-6 rounded-full bg-hairline-strong" />
|
|
</div>
|
|
) : (
|
|
<div
|
|
className={[
|
|
"flex h-11 w-full items-center gap-3 rounded-full px-3 text-sm",
|
|
componentActive ? "text-foreground" : "text-muted-foreground",
|
|
].join(" ")}
|
|
>
|
|
<Boxes size={18} />
|
|
<span className="font-medium">组件库</span>
|
|
</div>
|
|
)}
|
|
|
|
<div
|
|
className={[
|
|
"mt-1 space-y-1 transition-[padding] duration-300 ease-[cubic-bezier(0.22,1,0.36,1)]",
|
|
collapsed ? "pl-0" : "pl-5",
|
|
].join(" ")}
|
|
>
|
|
{componentSubItems.map((item) => (
|
|
<NavButton
|
|
key={item.key}
|
|
active={active === item.key}
|
|
collapsed={collapsed}
|
|
icon={item.icon}
|
|
label={item.label}
|
|
onClick={() => onNavigate(item.key)}
|
|
small
|
|
/>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
|
|
<div className="pt-2">
|
|
{collapsed ? (
|
|
<div
|
|
className="flex h-8 items-center justify-center"
|
|
aria-hidden="true"
|
|
title="监控观察"
|
|
>
|
|
<span className="h-px w-6 rounded-full bg-hairline-strong" />
|
|
</div>
|
|
) : (
|
|
<div
|
|
className={[
|
|
"flex h-11 w-full items-center gap-3 rounded-full px-3 text-sm",
|
|
monitorActive ? "text-foreground" : "text-muted-foreground",
|
|
].join(" ")}
|
|
>
|
|
<Boxes size={18} />
|
|
<span className="font-medium">监控观察</span>
|
|
</div>
|
|
)}
|
|
|
|
<div
|
|
className={[
|
|
"mt-1 space-y-1 transition-[padding] duration-300 ease-[cubic-bezier(0.22,1,0.36,1)]",
|
|
collapsed ? "pl-0" : "pl-5",
|
|
].join(" ")}
|
|
>
|
|
{monitorSubItems.map((item) => (
|
|
<NavButton
|
|
key={item.key}
|
|
active={active === item.key}
|
|
collapsed={collapsed}
|
|
icon={item.icon}
|
|
label={item.label}
|
|
onClick={() => onNavigate(item.key)}
|
|
/>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
<div className="pt-2">
|
|
{mainItems.slice(1).map((item) => (
|
|
<NavButton
|
|
key={item.key}
|
|
active={active === item.key}
|
|
collapsed={collapsed}
|
|
icon={item.icon}
|
|
label={item.label}
|
|
onClick={() => onNavigate(item.key)}
|
|
/>
|
|
))}
|
|
</div>
|
|
|
|
</div>
|
|
</nav>
|
|
|
|
<div className="shrink-0 space-y-2 border-t border-sidebar-border bg-sidebar p-3 shadow-[0_-12px_24px_rgba(0,0,0,0.12)]">
|
|
{/* 个人中心 */}
|
|
<button
|
|
onClick={() => onNavigate("profile")}
|
|
title={collapsed ? "个人中心 · 管理员" : undefined}
|
|
className={[
|
|
"group relative flex w-full items-center overflow-hidden rounded-2xl border py-2 text-left transition-[background-color,color,border-color,box-shadow,transform] duration-200 active:scale-[0.98]",
|
|
active === "profile"
|
|
? "border-sidebar-border bg-sidebar-accent text-sidebar-accent-foreground shadow-sm"
|
|
: "border-transparent text-muted-foreground hover:border-sidebar-border hover:bg-sidebar-accent/60 hover:text-foreground hover:shadow-sm",
|
|
collapsed ? "justify-center gap-0 px-0" : "gap-3 px-2.5",
|
|
].join(" ")}
|
|
>
|
|
<span className="relative shrink-0">
|
|
<span
|
|
className="flex h-9 w-9 items-center justify-center rounded-full text-sm font-medium text-on-primary shadow-sm transition-transform duration-200 group-hover:scale-105"
|
|
style={{
|
|
backgroundColor: "var(--primary)",
|
|
backgroundImage:
|
|
"radial-gradient(circle at 30% 20%, color-mix(in srgb, var(--gradient-sky) 70%, transparent), transparent 60%), radial-gradient(circle at 80% 90%, color-mix(in srgb, var(--gradient-lavender) 65%, transparent), transparent 55%)",
|
|
}}
|
|
>
|
|
<span style={{ color: "var(--primary-foreground)" }}>管</span>
|
|
</span>
|
|
<span
|
|
className="absolute -bottom-0.5 -right-0.5 h-3 w-3 rounded-full border-2 border-sidebar"
|
|
style={{ backgroundColor: "var(--success)" }}
|
|
/>
|
|
</span>
|
|
|
|
<span
|
|
className={[
|
|
"min-w-0 overflow-hidden transition-all duration-300 ease-[cubic-bezier(0.22,1,0.36,1)]",
|
|
collapsed ? "w-0 flex-none opacity-0 -translate-x-2" : "flex-1 opacity-100 translate-x-0",
|
|
].join(" ")}
|
|
>
|
|
<span className="block truncate text-sm font-medium text-foreground">
|
|
管理员
|
|
</span>
|
|
</span>
|
|
</button>
|
|
|
|
{/* 收起 / 展开侧栏 */}
|
|
<button
|
|
onClick={onToggle}
|
|
title={collapsed ? "展开侧栏" : "收起侧栏"}
|
|
className={[
|
|
"group flex h-10 w-full items-center overflow-hidden rounded-full border border-hairline-strong text-sm text-muted-foreground transition-[background-color,color,border-color,transform] duration-200 hover:bg-surface-strong hover:text-foreground active:scale-[0.98]",
|
|
collapsed ? "justify-center gap-0 px-0" : "justify-between gap-2 px-3.5",
|
|
].join(" ")}
|
|
>
|
|
<span
|
|
className={[
|
|
"min-w-0 truncate transition-all duration-300 ease-[cubic-bezier(0.22,1,0.36,1)]",
|
|
collapsed ? "w-0 opacity-0 -translate-x-2" : "opacity-100 translate-x-0",
|
|
].join(" ")}
|
|
>
|
|
收起侧栏
|
|
</span>
|
|
<ChevronLeft
|
|
size={18}
|
|
className={[
|
|
"shrink-0 transition-transform duration-300 ease-[cubic-bezier(0.22,1,0.36,1)] group-hover:scale-110",
|
|
collapsed ? "rotate-180" : "rotate-0",
|
|
].join(" ")}
|
|
/>
|
|
</button>
|
|
</div>
|
|
</aside>
|
|
);
|
|
}
|
|
|
|
function NavButton({
|
|
active,
|
|
collapsed,
|
|
icon: Icon,
|
|
label,
|
|
onClick,
|
|
small = false,
|
|
}: {
|
|
active: boolean;
|
|
collapsed: boolean;
|
|
icon: React.ComponentType<{ size?: number; className?: string }>;
|
|
label: string;
|
|
onClick: () => void;
|
|
small?: boolean;
|
|
}) {
|
|
return (
|
|
<button
|
|
onClick={onClick}
|
|
title={collapsed ? label : undefined}
|
|
className={[
|
|
"group mt-1 flex w-full items-center overflow-hidden rounded-full text-sm transition-[background-color,color,transform] duration-200 active:scale-[0.98]",
|
|
small ? "h-10" : "h-11",
|
|
active
|
|
? "bg-sidebar-accent text-sidebar-accent-foreground font-medium"
|
|
: "text-muted-foreground hover:bg-sidebar-accent/60 hover:text-foreground",
|
|
collapsed ? "justify-center gap-0 px-0" : "gap-3 px-3 hover:translate-x-0.5",
|
|
].join(" ")}
|
|
>
|
|
<Icon
|
|
size={small ? 16 : 18}
|
|
className="shrink-0 transition-transform duration-200 group-hover:scale-105"
|
|
/>
|
|
<span
|
|
className={[
|
|
"min-w-0 truncate transition-all duration-300 ease-[cubic-bezier(0.22,1,0.36,1)]",
|
|
collapsed ? "w-0 opacity-0 -translate-x-2" : "w-[150px] opacity-100 translate-x-0",
|
|
].join(" ")}
|
|
>
|
|
{label}
|
|
</span>
|
|
</button>
|
|
);
|
|
}
|