@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
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
import { Command } from 'cmdk';
|
|
2
2
|
import dayjs from 'dayjs';
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
Bot,
|
|
5
|
+
ChevronRight,
|
|
6
|
+
FileText,
|
|
7
|
+
MessageCircle,
|
|
8
|
+
MessageSquare,
|
|
9
|
+
Plug,
|
|
10
|
+
Puzzle,
|
|
11
|
+
Sparkles,
|
|
12
|
+
} from 'lucide-react';
|
|
4
13
|
import { markdownToTxt } from 'markdown-to-txt';
|
|
5
14
|
import { memo } from 'react';
|
|
6
15
|
import { useTranslation } from 'react-i18next';
|
|
@@ -8,7 +17,6 @@ import { useNavigate } from 'react-router-dom';
|
|
|
8
17
|
|
|
9
18
|
import type { SearchResult } from '@/database/repositories/search';
|
|
10
19
|
|
|
11
|
-
import { useCommandMenuContext } from './CommandMenuContext';
|
|
12
20
|
import { CommandItem } from './components';
|
|
13
21
|
import { styles } from './styles';
|
|
14
22
|
import type { ValidSearchType } from './utils/queryParser';
|
|
@@ -29,7 +37,6 @@ const SearchResults = memo<SearchResultsProps>(
|
|
|
29
37
|
({ isLoading, onClose, onSetTypeFilter, results, searchQuery, typeFilter }) => {
|
|
30
38
|
const { t } = useTranslation('common');
|
|
31
39
|
const navigate = useNavigate();
|
|
32
|
-
const { menuContext } = useCommandMenuContext();
|
|
33
40
|
|
|
34
41
|
const handleNavigate = (result: SearchResult) => {
|
|
35
42
|
switch (result.type) {
|
|
@@ -146,15 +153,6 @@ const SearchResults = memo<SearchResultsProps>(
|
|
|
146
153
|
}
|
|
147
154
|
};
|
|
148
155
|
|
|
149
|
-
// Get trailing label for search results (shows "Market" for marketplace items)
|
|
150
|
-
const getTrailingLabel = (type: SearchResult['type']) => {
|
|
151
|
-
// Marketplace items: MCP, plugins, assistants
|
|
152
|
-
if (type === 'mcp' || type === 'plugin' || type === 'communityAgent') {
|
|
153
|
-
return t('cmdk.search.market');
|
|
154
|
-
}
|
|
155
|
-
return getTypeLabel(type);
|
|
156
|
-
};
|
|
157
|
-
|
|
158
156
|
// eslint-disable-next-line unicorn/consistent-function-scoping
|
|
159
157
|
const getItemValue = (result: SearchResult) => {
|
|
160
158
|
const meta = [result.title, result.description].filter(Boolean).join(' ');
|
|
@@ -193,26 +191,6 @@ const SearchResults = memo<SearchResultsProps>(
|
|
|
193
191
|
onSetTypeFilter(type);
|
|
194
192
|
};
|
|
195
193
|
|
|
196
|
-
// Helper to render "Search More" button
|
|
197
|
-
const renderSearchMore = (type: ValidSearchType, count: number) => {
|
|
198
|
-
// Don't show if already filtering by this type
|
|
199
|
-
if (typeFilter) return null;
|
|
200
|
-
|
|
201
|
-
// Show if there are results (might have more)
|
|
202
|
-
if (count === 0) return null;
|
|
203
|
-
|
|
204
|
-
return (
|
|
205
|
-
<CommandItem
|
|
206
|
-
forceMount
|
|
207
|
-
icon={getIcon(type)}
|
|
208
|
-
onSelect={() => handleSearchMore(type)}
|
|
209
|
-
title={t('cmdk.search.searchMore', { type: getTypeLabel(type) })}
|
|
210
|
-
value={`action-show-more-results-for-type-${type}`}
|
|
211
|
-
variant="detailed"
|
|
212
|
-
/>
|
|
213
|
-
);
|
|
214
|
-
};
|
|
215
|
-
|
|
216
194
|
const hasResults = results.length > 0;
|
|
217
195
|
|
|
218
196
|
// Group results by type
|
|
@@ -225,289 +203,135 @@ const SearchResults = memo<SearchResultsProps>(
|
|
|
225
203
|
const pluginResults = results.filter((r) => r.type === 'plugin');
|
|
226
204
|
const assistantResults = results.filter((r) => r.type === 'communityAgent');
|
|
227
205
|
|
|
228
|
-
// Detect context types
|
|
229
|
-
const isResourceContext = menuContext === 'resource';
|
|
230
|
-
const isPageContext = menuContext === 'page';
|
|
231
|
-
|
|
232
206
|
// Don't render anything if no results and not loading
|
|
233
207
|
if (!hasResults && !isLoading) {
|
|
234
208
|
return null;
|
|
235
209
|
}
|
|
236
210
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
description={result.type === 'file' ? result.fileType : undefined}
|
|
264
|
-
icon={getIcon(result.type)}
|
|
265
|
-
key={`file-page-context-${result.id}`}
|
|
266
|
-
onSelect={() => handleNavigate(result)}
|
|
267
|
-
title={result.title}
|
|
268
|
-
trailingLabel={getTrailingLabel(result.type)}
|
|
269
|
-
value={getItemValue(result)}
|
|
270
|
-
variant="detailed"
|
|
271
|
-
/>
|
|
272
|
-
))}
|
|
273
|
-
{renderSearchMore('file', fileResults.length)}
|
|
274
|
-
</Command.Group>
|
|
275
|
-
)}
|
|
211
|
+
// Render a single result item with type prefix (like "Message > content")
|
|
212
|
+
const renderResultItem = (result: SearchResult) => {
|
|
213
|
+
const typeLabel = getTypeLabel(result.type);
|
|
214
|
+
const subtitle = getSubtitle(result);
|
|
215
|
+
|
|
216
|
+
// Hide type prefix when filtering by specific type
|
|
217
|
+
const showTypePrefix = !typeFilter;
|
|
218
|
+
|
|
219
|
+
// Create title with or without type prefix
|
|
220
|
+
const titleWithPrefix = showTypePrefix ? (
|
|
221
|
+
<>
|
|
222
|
+
<span style={{ opacity: 0.5 }}>{typeLabel}</span>
|
|
223
|
+
<ChevronRight
|
|
224
|
+
size={14}
|
|
225
|
+
style={{
|
|
226
|
+
display: 'inline',
|
|
227
|
+
marginInline: '6px',
|
|
228
|
+
opacity: 0.5,
|
|
229
|
+
verticalAlign: 'middle',
|
|
230
|
+
}}
|
|
231
|
+
/>
|
|
232
|
+
{result.title}
|
|
233
|
+
</>
|
|
234
|
+
) : (
|
|
235
|
+
result.title
|
|
236
|
+
);
|
|
276
237
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
/>
|
|
290
|
-
))}
|
|
291
|
-
{renderSearchMore('agent', agentResults.length)}
|
|
292
|
-
</Command.Group>
|
|
293
|
-
)}
|
|
238
|
+
return (
|
|
239
|
+
<CommandItem
|
|
240
|
+
description={subtitle}
|
|
241
|
+
icon={getIcon(result.type)}
|
|
242
|
+
key={result.id}
|
|
243
|
+
onSelect={() => handleNavigate(result)}
|
|
244
|
+
title={titleWithPrefix}
|
|
245
|
+
value={getItemValue(result)}
|
|
246
|
+
variant="detailed"
|
|
247
|
+
/>
|
|
248
|
+
);
|
|
249
|
+
};
|
|
294
250
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
description={getSubtitle(result)}
|
|
300
|
-
icon={getIcon(result.type)}
|
|
301
|
-
key={`topic-page-context-${result.id}`}
|
|
302
|
-
onSelect={() => handleNavigate(result)}
|
|
303
|
-
title={result.title}
|
|
304
|
-
trailingLabel={getTrailingLabel(result.type)}
|
|
305
|
-
value={getItemValue(result)}
|
|
306
|
-
variant="detailed"
|
|
307
|
-
/>
|
|
308
|
-
))}
|
|
309
|
-
{renderSearchMore('topic', topicResults.length)}
|
|
310
|
-
</Command.Group>
|
|
311
|
-
)}
|
|
251
|
+
// Helper to render "Search More" button
|
|
252
|
+
const renderSearchMore = (type: ValidSearchType, count: number) => {
|
|
253
|
+
// Don't show if already filtering by this type
|
|
254
|
+
if (typeFilter) return null;
|
|
312
255
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
{messageResults.map((result) => (
|
|
316
|
-
<CommandItem
|
|
317
|
-
description={getSubtitle(result)}
|
|
318
|
-
icon={getIcon(result.type)}
|
|
319
|
-
key={`message-page-context-${result.id}`}
|
|
320
|
-
onSelect={() => handleNavigate(result)}
|
|
321
|
-
title={result.title}
|
|
322
|
-
trailingLabel={getTrailingLabel(result.type)}
|
|
323
|
-
value={getItemValue(result)}
|
|
324
|
-
variant="detailed"
|
|
325
|
-
/>
|
|
326
|
-
))}
|
|
327
|
-
{renderSearchMore('message', messageResults.length)}
|
|
328
|
-
</Command.Group>
|
|
329
|
-
)}
|
|
256
|
+
// Show if there are results (might have more)
|
|
257
|
+
if (count === 0) return null;
|
|
330
258
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
<Command.Group heading={t('cmdk.search.pages')} key="pages-resource">
|
|
334
|
-
{pageResults.map((result) => (
|
|
335
|
-
<CommandItem
|
|
336
|
-
description={result.description}
|
|
337
|
-
icon={getIcon(result.type)}
|
|
338
|
-
key={`page-resource-${result.id}`}
|
|
339
|
-
onSelect={() => handleNavigate(result)}
|
|
340
|
-
title={result.title}
|
|
341
|
-
trailingLabel={getTrailingLabel(result.type)}
|
|
342
|
-
value={getItemValue(result)}
|
|
343
|
-
variant="detailed"
|
|
344
|
-
/>
|
|
345
|
-
))}
|
|
346
|
-
{renderSearchMore('page', pageResults.length)}
|
|
347
|
-
</Command.Group>
|
|
348
|
-
)}
|
|
259
|
+
const typeLabel = getTypeLabel(type);
|
|
260
|
+
const titleText = `${t('cmdk.search.searchMore', { type: typeLabel })} with "${searchQuery}"`;
|
|
349
261
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
)}
|
|
262
|
+
return (
|
|
263
|
+
<Command.Item
|
|
264
|
+
forceMount
|
|
265
|
+
key={`search-more-${type}`}
|
|
266
|
+
keywords={[`zzz-action-${type}`]}
|
|
267
|
+
onSelect={() => handleSearchMore(type)}
|
|
268
|
+
value={`zzz-action-${type}-search-more`}
|
|
269
|
+
>
|
|
270
|
+
<div className={styles.itemContent}>
|
|
271
|
+
<div className={styles.itemIcon}>{getIcon(type)}</div>
|
|
272
|
+
<div className={styles.itemDetails}>
|
|
273
|
+
<div className={styles.itemTitle}>{titleText}</div>
|
|
274
|
+
</div>
|
|
275
|
+
</div>
|
|
276
|
+
</Command.Item>
|
|
277
|
+
);
|
|
278
|
+
};
|
|
368
279
|
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
key={`message-${result.id}`}
|
|
376
|
-
onSelect={() => handleNavigate(result)}
|
|
377
|
-
title={result.title}
|
|
378
|
-
trailingLabel={getTrailingLabel(result.type)}
|
|
379
|
-
value={getItemValue(result)}
|
|
380
|
-
variant="detailed"
|
|
381
|
-
/>
|
|
382
|
-
))}
|
|
280
|
+
return (
|
|
281
|
+
<>
|
|
282
|
+
{/* Render search results grouped by type without headers */}
|
|
283
|
+
{messageResults.length > 0 && (
|
|
284
|
+
<Command.Group>
|
|
285
|
+
{messageResults.map((result) => renderResultItem(result))}
|
|
383
286
|
{renderSearchMore('message', messageResults.length)}
|
|
384
287
|
</Command.Group>
|
|
385
288
|
)}
|
|
386
289
|
|
|
387
|
-
{
|
|
388
|
-
<Command.Group
|
|
389
|
-
{agentResults.map((result) => (
|
|
390
|
-
<CommandItem
|
|
391
|
-
description={getDescription(result)}
|
|
392
|
-
icon={getIcon(result.type)}
|
|
393
|
-
key={`agent-${result.id}`}
|
|
394
|
-
onSelect={() => handleNavigate(result)}
|
|
395
|
-
title={result.title}
|
|
396
|
-
trailingLabel={getTrailingLabel(result.type)}
|
|
397
|
-
value={getItemValue(result)}
|
|
398
|
-
variant="detailed"
|
|
399
|
-
/>
|
|
400
|
-
))}
|
|
290
|
+
{agentResults.length > 0 && (
|
|
291
|
+
<Command.Group>
|
|
292
|
+
{agentResults.map((result) => renderResultItem(result))}
|
|
401
293
|
{renderSearchMore('agent', agentResults.length)}
|
|
402
294
|
</Command.Group>
|
|
403
295
|
)}
|
|
404
296
|
|
|
405
|
-
{
|
|
406
|
-
<Command.Group
|
|
407
|
-
{topicResults.map((result) => (
|
|
408
|
-
<CommandItem
|
|
409
|
-
description={getSubtitle(result)}
|
|
410
|
-
icon={getIcon(result.type)}
|
|
411
|
-
key={`topic-${result.id}`}
|
|
412
|
-
onSelect={() => handleNavigate(result)}
|
|
413
|
-
title={result.title}
|
|
414
|
-
trailingLabel={getTrailingLabel(result.type)}
|
|
415
|
-
value={getItemValue(result)}
|
|
416
|
-
variant="detailed"
|
|
417
|
-
/>
|
|
418
|
-
))}
|
|
297
|
+
{topicResults.length > 0 && (
|
|
298
|
+
<Command.Group>
|
|
299
|
+
{topicResults.map((result) => renderResultItem(result))}
|
|
419
300
|
{renderSearchMore('topic', topicResults.length)}
|
|
420
301
|
</Command.Group>
|
|
421
302
|
)}
|
|
422
303
|
|
|
423
|
-
{
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
{pageResults.map((result) => (
|
|
427
|
-
<CommandItem
|
|
428
|
-
description={result.description}
|
|
429
|
-
icon={getIcon(result.type)}
|
|
430
|
-
key={`page-normal-${result.id}`}
|
|
431
|
-
onSelect={() => handleNavigate(result)}
|
|
432
|
-
title={result.title}
|
|
433
|
-
trailingLabel={getTrailingLabel(result.type)}
|
|
434
|
-
value={getItemValue(result)}
|
|
435
|
-
variant="detailed"
|
|
436
|
-
/>
|
|
437
|
-
))}
|
|
304
|
+
{pageResults.length > 0 && (
|
|
305
|
+
<Command.Group>
|
|
306
|
+
{pageResults.map((result) => renderResultItem(result))}
|
|
438
307
|
{renderSearchMore('page', pageResults.length)}
|
|
439
308
|
</Command.Group>
|
|
440
309
|
)}
|
|
441
310
|
|
|
442
|
-
{
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
{fileResults.map((result) => (
|
|
446
|
-
<CommandItem
|
|
447
|
-
description={result.type === 'file' ? result.fileType : undefined}
|
|
448
|
-
icon={getIcon(result.type)}
|
|
449
|
-
key={`file-${result.id}`}
|
|
450
|
-
onSelect={() => handleNavigate(result)}
|
|
451
|
-
title={result.title}
|
|
452
|
-
trailingLabel={getTrailingLabel(result.type)}
|
|
453
|
-
value={getItemValue(result)}
|
|
454
|
-
variant="detailed"
|
|
455
|
-
/>
|
|
456
|
-
))}
|
|
311
|
+
{fileResults.length > 0 && (
|
|
312
|
+
<Command.Group>
|
|
313
|
+
{fileResults.map((result) => renderResultItem(result))}
|
|
457
314
|
{renderSearchMore('file', fileResults.length)}
|
|
458
315
|
</Command.Group>
|
|
459
316
|
)}
|
|
460
317
|
|
|
461
|
-
{
|
|
462
|
-
<Command.Group
|
|
463
|
-
{mcpResults.map((result) => (
|
|
464
|
-
<CommandItem
|
|
465
|
-
description={getDescription(result)}
|
|
466
|
-
icon={getIcon(result.type)}
|
|
467
|
-
key={`mcp-${result.id}`}
|
|
468
|
-
onSelect={() => handleNavigate(result)}
|
|
469
|
-
title={result.title}
|
|
470
|
-
trailingLabel={getTrailingLabel(result.type)}
|
|
471
|
-
value={getItemValue(result)}
|
|
472
|
-
variant="detailed"
|
|
473
|
-
/>
|
|
474
|
-
))}
|
|
318
|
+
{mcpResults.length > 0 && (
|
|
319
|
+
<Command.Group>
|
|
320
|
+
{mcpResults.map((result) => renderResultItem(result))}
|
|
475
321
|
{renderSearchMore('mcp', mcpResults.length)}
|
|
476
322
|
</Command.Group>
|
|
477
323
|
)}
|
|
478
324
|
|
|
479
|
-
{
|
|
480
|
-
<Command.Group
|
|
481
|
-
{pluginResults.map((result) => (
|
|
482
|
-
<CommandItem
|
|
483
|
-
description={getDescription(result)}
|
|
484
|
-
icon={getIcon(result.type)}
|
|
485
|
-
key={`plugin-${result.id}`}
|
|
486
|
-
onSelect={() => handleNavigate(result)}
|
|
487
|
-
title={result.title}
|
|
488
|
-
trailingLabel={getTrailingLabel(result.type)}
|
|
489
|
-
value={getItemValue(result)}
|
|
490
|
-
variant="detailed"
|
|
491
|
-
/>
|
|
492
|
-
))}
|
|
325
|
+
{pluginResults.length > 0 && (
|
|
326
|
+
<Command.Group>
|
|
327
|
+
{pluginResults.map((result) => renderResultItem(result))}
|
|
493
328
|
{renderSearchMore('plugin', pluginResults.length)}
|
|
494
329
|
</Command.Group>
|
|
495
330
|
)}
|
|
496
331
|
|
|
497
|
-
{
|
|
498
|
-
<Command.Group
|
|
499
|
-
{assistantResults.map((result) => (
|
|
500
|
-
<CommandItem
|
|
501
|
-
description={getDescription(result)}
|
|
502
|
-
icon={getIcon(result.type)}
|
|
503
|
-
key={`assistant-${result.id}`}
|
|
504
|
-
onSelect={() => handleNavigate(result)}
|
|
505
|
-
title={result.title}
|
|
506
|
-
trailingLabel={getTrailingLabel(result.type)}
|
|
507
|
-
value={getItemValue(result)}
|
|
508
|
-
variant="detailed"
|
|
509
|
-
/>
|
|
510
|
-
))}
|
|
332
|
+
{assistantResults.length > 0 && (
|
|
333
|
+
<Command.Group>
|
|
334
|
+
{assistantResults.map((result) => renderResultItem(result))}
|
|
511
335
|
{renderSearchMore('communityAgent', assistantResults.length)}
|
|
512
336
|
</Command.Group>
|
|
513
337
|
)}
|
|
@@ -5,7 +5,7 @@ import { cloneElement, isValidElement, memo } from 'react';
|
|
|
5
5
|
import { useCommandMenuContext } from '../CommandMenuContext';
|
|
6
6
|
import { styles } from '../styles';
|
|
7
7
|
|
|
8
|
-
type BaseCommandItemProps = Omit<ComponentProps<typeof Command.Item>, 'children'> & {
|
|
8
|
+
type BaseCommandItemProps = Omit<ComponentProps<typeof Command.Item>, 'children' | 'title'> & {
|
|
9
9
|
/**
|
|
10
10
|
* Hide the item from default view but keep it searchable
|
|
11
11
|
* When true, the item won't show in the default list but will appear in search results
|
|
@@ -1,13 +1,19 @@
|
|
|
1
|
+
import { ENABLE_BUSINESS_FEATURES } from '@lobechat/business-const';
|
|
1
2
|
import type { LucideIcon } from 'lucide-react';
|
|
2
3
|
import {
|
|
3
4
|
Brain,
|
|
4
5
|
ChartColumnBigIcon,
|
|
6
|
+
Coins,
|
|
7
|
+
CreditCard,
|
|
5
8
|
EthernetPort,
|
|
9
|
+
Gift,
|
|
6
10
|
Image as ImageIcon,
|
|
7
11
|
Info,
|
|
8
12
|
KeyIcon,
|
|
9
13
|
KeyboardIcon,
|
|
14
|
+
Map,
|
|
10
15
|
Palette as PaletteIcon,
|
|
16
|
+
PieChart,
|
|
11
17
|
UserCircle,
|
|
12
18
|
} from 'lucide-react';
|
|
13
19
|
|
|
@@ -18,7 +24,7 @@ export interface ContextCommand {
|
|
|
18
24
|
keywords: string[];
|
|
19
25
|
label: string;
|
|
20
26
|
labelKey?: string; // i18n key for the label
|
|
21
|
-
labelNamespace?: 'setting' | 'auth'; // i18n namespace for the label
|
|
27
|
+
labelNamespace?: 'setting' | 'auth' | 'subscription'; // i18n namespace for the label
|
|
22
28
|
path: string;
|
|
23
29
|
subPath: string;
|
|
24
30
|
}
|
|
@@ -114,6 +120,55 @@ export const CONTEXT_COMMANDS: Record<ContextType, ContextCommand[]> = {
|
|
|
114
120
|
path: '/settings/about',
|
|
115
121
|
subPath: 'about',
|
|
116
122
|
},
|
|
123
|
+
...(ENABLE_BUSINESS_FEATURES
|
|
124
|
+
? [
|
|
125
|
+
{
|
|
126
|
+
icon: Map,
|
|
127
|
+
keywords: ['subscription', 'plan', 'upgrade', 'pricing'],
|
|
128
|
+
label: 'Subscription Plans',
|
|
129
|
+
labelKey: 'tab.plans',
|
|
130
|
+
labelNamespace: 'subscription' as const,
|
|
131
|
+
path: '/settings/plans',
|
|
132
|
+
subPath: 'plans',
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
icon: Coins,
|
|
136
|
+
keywords: ['funds', 'balance', 'credit', 'money'],
|
|
137
|
+
label: 'Funds',
|
|
138
|
+
labelKey: 'tab.funds',
|
|
139
|
+
labelNamespace: 'subscription' as const,
|
|
140
|
+
path: '/settings/funds',
|
|
141
|
+
subPath: 'funds',
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
icon: PieChart,
|
|
145
|
+
keywords: ['usage', 'statistics', 'consumption', 'quota'],
|
|
146
|
+
label: 'Usage',
|
|
147
|
+
labelKey: 'tab.usage',
|
|
148
|
+
labelNamespace: 'subscription' as const,
|
|
149
|
+
path: '/settings/usage',
|
|
150
|
+
subPath: 'usage',
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
icon: CreditCard,
|
|
154
|
+
keywords: ['billing', 'payment', 'invoice', 'transaction'],
|
|
155
|
+
label: 'Billing',
|
|
156
|
+
labelKey: 'tab.billing',
|
|
157
|
+
labelNamespace: 'subscription' as const,
|
|
158
|
+
path: '/settings/billing',
|
|
159
|
+
subPath: 'billing',
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
icon: Gift,
|
|
163
|
+
keywords: ['referral', 'rewards', 'invite', 'bonus'],
|
|
164
|
+
label: 'Referral Rewards',
|
|
165
|
+
labelKey: 'tab.referral',
|
|
166
|
+
labelNamespace: 'subscription' as const,
|
|
167
|
+
path: '/settings/referral',
|
|
168
|
+
subPath: 'referral',
|
|
169
|
+
},
|
|
170
|
+
]
|
|
171
|
+
: []),
|
|
117
172
|
],
|
|
118
173
|
};
|
|
119
174
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ENABLE_BUSINESS_FEATURES } from '@lobechat/business-const';
|
|
1
2
|
import { AgentRuntimeErrorType, type ILobeAgentRuntimeErrorType } from '@lobechat/model-runtime';
|
|
2
3
|
import { ChatErrorType, type ChatMessageError, type ErrorType } from '@lobechat/types';
|
|
3
4
|
import { type IPluginErrorType } from '@lobehub/chat-plugin-sdk';
|
|
@@ -6,6 +7,7 @@ import dynamic from 'next/dynamic';
|
|
|
6
7
|
import { memo, useMemo } from 'react';
|
|
7
8
|
import { useTranslation } from 'react-i18next';
|
|
8
9
|
|
|
10
|
+
import useRenderBusinessChatErrorMessageExtra from '@/business/client/hooks/useRenderBusinessChatErrorMessageExtra';
|
|
9
11
|
import ErrorContent from '@/features/Conversation/ChatItem/components/ErrorContent';
|
|
10
12
|
import { useProviderName } from '@/hooks/useProviderName';
|
|
11
13
|
|
|
@@ -103,7 +105,11 @@ interface ErrorExtraProps {
|
|
|
103
105
|
}
|
|
104
106
|
|
|
105
107
|
const ErrorMessageExtra = memo<ErrorExtraProps>(({ error: alertError, data }) => {
|
|
106
|
-
const error = data.error
|
|
108
|
+
const error = data.error;
|
|
109
|
+
const businessChatErrorMessageExtra = useRenderBusinessChatErrorMessageExtra(error, data.id);
|
|
110
|
+
if (ENABLE_BUSINESS_FEATURES && businessChatErrorMessageExtra)
|
|
111
|
+
return businessChatErrorMessageExtra;
|
|
112
|
+
|
|
107
113
|
if (!error?.type) return;
|
|
108
114
|
|
|
109
115
|
switch (error.type) {
|
|
@@ -5,7 +5,8 @@ import { type ReactNode, createContext, useCallback, useContext, useEffect, useS
|
|
|
5
5
|
import { useTranslation } from 'react-i18next';
|
|
6
6
|
import { mutate as globalMutate } from 'swr';
|
|
7
7
|
|
|
8
|
-
import {
|
|
8
|
+
import { lambdaClient } from '@/libs/trpc/client';
|
|
9
|
+
import { MARKET_OIDC_ENDPOINTS } from '@/services/_url';
|
|
9
10
|
import { useServerConfigStore } from '@/store/serverConfig';
|
|
10
11
|
import { serverConfigSelectors } from '@/store/serverConfig/selectors';
|
|
11
12
|
import { useUserStore } from '@/store/user';
|
|
@@ -32,31 +33,16 @@ interface MarketAuthProviderProps {
|
|
|
32
33
|
}
|
|
33
34
|
|
|
34
35
|
/**
|
|
35
|
-
*
|
|
36
|
+
* 获取用户信息(通过 tRPC OIDC endpoint)
|
|
36
37
|
* @param accessToken - 可选的 access token,如果不传则后端会尝试使用 trustedClientToken
|
|
37
38
|
*/
|
|
38
39
|
const fetchUserInfo = async (accessToken?: string): Promise<MarketUserInfo | null> => {
|
|
39
40
|
try {
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
headers: {
|
|
43
|
-
'Content-Type': 'application/json',
|
|
44
|
-
},
|
|
45
|
-
method: 'POST',
|
|
41
|
+
const userInfo = await lambdaClient.market.oidc.getUserInfo.mutate({
|
|
42
|
+
token: accessToken,
|
|
46
43
|
});
|
|
47
44
|
|
|
48
|
-
|
|
49
|
-
console.error(
|
|
50
|
-
'[MarketAuth] Failed to fetch user info:',
|
|
51
|
-
response.status,
|
|
52
|
-
response.statusText,
|
|
53
|
-
);
|
|
54
|
-
return null;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
const userInfo = (await response.json()) as MarketUserInfo;
|
|
58
|
-
|
|
59
|
-
return userInfo;
|
|
45
|
+
return userInfo as MarketUserInfo;
|
|
60
46
|
} catch (error) {
|
|
61
47
|
console.error('[MarketAuth] Error fetching user info:', error);
|
|
62
48
|
return null;
|
|
@@ -136,16 +122,11 @@ const refreshToken = async (): Promise<boolean> => {
|
|
|
136
122
|
*/
|
|
137
123
|
const checkNeedsProfileSetup = async (username: string): Promise<boolean> => {
|
|
138
124
|
try {
|
|
139
|
-
const
|
|
140
|
-
if (!response.ok) {
|
|
141
|
-
// User profile not found, needs setup
|
|
142
|
-
return true;
|
|
143
|
-
}
|
|
144
|
-
const profile = (await response.json()) as MarketUserProfile;
|
|
125
|
+
const profile = await lambdaClient.market.user.getUserByUsername.query({ username });
|
|
145
126
|
// If userName is not set, user needs to complete profile setup
|
|
146
127
|
return !profile.userName;
|
|
147
128
|
} catch {
|
|
148
|
-
// Error fetching profile, assume needs setup
|
|
129
|
+
// Error fetching profile (e.g., NOT_FOUND), assume needs setup
|
|
149
130
|
return true;
|
|
150
131
|
}
|
|
151
132
|
};
|
|
@@ -177,7 +158,9 @@ export const MarketAuthProvider = ({ children, isDesktop }: MarketAuthProviderPr
|
|
|
177
158
|
const isUserStateInit = useUserStore((s) => s.isUserStateInit);
|
|
178
159
|
|
|
179
160
|
// 检查是否启用了 Market Trusted Client 认证
|
|
180
|
-
const enableMarketTrustedClient = useServerConfigStore(
|
|
161
|
+
const enableMarketTrustedClient = useServerConfigStore(
|
|
162
|
+
serverConfigSelectors.enableMarketTrustedClient,
|
|
163
|
+
);
|
|
181
164
|
|
|
182
165
|
// 初始化 OIDC 客户端(仅在客户端)
|
|
183
166
|
useEffect(() => {
|