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.
- package/dist/assets/{index-7MNYa9sP.css → index-0U2uzpmH.css} +1 -1
- package/dist/assets/{index-f4vqBOE4.js → index-iJQOJ5Kf.js} +38 -38
- package/dist/index.html +2 -2
- package/package.json +7 -7
- package/src/client/AIAssist/AIAssistCodeMirrorKeyMap.ts +42 -0
- package/src/client/AIAssist/AIAssistWidget/index.tsx +96 -0
- package/src/client/AIAssist/AIAssistWidget/style.scss +19 -0
- package/src/client/AIAssist/generateFilesContext.ts +25 -0
- package/src/client/AIAssist/index.ts +3 -0
- package/src/client/AIAssist/startAIAssist.ts +93 -0
- package/src/client/CodeEditor/getOrCreateEditor.ts +9 -18
- package/src/client/CodeEditor/index.tsx +0 -16
- package/src/client/CodeEditor/typeScriptCompletions.ts +1 -5
- package/src/client/CodeEditor/typeScriptLinter.ts +2 -2
- package/src/client/CodeErrorOverlay/index.tsx +23 -18
- package/src/client/Icons/SparklesSVG.tsx +31 -0
- package/src/client/Icons/StopSVG.tsx +15 -0
- package/src/client/Icons/index.tsx +2 -0
- package/src/client/VZMiddle.tsx +20 -8
- package/src/client/generateRequestId.ts +6 -0
- package/src/client/useTypeScript/index.ts +4 -0
- package/src/server/computeInitialDocument.js +26 -37
- package/src/server/config.js +24 -0
- package/src/server/featureFlags.js +7 -1
- package/src/server/generateAIResponse.js +101 -0
- package/src/server/handleAIAssist.js +11 -100
- package/src/server/index.js +50 -58
- package/src/server/setupEnv.js +18 -0
- package/src/types.ts +23 -23
- package/src/client/AIAssist.ts +0 -133
- package/src/client/AIAssistWidget/index.tsx +0 -101
- 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-
|
|
28
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
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.
|
|
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": "^
|
|
103
|
-
"openai": "^4.
|
|
102
|
+
"open": "^10.0.1",
|
|
103
|
+
"openai": "^4.24.1",
|
|
104
104
|
"react": "^18.2.0",
|
|
105
|
-
"react-bootstrap": "^2.9.
|
|
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.
|
|
110
|
-
"ws": "^8.
|
|
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
|
|
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,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
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
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
|
|
1
|
+
import ts from 'typescript';
|
|
2
2
|
import {
|
|
3
3
|
LinterResponse,
|
|
4
4
|
LinterRequest,
|
|
5
5
|
} from '../useTypeScript/requestTypes';
|
|
6
|
-
import
|
|
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
|
-
|
|
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
|
-
|
|
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
|
+
);
|
package/src/client/VZMiddle.tsx
CHANGED
|
@@ -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 = ({
|
|
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
|
};
|
|
@@ -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({
|