vzcode 1.51.0 → 1.53.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.
@@ -13,6 +13,8 @@ import './styles.scss';
13
13
 
14
14
  const defaultAIChatEndpoint = '/api/ai-chat/';
15
15
 
16
+ const DEBUG = false;
17
+
16
18
  export const AIChat = () => {
17
19
  const { aiChatMessage, setAIChatMessage } =
18
20
  useContext(VZCodeContext);
@@ -54,110 +56,149 @@ export const AIChat = () => {
54
56
  // Check if this is the first time opening the chat (no messages)
55
57
  const isEmptyState = rawMessages.length === 0;
56
58
 
57
- const handleSendMessage = useCallback(async () => {
58
- if (!aiChatMessage.trim() || isLoading) return;
59
-
60
- const currentPrompt = aiChatMessage.trim();
61
- setAIChatMessage('');
62
- setIsLoading(true);
63
- setErrorMessage(null); // Clear any previous errors
59
+ const handleSendMessage = useCallback(
60
+ async (messageToSend?: string) => {
61
+ DEBUG &&
62
+ console.log(
63
+ 'AIChat: handleSendMessage called with:',
64
+ messageToSend,
65
+ 'aiChatMessage:',
66
+ aiChatMessage,
67
+ );
68
+ const messageContent = messageToSend || aiChatMessage;
69
+ if (
70
+ !messageContent ||
71
+ typeof messageContent !== 'string' ||
72
+ !messageContent.trim() ||
73
+ isLoading
74
+ )
75
+ return;
64
76
 
65
- // Call backend endpoint for AI response
66
- // The server will handle all ShareDB operations including adding the user message
67
- try {
68
- const response = await fetch(aiChatEndpoint, {
69
- method: 'POST',
70
- headers: {
71
- 'Content-Type': 'application/json',
72
- },
73
- body: JSON.stringify({
74
- ...aiChatOptions,
75
- vizId: aiChatOptions.vizId,
76
- content: currentPrompt,
77
- chatId: currentChatId,
78
- mode: aiChatMode,
79
- }),
80
- });
77
+ const currentPrompt = aiChatMessage.trim();
78
+ setAIChatMessage('');
79
+ setIsLoading(true);
80
+ setErrorMessage(null); // Clear any previous errors
81
81
 
82
- if (!response.ok) {
83
- throw new Error(
84
- `HTTP error! status: ${response.status}`,
85
- );
86
- }
82
+ // Call backend endpoint for AI response
83
+ // The server will handle all ShareDB operations including adding the user message
84
+ try {
85
+ const response = await fetch(aiChatEndpoint, {
86
+ method: 'POST',
87
+ headers: {
88
+ 'Content-Type': 'application/json',
89
+ },
90
+ body: JSON.stringify({
91
+ ...aiChatOptions,
92
+ vizId: aiChatOptions.vizId,
93
+ content: messageContent.trim(),
94
+ chatId: currentChatId,
95
+ mode: aiChatMode,
96
+ }),
97
+ });
87
98
 
88
- // Parse the response to check for errors
89
- const responseData = await response.json();
99
+ if (!response.ok) {
100
+ throw new Error(
101
+ `HTTP error! status: ${response.status}`,
102
+ );
103
+ }
90
104
 
91
- // Check if the response contains a VizHub error
92
- if (
93
- responseData.outcome === 'failure' &&
94
- responseData.error
95
- ) {
96
- const errorMessage = responseData.error.message;
105
+ // Parse the response to check for errors
106
+ const responseData = await response.json();
97
107
 
98
- // Check if this is the specific permission error that should trigger auto-fork
108
+ // Check if the response contains a VizHub error
99
109
  if (
100
- errorMessage ===
101
- '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.'
110
+ responseData.outcome === 'failure' &&
111
+ responseData.error
102
112
  ) {
103
- // Trigger auto-fork instead of showing error
104
- try {
105
- await autoForkAndRetryAI?.(
106
- currentPrompt,
107
- aiChatMode,
108
- );
109
- // If we reach here, the fork was successful and redirect should happen
110
- return;
111
- } catch (forkError) {
112
- console.error('Auto-fork failed:', forkError);
113
- setErrorMessage(
114
- 'Failed to fork visualization. Please try forking manually.',
115
- );
116
- return;
113
+ const errorMessage = responseData.error.message;
114
+
115
+ // Check if this is the specific permission error that should trigger auto-fork
116
+ if (
117
+ errorMessage ===
118
+ '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.'
119
+ ) {
120
+ // Trigger auto-fork instead of showing error
121
+ try {
122
+ await autoForkAndRetryAI?.(
123
+ currentPrompt,
124
+ aiChatMode,
125
+ );
126
+ // If we reach here, the fork was successful and redirect should happen
127
+ return;
128
+ } catch (forkError) {
129
+ console.error('Auto-fork failed:', forkError);
130
+ setErrorMessage(
131
+ 'Failed to fork visualization. Please try forking manually.',
132
+ );
133
+ return;
134
+ }
117
135
  }
136
+
137
+ // For other errors, show the error message
138
+ setErrorMessage(errorMessage);
139
+ return;
118
140
  }
119
141
 
120
- // For other errors, show the error message
121
- setErrorMessage(errorMessage);
122
- return;
142
+ // The backend handles all ShareDB operations for successful responses
143
+ } catch (error) {
144
+ console.error('Error getting AI response:', error);
145
+ setErrorMessage(
146
+ 'Failed to send message. Please try again.',
147
+ );
148
+ } finally {
149
+ setIsLoading(false);
123
150
  }
124
-
125
- // The backend handles all ShareDB operations for successful responses
126
- } catch (error) {
127
- console.error('Error getting AI response:', error);
128
- setErrorMessage(
129
- 'Failed to send message. Please try again.',
130
- );
131
- } finally {
132
- setIsLoading(false);
133
- }
134
- }, [
135
- aiChatMessage,
136
- isLoading,
137
- aiChatEndpoint,
138
- aiChatOptions,
139
- currentChatId,
140
- aiChatMode,
141
- autoForkAndRetryAI,
142
- ]);
151
+ },
152
+ [
153
+ aiChatMessage,
154
+ isLoading,
155
+ aiChatEndpoint,
156
+ aiChatOptions,
157
+ currentChatId,
158
+ aiChatMode,
159
+ autoForkAndRetryAI,
160
+ ],
161
+ );
143
162
 
144
163
  // Check for stored AI prompt on component mount (post-fork restoration)
145
164
  useEffect(() => {
146
- const storedPrompt = getStoredAIPrompt();
147
- if (storedPrompt) {
148
- // Restore the prompt and mode
149
- setAIChatMessage(storedPrompt.prompt);
150
- setAIChatMode(
151
- storedPrompt.modelName === 'ask' ? 'ask' : 'edit',
152
- );
165
+ DEBUG &&
166
+ console.log('AIChat: Checking for stored AI prompt');
167
+ if (getStoredAIPrompt) {
168
+ const storedPrompt = getStoredAIPrompt();
169
+ DEBUG &&
170
+ console.log(
171
+ 'AIChat: Stored prompt result:',
172
+ storedPrompt,
173
+ );
174
+ if (storedPrompt) {
175
+ // Restore the prompt and mode
176
+ DEBUG &&
177
+ console.log('AIChat: Restoring prompt and mode');
178
+ setAIChatMessage(storedPrompt.prompt);
179
+ setAIChatMode(
180
+ storedPrompt.modelName === 'ask' ? 'ask' : 'edit',
181
+ );
153
182
 
154
- // Clear the stored prompt
155
- clearStoredAIPrompt();
183
+ // Clear the stored prompt
184
+ DEBUG &&
185
+ console.log('AIChat: Clearing stored prompt');
186
+ clearStoredAIPrompt();
156
187
 
157
- // Auto-submit the restored prompt after a short delay
158
- setTimeout(() => {
159
- handleSendMessage();
160
- }, 100);
188
+ // Auto-submit the restored prompt after a short delay
189
+ DEBUG &&
190
+ console.log('AIChat: Scheduling auto-submit');
191
+ setTimeout(() => {
192
+ DEBUG &&
193
+ console.log(
194
+ 'AIChat: Auto-submitting restored prompt',
195
+ );
196
+ handleSendMessage(storedPrompt.prompt);
197
+ }, 100);
198
+ } else {
199
+ DEBUG &&
200
+ console.log('AIChat: No stored prompt found');
201
+ }
161
202
  }
162
203
  }, [
163
204
  getStoredAIPrompt,
@@ -181,7 +222,7 @@ export const AIChat = () => {
181
222
  <div className="ai-chat-empty">
182
223
  <div className="ai-chat-empty-icon">✨</div>
183
224
  <h3 className="ai-chat-empty-title">
184
- Hi, I'm VizBot!
225
+ Hi! I'm here to help you edit with AI.
185
226
  </h3>
186
227
  <div className="ai-chat-empty-text">
187
228
  How can I help you?
@@ -194,7 +235,7 @@ export const AIChat = () => {
194
235
  <button
195
236
  className="ai-chat-suggested-prompt"
196
237
  onClick={() =>
197
- setAIChatMessage(
238
+ handleSendMessage(
198
239
  'Explain how this works',
199
240
  )
200
241
  }
@@ -204,7 +245,7 @@ export const AIChat = () => {
204
245
  <button
205
246
  className="ai-chat-suggested-prompt"
206
247
  onClick={() =>
207
- setAIChatMessage(
248
+ handleSendMessage(
208
249
  'How could I change it so that the circles are bigger?',
209
250
  )
210
251
  }
@@ -215,7 +256,7 @@ export const AIChat = () => {
215
256
  <button
216
257
  className="ai-chat-suggested-prompt"
217
258
  onClick={() =>
218
- setAIChatMessage(
259
+ handleSendMessage(
219
260
  'What does this function do?',
220
261
  )
221
262
  }
@@ -225,7 +266,7 @@ export const AIChat = () => {
225
266
  <button
226
267
  className="ai-chat-suggested-prompt"
227
268
  onClick={() =>
228
- setAIChatMessage(
269
+ handleSendMessage(
229
270
  'How can I make this more accessible?',
230
271
  )
231
272
  }
@@ -241,7 +282,7 @@ export const AIChat = () => {
241
282
  <button
242
283
  className="ai-chat-suggested-prompt"
243
284
  onClick={() =>
244
- setAIChatMessage(
285
+ handleSendMessage(
245
286
  'Change the circles to squares',
246
287
  )
247
288
  }
@@ -251,7 +292,7 @@ export const AIChat = () => {
251
292
  <button
252
293
  className="ai-chat-suggested-prompt"
253
294
  onClick={() =>
254
- setAIChatMessage(
295
+ handleSendMessage(
255
296
  'Add a button that toggles the animation',
256
297
  )
257
298
  }
@@ -262,7 +303,7 @@ export const AIChat = () => {
262
303
  <button
263
304
  className="ai-chat-suggested-prompt"
264
305
  onClick={() =>
265
- setAIChatMessage(
306
+ handleSendMessage(
266
307
  'Fix the CSS so the layout is responsive',
267
308
  )
268
309
  }
@@ -273,7 +314,7 @@ export const AIChat = () => {
273
314
  <button
274
315
  className="ai-chat-suggested-prompt"
275
316
  onClick={() =>
276
- setAIChatMessage(
317
+ handleSendMessage(
277
318
  'Refactor this function to use async/await',
278
319
  )
279
320
  }
@@ -150,6 +150,33 @@
150
150
  line-height: 1.4;
151
151
  word-wrap: break-word;
152
152
 
153
+ // Undo button styles (moved from DiffView)
154
+ .undo-button-container {
155
+ display: flex;
156
+ justify-content: flex-end;
157
+ margin-top: 16px;
158
+ }
159
+
160
+ .undo-button {
161
+ background-color: #dc3545;
162
+ color: white;
163
+ border: none;
164
+ border-radius: 4px;
165
+ padding: 4px 8px;
166
+ font-size: 12px;
167
+ cursor: pointer;
168
+ transition: background-color 0.2s;
169
+
170
+ &:hover:not(:disabled) {
171
+ background-color: #c82333;
172
+ }
173
+
174
+ &:disabled {
175
+ background-color: #6c757d;
176
+ cursor: not-allowed;
177
+ }
178
+ }
179
+
153
180
  // Markdown styling for dark backgrounds
154
181
  pre {
155
182
  background-color: rgba(0, 0, 0, 0.3);
@@ -110,7 +110,7 @@ export const VZSidebar = ({
110
110
  ),
111
111
  aiChatToolTipText = (
112
112
  <div>
113
- <strong>VizBot - AI Code Editor</strong>
113
+ <strong>Edit with AI</strong>
114
114
  </div>
115
115
  ),
116
116
  }: {
@@ -4,3 +4,5 @@ export const enableLiveKit =
4
4
  export const enableAIChat = true;
5
5
 
6
6
  export const enableDiffView = true;
7
+
8
+ export const enableAskMode = false;
@@ -21,11 +21,11 @@ const DEBUG = false;
21
21
  export const handleAIChatMessage =
22
22
  ({
23
23
  shareDBDoc,
24
- createVizBotLocalPresence,
24
+ createAIEditLocalPresence,
25
25
  onCreditDeduction,
26
26
  }: {
27
27
  shareDBDoc: ShareDBDoc<VizContent>;
28
- createVizBotLocalPresence: () => any;
28
+ createAIEditLocalPresence: () => any;
29
29
  onCreditDeduction?: any;
30
30
  }) =>
31
31
  async (req: any, res: any) => {
@@ -58,7 +58,7 @@ export const handleAIChatMessage =
58
58
  // Create LLM function for streaming
59
59
  const llmFunction = createLLMFunction({
60
60
  shareDBDoc,
61
- createVizBotLocalPresence,
61
+ createAIEditLocalPresence,
62
62
  chatId,
63
63
  });
64
64
 
@@ -2,7 +2,6 @@ import {
2
2
  parseMarkdownFiles,
3
3
  StreamingMarkdownParser,
4
4
  } from 'llm-code-format';
5
- import { ChatOpenAI } from '@langchain/openai';
6
5
  import OpenAI from 'openai';
7
6
  import fs from 'fs';
8
7
  import { generateRunId } from '@vizhub/viz-utils';
@@ -44,15 +43,17 @@ const enableStreamingEditing = false;
44
43
  */
45
44
  export const createLLMFunction = ({
46
45
  shareDBDoc,
47
- createVizBotLocalPresence,
46
+ createAIEditLocalPresence,
48
47
  chatId,
49
48
  }: {
50
49
  shareDBDoc: ShareDBDoc<VizContent>;
51
- createVizBotLocalPresence: () => any;
50
+ createAIEditLocalPresence: () => any;
52
51
  chatId: VizChatId;
53
52
  }) => {
54
53
  return async (fullPrompt: string) => {
55
- const localPresence = createVizBotLocalPresence();
54
+ const localPresence = enableStreamingEditing
55
+ ? createAIEditLocalPresence()
56
+ : null;
56
57
 
57
58
  // Create OpenRouter client for reasoning token support
58
59
  const openRouterClient = new OpenAI({
@@ -133,13 +134,13 @@ export const createLLMFunction = ({
133
134
  line,
134
135
  );
135
136
 
136
- // Update VizBot presence to show cursor at the end of the file
137
+ // Update AI presence to show cursor at the end of the file
137
138
  const currentFile =
138
139
  shareDBDoc.data.files[currentEditingFileId];
139
140
  if (currentFile && currentFile.text) {
140
141
  const textLength = currentFile.text.length;
141
142
  const filePresence = {
142
- username: 'VizBot',
143
+ username: 'AI Editor',
143
144
  start: [
144
145
  'files',
145
146
  currentEditingFileId,
@@ -157,7 +158,7 @@ export const createLLMFunction = ({
157
158
  localPresence.submit(filePresence, (error) => {
158
159
  if (error) {
159
160
  console.warn(
160
- 'VizBot line presence submission error:',
161
+ 'AI Editor line presence submission error:',
161
162
  error,
162
163
  );
163
164
  }
@@ -196,11 +197,13 @@ export const createLLMFunction = ({
196
197
  )({
197
198
  model: modelName,
198
199
  messages: [{ role: 'user', content: fullPrompt }],
199
- max_tokens: 8192,
200
200
  reasoning: {
201
201
  effort: 'medium',
202
202
  exclude: false,
203
203
  },
204
+ provider: {
205
+ sort: 'throughput',
206
+ },
204
207
  usage: { include: true },
205
208
  stream: true,
206
209
  });
@@ -275,16 +278,16 @@ export const createLLMFunction = ({
275
278
  // Finalize the AI message by clearing temporary fields
276
279
  finalizeAIMessage(shareDBDoc, chatId);
277
280
 
278
- // Clear VizBot presence when done
281
+ // Clear AI Editor presence when done
279
282
  DEBUG &&
280
283
  console.log(
281
- 'AI editing done, clearing VizBot presence',
284
+ 'AI editing done, clearing AI Editor presence',
282
285
  );
283
286
  localPresence.submit(null, (error) => {
284
- DEBUG && console.log('VizBot presence cleared');
287
+ DEBUG && console.log('AI Editor presence cleared');
285
288
  if (error) {
286
289
  console.warn(
287
- 'VizBot presence cleanup error:',
290
+ 'AI Editor presence cleanup error:',
288
291
  error,
289
292
  );
290
293
  }
@@ -154,6 +154,9 @@ export const generateAIResponse = async ({
154
154
  effort: 'medium',
155
155
  exclude: false,
156
156
  } as any, // Type assertion for OpenRouter-specific reasoning parameter
157
+ provider: {
158
+ sort: 'throughput',
159
+ } as any, // Type assertion for OpenRouter-specific provider parameter
157
160
  stream: true,
158
161
  });
159
162
 
@@ -40,6 +40,9 @@ export const handleAICopilot = () => {
40
40
  apiKey: VZCODE_AI_COPILOT_API_KEY,
41
41
  baseURL: VZCODE_AI_COPILOT_BASE_URL,
42
42
  },
43
+ additionalParameters: {
44
+ provider: { sort: 'throughput' },
45
+ },
43
46
  streaming: false,
44
47
  };
45
48
  debug && console.log('chatModel options:', options);
@@ -100,20 +100,20 @@ app.use(express.static(dir));
100
100
  const shareDBConnection = shareDBBackend.connect();
101
101
  const shareDBDoc = shareDBConnection.get('documents', '1');
102
102
 
103
- // Set up presence for VizBot following the same pattern as useShareDB.ts
103
+ // Set up presence for AI editing following the same pattern as useShareDB.ts
104
104
  const docPresence = shareDBConnection.getDocPresence(
105
105
  'documents',
106
106
  '1',
107
107
  );
108
108
 
109
- // Create local presence for VizBot with a unique ID
110
- const generateVizBotId = () => {
109
+ // Create local presence for AI editing with a unique ID
110
+ const generateAIEditId = () => {
111
111
  const timestamp = Date.now().toString(36);
112
- return `vizbot-${timestamp}`;
112
+ return `ai-edit-${timestamp}`;
113
113
  };
114
114
 
115
- const createVizBotLocalPresence = () =>
116
- docPresence.create(generateVizBotId());
115
+ const createAIEditLocalPresence = () =>
116
+ docPresence.create(generateAIEditId());
117
117
 
118
118
  shareDBDoc.create(initialDocument, json1Presence.type.uri);
119
119
 
@@ -137,7 +137,7 @@ app.post(
137
137
  bodyParser.json(),
138
138
  handleAIChatMessage({
139
139
  shareDBDoc,
140
- createVizBotLocalPresence,
140
+ createAIEditLocalPresence,
141
141
  onCreditDeduction: undefined,
142
142
  }),
143
143
  );