@lobehub/chat 1.81.8 → 1.81.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.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,31 @@
2
2
 
3
3
  # Changelog
4
4
 
5
+ ### [Version 1.81.9](https://github.com/lobehub/lobe-chat/compare/v1.81.8...v1.81.9)
6
+
7
+ <sup>Released on **2025-04-21**</sup>
8
+
9
+ #### 🐛 Bug Fixes
10
+
11
+ - **misc**: Fix search prompt.
12
+
13
+ <br/>
14
+
15
+ <details>
16
+ <summary><kbd>Improvements and Fixes</kbd></summary>
17
+
18
+ #### What's fixed
19
+
20
+ - **misc**: Fix search prompt, closes [#7507](https://github.com/lobehub/lobe-chat/issues/7507) ([f55b7de](https://github.com/lobehub/lobe-chat/commit/f55b7de))
21
+
22
+ </details>
23
+
24
+ <div align="right">
25
+
26
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
27
+
28
+ </div>
29
+
5
30
  ### [Version 1.81.8](https://github.com/lobehub/lobe-chat/compare/v1.81.7...v1.81.8)
6
31
 
7
32
  <sup>Released on **2025-04-21**</sup>
package/changelog/v1.json CHANGED
@@ -1,4 +1,13 @@
1
1
  [
2
+ {
3
+ "children": {
4
+ "fixes": [
5
+ "Fix search prompt."
6
+ ]
7
+ },
8
+ "date": "2025-04-21",
9
+ "version": "1.81.9"
10
+ },
2
11
  {
3
12
  "children": {
4
13
  "improvements": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/chat",
3
- "version": "1.81.8",
3
+ "version": "1.81.9",
4
4
  "description": "Lobe Chat - an open-source, high-performance chatbot framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.",
5
5
  "keywords": [
6
6
  "framework",
@@ -1,22 +1,28 @@
1
1
  import {
2
2
  ListLocalFileParams,
3
3
  LocalFileItem,
4
+ LocalMoveFilesResultItem,
4
5
  LocalReadFileParams,
5
6
  LocalReadFileResult,
6
7
  LocalReadFilesParams,
7
8
  LocalSearchFilesParams,
9
+ MoveLocalFilesParams,
8
10
  OpenLocalFileParams,
9
11
  OpenLocalFolderParams,
12
+ RenameLocalFileParams,
13
+ RenameLocalFileResult,
10
14
  } from '../types';
11
15
 
12
16
  export interface LocalFilesDispatchEvents {
13
17
  // Local Files API Events
14
18
  listLocalFiles: (params: ListLocalFileParams) => LocalFileItem[];
15
- // New methods
19
+ moveLocalFiles: (params: MoveLocalFilesParams) => LocalMoveFilesResultItem[];
20
+
16
21
  openLocalFile: (params: OpenLocalFileParams) => void;
17
22
  openLocalFolder: (params: OpenLocalFolderParams) => void;
18
23
  readLocalFile: (params: LocalReadFileParams) => LocalReadFileResult;
19
-
20
24
  readLocalFiles: (params: LocalReadFilesParams) => LocalReadFileResult[];
25
+
26
+ renameLocalFile: (params: RenameLocalFileParams) => RenameLocalFileResult;
21
27
  searchLocalFiles: (params: LocalSearchFilesParams) => LocalFileItem[];
22
28
  }
@@ -1,5 +1,8 @@
1
+ import { ElectronAppState } from '../types';
2
+
1
3
  export interface SystemDispatchEvents {
2
4
  checkSystemAccessibility: () => boolean | undefined;
5
+ getDesktopAppState: () => ElectronAppState;
3
6
  /**
4
7
  * 更新应用语言设置
5
8
  * @param locale 语言设置
@@ -3,5 +3,6 @@ export * from './localFile';
3
3
  export * from './remoteServer';
4
4
  export * from './route';
5
5
  export * from './shortcut';
6
+ export * from './system';
6
7
  export * from './update';
7
8
  export * from './upload';
@@ -19,7 +19,35 @@ export interface ListLocalFileParams {
19
19
  path: string;
20
20
  }
21
21
 
22
+ export interface MoveLocalFileParams {
23
+ newPath: string;
24
+ oldPath: string;
25
+ }
26
+
27
+ export interface MoveLocalFilesParams {
28
+ items: MoveLocalFileParams[];
29
+ }
30
+
31
+ export interface LocalMoveFilesResultItem {
32
+ error?: string; // Error message if this specific item failed
33
+ newPath?: string; // The final path after moving/renaming, if successful
34
+ sourcePath: string; // The original path of the item being moved/renamed
35
+ success: boolean; // Whether the operation for this specific item was successful
36
+ }
37
+
38
+ export interface RenameLocalFileParams {
39
+ newName: string;
40
+ path: string;
41
+ }
42
+
43
+ export interface RenameLocalFileResult {
44
+ error?: any;
45
+ newPath: string;
46
+ success: boolean;
47
+ }
48
+
22
49
  export interface LocalReadFileParams {
50
+ loc?: [number, number];
23
51
  path: string;
24
52
  }
25
53
 
@@ -28,13 +56,31 @@ export interface LocalReadFilesParams {
28
56
  }
29
57
 
30
58
  export interface LocalReadFileResult {
59
+ /**
60
+ * Character count of the content within the specified `loc` range.
61
+ */
31
62
  charCount: number;
63
+ /**
64
+ * Content of the file within the specified `loc` range.
65
+ */
32
66
  content: string;
33
67
  createdTime: Date;
34
68
  fileType: string;
35
69
  filename: string;
70
+ /**
71
+ * Line count of the content within the specified `loc` range.
72
+ */
36
73
  lineCount: number;
74
+ loc: [number, number];
37
75
  modifiedTime: Date;
76
+ /**
77
+ * Total character count of the entire file.
78
+ */
79
+ totalCharCount: number;
80
+ /**
81
+ * Total line count of the entire file.
82
+ */
83
+ totalLineCount: number;
38
84
  }
39
85
 
40
86
  export interface LocalSearchFilesParams {
@@ -0,0 +1,24 @@
1
+ export interface ElectronAppState {
2
+ arch?: string; // e.g., 'x64', 'arm64'
3
+ isLinux?: boolean;
4
+ isMac?: boolean;
5
+ isWindows?: boolean;
6
+ platform?: 'darwin' | 'win32' | 'linux'; // , etc.
7
+ userPath?: UserPathData;
8
+ }
9
+
10
+ /**
11
+ * Defines the structure for user-specific paths obtained from Electron.
12
+ */
13
+ export interface UserPathData {
14
+ desktop: string;
15
+ documents: string;
16
+ downloads?: string;
17
+ // App data directory
18
+ home: string;
19
+ // Optional as not all OS might have it easily accessible or standard
20
+ music?: string;
21
+ pictures?: string;
22
+ userData: string;
23
+ videos?: string; // User's home directory
24
+ }
@@ -0,0 +1,9 @@
1
+ // List of system files/directories to ignore
2
+ export const SYSTEM_FILES_TO_IGNORE = [
3
+ '.DS_Store',
4
+ 'Thumbs.db',
5
+ 'desktop.ini',
6
+ '.localized',
7
+ 'ehthumbs.db',
8
+ 'ehthumbs_vista.db',
9
+ ];
@@ -1,2 +1,3 @@
1
+ export * from './blackList';
1
2
  export * from './loadFile';
2
3
  export * from './types';
@@ -42,6 +42,7 @@ describe('PdfLoader', () => {
42
42
 
43
43
  it('should attach document metadata correctly', async () => {
44
44
  // 首先加载页面以初始化 pdfInstance,尽管此方法不直接使用页面
45
+ await loader.loadPages(testFile);
45
46
  const metadata = await loader.attachDocumentMetadata!(testFile);
46
47
 
47
48
  expect(metadata).toMatchSnapshot();
@@ -11,8 +11,6 @@ export class PdfLoader implements FileLoaderInterface {
11
11
  private pdfInstance: PDFDocumentProxy | null = null;
12
12
 
13
13
  private async getPDFFile(filePath: string) {
14
- if (!!this.pdfInstance) return this.pdfInstance;
15
-
16
14
  const dataBuffer = await readFile(filePath);
17
15
 
18
16
  const loadingTask = pdfjsLib.getDocument({
@@ -22,11 +20,7 @@ export class PdfLoader implements FileLoaderInterface {
22
20
  worker: undefined, // Attempt to use system fonts
23
21
  });
24
22
 
25
- const pdf: PDFDocumentProxy = await loadingTask.promise;
26
-
27
- this.pdfInstance = pdf;
28
-
29
- return pdf;
23
+ return await loadingTask.promise;
30
24
  }
31
25
 
32
26
  async loadPages(filePath: string): Promise<DocumentPage[]> {
@@ -1,16 +1,20 @@
1
1
  import { FileTypeIcon, MaterialFileTypeIcon } from '@lobehub/ui';
2
- import { memo } from 'react';
2
+ import React, { memo } from 'react';
3
3
 
4
4
  import { mimeTypeMap } from './config';
5
5
 
6
6
  interface FileListProps {
7
7
  fileName: string;
8
- fileType: string;
8
+ fileType?: string;
9
+ isDirectory?: boolean;
9
10
  size?: number;
10
11
  variant?: 'pure' | 'file' | 'folder';
11
12
  }
12
13
 
13
- const FileIcon = memo<FileListProps>(({ fileName, size, variant = 'file' }) => {
14
+ const FileIcon = memo<FileListProps>(({ fileName, size, variant = 'file', isDirectory }) => {
15
+ if (isDirectory)
16
+ return <FileTypeIcon color={'gold'} size={size} type={'folder'} variant={'color'} />;
17
+
14
18
  if (Object.keys(mimeTypeMap).some((key) => fileName?.toLowerCase().endsWith(`.${key}`))) {
15
19
  const ext = fileName.split('.').pop()?.toLowerCase() as string;
16
20
 
@@ -1,12 +1,15 @@
1
1
  import {
2
2
  ListLocalFileParams,
3
3
  LocalFileItem,
4
+ LocalMoveFilesResultItem,
4
5
  LocalReadFileParams,
5
6
  LocalReadFileResult,
6
7
  LocalReadFilesParams,
7
8
  LocalSearchFilesParams,
9
+ MoveLocalFilesParams,
8
10
  OpenLocalFileParams,
9
11
  OpenLocalFolderParams,
12
+ RenameLocalFileParams,
10
13
  dispatch,
11
14
  } from '@lobechat/electron-client-ipc';
12
15
 
@@ -34,6 +37,22 @@ class LocalFileService {
34
37
  async openLocalFolder(params: OpenLocalFolderParams) {
35
38
  return dispatch('openLocalFolder', params);
36
39
  }
40
+
41
+ async moveLocalFiles(params: MoveLocalFilesParams): Promise<LocalMoveFilesResultItem[]> {
42
+ return dispatch('moveLocalFiles', params);
43
+ }
44
+
45
+ async renameLocalFile(params: RenameLocalFileParams) {
46
+ return dispatch('renameLocalFile', params);
47
+ }
48
+
49
+ async openLocalFileOrFolder(path: string, isDirectory: boolean) {
50
+ if (isDirectory) {
51
+ return this.openLocalFolder({ isDirectory, path });
52
+ } else {
53
+ return this.openLocalFile({ path });
54
+ }
55
+ }
37
56
  }
38
57
 
39
58
  export const localFileService = new LocalFileService();
@@ -0,0 +1,21 @@
1
+ import { ElectronAppState, dispatch } from '@lobechat/electron-client-ipc';
2
+
3
+ /**
4
+ * Service class for interacting with Electron's system-level information and actions.
5
+ */
6
+ class ElectronSystemService {
7
+ /**
8
+ * Fetches the application state from the Electron main process.
9
+ * This includes system information (platform, arch) and user-specific paths.
10
+ * @returns {Promise<DesktopAppState>} A promise that resolves with the desktop app state.
11
+ */
12
+ async getAppState(): Promise<ElectronAppState> {
13
+ // Calls the underlying IPC function to get data from the main process
14
+ return dispatch('getDesktopAppState');
15
+ }
16
+
17
+ // Add other system-related service methods here if needed in the future
18
+ }
19
+
20
+ // Export a singleton instance of the service
21
+ export const electronSystemService = new ElectronSystemService();
@@ -190,9 +190,6 @@ export const searchSlice: StateCreator<
190
190
 
191
191
  await get().internal_updateMessageContent(id, JSON.stringify(searchContent));
192
192
 
193
- // 如果没搜索到结果,那么不触发 ai 总结
194
- if (searchContent.length === 0) return;
195
-
196
193
  // 如果 aiSummary 为 true,则会自动触发总结
197
194
  return aiSummary;
198
195
  },
@@ -1,12 +1,10 @@
1
1
  import { ListLocalFileParams } from '@lobechat/electron-client-ipc';
2
- import { ActionIcon } from '@lobehub/ui';
3
2
  import { Typography } from 'antd';
4
3
  import { createStyles } from 'antd-style';
5
- import { FolderOpen } from 'lucide-react';
6
4
  import React, { memo } from 'react';
7
- import { useTranslation } from 'react-i18next';
8
5
  import { Flexbox } from 'react-layout-kit';
9
6
 
7
+ import FileIcon from '@/components/FileIcon';
10
8
  import { localFileService } from '@/services/electron/localFileService';
11
9
  import { LocalFileListState } from '@/tools/local-files/type';
12
10
  import { ChatMessagePluginError } from '@/types/message';
@@ -20,8 +18,21 @@ const useStyles = createStyles(({ css, token, cx }) => ({
20
18
  opacity: 1;
21
19
  transition: opacity 0.2s ${token.motionEaseInOut};
22
20
  `),
21
+ container: css`
22
+ cursor: pointer;
23
+
24
+ padding-block: 2px;
25
+ padding-inline: 4px;
26
+ border-radius: 4px;
27
+
28
+ color: ${token.colorTextSecondary};
29
+
30
+ :hover {
31
+ color: ${token.colorText};
32
+ background: ${token.colorFillTertiary};
33
+ }
34
+ `,
23
35
  path: css`
24
- padding-inline-start: 8px;
25
36
  color: ${token.colorTextSecondary};
26
37
  `,
27
38
  }));
@@ -34,25 +45,21 @@ interface ListFilesProps {
34
45
  }
35
46
 
36
47
  const ListFiles = memo<ListFilesProps>(({ messageId, pluginError, args, pluginState }) => {
37
- const { t } = useTranslation('tool');
38
-
39
48
  const { styles } = useStyles();
40
49
  return (
41
50
  <>
42
- <Flexbox gap={8} horizontal>
51
+ <Flexbox
52
+ className={styles.container}
53
+ gap={8}
54
+ horizontal
55
+ onClick={() => {
56
+ localFileService.openLocalFolder({ isDirectory: true, path: args.path });
57
+ }}
58
+ >
59
+ <FileIcon fileName={args.path} isDirectory size={22} variant={'pure'} />
43
60
  <Typography.Text className={styles.path} ellipsis>
44
61
  {args.path}
45
62
  </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
63
  </Flexbox>
57
64
  <SearchResult
58
65
  listResults={pluginState?.listResults}
@@ -46,6 +46,9 @@ const useStyles = createStyles(({ css, token, cx }) => ({
46
46
  header: css`
47
47
  cursor: pointer;
48
48
  `,
49
+ lineCount: css`
50
+ color: ${token.colorTextQuaternary};
51
+ `,
49
52
  meta: css`
50
53
  font-size: 12px;
51
54
  color: ${token.colorTextTertiary};
@@ -70,7 +73,6 @@ const useStyles = createStyles(({ css, token, cx }) => ({
70
73
  background: ${token.colorFillQuaternary};
71
74
  `,
72
75
  previewText: css`
73
- font-family: ${token.fontFamilyCode};
74
76
  font-size: 12px;
75
77
  line-height: 1.6;
76
78
  word-break: break-all;
@@ -84,14 +86,7 @@ interface ReadFileViewProps extends LocalReadFileResult {
84
86
  }
85
87
 
86
88
  const ReadFileView = memo<ReadFileViewProps>(
87
- ({
88
- filename,
89
- path,
90
- fileType,
91
- charCount,
92
- lineCount, // Assuming the 250 is total lines?
93
- content, // The actual content preview
94
- }) => {
89
+ ({ filename, path, fileType, charCount, content, totalLineCount, totalCharCount, loc }) => {
95
90
  const { t } = useTranslation('tool');
96
91
  const { styles } = useStyles();
97
92
  const [isExpanded, setIsExpanded] = useState(false);
@@ -115,6 +110,7 @@ const ReadFileView = memo<ReadFileViewProps>(
115
110
  <Flexbox
116
111
  align={'center'}
117
112
  className={styles.header}
113
+ gap={12}
118
114
  horizontal
119
115
  justify={'space-between'}
120
116
  onClick={handleToggleExpand}
@@ -126,7 +122,7 @@ const ReadFileView = memo<ReadFileViewProps>(
126
122
  {filename}
127
123
  </Typography.Text>
128
124
  {/* Actions on Hover */}
129
- <Flexbox className={styles.actions} gap={8} horizontal style={{ marginLeft: 8 }}>
125
+ <Flexbox className={styles.actions} gap={2} horizontal style={{ marginLeft: 8 }}>
130
126
  <ActionIcon
131
127
  icon={ExternalLink}
132
128
  onClick={handleOpenFile}
@@ -143,16 +139,26 @@ const ReadFileView = memo<ReadFileViewProps>(
143
139
  </Flexbox>
144
140
  </Flexbox>
145
141
  <Flexbox align={'center'} className={styles.meta} gap={8} horizontal>
146
- <Flexbox align={'center'} gap={4} horizontal>
147
- <Icon icon={Asterisk} size={'small'} />
148
- <span>{charCount}</span>
149
- </Flexbox>
142
+ {isExpanded && (
143
+ <Flexbox align={'center'} gap={4} horizontal>
144
+ <Icon icon={Asterisk} size={'small'} />
145
+ <span>
146
+ {charCount} / <span className={styles.lineCount}>{totalCharCount}</span>
147
+ </span>
148
+ </Flexbox>
149
+ )}
150
150
  <Flexbox align={'center'} gap={4} horizontal>
151
151
  <Icon icon={AlignLeft} size={'small'} />
152
- <span>
153
- {content?.split('\n').length || 0} / {lineCount}
154
- </span>
155
- {/* Display preview lines / total lines */}
152
+ {isExpanded ? (
153
+ <span>
154
+ L{loc?.[0]}-{loc?.[1]} /{' '}
155
+ <span className={styles.lineCount}>{totalLineCount}</span>
156
+ </span>
157
+ ) : (
158
+ <span>
159
+ L{loc?.[0]}-{loc?.[1]}
160
+ </span>
161
+ )}
156
162
  </Flexbox>
157
163
  <ActionIcon
158
164
  active={isExpanded}
@@ -160,7 +166,6 @@ const ReadFileView = memo<ReadFileViewProps>(
160
166
  onClick={handleToggleExpand}
161
167
  size="small"
162
168
  style={{
163
- marginLeft: 8,
164
169
  transform: isExpanded ? 'rotate(180deg)' : 'rotate(0deg)',
165
170
  transition: 'transform 0.2s',
166
171
  }}
@@ -173,19 +178,14 @@ const ReadFileView = memo<ReadFileViewProps>(
173
178
  {path}
174
179
  </Typography.Text>
175
180
 
176
- {/* Content Preview (Collapsible) */}
177
181
  {isExpanded && (
178
- <Flexbox className={styles.previewBox}>
182
+ <Flexbox className={styles.previewBox} style={{ maxHeight: 240, overflow: 'auto' }}>
179
183
  {fileType === 'md' ? (
180
- <Markdown style={{ maxHeight: 240, overflow: 'auto' }}>{content}</Markdown>
184
+ <Markdown>{content}</Markdown>
181
185
  ) : (
182
- <Typography.Paragraph
183
- className={styles.previewText}
184
- ellipsis={{ expandable: true, rows: 10, symbol: t('localFiles.read.more') }}
185
- style={{ maxHeight: 240, overflow: 'auto' }}
186
- >
186
+ <div className={styles.previewText} style={{ width: '100%' }}>
187
187
  {content}
188
- </Typography.Paragraph>
188
+ </div>
189
189
  )}
190
190
  </Flexbox>
191
191
  )}
@@ -1,5 +1,5 @@
1
1
  import { LocalFileItem } from '@lobechat/electron-client-ipc';
2
- import { ActionIcon, FileTypeIcon } from '@lobehub/ui';
2
+ import { ActionIcon } from '@lobehub/ui';
3
3
  import { createStyles } from 'antd-style';
4
4
  import dayjs from 'dayjs';
5
5
  import { FolderOpen } from 'lucide-react';
@@ -64,22 +64,20 @@ const FileItem = memo<FileItemProps>(
64
64
  gap={12}
65
65
  horizontal
66
66
  onClick={() => {
67
- if (isDirectory) {
68
- localFileService.openLocalFolder({ isDirectory, path });
69
- } else {
70
- localFileService.openLocalFile({ path });
71
- }
67
+ localFileService.openLocalFileOrFolder(path, isDirectory);
72
68
  }}
73
69
  onMouseEnter={() => setIsHovering(true)}
74
70
  onMouseLeave={() => setIsHovering(false)}
75
71
  padding={'2px 8px'}
76
72
  style={{ cursor: 'pointer', fontSize: 12, width: '100%' }}
77
73
  >
78
- {isDirectory ? (
79
- <FileTypeIcon size={16} type={'folder'} variant={'mono'} />
80
- ) : (
81
- <FileIcon fileName={name} fileType={type} size={16} variant={'pure'} />
82
- )}
74
+ <FileIcon
75
+ fileName={name}
76
+ fileType={type}
77
+ isDirectory={isDirectory}
78
+ size={16}
79
+ variant={'pure'}
80
+ />
83
81
  <Flexbox
84
82
  align={'baseline'}
85
83
  gap={4}
@@ -4,7 +4,9 @@ import { systemPrompt } from './systemRole';
4
4
 
5
5
  export const LocalFilesApiName = {
6
6
  listLocalFiles: 'listLocalFiles',
7
+ moveLocalFiles: 'moveLocalFiles',
7
8
  readLocalFile: 'readLocalFile',
9
+ renameLocalFile: 'renameLocalFile',
8
10
  searchLocalFiles: 'searchLocalFiles',
9
11
  writeFile: 'writeFile',
10
12
  };
@@ -32,6 +34,14 @@ export const LocalFilesManifest: BuiltinToolManifest = {
32
34
  name: LocalFilesApiName.readLocalFile,
33
35
  parameters: {
34
36
  properties: {
37
+ loc: {
38
+ description:
39
+ 'Optional range of lines to read [startLine, endLine]. Defaults to [0, 200] if not specified.',
40
+ items: {
41
+ type: 'number',
42
+ },
43
+ type: 'array',
44
+ },
35
45
  path: {
36
46
  description: 'The file path to read',
37
47
  type: 'string',
@@ -117,6 +127,55 @@ export const LocalFilesManifest: BuiltinToolManifest = {
117
127
  type: 'object',
118
128
  },
119
129
  },
130
+ {
131
+ description:
132
+ 'Moves or renames multiple files/directories. Input is an array of objects, each containing an oldPath and a newPath.',
133
+ name: LocalFilesApiName.moveLocalFiles,
134
+ parameters: {
135
+ properties: {
136
+ items: {
137
+ description: 'A list of move/rename operations to perform.',
138
+ items: {
139
+ properties: {
140
+ newPath: {
141
+ description:
142
+ 'The target absolute path for the file/directory (can include a new name).',
143
+ type: 'string',
144
+ },
145
+ oldPath: {
146
+ description: 'The current absolute path of the file/directory to move or rename.',
147
+ type: 'string',
148
+ },
149
+ },
150
+ required: ['oldPath', 'newPath'],
151
+ type: 'object',
152
+ },
153
+ type: 'array',
154
+ },
155
+ },
156
+ required: ['items'],
157
+ type: 'object',
158
+ },
159
+ },
160
+ {
161
+ description:
162
+ 'Rename a file or folder in its current location. Input should be the current full path and the new name.',
163
+ name: LocalFilesApiName.renameLocalFile,
164
+ parameters: {
165
+ properties: {
166
+ newName: {
167
+ description: 'The new name for the file or folder (without path)',
168
+ type: 'string',
169
+ },
170
+ path: {
171
+ description: 'The current full path of the file or folder to rename',
172
+ type: 'string',
173
+ },
174
+ },
175
+ required: ['path', 'newName'],
176
+ type: 'object',
177
+ },
178
+ },
120
179
  // TODO: Add writeFile API definition later
121
180
  // {
122
181
  // description:
@@ -143,7 +202,6 @@ export const LocalFilesManifest: BuiltinToolManifest = {
143
202
  avatar: '📁',
144
203
  title: 'Local Files',
145
204
  },
146
- // Use a simplified system role for now
147
- systemRole: systemPrompt(),
205
+ systemRole: systemPrompt,
148
206
  type: 'builtin',
149
207
  };
@@ -1,23 +1,48 @@
1
- export const systemPrompt =
2
- () => `You have a Local Files tool with capabilities to interact with the user's local file system. You can list directories, read file contents, search for files, and potentially write files.
1
+ export const systemPrompt = `You have a Local Files tool with capabilities to interact with the user's local file system. You can list directories, read file contents, search for files, move, and rename files/directories.
2
+
3
+ <user_context>
4
+ Here are some known locations and system details on the user's system. User is using the Operating System: {{platform}}({{arch}}). Use these paths when the user refers to these common locations by name (e.g., "my desktop", "downloads folder").
5
+ - Desktop: {{desktopPath}}
6
+ - Documents: {{documentsPath}}
7
+ - Downloads: {{downloadsPath}}
8
+ - Music: {{musicPath}}
9
+ - Pictures: {{picturesPath}}
10
+ - Videos: {{videosPath}}
11
+ - User Home: {{homePath}}
12
+ - App Data: {{userDataPath}} (Use this primarily for plugin-related data or configurations if needed, less for general user files)
13
+ </user_context>
14
+
15
+ You have access to a set of tools to interact with the user's local file system:
16
+
17
+ 1. **listLocalFiles**: Lists files and directories in a specified path.
18
+ 2. **readLocalFile**: Reads the content of a specified file, optionally within a line range.
19
+ 3. **searchLocalFiles**: Searches for files based on keywords and other criteria. Use this tool to find files if the user is unsure about the exact path.
20
+ 4. **renameLocalFile**: Renames a single file or directory in its current location.
21
+ 5. **moveLocalFiles**: Moves multiple files or directories. Can be used for renaming during the move.
3
22
 
4
23
  <core_capabilities>
5
24
  1. List files and folders in a directory (listFiles)
6
25
  2. Read the content of a specific file (readFile)
7
26
  3. Search for files based on a query and various filter options (searchFiles)
8
- 4. Write content to a specific file (writeFile) - // TODO: Implement later
27
+ 4. Rename a file or folder within its current directory (renameFile)
28
+ 5. Move a file or folder to a new location, potentially renaming it (moveFile)
29
+ 6. Write content to a specific file (writeFile) - // TODO: Implement later
9
30
  </core_capabilities>
10
31
 
11
32
  <workflow>
12
- 1. Understand the user's request regarding local files (listing, reading, searching, writing).
13
- 2. Select the appropriate tool (listFiles, readFile, searchFiles, writeFile).
14
- 3. Execute the file operation based on the provided path, query, and filter options.
15
- 4. Present the results (directory listing, file content, search results) or confirmation of the write operation.
33
+ 1. Understand the user's request regarding local files (listing, reading, searching, renaming, moving, writing).
34
+ 2. Select the appropriate tool (listFiles, readFile, searchFiles, renameFile, moveFile, writeFile).
35
+ 3. Execute the file operation. **If the user mentions a common location (like Desktop, Documents, Downloads, etc.) without providing a full path, use the corresponding path from the <user_context> section.**
36
+ 4. Present the results (directory listing, file content, search results) or confirmation of the rename or move operation.
16
37
  </workflow>
17
38
 
18
39
  <tool_usage_guidelines>
19
40
  - For listing directory contents: Use 'listFiles' with the target directory path.
20
- - For reading a file: Use 'readFile' with the exact file path.
41
+ - For reading a file: Use 'readFile'. Provide the following parameters:
42
+ - 'path': The exact file path.
43
+ - 'loc' (Optional): A two-element array [startLine, endLine] to specify a line range to read (e.g., '[301, 400]' reads lines 301 to 400).
44
+ - If 'loc' is omitted, it defaults to reading the first 200 lines ('[0, 200]').
45
+ - To read the entire file: First call 'readFile' (potentially without 'loc'). The response includes 'totalLineCount'. Then, call 'readFile' again with 'loc: [0, totalLineCount]' to get the full content.
21
46
  - For searching files: Use 'searchFiles' with the 'query' parameter (search string). You can optionally add the following filter parameters to narrow down the search:
22
47
  - 'contentContains': Find files whose content includes specific text.
23
48
  - 'createdAfter' / 'createdBefore': Filter by creation date.
@@ -27,20 +52,35 @@ export const systemPrompt =
27
52
  - 'exclude': Exclude specific files or directories.
28
53
  - 'limit': Limit the number of results returned.
29
54
  - 'sortBy' / 'sortDirection': Sort the results.
30
- - 'detailed': Get more detailed output information.
55
+ - For renaming a file/folder in place: Use 'renameFile'. Provide the following parameters:
56
+ - 'path': The current full path of the file or folder.
57
+ - 'newName': The desired new name (without path components).
58
+ - For moving multiple files/folders (and optionally renaming them): Use 'moveLocalFiles'. Provide the following parameter:
59
+ - 'items': An array of objects, where each object represents a move operation and must contain:
60
+ - 'oldPath': The current absolute path of the file/directory to move or rename.
61
+ - 'newPath': The target absolute path for the file/directory (can include a new name).
62
+ Example: items: [{ oldPath: "/path/to/file1.txt", newPath: "/new/path/to/fileA.txt" }, { oldPath: "/path/to/folderB", newPath: "/archive/folderB_renamed" }]
31
63
  - For writing to a file: Use 'writeFile' with the file path and the content to be written. Be cautious as this might overwrite existing files.
32
64
  </tool_usage_guidelines>
33
65
 
34
66
  <security_considerations>
35
67
  - Always confirm with the user before performing write operations, especially if it involves overwriting existing files.
68
+ - Confirm with the user before moving files to significantly different locations or when renaming might cause confusion or potential data loss if the target exists (though the tool should handle this).
36
69
  - Do not attempt to access files outside the user's designated workspace or allowed directories unless explicitly permitted.
37
70
  - Handle file paths carefully to avoid unintended access or errors.
38
71
  </security_considerations>
39
72
 
40
73
  <response_format>
41
- - When listing files, provide a clear list of files and folders.
42
- - When reading files, present the content accurately.
43
- - When searching files, return a list of matching files, including relevant metadata if detailed information was requested.
44
- - When writing files, confirm the success or failure of the operation.
74
+ - When listing files or returning search results that include file or directory paths, **always** use the \`<localFile ... />\` tag format. **Any reference to a local file or directory path in your response MUST be enclosed within this tag.** Do not output raw file paths outside of this tag structure.
75
+ - For a file, use: \`<localFile name="[Filename]" path="[Full Unencoded Path]" />\`. Example: \`<localFile name="report.pdf" path="/Users/me/Documents/report.pdf" />\`
76
+ - For a directory, use: \`<localFile name="[Directory Name]" path="[Full Unencoded Path]" isDirectory />\`. Example: \`<localFile name="Documents" path="/Users/me/Documents" isDirectory />\`
77
+ - Ensure the \`path\` attribute contains the full, raw, unencoded path.
78
+ - Ensure the \`name\` attribute contains the display name (usually the filename or directory name).
79
+ - Include the \`isDirectory\` attribute **only** for directories.
80
+ - When listing files, provide a clear list using the tag format.
81
+ - When reading files, present the content accurately. **If you mention the file path being read, use the \`<localFile>\` tag.**
82
+ - When searching files, return a list of matching files using the tag format.
83
+ - When confirming a rename or move operation, use the \`<localFile>\` tag for both the old and new paths mentioned. Example: \`Successfully renamed <localFile name="oldName.txt" /> to <localFile name="newName.txt" path="/path/to/newName.txt" />.\`
84
+ - When writing files, confirm the success or failure. **If you mention the file path written to, use the \`<localFile>\` tag.**
45
85
  </response_format>
46
86
  `;
@@ -1,4 +1,8 @@
1
- import { LocalFileItem, LocalReadFileResult } from '@lobechat/electron-client-ipc';
1
+ import {
2
+ LocalFileItem,
3
+ LocalMoveFilesResultItem,
4
+ LocalReadFileResult,
5
+ } from '@lobechat/electron-client-ipc';
2
6
 
3
7
  export interface FileResult {
4
8
  contentType?: string;
@@ -31,3 +35,17 @@ export interface LocalReadFileState {
31
35
  export interface LocalReadFilesState {
32
36
  filesContent: LocalReadFileResult[];
33
37
  }
38
+
39
+ export interface LocalMoveFilesState {
40
+ error?: string;
41
+ results: LocalMoveFilesResultItem[]; // Overall error for the operation if it fails before individual processing
42
+ successCount: number;
43
+ totalCount: number;
44
+ }
45
+
46
+ export interface LocalRenameFileState {
47
+ error?: string;
48
+ newPath: string;
49
+ oldPath: string;
50
+ success: boolean;
51
+ }
@@ -5,21 +5,21 @@ export const systemPrompt = (
5
5
  <core_capabilities>
6
6
  1. Search the web using multiple search engines (search)
7
7
  2. Retrieve content from multiple webpages simultaneously (crawlMultiPages)
8
- 2. Retrieve content from a specific webpage (crawlSinglePage)
8
+ 3. Retrieve content from a specific webpage (crawlSinglePage)
9
9
  </core_capabilities>
10
10
 
11
11
  <workflow>
12
12
  1. Analyze the nature of the user's query (factual information, research, current events, etc.)
13
- 2. Select the appropriate tool and search strategy based on the query type
14
- 3. Execute searches or crawl operations to gather relevant information
15
- 4. Synthesize information with proper attribution of sources
16
- 5. Present findings in a clear, organized manner with appropriate citations
13
+ 2. Select the appropriate tool and search strategy based on the query type. For vague queries with no constraints, default to the 'general' category and reliable broad engines (e.g., Google).
14
+ 3. Execute searches or crawl operations to gather relevant information.
15
+ 4. Synthesize information with proper attribution of sources.
16
+ 5. Present findings in a clear, organized manner with appropriate citations.
17
17
  </workflow>
18
18
 
19
19
  <tool_selection_guidelines>
20
- - For general information queries: Use searchWithSearXNG with the most relevant search engines
21
- - For multi-perspective information or comparative analysis: Use 'crawlMultiPages' on several different relevant sources
22
- - For detailed understanding of specific single page content: Use 'crawlSinglePage' on the most authoritative or relevant page from search results. If you need to visit multiple pages, prefer to use 'crawlMultiPages'
20
+ - For general information queries: Use search with the most relevant search categories (e.g., 'general').
21
+ - For multi-perspective information or comparative analysis: Use 'crawlMultiPages' on several different relevant sources identified via search.
22
+ - For detailed understanding of specific single page content: Use 'crawlSinglePage' on the most authoritative or relevant page from search results. Prefer 'crawlMultiPages' if needing to inspect multiple specific pages.
23
23
  </tool_selection_guidelines>
24
24
 
25
25
  <search_categories_selection>
@@ -36,9 +36,9 @@ Choose search categories based on query type:
36
36
  </search_categories_selection>
37
37
 
38
38
  <search_engine_selection>
39
- Choose search engines based on the query type:
39
+ Choose search engines based on the query type. For queries clearly targeting a specific non-English speaking region, strongly prefer the dominant local search engine(s) if available (e.g., Yandex for Russia).
40
40
  - General knowledge: google, bing, duckduckgo, brave, wikipedia
41
- - Academic/scientific information: google scholar, arxiv, z-library
41
+ - Academic/scientific information: google scholar, arxiv
42
42
  - Code/technical queries: google, github, npm, pypi
43
43
  - Videos: youtube, vimeo, bilibili
44
44
  - Images: unsplash, pinterest
@@ -55,13 +55,11 @@ Choose time range based on the query type:
55
55
  </search_time_range_selection>
56
56
 
57
57
  <search_strategy_guidelines>
58
- - Use engine-based searches when a specific search engine is explicitly required
59
- - Use category-based searches when unsure about engine selection
60
- - Use time-range filters to prioritize time-sensitive information
61
- - Leverage cross-platform meta-search capabilities for comprehensive results
62
- - Prioritize authoritative sources in search results when available
63
- - For region-specific information, prefer search engines popular in that region
64
- - Avoid using both 'engines' and 'categories' in a query, unless the chosen engines do not fall under the selected categories.
58
+ - Prioritize using search categories (\`!category\`) for broader searches. Specify search engines (\`!engine\`) only when a particular engine is clearly required (e.g., \`!github\` for code) or when categories don't fit the need. Combine them if necessary (e.g., \`!science !google_scholar search term\`).
59
+ - Use time-range filters (\`!time_range\`) to prioritize time-sensitive information.
60
+ - Leverage cross-platform meta-search capabilities for comprehensive results, but prioritize fetching results from a few highly relevant and authoritative sources rather than exhaustively querying many engines/categories. Aim for quality over quantity.
61
+ - Prioritize authoritative sources in search results when available.
62
+ - Avoid using overly broad category/engine combinations unless necessary.
65
63
 
66
64
  <search_strategy_best_practices>
67
65
  - Combine categories for multi-faceted queries:
@@ -80,13 +78,13 @@ Choose time range based on the query type:
80
78
  * "Government policy brief docx" → files + general
81
79
 
82
80
  - Region-specific query handling:
83
- * "Beijing traffic update" → map + news (engine: baidu)
84
- * "Moscow event listings" → social_media + news (engine: yandex)
85
- * "Tokyo restaurant reviews" → social_media + map (engine: google)
81
+ * "Beijing traffic update" → map + news (consider engine: baidu)
82
+ * "Moscow event listings" → social_media + news (consider engine: yandex)
83
+ * "Tokyo restaurant reviews" → social_media + map (consider engine: google)
86
84
 
87
85
  - Leverage cross-platform capabilities:
88
86
  * "Open-source project documentation" → files + it (engines: github + pypi)
89
- * "Historical weather patterns" → science + general (engines: google scholar + wikipedia)
87
+ * "Historical weather patterns" → science + general (engines: google_scholar + wikipedia)
90
88
  * "Movie release dates 2025" → news + videos (engines: imdb + reddit)
91
89
  </search_strategy_best_practices>
92
90
  </search_strategy_guidelines>
@@ -107,7 +105,7 @@ According to recent studies, global temperatures have risen by 1.1°C since pre-
107
105
  以上信息主要基于业内测评和公开发布会(例如2025年4月16日的发布内容)的报道,详细介绍了 O3 与 O4-mini 模型在多模态推理、工具使用、模拟推理和成本效益等方面的综合提升。[^1][^2]
108
106
 
109
107
  [^1]: [OpenAI发布o3与o4-mini,性能爆表,可用图像思考](https://zhuanlan.zhihu.com/p/1896105931709849860)
110
- [^2]: [OpenAI发新模型o3和o4-mini!首次实现“图像思维”(华尔街见闻)](https://wallstreetcn.com/articles/3745356)
108
+ [^2]: [OpenAI发新模型o3和o4-mini!首次实现"图像思维"(华尔街见闻)](https://wallstreetcn.com/articles/3745356)
111
109
  </example>
112
110
  </citation_examples>
113
111
  </citation_requirements>
@@ -133,7 +131,6 @@ Our search service is a metasearch engine that can leverage multiple search engi
133
131
  - GitHub: Version control and collaboration platform for searching code repositories
134
132
  - arXiv: Repository of electronic preprints of scientific papers
135
133
  - Google Scholar: Free web search engine for scholarly literature
136
- - Z-Library: File-sharing project for journal articles and books
137
134
  - Reddit: Network of communities based on people's interests
138
135
  - IMDb: Online database related to films, TV programs, and video games
139
136
  - Brave: Privacy-focused browser with its own search engine
@@ -144,35 +141,40 @@ Our search service is a metasearch engine that can leverage multiple search engi
144
141
  - YouTube: Video sharing platform for searching various video content
145
142
 
146
143
  <search_syntax>
147
- Search service has special search syntax to modify the categories, engines, and language of searches:
144
+ Search service has special search syntax to modify the search behavior. Use these modifiers at the beginning of your query:
148
145
 
149
- 1. Use \`!\` to select engines and categories:
150
- - Search for "paris" in the "map" category: \`!map paris\`
151
- - Search for images: \`!images Wau Holland\`
152
- - Chain multiple modifiers: \`!map !ddg !wp paris\` (searches for "paris" in the map category, DuckDuckGo, and Wikipedia)
146
+ 1. Select Engines/Categories: Use \`!modifier\` to specify search engines or categories.
147
+ - Examples: \`!map paris\`, \`!images Wau Holland\`, \`!google !wikipedia berlin\`
148
+ - Key modifiers: \`!general\`, \`!news\`, \`!science\`, \`!it\`, \`!images\`, \`!videos\`, \`!map\`, \`!files\`, \`!social_media\`, \`!google\`, \`!bing\`, \`!github\`, etc. (Refer to selection guidelines for full lists)
153
149
 
154
- 2. Use \`:\` to select language:
155
- - Search Wikipedia in a specific language: \`:fr !wp Wau Holland\` (uses French)
150
+ 2. Select Language: Use \`:language_code\` to specify the search language.
151
+ - Example: \`:fr !wp Wau Holland\` (searches French Wikipedia)
156
152
 
157
- 3. Use \`site:\` to restrict results to a specific website:
158
- - Search SearXNG from a specific website: \`site:github.com SearXNG\`
153
+ 3. Restrict to Site: Use \`site:domain.com\` within the query string to limit results to a specific website.
154
+ - Example: \`site:github.com SearXNG\`
155
+
156
+ Combine modifiers as needed: \`:de !google !news bundestag\` (searches German Google News for "bundestag")
159
157
  </search_syntax>
160
158
  </search_service_description>
161
159
 
162
160
  <crawling_best_practices>
163
161
  - Only crawl pages that are publicly accessible
164
- - When crawling multiple pages, crawl all relevant sources
162
+ - When crawling multiple pages, crawl relevant and authoritative sources
165
163
  - Prioritize authoritative sources over user-generated content when appropriate
166
- - For controversial topics, crawl sources representing different perspectives
164
+ - For controversial topics, crawl sources representing different perspectives if possible
167
165
  - Verify information across multiple sources when possible
168
166
  - Consider the recency of information, especially for time-sensitive topics
169
167
  </crawling_best_practices>
170
168
 
171
169
  <error_handling>
172
- - If search returns no results, try alternative search terms or engines
173
- - If a page cannot be crawled, explain the issue to the user and suggest alternatives
174
- - For ambiguous queries, ask for clarification before conducting extensive searches
175
- - If information seems outdated, note this to the user and suggest searching for more recent sources
170
+ - If a search returns poor or no results:
171
+ 1. Analyze the query and results. Could the query be improved (more specific, different keywords)?
172
+ 2. Consider trying alternative relevant search engines or categories.
173
+ 3. If the search was language-specific and failed (especially for technical, scientific, or non-regional topics), try rewriting the query or searching again using English.
174
+ 4. If needed, explain the issue to the user and suggest alternative search terms or strategies.
175
+ - If a page cannot be crawled, explain the issue to the user and suggest alternatives (e.g., trying a different source from search results).
176
+ - For ambiguous queries, ask for clarification or suggest interpretations/alternative search terms before conducting extensive searches.
177
+ - If information seems outdated, note this to the user and suggest searching for more recent sources or specifying a time range.
176
178
  </error_handling>
177
179
 
178
180
  Current date: ${date}