@lobehub/chat 1.97.17 → 1.98.0
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/CHANGELOG.md +25 -0
- package/apps/desktop/package.json +8 -5
- package/apps/desktop/src/main/const/store.ts +12 -0
- package/apps/desktop/src/main/controllers/NetworkProxyCtr.ts +172 -0
- package/apps/desktop/src/main/controllers/__tests__/NetworkProxyCtr.test.ts +401 -0
- package/apps/desktop/src/main/core/Browser.ts +2 -0
- package/apps/desktop/src/main/modules/networkProxy/dispatcher.ts +116 -0
- package/apps/desktop/src/main/modules/networkProxy/index.ts +6 -0
- package/apps/desktop/src/main/modules/networkProxy/tester.ts +163 -0
- package/apps/desktop/src/main/modules/networkProxy/urlBuilder.ts +25 -0
- package/apps/desktop/src/main/modules/networkProxy/validator.ts +80 -0
- package/apps/desktop/src/main/types/store.ts +2 -1
- package/apps/desktop/src/main/utils/logger.ts +2 -1
- package/changelog/v1.json +9 -0
- package/locales/ar/electron.json +39 -0
- package/locales/ar/setting.json +1 -0
- package/locales/bg-BG/electron.json +39 -0
- package/locales/bg-BG/setting.json +1 -0
- package/locales/de-DE/electron.json +39 -0
- package/locales/de-DE/setting.json +1 -0
- package/locales/en-US/electron.json +39 -0
- package/locales/en-US/setting.json +1 -0
- package/locales/es-ES/electron.json +39 -0
- package/locales/es-ES/setting.json +1 -0
- package/locales/fa-IR/electron.json +39 -0
- package/locales/fa-IR/setting.json +1 -0
- package/locales/fr-FR/electron.json +39 -0
- package/locales/fr-FR/setting.json +1 -0
- package/locales/it-IT/electron.json +39 -0
- package/locales/it-IT/setting.json +1 -0
- package/locales/ja-JP/electron.json +39 -0
- package/locales/ja-JP/setting.json +1 -0
- package/locales/ko-KR/electron.json +39 -0
- package/locales/ko-KR/setting.json +1 -0
- package/locales/nl-NL/electron.json +39 -0
- package/locales/nl-NL/setting.json +1 -0
- package/locales/pl-PL/electron.json +39 -0
- package/locales/pl-PL/setting.json +1 -0
- package/locales/pt-BR/electron.json +39 -0
- package/locales/pt-BR/setting.json +1 -0
- package/locales/ru-RU/electron.json +39 -0
- package/locales/ru-RU/setting.json +1 -0
- package/locales/tr-TR/electron.json +39 -0
- package/locales/tr-TR/setting.json +1 -0
- package/locales/vi-VN/electron.json +39 -0
- package/locales/vi-VN/setting.json +1 -0
- package/locales/zh-CN/electron.json +39 -0
- package/locales/zh-CN/setting.json +1 -0
- package/locales/zh-TW/electron.json +39 -0
- package/locales/zh-TW/setting.json +1 -0
- package/package.json +3 -3
- package/packages/electron-client-ipc/src/events/index.ts +3 -1
- package/packages/electron-client-ipc/src/events/settings.ts +12 -0
- package/packages/electron-client-ipc/src/types/index.ts +1 -0
- package/packages/electron-client-ipc/src/types/proxy.ts +12 -0
- package/src/app/[variants]/(main)/settings/hooks/useCategory.tsx +11 -1
- package/src/app/[variants]/(main)/settings/proxy/features/ProxyForm.tsx +369 -0
- package/src/app/[variants]/(main)/settings/proxy/index.tsx +22 -0
- package/src/app/[variants]/(main)/settings/proxy/page.tsx +28 -0
- package/src/locales/default/electron.ts +39 -0
- package/src/locales/default/setting.ts +1 -0
- package/src/services/electron/settings.ts +33 -0
- package/src/store/electron/actions/settings.ts +55 -0
- package/src/store/electron/initialState.ts +12 -1
- package/src/store/electron/selectors/__tests__/desktopState.test.ts +3 -1
- package/src/store/electron/store.ts +4 -1
- package/src/store/global/initialState.ts +1 -0
- package/apps/desktop/scripts/pglite-server.ts +0 -14
@@ -1,4 +1,43 @@
|
|
1
1
|
{
|
2
|
+
"proxy": {
|
3
|
+
"auth": "Cần xác thực",
|
4
|
+
"authDesc": "Nếu máy chủ proxy yêu cầu tên đăng nhập và mật khẩu",
|
5
|
+
"authSettings": "Cài đặt xác thực",
|
6
|
+
"basicSettings": "Cài đặt proxy",
|
7
|
+
"basicSettingsDesc": "Cấu hình các tham số kết nối của máy chủ proxy",
|
8
|
+
"bypass": "Địa chỉ không sử dụng proxy",
|
9
|
+
"connectionTest": "Kiểm tra kết nối",
|
10
|
+
"enable": "Bật proxy",
|
11
|
+
"enableDesc": "Khi bật, truy cập mạng sẽ thông qua máy chủ proxy",
|
12
|
+
"password": "Mật khẩu",
|
13
|
+
"password_placeholder": "Vui lòng nhập mật khẩu",
|
14
|
+
"port": "Cổng",
|
15
|
+
"resetButton": "Đặt lại",
|
16
|
+
"saveButton": "Lưu",
|
17
|
+
"saveFailed": "Lưu thất bại: {{error}}",
|
18
|
+
"saveSuccess": "Lưu cài đặt proxy thành công",
|
19
|
+
"server": "Địa chỉ máy chủ",
|
20
|
+
"testButton": "Kiểm tra kết nối",
|
21
|
+
"testDescription": "Sử dụng cấu hình proxy hiện tại để kiểm tra kết nối, xác nhận cấu hình hoạt động bình thường",
|
22
|
+
"testFailed": "Kết nối thất bại",
|
23
|
+
"testSuccessWithTime": "Kiểm tra kết nối thành công, thời gian: {{time}} ms",
|
24
|
+
"testUrl": "Địa chỉ kiểm tra",
|
25
|
+
"testUrlPlaceholder": "Vui lòng nhập URL cần kiểm tra",
|
26
|
+
"testing": "Đang kiểm tra kết nối...",
|
27
|
+
"type": "Loại proxy",
|
28
|
+
"unsavedChanges": "Bạn có các thay đổi chưa được lưu",
|
29
|
+
"username": "Tên người dùng",
|
30
|
+
"username_placeholder": "Vui lòng nhập tên người dùng",
|
31
|
+
"validation": {
|
32
|
+
"passwordRequired": "Mật khẩu là bắt buộc khi bật xác thực",
|
33
|
+
"portInvalid": "Cổng phải là số từ 1 đến 65535",
|
34
|
+
"portRequired": "Cổng là bắt buộc khi bật proxy",
|
35
|
+
"serverInvalid": "Vui lòng nhập địa chỉ máy chủ hợp lệ (IP hoặc tên miền)",
|
36
|
+
"serverRequired": "Địa chỉ máy chủ là bắt buộc khi bật proxy",
|
37
|
+
"typeRequired": "Loại proxy là bắt buộc khi bật proxy",
|
38
|
+
"usernameRequired": "Tên đăng nhập là bắt buộc khi bật xác thực"
|
39
|
+
}
|
40
|
+
},
|
2
41
|
"remoteServer": {
|
3
42
|
"authError": "Xác thực thất bại: {{error}}",
|
4
43
|
"authPending": "Vui lòng hoàn tất xác thực trong trình duyệt",
|
@@ -1,4 +1,43 @@
|
|
1
1
|
{
|
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": "请在浏览器中完成授权",
|
@@ -1,4 +1,43 @@
|
|
1
1
|
{
|
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}} 毫秒",
|
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": "請在瀏覽器中完成授權",
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@lobehub/chat",
|
3
|
-
"version": "1.
|
3
|
+
"version": "1.98.0",
|
4
4
|
"description": "Lobe Chat - an open-source, high-performance chatbot framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.",
|
5
5
|
"keywords": [
|
6
6
|
"framework",
|
@@ -118,7 +118,7 @@
|
|
118
118
|
"dependencies": {
|
119
119
|
"@ant-design/icons": "^5.6.1",
|
120
120
|
"@ant-design/pro-components": "^2.8.7",
|
121
|
-
"@anthropic-ai/sdk": "^0.
|
121
|
+
"@anthropic-ai/sdk": "^0.56.0",
|
122
122
|
"@auth/core": "^0.38.0",
|
123
123
|
"@aws-sdk/client-bedrock-runtime": "^3.821.0",
|
124
124
|
"@aws-sdk/client-s3": "^3.821.0",
|
@@ -199,7 +199,7 @@
|
|
199
199
|
"langfuse": "^3.37.3",
|
200
200
|
"langfuse-core": "^3.37.3",
|
201
201
|
"lodash-es": "^4.17.21",
|
202
|
-
"lucide-react": "^0.
|
202
|
+
"lucide-react": "^0.525.0",
|
203
203
|
"mammoth": "^1.9.1",
|
204
204
|
"markdown-to-txt": "^2.0.1",
|
205
205
|
"mdast-util-to-markdown": "^2.1.2",
|
@@ -1,6 +1,7 @@
|
|
1
1
|
import { LocalFilesDispatchEvents } from './localFile';
|
2
2
|
import { MenuDispatchEvents } from './menu';
|
3
3
|
import { RemoteServerBroadcastEvents, RemoteServerDispatchEvents } from './remoteServer';
|
4
|
+
import { DesktopSettingsDispatchEvents } from './settings';
|
4
5
|
import { ShortcutDispatchEvents } from './shortcut';
|
5
6
|
import { SystemBroadcastEvents, SystemDispatchEvents } from './system';
|
6
7
|
import { TrayDispatchEvents } from './tray';
|
@@ -21,7 +22,8 @@ export interface ClientDispatchEvents
|
|
21
22
|
ShortcutDispatchEvents,
|
22
23
|
RemoteServerDispatchEvents,
|
23
24
|
UploadFilesDispatchEvents,
|
24
|
-
TrayDispatchEvents
|
25
|
+
TrayDispatchEvents,
|
26
|
+
DesktopSettingsDispatchEvents {}
|
25
27
|
|
26
28
|
export type ClientDispatchEventKey = keyof ClientDispatchEvents;
|
27
29
|
|
@@ -0,0 +1,12 @@
|
|
1
|
+
import { NetworkProxySettings } from '../types';
|
2
|
+
|
3
|
+
export interface DesktopSettingsDispatchEvents {
|
4
|
+
getProxySettings: () => NetworkProxySettings;
|
5
|
+
setProxySettings: (settings: Partial<NetworkProxySettings>) => void;
|
6
|
+
testProxyConfig: (data: { config: NetworkProxySettings; testUrl?: string }) => Promise<{
|
7
|
+
message?: string;
|
8
|
+
responseTime?: number;
|
9
|
+
success: boolean;
|
10
|
+
}>;
|
11
|
+
testProxyConnection: (url: string) => Promise<{ message?: string; success: boolean }>;
|
12
|
+
}
|
@@ -0,0 +1,12 @@
|
|
1
|
+
export type ProxyType = 'http' | 'https' | 'socks5';
|
2
|
+
|
3
|
+
export interface NetworkProxySettings {
|
4
|
+
enableProxy: boolean;
|
5
|
+
proxyBypass?: string;
|
6
|
+
proxyPassword?: string;
|
7
|
+
proxyPort: string;
|
8
|
+
proxyRequireAuth: boolean;
|
9
|
+
proxyServer: string;
|
10
|
+
proxyType: ProxyType;
|
11
|
+
proxyUsername?: string;
|
12
|
+
}
|
@@ -4,6 +4,7 @@ import {
|
|
4
4
|
Brain,
|
5
5
|
Cloudy,
|
6
6
|
Database,
|
7
|
+
EthernetPort,
|
7
8
|
Info,
|
8
9
|
KeyboardIcon,
|
9
10
|
Mic2,
|
@@ -16,7 +17,7 @@ import { useTranslation } from 'react-i18next';
|
|
16
17
|
import { Flexbox } from 'react-layout-kit';
|
17
18
|
|
18
19
|
import type { MenuProps } from '@/components/Menu';
|
19
|
-
import { isDeprecatedEdition } from '@/const/version';
|
20
|
+
import { isDeprecatedEdition, isDesktop } from '@/const/version';
|
20
21
|
import { SettingsTabs } from '@/store/global/initialState';
|
21
22
|
import { featureFlagsSelectors, useServerConfigStore } from '@/store/serverConfig';
|
22
23
|
|
@@ -117,6 +118,15 @@ export const useCategory = () => {
|
|
117
118
|
{
|
118
119
|
type: 'divider',
|
119
120
|
},
|
121
|
+
isDesktop && {
|
122
|
+
icon: <Icon icon={EthernetPort} />,
|
123
|
+
key: SettingsTabs.Proxy,
|
124
|
+
label: (
|
125
|
+
<Link href={'/settings/proxy'} onClick={(e) => e.preventDefault()}>
|
126
|
+
{t('tab.proxy')}
|
127
|
+
</Link>
|
128
|
+
),
|
129
|
+
},
|
120
130
|
{
|
121
131
|
icon: <Icon icon={Database} />,
|
122
132
|
key: SettingsTabs.Storage,
|
@@ -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;
|