diff --git a/web/src/components/empty/empty.tsx b/web/src/components/empty/empty.tsx index 33cc44fa04..9b4b507c36 100644 --- a/web/src/components/empty/empty.tsx +++ b/web/src/components/empty/empty.tsx @@ -10,14 +10,7 @@ import { EmptyCardData, EmptyCardType, EmptyType } from './constant'; import { EmptyCardProps, EmptyProps } from './interface'; const EmptyIcon = ({ name, width }: { name: string; width?: number }) => { - return ( - // {t('common.noData')} - - ); + return ; }; const Empty = (props: EmptyProps) => { @@ -58,16 +51,18 @@ export const EmptyCard = (props: EmptyCardProps) => { return (
{icon} - {title &&
{title}
} + {title &&
{title}
} {description && ( -

{description}

+

{description}

)} {children}
@@ -89,7 +84,7 @@ export const EmptyAppCard = (props: { props; const { t } = useTranslation(); let defaultClass = ''; - let style = {}; + let style: React.CSSProperties | undefined; const cardData = EmptyCardData[type]; const title = t(cardData.titleKey); const notFound = t(cardData.notFoundKey); @@ -100,30 +95,36 @@ export const EmptyAppCard = (props: { defaultClass = 'mt-1'; break; case 'large': - style = { width: '480px' }; defaultClass = 'mt-5'; break; default: defaultClass = ''; break; } + return ( -
+
{!isSearch && !children && (
diff --git a/web/src/components/list-filter-bar/index.tsx b/web/src/components/list-filter-bar/index.tsx index 95347e02d6..5d2cf299e8 100644 --- a/web/src/components/list-filter-bar/index.tsx +++ b/web/src/components/list-filter-bar/index.tsx @@ -32,14 +32,6 @@ export const FilterButton = React.forwardRef< {...props} ref={ref} > - {/* 0, - 'text-text-sub-title-invert': count === 0, - })} - > - Filter - */} {count > 0 && ( @@ -52,6 +44,7 @@ export const FilterButton = React.forwardRef< }); FilterButton.displayName = 'FilterButton'; + export default function ListFilterBar({ title, children, @@ -94,11 +87,17 @@ export default function ListFilterBar({ : 0; }, [value]); + const hasFilter = Boolean(filters?.length && showFilter); + return ( -
-

+
+

{typeof icon === 'string' ? ( - // -
+
{preChildren} - {filters?.length && showFilter && ( + {hasFilter && ( - + )} {showSearch && ( + /> + )} + + {children && ( +
{children}
)} - {children}
); diff --git a/web/src/layouts/components/bell-button.tsx b/web/src/layouts/components/bell-button.tsx index 0f4929866f..6a64aeeb38 100644 --- a/web/src/layouts/components/bell-button.tsx +++ b/web/src/layouts/components/bell-button.tsx @@ -1,18 +1,19 @@ import { Button } from '@/components/ui/button'; +import { cn } from '@/lib/utils'; import { Routes } from '@/routes'; import { BellRing } from 'lucide-react'; -export function BellButton() { +export function BellButton({ className }: { className?: string }) { return ( ); } diff --git a/web/src/layouts/components/global-navbar.tsx b/web/src/layouts/components/global-navbar.tsx index 7aa65f047e..fb2a3978cd 100644 --- a/web/src/layouts/components/global-navbar.tsx +++ b/web/src/layouts/components/global-navbar.tsx @@ -1,9 +1,20 @@ -import { useId, useMemo } from 'react'; +import { useId, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { Link, useLocation } from 'react-router'; -import { LucideHouse } from 'lucide-react'; +import { + LucideBrain, + LucideCpu, + LucideDatabase, + LucideFolderOpen, + LucideHouse, + LucideMenu, + LucideMessageSquareText, + LucideSearch, +} from 'lucide-react'; +import { Button } from '@/components/ui/button'; +import { Sheet, SheetContent, SheetTrigger } from '@/components/ui/sheet'; import { cn } from '@/lib/utils'; import { Routes } from '@/routes'; import { supportsCssAnchor } from '@/utils/css-support'; @@ -23,141 +34,243 @@ const matchesPath = (pathname: string, candidate: string) => pathname === candidate || pathname.startsWith(`${candidate}/`); const menuItems = [ - { path: Routes.Root, name: 'header.Root', icon: LucideHouse }, - { path: Routes.Datasets, name: 'header.dataset' /* icon: Library, */ }, + { path: Routes.Root, name: 'header.home', icon: LucideHouse }, + { path: Routes.Datasets, name: 'header.dataset', icon: LucideDatabase }, { path: Routes.Chats, name: 'header.chat', - /* icon: MessageSquareText, */ 'data-testid': 'nav-chat', + icon: LucideMessageSquareText, + 'data-testid': 'nav-chat', }, { path: Routes.Searches, name: 'header.search', - /* icon: Search, */ 'data-testid': 'nav-search', + icon: LucideSearch, + 'data-testid': 'nav-search', }, { path: Routes.Agents, name: 'header.flow', - /* icon: Cpu, */ 'data-testid': 'nav-agent', + icon: LucideCpu, + 'data-testid': 'nav-agent', }, - { path: Routes.Memories, name: 'header.memories' /* icon: Cpu, */ }, - { path: Routes.Files, name: 'header.fileManager' /* icon: File, */ }, + { path: Routes.Memories, name: 'header.memories', icon: LucideBrain }, + { path: Routes.Files, name: 'header.fileManager', icon: LucideFolderOpen }, ]; -const GlobalNavbar = supportsCssAnchor - ? () => { - const { t } = useTranslation(); - const { pathname } = useLocation(); - const navbarAnchorNamePrefix = useId().replace(/:/g, ''); +function useActivePath() { + const { pathname } = useLocation(); - const activePath = useMemo(() => { - return ( - Object.keys(PathMap).find((x: string) => - PathMap[x as keyof typeof PathMap].some((y: string) => - matchesPath(pathname, y), - ), - ) || pathname - ); - }, [pathname]); + return useMemo(() => { + return ( + Object.keys(PathMap).find((x: string) => + PathMap[x as keyof typeof PathMap].some((y: string) => + matchesPath(pathname, y), + ), + ) || pathname + ); + }, [pathname]); +} - const activePathAnchorName = `--${navbarAnchorNamePrefix}${activePath === Routes.Root ? '-root' : activePath.replace('/', '-')}`; +const DesktopNavbarWithAnchor = () => { + const { t } = useTranslation(); + const activePath = useActivePath(); + const navbarAnchorNamePrefix = useId().replace(/:/g, ''); - const hasAnyActive = useMemo( - () => menuItems.some(({ path }) => path === activePath), - [activePath], - ); + const activePathAnchorName = `--${navbarAnchorNamePrefix}${activePath === Routes.Root ? '-root' : activePath.replace('/', '-')}`; - return ( -

- + ); } diff --git a/web/src/layouts/components/mobile-menu-footer.tsx b/web/src/layouts/components/mobile-menu-footer.tsx new file mode 100644 index 0000000000..36795b35ff --- /dev/null +++ b/web/src/layouts/components/mobile-menu-footer.tsx @@ -0,0 +1,72 @@ +import { useTranslation } from 'react-i18next'; + +function FooterDivider() { + return |; +} + +function FooterLink({ + children, + onClick, + href, + target, + rel, +}: { + children: React.ReactNode; + onClick?: () => void; + href: string; + target?: string; + rel?: string; +}) { + return ( + + {children} + + ); +} + +type MobileMenuFooterProps = { + onClose: () => void; +}; + +export function MobileMenuFooter({ onClose }: MobileMenuFooterProps) { + const { t } = useTranslation(); + + return ( +
+
+ + {t('header.discord')} + + + + {t('header.github')} + + + + {t('header.help')} + +
+
+ ); +} diff --git a/web/src/layouts/components/theme-button.tsx b/web/src/layouts/components/theme-button.tsx index a4e8a2cd1e..9d2ad55fcd 100644 --- a/web/src/layouts/components/theme-button.tsx +++ b/web/src/layouts/components/theme-button.tsx @@ -3,20 +3,25 @@ import { LucideMoon, LucideSun } from 'lucide-react'; import { useTheme } from '@/components/theme-provider'; import { Button } from '@/components/ui/button'; import { ThemeEnum } from '@/constants/common'; +import { cn } from '@/lib/utils'; -export default function ThemeButton() { +export default function ThemeButton({ className }: { className?: string }) { const { setTheme, theme } = useTheme(); return ( ); } diff --git a/web/src/layouts/components/use-header-nav-layout.ts b/web/src/layouts/components/use-header-nav-layout.ts new file mode 100644 index 0000000000..66c2cc71c1 --- /dev/null +++ b/web/src/layouts/components/use-header-nav-layout.ts @@ -0,0 +1,58 @@ +import { useLayoutEffect, useRef, useState } from 'react'; + +const LAYOUT_GAP = 48; +const FIT_BUFFER = 16; + +export function useHeaderNavLayout(measureKey = '') { + const headerRef = useRef(null); + const logoRef = useRef(null); + const expandedRightMeasureRef = useRef(null); + const navMeasureRef = useRef(null); + const [isCompact, setIsCompact] = useState(true); + + useLayoutEffect(() => { + const measure = () => { + const header = headerRef.current; + const logo = logoRef.current; + const expandedRight = expandedRightMeasureRef.current; + const nav = navMeasureRef.current; + + if (!header || !logo || !expandedRight || !nav) { + return; + } + + const navWidth = nav.scrollWidth; + const availableForDesktop = + header.clientWidth - + logo.offsetWidth - + expandedRight.offsetWidth - + LAYOUT_GAP; + + setIsCompact(navWidth + FIT_BUFFER > availableForDesktop); + }; + + measure(); + + const observer = new ResizeObserver(measure); + [ + headerRef.current, + logoRef.current, + expandedRightMeasureRef.current, + navMeasureRef.current, + ].forEach((node) => { + if (node) { + observer.observe(node); + } + }); + + return () => observer.disconnect(); + }, [measureKey]); + + return { + headerRef, + logoRef, + expandedRightMeasureRef, + navMeasureRef, + isCompact, + }; +} diff --git a/web/src/layouts/root-layout.tsx b/web/src/layouts/root-layout.tsx index 6cc34435ba..c26280e711 100644 --- a/web/src/layouts/root-layout.tsx +++ b/web/src/layouts/root-layout.tsx @@ -3,10 +3,10 @@ import { Header } from './components/header'; export function RootLayoutContainer({ children }: React.PropsWithChildren) { return ( -
+
-
{children}
+
{children}
); } diff --git a/web/src/locales/en.ts b/web/src/locales/en.ts index 3b4bdcba87..43745cc384 100644 --- a/web/src/locales/en.ts +++ b/web/src/locales/en.ts @@ -124,6 +124,9 @@ export default { welcome: 'Welcome to', dataset: 'Dataset', memories: 'Memory', + discord: 'Discord', + github: 'GitHub', + help: 'Help', }, skills: { title: 'Skills', diff --git a/web/src/locales/zh.ts b/web/src/locales/zh.ts index 5670cd95b0..6fe42a0e4c 100644 --- a/web/src/locales/zh.ts +++ b/web/src/locales/zh.ts @@ -108,6 +108,9 @@ export default { welcome: '欢迎来到', dataset: '知识库', memories: '记忆', + discord: 'Discord', + github: 'GitHub', + help: '帮助', }, skills: { title: '技能', diff --git a/web/src/pages/agents/index.tsx b/web/src/pages/agents/index.tsx index 5d53fc8c2d..9e95e53870 100644 --- a/web/src/pages/agents/index.tsx +++ b/web/src/pages/agents/index.tsx @@ -86,8 +86,11 @@ export default function Agents() { return ( <> {data?.length || searchString ? ( -
-
+
+
{kbs?.length || searchString ? (
-
+
-
+
+
{list?.data?.memory_list?.length || searchString ? ( -
-
+
+
) : ( -
+
openCreateModalFun()} @@ -127,13 +129,12 @@ export default function MemoryList() {
) : (
openCreateModalFun()} /> diff --git a/web/src/pages/next-chats/index.tsx b/web/src/pages/next-chats/index.tsx index ed10d2df93..143d7f5130 100644 --- a/web/src/pages/next-chats/index.tsx +++ b/web/src/pages/next-chats/index.tsx @@ -92,8 +92,11 @@ export default function ChatList() { return ( <> {data.chats?.length || searchString ? ( -
-
+
+
{list?.data?.search_apps?.length || searchString ? ( -
-
+
+