mirror of
https://github.com/lobehub/lobe-chat.git
synced 2026-06-14 03:30:19 +00:00
🐛 fix: Fix market search (fix #437)
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { DraggablePanel, DraggablePanelBody, DraggablePanelContainer } from '@lobehub/ui';
|
||||
import { createStyles } from 'antd-style';
|
||||
import { memo, useState } from 'react';
|
||||
import { memo, useCallback, useState } from 'react';
|
||||
|
||||
import SafeSpacing from '@/components/SafeSpacing';
|
||||
import { MARKET_SIDEBAR_WIDTH } from '@/const/layoutTokens';
|
||||
@@ -31,6 +31,18 @@ const SideBar = memo(() => {
|
||||
s.activateAgent,
|
||||
]);
|
||||
|
||||
const handleExpandChange = useCallback(
|
||||
(show: boolean) => {
|
||||
if (!show) {
|
||||
setTempId(useMarketStore.getState().currentIdentifier);
|
||||
deactivateAgent();
|
||||
} else if (tempId) {
|
||||
activateAgent(tempId);
|
||||
}
|
||||
},
|
||||
[deactivateAgent, activateAgent, tempId],
|
||||
);
|
||||
|
||||
return (
|
||||
<DraggablePanel
|
||||
className={styles.drawer}
|
||||
@@ -40,14 +52,7 @@ const SideBar = memo(() => {
|
||||
expand={showAgentSidebar}
|
||||
minWidth={MARKET_SIDEBAR_WIDTH}
|
||||
mode={'fixed'}
|
||||
onExpandChange={(show) => {
|
||||
if (!show) {
|
||||
setTempId(useMarketStore.getState().currentIdentifier);
|
||||
deactivateAgent();
|
||||
} else if (tempId) {
|
||||
activateAgent(tempId);
|
||||
}
|
||||
}}
|
||||
onExpandChange={handleExpandChange}
|
||||
placement={'right'}
|
||||
>
|
||||
<DraggablePanelContainer
|
||||
|
||||
@@ -6,18 +6,17 @@ import { FC, memo } from 'react';
|
||||
|
||||
import AgentCard from '@/app/market/features/AgentCard';
|
||||
import ResponsiveIndex from '@/components/ResponsiveIndex';
|
||||
import { AgentsMarketIndexItem } from '@/types/market';
|
||||
|
||||
import Index from '../index';
|
||||
import Layout from './layout.desktop';
|
||||
|
||||
const Mobile: FC = dynamic(() => import('../(mobile)'), { ssr: false }) as FC;
|
||||
|
||||
export default memo<{ defaultAgents?: AgentsMarketIndexItem[] }>(({ defaultAgents }) => (
|
||||
export default memo(() => (
|
||||
<ResponsiveIndex Mobile={Mobile}>
|
||||
<Layout>
|
||||
<Index />
|
||||
<AgentCard CardRender={SpotlightCard} defaultAgents={defaultAgents} />
|
||||
<AgentCard CardRender={SpotlightCard} />
|
||||
</Layout>
|
||||
</ResponsiveIndex>
|
||||
));
|
||||
|
||||
@@ -3,15 +3,14 @@
|
||||
import { memo } from 'react';
|
||||
|
||||
import AgentCard from '@/app/market/features/AgentCard';
|
||||
import { AgentsMarketIndexItem } from '@/types/market';
|
||||
|
||||
import Index from '../index';
|
||||
import CardRender from './features/AgentCard';
|
||||
import Layout from './layout.mobile';
|
||||
|
||||
export default memo<{ defaultAgents?: AgentsMarketIndexItem[] }>(({ defaultAgents }) => (
|
||||
export default memo(() => (
|
||||
<Layout>
|
||||
<Index />
|
||||
<AgentCard CardRender={CardRender} defaultAgents={defaultAgents} mobile />
|
||||
<AgentCard CardRender={CardRender} mobile />
|
||||
</Layout>
|
||||
));
|
||||
|
||||
@@ -13,13 +13,16 @@ import AgentCardBanner from './AgentCardBanner';
|
||||
import { useStyles } from './style';
|
||||
|
||||
const { Paragraph } = Typography;
|
||||
|
||||
const AgentCardItem = memo<AgentsMarketIndexItem>(({ meta, identifier }) => {
|
||||
const ref = useRef(null);
|
||||
const isHovering = useHover(ref);
|
||||
const onAgentCardClick = useMarketStore((s) => s.activateAgent);
|
||||
const { avatar, title, description, tags, backgroundColor } = meta;
|
||||
const { styles, theme } = useStyles();
|
||||
const { isDarkMode } = useThemeMode();
|
||||
|
||||
const { avatar, title, description, tags, backgroundColor } = meta;
|
||||
|
||||
return (
|
||||
<Flexbox className={styles.container} onClick={() => onAgentCardClick(identifier)}>
|
||||
<AgentCardBanner meta={meta} style={{ opacity: isDarkMode ? 0.9 : 0.4 }} />
|
||||
|
||||
@@ -1,71 +1,60 @@
|
||||
import { SpotlightCardProps } from '@lobehub/ui';
|
||||
import { FC, memo, useMemo } from 'react';
|
||||
import isEqual from 'fast-deep-equal';
|
||||
import { FC, memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Flexbox } from 'react-layout-kit';
|
||||
import LazyLoad from 'react-lazy-load';
|
||||
|
||||
import { agentMarketSelectors, useMarketStore } from '@/store/market';
|
||||
import { AgentsMarketIndexItem } from '@/types/market';
|
||||
|
||||
import TagList from '../TagList';
|
||||
import AgentCardItem from './AgentCardItem';
|
||||
import Loading from './Loading';
|
||||
import { useStyles } from './style';
|
||||
|
||||
const gridRender: SpotlightCardProps['renderItem'] = (item) => {
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||
const { styles } = useStyles();
|
||||
return (
|
||||
<LazyLoad className={styles.lazy}>
|
||||
<AgentCardItem {...item} />
|
||||
</LazyLoad>
|
||||
);
|
||||
};
|
||||
|
||||
export interface AgentCardProps {
|
||||
CardRender: FC<SpotlightCardProps>;
|
||||
defaultAgents?: AgentsMarketIndexItem[];
|
||||
mobile?: boolean;
|
||||
}
|
||||
|
||||
const AgentCard = memo<AgentCardProps>(({ defaultAgents, CardRender, mobile }) => {
|
||||
const AgentCard = memo<AgentCardProps>(({ CardRender, mobile }) => {
|
||||
const { t } = useTranslation('market');
|
||||
|
||||
const { styles } = useStyles();
|
||||
|
||||
const [useFetchAgentList, keywords] = useMarketStore((s) => [
|
||||
s.useFetchAgentList,
|
||||
s.searchKeywords,
|
||||
]);
|
||||
useFetchAgentList();
|
||||
const { isLoading } = useFetchAgentList();
|
||||
const agentList = useMarketStore(agentMarketSelectors.getAgentList, isEqual);
|
||||
const { styles } = useStyles();
|
||||
|
||||
const clientAgentList = useMarketStore(agentMarketSelectors.getAgentList);
|
||||
const agentList = clientAgentList.length === 0 && defaultAgents ? defaultAgents : clientAgentList;
|
||||
const GridRender: SpotlightCardProps['renderItem'] = useCallback(
|
||||
(props: any) => (
|
||||
<LazyLoad className={styles.lazy}>
|
||||
<AgentCardItem {...props} />
|
||||
</LazyLoad>
|
||||
),
|
||||
[styles.lazy],
|
||||
);
|
||||
|
||||
const agentListData = useMemo(() => {
|
||||
if (!keywords) return agentList;
|
||||
return agentList.filter(({ meta }) => JSON.stringify(meta).toLowerCase().includes(keywords));
|
||||
}, [agentList, keywords]);
|
||||
|
||||
if (agentList.length === 0) return <Loading />;
|
||||
if (isLoading) return <Loading />;
|
||||
|
||||
return (
|
||||
<Flexbox gap={mobile ? 16 : 24}>
|
||||
<TagList />
|
||||
{keywords ? (
|
||||
<CardRender
|
||||
items={agentListData}
|
||||
renderItem={gridRender}
|
||||
items={agentList}
|
||||
renderItem={GridRender}
|
||||
spotlight={mobile ? undefined : false}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<div className={styles.subTitle}>{t('title.recentSubmits')}</div>
|
||||
<CardRender items={agentListData.slice(0, 3)} renderItem={gridRender} />
|
||||
<CardRender items={agentList.slice(0, 3)} renderItem={GridRender} />
|
||||
<div className={styles.subTitle}>{t('title.allAgents')}</div>
|
||||
<CardRender
|
||||
items={agentListData.slice(3)}
|
||||
renderItem={gridRender}
|
||||
items={agentList.slice(3)}
|
||||
renderItem={GridRender}
|
||||
spotlight={mobile ? undefined : false}
|
||||
/>
|
||||
</>
|
||||
|
||||
@@ -17,7 +17,6 @@ const { Link } = Typography;
|
||||
const Header = memo(() => {
|
||||
const { t } = useTranslation('market');
|
||||
const { styles, theme } = useStyles();
|
||||
|
||||
const createSession = useSessionStore((s) => s.createSession);
|
||||
const switchSideBar = useGlobalStore((s) => s.switchSideBar);
|
||||
const agentItem = useMarketStore(agentMarketSelectors.currentAgentItem);
|
||||
|
||||
@@ -22,11 +22,10 @@ const AgentModalInner = memo(() => {
|
||||
s.useFetchAgent,
|
||||
s.currentIdentifier,
|
||||
]);
|
||||
const { styles } = useStyles();
|
||||
|
||||
const { data, isLoading } = useFetchAgent(currentIdentifier);
|
||||
const { t } = useTranslation('market');
|
||||
const [tab, setTab] = useState<string>(InfoTabs.prompt);
|
||||
const { data, isLoading } = useFetchAgent(currentIdentifier);
|
||||
const { styles } = useStyles();
|
||||
|
||||
if (isLoading || !data?.meta) return <Loading />;
|
||||
|
||||
|
||||
@@ -1,22 +1,27 @@
|
||||
import { SearchBar } from '@lobehub/ui';
|
||||
import { useResponsive } from 'antd-style';
|
||||
import { memo, useState } from 'react';
|
||||
import { memo, useCallback, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { useMarketStore } from '@/store/market';
|
||||
|
||||
const AgentSearchBar = memo(() => {
|
||||
const { t } = useTranslation('market');
|
||||
const keywords = useMarketStore((s) => s.searchKeywords);
|
||||
const [keywords, setKeywords] = useMarketStore((s) => [s.searchKeywords, s.setSearchKeywords]);
|
||||
const [value, setValue] = useState(keywords);
|
||||
const { mobile } = useResponsive();
|
||||
|
||||
const handleSearch = useCallback(() => {
|
||||
setKeywords(value);
|
||||
}, [value, setKeywords]);
|
||||
|
||||
return (
|
||||
<SearchBar
|
||||
allowClear
|
||||
enableShortKey={!mobile}
|
||||
onChange={(e) => setValue(e.target.value)}
|
||||
onPressEnter={() => useMarketStore.setState({ searchKeywords: value })}
|
||||
onSubmit={() => useMarketStore.setState({ searchKeywords: value })}
|
||||
onPressEnter={handleSearch}
|
||||
onSubmit={handleSearch}
|
||||
placeholder={t('search.placeholder')}
|
||||
shortKey={'k'}
|
||||
spotlight={!mobile}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import { Button } from 'antd';
|
||||
import isEqual from 'fast-deep-equal';
|
||||
import { startCase } from 'lodash-es';
|
||||
import { memo } from 'react';
|
||||
|
||||
import { agentMarketSelectors, useMarketStore } from '@/store/market';
|
||||
|
||||
const Inner = memo(() => {
|
||||
const agentTagList = useMarketStore(agentMarketSelectors.getAgentTagList, isEqual);
|
||||
const [keywords, setSearchKeywords] = useMarketStore((s) => [
|
||||
s.searchKeywords,
|
||||
s.setSearchKeywords,
|
||||
]);
|
||||
|
||||
return agentTagList.map((item) => (
|
||||
<Button
|
||||
key={item}
|
||||
onClick={() => setSearchKeywords(item)}
|
||||
shape={'round'}
|
||||
size={'small'}
|
||||
type={keywords === item ? 'primary' : 'default'}
|
||||
>
|
||||
{startCase(item)}
|
||||
</Button>
|
||||
));
|
||||
});
|
||||
|
||||
export default Inner;
|
||||
@@ -1,32 +1,21 @@
|
||||
import { Button, Skeleton } from 'antd';
|
||||
import isEqual from 'fast-deep-equal';
|
||||
import { startCase } from 'lodash-es';
|
||||
import { memo } from 'react';
|
||||
import { Skeleton } from 'antd';
|
||||
import { Suspense, memo } from 'react';
|
||||
import { Flexbox } from 'react-layout-kit';
|
||||
|
||||
import { agentMarketSelectors, useMarketStore } from '@/store/market';
|
||||
import Inner from '@/app/market/features/TagList/Inner';
|
||||
|
||||
const TagList = memo(() => {
|
||||
const agentTagList = useMarketStore(agentMarketSelectors.getAgentTagList, isEqual);
|
||||
const keywords = useMarketStore((s) => s.searchKeywords);
|
||||
|
||||
return (
|
||||
<Flexbox gap={6} horizontal style={{ flexWrap: 'wrap' }}>
|
||||
{agentTagList?.length > 0
|
||||
? agentTagList.map((item) => (
|
||||
<Button
|
||||
key={item}
|
||||
onClick={() => useMarketStore.setState({ searchKeywords: item })}
|
||||
shape={'round'}
|
||||
size={'small'}
|
||||
type={keywords === item ? 'primary' : 'default'}
|
||||
>
|
||||
{startCase(item)}
|
||||
</Button>
|
||||
))
|
||||
: Array.from({ length: 5 })
|
||||
.fill('')
|
||||
.map((_, index) => <Skeleton.Button key={index} shape={'round'} size={'small'} />)}
|
||||
<Suspense
|
||||
fallback={Array.from({ length: 5 })
|
||||
.fill('')
|
||||
.map((_, index) => (
|
||||
<Skeleton.Button key={index} shape={'round'} size={'small'} />
|
||||
))}
|
||||
>
|
||||
<Inner />
|
||||
</Suspense>
|
||||
</Flexbox>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -12,6 +12,7 @@ import type { Store } from './store';
|
||||
export interface StoreAction {
|
||||
activateAgent: (identifier: string) => void;
|
||||
deactivateAgent: () => void;
|
||||
setSearchKeywords: (keywords: string) => void;
|
||||
updateAgentMap: (key: string, value: AgentsMarketItem) => void;
|
||||
useFetchAgent: (identifier: string) => SWRResponse<AgentsMarketItem>;
|
||||
useFetchAgentList: () => SWRResponse<LobeChatAgentsMarketIndex>;
|
||||
@@ -29,6 +30,9 @@ export const createMarketAction: StateCreator<
|
||||
deactivateAgent: () => {
|
||||
set({ currentIdentifier: undefined }, false, 'deactivateAgent');
|
||||
},
|
||||
setSearchKeywords: (keywords) => {
|
||||
set({ searchKeywords: keywords });
|
||||
},
|
||||
updateAgentMap: (key, value) => {
|
||||
const { agentMap } = get();
|
||||
|
||||
@@ -56,7 +60,11 @@ export const createMarketAction: StateCreator<
|
||||
useFetchAgentList: () =>
|
||||
useSWR<LobeChatAgentsMarketIndex>(getCurrentLanguage(), getAgentList, {
|
||||
onSuccess: (agentMarketIndex) => {
|
||||
set({ agentList: agentMarketIndex.agents }, false, 'useFetchAgentList');
|
||||
set(
|
||||
{ agentList: agentMarketIndex.agents, tagList: agentMarketIndex.tags },
|
||||
false,
|
||||
'useFetchAgentList',
|
||||
);
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -7,6 +7,7 @@ export interface StoreState {
|
||||
agentMap: MarketAgentMap;
|
||||
currentIdentifier: string;
|
||||
searchKeywords: string;
|
||||
tagList: string[];
|
||||
}
|
||||
|
||||
export const initialState: StoreState = {
|
||||
@@ -14,4 +15,5 @@ export const initialState: StoreState = {
|
||||
agentMap: {},
|
||||
currentIdentifier: '',
|
||||
searchKeywords: '',
|
||||
tagList: [],
|
||||
};
|
||||
|
||||
@@ -1,17 +1,19 @@
|
||||
import { flatten } from 'lodash-es';
|
||||
|
||||
import { DEFAULT_AGENTS_MARKET_ITEM } from '@/const/market';
|
||||
import { AgentsMarketItem } from '@/types/market';
|
||||
import { findDuplicates } from '@/utils/findDuplicates';
|
||||
|
||||
import type { Store } from './store';
|
||||
|
||||
const getAgentList = (s: Store) => s.agentList;
|
||||
const getAgentTagList = (s: Store) => {
|
||||
const agentList = s.agentList;
|
||||
const rawAgentTagList = flatten(agentList.map((item) => item.meta.tags)) as string[];
|
||||
return findDuplicates(rawAgentTagList);
|
||||
const getAgentList = (s: Store) => {
|
||||
const { searchKeywords, agentList } = s;
|
||||
if (!searchKeywords) return agentList;
|
||||
return agentList.filter(({ meta }) => {
|
||||
const checkMeta: string = [meta.tags, meta.title, meta.description, meta.avatar]
|
||||
.filter(Boolean)
|
||||
.join('');
|
||||
return checkMeta.toLowerCase().includes(searchKeywords.toLowerCase());
|
||||
});
|
||||
};
|
||||
const getAgentTagList = (s: Store) => s.tagList;
|
||||
|
||||
const getAgentItemById = (d: string) => (s: Store) => s.agentMap[d];
|
||||
|
||||
|
||||
@@ -16,4 +16,5 @@ export type AgentsMarketItem = AgentsMarketIndexItem & LobeAgentSettings;
|
||||
export interface LobeChatAgentsMarketIndex {
|
||||
agents: AgentsMarketIndexItem[];
|
||||
schemaVersion: 1;
|
||||
tags: string[];
|
||||
}
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
export const findDuplicates = (arr: string[]): string[] => {
|
||||
const duplicates: { [key: string]: number } = {};
|
||||
|
||||
// 统计每个项目出现的次数
|
||||
for (const item of arr) {
|
||||
if (duplicates[item]) {
|
||||
duplicates[item]++;
|
||||
} else {
|
||||
duplicates[item] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
// 挑出重复出现 3 次以上的项目
|
||||
const COUNT = 3;
|
||||
|
||||
const result = Object.keys(duplicates).filter((item) => duplicates[item] >= COUNT);
|
||||
|
||||
// 按重复次数从多到少排序
|
||||
result.sort((a, b) => duplicates[b] - duplicates[a]);
|
||||
|
||||
return result;
|
||||
};
|
||||
Reference in New Issue
Block a user