@nextclaw/ui 0.9.11 → 0.9.13
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 +14 -0
- package/dist/assets/{ChannelsList-Brc1qLSU.js → ChannelsList-bROKR37R.js} +1 -1
- package/dist/assets/ChatPage-B9dHVmrV.js +41 -0
- package/dist/assets/{DocBrowser-xLVf1p4L.js → DocBrowser-S-1-qnZQ.js} +1 -1
- package/dist/assets/{LogoBadge-CcTyimdr.js → LogoBadge-t1JzzCtI.js} +1 -1
- package/dist/assets/{MarketplacePage-Bk-qXxyh.js → MarketplacePage-CzIHYJpM.js} +2 -2
- package/dist/assets/{McpMarketplacePage-gFqAYekc.js → McpMarketplacePage-BTJdjNQ1.js} +1 -1
- package/dist/assets/{ModelConfig-DnKNTuw6.js → ModelConfig-BD4o3Kna.js} +1 -1
- package/dist/assets/{ProvidersList-Cjr8EFu_.js → ProvidersList-BOQArFRk.js} +1 -1
- package/dist/assets/RemoteAccessPage-CYNQ53xu.js +1 -0
- package/dist/assets/{RuntimeConfig-CttN--Tv.js → RuntimeConfig-B0B73pye.js} +1 -1
- package/dist/assets/{SearchConfig-D-GzinsL.js → SearchConfig-CKy2QkAP.js} +1 -1
- package/dist/assets/{SecretsConfig-BvqQq4Ds.js → SecretsConfig-BpZLUu88.js} +2 -2
- package/dist/assets/{SessionsConfig-DbtnLmI6.js → SessionsConfig-CoFI6Fa2.js} +1 -1
- package/dist/assets/{chat-message-DYQjL1tD.js → chat-message-D3jZIASl.js} +1 -1
- package/dist/assets/index-CmGwUgcl.js +8 -0
- package/dist/assets/{index-DfEAJJsA.css → index-SGSkQCPi.css} +1 -1
- package/dist/assets/{label-DBSKOMGE.js → label-BOvIOmQx.js} +1 -1
- package/dist/assets/{page-layout-B5th9UzR.js → page-layout-PG3cwSpz.js} +1 -1
- package/dist/assets/popover-BB-kINz7.js +1 -0
- package/dist/assets/{security-config-D72JskP5.js → security-config-Bb6l-viE.js} +1 -1
- package/dist/assets/skeleton-CLSc5FYO.js +1 -0
- package/dist/assets/{status-dot-CU5ZpOn1.js → status-dot-Behu7kDZ.js} +1 -1
- package/dist/assets/{switch-BdaXEtXk.js → switch-CvNG9775.js} +1 -1
- package/dist/assets/{tabs-custom-BVhSoteN.js → tabs-custom-CUdBQO_7.js} +1 -1
- package/dist/assets/{useConfirmDialog-Dugi9V-Z.js → useConfirmDialog-CLLe2uIJ.js} +1 -1
- package/dist/assets/{vendor-CmQZsDAE.js → vendor-TJ2hy_Lv.js} +87 -82
- package/dist/index.html +3 -3
- package/package.json +4 -4
- package/src/account/managers/account.manager.ts +8 -1
- package/src/account/stores/account.store.ts +3 -0
- package/src/api/api-base.ts +16 -0
- package/src/api/client.test.ts +69 -0
- package/src/api/client.ts +29 -87
- package/src/api/config.stream.test.ts +115 -0
- package/src/api/config.ts +49 -121
- package/src/api/raw-client.ts +87 -0
- package/src/components/chat/ChatSidebar.test.tsx +134 -1
- package/src/components/chat/ChatSidebar.tsx +87 -37
- package/src/components/chat/chat-session-label.service.ts +34 -0
- package/src/components/chat/chat-sidebar-session-item.tsx +147 -0
- package/src/components/chat/ncp/NcpChatPage.tsx +3 -10
- package/src/components/chat/ncp/ncp-app-client-fetch.test.ts +69 -0
- package/src/components/chat/ncp/ncp-app-client-fetch.ts +127 -0
- package/src/components/remote/RemoteAccessPage.test.tsx +103 -0
- package/src/components/remote/RemoteAccessPage.tsx +28 -93
- package/src/lib/i18n.remote.ts +20 -8
- package/src/remote/managers/remote-access.manager.ts +13 -0
- package/src/remote/remote-access-feedback.service.test.ts +75 -0
- package/src/remote/remote-access-feedback.service.ts +195 -0
- package/src/transport/app-client.test.ts +49 -0
- package/src/transport/app-client.ts +23 -7
- package/src/transport/local.transport.ts +3 -2
- package/src/transport/remote.transport.ts +7 -2
- package/dist/assets/ChatPage-DmGI776q.js +0 -38
- package/dist/assets/RemoteAccessPage-Rzi5a6Gc.js +0 -1
- package/dist/assets/index-ClLy_7T2.js +0 -8
- package/dist/assets/popover-BEIWRoeP.js +0 -1
- package/dist/assets/skeleton-B_Pn9x0i.js +0 -1
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import type { RemoteAccessView } from '@/api/remote.types';
|
|
2
|
+
import { t } from '@/lib/i18n';
|
|
3
|
+
|
|
4
|
+
type RemoteHeroView = {
|
|
5
|
+
badgeStatus: 'active' | 'inactive' | 'ready' | 'setup' | 'warning';
|
|
6
|
+
badgeLabel: string;
|
|
7
|
+
title: string;
|
|
8
|
+
description: string;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
type RemotePrimaryAction =
|
|
12
|
+
| {
|
|
13
|
+
kind: 'sign-in-enable' | 'enable' | 'repair' | 'reauthorize';
|
|
14
|
+
label: string;
|
|
15
|
+
showRefreshIcon: boolean;
|
|
16
|
+
}
|
|
17
|
+
| null;
|
|
18
|
+
|
|
19
|
+
type RemoteIssueHint = {
|
|
20
|
+
title: string;
|
|
21
|
+
body: string;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export type RemoteAccessFeedbackView = {
|
|
25
|
+
hero: RemoteHeroView;
|
|
26
|
+
primaryAction: RemotePrimaryAction;
|
|
27
|
+
issueHint: RemoteIssueHint | null;
|
|
28
|
+
shouldShowIssueHint: boolean;
|
|
29
|
+
requiresReauthorization: boolean;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const AUTH_EXPIRED_PATTERNS = [
|
|
33
|
+
/invalid or expired token/i,
|
|
34
|
+
/missing bearer token/i,
|
|
35
|
+
/token expired/i,
|
|
36
|
+
/token is invalid/i,
|
|
37
|
+
/run "nextclaw login"/i,
|
|
38
|
+
/browser sign-in again/i
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
function readRuntimeError(status: RemoteAccessView | undefined): string {
|
|
42
|
+
return status?.runtime?.lastError?.trim() || '';
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function requiresRemoteReauthorization(status: RemoteAccessView | undefined): boolean {
|
|
46
|
+
if (!status?.settings.enabled) {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
const error = readRuntimeError(status);
|
|
50
|
+
return AUTH_EXPIRED_PATTERNS.some((pattern) => pattern.test(error));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function buildRemoteAccessFeedbackView(status: RemoteAccessView | undefined): RemoteAccessFeedbackView {
|
|
54
|
+
const reauthorizationRequired = requiresRemoteReauthorization(status);
|
|
55
|
+
|
|
56
|
+
if (reauthorizationRequired) {
|
|
57
|
+
return {
|
|
58
|
+
hero: {
|
|
59
|
+
badgeStatus: 'warning',
|
|
60
|
+
badgeLabel: t('remoteStateReauthorizationRequired'),
|
|
61
|
+
title: t('remoteStatusReauthorizationTitle'),
|
|
62
|
+
description: t('remoteStatusReauthorizationDescription')
|
|
63
|
+
},
|
|
64
|
+
primaryAction: {
|
|
65
|
+
kind: 'reauthorize',
|
|
66
|
+
label: t('remoteReauthorizeNow'),
|
|
67
|
+
showRefreshIcon: false
|
|
68
|
+
},
|
|
69
|
+
issueHint: {
|
|
70
|
+
title: t('remoteStatusRecoveryTitle'),
|
|
71
|
+
body: t('remoteStatusReauthorizationHint')
|
|
72
|
+
},
|
|
73
|
+
shouldShowIssueHint: true,
|
|
74
|
+
requiresReauthorization: true
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (!status?.account.loggedIn) {
|
|
79
|
+
return {
|
|
80
|
+
hero: {
|
|
81
|
+
badgeStatus: 'setup',
|
|
82
|
+
badgeLabel: t('statusSetup'),
|
|
83
|
+
title: t('remoteStatusNeedsSignIn'),
|
|
84
|
+
description: t('remoteStatusNeedsSignInDescription')
|
|
85
|
+
},
|
|
86
|
+
primaryAction: {
|
|
87
|
+
kind: 'sign-in-enable',
|
|
88
|
+
label: t('remoteSignInAndEnable'),
|
|
89
|
+
showRefreshIcon: false
|
|
90
|
+
},
|
|
91
|
+
issueHint: null,
|
|
92
|
+
shouldShowIssueHint: false,
|
|
93
|
+
requiresReauthorization: false
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (!status.settings.enabled) {
|
|
98
|
+
return {
|
|
99
|
+
hero: {
|
|
100
|
+
badgeStatus: 'inactive',
|
|
101
|
+
badgeLabel: t('statusInactive'),
|
|
102
|
+
title: t('remoteStatusNeedsEnable'),
|
|
103
|
+
description: t('remoteStatusNeedsEnableDescription')
|
|
104
|
+
},
|
|
105
|
+
primaryAction: {
|
|
106
|
+
kind: 'enable',
|
|
107
|
+
label: t('remoteEnableNow'),
|
|
108
|
+
showRefreshIcon: false
|
|
109
|
+
},
|
|
110
|
+
issueHint: null,
|
|
111
|
+
shouldShowIssueHint: false,
|
|
112
|
+
requiresReauthorization: false
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (!status.service.running) {
|
|
117
|
+
return {
|
|
118
|
+
hero: {
|
|
119
|
+
badgeStatus: 'warning',
|
|
120
|
+
badgeLabel: t('remoteServiceStopped'),
|
|
121
|
+
title: t('remoteStatusNeedsServiceTitle'),
|
|
122
|
+
description: t('remoteStatusNeedsServiceDescription')
|
|
123
|
+
},
|
|
124
|
+
primaryAction: {
|
|
125
|
+
kind: 'repair',
|
|
126
|
+
label: t('remoteReconnectNow'),
|
|
127
|
+
showRefreshIcon: true
|
|
128
|
+
},
|
|
129
|
+
issueHint: {
|
|
130
|
+
title: t('remoteStatusRecoveryTitle'),
|
|
131
|
+
body: t('remoteStatusIssueDetailServiceStopped')
|
|
132
|
+
},
|
|
133
|
+
shouldShowIssueHint: true,
|
|
134
|
+
requiresReauthorization: false
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (status.runtime?.state === 'connected') {
|
|
139
|
+
return {
|
|
140
|
+
hero: {
|
|
141
|
+
badgeStatus: 'ready',
|
|
142
|
+
badgeLabel: t('statusReady'),
|
|
143
|
+
title: t('remoteStatusReadyTitle'),
|
|
144
|
+
description: t('remoteStatusReadyDescription')
|
|
145
|
+
},
|
|
146
|
+
primaryAction: {
|
|
147
|
+
kind: 'repair',
|
|
148
|
+
label: t('remoteReconnectNow'),
|
|
149
|
+
showRefreshIcon: true
|
|
150
|
+
},
|
|
151
|
+
issueHint: null,
|
|
152
|
+
shouldShowIssueHint: false,
|
|
153
|
+
requiresReauthorization: false
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (status.runtime?.state === 'connecting') {
|
|
158
|
+
return {
|
|
159
|
+
hero: {
|
|
160
|
+
badgeStatus: 'active',
|
|
161
|
+
badgeLabel: t('connecting'),
|
|
162
|
+
title: t('remoteStatusConnectingTitle'),
|
|
163
|
+
description: t('remoteStatusConnectingDescription')
|
|
164
|
+
},
|
|
165
|
+
primaryAction: {
|
|
166
|
+
kind: 'repair',
|
|
167
|
+
label: t('remoteReconnectNow'),
|
|
168
|
+
showRefreshIcon: true
|
|
169
|
+
},
|
|
170
|
+
issueHint: null,
|
|
171
|
+
shouldShowIssueHint: false,
|
|
172
|
+
requiresReauthorization: false
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return {
|
|
177
|
+
hero: {
|
|
178
|
+
badgeStatus: 'warning',
|
|
179
|
+
badgeLabel: t('remoteStateDisconnected'),
|
|
180
|
+
title: t('remoteStatusIssueTitle'),
|
|
181
|
+
description: t('remoteStatusIssueDescription')
|
|
182
|
+
},
|
|
183
|
+
primaryAction: {
|
|
184
|
+
kind: 'repair',
|
|
185
|
+
label: t('remoteReconnectNow'),
|
|
186
|
+
showRefreshIcon: true
|
|
187
|
+
},
|
|
188
|
+
issueHint: {
|
|
189
|
+
title: t('remoteStatusRecoveryTitle'),
|
|
190
|
+
body: t('remoteStatusIssueDetailGeneric')
|
|
191
|
+
},
|
|
192
|
+
shouldShowIssueHint: Boolean(status.settings.enabled && status.account.loggedIn),
|
|
193
|
+
requiresReauthorization: false
|
|
194
|
+
};
|
|
195
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
|
|
3
|
+
describe('appClient runtime detection', () => {
|
|
4
|
+
beforeEach(() => {
|
|
5
|
+
vi.resetModules();
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
afterEach(() => {
|
|
9
|
+
vi.restoreAllMocks();
|
|
10
|
+
vi.unstubAllGlobals();
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it('falls back to LocalAppTransport when runtime probe returns html', async () => {
|
|
14
|
+
const fetchMock = vi.fn().mockResolvedValue(
|
|
15
|
+
new Response('<html>ui shell</html>', {
|
|
16
|
+
status: 200,
|
|
17
|
+
headers: {
|
|
18
|
+
'content-type': 'text/html; charset=utf-8'
|
|
19
|
+
}
|
|
20
|
+
})
|
|
21
|
+
);
|
|
22
|
+
vi.stubGlobal('fetch', fetchMock);
|
|
23
|
+
|
|
24
|
+
const { LocalAppTransport } = await import('@/transport/local.transport');
|
|
25
|
+
const localRequest = vi
|
|
26
|
+
.spyOn(LocalAppTransport.prototype, 'request')
|
|
27
|
+
.mockResolvedValue({ ok: true } as never);
|
|
28
|
+
|
|
29
|
+
const { appClient } = await import('@/transport/app-client');
|
|
30
|
+
const result = await appClient.request<{ ok: boolean }>({
|
|
31
|
+
method: 'GET',
|
|
32
|
+
path: '/api/config'
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
expect(fetchMock).toHaveBeenCalledWith(
|
|
36
|
+
expect.stringContaining('/_remote/runtime'),
|
|
37
|
+
expect.objectContaining({
|
|
38
|
+
method: 'GET',
|
|
39
|
+
credentials: 'include',
|
|
40
|
+
cache: 'no-store'
|
|
41
|
+
})
|
|
42
|
+
);
|
|
43
|
+
expect(localRequest).toHaveBeenCalledWith({
|
|
44
|
+
method: 'GET',
|
|
45
|
+
path: '/api/config'
|
|
46
|
+
});
|
|
47
|
+
expect(result).toEqual({ ok: true });
|
|
48
|
+
});
|
|
49
|
+
});
|
|
@@ -1,9 +1,14 @@
|
|
|
1
|
-
import { API_BASE } from '@/api/
|
|
1
|
+
import { API_BASE } from '@/api/api-base';
|
|
2
2
|
import { LocalAppTransport } from './local.transport';
|
|
3
3
|
import { RemoteSessionMultiplexTransport } from './remote.transport';
|
|
4
4
|
import type { AppTransport, RemoteRuntimeInfo, RequestInput, StreamInput, StreamSession } from './transport.types';
|
|
5
5
|
|
|
6
6
|
const REMOTE_RUNTIME_PATH = '/_remote/runtime';
|
|
7
|
+
const DEFAULT_REMOTE_RUNTIME: RemoteRuntimeInfo = {
|
|
8
|
+
mode: 'remote',
|
|
9
|
+
protocolVersion: 1,
|
|
10
|
+
wsPath: '/_remote/ws'
|
|
11
|
+
};
|
|
7
12
|
|
|
8
13
|
async function resolveRuntime(apiBase: string): Promise<AppTransport> {
|
|
9
14
|
const runtimeUrl = `${apiBase.replace(/\/$/, '')}${REMOTE_RUNTIME_PATH}`;
|
|
@@ -22,17 +27,28 @@ async function resolveRuntime(apiBase: string): Promise<AppTransport> {
|
|
|
22
27
|
return new LocalAppTransport({ apiBase });
|
|
23
28
|
}
|
|
24
29
|
|
|
25
|
-
const
|
|
30
|
+
const contentType = response.headers.get('content-type')?.toLowerCase() ?? '';
|
|
31
|
+
if (!contentType.includes('application/json')) {
|
|
32
|
+
return response.status >= 400
|
|
33
|
+
? new RemoteSessionMultiplexTransport(DEFAULT_REMOTE_RUNTIME, apiBase)
|
|
34
|
+
: new LocalAppTransport({ apiBase });
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
let payload: { ok?: boolean; data?: RemoteRuntimeInfo } | null = null;
|
|
38
|
+
try {
|
|
39
|
+
payload = await response.json() as { ok?: boolean; data?: RemoteRuntimeInfo };
|
|
40
|
+
} catch {
|
|
41
|
+
return response.status >= 400
|
|
42
|
+
? new RemoteSessionMultiplexTransport(DEFAULT_REMOTE_RUNTIME, apiBase)
|
|
43
|
+
: new LocalAppTransport({ apiBase });
|
|
44
|
+
}
|
|
45
|
+
|
|
26
46
|
if (response.ok && payload.ok && payload.data?.mode === 'remote') {
|
|
27
47
|
return new RemoteSessionMultiplexTransport(payload.data, apiBase);
|
|
28
48
|
}
|
|
29
49
|
|
|
30
50
|
if (response.status >= 400) {
|
|
31
|
-
return new RemoteSessionMultiplexTransport(
|
|
32
|
-
mode: 'remote',
|
|
33
|
-
protocolVersion: 1,
|
|
34
|
-
wsPath: '/_remote/ws'
|
|
35
|
-
}, apiBase);
|
|
51
|
+
return new RemoteSessionMultiplexTransport(DEFAULT_REMOTE_RUNTIME, apiBase);
|
|
36
52
|
}
|
|
37
53
|
|
|
38
54
|
return new LocalAppTransport({ apiBase });
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { API_BASE
|
|
1
|
+
import { API_BASE } from '@/api/api-base';
|
|
2
|
+
import { requestRawApiResponse } from '@/api/raw-client';
|
|
2
3
|
import type { ApiResponse } from '@/api/types';
|
|
3
4
|
import type { AppEvent, AppTransport, RequestInput, StreamInput, StreamSession } from './transport.types';
|
|
4
5
|
import { readSseStreamResult } from './sse-stream';
|
|
@@ -112,7 +113,7 @@ export class LocalAppTransport implements AppTransport {
|
|
|
112
113
|
}
|
|
113
114
|
|
|
114
115
|
async request<T>(input: RequestInput): Promise<T> {
|
|
115
|
-
const response = await
|
|
116
|
+
const response = await requestRawApiResponse<T>(input.path, {
|
|
116
117
|
method: input.method,
|
|
117
118
|
...(input.body !== undefined ? { body: JSON.stringify(input.body) } : {})
|
|
118
119
|
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { API_BASE } from '@/api/
|
|
1
|
+
import { API_BASE } from '@/api/api-base';
|
|
2
2
|
import type { ApiError } from '@/api/types';
|
|
3
3
|
import type { AppEvent, AppTransport, RemoteRuntimeInfo, RequestInput, StreamInput, StreamSession } from './transport.types';
|
|
4
4
|
import { resolveTransportWebSocketUrl } from './transport-websocket-url';
|
|
@@ -350,7 +350,12 @@ export class RemoteSessionMultiplexTransport implements AppTransport {
|
|
|
350
350
|
return;
|
|
351
351
|
}
|
|
352
352
|
if (frame.type === 'stream.event') {
|
|
353
|
-
|
|
353
|
+
try {
|
|
354
|
+
pending.onEvent({ name: frame.event, payload: frame.payload });
|
|
355
|
+
} catch (error) {
|
|
356
|
+
this.pendingStreams.delete(frame.streamId);
|
|
357
|
+
pending.reject(error instanceof Error ? error : new Error(String(error)));
|
|
358
|
+
}
|
|
354
359
|
return;
|
|
355
360
|
}
|
|
356
361
|
this.pendingStreams.delete(frame.streamId);
|