@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.
Files changed (96) hide show
  1. package/.cursor/rules/desktop-local-tools-implement.mdc +80 -0
  2. package/.env.desktop +2 -1
  3. package/.github/scripts/pr-comment.js +4 -9
  4. package/CHANGELOG.md +25 -0
  5. package/changelog/v1.json +9 -0
  6. package/locales/ar/electron.json +38 -2
  7. package/locales/ar/plugin.json +31 -31
  8. package/locales/bg-BG/electron.json +38 -2
  9. package/locales/bg-BG/plugin.json +31 -31
  10. package/locales/de-DE/electron.json +38 -2
  11. package/locales/de-DE/plugin.json +3 -8
  12. package/locales/en-US/electron.json +38 -2
  13. package/locales/en-US/plugin.json +3 -8
  14. package/locales/es-ES/electron.json +38 -2
  15. package/locales/es-ES/plugin.json +31 -31
  16. package/locales/fa-IR/electron.json +38 -2
  17. package/locales/fa-IR/plugin.json +31 -31
  18. package/locales/fr-FR/electron.json +38 -2
  19. package/locales/fr-FR/plugin.json +31 -31
  20. package/locales/it-IT/electron.json +38 -2
  21. package/locales/it-IT/plugin.json +31 -31
  22. package/locales/ja-JP/electron.json +38 -2
  23. package/locales/ja-JP/plugin.json +31 -31
  24. package/locales/ko-KR/electron.json +38 -2
  25. package/locales/ko-KR/plugin.json +3 -8
  26. package/locales/nl-NL/electron.json +38 -2
  27. package/locales/nl-NL/plugin.json +31 -31
  28. package/locales/pl-PL/electron.json +38 -2
  29. package/locales/pl-PL/plugin.json +3 -8
  30. package/locales/pt-BR/electron.json +38 -2
  31. package/locales/pt-BR/plugin.json +31 -31
  32. package/locales/ru-RU/electron.json +38 -2
  33. package/locales/ru-RU/plugin.json +31 -31
  34. package/locales/tr-TR/electron.json +38 -2
  35. package/locales/tr-TR/plugin.json +31 -31
  36. package/locales/vi-VN/electron.json +38 -2
  37. package/locales/vi-VN/plugin.json +3 -8
  38. package/locales/zh-CN/electron.json +38 -2
  39. package/locales/zh-CN/plugin.json +14 -9
  40. package/locales/zh-TW/electron.json +38 -2
  41. package/locales/zh-TW/plugin.json +31 -31
  42. package/package.json +1 -1
  43. package/packages/electron-client-ipc/src/events/update.ts +3 -3
  44. package/src/app/[variants]/(main)/_layout/Desktop/ElectronTitlebar/Connection/Mode.tsx +222 -0
  45. package/src/app/[variants]/(main)/_layout/Desktop/ElectronTitlebar/Connection/Option.tsx +104 -0
  46. package/src/app/[variants]/(main)/_layout/Desktop/ElectronTitlebar/Connection/Sync.tsx +42 -0
  47. package/src/app/[variants]/(main)/_layout/Desktop/ElectronTitlebar/Connection/Waiting.tsx +203 -0
  48. package/src/app/[variants]/(main)/_layout/Desktop/ElectronTitlebar/Connection/index.tsx +57 -0
  49. package/src/app/[variants]/(main)/_layout/Desktop/ElectronTitlebar/UpdateModal.tsx +242 -0
  50. package/src/app/[variants]/(main)/_layout/Desktop/ElectronTitlebar/UpdateNotification.tsx +193 -0
  51. package/src/app/[variants]/(main)/_layout/Desktop/{Titlebar.tsx → ElectronTitlebar/index.tsx} +15 -1
  52. package/src/app/[variants]/(main)/_layout/Desktop/SideBar/BottomActions.tsx +3 -2
  53. package/src/app/[variants]/(main)/_layout/Desktop/index.tsx +1 -1
  54. package/src/app/[variants]/layout.tsx +2 -1
  55. package/src/features/Conversation/components/MarkdownElements/LocalFile/Render/LocalFile.tsx +65 -0
  56. package/src/features/Conversation/components/MarkdownElements/LocalFile/Render/index.tsx +29 -0
  57. package/src/features/Conversation/components/MarkdownElements/LocalFile/index.ts +16 -0
  58. package/src/features/Conversation/components/MarkdownElements/index.ts +7 -1
  59. package/src/features/Conversation/components/MarkdownElements/remarkPlugins/__snapshots__/createRemarkSelfClosingTagPlugin.test.ts.snap +260 -0
  60. package/src/features/Conversation/components/MarkdownElements/remarkPlugins/createRemarkSelfClosingTagPlugin.test.ts +204 -0
  61. package/src/features/Conversation/components/MarkdownElements/remarkPlugins/createRemarkSelfClosingTagPlugin.ts +133 -0
  62. package/src/features/Conversation/components/MarkdownElements/type.ts +5 -1
  63. package/src/features/PluginDevModal/MCPManifestForm/ArgsInput.tsx +20 -0
  64. package/src/features/PluginDevModal/MCPManifestForm/MCPTypeSelect.tsx +176 -0
  65. package/src/features/PluginDevModal/{MCPManifestForm.tsx → MCPManifestForm/index.tsx} +92 -30
  66. package/src/libs/mcp/__tests__/__snapshots__/index.test.ts.snap +0 -56
  67. package/src/locales/default/electron.ts +38 -2
  68. package/src/locales/default/plugin.ts +14 -7
  69. package/src/server/modules/ElectronIPCClient/index.ts +36 -0
  70. package/src/server/routers/lambda/session.ts +2 -6
  71. package/src/server/routers/tools/mcp.ts +6 -0
  72. package/src/server/services/file/impls/index.ts +9 -1
  73. package/src/server/services/file/impls/local.test.ts +299 -0
  74. package/src/server/services/file/impls/local.ts +183 -0
  75. package/src/server/services/mcp/index.ts +19 -0
  76. package/src/services/aiModel/index.ts +5 -1
  77. package/src/services/aiProvider/index.ts +5 -1
  78. package/src/services/electron/autoUpdate.ts +4 -0
  79. package/src/services/file/index.ts +5 -1
  80. package/src/services/mcp.ts +13 -2
  81. package/src/services/message/index.ts +5 -1
  82. package/src/services/plugin/index.ts +5 -1
  83. package/src/services/session/index.ts +5 -1
  84. package/src/services/tableViewer/desktop.ts +15 -0
  85. package/src/services/tableViewer/index.ts +4 -1
  86. package/src/services/thread/index.ts +5 -1
  87. package/src/services/topic/index.ts +5 -1
  88. package/src/services/user/index.ts +5 -1
  89. package/src/store/electron/actions/app.ts +59 -0
  90. package/src/store/electron/actions/sync.ts +5 -1
  91. package/src/store/electron/initialState.ts +3 -1
  92. package/src/store/electron/store.ts +6 -1
  93. package/src/store/tool/slices/customPlugin/action.ts +16 -4
  94. package/src/utils/client/GlobalAgentContextManager.ts +85 -0
  95. package/src/utils/promptTemplate.test.ts +78 -0
  96. 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
+ };
@@ -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
- <div>{/* TODO */}</div>
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[] = [Thinking, LobeArtifact, LobeThinking];
7
+ export const markdownElements: MarkdownElement[] = [
8
+ Thinking,
9
+ LobeArtifact,
10
+ LobeThinking,
11
+ LocalFile,
12
+ ];