vzcode 0.28.0 → 0.30.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-836bdce7.css +1 -0
- package/dist/assets/index-c3a6c525.js +115 -0
- package/dist/assets/worker-ed0e2778.js +366 -0
- package/dist/index.html +2 -2
- package/package.json +10 -6
- package/src/client/AIAssist.ts +110 -19
- package/src/client/AIAssistWidget/index.tsx +101 -0
- package/src/client/AIAssistWidget/style.scss +10 -0
- package/src/client/App/VZCodeContext.tsx +249 -0
- package/src/client/App/VZLeft.tsx +56 -0
- package/src/client/App/VZMiddle.tsx +91 -0
- package/src/client/App/VZRight.tsx +15 -0
- package/src/client/App/index.tsx +106 -0
- package/src/client/{style.scss → App/style.scss} +14 -3
- package/src/client/App/useShareDB.ts +110 -0
- package/src/client/CodeEditor/getOrCreateEditor.ts +54 -6
- package/src/client/CodeEditor/index.tsx +9 -0
- package/src/client/CodeEditor/json1PresenceDisplay.ts +38 -9
- package/src/client/CodeEditor/typeScriptCompletions.ts +108 -0
- package/src/client/CodeEditor/typeScriptLinter.ts +45 -0
- package/src/client/CodeErrorOverlay/index.tsx +27 -1
- package/src/client/CodeErrorOverlay/style.scss +11 -11
- package/src/client/SplitPaneResizeContext.tsx +198 -39
- package/src/client/TabList/index.tsx +1 -27
- package/src/client/{Resizer → VZResizer}/index.tsx +37 -13
- package/src/client/VZSettings.tsx +1 -1
- package/src/client/VZSidebar/CreateFileModal.tsx +93 -0
- package/src/client/VZSidebar/DeleteConfirmationModal.tsx +42 -24
- package/src/client/VZSidebar/index.tsx +69 -9
- package/src/client/useFileCRUD.ts +19 -19
- package/src/client/useOpenDirectories.ts +12 -8
- package/src/client/usePrettier/index.ts +13 -1
- package/src/client/useSubmitOperation.ts +16 -19
- package/src/client/useTypeScript/index.ts +44 -0
- package/src/client/useTypeScript/requestTypes.ts +29 -0
- package/src/client/useTypeScript/worker.ts +242 -0
- package/src/client/usernameLocalStorage.ts +1 -1
- package/src/client/vzReducer/createInitialState.ts +4 -5
- package/src/index.ts +5 -1
- package/src/server/handleAIAssist.js +76 -12
- package/src/server/index.js +55 -2
- package/src/types.ts +78 -1
- package/dist/assets/index-a9585cbc.js +0 -111
- package/dist/assets/index-d30c9f1b.css +0 -1
- package/src/client/App.tsx +0 -286
- /package/src/client/{Resizer → VZResizer}/styles.scss +0 -0
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
import * as tsvfs from '@typescript/vfs';
|
|
2
|
+
import ts from 'typescript';
|
|
3
|
+
import { File, Files, VZCodeContent } from '../../types';
|
|
4
|
+
import {
|
|
5
|
+
AutocompleteRequest,
|
|
6
|
+
AutocompleteResponse,
|
|
7
|
+
LinterRequest,
|
|
8
|
+
LinterResponse,
|
|
9
|
+
} from './requestTypes';
|
|
10
|
+
|
|
11
|
+
let isFileSystemInitialized = false;
|
|
12
|
+
|
|
13
|
+
let env: tsvfs.VirtualTypeScriptEnvironment = null;
|
|
14
|
+
|
|
15
|
+
const debug = false;
|
|
16
|
+
|
|
17
|
+
// replace .js or .jsx with .ts or .tsx,
|
|
18
|
+
// to support TypeScript completions on non-TS files.
|
|
19
|
+
const getTSFileName = (fileName: string) => {
|
|
20
|
+
if (fileName.endsWith('.js')) {
|
|
21
|
+
return fileName.replace('.js', '.ts');
|
|
22
|
+
}
|
|
23
|
+
if (fileName.endsWith('.jsx')) {
|
|
24
|
+
return fileName.replace('.jsx', '.tsx');
|
|
25
|
+
}
|
|
26
|
+
return fileName;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
// Returns true if the file name ends with `.ts` or `.tsx`.
|
|
30
|
+
const isTS = (fileName: string) => {
|
|
31
|
+
return (
|
|
32
|
+
fileName.endsWith('.ts') || fileName.endsWith('.tsx')
|
|
33
|
+
);
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
// This is a place for things we only do _once_.
|
|
37
|
+
const initializeFileSystem = async () => {
|
|
38
|
+
if (debug) {
|
|
39
|
+
console.log('initializeFileSystem');
|
|
40
|
+
}
|
|
41
|
+
const compilerOptions: ts.CompilerOptions = {};
|
|
42
|
+
|
|
43
|
+
// `true` breaks in a Web Worker because
|
|
44
|
+
// this uses `localStorage` under the hood,
|
|
45
|
+
// which is not available in a Web Worker.
|
|
46
|
+
const cache = false;
|
|
47
|
+
|
|
48
|
+
const fsMap = await tsvfs.createDefaultMapFromCDN(
|
|
49
|
+
compilerOptions,
|
|
50
|
+
ts.version,
|
|
51
|
+
cache,
|
|
52
|
+
ts,
|
|
53
|
+
);
|
|
54
|
+
const sys: ts.System = tsvfs.createSystem(fsMap);
|
|
55
|
+
|
|
56
|
+
// We'll add files to this later.
|
|
57
|
+
const rootFiles = [];
|
|
58
|
+
|
|
59
|
+
env = tsvfs.createVirtualTypeScriptEnvironment(
|
|
60
|
+
sys,
|
|
61
|
+
rootFiles,
|
|
62
|
+
ts,
|
|
63
|
+
compilerOptions,
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
if (debug) {
|
|
67
|
+
console.log('initializeFileSystem done');
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const setFile = (tsFileName: string, text: string) => {
|
|
72
|
+
const existingFile = env.getSourceFile(tsFileName);
|
|
73
|
+
if (existingFile === undefined) {
|
|
74
|
+
env.createFile(tsFileName, text);
|
|
75
|
+
} else {
|
|
76
|
+
env.updateFile(tsFileName, text);
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
// Inspired by: https://stackblitz.com/edit/codemirror-6-typescript?file=client%2Findex.ts%3AL44-L44
|
|
81
|
+
const convertToCodeMirrorDiagnostic = (
|
|
82
|
+
tsErrors: ts.Diagnostic[],
|
|
83
|
+
) => {
|
|
84
|
+
return tsErrors.map((tsError: ts.Diagnostic) => ({
|
|
85
|
+
from: tsError.start,
|
|
86
|
+
to: tsError.start + tsError.length,
|
|
87
|
+
severity: 'error',
|
|
88
|
+
message:
|
|
89
|
+
typeof tsError.messageText === 'string'
|
|
90
|
+
? tsError.messageText
|
|
91
|
+
: tsError.messageText.messageText,
|
|
92
|
+
}));
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
onmessage = async ({ data }) => {
|
|
96
|
+
if (debug) {
|
|
97
|
+
console.log('message received');
|
|
98
|
+
}
|
|
99
|
+
// Initialize the file system.
|
|
100
|
+
if (!isFileSystemInitialized) {
|
|
101
|
+
isFileSystemInitialized = true;
|
|
102
|
+
await initializeFileSystem();
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Sanity check.
|
|
106
|
+
if (env === null) {
|
|
107
|
+
throw new Error('File system not initialized');
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Handle the update-content event, which
|
|
111
|
+
// updates the files as they change.
|
|
112
|
+
// This handles the cases where:
|
|
113
|
+
// * The file system is populated for the first time.
|
|
114
|
+
// * Files are edited by remote users.
|
|
115
|
+
if (data.event === 'update-content') {
|
|
116
|
+
if (debug) {
|
|
117
|
+
console.log('update-content message received');
|
|
118
|
+
}
|
|
119
|
+
// Unpack the files
|
|
120
|
+
const content: VZCodeContent = data.details;
|
|
121
|
+
const files: Files = content.files;
|
|
122
|
+
|
|
123
|
+
// Iterate over the files
|
|
124
|
+
for (const fileId of Object.keys(files)) {
|
|
125
|
+
const file: File = files[fileId];
|
|
126
|
+
const { name, text } = file;
|
|
127
|
+
|
|
128
|
+
const tsFileName = getTSFileName(name);
|
|
129
|
+
|
|
130
|
+
if (!isTS(tsFileName)) {
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
setFile(tsFileName, text);
|
|
135
|
+
// TODO - Handle renaming files.
|
|
136
|
+
// TODO - Handle deleting files.
|
|
137
|
+
// TODO - Handle directories.
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (data.event === 'autocomplete-request') {
|
|
142
|
+
if (debug) {
|
|
143
|
+
console.log('autocomplete-request message received');
|
|
144
|
+
}
|
|
145
|
+
// Should not happen.
|
|
146
|
+
if (env === null) {
|
|
147
|
+
console.log('env is null');
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Example of `data`:
|
|
152
|
+
// {
|
|
153
|
+
// "event": "autocomplete-request",
|
|
154
|
+
// "pos": 8,
|
|
155
|
+
// "location": "index.js",
|
|
156
|
+
// "requestId": "0.9090605799171392"
|
|
157
|
+
// }
|
|
158
|
+
|
|
159
|
+
const autocompleteRequest: AutocompleteRequest = data;
|
|
160
|
+
const { fileName, fileContent, position, requestId } =
|
|
161
|
+
autocompleteRequest;
|
|
162
|
+
|
|
163
|
+
const tsFileName = getTSFileName(fileName);
|
|
164
|
+
|
|
165
|
+
let completions = null;
|
|
166
|
+
if (isTS(tsFileName)) {
|
|
167
|
+
// Update the file in the file system to the
|
|
168
|
+
// absolute latest version. This is critical
|
|
169
|
+
// for correct completions.
|
|
170
|
+
setFile(tsFileName, fileContent);
|
|
171
|
+
|
|
172
|
+
completions =
|
|
173
|
+
env.languageService.getCompletionsAtPosition(
|
|
174
|
+
tsFileName,
|
|
175
|
+
position,
|
|
176
|
+
{},
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const autocompleteResponse: AutocompleteResponse = {
|
|
181
|
+
event: 'post-completions',
|
|
182
|
+
completions,
|
|
183
|
+
requestId,
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
postMessage(autocompleteResponse);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (data.event === 'lint-request') {
|
|
190
|
+
if (debug) {
|
|
191
|
+
console.log('Lint Request');
|
|
192
|
+
}
|
|
193
|
+
const linterRequest: LinterRequest = data;
|
|
194
|
+
const { fileName, fileContent, requestId } =
|
|
195
|
+
linterRequest;
|
|
196
|
+
|
|
197
|
+
const tsFileName = getTSFileName(fileName);
|
|
198
|
+
let tsErrors = null;
|
|
199
|
+
// Since we are also updating the server when we autocomplete we do not need to update
|
|
200
|
+
if (isTS(tsFileName)) {
|
|
201
|
+
setFile(tsFileName, fileContent);
|
|
202
|
+
// Creates an array of diagnostic objects containing
|
|
203
|
+
// both semantic and syntactic diagnostics.
|
|
204
|
+
tsErrors = env.languageService
|
|
205
|
+
.getSemanticDiagnostics(tsFileName)
|
|
206
|
+
.concat(
|
|
207
|
+
env.languageService.getSyntacticDiagnostics(
|
|
208
|
+
tsFileName,
|
|
209
|
+
),
|
|
210
|
+
);
|
|
211
|
+
|
|
212
|
+
// Be less aggressive for non-TS files,
|
|
213
|
+
// e.g. files that end in .js or .jsx.
|
|
214
|
+
if (!isTS(fileName)) {
|
|
215
|
+
// This code is for errors like:
|
|
216
|
+
// "Binding element 'data' implicitly has an 'any' type."
|
|
217
|
+
const LINT_ERROR_CODE_ANY = 7031;
|
|
218
|
+
|
|
219
|
+
// This code is for errors like:
|
|
220
|
+
// "Cannot find module 'd3' or its corresponding type declarations."
|
|
221
|
+
const LINT_ERROR_CODE_IMPORT = 2307;
|
|
222
|
+
|
|
223
|
+
if (debug) {
|
|
224
|
+
console.log(tsErrors);
|
|
225
|
+
}
|
|
226
|
+
tsErrors = tsErrors.filter(
|
|
227
|
+
(error: { code: number }) =>
|
|
228
|
+
error.code !== LINT_ERROR_CODE_ANY &&
|
|
229
|
+
error.code !== LINT_ERROR_CODE_IMPORT,
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
tsErrors = convertToCodeMirrorDiagnostic(tsErrors);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const linterResponse: LinterResponse = {
|
|
236
|
+
event: 'post-error-linter',
|
|
237
|
+
tsErrors,
|
|
238
|
+
requestId,
|
|
239
|
+
};
|
|
240
|
+
postMessage(linterResponse);
|
|
241
|
+
}
|
|
242
|
+
};
|
|
@@ -1,19 +1,18 @@
|
|
|
1
1
|
import { VZState } from '.';
|
|
2
|
+
import { Username } from '../../types';
|
|
2
3
|
import { ThemeLabel } from '../themes';
|
|
3
4
|
|
|
4
5
|
export const createInitialState = ({
|
|
5
6
|
defaultTheme,
|
|
6
|
-
initialUsername,
|
|
7
|
+
initialUsername = 'Anonymous',
|
|
7
8
|
}: {
|
|
8
9
|
defaultTheme: ThemeLabel;
|
|
9
|
-
initialUsername?:
|
|
10
|
+
initialUsername?: Username;
|
|
10
11
|
}): VZState => ({
|
|
11
12
|
tabList: [],
|
|
12
13
|
activeFileId: null,
|
|
13
14
|
theme: defaultTheme,
|
|
14
15
|
isSettingsOpen: false,
|
|
15
16
|
editorWantsFocus: false,
|
|
16
|
-
|
|
17
|
-
// TODO unify this with App.tsx
|
|
18
|
-
username: initialUsername || 'Anonymous',
|
|
17
|
+
username: initialUsername,
|
|
19
18
|
});
|
package/src/index.ts
CHANGED
|
@@ -13,13 +13,17 @@ export type {
|
|
|
13
13
|
ThemeOption,
|
|
14
14
|
} from './client/themes';
|
|
15
15
|
|
|
16
|
+
export { VZCodeProvider } from './client/App/VZCodeContext';
|
|
16
17
|
export { VZSidebar } from './client/VZSidebar';
|
|
17
18
|
export { VZSettings } from './client/VZSettings';
|
|
19
|
+
export { VZResizer } from './client/VZResizer';
|
|
20
|
+
export { VZLeft } from './client/App/VZLeft';
|
|
21
|
+
export { VZMiddle } from './client/App/VZMiddle';
|
|
22
|
+
|
|
18
23
|
export {
|
|
19
24
|
SplitPaneResizeProvider,
|
|
20
25
|
SplitPaneResizeContext,
|
|
21
26
|
} from './client/SplitPaneResizeContext';
|
|
22
|
-
export { Resizer } from './client/Resizer';
|
|
23
27
|
export { TabList } from './client/TabList';
|
|
24
28
|
export { CodeEditor } from './client/CodeEditor';
|
|
25
29
|
export { usePrettier } from './client/usePrettier';
|
|
@@ -1,45 +1,99 @@
|
|
|
1
1
|
import OpenAI from 'openai';
|
|
2
|
-
import { editOp } from 'ot-json1';
|
|
2
|
+
import { editOp, type, replaceOp } from 'ot-json1';
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
// Feature flag to slow down AI for development/testing
|
|
5
|
+
const slowdown = false;
|
|
6
|
+
|
|
7
|
+
let openai;
|
|
8
|
+
if (process.env.OPENAI_API_KEY !== undefined) {
|
|
9
|
+
openai = new OpenAI();
|
|
10
|
+
}
|
|
5
11
|
|
|
6
12
|
export async function generateAIResponse({
|
|
7
13
|
inputText,
|
|
8
14
|
insertionCursor,
|
|
9
15
|
fileId,
|
|
16
|
+
streamId,
|
|
10
17
|
shareDBDoc,
|
|
11
18
|
}) {
|
|
12
|
-
|
|
13
|
-
|
|
19
|
+
function accomodateDocChanges(op, source) {
|
|
20
|
+
if (!opComesFromAIAssist(op, source)) {
|
|
21
|
+
if (op !== null) {
|
|
22
|
+
insertionCursor = type
|
|
23
|
+
.transformPosition(
|
|
24
|
+
['files', fileId, 'text', insertionCursor],
|
|
25
|
+
op,
|
|
26
|
+
)
|
|
27
|
+
.slice(-1)[0];
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
shareDBDoc.on('op', accomodateDocChanges);
|
|
33
|
+
|
|
34
|
+
streams[streamId] = await openai.chat.completions.create({
|
|
35
|
+
// model: 'gpt-3.5-turbo',
|
|
36
|
+
model: 'gpt-4',
|
|
37
|
+
|
|
14
38
|
messages: [
|
|
15
39
|
{
|
|
16
40
|
role: 'system',
|
|
17
41
|
content:
|
|
18
|
-
'Write typescript or javascript code
|
|
42
|
+
'Write typescript or javascript code to continue the current file, given the other files for context. Use // for comments.',
|
|
19
43
|
},
|
|
20
44
|
{ role: 'user', content: inputText },
|
|
21
45
|
],
|
|
22
46
|
stream: true,
|
|
23
47
|
});
|
|
24
48
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
for await (const part of stream) {
|
|
49
|
+
for await (const part of streams[streamId]) {
|
|
28
50
|
const op = editOp(
|
|
29
51
|
['files', fileId, 'text'],
|
|
30
52
|
'text-unicode',
|
|
31
53
|
[
|
|
32
|
-
|
|
54
|
+
insertionCursor,
|
|
33
55
|
part.choices[0]?.delta?.content || '',
|
|
34
56
|
],
|
|
35
57
|
);
|
|
36
58
|
|
|
37
|
-
shareDBDoc.submitOp(op);
|
|
38
|
-
|
|
39
|
-
|
|
59
|
+
shareDBDoc.submitOp(op, { source: AISourceName });
|
|
60
|
+
|
|
61
|
+
// Wait for 500ms
|
|
62
|
+
if (slowdown) {
|
|
63
|
+
await new Promise((resolve) => {
|
|
64
|
+
setTimeout(resolve, 1000);
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
insertionCursor += (
|
|
69
|
+
part.choices[0]?.delta?.content || ''
|
|
70
|
+
).length;
|
|
40
71
|
}
|
|
72
|
+
shareDBDoc.off('op', accomodateDocChanges);
|
|
73
|
+
|
|
74
|
+
const confirmCompletedOperation = replaceOp(
|
|
75
|
+
[
|
|
76
|
+
'aiStreams',
|
|
77
|
+
streamId,
|
|
78
|
+
'AIStreamStatus',
|
|
79
|
+
'serverIsRunning',
|
|
80
|
+
],
|
|
81
|
+
true,
|
|
82
|
+
false,
|
|
83
|
+
);
|
|
84
|
+
shareDBDoc.submitOp(confirmCompletedOperation, {
|
|
85
|
+
source: 'AIServer',
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const AISourceName = 'AIAssist';
|
|
90
|
+
|
|
91
|
+
function opComesFromAIAssist(ops, source) {
|
|
92
|
+
return source === AISourceName;
|
|
41
93
|
}
|
|
42
94
|
|
|
95
|
+
const streams = {};
|
|
96
|
+
|
|
43
97
|
export const handleAIAssist =
|
|
44
98
|
(shareDBDoc) => async (req, res) => {
|
|
45
99
|
const {
|
|
@@ -67,3 +121,13 @@ export const handleAIAssist =
|
|
|
67
121
|
});
|
|
68
122
|
}
|
|
69
123
|
};
|
|
124
|
+
|
|
125
|
+
export function haltGeneration(streamId) {
|
|
126
|
+
const stream = streams[streamId];
|
|
127
|
+
|
|
128
|
+
// Stream can be undefined here if the user
|
|
129
|
+
// clicks start and stop very quickly.
|
|
130
|
+
if (stream) {
|
|
131
|
+
stream.controller.abort();
|
|
132
|
+
}
|
|
133
|
+
}
|
package/src/server/index.js
CHANGED
|
@@ -13,7 +13,12 @@ import dotenv from 'dotenv';
|
|
|
13
13
|
import { computeInitialDocument } from './computeInitialDocument.js';
|
|
14
14
|
import { json1Presence } from '../ot.js';
|
|
15
15
|
import bodyParser from 'body-parser';
|
|
16
|
-
import {
|
|
16
|
+
import {
|
|
17
|
+
generateAIResponse,
|
|
18
|
+
haltGeneration,
|
|
19
|
+
handleAIAssist,
|
|
20
|
+
} from './handleAIAssist.js';
|
|
21
|
+
import { replaceOp } from 'ot-json1';
|
|
17
22
|
|
|
18
23
|
dotenv.config({ path: '../../.env' });
|
|
19
24
|
|
|
@@ -208,12 +213,60 @@ function debounceSave() {
|
|
|
208
213
|
|
|
209
214
|
// Subscribe to listen for modifications
|
|
210
215
|
shareDBDoc.subscribe(() => {
|
|
211
|
-
shareDBDoc.on('op', (op) => {
|
|
216
|
+
shareDBDoc.on('op', (op, source) => {
|
|
212
217
|
if (shareDBDoc.data.isInteracting) {
|
|
213
218
|
throttleSave();
|
|
214
219
|
} else {
|
|
215
220
|
debounceSave();
|
|
216
221
|
}
|
|
222
|
+
|
|
223
|
+
if (
|
|
224
|
+
op !== null &&
|
|
225
|
+
op[0] == 'aiStreams' &&
|
|
226
|
+
source !== 'AIServer' &&
|
|
227
|
+
source !== 'AIAssist'
|
|
228
|
+
) {
|
|
229
|
+
//This is an insert operation (a new request)
|
|
230
|
+
if (
|
|
231
|
+
op[op.length - 1]['i'] !== undefined &&
|
|
232
|
+
op[op.length - 1]['i']['AIStreamStatus'] !==
|
|
233
|
+
undefined
|
|
234
|
+
) {
|
|
235
|
+
const input =
|
|
236
|
+
op[op.length - 1]['i']['AIStreamStatus'];
|
|
237
|
+
|
|
238
|
+
generateAIResponse({
|
|
239
|
+
inputText: input.text,
|
|
240
|
+
insertionCursor: input.insertionCursor,
|
|
241
|
+
shareDBDoc: shareDBDoc,
|
|
242
|
+
streamId: op[op.length - 2],
|
|
243
|
+
fileId: input.fileId,
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
const confirmStartOperation = replaceOp(
|
|
247
|
+
[
|
|
248
|
+
...op.filter(
|
|
249
|
+
(value) => typeof value === 'string',
|
|
250
|
+
),
|
|
251
|
+
'AIStreamStatus',
|
|
252
|
+
'serverIsRunning',
|
|
253
|
+
],
|
|
254
|
+
true,
|
|
255
|
+
true,
|
|
256
|
+
);
|
|
257
|
+
|
|
258
|
+
shareDBDoc.submitOp(confirmStartOperation, {
|
|
259
|
+
source: 'AIServer',
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
if (
|
|
263
|
+
op[op.length - 1]['r'] !== null &&
|
|
264
|
+
op[op.length - 2] == 'clientWantsToStart'
|
|
265
|
+
) {
|
|
266
|
+
//client wants to halt generation
|
|
267
|
+
haltGeneration(op[1]);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
217
270
|
});
|
|
218
271
|
});
|
|
219
272
|
|
package/src/types.ts
CHANGED
|
@@ -72,7 +72,10 @@ export type ShareDBDoc<T> = {
|
|
|
72
72
|
event: string,
|
|
73
73
|
callback: (op: JSONOp, source: boolean) => void,
|
|
74
74
|
) => void;
|
|
75
|
-
off: (
|
|
75
|
+
off: (
|
|
76
|
+
event: string,
|
|
77
|
+
callback: (op: JSONOp, source: boolean) => void,
|
|
78
|
+
) => void;
|
|
76
79
|
submitOp: (
|
|
77
80
|
op: JSONOp,
|
|
78
81
|
options?: any,
|
|
@@ -80,6 +83,61 @@ export type ShareDBDoc<T> = {
|
|
|
80
83
|
) => void;
|
|
81
84
|
};
|
|
82
85
|
|
|
86
|
+
// A unique ID for an AI stream.
|
|
87
|
+
// Generated by the client.
|
|
88
|
+
export type AIStreamId = string;
|
|
89
|
+
|
|
90
|
+
// TODO define this interface.
|
|
91
|
+
export type AIStreamStatus = {
|
|
92
|
+
// This field is managed by the client.
|
|
93
|
+
// * `true` if the client wants the server to start the generation.
|
|
94
|
+
// * `false` if the client does not want the server to start the generation.
|
|
95
|
+
clientWantsToStart: boolean;
|
|
96
|
+
|
|
97
|
+
// This field is managed by the server.
|
|
98
|
+
// * `true` if the server is running the generation.
|
|
99
|
+
// * `false` if the server is not running the generation.
|
|
100
|
+
serverIsRunning: boolean;
|
|
101
|
+
|
|
102
|
+
text: string;
|
|
103
|
+
|
|
104
|
+
startingInsertionCursor: number;
|
|
105
|
+
fileId: FileId;
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
/// Alternative universe
|
|
109
|
+
|
|
110
|
+
// // // If the generation started yet or not
|
|
111
|
+
// // // * If not, the server will start it.
|
|
112
|
+
// // started: boolean;
|
|
113
|
+
|
|
114
|
+
// // Required?
|
|
115
|
+
// // aiStreamId: AIStreamId
|
|
116
|
+
|
|
117
|
+
// // The generation status
|
|
118
|
+
// // * "running" if the generation is running
|
|
119
|
+
// // * "finished" if the generation is finished
|
|
120
|
+
// // * "error" if the generation errored
|
|
121
|
+
// // * "stopped" if the generation was stopped
|
|
122
|
+
// status:
|
|
123
|
+
// | 'unstarted'
|
|
124
|
+
// | 'running'
|
|
125
|
+
// | 'finished'
|
|
126
|
+
// | 'error'
|
|
127
|
+
// | 'stopped';
|
|
128
|
+
|
|
129
|
+
// // Possible states:
|
|
130
|
+
// // * { started: 'unstarted' } - client added this,
|
|
131
|
+
// // It should trigger the server to start the generation.
|
|
132
|
+
// // * { status: 'running' } - server added this,
|
|
133
|
+
// // It indicates that the generation is running.
|
|
134
|
+
// // * { status: 'finished' } - server added this,
|
|
135
|
+
// // It indicates that the generation is finished.
|
|
136
|
+
// // * { status: 'stopped' } - server added this,
|
|
137
|
+
// // It indicates that the generation was stopped.
|
|
138
|
+
// //
|
|
139
|
+
// // After `status` transitions to `finished` or `stopped`,
|
|
140
|
+
|
|
83
141
|
// The ShareDB document type for VZCode.
|
|
84
142
|
export type VZCodeContent = {
|
|
85
143
|
// `files`
|
|
@@ -93,6 +151,25 @@ export type VZCodeContent = {
|
|
|
93
151
|
// * `false` or `undefined` when they are not (e.g. normal typing)
|
|
94
152
|
// * Hot reloading is debounced when this is `false`.
|
|
95
153
|
isInteracting?: boolean;
|
|
154
|
+
|
|
155
|
+
// `aiStreams`
|
|
156
|
+
// * The AI streams in the VZCode instance.
|
|
157
|
+
// * Keys are AI stream IDs.
|
|
158
|
+
// * Values are AI streams.
|
|
159
|
+
// What is this for?
|
|
160
|
+
// * Synchronize the status of streams between the
|
|
161
|
+
// client and server.
|
|
162
|
+
// * This can power the transition of the icon from
|
|
163
|
+
// lightning bolt to stop sign and back (when stream finishes)
|
|
164
|
+
aiStreams: {
|
|
165
|
+
// These need to be removed at some point?
|
|
166
|
+
[aiStreamId: AIStreamId]: {
|
|
167
|
+
AIStreamStatus: AIStreamStatus;
|
|
168
|
+
};
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
// TODO consider is there a way to replace the HTTP request to
|
|
172
|
+
// the AIAssist endpoint with a manipulation of the ShareDB document?
|
|
96
173
|
};
|
|
97
174
|
|
|
98
175
|
// An id used for presence.
|