@nextclaw/ui 0.12.25 → 0.12.26
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 +68 -0
- package/dist/assets/{channels-list-page-FJDuPwU6.js → channels-list-page-HgLgrEg4.js} +1 -1
- package/dist/assets/chat-page-DAKMFDrS.js +1 -0
- package/dist/assets/{desktop-kk7qvZ-v.js → desktop-DVUbOWbR.js} +1 -1
- package/dist/assets/{index-D-AAMKCt.js → index-Cuwst6cc.js} +34 -37
- package/dist/assets/index-dlcqieQ0.css +1 -0
- package/dist/assets/{marketplace-page-BrCLRIc4.js → marketplace-page-BeFbwxR-.js} +2 -2
- package/dist/assets/marketplace-page-CR4xq-TM.js +1 -0
- package/dist/assets/mcp-marketplace-page-DlRrSCj3.js +1 -0
- package/dist/assets/{mcp-marketplace-page-DIq_SpMe.js → mcp-marketplace-page-DwnaLNTx.js} +1 -1
- package/dist/assets/{model-config-Bc6VVnxy.js → model-config-L2l6YAlQ.js} +1 -1
- package/dist/assets/{providers-list-DN0tvISH.js → providers-list-DYAEunOp.js} +1 -1
- package/dist/assets/{runtime-config-page-CRWOwBbl.js → runtime-config-page-BdeU8PEK.js} +1 -1
- package/dist/assets/{search-config-C4c1yZSP.js → search-config-CQUhd5RU.js} +1 -1
- package/dist/assets/{secrets-config-zAF30YfO.js → secrets-config-D-NWlW9q.js} +1 -1
- package/dist/assets/{use-infinite-scroll-loader-Cvz8ZteY.js → use-infinite-scroll-loader-CFVdPpNv.js} +1 -1
- package/dist/index.html +3 -3
- package/package.json +9 -9
- package/src/features/agents/components/agents-page.test.tsx +1 -1
- package/src/features/agents/components/agents-page.tsx +1 -1
- package/src/features/chat/components/chat-session-workspace-panel.tsx +31 -45
- package/src/features/chat/components/chat-sidebar-session-item.tsx +7 -9
- package/src/features/chat/components/conversation/chat-conversation-header.test.tsx +5 -2
- package/src/features/chat/components/conversation/chat-conversation-header.tsx +2 -2
- package/src/features/chat/components/conversation/chat-conversation-panel.test.tsx +106 -78
- package/src/features/chat/components/conversation/chat-conversation-panel.tsx +172 -167
- package/src/features/chat/components/conversation/chat-input-bar.container.tsx +11 -1
- package/src/features/chat/components/conversation/session-header/chat-session-header-actions.tsx +2 -2
- package/src/features/chat/components/providers/chat-presenter.provider.tsx +2 -0
- package/src/features/chat/hooks/use-ncp-agent-runtime.test.tsx +147 -88
- package/src/features/chat/managers/ncp-chat-input.manager.test.ts +20 -0
- package/src/features/chat/managers/ncp-chat-input.manager.ts +18 -0
- package/src/features/chat/managers/ncp-chat-presenter.manager.ts +1 -0
- package/src/features/chat/pages/ncp-chat-page.tsx +4 -1
- package/src/features/chat/stores/chat-input.store.ts +3 -1
- package/src/features/chat/utils/ncp-chat-input-availability.utils.test.ts +1 -0
- package/src/platforms/desktop/components/desktop-app-shell.test.tsx +1 -0
- package/src/platforms/desktop/components/desktop-app-shell.tsx +1 -1
- package/dist/assets/chat-page-D1fMNBrT.js +0 -1
- package/dist/assets/index-DnBeV2Xm.css +0 -1
- package/dist/assets/marketplace-page-odDpPYEs.js +0 -1
- package/dist/assets/mcp-marketplace-page-CfbOBgKK.js +0 -1
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useMemo, useRef } from "react";
|
|
2
2
|
import { useStickyBottomScroll } from "@nextclaw/agent-chat-ui";
|
|
3
|
-
import type { ChatFileOpenActionViewModel } from "@nextclaw/agent-chat-ui";
|
|
4
3
|
import { ChatInputBarContainer } from "@/features/chat/components/conversation/chat-input-bar.container";
|
|
5
4
|
import { ChatMessageListContainer } from "@/features/chat/components/conversation/chat-message-list.container";
|
|
6
5
|
import {
|
|
@@ -8,7 +7,7 @@ import {
|
|
|
8
7
|
ChatParentSessionBanner,
|
|
9
8
|
} from "@/features/chat/components/conversation/chat-conversation-header";
|
|
10
9
|
import { ChatWelcome } from "@/features/chat/components/chat-welcome";
|
|
11
|
-
import { ChatSessionWorkspacePanel } from "@/features/chat";
|
|
10
|
+
import { ChatSessionWorkspacePanel } from "@/features/chat/components/chat-session-workspace-panel";
|
|
12
11
|
import { usePresenter } from "@/features/chat/components/providers/chat-presenter.provider";
|
|
13
12
|
import { resolveAgentRuntimeSessionType } from "@/features/chat/hooks/use-chat-session-type-state";
|
|
14
13
|
import { useChatInputStore } from "@/features/chat/stores/chat-input.store";
|
|
@@ -103,26 +102,17 @@ function ChatConversationSkeleton() {
|
|
|
103
102
|
);
|
|
104
103
|
}
|
|
105
104
|
|
|
106
|
-
type ChatThreadSnapshot = ReturnType<
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
105
|
+
type ChatThreadSnapshot = ReturnType<
|
|
106
|
+
typeof useChatThreadStore.getState
|
|
107
|
+
>["snapshot"];
|
|
108
|
+
type ChatConversationLayoutMode = "desktop" | "mobile";
|
|
109
|
+
|
|
110
|
+
function ChatConversationAlerts() {
|
|
111
|
+
const presenter = usePresenter();
|
|
112
|
+
const snapshot = useChatThreadStore((state) => state.snapshot);
|
|
113
|
+
const shouldShowProviderHint =
|
|
114
|
+
snapshot.isProviderStateResolved && snapshot.modelOptions.length === 0;
|
|
113
115
|
|
|
114
|
-
type ChatConversationAlertsProps = {
|
|
115
|
-
shouldShowProviderHint: boolean;
|
|
116
|
-
sessionTypeUnavailable: boolean;
|
|
117
|
-
sessionTypeUnavailableMessage: string | null;
|
|
118
|
-
onGoToProviders: () => void;
|
|
119
|
-
};
|
|
120
|
-
function ChatConversationAlerts({
|
|
121
|
-
shouldShowProviderHint,
|
|
122
|
-
sessionTypeUnavailable,
|
|
123
|
-
sessionTypeUnavailableMessage,
|
|
124
|
-
onGoToProviders,
|
|
125
|
-
}: ChatConversationAlertsProps) {
|
|
126
116
|
return (
|
|
127
117
|
<>
|
|
128
118
|
{shouldShowProviderHint ? (
|
|
@@ -132,17 +122,18 @@ function ChatConversationAlerts({
|
|
|
132
122
|
</span>
|
|
133
123
|
<button
|
|
134
124
|
type="button"
|
|
135
|
-
onClick={
|
|
125
|
+
onClick={presenter.chatThreadManager.goToProviders}
|
|
136
126
|
className="text-xs font-semibold text-amber-900 underline-offset-2 hover:underline"
|
|
137
127
|
>
|
|
138
128
|
{t("chatGoConfigureProvider")}
|
|
139
129
|
</button>
|
|
140
130
|
</div>
|
|
141
131
|
) : null}
|
|
142
|
-
{sessionTypeUnavailable &&
|
|
132
|
+
{snapshot.sessionTypeUnavailable &&
|
|
133
|
+
snapshot.sessionTypeUnavailableMessage?.trim() ? (
|
|
143
134
|
<div className="px-4 py-2.5 border-b border-amber-200/70 bg-amber-50/70 shrink-0 sm:px-5">
|
|
144
135
|
<span className="text-xs text-amber-800">
|
|
145
|
-
{sessionTypeUnavailableMessage}
|
|
136
|
+
{snapshot.sessionTypeUnavailableMessage}
|
|
146
137
|
</span>
|
|
147
138
|
</div>
|
|
148
139
|
) : null}
|
|
@@ -150,35 +141,59 @@ function ChatConversationAlerts({
|
|
|
150
141
|
);
|
|
151
142
|
}
|
|
152
143
|
|
|
153
|
-
type ChatConversationContentProps = {
|
|
154
|
-
snapshot: ChatThreadSnapshot;
|
|
155
|
-
availableAgents: NonNullable<ChatThreadSnapshot["availableAgents"]>;
|
|
156
|
-
hideEmptyHint: boolean;
|
|
157
|
-
showWelcome: boolean;
|
|
158
|
-
threadRef: ComponentProps<"div">["ref"];
|
|
159
|
-
onScroll: ComponentProps<"div">["onScroll"];
|
|
160
|
-
onCreateSession: () => void;
|
|
161
|
-
onSelectAgent: (agentId: string) => void;
|
|
162
|
-
onToolAction: ChatToolActionHandler;
|
|
163
|
-
onFileOpen: ChatFileOpenHandler;
|
|
164
|
-
};
|
|
165
|
-
|
|
166
144
|
function ChatConversationContent({
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
145
|
+
layoutMode,
|
|
146
|
+
}: {
|
|
147
|
+
layoutMode: ChatConversationLayoutMode;
|
|
148
|
+
}) {
|
|
149
|
+
const presenter = usePresenter();
|
|
150
|
+
const defaultSessionType = useChatInputStore(
|
|
151
|
+
(state) => state.snapshot.defaultSessionType,
|
|
152
|
+
);
|
|
153
|
+
const snapshot = useChatThreadStore((state) => state.snapshot);
|
|
154
|
+
const fallbackThreadRef = useRef<HTMLDivElement | null>(null);
|
|
155
|
+
const threadRef = snapshot.threadRef ?? fallbackThreadRef;
|
|
156
|
+
const availableAgents = snapshot.availableAgents ?? [];
|
|
157
|
+
const showWelcome =
|
|
158
|
+
!snapshot.canDeleteSession &&
|
|
159
|
+
!snapshot.hasSubmittedDraftMessage &&
|
|
160
|
+
snapshot.messages.length === 0 &&
|
|
161
|
+
!snapshot.isSending;
|
|
162
|
+
const hideEmptyHint =
|
|
163
|
+
snapshot.isHistoryLoading &&
|
|
164
|
+
snapshot.messages.length === 0 &&
|
|
165
|
+
!snapshot.isSending &&
|
|
166
|
+
!snapshot.isAwaitingAssistantOutput;
|
|
167
|
+
const resolveDraftAgent = (agentId: string) =>
|
|
168
|
+
availableAgents.find((agent) => agent.id === agentId) ?? null;
|
|
169
|
+
const createDraftSessionForAgent = () => {
|
|
170
|
+
const sessionType = resolveAgentRuntimeSessionType(
|
|
171
|
+
resolveDraftAgent(snapshot.agentId ?? "main"),
|
|
172
|
+
defaultSessionType,
|
|
173
|
+
);
|
|
174
|
+
presenter.chatSessionListManager.createSession(sessionType);
|
|
175
|
+
if (layoutMode === "mobile") presenter.chatUiManager.goToChatRoot();
|
|
176
|
+
};
|
|
177
|
+
const selectDraftAgent = (agentId: string) => {
|
|
178
|
+
presenter.chatSessionListManager.setSelectedAgentId(agentId);
|
|
179
|
+
presenter.chatInputManager.setPendingSessionType(
|
|
180
|
+
resolveAgentRuntimeSessionType(
|
|
181
|
+
resolveDraftAgent(agentId),
|
|
182
|
+
defaultSessionType,
|
|
183
|
+
),
|
|
184
|
+
);
|
|
185
|
+
};
|
|
186
|
+
const { onScroll } = useStickyBottomScroll({
|
|
187
|
+
scrollRef: threadRef,
|
|
188
|
+
resetKey: snapshot.sessionKey,
|
|
189
|
+
isLoading: snapshot.isHistoryLoading,
|
|
190
|
+
hasContent: snapshot.messages.length > 0,
|
|
191
|
+
contentVersion: snapshot.messages[snapshot.messages.length - 1] ?? null,
|
|
192
|
+
});
|
|
193
|
+
const hasMessages = snapshot.messages.length > 0;
|
|
178
194
|
const isAwaitingAssistantOutput =
|
|
179
|
-
snapshot.isSending && snapshot.isAwaitingAssistantOutput;
|
|
180
|
-
const shouldShowMessages =
|
|
181
|
-
snapshot.messages.length > 0 || isAwaitingAssistantOutput;
|
|
195
|
+
hasMessages && snapshot.isSending && snapshot.isAwaitingAssistantOutput;
|
|
196
|
+
const shouldShowMessages = hasMessages;
|
|
182
197
|
|
|
183
198
|
return (
|
|
184
199
|
<div
|
|
@@ -188,18 +203,18 @@ function ChatConversationContent({
|
|
|
188
203
|
>
|
|
189
204
|
{showWelcome ? (
|
|
190
205
|
<ChatWelcome
|
|
191
|
-
onCreateSession={
|
|
206
|
+
onCreateSession={createDraftSessionForAgent}
|
|
192
207
|
agents={availableAgents}
|
|
193
208
|
selectedAgentId={snapshot.agentId ?? "main"}
|
|
194
|
-
onSelectAgent={
|
|
209
|
+
onSelectAgent={selectDraftAgent}
|
|
195
210
|
/>
|
|
196
211
|
) : hideEmptyHint || !shouldShowMessages ? null : (
|
|
197
212
|
<div className="mx-auto w-full max-w-[min(1120px,100%)] px-4 py-4 sm:px-6 sm:py-5">
|
|
198
213
|
<ChatMessageListContainer
|
|
199
214
|
messages={snapshot.messages}
|
|
200
215
|
isSending={isAwaitingAssistantOutput}
|
|
201
|
-
onToolAction={
|
|
202
|
-
onFileOpen={
|
|
216
|
+
onToolAction={presenter.chatThreadManager.openSessionFromToolAction}
|
|
217
|
+
onFileOpen={presenter.chatThreadManager.openFilePreview}
|
|
203
218
|
/>
|
|
204
219
|
</div>
|
|
205
220
|
)}
|
|
@@ -216,21 +231,34 @@ function shouldShowWorkspacePanel(
|
|
|
216
231
|
if (snapshot.workspacePanelParentKey !== snapshot.sessionKey) {
|
|
217
232
|
return false;
|
|
218
233
|
}
|
|
219
|
-
return
|
|
234
|
+
return (
|
|
235
|
+
childSessionTabs.length > 0 ||
|
|
236
|
+
workspaceFileTabs.length > 0 ||
|
|
237
|
+
sessionCronJobCount > 0
|
|
238
|
+
);
|
|
220
239
|
}
|
|
221
240
|
|
|
222
241
|
function useSessionWorkspaceState(snapshot: ChatThreadSnapshot) {
|
|
223
242
|
const childSessionTabs = useMemo(
|
|
224
|
-
() =>
|
|
243
|
+
() =>
|
|
244
|
+
snapshot.childSessionTabs.filter(
|
|
245
|
+
(tab) => tab.parentSessionKey === snapshot.sessionKey,
|
|
246
|
+
),
|
|
225
247
|
[snapshot.childSessionTabs, snapshot.sessionKey],
|
|
226
248
|
);
|
|
227
249
|
const workspaceFileTabs = useMemo(
|
|
228
|
-
() =>
|
|
250
|
+
() =>
|
|
251
|
+
snapshot.workspaceFileTabs.filter(
|
|
252
|
+
(tab) => tab.parentSessionKey === snapshot.sessionKey,
|
|
253
|
+
),
|
|
229
254
|
[snapshot.sessionKey, snapshot.workspaceFileTabs],
|
|
230
255
|
);
|
|
231
256
|
const cronQuery = useCronJobs({ all: true });
|
|
232
257
|
const sessionCronJobs = useMemo(
|
|
233
|
-
() =>
|
|
258
|
+
() =>
|
|
259
|
+
(cronQuery.data?.jobs ?? []).filter((job) =>
|
|
260
|
+
isCronJobForSession(job, snapshot.sessionKey),
|
|
261
|
+
),
|
|
234
262
|
[cronQuery.data?.jobs, snapshot.sessionKey],
|
|
235
263
|
);
|
|
236
264
|
return {
|
|
@@ -246,65 +274,42 @@ function useSessionWorkspaceState(snapshot: ChatThreadSnapshot) {
|
|
|
246
274
|
};
|
|
247
275
|
}
|
|
248
276
|
|
|
249
|
-
|
|
250
|
-
|
|
277
|
+
function ChatParentSessionBannerContainer() {
|
|
278
|
+
const presenter = usePresenter();
|
|
279
|
+
const snapshot = useChatThreadStore((state) => state.snapshot);
|
|
280
|
+
return (
|
|
281
|
+
<ChatParentSessionBanner
|
|
282
|
+
parentSessionLabel={
|
|
283
|
+
snapshot.parentSessionKey ? (snapshot.parentSessionLabel ?? null) : null
|
|
284
|
+
}
|
|
285
|
+
onGoToParentSession={presenter.chatThreadManager.goToParentSession}
|
|
286
|
+
/>
|
|
287
|
+
);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function ChatConversationHeaderContainer({
|
|
291
|
+
layoutMode,
|
|
251
292
|
onBackToList,
|
|
252
293
|
}: {
|
|
253
|
-
layoutMode
|
|
294
|
+
layoutMode: ChatConversationLayoutMode;
|
|
254
295
|
onBackToList?: () => void;
|
|
255
296
|
}) {
|
|
256
297
|
const presenter = usePresenter();
|
|
257
|
-
const defaultSessionType = useChatInputStore((state) => state.snapshot.defaultSessionType);
|
|
258
298
|
const snapshot = useChatThreadStore((state) => state.snapshot);
|
|
259
|
-
const
|
|
260
|
-
const threadRef = snapshot.threadRef ?? fallbackThreadRef;
|
|
261
|
-
const { childSessionTabs, workspaceFileTabs, sessionCronJobs, showWorkspacePanel } =
|
|
299
|
+
const { childSessionTabs, sessionCronJobs } =
|
|
262
300
|
useSessionWorkspaceState(snapshot);
|
|
263
301
|
const shouldShowSessionHeader = Boolean(
|
|
264
302
|
snapshot.sessionKey || snapshot.sessionTypeLabel,
|
|
265
303
|
);
|
|
266
304
|
const sessionHeaderTitle =
|
|
267
305
|
snapshot.sessionDisplayName ||
|
|
268
|
-
(snapshot.canDeleteSession && snapshot.sessionKey
|
|
306
|
+
(snapshot.canDeleteSession && snapshot.sessionKey
|
|
307
|
+
? snapshot.sessionKey
|
|
308
|
+
: null) ||
|
|
269
309
|
t("chatSidebarNewTask");
|
|
270
310
|
const normalizedAgentId = snapshot.agentId?.trim() ?? "";
|
|
271
311
|
const shouldShowHeaderAgentAvatar =
|
|
272
|
-
normalizedAgentId.length > 0 &&
|
|
273
|
-
normalizedAgentId.toLowerCase() !== "main";
|
|
274
|
-
|
|
275
|
-
const showWelcome =
|
|
276
|
-
!snapshot.canDeleteSession &&
|
|
277
|
-
!snapshot.hasSubmittedDraftMessage &&
|
|
278
|
-
snapshot.messages.length === 0 &&
|
|
279
|
-
!snapshot.isSending;
|
|
280
|
-
const hasConfiguredModel = snapshot.modelOptions.length > 0;
|
|
281
|
-
const shouldShowProviderHint =
|
|
282
|
-
snapshot.isProviderStateResolved && !hasConfiguredModel;
|
|
283
|
-
const hideEmptyHint =
|
|
284
|
-
snapshot.isHistoryLoading &&
|
|
285
|
-
snapshot.messages.length === 0 &&
|
|
286
|
-
!snapshot.isSending &&
|
|
287
|
-
!snapshot.isAwaitingAssistantOutput;
|
|
288
|
-
const availableAgents = snapshot.availableAgents ?? [];
|
|
289
|
-
const resolveDraftAgent = (agentId: string) =>
|
|
290
|
-
availableAgents.find((agent) => agent.id === agentId) ?? null;
|
|
291
|
-
const createDraftSessionForAgent = () => {
|
|
292
|
-
const sessionType = resolveAgentRuntimeSessionType(
|
|
293
|
-
resolveDraftAgent(snapshot.agentId ?? "main"),
|
|
294
|
-
defaultSessionType,
|
|
295
|
-
);
|
|
296
|
-
presenter.chatSessionListManager.createSession(sessionType);
|
|
297
|
-
if (layoutMode === "mobile") presenter.chatUiManager.goToChatRoot();
|
|
298
|
-
};
|
|
299
|
-
const selectDraftAgent = (agentId: string) => {
|
|
300
|
-
presenter.chatSessionListManager.setSelectedAgentId(agentId);
|
|
301
|
-
presenter.chatInputManager.setPendingSessionType(
|
|
302
|
-
resolveAgentRuntimeSessionType(resolveDraftAgent(agentId), defaultSessionType),
|
|
303
|
-
);
|
|
304
|
-
};
|
|
305
|
-
const openFilePreview = (action: ChatFileOpenActionViewModel) => {
|
|
306
|
-
presenter.chatThreadManager.openFilePreview(action);
|
|
307
|
-
};
|
|
312
|
+
normalizedAgentId.length > 0 && normalizedAgentId.toLowerCase() !== "main";
|
|
308
313
|
const openChildSessions = () => {
|
|
309
314
|
if (!snapshot.sessionKey) {
|
|
310
315
|
return;
|
|
@@ -321,13 +326,64 @@ export function ChatConversationPanel({
|
|
|
321
326
|
presenter.chatThreadManager.openSessionCronPanel(snapshot.sessionKey);
|
|
322
327
|
};
|
|
323
328
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
329
|
+
return (
|
|
330
|
+
<ChatConversationHeader
|
|
331
|
+
snapshot={snapshot}
|
|
332
|
+
childSessionCount={childSessionTabs.length}
|
|
333
|
+
sessionCronJobCount={sessionCronJobs.length}
|
|
334
|
+
layoutMode={layoutMode}
|
|
335
|
+
normalizedAgentId={normalizedAgentId}
|
|
336
|
+
sessionHeaderTitle={sessionHeaderTitle}
|
|
337
|
+
shouldShowHeaderAgentAvatar={shouldShowHeaderAgentAvatar}
|
|
338
|
+
shouldShowSessionHeader={shouldShowSessionHeader}
|
|
339
|
+
onBackToList={onBackToList}
|
|
340
|
+
onOpenChildSessions={openChildSessions}
|
|
341
|
+
onOpenSessionCronJobs={openSessionCronJobs}
|
|
342
|
+
onDeleteSession={presenter.chatThreadManager.deleteSession}
|
|
343
|
+
/>
|
|
344
|
+
);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
function ChatSessionWorkspacePanelContainer({
|
|
348
|
+
layoutMode,
|
|
349
|
+
}: {
|
|
350
|
+
layoutMode: ChatConversationLayoutMode;
|
|
351
|
+
}) {
|
|
352
|
+
const snapshot = useChatThreadStore((state) => state.snapshot);
|
|
353
|
+
const {
|
|
354
|
+
childSessionTabs,
|
|
355
|
+
workspaceFileTabs,
|
|
356
|
+
sessionCronJobs,
|
|
357
|
+
showWorkspacePanel,
|
|
358
|
+
} = useSessionWorkspaceState(snapshot);
|
|
359
|
+
|
|
360
|
+
if (!showWorkspacePanel) {
|
|
361
|
+
return null;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
return (
|
|
365
|
+
<ChatSessionWorkspacePanel
|
|
366
|
+
sessionKey={snapshot.sessionKey}
|
|
367
|
+
childSessionTabs={childSessionTabs}
|
|
368
|
+
activeChildSessionKey={snapshot.activeChildSessionKey ?? null}
|
|
369
|
+
workspaceFileTabs={workspaceFileTabs}
|
|
370
|
+
activeWorkspaceFileKey={snapshot.activeWorkspaceFileKey ?? null}
|
|
371
|
+
activePanelKind={snapshot.activeWorkspacePanelKind ?? null}
|
|
372
|
+
sessionCronJobs={sessionCronJobs}
|
|
373
|
+
sessionProjectRoot={snapshot.sessionProjectRoot ?? null}
|
|
374
|
+
displayMode={layoutMode === "mobile" ? "overlay" : "docked"}
|
|
375
|
+
/>
|
|
376
|
+
);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
export function ChatConversationPanel({
|
|
380
|
+
layoutMode = "desktop",
|
|
381
|
+
onBackToList,
|
|
382
|
+
}: {
|
|
383
|
+
layoutMode?: ChatConversationLayoutMode;
|
|
384
|
+
onBackToList?: () => void;
|
|
385
|
+
}) {
|
|
386
|
+
const snapshot = useChatThreadStore((state) => state.snapshot);
|
|
331
387
|
|
|
332
388
|
if (!snapshot.isProviderStateResolved) {
|
|
333
389
|
return <ChatConversationSkeleton />;
|
|
@@ -336,68 +392,17 @@ export function ChatConversationPanel({
|
|
|
336
392
|
return (
|
|
337
393
|
<section className="flex-1 min-h-0 flex overflow-hidden bg-gradient-to-b from-gray-50/60 to-white">
|
|
338
394
|
<div className="flex min-h-0 flex-1 flex-col overflow-hidden">
|
|
339
|
-
<
|
|
340
|
-
|
|
341
|
-
snapshot.parentSessionKey ? (snapshot.parentSessionLabel ?? null) : null
|
|
342
|
-
}
|
|
343
|
-
onGoToParentSession={presenter.chatThreadManager.goToParentSession}
|
|
344
|
-
/>
|
|
345
|
-
<ChatConversationHeader
|
|
346
|
-
snapshot={snapshot}
|
|
347
|
-
childSessionCount={childSessionTabs.length}
|
|
348
|
-
sessionCronJobCount={sessionCronJobs.length}
|
|
395
|
+
<ChatParentSessionBannerContainer />
|
|
396
|
+
<ChatConversationHeaderContainer
|
|
349
397
|
layoutMode={layoutMode}
|
|
350
|
-
normalizedAgentId={normalizedAgentId}
|
|
351
|
-
sessionHeaderTitle={sessionHeaderTitle}
|
|
352
|
-
shouldShowHeaderAgentAvatar={shouldShowHeaderAgentAvatar}
|
|
353
|
-
shouldShowSessionHeader={shouldShowSessionHeader}
|
|
354
398
|
onBackToList={onBackToList}
|
|
355
|
-
onOpenChildSessions={openChildSessions}
|
|
356
|
-
onOpenSessionCronJobs={openSessionCronJobs}
|
|
357
|
-
onDeleteSession={presenter.chatThreadManager.deleteSession}
|
|
358
399
|
/>
|
|
359
|
-
<ChatConversationAlerts
|
|
360
|
-
|
|
361
|
-
sessionTypeUnavailable={snapshot.sessionTypeUnavailable}
|
|
362
|
-
sessionTypeUnavailableMessage={snapshot.sessionTypeUnavailableMessage ?? null}
|
|
363
|
-
onGoToProviders={presenter.chatThreadManager.goToProviders}
|
|
364
|
-
/>
|
|
365
|
-
<ChatConversationContent
|
|
366
|
-
snapshot={snapshot}
|
|
367
|
-
availableAgents={availableAgents}
|
|
368
|
-
hideEmptyHint={hideEmptyHint}
|
|
369
|
-
showWelcome={showWelcome}
|
|
370
|
-
threadRef={threadRef}
|
|
371
|
-
onScroll={handleScroll}
|
|
372
|
-
onCreateSession={createDraftSessionForAgent}
|
|
373
|
-
onSelectAgent={selectDraftAgent}
|
|
374
|
-
onToolAction={presenter.chatThreadManager.openSessionFromToolAction}
|
|
375
|
-
onFileOpen={openFilePreview}
|
|
376
|
-
/>
|
|
377
|
-
|
|
400
|
+
<ChatConversationAlerts />
|
|
401
|
+
<ChatConversationContent layoutMode={layoutMode} />
|
|
378
402
|
<ChatInputBarContainer />
|
|
379
403
|
</div>
|
|
380
404
|
|
|
381
|
-
{
|
|
382
|
-
<ChatSessionWorkspacePanel
|
|
383
|
-
childSessionTabs={childSessionTabs}
|
|
384
|
-
activeChildSessionKey={snapshot.activeChildSessionKey ?? null}
|
|
385
|
-
workspaceFileTabs={workspaceFileTabs}
|
|
386
|
-
activeWorkspaceFileKey={snapshot.activeWorkspaceFileKey ?? null}
|
|
387
|
-
activePanelKind={snapshot.activeWorkspacePanelKind ?? null}
|
|
388
|
-
sessionCronJobs={sessionCronJobs}
|
|
389
|
-
sessionProjectRoot={snapshot.sessionProjectRoot ?? null}
|
|
390
|
-
displayMode={layoutMode === "mobile" ? "overlay" : "docked"}
|
|
391
|
-
onSelectSession={presenter.chatThreadManager.selectChildSessionDetail}
|
|
392
|
-
onSelectFile={presenter.chatThreadManager.selectWorkspaceFile}
|
|
393
|
-
onCloseFile={presenter.chatThreadManager.closeWorkspaceFile}
|
|
394
|
-
onSelectCronJobs={() => snapshot.sessionKey ? presenter.chatThreadManager.openSessionCronPanel(snapshot.sessionKey) : undefined}
|
|
395
|
-
onClose={presenter.chatThreadManager.closeWorkspacePanel}
|
|
396
|
-
onBackToParent={presenter.chatThreadManager.goToParentSession}
|
|
397
|
-
onToolAction={presenter.chatThreadManager.openSessionFromToolAction}
|
|
398
|
-
onFileOpen={openFilePreview}
|
|
399
|
-
/>
|
|
400
|
-
) : null}
|
|
405
|
+
<ChatSessionWorkspacePanelContainer layoutMode={layoutMode} />
|
|
401
406
|
</section>
|
|
402
407
|
);
|
|
403
408
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useCallback, useMemo, useRef, useState, type ChangeEvent, type RefObject } from 'react';
|
|
1
|
+
import { useCallback, useEffect, useMemo, useRef, useState, type ChangeEvent, type RefObject } from 'react';
|
|
2
2
|
import { ChatInputBar, type ChatInputBarHandle } from '@nextclaw/agent-chat-ui';
|
|
3
3
|
import { DEFAULT_NCP_ATTACHMENT_MAX_BYTES, uploadFilesAsNcpDraftAttachments } from '@nextclaw/ncp-react';
|
|
4
4
|
import { uploadNcpAssets } from '@/shared/lib/api';
|
|
@@ -234,6 +234,16 @@ export function ChatInputBarContainer() {
|
|
|
234
234
|
? t('chatStopPreparing')
|
|
235
235
|
: snapshot.stopDisabledReason?.trim() || t('chatStopUnavailable');
|
|
236
236
|
const { handleFilesAdd, handleFileInputChange } = useChatInputBarAttachments({ attachmentSupported, inputBarRef, presenter });
|
|
237
|
+
useEffect(() => {
|
|
238
|
+
const request = snapshot.composerFocusRequest;
|
|
239
|
+
if (!request) {
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
if (request.placement === 'end') {
|
|
243
|
+
inputBarRef.current?.focusComposerAtEnd();
|
|
244
|
+
}
|
|
245
|
+
presenter.chatInputManager.consumeComposerFocusRequest(request.id);
|
|
246
|
+
}, [presenter.chatInputManager, snapshot.composerFocusRequest]);
|
|
237
247
|
const toolbarSelects = buildToolbarSelects({
|
|
238
248
|
allModelsLabel: labels.allModelsLabel,
|
|
239
249
|
hasModelOptions,
|
package/src/features/chat/components/conversation/session-header/chat-session-header-actions.tsx
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useState } from 'react';
|
|
2
|
-
import { AlarmClock, FolderOpen, GitBranch,
|
|
2
|
+
import { AlarmClock, FolderOpen, GitBranch, MoreVertical, Trash2 } from 'lucide-react';
|
|
3
3
|
import { Button } from '@/shared/components/ui/button';
|
|
4
4
|
import { Popover, PopoverContent, PopoverTrigger } from '@/shared/components/ui/popover';
|
|
5
5
|
import { useChatSessionProject } from '@/features/chat/hooks/use-chat-session-project';
|
|
@@ -94,7 +94,7 @@ export function ChatSessionHeaderActions({
|
|
|
94
94
|
aria-label={t('chatSessionMoreActions')}
|
|
95
95
|
disabled={isBusy}
|
|
96
96
|
>
|
|
97
|
-
<
|
|
97
|
+
<MoreVertical className="h-4 w-4" />
|
|
98
98
|
</Button>
|
|
99
99
|
</PopoverTrigger>
|
|
100
100
|
<PopoverContent align="end" className="w-56 p-2">
|
|
@@ -10,6 +10,8 @@ import type { ChatThreadSnapshot } from '@/features/chat/stores/chat-thread.stor
|
|
|
10
10
|
export type ChatInputManagerLike = {
|
|
11
11
|
syncSnapshot: (patch: Record<string, unknown>) => void;
|
|
12
12
|
setDraft: (next: SetStateAction<string>) => void;
|
|
13
|
+
requestComposerFocusAtEnd: () => void;
|
|
14
|
+
consumeComposerFocusRequest: (requestId: number) => void;
|
|
13
15
|
setComposerNodes: (next: SetStateAction<ChatComposerNode[]>) => void;
|
|
14
16
|
addAttachments?: (attachments: NcpDraftAttachment[]) => NcpDraftAttachment[];
|
|
15
17
|
restoreComposerState?: (nodes: ChatComposerNode[], attachments: NcpDraftAttachment[]) => void;
|