vzcode 2.2.0 → 2.4.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.
Files changed (47) hide show
  1. package/dist/assets/{buildWorker-Dsx6rOdK.js → buildWorker-wLkQ0yz1.js} +64 -64
  2. package/dist/assets/{index-BM8tDh6M.css → index-BoFeK4GL.css} +1 -1
  3. package/dist/assets/{index-CpxqGhXj.js → index-DuUjtrt6.js} +150 -150
  4. package/dist/assets/{worker-BeBZGG1n.js → worker-DHxSPM7N.js} +78 -78
  5. package/dist/cli.js +113 -0
  6. package/dist/index.html +2 -2
  7. package/dist/ot.js +12 -0
  8. package/dist/randomId.js +14 -0
  9. package/dist/runCode.js +20 -0
  10. package/dist/server/aiChatHandler/aiEditing.js +89 -0
  11. package/dist/server/aiChatHandler/chatOperations.js +360 -0
  12. package/dist/server/aiChatHandler/errorHandling.js +49 -0
  13. package/dist/server/aiChatHandler/index.js +112 -0
  14. package/dist/server/aiChatHandler/llmStreaming.js +284 -0
  15. package/dist/server/aiChatHandler/validation.js +19 -0
  16. package/dist/server/computeInitialDocument.js +146 -0
  17. package/dist/server/config.js +21 -0
  18. package/dist/server/featureFlags.js +5 -0
  19. package/dist/server/generateAIResponse.js +129 -0
  20. package/dist/server/handleAIAssist.js +39 -0
  21. package/dist/server/handleAIChatMessage.js +2 -0
  22. package/dist/server/handleAICopilot.js +81 -0
  23. package/dist/server/index.js +297 -0
  24. package/dist/server/isDirectory.js +1 -0
  25. package/dist/server/livekit.js +17 -0
  26. package/dist/server/prettier.js +90 -0
  27. package/dist/server/setupEnv.js +7 -0
  28. package/dist/submitOperation.js +14 -0
  29. package/dist/types.js +1 -0
  30. package/dist/utils/fileDiff.js +66 -0
  31. package/package.json +21 -20
  32. package/src/cli.ts +208 -0
  33. package/src/client/App/index.tsx +0 -1
  34. package/src/client/VZCodeContext/types.ts +3 -4
  35. package/src/client/VZCodeContext/useVZCodeState.ts +24 -6
  36. package/src/client/VZSidebar/AIChat/ChatInput.tsx +4 -0
  37. package/src/client/VZSidebar/AIChat/Message.tsx +2 -66
  38. package/src/client/VZSidebar/AIChat/MessageList.tsx +0 -15
  39. package/src/client/VZSidebar/AIChat/index.tsx +91 -7
  40. package/src/client/VZSidebar/AIChat/styles.scss +19 -0
  41. package/src/server/aiChatHandler/aiEditing.ts +0 -1
  42. package/src/server/aiChatHandler/chatOperations.ts +0 -34
  43. package/src/server/aiChatHandler/index.ts +0 -1
  44. package/src/server/aiChatHandler/llmStreaming.ts +3 -3
  45. package/src/server/index.ts +1 -10
  46. package/src/server/aiChatHandler/undoHandler.ts +0 -97
  47. package/src/server/handleAIChatUndo.ts +0 -2
@@ -0,0 +1,14 @@
1
+ import { diff } from './ot.js';
2
+ /**
3
+ * Creates a submitOperation function that can be used to submit diff-based operations to ShareDB.
4
+ * This is the core logic extracted from useSubmitOperation for reuse across client and server.
5
+ */
6
+ export const createSubmitOperation = (shareDBDoc) => {
7
+ return (next) => {
8
+ const data = shareDBDoc.data;
9
+ const op = diff(data, next(data));
10
+ if (op && shareDBDoc) {
11
+ shareDBDoc.submitOp(op);
12
+ }
13
+ };
14
+ };
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,66 @@
1
+ import { createTwoFilesPatch } from 'diff';
2
+ /**
3
+ * Generate a unified diff for a single file using the diff library
4
+ */
5
+ export function generateFileUnifiedDiff(fileId, fileName, beforeContent, afterContent) {
6
+ // Only generate diff if there are actual changes
7
+ if (beforeContent === afterContent) {
8
+ return '';
9
+ }
10
+ // Use the diff library's createTwoFilesPatch function to generate unified diff
11
+ const unifiedDiff = createTwoFilesPatch(fileName, fileName, beforeContent, afterContent, '', '', { context: 3 });
12
+ return unifiedDiff;
13
+ }
14
+ /**
15
+ * Generate unified diffs for multiple files
16
+ */
17
+ export function generateFilesUnifiedDiff(beforeFiles, afterFiles) {
18
+ const result = {};
19
+ const allFileIds = new Set([
20
+ ...Object.keys(beforeFiles),
21
+ ...Object.keys(afterFiles),
22
+ ]);
23
+ for (const fileId of allFileIds) {
24
+ const beforeFile = beforeFiles[fileId];
25
+ const afterFile = afterFiles[fileId];
26
+ const beforeContent = beforeFile?.text || '';
27
+ const afterContent = afterFile?.text || '';
28
+ const fileName = afterFile?.name || beforeFile?.name || fileId;
29
+ const unifiedDiff = generateFileUnifiedDiff(fileId, fileName, beforeContent, afterContent);
30
+ // Only include files that have changes
31
+ if (unifiedDiff) {
32
+ result[fileId] = unifiedDiff;
33
+ }
34
+ }
35
+ return result;
36
+ }
37
+ /**
38
+ * Create a snapshot of current files for diff comparison
39
+ */
40
+ export function createFilesSnapshot(files) {
41
+ return JSON.parse(JSON.stringify(files));
42
+ }
43
+ /**
44
+ * Parse unified diff to extract basic statistics
45
+ */
46
+ export function parseUnifiedDiffStats(unifiedDiff) {
47
+ const lines = unifiedDiff.split('\n');
48
+ let additions = 0;
49
+ let deletions = 0;
50
+ for (const line of lines) {
51
+ if (line.startsWith('+') && !line.startsWith('+++')) {
52
+ additions++;
53
+ }
54
+ else if (line.startsWith('-') &&
55
+ !line.startsWith('---')) {
56
+ deletions++;
57
+ }
58
+ }
59
+ return { additions, deletions };
60
+ }
61
+ /**
62
+ * Combine multiple unified diffs into a single diff string
63
+ */
64
+ export function combineUnifiedDiffs(unifiedDiffs) {
65
+ return Object.values(unifiedDiffs).join('\n');
66
+ }
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "vzcode",
3
- "version": "2.2.0",
3
+ "version": "2.4.0",
4
4
  "description": "Multiplayer code editor system",
5
5
  "main": "src/index.ts",
6
6
  "type": "module",
7
- "bin": "src/server/index.ts",
7
+ "bin": "dist/cli.js",
8
8
  "files": [
9
9
  "dist/**",
10
10
  "src/**"
@@ -15,8 +15,9 @@
15
15
  "prettier": "prettier {*.*,**/*.*} --write",
16
16
  "typecheck": "tsc --noEmit",
17
17
  "dev": "cross-env EDITOR_PORT=5173 concurrently \"npm run test-interactive\" \"vite\"",
18
- "build": "vite build",
19
- "build-release": "vite build --mode release",
18
+ "build": "vite build && npm run build:server",
19
+ "build:server": "tsc --project tsconfig.server.json",
20
+ "build-release": "vite build --mode release && npm run build:server",
20
21
  "preview": "concurrently \"npm run test-interactive\" \"vite preview\"",
21
22
  "tauri": "tauri",
22
23
  "tauri:dev": "tauri dev",
@@ -117,7 +118,7 @@
117
118
  "@codemirror/state": "^6.5.2",
118
119
  "@codemirror/theme-one-dark": "^6.1.3",
119
120
  "@codemirror/view": "^6.38.1",
120
- "@langchain/core": "^0.3.68",
121
+ "@langchain/core": "^0.3.70",
121
122
  "@langchain/openai": "^0.6.7",
122
123
  "@lezer/highlight": "^1.2.1",
123
124
  "@livekit/components-react": "^2.9.14",
@@ -127,17 +128,17 @@
127
128
  "@replit/codemirror-vscode-keymap": "^6.0.2",
128
129
  "@teamwork/websocket-json-stream": "^2.0.0",
129
130
  "@typescript/vfs": "^1.6.1",
130
- "@uiw/codemirror-theme-abcdef": "^4.24.2",
131
- "@uiw/codemirror-theme-dracula": "^4.24.2",
132
- "@uiw/codemirror-theme-eclipse": "^4.24.2",
133
- "@uiw/codemirror-theme-github": "^4.24.2",
134
- "@uiw/codemirror-theme-material": "^4.24.2",
135
- "@uiw/codemirror-theme-nord": "^4.24.2",
136
- "@uiw/codemirror-theme-okaidia": "^4.24.2",
137
- "@uiw/codemirror-theme-xcode": "^4.24.2",
138
- "@uiw/codemirror-themes": "^4.24.2",
131
+ "@uiw/codemirror-theme-abcdef": "^4.25.1",
132
+ "@uiw/codemirror-theme-dracula": "^4.25.1",
133
+ "@uiw/codemirror-theme-eclipse": "^4.25.1",
134
+ "@uiw/codemirror-theme-github": "^4.25.1",
135
+ "@uiw/codemirror-theme-material": "^4.25.1",
136
+ "@uiw/codemirror-theme-nord": "^4.25.1",
137
+ "@uiw/codemirror-theme-okaidia": "^4.25.1",
138
+ "@uiw/codemirror-theme-xcode": "^4.25.1",
139
+ "@uiw/codemirror-themes": "^4.25.1",
139
140
  "@valtown/codemirror-ts": "^2.3.1",
140
- "@vizhub/runtime": "^4.3.0",
141
+ "@vizhub/runtime": "^4.4.0",
141
142
  "@vizhub/viz-types": "^0.4.0",
142
143
  "@vizhub/viz-utils": "^1.4.0",
143
144
  "body-parser": "^2.2.0",
@@ -153,12 +154,12 @@
153
154
  "diff2html": "^3.4.52",
154
155
  "dotenv": "^17.2.1",
155
156
  "editcodewithai": "^2.3.0",
156
- "eslint-linter-browserify": "^9.32.0",
157
+ "eslint-linter-browserify": "^9.33.0",
157
158
  "express": "^5.1.0",
158
159
  "ignore": "^7.0.5",
159
160
  "json0-ot-diff": "^1.1.2",
160
161
  "jszip": "^3.10.1",
161
- "livekit-server-sdk": "^2.13.1",
162
+ "livekit-server-sdk": "^2.13.2",
162
163
  "llm-code-format": "^3.0.0",
163
164
  "lucide-react": "^0.539.0",
164
165
  "npm": "^11.5.2",
@@ -177,21 +178,21 @@
177
178
  "ws": "^8.18.3"
178
179
  },
179
180
  "devDependencies": {
180
- "@eslint/js": "^9.32.0",
181
+ "@eslint/js": "^9.33.0",
181
182
  "@tauri-apps/cli": "^2.7.1",
182
183
  "@types/react": "^18",
183
184
  "@types/react-dom": "^18",
184
185
  "@vitejs/plugin-react": "^5.0.0",
185
186
  "concurrently": "^9.2.0",
186
187
  "cross-env": "^10.0.0",
187
- "eslint": "^9.32.0",
188
+ "eslint": "^9.33.0",
188
189
  "globals": "^16.3.0",
189
190
  "npm-check-updates": "^18.0.2",
190
191
  "prettier": "^3.6.2",
191
192
  "sass": "^1.90.0",
192
193
  "ts-node": "^10.9.2",
193
194
  "typescript": "^5.9.2",
194
- "vite": "^7.1.1",
195
+ "vite": "^7.1.2",
195
196
  "vitest": "^3.2.4"
196
197
  },
197
198
  "optionalDependencies": {
package/src/cli.ts ADDED
@@ -0,0 +1,208 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { spawn } from 'child_process';
4
+ import path from 'path';
5
+ import { fileURLToPath } from 'url';
6
+ import fs from 'fs';
7
+
8
+ // Get the directory of the current module
9
+ const __filename = fileURLToPath(import.meta.url);
10
+ const __dirname = path.dirname(__filename);
11
+
12
+ // Function to find the Tauri binary
13
+ function findTauriApp(): string | null {
14
+ // Look for the Tauri binary in common locations
15
+ const possiblePaths = [
16
+ // Development location (when running from source)
17
+ path.join(
18
+ __dirname,
19
+ '..',
20
+ 'src-tauri',
21
+ 'target',
22
+ 'debug',
23
+ 'vzcode',
24
+ ),
25
+ path.join(
26
+ __dirname,
27
+ '..',
28
+ 'src-tauri',
29
+ 'target',
30
+ 'debug',
31
+ 'vzcode.exe',
32
+ ),
33
+ // Release location (when running from built package)
34
+ path.join(
35
+ __dirname,
36
+ '..',
37
+ 'src-tauri',
38
+ 'target',
39
+ 'release',
40
+ 'vzcode',
41
+ ),
42
+ path.join(
43
+ __dirname,
44
+ '..',
45
+ 'src-tauri',
46
+ 'target',
47
+ 'release',
48
+ 'vzcode.exe',
49
+ ),
50
+ // MacOS app bundle
51
+ path.join(
52
+ __dirname,
53
+ '..',
54
+ 'src-tauri',
55
+ 'target',
56
+ 'debug',
57
+ 'vzcode.app',
58
+ 'Contents',
59
+ 'MacOS',
60
+ 'vzcode',
61
+ ),
62
+ path.join(
63
+ __dirname,
64
+ '..',
65
+ 'src-tauri',
66
+ 'target',
67
+ 'release',
68
+ 'vzcode.app',
69
+ 'Contents',
70
+ 'MacOS',
71
+ 'vzcode',
72
+ ),
73
+ ];
74
+
75
+ for (const binaryPath of possiblePaths) {
76
+ if (fs.existsSync(binaryPath)) {
77
+ return binaryPath;
78
+ }
79
+ }
80
+
81
+ return null;
82
+ }
83
+
84
+ // Function to launch the Tauri app
85
+ function launchTauriApp(workingDirectory: string): void {
86
+ const tauriBinary = findTauriApp();
87
+
88
+ if (!tauriBinary) {
89
+ console.error(
90
+ 'VZCode desktop app not found. Please build it first with: npm run tauri:build',
91
+ );
92
+ console.error(
93
+ 'Falling back to launching browser version...',
94
+ );
95
+ launchBrowserVersion(workingDirectory);
96
+ return;
97
+ }
98
+
99
+ console.log(
100
+ `Launching VZCode desktop app from: ${workingDirectory}`,
101
+ );
102
+
103
+ // First start the server in the background
104
+ startServerInBackground(workingDirectory);
105
+
106
+ // Give the server a moment to start, then launch Tauri
107
+ setTimeout(() => {
108
+ const child = spawn(tauriBinary, [], {
109
+ cwd: workingDirectory,
110
+ stdio: 'inherit',
111
+ detached: true,
112
+ });
113
+
114
+ // Allow the parent process to exit while keeping the child running
115
+ child.unref();
116
+ }, 2000);
117
+ }
118
+
119
+ // Function to start the server in the background
120
+ function startServerInBackground(
121
+ workingDirectory: string,
122
+ ): void {
123
+ // Start the server process in the background
124
+ const serverPath = path.join(
125
+ __dirname,
126
+ 'server',
127
+ 'index.js',
128
+ );
129
+ const serverChild = spawn('node', [serverPath], {
130
+ cwd: workingDirectory,
131
+ stdio: ['ignore', 'pipe', 'pipe'],
132
+ detached: true,
133
+ });
134
+
135
+ // Don't block on the server process
136
+ serverChild.unref();
137
+
138
+ // Log server output for debugging
139
+ if (serverChild.stdout) {
140
+ serverChild.stdout.on('data', (data) => {
141
+ console.log('Server:', data.toString());
142
+ });
143
+ }
144
+
145
+ if (serverChild.stderr) {
146
+ serverChild.stderr.on('data', (data) => {
147
+ console.error('Server error:', data.toString());
148
+ });
149
+ }
150
+ }
151
+
152
+ // Fallback function to launch the browser version
153
+ async function launchBrowserVersion(
154
+ workingDirectory: string,
155
+ ): Promise<void> {
156
+ const { default: open } = await import('open');
157
+ const serverModule = await import('./server/index.js');
158
+
159
+ // Note: The server/index.js will use process.cwd() automatically
160
+ // and we're already in the correct working directory
161
+ console.log(
162
+ `Starting VZCode server from: ${workingDirectory}`,
163
+ );
164
+ }
165
+
166
+ // Main CLI function
167
+ function main(): void {
168
+ const workingDirectory = process.cwd();
169
+
170
+ // Parse command line arguments
171
+ const args = process.argv.slice(2);
172
+
173
+ if (args.includes('--help') || args.includes('-h')) {
174
+ console.log('VZCode - Multiplayer Code Editor');
175
+ console.log('');
176
+ console.log('Usage: vzcode [options]');
177
+ console.log('');
178
+ console.log('Options:');
179
+ console.log(' --help, -h Show this help message');
180
+ console.log(
181
+ ' --browser Force browser version instead of desktop app',
182
+ );
183
+ console.log(
184
+ ' --port=<port> Specify port for server (default: 3030)',
185
+ );
186
+ console.log('');
187
+ console.log('Examples:');
188
+ console.log(
189
+ ' vzcode # Launch desktop app in current directory',
190
+ );
191
+ console.log(
192
+ ' vzcode --browser # Launch browser version',
193
+ );
194
+ console.log(
195
+ ' vzcode --port=4000 # Start server on port 4000',
196
+ );
197
+ return;
198
+ }
199
+
200
+ if (args.includes('--browser')) {
201
+ launchBrowserVersion(workingDirectory);
202
+ } else {
203
+ launchTauriApp(workingDirectory);
204
+ }
205
+ }
206
+
207
+ // Run the CLI
208
+ main();
@@ -118,7 +118,6 @@ function App() {
118
118
  liveKitConnection={liveKitConnection}
119
119
  setLiveKitConnection={setLiveKitConnection}
120
120
  aiChatEndpoint="/ai-chat-message"
121
- aiChatUndoEndpoint="/ai-chat-undo"
122
121
  aiChatOptions={{}}
123
122
  >
124
123
  <LiveKitRoom
@@ -103,7 +103,6 @@ export type VZCodeContextValue = {
103
103
  aiChatMode: 'ask' | 'edit';
104
104
  setAIChatMode: (mode: 'ask' | 'edit') => void;
105
105
  aiChatEndpoint?: string;
106
- aiChatUndoEndpoint?: string;
107
106
  aiChatOptions?: { [key: string]: any };
108
107
  setSearchResults: (files: ShareDBDoc<VizContent>) => void;
109
108
  setSearchFileVisibility: (
@@ -173,6 +172,8 @@ export type VZCodeContextValue = {
173
172
  setAIChatMessage: (message: string) => void;
174
173
  isLoading: boolean;
175
174
  currentChatId: string;
175
+ selectedChatId: string | null;
176
+ setSelectedChatId: (chatId: string | null) => void;
176
177
  aiErrorMessage: string | null;
177
178
  setAIErrorMessage: (state: string | null) => void;
178
179
  handleSendMessage: (
@@ -201,7 +202,6 @@ export type VZCodeContextValue = {
201
202
  additionalWidgets?: React.ComponentType<{
202
203
  messageId: string;
203
204
  chatId: string;
204
- canUndo: boolean;
205
205
  handleSendMessage?: (
206
206
  messageToSend?: string,
207
207
  options?: Record<string, string>,
@@ -228,7 +228,6 @@ export interface VZCodeProviderProps {
228
228
  liveKitConnection?: boolean;
229
229
  setLiveKitConnection?: (state: boolean) => void;
230
230
  aiChatEndpoint?: string;
231
- aiChatUndoEndpoint?: string;
232
231
  aiChatOptions?: { [key: string]: any };
233
232
  autoForkAndRetryAI?: (
234
233
  prompt: string,
@@ -243,7 +242,7 @@ export interface VZCodeProviderProps {
243
242
  additionalWidgets?: React.ComponentType<{
244
243
  messageId: string;
245
244
  chatId: string;
246
- canUndo: boolean;
247
245
  }>;
248
246
  iframeRef?: React.MutableRefObject<HTMLIFrameElement>;
247
+ handleChatError?: (error: string) => void;
249
248
  }
@@ -46,13 +46,13 @@ export const useVZCodeState = ({
46
46
  liveKitConnection,
47
47
  setLiveKitConnection,
48
48
  aiChatEndpoint,
49
- aiChatUndoEndpoint,
50
49
  aiChatOptions,
51
50
  autoForkAndRetryAI,
52
51
  clearStoredAIPrompt,
53
52
  getStoredAIPrompt,
54
53
  additionalWidgets,
55
54
  iframeRef: externalIframeRef,
55
+ handleChatError,
56
56
  }: Omit<
57
57
  VZCodeProviderProps,
58
58
  'children'
@@ -272,7 +272,13 @@ export const useVZCodeState = ({
272
272
 
273
273
  // Compute isLoading based on the current chat's aiStatus
274
274
  const [currentChatId] = useState(() => uuidv4());
275
- const currentChat = content?.chats?.[currentChatId];
275
+ const [selectedChatId, setSelectedChatId] = useState<
276
+ string | null
277
+ >(null);
278
+
279
+ // Use selectedChatId if available, otherwise use currentChatId for backward compatibility
280
+ const activeChatId = selectedChatId || currentChatId;
281
+ const currentChat = content?.chats?.[activeChatId];
276
282
  const isLoading = currentChat?.aiStatus === 'generating';
277
283
 
278
284
  // Message history for up/down arrow navigation - using ShareDB document data
@@ -287,10 +293,21 @@ export const useVZCodeState = ({
287
293
  const [historyIndex, setHistoryIndex] = useState(-1);
288
294
  const [currentDraft, setCurrentDraft] = useState('');
289
295
 
290
- const [aiErrorMessage, setAIErrorMessage] = useState<
296
+ const [aiErrorMessage, setAIErrorMessageState] = useState<
291
297
  string | null
292
298
  >(null);
293
299
 
300
+ // Create the combined setAIErrorMessage function that calls both state setter and external callback
301
+ const setAIErrorMessage = useCallback(
302
+ (error: string | null) => {
303
+ setAIErrorMessageState(error);
304
+ if (error && handleChatError) {
305
+ handleChatError(error);
306
+ }
307
+ },
308
+ [handleChatError],
309
+ );
310
+
294
311
  // Message history navigation functions
295
312
  const navigateMessageHistoryUp = useCallback(() => {
296
313
  if (messageHistory.length === 0) return;
@@ -378,7 +395,7 @@ export const useVZCodeState = ({
378
395
  body: JSON.stringify({
379
396
  ...aiChatOptions,
380
397
  content: messageContent.trim(),
381
- chatId: currentChatId,
398
+ chatId: activeChatId,
382
399
  mode: aiChatMode,
383
400
  ...options,
384
401
  }),
@@ -441,7 +458,7 @@ export const useVZCodeState = ({
441
458
  isLoading,
442
459
  aiChatEndpoint,
443
460
  aiChatOptions,
444
- currentChatId,
461
+ activeChatId,
445
462
  aiChatMode,
446
463
  autoForkAndRetryAI,
447
464
  ],
@@ -497,7 +514,6 @@ export const useVZCodeState = ({
497
514
  aiChatMode: state.aiChatMode,
498
515
  setAIChatMode,
499
516
  aiChatEndpoint,
500
- aiChatUndoEndpoint,
501
517
  aiChatOptions,
502
518
 
503
519
  isSettingsOpen,
@@ -564,6 +580,8 @@ export const useVZCodeState = ({
564
580
  setAIChatMessage,
565
581
  isLoading,
566
582
  currentChatId,
583
+ selectedChatId,
584
+ setSelectedChatId,
567
585
  aiErrorMessage,
568
586
  setAIErrorMessage,
569
587
  handleSendMessage,
@@ -60,6 +60,10 @@ const ChatInputComponent = ({
60
60
  if (event.key === 'Enter' && !event.shiftKey) {
61
61
  event.preventDefault();
62
62
  onSendMessage();
63
+ } else if (event.key === 'Enter' && event.shiftKey) {
64
+ // Shift+Enter should add a newline, not trigger run code
65
+ // We prevent the event from bubbling up to the global keyboard handler
66
+ event.stopPropagation();
63
67
  } else if (event.key === 'ArrowUp') {
64
68
  // Only navigate history if cursor is at the beginning of the first line
65
69
  const textarea =
@@ -19,9 +19,7 @@ interface MessageProps {
19
19
  timestamp: number;
20
20
  isStreaming?: boolean;
21
21
  diffData?: UnifiedFilesDiff;
22
- beforeFiles?: any;
23
22
  chatId?: string;
24
- canUndo?: boolean;
25
23
  }
26
24
 
27
25
  const MessageComponent = ({
@@ -31,17 +29,10 @@ const MessageComponent = ({
31
29
  timestamp,
32
30
  isStreaming,
33
31
  diffData,
34
- beforeFiles,
35
32
  chatId,
36
- canUndo,
37
33
  }: MessageProps) => {
38
- const [isUndoing, setIsUndoing] = useState(false);
39
- const {
40
- aiChatUndoEndpoint,
41
- aiChatOptions = {},
42
- additionalWidgets,
43
- handleSendMessage,
44
- } = useContext(VZCodeContext);
34
+ const { additionalWidgets, handleSendMessage } =
35
+ useContext(VZCodeContext);
45
36
 
46
37
  // Memoize date formatting to avoid repeated computation
47
38
  const formattedTime = useMemo(() => {
@@ -59,47 +50,6 @@ const MessageComponent = ({
59
50
  return `ai-chat-message ${role}${isStreaming ? ' streaming' : ''}`;
60
51
  }, [role, isStreaming]);
61
52
 
62
- const handleUndo = async () => {
63
- if (
64
- !id ||
65
- !chatId ||
66
- !beforeFiles ||
67
- isUndoing ||
68
- !aiChatUndoEndpoint
69
- ) {
70
- return;
71
- }
72
-
73
- setIsUndoing(true);
74
- try {
75
- const response = await fetch(aiChatUndoEndpoint, {
76
- method: 'POST',
77
- headers: {
78
- 'Content-Type': 'application/json',
79
- },
80
- body: JSON.stringify({
81
- vizId: aiChatOptions.vizId,
82
- chatId,
83
- messageId: id,
84
- }),
85
- });
86
-
87
- if (!response.ok) {
88
- throw new Error(
89
- `HTTP error! status: ${response.status}`,
90
- );
91
- }
92
-
93
- // The server will handle the ShareDB operations to undo the changes
94
- // The UI will update automatically via ShareDB
95
- } catch (error) {
96
- console.error('Error undoing AI edit:', error);
97
- // TODO: Show user-friendly error message
98
- } finally {
99
- setIsUndoing(false);
100
- }
101
- };
102
-
103
53
  return (
104
54
  <div className={messageClassName}>
105
55
  <div className="ai-chat-message-content">
@@ -112,26 +62,12 @@ const MessageComponent = ({
112
62
  <DiffView diffData={diffData} />
113
63
  )}
114
64
  {additionalWidgets &&
115
- canUndo &&
116
65
  chatId &&
117
66
  React.createElement(additionalWidgets, {
118
67
  messageId: id,
119
68
  chatId: chatId,
120
- canUndo: canUndo,
121
69
  handleSendMessage,
122
70
  })}
123
- {!additionalWidgets && canUndo && beforeFiles && (
124
- <div className="undo-button-container">
125
- <button
126
- className="undo-button"
127
- onClick={handleUndo}
128
- disabled={isUndoing}
129
- title="Undo this AI edit"
130
- >
131
- {isUndoing ? 'Undoing...' : 'Undo'}
132
- </button>
133
- </div>
134
- )}
135
71
  </div>
136
72
  <div className="ai-chat-message-time">
137
73
  {formattedTime}
@@ -161,19 +161,6 @@ const MessageListComponent = ({
161
161
  onScroll={handleScroll}
162
162
  >
163
163
  {messages.map((msg, index) => {
164
- // Only the most recent assistant message with diffData can be undone
165
- const isLastAssistantMessage =
166
- msg.role === 'assistant' &&
167
- index === messages.length - 1 &&
168
- !isLoading; // Can't undo while AI is still generating
169
-
170
- const canUndo =
171
- isLastAssistantMessage &&
172
- (msg as any).diffData &&
173
- (msg as any).beforeFiles &&
174
- Object.keys((msg as any).diffData || {}).length >
175
- 0;
176
-
177
164
  return (
178
165
  <Message
179
166
  key={msg.id}
@@ -182,9 +169,7 @@ const MessageListComponent = ({
182
169
  content={msg.content}
183
170
  timestamp={msg.timestamp}
184
171
  diffData={(msg as any).diffData}
185
- beforeFiles={(msg as any).beforeFiles}
186
172
  chatId={chatId}
187
- canUndo={canUndo}
188
173
  />
189
174
  );
190
175
  })}