vzcode 2.17.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,7 +20,7 @@
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-BiPmUreW.js"></script>
23
+ <script type="module" crossorigin src="/assets/index-DuDXdJWC.js"></script>
24
24
  <link rel="stylesheet" crossorigin href="/assets/index-BvGPtSrr.css">
25
25
  </head>
26
26
  <body>
@@ -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.17.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(() => {
@@ -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
- // 1. Capture the current state of files before editing
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
- // 2. Assemble the final prompt
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 and incremental file updates
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
@@ -376,6 +376,8 @@ export const updateFiles = (
376
376
  ...shareDBDoc.data,
377
377
  files,
378
378
  });
379
+ DEBUG && console.log('updateFiles op:');
380
+ DEBUG && console.log(JSON.stringify(filesOp, null, 2));
379
381
  shareDBDoc.submitOp(filesOp);
380
382
  };
381
383
 
@@ -4,8 +4,10 @@ import {
4
4
  StreamingMarkdownParser,
5
5
  } from 'llm-code-format';
6
6
  import { mergeFileChanges } from 'editcodewithai';
7
- import { VizChatId } from '@vizhub/viz-types';
8
- import { generateRunId } from '@vizhub/viz-utils';
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
- updateFiles(
290
- shareDBDoc,
291
- mergeFileChanges(
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
- await finalizeStreamingMessage(shareDBDoc, chatId);
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,
@@ -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 { VizFiles, VizFileId } from '@vizhub/viz-types';
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
- files: VizFiles,
92
- fileIds?: VizFileId[],
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 = fileIds || Object.keys(files);
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 file = files[fileId];
101
- if (!file) return;
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
- file.text,
105
- file.name,
120
+ fileText,
121
+ fileName,
106
122
  );
107
- if (formatted !== null && formatted !== file.text) {
123
+ if (formatted !== null && formatted !== fileText) {
108
124
  results[fileId] = formatted;
109
125
  }
110
126
  },