vzcode 1.57.0 → 1.59.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-C_tGhWXG.js → buildWorker-Dsx6rOdK.js} +1 -1
- package/dist/assets/{index-DqIc-0Gp.js → index-BJI4JLx9.js} +120 -120
- package/dist/assets/{index-CE_xP06t.css → index-afP-wJTZ.css} +1 -1
- package/dist/assets/{worker-BrwN8AXx.js → worker-BSzbM0Uo.js} +180 -180
- package/dist/assets/{worker-CekYspLa.js → worker-y5jhqCTR.js} +1 -1
- package/dist/index.html +2 -2
- package/package.json +7 -7
- package/src/client/CodeEditor/getOrCreateEditor.tsx +7 -7
- package/src/client/CodeEditor/index.tsx +2 -0
- package/src/client/VZCodeContext.tsx +155 -0
- package/src/client/VZSidebar/AIChat/Message.tsx +22 -4
- package/src/client/VZSidebar/AIChat/MessageList.tsx +0 -2
- package/src/client/VZSidebar/AIChat/ThinkingScratchpad.tsx +121 -2
- package/src/client/VZSidebar/AIChat/index.tsx +12 -138
- package/src/client/VZSidebar/AIChat/styles.scss +16 -0
- package/src/server/aiChatHandler/chatOperations.ts +4 -2
- package/src/server/aiChatHandler/index.ts +12 -1
- package/src/server/aiChatHandler/llmStreaming.ts +26 -10
|
@@ -1,42 +1,28 @@
|
|
|
1
|
-
import {
|
|
2
|
-
useContext,
|
|
3
|
-
useState,
|
|
4
|
-
useCallback,
|
|
5
|
-
useMemo,
|
|
6
|
-
useEffect,
|
|
7
|
-
} from 'react';
|
|
1
|
+
import { useContext, useMemo, useEffect } from 'react';
|
|
8
2
|
import { VZCodeContext } from '../../VZCodeContext';
|
|
9
|
-
import { v4 as uuidv4 } from 'uuid';
|
|
10
3
|
import { MessageList } from './MessageList';
|
|
11
4
|
import { ChatInput } from './ChatInput';
|
|
12
5
|
import './styles.scss';
|
|
13
6
|
|
|
14
|
-
const defaultAIChatEndpoint = '/api/ai-chat/';
|
|
15
|
-
|
|
16
7
|
const DEBUG = false;
|
|
17
8
|
|
|
18
|
-
const showSuggestedRequests =
|
|
9
|
+
const showSuggestedRequests = true;
|
|
19
10
|
|
|
20
11
|
export const AIChat = () => {
|
|
21
|
-
const { aiChatMessage, setAIChatMessage } =
|
|
22
|
-
useContext(VZCodeContext);
|
|
23
|
-
|
|
24
|
-
const [isLoading, setIsLoading] = useState(false);
|
|
25
|
-
const [currentChatId] = useState(() => uuidv4());
|
|
26
|
-
const [errorMessage, setErrorMessage] = useState<
|
|
27
|
-
string | null
|
|
28
|
-
>(null);
|
|
29
|
-
|
|
30
12
|
const {
|
|
31
13
|
aiChatFocused,
|
|
32
14
|
content,
|
|
33
|
-
aiChatEndpoint = defaultAIChatEndpoint,
|
|
34
|
-
aiChatOptions = {},
|
|
35
15
|
aiChatMode,
|
|
16
|
+
aiChatMessage,
|
|
17
|
+
isLoading,
|
|
18
|
+
currentChatId,
|
|
19
|
+
aiErrorMessage,
|
|
36
20
|
setAIChatMode,
|
|
37
|
-
autoForkAndRetryAI,
|
|
38
21
|
clearStoredAIPrompt,
|
|
39
22
|
getStoredAIPrompt,
|
|
23
|
+
setAIChatMessage,
|
|
24
|
+
handleSendMessage,
|
|
25
|
+
setAIErrorMessage,
|
|
40
26
|
} = useContext(VZCodeContext);
|
|
41
27
|
|
|
42
28
|
// Get current chat data from content
|
|
@@ -58,118 +44,6 @@ export const AIChat = () => {
|
|
|
58
44
|
// Check if this is the first time opening the chat (no messages)
|
|
59
45
|
const isEmptyState = rawMessages.length === 0;
|
|
60
46
|
|
|
61
|
-
const handleSendMessage = useCallback(
|
|
62
|
-
async (messageToSend?: string) => {
|
|
63
|
-
DEBUG &&
|
|
64
|
-
console.log(
|
|
65
|
-
'AIChat: handleSendMessage called with:',
|
|
66
|
-
messageToSend,
|
|
67
|
-
'aiChatMessage:',
|
|
68
|
-
aiChatMessage,
|
|
69
|
-
);
|
|
70
|
-
const messageContent = messageToSend || aiChatMessage;
|
|
71
|
-
|
|
72
|
-
if (
|
|
73
|
-
!messageContent ||
|
|
74
|
-
typeof messageContent !== 'string' ||
|
|
75
|
-
!messageContent.trim() ||
|
|
76
|
-
isLoading
|
|
77
|
-
) {
|
|
78
|
-
return;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
const currentPrompt = aiChatMessage.trim();
|
|
82
|
-
setAIChatMessage('');
|
|
83
|
-
setIsLoading(true);
|
|
84
|
-
setErrorMessage(null); // Clear any previous errors
|
|
85
|
-
|
|
86
|
-
// Call backend endpoint for AI response
|
|
87
|
-
// The server will handle all ShareDB operations including adding the user message
|
|
88
|
-
try {
|
|
89
|
-
const response = await fetch(aiChatEndpoint, {
|
|
90
|
-
method: 'POST',
|
|
91
|
-
headers: {
|
|
92
|
-
'Content-Type': 'application/json',
|
|
93
|
-
},
|
|
94
|
-
body: JSON.stringify({
|
|
95
|
-
...aiChatOptions,
|
|
96
|
-
vizId: aiChatOptions.vizId,
|
|
97
|
-
content: messageContent.trim(),
|
|
98
|
-
chatId: currentChatId,
|
|
99
|
-
mode: aiChatMode,
|
|
100
|
-
}),
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
if (!response.ok) {
|
|
104
|
-
throw new Error(
|
|
105
|
-
`HTTP error! status: ${response.status}`,
|
|
106
|
-
);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
// Parse the response to check for errors
|
|
110
|
-
const responseData = await response.json();
|
|
111
|
-
|
|
112
|
-
// Check if the response contains a VizHub error
|
|
113
|
-
if (
|
|
114
|
-
responseData.outcome === 'failure' &&
|
|
115
|
-
responseData.error
|
|
116
|
-
) {
|
|
117
|
-
const errorMessage = responseData.error.message;
|
|
118
|
-
|
|
119
|
-
// Check if this is the specific permission error that should trigger auto-fork
|
|
120
|
-
if (
|
|
121
|
-
errorMessage ===
|
|
122
|
-
'You do not have permission to use AI chat on this visualization. Only users with edit access can use this feature. Fork the viz to edit it.'
|
|
123
|
-
) {
|
|
124
|
-
// Trigger auto-fork instead of showing error
|
|
125
|
-
try {
|
|
126
|
-
await autoForkAndRetryAI?.(
|
|
127
|
-
currentPrompt,
|
|
128
|
-
aiChatMode,
|
|
129
|
-
);
|
|
130
|
-
// If we reach here, the fork was successful and redirect should happen
|
|
131
|
-
return;
|
|
132
|
-
} catch (forkError) {
|
|
133
|
-
console.error('Auto-fork failed:', forkError);
|
|
134
|
-
setErrorMessage(
|
|
135
|
-
'Failed to fork visualization. Please try forking manually.',
|
|
136
|
-
);
|
|
137
|
-
return;
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// For other errors, show the error message
|
|
142
|
-
setErrorMessage(errorMessage);
|
|
143
|
-
return;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
// The backend handles all ShareDB operations for successful responses
|
|
147
|
-
} catch (error) {
|
|
148
|
-
console.error('Error getting AI response:', error);
|
|
149
|
-
// Fail silently in case of timeout,
|
|
150
|
-
// which happens frequently with the longer running LLM calls.
|
|
151
|
-
// TODO refactor this so that the network request to the server
|
|
152
|
-
// always returns immediately, and we track the `isLoading` state
|
|
153
|
-
// within the ShareDB document itself.
|
|
154
|
-
//
|
|
155
|
-
// setErrorMessage(
|
|
156
|
-
// 'Failed to send message. Please try again.',
|
|
157
|
-
// );
|
|
158
|
-
} finally {
|
|
159
|
-
setIsLoading(false);
|
|
160
|
-
}
|
|
161
|
-
},
|
|
162
|
-
[
|
|
163
|
-
aiChatMessage,
|
|
164
|
-
isLoading,
|
|
165
|
-
aiChatEndpoint,
|
|
166
|
-
aiChatOptions,
|
|
167
|
-
currentChatId,
|
|
168
|
-
aiChatMode,
|
|
169
|
-
autoForkAndRetryAI,
|
|
170
|
-
],
|
|
171
|
-
);
|
|
172
|
-
|
|
173
47
|
// Check for stored AI prompt on component mount (post-fork restoration)
|
|
174
48
|
useEffect(() => {
|
|
175
49
|
DEBUG &&
|
|
@@ -350,18 +224,18 @@ export const AIChat = () => {
|
|
|
350
224
|
aiScratchpad={aiScratchpad}
|
|
351
225
|
/>
|
|
352
226
|
)}
|
|
353
|
-
{
|
|
227
|
+
{aiErrorMessage && (
|
|
354
228
|
<div className="ai-chat-error">
|
|
355
229
|
<div className="ai-chat-error-content">
|
|
356
230
|
<span className="ai-chat-error-icon">
|
|
357
231
|
⚠️
|
|
358
232
|
</span>
|
|
359
233
|
<span className="ai-chat-error-message">
|
|
360
|
-
{
|
|
234
|
+
{aiErrorMessage}
|
|
361
235
|
</span>
|
|
362
236
|
<button
|
|
363
237
|
className="ai-chat-error-dismiss"
|
|
364
|
-
onClick={() =>
|
|
238
|
+
onClick={() => setAIErrorMessage(null)}
|
|
365
239
|
aria-label="Dismiss error"
|
|
366
240
|
>
|
|
367
241
|
×
|
|
@@ -182,6 +182,7 @@
|
|
|
182
182
|
.undo-button-container {
|
|
183
183
|
display: flex;
|
|
184
184
|
justify-content: flex-end;
|
|
185
|
+
gap: 8px;
|
|
185
186
|
margin-top: 16px;
|
|
186
187
|
}
|
|
187
188
|
|
|
@@ -560,6 +561,21 @@
|
|
|
560
561
|
max-height: 200px;
|
|
561
562
|
overflow-y: auto;
|
|
562
563
|
background: var(--vh-color-neutral-02);
|
|
564
|
+
scroll-behavior: smooth;
|
|
565
|
+
|
|
566
|
+
&::-webkit-scrollbar {
|
|
567
|
+
width: 6px;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
&::-webkit-scrollbar-track {
|
|
571
|
+
background: var(--vh-color-neutral-02);
|
|
572
|
+
border-radius: 3px;
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
&::-webkit-scrollbar-thumb {
|
|
576
|
+
background: var(--vh-color-neutral-04);
|
|
577
|
+
border-radius: 3px;
|
|
578
|
+
}
|
|
563
579
|
|
|
564
580
|
// Style markdown content in thinking scratchpad
|
|
565
581
|
p {
|
|
@@ -256,7 +256,8 @@ export const addDiffToAIMessage = (
|
|
|
256
256
|
shareDBDoc: ShareDBDoc<VizContent>,
|
|
257
257
|
chatId: VizChatId,
|
|
258
258
|
diffData: any,
|
|
259
|
-
beforeFiles?: VizFiles, // Optional snapshot for undo functionality
|
|
259
|
+
beforeFiles?: VizFiles, // Optional snapshot for undo functionality (legacy)
|
|
260
|
+
beforeCommitId?: string, // Commit ID before AI changes for VizHub integration
|
|
260
261
|
) => {
|
|
261
262
|
const chat = shareDBDoc.data.chats[chatId];
|
|
262
263
|
const messages = [...chat.messages];
|
|
@@ -270,7 +271,8 @@ export const addDiffToAIMessage = (
|
|
|
270
271
|
const newMessage = {
|
|
271
272
|
...messages[lastAIMessageIndex],
|
|
272
273
|
diffData,
|
|
273
|
-
...(beforeFiles && { beforeFiles }), // Add beforeFiles only if provided
|
|
274
|
+
...(beforeFiles && { beforeFiles }), // Add beforeFiles only if provided (legacy)
|
|
275
|
+
...(beforeCommitId && { beforeCommitId }), // Add beforeCommitId for VizHub integration
|
|
274
276
|
};
|
|
275
277
|
// Use type assertion to extend the message with diffData
|
|
276
278
|
(messages[lastAIMessageIndex] as any) = newMessage;
|
|
@@ -23,10 +23,14 @@ export const handleAIChatMessage =
|
|
|
23
23
|
shareDBDoc,
|
|
24
24
|
createAIEditLocalPresence,
|
|
25
25
|
onCreditDeduction,
|
|
26
|
+
getCurrentCommitId,
|
|
27
|
+
model,
|
|
26
28
|
}: {
|
|
27
29
|
shareDBDoc: ShareDBDoc<VizContent>;
|
|
28
30
|
createAIEditLocalPresence: () => any;
|
|
29
31
|
onCreditDeduction?: any;
|
|
32
|
+
getCurrentCommitId?: () => string | null;
|
|
33
|
+
model?: string;
|
|
30
34
|
}) =>
|
|
31
35
|
async (req: any, res: any) => {
|
|
32
36
|
const { content, chatId, mode = 'edit' } = req.body;
|
|
@@ -52,6 +56,11 @@ export const handleAIChatMessage =
|
|
|
52
56
|
ensureChatsExist(shareDBDoc);
|
|
53
57
|
ensureChatExists(shareDBDoc, chatId);
|
|
54
58
|
|
|
59
|
+
// Capture the current commit ID before making changes (for VizHub integration)
|
|
60
|
+
const beforeCommitId = getCurrentCommitId
|
|
61
|
+
? getCurrentCommitId()
|
|
62
|
+
: null;
|
|
63
|
+
|
|
55
64
|
// Add user message to chat
|
|
56
65
|
addUserMessage(shareDBDoc, chatId, content);
|
|
57
66
|
|
|
@@ -60,6 +69,7 @@ export const handleAIChatMessage =
|
|
|
60
69
|
shareDBDoc,
|
|
61
70
|
createAIEditLocalPresence,
|
|
62
71
|
chatId,
|
|
72
|
+
model,
|
|
63
73
|
});
|
|
64
74
|
|
|
65
75
|
// Create server-side runCode function using shareDBDoc
|
|
@@ -92,7 +102,8 @@ export const handleAIChatMessage =
|
|
|
92
102
|
shareDBDoc,
|
|
93
103
|
chatId,
|
|
94
104
|
editResult.diffData,
|
|
95
|
-
(editResult as any).beforeFiles, // Pass the beforeFiles snapshot for undo (
|
|
105
|
+
(editResult as any).beforeFiles, // Pass the beforeFiles snapshot for undo (legacy)
|
|
106
|
+
beforeCommitId, // Pass the commit ID before AI changes (for VizHub integration)
|
|
96
107
|
);
|
|
97
108
|
}
|
|
98
109
|
|
|
@@ -45,10 +45,17 @@ export const createLLMFunction = ({
|
|
|
45
45
|
shareDBDoc,
|
|
46
46
|
createAIEditLocalPresence,
|
|
47
47
|
chatId,
|
|
48
|
+
// Feature flag to enable/disable reasoning tokens.
|
|
49
|
+
// When false, reasoning tokens are not requested from the API
|
|
50
|
+
// and reasoning content is not processed in the streaming response.
|
|
51
|
+
enableReasoningTokens = false,
|
|
52
|
+
model,
|
|
48
53
|
}: {
|
|
49
54
|
shareDBDoc: ShareDBDoc<VizContent>;
|
|
50
55
|
createAIEditLocalPresence: () => any;
|
|
51
56
|
chatId: VizChatId;
|
|
57
|
+
enableReasoningTokens?: boolean;
|
|
58
|
+
model?: string;
|
|
52
59
|
}) => {
|
|
53
60
|
return async (fullPrompt: string) => {
|
|
54
61
|
const localPresence = enableStreamingEditing
|
|
@@ -195,23 +202,32 @@ export const createLLMFunction = ({
|
|
|
195
202
|
|
|
196
203
|
// Stream the response with reasoning tokens
|
|
197
204
|
const modelName =
|
|
205
|
+
model ||
|
|
198
206
|
process.env.VIZHUB_EDIT_WITH_AI_MODEL_NAME ||
|
|
199
207
|
'anthropic/claude-3.5-sonnet';
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
208
|
+
|
|
209
|
+
// Configure reasoning tokens based on enableReasoningTokens flag
|
|
210
|
+
const requestConfig: any = {
|
|
203
211
|
model: modelName,
|
|
204
212
|
messages: [{ role: 'user', content: fullPrompt }],
|
|
205
|
-
reasoning: {
|
|
206
|
-
effort: 'low',
|
|
207
|
-
exclude: false,
|
|
208
|
-
},
|
|
209
213
|
provider: {
|
|
210
214
|
sort: 'price',
|
|
211
215
|
},
|
|
212
216
|
usage: { include: true },
|
|
213
217
|
stream: true,
|
|
214
|
-
}
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
// Only include reasoning configuration if reasoning tokens are enabled
|
|
221
|
+
if (enableReasoningTokens) {
|
|
222
|
+
requestConfig.reasoning = {
|
|
223
|
+
effort: 'low',
|
|
224
|
+
exclude: false,
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const stream = await (
|
|
229
|
+
openRouterClient.chat.completions.create as any
|
|
230
|
+
)(requestConfig);
|
|
215
231
|
|
|
216
232
|
let reasoningStarted = false;
|
|
217
233
|
let contentStarted = false;
|
|
@@ -219,8 +235,8 @@ export const createLLMFunction = ({
|
|
|
219
235
|
for await (const chunk of stream) {
|
|
220
236
|
const delta = chunk.choices[0]?.delta as any; // Type assertion for OpenRouter-specific reasoning fields
|
|
221
237
|
|
|
222
|
-
if (delta?.reasoning) {
|
|
223
|
-
// Handle reasoning tokens (thinking)
|
|
238
|
+
if (delta?.reasoning && enableReasoningTokens) {
|
|
239
|
+
// Handle reasoning tokens (thinking) - only if enabled
|
|
224
240
|
if (!reasoningStarted) {
|
|
225
241
|
reasoningStarted = true;
|
|
226
242
|
updateAIStatus(shareDBDoc, chatId, 'Thinking...');
|