vzcode 2.18.0 → 2.21.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-D6-he0oi.js +476 -0
- package/dist/assets/{index-BvGPtSrr.css → index-fYq6iaqF.css} +1 -1
- package/dist/assets/{worker-y5jhqCTR.js → worker-Cm3I3tnR.js} +57 -57
- package/dist/assets/{worker-ClF0pBYr.js → worker-KiuLM-X4.js} +71 -71
- package/dist/index.html +2 -2
- package/dist/server/aiChatHandler/chatOperations.js +3 -4
- package/dist/server/aiChatHandler/llmStreaming.js +57 -4
- package/dist/server/prettier.js +18 -16
- package/dist/utils/fileDiff.js +25 -1
- package/package.json +31 -31
- package/src/client/CodeEditor/getOrCreateEditor.tsx +263 -256
- package/src/client/VZRight.tsx +4 -0
- package/src/client/VZSidebar/AIChat/ChatInput.tsx +1 -1
- package/src/client/VZSidebar/AIChat/DiffView.scss +37 -1
- package/src/client/VZSidebar/AIChat/DiffView.tsx +55 -16
- package/src/client/featureFlags.ts +7 -0
- package/src/server/aiChatHandler/chatOperations.ts +3 -4
- package/src/server/aiChatHandler/llmStreaming.ts +87 -4
- package/src/server/prettier.ts +30 -31
- package/src/types.ts +5 -0
- package/src/utils/fileDiff.ts +36 -2
- package/dist/assets/index-DuDXdJWC.js +0 -477
|
@@ -9,6 +9,8 @@ import {
|
|
|
9
9
|
UnifiedFilesDiff,
|
|
10
10
|
parseUnifiedDiffStats,
|
|
11
11
|
combineUnifiedDiffs,
|
|
12
|
+
isFileDeletion,
|
|
13
|
+
getDeletedFileName,
|
|
12
14
|
} from '../../../utils/fileDiff';
|
|
13
15
|
import * as Diff2Html from 'diff2html';
|
|
14
16
|
import 'diff2html/bundles/css/diff2html.min.css';
|
|
@@ -41,21 +43,33 @@ export const DiffView = forwardRef<
|
|
|
41
43
|
useContext(VZCodeContext);
|
|
42
44
|
const diffContainerRef = useRef<HTMLDivElement>(null);
|
|
43
45
|
|
|
44
|
-
const
|
|
46
|
+
const allDiffs = Object.values(diffData).filter(
|
|
45
47
|
(diff) => diff.length > 0,
|
|
46
48
|
);
|
|
47
49
|
|
|
48
|
-
//
|
|
50
|
+
// Separate deleted files from regular diffs
|
|
51
|
+
const deletedFiles: string[] = [];
|
|
52
|
+
const regularDiffs: string[] = [];
|
|
53
|
+
|
|
54
|
+
for (const diff of allDiffs) {
|
|
55
|
+
if (isFileDeletion(diff)) {
|
|
56
|
+
deletedFiles.push(diff);
|
|
57
|
+
} else {
|
|
58
|
+
regularDiffs.push(diff);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Calculate statistics from regular unified diffs only
|
|
49
63
|
let totalAdditions = 0;
|
|
50
64
|
let totalDeletions = 0;
|
|
51
65
|
|
|
52
|
-
for (const unifiedDiff of
|
|
66
|
+
for (const unifiedDiff of regularDiffs) {
|
|
53
67
|
const stats = parseUnifiedDiffStats(unifiedDiff);
|
|
54
68
|
totalAdditions += stats.additions;
|
|
55
69
|
totalDeletions += stats.deletions;
|
|
56
70
|
}
|
|
57
71
|
|
|
58
|
-
// Combine
|
|
72
|
+
// Combine regular unified diffs and convert to HTML using diff2html
|
|
59
73
|
const combinedUnifiedDiff = combineUnifiedDiffs(diffData);
|
|
60
74
|
const diffHtml = Diff2Html.html(combinedUnifiedDiff, {
|
|
61
75
|
drawFileList: false,
|
|
@@ -155,7 +169,7 @@ export const DiffView = forwardRef<
|
|
|
155
169
|
};
|
|
156
170
|
}, [diffHtml, content, openTab, setIsAIChatOpen]);
|
|
157
171
|
|
|
158
|
-
if (
|
|
172
|
+
if (allDiffs.length === 0) {
|
|
159
173
|
return null;
|
|
160
174
|
}
|
|
161
175
|
|
|
@@ -164,8 +178,8 @@ export const DiffView = forwardRef<
|
|
|
164
178
|
<div className="diff-summary" id="diff-summary">
|
|
165
179
|
<div className="diff-stats">
|
|
166
180
|
<span className="files-changed">
|
|
167
|
-
{
|
|
168
|
-
{
|
|
181
|
+
{allDiffs.length} file
|
|
182
|
+
{allDiffs.length !== 1 ? 's' : ''} changed
|
|
169
183
|
</span>
|
|
170
184
|
{totalAdditions > 0 && (
|
|
171
185
|
<span className="additions">
|
|
@@ -177,18 +191,43 @@ export const DiffView = forwardRef<
|
|
|
177
191
|
-{totalDeletions}
|
|
178
192
|
</span>
|
|
179
193
|
)}
|
|
194
|
+
{deletedFiles.length > 0 && (
|
|
195
|
+
<span className="deletions">
|
|
196
|
+
{deletedFiles.length} deleted
|
|
197
|
+
</span>
|
|
198
|
+
)}
|
|
180
199
|
</div>
|
|
181
200
|
</div>
|
|
182
201
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
202
|
+
{/* Render deleted files */}
|
|
203
|
+
{deletedFiles.map((deletionMarker, index) => {
|
|
204
|
+
const fileName = getDeletedFileName(deletionMarker);
|
|
205
|
+
return (
|
|
206
|
+
<div key={index} className="deleted-file">
|
|
207
|
+
<div className="deleted-file-header">
|
|
208
|
+
<span className="deleted-file-name">
|
|
209
|
+
{fileName}
|
|
210
|
+
</span>
|
|
211
|
+
<span className="deleted-file-status">
|
|
212
|
+
File deleted
|
|
213
|
+
</span>
|
|
214
|
+
</div>
|
|
215
|
+
</div>
|
|
216
|
+
);
|
|
217
|
+
})}
|
|
218
|
+
|
|
219
|
+
{/* Render regular diffs */}
|
|
220
|
+
{regularDiffs.length > 0 && (
|
|
221
|
+
<div
|
|
222
|
+
className="diff-files"
|
|
223
|
+
ref={diffContainerRef}
|
|
224
|
+
tabIndex={-1}
|
|
225
|
+
role="region"
|
|
226
|
+
aria-label="Code diff content"
|
|
227
|
+
aria-describedby="diff-summary"
|
|
228
|
+
dangerouslySetInnerHTML={{ __html: diffHtml }}
|
|
229
|
+
/>
|
|
230
|
+
)}
|
|
192
231
|
</div>
|
|
193
232
|
);
|
|
194
233
|
});
|
|
@@ -9,3 +9,10 @@ export const enableAskMode = false;
|
|
|
9
9
|
|
|
10
10
|
// Phase 0: Feature flag for minimal AI edit flow
|
|
11
11
|
export const enableMinimalEditFlow = true;
|
|
12
|
+
|
|
13
|
+
// If true, only include the minimal set of extensions,
|
|
14
|
+
// namely only the JSON1 OT extension and that's it.
|
|
15
|
+
// This is useful for testing and debugging the OT functionality
|
|
16
|
+
// without any other extensions getting in the way,
|
|
17
|
+
// just to rule them out as the source of a bug.
|
|
18
|
+
export const MINIMAL_EXTENSIONS = false;
|
|
@@ -51,6 +51,7 @@ export const ensureChatExists = (
|
|
|
51
51
|
|
|
52
52
|
/**
|
|
53
53
|
* Adds a user message to the chat
|
|
54
|
+
* Clears old messages to reflect that each prompt is a self-contained code transformation
|
|
54
55
|
*/
|
|
55
56
|
export const addUserMessage = (
|
|
56
57
|
shareDBDoc: ShareDBDoc<VizContent>,
|
|
@@ -70,10 +71,7 @@ export const addUserMessage = (
|
|
|
70
71
|
...shareDBDoc.data.chats,
|
|
71
72
|
[chatId]: {
|
|
72
73
|
...shareDBDoc.data.chats[chatId],
|
|
73
|
-
messages: [
|
|
74
|
-
...shareDBDoc.data.chats[chatId].messages,
|
|
75
|
-
userMessage,
|
|
76
|
-
],
|
|
74
|
+
messages: [userMessage], // Replace old messages with just the new user message
|
|
77
75
|
updatedAt: dateToTimestamp(new Date()),
|
|
78
76
|
},
|
|
79
77
|
},
|
|
@@ -379,6 +377,7 @@ export const updateFiles = (
|
|
|
379
377
|
DEBUG && console.log('updateFiles op:');
|
|
380
378
|
DEBUG && console.log(JSON.stringify(filesOp, null, 2));
|
|
381
379
|
shareDBDoc.submitOp(filesOp);
|
|
380
|
+
return filesOp;
|
|
382
381
|
};
|
|
383
382
|
|
|
384
383
|
/**
|
|
@@ -7,6 +7,7 @@ import { mergeFileChanges } from 'editcodewithai';
|
|
|
7
7
|
import {
|
|
8
8
|
FileCollection,
|
|
9
9
|
VizChatId,
|
|
10
|
+
VizFiles,
|
|
10
11
|
} from '@vizhub/viz-types';
|
|
11
12
|
import {
|
|
12
13
|
updateFiles,
|
|
@@ -22,11 +23,18 @@ import {
|
|
|
22
23
|
} from '../../types.js';
|
|
23
24
|
import { formatFiles } from '../prettier.js';
|
|
24
25
|
|
|
26
|
+
// Verbose logs
|
|
25
27
|
const DEBUG = false;
|
|
26
28
|
|
|
27
29
|
// Useful for testing/debugging the streaming behavior
|
|
28
30
|
const slowMode = false;
|
|
29
31
|
|
|
32
|
+
// If the `EMIT_FIXTURES` variable is true,
|
|
33
|
+
// then an output file in the `test/fixtures` folder
|
|
34
|
+
// with the before and after file states for testing purposes.
|
|
35
|
+
// This feeds into tests in codemirror-ot.
|
|
36
|
+
const EMIT_FIXTURES = false;
|
|
37
|
+
|
|
30
38
|
/**
|
|
31
39
|
* Creates and configures the LLM function for streaming with reasoning tokens
|
|
32
40
|
*/
|
|
@@ -109,7 +117,7 @@ export const createLLMFunction = ({
|
|
|
109
117
|
const completeFileEditing = async (
|
|
110
118
|
fileName: string,
|
|
111
119
|
) => {
|
|
112
|
-
if (fileName
|
|
120
|
+
if (fileName) {
|
|
113
121
|
DEBUG &&
|
|
114
122
|
console.log(
|
|
115
123
|
`LLMStreaming: Completing file editing for ${fileName}`,
|
|
@@ -185,6 +193,38 @@ export const createLLMFunction = ({
|
|
|
185
193
|
}
|
|
186
194
|
}
|
|
187
195
|
},
|
|
196
|
+
onFileDelete: async (fileName: string) => {
|
|
197
|
+
DEBUG &&
|
|
198
|
+
console.log(
|
|
199
|
+
`LLMStreaming: File marked for deletion: ${fileName}`,
|
|
200
|
+
);
|
|
201
|
+
|
|
202
|
+
// Emit any accumulated text chunk first
|
|
203
|
+
await emitTextChunk();
|
|
204
|
+
|
|
205
|
+
// Complete previous file if any
|
|
206
|
+
if (currentEditingFileName) {
|
|
207
|
+
await completeFileEditing(currentEditingFileName);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Reset current editing state
|
|
211
|
+
currentEditingFileName = null;
|
|
212
|
+
currentFileContent = '';
|
|
213
|
+
|
|
214
|
+
// Emit file delete event
|
|
215
|
+
await addStreamingEvent(shareDBDoc, chatId, {
|
|
216
|
+
type: 'file_delete',
|
|
217
|
+
fileName,
|
|
218
|
+
timestamp: Date.now(),
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
// Update status
|
|
222
|
+
updateStreamingStatus(
|
|
223
|
+
shareDBDoc,
|
|
224
|
+
chatId,
|
|
225
|
+
`Deleting ${fileName}...`,
|
|
226
|
+
);
|
|
227
|
+
},
|
|
188
228
|
};
|
|
189
229
|
|
|
190
230
|
const parser = new StreamingMarkdownParser(callbacks);
|
|
@@ -297,17 +337,60 @@ export const createLLMFunction = ({
|
|
|
297
337
|
const newFilesUnformatted: FileCollection =
|
|
298
338
|
parseMarkdownFiles(fullContent, 'bold').files;
|
|
299
339
|
|
|
300
|
-
// Run Prettier on `newFiles` before applying them
|
|
340
|
+
// Run Prettier on `newFiles` before applying them,
|
|
341
|
+
// preserving empty files as empty
|
|
342
|
+
// since that is the cue to delete a file.
|
|
301
343
|
const newFilesFormatted = await formatFiles(
|
|
302
344
|
newFilesUnformatted,
|
|
303
345
|
);
|
|
304
346
|
|
|
347
|
+
// Capture the current state of files before applying changes
|
|
348
|
+
let vizFilesBefore: VizFiles;
|
|
349
|
+
if (EMIT_FIXTURES) {
|
|
350
|
+
vizFilesBefore = JSON.parse(
|
|
351
|
+
JSON.stringify(shareDBDoc.data.files),
|
|
352
|
+
);
|
|
353
|
+
}
|
|
354
|
+
|
|
305
355
|
// Apply all the edits at once
|
|
306
|
-
const
|
|
356
|
+
const vizFilesAfter: VizFiles = mergeFileChanges(
|
|
307
357
|
shareDBDoc.data.files,
|
|
308
358
|
newFilesFormatted,
|
|
309
359
|
);
|
|
310
|
-
|
|
360
|
+
|
|
361
|
+
const filesOp = updateFiles(shareDBDoc, vizFilesAfter);
|
|
362
|
+
|
|
363
|
+
if (EMIT_FIXTURES) {
|
|
364
|
+
const fs = await import('fs');
|
|
365
|
+
const path = await import('path');
|
|
366
|
+
const testCasesDir = path.resolve(
|
|
367
|
+
process.cwd(),
|
|
368
|
+
'../',
|
|
369
|
+
'fixtures',
|
|
370
|
+
);
|
|
371
|
+
if (!fs.existsSync(testCasesDir)) {
|
|
372
|
+
fs.mkdirSync(testCasesDir, { recursive: true });
|
|
373
|
+
}
|
|
374
|
+
const timestamp = new Date()
|
|
375
|
+
.toISOString()
|
|
376
|
+
.replace(/[:.]/g, '-');
|
|
377
|
+
const testCasePath = path.join(
|
|
378
|
+
testCasesDir,
|
|
379
|
+
`ai-chat-${timestamp}.json`,
|
|
380
|
+
);
|
|
381
|
+
const testCaseData = {
|
|
382
|
+
vizFilesBefore,
|
|
383
|
+
vizFilesAfter,
|
|
384
|
+
filesOp,
|
|
385
|
+
};
|
|
386
|
+
fs.writeFileSync(
|
|
387
|
+
testCasePath,
|
|
388
|
+
JSON.stringify(testCaseData, null, 2),
|
|
389
|
+
);
|
|
390
|
+
console.log(
|
|
391
|
+
`AI chat test case written to ${testCasePath}`,
|
|
392
|
+
);
|
|
393
|
+
}
|
|
311
394
|
|
|
312
395
|
// Finalize streaming message
|
|
313
396
|
finalizeStreamingMessage(shareDBDoc, chatId);
|
package/src/server/prettier.ts
CHANGED
|
@@ -5,10 +5,7 @@ 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 {
|
|
9
|
-
VizFileId,
|
|
10
|
-
FileCollection,
|
|
11
|
-
} from '@vizhub/viz-types';
|
|
8
|
+
import { FileCollection } from '@vizhub/viz-types';
|
|
12
9
|
|
|
13
10
|
// Parser mappings - matches client-side implementation
|
|
14
11
|
const parsers = {
|
|
@@ -85,43 +82,45 @@ export const formatFile = async (
|
|
|
85
82
|
}
|
|
86
83
|
};
|
|
87
84
|
|
|
85
|
+
// Keys are file names, values are file text contents
|
|
86
|
+
//export type FileCollection = Record<string, string>;
|
|
87
|
+
|
|
88
88
|
/**
|
|
89
89
|
* Formats multiple files using Prettier
|
|
90
90
|
* Only formats files that have supported extensions
|
|
91
91
|
* Returns a map of fileId -> formatted text for successfully formatted files
|
|
92
92
|
*/
|
|
93
93
|
export const formatFiles = async (
|
|
94
|
-
fileCollection:
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
const results: { [fileId: VizFileId]: string } = {};
|
|
99
|
-
const targetFileIds = Object.keys(fileCollection);
|
|
94
|
+
fileCollection: FileCollection,
|
|
95
|
+
): Promise<FileCollection> => {
|
|
96
|
+
const results: FileCollection = {};
|
|
97
|
+
const targetFileNames = Object.keys(fileCollection);
|
|
100
98
|
|
|
101
99
|
// Process files in parallel for better performance
|
|
102
|
-
const formatPromises =
|
|
103
|
-
async (
|
|
104
|
-
const
|
|
105
|
-
|
|
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;
|
|
100
|
+
const formatPromises = targetFileNames.map(
|
|
101
|
+
async (fileName: string) => {
|
|
102
|
+
const fileText = fileCollection[fileName];
|
|
103
|
+
results[fileName] = fileText;
|
|
116
104
|
|
|
117
|
-
|
|
105
|
+
// Preserve empty files as empty,
|
|
106
|
+
// since this is the cue to delete a file.
|
|
107
|
+
if (!fileText || fileText.trim() === '') {
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
118
110
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
111
|
+
try {
|
|
112
|
+
const formatted = await formatFile(
|
|
113
|
+
fileText,
|
|
114
|
+
fileName,
|
|
115
|
+
);
|
|
116
|
+
if (formatted !== null && formatted !== fileText) {
|
|
117
|
+
results[fileName] = formatted;
|
|
118
|
+
}
|
|
119
|
+
} catch (error) {
|
|
120
|
+
console.error(
|
|
121
|
+
`Error formatting ${fileName}:`,
|
|
122
|
+
error,
|
|
123
|
+
);
|
|
125
124
|
}
|
|
126
125
|
},
|
|
127
126
|
);
|
package/src/types.ts
CHANGED
package/src/utils/fileDiff.ts
CHANGED
|
@@ -2,7 +2,33 @@ import { createTwoFilesPatch } from 'diff';
|
|
|
2
2
|
import { VizFiles, VizFileId } from '@vizhub/viz-types';
|
|
3
3
|
|
|
4
4
|
export interface UnifiedFilesDiff {
|
|
5
|
-
[fileId: VizFileId]: string; // Unified diff string
|
|
5
|
+
[fileId: VizFileId]: string; // Unified diff string or deletion marker
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
// Special marker to indicate a file deletion
|
|
9
|
+
export const FILE_DELETION_MARKER = '__FILE_DELETED__';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Check if a diff string represents a file deletion
|
|
13
|
+
*/
|
|
14
|
+
export function isFileDeletion(
|
|
15
|
+
diffString: string,
|
|
16
|
+
): boolean {
|
|
17
|
+
return diffString.startsWith(FILE_DELETION_MARKER);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Extract file name from a deletion marker
|
|
22
|
+
*/
|
|
23
|
+
export function getDeletedFileName(
|
|
24
|
+
diffString: string,
|
|
25
|
+
): string {
|
|
26
|
+
if (!isFileDeletion(diffString)) {
|
|
27
|
+
return '';
|
|
28
|
+
}
|
|
29
|
+
return diffString.substring(
|
|
30
|
+
FILE_DELETION_MARKER.length + 1,
|
|
31
|
+
);
|
|
6
32
|
}
|
|
7
33
|
|
|
8
34
|
/**
|
|
@@ -19,6 +45,11 @@ export function generateFileUnifiedDiff(
|
|
|
19
45
|
return '';
|
|
20
46
|
}
|
|
21
47
|
|
|
48
|
+
// Handle file deletion case - when file existed before but is now empty/deleted
|
|
49
|
+
if (beforeContent && !afterContent) {
|
|
50
|
+
return `${FILE_DELETION_MARKER}:${fileName}`;
|
|
51
|
+
}
|
|
52
|
+
|
|
22
53
|
// Use the diff library's createTwoFilesPatch function to generate unified diff
|
|
23
54
|
const unifiedDiff = createTwoFilesPatch(
|
|
24
55
|
fileName,
|
|
@@ -109,9 +140,12 @@ export function parseUnifiedDiffStats(
|
|
|
109
140
|
|
|
110
141
|
/**
|
|
111
142
|
* Combine multiple unified diffs into a single diff string
|
|
143
|
+
* Note: File deletion markers are excluded from combination
|
|
112
144
|
*/
|
|
113
145
|
export function combineUnifiedDiffs(
|
|
114
146
|
unifiedDiffs: UnifiedFilesDiff,
|
|
115
147
|
): string {
|
|
116
|
-
return Object.values(unifiedDiffs)
|
|
148
|
+
return Object.values(unifiedDiffs)
|
|
149
|
+
.filter((diff) => !isFileDeletion(diff))
|
|
150
|
+
.join('\n');
|
|
117
151
|
}
|