@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,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;