vzcode 1.43.0 → 1.45.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/bindings_wasm_bg-CH5ekNIA.wasm +0 -0
- package/dist/assets/buildWorker-C_tGhWXG.js +682 -0
- package/dist/assets/index-C1lTrzOZ.js +457 -0
- package/dist/assets/{index-CP9o_hN5.css → index-DeeOcIzw.css} +1 -1
- package/dist/assets/worker-CekYspLa.js +453 -0
- package/dist/index.html +2 -2
- package/package.json +20 -18
- package/src/client/CodeEditor/{getOrCreateEditor.ts → getOrCreateEditor.tsx} +129 -1
- package/src/client/CodeEditor/index.tsx +4 -0
- package/src/client/Icons/SparklesSVG.tsx +9 -3
- package/src/client/ImageViewer/index.tsx +91 -0
- package/src/client/ImageViewer/style.scss +41 -0
- package/src/client/VZCodeContext.tsx +34 -6
- package/src/client/VZMiddle.tsx +22 -7
- package/src/client/VZRight.tsx +7 -2
- package/src/client/VZSidebar/AIChat/ChatInput.tsx +8 -8
- package/src/client/VZSidebar/AIChat/DiffView.scss +125 -0
- package/src/client/VZSidebar/AIChat/DiffView.tsx +72 -0
- package/src/client/VZSidebar/AIChat/Message.tsx +11 -0
- package/src/client/VZSidebar/AIChat/MessageList.tsx +1 -0
- package/src/client/VZSidebar/AIChat/index.tsx +9 -7
- package/src/client/VZSidebar/aiCopyPaste.ts +64 -1
- package/src/client/VZSidebar/index.tsx +18 -1
- package/src/client/VZSidebar/styles.scss +9 -0
- package/src/client/VZSidebar/useDragAndDrop.tsx +133 -8
- package/src/client/featureFlags.ts +2 -0
- package/src/client/useRuntimeError.ts +32 -0
- package/src/client/utils/isImageFile.ts +12 -0
- package/src/server/aiChatHandler/aiEditing.ts +19 -0
- package/src/server/aiChatHandler/chatOperations.ts +40 -0
- package/src/server/aiChatHandler/index.ts +13 -0
- package/src/server/computeInitialDocument.ts +25 -6
- package/src/server/index.ts +31 -2
- package/src/utils/fileDiff.ts +114 -0
- package/dist/assets/bindings_wasm_bg-BQfW5T_2.wasm +0 -0
- package/dist/assets/buildWorker-B-vXZ9gA.js +0 -579
- package/dist/assets/index-B83lKfVG.js +0 -329
- package/dist/assets/worker-DkYJN5WQ.js +0 -453
|
@@ -3,6 +3,10 @@ import {
|
|
|
3
3
|
prepareFilesForPrompt,
|
|
4
4
|
} from 'editcodewithai';
|
|
5
5
|
import { formatMarkdownFiles } from 'llm-code-format';
|
|
6
|
+
import {
|
|
7
|
+
createFilesSnapshot,
|
|
8
|
+
generateFilesUnifiedDiff,
|
|
9
|
+
} from '../../utils/fileDiff.js';
|
|
6
10
|
|
|
7
11
|
// Dev flag for waiting 1 second before starting the LLM function.
|
|
8
12
|
// Useful for debugging and testing purposes, e.g. checking the typing indicator.
|
|
@@ -17,6 +21,11 @@ export const performAIEditing = async ({
|
|
|
17
21
|
llmFunction,
|
|
18
22
|
runCode,
|
|
19
23
|
}) => {
|
|
24
|
+
// 1. Capture the current state of files before editing
|
|
25
|
+
const beforeFiles = createFilesSnapshot(
|
|
26
|
+
shareDBDoc.data.files,
|
|
27
|
+
);
|
|
28
|
+
|
|
20
29
|
const preparedFiles = prepareFilesForPrompt(
|
|
21
30
|
shareDBDoc.data.files,
|
|
22
31
|
);
|
|
@@ -40,8 +49,18 @@ export const performAIEditing = async ({
|
|
|
40
49
|
|
|
41
50
|
runCode();
|
|
42
51
|
|
|
52
|
+
// 3. Capture the state of files after editing and generate diff
|
|
53
|
+
const afterFiles = createFilesSnapshot(
|
|
54
|
+
shareDBDoc.data.files,
|
|
55
|
+
);
|
|
56
|
+
const diffData = generateFilesUnifiedDiff(
|
|
57
|
+
beforeFiles,
|
|
58
|
+
afterFiles,
|
|
59
|
+
);
|
|
60
|
+
|
|
43
61
|
return {
|
|
44
62
|
content: result.content,
|
|
45
63
|
generationId: result.generationId,
|
|
64
|
+
diffData,
|
|
46
65
|
};
|
|
47
66
|
};
|
|
@@ -246,6 +246,46 @@ export const finalizeAIMessage = (
|
|
|
246
246
|
shareDBDoc.submitOp(op);
|
|
247
247
|
};
|
|
248
248
|
|
|
249
|
+
/**
|
|
250
|
+
* Adds diff data to the most recent AI message
|
|
251
|
+
*/
|
|
252
|
+
export const addDiffToAIMessage = (
|
|
253
|
+
shareDBDoc: ShareDBDoc<VizContent>,
|
|
254
|
+
chatId: VizChatId,
|
|
255
|
+
diffData: any,
|
|
256
|
+
) => {
|
|
257
|
+
const chat = shareDBDoc.data.chats[chatId];
|
|
258
|
+
const messages = [...chat.messages];
|
|
259
|
+
|
|
260
|
+
// Find the most recent AI message
|
|
261
|
+
const lastAIMessageIndex = messages.length - 1;
|
|
262
|
+
if (
|
|
263
|
+
lastAIMessageIndex >= 0 &&
|
|
264
|
+
messages[lastAIMessageIndex].role === 'assistant'
|
|
265
|
+
) {
|
|
266
|
+
const newMessage = {
|
|
267
|
+
...messages[lastAIMessageIndex],
|
|
268
|
+
diffData,
|
|
269
|
+
};
|
|
270
|
+
// Use type assertion to extend the message with diffData
|
|
271
|
+
(messages[lastAIMessageIndex] as any) = newMessage;
|
|
272
|
+
|
|
273
|
+
const messageOp = diff(shareDBDoc.data, {
|
|
274
|
+
...shareDBDoc.data,
|
|
275
|
+
chats: {
|
|
276
|
+
...shareDBDoc.data.chats,
|
|
277
|
+
[chatId]: {
|
|
278
|
+
...chat,
|
|
279
|
+
messages,
|
|
280
|
+
updatedAt: dateToTimestamp(new Date()),
|
|
281
|
+
},
|
|
282
|
+
},
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
shareDBDoc.submitOp(messageOp);
|
|
286
|
+
}
|
|
287
|
+
};
|
|
288
|
+
|
|
249
289
|
/**
|
|
250
290
|
* Adds an AI response message to the chat (legacy function, kept for compatibility)
|
|
251
291
|
*/
|
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
ensureChatsExist,
|
|
4
4
|
ensureChatExists,
|
|
5
5
|
addUserMessage,
|
|
6
|
+
addDiffToAIMessage,
|
|
6
7
|
} from './chatOperations.js';
|
|
7
8
|
import { createLLMFunction } from './llmStreaming.js';
|
|
8
9
|
import { performAIEditing } from './aiEditing.js';
|
|
@@ -72,6 +73,18 @@ export const handleAIChatMessage =
|
|
|
72
73
|
runCode,
|
|
73
74
|
});
|
|
74
75
|
|
|
76
|
+
// Add diff data to the AI message if there are changes
|
|
77
|
+
if (
|
|
78
|
+
editResult.diffData &&
|
|
79
|
+
Object.keys(editResult.diffData).length > 0
|
|
80
|
+
) {
|
|
81
|
+
addDiffToAIMessage(
|
|
82
|
+
shareDBDoc,
|
|
83
|
+
chatId,
|
|
84
|
+
editResult.diffData,
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
75
88
|
// Handle credit deduction if callback is provided
|
|
76
89
|
if (
|
|
77
90
|
onCreditDeduction &&
|
|
@@ -10,6 +10,15 @@ import createIgnore from 'ignore';
|
|
|
10
10
|
import { ignoreFilePattern, baseIgnore } from './config.js';
|
|
11
11
|
import { isDirectory } from './isDirectory.js';
|
|
12
12
|
|
|
13
|
+
// Import the image file utility
|
|
14
|
+
const isImageFile = (fileName) => {
|
|
15
|
+
return (
|
|
16
|
+
fileName.match(
|
|
17
|
+
/\.(png|jpg|jpeg|gif|bmp|svg|webp)$/i,
|
|
18
|
+
) !== null
|
|
19
|
+
);
|
|
20
|
+
};
|
|
21
|
+
|
|
13
22
|
/**
|
|
14
23
|
* @param {string} fullPath - absolut path of the workspace root
|
|
15
24
|
* @param {string} currentDirectoryPath - path where the ignore file is found, relative to fullPath
|
|
@@ -180,13 +189,23 @@ export const computeInitialDocument = ({ fullPath }) => {
|
|
|
180
189
|
|
|
181
190
|
files.forEach((file) => {
|
|
182
191
|
const id = randomId();
|
|
192
|
+
let text = null;
|
|
193
|
+
|
|
194
|
+
if (!isDirectory(file)) {
|
|
195
|
+
const filePath = path.join(fullPath, file);
|
|
196
|
+
|
|
197
|
+
if (isImageFile(file)) {
|
|
198
|
+
// Read image files as binary and convert to base64
|
|
199
|
+
const buffer = fs.readFileSync(filePath);
|
|
200
|
+
text = buffer.toString('base64');
|
|
201
|
+
} else {
|
|
202
|
+
// Read non-image files as UTF-8 text
|
|
203
|
+
text = fs.readFileSync(filePath, 'utf-8');
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
183
207
|
initialDocument.files[id] = {
|
|
184
|
-
text
|
|
185
|
-
? null
|
|
186
|
-
: fs.readFileSync(
|
|
187
|
-
path.join(fullPath, file),
|
|
188
|
-
'utf-8',
|
|
189
|
-
),
|
|
208
|
+
text,
|
|
190
209
|
name: file,
|
|
191
210
|
};
|
|
192
211
|
});
|
package/src/server/index.ts
CHANGED
|
@@ -18,6 +18,15 @@ import { isDirectory } from './isDirectory.js';
|
|
|
18
18
|
import { createToken } from './livekit.js';
|
|
19
19
|
import './setupEnv.js';
|
|
20
20
|
|
|
21
|
+
// Import the image file utility
|
|
22
|
+
const isImageFile = (fileName) => {
|
|
23
|
+
return (
|
|
24
|
+
fileName.match(
|
|
25
|
+
/\.(png|jpg|jpeg|gif|bmp|svg|webp)$/i,
|
|
26
|
+
) !== null
|
|
27
|
+
);
|
|
28
|
+
};
|
|
29
|
+
|
|
21
30
|
// The time in milliseconds by which auto-saving is debounced.
|
|
22
31
|
const autoSaveDebounceTimeMS = 800;
|
|
23
32
|
|
|
@@ -161,7 +170,17 @@ const save = () => {
|
|
|
161
170
|
if (previous && current) {
|
|
162
171
|
// Handle changing of text content.
|
|
163
172
|
if (previous.text !== current.text) {
|
|
164
|
-
|
|
173
|
+
if (isImageFile(current.name)) {
|
|
174
|
+
// Write image files as binary from base64
|
|
175
|
+
const buffer = Buffer.from(
|
|
176
|
+
current.text,
|
|
177
|
+
'base64',
|
|
178
|
+
);
|
|
179
|
+
fs.writeFileSync(current.name, buffer);
|
|
180
|
+
} else {
|
|
181
|
+
// Write non-image files as text
|
|
182
|
+
fs.writeFileSync(current.name, current.text);
|
|
183
|
+
}
|
|
165
184
|
}
|
|
166
185
|
|
|
167
186
|
// // Handle renaming files.
|
|
@@ -256,7 +275,17 @@ const save = () => {
|
|
|
256
275
|
if (!previous && current) {
|
|
257
276
|
//File Creation
|
|
258
277
|
if (!isDirectory(current.name)) {
|
|
259
|
-
|
|
278
|
+
if (isImageFile(current.name)) {
|
|
279
|
+
// Write image files as binary from base64
|
|
280
|
+
const buffer = Buffer.from(
|
|
281
|
+
current.text,
|
|
282
|
+
'base64',
|
|
283
|
+
);
|
|
284
|
+
fs.writeFileSync(current.name, buffer);
|
|
285
|
+
} else {
|
|
286
|
+
// Write non-image files as text
|
|
287
|
+
fs.writeFileSync(current.name, current.text);
|
|
288
|
+
}
|
|
260
289
|
} else {
|
|
261
290
|
fs.mkdirSync(current.name, { recursive: true });
|
|
262
291
|
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { createTwoFilesPatch } from 'diff';
|
|
2
|
+
import { VizFiles, VizFileId } from '@vizhub/viz-types';
|
|
3
|
+
|
|
4
|
+
export interface UnifiedFilesDiff {
|
|
5
|
+
[fileId: VizFileId]: string; // Unified diff string
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Generate a unified diff for a single file using the diff library
|
|
10
|
+
*/
|
|
11
|
+
export function generateFileUnifiedDiff(
|
|
12
|
+
fileId: VizFileId,
|
|
13
|
+
fileName: string,
|
|
14
|
+
beforeContent: string,
|
|
15
|
+
afterContent: string,
|
|
16
|
+
): string {
|
|
17
|
+
// Only generate diff if there are actual changes
|
|
18
|
+
if (beforeContent === afterContent) {
|
|
19
|
+
return '';
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Use the diff library's createTwoFilesPatch function to generate unified diff
|
|
23
|
+
const unifiedDiff = createTwoFilesPatch(
|
|
24
|
+
fileName,
|
|
25
|
+
fileName,
|
|
26
|
+
beforeContent,
|
|
27
|
+
afterContent,
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
return unifiedDiff;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Generate unified diffs for multiple files
|
|
35
|
+
*/
|
|
36
|
+
export function generateFilesUnifiedDiff(
|
|
37
|
+
beforeFiles: VizFiles,
|
|
38
|
+
afterFiles: VizFiles,
|
|
39
|
+
): UnifiedFilesDiff {
|
|
40
|
+
const result: UnifiedFilesDiff = {};
|
|
41
|
+
const allFileIds = new Set([
|
|
42
|
+
...Object.keys(beforeFiles),
|
|
43
|
+
...Object.keys(afterFiles),
|
|
44
|
+
]);
|
|
45
|
+
|
|
46
|
+
for (const fileId of allFileIds) {
|
|
47
|
+
const beforeFile = beforeFiles[fileId];
|
|
48
|
+
const afterFile = afterFiles[fileId];
|
|
49
|
+
|
|
50
|
+
const beforeContent = beforeFile?.text || '';
|
|
51
|
+
const afterContent = afterFile?.text || '';
|
|
52
|
+
const fileName =
|
|
53
|
+
afterFile?.name || beforeFile?.name || fileId;
|
|
54
|
+
|
|
55
|
+
const unifiedDiff = generateFileUnifiedDiff(
|
|
56
|
+
fileId,
|
|
57
|
+
fileName,
|
|
58
|
+
beforeContent,
|
|
59
|
+
afterContent,
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
// Only include files that have changes
|
|
63
|
+
if (unifiedDiff) {
|
|
64
|
+
result[fileId] = unifiedDiff;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return result;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Create a snapshot of current files for diff comparison
|
|
73
|
+
*/
|
|
74
|
+
export function createFilesSnapshot(
|
|
75
|
+
files: VizFiles,
|
|
76
|
+
): VizFiles {
|
|
77
|
+
return JSON.parse(JSON.stringify(files));
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Parse unified diff to extract basic statistics
|
|
82
|
+
*/
|
|
83
|
+
export function parseUnifiedDiffStats(
|
|
84
|
+
unifiedDiff: string,
|
|
85
|
+
): {
|
|
86
|
+
additions: number;
|
|
87
|
+
deletions: number;
|
|
88
|
+
} {
|
|
89
|
+
const lines = unifiedDiff.split('\n');
|
|
90
|
+
let additions = 0;
|
|
91
|
+
let deletions = 0;
|
|
92
|
+
|
|
93
|
+
for (const line of lines) {
|
|
94
|
+
if (line.startsWith('+') && !line.startsWith('+++')) {
|
|
95
|
+
additions++;
|
|
96
|
+
} else if (
|
|
97
|
+
line.startsWith('-') &&
|
|
98
|
+
!line.startsWith('---')
|
|
99
|
+
) {
|
|
100
|
+
deletions++;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return { additions, deletions };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Combine multiple unified diffs into a single diff string
|
|
109
|
+
*/
|
|
110
|
+
export function combineUnifiedDiffs(
|
|
111
|
+
unifiedDiffs: UnifiedFilesDiff,
|
|
112
|
+
): string {
|
|
113
|
+
return Object.values(unifiedDiffs).join('\n');
|
|
114
|
+
}
|
|
Binary file
|