feat: update the discover page sort, add haveSkill、mostUsage params (#11807)

* fix: slove group member plugin is lost & not use the plugins

* feat: add the agents list sort params useage/skilled

* fix: slove the test lint error
This commit is contained in:
Shinji-Li
2026-01-25 17:51:26 +08:00
committed by GitHub
parent 0a856bcb4d
commit 01c641ed09
10 changed files with 63 additions and 171 deletions

View File

@@ -56,15 +56,10 @@
"assistants.more": "More",
"assistants.plugins": "Integrated Skills",
"assistants.recentSubmits": "Recent Updates",
"assistants.sorts.createdAt": "Recently Published",
"assistants.sorts.identifier": "Agent ID",
"assistants.sorts.knowledgeCount": "Libraries",
"assistants.sorts.myown": "View My Agents",
"assistants.sorts.pluginCount": "Skills",
"assistants.sorts.recommended": "Recommended",
"assistants.sorts.title": "Agent Name",
"assistants.sorts.tokenUsage": "Token Usage",
"assistants.status.archived.reasons.official": "The platform removed this Agent due to security, policy, or other concerns.",
"assistants.sorts.haveSkills": "Skilled",
"assistants.sorts.mostUsage": "Most Usage",
"assistants.sorts.updatedAt": "Recently Updated", "assistants.status.archived.reasons.official": "The platform removed this Agent due to security, policy, or other concerns.",
"assistants.status.archived.reasons.owner": "The creator archived or removed this Agent.",
"assistants.status.archived.subtitle": "This Agent has been archived. Possible reasons:",
"assistants.status.archived.title": "Agent archived",

View File

@@ -56,15 +56,10 @@
"assistants.more": "更多",
"assistants.plugins": "集成技能",
"assistants.recentSubmits": "最近更新",
"assistants.sorts.createdAt": "最近发布",
"assistants.sorts.identifier": "助理 ID",
"assistants.sorts.knowledgeCount": "资源库数量",
"assistants.sorts.myown": "查看我的",
"assistants.sorts.pluginCount": "技能数量",
"assistants.sorts.recommended": "推荐",
"assistants.sorts.title": "助理名称",
"assistants.sorts.tokenUsage": "Token 使用",
"assistants.status.archived.reasons.official": "助理有安全/政治等问题,被官方下架",
"assistants.sorts.haveSkills": "技能筛选",
"assistants.sorts.mostUsage": "最多使用",
"assistants.sorts.updatedAt": "最近更新", "assistants.status.archived.reasons.official": "助理有安全/政治等问题,被官方下架",
"assistants.status.archived.reasons.owner": "开发助理的 owner 主动下架/归档该助理",
"assistants.status.archived.subtitle": "该助理已被归档,可能原因包括:",
"assistants.status.archived.title": "该助理已归档",

View File

@@ -22,14 +22,10 @@ export enum AssistantCategory {
}
export enum AssistantSorts {
CreatedAt = 'createdAt',
Identifier = 'identifier',
KnowledgeCount = 'knowledgeCount',
MyOwn = 'myown',
PluginCount = 'pluginCount',
HaveSkills = 'haveSkills',
MostUsage = 'mostUsage',
Recommended = 'recommended',
Title = 'title',
TokenUsage = 'tokenUsage',
UpdatedAt = 'updatedAt',
}
export enum AssistantNavKey {
@@ -72,6 +68,7 @@ export type AssistantMarketSource = 'legacy' | 'new';
export interface AssistantQueryParams {
category?: string;
haveSkills?: boolean;
includeAgentGroup?: boolean;
locale?: string;
order?: 'asc' | 'desc';

View File

@@ -65,7 +65,7 @@ const AssistantItem = memo<DiscoverAssistantItem>(
tokenUsage,
pluginCount,
knowledgeCount,
installCount,
forkCount,
backgroundColor,
userName,
type,
@@ -202,7 +202,7 @@ const AssistantItem = memo<DiscoverAssistantItem>(
{description}
</Text>
<TokenTag
installCount={installCount}
forkCount={forkCount}
knowledgeCount={knowledgeCount}
pluginCount={pluginCount}
tokenUsage={tokenUsage}

View File

@@ -1,7 +1,7 @@
import { MCP } from '@lobehub/icons';
import { Flexbox, Icon, Tag, Tooltip } from '@lobehub/ui';
import { createStaticStyles, cssVar } from 'antd-style';
import { BookTextIcon, CoinsIcon, DownloadIcon } from 'lucide-react';
import { BookTextIcon, CoinsIcon, GitForkIcon } from 'lucide-react';
import { memo } from 'react';
import { useTranslation } from 'react-i18next';
@@ -22,7 +22,7 @@ const styles = createStaticStyles(({ css, cssVar }) => {
});
interface TokenTagProps {
installCount?: number;
forkCount?: number;
knowledgeCount?: number;
placement?: 'top' | 'right';
pluginCount?: number;
@@ -30,7 +30,7 @@ interface TokenTagProps {
}
const TokenTag = memo<TokenTagProps>(
({ tokenUsage, pluginCount, knowledgeCount, installCount, placement = 'right' }) => {
({ tokenUsage, pluginCount, knowledgeCount, forkCount, placement = 'right' }) => {
const { t } = useTranslation('discover');
return (
<Flexbox align={'center'} gap={4} horizontal>
@@ -43,14 +43,14 @@ const TokenTag = memo<TokenTagProps>(
{formatIntergerNumber(tokenUsage)}
</Tag>
</Tooltip>
{Boolean(installCount && installCount > 0) && (
{Boolean(forkCount && forkCount > 0) && (
<Tooltip
placement={placement}
styles={{ root: { pointerEvents: 'none' } }}
title={t('assistants.downloads')}
title={t('fork.forksCount', { count: forkCount })}
>
<Tag className={styles.token} icon={<Icon icon={DownloadIcon} />}>
{formatIntergerNumber(installCount)}
<Tag className={styles.token} icon={<Icon icon={GitForkIcon} />}>
{formatIntergerNumber(forkCount)}
</Tag>
</Tooltip>
)}

View File

@@ -10,7 +10,6 @@ import { memo, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { useQueryRoute } from '@/hooks/useQueryRoute';
import { useMarketAuth } from '@/layout/AuthProvider/MarketAuth';
import { usePathname, useQuery } from '@/libs/router/navigation';
import {
AssistantSorts,
@@ -26,7 +25,6 @@ const SortButton = memo(() => {
const pathname = usePathname();
const { sort } = useQuery();
const router = useQueryRoute();
const { isAuthenticated, getCurrentUserInfo } = useMarketAuth();
const activeTab = useMemo(() => pathname.split('community/')[1] as DiscoverTab, [pathname]);
type SortItem = Extract<DropdownItem, { type?: 'item' }> & {
key: string;
@@ -35,46 +33,24 @@ const SortButton = memo(() => {
const items = useMemo<SortItem[]>(() => {
switch (activeTab) {
case DiscoverTab.Assistants: {
const baseItems = [
return [
{
key: AssistantSorts.Recommended,
label: t('assistants.sorts.recommended'),
},
{
key: AssistantSorts.CreatedAt,
label: t('assistants.sorts.createdAt'),
key: AssistantSorts.UpdatedAt,
label: t('assistants.sorts.updatedAt'),
},
{
key: AssistantSorts.Title,
label: t('assistants.sorts.title'),
key: AssistantSorts.MostUsage,
label: t('assistants.sorts.mostUsage'),
},
{
key: AssistantSorts.Identifier,
label: t('assistants.sorts.identifier'),
},
{
key: AssistantSorts.TokenUsage,
label: t('assistants.sorts.tokenUsage'),
},
{
key: AssistantSorts.PluginCount,
label: t('assistants.sorts.pluginCount'),
},
{
key: AssistantSorts.KnowledgeCount,
label: t('assistants.sorts.knowledgeCount'),
key: AssistantSorts.HaveSkills,
label: t('assistants.sorts.haveSkills'),
},
];
// Only add "My Own" option if user is authenticated
if (isAuthenticated) {
baseItems.push({
key: AssistantSorts.MyOwn,
label: t('assistants.sorts.myown'),
});
}
return baseItems;
}
case DiscoverTab.Plugins: {
return [
@@ -172,7 +148,7 @@ const SortButton = memo(() => {
return [];
}
}
}, [t, activeTab, isAuthenticated]);
}, [t, activeTab]);
const activeItem = useMemo<SortItem | undefined>(() => {
if (sort) {
@@ -183,18 +159,7 @@ const SortButton = memo(() => {
}, [items, sort]);
const handleSort = (config: string) => {
const query: any = { sort: config };
// If "My Own" is selected, add ownerId to query
if (config === AssistantSorts.MyOwn) {
const userInfo = getCurrentUserInfo();
if (userInfo?.accountId) {
query.ownerId = userInfo.accountId;
}
}
router.push(pathname, { query });
router.push(pathname, { query: { sort: config } });
};
const menuItems = useMemo<DropdownMenuCheckboxItem[]>(

View File

@@ -59,14 +59,10 @@ export default {
'assistants.more': 'More',
'assistants.plugins': 'Integrated Skills',
'assistants.recentSubmits': 'Recent Updates',
'assistants.sorts.createdAt': 'Recently Published',
'assistants.sorts.identifier': 'Agent ID',
'assistants.sorts.knowledgeCount': 'Libraries',
'assistants.sorts.myown': 'View My Agents',
'assistants.sorts.pluginCount': 'Skills',
'assistants.sorts.haveSkills': 'Skilled',
'assistants.sorts.mostUsage': 'Most Usage',
'assistants.sorts.recommended': 'Recommended',
'assistants.sorts.title': 'Agent Name',
'assistants.sorts.tokenUsage': 'Token Usage',
'assistants.sorts.updatedAt': 'Recently Updated',
'assistants.status.archived.reasons.official':
'The platform removed this Agent due to security, policy, or other concerns.',
'assistants.status.archived.reasons.owner': 'The creator archived or removed this Agent.',

View File

@@ -146,6 +146,7 @@ export const searchRouter = router({
searchPromises.push(
ctx.discoverService
.getAssistantList({
includeAgentGroup: true,
locale,
pageSize: limitPerType,
q: query,

View File

@@ -395,30 +395,6 @@ describe('DiscoverService', () => {
expect(result.items.map((item) => item.identifier)).toContain('assistant-3');
});
it('should sort by creation date descending', async () => {
const result = await service.getAssistantList({
sort: AssistantSorts.CreatedAt,
order: 'desc',
source: 'legacy',
});
expect(result.items[0].identifier).toBe('assistant-3');
expect(result.items[1].identifier).toBe('assistant-2');
expect(result.items[2].identifier).toBe('assistant-1');
});
it('should sort by title ascending', async () => {
const result = await service.getAssistantList({
sort: AssistantSorts.Title,
order: 'asc',
source: 'legacy',
});
// Note: The service has reversed logic for title sorting
expect(result.items[0].title).toBe('Test Assistant 3');
expect(result.items[1].title).toBe('Test Assistant 2');
});
it('should paginate results', async () => {
const result = await service.getAssistantList({ page: 1, pageSize: 1, source: 'legacy' });

View File

@@ -375,6 +375,7 @@ export class DiscoverService {
const assistant = merge(cloneDeep(DEFAULT_DISCOVER_ASSISTANT_ITEM), { ...item, ...meta });
const list = await this.getAssistantList({
category: assistant.category,
includeAgentGroup: true,
locale,
page: 1,
pageSize: 7,
@@ -415,7 +416,7 @@ export class DiscoverService {
page = 1,
pageSize = 20,
q,
sort = AssistantSorts.CreatedAt,
sort = AssistantSorts.Recommended,
ownerId,
} = params;
const currentPage = Number(page) || 1;
@@ -466,7 +467,8 @@ export class DiscoverService {
if (sort) {
log('legacyGetAssistantList: sorting by %s %s', sort, order);
switch (sort) {
case AssistantSorts.CreatedAt: {
case AssistantSorts.UpdatedAt: {
// Legacy source doesn't have updatedAt, fallback to createdAt
list = list.sort((a, b) => {
if (order === 'asc') {
return dayjs(a.createdAt).unix() - dayjs(b.createdAt).unix();
@@ -476,57 +478,8 @@ export class DiscoverService {
});
break;
}
case AssistantSorts.KnowledgeCount: {
list = list.sort((a, b) => {
if (order === 'asc') {
return (a.knowledgeCount || 0) - (b.knowledgeCount || 0);
} else {
return (b.knowledgeCount || 0) - (a.knowledgeCount || 0);
}
});
break;
}
case AssistantSorts.PluginCount: {
list = list.sort((a, b) => {
if (order === 'asc') {
return (a.pluginCount || 0) - (b.pluginCount || 0);
} else {
return (b.pluginCount || 0) - (a.pluginCount || 0);
}
});
break;
}
case AssistantSorts.TokenUsage: {
list = list.sort((a, b) => {
if (order === 'asc') {
return (a.tokenUsage || 0) - (b.tokenUsage || 0);
} else {
return (b.tokenUsage || 0) - (a.tokenUsage || 0);
}
});
break;
}
case AssistantSorts.Identifier: {
list = list.sort((a, b) => {
if (order !== 'desc') {
return a.identifier.localeCompare(b.identifier);
} else {
return b.identifier.localeCompare(a.identifier);
}
});
break;
}
case AssistantSorts.Title: {
list = list.sort((a, b) => {
if (order === 'desc') {
return (a.title || a.identifier).localeCompare(b.title || b.identifier);
} else {
return (b.title || b.identifier).localeCompare(a.title || a.identifier);
}
});
break;
}
default: {
// Legacy source doesn't support these sorts (MostUsage, HaveSkills, Recommended), keep original order
break;
}
}
@@ -620,9 +573,9 @@ export class DiscoverService {
examples: Array.isArray((data as any).examples)
? (data as any).examples.map((example: any) => ({
content: typeof example === 'string' ? example : example.content || '',
role: example.role || 'user',
}))
content: typeof example === 'string' ? example : example.content || '',
role: example.role || 'user',
}))
: [],
homepage:
(data as any).homepage ||
@@ -654,6 +607,7 @@ export class DiscoverService {
// Get related assistants
const list = await this.getAssistantList({
category: assistant.category,
includeAgentGroup: true,
locale,
page: 1,
pageSize: 7,
@@ -711,7 +665,7 @@ export class DiscoverService {
page = 1,
pageSize = 20,
q,
sort = AssistantSorts.CreatedAt,
sort = AssistantSorts.Recommended,
ownerId,
includeAgentGroup,
} = rest;
@@ -719,25 +673,37 @@ export class DiscoverService {
try {
const normalizedLocale = normalizeLocale(locale);
let apiSort: 'createdAt' | 'updatedAt' | 'name' = 'createdAt';
let apiSort: 'createdAt' | 'updatedAt' | 'name' | 'mostUsage' | 'recommended' =
'recommended';
let haveSkills: boolean | undefined = rest.haveSkills;
switch (sort) {
case AssistantSorts.Identifier:
case AssistantSorts.Title: {
apiSort = 'name';
case AssistantSorts.UpdatedAt: {
apiSort = 'updatedAt';
break;
}
case AssistantSorts.CreatedAt:
case AssistantSorts.MyOwn: {
apiSort = 'createdAt';
case AssistantSorts.MostUsage: {
apiSort = 'mostUsage';
break;
}
case AssistantSorts.HaveSkills: {
// When user selects "Skilled", set haveSkills=true and use recommended sort
haveSkills = true;
apiSort = 'updatedAt';
break;
}
case AssistantSorts.Recommended: {
apiSort = 'recommended';
break;
}
default: {
apiSort = 'createdAt';
apiSort = 'recommended';
}
}
const data = await this.market.agents.getAgentList({
category,
haveSkills,
// includeAgentGroup may not be in SDK type definition yet, using 'as any'
includeAgentGroup,
locale: normalizedLocale,
@@ -761,6 +727,7 @@ export class DiscoverService {
config: item.config || {},
createdAt: item.createdAt || item.updatedAt || new Date().toISOString(),
description: item.description || item.summary || '',
forkCount: item.forkCount,
homepage: item.homepage || `https://lobehub.com/discover/assistant/${item.identifier}`,
identifier: item.identifier,
installCount: item.installCount,