@nextclaw/ui 0.11.0 → 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 +8 -2
- package/dist/assets/{ChannelsList-BqsOYnXz.js → ChannelsList-CVPqrxns.js} +4 -4
- package/dist/assets/ChatPage-BO1VUrAY.js +37 -0
- package/dist/assets/{DocBrowser-BmL0QXBZ.js → DocBrowser-FBwg8iji.js} +1 -1
- package/dist/assets/{LogoBadge-C1HiPZPf.js → LogoBadge-BCmJfRT8.js} +1 -1
- package/dist/assets/MarketplacePage-DWxXUOCx.js +49 -0
- package/dist/assets/{McpMarketplacePage-CLHFnNBd.js → McpMarketplacePage-Bth9X_hu.js} +2 -2
- package/dist/assets/{ModelConfig-LQSR58tc.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-Chzo_JGs.js → SearchConfig-KZUAqYJN.js} +1 -1
- package/dist/assets/{SecretsConfig-CEIbjZYA.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-j6A_-1b6.js → index-D41ntvb7.js} +6 -6
- package/dist/assets/{label-GACO2RzW.js → label-7JEFhkur.js} +1 -1
- package/dist/assets/ncp-session-adapter-BOqhkrc-.js +1 -0
- package/dist/assets/{page-layout-DjXaK3A3.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-IWEBezqb.js → status-dot-BilwNdTT.js} +1 -1
- package/dist/assets/{switch-DCHAJSrA.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/types.ts +0 -117
- 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/chat-attachment-upload-limit.test.ts +41 -0
- 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/ncp/NcpChatPage.tsx +3 -3
- 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-CJBYKR-Y.js +0 -38
- package/dist/assets/MarketplacePage-BIRP0NRS.js +0 -49
- package/dist/assets/ProvidersList-CwI-mxah.js +0 -1
- package/dist/assets/RemoteAccessPage-Cw5BqZb6.js +0 -1
- package/dist/assets/RuntimeConfig-DbowSRAb.js +0 -1
- package/dist/assets/SessionsConfig-BR8GfGWL.js +0 -2
- package/dist/assets/chat-message-CPG7zxRR.js +0 -3
- package/dist/assets/index-kaPUhd-8.css +0 -1
- package/dist/assets/popover-DTaFiTmU.js +0 -1
- package/dist/assets/security-config-Dk-yoKvK.js +0 -1
- package/dist/assets/skeleton-Dm2xOBSA.js +0 -1
- package/dist/assets/tabs-custom-DKSbDSB9.js +0 -1
- package/dist/assets/useConfirmDialog-ByJ8A8n7.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
|
@@ -1,16 +1,6 @@
|
|
|
1
|
-
import { useLocation } from 'react-router-dom';
|
|
2
|
-
import { resolveChatChain } from '@/components/chat/chat-chain';
|
|
3
1
|
import type { ChatPageProps } from '@/components/chat/chat-page-shell';
|
|
4
|
-
import { LegacyChatPage } from '@/components/chat/legacy/LegacyChatPage';
|
|
5
2
|
import { NcpChatPage } from '@/components/chat/ncp/NcpChatPage';
|
|
6
3
|
|
|
7
4
|
export function ChatPage({ view }: ChatPageProps) {
|
|
8
|
-
|
|
9
|
-
const chatChain = resolveChatChain(location.search);
|
|
10
|
-
|
|
11
|
-
if (chatChain === 'ncp') {
|
|
12
|
-
return <NcpChatPage view={view} />;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
return <LegacyChatPage view={view} />;
|
|
5
|
+
return <NcpChatPage view={view} />;
|
|
16
6
|
}
|
|
@@ -11,7 +11,6 @@ const mocks = vi.hoisted(() => ({
|
|
|
11
11
|
setQuery: vi.fn(),
|
|
12
12
|
selectSession: vi.fn(),
|
|
13
13
|
docOpen: vi.fn(),
|
|
14
|
-
updateSession: vi.fn(),
|
|
15
14
|
updateNcpSession: vi.fn()
|
|
16
15
|
}));
|
|
17
16
|
|
|
@@ -33,15 +32,9 @@ vi.mock('@/components/doc-browser', () => ({
|
|
|
33
32
|
|
|
34
33
|
vi.mock('@/components/chat/chat-session-label.service', () => ({
|
|
35
34
|
useChatSessionLabelService: () => async (params: {
|
|
36
|
-
chatChain: 'legacy' | 'ncp';
|
|
37
35
|
sessionKey: string;
|
|
38
36
|
label: string | null;
|
|
39
|
-
}) => {
|
|
40
|
-
if (params.chatChain === 'ncp') {
|
|
41
|
-
return mocks.updateNcpSession(params.sessionKey, { label: params.label });
|
|
42
|
-
}
|
|
43
|
-
return mocks.updateSession(params.sessionKey, { label: params.label });
|
|
44
|
-
}
|
|
37
|
+
}) => mocks.updateNcpSession(params.sessionKey, { label: params.label })
|
|
45
38
|
}));
|
|
46
39
|
|
|
47
40
|
vi.mock('@/components/common/BrandHeader', () => ({
|
|
@@ -77,9 +70,7 @@ describe('ChatSidebar', () => {
|
|
|
77
70
|
mocks.setQuery.mockReset();
|
|
78
71
|
mocks.selectSession.mockReset();
|
|
79
72
|
mocks.docOpen.mockReset();
|
|
80
|
-
mocks.updateSession.mockReset();
|
|
81
73
|
mocks.updateNcpSession.mockReset();
|
|
82
|
-
mocks.updateSession.mockResolvedValue({});
|
|
83
74
|
mocks.updateNcpSession.mockResolvedValue({});
|
|
84
75
|
|
|
85
76
|
useChatInputStore.setState({
|
|
@@ -285,48 +276,9 @@ describe('ChatSidebar', () => {
|
|
|
285
276
|
label: 'Renamed Label'
|
|
286
277
|
});
|
|
287
278
|
});
|
|
288
|
-
expect(mocks.updateSession).not.toHaveBeenCalled();
|
|
289
279
|
expect(screen.getByText('Renamed Label')).not.toBeNull();
|
|
290
280
|
});
|
|
291
281
|
|
|
292
|
-
it('routes inline session label edits to the legacy session api when chatChain=legacy', async () => {
|
|
293
|
-
useChatSessionListStore.setState({
|
|
294
|
-
snapshot: {
|
|
295
|
-
...useChatSessionListStore.getState().snapshot,
|
|
296
|
-
sessions: [
|
|
297
|
-
{
|
|
298
|
-
key: 'session:legacy-1',
|
|
299
|
-
createdAt: '2026-03-19T09:00:00.000Z',
|
|
300
|
-
updatedAt: '2026-03-19T09:05:00.000Z',
|
|
301
|
-
label: 'Legacy Label',
|
|
302
|
-
sessionType: 'native',
|
|
303
|
-
sessionTypeMutable: false,
|
|
304
|
-
messageCount: 1
|
|
305
|
-
}
|
|
306
|
-
]
|
|
307
|
-
}
|
|
308
|
-
});
|
|
309
|
-
|
|
310
|
-
render(
|
|
311
|
-
<MemoryRouter initialEntries={['/chat?chatChain=legacy']}>
|
|
312
|
-
<ChatSidebar />
|
|
313
|
-
</MemoryRouter>
|
|
314
|
-
);
|
|
315
|
-
|
|
316
|
-
fireEvent.click(screen.getByLabelText('Edit'));
|
|
317
|
-
fireEvent.change(screen.getByPlaceholderText('Session label (optional)'), {
|
|
318
|
-
target: { value: 'Legacy Renamed' }
|
|
319
|
-
});
|
|
320
|
-
fireEvent.click(screen.getByLabelText('Save'));
|
|
321
|
-
|
|
322
|
-
await waitFor(() => {
|
|
323
|
-
expect(mocks.updateSession).toHaveBeenCalledWith('session:legacy-1', {
|
|
324
|
-
label: 'Legacy Renamed'
|
|
325
|
-
});
|
|
326
|
-
});
|
|
327
|
-
expect(mocks.updateNcpSession).not.toHaveBeenCalled();
|
|
328
|
-
});
|
|
329
|
-
|
|
330
282
|
it('cancels inline session label editing without saving', () => {
|
|
331
283
|
useChatSessionListStore.setState({
|
|
332
284
|
snapshot: {
|
|
@@ -357,7 +309,6 @@ describe('ChatSidebar', () => {
|
|
|
357
309
|
});
|
|
358
310
|
fireEvent.click(screen.getByLabelText('Cancel'));
|
|
359
311
|
|
|
360
|
-
expect(mocks.updateSession).not.toHaveBeenCalled();
|
|
361
312
|
expect(mocks.updateNcpSession).not.toHaveBeenCalled();
|
|
362
313
|
expect(screen.queryByDisplayValue('Should Not Persist')).toBeNull();
|
|
363
314
|
expect(screen.getByText('Cancelable Label')).not.toBeNull();
|
|
@@ -12,7 +12,6 @@ import { usePresenter } from '@/components/chat/presenter/chat-presenter-context
|
|
|
12
12
|
import { useChatInputStore } from '@/components/chat/stores/chat-input.store';
|
|
13
13
|
import { useChatRunStatusStore } from '@/components/chat/stores/chat-run-status.store';
|
|
14
14
|
import { useChatSessionListStore } from '@/components/chat/stores/chat-session-list.store';
|
|
15
|
-
import { resolveChatChain } from '@/components/chat/chat-chain';
|
|
16
15
|
import { cn } from '@/lib/utils';
|
|
17
16
|
import { LANGUAGE_OPTIONS, t, type I18nLanguage } from '@/lib/i18n';
|
|
18
17
|
import { THEME_OPTIONS, type UiTheme } from '@/lib/theme';
|
|
@@ -21,7 +20,6 @@ import { useTheme } from '@/components/providers/ThemeProvider';
|
|
|
21
20
|
import { useDocBrowser } from '@/components/doc-browser';
|
|
22
21
|
import { SidebarActionItem, SidebarNavLinkItem, SidebarSelectItem } from '@/components/layout/sidebar-items';
|
|
23
22
|
import { useUiStore } from '@/stores/ui.store';
|
|
24
|
-
import { useLocation } from 'react-router-dom';
|
|
25
23
|
import {
|
|
26
24
|
AlarmClock,
|
|
27
25
|
BookOpen,
|
|
@@ -117,7 +115,6 @@ const navItems = [
|
|
|
117
115
|
export function ChatSidebar() {
|
|
118
116
|
const presenter = usePresenter();
|
|
119
117
|
const docBrowser = useDocBrowser();
|
|
120
|
-
const location = useLocation();
|
|
121
118
|
const [isCreateMenuOpen, setIsCreateMenuOpen] = useState(false);
|
|
122
119
|
const [editingSessionKey, setEditingSessionKey] = useState<string | null>(null);
|
|
123
120
|
const [draftLabel, setDraftLabel] = useState('');
|
|
@@ -129,7 +126,6 @@ export function ChatSidebar() {
|
|
|
129
126
|
const { language, setLanguage } = useI18n();
|
|
130
127
|
const { theme, setTheme } = useTheme();
|
|
131
128
|
const updateSessionLabel = useChatSessionLabelService();
|
|
132
|
-
const chatChain = resolveChatChain(location.search);
|
|
133
129
|
const currentThemeLabel = t(THEME_OPTIONS.find((o) => o.value === theme)?.labelKey ?? 'themeWarm');
|
|
134
130
|
const currentLanguageLabel = LANGUAGE_OPTIONS.find((o) => o.value === language)?.label ?? language;
|
|
135
131
|
|
|
@@ -182,7 +178,6 @@ export function ChatSidebar() {
|
|
|
182
178
|
setSavingSessionKey(session.key);
|
|
183
179
|
try {
|
|
184
180
|
await updateSessionLabel({
|
|
185
|
-
chatChain,
|
|
186
181
|
sessionKey: session.key,
|
|
187
182
|
label: normalizedLabel || null
|
|
188
183
|
});
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
DEFAULT_NCP_ATTACHMENT_MAX_BYTES,
|
|
4
|
+
uploadFilesAsNcpDraftAttachments
|
|
5
|
+
} from '../../../../ncp-packages/nextclaw-ncp-react/src/attachments/ncp-attachments.ts';
|
|
6
|
+
|
|
7
|
+
describe('ncp attachment upload limit', () => {
|
|
8
|
+
it('accepts files larger than the previous 10MB cap', async () => {
|
|
9
|
+
expect(DEFAULT_NCP_ATTACHMENT_MAX_BYTES).toBe(200 * 1024 * 1024);
|
|
10
|
+
|
|
11
|
+
const file = new File([new Uint8Array(12 * 1024 * 1024)], 'large-image.png', {
|
|
12
|
+
type: 'image/png'
|
|
13
|
+
});
|
|
14
|
+
const uploadBatch = vi.fn(async (files: File[]) =>
|
|
15
|
+
files.map((entry) => ({
|
|
16
|
+
id: entry.name,
|
|
17
|
+
name: entry.name,
|
|
18
|
+
mimeType: entry.type,
|
|
19
|
+
sizeBytes: entry.size,
|
|
20
|
+
assetUri: `asset://store/${entry.name}`,
|
|
21
|
+
}))
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
const result = await uploadFilesAsNcpDraftAttachments([file], {
|
|
25
|
+
uploadBatch
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
expect(result.rejected).toEqual([]);
|
|
29
|
+
expect(uploadBatch).toHaveBeenCalledOnce();
|
|
30
|
+
expect(uploadBatch).toHaveBeenCalledWith([file]);
|
|
31
|
+
expect(result.attachments).toEqual([
|
|
32
|
+
{
|
|
33
|
+
id: 'large-image.png',
|
|
34
|
+
name: 'large-image.png',
|
|
35
|
+
mimeType: 'image/png',
|
|
36
|
+
sizeBytes: 12 * 1024 * 1024,
|
|
37
|
+
assetUri: 'asset://store/large-image.png',
|
|
38
|
+
}
|
|
39
|
+
]);
|
|
40
|
+
});
|
|
41
|
+
});
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { SessionEntryView } from '@/api/types';
|
|
2
|
+
|
|
3
|
+
export function sessionDisplayName(session: SessionEntryView): string {
|
|
4
|
+
if (session.label && session.label.trim()) {
|
|
5
|
+
return session.label.trim();
|
|
6
|
+
}
|
|
7
|
+
const chunks = session.key.split(':');
|
|
8
|
+
return chunks[chunks.length - 1] || session.key;
|
|
9
|
+
}
|
|
@@ -1,12 +1,9 @@
|
|
|
1
1
|
import { useQueryClient } from '@tanstack/react-query';
|
|
2
2
|
import { toast } from 'sonner';
|
|
3
|
-
import { updateSession } from '@/api/config';
|
|
4
3
|
import { updateNcpSession } from '@/api/ncp-session';
|
|
5
|
-
import type { ChatChain } from '@/components/chat/chat-chain';
|
|
6
4
|
import { t } from '@/lib/i18n';
|
|
7
5
|
|
|
8
6
|
type UpdateChatSessionLabelParams = {
|
|
9
|
-
chatChain: ChatChain;
|
|
10
7
|
sessionKey: string;
|
|
11
8
|
label: string | null;
|
|
12
9
|
};
|
|
@@ -16,15 +13,9 @@ export function useChatSessionLabelService() {
|
|
|
16
13
|
|
|
17
14
|
return async (params: UpdateChatSessionLabelParams): Promise<void> => {
|
|
18
15
|
try {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
queryClient.invalidateQueries({ queryKey: ['ncp-session-messages', params.sessionKey] });
|
|
23
|
-
} else {
|
|
24
|
-
await updateSession(params.sessionKey, { label: params.label });
|
|
25
|
-
queryClient.invalidateQueries({ queryKey: ['sessions'] });
|
|
26
|
-
queryClient.invalidateQueries({ queryKey: ['session-history', params.sessionKey] });
|
|
27
|
-
}
|
|
16
|
+
await updateNcpSession(params.sessionKey, { label: params.label });
|
|
17
|
+
queryClient.invalidateQueries({ queryKey: ['ncp-sessions'] });
|
|
18
|
+
queryClient.invalidateQueries({ queryKey: ['ncp-session-messages', params.sessionKey] });
|
|
28
19
|
toast.success(t('configSavedApplied'));
|
|
29
20
|
} catch (error) {
|
|
30
21
|
toast.error(t('configSaveFailed') + ': ' + (error instanceof Error ? error.message : String(error)));
|
|
@@ -1,19 +1,16 @@
|
|
|
1
1
|
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
-
import {
|
|
2
|
+
import { updateNcpSession } from '@/api/ncp-session';
|
|
3
3
|
import { ChatSessionPreferenceSync } from '@/components/chat/chat-session-preference-sync';
|
|
4
4
|
import { useChatInputStore } from '@/components/chat/stores/chat-input.store';
|
|
5
5
|
import { useChatSessionListStore } from '@/components/chat/stores/chat-session-list.store';
|
|
6
6
|
|
|
7
|
-
vi.mock('@/api/
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
metadata: {},
|
|
15
|
-
messages: [],
|
|
16
|
-
events: []
|
|
7
|
+
vi.mock('@/api/ncp-session', () => ({
|
|
8
|
+
updateNcpSession: vi.fn(async () => ({
|
|
9
|
+
sessionId: 'session-1',
|
|
10
|
+
messageCount: 0,
|
|
11
|
+
updatedAt: new Date().toISOString(),
|
|
12
|
+
status: 'idle',
|
|
13
|
+
metadata: {}
|
|
17
14
|
}))
|
|
18
15
|
}));
|
|
19
16
|
|
|
@@ -50,10 +47,10 @@ describe('ChatSessionPreferenceSync', () => {
|
|
|
50
47
|
}
|
|
51
48
|
}));
|
|
52
49
|
|
|
53
|
-
const sync = new ChatSessionPreferenceSync(
|
|
50
|
+
const sync = new ChatSessionPreferenceSync(updateNcpSession);
|
|
54
51
|
sync.syncSelectedSessionPreferences();
|
|
55
52
|
await vi.waitFor(() => {
|
|
56
|
-
expect(
|
|
53
|
+
expect(updateNcpSession).toHaveBeenCalledWith('session-1', {
|
|
57
54
|
preferredModel: 'openai/gpt-5',
|
|
58
55
|
preferredThinking: 'high'
|
|
59
56
|
});
|
|
@@ -1,18 +1,9 @@
|
|
|
1
1
|
import type { ChatComposerNode } from '@nextclaw/agent-chat-ui';
|
|
2
2
|
import type { NcpMessagePart } from '@nextclaw/ncp';
|
|
3
3
|
import type { NcpDraftAttachment } from '@nextclaw/ncp-react';
|
|
4
|
-
import type {
|
|
5
|
-
import type {
|
|
6
|
-
ChatRunView,
|
|
7
|
-
ChatTurnStreamDeltaEvent,
|
|
8
|
-
ChatTurnStreamReadyEvent,
|
|
9
|
-
ChatTurnStreamSessionEvent,
|
|
10
|
-
SessionMessageView,
|
|
11
|
-
ThinkingLevel
|
|
12
|
-
} from '@/api/types';
|
|
4
|
+
import type { ThinkingLevel } from '@/api/types';
|
|
13
5
|
|
|
14
6
|
export type SendMessageParams = {
|
|
15
|
-
runId?: string;
|
|
16
7
|
message: string;
|
|
17
8
|
sessionKey: string;
|
|
18
9
|
agentId: string;
|
|
@@ -28,58 +19,14 @@ export type SendMessageParams = {
|
|
|
28
19
|
composerNodes?: ChatComposerNode[];
|
|
29
20
|
};
|
|
30
21
|
|
|
31
|
-
export type
|
|
32
|
-
localRunId: number;
|
|
22
|
+
export type ResumeRunParams = {
|
|
33
23
|
sessionKey: string;
|
|
34
|
-
agentId?: string;
|
|
35
|
-
backendRunId?: string;
|
|
36
|
-
backendStopSupported: boolean;
|
|
37
|
-
backendStopReason?: string;
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
export type StreamReadyPayload = {
|
|
41
|
-
sessionKey: string;
|
|
42
|
-
runId?: string;
|
|
43
|
-
stopSupported?: boolean;
|
|
44
|
-
stopReason?: string;
|
|
45
|
-
requestedAt?: string;
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
export type StreamReadyEvent = ChatTurnStreamReadyEvent;
|
|
49
|
-
export type StreamDeltaEvent = ChatTurnStreamDeltaEvent;
|
|
50
|
-
export type StreamSessionEvent = ChatTurnStreamSessionEvent;
|
|
51
|
-
|
|
52
|
-
export type NextbotAgentRunMetadata =
|
|
53
|
-
| {
|
|
54
|
-
driver: 'nextbot-stream';
|
|
55
|
-
mode: 'send';
|
|
56
|
-
payload: SendMessageParams;
|
|
57
|
-
requestedSkills: string[];
|
|
58
|
-
}
|
|
59
|
-
| {
|
|
60
|
-
driver: 'nextbot-stream';
|
|
61
|
-
mode: 'resume';
|
|
62
|
-
runId: string;
|
|
63
|
-
fromEventIndex?: number;
|
|
64
|
-
sessionKey?: string;
|
|
65
|
-
agentId?: string;
|
|
66
|
-
stopSupported?: boolean;
|
|
67
|
-
stopReason?: string;
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
export type UseChatStreamControllerParams = {
|
|
71
|
-
selectedSessionKeyRef: MutableRefObject<string | null>;
|
|
72
|
-
setSelectedSessionKey: Dispatch<SetStateAction<string | null>>;
|
|
73
|
-
setDraft: Dispatch<SetStateAction<string>>;
|
|
74
|
-
setComposerNodes: Dispatch<SetStateAction<ChatComposerNode[]>>;
|
|
75
|
-
refetchSessions: () => Promise<unknown>;
|
|
76
|
-
refetchHistory: () => Promise<unknown>;
|
|
77
24
|
};
|
|
78
25
|
|
|
79
26
|
export type ChatStreamActions = {
|
|
80
27
|
sendMessage: (payload: SendMessageParams) => Promise<void>;
|
|
81
28
|
stopCurrentRun: () => Promise<void>;
|
|
82
|
-
resumeRun: (run:
|
|
29
|
+
resumeRun: (run: ResumeRunParams) => Promise<void>;
|
|
83
30
|
resetStreamState: () => void;
|
|
84
|
-
applyHistoryMessages: (messages:
|
|
31
|
+
applyHistoryMessages: (messages: unknown[], options?: { isLoading?: boolean }) => void;
|
|
85
32
|
};
|
|
@@ -8,15 +8,15 @@ import {
|
|
|
8
8
|
import { useLocation, useNavigate, useParams } from 'react-router-dom';
|
|
9
9
|
import { API_BASE } from '@/api/api-base';
|
|
10
10
|
import { fetchNcpSessionMessages } from '@/api/ncp-session';
|
|
11
|
-
import type { ChatRunView } from '@/api/types';
|
|
12
|
-
import { sessionDisplayName } from '@/components/chat/chat-page-data';
|
|
13
11
|
import { ChatPageLayout, type ChatPageProps, useChatSessionSync } from '@/components/chat/chat-page-shell';
|
|
12
|
+
import { sessionDisplayName } from '@/components/chat/chat-session-display';
|
|
14
13
|
import { createNcpAppClientFetch } from '@/components/chat/ncp/ncp-app-client-fetch';
|
|
15
14
|
import { parseSessionKeyFromRoute, resolveAgentIdFromSessionKey } from '@/components/chat/chat-session-route';
|
|
16
15
|
import { useNcpChatPageData } from '@/components/chat/ncp/ncp-chat-page-data';
|
|
17
16
|
import { NcpChatPresenter } from '@/components/chat/ncp/ncp-chat.presenter';
|
|
18
17
|
import { adaptNcpMessagesToUiMessages, buildNcpSessionRunStatusByKey, createNcpSessionId } from '@/components/chat/ncp/ncp-session-adapter';
|
|
19
18
|
import { ChatPresenterProvider } from '@/components/chat/presenter/chat-presenter-context';
|
|
19
|
+
import type { ResumeRunParams } from '@/components/chat/chat-stream/types';
|
|
20
20
|
import { useChatInputStore } from '@/components/chat/stores/chat-input.store';
|
|
21
21
|
import { useChatSessionListStore } from '@/components/chat/stores/chat-session-list.store';
|
|
22
22
|
import { resolveSessionTypeLabel } from '@/components/chat/useChatSessionTypeState';
|
|
@@ -224,7 +224,7 @@ export function NcpChatPage({ view }: ChatPageProps) {
|
|
|
224
224
|
await agent.abort();
|
|
225
225
|
await sessionsQuery.refetch();
|
|
226
226
|
},
|
|
227
|
-
resumeRun: async (run:
|
|
227
|
+
resumeRun: async (run: ResumeRunParams) => {
|
|
228
228
|
if (run.sessionKey !== activeSessionId) {
|
|
229
229
|
return;
|
|
230
230
|
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { renderHook, waitFor } from '@testing-library/react';
|
|
2
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
3
|
+
import { useHydratedNcpAgent } from '../../../../ncp-packages/nextclaw-ncp-react/src/hooks/use-hydrated-ncp-agent.ts';
|
|
4
|
+
|
|
5
|
+
const mocks = vi.hoisted(() => ({
|
|
6
|
+
manager: {
|
|
7
|
+
reset: vi.fn(),
|
|
8
|
+
hydrate: vi.fn()
|
|
9
|
+
},
|
|
10
|
+
runtime: {
|
|
11
|
+
snapshot: {
|
|
12
|
+
messages: [],
|
|
13
|
+
streamingMessage: null,
|
|
14
|
+
activeRun: null,
|
|
15
|
+
error: null
|
|
16
|
+
},
|
|
17
|
+
visibleMessages: [],
|
|
18
|
+
activeRunId: null,
|
|
19
|
+
isRunning: false,
|
|
20
|
+
isSending: false,
|
|
21
|
+
send: vi.fn(),
|
|
22
|
+
abort: vi.fn(),
|
|
23
|
+
streamRun: vi.fn()
|
|
24
|
+
}
|
|
25
|
+
}));
|
|
26
|
+
|
|
27
|
+
vi.mock('../../../../ncp-packages/nextclaw-ncp-react/src/hooks/use-ncp-agent-runtime.js', () => ({
|
|
28
|
+
useScopedAgentManager: () => mocks.manager,
|
|
29
|
+
useNcpAgentRuntime: () => mocks.runtime
|
|
30
|
+
}));
|
|
31
|
+
|
|
32
|
+
describe('useHydratedNcpAgent', () => {
|
|
33
|
+
beforeEach(() => {
|
|
34
|
+
mocks.manager.reset.mockReset();
|
|
35
|
+
mocks.manager.hydrate.mockReset();
|
|
36
|
+
mocks.runtime.send.mockReset();
|
|
37
|
+
mocks.runtime.abort.mockReset();
|
|
38
|
+
mocks.runtime.streamRun.mockReset();
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('treats a newly selected session as hydrating immediately on rerender', async () => {
|
|
42
|
+
const client = {
|
|
43
|
+
stop: vi.fn().mockResolvedValue(undefined),
|
|
44
|
+
stream: vi.fn().mockResolvedValue(undefined)
|
|
45
|
+
} as never;
|
|
46
|
+
const loadSeed = vi
|
|
47
|
+
.fn()
|
|
48
|
+
.mockResolvedValueOnce({ messages: [], status: 'idle' })
|
|
49
|
+
.mockResolvedValueOnce({ messages: [], status: 'idle' });
|
|
50
|
+
|
|
51
|
+
const { result, rerender } = renderHook(
|
|
52
|
+
({ sessionId }: { sessionId: string }) =>
|
|
53
|
+
useHydratedNcpAgent({
|
|
54
|
+
sessionId,
|
|
55
|
+
client,
|
|
56
|
+
loadSeed
|
|
57
|
+
}),
|
|
58
|
+
{
|
|
59
|
+
initialProps: {
|
|
60
|
+
sessionId: 'session-a'
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
await waitFor(() => {
|
|
66
|
+
expect(result.current.isHydrating).toBe(false);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
rerender({ sessionId: 'session-b' });
|
|
70
|
+
|
|
71
|
+
expect(result.current.isHydrating).toBe(true);
|
|
72
|
+
|
|
73
|
+
await waitFor(() => {
|
|
74
|
+
expect(result.current.isHydrating).toBe(false);
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
});
|