@nextclaw/ui 0.10.5 → 0.11.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +23 -2
- package/dist/assets/{ChannelsList-Nu7Ig6_-.js → ChannelsList-CVPqrxns.js} +4 -4
- package/dist/assets/ChatPage-BO1VUrAY.js +37 -0
- package/dist/assets/{DocBrowser-3CfKmJA6.js → DocBrowser-FBwg8iji.js} +1 -1
- package/dist/assets/{LogoBadge-DdthDJOp.js → LogoBadge-BCmJfRT8.js} +1 -1
- package/dist/assets/MarketplacePage-DWxXUOCx.js +49 -0
- package/dist/assets/{McpMarketplacePage-Dg8GSZh6.js → McpMarketplacePage-Bth9X_hu.js} +2 -2
- package/dist/assets/{ModelConfig-DyQ6cC92.js → ModelConfig-PkSp_ioc.js} +1 -1
- package/dist/assets/ProvidersList-DVDge8wa.js +1 -0
- package/dist/assets/RemoteAccessPage-BVkzfEaL.js +1 -0
- package/dist/assets/RuntimeConfig-ByJs3khh.js +1 -0
- package/dist/assets/{SearchConfig-R1BcCLWO.js → SearchConfig-KZUAqYJN.js} +1 -1
- package/dist/assets/{SecretsConfig-D-jZMHeY.js → SecretsConfig-qwB_Y_Ka.js} +2 -2
- package/dist/assets/SessionsConfig-CGCl4UTr.js +2 -0
- package/dist/assets/index-CrilScMo.css +1 -0
- package/dist/assets/{index-BulnQWr6.js → index-D41ntvb7.js} +6 -6
- package/dist/assets/{label-C7yzBvzK.js → label-7JEFhkur.js} +1 -1
- package/dist/assets/ncp-session-adapter-BOqhkrc-.js +1 -0
- package/dist/assets/{page-layout-DF0xpax2.js → page-layout-B7q511TE.js} +1 -1
- package/dist/assets/popover-CywJGmPr.js +1 -0
- package/dist/assets/security-config-zi2UxN5r.js +1 -0
- package/dist/assets/skeleton-qUJZQ03S.js +1 -0
- package/dist/assets/{status-dot-B9opOZ22.js → status-dot-BilwNdTT.js} +1 -1
- package/dist/assets/{switch-l1P0ev4D.js → switch-BLp2Pno1.js} +1 -1
- package/dist/assets/tabs-custom-CgIdQMGC.js +1 -0
- package/dist/assets/useConfirmDialog-BitswAkv.js +1 -0
- package/dist/assets/{vendor-CNhxtHCf.js → vendor-D_JxmsLV.js} +87 -87
- package/dist/index.html +3 -3
- package/package.json +4 -4
- package/src/App.test.tsx +42 -10
- package/src/App.tsx +5 -40
- package/src/api/api-base.test.ts +37 -0
- package/src/api/api-base.ts +0 -4
- package/src/api/config.ts +2 -270
- package/src/api/ncp-attachments.ts +12 -12
- package/src/api/types.ts +4 -121
- package/src/components/chat/ChatPage.tsx +1 -11
- package/src/components/chat/ChatSidebar.test.tsx +1 -50
- package/src/components/chat/ChatSidebar.tsx +0 -5
- package/src/components/chat/README.md +2 -0
- package/src/components/chat/adapters/chat-message.adapter.test.ts +39 -0
- package/src/components/chat/adapters/chat-message.adapter.ts +56 -0
- package/src/components/chat/chat-attachment-upload-limit.test.ts +41 -0
- package/src/components/chat/chat-composer-state.test.ts +4 -4
- package/src/components/chat/chat-composer-state.ts +1 -1
- package/src/components/chat/chat-session-display.ts +9 -0
- package/src/components/chat/chat-session-label.service.ts +3 -12
- package/src/components/chat/chat-session-preference-sync.test.ts +10 -13
- package/src/components/chat/chat-stream/types.ts +4 -57
- package/src/components/chat/containers/chat-input-bar.container.tsx +2 -2
- package/src/components/chat/ncp/NcpChatPage.tsx +3 -3
- package/src/components/chat/ncp/ncp-chat-input.manager.ts +1 -1
- package/src/components/chat/useHydratedNcpAgent.test.tsx +77 -0
- package/src/components/config/README.md +2 -0
- package/src/components/config/SessionsConfig.tsx +152 -132
- package/src/hooks/use-auth.test.ts +3 -3
- package/src/hooks/use-auth.ts +16 -4
- package/src/hooks/use-realtime-query-bridge.ts +0 -24
- package/src/hooks/useConfig.ts +10 -137
- package/src/lib/session-run-status.ts +1 -63
- package/src/vite-env.d.ts +1 -0
- package/vite.config.ts +4 -4
- package/dist/assets/ChatPage-CBCFSk4e.js +0 -38
- package/dist/assets/MarketplacePage-inGGiv1T.js +0 -49
- package/dist/assets/ProvidersList-B2T8Lc_i.js +0 -1
- package/dist/assets/RemoteAccessPage-C9LxgK-C.js +0 -1
- package/dist/assets/RuntimeConfig-Ey4VIqTW.js +0 -1
- package/dist/assets/SessionsConfig-Cawoh4_2.js +0 -2
- package/dist/assets/chat-message-BbuIK4dQ.js +0 -3
- package/dist/assets/index-kaPUhd-8.css +0 -1
- package/dist/assets/popover-DjaScZDJ.js +0 -1
- package/dist/assets/security-config-Bg2eriNx.js +0 -1
- package/dist/assets/skeleton-DycBJAJF.js +0 -1
- package/dist/assets/tabs-custom-BG9y2JhC.js +0 -1
- package/dist/assets/useConfirmDialog-DTducNfn.js +0 -1
- package/src/api/config.stream.test.ts +0 -115
- package/src/components/chat/chat-chain.test.ts +0 -22
- package/src/components/chat/chat-chain.ts +0 -23
- package/src/components/chat/chat-page-data.ts +0 -171
- package/src/components/chat/chat-page-runtime.ts +0 -190
- package/src/components/chat/chat-stream/nextbot-parsers.ts +0 -52
- package/src/components/chat/chat-stream/nextbot-runtime-agent.ts +0 -413
- package/src/components/chat/chat-stream/stream-event-adapter.ts +0 -98
- package/src/components/chat/chat-stream/transport.ts +0 -253
- package/src/components/chat/legacy/LegacyChatPage.tsx +0 -223
- package/src/components/chat/managers/chat-input.manager.ts +0 -228
- package/src/components/chat/managers/chat-thread.manager.ts +0 -87
- package/src/components/chat/presenter/chat.presenter.ts +0 -32
- package/src/components/chat/useChatRuntimeController.ts +0 -134
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-D41ntvb7.js"></script>
|
|
10
|
+
<link rel="modulepreload" crossorigin href="/assets/vendor-D_JxmsLV.js">
|
|
11
|
+
<link rel="stylesheet" crossorigin href="/assets/index-CrilScMo.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.11.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -28,11 +28,11 @@
|
|
|
28
28
|
"tailwind-merge": "^2.5.4",
|
|
29
29
|
"zod": "^3.23.8",
|
|
30
30
|
"zustand": "^5.0.2",
|
|
31
|
-
"@nextclaw/ncp": "0.3.3",
|
|
32
31
|
"@nextclaw/agent-chat": "0.1.3",
|
|
32
|
+
"@nextclaw/ncp-http-agent-client": "0.3.4",
|
|
33
33
|
"@nextclaw/agent-chat-ui": "0.2.5",
|
|
34
|
-
"@nextclaw/ncp
|
|
35
|
-
"@nextclaw/ncp-react": "0.
|
|
34
|
+
"@nextclaw/ncp": "0.4.0",
|
|
35
|
+
"@nextclaw/ncp-react": "0.4.1"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
38
|
"@testing-library/react": "^16.3.0",
|
package/src/App.test.tsx
CHANGED
|
@@ -1,41 +1,73 @@
|
|
|
1
1
|
import { render, screen } from '@testing-library/react';
|
|
2
2
|
import userEvent from '@testing-library/user-event';
|
|
3
3
|
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
4
|
+
import { MemoryRouter } from 'react-router-dom';
|
|
5
|
+
import { I18nProvider } from '@/components/providers/I18nProvider';
|
|
6
|
+
import { ThemeProvider } from '@/components/providers/ThemeProvider';
|
|
4
7
|
import AppContent from '@/App';
|
|
5
8
|
|
|
6
9
|
const mocks = vi.hoisted(() => ({
|
|
7
10
|
refetch: vi.fn(),
|
|
8
|
-
useAuthStatus: vi.fn()
|
|
11
|
+
useAuthStatus: vi.fn(),
|
|
12
|
+
isTransientAuthStatusBootstrapError: vi.fn()
|
|
9
13
|
}));
|
|
10
14
|
|
|
11
15
|
vi.mock('@/hooks/use-auth', () => ({
|
|
12
|
-
useAuthStatus: mocks.useAuthStatus
|
|
16
|
+
useAuthStatus: mocks.useAuthStatus,
|
|
17
|
+
isTransientAuthStatusBootstrapError: mocks.isTransientAuthStatusBootstrapError
|
|
13
18
|
}));
|
|
14
19
|
|
|
15
20
|
describe('App auth bootstrap', () => {
|
|
21
|
+
function renderApp() {
|
|
22
|
+
return render(
|
|
23
|
+
<ThemeProvider>
|
|
24
|
+
<I18nProvider>
|
|
25
|
+
<MemoryRouter initialEntries={['/chat']}>
|
|
26
|
+
<AppContent />
|
|
27
|
+
</MemoryRouter>
|
|
28
|
+
</I18nProvider>
|
|
29
|
+
</ThemeProvider>
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
16
33
|
beforeEach(() => {
|
|
17
34
|
mocks.refetch.mockReset();
|
|
18
35
|
mocks.useAuthStatus.mockReset();
|
|
36
|
+
mocks.isTransientAuthStatusBootstrapError.mockReset();
|
|
37
|
+
mocks.isTransientAuthStatusBootstrapError.mockReturnValue(false);
|
|
38
|
+
vi.useRealTimers();
|
|
19
39
|
});
|
|
20
40
|
|
|
21
|
-
it('
|
|
22
|
-
|
|
41
|
+
it('does not block the app shell for transient bootstrap failures', () => {
|
|
42
|
+
mocks.isTransientAuthStatusBootstrapError.mockReturnValue(true);
|
|
23
43
|
mocks.useAuthStatus.mockReturnValue({
|
|
24
44
|
isLoading: false,
|
|
25
45
|
isError: true,
|
|
26
46
|
isRefetching: false,
|
|
27
|
-
error: new Error('
|
|
47
|
+
error: new Error('Failed to fetch'),
|
|
28
48
|
refetch: mocks.refetch,
|
|
29
49
|
data: undefined
|
|
30
50
|
});
|
|
31
51
|
|
|
32
|
-
|
|
52
|
+
renderApp();
|
|
53
|
+
|
|
54
|
+
expect(screen.queryByRole('heading', { name: /waiting for the local ui service to start/i })).toBeNull();
|
|
55
|
+
expect(screen.queryByText('Failed to fetch')).toBeNull();
|
|
56
|
+
});
|
|
33
57
|
|
|
34
|
-
|
|
35
|
-
|
|
58
|
+
it('does not block the app shell for stable auth bootstrap failures', async () => {
|
|
59
|
+
mocks.useAuthStatus.mockReturnValue({
|
|
60
|
+
isLoading: false,
|
|
61
|
+
isError: true,
|
|
62
|
+
isRefetching: false,
|
|
63
|
+
error: new Error('Authentication required.'),
|
|
64
|
+
refetch: mocks.refetch,
|
|
65
|
+
data: undefined
|
|
66
|
+
});
|
|
36
67
|
|
|
37
|
-
|
|
68
|
+
renderApp();
|
|
38
69
|
|
|
39
|
-
expect(
|
|
70
|
+
expect(screen.queryByRole('heading', { name: /load authentication status/i })).toBeNull();
|
|
71
|
+
expect(screen.queryByText('Authentication required.')).toBeNull();
|
|
40
72
|
});
|
|
41
73
|
});
|
package/src/App.tsx
CHANGED
|
@@ -3,10 +3,8 @@ import { QueryClientProvider } from '@tanstack/react-query';
|
|
|
3
3
|
import { AccountPanel } from '@/account/components/account-panel';
|
|
4
4
|
import { appQueryClient } from '@/app-query-client';
|
|
5
5
|
import { LoginPage } from '@/components/auth/login-page';
|
|
6
|
-
import { Button } from '@/components/ui/button';
|
|
7
6
|
import { AppLayout } from '@/components/layout/AppLayout';
|
|
8
|
-
import { useAuthStatus } from '@/hooks/use-auth';
|
|
9
|
-
import { t } from '@/lib/i18n';
|
|
7
|
+
import { isTransientAuthStatusBootstrapError, useAuthStatus } from '@/hooks/use-auth';
|
|
10
8
|
import { useRealtimeQueryBridge } from '@/hooks/use-realtime-query-bridge';
|
|
11
9
|
import { AppPresenterProvider } from '@/presenter/app-presenter-context';
|
|
12
10
|
import { Toaster } from 'sonner';
|
|
@@ -33,29 +31,6 @@ function LazyRoute({ children }: { children: JSX.Element }) {
|
|
|
33
31
|
return <Suspense fallback={<RouteFallback />}>{children}</Suspense>;
|
|
34
32
|
}
|
|
35
33
|
|
|
36
|
-
function AuthBootstrapErrorState(props: {
|
|
37
|
-
message: string;
|
|
38
|
-
retrying: boolean;
|
|
39
|
-
onRetry: () => void;
|
|
40
|
-
}) {
|
|
41
|
-
return (
|
|
42
|
-
<main className="flex min-h-screen items-center justify-center bg-secondary px-6 py-10">
|
|
43
|
-
<div className="w-full max-w-lg rounded-3xl border border-gray-200 bg-white p-8 shadow-card">
|
|
44
|
-
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-gray-500">{t('authBrand')}</p>
|
|
45
|
-
<h1 className="mt-3 text-2xl font-semibold text-gray-900">{t('authStatusLoadFailed')}</h1>
|
|
46
|
-
<p className="mt-3 text-sm leading-6 text-gray-600">
|
|
47
|
-
{props.message}
|
|
48
|
-
</p>
|
|
49
|
-
<div className="mt-6 flex gap-3">
|
|
50
|
-
<Button onClick={props.onRetry} disabled={props.retrying}>
|
|
51
|
-
{t('authRetryStatus')}
|
|
52
|
-
</Button>
|
|
53
|
-
</div>
|
|
54
|
-
</div>
|
|
55
|
-
</main>
|
|
56
|
-
);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
34
|
function ProtectedApp() {
|
|
60
35
|
useRealtimeQueryBridge(appQueryClient);
|
|
61
36
|
|
|
@@ -95,21 +70,11 @@ function ProtectedApp() {
|
|
|
95
70
|
|
|
96
71
|
function AuthGate() {
|
|
97
72
|
const authStatus = useAuthStatus();
|
|
73
|
+
const isTransientBootstrapFailure =
|
|
74
|
+
authStatus.isError && isTransientAuthStatusBootstrapError(authStatus.error);
|
|
98
75
|
|
|
99
|
-
if (authStatus.isLoading && !authStatus.isError) {
|
|
100
|
-
return <
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
if (authStatus.isError) {
|
|
104
|
-
return (
|
|
105
|
-
<AuthBootstrapErrorState
|
|
106
|
-
message={authStatus.error instanceof Error ? authStatus.error.message : t('authStatusLoadFailed')}
|
|
107
|
-
retrying={authStatus.isRefetching}
|
|
108
|
-
onRetry={() => {
|
|
109
|
-
void authStatus.refetch();
|
|
110
|
-
}}
|
|
111
|
-
/>
|
|
112
|
-
);
|
|
76
|
+
if ((authStatus.isLoading && !authStatus.isError) || isTransientBootstrapFailure || authStatus.isError) {
|
|
77
|
+
return <ProtectedApp />;
|
|
113
78
|
}
|
|
114
79
|
|
|
115
80
|
if (authStatus.data?.enabled && !authStatus.data.authenticated) {
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
|
|
3
|
+
describe('API_BASE', () => {
|
|
4
|
+
beforeEach(() => {
|
|
5
|
+
vi.resetModules();
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
afterEach(() => {
|
|
9
|
+
vi.unstubAllEnvs();
|
|
10
|
+
vi.unstubAllGlobals();
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it('prefers explicit VITE_API_BASE when provided', async () => {
|
|
14
|
+
vi.stubEnv('VITE_API_BASE', 'https://api.example.com/');
|
|
15
|
+
vi.stubGlobal('window', {
|
|
16
|
+
location: {
|
|
17
|
+
origin: 'https://remote.claw.cool'
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
const { API_BASE } = await import('@/api/api-base');
|
|
22
|
+
|
|
23
|
+
expect(API_BASE).toBe('https://api.example.com');
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('falls back to window origin when no explicit API base is configured', async () => {
|
|
27
|
+
vi.stubGlobal('window', {
|
|
28
|
+
location: {
|
|
29
|
+
origin: 'https://remote.claw.cool'
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
const { API_BASE } = await import('@/api/api-base');
|
|
34
|
+
|
|
35
|
+
expect(API_BASE).toBe('https://remote.claw.cool');
|
|
36
|
+
});
|
|
37
|
+
});
|
package/src/api/api-base.ts
CHANGED
package/src/api/config.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { api } from './client';
|
|
2
|
-
import { appClient } from '@/transport';
|
|
3
2
|
import type {
|
|
4
3
|
AuthEnabledUpdateRequest,
|
|
5
4
|
AuthLoginRequest,
|
|
@@ -30,22 +29,7 @@ import type {
|
|
|
30
29
|
SecretsView,
|
|
31
30
|
ConfigActionExecuteRequest,
|
|
32
31
|
ConfigActionExecuteResult,
|
|
33
|
-
SessionsListView,
|
|
34
|
-
SessionHistoryView,
|
|
35
|
-
SessionPatchUpdate,
|
|
36
|
-
ChatTurnRequest,
|
|
37
|
-
ChatTurnView,
|
|
38
|
-
ChatTurnStreamDeltaEvent,
|
|
39
|
-
ChatTurnStreamErrorEvent,
|
|
40
|
-
ChatTurnStreamReadyEvent,
|
|
41
|
-
ChatTurnStreamSessionEvent,
|
|
42
|
-
ChatCapabilitiesView,
|
|
43
32
|
ChatSessionTypesView,
|
|
44
|
-
ChatTurnStopRequest,
|
|
45
|
-
ChatTurnStopResult,
|
|
46
|
-
ChatRunListView,
|
|
47
|
-
ChatRunState,
|
|
48
|
-
ChatRunView,
|
|
49
33
|
CronListView,
|
|
50
34
|
CronEnableRequest,
|
|
51
35
|
CronRunRequest,
|
|
@@ -53,8 +37,8 @@ import type {
|
|
|
53
37
|
} from './types';
|
|
54
38
|
|
|
55
39
|
// GET /api/auth/status
|
|
56
|
-
export async function fetchAuthStatus(): Promise<AuthStatusView> {
|
|
57
|
-
const response = await api.get<AuthStatusView>('/api/auth/status', { timeoutMs: 5_000 });
|
|
40
|
+
export async function fetchAuthStatus(options: { timeoutMs?: number } = {}): Promise<AuthStatusView> {
|
|
41
|
+
const response = await api.get<AuthStatusView>('/api/auth/status', { timeoutMs: options.timeoutMs ?? 5_000 });
|
|
58
42
|
if (!response.ok) {
|
|
59
43
|
throw new Error(response.error.message);
|
|
60
44
|
}
|
|
@@ -317,216 +301,6 @@ export async function executeConfigAction(
|
|
|
317
301
|
return response.data;
|
|
318
302
|
}
|
|
319
303
|
|
|
320
|
-
|
|
321
|
-
// GET /api/sessions
|
|
322
|
-
export async function fetchSessions(params?: { q?: string; limit?: number; activeMinutes?: number }): Promise<SessionsListView> {
|
|
323
|
-
const query = new URLSearchParams();
|
|
324
|
-
if (params?.q?.trim()) {
|
|
325
|
-
query.set('q', params.q.trim());
|
|
326
|
-
}
|
|
327
|
-
if (typeof params?.limit === 'number' && Number.isFinite(params.limit)) {
|
|
328
|
-
query.set('limit', String(Math.max(0, Math.trunc(params.limit))));
|
|
329
|
-
}
|
|
330
|
-
if (typeof params?.activeMinutes === 'number' && Number.isFinite(params.activeMinutes)) {
|
|
331
|
-
query.set('activeMinutes', String(Math.max(0, Math.trunc(params.activeMinutes))));
|
|
332
|
-
}
|
|
333
|
-
const suffix = query.toString();
|
|
334
|
-
const response = await api.get<SessionsListView>(suffix ? '/api/sessions?' + suffix : '/api/sessions');
|
|
335
|
-
if (!response.ok) {
|
|
336
|
-
throw new Error(response.error.message);
|
|
337
|
-
}
|
|
338
|
-
return response.data;
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
// GET /api/sessions/:key/history
|
|
342
|
-
export async function fetchSessionHistory(key: string, limit = 200): Promise<SessionHistoryView> {
|
|
343
|
-
const response = await api.get<SessionHistoryView>(`/api/sessions/${encodeURIComponent(key)}/history?limit=${Math.max(1, Math.trunc(limit))}`);
|
|
344
|
-
if (!response.ok) {
|
|
345
|
-
throw new Error(response.error.message);
|
|
346
|
-
}
|
|
347
|
-
return response.data;
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
// PUT /api/sessions/:key
|
|
351
|
-
export async function updateSession(
|
|
352
|
-
key: string,
|
|
353
|
-
data: SessionPatchUpdate
|
|
354
|
-
): Promise<SessionHistoryView> {
|
|
355
|
-
const response = await api.put<SessionHistoryView>(`/api/sessions/${encodeURIComponent(key)}`, data);
|
|
356
|
-
if (!response.ok) {
|
|
357
|
-
throw new Error(response.error.message);
|
|
358
|
-
}
|
|
359
|
-
return response.data;
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
// DELETE /api/sessions/:key
|
|
363
|
-
export async function deleteSession(key: string): Promise<{ deleted: boolean }> {
|
|
364
|
-
const response = await api.delete<{ deleted: boolean }>(`/api/sessions/${encodeURIComponent(key)}`);
|
|
365
|
-
if (!response.ok) {
|
|
366
|
-
throw new Error(response.error.message);
|
|
367
|
-
}
|
|
368
|
-
return response.data;
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
// POST /api/chat/turn
|
|
372
|
-
export async function sendChatTurn(data: ChatTurnRequest): Promise<ChatTurnView> {
|
|
373
|
-
const response = await api.post<ChatTurnView>('/api/chat/turn', data);
|
|
374
|
-
if (!response.ok) {
|
|
375
|
-
throw new Error(response.error.message);
|
|
376
|
-
}
|
|
377
|
-
return response.data;
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
async function readSseStream(params: {
|
|
381
|
-
path: string;
|
|
382
|
-
method: 'GET' | 'POST';
|
|
383
|
-
body?: unknown;
|
|
384
|
-
signal?: AbortSignal;
|
|
385
|
-
onReady: (event: ChatTurnStreamReadyEvent) => void;
|
|
386
|
-
onDelta: (event: ChatTurnStreamDeltaEvent) => void;
|
|
387
|
-
onSessionEvent: (event: ChatTurnStreamSessionEvent) => void;
|
|
388
|
-
}): Promise<{ sessionKey: string; reply: string }> {
|
|
389
|
-
let finalResult: { sessionKey: string; reply: string } | null = null;
|
|
390
|
-
let readySessionKey = '';
|
|
391
|
-
|
|
392
|
-
const session = appClient.openStream<ChatTurnView>({
|
|
393
|
-
method: params.method,
|
|
394
|
-
path: params.path,
|
|
395
|
-
...(params.body !== undefined ? { body: params.body } : {}),
|
|
396
|
-
signal: params.signal,
|
|
397
|
-
onEvent: (event) => {
|
|
398
|
-
if (event.name === 'ready') {
|
|
399
|
-
const ready = (event.payload ?? {}) as ChatTurnStreamReadyEvent;
|
|
400
|
-
if (typeof ready.sessionKey === 'string' && ready.sessionKey.trim()) {
|
|
401
|
-
readySessionKey = ready.sessionKey;
|
|
402
|
-
}
|
|
403
|
-
params.onReady(ready);
|
|
404
|
-
return;
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
if (event.name === 'delta') {
|
|
408
|
-
params.onDelta((event.payload ?? { delta: '' }) as ChatTurnStreamDeltaEvent);
|
|
409
|
-
return;
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
if (event.name === 'session_event') {
|
|
413
|
-
params.onSessionEvent({ data: event.payload as ChatTurnStreamSessionEvent['data'] });
|
|
414
|
-
return;
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
if (event.name === 'final') {
|
|
418
|
-
const result = event.payload as ChatTurnView;
|
|
419
|
-
finalResult = {
|
|
420
|
-
sessionKey: typeof result?.sessionKey === 'string' && result.sessionKey.trim()
|
|
421
|
-
? result.sessionKey
|
|
422
|
-
: readySessionKey,
|
|
423
|
-
reply: typeof result?.reply === 'string' ? result.reply : ''
|
|
424
|
-
};
|
|
425
|
-
return;
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
if (event.name === 'error') {
|
|
429
|
-
const errorPayload = (event.payload ?? {}) as ChatTurnStreamErrorEvent;
|
|
430
|
-
throw new Error((errorPayload.message ?? '').trim() || 'chat stream failed');
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
});
|
|
434
|
-
|
|
435
|
-
const result = await session.finished;
|
|
436
|
-
if (finalResult) {
|
|
437
|
-
return finalResult;
|
|
438
|
-
}
|
|
439
|
-
if (readySessionKey) {
|
|
440
|
-
return {
|
|
441
|
-
sessionKey: readySessionKey,
|
|
442
|
-
reply: typeof result?.reply === 'string' ? result.reply : ''
|
|
443
|
-
};
|
|
444
|
-
}
|
|
445
|
-
if (typeof result?.sessionKey === 'string' && result.sessionKey.trim()) {
|
|
446
|
-
return {
|
|
447
|
-
sessionKey: result.sessionKey,
|
|
448
|
-
reply: typeof result?.reply === 'string' ? result.reply : ''
|
|
449
|
-
};
|
|
450
|
-
}
|
|
451
|
-
throw new Error('chat stream ended without final event');
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
// POST /api/chat/turn/stream
|
|
455
|
-
export async function sendChatTurnStream(
|
|
456
|
-
data: ChatTurnRequest,
|
|
457
|
-
params: {
|
|
458
|
-
signal?: AbortSignal;
|
|
459
|
-
onReady: (event: ChatTurnStreamReadyEvent) => void;
|
|
460
|
-
onDelta: (event: ChatTurnStreamDeltaEvent) => void;
|
|
461
|
-
onSessionEvent: (event: ChatTurnStreamSessionEvent) => void;
|
|
462
|
-
}
|
|
463
|
-
): Promise<{ sessionKey: string; reply: string }> {
|
|
464
|
-
return readSseStream({
|
|
465
|
-
path: '/api/chat/turn/stream',
|
|
466
|
-
method: 'POST',
|
|
467
|
-
body: data,
|
|
468
|
-
signal: params.signal,
|
|
469
|
-
onReady: params.onReady,
|
|
470
|
-
onDelta: params.onDelta,
|
|
471
|
-
onSessionEvent: params.onSessionEvent
|
|
472
|
-
});
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
// GET /api/chat/runs/:runId/stream
|
|
476
|
-
export async function streamChatRun(
|
|
477
|
-
data: {
|
|
478
|
-
runId: string;
|
|
479
|
-
fromEventIndex?: number;
|
|
480
|
-
},
|
|
481
|
-
params: {
|
|
482
|
-
signal?: AbortSignal;
|
|
483
|
-
onReady: (event: ChatTurnStreamReadyEvent) => void;
|
|
484
|
-
onDelta: (event: ChatTurnStreamDeltaEvent) => void;
|
|
485
|
-
onSessionEvent: (event: ChatTurnStreamSessionEvent) => void;
|
|
486
|
-
}
|
|
487
|
-
): Promise<{ sessionKey: string; reply: string }> {
|
|
488
|
-
const query = new URLSearchParams();
|
|
489
|
-
if (typeof data.fromEventIndex === 'number' && Number.isFinite(data.fromEventIndex)) {
|
|
490
|
-
query.set('fromEventIndex', String(Math.max(0, Math.trunc(data.fromEventIndex))));
|
|
491
|
-
}
|
|
492
|
-
const suffix = query.toString();
|
|
493
|
-
const path = `/api/chat/runs/${encodeURIComponent(data.runId)}/stream${suffix ? `?${suffix}` : ''}`;
|
|
494
|
-
return readSseStream({
|
|
495
|
-
path,
|
|
496
|
-
method: 'GET',
|
|
497
|
-
signal: params.signal,
|
|
498
|
-
onReady: params.onReady,
|
|
499
|
-
onDelta: params.onDelta,
|
|
500
|
-
onSessionEvent: params.onSessionEvent
|
|
501
|
-
});
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
// GET /api/chat/capabilities
|
|
505
|
-
export async function fetchChatCapabilities(params?: { sessionKey?: string; agentId?: string }): Promise<ChatCapabilitiesView> {
|
|
506
|
-
const query = new URLSearchParams();
|
|
507
|
-
if (params?.sessionKey?.trim()) {
|
|
508
|
-
query.set('sessionKey', params.sessionKey.trim());
|
|
509
|
-
}
|
|
510
|
-
if (params?.agentId?.trim()) {
|
|
511
|
-
query.set('agentId', params.agentId.trim());
|
|
512
|
-
}
|
|
513
|
-
const suffix = query.toString();
|
|
514
|
-
const response = await api.get<ChatCapabilitiesView>(suffix ? `/api/chat/capabilities?${suffix}` : '/api/chat/capabilities');
|
|
515
|
-
if (!response.ok) {
|
|
516
|
-
throw new Error(response.error.message);
|
|
517
|
-
}
|
|
518
|
-
return response.data;
|
|
519
|
-
}
|
|
520
|
-
|
|
521
|
-
// GET /api/chat/session-types
|
|
522
|
-
export async function fetchChatSessionTypes(): Promise<ChatSessionTypesView> {
|
|
523
|
-
const response = await api.get<ChatSessionTypesView>('/api/chat/session-types');
|
|
524
|
-
if (!response.ok) {
|
|
525
|
-
throw new Error(response.error.message);
|
|
526
|
-
}
|
|
527
|
-
return response.data;
|
|
528
|
-
}
|
|
529
|
-
|
|
530
304
|
// GET /api/ncp/session-types
|
|
531
305
|
export async function fetchNcpChatSessionTypes(): Promise<ChatSessionTypesView> {
|
|
532
306
|
const response = await api.get<ChatSessionTypesView>('/api/ncp/session-types');
|
|
@@ -536,48 +310,6 @@ export async function fetchNcpChatSessionTypes(): Promise<ChatSessionTypesView>
|
|
|
536
310
|
return response.data;
|
|
537
311
|
}
|
|
538
312
|
|
|
539
|
-
// POST /api/chat/turn/stop
|
|
540
|
-
export async function stopChatTurn(data: ChatTurnStopRequest): Promise<ChatTurnStopResult> {
|
|
541
|
-
const response = await api.post<ChatTurnStopResult>('/api/chat/turn/stop', data);
|
|
542
|
-
if (!response.ok) {
|
|
543
|
-
throw new Error(response.error.message);
|
|
544
|
-
}
|
|
545
|
-
return response.data;
|
|
546
|
-
}
|
|
547
|
-
|
|
548
|
-
// GET /api/chat/runs
|
|
549
|
-
export async function fetchChatRuns(params?: {
|
|
550
|
-
sessionKey?: string;
|
|
551
|
-
states?: ChatRunState[];
|
|
552
|
-
limit?: number;
|
|
553
|
-
}): Promise<ChatRunListView> {
|
|
554
|
-
const query = new URLSearchParams();
|
|
555
|
-
if (params?.sessionKey?.trim()) {
|
|
556
|
-
query.set('sessionKey', params.sessionKey.trim());
|
|
557
|
-
}
|
|
558
|
-
if (Array.isArray(params?.states) && params.states.length > 0) {
|
|
559
|
-
query.set('states', params.states.join(','));
|
|
560
|
-
}
|
|
561
|
-
if (typeof params?.limit === 'number' && Number.isFinite(params.limit)) {
|
|
562
|
-
query.set('limit', String(Math.max(0, Math.trunc(params.limit))));
|
|
563
|
-
}
|
|
564
|
-
const suffix = query.toString();
|
|
565
|
-
const response = await api.get<ChatRunListView>(suffix ? `/api/chat/runs?${suffix}` : '/api/chat/runs');
|
|
566
|
-
if (!response.ok) {
|
|
567
|
-
throw new Error(response.error.message);
|
|
568
|
-
}
|
|
569
|
-
return response.data;
|
|
570
|
-
}
|
|
571
|
-
|
|
572
|
-
// GET /api/chat/runs/:runId
|
|
573
|
-
export async function fetchChatRun(runId: string): Promise<ChatRunView> {
|
|
574
|
-
const response = await api.get<ChatRunView>(`/api/chat/runs/${encodeURIComponent(runId)}`);
|
|
575
|
-
if (!response.ok) {
|
|
576
|
-
throw new Error(response.error.message);
|
|
577
|
-
}
|
|
578
|
-
return response.data;
|
|
579
|
-
}
|
|
580
|
-
|
|
581
313
|
// GET /api/cron
|
|
582
314
|
export async function fetchCronJobs(params?: { all?: boolean }): Promise<CronListView> {
|
|
583
315
|
const query = new URLSearchParams();
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { NcpDraftAttachment } from "@nextclaw/ncp-react";
|
|
2
2
|
import { API_BASE } from "./api-base";
|
|
3
|
-
import type { ApiResponse,
|
|
3
|
+
import type { ApiResponse, NcpAssetPutView } from "./types";
|
|
4
4
|
|
|
5
5
|
function readErrorMessage(payload: unknown, fallback: string): string {
|
|
6
6
|
if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
|
|
@@ -14,28 +14,28 @@ function readErrorMessage(payload: unknown, fallback: string): string {
|
|
|
14
14
|
return typeof message === "string" && message.trim().length > 0 ? message : fallback;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
export async function
|
|
17
|
+
export async function uploadNcpAssets(files: File[]): Promise<NcpDraftAttachment[]> {
|
|
18
18
|
const formData = new FormData();
|
|
19
19
|
for (const file of files) {
|
|
20
20
|
formData.append("files", file);
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
const response = await fetch(`${API_BASE}/api/ncp/
|
|
23
|
+
const response = await fetch(`${API_BASE}/api/ncp/assets`, {
|
|
24
24
|
method: "POST",
|
|
25
25
|
body: formData,
|
|
26
26
|
credentials: "include",
|
|
27
27
|
});
|
|
28
|
-
const payload = (await response.json()) as ApiResponse<
|
|
28
|
+
const payload = (await response.json()) as ApiResponse<NcpAssetPutView>;
|
|
29
29
|
if (!response.ok || !payload.ok) {
|
|
30
|
-
throw new Error(readErrorMessage(payload, "Failed to
|
|
30
|
+
throw new Error(readErrorMessage(payload, "Failed to put assets."));
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
return payload.data.
|
|
34
|
-
id:
|
|
35
|
-
name:
|
|
36
|
-
mimeType:
|
|
37
|
-
sizeBytes:
|
|
38
|
-
|
|
39
|
-
url:
|
|
33
|
+
return payload.data.assets.map((asset) => ({
|
|
34
|
+
id: asset.id,
|
|
35
|
+
name: asset.name,
|
|
36
|
+
mimeType: asset.mimeType,
|
|
37
|
+
sizeBytes: asset.sizeBytes,
|
|
38
|
+
assetUri: asset.assetUri,
|
|
39
|
+
url: asset.url,
|
|
40
40
|
}));
|
|
41
41
|
}
|