@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,197 @@
1
+ import { LocalReadFileResult } from '@lobechat/electron-client-ipc';
2
+ import { ActionIcon, Icon, Markdown } from '@lobehub/ui';
3
+ import { Typography } from 'antd';
4
+ import { createStyles } from 'antd-style';
5
+ import { AlignLeft, Asterisk, ChevronDownIcon, ExternalLink, FolderOpen } from 'lucide-react';
6
+ import React, { memo, useState } from 'react';
7
+ import { useTranslation } from 'react-i18next';
8
+ import { Flexbox } from 'react-layout-kit';
9
+
10
+ import FileIcon from '@/components/FileIcon';
11
+ import { localFileService } from '@/services/electron/localFileService';
12
+
13
+ const useStyles = createStyles(({ css, token, cx }) => ({
14
+ actions: cx(
15
+ 'local-file-actions',
16
+ css`
17
+ cursor: pointer;
18
+ color: ${token.colorTextTertiary};
19
+ opacity: 0;
20
+ transition: opacity 0.2s ${token.motionEaseInOut};
21
+ `,
22
+ ),
23
+ container: css`
24
+ padding: 8px;
25
+ border: 1px solid ${token.colorBorderSecondary};
26
+ border-radius: ${token.borderRadiusLG}px;
27
+ transition: all 0.2s ${token.motionEaseInOut};
28
+
29
+ &:hover {
30
+ border-color: ${token.colorBorder};
31
+
32
+ .local-file-actions {
33
+ opacity: 1;
34
+ }
35
+ }
36
+ `,
37
+ fileName: css`
38
+ flex: 1;
39
+ margin-inline-start: 8px;
40
+ color: ${token.colorTextSecondary};
41
+
42
+ &:hover {
43
+ color: ${token.colorText};
44
+ }
45
+ `,
46
+ header: css`
47
+ cursor: pointer;
48
+ `,
49
+ meta: css`
50
+ font-size: 12px;
51
+ color: ${token.colorTextTertiary};
52
+ `,
53
+ path: css`
54
+ margin-block-start: 4px;
55
+ padding-inline: 4px;
56
+
57
+ font-size: 12px;
58
+ color: ${token.colorTextSecondary};
59
+ word-break: break-all;
60
+ `,
61
+ previewBox: css`
62
+ position: relative;
63
+
64
+ overflow: hidden;
65
+
66
+ margin-block-start: 8px;
67
+ padding: 8px;
68
+ border-radius: 8px;
69
+
70
+ background: ${token.colorFillQuaternary};
71
+ `,
72
+ previewText: css`
73
+ font-family: ${token.fontFamilyCode};
74
+ font-size: 12px;
75
+ line-height: 1.6;
76
+ word-break: break-all;
77
+ white-space: pre-wrap;
78
+ `,
79
+ }));
80
+
81
+ // Assuming the result object might include the original path and an optional warning
82
+ interface ReadFileViewProps extends LocalReadFileResult {
83
+ path: string; // The full path requested
84
+ }
85
+
86
+ 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
+ }) => {
95
+ const { t } = useTranslation('tool');
96
+ const { styles } = useStyles();
97
+ const [isExpanded, setIsExpanded] = useState(false);
98
+
99
+ const handleToggleExpand = () => {
100
+ setIsExpanded(!isExpanded);
101
+ };
102
+
103
+ const handleOpenFile = (e: React.MouseEvent) => {
104
+ e.stopPropagation();
105
+ localFileService.openLocalFile({ path });
106
+ };
107
+
108
+ const handleOpenFolder = (e: React.MouseEvent) => {
109
+ e.stopPropagation();
110
+ localFileService.openLocalFolder({ isDirectory: false, path });
111
+ };
112
+
113
+ return (
114
+ <Flexbox className={styles.container}>
115
+ <Flexbox
116
+ align={'center'}
117
+ className={styles.header}
118
+ horizontal
119
+ justify={'space-between'}
120
+ onClick={handleToggleExpand}
121
+ >
122
+ <Flexbox align={'center'} flex={1} gap={0} horizontal style={{ overflow: 'hidden' }}>
123
+ <FileIcon fileName={filename} fileType={fileType} size={24} variant={'pure'} />
124
+ <Flexbox horizontal>
125
+ <Typography.Text className={styles.fileName} ellipsis>
126
+ {filename}
127
+ </Typography.Text>
128
+ {/* Actions on Hover */}
129
+ <Flexbox className={styles.actions} gap={8} horizontal style={{ marginLeft: 8 }}>
130
+ <ActionIcon
131
+ icon={ExternalLink}
132
+ onClick={handleOpenFile}
133
+ size="small"
134
+ title={t('localFiles.openFile')}
135
+ />
136
+ <ActionIcon
137
+ icon={FolderOpen}
138
+ onClick={handleOpenFolder}
139
+ size="small"
140
+ title={t('localFiles.openFolder')}
141
+ />
142
+ </Flexbox>
143
+ </Flexbox>
144
+ </Flexbox>
145
+ <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>
150
+ <Flexbox align={'center'} gap={4} horizontal>
151
+ <Icon icon={AlignLeft} size={'small'} />
152
+ <span>
153
+ {content?.split('\n').length || 0} / {lineCount}
154
+ </span>
155
+ {/* Display preview lines / total lines */}
156
+ </Flexbox>
157
+ <ActionIcon
158
+ active={isExpanded}
159
+ icon={ChevronDownIcon}
160
+ onClick={handleToggleExpand}
161
+ size="small"
162
+ style={{
163
+ marginLeft: 8,
164
+ transform: isExpanded ? 'rotate(180deg)' : 'rotate(0deg)',
165
+ transition: 'transform 0.2s',
166
+ }}
167
+ />
168
+ </Flexbox>
169
+ </Flexbox>
170
+
171
+ {/* Path */}
172
+ <Typography.Text className={styles.path} ellipsis type={'secondary'}>
173
+ {path}
174
+ </Typography.Text>
175
+
176
+ {/* Content Preview (Collapsible) */}
177
+ {isExpanded && (
178
+ <Flexbox className={styles.previewBox}>
179
+ {fileType === 'md' ? (
180
+ <Markdown style={{ maxHeight: 240, overflow: 'auto' }}>{content}</Markdown>
181
+ ) : (
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
+ >
187
+ {content}
188
+ </Typography.Paragraph>
189
+ )}
190
+ </Flexbox>
191
+ )}
192
+ </Flexbox>
193
+ );
194
+ },
195
+ );
196
+
197
+ export default ReadFileView;
@@ -0,0 +1,31 @@
1
+ import { LocalReadFileParams } from '@lobechat/electron-client-ipc';
2
+ import { memo } from 'react';
3
+
4
+ import { useChatStore } from '@/store/chat';
5
+ import { chatToolSelectors } from '@/store/chat/slices/builtinTool/selectors';
6
+ import { LocalReadFileState } from '@/tools/local-files/type';
7
+ import { ChatMessagePluginError } from '@/types/message';
8
+
9
+ import ReadFileSkeleton from './ReadFileSkeleton';
10
+ import ReadFileView from './ReadFileView';
11
+
12
+ interface ReadFileQueryProps {
13
+ args: LocalReadFileParams;
14
+ messageId: string;
15
+ pluginError: ChatMessagePluginError;
16
+ pluginState: LocalReadFileState;
17
+ }
18
+
19
+ const ReadFileQuery = memo<ReadFileQueryProps>(({ args, pluginState, messageId }) => {
20
+ const loading = useChatStore(chatToolSelectors.isSearchingLocalFiles(messageId));
21
+
22
+ if (loading) {
23
+ return <ReadFileSkeleton />;
24
+ }
25
+
26
+ if (!args?.path || !pluginState) return null;
27
+
28
+ return <ReadFileView {...pluginState.fileContent} path={args.path} />;
29
+ });
30
+
31
+ export default ReadFileQuery;
@@ -0,0 +1,37 @@
1
+ import { createStyles } from 'antd-style';
2
+
3
+ export const useStyles = createStyles(({ css, token }) => ({
4
+ container: css`
5
+ overflow: hidden;
6
+
7
+ max-width: 100%;
8
+ padding: 12px;
9
+ border: 1px solid ${token.colorBorderSecondary};
10
+ border-radius: ${token.borderRadius}px;
11
+ `,
12
+ fileName: css`
13
+ color: ${token.colorTextSecondary};
14
+ `,
15
+ meta: css`
16
+ font-size: 10px;
17
+ color: ${token.colorTextSecondary};
18
+ `,
19
+ metaItem: css`
20
+ white-space: nowrap;
21
+ `,
22
+ path: css`
23
+ font-size: 12px;
24
+ line-height: 1;
25
+ `,
26
+ previewBox: css`
27
+ padding-block: 8px;
28
+ padding-inline: 12px;
29
+ border-radius: ${token.borderRadiusSM}px;
30
+ background: ${token.colorFillTertiary};
31
+ `,
32
+ previewText: css`
33
+ font-family: ${token.fontFamilyCode};
34
+ font-size: 12px;
35
+ color: ${token.colorTextSecondary};
36
+ `,
37
+ }));
@@ -0,0 +1,42 @@
1
+ import { Skeleton } from 'antd';
2
+ import { memo } from 'react';
3
+ import { Flexbox } from 'react-layout-kit';
4
+
5
+ import { useChatStore } from '@/store/chat';
6
+ import { chatToolSelectors } from '@/store/chat/selectors';
7
+ import FileItem from '@/tools/local-files/components/FileItem';
8
+ import { FileResult } from '@/tools/local-files/type';
9
+ import { ChatMessagePluginError } from '@/types/message';
10
+
11
+ interface SearchFilesProps {
12
+ messageId: string;
13
+ pluginError: ChatMessagePluginError;
14
+ searchResults?: FileResult[];
15
+ }
16
+
17
+ const SearchFiles = memo<SearchFilesProps>(({ searchResults = [], 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
+ {searchResults.map((item) => (
34
+ <FileItem key={item.path} {...item} />
35
+ ))}
36
+ </Flexbox>
37
+ );
38
+ });
39
+
40
+ SearchFiles.displayName = 'SearchFiles';
41
+
42
+ export default SearchFiles;
@@ -0,0 +1,77 @@
1
+ import { Icon } from '@lobehub/ui';
2
+ import { Skeleton } from 'antd';
3
+ import { createStyles } from 'antd-style';
4
+ import { SearchIcon } from 'lucide-react';
5
+ import { memo } from 'react';
6
+ import { useTranslation } from 'react-i18next';
7
+ import { Flexbox } from 'react-layout-kit';
8
+
9
+ import { useIsMobile } from '@/hooks/useIsMobile';
10
+ import { shinyTextStylish } from '@/styles/loading';
11
+
12
+ const useStyles = createStyles(({ css, token }) => ({
13
+ font: css`
14
+ font-size: 12px;
15
+ color: ${token.colorTextTertiary};
16
+ `,
17
+ query: css`
18
+ cursor: pointer;
19
+
20
+ padding-block: 4px;
21
+ padding-inline: 8px;
22
+ border-radius: 8px;
23
+
24
+ font-size: 12px;
25
+ color: ${token.colorTextSecondary};
26
+
27
+ &:hover {
28
+ background: ${token.colorFillTertiary};
29
+ }
30
+ `,
31
+ shinyText: shinyTextStylish(token),
32
+ }));
33
+
34
+ interface SearchBarProps {
35
+ defaultQuery: string;
36
+ onEditingChange: (editing: boolean) => void;
37
+ resultsNumber: number;
38
+ searching?: boolean;
39
+ }
40
+
41
+ const SearchBar = memo<SearchBarProps>(
42
+ ({ defaultQuery, resultsNumber, onEditingChange, searching }) => {
43
+ const { t } = useTranslation('tool');
44
+ const isMobile = useIsMobile();
45
+ const { styles, cx } = useStyles();
46
+ return (
47
+ <Flexbox
48
+ align={isMobile ? 'flex-start' : 'center'}
49
+ distribution={'space-between'}
50
+ gap={isMobile ? 8 : 40}
51
+ height={isMobile ? undefined : 32}
52
+ horizontal={!isMobile}
53
+ >
54
+ <Flexbox
55
+ align={'center'}
56
+ className={cx(styles.query, searching && styles.shinyText)}
57
+ gap={8}
58
+ horizontal
59
+ onClick={() => {
60
+ onEditingChange(true);
61
+ }}
62
+ >
63
+ <Icon icon={SearchIcon} />
64
+ {defaultQuery}
65
+ </Flexbox>
66
+
67
+ <Flexbox align={'center'} horizontal>
68
+ <>
69
+ <div className={styles.font}>{t('search.searchResult')}</div>
70
+ {searching ? <Skeleton.Button active size={'small'} /> : resultsNumber}
71
+ </>
72
+ </Flexbox>
73
+ </Flexbox>
74
+ );
75
+ },
76
+ );
77
+ export default SearchBar;
@@ -0,0 +1,72 @@
1
+ import { LocalSearchFilesParams } from '@lobechat/electron-client-ipc';
2
+ import { ActionIcon, Icon } from '@lobehub/ui';
3
+ import { Button, Input, Space } from 'antd';
4
+ import { SearchIcon, XIcon } from 'lucide-react';
5
+ import { memo, useState } from 'react';
6
+ import { useTranslation } from 'react-i18next';
7
+ import { Flexbox } from 'react-layout-kit';
8
+
9
+ import { useChatStore } from '@/store/chat';
10
+ import { chatToolSelectors } from '@/store/chat/selectors';
11
+ import { LocalFileSearchState } from '@/tools/local-files/type';
12
+
13
+ import SearchView from './SearchView';
14
+
15
+ interface SearchQueryViewProps {
16
+ args: LocalSearchFilesParams;
17
+
18
+ messageId: string;
19
+ pluginState?: LocalFileSearchState;
20
+ }
21
+
22
+ const SearchQueryView = memo<SearchQueryViewProps>(({ messageId, args, pluginState }) => {
23
+ const { t } = useTranslation('tool');
24
+ const loading = useChatStore(chatToolSelectors.isSearchingLocalFiles(messageId));
25
+ const reSearchLocalFiles = useChatStore((s) => s.reSearchLocalFiles);
26
+ const searchResults = pluginState?.searchResults || [];
27
+
28
+ const [editing, setEditing] = useState(false);
29
+ const [query, setQuery] = useState(args.keywords);
30
+
31
+ const updateAndSearch = async () => {
32
+ const data: LocalSearchFilesParams = { keywords: query };
33
+
34
+ await reSearchLocalFiles(messageId, data);
35
+ };
36
+
37
+ return editing ? (
38
+ <Flexbox align={'center'} flex={1} gap={8} height={32} horizontal>
39
+ <Space.Compact style={{ width: '100%' }}>
40
+ <Input
41
+ autoFocus
42
+ onChange={(e) => {
43
+ setQuery(e.target.value);
44
+ }}
45
+ onPressEnter={updateAndSearch}
46
+ placeholder={t('search.searchBar.placeholder')}
47
+ style={{ minWidth: 400 }}
48
+ value={query}
49
+ variant={'filled'}
50
+ />
51
+ <Button
52
+ icon={<Icon icon={SearchIcon} />}
53
+ loading={loading}
54
+ onClick={updateAndSearch}
55
+ type={'primary'}
56
+ >
57
+ {t('search.searchBar.button')}
58
+ </Button>
59
+ </Space.Compact>
60
+ <ActionIcon icon={XIcon} onClick={() => setEditing(false)} />
61
+ </Flexbox>
62
+ ) : (
63
+ <SearchView
64
+ defaultQuery={args?.keywords}
65
+ onEditingChange={setEditing}
66
+ resultsNumber={searchResults.length}
67
+ searching={loading || !pluginState}
68
+ />
69
+ );
70
+ });
71
+
72
+ export default SearchQueryView;
@@ -0,0 +1,32 @@
1
+ import { LocalSearchFilesParams } from '@lobechat/electron-client-ipc';
2
+ import { memo } from 'react';
3
+
4
+ import { LocalFileSearchState } from '@/tools/local-files/type';
5
+ import { ChatMessagePluginError } from '@/types/message';
6
+
7
+ import SearchResult from './Result';
8
+ import SearchQuery from './SearchQuery';
9
+
10
+ interface SearchFilesProps {
11
+ args: LocalSearchFilesParams;
12
+ messageId: string;
13
+ pluginError: ChatMessagePluginError;
14
+ pluginState?: LocalFileSearchState;
15
+ }
16
+
17
+ const SearchFiles = memo<SearchFilesProps>(({ messageId, pluginError, args, pluginState }) => {
18
+ return (
19
+ <>
20
+ <SearchQuery args={args} messageId={messageId} pluginState={pluginState} />
21
+ <SearchResult
22
+ messageId={messageId}
23
+ pluginError={pluginError}
24
+ searchResults={pluginState?.searchResults}
25
+ />
26
+ </>
27
+ );
28
+ });
29
+
30
+ SearchFiles.displayName = 'SearchFiles';
31
+
32
+ export default SearchFiles;
@@ -0,0 +1,36 @@
1
+ import { LocalFileItem } from '@lobechat/electron-client-ipc';
2
+ import { memo } from 'react';
3
+
4
+ import { LocalFilesApiName } from '@/tools/local-files';
5
+ import { BuiltinRenderProps } from '@/types/tool';
6
+
7
+ import ListFiles from './ListFiles';
8
+ import ReadLocalFile from './ReadLocalFile';
9
+ import SearchFiles from './SearchFiles';
10
+
11
+ const RenderMap = {
12
+ [LocalFilesApiName.searchLocalFiles]: SearchFiles,
13
+ [LocalFilesApiName.listLocalFiles]: ListFiles,
14
+ [LocalFilesApiName.readLocalFile]: ReadLocalFile,
15
+ };
16
+
17
+ const LocalFilesRender = memo<BuiltinRenderProps<LocalFileItem[]>>(
18
+ ({ pluginState, apiName, messageId, pluginError, args }) => {
19
+ const Render = RenderMap[apiName as any];
20
+
21
+ if (!Render) return;
22
+
23
+ return (
24
+ <Render
25
+ args={args}
26
+ messageId={messageId}
27
+ pluginError={pluginError}
28
+ pluginState={pluginState}
29
+ />
30
+ );
31
+ },
32
+ );
33
+
34
+ LocalFilesRender.displayName = 'LocalFilesRender';
35
+
36
+ export default LocalFilesRender;
@@ -0,0 +1,117 @@
1
+ import { LocalFileItem } from '@lobechat/electron-client-ipc';
2
+ import { ActionIcon, FileTypeIcon } from '@lobehub/ui';
3
+ import { createStyles } from 'antd-style';
4
+ import dayjs from 'dayjs';
5
+ import { FolderOpen } from 'lucide-react';
6
+ import React, { memo, useState } from 'react';
7
+ import { useTranslation } from 'react-i18next';
8
+ import { Flexbox } from 'react-layout-kit';
9
+
10
+ import FileIcon from '@/components/FileIcon';
11
+ import { localFileService } from '@/services/electron/localFileService';
12
+ import { formatSize } from '@/utils/format';
13
+
14
+ const useStyles = createStyles(({ css, token }) => ({
15
+ container: css`
16
+ border-radius: 4px;
17
+ color: ${token.colorTextSecondary};
18
+
19
+ :hover {
20
+ color: ${token.colorText};
21
+ background: ${token.colorFillTertiary};
22
+ }
23
+ `,
24
+ path: css`
25
+ overflow: hidden;
26
+
27
+ font-size: 10px;
28
+ line-height: 1;
29
+ color: ${token.colorTextDescription};
30
+ text-overflow: ellipsis;
31
+ white-space: nowrap;
32
+ `,
33
+ size: css`
34
+ min-width: 50px;
35
+
36
+ font-family: ${token.fontFamilyCode};
37
+ font-size: 10px;
38
+ color: ${token.colorTextTertiary};
39
+ text-align: end;
40
+ `,
41
+ title: css`
42
+ overflow: hidden;
43
+ display: block;
44
+
45
+ color: inherit;
46
+ text-overflow: ellipsis;
47
+ white-space: nowrap;
48
+ `,
49
+ }));
50
+
51
+ interface FileItemProps extends LocalFileItem {
52
+ showTime?: boolean;
53
+ }
54
+ const FileItem = memo<FileItemProps>(
55
+ ({ isDirectory, name, path, size, type, showTime = false, createdTime }) => {
56
+ const { t } = useTranslation('tool');
57
+ const { styles } = useStyles();
58
+ const [isHovering, setIsHovering] = useState(false);
59
+
60
+ return (
61
+ <Flexbox
62
+ align={'center'}
63
+ className={styles.container}
64
+ gap={12}
65
+ horizontal
66
+ onClick={() => {
67
+ if (isDirectory) {
68
+ localFileService.openLocalFolder({ isDirectory, path });
69
+ } else {
70
+ localFileService.openLocalFile({ path });
71
+ }
72
+ }}
73
+ onMouseEnter={() => setIsHovering(true)}
74
+ onMouseLeave={() => setIsHovering(false)}
75
+ padding={'2px 8px'}
76
+ style={{ cursor: 'pointer', fontSize: 12, width: '100%' }}
77
+ >
78
+ {isDirectory ? (
79
+ <FileTypeIcon size={16} type={'folder'} variant={'mono'} />
80
+ ) : (
81
+ <FileIcon fileName={name} fileType={type} size={16} variant={'pure'} />
82
+ )}
83
+ <Flexbox
84
+ align={'baseline'}
85
+ gap={4}
86
+ horizontal
87
+ style={{ overflow: 'hidden', width: '100%' }}
88
+ >
89
+ <div className={styles.title}>{name}</div>
90
+ {showTime ? (
91
+ <div className={styles.path}>{dayjs(createdTime).format('MMM DD hh:mm')}</div>
92
+ ) : (
93
+ <div className={styles.path}>{path}</div>
94
+ )}
95
+ </Flexbox>
96
+ {isHovering ? (
97
+ <Flexbox direction={'horizontal-reverse'} gap={8} style={{ minWidth: 50 }}>
98
+ <ActionIcon
99
+ icon={FolderOpen}
100
+ onClick={(e) => {
101
+ e.stopPropagation();
102
+ localFileService.openLocalFolder({ isDirectory, path });
103
+ }}
104
+ size={'small'}
105
+ style={{ height: 16, width: 16 }}
106
+ title={t('localFiles.openFolder')}
107
+ />
108
+ </Flexbox>
109
+ ) : (
110
+ <span className={styles.size}>{formatSize(size)}</span>
111
+ )}
112
+ </Flexbox>
113
+ );
114
+ },
115
+ );
116
+
117
+ export default FileItem;