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.
- package/dist/assets/{buildWorker-Dsx6rOdK.js → buildWorker-wLkQ0yz1.js} +64 -64
- package/dist/assets/{index-BM8tDh6M.css → index-BoFeK4GL.css} +1 -1
- package/dist/assets/{index-CpxqGhXj.js → index-DuUjtrt6.js} +150 -150
- package/dist/assets/{worker-BeBZGG1n.js → worker-DHxSPM7N.js} +78 -78
- package/dist/cli.js +113 -0
- package/dist/index.html +2 -2
- package/dist/ot.js +12 -0
- package/dist/randomId.js +14 -0
- package/dist/runCode.js +20 -0
- package/dist/server/aiChatHandler/aiEditing.js +89 -0
- package/dist/server/aiChatHandler/chatOperations.js +360 -0
- package/dist/server/aiChatHandler/errorHandling.js +49 -0
- package/dist/server/aiChatHandler/index.js +112 -0
- package/dist/server/aiChatHandler/llmStreaming.js +284 -0
- package/dist/server/aiChatHandler/validation.js +19 -0
- package/dist/server/computeInitialDocument.js +146 -0
- package/dist/server/config.js +21 -0
- package/dist/server/featureFlags.js +5 -0
- package/dist/server/generateAIResponse.js +129 -0
- package/dist/server/handleAIAssist.js +39 -0
- package/dist/server/handleAIChatMessage.js +2 -0
- package/dist/server/handleAICopilot.js +81 -0
- package/dist/server/index.js +297 -0
- package/dist/server/isDirectory.js +1 -0
- package/dist/server/livekit.js +17 -0
- package/dist/server/prettier.js +90 -0
- package/dist/server/setupEnv.js +7 -0
- package/dist/submitOperation.js +14 -0
- package/dist/types.js +1 -0
- package/dist/utils/fileDiff.js +66 -0
- package/package.json +21 -20
- package/src/cli.ts +208 -0
- package/src/client/App/index.tsx +0 -1
- package/src/client/VZCodeContext/types.ts +3 -4
- package/src/client/VZCodeContext/useVZCodeState.ts +24 -6
- package/src/client/VZSidebar/AIChat/ChatInput.tsx +4 -0
- package/src/client/VZSidebar/AIChat/Message.tsx +2 -66
- package/src/client/VZSidebar/AIChat/MessageList.tsx +0 -15
- package/src/client/VZSidebar/AIChat/index.tsx +91 -7
- package/src/client/VZSidebar/AIChat/styles.scss +19 -0
- package/src/server/aiChatHandler/aiEditing.ts +0 -1
- package/src/server/aiChatHandler/chatOperations.ts +0 -34
- package/src/server/aiChatHandler/index.ts +0 -1
- package/src/server/aiChatHandler/llmStreaming.ts +3 -3
- package/src/server/index.ts +1 -10
- package/src/server/aiChatHandler/undoHandler.ts +0 -97
- package/src/server/handleAIChatUndo.ts +0 -2
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
useContext,
|
|
3
|
+
useMemo,
|
|
4
|
+
useEffect,
|
|
5
|
+
useCallback,
|
|
6
|
+
} from 'react';
|
|
2
7
|
import { VZCodeContext } from '../../VZCodeContext';
|
|
3
8
|
import { MessageList } from './MessageList';
|
|
4
9
|
import { ChatInput } from './ChatInput';
|
|
@@ -8,6 +13,35 @@ const DEBUG = false;
|
|
|
8
13
|
|
|
9
14
|
const showSuggestedRequests = false;
|
|
10
15
|
|
|
16
|
+
// Component for displaying the list of existing chats
|
|
17
|
+
const ChatList = ({
|
|
18
|
+
chats,
|
|
19
|
+
selectedChatId,
|
|
20
|
+
onSelectChat,
|
|
21
|
+
getChatTitle,
|
|
22
|
+
}) => {
|
|
23
|
+
return (
|
|
24
|
+
<div className="ai-chat-list">
|
|
25
|
+
<div className="ai-chat-list-header">
|
|
26
|
+
<h4>Previous Chats</h4>
|
|
27
|
+
</div>
|
|
28
|
+
<div className="ai-chat-suggested-prompts">
|
|
29
|
+
{chats.map((chat) => (
|
|
30
|
+
<button
|
|
31
|
+
key={chat.id}
|
|
32
|
+
className={`ai-chat-suggested-prompt ${
|
|
33
|
+
selectedChatId === chat.id ? 'selected' : ''
|
|
34
|
+
}`}
|
|
35
|
+
onClick={() => onSelectChat(chat.id)}
|
|
36
|
+
>
|
|
37
|
+
{getChatTitle(chat)}
|
|
38
|
+
</button>
|
|
39
|
+
))}
|
|
40
|
+
</div>
|
|
41
|
+
</div>
|
|
42
|
+
);
|
|
43
|
+
};
|
|
44
|
+
|
|
11
45
|
export const AIChat = () => {
|
|
12
46
|
const {
|
|
13
47
|
aiChatFocused,
|
|
@@ -16,6 +50,8 @@ export const AIChat = () => {
|
|
|
16
50
|
aiChatMessage,
|
|
17
51
|
isLoading,
|
|
18
52
|
currentChatId,
|
|
53
|
+
selectedChatId,
|
|
54
|
+
setSelectedChatId,
|
|
19
55
|
aiErrorMessage,
|
|
20
56
|
setAIChatMode,
|
|
21
57
|
clearStoredAIPrompt,
|
|
@@ -28,8 +64,12 @@ export const AIChat = () => {
|
|
|
28
64
|
resetMessageHistoryNavigation,
|
|
29
65
|
} = useContext(VZCodeContext);
|
|
30
66
|
|
|
31
|
-
// Get
|
|
32
|
-
|
|
67
|
+
// Get the active chat ID and chat data
|
|
68
|
+
// If selectedChatId is set, use it; otherwise, if no chat selected, don't default to currentChatId
|
|
69
|
+
const activeChatId = selectedChatId;
|
|
70
|
+
const currentChat = activeChatId
|
|
71
|
+
? content?.chats?.[activeChatId]
|
|
72
|
+
: null;
|
|
33
73
|
const rawMessages = currentChat?.messages || [];
|
|
34
74
|
const aiStatus = currentChat?.aiStatus;
|
|
35
75
|
const aiScratchpad = currentChat?.aiScratchpad;
|
|
@@ -44,8 +84,44 @@ export const AIChat = () => {
|
|
|
44
84
|
[rawMessages],
|
|
45
85
|
);
|
|
46
86
|
|
|
47
|
-
// Check if this is the first time opening the chat (no messages)
|
|
48
|
-
const isEmptyState =
|
|
87
|
+
// Check if this is the first time opening the chat (no messages) or no chat selected
|
|
88
|
+
const isEmptyState =
|
|
89
|
+
!selectedChatId || rawMessages.length === 0;
|
|
90
|
+
|
|
91
|
+
// Get all existing chats
|
|
92
|
+
const allChats = content?.chats || {};
|
|
93
|
+
const existingChats = Object.values(allChats).filter(
|
|
94
|
+
(chat) => chat.messages.length > 0,
|
|
95
|
+
);
|
|
96
|
+
const hasExistingChats = existingChats.length > 0;
|
|
97
|
+
|
|
98
|
+
// Generate title for a chat (first 50 characters of first user message)
|
|
99
|
+
const getChatTitle = (chat) => {
|
|
100
|
+
const firstUserMessage = chat.messages.find(
|
|
101
|
+
(msg) => msg.role === 'user',
|
|
102
|
+
);
|
|
103
|
+
if (!firstUserMessage) return 'New Chat';
|
|
104
|
+
return (
|
|
105
|
+
firstUserMessage.content.slice(0, 50) +
|
|
106
|
+
(firstUserMessage.content.length > 50 ? '...' : '')
|
|
107
|
+
);
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
// Wrapper for handleSendMessage to automatically select the chat when sending
|
|
111
|
+
const handleSendMessageWithSelection = useCallback(() => {
|
|
112
|
+
// If no chat is selected, select the currentChatId when sending a message
|
|
113
|
+
if (!selectedChatId) {
|
|
114
|
+
setSelectedChatId(currentChatId);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Call the original handleSendMessage without parameters
|
|
118
|
+
return handleSendMessage();
|
|
119
|
+
}, [
|
|
120
|
+
selectedChatId,
|
|
121
|
+
setSelectedChatId,
|
|
122
|
+
currentChatId,
|
|
123
|
+
handleSendMessage,
|
|
124
|
+
]);
|
|
49
125
|
|
|
50
126
|
// Check for stored AI prompt on component mount (post-fork restoration)
|
|
51
127
|
useEffect(() => {
|
|
@@ -108,6 +184,14 @@ export const AIChat = () => {
|
|
|
108
184
|
<div className="ai-chat-empty-text">
|
|
109
185
|
How can I help you?
|
|
110
186
|
</div>
|
|
187
|
+
{hasExistingChats && (
|
|
188
|
+
<ChatList
|
|
189
|
+
chats={existingChats}
|
|
190
|
+
selectedChatId={selectedChatId}
|
|
191
|
+
onSelectChat={setSelectedChatId}
|
|
192
|
+
getChatTitle={getChatTitle}
|
|
193
|
+
/>
|
|
194
|
+
)}
|
|
111
195
|
{showSuggestedRequests && (
|
|
112
196
|
<div className="ai-chat-empty-examples">
|
|
113
197
|
{aiChatMode === 'ask' ? (
|
|
@@ -223,7 +307,7 @@ export const AIChat = () => {
|
|
|
223
307
|
messages={messages}
|
|
224
308
|
aiStatus={aiStatus}
|
|
225
309
|
isLoading={isLoading}
|
|
226
|
-
chatId={currentChatId}
|
|
310
|
+
chatId={selectedChatId || currentChatId}
|
|
227
311
|
aiScratchpad={aiScratchpad}
|
|
228
312
|
/>
|
|
229
313
|
)}
|
|
@@ -252,7 +336,7 @@ export const AIChat = () => {
|
|
|
252
336
|
<ChatInput
|
|
253
337
|
aiChatMessage={aiChatMessage}
|
|
254
338
|
setAIChatMessage={setAIChatMessage}
|
|
255
|
-
onSendMessage={
|
|
339
|
+
onSendMessage={handleSendMessageWithSelection}
|
|
256
340
|
isLoading={isLoading}
|
|
257
341
|
focused={aiChatFocused}
|
|
258
342
|
aiChatMode={aiChatMode}
|
|
@@ -100,11 +100,30 @@
|
|
|
100
100
|
color: white;
|
|
101
101
|
border-color: var(--vh-color-primary-01);
|
|
102
102
|
}
|
|
103
|
+
|
|
104
|
+
&.selected {
|
|
105
|
+
background: var(--vh-color-primary-01);
|
|
106
|
+
color: white;
|
|
107
|
+
border-color: var(--vh-color-primary-01);
|
|
108
|
+
}
|
|
103
109
|
}
|
|
104
110
|
}
|
|
105
111
|
}
|
|
106
112
|
}
|
|
107
113
|
|
|
114
|
+
.ai-chat-list {
|
|
115
|
+
margin-top: 20px;
|
|
116
|
+
|
|
117
|
+
.ai-chat-list-header {
|
|
118
|
+
h4 {
|
|
119
|
+
font-size: 14px;
|
|
120
|
+
font-weight: 600;
|
|
121
|
+
color: var(--vh-color-neutral-04);
|
|
122
|
+
margin-bottom: 10px;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
108
127
|
.ai-chat-messages {
|
|
109
128
|
flex: 1;
|
|
110
129
|
overflow-y: auto;
|
|
@@ -278,7 +278,6 @@ export const addDiffToAIMessage = (
|
|
|
278
278
|
shareDBDoc: ShareDBDoc<VizContent>,
|
|
279
279
|
chatId: VizChatId,
|
|
280
280
|
diffData: any,
|
|
281
|
-
beforeFiles?: VizFiles, // Optional snapshot for undo functionality (legacy)
|
|
282
281
|
beforeCommitId?: string, // Commit ID before AI changes for VizHub integration
|
|
283
282
|
) => {
|
|
284
283
|
const chat = shareDBDoc.data.chats[chatId];
|
|
@@ -293,7 +292,6 @@ export const addDiffToAIMessage = (
|
|
|
293
292
|
const newMessage = {
|
|
294
293
|
...messages[lastAIMessageIndex],
|
|
295
294
|
diffData,
|
|
296
|
-
...(beforeFiles && { beforeFiles }), // Add beforeFiles only if provided (legacy)
|
|
297
295
|
...(beforeCommitId && { beforeCommitId }), // Add beforeCommitId for VizHub integration
|
|
298
296
|
};
|
|
299
297
|
// Use type assertion to extend the message with diffData
|
|
@@ -476,35 +474,3 @@ export const appendLineToFile = (
|
|
|
476
474
|
|
|
477
475
|
shareDBDoc.submitOp(diff(shareDBDoc.data, newDocState));
|
|
478
476
|
};
|
|
479
|
-
|
|
480
|
-
/**
|
|
481
|
-
* Undoes the last AI edit by restoring files and removing the AI message
|
|
482
|
-
*/
|
|
483
|
-
export const undoAIEdit = (
|
|
484
|
-
shareDBDoc: ShareDBDoc<VizContent>,
|
|
485
|
-
chatId: VizChatId,
|
|
486
|
-
messageId: string,
|
|
487
|
-
beforeFiles: VizFiles,
|
|
488
|
-
) => {
|
|
489
|
-
const chat = shareDBDoc.data.chats[chatId];
|
|
490
|
-
const messages = chat.messages.filter(
|
|
491
|
-
(msg) => msg.id !== messageId,
|
|
492
|
-
);
|
|
493
|
-
|
|
494
|
-
const op = diff(shareDBDoc.data, {
|
|
495
|
-
...shareDBDoc.data,
|
|
496
|
-
files: beforeFiles,
|
|
497
|
-
chats: {
|
|
498
|
-
...shareDBDoc.data.chats,
|
|
499
|
-
[chatId]: {
|
|
500
|
-
...chat,
|
|
501
|
-
messages,
|
|
502
|
-
updatedAt: dateToTimestamp(new Date()),
|
|
503
|
-
},
|
|
504
|
-
},
|
|
505
|
-
// Trigger a re-run
|
|
506
|
-
runId: generateRunId(),
|
|
507
|
-
});
|
|
508
|
-
|
|
509
|
-
shareDBDoc.submitOp(op);
|
|
510
|
-
};
|
|
@@ -163,7 +163,6 @@ const processAIRequestAsync = async ({
|
|
|
163
163
|
shareDBDoc,
|
|
164
164
|
chatId,
|
|
165
165
|
editResult.diffData,
|
|
166
|
-
(editResult as any).beforeFiles, // Pass the beforeFiles snapshot for undo (legacy)
|
|
167
166
|
beforeCommitId, // Pass the commit ID before AI changes (for VizHub integration)
|
|
168
167
|
);
|
|
169
168
|
}
|
|
@@ -77,9 +77,9 @@ export const createLLMFunction = ({
|
|
|
77
77
|
|
|
78
78
|
// Create OpenRouter client for reasoning token support
|
|
79
79
|
const openRouterClient = new OpenAI({
|
|
80
|
-
apiKey: process.env.
|
|
80
|
+
apiKey: process.env.VZCODE_EDIT_WITH_AI_API_KEY,
|
|
81
81
|
baseURL:
|
|
82
|
-
process.env.
|
|
82
|
+
process.env.VZCODE_EDIT_WITH_AI_BASE_URL ||
|
|
83
83
|
'https://openrouter.ai/api/v1',
|
|
84
84
|
defaultHeaders: {
|
|
85
85
|
'HTTP-Referer': 'https://vizhub.com',
|
|
@@ -256,7 +256,7 @@ export const createLLMFunction = ({
|
|
|
256
256
|
// Stream the response with reasoning tokens
|
|
257
257
|
const modelName =
|
|
258
258
|
model ||
|
|
259
|
-
process.env.
|
|
259
|
+
process.env.VZCODE_EDIT_WITH_AI_MODEL_NAME ||
|
|
260
260
|
'anthropic/claude-3.5-sonnet';
|
|
261
261
|
|
|
262
262
|
// Configure reasoning tokens based on enableReasoningTokens flag
|
package/src/server/index.ts
CHANGED
|
@@ -14,7 +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
|
-
|
|
17
|
+
|
|
18
18
|
import { isDirectory } from './isDirectory.js';
|
|
19
19
|
import { createToken } from './livekit.js';
|
|
20
20
|
import './setupEnv.js';
|
|
@@ -142,15 +142,6 @@ app.post(
|
|
|
142
142
|
}),
|
|
143
143
|
);
|
|
144
144
|
|
|
145
|
-
// Handle AI Chat Undo requests.
|
|
146
|
-
app.post(
|
|
147
|
-
'/ai-chat-undo',
|
|
148
|
-
bodyParser.json(),
|
|
149
|
-
handleAIChatUndo({
|
|
150
|
-
shareDBDoc,
|
|
151
|
-
}),
|
|
152
|
-
);
|
|
153
|
-
|
|
154
145
|
// Livekit Token Generator
|
|
155
146
|
app.get('/livekit-token', async (req, res) => {
|
|
156
147
|
const { room, username } = req.query;
|
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
import { undoAIEdit } from './chatOperations.js';
|
|
2
|
-
import { handleError } from './errorHandling.js';
|
|
3
|
-
import { ShareDBDoc } from '../../types.js';
|
|
4
|
-
import { VizContent } from '@vizhub/viz-types';
|
|
5
|
-
|
|
6
|
-
const DEBUG = false;
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Validates incoming request data for AI chat undo
|
|
10
|
-
*/
|
|
11
|
-
const validateUndoRequest = (req, res) => {
|
|
12
|
-
const { chatId, messageId } = req.body;
|
|
13
|
-
|
|
14
|
-
if (!chatId || typeof chatId !== 'string') {
|
|
15
|
-
res.status(400).json({
|
|
16
|
-
error:
|
|
17
|
-
'Invalid request: chatId is required and must be a string',
|
|
18
|
-
});
|
|
19
|
-
return false;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
if (!messageId || typeof messageId !== 'string') {
|
|
23
|
-
res.status(400).json({
|
|
24
|
-
error:
|
|
25
|
-
'Invalid request: messageId is required and must be a string',
|
|
26
|
-
});
|
|
27
|
-
return false;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
return true;
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
export const handleAIChatUndo =
|
|
34
|
-
({
|
|
35
|
-
shareDBDoc,
|
|
36
|
-
}: {
|
|
37
|
-
shareDBDoc: ShareDBDoc<VizContent>;
|
|
38
|
-
}) =>
|
|
39
|
-
async (req: any, res: any) => {
|
|
40
|
-
const { chatId, messageId } = req.body;
|
|
41
|
-
|
|
42
|
-
if (DEBUG) {
|
|
43
|
-
console.log(
|
|
44
|
-
'[handleAIChatUndo] chatId:',
|
|
45
|
-
chatId,
|
|
46
|
-
'messageId:',
|
|
47
|
-
messageId,
|
|
48
|
-
'shareDBDoc:',
|
|
49
|
-
shareDBDoc,
|
|
50
|
-
);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// Validate request
|
|
54
|
-
if (!validateUndoRequest(req, res)) {
|
|
55
|
-
return;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
try {
|
|
59
|
-
const chat = shareDBDoc.data.chats?.[chatId];
|
|
60
|
-
if (!chat) {
|
|
61
|
-
res.status(404).json({
|
|
62
|
-
error: 'Chat not found',
|
|
63
|
-
});
|
|
64
|
-
return;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
const message = chat.messages.find(
|
|
68
|
-
(msg) => msg.id === messageId,
|
|
69
|
-
);
|
|
70
|
-
|
|
71
|
-
if (
|
|
72
|
-
!message ||
|
|
73
|
-
message.role !== 'assistant' ||
|
|
74
|
-
!(message as any).beforeFiles
|
|
75
|
-
) {
|
|
76
|
-
res.status(400).json({
|
|
77
|
-
error: 'Invalid message for undo',
|
|
78
|
-
});
|
|
79
|
-
return;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// Perform the undo operation
|
|
83
|
-
undoAIEdit(
|
|
84
|
-
shareDBDoc,
|
|
85
|
-
chatId,
|
|
86
|
-
messageId,
|
|
87
|
-
(message as any).beforeFiles,
|
|
88
|
-
);
|
|
89
|
-
|
|
90
|
-
res.status(200).json({ success: true });
|
|
91
|
-
} catch (error) {
|
|
92
|
-
if (DEBUG) {
|
|
93
|
-
console.error('[handleAIChatUndo] Error:', error);
|
|
94
|
-
}
|
|
95
|
-
handleError(shareDBDoc, chatId, error, res);
|
|
96
|
-
}
|
|
97
|
-
};
|