vzcode 2.3.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 (196) hide show
  1. package/package.json +3 -2
  2. package/src/cli.ts +208 -0
  3. package/src/client/AIAssist/AIAssistCodeMirrorKeyMap.ts +42 -0
  4. package/src/client/AIAssist/AIAssistWidget/index.tsx +135 -0
  5. package/src/client/AIAssist/AIAssistWidget/style.scss +47 -0
  6. package/src/client/AIAssist/Spinner.jsx +76 -0
  7. package/src/client/AIAssist/formatFile.ts +27 -0
  8. package/src/client/AIAssist/generateFilesContext.ts +25 -0
  9. package/src/client/AIAssist/index.ts +2 -0
  10. package/src/client/AIAssist/startAIAssist.ts +95 -0
  11. package/src/client/App/index.tsx +152 -0
  12. package/src/client/App/style.scss +82 -0
  13. package/src/client/App/usePending.ts +51 -0
  14. package/src/client/App/useShareDB.ts +161 -0
  15. package/src/client/CodeEditor/Copilot/index.ts +74 -0
  16. package/src/client/CodeEditor/InteractiveWidgets/boolToggler.ts +12 -0
  17. package/src/client/CodeEditor/InteractiveWidgets/colorPicker.ts +68 -0
  18. package/src/client/CodeEditor/InteractiveWidgets/colorsInTextPlugin.ts +174 -0
  19. package/src/client/CodeEditor/InteractiveWidgets/highlightWidgets.ts +79 -0
  20. package/src/client/CodeEditor/InteractiveWidgets/index.ts +44 -0
  21. package/src/client/CodeEditor/InteractiveWidgets/markdownCheckboxToggler.ts +12 -0
  22. package/src/client/CodeEditor/InteractiveWidgets/numberDragger.ts +20 -0
  23. package/src/client/CodeEditor/InteractiveWidgets/rotateWidget.ts +143 -0
  24. package/src/client/CodeEditor/InteractiveWidgets/urlClicker.test.ts +48 -0
  25. package/src/client/CodeEditor/InteractiveWidgets/urlClicker.ts +12 -0
  26. package/src/client/CodeEditor/InteractiveWidgets/vec2Slider.ts +25 -0
  27. package/src/client/CodeEditor/basicSetup.ts +73 -0
  28. package/src/client/CodeEditor/getOrCreateEditor.tsx +615 -0
  29. package/src/client/CodeEditor/index.tsx +202 -0
  30. package/src/client/CodeEditor/json1PresenceBroadcast.ts +41 -0
  31. package/src/client/CodeEditor/json1PresenceDisplay.ts +350 -0
  32. package/src/client/CodeEditor/rainbowBrackets.ts +122 -0
  33. package/src/client/CodeEditor/style.scss +156 -0
  34. package/src/client/CodeEditor/typescriptExtension/worker.ts +32 -0
  35. package/src/client/CodeErrorOverlay/index.tsx +77 -0
  36. package/src/client/CodeErrorOverlay/style.scss +33 -0
  37. package/src/client/Fonts/fonts.ts +8 -0
  38. package/src/client/Icons/AdjustmentSVG.tsx +19 -0
  39. package/src/client/Icons/BugSVG.tsx +20 -0
  40. package/src/client/Icons/CloseSVG.tsx +19 -0
  41. package/src/client/Icons/CssSVG.tsx +16 -0
  42. package/src/client/Icons/DirectoryArrowSVG.tsx +19 -0
  43. package/src/client/Icons/EditSVG.tsx +19 -0
  44. package/src/client/Icons/FileSVG.tsx +19 -0
  45. package/src/client/Icons/FolderSVG.tsx +20 -0
  46. package/src/client/Icons/GearSVG.tsx +16 -0
  47. package/src/client/Icons/HtmlSVG.tsx +16 -0
  48. package/src/client/Icons/JavaScriptSVG.tsx +16 -0
  49. package/src/client/Icons/JsonSVG.tsx +16 -0
  50. package/src/client/Icons/MarkdownSVG.tsx +16 -0
  51. package/src/client/Icons/MicOffSVG.tsx +35 -0
  52. package/src/client/Icons/MicSVG.tsx +25 -0
  53. package/src/client/Icons/NewSVG.tsx +19 -0
  54. package/src/client/Icons/PinSVG.tsx +14 -0
  55. package/src/client/Icons/PlaySVG.tsx +16 -0
  56. package/src/client/Icons/QuestionMarkSVG.tsx +18 -0
  57. package/src/client/Icons/ReactSVG.tsx +16 -0
  58. package/src/client/Icons/SearchSVG.tsx +20 -0
  59. package/src/client/Icons/SparklesSVG.tsx +23 -0
  60. package/src/client/Icons/SplitEditorSVG.tsx +34 -0
  61. package/src/client/Icons/StopSVG.tsx +15 -0
  62. package/src/client/Icons/SvelteSVG.tsx +16 -0
  63. package/src/client/Icons/TrashSVG.tsx +19 -0
  64. package/src/client/Icons/TypeScriptSVG.tsx +16 -0
  65. package/src/client/Icons/index.tsx +26 -0
  66. package/src/client/ImageViewer/index.tsx +91 -0
  67. package/src/client/ImageViewer/style.scss +41 -0
  68. package/src/client/PresenceNotifications/PresenceNotifications.test.ts +134 -0
  69. package/src/client/PresenceNotifications/index.tsx +158 -0
  70. package/src/client/PresenceNotifications/style.scss +17 -0
  71. package/src/client/RunCodeWidget/index.tsx +117 -0
  72. package/src/client/RunCodeWidget/style.scss +24 -0
  73. package/src/client/SplitPaneResizeContext.tsx +159 -0
  74. package/src/client/TabList/Tab.tsx +73 -0
  75. package/src/client/TabList/index.tsx +35 -0
  76. package/src/client/TabList/style.scss +51 -0
  77. package/src/client/VZCodeContext/index.tsx +29 -0
  78. package/src/client/VZCodeContext/types.ts +248 -0
  79. package/src/client/VZCodeContext/useVZCodeState.ts +602 -0
  80. package/src/client/VZKeyboardShortcutsDoc.tsx +148 -0
  81. package/src/client/VZLeft.tsx +28 -0
  82. package/src/client/VZMiddle.tsx +172 -0
  83. package/src/client/VZResizer/index.tsx +128 -0
  84. package/src/client/VZResizer/styles.scss +33 -0
  85. package/src/client/VZRight.tsx +113 -0
  86. package/src/client/VZSettings.tsx +275 -0
  87. package/src/client/VZSidebar/AIChat/ChatInput.tsx +243 -0
  88. package/src/client/VZSidebar/AIChat/DiffView.scss +136 -0
  89. package/src/client/VZSidebar/AIChat/DiffView.tsx +176 -0
  90. package/src/client/VZSidebar/AIChat/Message.tsx +79 -0
  91. package/src/client/VZSidebar/AIChat/MessageList.tsx +190 -0
  92. package/src/client/VZSidebar/AIChat/StreamingMessage.tsx +35 -0
  93. package/src/client/VZSidebar/AIChat/ThinkingScratchpad.tsx +156 -0
  94. package/src/client/VZSidebar/AIChat/TypingIndicator.tsx +19 -0
  95. package/src/client/VZSidebar/AIChat/index.tsx +357 -0
  96. package/src/client/VZSidebar/AIChat/styles.scss +646 -0
  97. package/src/client/VZSidebar/AIChat/useSpeechRecognition.ts +117 -0
  98. package/src/client/VZSidebar/CreateDirModal.tsx +111 -0
  99. package/src/client/VZSidebar/CreateFileModal.tsx +111 -0
  100. package/src/client/VZSidebar/DeleteConfirmationModal.tsx +76 -0
  101. package/src/client/VZSidebar/DirectoryListing.tsx +89 -0
  102. package/src/client/VZSidebar/EmptyState.tsx +11 -0
  103. package/src/client/VZSidebar/FileListing.tsx +91 -0
  104. package/src/client/VZSidebar/FileTypeIcon.tsx +49 -0
  105. package/src/client/VZSidebar/Item.tsx +237 -0
  106. package/src/client/VZSidebar/Listing.tsx +47 -0
  107. package/src/client/VZSidebar/Search.tsx +527 -0
  108. package/src/client/VZSidebar/VisualEditor.tsx +282 -0
  109. package/src/client/VZSidebar/VoiceChatModal.tsx +121 -0
  110. package/src/client/VZSidebar/aiCopyPaste.ts +199 -0
  111. package/src/client/VZSidebar/index.tsx +728 -0
  112. package/src/client/VZSidebar/styles.scss +612 -0
  113. package/src/client/VZSidebar/useDragAndDrop.tsx +408 -0
  114. package/src/client/bootstrap.ts +26 -0
  115. package/src/client/buildWorker.ts +3 -0
  116. package/src/client/featureFlags.ts +8 -0
  117. package/src/client/generateRequestId.ts +6 -0
  118. package/src/client/getFileTree.ts +53 -0
  119. package/src/client/index.ts +19 -0
  120. package/src/client/main.jsx +10 -0
  121. package/src/client/presenceColor.ts +8 -0
  122. package/src/client/shouldTriggerRun.ts +31 -0
  123. package/src/client/sortFileTree.ts +21 -0
  124. package/src/client/tabsSearchParameters.test.ts +79 -0
  125. package/src/client/tabsSearchParameters.ts +137 -0
  126. package/src/client/themes/index.ts +7 -0
  127. package/src/client/themes/themes.ts +77 -0
  128. package/src/client/themes/uiwImports.ts +32 -0
  129. package/src/client/themes/useDynamicTheme.ts +29 -0
  130. package/src/client/themes/vizhubTheme/colors.ts +80 -0
  131. package/src/client/themes/vizhubTheme/index.ts +193 -0
  132. package/src/client/useActions.ts +288 -0
  133. package/src/client/useESLint/index.ts +72 -0
  134. package/src/client/useESLint/worker.ts +113 -0
  135. package/src/client/useEditorCache.ts +35 -0
  136. package/src/client/useFileCRUD.ts +228 -0
  137. package/src/client/useKeyboardShortcuts.ts +451 -0
  138. package/src/client/useOpenDirectories.ts +99 -0
  139. package/src/client/usePresenceAutoFollow.ts +85 -0
  140. package/src/client/usePrettier/index.ts +178 -0
  141. package/src/client/usePrettier/worker.ts +109 -0
  142. package/src/client/useRunCode.tsx +15 -0
  143. package/src/client/useRuntimeError.ts +32 -0
  144. package/src/client/useSubmitOperation.ts +15 -0
  145. package/src/client/useURLSync.ts +137 -0
  146. package/src/client/usernameLocalStorage.ts +43 -0
  147. package/src/client/utils/fileExtension.ts +10 -0
  148. package/src/client/utils/isImageFile.ts +12 -0
  149. package/src/client/vzReducer/aiChatReducer.ts +44 -0
  150. package/src/client/vzReducer/closeTabReducer.test.ts +205 -0
  151. package/src/client/vzReducer/closeTabsReducer.ts +96 -0
  152. package/src/client/vzReducer/createInitialState.ts +38 -0
  153. package/src/client/vzReducer/editorNoLongerWantsFocusReducer.ts +9 -0
  154. package/src/client/vzReducer/findPane.ts +19 -0
  155. package/src/client/vzReducer/index.ts +250 -0
  156. package/src/client/vzReducer/openTabReducer.test.ts +127 -0
  157. package/src/client/vzReducer/openTabReducer.ts +74 -0
  158. package/src/client/vzReducer/searchReducer.test.ts +292 -0
  159. package/src/client/vzReducer/searchReducer.ts +181 -0
  160. package/src/client/vzReducer/setActiveFileIdReducer.ts +28 -0
  161. package/src/client/vzReducer/setActiveFileLeftRightReducer.ts +76 -0
  162. package/src/client/vzReducer/setIsDocOpenReducer.ts +9 -0
  163. package/src/client/vzReducer/setIsSettingsOpenReducer.ts +9 -0
  164. package/src/client/vzReducer/setThemeReducer.ts +9 -0
  165. package/src/client/vzReducer/setUsernameReducer.ts +9 -0
  166. package/src/client/vzReducer/splitCurrentPaneReducer.ts +66 -0
  167. package/src/client/vzReducer/toggleAutoFollowReducer.ts +12 -0
  168. package/src/client/vzReducer/updatePane.ts +40 -0
  169. package/src/client/vzReducer/updatePresenceIndicatorReducer.ts +30 -0
  170. package/src/client/vzReducer/visualEditorReducer.ts +9 -0
  171. package/src/ot.ts +24 -0
  172. package/src/randomId.ts +15 -0
  173. package/src/reactRouterExports.ts +15 -0
  174. package/src/runCode.ts +27 -0
  175. package/src/server/aiChatHandler/aiEditing.ts +151 -0
  176. package/src/server/aiChatHandler/chatOperations.ts +476 -0
  177. package/src/server/aiChatHandler/errorHandling.ts +66 -0
  178. package/src/server/aiChatHandler/index.ts +199 -0
  179. package/src/server/aiChatHandler/llmStreaming.ts +419 -0
  180. package/src/server/aiChatHandler/validation.ts +24 -0
  181. package/src/server/computeInitialDocument.ts +220 -0
  182. package/src/server/config.ts +24 -0
  183. package/src/server/featureFlags.ts +8 -0
  184. package/src/server/generateAIResponse.ts +188 -0
  185. package/src/server/handleAIAssist.ts +53 -0
  186. package/src/server/handleAIChatMessage.ts +2 -0
  187. package/src/server/handleAICopilot.ts +97 -0
  188. package/src/server/index.ts +379 -0
  189. package/src/server/isDirectory.ts +1 -0
  190. package/src/server/livekit.ts +24 -0
  191. package/src/server/prettier.ts +115 -0
  192. package/src/server/setupEnv.ts +8 -0
  193. package/src/submitOperation.ts +25 -0
  194. package/src/types.ts +205 -0
  195. package/src/utils/fileDiff.ts +117 -0
  196. package/src/vite-env.d.ts +10 -0
@@ -0,0 +1,408 @@
1
+ import { useState, useContext, useCallback } from 'react';
2
+ import { VZCodeContext } from '../VZCodeContext';
3
+ import './styles.scss';
4
+
5
+ interface FileSystemEntry {
6
+ isFile: boolean;
7
+ isDirectory: boolean;
8
+ name: string;
9
+ createReader: () => FileSystemDirectoryReader;
10
+ file: (callback: (file: File) => void) => void;
11
+ }
12
+
13
+ interface FileSystemDirectoryReader {
14
+ readEntries: (
15
+ callback: (entries: FileSystemEntry[]) => void,
16
+ ) => void;
17
+ }
18
+
19
+ const DEBUG = false;
20
+
21
+ export const useDragAndDrop = () => {
22
+ const [isDragOver, setIsDragOver] = useState(false);
23
+ const [isProcessing, setIsProcessing] = useState(false);
24
+ const { createFile } = useContext(VZCodeContext);
25
+
26
+ const handleDragEnter = useCallback(
27
+ (event: React.DragEvent) => {
28
+ event.preventDefault();
29
+ event.stopPropagation();
30
+ DEBUG && console.log('[useDragAndDrop] Drag Enter');
31
+ setIsDragOver(true);
32
+ },
33
+ [],
34
+ );
35
+
36
+ const handleDragOver = useCallback(
37
+ (event: React.DragEvent) => {
38
+ event.preventDefault();
39
+ event.stopPropagation();
40
+ DEBUG && console.log('[useDragAndDrop] Drag Over');
41
+ if (!isDragOver) setIsDragOver(true);
42
+ },
43
+ [isDragOver],
44
+ );
45
+
46
+ const handleDragLeave = useCallback(
47
+ (event: React.DragEvent<HTMLDivElement>) => {
48
+ event.preventDefault();
49
+ event.stopPropagation();
50
+ DEBUG && console.log('[useDragAndDrop] Drag Leave');
51
+ setIsDragOver(false);
52
+ },
53
+ [],
54
+ );
55
+
56
+ const isTextFile = (file: File): boolean => {
57
+ // Add more text file types as needed
58
+ const textTypes = [
59
+ 'text/',
60
+ 'application/json',
61
+ 'application/javascript',
62
+ 'application/typescript',
63
+ 'application/xml',
64
+ 'application/x-httpd-php',
65
+ ];
66
+ const isText =
67
+ textTypes.some((type) =>
68
+ file.type.startsWith(type),
69
+ ) ||
70
+ file.name.match(
71
+ /\.(txt|js|jsx|ts|tsx|md|css|scss|html|xml|json|php|py|rb|java|c|cpp|h|hpp)$/i,
72
+ ) !== null;
73
+
74
+ DEBUG &&
75
+ console.log(
76
+ `[useDragAndDrop] File ${file.name} is${isText ? '' : ' not'} a text file`,
77
+ );
78
+ return isText;
79
+ };
80
+
81
+ const isImageFile = (file: File): boolean => {
82
+ const imageTypes = ['image/'];
83
+ const isImage =
84
+ imageTypes.some((type) =>
85
+ file.type.startsWith(type),
86
+ ) ||
87
+ file.name.match(
88
+ /\.(png|jpg|jpeg|gif|bmp|svg|webp)$/i,
89
+ ) !== null;
90
+
91
+ DEBUG &&
92
+ console.log(
93
+ `[useDragAndDrop] File ${file.name} is${isImage ? '' : ' not'} an image file`,
94
+ );
95
+ return isImage;
96
+ };
97
+
98
+ const readFileAsText = (file: File): Promise<string> => {
99
+ return new Promise((resolve, reject) => {
100
+ DEBUG &&
101
+ console.log(
102
+ `[useDragAndDrop] Reading file: ${file.name}`,
103
+ );
104
+
105
+ if (!isTextFile(file)) {
106
+ DEBUG &&
107
+ console.log(
108
+ `[useDragAndDrop] Rejected non-text file: ${file.name}`,
109
+ );
110
+ reject(
111
+ new Error(`File ${file.name} is not a text file`),
112
+ );
113
+ return;
114
+ }
115
+
116
+ const reader = new FileReader();
117
+ reader.onload = (e) => {
118
+ if (e.target?.result) {
119
+ DEBUG &&
120
+ console.log(
121
+ `[useDragAndDrop] Successfully read file: ${file.name}`,
122
+ );
123
+ resolve(e.target.result as string);
124
+ } else {
125
+ DEBUG &&
126
+ console.log(
127
+ `[useDragAndDrop] Failed to read file: ${file.name}`,
128
+ );
129
+ reject(
130
+ new Error(`Failed to read file ${file.name}`),
131
+ );
132
+ }
133
+ };
134
+ reader.onerror = () => {
135
+ DEBUG &&
136
+ console.log(
137
+ `[useDragAndDrop] Error reading file: ${file.name}`,
138
+ );
139
+ reject(
140
+ new Error(`Error reading file ${file.name}`),
141
+ );
142
+ };
143
+ reader.readAsText(file);
144
+ });
145
+ };
146
+
147
+ const readImageAsBase64 = (
148
+ file: File,
149
+ ): Promise<string> => {
150
+ return new Promise((resolve, reject) => {
151
+ DEBUG &&
152
+ console.log(
153
+ `[useDragAndDrop] Reading image file: ${file.name}`,
154
+ );
155
+
156
+ if (!isImageFile(file)) {
157
+ DEBUG &&
158
+ console.log(
159
+ `[useDragAndDrop] Rejected non-image file: ${file.name}`,
160
+ );
161
+ reject(
162
+ new Error(
163
+ `File ${file.name} is not an image file`,
164
+ ),
165
+ );
166
+ return;
167
+ }
168
+
169
+ // For SVG files, read as text if they're text-based
170
+ if (
171
+ file.type === 'image/svg+xml' ||
172
+ file.name.toLowerCase().endsWith('.svg')
173
+ ) {
174
+ const textReader = new FileReader();
175
+ textReader.onload = (e) => {
176
+ if (e.target?.result) {
177
+ DEBUG &&
178
+ console.log(
179
+ `[useDragAndDrop] Successfully read SVG file as text: ${file.name}`,
180
+ );
181
+ resolve(e.target.result as string);
182
+ } else {
183
+ reject(
184
+ new Error(
185
+ `Failed to read SVG file ${file.name}`,
186
+ ),
187
+ );
188
+ }
189
+ };
190
+ textReader.onerror = () =>
191
+ reject(
192
+ new Error(
193
+ `Error reading SVG file ${file.name}`,
194
+ ),
195
+ );
196
+ textReader.readAsText(file);
197
+ return;
198
+ }
199
+
200
+ const reader = new FileReader();
201
+ reader.onload = (e) => {
202
+ if (e.target?.result) {
203
+ DEBUG &&
204
+ console.log(
205
+ `[useDragAndDrop] Successfully read image file: ${file.name}`,
206
+ );
207
+ // Return just the base64 part without the data URL prefix
208
+ const base64 = (e.target.result as string).split(
209
+ ',',
210
+ )[1];
211
+ resolve(base64);
212
+ } else {
213
+ DEBUG &&
214
+ console.log(
215
+ `[useDragAndDrop] Failed to read image file: ${file.name}`,
216
+ );
217
+ reject(
218
+ new Error(
219
+ `Failed to read image file ${file.name}`,
220
+ ),
221
+ );
222
+ }
223
+ };
224
+ reader.onerror = () => {
225
+ DEBUG &&
226
+ console.log(
227
+ `[useDragAndDrop] Error reading image file: ${file.name}`,
228
+ );
229
+ reject(
230
+ new Error(
231
+ `Error reading image file ${file.name}`,
232
+ ),
233
+ );
234
+ };
235
+ reader.readAsDataURL(file);
236
+ });
237
+ };
238
+
239
+ const processEntry = async (
240
+ entry: FileSystemEntry,
241
+ path: string,
242
+ ): Promise<void> => {
243
+ try {
244
+ DEBUG &&
245
+ console.log(
246
+ `[useDragAndDrop] Processing entry: ${entry.name} at path: ${path}`,
247
+ );
248
+
249
+ if (entry.isFile) {
250
+ DEBUG &&
251
+ console.log(
252
+ `[useDragAndDrop] Processing file: ${entry.name}`,
253
+ );
254
+ entry.file(async (file) => {
255
+ try {
256
+ if (isImageFile(file)) {
257
+ const content = await readImageAsBase64(file);
258
+ DEBUG &&
259
+ console.log(
260
+ `[useDragAndDrop] Creating image file: ${path}${file.name}`,
261
+ );
262
+ createFile(`${path}${file.name}`, content);
263
+ } else {
264
+ const content = await readFileAsText(file);
265
+ DEBUG &&
266
+ console.log(
267
+ `[useDragAndDrop] Creating text file: ${path}${file.name}`,
268
+ );
269
+ createFile(`${path}${file.name}`, content);
270
+ }
271
+ } catch (error) {
272
+ console.error(
273
+ `Error processing file ${file.name}:`,
274
+ error,
275
+ );
276
+ }
277
+ });
278
+ } else if (entry.isDirectory) {
279
+ DEBUG &&
280
+ console.log(
281
+ `[useDragAndDrop] Processing directory: ${entry.name}`,
282
+ );
283
+ return new Promise<void>((resolve) => {
284
+ const dirReader = entry.createReader();
285
+ const readEntries = () => {
286
+ dirReader.readEntries(async (entries) => {
287
+ if (entries.length === 0) {
288
+ DEBUG &&
289
+ console.log(
290
+ `[useDragAndDrop] Finished reading directory: ${entry.name}`,
291
+ );
292
+ resolve();
293
+ return;
294
+ }
295
+
296
+ const newPath = `${path}${entry.name}/`;
297
+ DEBUG &&
298
+ console.log(
299
+ `[useDragAndDrop] Reading ${entries.length} entries in directory: ${newPath}`,
300
+ );
301
+ await Promise.all(
302
+ entries.map((entry) =>
303
+ processEntry(entry, newPath),
304
+ ),
305
+ );
306
+ readEntries(); // Continue reading if there are more entries
307
+ });
308
+ };
309
+ readEntries();
310
+ });
311
+ }
312
+ } catch (error) {
313
+ console.error(
314
+ `Error processing entry ${entry.name}:`,
315
+ error,
316
+ );
317
+ }
318
+ };
319
+
320
+ const handleDrop = useCallback(
321
+ async (event: React.DragEvent<HTMLDivElement>) => {
322
+ event.preventDefault();
323
+ event.stopPropagation();
324
+ DEBUG &&
325
+ console.log('[useDragAndDrop] Drop event started');
326
+ setIsDragOver(false);
327
+ setIsProcessing(true);
328
+
329
+ try {
330
+ const items = event.dataTransfer.items;
331
+ DEBUG &&
332
+ console.log(
333
+ `[useDragAndDrop] Processing ${items.length} dropped items`,
334
+ );
335
+ const entries: FileSystemEntry[] = [];
336
+
337
+ for (const item of Array.from(items)) {
338
+ // Try webkit first, then fallback to other methods
339
+ const entry =
340
+ item.webkitGetAsEntry?.() ||
341
+ (item as any).getAsEntry?.() ||
342
+ (item as any).createEntry?.();
343
+
344
+ if (entry) {
345
+ DEBUG &&
346
+ console.log(
347
+ `[useDragAndDrop] Got entry for: ${entry.name}`,
348
+ );
349
+ entries.push(entry as FileSystemEntry);
350
+ } else if (item.kind === 'file') {
351
+ const file = item.getAsFile();
352
+ if (file) {
353
+ DEBUG &&
354
+ console.log(
355
+ `[useDragAndDrop] Processing dropped file: ${file.name}`,
356
+ );
357
+ try {
358
+ if (isImageFile(file)) {
359
+ const content =
360
+ await readImageAsBase64(file);
361
+ createFile(file.name, content);
362
+ } else {
363
+ const content =
364
+ await readFileAsText(file);
365
+ createFile(file.name, content);
366
+ }
367
+ } catch (error) {
368
+ console.error(
369
+ `Error processing file ${file.name}:`,
370
+ error,
371
+ );
372
+ }
373
+ }
374
+ }
375
+ }
376
+
377
+ DEBUG &&
378
+ console.log(
379
+ `[useDragAndDrop] Processing ${entries.length} entries`,
380
+ );
381
+ await Promise.all(
382
+ entries.map((entry) => processEntry(entry, '')),
383
+ );
384
+ } catch (error) {
385
+ console.error(
386
+ 'Error processing dropped items:',
387
+ error,
388
+ );
389
+ } finally {
390
+ DEBUG &&
391
+ console.log(
392
+ '[useDragAndDrop] Drop processing completed',
393
+ );
394
+ setIsProcessing(false);
395
+ }
396
+ },
397
+ [createFile],
398
+ );
399
+
400
+ return {
401
+ isDragOver,
402
+ isProcessing,
403
+ handleDragEnter,
404
+ handleDragOver,
405
+ handleDragLeave,
406
+ handleDrop,
407
+ };
408
+ };
@@ -0,0 +1,26 @@
1
+ // A central place where we import what we need from react-bootstrap.
2
+ // This strange looking way of importing makes it work with Vite's SSR build.
3
+ // Otherwise we get `Error [ERR_UNSUPPORTED_DIR_IMPORT]: Directory import`
4
+ import Form from 'react-bootstrap/cjs/Form.js';
5
+ import Button from 'react-bootstrap/cjs/Button.js';
6
+ import ButtonGroup from 'react-bootstrap/cjs/ButtonGroup.js';
7
+ import ToggleButton from 'react-bootstrap/cjs/ToggleButton.js';
8
+ import Modal from 'react-bootstrap/cjs/Modal.js';
9
+ import OverlayTrigger from 'react-bootstrap/cjs/OverlayTrigger.js';
10
+ import Tooltip from 'react-bootstrap/cjs/Tooltip.js';
11
+
12
+ // Pull in custom CSS from vizhub-ui.
13
+ import 'vizhub-ui/dist/vizhub-ui.css';
14
+
15
+ // Pull in Bootstrap Icons CSS.
16
+ import 'bootstrap-icons/font/bootstrap-icons.css';
17
+
18
+ export {
19
+ Form,
20
+ Button,
21
+ ButtonGroup,
22
+ ToggleButton,
23
+ Modal,
24
+ OverlayTrigger,
25
+ Tooltip,
26
+ };
@@ -0,0 +1,3 @@
1
+ // @ts-ignore TODO fix this type error
2
+ import { initWorker } from '@vizhub/runtime/worker';
3
+ initWorker();
@@ -0,0 +1,8 @@
1
+ export const enableLiveKit =
2
+ import.meta.env.VITE_ENABLE_LIVEKIT === 'true';
3
+
4
+ export const enableAIChat = true;
5
+
6
+ export const enableDiffView = true;
7
+
8
+ export const enableAskMode = false;
@@ -0,0 +1,6 @@
1
+ export type RequestId = string;
2
+
3
+ // Generates a random string of digits.
4
+ // e.g. "6891835662438279"
5
+ export const generateRequestId = (): RequestId =>
6
+ (Math.random() + '').slice(2);
@@ -0,0 +1,53 @@
1
+ // Inspired by
2
+
3
+ import { FileTree } from '../types';
4
+ import { VizFiles } from '@vizhub/viz-types';
5
+
6
+ // https://github.com/vizhub-core/vizhub/blob/main/vizhub-v2/packages/neoFrontend/src/pages/VizPage/Body/Editor/FilesSection/FileTree/getFileTree.js
7
+ export const getFileTree = (files: VizFiles): FileTree => {
8
+ const tree: FileTree = { name: 'files' };
9
+ for (const fileId of Object.keys(files)) {
10
+ const file = files[fileId];
11
+ const path = file.name.split('/');
12
+ const n = path.length;
13
+ let node = tree;
14
+
15
+ // Walk the path.
16
+ for (let i = 0; i < n - 1; i++) {
17
+ // Search for an existing child.
18
+ let child: FileTree | undefined;
19
+ const name = path[i];
20
+ if (node.children) {
21
+ for (let j = 0; j < node.children.length; j++) {
22
+ const nodeChild = node.children[j];
23
+ if (nodeChild.name === name) {
24
+ child = nodeChild;
25
+ break;
26
+ }
27
+ }
28
+ }
29
+
30
+ // Create a child if none with matching name exists.
31
+ if (!child) {
32
+ child = {
33
+ name: path[i],
34
+ path: path.slice(0, i + 1).join('/'),
35
+ };
36
+ (node.children || (node.children = [])).push(child);
37
+ }
38
+
39
+ node = child;
40
+ }
41
+
42
+ // file.text being `null` signifies
43
+ // that this is a directory.
44
+ if (file.text !== null) {
45
+ (node.children || (node.children = [])).push({
46
+ name: path[n - 1],
47
+ file: file as any, // Type assertion to resolve compatibility issue
48
+ fileId,
49
+ });
50
+ }
51
+ }
52
+ return tree;
53
+ };
@@ -0,0 +1,19 @@
1
+ export type { ThemeLabel, ThemeOption } from './themes';
2
+ export type { EditorCache } from './useEditorCache';
3
+
4
+ export { VZCodeProvider } from './VZCodeContext';
5
+ export { VZSidebar } from './VZSidebar';
6
+ export { VZSettings } from './VZSettings';
7
+ export { VZKeyboardShortcutsDoc } from './VZKeyboardShortcutsDoc';
8
+ export { VZResizer } from './VZResizer';
9
+ export { VZLeft } from './VZLeft';
10
+ export { VZMiddle } from './VZMiddle';
11
+ export {
12
+ SplitPaneResizeProvider,
13
+ SplitPaneResizeContext,
14
+ } from './SplitPaneResizeContext';
15
+ export { useSubmitOperation } from './useSubmitOperation';
16
+ export { shouldTriggerRun } from './shouldTriggerRun';
17
+ export { mergeFileChanges } from 'editcodewithai';
18
+
19
+ export * from './Icons';
@@ -0,0 +1,10 @@
1
+ import * as React from 'react';
2
+ import * as ReactDOM from 'react-dom/client';
3
+ import App from './App';
4
+ import { BrowserRouter } from 'react-router-dom';
5
+
6
+ ReactDOM.createRoot(document.getElementById('root')).render(
7
+ <BrowserRouter>
8
+ <App />
9
+ </BrowserRouter>,
10
+ );
@@ -0,0 +1,8 @@
1
+ import ColorHash from 'color-hash';
2
+
3
+ export const assignUserColor = (name: string): string => {
4
+ const userColor = new ColorHash({ lightness: 0.75 })
5
+ .rgb(name)
6
+ .join(',');
7
+ return userColor;
8
+ };
@@ -0,0 +1,31 @@
1
+ // This module is responsible for checking
2
+ // keyboard events and determining whether
3
+ // a run should be triggered or not.
4
+ export const shouldTriggerRun = (event: KeyboardEvent) => {
5
+ const isMac = /Mac|iMac|MacBook/i.test(
6
+ navigator.userAgent,
7
+ );
8
+ const ctrlOrCmd = isMac ? event.metaKey : event.ctrlKey;
9
+
10
+ if (ctrlOrCmd && event.key === 's') {
11
+ // Save
12
+ return true;
13
+ } else if (
14
+ event.key === 'F5' || // Run (F5)
15
+ (ctrlOrCmd && event.key === 'Enter') || // Ctrl+Enter or Cmd+Enter
16
+ (event.shiftKey && event.key === 'Enter') || // Shift+Enter
17
+ // Let's not override the browser's default behavior for refresh page.
18
+ // (ctrlOrCmd && event.key === 'r') || // Ctrl+R or Cmd+R
19
+
20
+ (event.altKey && event.key === 'Enter') || // Alt+Enter
21
+ (ctrlOrCmd &&
22
+ event.shiftKey &&
23
+ (event.key === 'B' || event.key === 'b')) || // Ctrl+Shift+B or Cmd+Shift+B
24
+ (ctrlOrCmd && event.shiftKey && event.key === 'F10') || // Ctrl+Shift+F10 or Cmd+Shift+F10
25
+ event.key === 'F8' ||
26
+ event.key === 'F9'
27
+ ) {
28
+ return true;
29
+ }
30
+ return false;
31
+ };
@@ -0,0 +1,21 @@
1
+ // Recursively sorts the file tree in-place.
2
+ // Sorts first to group directories before files.
3
+ // Sorts second alphabetically.
4
+
5
+ import { FileTree, FileTreeFile } from '../types';
6
+
7
+ // From https://github.com/vizhub-core/vizhub/blob/main/vizhub-v2/packages/neoFrontend/src/pages/VizPage/Body/Editor/FilesSection/FileTree/sortFileTree.js
8
+ export const sortFileTree = (fileTree: FileTree) => {
9
+ if (fileTree.children) {
10
+ fileTree.children.sort((a, b) => {
11
+ const aIsFile = (a as FileTreeFile).file ? 1 : 0;
12
+ const bIsFile = (b as FileTreeFile).file ? 1 : 0;
13
+ // Directories before files.
14
+ return (
15
+ aIsFile - bIsFile || a.name.localeCompare(b.name)
16
+ );
17
+ });
18
+ fileTree.children.forEach(sortFileTree);
19
+ }
20
+ return fileTree;
21
+ };
@@ -0,0 +1,79 @@
1
+ import { describe, expect, test } from 'vitest';
2
+ import {
3
+ encodeTabs,
4
+ decodeTabs,
5
+ delimiter,
6
+ } from './tabsSearchParameters';
7
+ import { VizContent } from '@vizhub/viz-types';
8
+
9
+ describe('tabsSearchParameters', () => {
10
+ test.each([
11
+ {
12
+ name: 'empty',
13
+ tabStateParams: {},
14
+ tabList: [],
15
+ activeFileId: null,
16
+ },
17
+ {
18
+ name: 'single active tab',
19
+ tabStateParams: {
20
+ file: 'index.html',
21
+ },
22
+
23
+ tabList: [{ fileId: '789' }],
24
+ activeFileId: '789',
25
+ },
26
+ {
27
+ name: 'multiple tabs, active tab first',
28
+ tabStateParams: {
29
+ file: 'index.html',
30
+ tabs: `index.html${delimiter}README.md${delimiter}index.js`,
31
+ },
32
+ tabList: [
33
+ { fileId: '789' },
34
+ { fileId: '456' },
35
+ { fileId: '123' },
36
+ ],
37
+ activeFileId: '789',
38
+ },
39
+ {
40
+ name: 'multiple tabs, active tab in middle',
41
+ tabStateParams: {
42
+ file: 'README.md',
43
+ tabs: `index.html${delimiter}README.md${delimiter}index.js`,
44
+ },
45
+ tabList: [
46
+ { fileId: '789' },
47
+ { fileId: '456' },
48
+ { fileId: '123' },
49
+ ],
50
+ activeFileId: '456',
51
+ },
52
+ ])(
53
+ 'round trip: $name',
54
+ ({ tabStateParams, tabList, activeFileId }) => {
55
+ // Fake Content object
56
+ const content: VizContent = {
57
+ id: 'test-viz-id', // Add required id property
58
+ files: {
59
+ '123': { name: 'index.js', text: 'abc' },
60
+ '456': { name: 'README.md', text: 'def' },
61
+ '789': { name: 'index.html', text: 'ghi' },
62
+ },
63
+ };
64
+
65
+ const decoded = decodeTabs({
66
+ tabStateParams,
67
+ content,
68
+ });
69
+ const expected = { tabList, activeFileId };
70
+ expect(decoded).toStrictEqual(expected);
71
+ const encoded = encodeTabs({
72
+ tabList,
73
+ activeFileId,
74
+ content,
75
+ });
76
+ expect(encoded).toEqual(tabStateParams);
77
+ },
78
+ );
79
+ });