@nextclaw/ui 0.11.8 → 0.11.10
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 +21 -0
- package/dist/assets/{ChannelsList-dsxeZk5v.js → ChannelsList-C63gOoYI.js} +3 -3
- package/dist/assets/ChatPage-Ci3Gz0qh.js +37 -0
- package/dist/assets/{DocBrowser-B9RfxWIh.js → DocBrowser-CI4jOzJY.js} +1 -1
- package/dist/assets/{LogoBadge-iVDzhkNu.js → LogoBadge-DImV63-L.js} +1 -1
- package/dist/assets/{MarketplacePage-4MZIcD0K.js → MarketplacePage-B360oSAV.js} +1 -1
- package/dist/assets/{McpMarketplacePage-cwLMty-D.js → McpMarketplacePage-KIQgx_7h.js} +2 -2
- package/dist/assets/{ModelConfig-CNICBWzw.js → ModelConfig-Ben3tQoX.js} +1 -1
- package/dist/assets/{ProvidersList-CEHGsRSL.js → ProvidersList-DE-S9mq0.js} +1 -1
- package/dist/assets/RemoteAccessPage-DxUia6R-.js +1 -0
- package/dist/assets/RuntimeConfig-CQcGfNZT.js +1 -0
- package/dist/assets/{SearchConfig-CaFAgBMN.js → SearchConfig-DeOa-M6j.js} +1 -1
- package/dist/assets/{SecretsConfig-DzWq8hGZ.js → SecretsConfig-Ci8pJmzd.js} +2 -2
- package/dist/assets/{SessionsConfig-CJJTcxyQ.js → SessionsConfig-B6zq55yu.js} +2 -2
- package/dist/assets/chat-session-display--oo5yuIw.js +1 -0
- package/dist/assets/{index-BlrweCCh.js → index-LhlkB00c.js} +4 -4
- package/dist/assets/{label-DtssWSI4.js → label-3TKt0PoZ.js} +1 -1
- package/dist/assets/{page-layout-DnRqSldv.js → page-layout-CopkIM3Q.js} +1 -1
- package/dist/assets/{popover-Un2VFGcS.js → popover-CUx8uRJw.js} +1 -1
- package/dist/assets/security-config-BL29kTzz.js +1 -0
- package/dist/assets/{skeleton-DTFzTqqO.js → skeleton-Bs4zvcql.js} +1 -1
- package/dist/assets/{status-dot-DOJX6vii.js → status-dot-D6vJMwD7.js} +1 -1
- package/dist/assets/{switch-utBdpBRv.js → switch-A3-ClT1P.js} +1 -1
- package/dist/assets/{tabs-custom-ExyfvfgG.js → tabs-custom-BVSd5urq.js} +1 -1
- package/dist/assets/{useConfirmDialog-DjNtKs4n.js → useConfirmDialog-ChPriea6.js} +1 -1
- package/dist/index.html +1 -1
- package/package.json +5 -5
- package/src/api/ncp-session-query-cache.test.ts +89 -0
- package/src/api/ncp-session-query-cache.ts +85 -0
- package/src/api/types.ts +2 -0
- package/src/components/chat/ChatConversationPanel.test.tsx +1 -1
- package/src/components/chat/ChatConversationPanel.tsx +62 -32
- package/src/components/chat/ChatSidebar.test.tsx +87 -92
- package/src/components/chat/ChatSidebar.tsx +21 -36
- package/src/components/chat/adapters/chat-message.adapter.ts +17 -5
- package/src/components/chat/chat-session-label.service.ts +3 -3
- package/src/components/chat/containers/chat-message-list.container.test.tsx +101 -0
- package/src/components/chat/containers/chat-message-list.container.tsx +93 -54
- package/src/components/chat/managers/chat-session-list.manager.ts +0 -18
- package/src/components/chat/ncp/NcpChatPage.tsx +4 -52
- package/src/components/chat/ncp/ncp-chat-thread.manager.ts +4 -18
- package/src/components/chat/ncp/ncp-chat.presenter.ts +0 -2
- package/src/components/chat/ncp/ncp-session-adapter.test.ts +0 -23
- package/src/components/chat/ncp/ncp-session-adapter.ts +0 -19
- package/src/components/chat/ncp/use-ncp-session-list-view.ts +42 -0
- package/src/components/chat/presenter/chat-presenter-context.tsx +0 -3
- package/src/components/chat/stores/chat-session-list.store.ts +1 -7
- package/src/components/chat/stores/chat-thread.store.ts +3 -3
- package/src/hooks/use-realtime-query-bridge.ts +14 -19
- package/src/hooks/useConfig.ts +10 -11
- package/dist/assets/ChatPage-CWK4Bckz.js +0 -37
- package/dist/assets/RemoteAccessPage-uYxoaQ8V.js +0 -1
- package/dist/assets/RuntimeConfig-CYQq4S_m.js +0 -1
- package/dist/assets/ncp-session-adapter-C-jqQqcV.js +0 -1
- package/dist/assets/security-config-B7Bkebpm.js +0 -1
- package/src/components/chat/managers/chat-run-status.manager.ts +0 -32
- package/src/components/chat/stores/chat-run-status.store.ts +0 -30
|
@@ -1,82 +1,121 @@
|
|
|
1
1
|
import { useMemo } from "react";
|
|
2
|
-
import {
|
|
3
|
-
import { ChatMessageList } from "@nextclaw/agent-chat-ui";
|
|
2
|
+
import type { NcpMessage } from "@nextclaw/ncp";
|
|
4
3
|
import {
|
|
5
|
-
|
|
4
|
+
type ChatMessageViewModel,
|
|
5
|
+
ChatMessageList,
|
|
6
|
+
} from "@nextclaw/agent-chat-ui";
|
|
7
|
+
import {
|
|
8
|
+
adaptChatMessage,
|
|
9
|
+
type ChatMessageAdapterTexts,
|
|
6
10
|
type ChatMessageSource,
|
|
7
11
|
} from "@/components/chat/adapters/chat-message.adapter";
|
|
12
|
+
import { adaptNcpMessageToUiMessage } from "@/components/chat/ncp/ncp-session-adapter";
|
|
8
13
|
import { useI18n } from "@/components/providers/I18nProvider";
|
|
9
14
|
import { formatDateTime, t } from "@/lib/i18n";
|
|
10
15
|
|
|
11
16
|
type ChatMessageListContainerProps = {
|
|
12
|
-
|
|
17
|
+
messages: readonly NcpMessage[];
|
|
13
18
|
isSending: boolean;
|
|
14
19
|
className?: string;
|
|
15
20
|
};
|
|
16
21
|
|
|
22
|
+
const messageViewModelCache = new WeakMap<
|
|
23
|
+
NcpMessage,
|
|
24
|
+
{ language: string; viewModel: ChatMessageViewModel }
|
|
25
|
+
>();
|
|
26
|
+
|
|
27
|
+
function buildChatMessageAdapterTexts(
|
|
28
|
+
language: string,
|
|
29
|
+
): ChatMessageAdapterTexts {
|
|
30
|
+
void language;
|
|
31
|
+
return {
|
|
32
|
+
roleLabels: {
|
|
33
|
+
user: t("chatRoleUser"),
|
|
34
|
+
assistant: t("chatRoleAssistant"),
|
|
35
|
+
tool: t("chatRoleTool"),
|
|
36
|
+
system: t("chatRoleSystem"),
|
|
37
|
+
fallback: t("chatRoleMessage"),
|
|
38
|
+
},
|
|
39
|
+
reasoningLabel: t("chatReasoning"),
|
|
40
|
+
toolCallLabel: t("chatToolCall"),
|
|
41
|
+
toolResultLabel: t("chatToolResult"),
|
|
42
|
+
toolNoOutputLabel: t("chatToolNoOutput"),
|
|
43
|
+
toolOutputLabel: t("chatToolOutput"),
|
|
44
|
+
toolStatusPreparingLabel: t("chatToolStatusPreparing"),
|
|
45
|
+
toolStatusRunningLabel: t("chatToolStatusRunning"),
|
|
46
|
+
toolStatusCompletedLabel: t("chatToolStatusCompleted"),
|
|
47
|
+
toolStatusFailedLabel: t("chatToolStatusFailed"),
|
|
48
|
+
toolStatusCancelledLabel: t("chatToolStatusCancelled"),
|
|
49
|
+
imageAttachmentLabel: t("chatImageAttachment"),
|
|
50
|
+
fileAttachmentLabel: t("chatFileAttachment"),
|
|
51
|
+
unknownPartLabel: t("chatUnknownPart"),
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function buildChatMessageTexts(language: string) {
|
|
56
|
+
void language;
|
|
57
|
+
return {
|
|
58
|
+
copyCodeLabel: t("chatCodeCopy"),
|
|
59
|
+
copiedCodeLabel: t("chatCodeCopied"),
|
|
60
|
+
typingLabel: t("chatTyping"),
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
17
64
|
export function ChatMessageListContainer(props: ChatMessageListContainerProps) {
|
|
18
65
|
const { language } = useI18n();
|
|
19
|
-
const
|
|
20
|
-
() =>
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
66
|
+
const texts = useMemo<ChatMessageAdapterTexts>(
|
|
67
|
+
() => buildChatMessageAdapterTexts(language),
|
|
68
|
+
[language],
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
const messages = useMemo(() => {
|
|
72
|
+
return props.messages.map((message) => {
|
|
73
|
+
const cached = messageViewModelCache.get(message);
|
|
74
|
+
if (cached && cached.language === language) {
|
|
75
|
+
return cached.viewModel;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const uiMessage = adaptNcpMessageToUiMessage(message);
|
|
79
|
+
const sourceMessage: ChatMessageSource = {
|
|
80
|
+
id: uiMessage.id,
|
|
81
|
+
role: uiMessage.role,
|
|
24
82
|
meta: {
|
|
25
|
-
timestamp:
|
|
26
|
-
status:
|
|
83
|
+
timestamp: uiMessage.meta?.timestamp,
|
|
84
|
+
status: uiMessage.meta?.status,
|
|
27
85
|
},
|
|
28
|
-
parts:
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
|
|
86
|
+
parts: uiMessage.parts as unknown as ChatMessageSource["parts"],
|
|
87
|
+
};
|
|
88
|
+
const viewModel = adaptChatMessage(sourceMessage, {
|
|
89
|
+
formatTimestamp: (value) => formatDateTime(value, language),
|
|
90
|
+
texts,
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
messageViewModelCache.set(message, { language, viewModel });
|
|
94
|
+
return viewModel;
|
|
95
|
+
});
|
|
96
|
+
}, [language, props.messages, texts]);
|
|
32
97
|
|
|
33
|
-
const
|
|
98
|
+
const hasAssistantDraft = useMemo(
|
|
34
99
|
() =>
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
},
|
|
46
|
-
reasoningLabel: t("chatReasoning"),
|
|
47
|
-
toolCallLabel: t("chatToolCall"),
|
|
48
|
-
toolResultLabel: t("chatToolResult"),
|
|
49
|
-
toolNoOutputLabel: t("chatToolNoOutput"),
|
|
50
|
-
toolOutputLabel: t("chatToolOutput"),
|
|
51
|
-
toolStatusPreparingLabel: t("chatToolStatusPreparing"),
|
|
52
|
-
toolStatusRunningLabel: t("chatToolStatusRunning"),
|
|
53
|
-
toolStatusCompletedLabel: t("chatToolStatusCompleted"),
|
|
54
|
-
toolStatusFailedLabel: t("chatToolStatusFailed"),
|
|
55
|
-
toolStatusCancelledLabel: t("chatToolStatusCancelled"),
|
|
56
|
-
imageAttachmentLabel: t("chatImageAttachment"),
|
|
57
|
-
fileAttachmentLabel: t("chatFileAttachment"),
|
|
58
|
-
unknownPartLabel: t("chatUnknownPart"),
|
|
59
|
-
},
|
|
60
|
-
}),
|
|
61
|
-
[language, sourceMessages],
|
|
100
|
+
messages.some(
|
|
101
|
+
(message) =>
|
|
102
|
+
message.role === "assistant" &&
|
|
103
|
+
(message.status === "streaming" || message.status === "pending"),
|
|
104
|
+
),
|
|
105
|
+
[messages],
|
|
106
|
+
);
|
|
107
|
+
const messageTexts = useMemo(
|
|
108
|
+
() => buildChatMessageTexts(language),
|
|
109
|
+
[language],
|
|
62
110
|
);
|
|
63
111
|
|
|
64
112
|
return (
|
|
65
113
|
<ChatMessageList
|
|
66
114
|
messages={messages}
|
|
67
115
|
isSending={props.isSending}
|
|
68
|
-
hasAssistantDraft={
|
|
69
|
-
(message) =>
|
|
70
|
-
message.role === "assistant" &&
|
|
71
|
-
(message.meta?.status === "streaming" ||
|
|
72
|
-
message.meta?.status === "pending"),
|
|
73
|
-
)}
|
|
116
|
+
hasAssistantDraft={hasAssistantDraft}
|
|
74
117
|
className={props.className}
|
|
75
|
-
texts={
|
|
76
|
-
copyCodeLabel: t("chatCodeCopy"),
|
|
77
|
-
copiedCodeLabel: t("chatCodeCopied"),
|
|
78
|
-
typingLabel: t("chatTyping"),
|
|
79
|
-
}}
|
|
118
|
+
texts={messageTexts}
|
|
80
119
|
/>
|
|
81
120
|
);
|
|
82
121
|
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { useChatSessionListStore } from '@/components/chat/stores/chat-session-list.store';
|
|
2
2
|
import { useChatInputStore } from '@/components/chat/stores/chat-input.store';
|
|
3
3
|
import type { ChatUiManager } from '@/components/chat/managers/chat-ui.manager';
|
|
4
|
-
import type { ChatSessionListSnapshot } from '@/components/chat/stores/chat-session-list.store';
|
|
5
4
|
import type { SetStateAction } from 'react';
|
|
6
5
|
import type { ChatStreamActionsManager } from '@/components/chat/managers/chat-stream-actions.manager';
|
|
7
6
|
|
|
@@ -11,16 +10,6 @@ export class ChatSessionListManager {
|
|
|
11
10
|
private streamActionsManager: ChatStreamActionsManager
|
|
12
11
|
) {}
|
|
13
12
|
|
|
14
|
-
private hasSnapshotChanges = (patch: Partial<ChatSessionListSnapshot>): boolean => {
|
|
15
|
-
const current = useChatSessionListStore.getState().snapshot;
|
|
16
|
-
for (const [key, value] of Object.entries(patch) as Array<[keyof ChatSessionListSnapshot, ChatSessionListSnapshot[keyof ChatSessionListSnapshot]]>) {
|
|
17
|
-
if (!Object.is(current[key], value)) {
|
|
18
|
-
return true;
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
return false;
|
|
22
|
-
};
|
|
23
|
-
|
|
24
13
|
private resolveUpdateValue = <T>(prev: T, next: SetStateAction<T>): T => {
|
|
25
14
|
if (typeof next === 'function') {
|
|
26
15
|
return (next as (value: T) => T)(prev);
|
|
@@ -28,13 +17,6 @@ export class ChatSessionListManager {
|
|
|
28
17
|
return next;
|
|
29
18
|
};
|
|
30
19
|
|
|
31
|
-
syncSnapshot = (patch: Partial<ChatSessionListSnapshot>) => {
|
|
32
|
-
if (!this.hasSnapshotChanges(patch)) {
|
|
33
|
-
return;
|
|
34
|
-
}
|
|
35
|
-
useChatSessionListStore.getState().setSnapshot(patch);
|
|
36
|
-
};
|
|
37
|
-
|
|
38
20
|
setSelectedAgentId = (next: SetStateAction<string>) => {
|
|
39
21
|
const prev = useChatSessionListStore.getState().snapshot.selectedAgentId;
|
|
40
22
|
const value = this.resolveUpdateValue(prev, next);
|
|
@@ -14,7 +14,7 @@ import { createNcpAppClientFetch } from '@/components/chat/ncp/ncp-app-client-fe
|
|
|
14
14
|
import { parseSessionKeyFromRoute, resolveAgentIdFromSessionKey } from '@/components/chat/chat-session-route';
|
|
15
15
|
import { useNcpChatPageData } from '@/components/chat/ncp/ncp-chat-page-data';
|
|
16
16
|
import { NcpChatPresenter } from '@/components/chat/ncp/ncp-chat.presenter';
|
|
17
|
-
import {
|
|
17
|
+
import { createNcpSessionId } from '@/components/chat/ncp/ncp-session-adapter';
|
|
18
18
|
import { ChatPresenterProvider } from '@/components/chat/presenter/chat-presenter-context';
|
|
19
19
|
import type { ResumeRunParams } from '@/components/chat/chat-stream/types';
|
|
20
20
|
import { useChatInputStore } from '@/components/chat/stores/chat-input.store';
|
|
@@ -74,12 +74,10 @@ export function NcpChatPage({ view }: ChatPageProps) {
|
|
|
74
74
|
[routeSessionIdParam]
|
|
75
75
|
);
|
|
76
76
|
const {
|
|
77
|
-
sessionsQuery,
|
|
78
77
|
installedSkillsQuery,
|
|
79
78
|
isProviderStateResolved,
|
|
80
79
|
modelOptions,
|
|
81
80
|
sessionSummaries,
|
|
82
|
-
sessions,
|
|
83
81
|
skillRecords,
|
|
84
82
|
selectedSession,
|
|
85
83
|
sessionTypeOptions,
|
|
@@ -97,7 +95,6 @@ export function NcpChatPage({ view }: ChatPageProps) {
|
|
|
97
95
|
setSelectedModel: presenter.chatInputManager.setSelectedModel,
|
|
98
96
|
setSelectedThinkingLevel: presenter.chatInputManager.setSelectedThinkingLevel
|
|
99
97
|
});
|
|
100
|
-
const refetchSessions = sessionsQuery.refetch;
|
|
101
98
|
|
|
102
99
|
const activeSessionId = selectedSessionKey ?? draftSessionId;
|
|
103
100
|
const sessionSummariesRef = useRef(sessionSummaries);
|
|
@@ -151,32 +148,11 @@ export function NcpChatPage({ view }: ChatPageProps) {
|
|
|
151
148
|
}
|
|
152
149
|
}, [presenter, selectedSessionKey]);
|
|
153
150
|
|
|
154
|
-
const uiMessages = useMemo(
|
|
155
|
-
() => adaptNcpMessagesToUiMessages(agent.visibleMessages),
|
|
156
|
-
[agent.visibleMessages]
|
|
157
|
-
);
|
|
158
151
|
const isSending = agent.isSending || agent.isRunning;
|
|
159
152
|
const isAwaitingAssistantOutput = agent.isRunning;
|
|
160
153
|
const canStopCurrentRun = agent.isRunning;
|
|
161
154
|
const stopDisabledReason = agent.isRunning ? null : '__preparing__';
|
|
162
155
|
const lastSendError = agent.hydrateError?.message ?? agent.snapshot.error?.message ?? null;
|
|
163
|
-
const activeBackendRunId = agent.activeRunId;
|
|
164
|
-
const sessionRunStatusByKey = useMemo(
|
|
165
|
-
() =>
|
|
166
|
-
buildNcpSessionRunStatusByKey({
|
|
167
|
-
summaries: sessionSummaries,
|
|
168
|
-
activeSessionId,
|
|
169
|
-
isLocallyRunning: isSending || Boolean(activeBackendRunId)
|
|
170
|
-
}),
|
|
171
|
-
[activeBackendRunId, activeSessionId, isSending, sessionSummaries]
|
|
172
|
-
);
|
|
173
|
-
|
|
174
|
-
useEffect(() => {
|
|
175
|
-
if (!isSending && !activeBackendRunId) {
|
|
176
|
-
return;
|
|
177
|
-
}
|
|
178
|
-
void refetchSessions();
|
|
179
|
-
}, [activeBackendRunId, isSending, refetchSessions]);
|
|
180
156
|
|
|
181
157
|
useEffect(() => {
|
|
182
158
|
presenter.chatStreamActionsManager.bind({
|
|
@@ -201,9 +177,7 @@ export function NcpChatPage({ view }: ChatPageProps) {
|
|
|
201
177
|
return;
|
|
202
178
|
}
|
|
203
179
|
try {
|
|
204
|
-
void sessionsQuery.refetch();
|
|
205
180
|
await agent.send(envelope);
|
|
206
|
-
await sessionsQuery.refetch();
|
|
207
181
|
} catch (error) {
|
|
208
182
|
if (payload.restoreDraftOnError) {
|
|
209
183
|
if (payload.composerNodes && payload.composerNodes.length > 0) {
|
|
@@ -222,7 +196,6 @@ export function NcpChatPage({ view }: ChatPageProps) {
|
|
|
222
196
|
},
|
|
223
197
|
stopCurrentRun: async () => {
|
|
224
198
|
await agent.abort();
|
|
225
|
-
await sessionsQuery.refetch();
|
|
226
199
|
},
|
|
227
200
|
resumeRun: async (run: ResumeRunParams) => {
|
|
228
201
|
if (run.sessionKey !== activeSessionId) {
|
|
@@ -235,7 +208,7 @@ export function NcpChatPage({ view }: ChatPageProps) {
|
|
|
235
208
|
},
|
|
236
209
|
applyHistoryMessages: () => {}
|
|
237
210
|
});
|
|
238
|
-
}, [activeSessionId, agent, presenter
|
|
211
|
+
}, [activeSessionId, agent, presenter]);
|
|
239
212
|
|
|
240
213
|
useChatSessionSync({
|
|
241
214
|
view,
|
|
@@ -264,12 +237,6 @@ export function NcpChatPage({ view }: ChatPageProps) {
|
|
|
264
237
|
sessionTypeOptions.find((option) => option.value === selectedSessionType)?.label ??
|
|
265
238
|
resolveSessionTypeLabel(selectedSessionType);
|
|
266
239
|
|
|
267
|
-
useEffect(() => {
|
|
268
|
-
presenter.chatThreadManager.bindActions({
|
|
269
|
-
refetchSessions: sessionsQuery.refetch
|
|
270
|
-
});
|
|
271
|
-
}, [presenter, sessionsQuery.refetch]);
|
|
272
|
-
|
|
273
240
|
useEffect(() => {
|
|
274
241
|
presenter.chatInputManager.syncSnapshot({
|
|
275
242
|
isProviderStateResolved,
|
|
@@ -288,16 +255,6 @@ export function NcpChatPage({ view }: ChatPageProps) {
|
|
|
288
255
|
skillRecords,
|
|
289
256
|
isSkillsLoading: installedSkillsQuery.isLoading
|
|
290
257
|
});
|
|
291
|
-
presenter.chatSessionListManager.syncSnapshot({
|
|
292
|
-
sessions,
|
|
293
|
-
query,
|
|
294
|
-
isLoading: sessionsQuery.isLoading
|
|
295
|
-
});
|
|
296
|
-
presenter.chatRunStatusManager.syncSnapshot({
|
|
297
|
-
sessionRunStatusByKey,
|
|
298
|
-
isLocallyRunning: isSending || Boolean(activeBackendRunId),
|
|
299
|
-
activeBackendRunId
|
|
300
|
-
});
|
|
301
258
|
presenter.chatThreadManager.syncSnapshot({
|
|
302
259
|
isProviderStateResolved,
|
|
303
260
|
modelOptions,
|
|
@@ -309,12 +266,11 @@ export function NcpChatPage({ view }: ChatPageProps) {
|
|
|
309
266
|
canDeleteSession: Boolean(selectedSession),
|
|
310
267
|
threadRef,
|
|
311
268
|
isHistoryLoading: agent.isHydrating,
|
|
312
|
-
|
|
269
|
+
messages: agent.visibleMessages,
|
|
313
270
|
isSending,
|
|
314
271
|
isAwaitingAssistantOutput
|
|
315
272
|
});
|
|
316
273
|
}, [
|
|
317
|
-
activeBackendRunId,
|
|
318
274
|
agent.isHydrating,
|
|
319
275
|
canEditSessionType,
|
|
320
276
|
canStopCurrentRun,
|
|
@@ -329,20 +285,16 @@ export function NcpChatPage({ view }: ChatPageProps) {
|
|
|
329
285
|
modelOptions.length,
|
|
330
286
|
modelOptions,
|
|
331
287
|
presenter,
|
|
332
|
-
query,
|
|
333
288
|
selectedSession,
|
|
334
289
|
selectedSessionKey,
|
|
335
290
|
selectedSessionType,
|
|
336
|
-
sessionRunStatusByKey,
|
|
337
291
|
sessionTypeOptions,
|
|
338
292
|
sessionTypeUnavailable,
|
|
339
293
|
sessionTypeUnavailableMessage,
|
|
340
|
-
sessions,
|
|
341
|
-
sessionsQuery.isLoading,
|
|
342
294
|
skillRecords,
|
|
343
295
|
stopDisabledReason,
|
|
344
296
|
threadRef,
|
|
345
|
-
|
|
297
|
+
agent.visibleMessages
|
|
346
298
|
]);
|
|
347
299
|
|
|
348
300
|
return (
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { appQueryClient } from '@/app-query-client';
|
|
2
|
+
import { deleteNcpSessionSummaryInQueryClient } from '@/api/ncp-session-query-cache';
|
|
1
3
|
import { deleteNcpSession as deleteNcpSessionApi } from '@/api/ncp-session';
|
|
2
4
|
import type { ChatSessionListManager } from '@/components/chat/managers/chat-session-list.manager';
|
|
3
5
|
import type { ChatStreamActionsManager } from '@/components/chat/managers/chat-stream-actions.manager';
|
|
@@ -7,30 +9,13 @@ import type { ChatThreadSnapshot } from '@/components/chat/stores/chat-thread.st
|
|
|
7
9
|
import { useChatThreadStore } from '@/components/chat/stores/chat-thread.store';
|
|
8
10
|
import { t } from '@/lib/i18n';
|
|
9
11
|
|
|
10
|
-
export type NcpChatThreadManagerActions = {
|
|
11
|
-
refetchSessions: () => Promise<unknown>;
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
const noopAsync = async () => {};
|
|
15
|
-
|
|
16
12
|
export class NcpChatThreadManager {
|
|
17
|
-
private actions: NcpChatThreadManagerActions = {
|
|
18
|
-
refetchSessions: noopAsync
|
|
19
|
-
};
|
|
20
|
-
|
|
21
13
|
constructor(
|
|
22
14
|
private uiManager: ChatUiManager,
|
|
23
15
|
private sessionListManager: ChatSessionListManager,
|
|
24
16
|
private streamActionsManager: ChatStreamActionsManager
|
|
25
17
|
) {}
|
|
26
18
|
|
|
27
|
-
bindActions = (patch: Partial<NcpChatThreadManagerActions>) => {
|
|
28
|
-
this.actions = {
|
|
29
|
-
...this.actions,
|
|
30
|
-
...patch
|
|
31
|
-
};
|
|
32
|
-
};
|
|
33
|
-
|
|
34
19
|
private hasSnapshotChanges = (patch: Partial<ChatThreadSnapshot>): boolean => {
|
|
35
20
|
const current = useChatThreadStore.getState().snapshot;
|
|
36
21
|
for (const [key, value] of Object.entries(patch) as Array<[keyof ChatThreadSnapshot, ChatThreadSnapshot[keyof ChatThreadSnapshot]]>) {
|
|
@@ -78,9 +63,10 @@ export class NcpChatThreadManager {
|
|
|
78
63
|
useChatThreadStore.getState().setSnapshot({ isDeletePending: true });
|
|
79
64
|
try {
|
|
80
65
|
await deleteNcpSessionApi(selectedSessionKey);
|
|
66
|
+
deleteNcpSessionSummaryInQueryClient(appQueryClient, selectedSessionKey);
|
|
67
|
+
appQueryClient.removeQueries({ queryKey: ['ncp-session-messages', selectedSessionKey] });
|
|
81
68
|
this.streamActionsManager.resetStreamState();
|
|
82
69
|
this.uiManager.goToChatRoot({ replace: true });
|
|
83
|
-
await this.actions.refetchSessions();
|
|
84
70
|
} finally {
|
|
85
71
|
useChatThreadStore.getState().setSnapshot({ isDeletePending: false });
|
|
86
72
|
}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { ChatRunStatusManager } from '@/components/chat/managers/chat-run-status.manager';
|
|
2
1
|
import { ChatSessionListManager } from '@/components/chat/managers/chat-session-list.manager';
|
|
3
2
|
import { ChatStreamActionsManager } from '@/components/chat/managers/chat-stream-actions.manager';
|
|
4
3
|
import { ChatUiManager } from '@/components/chat/managers/chat-ui.manager';
|
|
@@ -14,7 +13,6 @@ export class NcpChatPresenter {
|
|
|
14
13
|
this.chatStreamActionsManager,
|
|
15
14
|
() => this.getDraftSessionId()
|
|
16
15
|
);
|
|
17
|
-
chatRunStatusManager = new ChatRunStatusManager();
|
|
18
16
|
chatThreadManager = new NcpChatThreadManager(
|
|
19
17
|
this.chatUiManager,
|
|
20
18
|
this.chatSessionListManager,
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
adaptNcpMessageToUiMessage,
|
|
3
3
|
adaptNcpSessionSummary,
|
|
4
|
-
buildNcpSessionRunStatusByKey,
|
|
5
4
|
readNcpSessionPreferredThinking
|
|
6
5
|
} from '@/components/chat/ncp/ncp-session-adapter';
|
|
7
6
|
import type { NcpSessionSummaryView } from '@/api/types';
|
|
@@ -95,25 +94,3 @@ describe('readNcpSessionPreferredThinking', () => {
|
|
|
95
94
|
expect(thinking).toBe('high');
|
|
96
95
|
});
|
|
97
96
|
});
|
|
98
|
-
|
|
99
|
-
describe('buildNcpSessionRunStatusByKey', () => {
|
|
100
|
-
it('marks the active local session as running before the server summary catches up', () => {
|
|
101
|
-
const statuses = buildNcpSessionRunStatusByKey({
|
|
102
|
-
summaries: [createSummary({ sessionId: 'ncp-session-1', status: 'idle' })],
|
|
103
|
-
activeSessionId: 'ncp-session-1',
|
|
104
|
-
isLocallyRunning: true
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
expect(statuses.get('ncp-session-1')).toBe('running');
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
it('keeps persisted running sessions marked as running', () => {
|
|
111
|
-
const statuses = buildNcpSessionRunStatusByKey({
|
|
112
|
-
summaries: [createSummary({ sessionId: 'ncp-session-2', status: 'running' })],
|
|
113
|
-
activeSessionId: null,
|
|
114
|
-
isLocallyRunning: false
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
expect(statuses.get('ncp-session-2')).toBe('running');
|
|
118
|
-
});
|
|
119
|
-
});
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { ToolInvocationStatus, type UIMessage } from '@nextclaw/agent-chat';
|
|
2
2
|
import type { NcpMessagePart } from '@nextclaw/ncp';
|
|
3
3
|
import type { NcpMessageView, NcpSessionSummaryView, SessionEntryView, ThinkingLevel } from '@/api/types';
|
|
4
|
-
import type { SessionRunStatus } from '@/lib/session-run-status';
|
|
5
4
|
|
|
6
5
|
const THINKING_LEVEL_SET = new Set<string>(['off', 'minimal', 'low', 'medium', 'high', 'adaptive', 'xhigh']);
|
|
7
6
|
|
|
@@ -206,24 +205,6 @@ export function adaptNcpSessionSummaries(summaries: NcpSessionSummaryView[]): Se
|
|
|
206
205
|
return summaries.map(adaptNcpSessionSummary);
|
|
207
206
|
}
|
|
208
207
|
|
|
209
|
-
export function buildNcpSessionRunStatusByKey(params: {
|
|
210
|
-
summaries: readonly NcpSessionSummaryView[];
|
|
211
|
-
activeSessionId?: string | null;
|
|
212
|
-
isLocallyRunning?: boolean;
|
|
213
|
-
}): Map<string, SessionRunStatus> {
|
|
214
|
-
const map = new Map<string, SessionRunStatus>();
|
|
215
|
-
for (const summary of params.summaries) {
|
|
216
|
-
if (summary.status === 'running') {
|
|
217
|
-
map.set(summary.sessionId, 'running');
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
const activeSessionId = readOptionalString(params.activeSessionId);
|
|
221
|
-
if (params.isLocallyRunning && activeSessionId) {
|
|
222
|
-
map.set(activeSessionId, 'running');
|
|
223
|
-
}
|
|
224
|
-
return map;
|
|
225
|
-
}
|
|
226
|
-
|
|
227
208
|
export function createNcpSessionId(): string {
|
|
228
209
|
return `ncp-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
|
|
229
210
|
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { useMemo } from 'react';
|
|
2
|
+
import type { SessionEntryView } from '@/api/types';
|
|
3
|
+
import { adaptNcpSessionSummaries } from '@/components/chat/ncp/ncp-session-adapter';
|
|
4
|
+
import { useChatSessionListStore } from '@/components/chat/stores/chat-session-list.store';
|
|
5
|
+
import { useNcpSessions } from '@/hooks/useConfig';
|
|
6
|
+
import type { SessionRunStatus } from '@/lib/session-run-status';
|
|
7
|
+
|
|
8
|
+
export type NcpSessionListItemView = {
|
|
9
|
+
session: SessionEntryView;
|
|
10
|
+
runStatus?: SessionRunStatus;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
function filterSessionsByQuery(sessions: readonly SessionEntryView[], query: string): SessionEntryView[] {
|
|
14
|
+
const normalizedQuery = query.trim().toLowerCase();
|
|
15
|
+
if (!normalizedQuery) {
|
|
16
|
+
return [...sessions];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return sessions.filter((session) => session.key.toLowerCase().includes(normalizedQuery));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function useNcpSessionListView(params: { limit?: number } = {}) {
|
|
23
|
+
const query = useChatSessionListStore((state) => state.snapshot.query);
|
|
24
|
+
const sessionsQuery = useNcpSessions({ limit: params.limit ?? 200 });
|
|
25
|
+
|
|
26
|
+
const items = useMemo<NcpSessionListItemView[]>(() => {
|
|
27
|
+
const summaries = sessionsQuery.data?.sessions ?? [];
|
|
28
|
+
const sessions = adaptNcpSessionSummaries(summaries);
|
|
29
|
+
const filteredSessions = filterSessionsByQuery(sessions, query);
|
|
30
|
+
const summaryBySessionId = new Map(summaries.map((summary) => [summary.sessionId, summary]));
|
|
31
|
+
|
|
32
|
+
return filteredSessions.map((session) => ({
|
|
33
|
+
session,
|
|
34
|
+
runStatus: summaryBySessionId.get(session.key)?.status === 'running' ? 'running' : undefined
|
|
35
|
+
}));
|
|
36
|
+
}, [query, sessionsQuery.data?.sessions]);
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
isLoading: sessionsQuery.isLoading,
|
|
40
|
+
items
|
|
41
|
+
};
|
|
42
|
+
}
|
|
@@ -3,7 +3,6 @@ import type { NcpDraftAttachment } from '@nextclaw/ncp-react';
|
|
|
3
3
|
import { createContext, useContext } from 'react';
|
|
4
4
|
import type { ReactNode } from 'react';
|
|
5
5
|
import type { SetStateAction } from 'react';
|
|
6
|
-
import type { ChatRunStatusManager } from '@/components/chat/managers/chat-run-status.manager';
|
|
7
6
|
import type { ChatSessionListManager } from '@/components/chat/managers/chat-session-list.manager';
|
|
8
7
|
import type { ChatStreamActionsManager } from '@/components/chat/managers/chat-stream-actions.manager';
|
|
9
8
|
import type { ChatUiManager } from '@/components/chat/managers/chat-ui.manager';
|
|
@@ -33,7 +32,6 @@ export type ChatInputManagerLike = {
|
|
|
33
32
|
};
|
|
34
33
|
|
|
35
34
|
export type ChatThreadManagerLike = {
|
|
36
|
-
bindActions: (patch: { refetchSessions?: () => Promise<unknown> }) => void;
|
|
37
35
|
syncSnapshot: (patch: Partial<ChatThreadSnapshot>) => void;
|
|
38
36
|
deleteSession: () => void;
|
|
39
37
|
createSession: () => void;
|
|
@@ -45,7 +43,6 @@ export type ChatPresenterLike = {
|
|
|
45
43
|
chatStreamActionsManager: ChatStreamActionsManager;
|
|
46
44
|
chatInputManager: ChatInputManagerLike;
|
|
47
45
|
chatSessionListManager: ChatSessionListManager;
|
|
48
|
-
chatRunStatusManager: ChatRunStatusManager;
|
|
49
46
|
chatThreadManager: ChatThreadManagerLike;
|
|
50
47
|
};
|
|
51
48
|
|
|
@@ -1,12 +1,8 @@
|
|
|
1
1
|
import { create } from 'zustand';
|
|
2
|
-
import type { SessionEntryView } from '@/api/types';
|
|
3
|
-
|
|
4
2
|
export type ChatSessionListSnapshot = {
|
|
5
|
-
sessions: SessionEntryView[];
|
|
6
3
|
selectedSessionKey: string | null;
|
|
7
4
|
selectedAgentId: string;
|
|
8
5
|
query: string;
|
|
9
|
-
isLoading: boolean;
|
|
10
6
|
};
|
|
11
7
|
|
|
12
8
|
type ChatSessionListStore = {
|
|
@@ -15,11 +11,9 @@ type ChatSessionListStore = {
|
|
|
15
11
|
};
|
|
16
12
|
|
|
17
13
|
const initialSnapshot: ChatSessionListSnapshot = {
|
|
18
|
-
sessions: [],
|
|
19
14
|
selectedSessionKey: null,
|
|
20
15
|
selectedAgentId: 'main',
|
|
21
|
-
query: ''
|
|
22
|
-
isLoading: false
|
|
16
|
+
query: ''
|
|
23
17
|
};
|
|
24
18
|
|
|
25
19
|
export const useChatSessionListStore = create<ChatSessionListStore>((set) => ({
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { create } from 'zustand';
|
|
2
2
|
import type { MutableRefObject } from 'react';
|
|
3
|
-
import type {
|
|
3
|
+
import type { NcpMessage } from '@nextclaw/ncp';
|
|
4
4
|
import type { ChatModelOption } from '@/components/chat/chat-input.types';
|
|
5
5
|
|
|
6
6
|
export type ChatThreadSnapshot = {
|
|
@@ -15,7 +15,7 @@ export type ChatThreadSnapshot = {
|
|
|
15
15
|
isDeletePending: boolean;
|
|
16
16
|
threadRef: MutableRefObject<HTMLDivElement | null> | null;
|
|
17
17
|
isHistoryLoading: boolean;
|
|
18
|
-
|
|
18
|
+
messages: readonly NcpMessage[];
|
|
19
19
|
isSending: boolean;
|
|
20
20
|
isAwaitingAssistantOutput: boolean;
|
|
21
21
|
};
|
|
@@ -37,7 +37,7 @@ const initialSnapshot: ChatThreadSnapshot = {
|
|
|
37
37
|
isDeletePending: false,
|
|
38
38
|
threadRef: null,
|
|
39
39
|
isHistoryLoading: false,
|
|
40
|
-
|
|
40
|
+
messages: [],
|
|
41
41
|
isSending: false,
|
|
42
42
|
isAwaitingAssistantOutput: false
|
|
43
43
|
};
|