@nextclaw/ui 0.6.10 → 0.6.12

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 (90) hide show
  1. package/.eslintrc.cjs +10 -0
  2. package/CHANGELOG.md +16 -0
  3. package/dist/assets/ChannelsList-DBDjwf-X.js +1 -0
  4. package/dist/assets/ChatPage-C18sGGk1.js +36 -0
  5. package/dist/assets/DocBrowser-ZOplDEMS.js +1 -0
  6. package/dist/assets/LogoBadge-2LMzEMwe.js +1 -0
  7. package/dist/assets/MarketplacePage-D4JHYcB5.js +49 -0
  8. package/dist/assets/ModelConfig-DZVvdLFq.js +1 -0
  9. package/dist/assets/ProvidersList-Dum31480.js +1 -0
  10. package/dist/assets/{RuntimeConfig-BO6s-ls-.js → RuntimeConfig-4sb3mpkd.js} +1 -1
  11. package/dist/assets/SearchConfig-B4u_MxRG.js +1 -0
  12. package/dist/assets/{SecretsConfig-mayFdxpM.js → SecretsConfig-BQXblZvb.js} +2 -2
  13. package/dist/assets/SessionsConfig-Jk29xjQU.js +2 -0
  14. package/dist/assets/{card-BP5YnL-G.js → card-BekAnCgX.js} +1 -1
  15. package/dist/assets/config-layout-BHnOoweL.js +1 -0
  16. package/dist/assets/index-BXwjfCEO.css +1 -0
  17. package/dist/assets/index-Dl6t70wA.js +8 -0
  18. package/dist/assets/{input-B1D2QX0O.js → input-MMn_Na9q.js} +1 -1
  19. package/dist/assets/{label-DW0j-fXA.js → label-Dg2ydpN0.js} +1 -1
  20. package/dist/assets/{page-layout-Ch-H9gD-.js → page-layout-7K0rcz0I.js} +1 -1
  21. package/dist/assets/session-run-status-CAdjSqeb.js +3 -0
  22. package/dist/assets/{switch-_cZHlGKB.js → switch-DnDMlDVu.js} +1 -1
  23. package/dist/assets/{tabs-custom-ARxqYYjG.js → tabs-custom-khLM8lWj.js} +1 -1
  24. package/dist/assets/{useConfirmDialog-BaU7nIat.js → useConfirmDialog-BYA1XnVU.js} +2 -2
  25. package/dist/assets/{vendor-C--HHaLf.js → vendor-d7E8OgNx.js} +84 -84
  26. package/dist/index.html +3 -3
  27. package/package.json +4 -2
  28. package/src/App.tsx +3 -2
  29. package/src/api/config.ts +212 -200
  30. package/src/api/types.ts +93 -24
  31. package/src/components/chat/ChatConversationPanel.tsx +102 -121
  32. package/src/components/chat/ChatPage.tsx +165 -437
  33. package/src/components/chat/ChatSidebar.tsx +30 -36
  34. package/src/components/chat/ChatThread.tsx +73 -131
  35. package/src/components/chat/chat-input/ChatInputBarView.tsx +82 -0
  36. package/src/components/chat/chat-input/ChatInputBottomToolbar.tsx +71 -0
  37. package/src/components/chat/chat-input/components/ChatInputModelStateHint.tsx +39 -0
  38. package/src/components/chat/chat-input/components/ChatInputSelectedSkillsSection.tsx +31 -0
  39. package/src/components/chat/chat-input/components/ChatInputSlashPanelSection.tsx +112 -0
  40. package/src/components/chat/chat-input/components/bottom-toolbar/ChatInputAttachButton.tsx +24 -0
  41. package/src/components/chat/chat-input/components/bottom-toolbar/ChatInputModelSelector.tsx +58 -0
  42. package/src/components/chat/chat-input/components/bottom-toolbar/ChatInputSendControls.tsx +56 -0
  43. package/src/components/chat/chat-input/components/bottom-toolbar/ChatInputSessionTypeSelector.tsx +40 -0
  44. package/src/components/chat/chat-input/useChatInputBarController.ts +313 -0
  45. package/src/components/chat/chat-input.types.ts +15 -0
  46. package/src/components/chat/chat-page-data.ts +121 -0
  47. package/src/components/chat/chat-page-runtime.ts +221 -0
  48. package/src/components/chat/chat-session-route.ts +59 -0
  49. package/src/components/chat/chat-stream/nextbot-parsers.ts +52 -0
  50. package/src/components/chat/chat-stream/nextbot-runtime-agent.ts +413 -0
  51. package/src/components/chat/chat-stream/stream-event-adapter.ts +98 -0
  52. package/src/components/chat/chat-stream/transport.ts +159 -0
  53. package/src/components/chat/chat-stream/types.ts +76 -0
  54. package/src/components/chat/managers/chat-input.manager.ts +142 -0
  55. package/src/components/chat/managers/chat-run-status.manager.ts +32 -0
  56. package/src/components/chat/managers/chat-session-list.manager.ts +77 -0
  57. package/src/components/chat/managers/chat-stream-actions.manager.ts +34 -0
  58. package/src/components/chat/managers/chat-thread.manager.ts +86 -0
  59. package/src/components/chat/managers/chat-ui.manager.ts +65 -0
  60. package/src/components/chat/presenter/chat-presenter-context.tsx +25 -0
  61. package/src/components/chat/presenter/chat.presenter.ts +32 -0
  62. package/src/components/chat/stores/chat-input.store.ts +62 -0
  63. package/src/components/chat/stores/chat-run-status.store.ts +30 -0
  64. package/src/components/chat/stores/chat-session-list.store.ts +34 -0
  65. package/src/components/chat/stores/chat-thread.store.ts +52 -0
  66. package/src/components/chat/useChatRuntimeController.ts +134 -0
  67. package/src/components/chat/useChatSessionTypeState.ts +148 -0
  68. package/src/components/common/MaskedInput.tsx +1 -1
  69. package/src/components/config/SearchConfig.tsx +297 -0
  70. package/src/components/layout/Sidebar.tsx +6 -1
  71. package/src/hooks/useConfig.ts +48 -1
  72. package/src/hooks/useObservable.ts +20 -0
  73. package/src/lib/chat-message.ts +2 -202
  74. package/src/lib/chat-runtime-utils.ts +250 -0
  75. package/src/lib/i18n.ts +31 -0
  76. package/tsconfig.json +2 -1
  77. package/vite.config.ts +2 -1
  78. package/dist/assets/ChannelsList-TyMb5Mgz.js +0 -1
  79. package/dist/assets/ChatPage-CQerYqvy.js +0 -34
  80. package/dist/assets/DocBrowser-CNtrA0ps.js +0 -1
  81. package/dist/assets/LogoBadge-BLqiOM5D.js +0 -1
  82. package/dist/assets/MarketplacePage-CotZxxNe.js +0 -49
  83. package/dist/assets/ModelConfig-CCsQ8KFq.js +0 -1
  84. package/dist/assets/ProvidersList-BYYX5K_g.js +0 -1
  85. package/dist/assets/SessionsConfig-DAIczdBj.js +0 -2
  86. package/dist/assets/index-BUiahmWm.css +0 -1
  87. package/dist/assets/index-D6_5HaDl.js +0 -7
  88. package/dist/assets/session-run-status-BUYsQeWs.js +0 -5
  89. package/src/components/chat/ChatInputBar.tsx +0 -590
  90. package/src/components/chat/useChatStreamController.ts +0 -591
package/src/api/types.ts CHANGED
@@ -74,6 +74,52 @@ export type ProviderConnectionTestResult = {
74
74
  hint?: string;
75
75
  };
76
76
 
77
+ export type SearchProviderName = "bocha" | "brave";
78
+ export type BochaFreshnessValue = "noLimit" | "oneDay" | "oneWeek" | "oneMonth" | "oneYear" | string;
79
+
80
+ export type SearchProviderConfigView = {
81
+ enabled: boolean;
82
+ apiKeySet: boolean;
83
+ apiKeyMasked?: string;
84
+ baseUrl: string;
85
+ docsUrl?: string;
86
+ summary?: boolean;
87
+ freshness?: BochaFreshnessValue;
88
+ };
89
+
90
+ export type SearchConfigView = {
91
+ provider: SearchProviderName;
92
+ enabledProviders: SearchProviderName[];
93
+ defaults: {
94
+ maxResults: number;
95
+ };
96
+ providers: {
97
+ bocha: SearchProviderConfigView;
98
+ brave: SearchProviderConfigView;
99
+ };
100
+ };
101
+
102
+ export type SearchConfigUpdate = {
103
+ provider?: SearchProviderName;
104
+ enabledProviders?: SearchProviderName[];
105
+ defaults?: {
106
+ maxResults?: number;
107
+ };
108
+ providers?: {
109
+ bocha?: {
110
+ apiKey?: string | null;
111
+ baseUrl?: string | null;
112
+ docsUrl?: string | null;
113
+ summary?: boolean;
114
+ freshness?: BochaFreshnessValue | null;
115
+ };
116
+ brave?: {
117
+ apiKey?: string | null;
118
+ baseUrl?: string | null;
119
+ };
120
+ };
121
+ };
122
+
77
123
  export type ProviderAuthStartResult = {
78
124
  provider: string;
79
125
  kind: "device_code";
@@ -146,6 +192,8 @@ export type SessionEntryView = {
146
192
  updatedAt: string;
147
193
  label?: string;
148
194
  preferredModel?: string;
195
+ sessionType: string;
196
+ sessionTypeMutable: boolean;
149
197
  messageCount: number;
150
198
  lastRole?: string;
151
199
  lastTimestamp?: string;
@@ -177,6 +225,8 @@ export type SessionHistoryView = {
177
225
  key: string;
178
226
  totalMessages: number;
179
227
  totalEvents: number;
228
+ sessionType: string;
229
+ sessionTypeMutable: boolean;
180
230
  metadata: Record<string, unknown>;
181
231
  messages: SessionMessageView[];
182
232
  events: SessionEventView[];
@@ -185,6 +235,7 @@ export type SessionHistoryView = {
185
235
  export type SessionPatchUpdate = {
186
236
  label?: string | null;
187
237
  preferredModel?: string | null;
238
+ sessionType?: string | null;
188
239
  clearHistory?: boolean;
189
240
  };
190
241
 
@@ -208,11 +259,42 @@ export type ChatTurnView = {
208
259
  durationMs: number;
209
260
  };
210
261
 
262
+ export type ChatTurnStreamReadyEvent = {
263
+ sessionKey: string;
264
+ requestedAt?: string;
265
+ runId?: string;
266
+ stopSupported?: boolean;
267
+ stopReason?: string;
268
+ };
269
+
270
+ export type ChatTurnStreamDeltaEvent = {
271
+ delta: string;
272
+ };
273
+
274
+ export type ChatTurnStreamSessionEvent = {
275
+ data: SessionEventView;
276
+ };
277
+
278
+ export type ChatTurnStreamErrorEvent = {
279
+ code?: string;
280
+ message?: string;
281
+ };
282
+
211
283
  export type ChatCapabilitiesView = {
212
284
  stopSupported: boolean;
213
285
  stopReason?: string;
214
286
  };
215
287
 
288
+ export type ChatSessionTypeOptionView = {
289
+ value: string;
290
+ label: string;
291
+ };
292
+
293
+ export type ChatSessionTypesView = {
294
+ defaultType: string;
295
+ options: ChatSessionTypeOptionView[];
296
+ };
297
+
216
298
  export type ChatCommandOptionView = {
217
299
  name: string;
218
300
  description: string;
@@ -267,30 +349,6 @@ export type ChatRunListView = {
267
349
  total: number;
268
350
  };
269
351
 
270
- export type ChatTurnStreamReadyEvent = {
271
- event: "ready";
272
- sessionKey: string;
273
- requestedAt: string;
274
- runId?: string;
275
- stopSupported?: boolean;
276
- stopReason?: string;
277
- };
278
-
279
- export type ChatTurnStreamDeltaEvent = {
280
- event: "delta";
281
- delta: string;
282
- };
283
-
284
- export type ChatTurnStreamSessionEvent = {
285
- event: "session_event";
286
- data: SessionEventView;
287
- };
288
-
289
- export type ChatTurnStreamFinalEvent = {
290
- event: "final";
291
- data: ChatTurnView;
292
- };
293
-
294
352
  export type CronScheduleView =
295
353
  | { kind: "at"; atMs?: number | null }
296
354
  | { kind: "every"; everyMs?: number | null }
@@ -433,6 +491,7 @@ export type ConfigView = {
433
491
  };
434
492
  };
435
493
  providers: Record<string, ProviderConfigView>;
494
+ search: SearchConfigView;
436
495
  channels: Record<string, Record<string, unknown>>;
437
496
  bindings?: AgentBindingView[];
438
497
  session?: SessionConfigView;
@@ -495,8 +554,18 @@ export type ChannelSpecView = {
495
554
  };
496
555
  };
497
556
 
557
+ export type SearchProviderSpecView = {
558
+ name: SearchProviderName;
559
+ displayName: string;
560
+ description: string;
561
+ docsUrl?: string;
562
+ isDefault?: boolean;
563
+ supportsSummary?: boolean;
564
+ };
565
+
498
566
  export type ConfigMetaView = {
499
567
  providers: ProviderSpecView[];
568
+ search: SearchProviderSpecView[];
500
569
  channels: ChannelSpecView[];
501
570
  };
502
571
 
@@ -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
  }