vzcode 0.44.0 → 0.46.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 (32) hide show
  1. package/dist/assets/{index-7MNYa9sP.css → index-0U2uzpmH.css} +1 -1
  2. package/dist/assets/{index-f4vqBOE4.js → index-iJQOJ5Kf.js} +38 -38
  3. package/dist/index.html +2 -2
  4. package/package.json +7 -7
  5. package/src/client/AIAssist/AIAssistCodeMirrorKeyMap.ts +42 -0
  6. package/src/client/AIAssist/AIAssistWidget/index.tsx +96 -0
  7. package/src/client/AIAssist/AIAssistWidget/style.scss +19 -0
  8. package/src/client/AIAssist/generateFilesContext.ts +25 -0
  9. package/src/client/AIAssist/index.ts +3 -0
  10. package/src/client/AIAssist/startAIAssist.ts +93 -0
  11. package/src/client/CodeEditor/getOrCreateEditor.ts +9 -18
  12. package/src/client/CodeEditor/index.tsx +0 -16
  13. package/src/client/CodeEditor/typeScriptCompletions.ts +1 -5
  14. package/src/client/CodeEditor/typeScriptLinter.ts +2 -2
  15. package/src/client/CodeErrorOverlay/index.tsx +23 -18
  16. package/src/client/Icons/SparklesSVG.tsx +31 -0
  17. package/src/client/Icons/StopSVG.tsx +15 -0
  18. package/src/client/Icons/index.tsx +2 -0
  19. package/src/client/VZMiddle.tsx +20 -8
  20. package/src/client/generateRequestId.ts +6 -0
  21. package/src/client/useTypeScript/index.ts +4 -0
  22. package/src/server/computeInitialDocument.js +26 -37
  23. package/src/server/config.js +24 -0
  24. package/src/server/featureFlags.js +7 -1
  25. package/src/server/generateAIResponse.js +101 -0
  26. package/src/server/handleAIAssist.js +11 -100
  27. package/src/server/index.js +50 -58
  28. package/src/server/setupEnv.js +18 -0
  29. package/src/types.ts +23 -23
  30. package/src/client/AIAssist.ts +0 -133
  31. package/src/client/AIAssistWidget/index.tsx +0 -101
  32. package/src/client/AIAssistWidget/style.scss +0 -10
package/dist/index.html CHANGED
@@ -24,8 +24,8 @@
24
24
  href="https://unpkg.com/boxicons@2.0.7/css/boxicons.min.css"
25
25
  rel="stylesheet"
26
26
  />
27
- <script type="module" crossorigin src="/assets/index-f4vqBOE4.js"></script>
28
- <link rel="stylesheet" crossorigin href="/assets/index-7MNYa9sP.css">
27
+ <script type="module" crossorigin src="/assets/index-iJQOJ5Kf.js"></script>
28
+ <link rel="stylesheet" crossorigin href="/assets/index-0U2uzpmH.css">
29
29
  </head>
30
30
  <body>
31
31
  <div id="root"></div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vzcode",
3
- "version": "0.44.0",
3
+ "version": "0.46.0",
4
4
  "description": "Multiplayer code editor system",
5
5
  "main": "src/index.ts",
6
6
  "type": "module",
@@ -99,15 +99,15 @@
99
99
  "ignore": "^5.3.0",
100
100
  "json0-ot-diff": "^1.1.2",
101
101
  "ngrok": "^4.3.3",
102
- "open": "^9.1.0",
103
- "openai": "^4.23.0",
102
+ "open": "^10.0.1",
103
+ "openai": "^4.24.1",
104
104
  "react": "^18.2.0",
105
- "react-bootstrap": "^2.9.1",
105
+ "react-bootstrap": "^2.9.2",
106
106
  "react-dom": "^18.2.0",
107
107
  "sharedb": "^4.1.2",
108
108
  "sharedb-client-browser": "^4.4.0",
109
- "vizhub-ui": "^3.13.0",
110
- "ws": "^8.15.1"
109
+ "vizhub-ui": "^3.14.0",
110
+ "ws": "^8.16.0"
111
111
  },
112
112
  "devDependencies": {
113
113
  "@types/react": "^18.2.45",
@@ -117,6 +117,6 @@
117
117
  "sass": "^1.69.5",
118
118
  "typescript": "^5.3.3",
119
119
  "vite": "^5.0.10",
120
- "vitest": "^1.0.4"
120
+ "vitest": "^1.1.0"
121
121
  }
122
122
  }
@@ -0,0 +1,42 @@
1
+ import { EditorView, keymap } from '@codemirror/view';
2
+ import { ShareDBDoc, VZCodeContent } from '../../types';
3
+ import { TabState } from '../vzReducer';
4
+
5
+ export const AIAssistCodeMirrorKeyMap = ({
6
+ shareDBDoc,
7
+ fileId,
8
+ tabList,
9
+ }: {
10
+ shareDBDoc: ShareDBDoc<VZCodeContent>;
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,96 @@
1
+ import { useCallback, useState } from 'react';
2
+ import {
3
+ FileId,
4
+ ShareDBDoc,
5
+ VZCodeContent,
6
+ } from '../../../types';
7
+ import { Button } from '../../bootstrap';
8
+ import { EditorCache } from '../../useEditorCache';
9
+ import { TabState } from '../../vzReducer';
10
+ import {
11
+ RequestId,
12
+ generateRequestId,
13
+ } from '../../generateRequestId';
14
+ import { SparklesSVG, StopSVG } from '../../Icons';
15
+ import { startAIAssist } from '../startAIAssist';
16
+ import './style.scss';
17
+
18
+ const enableStopGeneration = false;
19
+
20
+ export const AIAssistWidget = ({
21
+ activeFileId,
22
+ shareDBDoc,
23
+ editorCache,
24
+ tabList,
25
+ aiAssistEndpoint,
26
+ aiAssistOptions,
27
+ }: {
28
+ activeFileId: FileId;
29
+ shareDBDoc: ShareDBDoc<VZCodeContent>;
30
+ editorCache: EditorCache;
31
+ tabList: Array<TabState>;
32
+ aiAssistEndpoint: string;
33
+ aiAssistOptions: { [key: string]: string };
34
+ }) => {
35
+ // The stream ID of the most recent request.
36
+ // * If `null`, no request has been made yet.
37
+ // * If non-null, a request has been made, and the
38
+ // server is processing it.
39
+ const [aiStreamId, setAiStreamId] =
40
+ useState<RequestId | null>(null);
41
+
42
+ const handleClick = useCallback(async () => {
43
+ const isCurrentlyGenerationg = aiStreamId !== null;
44
+ if (isCurrentlyGenerationg) {
45
+ // TODO stop generation
46
+ } else {
47
+ const newAiStreamId = generateRequestId();
48
+ setAiStreamId(newAiStreamId);
49
+
50
+ // Wait for generation to finish.
51
+ await startAIAssist({
52
+ view: editorCache.get(activeFileId).editor,
53
+ shareDBDoc,
54
+ fileId: activeFileId,
55
+ tabList,
56
+ aiStreamId: newAiStreamId,
57
+ aiAssistEndpoint,
58
+ aiAssistOptions,
59
+ });
60
+
61
+ // Handles the case that the user has started,
62
+ // stopped, and started again before the first request
63
+ // has finished, bu comparing the current stream ID
64
+ // with the stream ID that was active when the request
65
+ // was started.
66
+ setAiStreamId((currentAIStreamId) =>
67
+ currentAIStreamId === newAiStreamId
68
+ ? null
69
+ : currentAIStreamId,
70
+ );
71
+ }
72
+ }, [activeFileId, aiStreamId]);
73
+
74
+ const showWidget = enableStopGeneration
75
+ ? true
76
+ : aiStreamId === null;
77
+
78
+ return (
79
+ showWidget && (
80
+ <div className="vz-code-ai-assist-widget">
81
+ <Button
82
+ title="Start AI Assist"
83
+ onClick={handleClick}
84
+ >
85
+ {aiStreamId ? (
86
+ enableStopGeneration ? (
87
+ <StopSVG />
88
+ ) : null
89
+ ) : (
90
+ <SparklesSVG />
91
+ )}
92
+ </Button>
93
+ </div>
94
+ )
95
+ );
96
+ };
@@ -0,0 +1,19 @@
1
+ .vz-code-ai-assist-widget {
2
+ position: absolute;
3
+ bottom: 20px;
4
+ right: 20px;
5
+
6
+ .btn {
7
+ /* Center the icon */
8
+ padding: 6px 6px 7px 6px;
9
+ background: #33415b;
10
+ color: #e0e8ff;
11
+ border-radius: 4px;
12
+ border: none;
13
+ }
14
+ .btn:hover,
15
+ .btn:active,
16
+ .btn:focus {
17
+ background: #47546b;
18
+ }
19
+ }
@@ -0,0 +1,25 @@
1
+ import { File } from '../../types';
2
+
3
+ const maxFileNameLength = 100;
4
+ const maxFileTextLength = 2000;
5
+
6
+ export const generateFilesContext = async (
7
+ goodFiles: Array<File>,
8
+ ): Promise<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,3 @@
1
+ export { AIAssistWidget } from './AIAssistWidget';
2
+ export { AIAssistCodeMirrorKeyMap } from './AIAssistCodeMirrorKeyMap';
3
+ export type { AIAssistState } from './AIAssistState';
@@ -0,0 +1,93 @@
1
+ import { EditorView } from 'codemirror';
2
+ import {
3
+ FileId,
4
+ ShareDBDoc,
5
+ VZCodeContent,
6
+ } from '../../types';
7
+ import { TabState } from '../vzReducer';
8
+ import { generateFilesContext } from './generateFilesContext';
9
+ import {
10
+ RequestId,
11
+ generateRequestId,
12
+ } from '../generateRequestId';
13
+
14
+ const debug = 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<VZCodeContent>;
29
+ fileId: FileId;
30
+ tabList: Array<TabState>;
31
+ aiAssistEndpoint?: string;
32
+ aiAssistOptions?: {
33
+ [key: string]: any;
34
+ };
35
+ aiStreamId: RequestId;
36
+ }) => {
37
+ const inputText =
38
+ (await generateFilesContext(
39
+ tabList.map(
40
+ (tabState) =>
41
+ shareDBDoc.data.files[tabState.fileId],
42
+ ),
43
+ )) +
44
+ 'Current File:\n' +
45
+ view.state.sliceDoc(0, view.state.selection.main.to);
46
+
47
+ const insertionCursor = view.state.selection.main.to;
48
+
49
+ if (debug) {
50
+ console.log('[startAIAssist] sending HTTP request:');
51
+ console.log(
52
+ '[startAIAssist] aiAssistEndpoint:',
53
+ aiAssistEndpoint,
54
+ );
55
+ console.log(
56
+ '[startAIAssist] aiAssistOptions:',
57
+ aiAssistOptions,
58
+ );
59
+ console.log('[startAIAssist] inputText:', inputText);
60
+ console.log('[startAIAssist] fileId:', fileId);
61
+ console.log(
62
+ '[startAIAssist] insertionCursor:',
63
+ insertionCursor,
64
+ );
65
+ console.log(
66
+ '[startAIAssist] aiStreamId:',
67
+ aiStreamId,
68
+ );
69
+ }
70
+
71
+ const response = await fetch(aiAssistEndpoint, {
72
+ method: 'POST',
73
+ headers: {
74
+ 'Content-Type': 'application/json',
75
+ },
76
+ body: JSON.stringify({
77
+ ...aiAssistOptions,
78
+ inputText,
79
+ fileId,
80
+ insertionCursor,
81
+ aiStreamId,
82
+ }),
83
+ });
84
+
85
+ const responseJson = await response.json();
86
+
87
+ if (responseJson.error) {
88
+ console.error(
89
+ '[startAIAssist] error from server:',
90
+ responseJson.error,
91
+ );
92
+ }
93
+ };
@@ -34,10 +34,8 @@ import {
34
34
  EditorCacheValue,
35
35
  } from '../useEditorCache';
36
36
  import { ThemeLabel, themeOptionsByLabel } from '../themes';
37
- import { AIAssist } from '../AIAssist';
38
37
  import { typeScriptCompletions } from './typeScriptCompletions';
39
38
  import { typeScriptLinter } from './typeScriptLinter';
40
- import { TabState } from '../vzReducer';
41
39
  import { keymap } from '@codemirror/view';
42
40
  import { basicSetup } from './basicSetup';
43
41
 
@@ -87,10 +85,7 @@ export const getOrCreateEditor = ({
87
85
  onInteract,
88
86
  editorCache,
89
87
  usernameRef,
90
- aiAssistEndpoint,
91
- aiAssistOptions,
92
88
  typeScriptWorker,
93
- tabList,
94
89
  }: {
95
90
  fileId: FileId;
96
91
 
@@ -111,11 +106,7 @@ export const getOrCreateEditor = ({
111
106
  editorCache: EditorCache;
112
107
  usernameRef: React.MutableRefObject<Username>;
113
108
  aiAssistEndpoint?: string;
114
- aiAssistOptions?: {
115
- [key: string]: any;
116
- };
117
109
  typeScriptWorker: Worker;
118
- tabList: Array<TabState>;
119
110
  }): EditorCacheValue => {
120
111
  // Cache hit
121
112
  if (editorCache.has(fileId)) {
@@ -231,15 +222,15 @@ export const getOrCreateEditor = ({
231
222
 
232
223
  extensions.push(rotationIndicator);
233
224
 
234
- extensions.push(
235
- AIAssist({
236
- shareDBDoc,
237
- fileId,
238
- tabList,
239
- aiAssistEndpoint,
240
- aiAssistOptions,
241
- }),
242
- );
225
+ // extensions.push(
226
+ // AIAssistCodeMirrorKeyMap({
227
+ // shareDBDoc,
228
+ // fileId,
229
+ // tabList,
230
+ // aiAssistEndpoint,
231
+ // aiAssistOptions,
232
+ // }),
233
+ // );
243
234
 
244
235
  // Add the extension that provides TypeScript completions.
245
236
  if (enableTypeScriptCompletions) {
@@ -17,7 +17,6 @@ import {
17
17
  } from '../useEditorCache';
18
18
  import { getOrCreateEditor } from './getOrCreateEditor';
19
19
  import './style.scss';
20
- import { TabState } from '../vzReducer';
21
20
 
22
21
  export const CodeEditor = ({
23
22
  activeFileId,
@@ -31,10 +30,7 @@ export const CodeEditor = ({
31
30
  editorWantsFocus,
32
31
  editorNoLongerWantsFocus,
33
32
  username,
34
- aiAssistEndpoint,
35
- aiAssistOptions,
36
33
  typeScriptWorker,
37
- tabList,
38
34
  }: {
39
35
  activeFileId: FileId;
40
36
  shareDBDoc: ShareDBDoc<VZCodeContent> | null;
@@ -66,8 +62,6 @@ export const CodeEditor = ({
66
62
  [key: string]: string;
67
63
  };
68
64
  typeScriptWorker: Worker;
69
-
70
- tabList: Array<TabState>;
71
65
  }) => {
72
66
  const ref = useRef<HTMLDivElement>(null);
73
67
 
@@ -115,10 +109,7 @@ export const CodeEditor = ({
115
109
  onInteract,
116
110
  editorCache,
117
111
  usernameRef,
118
- aiAssistEndpoint,
119
- aiAssistOptions,
120
112
  typeScriptWorker,
121
- tabList,
122
113
  }),
123
114
  [
124
115
  activeFileId,
@@ -130,8 +121,6 @@ export const CodeEditor = ({
130
121
  onInteract,
131
122
  editorCache,
132
123
  usernameRef,
133
- aiAssistEndpoint,
134
- aiAssistOptions,
135
124
  typeScriptWorker,
136
125
  ],
137
126
  );
@@ -154,11 +143,6 @@ export const CodeEditor = ({
154
143
  };
155
144
  }, [shareDBDoc, editorCacheValue]);
156
145
 
157
- // useEffect(() => {
158
- // if (editorWantsFocus) {
159
- // editorCacheValue.editor.focus();
160
- // }
161
-
162
146
  // Focus the editor
163
147
  useLayoutEffect(() => {
164
148
  if (editorWantsFocus) {
@@ -6,11 +6,7 @@ import {
6
6
  AutocompleteRequest,
7
7
  AutocompleteResponse,
8
8
  } from '../useTypeScript/requestTypes';
9
-
10
- type RequestId = string;
11
-
12
- export const generateRequestId = (): RequestId =>
13
- (Math.random() + '').slice(2);
9
+ import { generateRequestId } from '../generateRequestId';
14
10
 
15
11
  export const typeScriptCompletions = ({
16
12
  typeScriptWorker,
@@ -1,9 +1,9 @@
1
- import { generateRequestId } from './typeScriptCompletions';
1
+ import ts from 'typescript';
2
2
  import {
3
3
  LinterResponse,
4
4
  LinterRequest,
5
5
  } from '../useTypeScript/requestTypes';
6
- import ts from 'typescript';
6
+ import { generateRequestId } from '../generateRequestId';
7
7
 
8
8
  export const typeScriptLinter = ({
9
9
  typeScriptWorker,
@@ -1,12 +1,15 @@
1
1
  import { useState, useCallback, useEffect } from 'react';
2
2
  import './style.scss';
3
+ import { VZCodeContent } from '../../types';
3
4
 
4
5
  const enableErrorDismiss = true;
5
6
 
6
7
  export const CodeErrorOverlay = ({
7
8
  errorMessage,
9
+ content,
8
10
  }: {
9
11
  errorMessage: string | null;
12
+ content: VZCodeContent;
10
13
  }) => {
11
14
  const [isOverlayVisible, setIsOverlayVisible] =
12
15
  useState(true);
@@ -18,36 +21,38 @@ export const CodeErrorOverlay = ({
18
21
  }
19
22
  }, [errorMessage]);
20
23
 
24
+ // If the content changes, set the overlay to be not visible.
21
25
  useEffect(() => {
26
+ setIsOverlayVisible(false);
27
+ }, [content]);
28
+
29
+ // Set the visibility state to false when the button is clicked
30
+ const handleCloseClick = useCallback(() => {
31
+ setIsOverlayVisible(false);
32
+ }, []);
33
+
34
+ // If the user presses the escape key, close the overlay.
35
+ useEffect(() => {
36
+ const handleKeyDown = (event: KeyboardEvent) => {
37
+ if (event.key === 'Escape') {
38
+ handleCloseClick();
39
+ }
40
+ };
22
41
  document.addEventListener(
23
42
  'keydown',
24
- detectKeyDown,
43
+ handleKeyDown,
25
44
  true,
26
45
  );
27
46
 
47
+ // Remove the event listener when the component unmounts
28
48
  return () => {
29
- // Remove the event listener when the component unmounts
30
49
  document.removeEventListener(
31
50
  'keydown',
32
- detectKeyDown,
51
+ handleKeyDown,
33
52
  true,
34
53
  );
35
54
  };
36
- }, []);
37
-
38
- const handleCloseClick = useCallback(() => {
39
- // Set the visibility state to false when the button is clicked
40
- setIsOverlayVisible(false);
41
- }, []);
42
-
43
- const detectKeyDown = useCallback(
44
- (event: KeyboardEvent) => {
45
- if (event.key === 'Escape') {
46
- handleCloseClick();
47
- }
48
- },
49
- [handleCloseClick],
50
- );
55
+ }, [handleCloseClick]);
51
56
 
52
57
  return isOverlayVisible && errorMessage !== null ? (
53
58
  <div className="vz-code-error-overlay">
@@ -0,0 +1,31 @@
1
+ export const SparklesSVG = () => (
2
+ <svg
3
+ xmlns="http://www.w3.org/2000/svg"
4
+ width="24"
5
+ height="24"
6
+ viewBox="0 0 24 24"
7
+ fill="none"
8
+ >
9
+ <path
10
+ d="M9.8132 15.9038L9 18.75L8.1868 15.9038C7.75968 14.4089 6.59112 13.2403 5.09619 12.8132L2.25 12L5.09619 11.1868C6.59113 10.7597 7.75968 9.59112 8.1868 8.09619L9 5.25L9.8132 8.09619C10.2403 9.59113 11.4089 10.7597 12.9038 11.1868L15.75 12L12.9038 12.8132C11.4089 13.2403 10.2403 14.4089 9.8132 15.9038Z"
11
+ stroke="currentColor"
12
+ strokeWidth="1.5"
13
+ strokeLinecap="round"
14
+ strokeLinejoin="round"
15
+ />
16
+ <path
17
+ d="M18.2589 8.71454L18 9.75L17.7411 8.71454C17.4388 7.50533 16.4947 6.56117 15.2855 6.25887L14.25 6L15.2855 5.74113C16.4947 5.43883 17.4388 4.49467 17.7411 3.28546L18 2.25L18.2589 3.28546C18.5612 4.49467 19.5053 5.43883 20.7145 5.74113L21.75 6L20.7145 6.25887C19.5053 6.56117 18.5612 7.50533 18.2589 8.71454Z"
18
+ stroke="currentColor"
19
+ strokeWidth="1.5"
20
+ strokeLinecap="round"
21
+ strokeLinejoin="round"
22
+ />
23
+ <path
24
+ d="M16.8942 20.5673L16.5 21.75L16.1058 20.5673C15.8818 19.8954 15.3546 19.3682 14.6827 19.1442L13.5 18.75L14.6827 18.3558C15.3546 18.1318 15.8818 17.6046 16.1058 16.9327L16.5 15.75L16.8942 16.9327C17.1182 17.6046 17.6454 18.1318 18.3173 18.3558L19.5 18.75L18.3173 19.1442C17.6454 19.3682 17.1182 19.8954 16.8942 20.5673Z"
25
+ stroke="currentColor"
26
+ strokeWidth="1.5"
27
+ strokeLinecap="round"
28
+ strokeLinejoin="round"
29
+ />
30
+ </svg>
31
+ );
@@ -0,0 +1,15 @@
1
+ // From Wikimedia
2
+ // https://commons.wikimedia.org/wiki/File:Stopsign.svg
3
+ export const StopSVG = () => (
4
+ <svg
5
+ width="15pt"
6
+ height="15pt"
7
+ version="1.1"
8
+ viewBox="0 0 15 15"
9
+ xmlns="http://www.w3.org/2000/svg"
10
+ >
11
+ <g transform="scale(.75)" fill="#fff" stroke="#000">
12
+ <path d="m17.5 0.5c1.1055 0 2 0.89453 2 2v15c0 1.1055-0.89453 2-2 2h-15c-1.1055 0-2-0.89453-2-2v-15c0-1.1055 0.89453-2 2-2z" />
13
+ </g>
14
+ </svg>
15
+ );
@@ -0,0 +1,2 @@
1
+ export { SparklesSVG } from './SparklesSVG';
2
+ export { StopSVG } from './StopSVG';
@@ -4,7 +4,7 @@ import { TabList } from './TabList';
4
4
  import { CodeEditor } from './CodeEditor';
5
5
  import { CodeErrorOverlay } from './CodeErrorOverlay';
6
6
  import { PresenceNotifications } from './PresenceNotifications';
7
- import { AIAssistWidget } from './AIAssistWidget';
7
+ import { AIAssistWidget } from './AIAssist/AIAssistWidget';
8
8
  import { VZCodeContext } from './VZCodeContext';
9
9
 
10
10
  // The middle portion of the VZCode environment, containing:
@@ -13,7 +13,15 @@ import { VZCodeContext } from './VZCodeContext';
13
13
  // * The code error overlay
14
14
  // * The presence notifications
15
15
  // * The UI for AI Assist
16
- export const VZMiddle = ({ enableAIAssist = true }) => {
16
+ export const VZMiddle = ({
17
+ enableAIAssist = true,
18
+ aiAssistEndpoint,
19
+ aiAssistOptions,
20
+ }: {
21
+ enableAIAssist?: boolean;
22
+ aiAssistEndpoint: string;
23
+ aiAssistOptions: { [key: string]: string };
24
+ }) => {
17
25
  const { codeEditorWidth } = useContext(
18
26
  SplitPaneResizeContext,
19
27
  );
@@ -70,22 +78,26 @@ export const VZMiddle = ({ enableAIAssist = true }) => {
70
78
  }
71
79
  username={username}
72
80
  typeScriptWorker={typeScriptWorker}
73
- tabList={tabList}
74
81
  />
75
82
  ) : null}
76
- <CodeErrorOverlay errorMessage={errorMessage} />
77
- <PresenceNotifications
78
- docPresence={docPresence}
79
- localPresence={localPresence}
80
- />
81
83
  {enableAIAssist && content && activeFileId ? (
82
84
  <AIAssistWidget
83
85
  activeFileId={activeFileId}
84
86
  shareDBDoc={shareDBDoc}
85
87
  editorCache={editorCache}
86
88
  tabList={tabList}
89
+ aiAssistEndpoint={aiAssistEndpoint}
90
+ aiAssistOptions={aiAssistOptions}
87
91
  />
88
92
  ) : null}
93
+ <CodeErrorOverlay
94
+ errorMessage={errorMessage}
95
+ content={content}
96
+ />
97
+ <PresenceNotifications
98
+ docPresence={docPresence}
99
+ localPresence={localPresence}
100
+ />
89
101
  </div>
90
102
  ) : null;
91
103
  };
@@ -0,0 +1,6 @@
1
+ export type RequestId = string;
2
+
3
+ // Generates a random string of digits.
4
+ // e.g. "6891835662438279"
5
+ export const generateRequestId = (): RequestId =>
6
+ (Math.random() + '').slice(2);
@@ -27,6 +27,10 @@ export const useTypeScript = ({
27
27
 
28
28
  const debounceUpdateContent = useCallback(
29
29
  (content: VZCodeContent) => {
30
+ // Handle the case where the content has not yet been loaded.
31
+ if (content === null) {
32
+ return;
33
+ }
30
34
  clearTimeout(debounceTimeoutId.current);
31
35
  debounceTimeoutId.current = window.setTimeout(() => {
32
36
  typeScriptWorker.postMessage({