@nextclaw/ui 0.7.0 → 0.9.0
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 +30 -0
- package/dist/assets/ChannelsList-C7F_As4r.js +1 -0
- package/dist/assets/ChatPage-Oo7-OUsx.js +37 -0
- package/dist/assets/{DocBrowser-B9ws5JL7.js → DocBrowser-Dsd8Dlq8.js} +1 -1
- package/dist/assets/{LogoBadge-DvGAzkZ3.js → LogoBadge-2ChEc_oz.js} +1 -1
- package/dist/assets/MarketplacePage-BXck6-X3.js +49 -0
- package/dist/assets/{ModelConfig-BL_HsOsm.js → ModelConfig-CgHRSD0b.js} +1 -1
- package/dist/assets/ProvidersList-PPfZucvS.js +1 -0
- package/dist/assets/RuntimeConfig-ClLEKNTN.js +1 -0
- package/dist/assets/{SearchConfig-BhaI0fUf.js → SearchConfig-CuXVCbrf.js} +1 -1
- package/dist/assets/{SecretsConfig-CFoimOh9.js → SecretsConfig-udJz6Ake.js} +2 -2
- package/dist/assets/SessionsConfig-C1XnFfiC.js +2 -0
- package/dist/assets/{session-run-status-TkIuGbVw.js → chat-message-BETwXLD4.js} +3 -3
- package/dist/assets/{index-uMsNsQX6.js → index-COJdlL0e.js} +1 -1
- package/dist/assets/index-CsvP4CER.js +8 -0
- package/dist/assets/index-D-bXl7qL.css +1 -0
- package/dist/assets/{label-D8ly4a2P.js → label-BGL-ztxh.js} +1 -1
- package/dist/assets/{page-layout-BSYfvwbp.js → page-layout-aw88k7tG.js} +1 -1
- package/dist/assets/popover-DyEvzhmV.js +1 -0
- package/dist/assets/security-config-BuPAQn82.js +1 -0
- package/dist/assets/skeleton-drzO_tdU.js +1 -0
- package/dist/assets/{switch-Ce_g9lpN.js → switch-BK8jIzto.js} +1 -1
- package/dist/assets/{tabs-custom-Cf5azvT5.js → tabs-custom-Da3cEOji.js} +1 -1
- package/dist/assets/{useConfirmDialog-A8Ek8Wu7.js → useConfirmDialog-z0CE92iS.js} +2 -2
- package/dist/assets/{vendor-B7ozqnFC.js → vendor-CkJHmX1g.js} +65 -70
- package/dist/index.html +3 -3
- package/package.json +5 -2
- package/src/api/config.ts +9 -0
- package/src/api/ncp-session.ts +50 -0
- package/src/api/types.ts +20 -0
- package/src/components/chat/ChatConversationPanel.test.tsx +65 -0
- package/src/components/chat/ChatConversationPanel.tsx +21 -12
- package/src/components/chat/ChatPage.tsx +10 -324
- package/src/components/chat/ChatSidebar.test.tsx +203 -0
- package/src/components/chat/ChatSidebar.tsx +97 -7
- package/src/components/chat/adapters/chat-message.adapter.test.ts +132 -81
- package/src/components/chat/adapters/chat-message.adapter.ts +27 -9
- package/src/components/chat/chat-chain.test.ts +22 -0
- package/src/components/chat/chat-chain.ts +23 -0
- package/src/components/chat/chat-page-data.ts +30 -1
- package/src/components/chat/chat-page-runtime.test.ts +181 -0
- package/src/components/chat/chat-page-runtime.ts +101 -15
- package/src/components/chat/chat-page-shell.tsx +103 -0
- package/src/components/chat/chat-session-preference-sync.test.ts +62 -0
- package/src/components/chat/chat-session-preference-sync.ts +75 -0
- package/src/components/chat/containers/chat-input-bar.container.tsx +0 -22
- package/src/components/chat/containers/chat-message-list.container.tsx +34 -26
- package/src/components/chat/legacy/LegacyChatPage.tsx +252 -0
- package/src/components/chat/managers/chat-input.manager.ts +5 -0
- package/src/components/chat/managers/chat-session-list.manager.test.ts +39 -0
- package/src/components/chat/managers/chat-session-list.manager.ts +9 -3
- package/src/components/chat/ncp/NcpChatPage.tsx +381 -0
- package/src/components/chat/ncp/ncp-chat-input.manager.ts +179 -0
- package/src/components/chat/ncp/ncp-chat-page-data.ts +166 -0
- package/src/components/chat/ncp/ncp-chat-thread.manager.ts +89 -0
- package/src/components/chat/ncp/ncp-chat.presenter.ts +33 -0
- package/src/components/chat/ncp/ncp-session-adapter.test.ts +75 -0
- package/src/components/chat/ncp/ncp-session-adapter.ts +214 -0
- package/src/components/chat/presenter/chat-presenter-context.tsx +43 -4
- package/src/components/chat/stores/chat-thread.store.ts +2 -0
- package/src/components/chat/useChatSessionTypeState.test.tsx +58 -0
- package/src/components/chat/useChatSessionTypeState.ts +25 -8
- package/src/hooks/use-ncp-chat-session-types.ts +11 -0
- package/src/hooks/useConfig.ts +41 -1
- package/src/hooks/useMarketplace.ts +7 -4
- package/src/hooks/useWebSocket.ts +23 -2
- package/src/lib/i18n.ts +1 -1
- package/tailwind.config.js +8 -3
- package/tsconfig.json +4 -1
- package/dist/assets/ChannelsList-DF2U-LY1.js +0 -1
- package/dist/assets/ChatPage-BX39y0U5.js +0 -36
- package/dist/assets/MarketplacePage-DG5mHWJ8.js +0 -49
- package/dist/assets/ProvidersList-CH5z00YT.js +0 -1
- package/dist/assets/RuntimeConfig-BplBgkwo.js +0 -1
- package/dist/assets/SessionsConfig-BHTAYn9T.js +0 -2
- package/dist/assets/index-BLeJkJ0o.css +0 -1
- package/dist/assets/index-DK4TS5ev.js +0 -8
- package/dist/assets/index-X5J6Mm--.js +0 -1
- package/dist/assets/security-config-DlKEYHNN.js +0 -1
- package/dist/assets/skeleton-CWbsNx2h.js +0 -1
package/dist/index.html
CHANGED
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
<link rel="icon" type="image/svg+xml" href="/logo.svg" />
|
|
7
7
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
8
8
|
<title>NextClaw - 系统配置</title>
|
|
9
|
-
<script type="module" crossorigin src="/assets/index-
|
|
10
|
-
<link rel="modulepreload" crossorigin href="/assets/vendor-
|
|
11
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
9
|
+
<script type="module" crossorigin src="/assets/index-CsvP4CER.js"></script>
|
|
10
|
+
<link rel="modulepreload" crossorigin href="/assets/vendor-CkJHmX1g.js">
|
|
11
|
+
<link rel="stylesheet" crossorigin href="/assets/index-D-bXl7qL.css">
|
|
12
12
|
</head>
|
|
13
13
|
|
|
14
14
|
<body>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nextclaw/ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -27,7 +27,10 @@
|
|
|
27
27
|
"tailwind-merge": "^2.5.4",
|
|
28
28
|
"zod": "^3.23.8",
|
|
29
29
|
"zustand": "^5.0.2",
|
|
30
|
-
"@nextclaw/agent-
|
|
30
|
+
"@nextclaw/ncp-http-agent-client": "0.3.0",
|
|
31
|
+
"@nextclaw/agent-chat-ui": "0.2.0",
|
|
32
|
+
"@nextclaw/ncp-react": "0.3.1",
|
|
33
|
+
"@nextclaw/ncp": "0.3.0",
|
|
31
34
|
"@nextclaw/agent-chat": "0.1.1"
|
|
32
35
|
},
|
|
33
36
|
"devDependencies": {
|
package/src/api/config.ts
CHANGED
|
@@ -601,6 +601,15 @@ export async function fetchChatSessionTypes(): Promise<ChatSessionTypesView> {
|
|
|
601
601
|
return response.data;
|
|
602
602
|
}
|
|
603
603
|
|
|
604
|
+
// GET /api/ncp/session-types
|
|
605
|
+
export async function fetchNcpChatSessionTypes(): Promise<ChatSessionTypesView> {
|
|
606
|
+
const response = await api.get<ChatSessionTypesView>('/api/ncp/session-types');
|
|
607
|
+
if (!response.ok) {
|
|
608
|
+
throw new Error(response.error.message);
|
|
609
|
+
}
|
|
610
|
+
return response.data;
|
|
611
|
+
}
|
|
612
|
+
|
|
604
613
|
// POST /api/chat/turn/stop
|
|
605
614
|
export async function stopChatTurn(data: ChatTurnStopRequest): Promise<ChatTurnStopResult> {
|
|
606
615
|
const response = await api.post<ChatTurnStopResult>('/api/chat/turn/stop', data);
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { api } from './client';
|
|
2
|
+
import type { NcpSessionMessagesView, NcpSessionsListView, NcpSessionSummaryView, SessionPatchUpdate } from './types';
|
|
3
|
+
|
|
4
|
+
// GET /api/ncp/sessions
|
|
5
|
+
export async function fetchNcpSessions(params?: { limit?: number }): Promise<NcpSessionsListView> {
|
|
6
|
+
const query = new URLSearchParams();
|
|
7
|
+
if (typeof params?.limit === 'number' && Number.isFinite(params.limit)) {
|
|
8
|
+
query.set('limit', String(Math.max(1, Math.trunc(params.limit))));
|
|
9
|
+
}
|
|
10
|
+
const suffix = query.toString();
|
|
11
|
+
const response = await api.get<NcpSessionsListView>(suffix ? `/api/ncp/sessions?${suffix}` : '/api/ncp/sessions');
|
|
12
|
+
if (!response.ok) {
|
|
13
|
+
throw new Error(response.error.message);
|
|
14
|
+
}
|
|
15
|
+
return response.data;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// GET /api/ncp/sessions/:sessionId/messages
|
|
19
|
+
export async function fetchNcpSessionMessages(sessionId: string, limit = 200): Promise<NcpSessionMessagesView> {
|
|
20
|
+
const response = await api.get<NcpSessionMessagesView>(
|
|
21
|
+
`/api/ncp/sessions/${encodeURIComponent(sessionId)}/messages?limit=${Math.max(1, Math.trunc(limit))}`
|
|
22
|
+
);
|
|
23
|
+
if (!response.ok) {
|
|
24
|
+
throw new Error(response.error.message);
|
|
25
|
+
}
|
|
26
|
+
return response.data;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// PUT /api/ncp/sessions/:sessionId
|
|
30
|
+
export async function updateNcpSession(
|
|
31
|
+
sessionId: string,
|
|
32
|
+
data: SessionPatchUpdate
|
|
33
|
+
): Promise<NcpSessionSummaryView> {
|
|
34
|
+
const response = await api.put<NcpSessionSummaryView>(`/api/ncp/sessions/${encodeURIComponent(sessionId)}`, data);
|
|
35
|
+
if (!response.ok) {
|
|
36
|
+
throw new Error(response.error.message);
|
|
37
|
+
}
|
|
38
|
+
return response.data;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// DELETE /api/ncp/sessions/:sessionId
|
|
42
|
+
export async function deleteNcpSession(sessionId: string): Promise<{ deleted: boolean; sessionId: string }> {
|
|
43
|
+
const response = await api.delete<{ deleted: boolean; sessionId: string }>(
|
|
44
|
+
`/api/ncp/sessions/${encodeURIComponent(sessionId)}`
|
|
45
|
+
);
|
|
46
|
+
if (!response.ok) {
|
|
47
|
+
throw new Error(response.error.message);
|
|
48
|
+
}
|
|
49
|
+
return response.data;
|
|
50
|
+
}
|
package/src/api/types.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { NcpMessage, NcpSessionStatus, NcpSessionSummary } from '@nextclaw/ncp';
|
|
2
|
+
|
|
1
3
|
// API Types - matching backend response format
|
|
2
4
|
export type ApiError = {
|
|
3
5
|
code: string;
|
|
@@ -261,9 +263,27 @@ export type SessionHistoryView = {
|
|
|
261
263
|
events: SessionEventView[];
|
|
262
264
|
};
|
|
263
265
|
|
|
266
|
+
export type NcpSessionSummaryView = NcpSessionSummary;
|
|
267
|
+
|
|
268
|
+
export type NcpSessionsListView = {
|
|
269
|
+
sessions: NcpSessionSummaryView[];
|
|
270
|
+
total: number;
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
export type NcpMessageView = NcpMessage;
|
|
274
|
+
|
|
275
|
+
export type NcpSessionMessagesView = {
|
|
276
|
+
sessionId: string;
|
|
277
|
+
messages: NcpMessageView[];
|
|
278
|
+
total: number;
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
export type NcpSessionStatusView = NcpSessionStatus;
|
|
282
|
+
|
|
264
283
|
export type SessionPatchUpdate = {
|
|
265
284
|
label?: string | null;
|
|
266
285
|
preferredModel?: string | null;
|
|
286
|
+
preferredThinking?: ThinkingLevel | null;
|
|
267
287
|
sessionType?: string | null;
|
|
268
288
|
clearHistory?: boolean;
|
|
269
289
|
};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { render, screen } from '@testing-library/react';
|
|
2
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
3
|
+
import { ChatConversationPanel } from '@/components/chat/ChatConversationPanel';
|
|
4
|
+
import { useChatThreadStore } from '@/components/chat/stores/chat-thread.store';
|
|
5
|
+
|
|
6
|
+
const mocks = vi.hoisted(() => ({
|
|
7
|
+
deleteSession: vi.fn(),
|
|
8
|
+
goToProviders: vi.fn()
|
|
9
|
+
}));
|
|
10
|
+
|
|
11
|
+
vi.mock('@nextclaw/agent-chat-ui', () => ({
|
|
12
|
+
useStickyBottomScroll: () => ({
|
|
13
|
+
onScroll: vi.fn()
|
|
14
|
+
})
|
|
15
|
+
}));
|
|
16
|
+
|
|
17
|
+
vi.mock('@/components/chat/nextclaw', () => ({
|
|
18
|
+
ChatInputBarContainer: () => <div data-testid="chat-input-bar" />,
|
|
19
|
+
ChatMessageListContainer: () => <div data-testid="chat-message-list" />
|
|
20
|
+
}));
|
|
21
|
+
|
|
22
|
+
vi.mock('@/components/chat/ChatWelcome', () => ({
|
|
23
|
+
ChatWelcome: () => <div data-testid="chat-welcome" />
|
|
24
|
+
}));
|
|
25
|
+
|
|
26
|
+
vi.mock('@/components/chat/presenter/chat-presenter-context', () => ({
|
|
27
|
+
usePresenter: () => ({
|
|
28
|
+
chatThreadManager: {
|
|
29
|
+
deleteSession: mocks.deleteSession,
|
|
30
|
+
goToProviders: mocks.goToProviders,
|
|
31
|
+
createSession: vi.fn()
|
|
32
|
+
}
|
|
33
|
+
})
|
|
34
|
+
}));
|
|
35
|
+
|
|
36
|
+
describe('ChatConversationPanel', () => {
|
|
37
|
+
beforeEach(() => {
|
|
38
|
+
mocks.deleteSession.mockReset();
|
|
39
|
+
mocks.goToProviders.mockReset();
|
|
40
|
+
useChatThreadStore.setState({
|
|
41
|
+
snapshot: {
|
|
42
|
+
...useChatThreadStore.getState().snapshot,
|
|
43
|
+
isProviderStateResolved: true,
|
|
44
|
+
modelOptions: [{ value: 'openai/gpt-5.1', modelLabel: 'gpt-5.1', providerLabel: 'OpenAI' } as never],
|
|
45
|
+
sessionTypeLabel: 'Codex',
|
|
46
|
+
selectedSessionKey: null,
|
|
47
|
+
sessionDisplayName: undefined,
|
|
48
|
+
canDeleteSession: false,
|
|
49
|
+
isDeletePending: false,
|
|
50
|
+
isHistoryLoading: false,
|
|
51
|
+
uiMessages: [],
|
|
52
|
+
isSending: false,
|
|
53
|
+
isAwaitingAssistantOutput: false
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('shows the draft session type in the conversation header', () => {
|
|
59
|
+
render(<ChatConversationPanel />);
|
|
60
|
+
|
|
61
|
+
expect(screen.getByText('New Task')).toBeTruthy();
|
|
62
|
+
expect(screen.getByText('Codex')).toBeTruthy();
|
|
63
|
+
expect(screen.queryByRole('button')).toBeNull();
|
|
64
|
+
});
|
|
65
|
+
});
|
|
@@ -42,6 +42,8 @@ export function ChatConversationPanel() {
|
|
|
42
42
|
const snapshot = useChatThreadStore((state) => state.snapshot);
|
|
43
43
|
const fallbackThreadRef = useRef<HTMLDivElement | null>(null);
|
|
44
44
|
const threadRef = snapshot.threadRef ?? fallbackThreadRef;
|
|
45
|
+
const shouldShowSessionHeader = Boolean(snapshot.selectedSessionKey || snapshot.sessionTypeLabel);
|
|
46
|
+
const sessionHeaderTitle = snapshot.sessionDisplayName || snapshot.selectedSessionKey || t('chatSidebarNewTask');
|
|
45
47
|
|
|
46
48
|
const showWelcome = !snapshot.selectedSessionKey && snapshot.uiMessages.length === 0 && !snapshot.isSending;
|
|
47
49
|
const hasConfiguredModel = snapshot.modelOptions.length > 0;
|
|
@@ -68,22 +70,29 @@ export function ChatConversationPanel() {
|
|
|
68
70
|
<section className="flex-1 min-h-0 flex flex-col overflow-hidden bg-gradient-to-b from-gray-50/60 to-white">
|
|
69
71
|
<div className={cn(
|
|
70
72
|
"px-5 border-b border-gray-200/60 bg-white/80 backdrop-blur-sm flex items-center justify-between shrink-0 overflow-hidden transition-all duration-200",
|
|
71
|
-
|
|
73
|
+
shouldShowSessionHeader ? "py-3 opacity-100" : "h-0 py-0 opacity-0 border-b-0"
|
|
72
74
|
)}>
|
|
73
|
-
<div className="min-w-0 flex-1">
|
|
75
|
+
<div className="min-w-0 flex-1 flex items-center gap-2">
|
|
74
76
|
<span className="text-sm font-medium text-gray-700 truncate">
|
|
75
|
-
{
|
|
77
|
+
{sessionHeaderTitle}
|
|
76
78
|
</span>
|
|
79
|
+
{snapshot.sessionTypeLabel ? (
|
|
80
|
+
<span className="shrink-0 rounded-full border border-gray-200 bg-gray-100 px-2 py-0.5 text-[11px] font-medium text-gray-600">
|
|
81
|
+
{snapshot.sessionTypeLabel}
|
|
82
|
+
</span>
|
|
83
|
+
) : null}
|
|
77
84
|
</div>
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
85
|
+
{snapshot.selectedSessionKey ? (
|
|
86
|
+
<Button
|
|
87
|
+
variant="ghost"
|
|
88
|
+
size="icon"
|
|
89
|
+
className="rounded-lg shrink-0 text-gray-400 hover:text-destructive"
|
|
90
|
+
onClick={presenter.chatThreadManager.deleteSession}
|
|
91
|
+
disabled={!snapshot.canDeleteSession || snapshot.isDeletePending}
|
|
92
|
+
>
|
|
93
|
+
<Trash2 className="h-4 w-4" />
|
|
94
|
+
</Button>
|
|
95
|
+
) : null}
|
|
87
96
|
</div>
|
|
88
97
|
|
|
89
98
|
{shouldShowProviderHint && (
|
|
@@ -1,330 +1,16 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import { CronConfig } from '@/components/config/CronConfig';
|
|
7
|
-
import { MarketplacePage } from '@/components/marketplace/MarketplacePage';
|
|
8
|
-
import { useSessionRunStatus } from '@/components/chat/chat-page-runtime';
|
|
9
|
-
import { useChatRuntimeController } from '@/components/chat/useChatRuntimeController';
|
|
10
|
-
import { parseSessionKeyFromRoute, resolveAgentIdFromSessionKey } from '@/components/chat/chat-session-route';
|
|
11
|
-
import { useChatPageData, sessionDisplayName } from '@/components/chat/chat-page-data';
|
|
12
|
-
import { ChatPresenterProvider } from '@/components/chat/presenter/chat-presenter-context';
|
|
13
|
-
import { ChatPresenter } from '@/components/chat/presenter/chat.presenter';
|
|
14
|
-
import { useChatInputStore } from '@/components/chat/stores/chat-input.store';
|
|
15
|
-
import { useChatSessionListStore } from '@/components/chat/stores/chat-session-list.store';
|
|
16
|
-
import { useLocation, useNavigate, useParams } from 'react-router-dom';
|
|
17
|
-
|
|
18
|
-
type MainPanelView = 'chat' | 'cron' | 'skills';
|
|
19
|
-
|
|
20
|
-
type ChatPageProps = {
|
|
21
|
-
view: MainPanelView;
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
type UseSessionSyncParams = {
|
|
25
|
-
view: MainPanelView;
|
|
26
|
-
routeSessionKey: string | null;
|
|
27
|
-
selectedSessionKey: string | null;
|
|
28
|
-
selectedAgentId: string;
|
|
29
|
-
setSelectedSessionKey: Dispatch<SetStateAction<string | null>>;
|
|
30
|
-
setSelectedAgentId: Dispatch<SetStateAction<string>>;
|
|
31
|
-
selectedSessionKeyRef: MutableRefObject<string | null>;
|
|
32
|
-
resetStreamState: () => void;
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
function useChatSessionSync(params: UseSessionSyncParams): void {
|
|
36
|
-
const {
|
|
37
|
-
view,
|
|
38
|
-
routeSessionKey,
|
|
39
|
-
selectedSessionKey,
|
|
40
|
-
selectedAgentId,
|
|
41
|
-
setSelectedSessionKey,
|
|
42
|
-
setSelectedAgentId,
|
|
43
|
-
selectedSessionKeyRef,
|
|
44
|
-
resetStreamState
|
|
45
|
-
} = params;
|
|
46
|
-
|
|
47
|
-
useEffect(() => {
|
|
48
|
-
if (view !== 'chat') {
|
|
49
|
-
return;
|
|
50
|
-
}
|
|
51
|
-
if (routeSessionKey) {
|
|
52
|
-
if (selectedSessionKey !== routeSessionKey) {
|
|
53
|
-
setSelectedSessionKey(routeSessionKey);
|
|
54
|
-
}
|
|
55
|
-
return;
|
|
56
|
-
}
|
|
57
|
-
if (selectedSessionKey !== null) {
|
|
58
|
-
setSelectedSessionKey(null);
|
|
59
|
-
resetStreamState();
|
|
60
|
-
}
|
|
61
|
-
}, [resetStreamState, routeSessionKey, selectedSessionKey, setSelectedSessionKey, view]);
|
|
62
|
-
|
|
63
|
-
useEffect(() => {
|
|
64
|
-
const inferred = selectedSessionKey ? resolveAgentIdFromSessionKey(selectedSessionKey) : null;
|
|
65
|
-
if (!inferred) {
|
|
66
|
-
return;
|
|
67
|
-
}
|
|
68
|
-
if (selectedAgentId !== inferred) {
|
|
69
|
-
setSelectedAgentId(inferred);
|
|
70
|
-
}
|
|
71
|
-
}, [selectedAgentId, selectedSessionKey, setSelectedAgentId]);
|
|
72
|
-
|
|
73
|
-
useEffect(() => {
|
|
74
|
-
selectedSessionKeyRef.current = selectedSessionKey;
|
|
75
|
-
}, [selectedSessionKey, selectedSessionKeyRef]);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
type ChatPageLayoutProps = {
|
|
79
|
-
view: MainPanelView;
|
|
80
|
-
confirmDialog: JSX.Element;
|
|
81
|
-
};
|
|
82
|
-
|
|
83
|
-
function ChatPageLayout({ view, confirmDialog }: ChatPageLayoutProps) {
|
|
84
|
-
return (
|
|
85
|
-
<div className="h-full flex">
|
|
86
|
-
<ChatSidebar />
|
|
87
|
-
|
|
88
|
-
{view === 'chat' ? (
|
|
89
|
-
<ChatConversationPanel />
|
|
90
|
-
) : (
|
|
91
|
-
<section className="flex-1 min-h-0 overflow-hidden bg-gradient-to-b from-gray-50/60 to-white">
|
|
92
|
-
{view === 'cron' ? (
|
|
93
|
-
<div className="h-full overflow-auto custom-scrollbar">
|
|
94
|
-
<div className="mx-auto w-full max-w-[min(1120px,100%)] px-6 py-5">
|
|
95
|
-
<CronConfig />
|
|
96
|
-
</div>
|
|
97
|
-
</div>
|
|
98
|
-
) : (
|
|
99
|
-
<div className="h-full overflow-hidden">
|
|
100
|
-
<div className="mx-auto flex h-full min-h-0 w-full max-w-[min(1120px,100%)] flex-col px-6 py-5">
|
|
101
|
-
<MarketplacePage forcedType="skills" />
|
|
102
|
-
</div>
|
|
103
|
-
</div>
|
|
104
|
-
)}
|
|
105
|
-
</section>
|
|
106
|
-
)}
|
|
107
|
-
|
|
108
|
-
{confirmDialog}
|
|
109
|
-
</div>
|
|
110
|
-
);
|
|
111
|
-
}
|
|
1
|
+
import { useLocation } from 'react-router-dom';
|
|
2
|
+
import { resolveChatChain } from '@/components/chat/chat-chain';
|
|
3
|
+
import type { ChatPageProps } from '@/components/chat/chat-page-shell';
|
|
4
|
+
import { LegacyChatPage } from '@/components/chat/legacy/LegacyChatPage';
|
|
5
|
+
import { NcpChatPage } from '@/components/chat/ncp/NcpChatPage';
|
|
112
6
|
|
|
113
7
|
export function ChatPage({ view }: ChatPageProps) {
|
|
114
|
-
const [presenter] = useState(() => new ChatPresenter());
|
|
115
|
-
const query = useChatSessionListStore((state) => state.snapshot.query);
|
|
116
|
-
const selectedSessionKey = useChatSessionListStore((state) => state.snapshot.selectedSessionKey);
|
|
117
|
-
const selectedAgentId = useChatSessionListStore((state) => state.snapshot.selectedAgentId);
|
|
118
|
-
const pendingSessionType = useChatInputStore((state) => state.snapshot.pendingSessionType);
|
|
119
|
-
const { confirm, ConfirmDialog } = useConfirmDialog();
|
|
120
8
|
const location = useLocation();
|
|
121
|
-
const
|
|
122
|
-
const { sessionId: routeSessionIdParam } = useParams<{ sessionId?: string }>();
|
|
123
|
-
const threadRef = useRef<HTMLDivElement | null>(null);
|
|
124
|
-
const selectedSessionKeyRef = useRef<string | null>(selectedSessionKey);
|
|
125
|
-
const thinkingHydratedSessionKeyRef = useRef<string | null>(null);
|
|
126
|
-
const routeSessionKey = useMemo(
|
|
127
|
-
() => parseSessionKeyFromRoute(routeSessionIdParam),
|
|
128
|
-
[routeSessionIdParam]
|
|
129
|
-
);
|
|
130
|
-
const {
|
|
131
|
-
sessionsQuery,
|
|
132
|
-
installedSkillsQuery,
|
|
133
|
-
chatCapabilitiesQuery,
|
|
134
|
-
historyQuery,
|
|
135
|
-
isProviderStateResolved,
|
|
136
|
-
modelOptions,
|
|
137
|
-
sessions,
|
|
138
|
-
skillRecords,
|
|
139
|
-
selectedSession,
|
|
140
|
-
historyMessages,
|
|
141
|
-
selectedSessionThinkingLevel,
|
|
142
|
-
sessionTypeOptions,
|
|
143
|
-
defaultSessionType,
|
|
144
|
-
selectedSessionType,
|
|
145
|
-
canEditSessionType,
|
|
146
|
-
sessionTypeUnavailable,
|
|
147
|
-
sessionTypeUnavailableMessage
|
|
148
|
-
} = useChatPageData({
|
|
149
|
-
query,
|
|
150
|
-
selectedSessionKey,
|
|
151
|
-
selectedAgentId,
|
|
152
|
-
pendingSessionType,
|
|
153
|
-
setPendingSessionType: presenter.chatInputManager.setPendingSessionType,
|
|
154
|
-
setSelectedModel: presenter.chatInputManager.setSelectedModel
|
|
155
|
-
});
|
|
156
|
-
const {
|
|
157
|
-
uiMessages,
|
|
158
|
-
isSending,
|
|
159
|
-
isAwaitingAssistantOutput,
|
|
160
|
-
canStopCurrentRun,
|
|
161
|
-
stopDisabledReason,
|
|
162
|
-
lastSendError,
|
|
163
|
-
activeBackendRunId,
|
|
164
|
-
sendMessage,
|
|
165
|
-
stopCurrentRun,
|
|
166
|
-
resumeRun,
|
|
167
|
-
resetStreamState,
|
|
168
|
-
applyHistoryMessages
|
|
169
|
-
} = useChatRuntimeController(
|
|
170
|
-
{
|
|
171
|
-
selectedSessionKeyRef,
|
|
172
|
-
setSelectedSessionKey: presenter.chatSessionListManager.setSelectedSessionKey,
|
|
173
|
-
setDraft: presenter.chatInputManager.setDraft,
|
|
174
|
-
refetchSessions: sessionsQuery.refetch,
|
|
175
|
-
refetchHistory: historyQuery.refetch
|
|
176
|
-
},
|
|
177
|
-
presenter.chatController
|
|
178
|
-
);
|
|
179
|
-
|
|
180
|
-
console.log('[ChatPage] uiMessages', { uiMessages, historyMessages });
|
|
181
|
-
useEffect(() => {
|
|
182
|
-
presenter.chatStreamActionsManager.bind({
|
|
183
|
-
sendMessage,
|
|
184
|
-
stopCurrentRun,
|
|
185
|
-
resumeRun,
|
|
186
|
-
resetStreamState,
|
|
187
|
-
applyHistoryMessages
|
|
188
|
-
});
|
|
189
|
-
}, [applyHistoryMessages, presenter, resetStreamState, resumeRun, sendMessage, stopCurrentRun]);
|
|
190
|
-
|
|
191
|
-
const { sessionRunStatusByKey } = useSessionRunStatus({
|
|
192
|
-
view,
|
|
193
|
-
selectedSessionKey,
|
|
194
|
-
activeBackendRunId,
|
|
195
|
-
isLocallyRunning: isSending || Boolean(activeBackendRunId),
|
|
196
|
-
resumeRun: presenter.chatStreamActionsManager.resumeRun
|
|
197
|
-
});
|
|
198
|
-
|
|
199
|
-
useChatSessionSync({
|
|
200
|
-
view,
|
|
201
|
-
routeSessionKey,
|
|
202
|
-
selectedSessionKey,
|
|
203
|
-
selectedAgentId,
|
|
204
|
-
setSelectedSessionKey: presenter.chatSessionListManager.setSelectedSessionKey,
|
|
205
|
-
setSelectedAgentId: presenter.chatSessionListManager.setSelectedAgentId,
|
|
206
|
-
selectedSessionKeyRef,
|
|
207
|
-
resetStreamState: presenter.chatStreamActionsManager.resetStreamState
|
|
208
|
-
});
|
|
209
|
-
|
|
210
|
-
useEffect(() => {
|
|
211
|
-
presenter.chatStreamActionsManager.applyHistoryMessages(historyMessages, {
|
|
212
|
-
isLoading: historyQuery.isLoading
|
|
213
|
-
});
|
|
214
|
-
}, [historyMessages, historyQuery.isLoading, presenter]);
|
|
215
|
-
|
|
216
|
-
useEffect(() => {
|
|
217
|
-
presenter.chatUiManager.syncState({
|
|
218
|
-
pathname: location.pathname
|
|
219
|
-
});
|
|
220
|
-
presenter.chatUiManager.bindActions({
|
|
221
|
-
navigate,
|
|
222
|
-
confirm
|
|
223
|
-
});
|
|
224
|
-
}, [confirm, location.pathname, navigate, presenter]);
|
|
225
|
-
const currentSessionDisplayName = selectedSession ? sessionDisplayName(selectedSession) : undefined;
|
|
226
|
-
|
|
227
|
-
useEffect(() => {
|
|
228
|
-
presenter.chatThreadManager.bindActions({
|
|
229
|
-
refetchSessions: sessionsQuery.refetch
|
|
230
|
-
});
|
|
231
|
-
}, [
|
|
232
|
-
presenter,
|
|
233
|
-
sessionsQuery.refetch,
|
|
234
|
-
]);
|
|
235
|
-
|
|
236
|
-
useEffect(() => {
|
|
237
|
-
const shouldHydrateThinkingFromHistory =
|
|
238
|
-
!isSending &&
|
|
239
|
-
!isAwaitingAssistantOutput &&
|
|
240
|
-
!historyQuery.isLoading &&
|
|
241
|
-
selectedSessionKey !== thinkingHydratedSessionKeyRef.current;
|
|
9
|
+
const chatChain = resolveChatChain(location.search);
|
|
242
10
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
canStopGeneration: canStopCurrentRun,
|
|
247
|
-
stopDisabledReason,
|
|
248
|
-
stopSupported: chatCapabilitiesQuery.data?.stopSupported ?? false,
|
|
249
|
-
stopReason: chatCapabilitiesQuery.data?.stopReason,
|
|
250
|
-
sendError: lastSendError,
|
|
251
|
-
isSending,
|
|
252
|
-
modelOptions,
|
|
253
|
-
sessionTypeOptions,
|
|
254
|
-
selectedSessionType,
|
|
255
|
-
...(shouldHydrateThinkingFromHistory ? { selectedThinkingLevel: selectedSessionThinkingLevel } : {}),
|
|
256
|
-
canEditSessionType,
|
|
257
|
-
sessionTypeUnavailable,
|
|
258
|
-
skillRecords,
|
|
259
|
-
isSkillsLoading: installedSkillsQuery.isLoading
|
|
260
|
-
});
|
|
261
|
-
if (shouldHydrateThinkingFromHistory) {
|
|
262
|
-
thinkingHydratedSessionKeyRef.current = selectedSessionKey;
|
|
263
|
-
}
|
|
264
|
-
if (!selectedSessionKey) {
|
|
265
|
-
thinkingHydratedSessionKeyRef.current = null;
|
|
266
|
-
}
|
|
267
|
-
presenter.chatSessionListManager.syncSnapshot({
|
|
268
|
-
sessions,
|
|
269
|
-
query,
|
|
270
|
-
isLoading: sessionsQuery.isLoading
|
|
271
|
-
});
|
|
272
|
-
presenter.chatRunStatusManager.syncSnapshot({
|
|
273
|
-
sessionRunStatusByKey,
|
|
274
|
-
isLocallyRunning: isSending || Boolean(activeBackendRunId),
|
|
275
|
-
activeBackendRunId
|
|
276
|
-
});
|
|
277
|
-
presenter.chatThreadManager.syncSnapshot({
|
|
278
|
-
isProviderStateResolved,
|
|
279
|
-
modelOptions,
|
|
280
|
-
sessionTypeUnavailable,
|
|
281
|
-
sessionTypeUnavailableMessage,
|
|
282
|
-
selectedSessionKey,
|
|
283
|
-
sessionDisplayName: currentSessionDisplayName,
|
|
284
|
-
canDeleteSession: Boolean(selectedSession),
|
|
285
|
-
threadRef,
|
|
286
|
-
isHistoryLoading: historyQuery.isLoading,
|
|
287
|
-
uiMessages,
|
|
288
|
-
isSending,
|
|
289
|
-
isAwaitingAssistantOutput
|
|
290
|
-
});
|
|
291
|
-
}, [
|
|
292
|
-
activeBackendRunId,
|
|
293
|
-
canEditSessionType,
|
|
294
|
-
canStopCurrentRun,
|
|
295
|
-
currentSessionDisplayName,
|
|
296
|
-
chatCapabilitiesQuery.data?.stopReason,
|
|
297
|
-
chatCapabilitiesQuery.data?.stopSupported,
|
|
298
|
-
defaultSessionType,
|
|
299
|
-
historyQuery.isLoading,
|
|
300
|
-
installedSkillsQuery.isLoading,
|
|
301
|
-
isAwaitingAssistantOutput,
|
|
302
|
-
isProviderStateResolved,
|
|
303
|
-
isSending,
|
|
304
|
-
lastSendError,
|
|
305
|
-
uiMessages,
|
|
306
|
-
modelOptions,
|
|
307
|
-
presenter,
|
|
308
|
-
query,
|
|
309
|
-
selectedSession,
|
|
310
|
-
selectedSessionThinkingLevel,
|
|
311
|
-
selectedSessionKey,
|
|
312
|
-
selectedAgentId,
|
|
313
|
-
selectedSessionType,
|
|
314
|
-
sessionRunStatusByKey,
|
|
315
|
-
sessionTypeOptions,
|
|
316
|
-
sessionTypeUnavailable,
|
|
317
|
-
sessionTypeUnavailableMessage,
|
|
318
|
-
sessions,
|
|
319
|
-
sessionsQuery.isLoading,
|
|
320
|
-
stopDisabledReason,
|
|
321
|
-
threadRef,
|
|
322
|
-
skillRecords
|
|
323
|
-
]);
|
|
11
|
+
if (chatChain === 'ncp') {
|
|
12
|
+
return <NcpChatPage view={view} />;
|
|
13
|
+
}
|
|
324
14
|
|
|
325
|
-
return
|
|
326
|
-
<ChatPresenterProvider presenter={presenter}>
|
|
327
|
-
<ChatPageLayout view={view} confirmDialog={<ConfirmDialog />} />
|
|
328
|
-
</ChatPresenterProvider>
|
|
329
|
-
);
|
|
15
|
+
return <LegacyChatPage view={view} />;
|
|
330
16
|
}
|