Skip to content

Commit 4e060be

Browse files
authored
✨ feat: support agent group unpublish agents (#11687)
feat: support agent group unpublish agents
1 parent b13bb8a commit 4e060be

File tree

7 files changed

+397
-45
lines changed

7 files changed

+397
-45
lines changed

src/app/[variants]/(main)/community/(detail)/user/features/DetailProvider.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,11 @@ export interface UserDetailContextConfig {
1919
isOwner: boolean;
2020
mobile?: boolean;
2121
onEditProfile?: (onSuccess?: (profile: MarketUserProfile) => void) => void;
22-
onStatusChange?: (identifier: string, action: 'publish' | 'unpublish' | 'deprecate') => void;
22+
onStatusChange?: (
23+
identifier: string,
24+
action: 'publish' | 'unpublish' | 'deprecate',
25+
type?: 'agent' | 'group',
26+
) => void;
2327
totalInstalls: number;
2428
user: DiscoverUserInfo;
2529
}

src/app/[variants]/(main)/community/(detail)/user/features/UserAgentCard.tsx

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ const styles = createStaticStyles(({ css, cssVar }) => {
7878
`,
7979
moreButton: css`
8080
position: absolute;
81+
z-index: 10;
8182
inset-block-start: 12px;
8283
inset-inline-end: 12px;
8384
@@ -259,14 +260,13 @@ const UserAgentCard = memo<UserAgentCardProps>(
259260
width={'100%'}
260261
>
261262
{isOwner && (
262-
<DropdownMenu items={menuItems}>
263-
<div
264-
className={cx('more-button', styles.moreButton)}
265-
onClick={(e) => e.stopPropagation()}
266-
>
267-
<Icon icon={MoreVerticalIcon} size={16} style={{ cursor: 'pointer' }} />
268-
</div>
269-
</DropdownMenu>
263+
<div onClick={(e) => e.stopPropagation()}>
264+
<DropdownMenu items={menuItems as any}>
265+
<div className={cx('more-button', styles.moreButton)}>
266+
<Icon icon={MoreVerticalIcon} size={16} style={{ cursor: 'pointer' }} />
267+
</div>
268+
</DropdownMenu>
269+
</div>
270270
)}
271271
<Flexbox
272272
align={'flex-start'}

src/app/[variants]/(main)/community/(detail)/user/features/UserGroupCard.tsx

Lines changed: 142 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,60 @@
11
'use client';
22

3-
import { Avatar, Block, Flexbox, Icon, Tag, Text, Tooltip, TooltipGroup } from '@lobehub/ui';
4-
import { createStaticStyles } from 'antd-style';
5-
import { ClockIcon, DownloadIcon, UsersIcon } from 'lucide-react';
3+
import {
4+
Tag as AntTag,
5+
Avatar,
6+
Block,
7+
DropdownMenu,
8+
Flexbox,
9+
Icon,
10+
Tag,
11+
Text,
12+
Tooltip,
13+
TooltipGroup,
14+
} from '@lobehub/ui';
15+
import { createStaticStyles, cx } from 'antd-style';
16+
import {
17+
AlertTriangle,
18+
ClockIcon,
19+
DownloadIcon,
20+
Eye,
21+
EyeOff,
22+
MoreVerticalIcon,
23+
Pencil,
24+
UsersIcon,
25+
} from 'lucide-react';
626
import qs from 'query-string';
727
import { memo, useCallback } from 'react';
828
import { useTranslation } from 'react-i18next';
929
import { Link, useNavigate } from 'react-router-dom';
1030
import urlJoin from 'url-join';
1131

1232
import PublishedTime from '@/components/PublishedTime';
13-
import { type DiscoverGroupAgentItem } from '@/types/discover';
33+
import { type DiscoverGroupAgentItem, type GroupAgentStatus } from '@/types/discover';
1434
import { formatIntergerNumber } from '@/utils/format';
1535

36+
import { useUserDetailContext } from './DetailProvider';
37+
38+
const getStatusTagColor = (status?: GroupAgentStatus) => {
39+
switch (status) {
40+
case 'published': {
41+
return 'green';
42+
}
43+
case 'unpublished': {
44+
return 'orange';
45+
}
46+
case 'deprecated': {
47+
return 'red';
48+
}
49+
case 'archived': {
50+
return 'default';
51+
}
52+
default: {
53+
return 'default';
54+
}
55+
}
56+
};
57+
1658
const styles = createStaticStyles(({ css, cssVar }) => {
1759
return {
1860
desc: css`
@@ -25,6 +67,16 @@ const styles = createStaticStyles(({ css, cssVar }) => {
2567
border-block-start: 1px dashed ${cssVar.colorBorder};
2668
background: ${cssVar.colorBgContainer};
2769
`,
70+
moreButton: css`
71+
position: absolute;
72+
z-index: 10;
73+
inset-block-start: 12px;
74+
inset-inline-end: 12px;
75+
76+
opacity: 0;
77+
78+
transition: opacity 0.2s;
79+
`,
2880
secondaryDesc: css`
2981
font-size: 12px;
3082
color: ${cssVar.colorTextDescription};
@@ -47,15 +99,31 @@ const styles = createStaticStyles(({ css, cssVar }) => {
4799
color: ${cssVar.colorLink};
48100
}
49101
`,
102+
wrapper: css`
103+
&:hover .more-button {
104+
opacity: 1;
105+
}
106+
`,
50107
};
51108
});
52109

53110
type UserGroupCardProps = DiscoverGroupAgentItem;
54111

55112
const UserGroupCard = memo<UserGroupCardProps>(
56-
({ avatar, title, description, createdAt, category, installCount, identifier, memberCount }) => {
57-
const { t } = useTranslation(['discover']);
113+
({
114+
avatar,
115+
title,
116+
description,
117+
createdAt,
118+
category,
119+
installCount,
120+
identifier,
121+
memberCount,
122+
status,
123+
}) => {
124+
const { t } = useTranslation(['discover', 'setting']);
58125
const navigate = useNavigate();
126+
const { isOwner, onStatusChange } = useUserDetailContext();
59127

60128
const link = qs.stringifyUrl(
61129
{
@@ -65,12 +133,55 @@ const UserGroupCard = memo<UserGroupCardProps>(
65133
{ skipNull: true },
66134
);
67135

136+
const isPublished = status === 'published';
137+
68138
const handleCardClick = useCallback(() => {
69139
navigate(link);
70140
}, [link, navigate]);
71141

142+
const handleEdit = useCallback(() => {
143+
navigate(urlJoin('/group', identifier, 'profile'));
144+
}, [identifier, navigate]);
145+
146+
const handleStatusAction = useCallback(
147+
(action: 'publish' | 'unpublish' | 'deprecate') => {
148+
onStatusChange?.(identifier, action, 'group');
149+
},
150+
[identifier, onStatusChange],
151+
);
152+
153+
const menuItems = isOwner
154+
? [
155+
{
156+
icon: <Icon icon={Pencil} />,
157+
key: 'edit',
158+
label: t('setting:myAgents.actions.edit'),
159+
onClick: handleEdit,
160+
},
161+
{
162+
type: 'divider' as const,
163+
},
164+
{
165+
icon: <Icon icon={isPublished ? EyeOff : Eye} />,
166+
key: 'togglePublish',
167+
label: isPublished
168+
? t('setting:myAgents.actions.unpublish')
169+
: t('setting:myAgents.actions.publish'),
170+
onClick: () => handleStatusAction(isPublished ? 'unpublish' : 'publish'),
171+
},
172+
{
173+
danger: true,
174+
icon: <Icon icon={AlertTriangle} />,
175+
key: 'deprecate',
176+
label: t('setting:myAgents.actions.deprecate'),
177+
onClick: () => handleStatusAction('deprecate'),
178+
},
179+
]
180+
: [];
181+
72182
return (
73183
<Block
184+
className={styles.wrapper}
74185
clickable
75186
height={'100%'}
76187
onClick={handleCardClick}
@@ -82,6 +193,15 @@ const UserGroupCard = memo<UserGroupCardProps>(
82193
variant={'outlined'}
83194
width={'100%'}
84195
>
196+
{isOwner && (
197+
<div onClick={(e) => e.stopPropagation()}>
198+
<DropdownMenu items={menuItems as any}>
199+
<div className={cx('more-button', styles.moreButton)}>
200+
<Icon icon={MoreVerticalIcon} size={16} style={{ cursor: 'pointer' }} />
201+
</div>
202+
</DropdownMenu>
203+
</div>
204+
)}
85205
<Flexbox
86206
align={'flex-start'}
87207
gap={16}
@@ -105,15 +225,22 @@ const UserGroupCard = memo<UserGroupCardProps>(
105225
overflow: 'hidden',
106226
}}
107227
>
108-
<Link
109-
onClick={(e) => e.stopPropagation()}
110-
style={{ color: 'inherit', flex: 1, overflow: 'hidden' }}
111-
to={link}
112-
>
113-
<Text as={'h3'} className={styles.title} ellipsis style={{ flex: 1 }}>
114-
{title}
115-
</Text>
116-
</Link>
228+
<Flexbox align={'center'} gap={8} horizontal>
229+
<Link
230+
onClick={(e) => e.stopPropagation()}
231+
style={{ color: 'inherit', flex: 1, overflow: 'hidden' }}
232+
to={link}
233+
>
234+
<Text as={'h3'} className={styles.title} ellipsis style={{ flex: 1 }}>
235+
{title}
236+
</Text>
237+
</Link>
238+
{isOwner && status && (
239+
<AntTag color={getStatusTagColor(status)} style={{ flexShrink: 0, margin: 0 }}>
240+
{t(`setting:myAgents.status.${status}`)}
241+
</AntTag>
242+
)}
243+
</Flexbox>
117244
</Flexbox>
118245
</Flexbox>
119246
</Flexbox>

src/app/[variants]/(main)/community/(detail)/user/features/useUserDetail.ts

Lines changed: 45 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@ import { useTranslation } from 'react-i18next';
66

77
import { useMarketAuth } from '@/layout/AuthProvider/MarketAuth';
88
import { marketApiService } from '@/services/marketApi';
9+
import { serverConfigSelectors, useServerConfigStore } from '@/store/serverConfig';
910

1011
export type AgentStatusAction = 'publish' | 'unpublish' | 'deprecate';
12+
export type EntityType = 'agent' | 'group';
1113

1214
interface UseUserDetailOptions {
1315
onMutate?: () => void;
@@ -17,68 +19,91 @@ export const useUserDetail = ({ onMutate }: UseUserDetailOptions = {}) => {
1719
const { t } = useTranslation('setting');
1820
const { message, modal } = App.useApp();
1921
const { session } = useMarketAuth();
22+
const enableMarketTrustedClient = useServerConfigStore(
23+
serverConfigSelectors.enableMarketTrustedClient,
24+
);
2025

2126
const handleStatusChange = useCallback(
22-
async (identifier: string, action: AgentStatusAction) => {
23-
if (!session?.accessToken) {
27+
async (identifier: string, action: AgentStatusAction, type: EntityType = 'agent') => {
28+
if (!enableMarketTrustedClient && !session?.accessToken) {
2429
message.error(t('myAgents.errors.notAuthenticated'));
2530
return;
2631
}
2732

28-
const messageKey = `agent-status-${action}`;
33+
const messageKey = `${type}-status-${action}`;
2934
const loadingText = t(`myAgents.actions.${action}Loading` as any);
3035
const successText = t(`myAgents.actions.${action}Success` as any);
3136
const errorText = t(`myAgents.actions.${action}Error` as any);
3237

33-
async function executeStatusChange(identifier: string, action: AgentStatusAction) {
38+
async function executeStatusChange(
39+
identifier: string,
40+
action: AgentStatusAction,
41+
type: EntityType,
42+
) {
3443
try {
3544
message.loading({ content: loadingText, key: messageKey });
3645
marketApiService.setAccessToken(session!.accessToken);
3746

38-
switch (action) {
39-
case 'publish': {
40-
await marketApiService.publishAgent(identifier);
41-
break;
42-
}
43-
case 'unpublish': {
44-
await marketApiService.unpublishAgent(identifier);
45-
break;
47+
if (type === 'group') {
48+
switch (action) {
49+
case 'publish': {
50+
await marketApiService.publishAgentGroup(identifier);
51+
break;
52+
}
53+
case 'unpublish': {
54+
await marketApiService.unpublishAgentGroup(identifier);
55+
break;
56+
}
57+
case 'deprecate': {
58+
await marketApiService.deprecateAgentGroup(identifier);
59+
break;
60+
}
4661
}
47-
case 'deprecate': {
48-
await marketApiService.deprecateAgent(identifier);
49-
break;
62+
} else {
63+
switch (action) {
64+
case 'publish': {
65+
await marketApiService.publishAgent(identifier);
66+
break;
67+
}
68+
case 'unpublish': {
69+
await marketApiService.unpublishAgent(identifier);
70+
break;
71+
}
72+
case 'deprecate': {
73+
await marketApiService.deprecateAgent(identifier);
74+
break;
75+
}
5076
}
5177
}
5278

5379
message.success({ content: successText, key: messageKey });
5480
onMutate?.();
5581
} catch (error) {
56-
console.error(`[useUserDetail] ${action} agent error:`, error);
82+
console.error(`[useUserDetail] ${action} ${type} error:`, error);
5783
message.error({
5884
content: `${errorText}: ${error instanceof Error ? error.message : 'Unknown error'}`,
5985
key: messageKey,
6086
});
6187
}
6288
}
6389

64-
// For deprecate action, show confirmation dialog first
6590
if (action === 'deprecate') {
6691
modal.confirm({
6792
cancelText: t('myAgents.actions.cancel'),
6893
content: t('myAgents.actions.deprecateConfirmContent'),
6994
okButtonProps: { danger: true },
7095
okText: t('myAgents.actions.confirmDeprecate'),
7196
onOk: async () => {
72-
await executeStatusChange(identifier, action);
97+
await executeStatusChange(identifier, action, type);
7398
},
7499
title: t('myAgents.actions.deprecateConfirmTitle'),
75100
});
76101
return;
77102
}
78103

79-
await executeStatusChange(identifier, action);
104+
await executeStatusChange(identifier, action, type);
80105
},
81-
[session?.accessToken, message, modal, t, onMutate],
106+
[enableMarketTrustedClient, session?.accessToken, message, modal, t, onMutate],
82107
);
83108

84109
return {

0 commit comments

Comments
 (0)