vzcode 0.45.0 → 0.47.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-iQRmsAKc.css → index-0U2uzpmH.css} +1 -1
- package/dist/assets/{index-pub_uACz.js → index-r3WFw-uS.js} +38 -41
- package/dist/index.html +2 -2
- package/package.json +5 -5
- 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/formatFile.ts +27 -0
- package/src/client/AIAssist/generateFilesContext.ts +25 -0
- package/src/client/AIAssist/index.ts +3 -0
- package/src/client/AIAssist/startAIAssist.ts +120 -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/generateAIResponse.js +111 -0
- package/src/server/handleAIAssist.js +11 -100
- package/src/server/index.js +49 -55
- package/src/types.ts +21 -21
- package/src/client/AIAssist.ts +0 -133
- package/src/client/AIAssistWidget/index.tsx +0 -101
- package/src/client/AIAssistWidget/style.scss +0 -10
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({
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import OpenAI from 'openai';
|
|
2
|
+
import { json1Presence } from '../ot.js';
|
|
3
|
+
|
|
4
|
+
const { editOp, type } = json1Presence;
|
|
5
|
+
|
|
6
|
+
const debug = false;
|
|
7
|
+
|
|
8
|
+
// Feature flag to slow down AI for development/testing
|
|
9
|
+
const slowdown = false;
|
|
10
|
+
|
|
11
|
+
let openai;
|
|
12
|
+
if (process.env.OPENAI_API_KEY !== undefined) {
|
|
13
|
+
openai = new OpenAI();
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const AISourceName = 'AIAssist';
|
|
17
|
+
|
|
18
|
+
function opComesFromAIAssist(ops, source) {
|
|
19
|
+
return source === AISourceName;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const streams = {};
|
|
23
|
+
|
|
24
|
+
export const generateAIResponse = async ({
|
|
25
|
+
inputText,
|
|
26
|
+
insertionCursor,
|
|
27
|
+
fileId,
|
|
28
|
+
streamId,
|
|
29
|
+
shareDBDoc,
|
|
30
|
+
}) => {
|
|
31
|
+
if (debug) {
|
|
32
|
+
console.log(
|
|
33
|
+
'[generateAIResponse] inputText:',
|
|
34
|
+
inputText,
|
|
35
|
+
);
|
|
36
|
+
console.log(
|
|
37
|
+
'[generateAIResponse] insertionCursor:',
|
|
38
|
+
insertionCursor,
|
|
39
|
+
);
|
|
40
|
+
console.log('[generateAIResponse] fileId:', fileId);
|
|
41
|
+
console.log('[generateAIResponse] streamId:', streamId);
|
|
42
|
+
console.log(
|
|
43
|
+
'[generateAIResponse] shareDBDoc:',
|
|
44
|
+
shareDBDoc,
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
function accomodateDocChanges(op, source) {
|
|
48
|
+
if (!opComesFromAIAssist(op, source)) {
|
|
49
|
+
if (op !== null) {
|
|
50
|
+
insertionCursor = type
|
|
51
|
+
.transformPosition(
|
|
52
|
+
['files', fileId, 'text', insertionCursor],
|
|
53
|
+
op,
|
|
54
|
+
)
|
|
55
|
+
.slice(-1)[0];
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
shareDBDoc.on('op', accomodateDocChanges);
|
|
61
|
+
|
|
62
|
+
const messages = [
|
|
63
|
+
{
|
|
64
|
+
role: 'system',
|
|
65
|
+
content: [
|
|
66
|
+
'You are an expert programmer.',
|
|
67
|
+
'Your task is to output ONLY the code that replaces <FILL_ME> correctly.',
|
|
68
|
+
'Do not add any markdown.',
|
|
69
|
+
'Do not duplicate the code before or after <FILL_ME>.',
|
|
70
|
+
'Do not make any changes outside of <FILL_ME>.',
|
|
71
|
+
].join(' '),
|
|
72
|
+
},
|
|
73
|
+
{ role: 'user', content: inputText },
|
|
74
|
+
];
|
|
75
|
+
|
|
76
|
+
console.log('messages');
|
|
77
|
+
console.log(messages);
|
|
78
|
+
|
|
79
|
+
streams[streamId] = await openai.chat.completions.create({
|
|
80
|
+
// model: 'gpt-3.5-turbo',
|
|
81
|
+
model: 'gpt-4',
|
|
82
|
+
|
|
83
|
+
messages,
|
|
84
|
+
stream: true,
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
for await (const part of streams[streamId]) {
|
|
88
|
+
const op = editOp(
|
|
89
|
+
['files', fileId, 'text'],
|
|
90
|
+
'text-unicode',
|
|
91
|
+
[
|
|
92
|
+
insertionCursor,
|
|
93
|
+
part.choices[0]?.delta?.content || '',
|
|
94
|
+
],
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
shareDBDoc.submitOp(op, { source: AISourceName });
|
|
98
|
+
|
|
99
|
+
// Wait for 500ms
|
|
100
|
+
if (slowdown) {
|
|
101
|
+
await new Promise((resolve) => {
|
|
102
|
+
setTimeout(resolve, 1000);
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
insertionCursor += (
|
|
107
|
+
part.choices[0]?.delta?.content || ''
|
|
108
|
+
).length;
|
|
109
|
+
}
|
|
110
|
+
shareDBDoc.off('op', accomodateDocChanges);
|
|
111
|
+
};
|
|
@@ -1,109 +1,20 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { json1Presence } from '../ot.js';
|
|
1
|
+
import { generateAIResponse } from './generateAIResponse.js';
|
|
3
2
|
|
|
4
|
-
const
|
|
3
|
+
const debug = false;
|
|
5
4
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
let openai;
|
|
10
|
-
if (process.env.OPENAI_API_KEY !== undefined) {
|
|
11
|
-
openai = new OpenAI();
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export async function generateAIResponse({
|
|
15
|
-
inputText,
|
|
16
|
-
insertionCursor,
|
|
17
|
-
fileId,
|
|
18
|
-
streamId,
|
|
19
|
-
shareDBDoc,
|
|
20
|
-
}) {
|
|
21
|
-
function accomodateDocChanges(op, source) {
|
|
22
|
-
if (!opComesFromAIAssist(op, source)) {
|
|
23
|
-
if (op !== null) {
|
|
24
|
-
insertionCursor = type
|
|
25
|
-
.transformPosition(
|
|
26
|
-
['files', fileId, 'text', insertionCursor],
|
|
27
|
-
op,
|
|
28
|
-
)
|
|
29
|
-
.slice(-1)[0];
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
shareDBDoc.on('op', accomodateDocChanges);
|
|
35
|
-
|
|
36
|
-
streams[streamId] = await openai.chat.completions.create({
|
|
37
|
-
// model: 'gpt-3.5-turbo',
|
|
38
|
-
model: 'gpt-4',
|
|
39
|
-
|
|
40
|
-
messages: [
|
|
41
|
-
{
|
|
42
|
-
role: 'system',
|
|
43
|
-
content:
|
|
44
|
-
'Write typescript or javascript code to continue the current file, given the other files for context. Use // for comments.',
|
|
45
|
-
},
|
|
46
|
-
{ role: 'user', content: inputText },
|
|
47
|
-
],
|
|
48
|
-
stream: true,
|
|
49
|
-
});
|
|
5
|
+
export const handleAIAssist =
|
|
6
|
+
(shareDBDoc) => async (req, res) => {
|
|
7
|
+
const { inputText, insertionCursor, fileId } = req.body;
|
|
50
8
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
[
|
|
9
|
+
if (debug) {
|
|
10
|
+
console.log('[handleAIAssist] inputText:', inputText);
|
|
11
|
+
console.log(
|
|
12
|
+
'[handleAIAssist] insertionCursor:',
|
|
56
13
|
insertionCursor,
|
|
57
|
-
|
|
58
|
-
],
|
|
59
|
-
);
|
|
60
|
-
|
|
61
|
-
shareDBDoc.submitOp(op, { source: AISourceName });
|
|
62
|
-
|
|
63
|
-
// Wait for 500ms
|
|
64
|
-
if (slowdown) {
|
|
65
|
-
await new Promise((resolve) => {
|
|
66
|
-
setTimeout(resolve, 1000);
|
|
67
|
-
});
|
|
14
|
+
);
|
|
15
|
+
console.log('[handleAIAssist] fileId:', fileId);
|
|
68
16
|
}
|
|
69
17
|
|
|
70
|
-
insertionCursor += (
|
|
71
|
-
part.choices[0]?.delta?.content || ''
|
|
72
|
-
).length;
|
|
73
|
-
}
|
|
74
|
-
shareDBDoc.off('op', accomodateDocChanges);
|
|
75
|
-
|
|
76
|
-
const confirmCompletedOperation = replaceOp(
|
|
77
|
-
[
|
|
78
|
-
'aiStreams',
|
|
79
|
-
streamId,
|
|
80
|
-
'AIStreamStatus',
|
|
81
|
-
'serverIsRunning',
|
|
82
|
-
],
|
|
83
|
-
true,
|
|
84
|
-
false,
|
|
85
|
-
);
|
|
86
|
-
shareDBDoc.submitOp(confirmCompletedOperation, {
|
|
87
|
-
source: 'AIServer',
|
|
88
|
-
});
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
const AISourceName = 'AIAssist';
|
|
92
|
-
|
|
93
|
-
function opComesFromAIAssist(ops, source) {
|
|
94
|
-
return source === AISourceName;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
const streams = {};
|
|
98
|
-
|
|
99
|
-
export const handleAIAssist =
|
|
100
|
-
(shareDBDoc) => async (req, res) => {
|
|
101
|
-
const {
|
|
102
|
-
text: inputText,
|
|
103
|
-
cursorLocation: insertionCursor,
|
|
104
|
-
fileId,
|
|
105
|
-
} = req.body;
|
|
106
|
-
|
|
107
18
|
try {
|
|
108
19
|
await generateAIResponse({
|
|
109
20
|
inputText,
|
package/src/server/index.js
CHANGED
|
@@ -13,13 +13,7 @@ import ngrok from 'ngrok';
|
|
|
13
13
|
import bodyParser from 'body-parser';
|
|
14
14
|
import { json1Presence } from '../ot.js';
|
|
15
15
|
import { computeInitialDocument } from './computeInitialDocument.js';
|
|
16
|
-
import {
|
|
17
|
-
generateAIResponse,
|
|
18
|
-
haltGeneration,
|
|
19
|
-
handleAIAssist,
|
|
20
|
-
} from './handleAIAssist.js';
|
|
21
|
-
|
|
22
|
-
const { replaceOp } = json1Presence;
|
|
16
|
+
import { handleAIAssist } from './handleAIAssist.js';
|
|
23
17
|
|
|
24
18
|
// The time in milliseconds by which auto-saving is debounced.
|
|
25
19
|
const autoSaveDebounceTimeMS = 800;
|
|
@@ -103,7 +97,7 @@ shareDBDoc.create(initialDocument, json1Presence.type.uri);
|
|
|
103
97
|
|
|
104
98
|
// Handle AI Assist requests.
|
|
105
99
|
app.post(
|
|
106
|
-
'/
|
|
100
|
+
'/ai-assist',
|
|
107
101
|
bodyParser.json(),
|
|
108
102
|
handleAIAssist(shareDBDoc),
|
|
109
103
|
);
|
|
@@ -219,53 +213,53 @@ shareDBDoc.subscribe(() => {
|
|
|
219
213
|
debounceSave();
|
|
220
214
|
}
|
|
221
215
|
|
|
222
|
-
if (
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
) {
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
}
|
|
216
|
+
// if (
|
|
217
|
+
// op !== null &&
|
|
218
|
+
// op[0] == 'aiStreams' &&
|
|
219
|
+
// source !== 'AIServer' &&
|
|
220
|
+
// source !== 'AIAssist'
|
|
221
|
+
// ) {
|
|
222
|
+
// //This is an insert operation (a new request)
|
|
223
|
+
// if (
|
|
224
|
+
// op[op.length - 1]['i'] !== undefined &&
|
|
225
|
+
// op[op.length - 1]['i']['AIStreamStatus'] !==
|
|
226
|
+
// undefined
|
|
227
|
+
// ) {
|
|
228
|
+
// const input =
|
|
229
|
+
// op[op.length - 1]['i']['AIStreamStatus'];
|
|
230
|
+
|
|
231
|
+
// generateAIResponse({
|
|
232
|
+
// inputText: input.text,
|
|
233
|
+
// insertionCursor: input.insertionCursor,
|
|
234
|
+
// shareDBDoc: shareDBDoc,
|
|
235
|
+
// streamId: op[op.length - 2],
|
|
236
|
+
// fileId: input.fileId,
|
|
237
|
+
// });
|
|
238
|
+
|
|
239
|
+
// const confirmStartOperation = replaceOp(
|
|
240
|
+
// [
|
|
241
|
+
// ...op.filter(
|
|
242
|
+
// (value) => typeof value === 'string',
|
|
243
|
+
// ),
|
|
244
|
+
// 'AIStreamStatus',
|
|
245
|
+
// 'serverIsRunning',
|
|
246
|
+
// ],
|
|
247
|
+
// true,
|
|
248
|
+
// true,
|
|
249
|
+
// );
|
|
250
|
+
|
|
251
|
+
// shareDBDoc.submitOp(confirmStartOperation, {
|
|
252
|
+
// source: 'AIServer',
|
|
253
|
+
// });
|
|
254
|
+
// }
|
|
255
|
+
// if (
|
|
256
|
+
// op[op.length - 1]['r'] !== null &&
|
|
257
|
+
// op[op.length - 2] == 'clientWantsToStart'
|
|
258
|
+
// ) {
|
|
259
|
+
// //client wants to halt generation
|
|
260
|
+
// haltGeneration(op[1]);
|
|
261
|
+
// }
|
|
262
|
+
// }
|
|
269
263
|
});
|
|
270
264
|
});
|
|
271
265
|
|
package/src/types.ts
CHANGED
|
@@ -87,23 +87,23 @@ export type ShareDBDoc<T> = {
|
|
|
87
87
|
// Generated by the client.
|
|
88
88
|
export type AIStreamId = string;
|
|
89
89
|
|
|
90
|
-
// TODO define this interface.
|
|
91
|
-
export type AIStreamStatus = {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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;
|
|
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;
|
|
101
96
|
|
|
102
|
-
|
|
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;
|
|
103
101
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
102
|
+
// text: string;
|
|
103
|
+
|
|
104
|
+
// startingInsertionCursor: number;
|
|
105
|
+
// fileId: FileId;
|
|
106
|
+
// };
|
|
107
107
|
|
|
108
108
|
/// Alternative universe
|
|
109
109
|
|
|
@@ -161,12 +161,12 @@ export type VZCodeContent = {
|
|
|
161
161
|
// client and server.
|
|
162
162
|
// * This can power the transition of the icon from
|
|
163
163
|
// lightning bolt to stop sign and back (when stream finishes)
|
|
164
|
-
aiStreams?: {
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
};
|
|
164
|
+
// aiStreams?: {
|
|
165
|
+
// // These need to be removed at some point?
|
|
166
|
+
// [aiStreamId: AIStreamId]: {
|
|
167
|
+
// AIStreamStatus: AIStreamStatus;
|
|
168
|
+
// };
|
|
169
|
+
// };
|
|
170
170
|
|
|
171
171
|
// TODO consider is there a way to replace the HTTP request to
|
|
172
172
|
// the AIAssist endpoint with a manipulation of the ShareDB document?
|
package/src/client/AIAssist.ts
DELETED
|
@@ -1,133 +0,0 @@
|
|
|
1
|
-
import { EditorView, keymap } from '@codemirror/view';
|
|
2
|
-
import { json1Presence } from '../ot';
|
|
3
|
-
import {
|
|
4
|
-
FileId,
|
|
5
|
-
File,
|
|
6
|
-
ShareDBDoc,
|
|
7
|
-
VZCodeContent,
|
|
8
|
-
} from '../types';
|
|
9
|
-
import { generateRequestId } from './CodeEditor/typeScriptCompletions';
|
|
10
|
-
|
|
11
|
-
import { TabState } from './vzReducer';
|
|
12
|
-
|
|
13
|
-
const { insertOp, replaceOp } = json1Presence;
|
|
14
|
-
|
|
15
|
-
export const AIAssist = ({
|
|
16
|
-
shareDBDoc,
|
|
17
|
-
// The file id of the file the AI should assist with.
|
|
18
|
-
fileId,
|
|
19
|
-
tabList,
|
|
20
|
-
}: {
|
|
21
|
-
shareDBDoc: ShareDBDoc<VZCodeContent>;
|
|
22
|
-
fileId: string;
|
|
23
|
-
aiAssistEndpoint: string;
|
|
24
|
-
aiAssistOptions?: {
|
|
25
|
-
[key: string]: any;
|
|
26
|
-
};
|
|
27
|
-
tabList: Array<TabState>;
|
|
28
|
-
}) =>
|
|
29
|
-
keymap.of([
|
|
30
|
-
{
|
|
31
|
-
key: 'control-m',
|
|
32
|
-
run: (view: EditorView) => {
|
|
33
|
-
if (
|
|
34
|
-
shareDBDoc.data.aiStreams === undefined ||
|
|
35
|
-
shareDBDoc.data.aiStreams[mostRecentStreamId] ===
|
|
36
|
-
null ||
|
|
37
|
-
shareDBDoc.data.aiStreams[mostRecentStreamId]
|
|
38
|
-
?.AIStreamStatus.serverIsRunning !== true
|
|
39
|
-
) {
|
|
40
|
-
startAIAssist(view, shareDBDoc, fileId, tabList);
|
|
41
|
-
} else {
|
|
42
|
-
haltAIAssist(shareDBDoc);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
return true;
|
|
46
|
-
},
|
|
47
|
-
},
|
|
48
|
-
]);
|
|
49
|
-
|
|
50
|
-
// TODO use ref forwarding instead of `export let`
|
|
51
|
-
export let mostRecentStreamId = null;
|
|
52
|
-
|
|
53
|
-
export const startAIAssist = async (
|
|
54
|
-
view: EditorView,
|
|
55
|
-
shareDBDoc: ShareDBDoc<VZCodeContent>,
|
|
56
|
-
fileId: FileId,
|
|
57
|
-
tabList: Array<TabState>,
|
|
58
|
-
// TODO mostRecentStreamIdRef: React.MutableRefObject<string>,
|
|
59
|
-
) => {
|
|
60
|
-
const textToSend =
|
|
61
|
-
(await generateFilesContext(
|
|
62
|
-
tabList.map(
|
|
63
|
-
(tabState) =>
|
|
64
|
-
shareDBDoc.data.files[tabState.fileId],
|
|
65
|
-
),
|
|
66
|
-
)) +
|
|
67
|
-
'Current File:\n' +
|
|
68
|
-
view.state.sliceDoc(0, view.state.selection.main.to);
|
|
69
|
-
|
|
70
|
-
console.log(textToSend);
|
|
71
|
-
|
|
72
|
-
// TODO mostRecentStreamIdRef.current = generateRequestId();
|
|
73
|
-
mostRecentStreamId = generateRequestId();
|
|
74
|
-
|
|
75
|
-
if (shareDBDoc.data['aiStreams'] === undefined) {
|
|
76
|
-
shareDBDoc.submitOp(insertOp(['aiStreams'], {}), {
|
|
77
|
-
source: 'AIClient',
|
|
78
|
-
});
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
shareDBDoc.submitOp(
|
|
82
|
-
insertOp(['aiStreams', mostRecentStreamId], {
|
|
83
|
-
AIStreamStatus: {
|
|
84
|
-
clientWantsToStart: true,
|
|
85
|
-
serverIsRunning: false,
|
|
86
|
-
text: textToSend,
|
|
87
|
-
insertionCursor: view.state.selection.main.to,
|
|
88
|
-
fileId: fileId,
|
|
89
|
-
},
|
|
90
|
-
}),
|
|
91
|
-
{ source: 'AIClient' },
|
|
92
|
-
);
|
|
93
|
-
};
|
|
94
|
-
|
|
95
|
-
export const haltAIAssist = (
|
|
96
|
-
shareDBDoc: ShareDBDoc<VZCodeContent>,
|
|
97
|
-
) => {
|
|
98
|
-
const haltGenerationOp = replaceOp(
|
|
99
|
-
[
|
|
100
|
-
'aiStreams',
|
|
101
|
-
mostRecentStreamId,
|
|
102
|
-
'AIStreamStatus',
|
|
103
|
-
'clientWantsToStart',
|
|
104
|
-
],
|
|
105
|
-
true,
|
|
106
|
-
false,
|
|
107
|
-
);
|
|
108
|
-
|
|
109
|
-
shareDBDoc.submitOp(haltGenerationOp, {
|
|
110
|
-
source: 'AIClient',
|
|
111
|
-
});
|
|
112
|
-
};
|
|
113
|
-
|
|
114
|
-
export const generateFilesContext = async (
|
|
115
|
-
goodFiles: File[],
|
|
116
|
-
): Promise<string> => {
|
|
117
|
-
const input = goodFiles
|
|
118
|
-
.map((file) => {
|
|
119
|
-
const nameSubstring = file.name
|
|
120
|
-
// ?.substring(0, maxFileNameLength)
|
|
121
|
-
.trim();
|
|
122
|
-
|
|
123
|
-
const textSubstring = file.text
|
|
124
|
-
// ?.substring(0, maxFileTextLength)
|
|
125
|
-
.trim();
|
|
126
|
-
|
|
127
|
-
// Generate Markdown that AI will understand.
|
|
128
|
-
return `File \`${nameSubstring}\`:\n\`\`\`${textSubstring}\`\`\``;
|
|
129
|
-
})
|
|
130
|
-
.join('\n\n');
|
|
131
|
-
|
|
132
|
-
return input;
|
|
133
|
-
};
|