@lobehub/chat 1.141.7 → 1.141.9

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 (128) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/apps/desktop/package.json +1 -0
  3. package/apps/desktop/src/main/controllers/LocalFileCtr.ts +279 -52
  4. package/apps/desktop/src/main/controllers/__tests__/LocalFileCtr.test.ts +392 -0
  5. package/changelog/v1.json +18 -0
  6. package/docs/usage/features/{group-chat.mdx → agent-team.mdx} +14 -14
  7. package/docs/usage/features/agent-team.zh-CN.mdx +52 -0
  8. package/locales/ar/chat.json +17 -17
  9. package/locales/ar/setting.json +15 -19
  10. package/locales/ar/welcome.json +1 -1
  11. package/locales/bg-BG/chat.json +17 -17
  12. package/locales/bg-BG/setting.json +15 -19
  13. package/locales/de-DE/chat.json +17 -17
  14. package/locales/de-DE/setting.json +15 -19
  15. package/locales/de-DE/welcome.json +1 -1
  16. package/locales/en-US/chat.json +17 -17
  17. package/locales/en-US/setting.json +15 -19
  18. package/locales/en-US/welcome.json +1 -1
  19. package/locales/es-ES/chat.json +17 -17
  20. package/locales/es-ES/setting.json +15 -19
  21. package/locales/es-ES/welcome.json +1 -1
  22. package/locales/fa-IR/chat.json +17 -17
  23. package/locales/fa-IR/setting.json +15 -19
  24. package/locales/fa-IR/welcome.json +1 -1
  25. package/locales/fr-FR/chat.json +16 -16
  26. package/locales/fr-FR/setting.json +15 -19
  27. package/locales/fr-FR/welcome.json +1 -1
  28. package/locales/it-IT/chat.json +17 -17
  29. package/locales/it-IT/setting.json +15 -19
  30. package/locales/it-IT/welcome.json +1 -1
  31. package/locales/ja-JP/chat.json +17 -17
  32. package/locales/ja-JP/setting.json +15 -19
  33. package/locales/ja-JP/welcome.json +1 -1
  34. package/locales/ko-KR/chat.json +17 -17
  35. package/locales/ko-KR/setting.json +15 -19
  36. package/locales/ko-KR/welcome.json +1 -1
  37. package/locales/nl-NL/chat.json +17 -17
  38. package/locales/nl-NL/setting.json +15 -19
  39. package/locales/nl-NL/welcome.json +1 -1
  40. package/locales/pl-PL/chat.json +17 -17
  41. package/locales/pl-PL/setting.json +15 -19
  42. package/locales/pt-BR/chat.json +17 -17
  43. package/locales/pt-BR/setting.json +15 -19
  44. package/locales/pt-BR/welcome.json +1 -1
  45. package/locales/ru-RU/chat.json +17 -17
  46. package/locales/ru-RU/setting.json +15 -19
  47. package/locales/ru-RU/welcome.json +1 -1
  48. package/locales/tr-TR/chat.json +17 -17
  49. package/locales/tr-TR/setting.json +15 -19
  50. package/locales/vi-VN/chat.json +15 -15
  51. package/locales/vi-VN/setting.json +15 -19
  52. package/locales/zh-CN/chat.json +17 -17
  53. package/locales/zh-CN/setting.json +15 -19
  54. package/locales/zh-CN/welcome.json +1 -1
  55. package/locales/zh-TW/chat.json +17 -17
  56. package/locales/zh-TW/setting.json +15 -19
  57. package/locales/zh-TW/welcome.json +1 -1
  58. package/package.json +1 -1
  59. package/packages/agent-runtime/src/core/InterventionChecker.ts +173 -0
  60. package/packages/agent-runtime/src/core/UsageCounter.ts +248 -0
  61. package/packages/agent-runtime/src/core/__tests__/InterventionChecker.test.ts +334 -0
  62. package/packages/agent-runtime/src/core/__tests__/UsageCounter.test.ts +873 -0
  63. package/packages/agent-runtime/src/core/__tests__/runtime.test.ts +32 -26
  64. package/packages/agent-runtime/src/core/index.ts +2 -0
  65. package/packages/agent-runtime/src/core/runtime.ts +31 -18
  66. package/packages/agent-runtime/src/types/instruction.ts +1 -1
  67. package/packages/agent-runtime/src/types/state.ts +3 -3
  68. package/packages/agent-runtime/src/types/usage.ts +34 -25
  69. package/packages/const/src/settings/systemAgent.ts +0 -1
  70. package/packages/context-engine/src/index.ts +1 -0
  71. package/packages/context-engine/src/tools/ToolNameResolver.ts +2 -2
  72. package/packages/context-engine/src/tools/ToolsEngine.ts +37 -8
  73. package/packages/context-engine/src/tools/__tests__/ToolsEngine.test.ts +149 -5
  74. package/packages/context-engine/src/tools/__tests__/utils.test.ts +2 -2
  75. package/packages/context-engine/src/tools/index.ts +1 -0
  76. package/packages/context-engine/src/tools/types.ts +18 -3
  77. package/packages/context-engine/src/tools/utils.ts +4 -4
  78. package/packages/types/src/tool/builtin.ts +54 -1
  79. package/packages/types/src/tool/index.ts +1 -0
  80. package/packages/types/src/tool/intervention.ts +114 -0
  81. package/packages/types/src/user/settings/systemAgent.ts +0 -1
  82. package/packages/types/src/user/settings/tool.ts +37 -0
  83. package/src/app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatList/ChatItem/OrchestratorThinking.tsx +2 -3
  84. package/src/app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatList/ChatItem/index.tsx +2 -2
  85. package/src/app/[variants]/(main)/chat/(workspace)/@topic/features/GroupConfig/GroupMember.tsx +34 -2
  86. package/src/app/[variants]/(main)/chat/(workspace)/_layout/Desktop/ChatHeader/Main.tsx +1 -1
  87. package/src/app/[variants]/(main)/chat/(workspace)/features/{GroupChatSettings → AgentTeamSettings}/index.tsx +4 -5
  88. package/src/app/[variants]/(main)/chat/(workspace)/features/SettingButton.tsx +2 -2
  89. package/src/app/[variants]/(main)/chat/@session/_layout/Desktop/SessionHeader.tsx +2 -0
  90. package/src/app/[variants]/(main)/chat/@session/features/SessionListContent/CollapseGroup/Actions.tsx +18 -1
  91. package/src/components/ChatGroupWizard/ChatGroupWizard.tsx +33 -5
  92. package/src/components/MemberSelectionModal/MemberSelectionModal.tsx +170 -26
  93. package/src/features/Conversation/Messages/Assistant/Actions/index.tsx +7 -2
  94. package/src/features/Conversation/Messages/Assistant/Tool/Render/index.tsx +4 -2
  95. package/src/features/Conversation/Messages/User/Actions.tsx +8 -2
  96. package/src/features/GroupChatSettings/{ChatGroupSettings.tsx → AgentTeamChatSettings.tsx} +6 -5
  97. package/src/features/GroupChatSettings/{GroupMembers.tsx → AgentTeamMembersSettings.tsx} +64 -19
  98. package/src/features/GroupChatSettings/{ChatGroupMeta.tsx → AgentTeamMetaSettings.tsx} +2 -2
  99. package/src/features/GroupChatSettings/AgentTeamSettings.tsx +54 -0
  100. package/src/features/GroupChatSettings/index.ts +4 -5
  101. package/src/locales/default/chat.ts +17 -17
  102. package/src/locales/default/setting.ts +15 -19
  103. package/src/locales/default/welcome.ts +1 -1
  104. package/src/store/chat/slices/aiChat/actions/generateAIGroupChat.ts +2 -1
  105. package/src/store/chat/slices/builtinTool/actions/{dalle.test.ts → __tests__/dalle.test.ts} +2 -5
  106. package/src/store/chat/slices/builtinTool/actions/__tests__/{localFile.test.ts → localSystem.test.ts} +4 -4
  107. package/src/store/chat/slices/builtinTool/actions/index.ts +2 -2
  108. package/src/store/chat/slices/builtinTool/actions/{localFile.ts → localSystem.ts} +183 -69
  109. package/src/store/chatGroup/action.ts +36 -1
  110. package/src/store/electron/selectors/__tests__/desktopState.test.ts +3 -3
  111. package/src/store/electron/selectors/desktopState.ts +11 -2
  112. package/src/store/user/slices/settings/selectors/__snapshots__/settings.test.ts.snap +0 -4
  113. package/src/store/user/slices/settings/selectors/systemAgent.ts +0 -2
  114. package/src/tools/local-system/Placeholder/ListFiles.tsx +10 -8
  115. package/src/tools/local-system/Placeholder/SearchFiles.tsx +12 -10
  116. package/src/tools/local-system/Placeholder/index.tsx +1 -1
  117. package/src/tools/local-system/Render/ReadLocalFile/ReadFileSkeleton.tsx +8 -18
  118. package/src/tools/local-system/Render/ReadLocalFile/ReadFileView.tsx +21 -6
  119. package/src/tools/local-system/Render/SearchFiles/Result.tsx +5 -4
  120. package/src/tools/local-system/Render/SearchFiles/SearchQuery/SearchView.tsx +4 -15
  121. package/src/tools/local-system/Render/SearchFiles/index.tsx +3 -2
  122. package/src/tools/local-system/type.ts +39 -0
  123. package/docs/usage/features/group-chat.zh-CN.mdx +0 -52
  124. package/src/features/GroupChatSettings/GroupSettings.tsx +0 -30
  125. package/src/features/GroupChatSettings/GroupSettingsContent.tsx +0 -24
  126. package/src/tools/local-system/Placeholder/ReadLocalFile.tsx +0 -9
  127. package/src/tools/local-system/Render/ReadLocalFile/style.ts +0 -37
  128. /package/src/store/chat/slices/builtinTool/actions/{search.test.ts → __tests__/search.test.ts} +0 -0
@@ -1,4 +1,9 @@
1
1
  import {
2
+ EditLocalFileParams,
3
+ GetCommandOutputParams,
4
+ GlobFilesParams,
5
+ GrepContentParams,
6
+ KillCommandParams,
2
7
  ListLocalFileParams,
3
8
  LocalMoveFilesResultItem,
4
9
  LocalReadFileParams,
@@ -6,6 +11,7 @@ import {
6
11
  LocalSearchFilesParams,
7
12
  MoveLocalFilesParams,
8
13
  RenameLocalFileParams,
14
+ RunCommandParams,
9
15
  WriteLocalFileParams,
10
16
  } from '@lobechat/electron-client-ipc';
11
17
  import { StateCreator } from 'zustand/vanilla';
@@ -13,67 +19,91 @@ import { StateCreator } from 'zustand/vanilla';
13
19
  import { localFileService } from '@/services/electron/localFileService';
14
20
  import { ChatStore } from '@/store/chat/store';
15
21
  import {
22
+ EditLocalFileState,
23
+ GetCommandOutputState,
24
+ GlobFilesState,
25
+ GrepContentState,
26
+ KillCommandState,
16
27
  LocalFileListState,
17
28
  LocalFileSearchState,
18
29
  LocalMoveFilesState,
19
30
  LocalReadFileState,
20
31
  LocalReadFilesState,
21
32
  LocalRenameFileState,
33
+ RunCommandState,
22
34
  } from '@/tools/local-system/type';
23
35
 
36
+ /* eslint-disable typescript-sort-keys/interface */
24
37
  export interface LocalFileAction {
25
38
  internal_triggerLocalFileToolCalling: <T = any>(
26
39
  id: string,
27
40
  callingService: () => Promise<{ content: any; state?: T }>,
28
41
  ) => Promise<boolean>;
29
42
 
43
+ // File Operations
30
44
  listLocalFiles: (id: string, params: ListLocalFileParams) => Promise<boolean>;
31
45
  moveLocalFiles: (id: string, params: MoveLocalFilesParams) => Promise<boolean>;
32
- reSearchLocalFiles: (id: string, params: LocalSearchFilesParams) => Promise<boolean>;
33
46
  readLocalFile: (id: string, params: LocalReadFileParams) => Promise<boolean>;
34
47
  readLocalFiles: (id: string, params: LocalReadFilesParams) => Promise<boolean>;
35
48
  renameLocalFile: (id: string, params: RenameLocalFileParams) => Promise<boolean>;
36
- // Added rename action
49
+ reSearchLocalFiles: (id: string, params: LocalSearchFilesParams) => Promise<boolean>;
37
50
  searchLocalFiles: (id: string, params: LocalSearchFilesParams) => Promise<boolean>;
38
51
  toggleLocalFileLoading: (id: string, loading: boolean) => void;
39
-
40
52
  writeLocalFile: (id: string, params: WriteLocalFileParams) => Promise<boolean>;
53
+
54
+ // Shell Commands
55
+ editLocalFile: (id: string, params: EditLocalFileParams) => Promise<boolean>;
56
+ getCommandOutput: (id: string, params: GetCommandOutputParams) => Promise<boolean>;
57
+ killCommand: (id: string, params: KillCommandParams) => Promise<boolean>;
58
+ runCommand: (id: string, params: RunCommandParams) => Promise<boolean>;
59
+
60
+ // Search & Find
61
+ globLocalFiles: (id: string, params: GlobFilesParams) => Promise<boolean>;
62
+ grepContent: (id: string, params: GrepContentParams) => Promise<boolean>;
41
63
  }
64
+ /* eslint-enable typescript-sort-keys/interface */
42
65
 
43
- export const localFileSlice: StateCreator<
66
+ /* eslint-disable sort-keys-fix/sort-keys-fix */
67
+ export const localSystemSlice: StateCreator<
44
68
  ChatStore,
45
69
  [['zustand/devtools', never]],
46
70
  [],
47
71
  LocalFileAction
48
72
  > = (set, get) => ({
49
- internal_triggerLocalFileToolCalling: async (id, callingService) => {
50
- get().toggleLocalFileLoading(id, true);
51
- try {
52
- const { state, content } = await callingService();
53
- if (state) {
54
- await get().updatePluginState(id, state as any);
55
- }
56
- await get().internal_updateMessageContent(id, JSON.stringify(content));
57
- } catch (error) {
58
- await get().internal_updateMessagePluginError(id, {
59
- body: error,
60
- message: (error as Error).message,
61
- type: 'PluginServerError',
62
- });
63
- }
64
- get().toggleLocalFileLoading(id, false);
73
+ // ==================== File Editing ====================
74
+ editLocalFile: async (id, params) => {
75
+ return get().internal_triggerLocalFileToolCalling<EditLocalFileState>(id, async () => {
76
+ const result = await localFileService.editLocalFile(params);
65
77
 
66
- return true;
67
- },
78
+ const message = result.success
79
+ ? `Successfully replaced ${result.replacements} occurrence(s) in ${params.file_path}`
80
+ : `Edit failed: ${result.error}`;
81
+
82
+ const state: EditLocalFileState = { message, result };
68
83
 
69
- listLocalFiles: async (id, params) => {
70
- return get().internal_triggerLocalFileToolCalling<LocalFileListState>(id, async () => {
71
- const result = await localFileService.listLocalFiles(params);
72
- const state: LocalFileListState = { listResults: result };
73
84
  return { content: result, state };
74
85
  });
75
86
  },
76
87
 
88
+ writeLocalFile: async (id, params) => {
89
+ return get().internal_triggerLocalFileToolCalling(id, async () => {
90
+ const result = await localFileService.writeFile(params);
91
+
92
+ let content: { message: string; success: boolean };
93
+
94
+ if (result.success) {
95
+ content = {
96
+ message: `成功写入文件 ${params.path}`,
97
+ success: true,
98
+ };
99
+ } else {
100
+ const errorMessage = result.error;
101
+
102
+ content = { message: errorMessage || '写入文件失败', success: false };
103
+ }
104
+ return { content };
105
+ });
106
+ },
77
107
  moveLocalFiles: async (id, params) => {
78
108
  return get().internal_triggerLocalFileToolCalling<LocalMoveFilesState>(id, async () => {
79
109
  const results: LocalMoveFilesResultItem[] = await localFileService.moveLocalFiles(params);
@@ -100,31 +130,6 @@ export const localFileSlice: StateCreator<
100
130
  return { content: { message, results }, state };
101
131
  });
102
132
  },
103
-
104
- reSearchLocalFiles: async (id, params) => {
105
- get().toggleLocalFileLoading(id, true);
106
-
107
- await get().updatePluginArguments(id, params);
108
-
109
- return get().searchLocalFiles(id, params);
110
- },
111
-
112
- readLocalFile: async (id, params) => {
113
- return get().internal_triggerLocalFileToolCalling<LocalReadFileState>(id, async () => {
114
- const result = await localFileService.readLocalFile(params);
115
- const state: LocalReadFileState = { fileContent: result };
116
- return { content: result, state };
117
- });
118
- },
119
-
120
- readLocalFiles: async (id, params) => {
121
- return get().internal_triggerLocalFileToolCalling<LocalReadFilesState>(id, async () => {
122
- const results = await localFileService.readLocalFiles(params);
123
- const state: LocalReadFilesState = { filesContent: results };
124
- return { content: results, state };
125
- });
126
- },
127
-
128
133
  renameLocalFile: async (id, params) => {
129
134
  return get().internal_triggerLocalFileToolCalling<LocalRenameFileState>(id, async () => {
130
135
  const { path: currentPath, newName } = params;
@@ -169,6 +174,33 @@ export const localFileSlice: StateCreator<
169
174
  });
170
175
  },
171
176
 
177
+ // ==================== Search & Find ====================
178
+ grepContent: async (id, params) => {
179
+ return get().internal_triggerLocalFileToolCalling<GrepContentState>(id, async () => {
180
+ const result = await localFileService.grepContent(params);
181
+
182
+ const message = result.success
183
+ ? `Found ${result.total_matches} matches in ${result.matches.length} locations`
184
+ : 'Search failed';
185
+
186
+ const state: GrepContentState = { message, result };
187
+
188
+ return { content: result, state };
189
+ });
190
+ },
191
+
192
+ globLocalFiles: async (id, params) => {
193
+ return get().internal_triggerLocalFileToolCalling<GlobFilesState>(id, async () => {
194
+ const result = await localFileService.globFiles(params);
195
+
196
+ const message = result.success ? `Found ${result.total_files} files` : 'Glob search failed';
197
+
198
+ const state: GlobFilesState = { message, result };
199
+
200
+ return { content: result, state };
201
+ });
202
+ },
203
+
172
204
  searchLocalFiles: async (id, params) => {
173
205
  return get().internal_triggerLocalFileToolCalling<LocalFileSearchState>(id, async () => {
174
206
  const result = await localFileService.searchLocalFiles(params);
@@ -177,6 +209,89 @@ export const localFileSlice: StateCreator<
177
209
  });
178
210
  },
179
211
 
212
+ listLocalFiles: async (id, params) => {
213
+ return get().internal_triggerLocalFileToolCalling<LocalFileListState>(id, async () => {
214
+ const result = await localFileService.listLocalFiles(params);
215
+ const state: LocalFileListState = { listResults: result };
216
+ return { content: result, state };
217
+ });
218
+ },
219
+
220
+ reSearchLocalFiles: async (id, params) => {
221
+ get().toggleLocalFileLoading(id, true);
222
+
223
+ await get().updatePluginArguments(id, params);
224
+
225
+ return get().searchLocalFiles(id, params);
226
+ },
227
+
228
+ readLocalFile: async (id, params) => {
229
+ return get().internal_triggerLocalFileToolCalling<LocalReadFileState>(id, async () => {
230
+ const result = await localFileService.readLocalFile(params);
231
+ const state: LocalReadFileState = { fileContent: result };
232
+ return { content: result, state };
233
+ });
234
+ },
235
+
236
+ readLocalFiles: async (id, params) => {
237
+ return get().internal_triggerLocalFileToolCalling<LocalReadFilesState>(id, async () => {
238
+ const results = await localFileService.readLocalFiles(params);
239
+ const state: LocalReadFilesState = { filesContent: results };
240
+ return { content: results, state };
241
+ });
242
+ },
243
+
244
+ // ==================== Shell Commands ====================
245
+ runCommand: async (id, params) => {
246
+ return get().internal_triggerLocalFileToolCalling<RunCommandState>(id, async () => {
247
+ const result = await localFileService.runCommand(params);
248
+
249
+ let message: string;
250
+
251
+ if (result.success) {
252
+ if (result.shell_id) {
253
+ message = `Command started in background with shell_id: ${result.shell_id}`;
254
+ } else {
255
+ message = `Command completed successfully. Exit code: ${result.exit_code}`;
256
+ }
257
+ } else {
258
+ message = `Command failed: ${result.error}`;
259
+ }
260
+
261
+ const state: RunCommandState = { message, result };
262
+
263
+ return { content: result, state };
264
+ });
265
+ },
266
+ killCommand: async (id, params) => {
267
+ return get().internal_triggerLocalFileToolCalling<KillCommandState>(id, async () => {
268
+ const result = await localFileService.killCommand(params);
269
+
270
+ const message = result.success
271
+ ? `Successfully killed shell: ${params.shell_id}`
272
+ : `Failed to kill shell: ${result.error}`;
273
+
274
+ const state: KillCommandState = { message, result };
275
+
276
+ return { content: result, state };
277
+ });
278
+ },
279
+ getCommandOutput: async (id, params) => {
280
+ return get().internal_triggerLocalFileToolCalling<GetCommandOutputState>(id, async () => {
281
+ const result = await localFileService.getCommandOutput(params);
282
+
283
+ const message = result.success
284
+ ? `Output retrieved. Running: ${result.running}`
285
+ : `Failed: ${result.error}`;
286
+
287
+ const state: GetCommandOutputState = { message, result };
288
+
289
+ return { content: result, state };
290
+ });
291
+ },
292
+
293
+ // ==================== utils ====================
294
+
180
295
  toggleLocalFileLoading: (id, loading) => {
181
296
  // Assuming a loading state structure similar to searchLoading
182
297
  set(
@@ -187,24 +302,23 @@ export const localFileSlice: StateCreator<
187
302
  `toggleLocalFileLoading/${loading ? 'start' : 'end'}`,
188
303
  );
189
304
  },
190
-
191
- writeLocalFile: async (id, params) => {
192
- return get().internal_triggerLocalFileToolCalling(id, async () => {
193
- const result = await localFileService.writeFile(params);
194
-
195
- let content: { message: string; success: boolean };
196
-
197
- if (result.success) {
198
- content = {
199
- message: `成功写入文件 ${params.path}`,
200
- success: true,
201
- };
202
- } else {
203
- const errorMessage = result.error;
204
-
205
- content = { message: errorMessage || '写入文件失败', success: false };
305
+ internal_triggerLocalFileToolCalling: async (id, callingService) => {
306
+ get().toggleLocalFileLoading(id, true);
307
+ try {
308
+ const { state, content } = await callingService();
309
+ if (state) {
310
+ await get().updatePluginState(id, state as any);
206
311
  }
207
- return { content };
208
- });
312
+ await get().internal_updateMessageContent(id, JSON.stringify(content));
313
+ } catch (error) {
314
+ await get().internal_updateMessagePluginError(id, {
315
+ body: error,
316
+ message: (error as Error).message,
317
+ type: 'PluginServerError',
318
+ });
319
+ }
320
+ get().toggleLocalFileLoading(id, false);
321
+
322
+ return true;
209
323
  },
210
324
  });
@@ -98,14 +98,49 @@ export const chatGroupAction: StateCreator<
98
98
  return group.id;
99
99
  },
100
100
  deleteGroup: async (id) => {
101
+ // First, get all group members to identify virtual members
102
+ // Note: ChatGroupAgentItem type is incorrectly defined in schema as agents table type
103
+ // but getGroupAgents actually returns chatGroupsAgents junction table entries
104
+ const groupAgents = (await chatGroupService.getGroupAgents(id)) as unknown as Array<{
105
+ agentId: string;
106
+ chatGroupId: string;
107
+ }>;
108
+
109
+ // Delete the group first (this will cascade delete the chat_groups_agents entries)
101
110
  await chatGroupService.deleteGroup(id);
102
111
  dispatch({ payload: id, type: 'deleteGroup' });
103
112
 
113
+ // Now delete virtual members (agents with virtual: true)
114
+ const sessionStore = getSessionStoreState();
115
+ const sessions = sessionStore.sessions || [];
116
+
117
+ // Find and delete all virtual sessions that were members of this group
118
+ const virtualMemberDeletions = groupAgents
119
+ .map((groupAgent) => {
120
+ // groupAgent has agentId property from the junction table
121
+ const session = sessions.find((s) => {
122
+ // Type guard: check if it's an agent session
123
+ if (s.type === 'agent') {
124
+ return s.config?.id === groupAgent.agentId;
125
+ }
126
+ return false;
127
+ });
128
+
129
+ // Only delete if the session exists and has virtual flag set to true
130
+ if (session && session.type === 'agent' && session.config?.virtual) {
131
+ return sessionStore.removeSession(session.id);
132
+ }
133
+ return null;
134
+ })
135
+ .filter(Boolean);
136
+
137
+ // Wait for all virtual member deletions to complete
138
+ await Promise.all(virtualMemberDeletions);
139
+
104
140
  await get().loadGroups();
105
141
  await getSessionStoreState().refreshSessions();
106
142
 
107
143
  // If the active session is the deleted group, switch to the inbox session
108
- const sessionStore = getSessionStoreState();
109
144
  if (sessionStore.activeId === id) {
110
145
  sessionStore.switchSession(INBOX_SESSION_ID);
111
146
  }
@@ -6,7 +6,7 @@ import { merge } from '@/utils/merge';
6
6
  import { desktopStateSelectors } from '../desktopState';
7
7
 
8
8
  describe('desktopStateSelectors', () => {
9
- describe('usePath', () => {
9
+ describe('userPath', () => {
10
10
  it('should return userPath from appState', () => {
11
11
  const state: ElectronState = merge(initialState, {
12
12
  appState: {
@@ -23,7 +23,7 @@ describe('desktopStateSelectors', () => {
23
23
  },
24
24
  });
25
25
 
26
- expect(desktopStateSelectors.usePath(state)).toEqual({
26
+ expect(desktopStateSelectors.userPath(state)).toEqual({
27
27
  desktop: '/test/desktop',
28
28
  documents: '/test/documents',
29
29
  downloads: '/test/downloads',
@@ -40,7 +40,7 @@ describe('desktopStateSelectors', () => {
40
40
  appState: {},
41
41
  });
42
42
 
43
- expect(desktopStateSelectors.usePath(state)).toBeUndefined();
43
+ expect(desktopStateSelectors.userPath(state)).toBeUndefined();
44
44
  });
45
45
  });
46
46
  });
@@ -1,7 +1,16 @@
1
1
  import { ElectronState } from '@/store/electron/initialState';
2
2
 
3
- const usePath = (s: ElectronState) => s.appState.userPath;
3
+ const userPath = (s: ElectronState) => s.appState.userPath;
4
+ const userHomePath = (s: ElectronState) => userPath(s)?.home || '';
5
+
6
+ const displayRelativePath = (path: string) => (s: ElectronState) => {
7
+ const basePath = userHomePath(s);
8
+
9
+ return !!basePath ? path.replaceAll(basePath, '~') : path;
10
+ };
4
11
 
5
12
  export const desktopStateSelectors = {
6
- usePath,
13
+ displayRelativePath,
14
+ userHomePath,
15
+ userPath,
7
16
  };
@@ -59,10 +59,6 @@ exports[`settingsSelectors > currentSystemAgent > should merge DEFAULT_SYSTEM_AG
59
59
  "model": "gpt-5-mini",
60
60
  "provider": "openai",
61
61
  },
62
- "groupChatSupervisor": {
63
- "model": "gpt-5-mini",
64
- "provider": "openai",
65
- },
66
62
  "historyCompress": {
67
63
  "model": "gpt-5-mini",
68
64
  "provider": "openai",
@@ -14,12 +14,10 @@ const agentMeta = (s: UserStore) => currentSystemAgent(s).agentMeta;
14
14
  const queryRewrite = (s: UserStore) => currentSystemAgent(s).queryRewrite;
15
15
  const historyCompress = (s: UserStore) => currentSystemAgent(s).historyCompress;
16
16
  const generationTopic = (s: UserStore) => currentSystemAgent(s).generationTopic;
17
- const groupChatSupervisor = (s: UserStore) => currentSystemAgent(s).groupChatSupervisor;
18
17
 
19
18
  export const systemAgentSelectors = {
20
19
  agentMeta,
21
20
  generationTopic,
22
- groupChatSupervisor,
23
21
  historyCompress,
24
22
  queryRewrite,
25
23
  thread,
@@ -1,7 +1,7 @@
1
1
  import { ListLocalFileParams } from '@lobechat/electron-client-ipc';
2
2
  import { Skeleton } from 'antd';
3
3
  import React, { memo } from 'react';
4
- import { Flexbox } from 'react-layout-kit';
4
+ import { Center, Flexbox } from 'react-layout-kit';
5
5
 
6
6
  import { LocalFolder } from '@/features/LocalFile';
7
7
 
@@ -10,14 +10,16 @@ interface ListFilesProps {
10
10
  }
11
11
  export const ListFiles = memo<ListFilesProps>(({ args }) => {
12
12
  return (
13
- <Flexbox gap={8}>
13
+ <Flexbox gap={12}>
14
14
  <LocalFolder path={args.path} />
15
- <Flexbox gap={4}>
16
- <Skeleton.Button active block style={{ height: 16 }} />
17
- <Skeleton.Button active block style={{ height: 16 }} />
18
- <Skeleton.Button active block style={{ height: 16 }} />
19
- <Skeleton.Button active block style={{ height: 16 }} />
20
- </Flexbox>
15
+ <Center height={140}>
16
+ <Flexbox gap={4} width={'90%'}>
17
+ <Skeleton.Button active block style={{ height: 16 }} />
18
+ <Skeleton.Button active block style={{ height: 16 }} />
19
+ <Skeleton.Button active block style={{ height: 16 }} />
20
+ <Skeleton.Button active block style={{ height: 16 }} />
21
+ </Flexbox>
22
+ </Center>
21
23
  </Flexbox>
22
24
  );
23
25
  });
@@ -3,8 +3,8 @@ import { Icon } from '@lobehub/ui';
3
3
  import { Skeleton } from 'antd';
4
4
  import { createStyles } from 'antd-style';
5
5
  import { SearchIcon } from 'lucide-react';
6
- import { memo } from 'react';
7
- import { Flexbox } from 'react-layout-kit';
6
+ import React, { memo } from 'react';
7
+ import { Center, Flexbox } from 'react-layout-kit';
8
8
 
9
9
  const useStyles = createStyles(({ css, token, cx }) => ({
10
10
  query: cx(css`
@@ -29,8 +29,8 @@ const SearchFiles = memo<SearchFilesProps>(({ args }) => {
29
29
  const { styles } = useStyles();
30
30
 
31
31
  return (
32
- <Flexbox gap={8}>
33
- <Flexbox align={'center'} distribution={'space-between'} gap={40} height={32} horizontal>
32
+ <Flexbox gap={4}>
33
+ <Flexbox align={'center'} distribution={'space-between'} gap={40} height={26} horizontal>
34
34
  <Flexbox align={'center'} className={styles.query} gap={8} horizontal>
35
35
  <Icon icon={SearchIcon} />
36
36
  {args.keywords ? (
@@ -42,12 +42,14 @@ const SearchFiles = memo<SearchFilesProps>(({ args }) => {
42
42
 
43
43
  <Skeleton.Node active style={{ height: 20, width: 40 }} />
44
44
  </Flexbox>
45
- <Flexbox gap={4}>
46
- <Skeleton.Button active block style={{ height: 16 }} />
47
- <Skeleton.Button active block style={{ height: 16 }} />
48
- <Skeleton.Button active block style={{ height: 16 }} />
49
- <Skeleton.Button active block style={{ height: 16 }} />
50
- </Flexbox>
45
+ <Center height={140}>
46
+ <Flexbox gap={4} width={'90%'}>
47
+ <Skeleton.Button active block style={{ height: 16 }} />
48
+ <Skeleton.Button active block style={{ height: 16 }} />
49
+ <Skeleton.Button active block style={{ height: 16 }} />
50
+ <Skeleton.Button active block style={{ height: 16 }} />
51
+ </Flexbox>
52
+ </Center>
51
53
  </Flexbox>
52
54
  );
53
55
  });
@@ -3,8 +3,8 @@ import { memo } from 'react';
3
3
 
4
4
  import { LocalSystemApiName } from '@/tools/local-system';
5
5
 
6
+ import ReadLocalFile from '../Render/ReadLocalFile/ReadFileSkeleton';
6
7
  import { ListFiles } from './ListFiles';
7
- import ReadLocalFile from './ReadLocalFile';
8
8
  import SearchFiles from './SearchFiles';
9
9
 
10
10
  const RenderMap = {
@@ -9,40 +9,30 @@ const useStyles = createStyles(({ css, token }) => ({
9
9
  border: 1px solid ${token.colorBorderSecondary};
10
10
  border-radius: ${token.borderRadiusLG}px;
11
11
  `,
12
- header: css`
13
- margin-block-end: 4px;
14
- `,
12
+
15
13
  meta: css`
16
14
  font-size: 12px;
17
15
  `,
18
- path: css`
19
- margin-block-start: 4px;
20
- `,
21
16
  }));
22
17
 
23
18
  const ReadFileSkeleton = memo(() => {
24
19
  const { styles } = useStyles();
25
20
 
26
21
  return (
27
- <Flexbox className={styles.container}>
28
- <Flexbox
29
- align={'center'}
30
- className={styles.header}
31
- gap={24}
32
- horizontal
33
- justify={'space-between'}
34
- >
22
+ <Flexbox className={styles.container} gap={2}>
23
+ <Flexbox align={'center'} gap={24} horizontal justify={'space-between'}>
35
24
  <Flexbox align={'center'} flex={1} gap={8} horizontal style={{ overflow: 'hidden' }}>
36
- <Skeleton.Avatar active shape="square" size={24} style={{ borderRadius: 4 }} />
37
- <Skeleton.Input active size="small" style={{ flex: 1, minWidth: 100 }} />
25
+ <Skeleton.Node active style={{ flex: 1, height: 16, width: 20 }} />
26
+
27
+ <Skeleton.Node active style={{ flex: 1, height: 16, minWidth: 100 }} />
38
28
  </Flexbox>
39
29
  <Flexbox align={'center'} className={styles.meta} gap={16}>
40
- <Skeleton.Input active size="small" style={{ maxWidth: 40 }} />
30
+ <Skeleton.Node active style={{ height: 16, maxWidth: 40 }} />
41
31
  </Flexbox>
42
32
  </Flexbox>
43
33
 
44
34
  {/* Path */}
45
- <Skeleton.Input active block className={styles.path} size="small" />
35
+ <Skeleton.Node active style={{ height: 16, width: '100%' }} />
46
36
  </Flexbox>
47
37
  );
48
38
  });
@@ -8,6 +8,8 @@ import { Flexbox } from 'react-layout-kit';
8
8
 
9
9
  import FileIcon from '@/components/FileIcon';
10
10
  import { localFileService } from '@/services/electron/localFileService';
11
+ import { useElectronStore } from '@/store/electron';
12
+ import { desktopStateSelectors } from '@/store/electron/selectors';
11
13
 
12
14
  const useStyles = createStyles(({ css, token, cx }) => ({
13
15
  actions: cx(
@@ -20,11 +22,19 @@ const useStyles = createStyles(({ css, token, cx }) => ({
20
22
  `,
21
23
  ),
22
24
  container: css`
25
+ justify-content: space-between;
26
+
27
+ height: 64px;
23
28
  padding: 8px;
24
29
  border: 1px solid ${token.colorBorderSecondary};
25
30
  border-radius: ${token.borderRadiusLG}px;
31
+
26
32
  transition: all 0.2s ${token.motionEaseInOut};
27
33
 
34
+ .local-file-actions {
35
+ opacity: 0;
36
+ }
37
+
28
38
  &:hover {
29
39
  border-color: ${token.colorBorder};
30
40
 
@@ -48,10 +58,13 @@ const useStyles = createStyles(({ css, token, cx }) => ({
48
58
  lineCount: css`
49
59
  color: ${token.colorTextQuaternary};
50
60
  `,
51
- meta: css`
52
- font-size: 12px;
53
- color: ${token.colorTextTertiary};
54
- `,
61
+ meta: cx(
62
+ 'local-file-actions',
63
+ css`
64
+ font-size: 12px;
65
+ color: ${token.colorTextTertiary};
66
+ `,
67
+ ),
55
68
  path: css`
56
69
  margin-block-start: 4px;
57
70
  padding-inline: 4px;
@@ -104,6 +117,8 @@ const ReadFileView = memo<ReadFileViewProps>(
104
117
  localFileService.openLocalFolder({ isDirectory: false, path });
105
118
  };
106
119
 
120
+ const displayPath = useElectronStore(desktopStateSelectors.displayRelativePath(path));
121
+
107
122
  return (
108
123
  <Flexbox className={styles.container}>
109
124
  <Flexbox
@@ -115,7 +130,7 @@ const ReadFileView = memo<ReadFileViewProps>(
115
130
  onClick={handleToggleExpand}
116
131
  >
117
132
  <Flexbox align={'center'} flex={1} gap={0} horizontal style={{ overflow: 'hidden' }}>
118
- <FileIcon fileName={filename} fileType={fileType} size={24} variant={'raw'} />
133
+ <FileIcon fileName={filename} fileType={fileType} size={16} variant={'raw'} />
119
134
  <Flexbox horizontal>
120
135
  <Text className={styles.fileName} ellipsis>
121
136
  {filename}
@@ -174,7 +189,7 @@ const ReadFileView = memo<ReadFileViewProps>(
174
189
 
175
190
  {/* Path */}
176
191
  <Text className={styles.path} ellipsis type={'secondary'}>
177
- {path}
192
+ {displayPath}
178
193
  </Text>
179
194
 
180
195
  {isExpanded && (