vzcode 2.15.0 → 2.17.0

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 (51) hide show
  1. package/dist/assets/{bindings_wasm_bg-9w8E-TmY.wasm → bindings_wasm_bg-D9vNLG9F.wasm} +0 -0
  2. package/dist/assets/buildWorker-DL4wXpRr.js +695 -0
  3. package/dist/assets/index-BiPmUreW.js +474 -0
  4. package/dist/assets/{index-DLm5FQ0E.css → index-BvGPtSrr.css} +1 -1
  5. package/dist/assets/worker-RJ-cjbld.js +327 -0
  6. package/dist/index.html +2 -2
  7. package/dist/server/aiChatHandler/chatOperations.js +168 -35
  8. package/dist/server/aiChatHandler/index.js +11 -30
  9. package/dist/server/aiChatHandler/llmStreaming.js +105 -118
  10. package/package.json +11 -11
  11. package/src/client/AIAssist/AIAssistWidget/index.tsx +12 -1
  12. package/src/client/App/useShareDB.ts +1 -1
  13. package/src/client/CodeEditor/index.tsx +9 -10
  14. package/src/client/VZCodeContext/types.ts +3 -1
  15. package/src/client/VZCodeContext/useVZCodeState.ts +7 -5
  16. package/src/client/VZRight.tsx +10 -2
  17. package/src/client/VZSidebar/AIChat/ChatInput.tsx +1 -7
  18. package/src/client/VZSidebar/AIChat/DiffView.scss +1 -0
  19. package/src/client/VZSidebar/AIChat/DiffView.tsx +72 -29
  20. package/src/client/VZSidebar/AIChat/FileEditingIndicator.tsx +89 -0
  21. package/src/client/VZSidebar/AIChat/IndividualFileDiff.tsx +86 -0
  22. package/src/client/VZSidebar/AIChat/JumpToLatestButton.tsx +64 -0
  23. package/src/client/VZSidebar/AIChat/Message.tsx +124 -56
  24. package/src/client/VZSidebar/AIChat/MessageList.tsx +155 -114
  25. package/src/client/VZSidebar/AIChat/ThinkingScratchpad.tsx +4 -118
  26. package/src/client/VZSidebar/AIChat/index.tsx +59 -19
  27. package/src/client/VZSidebar/AIChat/styles.scss +190 -43
  28. package/src/client/VZSidebar/Item.tsx +2 -2
  29. package/src/client/VZSidebar/Search.tsx +13 -6
  30. package/src/client/VZSidebar/VisualEditor/VisualEditor.tsx +39 -23
  31. package/src/client/VZSidebar/VisualEditor/utils.ts +1 -1
  32. package/src/client/VZSidebar/index.tsx +12 -10
  33. package/src/client/VZSidebar/useDragAndDrop.tsx +225 -214
  34. package/src/client/featureFlags.ts +3 -0
  35. package/src/client/hooks/useAutoScroll.ts +328 -0
  36. package/src/client/tabsSearchParameters.ts +1 -17
  37. package/src/client/useFileCRUD.ts +5 -2
  38. package/src/client/useKeyboardShortcuts.ts +7 -0
  39. package/src/client/useOpenDirectories.ts +2 -1
  40. package/src/client/usePrettier/index.ts +1 -1
  41. package/src/client/useURLSync.ts +1 -0
  42. package/src/client/utils/scrollUtils.ts +170 -0
  43. package/src/client/vzReducer/searchReducer.ts +6 -3
  44. package/src/server/aiChatHandler/chatOperations.ts +224 -43
  45. package/src/server/aiChatHandler/index.ts +8 -48
  46. package/src/server/aiChatHandler/llmStreaming.ts +149 -170
  47. package/src/types.ts +81 -1
  48. package/dist/assets/buildWorker-Bi6wsfQk.js +0 -695
  49. package/dist/assets/index-BpUpNto4.js +0 -461
  50. package/dist/assets/worker-BSzbM0Uo.js +0 -330
  51. package/src/client/VZSidebar/AIChat/StreamingMessage.tsx +0 -35
@@ -0,0 +1,64 @@
1
+ import React, { useCallback } from 'react';
2
+
3
+ /**
4
+ * Props for the JumpToLatestButton component
5
+ */
6
+ interface JumpToLatestButtonProps {
7
+ /** Whether the button is visible */
8
+ visible: boolean;
9
+ /** Callback when button is clicked */
10
+ onClick: () => void;
11
+ /** Additional CSS class name */
12
+ className?: string;
13
+ }
14
+
15
+ /**
16
+ * Circular "down arrow" button that floats at the bottom center of scroll area
17
+ * Appears when auto-scroll is disabled and user is not at bottom
18
+ */
19
+ export const JumpToLatestButton: React.FC<
20
+ JumpToLatestButtonProps
21
+ > = ({ visible, onClick, className = '' }) => {
22
+ /**
23
+ * Handle button click
24
+ */
25
+ const handleClick = useCallback(() => {
26
+ onClick();
27
+ }, [onClick]);
28
+
29
+ /**
30
+ * Handle keyboard events for accessibility
31
+ */
32
+ const handleKeyDown = useCallback(
33
+ (event: React.KeyboardEvent) => {
34
+ if (event.key === 'Enter' || event.key === ' ') {
35
+ event.preventDefault();
36
+ onClick();
37
+ }
38
+ },
39
+ [onClick],
40
+ );
41
+
42
+ if (!visible) {
43
+ return null;
44
+ }
45
+
46
+ return (
47
+ <button
48
+ className={`jump-to-latest-button ${className}`.trim()}
49
+ onClick={handleClick}
50
+ onKeyDown={handleKeyDown}
51
+ aria-label="Jump to latest"
52
+ tabIndex={0}
53
+ type="button"
54
+ >
55
+ {/* Down arrow icon using Unicode or can be replaced with SVG */}
56
+ <span
57
+ className="jump-to-latest-icon"
58
+ aria-hidden="true"
59
+ >
60
+
61
+ </span>
62
+ </button>
63
+ );
64
+ };
@@ -1,77 +1,145 @@
1
+ import React, {
2
+ useMemo,
3
+ useContext,
4
+ forwardRef,
5
+ } from 'react';
1
6
  import { timestampToDate } from '@vizhub/viz-utils';
2
7
  import Markdown from 'react-markdown';
3
8
  import remarkGfm from 'remark-gfm';
4
- import React, { useMemo, memo, useContext } from 'react';
5
- import { DiffView } from './DiffView';
9
+ import { StreamingEvent } from '../../../types.js';
10
+ import { IndividualFileDiff } from './IndividualFileDiff';
11
+ import { VZCodeContext } from '../../VZCodeContext';
12
+ import { DiffView, DiffViewRef } from './DiffView';
6
13
  import { UnifiedFilesDiff } from '../../../utils/fileDiff';
7
14
  import { enableDiffView } from '../../featureFlags';
8
- import { VZCodeContext } from '../../VZCodeContext';
15
+
16
+ const DEBUG = false;
9
17
 
10
18
  interface MessageProps {
11
19
  id: string;
12
20
  role: 'user' | 'assistant';
13
- content: string;
21
+ content?: string; // For non-streaming messages
14
22
  timestamp: number;
15
- isStreaming?: boolean;
16
- diffData?: UnifiedFilesDiff;
23
+ events?: StreamingEvent[]; // For streaming messages
24
+ isActive?: boolean; // Is this the currently streaming message?
17
25
  chatId?: string;
18
26
  showAdditionalWidgets?: boolean;
27
+ isStreaming?: boolean;
28
+ diffData?: UnifiedFilesDiff;
19
29
  }
20
30
 
21
- const MessageComponent = ({
22
- id,
23
- role,
24
- content,
25
- timestamp,
26
- isStreaming,
27
- diffData,
28
- chatId,
29
- showAdditionalWidgets = false,
30
- }: MessageProps) => {
31
- const { additionalWidgets, handleSendMessage } =
32
- useContext(VZCodeContext);
31
+ export const Message = forwardRef<
32
+ DiffViewRef,
33
+ React.PropsWithChildren<MessageProps>
34
+ >(
35
+ (
36
+ {
37
+ id,
38
+ role,
39
+ content,
40
+ timestamp,
41
+ events = [],
42
+ isActive,
43
+ children,
44
+ chatId,
45
+ showAdditionalWidgets = false,
46
+ isStreaming = false,
47
+ diffData,
48
+ },
49
+ ref,
50
+ ) => {
51
+ const { additionalWidgets, handleSendMessage } =
52
+ useContext(VZCodeContext);
33
53
 
34
- // Memoize date formatting to avoid repeated computation
35
- const formattedTime = useMemo(() => {
36
- return timestampToDate(timestamp).toLocaleTimeString(
37
- [],
38
- {
39
- hour: '2-digit',
40
- minute: '2-digit',
41
- },
42
- );
43
- }, [timestamp]);
54
+ DEBUG &&
55
+ console.log(
56
+ 'StreamingMessage: Rendered with events:',
57
+ events,
58
+ );
59
+
60
+ // Memoize date formatting to avoid repeated computation
61
+ const formattedTime = useMemo(() => {
62
+ return timestampToDate(timestamp).toLocaleTimeString(
63
+ [],
64
+ {
65
+ hour: '2-digit',
66
+ minute: '2-digit',
67
+ },
68
+ );
69
+ }, [timestamp]);
44
70
 
45
- // Memoize the className string to avoid recreation
46
- const messageClassName = useMemo(() => {
47
- return `ai-chat-message ${role}${isStreaming ? ' streaming' : ''}`;
48
- }, [role, isStreaming]);
71
+ // Memoize the className string to avoid recreation
72
+ const messageClassName = useMemo(() => {
73
+ return `ai-chat-message ${role}${isStreaming ? ' streaming' : ''}`;
74
+ }, [role, isStreaming]);
49
75
 
50
- return (
51
- <div className={messageClassName}>
52
- <div className="ai-chat-message-content">
53
- <Markdown remarkPlugins={[remarkGfm]}>
54
- {content}
55
- </Markdown>
56
- {enableDiffView &&
57
- diffData &&
58
- Object.keys(diffData).length > 0 && (
59
- <DiffView diffData={diffData} />
76
+ return (
77
+ <div className={messageClassName}>
78
+ <div className="ai-chat-message-content">
79
+ {/* Render regular content for non-streaming messages */}
80
+ {content && (
81
+ <Markdown remarkPlugins={[remarkGfm]}>
82
+ {content}
83
+ </Markdown>
60
84
  )}
61
- {showAdditionalWidgets &&
62
- additionalWidgets &&
63
- chatId &&
64
- additionalWidgets({
65
- messageId: id,
66
- chatId: chatId,
67
- handleSendMessage,
85
+
86
+ {/* Render diff view if present */}
87
+ {enableDiffView &&
88
+ diffData &&
89
+ Object.keys(diffData).length > 0 && (
90
+ <DiffView diffData={diffData} ref={ref} />
91
+ )}
92
+
93
+ {/* Render events in order for streaming messages */}
94
+ {events.map((event, index) => {
95
+ switch (event.type) {
96
+ case 'text_chunk':
97
+ return (
98
+ <div
99
+ key={`text-${index}`}
100
+ className="text-chunk"
101
+ >
102
+ <Markdown remarkPlugins={[remarkGfm]}>
103
+ {event.content}
104
+ </Markdown>
105
+ </div>
106
+ );
107
+ case 'file_complete':
108
+ return (
109
+ <IndividualFileDiff
110
+ key={`file-${event.fileName}-${index}`}
111
+ fileName={event.fileName}
112
+ beforeContent={
113
+ event.beforeContent || ''
114
+ }
115
+ afterContent={event.afterContent || ''}
116
+ />
117
+ );
118
+ case 'file_start':
119
+ // File start events are now handled by centralized status logic in MessageList
120
+ return null;
121
+ default:
122
+ return null;
123
+ }
68
124
  })}
125
+ {/* Additional widgets are now shown within the "Done" status indicator */}
126
+ {showAdditionalWidgets &&
127
+ additionalWidgets &&
128
+ chatId &&
129
+ !isStreaming && // Only show here for non-streaming messages (completed messages)
130
+ additionalWidgets({
131
+ messageId: id,
132
+ chatId: chatId,
133
+ handleSendMessage,
134
+ })}
135
+ {isActive && children}
136
+ </div>
137
+ <div className="ai-chat-message-time">
138
+ {formattedTime}
139
+ </div>
69
140
  </div>
70
- <div className="ai-chat-message-time">
71
- {formattedTime}
72
- </div>
73
- </div>
74
- );
75
- };
141
+ );
142
+ },
143
+ );
76
144
 
77
- export const Message = memo(MessageComponent);
145
+ Message.displayName = 'Message';
@@ -1,141 +1,84 @@
1
1
  import {
2
2
  useRef,
3
3
  useEffect,
4
- useCallback,
5
4
  memo,
6
5
  useState,
6
+ useContext,
7
7
  } from 'react';
8
8
  import { Message } from './Message';
9
9
  import { TypingIndicator } from './TypingIndicator';
10
10
  import { ThinkingScratchpad } from './ThinkingScratchpad';
11
+ import { AIEditingStatusIndicator } from './FileEditingIndicator';
11
12
  import { VizChatMessage } from '@vizhub/viz-types';
13
+ import { ExtendedVizChatMessage } from '../../../types.js';
14
+ import { DiffViewRef } from './DiffView.js';
15
+ import { VZCodeContext } from '../../VZCodeContext';
12
16
 
13
17
  const MessageListComponent = ({
14
18
  messages,
15
19
  isLoading,
16
20
  chatId, // Add chatId prop
17
21
  aiScratchpad, // Add aiScratchpad prop
22
+ currentStatus, // Add current status prop
23
+ onNewEvent,
24
+ onJumpToLatest,
25
+ beforeRender,
26
+ afterRender,
18
27
  }: {
19
28
  messages: VizChatMessage[];
20
29
  isLoading: boolean;
21
30
  chatId?: string; // Add chatId to the type
22
31
  aiScratchpad?: string; // Add aiScratchpad to the type
32
+ currentStatus?: string; // Add current status to the type
33
+ onNewEvent: (targetElement?: HTMLElement) => void;
34
+ onJumpToLatest: (targetElement?: HTMLElement) => void;
35
+ beforeRender: () => number;
36
+ afterRender: (prevScrollHeight: number) => void;
23
37
  }) => {
24
38
  const messagesEndRef = useRef<HTMLDivElement>(null);
25
- const messagesContainerRef = useRef<HTMLDivElement>(null);
26
- const [isUserScrolled, setIsUserScrolled] =
27
- useState(false);
28
- const [autoScrollEnabled, setAutoScrollEnabled] =
29
- useState(true);
30
- const scrollTimeoutRef =
31
- useRef<ReturnType<typeof setTimeout>>();
32
-
33
- // Check if the user is scrolled to the bottom
34
- const isScrolledToBottom = useCallback(() => {
35
- const container = messagesContainerRef.current;
36
- if (!container) return true;
37
-
38
- const threshold = 50; // Allow 50px tolerance for "at bottom"
39
- const { scrollTop, scrollHeight, clientHeight } =
40
- container;
41
- return (
42
- scrollHeight - scrollTop - clientHeight < threshold
43
- );
44
- }, []);
45
-
46
- // Smooth scroll to bottom with linear transition
47
- const scrollToBottom = useCallback(() => {
48
- if (!autoScrollEnabled) return;
49
-
50
- messagesEndRef.current?.scrollIntoView({
51
- behavior: 'smooth',
52
- block: 'end',
53
- });
54
- }, [autoScrollEnabled]);
55
-
56
- // Handle scroll events to detect user manual scrolling
57
- const handleScroll = useCallback(() => {
58
- const container = messagesContainerRef.current;
59
- if (!container) return;
60
-
61
- const isAtBottom = isScrolledToBottom();
62
-
63
- // Clear any existing timeout
64
- if (scrollTimeoutRef.current) {
65
- clearTimeout(scrollTimeoutRef.current);
66
- }
39
+ const diffViewRef = useRef<DiffViewRef>(null);
67
40
 
68
- // If user scrolled up from bottom, disable auto-scroll
69
- if (!isAtBottom && !isUserScrolled) {
70
- setIsUserScrolled(true);
71
- setAutoScrollEnabled(false);
72
- }
41
+ // Get additional widgets from context
42
+ const { additionalWidgets, handleSendMessage } =
43
+ useContext(VZCodeContext);
73
44
 
74
- // If user scrolled back to bottom, re-enable auto-scroll after a brief delay
75
- if (isAtBottom && isUserScrolled) {
76
- scrollTimeoutRef.current = setTimeout(() => {
77
- setIsUserScrolled(false);
78
- setAutoScrollEnabled(true);
79
- }, 500); // 500ms delay to prevent flickering
80
- }
81
- }, [isUserScrolled, isScrolledToBottom]);
45
+ // Track previous loading state to detect when AI generation completes
46
+ const [prevIsLoading, setPrevIsLoading] =
47
+ useState(isLoading);
82
48
 
83
- // Auto-scroll when messages change, but only if auto-scroll is enabled
49
+ // Auto-scroll when messages change
84
50
  useEffect(() => {
85
- if (autoScrollEnabled && !isUserScrolled) {
86
- // Use a short debounce to prevent multiple scroll calls during rapid updates
87
- if (scrollTimeoutRef.current) {
88
- clearTimeout(scrollTimeoutRef.current);
89
- }
90
-
91
- scrollTimeoutRef.current = setTimeout(() => {
92
- scrollToBottom();
93
- }, 100); // 100ms debounce
51
+ // Get scroll height before render for anchoring
52
+ const prevScrollHeight = beforeRender();
53
+
54
+ // Determine if we should scroll to a specific diff or to the bottom
55
+ const lastMessageHasDiff =
56
+ messages.length > 0 &&
57
+ (messages[messages.length - 1] as any).diffData;
58
+
59
+ let targetElement: HTMLElement | null = null;
60
+ if (lastMessageHasDiff && diffViewRef.current) {
61
+ targetElement =
62
+ diffViewRef.current.getFirstHunkElement();
94
63
  }
95
64
 
96
- return () => {
97
- if (scrollTimeoutRef.current) {
98
- clearTimeout(scrollTimeoutRef.current);
99
- }
100
- };
101
- }, [
102
- messages,
103
- autoScrollEnabled,
104
- isUserScrolled,
105
- scrollToBottom,
106
- ]);
65
+ // Trigger auto-scroll if enabled
66
+ onNewEvent(targetElement);
107
67
 
108
- // Track previous loading state to detect when AI generation completes
109
- const [prevIsLoading, setPrevIsLoading] =
110
- useState(isLoading);
68
+ // Adjust scroll position after render for anchoring
69
+ afterRender(prevScrollHeight);
70
+ }, [messages, onNewEvent, beforeRender, afterRender]);
111
71
 
112
72
  // Scroll to bottom when AI generation completes (loading changes from true to false)
113
73
  useEffect(() => {
114
- // If AI was generating and now it's finished, scroll to bottom
74
+ // If AI was generating and now it's finished, force scroll to bottom
115
75
  if (prevIsLoading && !isLoading) {
116
- // Force scroll to bottom regardless of user scroll state
117
- // This ensures we always scroll to bottom when generation completes
118
- messagesEndRef.current?.scrollIntoView({
119
- behavior: 'smooth',
120
- block: 'end',
121
- });
122
-
123
- // Re-enable auto-scroll for future messages
124
- setIsUserScrolled(false);
125
- setAutoScrollEnabled(true);
76
+ // Force jump to latest regardless of current auto-scroll state
77
+ onJumpToLatest();
126
78
  }
127
79
 
128
80
  setPrevIsLoading(isLoading);
129
- }, [isLoading, prevIsLoading]);
130
-
131
- // Clean up timeout on unmount
132
- useEffect(() => {
133
- return () => {
134
- if (scrollTimeoutRef.current) {
135
- clearTimeout(scrollTimeoutRef.current);
136
- }
137
- };
138
- }, []);
81
+ }, [isLoading, prevIsLoading, onJumpToLatest]);
139
82
 
140
83
  // Check if AI generation has started (last message is from assistant)
141
84
  const lastMessage = messages[messages.length - 1];
@@ -152,6 +95,56 @@ const MessageListComponent = ({
152
95
  aiScratchpad && aiScratchpad.trim(),
153
96
  );
154
97
 
98
+ // Determine the single, most relevant status to display with priority:
99
+ // 1. Active file editing status from streaming events
100
+ // 2. General AI status from currentStatus
101
+ const getConsolidatedStatus = () => {
102
+ // Check if there's an active streaming message with file editing
103
+ if (lastMessage && lastMessage.role === 'assistant') {
104
+ const extendedMsg =
105
+ lastMessage as ExtendedVizChatMessage;
106
+ if (
107
+ extendedMsg.streamingEvents &&
108
+ extendedMsg.streamingEvents.length > 0
109
+ ) {
110
+ // Look for active file editing (file_start without corresponding file_complete)
111
+ const fileStates = new Map<string, boolean>();
112
+
113
+ extendedMsg.streamingEvents.forEach((event) => {
114
+ if (event.type === 'file_start') {
115
+ fileStates.set(event.fileName, false); // Mark as editing
116
+ } else if (event.type === 'file_complete') {
117
+ fileStates.set(event.fileName, true); // Mark as complete
118
+ }
119
+ });
120
+
121
+ // Find the first file that's still being edited
122
+ for (const [fileName, isComplete] of fileStates) {
123
+ if (!isComplete) {
124
+ return {
125
+ status: `Editing ${fileName}...`,
126
+ fileName,
127
+ };
128
+ }
129
+ }
130
+ }
131
+ }
132
+
133
+ // Fall back to general AI status if no active file editing
134
+ if (currentStatus) {
135
+ return { status: currentStatus };
136
+ }
137
+
138
+ return null;
139
+ };
140
+
141
+ const consolidatedStatus = getConsolidatedStatus();
142
+
143
+ // Show consolidated status indicator when there's a status to display
144
+ const showConsolidatedStatusIndicator = Boolean(
145
+ consolidatedStatus,
146
+ );
147
+
155
148
  // Find the most recent assistant message index
156
149
  const lastAssistantMessageIndex = messages
157
150
  .map((m, i) => (m.role === 'assistant' ? i : -1))
@@ -161,37 +154,85 @@ const MessageListComponent = ({
161
154
  return (
162
155
  <div
163
156
  className="ai-chat-messages"
164
- ref={messagesContainerRef}
165
- onScroll={handleScroll}
157
+ role="log"
158
+ aria-live="polite"
159
+ aria-relevant="additions"
166
160
  >
167
161
  {messages.map((msg, index) => {
162
+ const isLastMessage = index === messages.length - 1;
163
+ // Cast to extended message to check for streaming events
164
+ const extendedMsg = msg as ExtendedVizChatMessage;
165
+
166
+ // Check if this is a streaming message
167
+ const isStreamingMessage =
168
+ !!extendedMsg.isProgressive;
169
+
168
170
  // Only show additionalWidgets for the most recent assistant message
169
171
  const showAdditionalWidgets =
170
172
  msg.role === 'assistant' &&
171
173
  index === lastAssistantMessageIndex;
172
174
 
175
+ // Use Message for all messages now
173
176
  return (
174
177
  <Message
175
178
  key={msg.id}
176
179
  id={msg.id}
177
180
  role={msg.role}
178
- content={msg.content}
181
+ content={
182
+ isStreamingMessage ? undefined : msg.content
183
+ }
179
184
  timestamp={msg.timestamp}
180
- diffData={(msg as any).diffData}
185
+ events={
186
+ isStreamingMessage
187
+ ? extendedMsg.streamingEvents || []
188
+ : []
189
+ }
190
+ isActive={index === lastAssistantMessageIndex}
181
191
  chatId={chatId}
182
192
  showAdditionalWidgets={showAdditionalWidgets}
183
- />
193
+ isStreaming={isStreamingMessage}
194
+ diffData={(msg as any).diffData}
195
+ ref={
196
+ isLastMessage && (msg as any).diffData
197
+ ? diffViewRef
198
+ : null
199
+ }
200
+ >
201
+ {isStreamingMessage &&
202
+ showThinkingScratchpad && (
203
+ <ThinkingScratchpad
204
+ content={aiScratchpad || ''}
205
+ isVisible={showThinkingScratchpad}
206
+ />
207
+ )}
208
+
209
+ {isStreamingMessage &&
210
+ showConsolidatedStatusIndicator && (
211
+ <AIEditingStatusIndicator
212
+ status={consolidatedStatus?.status || ''}
213
+ fileName={consolidatedStatus?.fileName}
214
+ additionalWidgets={
215
+ consolidatedStatus?.status === 'Done' &&
216
+ showAdditionalWidgets &&
217
+ additionalWidgets &&
218
+ chatId
219
+ ? additionalWidgets({
220
+ messageId: msg.id,
221
+ chatId: chatId,
222
+ handleSendMessage,
223
+ })
224
+ : undefined
225
+ }
226
+ />
227
+ )}
228
+
229
+ {isStreamingMessage && showTypingIndicator && (
230
+ <TypingIndicator />
231
+ )}
232
+ </Message>
184
233
  );
185
234
  })}
186
235
 
187
- {showThinkingScratchpad && (
188
- <ThinkingScratchpad
189
- content={aiScratchpad || ''}
190
- isVisible={showThinkingScratchpad}
191
- />
192
- )}
193
-
194
- {showTypingIndicator && <TypingIndicator />}
195
236
  <div ref={messagesEndRef} />
196
237
  </div>
197
238
  );