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/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-DuDXdJWC.js"></script>
24
- <link rel="stylesheet" crossorigin href="/assets/index-BvGPtSrr.css">
23
+ <script type="module" crossorigin src="/assets/index-D6-he0oi.js"></script>
24
+ <link rel="stylesheet" crossorigin href="/assets/index-fYq6iaqF.css">
25
25
  </head>
26
26
  <body>
27
27
  <div id="root"></div>
@@ -35,6 +35,7 @@ export const ensureChatExists = (shareDBDoc, chatId) => {
35
35
  };
36
36
  /**
37
37
  * Adds a user message to the chat
38
+ * Clears old messages to reflect that each prompt is a self-contained code transformation
38
39
  */
39
40
  export const addUserMessage = (shareDBDoc, chatId, content) => {
40
41
  const userMessage = {
@@ -49,10 +50,7 @@ export const addUserMessage = (shareDBDoc, chatId, content) => {
49
50
  ...shareDBDoc.data.chats,
50
51
  [chatId]: {
51
52
  ...shareDBDoc.data.chats[chatId],
52
- messages: [
53
- ...shareDBDoc.data.chats[chatId].messages,
54
- userMessage,
55
- ],
53
+ messages: [userMessage], // Replace old messages with just the new user message
56
54
  updatedAt: dateToTimestamp(new Date()),
57
55
  },
58
56
  },
@@ -281,6 +279,7 @@ export const updateFiles = (shareDBDoc, files) => {
281
279
  DEBUG && console.log('updateFiles op:');
282
280
  DEBUG && console.log(JSON.stringify(filesOp, null, 2));
283
281
  shareDBDoc.submitOp(filesOp);
282
+ return filesOp;
284
283
  };
285
284
  /**
286
285
  * Finds a file ID by searching for a matching file name
@@ -3,9 +3,15 @@ import { parseMarkdownFiles, StreamingMarkdownParser, } from 'llm-code-format';
3
3
  import { mergeFileChanges } from 'editcodewithai';
4
4
  import { updateFiles, updateAIScratchpad, createStreamingAIMessage, addStreamingEvent, updateStreamingStatus, finalizeStreamingMessage, } from './chatOperations.js';
5
5
  import { formatFiles } from '../prettier.js';
6
+ // Verbose logs
6
7
  const DEBUG = false;
7
8
  // Useful for testing/debugging the streaming behavior
8
9
  const slowMode = false;
10
+ // If the `EMIT_FIXTURES` variable is true,
11
+ // then an output file in the `test/fixtures` folder
12
+ // with the before and after file states for testing purposes.
13
+ // This feeds into tests in codemirror-ot.
14
+ const EMIT_FIXTURES = false;
9
15
  /**
10
16
  * Creates and configures the LLM function for streaming with reasoning tokens
11
17
  */
@@ -59,7 +65,7 @@ enableReasoningTokens = false, model, aiRequestOptions, }) => {
59
65
  };
60
66
  // Helper to complete file editing
61
67
  const completeFileEditing = async (fileName) => {
62
- if (fileName && currentFileContent) {
68
+ if (fileName) {
63
69
  DEBUG &&
64
70
  console.log(`LLMStreaming: Completing file editing for ${fileName}`);
65
71
  await addStreamingEvent(shareDBDoc, chatId, {
@@ -114,6 +120,27 @@ enableReasoningTokens = false, model, aiRequestOptions, }) => {
114
120
  }
115
121
  }
116
122
  },
123
+ onFileDelete: async (fileName) => {
124
+ DEBUG &&
125
+ console.log(`LLMStreaming: File marked for deletion: ${fileName}`);
126
+ // Emit any accumulated text chunk first
127
+ await emitTextChunk();
128
+ // Complete previous file if any
129
+ if (currentEditingFileName) {
130
+ await completeFileEditing(currentEditingFileName);
131
+ }
132
+ // Reset current editing state
133
+ currentEditingFileName = null;
134
+ currentFileContent = '';
135
+ // Emit file delete event
136
+ await addStreamingEvent(shareDBDoc, chatId, {
137
+ type: 'file_delete',
138
+ fileName,
139
+ timestamp: Date.now(),
140
+ });
141
+ // Update status
142
+ updateStreamingStatus(shareDBDoc, chatId, `Deleting ${fileName}...`);
143
+ },
117
144
  };
118
145
  const parser = new StreamingMarkdownParser(callbacks);
119
146
  const chunks = [];
@@ -197,11 +224,37 @@ enableReasoningTokens = false, model, aiRequestOptions, }) => {
197
224
  // Parse the full content to extract file changes
198
225
  // export type FileCollection = Record<string, string>;
199
226
  const newFilesUnformatted = parseMarkdownFiles(fullContent, 'bold').files;
200
- // Run Prettier on `newFiles` before applying them
227
+ // Run Prettier on `newFiles` before applying them,
228
+ // preserving empty files as empty
229
+ // since that is the cue to delete a file.
201
230
  const newFilesFormatted = await formatFiles(newFilesUnformatted);
231
+ // Capture the current state of files before applying changes
232
+ let vizFilesBefore;
233
+ if (EMIT_FIXTURES) {
234
+ vizFilesBefore = JSON.parse(JSON.stringify(shareDBDoc.data.files));
235
+ }
202
236
  // Apply all the edits at once
203
- const mergedChanges = mergeFileChanges(shareDBDoc.data.files, newFilesFormatted);
204
- updateFiles(shareDBDoc, mergedChanges);
237
+ const vizFilesAfter = mergeFileChanges(shareDBDoc.data.files, newFilesFormatted);
238
+ const filesOp = updateFiles(shareDBDoc, vizFilesAfter);
239
+ if (EMIT_FIXTURES) {
240
+ const fs = await import('fs');
241
+ const path = await import('path');
242
+ const testCasesDir = path.resolve(process.cwd(), '../', 'fixtures');
243
+ if (!fs.existsSync(testCasesDir)) {
244
+ fs.mkdirSync(testCasesDir, { recursive: true });
245
+ }
246
+ const timestamp = new Date()
247
+ .toISOString()
248
+ .replace(/[:.]/g, '-');
249
+ const testCasePath = path.join(testCasesDir, `ai-chat-${timestamp}.json`);
250
+ const testCaseData = {
251
+ vizFilesBefore,
252
+ vizFilesAfter,
253
+ filesOp,
254
+ };
255
+ fs.writeFileSync(testCasePath, JSON.stringify(testCaseData, null, 2));
256
+ console.log(`AI chat test case written to ${testCasePath}`);
257
+ }
205
258
  // Finalize streaming message
206
259
  finalizeStreamingMessage(shareDBDoc, chatId);
207
260
  return {
@@ -67,6 +67,8 @@ export const formatFile = async (fileText, fileName) => {
67
67
  return null;
68
68
  }
69
69
  };
70
+ // Keys are file names, values are file text contents
71
+ //export type FileCollection = Record<string, string>;
70
72
  /**
71
73
  * Formats multiple files using Prettier
72
74
  * Only formats files that have supported extensions
@@ -74,24 +76,24 @@ export const formatFile = async (fileText, fileName) => {
74
76
  */
75
77
  export const formatFiles = async (fileCollection) => {
76
78
  const results = {};
77
- const targetFileIds = Object.keys(fileCollection);
79
+ const targetFileNames = Object.keys(fileCollection);
78
80
  // Process files in parallel for better performance
79
- const formatPromises = targetFileIds.map(async (fileId) => {
80
- const fileData = fileCollection[fileId];
81
- if (!fileData)
81
+ const formatPromises = targetFileNames.map(async (fileName) => {
82
+ const fileText = fileCollection[fileName];
83
+ results[fileName] = fileText;
84
+ // Preserve empty files as empty,
85
+ // since this is the cue to delete a file.
86
+ if (!fileText || fileText.trim() === '') {
82
87
  return;
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) {
94
- results[fileId] = formatted;
88
+ }
89
+ try {
90
+ const formatted = await formatFile(fileText, fileName);
91
+ if (formatted !== null && formatted !== fileText) {
92
+ results[fileName] = formatted;
93
+ }
94
+ }
95
+ catch (error) {
96
+ console.error(`Error formatting ${fileName}:`, error);
95
97
  }
96
98
  });
97
99
  await Promise.all(formatPromises);
@@ -1,4 +1,21 @@
1
1
  import { createTwoFilesPatch } from 'diff';
2
+ // Special marker to indicate a file deletion
3
+ export const FILE_DELETION_MARKER = '__FILE_DELETED__';
4
+ /**
5
+ * Check if a diff string represents a file deletion
6
+ */
7
+ export function isFileDeletion(diffString) {
8
+ return diffString.startsWith(FILE_DELETION_MARKER);
9
+ }
10
+ /**
11
+ * Extract file name from a deletion marker
12
+ */
13
+ export function getDeletedFileName(diffString) {
14
+ if (!isFileDeletion(diffString)) {
15
+ return '';
16
+ }
17
+ return diffString.substring(FILE_DELETION_MARKER.length + 1);
18
+ }
2
19
  /**
3
20
  * Generate a unified diff for a single file using the diff library
4
21
  */
@@ -7,6 +24,10 @@ export function generateFileUnifiedDiff(fileId, fileName, beforeContent, afterCo
7
24
  if (beforeContent === afterContent) {
8
25
  return '';
9
26
  }
27
+ // Handle file deletion case - when file existed before but is now empty/deleted
28
+ if (beforeContent && !afterContent) {
29
+ return `${FILE_DELETION_MARKER}:${fileName}`;
30
+ }
10
31
  // Use the diff library's createTwoFilesPatch function to generate unified diff
11
32
  const unifiedDiff = createTwoFilesPatch(fileName, fileName, beforeContent, afterContent, '', '', { context: 3 });
12
33
  return unifiedDiff;
@@ -60,7 +81,10 @@ export function parseUnifiedDiffStats(unifiedDiff) {
60
81
  }
61
82
  /**
62
83
  * Combine multiple unified diffs into a single diff string
84
+ * Note: File deletion markers are excluded from combination
63
85
  */
64
86
  export function combineUnifiedDiffs(unifiedDiffs) {
65
- return Object.values(unifiedDiffs).join('\n');
87
+ return Object.values(unifiedDiffs)
88
+ .filter((diff) => !isFileDeletion(diff))
89
+ .join('\n');
66
90
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vzcode",
3
- "version": "2.18.0",
3
+ "version": "2.21.0",
4
4
  "description": "Multiplayer code editor system",
5
5
  "main": "src/index.ts",
6
6
  "type": "module",
@@ -11,7 +11,7 @@
11
11
  ],
12
12
  "scripts": {
13
13
  "test": "vitest run",
14
- "test-interactive": "cd test/sampleDirectories/visualEditor && node --import \"data:text/javascript,import { register } from 'node:module'; import { pathToFileURL } from 'node:url'; register('ts-node/esm', pathToFileURL('./'));\" ../../../src/server/index.ts",
14
+ "test-interactive": "cd test/sampleDirectories/playground && node --import \"data:text/javascript,import { register } from 'node:module'; import { pathToFileURL } from 'node:url'; register('ts-node/esm', pathToFileURL('./'));\" ../../../src/server/index.ts",
15
15
  "prettier": "prettier {*.*,**/*.*} --write",
16
16
  "typecheck": "tsc --noEmit",
17
17
  "lint": "eslint . --ext .js,.ts,.tsx",
@@ -110,20 +110,20 @@
110
110
  },
111
111
  "homepage": "https://github.com/vizhub-core/vzcode#readme",
112
112
  "dependencies": {
113
- "@codemirror/autocomplete": "^6.18.7",
113
+ "@codemirror/autocomplete": "^6.19.0",
114
114
  "@codemirror/lang-css": "^6.3.1",
115
- "@codemirror/lang-html": "^6.4.10",
115
+ "@codemirror/lang-html": "^6.4.11",
116
116
  "@codemirror/lang-javascript": "^6.2.4",
117
117
  "@codemirror/lang-json": "^6.0.2",
118
- "@codemirror/lang-markdown": "^6.3.4",
119
- "@codemirror/lint": "^6.8.5",
118
+ "@codemirror/lang-markdown": "^6.4.0",
119
+ "@codemirror/lint": "^6.9.0",
120
120
  "@codemirror/state": "^6.5.2",
121
121
  "@codemirror/theme-one-dark": "^6.1.3",
122
- "@codemirror/view": "^6.38.2",
123
- "@langchain/core": "^0.3.77",
124
- "@langchain/openai": "^0.6.13",
122
+ "@codemirror/view": "^6.38.5",
123
+ "@langchain/core": "^0.3.78",
124
+ "@langchain/openai": "^0.6.14",
125
125
  "@lezer/highlight": "^1.2.1",
126
- "@livekit/components-react": "^2.9.14",
126
+ "@livekit/components-react": "^2.9.15",
127
127
  "@replit/codemirror-indentation-markers": "^6.5.3",
128
128
  "@replit/codemirror-interact": "^6.3.1",
129
129
  "@replit/codemirror-lang-svelte": "^6.0.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.6.0",
150
+ "codemirror-ot": "^5.8.0",
151
151
  "color-hash": "^2.0.2",
152
152
  "comlink": "^4.4.2",
153
153
  "d3-array": "^3.2.4",
@@ -155,24 +155,24 @@
155
155
  "diff": "^8.0.2",
156
156
  "diff-match-patch": "^1.0.5",
157
157
  "diff2html": "^3.4.52",
158
- "dotenv": "^17.2.2",
158
+ "dotenv": "^17.2.3",
159
159
  "editcodewithai": "^2.4.0",
160
- "eslint-linter-browserify": "^9.36.0",
160
+ "eslint-linter-browserify": "^9.37.0",
161
161
  "express": "^5.1.0",
162
162
  "ignore": "^7.0.5",
163
163
  "json0-ot-diff": "^1.1.2",
164
164
  "jszip": "^3.10.1",
165
- "livekit-server-sdk": "^2.13.3",
166
- "llm-code-format": "^3.0.0",
167
- "lucide-react": "^0.544.0",
168
- "npm": "^11.6.0",
165
+ "livekit-server-sdk": "^2.14.0",
166
+ "llm-code-format": "^3.1.0",
167
+ "lucide-react": "^0.545.0",
168
+ "npm": "^11.6.2",
169
169
  "open": "^10.2.0",
170
170
  "prettier-plugin-svelte": "^3.4.0",
171
171
  "react": "^18",
172
172
  "react-bootstrap": "^2.10.10",
173
173
  "react-dom": "^18",
174
174
  "react-markdown": "^10.1.0",
175
- "react-router-dom": "^7.9.1",
175
+ "react-router-dom": "^7.9.4",
176
176
  "remark-gfm": "^4.0.1",
177
177
  "sharedb": "^5.2.2",
178
178
  "sharedb-client-browser": "^5.2.2",
@@ -181,31 +181,31 @@
181
181
  "ws": "^8.18.3"
182
182
  },
183
183
  "devDependencies": {
184
- "@eslint/js": "^9.36.0",
184
+ "@eslint/js": "^9.37.0",
185
185
  "@tauri-apps/cli": "^2.8.4",
186
186
  "@types/d3-color": "^3.1.3",
187
187
  "@types/react": "^18",
188
188
  "@types/react-dom": "^18",
189
- "@typescript-eslint/eslint-plugin": "^8.44.0",
190
- "@typescript-eslint/parser": "^8.44.0",
191
- "@vitejs/plugin-react": "^5.0.3",
189
+ "@typescript-eslint/eslint-plugin": "^8.46.0",
190
+ "@typescript-eslint/parser": "^8.46.0",
191
+ "@vitejs/plugin-react": "^5.0.4",
192
192
  "concurrently": "^9.2.1",
193
- "cross-env": "^10.0.0",
194
- "eslint": "^9.36.0",
193
+ "cross-env": "^10.1.0",
194
+ "eslint": "^9.37.0",
195
195
  "eslint-plugin-jsx-a11y": "^6.10.2",
196
196
  "eslint-plugin-react": "^7.37.5",
197
- "eslint-plugin-react-hooks": "^5.2.0",
197
+ "eslint-plugin-react-hooks": "^7.0.0",
198
198
  "globals": "^16.4.0",
199
- "npm-check-updates": "^18.2.1",
199
+ "npm-check-updates": "^19.0.0",
200
200
  "prettier": "^3.6.2",
201
- "sass": "^1.92.1",
201
+ "sass": "^1.93.2",
202
202
  "ts-node": "^10.9.2",
203
- "typescript": "^5.9.2",
204
- "vite": "^7.1.6",
203
+ "typescript": "^5.9.3",
204
+ "vite": "^7.1.9",
205
205
  "vitest": "^3.2.4"
206
206
  },
207
207
  "optionalDependencies": {
208
- "@rollup/rollup-darwin-arm64": "^4.51.0",
209
- "@rollup/rollup-win32-x64-msvc": "^4.51.0"
208
+ "@rollup/rollup-darwin-arm64": "^4.52.4",
209
+ "@rollup/rollup-win32-x64-msvc": "^4.52.4"
210
210
  }
211
211
  }