@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
|
@@ -9,7 +9,7 @@ import { memo, useCallback, useEffect, useState } from 'react';
|
|
|
9
9
|
import { useTranslation } from 'react-i18next';
|
|
10
10
|
|
|
11
11
|
import EmojiPicker from '@/components/EmojiPicker';
|
|
12
|
-
import {
|
|
12
|
+
import { lambdaClient } from '@/libs/trpc/client';
|
|
13
13
|
import { useFileStore } from '@/store/file';
|
|
14
14
|
import { useGlobalStore } from '@/store/global';
|
|
15
15
|
import { globalGeneralSelectors } from '@/store/global/selectors';
|
|
@@ -211,37 +211,42 @@ const ProfileSetupModal = memo<ProfileSetupModalProps>(
|
|
|
211
211
|
if (bannerUrl) meta.bannerUrl = bannerUrl;
|
|
212
212
|
if (Object.keys(socialLinks).length > 0) meta.socialLinks = socialLinks;
|
|
213
213
|
|
|
214
|
-
const
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
userName: values.userName,
|
|
220
|
-
}),
|
|
221
|
-
headers: {
|
|
222
|
-
'Authorization': `Bearer ${accessToken}`,
|
|
223
|
-
'Content-Type': 'application/json',
|
|
224
|
-
},
|
|
225
|
-
method: 'PUT',
|
|
214
|
+
const result = await lambdaClient.market.user.updateUserProfile.mutate({
|
|
215
|
+
avatarUrl: avatarUrl || undefined,
|
|
216
|
+
displayName: values.displayName,
|
|
217
|
+
meta: Object.keys(meta).length > 0 ? meta : undefined,
|
|
218
|
+
userName: values.userName,
|
|
226
219
|
});
|
|
227
220
|
|
|
228
|
-
if (!response.ok) {
|
|
229
|
-
const errorData = await response.json();
|
|
230
|
-
if (errorData.error === 'username_taken') {
|
|
231
|
-
message.error(t('profileSetup.errors.usernameTaken'));
|
|
232
|
-
return;
|
|
233
|
-
}
|
|
234
|
-
throw new Error(errorData.message || 'Update failed');
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
const data = await response.json();
|
|
238
221
|
message.success(t('profileSetup.success'));
|
|
239
|
-
|
|
222
|
+
// Cast result.user to MarketUserProfile with required fields
|
|
223
|
+
const userProfile: MarketUserProfile = {
|
|
224
|
+
avatarUrl: result.user?.avatarUrl || avatarUrl || null,
|
|
225
|
+
bannerUrl: bannerUrl || null,
|
|
226
|
+
createdAt: result.user?.createdAt || new Date().toISOString(),
|
|
227
|
+
description: values.description || null,
|
|
228
|
+
displayName: values.displayName || null,
|
|
229
|
+
id: result.user?.id || 0,
|
|
230
|
+
namespace: result.user?.namespace || '',
|
|
231
|
+
socialLinks: Object.keys(socialLinks).length > 0 ? socialLinks : null,
|
|
232
|
+
type: result.user?.type || null,
|
|
233
|
+
userName: values.userName || null,
|
|
234
|
+
};
|
|
235
|
+
onSuccess?.(userProfile);
|
|
240
236
|
onClose();
|
|
241
237
|
} catch (error) {
|
|
242
238
|
console.error('[ProfileSetupModal] Update failed:', error);
|
|
243
239
|
if (error instanceof Error && error.message !== 'Validation failed') {
|
|
244
|
-
|
|
240
|
+
// Check for username taken error (tRPC CONFLICT code)
|
|
241
|
+
const errorMessage = error.message || '';
|
|
242
|
+
if (
|
|
243
|
+
errorMessage.toLowerCase().includes('already taken') ||
|
|
244
|
+
errorMessage.includes('CONFLICT')
|
|
245
|
+
) {
|
|
246
|
+
message.error(t('profileSetup.errors.usernameTaken'));
|
|
247
|
+
} else {
|
|
248
|
+
message.error(t('profileSetup.errors.updateFailed'));
|
|
249
|
+
}
|
|
245
250
|
}
|
|
246
251
|
} finally {
|
|
247
252
|
setLoading(false);
|
|
@@ -1,20 +1,15 @@
|
|
|
1
1
|
import useSWR from 'swr';
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { lambdaClient } from '@/libs/trpc/client';
|
|
4
4
|
|
|
5
5
|
import { type MarketUserProfile } from './types';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
|
-
* Fetcher function for user profile
|
|
8
|
+
* Fetcher function for user profile using tRPC
|
|
9
9
|
*/
|
|
10
10
|
const fetchUserProfile = async (username: string): Promise<MarketUserProfile | null> => {
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
if (!response.ok) {
|
|
14
|
-
throw new Error(`Failed to fetch user profile: ${response.status}`);
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
return response.json();
|
|
11
|
+
const result = await lambdaClient.market.user.getUserByUsername.query({ username });
|
|
12
|
+
return result as MarketUserProfile;
|
|
18
13
|
};
|
|
19
14
|
|
|
20
15
|
/**
|
|
@@ -2,6 +2,33 @@ import { IoRedisRedisProvider } from './redis';
|
|
|
2
2
|
import { type BaseRedisProvider, type RedisConfig } from './types';
|
|
3
3
|
import { UpstashRedisProvider } from './upstash';
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* Create a Redis provider instance based on config
|
|
7
|
+
*
|
|
8
|
+
* @param config - Redis config
|
|
9
|
+
* @param prefix - Optional custom prefix to override config.prefix
|
|
10
|
+
* @returns Provider instance or null if disabled/unsupported
|
|
11
|
+
*/
|
|
12
|
+
const createProvider = (config: RedisConfig, prefix?: string): BaseRedisProvider | null => {
|
|
13
|
+
if (!config.enabled) return null;
|
|
14
|
+
|
|
15
|
+
const actualPrefix = prefix ?? config.prefix;
|
|
16
|
+
|
|
17
|
+
if (config.provider === 'redis') {
|
|
18
|
+
return new IoRedisRedisProvider({ ...config, prefix: actualPrefix });
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (config.provider === 'upstash') {
|
|
22
|
+
return new UpstashRedisProvider({
|
|
23
|
+
prefix: actualPrefix,
|
|
24
|
+
token: config.token,
|
|
25
|
+
url: config.url,
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return null;
|
|
30
|
+
};
|
|
31
|
+
|
|
5
32
|
class RedisManager {
|
|
6
33
|
private static instance: BaseRedisProvider | null = null;
|
|
7
34
|
// NOTICE: initPromise keeps concurrent initialize() calls sharing the same in-flight setup,
|
|
@@ -13,25 +40,13 @@ class RedisManager {
|
|
|
13
40
|
if (RedisManager.initPromise) return RedisManager.initPromise;
|
|
14
41
|
|
|
15
42
|
RedisManager.initPromise = (async () => {
|
|
16
|
-
|
|
43
|
+
const provider = createProvider(config);
|
|
44
|
+
|
|
45
|
+
if (!provider) {
|
|
17
46
|
RedisManager.instance = null;
|
|
18
47
|
return null;
|
|
19
48
|
}
|
|
20
49
|
|
|
21
|
-
let provider: BaseRedisProvider;
|
|
22
|
-
|
|
23
|
-
if (config.provider === 'redis') {
|
|
24
|
-
provider = new IoRedisRedisProvider(config);
|
|
25
|
-
} else if (config.provider === 'upstash') {
|
|
26
|
-
provider = new UpstashRedisProvider({
|
|
27
|
-
prefix: config.prefix,
|
|
28
|
-
token: config.token,
|
|
29
|
-
url: config.url,
|
|
30
|
-
});
|
|
31
|
-
} else {
|
|
32
|
-
throw new Error(`Unsupported redis provider: ${String((config as any).provider)}`);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
50
|
await provider.initialize();
|
|
36
51
|
RedisManager.instance = provider;
|
|
37
52
|
|
|
@@ -58,3 +73,24 @@ export const initializeRedis = (config: RedisConfig) => RedisManager.initialize(
|
|
|
58
73
|
export const resetRedisClient = () => RedisManager.reset();
|
|
59
74
|
export const isRedisEnabled = (config: RedisConfig) => config.enabled;
|
|
60
75
|
export { RedisManager };
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Create a Redis client with custom prefix
|
|
79
|
+
*
|
|
80
|
+
* Unlike initializeRedis, this creates an independent client
|
|
81
|
+
* that doesn't share the singleton instance.
|
|
82
|
+
*
|
|
83
|
+
* @param config - Redis config
|
|
84
|
+
* @param prefix - Custom prefix for all keys (e.g., 'aiGeneration')
|
|
85
|
+
* @returns Redis client or null if Redis is disabled
|
|
86
|
+
*/
|
|
87
|
+
export const createRedisWithPrefix = async (
|
|
88
|
+
config: RedisConfig,
|
|
89
|
+
prefix: string,
|
|
90
|
+
): Promise<BaseRedisProvider | null> => {
|
|
91
|
+
const provider = createProvider(config, prefix);
|
|
92
|
+
if (!provider) return null;
|
|
93
|
+
|
|
94
|
+
await provider.initialize();
|
|
95
|
+
return provider;
|
|
96
|
+
};
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { MarketSDK } from '@lobehub/market-sdk';
|
|
2
|
+
|
|
3
|
+
import { generateTrustedClientToken, type TrustedClientUserInfo } from '@/libs/trusted-client';
|
|
4
|
+
|
|
5
|
+
import { trpc } from '../init';
|
|
6
|
+
|
|
7
|
+
interface ContextWithMarketUserInfo {
|
|
8
|
+
marketAccessToken?: string;
|
|
9
|
+
marketUserInfo?: TrustedClientUserInfo;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Middleware that initializes MarketSDK with proper authentication.
|
|
14
|
+
* This requires marketUserInfo middleware to be applied first.
|
|
15
|
+
*
|
|
16
|
+
* Provides:
|
|
17
|
+
* - ctx.marketSDK: Initialized MarketSDK instance with trustedClientToken and optional accessToken
|
|
18
|
+
* - ctx.trustedClientToken: The generated trusted client token (if available)
|
|
19
|
+
*/
|
|
20
|
+
export const marketSDK = trpc.middleware(async (opts) => {
|
|
21
|
+
const ctx = opts.ctx as ContextWithMarketUserInfo;
|
|
22
|
+
|
|
23
|
+
// Generate trusted client token if user info is available
|
|
24
|
+
const trustedClientToken = ctx.marketUserInfo
|
|
25
|
+
? generateTrustedClientToken(ctx.marketUserInfo)
|
|
26
|
+
: undefined;
|
|
27
|
+
|
|
28
|
+
// Initialize MarketSDK with both authentication methods
|
|
29
|
+
const market = new MarketSDK({
|
|
30
|
+
accessToken: ctx.marketAccessToken,
|
|
31
|
+
baseURL: process.env.NEXT_PUBLIC_MARKET_BASE_URL,
|
|
32
|
+
trustedClientToken,
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
return opts.next({
|
|
36
|
+
ctx: {
|
|
37
|
+
marketSDK: market,
|
|
38
|
+
trustedClientToken,
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Middleware that requires authentication for Market API access.
|
|
45
|
+
* This middleware ensures that either accessToken or trustedClientToken is available.
|
|
46
|
+
* It should be used after marketUserInfo and marketSDK middlewares.
|
|
47
|
+
*
|
|
48
|
+
* Throws UNAUTHORIZED error if neither authentication method is available.
|
|
49
|
+
*/
|
|
50
|
+
export const requireMarketAuth = trpc.middleware(async (opts) => {
|
|
51
|
+
const ctx = opts.ctx as ContextWithMarketUserInfo & {
|
|
52
|
+
trustedClientToken?: string;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
// Check if any authentication is available
|
|
56
|
+
const hasAccessToken = !!ctx.marketAccessToken;
|
|
57
|
+
const hasTrustedToken = !!ctx.trustedClientToken;
|
|
58
|
+
|
|
59
|
+
if (!hasAccessToken && !hasTrustedToken) {
|
|
60
|
+
const { TRPCError } = await import('@trpc/server');
|
|
61
|
+
throw new TRPCError({
|
|
62
|
+
code: 'UNAUTHORIZED',
|
|
63
|
+
message: 'Authentication required. Please sign in.',
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return opts.next();
|
|
68
|
+
});
|
|
@@ -133,8 +133,8 @@ export default {
|
|
|
133
133
|
'cmdk.navigate': 'Navigate',
|
|
134
134
|
'cmdk.newAgent': 'Create New Agent',
|
|
135
135
|
'cmdk.newAgentTeam': 'Create New Group',
|
|
136
|
-
'cmdk.newLibrary': 'New Library',
|
|
137
|
-
'cmdk.newPage': 'New Page',
|
|
136
|
+
'cmdk.newLibrary': 'Create New Library',
|
|
137
|
+
'cmdk.newPage': 'Create New Page',
|
|
138
138
|
'cmdk.newTopic': 'New topic in current Agent',
|
|
139
139
|
'cmdk.noResults': 'No results found',
|
|
140
140
|
'cmdk.openSettings': 'Open Settings',
|
|
@@ -140,6 +140,11 @@ export default {
|
|
|
140
140
|
'llm.proxyUrl.title': 'API proxy URL',
|
|
141
141
|
'llm.waitingForMore': 'More models are <1>planned to be added</1>, stay tuned',
|
|
142
142
|
'llm.waitingForMoreLinkAriaLabel': 'Open the Provider request form',
|
|
143
|
+
'marketPublish.forkConfirm.by': 'by {{author}}',
|
|
144
|
+
'marketPublish.forkConfirm.confirm': 'Confirm Publish',
|
|
145
|
+
'marketPublish.forkConfirm.description':
|
|
146
|
+
'You are about to publish a derivative version based on an existing agent from the community. Your new agent will be created as a separate entry in the marketplace.',
|
|
147
|
+
'marketPublish.forkConfirm.title': 'Publish Derivative Agent',
|
|
143
148
|
'marketPublish.modal.changelog.extra':
|
|
144
149
|
'Describe the key changes and improvements in this version',
|
|
145
150
|
'marketPublish.modal.changelog.label': 'Changelog',
|