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, };