@nextclaw/ui 0.5.46 → 0.5.48
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 +12 -0
- package/dist/assets/{ChannelsList-C5IzoBSZ.js → ChannelsList-C8cguFLc.js} +1 -1
- package/dist/assets/ChatPage-BkHWNUNR.js +32 -0
- package/dist/assets/CronConfig-D-ESQlvk.js +1 -0
- package/dist/assets/DocBrowser-B9ZD6pAk.js +1 -0
- package/dist/assets/{MarketplacePage-DmDTJ27k.js → MarketplacePage-Ds_l9KTF.js} +1 -1
- package/dist/assets/{ModelConfig-DQ1d3Dtn.js → ModelConfig-N1tbLv9b.js} +1 -1
- package/dist/assets/ProvidersList-BXHpjVtO.js +1 -0
- package/dist/assets/RuntimeConfig-KsKfkjgv.js +1 -0
- package/dist/assets/{SecretsConfig-DlNYyu9I.js → SecretsConfig-KkgMzdt1.js} +2 -2
- package/dist/assets/{SessionsConfig-Dt9heKTD.js → SessionsConfig-CWBp8IPf.js} +2 -2
- package/dist/assets/{card-KmfXQ4Bm.js → card-D7NY0Szf.js} +1 -1
- package/dist/assets/index-BRBYYgR_.js +2 -0
- package/dist/assets/{label-iF47BkaM.js → label-Ojs7Al6B.js} +1 -1
- package/dist/assets/{logos-C8Yako2a.js → logos-B1qBsCSi.js} +1 -1
- package/dist/assets/{page-layout-sZk-HtoA.js → page-layout-CUMMO0nN.js} +1 -1
- package/dist/assets/{switch-BnYsX3uS.js → switch-BdhS_16-.js} +1 -1
- package/dist/assets/{tabs-custom-BEBNjTRA.js → tabs-custom-D261E5EA.js} +1 -1
- package/dist/assets/{useConfig-DJ_KEVx0.js → useConfig-txxbxXnT.js} +1 -1
- package/dist/assets/{useConfirmDialog-BwqRK1uy.js → useConfirmDialog-BUKGHDL6.js} +1 -1
- package/dist/assets/{vendor-DfLizrKM.js → vendor-Dh04PGww.js} +1 -1
- package/dist/index.html +2 -2
- package/package.json +1 -1
- package/src/components/chat/ChatConversationPanel.tsx +148 -0
- package/src/components/chat/ChatPage.tsx +80 -353
- package/src/components/chat/ChatSessionsSidebar.tsx +100 -0
- package/src/components/chat/useChatStreamController.ts +268 -0
- package/src/components/layout/Sidebar.tsx +55 -13
- package/src/lib/i18n.ts +1 -0
- package/dist/assets/ChatPage-BEBmDqtw.js +0 -32
- package/dist/assets/CronConfig-BdLVPoNw.js +0 -1
- package/dist/assets/DocBrowser-DzsZUzgQ.js +0 -1
- package/dist/assets/ProvidersList-snuzM5CK.js +0 -1
- package/dist/assets/RuntimeConfig-BSDP51k6.js +0 -1
- package/dist/assets/index-BhpPLO5K.js +0 -2
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
2
|
+
import type { Dispatch, MutableRefObject, SetStateAction } from 'react';
|
|
3
|
+
import type { SessionEventView } from '@/api/types';
|
|
4
|
+
import { sendChatTurnStream } from '@/api/config';
|
|
5
|
+
|
|
6
|
+
type PendingChatMessage = {
|
|
7
|
+
id: number;
|
|
8
|
+
message: string;
|
|
9
|
+
sessionKey: string;
|
|
10
|
+
agentId: string;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
type SendMessageParams = {
|
|
14
|
+
message: string;
|
|
15
|
+
sessionKey: string;
|
|
16
|
+
agentId: string;
|
|
17
|
+
restoreDraftOnError?: boolean;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
type UseChatStreamControllerParams = {
|
|
21
|
+
nextOptimisticUserSeq: number;
|
|
22
|
+
selectedSessionKeyRef: MutableRefObject<string | null>;
|
|
23
|
+
setSelectedSessionKey: Dispatch<SetStateAction<string | null>>;
|
|
24
|
+
setDraft: Dispatch<SetStateAction<string>>;
|
|
25
|
+
refetchSessions: () => Promise<unknown>;
|
|
26
|
+
refetchHistory: () => Promise<unknown>;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
type StreamSetters = {
|
|
30
|
+
setOptimisticUserEvent: Dispatch<SetStateAction<SessionEventView | null>>;
|
|
31
|
+
setStreamingSessionEvents: Dispatch<SetStateAction<SessionEventView[]>>;
|
|
32
|
+
setStreamingAssistantText: Dispatch<SetStateAction<string>>;
|
|
33
|
+
setStreamingAssistantTimestamp: Dispatch<SetStateAction<string | null>>;
|
|
34
|
+
setIsSending: Dispatch<SetStateAction<boolean>>;
|
|
35
|
+
setIsAwaitingAssistantOutput: Dispatch<SetStateAction<boolean>>;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
function clearStreamingState(setters: StreamSetters) {
|
|
39
|
+
setters.setIsSending(false);
|
|
40
|
+
setters.setOptimisticUserEvent(null);
|
|
41
|
+
setters.setStreamingSessionEvents([]);
|
|
42
|
+
setters.setStreamingAssistantText('');
|
|
43
|
+
setters.setStreamingAssistantTimestamp(null);
|
|
44
|
+
setters.setIsAwaitingAssistantOutput(false);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async function executeSendRun(params: {
|
|
48
|
+
item: PendingChatMessage;
|
|
49
|
+
runId: number;
|
|
50
|
+
runIdRef: MutableRefObject<number>;
|
|
51
|
+
nextOptimisticUserSeq: number;
|
|
52
|
+
selectedSessionKeyRef: MutableRefObject<string | null>;
|
|
53
|
+
setSelectedSessionKey: Dispatch<SetStateAction<string | null>>;
|
|
54
|
+
setDraft: Dispatch<SetStateAction<string>>;
|
|
55
|
+
refetchSessions: () => Promise<unknown>;
|
|
56
|
+
refetchHistory: () => Promise<unknown>;
|
|
57
|
+
restoreDraftOnError?: boolean;
|
|
58
|
+
setters: StreamSetters;
|
|
59
|
+
}): Promise<void> {
|
|
60
|
+
const {
|
|
61
|
+
item,
|
|
62
|
+
runId,
|
|
63
|
+
runIdRef,
|
|
64
|
+
nextOptimisticUserSeq,
|
|
65
|
+
selectedSessionKeyRef,
|
|
66
|
+
setSelectedSessionKey,
|
|
67
|
+
setDraft,
|
|
68
|
+
refetchSessions,
|
|
69
|
+
refetchHistory,
|
|
70
|
+
restoreDraftOnError,
|
|
71
|
+
setters
|
|
72
|
+
} = params;
|
|
73
|
+
|
|
74
|
+
setters.setStreamingSessionEvents([]);
|
|
75
|
+
setters.setStreamingAssistantText('');
|
|
76
|
+
setters.setStreamingAssistantTimestamp(null);
|
|
77
|
+
setters.setOptimisticUserEvent({
|
|
78
|
+
seq: nextOptimisticUserSeq,
|
|
79
|
+
type: 'message.user.optimistic',
|
|
80
|
+
timestamp: new Date().toISOString(),
|
|
81
|
+
message: {
|
|
82
|
+
role: 'user',
|
|
83
|
+
content: item.message,
|
|
84
|
+
timestamp: new Date().toISOString()
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
setters.setIsSending(true);
|
|
88
|
+
setters.setIsAwaitingAssistantOutput(true);
|
|
89
|
+
|
|
90
|
+
try {
|
|
91
|
+
let streamText = '';
|
|
92
|
+
const streamTimestamp = new Date().toISOString();
|
|
93
|
+
setters.setStreamingAssistantTimestamp(streamTimestamp);
|
|
94
|
+
|
|
95
|
+
const result = await sendChatTurnStream({
|
|
96
|
+
message: item.message,
|
|
97
|
+
sessionKey: item.sessionKey,
|
|
98
|
+
agentId: item.agentId,
|
|
99
|
+
channel: 'ui',
|
|
100
|
+
chatId: 'web-ui'
|
|
101
|
+
}, {
|
|
102
|
+
onReady: (event) => {
|
|
103
|
+
if (runId !== runIdRef.current) {
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
if (event.sessionKey) {
|
|
107
|
+
setSelectedSessionKey((prev) => prev === event.sessionKey ? prev : event.sessionKey);
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
onDelta: (event) => {
|
|
111
|
+
if (runId !== runIdRef.current) {
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
streamText += event.delta;
|
|
115
|
+
setters.setStreamingAssistantText(streamText);
|
|
116
|
+
setters.setIsAwaitingAssistantOutput(false);
|
|
117
|
+
},
|
|
118
|
+
onSessionEvent: (event) => {
|
|
119
|
+
if (runId !== runIdRef.current) {
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
if (event.data.message?.role === 'user') {
|
|
123
|
+
setters.setOptimisticUserEvent(null);
|
|
124
|
+
}
|
|
125
|
+
setters.setStreamingSessionEvents((prev) => {
|
|
126
|
+
const next = [...prev];
|
|
127
|
+
const hit = next.findIndex((streamEvent) => streamEvent.seq === event.data.seq);
|
|
128
|
+
if (hit >= 0) {
|
|
129
|
+
next[hit] = event.data;
|
|
130
|
+
} else {
|
|
131
|
+
next.push(event.data);
|
|
132
|
+
}
|
|
133
|
+
return next;
|
|
134
|
+
});
|
|
135
|
+
if (event.data.message?.role === 'assistant') {
|
|
136
|
+
// Reset delta accumulator once assistant event lands in session timeline.
|
|
137
|
+
streamText = '';
|
|
138
|
+
setters.setStreamingAssistantText('');
|
|
139
|
+
setters.setIsAwaitingAssistantOutput(false);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
if (runId !== runIdRef.current) {
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
setters.setOptimisticUserEvent(null);
|
|
147
|
+
if (result.sessionKey !== item.sessionKey) {
|
|
148
|
+
setSelectedSessionKey(result.sessionKey);
|
|
149
|
+
}
|
|
150
|
+
await refetchSessions();
|
|
151
|
+
const activeSessionKey = selectedSessionKeyRef.current;
|
|
152
|
+
if (!activeSessionKey || activeSessionKey === item.sessionKey || activeSessionKey === result.sessionKey) {
|
|
153
|
+
await refetchHistory();
|
|
154
|
+
}
|
|
155
|
+
setters.setStreamingSessionEvents([]);
|
|
156
|
+
setters.setStreamingAssistantText('');
|
|
157
|
+
setters.setStreamingAssistantTimestamp(null);
|
|
158
|
+
setters.setIsAwaitingAssistantOutput(false);
|
|
159
|
+
setters.setIsSending(false);
|
|
160
|
+
} catch {
|
|
161
|
+
if (runId !== runIdRef.current) {
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
runIdRef.current += 1;
|
|
165
|
+
clearStreamingState(setters);
|
|
166
|
+
if (restoreDraftOnError) {
|
|
167
|
+
setDraft((prev) => prev.trim().length === 0 ? item.message : prev);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export function useChatStreamController(params: UseChatStreamControllerParams) {
|
|
173
|
+
const [optimisticUserEvent, setOptimisticUserEvent] = useState<SessionEventView | null>(null);
|
|
174
|
+
const [streamingSessionEvents, setStreamingSessionEvents] = useState<SessionEventView[]>([]);
|
|
175
|
+
const [streamingAssistantText, setStreamingAssistantText] = useState('');
|
|
176
|
+
const [streamingAssistantTimestamp, setStreamingAssistantTimestamp] = useState<string | null>(null);
|
|
177
|
+
const [isSending, setIsSending] = useState(false);
|
|
178
|
+
const [isAwaitingAssistantOutput, setIsAwaitingAssistantOutput] = useState(false);
|
|
179
|
+
const [queuedMessages, setQueuedMessages] = useState<PendingChatMessage[]>([]);
|
|
180
|
+
|
|
181
|
+
const streamRunIdRef = useRef(0);
|
|
182
|
+
const queueIdRef = useRef(0);
|
|
183
|
+
|
|
184
|
+
const resetStreamState = useCallback(() => {
|
|
185
|
+
streamRunIdRef.current += 1;
|
|
186
|
+
setQueuedMessages([]);
|
|
187
|
+
clearStreamingState({
|
|
188
|
+
setOptimisticUserEvent,
|
|
189
|
+
setStreamingSessionEvents,
|
|
190
|
+
setStreamingAssistantText,
|
|
191
|
+
setStreamingAssistantTimestamp,
|
|
192
|
+
setIsSending,
|
|
193
|
+
setIsAwaitingAssistantOutput
|
|
194
|
+
});
|
|
195
|
+
}, []);
|
|
196
|
+
|
|
197
|
+
useEffect(() => {
|
|
198
|
+
return () => {
|
|
199
|
+
streamRunIdRef.current += 1;
|
|
200
|
+
};
|
|
201
|
+
}, []);
|
|
202
|
+
|
|
203
|
+
const runSend = useCallback(
|
|
204
|
+
async (item: PendingChatMessage, options?: { restoreDraftOnError?: boolean }) => {
|
|
205
|
+
streamRunIdRef.current += 1;
|
|
206
|
+
await executeSendRun({
|
|
207
|
+
item,
|
|
208
|
+
runId: streamRunIdRef.current,
|
|
209
|
+
runIdRef: streamRunIdRef,
|
|
210
|
+
nextOptimisticUserSeq: params.nextOptimisticUserSeq,
|
|
211
|
+
selectedSessionKeyRef: params.selectedSessionKeyRef,
|
|
212
|
+
setSelectedSessionKey: params.setSelectedSessionKey,
|
|
213
|
+
setDraft: params.setDraft,
|
|
214
|
+
refetchSessions: params.refetchSessions,
|
|
215
|
+
refetchHistory: params.refetchHistory,
|
|
216
|
+
restoreDraftOnError: options?.restoreDraftOnError,
|
|
217
|
+
setters: {
|
|
218
|
+
setOptimisticUserEvent,
|
|
219
|
+
setStreamingSessionEvents,
|
|
220
|
+
setStreamingAssistantText,
|
|
221
|
+
setStreamingAssistantTimestamp,
|
|
222
|
+
setIsSending,
|
|
223
|
+
setIsAwaitingAssistantOutput
|
|
224
|
+
}
|
|
225
|
+
});
|
|
226
|
+
},
|
|
227
|
+
[params]
|
|
228
|
+
);
|
|
229
|
+
|
|
230
|
+
useEffect(() => {
|
|
231
|
+
if (isSending || queuedMessages.length === 0) {
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
const [next, ...rest] = queuedMessages;
|
|
235
|
+
setQueuedMessages(rest);
|
|
236
|
+
void runSend(next, { restoreDraftOnError: true });
|
|
237
|
+
}, [isSending, queuedMessages, runSend]);
|
|
238
|
+
|
|
239
|
+
const sendMessage = useCallback(
|
|
240
|
+
async (payload: SendMessageParams) => {
|
|
241
|
+
queueIdRef.current += 1;
|
|
242
|
+
const item: PendingChatMessage = {
|
|
243
|
+
id: queueIdRef.current,
|
|
244
|
+
message: payload.message,
|
|
245
|
+
sessionKey: payload.sessionKey,
|
|
246
|
+
agentId: payload.agentId
|
|
247
|
+
};
|
|
248
|
+
if (isSending) {
|
|
249
|
+
setQueuedMessages((prev) => [...prev, item]);
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
await runSend(item, { restoreDraftOnError: payload.restoreDraftOnError });
|
|
253
|
+
},
|
|
254
|
+
[isSending, runSend]
|
|
255
|
+
);
|
|
256
|
+
|
|
257
|
+
return {
|
|
258
|
+
optimisticUserEvent,
|
|
259
|
+
streamingSessionEvents,
|
|
260
|
+
streamingAssistantText,
|
|
261
|
+
streamingAssistantTimestamp,
|
|
262
|
+
isSending,
|
|
263
|
+
isAwaitingAssistantOutput,
|
|
264
|
+
queuedCount: queuedMessages.length,
|
|
265
|
+
sendMessage,
|
|
266
|
+
resetStreamState
|
|
267
|
+
};
|
|
268
|
+
}
|
|
@@ -30,7 +30,8 @@ export function Sidebar() {
|
|
|
30
30
|
setTheme(nextTheme);
|
|
31
31
|
};
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
// Core navigation items - primary features
|
|
34
|
+
const coreNavItems = [
|
|
34
35
|
{
|
|
35
36
|
target: '/chat',
|
|
36
37
|
label: t('chat'),
|
|
@@ -51,6 +52,20 @@ export function Sidebar() {
|
|
|
51
52
|
label: t('channels'),
|
|
52
53
|
icon: MessageSquare,
|
|
53
54
|
},
|
|
55
|
+
{
|
|
56
|
+
target: '/cron',
|
|
57
|
+
label: t('cron'),
|
|
58
|
+
icon: AlarmClock,
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
target: '/marketplace/skills',
|
|
62
|
+
label: t('marketplaceFilterSkills'),
|
|
63
|
+
icon: BrainCircuit,
|
|
64
|
+
}
|
|
65
|
+
];
|
|
66
|
+
|
|
67
|
+
// Advanced navigation items - secondary features
|
|
68
|
+
const advancedNavItems = [
|
|
54
69
|
{
|
|
55
70
|
target: '/runtime',
|
|
56
71
|
label: t('runtime'),
|
|
@@ -61,11 +76,6 @@ export function Sidebar() {
|
|
|
61
76
|
label: t('sessions'),
|
|
62
77
|
icon: History,
|
|
63
78
|
},
|
|
64
|
-
{
|
|
65
|
-
target: '/cron',
|
|
66
|
-
label: t('cron'),
|
|
67
|
-
icon: AlarmClock,
|
|
68
|
-
},
|
|
69
79
|
{
|
|
70
80
|
target: '/secrets',
|
|
71
81
|
label: t('secrets'),
|
|
@@ -75,11 +85,6 @@ export function Sidebar() {
|
|
|
75
85
|
target: '/marketplace/plugins',
|
|
76
86
|
label: t('marketplaceFilterPlugins'),
|
|
77
87
|
icon: Plug,
|
|
78
|
-
},
|
|
79
|
-
{
|
|
80
|
-
target: '/marketplace/skills',
|
|
81
|
-
label: t('marketplaceFilterSkills'),
|
|
82
|
-
icon: BrainCircuit,
|
|
83
88
|
}
|
|
84
89
|
];
|
|
85
90
|
|
|
@@ -96,9 +101,10 @@ export function Sidebar() {
|
|
|
96
101
|
</div>
|
|
97
102
|
|
|
98
103
|
{/* Navigation */}
|
|
99
|
-
<nav className="flex-1">
|
|
104
|
+
<nav className="flex-1 flex flex-col">
|
|
105
|
+
{/* Core Navigation */}
|
|
100
106
|
<ul className="space-y-1">
|
|
101
|
-
{
|
|
107
|
+
{coreNavItems.map((item) => {
|
|
102
108
|
const Icon = item.icon;
|
|
103
109
|
|
|
104
110
|
return (
|
|
@@ -126,6 +132,42 @@ export function Sidebar() {
|
|
|
126
132
|
);
|
|
127
133
|
})}
|
|
128
134
|
</ul>
|
|
135
|
+
|
|
136
|
+
{/* Advanced Navigation */}
|
|
137
|
+
<div className="mt-3 pt-3 border-t border-[#dde0ea]">
|
|
138
|
+
<div className="px-3 mb-2">
|
|
139
|
+
<span className="text-[11px] font-medium text-gray-400 uppercase tracking-wider">{t('advanced')}</span>
|
|
140
|
+
</div>
|
|
141
|
+
<ul className="space-y-1">
|
|
142
|
+
{advancedNavItems.map((item) => {
|
|
143
|
+
const Icon = item.icon;
|
|
144
|
+
|
|
145
|
+
return (
|
|
146
|
+
<li key={item.target}>
|
|
147
|
+
<NavLink
|
|
148
|
+
to={item.target}
|
|
149
|
+
className={({ isActive }) => cn(
|
|
150
|
+
'group w-full flex items-center gap-3 px-3 py-2.5 rounded-xl text-[14px] font-medium transition-all duration-base',
|
|
151
|
+
isActive
|
|
152
|
+
? 'bg-gray-200 text-gray-900 font-semibold shadow-sm'
|
|
153
|
+
: 'text-gray-600 hover:bg-gray-200/60 hover:text-gray-900'
|
|
154
|
+
)}
|
|
155
|
+
>
|
|
156
|
+
{({ isActive }) => (
|
|
157
|
+
<>
|
|
158
|
+
<Icon className={cn(
|
|
159
|
+
'h-[17px] w-[17px] transition-colors',
|
|
160
|
+
isActive ? 'text-gray-900' : 'text-gray-500 group-hover:text-gray-800'
|
|
161
|
+
)} />
|
|
162
|
+
<span className="flex-1 text-left">{item.label}</span>
|
|
163
|
+
</>
|
|
164
|
+
)}
|
|
165
|
+
</NavLink>
|
|
166
|
+
</li>
|
|
167
|
+
);
|
|
168
|
+
})}
|
|
169
|
+
</ul>
|
|
170
|
+
</div>
|
|
129
171
|
</nav>
|
|
130
172
|
|
|
131
173
|
{/* Help Button */}
|
package/src/lib/i18n.ts
CHANGED
|
@@ -131,6 +131,7 @@ export const LABELS: Record<string, { zh: string; en: string }> = {
|
|
|
131
131
|
secrets: { zh: '密钥管理', en: 'Secrets' },
|
|
132
132
|
runtime: { zh: '路由与运行时', en: 'Routing & Runtime' },
|
|
133
133
|
marketplace: { zh: '市场', en: 'Marketplace' },
|
|
134
|
+
advanced: { zh: '高级', en: 'Advanced' },
|
|
134
135
|
|
|
135
136
|
// Common
|
|
136
137
|
enabled: { zh: '启用', en: 'Enabled' },
|