@lobehub/lobehub 2.0.0-next.182 → 2.0.0-next.184

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 (60) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/changelog/v1.json +14 -0
  3. package/package.json +1 -1
  4. package/packages/builtin-tool-local-system/package.json +10 -0
  5. package/packages/builtin-tool-local-system/src/client/Inspector/EditLocalFile/index.tsx +81 -0
  6. package/packages/builtin-tool-local-system/src/client/Inspector/GlobLocalFiles/index.tsx +73 -0
  7. package/packages/builtin-tool-local-system/src/client/Inspector/GrepContent/index.tsx +73 -0
  8. package/packages/builtin-tool-local-system/src/client/Inspector/ReadLocalFile/index.tsx +81 -0
  9. package/packages/builtin-tool-local-system/src/client/Inspector/RunCommand/index.tsx +80 -0
  10. package/packages/builtin-tool-local-system/src/client/Inspector/SearchLocalFiles/index.tsx +71 -0
  11. package/{src/tools/local-system → packages/builtin-tool-local-system/src/client}/Inspector/index.ts +1 -2
  12. package/{src/tools/local-system → packages/builtin-tool-local-system/src/client}/Intervention/index.ts +1 -2
  13. package/{src/tools/local-system → packages/builtin-tool-local-system/src/client}/Render/ListFiles/Result.tsx +2 -1
  14. package/{src/tools/local-system → packages/builtin-tool-local-system/src/client}/Render/index.ts +1 -2
  15. package/{src/tools/local-system → packages/builtin-tool-local-system/src/client}/Streaming/index.ts +1 -2
  16. package/packages/builtin-tool-local-system/src/client/index.ts +20 -0
  17. package/src/app/[variants]/(main)/chat/_layout/Sidebar/Topic/index.tsx +3 -3
  18. package/src/app/[variants]/(main)/group/_layout/Sidebar/Topic/index.tsx +3 -3
  19. package/src/app/[variants]/(main)/home/_layout/Body/Agent/index.tsx +3 -3
  20. package/src/app/[variants]/(main)/home/features/RecentPage/index.tsx +3 -2
  21. package/src/app/[variants]/(main)/home/features/RecentResource/index.tsx +3 -2
  22. package/src/app/[variants]/(main)/home/features/RecentTopic/index.tsx +3 -3
  23. package/src/components/NeuralNetworkLoading/index.tsx +181 -0
  24. package/src/libs/swr/index.ts +1 -8
  25. package/src/store/chat/slices/topic/action.test.ts +154 -0
  26. package/src/store/chat/slices/topic/action.ts +48 -4
  27. package/src/tools/inspectors.ts +6 -5
  28. package/src/tools/interventions.ts +5 -4
  29. package/src/tools/placeholders.ts +9 -7
  30. package/src/tools/renders.ts +5 -3
  31. package/src/tools/streamings.ts +6 -5
  32. package/src/tools/local-system/Inspector/EditLocalFile/index.tsx +0 -55
  33. package/src/tools/local-system/Inspector/GlobLocalFiles/index.tsx +0 -59
  34. package/src/tools/local-system/Inspector/GrepContent/index.tsx +0 -59
  35. package/src/tools/local-system/Inspector/ReadLocalFile/index.tsx +0 -55
  36. package/src/tools/local-system/Inspector/RunCommand/index.tsx +0 -66
  37. package/src/tools/local-system/Inspector/SearchLocalFiles/index.tsx +0 -59
  38. /package/{src/tools/local-system → packages/builtin-tool-local-system/src/client}/Intervention/EditLocalFile/index.tsx +0 -0
  39. /package/{src/tools/local-system → packages/builtin-tool-local-system/src/client}/Intervention/MoveLocalFiles/MoveFileItem.tsx +0 -0
  40. /package/{src/tools/local-system → packages/builtin-tool-local-system/src/client}/Intervention/MoveLocalFiles/index.tsx +0 -0
  41. /package/{src/tools/local-system → packages/builtin-tool-local-system/src/client}/Intervention/RunCommand/index.tsx +0 -0
  42. /package/{src/tools/local-system → packages/builtin-tool-local-system/src/client}/Intervention/WriteFile/index.tsx +0 -0
  43. /package/{src/tools/local-system → packages/builtin-tool-local-system/src/client}/Placeholder/ListFiles.tsx +0 -0
  44. /package/{src/tools/local-system → packages/builtin-tool-local-system/src/client}/Placeholder/SearchFiles.tsx +0 -0
  45. /package/{src/tools/local-system → packages/builtin-tool-local-system/src/client}/Render/EditLocalFile/index.tsx +0 -0
  46. /package/{src/tools/local-system → packages/builtin-tool-local-system/src/client}/Render/ListFiles/index.tsx +0 -0
  47. /package/{src/tools/local-system → packages/builtin-tool-local-system/src/client}/Render/MoveLocalFiles/MoveFileItem.tsx +0 -0
  48. /package/{src/tools/local-system → packages/builtin-tool-local-system/src/client}/Render/MoveLocalFiles/index.tsx +0 -0
  49. /package/{src/tools/local-system → packages/builtin-tool-local-system/src/client}/Render/ReadLocalFile/ReadFileSkeleton.tsx +0 -0
  50. /package/{src/tools/local-system → packages/builtin-tool-local-system/src/client}/Render/ReadLocalFile/ReadFileView.tsx +0 -0
  51. /package/{src/tools/local-system → packages/builtin-tool-local-system/src/client}/Render/ReadLocalFile/index.tsx +0 -0
  52. /package/{src/tools/local-system → packages/builtin-tool-local-system/src/client}/Render/RenameLocalFile/index.tsx +0 -0
  53. /package/{src/tools/local-system → packages/builtin-tool-local-system/src/client}/Render/RunCommand/index.tsx +0 -0
  54. /package/{src/tools/local-system → packages/builtin-tool-local-system/src/client}/Render/SearchFiles/Result.tsx +0 -0
  55. /package/{src/tools/local-system → packages/builtin-tool-local-system/src/client}/Render/SearchFiles/SearchQuery/SearchView.tsx +0 -0
  56. /package/{src/tools/local-system → packages/builtin-tool-local-system/src/client}/Render/SearchFiles/SearchQuery/index.tsx +0 -0
  57. /package/{src/tools/local-system → packages/builtin-tool-local-system/src/client}/Render/SearchFiles/index.tsx +0 -0
  58. /package/{src/tools/local-system → packages/builtin-tool-local-system/src/client}/Render/WriteFile/index.tsx +0 -0
  59. /package/{src/tools/local-system → packages/builtin-tool-local-system/src/client}/Streaming/RunCommand/index.tsx +0 -0
  60. /package/{src/tools/local-system → packages/builtin-tool-local-system/src/client}/components/FileItem.tsx +0 -0
@@ -1,10 +1,10 @@
1
1
  'use client';
2
2
 
3
- import { AccordionItem, ActionIcon, Dropdown, Flexbox, Text } from '@lobehub/ui';
4
- import { Loader2Icon } from 'lucide-react';
3
+ import { AccordionItem, Dropdown, Flexbox, Text } from '@lobehub/ui';
5
4
  import React, { Suspense, memo } from 'react';
6
5
  import { useTranslation } from 'react-i18next';
7
6
 
7
+ import NeuralNetworkLoading from '@/components/NeuralNetworkLoading';
8
8
  import SkeletonList from '@/features/NavPanel/components/SkeletonList';
9
9
  import { useFetchTopics } from '@/hooks/useFetchTopics';
10
10
  import { useChatStore } from '@/store/chat';
@@ -45,7 +45,7 @@ const Topic = memo<TopicProps>(({ itemKey }) => {
45
45
  <Text ellipsis fontSize={12} type={'secondary'} weight={500}>
46
46
  {`${t('title')} ${topicCount > 0 ? topicCount : ''}`}
47
47
  </Text>
48
- {isRevalidating && <ActionIcon icon={Loader2Icon} loading size={'small'} />}
48
+ {isRevalidating && <NeuralNetworkLoading size={14} />}
49
49
  </Flexbox>
50
50
  }
51
51
  >
@@ -1,10 +1,10 @@
1
1
  'use client';
2
2
 
3
- import { AccordionItem, ActionIcon, Dropdown, Flexbox, Text } from '@lobehub/ui';
4
- import { Loader2Icon } from 'lucide-react';
3
+ import { AccordionItem, Dropdown, Flexbox, Text } from '@lobehub/ui';
5
4
  import React, { Suspense, memo, useCallback } from 'react';
6
5
  import { useTranslation } from 'react-i18next';
7
6
 
7
+ import NeuralNetworkLoading from '@/components/NeuralNetworkLoading';
8
8
  import { useFetchAgentList } from '@/hooks/useFetchAgentList';
9
9
 
10
10
  import SkeletonList from '../../../../../../../features/NavPanel/components/SkeletonList';
@@ -56,7 +56,7 @@ const Agent = memo<AgentProps>(({ itemKey }) => {
56
56
  <Text ellipsis fontSize={12} type={'secondary'} weight={500}>
57
57
  {t('navPanel.agent')}
58
58
  </Text>
59
- {isRevalidating && <ActionIcon icon={Loader2Icon} loading size={'small'} />}
59
+ {isRevalidating && <NeuralNetworkLoading size={14} />}
60
60
  </Flexbox>
61
61
  }
62
62
  >
@@ -1,12 +1,13 @@
1
1
  'use client';
2
2
 
3
3
  import { ActionIcon, Dropdown } from '@lobehub/ui';
4
- import { FileTextIcon, Loader2Icon, MoreHorizontal } from 'lucide-react';
4
+ import { FileTextIcon, MoreHorizontal } from 'lucide-react';
5
5
  import { Suspense, memo } from 'react';
6
6
  import { useTranslation } from 'react-i18next';
7
7
  import { useNavigate } from 'react-router-dom';
8
8
 
9
9
  import { useResourceManagerStore } from '@/app/[variants]/(main)/resource/features/store';
10
+ import NeuralNetworkLoading from '@/components/NeuralNetworkLoading';
10
11
  import { useInitRecentPage } from '@/hooks/useInitRecentPage';
11
12
  import { useHomeStore } from '@/store/home';
12
13
  import { homeRecentSelectors } from '@/store/home/selectors';
@@ -35,7 +36,7 @@ const RecentPage = memo(() => {
35
36
  <GroupBlock
36
37
  action={
37
38
  <>
38
- {isRevalidating && <ActionIcon icon={Loader2Icon} loading size={'small'} />}
39
+ {isRevalidating && <NeuralNetworkLoading size={14} />}
39
40
  <Dropdown
40
41
  menu={{
41
42
  items: [
@@ -1,12 +1,13 @@
1
1
  'use client';
2
2
 
3
3
  import { ActionIcon, Dropdown } from '@lobehub/ui';
4
- import { Clock, Loader2Icon, MoreHorizontal } from 'lucide-react';
4
+ import { Clock, MoreHorizontal } from 'lucide-react';
5
5
  import { Suspense, memo } from 'react';
6
6
  import { useTranslation } from 'react-i18next';
7
7
  import { useNavigate } from 'react-router-dom';
8
8
 
9
9
  import { useResourceManagerStore } from '@/app/[variants]/(main)/resource/features/store';
10
+ import NeuralNetworkLoading from '@/components/NeuralNetworkLoading';
10
11
  import { useInitRecentResource } from '@/hooks/useInitRecentResource';
11
12
  import { useHomeStore } from '@/store/home';
12
13
  import { homeRecentSelectors } from '@/store/home/selectors';
@@ -35,7 +36,7 @@ const RecentResource = memo(() => {
35
36
  <GroupBlock
36
37
  action={
37
38
  <>
38
- {isRevalidating && <ActionIcon icon={Loader2Icon} loading size={'small'} />}
39
+ {isRevalidating && <NeuralNetworkLoading size={14} />}
39
40
  <Dropdown
40
41
  menu={{
41
42
  items: [
@@ -1,8 +1,8 @@
1
- import { ActionIcon } from '@lobehub/ui';
2
- import { BotMessageSquareIcon, Loader2Icon } from 'lucide-react';
1
+ import { BotMessageSquareIcon } from 'lucide-react';
3
2
  import { Suspense, memo } from 'react';
4
3
  import { useTranslation } from 'react-i18next';
5
4
 
5
+ import NeuralNetworkLoading from '@/components/NeuralNetworkLoading';
6
6
  import { useInitRecentTopic } from '@/hooks/useInitRecentTopic';
7
7
  import { useHomeStore } from '@/store/home';
8
8
  import { homeRecentSelectors } from '@/store/home/selectors';
@@ -26,7 +26,7 @@ const RecentTopic = memo(() => {
26
26
 
27
27
  return (
28
28
  <GroupBlock
29
- action={isRevalidating && <ActionIcon icon={Loader2Icon} loading size={'small'} />}
29
+ action={isRevalidating && <NeuralNetworkLoading size={14} />}
30
30
  icon={BotMessageSquareIcon}
31
31
  title={t('topic.recent')}
32
32
  >
@@ -0,0 +1,181 @@
1
+ 'use client';
2
+
3
+ import { createStaticStyles, keyframes } from 'antd-style';
4
+ import { CSSProperties, memo } from 'react';
5
+
6
+ const pulseAnim = keyframes`
7
+ 0%, 100% {
8
+ opacity: 0.3;
9
+ }
10
+ 50% {
11
+ opacity: 1;
12
+ }
13
+ `;
14
+
15
+ const flowAnim = keyframes`
16
+ 0% {
17
+ transform: translateX(0);
18
+ opacity: 0.5;
19
+ }
20
+ 50% {
21
+ opacity: 1;
22
+ }
23
+ 100% {
24
+ transform: translateX(var(--flow-distance));
25
+ opacity: 0.5;
26
+ }
27
+ `;
28
+
29
+ const rotateAnim = keyframes`
30
+ 100% {
31
+ transform: rotate(360deg);
32
+ }
33
+ `;
34
+
35
+ const scaleAnim = keyframes`
36
+ 0%, 100% {
37
+ transform: scale(0.8);
38
+ opacity: 0.5;
39
+ }
40
+ 50% {
41
+ transform: scale(1);
42
+ opacity: 1;
43
+ }
44
+ `;
45
+
46
+ const styles = createStaticStyles(({ css, cssVar }) => ({
47
+ center: css`
48
+ fill: ${cssVar.colorTextSecondary};
49
+ animation: ${scaleAnim} 2s infinite;
50
+ `,
51
+
52
+ connection: css`
53
+ opacity: 0.3;
54
+ stroke: ${cssVar.colorTextSecondary};
55
+ stroke-width: 0.5;
56
+ `,
57
+
58
+ container: css`
59
+ display: flex;
60
+ align-items: center;
61
+ justify-content: center;
62
+ `,
63
+
64
+ node: css`
65
+ fill: ${cssVar.colorTextSecondary};
66
+ animation: ${pulseAnim} 2s infinite;
67
+ `,
68
+
69
+ particle: css`
70
+ fill: ${cssVar.colorTextSecondary};
71
+ animation: ${flowAnim} 2s infinite;
72
+ `,
73
+
74
+ ring: css`
75
+ transform-origin: center;
76
+
77
+ fill: none;
78
+ stroke: ${cssVar.colorFill};
79
+ stroke-dasharray: 0 8;
80
+ stroke-width: 1;
81
+
82
+ animation: ${rotateAnim} 20s infinite linear;
83
+ `,
84
+
85
+ svg: css`
86
+ width: 100%;
87
+ height: 100%;
88
+ `,
89
+ }));
90
+
91
+ interface NeuralNetworkLoadingProps {
92
+ size?: number;
93
+ }
94
+
95
+ const NeuralNetworkLoading = memo<NeuralNetworkLoadingProps>(({ size = 16 }) => {
96
+ const nodeCount = 3;
97
+ const layerCount = 3;
98
+
99
+ // Generate nodes for each layer
100
+ const nodes = [];
101
+ for (let layerIndex = 0; layerIndex < layerCount; layerIndex++) {
102
+ for (let nodeIndex = 0; nodeIndex < nodeCount; nodeIndex++) {
103
+ const x = 25 + layerIndex * 25;
104
+ const y = 25 + nodeIndex * 25;
105
+ const delay = (layerIndex * nodeCount + nodeIndex) * 0.2;
106
+ nodes.push(
107
+ <circle
108
+ className={styles.node}
109
+ cx={x}
110
+ cy={y}
111
+ key={`node-${layerIndex}-${nodeIndex}`}
112
+ r="3"
113
+ style={{ animationDelay: `${delay}s` }}
114
+ />,
115
+ );
116
+ }
117
+ }
118
+
119
+ // Generate connections between layers
120
+ const connections = [];
121
+ for (let layerIndex = 0; layerIndex < layerCount - 1; layerIndex++) {
122
+ for (let nodeIndex = 0; nodeIndex < nodeCount; nodeIndex++) {
123
+ const x1 = 25 + layerIndex * 25;
124
+ const y1 = 25 + nodeIndex * 25;
125
+ for (let targetIndex = 0; targetIndex < nodeCount; targetIndex++) {
126
+ const x2 = 25 + (layerIndex + 1) * 25;
127
+ const y2 = 25 + targetIndex * 25;
128
+ connections.push(
129
+ <line
130
+ className={styles.connection}
131
+ key={`connection-${layerIndex}-${nodeIndex}-${targetIndex}`}
132
+ x1={x1}
133
+ x2={x2}
134
+ y1={y1}
135
+ y2={y2}
136
+ />,
137
+ );
138
+ }
139
+ }
140
+ }
141
+
142
+ // Generate particles
143
+ const particles = [0, 1, 2].map((index) => (
144
+ <circle
145
+ className={styles.particle}
146
+ cx={25}
147
+ cy={50}
148
+ key={`particle-${index}`}
149
+ r="1.5"
150
+ style={
151
+ {
152
+ '--flow-distance': '50px',
153
+ 'animationDelay': `${index * 0.6}s`,
154
+ } as CSSProperties
155
+ }
156
+ />
157
+ ));
158
+
159
+ return (
160
+ <div className={styles.container} style={{ height: size, width: size }}>
161
+ <svg className={styles.svg} viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
162
+ {/* Connections */}
163
+ {connections}
164
+
165
+ {/* Nodes */}
166
+ {nodes}
167
+
168
+ {/* Particles */}
169
+ {particles}
170
+
171
+ {/* Central processing unit */}
172
+ <rect className={styles.center} height="6" width="6" x="47" y="47" />
173
+
174
+ {/* Rotating outer ring */}
175
+ <circle className={styles.ring} cx="50" cy="50" r="40" />
176
+ </svg>
177
+ </div>
178
+ );
179
+ });
180
+
181
+ export default NeuralNetworkLoading;
@@ -1,6 +1,5 @@
1
1
  import useSWR, { type SWRHook } from 'swr';
2
2
 
3
- import { isDesktop } from '@/const/version';
4
3
 
5
4
  /**
6
5
  * This type of request method is relatively flexible data, which will be triggered on the first time
@@ -27,13 +26,7 @@ export const useClientDataSWR: SWRHook = (key, fetch, config) =>
27
26
  // Cause issue like this: https://github.com/lobehub/lobe-chat/issues/532
28
27
  // we need to set it to 0.
29
28
  dedupingInterval: 0,
30
- focusThrottleInterval:
31
- // FIXME: desktop 云同步模式也是走 edge 请求,也应该增大延迟
32
- // desktop 1.5s
33
- isDesktop
34
- ? 1500
35
- : // web 300s
36
- 5 * 60 * 1000,
29
+ focusThrottleInterval: 5 * 60 * 1000,
37
30
  // Custom error retry logic: don't retry on 401 errors
38
31
  onErrorRetry: (error: any, key: any, config: any, revalidate: any, { retryCount }: any) => {
39
32
  // Check if error is marked as non-retryable (e.g., 401 authentication errors)
@@ -345,6 +345,160 @@ describe('topic action', () => {
345
345
  // Verify that the refreshMessages was called to update the messages
346
346
  expect(refreshMessagesSpy).toHaveBeenCalled();
347
347
  });
348
+
349
+ it('should support boolean as second parameter for backward compatibility', async () => {
350
+ const topicId = 'topic-id';
351
+ const { result } = renderHook(() => useChatStore());
352
+
353
+ const refreshMessagesSpy = vi.spyOn(result.current, 'refreshMessages');
354
+
355
+ // Call with boolean (old API)
356
+ await act(async () => {
357
+ await result.current.switchTopic(topicId, true);
358
+ });
359
+
360
+ expect(useChatStore.getState().activeTopicId).toBe(topicId);
361
+ // Should not call refreshMessages when skipRefreshMessage is true
362
+ expect(refreshMessagesSpy).not.toHaveBeenCalled();
363
+ });
364
+
365
+ it('should support options object as second parameter', async () => {
366
+ const topicId = 'topic-id';
367
+ const { result } = renderHook(() => useChatStore());
368
+
369
+ const refreshMessagesSpy = vi.spyOn(result.current, 'refreshMessages');
370
+
371
+ // Call with options object (new API)
372
+ await act(async () => {
373
+ await result.current.switchTopic(topicId, { skipRefreshMessage: true });
374
+ });
375
+
376
+ expect(useChatStore.getState().activeTopicId).toBe(topicId);
377
+ expect(refreshMessagesSpy).not.toHaveBeenCalled();
378
+ });
379
+
380
+ it('should clear new key data when switching to new state (main scope)', async () => {
381
+ const { result } = renderHook(() => useChatStore());
382
+ const activeAgentId = 'test-agent-id';
383
+ const newKey = messageMapKey({ agentId: activeAgentId, topicId: null });
384
+
385
+ // Setup initial state with some messages in the new key
386
+ await act(async () => {
387
+ useChatStore.setState({
388
+ activeAgentId,
389
+ activeTopicId: 'existing-topic',
390
+ dbMessagesMap: {
391
+ [newKey]: [{ id: 'msg-1' }, { id: 'msg-2' }] as any,
392
+ },
393
+ messagesMap: {
394
+ [newKey]: [{ id: 'msg-1' }, { id: 'msg-2' }] as any,
395
+ },
396
+ });
397
+ });
398
+
399
+ const replaceMessagesSpy = vi.spyOn(result.current, 'replaceMessages');
400
+
401
+ // Switch to new state (id = undefined)
402
+ await act(async () => {
403
+ await result.current.switchTopic(undefined, { skipRefreshMessage: true });
404
+ });
405
+
406
+ // Verify replaceMessages was called to clear the new key
407
+ expect(replaceMessagesSpy).toHaveBeenCalledWith([], {
408
+ context: {
409
+ agentId: activeAgentId,
410
+ groupId: undefined,
411
+ scope: 'main',
412
+ topicId: null,
413
+ },
414
+ action: expect.any(String),
415
+ });
416
+
417
+ // Verify activeTopicId is now null
418
+ expect(useChatStore.getState().activeTopicId).toBeNull();
419
+ });
420
+
421
+ it('should clear new key data when switching to new state (group scope)', async () => {
422
+ const { result } = renderHook(() => useChatStore());
423
+ const activeAgentId = 'test-agent-id';
424
+ const activeGroupId = 'test-group-id';
425
+
426
+ // Setup initial state with group context
427
+ await act(async () => {
428
+ useChatStore.setState({
429
+ activeAgentId,
430
+ activeGroupId,
431
+ activeTopicId: 'existing-topic',
432
+ });
433
+ });
434
+
435
+ const replaceMessagesSpy = vi.spyOn(result.current, 'replaceMessages');
436
+
437
+ // Switch to new state
438
+ await act(async () => {
439
+ await result.current.switchTopic(undefined, { skipRefreshMessage: true });
440
+ });
441
+
442
+ // Verify replaceMessages was called with group scope
443
+ expect(replaceMessagesSpy).toHaveBeenCalledWith([], {
444
+ context: {
445
+ agentId: activeAgentId,
446
+ groupId: activeGroupId,
447
+ scope: 'group',
448
+ topicId: null,
449
+ },
450
+ action: expect.any(String),
451
+ });
452
+ });
453
+
454
+ it('should use explicit scope from options when provided', async () => {
455
+ const { result } = renderHook(() => useChatStore());
456
+ const activeAgentId = 'test-agent-id';
457
+
458
+ await act(async () => {
459
+ useChatStore.setState({
460
+ activeAgentId,
461
+ activeTopicId: 'existing-topic',
462
+ });
463
+ });
464
+
465
+ const replaceMessagesSpy = vi.spyOn(result.current, 'replaceMessages');
466
+
467
+ // Switch to new state with explicit scope
468
+ await act(async () => {
469
+ await result.current.switchTopic(undefined, { skipRefreshMessage: true, scope: 'group' });
470
+ });
471
+
472
+ // Verify replaceMessages was called with explicit scope
473
+ expect(replaceMessagesSpy).toHaveBeenCalledWith([], {
474
+ context: expect.objectContaining({
475
+ scope: 'group',
476
+ }),
477
+ action: expect.any(String),
478
+ });
479
+ });
480
+
481
+ it('should not clear new key data when switching to an existing topic', async () => {
482
+ const { result } = renderHook(() => useChatStore());
483
+ const activeAgentId = 'test-agent-id';
484
+
485
+ await act(async () => {
486
+ useChatStore.setState({
487
+ activeAgentId,
488
+ activeTopicId: undefined,
489
+ });
490
+ });
491
+
492
+ const replaceMessagesSpy = vi.spyOn(result.current, 'replaceMessages');
493
+
494
+ // Switch to an existing topic (not new state)
495
+ await act(async () => {
496
+ await result.current.switchTopic('existing-topic-id', { skipRefreshMessage: true });
497
+ });
498
+
499
+ // replaceMessages should not be called when switching to existing topic
500
+ expect(replaceMessagesSpy).not.toHaveBeenCalled();
501
+ });
348
502
  });
349
503
  describe('removeSessionTopics', () => {
350
504
  it('should remove all topics from the current session and refresh the topic list', async () => {
@@ -2,7 +2,7 @@
2
2
  // Note: To make the code more logic and readable, we just disable the auto sort key eslint rule
3
3
  // DON'T REMOVE THE FIRST LINE
4
4
  import { chainSummaryTitle } from '@lobechat/prompts';
5
- import { TraceNameMap, type UIChatMessage } from '@lobechat/types';
5
+ import { type MessageMapScope, TraceNameMap, type UIChatMessage } from '@lobechat/types';
6
6
  import isEqual from 'fast-deep-equal';
7
7
  import { t } from 'i18next';
8
8
  import useSWR, { type SWRResponse } from 'swr';
@@ -34,6 +34,22 @@ const n = setNamespace('t');
34
34
  const SWR_USE_FETCH_TOPIC = 'SWR_USE_FETCH_TOPIC';
35
35
  const SWR_USE_SEARCH_TOPIC = 'SWR_USE_SEARCH_TOPIC';
36
36
 
37
+ /**
38
+ * Options for switchTopic action
39
+ */
40
+ export interface SwitchTopicOptions {
41
+ /**
42
+ * Explicit scope for clearing new key data
43
+ * If not provided, will be inferred from store state (activeGroupId)
44
+ */
45
+ scope?: MessageMapScope;
46
+ /**
47
+ * Skip refreshing messages after switching topic
48
+ * @default false
49
+ */
50
+ skipRefreshMessage?: boolean;
51
+ }
52
+
37
53
  export interface ChatTopicAction {
38
54
  closeAllTopicsDrawer: () => void;
39
55
  favoriteTopic: (id: string, favState: boolean) => Promise<void>;
@@ -53,7 +69,12 @@ export interface ChatTopicAction {
53
69
  autoRenameTopicTitle: (id: string) => Promise<void>;
54
70
  duplicateTopic: (id: string) => Promise<void>;
55
71
  summaryTopicTitle: (topicId: string, messages: UIChatMessage[]) => Promise<void>;
56
- switchTopic: (id?: string, skipRefreshMessage?: boolean) => Promise<void>;
72
+ /**
73
+ * Switch to a topic or create new topic state
74
+ * @param id - Topic ID to switch to, or undefined/null to switch to "new" state
75
+ * @param options - Options object or boolean for backward compatibility (skipRefreshMessage)
76
+ */
77
+ switchTopic: (id?: string, options?: boolean | SwitchTopicOptions) => Promise<void>;
57
78
  updateTopicTitle: (id: string, title: string) => Promise<void>;
58
79
  useFetchTopics: (
59
80
  enable: boolean,
@@ -423,14 +444,37 @@ export const chatTopic: StateCreator<
423
444
  },
424
445
  ),
425
446
 
426
- switchTopic: async (id, skipRefreshMessage) => {
447
+ switchTopic: async (id, options) => {
448
+ // Backward compatibility: support both boolean and options object
449
+ const opts: SwitchTopicOptions =
450
+ typeof options === 'boolean' ? { skipRefreshMessage: options } : (options ?? {});
451
+
452
+ const { activeAgentId, activeGroupId } = get();
453
+
454
+ // When switching to "new" state (id is undefined/null), clear the new key data
455
+ // This prevents stale data from previous conversations showing up
456
+ if (!id && activeAgentId) {
457
+ // Determine scope: use explicit scope from options, or infer from activeGroupId
458
+ const scope = opts.scope ?? (activeGroupId ? 'group' : 'main');
459
+
460
+ get().replaceMessages([], {
461
+ context: {
462
+ agentId: activeAgentId,
463
+ groupId: activeGroupId,
464
+ scope,
465
+ topicId: null,
466
+ },
467
+ action: n('clearNewKeyData'),
468
+ });
469
+ }
470
+
427
471
  set(
428
472
  { activeTopicId: !id ? (null as any) : id, activeThreadId: undefined },
429
473
  false,
430
474
  n('toggleTopic'),
431
475
  );
432
476
 
433
- if (skipRefreshMessage) return;
477
+ if (opts.skipRefreshMessage) return;
434
478
  await get().refreshMessages();
435
479
  },
436
480
  // delete
@@ -7,16 +7,17 @@ import {
7
7
  GroupManagementManifest,
8
8
  } from '@lobechat/builtin-tool-group-management/client';
9
9
  import { GTDInspectors, GTDManifest } from '@lobechat/builtin-tool-gtd/client';
10
- import { LocalSystemManifest } from '@lobechat/builtin-tool-local-system';
11
- import { PageAgentIdentifier, PageAgentInspectors } from '@lobechat/builtin-tool-page-agent/client';
10
+ import {
11
+ LocalSystemInspectors,
12
+ LocalSystemManifest,
13
+ } from '@lobechat/builtin-tool-local-system/client';
14
+ import { PageAgentInspectors, PageAgentManifest } from '@lobechat/builtin-tool-page-agent/client';
12
15
  import {
13
16
  WebBrowsingInspectors,
14
17
  WebBrowsingManifest,
15
18
  } from '@lobechat/builtin-tool-web-browsing/client';
16
19
  import { type BuiltinInspector } from '@lobechat/types';
17
20
 
18
- import { LocalSystemInspectors } from './local-system/Inspector';
19
-
20
21
  /**
21
22
  * Builtin tools inspector registry
22
23
  * Organized by toolset (identifier) -> API name
@@ -32,7 +33,7 @@ const BuiltinToolInspectors: Record<string, Record<string, BuiltinInspector>> =
32
33
  >,
33
34
  [GTDManifest.identifier]: GTDInspectors as Record<string, BuiltinInspector>,
34
35
  [LocalSystemManifest.identifier]: LocalSystemInspectors as Record<string, BuiltinInspector>,
35
- [PageAgentIdentifier]: PageAgentInspectors as Record<string, BuiltinInspector>,
36
+ [PageAgentManifest.identifier]: PageAgentInspectors as Record<string, BuiltinInspector>,
36
37
  [WebBrowsingManifest.identifier]: WebBrowsingInspectors as Record<string, BuiltinInspector>,
37
38
  };
38
39
 
@@ -9,13 +9,14 @@ import {
9
9
  GroupManagementManifest,
10
10
  } from '@lobechat/builtin-tool-group-management/client';
11
11
  import { GTDInterventions, GTDManifest } from '@lobechat/builtin-tool-gtd/client';
12
- import { LocalSystemManifest } from '@lobechat/builtin-tool-local-system';
12
+ import {
13
+ LocalSystemIdentifier,
14
+ LocalSystemInterventions,
15
+ } from '@lobechat/builtin-tool-local-system/client';
13
16
  import { NotebookManifest } from '@lobechat/builtin-tool-notebook';
14
17
  import { NotebookInterventions } from '@lobechat/builtin-tool-notebook/client';
15
18
  import { type BuiltinIntervention } from '@lobechat/types';
16
19
 
17
- import { LocalSystemInterventions } from './local-system/Intervention';
18
-
19
20
  /**
20
21
  * Builtin tools interventions registry
21
22
  * Organized by toolset (identifier) -> API name
@@ -26,7 +27,7 @@ export const BuiltinToolInterventions: Record<string, Record<string, any>> = {
26
27
  [CloudSandboxManifest.identifier]: CloudSandboxInterventions,
27
28
  [GroupManagementManifest.identifier]: GroupManagementInterventions,
28
29
  [GTDManifest.identifier]: GTDInterventions,
29
- [LocalSystemManifest.identifier]: LocalSystemInterventions,
30
+ [LocalSystemIdentifier]: LocalSystemInterventions,
30
31
  [NotebookManifest.identifier]: NotebookInterventions,
31
32
  };
32
33
 
@@ -1,21 +1,23 @@
1
- import { LocalSystemApiName, LocalSystemManifest } from '@lobechat/builtin-tool-local-system';
1
+ import {
2
+ LocalSystemApiName,
3
+ LocalSystemIdentifier,
4
+ LocalSystemListFilesPlaceholder,
5
+ LocalSystemSearchFilesPlaceholder,
6
+ } from '@lobechat/builtin-tool-local-system/client';
2
7
  import {
3
8
  WebBrowsingManifest,
4
9
  WebBrowsingPlaceholders,
5
10
  } from '@lobechat/builtin-tool-web-browsing/client';
6
11
  import { type BuiltinPlaceholder } from '@lobechat/types';
7
12
 
8
- import { ListFiles as LocalSystemListFiles } from './local-system/Placeholder/ListFiles';
9
- import LocalSystemSearchFiles from './local-system/Placeholder/SearchFiles';
10
-
11
13
  /**
12
14
  * Builtin tools placeholders registry
13
15
  * Organized by toolset (identifier) -> API name
14
16
  */
15
17
  export const BuiltinToolPlaceholders: Record<string, Record<string, any>> = {
16
- [LocalSystemManifest.identifier]: {
17
- [LocalSystemApiName.searchLocalFiles]: LocalSystemSearchFiles,
18
- [LocalSystemApiName.listLocalFiles]: LocalSystemListFiles,
18
+ [LocalSystemIdentifier]: {
19
+ [LocalSystemApiName.searchLocalFiles]: LocalSystemSearchFilesPlaceholder,
20
+ [LocalSystemApiName.listLocalFiles]: LocalSystemListFilesPlaceholder,
19
21
  },
20
22
  [WebBrowsingManifest.identifier]: WebBrowsingPlaceholders as Record<string, any>,
21
23
  };
@@ -10,7 +10,10 @@ import { GroupManagementRenders } from '@lobechat/builtin-tool-group-management/
10
10
  // gtd
11
11
  import { GTDManifest, GTDRenders } from '@lobechat/builtin-tool-gtd/client';
12
12
  // local-system
13
- import { LocalSystemManifest } from '@lobechat/builtin-tool-local-system';
13
+ import {
14
+ LocalSystemIdentifier,
15
+ LocalSystemRenders,
16
+ } from '@lobechat/builtin-tool-local-system/client';
14
17
  import { NotebookManifest, NotebookRenders } from '@lobechat/builtin-tool-notebook/client';
15
18
  // web-browsing
16
19
  import {
@@ -22,7 +25,6 @@ import { type BuiltinRender } from '@lobechat/types';
22
25
  // knowledge-base
23
26
  import { KnowledgeBaseManifest } from './knowledge-base';
24
27
  import { KnowledgeBaseRenders } from './knowledge-base/Render';
25
- import { LocalSystemRenders } from './local-system/Render';
26
28
 
27
29
  /**
28
30
  * Builtin tools renders registry
@@ -35,7 +37,7 @@ const BuiltinToolsRenders: Record<string, Record<string, BuiltinRender>> = {
35
37
  [GTDManifest.identifier]: GTDRenders as Record<string, BuiltinRender>,
36
38
  [NotebookManifest.identifier]: NotebookRenders as Record<string, BuiltinRender>,
37
39
  [KnowledgeBaseManifest.identifier]: KnowledgeBaseRenders as Record<string, BuiltinRender>,
38
- [LocalSystemManifest.identifier]: LocalSystemRenders as Record<string, BuiltinRender>,
40
+ [LocalSystemIdentifier]: LocalSystemRenders as Record<string, BuiltinRender>,
39
41
  [WebBrowsingManifest.identifier]: WebBrowsingRenders as Record<string, BuiltinRender>,
40
42
  };
41
43