@lobehub/lobehub 2.0.0-next.322 → 2.0.0-next.323
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +55 -0
- package/apps/desktop/src/main/core/infrastructure/UpdaterManager.ts +9 -76
- package/apps/desktop/src/main/core/infrastructure/__tests__/UpdaterManager.test.ts +0 -1
- package/apps/desktop/src/main/modules/updater/configs.ts +0 -4
- package/changelog/v1.json +15 -0
- package/e2e/src/mocks/llm/index.ts +3 -3
- package/locales/ar/common.json +5 -0
- package/locales/ar/error.json +10 -1
- package/locales/bg-BG/common.json +5 -0
- package/locales/bg-BG/error.json +10 -1
- package/locales/de-DE/common.json +5 -0
- package/locales/de-DE/error.json +10 -1
- package/locales/en-US/common.json +5 -0
- package/locales/es-ES/common.json +5 -0
- package/locales/es-ES/error.json +10 -1
- package/locales/fa-IR/common.json +5 -0
- package/locales/fa-IR/error.json +10 -1
- package/locales/fr-FR/common.json +5 -0
- package/locales/fr-FR/error.json +10 -1
- package/locales/it-IT/common.json +5 -0
- package/locales/it-IT/error.json +10 -1
- package/locales/ja-JP/common.json +5 -0
- package/locales/ja-JP/error.json +10 -1
- package/locales/ko-KR/common.json +5 -0
- package/locales/ko-KR/error.json +10 -1
- package/locales/nl-NL/common.json +5 -0
- package/locales/nl-NL/error.json +10 -1
- package/locales/pl-PL/common.json +5 -0
- package/locales/pl-PL/error.json +10 -1
- package/locales/pt-BR/common.json +5 -0
- package/locales/pt-BR/error.json +10 -1
- package/locales/ru-RU/common.json +5 -0
- package/locales/ru-RU/error.json +10 -1
- package/locales/tr-TR/common.json +5 -0
- package/locales/tr-TR/error.json +10 -1
- package/locales/vi-VN/common.json +5 -0
- package/locales/vi-VN/error.json +10 -1
- package/locales/zh-CN/common.json +5 -0
- package/locales/zh-TW/common.json +5 -0
- package/locales/zh-TW/error.json +10 -1
- package/package.json +2 -2
- package/packages/business/const/src/branding.ts +1 -0
- package/packages/business/const/src/llm.ts +2 -1
- package/packages/const/src/settings/llm.ts +2 -1
- package/packages/const/src/settings/systemAgent.ts +12 -7
- package/packages/database/src/models/agent.ts +18 -1
- package/packages/database/src/models/chatGroup.ts +18 -1
- package/packages/database/src/types/chatGroup.ts +1 -0
- package/packages/model-bank/package.json +1 -1
- package/packages/model-bank/src/aiModels/index.ts +2 -2
- package/packages/model-bank/src/aiModels/lobehub/chat/anthropic.ts +256 -0
- package/packages/model-bank/src/aiModels/lobehub/chat/deepseek.ts +45 -0
- package/packages/model-bank/src/aiModels/lobehub/chat/google.ts +267 -0
- package/packages/model-bank/src/aiModels/lobehub/chat/index.ts +26 -0
- package/packages/model-bank/src/aiModels/lobehub/chat/minimax.ts +75 -0
- package/packages/model-bank/src/aiModels/lobehub/chat/moonshot.ts +28 -0
- package/packages/model-bank/src/aiModels/lobehub/chat/openai.ts +345 -0
- package/packages/model-bank/src/aiModels/lobehub/chat/xai.ts +32 -0
- package/packages/model-bank/src/aiModels/lobehub/image.ts +240 -0
- package/packages/model-bank/src/aiModels/lobehub/index.ts +10 -0
- package/packages/model-bank/src/aiModels/lobehub/utils.ts +58 -0
- package/packages/model-bank/src/modelProviders/index.ts +10 -10
- package/packages/model-runtime/src/core/streams/qwen.test.ts +320 -0
- package/packages/model-runtime/src/core/streams/qwen.ts +19 -10
- package/packages/types/package.json +1 -1
- package/packages/types/src/agentGroup/index.ts +2 -0
- package/packages/types/src/discover/assistants.ts +9 -0
- package/packages/types/src/discover/fork.ts +163 -0
- package/packages/types/src/discover/groupAgents.ts +13 -4
- package/packages/types/src/discover/index.ts +9 -0
- package/src/app/[variants]/(auth)/_layout/index.tsx +2 -1
- package/src/app/[variants]/(auth)/auth-error/page.tsx +5 -5
- package/src/app/[variants]/(main)/agent/_layout/Sidebar/Topic/List/Item/index.tsx +1 -2
- package/src/app/[variants]/(main)/community/(detail)/agent/features/Header.tsx +37 -0
- package/src/app/[variants]/(main)/community/(detail)/agent/features/Sidebar/ActionButton/ForkAndChat.tsx +133 -0
- package/src/app/[variants]/(main)/community/(detail)/agent/features/Sidebar/ActionButton/index.tsx +2 -2
- package/src/app/[variants]/(main)/community/(detail)/group_agent/features/Details/index.tsx +7 -10
- package/src/app/[variants]/(main)/community/(detail)/group_agent/features/Sidebar/ActionButton/ForkGroupAndChat.tsx +208 -0
- package/src/app/[variants]/(main)/community/(detail)/group_agent/features/Sidebar/ActionButton/index.tsx +2 -2
- package/src/app/[variants]/(main)/community/(detail)/user/features/DetailProvider.tsx +2 -0
- package/src/app/[variants]/(main)/community/(detail)/user/features/UserContent.tsx +7 -0
- package/src/app/[variants]/(main)/community/(detail)/user/features/UserForkedAgentGroups.tsx +63 -0
- package/src/app/[variants]/(main)/community/(detail)/user/features/UserForkedAgents.tsx +61 -0
- package/src/app/[variants]/(main)/community/(detail)/user/index.tsx +3 -1
- package/src/app/[variants]/(main)/group/_layout/Sidebar/Topic/List/Item/index.tsx +1 -2
- package/src/app/[variants]/(main)/settings/profile/index.tsx +92 -68
- package/src/app/[variants]/(mobile)/chat/features/Topic/index.tsx +2 -1
- package/src/features/CommandMenu/AskAgentCommands.tsx +105 -0
- package/src/features/CommandMenu/CommandMenuContext.tsx +57 -38
- package/src/features/CommandMenu/components/CommandInput.tsx +43 -9
- package/src/features/CommandMenu/index.tsx +89 -27
- package/src/features/CommandMenu/types.ts +6 -0
- package/src/features/CommandMenu/useCommandMenu.ts +62 -39
- package/src/locales/default/common.ts +5 -0
- package/src/locales/default/discover.ts +371 -0
- package/src/server/globalConfig/parseMemoryExtractionConfig.ts +7 -8
- package/src/server/routers/lambda/agent.ts +14 -0
- package/src/server/routers/lambda/agentGroup.ts +19 -3
- package/src/server/routers/lambda/market/agent.ts +234 -26
- package/src/server/routers/lambda/market/agentGroup.ts +204 -1
- package/src/server/services/discover/index.ts +52 -2
- package/src/services/agent.ts +8 -0
- package/src/services/chatGroup/index.ts +8 -0
- package/src/services/marketApi.ts +78 -0
- package/src/store/user/slices/settings/selectors/__snapshots__/settings.test.ts.snap +12 -12
- package/packages/model-bank/src/aiModels/lobehub.ts +0 -1315
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import { SiDiscord } from '@icons-pack/react-simple-icons';
|
|
4
|
+
import { SOCIAL_URL } from '@lobechat/business-const';
|
|
4
5
|
import { Alert, Button, Flexbox, Icon } from '@lobehub/ui';
|
|
5
|
-
import
|
|
6
|
+
import { cssVar } from 'antd-style';
|
|
6
7
|
import { parseAsString, useQueryState } from 'nuqs';
|
|
7
8
|
import { memo } from 'react';
|
|
8
9
|
import { useTranslation } from 'react-i18next';
|
|
9
10
|
|
|
10
11
|
import AuthCard from '@/features/AuthCard';
|
|
11
|
-
|
|
12
|
-
const DISCORD_URL = 'https://discord.gg/AYFPHvv2jT';
|
|
12
|
+
import Link from '@/libs/next/Link';
|
|
13
13
|
|
|
14
14
|
const normalizeErrorCode = (code?: string | null) =>
|
|
15
15
|
(code || 'UNKNOWN').trim().toUpperCase().replaceAll('-', '_');
|
|
@@ -35,8 +35,8 @@ const AuthErrorPage = memo(() => {
|
|
|
35
35
|
{t('actions.home')}
|
|
36
36
|
</Button>
|
|
37
37
|
</Link>
|
|
38
|
-
<Link href={
|
|
39
|
-
<Button block icon={<Icon icon={SiDiscord} />} type="text">
|
|
38
|
+
<Link href={SOCIAL_URL.discord} rel="noopener noreferrer" target="_blank">
|
|
39
|
+
<Button block icon={<Icon fill={cssVar.colorText} icon={SiDiscord} />} type="text">
|
|
40
40
|
{t('actions.discord')}
|
|
41
41
|
</Button>
|
|
42
42
|
</Link>
|
|
@@ -3,7 +3,6 @@ import { cssVar } from 'antd-style';
|
|
|
3
3
|
import { MessageSquareDashed, Star } from 'lucide-react';
|
|
4
4
|
import { Suspense, memo, useCallback, useMemo } from 'react';
|
|
5
5
|
import { useTranslation } from 'react-i18next';
|
|
6
|
-
import urlJoin from 'url-join';
|
|
7
6
|
|
|
8
7
|
import { isDesktop } from '@/const/version';
|
|
9
8
|
import NavItem from '@/features/NavPanel/components/NavItem';
|
|
@@ -33,7 +32,7 @@ const TopicItem = memo<TopicItemProps>(({ id, title, fav, active, threadId }) =>
|
|
|
33
32
|
// Construct href for cmd+click support
|
|
34
33
|
const href = useMemo(() => {
|
|
35
34
|
if (!activeAgentId || !id) return undefined;
|
|
36
|
-
return
|
|
35
|
+
return `/agent/${activeAgentId}?topic=${id}`;
|
|
37
36
|
}, [activeAgentId, id]);
|
|
38
37
|
|
|
39
38
|
const [editing, isLoading] = useChatStore((s) => [
|
|
@@ -19,6 +19,8 @@ import {
|
|
|
19
19
|
BookmarkIcon,
|
|
20
20
|
CoinsIcon,
|
|
21
21
|
DotIcon,
|
|
22
|
+
GitBranchIcon,
|
|
23
|
+
GitForkIcon,
|
|
22
24
|
HeartIcon,
|
|
23
25
|
} from 'lucide-react';
|
|
24
26
|
import qs from 'query-string';
|
|
@@ -29,6 +31,7 @@ import useSWR from 'swr';
|
|
|
29
31
|
import urlJoin from 'url-join';
|
|
30
32
|
|
|
31
33
|
import { useMarketAuth } from '@/layout/AuthProvider/MarketAuth';
|
|
34
|
+
import { marketApiService } from '@/services/marketApi';
|
|
32
35
|
import { socialService } from '@/services/social';
|
|
33
36
|
import { formatIntergerNumber } from '@/utils/format';
|
|
34
37
|
|
|
@@ -57,6 +60,8 @@ const Header = memo<{ mobile?: boolean }>(({ mobile: isMobile }) => {
|
|
|
57
60
|
pluginCount,
|
|
58
61
|
knowledgeCount,
|
|
59
62
|
userName,
|
|
63
|
+
forkCount,
|
|
64
|
+
forkedFromAgentId,
|
|
60
65
|
} = useDetailContext();
|
|
61
66
|
const { mobile = isMobile } = useResponsive();
|
|
62
67
|
const { isAuthenticated, signIn, session } = useMarketAuth();
|
|
@@ -85,6 +90,13 @@ const Header = memo<{ mobile?: boolean }>(({ mobile: isMobile }) => {
|
|
|
85
90
|
);
|
|
86
91
|
const isLiked = likeStatus?.isLiked ?? false;
|
|
87
92
|
|
|
93
|
+
// Fetch fork source info
|
|
94
|
+
const { data: forkSource } = useSWR(
|
|
95
|
+
identifier && forkedFromAgentId ? ['fork-source', identifier] : null,
|
|
96
|
+
() => marketApiService.getAgentForkSource(identifier!),
|
|
97
|
+
{ revalidateOnFocus: false },
|
|
98
|
+
);
|
|
99
|
+
|
|
88
100
|
const handleFavoriteClick = async () => {
|
|
89
101
|
if (!isAuthenticated) {
|
|
90
102
|
await signIn();
|
|
@@ -223,6 +235,31 @@ const Header = memo<{ mobile?: boolean }>(({ mobile: isMobile }) => {
|
|
|
223
235
|
template={'MMM DD, YYYY'}
|
|
224
236
|
/>
|
|
225
237
|
</Flexbox>
|
|
238
|
+
|
|
239
|
+
{/* Fork information */}
|
|
240
|
+
{(forkSource?.source || (forkCount && forkCount > 0)) && (
|
|
241
|
+
<Flexbox align={'center'} gap={12} horizontal>
|
|
242
|
+
{forkSource?.source && (
|
|
243
|
+
<Flexbox align={'center'} gap={6} horizontal>
|
|
244
|
+
<Icon icon={GitForkIcon} size={'small'} />
|
|
245
|
+
<Text className={styles.time} type={'secondary'}>
|
|
246
|
+
{t('fork.forkedFrom')}:{' '}
|
|
247
|
+
<Link to={urlJoin('/community/agent', forkSource.source.identifier)}>
|
|
248
|
+
{forkSource.source.name}
|
|
249
|
+
</Link>
|
|
250
|
+
</Text>
|
|
251
|
+
</Flexbox>
|
|
252
|
+
)}
|
|
253
|
+
{forkCount && forkCount > 0 && (
|
|
254
|
+
<Flexbox align={'center'} gap={6} horizontal>
|
|
255
|
+
<Icon icon={GitBranchIcon} size={'small'} />
|
|
256
|
+
<Text className={styles.time} type={'secondary'}>
|
|
257
|
+
{forkCount} {t('fork.forks')}
|
|
258
|
+
</Text>
|
|
259
|
+
</Flexbox>
|
|
260
|
+
)}
|
|
261
|
+
</Flexbox>
|
|
262
|
+
)}
|
|
226
263
|
</Flexbox>
|
|
227
264
|
</Flexbox>
|
|
228
265
|
<TooltipGroup>
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { Button } from '@lobehub/ui';
|
|
4
|
+
import { App } from 'antd';
|
|
5
|
+
import { createStaticStyles } from 'antd-style';
|
|
6
|
+
import { customAlphabet } from 'nanoid/non-secure';
|
|
7
|
+
import { memo, useState } from 'react';
|
|
8
|
+
import { useTranslation } from 'react-i18next';
|
|
9
|
+
import { useNavigate } from 'react-router-dom';
|
|
10
|
+
|
|
11
|
+
import { SESSION_CHAT_URL } from '@/const/url';
|
|
12
|
+
import { agentService } from '@/services/agent';
|
|
13
|
+
import { discoverService } from '@/services/discover';
|
|
14
|
+
import { marketApiService } from '@/services/marketApi';
|
|
15
|
+
import { useAgentStore } from '@/store/agent';
|
|
16
|
+
import { useHomeStore } from '@/store/home';
|
|
17
|
+
|
|
18
|
+
import { useDetailContext } from '../../DetailProvider';
|
|
19
|
+
|
|
20
|
+
const styles = createStaticStyles(({ css }) => ({
|
|
21
|
+
buttonGroup: css`
|
|
22
|
+
width: 100%;
|
|
23
|
+
`,
|
|
24
|
+
}));
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Generate a market identifier (8-character lowercase alphanumeric string)
|
|
28
|
+
*/
|
|
29
|
+
const generateMarketIdentifier = () => {
|
|
30
|
+
const alphabet = '0123456789abcdefghijklmnopqrstuvwxyz';
|
|
31
|
+
const generate = customAlphabet(alphabet, 8);
|
|
32
|
+
return generate();
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const ForkAndChat = memo<{ mobile?: boolean }>(({ mobile }) => {
|
|
36
|
+
const { identifier, title, config, avatar, backgroundColor, description, tags, editorData } =
|
|
37
|
+
useDetailContext();
|
|
38
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
39
|
+
const createAgent = useAgentStore((s) => s.createAgent);
|
|
40
|
+
const refreshAgentList = useHomeStore((s) => s.refreshAgentList);
|
|
41
|
+
const { message } = App.useApp();
|
|
42
|
+
const navigate = useNavigate();
|
|
43
|
+
const { t } = useTranslation('discover');
|
|
44
|
+
|
|
45
|
+
const meta = {
|
|
46
|
+
avatar,
|
|
47
|
+
backgroundColor,
|
|
48
|
+
description,
|
|
49
|
+
marketIdentifier: identifier,
|
|
50
|
+
tags,
|
|
51
|
+
title,
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const handleForkAndChat = async () => {
|
|
55
|
+
try {
|
|
56
|
+
setIsLoading(true);
|
|
57
|
+
|
|
58
|
+
// Step 1: Check if user has already forked this agent
|
|
59
|
+
const existingAgentId = await agentService.getAgentByForkedFromIdentifier(identifier!);
|
|
60
|
+
|
|
61
|
+
if (existingAgentId) {
|
|
62
|
+
// User has already forked this agent, navigate to existing fork
|
|
63
|
+
message.info(t('fork.alreadyForked'));
|
|
64
|
+
navigate(SESSION_CHAT_URL(existingAgentId, mobile));
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Generate a unique identifier for the forked agent
|
|
69
|
+
const newIdentifier = generateMarketIdentifier();
|
|
70
|
+
|
|
71
|
+
// Step 2: Fork the agent via Market API
|
|
72
|
+
const forkResult = await marketApiService.forkAgent(identifier!, {
|
|
73
|
+
identifier: newIdentifier,
|
|
74
|
+
name: title,
|
|
75
|
+
status: 'published',
|
|
76
|
+
visibility: 'public',
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
// Step 3: Create agent config with forked data
|
|
80
|
+
if (!config) throw new Error('Agent config is missing');
|
|
81
|
+
|
|
82
|
+
const agentData = {
|
|
83
|
+
config: {
|
|
84
|
+
...config,
|
|
85
|
+
editorData,
|
|
86
|
+
...meta,
|
|
87
|
+
marketIdentifier: forkResult.agent.identifier,
|
|
88
|
+
params: {
|
|
89
|
+
...config.params,
|
|
90
|
+
forkedFromIdentifier: identifier, // Store the source agent identifier
|
|
91
|
+
},
|
|
92
|
+
title: forkResult.agent.name,
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
// Step 4: Add to local agent list
|
|
97
|
+
const result = await createAgent(agentData);
|
|
98
|
+
await refreshAgentList();
|
|
99
|
+
|
|
100
|
+
// Step 5: Report fork event (using 'add' event type)
|
|
101
|
+
discoverService.reportAgentEvent({
|
|
102
|
+
event: 'add',
|
|
103
|
+
identifier: forkResult.agent.identifier,
|
|
104
|
+
source: location.pathname,
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
message.success(t('fork.success'));
|
|
108
|
+
|
|
109
|
+
// Step 6: Navigate to chat
|
|
110
|
+
navigate(SESSION_CHAT_URL(result!.agentId || result!.sessionId, mobile));
|
|
111
|
+
} catch (error: any) {
|
|
112
|
+
console.error('Fork failed:', error);
|
|
113
|
+
message.error(t('fork.failed'));
|
|
114
|
+
} finally {
|
|
115
|
+
setIsLoading(false);
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
return (
|
|
120
|
+
<Button
|
|
121
|
+
block
|
|
122
|
+
className={styles.buttonGroup}
|
|
123
|
+
loading={isLoading}
|
|
124
|
+
onClick={handleForkAndChat}
|
|
125
|
+
size={'large'}
|
|
126
|
+
type={'primary'}
|
|
127
|
+
>
|
|
128
|
+
{t('fork.forkAndChat')}
|
|
129
|
+
</Button>
|
|
130
|
+
);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
export default ForkAndChat;
|
package/src/app/[variants]/(main)/community/(detail)/agent/features/Sidebar/ActionButton/index.tsx
CHANGED
|
@@ -8,13 +8,13 @@ import { OFFICIAL_URL } from '@/const/url';
|
|
|
8
8
|
|
|
9
9
|
import ShareButton from '../../../../features/ShareButton';
|
|
10
10
|
import { useDetailContext } from '../../DetailProvider';
|
|
11
|
-
import
|
|
11
|
+
import ForkAndChat from './ForkAndChat';
|
|
12
12
|
|
|
13
13
|
const ActionButton = memo<{ mobile?: boolean }>(({ mobile }) => {
|
|
14
14
|
const { avatar, description, tags, title, identifier } = useDetailContext();
|
|
15
15
|
return (
|
|
16
16
|
<Flexbox align={'center'} gap={8} horizontal>
|
|
17
|
-
<
|
|
17
|
+
<ForkAndChat mobile={mobile} />
|
|
18
18
|
<ShareButton
|
|
19
19
|
meta={{
|
|
20
20
|
avatar: avatar,
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { Flexbox } from '@lobehub/ui';
|
|
2
2
|
import { useResponsive } from 'antd-style';
|
|
3
|
-
import { useQueryState } from 'nuqs';
|
|
4
3
|
import { memo } from 'react';
|
|
5
4
|
|
|
5
|
+
import { useQueryState } from '@/hooks/useQueryParam';
|
|
6
|
+
|
|
6
7
|
import Sidebar from '../Sidebar';
|
|
7
8
|
import Nav, { GroupAgentNavKey } from './Nav';
|
|
8
9
|
import Overview from './Overview';
|
|
@@ -11,37 +12,33 @@ import Versions from './Versions';
|
|
|
11
12
|
|
|
12
13
|
const Details = memo<{ mobile?: boolean }>(({ mobile: isMobile }) => {
|
|
13
14
|
const { mobile = isMobile } = useResponsive();
|
|
14
|
-
const [
|
|
15
|
-
|
|
15
|
+
const [activeTab, setActiveTab] = useQueryState('activeTab', {
|
|
16
|
+
clearOnDefault: true,
|
|
17
|
+
defaultValue: GroupAgentNavKey.Overview,
|
|
18
|
+
});
|
|
16
19
|
|
|
17
20
|
return (
|
|
18
21
|
<Flexbox gap={24}>
|
|
19
|
-
{/* Navigation */}
|
|
20
22
|
<Nav
|
|
21
23
|
activeTab={activeTab as GroupAgentNavKey}
|
|
22
24
|
mobile={mobile}
|
|
23
|
-
setActiveTab={
|
|
25
|
+
setActiveTab={setActiveTab}
|
|
24
26
|
/>
|
|
25
|
-
|
|
26
27
|
<Flexbox
|
|
27
28
|
gap={48}
|
|
28
29
|
horizontal={!mobile}
|
|
29
30
|
style={mobile ? { flexDirection: 'column-reverse' } : undefined}
|
|
30
31
|
>
|
|
31
|
-
{/* Main Content */}
|
|
32
32
|
<Flexbox
|
|
33
33
|
style={{
|
|
34
34
|
overflow: 'hidden',
|
|
35
35
|
}}
|
|
36
36
|
width={'100%'}
|
|
37
37
|
>
|
|
38
|
-
{/* Tab Content */}
|
|
39
38
|
{activeTab === GroupAgentNavKey.Overview && <Overview />}
|
|
40
39
|
{activeTab === GroupAgentNavKey.SystemRole && <SystemRole />}
|
|
41
40
|
{activeTab === GroupAgentNavKey.Versions && <Versions />}
|
|
42
41
|
</Flexbox>
|
|
43
|
-
|
|
44
|
-
{/* Sidebar */}
|
|
45
42
|
<Sidebar mobile={mobile} />
|
|
46
43
|
</Flexbox>
|
|
47
44
|
</Flexbox>
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { Button } from '@lobehub/ui';
|
|
4
|
+
import { App } from 'antd';
|
|
5
|
+
import { createStaticStyles } from 'antd-style';
|
|
6
|
+
import { customAlphabet } from 'nanoid/non-secure';
|
|
7
|
+
import { memo, useState } from 'react';
|
|
8
|
+
import { useTranslation } from 'react-i18next';
|
|
9
|
+
import { useNavigate } from 'react-router-dom';
|
|
10
|
+
import urlJoin from 'url-join';
|
|
11
|
+
|
|
12
|
+
import { chatGroupService } from '@/services/chatGroup';
|
|
13
|
+
import { discoverService } from '@/services/discover';
|
|
14
|
+
import { marketApiService } from '@/services/marketApi';
|
|
15
|
+
import { useAgentGroupStore } from '@/store/agentGroup';
|
|
16
|
+
|
|
17
|
+
import { useDetailContext } from '../../DetailProvider';
|
|
18
|
+
|
|
19
|
+
const styles = createStaticStyles(({ css }) => ({
|
|
20
|
+
buttonGroup: css`
|
|
21
|
+
width: 100%;
|
|
22
|
+
`,
|
|
23
|
+
}));
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Generate a market identifier (8-character lowercase alphanumeric string)
|
|
27
|
+
*/
|
|
28
|
+
const generateMarketIdentifier = () => {
|
|
29
|
+
const alphabet = '0123456789abcdefghijklmnopqrstuvwxyz';
|
|
30
|
+
const generate = customAlphabet(alphabet, 8);
|
|
31
|
+
return generate();
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const ForkGroupAndChat = memo<{ mobile?: boolean }>(() => {
|
|
35
|
+
const {
|
|
36
|
+
avatar,
|
|
37
|
+
backgroundColor,
|
|
38
|
+
description,
|
|
39
|
+
tags,
|
|
40
|
+
title,
|
|
41
|
+
config,
|
|
42
|
+
identifier,
|
|
43
|
+
memberAgents = [],
|
|
44
|
+
} = useDetailContext();
|
|
45
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
46
|
+
const { message } = App.useApp();
|
|
47
|
+
const { t } = useTranslation('discover');
|
|
48
|
+
const navigate = useNavigate();
|
|
49
|
+
const loadGroups = useAgentGroupStore((s) => s.loadGroups);
|
|
50
|
+
|
|
51
|
+
const meta = {
|
|
52
|
+
avatar,
|
|
53
|
+
backgroundColor,
|
|
54
|
+
description,
|
|
55
|
+
tags,
|
|
56
|
+
title,
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const handleForkAndChat = async () => {
|
|
60
|
+
try {
|
|
61
|
+
setIsLoading(true);
|
|
62
|
+
|
|
63
|
+
// Step 1: Check if user has already forked this group
|
|
64
|
+
const existingGroupId = await chatGroupService.getGroupByForkedFromIdentifier(identifier!);
|
|
65
|
+
|
|
66
|
+
if (existingGroupId) {
|
|
67
|
+
// User has already forked this group, navigate to existing fork
|
|
68
|
+
message.info(t('fork.alreadyForked'));
|
|
69
|
+
navigate(urlJoin('/group', existingGroupId));
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (!config) {
|
|
74
|
+
message.error(
|
|
75
|
+
t('groupAgents.noConfig', { defaultValue: 'Group configuration not available' }),
|
|
76
|
+
);
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Generate a unique identifier for the forked group
|
|
81
|
+
const newIdentifier = generateMarketIdentifier();
|
|
82
|
+
|
|
83
|
+
// Step 2: Fork the group via Market API
|
|
84
|
+
const forkResult = await marketApiService.forkAgentGroup(identifier!, {
|
|
85
|
+
identifier: newIdentifier,
|
|
86
|
+
name: title,
|
|
87
|
+
status: 'published',
|
|
88
|
+
visibility: 'public',
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
// Step 3: Find supervisor from memberAgents
|
|
92
|
+
const supervisorMember = memberAgents.find((member: any) => {
|
|
93
|
+
const agent = member.agent || member;
|
|
94
|
+
const role = member.role || agent.role;
|
|
95
|
+
return role === 'supervisor';
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
// Prepare supervisor config
|
|
99
|
+
let supervisorConfig;
|
|
100
|
+
if (supervisorMember) {
|
|
101
|
+
const member = supervisorMember as any;
|
|
102
|
+
const agent = member.agent || member;
|
|
103
|
+
const currentVersion = member.currentVersion || member;
|
|
104
|
+
const rawConfig = {
|
|
105
|
+
avatar: currentVersion.avatar,
|
|
106
|
+
backgroundColor: currentVersion.backgroundColor,
|
|
107
|
+
description: currentVersion.description,
|
|
108
|
+
model: currentVersion.config?.model || currentVersion.model,
|
|
109
|
+
params: currentVersion.config?.params || currentVersion.params,
|
|
110
|
+
provider: currentVersion.config?.provider || currentVersion.provider,
|
|
111
|
+
systemRole:
|
|
112
|
+
currentVersion.config?.systemRole ||
|
|
113
|
+
currentVersion.config?.systemPrompt ||
|
|
114
|
+
currentVersion.systemRole ||
|
|
115
|
+
currentVersion.content,
|
|
116
|
+
tags: currentVersion.tags,
|
|
117
|
+
title: currentVersion.name || agent.name || 'Supervisor',
|
|
118
|
+
};
|
|
119
|
+
// Filter out null/undefined values
|
|
120
|
+
supervisorConfig = Object.fromEntries(
|
|
121
|
+
// eslint-disable-next-line eqeqeq, @typescript-eslint/no-unused-vars
|
|
122
|
+
Object.entries(rawConfig).filter(([_, v]) => v != null),
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Step 4: Prepare group config
|
|
127
|
+
const groupConfig = {
|
|
128
|
+
config: {
|
|
129
|
+
...config,
|
|
130
|
+
forkedFromIdentifier: identifier, // Store the source group identifier
|
|
131
|
+
},
|
|
132
|
+
// Group content is the supervisor's systemRole (for backward compatibility)
|
|
133
|
+
content: supervisorConfig?.systemRole || config.systemRole,
|
|
134
|
+
...meta,
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
// Step 5: Prepare member agents from market data
|
|
138
|
+
// Filter out supervisor role as it will be created separately using supervisorConfig
|
|
139
|
+
const members = memberAgents
|
|
140
|
+
.filter((member: any) => {
|
|
141
|
+
const agent = member.agent || member;
|
|
142
|
+
const role = member.role || agent.role;
|
|
143
|
+
return role !== 'supervisor';
|
|
144
|
+
})
|
|
145
|
+
.map((member: any) => {
|
|
146
|
+
const agent = member.agent || member;
|
|
147
|
+
const currentVersion = member.currentVersion || member;
|
|
148
|
+
return {
|
|
149
|
+
avatar: currentVersion.avatar,
|
|
150
|
+
backgroundColor: currentVersion.backgroundColor,
|
|
151
|
+
description: currentVersion.description,
|
|
152
|
+
model: currentVersion.config?.model || currentVersion.model,
|
|
153
|
+
plugins: currentVersion.plugins,
|
|
154
|
+
provider: currentVersion.config?.provider || currentVersion.provider,
|
|
155
|
+
systemRole:
|
|
156
|
+
currentVersion.config?.systemRole ||
|
|
157
|
+
currentVersion.config?.systemPrompt ||
|
|
158
|
+
currentVersion.systemRole ||
|
|
159
|
+
currentVersion.content,
|
|
160
|
+
tags: currentVersion.tags,
|
|
161
|
+
title: currentVersion.name || agent.name,
|
|
162
|
+
};
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
// Step 6: Create group with all members in one request
|
|
166
|
+
const result = await chatGroupService.createGroupWithMembers(
|
|
167
|
+
groupConfig,
|
|
168
|
+
members,
|
|
169
|
+
supervisorConfig,
|
|
170
|
+
);
|
|
171
|
+
|
|
172
|
+
// Refresh group list
|
|
173
|
+
await loadGroups();
|
|
174
|
+
|
|
175
|
+
// Step 7: Report fork event (using 'add' event type)
|
|
176
|
+
discoverService.reportAgentEvent({
|
|
177
|
+
event: 'add',
|
|
178
|
+
identifier: forkResult.group.identifier,
|
|
179
|
+
source: location.pathname,
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
message.success(t('fork.success'));
|
|
183
|
+
|
|
184
|
+
// Step 8: Navigate to chat
|
|
185
|
+
navigate(urlJoin('/group', result.groupId));
|
|
186
|
+
} catch (error: any) {
|
|
187
|
+
console.error('Fork group failed:', error);
|
|
188
|
+
message.error(t('fork.failed'));
|
|
189
|
+
} finally {
|
|
190
|
+
setIsLoading(false);
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
return (
|
|
195
|
+
<Button
|
|
196
|
+
block
|
|
197
|
+
className={styles.buttonGroup}
|
|
198
|
+
loading={isLoading}
|
|
199
|
+
onClick={handleForkAndChat}
|
|
200
|
+
size={'large'}
|
|
201
|
+
type={'primary'}
|
|
202
|
+
>
|
|
203
|
+
{t('fork.forkAndChat')}
|
|
204
|
+
</Button>
|
|
205
|
+
);
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
export default ForkGroupAndChat;
|
|
@@ -8,14 +8,14 @@ import { OFFICIAL_URL } from '@/const/url';
|
|
|
8
8
|
|
|
9
9
|
import ShareButton from '../../../../features/ShareButton';
|
|
10
10
|
import { useDetailContext } from '../../DetailProvider';
|
|
11
|
-
import
|
|
11
|
+
import ForkGroupAndChat from './ForkGroupAndChat';
|
|
12
12
|
|
|
13
13
|
const ActionButton = memo<{ mobile?: boolean }>(({ mobile }) => {
|
|
14
14
|
const { avatar, title, description, tags, identifier } = useDetailContext();
|
|
15
15
|
|
|
16
16
|
return (
|
|
17
17
|
<Flexbox align={'center'} gap={8} horizontal>
|
|
18
|
-
<
|
|
18
|
+
<ForkGroupAndChat mobile={mobile} />
|
|
19
19
|
{identifier && (
|
|
20
20
|
<ShareButton
|
|
21
21
|
meta={{
|
|
@@ -13,6 +13,8 @@ export interface UserDetailContextConfig {
|
|
|
13
13
|
agentCount: number;
|
|
14
14
|
agentGroups?: DiscoverGroupAgentItem[];
|
|
15
15
|
agents: DiscoverAssistantItem[];
|
|
16
|
+
forkedAgentGroups?: DiscoverGroupAgentItem[];
|
|
17
|
+
forkedAgents?: DiscoverAssistantItem[];
|
|
16
18
|
groupCount: number;
|
|
17
19
|
isOwner: boolean;
|
|
18
20
|
mobile?: boolean;
|
|
@@ -3,16 +3,23 @@
|
|
|
3
3
|
import { Flexbox } from '@lobehub/ui';
|
|
4
4
|
import { memo } from 'react';
|
|
5
5
|
|
|
6
|
+
import { useUserDetailContext } from './DetailProvider';
|
|
6
7
|
import UserAgentList from './UserAgentList';
|
|
7
8
|
import UserFavoriteAgents from './UserFavoriteAgents';
|
|
8
9
|
import UserFavoritePlugins from './UserFavoritePlugins';
|
|
10
|
+
import UserForkedAgentGroups from './UserForkedAgentGroups';
|
|
11
|
+
import UserForkedAgents from './UserForkedAgents';
|
|
9
12
|
import UserGroupList from './UserGroupList';
|
|
10
13
|
|
|
11
14
|
const UserContent = memo(() => {
|
|
15
|
+
const { forkedAgents, forkedAgentGroups } = useUserDetailContext();
|
|
16
|
+
|
|
12
17
|
return (
|
|
13
18
|
<Flexbox gap={32}>
|
|
14
19
|
<UserAgentList />
|
|
15
20
|
<UserGroupList />
|
|
21
|
+
<UserForkedAgents agents={forkedAgents} />
|
|
22
|
+
<UserForkedAgentGroups agentGroups={forkedAgentGroups} />
|
|
16
23
|
<UserFavoriteAgents />
|
|
17
24
|
<UserFavoritePlugins />
|
|
18
25
|
</Flexbox>
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { Flexbox, Grid, Tag, Text } from '@lobehub/ui';
|
|
4
|
+
import { Pagination } from 'antd';
|
|
5
|
+
import { GitForkIcon } from 'lucide-react';
|
|
6
|
+
import { memo, useMemo, useState } from 'react';
|
|
7
|
+
import { useTranslation } from 'react-i18next';
|
|
8
|
+
|
|
9
|
+
import { type DiscoverGroupAgentItem } from '@/types/discover';
|
|
10
|
+
|
|
11
|
+
import UserGroupCard from './UserGroupCard';
|
|
12
|
+
|
|
13
|
+
interface UserForkedAgentGroupsProps {
|
|
14
|
+
agentGroups?: DiscoverGroupAgentItem[];
|
|
15
|
+
pageSize?: number;
|
|
16
|
+
rows?: number;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const UserForkedAgentGroups = memo<UserForkedAgentGroupsProps>(
|
|
20
|
+
({ agentGroups = [], rows = 4, pageSize = 10 }) => {
|
|
21
|
+
const { t } = useTranslation('discover');
|
|
22
|
+
const [currentPage, setCurrentPage] = useState(1);
|
|
23
|
+
|
|
24
|
+
const paginatedGroups = useMemo(() => {
|
|
25
|
+
const startIndex = (currentPage - 1) * pageSize;
|
|
26
|
+
return agentGroups.slice(startIndex, startIndex + pageSize);
|
|
27
|
+
}, [agentGroups, currentPage, pageSize]);
|
|
28
|
+
|
|
29
|
+
if (agentGroups.length === 0) return null;
|
|
30
|
+
|
|
31
|
+
const showPagination = agentGroups.length > pageSize;
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<Flexbox gap={16}>
|
|
35
|
+
<Flexbox align={'center'} gap={8} horizontal>
|
|
36
|
+
<GitForkIcon size={16} />
|
|
37
|
+
<Text fontSize={16} weight={500}>
|
|
38
|
+
{t('user.forkedAgentGroups')}
|
|
39
|
+
</Text>
|
|
40
|
+
<Tag>{agentGroups.length}</Tag>
|
|
41
|
+
</Flexbox>
|
|
42
|
+
<Grid rows={rows} width={'100%'}>
|
|
43
|
+
{paginatedGroups.map((group, index) => (
|
|
44
|
+
<UserGroupCard key={group.identifier || index} {...group} />
|
|
45
|
+
))}
|
|
46
|
+
</Grid>
|
|
47
|
+
{showPagination && (
|
|
48
|
+
<Flexbox align={'center'} justify={'center'}>
|
|
49
|
+
<Pagination
|
|
50
|
+
current={currentPage}
|
|
51
|
+
onChange={(page) => setCurrentPage(page)}
|
|
52
|
+
pageSize={pageSize}
|
|
53
|
+
showSizeChanger={false}
|
|
54
|
+
total={agentGroups.length}
|
|
55
|
+
/>
|
|
56
|
+
</Flexbox>
|
|
57
|
+
)}
|
|
58
|
+
</Flexbox>
|
|
59
|
+
);
|
|
60
|
+
},
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
export default UserForkedAgentGroups;
|