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,602 @@
1
+ import {
2
+ useCallback,
3
+ useMemo,
4
+ useReducer,
5
+ useRef,
6
+ useState,
7
+ } from 'react';
8
+ import { VizContent } from '@vizhub/viz-types';
9
+ import { defaultTheme, useDynamicTheme } from '../themes';
10
+ import { useActions } from '../useActions';
11
+ import { useEditorCache } from '../useEditorCache';
12
+ import { useFileCRUD } from '../useFileCRUD';
13
+ import { useKeyboardShortcuts } from '../useKeyboardShortcuts';
14
+ import { useOpenDirectories } from '../useOpenDirectories';
15
+ import { usePrettier } from '../usePrettier';
16
+ import { useRunCode } from '../useRunCode';
17
+ import { useRuntimeError } from '../useRuntimeError';
18
+ import { useURLSync } from '../useURLSync';
19
+ import {
20
+ createInitialState,
21
+ vzReducer,
22
+ } from '../vzReducer';
23
+ import { findPane } from '../vzReducer/findPane';
24
+ import { usePresenceAutoFollow } from '../usePresenceAutoFollow';
25
+ import { v4 as uuidv4 } from 'uuid';
26
+ import {
27
+ VZCodeContextValue,
28
+ VZCodeProviderProps,
29
+ } from './types';
30
+
31
+ export const useVZCodeState = ({
32
+ content,
33
+ shareDBDoc,
34
+ submitOperation,
35
+ localPresence,
36
+ docPresence,
37
+ prettierWorker,
38
+ initialUsername,
39
+ codeError = null,
40
+ connected,
41
+ pending,
42
+ liveKitToken,
43
+ setLiveKitToken,
44
+ liveKitRoomName,
45
+ setLiveKitRoom,
46
+ liveKitConnection,
47
+ setLiveKitConnection,
48
+ aiChatEndpoint,
49
+ aiChatOptions,
50
+ autoForkAndRetryAI,
51
+ clearStoredAIPrompt,
52
+ getStoredAIPrompt,
53
+ additionalWidgets,
54
+ iframeRef: externalIframeRef,
55
+ handleChatError,
56
+ }: Omit<
57
+ VZCodeProviderProps,
58
+ 'children'
59
+ >): VZCodeContextValue => {
60
+ // Auto-run Prettier after local changes.
61
+ const { prettierError, runPrettierRef } = usePrettier({
62
+ shareDBDoc,
63
+ submitOperation,
64
+ prettierWorker,
65
+ });
66
+
67
+ // Handle runtime errors from the iframe
68
+ const {
69
+ runtimeError,
70
+ handleRuntimeError,
71
+ clearRuntimeError,
72
+ } = useRuntimeError();
73
+
74
+ const runCodeRef = useRunCode(submitOperation);
75
+
76
+ const sidebarRef = useRef(null);
77
+
78
+ const codeEditorRef = useRef(null);
79
+
80
+ // Use external iframeRef if provided, otherwise create our own
81
+ const iframeRef = externalIframeRef || useRef(null);
82
+
83
+ // The error message shows errors in order of priority:
84
+ // * `runtimeError` - errors from runtime execution, highest priority
85
+ // * `prettierError` - errors from Prettier, client-side only
86
+ // * `codeError` - errors from an external source, such as
87
+ // build-time errors or intercepted runtime errors.
88
+ // Runtime errors are prioritized since they indicate immediate execution issues.
89
+ // Prettier errors are next since they surface syntax errors that are likely
90
+ // to be useful to the user.
91
+ const errorMessage: string | null = runtimeError
92
+ ? runtimeError
93
+ : prettierError
94
+ ? prettierError
95
+ : codeError;
96
+
97
+ // Set up the reducer that manages much of the application state.
98
+ // See https://react.dev/reference/react/useReducer
99
+ const [state, dispatch] = useReducer(
100
+ vzReducer,
101
+ { defaultTheme, initialUsername },
102
+ createInitialState,
103
+ );
104
+
105
+ // Unpack state.
106
+ const {
107
+ pane,
108
+ activePaneId,
109
+ theme,
110
+ search,
111
+ isSearchOpen,
112
+ isVisualEditorOpen,
113
+ isAIChatOpen,
114
+ aiChatFocused,
115
+ isSettingsOpen,
116
+ isDocOpen,
117
+ editorWantsFocus,
118
+ username,
119
+ enableAutoFollow,
120
+ sidebarPresenceIndicators,
121
+ aiChatMode,
122
+ } = state;
123
+
124
+ const activePane = findPane(pane, activePaneId);
125
+ if (activePane.type !== 'leafPane') {
126
+ // Should never happen
127
+ throw new Error('Expected leafPane');
128
+ }
129
+
130
+ // Functions for dispatching actions to the reducer.
131
+ const {
132
+ setActiveFileId,
133
+ setActiveFileLeft,
134
+ setActiveFileRight,
135
+ openTab,
136
+ closeTabs,
137
+ setTheme,
138
+ setIsSearchOpen,
139
+ setSearch,
140
+ setSearchResults,
141
+ setSearchFileVisibility,
142
+ setSearchLineVisibility,
143
+ setSearchFocusedIndex,
144
+ toggleSearchFocused,
145
+ setIsVisualEditorOpen,
146
+ setIsAIChatOpen,
147
+ toggleAIChatFocused,
148
+ setAIChatMode,
149
+ setIsSettingsOpen,
150
+ setIsDocOpen,
151
+ closeSettings,
152
+ closeDoc,
153
+ editorNoLongerWantsFocus,
154
+ setUsername,
155
+ toggleAutoFollow,
156
+ updatePresenceIndicator,
157
+ splitCurrentPane,
158
+ } = useActions(dispatch);
159
+
160
+ // Sync tab state to the URL.
161
+ useURLSync({
162
+ content,
163
+ openTab,
164
+ setActiveFileId,
165
+ tabList: activePane.tabList,
166
+ activeFileId: activePane.activeFileId,
167
+ });
168
+
169
+ // The set of open directories.
170
+ const { isDirectoryOpen, toggleDirectory } =
171
+ useOpenDirectories({ activePane, content });
172
+
173
+ // Cache of CodeMirror editors by file id.
174
+ const editorCache = useEditorCache();
175
+
176
+ // Handle dynamic theme changes.
177
+ useDynamicTheme(editorCache, theme);
178
+
179
+ // Handle file CRUD operations (Create, Read, Update, Delete)
180
+ const {
181
+ createFile,
182
+ renameFile,
183
+ deleteFile,
184
+ createDirectory,
185
+ renameDirectory,
186
+ deleteDirectory,
187
+ deleteAllFiles,
188
+ } = useFileCRUD({
189
+ submitOperation,
190
+ closeTabs,
191
+ openTab,
192
+ editorCache,
193
+ content,
194
+ });
195
+
196
+ // State to control the create file modal's visibility
197
+ const [isCreateFileModalOpen, setIsCreateFileModalOpen] =
198
+ useState(false);
199
+
200
+ const handleOpenCreateFileModal = useCallback(() => {
201
+ setIsCreateFileModalOpen(true);
202
+ }, []);
203
+
204
+ const handleCloseCreateFileModal = useCallback(() => {
205
+ setIsCreateFileModalOpen(false);
206
+ }, []);
207
+
208
+ const handleCreateFileClick = useCallback(
209
+ (newFileName: string) => {
210
+ createFile(newFileName);
211
+ setIsCreateFileModalOpen(false);
212
+ },
213
+ [createFile, setIsCreateFileModalOpen],
214
+ );
215
+
216
+ // State to control the create directory modal's visibility
217
+ const [isCreateDirModalOpen, setIsCreateDirModalOpen] =
218
+ useState(false);
219
+
220
+ const handleOpenCreateDirModal = useCallback(() => {
221
+ setIsCreateDirModalOpen(true);
222
+ }, []);
223
+
224
+ const handleCloseCreateDirModal = useCallback(() => {
225
+ setIsCreateDirModalOpen(false);
226
+ }, []);
227
+
228
+ const handleCreateDirClick = useCallback(
229
+ (newDirName: string) => {
230
+ createDirectory(newDirName);
231
+ setIsCreateDirModalOpen(false);
232
+ },
233
+ [createDirectory, setIsCreateDirModalOpen],
234
+ );
235
+
236
+ // Isolate the files object from the document.
237
+ const files = content ? content.files : null;
238
+
239
+ useKeyboardShortcuts({
240
+ closeTabs,
241
+ activeFileId: activePane.activeFileId,
242
+ activePaneId,
243
+ handleOpenCreateFileModal,
244
+ setActiveFileLeft,
245
+ setActiveFileRight,
246
+ toggleSearchFocused,
247
+ runPrettierRef,
248
+ runCodeRef,
249
+ sidebarRef,
250
+ editorCache,
251
+ codeEditorRef,
252
+ });
253
+
254
+ // Track the currently hovered file id.
255
+ const [hoveredItemId, setHoveredItemId] = useState(null);
256
+
257
+ // Handle presence-based auto-following
258
+ usePresenceAutoFollow({
259
+ docPresence,
260
+ enableAutoFollow,
261
+ openTab,
262
+ activePane,
263
+ });
264
+
265
+ // Livekit Voice Chat Modal
266
+ const [voiceChatModalOpen, setVoiceChatModalOpen] =
267
+ useState(false);
268
+
269
+ const [aiChatMessage, setAIChatMessage] = useState('');
270
+
271
+ const DEBUG = false;
272
+
273
+ // Compute isLoading based on the current chat's aiStatus
274
+ const [currentChatId] = useState(() => uuidv4());
275
+ const [selectedChatId, setSelectedChatId] = useState<
276
+ string | null
277
+ >(null);
278
+
279
+ // Use selectedChatId if available, otherwise use currentChatId for backward compatibility
280
+ const activeChatId = selectedChatId || currentChatId;
281
+ const currentChat = content?.chats?.[activeChatId];
282
+ const isLoading = currentChat?.aiStatus === 'generating';
283
+
284
+ // Message history for up/down arrow navigation - using ShareDB document data
285
+ const messageHistory = useMemo(() => {
286
+ if (!currentChat?.messages) return [];
287
+ // Extract user messages in chronological order for history navigation
288
+ return currentChat.messages
289
+ .filter((msg) => msg.role === 'user')
290
+ .map((msg) => msg.content);
291
+ }, [currentChat?.messages]);
292
+
293
+ const [historyIndex, setHistoryIndex] = useState(-1);
294
+ const [currentDraft, setCurrentDraft] = useState('');
295
+
296
+ const [aiErrorMessage, setAIErrorMessageState] = useState<
297
+ string | null
298
+ >(null);
299
+
300
+ // Create the combined setAIErrorMessage function that calls both state setter and external callback
301
+ const setAIErrorMessage = useCallback(
302
+ (error: string | null) => {
303
+ setAIErrorMessageState(error);
304
+ if (error && handleChatError) {
305
+ handleChatError(error);
306
+ }
307
+ },
308
+ [handleChatError],
309
+ );
310
+
311
+ // Message history navigation functions
312
+ const navigateMessageHistoryUp = useCallback(() => {
313
+ if (messageHistory.length === 0) return;
314
+
315
+ // If we're not navigating history, save current draft
316
+ if (historyIndex === -1) {
317
+ setCurrentDraft(aiChatMessage);
318
+ }
319
+
320
+ const newIndex =
321
+ historyIndex < messageHistory.length - 1
322
+ ? historyIndex + 1
323
+ : historyIndex;
324
+ setHistoryIndex(newIndex);
325
+ setAIChatMessage(
326
+ messageHistory[messageHistory.length - 1 - newIndex],
327
+ );
328
+ }, [messageHistory, historyIndex, aiChatMessage]);
329
+
330
+ const navigateMessageHistoryDown = useCallback(() => {
331
+ if (historyIndex === -1) return;
332
+
333
+ const newIndex = historyIndex - 1;
334
+ if (newIndex === -1) {
335
+ // Return to draft
336
+ setHistoryIndex(-1);
337
+ setAIChatMessage(currentDraft);
338
+ setCurrentDraft('');
339
+ } else {
340
+ setHistoryIndex(newIndex);
341
+ setAIChatMessage(
342
+ messageHistory[
343
+ messageHistory.length - 1 - newIndex
344
+ ],
345
+ );
346
+ }
347
+ }, [messageHistory, historyIndex, currentDraft]);
348
+
349
+ const resetMessageHistoryNavigation = useCallback(() => {
350
+ if (historyIndex !== -1) {
351
+ setHistoryIndex(-1);
352
+ setCurrentDraft('');
353
+ }
354
+ }, [historyIndex]);
355
+
356
+ const handleSendMessage = useCallback(
357
+ async (
358
+ messageToSend?: string,
359
+ options?: Record<string, string>,
360
+ ) => {
361
+ DEBUG &&
362
+ console.log(
363
+ 'AIChat: handleSendMessage called with:',
364
+ messageToSend,
365
+ 'aiChatMessage:',
366
+ aiChatMessage,
367
+ );
368
+ const messageContent = messageToSend || aiChatMessage;
369
+
370
+ if (
371
+ !messageContent ||
372
+ typeof messageContent !== 'string' ||
373
+ !messageContent.trim() ||
374
+ isLoading
375
+ ) {
376
+ return;
377
+ }
378
+
379
+ const currentPrompt = aiChatMessage.trim();
380
+ setAIChatMessage('');
381
+
382
+ // Reset history navigation when sending a message
383
+ setHistoryIndex(-1);
384
+ setCurrentDraft('');
385
+
386
+ setAIErrorMessage(null); // Clear any previous errors
387
+
388
+ // Call backend endpoint for AI response
389
+ try {
390
+ const response = await fetch(aiChatEndpoint, {
391
+ method: 'POST',
392
+ headers: {
393
+ 'Content-Type': 'application/json',
394
+ },
395
+ body: JSON.stringify({
396
+ ...aiChatOptions,
397
+ content: messageContent.trim(),
398
+ chatId: activeChatId,
399
+ mode: aiChatMode,
400
+ ...options,
401
+ }),
402
+ });
403
+
404
+ if (!response.ok) {
405
+ throw new Error(
406
+ `HTTP error! status: ${response.status}`,
407
+ );
408
+ }
409
+
410
+ // Parse the response to check for errors
411
+ const responseData = await response.json();
412
+
413
+ // Check if the response contains a VizHub error
414
+ if (
415
+ responseData.outcome === 'failure' &&
416
+ responseData.error
417
+ ) {
418
+ const errorMessage = responseData.error.message;
419
+
420
+ // Check if this is the specific permission error that should trigger auto-fork
421
+ if (
422
+ errorMessage ===
423
+ '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.'
424
+ ) {
425
+ // Trigger auto-fork instead of showing error
426
+ try {
427
+ await autoForkAndRetryAI?.(
428
+ currentPrompt,
429
+ aiChatMode,
430
+ );
431
+ // If we reach here, the fork was successful and redirect should happen
432
+ return;
433
+ } catch (forkError) {
434
+ console.error('Auto-fork failed:', forkError);
435
+ setAIErrorMessage(
436
+ 'Failed to fork visualization. Please try forking manually.',
437
+ );
438
+ return;
439
+ }
440
+ }
441
+
442
+ // For other errors, show the error message
443
+ setAIErrorMessage(errorMessage);
444
+ return;
445
+ }
446
+
447
+ // The backend handles all ShareDB operations for successful responses
448
+ // The loading state is now managed via ShareDB aiStatus
449
+ } catch (error) {
450
+ console.error('Error getting AI response:', error);
451
+ setAIErrorMessage(
452
+ 'Failed to send message. Please try again.',
453
+ );
454
+ }
455
+ },
456
+ [
457
+ aiChatMessage,
458
+ isLoading,
459
+ aiChatEndpoint,
460
+ aiChatOptions,
461
+ activeChatId,
462
+ aiChatMode,
463
+ autoForkAndRetryAI,
464
+ ],
465
+ );
466
+
467
+ // Return the context value
468
+ return {
469
+ content,
470
+ shareDBDoc,
471
+ submitOperation,
472
+ localPresence,
473
+ docPresence,
474
+
475
+ files,
476
+ createFile,
477
+ renameFile,
478
+ deleteFile,
479
+ createDirectory,
480
+ renameDirectory,
481
+ deleteDirectory,
482
+ deleteAllFiles,
483
+
484
+ setActiveFileId,
485
+ setActiveFileLeft,
486
+ setActiveFileRight,
487
+
488
+ activePaneId,
489
+ // Root pane
490
+ pane,
491
+ // Active leaf pane
492
+ activePane,
493
+
494
+ openTab,
495
+ closeTabs,
496
+
497
+ search,
498
+ isSearchOpen,
499
+ setIsSearchOpen,
500
+ setSearch,
501
+ setSearchResults,
502
+ setSearchFileVisibility,
503
+ setSearchLineVisibility,
504
+ setSearchFocusedIndex,
505
+ toggleSearchFocused,
506
+
507
+ isVisualEditorOpen,
508
+ setIsVisualEditorOpen,
509
+
510
+ isAIChatOpen,
511
+ setIsAIChatOpen,
512
+ aiChatFocused,
513
+ toggleAIChatFocused,
514
+ aiChatMode: state.aiChatMode,
515
+ setAIChatMode,
516
+ aiChatEndpoint,
517
+ aiChatOptions,
518
+
519
+ isSettingsOpen,
520
+ setIsSettingsOpen,
521
+ closeSettings,
522
+
523
+ isDocOpen,
524
+ setIsDocOpen,
525
+ closeDoc,
526
+
527
+ theme,
528
+ setTheme,
529
+
530
+ username,
531
+ setUsername,
532
+
533
+ isDirectoryOpen,
534
+ toggleDirectory,
535
+
536
+ editorCache,
537
+ editorWantsFocus,
538
+ editorNoLongerWantsFocus,
539
+
540
+ errorMessage,
541
+
542
+ handleRuntimeError,
543
+ clearRuntimeError,
544
+
545
+ isCreateFileModalOpen,
546
+ handleOpenCreateFileModal,
547
+ handleCloseCreateFileModal,
548
+ handleCreateFileClick,
549
+ isCreateDirModalOpen,
550
+ handleOpenCreateDirModal,
551
+ handleCloseCreateDirModal,
552
+ handleCreateDirClick,
553
+ runPrettierRef,
554
+ runCodeRef,
555
+ sidebarRef,
556
+ codeEditorRef,
557
+ iframeRef,
558
+
559
+ connected,
560
+ pending,
561
+
562
+ hoveredItemId,
563
+ setHoveredItemId,
564
+
565
+ enableAutoFollow,
566
+ toggleAutoFollow,
567
+ updatePresenceIndicator,
568
+ sidebarPresenceIndicators,
569
+ splitCurrentPane,
570
+ liveKitRoomName,
571
+ setLiveKitRoom,
572
+ liveKitToken,
573
+ setLiveKitToken,
574
+ liveKitConnection,
575
+ setLiveKitConnection,
576
+ voiceChatModalOpen,
577
+ setVoiceChatModalOpen,
578
+
579
+ aiChatMessage,
580
+ setAIChatMessage,
581
+ isLoading,
582
+ currentChatId,
583
+ selectedChatId,
584
+ setSelectedChatId,
585
+ aiErrorMessage,
586
+ setAIErrorMessage,
587
+ handleSendMessage,
588
+
589
+ // Message history navigation
590
+ navigateMessageHistoryUp,
591
+ navigateMessageHistoryDown,
592
+ resetMessageHistoryNavigation,
593
+
594
+ // Auto-fork functions for VizHub integration
595
+ autoForkAndRetryAI,
596
+ clearStoredAIPrompt,
597
+ getStoredAIPrompt,
598
+
599
+ // Additional widgets that can be rendered in AI chat messages
600
+ additionalWidgets,
601
+ };
602
+ };