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,282 @@
1
+ import {
2
+ useCallback,
3
+ useContext,
4
+ useState,
5
+ useRef,
6
+ useEffect,
7
+ } from 'react';
8
+ import { VZCodeContext } from '../VZCodeContext';
9
+ import { VizContent, VizFileId } from '@vizhub/viz-types';
10
+ import { VisualEditorConfigEntry } from '../../types';
11
+ import { EmptyState } from './EmptyState';
12
+
13
+ const CONFIG_FILE_NAME = 'config.json';
14
+
15
+ // Helper function to calculate percentage for progress fill
16
+ const calculatePercentage = (
17
+ value: number,
18
+ min: number,
19
+ max: number,
20
+ ) => {
21
+ return ((value - min) / (max - min)) * 100;
22
+ };
23
+
24
+ // Helper function to format values nicely
25
+ const formatValue = (
26
+ value: number,
27
+ min: number,
28
+ max: number,
29
+ ) => {
30
+ // Determine decimal places based on the range
31
+ const range = max - min;
32
+ const decimalPlaces =
33
+ range < 10 ? 2 : range < 100 ? 1 : 0;
34
+ return Number(value).toFixed(decimalPlaces);
35
+ };
36
+
37
+ export const VisualEditor = () => {
38
+ const { files, submitOperation, iframeRef } =
39
+ useContext(VZCodeContext);
40
+
41
+ // Local state to track slider values during user interaction
42
+ const [localValues, setLocalValues] = useState<{
43
+ [key: string]: number;
44
+ }>({});
45
+
46
+ let configFileId: VizFileId | null = null;
47
+ for (const fileId in files) {
48
+ if (files[fileId].name === CONFIG_FILE_NAME) {
49
+ configFileId = fileId;
50
+ break;
51
+ }
52
+ }
53
+
54
+ if (configFileId === null) {
55
+ return (
56
+ <EmptyState>
57
+ To begin using the visual editor, create a
58
+ config.json.
59
+ </EmptyState>
60
+ );
61
+ }
62
+
63
+ let configData;
64
+
65
+ try {
66
+ configData = JSON.parse(files[configFileId].text);
67
+ } catch (error) {
68
+ return (
69
+ <EmptyState>
70
+ Your config.json file is not valid json.
71
+ </EmptyState>
72
+ );
73
+ }
74
+
75
+ if (!('visualEditorWidgets' in configData)) {
76
+ return (
77
+ <EmptyState>
78
+ To begin using the visual editor, make sure your
79
+ config.json has a key called "visualEditorWidgets",
80
+ whose value is the config for the visual editor.
81
+ </EmptyState>
82
+ );
83
+ }
84
+
85
+ if (!Array.isArray(configData.visualEditorWidgets)) {
86
+ return (
87
+ <EmptyState>
88
+ Your config.json file has "visualEditorWidgets" but
89
+ it is not an array. Please make sure
90
+ "visualEditorWidgets" is an array of widget
91
+ configurations.
92
+ </EmptyState>
93
+ );
94
+ }
95
+
96
+ const onSliderChange = useCallback(
97
+ (property: string) =>
98
+ (event: React.ChangeEvent<HTMLInputElement>) => {
99
+ const newValue = parseFloat(
100
+ event.currentTarget.value,
101
+ );
102
+
103
+ // Update local state immediately for responsive UI
104
+ setLocalValues((prev) => ({
105
+ ...prev,
106
+ [property]: newValue,
107
+ }));
108
+
109
+ // Update config.json
110
+ const newConfigData = {
111
+ ...configData,
112
+ [property]: newValue,
113
+ };
114
+
115
+ submitOperation((document: VizContent) => ({
116
+ ...document,
117
+ files: {
118
+ ...files,
119
+ [configFileId]: {
120
+ name: 'config.json',
121
+ text: JSON.stringify(newConfigData, null, 2),
122
+ },
123
+ },
124
+ }));
125
+ },
126
+ [configData, files, configFileId, setLocalValues],
127
+ );
128
+
129
+ const visualEditorWidgets: VisualEditorConfigEntry[] =
130
+ configData.visualEditorWidgets;
131
+
132
+ // Sync local values with config data when it changes (including remote updates)
133
+ useEffect(() => {
134
+ const newLocalValues: { [key: string]: number } = {};
135
+ visualEditorWidgets.forEach((widget) => {
136
+ if (widget.type === 'number') {
137
+ newLocalValues[widget.property] =
138
+ configData[widget.property];
139
+ }
140
+ });
141
+ setLocalValues(newLocalValues);
142
+ }, [configData, visualEditorWidgets]);
143
+
144
+ // Track previous config state to detect changes from any source (remote clients, text editor, etc.)
145
+ const previousConfigRef = useRef<any>(null);
146
+
147
+ // Detect config.json changes and send updates to iframe
148
+ useEffect(() => {
149
+ if (!configFileId || !files || !files[configFileId]) {
150
+ return;
151
+ }
152
+
153
+ let newConfigData;
154
+ try {
155
+ newConfigData = JSON.parse(files[configFileId].text);
156
+ } catch (error) {
157
+ // If config is invalid JSON, we can't process changes
158
+ return;
159
+ }
160
+
161
+ const previousConfig = previousConfigRef.current;
162
+
163
+ // Update the ref with the new config
164
+ previousConfigRef.current = newConfigData;
165
+
166
+ // Skip processing if this is the first time or if there's no previous config
167
+ if (!previousConfig) {
168
+ return;
169
+ }
170
+
171
+ // Find changed top-level properties
172
+ const changedProperties: { [key: string]: any } = {};
173
+
174
+ // Check all properties in the new config
175
+ for (const key in newConfigData) {
176
+ if (newConfigData[key] !== previousConfig[key]) {
177
+ // Deep comparison for objects to detect actual changes
178
+ if (
179
+ typeof newConfigData[key] === 'object' &&
180
+ typeof previousConfig[key] === 'object'
181
+ ) {
182
+ if (
183
+ JSON.stringify(newConfigData[key]) !==
184
+ JSON.stringify(previousConfig[key])
185
+ ) {
186
+ changedProperties[key] = newConfigData[key];
187
+ }
188
+ } else {
189
+ changedProperties[key] = newConfigData[key];
190
+ }
191
+ }
192
+ }
193
+
194
+ // Check for deleted properties (properties that existed before but don't exist now)
195
+ for (const key in previousConfig) {
196
+ if (!(key in newConfigData)) {
197
+ changedProperties[key] = undefined;
198
+ }
199
+ }
200
+
201
+ // Send changed properties to iframe if any changes were detected
202
+ if (Object.keys(changedProperties).length > 0) {
203
+ try {
204
+ iframeRef.current.contentWindow.postMessage(
205
+ changedProperties,
206
+ );
207
+ } catch (error) {
208
+ console.error(
209
+ 'Failed to send config changes to iframe:',
210
+ error,
211
+ );
212
+ }
213
+ }
214
+ }, [files, configFileId, iframeRef]);
215
+
216
+ return (
217
+ <div className="visual-editor">
218
+ {visualEditorWidgets.map((widgetConfig, index) => {
219
+ if (widgetConfig.type === 'number') {
220
+ // Use local value if available, otherwise fall back to config value
221
+ const currentValue =
222
+ localValues[widgetConfig.property] ??
223
+ configData[widgetConfig.property];
224
+ const percentage = calculatePercentage(
225
+ currentValue,
226
+ widgetConfig.min,
227
+ widgetConfig.max,
228
+ );
229
+
230
+ return (
231
+ <div
232
+ key={widgetConfig.property}
233
+ className="visual-editor-slider"
234
+ >
235
+ <div className="slider-header">
236
+ <label
237
+ htmlFor={widgetConfig.property}
238
+ className="slider-label"
239
+ >
240
+ {widgetConfig.label}
241
+ </label>
242
+ <span className="slider-value">
243
+ {formatValue(
244
+ currentValue,
245
+ widgetConfig.min,
246
+ widgetConfig.max,
247
+ )}
248
+ </span>
249
+ </div>
250
+ <div className="slider-container">
251
+ <input
252
+ type="range"
253
+ id={widgetConfig.property}
254
+ className="slider-input"
255
+ min={widgetConfig.min}
256
+ max={widgetConfig.max}
257
+ step="any"
258
+ value={currentValue}
259
+ onChange={onSliderChange(
260
+ widgetConfig.property,
261
+ )}
262
+ />
263
+ <div
264
+ className="slider-track-fill"
265
+ style={{ width: `${percentage}%` }}
266
+ />
267
+ </div>
268
+ <div className="slider-bounds">
269
+ <span className="min-value">
270
+ {widgetConfig.min}
271
+ </span>
272
+ <span className="max-value">
273
+ {widgetConfig.max}
274
+ </span>
275
+ </div>
276
+ </div>
277
+ );
278
+ }
279
+ })}
280
+ </div>
281
+ );
282
+ };
@@ -0,0 +1,121 @@
1
+ import {
2
+ ParticipantLoop,
3
+ ParticipantName,
4
+ useParticipants,
5
+ useRoomContext,
6
+ } from '@livekit/components-react';
7
+ import { useContext, useState } from 'react';
8
+ import { Button, Form, Modal } from 'react-bootstrap';
9
+ import { v4 } from 'uuid';
10
+ import { VZCodeContext } from '../VZCodeContext';
11
+
12
+ export function VoiceChatModal() {
13
+ const {
14
+ liveKitRoomName,
15
+ setLiveKitRoom,
16
+ liveKitToken,
17
+ setLiveKitToken,
18
+ liveKitConnection,
19
+ setLiveKitConnection,
20
+ voiceChatModalOpen,
21
+ setVoiceChatModalOpen,
22
+ } = useContext(VZCodeContext);
23
+ const participants = useParticipants();
24
+
25
+ const [roomName, setRoomName] = useState(
26
+ liveKitRoomName || '',
27
+ );
28
+ const [username, setUsername] = useState('');
29
+ const room = useRoomContext();
30
+
31
+ const handleConnect = () => {
32
+ if (liveKitConnection) {
33
+ setLiveKitConnection(false);
34
+ setLiveKitRoom(`${v4()}`);
35
+ setLiveKitToken('');
36
+ room.disconnect(true);
37
+ return;
38
+ }
39
+ setLiveKitRoom(roomName);
40
+ const fetchToken = async () => {
41
+ try {
42
+ const res = await fetch(
43
+ `/livekit-token?room=${roomName}&username=${username}`,
44
+ {
45
+ method: 'GET',
46
+ },
47
+ );
48
+ const tokenResponse = await res.text();
49
+ setLiveKitToken(tokenResponse);
50
+ setLiveKitConnection(true);
51
+ } catch (error) {
52
+ console.error('Error fetching token:', error);
53
+ }
54
+ };
55
+
56
+ fetchToken();
57
+ };
58
+
59
+ const handleClose = () => {
60
+ setVoiceChatModalOpen(false);
61
+ };
62
+
63
+ return (
64
+ <Modal
65
+ className="vz-settings"
66
+ show={voiceChatModalOpen}
67
+ onHide={handleClose}
68
+ animation={false}
69
+ >
70
+ <Modal.Header closeButton>
71
+ <Modal.Title>Connect to LiveKit Room</Modal.Title>
72
+ </Modal.Header>
73
+ <Modal.Body>
74
+ <Form>
75
+ <Form.Group controlId="formRoomName">
76
+ <Form.Label>Room Name</Form.Label>
77
+ <Form.Control
78
+ type="text"
79
+ placeholder="Enter room name"
80
+ value={roomName}
81
+ onChange={(e) => setRoomName(e.target.value)}
82
+ />
83
+ </Form.Group>
84
+ </Form>
85
+ <Form>
86
+ <Form.Group controlId="formUserName">
87
+ <Form.Label>Username</Form.Label>
88
+ <Form.Control
89
+ type="text"
90
+ placeholder="Enter Username"
91
+ value={username}
92
+ onChange={(e) => setUsername(e.target.value)}
93
+ />
94
+ </Form.Group>
95
+ </Form>
96
+ <div
97
+ style={{
98
+ display: 'flex',
99
+ flexDirection: 'column',
100
+ }}
101
+ >
102
+ <b>Users in Room: </b>
103
+ <ParticipantLoop participants={participants}>
104
+ <ParticipantName />
105
+ </ParticipantLoop>
106
+ </div>
107
+ </Modal.Body>
108
+ <Modal.Footer>
109
+ <Button variant="secondary" onClick={handleClose}>
110
+ Cancel
111
+ </Button>
112
+ <Button
113
+ variant={liveKitConnection ? 'danger' : 'primary'}
114
+ onClick={handleConnect}
115
+ >
116
+ {liveKitConnection ? 'Disconnect' : 'Connect'}
117
+ </Button>
118
+ </Modal.Footer>
119
+ </Modal>
120
+ );
121
+ }
@@ -0,0 +1,199 @@
1
+ import {
2
+ formatMarkdownFiles,
3
+ parseMarkdownFiles,
4
+ } from 'llm-code-format';
5
+ import {
6
+ FORMAT_INSTRUCTIONS,
7
+ mergeFileChanges,
8
+ } from 'editcodewithai';
9
+ import { VizFiles } from '@vizhub/viz-types';
10
+ import {
11
+ generateRunId,
12
+ vizFilesToFileCollection,
13
+ } from '@vizhub/viz-utils';
14
+ import JSZip from 'jszip';
15
+ import { generate } from '@langchain/core/dist/utils/fast-json-patch';
16
+
17
+ export const createAICopyPasteHandlers = (
18
+ files: VizFiles,
19
+ submitOperation: any,
20
+ setCopyButtonText: (text: string) => void,
21
+ setPasteButtonText: (text: string) => void,
22
+ setExportButtonText: (text: string) => void,
23
+ ) => {
24
+ // Copy for AI - formats all files and copies to clipboard
25
+ const handleCopyForAI = async () => {
26
+ if (!files || Object.keys(files).length === 0) {
27
+ setCopyButtonText('No files to copy');
28
+ setTimeout(
29
+ () => setCopyButtonText('Copy for AI'),
30
+ 2000,
31
+ );
32
+ return;
33
+ }
34
+
35
+ try {
36
+ const fileCollection =
37
+ vizFilesToFileCollection(files);
38
+
39
+ // Format files for AI consumption
40
+ const formattedFiles =
41
+ formatMarkdownFiles(fileCollection) +
42
+ '\n\n' +
43
+ FORMAT_INSTRUCTIONS.whole;
44
+
45
+ // Copy to clipboard
46
+ await navigator.clipboard.writeText(formattedFiles);
47
+
48
+ // Show success feedback
49
+ setCopyButtonText('Copied!');
50
+ setTimeout(
51
+ () => setCopyButtonText('Copy for AI'),
52
+ 2000,
53
+ );
54
+ } catch (error) {
55
+ console.error('Failed to copy files for AI:', error);
56
+ setCopyButtonText('Error');
57
+ setTimeout(
58
+ () => setCopyButtonText('Copy for AI'),
59
+ 2000,
60
+ );
61
+ }
62
+ };
63
+
64
+ // Paste for AI - parses clipboard content and updates files
65
+ const handlePasteForAI = async () => {
66
+ if (!submitOperation) return;
67
+
68
+ try {
69
+ // Read from clipboard
70
+ const clipboardText =
71
+ await navigator.clipboard.readText();
72
+
73
+ // Handle various line endings
74
+ const normalized = clipboardText.replace(
75
+ /\r\n?/g,
76
+ '\n',
77
+ );
78
+
79
+ if (!normalized.trim()) {
80
+ setPasteButtonText('Empty clipboard');
81
+ setTimeout(
82
+ () => setPasteButtonText('Paste for AI'),
83
+ 2000,
84
+ );
85
+ return;
86
+ }
87
+
88
+ // Preprocess to remove formatting instructions section to avoid creating extra files
89
+ const preprocessed = normalized
90
+ .split('## Formatting Instructions')[0]
91
+ .trim();
92
+
93
+ // Parse the markdown files format
94
+ const parsed = parseMarkdownFiles(
95
+ preprocessed,
96
+ 'bold',
97
+ );
98
+
99
+ if (
100
+ parsed.files &&
101
+ Object.keys(parsed.files).length > 0
102
+ ) {
103
+ // Use mergeFileChanges utility from editcodewithai
104
+ const mergedFiles = mergeFileChanges(
105
+ files || {},
106
+ parsed.files,
107
+ );
108
+
109
+ // Update files using the submit operation
110
+ submitOperation((document) => ({
111
+ ...document,
112
+ files: mergedFiles,
113
+ runId: generateRunId(),
114
+ }));
115
+
116
+ const fileCount = Object.keys(parsed.files).length;
117
+ setPasteButtonText('Pasted!');
118
+ setTimeout(
119
+ () => setPasteButtonText('Paste for AI'),
120
+ 2000,
121
+ );
122
+ } else {
123
+ setPasteButtonText('No files found');
124
+ setTimeout(
125
+ () => setPasteButtonText('Paste for AI'),
126
+ 2000,
127
+ );
128
+ }
129
+ } catch (error) {
130
+ console.error(
131
+ 'Failed to paste files from AI:',
132
+ error,
133
+ );
134
+ setPasteButtonText('Error');
135
+ setTimeout(
136
+ () => setPasteButtonText('Paste for AI'),
137
+ 2000,
138
+ );
139
+ }
140
+ };
141
+
142
+ // Export to ZIP - creates and downloads a ZIP file with all viz files
143
+ const handleExportToZip = async () => {
144
+ if (!files || Object.keys(files).length === 0) {
145
+ setExportButtonText('No files to export');
146
+ setTimeout(
147
+ () => setExportButtonText('Export to ZIP'),
148
+ 2000,
149
+ );
150
+ return;
151
+ }
152
+
153
+ try {
154
+ // Create the ZIP
155
+ const zip = new JSZip();
156
+
157
+ // Add each file to the ZIP
158
+ Object.entries(files).forEach(([fileId, file]) => {
159
+ const fileName = file.name || fileId;
160
+ const fileContent = file.text || '';
161
+ zip.file(fileName, fileContent);
162
+ });
163
+
164
+ // Generate and download
165
+ const blob = await zip.generateAsync({
166
+ type: 'blob',
167
+ });
168
+ const url = URL.createObjectURL(blob);
169
+ const a = document.createElement('a');
170
+ a.href = url;
171
+ a.download = 'viz-files.zip';
172
+ a.click();
173
+ URL.revokeObjectURL(url);
174
+
175
+ // Show success feedback
176
+ setExportButtonText('Exported!');
177
+ setTimeout(
178
+ () => setExportButtonText('Export to ZIP'),
179
+ 2000,
180
+ );
181
+ } catch (error) {
182
+ console.error(
183
+ 'Failed to export files to ZIP:',
184
+ error,
185
+ );
186
+ setExportButtonText('Error');
187
+ setTimeout(
188
+ () => setExportButtonText('Export to ZIP'),
189
+ 2000,
190
+ );
191
+ }
192
+ };
193
+
194
+ return {
195
+ handleCopyForAI,
196
+ handlePasteForAI,
197
+ handleExportToZip,
198
+ };
199
+ };