diff --git a/src/app/(main)/chat/(workspace)/@topic/_layout/Desktop.tsx b/src/app/(main)/chat/(workspace)/@topic/_layout/Desktop.tsx
new file mode 100644
index 000000000000..522f38621584
--- /dev/null
+++ b/src/app/(main)/chat/(workspace)/@topic/_layout/Desktop.tsx
@@ -0,0 +1,17 @@
+import { PropsWithChildren } from 'react';
+import { Flexbox } from 'react-layout-kit';
+
+import Header from '../features/Header';
+
+const Layout = ({ children }: PropsWithChildren) => {
+ return (
+ <>
+
+
+ {children}
+
+ >
+ );
+};
+
+export default Layout;
diff --git a/src/app/(main)/chat/(workspace)/@topic/_layout/Mobile.tsx b/src/app/(main)/chat/(workspace)/@topic/_layout/Mobile.tsx
new file mode 100644
index 000000000000..296f57178070
--- /dev/null
+++ b/src/app/(main)/chat/(workspace)/@topic/_layout/Mobile.tsx
@@ -0,0 +1,21 @@
+import { PropsWithChildren } from 'react';
+import { Flexbox } from 'react-layout-kit';
+
+import TopicSearchBar from '../features/TopicSearchBar';
+
+const Layout = ({ children }: PropsWithChildren) => {
+ return (
+
+
+
+ {children}
+
+
+ );
+};
+
+export default Layout;
diff --git a/src/app/(main)/chat/(workspace)/@topic/default.tsx b/src/app/(main)/chat/(workspace)/@topic/default.tsx
index 6e99bfc39ed4..b776c5f97b4f 100644
--- a/src/app/(main)/chat/(workspace)/@topic/default.tsx
+++ b/src/app/(main)/chat/(workspace)/@topic/default.tsx
@@ -1,15 +1,21 @@
import { isMobileDevice } from '@/utils/responsive';
+import Desktop from './_layout/Desktop';
+import Mobile from './_layout/Mobile';
import SystemRole from './features/SystemRole';
import TopicListContent from './features/TopicListContent';
const Topic = () => {
const mobile = isMobileDevice();
+ const Layout = mobile ? Mobile : Desktop;
+
return (
<>
{!mobile && }
-
+
+
+
>
);
};
diff --git a/src/app/(main)/chat/(workspace)/@topic/features/TopicListContent/Header.tsx b/src/app/(main)/chat/(workspace)/@topic/features/Header.tsx
similarity index 100%
rename from src/app/(main)/chat/(workspace)/@topic/features/TopicListContent/Header.tsx
rename to src/app/(main)/chat/(workspace)/@topic/features/Header.tsx
diff --git a/src/app/(main)/chat/(workspace)/@topic/features/TopicListContent/Topic/DefaultContent.tsx b/src/app/(main)/chat/(workspace)/@topic/features/TopicListContent/DefaultContent.tsx
similarity index 100%
rename from src/app/(main)/chat/(workspace)/@topic/features/TopicListContent/Topic/DefaultContent.tsx
rename to src/app/(main)/chat/(workspace)/@topic/features/TopicListContent/DefaultContent.tsx
diff --git a/src/app/(main)/chat/(workspace)/@topic/features/TopicListContent/Topic/SkeletonList.tsx b/src/app/(main)/chat/(workspace)/@topic/features/TopicListContent/SkeletonList.tsx
similarity index 96%
rename from src/app/(main)/chat/(workspace)/@topic/features/TopicListContent/Topic/SkeletonList.tsx
rename to src/app/(main)/chat/(workspace)/@topic/features/TopicListContent/SkeletonList.tsx
index 1b18f4df84b8..41d2b776d1eb 100644
--- a/src/app/(main)/chat/(workspace)/@topic/features/TopicListContent/Topic/SkeletonList.tsx
+++ b/src/app/(main)/chat/(workspace)/@topic/features/TopicListContent/SkeletonList.tsx
@@ -45,7 +45,7 @@ export const Placeholder = memo(() => {
});
export const SkeletonList = memo(() => (
-
+
{Array.from({ length: 8 }).map((_, i) => (
))}
diff --git a/src/app/(main)/chat/(workspace)/@topic/features/TopicListContent/Topic/index.tsx b/src/app/(main)/chat/(workspace)/@topic/features/TopicListContent/Topic/index.tsx
deleted file mode 100644
index 89f221c3d743..000000000000
--- a/src/app/(main)/chat/(workspace)/@topic/features/TopicListContent/Topic/index.tsx
+++ /dev/null
@@ -1,101 +0,0 @@
-'use client';
-
-import { EmptyCard } from '@lobehub/ui';
-import { useThemeMode } from 'antd-style';
-import isEqual from 'fast-deep-equal';
-import React, { memo, useCallback, useRef } from 'react';
-import { useTranslation } from 'react-i18next';
-import { Flexbox } from 'react-layout-kit';
-import { Virtuoso, VirtuosoHandle } from 'react-virtuoso';
-
-import { imageUrl } from '@/const/url';
-import { useChatStore } from '@/store/chat';
-import { topicSelectors } from '@/store/chat/selectors';
-import { useUserStore } from '@/store/user';
-import { ChatTopic } from '@/types/topic';
-
-import { Placeholder, SkeletonList } from './SkeletonList';
-import TopicItem from './TopicItem';
-
-export const Topic = memo<{ mobile?: boolean }>(({ mobile }) => {
- const { t } = useTranslation('chat');
- const virtuosoRef = useRef(null);
- const { isDarkMode } = useThemeMode();
- const [topicsInit, activeTopicId, topicLength] = useChatStore((s) => [
- s.topicsInit,
- s.activeTopicId,
- topicSelectors.currentTopicLength(s),
- ]);
- const [visible, updateGuideState] = useUserStore((s) => [
- s.preference.guide?.topic,
- s.updateGuideState,
- ]);
-
- const topics = useChatStore(
- (s) => [
- {
- favorite: false,
- id: 'default',
- title: t('topic.defaultTitle'),
- } as ChatTopic,
- ...topicSelectors.displayTopics(s),
- ],
- isEqual,
- );
-
- const itemContent = useCallback(
- (index: number, { id, favorite, title }: ChatTopic) =>
- index === 0 ? (
-
- ) : (
-
- ),
- [activeTopicId],
- );
-
- const activeIndex = topics.findIndex((topic) => topic.id === activeTopicId);
-
- return !topicsInit ? (
-
- ) : (
-
- {topicLength === 0 && (
- {
- updateGuideState({ topic: visible });
- }}
- style={{ flex: 'none', marginBottom: 12 }}
- title={t('topic.guide.title')}
- visible={visible}
- width={200}
- />
- )}
- item.id}
- data={topics}
- fixedItemHeight={44}
- initialTopMostItemIndex={Math.max(activeIndex, 0)}
- itemContent={itemContent}
- overscan={44 * 10}
- ref={virtuosoRef}
- scrollSeekConfiguration={{
- enter: (velocity) => Math.abs(velocity) > 350,
- exit: (velocity) => Math.abs(velocity) < 10,
- }}
- />
-
- );
-});
diff --git a/src/app/(main)/chat/(workspace)/@topic/features/TopicListContent/Topic/TopicContent.tsx b/src/app/(main)/chat/(workspace)/@topic/features/TopicListContent/TopicContent.tsx
similarity index 100%
rename from src/app/(main)/chat/(workspace)/@topic/features/TopicListContent/Topic/TopicContent.tsx
rename to src/app/(main)/chat/(workspace)/@topic/features/TopicListContent/TopicContent.tsx
diff --git a/src/app/(main)/chat/(workspace)/@topic/features/TopicListContent/Topic/TopicItem.tsx b/src/app/(main)/chat/(workspace)/@topic/features/TopicListContent/TopicItem.tsx
similarity index 95%
rename from src/app/(main)/chat/(workspace)/@topic/features/TopicListContent/Topic/TopicItem.tsx
rename to src/app/(main)/chat/(workspace)/@topic/features/TopicListContent/TopicItem.tsx
index 66779df79b34..42252183f25f 100644
--- a/src/app/(main)/chat/(workspace)/@topic/features/TopicListContent/Topic/TopicItem.tsx
+++ b/src/app/(main)/chat/(workspace)/@topic/features/TopicListContent/TopicItem.tsx
@@ -19,7 +19,12 @@ const useStyles = createStyles(({ css, token, isDarkMode }) => ({
`,
container: css`
cursor: pointer;
+
+ width: calc(100% - 16px);
+ margin-block: 2px;
+ margin-inline: 8px;
padding: 8px;
+
border-radius: ${token.borderRadius}px;
&:hover {
diff --git a/src/app/(main)/chat/(workspace)/@topic/features/TopicListContent/index.tsx b/src/app/(main)/chat/(workspace)/@topic/features/TopicListContent/index.tsx
index 381aae2203da..de2a4a911f76 100644
--- a/src/app/(main)/chat/(workspace)/@topic/features/TopicListContent/index.tsx
+++ b/src/app/(main)/chat/(workspace)/@topic/features/TopicListContent/index.tsx
@@ -1,18 +1,100 @@
+'use client';
+
+import { EmptyCard } from '@lobehub/ui';
+import { useThemeMode } from 'antd-style';
+import isEqual from 'fast-deep-equal';
+import React, { memo, useCallback, useRef } from 'react';
+import { useTranslation } from 'react-i18next';
import { Flexbox } from 'react-layout-kit';
+import { Virtuoso, VirtuosoHandle } from 'react-virtuoso';
+
+import { imageUrl } from '@/const/url';
+import { useChatStore } from '@/store/chat';
+import { topicSelectors } from '@/store/chat/selectors';
+import { useUserStore } from '@/store/user';
+import { ChatTopic } from '@/types/topic';
+
+import { Placeholder, SkeletonList } from './SkeletonList';
+import TopicItem from './TopicItem';
+
+const TopicListContent = memo(() => {
+ const { t } = useTranslation('chat');
+ const virtuosoRef = useRef(null);
+ const { isDarkMode } = useThemeMode();
+ const [topicsInit, activeTopicId, topicLength] = useChatStore((s) => [
+ s.topicsInit,
+ s.activeTopicId,
+ topicSelectors.currentTopicLength(s),
+ ]);
+ const [visible, updateGuideState] = useUserStore((s) => [
+ s.preference.guide?.topic,
+ s.updateGuideState,
+ ]);
+
+ const topics = useChatStore(
+ (s) => [
+ {
+ favorite: false,
+ id: 'default',
+ title: t('topic.defaultTitle'),
+ } as ChatTopic,
+ ...topicSelectors.displayTopics(s),
+ ],
+ isEqual,
+ );
+
+ const itemContent = useCallback(
+ (index: number, { id, favorite, title }: ChatTopic) =>
+ index === 0 ? (
+
+ ) : (
+
+ ),
+ [activeTopicId],
+ );
+
+ const activeIndex = topics.findIndex((topic) => topic.id === activeTopicId);
-import Header from './Header';
-import { Topic } from './Topic';
-import TopicSearchBar from './TopicSearchBar';
-
-const TopicListContent = ({ mobile }: { mobile?: boolean }) => {
- return (
-
- {mobile ? : }
-
-
-
-
+ return !topicsInit ? (
+
+ ) : (
+ <>
+ {topicLength === 0 && visible && (
+
+ {
+ updateGuideState({ topic: visible });
+ }}
+ style={{ flex: 'none', marginBottom: 12 }}
+ title={t('topic.guide.title')}
+ visible={visible}
+ width={200}
+ />
+
+ )}
+ item.id}
+ data={topics}
+ fixedItemHeight={44}
+ initialTopMostItemIndex={Math.max(activeIndex, 0)}
+ itemContent={itemContent}
+ overscan={44 * 10}
+ ref={virtuosoRef}
+ scrollSeekConfiguration={{
+ enter: (velocity) => Math.abs(velocity) > 350,
+ exit: (velocity) => Math.abs(velocity) < 10,
+ }}
+ />
+ >
);
-};
+});
export default TopicListContent;
diff --git a/src/app/(main)/chat/(workspace)/@topic/features/TopicListContent/TopicSearchBar/index.tsx b/src/app/(main)/chat/(workspace)/@topic/features/TopicSearchBar/index.tsx
similarity index 100%
rename from src/app/(main)/chat/(workspace)/@topic/features/TopicListContent/TopicSearchBar/index.tsx
rename to src/app/(main)/chat/(workspace)/@topic/features/TopicSearchBar/index.tsx
diff --git a/src/app/(main)/chat/(workspace)/_layout/Desktop/TopicPanel.tsx b/src/app/(main)/chat/(workspace)/_layout/Desktop/TopicPanel.tsx
index 5b4559883e84..119fa57f0576 100644
--- a/src/app/(main)/chat/(workspace)/_layout/Desktop/TopicPanel.tsx
+++ b/src/app/(main)/chat/(workspace)/_layout/Desktop/TopicPanel.tsx
@@ -2,7 +2,8 @@
import { DraggablePanel, DraggablePanelContainer } from '@lobehub/ui';
import { createStyles, useResponsive } from 'antd-style';
-import { PropsWithChildren, memo, useEffect, useLayoutEffect, useState } from 'react';
+import isEqual from 'fast-deep-equal';
+import { PropsWithChildren, memo, useEffect, useState } from 'react';
import SafeSpacing from '@/components/SafeSpacing';
import { CHAT_SIDEBAR_WIDTH } from '@/const/layoutTokens';
@@ -26,27 +27,23 @@ const useStyles = createStyles(({ css, token }) => ({
const TopicPanel = memo(({ children }: PropsWithChildren) => {
const { styles } = useStyles();
const { md = true, lg = true } = useResponsive();
- const [showAgentSettings, toggleConfig, isPreferenceInit] = useGlobalStore((s) => [
+ const [showAgentSettings, toggleConfig] = useGlobalStore((s) => [
s.preference.showChatSideBar,
s.toggleChatSideBar,
s.isPreferenceInit,
]);
- const [expand, setExpand] = useState(showAgentSettings);
+ const [cacheExpand, setCacheExpand] = useState(Boolean(showAgentSettings));
- const handleExpand = (e: boolean) => {
- toggleConfig(e);
- setExpand(e);
+ const handleExpand = (expand: boolean) => {
+ if (isEqual(expand, Boolean(showAgentSettings))) return;
+ toggleConfig(expand);
+ setCacheExpand(expand);
};
- useLayoutEffect(() => {
- if (!isPreferenceInit) return;
- setExpand(showAgentSettings);
- }, [isPreferenceInit, showAgentSettings]);
-
useEffect(() => {
- if (lg && showAgentSettings) setExpand(true);
- if (!lg) setExpand(false);
- }, [lg, showAgentSettings]);
+ if (lg && cacheExpand) toggleConfig(true);
+ if (!lg) toggleConfig(false);
+ }, [lg, cacheExpand]);
return (
{
classNames={{
content: styles.content,
}}
- expand={expand}
+ expand={showAgentSettings}
minWidth={CHAT_SIDEBAR_WIDTH}
mode={md ? 'fixed' : 'float'}
onExpandChange={handleExpand}
diff --git a/src/app/(main)/chat/(workspace)/_layout/Mobile/TopicModal.tsx b/src/app/(main)/chat/(workspace)/_layout/Mobile/TopicModal.tsx
index a569f83d3eaf..e95ac732dc35 100644
--- a/src/app/(main)/chat/(workspace)/_layout/Mobile/TopicModal.tsx
+++ b/src/app/(main)/chat/(workspace)/_layout/Mobile/TopicModal.tsx
@@ -17,7 +17,15 @@ const Topics = memo(({ children }: PropsWithChildren) => {
const { t } = useTranslation('chat');
return (
- setOpen(false)} open={open} title={t('topic.title')}>
+ setOpen(false)}
+ open={open}
+ styles={{
+ body: { padding: 0 },
+ }}
+ title={t('topic.title')}
+ >
{children}
);
diff --git a/src/app/(main)/chat/_layout/Desktop/SessionPanel.tsx b/src/app/(main)/chat/_layout/Desktop/SessionPanel.tsx
index e066e3145ade..bf5cff3f02e1 100644
--- a/src/app/(main)/chat/_layout/Desktop/SessionPanel.tsx
+++ b/src/app/(main)/chat/_layout/Desktop/SessionPanel.tsx
@@ -3,7 +3,7 @@
import { DraggablePanel, DraggablePanelContainer, type DraggablePanelProps } from '@lobehub/ui';
import { createStyles, useResponsive } from 'antd-style';
import isEqual from 'fast-deep-equal';
-import { PropsWithChildren, memo, useEffect, useLayoutEffect, useState } from 'react';
+import { PropsWithChildren, memo, useEffect, useState } from 'react';
import { FOLDER_WIDTH } from '@/const/layoutTokens';
import { useGlobalStore } from '@/store/global';
@@ -18,25 +18,21 @@ export const useStyles = createStyles(({ css, token }) => ({
const SessionPanel = memo(({ children }) => {
const { md = true } = useResponsive();
+
const { styles } = useStyles();
- const [sessionsWidth, sessionExpandable, updatePreference, isPreferenceInit] = useGlobalStore(
- (s) => [
- s.preference.sessionsWidth,
- s.preference.showSessionPanel,
- s.updatePreference,
- s.isPreferenceInit,
- ],
- );
- const [expand, setExpand] = useState(sessionExpandable);
+ const [sessionsWidth, sessionExpandable, updatePreference] = useGlobalStore((s) => [
+ s.preference.sessionsWidth,
+ s.preference.showSessionPanel,
+ s.updatePreference,
+ ]);
+ const [cacheExpand, setCacheExpand] = useState(Boolean(sessionExpandable));
const [tmpWidth, setWidth] = useState(sessionsWidth);
if (tmpWidth !== sessionsWidth) setWidth(sessionsWidth);
- const handleExpand: DraggablePanelProps['onExpandChange'] = (e) => {
- updatePreference({
- sessionsWidth: e ? 320 : 0,
- showSessionPanel: e,
- });
- setExpand(e);
+ const handleExpand = (expand: boolean) => {
+ if (isEqual(expand, sessionExpandable)) return;
+ updatePreference({ showSessionPanel: expand });
+ setCacheExpand(expand);
};
const handleSizeChange: DraggablePanelProps['onSizeChange'] = (_, size) => {
@@ -47,21 +43,16 @@ const SessionPanel = memo(({ children }) => {
updatePreference({ sessionsWidth: nextWidth });
};
- useLayoutEffect(() => {
- if (!isPreferenceInit) return;
- setExpand(sessionExpandable);
- }, [isPreferenceInit, sessionExpandable]);
-
useEffect(() => {
- if (md && sessionExpandable) setExpand(true);
- if (!md) setExpand(false);
- }, [md, sessionExpandable]);
+ if (md && cacheExpand) updatePreference({ showSessionPanel: true });
+ if (!md) updatePreference({ showSessionPanel: false });
+ }, [md, cacheExpand]);
return (
({
+const useStyles = createStyles(({ css }) => ({
header: css`
z-index: 10;
- box-shadow: 0 2px 6px ${token.colorBgLayout};
`,
}));
diff --git a/src/components/server/MobileNavLayout.tsx b/src/components/server/MobileNavLayout.tsx
index 81e0c4d6272f..35adde475adc 100644
--- a/src/components/server/MobileNavLayout.tsx
+++ b/src/components/server/MobileNavLayout.tsx
@@ -16,6 +16,7 @@ const MobileContentLayout = ({
const content = (
(({ mobile }) => {
));
- const cards = useMemo(
- () =>
- agentList.slice(sliceStart, sliceStart + agentLength).map((agent) => (
-
-
-
-
-
- {agent.meta.title}
-
-
- {agent.meta.description}
-
-
-
-
- )),
- [agentList, sliceStart],
- );
-
const handleRefresh = () => {
if (!agentList) return;
setSliceStart(Math.floor((Math.random() * agentList.length) / 2));
@@ -108,7 +88,23 @@ const AgentsSuggest = memo<{ mobile?: boolean }>(({ mobile }) => {
/>
- {isLoading ? loadingCards : cards}
+ {isLoading
+ ? loadingCards
+ : agentList.slice(sliceStart, sliceStart + agentLength).map((agent) => (
+
+
+
+
+
+ {agent.meta.title}
+
+
+ {agent.meta.description}
+
+
+
+
+ ))}
);
diff --git a/src/layout/GlobalProvider/AppTheme.tsx b/src/layout/GlobalProvider/AppTheme.tsx
index 4a5199641f4d..ff32f123bc5a 100644
--- a/src/layout/GlobalProvider/AppTheme.tsx
+++ b/src/layout/GlobalProvider/AppTheme.tsx
@@ -30,12 +30,25 @@ const useStyles = createStyles(({ css, token }) => ({
height: 100%;
min-height: 100dvh;
max-height: 100dvh;
+
+ @media (min-device-width: 576px) {
+ overflow: hidden;
+ }
`,
// scrollbar-width and scrollbar-color are supported from Chrome 121
// https://developer.mozilla.org/en-US/docs/Web/CSS/scrollbar-color
scrollbar: css`
scrollbar-color: ${token.colorFill} transparent;
scrollbar-width: thin;
+
+ #lobe-mobile-scroll-container {
+ scrollbar-width: none;
+
+ ::-webkit-scrollbar {
+ width: 0;
+ height: 0;
+ }
+ }
`,
// so this is a polyfill for older browsers
diff --git a/src/store/user/slices/preference/initialState.ts b/src/store/user/slices/preference/initialState.ts
index 8691b36e692b..5ff02ee7c976 100644
--- a/src/store/user/slices/preference/initialState.ts
+++ b/src/store/user/slices/preference/initialState.ts
@@ -32,6 +32,7 @@ export interface UserPreferenceState {
export const DEFAULT_PREFERENCE: UserPreference = {
guide: {
moveSettingsToAvatar: true,
+ topic: true,
},
telemetry: null,
useCmdEnterToSend: false,
diff --git a/src/styles/global.ts b/src/styles/global.ts
index 5b2739506242..0dc17cf1fbbc 100644
--- a/src/styles/global.ts
+++ b/src/styles/global.ts
@@ -16,10 +16,33 @@ export default ({ token }: { prefixCls: string; token: Theme }) => css`
max-height: 100dvh;
background: ${token.colorBgLayout};
+
+ @media (min-device-width: 576px) {
+ overflow: hidden;
+ }
}
* {
scrollbar-color: ${token.colorFill} transparent;
scrollbar-width: thin;
+
+ ::-webkit-scrollbar {
+ width: 0.75em;
+ height: 0.75em;
+ }
+
+ ::-webkit-scrollbar-thumb {
+ border-radius: 10px;
+ }
+
+ :hover::-webkit-scrollbar-thumb {
+ background-color: ${token.colorText};
+ background-clip: content-box;
+ border: 3px solid transparent;
+ }
+
+ ::-webkit-scrollbar-track {
+ background-color: transparent;
+ }
}
`;