@lobehub/lobehub 2.0.0-next.61 → 2.0.0-next.63

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 (59) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/changelog/v1.json +14 -0
  3. package/locales/ar/plugin.json +2 -0
  4. package/locales/bg-BG/plugin.json +2 -0
  5. package/locales/de-DE/plugin.json +2 -0
  6. package/locales/en-US/plugin.json +8 -1
  7. package/locales/es-ES/plugin.json +2 -0
  8. package/locales/fa-IR/plugin.json +2 -0
  9. package/locales/fr-FR/plugin.json +2 -0
  10. package/locales/it-IT/plugin.json +2 -0
  11. package/locales/ja-JP/plugin.json +2 -0
  12. package/locales/ko-KR/plugin.json +2 -0
  13. package/locales/nl-NL/plugin.json +2 -0
  14. package/locales/pl-PL/plugin.json +2 -0
  15. package/locales/pt-BR/plugin.json +2 -0
  16. package/locales/ru-RU/plugin.json +2 -0
  17. package/locales/tr-TR/plugin.json +2 -0
  18. package/locales/vi-VN/plugin.json +2 -0
  19. package/locales/zh-CN/plugin.json +8 -1
  20. package/locales/zh-TW/plugin.json +2 -0
  21. package/package.json +1 -1
  22. package/src/app/(backend)/middleware/auth/index.ts +7 -2
  23. package/src/app/(backend)/trpc/async/[trpc]/route.ts +9 -3
  24. package/src/app/(backend)/trpc/desktop/[trpc]/route.ts +9 -3
  25. package/src/app/(backend)/trpc/lambda/[trpc]/route.ts +9 -3
  26. package/src/app/(backend)/trpc/mobile/[trpc]/route.ts +9 -3
  27. package/src/app/(backend)/trpc/tools/[trpc]/route.ts +9 -3
  28. package/src/components/FileIcon/index.tsx +0 -2
  29. package/src/features/Conversation/Messages/Assistant/MessageContent.tsx +0 -18
  30. package/src/features/Conversation/Messages/Group/Tool/Inspector/index.tsx +26 -5
  31. package/src/features/Conversation/Messages/Group/Tool/index.tsx +1 -1
  32. package/src/features/Conversation/Messages/{Assistant/Tool → Tool}/Inspector/ToolTitle.tsx +1 -7
  33. package/src/features/Conversation/Messages/{Assistant/Tool → Tool}/Inspector/index.tsx +2 -15
  34. package/src/features/Conversation/Messages/{Assistant/Tool → Tool}/Render/CustomRender.tsx +1 -1
  35. package/src/features/Conversation/Messages/{Assistant/Tool → Tool}/Render/PluginSettings.tsx +2 -2
  36. package/src/features/Conversation/Messages/{Assistant/Tool → Tool}/Render/index.tsx +2 -31
  37. package/src/features/Conversation/Messages/Tool/ToolItem.tsx +51 -0
  38. package/src/features/Conversation/Messages/Tool/index.tsx +58 -0
  39. package/src/features/Conversation/Messages/index.tsx +5 -0
  40. package/src/features/ShareModal/ShareJSON/index.tsx +2 -2
  41. package/src/libs/trpc/utils/request-adapter.ts +20 -0
  42. package/src/locales/default/plugin.ts +8 -0
  43. package/src/store/chat/slices/message/action.test.ts +8 -4
  44. package/src/store/chat/slices/message/actions/optimisticUpdate.ts +12 -0
  45. package/src/store/chat/slices/message/actions/publicApi.ts +19 -17
  46. package/src/tools/local-system/index.ts +27 -27
  47. package/src/tools/local-system/systemRole.ts +7 -7
  48. package/src/features/Conversation/Messages/Assistant/Tool/Render/LoadingPlaceholder/index.tsx +0 -29
  49. package/src/features/Conversation/Messages/Assistant/Tool/index.tsx +0 -76
  50. /package/src/features/Conversation/Messages/{Assistant/Tool → Tool}/Inspector/BuiltinPluginTitle.tsx +0 -0
  51. /package/src/features/Conversation/Messages/{Assistant/Tool → Tool}/Inspector/Debug.tsx +0 -0
  52. /package/src/features/Conversation/Messages/{Assistant/Tool → Tool}/Inspector/PluginResult.tsx +0 -0
  53. /package/src/features/Conversation/Messages/{Assistant/Tool → Tool}/Inspector/PluginState.tsx +0 -0
  54. /package/src/features/Conversation/Messages/{Assistant/Tool → Tool}/Inspector/Settings.tsx +0 -0
  55. /package/src/features/Conversation/Messages/{Assistant/Tool → Tool}/Render/Arguments/ObjectEntity.tsx +0 -0
  56. /package/src/features/Conversation/Messages/{Assistant/Tool → Tool}/Render/Arguments/ValueCell.tsx +0 -0
  57. /package/src/features/Conversation/Messages/{Assistant/Tool → Tool}/Render/Arguments/index.tsx +0 -0
  58. /package/src/features/Conversation/Messages/{Assistant/Tool → Tool}/Render/ErrorResponse.tsx +0 -0
  59. /package/src/features/Conversation/Messages/{Assistant/Tool → Tool}/Render/KeyValueEditor.tsx +0 -0
@@ -6,8 +6,6 @@ import { memo, useMemo } from 'react';
6
6
  import { useTranslation } from 'react-i18next';
7
7
  import { Flexbox } from 'react-layout-kit';
8
8
 
9
- import { useChatStore } from '@/store/chat';
10
- import { messageStateSelectors } from '@/store/chat/selectors';
11
9
  import { pluginHelpers, useToolStore } from '@/store/tool';
12
10
  import { toolSelectors } from '@/store/tool/selectors';
13
11
  import { shinyTextStylish } from '@/styles/loading';
@@ -43,10 +41,6 @@ const ToolTitle = memo<ToolTitleProps>(({ identifier, messageId, index, apiName,
43
41
  const { t } = useTranslation('plugin');
44
42
  const { styles } = useStyles();
45
43
 
46
- const isLoading = useChatStore(
47
- messageStateSelectors.isToolApiNameShining(messageId, index, toolCallId),
48
- );
49
-
50
44
  const pluginMeta = useToolStore(toolSelectors.getMetaById(identifier), isEqual);
51
45
 
52
46
  const plugins = useMemo(
@@ -84,7 +78,7 @@ const ToolTitle = memo<ToolTitleProps>(({ identifier, messageId, index, apiName,
84
78
  const pluginTitle = pluginHelpers.getPluginTitle(pluginMeta) ?? t('unknownPlugin');
85
79
 
86
80
  return (
87
- <Flexbox align={'center'} className={isLoading ? styles.shinyText : ''} gap={6} horizontal>
81
+ <Flexbox align={'center'} gap={6} horizontal>
88
82
  <div>{pluginTitle}</div> <Icon icon={ChevronRight} />
89
83
  <span className={styles.apiName}>{apiName}</span>
90
84
  </Flexbox>
@@ -68,10 +68,8 @@ interface InspectorProps {
68
68
  messageId: string;
69
69
  payload: object;
70
70
  setShowPluginRender: (show: boolean) => void;
71
- setShowRender: (show: boolean) => void;
72
71
  showPluginRender: boolean;
73
72
  showPortal?: boolean;
74
- showRender: boolean;
75
73
  style?: CSSProperties;
76
74
  }
77
75
 
@@ -83,9 +81,7 @@ const Inspectors = memo<InspectorProps>(
83
81
  apiName,
84
82
  id,
85
83
  arguments: requestArgs,
86
- showRender,
87
84
  payload,
88
- setShowRender,
89
85
  showPluginRender,
90
86
  setShowPluginRender,
91
87
  hidePluginUI = false,
@@ -98,16 +94,7 @@ const Inspectors = memo<InspectorProps>(
98
94
  return (
99
95
  <Flexbox className={styles.container} gap={4}>
100
96
  <Flexbox align={'center'} distribution={'space-between'} gap={8} horizontal>
101
- <Flexbox
102
- align={'center'}
103
- className={styles.tool}
104
- gap={8}
105
- horizontal
106
- onClick={() => {
107
- setShowRender(!showRender);
108
- }}
109
- paddingInline={4}
110
- >
97
+ <Flexbox align={'center'} className={styles.tool} gap={8} horizontal paddingInline={4}>
111
98
  <ToolTitle
112
99
  apiName={apiName}
113
100
  identifier={identifier}
@@ -117,7 +104,7 @@ const Inspectors = memo<InspectorProps>(
117
104
  />
118
105
  </Flexbox>
119
106
  <Flexbox className={styles.actions} horizontal>
120
- {showRender && !hidePluginUI && (
107
+ {!hidePluginUI && (
121
108
  <ActionIcon
122
109
  icon={showPluginRender ? LogsIcon : LayoutPanelTop}
123
110
  onClick={() => {
@@ -7,11 +7,11 @@ import { memo, useCallback, useEffect, useState } from 'react';
7
7
  import { useTranslation } from 'react-i18next';
8
8
  import { Flexbox } from 'react-layout-kit';
9
9
 
10
- import PluginResult from '@/features/Conversation/Messages/Assistant/Tool/Inspector/PluginResult';
11
10
  import PluginRender from '@/features/PluginsUI/Render';
12
11
  import { useChatStore } from '@/store/chat';
13
12
  import { messageStateSelectors } from '@/store/chat/selectors';
14
13
 
14
+ import PluginResult from '../Inspector/PluginResult';
15
15
  import Arguments from './Arguments';
16
16
  import KeyValueEditor from './KeyValueEditor';
17
17
 
@@ -1,4 +1,3 @@
1
- import { ChatPluginPayload } from '@lobechat/types';
2
1
  import { Avatar, Button } from '@lobehub/ui';
3
2
  import { Divider } from 'antd';
4
3
  import { useTheme } from 'antd-style';
@@ -11,8 +10,9 @@ import PluginSettingsConfig from '@/features/PluginSettings';
11
10
  import { useChatStore } from '@/store/chat';
12
11
  import { pluginHelpers, useToolStore } from '@/store/tool';
13
12
  import { pluginSelectors } from '@/store/tool/selectors';
13
+ import { ChatPluginPayload } from '@/types/index';
14
14
 
15
- import { ErrorActionContainer, useStyles } from '../../../../Error/style';
15
+ import { ErrorActionContainer, useStyles } from '../../../Error/style';
16
16
 
17
17
  interface PluginSettingsProps {
18
18
  id: string;
@@ -1,4 +1,3 @@
1
- import { LOADING_FLAT } from '@lobechat/const';
2
1
  import { Suspense, memo } from 'react';
3
2
 
4
3
  import { useChatStore } from '@/store/chat';
@@ -6,11 +5,8 @@ import { dbMessageSelectors, messageStateSelectors } from '@/store/chat/selector
6
5
 
7
6
  import CustomRender from './CustomRender';
8
7
  import ErrorResponse from './ErrorResponse';
9
- import LoadingPlaceholder from './LoadingPlaceholder';
10
8
 
11
9
  interface RenderProps {
12
- apiName: string;
13
- identifier: string;
14
10
  messageId: string;
15
11
  requestArgs?: string;
16
12
  setShowPluginRender: (show: boolean) => void;
@@ -20,16 +16,7 @@ interface RenderProps {
20
16
  }
21
17
 
22
18
  const Render = memo<RenderProps>(
23
- ({
24
- toolCallId,
25
- toolIndex,
26
- messageId,
27
- requestArgs,
28
- showPluginRender,
29
- setShowPluginRender,
30
- identifier,
31
- apiName,
32
- }) => {
19
+ ({ toolCallId, toolIndex, messageId, requestArgs, showPluginRender, setShowPluginRender }) => {
33
20
  const loading = useChatStore(messageStateSelectors.isToolCallStreaming(messageId, toolIndex));
34
21
  const toolMessage = useChatStore(dbMessageSelectors.getDbMessageByToolCallId(toolCallId));
35
22
 
@@ -40,24 +27,8 @@ const Render = memo<RenderProps>(
40
27
  return <ErrorResponse {...toolMessage.error} id={messageId} plugin={toolMessage.plugin} />;
41
28
  }
42
29
 
43
- const placeholder = (
44
- <LoadingPlaceholder
45
- apiName={apiName}
46
- identifier={identifier}
47
- loading
48
- requestArgs={requestArgs}
49
- />
50
- );
51
-
52
- // 如果是 LOADING_FLAT 则说明还在加载中
53
- // 而 standalone 模式的插件 content 应该始终是 LOADING_FLAT
54
- const inPlaceholder =
55
- toolMessage.content === LOADING_FLAT && toolMessage.plugin?.type !== 'standalone';
56
-
57
- if (inPlaceholder) return placeholder;
58
-
59
30
  return (
60
- <Suspense fallback={placeholder}>
31
+ <Suspense>
61
32
  <CustomRender
62
33
  {...toolMessage}
63
34
  requestArgs={requestArgs}
@@ -0,0 +1,51 @@
1
+ import { CSSProperties, memo, useState } from 'react';
2
+ import { Flexbox } from 'react-layout-kit';
3
+
4
+ import Inspectors from './Inspector';
5
+ import Render from './Render';
6
+
7
+ export interface InspectorProps {
8
+ apiName: string;
9
+ arguments?: string;
10
+ identifier: string;
11
+ index: number;
12
+ messageId: string;
13
+ payload: object;
14
+ style?: CSSProperties;
15
+ toolCallId: string;
16
+ type?: string;
17
+ }
18
+
19
+ const Tool = memo<InspectorProps>(
20
+ ({ arguments: requestArgs, apiName, messageId, toolCallId, index, identifier, payload }) => {
21
+ const [showPluginRender, setShowPluginRender] = useState(false);
22
+
23
+ return (
24
+ <Flexbox gap={8} style={{ paddingBottom: 12 }}>
25
+ <Inspectors
26
+ apiName={apiName}
27
+ arguments={requestArgs}
28
+ id={toolCallId}
29
+ identifier={identifier}
30
+ index={index}
31
+ messageId={messageId}
32
+ payload={payload}
33
+ setShowPluginRender={setShowPluginRender}
34
+ showPluginRender={showPluginRender}
35
+ />
36
+ <Render
37
+ messageId={messageId}
38
+ requestArgs={requestArgs}
39
+ setShowPluginRender={setShowPluginRender}
40
+ showPluginRender={showPluginRender}
41
+ toolCallId={toolCallId}
42
+ toolIndex={index}
43
+ />
44
+ </Flexbox>
45
+ );
46
+ },
47
+ );
48
+
49
+ Tool.displayName = 'AssistantTool';
50
+
51
+ export default Tool;
@@ -0,0 +1,58 @@
1
+ import { Alert } from '@lobehub/ui';
2
+ import { Button } from 'antd';
3
+ import isEqual from 'fast-deep-equal';
4
+ import { memo, useState } from 'react';
5
+ import { useTranslation } from 'react-i18next';
6
+ import { Flexbox } from 'react-layout-kit';
7
+
8
+ import { useChatStore } from '@/store/chat';
9
+ import { dbMessageSelectors } from '@/store/chat/selectors';
10
+ import { UIChatMessage } from '@/types/message';
11
+
12
+ import ToolItem from './ToolItem';
13
+
14
+ interface ToolMessageProps {
15
+ id: string;
16
+ index: number;
17
+ }
18
+
19
+ const Tool = memo<ToolMessageProps>(({ id, index }) => {
20
+ const { t } = useTranslation('plugin');
21
+ const item = useChatStore(dbMessageSelectors.getDbMessageById(id), isEqual) as UIChatMessage;
22
+ const deleteToolMessage = useChatStore((s) => s.deleteToolMessage);
23
+ const [loading, setLoading] = useState(false);
24
+
25
+ const handleDelete = async () => {
26
+ setLoading(true);
27
+ try {
28
+ await deleteToolMessage(id);
29
+ } finally {
30
+ setLoading(false);
31
+ }
32
+ };
33
+
34
+ return (
35
+ <Flexbox gap={4} paddingBlock={12} paddingInline={12}>
36
+ <Alert
37
+ action={
38
+ <Button loading={loading} onClick={handleDelete} size={'small'} type={'primary'}>
39
+ {t('inspector.delete')}
40
+ </Button>
41
+ }
42
+ message={t('inspector.orphanedToolCall')}
43
+ type={'warning'}
44
+ variant={'borderless'}
45
+ />
46
+ {item.plugin && (
47
+ <ToolItem
48
+ {...item.plugin}
49
+ index={index}
50
+ messageId={id}
51
+ payload={item.plugin || {}}
52
+ toolCallId={item.tool_call_id!}
53
+ />
54
+ )}
55
+ </Flexbox>
56
+ );
57
+ });
58
+ export default Tool;
@@ -18,6 +18,7 @@ import { InPortalThreadContext } from '../context/InPortalThreadContext';
18
18
  import AssistantMessage from './Assistant';
19
19
  import GroupMessage from './Group';
20
20
  import SupervisorMessage from './Supervisor';
21
+ import ToolMessage from './Tool';
21
22
  import UserMessage from './User';
22
23
 
23
24
  const useStyles = createStyles(({ css, prefixCls }) => ({
@@ -133,6 +134,10 @@ const Item = memo<ChatListItemProps>(
133
134
  return <GroupMessage disableEditing={disableEditing} id={id} index={index} />;
134
135
  }
135
136
 
137
+ case 'tool': {
138
+ return <ToolMessage id={id} index={index} />;
139
+ }
140
+
136
141
  case 'supervisor': {
137
142
  return <SupervisorMessage disableEditing={disableEditing} id={id} index={index} />;
138
143
  }
@@ -12,7 +12,7 @@ import { useIsMobile } from '@/hooks/useIsMobile';
12
12
  import { useAgentStore } from '@/store/agent';
13
13
  import { agentSelectors } from '@/store/agent/selectors';
14
14
  import { useChatStore } from '@/store/chat';
15
- import { chatSelectors, topicSelectors } from '@/store/chat/selectors';
15
+ import { dbMessageSelectors, topicSelectors } from '@/store/chat/selectors';
16
16
 
17
17
  import { useStyles } from '../style';
18
18
  import Preview from './Preview';
@@ -50,7 +50,7 @@ const ShareImage = memo(() => {
50
50
  ];
51
51
 
52
52
  const systemRole = useAgentStore(agentSelectors.currentAgentSystemRole);
53
- const messages = useChatStore(chatSelectors.activeBaseChats, isEqual);
53
+ const messages = useChatStore(dbMessageSelectors.activeDbMessages, isEqual);
54
54
  const data = generateMessages({ ...fieldValue, messages, systemRole });
55
55
  const content = JSON.stringify(data, null, 2);
56
56
 
@@ -0,0 +1,20 @@
1
+ import { NextRequest } from 'next/server';
2
+
3
+ /**
4
+ * Prepare Request object for tRPC fetchRequestHandler
5
+ *
6
+ * This function solves the "Response body object should not be disturbed or locked" error
7
+ * that occurs in Next.js 16 when the request body stream has been consumed or locked
8
+ * by Next.js internal mechanisms.
9
+ *
10
+ * By cloning the Request object, we create an independent body stream that tRPC can safely read.
11
+ *
12
+ * @see https://github.com/vercel/next.js/issues/83453
13
+ * @param req - The original NextRequest object
14
+ * @returns A cloned Request object with an independent body stream
15
+ */
16
+ export function prepareRequestForTRPC(req: NextRequest): Request {
17
+ // Clone the Request to create an independent body stream
18
+ // This ensures tRPC can read the body even if the original request's body was disturbed
19
+ return req.clone();
20
+ }
@@ -237,6 +237,9 @@ export default {
237
237
  },
238
238
  inspector: {
239
239
  args: '查看参数列表',
240
+ delete: '删除工具调用',
241
+ orphanedToolCall:
242
+ '该工具调用消息可能因异常原因成为孤立消息,这会影响 Agent 的正常执行,请将其移除',
240
243
  pluginRender: '查看插件界面',
241
244
  },
242
245
  list: {
@@ -252,6 +255,11 @@ export default {
252
255
  },
253
256
  localSystem: {
254
257
  apiName: {
258
+ editLocalFile: '编辑文件',
259
+ getCommandOutput: '获取代码输出',
260
+ globLocalFiles: '匹配搜索文件',
261
+ grepContent: '搜索内容',
262
+ killCommand: '终止代码执行',
255
263
  listLocalFiles: '查看文件列表',
256
264
  moveLocalFiles: '移动文件',
257
265
  readLocalFile: '读取文件内容',
@@ -227,13 +227,16 @@ describe('chatMessage actions', () => {
227
227
  expect(replaceMessagesSpy).toHaveBeenCalledWith(mockMessages);
228
228
  });
229
229
 
230
- it('deleteMessage should remove messages with tools', async () => {
230
+ it('deleteMessage should remove the message only', async () => {
231
231
  const { result } = renderHook(() => useChatStore());
232
232
  const messageId = 'message-id';
233
233
  const removeMessagesSpy = vi.spyOn(messageService, 'removeMessages');
234
- const mockMessages = [{ id: 'remaining-message' }] as any;
234
+ const mockMessages = [
235
+ { id: '2', tool_call_id: 'tool1', role: 'tool' },
236
+ { id: '3', tool_call_id: 'tool2', role: 'tool' },
237
+ ] as any;
235
238
 
236
- // Mock the service to return messages
239
+ // Mock the service to return remaining messages (orphaned tool messages)
237
240
  (messageService.removeMessages as Mock).mockResolvedValue({
238
241
  success: true,
239
242
  messages: mockMessages,
@@ -258,7 +261,8 @@ describe('chatMessage actions', () => {
258
261
  await result.current.deleteMessage(messageId);
259
262
  });
260
263
 
261
- expect(removeMessagesSpy).toHaveBeenCalledWith([messageId, '2', '3'], {
264
+ // Only the message itself should be deleted, tool messages remain as orphaned
265
+ expect(removeMessagesSpy).toHaveBeenCalledWith([messageId], {
262
266
  sessionId: 'session-id',
263
267
  topicId: undefined,
264
268
  });
@@ -41,6 +41,7 @@ export interface MessageOptimisticUpdateAction {
41
41
  * delete the message content with optimistic update
42
42
  */
43
43
  optimisticDeleteMessage: (id: string) => Promise<void>;
44
+ optimisticDeleteMessages: (ids: string[]) => Promise<void>;
44
45
 
45
46
  /**
46
47
  * update the message content with optimistic update
@@ -154,6 +155,17 @@ export const messageOptimisticUpdate: StateCreator<
154
155
  }
155
156
  },
156
157
 
158
+ optimisticDeleteMessages: async (ids) => {
159
+ get().internal_dispatchMessage({ ids, type: 'deleteMessages' });
160
+ const result = await messageService.removeMessages(ids, {
161
+ sessionId: get().activeId,
162
+ topicId: get().activeTopicId,
163
+ });
164
+ if (result?.success && result.messages) {
165
+ get().replaceMessages(result.messages);
166
+ }
167
+ },
168
+
157
169
  optimisticUpdateMessageContent: async (id, content, extra) => {
158
170
  const {
159
171
  internal_dispatchMessage,
@@ -31,6 +31,7 @@ export interface MessagePublicApiAction {
31
31
  */
32
32
  clearMessage: () => Promise<void>;
33
33
  deleteMessage: (id: string) => Promise<void>;
34
+ deleteAssistantMessage: (id: string) => Promise<void>;
34
35
  deleteDBMessage: (id: string) => Promise<void>;
35
36
  deleteToolMessage: (id: string) => Promise<void>;
36
37
  clearAllMessages: () => Promise<void>;
@@ -97,12 +98,28 @@ export const messagePublicApi: StateCreator<
97
98
  }
98
99
  },
99
100
 
101
+ deleteAssistantMessage: async (id) => {
102
+ const message = dbMessageSelectors.getDbMessageById(id)(get());
103
+ if (!message) return;
104
+
105
+ let ids = [message.id];
106
+ if (message.tools) {
107
+ const allMessages = dbMessageSelectors.activeDbMessages(get());
108
+
109
+ const toolMessageIds = message.tools.flatMap((tool) => {
110
+ const messages = allMessages.filter((m) => m.tool_call_id === tool.id);
111
+ return messages.map((m) => m.id);
112
+ });
113
+ ids = ids.concat(toolMessageIds);
114
+ }
115
+
116
+ await get().optimisticDeleteMessages(ids);
117
+ },
100
118
  deleteMessage: async (id) => {
101
119
  const message = displayMessageSelectors.getDisplayMessageById(id)(get());
102
120
  if (!message) return;
103
121
 
104
122
  let ids = [message.id];
105
- const allMessages = displayMessageSelectors.activeDisplayMessages(get());
106
123
 
107
124
  // Handle assistantGroup messages: delete all child blocks and tool results
108
125
  if (message.role === 'assistantGroup' && message.children) {
@@ -117,23 +134,8 @@ export const messagePublicApi: StateCreator<
117
134
  });
118
135
  ids = ids.concat(toolResultIds);
119
136
  }
120
- // Handle regular messages with tools: find and delete related tool messages
121
- else if (message.tools) {
122
- const toolMessageIds = message.tools.flatMap((tool) => {
123
- const messages = allMessages.filter((m) => m.tool_call_id === tool.id);
124
- return messages.map((m) => m.id);
125
- });
126
- ids = ids.concat(toolMessageIds);
127
- }
128
137
 
129
- get().internal_dispatchMessage({ type: 'deleteMessages', ids });
130
- const result = await messageService.removeMessages(ids, {
131
- sessionId: get().activeId,
132
- topicId: get().activeTopicId,
133
- });
134
- if (result?.success && result.messages) {
135
- get().replaceMessages(result.messages);
136
- }
138
+ await get().optimisticDeleteMessages(ids);
137
139
  },
138
140
 
139
141
  deleteDBMessage: async (id) => {
@@ -203,6 +203,33 @@ export const LocalSystemManifest: BuiltinToolManifest = {
203
203
  type: 'object',
204
204
  },
205
205
  },
206
+ {
207
+ description:
208
+ 'Perform exact string replacements in files. Must read the file first before editing.',
209
+ name: LocalSystemApiName.editLocalFile,
210
+ parameters: {
211
+ properties: {
212
+ file_path: {
213
+ description: 'The absolute path to the file to modify',
214
+ type: 'string',
215
+ },
216
+ new_string: {
217
+ description: 'The text to replace with (must differ from old_string)',
218
+ type: 'string',
219
+ },
220
+ old_string: {
221
+ description: 'The exact text to replace',
222
+ type: 'string',
223
+ },
224
+ replace_all: {
225
+ description: 'Replace all occurrences of old_string (default: false)',
226
+ type: 'boolean',
227
+ },
228
+ },
229
+ required: ['file_path', 'old_string', 'new_string'],
230
+ type: 'object',
231
+ },
232
+ },
206
233
  {
207
234
  description:
208
235
  'Execute a shell command and return its output. Supports both synchronous and background execution with timeout control.',
@@ -349,33 +376,6 @@ export const LocalSystemManifest: BuiltinToolManifest = {
349
376
  type: 'object',
350
377
  },
351
378
  },
352
- {
353
- description:
354
- 'Perform exact string replacements in files. Must read the file first before editing.',
355
- name: LocalSystemApiName.editLocalFile,
356
- parameters: {
357
- properties: {
358
- file_path: {
359
- description: 'The absolute path to the file to modify',
360
- type: 'string',
361
- },
362
- new_string: {
363
- description: 'The text to replace with (must differ from old_string)',
364
- type: 'string',
365
- },
366
- old_string: {
367
- description: 'The exact text to replace',
368
- type: 'string',
369
- },
370
- replace_all: {
371
- description: 'Replace all occurrences of old_string (default: false)',
372
- type: 'boolean',
373
- },
374
- },
375
- required: ['file_path', 'old_string', 'new_string'],
376
- type: 'object',
377
- },
378
- },
379
379
  ],
380
380
  identifier: 'lobe-local-system',
381
381
  meta: {
@@ -19,17 +19,17 @@ You have access to a set of tools to interact with the user's local file system:
19
19
  1. **listLocalFiles**: Lists files and directories in a specified path.
20
20
  2. **readLocalFile**: Reads the content of a specified file, optionally within a line range. You can read file types such as Word, Excel, PowerPoint, PDF, and plain text files.
21
21
  3. **writeLocalFile**: Write content to a specific file, only support plain text file like \`.text\` or \`.md\`
22
- 4. **searchLocalFiles**: Searches for files based on keywords and other criteria using Spotlight (macOS) or native search. Use this tool to find files if the user is unsure about the exact path.
22
+ 4. **editLocalFile**: Performs exact string replacements in files. Must read the file first before editing.
23
23
  5. **renameLocalFile**: Renames a single file or directory in its current location.
24
24
  6. **moveLocalFiles**: Moves multiple files or directories. Can be used for renaming during the move.
25
- 7. **editLocalFile**: Performs exact string replacements in files. Must read the file first before editing.
26
25
 
27
26
  **Shell Commands:**
28
- 8. **runCommand**: Execute shell commands with timeout control. Supports both synchronous and background execution. When providing a description, always use the same language as the user's input.
29
- 9. **getCommandOutput**: Retrieve output from running background commands. Returns only new output since last check.
30
- 10. **killCommand**: Terminate a running background shell command by its ID.
27
+ 7. **runCommand**: Execute shell commands with timeout control. Supports both synchronous and background execution. When providing a description, always use the same language as the user's input.
28
+ 8. **getCommandOutput**: Retrieve output from running background commands. Returns only new output since last check.
29
+ 9. **killCommand**: Terminate a running background shell command by its ID.
31
30
 
32
31
  **Search & Find:**
32
+ 10. **searchLocalFiles**: Searches for files based on keywords and other criteria using Spotlight (macOS) or native search. Use this tool to find files if the user is unsure about the exact path.
33
33
  11. **grepContent**: Search for content within files using regex patterns. Supports various output modes, filtering, and context lines.
34
34
  12. **globLocalFiles**: Find files matching glob patterns (e.g., "**/*.js", "*.{ts,tsx}").
35
35
  </core_capabilities>
@@ -37,9 +37,9 @@ You have access to a set of tools to interact with the user's local file system:
37
37
  <workflow>
38
38
  1. Understand the user's request regarding local operations (files, commands, searches).
39
39
  2. Select the appropriate tool:
40
- - File operations: listLocalFiles, readLocalFile, writeLocalFile, editLocalFile, searchLocalFiles, renameLocalFile, moveLocalFiles
40
+ - File operations: listLocalFiles, readLocalFile, writeLocalFile, editLocalFile, renameLocalFile, moveLocalFiles
41
41
  - Shell commands: runCommand, getCommandOutput, killCommand
42
- - Search/Find: grepContent, globLocalFiles
42
+ - Search/Find: searchLocalFiles, grepContent, globLocalFiles
43
43
  3. Execute the operation. **If the user mentions a common location (like Desktop, Documents, Downloads, etc.) without providing a full path, use the corresponding path from the <user_context> section.**
44
44
  4. Present the results or confirmation.
45
45
  </workflow>
@@ -1,29 +0,0 @@
1
- import { safeParseJSON } from '@lobechat/utils';
2
- import { memo } from 'react';
3
-
4
- import { getBuiltinPlaceholder } from '@/tools/placeholders';
5
-
6
- import Arguments from '../Arguments';
7
-
8
- interface LoadingPlaceholderProps {
9
- apiName: string;
10
- identifier: string;
11
- loading?: boolean;
12
- requestArgs?: string;
13
- }
14
-
15
- const LoadingPlaceholder = memo<LoadingPlaceholderProps>(
16
- ({ identifier, requestArgs, apiName, loading }) => {
17
- const Render = getBuiltinPlaceholder(identifier, apiName);
18
-
19
- if (Render) {
20
- return (
21
- <Render apiName={apiName} args={safeParseJSON(requestArgs) || {}} identifier={identifier} />
22
- );
23
- }
24
-
25
- return <Arguments arguments={requestArgs} shine={loading} />;
26
- },
27
- );
28
-
29
- export default LoadingPlaceholder;