@nextclaw/ui 0.9.2 → 0.9.4
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 +12 -0
- package/dist/assets/ChannelsList-DDfZIiJa.js +1 -0
- package/dist/assets/ChatPage-FpRraTxm.js +38 -0
- package/dist/assets/{DocBrowser-CVwUDJMO.js → DocBrowser-Kndx8OJj.js} +1 -1
- package/dist/assets/LogoBadge-hKHoLH9n.js +1 -0
- package/dist/assets/MarketplacePage-CZIJyfjK.js +49 -0
- package/dist/assets/McpMarketplacePage-BGrAMA37.js +40 -0
- package/dist/assets/{ModelConfig-CsX-_fyy.js → ModelConfig-BpKQeGfb.js} +1 -1
- package/dist/assets/ProvidersList-qfUL6mrW.js +1 -0
- package/dist/assets/RemoteAccessPage-BQuMsngI.js +1 -0
- package/dist/assets/{RuntimeConfig-CX2TGEG1.js → RuntimeConfig-CVlqNWKO.js} +1 -1
- package/dist/assets/{SearchConfig-C-WBTcWi.js → SearchConfig-DXFV6Mvx.js} +1 -1
- package/dist/assets/{SecretsConfig-9kbR0ZCB.js → SecretsConfig-BGW9aUqv.js} +2 -2
- package/dist/assets/{SessionsConfig-Bohn3P1q.js → SessionsConfig-BByfa1ke.js} +2 -2
- package/dist/assets/{chat-message-AWIcksDK.js → chat-message-ZwnDwDuQ.js} +1 -1
- package/dist/assets/index-BWvap_iq.js +8 -0
- package/dist/assets/index-COrhpAdh.css +1 -0
- package/dist/assets/{index-CPDASUXh.js → index-Ct7FQpxN.js} +1 -1
- package/dist/assets/{label-DD61y-4v.js → label-Bklr3fXc.js} +1 -1
- package/dist/assets/marketplace-localization-Dk31LJJJ.js +1 -0
- package/dist/assets/{page-layout-CfnoVycc.js → page-layout-sNhcbwtm.js} +1 -1
- package/dist/assets/{popover-DsugZ6rp.js → popover-C3rJrJJG.js} +1 -1
- package/dist/assets/{security-config-DIrf2Z0O.js → security-config-BueosYw1.js} +1 -1
- package/dist/assets/skeleton-CiG6msbm.js +1 -0
- package/dist/assets/status-dot-CsIV5YrS.js +1 -0
- package/dist/assets/{switch-NX5OmUXQ.js → switch-DSdHSIsC.js} +1 -1
- package/dist/assets/{tabs-custom-9ihB5Jem.js → tabs-custom-BB-VjdL2.js} +1 -1
- package/dist/assets/{useConfirmDialog-BuQnVTeR.js → useConfirmDialog-BL5s8KDC.js} +2 -2
- package/dist/assets/{vendor-DKBNiC31.js → vendor-CwsIoNvJ.js} +128 -93
- package/dist/index.html +3 -3
- package/package.json +3 -3
- package/src/App.tsx +4 -0
- package/src/api/auth.types.ts +24 -0
- package/src/api/chat-session-type.types.ts +21 -0
- package/src/api/marketplace.ts +8 -2
- package/src/api/mcp-marketplace.ts +138 -0
- package/src/api/remote.ts +77 -0
- package/src/api/remote.types.ts +104 -0
- package/src/api/types.ts +28 -34
- package/src/components/chat/ChatSidebar.test.tsx +31 -2
- package/src/components/chat/ChatSidebar.tsx +26 -2
- package/src/components/chat/chat-page-data.ts +36 -38
- package/src/components/chat/chat-page-runtime.test.ts +96 -2
- package/src/components/chat/chat-page-runtime.ts +1 -135
- package/src/components/chat/chat-session-preference-governance.ts +303 -0
- package/src/components/chat/legacy/LegacyChatPage.tsx +4 -19
- package/src/components/chat/ncp/NcpChatPage.tsx +4 -19
- package/src/components/chat/ncp/ncp-chat-page-data.test.ts +36 -0
- package/src/components/chat/ncp/ncp-chat-page-data.ts +62 -21
- package/src/components/chat/ncp/ncp-session-adapter.test.ts +2 -0
- package/src/components/chat/ncp/ncp-session-adapter.ts +2 -0
- package/src/components/chat/stores/chat-input.store.ts +14 -1
- package/src/components/chat/useChatSessionTypeState.test.tsx +29 -0
- package/src/components/chat/useChatSessionTypeState.ts +55 -12
- package/src/components/layout/Sidebar.tsx +11 -1
- package/src/components/marketplace/MarketplacePage.test.tsx +152 -0
- package/src/components/marketplace/MarketplacePage.tsx +52 -199
- package/src/components/marketplace/marketplace-installed-cache.test.ts +110 -0
- package/src/components/marketplace/marketplace-installed-cache.ts +149 -0
- package/src/components/marketplace/marketplace-localization.ts +77 -0
- package/src/components/marketplace/marketplace-page-parts.tsx +102 -0
- package/src/components/marketplace/mcp/McpMarketplacePage.test.tsx +208 -0
- package/src/components/marketplace/mcp/McpMarketplacePage.tsx +578 -0
- package/src/components/remote/RemoteAccessPage.tsx +396 -0
- package/src/components/ui/input.tsx +1 -1
- package/src/components/ui/label.tsx +1 -1
- package/src/hooks/useMarketplace.ts +36 -7
- package/src/hooks/useMcpMarketplace.ts +99 -0
- package/src/hooks/useRemoteAccess.ts +120 -0
- package/src/hooks/useWebSocket.ts +25 -16
- package/src/lib/i18n.marketplace.ts +91 -0
- package/src/lib/i18n.remote.ts +142 -0
- package/src/lib/i18n.ts +10 -68
- package/dist/assets/ChannelsList-DKD6Llid.js +0 -1
- package/dist/assets/ChatPage-BK9X4Tin.js +0 -38
- package/dist/assets/LogoBadge-CYQ_b7jk.js +0 -1
- package/dist/assets/MarketplacePage-B_2z3ii_.js +0 -49
- package/dist/assets/ProvidersList-CZstsyv7.js +0 -1
- package/dist/assets/index-BEgClaDH.js +0 -8
- package/dist/assets/index-C8GsgIUn.css +0 -1
- package/dist/assets/skeleton-DJ-Wen2o.js +0 -1
package/dist/index.html
CHANGED
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
<link rel="icon" type="image/svg+xml" href="/logo.svg" />
|
|
7
7
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
8
8
|
<title>NextClaw - 系统配置</title>
|
|
9
|
-
<script type="module" crossorigin src="/assets/index-
|
|
10
|
-
<link rel="modulepreload" crossorigin href="/assets/vendor-
|
|
11
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
9
|
+
<script type="module" crossorigin src="/assets/index-BWvap_iq.js"></script>
|
|
10
|
+
<link rel="modulepreload" crossorigin href="/assets/vendor-CwsIoNvJ.js">
|
|
11
|
+
<link rel="stylesheet" crossorigin href="/assets/index-COrhpAdh.css">
|
|
12
12
|
</head>
|
|
13
13
|
|
|
14
14
|
<body>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nextclaw/ui",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.4",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -28,10 +28,10 @@
|
|
|
28
28
|
"zod": "^3.23.8",
|
|
29
29
|
"zustand": "^5.0.2",
|
|
30
30
|
"@nextclaw/agent-chat-ui": "0.2.1",
|
|
31
|
+
"@nextclaw/ncp-http-agent-client": "0.3.1",
|
|
31
32
|
"@nextclaw/ncp": "0.3.1",
|
|
32
33
|
"@nextclaw/ncp-react": "0.3.2",
|
|
33
|
-
"@nextclaw/agent-chat": "0.1.1"
|
|
34
|
-
"@nextclaw/ncp-http-agent-client": "0.3.1"
|
|
34
|
+
"@nextclaw/agent-chat": "0.1.1"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
37
|
"@testing-library/react": "^16.3.0",
|
package/src/App.tsx
CHANGED
|
@@ -25,7 +25,9 @@ const RuntimeConfigPage = lazy(async () => ({ default: (await import('@/componen
|
|
|
25
25
|
const SecurityConfigPage = lazy(async () => ({ default: (await import('@/components/config/security-config')).SecurityConfig }));
|
|
26
26
|
const SessionsConfigPage = lazy(async () => ({ default: (await import('@/components/config/SessionsConfig')).SessionsConfig }));
|
|
27
27
|
const SecretsConfigPage = lazy(async () => ({ default: (await import('@/components/config/SecretsConfig')).SecretsConfig }));
|
|
28
|
+
const RemoteAccessPage = lazy(async () => ({ default: (await import('@/components/remote/RemoteAccessPage')).RemoteAccessPage }));
|
|
28
29
|
const MarketplacePage = lazy(async () => ({ default: (await import('@/components/marketplace/MarketplacePage')).MarketplacePage }));
|
|
30
|
+
const McpMarketplacePage = lazy(async () => ({ default: (await import('@/components/marketplace/mcp/McpMarketplacePage')).McpMarketplacePage }));
|
|
29
31
|
|
|
30
32
|
function RouteFallback() {
|
|
31
33
|
return <div className="h-full w-full animate-pulse rounded-2xl border border-border/40 bg-card/40" />;
|
|
@@ -52,12 +54,14 @@ function ProtectedApp() {
|
|
|
52
54
|
<Route path="/providers" element={<LazyRoute><ProvidersListPage /></LazyRoute>} />
|
|
53
55
|
<Route path="/channels" element={<LazyRoute><ChannelsListPage /></LazyRoute>} />
|
|
54
56
|
<Route path="/runtime" element={<LazyRoute><RuntimeConfigPage /></LazyRoute>} />
|
|
57
|
+
<Route path="/remote" element={<LazyRoute><RemoteAccessPage /></LazyRoute>} />
|
|
55
58
|
<Route path="/security" element={<LazyRoute><SecurityConfigPage /></LazyRoute>} />
|
|
56
59
|
<Route path="/sessions" element={<LazyRoute><SessionsConfigPage /></LazyRoute>} />
|
|
57
60
|
<Route path="/secrets" element={<LazyRoute><SecretsConfigPage /></LazyRoute>} />
|
|
58
61
|
<Route path="/settings" element={<Navigate to="/model" replace />} />
|
|
59
62
|
<Route path="/marketplace/skills" element={<Navigate to="/skills" replace />} />
|
|
60
63
|
<Route path="/marketplace" element={<Navigate to="/marketplace/plugins" replace />} />
|
|
64
|
+
<Route path="/marketplace/mcp" element={<LazyRoute><McpMarketplacePage /></LazyRoute>} />
|
|
61
65
|
<Route path="/marketplace/:type" element={<LazyRoute><MarketplacePage /></LazyRoute>} />
|
|
62
66
|
<Route path="/" element={<Navigate to="/chat" replace />} />
|
|
63
67
|
<Route path="*" element={<Navigate to="/chat" replace />} />
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export type AuthStatusView = {
|
|
2
|
+
enabled: boolean;
|
|
3
|
+
configured: boolean;
|
|
4
|
+
authenticated: boolean;
|
|
5
|
+
username?: string;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export type AuthSetupRequest = {
|
|
9
|
+
username: string;
|
|
10
|
+
password: string;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export type AuthLoginRequest = {
|
|
14
|
+
username: string;
|
|
15
|
+
password: string;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export type AuthPasswordUpdateRequest = {
|
|
19
|
+
password: string;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export type AuthEnabledUpdateRequest = {
|
|
23
|
+
enabled: boolean;
|
|
24
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export type ChatSessionTypeCtaView = {
|
|
2
|
+
kind: string;
|
|
3
|
+
label?: string;
|
|
4
|
+
href?: string;
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
export type ChatSessionTypeOptionView = {
|
|
8
|
+
value: string;
|
|
9
|
+
label: string;
|
|
10
|
+
ready?: boolean;
|
|
11
|
+
reason?: string | null;
|
|
12
|
+
reasonMessage?: string | null;
|
|
13
|
+
supportedModels?: string[];
|
|
14
|
+
recommendedModel?: string | null;
|
|
15
|
+
cta?: ChatSessionTypeCtaView | null;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export type ChatSessionTypesView = {
|
|
19
|
+
defaultType: string;
|
|
20
|
+
options: ChatSessionTypeOptionView[];
|
|
21
|
+
};
|
package/src/api/marketplace.ts
CHANGED
|
@@ -23,8 +23,14 @@ export type MarketplaceListParams = {
|
|
|
23
23
|
pageSize?: number;
|
|
24
24
|
};
|
|
25
25
|
|
|
26
|
-
function toMarketplaceTypeSegment(type: MarketplaceItemType): 'plugins' | 'skills' {
|
|
27
|
-
|
|
26
|
+
function toMarketplaceTypeSegment(type: MarketplaceItemType): 'plugins' | 'skills' | 'mcp' {
|
|
27
|
+
if (type === 'plugin') {
|
|
28
|
+
return 'plugins';
|
|
29
|
+
}
|
|
30
|
+
if (type === 'skill') {
|
|
31
|
+
return 'skills';
|
|
32
|
+
}
|
|
33
|
+
return 'mcp';
|
|
28
34
|
}
|
|
29
35
|
|
|
30
36
|
export async function fetchMarketplaceItems(params: MarketplaceListParams): Promise<MarketplaceListView> {
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { api } from './client';
|
|
2
|
+
import type {
|
|
3
|
+
MarketplaceInstalledView,
|
|
4
|
+
MarketplaceItemView,
|
|
5
|
+
MarketplaceListView,
|
|
6
|
+
MarketplaceMcpContentView,
|
|
7
|
+
MarketplaceMcpDoctorResult,
|
|
8
|
+
MarketplaceRecommendationView,
|
|
9
|
+
MarketplaceSort
|
|
10
|
+
} from './types';
|
|
11
|
+
|
|
12
|
+
export type McpMarketplaceListParams = {
|
|
13
|
+
q?: string;
|
|
14
|
+
tag?: string;
|
|
15
|
+
sort?: MarketplaceSort;
|
|
16
|
+
page?: number;
|
|
17
|
+
pageSize?: number;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export async function fetchMcpMarketplaceItems(params: McpMarketplaceListParams): Promise<MarketplaceListView> {
|
|
21
|
+
const query = new URLSearchParams();
|
|
22
|
+
if (params.q?.trim()) {
|
|
23
|
+
query.set('q', params.q.trim());
|
|
24
|
+
}
|
|
25
|
+
if (params.tag?.trim()) {
|
|
26
|
+
query.set('tag', params.tag.trim());
|
|
27
|
+
}
|
|
28
|
+
if (params.sort) {
|
|
29
|
+
query.set('sort', params.sort);
|
|
30
|
+
}
|
|
31
|
+
if (typeof params.page === 'number' && Number.isFinite(params.page)) {
|
|
32
|
+
query.set('page', String(Math.max(1, Math.trunc(params.page))));
|
|
33
|
+
}
|
|
34
|
+
if (typeof params.pageSize === 'number' && Number.isFinite(params.pageSize)) {
|
|
35
|
+
query.set('pageSize', String(Math.max(1, Math.trunc(params.pageSize))));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const suffix = query.toString();
|
|
39
|
+
const response = await api.get<MarketplaceListView>(
|
|
40
|
+
suffix ? `/api/marketplace/mcp/items?${suffix}` : '/api/marketplace/mcp/items'
|
|
41
|
+
);
|
|
42
|
+
if (!response.ok) {
|
|
43
|
+
throw new Error(response.error.message);
|
|
44
|
+
}
|
|
45
|
+
return response.data;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export async function fetchMcpMarketplaceInstalled(): Promise<MarketplaceInstalledView> {
|
|
49
|
+
const response = await api.get<MarketplaceInstalledView>('/api/marketplace/mcp/installed');
|
|
50
|
+
if (!response.ok) {
|
|
51
|
+
throw new Error(response.error.message);
|
|
52
|
+
}
|
|
53
|
+
return response.data;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export async function fetchMcpMarketplaceItem(slug: string): Promise<MarketplaceItemView> {
|
|
57
|
+
const response = await api.get<MarketplaceItemView>(`/api/marketplace/mcp/items/${encodeURIComponent(slug)}`);
|
|
58
|
+
if (!response.ok) {
|
|
59
|
+
throw new Error(response.error.message);
|
|
60
|
+
}
|
|
61
|
+
return response.data;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export async function fetchMcpMarketplaceContent(slug: string): Promise<MarketplaceMcpContentView> {
|
|
65
|
+
const response = await api.get<MarketplaceMcpContentView>(`/api/marketplace/mcp/items/${encodeURIComponent(slug)}/content`);
|
|
66
|
+
if (!response.ok) {
|
|
67
|
+
throw new Error(response.error.message);
|
|
68
|
+
}
|
|
69
|
+
return response.data;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export async function fetchMcpMarketplaceRecommendations(params: {
|
|
73
|
+
scene?: string;
|
|
74
|
+
limit?: number;
|
|
75
|
+
} = {}): Promise<MarketplaceRecommendationView> {
|
|
76
|
+
const query = new URLSearchParams();
|
|
77
|
+
if (params.scene?.trim()) {
|
|
78
|
+
query.set('scene', params.scene.trim());
|
|
79
|
+
}
|
|
80
|
+
if (typeof params.limit === 'number' && Number.isFinite(params.limit)) {
|
|
81
|
+
query.set('limit', String(Math.max(1, Math.trunc(params.limit))));
|
|
82
|
+
}
|
|
83
|
+
const suffix = query.toString();
|
|
84
|
+
const response = await api.get<MarketplaceRecommendationView>(
|
|
85
|
+
suffix ? `/api/marketplace/mcp/recommendations?${suffix}` : '/api/marketplace/mcp/recommendations'
|
|
86
|
+
);
|
|
87
|
+
if (!response.ok) {
|
|
88
|
+
throw new Error(response.error.message);
|
|
89
|
+
}
|
|
90
|
+
return response.data;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export async function installMcpMarketplaceItem(request: {
|
|
94
|
+
spec: string;
|
|
95
|
+
name?: string;
|
|
96
|
+
enabled?: boolean;
|
|
97
|
+
allAgents?: boolean;
|
|
98
|
+
agents?: string[];
|
|
99
|
+
inputs?: Record<string, string>;
|
|
100
|
+
}): Promise<{ type: 'mcp'; spec: string; name?: string; message: string; output?: string }> {
|
|
101
|
+
const response = await api.post<{ type: 'mcp'; spec: string; name?: string; message: string; output?: string }>(
|
|
102
|
+
'/api/marketplace/mcp/install',
|
|
103
|
+
{
|
|
104
|
+
type: 'mcp',
|
|
105
|
+
...request
|
|
106
|
+
}
|
|
107
|
+
);
|
|
108
|
+
if (!response.ok) {
|
|
109
|
+
throw new Error(response.error.message);
|
|
110
|
+
}
|
|
111
|
+
return response.data;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export async function manageMcpMarketplaceItem(request: {
|
|
115
|
+
action: 'enable' | 'disable' | 'remove';
|
|
116
|
+
id?: string;
|
|
117
|
+
spec?: string;
|
|
118
|
+
}): Promise<{ type: 'mcp'; action: 'enable' | 'disable' | 'remove'; id: string; message: string; output?: string }> {
|
|
119
|
+
const response = await api.post<{ type: 'mcp'; action: 'enable' | 'disable' | 'remove'; id: string; message: string; output?: string }>(
|
|
120
|
+
'/api/marketplace/mcp/manage',
|
|
121
|
+
{
|
|
122
|
+
type: 'mcp',
|
|
123
|
+
...request
|
|
124
|
+
}
|
|
125
|
+
);
|
|
126
|
+
if (!response.ok) {
|
|
127
|
+
throw new Error(response.error.message);
|
|
128
|
+
}
|
|
129
|
+
return response.data;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export async function doctorMcpMarketplaceItem(name: string): Promise<MarketplaceMcpDoctorResult> {
|
|
133
|
+
const response = await api.post<MarketplaceMcpDoctorResult>('/api/marketplace/mcp/doctor', { name });
|
|
134
|
+
if (!response.ok) {
|
|
135
|
+
throw new Error(response.error.message);
|
|
136
|
+
}
|
|
137
|
+
return response.data;
|
|
138
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { api } from './client';
|
|
2
|
+
import type {
|
|
3
|
+
RemoteAccessView,
|
|
4
|
+
RemoteBrowserAuthPollRequest,
|
|
5
|
+
RemoteBrowserAuthPollResult,
|
|
6
|
+
RemoteBrowserAuthStartRequest,
|
|
7
|
+
RemoteBrowserAuthStartResult,
|
|
8
|
+
RemoteDoctorView,
|
|
9
|
+
RemoteLoginRequest,
|
|
10
|
+
RemoteServiceAction,
|
|
11
|
+
RemoteServiceActionResult,
|
|
12
|
+
RemoteSettingsUpdateRequest
|
|
13
|
+
} from './remote.types';
|
|
14
|
+
|
|
15
|
+
export async function fetchRemoteStatus(): Promise<RemoteAccessView> {
|
|
16
|
+
const response = await api.get<RemoteAccessView>('/api/remote/status');
|
|
17
|
+
if (!response.ok) {
|
|
18
|
+
throw new Error(response.error.message);
|
|
19
|
+
}
|
|
20
|
+
return response.data;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export async function fetchRemoteDoctor(): Promise<RemoteDoctorView> {
|
|
24
|
+
const response = await api.get<RemoteDoctorView>('/api/remote/doctor');
|
|
25
|
+
if (!response.ok) {
|
|
26
|
+
throw new Error(response.error.message);
|
|
27
|
+
}
|
|
28
|
+
return response.data;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export async function loginRemote(data: RemoteLoginRequest): Promise<RemoteAccessView> {
|
|
32
|
+
const response = await api.post<RemoteAccessView>('/api/remote/login', data);
|
|
33
|
+
if (!response.ok) {
|
|
34
|
+
throw new Error(response.error.message);
|
|
35
|
+
}
|
|
36
|
+
return response.data;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export async function startRemoteBrowserAuth(data: RemoteBrowserAuthStartRequest): Promise<RemoteBrowserAuthStartResult> {
|
|
40
|
+
const response = await api.post<RemoteBrowserAuthStartResult>('/api/remote/auth/start', data);
|
|
41
|
+
if (!response.ok) {
|
|
42
|
+
throw new Error(response.error.message);
|
|
43
|
+
}
|
|
44
|
+
return response.data;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export async function pollRemoteBrowserAuth(data: RemoteBrowserAuthPollRequest): Promise<RemoteBrowserAuthPollResult> {
|
|
48
|
+
const response = await api.post<RemoteBrowserAuthPollResult>('/api/remote/auth/poll', data);
|
|
49
|
+
if (!response.ok) {
|
|
50
|
+
throw new Error(response.error.message);
|
|
51
|
+
}
|
|
52
|
+
return response.data;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export async function logoutRemote(): Promise<RemoteAccessView> {
|
|
56
|
+
const response = await api.post<RemoteAccessView>('/api/remote/logout', {});
|
|
57
|
+
if (!response.ok) {
|
|
58
|
+
throw new Error(response.error.message);
|
|
59
|
+
}
|
|
60
|
+
return response.data;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export async function updateRemoteSettings(data: RemoteSettingsUpdateRequest): Promise<RemoteAccessView> {
|
|
64
|
+
const response = await api.put<RemoteAccessView>('/api/remote/settings', data);
|
|
65
|
+
if (!response.ok) {
|
|
66
|
+
throw new Error(response.error.message);
|
|
67
|
+
}
|
|
68
|
+
return response.data;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export async function controlRemoteService(action: RemoteServiceAction): Promise<RemoteServiceActionResult> {
|
|
72
|
+
const response = await api.post<RemoteServiceActionResult>(`/api/remote/service/${action}`, {});
|
|
73
|
+
if (!response.ok) {
|
|
74
|
+
throw new Error(response.error.message);
|
|
75
|
+
}
|
|
76
|
+
return response.data;
|
|
77
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
export type RemoteAccountView = {
|
|
2
|
+
loggedIn: boolean;
|
|
3
|
+
email?: string;
|
|
4
|
+
role?: string;
|
|
5
|
+
platformBase?: string | null;
|
|
6
|
+
apiBase?: string | null;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export type RemoteRuntimeView = {
|
|
10
|
+
enabled: boolean;
|
|
11
|
+
mode: "service" | "foreground";
|
|
12
|
+
state: "disabled" | "connecting" | "connected" | "disconnected" | "error";
|
|
13
|
+
deviceId?: string;
|
|
14
|
+
deviceName?: string;
|
|
15
|
+
platformBase?: string;
|
|
16
|
+
localOrigin?: string;
|
|
17
|
+
lastConnectedAt?: string | null;
|
|
18
|
+
lastError?: string | null;
|
|
19
|
+
updatedAt: string;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export type RemoteServiceView = {
|
|
23
|
+
running: boolean;
|
|
24
|
+
pid?: number;
|
|
25
|
+
uiUrl?: string;
|
|
26
|
+
uiPort?: number;
|
|
27
|
+
currentProcess: boolean;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export type RemoteSettingsView = {
|
|
31
|
+
enabled: boolean;
|
|
32
|
+
deviceName: string;
|
|
33
|
+
platformApiBase: string;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export type RemoteAccessView = {
|
|
37
|
+
account: RemoteAccountView;
|
|
38
|
+
settings: RemoteSettingsView;
|
|
39
|
+
service: RemoteServiceView;
|
|
40
|
+
localOrigin: string;
|
|
41
|
+
configuredEnabled: boolean;
|
|
42
|
+
platformBase?: string | null;
|
|
43
|
+
runtime: RemoteRuntimeView | null;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export type RemoteDoctorCheckView = {
|
|
47
|
+
name: string;
|
|
48
|
+
ok: boolean;
|
|
49
|
+
detail: string;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export type RemoteDoctorView = {
|
|
53
|
+
generatedAt: string;
|
|
54
|
+
checks: RemoteDoctorCheckView[];
|
|
55
|
+
snapshot: {
|
|
56
|
+
configuredEnabled: boolean;
|
|
57
|
+
runtime: RemoteRuntimeView | null;
|
|
58
|
+
};
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
export type RemoteLoginRequest = {
|
|
62
|
+
email: string;
|
|
63
|
+
password: string;
|
|
64
|
+
apiBase?: string;
|
|
65
|
+
register?: boolean;
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
export type RemoteBrowserAuthStartRequest = {
|
|
69
|
+
apiBase?: string;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
export type RemoteBrowserAuthStartResult = {
|
|
73
|
+
sessionId: string;
|
|
74
|
+
verificationUri: string;
|
|
75
|
+
expiresAt: string;
|
|
76
|
+
intervalMs: number;
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
export type RemoteBrowserAuthPollRequest = {
|
|
80
|
+
sessionId: string;
|
|
81
|
+
apiBase?: string;
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
export type RemoteBrowserAuthPollResult = {
|
|
85
|
+
status: "pending" | "authorized" | "expired";
|
|
86
|
+
message?: string;
|
|
87
|
+
nextPollMs?: number;
|
|
88
|
+
email?: string;
|
|
89
|
+
role?: string;
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
export type RemoteSettingsUpdateRequest = {
|
|
93
|
+
enabled?: boolean;
|
|
94
|
+
deviceName?: string;
|
|
95
|
+
platformApiBase?: string;
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
export type RemoteServiceAction = "start" | "restart" | "stop";
|
|
99
|
+
|
|
100
|
+
export type RemoteServiceActionResult = {
|
|
101
|
+
accepted: boolean;
|
|
102
|
+
action: RemoteServiceAction;
|
|
103
|
+
message: string;
|
|
104
|
+
};
|
package/src/api/types.ts
CHANGED
|
@@ -160,30 +160,27 @@ export type ProviderAuthImportResult = {
|
|
|
160
160
|
expiresAt?: string;
|
|
161
161
|
};
|
|
162
162
|
|
|
163
|
-
export type
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
export type AuthEnabledUpdateRequest = {
|
|
185
|
-
enabled: boolean;
|
|
186
|
-
};
|
|
163
|
+
export type {
|
|
164
|
+
AuthEnabledUpdateRequest,
|
|
165
|
+
AuthLoginRequest,
|
|
166
|
+
AuthPasswordUpdateRequest,
|
|
167
|
+
AuthSetupRequest,
|
|
168
|
+
AuthStatusView
|
|
169
|
+
} from './auth.types';
|
|
170
|
+
|
|
171
|
+
export type {
|
|
172
|
+
RemoteAccessView,
|
|
173
|
+
RemoteAccountView,
|
|
174
|
+
RemoteDoctorCheckView,
|
|
175
|
+
RemoteDoctorView,
|
|
176
|
+
RemoteLoginRequest,
|
|
177
|
+
RemoteRuntimeView,
|
|
178
|
+
RemoteServiceAction,
|
|
179
|
+
RemoteServiceActionResult,
|
|
180
|
+
RemoteServiceView,
|
|
181
|
+
RemoteSettingsUpdateRequest,
|
|
182
|
+
RemoteSettingsView
|
|
183
|
+
} from './remote.types';
|
|
187
184
|
|
|
188
185
|
export type AgentProfileView = {
|
|
189
186
|
id: string;
|
|
@@ -223,6 +220,7 @@ export type SessionEntryView = {
|
|
|
223
220
|
updatedAt: string;
|
|
224
221
|
label?: string;
|
|
225
222
|
preferredModel?: string;
|
|
223
|
+
preferredThinking?: ThinkingLevel | null;
|
|
226
224
|
sessionType: string;
|
|
227
225
|
sessionTypeMutable: boolean;
|
|
228
226
|
messageCount: number;
|
|
@@ -316,6 +314,12 @@ export type ChatTurnStreamReadyEvent = {
|
|
|
316
314
|
stopReason?: string;
|
|
317
315
|
};
|
|
318
316
|
|
|
317
|
+
export type {
|
|
318
|
+
ChatSessionTypeCtaView,
|
|
319
|
+
ChatSessionTypeOptionView,
|
|
320
|
+
ChatSessionTypesView,
|
|
321
|
+
} from './chat-session-type.types';
|
|
322
|
+
|
|
319
323
|
export type ChatTurnStreamDeltaEvent = {
|
|
320
324
|
delta: string;
|
|
321
325
|
};
|
|
@@ -334,16 +338,6 @@ export type ChatCapabilitiesView = {
|
|
|
334
338
|
stopReason?: string;
|
|
335
339
|
};
|
|
336
340
|
|
|
337
|
-
export type ChatSessionTypeOptionView = {
|
|
338
|
-
value: string;
|
|
339
|
-
label: string;
|
|
340
|
-
};
|
|
341
|
-
|
|
342
|
-
export type ChatSessionTypesView = {
|
|
343
|
-
defaultType: string;
|
|
344
|
-
options: ChatSessionTypeOptionView[];
|
|
345
|
-
};
|
|
346
|
-
|
|
347
341
|
export type ChatCommandOptionView = {
|
|
348
342
|
name: string;
|
|
349
343
|
description: string;
|
|
@@ -68,8 +68,8 @@ describe('ChatSidebar', () => {
|
|
|
68
68
|
...useChatInputStore.getState().snapshot,
|
|
69
69
|
defaultSessionType: 'native',
|
|
70
70
|
sessionTypeOptions: [
|
|
71
|
-
{ value: 'native', label: 'Native' },
|
|
72
|
-
{ value: 'codex', label: 'Codex' }
|
|
71
|
+
{ value: 'native', label: 'Native', ready: true },
|
|
72
|
+
{ value: 'codex', label: 'Codex', ready: true }
|
|
73
73
|
]
|
|
74
74
|
}
|
|
75
75
|
});
|
|
@@ -105,6 +105,35 @@ describe('ChatSidebar', () => {
|
|
|
105
105
|
});
|
|
106
106
|
});
|
|
107
107
|
|
|
108
|
+
it('shows setup required status for runtime session types that are not ready yet', () => {
|
|
109
|
+
useChatInputStore.setState({
|
|
110
|
+
snapshot: {
|
|
111
|
+
...useChatInputStore.getState().snapshot,
|
|
112
|
+
sessionTypeOptions: [
|
|
113
|
+
{ value: 'native', label: 'Native', ready: true },
|
|
114
|
+
{
|
|
115
|
+
value: 'claude',
|
|
116
|
+
label: 'Claude',
|
|
117
|
+
ready: false,
|
|
118
|
+
reasonMessage: 'Configure a provider API key first.'
|
|
119
|
+
}
|
|
120
|
+
]
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
render(
|
|
125
|
+
<MemoryRouter>
|
|
126
|
+
<ChatSidebar />
|
|
127
|
+
</MemoryRouter>
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
fireEvent.click(screen.getByLabelText('Session Type'));
|
|
131
|
+
|
|
132
|
+
expect(screen.getByText('Claude')).not.toBeNull();
|
|
133
|
+
expect(screen.getByText('Setup')).not.toBeNull();
|
|
134
|
+
expect(screen.getByText('Configure a provider API key first.')).not.toBeNull();
|
|
135
|
+
});
|
|
136
|
+
|
|
108
137
|
it('shows a session type badge for non-native sessions in the list', () => {
|
|
109
138
|
useChatSessionListStore.setState({
|
|
110
139
|
snapshot: {
|
|
@@ -85,6 +85,16 @@ function resolveSessionTypeLabel(
|
|
|
85
85
|
.join(' ');
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
+
function resolveSessionTypeStatusText(option: {
|
|
89
|
+
ready?: boolean;
|
|
90
|
+
reasonMessage?: string | null;
|
|
91
|
+
}): string {
|
|
92
|
+
if (option.ready === false) {
|
|
93
|
+
return option.reasonMessage?.trim() || t('statusSetup');
|
|
94
|
+
}
|
|
95
|
+
return t('statusReady');
|
|
96
|
+
}
|
|
97
|
+
|
|
88
98
|
const navItems = [
|
|
89
99
|
{ target: '/cron', label: () => t('chatSidebarScheduledTasks'), icon: AlarmClock },
|
|
90
100
|
{ target: '/skills', label: () => t('chatSidebarSkills'), icon: BrainCircuit },
|
|
@@ -168,8 +178,22 @@ export function ChatSidebar() {
|
|
|
168
178
|
}}
|
|
169
179
|
className="w-full rounded-xl px-3 py-2 text-left transition-colors hover:bg-gray-100"
|
|
170
180
|
>
|
|
171
|
-
<div className="
|
|
172
|
-
|
|
181
|
+
<div className="flex items-center justify-between gap-3">
|
|
182
|
+
<div className="text-[13px] font-medium text-gray-900">{option.label}</div>
|
|
183
|
+
<span
|
|
184
|
+
className={cn(
|
|
185
|
+
'shrink-0 rounded-full px-2 py-0.5 text-[10px] font-semibold',
|
|
186
|
+
option.ready === false
|
|
187
|
+
? 'bg-amber-100 text-amber-800'
|
|
188
|
+
: 'bg-emerald-100 text-emerald-700'
|
|
189
|
+
)}
|
|
190
|
+
>
|
|
191
|
+
{option.ready === false ? t('statusSetup') : t('statusReady')}
|
|
192
|
+
</span>
|
|
193
|
+
</div>
|
|
194
|
+
<div className="mt-0.5 text-[11px] text-gray-500">
|
|
195
|
+
{resolveSessionTypeStatusText(option)}
|
|
196
|
+
</div>
|
|
173
197
|
</button>
|
|
174
198
|
))}
|
|
175
199
|
</div>
|