@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,222 @@
|
|
1
|
+
import { Input } from '@lobehub/ui';
|
2
|
+
import { LobeHub } from '@lobehub/ui/brand';
|
3
|
+
import { Button } from 'antd';
|
4
|
+
import { createStyles } from 'antd-style';
|
5
|
+
import { ComputerIcon, Server } from 'lucide-react';
|
6
|
+
import { memo, useCallback, useState } from 'react';
|
7
|
+
import { useTranslation } from 'react-i18next';
|
8
|
+
import { Center, Flexbox } from 'react-layout-kit';
|
9
|
+
|
10
|
+
import { useElectronStore } from '@/store/electron';
|
11
|
+
|
12
|
+
import { AccessOption, Option } from './Option';
|
13
|
+
|
14
|
+
const useStyles = createStyles(({ token, css }) => {
|
15
|
+
return {
|
16
|
+
cardGroup: css`
|
17
|
+
width: 400px; /* Increased width */
|
18
|
+
`,
|
19
|
+
container: css`
|
20
|
+
overflow-y: auto;
|
21
|
+
|
22
|
+
width: 100%;
|
23
|
+
height: 100%;
|
24
|
+
padding-block: 0 40px;
|
25
|
+
padding-inline: 24px; /* Increased top padding */
|
26
|
+
`,
|
27
|
+
continueButton: css`
|
28
|
+
width: 100%;
|
29
|
+
margin-block-start: 40px;
|
30
|
+
`,
|
31
|
+
groupTitle: css`
|
32
|
+
padding-inline-start: 4px; /* Align with card padding */
|
33
|
+
font-size: 16px;
|
34
|
+
font-weight: 500;
|
35
|
+
color: ${token.colorTextSecondary};
|
36
|
+
`,
|
37
|
+
header: css`
|
38
|
+
text-align: center;
|
39
|
+
`,
|
40
|
+
inputError: css`
|
41
|
+
margin-block-start: 8px;
|
42
|
+
font-size: 12px;
|
43
|
+
color: ${token.colorError};
|
44
|
+
`,
|
45
|
+
modal: css`
|
46
|
+
.ant-drawer-close {
|
47
|
+
position: absolute;
|
48
|
+
inset-block-start: 8px;
|
49
|
+
inset-inline-end: 0;
|
50
|
+
}
|
51
|
+
`,
|
52
|
+
selfHostedInput: css`
|
53
|
+
margin-block-start: 12px;
|
54
|
+
`,
|
55
|
+
selfHostedText: css`
|
56
|
+
cursor: pointer;
|
57
|
+
font-size: 14px;
|
58
|
+
color: ${token.colorTextTertiary};
|
59
|
+
|
60
|
+
:hover {
|
61
|
+
color: ${token.colorTextSecondary};
|
62
|
+
}
|
63
|
+
`,
|
64
|
+
title: css`
|
65
|
+
margin-block: 16px 48px; /* Increased Spacing below title */
|
66
|
+
font-size: 24px; /* Increased font size */
|
67
|
+
font-weight: 600;
|
68
|
+
color: ${token.colorTextHeading};
|
69
|
+
`,
|
70
|
+
};
|
71
|
+
});
|
72
|
+
|
73
|
+
interface ConnectionModeProps {
|
74
|
+
setIsOpen: (open: boolean) => void;
|
75
|
+
setWaiting: (waiting: boolean) => void;
|
76
|
+
}
|
77
|
+
|
78
|
+
const ConnectionMode = memo<ConnectionModeProps>(({ setIsOpen, setWaiting }) => {
|
79
|
+
const { styles } = useStyles();
|
80
|
+
const { t } = useTranslation(['electron', 'common']);
|
81
|
+
const [selectedOption, setSelectedOption] = useState<AccessOption>();
|
82
|
+
const [selfHostedUrl, setSelfHostedUrl] = useState('');
|
83
|
+
const [urlError, setUrlError] = useState<string | undefined>();
|
84
|
+
|
85
|
+
const connect = useElectronStore((s) => s.connectRemoteServer);
|
86
|
+
const disconnect = useElectronStore((s) => s.disconnectRemoteServer);
|
87
|
+
|
88
|
+
const validateUrl = useCallback((url: string) => {
|
89
|
+
if (!url) {
|
90
|
+
return t('remoteServer.urlRequired');
|
91
|
+
}
|
92
|
+
try {
|
93
|
+
new URL(url);
|
94
|
+
if (!url.startsWith('http://') && !url.startsWith('https://')) {
|
95
|
+
throw new Error('Invalid protocol');
|
96
|
+
}
|
97
|
+
return undefined;
|
98
|
+
} catch {
|
99
|
+
return t('remoteServer.invalidUrl');
|
100
|
+
}
|
101
|
+
}, []);
|
102
|
+
|
103
|
+
const handleSelectOption = (option: AccessOption) => {
|
104
|
+
setSelectedOption(option);
|
105
|
+
if (option !== 'self-hosted') {
|
106
|
+
setUrlError(undefined);
|
107
|
+
} else {
|
108
|
+
setUrlError(validateUrl(selfHostedUrl));
|
109
|
+
}
|
110
|
+
};
|
111
|
+
|
112
|
+
const handleContinue = async () => {
|
113
|
+
if (selectedOption === 'self-hosted') {
|
114
|
+
const error = validateUrl(selfHostedUrl);
|
115
|
+
setUrlError(error);
|
116
|
+
if (error) {
|
117
|
+
return;
|
118
|
+
}
|
119
|
+
}
|
120
|
+
|
121
|
+
if (selectedOption === 'local') {
|
122
|
+
await disconnect();
|
123
|
+
setIsOpen(false);
|
124
|
+
return;
|
125
|
+
}
|
126
|
+
|
127
|
+
// try to connect
|
128
|
+
setWaiting(true);
|
129
|
+
await connect(
|
130
|
+
selectedOption === 'self-hosted'
|
131
|
+
? { isSelfHosted: true, serverUrl: selfHostedUrl }
|
132
|
+
: { isSelfHosted: false },
|
133
|
+
);
|
134
|
+
};
|
135
|
+
|
136
|
+
return (
|
137
|
+
<Center className={styles.container}>
|
138
|
+
<Flexbox align={'center'} gap={0}>
|
139
|
+
<h1 className={styles.title}>{t('sync.mode.title')}</h1>
|
140
|
+
</Flexbox>
|
141
|
+
|
142
|
+
<Flexbox className={styles.cardGroup} gap={24}>
|
143
|
+
<Flexbox gap={16}>
|
144
|
+
<Flexbox align="center" horizontal justify="space-between">
|
145
|
+
<div className={styles.groupTitle}>{t('sync.mode.cloudSync')}</div>
|
146
|
+
<div
|
147
|
+
className={styles.selfHostedText}
|
148
|
+
onClick={() => handleSelectOption('self-hosted')}
|
149
|
+
>
|
150
|
+
{t('sync.mode.useSelfHosted')}
|
151
|
+
</div>
|
152
|
+
</Flexbox>
|
153
|
+
<Option
|
154
|
+
description={t('sync.lobehubCloud.description')}
|
155
|
+
icon={LobeHub}
|
156
|
+
isSelected={selectedOption === 'cloud'}
|
157
|
+
label={t('sync.lobehubCloud.title')}
|
158
|
+
onClick={handleSelectOption}
|
159
|
+
value="cloud"
|
160
|
+
/>
|
161
|
+
{selectedOption === 'self-hosted' && (
|
162
|
+
<Option
|
163
|
+
description={t('sync.selfHosted.description')}
|
164
|
+
icon={Server}
|
165
|
+
isSelected={selectedOption === 'self-hosted'}
|
166
|
+
label={t('sync.selfHosted.title')}
|
167
|
+
onClick={handleSelectOption}
|
168
|
+
value="self-hosted"
|
169
|
+
>
|
170
|
+
{selectedOption === 'self-hosted' && (
|
171
|
+
<>
|
172
|
+
<Input
|
173
|
+
autoFocus
|
174
|
+
className={styles.selfHostedInput}
|
175
|
+
onChange={(e) => {
|
176
|
+
const newUrl = e.target.value;
|
177
|
+
setSelfHostedUrl(newUrl);
|
178
|
+
setUrlError(validateUrl(newUrl));
|
179
|
+
}}
|
180
|
+
onClick={(e) => e.stopPropagation()}
|
181
|
+
placeholder="https://your-lobechat.com"
|
182
|
+
status={urlError ? 'error' : undefined}
|
183
|
+
value={selfHostedUrl}
|
184
|
+
/>
|
185
|
+
{urlError && <div className={styles.inputError}>{urlError}</div>}
|
186
|
+
</>
|
187
|
+
)}
|
188
|
+
</Option>
|
189
|
+
)}
|
190
|
+
</Flexbox>
|
191
|
+
<Flexbox>
|
192
|
+
<div className={styles.groupTitle} style={{ marginBottom: 12 }}>
|
193
|
+
{t('sync.mode.localStorage')}
|
194
|
+
</div>
|
195
|
+
<Option
|
196
|
+
description={t('sync.local.description')}
|
197
|
+
icon={ComputerIcon}
|
198
|
+
isSelected={selectedOption === 'local'}
|
199
|
+
label={t('sync.local.title')}
|
200
|
+
onClick={handleSelectOption}
|
201
|
+
value="local"
|
202
|
+
/>
|
203
|
+
</Flexbox>
|
204
|
+
</Flexbox>
|
205
|
+
|
206
|
+
<Button
|
207
|
+
className={styles.continueButton}
|
208
|
+
disabled={
|
209
|
+
!selectedOption || (selectedOption === 'self-hosted' && (!!urlError || !selfHostedUrl))
|
210
|
+
}
|
211
|
+
onClick={handleContinue}
|
212
|
+
size="large"
|
213
|
+
style={{ maxWidth: 400 }}
|
214
|
+
type="primary"
|
215
|
+
>
|
216
|
+
{selectedOption === 'local' ? t('save', { ns: 'common' }) : t('sync.continue')}
|
217
|
+
</Button>
|
218
|
+
</Center>
|
219
|
+
);
|
220
|
+
});
|
221
|
+
|
222
|
+
export default ConnectionMode;
|
@@ -0,0 +1,104 @@
|
|
1
|
+
import { CheckCircleFilled } from '@ant-design/icons';
|
2
|
+
import { createStyles } from 'antd-style';
|
3
|
+
import { ComponentType, ReactNode } from 'react';
|
4
|
+
import { Center, Flexbox } from 'react-layout-kit';
|
5
|
+
|
6
|
+
const useStyles = createStyles(({ token, css }) => ({
|
7
|
+
checked: css`
|
8
|
+
position: relative;
|
9
|
+
border: 1px solid ${token.colorPrimary};
|
10
|
+
`,
|
11
|
+
description: css`
|
12
|
+
margin-block-start: 4px; /* Adjust spacing */
|
13
|
+
font-size: 13px; /* Slightly larger description */
|
14
|
+
color: ${token.colorTextSecondary};
|
15
|
+
`,
|
16
|
+
iconWrapper: css`
|
17
|
+
margin-block-start: 2px;
|
18
|
+
padding: 0;
|
19
|
+
color: ${token.colorTextSecondary};
|
20
|
+
|
21
|
+
svg {
|
22
|
+
display: block;
|
23
|
+
font-size: 24px; /* Increased icon size */
|
24
|
+
stroke-width: 2; /* Ensure lucide icons look bolder */
|
25
|
+
}
|
26
|
+
`,
|
27
|
+
label: css`
|
28
|
+
font-size: 16px;
|
29
|
+
font-weight: 600; /* Bolder label */
|
30
|
+
color: ${token.colorText};
|
31
|
+
`,
|
32
|
+
optionCard: css`
|
33
|
+
cursor: pointer;
|
34
|
+
|
35
|
+
width: 100%;
|
36
|
+
padding: 16px;
|
37
|
+
border: 1px solid ${token.colorBorderSecondary}; /* Use secondary border */
|
38
|
+
border-radius: ${token.borderRadiusLG}px;
|
39
|
+
|
40
|
+
color: ${token.colorText};
|
41
|
+
|
42
|
+
background-color: ${token.colorBgContainer};
|
43
|
+
|
44
|
+
transition: all 0.2s ${token.motionEaseInOut};
|
45
|
+
|
46
|
+
:hover {
|
47
|
+
border-color: ${token.colorPrimary};
|
48
|
+
}
|
49
|
+
`,
|
50
|
+
optionInner: css`
|
51
|
+
display: flex;
|
52
|
+
gap: 16px;
|
53
|
+
align-items: flex-start;
|
54
|
+
justify-content: space-between;
|
55
|
+
`,
|
56
|
+
}));
|
57
|
+
|
58
|
+
// 定义选项类型
|
59
|
+
export type AccessOption = 'cloud' | 'self-hosted' | 'local';
|
60
|
+
|
61
|
+
export interface OptionProps {
|
62
|
+
children?: ReactNode;
|
63
|
+
description: string;
|
64
|
+
icon: ComponentType<any>;
|
65
|
+
isSelected: boolean;
|
66
|
+
label: string;
|
67
|
+
onClick: (value: AccessOption) => void;
|
68
|
+
value: AccessOption; // For self-hosted input
|
69
|
+
}
|
70
|
+
|
71
|
+
export const Option = ({
|
72
|
+
description,
|
73
|
+
icon: PrefixIcon,
|
74
|
+
label,
|
75
|
+
value,
|
76
|
+
isSelected,
|
77
|
+
onClick,
|
78
|
+
children,
|
79
|
+
}: OptionProps) => {
|
80
|
+
const { styles, cx } = useStyles();
|
81
|
+
|
82
|
+
return (
|
83
|
+
<Flexbox
|
84
|
+
className={cx(styles.optionCard, isSelected && styles.checked)}
|
85
|
+
direction="vertical"
|
86
|
+
key={value}
|
87
|
+
onClick={() => onClick(value)}
|
88
|
+
>
|
89
|
+
<div className={styles.optionInner}>
|
90
|
+
<Flexbox gap={16} horizontal>
|
91
|
+
<Center className={styles.iconWrapper}>
|
92
|
+
<PrefixIcon />
|
93
|
+
</Center>
|
94
|
+
<Flexbox gap={8}>
|
95
|
+
<div className={styles.label}>{label}</div>
|
96
|
+
<div className={styles.description}>{description}</div>
|
97
|
+
</Flexbox>
|
98
|
+
</Flexbox>
|
99
|
+
{isSelected && <CheckCircleFilled style={{ fontSize: 16 }} />}
|
100
|
+
</div>
|
101
|
+
{children}
|
102
|
+
</Flexbox>
|
103
|
+
);
|
104
|
+
};
|
@@ -0,0 +1,42 @@
|
|
1
|
+
import { ActionIcon } from '@lobehub/ui';
|
2
|
+
import { Loader, Wifi, WifiOffIcon } from 'lucide-react';
|
3
|
+
import { memo } from 'react';
|
4
|
+
import { useTranslation } from 'react-i18next';
|
5
|
+
|
6
|
+
import { useElectronStore } from '@/store/electron';
|
7
|
+
import { electronSyncSelectors } from '@/store/electron/selectors';
|
8
|
+
|
9
|
+
interface SyncProps {
|
10
|
+
onClick: () => void;
|
11
|
+
}
|
12
|
+
const Sync = memo<SyncProps>(({ onClick }) => {
|
13
|
+
const { t } = useTranslation('electron');
|
14
|
+
|
15
|
+
const [isIniting, isSyncActive, useRemoteServerConfig] = useElectronStore((s) => [
|
16
|
+
!s.isInitRemoteServerConfig,
|
17
|
+
electronSyncSelectors.isSyncActive(s),
|
18
|
+
s.useRemoteServerConfig,
|
19
|
+
]);
|
20
|
+
|
21
|
+
// 使用useSWR获取远程服务器配置
|
22
|
+
useRemoteServerConfig();
|
23
|
+
|
24
|
+
return (
|
25
|
+
<ActionIcon
|
26
|
+
icon={isIniting ? Loader : isSyncActive ? Wifi : WifiOffIcon}
|
27
|
+
loading={isIniting}
|
28
|
+
onClick={onClick}
|
29
|
+
placement={'bottomRight'}
|
30
|
+
size="small"
|
31
|
+
title={
|
32
|
+
isIniting
|
33
|
+
? t('sync.isIniting')
|
34
|
+
: isSyncActive
|
35
|
+
? t('sync.inCloud')
|
36
|
+
: t('sync.inLocalStorage')
|
37
|
+
}
|
38
|
+
/>
|
39
|
+
);
|
40
|
+
});
|
41
|
+
|
42
|
+
export default Sync;
|
@@ -0,0 +1,203 @@
|
|
1
|
+
'use client';
|
2
|
+
|
3
|
+
import { useWatchBroadcast } from '@lobechat/electron-client-ipc';
|
4
|
+
import { Icon } from '@lobehub/ui';
|
5
|
+
import { Button, Typography } from 'antd';
|
6
|
+
import { createStyles, cx, keyframes } from 'antd-style';
|
7
|
+
import { WifiIcon } from 'lucide-react';
|
8
|
+
import { memo } from 'react';
|
9
|
+
import { useTranslation } from 'react-i18next';
|
10
|
+
|
11
|
+
import { useElectronStore } from '@/store/electron';
|
12
|
+
|
13
|
+
const { Text, Title } = Typography;
|
14
|
+
|
15
|
+
const airdropPulse = keyframes`
|
16
|
+
0% {
|
17
|
+
transform: translate(-50%, -50%) scale(0.8);
|
18
|
+
opacity: 0.5;
|
19
|
+
}
|
20
|
+
100% {
|
21
|
+
transform: translate(-50%, -50%) scale(2.5);
|
22
|
+
opacity: 0;
|
23
|
+
}
|
24
|
+
`;
|
25
|
+
|
26
|
+
const useStyles = createStyles(({ css, token }) => ({
|
27
|
+
container: css`
|
28
|
+
overflow: hidden;
|
29
|
+
display: flex;
|
30
|
+
flex-direction: column;
|
31
|
+
align-items: center;
|
32
|
+
justify-content: center;
|
33
|
+
|
34
|
+
min-height: 100vh;
|
35
|
+
|
36
|
+
color: ${token.colorTextBase};
|
37
|
+
|
38
|
+
background-color: ${token.colorBgContainer};
|
39
|
+
`,
|
40
|
+
|
41
|
+
content: css`
|
42
|
+
z-index: 10;
|
43
|
+
display: flex;
|
44
|
+
flex-direction: column;
|
45
|
+
align-items: center;
|
46
|
+
`,
|
47
|
+
|
48
|
+
description: css`
|
49
|
+
margin-block-end: ${token.marginXL}px !important;
|
50
|
+
color: ${token.colorTextSecondary} !important;
|
51
|
+
`,
|
52
|
+
|
53
|
+
helpLink: css`
|
54
|
+
margin-inline-start: ${token.marginXXS}px;
|
55
|
+
color: ${token.colorTextSecondary};
|
56
|
+
text-decoration: underline;
|
57
|
+
text-underline-offset: 2px;
|
58
|
+
|
59
|
+
&:hover {
|
60
|
+
color: ${token.colorText};
|
61
|
+
}
|
62
|
+
`,
|
63
|
+
|
64
|
+
helpText: css`
|
65
|
+
margin-block-start: ${token.marginLG}px;
|
66
|
+
font-size: ${token.fontSizeSM}px;
|
67
|
+
color: ${token.colorTextTertiary};
|
68
|
+
`,
|
69
|
+
// 新增:图标和脉冲动画的容器
|
70
|
+
iconContainer: css`
|
71
|
+
position: relative;
|
72
|
+
|
73
|
+
display: flex;
|
74
|
+
align-items: center;
|
75
|
+
justify-content: center;
|
76
|
+
|
77
|
+
width: 160px;
|
78
|
+
height: 160px;
|
79
|
+
margin-block-end: ${token.marginXL}px;
|
80
|
+
`,
|
81
|
+
|
82
|
+
// 新增:不同延迟的脉冲动画
|
83
|
+
pulse1: css`
|
84
|
+
animation: ${airdropPulse} 3s ease-out infinite;
|
85
|
+
`,
|
86
|
+
|
87
|
+
pulse2: css`
|
88
|
+
animation: ${airdropPulse} 3s ease-out 1.2s infinite;
|
89
|
+
`,
|
90
|
+
|
91
|
+
pulse3: css`
|
92
|
+
animation: ${airdropPulse} 3s ease-out 1.8s infinite;
|
93
|
+
`,
|
94
|
+
// 新增:基础脉冲样式
|
95
|
+
pulseBase: css`
|
96
|
+
pointer-events: none;
|
97
|
+
content: '';
|
98
|
+
|
99
|
+
position: absolute;
|
100
|
+
inset-block-start: 50%;
|
101
|
+
inset-inline-start: 50%;
|
102
|
+
transform: translate(-50%, -50%);
|
103
|
+
|
104
|
+
width: 100px;
|
105
|
+
height: 100px;
|
106
|
+
border-radius: 50%;
|
107
|
+
|
108
|
+
opacity: 0;
|
109
|
+
background-color: ${token.colorPrimaryBgHover};
|
110
|
+
`,
|
111
|
+
|
112
|
+
// 新增:Radar 图标样式
|
113
|
+
radarIcon: css`
|
114
|
+
z-index: 1;
|
115
|
+
color: ${token.colorPrimary};
|
116
|
+
`,
|
117
|
+
|
118
|
+
ring1: css`
|
119
|
+
width: 80px;
|
120
|
+
height: 80px;
|
121
|
+
border: 1px solid ${token.colorText};
|
122
|
+
`,
|
123
|
+
|
124
|
+
ring2: css`
|
125
|
+
width: 120px;
|
126
|
+
height: 120px;
|
127
|
+
border: 1px solid ${token.colorTextQuaternary};
|
128
|
+
`,
|
129
|
+
|
130
|
+
ring3: css`
|
131
|
+
width: 160px;
|
132
|
+
height: 160px;
|
133
|
+
border: 1px solid ${token.colorFillSecondary};
|
134
|
+
`,
|
135
|
+
|
136
|
+
// 新增:星环基础样式
|
137
|
+
ringBase: css`
|
138
|
+
pointer-events: none;
|
139
|
+
|
140
|
+
position: absolute;
|
141
|
+
inset-block-start: 50%;
|
142
|
+
inset-inline-start: 50%;
|
143
|
+
transform: translate(-50%, -50%);
|
144
|
+
|
145
|
+
border-radius: 50%;
|
146
|
+
`,
|
147
|
+
title: css`
|
148
|
+
margin-block-end: ${token.marginSM}px !important;
|
149
|
+
color: ${token.colorText} !important;
|
150
|
+
`,
|
151
|
+
}));
|
152
|
+
|
153
|
+
interface WaitingOAuthProps {
|
154
|
+
setIsOpen: (open: boolean) => void;
|
155
|
+
setWaiting: (waiting: boolean) => void;
|
156
|
+
}
|
157
|
+
const WaitingOAuth = memo<WaitingOAuthProps>(({ setWaiting, setIsOpen }) => {
|
158
|
+
const { styles } = useStyles();
|
159
|
+
const { t } = useTranslation('electron'); // 指定 namespace 为 electron
|
160
|
+
const [disconnect, refreshServerConfig] = useElectronStore((s) => [
|
161
|
+
s.disconnectRemoteServer,
|
162
|
+
s.refreshServerConfig,
|
163
|
+
]);
|
164
|
+
|
165
|
+
const handleCancel = async () => {
|
166
|
+
await disconnect();
|
167
|
+
setWaiting(false);
|
168
|
+
};
|
169
|
+
|
170
|
+
useWatchBroadcast('authorizationSuccessful', async () => {
|
171
|
+
setIsOpen(false);
|
172
|
+
setWaiting(false);
|
173
|
+
await refreshServerConfig();
|
174
|
+
});
|
175
|
+
|
176
|
+
return (
|
177
|
+
<div className={styles.container}>
|
178
|
+
<div className={styles.content}>
|
179
|
+
{/* 更新为新的图标和脉冲动画结构 */}
|
180
|
+
<div className={styles.iconContainer}>
|
181
|
+
{/* 新增:星环 */}
|
182
|
+
<div className={cx(styles.ringBase, styles.ring1)} />
|
183
|
+
<div className={cx(styles.ringBase, styles.ring2)} />
|
184
|
+
<div className={cx(styles.ringBase, styles.ring3)} />
|
185
|
+
{/* 脉冲 */}
|
186
|
+
<div className={cx(styles.pulseBase, styles.pulse1)} />
|
187
|
+
<div className={cx(styles.pulseBase, styles.pulse2)} />
|
188
|
+
<div className={cx(styles.pulseBase, styles.pulse3)} />
|
189
|
+
|
190
|
+
<Icon className={styles.radarIcon} icon={WifiIcon} size={{ fontSize: 40 }} />
|
191
|
+
</div>
|
192
|
+
<Title className={styles.title} level={4}>
|
193
|
+
{t('waitingOAuth.title')}
|
194
|
+
</Title>
|
195
|
+
<Text className={styles.description}>{t('waitingOAuth.description')}</Text>
|
196
|
+
<Button onClick={handleCancel}>{t('waitingOAuth.cancel')}</Button>{' '}
|
197
|
+
<Text className={styles.helpText}>{t('waitingOAuth.helpText')}</Text>
|
198
|
+
</div>
|
199
|
+
</div>
|
200
|
+
);
|
201
|
+
});
|
202
|
+
|
203
|
+
export default WaitingOAuth;
|
@@ -0,0 +1,57 @@
|
|
1
|
+
import { Drawer } from 'antd';
|
2
|
+
import { createStyles } from 'antd-style';
|
3
|
+
import { useState } from 'react';
|
4
|
+
|
5
|
+
import Mode from './Mode';
|
6
|
+
import Sync from './Sync';
|
7
|
+
import WaitingOAuth from './Waiting';
|
8
|
+
|
9
|
+
const useStyles = createStyles(({ css }) => {
|
10
|
+
return {
|
11
|
+
modal: css`
|
12
|
+
.ant-drawer-close {
|
13
|
+
position: absolute;
|
14
|
+
inset-block-start: 8px;
|
15
|
+
inset-inline-end: 0;
|
16
|
+
}
|
17
|
+
`,
|
18
|
+
};
|
19
|
+
});
|
20
|
+
|
21
|
+
const Connection = () => {
|
22
|
+
const { styles, theme } = useStyles();
|
23
|
+
|
24
|
+
const [isOpen, setIsOpen] = useState(false);
|
25
|
+
const [isWaiting, setWaiting] = useState(false);
|
26
|
+
|
27
|
+
return (
|
28
|
+
<>
|
29
|
+
<Sync
|
30
|
+
onClick={() => {
|
31
|
+
setIsOpen(true);
|
32
|
+
}}
|
33
|
+
/>
|
34
|
+
<Drawer
|
35
|
+
classNames={{ header: styles.modal }}
|
36
|
+
height={'100vh'}
|
37
|
+
onClose={() => {
|
38
|
+
setIsOpen(false);
|
39
|
+
}}
|
40
|
+
open={isOpen}
|
41
|
+
placement={'top'}
|
42
|
+
style={{
|
43
|
+
background: theme.colorBgLayout,
|
44
|
+
}}
|
45
|
+
styles={{ body: { padding: 0 }, header: { padding: 0 } }}
|
46
|
+
>
|
47
|
+
{isWaiting ? (
|
48
|
+
<WaitingOAuth setIsOpen={setIsOpen} setWaiting={setWaiting} />
|
49
|
+
) : (
|
50
|
+
<Mode setIsOpen={setIsOpen} setWaiting={setWaiting} />
|
51
|
+
)}
|
52
|
+
</Drawer>
|
53
|
+
</>
|
54
|
+
);
|
55
|
+
};
|
56
|
+
|
57
|
+
export default Connection;
|