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.
- package/dist/assets/bindings_wasm_bg-BQfW5T_2.wasm +0 -0
- package/dist/assets/buildWorker-B-vXZ9gA.js +579 -0
- package/dist/assets/{index-DKLiE98a.css → index-CP9o_hN5.css} +1 -1
- package/dist/assets/index-kPLgjbcb.js +329 -0
- package/dist/index.html +2 -2
- package/package.json +17 -16
- package/src/client/App/style.scss +2 -2
- package/src/client/CodeEditor/getOrCreateEditor.ts +2 -5
- package/src/client/CodeEditor/index.tsx +1 -1
- package/src/client/CodeEditor/json1PresenceDisplay.ts +40 -12
- package/src/client/VZRight.tsx +68 -6
- package/src/client/VZSidebar/AIChat/ChatInput.tsx +17 -5
- package/src/client/VZSidebar/AIChat/Message.tsx +21 -8
- package/src/client/VZSidebar/AIChat/MessageList.tsx +29 -31
- package/src/client/VZSidebar/AIChat/StreamingMessage.tsx +6 -1
- package/src/client/VZSidebar/AIChat/TypingIndicator.tsx +7 -1
- package/src/client/VZSidebar/AIChat/index.tsx +22 -9
- package/src/client/VZSidebar/aiCopyPaste.ts +8 -1
- package/src/client/VZSidebar/index.tsx +10 -8
- package/src/client/buildWorker.ts +3 -0
- package/src/client/vzReducer/searchReducer.test.ts +250 -0
- package/src/client/vzReducer/searchReducer.ts +3 -1
- package/src/{ot.js → ot.ts} +11 -0
- package/src/runCode.ts +4 -23
- package/src/server/aiChatHandler/aiEditing.ts +10 -7
- package/src/server/aiChatHandler/chatOperations.ts +144 -58
- package/src/server/aiChatHandler/errorHandling.ts +1 -1
- package/src/server/aiChatHandler/index.ts +8 -2
- package/src/server/aiChatHandler/llmStreaming.ts +83 -44
- package/src/submitOperation.ts +1 -1
- package/dist/assets/index-ubSLcdJf.js +0 -327
- package/src/client/diff.js +0 -12
- package/src/runCode.js +0 -48
- package/src/submitOperation.js +0 -16
- /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]
|
|
27
|
+
const index = lines[j]
|
|
28
|
+
.toLowerCase()
|
|
29
|
+
.indexOf(pattern.toLowerCase());
|
|
28
30
|
|
|
29
31
|
if (index !== -1) {
|
|
30
32
|
matches.push({
|
package/src/{ot.js → ot.ts}
RENAMED
|
@@ -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
|
-
|
|
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
|
-
},
|
|
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
|
-
|
|
52
|
-
| SubmitOperation
|
|
53
|
-
| { data: any; submitOp: (ops: any) => void },
|
|
34
|
+
submitOperation: SubmitOperation,
|
|
54
35
|
) => {
|
|
55
36
|
return {
|
|
56
|
-
current: createRunCodeFunction(
|
|
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 = (
|
|
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
|
-
*
|
|
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 = (
|
|
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 = (
|
|
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 = (
|
|
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]:
|
|
401
|
+
[fileId]: {
|
|
402
|
+
...currentFile,
|
|
403
|
+
text: newContent,
|
|
404
|
+
},
|
|
317
405
|
},
|
|
318
406
|
};
|
|
319
407
|
|
|
320
|
-
|
|
321
|
-
const op = diff(shareDBDoc.data, newDocState);
|
|
322
|
-
shareDBDoc.submitOp(op);
|
|
408
|
+
shareDBDoc.submitOp(diff(shareDBDoc.data, newDocState));
|
|
323
409
|
};
|
|
@@ -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
|
|
59
|
-
const
|
|
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({
|