@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.
- package/CHANGELOG.md +59 -0
- package/changelog/v1.json +21 -0
- package/locales/ar/common.json +2 -0
- package/locales/ar/electron.json +32 -0
- package/locales/ar/models.json +126 -3
- package/locales/ar/plugin.json +1 -0
- package/locales/ar/tool.json +25 -0
- package/locales/bg-BG/common.json +2 -0
- package/locales/bg-BG/electron.json +32 -0
- package/locales/bg-BG/models.json +126 -3
- package/locales/bg-BG/plugin.json +1 -0
- package/locales/bg-BG/tool.json +25 -0
- package/locales/de-DE/common.json +2 -0
- package/locales/de-DE/electron.json +32 -0
- package/locales/de-DE/models.json +126 -3
- package/locales/de-DE/plugin.json +1 -0
- package/locales/de-DE/tool.json +25 -0
- package/locales/en-US/common.json +2 -0
- package/locales/en-US/electron.json +32 -0
- package/locales/en-US/models.json +126 -3
- package/locales/en-US/plugin.json +1 -0
- package/locales/en-US/tool.json +25 -0
- package/locales/es-ES/common.json +2 -0
- package/locales/es-ES/electron.json +32 -0
- package/locales/es-ES/models.json +126 -3
- package/locales/es-ES/plugin.json +1 -0
- package/locales/es-ES/tool.json +25 -0
- package/locales/fa-IR/common.json +2 -0
- package/locales/fa-IR/electron.json +32 -0
- package/locales/fa-IR/models.json +126 -3
- package/locales/fa-IR/plugin.json +1 -0
- package/locales/fa-IR/tool.json +25 -0
- package/locales/fr-FR/common.json +2 -0
- package/locales/fr-FR/electron.json +32 -0
- package/locales/fr-FR/models.json +126 -3
- package/locales/fr-FR/plugin.json +1 -0
- package/locales/fr-FR/tool.json +25 -0
- package/locales/it-IT/common.json +2 -0
- package/locales/it-IT/electron.json +32 -0
- package/locales/it-IT/models.json +126 -3
- package/locales/it-IT/plugin.json +1 -0
- package/locales/it-IT/tool.json +25 -0
- package/locales/ja-JP/common.json +2 -0
- package/locales/ja-JP/electron.json +32 -0
- package/locales/ja-JP/models.json +126 -3
- package/locales/ja-JP/plugin.json +1 -0
- package/locales/ja-JP/tool.json +25 -0
- package/locales/ko-KR/common.json +2 -0
- package/locales/ko-KR/electron.json +32 -0
- package/locales/ko-KR/models.json +126 -3
- package/locales/ko-KR/plugin.json +1 -0
- package/locales/ko-KR/tool.json +25 -0
- package/locales/nl-NL/common.json +2 -0
- package/locales/nl-NL/electron.json +32 -0
- package/locales/nl-NL/models.json +126 -3
- package/locales/nl-NL/plugin.json +1 -0
- package/locales/nl-NL/tool.json +25 -0
- package/locales/pl-PL/common.json +2 -0
- package/locales/pl-PL/electron.json +32 -0
- package/locales/pl-PL/models.json +126 -3
- package/locales/pl-PL/plugin.json +1 -0
- package/locales/pl-PL/tool.json +25 -0
- package/locales/pt-BR/common.json +2 -0
- package/locales/pt-BR/electron.json +32 -0
- package/locales/pt-BR/models.json +126 -3
- package/locales/pt-BR/plugin.json +1 -0
- package/locales/pt-BR/tool.json +25 -0
- package/locales/ru-RU/common.json +2 -0
- package/locales/ru-RU/electron.json +32 -0
- package/locales/ru-RU/models.json +126 -3
- package/locales/ru-RU/plugin.json +1 -0
- package/locales/ru-RU/tool.json +25 -0
- package/locales/tr-TR/common.json +2 -0
- package/locales/tr-TR/electron.json +32 -0
- package/locales/tr-TR/models.json +126 -3
- package/locales/tr-TR/plugin.json +1 -0
- package/locales/tr-TR/tool.json +25 -0
- package/locales/vi-VN/common.json +2 -0
- package/locales/vi-VN/electron.json +32 -0
- package/locales/vi-VN/models.json +126 -3
- package/locales/vi-VN/plugin.json +1 -0
- package/locales/vi-VN/tool.json +25 -0
- package/locales/zh-CN/common.json +2 -0
- package/locales/zh-CN/electron.json +32 -0
- package/locales/zh-CN/models.json +131 -8
- package/locales/zh-CN/plugin.json +1 -0
- package/locales/zh-CN/tool.json +25 -0
- package/locales/zh-TW/common.json +2 -0
- package/locales/zh-TW/electron.json +32 -0
- package/locales/zh-TW/models.json +126 -3
- package/locales/zh-TW/plugin.json +1 -0
- package/locales/zh-TW/tool.json +25 -0
- package/package.json +3 -2
- package/packages/electron-client-ipc/src/events/index.ts +5 -5
- package/packages/electron-client-ipc/src/events/localFile.ts +22 -0
- package/packages/electron-client-ipc/src/events/{file.ts → upload.ts} +1 -1
- package/packages/electron-client-ipc/src/types/index.ts +2 -1
- package/packages/electron-client-ipc/src/types/localFile.ts +52 -0
- package/scripts/prebuild.mts +5 -1
- package/src/app/(backend)/trpc/desktop/[trpc]/route.ts +26 -0
- package/src/config/aiModels/cloudflare.ts +41 -37
- package/src/config/aiModels/github.ts +90 -0
- package/src/config/aiModels/google.ts +25 -0
- package/src/features/Conversation/Messages/Assistant/Tool/Render/Arguments/ObjectEntity.tsx +81 -0
- package/src/features/Conversation/Messages/Assistant/Tool/Render/Arguments/ValueCell.tsx +43 -0
- package/src/features/Conversation/Messages/Assistant/Tool/Render/Arguments/index.tsx +120 -0
- package/src/features/Conversation/Messages/Assistant/Tool/Render/CustomRender.tsx +75 -2
- package/src/features/Conversation/Messages/Assistant/Tool/Render/KeyValueEditor.tsx +214 -0
- package/src/features/User/UserPanel/useMenu.tsx +8 -1
- package/src/libs/agent-runtime/google/index.ts +3 -0
- package/src/libs/trpc/client/desktop.ts +14 -0
- package/src/locales/default/common.ts +2 -0
- package/src/locales/default/electron.ts +34 -0
- package/src/locales/default/index.ts +2 -0
- package/src/locales/default/tool.ts +25 -0
- package/src/server/routers/desktop/index.ts +9 -0
- package/src/server/routers/desktop/pgTable.ts +43 -0
- package/src/services/electron/autoUpdate.ts +17 -0
- package/src/services/electron/file.ts +31 -0
- package/src/services/electron/localFileService.ts +39 -0
- package/src/services/electron/remoteServer.ts +40 -0
- package/src/store/chat/index.ts +1 -1
- package/src/store/chat/slices/builtinTool/actions/index.ts +3 -1
- package/src/store/chat/slices/builtinTool/actions/localFile.ts +129 -0
- package/src/store/chat/slices/builtinTool/initialState.ts +2 -0
- package/src/store/chat/slices/builtinTool/selectors.ts +2 -0
- package/src/store/chat/slices/plugin/action.ts +3 -3
- package/src/store/chat/store.ts +2 -0
- package/src/store/electron/actions/sync.ts +117 -0
- package/src/store/electron/index.ts +1 -0
- package/src/store/electron/initialState.ts +18 -0
- package/src/store/electron/selectors/index.ts +1 -0
- package/src/store/electron/selectors/sync.ts +9 -0
- package/src/store/electron/store.ts +29 -0
- package/src/tools/index.ts +8 -0
- package/src/tools/local-files/Render/ListFiles/Result.tsx +42 -0
- package/src/tools/local-files/Render/ListFiles/index.tsx +68 -0
- package/src/tools/local-files/Render/ReadLocalFile/ReadFileSkeleton.tsx +50 -0
- package/src/tools/local-files/Render/ReadLocalFile/ReadFileView.tsx +197 -0
- package/src/tools/local-files/Render/ReadLocalFile/index.tsx +31 -0
- package/src/tools/local-files/Render/ReadLocalFile/style.ts +37 -0
- package/src/tools/local-files/Render/SearchFiles/Result.tsx +42 -0
- package/src/tools/local-files/Render/SearchFiles/SearchQuery/SearchView.tsx +77 -0
- package/src/tools/local-files/Render/SearchFiles/SearchQuery/index.tsx +72 -0
- package/src/tools/local-files/Render/SearchFiles/index.tsx +32 -0
- package/src/tools/local-files/Render/index.tsx +36 -0
- package/src/tools/local-files/components/FileItem.tsx +117 -0
- package/src/tools/local-files/index.ts +149 -0
- package/src/tools/local-files/systemRole.ts +46 -0
- package/src/tools/local-files/type.ts +33 -0
- package/src/tools/renders.ts +3 -0
- package/packages/electron-client-ipc/src/events/search.ts +0 -4
- package/src/features/Conversation/Messages/Assistant/Tool/Render/Arguments.tsx +0 -165
- /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;
|