@nextclaw/ui 0.6.10 → 0.6.11

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 (80) hide show
  1. package/.eslintrc.cjs +10 -0
  2. package/CHANGELOG.md +9 -0
  3. package/dist/assets/{ChannelsList-TyMb5Mgz.js → ChannelsList-C49JQ-Zt.js} +1 -1
  4. package/dist/assets/ChatPage-DIx05c6s.js +36 -0
  5. package/dist/assets/{DocBrowser-CNtrA0ps.js → DocBrowser-CpOosDEI.js} +1 -1
  6. package/dist/assets/{LogoBadge-BLqiOM5D.js → LogoBadge-CL_8ZPXU.js} +1 -1
  7. package/dist/assets/MarketplacePage-BOzko5s9.js +49 -0
  8. package/dist/assets/{ModelConfig-CCsQ8KFq.js → ModelConfig-BZ4ZfaQB.js} +1 -1
  9. package/dist/assets/ProvidersList-fPpJ5gl6.js +1 -0
  10. package/dist/assets/{RuntimeConfig-BO6s-ls-.js → RuntimeConfig-Dt9pLB9P.js} +1 -1
  11. package/dist/assets/{SecretsConfig-mayFdxpM.js → SecretsConfig-C1PU0Yy8.js} +2 -2
  12. package/dist/assets/{SessionsConfig-DAIczdBj.js → SessionsConfig-EskBOofQ.js} +2 -2
  13. package/dist/assets/{card-BP5YnL-G.js → card-C7Gtw2Vs.js} +1 -1
  14. package/dist/assets/index-Cn6_2To7.js +8 -0
  15. package/dist/assets/{index-BUiahmWm.css → index-nEYGCJTC.css} +1 -1
  16. package/dist/assets/{input-B1D2QX0O.js → input-oBvxsnV9.js} +1 -1
  17. package/dist/assets/{label-DW0j-fXA.js → label-C7F8lMpQ.js} +1 -1
  18. package/dist/assets/{page-layout-Ch-H9gD-.js → page-layout-DO8BlScF.js} +1 -1
  19. package/dist/assets/session-run-status-Kg0FwAPn.js +3 -0
  20. package/dist/assets/{switch-_cZHlGKB.js → switch-C6a5GyZB.js} +1 -1
  21. package/dist/assets/{tabs-custom-ARxqYYjG.js → tabs-custom-BatFap5k.js} +1 -1
  22. package/dist/assets/{useConfirmDialog-BaU7nIat.js → useConfirmDialog-zJzVKMdu.js} +2 -2
  23. package/dist/assets/{vendor-C--HHaLf.js → vendor-TlME1INH.js} +84 -84
  24. package/dist/index.html +3 -3
  25. package/package.json +4 -2
  26. package/src/App.tsx +1 -2
  27. package/src/api/config.ts +199 -200
  28. package/src/api/types.ts +36 -24
  29. package/src/components/chat/ChatConversationPanel.tsx +102 -121
  30. package/src/components/chat/ChatPage.tsx +165 -437
  31. package/src/components/chat/ChatSidebar.tsx +30 -36
  32. package/src/components/chat/ChatThread.tsx +73 -131
  33. package/src/components/chat/chat-input/ChatInputBarView.tsx +82 -0
  34. package/src/components/chat/chat-input/ChatInputBottomToolbar.tsx +71 -0
  35. package/src/components/chat/chat-input/components/ChatInputModelStateHint.tsx +39 -0
  36. package/src/components/chat/chat-input/components/ChatInputSelectedSkillsSection.tsx +31 -0
  37. package/src/components/chat/chat-input/components/ChatInputSlashPanelSection.tsx +112 -0
  38. package/src/components/chat/chat-input/components/bottom-toolbar/ChatInputAttachButton.tsx +24 -0
  39. package/src/components/chat/chat-input/components/bottom-toolbar/ChatInputModelSelector.tsx +58 -0
  40. package/src/components/chat/chat-input/components/bottom-toolbar/ChatInputSendControls.tsx +56 -0
  41. package/src/components/chat/chat-input/components/bottom-toolbar/ChatInputSessionTypeSelector.tsx +40 -0
  42. package/src/components/chat/chat-input/useChatInputBarController.ts +313 -0
  43. package/src/components/chat/chat-input.types.ts +15 -0
  44. package/src/components/chat/chat-page-data.ts +121 -0
  45. package/src/components/chat/chat-page-runtime.ts +221 -0
  46. package/src/components/chat/chat-session-route.ts +59 -0
  47. package/src/components/chat/chat-stream/nextbot-parsers.ts +52 -0
  48. package/src/components/chat/chat-stream/nextbot-runtime-agent.ts +413 -0
  49. package/src/components/chat/chat-stream/stream-event-adapter.ts +98 -0
  50. package/src/components/chat/chat-stream/transport.ts +159 -0
  51. package/src/components/chat/chat-stream/types.ts +76 -0
  52. package/src/components/chat/managers/chat-input.manager.ts +142 -0
  53. package/src/components/chat/managers/chat-run-status.manager.ts +32 -0
  54. package/src/components/chat/managers/chat-session-list.manager.ts +77 -0
  55. package/src/components/chat/managers/chat-stream-actions.manager.ts +34 -0
  56. package/src/components/chat/managers/chat-thread.manager.ts +86 -0
  57. package/src/components/chat/managers/chat-ui.manager.ts +65 -0
  58. package/src/components/chat/presenter/chat-presenter-context.tsx +25 -0
  59. package/src/components/chat/presenter/chat.presenter.ts +32 -0
  60. package/src/components/chat/stores/chat-input.store.ts +62 -0
  61. package/src/components/chat/stores/chat-run-status.store.ts +30 -0
  62. package/src/components/chat/stores/chat-session-list.store.ts +34 -0
  63. package/src/components/chat/stores/chat-thread.store.ts +52 -0
  64. package/src/components/chat/useChatRuntimeController.ts +134 -0
  65. package/src/components/chat/useChatSessionTypeState.ts +148 -0
  66. package/src/components/common/MaskedInput.tsx +1 -1
  67. package/src/hooks/useConfig.ts +31 -1
  68. package/src/hooks/useObservable.ts +20 -0
  69. package/src/lib/chat-message.ts +2 -202
  70. package/src/lib/chat-runtime-utils.ts +250 -0
  71. package/src/lib/i18n.ts +9 -0
  72. package/tsconfig.json +2 -1
  73. package/vite.config.ts +2 -1
  74. package/dist/assets/ChatPage-CQerYqvy.js +0 -34
  75. package/dist/assets/MarketplacePage-CotZxxNe.js +0 -49
  76. package/dist/assets/ProvidersList-BYYX5K_g.js +0 -1
  77. package/dist/assets/index-D6_5HaDl.js +0 -7
  78. package/dist/assets/session-run-status-BUYsQeWs.js +0 -5
  79. package/src/components/chat/ChatInputBar.tsx +0 -590
  80. package/src/components/chat/useChatStreamController.ts +0 -591
@@ -1,44 +1,15 @@
1
- import type { MutableRefObject } from 'react';
2
- import type { MarketplaceInstalledRecord, SessionEventView } from '@/api/types';
1
+ import { useCallback, useEffect, useLayoutEffect, useRef } from 'react';
3
2
  import { Button } from '@/components/ui/button';
4
3
  import { ChatThread } from '@/components/chat/ChatThread';
5
- import { ChatInputBar, type ChatModelOption } from '@/components/chat/ChatInputBar';
6
4
  import { ChatWelcome } from '@/components/chat/ChatWelcome';
5
+ import { ChatInputBarView } from '@/components/chat/chat-input/ChatInputBarView';
6
+ import { usePresenter } from '@/components/chat/presenter/chat-presenter-context';
7
+ import { useChatThreadStore } from '@/components/chat/stores/chat-thread.store';
7
8
  import { t } from '@/lib/i18n';
9
+ import { cn } from '@/lib/utils';
8
10
  import { Trash2 } from 'lucide-react';
9
11
 
10
- type ChatConversationPanelProps = {
11
- isProviderStateResolved: boolean;
12
- modelOptions: ChatModelOption[];
13
- selectedModel: string;
14
- onSelectedModelChange: (value: string) => void;
15
- onGoToProviders: () => void;
16
- skillRecords: MarketplaceInstalledRecord[];
17
- isSkillsLoading?: boolean;
18
- selectedSkills: string[];
19
- onSelectedSkillsChange: (next: string[]) => void;
20
- selectedSessionKey: string | null;
21
- sessionDisplayName?: string;
22
- canDeleteSession: boolean;
23
- isDeletePending: boolean;
24
- onDeleteSession: () => void;
25
- onCreateSession: () => void;
26
- threadRef: MutableRefObject<HTMLDivElement | null>;
27
- onThreadScroll: () => void;
28
- isHistoryLoading: boolean;
29
- mergedEvents: SessionEventView[];
30
- isSending: boolean;
31
- isAwaitingAssistantOutput: boolean;
32
- streamingAssistantText: string;
33
- draft: string;
34
- onDraftChange: (value: string) => void;
35
- onSend: () => Promise<void> | void;
36
- onStop: () => Promise<void> | void;
37
- canStopGeneration: boolean;
38
- stopDisabledReason?: string | null;
39
- sendError?: string | null;
40
- queuedCount: number;
41
- };
12
+ const STICKY_BOTTOM_THRESHOLD_PX = 10;
42
13
 
43
14
  function ChatConversationSkeleton() {
44
15
  return (
@@ -68,80 +39,101 @@ function ChatConversationSkeleton() {
68
39
  );
69
40
  }
70
41
 
71
- export function ChatConversationPanel({
72
- isProviderStateResolved,
73
- modelOptions,
74
- selectedModel,
75
- onSelectedModelChange,
76
- onGoToProviders,
77
- skillRecords,
78
- isSkillsLoading = false,
79
- selectedSkills,
80
- onSelectedSkillsChange,
81
- selectedSessionKey,
82
- sessionDisplayName,
83
- canDeleteSession,
84
- isDeletePending,
85
- onDeleteSession,
86
- onCreateSession,
87
- threadRef,
88
- onThreadScroll,
89
- isHistoryLoading,
90
- mergedEvents,
91
- isSending,
92
- isAwaitingAssistantOutput,
93
- streamingAssistantText,
94
- draft,
95
- onDraftChange,
96
- onSend,
97
- onStop,
98
- canStopGeneration,
99
- stopDisabledReason,
100
- sendError,
101
- queuedCount,
102
- }: ChatConversationPanelProps) {
103
- const showWelcome = !selectedSessionKey && mergedEvents.length === 0;
104
- const hasConfiguredModel = modelOptions.length > 0;
105
- const shouldShowProviderHint = isProviderStateResolved && !hasConfiguredModel;
42
+ export function ChatConversationPanel() {
43
+ const presenter = usePresenter();
44
+ const snapshot = useChatThreadStore((state) => state.snapshot);
45
+ const fallbackThreadRef = useRef<HTMLDivElement | null>(null);
46
+ const threadRef = snapshot.threadRef ?? fallbackThreadRef;
47
+
48
+ // --- Sticky-to-bottom scroll state ---
49
+ const isStickyRef = useRef(true);
50
+ const isProgrammaticScrollRef = useRef(false);
51
+ const previousSessionKeyRef = useRef<string | null>(null);
52
+ const pendingInitialScrollRef = useRef(false);
53
+
54
+ const showWelcome = !snapshot.selectedSessionKey && snapshot.uiMessages.length === 0 && !snapshot.isSending;
55
+ const hasConfiguredModel = snapshot.modelOptions.length > 0;
56
+ const shouldShowProviderHint = snapshot.isProviderStateResolved && !hasConfiguredModel;
106
57
  const hideEmptyHint =
107
- isHistoryLoading &&
108
- mergedEvents.length === 0 &&
109
- !isSending &&
110
- !isAwaitingAssistantOutput &&
111
- !streamingAssistantText.trim();
58
+ snapshot.isHistoryLoading &&
59
+ snapshot.uiMessages.length === 0 &&
60
+ !snapshot.isSending &&
61
+ !snapshot.isAwaitingAssistantOutput;
62
+
63
+ const scrollToBottom = useCallback(() => {
64
+ const el = threadRef.current;
65
+ if (!el) return;
66
+ isProgrammaticScrollRef.current = true;
67
+ el.scrollTop = el.scrollHeight;
68
+ }, [threadRef]);
69
+
70
+ const handleScroll = useCallback(() => {
71
+ // Skip sticky check for programmatic scrolls
72
+ if (isProgrammaticScrollRef.current) {
73
+ isProgrammaticScrollRef.current = false;
74
+ return;
75
+ }
76
+ const el = threadRef.current;
77
+ if (!el) return;
78
+ const distanceFromBottom = el.scrollHeight - el.scrollTop - el.clientHeight;
79
+ isStickyRef.current = distanceFromBottom <= STICKY_BOTTOM_THRESHOLD_PX;
80
+ }, [threadRef]);
81
+
82
+ // Session change → force sticky + schedule initial scroll
83
+ useEffect(() => {
84
+ if (previousSessionKeyRef.current === snapshot.selectedSessionKey) return;
85
+ previousSessionKeyRef.current = snapshot.selectedSessionKey;
86
+ isStickyRef.current = true;
87
+ pendingInitialScrollRef.current = true;
88
+ }, [snapshot.selectedSessionKey]);
89
+
90
+ // Initial scroll after history loads for a new session
91
+ useLayoutEffect(() => {
92
+ if (!pendingInitialScrollRef.current) return;
93
+ if (snapshot.isHistoryLoading || snapshot.uiMessages.length === 0) return;
94
+ pendingInitialScrollRef.current = false;
95
+ scrollToBottom();
96
+ }, [scrollToBottom, snapshot.isHistoryLoading, snapshot.uiMessages]);
97
+
98
+ // Streaming updates: keep bottom visible while still sticky.
99
+ useLayoutEffect(() => {
100
+ if (!isStickyRef.current) return;
101
+ if (snapshot.uiMessages.length === 0) return;
102
+ scrollToBottom();
103
+ }, [scrollToBottom, snapshot.uiMessages]);
112
104
 
113
- if (!isProviderStateResolved) {
105
+ if (!snapshot.isProviderStateResolved) {
114
106
  return <ChatConversationSkeleton />;
115
107
  }
116
108
 
117
109
  return (
118
110
  <section className="flex-1 min-h-0 flex flex-col overflow-hidden bg-gradient-to-b from-gray-50/60 to-white">
119
- {/* Minimal top bar - only shown when session is active */}
120
- {selectedSessionKey && (
121
- <div className="px-5 py-3 border-b border-gray-200/60 bg-white/80 backdrop-blur-sm flex items-center justify-between shrink-0">
122
- <div className="min-w-0 flex-1">
123
- <span className="text-sm font-medium text-gray-700 truncate">
124
- {sessionDisplayName || selectedSessionKey}
125
- </span>
126
- </div>
127
- <Button
128
- variant="ghost"
129
- size="icon"
130
- className="rounded-lg shrink-0 text-gray-400 hover:text-destructive"
131
- onClick={onDeleteSession}
132
- disabled={!canDeleteSession || isDeletePending}
133
- >
134
- <Trash2 className="h-4 w-4" />
135
- </Button>
111
+ <div className={cn(
112
+ "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",
113
+ snapshot.selectedSessionKey ? "py-3 opacity-100" : "h-0 py-0 opacity-0 border-b-0"
114
+ )}>
115
+ <div className="min-w-0 flex-1">
116
+ <span className="text-sm font-medium text-gray-700 truncate">
117
+ {snapshot.sessionDisplayName || snapshot.selectedSessionKey}
118
+ </span>
136
119
  </div>
137
- )}
120
+ <Button
121
+ variant="ghost"
122
+ size="icon"
123
+ className="rounded-lg shrink-0 text-gray-400 hover:text-destructive"
124
+ onClick={presenter.chatThreadManager.deleteSession}
125
+ disabled={!snapshot.canDeleteSession || snapshot.isDeletePending}
126
+ >
127
+ <Trash2 className="h-4 w-4" />
128
+ </Button>
129
+ </div>
138
130
 
139
131
  {shouldShowProviderHint && (
140
132
  <div className="px-5 py-2.5 border-b border-amber-200/70 bg-amber-50/70 flex items-center justify-between gap-3 shrink-0">
141
133
  <span className="text-xs text-amber-800">{t('chatModelNoOptions')}</span>
142
134
  <button
143
135
  type="button"
144
- onClick={onGoToProviders}
136
+ onClick={presenter.chatThreadManager.goToProviders}
145
137
  className="text-xs font-semibold text-amber-900 underline-offset-2 hover:underline"
146
138
  >
147
139
  {t('chatGoConfigureProvider')}
@@ -149,42 +141,31 @@ export function ChatConversationPanel({
149
141
  </div>
150
142
  )}
151
143
 
152
- {/* Message thread or welcome */}
153
- <div ref={threadRef} onScroll={onThreadScroll} className="flex-1 min-h-0 overflow-y-auto custom-scrollbar">
144
+ {snapshot.sessionTypeUnavailable && snapshot.sessionTypeUnavailableMessage?.trim() && (
145
+ <div className="px-5 py-2.5 border-b border-amber-200/70 bg-amber-50/70 shrink-0">
146
+ <span className="text-xs text-amber-800">{snapshot.sessionTypeUnavailableMessage}</span>
147
+ </div>
148
+ )}
149
+
150
+ <div
151
+ ref={threadRef}
152
+ onScroll={handleScroll}
153
+ className="flex-1 min-h-0 overflow-y-auto custom-scrollbar"
154
+ >
154
155
  {showWelcome ? (
155
- <ChatWelcome onCreateSession={onCreateSession} />
156
+ <ChatWelcome onCreateSession={presenter.chatThreadManager.createSession} />
156
157
  ) : hideEmptyHint ? (
157
158
  <div className="h-full" />
158
- ) : mergedEvents.length === 0 ? (
159
+ ) : snapshot.uiMessages.length === 0 ? (
159
160
  <div className="px-5 py-5 text-sm text-gray-500">{t('chatNoMessages')}</div>
160
161
  ) : (
161
162
  <div className="mx-auto w-full max-w-[min(1120px,100%)] px-6 py-5">
162
- <ChatThread events={mergedEvents} isSending={isSending && isAwaitingAssistantOutput} />
163
+ <ChatThread uiMessages={snapshot.uiMessages} isSending={snapshot.isSending && snapshot.isAwaitingAssistantOutput} />
163
164
  </div>
164
165
  )}
165
166
  </div>
166
167
 
167
- {/* Enhanced input bar */}
168
- <ChatInputBar
169
- isProviderStateResolved={isProviderStateResolved}
170
- draft={draft}
171
- onDraftChange={onDraftChange}
172
- onSend={onSend}
173
- onStop={onStop}
174
- onGoToProviders={onGoToProviders}
175
- canStopGeneration={canStopGeneration}
176
- stopDisabledReason={stopDisabledReason}
177
- sendError={sendError}
178
- isSending={isSending}
179
- queuedCount={queuedCount}
180
- modelOptions={modelOptions}
181
- selectedModel={selectedModel}
182
- onSelectedModelChange={onSelectedModelChange}
183
- skillRecords={skillRecords}
184
- isSkillsLoading={isSkillsLoading}
185
- selectedSkills={selectedSkills}
186
- onSelectedSkillsChange={onSelectedSkillsChange}
187
- />
168
+ <ChatInputBarView />
188
169
  </section>
189
170
  );
190
171
  }