@lobehub/lobehub 2.0.0-next.224 → 2.0.0-next.226
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/.github/workflows/test.yml +18 -14
- package/CHANGELOG.md +50 -0
- package/changelog/v1.json +14 -0
- package/locales/en-US/common.json +3 -2
- package/locales/en-US/setting.json +4 -0
- package/locales/zh-CN/setting.json +4 -0
- package/package.json +2 -2
- package/packages/database/src/models/user.ts +33 -0
- package/packages/database/src/repositories/knowledge/index.ts +1 -1
- package/src/app/[variants]/(main)/chat/profile/features/Header/AgentPublishButton/ForkConfirmModal.tsx +67 -0
- package/src/app/[variants]/(main)/chat/profile/features/Header/AgentPublishButton/PublishButton.tsx +92 -105
- package/src/app/[variants]/(main)/chat/profile/features/Header/AgentPublishButton/index.tsx +13 -48
- package/src/app/[variants]/(main)/chat/profile/features/Header/AgentPublishButton/useMarketPublish.ts +69 -93
- package/src/business/client/hooks/useRenderBusinessChatErrorMessageExtra.tsx +10 -0
- package/src/features/CommandMenu/ContextCommands.tsx +97 -37
- package/src/features/CommandMenu/SearchResults.tsx +100 -276
- package/src/features/CommandMenu/components/CommandItem.tsx +1 -1
- package/src/features/CommandMenu/utils/contextCommands.ts +56 -1
- package/src/features/Conversation/Error/index.tsx +7 -1
- package/src/layout/AuthProvider/MarketAuth/MarketAuthProvider.tsx +11 -28
- package/src/layout/AuthProvider/MarketAuth/ProfileSetupModal.tsx +30 -25
- package/src/layout/AuthProvider/MarketAuth/useMarketUserProfile.ts +4 -9
- package/src/libs/redis/manager.ts +51 -15
- package/src/libs/trpc/lambda/middleware/index.ts +1 -0
- package/src/libs/trpc/lambda/middleware/marketSDK.ts +68 -0
- package/src/locales/default/common.ts +2 -2
- package/src/locales/default/setting.ts +5 -0
- package/src/server/routers/lambda/market/agent.ts +504 -0
- package/src/server/routers/lambda/market/index.ts +17 -0
- package/src/server/routers/lambda/market/oidc.ts +169 -0
- package/src/server/routers/lambda/market/social.ts +532 -0
- package/src/server/routers/lambda/market/user.ts +123 -0
- package/src/services/marketApi.ts +24 -84
- package/src/services/social.ts +70 -166
|
@@ -3,32 +3,45 @@ import { useCallback, useRef, useState } from 'react';
|
|
|
3
3
|
import { useTranslation } from 'react-i18next';
|
|
4
4
|
|
|
5
5
|
import { message } from '@/components/AntdStaticMethods';
|
|
6
|
-
import { checkOwnership } from '@/hooks/useAgentOwnershipCheck';
|
|
7
6
|
import { useTokenCount } from '@/hooks/useTokenCount';
|
|
8
7
|
import { useMarketAuth } from '@/layout/AuthProvider/MarketAuth';
|
|
9
|
-
import {
|
|
8
|
+
import { lambdaClient } from '@/libs/trpc/client';
|
|
10
9
|
import { useAgentStore } from '@/store/agent';
|
|
11
10
|
import { agentChatConfigSelectors, agentSelectors } from '@/store/agent/selectors';
|
|
12
11
|
import { useGlobalStore } from '@/store/global';
|
|
13
12
|
import { globalGeneralSelectors } from '@/store/global/selectors';
|
|
14
|
-
import { useServerConfigStore } from '@/store/serverConfig';
|
|
15
|
-
import { serverConfigSelectors } from '@/store/serverConfig/selectors';
|
|
16
13
|
|
|
17
14
|
import type { MarketPublishAction } from './types';
|
|
18
|
-
import { generateDefaultChangelog
|
|
15
|
+
import { generateDefaultChangelog } from './utils';
|
|
16
|
+
|
|
17
|
+
export interface OriginalAgentInfo {
|
|
18
|
+
author?: {
|
|
19
|
+
avatar?: string;
|
|
20
|
+
name?: string;
|
|
21
|
+
userName?: string;
|
|
22
|
+
};
|
|
23
|
+
avatar?: string;
|
|
24
|
+
identifier: string;
|
|
25
|
+
name: string;
|
|
26
|
+
}
|
|
19
27
|
|
|
20
28
|
interface UseMarketPublishOptions {
|
|
21
29
|
action: MarketPublishAction;
|
|
22
30
|
onSuccess?: (identifier: string) => void;
|
|
23
31
|
}
|
|
24
32
|
|
|
33
|
+
export interface CheckOwnershipResult {
|
|
34
|
+
needsForkConfirm: boolean;
|
|
35
|
+
originalAgent: OriginalAgentInfo | null;
|
|
36
|
+
}
|
|
37
|
+
|
|
25
38
|
export const useMarketPublish = ({ action, onSuccess }: UseMarketPublishOptions) => {
|
|
26
39
|
const { t } = useTranslation('setting');
|
|
27
40
|
const [isPublishing, setIsPublishing] = useState(false);
|
|
41
|
+
const [isCheckingOwnership, setIsCheckingOwnership] = useState(false);
|
|
28
42
|
// 使用 ref 来同步跟踪发布状态,避免闭包导致的竞态问题
|
|
29
43
|
const isPublishingRef = useRef(false);
|
|
30
|
-
const { isAuthenticated
|
|
31
|
-
const enableMarketTrustedClient = useServerConfigStore(serverConfigSelectors.enableMarketTrustedClient);
|
|
44
|
+
const { isAuthenticated } = useMarketAuth();
|
|
32
45
|
|
|
33
46
|
// Agent data from store
|
|
34
47
|
const meta = useAgentStore(agentSelectors.currentAgentMeta, isEqual);
|
|
@@ -46,15 +59,49 @@ export const useMarketPublish = ({ action, onSuccess }: UseMarketPublishOptions)
|
|
|
46
59
|
|
|
47
60
|
const isSubmit = action === 'submit';
|
|
48
61
|
|
|
62
|
+
/**
|
|
63
|
+
* Check ownership before publishing
|
|
64
|
+
* Returns whether fork confirmation is needed and original agent info
|
|
65
|
+
*/
|
|
66
|
+
const checkOwnership = useCallback(async (): Promise<CheckOwnershipResult> => {
|
|
67
|
+
const identifier = meta?.marketIdentifier;
|
|
68
|
+
|
|
69
|
+
// No identifier means new agent, no need to check
|
|
70
|
+
if (!identifier) {
|
|
71
|
+
return { needsForkConfirm: false, originalAgent: null };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
try {
|
|
75
|
+
setIsCheckingOwnership(true);
|
|
76
|
+
const result = await lambdaClient.market.agent.checkOwnership.query({ identifier });
|
|
77
|
+
|
|
78
|
+
// If agent doesn't exist or user is owner, no confirmation needed
|
|
79
|
+
if (!result.exists || result.isOwner) {
|
|
80
|
+
return { needsForkConfirm: false, originalAgent: null };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// User is not owner, need fork confirmation
|
|
84
|
+
return {
|
|
85
|
+
needsForkConfirm: true,
|
|
86
|
+
originalAgent: result.originalAgent as OriginalAgentInfo,
|
|
87
|
+
};
|
|
88
|
+
} catch (error) {
|
|
89
|
+
console.error('[useMarketPublish] Failed to check ownership:', error);
|
|
90
|
+
// On error, proceed without confirmation
|
|
91
|
+
return { needsForkConfirm: false, originalAgent: null };
|
|
92
|
+
} finally {
|
|
93
|
+
setIsCheckingOwnership(false);
|
|
94
|
+
}
|
|
95
|
+
}, [meta?.marketIdentifier]);
|
|
96
|
+
|
|
49
97
|
const publish = useCallback(async () => {
|
|
50
98
|
// 防止重复发布:使用 ref 同步检查,避免闭包导致的竞态问题
|
|
51
99
|
if (isPublishingRef.current) {
|
|
52
100
|
return { success: false };
|
|
53
101
|
}
|
|
54
102
|
|
|
55
|
-
//
|
|
56
|
-
|
|
57
|
-
if (!isAuthenticated || (!enableMarketTrustedClient && !session?.accessToken)) {
|
|
103
|
+
// 检查认证状态 - tRPC 会自动处理 trustedClient
|
|
104
|
+
if (!isAuthenticated) {
|
|
58
105
|
return { success: false };
|
|
59
106
|
}
|
|
60
107
|
|
|
@@ -63,8 +110,6 @@ export const useMarketPublish = ({ action, onSuccess }: UseMarketPublishOptions)
|
|
|
63
110
|
? t('marketPublish.modal.loading.submit')
|
|
64
111
|
: t('marketPublish.modal.loading.upload');
|
|
65
112
|
|
|
66
|
-
let identifier = meta?.marketIdentifier;
|
|
67
|
-
|
|
68
113
|
const changelog = generateDefaultChangelog();
|
|
69
114
|
|
|
70
115
|
try {
|
|
@@ -72,61 +117,9 @@ export const useMarketPublish = ({ action, onSuccess }: UseMarketPublishOptions)
|
|
|
72
117
|
isPublishingRef.current = true;
|
|
73
118
|
setIsPublishing(true);
|
|
74
119
|
message.loading({ content: loadingMessage, key: messageKey });
|
|
75
|
-
// 只有在非 trustedClient 模式下才需要设置 accessToken
|
|
76
|
-
if (session?.accessToken) {
|
|
77
|
-
marketApiService.setAccessToken(session.accessToken);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// 判断是否需要创建新 agent
|
|
81
|
-
let needsCreateAgent = false;
|
|
82
|
-
|
|
83
|
-
if (!identifier) {
|
|
84
|
-
// 没有 marketIdentifier,需要创建新 agent
|
|
85
|
-
needsCreateAgent = true;
|
|
86
|
-
} else if (isSubmit) {
|
|
87
|
-
// 有 marketIdentifier 且是 submit 操作,需要检查是否是自己的 agent
|
|
88
|
-
const userInfo = getCurrentUserInfo?.() ?? session?.userInfo;
|
|
89
|
-
const accountId = userInfo?.accountId;
|
|
90
|
-
|
|
91
|
-
if (accountId) {
|
|
92
|
-
const isOwner = await checkOwnership({
|
|
93
|
-
accessToken: session?.accessToken,
|
|
94
|
-
accountId,
|
|
95
|
-
enableMarketTrustedClient,
|
|
96
|
-
marketIdentifier: identifier,
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
if (!isOwner) {
|
|
100
|
-
// 不是自己的 agent,需要创建新的
|
|
101
|
-
needsCreateAgent = true;
|
|
102
|
-
}
|
|
103
|
-
} else {
|
|
104
|
-
// 无法获取用户 ID,为安全起见创建新 agent
|
|
105
|
-
needsCreateAgent = true;
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
120
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
try {
|
|
113
|
-
await marketApiService.getAgentDetail(identifier);
|
|
114
|
-
} catch {
|
|
115
|
-
const createPayload: Record<string, unknown> = {
|
|
116
|
-
identifier,
|
|
117
|
-
name: meta?.title || '',
|
|
118
|
-
};
|
|
119
|
-
await marketApiService.createAgent(createPayload as any);
|
|
120
|
-
}
|
|
121
|
-
} else if (!identifier) {
|
|
122
|
-
message.error({
|
|
123
|
-
content: t('marketPublish.modal.messages.missingIdentifier'),
|
|
124
|
-
key: messageKey,
|
|
125
|
-
});
|
|
126
|
-
return { success: false };
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
const versionPayload = {
|
|
121
|
+
// 使用 tRPC publishOrCreate - 后端会自动处理 ownership 检查
|
|
122
|
+
const result = await lambdaClient.market.agent.publishOrCreate.mutate({
|
|
130
123
|
avatar: meta?.avatar,
|
|
131
124
|
changelog,
|
|
132
125
|
config: {
|
|
@@ -157,31 +150,16 @@ export const useMarketPublish = ({ action, onSuccess }: UseMarketPublishOptions)
|
|
|
157
150
|
},
|
|
158
151
|
description: meta?.description || '',
|
|
159
152
|
editorData: editorData,
|
|
160
|
-
identifier
|
|
153
|
+
// 传递现有的 identifier,后端会检查 ownership
|
|
154
|
+
identifier: meta?.marketIdentifier,
|
|
161
155
|
name: meta?.title || '',
|
|
162
156
|
tags: meta?.tags,
|
|
163
157
|
tokenUsage: tokenUsage,
|
|
164
|
-
};
|
|
165
|
-
|
|
166
|
-
try {
|
|
167
|
-
await marketApiService.createAgentVersion(versionPayload);
|
|
168
|
-
} catch (versionError) {
|
|
169
|
-
const errorMessage =
|
|
170
|
-
versionError instanceof Error
|
|
171
|
-
? versionError.message
|
|
172
|
-
: t('unknownError', { ns: 'common' });
|
|
173
|
-
message.error({
|
|
174
|
-
content: t('marketPublish.modal.messages.createVersionFailed', {
|
|
175
|
-
message: errorMessage,
|
|
176
|
-
}),
|
|
177
|
-
key: messageKey,
|
|
178
|
-
});
|
|
179
|
-
return { success: false };
|
|
180
|
-
}
|
|
158
|
+
});
|
|
181
159
|
|
|
182
|
-
//
|
|
183
|
-
if (
|
|
184
|
-
updateAgentMeta({ marketIdentifier: identifier });
|
|
160
|
+
// 如果是新创建的 agent,需要更新 meta
|
|
161
|
+
if (result.isNewAgent) {
|
|
162
|
+
updateAgentMeta({ marketIdentifier: result.identifier });
|
|
185
163
|
}
|
|
186
164
|
|
|
187
165
|
message.success({
|
|
@@ -189,8 +167,8 @@ export const useMarketPublish = ({ action, onSuccess }: UseMarketPublishOptions)
|
|
|
189
167
|
key: messageKey,
|
|
190
168
|
});
|
|
191
169
|
|
|
192
|
-
onSuccess?.(identifier
|
|
193
|
-
return { identifier, success: true };
|
|
170
|
+
onSuccess?.(result.identifier);
|
|
171
|
+
return { identifier: result.identifier, success: true };
|
|
194
172
|
} catch (error) {
|
|
195
173
|
const errorMessage =
|
|
196
174
|
error instanceof Error ? error.message : t('unknownError', { ns: 'common' });
|
|
@@ -211,8 +189,6 @@ export const useMarketPublish = ({ action, onSuccess }: UseMarketPublishOptions)
|
|
|
211
189
|
chatConfig?.historyCount,
|
|
212
190
|
chatConfig?.searchMode,
|
|
213
191
|
editorData,
|
|
214
|
-
enableMarketTrustedClient,
|
|
215
|
-
getCurrentUserInfo,
|
|
216
192
|
isAuthenticated,
|
|
217
193
|
isSubmit,
|
|
218
194
|
language,
|
|
@@ -225,8 +201,6 @@ export const useMarketPublish = ({ action, onSuccess }: UseMarketPublishOptions)
|
|
|
225
201
|
onSuccess,
|
|
226
202
|
plugins,
|
|
227
203
|
provider,
|
|
228
|
-
session?.accessToken,
|
|
229
|
-
session?.userInfo,
|
|
230
204
|
systemRole,
|
|
231
205
|
tokenUsage,
|
|
232
206
|
t,
|
|
@@ -234,6 +208,8 @@ export const useMarketPublish = ({ action, onSuccess }: UseMarketPublishOptions)
|
|
|
234
208
|
]);
|
|
235
209
|
|
|
236
210
|
return {
|
|
211
|
+
checkOwnership,
|
|
212
|
+
isCheckingOwnership,
|
|
237
213
|
isPublishing,
|
|
238
214
|
publish,
|
|
239
215
|
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type ChatMessageError } from '@lobechat/types';
|
|
2
|
+
|
|
3
|
+
export default function useRenderBusinessChatErrorMessageExtra(
|
|
4
|
+
// eslint-disable-next-line unused-imports/no-unused-vars, @typescript-eslint/no-unused-vars
|
|
5
|
+
error: ChatMessageError | null | undefined,
|
|
6
|
+
// eslint-disable-next-line unused-imports/no-unused-vars, @typescript-eslint/no-unused-vars
|
|
7
|
+
messageId: string,
|
|
8
|
+
) {
|
|
9
|
+
return null;
|
|
10
|
+
}
|
|
@@ -6,11 +6,12 @@ import { useTranslation } from 'react-i18next';
|
|
|
6
6
|
import { useCommandMenuContext } from './CommandMenuContext';
|
|
7
7
|
import { CommandItem } from './components';
|
|
8
8
|
import { useCommandMenu } from './useCommandMenu';
|
|
9
|
-
import { getContextCommands } from './utils/contextCommands';
|
|
9
|
+
import { CONTEXT_COMMANDS, getContextCommands } from './utils/contextCommands';
|
|
10
10
|
|
|
11
11
|
const ContextCommands = memo(() => {
|
|
12
12
|
const { t } = useTranslation('setting');
|
|
13
13
|
const { t: tAuth } = useTranslation('auth');
|
|
14
|
+
const { t: tSubscription } = useTranslation('subscription');
|
|
14
15
|
const { t: tCommon } = useTranslation('common');
|
|
15
16
|
const { handleNavigate } = useCommandMenu();
|
|
16
17
|
const { menuContext, pathname } = useCommandMenuContext();
|
|
@@ -23,48 +24,107 @@ const ContextCommands = memo(() => {
|
|
|
23
24
|
|
|
24
25
|
const commands = getContextCommands(menuContext, subPath);
|
|
25
26
|
|
|
26
|
-
|
|
27
|
+
// Get settings commands to show globally (when not in settings context)
|
|
28
|
+
const globalSettingsCommands = useMemo(() => {
|
|
29
|
+
if (menuContext === 'settings') return [];
|
|
30
|
+
return CONTEXT_COMMANDS.settings;
|
|
31
|
+
}, [menuContext]);
|
|
32
|
+
|
|
33
|
+
const hasCommands = commands.length > 0 || globalSettingsCommands.length > 0;
|
|
34
|
+
|
|
35
|
+
if (!hasCommands) return null;
|
|
27
36
|
|
|
28
37
|
// Get localized context name
|
|
29
38
|
const contextName = tCommon(`cmdk.context.${menuContext}`, { defaultValue: menuContext });
|
|
39
|
+
const settingsContextName = tCommon('cmdk.context.settings', { defaultValue: 'settings' });
|
|
30
40
|
|
|
31
41
|
return (
|
|
32
|
-
|
|
33
|
-
{commands
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
label =
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
42
|
+
<>
|
|
43
|
+
{/* Current context commands */}
|
|
44
|
+
{commands.length > 0 && (
|
|
45
|
+
<Command.Group>
|
|
46
|
+
{commands.map((cmd) => {
|
|
47
|
+
const Icon = cmd.icon;
|
|
48
|
+
// Get localized label using the correct namespace
|
|
49
|
+
let label = cmd.label;
|
|
50
|
+
if (cmd.labelKey) {
|
|
51
|
+
if (cmd.labelNamespace === 'auth') {
|
|
52
|
+
label = tAuth(cmd.labelKey, { defaultValue: cmd.label });
|
|
53
|
+
} else if (cmd.labelNamespace === 'subscription') {
|
|
54
|
+
label = tSubscription(cmd.labelKey, { defaultValue: cmd.label });
|
|
55
|
+
} else {
|
|
56
|
+
label = t(cmd.labelKey, { defaultValue: cmd.label });
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
const searchValue = `${contextName} ${label} ${cmd.keywords.join(' ')}`;
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
<CommandItem
|
|
63
|
+
icon={<Icon />}
|
|
64
|
+
key={cmd.path}
|
|
65
|
+
onSelect={() => handleNavigate(cmd.path)}
|
|
66
|
+
value={searchValue}
|
|
67
|
+
>
|
|
68
|
+
<span style={{ opacity: 0.5 }}>{contextName}</span>
|
|
69
|
+
<ChevronRight
|
|
70
|
+
size={14}
|
|
71
|
+
style={{
|
|
72
|
+
display: 'inline',
|
|
73
|
+
marginInline: '6px',
|
|
74
|
+
opacity: 0.5,
|
|
75
|
+
verticalAlign: 'middle',
|
|
76
|
+
}}
|
|
77
|
+
/>
|
|
78
|
+
{label}
|
|
79
|
+
</CommandItem>
|
|
80
|
+
);
|
|
81
|
+
})}
|
|
82
|
+
</Command.Group>
|
|
83
|
+
)}
|
|
84
|
+
|
|
85
|
+
{/* Global settings commands (searchable from any page) */}
|
|
86
|
+
{globalSettingsCommands.length > 0 && (
|
|
87
|
+
<Command.Group>
|
|
88
|
+
{globalSettingsCommands.map((cmd) => {
|
|
89
|
+
const Icon = cmd.icon;
|
|
90
|
+
// Get localized label using the correct namespace
|
|
91
|
+
let label = cmd.label;
|
|
92
|
+
if (cmd.labelKey) {
|
|
93
|
+
if (cmd.labelNamespace === 'auth') {
|
|
94
|
+
label = tAuth(cmd.labelKey, { defaultValue: cmd.label });
|
|
95
|
+
} else if (cmd.labelNamespace === 'subscription') {
|
|
96
|
+
label = tSubscription(cmd.labelKey, { defaultValue: cmd.label });
|
|
97
|
+
} else {
|
|
98
|
+
label = t(cmd.labelKey, { defaultValue: cmd.label });
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
const searchValue = `${settingsContextName} ${label} ${cmd.keywords.join(' ')}`;
|
|
45
102
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
103
|
+
return (
|
|
104
|
+
<CommandItem
|
|
105
|
+
icon={<Icon />}
|
|
106
|
+
key={cmd.path}
|
|
107
|
+
onSelect={() => handleNavigate(cmd.path)}
|
|
108
|
+
unpinned={true}
|
|
109
|
+
value={searchValue}
|
|
110
|
+
>
|
|
111
|
+
<span style={{ opacity: 0.5 }}>{settingsContextName}</span>
|
|
112
|
+
<ChevronRight
|
|
113
|
+
size={14}
|
|
114
|
+
style={{
|
|
115
|
+
display: 'inline',
|
|
116
|
+
marginInline: '6px',
|
|
117
|
+
opacity: 0.5,
|
|
118
|
+
verticalAlign: 'middle',
|
|
119
|
+
}}
|
|
120
|
+
/>
|
|
121
|
+
{label}
|
|
122
|
+
</CommandItem>
|
|
123
|
+
);
|
|
124
|
+
})}
|
|
125
|
+
</Command.Group>
|
|
126
|
+
)}
|
|
127
|
+
</>
|
|
68
128
|
);
|
|
69
129
|
});
|
|
70
130
|
|