vzcode 2.2.0 → 2.3.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 (220) hide show
  1. package/dist/assets/{buildWorker-Dsx6rOdK.js → buildWorker-wLkQ0yz1.js} +64 -64
  2. package/dist/assets/{index-BM8tDh6M.css → index-BoFeK4GL.css} +1 -1
  3. package/dist/assets/{index-CpxqGhXj.js → index-DuUjtrt6.js} +150 -150
  4. package/dist/assets/{worker-BeBZGG1n.js → worker-DHxSPM7N.js} +78 -78
  5. package/dist/cli.js +113 -0
  6. package/dist/index.html +2 -2
  7. package/{src/ot.ts → dist/ot.js} +2 -14
  8. package/{src/randomId.ts → dist/randomId.js} +5 -6
  9. package/dist/runCode.js +20 -0
  10. package/dist/server/aiChatHandler/aiEditing.js +89 -0
  11. package/dist/server/aiChatHandler/chatOperations.js +360 -0
  12. package/dist/server/aiChatHandler/errorHandling.js +49 -0
  13. package/dist/server/aiChatHandler/index.js +112 -0
  14. package/dist/server/aiChatHandler/llmStreaming.js +284 -0
  15. package/dist/server/aiChatHandler/validation.js +19 -0
  16. package/dist/server/computeInitialDocument.js +146 -0
  17. package/{src/server/config.ts → dist/server/config.js} +4 -7
  18. package/{src/server/featureFlags.ts → dist/server/featureFlags.js} +0 -3
  19. package/dist/server/generateAIResponse.js +129 -0
  20. package/dist/server/handleAIAssist.js +39 -0
  21. package/dist/server/handleAICopilot.js +81 -0
  22. package/dist/server/index.js +297 -0
  23. package/dist/server/livekit.js +17 -0
  24. package/dist/server/prettier.js +90 -0
  25. package/{src/server/setupEnv.ts → dist/server/setupEnv.js} +1 -2
  26. package/dist/submitOperation.js +14 -0
  27. package/dist/types.js +1 -0
  28. package/dist/utils/fileDiff.js +66 -0
  29. package/package.json +22 -22
  30. package/src/client/AIAssist/AIAssistCodeMirrorKeyMap.ts +0 -42
  31. package/src/client/AIAssist/AIAssistWidget/index.tsx +0 -135
  32. package/src/client/AIAssist/AIAssistWidget/style.scss +0 -47
  33. package/src/client/AIAssist/Spinner.jsx +0 -76
  34. package/src/client/AIAssist/formatFile.ts +0 -27
  35. package/src/client/AIAssist/generateFilesContext.ts +0 -25
  36. package/src/client/AIAssist/index.ts +0 -2
  37. package/src/client/AIAssist/startAIAssist.ts +0 -95
  38. package/src/client/App/index.tsx +0 -153
  39. package/src/client/App/style.scss +0 -82
  40. package/src/client/App/usePending.ts +0 -51
  41. package/src/client/App/useShareDB.ts +0 -161
  42. package/src/client/CodeEditor/Copilot/index.ts +0 -74
  43. package/src/client/CodeEditor/InteractiveWidgets/boolToggler.ts +0 -12
  44. package/src/client/CodeEditor/InteractiveWidgets/colorPicker.ts +0 -68
  45. package/src/client/CodeEditor/InteractiveWidgets/colorsInTextPlugin.ts +0 -174
  46. package/src/client/CodeEditor/InteractiveWidgets/highlightWidgets.ts +0 -79
  47. package/src/client/CodeEditor/InteractiveWidgets/index.ts +0 -44
  48. package/src/client/CodeEditor/InteractiveWidgets/markdownCheckboxToggler.ts +0 -12
  49. package/src/client/CodeEditor/InteractiveWidgets/numberDragger.ts +0 -20
  50. package/src/client/CodeEditor/InteractiveWidgets/rotateWidget.ts +0 -143
  51. package/src/client/CodeEditor/InteractiveWidgets/urlClicker.test.ts +0 -48
  52. package/src/client/CodeEditor/InteractiveWidgets/urlClicker.ts +0 -12
  53. package/src/client/CodeEditor/InteractiveWidgets/vec2Slider.ts +0 -25
  54. package/src/client/CodeEditor/basicSetup.ts +0 -73
  55. package/src/client/CodeEditor/getOrCreateEditor.tsx +0 -615
  56. package/src/client/CodeEditor/index.tsx +0 -202
  57. package/src/client/CodeEditor/json1PresenceBroadcast.ts +0 -41
  58. package/src/client/CodeEditor/json1PresenceDisplay.ts +0 -350
  59. package/src/client/CodeEditor/rainbowBrackets.ts +0 -122
  60. package/src/client/CodeEditor/style.scss +0 -156
  61. package/src/client/CodeEditor/typescriptExtension/worker.ts +0 -32
  62. package/src/client/CodeErrorOverlay/index.tsx +0 -77
  63. package/src/client/CodeErrorOverlay/style.scss +0 -33
  64. package/src/client/Fonts/fonts.ts +0 -8
  65. package/src/client/Icons/AdjustmentSVG.tsx +0 -19
  66. package/src/client/Icons/BugSVG.tsx +0 -20
  67. package/src/client/Icons/CloseSVG.tsx +0 -19
  68. package/src/client/Icons/CssSVG.tsx +0 -16
  69. package/src/client/Icons/DirectoryArrowSVG.tsx +0 -19
  70. package/src/client/Icons/EditSVG.tsx +0 -19
  71. package/src/client/Icons/FileSVG.tsx +0 -19
  72. package/src/client/Icons/FolderSVG.tsx +0 -20
  73. package/src/client/Icons/GearSVG.tsx +0 -16
  74. package/src/client/Icons/HtmlSVG.tsx +0 -16
  75. package/src/client/Icons/JavaScriptSVG.tsx +0 -16
  76. package/src/client/Icons/JsonSVG.tsx +0 -16
  77. package/src/client/Icons/MarkdownSVG.tsx +0 -16
  78. package/src/client/Icons/MicOffSVG.tsx +0 -35
  79. package/src/client/Icons/MicSVG.tsx +0 -25
  80. package/src/client/Icons/NewSVG.tsx +0 -19
  81. package/src/client/Icons/PinSVG.tsx +0 -14
  82. package/src/client/Icons/PlaySVG.tsx +0 -16
  83. package/src/client/Icons/QuestionMarkSVG.tsx +0 -18
  84. package/src/client/Icons/ReactSVG.tsx +0 -16
  85. package/src/client/Icons/SearchSVG.tsx +0 -20
  86. package/src/client/Icons/SparklesSVG.tsx +0 -23
  87. package/src/client/Icons/SplitEditorSVG.tsx +0 -34
  88. package/src/client/Icons/StopSVG.tsx +0 -15
  89. package/src/client/Icons/SvelteSVG.tsx +0 -16
  90. package/src/client/Icons/TrashSVG.tsx +0 -19
  91. package/src/client/Icons/TypeScriptSVG.tsx +0 -16
  92. package/src/client/Icons/index.tsx +0 -26
  93. package/src/client/ImageViewer/index.tsx +0 -91
  94. package/src/client/ImageViewer/style.scss +0 -41
  95. package/src/client/PresenceNotifications/PresenceNotifications.test.ts +0 -134
  96. package/src/client/PresenceNotifications/index.tsx +0 -158
  97. package/src/client/PresenceNotifications/style.scss +0 -17
  98. package/src/client/RunCodeWidget/index.tsx +0 -117
  99. package/src/client/RunCodeWidget/style.scss +0 -24
  100. package/src/client/SplitPaneResizeContext.tsx +0 -159
  101. package/src/client/TabList/Tab.tsx +0 -73
  102. package/src/client/TabList/index.tsx +0 -35
  103. package/src/client/TabList/style.scss +0 -51
  104. package/src/client/VZCodeContext/index.tsx +0 -29
  105. package/src/client/VZCodeContext/types.ts +0 -249
  106. package/src/client/VZCodeContext/useVZCodeState.ts +0 -584
  107. package/src/client/VZKeyboardShortcutsDoc.tsx +0 -148
  108. package/src/client/VZLeft.tsx +0 -28
  109. package/src/client/VZMiddle.tsx +0 -172
  110. package/src/client/VZResizer/index.tsx +0 -128
  111. package/src/client/VZResizer/styles.scss +0 -33
  112. package/src/client/VZRight.tsx +0 -113
  113. package/src/client/VZSettings.tsx +0 -275
  114. package/src/client/VZSidebar/AIChat/ChatInput.tsx +0 -239
  115. package/src/client/VZSidebar/AIChat/DiffView.scss +0 -136
  116. package/src/client/VZSidebar/AIChat/DiffView.tsx +0 -176
  117. package/src/client/VZSidebar/AIChat/Message.tsx +0 -143
  118. package/src/client/VZSidebar/AIChat/MessageList.tsx +0 -205
  119. package/src/client/VZSidebar/AIChat/StreamingMessage.tsx +0 -35
  120. package/src/client/VZSidebar/AIChat/ThinkingScratchpad.tsx +0 -156
  121. package/src/client/VZSidebar/AIChat/TypingIndicator.tsx +0 -19
  122. package/src/client/VZSidebar/AIChat/index.tsx +0 -273
  123. package/src/client/VZSidebar/AIChat/styles.scss +0 -627
  124. package/src/client/VZSidebar/AIChat/useSpeechRecognition.ts +0 -117
  125. package/src/client/VZSidebar/CreateDirModal.tsx +0 -111
  126. package/src/client/VZSidebar/CreateFileModal.tsx +0 -111
  127. package/src/client/VZSidebar/DeleteConfirmationModal.tsx +0 -76
  128. package/src/client/VZSidebar/DirectoryListing.tsx +0 -89
  129. package/src/client/VZSidebar/EmptyState.tsx +0 -11
  130. package/src/client/VZSidebar/FileListing.tsx +0 -91
  131. package/src/client/VZSidebar/FileTypeIcon.tsx +0 -49
  132. package/src/client/VZSidebar/Item.tsx +0 -237
  133. package/src/client/VZSidebar/Listing.tsx +0 -47
  134. package/src/client/VZSidebar/Search.tsx +0 -527
  135. package/src/client/VZSidebar/VisualEditor.tsx +0 -282
  136. package/src/client/VZSidebar/VoiceChatModal.tsx +0 -121
  137. package/src/client/VZSidebar/aiCopyPaste.ts +0 -199
  138. package/src/client/VZSidebar/index.tsx +0 -728
  139. package/src/client/VZSidebar/styles.scss +0 -612
  140. package/src/client/VZSidebar/useDragAndDrop.tsx +0 -408
  141. package/src/client/bootstrap.ts +0 -26
  142. package/src/client/buildWorker.ts +0 -3
  143. package/src/client/featureFlags.ts +0 -8
  144. package/src/client/generateRequestId.ts +0 -6
  145. package/src/client/getFileTree.ts +0 -53
  146. package/src/client/index.ts +0 -19
  147. package/src/client/main.jsx +0 -10
  148. package/src/client/presenceColor.ts +0 -8
  149. package/src/client/shouldTriggerRun.ts +0 -31
  150. package/src/client/sortFileTree.ts +0 -21
  151. package/src/client/tabsSearchParameters.test.ts +0 -79
  152. package/src/client/tabsSearchParameters.ts +0 -137
  153. package/src/client/themes/index.ts +0 -7
  154. package/src/client/themes/themes.ts +0 -77
  155. package/src/client/themes/uiwImports.ts +0 -32
  156. package/src/client/themes/useDynamicTheme.ts +0 -29
  157. package/src/client/themes/vizhubTheme/colors.ts +0 -80
  158. package/src/client/themes/vizhubTheme/index.ts +0 -193
  159. package/src/client/useActions.ts +0 -288
  160. package/src/client/useESLint/index.ts +0 -72
  161. package/src/client/useESLint/worker.ts +0 -113
  162. package/src/client/useEditorCache.ts +0 -35
  163. package/src/client/useFileCRUD.ts +0 -228
  164. package/src/client/useKeyboardShortcuts.ts +0 -451
  165. package/src/client/useOpenDirectories.ts +0 -99
  166. package/src/client/usePresenceAutoFollow.ts +0 -85
  167. package/src/client/usePrettier/index.ts +0 -178
  168. package/src/client/usePrettier/worker.ts +0 -109
  169. package/src/client/useRunCode.tsx +0 -15
  170. package/src/client/useRuntimeError.ts +0 -32
  171. package/src/client/useSubmitOperation.ts +0 -15
  172. package/src/client/useURLSync.ts +0 -137
  173. package/src/client/usernameLocalStorage.ts +0 -43
  174. package/src/client/utils/fileExtension.ts +0 -10
  175. package/src/client/utils/isImageFile.ts +0 -12
  176. package/src/client/vzReducer/aiChatReducer.ts +0 -44
  177. package/src/client/vzReducer/closeTabReducer.test.ts +0 -205
  178. package/src/client/vzReducer/closeTabsReducer.ts +0 -96
  179. package/src/client/vzReducer/createInitialState.ts +0 -38
  180. package/src/client/vzReducer/editorNoLongerWantsFocusReducer.ts +0 -9
  181. package/src/client/vzReducer/findPane.ts +0 -19
  182. package/src/client/vzReducer/index.ts +0 -250
  183. package/src/client/vzReducer/openTabReducer.test.ts +0 -127
  184. package/src/client/vzReducer/openTabReducer.ts +0 -74
  185. package/src/client/vzReducer/searchReducer.test.ts +0 -292
  186. package/src/client/vzReducer/searchReducer.ts +0 -181
  187. package/src/client/vzReducer/setActiveFileIdReducer.ts +0 -28
  188. package/src/client/vzReducer/setActiveFileLeftRightReducer.ts +0 -76
  189. package/src/client/vzReducer/setIsDocOpenReducer.ts +0 -9
  190. package/src/client/vzReducer/setIsSettingsOpenReducer.ts +0 -9
  191. package/src/client/vzReducer/setThemeReducer.ts +0 -9
  192. package/src/client/vzReducer/setUsernameReducer.ts +0 -9
  193. package/src/client/vzReducer/splitCurrentPaneReducer.ts +0 -66
  194. package/src/client/vzReducer/toggleAutoFollowReducer.ts +0 -12
  195. package/src/client/vzReducer/updatePane.ts +0 -40
  196. package/src/client/vzReducer/updatePresenceIndicatorReducer.ts +0 -30
  197. package/src/client/vzReducer/visualEditorReducer.ts +0 -9
  198. package/src/reactRouterExports.ts +0 -15
  199. package/src/runCode.ts +0 -27
  200. package/src/server/aiChatHandler/aiEditing.ts +0 -152
  201. package/src/server/aiChatHandler/chatOperations.ts +0 -510
  202. package/src/server/aiChatHandler/errorHandling.ts +0 -66
  203. package/src/server/aiChatHandler/index.ts +0 -200
  204. package/src/server/aiChatHandler/llmStreaming.ts +0 -419
  205. package/src/server/aiChatHandler/undoHandler.ts +0 -97
  206. package/src/server/aiChatHandler/validation.ts +0 -24
  207. package/src/server/computeInitialDocument.ts +0 -220
  208. package/src/server/generateAIResponse.ts +0 -188
  209. package/src/server/handleAIAssist.ts +0 -53
  210. package/src/server/handleAIChatUndo.ts +0 -2
  211. package/src/server/handleAICopilot.ts +0 -97
  212. package/src/server/index.ts +0 -388
  213. package/src/server/livekit.ts +0 -24
  214. package/src/server/prettier.ts +0 -115
  215. package/src/submitOperation.ts +0 -25
  216. package/src/types.ts +0 -205
  217. package/src/utils/fileDiff.ts +0 -117
  218. package/src/vite-env.d.ts +0 -10
  219. /package/{src/server/handleAIChatMessage.ts → dist/server/handleAIChatMessage.js} +0 -0
  220. /package/{src/server/isDirectory.ts → dist/server/isDirectory.js} +0 -0
@@ -1,200 +0,0 @@
1
- import { validateRequest } from './validation.js';
2
- import {
3
- ensureChatsExist,
4
- ensureChatExists,
5
- addUserMessage,
6
- addDiffToAIMessage,
7
- setAIStatus,
8
- } from './chatOperations.js';
9
- import { createLLMFunction } from './llmStreaming.js';
10
- import {
11
- performAIEditing,
12
- performAIChat,
13
- } from './aiEditing.js';
14
- import {
15
- handleError,
16
- handleBackgroundError,
17
- } from './errorHandling.js';
18
- import { createRunCodeFunction } from '../../runCode.js';
19
- import { ShareDBDoc } from '../../types.js';
20
- import { VizContent } from '@vizhub/viz-types';
21
- import { createSubmitOperation } from '../../submitOperation.js';
22
-
23
- const DEBUG = false;
24
-
25
- export const handleAIChatMessage =
26
- ({
27
- shareDBDoc,
28
- createAIEditLocalPresence,
29
- onCreditDeduction,
30
- getCurrentCommitId,
31
- model,
32
- aiRequestOptions,
33
- }: {
34
- shareDBDoc: ShareDBDoc<VizContent>;
35
- createAIEditLocalPresence: () => any;
36
- onCreditDeduction?: any;
37
- getCurrentCommitId?: () => string | null;
38
- model?: string;
39
- aiRequestOptions?: any;
40
- }) =>
41
- async (req: any, res: any) => {
42
- const { content, chatId, mode = 'edit' } = req.body;
43
-
44
- if (DEBUG) {
45
- console.log(
46
- '[handleAIChatMessage] content:',
47
- content,
48
- 'chatId:',
49
- chatId,
50
- 'shareDBDoc:',
51
- shareDBDoc,
52
- );
53
- }
54
-
55
- // Validate request
56
- if (!validateRequest(req, res)) {
57
- return;
58
- }
59
-
60
- try {
61
- // Ensure chats structure exists
62
- ensureChatsExist(shareDBDoc);
63
- ensureChatExists(shareDBDoc, chatId);
64
-
65
- // Add user message to chat
66
- addUserMessage(shareDBDoc, chatId, content);
67
-
68
- // Return success immediately - AI generation continues in background
69
- res.status(200).json('success');
70
-
71
- // Set AI status to indicate generation is starting
72
- setAIStatus(shareDBDoc, chatId, 'generating');
73
-
74
- // Continue AI processing in background (don't await)
75
- processAIRequestAsync({
76
- shareDBDoc,
77
- chatId,
78
- content,
79
- mode,
80
- createAIEditLocalPresence,
81
- getCurrentCommitId,
82
- model,
83
- aiRequestOptions,
84
- onCreditDeduction,
85
- }).catch((error) => {
86
- console.error(
87
- 'Background AI processing error:',
88
- error,
89
- );
90
- // Handle error without HTTP response
91
- handleBackgroundError(shareDBDoc, chatId, error);
92
- });
93
- } catch (error) {
94
- handleError(shareDBDoc, chatId, error, res);
95
- }
96
- };
97
-
98
- /**
99
- * Processes the AI request asynchronously in the background
100
- */
101
- const processAIRequestAsync = async ({
102
- shareDBDoc,
103
- chatId,
104
- content,
105
- mode,
106
- createAIEditLocalPresence,
107
- getCurrentCommitId,
108
- model,
109
- aiRequestOptions,
110
- onCreditDeduction,
111
- }: {
112
- shareDBDoc: ShareDBDoc<VizContent>;
113
- chatId: string;
114
- content: string;
115
- mode: string;
116
- createAIEditLocalPresence: () => any;
117
- getCurrentCommitId?: () => string | null;
118
- model?: string;
119
- aiRequestOptions?: any;
120
- onCreditDeduction?: any;
121
- }) => {
122
- try {
123
- // Capture the current commit ID before making changes (for VizHub integration)
124
- const beforeCommitId = getCurrentCommitId
125
- ? getCurrentCommitId()
126
- : null;
127
-
128
- // Create LLM function for streaming
129
- const llmFunction = createLLMFunction({
130
- shareDBDoc,
131
- createAIEditLocalPresence,
132
- chatId,
133
- model,
134
- aiRequestOptions,
135
- });
136
-
137
- // Create server-side runCode function using shareDBDoc
138
- const submitOperation =
139
- createSubmitOperation(shareDBDoc);
140
- const runCode = createRunCodeFunction(submitOperation);
141
-
142
- // Perform AI editing or chat based on mode
143
- const editResult =
144
- mode === 'ask'
145
- ? await performAIChat({
146
- prompt: content,
147
- shareDBDoc,
148
- llmFunction,
149
- })
150
- : await performAIEditing({
151
- prompt: content,
152
- shareDBDoc,
153
- llmFunction,
154
- runCode,
155
- });
156
-
157
- // Add diff data to the AI message if there are changes
158
- if (
159
- editResult.diffData &&
160
- Object.keys(editResult.diffData).length > 0
161
- ) {
162
- addDiffToAIMessage(
163
- shareDBDoc,
164
- chatId,
165
- editResult.diffData,
166
- (editResult as any).beforeFiles, // Pass the beforeFiles snapshot for undo (legacy)
167
- beforeCommitId, // Pass the commit ID before AI changes (for VizHub integration)
168
- );
169
- }
170
-
171
- // Handle credit deduction if callback is provided
172
- if (
173
- onCreditDeduction &&
174
- (editResult as any).upstreamCostCents
175
- ) {
176
- try {
177
- await onCreditDeduction({
178
- upstreamCostCents: (editResult as any)
179
- .upstreamCostCents,
180
- provider: (editResult as any).provider,
181
- inputTokens: (editResult as any).inputTokens,
182
- outputTokens: (editResult as any).outputTokens,
183
- });
184
- } catch (creditError) {
185
- console.error(
186
- 'Credit deduction error:',
187
- creditError,
188
- );
189
- // Don't fail the request if credit deduction fails
190
- }
191
- }
192
-
193
- // Clear the AI status to indicate completion
194
- setAIStatus(shareDBDoc, chatId, undefined);
195
- } catch (error) {
196
- // Set error status and add error message to chat
197
- setAIStatus(shareDBDoc, chatId, 'error');
198
- handleBackgroundError(shareDBDoc, chatId, error);
199
- }
200
- };
@@ -1,419 +0,0 @@
1
- import {
2
- parseMarkdownFiles,
3
- StreamingMarkdownParser,
4
- } from 'llm-code-format';
5
- import OpenAI from 'openai';
6
- import fs from 'fs';
7
- import { generateRunId } from '@vizhub/viz-utils';
8
- import {
9
- updateAIStatus,
10
- createAIMessage,
11
- updateAIMessageContent,
12
- finalizeAIMessage,
13
- ensureFileExists,
14
- clearFileContent,
15
- appendLineToFile,
16
- updateFiles,
17
- updateAIScratchpad,
18
- } from './chatOperations.js';
19
- import { mergeFileChanges } from 'editcodewithai';
20
- import { diff } from '../../ot.js';
21
- import { VizChatId, VizContent } from '@vizhub/viz-types';
22
- import { ShareDBDoc } from '../../types.js';
23
-
24
- const DEBUG = false;
25
-
26
- // Useful for testing/debugging the streaming behavior
27
- const slowMode = false;
28
-
29
- // Throttle the streaming updates, so that we don't
30
- // overwhelm the ShareDB server with too many updates.
31
- // It happened actually, before adding this.
32
- // MongoDB VizHub server got in fact overloaded with
33
- // too many updates from the AI streaming response, with
34
- // warning: "Replication Oplog Window has gone below 1 hour"
35
- const THROTTLE_INTERVAL_MS = 100;
36
-
37
- // Feature flag to enable/disable streaming editing.
38
- // * If `true`, the AI streaming response will be used to
39
- // edit files in real-time by submitting ShareDB ops.
40
- // * If `false`, the updates to code files will be applied
41
- // only after the AI has finished generating the entire response.
42
- //
43
- // Current status: there's a tricky bug with the streaming
44
- // where the AI edits sometimes don't apply correctly in CodeMirror.
45
- // It seems that sometimes the op that clears the file content
46
- // is not applied correctly in the front end, leading to
47
- // a situation where the AI streaming edits are concatenated into the middle
48
- // of the file instead of replacing it.
49
- // See https://github.com/codemirror/codemirror.next/issues/1234
50
- const enableStreamingEditing = false;
51
-
52
- /**
53
- * Creates and configures the LLM function for streaming with reasoning tokens
54
- */
55
- export const createLLMFunction = ({
56
- shareDBDoc,
57
- createAIEditLocalPresence,
58
- chatId,
59
- // Feature flag to enable/disable reasoning tokens.
60
- // When false, reasoning tokens are not requested from the API
61
- // and reasoning content is not processed in the streaming response.
62
- enableReasoningTokens = false,
63
- model,
64
- aiRequestOptions,
65
- }: {
66
- shareDBDoc: ShareDBDoc<VizContent>;
67
- createAIEditLocalPresence: () => any;
68
- chatId: VizChatId;
69
- enableReasoningTokens?: boolean;
70
- model?: string;
71
- aiRequestOptions?: any;
72
- }) => {
73
- return async (fullPrompt: string) => {
74
- const localPresence = enableStreamingEditing
75
- ? createAIEditLocalPresence()
76
- : null;
77
-
78
- // Create OpenRouter client for reasoning token support
79
- const openRouterClient = new OpenAI({
80
- apiKey: process.env.VIZHUB_EDIT_WITH_AI_API_KEY,
81
- baseURL:
82
- process.env.VIZHUB_EDIT_WITH_AI_BASE_URL ||
83
- 'https://openrouter.ai/api/v1',
84
- defaultHeaders: {
85
- 'HTTP-Referer': 'https://vizhub.com',
86
- 'X-Title': 'VizHub',
87
- },
88
- });
89
-
90
- let fullContent = '';
91
- let generationId = '';
92
- let currentEditingFileId = null;
93
- let currentEditingFileName = null;
94
-
95
- // Create initial AI message for streaming
96
- const aiMessageId = createAIMessage(shareDBDoc, chatId);
97
-
98
- // --- throttle wrapper ---
99
- function makeThrottledUpdater() {
100
- let lastCall = 0;
101
- let latestContent = '';
102
- let timer: NodeJS.Timeout | null = null;
103
-
104
- function invoke() {
105
- updateAIMessageContent(
106
- shareDBDoc,
107
- chatId,
108
- aiMessageId,
109
- latestContent,
110
- );
111
- lastCall = Date.now();
112
- }
113
-
114
- const fn = (content: string) => {
115
- latestContent = content;
116
- const now = Date.now();
117
-
118
- if (now - lastCall >= THROTTLE_INTERVAL_MS) {
119
- // safe to call immediately
120
- invoke();
121
- } else if (!timer) {
122
- // schedule for later
123
- timer = setTimeout(
124
- () => {
125
- timer = null;
126
- invoke();
127
- },
128
- THROTTLE_INTERVAL_MS - (now - lastCall),
129
- );
130
- }
131
- };
132
-
133
- // expose a flush() helper to force an immediate write
134
- fn.flush = () => {
135
- if (timer) {
136
- clearTimeout(timer);
137
- timer = null;
138
- }
139
- invoke();
140
- };
141
-
142
- return fn;
143
- }
144
-
145
- const throttledUpdateAIMessageContent =
146
- makeThrottledUpdater();
147
-
148
- // Function to report file edited
149
- // This is called when the AI has finished editing a file
150
- // and we want to update the message content with the file name.
151
- const reportFileEdited = () => {
152
- if (currentEditingFileName) {
153
- fullContent += ` * Edited ${currentEditingFileName}\n`;
154
- throttledUpdateAIMessageContent(fullContent);
155
- currentEditingFileName = null;
156
- }
157
- };
158
-
159
- // Define callbacks for streaming parser
160
- const callbacks = {
161
- onFileNameChange: async (
162
- fileName: string,
163
- format: string,
164
- ) => {
165
- DEBUG &&
166
- console.log(
167
- `File changed to: ${fileName} (${format})`,
168
- );
169
-
170
- // Find existing file or create new one
171
- currentEditingFileId = ensureFileExists(
172
- shareDBDoc,
173
- fileName,
174
- );
175
-
176
- reportFileEdited();
177
- currentEditingFileName = fileName;
178
-
179
- // Clear the file content to start fresh
180
- // (AI will regenerate the entire file content)
181
- clearFileContent(shareDBDoc, currentEditingFileId);
182
-
183
- // Update AI status
184
- updateAIStatus(
185
- shareDBDoc,
186
- chatId,
187
- 'Editing ' + fileName,
188
- );
189
- },
190
- onCodeLine: async (line: string) => {
191
- DEBUG && console.log(`Code line: ${line}`);
192
-
193
- // If streaming is enabled, we apply the line immediately
194
- if (currentEditingFileId) {
195
- // Apply OT operation for this line immediately
196
- appendLineToFile(
197
- shareDBDoc,
198
- currentEditingFileId,
199
- line,
200
- );
201
-
202
- // Update AI presence to show cursor at the end of the file
203
- const currentFile =
204
- shareDBDoc.data.files[currentEditingFileId];
205
- if (currentFile && currentFile.text) {
206
- const textLength = currentFile.text.length;
207
- const filePresence = {
208
- username: 'AI Editor',
209
- start: [
210
- 'files',
211
- currentEditingFileId,
212
- 'text',
213
- textLength,
214
- ],
215
- end: [
216
- 'files',
217
- currentEditingFileId,
218
- 'text',
219
- textLength,
220
- ],
221
- };
222
-
223
- if (localPresence) {
224
- localPresence.submit(
225
- filePresence,
226
- (error) => {
227
- if (error) {
228
- console.warn(
229
- 'AI Editor line presence submission error:',
230
- error,
231
- );
232
- }
233
- },
234
- );
235
- }
236
- }
237
- }
238
- },
239
- onNonCodeLine: async (line: string) => {
240
- // We want to report a file edited only if the line is not empty,
241
- // because sometimes the LLMs leave a newline between the file name
242
- // declaration and th
243
- if (line.trim() !== '') {
244
- reportFileEdited();
245
- }
246
- fullContent += line + '\n';
247
- throttledUpdateAIMessageContent(fullContent);
248
- },
249
- };
250
-
251
- const parser = new StreamingMarkdownParser(callbacks);
252
-
253
- const chunks = [];
254
- let reasoningContent = '';
255
-
256
- // Stream the response with reasoning tokens
257
- const modelName =
258
- model ||
259
- process.env.VIZHUB_EDIT_WITH_AI_MODEL_NAME ||
260
- 'anthropic/claude-3.5-sonnet';
261
-
262
- // Configure reasoning tokens based on enableReasoningTokens flag
263
- const requestConfig: any = {
264
- model: modelName,
265
- messages: [{ role: 'user', content: fullPrompt }],
266
- usage: { include: true },
267
- stream: true,
268
- ...aiRequestOptions,
269
- };
270
-
271
- // Only include reasoning configuration if reasoning tokens are enabled
272
- if (enableReasoningTokens) {
273
- requestConfig.reasoning = {
274
- effort: 'low',
275
- exclude: false,
276
- };
277
- }
278
-
279
- const stream = await (
280
- openRouterClient.chat.completions.create as any
281
- )(requestConfig);
282
-
283
- let reasoningStarted = false;
284
- let contentStarted = false;
285
-
286
- for await (const chunk of stream) {
287
- if (slowMode) {
288
- await new Promise((resolve) =>
289
- setTimeout(resolve, 500),
290
- );
291
- }
292
- const delta = chunk.choices[0]?.delta as any; // Type assertion for OpenRouter-specific reasoning fields
293
-
294
- if (delta?.reasoning && enableReasoningTokens) {
295
- // Handle reasoning tokens (thinking) - only if enabled
296
- if (!reasoningStarted) {
297
- reasoningStarted = true;
298
- updateAIStatus(shareDBDoc, chatId, 'Thinking...');
299
- }
300
- reasoningContent += delta.reasoning;
301
- updateAIScratchpad(
302
- shareDBDoc,
303
- chatId,
304
- reasoningContent,
305
- );
306
- } else if (delta?.content) {
307
- // Handle regular content tokens
308
- if (reasoningStarted && !contentStarted) {
309
- // Clear reasoning when content starts
310
- contentStarted = true;
311
- updateAIScratchpad(shareDBDoc, chatId, '');
312
- updateAIStatus(
313
- shareDBDoc,
314
- chatId,
315
- 'Generating response...',
316
- );
317
- }
318
-
319
- const chunkContent = delta.content;
320
- chunks.push(chunkContent);
321
-
322
- if (enableStreamingEditing) {
323
- await parser.processChunk(chunkContent);
324
- } else {
325
- fullContent += chunkContent;
326
- throttledUpdateAIMessageContent(fullContent);
327
- }
328
- } else if (chunk.usage) {
329
- // Handle usage information
330
- DEBUG && console.log('Usage:', chunk.usage);
331
- }
332
-
333
- if (!generationId && chunk.id) {
334
- generationId = chunk.id;
335
- }
336
- }
337
- await parser.flushRemaining();
338
- reportFileEdited();
339
-
340
- // Final cleanup - clear scratchpad and set final status
341
- updateAIScratchpad(shareDBDoc, chatId, '');
342
- updateAIStatus(shareDBDoc, chatId, 'Done editing.');
343
- throttledUpdateAIMessageContent(fullContent);
344
-
345
- // Flush to ensure the final content is written immediately
346
- throttledUpdateAIMessageContent.flush();
347
-
348
- // Finalize the AI message by clearing temporary fields
349
- finalizeAIMessage(shareDBDoc, chatId);
350
-
351
- // Clear AI Editor presence when done
352
- DEBUG &&
353
- console.log(
354
- 'AI editing done, clearing AI Editor presence',
355
- );
356
- if (localPresence) {
357
- localPresence.submit(null, (error) => {
358
- DEBUG && console.log('AI Editor presence cleared');
359
- if (error) {
360
- console.warn(
361
- 'AI Editor presence cleanup error:',
362
- error,
363
- );
364
- }
365
- });
366
- }
367
-
368
- // If streaming editing is not enabled, we need to
369
- // apply all the edits at once
370
- if (!enableStreamingEditing) {
371
- updateFiles(
372
- shareDBDoc,
373
- mergeFileChanges(
374
- shareDBDoc.data.files,
375
- parseMarkdownFiles(fullContent, 'bold').files,
376
- ),
377
- );
378
- }
379
-
380
- // Generate a new runId to trigger a run when AI finishes editing
381
- // This will trigger a re-run without hot reloading
382
- const newRunId = generateRunId();
383
- const runIdOp = diff(shareDBDoc.data, {
384
- ...shareDBDoc.data,
385
- runId: newRunId,
386
- });
387
- shareDBDoc.submitOp(runIdOp, (error) => {
388
- if (error) {
389
- console.warn(
390
- 'Error setting runId after AI editing:',
391
- error,
392
- );
393
- } else {
394
- DEBUG &&
395
- console.log(
396
- 'Set new runId after AI editing:',
397
- newRunId,
398
- );
399
- }
400
- });
401
-
402
- // Write chunks file for debugging
403
- if (DEBUG) {
404
- const chunksFileJSONpath = `./ai-chunks-${chatId}.json`;
405
- fs.writeFileSync(
406
- chunksFileJSONpath,
407
- JSON.stringify(chunks, null, 2),
408
- );
409
- console.log(
410
- `AI chunks written to ${chunksFileJSONpath}`,
411
- );
412
- }
413
-
414
- return {
415
- content: fullContent,
416
- generationId: generationId,
417
- };
418
- };
419
- };
@@ -1,97 +0,0 @@
1
- import { undoAIEdit } from './chatOperations.js';
2
- import { handleError } from './errorHandling.js';
3
- import { ShareDBDoc } from '../../types.js';
4
- import { VizContent } from '@vizhub/viz-types';
5
-
6
- const DEBUG = false;
7
-
8
- /**
9
- * Validates incoming request data for AI chat undo
10
- */
11
- const validateUndoRequest = (req, res) => {
12
- const { chatId, messageId } = req.body;
13
-
14
- if (!chatId || typeof chatId !== 'string') {
15
- res.status(400).json({
16
- error:
17
- 'Invalid request: chatId is required and must be a string',
18
- });
19
- return false;
20
- }
21
-
22
- if (!messageId || typeof messageId !== 'string') {
23
- res.status(400).json({
24
- error:
25
- 'Invalid request: messageId is required and must be a string',
26
- });
27
- return false;
28
- }
29
-
30
- return true;
31
- };
32
-
33
- export const handleAIChatUndo =
34
- ({
35
- shareDBDoc,
36
- }: {
37
- shareDBDoc: ShareDBDoc<VizContent>;
38
- }) =>
39
- async (req: any, res: any) => {
40
- const { chatId, messageId } = req.body;
41
-
42
- if (DEBUG) {
43
- console.log(
44
- '[handleAIChatUndo] chatId:',
45
- chatId,
46
- 'messageId:',
47
- messageId,
48
- 'shareDBDoc:',
49
- shareDBDoc,
50
- );
51
- }
52
-
53
- // Validate request
54
- if (!validateUndoRequest(req, res)) {
55
- return;
56
- }
57
-
58
- try {
59
- const chat = shareDBDoc.data.chats?.[chatId];
60
- if (!chat) {
61
- res.status(404).json({
62
- error: 'Chat not found',
63
- });
64
- return;
65
- }
66
-
67
- const message = chat.messages.find(
68
- (msg) => msg.id === messageId,
69
- );
70
-
71
- if (
72
- !message ||
73
- message.role !== 'assistant' ||
74
- !(message as any).beforeFiles
75
- ) {
76
- res.status(400).json({
77
- error: 'Invalid message for undo',
78
- });
79
- return;
80
- }
81
-
82
- // Perform the undo operation
83
- undoAIEdit(
84
- shareDBDoc,
85
- chatId,
86
- messageId,
87
- (message as any).beforeFiles,
88
- );
89
-
90
- res.status(200).json({ success: true });
91
- } catch (error) {
92
- if (DEBUG) {
93
- console.error('[handleAIChatUndo] Error:', error);
94
- }
95
- handleError(shareDBDoc, chatId, error, res);
96
- }
97
- };