@nextclaw/ui 0.9.6 → 0.9.7
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 +8 -2
- package/dist/assets/ChannelsList-BEbqjBdM.js +1 -0
- package/dist/assets/{ChatPage-DM1ewbWf.js → ChatPage-BaUQCtqU.js} +2 -2
- package/dist/assets/{DocBrowser-BLv77lJ0.js → DocBrowser-CtDiU1-0.js} +1 -1
- package/dist/assets/{LogoBadge-D7j1al-w.js → LogoBadge-Cy-EHxZ6.js} +1 -1
- package/dist/assets/MarketplacePage-D_r1Xy_C.js +49 -0
- package/dist/assets/{McpMarketplacePage-DpMjaD3m.js → McpMarketplacePage-Bftp9zkB.js} +2 -2
- package/dist/assets/ModelConfig-CdFIVFue.js +1 -0
- package/dist/assets/ProvidersList-Bx3w67f2.js +1 -0
- package/dist/assets/RemoteAccessPage-3JbDaO3y.js +1 -0
- package/dist/assets/{RuntimeConfig-BbX4yFKy.js → RuntimeConfig-V2C7bIJt.js} +1 -1
- package/dist/assets/{SearchConfig-BmmmeyJd.js → SearchConfig-Wq9VNK2x.js} +1 -1
- package/dist/assets/{SecretsConfig-CWG8J01H.js → SecretsConfig-DuYOs4z3.js} +2 -2
- package/dist/assets/SessionsConfig-fuPlbwC7.js +2 -0
- package/dist/assets/{chat-message-CGXiVhyN.js → chat-message-D729qtQ5.js} +1 -1
- package/dist/assets/index-D3eWL7gT.js +8 -0
- package/dist/assets/index-DfEAJJsA.css +1 -0
- package/dist/assets/{label-CCSffS1D.js → label-DCtPX8p3.js} +1 -1
- package/dist/assets/{page-layout-ud8wZ8gX.js → page-layout-Dr5ymRVA.js} +1 -1
- package/dist/assets/popover-YWmgsAlv.js +1 -0
- package/dist/assets/{security-config-DJJUCMov.js → security-config-bwP5X0a-.js} +1 -1
- package/dist/assets/skeleton-C4iXwmBW.js +1 -0
- package/dist/assets/{status-dot-Fz9-eKsl.js → status-dot-SDG0WNvL.js} +1 -1
- package/dist/assets/{switch-B-_SrMSL.js → switch-lC_seKUL.js} +1 -1
- package/dist/assets/{tabs-custom-6Tm1ZHfS.js → tabs-custom-LAWD0u69.js} +1 -1
- package/dist/assets/useConfirmDialog-D0akncY4.js +1 -0
- package/dist/assets/vendor-CmQZsDAE.js +436 -0
- package/dist/index.html +3 -3
- package/package.json +4 -4
- package/src/App.tsx +36 -39
- package/src/account/components/account-panel.tsx +93 -0
- package/src/account/managers/account.manager.ts +179 -0
- package/src/account/stores/account.store.ts +68 -0
- package/src/app-query-client.ts +10 -0
- package/src/components/layout/Sidebar.tsx +26 -0
- package/src/components/remote/RemoteAccessPage.tsx +162 -442
- package/src/hooks/useRemoteAccess.ts +7 -6
- package/src/lib/i18n.remote.ts +108 -4
- package/src/presenter/app-presenter-context.tsx +20 -0
- package/src/presenter/app.presenter.ts +12 -0
- package/src/remote/managers/remote-access.manager.ts +196 -0
- package/src/remote/remote-access.query.ts +78 -0
- package/src/remote/stores/remote-access.store.ts +44 -0
- package/dist/assets/ChannelsList-Byfj2R01.js +0 -1
- package/dist/assets/MarketplacePage-DuskLKYh.js +0 -49
- package/dist/assets/ModelConfig-ubaecweS.js +0 -1
- package/dist/assets/ProvidersList-w8MJH2LI.js +0 -1
- package/dist/assets/RemoteAccessPage-D79_5Kbn.js +0 -1
- package/dist/assets/SessionsConfig-D-vg_Lgv.js +0 -2
- package/dist/assets/index-COrhpAdh.css +0 -1
- package/dist/assets/index-CeRbsQ90.js +0 -8
- package/dist/assets/index-Ct7FQpxN.js +0 -1
- package/dist/assets/popover-Bfoe6YBX.js +0 -1
- package/dist/assets/skeleton-IOOTmHzP.js +0 -1
- package/dist/assets/useConfirmDialog-BeOW2bOI.js +0 -5
- package/dist/assets/vendor-CwsIoNvJ.js +0 -442
|
@@ -10,11 +10,12 @@ import {
|
|
|
10
10
|
updateRemoteSettings
|
|
11
11
|
} from '@/api/remote';
|
|
12
12
|
import { t } from '@/lib/i18n';
|
|
13
|
+
import { REMOTE_STATUS_QUERY_KEY } from '@/remote/remote-access.query';
|
|
13
14
|
import { toast } from 'sonner';
|
|
14
15
|
|
|
15
16
|
export function useRemoteStatus() {
|
|
16
17
|
return useQuery({
|
|
17
|
-
queryKey:
|
|
18
|
+
queryKey: REMOTE_STATUS_QUERY_KEY,
|
|
18
19
|
queryFn: fetchRemoteStatus,
|
|
19
20
|
staleTime: 5000,
|
|
20
21
|
refetchOnWindowFocus: true
|
|
@@ -27,7 +28,7 @@ export function useRemoteLogin() {
|
|
|
27
28
|
return useMutation({
|
|
28
29
|
mutationFn: loginRemote,
|
|
29
30
|
onSuccess: () => {
|
|
30
|
-
queryClient.invalidateQueries({ queryKey:
|
|
31
|
+
queryClient.invalidateQueries({ queryKey: REMOTE_STATUS_QUERY_KEY });
|
|
31
32
|
toast.success(t('remoteLoginSuccess'));
|
|
32
33
|
},
|
|
33
34
|
onError: (error: Error) => {
|
|
@@ -52,7 +53,7 @@ export function useRemoteBrowserAuthPoll() {
|
|
|
52
53
|
mutationFn: pollRemoteBrowserAuth,
|
|
53
54
|
onSuccess: (result) => {
|
|
54
55
|
if (result.status === 'authorized') {
|
|
55
|
-
queryClient.invalidateQueries({ queryKey:
|
|
56
|
+
queryClient.invalidateQueries({ queryKey: REMOTE_STATUS_QUERY_KEY });
|
|
56
57
|
toast.success(t('remoteLoginSuccess'));
|
|
57
58
|
}
|
|
58
59
|
},
|
|
@@ -68,7 +69,7 @@ export function useRemoteLogout() {
|
|
|
68
69
|
return useMutation({
|
|
69
70
|
mutationFn: logoutRemote,
|
|
70
71
|
onSuccess: () => {
|
|
71
|
-
queryClient.invalidateQueries({ queryKey:
|
|
72
|
+
queryClient.invalidateQueries({ queryKey: REMOTE_STATUS_QUERY_KEY });
|
|
72
73
|
toast.success(t('remoteLogoutSuccess'));
|
|
73
74
|
},
|
|
74
75
|
onError: (error: Error) => {
|
|
@@ -83,7 +84,7 @@ export function useRemoteSettings() {
|
|
|
83
84
|
return useMutation({
|
|
84
85
|
mutationFn: updateRemoteSettings,
|
|
85
86
|
onSuccess: () => {
|
|
86
|
-
queryClient.invalidateQueries({ queryKey:
|
|
87
|
+
queryClient.invalidateQueries({ queryKey: REMOTE_STATUS_QUERY_KEY });
|
|
87
88
|
toast.success(t('remoteSettingsSaved'));
|
|
88
89
|
},
|
|
89
90
|
onError: (error: Error) => {
|
|
@@ -110,7 +111,7 @@ export function useRemoteServiceControl() {
|
|
|
110
111
|
return useMutation({
|
|
111
112
|
mutationFn: controlRemoteService,
|
|
112
113
|
onSuccess: (result) => {
|
|
113
|
-
queryClient.invalidateQueries({ queryKey:
|
|
114
|
+
queryClient.invalidateQueries({ queryKey: REMOTE_STATUS_QUERY_KEY });
|
|
114
115
|
toast.success(result.message);
|
|
115
116
|
},
|
|
116
117
|
onError: (error: Error) => {
|
package/src/lib/i18n.remote.ts
CHANGED
|
@@ -1,14 +1,110 @@
|
|
|
1
1
|
export const REMOTE_LABELS: Record<string, { zh: string; en: string }> = {
|
|
2
2
|
remotePageTitle: { zh: '远程访问', en: 'Remote Access' },
|
|
3
3
|
remotePageDescription: {
|
|
4
|
-
zh: '
|
|
5
|
-
en: '
|
|
4
|
+
zh: '让这台设备出现在 NextClaw Platform 的设备列表里,并从网页中打开它。',
|
|
5
|
+
en: 'Make this device appear in your NextClaw Platform device list and open it from the web.'
|
|
6
|
+
},
|
|
7
|
+
remoteOpenWeb: { zh: '前往 NextClaw Web', en: 'Open NextClaw Web' },
|
|
8
|
+
remoteOpenDeviceList: { zh: '查看我的设备', en: 'View My Devices' },
|
|
9
|
+
remoteOpenWebHint: {
|
|
10
|
+
zh: '开启后,这台设备会出现在 NextClaw Web 中,你可以在那里点击打开并继续使用。',
|
|
11
|
+
en: 'Once enabled, this device appears in NextClaw Web, where you can open it and keep working.'
|
|
12
|
+
},
|
|
13
|
+
remoteOpenWebUnavailable: {
|
|
14
|
+
zh: '暂时还没有可用的平台地址,请先完成登录。',
|
|
15
|
+
en: 'No platform URL is available yet. Sign in first.'
|
|
6
16
|
},
|
|
7
17
|
remoteLoading: { zh: '正在加载远程访问状态...', en: 'Loading remote access status...' },
|
|
18
|
+
remoteStatusNeedsSignIn: { zh: '先登录 NextClaw', en: 'Sign in to NextClaw first' },
|
|
19
|
+
remoteStatusNeedsSignInDescription: {
|
|
20
|
+
zh: '远程访问依赖 NextClaw 账号。登录后,这台设备才能和网页版关联起来。',
|
|
21
|
+
en: 'Remote access depends on your NextClaw account. Sign in first to link this device to the web app.'
|
|
22
|
+
},
|
|
23
|
+
remoteStatusNeedsEnable: { zh: '还没有开启远程访问', en: 'Remote access is not enabled yet' },
|
|
24
|
+
remoteStatusNeedsEnableDescription: {
|
|
25
|
+
zh: '你已经登录 NextClaw。开启后,这台设备会出现在网页版的设备列表中。',
|
|
26
|
+
en: 'You are already signed in. Enable remote access and this device will appear in your web device list.'
|
|
27
|
+
},
|
|
28
|
+
remoteStatusConnectingTitle: { zh: '正在把这台设备接入 NextClaw Web', en: 'Connecting this device to NextClaw Web' },
|
|
29
|
+
remoteStatusConnectingDescription: {
|
|
30
|
+
zh: '后台服务正在建立连接,几秒后刷新即可看到最新状态。',
|
|
31
|
+
en: 'The managed service is establishing the connection. Refresh in a few seconds to see the latest state.'
|
|
32
|
+
},
|
|
33
|
+
remoteStatusReadyTitle: { zh: '这台设备已经可在网页中打开', en: 'This device is ready in the web app' },
|
|
34
|
+
remoteStatusReadyDescription: {
|
|
35
|
+
zh: '你现在可以前往 NextClaw Web,在设备列表中点击打开,继续这条 Agent 链路。',
|
|
36
|
+
en: 'You can now open NextClaw Web, find this device in the list, and continue your agent workflow there.'
|
|
37
|
+
},
|
|
38
|
+
remoteStatusNeedsServiceTitle: { zh: '需要拉起后台服务', en: 'The managed service needs to run' },
|
|
39
|
+
remoteStatusNeedsServiceDescription: {
|
|
40
|
+
zh: '远程访问已经开启,但后台服务没有运行。拉起后才会真正连到网页版。',
|
|
41
|
+
en: 'Remote access is enabled, but the managed service is not running yet. Start it to connect to the web app.'
|
|
42
|
+
},
|
|
43
|
+
remoteStatusIssueTitle: { zh: '远程连接当前有异常', en: 'The remote connection needs attention' },
|
|
44
|
+
remoteStatusIssueDescription: {
|
|
45
|
+
zh: '账号和设备配置都还在,但当前没有稳定连上平台。你可以重新连接,或先去设备列表确认这台设备的状态。',
|
|
46
|
+
en: 'Your account and device settings are still there, but this device is not stably connected to the platform right now. Reconnect it or check the device list first.'
|
|
47
|
+
},
|
|
48
|
+
remoteStatusIssueDetailTitle: { zh: '当前提示', en: 'Current Hint' },
|
|
49
|
+
remoteStatusIssueDetailGeneric: {
|
|
50
|
+
zh: '连接曾经建立,但随后被平台侧主动关闭。常见原因包括登录态失效、平台侧中继不可用,或云端配额暂时触顶。',
|
|
51
|
+
en: 'The connection was established and then closed by the platform. Common causes include an expired session, an unavailable relay, or a temporary cloud quota limit.'
|
|
52
|
+
},
|
|
53
|
+
remoteStatusIssueDetailServiceStopped: {
|
|
54
|
+
zh: '本地托管服务没有在运行,所以远程连接不会保持在线。',
|
|
55
|
+
en: 'The local managed service is not running, so the remote connection cannot stay online.'
|
|
56
|
+
},
|
|
57
|
+
remoteSignInAndEnable: { zh: '登录并开启远程访问', en: 'Sign In and Enable Remote Access' },
|
|
58
|
+
remoteEnableNow: { zh: '开启远程访问', en: 'Enable Remote Access' },
|
|
59
|
+
remoteReconnectNow: { zh: '重新连接', en: 'Reconnect' },
|
|
60
|
+
remoteDisable: { zh: '关闭远程访问', en: 'Disable Remote Access' },
|
|
61
|
+
remoteDeviceSummaryTitle: { zh: '当前设备', en: 'This Device' },
|
|
62
|
+
remoteDeviceSummaryDescription: {
|
|
63
|
+
zh: '普通用户只需要关心账号、设备名、连接状态和网页版入口。',
|
|
64
|
+
en: 'Users only need the account, device name, connection state, and web entry.'
|
|
65
|
+
},
|
|
66
|
+
remoteSignedInAccount: { zh: '当前账号', en: 'Signed-in Account' },
|
|
67
|
+
remoteConnectionStatus: { zh: '连接状态', en: 'Connection Status' },
|
|
68
|
+
remoteAdvancedTitle: { zh: '高级设置', en: 'Advanced Settings' },
|
|
69
|
+
remoteAdvancedDescription: {
|
|
70
|
+
zh: '只有在排查或自定义平台地址时,才需要打开这一层。',
|
|
71
|
+
en: 'Only open this section when you need diagnostics or a custom platform API base.'
|
|
72
|
+
},
|
|
73
|
+
remoteAdvancedToggleOpen: { zh: '展开高级设置', en: 'Show Advanced Settings' },
|
|
74
|
+
remoteAdvancedToggleClose: { zh: '收起高级设置', en: 'Hide Advanced Settings' },
|
|
75
|
+
remoteAdvancedSaved: { zh: '高级设置已保存', en: 'Advanced settings saved' },
|
|
76
|
+
remoteEnabledReady: { zh: '远程访问已开启,现在可以前往 NextClaw Web 使用', en: 'Remote access is enabled. You can now use NextClaw Web.' },
|
|
77
|
+
remoteDisabledDone: { zh: '远程访问已关闭', en: 'Remote access is disabled' },
|
|
78
|
+
remoteServiceRecovered: { zh: '后台服务已重新接上远程访问', en: 'The managed service is connected again' },
|
|
79
|
+
remoteActionEnabling: { zh: '正在开启远程访问...', en: 'Enabling remote access...' },
|
|
80
|
+
remoteActionDisabling: { zh: '正在关闭远程访问...', en: 'Disabling remote access...' },
|
|
81
|
+
remoteActionSavingAdvanced: { zh: '正在保存高级设置...', en: 'Saving advanced settings...' },
|
|
82
|
+
remoteActionStarting: { zh: '正在启动后台服务...', en: 'Starting the managed service...' },
|
|
83
|
+
remoteActionRestarting: { zh: '正在重启后台服务...', en: 'Restarting the managed service...' },
|
|
84
|
+
remoteActionStopping: { zh: '正在停止后台服务...', en: 'Stopping the managed service...' },
|
|
85
|
+
remoteAccountEntryTitle: { zh: 'NextClaw 账号', en: 'NextClaw Account' },
|
|
86
|
+
remoteAccountEntryDisconnected: { zh: '未登录,点击连接', en: 'Not signed in. Click to connect.' },
|
|
87
|
+
remoteAccountEntryConnected: { zh: '已连接到 NextClaw', en: 'Connected to NextClaw' },
|
|
88
|
+
remoteAccountEntryManage: { zh: '账号与设备入口', en: 'Account and Device Entry' },
|
|
89
|
+
accountPanelTitle: { zh: 'NextClaw 账号', en: 'NextClaw Account' },
|
|
90
|
+
accountPanelDescription: {
|
|
91
|
+
zh: '远程访问依赖这个账号登录。后续 token、授权和更多云端能力也会基于它展开。',
|
|
92
|
+
en: 'Remote access depends on this account. Tokens, authorization, and future cloud capabilities will build on it.'
|
|
93
|
+
},
|
|
94
|
+
accountPanelSignedInTitle: { zh: '账号已连接', en: 'Account Connected' },
|
|
95
|
+
accountPanelSignedInDescription: {
|
|
96
|
+
zh: '这台设备已经和你的 NextClaw 账号关联,可以直接去网页版查看设备。',
|
|
97
|
+
en: 'This device is linked to your NextClaw account. You can go to the web app and open the device there.'
|
|
98
|
+
},
|
|
99
|
+
accountPanelSignedOutTitle: { zh: '通过浏览器完成登录', en: 'Continue Sign-In in Your Browser' },
|
|
100
|
+
accountPanelSignedOutDescription: {
|
|
101
|
+
zh: '点击下方按钮后会打开 NextClaw 网页,在网页中登录或注册,当前设备会自动接入。',
|
|
102
|
+
en: 'Click the button below to open NextClaw Web, sign in or create an account there, and this device will attach automatically.'
|
|
103
|
+
},
|
|
8
104
|
remoteOverviewTitle: { zh: '连接总览', en: 'Connection Overview' },
|
|
9
105
|
remoteOverviewDescription: {
|
|
10
|
-
zh: '
|
|
11
|
-
en: '
|
|
106
|
+
zh: '只保留普通用户真正需要知道的信息。',
|
|
107
|
+
en: 'Keep only the information ordinary users actually need.'
|
|
12
108
|
},
|
|
13
109
|
remoteAccountConnected: { zh: '平台已登录', en: 'Platform Connected' },
|
|
14
110
|
remoteAccountNotConnected: { zh: '平台未登录', en: 'Platform Not Connected' },
|
|
@@ -21,6 +117,7 @@ export const REMOTE_LABELS: Record<string, { zh: string; en: string }> = {
|
|
|
21
117
|
remoteLocalOrigin: { zh: '本地服务地址', en: 'Local Origin' },
|
|
22
118
|
remotePublicPlatform: { zh: '平台地址', en: 'Platform Base' },
|
|
23
119
|
remoteDeviceId: { zh: '设备 ID', en: 'Device ID' },
|
|
120
|
+
remoteRuntimeUpdatedAt: { zh: '状态更新时间', en: 'Status Updated At' },
|
|
24
121
|
remoteLastConnectedAt: { zh: '上次连接时间', en: 'Last Connected At' },
|
|
25
122
|
remoteLastError: { zh: '最近错误', en: 'Last Error' },
|
|
26
123
|
remoteDeviceTitle: { zh: '设备配置', en: 'Device Settings' },
|
|
@@ -28,6 +125,12 @@ export const REMOTE_LABELS: Record<string, { zh: string; en: string }> = {
|
|
|
28
125
|
zh: '保存远程访问开关、设备名和平台 API Base。',
|
|
29
126
|
en: 'Save remote access state, device name, and platform API base.'
|
|
30
127
|
},
|
|
128
|
+
remoteDeviceSectionTitle: { zh: '设备信息', en: 'Device Info' },
|
|
129
|
+
remoteDeviceSectionDescription: {
|
|
130
|
+
zh: '开启之后,这台设备会在平台网页的设备列表中出现。',
|
|
131
|
+
en: 'Once enabled, this device will appear in the platform web device list.'
|
|
132
|
+
},
|
|
133
|
+
remoteDeviceNameAuto: { zh: '未命名设备', en: 'Unnamed Device' },
|
|
31
134
|
remoteEnabled: { zh: '启用远程访问', en: 'Enable Remote Access' },
|
|
32
135
|
remoteEnabledHelp: {
|
|
33
136
|
zh: '保存后需要启动或重启后台服务,新的远程配置才会真正生效。',
|
|
@@ -61,6 +164,7 @@ export const REMOTE_LABELS: Record<string, { zh: string; en: string }> = {
|
|
|
61
164
|
en: 'Open the platform authorization page in your browser, then sign in or create an account there.'
|
|
62
165
|
},
|
|
63
166
|
remoteBrowserAuthAction: { zh: '前往浏览器授权', en: 'Continue in Browser' },
|
|
167
|
+
remoteBrowserAuthActionRetry: { zh: '重新发起浏览器登录', en: 'Restart Browser Sign-In' },
|
|
64
168
|
remoteBrowserAuthResume: { zh: '重新打开授权页', en: 'Reopen Authorization Page' },
|
|
65
169
|
remoteBrowserAuthStarting: { zh: '正在创建授权会话...', en: 'Starting authorization...' },
|
|
66
170
|
remoteBrowserAuthAuthorizing: { zh: '等待浏览器完成授权...', en: 'Waiting for browser authorization...' },
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { createContext, useContext, type ReactNode } from 'react';
|
|
2
|
+
import { appPresenter, type AppPresenter } from '@/presenter/app.presenter';
|
|
3
|
+
|
|
4
|
+
const AppPresenterContext = createContext<AppPresenter | null>(null);
|
|
5
|
+
|
|
6
|
+
type AppPresenterProviderProps = {
|
|
7
|
+
children: ReactNode;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export function AppPresenterProvider({ children }: AppPresenterProviderProps) {
|
|
11
|
+
return <AppPresenterContext.Provider value={appPresenter}>{children}</AppPresenterContext.Provider>;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function useAppPresenter() {
|
|
15
|
+
const presenter = useContext(AppPresenterContext);
|
|
16
|
+
if (!presenter) {
|
|
17
|
+
throw new Error('useAppPresenter must be used inside AppPresenterProvider');
|
|
18
|
+
}
|
|
19
|
+
return presenter;
|
|
20
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { AccountManager } from '@/account/managers/account.manager';
|
|
2
|
+
import { RemoteAccessManager } from '@/remote/managers/remote-access.manager';
|
|
3
|
+
|
|
4
|
+
export class AppPresenter {
|
|
5
|
+
accountManager = new AccountManager();
|
|
6
|
+
remoteAccessManager = new RemoteAccessManager();
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export const appPresenter = new AppPresenter();
|
|
10
|
+
|
|
11
|
+
appPresenter.accountManager.bindSignedInContinuation(appPresenter.remoteAccessManager.resumePendingActionAfterSignIn);
|
|
12
|
+
appPresenter.remoteAccessManager.bindAccountManager(appPresenter.accountManager);
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { controlRemoteService, fetchRemoteDoctor, updateRemoteSettings } from '@/api/remote';
|
|
2
|
+
import type { RemoteAccessView } from '@/api/remote.types';
|
|
3
|
+
import type { AccountPendingAction } from '@/account/stores/account.store';
|
|
4
|
+
import type { AccountManager } from '@/account/managers/account.manager';
|
|
5
|
+
import { useRemoteAccessStore } from '@/remote/stores/remote-access.store';
|
|
6
|
+
import { refreshRemoteStatus } from '@/remote/remote-access.query';
|
|
7
|
+
import { t } from '@/lib/i18n';
|
|
8
|
+
import { toast } from 'sonner';
|
|
9
|
+
|
|
10
|
+
export class RemoteAccessManager {
|
|
11
|
+
private accountManager: AccountManager | null = null;
|
|
12
|
+
|
|
13
|
+
bindAccountManager = (accountManager: AccountManager) => {
|
|
14
|
+
this.accountManager = accountManager;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
syncStatus = (status: RemoteAccessView | undefined) => {
|
|
18
|
+
if (!status) {
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
const store = useRemoteAccessStore.getState();
|
|
22
|
+
if (store.draftTouched || store.actionLabel) {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
this.hydrateDraftFromStatus(status);
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
setEnabled = (enabled: boolean) => {
|
|
29
|
+
useRemoteAccessStore.getState().setEnabled(enabled);
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
setDeviceName = (deviceName: string) => {
|
|
33
|
+
useRemoteAccessStore.getState().setDeviceName(deviceName);
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
setPlatformApiBase = (platformApiBase: string) => {
|
|
37
|
+
useRemoteAccessStore.getState().setPlatformApiBase(platformApiBase);
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
setAdvancedOpen = (advancedOpen: boolean) => {
|
|
41
|
+
useRemoteAccessStore.getState().setAdvancedOpen(advancedOpen);
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
enableRemoteAccess = async (status: RemoteAccessView | undefined) => {
|
|
45
|
+
const currentStatus = status ?? (await refreshRemoteStatus());
|
|
46
|
+
const draft = useRemoteAccessStore.getState();
|
|
47
|
+
if (!currentStatus.account.loggedIn) {
|
|
48
|
+
await this.accountManager?.ensureSignedIn({
|
|
49
|
+
pendingAction: { type: 'enable-remote' },
|
|
50
|
+
apiBase: draft.platformApiBase
|
|
51
|
+
});
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
await this.applyEnabledState(true, currentStatus);
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
disableRemoteAccess = async (status: RemoteAccessView | undefined) => {
|
|
58
|
+
const currentStatus = status ?? (await refreshRemoteStatus());
|
|
59
|
+
await this.applyEnabledState(false, currentStatus);
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
repairRemoteAccess = async (status: RemoteAccessView | undefined) => {
|
|
63
|
+
const currentStatus = status ?? (await refreshRemoteStatus());
|
|
64
|
+
if (!currentStatus.account.loggedIn) {
|
|
65
|
+
await this.accountManager?.ensureSignedIn({
|
|
66
|
+
pendingAction: { type: 'enable-remote' }
|
|
67
|
+
});
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
const action = currentStatus.service.running ? 'restart' : 'start';
|
|
71
|
+
await this.runManagedAction({
|
|
72
|
+
actionLabel: action === 'restart' ? t('remoteActionRestarting') : t('remoteActionStarting'),
|
|
73
|
+
job: async () => {
|
|
74
|
+
await controlRemoteService(action);
|
|
75
|
+
const nextStatus = await refreshRemoteStatus();
|
|
76
|
+
this.hydrateDraftFromStatus(nextStatus);
|
|
77
|
+
},
|
|
78
|
+
successMessage: t('remoteServiceRecovered')
|
|
79
|
+
});
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
saveAdvancedSettings = async (status: RemoteAccessView | undefined) => {
|
|
83
|
+
const currentStatus = status ?? (await refreshRemoteStatus());
|
|
84
|
+
const draft = useRemoteAccessStore.getState();
|
|
85
|
+
await this.runManagedAction({
|
|
86
|
+
actionLabel: t('remoteActionSavingAdvanced'),
|
|
87
|
+
job: async () => {
|
|
88
|
+
await updateRemoteSettings({
|
|
89
|
+
enabled: draft.enabled,
|
|
90
|
+
deviceName: draft.deviceName.trim(),
|
|
91
|
+
platformApiBase: draft.platformApiBase.trim()
|
|
92
|
+
});
|
|
93
|
+
const nextStatus = await refreshRemoteStatus();
|
|
94
|
+
this.hydrateDraftFromStatus(nextStatus);
|
|
95
|
+
},
|
|
96
|
+
successMessage: currentStatus.settings.enabled === draft.enabled ? t('remoteSettingsSaved') : t('remoteAdvancedSaved')
|
|
97
|
+
});
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
runDoctor = async () => {
|
|
101
|
+
await this.runManagedAction({
|
|
102
|
+
actionLabel: t('remoteDoctorRunning'),
|
|
103
|
+
job: async () => {
|
|
104
|
+
const doctor = await fetchRemoteDoctor();
|
|
105
|
+
useRemoteAccessStore.getState().setDoctor(doctor);
|
|
106
|
+
},
|
|
107
|
+
successMessage: t('remoteDoctorCompleted')
|
|
108
|
+
});
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
startService = async () => {
|
|
112
|
+
await this.runServiceAction('start', t('remoteActionStarting'));
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
restartService = async () => {
|
|
116
|
+
await this.runServiceAction('restart', t('remoteActionRestarting'));
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
stopService = async () => {
|
|
120
|
+
await this.runServiceAction('stop', t('remoteActionStopping'));
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
resumePendingActionAfterSignIn = async (action: AccountPendingAction, status: RemoteAccessView) => {
|
|
124
|
+
if (!action) {
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
if (action.type === 'enable-remote') {
|
|
128
|
+
await this.applyEnabledState(true, status);
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
private applyEnabledState = async (enabled: boolean, status: RemoteAccessView) => {
|
|
133
|
+
const draft = useRemoteAccessStore.getState();
|
|
134
|
+
await this.runManagedAction({
|
|
135
|
+
actionLabel: enabled ? t('remoteActionEnabling') : t('remoteActionDisabling'),
|
|
136
|
+
job: async () => {
|
|
137
|
+
await updateRemoteSettings({
|
|
138
|
+
enabled,
|
|
139
|
+
deviceName: draft.deviceName.trim(),
|
|
140
|
+
platformApiBase: draft.platformApiBase.trim()
|
|
141
|
+
});
|
|
142
|
+
const nextStatus = await refreshRemoteStatus();
|
|
143
|
+
this.hydrateDraftFromStatus(nextStatus);
|
|
144
|
+
if (enabled) {
|
|
145
|
+
const action = nextStatus.service.running ? 'restart' : 'start';
|
|
146
|
+
await controlRemoteService(action);
|
|
147
|
+
} else if (status.service.running) {
|
|
148
|
+
await controlRemoteService('restart');
|
|
149
|
+
}
|
|
150
|
+
const finalStatus = await refreshRemoteStatus();
|
|
151
|
+
this.hydrateDraftFromStatus(finalStatus);
|
|
152
|
+
},
|
|
153
|
+
successMessage: enabled ? t('remoteEnabledReady') : t('remoteDisabledDone')
|
|
154
|
+
});
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
private runServiceAction = async (action: 'start' | 'restart' | 'stop', actionLabel: string) => {
|
|
158
|
+
await this.runManagedAction({
|
|
159
|
+
actionLabel,
|
|
160
|
+
job: async () => {
|
|
161
|
+
const result = await controlRemoteService(action);
|
|
162
|
+
const nextStatus = await refreshRemoteStatus();
|
|
163
|
+
this.hydrateDraftFromStatus(nextStatus);
|
|
164
|
+
toast.success(result.message);
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
private hydrateDraftFromStatus = (status: RemoteAccessView) => {
|
|
170
|
+
useRemoteAccessStore.getState().hydrateDraft({
|
|
171
|
+
enabled: status.settings.enabled,
|
|
172
|
+
deviceName: status.settings.deviceName,
|
|
173
|
+
platformApiBase: status.settings.platformApiBase
|
|
174
|
+
});
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
private runManagedAction = async (params: {
|
|
178
|
+
actionLabel: string;
|
|
179
|
+
job: () => Promise<void>;
|
|
180
|
+
successMessage?: string;
|
|
181
|
+
}) => {
|
|
182
|
+
useRemoteAccessStore.getState().beginAction(params.actionLabel);
|
|
183
|
+
try {
|
|
184
|
+
await params.job();
|
|
185
|
+
if (params.successMessage) {
|
|
186
|
+
toast.success(params.successMessage);
|
|
187
|
+
}
|
|
188
|
+
} catch (error) {
|
|
189
|
+
const message = error instanceof Error ? error.message : t('error');
|
|
190
|
+
toast.error(message);
|
|
191
|
+
throw error;
|
|
192
|
+
} finally {
|
|
193
|
+
useRemoteAccessStore.getState().finishAction();
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { fetchRemoteStatus } from '@/api/remote';
|
|
2
|
+
import type { RemoteAccessView } from '@/api/remote.types';
|
|
3
|
+
import { appQueryClient } from '@/app-query-client';
|
|
4
|
+
|
|
5
|
+
export const REMOTE_STATUS_QUERY_KEY = ['remote-status'] as const;
|
|
6
|
+
const DEFAULT_NEXTCLAW_WEB_BASE = 'https://platform.nextclaw.io';
|
|
7
|
+
const PREVIEW_NEXTCLAW_WEB_BASE = 'https://nextclaw-platform-console.pages.dev';
|
|
8
|
+
|
|
9
|
+
export const getRemoteStatusSnapshot = () => {
|
|
10
|
+
return appQueryClient.getQueryData<RemoteAccessView>(REMOTE_STATUS_QUERY_KEY);
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export const ensureRemoteStatus = async () => {
|
|
14
|
+
return await appQueryClient.fetchQuery({
|
|
15
|
+
queryKey: REMOTE_STATUS_QUERY_KEY,
|
|
16
|
+
queryFn: fetchRemoteStatus,
|
|
17
|
+
staleTime: 5000
|
|
18
|
+
});
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export const refreshRemoteStatus = async () => {
|
|
22
|
+
await appQueryClient.invalidateQueries({ queryKey: REMOTE_STATUS_QUERY_KEY });
|
|
23
|
+
return await ensureRemoteStatus();
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export const resolveRemotePlatformApiBase = (status: RemoteAccessView | undefined, override?: string) => {
|
|
27
|
+
const trimmedOverride = override?.trim();
|
|
28
|
+
if (trimmedOverride) {
|
|
29
|
+
return trimmedOverride;
|
|
30
|
+
}
|
|
31
|
+
const trimmedSettingsBase = status?.settings.platformApiBase?.trim();
|
|
32
|
+
if (trimmedSettingsBase) {
|
|
33
|
+
return trimmedSettingsBase;
|
|
34
|
+
}
|
|
35
|
+
return status?.account.apiBase?.trim() || undefined;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export const resolveRemotePlatformBase = (status: RemoteAccessView | undefined) => {
|
|
39
|
+
return status?.platformBase?.trim() || status?.account.platformBase?.trim() || undefined;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
function resolveWebBaseFromRawUrl(rawUrl: string | null | undefined): string | undefined {
|
|
43
|
+
const trimmed = rawUrl?.trim();
|
|
44
|
+
if (!trimmed) {
|
|
45
|
+
return undefined;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
let parsedUrl: URL;
|
|
49
|
+
try {
|
|
50
|
+
parsedUrl = new URL(trimmed);
|
|
51
|
+
} catch {
|
|
52
|
+
return undefined;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (parsedUrl.hostname === 'platform.nextclaw.io' || parsedUrl.hostname === 'nextclaw-platform-console.pages.dev') {
|
|
56
|
+
return parsedUrl.origin;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (parsedUrl.hostname === 'ai-gateway-api.nextclaw.io') {
|
|
60
|
+
return DEFAULT_NEXTCLAW_WEB_BASE;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (parsedUrl.hostname.includes('nextclaw-provider-gateway-api') && parsedUrl.hostname.endsWith('.workers.dev')) {
|
|
64
|
+
return PREVIEW_NEXTCLAW_WEB_BASE;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return undefined;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export const resolveRemoteWebBase = (status: RemoteAccessView | undefined) => {
|
|
71
|
+
return (
|
|
72
|
+
resolveWebBaseFromRawUrl(status?.account.apiBase) ||
|
|
73
|
+
resolveWebBaseFromRawUrl(status?.settings.platformApiBase) ||
|
|
74
|
+
resolveWebBaseFromRawUrl(status?.platformBase) ||
|
|
75
|
+
resolveWebBaseFromRawUrl(status?.account.platformBase) ||
|
|
76
|
+
undefined
|
|
77
|
+
);
|
|
78
|
+
};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { RemoteDoctorView } from '@/api/remote.types';
|
|
2
|
+
import { create } from 'zustand';
|
|
3
|
+
|
|
4
|
+
type RemoteAccessStoreState = {
|
|
5
|
+
enabled: boolean;
|
|
6
|
+
deviceName: string;
|
|
7
|
+
platformApiBase: string;
|
|
8
|
+
draftTouched: boolean;
|
|
9
|
+
advancedOpen: boolean;
|
|
10
|
+
actionLabel: string | null;
|
|
11
|
+
doctor: RemoteDoctorView | null;
|
|
12
|
+
setEnabled: (enabled: boolean) => void;
|
|
13
|
+
setDeviceName: (deviceName: string) => void;
|
|
14
|
+
setPlatformApiBase: (platformApiBase: string) => void;
|
|
15
|
+
setAdvancedOpen: (advancedOpen: boolean) => void;
|
|
16
|
+
hydrateDraft: (payload: { enabled: boolean; deviceName: string; platformApiBase: string }) => void;
|
|
17
|
+
beginAction: (actionLabel: string) => void;
|
|
18
|
+
finishAction: () => void;
|
|
19
|
+
setDoctor: (doctor: RemoteDoctorView | null) => void;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export const useRemoteAccessStore = create<RemoteAccessStoreState>((set) => ({
|
|
23
|
+
enabled: false,
|
|
24
|
+
deviceName: '',
|
|
25
|
+
platformApiBase: '',
|
|
26
|
+
advancedOpen: false,
|
|
27
|
+
draftTouched: false,
|
|
28
|
+
actionLabel: null,
|
|
29
|
+
doctor: null,
|
|
30
|
+
setEnabled: (enabled) => set({ enabled, draftTouched: true }),
|
|
31
|
+
setDeviceName: (deviceName) => set({ deviceName, draftTouched: true }),
|
|
32
|
+
setPlatformApiBase: (platformApiBase) => set({ platformApiBase, draftTouched: true }),
|
|
33
|
+
setAdvancedOpen: (advancedOpen) => set({ advancedOpen }),
|
|
34
|
+
hydrateDraft: ({ enabled, deviceName, platformApiBase }) =>
|
|
35
|
+
set({
|
|
36
|
+
enabled,
|
|
37
|
+
deviceName,
|
|
38
|
+
platformApiBase,
|
|
39
|
+
draftTouched: false
|
|
40
|
+
}),
|
|
41
|
+
beginAction: (actionLabel) => set({ actionLabel }),
|
|
42
|
+
finishAction: () => set({ actionLabel: null }),
|
|
43
|
+
setDoctor: (doctor) => set({ doctor })
|
|
44
|
+
}));
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{r as v,j as a,ax as Z,J as ee,e as F,K as ae,af as te,aS as se,aT as ne,aU as le,D as re,s as oe,a7 as ce,v as ie}from"./vendor-CwsIoNvJ.js";import{t as e,c as I,_ as me,u as q,a as $,b as H,a0 as pe,a1 as de,I as D,S as be,e as ue,f as xe,g as ye,h as ge,B as E}from"./index-CeRbsQ90.js";import{L as he}from"./label-CCSffS1D.js";import{S as fe}from"./switch-B-_SrMSL.js";import{S as J}from"./status-dot-Fz9-eKsl.js";import{L as K}from"./LogoBadge-D7j1al-w.js";import{h as _}from"./config-hints-CApS3K_7.js";import{c as we,b as ve,a as je,C as ke}from"./config-layout-BHnOoweL.js";import{T as Se}from"./tabs-custom-6Tm1ZHfS.js";import{P as Ce,a as Ne}from"./page-layout-ud8wZ8gX.js";function Pe({value:t,onChange:m,className:i,placeholder:r=""}){const[o,u]=v.useState(""),d=x=>{x.key==="Enter"&&o.trim()?(x.preventDefault(),m([...t,o.trim()]),u("")):x.key==="Backspace"&&!o&&t.length>0&&m(t.slice(0,-1))},g=x=>{m(t.filter((j,h)=>h!==x))};return a.jsxs("div",{className:I("flex flex-wrap gap-2 p-2 border rounded-md min-h-[42px]",i),children:[t.map((x,j)=>a.jsxs("span",{className:"inline-flex items-center gap-1 px-2 py-1 bg-primary text-primary-foreground rounded text-sm",children:[x,a.jsx("button",{type:"button",onClick:()=>g(j),className:"hover:text-red-300 transition-colors",children:a.jsx(Z,{className:"h-3 w-3"})})]},j)),a.jsx("input",{type:"text",value:o,onChange:x=>u(x.target.value),onKeyDown:d,className:"flex-1 outline-none min-w-[100px] bg-transparent text-sm",placeholder:r||e("enterTag")})]})}function z(t){var r,o;const m=me();return((r=t.tutorialUrls)==null?void 0:r[m])||((o=t.tutorialUrls)==null?void 0:o.default)||t.tutorialUrl}const Ie={telegram:"telegram.svg",slack:"slack.svg",discord:"discord.svg",whatsapp:"whatsapp.svg",qq:"qq.svg",feishu:"feishu.svg",dingtalk:"dingtalk.svg",wecom:"wecom.svg",mochat:"mochat.svg",email:"email.svg"};function Te(t,m){const i=m.toLowerCase(),r=t[i];return r?`/logos/${r}`:null}function Y(t){return Te(Ie,t)}const B=[{value:"pairing",label:"pairing"},{value:"allowlist",label:"allowlist"},{value:"open",label:"open"},{value:"disabled",label:"disabled"}],R=[{value:"open",label:"open"},{value:"allowlist",label:"allowlist"},{value:"disabled",label:"disabled"}],Fe=[{value:"off",label:"off"},{value:"partial",label:"partial"},{value:"block",label:"block"},{value:"progress",label:"progress"}],De=t=>t.includes("token")||t.includes("secret")||t.includes("password")?a.jsx(ae,{className:"h-3.5 w-3.5 text-gray-500"}):t.includes("url")||t.includes("host")?a.jsx(te,{className:"h-3.5 w-3.5 text-gray-500"}):t.includes("email")||t.includes("mail")?a.jsx(se,{className:"h-3.5 w-3.5 text-gray-500"}):t.includes("id")||t.includes("from")?a.jsx(ne,{className:"h-3.5 w-3.5 text-gray-500"}):t==="enabled"||t==="consentGranted"?a.jsx(le,{className:"h-3.5 w-3.5 text-gray-500"}):a.jsx(re,{className:"h-3.5 w-3.5 text-gray-500"});function G(){return{telegram:[{name:"enabled",type:"boolean",label:e("enabled")},{name:"token",type:"password",label:e("botToken")},{name:"allowFrom",type:"tags",label:e("allowFrom")},{name:"proxy",type:"text",label:e("proxy")},{name:"accountId",type:"text",label:e("accountId")},{name:"dmPolicy",type:"select",label:e("dmPolicy"),options:B},{name:"groupPolicy",type:"select",label:e("groupPolicy"),options:R},{name:"groupAllowFrom",type:"tags",label:e("groupAllowFrom")},{name:"requireMention",type:"boolean",label:e("requireMention")},{name:"mentionPatterns",type:"tags",label:e("mentionPatterns")},{name:"groups",type:"json",label:e("groupRulesJson")}],discord:[{name:"enabled",type:"boolean",label:e("enabled")},{name:"token",type:"password",label:e("botToken")},{name:"allowBots",type:"boolean",label:e("allowBotMessages")},{name:"allowFrom",type:"tags",label:e("allowFrom")},{name:"gatewayUrl",type:"text",label:e("gatewayUrl")},{name:"intents",type:"number",label:e("intents")},{name:"proxy",type:"text",label:e("proxy")},{name:"mediaMaxMb",type:"number",label:e("attachmentMaxSizeMb")},{name:"streaming",type:"select",label:e("streamingMode"),options:Fe},{name:"draftChunk",type:"json",label:e("draftChunkingJson")},{name:"textChunkLimit",type:"number",label:e("textChunkLimit")},{name:"accountId",type:"text",label:e("accountId")},{name:"dmPolicy",type:"select",label:e("dmPolicy"),options:B},{name:"groupPolicy",type:"select",label:e("groupPolicy"),options:R},{name:"groupAllowFrom",type:"tags",label:e("groupAllowFrom")},{name:"requireMention",type:"boolean",label:e("requireMention")},{name:"mentionPatterns",type:"tags",label:e("mentionPatterns")},{name:"groups",type:"json",label:e("groupRulesJson")}],whatsapp:[{name:"enabled",type:"boolean",label:e("enabled")},{name:"bridgeUrl",type:"text",label:e("bridgeUrl")},{name:"allowFrom",type:"tags",label:e("allowFrom")}],feishu:[{name:"enabled",type:"boolean",label:e("enabled")},{name:"appId",type:"text",label:e("appId")},{name:"appSecret",type:"password",label:e("appSecret")},{name:"encryptKey",type:"password",label:e("encryptKey")},{name:"verificationToken",type:"password",label:e("verificationToken")},{name:"allowFrom",type:"tags",label:e("allowFrom")}],dingtalk:[{name:"enabled",type:"boolean",label:e("enabled")},{name:"clientId",type:"text",label:e("clientId")},{name:"clientSecret",type:"password",label:e("clientSecret")},{name:"allowFrom",type:"tags",label:e("allowFrom")}],wecom:[{name:"enabled",type:"boolean",label:e("enabled")},{name:"corpId",type:"text",label:e("corpId")},{name:"agentId",type:"text",label:e("agentId")},{name:"secret",type:"password",label:e("secret")},{name:"token",type:"password",label:e("token")},{name:"callbackPort",type:"number",label:e("callbackPort")},{name:"callbackPath",type:"text",label:e("callbackPath")},{name:"allowFrom",type:"tags",label:e("allowFrom")}],slack:[{name:"enabled",type:"boolean",label:e("enabled")},{name:"mode",type:"text",label:e("mode")},{name:"webhookPath",type:"text",label:e("webhookPath")},{name:"allowBots",type:"boolean",label:e("allowBotMessages")},{name:"botToken",type:"password",label:e("botToken")},{name:"appToken",type:"password",label:e("appToken")}],email:[{name:"enabled",type:"boolean",label:e("enabled")},{name:"consentGranted",type:"boolean",label:e("consentGranted")},{name:"imapHost",type:"text",label:e("imapHost")},{name:"imapPort",type:"number",label:e("imapPort")},{name:"imapUsername",type:"text",label:e("imapUsername")},{name:"imapPassword",type:"password",label:e("imapPassword")},{name:"fromAddress",type:"email",label:e("fromAddress")}],mochat:[{name:"enabled",type:"boolean",label:e("enabled")},{name:"baseUrl",type:"text",label:e("baseUrl")},{name:"clawToken",type:"password",label:e("clawToken")},{name:"agentUserId",type:"text",label:e("agentUserId")},{name:"allowFrom",type:"tags",label:e("allowFrom")}],qq:[{name:"enabled",type:"boolean",label:e("enabled")},{name:"appId",type:"text",label:e("appId")},{name:"secret",type:"password",label:e("appSecret")},{name:"markdownSupport",type:"boolean",label:e("markdownSupport")},{name:"allowFrom",type:"tags",label:e("allowFrom")}]}}function A(t){return typeof t=="object"&&t!==null&&!Array.isArray(t)}function V(t,m){const i={...t};for(const[r,o]of Object.entries(m)){const u=i[r];if(A(u)&&A(o)){i[r]=V(u,o);continue}i[r]=o}return i}function Ae(t,m){const i=t.split("."),r={};let o=r;for(let u=0;u<i.length-1;u+=1){const d=i[u];o[d]={},o=o[d]}return o[i[i.length-1]]=m,r}function Le({channelName:t}){var O,U;const{data:m}=q(),{data:i}=$(),{data:r}=H(),o=pe(),u=de(),[d,g]=v.useState({}),[x,j]=v.useState({}),[h,f]=v.useState(null),k=t?m==null?void 0:m.channels[t]:null,w=t?G()[t]??[]:[],c=r==null?void 0:r.uiHints,p=t?`channels.${t}`:null,S=((O=r==null?void 0:r.actions)==null?void 0:O.filter(s=>s.scope===p))??[],C=t&&(((U=_(`channels.${t}`,c))==null?void 0:U.label)??t),P=i==null?void 0:i.channels.find(s=>s.name===t),T=P?z(P):void 0;v.useEffect(()=>{if(k){g({...k});const s={};(t?G()[t]??[]:[]).filter(l=>l.type==="json").forEach(l=>{const y=k[l.name];s[l.name]=JSON.stringify(y??{},null,2)}),j(s)}else g({}),j({})},[k,t]);const N=(s,n)=>{g(l=>({...l,[s]:n}))},L=s=>{if(s.preventDefault(),!t)return;const n={...d};for(const l of w){if(l.type!=="password")continue;const y=n[l.name];(typeof y!="string"||y.length===0)&&delete n[l.name]}for(const l of w){if(l.type!=="json")continue;const y=x[l.name]??"";try{n[l.name]=y.trim()?JSON.parse(y):{}}catch{F.error(`${e("invalidJson")}: ${l.name}`);return}}o.mutate({channel:t,data:n})},Q=s=>{if(!s||!t)return;const n=s.channels;if(!A(n))return;const l=n[t];A(l)&&g(y=>V(y,l))},W=async s=>{if(!(!t||!p)){f(s.id);try{let n={...d};s.saveBeforeRun&&(n={...n,...s.savePatch??{}},g(n),await o.mutateAsync({channel:t,data:n}));const l=await u.mutateAsync({actionId:s.id,data:{scope:p,draftConfig:Ae(p,n)}});Q(l.patch),l.ok?F.success(l.message||e("success")):F.error(l.message||e("error"))}catch(n){const l=n instanceof Error?n.message:String(n);F.error(`${e("error")}: ${l}`)}finally{f(null)}}};if(!t||!P||!k)return a.jsx("div",{className:we,children:a.jsxs("div",{children:[a.jsx("h3",{className:"text-base font-semibold text-gray-900",children:e("channelsSelectTitle")}),a.jsx("p",{className:"mt-2 text-sm text-gray-500",children:e("channelsSelectDescription")})]})});const M=!!k.enabled;return a.jsxs("div",{className:ve,children:[a.jsx("div",{className:"border-b border-gray-100 px-6 py-5",children:a.jsxs("div",{className:"flex flex-wrap items-center justify-between gap-3",children:[a.jsxs("div",{className:"min-w-0",children:[a.jsxs("div",{className:"flex items-center gap-3",children:[a.jsx(K,{name:t,src:Y(t),className:I("h-9 w-9 rounded-lg border",M?"border-primary/30 bg-white":"border-gray-200/70 bg-white"),imgClassName:"h-5 w-5 object-contain",fallback:a.jsx("span",{className:"text-sm font-semibold uppercase text-gray-500",children:t[0]})}),a.jsx("h3",{className:"truncate text-lg font-semibold text-gray-900 capitalize",children:C})]}),a.jsx("p",{className:"mt-2 text-sm text-gray-500",children:e("channelsFormDescription")}),T&&a.jsxs("a",{href:T,className:"mt-2 inline-flex items-center gap-1.5 text-xs text-primary transition-colors hover:text-primary-hover",children:[a.jsx(ee,{className:"h-3.5 w-3.5"}),e("channelsGuideTitle")]})]}),a.jsx(J,{status:M?"active":"inactive",label:M?e("statusActive"):e("statusInactive")})]})}),a.jsxs("form",{onSubmit:L,className:"flex min-h-0 flex-1 flex-col",children:[a.jsx("div",{className:"min-h-0 flex-1 space-y-6 overflow-y-auto overscroll-contain px-6 py-5",children:w.map(s=>{const n=t?_(`channels.${t}.${s.name}`,c):void 0,l=(n==null?void 0:n.label)??s.label,y=n==null?void 0:n.placeholder;return a.jsxs("div",{className:"space-y-2.5",children:[a.jsxs(he,{htmlFor:s.name,className:"flex items-center gap-2 text-sm font-medium text-gray-900",children:[De(s.name),l]}),s.type==="boolean"&&a.jsxs("div",{className:"flex items-center justify-between rounded-xl bg-gray-50 p-3",children:[a.jsx("span",{className:"text-sm text-gray-500",children:d[s.name]?e("enabled"):e("disabled")}),a.jsx(fe,{id:s.name,checked:d[s.name]||!1,onCheckedChange:b=>N(s.name,b),className:"data-[state=checked]:bg-emerald-500"})]}),(s.type==="text"||s.type==="email")&&a.jsx(D,{id:s.name,type:s.type,value:d[s.name]||"",onChange:b=>N(s.name,b.target.value),placeholder:y,className:"rounded-xl"}),s.type==="password"&&a.jsx(D,{id:s.name,type:"password",value:d[s.name]||"",onChange:b=>N(s.name,b.target.value),placeholder:y??e("leaveBlankToKeepUnchanged"),className:"rounded-xl"}),s.type==="number"&&a.jsx(D,{id:s.name,type:"number",value:d[s.name]||0,onChange:b=>N(s.name,parseInt(b.target.value,10)||0),placeholder:y,className:"rounded-xl"}),s.type==="tags"&&a.jsx(Pe,{value:d[s.name]||[],onChange:b=>N(s.name,b)}),s.type==="select"&&a.jsxs(be,{value:d[s.name]||"",onValueChange:b=>N(s.name,b),children:[a.jsx(ue,{className:"rounded-xl",children:a.jsx(xe,{})}),a.jsx(ye,{children:(s.options??[]).map(b=>a.jsx(ge,{value:b.value,children:b.label},b.value))})]}),s.type==="json"&&a.jsx("textarea",{id:s.name,value:x[s.name]??"{}",onChange:b=>j(X=>({...X,[s.name]:b.target.value})),className:"min-h-[120px] w-full resize-none rounded-lg border border-gray-200 bg-white px-3 py-2 text-xs font-mono"})]},s.name)})}),a.jsxs("div",{className:"flex flex-wrap items-center justify-between gap-3 border-t border-gray-100 px-6 py-4",children:[a.jsx("div",{className:"flex flex-wrap items-center gap-2",children:S.filter(s=>s.trigger==="manual").map(s=>a.jsx(E,{type:"button",onClick:()=>W(s),disabled:o.isPending||!!h,variant:"secondary",children:h===s.id?e("connecting"):s.title},s.id))}),a.jsx(E,{type:"submit",disabled:o.isPending||!!h,children:o.isPending?e("saving"):e("save")})]})]})]})}const Me={telegram:"channelDescTelegram",slack:"channelDescSlack",email:"channelDescEmail",webhook:"channelDescWebhook",discord:"channelDescDiscord",feishu:"channelDescFeishu"};function Je(){const{data:t}=q(),{data:m}=$(),{data:i}=H(),[r,o]=v.useState("enabled"),[u,d]=v.useState(),[g,x]=v.useState(""),j=i==null?void 0:i.uiHints,h=m==null?void 0:m.channels,f=t==null?void 0:t.channels,k=[{id:"enabled",label:e("channelsTabEnabled"),count:(h??[]).filter(c=>{var p;return(p=f==null?void 0:f[c.name])==null?void 0:p.enabled}).length},{id:"all",label:e("channelsTabAll"),count:(h??[]).length}],w=v.useMemo(()=>{const c=g.trim().toLowerCase();return(h??[]).filter(p=>{var C;const S=((C=f==null?void 0:f[p.name])==null?void 0:C.enabled)||!1;return r==="enabled"?S:!0}).filter(p=>c?(p.displayName||p.name).toLowerCase().includes(c)||p.name.toLowerCase().includes(c):!0)},[r,f,h,g]);return v.useEffect(()=>{if(w.length===0){d(void 0);return}w.some(p=>p.name===u)||d(w[0].name)},[w,u]),!t||!m?a.jsx("div",{className:"p-8 text-gray-400",children:e("channelsLoading")}):a.jsxs(Ce,{className:"xl:flex xl:h-full xl:min-h-0 xl:flex-col xl:pb-0",children:[a.jsx(Ne,{title:e("channelsPageTitle"),description:e("channelsPageDescription")}),a.jsxs("div",{className:I(ke,"xl:min-h-0 xl:flex-1"),children:[a.jsxs("section",{className:je,children:[a.jsx("div",{className:"border-b border-gray-100 px-4 pt-4",children:a.jsx(Se,{tabs:k,activeTab:r,onChange:o,className:"mb-0"})}),a.jsx("div",{className:"border-b border-gray-100 px-4 py-3",children:a.jsxs("div",{className:"relative",children:[a.jsx(oe,{className:"pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-gray-400"}),a.jsx(D,{value:g,onChange:c=>x(c.target.value),placeholder:e("channelsFilterPlaceholder"),className:"h-10 rounded-xl pl-9"})]})}),a.jsxs("div",{className:"min-h-0 flex-1 space-y-2 overflow-y-auto overscroll-contain p-3",children:[w.map(c=>{const p=t.channels[c.name],S=(p==null?void 0:p.enabled)||!1,C=_(`channels.${c.name}`,j),P=z(c),T=(C==null?void 0:C.help)||e(Me[c.name]||"channelDescriptionDefault"),N=u===c.name;return a.jsx("button",{type:"button",onClick:()=>d(c.name),className:I("w-full rounded-xl border p-2.5 text-left transition-all",N?"border-primary/30 bg-primary-50/40 shadow-sm":"border-gray-200/70 bg-white hover:border-gray-300 hover:bg-gray-50/70"),children:a.jsxs("div",{className:"flex items-start justify-between gap-3",children:[a.jsxs("div",{className:"flex min-w-0 items-center gap-3",children:[a.jsx(K,{name:c.name,src:Y(c.name),className:I("h-10 w-10 rounded-lg border",S?"border-primary/30 bg-white":"border-gray-200/70 bg-white"),imgClassName:"h-5 w-5 object-contain",fallback:a.jsx("span",{className:"text-sm font-semibold uppercase text-gray-500",children:c.name[0]})}),a.jsxs("div",{className:"min-w-0",children:[a.jsx("p",{className:"truncate text-sm font-semibold text-gray-900",children:c.displayName||c.name}),a.jsx("p",{className:"line-clamp-1 text-[11px] text-gray-500",children:T})]})]}),a.jsxs("div",{className:"flex items-center gap-2",children:[P&&a.jsx("a",{href:P,onClick:L=>L.stopPropagation(),className:"inline-flex h-7 w-7 items-center justify-center rounded-md text-gray-300 transition-colors hover:bg-gray-100/70 hover:text-gray-500",title:e("channelsGuideTitle"),children:a.jsx(ce,{className:"h-3.5 w-3.5"})}),a.jsx(J,{status:S?"active":"inactive",label:S?e("statusActive"):e("statusInactive"),className:"min-w-[56px] justify-center"})]})]})},c.name)}),w.length===0&&a.jsxs("div",{className:"flex h-full min-h-[220px] flex-col items-center justify-center rounded-xl border border-dashed border-gray-200 bg-gray-50/70 py-10 text-center",children:[a.jsx("div",{className:"mb-3 flex h-10 w-10 items-center justify-center rounded-lg bg-white",children:a.jsx(ie,{className:"h-5 w-5 text-gray-300"})}),a.jsx("p",{className:"text-sm font-medium text-gray-700",children:e("channelsNoMatch")})]})]})]}),a.jsx(Le,{channelName:u})]})]})}export{Je as ChannelsList};
|