@lobehub/lobehub 2.0.0-next.374 → 2.0.0-next.375

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 (27) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/changelog/v1.json +9 -0
  3. package/locales/en-US/chat.json +13 -13
  4. package/locales/zh-CN/chat.json +14 -14
  5. package/package.json +1 -1
  6. package/packages/builtin-tool-group-agent-builder/src/ExecutionRuntime/index.ts +31 -9
  7. package/packages/builtin-tool-group-agent-builder/src/client/Inspector/InviteAgent/index.tsx +28 -12
  8. package/packages/builtin-tool-group-agent-builder/src/client/Inspector/RemoveAgent/index.tsx +28 -12
  9. package/packages/builtin-tool-group-agent-builder/src/types.ts +8 -0
  10. package/packages/context-engine/src/engine/tools/ToolsEngine.ts +16 -10
  11. package/packages/context-engine/src/engine/tools/__tests__/ToolsEngine.test.ts +142 -0
  12. package/packages/context-engine/src/engine/tools/types.ts +6 -0
  13. package/src/features/Conversation/ChatItem/ChatItem.tsx +4 -7
  14. package/src/features/Conversation/ChatItem/style.ts +0 -3
  15. package/src/features/Conversation/ChatItem/type.ts +4 -1
  16. package/src/features/Conversation/Messages/Assistant/index.tsx +6 -2
  17. package/src/features/Conversation/Messages/AssistantGroup/index.tsx +3 -3
  18. package/src/features/Conversation/Messages/Supervisor/index.tsx +6 -2
  19. package/src/features/Conversation/Messages/Task/index.tsx +6 -2
  20. package/src/features/Conversation/Messages/components/useNewScreen.test.ts +331 -0
  21. package/src/features/Conversation/Messages/components/useNewScreen.ts +80 -5
  22. package/src/locales/default/chat.ts +13 -13
  23. package/src/services/chat/chat.test.ts +125 -45
  24. package/src/services/chat/index.ts +10 -14
  25. package/src/services/chat/mecha/agentConfigResolver.ts +8 -0
  26. package/src/store/chat/slices/aiChat/actions/__tests__/streamingExecutor.test.ts +29 -0
  27. package/src/store/chat/slices/aiChat/actions/streamingExecutor.ts +27 -19
@@ -39,7 +39,7 @@ const ChatItem = memo<ChatItemProps>(
39
39
  disabled = false,
40
40
  id,
41
41
  style,
42
- newScreen,
42
+ newScreenMinHeight,
43
43
  ...rest
44
44
  }) => {
45
45
  const isUser = placement === 'right';
@@ -63,15 +63,12 @@ const ChatItem = memo<ChatItemProps>(
63
63
  return (
64
64
  <Flexbox
65
65
  align={isUser ? 'flex-end' : 'flex-start'}
66
- className={cx(
67
- 'message-wrapper',
68
- styles.container,
69
- newScreen && styles.newScreen,
70
- className,
71
- )}
66
+ className={cx('message-wrapper', styles.container, className)}
67
+ data-message-id={id}
72
68
  gap={8}
73
69
  paddingBlock={8}
74
70
  style={{
71
+ minHeight: newScreenMinHeight,
75
72
  paddingInlineStart: isUser ? 36 : 0,
76
73
  ...style,
77
74
  }}
@@ -51,8 +51,5 @@ export const styles = createStaticStyles(({ css, cssVar }) => {
51
51
 
52
52
  background: ${cssVar.colorPrimary};
53
53
  `,
54
- newScreen: css`
55
- min-height: calc(-300px + 100dvh);
56
- `,
57
54
  };
58
55
  });
@@ -35,7 +35,10 @@ export interface ChatItemProps extends Omit<FlexboxProps, 'children' | 'onChange
35
35
  */
36
36
  message?: ReactNode;
37
37
  messageExtra?: ReactNode;
38
- newScreen?: boolean;
38
+ /**
39
+ * Dynamic min-height for new screen effect, e.g. "calc(100dvh - 350px)"
40
+ */
41
+ newScreenMinHeight?: string;
39
42
  onAvatarClick?: () => void;
40
43
  onDoubleClick?: DivProps['onDoubleClick'];
41
44
  /**
@@ -58,7 +58,11 @@ const AssistantMessage = memo<AssistantMessageProps>(
58
58
  const editing = useConversationStore(messageStateSelectors.isMessageEditing(id));
59
59
  const generating = useConversationStore(messageStateSelectors.isMessageGenerating(id));
60
60
  const creating = useConversationStore(messageStateSelectors.isMessageCreating(id));
61
- const newScreen = useNewScreen({ creating: creating || generating, isLatestItem });
61
+ const { minHeight } = useNewScreen({
62
+ creating: creating || generating,
63
+ isLatestItem,
64
+ messageId: id,
65
+ });
62
66
 
63
67
  const errorContent = useErrorContent(error);
64
68
 
@@ -114,7 +118,7 @@ const AssistantMessage = memo<AssistantMessageProps>(
114
118
  usage={usage! || metadata}
115
119
  />
116
120
  }
117
- newScreen={newScreen}
121
+ newScreenMinHeight={minHeight}
118
122
  onDoubleClick={onDoubleClick}
119
123
  onMouseEnter={onMouseEnter}
120
124
  placement={'left'}
@@ -2,13 +2,13 @@
2
2
 
3
3
  import type { AssistantContentBlock } from '@lobechat/types';
4
4
  import isEqual from 'fast-deep-equal';
5
- import dynamic from '@/libs/next/dynamic';
6
5
  import { type MouseEventHandler, Suspense, memo, useCallback, useMemo } from 'react';
7
6
 
8
7
  import { MESSAGE_ACTION_BAR_PORTAL_ATTRIBUTES } from '@/const/messageActionPortal';
9
8
  import { ChatItem } from '@/features/Conversation/ChatItem';
10
9
  import { useNewScreen } from '@/features/Conversation/Messages/components/useNewScreen';
11
10
  import { useOpenChatSettings } from '@/hooks/useInterceptingRoutes';
11
+ import dynamic from '@/libs/next/dynamic';
12
12
  import { useAgentStore } from '@/store/agent';
13
13
  import { builtinAgentSelectors } from '@/store/agent/selectors';
14
14
  import { useGlobalStore } from '@/store/global';
@@ -68,7 +68,7 @@ const GroupMessage = memo<GroupMessageProps>(({ id, index, disableEditing, isLat
68
68
  // Get editing state from ConversationStore
69
69
  const editing = useConversationStore(messageStateSelectors.isMessageEditing(contentId || ''));
70
70
  const creating = useConversationStore(messageStateSelectors.isMessageCreating(id));
71
- const newScreen = useNewScreen({ creating, isLatestItem });
71
+ const { minHeight } = useNewScreen({ creating, isLatestItem, messageId: id });
72
72
 
73
73
  const setMessageItemActionElementPortialContext = useSetMessageItemActionElementPortialContext();
74
74
  const setMessageItemActionTypeContext = useSetMessageItemActionTypeContext();
@@ -113,7 +113,7 @@ const GroupMessage = memo<GroupMessageProps>(({ id, index, disableEditing, isLat
113
113
  )
114
114
  }
115
115
  avatar={avatar}
116
- newScreen={newScreen}
116
+ newScreenMinHeight={minHeight}
117
117
  onAvatarClick={onAvatarClick}
118
118
  onMouseEnter={onMouseEnter}
119
119
  placement={'left'}
@@ -56,7 +56,11 @@ const GroupMessage = memo<GroupMessageProps>(({ id, index, disableEditing, isLat
56
56
  // Get editing state from ConversationStore
57
57
  const creating = useConversationStore(messageStateSelectors.isMessageCreating(id));
58
58
  const generating = useConversationStore(messageStateSelectors.isMessageGenerating(id));
59
- const newScreen = useNewScreen({ creating: creating || generating, isLatestItem });
59
+ const { minHeight } = useNewScreen({
60
+ creating: creating || generating,
61
+ isLatestItem,
62
+ messageId: id,
63
+ });
60
64
 
61
65
  const setMessageItemActionElementPortialContext = useSetMessageItemActionElementPortialContext();
62
66
  const setMessageItemActionTypeContext = useSetMessageItemActionTypeContext();
@@ -98,7 +102,7 @@ const GroupMessage = memo<GroupMessageProps>(({ id, index, disableEditing, isLat
98
102
  memberAvatars={memberAvatars}
99
103
  />
100
104
  )}
101
- newScreen={newScreen}
105
+ newScreenMinHeight={minHeight}
102
106
  onMouseEnter={onMouseEnter}
103
107
  placement={'left'}
104
108
  showTitle
@@ -44,7 +44,11 @@ const TaskMessage = memo<TaskMessageProps>(({ id, index, disableEditing, isLates
44
44
  const editing = useConversationStore(messageStateSelectors.isMessageEditing(id));
45
45
  const generating = useConversationStore(messageStateSelectors.isMessageGenerating(id));
46
46
  const creating = useConversationStore(messageStateSelectors.isMessageCreating(id));
47
- const newScreen = useNewScreen({ creating: generating || creating, isLatestItem });
47
+ const { minHeight } = useNewScreen({
48
+ creating: generating || creating,
49
+ isLatestItem,
50
+ messageId: id,
51
+ });
48
52
 
49
53
  const errorContent = useErrorContent(error);
50
54
 
@@ -84,7 +88,7 @@ const TaskMessage = memo<TaskMessageProps>(({ id, index, disableEditing, isLates
84
88
  id={id}
85
89
  loading={generating}
86
90
  message={message}
87
- newScreen={newScreen}
91
+ newScreenMinHeight={minHeight}
88
92
  onAvatarClick={onAvatarClick}
89
93
  onDoubleClick={onDoubleClick}
90
94
  placement={'left'}
@@ -0,0 +1,331 @@
1
+ import { renderHook } from '@testing-library/react';
2
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
3
+
4
+ import { useNewScreen } from './useNewScreen';
5
+
6
+ // Mock useConversationStore
7
+ const mockGetViewportSize = vi.fn();
8
+ vi.mock('../../store', () => ({
9
+ useConversationStore: vi.fn((selector) =>
10
+ selector({
11
+ virtuaScrollMethods: {
12
+ getViewportSize: mockGetViewportSize,
13
+ },
14
+ }),
15
+ ),
16
+ }));
17
+
18
+ describe('useNewScreen', () => {
19
+ beforeEach(() => {
20
+ vi.clearAllMocks();
21
+ mockGetViewportSize.mockReturnValue(800);
22
+ });
23
+
24
+ describe('when not the latest item', () => {
25
+ it('should return undefined minHeight', () => {
26
+ const { result } = renderHook(() =>
27
+ useNewScreen({
28
+ creating: true,
29
+ isLatestItem: false,
30
+ messageId: 'msg-1',
31
+ }),
32
+ );
33
+
34
+ expect(result.current.minHeight).toBeUndefined();
35
+ });
36
+
37
+ it('should clear minHeight when becoming not the latest item', () => {
38
+ const { result, rerender } = renderHook(
39
+ ({ isLatestItem }) =>
40
+ useNewScreen({
41
+ creating: true,
42
+ isLatestItem,
43
+ messageId: 'msg-1',
44
+ }),
45
+ {
46
+ initialProps: { isLatestItem: true },
47
+ },
48
+ );
49
+
50
+ // Initially, it will use fallback since no DOM elements exist
51
+ expect(result.current.minHeight).toBeDefined();
52
+
53
+ // When it's no longer the latest item
54
+ rerender({ isLatestItem: false });
55
+
56
+ expect(result.current.minHeight).toBeUndefined();
57
+ });
58
+ });
59
+
60
+ describe('when latest item but not creating', () => {
61
+ it('should not update minHeight (preserve existing value)', () => {
62
+ const { result, rerender } = renderHook(
63
+ ({ creating }) =>
64
+ useNewScreen({
65
+ creating,
66
+ isLatestItem: true,
67
+ messageId: 'msg-1',
68
+ }),
69
+ {
70
+ initialProps: { creating: true },
71
+ },
72
+ );
73
+
74
+ // Initially sets minHeight (fallback)
75
+ const initialMinHeight = result.current.minHeight;
76
+ expect(initialMinHeight).toBeDefined();
77
+
78
+ // When creating ends, should preserve the minHeight
79
+ rerender({ creating: false });
80
+
81
+ expect(result.current.minHeight).toBe(initialMinHeight);
82
+ });
83
+ });
84
+
85
+ describe('when latest item and creating', () => {
86
+ it('should calculate minHeight based on viewport and previous message height', () => {
87
+ // Setup DOM mocks
88
+ const mockPrevMessageEl = {
89
+ getBoundingClientRect: () => ({ height: 150 }),
90
+ };
91
+ const mockPrevWrapper = {
92
+ querySelector: vi.fn().mockReturnValue(mockPrevMessageEl),
93
+ getBoundingClientRect: () => ({ height: 150 }),
94
+ };
95
+ const mockCurrentWrapper = {
96
+ dataset: { index: '1' },
97
+ };
98
+ const mockMessageEl = {
99
+ closest: vi.fn().mockReturnValue(mockCurrentWrapper),
100
+ };
101
+
102
+ vi.spyOn(document, 'querySelector').mockImplementation((selector: string) => {
103
+ if (selector === '[data-message-id="msg-1"]') {
104
+ return mockMessageEl as unknown as Element;
105
+ }
106
+ if (selector === '[data-index="0"]') {
107
+ return mockPrevWrapper as unknown as Element;
108
+ }
109
+ return null;
110
+ });
111
+
112
+ mockGetViewportSize.mockReturnValue(800);
113
+
114
+ const { result } = renderHook(() =>
115
+ useNewScreen({
116
+ creating: true,
117
+ isLatestItem: true,
118
+ messageId: 'msg-1',
119
+ }),
120
+ );
121
+
122
+ // minHeight = viewportHeight - prevHeight - EXTRA_PADDING = 800 - 150 - 0 = 650
123
+ expect(result.current.minHeight).toBe('650px');
124
+ });
125
+
126
+ it('should use fallback height when previous message element not found', () => {
127
+ // Setup DOM mocks - no previous element
128
+ const mockCurrentWrapper = {
129
+ dataset: { index: '0' },
130
+ querySelector: vi.fn().mockReturnValue(null),
131
+ };
132
+ const mockMessageEl = {
133
+ closest: vi.fn().mockReturnValue(mockCurrentWrapper),
134
+ };
135
+
136
+ vi.spyOn(document, 'querySelector').mockImplementation((selector: string) => {
137
+ if (selector === '[data-message-id="msg-1"]') {
138
+ return mockMessageEl as unknown as Element;
139
+ }
140
+ return null;
141
+ });
142
+
143
+ mockGetViewportSize.mockReturnValue(800);
144
+
145
+ const { result } = renderHook(() =>
146
+ useNewScreen({
147
+ creating: true,
148
+ isLatestItem: true,
149
+ messageId: 'msg-1',
150
+ }),
151
+ );
152
+
153
+ // fallback: viewportHeight - DEFAULT_USER_MESSAGE_HEIGHT - EXTRA_PADDING = 800 - 200 - 0 = 600
154
+ expect(result.current.minHeight).toBe('600px');
155
+ });
156
+
157
+ it('should return undefined when calculated height is less than or equal to 0', () => {
158
+ // Setup DOM mocks with very large previous message
159
+ const mockPrevMessageEl = {
160
+ getBoundingClientRect: () => ({ height: 900 }), // Larger than viewport
161
+ };
162
+ const mockPrevWrapper = {
163
+ querySelector: vi.fn().mockReturnValue(mockPrevMessageEl),
164
+ getBoundingClientRect: () => ({ height: 900 }),
165
+ };
166
+ const mockCurrentWrapper = {
167
+ dataset: { index: '1' },
168
+ };
169
+ const mockMessageEl = {
170
+ closest: vi.fn().mockReturnValue(mockCurrentWrapper),
171
+ };
172
+
173
+ vi.spyOn(document, 'querySelector').mockImplementation((selector: string) => {
174
+ if (selector === '[data-message-id="msg-1"]') {
175
+ return mockMessageEl as unknown as Element;
176
+ }
177
+ if (selector === '[data-index="0"]') {
178
+ return mockPrevWrapper as unknown as Element;
179
+ }
180
+ return null;
181
+ });
182
+
183
+ mockGetViewportSize.mockReturnValue(800);
184
+
185
+ const { result } = renderHook(() =>
186
+ useNewScreen({
187
+ creating: true,
188
+ isLatestItem: true,
189
+ messageId: 'msg-1',
190
+ }),
191
+ );
192
+
193
+ // minHeight = 800 - 900 - 0 = -100, should be undefined
194
+ expect(result.current.minHeight).toBeUndefined();
195
+ });
196
+
197
+ it('should use window.innerHeight when virtuaScrollMethods is not available', () => {
198
+ // Reset mock to return undefined for virtuaScrollMethods
199
+ vi.mocked(mockGetViewportSize).mockReturnValue(undefined as unknown as number);
200
+
201
+ // Mock window.innerHeight
202
+ Object.defineProperty(window, 'innerHeight', { value: 768, writable: true });
203
+
204
+ // Setup DOM mocks - no previous element (fallback case)
205
+ const mockCurrentWrapper = {
206
+ dataset: { index: '0' },
207
+ querySelector: vi.fn().mockReturnValue(null),
208
+ };
209
+ const mockMessageEl = {
210
+ closest: vi.fn().mockReturnValue(mockCurrentWrapper),
211
+ };
212
+
213
+ vi.spyOn(document, 'querySelector').mockImplementation((selector: string) => {
214
+ if (selector === '[data-message-id="msg-1"]') {
215
+ return mockMessageEl as unknown as Element;
216
+ }
217
+ return null;
218
+ });
219
+
220
+ const { result } = renderHook(() =>
221
+ useNewScreen({
222
+ creating: true,
223
+ isLatestItem: true,
224
+ messageId: 'msg-1',
225
+ }),
226
+ );
227
+
228
+ // fallback: window.innerHeight - DEFAULT_USER_MESSAGE_HEIGHT = 768 - 200 = 568
229
+ expect(result.current.minHeight).toBe('568px');
230
+ });
231
+ });
232
+
233
+ describe('edge cases', () => {
234
+ it('should handle message element not found', () => {
235
+ vi.spyOn(document, 'querySelector').mockReturnValue(null);
236
+
237
+ const { result } = renderHook(() =>
238
+ useNewScreen({
239
+ creating: true,
240
+ isLatestItem: true,
241
+ messageId: 'non-existent',
242
+ }),
243
+ );
244
+
245
+ // Should use fallback
246
+ expect(result.current.minHeight).toBeDefined();
247
+ });
248
+
249
+ it('should handle negative prevIndex', () => {
250
+ const mockCurrentWrapper = {
251
+ dataset: { index: '0' }, // First item, prevIndex would be -1
252
+ querySelector: vi.fn().mockReturnValue(null),
253
+ };
254
+ const mockMessageEl = {
255
+ closest: vi.fn().mockReturnValue(mockCurrentWrapper),
256
+ };
257
+
258
+ vi.spyOn(document, 'querySelector').mockImplementation((selector: string) => {
259
+ if (selector === '[data-message-id="msg-1"]') {
260
+ return mockMessageEl as unknown as Element;
261
+ }
262
+ // Should not query for [data-index="-1"]
263
+ if (selector === '[data-index="-1"]') {
264
+ throw new Error('Should not query for negative index');
265
+ }
266
+ return null;
267
+ });
268
+
269
+ mockGetViewportSize.mockReturnValue(800);
270
+
271
+ const { result } = renderHook(() =>
272
+ useNewScreen({
273
+ creating: true,
274
+ isLatestItem: true,
275
+ messageId: 'msg-1',
276
+ }),
277
+ );
278
+
279
+ // Should use fallback without throwing
280
+ expect(result.current.minHeight).toBe('600px');
281
+ });
282
+
283
+ it('should recalculate when messageId changes', () => {
284
+ const mockPrevMessageEl = {
285
+ getBoundingClientRect: () => ({ height: 150 }),
286
+ };
287
+ const mockPrevWrapper = {
288
+ querySelector: vi.fn().mockReturnValue(mockPrevMessageEl),
289
+ getBoundingClientRect: () => ({ height: 150 }),
290
+ };
291
+ const mockCurrentWrapper = {
292
+ dataset: { index: '1' },
293
+ };
294
+ const mockMessageEl = {
295
+ closest: vi.fn().mockReturnValue(mockCurrentWrapper),
296
+ };
297
+
298
+ vi.spyOn(document, 'querySelector').mockImplementation((selector: string) => {
299
+ if (selector.includes('data-message-id')) {
300
+ return mockMessageEl as unknown as Element;
301
+ }
302
+ if (selector === '[data-index="0"]') {
303
+ return mockPrevWrapper as unknown as Element;
304
+ }
305
+ return null;
306
+ });
307
+
308
+ mockGetViewportSize.mockReturnValue(800);
309
+
310
+ const { result, rerender } = renderHook(
311
+ ({ messageId }) =>
312
+ useNewScreen({
313
+ creating: true,
314
+ isLatestItem: true,
315
+ messageId,
316
+ }),
317
+ {
318
+ initialProps: { messageId: 'msg-1' },
319
+ },
320
+ );
321
+
322
+ expect(result.current.minHeight).toBe('650px');
323
+
324
+ // Change messageId - should recalculate
325
+ rerender({ messageId: 'msg-2' });
326
+
327
+ // Still the same value since mocks return same values
328
+ expect(result.current.minHeight).toBe('650px');
329
+ });
330
+ });
331
+ });
@@ -1,16 +1,91 @@
1
+ import debug from 'debug';
1
2
  import { useEffect, useState } from 'react';
2
3
 
4
+ import { useConversationStore } from '../../store';
5
+
6
+ const log = debug('lobe-render:Conversation:newScreen');
7
+
8
+ /**
9
+ * Extra padding if needed
10
+ */
11
+ const EXTRA_PADDING = 0;
12
+
13
+ /**
14
+ * Default user message height (fallback)
15
+ */
16
+ const DEFAULT_USER_MESSAGE_HEIGHT = 200;
17
+
3
18
  export const useNewScreen = ({
4
19
  isLatestItem,
5
20
  creating,
21
+ messageId,
6
22
  }: {
7
23
  creating?: boolean;
8
24
  isLatestItem?: boolean;
25
+ messageId: string;
9
26
  }) => {
10
- const [newScreen, setNewScreen] = useState(false);
27
+ const [minHeight, setMinHeight] = useState<string | undefined>(undefined);
28
+ const virtuaScrollMethods = useConversationStore((s) => s.virtuaScrollMethods);
29
+
11
30
  useEffect(() => {
12
- if (!isLatestItem) setNewScreen(false);
13
- if (isLatestItem && creating) setNewScreen(true);
14
- }, [isLatestItem, creating]);
15
- return newScreen;
31
+ // Clear minHeight when no longer the latest item
32
+ if (!isLatestItem) {
33
+ setMinHeight(undefined);
34
+ return;
35
+ }
36
+
37
+ // Only calculate and set minHeight when creating, preserve after creating ends
38
+ if (!creating) {
39
+ return;
40
+ }
41
+
42
+ // Find current message element by data-message-id
43
+ const messageEl = document.querySelector(`[data-message-id="${messageId}"]`);
44
+ // Find VList item container (has data-index attribute)
45
+ const currentWrapper = messageEl?.closest('[data-index]') as HTMLElement | null;
46
+ // Get current index
47
+ const currentIndex = currentWrapper?.dataset.index;
48
+
49
+ // Find previous VList item by data-index (avoid sibling not existing due to virtualization)
50
+ const prevIndex = currentIndex ? Number(currentIndex) - 1 : -1;
51
+ const prevWrapper =
52
+ prevIndex >= 0 ? document.querySelector(`[data-index="${prevIndex}"]`) : null;
53
+ // Get previous message's .message-wrapper
54
+ const prevMessageEl = prevWrapper?.querySelector('.message-wrapper');
55
+
56
+ // Get real viewport height from VList
57
+ const viewportHeight = virtuaScrollMethods?.getViewportSize?.() || window.innerHeight;
58
+
59
+ if (prevMessageEl) {
60
+ const prevHeight = prevMessageEl.getBoundingClientRect().height;
61
+
62
+ // Goal: userMessage at top, so assistantMinHeight = viewportHeight - userMessageHeight
63
+ const calculatedHeight = viewportHeight - prevHeight - EXTRA_PADDING;
64
+
65
+ log(
66
+ 'calculate minHeight: messageId=%s, index=%s, viewportHeight=%d, prevHeight=%d, result=%d',
67
+ messageId,
68
+ currentIndex,
69
+ viewportHeight,
70
+ prevHeight,
71
+ calculatedHeight,
72
+ );
73
+
74
+ // Don't set minHeight if calculated height <= 0
75
+ setMinHeight(calculatedHeight > 0 ? `${calculatedHeight}px` : undefined);
76
+ } else {
77
+ // Fallback: use default value
78
+ const fallbackHeight = viewportHeight - DEFAULT_USER_MESSAGE_HEIGHT - EXTRA_PADDING;
79
+ log(
80
+ 'fallback minHeight: messageId=%s, viewportHeight=%d, fallbackHeight=%d',
81
+ messageId,
82
+ viewportHeight,
83
+ fallbackHeight,
84
+ );
85
+ // Don't set minHeight if calculated height <= 0
86
+ setMinHeight(fallbackHeight > 0 ? `${fallbackHeight}px` : undefined);
87
+ }
88
+ }, [isLatestItem, creating, messageId, virtuaScrollMethods]);
89
+
90
+ return { minHeight };
16
91
  };
@@ -48,7 +48,7 @@ export default {
48
48
  'confirmRemoveChatGroupItemAlert':
49
49
  'This Group will be deleted. Group-specific assistants will also be deleted, while external assistants will not be affected.',
50
50
  'confirmRemoveGroupItemAlert':
51
- 'You are about to delete this group. After deletion, its agents will be moved to the default list. Please confirm your action.',
51
+ 'You are about to delete this category. After deletion, its agents will be moved to the default list. Please confirm your action.',
52
52
  'confirmRemoveGroupSuccess': 'Group deleted successfully',
53
53
  'confirmRemoveSessionItemAlert':
54
54
  'You are about to delete this agent. Once deleted, it cannot be retrieved. Please confirm your action.',
@@ -267,29 +267,29 @@ export default {
267
267
  'searchAgents': 'Search agents...',
268
268
  'selectedAgents': 'Selected agents',
269
269
  'sendPlaceholder': 'Ask, create, or start a task, <hotkey><hotkey/>',
270
- 'sessionGroup.config': 'Group Management',
270
+ 'sessionGroup.config': 'Category Management',
271
271
  'sessionGroup.confirmRemoveGroupAlert':
272
- 'This group is about to be deleted. After deletion, the agents in this group will be moved to the default list. Please confirm your operation.',
272
+ 'This category is about to be deleted. After deletion, the agents in this category will be moved to the default list. Please confirm your operation.',
273
273
  'sessionGroup.createAgentSuccess': 'Agent created successfully',
274
- 'sessionGroup.createGroup': 'Add New Group',
275
- 'sessionGroup.createGroupFailed': 'Failed to create group chat',
276
- 'sessionGroup.createGroupSuccess': 'Group chat created successfully',
274
+ 'sessionGroup.createGroup': 'Add New Category',
275
+ 'sessionGroup.createGroupFailed': 'Failed to create category',
276
+ 'sessionGroup.createGroupSuccess': 'Category created successfully',
277
277
  'sessionGroup.createSuccess': 'Created successfully',
278
278
  'sessionGroup.creatingAgent': 'Creating agent...',
279
- 'sessionGroup.groupName': 'Group Name',
280
- 'sessionGroup.inputPlaceholder': 'Please enter group name...',
281
- 'sessionGroup.moveGroup': 'Move to Group',
282
- 'sessionGroup.newGroup': 'New Group',
279
+ 'sessionGroup.groupName': 'Category Name',
280
+ 'sessionGroup.inputPlaceholder': 'Please enter category name...',
281
+ 'sessionGroup.moveGroup': 'Move to Category',
282
+ 'sessionGroup.newGroup': 'New Category',
283
283
  'sessionGroup.noAvailableAgents': 'No available agents',
284
284
  'sessionGroup.noMatchingAgents': 'No matching agents found',
285
285
  'sessionGroup.noSelectedAgents': 'Please select agents',
286
- 'sessionGroup.rename': 'Rename Group',
286
+ 'sessionGroup.rename': 'Rename Category',
287
287
  'sessionGroup.renameSuccess': 'Renamed successfully',
288
288
  'sessionGroup.searchAgents': 'Search agents',
289
289
  'sessionGroup.selectedAgents': 'Selected agents ({{count}})',
290
290
  'sessionGroup.sortSuccess': 'Reorder successful',
291
- 'sessionGroup.sorting': 'Group sorting updating...',
292
- 'sessionGroup.tooLong': 'Group name length should be between 1-20',
291
+ 'sessionGroup.sorting': 'Category sorting updating...',
292
+ 'sessionGroup.tooLong': 'Category name length should be between 1-20',
293
293
  'shareModal.copy': 'Copy',
294
294
  'shareModal.copyLink': 'Copy Link',
295
295
  'shareModal.copyLinkSuccess': 'Link copied',