@nextclaw/ui 0.6.15 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +26 -0
- package/README.md +2 -0
- package/dist/assets/ChannelsList-DBcoVJRW.js +1 -0
- package/dist/assets/ChatPage-CD3cxyyM.js +37 -0
- package/dist/assets/DocBrowser-DDX2HMXW.js +1 -0
- package/dist/assets/{LogoBadge-Cer0jX6t.js → LogoBadge-J53F_3JA.js} +1 -1
- package/dist/assets/MarketplacePage-0BZ4bza0.js +49 -0
- package/dist/assets/ModelConfig-Wzq9wGHV.js +1 -0
- package/dist/assets/ProvidersList-kwzRS8_M.js +1 -0
- package/dist/assets/RuntimeConfig-N771_AM6.js +1 -0
- package/dist/assets/SearchConfig-DVt5QVa_.js +1 -0
- package/dist/assets/{SecretsConfig-BnGVZiv4.js → SecretsConfig-CkwauPa8.js} +2 -2
- package/dist/assets/SessionsConfig-C3mnHzkZ.js +2 -0
- package/dist/assets/{session-run-status-tZ4ISNj-.js → chat-message-pxr79GDs.js} +3 -3
- package/dist/assets/index-BIvFMkN4.js +1 -0
- package/dist/assets/index-CzkY1reu.js +8 -0
- package/dist/assets/{index-CkqvHQAt.js → index-GdpEEKnz.js} +1 -1
- package/dist/assets/index-RZ0kHHRI.css +1 -0
- package/dist/assets/{label-DkL14Jvl.js → label-CmksBHgc.js} +1 -1
- package/dist/assets/page-layout-Db0GbnhS.js +1 -0
- package/dist/assets/security-config-CjLFME5Q.js +1 -0
- package/dist/assets/skeleton-CkpQeVWN.js +1 -0
- package/dist/assets/{switch-CgbPbIX3.js → switch-C24d-UJU.js} +1 -1
- package/dist/assets/tabs-custom-D89bh-fc.js +1 -0
- package/dist/assets/useConfirmDialog-BeP35LcG.js +5 -0
- package/dist/assets/vendor-psXJBy9u.js +407 -0
- package/dist/index.html +3 -3
- package/package.json +12 -5
- package/src/App.tsx +49 -27
- package/src/api/client.ts +1 -0
- package/src/api/config.ts +98 -0
- package/src/api/types.ts +45 -0
- package/src/components/auth/login-page.tsx +69 -0
- package/src/components/chat/ChatConversationPanel.tsx +12 -54
- package/src/components/chat/ChatPage.tsx +10 -324
- package/src/components/chat/adapters/chat-input-bar.adapter.test.ts +80 -0
- package/src/components/chat/adapters/chat-input-bar.adapter.ts +329 -0
- package/src/components/chat/adapters/chat-message.adapter.test.ts +138 -0
- package/src/components/chat/adapters/chat-message.adapter.ts +200 -0
- package/src/components/chat/chat-chain.test.ts +22 -0
- package/src/components/chat/chat-chain.ts +23 -0
- package/src/components/chat/chat-input/chat-input-bar.controller.test.tsx +128 -0
- package/src/components/chat/chat-input/chat-input-bar.controller.ts +105 -0
- package/src/components/chat/chat-page-shell.tsx +103 -0
- package/src/components/chat/containers/chat-input-bar.container.tsx +270 -0
- package/src/components/chat/containers/chat-message-list.container.tsx +71 -0
- package/src/components/chat/index.ts +1 -0
- package/src/components/chat/legacy/LegacyChatPage.tsx +228 -0
- package/src/components/chat/managers/chat-thread.manager.ts +3 -1
- package/src/components/chat/ncp/NcpChatPage.tsx +349 -0
- package/src/components/chat/ncp/ncp-chat-input.manager.ts +173 -0
- package/src/components/chat/ncp/ncp-chat-page-data.ts +134 -0
- package/src/components/chat/ncp/ncp-chat-thread.manager.ts +89 -0
- package/src/components/chat/ncp/ncp-chat.presenter.ts +33 -0
- package/src/components/chat/ncp/ncp-session-adapter.test.ts +49 -0
- package/src/components/chat/ncp/ncp-session-adapter.ts +194 -0
- package/src/components/chat/nextclaw/index.ts +23 -0
- package/src/components/chat/presenter/chat-presenter-context.tsx +43 -4
- package/src/components/config/runtime-security-card.tsx +276 -0
- package/src/components/config/security-config.tsx +12 -0
- package/src/components/layout/Sidebar.tsx +6 -1
- package/src/components/marketplace/MarketplacePage.test.tsx +170 -0
- package/src/components/marketplace/MarketplacePage.tsx +77 -28
- package/src/hooks/use-auth.ts +111 -0
- package/src/hooks/useConfig.ts +42 -0
- package/src/hooks/useMarketplace.ts +9 -0
- package/src/lib/i18n.ts +73 -1
- package/src/test/setup.ts +16 -0
- package/tailwind.config.js +8 -3
- package/tsconfig.json +6 -2
- package/vite.config.ts +2 -1
- package/vitest.config.ts +16 -0
- package/dist/assets/ChannelsList-DzeVn-JC.js +0 -1
- package/dist/assets/ChatPage-BiFhIm1-.js +0 -36
- package/dist/assets/DocBrowser-By3lF9yN.js +0 -1
- package/dist/assets/MarketplacePage-EZxALdIz.js +0 -49
- package/dist/assets/ModelConfig-AchYxLft.js +0 -1
- package/dist/assets/ProvidersList-BsD-4kKX.js +0 -1
- package/dist/assets/RuntimeConfig-sKOERbFD.js +0 -1
- package/dist/assets/SearchConfig-DAfvDwX6.js +0 -1
- package/dist/assets/SessionsConfig-CzvrKDRs.js +0 -2
- package/dist/assets/card-BAM7vbMg.js +0 -1
- package/dist/assets/index-D9rRqOi8.css +0 -1
- package/dist/assets/index-DJZ5y7t1.js +0 -8
- package/dist/assets/input-BoelTiYL.js +0 -1
- package/dist/assets/page-layout-CERNdqzB.js +0 -1
- package/dist/assets/popover-uwYz3Chm.js +0 -1
- package/dist/assets/tabs-custom-pDyl95el.js +0 -1
- package/dist/assets/useConfirmDialog-DyP6Ac75.js +0 -5
- package/dist/assets/vendor-BKtTvQYU.js +0 -407
- package/src/components/chat/ChatThread.tsx +0 -402
- package/src/components/chat/SkillsPicker.tsx +0 -137
- package/src/components/chat/chat-input/ChatInputBarView.tsx +0 -82
- package/src/components/chat/chat-input/ChatInputBottomToolbar.tsx +0 -83
- package/src/components/chat/chat-input/components/ChatInputModelStateHint.tsx +0 -39
- package/src/components/chat/chat-input/components/ChatInputSelectedSkillsSection.tsx +0 -31
- package/src/components/chat/chat-input/components/ChatInputSlashPanelSection.tsx +0 -112
- package/src/components/chat/chat-input/components/bottom-toolbar/ChatInputAttachButton.tsx +0 -24
- package/src/components/chat/chat-input/components/bottom-toolbar/ChatInputModelSelector.tsx +0 -58
- package/src/components/chat/chat-input/components/bottom-toolbar/ChatInputSendControls.tsx +0 -56
- package/src/components/chat/chat-input/components/bottom-toolbar/ChatInputSessionTypeSelector.tsx +0 -40
- package/src/components/chat/chat-input/components/bottom-toolbar/ChatInputThinkingSelector.tsx +0 -74
- package/src/components/chat/chat-input/useChatInputBarController.ts +0 -322
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-CzkY1reu.js"></script>
|
|
10
|
+
<link rel="modulepreload" crossorigin href="/assets/vendor-psXJBy9u.js">
|
|
11
|
+
<link rel="stylesheet" crossorigin href="/assets/index-RZ0kHHRI.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.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -20,34 +20,41 @@
|
|
|
20
20
|
"react": "^18.3.1",
|
|
21
21
|
"react-dom": "^18.3.1",
|
|
22
22
|
"react-hook-form": "^7.53.2",
|
|
23
|
-
"react-markdown": "^10.1.0",
|
|
24
23
|
"react-router-dom": "^7.13.0",
|
|
25
24
|
"rehype-sanitize": "^6.0.0",
|
|
26
|
-
"remark-gfm": "^4.0.1",
|
|
27
25
|
"rxjs": "^7.8.2",
|
|
28
26
|
"sonner": "^1.7.1",
|
|
29
27
|
"tailwind-merge": "^2.5.4",
|
|
30
28
|
"zod": "^3.23.8",
|
|
31
29
|
"zustand": "^5.0.2",
|
|
30
|
+
"@nextclaw/ncp-http-agent-client": "0.3.0",
|
|
31
|
+
"@nextclaw/ncp-react": "0.3.0",
|
|
32
|
+
"@nextclaw/agent-chat-ui": "0.2.0",
|
|
33
|
+
"@nextclaw/ncp": "0.3.0",
|
|
32
34
|
"@nextclaw/agent-chat": "0.1.1"
|
|
33
35
|
},
|
|
34
36
|
"devDependencies": {
|
|
37
|
+
"@testing-library/react": "^16.3.0",
|
|
38
|
+
"@testing-library/user-event": "^14.6.1",
|
|
35
39
|
"@types/react": "^18.3.12",
|
|
36
40
|
"@types/react-dom": "^18.3.1",
|
|
37
41
|
"@vitejs/plugin-react": "^4.3.4",
|
|
38
42
|
"autoprefixer": "^10.4.20",
|
|
43
|
+
"jsdom": "^25.0.1",
|
|
39
44
|
"postcss": "^8.4.49",
|
|
40
45
|
"prettier": "^3.3.3",
|
|
41
46
|
"tailwindcss": "^3.4.15",
|
|
42
47
|
"tailwindcss-animate": "^1.0.7",
|
|
43
48
|
"typescript": "^5.6.3",
|
|
44
|
-
"vite": "^6.0.1"
|
|
49
|
+
"vite": "^6.0.1",
|
|
50
|
+
"vitest": "^2.1.2"
|
|
45
51
|
},
|
|
46
52
|
"scripts": {
|
|
47
53
|
"dev": "vite",
|
|
48
54
|
"build": "tsc && vite build",
|
|
49
55
|
"preview": "vite preview",
|
|
50
56
|
"lint": "eslint .",
|
|
51
|
-
"tsc": "tsc --noEmit"
|
|
57
|
+
"tsc": "tsc --noEmit",
|
|
58
|
+
"test": "vitest run"
|
|
52
59
|
}
|
|
53
60
|
}
|
package/src/App.tsx
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { lazy, Suspense } from 'react';
|
|
2
2
|
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|
3
|
+
import { LoginPage } from '@/components/auth/login-page';
|
|
3
4
|
import { AppLayout } from '@/components/layout/AppLayout';
|
|
5
|
+
import { useAuthStatus } from '@/hooks/use-auth';
|
|
4
6
|
import { useWebSocket } from '@/hooks/useWebSocket';
|
|
5
7
|
import { Toaster } from 'sonner';
|
|
6
8
|
import { Routes, Route, Navigate } from 'react-router-dom';
|
|
@@ -20,6 +22,7 @@ const SearchConfigPage = lazy(async () => ({ default: (await import('@/component
|
|
|
20
22
|
const ProvidersListPage = lazy(async () => ({ default: (await import('@/components/config/ProvidersList')).ProvidersList }));
|
|
21
23
|
const ChannelsListPage = lazy(async () => ({ default: (await import('@/components/config/ChannelsList')).ChannelsList }));
|
|
22
24
|
const RuntimeConfigPage = lazy(async () => ({ default: (await import('@/components/config/RuntimeConfig')).RuntimeConfig }));
|
|
25
|
+
const SecurityConfigPage = lazy(async () => ({ default: (await import('@/components/config/security-config')).SecurityConfig }));
|
|
23
26
|
const SessionsConfigPage = lazy(async () => ({ default: (await import('@/components/config/SessionsConfig')).SessionsConfig }));
|
|
24
27
|
const SecretsConfigPage = lazy(async () => ({ default: (await import('@/components/config/SecretsConfig')).SecretsConfig }));
|
|
25
28
|
const MarketplacePage = lazy(async () => ({ default: (await import('@/components/marketplace/MarketplacePage')).MarketplacePage }));
|
|
@@ -32,38 +35,57 @@ function LazyRoute({ children }: { children: JSX.Element }) {
|
|
|
32
35
|
return <Suspense fallback={<RouteFallback />}>{children}</Suspense>;
|
|
33
36
|
}
|
|
34
37
|
|
|
35
|
-
function
|
|
38
|
+
function ProtectedApp() {
|
|
36
39
|
useWebSocket(queryClient); // Initialize WebSocket connection
|
|
37
40
|
|
|
41
|
+
return (
|
|
42
|
+
<AppLayout>
|
|
43
|
+
<div className="w-full h-full">
|
|
44
|
+
<Routes>
|
|
45
|
+
<Route path="/chat/skills" element={<Navigate to="/skills" replace />} />
|
|
46
|
+
<Route path="/chat/cron" element={<Navigate to="/cron" replace />} />
|
|
47
|
+
<Route path="/chat/:sessionId?" element={<LazyRoute><ChatPage view="chat" /></LazyRoute>} />
|
|
48
|
+
<Route path="/skills" element={<LazyRoute><ChatPage view="skills" /></LazyRoute>} />
|
|
49
|
+
<Route path="/cron" element={<LazyRoute><ChatPage view="cron" /></LazyRoute>} />
|
|
50
|
+
<Route path="/model" element={<LazyRoute><ModelConfigPage /></LazyRoute>} />
|
|
51
|
+
<Route path="/search" element={<LazyRoute><SearchConfigPage /></LazyRoute>} />
|
|
52
|
+
<Route path="/providers" element={<LazyRoute><ProvidersListPage /></LazyRoute>} />
|
|
53
|
+
<Route path="/channels" element={<LazyRoute><ChannelsListPage /></LazyRoute>} />
|
|
54
|
+
<Route path="/runtime" element={<LazyRoute><RuntimeConfigPage /></LazyRoute>} />
|
|
55
|
+
<Route path="/security" element={<LazyRoute><SecurityConfigPage /></LazyRoute>} />
|
|
56
|
+
<Route path="/sessions" element={<LazyRoute><SessionsConfigPage /></LazyRoute>} />
|
|
57
|
+
<Route path="/secrets" element={<LazyRoute><SecretsConfigPage /></LazyRoute>} />
|
|
58
|
+
<Route path="/settings" element={<Navigate to="/model" replace />} />
|
|
59
|
+
<Route path="/marketplace/skills" element={<Navigate to="/skills" replace />} />
|
|
60
|
+
<Route path="/marketplace" element={<Navigate to="/marketplace/plugins" replace />} />
|
|
61
|
+
<Route path="/marketplace/:type" element={<LazyRoute><MarketplacePage /></LazyRoute>} />
|
|
62
|
+
<Route path="/" element={<Navigate to="/chat" replace />} />
|
|
63
|
+
<Route path="*" element={<Navigate to="/chat" replace />} />
|
|
64
|
+
</Routes>
|
|
65
|
+
</div>
|
|
66
|
+
</AppLayout>
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function AuthGate() {
|
|
71
|
+
const authStatus = useAuthStatus();
|
|
72
|
+
|
|
73
|
+
if (authStatus.isLoading && !authStatus.isError) {
|
|
74
|
+
return <RouteFallback />;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (authStatus.data?.enabled && !authStatus.data.authenticated) {
|
|
78
|
+
return <LoginPage username={authStatus.data.username} />;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return <ProtectedApp />;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export default function AppContent() {
|
|
38
85
|
return (
|
|
39
86
|
<QueryClientProvider client={queryClient}>
|
|
40
|
-
<
|
|
41
|
-
<div className="w-full h-full">
|
|
42
|
-
<Routes>
|
|
43
|
-
<Route path="/chat/skills" element={<Navigate to="/skills" replace />} />
|
|
44
|
-
<Route path="/chat/cron" element={<Navigate to="/cron" replace />} />
|
|
45
|
-
<Route path="/chat/:sessionId?" element={<LazyRoute><ChatPage view="chat" /></LazyRoute>} />
|
|
46
|
-
<Route path="/skills" element={<LazyRoute><ChatPage view="skills" /></LazyRoute>} />
|
|
47
|
-
<Route path="/cron" element={<LazyRoute><ChatPage view="cron" /></LazyRoute>} />
|
|
48
|
-
<Route path="/model" element={<LazyRoute><ModelConfigPage /></LazyRoute>} />
|
|
49
|
-
<Route path="/search" element={<LazyRoute><SearchConfigPage /></LazyRoute>} />
|
|
50
|
-
<Route path="/providers" element={<LazyRoute><ProvidersListPage /></LazyRoute>} />
|
|
51
|
-
<Route path="/channels" element={<LazyRoute><ChannelsListPage /></LazyRoute>} />
|
|
52
|
-
<Route path="/runtime" element={<LazyRoute><RuntimeConfigPage /></LazyRoute>} />
|
|
53
|
-
<Route path="/sessions" element={<LazyRoute><SessionsConfigPage /></LazyRoute>} />
|
|
54
|
-
<Route path="/secrets" element={<LazyRoute><SecretsConfigPage /></LazyRoute>} />
|
|
55
|
-
<Route path="/settings" element={<Navigate to="/model" replace />} />
|
|
56
|
-
<Route path="/marketplace/skills" element={<Navigate to="/skills" replace />} />
|
|
57
|
-
<Route path="/marketplace" element={<Navigate to="/marketplace/plugins" replace />} />
|
|
58
|
-
<Route path="/marketplace/:type" element={<LazyRoute><MarketplacePage /></LazyRoute>} />
|
|
59
|
-
<Route path="/" element={<Navigate to="/chat" replace />} />
|
|
60
|
-
<Route path="*" element={<Navigate to="/chat" replace />} />
|
|
61
|
-
</Routes>
|
|
62
|
-
</div>
|
|
63
|
-
</AppLayout>
|
|
87
|
+
<AuthGate />
|
|
64
88
|
<Toaster position="top-right" richColors />
|
|
65
89
|
</QueryClientProvider>
|
|
66
90
|
);
|
|
67
91
|
}
|
|
68
|
-
|
|
69
|
-
export default AppContent;
|
package/src/api/client.ts
CHANGED
package/src/api/config.ts
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import { api, API_BASE } from './client';
|
|
2
2
|
import type {
|
|
3
|
+
AuthEnabledUpdateRequest,
|
|
4
|
+
AuthLoginRequest,
|
|
5
|
+
AuthPasswordUpdateRequest,
|
|
6
|
+
AuthSetupRequest,
|
|
7
|
+
AuthStatusView,
|
|
3
8
|
AppMetaView,
|
|
4
9
|
ConfigView,
|
|
5
10
|
ConfigMetaView,
|
|
@@ -19,6 +24,8 @@ import type {
|
|
|
19
24
|
ProviderCreateRequest,
|
|
20
25
|
ProviderCreateResult,
|
|
21
26
|
ProviderDeleteResult,
|
|
27
|
+
NcpSessionMessagesView,
|
|
28
|
+
NcpSessionsListView,
|
|
22
29
|
RuntimeConfigUpdate,
|
|
23
30
|
SecretsConfigUpdate,
|
|
24
31
|
SecretsView,
|
|
@@ -46,6 +53,60 @@ import type {
|
|
|
46
53
|
CronActionResult
|
|
47
54
|
} from './types';
|
|
48
55
|
|
|
56
|
+
// GET /api/auth/status
|
|
57
|
+
export async function fetchAuthStatus(): Promise<AuthStatusView> {
|
|
58
|
+
const response = await api.get<AuthStatusView>('/api/auth/status');
|
|
59
|
+
if (!response.ok) {
|
|
60
|
+
throw new Error(response.error.message);
|
|
61
|
+
}
|
|
62
|
+
return response.data;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// POST /api/auth/setup
|
|
66
|
+
export async function setupAuth(data: AuthSetupRequest): Promise<AuthStatusView> {
|
|
67
|
+
const response = await api.post<AuthStatusView>('/api/auth/setup', data);
|
|
68
|
+
if (!response.ok) {
|
|
69
|
+
throw new Error(response.error.message);
|
|
70
|
+
}
|
|
71
|
+
return response.data;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// POST /api/auth/login
|
|
75
|
+
export async function loginAuth(data: AuthLoginRequest): Promise<AuthStatusView> {
|
|
76
|
+
const response = await api.post<AuthStatusView>('/api/auth/login', data);
|
|
77
|
+
if (!response.ok) {
|
|
78
|
+
throw new Error(response.error.message);
|
|
79
|
+
}
|
|
80
|
+
return response.data;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// POST /api/auth/logout
|
|
84
|
+
export async function logoutAuth(): Promise<{ success: boolean }> {
|
|
85
|
+
const response = await api.post<{ success: boolean }>('/api/auth/logout', {});
|
|
86
|
+
if (!response.ok) {
|
|
87
|
+
throw new Error(response.error.message);
|
|
88
|
+
}
|
|
89
|
+
return response.data;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// PUT /api/auth/password
|
|
93
|
+
export async function updateAuthPassword(data: AuthPasswordUpdateRequest): Promise<AuthStatusView> {
|
|
94
|
+
const response = await api.put<AuthStatusView>('/api/auth/password', data);
|
|
95
|
+
if (!response.ok) {
|
|
96
|
+
throw new Error(response.error.message);
|
|
97
|
+
}
|
|
98
|
+
return response.data;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// PUT /api/auth/enabled
|
|
102
|
+
export async function updateAuthEnabled(data: AuthEnabledUpdateRequest): Promise<AuthStatusView> {
|
|
103
|
+
const response = await api.put<AuthStatusView>('/api/auth/enabled', data);
|
|
104
|
+
if (!response.ok) {
|
|
105
|
+
throw new Error(response.error.message);
|
|
106
|
+
}
|
|
107
|
+
return response.data;
|
|
108
|
+
}
|
|
109
|
+
|
|
49
110
|
// GET /api/app/meta
|
|
50
111
|
export async function fetchAppMeta(): Promise<AppMetaView> {
|
|
51
112
|
const response = await api.get<AppMetaView>('/api/app/meta');
|
|
@@ -310,6 +371,42 @@ export async function deleteSession(key: string): Promise<{ deleted: boolean }>
|
|
|
310
371
|
return response.data;
|
|
311
372
|
}
|
|
312
373
|
|
|
374
|
+
// GET /api/ncp/sessions
|
|
375
|
+
export async function fetchNcpSessions(params?: { limit?: number }): Promise<NcpSessionsListView> {
|
|
376
|
+
const query = new URLSearchParams();
|
|
377
|
+
if (typeof params?.limit === 'number' && Number.isFinite(params.limit)) {
|
|
378
|
+
query.set('limit', String(Math.max(1, Math.trunc(params.limit))));
|
|
379
|
+
}
|
|
380
|
+
const suffix = query.toString();
|
|
381
|
+
const response = await api.get<NcpSessionsListView>(suffix ? `/api/ncp/sessions?${suffix}` : '/api/ncp/sessions');
|
|
382
|
+
if (!response.ok) {
|
|
383
|
+
throw new Error(response.error.message);
|
|
384
|
+
}
|
|
385
|
+
return response.data;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// GET /api/ncp/sessions/:sessionId/messages
|
|
389
|
+
export async function fetchNcpSessionMessages(sessionId: string, limit = 200): Promise<NcpSessionMessagesView> {
|
|
390
|
+
const response = await api.get<NcpSessionMessagesView>(
|
|
391
|
+
`/api/ncp/sessions/${encodeURIComponent(sessionId)}/messages?limit=${Math.max(1, Math.trunc(limit))}`
|
|
392
|
+
);
|
|
393
|
+
if (!response.ok) {
|
|
394
|
+
throw new Error(response.error.message);
|
|
395
|
+
}
|
|
396
|
+
return response.data;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// DELETE /api/ncp/sessions/:sessionId
|
|
400
|
+
export async function deleteNcpSession(sessionId: string): Promise<{ deleted: boolean; sessionId: string }> {
|
|
401
|
+
const response = await api.delete<{ deleted: boolean; sessionId: string }>(
|
|
402
|
+
`/api/ncp/sessions/${encodeURIComponent(sessionId)}`
|
|
403
|
+
);
|
|
404
|
+
if (!response.ok) {
|
|
405
|
+
throw new Error(response.error.message);
|
|
406
|
+
}
|
|
407
|
+
return response.data;
|
|
408
|
+
}
|
|
409
|
+
|
|
313
410
|
// POST /api/chat/turn
|
|
314
411
|
export async function sendChatTurn(data: ChatTurnRequest): Promise<ChatTurnView> {
|
|
315
412
|
const response = await api.post<ChatTurnView>('/api/chat/turn', data);
|
|
@@ -356,6 +453,7 @@ async function readSseStream(params: {
|
|
|
356
453
|
}): Promise<{ sessionKey: string; reply: string }> {
|
|
357
454
|
const response = await fetch(`${API_BASE}${params.path}`, {
|
|
358
455
|
method: params.method,
|
|
456
|
+
credentials: 'include',
|
|
359
457
|
headers: {
|
|
360
458
|
'Content-Type': 'application/json',
|
|
361
459
|
Accept: 'text/event-stream'
|
package/src/api/types.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { NcpMessage, NcpSessionStatus, NcpSessionSummary } from '@nextclaw/ncp';
|
|
2
|
+
|
|
1
3
|
// API Types - matching backend response format
|
|
2
4
|
export type ApiError = {
|
|
3
5
|
code: string;
|
|
@@ -158,6 +160,31 @@ export type ProviderAuthImportResult = {
|
|
|
158
160
|
expiresAt?: string;
|
|
159
161
|
};
|
|
160
162
|
|
|
163
|
+
export type AuthStatusView = {
|
|
164
|
+
enabled: boolean;
|
|
165
|
+
configured: boolean;
|
|
166
|
+
authenticated: boolean;
|
|
167
|
+
username?: string;
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
export type AuthSetupRequest = {
|
|
171
|
+
username: string;
|
|
172
|
+
password: string;
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
export type AuthLoginRequest = {
|
|
176
|
+
username: string;
|
|
177
|
+
password: string;
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
export type AuthPasswordUpdateRequest = {
|
|
181
|
+
password: string;
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
export type AuthEnabledUpdateRequest = {
|
|
185
|
+
enabled: boolean;
|
|
186
|
+
};
|
|
187
|
+
|
|
161
188
|
export type AgentProfileView = {
|
|
162
189
|
id: string;
|
|
163
190
|
default?: boolean;
|
|
@@ -236,6 +263,23 @@ export type SessionHistoryView = {
|
|
|
236
263
|
events: SessionEventView[];
|
|
237
264
|
};
|
|
238
265
|
|
|
266
|
+
export type NcpSessionSummaryView = NcpSessionSummary;
|
|
267
|
+
|
|
268
|
+
export type NcpSessionsListView = {
|
|
269
|
+
sessions: NcpSessionSummaryView[];
|
|
270
|
+
total: number;
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
export type NcpMessageView = NcpMessage;
|
|
274
|
+
|
|
275
|
+
export type NcpSessionMessagesView = {
|
|
276
|
+
sessionId: string;
|
|
277
|
+
messages: NcpMessageView[];
|
|
278
|
+
total: number;
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
export type NcpSessionStatusView = NcpSessionStatus;
|
|
282
|
+
|
|
239
283
|
export type SessionPatchUpdate = {
|
|
240
284
|
label?: string | null;
|
|
241
285
|
preferredModel?: string | null;
|
|
@@ -501,6 +545,7 @@ export type ConfigView = {
|
|
|
501
545
|
session?: SessionConfigView;
|
|
502
546
|
tools?: Record<string, unknown>;
|
|
503
547
|
gateway?: Record<string, unknown>;
|
|
548
|
+
ui?: Record<string, unknown>;
|
|
504
549
|
secrets?: SecretsView;
|
|
505
550
|
};
|
|
506
551
|
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { useState, type FormEvent } from 'react';
|
|
2
|
+
import { Button } from '@/components/ui/button';
|
|
3
|
+
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
|
4
|
+
import { Input } from '@/components/ui/input';
|
|
5
|
+
import { useLoginAuth } from '@/hooks/use-auth';
|
|
6
|
+
import { t } from '@/lib/i18n';
|
|
7
|
+
|
|
8
|
+
type LoginPageProps = {
|
|
9
|
+
username?: string;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export function LoginPage({ username }: LoginPageProps) {
|
|
13
|
+
const loginMutation = useLoginAuth();
|
|
14
|
+
const [formUsername, setFormUsername] = useState(username ?? '');
|
|
15
|
+
const [password, setPassword] = useState('');
|
|
16
|
+
const canSubmit = formUsername.trim().length > 0 && password.length > 0 && !loginMutation.isPending;
|
|
17
|
+
|
|
18
|
+
const handleSubmit = (event?: FormEvent<HTMLFormElement>) => {
|
|
19
|
+
event?.preventDefault();
|
|
20
|
+
if (!canSubmit) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
loginMutation.mutate({
|
|
24
|
+
username: formUsername.trim(),
|
|
25
|
+
password
|
|
26
|
+
});
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<main className="flex min-h-screen items-center justify-center bg-secondary px-6 py-10">
|
|
31
|
+
<Card hover={false} className="w-full max-w-md shadow-card-hover">
|
|
32
|
+
<CardHeader>
|
|
33
|
+
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-gray-500">{t('authBrand')}</p>
|
|
34
|
+
<CardTitle className="text-2xl">{t('authLoginTitle')}</CardTitle>
|
|
35
|
+
<CardDescription>{t('authLoginDescription')}</CardDescription>
|
|
36
|
+
</CardHeader>
|
|
37
|
+
<CardContent>
|
|
38
|
+
<form className="space-y-4" onSubmit={handleSubmit}>
|
|
39
|
+
<div className="space-y-2">
|
|
40
|
+
<label className="text-sm font-medium text-gray-800">{t('authUsername')}</label>
|
|
41
|
+
<Input
|
|
42
|
+
value={formUsername}
|
|
43
|
+
onChange={(event) => setFormUsername(event.target.value)}
|
|
44
|
+
placeholder={t('authUsernamePlaceholder')}
|
|
45
|
+
autoFocus
|
|
46
|
+
/>
|
|
47
|
+
</div>
|
|
48
|
+
<div className="space-y-2">
|
|
49
|
+
<label className="text-sm font-medium text-gray-800">{t('authPassword')}</label>
|
|
50
|
+
<Input
|
|
51
|
+
type="password"
|
|
52
|
+
value={password}
|
|
53
|
+
onChange={(event) => setPassword(event.target.value)}
|
|
54
|
+
placeholder={t('authPasswordPlaceholder')}
|
|
55
|
+
/>
|
|
56
|
+
</div>
|
|
57
|
+
<Button
|
|
58
|
+
type="submit"
|
|
59
|
+
className="w-full"
|
|
60
|
+
disabled={!canSubmit}
|
|
61
|
+
>
|
|
62
|
+
{loginMutation.isPending ? t('authLoggingIn') : t('authLoginAction')}
|
|
63
|
+
</Button>
|
|
64
|
+
</form>
|
|
65
|
+
</CardContent>
|
|
66
|
+
</Card>
|
|
67
|
+
</main>
|
|
68
|
+
);
|
|
69
|
+
}
|
|
@@ -1,16 +1,14 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useRef } from 'react';
|
|
2
|
+
import { useStickyBottomScroll } from '@nextclaw/agent-chat-ui';
|
|
2
3
|
import { Button } from '@/components/ui/button';
|
|
3
|
-
import {
|
|
4
|
+
import { ChatInputBarContainer, ChatMessageListContainer } from '@/components/chat/nextclaw';
|
|
4
5
|
import { ChatWelcome } from '@/components/chat/ChatWelcome';
|
|
5
|
-
import { ChatInputBarView } from '@/components/chat/chat-input/ChatInputBarView';
|
|
6
6
|
import { usePresenter } from '@/components/chat/presenter/chat-presenter-context';
|
|
7
7
|
import { useChatThreadStore } from '@/components/chat/stores/chat-thread.store';
|
|
8
8
|
import { t } from '@/lib/i18n';
|
|
9
9
|
import { cn } from '@/lib/utils';
|
|
10
10
|
import { Trash2 } from 'lucide-react';
|
|
11
11
|
|
|
12
|
-
const STICKY_BOTTOM_THRESHOLD_PX = 10;
|
|
13
|
-
|
|
14
12
|
function ChatConversationSkeleton() {
|
|
15
13
|
return (
|
|
16
14
|
<section className="flex-1 min-h-0 flex flex-col overflow-hidden bg-gradient-to-b from-gray-50/60 to-white">
|
|
@@ -45,12 +43,6 @@ export function ChatConversationPanel() {
|
|
|
45
43
|
const fallbackThreadRef = useRef<HTMLDivElement | null>(null);
|
|
46
44
|
const threadRef = snapshot.threadRef ?? fallbackThreadRef;
|
|
47
45
|
|
|
48
|
-
// --- Sticky-to-bottom scroll state ---
|
|
49
|
-
const isStickyRef = useRef(true);
|
|
50
|
-
const isProgrammaticScrollRef = useRef(false);
|
|
51
|
-
const previousSessionKeyRef = useRef<string | null>(null);
|
|
52
|
-
const pendingInitialScrollRef = useRef(false);
|
|
53
|
-
|
|
54
46
|
const showWelcome = !snapshot.selectedSessionKey && snapshot.uiMessages.length === 0 && !snapshot.isSending;
|
|
55
47
|
const hasConfiguredModel = snapshot.modelOptions.length > 0;
|
|
56
48
|
const shouldShowProviderHint = snapshot.isProviderStateResolved && !hasConfiguredModel;
|
|
@@ -60,47 +52,13 @@ export function ChatConversationPanel() {
|
|
|
60
52
|
!snapshot.isSending &&
|
|
61
53
|
!snapshot.isAwaitingAssistantOutput;
|
|
62
54
|
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
const handleScroll = useCallback(() => {
|
|
71
|
-
// Skip sticky check for programmatic scrolls
|
|
72
|
-
if (isProgrammaticScrollRef.current) {
|
|
73
|
-
isProgrammaticScrollRef.current = false;
|
|
74
|
-
return;
|
|
75
|
-
}
|
|
76
|
-
const el = threadRef.current;
|
|
77
|
-
if (!el) return;
|
|
78
|
-
const distanceFromBottom = el.scrollHeight - el.scrollTop - el.clientHeight;
|
|
79
|
-
isStickyRef.current = distanceFromBottom <= STICKY_BOTTOM_THRESHOLD_PX;
|
|
80
|
-
}, [threadRef]);
|
|
81
|
-
|
|
82
|
-
// Session change → force sticky + schedule initial scroll
|
|
83
|
-
useEffect(() => {
|
|
84
|
-
if (previousSessionKeyRef.current === snapshot.selectedSessionKey) return;
|
|
85
|
-
previousSessionKeyRef.current = snapshot.selectedSessionKey;
|
|
86
|
-
isStickyRef.current = true;
|
|
87
|
-
pendingInitialScrollRef.current = true;
|
|
88
|
-
}, [snapshot.selectedSessionKey]);
|
|
89
|
-
|
|
90
|
-
// Initial scroll after history loads for a new session
|
|
91
|
-
useLayoutEffect(() => {
|
|
92
|
-
if (!pendingInitialScrollRef.current) return;
|
|
93
|
-
if (snapshot.isHistoryLoading || snapshot.uiMessages.length === 0) return;
|
|
94
|
-
pendingInitialScrollRef.current = false;
|
|
95
|
-
scrollToBottom();
|
|
96
|
-
}, [scrollToBottom, snapshot.isHistoryLoading, snapshot.uiMessages]);
|
|
97
|
-
|
|
98
|
-
// Streaming updates: keep bottom visible while still sticky.
|
|
99
|
-
useLayoutEffect(() => {
|
|
100
|
-
if (!isStickyRef.current) return;
|
|
101
|
-
if (snapshot.uiMessages.length === 0) return;
|
|
102
|
-
scrollToBottom();
|
|
103
|
-
}, [scrollToBottom, snapshot.uiMessages]);
|
|
55
|
+
const { onScroll: handleScroll } = useStickyBottomScroll({
|
|
56
|
+
scrollRef: threadRef,
|
|
57
|
+
resetKey: snapshot.selectedSessionKey,
|
|
58
|
+
isLoading: snapshot.isHistoryLoading,
|
|
59
|
+
hasContent: snapshot.uiMessages.length > 0,
|
|
60
|
+
contentVersion: snapshot.uiMessages
|
|
61
|
+
});
|
|
104
62
|
|
|
105
63
|
if (!snapshot.isProviderStateResolved) {
|
|
106
64
|
return <ChatConversationSkeleton />;
|
|
@@ -160,12 +118,12 @@ export function ChatConversationPanel() {
|
|
|
160
118
|
<div className="px-5 py-5 text-sm text-gray-500">{t('chatNoMessages')}</div>
|
|
161
119
|
) : (
|
|
162
120
|
<div className="mx-auto w-full max-w-[min(1120px,100%)] px-6 py-5">
|
|
163
|
-
<
|
|
121
|
+
<ChatMessageListContainer uiMessages={snapshot.uiMessages} isSending={snapshot.isSending && snapshot.isAwaitingAssistantOutput} />
|
|
164
122
|
</div>
|
|
165
123
|
)}
|
|
166
124
|
</div>
|
|
167
125
|
|
|
168
|
-
<
|
|
126
|
+
<ChatInputBarContainer />
|
|
169
127
|
</section>
|
|
170
128
|
);
|
|
171
129
|
}
|