@lobehub/chat 1.81.3 → 1.81.5

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 (189) hide show
  1. package/.eslintrc.js +1 -0
  2. package/.github/workflows/release.yml +5 -0
  3. package/.github/workflows/test.yml +5 -0
  4. package/CHANGELOG.md +58 -0
  5. package/changelog/v1.json +21 -0
  6. package/locales/ar/common.json +2 -0
  7. package/locales/ar/electron.json +32 -0
  8. package/locales/ar/models.json +129 -3
  9. package/locales/ar/plugin.json +1 -0
  10. package/locales/ar/tool.json +25 -0
  11. package/locales/bg-BG/common.json +2 -0
  12. package/locales/bg-BG/electron.json +32 -0
  13. package/locales/bg-BG/models.json +129 -3
  14. package/locales/bg-BG/plugin.json +1 -0
  15. package/locales/bg-BG/tool.json +25 -0
  16. package/locales/de-DE/common.json +2 -0
  17. package/locales/de-DE/electron.json +32 -0
  18. package/locales/de-DE/models.json +129 -3
  19. package/locales/de-DE/plugin.json +1 -0
  20. package/locales/de-DE/tool.json +25 -0
  21. package/locales/en-US/common.json +2 -0
  22. package/locales/en-US/electron.json +32 -0
  23. package/locales/en-US/models.json +129 -3
  24. package/locales/en-US/plugin.json +1 -0
  25. package/locales/en-US/tool.json +25 -0
  26. package/locales/es-ES/common.json +2 -0
  27. package/locales/es-ES/electron.json +32 -0
  28. package/locales/es-ES/models.json +129 -3
  29. package/locales/es-ES/plugin.json +1 -0
  30. package/locales/es-ES/tool.json +25 -0
  31. package/locales/fa-IR/common.json +2 -0
  32. package/locales/fa-IR/electron.json +32 -0
  33. package/locales/fa-IR/models.json +129 -3
  34. package/locales/fa-IR/plugin.json +1 -0
  35. package/locales/fa-IR/tool.json +25 -0
  36. package/locales/fr-FR/common.json +2 -0
  37. package/locales/fr-FR/electron.json +32 -0
  38. package/locales/fr-FR/models.json +129 -3
  39. package/locales/fr-FR/plugin.json +1 -0
  40. package/locales/fr-FR/tool.json +25 -0
  41. package/locales/it-IT/common.json +2 -0
  42. package/locales/it-IT/electron.json +32 -0
  43. package/locales/it-IT/models.json +129 -3
  44. package/locales/it-IT/plugin.json +1 -0
  45. package/locales/it-IT/tool.json +25 -0
  46. package/locales/ja-JP/common.json +2 -0
  47. package/locales/ja-JP/electron.json +32 -0
  48. package/locales/ja-JP/models.json +129 -3
  49. package/locales/ja-JP/plugin.json +1 -0
  50. package/locales/ja-JP/tool.json +25 -0
  51. package/locales/ko-KR/common.json +2 -0
  52. package/locales/ko-KR/electron.json +32 -0
  53. package/locales/ko-KR/models.json +129 -3
  54. package/locales/ko-KR/plugin.json +1 -0
  55. package/locales/ko-KR/tool.json +25 -0
  56. package/locales/nl-NL/common.json +2 -0
  57. package/locales/nl-NL/electron.json +32 -0
  58. package/locales/nl-NL/models.json +129 -3
  59. package/locales/nl-NL/plugin.json +1 -0
  60. package/locales/nl-NL/tool.json +25 -0
  61. package/locales/pl-PL/common.json +2 -0
  62. package/locales/pl-PL/electron.json +32 -0
  63. package/locales/pl-PL/models.json +129 -3
  64. package/locales/pl-PL/plugin.json +1 -0
  65. package/locales/pl-PL/tool.json +25 -0
  66. package/locales/pt-BR/common.json +2 -0
  67. package/locales/pt-BR/electron.json +32 -0
  68. package/locales/pt-BR/models.json +129 -3
  69. package/locales/pt-BR/plugin.json +1 -0
  70. package/locales/pt-BR/tool.json +25 -0
  71. package/locales/ru-RU/common.json +2 -0
  72. package/locales/ru-RU/electron.json +32 -0
  73. package/locales/ru-RU/models.json +129 -3
  74. package/locales/ru-RU/plugin.json +1 -0
  75. package/locales/ru-RU/tool.json +25 -0
  76. package/locales/tr-TR/common.json +2 -0
  77. package/locales/tr-TR/electron.json +32 -0
  78. package/locales/tr-TR/models.json +129 -3
  79. package/locales/tr-TR/plugin.json +1 -0
  80. package/locales/tr-TR/tool.json +25 -0
  81. package/locales/vi-VN/common.json +2 -0
  82. package/locales/vi-VN/electron.json +32 -0
  83. package/locales/vi-VN/models.json +129 -3
  84. package/locales/vi-VN/plugin.json +1 -0
  85. package/locales/vi-VN/tool.json +25 -0
  86. package/locales/zh-CN/common.json +2 -0
  87. package/locales/zh-CN/electron.json +32 -0
  88. package/locales/zh-CN/models.json +134 -8
  89. package/locales/zh-CN/plugin.json +1 -0
  90. package/locales/zh-CN/tool.json +25 -0
  91. package/locales/zh-TW/common.json +2 -0
  92. package/locales/zh-TW/electron.json +32 -0
  93. package/locales/zh-TW/models.json +129 -3
  94. package/locales/zh-TW/plugin.json +1 -0
  95. package/locales/zh-TW/tool.json +25 -0
  96. package/package.json +4 -3
  97. package/packages/electron-client-ipc/src/events/index.ts +5 -5
  98. package/packages/electron-client-ipc/src/events/localFile.ts +22 -0
  99. package/packages/electron-client-ipc/src/events/{file.ts → upload.ts} +1 -1
  100. package/packages/electron-client-ipc/src/types/index.ts +2 -1
  101. package/packages/electron-client-ipc/src/types/localFile.ts +52 -0
  102. package/packages/file-loaders/README.md +63 -0
  103. package/packages/file-loaders/package.json +42 -0
  104. package/packages/file-loaders/src/index.ts +2 -0
  105. package/packages/file-loaders/src/loadFile.ts +206 -0
  106. package/packages/file-loaders/src/loaders/docx/__snapshots__/index.test.ts.snap +74 -0
  107. package/packages/file-loaders/src/loaders/docx/fixtures/test.docx +0 -0
  108. package/packages/file-loaders/src/loaders/docx/index.test.ts +41 -0
  109. package/packages/file-loaders/src/loaders/docx/index.ts +73 -0
  110. package/packages/file-loaders/src/loaders/excel/__snapshots__/index.test.ts.snap +58 -0
  111. package/packages/file-loaders/src/loaders/excel/fixtures/test.xlsx +0 -0
  112. package/packages/file-loaders/src/loaders/excel/index.test.ts +47 -0
  113. package/packages/file-loaders/src/loaders/excel/index.ts +121 -0
  114. package/packages/file-loaders/src/loaders/index.ts +19 -0
  115. package/packages/file-loaders/src/loaders/pdf/__snapshots__/index.test.ts.snap +98 -0
  116. package/packages/file-loaders/src/loaders/pdf/index.test.ts +49 -0
  117. package/packages/file-loaders/src/loaders/pdf/index.ts +133 -0
  118. package/packages/file-loaders/src/loaders/pptx/__snapshots__/index.test.ts.snap +40 -0
  119. package/packages/file-loaders/src/loaders/pptx/fixtures/test.pptx +0 -0
  120. package/packages/file-loaders/src/loaders/pptx/index.test.ts +47 -0
  121. package/packages/file-loaders/src/loaders/pptx/index.ts +186 -0
  122. package/packages/file-loaders/src/loaders/text/__snapshots__/index.test.ts.snap +15 -0
  123. package/packages/file-loaders/src/loaders/text/fixtures/test.txt +2 -0
  124. package/packages/file-loaders/src/loaders/text/index.test.ts +38 -0
  125. package/packages/file-loaders/src/loaders/text/index.ts +53 -0
  126. package/packages/file-loaders/src/types.ts +200 -0
  127. package/packages/file-loaders/src/utils/isTextReadableFile.ts +68 -0
  128. package/packages/file-loaders/src/utils/parser-utils.ts +112 -0
  129. package/packages/file-loaders/test/__snapshots__/loaders.test.ts.snap +93 -0
  130. package/packages/file-loaders/test/fixtures/test.csv +4 -0
  131. package/packages/file-loaders/test/fixtures/test.docx +0 -0
  132. package/packages/file-loaders/test/fixtures/test.epub +0 -0
  133. package/packages/file-loaders/test/fixtures/test.md +3 -0
  134. package/packages/file-loaders/test/fixtures/test.pptx +0 -0
  135. package/packages/file-loaders/test/fixtures/test.txt +3 -0
  136. package/packages/file-loaders/test/loaders.test.ts +39 -0
  137. package/scripts/prebuild.mts +5 -1
  138. package/src/app/(backend)/trpc/desktop/[trpc]/route.ts +26 -0
  139. package/src/features/Conversation/Messages/Assistant/Tool/Render/Arguments/ObjectEntity.tsx +81 -0
  140. package/src/features/Conversation/Messages/Assistant/Tool/Render/Arguments/ValueCell.tsx +43 -0
  141. package/src/features/Conversation/Messages/Assistant/Tool/Render/Arguments/index.tsx +120 -0
  142. package/src/features/Conversation/Messages/Assistant/Tool/Render/CustomRender.tsx +75 -2
  143. package/src/features/Conversation/Messages/Assistant/Tool/Render/KeyValueEditor.tsx +214 -0
  144. package/src/features/User/UserPanel/useMenu.tsx +8 -1
  145. package/src/libs/agent-runtime/google/index.ts +3 -0
  146. package/src/libs/trpc/client/desktop.ts +14 -0
  147. package/src/locales/default/common.ts +2 -0
  148. package/src/locales/default/electron.ts +34 -0
  149. package/src/locales/default/index.ts +2 -0
  150. package/src/locales/default/tool.ts +25 -0
  151. package/src/server/routers/desktop/index.ts +9 -0
  152. package/src/server/routers/desktop/pgTable.ts +43 -0
  153. package/src/services/electron/autoUpdate.ts +17 -0
  154. package/src/services/electron/file.ts +31 -0
  155. package/src/services/electron/localFileService.ts +39 -0
  156. package/src/services/electron/remoteServer.ts +40 -0
  157. package/src/store/chat/index.ts +1 -1
  158. package/src/store/chat/slices/builtinTool/actions/index.ts +3 -1
  159. package/src/store/chat/slices/builtinTool/actions/localFile.ts +129 -0
  160. package/src/store/chat/slices/builtinTool/initialState.ts +2 -0
  161. package/src/store/chat/slices/builtinTool/selectors.ts +2 -0
  162. package/src/store/chat/slices/plugin/action.ts +3 -3
  163. package/src/store/chat/store.ts +2 -0
  164. package/src/store/electron/actions/sync.ts +117 -0
  165. package/src/store/electron/index.ts +1 -0
  166. package/src/store/electron/initialState.ts +18 -0
  167. package/src/store/electron/selectors/index.ts +1 -0
  168. package/src/store/electron/selectors/sync.ts +9 -0
  169. package/src/store/electron/store.ts +29 -0
  170. package/src/tools/index.ts +8 -0
  171. package/src/tools/local-files/Render/ListFiles/Result.tsx +42 -0
  172. package/src/tools/local-files/Render/ListFiles/index.tsx +68 -0
  173. package/src/tools/local-files/Render/ReadLocalFile/ReadFileSkeleton.tsx +50 -0
  174. package/src/tools/local-files/Render/ReadLocalFile/ReadFileView.tsx +197 -0
  175. package/src/tools/local-files/Render/ReadLocalFile/index.tsx +31 -0
  176. package/src/tools/local-files/Render/ReadLocalFile/style.ts +37 -0
  177. package/src/tools/local-files/Render/SearchFiles/Result.tsx +42 -0
  178. package/src/tools/local-files/Render/SearchFiles/SearchQuery/SearchView.tsx +77 -0
  179. package/src/tools/local-files/Render/SearchFiles/SearchQuery/index.tsx +72 -0
  180. package/src/tools/local-files/Render/SearchFiles/index.tsx +32 -0
  181. package/src/tools/local-files/Render/index.tsx +36 -0
  182. package/src/tools/local-files/components/FileItem.tsx +117 -0
  183. package/src/tools/local-files/index.ts +149 -0
  184. package/src/tools/local-files/systemRole.ts +46 -0
  185. package/src/tools/local-files/type.ts +33 -0
  186. package/src/tools/renders.ts +3 -0
  187. package/packages/electron-client-ipc/src/events/search.ts +0 -4
  188. package/src/features/Conversation/Messages/Assistant/Tool/Render/Arguments.tsx +0 -165
  189. /package/packages/electron-client-ipc/src/types/{file.ts → upload.ts} +0 -0
@@ -0,0 +1,17 @@
1
+ import { dispatch } from '@lobechat/electron-client-ipc';
2
+
3
+ class AutoUpdateService {
4
+ checkUpdate = async () => {
5
+ return dispatch('checkUpdate');
6
+ };
7
+
8
+ installNow = async () => {
9
+ return dispatch('installNow');
10
+ };
11
+
12
+ installLater = async () => {
13
+ return dispatch('installLater');
14
+ };
15
+ }
16
+
17
+ export const autoUpdateService = new AutoUpdateService();
@@ -0,0 +1,31 @@
1
+ import { dispatch } from '@lobechat/electron-client-ipc';
2
+
3
+ import { FileMetadata } from '@/types/files';
4
+
5
+ /**
6
+ * 桌面应用文件API客户端服务
7
+ */
8
+ class DesktopFileAPI {
9
+ /**
10
+ * 上传文件到桌面应用
11
+ * @param file 文件对象
12
+ * @param hash 文件哈希
13
+ * @returns 上传结果
14
+ */
15
+ async uploadFile(
16
+ file: File,
17
+ hash: string,
18
+ ): Promise<{ metadata: FileMetadata; success: boolean }> {
19
+ const arrayBuffer = await file.arrayBuffer();
20
+
21
+ return dispatch('createFile', {
22
+ content: arrayBuffer,
23
+ filename: file.name,
24
+ hash,
25
+ path: file.name,
26
+ type: file.type,
27
+ });
28
+ }
29
+ }
30
+
31
+ export const desktopFileAPI = new DesktopFileAPI();
@@ -0,0 +1,39 @@
1
+ import {
2
+ ListLocalFileParams,
3
+ LocalFileItem,
4
+ LocalReadFileParams,
5
+ LocalReadFileResult,
6
+ LocalReadFilesParams,
7
+ LocalSearchFilesParams,
8
+ OpenLocalFileParams,
9
+ OpenLocalFolderParams,
10
+ dispatch,
11
+ } from '@lobechat/electron-client-ipc';
12
+
13
+ class LocalFileService {
14
+ async listLocalFiles(params: ListLocalFileParams): Promise<LocalFileItem[]> {
15
+ return dispatch('listLocalFiles', params);
16
+ }
17
+
18
+ async readLocalFile(params: LocalReadFileParams): Promise<LocalReadFileResult> {
19
+ return dispatch('readLocalFile', params);
20
+ }
21
+
22
+ async readLocalFiles(params: LocalReadFilesParams): Promise<LocalReadFileResult[]> {
23
+ return dispatch('readLocalFiles', params);
24
+ }
25
+
26
+ async searchLocalFiles(params: LocalSearchFilesParams): Promise<LocalFileItem[]> {
27
+ return dispatch('searchLocalFiles', params);
28
+ }
29
+
30
+ async openLocalFile(params: OpenLocalFileParams) {
31
+ return dispatch('openLocalFile', params);
32
+ }
33
+
34
+ async openLocalFolder(params: OpenLocalFolderParams) {
35
+ return dispatch('openLocalFolder', params);
36
+ }
37
+ }
38
+
39
+ export const localFileService = new LocalFileService();
@@ -0,0 +1,40 @@
1
+ import { RemoteServerConfig, dispatch } from '@lobechat/electron-client-ipc';
2
+
3
+ class RemoteServerService {
4
+ /**
5
+ * 获取远程服务器配置
6
+ */
7
+ getRemoteServerConfig = async () => {
8
+ return dispatch('getRemoteServerConfig');
9
+ };
10
+
11
+ /**
12
+ * 设置远程服务器配置
13
+ */
14
+ setRemoteServerConfig = async (config: RemoteServerConfig) => {
15
+ return dispatch('setRemoteServerConfig', config);
16
+ };
17
+
18
+ /**
19
+ * 清除远程服务器配置
20
+ */
21
+ clearRemoteServerConfig = async () => {
22
+ return dispatch('clearRemoteServerConfig');
23
+ };
24
+
25
+ /**
26
+ * 请求授权
27
+ */
28
+ requestAuthorization = async (serverUrl: string) => {
29
+ return dispatch('requestAuthorization', serverUrl);
30
+ };
31
+
32
+ /**
33
+ * 刷新访问令牌
34
+ */
35
+ refreshAccessToken = async () => {
36
+ return dispatch('refreshAccessToken');
37
+ };
38
+ }
39
+
40
+ export const remoteServerService = new RemoteServerService();
@@ -1,3 +1,3 @@
1
1
  export type { ChatStoreState } from './initialState';
2
2
  export type { ChatStore } from './store';
3
- export { useChatStore } from './store';
3
+ export { getChatStoreState, useChatStore } from './store';
@@ -3,9 +3,10 @@ import { StateCreator } from 'zustand/vanilla';
3
3
  import { ChatStore } from '@/store/chat/store';
4
4
 
5
5
  import { ChatDallEAction, dalleSlice } from './dalle';
6
+ import { LocalFileAction, localFileSlice } from './localFile';
6
7
  import { SearchAction, searchSlice } from './search';
7
8
 
8
- export interface ChatBuiltinToolAction extends ChatDallEAction, SearchAction {}
9
+ export interface ChatBuiltinToolAction extends ChatDallEAction, SearchAction, LocalFileAction {}
9
10
 
10
11
  export const chatToolSlice: StateCreator<
11
12
  ChatStore,
@@ -15,4 +16,5 @@ export const chatToolSlice: StateCreator<
15
16
  > = (...params) => ({
16
17
  ...dalleSlice(...params),
17
18
  ...searchSlice(...params),
19
+ ...localFileSlice(...params),
18
20
  });
@@ -0,0 +1,129 @@
1
+ import {
2
+ ListLocalFileParams,
3
+ LocalReadFileParams,
4
+ LocalReadFilesParams,
5
+ LocalSearchFilesParams,
6
+ } from '@lobechat/electron-client-ipc';
7
+ import { StateCreator } from 'zustand/vanilla';
8
+
9
+ import { localFileService } from '@/services/electron/localFileService';
10
+ import { ChatStore } from '@/store/chat/store';
11
+ import {
12
+ LocalFileListState,
13
+ LocalFileSearchState,
14
+ LocalReadFileState,
15
+ LocalReadFilesState,
16
+ } from '@/tools/local-files/type';
17
+
18
+ export interface LocalFileAction {
19
+ listLocalFiles: (id: string, params: ListLocalFileParams) => Promise<boolean>;
20
+ reSearchLocalFiles: (id: string, params: LocalSearchFilesParams) => Promise<boolean>;
21
+ readLocalFile: (id: string, params: LocalReadFileParams) => Promise<boolean>;
22
+ readLocalFiles: (id: string, params: LocalReadFilesParams) => Promise<boolean>;
23
+ searchLocalFiles: (id: string, params: LocalSearchFilesParams) => Promise<boolean>;
24
+ toggleLocalFileLoading: (id: string, loading: boolean) => void;
25
+ }
26
+
27
+ export const localFileSlice: StateCreator<
28
+ ChatStore,
29
+ [['zustand/devtools', never]],
30
+ [],
31
+ LocalFileAction
32
+ > = (set, get) => ({
33
+ listLocalFiles: async (id, params) => {
34
+ get().toggleLocalFileLoading(id, true);
35
+ try {
36
+ const data = await localFileService.listLocalFiles(params);
37
+ console.log(data);
38
+ await get().updatePluginState(id, { listResults: data } as LocalFileListState);
39
+ await get().internal_updateMessageContent(id, JSON.stringify(data));
40
+ } catch (error) {
41
+ console.error('Error listing local files:', error);
42
+ await get().internal_updateMessagePluginError(id, {
43
+ body: error,
44
+ message: (error as Error).message,
45
+ type: 'PluginServerError',
46
+ });
47
+ }
48
+ get().toggleLocalFileLoading(id, false);
49
+
50
+ return true;
51
+ },
52
+
53
+ reSearchLocalFiles: async (id, params) => {
54
+ get().toggleLocalFileLoading(id, true);
55
+
56
+ await get().updatePluginArguments(id, params);
57
+
58
+ return get().searchLocalFiles(id, params);
59
+ },
60
+
61
+ readLocalFile: async (id, params) => {
62
+ get().toggleLocalFileLoading(id, true);
63
+
64
+ try {
65
+ const result = await localFileService.readLocalFile(params);
66
+
67
+ await get().updatePluginState(id, { fileContent: result } as LocalReadFileState);
68
+ await get().internal_updateMessageContent(id, JSON.stringify(result));
69
+ } catch (error) {
70
+ console.error('Error reading local file:', error);
71
+ await get().internal_updateMessagePluginError(id, {
72
+ body: error,
73
+ message: (error as Error).message,
74
+ type: 'PluginServerError',
75
+ });
76
+ }
77
+ get().toggleLocalFileLoading(id, false);
78
+ return true;
79
+ },
80
+
81
+ readLocalFiles: async (id, params) => {
82
+ get().toggleLocalFileLoading(id, true);
83
+
84
+ try {
85
+ const results = await localFileService.readLocalFiles(params);
86
+ await get().updatePluginState(id, { filesContent: results } as LocalReadFilesState);
87
+ await get().internal_updateMessageContent(id, JSON.stringify(results));
88
+ } catch (error) {
89
+ console.error('Error reading local files:', error);
90
+ await get().internal_updateMessagePluginError(id, {
91
+ body: error,
92
+ message: (error as Error).message,
93
+ type: 'PluginServerError',
94
+ });
95
+ }
96
+ get().toggleLocalFileLoading(id, false);
97
+
98
+ return true;
99
+ },
100
+
101
+ searchLocalFiles: async (id, params) => {
102
+ get().toggleLocalFileLoading(id, true);
103
+ try {
104
+ const data = await localFileService.searchLocalFiles(params);
105
+ await get().updatePluginState(id, { searchResults: data } as LocalFileSearchState);
106
+ await get().internal_updateMessageContent(id, JSON.stringify(data));
107
+ } catch (error) {
108
+ console.error('Error searching local files:', error);
109
+ await get().internal_updateMessagePluginError(id, {
110
+ body: error,
111
+ message: (error as Error).message,
112
+ type: 'PluginServerError',
113
+ });
114
+ }
115
+ get().toggleLocalFileLoading(id, false);
116
+
117
+ return true;
118
+ },
119
+ toggleLocalFileLoading: (id, loading) => {
120
+ // Assuming a loading state structure similar to searchLoading
121
+ set(
122
+ (state) => ({
123
+ localFileLoading: { ...state.localFileLoading, [id]: loading },
124
+ }),
125
+ false,
126
+ `toggleLocalFileLoading/${loading ? 'start' : 'end'}`,
127
+ );
128
+ },
129
+ });
@@ -4,11 +4,13 @@ export interface ChatToolState {
4
4
  activePageContentUrl?: string;
5
5
  dalleImageLoading: Record<string, boolean>;
6
6
  dalleImageMap: Record<string, FileItem>;
7
+ localFileLoading: Record<string, boolean>;
7
8
  searchLoading: Record<string, boolean>;
8
9
  }
9
10
 
10
11
  export const initialToolState: ChatToolState = {
11
12
  dalleImageLoading: {},
12
13
  dalleImageMap: {},
14
+ localFileLoading: {},
13
15
  searchLoading: {},
14
16
  };
@@ -6,9 +6,11 @@ const isGeneratingDallEImage = (s: ChatStoreState) =>
6
6
  Object.values(s.dalleImageLoading).some(Boolean);
7
7
 
8
8
  const isSearXNGSearching = (id: string) => (s: ChatStoreState) => s.searchLoading[id];
9
+ const isSearchingLocalFiles = (id: string) => (s: ChatStoreState) => s.localFileLoading[id];
9
10
 
10
11
  export const chatToolSelectors = {
11
12
  isDallEImageGenerating,
12
13
  isGeneratingDallEImage,
13
14
  isSearXNGSearching,
15
+ isSearchingLocalFiles,
14
16
  };
@@ -59,7 +59,7 @@ export interface ChatPluginAction {
59
59
  params?: { threadId?: string; inPortalThread?: boolean; inSearchWorkflow?: boolean },
60
60
  ) => Promise<void>;
61
61
  updatePluginState: (id: string, value: any) => Promise<void>;
62
- updatePluginArguments: <T = any>(id: string, value: T) => Promise<void>;
62
+ updatePluginArguments: <T = any>(id: string, value: T, replace?: boolean) => Promise<void>;
63
63
 
64
64
  internal_addToolToAssistantMessage: (id: string, tool: ChatToolPayload) => Promise<void>;
65
65
  internal_removeToolToAssistantMessage: (id: string, tool_call_id?: string) => Promise<void>;
@@ -296,7 +296,7 @@ export const chatPlugin: StateCreator<
296
296
  await refreshMessages();
297
297
  },
298
298
 
299
- updatePluginArguments: async (id, value) => {
299
+ updatePluginArguments: async (id, value, replace = false) => {
300
300
  const { refreshMessages } = get();
301
301
  const toolMessage = chatSelectors.getMessageById(id)(get());
302
302
  if (!toolMessage || !toolMessage?.tool_call_id) return;
@@ -305,7 +305,7 @@ export const chatPlugin: StateCreator<
305
305
 
306
306
  const prevArguments = toolMessage?.plugin?.arguments;
307
307
  const prevJson = safeParseJSON(prevArguments || '');
308
- const nextValue = merge(prevJson || {}, value);
308
+ const nextValue = replace ? (value as any) : merge(prevJson || {}, value);
309
309
  if (isEqual(prevJson, nextValue)) return;
310
310
 
311
311
  // optimistic update
@@ -57,3 +57,5 @@ export const useChatStore = createWithEqualityFn<ChatStore>()(
57
57
  subscribeWithSelector(devtools(createStore)),
58
58
  shallow,
59
59
  );
60
+
61
+ export const getChatStoreState = () => useChatStore.getState();
@@ -0,0 +1,117 @@
1
+ import { RemoteServerConfig } from '@lobechat/electron-client-ipc';
2
+ import useSWR, { SWRResponse, mutate } from 'swr';
3
+ import type { StateCreator } from 'zustand/vanilla';
4
+
5
+ import { INBOX_SESSION_ID } from '@/const/session';
6
+ import { remoteServerService } from '@/services/electron/remoteServer';
7
+
8
+ import { initialState } from '../initialState';
9
+ import type { ElectronStore } from '../store';
10
+
11
+ /**
12
+ * 设置操作
13
+ */
14
+ export interface ElectronRemoteServerAction {
15
+ connectRemoteServer: (params: { isSelfHosted: boolean; serverUrl?: string }) => Promise<void>;
16
+ disconnectRemoteServer: () => Promise<void>;
17
+ refreshServerConfig: () => Promise<void>;
18
+ refreshUserData: () => Promise<void>;
19
+ useRemoteServerConfig: () => SWRResponse;
20
+ }
21
+
22
+ const REMOTE_SERVER_CONFIG_KEY = 'electron:getRemoteServerConfig';
23
+
24
+ export const remoteSyncSlice: StateCreator<
25
+ ElectronStore,
26
+ [['zustand/devtools', never]],
27
+ [],
28
+ ElectronRemoteServerAction
29
+ > = (set, get) => ({
30
+ connectRemoteServer: async (values) => {
31
+ if (!values.serverUrl) return;
32
+
33
+ set({ isConnectingServer: true });
34
+ try {
35
+ // 获取当前配置
36
+ const config = await remoteServerService.getRemoteServerConfig();
37
+
38
+ // 如果已经激活,需要先清除
39
+ if (config.active) {
40
+ await remoteServerService.clearRemoteServerConfig();
41
+ }
42
+
43
+ // 请求授权
44
+ const result = await remoteServerService.requestAuthorization(values.serverUrl);
45
+
46
+ if (!result.success) {
47
+ console.error('请求授权失败:', result.error);
48
+
49
+ set({
50
+ remoteServerSyncError: { message: result.error, type: 'AUTH_ERROR' },
51
+ });
52
+ }
53
+ // 刷新状态
54
+ await get().refreshServerConfig();
55
+ } catch (error) {
56
+ console.error('远程服务器配置出错:', error);
57
+ set({
58
+ remoteServerSyncError: { message: (error as Error).message, type: 'CONFIG_ERROR' },
59
+ });
60
+ } finally {
61
+ set({ isConnectingServer: false });
62
+ }
63
+ },
64
+
65
+ disconnectRemoteServer: async () => {
66
+ set({ isConnectingServer: false });
67
+ try {
68
+ await remoteServerService.clearRemoteServerConfig();
69
+ // 更新表单URL为空
70
+ set({ remoteServerConfig: initialState.remoteServerConfig });
71
+ // 刷新状态
72
+ await get().refreshServerConfig();
73
+ } catch (error) {
74
+ console.error('断开连接失败:', error);
75
+ set({
76
+ remoteServerSyncError: { message: (error as Error).message, type: 'DISCONNECT_ERROR' },
77
+ });
78
+ } finally {
79
+ set({ isConnectingServer: false });
80
+ }
81
+ },
82
+
83
+ refreshServerConfig: async () => {
84
+ await mutate(REMOTE_SERVER_CONFIG_KEY);
85
+ },
86
+
87
+ refreshUserData: async () => {
88
+ const { getSessionStoreState } = await import('@/store/session');
89
+ const { getChatStoreState } = await import('@/store/chat');
90
+ const { getUserStoreState } = await import('@/store/user');
91
+
92
+ await getSessionStoreState().refreshSessions();
93
+ await getChatStoreState().refreshMessages();
94
+ await getChatStoreState().refreshTopic();
95
+ await getUserStoreState().refreshUserState();
96
+ getSessionStoreState().switchSession(INBOX_SESSION_ID);
97
+ },
98
+
99
+ useRemoteServerConfig: () =>
100
+ useSWR<RemoteServerConfig>(
101
+ REMOTE_SERVER_CONFIG_KEY,
102
+ async () => {
103
+ try {
104
+ return await remoteServerService.getRemoteServerConfig();
105
+ } catch (error) {
106
+ console.error('获取远程服务器配置失败:', error);
107
+ throw error;
108
+ }
109
+ },
110
+ {
111
+ onSuccess: (data) => {
112
+ set({ isInitRemoteServerConfig: true, remoteServerConfig: data });
113
+ get().refreshUserData();
114
+ },
115
+ },
116
+ ),
117
+ });
@@ -0,0 +1 @@
1
+ export * from './store';
@@ -0,0 +1,18 @@
1
+ import { RemoteServerConfig } from '@lobechat/electron-client-ipc';
2
+
3
+ export type RemoteServerError = 'CONFIG_ERROR' | 'AUTH_ERROR' | 'DISCONNECT_ERROR';
4
+
5
+ export interface ElectronState {
6
+ isConnectingServer?: boolean;
7
+ isInitRemoteServerConfig: boolean;
8
+ isSyncActive?: boolean;
9
+ remoteServerConfig: RemoteServerConfig;
10
+ remoteServerSyncError?: { message?: string; type: RemoteServerError };
11
+ }
12
+
13
+ export const initialState: ElectronState = {
14
+ isConnectingServer: false,
15
+ isInitRemoteServerConfig: false,
16
+ isSyncActive: false,
17
+ remoteServerConfig: { active: false, isSelfHosted: false },
18
+ };
@@ -0,0 +1 @@
1
+ export * from './sync';
@@ -0,0 +1,9 @@
1
+ import { ElectronState } from '../initialState';
2
+
3
+ const isSyncActive = (s: ElectronState) => {
4
+ return s.remoteServerConfig.active;
5
+ };
6
+
7
+ export const electronSyncSelectors = {
8
+ isSyncActive,
9
+ };
@@ -0,0 +1,29 @@
1
+ import { shallow } from 'zustand/shallow';
2
+ import { createWithEqualityFn } from 'zustand/traditional';
3
+ import { StateCreator } from 'zustand/vanilla';
4
+
5
+ import { createDevtools } from '../middleware/createDevtools';
6
+ import { type ElectronRemoteServerAction, remoteSyncSlice } from './actions/sync';
7
+ import { type ElectronState, initialState } from './initialState';
8
+
9
+ // =============== 聚合 createStoreFn ============ //
10
+
11
+ export interface ElectronStore extends ElectronState, ElectronRemoteServerAction {
12
+ /* empty */
13
+ }
14
+
15
+ const createStore: StateCreator<ElectronStore, [['zustand/devtools', never]]> = (
16
+ ...parameters
17
+ ) => ({
18
+ ...initialState,
19
+ ...remoteSyncSlice(...parameters),
20
+ });
21
+
22
+ // =============== 实装 useStore ============ //
23
+
24
+ const devtools = createDevtools('electron');
25
+
26
+ export const useElectronStore = createWithEqualityFn<ElectronStore>()(
27
+ devtools(createStore),
28
+ shallow,
29
+ );
@@ -1,7 +1,9 @@
1
+ import { isDesktop } from '@/const/version';
1
2
  import { LobeBuiltinTool } from '@/types/tool';
2
3
 
3
4
  import { ArtifactsManifest } from './artifacts';
4
5
  import { DalleManifest } from './dalle';
6
+ import { LocalFilesManifest } from './local-files';
5
7
  import { WebBrowsingManifest } from './web-browsing';
6
8
 
7
9
  export const builtinTools: LobeBuiltinTool[] = [
@@ -15,6 +17,12 @@ export const builtinTools: LobeBuiltinTool[] = [
15
17
  manifest: DalleManifest,
16
18
  type: 'builtin',
17
19
  },
20
+ {
21
+ hidden: !isDesktop,
22
+ identifier: LocalFilesManifest.identifier,
23
+ manifest: LocalFilesManifest,
24
+ type: 'builtin',
25
+ },
18
26
  {
19
27
  hidden: true,
20
28
  identifier: WebBrowsingManifest.identifier,
@@ -0,0 +1,42 @@
1
+ import { LocalFileItem } from '@lobechat/electron-client-ipc';
2
+ import { Skeleton } from 'antd';
3
+ import { memo } from 'react';
4
+ import { Flexbox } from 'react-layout-kit';
5
+
6
+ import { useChatStore } from '@/store/chat';
7
+ import { chatToolSelectors } from '@/store/chat/selectors';
8
+ import FileItem from '@/tools/local-files/components/FileItem';
9
+ import { ChatMessagePluginError } from '@/types/message';
10
+
11
+ interface SearchFilesProps {
12
+ listResults?: LocalFileItem[];
13
+ messageId: string;
14
+ pluginError: ChatMessagePluginError;
15
+ }
16
+
17
+ const SearchFiles = memo<SearchFilesProps>(({ listResults = [], messageId }) => {
18
+ const loading = useChatStore(chatToolSelectors.isSearchingLocalFiles(messageId));
19
+
20
+ if (loading) {
21
+ return (
22
+ <Flexbox gap={4}>
23
+ <Skeleton.Button active block style={{ height: 16 }} />
24
+ <Skeleton.Button active block style={{ height: 16 }} />
25
+ <Skeleton.Button active block style={{ height: 16 }} />
26
+ <Skeleton.Button active block style={{ height: 16 }} />
27
+ </Flexbox>
28
+ );
29
+ }
30
+
31
+ return (
32
+ <Flexbox gap={2} style={{ maxHeight: 260, overflow: 'scroll' }}>
33
+ {listResults.map((item) => (
34
+ <FileItem key={item.path} {...item} showTime />
35
+ ))}
36
+ </Flexbox>
37
+ );
38
+ });
39
+
40
+ SearchFiles.displayName = 'SearchFiles';
41
+
42
+ export default SearchFiles;
@@ -0,0 +1,68 @@
1
+ import { ListLocalFileParams } from '@lobechat/electron-client-ipc';
2
+ import { ActionIcon } from '@lobehub/ui';
3
+ import { Typography } from 'antd';
4
+ import { createStyles } from 'antd-style';
5
+ import { FolderOpen } from 'lucide-react';
6
+ import React, { memo } from 'react';
7
+ import { useTranslation } from 'react-i18next';
8
+ import { Flexbox } from 'react-layout-kit';
9
+
10
+ import { localFileService } from '@/services/electron/localFileService';
11
+ import { LocalFileListState } from '@/tools/local-files/type';
12
+ import { ChatMessagePluginError } from '@/types/message';
13
+
14
+ import SearchResult from './Result';
15
+
16
+ const useStyles = createStyles(({ css, token, cx }) => ({
17
+ actions: cx(css`
18
+ cursor: pointer;
19
+ color: ${token.colorTextTertiary};
20
+ opacity: 1;
21
+ transition: opacity 0.2s ${token.motionEaseInOut};
22
+ `),
23
+ path: css`
24
+ padding-inline-start: 8px;
25
+ color: ${token.colorTextSecondary};
26
+ `,
27
+ }));
28
+
29
+ interface ListFilesProps {
30
+ args: ListLocalFileParams;
31
+ messageId: string;
32
+ pluginError: ChatMessagePluginError;
33
+ pluginState?: LocalFileListState;
34
+ }
35
+
36
+ const ListFiles = memo<ListFilesProps>(({ messageId, pluginError, args, pluginState }) => {
37
+ const { t } = useTranslation('tool');
38
+
39
+ const { styles } = useStyles();
40
+ return (
41
+ <>
42
+ <Flexbox gap={8} horizontal>
43
+ <Typography.Text className={styles.path} ellipsis>
44
+ {args.path}
45
+ </Typography.Text>
46
+ <Flexbox className={styles.actions} gap={8} horizontal style={{ marginLeft: 8 }}>
47
+ <ActionIcon
48
+ icon={FolderOpen}
49
+ onClick={() => {
50
+ localFileService.openLocalFolder({ isDirectory: true, path: args.path });
51
+ }}
52
+ size="small"
53
+ title={t('localFiles.openFolder')}
54
+ />
55
+ </Flexbox>
56
+ </Flexbox>
57
+ <SearchResult
58
+ listResults={pluginState?.listResults}
59
+ messageId={messageId}
60
+ pluginError={pluginError}
61
+ />
62
+ </>
63
+ );
64
+ });
65
+
66
+ ListFiles.displayName = 'ListFiles';
67
+
68
+ export default ListFiles;