mirror of
https://github.com/lobehub/lobe-chat.git
synced 2026-06-18 21:36:12 +00:00
Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3d24ad8f11 | |||
| 9717125f34 | |||
| 110b2483a5 | |||
| 2587752ee2 | |||
| abbbef35bd | |||
| 0854943696 | |||
| d8c7faa259 | |||
| 9b875117f9 | |||
| a9c90285f8 | |||
| a5a7ef2157 | |||
| fe617775bf | |||
| 2abad13296 |
@@ -1,11 +1,20 @@
|
||||
'use client';
|
||||
|
||||
import { ActionIcon, Flexbox, Text } from '@lobehub/ui';
|
||||
import { useClickAway } from 'ahooks';
|
||||
import { Drawer } from 'antd';
|
||||
import { cssVar } from 'antd-style';
|
||||
import { XIcon } from 'lucide-react';
|
||||
import type { ReactNode, Ref } from 'react';
|
||||
import { cloneElement, isValidElement, memo, Suspense, useCallback, useState } from 'react';
|
||||
import {
|
||||
cloneElement,
|
||||
isValidElement,
|
||||
memo,
|
||||
Suspense,
|
||||
useCallback,
|
||||
useImperativeHandle,
|
||||
useState,
|
||||
} from 'react';
|
||||
|
||||
import { DESKTOP_HEADER_ICON_SMALL_SIZE } from '@/const/layoutTokens';
|
||||
|
||||
@@ -14,11 +23,16 @@ import SkeletonList from './components/SkeletonList';
|
||||
import { OverlayContainerContext } from './OverlayContainer';
|
||||
import SideBarHeaderLayout from './SideBarHeaderLayout';
|
||||
|
||||
export interface SideBarDrawerHandle {
|
||||
close: () => void;
|
||||
open: () => void;
|
||||
}
|
||||
|
||||
interface SideBarDrawerProps {
|
||||
action?: ReactNode;
|
||||
children?: ReactNode;
|
||||
onClose: () => void;
|
||||
open: boolean;
|
||||
onOpenChange?: (open: boolean) => void;
|
||||
ref?: Ref<SideBarDrawerHandle>;
|
||||
subHeader?: ReactNode;
|
||||
title?: ReactNode;
|
||||
}
|
||||
@@ -39,10 +53,39 @@ const setRef = <T,>(ref: Ref<T> | undefined, value: T | null) => {
|
||||
};
|
||||
|
||||
const SideBarDrawer = memo<SideBarDrawerProps>(
|
||||
({ subHeader, open, onClose, children, title, action }) => {
|
||||
({ subHeader, onOpenChange, children, title, action, ref }) => {
|
||||
const size = 280;
|
||||
|
||||
const [overlayContainer, setOverlayContainer] = useState<HTMLDivElement | null>(null);
|
||||
const [internalOpen, setInternalOpen] = useState(false);
|
||||
|
||||
const handleOpen = useCallback(() => {
|
||||
setInternalOpen((prev) => {
|
||||
if (!prev) onOpenChange?.(true);
|
||||
return true;
|
||||
});
|
||||
}, [onOpenChange]);
|
||||
|
||||
const handleClose = useCallback(() => {
|
||||
setInternalOpen((prev) => {
|
||||
if (prev) onOpenChange?.(false);
|
||||
return false;
|
||||
});
|
||||
}, [onOpenChange]);
|
||||
|
||||
useImperativeHandle(
|
||||
ref,
|
||||
() => ({
|
||||
close: handleClose,
|
||||
open: handleOpen,
|
||||
}),
|
||||
[handleClose, handleOpen],
|
||||
);
|
||||
|
||||
useClickAway(() => {
|
||||
if (!internalOpen) return;
|
||||
handleClose();
|
||||
}, overlayContainer);
|
||||
|
||||
const renderDrawerContent = useCallback((node: ReactNode) => {
|
||||
if (!isValidElement<DrawerRenderNodeProps>(node)) return node;
|
||||
@@ -67,7 +110,7 @@ const SideBarDrawer = memo<SideBarDrawerProps>(
|
||||
drawerRender={renderDrawerContent}
|
||||
getContainer={() => document.querySelector(`#${NAV_PANEL_RIGHT_DRAWER_ID}`)!}
|
||||
mask={false}
|
||||
open={open}
|
||||
open={internalOpen}
|
||||
placement="left"
|
||||
size={size}
|
||||
rootStyle={{
|
||||
@@ -120,7 +163,7 @@ const SideBarDrawer = memo<SideBarDrawerProps>(
|
||||
icon={XIcon}
|
||||
size={DESKTOP_HEADER_ICON_SMALL_SIZE}
|
||||
style={{ marginInlineEnd: -2 }}
|
||||
onClick={onClose}
|
||||
onClick={handleClose}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
@@ -128,7 +171,7 @@ const SideBarDrawer = memo<SideBarDrawerProps>(
|
||||
{subHeader}
|
||||
</>
|
||||
}
|
||||
onClose={onClose}
|
||||
onClose={handleClose}
|
||||
>
|
||||
<Suspense
|
||||
fallback={
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
'use client';
|
||||
|
||||
import { Flexbox, SearchBar } from '@lobehub/ui';
|
||||
import { memo, useState } from 'react';
|
||||
import { memo, type Ref, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import SkeletonList from '@/features/NavPanel/components/SkeletonList';
|
||||
import SideBarDrawer from '@/features/NavPanel/SideBarDrawer';
|
||||
import SideBarDrawer, { type SideBarDrawerHandle } from '@/features/NavPanel/SideBarDrawer';
|
||||
import dynamic from '@/libs/next/dynamic';
|
||||
|
||||
const Content = dynamic(() => import('./Content'), {
|
||||
@@ -18,17 +18,16 @@ const Content = dynamic(() => import('./Content'), {
|
||||
});
|
||||
|
||||
interface AllPagesDrawerProps {
|
||||
onClose: () => void;
|
||||
open: boolean;
|
||||
ref?: Ref<SideBarDrawerHandle>;
|
||||
}
|
||||
|
||||
const AllPagesDrawer = memo<AllPagesDrawerProps>(({ open, onClose }) => {
|
||||
const AllPagesDrawer = memo<AllPagesDrawerProps>(({ ref }) => {
|
||||
const { t } = useTranslation('file');
|
||||
const [searchKeyword, setSearchKeyword] = useState('');
|
||||
|
||||
return (
|
||||
<SideBarDrawer
|
||||
open={open}
|
||||
ref={ref}
|
||||
title={t('pageList.title')}
|
||||
subHeader={
|
||||
<Flexbox paddingBlock={'0 8px'} paddingInline={8}>
|
||||
@@ -43,7 +42,6 @@ const AllPagesDrawer = memo<AllPagesDrawerProps>(({ open, onClose }) => {
|
||||
/>
|
||||
</Flexbox>
|
||||
}
|
||||
onClose={onClose}
|
||||
>
|
||||
<Content searchKeyword={searchKeyword} />
|
||||
</SideBarDrawer>
|
||||
|
||||
@@ -10,17 +10,20 @@ import { pageSelectors, usePageStore } from '@/store/page';
|
||||
|
||||
import Item from './Item';
|
||||
|
||||
interface PageListProps {
|
||||
onOpenDrawer: () => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show pages filtered by library
|
||||
*/
|
||||
const PageList = () => {
|
||||
const PageList = ({ onOpenDrawer }: PageListProps) => {
|
||||
const { t } = useTranslation(['file', 'common']);
|
||||
|
||||
const [filteredDocuments, hasMore, isLoadingMore, openAllPagesDrawer] = usePageStore((s) => [
|
||||
const [filteredDocuments, hasMore, isLoadingMore] = usePageStore((s) => [
|
||||
pageSelectors.getFilteredDocumentsLimited(s),
|
||||
pageSelectors.hasMoreFilteredDocuments(s),
|
||||
pageSelectors.isLoadingMoreDocuments(s),
|
||||
s.openAllPagesDrawer,
|
||||
]);
|
||||
|
||||
return (
|
||||
@@ -30,11 +33,7 @@ const PageList = () => {
|
||||
))}
|
||||
{isLoadingMore && <SkeletonList rows={3} />}
|
||||
{hasMore && !isLoadingMore && (
|
||||
<NavItem
|
||||
icon={MoreHorizontal}
|
||||
title={t('more', { ns: 'common' })}
|
||||
onClick={openAllPagesDrawer}
|
||||
/>
|
||||
<NavItem icon={MoreHorizontal} title={t('more', { ns: 'common' })} onClick={onOpenDrawer} />
|
||||
)}
|
||||
</Flexbox>
|
||||
);
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
'use client';
|
||||
|
||||
import { Accordion, AccordionItem, ContextMenuTrigger, Flexbox, Text } from '@lobehub/ui';
|
||||
import React, { memo, Suspense } from 'react';
|
||||
import React, { memo, Suspense, useCallback, useRef } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import SkeletonList from '@/features/NavPanel/components/SkeletonList';
|
||||
import { type SideBarDrawerHandle } from '@/features/NavPanel/SideBarDrawer';
|
||||
import PageEmpty from '@/features/PageEmpty';
|
||||
import { pageSelectors, usePageStore } from '@/store/page';
|
||||
|
||||
@@ -33,10 +34,8 @@ const Body = memo(() => {
|
||||
const filteredDocuments = usePageStore(pageSelectors.getFilteredDocumentsLimited);
|
||||
const searchKeywords = usePageStore((s) => s.searchKeywords);
|
||||
const dropdownMenu = useDropdownMenu();
|
||||
const [allPagesDrawerOpen, closeAllPagesDrawer] = usePageStore((s) => [
|
||||
s.allPagesDrawerOpen,
|
||||
s.closeAllPagesDrawer,
|
||||
]);
|
||||
const drawerRef = useRef<SideBarDrawerHandle>(null);
|
||||
const openDrawer = useCallback(() => drawerRef.current?.open(), []);
|
||||
|
||||
return (
|
||||
<Flexbox gap={1} paddingInline={4}>
|
||||
@@ -64,14 +63,14 @@ const Body = memo(() => {
|
||||
{filteredDocuments.length === 0 ? (
|
||||
<PageEmpty search={Boolean(searchKeywords.trim())} />
|
||||
) : (
|
||||
<List />
|
||||
<List onOpenDrawer={openDrawer} />
|
||||
)}
|
||||
</Flexbox>
|
||||
)}
|
||||
</Suspense>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
<AllPagesDrawer open={allPagesDrawerOpen} onClose={closeAllPagesDrawer} />
|
||||
<AllPagesDrawer ref={drawerRef} />
|
||||
</Flexbox>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
'use client';
|
||||
|
||||
import { Flexbox, SearchBar } from '@lobehub/ui';
|
||||
import { memo, useState } from 'react';
|
||||
import { memo, type Ref, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import SkeletonList from '@/features/NavPanel/components/SkeletonList';
|
||||
import SideBarDrawer from '@/features/NavPanel/SideBarDrawer';
|
||||
import SideBarDrawer, { type SideBarDrawerHandle } from '@/features/NavPanel/SideBarDrawer';
|
||||
import dynamic from '@/libs/next/dynamic';
|
||||
|
||||
const Content = dynamic(() => import('./Content'), {
|
||||
@@ -18,17 +18,17 @@ const Content = dynamic(() => import('./Content'), {
|
||||
});
|
||||
|
||||
interface AllTopicsDrawerProps {
|
||||
onClose: () => void;
|
||||
open: boolean;
|
||||
ref?: Ref<SideBarDrawerHandle>;
|
||||
}
|
||||
|
||||
const AllTopicsDrawer = memo<AllTopicsDrawerProps>(({ open, onClose }) => {
|
||||
const AllTopicsDrawer = memo<AllTopicsDrawerProps>(({ ref }) => {
|
||||
const { t } = useTranslation('topic');
|
||||
const [searchKeyword, setSearchKeyword] = useState('');
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<SideBarDrawer
|
||||
open={open}
|
||||
ref={ref}
|
||||
title={t('title')}
|
||||
subHeader={
|
||||
<Flexbox paddingBlock={'0 8px'} paddingInline={8}>
|
||||
@@ -43,9 +43,9 @@ const AllTopicsDrawer = memo<AllTopicsDrawerProps>(({ open, onClose }) => {
|
||||
/>
|
||||
</Flexbox>
|
||||
}
|
||||
onClose={onClose}
|
||||
onOpenChange={setIsOpen}
|
||||
>
|
||||
<Content open={open} searchKeyword={searchKeyword} />
|
||||
<Content open={isOpen} searchKeyword={searchKeyword} />
|
||||
</SideBarDrawer>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
'use client';
|
||||
|
||||
import React, { memo } from 'react';
|
||||
import React, { memo, useCallback, useRef } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import urlJoin from 'url-join';
|
||||
|
||||
import EmptyNavItem from '@/features/NavPanel/components/EmptyNavItem';
|
||||
import SkeletonList from '@/features/NavPanel/components/SkeletonList';
|
||||
import { type SideBarDrawerHandle } from '@/features/NavPanel/SideBarDrawer';
|
||||
import { useFetchChatTopics } from '@/hooks/useFetchChatTopics';
|
||||
import { useQueryRoute } from '@/hooks/useQueryRoute';
|
||||
import { useChatStore } from '@/store/chat';
|
||||
@@ -23,17 +24,15 @@ const TopicList = memo(() => {
|
||||
const topicLength = useChatStore((s) => topicSelectors.currentTopicLength(s));
|
||||
const isUndefinedTopics = useChatStore((s) => topicSelectors.isUndefinedTopics(s));
|
||||
|
||||
const [agentId, allTopicsDrawerOpen, closeAllTopicsDrawer] = useChatStore((s) => [
|
||||
s.activeAgentId,
|
||||
s.allTopicsDrawerOpen,
|
||||
s.closeAllTopicsDrawer,
|
||||
]);
|
||||
const agentId = useChatStore((s) => s.activeAgentId);
|
||||
|
||||
const { topicGroupMode } = useAgentTopicGroupMode();
|
||||
|
||||
const drawerRef = useRef<SideBarDrawerHandle>(null);
|
||||
const openDrawer = useCallback(() => drawerRef.current?.open(), []);
|
||||
|
||||
useFetchChatTopics();
|
||||
|
||||
// Show skeleton when current session's topic data is not yet loaded
|
||||
if (isUndefinedTopics) return <SkeletonList />;
|
||||
|
||||
return (
|
||||
@@ -47,13 +46,13 @@ const TopicList = memo(() => {
|
||||
/>
|
||||
)}
|
||||
{topicGroupMode === 'flat' ? (
|
||||
<FlatMode />
|
||||
<FlatMode onOpenDrawer={openDrawer} />
|
||||
) : topicGroupMode === 'byProject' ? (
|
||||
<ByProjectMode />
|
||||
<ByProjectMode onOpenDrawer={openDrawer} />
|
||||
) : (
|
||||
<ByTimeMode />
|
||||
<ByTimeMode onOpenDrawer={openDrawer} />
|
||||
)}
|
||||
<AllTopicsDrawer open={allTopicsDrawerOpen} onClose={closeAllTopicsDrawer} />
|
||||
<AllTopicsDrawer ref={drawerRef} />
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
+7
-1
@@ -5,7 +5,13 @@ import { memo } from 'react';
|
||||
import GroupedAccordion from '../GroupedAccordion';
|
||||
import GroupItem from './GroupItem';
|
||||
|
||||
const ByProjectMode = memo(() => <GroupedAccordion GroupItem={GroupItem} />);
|
||||
interface ByProjectModeProps {
|
||||
onOpenDrawer?: () => void;
|
||||
}
|
||||
|
||||
const ByProjectMode = memo<ByProjectModeProps>(({ onOpenDrawer }) => (
|
||||
<GroupedAccordion GroupItem={GroupItem} onOpenDrawer={onOpenDrawer} />
|
||||
));
|
||||
|
||||
ByProjectMode.displayName = 'ByProjectMode';
|
||||
|
||||
|
||||
@@ -5,7 +5,13 @@ import { memo } from 'react';
|
||||
import GroupedAccordion from '../GroupedAccordion';
|
||||
import GroupItem from './GroupItem';
|
||||
|
||||
const ByTimeMode = memo(() => <GroupedAccordion GroupItem={GroupItem} />);
|
||||
interface ByTimeModeProps {
|
||||
onOpenDrawer?: () => void;
|
||||
}
|
||||
|
||||
const ByTimeMode = memo<ByTimeModeProps>(({ onOpenDrawer }) => (
|
||||
<GroupedAccordion GroupItem={GroupItem} onOpenDrawer={onOpenDrawer} />
|
||||
));
|
||||
|
||||
ByTimeMode.displayName = 'ByTimeMode';
|
||||
|
||||
|
||||
@@ -17,19 +17,21 @@ import { preferenceSelectors } from '@/store/user/selectors';
|
||||
|
||||
import TopicItem from '../../List/Item';
|
||||
|
||||
const FlatMode = memo(() => {
|
||||
interface FlatModeProps {
|
||||
onOpenDrawer?: () => void;
|
||||
}
|
||||
|
||||
const FlatMode = memo<FlatModeProps>(({ onOpenDrawer }) => {
|
||||
const { t } = useTranslation('topic');
|
||||
const topicPageSize = useGlobalStore(systemStatusSelectors.topicPageSize);
|
||||
const topicSortBy = useUserStore(preferenceSelectors.topicSortBy);
|
||||
|
||||
const [activeTopicId, activeThreadId, hasMore, isExpandingPageSize, openAllTopicsDrawer] =
|
||||
useChatStore((s) => [
|
||||
s.activeTopicId,
|
||||
s.activeThreadId,
|
||||
topicSelectors.hasMoreTopics(s),
|
||||
topicSelectors.isExpandingPageSize(s),
|
||||
s.openAllTopicsDrawer,
|
||||
]);
|
||||
const [activeTopicId, activeThreadId, hasMore, isExpandingPageSize] = useChatStore((s) => [
|
||||
s.activeTopicId,
|
||||
s.activeThreadId,
|
||||
topicSelectors.hasMoreTopics(s),
|
||||
topicSelectors.isExpandingPageSize(s),
|
||||
]);
|
||||
|
||||
const activeTopicList = useChatStore(
|
||||
topicSelectors.displayTopicsForSidebar(topicPageSize, topicSortBy),
|
||||
@@ -51,8 +53,8 @@ const FlatMode = memo(() => {
|
||||
/>
|
||||
))}
|
||||
{isExpandingPageSize && <SkeletonList rows={3} />}
|
||||
{hasMore && !isExpandingPageSize && (
|
||||
<NavItem icon={MoreHorizontal} title={t('loadMore')} onClick={openAllTopicsDrawer} />
|
||||
{onOpenDrawer && hasMore && !isExpandingPageSize && (
|
||||
<NavItem icon={MoreHorizontal} title={t('loadMore')} onClick={onOpenDrawer} />
|
||||
)}
|
||||
</Flexbox>
|
||||
);
|
||||
|
||||
@@ -26,18 +26,18 @@ export interface GroupItemComponentProps {
|
||||
|
||||
interface GroupedAccordionProps {
|
||||
GroupItem: ComponentType<GroupItemComponentProps>;
|
||||
onOpenDrawer?: () => void;
|
||||
}
|
||||
|
||||
const GroupedAccordion = memo<GroupedAccordionProps>(({ GroupItem }) => {
|
||||
const GroupedAccordion = memo<GroupedAccordionProps>(({ GroupItem, onOpenDrawer }) => {
|
||||
const { t } = useTranslation('topic');
|
||||
const topicPageSize = useGlobalStore(systemStatusSelectors.topicPageSize);
|
||||
const topicSortBy = useUserStore(preferenceSelectors.topicSortBy);
|
||||
const { topicGroupMode } = useAgentTopicGroupMode();
|
||||
|
||||
const [hasMore, isExpandingPageSize, openAllTopicsDrawer] = useChatStore((s) => [
|
||||
const [hasMore, isExpandingPageSize] = useChatStore((s) => [
|
||||
topicSelectors.hasMoreTopics(s),
|
||||
topicSelectors.isExpandingPageSize(s),
|
||||
s.openAllTopicsDrawer,
|
||||
]);
|
||||
const [activeTopicId, activeThreadId] = useChatStore((s) => [s.activeTopicId, s.activeThreadId]);
|
||||
|
||||
@@ -78,8 +78,8 @@ const GroupedAccordion = memo<GroupedAccordionProps>(({ GroupItem }) => {
|
||||
))}
|
||||
</Accordion>
|
||||
{isExpandingPageSize && <SkeletonList rows={3} />}
|
||||
{hasMore && !isExpandingPageSize && (
|
||||
<NavItem icon={MoreHorizontal} title={t('loadMore')} onClick={openAllTopicsDrawer} />
|
||||
{onOpenDrawer && hasMore && !isExpandingPageSize && (
|
||||
<NavItem icon={MoreHorizontal} title={t('loadMore')} onClick={onOpenDrawer} />
|
||||
)}
|
||||
</Flexbox>
|
||||
);
|
||||
|
||||
@@ -33,7 +33,6 @@ const TopicListContent = memo(() => {
|
||||
|
||||
if (isInSearchMode) return <SearchResult />;
|
||||
|
||||
// Show skeleton when current session's topic data is not yet loaded
|
||||
if (isUndefinedTopics) return <SkeletonList />;
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
'use client';
|
||||
|
||||
import { Flexbox, SearchBar } from '@lobehub/ui';
|
||||
import { memo, useState } from 'react';
|
||||
import { memo, type Ref, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import SkeletonList from '@/features/NavPanel/components/SkeletonList';
|
||||
import SideBarDrawer from '@/features/NavPanel/SideBarDrawer';
|
||||
import SideBarDrawer, { type SideBarDrawerHandle } from '@/features/NavPanel/SideBarDrawer';
|
||||
import dynamic from '@/libs/next/dynamic';
|
||||
|
||||
const Content = dynamic(() => import('./Content'), {
|
||||
@@ -18,17 +18,17 @@ const Content = dynamic(() => import('./Content'), {
|
||||
});
|
||||
|
||||
interface AllTopicsDrawerProps {
|
||||
onClose: () => void;
|
||||
open: boolean;
|
||||
ref?: Ref<SideBarDrawerHandle>;
|
||||
}
|
||||
|
||||
const AllTopicsDrawer = memo<AllTopicsDrawerProps>(({ open, onClose }) => {
|
||||
const AllTopicsDrawer = memo<AllTopicsDrawerProps>(({ ref }) => {
|
||||
const { t } = useTranslation('topic');
|
||||
const [searchKeyword, setSearchKeyword] = useState('');
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<SideBarDrawer
|
||||
open={open}
|
||||
ref={ref}
|
||||
title={t('title')}
|
||||
subHeader={
|
||||
<Flexbox paddingBlock={'0 8px'} paddingInline={8}>
|
||||
@@ -43,9 +43,9 @@ const AllTopicsDrawer = memo<AllTopicsDrawerProps>(({ open, onClose }) => {
|
||||
/>
|
||||
</Flexbox>
|
||||
}
|
||||
onClose={onClose}
|
||||
onOpenChange={setIsOpen}
|
||||
>
|
||||
<Content open={open} searchKeyword={searchKeyword} />
|
||||
<Content open={isOpen} searchKeyword={searchKeyword} />
|
||||
</SideBarDrawer>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
'use client';
|
||||
|
||||
import React, { memo } from 'react';
|
||||
import React, { memo, useCallback, useRef } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import urlJoin from 'url-join';
|
||||
|
||||
import EmptyNavItem from '@/features/NavPanel/components/EmptyNavItem';
|
||||
import SkeletonList from '@/features/NavPanel/components/SkeletonList';
|
||||
import { type SideBarDrawerHandle } from '@/features/NavPanel/SideBarDrawer';
|
||||
import { useFetchChatTopics } from '@/hooks/useFetchChatTopics';
|
||||
import { useQueryRoute } from '@/hooks/useQueryRoute';
|
||||
import { useAgentGroupStore } from '@/store/agentGroup';
|
||||
@@ -24,16 +25,14 @@ const TopicList = memo(() => {
|
||||
const topicLength = useChatStore((s) => topicSelectors.currentTopicLength(s));
|
||||
const isUndefinedTopics = useChatStore((s) => topicSelectors.isUndefinedTopics(s));
|
||||
const activeGroupId = useAgentGroupStore((s) => s.activeGroupId);
|
||||
const [allTopicsDrawerOpen, closeAllTopicsDrawer] = useChatStore((s) => [
|
||||
s.allTopicsDrawerOpen,
|
||||
s.closeAllTopicsDrawer,
|
||||
]);
|
||||
|
||||
const topicGroupMode = useUserStore(preferenceSelectors.topicGroupMode);
|
||||
|
||||
const drawerRef = useRef<SideBarDrawerHandle>(null);
|
||||
const openDrawer = useCallback(() => drawerRef.current?.open(), []);
|
||||
|
||||
useFetchChatTopics();
|
||||
|
||||
// Show skeleton when current session's topic data is not yet loaded
|
||||
if (isUndefinedTopics) return <SkeletonList />;
|
||||
|
||||
return (
|
||||
@@ -46,8 +45,12 @@ const TopicList = memo(() => {
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{topicGroupMode === 'flat' ? <FlatMode /> : <ByTimeMode />}
|
||||
<AllTopicsDrawer open={allTopicsDrawerOpen} onClose={closeAllTopicsDrawer} />
|
||||
{topicGroupMode === 'flat' ? (
|
||||
<FlatMode onOpenDrawer={openDrawer} />
|
||||
) : (
|
||||
<ByTimeMode onOpenDrawer={openDrawer} />
|
||||
)}
|
||||
<AllTopicsDrawer ref={drawerRef} />
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -17,16 +17,19 @@ import { preferenceSelectors } from '@/store/user/selectors';
|
||||
|
||||
import GroupItem from './GroupItem';
|
||||
|
||||
const ByTimeMode = memo(() => {
|
||||
interface ByTimeModeProps {
|
||||
onOpenDrawer?: () => void;
|
||||
}
|
||||
|
||||
const ByTimeMode = memo<ByTimeModeProps>(({ onOpenDrawer }) => {
|
||||
const { t } = useTranslation('topic');
|
||||
const topicPageSize = useGlobalStore(systemStatusSelectors.topicPageSize);
|
||||
const topicSortBy = useUserStore(preferenceSelectors.topicSortBy);
|
||||
const topicGroupMode = useUserStore(preferenceSelectors.topicGroupMode);
|
||||
|
||||
const [hasMore, isExpandingPageSize, openAllTopicsDrawer] = useChatStore((s) => [
|
||||
const [hasMore, isExpandingPageSize] = useChatStore((s) => [
|
||||
topicSelectors.hasMoreTopics(s),
|
||||
topicSelectors.isExpandingPageSize(s),
|
||||
s.openAllTopicsDrawer,
|
||||
]);
|
||||
const [activeTopicId, activeThreadId] = useChatStore((s) => [s.activeTopicId, s.activeThreadId]);
|
||||
|
||||
@@ -68,8 +71,8 @@ const ByTimeMode = memo(() => {
|
||||
))}
|
||||
</Accordion>
|
||||
{isExpandingPageSize && <SkeletonList rows={3} />}
|
||||
{hasMore && !isExpandingPageSize && (
|
||||
<NavItem icon={MoreHorizontal} title={t('loadMore')} onClick={openAllTopicsDrawer} />
|
||||
{onOpenDrawer && hasMore && !isExpandingPageSize && (
|
||||
<NavItem icon={MoreHorizontal} title={t('loadMore')} onClick={onOpenDrawer} />
|
||||
)}
|
||||
</Flexbox>
|
||||
);
|
||||
|
||||
@@ -17,19 +17,21 @@ import { preferenceSelectors } from '@/store/user/selectors';
|
||||
|
||||
import TopicItem from '../../List/Item';
|
||||
|
||||
const FlatMode = memo(() => {
|
||||
interface FlatModeProps {
|
||||
onOpenDrawer?: () => void;
|
||||
}
|
||||
|
||||
const FlatMode = memo<FlatModeProps>(({ onOpenDrawer }) => {
|
||||
const { t } = useTranslation('topic');
|
||||
const topicPageSize = useGlobalStore(systemStatusSelectors.topicPageSize);
|
||||
const topicSortBy = useUserStore(preferenceSelectors.topicSortBy);
|
||||
|
||||
const [activeTopicId, activeThreadId, hasMore, isExpandingPageSize, openAllTopicsDrawer] =
|
||||
useChatStore((s) => [
|
||||
s.activeTopicId,
|
||||
s.activeThreadId,
|
||||
topicSelectors.hasMoreTopics(s),
|
||||
topicSelectors.isExpandingPageSize(s),
|
||||
s.openAllTopicsDrawer,
|
||||
]);
|
||||
const [activeTopicId, activeThreadId, hasMore, isExpandingPageSize] = useChatStore((s) => [
|
||||
s.activeTopicId,
|
||||
s.activeThreadId,
|
||||
topicSelectors.hasMoreTopics(s),
|
||||
topicSelectors.isExpandingPageSize(s),
|
||||
]);
|
||||
|
||||
const activeTopicList = useChatStore(
|
||||
topicSelectors.displayTopicsForSidebar(topicPageSize, topicSortBy),
|
||||
@@ -50,8 +52,8 @@ const FlatMode = memo(() => {
|
||||
/>
|
||||
))}
|
||||
{isExpandingPageSize && <SkeletonList rows={3} />}
|
||||
{hasMore && !isExpandingPageSize && (
|
||||
<NavItem icon={MoreHorizontal} title={t('loadMore')} onClick={openAllTopicsDrawer} />
|
||||
{onOpenDrawer && hasMore && !isExpandingPageSize && (
|
||||
<NavItem icon={MoreHorizontal} title={t('loadMore')} onClick={onOpenDrawer} />
|
||||
)}
|
||||
</Flexbox>
|
||||
);
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
'use client';
|
||||
|
||||
import React, { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import urlJoin from 'url-join';
|
||||
|
||||
import EmptyNavItem from '@/features/NavPanel/components/EmptyNavItem';
|
||||
import SkeletonList from '@/features/NavPanel/components/SkeletonList';
|
||||
import { useFetchChatTopics } from '@/hooks/useFetchChatTopics';
|
||||
import { useQueryRoute } from '@/hooks/useQueryRoute';
|
||||
import { useAgentGroupStore } from '@/store/agentGroup';
|
||||
import { useChatStore } from '@/store/chat';
|
||||
import { topicSelectors } from '@/store/chat/selectors';
|
||||
import { useUserStore } from '@/store/user';
|
||||
import { preferenceSelectors } from '@/store/user/selectors';
|
||||
|
||||
import ByTimeMode from './ByTimeMode';
|
||||
import FlatMode from './FlatMode';
|
||||
import SearchResult from './SearchResult';
|
||||
|
||||
const TopicListContent = memo(() => {
|
||||
const { t } = useTranslation('topic');
|
||||
const router = useQueryRoute();
|
||||
const topicLength = useChatStore((s) => topicSelectors.currentTopicLength(s));
|
||||
const [isUndefinedTopics, isInSearchMode] = useChatStore((s) => [
|
||||
topicSelectors.isUndefinedTopics(s),
|
||||
topicSelectors.isInSearchMode(s),
|
||||
]);
|
||||
const activeGroupId = useAgentGroupStore((s) => s.activeGroupId);
|
||||
const topicGroupMode = useUserStore(preferenceSelectors.topicGroupMode);
|
||||
|
||||
useFetchChatTopics();
|
||||
|
||||
if (isInSearchMode) return <SearchResult />;
|
||||
|
||||
// Show skeleton when current session's topic data is not yet loaded
|
||||
if (isUndefinedTopics) return <SkeletonList />;
|
||||
|
||||
return (
|
||||
<>
|
||||
{topicLength === 0 && activeGroupId && (
|
||||
<EmptyNavItem
|
||||
title={t('actions.addNewTopic')}
|
||||
onClick={() => {
|
||||
router.push(urlJoin('/group', activeGroupId));
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{topicGroupMode === 'flat' ? <FlatMode /> : <ByTimeMode />}
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
TopicListContent.displayName = 'TopicListContent';
|
||||
|
||||
export default TopicListContent;
|
||||
@@ -14,36 +14,28 @@ import GroupItem from '../List/AgentGroupItem';
|
||||
import AgentItem from '../List/AgentItem';
|
||||
|
||||
interface ContentProps {
|
||||
open: boolean;
|
||||
onNavigate: () => void;
|
||||
searchKeyword: string;
|
||||
}
|
||||
|
||||
const Content = memo<ContentProps>(({ searchKeyword }) => {
|
||||
// Use server-side search if there's a keyword
|
||||
const Content = memo<ContentProps>(({ searchKeyword, onNavigate }) => {
|
||||
const trimmedKeyword = searchKeyword.trim();
|
||||
const isSearching = trimmedKeyword.length > 0;
|
||||
|
||||
// Search agents using homeStore
|
||||
const [closeAllAgentsDrawer, useSearchAgents] = useHomeStore((s) => [
|
||||
s.closeAllAgentsDrawer,
|
||||
s.useSearchAgents,
|
||||
]);
|
||||
const useSearchAgents = useHomeStore((s) => s.useSearchAgents);
|
||||
const { data: searchResults, isLoading: isSearchLoading } = useSearchAgents(
|
||||
isSearching ? trimmedKeyword : undefined,
|
||||
);
|
||||
|
||||
// Get all agents from homeStore (ungrouped agents for default view)
|
||||
const allUngroupedAgents = useHomeStore(homeAgentListSelectors.ungroupedAgents, isEqual);
|
||||
|
||||
// Filter and display - searchResults already returns SidebarAgentItem[]
|
||||
const displayItems = isSearching ? searchResults || [] : allUngroupedAgents;
|
||||
|
||||
const count = displayItems.length;
|
||||
|
||||
// Close on navigation because the Home layout stays mounted offscreen across route changes.
|
||||
const handleNavigate = closeAllAgentsDrawer;
|
||||
const handleNavigate = onNavigate;
|
||||
|
||||
// Show loading skeleton when searching
|
||||
if (isSearching && (isSearchLoading || !searchResults)) {
|
||||
return (
|
||||
<Flexbox gap={1} paddingBlock={1} paddingInline={4}>
|
||||
@@ -52,7 +44,6 @@ const Content = memo<ContentProps>(({ searchKeyword }) => {
|
||||
);
|
||||
}
|
||||
|
||||
// Show empty state when no agents
|
||||
if (count === 0) {
|
||||
return <AgentSelectionEmpty search={isSearching} />;
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
'use client';
|
||||
|
||||
import { Flexbox, SearchBar } from '@lobehub/ui';
|
||||
import { memo, useState } from 'react';
|
||||
import { memo, type Ref, useCallback, useRef, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import SkeletonList from '@/features/NavPanel/components/SkeletonList';
|
||||
import SideBarDrawer from '@/features/NavPanel/SideBarDrawer';
|
||||
import SideBarDrawer, { type SideBarDrawerHandle } from '@/features/NavPanel/SideBarDrawer';
|
||||
import dynamic from '@/libs/next/dynamic';
|
||||
|
||||
const Content = dynamic(() => import('./Content'), {
|
||||
@@ -18,17 +18,31 @@ const Content = dynamic(() => import('./Content'), {
|
||||
});
|
||||
|
||||
interface AllAgentsDrawerProps {
|
||||
onClose: () => void;
|
||||
open: boolean;
|
||||
ref?: Ref<SideBarDrawerHandle>;
|
||||
}
|
||||
|
||||
const AllAgentsDrawer = memo<AllAgentsDrawerProps>(({ open, onClose }) => {
|
||||
const AllAgentsDrawer = memo<AllAgentsDrawerProps>(({ ref: externalRef }) => {
|
||||
const { t } = useTranslation('common');
|
||||
const [searchKeyword, setSearchKeyword] = useState('');
|
||||
const handleRef = useRef<SideBarDrawerHandle | null>(null);
|
||||
|
||||
const setHandle = useCallback(
|
||||
(handle: SideBarDrawerHandle | null) => {
|
||||
handleRef.current = handle;
|
||||
if (typeof externalRef === 'function') {
|
||||
externalRef(handle);
|
||||
} else if (externalRef) {
|
||||
externalRef.current = handle;
|
||||
}
|
||||
},
|
||||
[externalRef],
|
||||
);
|
||||
|
||||
const handleNavigate = useCallback(() => handleRef.current?.close(), []);
|
||||
|
||||
return (
|
||||
<SideBarDrawer
|
||||
open={open}
|
||||
ref={setHandle}
|
||||
title={t('navPanel.agent')}
|
||||
subHeader={
|
||||
<Flexbox paddingBlock={'0 8px'} paddingInline={8}>
|
||||
@@ -43,9 +57,8 @@ const AllAgentsDrawer = memo<AllAgentsDrawerProps>(({ open, onClose }) => {
|
||||
/>
|
||||
</Flexbox>
|
||||
}
|
||||
onClose={onClose}
|
||||
>
|
||||
<Content open={open} searchKeyword={searchKeyword} />
|
||||
<Content searchKeyword={searchKeyword} onNavigate={handleNavigate} />
|
||||
</SideBarDrawer>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -35,7 +35,6 @@ const List = memo<SessionListProps>(
|
||||
const isDefaultList = groupId === SessionDefaultGroup.Default;
|
||||
const ungroupedAgentsCount = useHomeStore(homeAgentListSelectors.ungroupedAgentsCount);
|
||||
const agentPageSize = useGlobalStore(systemStatusSelectors.agentPageSize);
|
||||
const openAllAgentsDrawer = useHomeStore((s) => s.openAllAgentsDrawer);
|
||||
|
||||
const hasMore = isDefaultList && ungroupedAgentsCount > agentPageSize;
|
||||
|
||||
@@ -59,12 +58,8 @@ const List = memo<SessionListProps>(
|
||||
<AgentItem className={itemClassName} item={item} key={item.id} style={itemStyle} />
|
||||
),
|
||||
)}
|
||||
{hasMore && (
|
||||
<NavItem
|
||||
icon={MoreHorizontal}
|
||||
title={t('input.more')}
|
||||
onClick={onMoreClick || openAllAgentsDrawer}
|
||||
/>
|
||||
{hasMore && onMoreClick && (
|
||||
<NavItem icon={MoreHorizontal} title={t('input.more')} onClick={onMoreClick} />
|
||||
)}
|
||||
{showCreateButton && <CreateAgentButton className={itemClassName} groupId={groupId} />}
|
||||
</Flexbox>
|
||||
|
||||
@@ -1,23 +1,21 @@
|
||||
'use client';
|
||||
|
||||
import { memo } from 'react';
|
||||
import { memo, useCallback, useRef } from 'react';
|
||||
|
||||
import { useHomeStore } from '@/store/home';
|
||||
import { type SideBarDrawerHandle } from '@/features/NavPanel/SideBarDrawer';
|
||||
|
||||
import AllAgentsDrawer from '../AllAgentsDrawer';
|
||||
import AgentListContent from './AgentListContent';
|
||||
|
||||
// The Home sidebar owns the all-agents drawer; other surfaces should import AgentListContent directly.
|
||||
const AgentList = memo<{ onMoreClick?: () => void }>(({ onMoreClick }) => {
|
||||
const [allAgentsDrawerOpen, closeAllAgentsDrawer] = useHomeStore((s) => [
|
||||
s.allAgentsDrawerOpen,
|
||||
s.closeAllAgentsDrawer,
|
||||
]);
|
||||
const drawerRef = useRef<SideBarDrawerHandle>(null);
|
||||
const openDrawer = useCallback(() => drawerRef.current?.open(), []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<AgentListContent onMoreClick={onMoreClick} />
|
||||
<AllAgentsDrawer open={allAgentsDrawerOpen} onClose={closeAllAgentsDrawer} />
|
||||
<AgentListContent onMoreClick={onMoreClick ?? openDrawer} />
|
||||
<AllAgentsDrawer ref={drawerRef} />
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -3,10 +3,11 @@
|
||||
import { ActionIcon } from '@lobehub/ui';
|
||||
import { Badge } from 'antd';
|
||||
import { BellIcon } from 'lucide-react';
|
||||
import { memo, useCallback, useState } from 'react';
|
||||
import { memo, useCallback, useRef } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { DESKTOP_HEADER_ICON_SMALL_SIZE } from '@/const/layoutTokens';
|
||||
import { type SideBarDrawerHandle } from '@/features/NavPanel/SideBarDrawer';
|
||||
import { useClientDataSWR } from '@/libs/swr';
|
||||
import { notificationService } from '@/services/notification';
|
||||
import { serverConfigSelectors, useServerConfigStore } from '@/store/serverConfig';
|
||||
@@ -16,7 +17,6 @@ import { UNREAD_COUNT_KEY } from './InboxDrawer/constants';
|
||||
|
||||
const InboxButton = memo(() => {
|
||||
const { t } = useTranslation('notification');
|
||||
const [open, setOpen] = useState(false);
|
||||
const enableBusinessFeatures = useServerConfigStore(serverConfigSelectors.enableBusinessFeatures);
|
||||
|
||||
const { data: unreadCount = 0 } = useClientDataSWR<number>(
|
||||
@@ -25,13 +25,8 @@ const InboxButton = memo(() => {
|
||||
{ refreshInterval: 10_000 },
|
||||
);
|
||||
|
||||
const handleToggle = useCallback(() => {
|
||||
setOpen((prev) => !prev);
|
||||
}, []);
|
||||
|
||||
const handleClose = useCallback(() => {
|
||||
setOpen(false);
|
||||
}, []);
|
||||
const drawerRef = useRef<SideBarDrawerHandle>(null);
|
||||
const handleOpen = useCallback(() => drawerRef.current?.open(), []);
|
||||
|
||||
if (!enableBusinessFeatures) return null;
|
||||
|
||||
@@ -42,10 +37,10 @@ const InboxButton = memo(() => {
|
||||
icon={BellIcon}
|
||||
size={DESKTOP_HEADER_ICON_SMALL_SIZE}
|
||||
title={t('inbox.title')}
|
||||
onClick={handleToggle}
|
||||
onClick={handleOpen}
|
||||
/>
|
||||
</Badge>
|
||||
<InboxDrawer open={open} onClose={handleClose} />
|
||||
<InboxDrawer ref={drawerRef} />
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
|
||||
import { ActionIcon, Flexbox } from '@lobehub/ui';
|
||||
import { ArchiveIcon, CheckCheckIcon, ListFilterIcon } from 'lucide-react';
|
||||
import { memo, useCallback, useState } from 'react';
|
||||
import { memo, type Ref, useCallback, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { DESKTOP_HEADER_ICON_SIZE } from '@/const/layoutTokens';
|
||||
import SkeletonList from '@/features/NavPanel/components/SkeletonList';
|
||||
import SideBarDrawer from '@/features/NavPanel/SideBarDrawer';
|
||||
import SideBarDrawer, { type SideBarDrawerHandle } from '@/features/NavPanel/SideBarDrawer';
|
||||
import dynamic from '@/libs/next/dynamic';
|
||||
import { mutate } from '@/libs/swr';
|
||||
import { notificationService } from '@/services/notification';
|
||||
@@ -24,13 +24,13 @@ const Content = dynamic(() => import('./Content'), {
|
||||
});
|
||||
|
||||
interface InboxDrawerProps {
|
||||
onClose: () => void;
|
||||
open: boolean;
|
||||
ref?: Ref<SideBarDrawerHandle>;
|
||||
}
|
||||
|
||||
const InboxDrawer = memo<InboxDrawerProps>(({ open, onClose }) => {
|
||||
const InboxDrawer = memo<InboxDrawerProps>(({ ref }) => {
|
||||
const { t } = useTranslation('notification');
|
||||
const [unreadOnly, setUnreadOnly] = useState(false);
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
const refreshList = useCallback(() => {
|
||||
mutate((key: unknown) => Array.isArray(key) && key[0] === FETCH_KEY);
|
||||
@@ -69,7 +69,7 @@ const InboxDrawer = memo<InboxDrawerProps>(({ open, onClose }) => {
|
||||
|
||||
return (
|
||||
<SideBarDrawer
|
||||
open={open}
|
||||
ref={ref}
|
||||
title={t('inbox.title')}
|
||||
action={
|
||||
<>
|
||||
@@ -94,10 +94,10 @@ const InboxDrawer = memo<InboxDrawerProps>(({ open, onClose }) => {
|
||||
/>
|
||||
</>
|
||||
}
|
||||
onClose={onClose}
|
||||
onOpenChange={setIsOpen}
|
||||
>
|
||||
<Content
|
||||
open={open}
|
||||
open={isOpen}
|
||||
unreadOnly={unreadOnly}
|
||||
onArchive={handleArchive}
|
||||
onMarkAsRead={handleMarkAsRead}
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
|
||||
import { Empty, Flexbox, SearchBar } from '@lobehub/ui';
|
||||
import { SearchIcon } from 'lucide-react';
|
||||
import { memo, useCallback, useMemo, useState } from 'react';
|
||||
import { memo, type Ref, useCallback, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import SkeletonList from '@/features/NavPanel/components/SkeletonList';
|
||||
import SideBarDrawer from '@/features/NavPanel/SideBarDrawer';
|
||||
import SideBarDrawer, { type SideBarDrawerHandle } from '@/features/NavPanel/SideBarDrawer';
|
||||
import { useClientDataSWR } from '@/libs/swr';
|
||||
import { recentService } from '@/services/recent';
|
||||
import { ALL_RECENTS_DRAWER_SWR_PREFIX } from '@/store/home/slices/recent/action';
|
||||
@@ -15,16 +15,16 @@ import { ALL_RECENTS_DRAWER_SWR_PREFIX } from '@/store/home/slices/recent/action
|
||||
import RecentListItem from './Item';
|
||||
|
||||
interface AllRecentsDrawerProps {
|
||||
onClose: () => void;
|
||||
open: boolean;
|
||||
ref?: Ref<SideBarDrawerHandle>;
|
||||
}
|
||||
|
||||
const AllRecentsDrawer = memo<AllRecentsDrawerProps>(({ open, onClose }) => {
|
||||
const AllRecentsDrawer = memo<AllRecentsDrawerProps>(({ ref }) => {
|
||||
const { t } = useTranslation('common');
|
||||
const [searchKeyword, setSearchKeyword] = useState('');
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
const { data: recents, isLoading } = useClientDataSWR(
|
||||
open ? [ALL_RECENTS_DRAWER_SWR_PREFIX, open] : null,
|
||||
isOpen ? [ALL_RECENTS_DRAWER_SWR_PREFIX, isOpen] : null,
|
||||
() => recentService.getAll(50),
|
||||
);
|
||||
|
||||
@@ -39,13 +39,12 @@ const AllRecentsDrawer = memo<AllRecentsDrawerProps>(({ open, onClose }) => {
|
||||
if (item.type !== 'task') return item.routePath;
|
||||
const taskId = item.id;
|
||||
if (!taskId) return item.routePath;
|
||||
|
||||
return `/task/${taskId}`;
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<SideBarDrawer
|
||||
open={open}
|
||||
ref={ref}
|
||||
title={t('recents')}
|
||||
subHeader={
|
||||
<Flexbox paddingBlock={'0 8px'} paddingInline={8}>
|
||||
@@ -60,7 +59,7 @@ const AllRecentsDrawer = memo<AllRecentsDrawerProps>(({ open, onClose }) => {
|
||||
/>
|
||||
</Flexbox>
|
||||
}
|
||||
onClose={onClose}
|
||||
onOpenChange={setIsOpen}
|
||||
>
|
||||
<Flexbox gap={1} paddingBlock={1} paddingInline={4}>
|
||||
{isLoading || !recents ? (
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { Flexbox } from '@lobehub/ui';
|
||||
import { MoreHorizontalIcon } from 'lucide-react';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import { memo, useCallback, useMemo, useRef } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import NavItem from '@/features/NavPanel/components/NavItem';
|
||||
import SkeletonList from '@/features/NavPanel/components/SkeletonList';
|
||||
import { type SideBarDrawerHandle } from '@/features/NavPanel/SideBarDrawer';
|
||||
import { useGlobalStore } from '@/store/global';
|
||||
import { systemStatusSelectors } from '@/store/global/selectors';
|
||||
import { useHomeStore } from '@/store/home';
|
||||
@@ -19,11 +20,9 @@ const RecentsList = memo(() => {
|
||||
const recents = useHomeStore(homeRecentSelectors.recents);
|
||||
const isInit = useHomeStore(homeRecentSelectors.isRecentsInit);
|
||||
const recentPageSize = useGlobalStore(systemStatusSelectors.recentPageSize);
|
||||
const [drawerOpen, openDrawer, closeDrawer] = useHomeStore((s) => [
|
||||
s.allRecentsDrawerOpen,
|
||||
s.openAllRecentsDrawer,
|
||||
s.closeAllRecentsDrawer,
|
||||
]);
|
||||
|
||||
const drawerRef = useRef<SideBarDrawerHandle>(null);
|
||||
const openDrawer = useCallback(() => drawerRef.current?.open(), []);
|
||||
|
||||
const displayItems = useMemo(() => recents.slice(0, recentPageSize), [recents, recentPageSize]);
|
||||
const hasMore = recents.length > recentPageSize;
|
||||
@@ -32,7 +31,6 @@ const RecentsList = memo(() => {
|
||||
if (item.type !== 'task') return item.routePath;
|
||||
const taskId = item.id;
|
||||
if (!taskId) return item.routePath;
|
||||
|
||||
return `/task/${taskId}`;
|
||||
}, []);
|
||||
|
||||
@@ -54,7 +52,7 @@ const RecentsList = memo(() => {
|
||||
{hasMore && (
|
||||
<NavItem icon={MoreHorizontalIcon} title={t('input.more')} onClick={openDrawer} />
|
||||
)}
|
||||
<AllRecentsDrawer open={drawerOpen} onClose={closeDrawer} />
|
||||
<AllRecentsDrawer ref={drawerRef} />
|
||||
</Flexbox>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -82,14 +82,6 @@ export class ChatTopicActionImpl {
|
||||
this.#get = get;
|
||||
}
|
||||
|
||||
closeAllTopicsDrawer = (): void => {
|
||||
this.#set({ allTopicsDrawerOpen: false }, false, n('closeAllTopicsDrawer'));
|
||||
};
|
||||
|
||||
openAllTopicsDrawer = (): void => {
|
||||
this.#set({ allTopicsDrawerOpen: true }, false, n('openAllTopicsDrawer'));
|
||||
};
|
||||
|
||||
openNewTopicOrSaveTopic = async (): Promise<void> => {
|
||||
const { switchTopic, saveToTopic, refreshMessages, activeTopicId } = this.#get();
|
||||
const hasTopic = !!activeTopicId;
|
||||
|
||||
@@ -23,10 +23,6 @@ export interface TopicData {
|
||||
export interface ChatTopicState {
|
||||
// TODO: need to add the null to the type
|
||||
activeTopicId?: string;
|
||||
/**
|
||||
* whether all topics drawer is open
|
||||
*/
|
||||
allTopicsDrawerOpen: boolean;
|
||||
creatingTopic: boolean;
|
||||
inSearchingMode?: boolean;
|
||||
isSearchingTopic: boolean;
|
||||
@@ -43,7 +39,6 @@ export interface ChatTopicState {
|
||||
|
||||
export const initialTopicState: ChatTopicState = {
|
||||
activeTopicId: null as any,
|
||||
allTopicsDrawerOpen: false,
|
||||
creatingTopic: false,
|
||||
isSearchingTopic: false,
|
||||
searchTopics: [],
|
||||
|
||||
@@ -29,14 +29,6 @@ export class AgentListActionImpl {
|
||||
this.#get = get;
|
||||
}
|
||||
|
||||
closeAllAgentsDrawer = (): void => {
|
||||
this.#set({ allAgentsDrawerOpen: false }, false, n('closeAllAgentsDrawer'));
|
||||
};
|
||||
|
||||
openAllAgentsDrawer = (): void => {
|
||||
this.#set({ allAgentsDrawerOpen: true }, false, n('openAllAgentsDrawer'));
|
||||
};
|
||||
|
||||
refreshAgentList = async (): Promise<void> => {
|
||||
await mutate([FETCH_AGENT_LIST_KEY, true]);
|
||||
};
|
||||
|
||||
@@ -9,10 +9,6 @@ export interface AgentListState {
|
||||
* Agent groups (user-defined folders)
|
||||
*/
|
||||
agentGroups: SidebarGroup[];
|
||||
/**
|
||||
* Whether all agents drawer is open
|
||||
*/
|
||||
allAgentsDrawerOpen: boolean;
|
||||
/**
|
||||
* Whether the agent list has been initialized
|
||||
*/
|
||||
@@ -29,7 +25,6 @@ export interface AgentListState {
|
||||
|
||||
export const initialAgentListState: AgentListState = {
|
||||
agentGroups: [],
|
||||
allAgentsDrawerOpen: false,
|
||||
isAgentListInit: false,
|
||||
pinnedAgents: [],
|
||||
ungroupedAgents: [],
|
||||
|
||||
@@ -33,14 +33,6 @@ export class RecentActionImpl {
|
||||
this.#get = get;
|
||||
}
|
||||
|
||||
closeAllRecentsDrawer = (): void => {
|
||||
this.#set({ allRecentsDrawerOpen: false }, false, n('closeAllRecentsDrawer'));
|
||||
};
|
||||
|
||||
openAllRecentsDrawer = (): void => {
|
||||
this.#set({ allRecentsDrawerOpen: true }, false, n('openAllRecentsDrawer'));
|
||||
};
|
||||
|
||||
updateRecentTitle = (id: string, title: string): void => {
|
||||
const recents = this.#get().recents.map((item) => (item.id === id ? { ...item, title } : item));
|
||||
this.#set({ recents }, false, n('updateRecentTitle'));
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
import { type RecentItem } from '@/server/routers/lambda/recent';
|
||||
|
||||
export interface RecentState {
|
||||
allRecentsDrawerOpen: boolean;
|
||||
isRecentsInit: boolean;
|
||||
recents: RecentItem[];
|
||||
}
|
||||
|
||||
export const initialRecentState: RecentState = {
|
||||
allRecentsDrawerOpen: false,
|
||||
isRecentsInit: false,
|
||||
recents: [],
|
||||
};
|
||||
|
||||
@@ -8,11 +8,6 @@ export interface PageQueryFilter {
|
||||
}
|
||||
|
||||
export interface PageState {
|
||||
// ===== Selection & Navigation =====
|
||||
/**
|
||||
* Whether all pages drawer is open
|
||||
*/
|
||||
allPagesDrawerOpen: boolean;
|
||||
// ===== List Management =====
|
||||
/**
|
||||
* Current page number (0-based) for pagination
|
||||
@@ -66,9 +61,6 @@ export interface PageState {
|
||||
}
|
||||
|
||||
export const initialState: PageState = {
|
||||
// Selection & Navigation
|
||||
allPagesDrawerOpen: false,
|
||||
|
||||
// List Management
|
||||
currentPage: 0,
|
||||
|
||||
|
||||
@@ -19,14 +19,6 @@ export class SelectionActionImpl {
|
||||
this.#get = get;
|
||||
}
|
||||
|
||||
closeAllPagesDrawer = (): void => {
|
||||
this.#set({ allPagesDrawerOpen: false }, false, n('closeAllPagesDrawer'));
|
||||
};
|
||||
|
||||
openAllPagesDrawer = (): void => {
|
||||
this.#set({ allPagesDrawerOpen: true }, false, n('openAllPagesDrawer'));
|
||||
};
|
||||
|
||||
selectPage = (pageId: string): void => {
|
||||
const { selectedPageId } = this.#get();
|
||||
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
export interface SelectionState {
|
||||
allPagesDrawerOpen: boolean;
|
||||
renamingPageId: string | null;
|
||||
selectedPageId: string | null;
|
||||
}
|
||||
|
||||
export const initialSelectionState: SelectionState = {
|
||||
allPagesDrawerOpen: false,
|
||||
renamingPageId: null,
|
||||
selectedPageId: null,
|
||||
};
|
||||
|
||||
@@ -55,10 +55,6 @@ export class SessionActionImpl {
|
||||
await this.#get().refreshSessions();
|
||||
};
|
||||
|
||||
closeAllAgentsDrawer = (): void => {
|
||||
this.#set({ allAgentsDrawerOpen: false }, false, n('closeAllAgentsDrawer'));
|
||||
};
|
||||
|
||||
/** @deprecated Use agentStore.createAgent instead */
|
||||
createSession = async (
|
||||
agent?: PartialDeep<LobeAgentSession>,
|
||||
@@ -133,10 +129,6 @@ export class SessionActionImpl {
|
||||
switchSession(newId);
|
||||
};
|
||||
|
||||
openAllAgentsDrawer = (): void => {
|
||||
this.#set({ allAgentsDrawerOpen: true }, false, n('openAllAgentsDrawer'));
|
||||
};
|
||||
|
||||
pinSession = async (id: string, pinned: boolean): Promise<void> => {
|
||||
await this.#get().internal_updateSession(id, { pinned });
|
||||
};
|
||||
|
||||
@@ -7,10 +7,6 @@ export interface SessionState {
|
||||
* @description The session currently being edited or viewed
|
||||
*/
|
||||
activeId: string;
|
||||
/**
|
||||
* whether all agents drawer is open
|
||||
*/
|
||||
allAgentsDrawerOpen: boolean;
|
||||
defaultSessions: LobeSessions;
|
||||
/**
|
||||
* @title Whether the agent panel is pinned
|
||||
@@ -40,7 +36,6 @@ export interface SessionState {
|
||||
|
||||
export const initialSessionState: SessionState = {
|
||||
activeId: 'inbox',
|
||||
allAgentsDrawerOpen: false,
|
||||
defaultSessions: [],
|
||||
isAgentPinned: false,
|
||||
isSearching: false,
|
||||
|
||||
Reference in New Issue
Block a user