@lobehub/lobehub 2.0.0-next.47 → 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 (228) hide show
  1. package/.env.example +11 -0
  2. package/CHANGELOG.md +17 -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 +5 -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/message.ts +1 -1
  74. package/packages/database/src/models/session.ts +42 -1
  75. package/packages/database/src/schemas/agent.ts +1 -0
  76. package/packages/database/src/schemas/message.ts +5 -8
  77. package/packages/electron-client-ipc/src/events/index.ts +6 -1
  78. package/packages/electron-client-ipc/src/events/remoteServer.ts +8 -0
  79. package/packages/fetch-sse/package.json +29 -0
  80. package/packages/{utils/src/fetch → fetch-sse/src}/__tests__/fetchSSE.test.ts +4 -4
  81. package/packages/{utils/src/fetch → fetch-sse/src}/__tests__/parseError.test.ts +7 -4
  82. package/packages/{utils/src/fetch → fetch-sse/src}/fetchSSE.ts +2 -2
  83. package/packages/{utils/src/fetch → fetch-sse/src}/parseError.ts +3 -3
  84. package/packages/model-bank/src/aiModels/mistral.ts +2 -1
  85. package/packages/model-runtime/src/core/contextBuilders/anthropic.test.ts +17 -11
  86. package/packages/model-runtime/src/core/contextBuilders/anthropic.ts +1 -1
  87. package/packages/model-runtime/src/core/contextBuilders/google.test.ts +1 -1
  88. package/packages/model-runtime/src/core/contextBuilders/google.ts +3 -6
  89. package/packages/model-runtime/src/core/contextBuilders/openai.test.ts +4 -2
  90. package/packages/model-runtime/src/core/contextBuilders/openai.ts +1 -1
  91. package/packages/model-runtime/src/core/openaiCompatibleFactory/createImage.test.ts +1 -1
  92. package/packages/model-runtime/src/core/openaiCompatibleFactory/createImage.ts +1 -1
  93. package/packages/model-runtime/src/core/openaiCompatibleFactory/index.test.ts +3 -6
  94. package/packages/model-runtime/src/core/streams/openai/responsesStream.test.ts +1 -1
  95. package/packages/model-runtime/src/helpers/mergeChatMethodOptions.ts +2 -1
  96. package/packages/model-runtime/src/providers/aihubmix/index.test.ts +1 -1
  97. package/packages/model-runtime/src/providers/anthropic/generateObject.test.ts +1 -1
  98. package/packages/model-runtime/src/providers/anthropic/index.test.ts +1 -1
  99. package/packages/model-runtime/src/providers/baichuan/index.test.ts +1 -1
  100. package/packages/model-runtime/src/providers/bedrock/index.test.ts +1 -1
  101. package/packages/model-runtime/src/providers/bfl/createImage.test.ts +4 -4
  102. package/packages/model-runtime/src/providers/bfl/createImage.ts +1 -1
  103. package/packages/model-runtime/src/providers/cloudflare/index.test.ts +1 -1
  104. package/packages/model-runtime/src/providers/cohere/index.test.ts +1 -1
  105. package/packages/model-runtime/src/providers/google/createImage.test.ts +2 -2
  106. package/packages/model-runtime/src/providers/google/createImage.ts +1 -1
  107. package/packages/model-runtime/src/providers/google/generateObject.test.ts +1 -1
  108. package/packages/model-runtime/src/providers/google/index.test.ts +1 -4
  109. package/packages/model-runtime/src/providers/groq/index.test.ts +1 -1
  110. package/packages/model-runtime/src/providers/hunyuan/index.test.ts +1 -1
  111. package/packages/model-runtime/src/providers/minimax/createImage.test.ts +1 -1
  112. package/packages/model-runtime/src/providers/mistral/index.test.ts +1 -1
  113. package/packages/model-runtime/src/providers/moonshot/index.test.ts +1 -1
  114. package/packages/model-runtime/src/providers/novita/index.test.ts +1 -1
  115. package/packages/model-runtime/src/providers/ollama/index.test.ts +43 -32
  116. package/packages/model-runtime/src/providers/ollama/index.ts +31 -7
  117. package/packages/model-runtime/src/providers/openrouter/index.test.ts +1 -1
  118. package/packages/model-runtime/src/providers/perplexity/index.test.ts +1 -1
  119. package/packages/model-runtime/src/providers/ppio/index.test.ts +1 -1
  120. package/packages/model-runtime/src/providers/qwen/createImage.test.ts +1 -1
  121. package/packages/model-runtime/src/providers/search1api/index.test.ts +1 -1
  122. package/packages/model-runtime/src/providers/siliconcloud/createImage.ts +1 -1
  123. package/packages/model-runtime/src/providers/taichu/index.test.ts +1 -1
  124. package/packages/model-runtime/src/providers/wenxin/index.test.ts +1 -1
  125. package/packages/model-runtime/src/providers/zhipu/index.test.ts +1 -1
  126. package/packages/model-runtime/src/utils/errorResponse.test.ts +1 -1
  127. package/packages/ssrf-safe-fetch/index.browser.ts +14 -0
  128. package/packages/ssrf-safe-fetch/package.json +8 -1
  129. package/packages/types/src/discover/assistants.ts +16 -0
  130. package/packages/types/src/index.ts +1 -0
  131. package/packages/types/src/message/common/tools.ts +10 -0
  132. package/packages/types/src/message/db/item.ts +15 -1
  133. package/packages/types/src/message/ui/params.ts +15 -1
  134. package/packages/types/src/meta.ts +4 -0
  135. package/packages/types/src/session/agentSession.ts +2 -0
  136. package/packages/utils/src/imageToBase64.ts +17 -10
  137. package/packages/utils/src/index.ts +1 -1
  138. package/src/app/(backend)/market/agent/[[...segments]]/route.ts +153 -0
  139. package/src/app/(backend)/market/oidc/[[...segments]]/route.ts +207 -0
  140. package/src/app/[variants]/(main)/(mobile)/me/settings/features/useCategory.tsx +1 -0
  141. package/src/app/[variants]/(main)/_layout/Desktop/SideBar/PinList/index.tsx +4 -2
  142. package/src/app/[variants]/(main)/chat/settings/features/AgentInfoDescription/index.tsx +349 -0
  143. package/src/app/[variants]/(main)/chat/settings/features/HeaderContent.tsx +2 -2
  144. package/src/app/[variants]/(main)/chat/settings/features/PublishResultModal/index.tsx +64 -0
  145. package/src/app/[variants]/(main)/chat/settings/features/SmartAgentActionButton/MarketPublishButton.tsx +196 -0
  146. package/src/app/[variants]/(main)/chat/settings/features/SmartAgentActionButton/MarketPublishModal.tsx +358 -0
  147. package/src/app/[variants]/(main)/chat/settings/features/SmartAgentActionButton/index.tsx +75 -0
  148. package/src/app/[variants]/(main)/discover/(detail)/assistant/AssistantDetailPage.tsx +11 -2
  149. package/src/app/[variants]/(main)/discover/(detail)/assistant/[...slugs]/Client.tsx +12 -1
  150. package/src/app/[variants]/(main)/discover/(detail)/assistant/[...slugs]/features/Details/Nav.tsx +19 -12
  151. package/src/app/[variants]/(main)/discover/(detail)/assistant/[...slugs]/features/Details/Overview/TagList.tsx +14 -5
  152. package/src/app/[variants]/(main)/discover/(detail)/assistant/[...slugs]/features/Details/Overview/index.tsx +2 -0
  153. package/src/app/[variants]/(main)/discover/(detail)/assistant/[...slugs]/features/Details/Related/index.tsx +14 -5
  154. package/src/app/[variants]/(main)/discover/(detail)/assistant/[...slugs]/features/Details/SystemRole/TagList.tsx +14 -5
  155. package/src/app/[variants]/(main)/discover/(detail)/assistant/[...slugs]/features/Details/SystemRole/index.tsx +43 -29
  156. package/src/app/[variants]/(main)/discover/(detail)/assistant/[...slugs]/features/Details/Versions/index.tsx +137 -0
  157. package/src/app/[variants]/(main)/discover/(detail)/assistant/[...slugs]/features/Details/index.tsx +2 -0
  158. package/src/app/[variants]/(main)/discover/(detail)/assistant/[...slugs]/features/Header.tsx +9 -10
  159. package/src/app/[variants]/(main)/discover/(detail)/assistant/[...slugs]/features/Sidebar/ActionButton/AddAgent.tsx +105 -14
  160. package/src/app/[variants]/(main)/discover/(detail)/assistant/[...slugs]/features/Sidebar/Related/index.tsx +20 -6
  161. package/src/app/[variants]/(main)/discover/(detail)/assistant/[...slugs]/features/StatusPage/index.tsx +113 -0
  162. package/src/app/[variants]/(main)/discover/(detail)/features/Breadcrumb.tsx +4 -3
  163. package/src/app/[variants]/(main)/discover/(list)/_layout/Desktop/Nav.tsx +3 -1
  164. package/src/app/[variants]/(main)/discover/(list)/assistant/AssistantPage.tsx +4 -1
  165. package/src/app/[variants]/(main)/discover/(list)/assistant/Client.tsx +6 -2
  166. package/src/app/[variants]/(main)/discover/(list)/assistant/features/Category/index.tsx +7 -3
  167. package/src/app/[variants]/(main)/discover/(list)/assistant/features/List/Item.tsx +13 -2
  168. package/src/app/[variants]/(main)/discover/(list)/assistant/features/MarketSourceSwitch.tsx +64 -0
  169. package/src/app/[variants]/(main)/discover/(list)/features/SortButton/index.tsx +26 -7
  170. package/src/app/[variants]/(main)/profile/_layout/Desktop/index.tsx +10 -10
  171. package/src/app/[variants]/(main)/settings/_layout/type.ts +1 -1
  172. package/src/app/[variants]/(main)/settings/agent/index.tsx +11 -10
  173. package/src/app/[variants]/(main)/settings/common/index.tsx +1 -1
  174. package/src/app/[variants]/(main)/settings/page.tsx +13 -10
  175. package/src/app/[variants]/(main)/settings/provider/ProviderMenu/Item.tsx +35 -36
  176. package/src/app/[variants]/(main)/settings/provider/ProviderMenu/SearchResult.tsx +5 -5
  177. package/src/app/[variants]/(main)/settings/provider/_layout/Desktop/Container.tsx +10 -4
  178. package/src/app/market-auth-callback/layout.tsx +15 -0
  179. package/src/app/market-auth-callback/page.tsx +196 -0
  180. package/src/features/AgentSetting/AgentPrompt/TokenTag.tsx +3 -2
  181. package/src/features/AgentSetting/AgentTTS/SelectWithTTSPreview.tsx +1 -1
  182. package/src/features/AgentSetting/store/action.ts +1 -1
  183. package/src/features/ChatInput/ActionBar/STT/browser.tsx +1 -1
  184. package/src/features/ChatInput/ActionBar/STT/openai.tsx +1 -1
  185. package/src/features/Conversation/components/Extras/TTS/InitPlayer.tsx +1 -1
  186. package/src/features/PluginTag/PluginStatus.tsx +1 -1
  187. package/src/hooks/useAgentOwnershipCheck.ts +143 -0
  188. package/src/instrumentation.node.ts +3 -2
  189. package/src/layout/AuthProvider/MarketAuth/MarketAuthProvider.tsx +364 -0
  190. package/src/layout/AuthProvider/MarketAuth/errors.ts +75 -0
  191. package/src/layout/AuthProvider/MarketAuth/index.ts +2 -0
  192. package/src/layout/AuthProvider/MarketAuth/oidc.ts +382 -0
  193. package/src/layout/AuthProvider/MarketAuth/types.ts +64 -0
  194. package/src/layout/AuthProvider/index.tsx +17 -4
  195. package/src/locales/default/discover.ts +46 -0
  196. package/src/locales/default/index.ts +2 -0
  197. package/src/locales/default/marketAuth.ts +42 -0
  198. package/src/locales/default/setting.ts +94 -1
  199. package/src/server/globalConfig/genServerAiProviderConfig.test.ts +5 -5
  200. package/src/server/globalConfig/genServerAiProviderConfig.ts +1 -1
  201. package/src/server/routers/lambda/market/index.ts +36 -14
  202. package/src/server/routers/lambda/message.ts +2 -2
  203. package/src/server/services/discover/index.test.ts +153 -11
  204. package/src/server/services/discover/index.ts +339 -40
  205. package/src/server/sitemap.ts +49 -35
  206. package/src/services/_url.ts +15 -1
  207. package/src/services/chat/chat.test.ts +5 -5
  208. package/src/services/chat/clientModelRuntime.test.ts +1 -1
  209. package/src/services/chat/index.ts +6 -6
  210. package/src/services/chat/types.ts +1 -2
  211. package/src/services/discover.ts +16 -5
  212. package/src/services/electron/remoteServer.ts +8 -1
  213. package/src/services/marketApi.ts +124 -0
  214. package/src/services/models.ts +2 -1
  215. package/src/store/discover/slices/assistant/action.ts +20 -7
  216. package/{packages/utils/src → src/utils}/electron/desktopRemoteRPCFetch.ts +1 -1
  217. package/{packages/utils/src → src/utils/server}/parseModels.ts +1 -2
  218. package/vitest.config.mts +2 -0
  219. package/packages/model-runtime/src/utils/imageToBase64.test.ts +0 -91
  220. package/packages/model-runtime/src/utils/imageToBase64.ts +0 -62
  221. package/src/app/[variants]/(main)/chat/settings/features/SubmitAgentButton/SubmitAgentModal.tsx +0 -98
  222. package/src/app/[variants]/(main)/chat/settings/features/SubmitAgentButton/index.tsx +0 -35
  223. package/src/app/[variants]/(main)/chat/settings/features/SubmitAgentButton/style.ts +0 -47
  224. /package/packages/{utils/src/fetch → fetch-sse/src}/headers.ts +0 -0
  225. /package/packages/{utils/src/fetch → fetch-sse/src}/index.ts +0 -0
  226. /package/packages/{utils/src/fetch → fetch-sse/src}/request.ts +0 -0
  227. /package/{packages/utils/src → src/utils/server}/__snapshots__/parseModels.test.ts.snap +0 -0
  228. /package/{packages/utils/src → src/utils/server}/parseModels.test.ts +0 -0
@@ -0,0 +1,382 @@
1
+ import { MARKET_OIDC_ENDPOINTS } from '@/services/_url';
2
+
3
+ import { MarketAuthError } from './errors';
4
+ import { OIDCConfig, PKCEParams, TokenResponse } from './types';
5
+
6
+ /**
7
+ * Market OIDC 授权工具类
8
+ */
9
+ export class MarketOIDC {
10
+ private config: OIDCConfig;
11
+
12
+ private static readonly DESKTOP_HANDOFF_CLIENT = 'desktop';
13
+
14
+ private static readonly DESKTOP_HANDOFF_POLL_INTERVAL = 1500;
15
+
16
+ private static readonly DESKTOP_HANDOFF_TIMEOUT = 5 * 60 * 1000; // 5 minutes
17
+
18
+ constructor(config: OIDCConfig) {
19
+ this.config = config;
20
+ }
21
+
22
+ /**
23
+ * 生成 PKCE code verifier
24
+ */
25
+ private generateCodeVerifier(): string {
26
+ console.log('[MarketOIDC] Generating PKCE code verifier');
27
+ const array = new Uint8Array(32);
28
+ crypto.getRandomValues(array);
29
+ return btoa(String.fromCharCode.apply(null, Array.from(array)))
30
+ .replaceAll('+', '-')
31
+ .replaceAll('/', '_')
32
+ .replaceAll('=', '');
33
+ }
34
+
35
+ /**
36
+ * 生成 PKCE code challenge
37
+ */
38
+ private async generateCodeChallenge(codeVerifier: string): Promise<string> {
39
+ console.log('[MarketOIDC] Generating PKCE code challenge');
40
+ const encoder = new TextEncoder();
41
+ const data = encoder.encode(codeVerifier);
42
+ const digest = await crypto.subtle.digest('SHA-256', data);
43
+ return btoa(String.fromCharCode.apply(null, Array.from(new Uint8Array(digest))))
44
+ .replaceAll('+', '-')
45
+ .replaceAll('/', '_')
46
+ .replaceAll('=', '');
47
+ }
48
+
49
+ /**
50
+ * 生成随机 state
51
+ */
52
+ private generateState(): string {
53
+ console.log('[MarketOIDC] Generating random state');
54
+ const array = new Uint8Array(16);
55
+ crypto.getRandomValues(array);
56
+ return btoa(String.fromCharCode.apply(null, Array.from(array)))
57
+ .replaceAll('+', '-')
58
+ .replaceAll('/', '_')
59
+ .replaceAll('=', '');
60
+ }
61
+
62
+ /**
63
+ * 生成 PKCE 参数
64
+ */
65
+ async generatePKCEParams(): Promise<PKCEParams> {
66
+ console.log('[MarketOIDC] Generating PKCE parameters');
67
+ const codeVerifier = this.generateCodeVerifier();
68
+ const codeChallenge = await this.generateCodeChallenge(codeVerifier);
69
+ const state = this.generateState();
70
+
71
+ // 将参数存储到 sessionStorage 用于后续验证
72
+ sessionStorage.setItem('market_code_verifier', codeVerifier);
73
+ sessionStorage.setItem('market_state', state);
74
+
75
+ console.log('[MarketOIDC] PKCE parameters generated and stored');
76
+ return {
77
+ codeChallenge,
78
+ codeVerifier,
79
+ state,
80
+ };
81
+ }
82
+
83
+ /**
84
+ * 构建授权 URL
85
+ */
86
+ async buildAuthUrl(): Promise<string> {
87
+ console.log('[MarketOIDC] Building authorization URL');
88
+ const pkceParams = await this.generatePKCEParams();
89
+
90
+ console.log('[MarketOIDC] this.config:', this.config);
91
+
92
+ const authUrl = new URL(
93
+ `${this.config.baseUrl.replace(/\/$/, '')}${MARKET_OIDC_ENDPOINTS.auth}`,
94
+ );
95
+ authUrl.searchParams.set('client_id', this.config.clientId);
96
+ authUrl.searchParams.set('redirect_uri', this.config.redirectUri);
97
+ authUrl.searchParams.set('response_type', 'code');
98
+ authUrl.searchParams.set('scope', this.config.scope);
99
+ authUrl.searchParams.set('state', pkceParams.state);
100
+ authUrl.searchParams.set('code_challenge', pkceParams.codeChallenge);
101
+ authUrl.searchParams.set('code_challenge_method', 'S256');
102
+
103
+ console.log('[MarketOIDC] Authorization URL built:', authUrl.toString());
104
+ return authUrl.toString();
105
+ }
106
+
107
+ /**
108
+ * 用授权码换取访问令牌
109
+ */
110
+ async exchangeCodeForToken(code: string, state: string): Promise<TokenResponse> {
111
+ console.log('[MarketOIDC] Exchanging authorization code for token');
112
+
113
+ // 验证 state 参数
114
+ const storedState = sessionStorage.getItem('market_state');
115
+ if (state !== storedState) {
116
+ console.error('[MarketOIDC] State parameter mismatch');
117
+ throw new MarketAuthError('stateMismatch', { message: 'Invalid state parameter' });
118
+ }
119
+
120
+ // 获取存储的 code verifier
121
+ const codeVerifier = sessionStorage.getItem('market_code_verifier');
122
+ if (!codeVerifier) {
123
+ console.error('[MarketOIDC] Code verifier not found');
124
+ throw new MarketAuthError('codeVerifierMissing', { message: 'Code verifier not found' });
125
+ }
126
+
127
+ const tokenUrl = MARKET_OIDC_ENDPOINTS.token;
128
+ const body = new URLSearchParams({
129
+ client_id: this.config.clientId,
130
+ code,
131
+ code_verifier: codeVerifier,
132
+ grant_type: 'authorization_code',
133
+ redirect_uri: this.config.redirectUri,
134
+ });
135
+
136
+ console.log('[MarketOIDC] Sending token exchange request', {
137
+ client_id: this.config.clientId,
138
+ code,
139
+ code_verifier: codeVerifier,
140
+ grant_type: 'authorization_code',
141
+ redirect_uri: this.config.redirectUri,
142
+ });
143
+ const response = await fetch(tokenUrl, {
144
+ body: body.toString(),
145
+ headers: {
146
+ 'Content-Type': 'application/x-www-form-urlencoded',
147
+ },
148
+ method: 'POST',
149
+ });
150
+
151
+ console.log('[MarketOIDC] Token exchange response:', response);
152
+
153
+ if (!response.ok) {
154
+ const errorData = await response.json().catch(() => undefined);
155
+ console.log('[MarketOIDC] Token exchange error data:', errorData);
156
+ const errorMessage =
157
+ `Token exchange failed: ${response.status} ${response.statusText} ${errorData?.error_description || errorData?.error || ''}`.trim();
158
+ console.error('[MarketOIDC]', errorMessage);
159
+ throw new MarketAuthError('authorizationFailed', {
160
+ message: errorMessage,
161
+ meta: {
162
+ error: errorData,
163
+ status: response.status,
164
+ statusText: response.statusText,
165
+ },
166
+ });
167
+ }
168
+
169
+ const tokenData = (await response.json()) as TokenResponse;
170
+ console.log('[MarketOIDC] Token exchange successful');
171
+
172
+ // 清理 sessionStorage 中的临时数据
173
+ sessionStorage.removeItem('market_code_verifier');
174
+ sessionStorage.removeItem('market_state');
175
+
176
+ return tokenData;
177
+ }
178
+
179
+ /**
180
+ * 启动授权流程并返回授权结果
181
+ */
182
+ async startAuthorization(): Promise<{ code: string; state: string }> {
183
+ console.log('[MarketOIDC] Starting authorization flow');
184
+ const authUrl = await this.buildAuthUrl();
185
+
186
+ if (typeof window === 'undefined') {
187
+ throw new MarketAuthError('browserOnly', {
188
+ message: 'Authorization can only be initiated in a browser environment.',
189
+ });
190
+ }
191
+
192
+ const state = sessionStorage.getItem('market_state');
193
+ if (!state) {
194
+ console.error('[MarketOIDC] Missing state parameter in session storage');
195
+ throw new MarketAuthError('stateMissing', {
196
+ message: 'Authorization state not found. Please try again.',
197
+ });
198
+ }
199
+
200
+ const isDesktopApp = process.env.NEXT_PUBLIC_IS_DESKTOP_APP === '1';
201
+
202
+ // 在新窗口中打开授权页面
203
+ let popup: Window | null = null;
204
+ if (isDesktopApp) {
205
+ // Electron 桌面端:使用 IPC 调用主进程打开系统浏览器
206
+ console.log('[MarketOIDC] Desktop app detected, opening system browser via IPC');
207
+ const { remoteServerService } = await import('@/services/electron/remoteServer');
208
+
209
+ try {
210
+ const result = await remoteServerService.requestMarketAuthorization({ authUrl });
211
+ if (!result.success) {
212
+ console.error('[MarketOIDC] Failed to open system browser:', result.error);
213
+ throw new MarketAuthError('openBrowserFailed', {
214
+ message: result.error || 'Failed to open system browser',
215
+ meta: { error: result.error },
216
+ });
217
+ }
218
+ console.log('[MarketOIDC] System browser opened successfully');
219
+ } catch (error) {
220
+ console.error('[MarketOIDC] Exception opening system browser:', error);
221
+ throw new MarketAuthError('openBrowserFailed', {
222
+ cause: error,
223
+ message: 'Failed to open system browser. Please try again.',
224
+ });
225
+ }
226
+
227
+ return this.pollDesktopHandoff(state);
228
+ } else {
229
+ // 浏览器环境:使用 window.open 打开弹窗
230
+ popup = window.open(
231
+ authUrl,
232
+ 'market_auth',
233
+ 'width=580,height=720,scrollbars=yes,resizable=yes',
234
+ );
235
+
236
+ if (!popup) {
237
+ console.error('[MarketOIDC] Failed to open authorization popup');
238
+ throw new MarketAuthError('openPopupFailed', {
239
+ message: 'Failed to open authorization popup. Please check popup blocker settings.',
240
+ });
241
+ }
242
+ }
243
+
244
+ return new Promise((resolve, reject) => {
245
+ let checkClosed: number | undefined;
246
+
247
+ // 先声明,后定义,避免相互“定义前使用”
248
+ let messageHandler: (event: MessageEvent) => void;
249
+
250
+ // 清理函数
251
+ function cleanup() {
252
+ window.removeEventListener('message', messageHandler);
253
+ if (checkClosed) clearInterval(checkClosed);
254
+ }
255
+
256
+ // 监听消息事件,等待授权完成
257
+ messageHandler = (event: MessageEvent) => {
258
+ console.log('[MarketOIDC] Received message from popup:', event.data);
259
+
260
+ if (event.data.type === 'MARKET_AUTH_SUCCESS') {
261
+ cleanup();
262
+
263
+ // 不立即关闭弹窗,让用户看到成功状态
264
+ // 弹窗会在3秒后自动关闭
265
+ resolve({
266
+ code: event.data.code,
267
+ state: event.data.state,
268
+ });
269
+ } else if (event.data.type === 'MARKET_AUTH_ERROR') {
270
+ cleanup();
271
+ popup?.close();
272
+ reject(
273
+ new MarketAuthError('authorizationFailed', {
274
+ message: event.data.error || 'Authorization failed',
275
+ meta: { error: event.data.error },
276
+ }),
277
+ );
278
+ }
279
+ };
280
+
281
+ window.addEventListener('message', messageHandler);
282
+
283
+ // 检查弹窗是否被关闭
284
+ if (popup) {
285
+ checkClosed = setInterval(() => {
286
+ if (popup.closed) {
287
+ cleanup();
288
+ reject(
289
+ new MarketAuthError('popupClosed', { message: 'Authorization popup was closed' }),
290
+ );
291
+ }
292
+ }, 1000) as unknown as number;
293
+ }
294
+ });
295
+ }
296
+
297
+ /**
298
+ * 轮询 handoff 接口获取桌面端授权结果
299
+ */
300
+ private async pollDesktopHandoff(state: string): Promise<{ code: string; state: string }> {
301
+ console.log('[MarketOIDC] Starting desktop handoff polling with state:', state);
302
+
303
+ const startTime = Date.now();
304
+
305
+ const pollUrl = `${MARKET_OIDC_ENDPOINTS.handoff}?id=${encodeURIComponent(
306
+ state,
307
+ )}&client=${encodeURIComponent(MarketOIDC.DESKTOP_HANDOFF_CLIENT)}`;
308
+
309
+ console.log('[MarketOIDC] Poll URL:', pollUrl);
310
+
311
+ while (Date.now() - startTime < MarketOIDC.DESKTOP_HANDOFF_TIMEOUT) {
312
+ try {
313
+ const response = await fetch(pollUrl, {
314
+ cache: 'no-store',
315
+ credentials: 'include',
316
+ });
317
+
318
+ const data = await response.json().catch(() => undefined);
319
+
320
+ console.log('[MarketOIDC] Poll response:', response.status, data);
321
+
322
+ if (
323
+ response.status === 200 &&
324
+ data?.status === 'success' &&
325
+ typeof data?.code === 'string'
326
+ ) {
327
+ console.log('[MarketOIDC] Desktop handoff succeeded');
328
+ return {
329
+ code: data.code,
330
+ state,
331
+ };
332
+ }
333
+
334
+ if (response.status === 202 || data?.status === 'pending') {
335
+ await new Promise<void>((resolve) => {
336
+ setTimeout(resolve, MarketOIDC.DESKTOP_HANDOFF_POLL_INTERVAL);
337
+ });
338
+ continue;
339
+ }
340
+
341
+ if (response.status === 404 || data?.status === 'consumed') {
342
+ throw new MarketAuthError('codeConsumed', {
343
+ message: 'Authorization code already consumed. Please retry.',
344
+ });
345
+ }
346
+
347
+ if (response.status === 410 || data?.status === 'expired') {
348
+ throw new MarketAuthError('sessionExpired', {
349
+ message: 'Authorization session expired. Please restart the sign-in process.',
350
+ });
351
+ }
352
+
353
+ const errorMessage =
354
+ data?.error || data?.message || `Handoff request failed with status ${response.status}`;
355
+ console.error('[MarketOIDC] Handoff polling failed:', errorMessage);
356
+ throw new MarketAuthError('handoffFailed', {
357
+ message: errorMessage,
358
+ meta: { data, status: response.status },
359
+ });
360
+ } catch (error) {
361
+ console.error('[MarketOIDC] Error while polling handoff endpoint:', error);
362
+ if (error instanceof MarketAuthError) {
363
+ throw error;
364
+ }
365
+ const message =
366
+ error instanceof Error
367
+ ? error.message
368
+ : 'Failed to retrieve authorization result from handoff endpoint.';
369
+ throw new MarketAuthError('handoffFailed', {
370
+ cause: error,
371
+ message,
372
+ });
373
+ }
374
+ }
375
+
376
+ console.warn('[MarketOIDC] Desktop handoff polling timed out');
377
+ throw new MarketAuthError('handoffTimeout', {
378
+ message:
379
+ 'Authorization timeout. Please complete the authorization in the browser and try again.',
380
+ });
381
+ }
382
+ }
@@ -0,0 +1,64 @@
1
+ export interface MarketUserInfo {
2
+ accountId: number;
3
+ clientId: string;
4
+ grantId: string;
5
+ scopes: string[];
6
+ sub: string;
7
+ tokenData: {
8
+ accountId: string;
9
+ clientId: string;
10
+ exp: number;
11
+ expiresWithSession: boolean;
12
+ grantId: string;
13
+ gty: string;
14
+ iat: number;
15
+ jti: string;
16
+ kind: string;
17
+ scope: string;
18
+ sessionUid: string;
19
+ };
20
+ }
21
+
22
+ export interface MarketAuthSession {
23
+ accessToken: string;
24
+ expiresAt: number;
25
+ expiresIn: number;
26
+ scope: string;
27
+ tokenType: 'Bearer';
28
+ userInfo?: MarketUserInfo;
29
+ }
30
+
31
+ export interface MarketAuthState {
32
+ isAuthenticated: boolean;
33
+ isLoading: boolean;
34
+ session: MarketAuthSession | null;
35
+ status: 'loading' | 'authenticated' | 'unauthenticated';
36
+ }
37
+
38
+ export interface MarketAuthContextType extends MarketAuthState {
39
+ getCurrentUserInfo: () => MarketUserInfo | null;
40
+ refreshToken: () => Promise<boolean>;
41
+ signIn: () => Promise<number | null>;
42
+ signOut: () => void;
43
+ }
44
+
45
+ export interface OIDCConfig {
46
+ baseUrl: string;
47
+ clientId: string;
48
+ redirectUri: string;
49
+ scope: string;
50
+ }
51
+
52
+ export interface PKCEParams {
53
+ codeChallenge: string;
54
+ codeVerifier: string;
55
+ state: string;
56
+ }
57
+
58
+ export interface TokenResponse {
59
+ accessToken: string;
60
+ expiresIn: number;
61
+ idToken?: string;
62
+ scope: string;
63
+ tokenType: string;
64
+ }
@@ -1,17 +1,30 @@
1
1
  import { PropsWithChildren } from 'react';
2
2
 
3
+ import { isDesktop } from '@/const/version';
3
4
  import { authEnv } from '@/envs/auth';
4
5
 
5
6
  import Clerk from './Clerk';
7
+ import { MarketAuthProvider } from './MarketAuth';
6
8
  import NextAuth from './NextAuth';
7
9
  import NoAuth from './NoAuth';
8
10
 
9
11
  const AuthProvider = ({ children }: PropsWithChildren) => {
10
- if (authEnv.NEXT_PUBLIC_ENABLE_CLERK_AUTH) return <Clerk>{children}</Clerk>;
12
+ // 获取内部 AuthProvider
13
+ let InnerAuthProvider;
14
+ if (authEnv.NEXT_PUBLIC_ENABLE_CLERK_AUTH) {
15
+ InnerAuthProvider = ({ children }: PropsWithChildren) => <Clerk>{children}</Clerk>;
16
+ } else if (authEnv.NEXT_PUBLIC_ENABLE_NEXT_AUTH) {
17
+ InnerAuthProvider = ({ children }: PropsWithChildren) => <NextAuth>{children}</NextAuth>;
18
+ } else {
19
+ InnerAuthProvider = ({ children }: PropsWithChildren) => <NoAuth>{children}</NoAuth>;
20
+ }
11
21
 
12
- if (authEnv.NEXT_PUBLIC_ENABLE_NEXT_AUTH) return <NextAuth>{children}</NextAuth>;
13
-
14
- return <NoAuth>{children}</NoAuth>;
22
+ // MarketAuthProvider 包装在内部 AuthProvider 之外
23
+ return (
24
+ <InnerAuthProvider>
25
+ <MarketAuthProvider isDesktop={isDesktop}>{children}</MarketAuthProvider>
26
+ </InnerAuthProvider>
27
+ );
15
28
  };
16
29
 
17
30
  export default AuthProvider;
@@ -42,8 +42,28 @@ export default {
42
42
  openingQuestions: '开场问题',
43
43
  title: '助手设定',
44
44
  },
45
+ version: {
46
+ empty: '暂无历史版本',
47
+ status: {
48
+ archived: '已归档',
49
+ deprecated: '已拒绝',
50
+ unpublished: '审核中',
51
+ },
52
+ table: {
53
+ isLatest: '最新版本',
54
+ isValidated: '已验证',
55
+ publishAt: '发布日期',
56
+ version: '版本号',
57
+ },
58
+ title: '版本历史',
59
+ },
45
60
  },
46
61
  list: '助手列表',
62
+ marketSource: {
63
+ label: '切换市场源',
64
+ legacy: '旧市场',
65
+ new: '新市场',
66
+ },
47
67
  more: '更多',
48
68
  plugins: '集成插件',
49
69
  recentSubmits: '最近更新',
@@ -51,10 +71,36 @@ export default {
51
71
  createdAt: '最近发布',
52
72
  identifier: '助手 ID',
53
73
  knowledgeCount: '知识库数量',
74
+ myown: '查看我的',
54
75
  pluginCount: '插件数量',
55
76
  title: '助手名称',
56
77
  tokenUsage: 'Token 使用量',
57
78
  },
79
+ status: {
80
+ archived: {
81
+ reasons: {
82
+ official: '助手有安全/政治等问题,被官方下架',
83
+ owner: '开发助手的 owner 主动下架/归档该助手',
84
+ },
85
+ subtitle: '当前访问的助手已经因为以下可能的原因被归档了:',
86
+ title: '助手已被归档',
87
+ },
88
+ backToMarket: '返回助手市场',
89
+ deprecated: {
90
+ reasons: {
91
+ official: '助手有安全/政治等问题,被官方下架',
92
+ owner: '开发助手的 owner 主动下架/拒绝该助手',
93
+ },
94
+ subtitle: '当前访问的助手已经因为以下可能的原因被拒绝了:',
95
+ title: '助手已被拒绝',
96
+ },
97
+ support: '有各种问题请复制链接发送到 <1>support@lobehub.com</1> 进行咨询。',
98
+ unpublished: {
99
+ subtitle:
100
+ '当前访问的助手正在进行版本审核中,如果有疑问复制链接发送问题到 <1>support@lobehub.com</1> 进行咨询。',
101
+ title: '助手正在审核中',
102
+ },
103
+ },
58
104
  suggestions: '相关推荐',
59
105
  systemRole: '助手设定',
60
106
  tokenUsage: '助手提示词 Token 使用量',
@@ -14,6 +14,7 @@ import hotkey from './hotkey';
14
14
  import image from './image';
15
15
  import knowledgeBase from './knowledgeBase';
16
16
  import labs from './labs';
17
+ import marketAuth from './marketAuth';
17
18
  import metadata from './metadata';
18
19
  import migration from './migration';
19
20
  import modelProvider from './modelProvider';
@@ -47,6 +48,7 @@ const resources = {
47
48
  image,
48
49
  knowledgeBase,
49
50
  labs,
51
+ marketAuth,
50
52
  metadata,
51
53
  migration,
52
54
  modelProvider,
@@ -0,0 +1,42 @@
1
+ export default {
2
+ callback: {
3
+ buttons: {
4
+ close: '关闭窗口',
5
+ },
6
+ messages: {
7
+ authFailed: '授权失败: {{error}}',
8
+ missingParams: '授权参数缺失',
9
+ processing: '正在处理授权...',
10
+ successWithCountdown: '{{message}} 窗口将在 {{countdown}} 秒后自动关闭',
11
+ successWithRedirect: '授权成功!正在跳转...',
12
+ },
13
+ titles: {
14
+ error: '授权失败',
15
+ loading: 'LobeHub Market 授权',
16
+ success: '授权成功',
17
+ },
18
+ },
19
+ errors: {
20
+ authorizationFailed: '授权失败,请重试。',
21
+ browserOnly: '授权流程只能在浏览器中发起。',
22
+ codeConsumed: '授权码已被使用,请重新尝试。',
23
+ codeVerifierMissing: '授权会话无效,请重新发起登录流程。',
24
+ general: '授权出现错误,请重试。',
25
+ handoffFailed: '无法获取授权结果,请重试。',
26
+ handoffTimeout: '授权超时,请在浏览器中完成操作后重试。',
27
+ oidcNotReady: '授权服务尚未就绪,请稍后再试。',
28
+ openBrowserFailed: '无法打开系统浏览器,请重试。',
29
+ openPopupFailed: '无法打开授权弹窗,请检查浏览器的弹窗拦截设置。',
30
+ popupClosed: '授权窗口未完成即被关闭。',
31
+ sessionExpired: '授权会话已过期,请重新登录。',
32
+ stateMismatch: '授权状态不匹配,请重试。',
33
+ stateMissing: '未找到授权状态,请重试。',
34
+ },
35
+ messages: {
36
+ loading: '正在启动授权流程...',
37
+ success: {
38
+ submit: '授权成功!现在可以发布助手了。',
39
+ upload: '授权成功!现在可以发布新版本了。',
40
+ },
41
+ },
42
+ };