@lobehub/chat 1.82.0 → 1.82.1
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/.cursor/rules/desktop-local-tools-implement.mdc +80 -0
- package/.env.desktop +2 -1
- package/.github/scripts/pr-comment.js +4 -9
- package/CHANGELOG.md +25 -0
- package/changelog/v1.json +9 -0
- package/locales/ar/electron.json +38 -2
- package/locales/ar/plugin.json +31 -31
- package/locales/bg-BG/electron.json +38 -2
- package/locales/bg-BG/plugin.json +31 -31
- package/locales/de-DE/electron.json +38 -2
- package/locales/de-DE/plugin.json +3 -8
- package/locales/en-US/electron.json +38 -2
- package/locales/en-US/plugin.json +3 -8
- package/locales/es-ES/electron.json +38 -2
- package/locales/es-ES/plugin.json +31 -31
- package/locales/fa-IR/electron.json +38 -2
- package/locales/fa-IR/plugin.json +31 -31
- package/locales/fr-FR/electron.json +38 -2
- package/locales/fr-FR/plugin.json +31 -31
- package/locales/it-IT/electron.json +38 -2
- package/locales/it-IT/plugin.json +31 -31
- package/locales/ja-JP/electron.json +38 -2
- package/locales/ja-JP/plugin.json +31 -31
- package/locales/ko-KR/electron.json +38 -2
- package/locales/ko-KR/plugin.json +3 -8
- package/locales/nl-NL/electron.json +38 -2
- package/locales/nl-NL/plugin.json +31 -31
- package/locales/pl-PL/electron.json +38 -2
- package/locales/pl-PL/plugin.json +3 -8
- package/locales/pt-BR/electron.json +38 -2
- package/locales/pt-BR/plugin.json +31 -31
- package/locales/ru-RU/electron.json +38 -2
- package/locales/ru-RU/plugin.json +31 -31
- package/locales/tr-TR/electron.json +38 -2
- package/locales/tr-TR/plugin.json +31 -31
- package/locales/vi-VN/electron.json +38 -2
- package/locales/vi-VN/plugin.json +3 -8
- package/locales/zh-CN/electron.json +38 -2
- package/locales/zh-CN/plugin.json +14 -9
- package/locales/zh-TW/electron.json +38 -2
- package/locales/zh-TW/plugin.json +31 -31
- package/package.json +1 -1
- package/packages/electron-client-ipc/src/events/update.ts +3 -3
- package/src/app/[variants]/(main)/_layout/Desktop/ElectronTitlebar/Connection/Mode.tsx +222 -0
- package/src/app/[variants]/(main)/_layout/Desktop/ElectronTitlebar/Connection/Option.tsx +104 -0
- package/src/app/[variants]/(main)/_layout/Desktop/ElectronTitlebar/Connection/Sync.tsx +42 -0
- package/src/app/[variants]/(main)/_layout/Desktop/ElectronTitlebar/Connection/Waiting.tsx +203 -0
- package/src/app/[variants]/(main)/_layout/Desktop/ElectronTitlebar/Connection/index.tsx +57 -0
- package/src/app/[variants]/(main)/_layout/Desktop/ElectronTitlebar/UpdateModal.tsx +242 -0
- package/src/app/[variants]/(main)/_layout/Desktop/ElectronTitlebar/UpdateNotification.tsx +193 -0
- package/src/app/[variants]/(main)/_layout/Desktop/{Titlebar.tsx → ElectronTitlebar/index.tsx} +15 -1
- package/src/app/[variants]/(main)/_layout/Desktop/SideBar/BottomActions.tsx +3 -2
- package/src/app/[variants]/(main)/_layout/Desktop/index.tsx +1 -1
- package/src/app/[variants]/layout.tsx +2 -1
- package/src/features/Conversation/components/MarkdownElements/LocalFile/Render/LocalFile.tsx +65 -0
- package/src/features/Conversation/components/MarkdownElements/LocalFile/Render/index.tsx +29 -0
- package/src/features/Conversation/components/MarkdownElements/LocalFile/index.ts +16 -0
- package/src/features/Conversation/components/MarkdownElements/index.ts +7 -1
- package/src/features/Conversation/components/MarkdownElements/remarkPlugins/__snapshots__/createRemarkSelfClosingTagPlugin.test.ts.snap +260 -0
- package/src/features/Conversation/components/MarkdownElements/remarkPlugins/createRemarkSelfClosingTagPlugin.test.ts +204 -0
- package/src/features/Conversation/components/MarkdownElements/remarkPlugins/createRemarkSelfClosingTagPlugin.ts +133 -0
- package/src/features/Conversation/components/MarkdownElements/type.ts +5 -1
- package/src/features/PluginDevModal/MCPManifestForm/ArgsInput.tsx +20 -0
- package/src/features/PluginDevModal/MCPManifestForm/MCPTypeSelect.tsx +176 -0
- package/src/features/PluginDevModal/{MCPManifestForm.tsx → MCPManifestForm/index.tsx} +92 -30
- package/src/libs/mcp/__tests__/__snapshots__/index.test.ts.snap +0 -56
- package/src/locales/default/electron.ts +38 -2
- package/src/locales/default/plugin.ts +14 -7
- package/src/server/modules/ElectronIPCClient/index.ts +36 -0
- package/src/server/routers/lambda/session.ts +2 -6
- package/src/server/routers/tools/mcp.ts +6 -0
- package/src/server/services/file/impls/index.ts +9 -1
- package/src/server/services/file/impls/local.test.ts +299 -0
- package/src/server/services/file/impls/local.ts +183 -0
- package/src/server/services/mcp/index.ts +19 -0
- package/src/services/aiModel/index.ts +5 -1
- package/src/services/aiProvider/index.ts +5 -1
- package/src/services/electron/autoUpdate.ts +4 -0
- package/src/services/file/index.ts +5 -1
- package/src/services/mcp.ts +13 -2
- package/src/services/message/index.ts +5 -1
- package/src/services/plugin/index.ts +5 -1
- package/src/services/session/index.ts +5 -1
- package/src/services/tableViewer/desktop.ts +15 -0
- package/src/services/tableViewer/index.ts +4 -1
- package/src/services/thread/index.ts +5 -1
- package/src/services/topic/index.ts +5 -1
- package/src/services/user/index.ts +5 -1
- package/src/store/electron/actions/app.ts +59 -0
- package/src/store/electron/actions/sync.ts +5 -1
- package/src/store/electron/initialState.ts +3 -1
- package/src/store/electron/store.ts +6 -1
- package/src/store/tool/slices/customPlugin/action.ts +16 -4
- package/src/utils/client/GlobalAgentContextManager.ts +85 -0
- package/src/utils/promptTemplate.test.ts +78 -0
- package/src/utils/promptTemplate.ts +17 -0
@@ -0,0 +1,242 @@
|
|
1
|
+
import { ProgressInfo, UpdateInfo, useWatchBroadcast } from '@lobechat/electron-client-ipc';
|
2
|
+
import { App, Button, Modal, Progress, Spin } from 'antd';
|
3
|
+
import React, { memo, useState } from 'react';
|
4
|
+
import { useTranslation } from 'react-i18next';
|
5
|
+
|
6
|
+
import { autoUpdateService } from '@/services/electron/autoUpdate';
|
7
|
+
|
8
|
+
export const UpdateModal = memo(() => {
|
9
|
+
const { t } = useTranslation(['electron', 'common']);
|
10
|
+
|
11
|
+
const [isChecking, setIsChecking] = useState(false);
|
12
|
+
const [isDownloading, setIsDownloading] = useState(false);
|
13
|
+
const [updateAvailableInfo, setUpdateAvailableInfo] = useState<UpdateInfo | null>(null);
|
14
|
+
const [downloadedInfo, setDownloadedInfo] = useState<UpdateInfo | null>(null);
|
15
|
+
const [progress, setProgress] = useState<ProgressInfo | null>(null);
|
16
|
+
const [latestVersionInfo, setLatestVersionInfo] = useState<UpdateInfo | null>(null); // State for latest version modal
|
17
|
+
const { modal } = App.useApp();
|
18
|
+
// --- Event Listeners ---
|
19
|
+
|
20
|
+
useWatchBroadcast('manualUpdateCheckStart', () => {
|
21
|
+
console.log('[Manual Update] Check Start');
|
22
|
+
setIsChecking(true);
|
23
|
+
setUpdateAvailableInfo(null);
|
24
|
+
setDownloadedInfo(null);
|
25
|
+
setProgress(null);
|
26
|
+
setLatestVersionInfo(null); // Reset latest version info
|
27
|
+
// Optional: Show a brief notification that check has started
|
28
|
+
// notification.info({ message: t('updater.checking') });
|
29
|
+
});
|
30
|
+
|
31
|
+
useWatchBroadcast('manualUpdateAvailable', (info: UpdateInfo) => {
|
32
|
+
console.log('[Manual Update] Available:', info);
|
33
|
+
// Only react if it's part of a manual check flow (i.e., isChecking was true)
|
34
|
+
// No need to check isChecking here as this event is specific
|
35
|
+
setIsChecking(false);
|
36
|
+
setUpdateAvailableInfo(info);
|
37
|
+
});
|
38
|
+
|
39
|
+
useWatchBroadcast('manualUpdateNotAvailable', (info) => {
|
40
|
+
console.log('[Manual Update] Not Available:', info);
|
41
|
+
// Only react if it's part of a manual check flow
|
42
|
+
// No need to check isChecking here as this event is specific
|
43
|
+
setIsChecking(false);
|
44
|
+
setLatestVersionInfo(info); // Set info for the modal
|
45
|
+
// notification.success({
|
46
|
+
// description: t('updater.isLatestVersionDesc', { version: info.version }),
|
47
|
+
// message: t('updater.isLatestVersion'),
|
48
|
+
// });
|
49
|
+
});
|
50
|
+
|
51
|
+
useWatchBroadcast('updateError', (message: string) => {
|
52
|
+
console.log('[Manual Update] Error:', message);
|
53
|
+
// Only react if it's part of a manual check/download flow
|
54
|
+
if (isChecking || isDownloading) {
|
55
|
+
setIsChecking(false);
|
56
|
+
setIsDownloading(false);
|
57
|
+
// Show error modal or notification
|
58
|
+
modal.error({ content: message, title: t('updater.updateError') });
|
59
|
+
setLatestVersionInfo(null); // Ensure other modals are closed on error
|
60
|
+
setUpdateAvailableInfo(null);
|
61
|
+
setDownloadedInfo(null);
|
62
|
+
}
|
63
|
+
});
|
64
|
+
|
65
|
+
useWatchBroadcast('updateDownloadStart', () => {
|
66
|
+
console.log('[Manual Update] Download Start');
|
67
|
+
// This event implies a manual download was triggered (likely from the 'updateAvailable' modal)
|
68
|
+
setIsDownloading(true);
|
69
|
+
setUpdateAvailableInfo(null); // Hide the 'download' button modal
|
70
|
+
setProgress({ bytesPerSecond: 0, percent: 0, total: 0, transferred: 0 }); // Reset progress
|
71
|
+
setLatestVersionInfo(null); // Ensure other modals are closed
|
72
|
+
// Optional: Show notification that download started
|
73
|
+
// notification.info({ message: t('updater.downloadingUpdate') });
|
74
|
+
});
|
75
|
+
|
76
|
+
useWatchBroadcast('updateDownloadProgress', (progressInfo: ProgressInfo) => {
|
77
|
+
console.log('[Manual Update] Progress:', progressInfo);
|
78
|
+
// Only update progress if we are in the manual download state
|
79
|
+
setProgress(progressInfo);
|
80
|
+
});
|
81
|
+
|
82
|
+
useWatchBroadcast('updateDownloaded', (info: UpdateInfo) => {
|
83
|
+
console.log('[Manual Update] Downloaded:', info);
|
84
|
+
// This event implies a download finished, likely the one we started manually
|
85
|
+
setIsChecking(false);
|
86
|
+
setIsDownloading(false);
|
87
|
+
setDownloadedInfo(info);
|
88
|
+
setProgress(null); // Clear progress
|
89
|
+
setLatestVersionInfo(null); // Ensure other modals are closed
|
90
|
+
setUpdateAvailableInfo(null);
|
91
|
+
});
|
92
|
+
|
93
|
+
// --- Render Logic ---
|
94
|
+
|
95
|
+
const handleDownload = () => {
|
96
|
+
if (!updateAvailableInfo) return;
|
97
|
+
// No need to set states here, 'updateDownloadStart' will handle it
|
98
|
+
autoUpdateService.downloadUpdate();
|
99
|
+
};
|
100
|
+
|
101
|
+
const handleInstallNow = () => {
|
102
|
+
setDownloadedInfo(null); // Close modal immediately
|
103
|
+
autoUpdateService.installNow();
|
104
|
+
};
|
105
|
+
|
106
|
+
const handleInstallLater = () => {
|
107
|
+
// No need to set state here, 'updateWillInstallLater' handles it
|
108
|
+
autoUpdateService.installLater();
|
109
|
+
setDownloadedInfo(null); // Close the modal after clicking
|
110
|
+
};
|
111
|
+
|
112
|
+
const closeAvailableModal = () => setUpdateAvailableInfo(null);
|
113
|
+
const closeDownloadedModal = () => setDownloadedInfo(null);
|
114
|
+
const closeLatestVersionModal = () => setLatestVersionInfo(null);
|
115
|
+
|
116
|
+
const renderCheckingModal = () => (
|
117
|
+
<Modal
|
118
|
+
closable={false}
|
119
|
+
footer={null}
|
120
|
+
maskClosable={false}
|
121
|
+
open={isChecking}
|
122
|
+
title={t('updater.checkingUpdate')}
|
123
|
+
>
|
124
|
+
<Spin spinning={true}>
|
125
|
+
<div style={{ padding: '20px', textAlign: 'center' }}>
|
126
|
+
{t('updater.checkingUpdateDesc')}
|
127
|
+
</div>
|
128
|
+
</Spin>
|
129
|
+
</Modal>
|
130
|
+
);
|
131
|
+
|
132
|
+
const renderAvailableModal = () => (
|
133
|
+
<Modal
|
134
|
+
footer={[
|
135
|
+
<Button key="cancel" onClick={closeAvailableModal}>
|
136
|
+
{t('cancel', { ns: 'common' })}
|
137
|
+
</Button>,
|
138
|
+
<Button key="download" onClick={handleDownload} type="primary">
|
139
|
+
{t('updater.downloadNewVersion')}
|
140
|
+
</Button>,
|
141
|
+
]}
|
142
|
+
onCancel={closeAvailableModal}
|
143
|
+
open={!!updateAvailableInfo}
|
144
|
+
title={t('updater.newVersionAvailable')}
|
145
|
+
>
|
146
|
+
<h4>{t('updater.newVersionAvailableDesc', { version: updateAvailableInfo?.version })}</h4>
|
147
|
+
{updateAvailableInfo?.releaseNotes && (
|
148
|
+
<div
|
149
|
+
dangerouslySetInnerHTML={{ __html: updateAvailableInfo.releaseNotes as string }}
|
150
|
+
style={{
|
151
|
+
// background:theme
|
152
|
+
borderRadius: 4,
|
153
|
+
marginTop: 8,
|
154
|
+
maxHeight: 300,
|
155
|
+
overflow: 'auto',
|
156
|
+
padding: '8px 12px',
|
157
|
+
}}
|
158
|
+
/>
|
159
|
+
)}
|
160
|
+
</Modal>
|
161
|
+
);
|
162
|
+
|
163
|
+
const renderDownloadingModal = () => {
|
164
|
+
const percent = progress ? Math.round(progress.percent) : 0;
|
165
|
+
return (
|
166
|
+
<Modal
|
167
|
+
closable={false}
|
168
|
+
footer={null}
|
169
|
+
maskClosable={false}
|
170
|
+
open={isDownloading && !downloadedInfo}
|
171
|
+
title={t('updater.downloadingUpdate')}
|
172
|
+
>
|
173
|
+
<div style={{ padding: '20px 0' }}>
|
174
|
+
<Progress percent={percent} status="active" />
|
175
|
+
<div style={{ fontSize: 12, marginTop: 8, textAlign: 'center' }}>
|
176
|
+
{t('updater.downloadingUpdateDesc', { percent })}
|
177
|
+
{progress && progress.bytesPerSecond > 0 && (
|
178
|
+
<span> ({(progress.bytesPerSecond / 1024 / 1024).toFixed(2)} MB/s)</span>
|
179
|
+
)}
|
180
|
+
</div>
|
181
|
+
</div>
|
182
|
+
</Modal>
|
183
|
+
);
|
184
|
+
};
|
185
|
+
|
186
|
+
const renderDownloadedModal = () => (
|
187
|
+
<Modal
|
188
|
+
footer={[
|
189
|
+
<Button key="later" onClick={handleInstallLater}>
|
190
|
+
{t('updater.installLater')}
|
191
|
+
</Button>,
|
192
|
+
<Button key="now" onClick={handleInstallNow} type="primary">
|
193
|
+
{t('updater.restartAndInstall')}
|
194
|
+
</Button>,
|
195
|
+
]}
|
196
|
+
onCancel={closeDownloadedModal} // Allow closing if they don't want to decide now
|
197
|
+
open={!!downloadedInfo}
|
198
|
+
title={t('updater.updateReady')}
|
199
|
+
>
|
200
|
+
<h4>{t('updater.updateReadyDesc', { version: downloadedInfo?.version })}</h4>
|
201
|
+
{downloadedInfo?.releaseNotes && (
|
202
|
+
<div
|
203
|
+
dangerouslySetInnerHTML={{ __html: downloadedInfo.releaseNotes as string }}
|
204
|
+
style={{
|
205
|
+
borderRadius: 4,
|
206
|
+
marginTop: 8,
|
207
|
+
maxHeight: 300,
|
208
|
+
overflow: 'auto',
|
209
|
+
padding: '8px 12px',
|
210
|
+
}}
|
211
|
+
/>
|
212
|
+
)}
|
213
|
+
</Modal>
|
214
|
+
);
|
215
|
+
|
216
|
+
// New modal for "latest version"
|
217
|
+
const renderLatestVersionModal = () => (
|
218
|
+
<Modal
|
219
|
+
footer={[
|
220
|
+
<Button key="ok" onClick={closeLatestVersionModal} type="primary">
|
221
|
+
{t('ok', { ns: 'common' })}
|
222
|
+
</Button>,
|
223
|
+
]}
|
224
|
+
onCancel={closeLatestVersionModal}
|
225
|
+
open={!!latestVersionInfo}
|
226
|
+
title={t('updater.isLatestVersion')}
|
227
|
+
>
|
228
|
+
<p>{t('updater.isLatestVersionDesc', { version: latestVersionInfo?.version })}</p>
|
229
|
+
</Modal>
|
230
|
+
);
|
231
|
+
|
232
|
+
return (
|
233
|
+
<>
|
234
|
+
{renderCheckingModal()}
|
235
|
+
{renderAvailableModal()}
|
236
|
+
{renderDownloadingModal()}
|
237
|
+
{renderDownloadedModal()}
|
238
|
+
{renderLatestVersionModal()}
|
239
|
+
{/* Error state is handled by Modal.error currently */}
|
240
|
+
</>
|
241
|
+
);
|
242
|
+
});
|
@@ -0,0 +1,193 @@
|
|
1
|
+
import { DownloadOutlined } from '@ant-design/icons';
|
2
|
+
import { UpdateInfo, useWatchBroadcast } from '@lobechat/electron-client-ipc';
|
3
|
+
import { Icon } from '@lobehub/ui';
|
4
|
+
import { Badge, Button, Popover, Progress, Tooltip, theme } from 'antd';
|
5
|
+
import { createStyles } from 'antd-style';
|
6
|
+
import { Download } from 'lucide-react';
|
7
|
+
import React, { useState } from 'react';
|
8
|
+
import { useTranslation } from 'react-i18next';
|
9
|
+
import { Flexbox } from 'react-layout-kit';
|
10
|
+
|
11
|
+
import { autoUpdateService } from '@/services/electron/autoUpdate';
|
12
|
+
|
13
|
+
const useStyles = createStyles(({ css, token }) => ({
|
14
|
+
container: css`
|
15
|
+
cursor: pointer;
|
16
|
+
|
17
|
+
height: 24px;
|
18
|
+
padding-inline: 8px;
|
19
|
+
border: 1px solid ${token.green7A};
|
20
|
+
border-radius: 24px;
|
21
|
+
|
22
|
+
font-size: 12px;
|
23
|
+
line-height: 22px;
|
24
|
+
color: ${token.green11A};
|
25
|
+
|
26
|
+
background: ${token.green2A};
|
27
|
+
`,
|
28
|
+
|
29
|
+
releaseNote: css`
|
30
|
+
overflow: scroll;
|
31
|
+
|
32
|
+
max-height: 300px;
|
33
|
+
padding: 8px;
|
34
|
+
border-radius: 8px;
|
35
|
+
|
36
|
+
background: ${token.colorFillQuaternary};
|
37
|
+
`,
|
38
|
+
}));
|
39
|
+
|
40
|
+
export const UpdateNotification: React.FC = () => {
|
41
|
+
const { t } = useTranslation('electron');
|
42
|
+
const { styles } = useStyles();
|
43
|
+
const { token } = theme.useToken();
|
44
|
+
const [updateAvailable, setUpdateAvailable] = useState(false);
|
45
|
+
const [updateDownloaded, setUpdateDownloaded] = useState(false);
|
46
|
+
const [downloadProgress, setDownloadProgress] = useState(0);
|
47
|
+
const [updateInfo, setUpdateInfo] = useState<UpdateInfo | null>(null);
|
48
|
+
const [willInstallLater, setWillInstallLater] = useState(false);
|
49
|
+
const [isPopoverVisible, setIsPopoverVisible] = useState(false);
|
50
|
+
|
51
|
+
useWatchBroadcast('updateDownloadProgress', (progress: { percent: number }) => {
|
52
|
+
setDownloadProgress(progress.percent);
|
53
|
+
});
|
54
|
+
|
55
|
+
useWatchBroadcast('updateDownloaded', (info: UpdateInfo) => {
|
56
|
+
setUpdateInfo(info);
|
57
|
+
setUpdateDownloaded(true);
|
58
|
+
setUpdateAvailable(false);
|
59
|
+
});
|
60
|
+
|
61
|
+
useWatchBroadcast('updateWillInstallLater', () => {
|
62
|
+
setWillInstallLater(true);
|
63
|
+
setTimeout(() => setWillInstallLater(false), 5000); // 5秒后自动隐藏提示
|
64
|
+
});
|
65
|
+
|
66
|
+
// 没有更新或正在下载时不显示任何内容
|
67
|
+
if ((!updateAvailable && !updateDownloaded) || (downloadProgress > 0 && downloadProgress < 100)) {
|
68
|
+
return null;
|
69
|
+
}
|
70
|
+
|
71
|
+
// 如果正在下载,显示下载进度
|
72
|
+
if (downloadProgress > 0 && downloadProgress < 100) {
|
73
|
+
return (
|
74
|
+
<div
|
75
|
+
style={{
|
76
|
+
position: 'fixed',
|
77
|
+
right: 12,
|
78
|
+
top: 12,
|
79
|
+
zIndex: 1000,
|
80
|
+
}}
|
81
|
+
>
|
82
|
+
<Tooltip title={t('updater.downloadingUpdateDesc', '正在下载更新...')}>
|
83
|
+
<Badge
|
84
|
+
count={<DownloadOutlined style={{ color: token.colorPrimary }} />}
|
85
|
+
offset={[-4, 4]}
|
86
|
+
>
|
87
|
+
<div
|
88
|
+
style={{
|
89
|
+
alignItems: 'center',
|
90
|
+
background: token.colorBgElevated,
|
91
|
+
borderRadius: '50%',
|
92
|
+
boxShadow: token.boxShadow,
|
93
|
+
display: 'flex',
|
94
|
+
height: 32,
|
95
|
+
justifyContent: 'center',
|
96
|
+
position: 'relative',
|
97
|
+
width: 32,
|
98
|
+
}}
|
99
|
+
>
|
100
|
+
<Progress
|
101
|
+
percent={Math.round(downloadProgress)}
|
102
|
+
showInfo={false}
|
103
|
+
strokeWidth={12}
|
104
|
+
type="circle"
|
105
|
+
width={30}
|
106
|
+
/>
|
107
|
+
<span
|
108
|
+
style={{
|
109
|
+
fontSize: 10,
|
110
|
+
fontWeight: 'bold',
|
111
|
+
position: 'absolute',
|
112
|
+
}}
|
113
|
+
>
|
114
|
+
{Math.round(downloadProgress)}%
|
115
|
+
</span>
|
116
|
+
</div>
|
117
|
+
</Badge>
|
118
|
+
</Tooltip>
|
119
|
+
</div>
|
120
|
+
);
|
121
|
+
}
|
122
|
+
|
123
|
+
return (
|
124
|
+
<Flexbox>
|
125
|
+
<Popover
|
126
|
+
arrow={false}
|
127
|
+
content={
|
128
|
+
<Flexbox gap={8} style={{ maxWidth: 380 }}>
|
129
|
+
<div>
|
130
|
+
<h3 style={{ margin: 0 }}>{t('updater.updateReady')}</h3>
|
131
|
+
<div style={{ color: token.colorTextSecondary, fontSize: 12 }}>
|
132
|
+
{updateInfo?.version}
|
133
|
+
</div>
|
134
|
+
</div>
|
135
|
+
|
136
|
+
{updateInfo?.releaseNotes && (
|
137
|
+
<div
|
138
|
+
className={styles.releaseNote}
|
139
|
+
dangerouslySetInnerHTML={{ __html: updateInfo.releaseNotes }}
|
140
|
+
style={{ maxHeight: 300, overflow: 'scroll' }}
|
141
|
+
/>
|
142
|
+
)}
|
143
|
+
|
144
|
+
<div style={{ display: 'flex', gap: 8, justifyContent: 'flex-end' }}>
|
145
|
+
<Button
|
146
|
+
onClick={() => {
|
147
|
+
autoUpdateService.installNow();
|
148
|
+
}}
|
149
|
+
size="small"
|
150
|
+
type="primary"
|
151
|
+
>
|
152
|
+
{t('updater.upgradeNow')}
|
153
|
+
</Button>
|
154
|
+
</div>
|
155
|
+
</Flexbox>
|
156
|
+
}
|
157
|
+
onOpenChange={setIsPopoverVisible}
|
158
|
+
open={isPopoverVisible}
|
159
|
+
placement="bottomRight"
|
160
|
+
title={null}
|
161
|
+
trigger="hover"
|
162
|
+
>
|
163
|
+
<Flexbox
|
164
|
+
align={'center'}
|
165
|
+
className={styles.container}
|
166
|
+
gap={4}
|
167
|
+
horizontal
|
168
|
+
onClick={() => setIsPopoverVisible(true)}
|
169
|
+
>
|
170
|
+
<Icon icon={Download} style={{ fontSize: 14 }} /> 已有可用更新
|
171
|
+
</Flexbox>
|
172
|
+
</Popover>
|
173
|
+
{/* 下次启动时更新提示 */}
|
174
|
+
{willInstallLater && (
|
175
|
+
<div
|
176
|
+
style={{
|
177
|
+
backgroundColor: token.colorBgElevated,
|
178
|
+
borderRadius: token.borderRadius,
|
179
|
+
bottom: 20,
|
180
|
+
boxShadow: token.boxShadow,
|
181
|
+
color: token.colorText,
|
182
|
+
padding: '10px 16px',
|
183
|
+
position: 'fixed',
|
184
|
+
right: 20,
|
185
|
+
zIndex: 1000,
|
186
|
+
}}
|
187
|
+
>
|
188
|
+
{t('updater.willInstallLater', '更新将在下次启动时安装')}
|
189
|
+
</div>
|
190
|
+
)}
|
191
|
+
</Flexbox>
|
192
|
+
);
|
193
|
+
};
|
package/src/app/[variants]/(main)/_layout/Desktop/{Titlebar.tsx → ElectronTitlebar/index.tsx}
RENAMED
@@ -1,11 +1,20 @@
|
|
1
1
|
import { memo } from 'react';
|
2
2
|
import { Flexbox } from 'react-layout-kit';
|
3
3
|
|
4
|
+
import { useElectronStore } from '@/store/electron';
|
4
5
|
import { electronStylish } from '@/styles/electron';
|
5
6
|
|
7
|
+
import Connection from './Connection';
|
8
|
+
import { UpdateModal } from './UpdateModal';
|
9
|
+
import { UpdateNotification } from './UpdateNotification';
|
10
|
+
|
6
11
|
export const TITLE_BAR_HEIGHT = 36;
|
7
12
|
|
8
13
|
const TitleBar = memo(() => {
|
14
|
+
const initElectronAppState = useElectronStore((s) => s.useInitElectronAppState);
|
15
|
+
|
16
|
+
initElectronAppState();
|
17
|
+
|
9
18
|
return (
|
10
19
|
<Flexbox
|
11
20
|
align={'center'}
|
@@ -19,7 +28,12 @@ const TitleBar = memo(() => {
|
|
19
28
|
>
|
20
29
|
<div />
|
21
30
|
<div>{/* TODO */}</div>
|
22
|
-
|
31
|
+
|
32
|
+
<Flexbox className={electronStylish.nodrag} gap={8} horizontal>
|
33
|
+
<UpdateNotification />
|
34
|
+
<Connection />
|
35
|
+
</Flexbox>
|
36
|
+
<UpdateModal />
|
23
37
|
</Flexbox>
|
24
38
|
);
|
25
39
|
});
|
@@ -3,6 +3,7 @@ import { Book, Github } from 'lucide-react';
|
|
3
3
|
import Link from 'next/link';
|
4
4
|
import { memo } from 'react';
|
5
5
|
import { useTranslation } from 'react-i18next';
|
6
|
+
import { Flexbox } from 'react-layout-kit';
|
6
7
|
|
7
8
|
import { DOCUMENTS_REFER_URL, GITHUB } from '@/const/url';
|
8
9
|
import { featureFlagsSelectors, useServerConfigStore } from '@/store/serverConfig';
|
@@ -12,7 +13,7 @@ const BottomActions = memo(() => {
|
|
12
13
|
const { hideGitHub, hideDocs } = useServerConfigStore(featureFlagsSelectors);
|
13
14
|
|
14
15
|
return (
|
15
|
-
|
16
|
+
<Flexbox gap={8}>
|
16
17
|
{!hideGitHub && (
|
17
18
|
<Link aria-label={'GitHub'} href={GITHUB} target={'_blank'}>
|
18
19
|
<ActionIcon icon={Github} placement={'right'} title={'GitHub'} />
|
@@ -23,7 +24,7 @@ const BottomActions = memo(() => {
|
|
23
24
|
<ActionIcon icon={Book} placement={'right'} title={t('document')} />
|
24
25
|
</Link>
|
25
26
|
)}
|
26
|
-
|
27
|
+
</Flexbox>
|
27
28
|
);
|
28
29
|
});
|
29
30
|
|
@@ -14,9 +14,9 @@ import { usePlatform } from '@/hooks/usePlatform';
|
|
14
14
|
import { featureFlagsSelectors, useServerConfigStore } from '@/store/serverConfig';
|
15
15
|
import { HotkeyScopeEnum } from '@/types/hotkey';
|
16
16
|
|
17
|
+
import TitleBar, { TITLE_BAR_HEIGHT } from './ElectronTitlebar';
|
17
18
|
import RegisterHotkeys from './RegisterHotkeys';
|
18
19
|
import SideBar from './SideBar';
|
19
|
-
import TitleBar, { TITLE_BAR_HEIGHT } from './Titlebar';
|
20
20
|
|
21
21
|
const CloudBanner = dynamic(() => import('@/features/AlertBanner/CloudBanner'));
|
22
22
|
|
@@ -7,6 +7,7 @@ import { isRtlLang } from 'rtl-detect';
|
|
7
7
|
|
8
8
|
import Analytics from '@/components/Analytics';
|
9
9
|
import { DEFAULT_LANG } from '@/const/locale';
|
10
|
+
import { isDesktop } from '@/const/version';
|
10
11
|
import PWAInstall from '@/features/PWAInstall';
|
11
12
|
import AuthProvider from '@/layout/AuthProvider';
|
12
13
|
import GlobalProvider from '@/layout/GlobalProvider';
|
@@ -78,7 +79,7 @@ export const generateViewport = async (props: DynamicLayoutProps): ResolvingView
|
|
78
79
|
|
79
80
|
export const generateStaticParams = () => {
|
80
81
|
const themes: ThemeAppearance[] = ['dark', 'light'];
|
81
|
-
const mobileOptions = [true, false];
|
82
|
+
const mobileOptions = isDesktop ? [false] : [true, false];
|
82
83
|
// only static for serveral page, other go to dynamtic
|
83
84
|
const staticLocales: Locales[] = [DEFAULT_LANG, 'zh-CN'];
|
84
85
|
|
@@ -0,0 +1,65 @@
|
|
1
|
+
import { createStyles } from 'antd-style';
|
2
|
+
import React from 'react';
|
3
|
+
import { Flexbox } from 'react-layout-kit';
|
4
|
+
|
5
|
+
import FileIcon from '@/components/FileIcon';
|
6
|
+
import { localFileService } from '@/services/electron/localFileService';
|
7
|
+
|
8
|
+
const useStyles = createStyles(({ css, token }) => ({
|
9
|
+
container: css`
|
10
|
+
cursor: pointer;
|
11
|
+
|
12
|
+
padding-block: 2px;
|
13
|
+
padding-inline: 4px 8px;
|
14
|
+
border-radius: 4px;
|
15
|
+
|
16
|
+
color: ${token.colorTextSecondary};
|
17
|
+
|
18
|
+
:hover {
|
19
|
+
color: ${token.colorText};
|
20
|
+
background: ${token.colorFillTertiary};
|
21
|
+
}
|
22
|
+
`,
|
23
|
+
title: css`
|
24
|
+
overflow: hidden;
|
25
|
+
display: block;
|
26
|
+
|
27
|
+
line-height: 20px;
|
28
|
+
color: inherit;
|
29
|
+
text-overflow: ellipsis;
|
30
|
+
white-space: nowrap;
|
31
|
+
`,
|
32
|
+
}));
|
33
|
+
|
34
|
+
const LocalFile = ({
|
35
|
+
name,
|
36
|
+
path,
|
37
|
+
isDirectory,
|
38
|
+
}: {
|
39
|
+
isDirectory: boolean;
|
40
|
+
name: string;
|
41
|
+
path: string;
|
42
|
+
}) => {
|
43
|
+
const { styles } = useStyles();
|
44
|
+
const handleClick = () => {
|
45
|
+
localFileService.openLocalFileOrFolder(path, isDirectory);
|
46
|
+
};
|
47
|
+
|
48
|
+
return (
|
49
|
+
<Flexbox
|
50
|
+
align={'center'}
|
51
|
+
className={styles.container}
|
52
|
+
gap={4}
|
53
|
+
horizontal
|
54
|
+
onClick={handleClick}
|
55
|
+
style={{ display: 'inline-flex', verticalAlign: 'middle' }}
|
56
|
+
>
|
57
|
+
<FileIcon fileName={name} isDirectory={isDirectory} size={22} variant={'pure'} />
|
58
|
+
<Flexbox align={'baseline'} gap={4} horizontal style={{ overflow: 'hidden', width: '100%' }}>
|
59
|
+
<div className={styles.title}>{name}</div>
|
60
|
+
</Flexbox>
|
61
|
+
</Flexbox>
|
62
|
+
);
|
63
|
+
};
|
64
|
+
|
65
|
+
export default LocalFile;
|
@@ -0,0 +1,29 @@
|
|
1
|
+
import isEqual from 'fast-deep-equal';
|
2
|
+
import React, { memo } from 'react';
|
3
|
+
|
4
|
+
import { MarkdownElementProps } from '../../type';
|
5
|
+
import LocalFile from './LocalFile';
|
6
|
+
|
7
|
+
interface LocalFileProps {
|
8
|
+
isDirectory: boolean;
|
9
|
+
name: string;
|
10
|
+
path: string;
|
11
|
+
}
|
12
|
+
|
13
|
+
const Render = memo<MarkdownElementProps<LocalFileProps>>(({ node }) => {
|
14
|
+
// 从 node.properties 中提取属性
|
15
|
+
const { name, path, isDirectory } = node?.properties || {};
|
16
|
+
|
17
|
+
if (!name || !path) {
|
18
|
+
// 如果缺少必要属性,可以选择渲染错误提示或 null
|
19
|
+
console.error('LocalFile Render component missing required properties:', node?.properties);
|
20
|
+
return null; // 或者返回一个错误占位符
|
21
|
+
}
|
22
|
+
|
23
|
+
// isDirectory 属性可能为 true (来自插件) 或 undefined,我们需要确保它是 boolean
|
24
|
+
const isDir = isDirectory === true;
|
25
|
+
|
26
|
+
return <LocalFile isDirectory={isDir} name={name} path={path} />;
|
27
|
+
}, isEqual);
|
28
|
+
|
29
|
+
export default Render;
|
@@ -0,0 +1,16 @@
|
|
1
|
+
import { FC } from 'react';
|
2
|
+
|
3
|
+
import { createRemarkSelfClosingTagPlugin } from '../remarkPlugins/createRemarkSelfClosingTagPlugin';
|
4
|
+
import { MarkdownElement, MarkdownElementProps } from '../type';
|
5
|
+
import RenderComponent from './Render';
|
6
|
+
|
7
|
+
// 定义此元素的标签名
|
8
|
+
const tag = 'localFile';
|
9
|
+
|
10
|
+
const LocalFileElement: MarkdownElement = {
|
11
|
+
Component: RenderComponent as FC<MarkdownElementProps>,
|
12
|
+
remarkPlugin: createRemarkSelfClosingTagPlugin(tag),
|
13
|
+
tag,
|
14
|
+
};
|
15
|
+
|
16
|
+
export default LocalFileElement;
|
@@ -1,6 +1,12 @@
|
|
1
1
|
import LobeArtifact from './LobeArtifact';
|
2
2
|
import LobeThinking from './LobeThinking';
|
3
|
+
import LocalFile from './LocalFile';
|
3
4
|
import Thinking from './Thinking';
|
4
5
|
import { MarkdownElement } from './type';
|
5
6
|
|
6
|
-
export const markdownElements: MarkdownElement[] = [
|
7
|
+
export const markdownElements: MarkdownElement[] = [
|
8
|
+
Thinking,
|
9
|
+
LobeArtifact,
|
10
|
+
LobeThinking,
|
11
|
+
LocalFile,
|
12
|
+
];
|