@lobehub/lobehub 2.0.0-next.187 → 2.0.0-next.189

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 (177) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/changelog/v1.json +18 -0
  3. package/e2e/CLAUDE.md +109 -2
  4. package/e2e/docs/llm-mock.md +68 -0
  5. package/e2e/docs/local-setup.md +354 -0
  6. package/e2e/docs/testing-tips.md +94 -0
  7. package/e2e/src/features/journeys/agent/agent-conversation.feature +0 -32
  8. package/e2e/src/mocks/llm/index.ts +6 -6
  9. package/e2e/src/steps/agent/conversation.steps.ts +3 -471
  10. package/locales/ar/models.json +89 -5
  11. package/locales/ar/plugin.json +5 -0
  12. package/locales/ar/providers.json +1 -0
  13. package/locales/bg-BG/models.json +68 -0
  14. package/locales/bg-BG/plugin.json +5 -0
  15. package/locales/bg-BG/providers.json +1 -0
  16. package/locales/de-DE/models.json +85 -0
  17. package/locales/de-DE/plugin.json +5 -0
  18. package/locales/de-DE/providers.json +1 -0
  19. package/locales/en-US/models.json +11 -10
  20. package/locales/en-US/plugin.json +5 -0
  21. package/locales/en-US/providers.json +1 -0
  22. package/locales/es-ES/models.json +72 -0
  23. package/locales/es-ES/plugin.json +5 -0
  24. package/locales/es-ES/providers.json +1 -0
  25. package/locales/fa-IR/models.json +86 -0
  26. package/locales/fa-IR/plugin.json +5 -0
  27. package/locales/fa-IR/providers.json +1 -0
  28. package/locales/fr-FR/models.json +49 -0
  29. package/locales/fr-FR/plugin.json +5 -0
  30. package/locales/fr-FR/providers.json +1 -0
  31. package/locales/it-IT/models.json +82 -0
  32. package/locales/it-IT/plugin.json +5 -0
  33. package/locales/it-IT/providers.json +1 -0
  34. package/locales/ja-JP/models.json +42 -5
  35. package/locales/ja-JP/plugin.json +5 -0
  36. package/locales/ja-JP/providers.json +1 -0
  37. package/locales/ko-KR/models.json +54 -0
  38. package/locales/ko-KR/plugin.json +5 -0
  39. package/locales/ko-KR/providers.json +1 -0
  40. package/locales/nl-NL/models.json +12 -1
  41. package/locales/nl-NL/plugin.json +5 -0
  42. package/locales/nl-NL/providers.json +1 -0
  43. package/locales/pl-PL/models.json +46 -0
  44. package/locales/pl-PL/plugin.json +5 -0
  45. package/locales/pl-PL/providers.json +1 -0
  46. package/locales/pt-BR/models.json +59 -0
  47. package/locales/pt-BR/plugin.json +5 -0
  48. package/locales/pt-BR/providers.json +1 -0
  49. package/locales/ru-RU/models.json +85 -0
  50. package/locales/ru-RU/plugin.json +5 -0
  51. package/locales/ru-RU/providers.json +1 -0
  52. package/locales/tr-TR/models.json +81 -0
  53. package/locales/tr-TR/plugin.json +5 -0
  54. package/locales/tr-TR/providers.json +1 -0
  55. package/locales/vi-VN/models.json +54 -0
  56. package/locales/vi-VN/plugin.json +5 -0
  57. package/locales/vi-VN/providers.json +1 -0
  58. package/locales/zh-CN/models.json +42 -5
  59. package/locales/zh-CN/plugin.json +5 -0
  60. package/locales/zh-CN/providers.json +1 -0
  61. package/locales/zh-TW/models.json +85 -0
  62. package/locales/zh-TW/plugin.json +5 -0
  63. package/locales/zh-TW/providers.json +1 -0
  64. package/package.json +2 -2
  65. package/packages/builtin-tool-gtd/src/manifest.ts +13 -8
  66. package/packages/builtin-tool-gtd/src/systemRole.ts +54 -19
  67. package/packages/builtin-tool-knowledge-base/package.json +1 -0
  68. package/packages/builtin-tool-knowledge-base/src/client/Inspector/ReadKnowledge/index.tsx +97 -0
  69. package/packages/builtin-tool-knowledge-base/src/client/Inspector/SearchKnowledgeBase/index.tsx +75 -0
  70. package/packages/builtin-tool-knowledge-base/src/client/Inspector/index.ts +11 -0
  71. package/packages/builtin-tool-knowledge-base/src/client/Render/ReadKnowledge/FileCard.tsx +12 -12
  72. package/packages/builtin-tool-knowledge-base/src/client/Render/ReadKnowledge/index.tsx +16 -25
  73. package/packages/builtin-tool-knowledge-base/src/client/Render/SearchKnowledgeBase/Item/index.tsx +21 -47
  74. package/packages/builtin-tool-knowledge-base/src/client/Render/SearchKnowledgeBase/index.tsx +19 -31
  75. package/packages/builtin-tool-knowledge-base/src/client/Render/index.ts +0 -5
  76. package/packages/builtin-tool-knowledge-base/src/client/index.ts +5 -1
  77. package/packages/builtin-tool-knowledge-base/src/executor/index.ts +119 -0
  78. package/packages/builtin-tool-local-system/package.json +1 -0
  79. package/packages/builtin-tool-local-system/src/client/Inspector/EditLocalFile/index.tsx +44 -29
  80. package/packages/builtin-tool-local-system/src/client/Inspector/GrepContent/index.tsx +20 -18
  81. package/packages/builtin-tool-local-system/src/client/Inspector/ListLocalFiles/index.tsx +76 -0
  82. package/packages/builtin-tool-local-system/src/client/Inspector/ReadLocalFile/index.tsx +8 -32
  83. package/packages/builtin-tool-local-system/src/client/Inspector/RenameLocalFile/index.tsx +62 -0
  84. package/packages/builtin-tool-local-system/src/client/Inspector/SearchLocalFiles/index.tsx +17 -11
  85. package/packages/builtin-tool-local-system/src/client/Inspector/WriteLocalFile/index.tsx +61 -0
  86. package/packages/builtin-tool-local-system/src/client/Inspector/index.ts +6 -0
  87. package/packages/builtin-tool-local-system/src/client/Render/EditLocalFile/index.tsx +6 -1
  88. package/packages/builtin-tool-local-system/src/client/Render/SearchFiles/SearchQuery/SearchView.tsx +19 -31
  89. package/packages/builtin-tool-local-system/src/client/Render/SearchFiles/SearchQuery/index.tsx +2 -42
  90. package/packages/builtin-tool-local-system/src/client/Render/index.ts +0 -2
  91. package/packages/builtin-tool-local-system/src/client/components/FilePathDisplay.tsx +56 -0
  92. package/packages/builtin-tool-local-system/src/client/components/index.ts +2 -0
  93. package/packages/builtin-tool-local-system/src/executor/index.ts +435 -0
  94. package/packages/builtin-tool-web-browsing/src/client/Inspector/Search/index.tsx +32 -5
  95. package/packages/model-runtime/src/core/contextBuilders/google.test.ts +84 -0
  96. package/packages/model-runtime/src/core/contextBuilders/google.ts +37 -1
  97. package/src/app/[variants]/(main)/chat/_layout/Sidebar/Topic/List/Item/Actions.tsx +4 -13
  98. package/src/app/[variants]/(main)/chat/_layout/Sidebar/Topic/List/Item/index.tsx +23 -29
  99. package/src/app/[variants]/(main)/chat/_layout/Sidebar/Topic/List/Item/useDropdownMenu.tsx +3 -3
  100. package/src/app/[variants]/(main)/chat/_layout/Sidebar/Topic/TopicListContent/ThreadList/ThreadItem/Actions.tsx +4 -13
  101. package/src/app/[variants]/(main)/chat/_layout/Sidebar/Topic/TopicListContent/ThreadList/ThreadItem/index.tsx +10 -18
  102. package/src/app/[variants]/(main)/chat/_layout/Sidebar/Topic/TopicListContent/ThreadList/ThreadItem/useDropdownMenu.tsx +3 -3
  103. package/src/app/[variants]/(main)/community/(detail)/assistant/features/Sidebar/ActionButton/AddAgent.tsx +47 -27
  104. package/src/app/[variants]/(main)/community/(detail)/user/features/UserAgentCard.tsx +4 -3
  105. package/src/app/[variants]/(main)/group/_layout/Sidebar/Topic/List/Item/Actions.tsx +4 -13
  106. package/src/app/[variants]/(main)/group/_layout/Sidebar/Topic/List/Item/index.tsx +23 -29
  107. package/src/app/[variants]/(main)/group/_layout/Sidebar/Topic/List/Item/useDropdownMenu.tsx +3 -3
  108. package/src/app/[variants]/(main)/group/_layout/Sidebar/Topic/TopicListContent/ThreadList/ThreadItem/Actions.tsx +4 -13
  109. package/src/app/[variants]/(main)/group/_layout/Sidebar/Topic/TopicListContent/ThreadList/ThreadItem/index.tsx +10 -18
  110. package/src/app/[variants]/(main)/group/_layout/Sidebar/Topic/TopicListContent/ThreadList/ThreadItem/useDropdownMenu.tsx +3 -3
  111. package/src/app/[variants]/(main)/group/profile/features/AgentBuilder/TopicSelector.tsx +18 -20
  112. package/src/app/[variants]/(main)/home/_layout/Body/Agent/List/AgentGroupItem/index.tsx +19 -25
  113. package/src/app/[variants]/(main)/home/_layout/Body/Agent/List/AgentItem/index.tsx +21 -26
  114. package/src/app/[variants]/(main)/home/_layout/Body/Agent/List/Item/Actions.tsx +4 -13
  115. package/src/app/[variants]/(main)/home/_layout/Body/Agent/List/Item/useDropdownMenu.tsx +3 -3
  116. package/src/app/[variants]/(main)/home/_layout/Body/Project/List/Actions.tsx +4 -13
  117. package/src/app/[variants]/(main)/home/_layout/Body/Project/List/Item.tsx +8 -15
  118. package/src/app/[variants]/(main)/home/_layout/Body/Project/List/useDropdownMenu.tsx +3 -3
  119. package/src/app/[variants]/(main)/home/_layout/Header/components/AddButton.tsx +3 -4
  120. package/src/app/[variants]/(main)/page/_layout/Body/List/Item/Actions.tsx +4 -13
  121. package/src/app/[variants]/(main)/page/_layout/Body/List/Item/index.tsx +13 -20
  122. package/src/app/[variants]/(main)/page/_layout/Body/List/Item/useDropdownMenu.tsx +3 -3
  123. package/src/app/[variants]/(main)/resource/(home)/_layout/Body/LibraryList/List/Item/Actions.tsx +4 -13
  124. package/src/app/[variants]/(main)/resource/(home)/_layout/Body/LibraryList/List/Item/index.tsx +16 -23
  125. package/src/app/[variants]/(main)/resource/(home)/_layout/Body/LibraryList/List/Item/useDropdownMenu.tsx +3 -3
  126. package/src/app/[variants]/(main)/resource/library/_layout/Header/LibraryHead.tsx +4 -6
  127. package/src/features/AgentBuilder/TopicSelector.tsx +18 -17
  128. package/src/features/Conversation/ChatItem/style.ts +7 -0
  129. package/src/features/Conversation/Messages/Assistant/Actions/Error.tsx +1 -3
  130. package/src/features/Conversation/Messages/Assistant/Actions/index.tsx +37 -16
  131. package/src/features/Conversation/Messages/AssistantGroup/Actions/index.tsx +36 -17
  132. package/src/features/Conversation/Messages/Supervisor/Actions/index.tsx +36 -17
  133. package/src/features/Conversation/Messages/Task/Actions/Error.tsx +1 -3
  134. package/src/features/Conversation/Messages/Task/Actions/index.tsx +31 -15
  135. package/src/features/Conversation/Messages/User/Actions/index.tsx +1 -1
  136. package/src/features/Conversation/Messages/index.tsx +8 -59
  137. package/src/features/Conversation/components/ShareMessageModal/index.tsx +1 -1
  138. package/src/features/Conversation/hooks/useChatItemContextMenu.tsx +313 -83
  139. package/src/features/NavPanel/components/NavItem.tsx +33 -3
  140. package/src/features/PageEditor/Copilot/TopicSelector/Actions.tsx +6 -14
  141. package/src/features/PageEditor/Copilot/TopicSelector/TopicItem.tsx +1 -0
  142. package/src/features/PageEditor/Copilot/TopicSelector/useDropdownMenu.tsx +6 -3
  143. package/src/features/ResourceManager/components/Explorer/ItemDropdown/DropdownMenu.tsx +12 -35
  144. package/src/features/ResourceManager/components/Explorer/ItemDropdown/useFileItemDropdown.tsx +4 -8
  145. package/src/features/ResourceManager/components/Explorer/ListView/ListItem/index.tsx +162 -160
  146. package/src/features/ResourceManager/components/Explorer/MasonryView/MasonryFileItem/index.tsx +16 -8
  147. package/src/features/ResourceManager/components/Explorer/ToolBar/ActionIconWithChevron.tsx +4 -3
  148. package/src/features/ResourceManager/components/Explorer/ToolBar/BatchActionsDropdown.tsx +6 -12
  149. package/src/features/ResourceManager/components/Explorer/ToolBar/SortDropdown.tsx +8 -8
  150. package/src/features/ResourceManager/components/Explorer/ToolBar/ViewSwitcher.tsx +8 -11
  151. package/src/features/ResourceManager/components/Tree/index.tsx +121 -122
  152. package/src/helpers/toolEngineering/index.ts +1 -1
  153. package/src/layout/GlobalProvider/index.tsx +5 -2
  154. package/src/locales/default/plugin.ts +6 -0
  155. package/src/server/modules/Mecha/AgentToolsEngine/__tests__/index.test.ts +1 -1
  156. package/src/server/modules/Mecha/AgentToolsEngine/index.ts +1 -1
  157. package/src/store/chat/slices/builtinTool/actions/index.ts +1 -11
  158. package/src/store/tool/slices/builtin/executors/index.ts +4 -0
  159. package/src/styles/global.ts +6 -0
  160. package/src/styles/text.ts +1 -1
  161. package/src/tools/executionRuntimes.ts +3 -8
  162. package/src/tools/identifiers.ts +1 -1
  163. package/src/tools/index.ts +1 -1
  164. package/src/tools/inspectors.ts +5 -0
  165. package/src/tools/renders.ts +6 -12
  166. package/packages/builtin-tool-local-system/src/client/Render/RenameLocalFile/index.tsx +0 -37
  167. package/src/features/Conversation/components/ContextMenu.tsx +0 -418
  168. package/src/store/chat/slices/builtinTool/actions/__tests__/localSystem.test.ts +0 -201
  169. package/src/store/chat/slices/builtinTool/actions/knowledgeBase.ts +0 -163
  170. package/src/store/chat/slices/builtinTool/actions/localSystem.ts +0 -241
  171. package/src/tools/knowledge-base/ExecutionRuntime/index.ts +0 -25
  172. package/src/tools/knowledge-base/Render/ReadKnowledge/index.tsx +0 -29
  173. package/src/tools/knowledge-base/Render/SearchKnowledgeBase/index.tsx +0 -29
  174. package/src/tools/knowledge-base/Render/index.ts +0 -7
  175. package/src/tools/knowledge-base/index.ts +0 -12
  176. package/src/tools/local-system/ExecutionRuntime/index.ts +0 -9
  177. package/src/tools/local-system/systemRole.ts +0 -1
@@ -1,418 +0,0 @@
1
- 'use client';
2
-
3
- import { type UIChatMessage } from '@lobechat/types';
4
- import { ActionIcon } from '@lobehub/ui';
5
- import type { ActionIconGroupEvent, ActionIconGroupItemType } from '@lobehub/ui';
6
- import { Dropdown, type MenuProps } from 'antd';
7
- import { App } from 'antd';
8
- import { createStaticStyles } from 'antd-style';
9
- import isEqual from 'fast-deep-equal';
10
- import {
11
- type ComponentType,
12
- type ReactNode,
13
- type RefObject,
14
- isValidElement,
15
- memo,
16
- useCallback,
17
- useMemo,
18
- useState,
19
- } from 'react';
20
- import { createPortal } from 'react-dom';
21
- import { useTranslation } from 'react-i18next';
22
- import type { VListHandle } from 'virtua';
23
-
24
- import { useSessionStore } from '@/store/session';
25
- import { sessionSelectors } from '@/store/session/selectors';
26
-
27
- import { useChatListActionsBar } from '../hooks/useChatListActionsBar';
28
- import {
29
- dataSelectors,
30
- messageStateSelectors,
31
- useConversationStore,
32
- useConversationStoreApi,
33
- } from '../store';
34
- import ShareMessageModal from './ShareMessageModal';
35
-
36
- interface ActionMenuItem extends ActionIconGroupItemType {
37
- children?: { key: string; label: ReactNode }[];
38
- disable?: boolean;
39
- popupClassName?: string;
40
- }
41
-
42
- type MenuItem = ActionMenuItem | { type: 'divider' };
43
- type ContextMenuEvent = ActionIconGroupEvent & { selectedText?: string };
44
-
45
- const styles = createStaticStyles(({ css }) => ({
46
- contextMenu: css`
47
- position: fixed;
48
- z-index: 1000;
49
- min-width: 160px;
50
-
51
- .ant-dropdown-menu {
52
- border: none;
53
- border-radius: 6px;
54
- box-shadow: 0 4px 12px color-mix(in srgb, black 15%, transparent);
55
- }
56
- `,
57
- trigger: css`
58
- pointer-events: none;
59
-
60
- position: fixed;
61
-
62
- width: 1px;
63
- height: 1px;
64
-
65
- opacity: 0;
66
- `,
67
- }));
68
-
69
- interface ContextMenuProps {
70
- id: string;
71
- inPortalThread: boolean;
72
- index: number;
73
- onClose: () => void;
74
- position: { x: number; y: number };
75
- selectedText?: string;
76
- topic?: string | null;
77
- virtuaRef?: RefObject<VListHandle | null> | null;
78
- visible: boolean;
79
- }
80
-
81
- const ContextMenu = memo<ContextMenuProps>(
82
- ({ visible, position, selectedText, id, index, inPortalThread, topic, virtuaRef, onClose }) => {
83
- const { message } = App.useApp();
84
- const { t } = useTranslation('common');
85
- const [shareMessage, setShareMessage] = useState<UIChatMessage | null>(null);
86
- const [isShareModalOpen, setShareModalOpen] = useState(false);
87
-
88
- const storeApi = useConversationStoreApi();
89
-
90
- const [role, error, isCollapsed, hasThread, isRegenerating] = useConversationStore((s) => {
91
- const item = dataSelectors.getDisplayMessageById(id)(s);
92
- return [
93
- item?.role,
94
- item?.error,
95
- messageStateSelectors.isMessageCollapsed(id)(s),
96
- messageStateSelectors.hasThreadBySourceMsgId(id)(s),
97
- messageStateSelectors.isMessageRegenerating(id)(s),
98
- ];
99
- }, isEqual);
100
-
101
- const isThreadMode = useConversationStore(messageStateSelectors.isThreadMode);
102
- const isGroupSession = useSessionStore(sessionSelectors.isCurrentSessionGroupSession);
103
- const actionsBar = useChatListActionsBar({ hasThread, isRegenerating });
104
- const inThread = isThreadMode || inPortalThread;
105
-
106
- const [
107
- toggleMessageEditing,
108
- deleteMessage,
109
- regenerateUserMessage,
110
- regenerateAssistantMessage,
111
- translateMessage,
112
- ttsMessage,
113
- delAndRegenerateMessage,
114
- copyMessage,
115
- openThreadCreator,
116
- resendThreadMessage,
117
- delAndResendThreadMessage,
118
- toggleMessageCollapsed,
119
- ] = useConversationStore((s) => [
120
- s.toggleMessageEditing,
121
- s.deleteMessage,
122
- s.regenerateUserMessage,
123
- s.regenerateAssistantMessage,
124
- s.translateMessage,
125
- s.ttsMessage,
126
- s.delAndRegenerateMessage,
127
- s.copyMessage,
128
- s.openThreadCreator,
129
- s.resendThreadMessage,
130
- s.delAndResendThreadMessage,
131
- s.toggleMessageCollapsed,
132
- ]);
133
-
134
- const getMessage = useCallback(
135
- () => dataSelectors.getDisplayMessageById(id)(storeApi.getState()),
136
- [id, storeApi],
137
- );
138
-
139
- const menuItems = useMemo<MenuItem[]>(() => {
140
- if (!role) return [];
141
-
142
- const {
143
- branching,
144
- collapse,
145
- copy,
146
- del,
147
- delAndRegenerate,
148
- divider,
149
- edit,
150
- expand,
151
- regenerate,
152
- share,
153
- translate,
154
- tts,
155
- } = actionsBar;
156
-
157
- if (role === 'assistant') {
158
- if (error) {
159
- return [edit, copy, divider, del, divider, regenerate].filter(Boolean) as MenuItem[];
160
- }
161
-
162
- const collapseAction = isCollapsed ? expand : collapse;
163
- const list: MenuItem[] = [edit, copy, collapseAction];
164
-
165
- if (!inThread && !isGroupSession) list.push(branching);
166
-
167
- list.push(
168
- divider,
169
- tts,
170
- translate,
171
- divider,
172
- share,
173
- divider,
174
- regenerate,
175
- delAndRegenerate,
176
- del,
177
- );
178
-
179
- return list.filter(Boolean) as MenuItem[];
180
- }
181
-
182
- if (role === 'assistantGroup') {
183
- if (error) {
184
- return [edit, copy, divider, del, divider, regenerate].filter(Boolean) as MenuItem[];
185
- }
186
-
187
- const collapseAction = isCollapsed ? expand : collapse;
188
- const list: MenuItem[] = [
189
- edit,
190
- copy,
191
- collapseAction,
192
- divider,
193
- share,
194
- divider,
195
- regenerate,
196
- del,
197
- ];
198
-
199
- return list.filter(Boolean) as MenuItem[];
200
- }
201
-
202
- if (role === 'user') {
203
- const list: MenuItem[] = [edit, copy];
204
-
205
- if (!inThread) list.push(branching);
206
-
207
- list.push(divider, tts, translate, divider, regenerate, del);
208
-
209
- return list.filter(Boolean) as MenuItem[];
210
- }
211
-
212
- return [];
213
- }, [actionsBar, error, inThread, isCollapsed, isGroupSession, role]);
214
-
215
- const handleShare = useCallback(() => {
216
- const item = getMessage();
217
- if (!item || item.role !== 'assistant') return;
218
-
219
- setShareMessage(item);
220
- setShareModalOpen(true);
221
- }, [getMessage]);
222
-
223
- const handleShareClose = useCallback(() => {
224
- setShareModalOpen(false);
225
- setShareMessage(null);
226
- }, []);
227
-
228
- const handleAction = useCallback(
229
- async (action: ContextMenuEvent) => {
230
- const item = getMessage();
231
- if (!item) return;
232
-
233
- switch (action.key) {
234
- case 'edit': {
235
- toggleMessageEditing(id, true);
236
- break;
237
- }
238
- case 'copy': {
239
- await copyMessage(id, item.content);
240
- message.success(t('copySuccess'));
241
- break;
242
- }
243
- case 'expand':
244
- case 'collapse': {
245
- toggleMessageCollapsed(id);
246
- break;
247
- }
248
- case 'branching': {
249
- if (!topic) {
250
- message.warning(t('branchingRequiresSavedTopic'));
251
- break;
252
- }
253
- openThreadCreator(id);
254
- break;
255
- }
256
- case 'del': {
257
- deleteMessage(id);
258
- break;
259
- }
260
- case 'regenerate': {
261
- if (inPortalThread) {
262
- resendThreadMessage(id);
263
- } else if (role === 'assistant') {
264
- regenerateAssistantMessage(id);
265
- } else {
266
- regenerateUserMessage(id);
267
- }
268
-
269
- if (item.error) deleteMessage(id);
270
- break;
271
- }
272
- case 'delAndRegenerate': {
273
- if (inPortalThread) {
274
- delAndResendThreadMessage(id);
275
- } else {
276
- delAndRegenerateMessage(id);
277
- }
278
- break;
279
- }
280
- case 'tts': {
281
- ttsMessage(id);
282
- break;
283
- }
284
- case 'share': {
285
- handleShare();
286
- break;
287
- }
288
- }
289
-
290
- if (action.keyPath?.at(-1) === 'translate') {
291
- const lang = action.keyPath[0];
292
- translateMessage(id, lang);
293
- }
294
- },
295
- [
296
- copyMessage,
297
- deleteMessage,
298
- delAndRegenerateMessage,
299
- delAndResendThreadMessage,
300
- getMessage,
301
- handleShare,
302
- id,
303
- index,
304
- inPortalThread,
305
- message,
306
- openThreadCreator,
307
- regenerateAssistantMessage,
308
- regenerateUserMessage,
309
- resendThreadMessage,
310
- role,
311
- t,
312
- toggleMessageCollapsed,
313
- toggleMessageEditing,
314
- topic,
315
- translateMessage,
316
- ttsMessage,
317
- virtuaRef,
318
- ],
319
- );
320
-
321
- const renderIcon = useCallback((iconComponent: ActionIconGroupItemType['icon']) => {
322
- if (!iconComponent) return null;
323
-
324
- if (isValidElement(iconComponent)) {
325
- return <ActionIcon icon={iconComponent} size={'small'} />;
326
- }
327
-
328
- const IconComponent = iconComponent as ComponentType<{ size?: number }>;
329
-
330
- return <ActionIcon icon={<IconComponent size={16} />} size={'small'} />;
331
- }, []);
332
-
333
- const dropdownMenuItems = useMemo(() => {
334
- return (menuItems ?? []).filter(Boolean).map((item) => {
335
- if ('type' in item && item.type === 'divider') return { type: 'divider' as const };
336
-
337
- const actionItem = item as ActionMenuItem;
338
- const children = actionItem.children?.map((child) => ({
339
- key: child.key,
340
- label: child.label,
341
- }));
342
- const disabled =
343
- actionItem.disabled ??
344
- (typeof actionItem.disable === 'boolean' ? actionItem.disable : undefined);
345
-
346
- return {
347
- children,
348
- danger: actionItem.danger,
349
- disabled,
350
- icon: renderIcon(actionItem.icon),
351
- key: actionItem.key,
352
- label: actionItem.label,
353
- popupClassName: actionItem.popupClassName,
354
- };
355
- });
356
- }, [menuItems, renderIcon]);
357
-
358
- const handleMenuClick = useCallback(
359
- (info: Parameters<NonNullable<MenuProps['onClick']>>[0]) => {
360
- const event = {
361
- ...info,
362
- selectedText,
363
- } as ContextMenuEvent;
364
-
365
- handleAction(event);
366
- onClose();
367
- },
368
- [handleAction, onClose, selectedText],
369
- );
370
-
371
- if (!visible || menuItems.length === 0) return null;
372
-
373
- return (
374
- <>
375
- {createPortal(
376
- <>
377
- <div
378
- className={styles.trigger}
379
- style={{
380
- left: position.x,
381
- top: position.y,
382
- }}
383
- />
384
- <Dropdown
385
- menu={{
386
- items: dropdownMenuItems,
387
- onClick: handleMenuClick,
388
- }}
389
- open={visible}
390
- placement="bottomLeft"
391
- trigger={[]}
392
- >
393
- <div
394
- className={styles.contextMenu}
395
- style={{
396
- left: position.x,
397
- top: position.y,
398
- }}
399
- />
400
- </Dropdown>
401
- </>,
402
- document.body,
403
- )}
404
- {shareMessage && (
405
- <ShareMessageModal
406
- message={shareMessage}
407
- onCancel={handleShareClose}
408
- open={isShareModalOpen}
409
- />
410
- )}
411
- </>
412
- );
413
- },
414
- );
415
-
416
- ContextMenu.displayName = 'ContextMenu';
417
-
418
- export default ContextMenu;
@@ -1,201 +0,0 @@
1
- import { LocalFileItem, LocalMoveFilesResultItem } from '@lobechat/electron-client-ipc';
2
- import { describe, expect, it, vi } from 'vitest';
3
-
4
- import { localFileService } from '@/services/electron/localFileService';
5
- import { ChatStore } from '@/store/chat';
6
-
7
- import { localSystemSlice } from '../localSystem';
8
-
9
- vi.mock('@/services/electron/localFileService', () => ({
10
- localFileService: {
11
- listLocalFiles: vi.fn(),
12
- moveLocalFiles: vi.fn(),
13
- readLocalFile: vi.fn(),
14
- readLocalFiles: vi.fn(),
15
- renameLocalFile: vi.fn(),
16
- searchLocalFiles: vi.fn(),
17
- writeFile: vi.fn(),
18
- },
19
- }));
20
-
21
- const mockSet = vi.fn();
22
-
23
- const mockStore = {
24
- completeOperation: vi.fn(),
25
- failOperation: vi.fn(),
26
- internal_triggerLocalFileToolCalling: vi.fn(),
27
- messageOperationMap: {},
28
- optimisticUpdateMessageContent: vi.fn(),
29
- optimisticUpdateMessagePluginError: vi.fn(),
30
- optimisticUpdatePluginArguments: vi.fn(),
31
- optimisticUpdatePluginState: vi.fn(),
32
- set: mockSet,
33
- startOperation: vi.fn().mockReturnValue({
34
- abortController: new AbortController(),
35
- operationId: 'test-op-id',
36
- }),
37
- } as unknown as ChatStore;
38
-
39
- const createStore = () => {
40
- return localSystemSlice(
41
- (set) => ({
42
- ...mockStore,
43
- set,
44
- }),
45
- () => mockStore,
46
- {} as any,
47
- );
48
- };
49
-
50
- describe('localFileSlice', () => {
51
- const store = createStore();
52
-
53
- beforeEach(() => {
54
- vi.clearAllMocks();
55
- });
56
-
57
- describe('internal_triggerLocalFileToolCalling', () => {
58
- it('should handle successful calling', async () => {
59
- const mockContent = 'result content';
60
- const mockState = { state: 'test' };
61
- const mockService = vi
62
- .fn()
63
- .mockResolvedValue({ content: mockContent, state: mockState, success: true });
64
-
65
- await store.internal_triggerLocalFileToolCalling('test-id', mockService);
66
-
67
- expect(mockStore.startOperation).toBeCalled();
68
- expect(mockStore.completeOperation).toBeCalled();
69
- expect(mockStore.optimisticUpdatePluginState).toBeCalled();
70
- expect(mockStore.optimisticUpdateMessageContent).toBeCalled();
71
- });
72
-
73
- it('should handle error', async () => {
74
- const mockError = new Error('test error');
75
- const mockService = vi.fn().mockRejectedValue(mockError);
76
-
77
- await store.internal_triggerLocalFileToolCalling('test-id', mockService);
78
-
79
- expect(mockStore.optimisticUpdateMessagePluginError).toBeCalledWith(
80
- 'test-id',
81
- {
82
- body: mockError,
83
- message: 'test error',
84
- type: 'PluginServerError',
85
- },
86
- { operationId: 'test-op-id' },
87
- );
88
- });
89
- });
90
-
91
- describe('listLocalFiles', () => {
92
- it('should call listLocalFiles service and update state', async () => {
93
- const mockResult: LocalFileItem[] = [
94
- {
95
- name: 'test.txt',
96
- path: '/test.txt',
97
- isDirectory: false,
98
- createdTime: new Date(),
99
- lastAccessTime: new Date(),
100
- modifiedTime: new Date(),
101
- size: 100,
102
- type: 'file',
103
- },
104
- ];
105
- vi.mocked(localFileService.listLocalFiles).mockResolvedValue(mockResult);
106
-
107
- await store.listLocalFiles('test-id', { path: '/test' });
108
-
109
- expect(mockStore.internal_triggerLocalFileToolCalling).toBeCalled();
110
- });
111
- });
112
-
113
- describe('moveLocalFiles', () => {
114
- it('should handle successful move', async () => {
115
- const mockResults = [
116
- {
117
- sourcePath: '/test.txt',
118
- destinationPath: '/target/test.txt',
119
- success: true,
120
- },
121
- ] as unknown as LocalMoveFilesResultItem[];
122
-
123
- vi.mocked(localFileService.moveLocalFiles).mockResolvedValue(mockResults);
124
-
125
- await store.moveLocalFiles('test-id', {
126
- sourcePaths: ['/test.txt'],
127
- destinationDir: '/target',
128
- } as any);
129
-
130
- expect(mockStore.internal_triggerLocalFileToolCalling).toBeCalled();
131
- });
132
- });
133
-
134
- describe('writeLocalFile', () => {
135
- it('should handle successful write', async () => {
136
- vi.mocked(localFileService.writeFile).mockResolvedValue({
137
- success: true,
138
- newPath: '/test.txt',
139
- });
140
-
141
- await store.writeLocalFile('test-id', { path: '/test.txt', content: 'test' });
142
-
143
- expect(mockStore.internal_triggerLocalFileToolCalling).toBeCalled();
144
- });
145
-
146
- it('should handle write error', async () => {
147
- vi.mocked(localFileService.writeFile).mockResolvedValue({
148
- success: false,
149
- error: 'Write failed',
150
- newPath: '/test.txt',
151
- });
152
-
153
- await store.writeLocalFile('test-id', { path: '/test.txt', content: 'test' });
154
-
155
- expect(mockStore.internal_triggerLocalFileToolCalling).toBeCalled();
156
- });
157
- });
158
-
159
- describe('renameLocalFile', () => {
160
- it('should handle successful rename', async () => {
161
- vi.mocked(localFileService.renameLocalFile).mockResolvedValue({
162
- success: true,
163
- newPath: '/new.txt',
164
- });
165
-
166
- await store.renameLocalFile('test-id', { path: '/test.txt', newName: 'new.txt' });
167
-
168
- expect(mockStore.internal_triggerLocalFileToolCalling).toBeCalled();
169
- });
170
-
171
- it('should handle rename error', async () => {
172
- vi.mocked(localFileService.renameLocalFile).mockResolvedValue({
173
- success: false,
174
- error: 'Rename failed',
175
- newPath: '/test.txt',
176
- });
177
-
178
- await store.renameLocalFile('test-id', { path: '/test.txt', newName: 'new.txt' });
179
-
180
- expect(mockStore.internal_triggerLocalFileToolCalling).toBeCalled();
181
- });
182
-
183
- it('should validate new filename', async () => {
184
- vi.mocked(localFileService.renameLocalFile).mockRejectedValue(
185
- new Error('Invalid new name provided'),
186
- );
187
-
188
- await store.renameLocalFile('test-id', {
189
- path: '/test.txt',
190
- newName: '../invalid.txt',
191
- });
192
-
193
- expect(mockStore.internal_triggerLocalFileToolCalling).toBeCalledWith(
194
- 'test-id',
195
- expect.any(Function),
196
- );
197
- });
198
- });
199
-
200
- // toggleLocalFileLoading is no longer needed as we use operation-based state management
201
- });