@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.
Files changed (34) hide show
  1. package/.github/workflows/test.yml +18 -14
  2. package/CHANGELOG.md +50 -0
  3. package/changelog/v1.json +14 -0
  4. package/locales/en-US/common.json +3 -2
  5. package/locales/en-US/setting.json +4 -0
  6. package/locales/zh-CN/setting.json +4 -0
  7. package/package.json +2 -2
  8. package/packages/database/src/models/user.ts +33 -0
  9. package/packages/database/src/repositories/knowledge/index.ts +1 -1
  10. package/src/app/[variants]/(main)/chat/profile/features/Header/AgentPublishButton/ForkConfirmModal.tsx +67 -0
  11. package/src/app/[variants]/(main)/chat/profile/features/Header/AgentPublishButton/PublishButton.tsx +92 -105
  12. package/src/app/[variants]/(main)/chat/profile/features/Header/AgentPublishButton/index.tsx +13 -48
  13. package/src/app/[variants]/(main)/chat/profile/features/Header/AgentPublishButton/useMarketPublish.ts +69 -93
  14. package/src/business/client/hooks/useRenderBusinessChatErrorMessageExtra.tsx +10 -0
  15. package/src/features/CommandMenu/ContextCommands.tsx +97 -37
  16. package/src/features/CommandMenu/SearchResults.tsx +100 -276
  17. package/src/features/CommandMenu/components/CommandItem.tsx +1 -1
  18. package/src/features/CommandMenu/utils/contextCommands.ts +56 -1
  19. package/src/features/Conversation/Error/index.tsx +7 -1
  20. package/src/layout/AuthProvider/MarketAuth/MarketAuthProvider.tsx +11 -28
  21. package/src/layout/AuthProvider/MarketAuth/ProfileSetupModal.tsx +30 -25
  22. package/src/layout/AuthProvider/MarketAuth/useMarketUserProfile.ts +4 -9
  23. package/src/libs/redis/manager.ts +51 -15
  24. package/src/libs/trpc/lambda/middleware/index.ts +1 -0
  25. package/src/libs/trpc/lambda/middleware/marketSDK.ts +68 -0
  26. package/src/locales/default/common.ts +2 -2
  27. package/src/locales/default/setting.ts +5 -0
  28. package/src/server/routers/lambda/market/agent.ts +504 -0
  29. package/src/server/routers/lambda/market/index.ts +17 -0
  30. package/src/server/routers/lambda/market/oidc.ts +169 -0
  31. package/src/server/routers/lambda/market/social.ts +532 -0
  32. package/src/server/routers/lambda/market/user.ts +123 -0
  33. package/src/services/marketApi.ts +24 -84
  34. 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 { Bot, FileText, MessageCircle, MessageSquare, Plug, Puzzle, Sparkles } from 'lucide-react';
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
- return (
238
- <>
239
- {/* Show pages first in page context */}
240
- {hasResults && isPageContext && pageResults.length > 0 && (
241
- <Command.Group heading={t('cmdk.search.pages')} key="pages-page-context">
242
- {pageResults.map((result) => (
243
- <CommandItem
244
- description={result.description}
245
- icon={getIcon(result.type)}
246
- key={`page-page-context-${result.id}`}
247
- onSelect={() => handleNavigate(result)}
248
- title={result.title}
249
- trailingLabel={getTrailingLabel(result.type)}
250
- value={getItemValue(result)}
251
- variant="detailed"
252
- />
253
- ))}
254
- {renderSearchMore('page', pageResults.length)}
255
- </Command.Group>
256
- )}
257
-
258
- {/* Show other results in page context */}
259
- {hasResults && isPageContext && fileResults.length > 0 && (
260
- <Command.Group heading={t('cmdk.search.files')}>
261
- {fileResults.map((result) => (
262
- <CommandItem
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
- {hasResults && isPageContext && agentResults.length > 0 && (
278
- <Command.Group heading={t('cmdk.search.agents')}>
279
- {agentResults.map((result) => (
280
- <CommandItem
281
- description={getDescription(result)}
282
- icon={getIcon(result.type)}
283
- key={`agent-page-context-${result.id}`}
284
- onSelect={() => handleNavigate(result)}
285
- title={result.title}
286
- trailingLabel={getTrailingLabel(result.type)}
287
- value={getItemValue(result)}
288
- variant="detailed"
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
- {hasResults && isPageContext && topicResults.length > 0 && (
296
- <Command.Group heading={t('cmdk.search.topics')}>
297
- {topicResults.map((result) => (
298
- <CommandItem
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
- {hasResults && isPageContext && messageResults.length > 0 && (
314
- <Command.Group heading={t('cmdk.search.messages')}>
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
- {/* Show pages first in resource context */}
332
- {hasResults && isResourceContext && pageResults.length > 0 && (
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
- {/* Show files in resource context */}
351
- {hasResults && isResourceContext && fileResults.length > 0 && (
352
- <Command.Group heading={t('cmdk.search.files')}>
353
- {fileResults.map((result) => (
354
- <CommandItem
355
- description={result.type === 'file' ? result.fileType : undefined}
356
- icon={getIcon(result.type)}
357
- key={`file-${result.id}`}
358
- onSelect={() => handleNavigate(result)}
359
- title={result.title}
360
- trailingLabel={getTrailingLabel(result.type)}
361
- value={getItemValue(result)}
362
- variant="detailed"
363
- />
364
- ))}
365
- {renderSearchMore('file', fileResults.length)}
366
- </Command.Group>
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
- {hasResults && !isPageContext && !isResourceContext && messageResults.length > 0 && (
370
- <Command.Group heading={t('cmdk.search.messages')}>
371
- {messageResults.map((result) => (
372
- <CommandItem
373
- description={getSubtitle(result)}
374
- icon={getIcon(result.type)}
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
- {hasResults && !isPageContext && agentResults.length > 0 && (
388
- <Command.Group heading={t('cmdk.search.agents')}>
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
- {hasResults && !isPageContext && topicResults.length > 0 && (
406
- <Command.Group heading={t('cmdk.search.topics')}>
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
- {/* Show document pages in normal context (not in resource or page context) */}
424
- {hasResults && !isResourceContext && !isPageContext && pageResults.length > 0 && (
425
- <Command.Group heading={t('cmdk.search.pages')} key="pages-normal">
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
- {/* Show files in original position when NOT in resource or page context */}
443
- {hasResults && !isResourceContext && !isPageContext && fileResults.length > 0 && (
444
- <Command.Group heading={t('cmdk.search.files')}>
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
- {hasResults && mcpResults.length > 0 && (
462
- <Command.Group heading={t('cmdk.search.mcps')}>
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
- {hasResults && pluginResults.length > 0 && (
480
- <Command.Group heading={t('cmdk.search.plugins')}>
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
- {hasResults && assistantResults.length > 0 && (
498
- <Command.Group heading={t('cmdk.search.assistants')}>
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 as ChatMessageError;
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 { MARKET_ENDPOINTS, MARKET_OIDC_ENDPOINTS } from '@/services/_url';
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
- * 获取用户信息(从 OIDC userinfo endpoint)
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 response = await fetch(MARKET_OIDC_ENDPOINTS.userinfo, {
41
- body: JSON.stringify({ token: accessToken }),
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
- if (!response.ok) {
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 response = await fetch(MARKET_ENDPOINTS.getUserProfile(username));
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(serverConfigSelectors.enableMarketTrustedClient);
161
+ const enableMarketTrustedClient = useServerConfigStore(
162
+ serverConfigSelectors.enableMarketTrustedClient,
163
+ );
181
164
 
182
165
  // 初始化 OIDC 客户端(仅在客户端)
183
166
  useEffect(() => {