@lobehub/chat 1.81.9 → 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 +50 -0
- package/changelog/v1.json +18 -0
- package/locales/ar/electron.json +38 -2
- package/locales/ar/plugin.json +33 -2
- package/locales/bg-BG/electron.json +38 -2
- package/locales/bg-BG/plugin.json +33 -2
- package/locales/de-DE/electron.json +38 -2
- package/locales/de-DE/plugin.json +28 -2
- package/locales/en-US/electron.json +38 -2
- package/locales/en-US/plugin.json +28 -2
- package/locales/es-ES/electron.json +38 -2
- package/locales/es-ES/plugin.json +33 -2
- package/locales/fa-IR/electron.json +38 -2
- package/locales/fa-IR/plugin.json +33 -2
- package/locales/fr-FR/electron.json +38 -2
- package/locales/fr-FR/plugin.json +33 -2
- package/locales/it-IT/electron.json +38 -2
- package/locales/it-IT/plugin.json +33 -2
- package/locales/ja-JP/electron.json +38 -2
- package/locales/ja-JP/plugin.json +33 -2
- package/locales/ko-KR/electron.json +38 -2
- package/locales/ko-KR/plugin.json +28 -2
- package/locales/nl-NL/electron.json +38 -2
- package/locales/nl-NL/plugin.json +33 -2
- package/locales/pl-PL/electron.json +38 -2
- package/locales/pl-PL/plugin.json +28 -2
- package/locales/pt-BR/electron.json +38 -2
- package/locales/pt-BR/plugin.json +33 -2
- package/locales/ru-RU/electron.json +38 -2
- package/locales/ru-RU/plugin.json +33 -2
- package/locales/tr-TR/electron.json +38 -2
- package/locales/tr-TR/plugin.json +33 -2
- package/locales/vi-VN/electron.json +38 -2
- package/locales/vi-VN/plugin.json +28 -2
- package/locales/zh-CN/electron.json +38 -2
- package/locales/zh-CN/plugin.json +38 -2
- package/locales/zh-TW/electron.json +38 -2
- package/locales/zh-TW/plugin.json +33 -2
- 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/components/ManifestPreviewer/index.tsx +4 -1
- package/src/features/ChatInput/ActionBar/Tools/Dropdown.tsx +2 -1
- package/src/features/Conversation/Extras/Usage/index.tsx +7 -1
- package/src/features/Conversation/Messages/Assistant/Tool/Render/CustomRender.tsx +1 -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/PluginAvatar/index.tsx +2 -1
- package/src/features/PluginDevModal/MCPManifestForm/ArgsInput.tsx +20 -0
- package/src/features/PluginDevModal/MCPManifestForm/MCPTypeSelect.tsx +176 -0
- package/src/features/PluginDevModal/MCPManifestForm/index.tsx +226 -0
- package/src/features/PluginDevModal/PluginPreview.tsx +4 -3
- package/src/features/PluginDevModal/index.tsx +43 -34
- package/src/features/PluginStore/AddPluginButton.tsx +3 -1
- package/src/features/PluginStore/PluginItem/Action.tsx +5 -2
- package/src/features/PluginStore/PluginItem/PluginAvatar.tsx +25 -0
- package/src/features/PluginStore/PluginItem/index.tsx +4 -3
- package/src/features/PluginTag/index.tsx +8 -2
- package/src/{server/modules/MCPClient → libs/mcp}/__tests__/__snapshots__/index.test.ts.snap +0 -56
- package/src/{server/modules/MCPClient → libs/mcp}/__tests__/index.test.ts +2 -2
- package/src/{server/modules/MCPClient/index.ts → libs/mcp/client.ts} +29 -33
- package/src/libs/mcp/index.ts +2 -0
- package/src/libs/mcp/types.ts +27 -0
- package/src/locales/default/electron.ts +38 -2
- package/src/locales/default/plugin.ts +41 -3
- package/src/server/modules/ElectronIPCClient/index.ts +36 -0
- package/src/server/routers/lambda/session.ts +2 -6
- package/src/server/routers/tools/index.ts +2 -0
- package/src/server/routers/tools/mcp.ts +85 -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 +176 -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 +36 -0
- 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/chat/slices/plugin/action.ts +46 -2
- 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/types/tool/plugin.ts +9 -0
- package/src/utils/client/GlobalAgentContextManager.ts +85 -0
- package/src/utils/promptTemplate.test.ts +78 -0
- package/src/utils/promptTemplate.ts +17 -0
@@ -17,16 +17,52 @@
|
|
17
17
|
"statusDisconnected": "未连接",
|
18
18
|
"urlRequired": "请输入服务器地址"
|
19
19
|
},
|
20
|
+
"sync": {
|
21
|
+
"continue": "继续",
|
22
|
+
"inCloud": "当前使用云端同步",
|
23
|
+
"inLocalStorage": "当前使用本地存储",
|
24
|
+
"isIniting": "正在初始化...",
|
25
|
+
"lobehubCloud": {
|
26
|
+
"description": "官方提供的云版本",
|
27
|
+
"title": "LobeHub Cloud"
|
28
|
+
},
|
29
|
+
"local": {
|
30
|
+
"description": "使用本地数据库,完全离线可用",
|
31
|
+
"title": "本地数据库"
|
32
|
+
},
|
33
|
+
"mode": {
|
34
|
+
"cloudSync": "云端同步",
|
35
|
+
"localStorage": "本地存储",
|
36
|
+
"title": "选择你的连接模式",
|
37
|
+
"useSelfHosted": "使用自托管实例?"
|
38
|
+
},
|
39
|
+
"selfHosted": {
|
40
|
+
"description": "自行部署的社区版本",
|
41
|
+
"title": "自托管实例"
|
42
|
+
}
|
43
|
+
},
|
20
44
|
"updater": {
|
45
|
+
"checkingUpdate": "检查新版本",
|
46
|
+
"checkingUpdateDesc": "正在获取版本信息...",
|
47
|
+
"downloadNewVersion": "下载新版本",
|
21
48
|
"downloadingUpdate": "正在下载更新",
|
22
49
|
"downloadingUpdateDesc": "更新正在下载中,请稍候...",
|
50
|
+
"installLater": "下次启动时更新",
|
51
|
+
"isLatestVersion": "当前已是最新版本",
|
52
|
+
"isLatestVersionDesc": "非常棒,使用的版本 {{version}} 已是最前沿的版本。",
|
23
53
|
"later": "稍后更新",
|
24
54
|
"newVersionAvailable": "新版本可用",
|
25
55
|
"newVersionAvailableDesc": "发现新版本 {{version}},是否立即下载?",
|
26
|
-
"restartAndInstall": "
|
56
|
+
"restartAndInstall": "安装更新并重启",
|
27
57
|
"updateError": "更新错误",
|
28
58
|
"updateReady": "更新已就绪",
|
29
|
-
"updateReadyDesc": "
|
59
|
+
"updateReadyDesc": "新版本 {{version}} 已下载完成,重启应用后即可完成安装。",
|
30
60
|
"upgradeNow": "立即更新"
|
61
|
+
},
|
62
|
+
"waitingOAuth": {
|
63
|
+
"cancel": "取消",
|
64
|
+
"description": "浏览器已打开授权页面,请在浏览器中完成授权",
|
65
|
+
"helpText": "如果浏览器没有自动打开,请点击取消后重新尝试",
|
66
|
+
"title": "等待授权连接"
|
31
67
|
}
|
32
68
|
}
|
@@ -35,8 +35,8 @@
|
|
35
35
|
"label": "标识符"
|
36
36
|
},
|
37
37
|
"mode": {
|
38
|
-
"
|
39
|
-
"
|
38
|
+
"mcp": "MCP 插件",
|
39
|
+
"mcpExp": "实验性",
|
40
40
|
"url": "在线链接"
|
41
41
|
},
|
42
42
|
"name": {
|
@@ -45,6 +45,42 @@
|
|
45
45
|
"placeholder": "搜索引擎"
|
46
46
|
}
|
47
47
|
},
|
48
|
+
"mcp": {
|
49
|
+
"args": {
|
50
|
+
"label": "命令参数",
|
51
|
+
"desc": "传递给 STDIO 命令的参数列表,一般在这里输入 MCP 服务器名称",
|
52
|
+
"placeholder": "例如:mcp-hello-world"
|
53
|
+
},
|
54
|
+
"command": {
|
55
|
+
"label": "命令"
|
56
|
+
},
|
57
|
+
"endpoint": {
|
58
|
+
"desc": "输入你的 MCP Streamable HTTP Server 的地址",
|
59
|
+
"label": "MCP Endpoint URL"
|
60
|
+
},
|
61
|
+
"identifier": {
|
62
|
+
"desc": "为你的 MCP 插件指定一个名称,需要使用英文字符",
|
63
|
+
"invalid": "只能输入英文字符、数字 、- 和_ 这两个符号",
|
64
|
+
"label": "MCP 插件名称",
|
65
|
+
"placeholder": "例如:my-mcp-plugin"
|
66
|
+
},
|
67
|
+
"type": {
|
68
|
+
"desc": "选择 MCP 插件的通信方式,网页版只支持 Streamable HTTP",
|
69
|
+
"httpFeature1": "兼容网页版与桌面端",
|
70
|
+
"httpFeature2": "连接远程 MCP 服务端, 无需额外安装配置",
|
71
|
+
"httpShortDesc": "基于流式 HTTP 的通信协议",
|
72
|
+
"label": "MCP 插件类型",
|
73
|
+
"stdioFeature1": "更低的通信延迟, 适合本地执行",
|
74
|
+
"stdioFeature2": "通过系统标准输入输出通信,需在本地安装运行 MCP 服务端",
|
75
|
+
"stdioNotAvailable": "STDIO 模式仅在桌面版可用",
|
76
|
+
"stdioShortDesc": "基于标准输入输出的通信协议",
|
77
|
+
"title": "MCP 插件类型"
|
78
|
+
},
|
79
|
+
"url": {
|
80
|
+
"desc": "输入你的 MCP Server Streamable HTTP 地址,不会以 /sse 结尾",
|
81
|
+
"label": "Streamable HTTP Endpoint URL"
|
82
|
+
}
|
83
|
+
},
|
48
84
|
"meta": {
|
49
85
|
"author": {
|
50
86
|
"desc": "插件的作者",
|
@@ -17,16 +17,52 @@
|
|
17
17
|
"statusDisconnected": "未連接",
|
18
18
|
"urlRequired": "請輸入伺服器地址"
|
19
19
|
},
|
20
|
+
"sync": {
|
21
|
+
"continue": "繼續",
|
22
|
+
"inCloud": "當前使用雲端同步",
|
23
|
+
"inLocalStorage": "當前使用本地儲存",
|
24
|
+
"isIniting": "正在初始化...",
|
25
|
+
"lobehubCloud": {
|
26
|
+
"description": "官方提供的雲版本",
|
27
|
+
"title": "LobeHub Cloud"
|
28
|
+
},
|
29
|
+
"local": {
|
30
|
+
"description": "使用本地資料庫,完全離線可用",
|
31
|
+
"title": "本地資料庫"
|
32
|
+
},
|
33
|
+
"mode": {
|
34
|
+
"cloudSync": "雲端同步",
|
35
|
+
"localStorage": "本地儲存",
|
36
|
+
"title": "選擇你的連接模式",
|
37
|
+
"useSelfHosted": "使用自托管實例?"
|
38
|
+
},
|
39
|
+
"selfHosted": {
|
40
|
+
"description": "自行部署的社區版本",
|
41
|
+
"title": "自托管實例"
|
42
|
+
}
|
43
|
+
},
|
20
44
|
"updater": {
|
45
|
+
"checkingUpdate": "檢查新版本",
|
46
|
+
"checkingUpdateDesc": "正在獲取版本資訊...",
|
47
|
+
"downloadNewVersion": "下載新版本",
|
21
48
|
"downloadingUpdate": "正在下載更新",
|
22
49
|
"downloadingUpdateDesc": "更新正在下載中,請稍候...",
|
50
|
+
"installLater": "下次啟動時更新",
|
51
|
+
"isLatestVersion": "當前已是最新版本",
|
52
|
+
"isLatestVersionDesc": "非常棒,使用的版本 {{version}} 已是最前沿的版本。",
|
23
53
|
"later": "稍後更新",
|
24
54
|
"newVersionAvailable": "新版本可用",
|
25
55
|
"newVersionAvailableDesc": "發現新版本 {{version}},是否立即下載?",
|
26
|
-
"restartAndInstall": "
|
56
|
+
"restartAndInstall": "安裝更新並重啟",
|
27
57
|
"updateError": "更新錯誤",
|
28
58
|
"updateReady": "更新已就緒",
|
29
|
-
"updateReadyDesc": "
|
59
|
+
"updateReadyDesc": "新版本 {{version}} 已下載完成,重啟應用後即可完成安裝。",
|
30
60
|
"upgradeNow": "立即更新"
|
61
|
+
},
|
62
|
+
"waitingOAuth": {
|
63
|
+
"cancel": "取消",
|
64
|
+
"description": "瀏覽器已打開授權頁面,請在瀏覽器中完成授權",
|
65
|
+
"helpText": "如果瀏覽器沒有自動打開,請點擊取消後重新嘗試",
|
66
|
+
"title": "等待授權連接"
|
31
67
|
}
|
32
68
|
}
|
@@ -35,8 +35,8 @@
|
|
35
35
|
"label": "識別碼"
|
36
36
|
},
|
37
37
|
"mode": {
|
38
|
-
"
|
39
|
-
"
|
38
|
+
"mcp": "MCP 插件",
|
39
|
+
"mcpExp": "實驗性",
|
40
40
|
"url": "線上連結"
|
41
41
|
},
|
42
42
|
"name": {
|
@@ -45,6 +45,37 @@
|
|
45
45
|
"placeholder": "搜尋引擎"
|
46
46
|
}
|
47
47
|
},
|
48
|
+
"mcp": {
|
49
|
+
"args": {
|
50
|
+
"desc": "傳遞給 STDIO 命令的參數列表",
|
51
|
+
"label": "命令參數",
|
52
|
+
"placeholder": "例如:--port 8080 --debug",
|
53
|
+
"tooltip": "輸入參數後按回車或使用逗號/空格分隔"
|
54
|
+
},
|
55
|
+
"command": {
|
56
|
+
"desc": "用於啟動 MCP STDIO 插件的可執行文件或腳本",
|
57
|
+
"label": "命令",
|
58
|
+
"placeholder": "例如:python main.py 或 /path/to/executable"
|
59
|
+
},
|
60
|
+
"endpoint": {
|
61
|
+
"desc": "輸入你的 MCP Streamable HTTP Server 的地址",
|
62
|
+
"label": "MCP Endpoint URL"
|
63
|
+
},
|
64
|
+
"identifier": {
|
65
|
+
"desc": "為你的 MCP 插件指定一個名稱,需要使用英文字符",
|
66
|
+
"invalid": "只能輸入英文字符、數字、- 和_ 這兩個符號",
|
67
|
+
"label": "MCP 插件名稱",
|
68
|
+
"placeholder": "例如:my-mcp-plugin"
|
69
|
+
},
|
70
|
+
"type": {
|
71
|
+
"desc": "選擇 MCP 插件的通信方式,網頁版只支持 Streamable HTTP",
|
72
|
+
"label": "MCP 插件類型"
|
73
|
+
},
|
74
|
+
"url": {
|
75
|
+
"desc": "輸入你的 MCP Server Streamable HTTP 地址,結尾不會有 /sse",
|
76
|
+
"label": "HTTP Endpoint URL"
|
77
|
+
}
|
78
|
+
},
|
48
79
|
"meta": {
|
49
80
|
"author": {
|
50
81
|
"desc": "外掛的作者",
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@lobehub/chat",
|
3
|
-
"version": "1.
|
3
|
+
"version": "1.82.1",
|
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",
|
@@ -9,12 +9,12 @@ export interface AutoUpdateDispatchEvents {
|
|
9
9
|
}
|
10
10
|
|
11
11
|
export interface AutoUpdateBroadcastEvents {
|
12
|
-
|
13
|
-
|
12
|
+
manualUpdateAvailable: (info: UpdateInfo) => void;
|
13
|
+
manualUpdateCheckStart: () => void;
|
14
|
+
manualUpdateNotAvailable: (info: UpdateInfo) => void;
|
14
15
|
updateDownloadProgress: (progress: ProgressInfo) => void;
|
15
16
|
updateDownloadStart: () => void;
|
16
17
|
updateDownloaded: (info: UpdateInfo) => void;
|
17
18
|
updateError: (message: string) => void;
|
18
|
-
updateNotAvailable: (info: UpdateInfo) => void;
|
19
19
|
updateWillInstallLater: () => void;
|
20
20
|
}
|
@@ -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;
|