@lobehub/chat 1.81.2 → 1.81.4

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 (154) hide show
  1. package/CHANGELOG.md +59 -0
  2. package/changelog/v1.json +21 -0
  3. package/locales/ar/common.json +2 -0
  4. package/locales/ar/electron.json +32 -0
  5. package/locales/ar/models.json +126 -3
  6. package/locales/ar/plugin.json +1 -0
  7. package/locales/ar/tool.json +25 -0
  8. package/locales/bg-BG/common.json +2 -0
  9. package/locales/bg-BG/electron.json +32 -0
  10. package/locales/bg-BG/models.json +126 -3
  11. package/locales/bg-BG/plugin.json +1 -0
  12. package/locales/bg-BG/tool.json +25 -0
  13. package/locales/de-DE/common.json +2 -0
  14. package/locales/de-DE/electron.json +32 -0
  15. package/locales/de-DE/models.json +126 -3
  16. package/locales/de-DE/plugin.json +1 -0
  17. package/locales/de-DE/tool.json +25 -0
  18. package/locales/en-US/common.json +2 -0
  19. package/locales/en-US/electron.json +32 -0
  20. package/locales/en-US/models.json +126 -3
  21. package/locales/en-US/plugin.json +1 -0
  22. package/locales/en-US/tool.json +25 -0
  23. package/locales/es-ES/common.json +2 -0
  24. package/locales/es-ES/electron.json +32 -0
  25. package/locales/es-ES/models.json +126 -3
  26. package/locales/es-ES/plugin.json +1 -0
  27. package/locales/es-ES/tool.json +25 -0
  28. package/locales/fa-IR/common.json +2 -0
  29. package/locales/fa-IR/electron.json +32 -0
  30. package/locales/fa-IR/models.json +126 -3
  31. package/locales/fa-IR/plugin.json +1 -0
  32. package/locales/fa-IR/tool.json +25 -0
  33. package/locales/fr-FR/common.json +2 -0
  34. package/locales/fr-FR/electron.json +32 -0
  35. package/locales/fr-FR/models.json +126 -3
  36. package/locales/fr-FR/plugin.json +1 -0
  37. package/locales/fr-FR/tool.json +25 -0
  38. package/locales/it-IT/common.json +2 -0
  39. package/locales/it-IT/electron.json +32 -0
  40. package/locales/it-IT/models.json +126 -3
  41. package/locales/it-IT/plugin.json +1 -0
  42. package/locales/it-IT/tool.json +25 -0
  43. package/locales/ja-JP/common.json +2 -0
  44. package/locales/ja-JP/electron.json +32 -0
  45. package/locales/ja-JP/models.json +126 -3
  46. package/locales/ja-JP/plugin.json +1 -0
  47. package/locales/ja-JP/tool.json +25 -0
  48. package/locales/ko-KR/common.json +2 -0
  49. package/locales/ko-KR/electron.json +32 -0
  50. package/locales/ko-KR/models.json +126 -3
  51. package/locales/ko-KR/plugin.json +1 -0
  52. package/locales/ko-KR/tool.json +25 -0
  53. package/locales/nl-NL/common.json +2 -0
  54. package/locales/nl-NL/electron.json +32 -0
  55. package/locales/nl-NL/models.json +126 -3
  56. package/locales/nl-NL/plugin.json +1 -0
  57. package/locales/nl-NL/tool.json +25 -0
  58. package/locales/pl-PL/common.json +2 -0
  59. package/locales/pl-PL/electron.json +32 -0
  60. package/locales/pl-PL/models.json +126 -3
  61. package/locales/pl-PL/plugin.json +1 -0
  62. package/locales/pl-PL/tool.json +25 -0
  63. package/locales/pt-BR/common.json +2 -0
  64. package/locales/pt-BR/electron.json +32 -0
  65. package/locales/pt-BR/models.json +126 -3
  66. package/locales/pt-BR/plugin.json +1 -0
  67. package/locales/pt-BR/tool.json +25 -0
  68. package/locales/ru-RU/common.json +2 -0
  69. package/locales/ru-RU/electron.json +32 -0
  70. package/locales/ru-RU/models.json +126 -3
  71. package/locales/ru-RU/plugin.json +1 -0
  72. package/locales/ru-RU/tool.json +25 -0
  73. package/locales/tr-TR/common.json +2 -0
  74. package/locales/tr-TR/electron.json +32 -0
  75. package/locales/tr-TR/models.json +126 -3
  76. package/locales/tr-TR/plugin.json +1 -0
  77. package/locales/tr-TR/tool.json +25 -0
  78. package/locales/vi-VN/common.json +2 -0
  79. package/locales/vi-VN/electron.json +32 -0
  80. package/locales/vi-VN/models.json +126 -3
  81. package/locales/vi-VN/plugin.json +1 -0
  82. package/locales/vi-VN/tool.json +25 -0
  83. package/locales/zh-CN/common.json +2 -0
  84. package/locales/zh-CN/electron.json +32 -0
  85. package/locales/zh-CN/models.json +131 -8
  86. package/locales/zh-CN/plugin.json +1 -0
  87. package/locales/zh-CN/tool.json +25 -0
  88. package/locales/zh-TW/common.json +2 -0
  89. package/locales/zh-TW/electron.json +32 -0
  90. package/locales/zh-TW/models.json +126 -3
  91. package/locales/zh-TW/plugin.json +1 -0
  92. package/locales/zh-TW/tool.json +25 -0
  93. package/package.json +3 -2
  94. package/packages/electron-client-ipc/src/events/index.ts +5 -5
  95. package/packages/electron-client-ipc/src/events/localFile.ts +22 -0
  96. package/packages/electron-client-ipc/src/events/{file.ts → upload.ts} +1 -1
  97. package/packages/electron-client-ipc/src/types/index.ts +2 -1
  98. package/packages/electron-client-ipc/src/types/localFile.ts +52 -0
  99. package/scripts/prebuild.mts +5 -1
  100. package/src/app/(backend)/trpc/desktop/[trpc]/route.ts +26 -0
  101. package/src/config/aiModels/cloudflare.ts +41 -37
  102. package/src/config/aiModels/github.ts +90 -0
  103. package/src/config/aiModels/google.ts +25 -0
  104. package/src/features/Conversation/Messages/Assistant/Tool/Render/Arguments/ObjectEntity.tsx +81 -0
  105. package/src/features/Conversation/Messages/Assistant/Tool/Render/Arguments/ValueCell.tsx +43 -0
  106. package/src/features/Conversation/Messages/Assistant/Tool/Render/Arguments/index.tsx +120 -0
  107. package/src/features/Conversation/Messages/Assistant/Tool/Render/CustomRender.tsx +75 -2
  108. package/src/features/Conversation/Messages/Assistant/Tool/Render/KeyValueEditor.tsx +214 -0
  109. package/src/features/User/UserPanel/useMenu.tsx +8 -1
  110. package/src/libs/agent-runtime/google/index.ts +3 -0
  111. package/src/libs/trpc/client/desktop.ts +14 -0
  112. package/src/locales/default/common.ts +2 -0
  113. package/src/locales/default/electron.ts +34 -0
  114. package/src/locales/default/index.ts +2 -0
  115. package/src/locales/default/tool.ts +25 -0
  116. package/src/server/routers/desktop/index.ts +9 -0
  117. package/src/server/routers/desktop/pgTable.ts +43 -0
  118. package/src/services/electron/autoUpdate.ts +17 -0
  119. package/src/services/electron/file.ts +31 -0
  120. package/src/services/electron/localFileService.ts +39 -0
  121. package/src/services/electron/remoteServer.ts +40 -0
  122. package/src/store/chat/index.ts +1 -1
  123. package/src/store/chat/slices/builtinTool/actions/index.ts +3 -1
  124. package/src/store/chat/slices/builtinTool/actions/localFile.ts +129 -0
  125. package/src/store/chat/slices/builtinTool/initialState.ts +2 -0
  126. package/src/store/chat/slices/builtinTool/selectors.ts +2 -0
  127. package/src/store/chat/slices/plugin/action.ts +3 -3
  128. package/src/store/chat/store.ts +2 -0
  129. package/src/store/electron/actions/sync.ts +117 -0
  130. package/src/store/electron/index.ts +1 -0
  131. package/src/store/electron/initialState.ts +18 -0
  132. package/src/store/electron/selectors/index.ts +1 -0
  133. package/src/store/electron/selectors/sync.ts +9 -0
  134. package/src/store/electron/store.ts +29 -0
  135. package/src/tools/index.ts +8 -0
  136. package/src/tools/local-files/Render/ListFiles/Result.tsx +42 -0
  137. package/src/tools/local-files/Render/ListFiles/index.tsx +68 -0
  138. package/src/tools/local-files/Render/ReadLocalFile/ReadFileSkeleton.tsx +50 -0
  139. package/src/tools/local-files/Render/ReadLocalFile/ReadFileView.tsx +197 -0
  140. package/src/tools/local-files/Render/ReadLocalFile/index.tsx +31 -0
  141. package/src/tools/local-files/Render/ReadLocalFile/style.ts +37 -0
  142. package/src/tools/local-files/Render/SearchFiles/Result.tsx +42 -0
  143. package/src/tools/local-files/Render/SearchFiles/SearchQuery/SearchView.tsx +77 -0
  144. package/src/tools/local-files/Render/SearchFiles/SearchQuery/index.tsx +72 -0
  145. package/src/tools/local-files/Render/SearchFiles/index.tsx +32 -0
  146. package/src/tools/local-files/Render/index.tsx +36 -0
  147. package/src/tools/local-files/components/FileItem.tsx +117 -0
  148. package/src/tools/local-files/index.ts +149 -0
  149. package/src/tools/local-files/systemRole.ts +46 -0
  150. package/src/tools/local-files/type.ts +33 -0
  151. package/src/tools/renders.ts +3 -0
  152. package/packages/electron-client-ipc/src/events/search.ts +0 -4
  153. package/src/features/Conversation/Messages/Assistant/Tool/Render/Arguments.tsx +0 -165
  154. /package/packages/electron-client-ipc/src/types/{file.ts → upload.ts} +0 -0
@@ -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;
@@ -0,0 +1,50 @@
1
+ import { Skeleton } from 'antd';
2
+ import { createStyles } from 'antd-style';
3
+ import React, { memo } from 'react';
4
+ import { Flexbox } from 'react-layout-kit';
5
+
6
+ const useStyles = createStyles(({ css, token }) => ({
7
+ container: css`
8
+ padding: 8px;
9
+ border: 1px solid ${token.colorBorderSecondary};
10
+ border-radius: ${token.borderRadiusLG}px;
11
+ `,
12
+ header: css`
13
+ margin-block-end: 4px;
14
+ `,
15
+ meta: css`
16
+ font-size: 12px;
17
+ `,
18
+ path: css`
19
+ margin-block-start: 4px;
20
+ `,
21
+ }));
22
+
23
+ const ReadFileSkeleton = memo(() => {
24
+ const { styles } = useStyles();
25
+
26
+ 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
+ >
35
+ <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 }} />
38
+ </Flexbox>
39
+ <Flexbox align={'center'} className={styles.meta} gap={16}>
40
+ <Skeleton.Input active size="small" style={{ maxWidth: 40 }} />
41
+ </Flexbox>
42
+ </Flexbox>
43
+
44
+ {/* Path */}
45
+ <Skeleton.Input active block className={styles.path} size="small" />
46
+ </Flexbox>
47
+ );
48
+ });
49
+
50
+ export default ReadFileSkeleton;