diff --git a/src/features/Conversation/components/InboxWelcome/AgentsSuggest.tsx b/src/features/Conversation/components/InboxWelcome/AgentsSuggest.tsx
new file mode 100644
index 000000000000..8aabbb57d8ce
--- /dev/null
+++ b/src/features/Conversation/components/InboxWelcome/AgentsSuggest.tsx
@@ -0,0 +1,105 @@
+'use client';
+
+import { ActionIcon, Avatar, Grid } from '@lobehub/ui';
+import { Skeleton, Typography } from 'antd';
+import { createStyles } from 'antd-style';
+import isEqual from 'fast-deep-equal';
+import { RefreshCw } from 'lucide-react';
+import Link from 'next/link';
+import { memo, useMemo, useState } from 'react';
+import { Flexbox } from 'react-layout-kit';
+
+import { agentMarketSelectors, useMarketStore } from '@/store/market';
+
+const { Paragraph } = Typography;
+
+const useStyles = createStyles(({ css, token }) => ({
+ card: css`
+ position: relative;
+
+ height: 100%;
+ padding: 16px;
+
+ color: ${token.colorText};
+
+ background: ${token.colorFillTertiary};
+ border-radius: ${token.borderRadius}px;
+
+ &:hover {
+ background: ${token.colorFillSecondary};
+ }
+ `,
+ cardDesc: css`
+ margin-block: 0 !important;
+ color: ${token.colorTextDescription};
+ `,
+ cardTitle: css`
+ margin-block: 0 !important;
+ font-size: 16px;
+ font-weight: bold;
+ `,
+ icon: css`
+ color: ${token.colorTextSecondary};
+ `,
+ title: css`
+ color: ${token.colorTextDescription};
+ `,
+}));
+
+const AgentsSuggest = memo(() => {
+ const [sliceStart, setSliceStart] = useState(0);
+ const useFetchAgentList = useMarketStore((s) => s.useFetchAgentList);
+ const { isLoading } = useFetchAgentList();
+ const agentList = useMarketStore((s) => agentMarketSelectors.getAgentList(s), isEqual);
+ const { styles } = useStyles();
+
+ const loadingCards = Array.from({ length: 4 }).map((_, index) => (
+
+
+
+ ));
+
+ const cards = useMemo(
+ () =>
+ agentList.slice(sliceStart, sliceStart + 4).map((agent) => (
+
+
+
+
+
+ {agent.meta.title}
+
+
+ {agent.meta.description}
+
+
+
+
+ )),
+ [agentList, sliceStart],
+ );
+
+ const handleRefresh = () => {
+ if (!agentList) return;
+ setSliceStart(Math.floor((Math.random() * agentList.length) / 2));
+ };
+
+ return (
+
+
+ 新增助理推荐:
+
+
+
+ {isLoading ? loadingCards : cards}
+
+
+ );
+});
+
+export default AgentsSuggest;
diff --git a/src/features/Conversation/components/InboxWelcome/FeatureCards.tsx b/src/features/Conversation/components/InboxWelcome/FeatureCards.tsx
new file mode 100644
index 000000000000..db16267dc3d3
--- /dev/null
+++ b/src/features/Conversation/components/InboxWelcome/FeatureCards.tsx
@@ -0,0 +1,89 @@
+'use client';
+
+import { ActionIcon, Grid, Icon } from '@lobehub/ui';
+import { createStyles } from 'antd-style';
+import { ArrowRight, Blocks, Bot, BrainCircuit, Eye, Images, Mic2 } from 'lucide-react';
+import Link from 'next/link';
+import { memo } from 'react';
+import { Flexbox } from 'react-layout-kit';
+import urlJoin from 'url-join';
+
+const BASE_DOC_URL = 'https://lobehub.com/docs/usage/features';
+
+const useStyles = createStyles(({ css, token }) => ({
+ card: css`
+ padding: 16px;
+ color: ${token.colorText};
+ background: ${token.colorFillTertiary};
+ border-radius: ${token.borderRadius}px;
+
+ &:hover {
+ background: ${token.colorFillSecondary};
+ }
+ `,
+ icon: css`
+ color: ${token.colorTextSecondary};
+ `,
+ title: css`
+ color: ${token.colorTextDescription};
+ `,
+}));
+
+const FeatureCards = memo(() => {
+ const { styles } = useStyles();
+ const cards = [
+ {
+ icon: Eye,
+ title: '视觉识别',
+ url: 'vision',
+ },
+ {
+ icon: Mic2,
+ title: 'TTS & STT',
+ url: 'tts',
+ },
+ {
+ icon: Images,
+ title: '文生图',
+ url: 'text-to-image',
+ },
+ {
+ icon: Blocks,
+ title: '插件系统',
+ url: 'plugin-system',
+ },
+ {
+ icon: Bot,
+ title: '助手市场',
+ url: 'agent-market',
+ },
+ {
+ icon: BrainCircuit,
+ title: '多模型服务商',
+ url: 'multi-ai-providers',
+ },
+ ];
+
+ return (
+
+
+ 查看我的能力:
+
+
+
+
+
+ {cards.map((card) => (
+
+
+
+ {card.title}
+
+
+ ))}
+
+
+ );
+});
+
+export default FeatureCards;
diff --git a/src/features/Conversation/components/InboxWelcome/index.tsx b/src/features/Conversation/components/InboxWelcome/index.tsx
new file mode 100644
index 000000000000..b6ee7e1f3cd0
--- /dev/null
+++ b/src/features/Conversation/components/InboxWelcome/index.tsx
@@ -0,0 +1,52 @@
+'use client';
+
+import { FluentEmoji } from '@lobehub/ui';
+import { createStyles } from 'antd-style';
+import { memo } from 'react';
+import { Center, Flexbox } from 'react-layout-kit';
+
+import AgentsSuggest from './AgentsSuggest';
+import FeatureCards from './FeatureCards';
+
+const useStyles = createStyles(({ css, responsive }) => ({
+ container: css`
+ align-items: center;
+ ${responsive.mobile} {
+ align-items: flex-start;
+ }
+ `,
+ desc: css`
+ font-size: 14px;
+ `,
+ title: css`
+ margin-top: 0.2em;
+ margin-bottom: 0;
+
+ font-size: 32px;
+ font-weight: bolder;
+ line-height: 1;
+ ${responsive.mobile} {
+ font-size: 24px;
+ }
+ `,
+}));
+
+const InboxWelcome = memo(() => {
+ const { styles } = useStyles();
+ return (
+
+
+
+
+ 下午好
+
+ 我是 LobeChat 你的私人智能助理,我今天能帮你做什么?
+
+
+ {/*{t('inbox.defaultMessage')}*/}
+
+
+ );
+});
+
+export default InboxWelcome;
diff --git a/src/features/Conversation/components/VirtualizedList/index.tsx b/src/features/Conversation/components/VirtualizedList/index.tsx
index f1de204ba46c..2139b75cccec 100644
--- a/src/features/Conversation/components/VirtualizedList/index.tsx
+++ b/src/features/Conversation/components/VirtualizedList/index.tsx
@@ -9,10 +9,15 @@ import { isMobileScreen } from '@/utils/screen';
import AutoScroll from '../AutoScroll';
import Item from '../ChatItem';
+import InboxWelcome from '../InboxWelcome';
+
+const WELCOME_ID = 'welcome';
const itemContent = (index: number, id: string) => {
const isMobile = isMobileScreen();
+ if (id === WELCOME_ID) return ;
+
return index === 0 ? (
) : (
@@ -27,15 +32,17 @@ const VirtualizedList = memo(({ mobile }) => {
const virtuosoRef = useRef(null);
const [atBottom, setAtBottom] = useState(true);
- const data = useChatStore(
- (s) => ['empty', ...chatSelectors.currentChatIDsWithGuideMessage(s)],
- isEqual,
- );
const [id, chatLoading] = useChatStore((s) => [
chatSelectors.currentChatKey(s),
chatSelectors.currentChatLoadingState(s),
]);
+ const data = useChatStore((s) => {
+ const showInboxWelcome = chatSelectors.showInboxWelcome(s);
+ const ids = showInboxWelcome ? [WELCOME_ID] : chatSelectors.currentChatIDsWithGuideMessage(s);
+ return ['empty', ...ids];
+ }, isEqual);
+
useEffect(() => {
if (virtuosoRef.current) {
virtuosoRef.current.scrollToIndex({ align: 'end', behavior: 'auto', index: 'LAST' });
diff --git a/src/store/chat/slices/message/selectors.ts b/src/store/chat/slices/message/selectors.ts
index 2b4536b6ed04..cc6e74d4bef9 100644
--- a/src/store/chat/slices/message/selectors.ts
+++ b/src/store/chat/slices/message/selectors.ts
@@ -50,6 +50,15 @@ const currentChats = (s: ChatStore): ChatMessage[] => {
};
const initTime = Date.now();
+
+const showInboxWelcome = (s: ChatStore): boolean => {
+ const isInbox = s.activeId === INBOX_SESSION_ID;
+ if (!isInbox) return false;
+ const data = currentChats(s);
+ const isBrandNewChat = data.length === 0;
+ return isBrandNewChat;
+};
+
// 针对新助手添加初始化时的自定义消息
const currentChatsWithGuideMessage =
(meta: MetaData) =>
@@ -62,7 +71,7 @@ const currentChatsWithGuideMessage =
const [activeId, isInbox] = [s.activeId, s.activeId === INBOX_SESSION_ID];
- const inboxMsg = t('inbox.defaultMessage', { ns: 'chat' });
+ const inboxMsg = '';
const agentSystemRoleMsg = t('agentDefaultMessageWithSystemRole', {
name: meta.title || t('defaultAgent'),
ns: 'chat',
@@ -135,4 +144,5 @@ export const chatSelectors = {
getMessageById,
getTraceIdByMessageId,
latestMessage,
+ showInboxWelcome,
};