vzcode 1.40.0 → 1.42.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 (35) hide show
  1. package/dist/assets/bindings_wasm_bg-BQfW5T_2.wasm +0 -0
  2. package/dist/assets/buildWorker-B-vXZ9gA.js +579 -0
  3. package/dist/assets/{index-DKLiE98a.css → index-CP9o_hN5.css} +1 -1
  4. package/dist/assets/index-kPLgjbcb.js +329 -0
  5. package/dist/index.html +2 -2
  6. package/package.json +17 -16
  7. package/src/client/App/style.scss +2 -2
  8. package/src/client/CodeEditor/getOrCreateEditor.ts +2 -5
  9. package/src/client/CodeEditor/index.tsx +1 -1
  10. package/src/client/CodeEditor/json1PresenceDisplay.ts +40 -12
  11. package/src/client/VZRight.tsx +68 -6
  12. package/src/client/VZSidebar/AIChat/ChatInput.tsx +17 -5
  13. package/src/client/VZSidebar/AIChat/Message.tsx +21 -8
  14. package/src/client/VZSidebar/AIChat/MessageList.tsx +29 -31
  15. package/src/client/VZSidebar/AIChat/StreamingMessage.tsx +6 -1
  16. package/src/client/VZSidebar/AIChat/TypingIndicator.tsx +7 -1
  17. package/src/client/VZSidebar/AIChat/index.tsx +22 -9
  18. package/src/client/VZSidebar/aiCopyPaste.ts +8 -1
  19. package/src/client/VZSidebar/index.tsx +10 -8
  20. package/src/client/buildWorker.ts +3 -0
  21. package/src/client/vzReducer/searchReducer.test.ts +250 -0
  22. package/src/client/vzReducer/searchReducer.ts +3 -1
  23. package/src/{ot.js → ot.ts} +11 -0
  24. package/src/runCode.ts +4 -23
  25. package/src/server/aiChatHandler/aiEditing.ts +10 -7
  26. package/src/server/aiChatHandler/chatOperations.ts +144 -58
  27. package/src/server/aiChatHandler/errorHandling.ts +1 -1
  28. package/src/server/aiChatHandler/index.ts +8 -2
  29. package/src/server/aiChatHandler/llmStreaming.ts +83 -44
  30. package/src/submitOperation.ts +1 -1
  31. package/dist/assets/index-ubSLcdJf.js +0 -327
  32. package/src/client/diff.js +0 -12
  33. package/src/runCode.js +0 -48
  34. package/src/submitOperation.js +0 -16
  35. /package/src/{randomId.js → randomId.ts} +0 -0
@@ -0,0 +1,250 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { setSearchResultsReducer } from './searchReducer';
3
+ import { VZAction, VZState, createInitialState } from '.';
4
+ import { defaultTheme } from '../themes';
5
+
6
+ // Mock ShareDBDoc for testing
7
+ const createMockShareDBDoc = (files: any) => ({
8
+ data: { files },
9
+ });
10
+
11
+ const initialState: VZState = createInitialState({
12
+ defaultTheme,
13
+ });
14
+
15
+ describe('searchReducer', () => {
16
+ describe('setSearchResultsReducer', () => {
17
+ it('should find case-insensitive matches', () => {
18
+ const mockFiles = {
19
+ 'file1.js': {
20
+ name: 'file1.js',
21
+ text: 'const Hello = "world";\nconst goodbye = "HELLO";',
22
+ },
23
+ 'file2.js': {
24
+ name: 'file2.js',
25
+ text: 'function hello() {\n return "Hello World";\n}',
26
+ },
27
+ };
28
+
29
+ const stateWithSearch = {
30
+ ...initialState,
31
+ search: {
32
+ ...initialState.search,
33
+ pattern: 'hello', // lowercase search pattern
34
+ },
35
+ };
36
+
37
+ const action: VZAction = {
38
+ type: 'set_search_results',
39
+ files: createMockShareDBDoc(mockFiles),
40
+ };
41
+
42
+ const newState = setSearchResultsReducer(
43
+ stateWithSearch,
44
+ action,
45
+ );
46
+
47
+ // Should find matches in both files despite different cases
48
+ expect(
49
+ Object.keys(newState.search.results),
50
+ ).toHaveLength(2);
51
+
52
+ // Check file1.js results
53
+ const file1Results =
54
+ newState.search.results['file1.js'];
55
+ expect(file1Results).toBeDefined();
56
+ expect(file1Results.matches).toHaveLength(2); // "Hello" and "HELLO"
57
+ expect(file1Results.matches[0].line).toBe(1);
58
+ expect(file1Results.matches[0].index).toBe(6); // position of "Hello"
59
+ expect(file1Results.matches[1].line).toBe(2);
60
+ expect(file1Results.matches[1].index).toBe(18); // position of "HELLO"
61
+
62
+ // Check file2.js results
63
+ const file2Results =
64
+ newState.search.results['file2.js'];
65
+ expect(file2Results).toBeDefined();
66
+ expect(file2Results.matches).toHaveLength(2); // "hello" and "Hello"
67
+ expect(file2Results.matches[0].line).toBe(1);
68
+ expect(file2Results.matches[0].index).toBe(9); // position of "hello"
69
+ expect(file2Results.matches[1].line).toBe(2);
70
+ expect(file2Results.matches[1].index).toBe(10); // position of "Hello"
71
+ });
72
+
73
+ it('should find exact case matches', () => {
74
+ const mockFiles = {
75
+ 'file1.js': {
76
+ name: 'file1.js',
77
+ text: 'const hello = "world";',
78
+ },
79
+ };
80
+
81
+ const stateWithSearch = {
82
+ ...initialState,
83
+ search: {
84
+ ...initialState.search,
85
+ pattern: 'hello', // exact case match
86
+ },
87
+ };
88
+
89
+ const action: VZAction = {
90
+ type: 'set_search_results',
91
+ files: createMockShareDBDoc(mockFiles),
92
+ };
93
+
94
+ const newState = setSearchResultsReducer(
95
+ stateWithSearch,
96
+ action,
97
+ );
98
+
99
+ expect(
100
+ Object.keys(newState.search.results),
101
+ ).toHaveLength(1);
102
+ const file1Results =
103
+ newState.search.results['file1.js'];
104
+ expect(file1Results.matches).toHaveLength(1);
105
+ expect(file1Results.matches[0].line).toBe(1);
106
+ expect(file1Results.matches[0].index).toBe(6);
107
+ });
108
+
109
+ it('should not find matches when pattern does not exist', () => {
110
+ const mockFiles = {
111
+ 'file1.js': {
112
+ name: 'file1.js',
113
+ text: 'const world = "test";',
114
+ },
115
+ };
116
+
117
+ const stateWithSearch = {
118
+ ...initialState,
119
+ search: {
120
+ ...initialState.search,
121
+ pattern: 'hello',
122
+ },
123
+ };
124
+
125
+ const action: VZAction = {
126
+ type: 'set_search_results',
127
+ files: createMockShareDBDoc(mockFiles),
128
+ };
129
+
130
+ const newState = setSearchResultsReducer(
131
+ stateWithSearch,
132
+ action,
133
+ );
134
+
135
+ expect(
136
+ Object.keys(newState.search.results),
137
+ ).toHaveLength(0);
138
+ });
139
+
140
+ it('should handle uppercase search pattern', () => {
141
+ const mockFiles = {
142
+ 'file1.js': {
143
+ name: 'file1.js',
144
+ text: 'const Hello = "world";\nfunction hello() {}',
145
+ },
146
+ };
147
+
148
+ const stateWithSearch = {
149
+ ...initialState,
150
+ search: {
151
+ ...initialState.search,
152
+ pattern: 'HELLO', // uppercase search pattern
153
+ },
154
+ };
155
+
156
+ const action: VZAction = {
157
+ type: 'set_search_results',
158
+ files: createMockShareDBDoc(mockFiles),
159
+ };
160
+
161
+ const newState = setSearchResultsReducer(
162
+ stateWithSearch,
163
+ action,
164
+ );
165
+
166
+ expect(
167
+ Object.keys(newState.search.results),
168
+ ).toHaveLength(1);
169
+ const file1Results =
170
+ newState.search.results['file1.js'];
171
+ expect(file1Results.matches).toHaveLength(2); // Should find both "Hello" and "hello"
172
+ });
173
+
174
+ it('should handle mixed case search pattern', () => {
175
+ const mockFiles = {
176
+ 'file1.js': {
177
+ name: 'file1.js',
178
+ text: 'const HeLLo = "world";\nconst hello = "test";\nconst HELLO = "end";',
179
+ },
180
+ };
181
+
182
+ const stateWithSearch = {
183
+ ...initialState,
184
+ search: {
185
+ ...initialState.search,
186
+ pattern: 'HeLLo', // mixed case search pattern
187
+ },
188
+ };
189
+
190
+ const action: VZAction = {
191
+ type: 'set_search_results',
192
+ files: createMockShareDBDoc(mockFiles),
193
+ };
194
+
195
+ const newState = setSearchResultsReducer(
196
+ stateWithSearch,
197
+ action,
198
+ );
199
+
200
+ expect(
201
+ Object.keys(newState.search.results),
202
+ ).toHaveLength(1);
203
+ const file1Results =
204
+ newState.search.results['file1.js'];
205
+ expect(file1Results.matches).toHaveLength(3); // Should find all three variations
206
+ });
207
+
208
+ it('should skip files without text content', () => {
209
+ const mockFiles = {
210
+ 'file1.js': {
211
+ name: 'file1.js',
212
+ text: 'const hello = "world";',
213
+ },
214
+ 'file2.js': {
215
+ name: 'file2.js',
216
+ // no text property
217
+ },
218
+ };
219
+
220
+ const stateWithSearch = {
221
+ ...initialState,
222
+ search: {
223
+ ...initialState.search,
224
+ pattern: 'hello',
225
+ },
226
+ };
227
+
228
+ const action: VZAction = {
229
+ type: 'set_search_results',
230
+ files: createMockShareDBDoc(mockFiles),
231
+ };
232
+
233
+ const newState = setSearchResultsReducer(
234
+ stateWithSearch,
235
+ action,
236
+ );
237
+
238
+ // Should only find results in file1.js
239
+ expect(
240
+ Object.keys(newState.search.results),
241
+ ).toHaveLength(1);
242
+ expect(
243
+ newState.search.results['file1.js'],
244
+ ).toBeDefined();
245
+ expect(
246
+ newState.search.results['file2.js'],
247
+ ).toBeUndefined();
248
+ });
249
+ });
250
+ });
@@ -24,7 +24,9 @@ function searchPattern(
24
24
  const matches = [];
25
25
 
26
26
  for (let j = 0; j < lines.length; j++) {
27
- const index = lines[j].indexOf(pattern);
27
+ const index = lines[j]
28
+ .toLowerCase()
29
+ .indexOf(pattern.toLowerCase());
28
30
 
29
31
  if (index !== -1) {
30
32
  matches.push({
@@ -2,6 +2,8 @@
2
2
  // This file is the central point where the OT types are imported.
3
3
  // Localized to one file so it's easy to change it in future.
4
4
  import OTJSON1Presence from 'sharedb-client-browser/dist/ot-json1-presence-umd.cjs';
5
+ import jsondiff from 'json0-ot-diff';
6
+ import diffMatchPatch from 'diff-match-patch';
5
7
 
6
8
  export const { json1Presence, textUnicode } =
7
9
  OTJSON1Presence;
@@ -11,3 +13,12 @@ export const otType = json1Presence.type;
11
13
 
12
14
  // Applies an OT op to an object.
13
15
  export const apply = otType.apply;
16
+
17
+ export const diff = (a, b) =>
18
+ jsondiff(
19
+ a,
20
+ b,
21
+ diffMatchPatch,
22
+ json1Presence,
23
+ textUnicode,
24
+ );
package/src/runCode.ts CHANGED
@@ -1,31 +1,14 @@
1
1
  import { SubmitOperation } from './types';
2
2
  import { VizContent } from '@vizhub/viz-types';
3
- import { createSubmitOperation } from './submitOperation';
4
3
 
5
4
  /**
6
5
  * Creates a runCode function that triggers code execution by flashing `isInteracting` to `true`.
7
6
  * This works for both client-side (with submitOperation) and server-side (with ShareDB document).
8
7
  */
9
8
  export const createRunCodeFunction = (
10
- submitOperationOrDoc:
11
- | SubmitOperation
12
- | { data: any; submitOp: (ops: any) => void },
9
+ submitOperation: SubmitOperation,
13
10
  ) => {
14
11
  return () => {
15
- let submitOperation: SubmitOperation;
16
-
17
- // Check if this is a client-side submitOperation or server-side ShareDB document
18
- if (typeof submitOperationOrDoc === 'function') {
19
- // Client-side: already a submitOperation function
20
- submitOperation =
21
- submitOperationOrDoc as SubmitOperation;
22
- } else {
23
- // Server-side: create submitOperation from ShareDB document
24
- submitOperation = createSubmitOperation(
25
- submitOperationOrDoc,
26
- );
27
- }
28
-
29
12
  // Use the unified submitOperation approach for both client and server
30
13
  submitOperation((content: VizContent) => ({
31
14
  ...content,
@@ -39,7 +22,7 @@ export const createRunCodeFunction = (
39
22
  submitOperation(
40
23
  ({ isInteracting, ...newDocument }) => newDocument,
41
24
  );
42
- }, 0);
25
+ }, 100);
43
26
  };
44
27
  };
45
28
 
@@ -48,11 +31,9 @@ export const createRunCodeFunction = (
48
31
  * This is useful for maintaining compatibility with existing code that expects a ref.
49
32
  */
50
33
  export const createRunCodeRef = (
51
- submitOperationOrDoc:
52
- | SubmitOperation
53
- | { data: any; submitOp: (ops: any) => void },
34
+ submitOperation: SubmitOperation,
54
35
  ) => {
55
36
  return {
56
- current: createRunCodeFunction(submitOperationOrDoc),
37
+ current: createRunCodeFunction(submitOperation),
57
38
  };
58
39
  };
@@ -4,6 +4,10 @@ import {
4
4
  } from 'editcodewithai';
5
5
  import { formatMarkdownFiles } from 'llm-code-format';
6
6
 
7
+ // Dev flag for waiting 1 second before starting the LLM function.
8
+ // Useful for debugging and testing purposes, e.g. checking the typing indicator.
9
+ const delayStart = false;
10
+
7
11
  /**
8
12
  * Performs AI editing operations using streaming with incremental OT operations
9
13
  */
@@ -25,16 +29,15 @@ export const performAIEditing = async ({
25
29
  editFormat: 'whole',
26
30
  });
27
31
 
32
+ if (delayStart) {
33
+ await new Promise((resolve) =>
34
+ setTimeout(resolve, 1000),
35
+ );
36
+ }
37
+
28
38
  // Call the LLM function which will handle streaming and incremental file updates
29
39
  const result = await llmFunction(fullPrompt);
30
40
 
31
- // Clear the scratchpad and update status
32
- // clearAIScratchpadAndStatus(
33
- // shareDBDoc,
34
- // chatId,
35
- // 'Done editing with AI.',
36
- // );
37
-
38
41
  runCode();
39
42
 
40
43
  return {
@@ -1,6 +1,13 @@
1
1
  import { dateToTimestamp } from '@vizhub/viz-utils';
2
- import { diff } from '../../client/diff.js';
3
2
  import { randomId } from '../../randomId.js';
3
+ import { ShareDBDoc } from '../../types.js';
4
+ import { diff } from '../../ot.js';
5
+ import {
6
+ VizChatId,
7
+ VizContent,
8
+ VizFileId,
9
+ VizFiles,
10
+ } from '@vizhub/viz-types';
4
11
 
5
12
  /**
6
13
  * Ensures the chats object exists in the ShareDB document
@@ -18,7 +25,10 @@ export const ensureChatsExist = (shareDBDoc) => {
18
25
  /**
19
26
  * Ensures a specific chat exists in the ShareDB document
20
27
  */
21
- export const ensureChatExists = (shareDBDoc, chatId) => {
28
+ export const ensureChatExists = (
29
+ shareDBDoc: ShareDBDoc<VizContent>,
30
+ chatId: VizChatId,
31
+ ) => {
22
32
  if (!shareDBDoc.data.chats[chatId]) {
23
33
  const op = diff(shareDBDoc.data, {
24
34
  ...shareDBDoc.data,
@@ -40,9 +50,9 @@ export const ensureChatExists = (shareDBDoc, chatId) => {
40
50
  * Adds a user message to the chat
41
51
  */
42
52
  export const addUserMessage = (
43
- shareDBDoc,
44
- chatId,
45
- content,
53
+ shareDBDoc: ShareDBDoc<VizContent>,
54
+ chatId: VizChatId,
55
+ content: string,
46
56
  ) => {
47
57
  const userMessage = {
48
58
  id: `user-${Date.now()}`,
@@ -74,9 +84,9 @@ export const addUserMessage = (
74
84
  * Updates AI status in the chat
75
85
  */
76
86
  export const updateAIStatus = (
77
- shareDBDoc,
78
- chatId,
79
- status,
87
+ shareDBDoc: ShareDBDoc<VizContent>,
88
+ chatId: VizChatId,
89
+ status: string,
80
90
  ) => {
81
91
  const op = diff(shareDBDoc.data, {
82
92
  ...shareDBDoc.data,
@@ -95,9 +105,9 @@ export const updateAIStatus = (
95
105
  * Updates AI scratchpad content
96
106
  */
97
107
  export const updateAIScratchpad = (
98
- shareDBDoc,
99
- chatId,
100
- content,
108
+ shareDBDoc: ShareDBDoc<VizContent>,
109
+ chatId: VizChatId,
110
+ content: string,
101
111
  ) => {
102
112
  const op = diff(shareDBDoc.data, {
103
113
  ...shareDBDoc.data,
@@ -122,9 +132,9 @@ export const updateAIScratchpad = (
122
132
  * Clears AI scratchpad and updates status
123
133
  */
124
134
  export const clearAIScratchpadAndStatus = (
125
- shareDBDoc,
126
- chatId,
127
- status,
135
+ shareDBDoc: ShareDBDoc<VizContent>,
136
+ chatId: VizChatId,
137
+ status: string,
128
138
  ) => {
129
139
  const op = diff(shareDBDoc.data, {
130
140
  ...shareDBDoc.data,
@@ -141,12 +151,108 @@ export const clearAIScratchpadAndStatus = (
141
151
  };
142
152
 
143
153
  /**
144
- * Adds an AI response message to the chat
154
+ * Creates an initial empty AI message for streaming
155
+ */
156
+ export const createAIMessage = (
157
+ shareDBDoc: ShareDBDoc<VizContent>,
158
+ chatId: VizChatId,
159
+ ) => {
160
+ const aiMessage = {
161
+ id: `assistant-${Date.now()}`,
162
+ role: 'assistant',
163
+ content: '',
164
+ timestamp: dateToTimestamp(new Date()),
165
+ };
166
+
167
+ const messageOp = diff(shareDBDoc.data, {
168
+ ...shareDBDoc.data,
169
+ chats: {
170
+ ...shareDBDoc.data.chats,
171
+ [chatId]: {
172
+ ...shareDBDoc.data.chats[chatId],
173
+ messages: [
174
+ ...shareDBDoc.data.chats[chatId].messages,
175
+ aiMessage,
176
+ ],
177
+ updatedAt: dateToTimestamp(new Date()),
178
+ },
179
+ },
180
+ });
181
+ shareDBDoc.submitOp(messageOp);
182
+
183
+ return aiMessage.id;
184
+ };
185
+
186
+ /**
187
+ * Updates the content of an AI message during streaming
188
+ */
189
+ export const updateAIMessageContent = (
190
+ shareDBDoc: ShareDBDoc<VizContent>,
191
+ chatId: VizChatId,
192
+ messageId: string,
193
+ content: string,
194
+ ) => {
195
+ const chat = shareDBDoc.data.chats[chatId];
196
+ const messageIndex = chat.messages.findIndex(
197
+ (msg) => msg.id === messageId,
198
+ );
199
+
200
+ if (messageIndex === -1) {
201
+ console.warn(
202
+ `AI message with id ${messageId} not found`,
203
+ );
204
+ return;
205
+ }
206
+
207
+ const updatedMessages = [...chat.messages];
208
+ updatedMessages[messageIndex] = {
209
+ ...updatedMessages[messageIndex],
210
+ content,
211
+ };
212
+
213
+ const messageOp = diff(shareDBDoc.data, {
214
+ ...shareDBDoc.data,
215
+ chats: {
216
+ ...shareDBDoc.data.chats,
217
+ [chatId]: {
218
+ ...chat,
219
+ messages: updatedMessages,
220
+ updatedAt: dateToTimestamp(new Date()),
221
+ },
222
+ },
223
+ });
224
+ shareDBDoc.submitOp(messageOp);
225
+ };
226
+
227
+ /**
228
+ * Finalizes an AI message by clearing temporary fields
229
+ */
230
+ export const finalizeAIMessage = (
231
+ shareDBDoc: ShareDBDoc<VizContent>,
232
+ chatId: VizChatId,
233
+ ) => {
234
+ const op = diff(shareDBDoc.data, {
235
+ ...shareDBDoc.data,
236
+ chats: {
237
+ ...shareDBDoc.data.chats,
238
+ [chatId]: {
239
+ ...shareDBDoc.data.chats[chatId],
240
+ aiScratchpad: undefined,
241
+ aiStatus: undefined,
242
+ updatedAt: dateToTimestamp(new Date()),
243
+ },
244
+ },
245
+ });
246
+ shareDBDoc.submitOp(op);
247
+ };
248
+
249
+ /**
250
+ * Adds an AI response message to the chat (legacy function, kept for compatibility)
145
251
  */
146
252
  export const addAIMessage = (
147
- shareDBDoc,
148
- chatId,
149
- content,
253
+ shareDBDoc: ShareDBDoc<VizContent>,
254
+ chatId: VizChatId,
255
+ content?: string,
150
256
  ) => {
151
257
  const aiResponse = {
152
258
  id: Date.now() + 1,
@@ -178,7 +284,10 @@ export const addAIMessage = (
178
284
  /**
179
285
  * Updates files in the ShareDB document
180
286
  */
181
- export const updateFiles = (shareDBDoc, files) => {
287
+ export const updateFiles = (
288
+ shareDBDoc: ShareDBDoc<VizContent>,
289
+ files: VizFiles,
290
+ ) => {
182
291
  const filesOp = diff(shareDBDoc.data, {
183
292
  ...shareDBDoc.data,
184
293
  files,
@@ -186,30 +295,6 @@ export const updateFiles = (shareDBDoc, files) => {
186
295
  shareDBDoc.submitOp(filesOp);
187
296
  };
188
297
 
189
- /**
190
- * Sets isInteracting flag
191
- */
192
- export const setIsInteracting = (
193
- shareDBDoc,
194
- isInteracting,
195
- ) => {
196
- // Only generate an operation if the value is actually changing
197
- const currentIsInteracting =
198
- shareDBDoc.data.isInteracting;
199
-
200
- if (currentIsInteracting === isInteracting) {
201
- return;
202
- }
203
-
204
- const newState = {
205
- ...shareDBDoc.data,
206
- isInteracting: isInteracting,
207
- };
208
-
209
- const interactingOp = diff(shareDBDoc.data, newState);
210
- shareDBDoc.submitOp(interactingOp);
211
- };
212
-
213
298
  /**
214
299
  * Finds a file ID by searching for a matching file name
215
300
  */
@@ -233,7 +318,10 @@ export const resolveFileId = (
233
318
  /**
234
319
  * Creates a new file with a random ID
235
320
  */
236
- export const createNewFile = (shareDBDoc, fileName) => {
321
+ export const createNewFile = (
322
+ shareDBDoc: ShareDBDoc<VizContent>,
323
+ fileName: string,
324
+ ) => {
237
325
  // Generate a new random file ID
238
326
  const newFileId = randomId();
239
327
 
@@ -270,7 +358,10 @@ export const ensureFileExists = (shareDBDoc, fileName) => {
270
358
  /**
271
359
  * Clears the content of a file
272
360
  */
273
- export const clearFileContent = (shareDBDoc, fileId) => {
361
+ export const clearFileContent = (
362
+ shareDBDoc: ShareDBDoc<VizContent>,
363
+ fileId: VizFileId,
364
+ ) => {
274
365
  const currentFile = shareDBDoc.data.files[fileId];
275
366
 
276
367
  if (currentFile && currentFile.text) {
@@ -295,29 +386,24 @@ export const clearFileContent = (shareDBDoc, fileId) => {
295
386
  * Appends a line to a file using OT operations
296
387
  */
297
388
  export const appendLineToFile = (
298
- shareDBDoc,
299
- fileId,
300
- line,
389
+ shareDBDoc: ShareDBDoc<VizContent>,
390
+ fileId: VizFileId,
391
+ line: string,
301
392
  ) => {
302
393
  const currentFile = shareDBDoc.data.files[fileId];
303
394
  const currentContent = currentFile?.text || '';
304
395
  const newContent = currentContent + line + '\n';
305
396
 
306
- // Create the new file state
307
- const newFileState = {
308
- ...currentFile,
309
- text: newContent,
310
- };
311
-
312
397
  const newDocState = {
313
398
  ...shareDBDoc.data,
314
399
  files: {
315
400
  ...shareDBDoc.data.files,
316
- [fileId]: newFileState,
401
+ [fileId]: {
402
+ ...currentFile,
403
+ text: newContent,
404
+ },
317
405
  },
318
406
  };
319
407
 
320
- // Generate OT operation using the diff utility
321
- const op = diff(shareDBDoc.data, newDocState);
322
- shareDBDoc.submitOp(op);
408
+ shareDBDoc.submitOp(diff(shareDBDoc.data, newDocState));
323
409
  };
@@ -1,5 +1,5 @@
1
1
  import { dateToTimestamp } from '@vizhub/viz-utils';
2
- import { diff } from '../../client/diff.js';
2
+ import { diff } from '../../ot.js';
3
3
 
4
4
  /**
5
5
  * Handles errors by adding an error message to the chat and clearing AI state
@@ -10,6 +10,7 @@ import { handleError } from './errorHandling.js';
10
10
  import { createRunCodeFunction } from '../../runCode.js';
11
11
  import { ShareDBDoc } from '../../types.js';
12
12
  import { VizContent } from '@vizhub/viz-types';
13
+ import { createSubmitOperation } from '../../submitOperation.js';
13
14
 
14
15
  const DEBUG = false;
15
16
 
@@ -32,6 +33,8 @@ export const handleAIChatMessage =
32
33
  content,
33
34
  'chatId:',
34
35
  chatId,
36
+ 'shareDBDoc:',
37
+ shareDBDoc,
35
38
  );
36
39
  }
37
40
 
@@ -55,8 +58,11 @@ export const handleAIChatMessage =
55
58
  chatId,
56
59
  });
57
60
 
58
- // Create server-side runCode function using shared module
59
- const runCode = createRunCodeFunction(shareDBDoc);
61
+ // Create server-side runCode function using shareDBDoc
62
+ const submitOperation =
63
+ createSubmitOperation(shareDBDoc);
64
+ const runCode =
65
+ createRunCodeFunction(submitOperation);
60
66
 
61
67
  // Perform AI editing
62
68
  const editResult = await performAIEditing({