@lobehub/lobehub 2.0.0-next.35 → 2.0.0-next.37

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 (156) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/changelog/v1.json +18 -0
  3. package/next.config.ts +5 -6
  4. package/package.json +2 -2
  5. package/packages/agent-runtime/src/core/__tests__/runtime.test.ts +112 -77
  6. package/packages/agent-runtime/src/core/runtime.ts +63 -18
  7. package/packages/agent-runtime/src/types/generalAgent.ts +55 -0
  8. package/packages/agent-runtime/src/types/index.ts +1 -0
  9. package/packages/agent-runtime/src/types/instruction.ts +10 -3
  10. package/packages/const/src/user.ts +0 -1
  11. package/packages/context-engine/src/processors/GroupMessageFlatten.ts +8 -6
  12. package/packages/context-engine/src/processors/__tests__/GroupMessageFlatten.test.ts +12 -12
  13. package/packages/conversation-flow/src/__tests__/fixtures/inputs/branch/assistant-group-branches.json +249 -0
  14. package/packages/conversation-flow/src/__tests__/fixtures/inputs/branch/index.ts +4 -0
  15. package/packages/conversation-flow/src/__tests__/fixtures/inputs/branch/multi-assistant-group.json +260 -0
  16. package/packages/conversation-flow/src/__tests__/fixtures/outputs/branch/active-index-1.json +4 -0
  17. package/packages/conversation-flow/src/__tests__/fixtures/outputs/branch/assistant-group-branches.json +481 -0
  18. package/packages/conversation-flow/src/__tests__/fixtures/outputs/branch/conversation.json +5 -1
  19. package/packages/conversation-flow/src/__tests__/fixtures/outputs/branch/index.ts +4 -0
  20. package/packages/conversation-flow/src/__tests__/fixtures/outputs/branch/multi-assistant-group.json +407 -0
  21. package/packages/conversation-flow/src/__tests__/fixtures/outputs/branch/nested.json +18 -2
  22. package/packages/conversation-flow/src/__tests__/fixtures/outputs/complex-scenario.json +25 -3
  23. package/packages/conversation-flow/src/__tests__/parse.test.ts +12 -0
  24. package/packages/conversation-flow/src/index.ts +1 -1
  25. package/packages/conversation-flow/src/transformation/FlatListBuilder.ts +112 -34
  26. package/packages/conversation-flow/src/types/flatMessageList.ts +0 -12
  27. package/packages/conversation-flow/src/{types.ts → types/index.ts} +3 -14
  28. package/packages/database/src/models/__tests__/apiKey.test.ts +444 -0
  29. package/packages/database/src/models/message.ts +18 -19
  30. package/packages/types/src/aiChat.ts +2 -0
  31. package/packages/types/src/importer.ts +2 -2
  32. package/packages/types/src/message/ui/chat.ts +17 -1
  33. package/packages/types/src/message/ui/extra.ts +2 -2
  34. package/packages/types/src/message/ui/params.ts +2 -2
  35. package/packages/types/src/user/preference.ts +0 -4
  36. package/packages/utils/src/tokenizer/index.ts +3 -11
  37. package/src/app/[variants]/(main)/chat/components/conversation/features/ChatInput/Desktop/MessageFromUrl.tsx +3 -3
  38. package/src/app/[variants]/(main)/chat/components/conversation/features/ChatInput/V1Mobile/index.tsx +1 -1
  39. package/src/app/[variants]/(main)/chat/components/conversation/features/ChatInput/V1Mobile/useSend.ts +3 -3
  40. package/src/app/[variants]/(main)/chat/components/conversation/features/ChatInput/useSend.ts +6 -6
  41. package/src/app/[variants]/(main)/chat/components/conversation/features/ChatList/Content.tsx +5 -3
  42. package/src/app/[variants]/(main)/chat/components/conversation/features/ChatList/WelcomeChatItem/AgentWelcome/OpeningQuestions.tsx +2 -2
  43. package/src/app/[variants]/(main)/chat/components/conversation/features/ChatList/WelcomeChatItem/GroupWelcome/GroupUsageSuggest.tsx +2 -2
  44. package/src/app/[variants]/(main)/labs/page.tsx +0 -9
  45. package/src/features/ChatInput/ActionBar/STT/browser.tsx +3 -3
  46. package/src/features/ChatInput/ActionBar/STT/openai.tsx +3 -3
  47. package/src/features/Conversation/Error/AccessCodeForm.tsx +1 -1
  48. package/src/features/Conversation/Error/ChatInvalidApiKey.tsx +1 -1
  49. package/src/features/Conversation/Error/ClerkLogin/index.tsx +1 -1
  50. package/src/features/Conversation/Error/OAuthForm.tsx +1 -1
  51. package/src/features/Conversation/Error/index.tsx +0 -5
  52. package/src/features/Conversation/Messages/Assistant/Actions/index.tsx +13 -10
  53. package/src/features/Conversation/Messages/Assistant/Extra/index.test.tsx +3 -8
  54. package/src/features/Conversation/Messages/Assistant/Extra/index.tsx +2 -6
  55. package/src/features/Conversation/Messages/Assistant/MessageContent.tsx +7 -9
  56. package/src/features/Conversation/Messages/Assistant/Tool/Inspector/PluginResult.tsx +2 -2
  57. package/src/features/Conversation/Messages/Assistant/Tool/Inspector/PluginState.tsx +2 -2
  58. package/src/features/Conversation/Messages/Assistant/Tool/Render/PluginSettings.tsx +4 -1
  59. package/src/features/Conversation/Messages/Assistant/Tool/Render/index.tsx +2 -3
  60. package/src/features/Conversation/Messages/Assistant/index.tsx +57 -60
  61. package/src/features/Conversation/Messages/Default.tsx +1 -0
  62. package/src/features/Conversation/Messages/Group/Actions/WithContentId.tsx +38 -10
  63. package/src/features/Conversation/Messages/Group/Actions/index.tsx +1 -1
  64. package/src/features/Conversation/Messages/Group/ContentBlock.tsx +1 -3
  65. package/src/features/Conversation/Messages/Group/GroupChildren.tsx +12 -12
  66. package/src/features/Conversation/Messages/Group/MessageContent.tsx +7 -1
  67. package/src/features/Conversation/Messages/Group/Tool/Render/PluginSettings.tsx +1 -1
  68. package/src/features/Conversation/Messages/Group/index.tsx +2 -1
  69. package/src/features/Conversation/Messages/Supervisor/index.tsx +2 -2
  70. package/src/features/Conversation/Messages/User/{Actions.tsx → Actions/ActionsBar.tsx} +26 -25
  71. package/src/features/Conversation/Messages/User/Actions/MessageBranch.tsx +107 -0
  72. package/src/features/Conversation/Messages/User/Actions/index.tsx +42 -0
  73. package/src/features/Conversation/Messages/User/index.tsx +43 -44
  74. package/src/features/Conversation/Messages/index.tsx +3 -3
  75. package/src/features/Conversation/components/AutoScroll.tsx +3 -3
  76. package/src/features/Conversation/components/Extras/Usage/UsageDetail/AnimatedNumber.tsx +55 -0
  77. package/src/features/Conversation/components/Extras/Usage/UsageDetail/index.tsx +5 -2
  78. package/src/features/Conversation/components/VirtualizedList/index.tsx +29 -20
  79. package/src/features/Conversation/hooks/useChatListActionsBar.tsx +8 -10
  80. package/src/features/Portal/Thread/Chat/ChatInput/useSend.ts +3 -3
  81. package/src/hooks/useHotkeys/chatScope.ts +15 -7
  82. package/src/libs/trpc/client/lambda.ts +4 -3
  83. package/src/server/routers/lambda/__tests__/aiChat.test.ts +1 -1
  84. package/src/server/routers/lambda/__tests__/integration/message.integration.test.ts +0 -26
  85. package/src/server/routers/lambda/aiChat.ts +3 -2
  86. package/src/server/routers/lambda/message.ts +8 -16
  87. package/src/server/services/message/__tests__/index.test.ts +29 -39
  88. package/src/server/services/message/index.ts +41 -36
  89. package/src/services/electron/desktopNotification.ts +6 -6
  90. package/src/services/electron/file.ts +6 -6
  91. package/src/services/file/ClientS3/index.ts +8 -8
  92. package/src/services/message/__tests__/metadata-race-condition.test.ts +157 -0
  93. package/src/services/message/index.ts +21 -15
  94. package/src/services/upload.ts +11 -11
  95. package/src/services/utils/abortableRequest.test.ts +161 -0
  96. package/src/services/utils/abortableRequest.ts +67 -0
  97. package/src/store/chat/agents/GeneralChatAgent.ts +137 -0
  98. package/src/store/chat/agents/createAgentExecutors.ts +395 -0
  99. package/src/store/chat/helpers.test.ts +0 -99
  100. package/src/store/chat/helpers.ts +0 -11
  101. package/src/store/chat/slices/aiChat/actions/__tests__/conversationControl.test.ts +332 -0
  102. package/src/store/chat/slices/aiChat/actions/__tests__/conversationLifecycle.test.ts +257 -0
  103. package/src/store/chat/slices/aiChat/actions/__tests__/helpers.ts +11 -2
  104. package/src/store/chat/slices/aiChat/actions/__tests__/rag.test.ts +6 -6
  105. package/src/store/chat/slices/aiChat/actions/__tests__/streamingExecutor.test.ts +391 -0
  106. package/src/store/chat/slices/aiChat/actions/__tests__/streamingStates.test.ts +179 -0
  107. package/src/store/chat/slices/aiChat/actions/conversationControl.ts +157 -0
  108. package/src/store/chat/slices/aiChat/actions/conversationLifecycle.ts +329 -0
  109. package/src/store/chat/slices/aiChat/actions/generateAIGroupChat.ts +14 -14
  110. package/src/store/chat/slices/aiChat/actions/index.ts +12 -6
  111. package/src/store/chat/slices/aiChat/actions/rag.ts +9 -6
  112. package/src/store/chat/slices/aiChat/actions/streamingExecutor.ts +604 -0
  113. package/src/store/chat/slices/aiChat/actions/streamingStates.ts +84 -0
  114. package/src/store/chat/slices/builtinTool/actions/__tests__/localSystem.test.ts +4 -4
  115. package/src/store/chat/slices/builtinTool/actions/__tests__/search.test.ts +11 -11
  116. package/src/store/chat/slices/builtinTool/actions/interpreter.ts +8 -8
  117. package/src/store/chat/slices/builtinTool/actions/localSystem.ts +2 -2
  118. package/src/store/chat/slices/builtinTool/actions/search.ts +8 -8
  119. package/src/store/chat/slices/message/action.test.ts +79 -68
  120. package/src/store/chat/slices/message/actions/index.ts +39 -0
  121. package/src/store/chat/slices/message/actions/internals.ts +77 -0
  122. package/src/store/chat/slices/message/actions/optimisticUpdate.ts +260 -0
  123. package/src/store/chat/slices/message/actions/publicApi.ts +224 -0
  124. package/src/store/chat/slices/message/actions/query.ts +120 -0
  125. package/src/store/chat/slices/message/actions/runtimeState.ts +108 -0
  126. package/src/store/chat/slices/message/initialState.ts +13 -0
  127. package/src/store/chat/slices/message/reducer.test.ts +48 -370
  128. package/src/store/chat/slices/message/reducer.ts +17 -81
  129. package/src/store/chat/slices/message/selectors/chat.test.ts +13 -50
  130. package/src/store/chat/slices/message/selectors/chat.ts +78 -242
  131. package/src/store/chat/slices/message/selectors/dbMessage.ts +140 -0
  132. package/src/store/chat/slices/message/selectors/displayMessage.ts +301 -0
  133. package/src/store/chat/slices/message/selectors/messageState.ts +5 -2
  134. package/src/store/chat/slices/plugin/action.test.ts +62 -64
  135. package/src/store/chat/slices/plugin/action.ts +34 -28
  136. package/src/store/chat/slices/thread/action.test.ts +28 -31
  137. package/src/store/chat/slices/thread/action.ts +13 -10
  138. package/src/store/chat/slices/thread/selectors/index.ts +8 -6
  139. package/src/store/chat/slices/topic/reducer.ts +11 -3
  140. package/src/store/chat/store.ts +1 -1
  141. package/src/store/user/slices/preference/selectors/labPrefer.ts +0 -3
  142. package/packages/database/src/models/__tests__/message.grouping.test.ts +0 -812
  143. package/packages/database/src/utils/__tests__/groupMessages.test.ts +0 -1132
  144. package/packages/database/src/utils/groupMessages.ts +0 -361
  145. package/packages/utils/src/tokenizer/client.ts +0 -35
  146. package/packages/utils/src/tokenizer/estimated.ts +0 -4
  147. package/packages/utils/src/tokenizer/server.ts +0 -11
  148. package/packages/utils/src/tokenizer/tokenizer.worker.ts +0 -12
  149. package/src/app/(backend)/webapi/tokenizer/index.test.ts +0 -32
  150. package/src/app/(backend)/webapi/tokenizer/route.ts +0 -8
  151. package/src/features/Conversation/Error/InvalidAccessCode.tsx +0 -79
  152. package/src/store/chat/slices/aiChat/actions/__tests__/generateAIChat.test.ts +0 -975
  153. package/src/store/chat/slices/aiChat/actions/__tests__/generateAIChatV2.test.ts +0 -1050
  154. package/src/store/chat/slices/aiChat/actions/generateAIChat.ts +0 -720
  155. package/src/store/chat/slices/aiChat/actions/generateAIChatV2.ts +0 -849
  156. package/src/store/chat/slices/message/action.ts +0 -629
@@ -0,0 +1,42 @@
1
+ import { memo } from 'react';
2
+ import { Flexbox } from 'react-layout-kit';
3
+
4
+ import { useChatStore } from '@/store/chat';
5
+ import { messageStateSelectors } from '@/store/chat/selectors';
6
+ import { UIChatMessage } from '@/types/index';
7
+
8
+ import { UserActionsBar } from './ActionsBar';
9
+ import MessageBranch from './MessageBranch';
10
+
11
+ interface ActionsProps {
12
+ data: UIChatMessage;
13
+ disableEditing?: boolean;
14
+ id: string;
15
+ index: number;
16
+ }
17
+
18
+ const Actions = memo<ActionsProps>(({ id, data, index, disableEditing }) => {
19
+ const { branch } = data;
20
+ const [editing] = useChatStore((s) => [messageStateSelectors.isMessageEditing(id)(s)]);
21
+
22
+ return (
23
+ !editing && (
24
+ <Flexbox align={'center'} horizontal>
25
+ {!disableEditing && (
26
+ <Flexbox align={'flex-start'} role="menubar">
27
+ <UserActionsBar data={data} id={id} index={index} />
28
+ </Flexbox>
29
+ )}
30
+ {branch && (
31
+ <MessageBranch
32
+ activeBranchIndex={branch.activeBranchIndex}
33
+ count={branch.count}
34
+ messageId={id}
35
+ />
36
+ )}
37
+ </Flexbox>
38
+ )
39
+ );
40
+ });
41
+
42
+ export default Actions;
@@ -22,7 +22,7 @@ import { useUserStore } from '@/store/user';
22
22
  import { userProfileSelectors } from '@/store/user/selectors';
23
23
 
24
24
  import { useDoubleClickEdit } from '../../hooks/useDoubleClickEdit';
25
- import { UserActionsBar } from './Actions';
25
+ import Actions from './Actions';
26
26
  import { UserBelowMessage } from './BelowMessage';
27
27
  import { UserMessageExtra } from './Extra';
28
28
  import { MarkdownRender as UserMarkdownRender } from './MarkdownRender';
@@ -127,60 +127,59 @@ const UserMessage = memo<UserMessageProps>((props) => {
127
127
  );
128
128
 
129
129
  return (
130
- <Flexbox
131
- className={styles.container}
132
- direction={placement === 'left' ? 'horizontal' : 'horizontal-reverse'}
133
- gap={mobile ? 6 : 12}
134
- >
135
- <Avatar
136
- alt={title}
137
- avatar={{ avatar, title }}
138
- loading={loading}
139
- placement={placement}
140
- size={mobile ? 32 : undefined}
141
- style={{ marginTop: 6 }}
142
- />
130
+ <Flexbox className={styles.container} gap={8}>
143
131
  <Flexbox
144
- align={placement === 'left' ? 'flex-start' : 'flex-end'}
145
- className={styles.messageContainer}
132
+ direction={placement === 'left' ? 'horizontal' : 'horizontal-reverse'}
133
+ gap={mobile ? 6 : 12}
146
134
  >
147
- <Title
135
+ <Avatar
136
+ alt={title}
148
137
  avatar={{ avatar, title }}
138
+ loading={loading}
149
139
  placement={placement}
150
- showTitle={false}
151
- time={createdAt}
152
- titleAddon={dmIndicator}
140
+ size={32}
141
+ style={{ marginTop: 6 }}
153
142
  />
154
143
  <Flexbox
155
144
  align={placement === 'left' ? 'flex-start' : 'flex-end'}
156
- className={styles.messageContent}
157
- direction={placement === 'left' ? 'horizontal' : 'horizontal-reverse'}
158
- gap={8}
145
+ className={styles.messageContainer}
159
146
  >
160
- <Flexbox flex={1} style={{ maxWidth: '100%', minWidth: 0 }}>
161
- <MessageContent
162
- editing={editing}
163
- id={id}
164
- markdownProps={markdownProps}
165
- message={content}
166
- messageExtra={<UserMessageExtra content={content} extra={extra} id={id} />}
167
- onDoubleClick={onDoubleClick}
168
- placement={placement}
169
- primary
170
- renderMessage={renderMessage}
171
- variant={variant}
172
- />
173
- </Flexbox>
174
-
175
- {!disableEditing && !editing && (
176
- <Flexbox align={'flex-start'} className={styles.actions} role="menubar">
177
- <UserActionsBar data={props} id={id} index={index} />
147
+ <Title
148
+ avatar={{ avatar, title }}
149
+ placement={placement}
150
+ showTitle={false}
151
+ time={createdAt}
152
+ titleAddon={dmIndicator}
153
+ />
154
+ <Flexbox
155
+ align={placement === 'left' ? 'flex-start' : 'flex-end'}
156
+ className={styles.messageContent}
157
+ direction={placement === 'left' ? 'horizontal' : 'horizontal-reverse'}
158
+ gap={8}
159
+ >
160
+ <Flexbox flex={1} style={{ maxWidth: '100%', minWidth: 0 }}>
161
+ <MessageContent
162
+ editing={editing}
163
+ id={id}
164
+ markdownProps={markdownProps}
165
+ message={content}
166
+ messageExtra={<UserMessageExtra content={content} extra={extra} id={id} />}
167
+ onDoubleClick={onDoubleClick}
168
+ placement={placement}
169
+ primary
170
+ renderMessage={renderMessage}
171
+ variant={variant}
172
+ />
178
173
  </Flexbox>
179
- )}
174
+ </Flexbox>
175
+ <UserBelowMessage content={content} id={id} ragQuery={ragQuery} />
180
176
  </Flexbox>
181
- <UserBelowMessage content={content} id={id} ragQuery={ragQuery} />
177
+ {mobile && variant === 'bubble' && <BorderSpacing borderSpacing={32} />}
178
+ </Flexbox>
179
+
180
+ <Flexbox direction={'horizontal-reverse'}>
181
+ <Actions data={props} disableEditing={disableEditing} id={id} index={index} />
182
182
  </Flexbox>
183
- {mobile && variant === 'bubble' && <BorderSpacing borderSpacing={32} />}
184
183
  </Flexbox>
185
184
  );
186
185
  });
@@ -11,7 +11,7 @@ import {
11
11
  upsertVirtuosoVisibleItem,
12
12
  } from '@/features/Conversation/components/VirtualizedList/VirtuosoContext';
13
13
  import { useChatStore } from '@/store/chat';
14
- import { chatSelectors, messageStateSelectors } from '@/store/chat/selectors';
14
+ import { displayMessageSelectors, messageStateSelectors } from '@/store/chat/selectors';
15
15
 
16
16
  import History from '../components/History';
17
17
  import { InPortalThreadContext } from '../context/InPortalThreadContext';
@@ -56,7 +56,7 @@ const Item = memo<ChatListItemProps>(
56
56
  const { styles, cx } = useStyles();
57
57
  const containerRef = useRef<HTMLDivElement | null>(null);
58
58
 
59
- const item = useChatStore(chatSelectors.getMessageById(id), isEqual);
59
+ const item = useChatStore(displayMessageSelectors.getDisplayMessageById(id), isEqual);
60
60
 
61
61
  const [isMessageLoading] = useChatStore((s) => [messageStateSelectors.isMessageLoading(id)(s)]);
62
62
 
@@ -133,7 +133,7 @@ const Item = memo<ChatListItemProps>(
133
133
  );
134
134
  }
135
135
 
136
- case 'group': {
136
+ case 'assistantGroup': {
137
137
  return (
138
138
  <GroupMessage
139
139
  {...item}
@@ -1,7 +1,7 @@
1
1
  import { memo, useEffect } from 'react';
2
2
 
3
3
  import { useChatStore } from '@/store/chat';
4
- import { chatSelectors, messageStateSelectors } from '@/store/chat/selectors';
4
+ import { displayMessageSelectors, messageStateSelectors } from '@/store/chat/selectors';
5
5
 
6
6
  import BackBottom from './BackBottom';
7
7
 
@@ -12,8 +12,8 @@ interface AutoScrollProps {
12
12
  }
13
13
  const AutoScroll = memo<AutoScrollProps>(({ atBottom, isScrolling, onScrollToBottom }) => {
14
14
  const trackVisibility = useChatStore(messageStateSelectors.isAIGenerating);
15
- const str = useChatStore(chatSelectors.mainAIChatsMessageString);
16
- const reasoningStr = useChatStore(chatSelectors.mainAILatestMessageReasoningContent);
15
+ const str = useChatStore(displayMessageSelectors.mainAIChatsMessageString);
16
+ const reasoningStr = useChatStore(displayMessageSelectors.mainAILatestMessageReasoningContent);
17
17
 
18
18
  useEffect(() => {
19
19
  if (atBottom && trackVisibility && !isScrolling) {
@@ -0,0 +1,55 @@
1
+ import { memo, useEffect, useRef, useState } from 'react';
2
+
3
+ interface AnimatedNumberProps {
4
+ duration?: number;
5
+ formatter?: (value: number) => string;
6
+ value: number;
7
+ }
8
+
9
+ const AnimatedNumber = memo<AnimatedNumberProps>(({ value, duration = 3000, formatter }) => {
10
+ const [displayValue, setDisplayValue] = useState(value);
11
+ const frameRef = useRef<number>(undefined);
12
+ const startTimeRef = useRef<number>(undefined);
13
+ const startValueRef = useRef(value);
14
+
15
+ useEffect(() => {
16
+ const startValue = startValueRef.current;
17
+ const diff = value - startValue;
18
+
19
+ if (diff === 0) return;
20
+
21
+ const animate = (currentTime: number) => {
22
+ if (!startTimeRef.current) {
23
+ startTimeRef.current = currentTime;
24
+ }
25
+
26
+ const elapsed = currentTime - startTimeRef.current;
27
+ const progress = Math.min(elapsed / duration, 1);
28
+
29
+ // 使用 easeOutCubic 缓动函数
30
+ const easeProgress = 1 - (1 - progress) ** 3;
31
+ const current = startValue + diff * easeProgress;
32
+
33
+ setDisplayValue(current);
34
+
35
+ if (progress < 1) {
36
+ frameRef.current = requestAnimationFrame(animate);
37
+ } else {
38
+ startValueRef.current = value;
39
+ startTimeRef.current = undefined;
40
+ }
41
+ };
42
+
43
+ frameRef.current = requestAnimationFrame(animate);
44
+
45
+ return () => {
46
+ if (frameRef.current) {
47
+ cancelAnimationFrame(frameRef.current);
48
+ }
49
+ };
50
+ }, [value, duration]);
51
+
52
+ return formatter ? formatter(displayValue) : displayValue.toString();
53
+ });
54
+
55
+ export default AnimatedNumber;
@@ -13,6 +13,7 @@ import { useGlobalStore } from '@/store/global';
13
13
  import { systemStatusSelectors } from '@/store/global/selectors';
14
14
  import { formatNumber, formatShortenNumber } from '@/utils/format';
15
15
 
16
+ import AnimatedNumber from './AnimatedNumber';
16
17
  import ModelCard from './ModelCard';
17
18
  import TokenProgress, { TokenProgressItem } from './TokenProgress';
18
19
  import { getDetailsToken } from './tokens';
@@ -116,7 +117,6 @@ const TokenDetail = memo<TokenDetailProps>(({ meta, model, provider }) => {
116
117
  ? detailTokens.totalTokens.credit
117
118
  : detailTokens.totalTokens!.token;
118
119
 
119
- const shortTotal = (formatShortenNumber(totalCount) as string).toLowerCase?.();
120
120
  const detailTotal = formatNumber(totalCount);
121
121
 
122
122
  const averagePricing = formatNumber(
@@ -215,7 +215,10 @@ const TokenDetail = memo<TokenDetailProps>(({ meta, model, provider }) => {
215
215
  >
216
216
  <Center gap={2} horizontal style={{ cursor: 'default' }}>
217
217
  <Icon icon={isShowCredit ? BadgeCent : CoinsIcon} />
218
- {shortTotal}
218
+ <AnimatedNumber
219
+ formatter={(value) => (formatShortenNumber(value) as string).toLowerCase?.()}
220
+ value={totalCount}
221
+ />
219
222
  </Center>
220
223
  </Popover>
221
224
  );
@@ -1,16 +1,29 @@
1
1
  'use client';
2
2
 
3
- import { ReactNode, forwardRef, memo, useCallback, useEffect, useRef, useState } from 'react';
3
+ import {
4
+ ReactNode,
5
+ forwardRef,
6
+ memo,
7
+ useCallback,
8
+ useEffect,
9
+ useMemo,
10
+ useRef,
11
+ useState,
12
+ } from 'react';
4
13
  import { Flexbox } from 'react-layout-kit';
5
14
  import { Virtuoso, VirtuosoHandle } from 'react-virtuoso';
6
15
 
7
16
  import WideScreenContainer from '@/features/Conversation/components/WideScreenContainer';
8
17
  import { useChatStore } from '@/store/chat';
9
- import { chatSelectors } from '@/store/chat/selectors';
18
+ import { displayMessageSelectors } from '@/store/chat/selectors';
10
19
 
11
20
  import AutoScroll from '../AutoScroll';
12
21
  import SkeletonList from '../SkeletonList';
13
- import { VirtuosoContext, resetVirtuosoVisibleItems, setVirtuosoGlobalRef } from './VirtuosoContext';
22
+ import {
23
+ VirtuosoContext,
24
+ resetVirtuosoVisibleItems,
25
+ setVirtuosoGlobalRef,
26
+ } from './VirtuosoContext';
14
27
 
15
28
  interface VirtualizedListProps {
16
29
  dataSource: string[];
@@ -32,10 +45,9 @@ const VirtualizedList = memo<VirtualizedListProps>(({ mobile, dataSource, itemCo
32
45
  const [atBottom, setAtBottom] = useState(true);
33
46
  const [isScrolling, setIsScrolling] = useState(false);
34
47
 
35
- const [id, isFirstLoading, isCurrentChatLoaded] = useChatStore((s) => [
36
- chatSelectors.currentChatKey(s),
37
- chatSelectors.currentChatLoadingState(s),
38
- chatSelectors.isCurrentChatLoaded(s),
48
+ const [isFirstLoading, isCurrentChatLoaded] = useChatStore((s) => [
49
+ displayMessageSelectors.currentChatLoadingState(s),
50
+ displayMessageSelectors.isCurrentDisplayChatLoaded(s),
39
51
  ]);
40
52
 
41
53
  const getFollowOutput = useCallback(() => {
@@ -53,9 +65,8 @@ const VirtualizedList = memo<VirtualizedListProps>(({ mobile, dataSource, itemCo
53
65
  [atBottom],
54
66
  );
55
67
 
56
- useEffect(() => {
57
- scrollToBottom();
58
- }, [id]);
68
+ const components = useMemo(() => ({ List }), []);
69
+ const computeItemKey = useCallback((index: number, item: string) => item, []);
59
70
 
60
71
  useEffect(() => {
61
72
  setVirtuosoGlobalRef(virtuosoRef);
@@ -71,8 +82,8 @@ const VirtualizedList = memo<VirtualizedListProps>(({ mobile, dataSource, itemCo
71
82
  };
72
83
  }, []);
73
84
 
74
- // overscan should be 3 times the height of the window
75
- const overscan = typeof window !== 'undefined' ? window.innerHeight * 3 : 0;
85
+ // overscan should be 2 times the height of the window
86
+ const overscan = typeof window !== 'undefined' ? window.innerHeight * 2 : 0;
76
87
 
77
88
  // first time loading or not loaded
78
89
  if (isFirstLoading || !isCurrentChatLoaded) return <SkeletonList mobile={mobile} />;
@@ -81,12 +92,9 @@ const VirtualizedList = memo<VirtualizedListProps>(({ mobile, dataSource, itemCo
81
92
  <VirtuosoContext value={virtuosoRef}>
82
93
  <Virtuoso
83
94
  atBottomStateChange={setAtBottom}
84
- atBottomThreshold={50 * (mobile ? 2 : 1)}
85
- components={{
86
- List,
87
- }}
88
-
89
- computeItemKey={(_, item) => item}
95
+ atBottomThreshold={200 * (mobile ? 2 : 1)}
96
+ components={components}
97
+ computeItemKey={computeItemKey}
90
98
  data={dataSource}
91
99
  followOutput={getFollowOutput}
92
100
  increaseViewportBy={overscan}
@@ -108,13 +116,14 @@ const VirtualizedList = memo<VirtualizedListProps>(({ mobile, dataSource, itemCo
108
116
  atBottom={atBottom}
109
117
  isScrolling={isScrolling}
110
118
  onScrollToBottom={(type) => {
119
+ const virtuoso = virtuosoRef.current;
111
120
  switch (type) {
112
121
  case 'auto': {
113
- scrollToBottom();
122
+ virtuoso?.scrollToIndex({ align: 'end', behavior: 'auto', index: 'LAST' });
114
123
  break;
115
124
  }
116
125
  case 'click': {
117
- scrollToBottom('smooth');
126
+ virtuoso?.scrollToIndex({ align: 'end', behavior: 'smooth', index: 'LAST' });
118
127
  break;
119
128
  }
120
129
  }
@@ -15,7 +15,6 @@ import {
15
15
  import { useMemo } from 'react';
16
16
  import { useTranslation } from 'react-i18next';
17
17
 
18
- import { isDeprecatedEdition } from '@/const/version';
19
18
  import { localeOptions } from '@/locales/resources';
20
19
 
21
20
  const translateStyle = css`
@@ -41,18 +40,16 @@ interface ChatListActionsBar {
41
40
 
42
41
  export const useChatListActionsBar = ({
43
42
  hasThread,
44
- }: { hasThread?: boolean } = {}): ChatListActionsBar => {
43
+ isRegenerating,
44
+ }: { hasThread?: boolean; isRegenerating?: boolean } = {}): ChatListActionsBar => {
45
45
  const { t } = useTranslation(['common', 'chat']);
46
46
 
47
- return useMemo(
47
+ return useMemo<ChatListActionsBar>(
48
48
  () => ({
49
49
  branching: {
50
- disable: isDeprecatedEdition || undefined,
51
50
  icon: Split,
52
51
  key: 'branching',
53
- label: !isDeprecatedEdition
54
- ? t('branching', { defaultValue: 'Create Sub Topic' })
55
- : t('branchingDisable'),
52
+ label: t('branching', { defaultValue: 'Create Sub Topic' }),
56
53
  },
57
54
  copy: {
58
55
  icon: Copy,
@@ -61,13 +58,13 @@ export const useChatListActionsBar = ({
61
58
  },
62
59
  del: {
63
60
  danger: true,
64
- disable: hasThread || undefined,
61
+ disabled: hasThread,
65
62
  icon: Trash,
66
63
  key: 'del',
67
64
  label: hasThread ? t('messageAction.deleteDisabledByThreads', { ns: 'chat' }) : t('delete'),
68
65
  },
69
66
  delAndRegenerate: {
70
- disable: hasThread || undefined,
67
+ disabled: hasThread || isRegenerating,
71
68
  icon: ListRestart,
72
69
  key: 'delAndRegenerate',
73
70
  label: t('messageAction.delAndRegenerate', {
@@ -89,6 +86,7 @@ export const useChatListActionsBar = ({
89
86
  label: '导出为 PDF',
90
87
  },
91
88
  regenerate: {
89
+ disabled: isRegenerating,
92
90
  icon: RotateCcw,
93
91
  key: 'regenerate',
94
92
  label: t('regenerate', { defaultValue: 'Regenerate' }),
@@ -114,6 +112,6 @@ export const useChatListActionsBar = ({
114
112
  label: t('tts.action', { ns: 'chat' }),
115
113
  },
116
114
  }),
117
- [hasThread],
115
+ [hasThread, isRegenerating],
118
116
  );
119
117
  };
@@ -17,7 +17,7 @@ export const useSendThreadMessage = () => {
17
17
  const canNotSend = useChatStore(threadSelectors.isSendButtonDisabledByMessage);
18
18
  const generating = useChatStore((s) => threadSelectors.isThreadAIGenerating(s));
19
19
  const stop = useChatStore((s) => s.stopGenerateMessage);
20
- const [sendMessage, updateInputMessage] = useChatStore((s) => [
20
+ const [sendMessage, updateMessageInput] = useChatStore((s) => [
21
21
  s.sendThreadMessage,
22
22
  s.updateThreadInputMessage,
23
23
  ]);
@@ -54,11 +54,11 @@ export const useSendThreadMessage = () => {
54
54
 
55
55
  if (!shouldContinue) return;
56
56
 
57
- updateInputMessage(inputMessage);
57
+ updateMessageInput(inputMessage);
58
58
 
59
59
  sendMessage({ message: inputMessage, ...params });
60
60
 
61
- updateInputMessage('');
61
+ updateMessageInput('');
62
62
  threadInputEditor.clearContent();
63
63
  threadInputEditor.focus();
64
64
  };
@@ -7,7 +7,7 @@ import { useClearCurrentMessages } from '@/features/ChatInput/ActionBar/Clear';
7
7
  import { useOpenChatSettings } from '@/hooks/useInterceptingRoutes';
8
8
  import { useActionSWR } from '@/libs/swr';
9
9
  import { useChatStore } from '@/store/chat';
10
- import { chatSelectors } from '@/store/chat/selectors';
10
+ import { displayMessageSelectors } from '@/store/chat/selectors';
11
11
  import { useGlobalStore } from '@/store/global';
12
12
  import { systemStatusSelectors } from '@/store/global/selectors';
13
13
  import { HotkeyEnum, HotkeyScopeEnum } from '@/types/hotkey';
@@ -32,14 +32,22 @@ export const useOpenChatSettingsHotkey = () => {
32
32
  };
33
33
 
34
34
  export const useRegenerateMessageHotkey = () => {
35
- const regenerateMessage = useChatStore((s) => s.regenerateMessage);
36
- const lastMessage = useChatStore(chatSelectors.latestMessage, isEqual);
35
+ const [regenerateUserMessage, regenerateAssistantMessage] = useChatStore((s) => [
36
+ s.regenerateUserMessage,
37
+ s.regenerateAssistantMessage,
38
+ ]);
39
+ const lastMessage = useChatStore((s) => displayMessageSelectors.mainAIChats(s).at(-1), isEqual);
37
40
 
38
- const disable = !lastMessage || lastMessage.id === 'default' || lastMessage.role === 'system';
41
+ const disable = !lastMessage;
39
42
 
40
43
  return useHotkeyById(
41
44
  HotkeyEnum.RegenerateMessage,
42
- () => !disable && regenerateMessage(lastMessage.id),
45
+ () => {
46
+ if (!lastMessage) return;
47
+ if (lastMessage.role === 'user') return regenerateUserMessage(lastMessage.id);
48
+
49
+ return regenerateAssistantMessage(lastMessage.id);
50
+ },
43
51
  {
44
52
  enableOnContentEditable: true,
45
53
  enabled: !disable,
@@ -49,7 +57,7 @@ export const useRegenerateMessageHotkey = () => {
49
57
 
50
58
  export const useDeleteAndRegenerateMessageHotkey = () => {
51
59
  const delAndRegenerateMessage = useChatStore((s) => s.delAndRegenerateMessage);
52
- const lastMessage = useChatStore(chatSelectors.latestMessage, isEqual);
60
+ const lastMessage = useChatStore((s) => displayMessageSelectors.mainAIChats(s).at(-1), isEqual);
53
61
 
54
62
  const disable = !lastMessage || lastMessage.id === 'default' || lastMessage.role === 'system';
55
63
 
@@ -65,7 +73,7 @@ export const useDeleteAndRegenerateMessageHotkey = () => {
65
73
 
66
74
  export const useDeleteLastMessageHotkey = () => {
67
75
  const deleteMessage = useChatStore((s) => s.deleteMessage);
68
- const lastMessage = useChatStore(chatSelectors.latestMessage, isEqual);
76
+ const lastMessage = useChatStore((s) => displayMessageSelectors.mainAIChats(s).at(-1), isEqual);
69
77
 
70
78
  const disable = !lastMessage || lastMessage.id === 'default' || lastMessage.role === 'system';
71
79
 
@@ -84,7 +84,7 @@ const customHttpBatchLink = httpBatchLink({
84
84
  // dynamic import to avoid circular dependency
85
85
  const { createHeaderWithAuth } = await import('@/services/_auth');
86
86
 
87
- let provider: ModelProvider = ModelProvider.OpenAI;
87
+ let provider: ModelProvider | undefined;
88
88
  // for image page, we need to get the provider from the store
89
89
  log('Getting provider from store for image page: %s', location.pathname);
90
90
  if (location.pathname === '/image') {
@@ -96,8 +96,9 @@ const customHttpBatchLink = httpBatchLink({
96
96
  log('Getting provider from store for image page: %s', provider);
97
97
  }
98
98
 
99
- // TODO: we need to support provider select for chat page
100
- const headers = await createHeaderWithAuth({ provider });
99
+ // Only include provider in JWT for image operations
100
+ // For other operations (like knowledge base embedding), let server use its own config
101
+ const headers = await createHeaderWithAuth(provider ? { provider } : undefined);
101
102
  log('Headers: %O', headers);
102
103
  return headers;
103
104
  },
@@ -66,7 +66,7 @@ describe('aiChatRouter', () => {
66
66
  2,
67
67
  expect.objectContaining({
68
68
  content: expect.any(String),
69
- fromModel: 'gpt-4o',
69
+ model: 'gpt-4o',
70
70
  parentId: 'm-user',
71
71
  role: 'assistant',
72
72
  sessionId: 's1',
@@ -358,32 +358,6 @@ describe('Message Router Integration Tests', () => {
358
358
  expect(result).toHaveLength(1);
359
359
  expect(result[0].id).toBe(msg1.id);
360
360
  });
361
-
362
- it('should support useGroup parameter', async () => {
363
- const caller = messageRouter.createCaller(createTestContext(userId));
364
-
365
- // 创建多个消息
366
- await caller.createMessage({
367
- content: 'Message 1',
368
- role: 'assistant',
369
- sessionId: testSessionId,
370
- });
371
-
372
- await caller.createMessage({
373
- content: 'Message 2',
374
- role: 'assistant',
375
- sessionId: testSessionId,
376
- });
377
-
378
- // useGroup 参数应该影响消息分组展示
379
- const result = await caller.getMessages({
380
- sessionId: testSessionId,
381
- useGroup: true,
382
- });
383
-
384
- expect(result).toBeDefined();
385
- expect(Array.isArray(result)).toBe(true);
386
- });
387
361
  });
388
362
 
389
363
  describe('removeMessages', () => {
@@ -96,6 +96,7 @@ export const aiChatRouter = router({
96
96
  const userMessageItem = await ctx.messageModel.create({
97
97
  content: input.newUserMessage.content,
98
98
  files: input.newUserMessage.files,
99
+ parentId: input.newUserMessage.parentId,
99
100
  role: 'user',
100
101
  sessionId: input.sessionId!,
101
102
  threadId: input.threadId,
@@ -113,9 +114,9 @@ export const aiChatRouter = router({
113
114
  );
114
115
  const assistantMessageItem = await ctx.messageModel.create({
115
116
  content: LOADING_FLAT,
116
- fromModel: input.newAssistantMessage.model,
117
- fromProvider: input.newAssistantMessage.provider,
117
+ model: input.newAssistantMessage.model,
118
118
  parentId: messageId,
119
+ provider: input.newAssistantMessage.provider,
119
120
  role: 'assistant',
120
121
  sessionId: input.sessionId!,
121
122
  threadId: input.threadId,