@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.
Files changed (35) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/assets/{ChannelsList-C5IzoBSZ.js → ChannelsList-C8cguFLc.js} +1 -1
  3. package/dist/assets/ChatPage-BkHWNUNR.js +32 -0
  4. package/dist/assets/CronConfig-D-ESQlvk.js +1 -0
  5. package/dist/assets/DocBrowser-B9ZD6pAk.js +1 -0
  6. package/dist/assets/{MarketplacePage-DmDTJ27k.js → MarketplacePage-Ds_l9KTF.js} +1 -1
  7. package/dist/assets/{ModelConfig-DQ1d3Dtn.js → ModelConfig-N1tbLv9b.js} +1 -1
  8. package/dist/assets/ProvidersList-BXHpjVtO.js +1 -0
  9. package/dist/assets/RuntimeConfig-KsKfkjgv.js +1 -0
  10. package/dist/assets/{SecretsConfig-DlNYyu9I.js → SecretsConfig-KkgMzdt1.js} +2 -2
  11. package/dist/assets/{SessionsConfig-Dt9heKTD.js → SessionsConfig-CWBp8IPf.js} +2 -2
  12. package/dist/assets/{card-KmfXQ4Bm.js → card-D7NY0Szf.js} +1 -1
  13. package/dist/assets/index-BRBYYgR_.js +2 -0
  14. package/dist/assets/{label-iF47BkaM.js → label-Ojs7Al6B.js} +1 -1
  15. package/dist/assets/{logos-C8Yako2a.js → logos-B1qBsCSi.js} +1 -1
  16. package/dist/assets/{page-layout-sZk-HtoA.js → page-layout-CUMMO0nN.js} +1 -1
  17. package/dist/assets/{switch-BnYsX3uS.js → switch-BdhS_16-.js} +1 -1
  18. package/dist/assets/{tabs-custom-BEBNjTRA.js → tabs-custom-D261E5EA.js} +1 -1
  19. package/dist/assets/{useConfig-DJ_KEVx0.js → useConfig-txxbxXnT.js} +1 -1
  20. package/dist/assets/{useConfirmDialog-BwqRK1uy.js → useConfirmDialog-BUKGHDL6.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/src/components/layout/Sidebar.tsx +55 -13
  29. package/src/lib/i18n.ts +1 -0
  30. package/dist/assets/ChatPage-BEBmDqtw.js +0 -32
  31. package/dist/assets/CronConfig-BdLVPoNw.js +0 -1
  32. package/dist/assets/DocBrowser-DzsZUzgQ.js +0 -1
  33. package/dist/assets/ProvidersList-snuzM5CK.js +0 -1
  34. package/dist/assets/RuntimeConfig-BSDP51k6.js +0 -1
  35. 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
- const navItems = [
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
- {navItems.map((item) => {
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' },