@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/src/api/types.ts
CHANGED
|
@@ -211,11 +211,6 @@ export type SessionEntryView = {
|
|
|
211
211
|
lastTimestamp?: string;
|
|
212
212
|
};
|
|
213
213
|
|
|
214
|
-
export type SessionsListView = {
|
|
215
|
-
sessions: SessionEntryView[];
|
|
216
|
-
total: number;
|
|
217
|
-
};
|
|
218
|
-
|
|
219
214
|
export type SessionMessageView = {
|
|
220
215
|
role: string;
|
|
221
216
|
content: unknown;
|
|
@@ -233,17 +228,6 @@ export type SessionEventView = {
|
|
|
233
228
|
message?: SessionMessageView;
|
|
234
229
|
};
|
|
235
230
|
|
|
236
|
-
export type SessionHistoryView = {
|
|
237
|
-
key: string;
|
|
238
|
-
totalMessages: number;
|
|
239
|
-
totalEvents: number;
|
|
240
|
-
sessionType: string;
|
|
241
|
-
sessionTypeMutable: boolean;
|
|
242
|
-
metadata: Record<string, unknown>;
|
|
243
|
-
messages: SessionMessageView[];
|
|
244
|
-
events: SessionEventView[];
|
|
245
|
-
};
|
|
246
|
-
|
|
247
231
|
export type NcpSessionSummaryView = NcpSessionSummary;
|
|
248
232
|
|
|
249
233
|
export type NcpSessionsListView = {
|
|
@@ -259,17 +243,17 @@ export type NcpSessionMessagesView = {
|
|
|
259
243
|
total: number;
|
|
260
244
|
};
|
|
261
245
|
|
|
262
|
-
export type
|
|
246
|
+
export type NcpAssetView = {
|
|
263
247
|
id: string;
|
|
264
248
|
name: string;
|
|
265
249
|
mimeType: string;
|
|
266
250
|
sizeBytes: number;
|
|
267
|
-
|
|
251
|
+
assetUri: string;
|
|
268
252
|
url: string;
|
|
269
253
|
};
|
|
270
254
|
|
|
271
|
-
export type
|
|
272
|
-
|
|
255
|
+
export type NcpAssetPutView = {
|
|
256
|
+
assets: NcpAssetView[];
|
|
273
257
|
};
|
|
274
258
|
|
|
275
259
|
export type NcpSessionStatusView = NcpSessionStatus;
|
|
@@ -282,112 +266,12 @@ export type SessionPatchUpdate = {
|
|
|
282
266
|
clearHistory?: boolean;
|
|
283
267
|
};
|
|
284
268
|
|
|
285
|
-
export type ChatTurnRequest = {
|
|
286
|
-
message: string;
|
|
287
|
-
sessionKey?: string;
|
|
288
|
-
agentId?: string;
|
|
289
|
-
channel?: string;
|
|
290
|
-
chatId?: string;
|
|
291
|
-
model?: string;
|
|
292
|
-
metadata?: Record<string, unknown>;
|
|
293
|
-
};
|
|
294
|
-
|
|
295
|
-
export type ChatTurnView = {
|
|
296
|
-
reply: string;
|
|
297
|
-
sessionKey: string;
|
|
298
|
-
agentId?: string;
|
|
299
|
-
model?: string;
|
|
300
|
-
requestedAt: string;
|
|
301
|
-
completedAt: string;
|
|
302
|
-
durationMs: number;
|
|
303
|
-
};
|
|
304
|
-
|
|
305
|
-
export type ChatTurnStreamReadyEvent = {
|
|
306
|
-
sessionKey: string;
|
|
307
|
-
requestedAt?: string;
|
|
308
|
-
runId?: string;
|
|
309
|
-
stopSupported?: boolean;
|
|
310
|
-
stopReason?: string;
|
|
311
|
-
};
|
|
312
|
-
|
|
313
269
|
export type {
|
|
314
270
|
ChatSessionTypeCtaView,
|
|
315
271
|
ChatSessionTypeOptionView,
|
|
316
272
|
ChatSessionTypesView,
|
|
317
273
|
} from './chat-session-type.types';
|
|
318
274
|
|
|
319
|
-
export type ChatTurnStreamDeltaEvent = {
|
|
320
|
-
delta: string;
|
|
321
|
-
};
|
|
322
|
-
|
|
323
|
-
export type ChatTurnStreamSessionEvent = {
|
|
324
|
-
data: SessionEventView;
|
|
325
|
-
};
|
|
326
|
-
|
|
327
|
-
export type ChatTurnStreamErrorEvent = {
|
|
328
|
-
code?: string;
|
|
329
|
-
message?: string;
|
|
330
|
-
};
|
|
331
|
-
|
|
332
|
-
export type ChatCapabilitiesView = {
|
|
333
|
-
stopSupported: boolean;
|
|
334
|
-
stopReason?: string;
|
|
335
|
-
};
|
|
336
|
-
|
|
337
|
-
export type ChatCommandOptionView = {
|
|
338
|
-
name: string;
|
|
339
|
-
description: string;
|
|
340
|
-
type: 'string' | 'boolean' | 'number';
|
|
341
|
-
required?: boolean;
|
|
342
|
-
};
|
|
343
|
-
|
|
344
|
-
export type ChatCommandView = {
|
|
345
|
-
name: string;
|
|
346
|
-
description: string;
|
|
347
|
-
options?: ChatCommandOptionView[];
|
|
348
|
-
};
|
|
349
|
-
|
|
350
|
-
export type ChatCommandsView = {
|
|
351
|
-
commands: ChatCommandView[];
|
|
352
|
-
total: number;
|
|
353
|
-
};
|
|
354
|
-
|
|
355
|
-
export type ChatTurnStopRequest = {
|
|
356
|
-
runId: string;
|
|
357
|
-
sessionKey?: string;
|
|
358
|
-
agentId?: string;
|
|
359
|
-
};
|
|
360
|
-
|
|
361
|
-
export type ChatTurnStopResult = {
|
|
362
|
-
stopped: boolean;
|
|
363
|
-
runId: string;
|
|
364
|
-
sessionKey?: string;
|
|
365
|
-
reason?: string;
|
|
366
|
-
};
|
|
367
|
-
|
|
368
|
-
export type ChatRunState = 'queued' | 'running' | 'completed' | 'failed' | 'aborted';
|
|
369
|
-
|
|
370
|
-
export type ChatRunView = {
|
|
371
|
-
runId: string;
|
|
372
|
-
sessionKey: string;
|
|
373
|
-
agentId?: string;
|
|
374
|
-
model?: string;
|
|
375
|
-
state: ChatRunState;
|
|
376
|
-
requestedAt: string;
|
|
377
|
-
startedAt?: string;
|
|
378
|
-
completedAt?: string;
|
|
379
|
-
stopSupported: boolean;
|
|
380
|
-
stopReason?: string;
|
|
381
|
-
error?: string;
|
|
382
|
-
reply?: string;
|
|
383
|
-
eventCount: number;
|
|
384
|
-
};
|
|
385
|
-
|
|
386
|
-
export type ChatRunListView = {
|
|
387
|
-
runs: ChatRunView[];
|
|
388
|
-
total: number;
|
|
389
|
-
};
|
|
390
|
-
|
|
391
275
|
export type CronScheduleView =
|
|
392
276
|
| { kind: "at"; atMs?: number | null }
|
|
393
277
|
| { kind: "every"; everyMs?: number | null }
|
|
@@ -684,7 +568,6 @@ export type ConfigActionExecuteResult = {
|
|
|
684
568
|
// WebSocket events
|
|
685
569
|
export type WsEvent =
|
|
686
570
|
| { type: 'config.updated'; payload: { path: string } }
|
|
687
|
-
| { type: 'run.updated'; payload: { run: ChatRunView } }
|
|
688
571
|
| { type: 'session.updated'; payload: { sessionKey: string } }
|
|
689
572
|
| { type: 'config.reload.started'; payload?: Record<string, unknown> }
|
|
690
573
|
| { type: 'config.reload.finished'; payload?: Record<string, unknown> }
|
|
@@ -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
|
});
|
|
@@ -191,3 +191,42 @@ it("keeps named non-image files as downloadable attachments", () => {
|
|
|
191
191
|
},
|
|
192
192
|
});
|
|
193
193
|
});
|
|
194
|
+
|
|
195
|
+
it("renders asset tool results as previewable files", () => {
|
|
196
|
+
const adapted = adapt([
|
|
197
|
+
{
|
|
198
|
+
id: "assistant-asset",
|
|
199
|
+
role: "assistant",
|
|
200
|
+
parts: [
|
|
201
|
+
{
|
|
202
|
+
type: "tool-invocation",
|
|
203
|
+
toolInvocation: {
|
|
204
|
+
status: ToolInvocationStatus.RESULT,
|
|
205
|
+
toolCallId: "call-asset-1",
|
|
206
|
+
toolName: "asset_put",
|
|
207
|
+
args: { path: "/tmp/output.png" },
|
|
208
|
+
result: {
|
|
209
|
+
ok: true,
|
|
210
|
+
asset: {
|
|
211
|
+
uri: "asset://store/2026/03/27/asset_1",
|
|
212
|
+
name: "output.png",
|
|
213
|
+
mimeType: "image/png",
|
|
214
|
+
url: "/api/ncp/assets/content?uri=asset%3A%2F%2Fstore%2F2026%2F03%2F27%2Fasset_1",
|
|
215
|
+
},
|
|
216
|
+
},
|
|
217
|
+
},
|
|
218
|
+
},
|
|
219
|
+
],
|
|
220
|
+
},
|
|
221
|
+
] as unknown as ChatMessageSource[]);
|
|
222
|
+
|
|
223
|
+
expect(adapted[0]?.parts[0]).toEqual({
|
|
224
|
+
type: "file",
|
|
225
|
+
file: {
|
|
226
|
+
label: "output.png",
|
|
227
|
+
mimeType: "image/png",
|
|
228
|
+
dataUrl: "/api/ncp/assets/content?uri=asset%3A%2F%2Fstore%2F2026%2F03%2F27%2Fasset_1",
|
|
229
|
+
isImage: true,
|
|
230
|
+
},
|
|
231
|
+
});
|
|
232
|
+
});
|
|
@@ -76,6 +76,58 @@ function isRecord(value: unknown): value is Record<string, unknown> {
|
|
|
76
76
|
return typeof value === 'object' && value !== null;
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
+
function readOptionalString(value: unknown): string | null {
|
|
80
|
+
if (typeof value !== 'string') {
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
const trimmed = value.trim();
|
|
84
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function extractAssetFileView(
|
|
88
|
+
value: unknown,
|
|
89
|
+
texts: ChatMessageAdapterTexts
|
|
90
|
+
):
|
|
91
|
+
| {
|
|
92
|
+
type: 'file';
|
|
93
|
+
file: {
|
|
94
|
+
label: string;
|
|
95
|
+
mimeType: string;
|
|
96
|
+
dataUrl: string;
|
|
97
|
+
isImage: boolean;
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
| null {
|
|
101
|
+
if (!isRecord(value)) {
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
const assetCandidate = isRecord(value.asset)
|
|
105
|
+
? value.asset
|
|
106
|
+
: Array.isArray(value.assets) && value.assets.length > 0 && isRecord(value.assets[0])
|
|
107
|
+
? value.assets[0]
|
|
108
|
+
: null;
|
|
109
|
+
if (!assetCandidate) {
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
const url = readOptionalString(assetCandidate.url);
|
|
113
|
+
const mimeType = readOptionalString(assetCandidate.mimeType) ?? 'application/octet-stream';
|
|
114
|
+
if (!url) {
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
const label =
|
|
118
|
+
readOptionalString(assetCandidate.name) ??
|
|
119
|
+
(mimeType.startsWith('image/') ? texts.imageAttachmentLabel : texts.fileAttachmentLabel);
|
|
120
|
+
return {
|
|
121
|
+
type: 'file',
|
|
122
|
+
file: {
|
|
123
|
+
label,
|
|
124
|
+
mimeType,
|
|
125
|
+
dataUrl: url,
|
|
126
|
+
isImage: mimeType.startsWith('image/')
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
79
131
|
function isTextPart(part: ChatMessagePartSource): part is Extract<ChatMessagePartSource, { type: 'text' }> {
|
|
80
132
|
return part.type === 'text' && typeof part.text === 'string';
|
|
81
133
|
}
|
|
@@ -223,6 +275,10 @@ export function adaptChatMessages(params: {
|
|
|
223
275
|
}
|
|
224
276
|
if (isToolInvocationPart(part)) {
|
|
225
277
|
const invocation = part.toolInvocation;
|
|
278
|
+
const assetFileView = extractAssetFileView(invocation.result, params.texts);
|
|
279
|
+
if (assetFileView) {
|
|
280
|
+
return assetFileView;
|
|
281
|
+
}
|
|
226
282
|
const detail = summarizeToolArgs(invocation.parsedArgs ?? invocation.args);
|
|
227
283
|
const rawResult =
|
|
228
284
|
typeof invocation.error === 'string' && invocation.error.trim()
|
|
@@ -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
|
+
});
|
|
@@ -87,8 +87,8 @@ describe('deriveNcpMessagePartsFromComposer', () => {
|
|
|
87
87
|
name: 'config.json',
|
|
88
88
|
mimeType: 'application/json',
|
|
89
89
|
sizeBytes: 18,
|
|
90
|
-
|
|
91
|
-
url: '/api/ncp/
|
|
90
|
+
assetUri: 'asset://store/2026/03/26/asset_123',
|
|
91
|
+
url: '/api/ncp/assets/content?uri=asset%3A%2F%2Fstore%2F2026%2F03%2F26%2Fasset_123'
|
|
92
92
|
}
|
|
93
93
|
]
|
|
94
94
|
);
|
|
@@ -98,8 +98,8 @@ describe('deriveNcpMessagePartsFromComposer', () => {
|
|
|
98
98
|
type: 'file',
|
|
99
99
|
name: 'config.json',
|
|
100
100
|
mimeType: 'application/json',
|
|
101
|
-
|
|
102
|
-
url: '/api/ncp/
|
|
101
|
+
assetUri: 'asset://store/2026/03/26/asset_123',
|
|
102
|
+
url: '/api/ncp/assets/content?uri=asset%3A%2F%2Fstore%2F2026%2F03%2F26%2Fasset_123',
|
|
103
103
|
sizeBytes: 18
|
|
104
104
|
}
|
|
105
105
|
]);
|
|
@@ -108,7 +108,7 @@ export function deriveNcpMessagePartsFromComposer(
|
|
|
108
108
|
type: 'file',
|
|
109
109
|
name: attachment.name,
|
|
110
110
|
mimeType: attachment.mimeType,
|
|
111
|
-
...(attachment.
|
|
111
|
+
...(attachment.assetUri ? { assetUri: attachment.assetUri } : {}),
|
|
112
112
|
...(attachment.url ? { url: attachment.url } : {}),
|
|
113
113
|
...(attachment.contentBase64 ? { contentBase64: attachment.contentBase64 } : {}),
|
|
114
114
|
sizeBytes: attachment.sizeBytes
|
|
@@ -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
|
};
|