@nextclaw/ui 0.5.46 → 0.5.47

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.
Files changed (32) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/dist/assets/{ChannelsList-C5IzoBSZ.js → ChannelsList-B6N0kXyK.js} +1 -1
  3. package/dist/assets/ChatPage-DsDFvVQX.js +32 -0
  4. package/dist/assets/CronConfig-Cbz6V8MU.js +1 -0
  5. package/dist/assets/DocBrowser-hQzP4Iai.js +1 -0
  6. package/dist/assets/{MarketplacePage-DmDTJ27k.js → MarketplacePage-DMoWoU1y.js} +1 -1
  7. package/dist/assets/{ModelConfig-DQ1d3Dtn.js → ModelConfig-BXjF-qbA.js} +1 -1
  8. package/dist/assets/ProvidersList-D3hfY5U7.js +1 -0
  9. package/dist/assets/RuntimeConfig-DJ7qIejp.js +1 -0
  10. package/dist/assets/{SecretsConfig-DlNYyu9I.js → SecretsConfig-BFDeNvwV.js} +2 -2
  11. package/dist/assets/{SessionsConfig-Dt9heKTD.js → SessionsConfig-CJF7lPkX.js} +2 -2
  12. package/dist/assets/{card-KmfXQ4Bm.js → card-BREZdIEb.js} +1 -1
  13. package/dist/assets/{index-BhpPLO5K.js → index-uTbQ-MAY.js} +2 -2
  14. package/dist/assets/{label-iF47BkaM.js → label-CzMB2yjV.js} +1 -1
  15. package/dist/assets/{logos-C8Yako2a.js → logos-vVtRUuoo.js} +1 -1
  16. package/dist/assets/{page-layout-sZk-HtoA.js → page-layout-B07kdurB.js} +1 -1
  17. package/dist/assets/{switch-BnYsX3uS.js → switch-Cr6cemeT.js} +1 -1
  18. package/dist/assets/{tabs-custom-BEBNjTRA.js → tabs-custom-BzcvgsvR.js} +1 -1
  19. package/dist/assets/{useConfig-DJ_KEVx0.js → useConfig-B4Y6cGwc.js} +1 -1
  20. package/dist/assets/{useConfirmDialog-BwqRK1uy.js → useConfirmDialog-Dc5WHCUf.js} +1 -1
  21. package/dist/assets/{vendor-DfLizrKM.js → vendor-Dh04PGww.js} +1 -1
  22. package/dist/index.html +2 -2
  23. package/package.json +1 -1
  24. package/src/components/chat/ChatConversationPanel.tsx +148 -0
  25. package/src/components/chat/ChatPage.tsx +80 -353
  26. package/src/components/chat/ChatSessionsSidebar.tsx +100 -0
  27. package/src/components/chat/useChatStreamController.ts +268 -0
  28. package/dist/assets/ChatPage-BEBmDqtw.js +0 -32
  29. package/dist/assets/CronConfig-BdLVPoNw.js +0 -1
  30. package/dist/assets/DocBrowser-DzsZUzgQ.js +0 -1
  31. package/dist/assets/ProvidersList-snuzM5CK.js +0 -1
  32. package/dist/assets/RuntimeConfig-BSDP51k6.js +0 -1
@@ -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
+ }