vzcode 2.16.0 → 2.18.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.
package/dist/index.html CHANGED
@@ -20,8 +20,8 @@
20
20
  href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap"
21
21
  rel="stylesheet"
22
22
  />
23
- <script type="module" crossorigin src="/assets/index-C2BLPyQP.js"></script>
24
- <link rel="stylesheet" crossorigin href="/assets/index-DVR90LwF.css">
23
+ <script type="module" crossorigin src="/assets/index-DuDXdJWC.js"></script>
24
+ <link rel="stylesheet" crossorigin href="/assets/index-BvGPtSrr.css">
25
25
  </head>
26
26
  <body>
27
27
  <div id="root"></div>
@@ -1,8 +1,6 @@
1
1
  import { assembleFullPrompt, prepareFilesForPrompt, } from 'editcodewithai';
2
2
  import { formatMarkdownFiles } from 'llm-code-format';
3
3
  import { createFilesSnapshot, generateFilesUnifiedDiff, } from '../../utils/fileDiff.js';
4
- import { formatFiles } from '../prettier.js';
5
- import { createSubmitOperation } from '../../submitOperation.js';
6
4
  // Dev flag for waiting 1 second before starting the LLM function.
7
5
  // Useful for debugging and testing purposes, e.g. checking the typing indicator.
8
6
  const delayStart = false;
@@ -33,11 +31,11 @@ export const performAIChat = async ({ prompt, shareDBDoc, llmFunction, }) => {
33
31
  * Performs AI editing operations using streaming with incremental OT operations
34
32
  */
35
33
  export const performAIEditing = async ({ prompt, shareDBDoc, llmFunction, runCode, }) => {
36
- // 1. Capture the current state of files before editing
34
+ // Capture the current state of files before editing
37
35
  const beforeFiles = createFilesSnapshot(shareDBDoc.data.files);
38
36
  const { files } = prepareFilesForPrompt(shareDBDoc.data.files);
39
37
  const filesContext = formatMarkdownFiles(files);
40
- // 2. Assemble the final prompt
38
+ // Assemble the final prompt
41
39
  const fullPrompt = assembleFullPrompt({
42
40
  filesContext,
43
41
  prompt,
@@ -46,37 +44,8 @@ export const performAIEditing = async ({ prompt, shareDBDoc, llmFunction, runCod
46
44
  if (delayStart) {
47
45
  await new Promise((resolve) => setTimeout(resolve, 1000));
48
46
  }
49
- // Call the LLM function which will handle streaming and incremental file updates
47
+ // Call the LLM function which will handle streaming, incremental file updates, and Prettier formatting
50
48
  const result = await llmFunction(fullPrompt);
51
- // 3. Run Prettier on changed files before running code
52
- const afterLLMFiles = createFilesSnapshot(shareDBDoc.data.files);
53
- // Find files that were changed by the AI
54
- const changedFileIds = Object.keys(afterLLMFiles).filter((fileId) => beforeFiles[fileId]?.text !==
55
- afterLLMFiles[fileId]?.text);
56
- if (changedFileIds.length > 0) {
57
- // Format the changed files
58
- const formattedFiles = await formatFiles(shareDBDoc.data.files, changedFileIds);
59
- // Apply formatted versions to shareDBDoc if formatting succeeded
60
- if (Object.keys(formattedFiles).length > 0) {
61
- const submitOperation = createSubmitOperation(shareDBDoc);
62
- submitOperation((document) => {
63
- const updatedFiles = { ...document.files };
64
- // Apply each formatted file
65
- Object.entries(formattedFiles).forEach(([fileId, formattedText]) => {
66
- if (updatedFiles[fileId]) {
67
- updatedFiles[fileId] = {
68
- ...updatedFiles[fileId],
69
- text: formattedText,
70
- };
71
- }
72
- });
73
- return {
74
- ...document,
75
- files: updatedFiles,
76
- };
77
- });
78
- }
79
- }
80
49
  runCode();
81
50
  // 4. Capture the state of files after editing and formatting, then generate diff
82
51
  const afterFiles = createFilesSnapshot(shareDBDoc.data.files);
@@ -278,6 +278,8 @@ export const updateFiles = (shareDBDoc, files) => {
278
278
  ...shareDBDoc.data,
279
279
  files,
280
280
  });
281
+ DEBUG && console.log('updateFiles op:');
282
+ DEBUG && console.log(JSON.stringify(filesOp, null, 2));
281
283
  shareDBDoc.submitOp(filesOp);
282
284
  };
283
285
  /**
@@ -1,9 +1,8 @@
1
1
  import OpenAI from 'openai';
2
2
  import { parseMarkdownFiles, StreamingMarkdownParser, } from 'llm-code-format';
3
3
  import { mergeFileChanges } from 'editcodewithai';
4
- import { generateRunId } from '@vizhub/viz-utils';
5
4
  import { updateFiles, updateAIScratchpad, createStreamingAIMessage, addStreamingEvent, updateStreamingStatus, finalizeStreamingMessage, } from './chatOperations.js';
6
- import { diff } from '../../ot.js';
5
+ import { formatFiles } from '../prettier.js';
7
6
  const DEBUG = false;
8
7
  // Useful for testing/debugging the streaming behavior
9
8
  const slowMode = false;
@@ -191,37 +190,20 @@ enableReasoningTokens = false, model, aiRequestOptions, }) => {
191
190
  if (currentEditingFileName) {
192
191
  await completeFileEditing(currentEditingFileName);
193
192
  }
193
+ // // Capture the current state of files before applying changes
194
+ // const beforeFiles = createFilesSnapshot(
195
+ // shareDBDoc.data.files,
196
+ // );
197
+ // Parse the full content to extract file changes
198
+ // export type FileCollection = Record<string, string>;
199
+ const newFilesUnformatted = parseMarkdownFiles(fullContent, 'bold').files;
200
+ // Run Prettier on `newFiles` before applying them
201
+ const newFilesFormatted = await formatFiles(newFilesUnformatted);
194
202
  // Apply all the edits at once
195
- updateFiles(shareDBDoc, mergeFileChanges(shareDBDoc.data.files, parseMarkdownFiles(fullContent, 'bold').files));
203
+ const mergedChanges = mergeFileChanges(shareDBDoc.data.files, newFilesFormatted);
204
+ updateFiles(shareDBDoc, mergedChanges);
196
205
  // Finalize streaming message
197
- await finalizeStreamingMessage(shareDBDoc, chatId);
198
- // Generate a new runId to trigger a run when AI finishes editing
199
- // This will trigger a re-run without hot reloading
200
- const newRunId = generateRunId();
201
- const runIdOp = diff(shareDBDoc.data, {
202
- ...shareDBDoc.data,
203
- runId: newRunId,
204
- });
205
- shareDBDoc.submitOp(runIdOp, (error) => {
206
- if (error) {
207
- console.warn('Error setting runId after AI editing:', error);
208
- }
209
- else {
210
- DEBUG &&
211
- console.log('Set new runId after AI editing:', newRunId);
212
- }
213
- });
214
- // Write chunks file for debugging
215
- // if (DEBUG) {
216
- // const chunksFileJSONpath = `./ai-chunks-${chatId}.json`;
217
- // fs.writeFileSync(
218
- // chunksFileJSONpath,
219
- // JSON.stringify(chunks, null, 2),
220
- // );
221
- // console.log(
222
- // `AI chunks written to ${chunksFileJSONpath}`,
223
- // );
224
- // }
206
+ finalizeStreamingMessage(shareDBDoc, chatId);
225
207
  return {
226
208
  content: fullContent,
227
209
  generationId: generationId,
@@ -72,16 +72,25 @@ export const formatFile = async (fileText, fileName) => {
72
72
  * Only formats files that have supported extensions
73
73
  * Returns a map of fileId -> formatted text for successfully formatted files
74
74
  */
75
- export const formatFiles = async (files, fileIds) => {
75
+ export const formatFiles = async (fileCollection) => {
76
76
  const results = {};
77
- const targetFileIds = fileIds || Object.keys(files);
77
+ const targetFileIds = Object.keys(fileCollection);
78
78
  // Process files in parallel for better performance
79
79
  const formatPromises = targetFileIds.map(async (fileId) => {
80
- const file = files[fileId];
81
- if (!file)
80
+ const fileData = fileCollection[fileId];
81
+ if (!fileData)
82
82
  return;
83
- const formatted = await formatFile(file.text, file.name);
84
- if (formatted !== null && formatted !== file.text) {
83
+ // Handle both FileCollection format and test format
84
+ const fileName = typeof fileData === 'string'
85
+ ? fileId
86
+ : fileData.name;
87
+ const fileText = typeof fileData === 'string'
88
+ ? fileData
89
+ : fileData.text;
90
+ if (!fileText)
91
+ return;
92
+ const formatted = await formatFile(fileText, fileName);
93
+ if (formatted !== null && formatted !== fileText) {
85
94
  results[fileId] = formatted;
86
95
  }
87
96
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vzcode",
3
- "version": "2.16.0",
3
+ "version": "2.18.0",
4
4
  "description": "Multiplayer code editor system",
5
5
  "main": "src/index.ts",
6
6
  "type": "module",
@@ -130,15 +130,15 @@
130
130
  "@replit/codemirror-vscode-keymap": "^6.0.2",
131
131
  "@teamwork/websocket-json-stream": "^2.0.0",
132
132
  "@typescript/vfs": "^1.6.1",
133
- "@uiw/codemirror-theme-abcdef": "^4.25.1",
134
- "@uiw/codemirror-theme-dracula": "^4.25.1",
135
- "@uiw/codemirror-theme-eclipse": "^4.25.1",
136
- "@uiw/codemirror-theme-github": "^4.25.1",
137
- "@uiw/codemirror-theme-material": "^4.25.1",
138
- "@uiw/codemirror-theme-nord": "^4.25.1",
139
- "@uiw/codemirror-theme-okaidia": "^4.25.1",
140
- "@uiw/codemirror-theme-xcode": "^4.25.1",
141
- "@uiw/codemirror-themes": "^4.25.1",
133
+ "@uiw/codemirror-theme-abcdef": "^4.25.2",
134
+ "@uiw/codemirror-theme-dracula": "^4.25.2",
135
+ "@uiw/codemirror-theme-eclipse": "^4.25.2",
136
+ "@uiw/codemirror-theme-github": "^4.25.2",
137
+ "@uiw/codemirror-theme-material": "^4.25.2",
138
+ "@uiw/codemirror-theme-nord": "^4.25.2",
139
+ "@uiw/codemirror-theme-okaidia": "^4.25.2",
140
+ "@uiw/codemirror-theme-xcode": "^4.25.2",
141
+ "@uiw/codemirror-themes": "^4.25.2",
142
142
  "@valtown/codemirror-ts": "^2.3.1",
143
143
  "@vizhub/runtime": "^4.5.0",
144
144
  "@vizhub/viz-types": "^0.5.0",
@@ -147,7 +147,7 @@
147
147
  "bootstrap-icons": "^1.13.1",
148
148
  "codemirror": "^6.0.2",
149
149
  "codemirror-copilot": "^0.0.7",
150
- "codemirror-ot": "^5.0.0",
150
+ "codemirror-ot": "^5.6.0",
151
151
  "color-hash": "^2.0.2",
152
152
  "comlink": "^4.4.2",
153
153
  "d3-array": "^3.2.4",
@@ -157,7 +157,7 @@
157
157
  "diff2html": "^3.4.52",
158
158
  "dotenv": "^17.2.2",
159
159
  "editcodewithai": "^2.4.0",
160
- "eslint-linter-browserify": "^9.35.0",
160
+ "eslint-linter-browserify": "^9.36.0",
161
161
  "express": "^5.1.0",
162
162
  "ignore": "^7.0.5",
163
163
  "json0-ot-diff": "^1.1.2",
@@ -181,7 +181,7 @@
181
181
  "ws": "^8.18.3"
182
182
  },
183
183
  "devDependencies": {
184
- "@eslint/js": "^9.35.0",
184
+ "@eslint/js": "^9.36.0",
185
185
  "@tauri-apps/cli": "^2.8.4",
186
186
  "@types/d3-color": "^3.1.3",
187
187
  "@types/react": "^18",
@@ -191,12 +191,12 @@
191
191
  "@vitejs/plugin-react": "^5.0.3",
192
192
  "concurrently": "^9.2.1",
193
193
  "cross-env": "^10.0.0",
194
- "eslint": "^9.35.0",
194
+ "eslint": "^9.36.0",
195
195
  "eslint-plugin-jsx-a11y": "^6.10.2",
196
196
  "eslint-plugin-react": "^7.37.5",
197
197
  "eslint-plugin-react-hooks": "^5.2.0",
198
198
  "globals": "^16.4.0",
199
- "npm-check-updates": "^18.1.1",
199
+ "npm-check-updates": "^18.2.1",
200
200
  "prettier": "^3.6.2",
201
201
  "sass": "^1.92.1",
202
202
  "ts-node": "^10.9.2",
@@ -205,7 +205,7 @@
205
205
  "vitest": "^3.2.4"
206
206
  },
207
207
  "optionalDependencies": {
208
- "@rollup/rollup-darwin-arm64": "^4.50.2",
209
- "@rollup/rollup-win32-x64-msvc": "^4.50.2"
208
+ "@rollup/rollup-darwin-arm64": "^4.51.0",
209
+ "@rollup/rollup-win32-x64-msvc": "^4.51.0"
210
210
  }
211
211
  }
@@ -32,7 +32,6 @@ export const CodeEditor = ({
32
32
  const {
33
33
  activePane,
34
34
  shareDBDoc,
35
- content,
36
35
  submitOperation,
37
36
  localPresence,
38
37
  docPresence,
@@ -152,9 +151,7 @@ export const CodeEditor = ({
152
151
  useLayoutEffect(() => {
153
152
  // Guard against cases where page is still loading.
154
153
  if (!codeEditorRef.current) return;
155
- if (!content) return;
156
154
  if (!editorCacheValue) return; // Guard against null editorCacheValue
157
- // if (openSplitPane) return;
158
155
 
159
156
  const codeEditorEl = codeEditorRef.current;
160
157
 
@@ -178,7 +175,7 @@ export const CodeEditor = ({
178
175
  );
179
176
  }
180
177
  };
181
- }, [codeEditorRef, content, editorCacheValue]);
178
+ }, [codeEditorRef, editorCacheValue]);
182
179
 
183
180
  // Focus the editor
184
181
  useEffect(() => {
@@ -3,11 +3,12 @@ import { Spinner } from '../../AIAssist/Spinner';
3
3
  interface AIEditingStatusIndicatorProps {
4
4
  status: string;
5
5
  fileName?: string;
6
+ additionalWidgets?: React.ReactNode;
6
7
  }
7
8
 
8
9
  export const AIEditingStatusIndicator: React.FC<
9
10
  AIEditingStatusIndicatorProps
10
- > = ({ status, fileName }) => {
11
+ > = ({ status, fileName, additionalWidgets }) => {
11
12
  const getStatusDisplay = (status: string) => {
12
13
  switch (status) {
13
14
  case 'Analyzing request...':
@@ -36,9 +37,16 @@ export const AIEditingStatusIndicator: React.FC<
36
37
  );
37
38
  case 'Done':
38
39
  return (
39
- <>
40
- <span>Done</span>
41
- </>
40
+ <div className="file-editing-done-container">
41
+ <div className="file-editing-done-status">
42
+ ✅ <span>Done</span>
43
+ </div>
44
+ {additionalWidgets && (
45
+ <div className="file-editing-done-widgets">
46
+ {additionalWidgets}
47
+ </div>
48
+ )}
49
+ </div>
42
50
  );
43
51
  default:
44
52
  // Handle file editing status (e.g., "Editing filename.js...")
@@ -1,31 +1,36 @@
1
- import { timestampToDate } from '@vizhub/viz-utils';
2
- import Markdown from 'react-markdown';
3
- import remarkGfm from 'remark-gfm';
4
1
  import React, {
5
2
  useMemo,
6
- memo,
7
3
  useContext,
8
4
  forwardRef,
9
5
  } from 'react';
6
+ import { timestampToDate } from '@vizhub/viz-utils';
7
+ import Markdown from 'react-markdown';
8
+ import remarkGfm from 'remark-gfm';
9
+ import { StreamingEvent } from '../../../types.js';
10
+ import { IndividualFileDiff } from './IndividualFileDiff';
11
+ import { VZCodeContext } from '../../VZCodeContext';
10
12
  import { DiffView, DiffViewRef } from './DiffView';
11
13
  import { UnifiedFilesDiff } from '../../../utils/fileDiff';
12
14
  import { enableDiffView } from '../../featureFlags';
13
- import { VZCodeContext } from '../../VZCodeContext';
15
+
16
+ const DEBUG = false;
14
17
 
15
18
  interface MessageProps {
16
19
  id: string;
17
20
  role: 'user' | 'assistant';
18
- content: string;
21
+ content?: string; // For non-streaming messages
19
22
  timestamp: number;
20
- isStreaming?: boolean;
21
- diffData?: UnifiedFilesDiff;
23
+ events?: StreamingEvent[]; // For streaming messages
24
+ isActive?: boolean; // Is this the currently streaming message?
22
25
  chatId?: string;
23
26
  showAdditionalWidgets?: boolean;
27
+ isStreaming?: boolean;
28
+ diffData?: UnifiedFilesDiff;
24
29
  }
25
30
 
26
- const MessageComponent = forwardRef<
31
+ export const Message = forwardRef<
27
32
  DiffViewRef,
28
- MessageProps
33
+ React.PropsWithChildren<MessageProps>
29
34
  >(
30
35
  (
31
36
  {
@@ -33,16 +38,25 @@ const MessageComponent = forwardRef<
33
38
  role,
34
39
  content,
35
40
  timestamp,
36
- isStreaming,
37
- diffData,
41
+ events = [],
42
+ isActive,
43
+ children,
38
44
  chatId,
39
45
  showAdditionalWidgets = false,
46
+ isStreaming = false,
47
+ diffData,
40
48
  },
41
49
  ref,
42
50
  ) => {
43
51
  const { additionalWidgets, handleSendMessage } =
44
52
  useContext(VZCodeContext);
45
53
 
54
+ DEBUG &&
55
+ console.log(
56
+ 'StreamingMessage: Rendered with events:',
57
+ events,
58
+ );
59
+
46
60
  // Memoize date formatting to avoid repeated computation
47
61
  const formattedTime = useMemo(() => {
48
62
  return timestampToDate(timestamp).toLocaleTimeString(
@@ -62,22 +76,63 @@ const MessageComponent = forwardRef<
62
76
  return (
63
77
  <div className={messageClassName}>
64
78
  <div className="ai-chat-message-content">
65
- <Markdown remarkPlugins={[remarkGfm]}>
66
- {content}
67
- </Markdown>
79
+ {/* Render regular content for non-streaming messages */}
80
+ {content && (
81
+ <Markdown remarkPlugins={[remarkGfm]}>
82
+ {content}
83
+ </Markdown>
84
+ )}
85
+
86
+ {/* Render diff view if present */}
68
87
  {enableDiffView &&
69
88
  diffData &&
70
89
  Object.keys(diffData).length > 0 && (
71
90
  <DiffView diffData={diffData} ref={ref} />
72
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
+ }
124
+ })}
125
+ {/* Additional widgets are now shown within the "Done" status indicator */}
73
126
  {showAdditionalWidgets &&
74
127
  additionalWidgets &&
75
128
  chatId &&
129
+ !isStreaming && // Only show here for non-streaming messages (completed messages)
76
130
  additionalWidgets({
77
131
  messageId: id,
78
132
  chatId: chatId,
79
133
  handleSendMessage,
80
134
  })}
135
+ {isActive && children}
81
136
  </div>
82
137
  <div className="ai-chat-message-time">
83
138
  {formattedTime}
@@ -87,6 +142,4 @@ const MessageComponent = forwardRef<
87
142
  },
88
143
  );
89
144
 
90
- MessageComponent.displayName = 'MessageComponent';
91
-
92
- export const Message = memo(MessageComponent);
145
+ Message.displayName = 'Message';
@@ -1,12 +1,18 @@
1
- import { useRef, useEffect, memo, useState } from 'react';
1
+ import {
2
+ useRef,
3
+ useEffect,
4
+ memo,
5
+ useState,
6
+ useContext,
7
+ } from 'react';
2
8
  import { Message } from './Message';
3
- import { StreamingMessage } from './StreamingMessage';
4
9
  import { TypingIndicator } from './TypingIndicator';
5
10
  import { ThinkingScratchpad } from './ThinkingScratchpad';
6
11
  import { AIEditingStatusIndicator } from './FileEditingIndicator';
7
12
  import { VizChatMessage } from '@vizhub/viz-types';
8
13
  import { ExtendedVizChatMessage } from '../../../types.js';
9
14
  import { DiffViewRef } from './DiffView.js';
15
+ import { VZCodeContext } from '../../VZCodeContext';
10
16
 
11
17
  const MessageListComponent = ({
12
18
  messages,
@@ -32,6 +38,10 @@ const MessageListComponent = ({
32
38
  const messagesEndRef = useRef<HTMLDivElement>(null);
33
39
  const diffViewRef = useRef<DiffViewRef>(null);
34
40
 
41
+ // Get additional widgets from context
42
+ const { additionalWidgets, handleSendMessage } =
43
+ useContext(VZCodeContext);
44
+
35
45
  // Track previous loading state to detect when AI generation completes
36
46
  const [prevIsLoading, setPrevIsLoading] =
37
47
  useState(isLoading);
@@ -162,54 +172,64 @@ const MessageListComponent = ({
162
172
  msg.role === 'assistant' &&
163
173
  index === lastAssistantMessageIndex;
164
174
 
165
- // Use StreamingMessage for assistant messages with streaming events
166
- if (
167
- msg.role === 'assistant' &&
168
- isStreamingMessage
169
- ) {
170
- return (
171
- <StreamingMessage
172
- key={msg.id}
173
- timestamp={msg.timestamp}
174
- events={extendedMsg.streamingEvents || []}
175
- isActive={index === lastAssistantMessageIndex}
176
- >
177
- {showThinkingScratchpad && (
178
- <ThinkingScratchpad
179
- content={aiScratchpad || ''}
180
- isVisible={showThinkingScratchpad}
181
- />
182
- )}
183
-
184
- {showConsolidatedStatusIndicator && (
185
- <AIEditingStatusIndicator
186
- status={consolidatedStatus?.status || ''}
187
- fileName={consolidatedStatus?.fileName}
188
- />
189
- )}
190
-
191
- {showTypingIndicator && <TypingIndicator />}
192
- </StreamingMessage>
193
- );
194
- }
195
-
196
- // Use regular Message component for user messages and non-streaming assistant messages
175
+ // Use Message for all messages now
197
176
  return (
198
177
  <Message
199
178
  key={msg.id}
200
179
  id={msg.id}
201
180
  role={msg.role}
202
- content={msg.content}
181
+ content={
182
+ isStreamingMessage ? undefined : msg.content
183
+ }
203
184
  timestamp={msg.timestamp}
204
- diffData={(msg as any).diffData}
185
+ events={
186
+ isStreamingMessage
187
+ ? extendedMsg.streamingEvents || []
188
+ : []
189
+ }
190
+ isActive={index === lastAssistantMessageIndex}
205
191
  chatId={chatId}
206
192
  showAdditionalWidgets={showAdditionalWidgets}
193
+ isStreaming={isStreamingMessage}
194
+ diffData={(msg as any).diffData}
207
195
  ref={
208
196
  isLastMessage && (msg as any).diffData
209
197
  ? diffViewRef
210
198
  : null
211
199
  }
212
- />
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>
213
233
  );
214
234
  })}
215
235
 
@@ -78,9 +78,6 @@ export const AIChat = () => {
78
78
  afterRender,
79
79
  } = useAutoScroll({ threshold: 24 });
80
80
 
81
- // TODO fix this - currently always false
82
- console.log('showJumpButton:', showJumpButton);
83
-
84
81
  // Get the active chat ID and chat data
85
82
  // If selectedChatId is set, use it; otherwise, if no chat selected, don't default to currentChatId
86
83
  const activeChatId = selectedChatId;