@lobehub/lobehub 1.143.0-next.2 → 2.0.0-next.10

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 (244) hide show
  1. package/.github/workflows/desktop-pr-build.yml +12 -10
  2. package/.github/workflows/docker.yml +25 -20
  3. package/.github/workflows/e2e.yml +3 -3
  4. package/.github/workflows/release-desktop-beta.yml +8 -8
  5. package/.github/workflows/release.yml +1 -1
  6. package/.github/workflows/test.yml +4 -4
  7. package/CHANGELOG.md +265 -0
  8. package/README.md +7 -0
  9. package/README.zh-CN.md +7 -0
  10. package/apps/desktop/src/main/utils/next-electron-rsc.ts +7 -5
  11. package/changelog/v1.json +85 -0
  12. package/docs/development/database-schema.dbml +11 -1
  13. package/docs/self-hosting/advanced/auth/next-auth/auth0.mdx +2 -2
  14. package/docs/self-hosting/advanced/auth/next-auth/auth0.zh-CN.mdx +2 -2
  15. package/docs/self-hosting/advanced/auth/next-auth/authelia.mdx +2 -2
  16. package/docs/self-hosting/advanced/auth/next-auth/authelia.zh-CN.mdx +2 -2
  17. package/docs/self-hosting/advanced/auth/next-auth/authentik.mdx +2 -2
  18. package/docs/self-hosting/advanced/auth/next-auth/authentik.zh-CN.mdx +2 -2
  19. package/docs/self-hosting/advanced/auth/next-auth/casdoor.mdx +2 -2
  20. package/docs/self-hosting/advanced/auth/next-auth/casdoor.zh-CN.mdx +2 -2
  21. package/docs/self-hosting/advanced/auth/next-auth/cloudflare-zero-trust.mdx +2 -2
  22. package/docs/self-hosting/advanced/auth/next-auth/cloudflare-zero-trust.zh-CN.mdx +2 -2
  23. package/docs/self-hosting/advanced/auth/next-auth/github.mdx +2 -2
  24. package/docs/self-hosting/advanced/auth/next-auth/github.zh-CN.mdx +2 -2
  25. package/docs/self-hosting/advanced/auth/next-auth/google.mdx +32 -29
  26. package/docs/self-hosting/advanced/auth/next-auth/keycloak.mdx +2 -2
  27. package/docs/self-hosting/advanced/auth/next-auth/keycloak.zh-CN.mdx +2 -2
  28. package/docs/self-hosting/advanced/auth/next-auth/logto.mdx +5 -3
  29. package/docs/self-hosting/advanced/auth/next-auth/logto.zh-CN.mdx +5 -3
  30. package/docs/self-hosting/advanced/auth/next-auth/microsoft-entra-id.mdx +2 -2
  31. package/docs/self-hosting/advanced/auth/next-auth/microsoft-entra-id.zh-CN.mdx +2 -2
  32. package/docs/self-hosting/advanced/auth/next-auth/okta.mdx +2 -2
  33. package/docs/self-hosting/advanced/auth/next-auth/okta.zh-CN.mdx +2 -2
  34. package/docs/self-hosting/advanced/auth/next-auth/wechat.mdx +2 -2
  35. package/docs/self-hosting/advanced/auth/next-auth/wechat.zh-CN.mdx +2 -2
  36. package/docs/self-hosting/advanced/auth/next-auth/zitadel.mdx +2 -2
  37. package/docs/self-hosting/advanced/auth/next-auth/zitadel.zh-CN.mdx +2 -2
  38. package/docs/self-hosting/advanced/auth.mdx +32 -21
  39. package/docs/self-hosting/advanced/auth.zh-CN.mdx +30 -19
  40. package/docs/self-hosting/advanced/online-search.mdx +30 -25
  41. package/docs/self-hosting/advanced/online-search.zh-CN.mdx +25 -23
  42. package/locales/ar/models.json +15 -6
  43. package/locales/bg-BG/models.json +15 -6
  44. package/locales/de-DE/models.json +15 -6
  45. package/locales/en-US/models.json +15 -6
  46. package/locales/es-ES/models.json +15 -6
  47. package/locales/fa-IR/models.json +15 -6
  48. package/locales/fr-FR/models.json +15 -6
  49. package/locales/it-IT/models.json +15 -6
  50. package/locales/ja-JP/models.json +15 -6
  51. package/locales/ko-KR/models.json +15 -6
  52. package/locales/nl-NL/models.json +15 -6
  53. package/locales/pl-PL/models.json +15 -6
  54. package/locales/pt-BR/models.json +15 -6
  55. package/locales/ru-RU/models.json +15 -6
  56. package/locales/tr-TR/models.json +15 -6
  57. package/locales/vi-VN/models.json +15 -6
  58. package/locales/zh-CN/models.json +15 -6
  59. package/locales/zh-TW/models.json +15 -6
  60. package/next.config.ts +2 -3
  61. package/package.json +13 -19
  62. package/packages/const/src/index.ts +0 -1
  63. package/packages/const/src/models.ts +13 -0
  64. package/packages/const/src/url.ts +1 -4
  65. package/packages/context-engine/src/index.ts +1 -6
  66. package/packages/context-engine/src/processors/GroupMessageFlatten.ts +12 -2
  67. package/packages/context-engine/src/processors/__tests__/GroupMessageFlatten.test.ts +73 -9
  68. package/packages/context-engine/src/providers/index.ts +0 -2
  69. package/packages/database/migrations/0041_improve_index.sql +10 -0
  70. package/packages/database/migrations/meta/0041_snapshot.json +7784 -0
  71. package/packages/database/migrations/meta/_journal.json +7 -0
  72. package/packages/database/package.json +1 -1
  73. package/packages/database/src/core/migrations.json +17 -0
  74. package/packages/database/src/models/__tests__/message.grouping.test.ts +812 -0
  75. package/packages/database/src/models/__tests__/message.test.ts +322 -170
  76. package/packages/database/src/models/message.ts +62 -24
  77. package/packages/database/src/models/session.ts +60 -19
  78. package/packages/database/src/schemas/agent.ts +10 -11
  79. package/packages/database/src/schemas/message.ts +5 -1
  80. package/packages/database/src/schemas/relations.ts +6 -4
  81. package/packages/database/src/schemas/session.ts +2 -0
  82. package/packages/database/src/schemas/topic.ts +6 -1
  83. package/packages/database/src/utils/__tests__/groupMessages.test.ts +145 -2
  84. package/packages/database/src/utils/groupMessages.ts +7 -5
  85. package/packages/electron-client-ipc/package.json +4 -1
  86. package/packages/file-loaders/package.json +1 -0
  87. package/packages/model-bank/src/aiModels/anthropic.ts +0 -63
  88. package/packages/model-bank/src/aiModels/azure.ts +155 -0
  89. package/packages/model-bank/src/aiModels/bedrock.ts +44 -0
  90. package/packages/model-bank/src/aiModels/higress.ts +0 -55
  91. package/packages/model-bank/src/aiModels/infiniai.ts +21 -0
  92. package/packages/model-bank/src/aiModels/ollamacloud.ts +13 -0
  93. package/packages/model-bank/src/aiModels/siliconcloud.ts +19 -0
  94. package/packages/model-runtime/src/core/openaiCompatibleFactory/createImage.ts +1 -1
  95. package/packages/model-runtime/src/core/openaiCompatibleFactory/index.test.ts +33 -3
  96. package/packages/model-runtime/src/core/parameterResolver.ts +3 -0
  97. package/packages/model-runtime/src/core/streams/openai/__snapshots__/responsesStream.test.ts.snap +0 -38
  98. package/packages/model-runtime/src/providers/azureOpenai/index.ts +2 -1
  99. package/packages/model-runtime/src/providers/minimax/index.ts +5 -5
  100. package/packages/model-runtime/src/providers/search1api/index.test.ts +2 -2
  101. package/packages/types/src/message/common/base.ts +13 -0
  102. package/packages/types/src/message/common/image.ts +8 -0
  103. package/packages/types/src/message/common/metadata.ts +39 -0
  104. package/packages/types/src/message/common/tools.ts +10 -0
  105. package/packages/types/src/message/db/params.ts +47 -1
  106. package/packages/types/src/message/ui/chat.ts +4 -1
  107. package/packages/types/src/search.ts +16 -0
  108. package/packages/web-crawler/src/crawImpl/firecrawl.ts +39 -12
  109. package/scripts/migrateServerDB/index.ts +2 -1
  110. package/src/app/(backend)/oidc/consent/route.ts +0 -1
  111. package/src/app/(backend)/webapi/revalidate/route.ts +1 -1
  112. package/src/app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatInput/V1Mobile/index.tsx +2 -2
  113. package/src/app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatInput/V1Mobile/useSend.ts +6 -4
  114. package/src/app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatInput/useSend.ts +15 -10
  115. package/src/app/[variants]/(main)/chat/@session/features/SessionListContent/List/Item/index.tsx +4 -2
  116. package/src/app/[variants]/(main)/settings/_layout/SettingsContent.tsx +0 -3
  117. package/src/app/[variants]/layout.tsx +1 -0
  118. package/src/app/sitemap.tsx +7 -1
  119. package/src/components/Thinking/index.tsx +4 -3
  120. package/src/config/modelProviders/anthropic.ts +0 -23
  121. package/src/config/modelProviders/higress.ts +0 -23
  122. package/src/config/modelProviders/minimax.ts +1 -1
  123. package/src/config/modelProviders/qiniu.ts +1 -1
  124. package/src/envs/auth.ts +0 -179
  125. package/src/features/AgentSetting/AgentPlugin/index.tsx +21 -13
  126. package/src/features/ChatInput/ActionBar/STT/browser.tsx +2 -2
  127. package/src/features/ChatInput/ActionBar/STT/openai.tsx +2 -2
  128. package/src/features/ChatInput/ActionBar/Tools/useControls.tsx +1 -3
  129. package/src/features/Conversation/Error/ErrorJsonViewer.tsx +4 -3
  130. package/src/features/Conversation/Error/OllamaBizError/index.tsx +7 -2
  131. package/src/features/Conversation/Error/index.tsx +15 -5
  132. package/src/features/Conversation/MarkdownElements/LobeArtifact/Render/index.tsx +2 -2
  133. package/src/features/Conversation/Messages/Assistant/Extra/index.tsx +2 -2
  134. package/src/features/Conversation/Messages/Assistant/MessageContent.tsx +5 -3
  135. package/src/features/Conversation/Messages/Assistant/Tool/Inspector/BuiltinPluginTitle.tsx +2 -2
  136. package/src/features/Conversation/Messages/Assistant/Tool/Inspector/ToolTitle.tsx +4 -2
  137. package/src/features/Conversation/Messages/Assistant/Tool/Render/CustomRender.tsx +2 -2
  138. package/src/features/Conversation/Messages/Assistant/Tool/Render/index.tsx +2 -2
  139. package/src/features/Conversation/Messages/Assistant/Tool/index.tsx +2 -2
  140. package/src/features/Conversation/Messages/Assistant/index.tsx +4 -4
  141. package/src/features/Conversation/Messages/Default.tsx +2 -2
  142. package/src/features/Conversation/Messages/User/Extra.tsx +2 -2
  143. package/src/features/Conversation/Messages/User/index.tsx +4 -4
  144. package/src/features/Conversation/Messages/index.tsx +3 -3
  145. package/src/features/Conversation/components/AutoScroll.tsx +2 -2
  146. package/src/features/Conversation/components/Extras/Usage/UsageDetail/index.tsx +9 -6
  147. package/src/features/PluginTag/index.tsx +1 -3
  148. package/src/features/PluginsUI/Render/BuiltinType/index.test.tsx +37 -28
  149. package/src/features/Portal/Artifacts/Body/index.tsx +2 -2
  150. package/src/libs/next-auth/auth.config.ts +1 -1
  151. package/src/libs/next-auth/sso-providers/auth0.ts +0 -7
  152. package/src/libs/next-auth/sso-providers/authelia.ts +3 -5
  153. package/src/libs/next-auth/sso-providers/authentik.ts +0 -7
  154. package/src/libs/next-auth/sso-providers/cloudflare-zero-trust.ts +3 -6
  155. package/src/libs/next-auth/sso-providers/cognito.ts +1 -5
  156. package/src/libs/next-auth/sso-providers/generic-oidc.ts +3 -5
  157. package/src/libs/next-auth/sso-providers/github.ts +0 -6
  158. package/src/libs/next-auth/sso-providers/google.ts +0 -2
  159. package/src/libs/next-auth/sso-providers/index.ts +0 -2
  160. package/src/libs/next-auth/sso-providers/keycloak.ts +0 -3
  161. package/src/libs/next-auth/sso-providers/logto.ts +3 -5
  162. package/src/libs/next-auth/sso-providers/okta.ts +0 -4
  163. package/src/libs/next-auth/sso-providers/zitadel.ts +0 -7
  164. package/src/libs/oidc-provider/provider.ts +1 -1
  165. package/src/server/modules/AssistantStore/index.ts +1 -1
  166. package/src/server/modules/ModelRuntime/trace.ts +11 -4
  167. package/src/server/routers/lambda/message.ts +14 -3
  168. package/src/server/routers/lambda/session.ts +8 -5
  169. package/src/server/services/search/impls/firecrawl/index.ts +51 -11
  170. package/src/server/services/search/impls/firecrawl/type.ts +60 -9
  171. package/src/services/chat/chat.test.ts +1 -40
  172. package/src/services/chat/contextEngineering.test.ts +0 -30
  173. package/src/services/chat/contextEngineering.ts +1 -12
  174. package/src/services/chat/index.ts +2 -7
  175. package/src/services/chat/types.ts +1 -1
  176. package/src/services/message/_deprecated.ts +1 -1
  177. package/src/services/message/client.ts +8 -2
  178. package/src/services/message/server.ts +7 -2
  179. package/src/services/message/type.ts +6 -1
  180. package/src/services/user/client.test.ts +4 -1
  181. package/src/store/chat/helpers.test.ts +99 -0
  182. package/src/store/chat/helpers.ts +21 -2
  183. package/src/store/chat/selectors.ts +1 -1
  184. package/src/store/chat/slices/aiChat/actions/generateAIChat.ts +3 -3
  185. package/src/store/chat/slices/builtinTool/actions/index.ts +1 -4
  186. package/src/store/chat/slices/message/action.test.ts +5 -1
  187. package/src/store/chat/slices/message/action.ts +102 -14
  188. package/src/store/chat/slices/message/reducer.test.ts +363 -5
  189. package/src/store/chat/slices/message/reducer.ts +87 -3
  190. package/src/store/chat/slices/message/{selectors.test.ts → selectors/chat.test.ts} +266 -30
  191. package/src/store/chat/slices/message/{selectors.ts → selectors/chat.ts} +29 -79
  192. package/src/store/chat/slices/message/selectors/index.ts +2 -0
  193. package/src/store/chat/slices/message/selectors/messageState.test.ts +36 -0
  194. package/src/store/chat/slices/message/selectors/messageState.ts +80 -0
  195. package/src/store/chat/slices/plugin/action.test.ts +34 -132
  196. package/src/store/chat/slices/plugin/action.ts +1 -44
  197. package/src/store/tool/selectors/tool.test.ts +1 -1
  198. package/src/store/tool/selectors/tool.ts +6 -8
  199. package/src/store/tool/slices/builtin/action.test.ts +83 -35
  200. package/src/store/tool/slices/builtin/action.ts +0 -9
  201. package/src/store/tool/slices/builtin/selectors.test.ts +4 -30
  202. package/src/store/tool/slices/builtin/selectors.ts +15 -21
  203. package/src/tools/index.ts +0 -6
  204. package/src/tools/renders.ts +0 -3
  205. package/src/tools/web-browsing/Portal/Search/Footer.tsx +2 -2
  206. package/tsconfig.json +9 -2
  207. package/packages/const/src/guide.ts +0 -89
  208. package/packages/context-engine/src/providers/InboxGuide.ts +0 -102
  209. package/packages/context-engine/src/providers/__tests__/InboxGuideProvider.test.ts +0 -121
  210. package/src/app/[variants]/(main)/settings/llm/ProviderList/Azure/index.tsx +0 -93
  211. package/src/app/[variants]/(main)/settings/llm/ProviderList/Bedrock/index.tsx +0 -70
  212. package/src/app/[variants]/(main)/settings/llm/ProviderList/Cloudflare/index.tsx +0 -39
  213. package/src/app/[variants]/(main)/settings/llm/ProviderList/Github/index.tsx +0 -52
  214. package/src/app/[variants]/(main)/settings/llm/ProviderList/HuggingFace/index.tsx +0 -52
  215. package/src/app/[variants]/(main)/settings/llm/ProviderList/Ollama/index.tsx +0 -20
  216. package/src/app/[variants]/(main)/settings/llm/ProviderList/OpenAI/index.tsx +0 -17
  217. package/src/app/[variants]/(main)/settings/llm/ProviderList/providers.tsx +0 -132
  218. package/src/app/[variants]/(main)/settings/llm/components/Checker.tsx +0 -118
  219. package/src/app/[variants]/(main)/settings/llm/components/ProviderConfig/index.tsx +0 -303
  220. package/src/app/[variants]/(main)/settings/llm/components/ProviderModelList/CustomModelOption.tsx +0 -98
  221. package/src/app/[variants]/(main)/settings/llm/components/ProviderModelList/ModelConfigModal/Form.tsx +0 -104
  222. package/src/app/[variants]/(main)/settings/llm/components/ProviderModelList/ModelConfigModal/index.tsx +0 -77
  223. package/src/app/[variants]/(main)/settings/llm/components/ProviderModelList/ModelFetcher.tsx +0 -105
  224. package/src/app/[variants]/(main)/settings/llm/components/ProviderModelList/Option.tsx +0 -68
  225. package/src/app/[variants]/(main)/settings/llm/components/ProviderModelList/index.tsx +0 -146
  226. package/src/app/[variants]/(main)/settings/llm/const.ts +0 -20
  227. package/src/app/[variants]/(main)/settings/llm/features/Footer.tsx +0 -35
  228. package/src/app/[variants]/(main)/settings/llm/index.tsx +0 -30
  229. package/src/app/[variants]/(main)/settings/llm/type.ts +0 -5
  230. package/src/envs/__tests__/auth.test.ts +0 -200
  231. package/src/libs/next-auth/sso-providers/azure-ad.ts +0 -33
  232. package/src/services/chat/__snapshots__/chat.test.ts.snap +0 -110
  233. package/src/store/chat/slices/builtinTool/actions/__tests__/dalle.test.ts +0 -121
  234. package/src/store/chat/slices/builtinTool/actions/dalle.ts +0 -124
  235. package/src/tools/dalle/Render/GalleyGrid.tsx +0 -60
  236. package/src/tools/dalle/Render/Item/EditMode.tsx +0 -66
  237. package/src/tools/dalle/Render/Item/Error.tsx +0 -49
  238. package/src/tools/dalle/Render/Item/Image.tsx +0 -44
  239. package/src/tools/dalle/Render/Item/ImageFileItem.tsx +0 -57
  240. package/src/tools/dalle/Render/Item/index.tsx +0 -88
  241. package/src/tools/dalle/Render/ToolBar.tsx +0 -56
  242. package/src/tools/dalle/Render/index.tsx +0 -52
  243. package/src/tools/dalle/index.ts +0 -92
  244. /package/src/{middleware.ts → proxy.ts} +0 -0
@@ -15,7 +15,7 @@ import { useUserAvatar } from '@/hooks/useUserAvatar';
15
15
  import { useAgentStore } from '@/store/agent';
16
16
  import { agentChatConfigSelectors } from '@/store/agent/selectors';
17
17
  import { useChatStore } from '@/store/chat';
18
- import { chatSelectors } from '@/store/chat/selectors';
18
+ import { messageStateSelectors } from '@/store/chat/selectors';
19
19
  import { useSessionStore } from '@/store/session';
20
20
  import { sessionSelectors } from '@/store/session/selectors';
21
21
  import { useUserStore } from '@/store/user';
@@ -55,9 +55,9 @@ const UserMessage = memo<UserMessageProps>((props) => {
55
55
  const displayMode = useAgentStore(agentChatConfigSelectors.displayMode);
56
56
 
57
57
  const [editing, generating, isInRAGFlow] = useChatStore((s) => [
58
- chatSelectors.isMessageEditing(id)(s),
59
- chatSelectors.isMessageGenerating(id)(s),
60
- chatSelectors.isMessageInRAGFlow(id)(s),
58
+ messageStateSelectors.isMessageEditing(id)(s),
59
+ messageStateSelectors.isMessageGenerating(id)(s),
60
+ messageStateSelectors.isMessageInRAGFlow(id)(s),
61
61
  ]);
62
62
 
63
63
  const loading = isInRAGFlow || generating;
@@ -1,17 +1,17 @@
1
1
  'use client';
2
2
 
3
+ import { isDesktop } from '@lobechat/const';
3
4
  import { createStyles } from 'antd-style';
4
5
  import isEqual from 'fast-deep-equal';
5
6
  import { ReactNode, memo, useCallback, useEffect, useMemo, useRef } from 'react';
6
7
  import { Flexbox } from 'react-layout-kit';
7
8
 
8
- import { isDesktop } from '@/const/version';
9
9
  import {
10
10
  removeVirtuosoVisibleItem,
11
11
  upsertVirtuosoVisibleItem,
12
12
  } from '@/features/Conversation/components/VirtualizedList/VirtuosoContext';
13
13
  import { useChatStore } from '@/store/chat';
14
- import { chatSelectors } from '@/store/chat/selectors';
14
+ import { chatSelectors, messageStateSelectors } from '@/store/chat/selectors';
15
15
 
16
16
  import History from '../components/History';
17
17
  import { InPortalThreadContext } from '../context/InPortalThreadContext';
@@ -57,7 +57,7 @@ const Item = memo<ChatListItemProps>(
57
57
 
58
58
  const item = useChatStore(chatSelectors.getMessageById(id), isEqual);
59
59
 
60
- const [isMessageLoading] = useChatStore((s) => [chatSelectors.isMessageLoading(id)(s)]);
60
+ const [isMessageLoading] = useChatStore((s) => [messageStateSelectors.isMessageLoading(id)(s)]);
61
61
 
62
62
  // ======================= Performance Optimization ======================= //
63
63
  // these useMemo/useCallback are all for the performance optimization
@@ -1,7 +1,7 @@
1
1
  import { memo, useEffect } from 'react';
2
2
 
3
3
  import { useChatStore } from '@/store/chat';
4
- import { chatSelectors } from '@/store/chat/selectors';
4
+ import { chatSelectors, messageStateSelectors } from '@/store/chat/selectors';
5
5
 
6
6
  import BackBottom from './BackBottom';
7
7
 
@@ -11,7 +11,7 @@ interface AutoScrollProps {
11
11
  onScrollToBottom: (type: 'auto' | 'click') => void;
12
12
  }
13
13
  const AutoScroll = memo<AutoScrollProps>(({ atBottom, isScrolling, onScrollToBottom }) => {
14
- const trackVisibility = useChatStore(chatSelectors.isAIGenerating);
14
+ const trackVisibility = useChatStore(messageStateSelectors.isAIGenerating);
15
15
  const str = useChatStore(chatSelectors.mainAIChatsMessageString);
16
16
  const reasoningStr = useChatStore(chatSelectors.mainAILatestMessageReasoningContent);
17
17
 
@@ -11,7 +11,7 @@ import InfoTooltip from '@/components/InfoTooltip';
11
11
  import { aiModelSelectors, useAiInfraStore } from '@/store/aiInfra';
12
12
  import { useGlobalStore } from '@/store/global';
13
13
  import { systemStatusSelectors } from '@/store/global/selectors';
14
- import { formatNumber } from '@/utils/format';
14
+ import { formatNumber, formatShortenNumber } from '@/utils/format';
15
15
 
16
16
  import ModelCard from './ModelCard';
17
17
  import TokenProgress, { TokenProgressItem } from './TokenProgress';
@@ -111,10 +111,13 @@ const TokenDetail = memo<TokenDetailProps>(({ meta, model, provider }) => {
111
111
  },
112
112
  ].filter(Boolean) as TokenProgressItem[];
113
113
 
114
- const displayTotal =
114
+ const totalCount =
115
115
  isShowCredit && !!detailTokens.totalTokens
116
- ? formatNumber(detailTokens.totalTokens.credit)
117
- : formatNumber(detailTokens.totalTokens!.token);
116
+ ? detailTokens.totalTokens.credit
117
+ : detailTokens.totalTokens!.token;
118
+
119
+ const shortTotal = (formatShortenNumber(totalCount) as string).toLowerCase?.();
120
+ const detailTotal = formatNumber(totalCount);
118
121
 
119
122
  const averagePricing = formatNumber(
120
123
  detailTokens.totalTokens!.credit / detailTokens.totalTokens!.token,
@@ -171,7 +174,7 @@ const TokenDetail = memo<TokenDetailProps>(({ meta, model, provider }) => {
171
174
  <div style={{ color: theme.colorTextSecondary }}>
172
175
  {t('messages.tokenDetails.total')}
173
176
  </div>
174
- <div style={{ fontWeight: 500 }}>{displayTotal}</div>
177
+ <div style={{ fontWeight: 500 }}>{detailTotal}</div>
175
178
  </Flexbox>
176
179
  {isShowCredit && (
177
180
  <Flexbox align={'center'} gap={4} horizontal justify={'space-between'}>
@@ -212,7 +215,7 @@ const TokenDetail = memo<TokenDetailProps>(({ meta, model, provider }) => {
212
215
  >
213
216
  <Center gap={2} horizontal style={{ cursor: 'default' }}>
214
217
  <Icon icon={isShowCredit ? BadgeCent : CoinsIcon} />
215
- {displayTotal}
218
+ {shortTotal}
216
219
  </Center>
217
220
  </Popover>
218
221
  );
@@ -7,7 +7,6 @@ import { memo } from 'react';
7
7
  import { Center } from 'react-layout-kit';
8
8
 
9
9
  import Avatar from '@/components/Plugins/PluginAvatar';
10
- import { featureFlagsSelectors, useServerConfigStore } from '@/store/serverConfig';
11
10
  import { pluginHelpers, useToolStore } from '@/store/tool';
12
11
  import { toolSelectors } from '@/store/tool/selectors';
13
12
 
@@ -18,8 +17,7 @@ export interface PluginTagProps {
18
17
  }
19
18
 
20
19
  const PluginTag = memo<PluginTagProps>(({ plugins }) => {
21
- const { showDalle } = useServerConfigStore(featureFlagsSelectors);
22
- const list = useToolStore(toolSelectors.metaList(showDalle), isEqual);
20
+ const list = useToolStore(toolSelectors.metaList, isEqual);
23
21
 
24
22
  const displayPlugin = useToolStore(toolSelectors.getMetaById(plugins[0]), isEqual);
25
23
 
@@ -1,22 +1,19 @@
1
1
  import { render, screen } from '@testing-library/react';
2
2
  import { describe, expect, it, vi } from 'vitest';
3
3
 
4
- import { DalleManifest } from '@/tools/dalle';
5
- import { BuiltinToolsRenders } from '@/tools/renders';
6
-
7
4
  import BuiltinType from './index';
8
5
 
9
- // Mock Render component and useParseContent hook
6
+ // Mock renders module
10
7
  vi.mock('@/tools/renders', () => ({
11
8
  BuiltinToolsRenders: {
12
- dalle3: vi.fn(() => <div>Test Renderer</div>),
13
- [DalleManifest.identifier]: vi.fn(() => <div>{DalleManifest.identifier}</div>),
9
+ 'lobe-web-browsing': vi.fn(({ content }) => <div>WebBrowsingRender: {content}</div>),
10
+ 'lobe-code-interpreter': vi.fn(({ content }) => <div>CodeInterpreterRender: {content}</div>),
14
11
  },
15
12
  }));
16
13
 
17
- // Mock Loading component
18
- vi.mock('../Loading', () => ({
19
- default: vi.fn(() => <div>Loading...</div>),
14
+ // Mock useParseContent hook
15
+ vi.mock('../useParseContent', () => ({
16
+ useParseContent: vi.fn((content) => ({ data: content })),
20
17
  }));
21
18
 
22
19
  describe('BuiltinType', () => {
@@ -25,29 +22,41 @@ describe('BuiltinType', () => {
25
22
  expect(container).toBeEmptyDOMElement();
26
23
  });
27
24
 
28
- it('should not render anything if content is not JSON and no identifier', () => {
29
- const { container } = render(<BuiltinType content="..." id="123" />);
30
- expect(container).toBeEmptyDOMElement();
31
- });
32
-
33
25
  it('should not render anything if identifier is unknown', () => {
34
26
  const { container } = render(<BuiltinType content="{}" id="123" identifier="unknown" />);
35
27
  expect(container).toBeEmptyDOMElement();
36
28
  });
37
29
 
38
- describe('DALL·E', () => {
39
- it('should render the correct renderer if identifier is dalle3', () => {
40
- render(<BuiltinType content='{"some":"data"}' id="123" identifier="dalle3" />);
41
- expect(BuiltinToolsRenders.dalle3).toHaveBeenCalled();
42
- expect(screen.getByText('Test Renderer')).toBeInTheDocument();
43
- });
44
-
45
- it('should render the correct renderer if is DALL·E ', () => {
46
- render(
47
- <BuiltinType content='{"some":"data"}' id="123" identifier={DalleManifest.identifier} />,
48
- );
49
- expect(BuiltinToolsRenders.dalle3).toHaveBeenCalled();
50
- expect(screen.getByText(DalleManifest.identifier)).toBeInTheDocument();
51
- });
30
+ it('should render the correct renderer for web browsing', () => {
31
+ const content = '{"query":"test"}';
32
+ render(<BuiltinType content={content} id="123" identifier="lobe-web-browsing" />);
33
+ expect(screen.getByText(`WebBrowsingRender: ${content}`)).toBeInTheDocument();
34
+ });
35
+
36
+ it('should render the correct renderer for code interpreter', () => {
37
+ const content = '{"code":"print(1)"}';
38
+ render(<BuiltinType content={content} id="123" identifier="lobe-code-interpreter" />);
39
+ expect(screen.getByText(`CodeInterpreterRender: ${content}`)).toBeInTheDocument();
40
+ });
41
+
42
+ it('should pass correct props to renderer', () => {
43
+ const content = '{"test":"data"}';
44
+ const args = '{"arg":"value"}';
45
+ const pluginState = { state: 'value' };
46
+ const pluginError = { error: 'test' };
47
+
48
+ render(
49
+ <BuiltinType
50
+ content={content}
51
+ id="msg-123"
52
+ identifier="lobe-web-browsing"
53
+ arguments={args}
54
+ pluginState={pluginState}
55
+ pluginError={pluginError}
56
+ apiName="testApi"
57
+ />,
58
+ );
59
+
60
+ expect(screen.getByText(`WebBrowsingRender: ${content}`)).toBeInTheDocument();
52
61
  });
53
62
  });
@@ -3,7 +3,7 @@ import { memo, useEffect, useMemo } from 'react';
3
3
  import { Flexbox } from 'react-layout-kit';
4
4
 
5
5
  import { useChatStore } from '@/store/chat';
6
- import { chatPortalSelectors, chatSelectors } from '@/store/chat/selectors';
6
+ import { chatPortalSelectors, messageStateSelectors } from '@/store/chat/selectors';
7
7
  import { ArtifactDisplayMode } from '@/store/chat/slices/portal/initialState';
8
8
  import { ArtifactType } from '@/types/artifact';
9
9
 
@@ -24,7 +24,7 @@ const ArtifactsUI = memo(() => {
24
24
  return [
25
25
  messageId,
26
26
  s.portalArtifactDisplayMode,
27
- chatSelectors.isMessageGenerating(messageId)(s),
27
+ messageStateSelectors.isMessageGenerating(messageId)(s),
28
28
  chatPortalSelectors.artifactType(s),
29
29
  chatPortalSelectors.artifactCode(messageId)(s),
30
30
  chatPortalSelectors.artifactCodeLanguage(s),
@@ -58,7 +58,7 @@ export default {
58
58
  signIn: '/next-auth/signin',
59
59
  },
60
60
  providers: initSSOProviders(),
61
- secret: NEXT_AUTH_SECRET,
61
+ secret: NEXT_AUTH_SECRET ?? process.env.AUTH_SECRET,
62
62
  session: {
63
63
  // Force use JWT if server service is disabled
64
64
  strategy: NEXT_PUBLIC_ENABLED_SERVER_SERVICE ? NEXT_AUTH_SSO_SESSION_STRATEGY : 'jwt',
@@ -1,7 +1,5 @@
1
1
  import Auth0 from 'next-auth/providers/auth0';
2
2
 
3
- import { authEnv } from '@/envs/auth';
4
-
5
3
  import { CommonProviderConfig } from './sso.config';
6
4
 
7
5
  const provider = {
@@ -11,11 +9,6 @@ const provider = {
11
9
  // Specify auth scope, at least include 'openid email'
12
10
  // all scopes in Auth0 ref: https://auth0.com/docs/get-started/apis/scopes/openid-connect-scopes#standard-claims
13
11
  authorization: { params: { scope: 'openid email profile' } },
14
- // TODO(NextAuth ENVs Migration): Remove once nextauth envs migration time end
15
- clientId: authEnv.AUTH0_CLIENT_ID ?? process.env.AUTH_AUTH0_ID,
16
- clientSecret: authEnv.AUTH0_CLIENT_SECRET ?? process.env.AUTH_AUTH0_SECRET,
17
- issuer: authEnv.AUTH0_ISSUER ?? process.env.AUTH_AUTH0_ISSUER,
18
- // Remove End
19
12
  profile(profile) {
20
13
  return {
21
14
  email: profile.email,
@@ -1,7 +1,5 @@
1
1
  import type { OIDCConfig } from '@auth/core/providers';
2
2
 
3
- import { authEnv } from '@/envs/auth';
4
-
5
3
  import { CommonProviderConfig } from './sso.config';
6
4
 
7
5
  export type AutheliaProfile = {
@@ -21,10 +19,10 @@ const provider = {
21
19
  ...CommonProviderConfig,
22
20
  authorization: { params: { scope: 'openid email profile' } },
23
21
  checks: ['state', 'pkce'],
24
- clientId: authEnv.AUTHELIA_CLIENT_ID ?? process.env.AUTH_AUTHELIA_ID,
25
- clientSecret: authEnv.AUTHELIA_CLIENT_SECRET ?? process.env.AUTH_AUTHELIA_SECRET,
22
+ clientId: process.env.AUTH_AUTHELIA_ID,
23
+ clientSecret: process.env.AUTH_AUTHELIA_SECRET,
26
24
  id: 'authelia',
27
- issuer: authEnv.AUTHELIA_ISSUER ?? process.env.AUTH_AUTHELIA_ISSUER,
25
+ issuer: process.env.AUTH_AUTHELIA_ISSUER,
28
26
  name: 'Authelia',
29
27
  profile(profile) {
30
28
  return {
@@ -1,7 +1,5 @@
1
1
  import Authentik from 'next-auth/providers/authentik';
2
2
 
3
- import { authEnv } from '@/envs/auth';
4
-
5
3
  import { CommonProviderConfig } from './sso.config';
6
4
 
7
5
  const provider = {
@@ -11,11 +9,6 @@ const provider = {
11
9
  // Specify auth scope, at least include 'openid email'
12
10
  // all scopes in Authentik ref: https://goauthentik.io/docs/providers/oauth2
13
11
  authorization: { params: { scope: 'openid email profile' } },
14
- // TODO(NextAuth ENVs Migration): Remove once nextauth envs migration time end
15
- clientId: authEnv.AUTHENTIK_CLIENT_ID ?? process.env.AUTH_AUTHENTIK_ID,
16
- clientSecret: authEnv.AUTHENTIK_CLIENT_SECRET ?? process.env.AUTH_AUTHENTIK_SECRET,
17
- issuer: authEnv.AUTHENTIK_ISSUER ?? process.env.AUTH_AUTHENTIK_ISSUER,
18
- // Remove end
19
12
  // TODO(NextAuth): map unique user id to `providerAccountId` field
20
13
  // profile(profile) {
21
14
  // return {
@@ -1,7 +1,5 @@
1
1
  import type { OIDCConfig } from '@auth/core/providers';
2
2
 
3
- import { authEnv } from '@/envs/auth';
4
-
5
3
  import { CommonProviderConfig } from './sso.config';
6
4
 
7
5
  export type CloudflareZeroTrustProfile = {
@@ -16,11 +14,10 @@ const provider = {
16
14
  ...CommonProviderConfig,
17
15
  authorization: { params: { scope: 'openid email profile' } },
18
16
  checks: ['state', 'pkce'],
19
- clientId: authEnv.CLOUDFLARE_ZERO_TRUST_CLIENT_ID ?? process.env.AUTH_CLOUDFLARE_ZERO_TRUST_ID,
20
- clientSecret:
21
- authEnv.CLOUDFLARE_ZERO_TRUST_CLIENT_SECRET ?? process.env.AUTH_CLOUDFLARE_ZERO_TRUST_SECRET,
17
+ clientId: process.env.AUTH_CLOUDFLARE_ZERO_TRUST_ID,
18
+ clientSecret: process.env.AUTH_CLOUDFLARE_ZERO_TRUST_SECRET,
22
19
  id: 'cloudflare-zero-trust',
23
- issuer: authEnv.CLOUDFLARE_ZERO_TRUST_ISSUER ?? process.env.AUTH_CLOUDFLARE_ZERO_TRUST_ISSUER,
20
+ issuer: process.env.AUTH_CLOUDFLARE_ZERO_TRUST_ISSUER,
24
21
  name: 'Cloudflare Zero Trust',
25
22
  profile(profile) {
26
23
  return {
@@ -2,11 +2,7 @@ import Cognito from 'next-auth/providers/cognito';
2
2
 
3
3
  const provider = {
4
4
  id: 'cognito',
5
- provider: Cognito({
6
- clientId: process.env.AUTH_COGNITO_ID,
7
- clientSecret: process.env.AUTH_COGNITO_SECRET,
8
- issuer: process.env.AUTH_COGNITO_ISSUER,
9
- }),
5
+ provider: Cognito({}),
10
6
  };
11
7
 
12
8
  export default provider;
@@ -1,7 +1,5 @@
1
1
  import type { OIDCConfig } from '@auth/core/providers';
2
2
 
3
- import { authEnv } from '@/envs/auth';
4
-
5
3
  import { CommonProviderConfig } from './sso.config';
6
4
 
7
5
  export type GenericOIDCProfile = {
@@ -19,10 +17,10 @@ const provider = {
19
17
  ...CommonProviderConfig,
20
18
  authorization: { params: { scope: 'email openid profile' } },
21
19
  checks: ['state', 'pkce'],
22
- clientId: authEnv.GENERIC_OIDC_CLIENT_ID ?? process.env.AUTH_GENERIC_OIDC_ID,
23
- clientSecret: authEnv.GENERIC_OIDC_CLIENT_SECRET ?? process.env.AUTH_GENERIC_OIDC_SECRET,
20
+ clientId: process.env.AUTH_GENERIC_OIDC_ID,
21
+ clientSecret: process.env.AUTH_GENERIC_OIDC_SECRET,
24
22
  id: 'generic-oidc',
25
- issuer: authEnv.GENERIC_OIDC_ISSUER ?? process.env.AUTH_GENERIC_OIDC_ISSUER,
23
+ issuer: process.env.AUTH_GENERIC_OIDC_ISSUER,
26
24
  name: 'Generic OIDC',
27
25
  profile(profile) {
28
26
  return {
@@ -1,7 +1,5 @@
1
1
  import GitHub from 'next-auth/providers/github';
2
2
 
3
- import { authEnv } from '@/envs/auth';
4
-
5
3
  import { CommonProviderConfig } from './sso.config';
6
4
 
7
5
  const provider = {
@@ -10,10 +8,6 @@ const provider = {
10
8
  ...CommonProviderConfig,
11
9
  // Specify auth scope, at least include 'openid email'
12
10
  authorization: { params: { scope: 'read:user user:email' } },
13
- // TODO(NextAuth ENVs Migration): Remove once nextauth envs migration time end
14
- clientId: authEnv.GITHUB_CLIENT_ID ?? process.env.AUTH_GITHUB_ID,
15
- clientSecret: authEnv.GITHUB_CLIENT_SECRET ?? process.env.AUTH_GITHUB_SECRET,
16
- // Remove end
17
11
  profile: (profile) => {
18
12
  return {
19
13
  email: profile.email,
@@ -12,8 +12,6 @@ const provider = {
12
12
  'openid https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email openid',
13
13
  },
14
14
  },
15
- clientId: process.env.AUTH_GOOGLE_CLIENT_ID,
16
- clientSecret: process.env.AUTH_GOOGLE_CLIENT_SECRET,
17
15
  }),
18
16
  };
19
17
 
@@ -1,7 +1,6 @@
1
1
  import Auth0 from './auth0';
2
2
  import Authelia from './authelia';
3
3
  import Authentik from './authentik';
4
- import AzureAD from './azure-ad';
5
4
  import Casdoor from './casdoor';
6
5
  import CloudflareZeroTrust from './cloudflare-zero-trust';
7
6
  import Cognito from './cognito';
@@ -19,7 +18,6 @@ import Zitadel from './zitadel';
19
18
  export const ssoProviders = [
20
19
  Auth0,
21
20
  Authentik,
22
- AzureAD,
23
21
  GenericOIDC,
24
22
  Github,
25
23
  Zitadel,
@@ -8,9 +8,6 @@ const provider = {
8
8
  ...CommonProviderConfig,
9
9
  // Specify auth scope, at least include 'openid email'
10
10
  authorization: { params: { scope: 'openid email profile' } },
11
- clientId: process.env.AUTH_KEYCLOAK_ID,
12
- clientSecret: process.env.AUTH_KEYCLOAK_SECRET,
13
- issuer: process.env.AUTH_KEYCLOAK_ISSUER,
14
11
  profile(profile) {
15
12
  return {
16
13
  email: profile.email,
@@ -1,7 +1,5 @@
1
1
  import { OIDCConfig, OIDCUserConfig } from '@auth/core/providers';
2
2
 
3
- import { authEnv } from '@/envs/auth';
4
-
5
3
  import { CommonProviderConfig } from './sso.config';
6
4
 
7
5
  interface LogtoProfile extends Record<string, any> {
@@ -41,9 +39,9 @@ const provider = {
41
39
  },
42
40
  // You can get the issuer value from the Logto Application Details page,
43
41
  // in the field "Issuer endpoint"
44
- clientId: authEnv.LOGTO_CLIENT_ID ?? process.env.AUTH_LOGTO_ID,
45
- clientSecret: authEnv.LOGTO_CLIENT_SECRET ?? process.env.AUTH_LOGTO_SECRET,
46
- issuer: authEnv.LOGTO_ISSUER ?? process.env.AUTH_LOGTO_ISSUER,
42
+ clientId: process.env.AUTH_LOGTO_ID,
43
+ clientSecret: process.env.AUTH_LOGTO_SECRET,
44
+ issuer: process.env.AUTH_LOGTO_ISSUER,
47
45
  }),
48
46
  };
49
47
 
@@ -7,10 +7,6 @@ const provider = {
7
7
  provider: Okta({
8
8
  ...CommonProviderConfig,
9
9
  authorization: { params: { scope: 'openid email profile' } },
10
- clientId: process.env.AUTH_OKTA_ID,
11
- clientSecret: process.env.AUTH_OKTA_SECRET,
12
- issuer: process.env.AUTH_OKTA_ISSUER,
13
- // Remove End
14
10
  profile(profile) {
15
11
  return {
16
12
  email: profile.email,
@@ -1,17 +1,10 @@
1
1
  import Zitadel from 'next-auth/providers/zitadel';
2
2
 
3
- import { authEnv } from '@/envs/auth';
4
-
5
3
  const provider = {
6
4
  id: 'zitadel',
7
5
  provider: Zitadel({
8
6
  // Available scopes in ZITADEL: https://zitadel.com/docs/apis/openidoauth/scopes
9
7
  authorization: { params: { scope: 'openid email profile' } },
10
- // TODO(NextAuth ENVs Migration): Remove once nextauth envs migration time end
11
- clientId: authEnv.ZITADEL_CLIENT_ID ?? process.env.AUTH_ZITADEL_ID,
12
- clientSecret: authEnv.ZITADEL_CLIENT_SECRET ?? process.env.AUTH_ZITADEL_SECRET,
13
- issuer: authEnv.ZITADEL_ISSUER ?? process.env.AUTH_ZITADEL_ISSUER,
14
- // Remove end
15
8
  // TODO(NextAuth): map unique user id to `providerAccountId` field
16
9
  // profile(profile) {
17
10
  // return {
@@ -340,7 +340,7 @@ export const createOIDCProvider = async (db: LobeChatDatabase): Promise<Provider
340
340
 
341
341
  // 8. 令牌有效期
342
342
  ttl: {
343
- AccessToken: 25 * 3600, // 25 hour
343
+ AccessToken: 7 * 24 * 3600, // 7 days
344
344
  AuthorizationCode: 600, // 10 minutes
345
345
  DeviceCode: 600, // 10 minutes (if enabled)
346
346
 
@@ -93,7 +93,7 @@ export class AssistantStore {
93
93
  },
94
94
  });
95
95
  if (!res.ok) {
96
- res = await fetch(this.getAgentUrl(DEFAULT_LANG), {
96
+ res = await fetch(this.getAgentUrl(identifier, DEFAULT_LANG), {
97
97
  cache: 'force-cache',
98
98
  next: {
99
99
  revalidate: CacheRevalidate.Details,
@@ -42,6 +42,16 @@ export const createTraceOptions = (
42
42
  startTime: new Date(),
43
43
  });
44
44
 
45
+ const headers = new Headers();
46
+
47
+ if (trace?.id) {
48
+ headers.set(LOBE_CHAT_TRACE_ID, trace.id);
49
+ }
50
+
51
+ if (generation?.id) {
52
+ headers.set(LOBE_CHAT_OBSERVATION_ID, generation.id);
53
+ }
54
+
45
55
  return {
46
56
  callback: {
47
57
  onCompletion: async ({ text, thinking, usage, grounding, toolsCalling }) => {
@@ -94,9 +104,6 @@ export const createTraceOptions = (
94
104
  });
95
105
  },
96
106
  } as ChatStreamCallbacks,
97
- headers: {
98
- [LOBE_CHAT_OBSERVATION_ID]: generation?.id,
99
- [LOBE_CHAT_TRACE_ID]: trace?.id,
100
- },
107
+ headers: headers,
101
108
  };
102
109
  };
@@ -1,4 +1,9 @@
1
- import { BatchTaskResult, UIChatMessage, UpdateMessageRAGParamsSchema } from '@lobechat/types';
1
+ import {
2
+ BatchTaskResult,
3
+ UIChatMessage,
4
+ UpdateMessageParamsSchema,
5
+ UpdateMessageRAGParamsSchema,
6
+ } from '@lobechat/types';
2
7
  import { z } from 'zod';
3
8
 
4
9
  import { MessageModel } from '@/database/models/message';
@@ -180,11 +185,17 @@ export const messageRouter = router({
180
185
  .input(
181
186
  z.object({
182
187
  id: z.string(),
183
- value: z.object({}).passthrough().partial(),
188
+ sessionId: z.string().nullable().optional(),
189
+ topicId: z.string().nullable().optional(),
190
+ value: UpdateMessageParamsSchema,
184
191
  }),
185
192
  )
186
193
  .mutation(async ({ input, ctx }) => {
187
- return ctx.messageModel.update(input.id, input.value);
194
+ return ctx.messageModel.update(input.id, input.value as any, {
195
+ postProcessUrl: (path) => ctx.fileService.getFullFileUrl(path),
196
+ sessionId: input.sessionId,
197
+ topicId: input.topicId,
198
+ });
188
199
  }),
189
200
 
190
201
  updateMessagePlugin: messageProcedure
@@ -97,14 +97,17 @@ export const sessionRouter = router({
97
97
  }),
98
98
 
99
99
  getGroupedSessions: publicProcedure.query(async ({ ctx }): Promise<ChatSessionList> => {
100
- if (!ctx.userId) return { sessionGroups: [], sessions: [] };
100
+ const userId = ctx.userId;
101
+ if (!userId) return { sessionGroups: [], sessions: [] };
101
102
 
102
103
  const serverDB = await getServerDB();
103
- const sessionModel = new SessionModel(serverDB, ctx.userId!);
104
- const chatGroupModel = new ChatGroupModel(serverDB, ctx.userId!);
104
+ const sessionModel = new SessionModel(serverDB, userId);
105
+ const chatGroupModel = new ChatGroupModel(serverDB, userId);
105
106
 
106
- const { sessions, sessionGroups } = await sessionModel.queryWithGroups();
107
- const chatGroups = await chatGroupModel.queryWithMemberDetails();
107
+ const [{ sessions, sessionGroups }, chatGroups] = await Promise.all([
108
+ sessionModel.queryWithGroups(),
109
+ chatGroupModel.queryWithMemberDetails(),
110
+ ]);
108
111
 
109
112
  const groupSessions: LobeGroupSession[] = chatGroups.map((group) => {
110
113
  const { title, description, avatar, backgroundColor, groupId, ...rest } = group;