@lobehub/chat 1.97.17 → 1.98.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 (69) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/apps/desktop/package.json +8 -5
  3. package/apps/desktop/src/main/const/store.ts +12 -0
  4. package/apps/desktop/src/main/controllers/NetworkProxyCtr.ts +172 -0
  5. package/apps/desktop/src/main/controllers/__tests__/NetworkProxyCtr.test.ts +401 -0
  6. package/apps/desktop/src/main/core/Browser.ts +2 -0
  7. package/apps/desktop/src/main/modules/networkProxy/dispatcher.ts +116 -0
  8. package/apps/desktop/src/main/modules/networkProxy/index.ts +6 -0
  9. package/apps/desktop/src/main/modules/networkProxy/tester.ts +163 -0
  10. package/apps/desktop/src/main/modules/networkProxy/urlBuilder.ts +25 -0
  11. package/apps/desktop/src/main/modules/networkProxy/validator.ts +80 -0
  12. package/apps/desktop/src/main/types/store.ts +2 -1
  13. package/apps/desktop/src/main/utils/logger.ts +2 -1
  14. package/changelog/v1.json +18 -0
  15. package/locales/ar/electron.json +39 -0
  16. package/locales/ar/setting.json +1 -0
  17. package/locales/bg-BG/electron.json +39 -0
  18. package/locales/bg-BG/setting.json +1 -0
  19. package/locales/de-DE/electron.json +39 -0
  20. package/locales/de-DE/setting.json +1 -0
  21. package/locales/en-US/electron.json +39 -0
  22. package/locales/en-US/setting.json +1 -0
  23. package/locales/es-ES/electron.json +39 -0
  24. package/locales/es-ES/setting.json +1 -0
  25. package/locales/fa-IR/electron.json +39 -0
  26. package/locales/fa-IR/setting.json +1 -0
  27. package/locales/fr-FR/electron.json +39 -0
  28. package/locales/fr-FR/setting.json +1 -0
  29. package/locales/it-IT/electron.json +39 -0
  30. package/locales/it-IT/setting.json +1 -0
  31. package/locales/ja-JP/electron.json +39 -0
  32. package/locales/ja-JP/setting.json +1 -0
  33. package/locales/ko-KR/electron.json +39 -0
  34. package/locales/ko-KR/setting.json +1 -0
  35. package/locales/nl-NL/electron.json +39 -0
  36. package/locales/nl-NL/setting.json +1 -0
  37. package/locales/pl-PL/electron.json +39 -0
  38. package/locales/pl-PL/setting.json +1 -0
  39. package/locales/pt-BR/electron.json +39 -0
  40. package/locales/pt-BR/setting.json +1 -0
  41. package/locales/ru-RU/electron.json +39 -0
  42. package/locales/ru-RU/setting.json +1 -0
  43. package/locales/tr-TR/electron.json +39 -0
  44. package/locales/tr-TR/setting.json +1 -0
  45. package/locales/vi-VN/electron.json +39 -0
  46. package/locales/vi-VN/setting.json +1 -0
  47. package/locales/zh-CN/electron.json +39 -0
  48. package/locales/zh-CN/setting.json +1 -0
  49. package/locales/zh-TW/electron.json +39 -0
  50. package/locales/zh-TW/setting.json +1 -0
  51. package/package.json +3 -3
  52. package/packages/electron-client-ipc/src/events/index.ts +3 -1
  53. package/packages/electron-client-ipc/src/events/settings.ts +12 -0
  54. package/packages/electron-client-ipc/src/types/index.ts +1 -0
  55. package/packages/electron-client-ipc/src/types/proxy.ts +12 -0
  56. package/src/app/[variants]/(main)/discover/(list)/(home)/page.tsx +4 -4
  57. package/src/app/[variants]/(main)/settings/hooks/useCategory.tsx +11 -1
  58. package/src/app/[variants]/(main)/settings/proxy/features/ProxyForm.tsx +369 -0
  59. package/src/app/[variants]/(main)/settings/proxy/index.tsx +22 -0
  60. package/src/app/[variants]/(main)/settings/proxy/page.tsx +28 -0
  61. package/src/locales/default/electron.ts +39 -0
  62. package/src/locales/default/setting.ts +1 -0
  63. package/src/services/electron/settings.ts +33 -0
  64. package/src/store/electron/actions/settings.ts +55 -0
  65. package/src/store/electron/initialState.ts +12 -1
  66. package/src/store/electron/selectors/__tests__/desktopState.test.ts +3 -1
  67. package/src/store/electron/store.ts +4 -1
  68. package/src/store/global/initialState.ts +1 -0
  69. package/apps/desktop/scripts/pglite-server.ts +0 -14
@@ -0,0 +1,369 @@
1
+ 'use client';
2
+
3
+ import { NetworkProxySettings } from '@lobechat/electron-client-ipc';
4
+ import { Alert, Block, Text } from '@lobehub/ui';
5
+ import { App, Button, Divider, Form, Input, Radio, Skeleton, Space, Switch } from 'antd';
6
+ import isEqual from 'fast-deep-equal';
7
+ import { useCallback, useEffect, useState } from 'react';
8
+ import { useTranslation } from 'react-i18next';
9
+ import { Flexbox } from 'react-layout-kit';
10
+
11
+ import { desktopSettingsService } from '@/services/electron/settings';
12
+ import { useElectronStore } from '@/store/electron';
13
+
14
+ interface ProxyTestResult {
15
+ message?: string;
16
+ responseTime?: number;
17
+ success: boolean;
18
+ }
19
+
20
+ const ProxyForm = () => {
21
+ const { t } = useTranslation('electron');
22
+ const [form] = Form.useForm();
23
+ const { message } = App.useApp();
24
+ const [testUrl, setTestUrl] = useState('https://www.google.com');
25
+ const [isTesting, setIsTesting] = useState(false);
26
+ const [isSaving, setIsSaving] = useState(false);
27
+ const [testResult, setTestResult] = useState<ProxyTestResult | null>(null);
28
+ const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false);
29
+
30
+ const isEnableProxy = Form.useWatch('enableProxy', form);
31
+ const proxyRequireAuth = Form.useWatch('proxyRequireAuth', form);
32
+
33
+ const [setProxySettings, useGetProxySettings] = useElectronStore((s) => [
34
+ s.setProxySettings,
35
+ s.useGetProxySettings,
36
+ ]);
37
+ const { data: proxySettings, isLoading } = useGetProxySettings();
38
+
39
+ useEffect(() => {
40
+ if (proxySettings) {
41
+ form.setFieldsValue(proxySettings);
42
+ setHasUnsavedChanges(false);
43
+ }
44
+ }, [form, proxySettings]);
45
+
46
+ // 监听表单变化
47
+ const handleValuesChange = useCallback(() => {
48
+ setHasUnsavedChanges(true);
49
+ setTestResult(null); // 清除之前的测试结果
50
+ }, []);
51
+
52
+ const updateFormValue = (value: any) => {
53
+ const preValues = form.getFieldsValue();
54
+ form.setFieldsValue(value);
55
+ const newValues = form.getFieldsValue();
56
+ if (isEqual(newValues, preValues)) return;
57
+
58
+ handleValuesChange();
59
+ };
60
+
61
+ // 保存配置
62
+ const handleSave = useCallback(async () => {
63
+ try {
64
+ setIsSaving(true);
65
+ const values = await form.validateFields();
66
+ await setProxySettings(values);
67
+ setHasUnsavedChanges(false);
68
+ message.success(t('proxy.saveSuccess'));
69
+ } catch (error) {
70
+ if (error instanceof Error) {
71
+ message.error(t('proxy.saveFailed', { error: error.message }));
72
+ }
73
+ } finally {
74
+ setIsSaving(false);
75
+ }
76
+ }, [form, t, message]);
77
+
78
+ // 重置配置
79
+ const handleReset = useCallback(() => {
80
+ if (proxySettings) {
81
+ form.setFieldsValue(proxySettings);
82
+ setHasUnsavedChanges(false);
83
+ setTestResult(null);
84
+ }
85
+ }, [form, proxySettings]);
86
+
87
+ // 测试代理配置
88
+ const handleTest = useCallback(async () => {
89
+ try {
90
+ setIsTesting(true);
91
+ setTestResult(null);
92
+
93
+ // 验证表单并获取当前配置
94
+ const values = await form.validateFields();
95
+ const config: NetworkProxySettings = {
96
+ ...proxySettings,
97
+ ...values,
98
+ };
99
+
100
+ // 使用新的 testProxyConfig 方法测试用户正在配置的代理
101
+ const result = await desktopSettingsService.testProxyConfig(config, testUrl);
102
+
103
+ setTestResult(result);
104
+ } catch (error) {
105
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
106
+ const result: ProxyTestResult = {
107
+ message: errorMessage,
108
+ success: false,
109
+ };
110
+ setTestResult(result);
111
+ message.error(t('proxy.testFailed'));
112
+ } finally {
113
+ setIsTesting(false);
114
+ }
115
+ }, [proxySettings, testUrl]);
116
+
117
+ if (isLoading) return <Skeleton />;
118
+
119
+ return (
120
+ <Form
121
+ disabled={isSaving}
122
+ form={form}
123
+ layout="vertical"
124
+ onValuesChange={handleValuesChange}
125
+ requiredMark={false}
126
+ >
127
+ <Flexbox gap={24}>
128
+ {/* 基本代理设置 */}
129
+ <Block
130
+ paddingBlock={16}
131
+ paddingInline={24}
132
+ style={{ borderRadius: 12 }}
133
+ variant={'outlined'}
134
+ >
135
+ <Form.Item name="enableProxy" noStyle valuePropName="checked">
136
+ <Flexbox align={'center'} horizontal justify={'space-between'}>
137
+ <Flexbox>
138
+ <Text as={'h4'}>{t('proxy.enable')}</Text>
139
+ <Text type={'secondary'}>{t('proxy.enableDesc')}</Text>
140
+ </Flexbox>
141
+ <Switch
142
+ checked={isEnableProxy}
143
+ onChange={(checked) => {
144
+ updateFormValue({ enableProxy: checked });
145
+ }}
146
+ />
147
+ </Flexbox>
148
+ </Form.Item>
149
+ </Block>
150
+
151
+ {/* 认证设置 */}
152
+ <Block
153
+ paddingBlock={16}
154
+ paddingInline={24}
155
+ style={{ borderRadius: 12 }}
156
+ variant={'outlined'}
157
+ >
158
+ <Flexbox gap={24}>
159
+ <Flexbox>
160
+ <Text as={'h4'}>{t('proxy.basicSettings')}</Text>
161
+ <Text type={'secondary'}>{t('proxy.basicSettingsDesc')}</Text>
162
+ </Flexbox>
163
+ <Flexbox>
164
+ <Form.Item
165
+ dependencies={['enableProxy']}
166
+ label={t('proxy.type')}
167
+ name="proxyType"
168
+ rules={[
169
+ ({ getFieldValue }) => ({
170
+ message: t('proxy.validation.typeRequired'),
171
+ required: getFieldValue('enableProxy'),
172
+ }),
173
+ ]}
174
+ >
175
+ <Radio.Group disabled={!form.getFieldValue('enableProxy')}>
176
+ <Radio value="http">HTTP</Radio>
177
+ <Radio value="https">HTTPS</Radio>
178
+ <Radio value="socks5">SOCKS5</Radio>
179
+ </Radio.Group>
180
+ </Form.Item>
181
+
182
+ <Space.Compact style={{ width: '100%' }}>
183
+ <Form.Item
184
+ dependencies={['enableProxy']}
185
+ label={t('proxy.server')}
186
+ name="proxyServer"
187
+ rules={[
188
+ ({ getFieldValue }) => ({
189
+ message: t('proxy.validation.serverRequired'),
190
+ required: getFieldValue('enableProxy'),
191
+ }),
192
+ {
193
+ message: t('proxy.validation.serverInvalid'),
194
+ pattern:
195
+ /^((25[0-5]|2[0-4]\d|[01]?\d{1,2})\.){3}(25[0-5]|2[0-4]\d|[01]?\d{1,2})$|^[\dA-Za-z]([\dA-Za-z-]*[\dA-Za-z])?(\.[\dA-Za-z]([\dA-Za-z-]*[\dA-Za-z])?)*$/,
196
+ },
197
+ ]}
198
+ style={{ flex: 1, marginBottom: 0 }}
199
+ >
200
+ <Input disabled={!form.getFieldValue('enableProxy')} placeholder="127.0.0.1" />
201
+ </Form.Item>
202
+
203
+ <Form.Item
204
+ dependencies={['enableProxy']}
205
+ label={t('proxy.port')}
206
+ name="proxyPort"
207
+ rules={[
208
+ ({ getFieldValue }) => ({
209
+ message: t('proxy.validation.portRequired'),
210
+ required: getFieldValue('enableProxy'),
211
+ }),
212
+ {
213
+ message: t('proxy.validation.portInvalid'),
214
+ pattern:
215
+ /^([1-9]\d{0,3}|[1-5]\d{4}|6[0-4]\d{3}|65[0-4]\d{2}|655[0-2]\d|6553[0-5])$/,
216
+ },
217
+ ]}
218
+ style={{ marginBottom: 0, width: 120 }}
219
+ >
220
+ <Input disabled={!form.getFieldValue('enableProxy')} placeholder="7890" />
221
+ </Form.Item>
222
+ </Space.Compact>
223
+ </Flexbox>
224
+ <Divider size={'small'} />
225
+ <Flexbox gap={12}>
226
+ <Form.Item
227
+ dependencies={['enableProxy']}
228
+ name="proxyRequireAuth"
229
+ noStyle
230
+ valuePropName="checked"
231
+ >
232
+ <Flexbox align={'center'} horizontal justify={'space-between'}>
233
+ <Flexbox>
234
+ <Text as={'h5'}>{t('proxy.auth')}</Text>
235
+ <Text type={'secondary'}>{t('proxy.authDesc')}</Text>
236
+ </Flexbox>
237
+ <Switch
238
+ checked={proxyRequireAuth}
239
+ disabled={!isEnableProxy}
240
+ onChange={(checked) => {
241
+ updateFormValue({ proxyRequireAuth: checked });
242
+ }}
243
+ />
244
+ </Flexbox>
245
+ </Form.Item>
246
+
247
+ <Form.Item
248
+ dependencies={['proxyRequireAuth', 'enableProxy']}
249
+ label={t('proxy.username')}
250
+ name="proxyUsername"
251
+ rules={[
252
+ ({ getFieldValue }) => ({
253
+ message: t('proxy.validation.usernameRequired'),
254
+ required: getFieldValue('proxyRequireAuth') && getFieldValue('enableProxy'),
255
+ }),
256
+ ]}
257
+ style={{
258
+ display:
259
+ form.getFieldValue('proxyRequireAuth') && form.getFieldValue('enableProxy')
260
+ ? 'block'
261
+ : 'none',
262
+ }}
263
+ >
264
+ <Input placeholder={t('proxy.username_placeholder')} />
265
+ </Form.Item>
266
+
267
+ <Form.Item
268
+ dependencies={['proxyRequireAuth', 'enableProxy']}
269
+ label={t('proxy.password')}
270
+ name="proxyPassword"
271
+ rules={[
272
+ ({ getFieldValue }) => ({
273
+ message: t('proxy.validation.passwordRequired'),
274
+ required: getFieldValue('proxyRequireAuth') && getFieldValue('enableProxy'),
275
+ }),
276
+ ]}
277
+ style={{
278
+ display:
279
+ form.getFieldValue('proxyRequireAuth') && form.getFieldValue('enableProxy')
280
+ ? 'block'
281
+ : 'none',
282
+ }}
283
+ >
284
+ <Input.Password placeholder={t('proxy.password_placeholder')} />
285
+ </Form.Item>
286
+ </Flexbox>
287
+ </Flexbox>
288
+ </Block>
289
+
290
+ {/* 连接测试 */}
291
+
292
+ <Block
293
+ paddingBlock={16}
294
+ paddingInline={24}
295
+ style={{ borderRadius: 12 }}
296
+ variant={'outlined'}
297
+ >
298
+ <Flexbox gap={24}>
299
+ <Flexbox>
300
+ <Text as={'h4'}>{t('proxy.connectionTest')}</Text>
301
+ <Text type={'secondary'}>{t('proxy.testDescription')}</Text>
302
+ </Flexbox>
303
+ <Form.Item label={t('proxy.testUrl')}>
304
+ <Flexbox gap={8}>
305
+ <Space.Compact style={{ width: '100%' }}>
306
+ <Input
307
+ onChange={(e) => setTestUrl(e.target.value)}
308
+ placeholder={t('proxy.testUrlPlaceholder')}
309
+ style={{ flex: 1 }}
310
+ value={testUrl}
311
+ />
312
+ <Button loading={isTesting} onClick={handleTest} type="default">
313
+ {t('proxy.testButton')}
314
+ </Button>
315
+ </Space.Compact>
316
+ {/* 测试结果显示 */}
317
+ {!testResult ? null : testResult.success ? (
318
+ <Alert
319
+ closable
320
+ message={
321
+ <Flexbox align="center" gap={8} horizontal>
322
+ {t('proxy.testSuccessWithTime', { time: testResult.responseTime })}
323
+ </Flexbox>
324
+ }
325
+ type={'success'}
326
+ />
327
+ ) : (
328
+ <Alert
329
+ closable
330
+ message={
331
+ <Flexbox align="center" gap={8} horizontal>
332
+ {t('proxy.testFailed')}: {testResult.message}
333
+ </Flexbox>
334
+ }
335
+ type={'error'}
336
+ variant={'outlined'}
337
+ />
338
+ )}
339
+ </Flexbox>
340
+ </Form.Item>
341
+ </Flexbox>
342
+ </Block>
343
+ {/* 操作按钮 */}
344
+ <Space>
345
+ <Button
346
+ disabled={!hasUnsavedChanges}
347
+ loading={isSaving}
348
+ onClick={handleSave}
349
+ type="primary"
350
+ >
351
+ {t('proxy.saveButton')}
352
+ </Button>
353
+
354
+ <Button disabled={!hasUnsavedChanges || isSaving} onClick={handleReset}>
355
+ {t('proxy.resetButton')}
356
+ </Button>
357
+
358
+ {hasUnsavedChanges && (
359
+ <Text style={{ marginLeft: 8 }} type="warning">
360
+ {t('proxy.unsavedChanges')}
361
+ </Text>
362
+ )}
363
+ </Space>
364
+ </Flexbox>
365
+ </Form>
366
+ );
367
+ };
368
+
369
+ export default ProxyForm;
@@ -0,0 +1,22 @@
1
+ 'use client';
2
+
3
+ import { useTranslation } from 'react-i18next';
4
+
5
+ import PageTitle from '@/components/PageTitle';
6
+
7
+ import ProxyForm from './features/ProxyForm';
8
+
9
+ const ProxySettings = () => {
10
+ const { t } = useTranslation('setting');
11
+
12
+ return (
13
+ <div>
14
+ <PageTitle title={t('tab.proxy')} />
15
+ <ProxyForm />
16
+ </div>
17
+ );
18
+ };
19
+
20
+ ProxySettings.displayName = 'ProxySettings';
21
+
22
+ export default ProxySettings;
@@ -0,0 +1,28 @@
1
+ import { notFound } from 'next/navigation';
2
+
3
+ import { isDesktop } from '@/const/version';
4
+ import { metadataModule } from '@/server/metadata';
5
+ import { translation } from '@/server/translation';
6
+ import { DynamicLayoutProps } from '@/types/next';
7
+ import { RouteVariants } from '@/utils/server/routeVariants';
8
+
9
+ import Client from './index';
10
+
11
+ export const generateMetadata = async (props: DynamicLayoutProps) => {
12
+ const locale = await RouteVariants.getLocale(props);
13
+ const { t } = await translation('setting', locale);
14
+
15
+ return metadataModule.generate({
16
+ description: t('header.desc'),
17
+ title: t('tab.proxy'),
18
+ url: '/settings/proxy',
19
+ });
20
+ };
21
+
22
+ const Page = () => {
23
+ if (!isDesktop) return notFound();
24
+
25
+ return <Client />;
26
+ };
27
+
28
+ export default Page;
@@ -1,4 +1,43 @@
1
1
  const electron = {
2
+ proxy: {
3
+ auth: '需要认证',
4
+ authDesc: '如果代理服务器需要用户名和密码',
5
+ authSettings: '认证设置',
6
+ basicSettings: '代理设置',
7
+ basicSettingsDesc: '配置代理服务器的连接参数',
8
+ bypass: '不使用代理的地址',
9
+ connectionTest: '连接测试',
10
+ enable: '启用代理',
11
+ enableDesc: '开启后将通过代理服务器访问网络',
12
+ password: '密码',
13
+ password_placeholder: '请输入密码',
14
+ port: '端口',
15
+ resetButton: '重置',
16
+ saveButton: '保存',
17
+ saveFailed: '保存失败:{{error}}',
18
+ saveSuccess: '代理设置保存成功',
19
+ server: '服务器地址',
20
+ testButton: '测试连接',
21
+ testDescription: '使用当前代理配置测试连接,验证配置是否正常工作',
22
+ testFailed: '连接失败',
23
+ testSuccessWithTime: '测试连接成功,耗时 {{time}} ms',
24
+ testUrl: '测试地址',
25
+ testUrlPlaceholder: '请输入要测试的 URL',
26
+ testing: '正在测试连接...',
27
+ type: '代理类型',
28
+ unsavedChanges: '您有未保存的更改',
29
+ username: '用户名',
30
+ username_placeholder: '请输入用户名',
31
+ validation: {
32
+ passwordRequired: '启用认证时密码为必填项',
33
+ portInvalid: '端口必须是 1 到 65535 之间的数字',
34
+ portRequired: '启用代理时端口为必填项',
35
+ serverInvalid: '请输入有效的服务器地址(IP 或域名)',
36
+ serverRequired: '启用代理时服务器地址为必填项',
37
+ typeRequired: '启用代理时代理类型为必填项',
38
+ usernameRequired: '启用认证时用户名为必填项',
39
+ },
40
+ },
2
41
  remoteServer: {
3
42
  authError: '授权失败: {{error}}',
4
43
  authPending: '请在浏览器中完成授权',
@@ -524,6 +524,7 @@ export default {
524
524
  'hotkey': '快捷键',
525
525
  'llm': '语言模型',
526
526
  'provider': 'AI 服务商',
527
+ 'proxy': '网络代理',
527
528
  'storage': '数据存储',
528
529
  'sync': '云端同步',
529
530
  'system-agent': '系统助手',
@@ -0,0 +1,33 @@
1
+ import { NetworkProxySettings, dispatch } from '@lobechat/electron-client-ipc';
2
+
3
+ class DesktopSettingsService {
4
+ /**
5
+ * 获取远程服务器配置
6
+ */
7
+ getProxySettings = async () => {
8
+ return dispatch('getProxySettings');
9
+ };
10
+
11
+ /**
12
+ * 设置远程服务器配置
13
+ */
14
+ setSettings = async (data: Partial<NetworkProxySettings>) => {
15
+ return dispatch('setProxySettings', data);
16
+ };
17
+
18
+ /**
19
+ * 测试代理连接
20
+ */
21
+ testProxyConnection = async (url: string) => {
22
+ return dispatch('testProxyConnection', url);
23
+ };
24
+
25
+ /**
26
+ * 测试指定的代理配置
27
+ */
28
+ testProxyConfig = async (config: NetworkProxySettings, testUrl?: string) => {
29
+ return dispatch('testProxyConfig', { config, testUrl });
30
+ };
31
+ }
32
+
33
+ export const desktopSettingsService = new DesktopSettingsService();
@@ -0,0 +1,55 @@
1
+ import { NetworkProxySettings } from '@lobechat/electron-client-ipc';
2
+ import isEqual from 'fast-deep-equal';
3
+ import useSWR, { SWRResponse, mutate } from 'swr';
4
+ import type { StateCreator } from 'zustand/vanilla';
5
+
6
+ import { desktopSettingsService } from '@/services/electron/settings';
7
+
8
+ import type { ElectronStore } from '../store';
9
+
10
+ /**
11
+ * 设置操作
12
+ */
13
+ export interface ElectronSettingsAction {
14
+ refreshProxySettings: () => Promise<void>;
15
+ setProxySettings: (params: Partial<NetworkProxySettings>) => Promise<void>;
16
+ useGetProxySettings: () => SWRResponse;
17
+ }
18
+
19
+ const ELECTRON_PROXY_SETTINGS_KEY = 'electron:getProxySettings';
20
+
21
+ export const settingsSlice: StateCreator<
22
+ ElectronStore,
23
+ [['zustand/devtools', never]],
24
+ [],
25
+ ElectronSettingsAction
26
+ > = (set, get) => ({
27
+ refreshProxySettings: async () => {
28
+ await mutate(ELECTRON_PROXY_SETTINGS_KEY);
29
+ },
30
+
31
+ setProxySettings: async (values) => {
32
+ try {
33
+ // 更新设置
34
+ await desktopSettingsService.setSettings(values);
35
+
36
+ // 刷新状态
37
+ await get().refreshProxySettings();
38
+ } catch (error) {
39
+ console.error('代理设置更新失败:', error);
40
+ }
41
+ },
42
+
43
+ useGetProxySettings: () =>
44
+ useSWR<NetworkProxySettings>(
45
+ ELECTRON_PROXY_SETTINGS_KEY,
46
+ async () => desktopSettingsService.getProxySettings(),
47
+ {
48
+ onSuccess: (data) => {
49
+ if (!isEqual(data, get().proxySettings)) {
50
+ set({ proxySettings: data });
51
+ }
52
+ },
53
+ },
54
+ ),
55
+ });
@@ -1,7 +1,16 @@
1
- import { DataSyncConfig, ElectronAppState } from '@lobechat/electron-client-ipc';
1
+ import { DataSyncConfig, ElectronAppState, NetworkProxySettings } from '@lobechat/electron-client-ipc';
2
2
 
3
3
  export type RemoteServerError = 'CONFIG_ERROR' | 'AUTH_ERROR' | 'DISCONNECT_ERROR';
4
4
 
5
+ export const defaultProxySettings: NetworkProxySettings = {
6
+ enableProxy: false,
7
+ proxyBypass: 'localhost, 127.0.0.1, ::1',
8
+ proxyPort: '',
9
+ proxyRequireAuth: false,
10
+ proxyServer: '',
11
+ proxyType: 'http',
12
+ };
13
+
5
14
  export interface ElectronState {
6
15
  appState: ElectronAppState;
7
16
  dataSyncConfig: DataSyncConfig;
@@ -9,6 +18,7 @@ export interface ElectronState {
9
18
  isConnectingServer?: boolean;
10
19
  isInitRemoteServerConfig: boolean;
11
20
  isSyncActive?: boolean;
21
+ proxySettings: NetworkProxySettings;
12
22
  remoteServerSyncError?: { message?: string; type: RemoteServerError };
13
23
  }
14
24
 
@@ -19,4 +29,5 @@ export const initialState: ElectronState = {
19
29
  isConnectingServer: false,
20
30
  isInitRemoteServerConfig: false,
21
31
  isSyncActive: false,
32
+ proxySettings: defaultProxySettings,
22
33
  };
@@ -1,6 +1,6 @@
1
1
  import { describe, expect, it } from 'vitest';
2
2
 
3
- import { ElectronState } from '@/store/electron/initialState';
3
+ import { ElectronState, defaultProxySettings } from '@/store/electron/initialState';
4
4
 
5
5
  import { desktopStateSelectors } from '../desktopState';
6
6
 
@@ -25,6 +25,7 @@ describe('desktopStateSelectors', () => {
25
25
  storageMode: 'local',
26
26
  },
27
27
  isInitRemoteServerConfig: false,
28
+ proxySettings: defaultProxySettings,
28
29
  };
29
30
 
30
31
  expect(desktopStateSelectors.usePath(state)).toEqual({
@@ -47,6 +48,7 @@ describe('desktopStateSelectors', () => {
47
48
  storageMode: 'local',
48
49
  },
49
50
  isInitRemoteServerConfig: false,
51
+ proxySettings: defaultProxySettings,
50
52
  };
51
53
 
52
54
  expect(desktopStateSelectors.usePath(state)).toBeUndefined();
@@ -4,6 +4,7 @@ import { StateCreator } from 'zustand/vanilla';
4
4
 
5
5
  import { createDevtools } from '../middleware/createDevtools';
6
6
  import { type ElectronAppAction, createElectronAppSlice } from './actions/app';
7
+ import { type ElectronSettingsAction, settingsSlice } from './actions/settings';
7
8
  import { type ElectronRemoteServerAction, remoteSyncSlice } from './actions/sync';
8
9
  import { type ElectronState, initialState } from './initialState';
9
10
 
@@ -12,7 +13,8 @@ import { type ElectronState, initialState } from './initialState';
12
13
  export interface ElectronStore
13
14
  extends ElectronState,
14
15
  ElectronRemoteServerAction,
15
- ElectronAppAction {
16
+ ElectronAppAction,
17
+ ElectronSettingsAction {
16
18
  /* empty */
17
19
  }
18
20
 
@@ -22,6 +24,7 @@ const createStore: StateCreator<ElectronStore, [['zustand/devtools', never]]> =
22
24
  ...initialState,
23
25
  ...remoteSyncSlice(...parameters),
24
26
  ...createElectronAppSlice(...parameters),
27
+ ...settingsSlice(...parameters),
25
28
  });
26
29
 
27
30
  // =============== 实装 useStore ============ //
@@ -31,6 +31,7 @@ export enum SettingsTabs {
31
31
  Hotkey = 'hotkey',
32
32
  LLM = 'llm',
33
33
  Provider = 'provider',
34
+ Proxy = 'proxy',
34
35
  Storage = 'storage',
35
36
  Sync = 'sync',
36
37
  SystemAgent = 'system-agent',
@@ -1,14 +0,0 @@
1
- import { PGlite } from "@electric-sql/pglite";
2
- import { createServer } from "pglite-server";
3
-
4
- // 创建或连接到您现有的 PGlite 数据库
5
- const db = new PGlite("/Users/arvinxx/Library/Application Support/lobehub-desktop/lobehub-local-db");
6
- await db.waitReady;
7
-
8
- // 创建服务器并监听端口
9
- const PORT = 6543;
10
- const pgServer = createServer(db);
11
-
12
- pgServer.listen(PORT, () => {
13
- console.log(`PGlite 服务器已启动,监听端口 ${PORT}`);
14
- });