vzcode 2.0.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 (218) 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-BgqNq-ww.js → index-DuUjtrt6.js} +107 -107
  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 +23 -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/MicSVG.tsx +0 -25
  79. package/src/client/Icons/NewSVG.tsx +0 -19
  80. package/src/client/Icons/PinSVG.tsx +0 -14
  81. package/src/client/Icons/PlaySVG.tsx +0 -16
  82. package/src/client/Icons/QuestionMarkSVG.tsx +0 -18
  83. package/src/client/Icons/ReactSVG.tsx +0 -16
  84. package/src/client/Icons/SearchSVG.tsx +0 -20
  85. package/src/client/Icons/SparklesSVG.tsx +0 -23
  86. package/src/client/Icons/SplitEditorSVG.tsx +0 -34
  87. package/src/client/Icons/StopSVG.tsx +0 -15
  88. package/src/client/Icons/SvelteSVG.tsx +0 -16
  89. package/src/client/Icons/TrashSVG.tsx +0 -19
  90. package/src/client/Icons/TypeScriptSVG.tsx +0 -16
  91. package/src/client/Icons/index.tsx +0 -24
  92. package/src/client/ImageViewer/index.tsx +0 -91
  93. package/src/client/ImageViewer/style.scss +0 -41
  94. package/src/client/PresenceNotifications/PresenceNotifications.test.ts +0 -134
  95. package/src/client/PresenceNotifications/index.tsx +0 -158
  96. package/src/client/PresenceNotifications/style.scss +0 -17
  97. package/src/client/RunCodeWidget/index.tsx +0 -117
  98. package/src/client/RunCodeWidget/style.scss +0 -24
  99. package/src/client/SplitPaneResizeContext.tsx +0 -159
  100. package/src/client/TabList/Tab.tsx +0 -73
  101. package/src/client/TabList/index.tsx +0 -35
  102. package/src/client/TabList/style.scss +0 -51
  103. package/src/client/VZCodeContext/index.tsx +0 -29
  104. package/src/client/VZCodeContext/types.ts +0 -249
  105. package/src/client/VZCodeContext/useVZCodeState.ts +0 -584
  106. package/src/client/VZKeyboardShortcutsDoc.tsx +0 -148
  107. package/src/client/VZLeft.tsx +0 -28
  108. package/src/client/VZMiddle.tsx +0 -172
  109. package/src/client/VZResizer/index.tsx +0 -128
  110. package/src/client/VZResizer/styles.scss +0 -33
  111. package/src/client/VZRight.tsx +0 -113
  112. package/src/client/VZSettings.tsx +0 -275
  113. package/src/client/VZSidebar/AIChat/ChatInput.tsx +0 -196
  114. package/src/client/VZSidebar/AIChat/DiffView.scss +0 -136
  115. package/src/client/VZSidebar/AIChat/DiffView.tsx +0 -176
  116. package/src/client/VZSidebar/AIChat/Message.tsx +0 -143
  117. package/src/client/VZSidebar/AIChat/MessageList.tsx +0 -205
  118. package/src/client/VZSidebar/AIChat/StreamingMessage.tsx +0 -35
  119. package/src/client/VZSidebar/AIChat/ThinkingScratchpad.tsx +0 -156
  120. package/src/client/VZSidebar/AIChat/TypingIndicator.tsx +0 -19
  121. package/src/client/VZSidebar/AIChat/index.tsx +0 -273
  122. package/src/client/VZSidebar/AIChat/styles.scss +0 -627
  123. package/src/client/VZSidebar/CreateDirModal.tsx +0 -111
  124. package/src/client/VZSidebar/CreateFileModal.tsx +0 -111
  125. package/src/client/VZSidebar/DeleteConfirmationModal.tsx +0 -76
  126. package/src/client/VZSidebar/DirectoryListing.tsx +0 -89
  127. package/src/client/VZSidebar/EmptyState.tsx +0 -11
  128. package/src/client/VZSidebar/FileListing.tsx +0 -91
  129. package/src/client/VZSidebar/FileTypeIcon.tsx +0 -49
  130. package/src/client/VZSidebar/Item.tsx +0 -237
  131. package/src/client/VZSidebar/Listing.tsx +0 -47
  132. package/src/client/VZSidebar/Search.tsx +0 -527
  133. package/src/client/VZSidebar/VisualEditor.tsx +0 -295
  134. package/src/client/VZSidebar/VoiceChatModal.tsx +0 -121
  135. package/src/client/VZSidebar/aiCopyPaste.ts +0 -199
  136. package/src/client/VZSidebar/index.tsx +0 -728
  137. package/src/client/VZSidebar/styles.scss +0 -612
  138. package/src/client/VZSidebar/useDragAndDrop.tsx +0 -408
  139. package/src/client/bootstrap.ts +0 -26
  140. package/src/client/buildWorker.ts +0 -3
  141. package/src/client/featureFlags.ts +0 -8
  142. package/src/client/generateRequestId.ts +0 -6
  143. package/src/client/getFileTree.ts +0 -53
  144. package/src/client/index.ts +0 -19
  145. package/src/client/main.jsx +0 -10
  146. package/src/client/presenceColor.ts +0 -8
  147. package/src/client/shouldTriggerRun.ts +0 -31
  148. package/src/client/sortFileTree.ts +0 -21
  149. package/src/client/tabsSearchParameters.test.ts +0 -79
  150. package/src/client/tabsSearchParameters.ts +0 -137
  151. package/src/client/themes/index.ts +0 -7
  152. package/src/client/themes/themes.ts +0 -77
  153. package/src/client/themes/uiwImports.ts +0 -32
  154. package/src/client/themes/useDynamicTheme.ts +0 -29
  155. package/src/client/themes/vizhubTheme/colors.ts +0 -80
  156. package/src/client/themes/vizhubTheme/index.ts +0 -193
  157. package/src/client/useActions.ts +0 -288
  158. package/src/client/useESLint/index.ts +0 -72
  159. package/src/client/useESLint/worker.ts +0 -113
  160. package/src/client/useEditorCache.ts +0 -35
  161. package/src/client/useFileCRUD.ts +0 -228
  162. package/src/client/useKeyboardShortcuts.ts +0 -451
  163. package/src/client/useOpenDirectories.ts +0 -99
  164. package/src/client/usePresenceAutoFollow.ts +0 -85
  165. package/src/client/usePrettier/index.ts +0 -178
  166. package/src/client/usePrettier/worker.ts +0 -109
  167. package/src/client/useRunCode.tsx +0 -15
  168. package/src/client/useRuntimeError.ts +0 -32
  169. package/src/client/useSubmitOperation.ts +0 -15
  170. package/src/client/useURLSync.ts +0 -137
  171. package/src/client/usernameLocalStorage.ts +0 -43
  172. package/src/client/utils/fileExtension.ts +0 -10
  173. package/src/client/utils/isImageFile.ts +0 -12
  174. package/src/client/vzReducer/aiChatReducer.ts +0 -44
  175. package/src/client/vzReducer/closeTabReducer.test.ts +0 -205
  176. package/src/client/vzReducer/closeTabsReducer.ts +0 -96
  177. package/src/client/vzReducer/createInitialState.ts +0 -38
  178. package/src/client/vzReducer/editorNoLongerWantsFocusReducer.ts +0 -9
  179. package/src/client/vzReducer/findPane.ts +0 -19
  180. package/src/client/vzReducer/index.ts +0 -250
  181. package/src/client/vzReducer/openTabReducer.test.ts +0 -127
  182. package/src/client/vzReducer/openTabReducer.ts +0 -74
  183. package/src/client/vzReducer/searchReducer.test.ts +0 -292
  184. package/src/client/vzReducer/searchReducer.ts +0 -181
  185. package/src/client/vzReducer/setActiveFileIdReducer.ts +0 -28
  186. package/src/client/vzReducer/setActiveFileLeftRightReducer.ts +0 -76
  187. package/src/client/vzReducer/setIsDocOpenReducer.ts +0 -9
  188. package/src/client/vzReducer/setIsSettingsOpenReducer.ts +0 -9
  189. package/src/client/vzReducer/setThemeReducer.ts +0 -9
  190. package/src/client/vzReducer/setUsernameReducer.ts +0 -9
  191. package/src/client/vzReducer/splitCurrentPaneReducer.ts +0 -66
  192. package/src/client/vzReducer/toggleAutoFollowReducer.ts +0 -12
  193. package/src/client/vzReducer/updatePane.ts +0 -40
  194. package/src/client/vzReducer/updatePresenceIndicatorReducer.ts +0 -30
  195. package/src/client/vzReducer/visualEditorReducer.ts +0 -9
  196. package/src/reactRouterExports.ts +0 -15
  197. package/src/runCode.ts +0 -27
  198. package/src/server/aiChatHandler/aiEditing.ts +0 -152
  199. package/src/server/aiChatHandler/chatOperations.ts +0 -510
  200. package/src/server/aiChatHandler/errorHandling.ts +0 -66
  201. package/src/server/aiChatHandler/index.ts +0 -200
  202. package/src/server/aiChatHandler/llmStreaming.ts +0 -419
  203. package/src/server/aiChatHandler/undoHandler.ts +0 -97
  204. package/src/server/aiChatHandler/validation.ts +0 -24
  205. package/src/server/computeInitialDocument.ts +0 -220
  206. package/src/server/generateAIResponse.ts +0 -188
  207. package/src/server/handleAIAssist.ts +0 -53
  208. package/src/server/handleAIChatUndo.ts +0 -2
  209. package/src/server/handleAICopilot.ts +0 -97
  210. package/src/server/index.ts +0 -388
  211. package/src/server/livekit.ts +0 -24
  212. package/src/server/prettier.ts +0 -115
  213. package/src/submitOperation.ts +0 -25
  214. package/src/types.ts +0 -205
  215. package/src/utils/fileDiff.ts +0 -117
  216. package/src/vite-env.d.ts +0 -10
  217. /package/{src/server/handleAIChatMessage.ts → dist/server/handleAIChatMessage.js} +0 -0
  218. /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
- };