vzcode 1.45.0 → 1.47.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/{index-C1lTrzOZ.js → index-BMYXp1Uw.js} +114 -114
- package/dist/assets/{index-DeeOcIzw.css → index-BXyJ2hMf.css} +1 -1
- package/dist/index.html +2 -2
- package/package.json +7 -3
- package/src/client/CodeEditor/index.tsx +7 -4
- package/src/client/RunCodeWidget/index.tsx +18 -15
- package/src/client/SplitPaneResizeContext.tsx +26 -108
- package/src/client/VZCodeContext.tsx +8 -1
- package/src/client/VZRight.tsx +32 -4
- package/src/client/VZSidebar/AIChat/ChatInput.tsx +72 -2
- package/src/client/VZSidebar/AIChat/DiffView.scss +29 -0
- package/src/client/VZSidebar/AIChat/DiffView.tsx +62 -1
- package/src/client/VZSidebar/AIChat/Message.tsx +14 -1
- package/src/client/VZSidebar/AIChat/MessageList.tsx +134 -13
- package/src/client/VZSidebar/AIChat/ThinkingScratchpad.tsx +37 -0
- package/src/client/VZSidebar/AIChat/index.tsx +187 -16
- package/src/client/VZSidebar/AIChat/styles.scss +278 -1
- package/src/client/VZSidebar/aiCopyPaste.ts +6 -1
- package/src/client/VZSidebar/index.tsx +10 -2
- package/src/client/bootstrap.ts +11 -1
- package/src/client/useActions.ts +12 -0
- package/src/client/usePrettier/index.ts +1 -3
- package/src/client/vzReducer/aiChatReducer.ts +13 -0
- package/src/client/vzReducer/createInitialState.ts +1 -0
- package/src/client/vzReducer/index.ts +9 -0
- package/src/client/vzReducer/searchReducer.test.ts +44 -2
- package/src/runCode.ts +5 -17
- package/src/server/aiChatHandler/aiEditing.ts +37 -0
- package/src/server/aiChatHandler/chatOperations.ts +38 -1
- package/src/server/aiChatHandler/index.ts +20 -9
- package/src/server/aiChatHandler/llmStreaming.ts +101 -19
- package/src/server/generateAIResponse.ts +4 -0
- package/src/server/index.ts +52 -0
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
dateToTimestamp,
|
|
3
|
+
generateRunId,
|
|
4
|
+
} from '@vizhub/viz-utils';
|
|
2
5
|
import { randomId } from '../../randomId.js';
|
|
3
6
|
import { ShareDBDoc } from '../../types.js';
|
|
4
7
|
import { diff } from '../../ot.js';
|
|
@@ -253,6 +256,7 @@ export const addDiffToAIMessage = (
|
|
|
253
256
|
shareDBDoc: ShareDBDoc<VizContent>,
|
|
254
257
|
chatId: VizChatId,
|
|
255
258
|
diffData: any,
|
|
259
|
+
beforeFiles?: VizFiles, // Optional snapshot for undo functionality
|
|
256
260
|
) => {
|
|
257
261
|
const chat = shareDBDoc.data.chats[chatId];
|
|
258
262
|
const messages = [...chat.messages];
|
|
@@ -266,6 +270,7 @@ export const addDiffToAIMessage = (
|
|
|
266
270
|
const newMessage = {
|
|
267
271
|
...messages[lastAIMessageIndex],
|
|
268
272
|
diffData,
|
|
273
|
+
...(beforeFiles && { beforeFiles }), // Add beforeFiles only if provided
|
|
269
274
|
};
|
|
270
275
|
// Use type assertion to extend the message with diffData
|
|
271
276
|
(messages[lastAIMessageIndex] as any) = newMessage;
|
|
@@ -447,3 +452,35 @@ export const appendLineToFile = (
|
|
|
447
452
|
|
|
448
453
|
shareDBDoc.submitOp(diff(shareDBDoc.data, newDocState));
|
|
449
454
|
};
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
* Undoes the last AI edit by restoring files and removing the AI message
|
|
458
|
+
*/
|
|
459
|
+
export const undoAIEdit = (
|
|
460
|
+
shareDBDoc: ShareDBDoc<VizContent>,
|
|
461
|
+
chatId: VizChatId,
|
|
462
|
+
messageId: string,
|
|
463
|
+
beforeFiles: VizFiles,
|
|
464
|
+
) => {
|
|
465
|
+
const chat = shareDBDoc.data.chats[chatId];
|
|
466
|
+
const messages = chat.messages.filter(
|
|
467
|
+
(msg) => msg.id !== messageId,
|
|
468
|
+
);
|
|
469
|
+
|
|
470
|
+
const op = diff(shareDBDoc.data, {
|
|
471
|
+
...shareDBDoc.data,
|
|
472
|
+
files: beforeFiles,
|
|
473
|
+
chats: {
|
|
474
|
+
...shareDBDoc.data.chats,
|
|
475
|
+
[chatId]: {
|
|
476
|
+
...chat,
|
|
477
|
+
messages,
|
|
478
|
+
updatedAt: dateToTimestamp(new Date()),
|
|
479
|
+
},
|
|
480
|
+
},
|
|
481
|
+
// Trigger a re-run
|
|
482
|
+
runId: generateRunId(),
|
|
483
|
+
});
|
|
484
|
+
|
|
485
|
+
shareDBDoc.submitOp(op);
|
|
486
|
+
};
|
|
@@ -6,7 +6,10 @@ import {
|
|
|
6
6
|
addDiffToAIMessage,
|
|
7
7
|
} from './chatOperations.js';
|
|
8
8
|
import { createLLMFunction } from './llmStreaming.js';
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
performAIEditing,
|
|
11
|
+
performAIChat,
|
|
12
|
+
} from './aiEditing.js';
|
|
10
13
|
import { handleError } from './errorHandling.js';
|
|
11
14
|
import { createRunCodeFunction } from '../../runCode.js';
|
|
12
15
|
import { ShareDBDoc } from '../../types.js';
|
|
@@ -26,7 +29,7 @@ export const handleAIChatMessage =
|
|
|
26
29
|
onCreditDeduction?: any;
|
|
27
30
|
}) =>
|
|
28
31
|
async (req: any, res: any) => {
|
|
29
|
-
const { content, chatId } = req.body;
|
|
32
|
+
const { content, chatId, mode = 'edit' } = req.body;
|
|
30
33
|
|
|
31
34
|
if (DEBUG) {
|
|
32
35
|
console.log(
|
|
@@ -65,13 +68,20 @@ export const handleAIChatMessage =
|
|
|
65
68
|
const runCode =
|
|
66
69
|
createRunCodeFunction(submitOperation);
|
|
67
70
|
|
|
68
|
-
// Perform AI editing
|
|
69
|
-
const editResult =
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
71
|
+
// Perform AI editing or chat based on mode
|
|
72
|
+
const editResult =
|
|
73
|
+
mode === 'ask'
|
|
74
|
+
? await performAIChat({
|
|
75
|
+
prompt: content,
|
|
76
|
+
shareDBDoc,
|
|
77
|
+
llmFunction,
|
|
78
|
+
})
|
|
79
|
+
: await performAIEditing({
|
|
80
|
+
prompt: content,
|
|
81
|
+
shareDBDoc,
|
|
82
|
+
llmFunction,
|
|
83
|
+
runCode,
|
|
84
|
+
});
|
|
75
85
|
|
|
76
86
|
// Add diff data to the AI message if there are changes
|
|
77
87
|
if (
|
|
@@ -82,6 +92,7 @@ export const handleAIChatMessage =
|
|
|
82
92
|
shareDBDoc,
|
|
83
93
|
chatId,
|
|
84
94
|
editResult.diffData,
|
|
95
|
+
(editResult as any).beforeFiles, // Pass the beforeFiles snapshot for undo (only available for edit mode)
|
|
85
96
|
);
|
|
86
97
|
}
|
|
87
98
|
|
|
@@ -3,7 +3,9 @@ import {
|
|
|
3
3
|
StreamingMarkdownParser,
|
|
4
4
|
} from 'llm-code-format';
|
|
5
5
|
import { ChatOpenAI } from '@langchain/openai';
|
|
6
|
+
import OpenAI from 'openai';
|
|
6
7
|
import fs from 'fs';
|
|
8
|
+
import { generateRunId } from '@vizhub/viz-utils';
|
|
7
9
|
import {
|
|
8
10
|
updateAIStatus,
|
|
9
11
|
createAIMessage,
|
|
@@ -13,8 +15,12 @@ import {
|
|
|
13
15
|
clearFileContent,
|
|
14
16
|
appendLineToFile,
|
|
15
17
|
updateFiles,
|
|
18
|
+
updateAIScratchpad,
|
|
16
19
|
} from './chatOperations.js';
|
|
17
20
|
import { mergeFileChanges } from 'editcodewithai';
|
|
21
|
+
import { diff } from '../../ot.js';
|
|
22
|
+
import { VizChatId, VizContent } from '@vizhub/viz-types';
|
|
23
|
+
import { ShareDBDoc } from '../../types.js';
|
|
18
24
|
|
|
19
25
|
const DEBUG = false;
|
|
20
26
|
|
|
@@ -34,28 +40,30 @@ const DEBUG = false;
|
|
|
34
40
|
const enableStreamingEditing = false;
|
|
35
41
|
|
|
36
42
|
/**
|
|
37
|
-
* Creates and configures the LLM function for streaming
|
|
43
|
+
* Creates and configures the LLM function for streaming with reasoning tokens
|
|
38
44
|
*/
|
|
39
45
|
export const createLLMFunction = ({
|
|
40
46
|
shareDBDoc,
|
|
41
47
|
createVizBotLocalPresence,
|
|
42
48
|
chatId,
|
|
49
|
+
}: {
|
|
50
|
+
shareDBDoc: ShareDBDoc<VizContent>;
|
|
51
|
+
createVizBotLocalPresence: () => any;
|
|
52
|
+
chatId: VizChatId;
|
|
43
53
|
}) => {
|
|
44
54
|
return async (fullPrompt: string) => {
|
|
45
55
|
const localPresence = createVizBotLocalPresence();
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
},
|
|
56
|
+
|
|
57
|
+
// Create OpenRouter client for reasoning token support
|
|
58
|
+
const openRouterClient = new OpenAI({
|
|
59
|
+
apiKey: process.env.VIZHUB_EDIT_WITH_AI_API_KEY,
|
|
60
|
+
baseURL:
|
|
61
|
+
process.env.VIZHUB_EDIT_WITH_AI_BASE_URL ||
|
|
62
|
+
'https://openrouter.ai/api/v1',
|
|
63
|
+
defaultHeaders: {
|
|
64
|
+
'HTTP-Referer': 'https://vizhub.com',
|
|
65
|
+
'X-Title': 'VizHub',
|
|
57
66
|
},
|
|
58
|
-
streaming: true,
|
|
59
67
|
});
|
|
60
68
|
|
|
61
69
|
let fullContent = '';
|
|
@@ -177,12 +185,58 @@ export const createLLMFunction = ({
|
|
|
177
185
|
const parser = new StreamingMarkdownParser(callbacks);
|
|
178
186
|
|
|
179
187
|
const chunks = [];
|
|
188
|
+
let reasoningContent = '';
|
|
189
|
+
|
|
190
|
+
// Stream the response with reasoning tokens
|
|
191
|
+
const modelName =
|
|
192
|
+
process.env.VIZHUB_EDIT_WITH_AI_MODEL_NAME ||
|
|
193
|
+
'anthropic/claude-3.5-sonnet';
|
|
194
|
+
const stream = await (
|
|
195
|
+
openRouterClient.chat.completions.create as any
|
|
196
|
+
)({
|
|
197
|
+
model: modelName,
|
|
198
|
+
messages: [{ role: 'user', content: fullPrompt }],
|
|
199
|
+
max_tokens: 8192,
|
|
200
|
+
reasoning: {
|
|
201
|
+
effort: 'medium',
|
|
202
|
+
exclude: false,
|
|
203
|
+
},
|
|
204
|
+
usage: { include: true },
|
|
205
|
+
stream: true,
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
let reasoningStarted = false;
|
|
209
|
+
let contentStarted = false;
|
|
180
210
|
|
|
181
|
-
// Stream the response
|
|
182
|
-
const stream = await chatModel.stream(fullPrompt);
|
|
183
211
|
for await (const chunk of stream) {
|
|
184
|
-
|
|
185
|
-
|
|
212
|
+
const delta = chunk.choices[0]?.delta as any; // Type assertion for OpenRouter-specific reasoning fields
|
|
213
|
+
|
|
214
|
+
if (delta?.reasoning) {
|
|
215
|
+
// Handle reasoning tokens (thinking)
|
|
216
|
+
if (!reasoningStarted) {
|
|
217
|
+
reasoningStarted = true;
|
|
218
|
+
updateAIStatus(shareDBDoc, chatId, 'Thinking...');
|
|
219
|
+
}
|
|
220
|
+
reasoningContent += delta.reasoning;
|
|
221
|
+
updateAIScratchpad(
|
|
222
|
+
shareDBDoc,
|
|
223
|
+
chatId,
|
|
224
|
+
reasoningContent,
|
|
225
|
+
);
|
|
226
|
+
} else if (delta?.content) {
|
|
227
|
+
// Handle regular content tokens
|
|
228
|
+
if (reasoningStarted && !contentStarted) {
|
|
229
|
+
// Clear reasoning when content starts
|
|
230
|
+
contentStarted = true;
|
|
231
|
+
updateAIScratchpad(shareDBDoc, chatId, '');
|
|
232
|
+
updateAIStatus(
|
|
233
|
+
shareDBDoc,
|
|
234
|
+
chatId,
|
|
235
|
+
'Generating response...',
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const chunkContent = delta.content;
|
|
186
240
|
chunks.push(chunkContent);
|
|
187
241
|
|
|
188
242
|
if (enableStreamingEditing) {
|
|
@@ -196,14 +250,20 @@ export const createLLMFunction = ({
|
|
|
196
250
|
fullContent,
|
|
197
251
|
);
|
|
198
252
|
}
|
|
253
|
+
} else if (chunk.usage) {
|
|
254
|
+
// Handle usage information
|
|
255
|
+
DEBUG && console.log('Usage:', chunk.usage);
|
|
199
256
|
}
|
|
200
257
|
|
|
201
|
-
if (!generationId && chunk.
|
|
202
|
-
generationId = chunk.
|
|
258
|
+
if (!generationId && chunk.id) {
|
|
259
|
+
generationId = chunk.id;
|
|
203
260
|
}
|
|
204
261
|
}
|
|
205
262
|
await parser.flushRemaining();
|
|
206
263
|
reportFileEdited();
|
|
264
|
+
|
|
265
|
+
// Final cleanup - clear scratchpad and set final status
|
|
266
|
+
updateAIScratchpad(shareDBDoc, chatId, '');
|
|
207
267
|
updateAIStatus(shareDBDoc, chatId, 'Done editing.');
|
|
208
268
|
updateAIMessageContent(
|
|
209
269
|
shareDBDoc,
|
|
@@ -242,6 +302,28 @@ export const createLLMFunction = ({
|
|
|
242
302
|
);
|
|
243
303
|
}
|
|
244
304
|
|
|
305
|
+
// Generate a new runId to trigger a run when AI finishes editing
|
|
306
|
+
// This will trigger a re-run without hot reloading
|
|
307
|
+
const newRunId = generateRunId();
|
|
308
|
+
const runIdOp = diff(shareDBDoc.data, {
|
|
309
|
+
...shareDBDoc.data,
|
|
310
|
+
runId: newRunId,
|
|
311
|
+
});
|
|
312
|
+
shareDBDoc.submitOp(runIdOp, (error) => {
|
|
313
|
+
if (error) {
|
|
314
|
+
console.warn(
|
|
315
|
+
'Error setting runId after AI editing:',
|
|
316
|
+
error,
|
|
317
|
+
);
|
|
318
|
+
} else {
|
|
319
|
+
DEBUG &&
|
|
320
|
+
console.log(
|
|
321
|
+
'Set new runId after AI editing:',
|
|
322
|
+
newRunId,
|
|
323
|
+
);
|
|
324
|
+
}
|
|
325
|
+
});
|
|
326
|
+
|
|
245
327
|
// Write chunks file for debugging
|
|
246
328
|
if (DEBUG) {
|
|
247
329
|
const chunksFileJSONpath = `./ai-chunks-${chatId}.json`;
|
|
@@ -150,6 +150,10 @@ export const generateAIResponse = async ({
|
|
|
150
150
|
// model: 'gpt-3.5-turbo',
|
|
151
151
|
model: 'gpt-4o',
|
|
152
152
|
messages,
|
|
153
|
+
reasoning: {
|
|
154
|
+
effort: 'medium',
|
|
155
|
+
exclude: false,
|
|
156
|
+
} as any, // Type assertion for OpenRouter-specific reasoning parameter
|
|
153
157
|
stream: true,
|
|
154
158
|
});
|
|
155
159
|
|
package/src/server/index.ts
CHANGED
|
@@ -14,6 +14,7 @@ import { computeInitialDocument } from './computeInitialDocument.js';
|
|
|
14
14
|
import { handleAIAssist } from './handleAIAssist.js';
|
|
15
15
|
import { handleAICopilot } from './handleAICopilot.js';
|
|
16
16
|
import { handleAIChatMessage } from './handleAIChatMessage.js';
|
|
17
|
+
import { undoAIEdit } from './aiChatHandler/chatOperations.js';
|
|
17
18
|
import { isDirectory } from './isDirectory.js';
|
|
18
19
|
import { createToken } from './livekit.js';
|
|
19
20
|
import './setupEnv.js';
|
|
@@ -141,6 +142,57 @@ app.post(
|
|
|
141
142
|
}),
|
|
142
143
|
);
|
|
143
144
|
|
|
145
|
+
// Handle AI Chat Undo requests.
|
|
146
|
+
app.post(
|
|
147
|
+
'/ai-chat-undo',
|
|
148
|
+
bodyParser.json(),
|
|
149
|
+
async (req, res) => {
|
|
150
|
+
const { chatId, messageId } = req.body;
|
|
151
|
+
|
|
152
|
+
if (!chatId || !messageId) {
|
|
153
|
+
return res
|
|
154
|
+
.status(400)
|
|
155
|
+
.json({ error: 'Missing chatId or messageId' });
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
try {
|
|
159
|
+
const chat = shareDBDoc.data.chats?.[chatId];
|
|
160
|
+
if (!chat) {
|
|
161
|
+
return res
|
|
162
|
+
.status(404)
|
|
163
|
+
.json({ error: 'Chat not found' });
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const message = chat.messages.find(
|
|
167
|
+
(msg) => msg.id === messageId,
|
|
168
|
+
);
|
|
169
|
+
if (
|
|
170
|
+
!message ||
|
|
171
|
+
message.role !== 'assistant' ||
|
|
172
|
+
!message.beforeFiles
|
|
173
|
+
) {
|
|
174
|
+
return res
|
|
175
|
+
.status(400)
|
|
176
|
+
.json({ error: 'Invalid message for undo' });
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
undoAIEdit(
|
|
180
|
+
shareDBDoc,
|
|
181
|
+
chatId,
|
|
182
|
+
messageId,
|
|
183
|
+
message.beforeFiles,
|
|
184
|
+
);
|
|
185
|
+
|
|
186
|
+
res.status(200).json({ success: true });
|
|
187
|
+
} catch (error) {
|
|
188
|
+
console.error('Error undoing AI edit:', error);
|
|
189
|
+
res
|
|
190
|
+
.status(500)
|
|
191
|
+
.json({ error: 'Failed to undo edit' });
|
|
192
|
+
}
|
|
193
|
+
},
|
|
194
|
+
);
|
|
195
|
+
|
|
144
196
|
// Livekit Token Generator
|
|
145
197
|
app.get('/livekit-token', async (req, res) => {
|
|
146
198
|
const { room, username } = req.query;
|