@lobehub/lobehub 2.0.0-next.46 → 2.0.0-next.48

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 (254) hide show
  1. package/.env.example +11 -0
  2. package/CHANGELOG.md +42 -0
  3. package/apps/desktop/src/main/controllers/AuthCtr.ts +27 -2
  4. package/apps/desktop/src/main/core/infrastructure/ProtocolManager.ts +9 -4
  5. package/changelog/v1.json +14 -0
  6. package/docs/development/database-schema.dbml +2 -0
  7. package/docs/self-hosting/environment-variables/basic.mdx +49 -3
  8. package/docs/self-hosting/environment-variables/basic.zh-CN.mdx +49 -4
  9. package/locales/ar/discover.json +45 -0
  10. package/locales/ar/marketAuth.json +42 -0
  11. package/locales/ar/setting.json +94 -1
  12. package/locales/bg-BG/discover.json +45 -0
  13. package/locales/bg-BG/marketAuth.json +42 -0
  14. package/locales/bg-BG/setting.json +94 -1
  15. package/locales/de-DE/discover.json +45 -0
  16. package/locales/de-DE/marketAuth.json +42 -0
  17. package/locales/de-DE/setting.json +94 -1
  18. package/locales/en-US/discover.json +45 -0
  19. package/locales/en-US/marketAuth.json +42 -0
  20. package/locales/en-US/setting.json +94 -1
  21. package/locales/es-ES/discover.json +45 -0
  22. package/locales/es-ES/marketAuth.json +42 -0
  23. package/locales/es-ES/setting.json +94 -1
  24. package/locales/fa-IR/discover.json +45 -0
  25. package/locales/fa-IR/marketAuth.json +42 -0
  26. package/locales/fa-IR/setting.json +94 -1
  27. package/locales/fr-FR/discover.json +45 -0
  28. package/locales/fr-FR/marketAuth.json +42 -0
  29. package/locales/fr-FR/setting.json +94 -1
  30. package/locales/it-IT/discover.json +45 -0
  31. package/locales/it-IT/marketAuth.json +42 -0
  32. package/locales/it-IT/setting.json +94 -1
  33. package/locales/ja-JP/discover.json +45 -0
  34. package/locales/ja-JP/marketAuth.json +42 -0
  35. package/locales/ja-JP/setting.json +94 -1
  36. package/locales/ko-KR/discover.json +45 -0
  37. package/locales/ko-KR/marketAuth.json +42 -0
  38. package/locales/ko-KR/setting.json +94 -1
  39. package/locales/nl-NL/discover.json +45 -0
  40. package/locales/nl-NL/marketAuth.json +42 -0
  41. package/locales/nl-NL/setting.json +94 -1
  42. package/locales/pl-PL/discover.json +45 -0
  43. package/locales/pl-PL/marketAuth.json +42 -0
  44. package/locales/pl-PL/setting.json +94 -1
  45. package/locales/pt-BR/discover.json +45 -0
  46. package/locales/pt-BR/marketAuth.json +42 -0
  47. package/locales/pt-BR/setting.json +94 -1
  48. package/locales/ru-RU/discover.json +45 -0
  49. package/locales/ru-RU/marketAuth.json +42 -0
  50. package/locales/ru-RU/setting.json +94 -1
  51. package/locales/tr-TR/discover.json +45 -0
  52. package/locales/tr-TR/marketAuth.json +42 -0
  53. package/locales/tr-TR/setting.json +94 -1
  54. package/locales/vi-VN/discover.json +45 -0
  55. package/locales/vi-VN/marketAuth.json +42 -0
  56. package/locales/vi-VN/setting.json +94 -1
  57. package/locales/zh-CN/discover.json +45 -0
  58. package/locales/zh-CN/marketAuth.json +42 -0
  59. package/locales/zh-CN/setting.json +94 -1
  60. package/locales/zh-TW/discover.json +45 -0
  61. package/locales/zh-TW/marketAuth.json +42 -0
  62. package/locales/zh-TW/setting.json +94 -1
  63. package/package.json +27 -26
  64. package/packages/const/src/url.ts +1 -0
  65. package/packages/database/migrations/0044_add_tool_intervention.sql +1 -0
  66. package/packages/database/migrations/0044_high_toxin.sql +1 -0
  67. package/packages/database/migrations/0045_add_tool_intervention.sql +1 -0
  68. package/packages/database/migrations/meta/0039_snapshot.json +1 -1
  69. package/packages/database/migrations/meta/0044_snapshot.json +7813 -0
  70. package/packages/database/migrations/meta/0045_snapshot.json +8431 -0
  71. package/packages/database/migrations/meta/_journal.json +14 -0
  72. package/packages/database/src/core/migrations.json +36 -7
  73. package/packages/database/src/models/file.ts +15 -1
  74. package/packages/database/src/models/message.ts +1 -1
  75. package/packages/database/src/models/session.ts +42 -1
  76. package/packages/database/src/repositories/aiInfra/index.test.ts +1 -1
  77. package/packages/database/src/repositories/dataExporter/index.test.ts +1 -1
  78. package/packages/database/src/repositories/tableViewer/index.test.ts +1 -1
  79. package/packages/database/src/schemas/agent.ts +1 -0
  80. package/packages/database/src/schemas/message.ts +5 -8
  81. package/packages/electron-client-ipc/src/events/index.ts +6 -1
  82. package/packages/electron-client-ipc/src/events/remoteServer.ts +8 -0
  83. package/packages/fetch-sse/package.json +29 -0
  84. package/packages/{utils/src/fetch → fetch-sse/src}/__tests__/fetchSSE.test.ts +4 -4
  85. package/packages/{utils/src/fetch → fetch-sse/src}/__tests__/parseError.test.ts +7 -4
  86. package/packages/{utils/src/fetch → fetch-sse/src}/fetchSSE.ts +2 -2
  87. package/packages/{utils/src/fetch → fetch-sse/src}/parseError.ts +3 -3
  88. package/packages/model-bank/src/aiModels/mistral.ts +2 -1
  89. package/packages/model-runtime/src/core/contextBuilders/anthropic.test.ts +17 -11
  90. package/packages/model-runtime/src/core/contextBuilders/anthropic.ts +1 -1
  91. package/packages/model-runtime/src/core/contextBuilders/google.test.ts +1 -1
  92. package/packages/model-runtime/src/core/contextBuilders/google.ts +3 -6
  93. package/packages/model-runtime/src/core/contextBuilders/openai.test.ts +4 -2
  94. package/packages/model-runtime/src/core/contextBuilders/openai.ts +1 -1
  95. package/packages/model-runtime/src/core/openaiCompatibleFactory/createImage.test.ts +1 -1
  96. package/packages/model-runtime/src/core/openaiCompatibleFactory/createImage.ts +1 -1
  97. package/packages/model-runtime/src/core/openaiCompatibleFactory/index.test.ts +3 -6
  98. package/packages/model-runtime/src/core/streams/openai/responsesStream.test.ts +1 -1
  99. package/packages/model-runtime/src/helpers/mergeChatMethodOptions.ts +2 -1
  100. package/packages/model-runtime/src/providers/aihubmix/index.test.ts +1 -1
  101. package/packages/model-runtime/src/providers/anthropic/generateObject.test.ts +1 -1
  102. package/packages/model-runtime/src/providers/anthropic/index.test.ts +1 -1
  103. package/packages/model-runtime/src/providers/baichuan/index.test.ts +1 -1
  104. package/packages/model-runtime/src/providers/bedrock/index.test.ts +1 -1
  105. package/packages/model-runtime/src/providers/bfl/createImage.test.ts +4 -4
  106. package/packages/model-runtime/src/providers/bfl/createImage.ts +1 -1
  107. package/packages/model-runtime/src/providers/cloudflare/index.test.ts +1 -1
  108. package/packages/model-runtime/src/providers/cohere/index.test.ts +1 -1
  109. package/packages/model-runtime/src/providers/google/createImage.test.ts +2 -2
  110. package/packages/model-runtime/src/providers/google/createImage.ts +1 -1
  111. package/packages/model-runtime/src/providers/google/generateObject.test.ts +1 -1
  112. package/packages/model-runtime/src/providers/google/index.test.ts +1 -4
  113. package/packages/model-runtime/src/providers/groq/index.test.ts +1 -1
  114. package/packages/model-runtime/src/providers/hunyuan/index.test.ts +1 -1
  115. package/packages/model-runtime/src/providers/minimax/createImage.test.ts +1 -1
  116. package/packages/model-runtime/src/providers/mistral/index.test.ts +1 -1
  117. package/packages/model-runtime/src/providers/moonshot/index.test.ts +1 -1
  118. package/packages/model-runtime/src/providers/novita/index.test.ts +1 -1
  119. package/packages/model-runtime/src/providers/ollama/index.test.ts +43 -32
  120. package/packages/model-runtime/src/providers/ollama/index.ts +31 -7
  121. package/packages/model-runtime/src/providers/openrouter/index.test.ts +1 -1
  122. package/packages/model-runtime/src/providers/perplexity/index.test.ts +1 -1
  123. package/packages/model-runtime/src/providers/ppio/index.test.ts +1 -1
  124. package/packages/model-runtime/src/providers/qwen/createImage.test.ts +1 -1
  125. package/packages/model-runtime/src/providers/search1api/index.test.ts +1 -1
  126. package/packages/model-runtime/src/providers/siliconcloud/createImage.ts +1 -1
  127. package/packages/model-runtime/src/providers/taichu/index.test.ts +1 -1
  128. package/packages/model-runtime/src/providers/wenxin/index.test.ts +1 -1
  129. package/packages/model-runtime/src/providers/zhipu/index.test.ts +1 -1
  130. package/packages/model-runtime/src/utils/errorResponse.test.ts +1 -1
  131. package/packages/ssrf-safe-fetch/index.browser.ts +14 -0
  132. package/packages/ssrf-safe-fetch/package.json +8 -1
  133. package/packages/types/src/aiProvider.ts +1 -1
  134. package/packages/types/src/discover/assistants.ts +16 -0
  135. package/packages/types/src/document/index.ts +38 -38
  136. package/packages/types/src/exportConfig.ts +15 -15
  137. package/packages/types/src/generation/index.ts +5 -5
  138. package/packages/types/src/index.ts +1 -0
  139. package/packages/types/src/message/common/tools.ts +10 -0
  140. package/packages/types/src/message/db/item.ts +15 -1
  141. package/packages/types/src/message/ui/params.ts +15 -1
  142. package/packages/types/src/meta.ts +4 -0
  143. package/packages/types/src/openai/chat.ts +15 -15
  144. package/packages/types/src/plugins/mcp.ts +29 -29
  145. package/packages/types/src/plugins/protocol.ts +43 -43
  146. package/packages/types/src/search.ts +4 -4
  147. package/packages/types/src/session/agentSession.ts +2 -0
  148. package/packages/types/src/tool/plugin.ts +3 -3
  149. package/packages/utils/src/imageToBase64.ts +17 -10
  150. package/packages/utils/src/index.ts +1 -1
  151. package/src/app/(backend)/f/[id]/route.ts +55 -0
  152. package/src/app/(backend)/market/agent/[[...segments]]/route.ts +153 -0
  153. package/src/app/(backend)/market/oidc/[[...segments]]/route.ts +207 -0
  154. package/src/app/[variants]/(main)/(mobile)/me/settings/features/useCategory.tsx +1 -0
  155. package/src/app/[variants]/(main)/_layout/Desktop/SideBar/PinList/index.tsx +4 -2
  156. package/src/app/[variants]/(main)/chat/settings/features/AgentInfoDescription/index.tsx +349 -0
  157. package/src/app/[variants]/(main)/chat/settings/features/HeaderContent.tsx +2 -2
  158. package/src/app/[variants]/(main)/chat/settings/features/PublishResultModal/index.tsx +64 -0
  159. package/src/app/[variants]/(main)/chat/settings/features/SmartAgentActionButton/MarketPublishButton.tsx +196 -0
  160. package/src/app/[variants]/(main)/chat/settings/features/SmartAgentActionButton/MarketPublishModal.tsx +358 -0
  161. package/src/app/[variants]/(main)/chat/settings/features/SmartAgentActionButton/index.tsx +75 -0
  162. package/src/app/[variants]/(main)/discover/(detail)/assistant/AssistantDetailPage.tsx +11 -2
  163. package/src/app/[variants]/(main)/discover/(detail)/assistant/[...slugs]/Client.tsx +12 -1
  164. package/src/app/[variants]/(main)/discover/(detail)/assistant/[...slugs]/features/Details/Nav.tsx +19 -12
  165. package/src/app/[variants]/(main)/discover/(detail)/assistant/[...slugs]/features/Details/Overview/TagList.tsx +14 -5
  166. package/src/app/[variants]/(main)/discover/(detail)/assistant/[...slugs]/features/Details/Overview/index.tsx +2 -0
  167. package/src/app/[variants]/(main)/discover/(detail)/assistant/[...slugs]/features/Details/Related/index.tsx +14 -5
  168. package/src/app/[variants]/(main)/discover/(detail)/assistant/[...slugs]/features/Details/SystemRole/TagList.tsx +14 -5
  169. package/src/app/[variants]/(main)/discover/(detail)/assistant/[...slugs]/features/Details/SystemRole/index.tsx +43 -29
  170. package/src/app/[variants]/(main)/discover/(detail)/assistant/[...slugs]/features/Details/Versions/index.tsx +137 -0
  171. package/src/app/[variants]/(main)/discover/(detail)/assistant/[...slugs]/features/Details/index.tsx +2 -0
  172. package/src/app/[variants]/(main)/discover/(detail)/assistant/[...slugs]/features/Header.tsx +9 -10
  173. package/src/app/[variants]/(main)/discover/(detail)/assistant/[...slugs]/features/Sidebar/ActionButton/AddAgent.tsx +105 -14
  174. package/src/app/[variants]/(main)/discover/(detail)/assistant/[...slugs]/features/Sidebar/Related/index.tsx +20 -6
  175. package/src/app/[variants]/(main)/discover/(detail)/assistant/[...slugs]/features/StatusPage/index.tsx +113 -0
  176. package/src/app/[variants]/(main)/discover/(detail)/features/Breadcrumb.tsx +4 -3
  177. package/src/app/[variants]/(main)/discover/(list)/_layout/Desktop/Nav.tsx +3 -1
  178. package/src/app/[variants]/(main)/discover/(list)/assistant/AssistantPage.tsx +4 -1
  179. package/src/app/[variants]/(main)/discover/(list)/assistant/Client.tsx +6 -2
  180. package/src/app/[variants]/(main)/discover/(list)/assistant/features/Category/index.tsx +7 -3
  181. package/src/app/[variants]/(main)/discover/(list)/assistant/features/List/Item.tsx +13 -2
  182. package/src/app/[variants]/(main)/discover/(list)/assistant/features/MarketSourceSwitch.tsx +64 -0
  183. package/src/app/[variants]/(main)/discover/(list)/features/SortButton/index.tsx +26 -7
  184. package/src/app/[variants]/(main)/profile/_layout/Desktop/index.tsx +10 -10
  185. package/src/app/[variants]/(main)/settings/_layout/type.ts +1 -1
  186. package/src/app/[variants]/(main)/settings/agent/index.tsx +11 -10
  187. package/src/app/[variants]/(main)/settings/common/index.tsx +1 -1
  188. package/src/app/[variants]/(main)/settings/page.tsx +13 -10
  189. package/src/app/[variants]/(main)/settings/provider/ProviderMenu/Item.tsx +35 -36
  190. package/src/app/[variants]/(main)/settings/provider/ProviderMenu/SearchResult.tsx +5 -5
  191. package/src/app/[variants]/(main)/settings/provider/_layout/Desktop/Container.tsx +10 -4
  192. package/src/app/market-auth-callback/layout.tsx +15 -0
  193. package/src/app/market-auth-callback/page.tsx +196 -0
  194. package/src/envs/app.ts +4 -3
  195. package/src/features/AgentSetting/AgentPrompt/TokenTag.tsx +3 -2
  196. package/src/features/AgentSetting/AgentTTS/SelectWithTTSPreview.tsx +1 -1
  197. package/src/features/AgentSetting/store/action.ts +1 -1
  198. package/src/features/ChatInput/ActionBar/STT/browser.tsx +1 -1
  199. package/src/features/ChatInput/ActionBar/STT/openai.tsx +1 -1
  200. package/src/features/Conversation/components/Extras/TTS/InitPlayer.tsx +1 -1
  201. package/src/features/Conversation/components/VirtualizedList/index.tsx +2 -1
  202. package/src/features/PluginTag/PluginStatus.tsx +1 -1
  203. package/src/features/PluginsUI/Render/MCPType/index.tsx +26 -6
  204. package/src/hooks/useAgentOwnershipCheck.ts +143 -0
  205. package/src/instrumentation.node.ts +3 -2
  206. package/src/layout/AuthProvider/MarketAuth/MarketAuthProvider.tsx +364 -0
  207. package/src/layout/AuthProvider/MarketAuth/errors.ts +75 -0
  208. package/src/layout/AuthProvider/MarketAuth/index.ts +2 -0
  209. package/src/layout/AuthProvider/MarketAuth/oidc.ts +382 -0
  210. package/src/layout/AuthProvider/MarketAuth/types.ts +64 -0
  211. package/src/layout/AuthProvider/index.tsx +17 -4
  212. package/src/locales/default/discover.ts +46 -0
  213. package/src/locales/default/index.ts +2 -0
  214. package/src/locales/default/marketAuth.ts +42 -0
  215. package/src/locales/default/setting.ts +94 -1
  216. package/src/server/globalConfig/genServerAiProviderConfig.test.ts +5 -5
  217. package/src/server/globalConfig/genServerAiProviderConfig.ts +1 -1
  218. package/src/server/routers/desktop/mcp.ts +23 -8
  219. package/src/server/routers/lambda/market/index.ts +36 -14
  220. package/src/server/routers/lambda/message.ts +2 -2
  221. package/src/server/routers/tools/mcp.ts +24 -4
  222. package/src/server/services/discover/index.test.ts +153 -11
  223. package/src/server/services/discover/index.ts +339 -40
  224. package/src/server/services/file/impls/local.ts +4 -1
  225. package/src/server/services/file/index.ts +96 -1
  226. package/src/server/services/mcp/contentProcessor.ts +101 -0
  227. package/src/server/services/mcp/index.test.ts +52 -10
  228. package/src/server/services/mcp/index.ts +29 -26
  229. package/src/server/sitemap.ts +49 -35
  230. package/src/services/_url.ts +15 -1
  231. package/src/services/chat/chat.test.ts +5 -5
  232. package/src/services/chat/clientModelRuntime.test.ts +1 -1
  233. package/src/services/chat/index.ts +6 -6
  234. package/src/services/chat/types.ts +1 -2
  235. package/src/services/discover.ts +16 -5
  236. package/src/services/electron/remoteServer.ts +8 -1
  237. package/src/services/marketApi.ts +124 -0
  238. package/src/services/models.ts +2 -1
  239. package/src/services/session/index.ts +0 -14
  240. package/src/store/discover/slices/assistant/action.ts +20 -7
  241. package/{packages/utils/src → src/utils}/electron/desktopRemoteRPCFetch.ts +1 -1
  242. package/{packages/utils/src → src/utils/server}/parseModels.ts +1 -2
  243. package/src/utils/server/routeVariants.test.ts +340 -0
  244. package/vitest.config.mts +2 -0
  245. package/packages/model-runtime/src/utils/imageToBase64.test.ts +0 -91
  246. package/packages/model-runtime/src/utils/imageToBase64.ts +0 -62
  247. package/src/app/[variants]/(main)/chat/settings/features/SubmitAgentButton/SubmitAgentModal.tsx +0 -98
  248. package/src/app/[variants]/(main)/chat/settings/features/SubmitAgentButton/index.tsx +0 -35
  249. package/src/app/[variants]/(main)/chat/settings/features/SubmitAgentButton/style.ts +0 -47
  250. /package/packages/{utils/src/fetch → fetch-sse/src}/headers.ts +0 -0
  251. /package/packages/{utils/src/fetch → fetch-sse/src}/index.ts +0 -0
  252. /package/packages/{utils/src/fetch → fetch-sse/src}/request.ts +0 -0
  253. /package/{packages/utils/src → src/utils/server}/__snapshots__/parseModels.test.ts.snap +0 -0
  254. /package/{packages/utils/src → src/utils/server}/parseModels.test.ts +0 -0
@@ -7,6 +7,7 @@ import {
7
7
  } from '@lobechat/const';
8
8
  import {
9
9
  AssistantListResponse,
10
+ AssistantMarketSource,
10
11
  AssistantQueryParams,
11
12
  AssistantSorts,
12
13
  CacheRevalidate,
@@ -60,9 +61,12 @@ export class DiscoverService {
60
61
  constructor({ accessToken }: { accessToken?: string } = {}) {
61
62
  this.market = new MarketSDK({
62
63
  accessToken,
63
- baseURL: process.env.MARKET_BASE_URL,
64
+ baseURL: process.env.NEXT_PUBLIC_MARKET_BASE_URL,
64
65
  });
65
- log('DiscoverService initialized with market baseURL: %s', process.env.MARKET_BASE_URL);
66
+ log(
67
+ 'DiscoverService initialized with market baseURL: %s',
68
+ process.env.NEXT_PUBLIC_MARKET_BASE_URL,
69
+ );
66
70
  }
67
71
 
68
72
  async registerClient({ userAgent }: { userAgent?: string }) {
@@ -102,7 +106,7 @@ export class DiscoverService {
102
106
  async fetchM2MToken(params: { clientId: string; clientSecret: string }) {
103
107
  // 使用传入的客户端凭证创建新的 MarketSDK 实例
104
108
  const tokenMarket = new MarketSDK({
105
- baseURL: process.env.MARKET_BASE_URL,
109
+ baseURL: process.env.NEXT_PUBLIC_MARKET_BASE_URL,
106
110
  clientId: params.clientId,
107
111
  clientSecret: params.clientSecret,
108
112
  });
@@ -208,25 +212,47 @@ export class DiscoverService {
208
212
  return result;
209
213
  };
210
214
 
211
- // ============================== Assistant Market ==============================
215
+ private normalizeAuthorField = (author: unknown): string => {
216
+ if (!author) return '';
217
+
218
+ if (typeof author === 'string') return author;
219
+
220
+ if (typeof author === 'object') {
221
+ const { avatar, url, name } = author as {
222
+ avatar?: unknown;
223
+ name?: unknown;
224
+ url?: unknown;
225
+ };
226
+
227
+ if (typeof name === 'string' && name.length > 0) return name;
228
+ if (typeof avatar === 'string' && avatar.length > 0) return avatar;
229
+ if (typeof url === 'string' && url.length > 0) return url;
230
+ }
212
231
 
213
- private _getAssistantList = async (locale?: string): Promise<DiscoverAssistantItem[]> => {
214
- log('_getAssistantList: locale=%s', locale);
232
+ return '';
233
+ };
234
+
235
+ private isLegacySource = (source?: AssistantMarketSource) => source === 'legacy';
236
+
237
+ private legacyGetAssistantListRaw = async (locale?: string): Promise<DiscoverAssistantItem[]> => {
238
+ log('legacyGetAssistantListRaw: locale=%s', locale);
215
239
  const normalizedLocale = normalizeLocale(locale);
216
240
  const list = await this.assistantStore.getAgentIndex(normalizedLocale);
217
241
  if (!list || !Array.isArray(list)) {
218
- log('_getAssistantList: no valid list found, returning empty array');
242
+ log('legacyGetAssistantListRaw: no valid list found, returning empty array');
219
243
  return [];
220
244
  }
221
245
  const result = list.map(({ meta, ...item }) => ({ ...item, ...meta }));
222
- log('_getAssistantList: returning %d items', result.length);
246
+ log('legacyGetAssistantListRaw: returning %d items', result.length);
223
247
  return result;
224
248
  };
225
249
 
226
- getAssistantCategories = async (params: CategoryListQuery = {}): Promise<CategoryItem[]> => {
227
- log('getAssistantCategories: params=%O', params);
250
+ private legacyGetAssistantCategories = async (
251
+ params: CategoryListQuery = {},
252
+ ): Promise<CategoryItem[]> => {
253
+ log('legacyGetAssistantCategories: params=%O', params);
228
254
  const { q, locale } = params;
229
- let list = await this._getAssistantList(locale);
255
+ let list = await this.legacyGetAssistantListRaw(locale);
230
256
  if (q) {
231
257
  const originalCount = list.length;
232
258
  list = list.filter((item) => {
@@ -238,7 +264,7 @@ export class DiscoverService {
238
264
  .includes(decodeURIComponent(q).toLowerCase());
239
265
  });
240
266
  log(
241
- 'getAssistantCategories: filtered by query "%s", %d -> %d items',
267
+ 'legacyGetAssistantCategories: filtered by query "%s", %d -> %d items',
242
268
  q,
243
269
  originalCount,
244
270
  list.length,
@@ -246,25 +272,26 @@ export class DiscoverService {
246
272
  }
247
273
  const categoryCounts = countBy(list, (item) => item.category);
248
274
  const result = Object.entries(categoryCounts)
249
- .filter(([category]) => Boolean(category)) // 过滤掉空值
275
+ .filter(([category]) => Boolean(category))
250
276
  .map(([category, count]) => ({
251
277
  category,
252
278
  count,
253
279
  }));
254
- log('getAssistantCategories: returning %d categories', result.length);
280
+ log('legacyGetAssistantCategories: returning %d categories', result.length);
255
281
  return result;
256
282
  };
257
283
 
258
- getAssistantDetail = async (params: {
284
+ private legacyGetAssistantDetail = async (params: {
259
285
  identifier: string;
260
286
  locale?: string;
287
+ version?: string;
261
288
  }): Promise<DiscoverAssistantDetail | undefined> => {
262
- log('getAssistantDetail: params=%O', params);
289
+ log('legacyGetAssistantDetail: params=%O', params);
263
290
  const { locale, identifier } = params;
264
291
  const normalizedLocale = normalizeLocale(locale);
265
292
  let data = await this.assistantStore.getAgent(identifier, normalizedLocale);
266
293
  if (!data) {
267
- log('getAssistantDetail: assistant not found for identifier=%s', identifier);
294
+ log('legacyGetAssistantDetail: assistant not found for identifier=%s', identifier);
268
295
  return;
269
296
  }
270
297
  const { meta, ...item } = data;
@@ -274,30 +301,36 @@ export class DiscoverService {
274
301
  locale,
275
302
  page: 1,
276
303
  pageSize: 7,
304
+ source: 'legacy',
277
305
  });
278
306
  const result = {
279
307
  ...assistant,
280
308
  related: list.items.filter((item) => item.identifier !== assistant.identifier).slice(0, 6),
281
309
  };
282
- log('getAssistantDetail: returning assistant with %d related items', result.related.length);
310
+ log(
311
+ 'legacyGetAssistantDetail: returning assistant with %d related items',
312
+ result.related.length,
313
+ );
283
314
  return result;
284
315
  };
285
316
 
286
- getAssistantIdentifiers = async (): Promise<IdentifiersResponse> => {
287
- log('getAssistantIdentifiers: fetching identifiers');
288
- const list = await this._getAssistantList();
317
+ private legacyGetAssistantIdentifiers = async (): Promise<IdentifiersResponse> => {
318
+ log('legacyGetAssistantIdentifiers: fetching identifiers');
319
+ const list = await this.legacyGetAssistantListRaw();
289
320
  const result = list.map((item) => {
290
321
  return {
291
322
  identifier: item.identifier,
292
323
  lastModified: item.createdAt,
293
324
  };
294
325
  });
295
- log('getAssistantIdentifiers: returning %d identifiers', result.length);
326
+ log('legacyGetAssistantIdentifiers: returning %d identifiers', result.length);
296
327
  return result;
297
328
  };
298
329
 
299
- getAssistantList = async (params: AssistantQueryParams = {}): Promise<AssistantListResponse> => {
300
- log('getAssistantList: params=%O', params);
330
+ private legacyGetAssistantList = async (
331
+ params: AssistantQueryParams = {},
332
+ ): Promise<AssistantListResponse> => {
333
+ log('legacyGetAssistantList: params=%O', params);
301
334
  const {
302
335
  locale,
303
336
  category,
@@ -306,14 +339,29 @@ export class DiscoverService {
306
339
  pageSize = 20,
307
340
  q,
308
341
  sort = AssistantSorts.CreatedAt,
342
+ ownerId,
309
343
  } = params;
310
- let list = await this._getAssistantList(locale);
344
+ const currentPage = Number(page) || 1;
345
+ const currentPageSize = Number(pageSize) || 20;
346
+
347
+ if (ownerId) {
348
+ log('legacyGetAssistantList: ownerId filter not supported in legacy source');
349
+ return {
350
+ currentPage,
351
+ items: [],
352
+ pageSize: currentPageSize,
353
+ totalCount: 0,
354
+ totalPages: 0,
355
+ };
356
+ }
357
+
358
+ let list = await this.legacyGetAssistantListRaw(locale);
311
359
  const originalCount = list.length;
312
360
 
313
361
  if (category) {
314
362
  list = list.filter((item) => item.category === category);
315
363
  log(
316
- 'getAssistantList: filtered by category "%s", %d -> %d items',
364
+ 'legacyGetAssistantList: filtered by category "%s", %d -> %d items',
317
365
  category,
318
366
  originalCount,
319
367
  list.length,
@@ -330,11 +378,16 @@ export class DiscoverService {
330
378
  .toLowerCase()
331
379
  .includes(decodeURIComponent(q).toLowerCase());
332
380
  });
333
- log('getAssistantList: filtered by query "%s", %d -> %d items', q, beforeFilter, list.length);
381
+ log(
382
+ 'legacyGetAssistantList: filtered by query "%s", %d -> %d items',
383
+ q,
384
+ beforeFilter,
385
+ list.length,
386
+ );
334
387
  }
335
388
 
336
389
  if (sort) {
337
- log('getAssistantList: sorting by %s %s', sort, order);
390
+ log('legacyGetAssistantList: sorting by %s %s', sort, order);
338
391
  switch (sort) {
339
392
  case AssistantSorts.CreatedAt: {
340
393
  list = list.sort((a, b) => {
@@ -349,9 +402,9 @@ export class DiscoverService {
349
402
  case AssistantSorts.KnowledgeCount: {
350
403
  list = list.sort((a, b) => {
351
404
  if (order === 'asc') {
352
- return a.knowledgeCount - b.knowledgeCount;
405
+ return (a.knowledgeCount || 0) - (b.knowledgeCount || 0);
353
406
  } else {
354
- return b.knowledgeCount - a.knowledgeCount;
407
+ return (b.knowledgeCount || 0) - (a.knowledgeCount || 0);
355
408
  }
356
409
  });
357
410
  break;
@@ -359,9 +412,9 @@ export class DiscoverService {
359
412
  case AssistantSorts.PluginCount: {
360
413
  list = list.sort((a, b) => {
361
414
  if (order === 'asc') {
362
- return a.pluginCount - b.pluginCount;
415
+ return (a.pluginCount || 0) - (b.pluginCount || 0);
363
416
  } else {
364
- return b.pluginCount - a.pluginCount;
417
+ return (b.pluginCount || 0) - (a.pluginCount || 0);
365
418
  }
366
419
  });
367
420
  break;
@@ -369,9 +422,9 @@ export class DiscoverService {
369
422
  case AssistantSorts.TokenUsage: {
370
423
  list = list.sort((a, b) => {
371
424
  if (order === 'asc') {
372
- return a.tokenUsage - b.tokenUsage;
425
+ return (a.tokenUsage || 0) - (b.tokenUsage || 0);
373
426
  } else {
374
- return b.tokenUsage - a.tokenUsage;
427
+ return (b.tokenUsage || 0) - (a.tokenUsage || 0);
375
428
  }
376
429
  });
377
430
  break;
@@ -396,25 +449,271 @@ export class DiscoverService {
396
449
  });
397
450
  break;
398
451
  }
452
+ default: {
453
+ break;
454
+ }
399
455
  }
400
456
  }
401
457
 
458
+ const start = (currentPage - 1) * currentPageSize;
459
+ const end = currentPage * currentPageSize;
402
460
  const result = {
403
- currentPage: page,
404
- items: list.slice((page - 1) * pageSize, page * pageSize),
405
- pageSize,
461
+ currentPage,
462
+ items: list.slice(start, end),
463
+ pageSize: currentPageSize,
406
464
  totalCount: list.length,
407
- totalPages: Math.ceil(list.length / pageSize),
465
+ totalPages: Math.ceil(list.length / currentPageSize),
408
466
  };
409
467
  log(
410
- 'getAssistantList: returning page %d/%d with %d items',
411
- page,
468
+ 'legacyGetAssistantList: returning page %d/%d with %d items',
469
+ currentPage,
412
470
  result.totalPages,
413
471
  result.items.length,
414
472
  );
415
473
  return result;
416
474
  };
417
475
 
476
+ // ============================== Assistant Market ==============================
477
+
478
+ getAssistantCategories = async (
479
+ params: CategoryListQuery & { source?: AssistantMarketSource } = {},
480
+ ): Promise<CategoryItem[]> => {
481
+ log('getAssistantCategories: params=%O', params);
482
+ const { source, ...rest } = params;
483
+ if (this.isLegacySource(source)) {
484
+ return this.legacyGetAssistantCategories(rest);
485
+ }
486
+
487
+ const { q, locale } = rest;
488
+ const normalizedLocale = normalizeLocale(locale);
489
+
490
+ try {
491
+ // @ts-ignore
492
+ const categories = await this.market.agents.getCategories({
493
+ locale: normalizedLocale,
494
+ q,
495
+ });
496
+ log('getAssistantCategories: returning %d categories from market SDK', categories.length);
497
+ return categories;
498
+ } catch (error) {
499
+ log('getAssistantCategories: error fetching from market SDK: %O', error);
500
+ return [];
501
+ }
502
+ };
503
+
504
+ getAssistantDetail = async (params: {
505
+ identifier: string;
506
+ locale?: string;
507
+ source?: AssistantMarketSource;
508
+ version?: string;
509
+ }): Promise<DiscoverAssistantDetail | undefined> => {
510
+ log('getAssistantDetail: params=%O', params);
511
+ const { source, ...rest } = params;
512
+ if (this.isLegacySource(source)) {
513
+ return this.legacyGetAssistantDetail(rest);
514
+ }
515
+
516
+ const { locale, identifier, version } = rest;
517
+ const normalizedLocale = normalizeLocale(locale);
518
+
519
+ try {
520
+ // @ts-ignore
521
+ const data = await this.market.agents.getAgentDetail(identifier, {
522
+ locale: normalizedLocale,
523
+ version,
524
+ });
525
+
526
+ if (!data) {
527
+ log('getAssistantDetail: assistant not found for identifier=%s', identifier);
528
+ return;
529
+ }
530
+
531
+ const normalizedAuthor = this.normalizeAuthorField(data.author);
532
+ const assistant = {
533
+ author: normalizedAuthor || (data.ownerId !== null ? `User${data.ownerId}` : 'Unknown'),
534
+ avatar: data.avatar || normalizedAuthor || '',
535
+ category: (data as any).category || 'general',
536
+ config: data.config || {},
537
+ createdAt: (data as any).createdAt,
538
+ currentVersion: data.version,
539
+ description: (data as any).description || data.summary,
540
+ examples: Array.isArray((data as any).examples)
541
+ ? (data as any).examples.map((example: any) => ({
542
+ content: typeof example === 'string' ? example : example.content || '',
543
+ role: example.role || 'user',
544
+ }))
545
+ : [],
546
+ homepage:
547
+ (data as any).homepage ||
548
+ `https://lobehub.com/discover/assistant/${(data as any).identifier}`,
549
+ identifier: (data as any).identifier,
550
+ knowledgeCount:
551
+ (data.config as any)?.knowledgeBases?.length || (data as any).knowledgeCount || 0,
552
+ pluginCount: (data.config as any)?.plugins?.length || (data as any).pluginCount || 0,
553
+ readme: data.documentationUrl || '',
554
+ schemaVersion: 1,
555
+ status: data.status,
556
+ summary: data.summary || '',
557
+ systemRole: (data.config as any)?.systemRole || '',
558
+ tags: data.tags || [],
559
+ title: (data as any).name || (data as any).identifier,
560
+ tokenUsage: data.tokenUsage || 0,
561
+ versions:
562
+ // @ts-ignore
563
+ data.versions?.map((item) => ({
564
+ createdAt: (item as any).createdAt || item.updatedAt,
565
+ isLatest: item.isLatest,
566
+ isValidated: item.isValidated,
567
+ status: item.status as any,
568
+ version: item.version,
569
+ })) || [],
570
+ };
571
+
572
+ // Get related assistants
573
+ const list = await this.getAssistantList({
574
+ category: assistant.category,
575
+ locale,
576
+ page: 1,
577
+ pageSize: 7,
578
+ source,
579
+ });
580
+
581
+ const result = {
582
+ ...assistant,
583
+ related: list.items.filter((item) => item.identifier !== assistant.identifier).slice(0, 6),
584
+ };
585
+
586
+ log('getAssistantDetail: returning assistant with %d related items', result.related.length);
587
+ return result;
588
+ } catch (error) {
589
+ log('getAssistantDetail: error fetching from market SDK: %O', error);
590
+ return;
591
+ }
592
+ };
593
+
594
+ getAssistantIdentifiers = async (
595
+ params: { source?: AssistantMarketSource } = {},
596
+ ): Promise<IdentifiersResponse> => {
597
+ log('getAssistantIdentifiers: fetching identifiers with params=%O', params);
598
+ if (this.isLegacySource(params.source)) {
599
+ return this.legacyGetAssistantIdentifiers();
600
+ }
601
+
602
+ try {
603
+ // @ts-ignore
604
+ const identifiers = await this.market.agents.getPublishedIdentifiers();
605
+ // @ts-ignore
606
+ const result = identifiers.map((item) => ({
607
+ identifier: item.id,
608
+ lastModified: item.lastModified,
609
+ }));
610
+ log('getAssistantIdentifiers: returning %d identifiers from market SDK', result.length);
611
+ return result;
612
+ } catch (error) {
613
+ log('getAssistantIdentifiers: error fetching from market SDK: %O', error);
614
+ return [];
615
+ }
616
+ };
617
+
618
+ getAssistantList = async (params: AssistantQueryParams = {}): Promise<AssistantListResponse> => {
619
+ log('getAssistantList: params=%O', params);
620
+ const { source, ...rest } = params;
621
+ if (this.isLegacySource(source)) {
622
+ return this.legacyGetAssistantList(rest);
623
+ }
624
+
625
+ const {
626
+ locale,
627
+ category,
628
+ order = 'desc',
629
+ page = 1,
630
+ pageSize = 20,
631
+ q,
632
+ sort = AssistantSorts.CreatedAt,
633
+ ownerId,
634
+ } = rest;
635
+
636
+ try {
637
+ const normalizedLocale = normalizeLocale(locale);
638
+
639
+ let apiSort: 'createdAt' | 'updatedAt' | 'name' = 'createdAt';
640
+ switch (sort) {
641
+ case AssistantSorts.Identifier:
642
+ case AssistantSorts.Title: {
643
+ apiSort = 'name';
644
+ break;
645
+ }
646
+ case AssistantSorts.CreatedAt:
647
+ case AssistantSorts.MyOwn: {
648
+ apiSort = 'createdAt';
649
+ break;
650
+ }
651
+ default: {
652
+ apiSort = 'createdAt';
653
+ }
654
+ }
655
+
656
+ // @ts-ignore
657
+ const data = await this.market.agents.getAgentList({
658
+ category,
659
+ locale: normalizedLocale,
660
+ order,
661
+ ownerId,
662
+ page,
663
+ pageSize,
664
+ q,
665
+ sort: apiSort,
666
+ status: 'published',
667
+ visibility: 'public',
668
+ });
669
+
670
+ const transformedItems: DiscoverAssistantItem[] = (data.items || []).map((item: any) => {
671
+ const normalizedAuthor = this.normalizeAuthorField(item.author);
672
+ return {
673
+ author: normalizedAuthor || (item.ownerId !== null ? `User${item.ownerId}` : 'Unknown'),
674
+ avatar: item.avatar || normalizedAuthor || '',
675
+ category: item.category || 'general',
676
+ config: item.config || {},
677
+ createdAt: item.createdAt || item.updatedAt || new Date().toISOString(),
678
+ description: item.description || item.summary || '',
679
+ homepage: item.homepage || `https://lobehub.com/discover/assistant/${item.identifier}`,
680
+ identifier: item.identifier,
681
+ knowledgeCount: item.knowledgeCount ?? item.config?.knowledgeBases?.length ?? 0,
682
+ pluginCount: item.pluginCount ?? item.config?.plugins?.length ?? 0,
683
+ schemaVersion: item.schemaVersion ?? 1,
684
+ tags: item.tags || [],
685
+ title: item.name || item.identifier,
686
+ tokenUsage: item.tokenUsage || 0,
687
+ };
688
+ });
689
+
690
+ const result: AssistantListResponse = {
691
+ currentPage: data.currentPage || page,
692
+ items: transformedItems,
693
+ pageSize: data.pageSize || pageSize,
694
+ totalCount: data.totalCount || 0,
695
+ totalPages: data.totalPages || 0,
696
+ };
697
+
698
+ log(
699
+ 'getAssistantList: returning page %d/%d with %d items from market SDK',
700
+ result.currentPage,
701
+ result.totalPages,
702
+ result.items.length,
703
+ );
704
+ return result;
705
+ } catch (error) {
706
+ log('getAssistantList: error fetching from market SDK: %O', error);
707
+ return {
708
+ currentPage: page,
709
+ items: [],
710
+ pageSize,
711
+ totalCount: 0,
712
+ totalPages: 0,
713
+ };
714
+ }
715
+ };
716
+
418
717
  // ============================== MCP Market ==============================
419
718
 
420
719
  getMcpCategories = async (params: CategoryListQuery = {}): Promise<CategoryItem[]> => {
@@ -1,3 +1,4 @@
1
+ import debug from 'debug';
1
2
  import { sha256 } from 'js-sha256';
2
3
  import { existsSync, readFileSync } from 'node:fs';
3
4
  import path from 'node:path';
@@ -8,6 +9,8 @@ import { inferContentTypeFromImageUrl } from '@/utils/url';
8
9
  import { FileServiceImpl } from './type';
9
10
  import { extractKeyFromUrlOrReturnOriginal } from './utils';
10
11
 
12
+ const log = debug('lobe-file:desktop-local');
13
+
11
14
  /**
12
15
  * 桌面应用本地文件服务实现
13
16
  */
@@ -202,7 +205,7 @@ export class DesktopLocalFileImpl implements FileServiceImpl {
202
205
  throw new Error('Failed to upload file via Electron IPC');
203
206
  }
204
207
 
205
- console.log('[DesktopLocalFileImpl] File uploaded successfully:', result.metadata);
208
+ log('File uploaded successfully: %O', result.metadata);
206
209
  return { key: result.metadata.path };
207
210
  } catch (error) {
208
211
  console.error('[DesktopLocalFileImpl] Failed to upload media file:', error);
@@ -1,11 +1,12 @@
1
1
  import { LobeChatDatabase } from '@lobechat/database';
2
+ import { inferContentTypeFromImageUrl, nanoid, uuid } from '@lobechat/utils';
2
3
  import { TRPCError } from '@trpc/server';
4
+ import { sha256 } from 'js-sha256';
3
5
 
4
6
  import { serverDBEnv } from '@/config/db';
5
7
  import { FileModel } from '@/database/models/file';
6
8
  import { FileItem } from '@/database/schemas';
7
9
  import { TempFileManager } from '@/server/utils/tempFileManager';
8
- import { nanoid } from '@/utils/uuid';
9
10
 
10
11
  import { FileServiceImpl, createFileServiceModule } from './impls';
11
12
 
@@ -94,6 +95,100 @@ export class FileService {
94
95
  return this.impl.uploadMedia(key, buffer);
95
96
  }
96
97
 
98
+ /**
99
+ * Create file record (common method)
100
+ * Automatically handles globalFiles deduplication logic
101
+ *
102
+ * @param params - File parameters
103
+ * @param params.id - Optional custom file ID (defaults to auto-generated)
104
+ * @returns File record and proxy URL
105
+ */
106
+ public async createFileRecord(params: {
107
+ fileHash: string;
108
+ fileType: string;
109
+ id?: string;
110
+ name: string;
111
+ size: number;
112
+ url: string;
113
+ }): Promise<{ fileId: string; url: string }> {
114
+ // Check if hash already exists in globalFiles
115
+ const { isExist } = await this.fileModel.checkHash(params.fileHash);
116
+
117
+ // Create database record
118
+ // If hash doesn't exist, also create globalFiles record
119
+ const { id } = await this.fileModel.create(
120
+ {
121
+ fileHash: params.fileHash,
122
+ fileType: params.fileType,
123
+ id: params.id, // Use custom ID if provided
124
+ name: params.name,
125
+ size: params.size,
126
+ url: params.url,
127
+ },
128
+ !isExist, // insertToGlobalFiles
129
+ );
130
+
131
+ // Return unified proxy URL: /f/:id
132
+ return {
133
+ fileId: id,
134
+ url: `/f/${id}`,
135
+ };
136
+ }
137
+
138
+ /**
139
+ * Upload base64 data and create database record
140
+ * @param base64Data - Base64 data (supports data URI format or pure base64)
141
+ * @param pathname - File storage path (must include file extension)
142
+ * @returns Contains key (storage path), fileId (database record ID) and url (proxy access path)
143
+ */
144
+ public async uploadBase64(
145
+ base64Data: string,
146
+ pathname: string,
147
+ ): Promise<{ fileId: string; key: string; url: string }> {
148
+ let base64String: string;
149
+
150
+ // If data URI format (data:image/png;base64,xxx)
151
+ if (base64Data.startsWith('data:')) {
152
+ const commaIndex = base64Data.indexOf(',');
153
+ if (commaIndex === -1) {
154
+ throw new TRPCError({ code: 'BAD_REQUEST', message: 'Invalid base64 data format' });
155
+ }
156
+ base64String = base64Data.slice(commaIndex + 1);
157
+ } else {
158
+ // Pure base64 string
159
+ base64String = base64Data;
160
+ }
161
+
162
+ // Convert to Buffer
163
+ const buffer = Buffer.from(base64String, 'base64');
164
+
165
+ // Upload to storage (S3 or local)
166
+ const { key } = await this.uploadMedia(pathname, buffer);
167
+
168
+ // Extract filename from pathname
169
+ const name = pathname.split('/').pop() || 'unknown';
170
+
171
+ // Calculate file metadata
172
+ const size = buffer.length;
173
+ const fileType = inferContentTypeFromImageUrl(pathname) || 'application/octet-stream';
174
+ const hash = sha256(buffer);
175
+
176
+ // Generate UUID for cleaner URLs
177
+ const fileId = uuid();
178
+
179
+ // Use common method to create file record
180
+ const { fileId: createdId, url } = await this.createFileRecord({
181
+ fileHash: hash,
182
+ fileType,
183
+ id: fileId, // Use UUID instead of auto-generated ID
184
+ name,
185
+ size,
186
+ url: key, // Store original key (S3 key or desktop://)
187
+ });
188
+
189
+ return { fileId: createdId, key, url };
190
+ }
191
+
97
192
  async downloadFileToLocal(
98
193
  fileId: string,
99
194
  ): Promise<{ cleanup: () => void; file: FileItem; filePath: string }> {