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/assets/{index-DVR90LwF.css → index-BvGPtSrr.css} +1 -1
- package/dist/assets/{index-C2BLPyQP.js → index-DuDXdJWC.js} +127 -124
- package/dist/assets/{worker-CmHJK_do.js → worker-ClF0pBYr.js} +74 -74
- package/dist/index.html +2 -2
- package/dist/server/aiChatHandler/aiEditing.js +3 -34
- package/dist/server/aiChatHandler/chatOperations.js +2 -0
- package/dist/server/aiChatHandler/llmStreaming.js +13 -31
- package/dist/server/prettier.js +15 -6
- package/package.json +17 -17
- package/src/client/CodeEditor/index.tsx +1 -4
- package/src/client/VZSidebar/AIChat/FileEditingIndicator.tsx +12 -4
- package/src/client/VZSidebar/AIChat/Message.tsx +71 -18
- package/src/client/VZSidebar/AIChat/MessageList.tsx +57 -37
- package/src/client/VZSidebar/AIChat/index.tsx +0 -3
- package/src/client/VZSidebar/AIChat/styles.scss +33 -29
- package/src/client/hooks/useAutoScroll.ts +0 -1
- package/src/server/aiChatHandler/aiEditing.ts +3 -52
- package/src/server/aiChatHandler/chatOperations.ts +2 -0
- package/src/server/aiChatHandler/llmStreaming.ts +25 -44
- package/src/server/prettier.ts +25 -9
- package/src/client/VZSidebar/AIChat/StreamingMessage.tsx +0 -77
|
@@ -140,8 +140,7 @@
|
|
|
140
140
|
.file-editing-indicator {
|
|
141
141
|
display: flex;
|
|
142
142
|
align-items: center;
|
|
143
|
-
|
|
144
|
-
margin: 0.5rem 0;
|
|
143
|
+
margin: 0.25rem 0;
|
|
145
144
|
background-color: var(--vscode-editor-background);
|
|
146
145
|
border: 1px solid var(--vscode-panel-border);
|
|
147
146
|
border-radius: 4px;
|
|
@@ -150,6 +149,7 @@
|
|
|
150
149
|
display: flex;
|
|
151
150
|
align-items: center;
|
|
152
151
|
gap: 0.5rem;
|
|
152
|
+
width: 100%;
|
|
153
153
|
}
|
|
154
154
|
|
|
155
155
|
.file-editing-icon {
|
|
@@ -161,6 +161,7 @@
|
|
|
161
161
|
.file-editing-text {
|
|
162
162
|
color: var(--vscode-foreground);
|
|
163
163
|
font-size: 0.9rem;
|
|
164
|
+
flex: 1;
|
|
164
165
|
|
|
165
166
|
code {
|
|
166
167
|
background-color: var(
|
|
@@ -170,6 +171,36 @@
|
|
|
170
171
|
border-radius: 3px;
|
|
171
172
|
font-family: var(--vscode-editor-font-family);
|
|
172
173
|
}
|
|
174
|
+
|
|
175
|
+
.file-editing-done-container {
|
|
176
|
+
display: flex;
|
|
177
|
+
align-items: center;
|
|
178
|
+
justify-content: space-between;
|
|
179
|
+
width: 100%;
|
|
180
|
+
gap: 0.5rem;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
.file-editing-done-status {
|
|
184
|
+
display: flex;
|
|
185
|
+
align-items: center;
|
|
186
|
+
gap: 8px;
|
|
187
|
+
font-size: 16px;
|
|
188
|
+
font-weight: 500;
|
|
189
|
+
margin-left: 4px;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
.file-editing-done-widgets {
|
|
193
|
+
display: flex;
|
|
194
|
+
align-items: center;
|
|
195
|
+
gap: 0.5rem;
|
|
196
|
+
flex-shrink: 0;
|
|
197
|
+
|
|
198
|
+
// // Override the margin-top for undo-button-container when inside file editing indicator
|
|
199
|
+
// .undo-button-container {
|
|
200
|
+
// margin-top: 0;
|
|
201
|
+
// margin-bottom: 0;
|
|
202
|
+
// }
|
|
203
|
+
}
|
|
173
204
|
}
|
|
174
205
|
}
|
|
175
206
|
|
|
@@ -288,33 +319,6 @@
|
|
|
288
319
|
line-height: 1.4;
|
|
289
320
|
word-wrap: break-word;
|
|
290
321
|
|
|
291
|
-
.undo-button-container {
|
|
292
|
-
display: flex;
|
|
293
|
-
justify-content: flex-end;
|
|
294
|
-
gap: 8px;
|
|
295
|
-
margin-top: 16px;
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
.undo-button {
|
|
299
|
-
background-color: #dc3545;
|
|
300
|
-
color: white;
|
|
301
|
-
border: none;
|
|
302
|
-
border-radius: 4px;
|
|
303
|
-
padding: 4px 8px;
|
|
304
|
-
font-size: 12px;
|
|
305
|
-
cursor: pointer;
|
|
306
|
-
transition: background-color 0.2s;
|
|
307
|
-
|
|
308
|
-
&:hover:not(:disabled) {
|
|
309
|
-
background-color: #c82333;
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
&:disabled {
|
|
313
|
-
background-color: #6c757d;
|
|
314
|
-
cursor: not-allowed;
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
|
|
318
322
|
// Markdown styling for dark backgrounds
|
|
319
323
|
pre {
|
|
320
324
|
background-color: rgba(0, 0, 0, 0.3);
|
|
@@ -148,7 +148,6 @@ export const useAutoScroll = (
|
|
|
148
148
|
// Ignore scroll events during programmatic scrolling
|
|
149
149
|
if (isProgrammaticScrollRef.current) return;
|
|
150
150
|
|
|
151
|
-
console.log('handleScroll called');
|
|
152
151
|
const container = containerRef.current;
|
|
153
152
|
if (!container) return;
|
|
154
153
|
|
|
@@ -7,9 +7,6 @@ import {
|
|
|
7
7
|
createFilesSnapshot,
|
|
8
8
|
generateFilesUnifiedDiff,
|
|
9
9
|
} from '../../utils/fileDiff.js';
|
|
10
|
-
import { formatFiles } from '../prettier.js';
|
|
11
|
-
import { createSubmitOperation } from '../../submitOperation.js';
|
|
12
|
-
import { VizContent } from '@vizhub/viz-types';
|
|
13
10
|
|
|
14
11
|
// Dev flag for waiting 1 second before starting the LLM function.
|
|
15
12
|
// Useful for debugging and testing purposes, e.g. checking the typing indicator.
|
|
@@ -60,7 +57,7 @@ export const performAIEditing = async ({
|
|
|
60
57
|
llmFunction,
|
|
61
58
|
runCode,
|
|
62
59
|
}) => {
|
|
63
|
-
//
|
|
60
|
+
// Capture the current state of files before editing
|
|
64
61
|
const beforeFiles = createFilesSnapshot(
|
|
65
62
|
shareDBDoc.data.files,
|
|
66
63
|
);
|
|
@@ -70,7 +67,7 @@ export const performAIEditing = async ({
|
|
|
70
67
|
);
|
|
71
68
|
const filesContext = formatMarkdownFiles(files);
|
|
72
69
|
|
|
73
|
-
//
|
|
70
|
+
// Assemble the final prompt
|
|
74
71
|
const fullPrompt = assembleFullPrompt({
|
|
75
72
|
filesContext,
|
|
76
73
|
prompt,
|
|
@@ -83,55 +80,9 @@ export const performAIEditing = async ({
|
|
|
83
80
|
);
|
|
84
81
|
}
|
|
85
82
|
|
|
86
|
-
// Call the LLM function which will handle streaming
|
|
83
|
+
// Call the LLM function which will handle streaming, incremental file updates, and Prettier formatting
|
|
87
84
|
const result = await llmFunction(fullPrompt);
|
|
88
85
|
|
|
89
|
-
// 3. Run Prettier on changed files before running code
|
|
90
|
-
const afterLLMFiles = createFilesSnapshot(
|
|
91
|
-
shareDBDoc.data.files,
|
|
92
|
-
);
|
|
93
|
-
|
|
94
|
-
// Find files that were changed by the AI
|
|
95
|
-
const changedFileIds = Object.keys(afterLLMFiles).filter(
|
|
96
|
-
(fileId) =>
|
|
97
|
-
beforeFiles[fileId]?.text !==
|
|
98
|
-
afterLLMFiles[fileId]?.text,
|
|
99
|
-
);
|
|
100
|
-
|
|
101
|
-
if (changedFileIds.length > 0) {
|
|
102
|
-
// Format the changed files
|
|
103
|
-
const formattedFiles = await formatFiles(
|
|
104
|
-
shareDBDoc.data.files,
|
|
105
|
-
changedFileIds,
|
|
106
|
-
);
|
|
107
|
-
|
|
108
|
-
// Apply formatted versions to shareDBDoc if formatting succeeded
|
|
109
|
-
if (Object.keys(formattedFiles).length > 0) {
|
|
110
|
-
const submitOperation =
|
|
111
|
-
createSubmitOperation(shareDBDoc);
|
|
112
|
-
submitOperation((document: VizContent) => {
|
|
113
|
-
const updatedFiles = { ...document.files };
|
|
114
|
-
|
|
115
|
-
// Apply each formatted file
|
|
116
|
-
Object.entries(formattedFiles).forEach(
|
|
117
|
-
([fileId, formattedText]) => {
|
|
118
|
-
if (updatedFiles[fileId]) {
|
|
119
|
-
updatedFiles[fileId] = {
|
|
120
|
-
...updatedFiles[fileId],
|
|
121
|
-
text: formattedText,
|
|
122
|
-
};
|
|
123
|
-
}
|
|
124
|
-
},
|
|
125
|
-
);
|
|
126
|
-
|
|
127
|
-
return {
|
|
128
|
-
...document,
|
|
129
|
-
files: updatedFiles,
|
|
130
|
-
};
|
|
131
|
-
});
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
86
|
runCode();
|
|
136
87
|
|
|
137
88
|
// 4. Capture the state of files after editing and formatting, then generate diff
|
|
@@ -4,8 +4,10 @@ import {
|
|
|
4
4
|
StreamingMarkdownParser,
|
|
5
5
|
} from 'llm-code-format';
|
|
6
6
|
import { mergeFileChanges } from 'editcodewithai';
|
|
7
|
-
import {
|
|
8
|
-
|
|
7
|
+
import {
|
|
8
|
+
FileCollection,
|
|
9
|
+
VizChatId,
|
|
10
|
+
} from '@vizhub/viz-types';
|
|
9
11
|
import {
|
|
10
12
|
updateFiles,
|
|
11
13
|
updateAIScratchpad,
|
|
@@ -14,11 +16,11 @@ import {
|
|
|
14
16
|
updateStreamingStatus,
|
|
15
17
|
finalizeStreamingMessage,
|
|
16
18
|
} from './chatOperations.js';
|
|
17
|
-
import { diff } from '../../ot.js';
|
|
18
19
|
import {
|
|
19
20
|
ShareDBDoc,
|
|
20
21
|
ExtendedVizContent,
|
|
21
22
|
} from '../../types.js';
|
|
23
|
+
import { formatFiles } from '../prettier.js';
|
|
22
24
|
|
|
23
25
|
const DEBUG = false;
|
|
24
26
|
|
|
@@ -285,51 +287,30 @@ export const createLLMFunction = ({
|
|
|
285
287
|
await completeFileEditing(currentEditingFileName);
|
|
286
288
|
}
|
|
287
289
|
|
|
290
|
+
// // Capture the current state of files before applying changes
|
|
291
|
+
// const beforeFiles = createFilesSnapshot(
|
|
292
|
+
// shareDBDoc.data.files,
|
|
293
|
+
// );
|
|
294
|
+
|
|
295
|
+
// Parse the full content to extract file changes
|
|
296
|
+
// export type FileCollection = Record<string, string>;
|
|
297
|
+
const newFilesUnformatted: FileCollection =
|
|
298
|
+
parseMarkdownFiles(fullContent, 'bold').files;
|
|
299
|
+
|
|
300
|
+
// Run Prettier on `newFiles` before applying them
|
|
301
|
+
const newFilesFormatted = await formatFiles(
|
|
302
|
+
newFilesUnformatted,
|
|
303
|
+
);
|
|
304
|
+
|
|
288
305
|
// Apply all the edits at once
|
|
289
|
-
|
|
290
|
-
shareDBDoc,
|
|
291
|
-
|
|
292
|
-
shareDBDoc.data.files,
|
|
293
|
-
parseMarkdownFiles(fullContent, 'bold').files,
|
|
294
|
-
),
|
|
306
|
+
const mergedChanges = mergeFileChanges(
|
|
307
|
+
shareDBDoc.data.files,
|
|
308
|
+
newFilesFormatted,
|
|
295
309
|
);
|
|
310
|
+
updateFiles(shareDBDoc, mergedChanges);
|
|
296
311
|
|
|
297
312
|
// Finalize streaming message
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
// Generate a new runId to trigger a run when AI finishes editing
|
|
301
|
-
// This will trigger a re-run without hot reloading
|
|
302
|
-
const newRunId = generateRunId();
|
|
303
|
-
const runIdOp = diff(shareDBDoc.data, {
|
|
304
|
-
...shareDBDoc.data,
|
|
305
|
-
runId: newRunId,
|
|
306
|
-
});
|
|
307
|
-
shareDBDoc.submitOp(runIdOp, (error) => {
|
|
308
|
-
if (error) {
|
|
309
|
-
console.warn(
|
|
310
|
-
'Error setting runId after AI editing:',
|
|
311
|
-
error,
|
|
312
|
-
);
|
|
313
|
-
} else {
|
|
314
|
-
DEBUG &&
|
|
315
|
-
console.log(
|
|
316
|
-
'Set new runId after AI editing:',
|
|
317
|
-
newRunId,
|
|
318
|
-
);
|
|
319
|
-
}
|
|
320
|
-
});
|
|
321
|
-
|
|
322
|
-
// Write chunks file for debugging
|
|
323
|
-
// if (DEBUG) {
|
|
324
|
-
// const chunksFileJSONpath = `./ai-chunks-${chatId}.json`;
|
|
325
|
-
// fs.writeFileSync(
|
|
326
|
-
// chunksFileJSONpath,
|
|
327
|
-
// JSON.stringify(chunks, null, 2),
|
|
328
|
-
// );
|
|
329
|
-
// console.log(
|
|
330
|
-
// `AI chunks written to ${chunksFileJSONpath}`,
|
|
331
|
-
// );
|
|
332
|
-
// }
|
|
313
|
+
finalizeStreamingMessage(shareDBDoc, chatId);
|
|
333
314
|
|
|
334
315
|
return {
|
|
335
316
|
content: fullContent,
|
package/src/server/prettier.ts
CHANGED
|
@@ -5,7 +5,10 @@ import * as prettierPluginHtml from 'prettier/plugins/html';
|
|
|
5
5
|
import * as prettierPluginMarkdown from 'prettier/plugins/markdown';
|
|
6
6
|
import * as prettierPluginCSS from 'prettier/plugins/postcss';
|
|
7
7
|
import * as prettierPluginTypescript from 'prettier/plugins/typescript';
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
VizFileId,
|
|
10
|
+
FileCollection,
|
|
11
|
+
} from '@vizhub/viz-types';
|
|
9
12
|
|
|
10
13
|
// Parser mappings - matches client-side implementation
|
|
11
14
|
const parsers = {
|
|
@@ -88,23 +91,36 @@ export const formatFile = async (
|
|
|
88
91
|
* Returns a map of fileId -> formatted text for successfully formatted files
|
|
89
92
|
*/
|
|
90
93
|
export const formatFiles = async (
|
|
91
|
-
|
|
92
|
-
|
|
94
|
+
fileCollection:
|
|
95
|
+
| FileCollection
|
|
96
|
+
| { [key: string]: { name: string; text: string } },
|
|
93
97
|
): Promise<{ [fileId: VizFileId]: string }> => {
|
|
94
98
|
const results: { [fileId: VizFileId]: string } = {};
|
|
95
|
-
const targetFileIds =
|
|
99
|
+
const targetFileIds = Object.keys(fileCollection);
|
|
96
100
|
|
|
97
101
|
// Process files in parallel for better performance
|
|
98
102
|
const formatPromises = targetFileIds.map(
|
|
99
103
|
async (fileId) => {
|
|
100
|
-
const
|
|
101
|
-
if (!
|
|
104
|
+
const fileData = fileCollection[fileId];
|
|
105
|
+
if (!fileData) return;
|
|
106
|
+
|
|
107
|
+
// Handle both FileCollection format and test format
|
|
108
|
+
const fileName =
|
|
109
|
+
typeof fileData === 'string'
|
|
110
|
+
? fileId
|
|
111
|
+
: fileData.name;
|
|
112
|
+
const fileText =
|
|
113
|
+
typeof fileData === 'string'
|
|
114
|
+
? fileData
|
|
115
|
+
: fileData.text;
|
|
116
|
+
|
|
117
|
+
if (!fileText) return;
|
|
102
118
|
|
|
103
119
|
const formatted = await formatFile(
|
|
104
|
-
|
|
105
|
-
|
|
120
|
+
fileText,
|
|
121
|
+
fileName,
|
|
106
122
|
);
|
|
107
|
-
if (formatted !== null && formatted !==
|
|
123
|
+
if (formatted !== null && formatted !== fileText) {
|
|
108
124
|
results[fileId] = formatted;
|
|
109
125
|
}
|
|
110
126
|
},
|
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
import React, { useMemo } from 'react';
|
|
2
|
-
import { timestampToDate } from '@vizhub/viz-utils';
|
|
3
|
-
import Markdown from 'react-markdown';
|
|
4
|
-
import remarkGfm from 'remark-gfm';
|
|
5
|
-
import { StreamingEvent } from '../../../types.js';
|
|
6
|
-
import { IndividualFileDiff } from './IndividualFileDiff';
|
|
7
|
-
|
|
8
|
-
const DEBUG = false;
|
|
9
|
-
|
|
10
|
-
interface StreamingMessageProps {
|
|
11
|
-
timestamp: number;
|
|
12
|
-
events: StreamingEvent[];
|
|
13
|
-
isActive?: boolean; // Is this the currently streaming message?
|
|
14
|
-
children?: React.ReactNode;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export const StreamingMessage: React.FC<
|
|
18
|
-
StreamingMessageProps
|
|
19
|
-
> = ({ timestamp, events, isActive, children }) => {
|
|
20
|
-
DEBUG &&
|
|
21
|
-
console.log(
|
|
22
|
-
'StreamingMessage: Rendered with events:',
|
|
23
|
-
events,
|
|
24
|
-
);
|
|
25
|
-
|
|
26
|
-
// Memoize date formatting to avoid repeated computation
|
|
27
|
-
const formattedTime = useMemo(() => {
|
|
28
|
-
return timestampToDate(timestamp).toLocaleTimeString(
|
|
29
|
-
[],
|
|
30
|
-
{
|
|
31
|
-
hour: '2-digit',
|
|
32
|
-
minute: '2-digit',
|
|
33
|
-
},
|
|
34
|
-
);
|
|
35
|
-
}, [timestamp]);
|
|
36
|
-
|
|
37
|
-
return (
|
|
38
|
-
<div className="ai-chat-message assistant streaming">
|
|
39
|
-
<div className="ai-chat-message-content">
|
|
40
|
-
{/* Render events in order */}
|
|
41
|
-
{events.map((event, index) => {
|
|
42
|
-
switch (event.type) {
|
|
43
|
-
case 'text_chunk':
|
|
44
|
-
return (
|
|
45
|
-
<div
|
|
46
|
-
key={`text-${index}`}
|
|
47
|
-
className="text-chunk"
|
|
48
|
-
>
|
|
49
|
-
<Markdown remarkPlugins={[remarkGfm]}>
|
|
50
|
-
{event.content}
|
|
51
|
-
</Markdown>
|
|
52
|
-
</div>
|
|
53
|
-
);
|
|
54
|
-
case 'file_complete':
|
|
55
|
-
return (
|
|
56
|
-
<IndividualFileDiff
|
|
57
|
-
key={`file-${event.fileName}-${index}`}
|
|
58
|
-
fileName={event.fileName}
|
|
59
|
-
beforeContent={event.beforeContent || ''}
|
|
60
|
-
afterContent={event.afterContent || ''}
|
|
61
|
-
/>
|
|
62
|
-
);
|
|
63
|
-
case 'file_start':
|
|
64
|
-
// File start events are now handled by centralized status logic in MessageList
|
|
65
|
-
return null;
|
|
66
|
-
default:
|
|
67
|
-
return null;
|
|
68
|
-
}
|
|
69
|
-
})}
|
|
70
|
-
{isActive && children}
|
|
71
|
-
</div>
|
|
72
|
-
<div className="ai-chat-message-time">
|
|
73
|
-
{formattedTime}
|
|
74
|
-
</div>
|
|
75
|
-
</div>
|
|
76
|
-
);
|
|
77
|
-
};
|