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
package/package.json CHANGED
@@ -1,12 +1,13 @@
1
1
  {
2
2
  "name": "vzcode",
3
- "version": "2.3.0",
3
+ "version": "2.4.0",
4
4
  "description": "Multiplayer code editor system",
5
5
  "main": "src/index.ts",
6
6
  "type": "module",
7
7
  "bin": "dist/cli.js",
8
8
  "files": [
9
- "dist/**"
9
+ "dist/**",
10
+ "src/**"
10
11
  ],
11
12
  "scripts": {
12
13
  "test": "vitest run",
package/src/cli.ts ADDED
@@ -0,0 +1,208 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { spawn } from 'child_process';
4
+ import path from 'path';
5
+ import { fileURLToPath } from 'url';
6
+ import fs from 'fs';
7
+
8
+ // Get the directory of the current module
9
+ const __filename = fileURLToPath(import.meta.url);
10
+ const __dirname = path.dirname(__filename);
11
+
12
+ // Function to find the Tauri binary
13
+ function findTauriApp(): string | null {
14
+ // Look for the Tauri binary in common locations
15
+ const possiblePaths = [
16
+ // Development location (when running from source)
17
+ path.join(
18
+ __dirname,
19
+ '..',
20
+ 'src-tauri',
21
+ 'target',
22
+ 'debug',
23
+ 'vzcode',
24
+ ),
25
+ path.join(
26
+ __dirname,
27
+ '..',
28
+ 'src-tauri',
29
+ 'target',
30
+ 'debug',
31
+ 'vzcode.exe',
32
+ ),
33
+ // Release location (when running from built package)
34
+ path.join(
35
+ __dirname,
36
+ '..',
37
+ 'src-tauri',
38
+ 'target',
39
+ 'release',
40
+ 'vzcode',
41
+ ),
42
+ path.join(
43
+ __dirname,
44
+ '..',
45
+ 'src-tauri',
46
+ 'target',
47
+ 'release',
48
+ 'vzcode.exe',
49
+ ),
50
+ // MacOS app bundle
51
+ path.join(
52
+ __dirname,
53
+ '..',
54
+ 'src-tauri',
55
+ 'target',
56
+ 'debug',
57
+ 'vzcode.app',
58
+ 'Contents',
59
+ 'MacOS',
60
+ 'vzcode',
61
+ ),
62
+ path.join(
63
+ __dirname,
64
+ '..',
65
+ 'src-tauri',
66
+ 'target',
67
+ 'release',
68
+ 'vzcode.app',
69
+ 'Contents',
70
+ 'MacOS',
71
+ 'vzcode',
72
+ ),
73
+ ];
74
+
75
+ for (const binaryPath of possiblePaths) {
76
+ if (fs.existsSync(binaryPath)) {
77
+ return binaryPath;
78
+ }
79
+ }
80
+
81
+ return null;
82
+ }
83
+
84
+ // Function to launch the Tauri app
85
+ function launchTauriApp(workingDirectory: string): void {
86
+ const tauriBinary = findTauriApp();
87
+
88
+ if (!tauriBinary) {
89
+ console.error(
90
+ 'VZCode desktop app not found. Please build it first with: npm run tauri:build',
91
+ );
92
+ console.error(
93
+ 'Falling back to launching browser version...',
94
+ );
95
+ launchBrowserVersion(workingDirectory);
96
+ return;
97
+ }
98
+
99
+ console.log(
100
+ `Launching VZCode desktop app from: ${workingDirectory}`,
101
+ );
102
+
103
+ // First start the server in the background
104
+ startServerInBackground(workingDirectory);
105
+
106
+ // Give the server a moment to start, then launch Tauri
107
+ setTimeout(() => {
108
+ const child = spawn(tauriBinary, [], {
109
+ cwd: workingDirectory,
110
+ stdio: 'inherit',
111
+ detached: true,
112
+ });
113
+
114
+ // Allow the parent process to exit while keeping the child running
115
+ child.unref();
116
+ }, 2000);
117
+ }
118
+
119
+ // Function to start the server in the background
120
+ function startServerInBackground(
121
+ workingDirectory: string,
122
+ ): void {
123
+ // Start the server process in the background
124
+ const serverPath = path.join(
125
+ __dirname,
126
+ 'server',
127
+ 'index.js',
128
+ );
129
+ const serverChild = spawn('node', [serverPath], {
130
+ cwd: workingDirectory,
131
+ stdio: ['ignore', 'pipe', 'pipe'],
132
+ detached: true,
133
+ });
134
+
135
+ // Don't block on the server process
136
+ serverChild.unref();
137
+
138
+ // Log server output for debugging
139
+ if (serverChild.stdout) {
140
+ serverChild.stdout.on('data', (data) => {
141
+ console.log('Server:', data.toString());
142
+ });
143
+ }
144
+
145
+ if (serverChild.stderr) {
146
+ serverChild.stderr.on('data', (data) => {
147
+ console.error('Server error:', data.toString());
148
+ });
149
+ }
150
+ }
151
+
152
+ // Fallback function to launch the browser version
153
+ async function launchBrowserVersion(
154
+ workingDirectory: string,
155
+ ): Promise<void> {
156
+ const { default: open } = await import('open');
157
+ const serverModule = await import('./server/index.js');
158
+
159
+ // Note: The server/index.js will use process.cwd() automatically
160
+ // and we're already in the correct working directory
161
+ console.log(
162
+ `Starting VZCode server from: ${workingDirectory}`,
163
+ );
164
+ }
165
+
166
+ // Main CLI function
167
+ function main(): void {
168
+ const workingDirectory = process.cwd();
169
+
170
+ // Parse command line arguments
171
+ const args = process.argv.slice(2);
172
+
173
+ if (args.includes('--help') || args.includes('-h')) {
174
+ console.log('VZCode - Multiplayer Code Editor');
175
+ console.log('');
176
+ console.log('Usage: vzcode [options]');
177
+ console.log('');
178
+ console.log('Options:');
179
+ console.log(' --help, -h Show this help message');
180
+ console.log(
181
+ ' --browser Force browser version instead of desktop app',
182
+ );
183
+ console.log(
184
+ ' --port=<port> Specify port for server (default: 3030)',
185
+ );
186
+ console.log('');
187
+ console.log('Examples:');
188
+ console.log(
189
+ ' vzcode # Launch desktop app in current directory',
190
+ );
191
+ console.log(
192
+ ' vzcode --browser # Launch browser version',
193
+ );
194
+ console.log(
195
+ ' vzcode --port=4000 # Start server on port 4000',
196
+ );
197
+ return;
198
+ }
199
+
200
+ if (args.includes('--browser')) {
201
+ launchBrowserVersion(workingDirectory);
202
+ } else {
203
+ launchTauriApp(workingDirectory);
204
+ }
205
+ }
206
+
207
+ // Run the CLI
208
+ main();
@@ -0,0 +1,42 @@
1
+ import { EditorView, keymap } from '@codemirror/view';
2
+ import { ShareDBDoc, TabState } from '../../types';
3
+ import { VizContent } from '@vizhub/viz-types';
4
+
5
+ export const AIAssistCodeMirrorKeyMap = ({
6
+ shareDBDoc,
7
+ fileId,
8
+ tabList,
9
+ }: {
10
+ shareDBDoc: ShareDBDoc<VizContent>;
11
+ fileId: string;
12
+ tabList: Array<TabState>;
13
+ }) =>
14
+ keymap.of([
15
+ {
16
+ key: 'control-m',
17
+ run: (view: EditorView) => {
18
+ // if (
19
+ // shareDBDoc.data.aiStreams === undefined ||
20
+ // shareDBDoc.data.aiStreams[mostRecentStreamId] ===
21
+ // null ||
22
+ // shareDBDoc.data.aiStreams[mostRecentStreamId]
23
+ // ?.AIStreamStatus.serverIsRunning !== true
24
+ // ) {
25
+
26
+ // TODO handle starting it
27
+ // startAIAssist({
28
+ // view,
29
+ // shareDBDoc,
30
+ // fileId,
31
+ // tabList,
32
+ // });
33
+
34
+ // TODO handle stopping it
35
+ // } else {
36
+ // haltAIAssist(shareDBDoc);
37
+ // }
38
+
39
+ return true;
40
+ },
41
+ },
42
+ ]);
@@ -0,0 +1,135 @@
1
+ import { useCallback, useContext, useState } from 'react';
2
+ import { OverlayTrigger, Tooltip } from '../../bootstrap';
3
+ import { PaneId, TabState } from '../../../types';
4
+ import { VizFileId } from '@vizhub/viz-types';
5
+ import { VZCodeContext } from '../../VZCodeContext';
6
+ import {
7
+ RequestId,
8
+ generateRequestId,
9
+ } from '../../generateRequestId';
10
+ import { SparklesSVG } from '../../Icons';
11
+ import { startAIAssist } from '../startAIAssist';
12
+ import { editorCacheKey } from '../../useEditorCache';
13
+ import { Spinner } from '../Spinner';
14
+ import './style.scss';
15
+
16
+ const enableStopGeneration = false;
17
+
18
+ export const AIAssistWidget = ({
19
+ aiAssistEndpoint,
20
+ aiAssistOptions,
21
+ aiAssistTooltipText = 'Start AI Assist',
22
+ aiAssistClickOverride,
23
+ activeFileId,
24
+ tabList,
25
+ paneId,
26
+ }: {
27
+ aiAssistEndpoint: string;
28
+ aiAssistOptions: { [key: string]: string };
29
+ aiAssistTooltipText?: string;
30
+ aiAssistClickOverride?: () => void;
31
+ activeFileId: VizFileId;
32
+ tabList: Array<TabState>;
33
+ paneId: PaneId;
34
+ }) => {
35
+ const {
36
+ shareDBDoc,
37
+ editorCache,
38
+ runPrettierRef,
39
+ runCodeRef,
40
+ } = useContext(VZCodeContext);
41
+
42
+ // The stream ID of the most recent request.
43
+ // * If `null`, no request has been made yet.
44
+ // * If non-null, a request has been made, and the
45
+ // server is processing it.
46
+ const [aiStreamId, setAiStreamId] =
47
+ useState<RequestId | null>(null);
48
+
49
+ const handleClick = useCallback(async () => {
50
+ const isCurrentlyGenerationg = aiStreamId !== null;
51
+ if (isCurrentlyGenerationg) {
52
+ // TODO stop generation
53
+ } else {
54
+ const newAiStreamId = generateRequestId();
55
+ setAiStreamId(newAiStreamId);
56
+
57
+ // Wait for generation to finish.
58
+ await startAIAssist({
59
+ view: editorCache.get(
60
+ editorCacheKey(activeFileId, paneId),
61
+ ).editor,
62
+ shareDBDoc,
63
+ fileId: activeFileId,
64
+ tabList,
65
+ aiStreamId: newAiStreamId,
66
+ aiAssistEndpoint,
67
+ aiAssistOptions,
68
+ });
69
+
70
+ // Introduce a delay before running Prettier
71
+ // and the code, to give the ShareDB ops a chance
72
+ // to be applied and to propagate via WebSockets
73
+ // to the client. This is necessary because sometimes
74
+ // the ShareDB ops from the AI assist actually don't
75
+ // reach the client until AFTER the response comes back
76
+ // from `await startAIAssist`.
77
+ await new Promise((resolve) =>
78
+ setTimeout(resolve, 1000),
79
+ );
80
+
81
+ // Trigger a Prettier run after the AI Assist.
82
+ const runPrettier = runPrettierRef.current;
83
+ if (runPrettier !== null) {
84
+ runPrettier();
85
+ }
86
+
87
+ // Run the code after the AI Assist.
88
+ const runCode = runCodeRef.current;
89
+ if (runCode !== null) {
90
+ runCode();
91
+ }
92
+
93
+ // Handles the case that the user has started,
94
+ // stopped, and started again before the first request
95
+ // has finished, by comparing the current stream ID
96
+ // with the stream ID that was active when the request
97
+ // was started.
98
+ setAiStreamId((currentAIStreamId) =>
99
+ currentAIStreamId === newAiStreamId
100
+ ? null
101
+ : currentAIStreamId,
102
+ );
103
+ }
104
+ }, [activeFileId, paneId, aiStreamId]);
105
+
106
+ const showWidget = enableStopGeneration
107
+ ? true
108
+ : aiStreamId === null;
109
+
110
+ return (
111
+ <div className="vz-code-ai-assist-widget">
112
+ {aiStreamId ? (
113
+ <Spinner /> // Show spinner when aiStreamId is not null
114
+ ) : (
115
+ showWidget && (
116
+ <OverlayTrigger
117
+ placement="left"
118
+ overlay={
119
+ <Tooltip id="ai-assist-widget-tooltip">
120
+ {aiAssistTooltipText}
121
+ </Tooltip>
122
+ }
123
+ >
124
+ <i
125
+ className="icon-button icon-button-dark"
126
+ onClick={aiAssistClickOverride || handleClick}
127
+ >
128
+ <SparklesSVG />
129
+ </i>
130
+ </OverlayTrigger>
131
+ )
132
+ )}
133
+ </div>
134
+ );
135
+ };
@@ -0,0 +1,47 @@
1
+ .vz-code-ai-assist-widget {
2
+ position: absolute;
3
+ // bottom: 32px;
4
+ // right: 32px;
5
+ bottom: 16px;
6
+ right: 16px;
7
+ color: var(--vh-color-caution-01);
8
+ }
9
+
10
+ .vh-spinner {
11
+ display: flex;
12
+ height: 100%;
13
+ flex-direction: column;
14
+ align-items: center;
15
+ justify-content: center;
16
+ overflow: hidden;
17
+ }
18
+
19
+ /* Fade in after a delay */
20
+ /* https://stackoverflow.com/questions/29846224/css-animation-with-delay-and-opacity */
21
+ .vh-spinner.fade-in svg {
22
+ animation: 1s linear 0s normal forwards 1 fadein;
23
+ }
24
+
25
+ @keyframes fadein {
26
+ 0% {
27
+ opacity: 0;
28
+ }
29
+ 50% {
30
+ opacity: 0;
31
+ }
32
+ 100% {
33
+ opacity: 1;
34
+ }
35
+ }
36
+
37
+ /* Infinite rotation */
38
+ /* https://stackoverflow.com/questions/6410730/css-endless-rotation-animation */
39
+ .vh-spinner-dots {
40
+ animation: rotate 3s linear infinite;
41
+ }
42
+
43
+ @keyframes rotate {
44
+ to {
45
+ transform: rotate(360deg);
46
+ }
47
+ }
@@ -0,0 +1,76 @@
1
+ // From https://github.com/vizhub-core/vizhub/commit/6ca078935b9771dc6e05a6065bcfd522a4724dcf
2
+ import { useEffect, useState } from 'react';
3
+ import './AIAssistWidget/style.scss';
4
+
5
+ // const minRadius = 4;
6
+ // const maxRadius = 10;
7
+ // const numDots = 8;
8
+ // const wheelRadius = 40;
9
+ // const round = (number) => Math.floor(number * 100) / 100;
10
+ // const dots = [];
11
+ // for (let i = 0; i < numDots; i++) {
12
+ // const angle = (i / numDots) * Math.PI * 2;
13
+ // dots.push({
14
+ // x: round(Math.cos(angle) * wheelRadius),
15
+ // y: round(Math.sin(angle) * wheelRadius),
16
+ // r: round(
17
+ // minRadius +
18
+ // (i / (numDots - 1)) * (maxRadius - minRadius),
19
+ // ),
20
+ // });
21
+ // }
22
+ // console.log(JSON.stringify(dots));
23
+
24
+ // Hardcoded to avoid the computation.
25
+ const dots = [
26
+ { x: 40, y: 0, r: 4 },
27
+ { x: 28.28, y: 28.28, r: 4.85 },
28
+ { x: 0, y: 40, r: 5.71 },
29
+ { x: -28.29, y: 28.28, r: 6.57 },
30
+ { x: -40, y: 0, r: 7.42 },
31
+ { x: -28.29, y: -28.29, r: 8.28 },
32
+ { x: -0.01, y: -40, r: 9.14 },
33
+ { x: 28.28, y: -28.29, r: 10 },
34
+ ];
35
+
36
+ // From https://bl.ocks.org/curran/685fa8300650c4324d571c6b0ecc55de
37
+ // And vizhub-v2/packages/neoFrontend/src/LoadingScreen/index.js
38
+ export const Spinner = ({
39
+ height = 40,
40
+ fill = 'currentcolor',
41
+ fadeIn = true,
42
+ }) => {
43
+ const [show, setShow] = useState(false);
44
+
45
+ // Avoid SSR of all the SVG elements.
46
+ useEffect(() => {
47
+ setShow(true);
48
+ }, []);
49
+
50
+ return (
51
+ show && (
52
+ <div
53
+ className={`vh-spinner${fadeIn ? ' fade-in' : ''}`}
54
+ >
55
+ <svg
56
+ height={height}
57
+ viewBox="0 0 100 100"
58
+ style={{ opacity: 1 }}
59
+ >
60
+ <g transform="translate(50, 50)" fill={fill}>
61
+ <g className="vh-spinner-dots">
62
+ {dots.map(({ x, y, r }, i) => (
63
+ <circle
64
+ key={i}
65
+ cx={x}
66
+ cy={y}
67
+ r={r}
68
+ ></circle>
69
+ ))}
70
+ </g>
71
+ </g>
72
+ </svg>
73
+ </div>
74
+ )
75
+ );
76
+ };
@@ -0,0 +1,27 @@
1
+ import { VizFile } from '@vizhub/viz-types';
2
+
3
+ const maxFileNameLength = 100;
4
+ const maxFileTextLength = 2000;
5
+
6
+ // Formats a file for the prompt.
7
+ export const formatFile = (
8
+ file: VizFile,
9
+ truncateText = true,
10
+ ) => {
11
+ const nameTruncated = file.name
12
+ ?.substring(0, maxFileNameLength)
13
+ .trim();
14
+
15
+ const textTruncated = (
16
+ truncateText
17
+ ? file.text?.substring(0, maxFileTextLength)
18
+ : file.text
19
+ ).trim();
20
+
21
+ return [
22
+ '`' + nameTruncated + '`:',
23
+ '```',
24
+ textTruncated,
25
+ '```',
26
+ ].join('\n');
27
+ };
@@ -0,0 +1,25 @@
1
+ // import { File } from '../../types';
2
+
3
+ // const maxFileNameLength = 100;
4
+ // const maxFileTextLength = 2000;
5
+
6
+ // export const generateFilesContext = (
7
+ // goodFiles: Array<File>,
8
+ // ): string => {
9
+ // const input = goodFiles
10
+ // .map((file) => {
11
+ // const nameSubstring = file.name
12
+ // ?.substring(0, maxFileNameLength)
13
+ // .trim();
14
+
15
+ // const textSubstring = file.text
16
+ // ?.substring(0, maxFileTextLength)
17
+ // .trim();
18
+
19
+ // // Generate Markdown that AI will understand.
20
+ // return `File \`${nameSubstring}\`:\n\`\`\`${textSubstring}\`\`\``;
21
+ // })
22
+ // .join('\n\n');
23
+
24
+ // return input;
25
+ // };
@@ -0,0 +1,2 @@
1
+ export { AIAssistWidget } from './AIAssistWidget';
2
+ export { AIAssistCodeMirrorKeyMap } from './AIAssistCodeMirrorKeyMap';
@@ -0,0 +1,95 @@
1
+ import { EditorView } from 'codemirror';
2
+ import { ShareDBDoc, TabState } from '../../types';
3
+ import {
4
+ VizFile,
5
+ VizFileId,
6
+ VizContent,
7
+ } from '@vizhub/viz-types';
8
+ import { RequestId } from '../generateRequestId';
9
+ import { formatFile } from './formatFile';
10
+
11
+ const debug = false;
12
+
13
+ // Enables inclusion of code from open tabs as context.
14
+ const enableTabContext = false;
15
+
16
+ const defaultAIAssistEndpoint = '/ai-assist';
17
+
18
+ export const startAIAssist = async ({
19
+ view,
20
+ shareDBDoc,
21
+ fileId,
22
+ tabList,
23
+ aiAssistEndpoint = defaultAIAssistEndpoint,
24
+ aiAssistOptions = {},
25
+ aiStreamId,
26
+ }: {
27
+ view: EditorView;
28
+ shareDBDoc: ShareDBDoc<VizContent>;
29
+ fileId: VizFileId;
30
+ tabList: Array<TabState>;
31
+ aiAssistEndpoint?: string;
32
+ aiAssistOptions?: {
33
+ [key: string]: any;
34
+ };
35
+ aiStreamId: RequestId;
36
+ }) => {
37
+ const currentFileName =
38
+ shareDBDoc.data.files[fileId].name;
39
+
40
+ // Generate a version of the current file with a
41
+ // <FILL_ME> in the middle.
42
+ const prefix = view.state.sliceDoc(
43
+ 0,
44
+ view.state.selection.main.from,
45
+ );
46
+ const suffix = view.state.sliceDoc(
47
+ view.state.selection.main.to,
48
+ );
49
+ const combinedText = prefix + '<FILL_ME>' + suffix;
50
+ const currentFile = {
51
+ name: currentFileName,
52
+ text: combinedText,
53
+ };
54
+
55
+ const insertionCursor = view.state.selection.main.to;
56
+
57
+ // The main prompt is the current file, plus the context files.
58
+ let inputText = formatFile(currentFile, false);
59
+
60
+ if (enableTabContext) {
61
+ inputText +=
62
+ '\n\n' +
63
+ tabList
64
+ .filter((tabState) => tabState.fileId !== fileId)
65
+ .map((tabState) =>
66
+ formatFile(
67
+ shareDBDoc.data.files[tabState.fileId],
68
+ ),
69
+ )
70
+ .join('\n\n');
71
+ }
72
+
73
+ const response = await fetch(aiAssistEndpoint, {
74
+ method: 'POST',
75
+ headers: {
76
+ 'Content-Type': 'application/json',
77
+ },
78
+ body: JSON.stringify({
79
+ ...aiAssistOptions,
80
+ inputText,
81
+ fileId,
82
+ insertionCursor,
83
+ aiStreamId,
84
+ }),
85
+ });
86
+
87
+ const responseJson = await response.json();
88
+
89
+ if (responseJson.error) {
90
+ console.error(
91
+ '[startAIAssist] error from server:',
92
+ responseJson.error,
93
+ );
94
+ }
95
+ };