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.
Files changed (47) hide show
  1. package/dist/assets/{buildWorker-Dsx6rOdK.js → buildWorker-wLkQ0yz1.js} +64 -64
  2. package/dist/assets/{index-BM8tDh6M.css → index-BoFeK4GL.css} +1 -1
  3. package/dist/assets/{index-CpxqGhXj.js → index-DuUjtrt6.js} +150 -150
  4. package/dist/assets/{worker-BeBZGG1n.js → worker-DHxSPM7N.js} +78 -78
  5. package/dist/cli.js +113 -0
  6. package/dist/index.html +2 -2
  7. package/dist/ot.js +12 -0
  8. package/dist/randomId.js +14 -0
  9. package/dist/runCode.js +20 -0
  10. package/dist/server/aiChatHandler/aiEditing.js +89 -0
  11. package/dist/server/aiChatHandler/chatOperations.js +360 -0
  12. package/dist/server/aiChatHandler/errorHandling.js +49 -0
  13. package/dist/server/aiChatHandler/index.js +112 -0
  14. package/dist/server/aiChatHandler/llmStreaming.js +284 -0
  15. package/dist/server/aiChatHandler/validation.js +19 -0
  16. package/dist/server/computeInitialDocument.js +146 -0
  17. package/dist/server/config.js +21 -0
  18. package/dist/server/featureFlags.js +5 -0
  19. package/dist/server/generateAIResponse.js +129 -0
  20. package/dist/server/handleAIAssist.js +39 -0
  21. package/dist/server/handleAIChatMessage.js +2 -0
  22. package/dist/server/handleAICopilot.js +81 -0
  23. package/dist/server/index.js +297 -0
  24. package/dist/server/isDirectory.js +1 -0
  25. package/dist/server/livekit.js +17 -0
  26. package/dist/server/prettier.js +90 -0
  27. package/dist/server/setupEnv.js +7 -0
  28. package/dist/submitOperation.js +14 -0
  29. package/dist/types.js +1 -0
  30. package/dist/utils/fileDiff.js +66 -0
  31. package/package.json +21 -20
  32. package/src/cli.ts +208 -0
  33. package/src/client/App/index.tsx +0 -1
  34. package/src/client/VZCodeContext/types.ts +3 -4
  35. package/src/client/VZCodeContext/useVZCodeState.ts +24 -6
  36. package/src/client/VZSidebar/AIChat/ChatInput.tsx +4 -0
  37. package/src/client/VZSidebar/AIChat/Message.tsx +2 -66
  38. package/src/client/VZSidebar/AIChat/MessageList.tsx +0 -15
  39. package/src/client/VZSidebar/AIChat/index.tsx +91 -7
  40. package/src/client/VZSidebar/AIChat/styles.scss +19 -0
  41. package/src/server/aiChatHandler/aiEditing.ts +0 -1
  42. package/src/server/aiChatHandler/chatOperations.ts +0 -34
  43. package/src/server/aiChatHandler/index.ts +0 -1
  44. package/src/server/aiChatHandler/llmStreaming.ts +3 -3
  45. package/src/server/index.ts +1 -10
  46. package/src/server/aiChatHandler/undoHandler.ts +0 -97
  47. package/src/server/handleAIChatUndo.ts +0 -2
@@ -1,4 +1,9 @@
1
- import { useContext, useMemo, useEffect } from 'react';
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 current chat data from content
32
- const currentChat = content?.chats?.[currentChatId];
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 = rawMessages.length === 0;
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={handleSendMessage}
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;
@@ -147,6 +147,5 @@ export const performAIEditing = async ({
147
147
  content: result.content,
148
148
  generationId: result.generationId,
149
149
  diffData,
150
- beforeFiles, // Store the snapshot for undo functionality
151
150
  };
152
151
  };
@@ -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.VIZHUB_EDIT_WITH_AI_API_KEY,
80
+ apiKey: process.env.VZCODE_EDIT_WITH_AI_API_KEY,
81
81
  baseURL:
82
- process.env.VIZHUB_EDIT_WITH_AI_BASE_URL ||
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.VIZHUB_EDIT_WITH_AI_MODEL_NAME ||
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
@@ -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
- import { handleAIChatUndo } from './handleAIChatUndo.js';
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
- };
@@ -1,2 +0,0 @@
1
- // Re-export the handleAIChatUndo from the new modular structure
2
- export { handleAIChatUndo } from './aiChatHandler/undoHandler.js';