vzcode 2.18.0 → 2.22.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 (57) hide show
  1. package/README.md +359 -0
  2. package/dist/assets/index-CfDs-dAu.js +476 -0
  3. package/dist/assets/{index-BvGPtSrr.css → index-fSroLVgi.css} +1 -1
  4. package/dist/assets/{worker-y5jhqCTR.js → worker-Cm3I3tnR.js} +57 -57
  5. package/dist/assets/{worker-ClF0pBYr.js → worker-KiuLM-X4.js} +71 -71
  6. package/dist/index.html +2 -2
  7. package/dist/llm-streaming-server/aiEditing.js +58 -0
  8. package/dist/llm-streaming-server/chatOperations.js +494 -0
  9. package/dist/llm-streaming-server/errorHandling.js +49 -0
  10. package/dist/llm-streaming-server/index.js +20 -0
  11. package/dist/llm-streaming-server/llmStreaming.js +265 -0
  12. package/dist/llm-streaming-server/validation.js +19 -0
  13. package/dist/server/aiChatHandler/chatOperations.js +3 -4
  14. package/dist/server/aiChatHandler/index.js +5 -5
  15. package/dist/server/aiChatHandler/llmStreaming.js +57 -4
  16. package/dist/server/prettier.js +18 -16
  17. package/dist/utils/fileDiff.js +25 -1
  18. package/package.json +45 -45
  19. package/src/client/CodeEditor/getOrCreateEditor.tsx +272 -258
  20. package/src/client/CodeEditor/index.tsx +3 -3
  21. package/src/client/VZRight.tsx +4 -0
  22. package/src/client/VZSidebar/AIChat/ChatInput.tsx +1 -1
  23. package/src/client/VZSidebar/AIChat/DiffView.scss +37 -1
  24. package/src/client/VZSidebar/AIChat/DiffView.tsx +55 -16
  25. package/src/client/VZSidebar/AIChat/styles.scss +38 -0
  26. package/src/client/VZSidebar/FileTypeIcon.tsx +1 -0
  27. package/src/client/VZSidebar/index.tsx +1 -1
  28. package/src/client/featureFlags.ts +7 -0
  29. package/src/llm-streaming-server/README.md +71 -0
  30. package/src/llm-streaming-server/aiEditing.ts +102 -0
  31. package/src/llm-streaming-server/chatOperations.ts +655 -0
  32. package/src/llm-streaming-server/errorHandling.ts +66 -0
  33. package/src/llm-streaming-server/index.ts +51 -0
  34. package/src/llm-streaming-server/llmStreaming.ts +403 -0
  35. package/src/llm-streaming-server/validation.ts +24 -0
  36. package/src/llm-streaming-ui/README.md +103 -0
  37. package/src/llm-streaming-ui/components/ChatInput.tsx +237 -0
  38. package/src/llm-streaming-ui/components/DiffView.scss +173 -0
  39. package/src/llm-streaming-ui/components/DiffView.tsx +236 -0
  40. package/src/llm-streaming-ui/components/FileEditingIndicator.tsx +89 -0
  41. package/src/llm-streaming-ui/components/IndividualFileDiff.tsx +86 -0
  42. package/src/llm-streaming-ui/components/JumpToLatestButton.tsx +64 -0
  43. package/src/llm-streaming-ui/components/Message.tsx +145 -0
  44. package/src/llm-streaming-ui/components/MessageList.tsx +241 -0
  45. package/src/llm-streaming-ui/components/ThinkingScratchpad.tsx +42 -0
  46. package/src/llm-streaming-ui/components/TypingIndicator.tsx +19 -0
  47. package/src/llm-streaming-ui/components/index.tsx +388 -0
  48. package/src/llm-streaming-ui/components/styles.scss +831 -0
  49. package/src/llm-streaming-ui/components/useSpeechRecognition.ts +116 -0
  50. package/src/llm-streaming-ui/index.ts +34 -0
  51. package/src/server/aiChatHandler/chatOperations.ts +3 -4
  52. package/src/server/aiChatHandler/index.ts +5 -5
  53. package/src/server/aiChatHandler/llmStreaming.ts +87 -4
  54. package/src/server/prettier.ts +30 -31
  55. package/src/types.ts +5 -0
  56. package/src/utils/fileDiff.ts +36 -2
  57. package/dist/assets/index-DuDXdJWC.js +0 -477
@@ -0,0 +1,388 @@
1
+ import {
2
+ useContext,
3
+ useMemo,
4
+ useEffect,
5
+ useCallback,
6
+ } from 'react';
7
+ import { VZCodeContext } from '../../client/VZCodeContext';
8
+ import { MessageList } from './MessageList';
9
+ import { ChatInput } from './ChatInput';
10
+ import { ExtendedVizChat } from '../../types.js';
11
+ import { useAutoScroll } from '../../client/hooks/useAutoScroll';
12
+ import { JumpToLatestButton } from './JumpToLatestButton';
13
+ import './styles.scss';
14
+
15
+ const DEBUG = false;
16
+
17
+ const showSuggestedRequests = false;
18
+
19
+ const showPreviousChats = false;
20
+
21
+ // Component for displaying the list of existing chats
22
+ const ChatList = ({
23
+ chats,
24
+ selectedChatId,
25
+ onSelectChat,
26
+ getChatTitle,
27
+ }) => {
28
+ return (
29
+ <div className="ai-chat-list">
30
+ <div className="ai-chat-list-header">
31
+ <h4>Previous Chats</h4>
32
+ </div>
33
+ <div className="ai-chat-suggested-prompts">
34
+ {chats.map((chat) => (
35
+ <button
36
+ key={chat.id}
37
+ className={`ai-chat-suggested-prompt ${
38
+ selectedChatId === chat.id ? 'selected' : ''
39
+ }`}
40
+ onClick={() => onSelectChat(chat.id)}
41
+ >
42
+ {getChatTitle(chat)}
43
+ </button>
44
+ ))}
45
+ </div>
46
+ </div>
47
+ );
48
+ };
49
+
50
+ export const AIChat = () => {
51
+ const {
52
+ aiChatFocused,
53
+ content,
54
+ aiChatMode,
55
+ aiChatMessage,
56
+ currentChatId,
57
+ selectedChatId,
58
+ setSelectedChatId,
59
+ aiErrorMessage,
60
+ setAIChatMode,
61
+ getStoredAIPrompt,
62
+ setAIChatMessage,
63
+ handleSendMessage,
64
+ setAIErrorMessage,
65
+ navigateMessageHistoryUp,
66
+ navigateMessageHistoryDown,
67
+ resetMessageHistoryNavigation,
68
+ enableMinimalEditFlow,
69
+ } = useContext(VZCodeContext);
70
+
71
+ // Use the new simplified auto-scroll hook
72
+ const {
73
+ containerRef: messagesContainerRef,
74
+ showJumpButton,
75
+ onNewEvent,
76
+ onJumpToLatest,
77
+ beforeRender,
78
+ afterRender,
79
+ } = useAutoScroll({ threshold: 24 });
80
+
81
+ // Get the active chat ID and chat data
82
+ // If selectedChatId is set, use it; otherwise, if no chat selected, don't default to currentChatId
83
+ const activeChatId = selectedChatId;
84
+ const currentChat = activeChatId
85
+ ? content?.chats?.[activeChatId]
86
+ : null;
87
+ const rawMessages = useMemo(
88
+ () => currentChat?.messages || [],
89
+ [currentChat?.messages],
90
+ );
91
+ const aiStatus = currentChat?.aiStatus;
92
+ const aiScratchpad = currentChat?.aiScratchpad;
93
+ const currentStatus = (currentChat as ExtendedVizChat)
94
+ ?.currentStatus;
95
+
96
+ // Debug logging for AI status
97
+ DEBUG && console.log('AIChat: currentChat:', currentChat);
98
+ DEBUG && console.log('AIChat: aiStatus:', aiStatus);
99
+ DEBUG &&
100
+ console.log(
101
+ 'AIChat: enableMinimalEditFlow:',
102
+ enableMinimalEditFlow,
103
+ );
104
+
105
+ // Transform messages to ensure they have required id field - memoized to avoid recreation
106
+ const messages = useMemo(
107
+ () =>
108
+ rawMessages.map((msg, index) => ({
109
+ ...msg,
110
+ id: msg.id || `msg-${index}`,
111
+ })),
112
+ [rawMessages],
113
+ );
114
+
115
+ // Check if this is the first time opening the chat (no messages) or no chat selected
116
+ const isEmptyState =
117
+ !selectedChatId || rawMessages.length === 0;
118
+
119
+ // Get all existing chats
120
+ const allChats = content?.chats || {};
121
+ const existingChats = Object.values(allChats).filter(
122
+ (chat) => chat.messages.length > 0,
123
+ );
124
+ const hasExistingChats = existingChats.length > 0;
125
+
126
+ // Generate title for a chat (first 50 characters of first user message)
127
+ const getChatTitle = (chat) => {
128
+ const firstUserMessage = chat.messages.find(
129
+ (msg) => msg.role === 'user',
130
+ );
131
+ if (!firstUserMessage) return 'New Chat';
132
+ return (
133
+ firstUserMessage.content.slice(0, 50) +
134
+ (firstUserMessage.content.length > 50 ? '...' : '')
135
+ );
136
+ };
137
+
138
+ // Wrapper for handleSendMessage to automatically select the chat when sending
139
+ const handleSendMessageWithSelection = useCallback(() => {
140
+ // If no chat is selected, select the currentChatId when sending a message
141
+ if (!selectedChatId) {
142
+ setSelectedChatId(currentChatId);
143
+ }
144
+
145
+ // Call the original handleSendMessage without parameters
146
+ return handleSendMessage();
147
+ }, [
148
+ selectedChatId,
149
+ setSelectedChatId,
150
+ currentChatId,
151
+ handleSendMessage,
152
+ ]);
153
+
154
+ // Check for stored AI prompt on component mount (post-fork restoration)
155
+ // This logic is now handled by the AutoSendAIMessage component in VizHub
156
+ // to ensure proper timing and coordination with editor opening
157
+ useEffect(() => {
158
+ DEBUG &&
159
+ console.log('AIChat: Checking for stored AI prompt');
160
+ if (getStoredAIPrompt) {
161
+ const storedPrompt = getStoredAIPrompt();
162
+ DEBUG &&
163
+ console.log(
164
+ 'AIChat: Stored prompt result:',
165
+ storedPrompt,
166
+ );
167
+ if (storedPrompt) {
168
+ // Only restore the prompt and mode, but don't auto-send
169
+ // The AutoSendAIMessage component will handle the sending
170
+ DEBUG &&
171
+ console.log(
172
+ 'AIChat: Restoring prompt and mode only',
173
+ );
174
+ setAIChatMessage(storedPrompt.prompt);
175
+ setAIChatMode(
176
+ storedPrompt.modelName === 'ask' ? 'ask' : 'edit',
177
+ );
178
+
179
+ // Don't clear the stored prompt here - let AutoSendAIMessage handle it
180
+ // Don't auto-submit here - let AutoSendAIMessage handle the timing
181
+ DEBUG &&
182
+ console.log(
183
+ 'AIChat: Prompt restored, waiting for AutoSendAIMessage to handle sending',
184
+ );
185
+ } else {
186
+ DEBUG &&
187
+ console.log('AIChat: No stored prompt found');
188
+ }
189
+ }
190
+ }, [getStoredAIPrompt, setAIChatMessage, setAIChatMode]);
191
+
192
+ return (
193
+ <div className="ai-chat-container">
194
+ <div className="ai-chat-content">
195
+ <div
196
+ className="ai-chat-messages-container"
197
+ ref={messagesContainerRef}
198
+ >
199
+ {isEmptyState ? (
200
+ <div className="ai-chat-empty">
201
+ <div className="ai-chat-empty-icon">✨</div>
202
+ <h3 className="ai-chat-empty-title">
203
+ Edit with AI
204
+ </h3>
205
+ <div className="ai-chat-empty-text">
206
+ How can I help you?
207
+ </div>
208
+ {hasExistingChats && showPreviousChats && (
209
+ <ChatList
210
+ chats={existingChats}
211
+ selectedChatId={selectedChatId}
212
+ onSelectChat={setSelectedChatId}
213
+ getChatTitle={getChatTitle}
214
+ />
215
+ )}
216
+ {showSuggestedRequests && (
217
+ <div className="ai-chat-empty-examples">
218
+ {aiChatMode === 'ask' ? (
219
+ <>
220
+ <h4>Try asking questions like:</h4>
221
+ <div className="ai-chat-suggested-prompts">
222
+ <button
223
+ className="ai-chat-suggested-prompt"
224
+ onClick={() =>
225
+ handleSendMessage(
226
+ 'Explain how this works',
227
+ )
228
+ }
229
+ >
230
+ &quot;Explain how this works&quot;
231
+ </button>
232
+ <button
233
+ className="ai-chat-suggested-prompt"
234
+ onClick={() =>
235
+ handleSendMessage(
236
+ 'How could I change it so that the circles are bigger?',
237
+ )
238
+ }
239
+ >
240
+ &quot;How could I change it so
241
+ that the circles are bigger?&quot;
242
+ </button>
243
+ <button
244
+ className="ai-chat-suggested-prompt"
245
+ onClick={() =>
246
+ handleSendMessage(
247
+ 'What does this function do?',
248
+ )
249
+ }
250
+ >
251
+ &quot;What does this function
252
+ do?&quot;
253
+ </button>
254
+ <button
255
+ className="ai-chat-suggested-prompt"
256
+ onClick={() =>
257
+ handleSendMessage(
258
+ 'How can I make this more accessible?',
259
+ )
260
+ }
261
+ >
262
+ &quot;How can I make this more
263
+ accessible?&quot;
264
+ </button>
265
+ </div>
266
+ </>
267
+ ) : (
268
+ <>
269
+ <h4>Try edit requests like these:</h4>
270
+ <div className="ai-chat-suggested-prompts">
271
+ <button
272
+ className="ai-chat-suggested-prompt"
273
+ onClick={() =>
274
+ handleSendMessage(
275
+ 'Change the circles to squares',
276
+ )
277
+ }
278
+ >
279
+ &quot;Change the circles to
280
+ squares&quot;
281
+ </button>
282
+ <button
283
+ className="ai-chat-suggested-prompt"
284
+ onClick={() =>
285
+ handleSendMessage(
286
+ 'Add a button that toggles the animation',
287
+ )
288
+ }
289
+ >
290
+ &quot;Add a button that toggles
291
+ the animation&quot;
292
+ </button>
293
+ <button
294
+ className="ai-chat-suggested-prompt"
295
+ onClick={() =>
296
+ handleSendMessage(
297
+ 'Fix the CSS so the layout is responsive',
298
+ )
299
+ }
300
+ >
301
+ &quot;Fix the CSS so the layout is
302
+ responsive&quot;
303
+ </button>
304
+ <button
305
+ className="ai-chat-suggested-prompt"
306
+ onClick={() =>
307
+ handleSendMessage(
308
+ 'Refactor this function to use async/await',
309
+ )
310
+ }
311
+ >
312
+ &quot;Refactor this function to
313
+ use async/await&quot;
314
+ </button>
315
+ </div>
316
+ </>
317
+ )}
318
+ </div>
319
+ )}
320
+ {showSuggestedRequests && (
321
+ <div className="ai-chat-empty-text">
322
+ {aiChatMode === 'ask'
323
+ ? 'Type your question below to get started!'
324
+ : 'Type your edit request below to get started!'}
325
+ </div>
326
+ )}
327
+ </div>
328
+ ) : (
329
+ <MessageList
330
+ messages={messages}
331
+ isLoading={false}
332
+ chatId={selectedChatId || currentChatId}
333
+ aiScratchpad={aiScratchpad}
334
+ currentStatus={currentStatus}
335
+ onNewEvent={onNewEvent}
336
+ onJumpToLatest={onJumpToLatest}
337
+ beforeRender={beforeRender}
338
+ afterRender={afterRender}
339
+ />
340
+ )}
341
+ {aiErrorMessage && (
342
+ <div className="ai-chat-error">
343
+ <div className="ai-chat-error-content">
344
+ <span className="ai-chat-error-icon">
345
+ ⚠️
346
+ </span>
347
+ <span className="ai-chat-error-message">
348
+ {aiErrorMessage}
349
+ </span>
350
+ <button
351
+ className="ai-chat-error-dismiss"
352
+ onClick={() => setAIErrorMessage(null)}
353
+ aria-label="Dismiss error"
354
+ >
355
+ ×
356
+ </button>
357
+ </div>
358
+ </div>
359
+ )}
360
+ {/* Jump to Latest Button */}
361
+ <JumpToLatestButton
362
+ visible={showJumpButton}
363
+ onClick={onJumpToLatest}
364
+ />
365
+ </div>
366
+ </div>
367
+ <div className="ai-chat-input-fixed">
368
+ <ChatInput
369
+ aiChatMessage={aiChatMessage}
370
+ setAIChatMessage={setAIChatMessage}
371
+ onSendMessage={handleSendMessageWithSelection}
372
+ focused={aiChatFocused}
373
+ aiChatMode={aiChatMode}
374
+ setAIChatMode={setAIChatMode}
375
+ navigateMessageHistoryUp={
376
+ navigateMessageHistoryUp
377
+ }
378
+ navigateMessageHistoryDown={
379
+ navigateMessageHistoryDown
380
+ }
381
+ resetMessageHistoryNavigation={
382
+ resetMessageHistoryNavigation
383
+ }
384
+ />
385
+ </div>
386
+ </div>
387
+ );
388
+ };