vzcode 0.45.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-iQRmsAKc.css → index-0U2uzpmH.css} +1 -1
- package/dist/assets/{index-pub_uACz.js → index-iJQOJ5Kf.js} +38 -38
- 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/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/generateAIResponse.js +101 -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
|
@@ -0,0 +1,101 @@
|
|
|
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
|
+
streams[streamId] = await openai.chat.completions.create({
|
|
63
|
+
// model: 'gpt-3.5-turbo',
|
|
64
|
+
model: 'gpt-4',
|
|
65
|
+
|
|
66
|
+
messages: [
|
|
67
|
+
{
|
|
68
|
+
role: 'system',
|
|
69
|
+
content:
|
|
70
|
+
'Write typescript or javascript code to continue the current file, given the other files for context. Use // for comments.',
|
|
71
|
+
},
|
|
72
|
+
{ role: 'user', content: inputText },
|
|
73
|
+
],
|
|
74
|
+
stream: true,
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
for await (const part of streams[streamId]) {
|
|
78
|
+
const op = editOp(
|
|
79
|
+
['files', fileId, 'text'],
|
|
80
|
+
'text-unicode',
|
|
81
|
+
[
|
|
82
|
+
insertionCursor,
|
|
83
|
+
part.choices[0]?.delta?.content || '',
|
|
84
|
+
],
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
shareDBDoc.submitOp(op, { source: AISourceName });
|
|
88
|
+
|
|
89
|
+
// Wait for 500ms
|
|
90
|
+
if (slowdown) {
|
|
91
|
+
await new Promise((resolve) => {
|
|
92
|
+
setTimeout(resolve, 1000);
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
insertionCursor += (
|
|
97
|
+
part.choices[0]?.delta?.content || ''
|
|
98
|
+
).length;
|
|
99
|
+
}
|
|
100
|
+
shareDBDoc.off('op', accomodateDocChanges);
|
|
101
|
+
};
|
|
@@ -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
|
-
};
|
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
import { useCallback, useEffect, useState } from 'react';
|
|
2
|
-
import { Button } from '../bootstrap';
|
|
3
|
-
import './style.scss';
|
|
4
|
-
import {
|
|
5
|
-
mostRecentStreamId,
|
|
6
|
-
haltAIAssist,
|
|
7
|
-
startAIAssist,
|
|
8
|
-
} from '../AIAssist';
|
|
9
|
-
import {
|
|
10
|
-
FileId,
|
|
11
|
-
ShareDBDoc,
|
|
12
|
-
VZCodeContent,
|
|
13
|
-
} from '../../types';
|
|
14
|
-
import { EditorCache } from '../useEditorCache';
|
|
15
|
-
import { TabState } from '../vzReducer';
|
|
16
|
-
// From
|
|
17
|
-
// https://primer.style/foundations/icons/zap-24
|
|
18
|
-
const ZapSVG = () => (
|
|
19
|
-
<svg
|
|
20
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
21
|
-
viewBox="0 0 24 24"
|
|
22
|
-
width="24"
|
|
23
|
-
height="24"
|
|
24
|
-
>
|
|
25
|
-
<path d="M15.716 1.329a1.341 1.341 0 0 1 2.109 1.55L15.147 9h4.161c1.623 0 2.372 2.016 1.143 3.075L8.102 22.721a1.148 1.148 0 0 1-1.81-1.317L8.996 15H4.674c-1.619 0-2.37-2.008-1.148-3.07l12.19-10.6Zm.452 1.595L4.51 13.061a.25.25 0 0 0 .164.439h5.45a.749.749 0 0 1 .692 1.041l-2.559 6.066 11.215-9.668a.25.25 0 0 0-.164-.439H14a.75.75 0 0 1-.687-1.05Z"></path>
|
|
26
|
-
</svg>
|
|
27
|
-
);
|
|
28
|
-
|
|
29
|
-
// From Wikimedia
|
|
30
|
-
// https://commons.wikimedia.org/wiki/File:Stopsign.svg
|
|
31
|
-
const StopSVG = () => (
|
|
32
|
-
<svg
|
|
33
|
-
width="15pt"
|
|
34
|
-
height="15pt"
|
|
35
|
-
version="1.1"
|
|
36
|
-
viewBox="0 0 15 15"
|
|
37
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
38
|
-
>
|
|
39
|
-
<g transform="scale(.75)" fill="#fff" stroke="#000">
|
|
40
|
-
<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" />
|
|
41
|
-
</g>
|
|
42
|
-
</svg>
|
|
43
|
-
);
|
|
44
|
-
|
|
45
|
-
export const AIAssistWidget = ({
|
|
46
|
-
activeFileId,
|
|
47
|
-
shareDBDoc,
|
|
48
|
-
editorCache,
|
|
49
|
-
tabList,
|
|
50
|
-
}: {
|
|
51
|
-
activeFileId: FileId;
|
|
52
|
-
shareDBDoc: ShareDBDoc<VZCodeContent>;
|
|
53
|
-
editorCache: EditorCache;
|
|
54
|
-
tabList: Array<TabState>;
|
|
55
|
-
}) => {
|
|
56
|
-
const [AIAssistRunning, setAIAssistRunning] =
|
|
57
|
-
useState(false);
|
|
58
|
-
|
|
59
|
-
useEffect(() => {
|
|
60
|
-
const handleOp = (op, source) => {
|
|
61
|
-
if (op !== null && op[0] === 'aiStreams') {
|
|
62
|
-
// The check if the value is true is required
|
|
63
|
-
// because the value could be null rather than false.
|
|
64
|
-
setAIAssistRunning(
|
|
65
|
-
shareDBDoc.data.aiStreams[mostRecentStreamId]
|
|
66
|
-
?.AIStreamStatus.serverIsRunning === true,
|
|
67
|
-
);
|
|
68
|
-
}
|
|
69
|
-
};
|
|
70
|
-
shareDBDoc.on('op', handleOp);
|
|
71
|
-
|
|
72
|
-
return () => {
|
|
73
|
-
shareDBDoc.off('op', handleOp);
|
|
74
|
-
};
|
|
75
|
-
}, [shareDBDoc]);
|
|
76
|
-
|
|
77
|
-
const handleClick = useCallback(() => {
|
|
78
|
-
if (!AIAssistRunning) {
|
|
79
|
-
startAIAssist(
|
|
80
|
-
editorCache.get(activeFileId).editor,
|
|
81
|
-
shareDBDoc,
|
|
82
|
-
activeFileId,
|
|
83
|
-
tabList,
|
|
84
|
-
);
|
|
85
|
-
} else {
|
|
86
|
-
haltAIAssist(shareDBDoc);
|
|
87
|
-
}
|
|
88
|
-
}, [activeFileId, AIAssistRunning]);
|
|
89
|
-
|
|
90
|
-
return (
|
|
91
|
-
<div className="vz-code-ai-assist-widget">
|
|
92
|
-
<Button
|
|
93
|
-
variant="light"
|
|
94
|
-
title="Start AI Assist"
|
|
95
|
-
onClick={handleClick}
|
|
96
|
-
>
|
|
97
|
-
{AIAssistRunning ? <StopSVG /> : <ZapSVG />}
|
|
98
|
-
</Button>
|
|
99
|
-
</div>
|
|
100
|
-
);
|
|
101
|
-
};
|