vzcode 1.15.0 → 1.17.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 (44) hide show
  1. package/dist/assets/index-BQgRA0SD.js +181 -0
  2. package/dist/assets/index-CSSpCKM0.js +447 -0
  3. package/dist/assets/index-zzK6rRiK.css +1 -0
  4. package/dist/assets/worker-8af7bTHW.js +293 -0
  5. package/dist/index.html +2 -2
  6. package/package.json +40 -34
  7. package/src/client/AIAssist/AIAssistWidget/index.tsx +10 -6
  8. package/src/client/App/index.tsx +52 -20
  9. package/src/client/App/usePending.ts +50 -0
  10. package/src/client/App/useShareDB.ts +40 -1
  11. package/src/client/CodeEditor/Copilot/index.ts +28 -0
  12. package/src/client/CodeEditor/InteractiveWidgets/colorPicker.ts +129 -33
  13. package/src/client/CodeEditor/InteractiveWidgets/colorsInTextPlugin.ts +36 -1
  14. package/src/client/CodeEditor/getOrCreateEditor.ts +43 -6
  15. package/src/client/CodeEditor/index.tsx +7 -4
  16. package/src/client/CodeEditor/json1PresenceDisplay.ts +5 -5
  17. package/src/client/CodeEditor/rainbowBrackets.ts +14 -2
  18. package/src/client/CodeEditor/style.scss +106 -1
  19. package/src/client/Icons/MicSVG.tsx +25 -0
  20. package/src/client/RunCodeWidget/index.tsx +20 -3
  21. package/src/client/TabList/index.tsx +8 -11
  22. package/src/client/VZCodeContext.tsx +66 -24
  23. package/src/client/VZKeyboardShortcutsDoc.tsx +8 -8
  24. package/src/client/VZLeft.tsx +4 -2
  25. package/src/client/VZMiddle.tsx +86 -27
  26. package/src/client/VZResizer/index.tsx +3 -3
  27. package/src/client/VZSettings.tsx +54 -3
  28. package/src/client/VZSidebar/FileListing.tsx +20 -6
  29. package/src/client/VZSidebar/Listing.tsx +2 -2
  30. package/src/client/VZSidebar/VoiceChatModal.tsx +121 -0
  31. package/src/client/VZSidebar/index.tsx +80 -15
  32. package/src/client/VZSidebar/styles.scss +6 -2
  33. package/src/client/presenceColor.ts +8 -0
  34. package/src/client/useTypeScript/worker/constants.ts +6 -0
  35. package/src/server/generateAIResponse.js +79 -13
  36. package/src/server/handleAICopilot.js +77 -0
  37. package/src/server/index.js +23 -8
  38. package/src/server/livekit.js +24 -0
  39. package/src/server/setupEnv.js +1 -1
  40. package/src/types.ts +1 -0
  41. package/dist/assets/index-Bm65AzMP.js +0 -425
  42. package/dist/assets/index-DbFdL1A1.js +0 -159
  43. package/dist/assets/index-Yz-ZHc0O.css +0 -1
  44. package/dist/assets/worker-CJWrLBN2.js +0 -286
@@ -0,0 +1,121 @@
1
+ import {
2
+ ParticipantLoop,
3
+ ParticipantName,
4
+ useParticipants,
5
+ useRoomContext,
6
+ } from '@livekit/components-react';
7
+ import { useContext, useState } from 'react';
8
+ import { Button, Form, Modal } from 'react-bootstrap';
9
+ import { v4 } from 'uuid';
10
+ import { VZCodeContext } from '../VZCodeContext';
11
+
12
+ export function VoiceChatModal() {
13
+ const {
14
+ liveKitRoomName,
15
+ setLiveKitRoom,
16
+ liveKitToken,
17
+ setLiveKitToken,
18
+ liveKitConnection,
19
+ setLiveKitConnection,
20
+ voiceChatModalOpen,
21
+ setVoiceChatModalOpen,
22
+ } = useContext(VZCodeContext);
23
+ const participants = useParticipants();
24
+
25
+ const [roomName, setRoomName] = useState(
26
+ liveKitRoomName || '',
27
+ );
28
+ const [username, setUsername] = useState('');
29
+ const room = useRoomContext();
30
+
31
+ const handleConnect = () => {
32
+ if (liveKitConnection) {
33
+ setLiveKitConnection(false);
34
+ setLiveKitRoom(`${v4()}`);
35
+ setLiveKitToken('');
36
+ room.disconnect(true);
37
+ return;
38
+ }
39
+ setLiveKitRoom(roomName);
40
+ const fetchToken = async () => {
41
+ try {
42
+ const res = await fetch(
43
+ `/livekit-token?room=${roomName}&username=${username}`,
44
+ {
45
+ method: 'GET',
46
+ },
47
+ );
48
+ const tokenResponse = await res.text();
49
+ setLiveKitToken(tokenResponse);
50
+ setLiveKitConnection(true);
51
+ } catch (error) {
52
+ console.error('Error fetching token:', error);
53
+ }
54
+ };
55
+
56
+ fetchToken();
57
+ };
58
+
59
+ const handleClose = () => {
60
+ setVoiceChatModalOpen(false);
61
+ };
62
+
63
+ return (
64
+ <Modal
65
+ className="vz-settings"
66
+ show={voiceChatModalOpen}
67
+ onHide={handleClose}
68
+ animation={false}
69
+ >
70
+ <Modal.Header closeButton>
71
+ <Modal.Title>Connect to LiveKit Room</Modal.Title>
72
+ </Modal.Header>
73
+ <Modal.Body>
74
+ <Form>
75
+ <Form.Group controlId="formRoomName">
76
+ <Form.Label>Room Name</Form.Label>
77
+ <Form.Control
78
+ type="text"
79
+ placeholder="Enter room name"
80
+ value={roomName}
81
+ onChange={(e) => setRoomName(e.target.value)}
82
+ />
83
+ </Form.Group>
84
+ </Form>
85
+ <Form>
86
+ <Form.Group controlId="formUserName">
87
+ <Form.Label>Username</Form.Label>
88
+ <Form.Control
89
+ type="text"
90
+ placeholder="Enter Username"
91
+ value={username}
92
+ onChange={(e) => setUsername(e.target.value)}
93
+ />
94
+ </Form.Group>
95
+ </Form>
96
+ <div
97
+ style={{
98
+ display: 'flex',
99
+ flexDirection: 'column',
100
+ }}
101
+ >
102
+ <b>Users in Room: </b>
103
+ <ParticipantLoop participants={participants}>
104
+ <ParticipantName />
105
+ </ParticipantLoop>
106
+ </div>
107
+ </Modal.Body>
108
+ <Modal.Footer>
109
+ <Button variant="secondary" onClick={handleClose}>
110
+ Cancel
111
+ </Button>
112
+ <Button
113
+ variant={liveKitConnection ? 'danger' : 'primary'}
114
+ onClick={handleConnect}
115
+ >
116
+ {liveKitConnection ? 'Disconnect' : 'Connect'}
117
+ </Button>
118
+ </Modal.Footer>
119
+ </Modal>
120
+ );
121
+ }
@@ -4,6 +4,7 @@ import {
4
4
  useEffect,
5
5
  useMemo,
6
6
  useState,
7
+ useRef
7
8
  } from 'react';
8
9
  import {
9
10
  FileId,
@@ -13,25 +14,26 @@ import {
13
14
  PresenceId,
14
15
  PresenceIndicator,
15
16
  } from '../../types';
16
- import { Tooltip, OverlayTrigger } from '../bootstrap';
17
- import { Search } from './Search';
17
+ import { OverlayTrigger, Tooltip } from '../bootstrap';
18
18
  import { getFileTree } from '../getFileTree';
19
- import { sortFileTree } from '../sortFileTree';
20
- import { SplitPaneResizeContext } from '../SplitPaneResizeContext';
21
19
  import {
22
- FolderSVG,
23
- SearchSVG,
24
20
  BugSVG,
21
+ FileSVG,
22
+ FolderSVG,
25
23
  GearSVG,
26
24
  NewSVG,
27
- FileSVG,
28
- QuestionMarkSVG,
29
25
  PinSVG,
26
+ QuestionMarkSVG,
27
+ SearchSVG,
30
28
  } from '../Icons';
29
+ import { MicSVG } from '../Icons/MicSVG';
30
+ import { sortFileTree } from '../sortFileTree';
31
+ import { SplitPaneResizeContext } from '../SplitPaneResizeContext';
31
32
  import { VZCodeContext } from '../VZCodeContext';
32
33
  import { Listing } from './Listing';
33
- import { useDragAndDrop } from './useDragAndDrop';
34
+ import { Search } from './Search';
34
35
  import './styles.scss';
36
+ import { useDragAndDrop } from './useDragAndDrop';
35
37
 
36
38
  // TODO turn this UI back on when we are actually detecting
37
39
  // the connection status.
@@ -97,6 +99,11 @@ export const VZSidebar = ({
97
99
  <div>(Ctrl + Shift + A)</div>
98
100
  </div>
99
101
  ),
102
+ voiceChatToolTipText = (
103
+ <div>
104
+ <strong>Open Voice Chat Menu</strong>
105
+ </div>
106
+ ),
100
107
  }: {
101
108
  createFileTooltipText?: React.ReactNode;
102
109
  createDirTooltipText?: React.ReactNode;
@@ -107,6 +114,7 @@ export const VZSidebar = ({
107
114
  filesToolTipText?: React.ReactNode;
108
115
  enableAutoFollowTooltipText?: React.ReactNode;
109
116
  disableAutoFollowTooltipText?: React.ReactNode;
117
+ voiceChatToolTipText?: React.ReactNode;
110
118
  }) => {
111
119
  const {
112
120
  files,
@@ -118,12 +126,16 @@ export const VZSidebar = ({
118
126
  handleOpenCreateFileModal,
119
127
  handleOpenCreateDirModal,
120
128
  connected,
129
+ pending,
121
130
  sidebarRef,
122
131
  enableAutoFollow,
123
132
  toggleAutoFollow,
124
133
  docPresence,
125
134
  updatePresenceIndicator,
126
135
  sidebarPresenceIndicators,
136
+ liveKitConnection,
137
+ setLiveKitConnection,
138
+ setVoiceChatModalOpen,
127
139
  } = useContext(VZCodeContext);
128
140
 
129
141
  const fileTree = useMemo(
@@ -205,7 +217,31 @@ export const VZSidebar = ({
205
217
  }
206
218
  }, [docPresence]);
207
219
 
208
- // console.log(sidebarPresenceIndicators);
220
+ const [saved, setSaved] = useState(false);
221
+ const [isConnecting, setIsConnecting] = useState(true);
222
+ const previousPendingRef = useRef<boolean>(pending);
223
+
224
+ // Handle connection status transitions
225
+ useEffect(() => {
226
+ if (isConnecting && connected) {
227
+ setIsConnecting(false);
228
+ }
229
+ }, [connected, isConnecting]);
230
+
231
+ // Handle 'saved' state based on 'pending' transition
232
+ useEffect(() => {
233
+ // Check if 'pending' transitioned from true to false
234
+ if (previousPendingRef.current && !pending && connected && !isConnecting) {
235
+ setSaved(true);
236
+ const timer = setTimeout(() => {
237
+ setSaved(false);
238
+ }, 1500); // Reset after 2 seconds
239
+ return () => clearTimeout(timer);
240
+ }
241
+ // Update the ref with the current 'pending' state
242
+ previousPendingRef.current = pending;
243
+ }, [pending, connected, isConnecting]);
244
+
209
245
 
210
246
  return (
211
247
  <div
@@ -367,11 +403,31 @@ export const VZSidebar = ({
367
403
  }`}
368
404
  onClick={toggleAutoFollow}
369
405
  >
406
+ <i></i>
370
407
  <PinSVG />
371
408
  </i>
372
409
  </OverlayTrigger>
410
+ {/* Start Voice Chat */}
411
+ <OverlayTrigger
412
+ placement="right"
413
+ overlay={
414
+ <Tooltip id="voice-chat">
415
+ {voiceChatToolTipText}
416
+ </Tooltip>
417
+ }
418
+ >
419
+ <i
420
+ id="mic-icon"
421
+ className="icon-button icon-button-dark"
422
+ onClick={() => {
423
+ console.log('clicking', liveKitConnection);
424
+ setVoiceChatModalOpen(true);
425
+ }}
426
+ >
427
+ <MicSVG />
428
+ </i>
429
+ </OverlayTrigger>
373
430
  </div>
374
-
375
431
  <div className="files" id="sidebar-view-container">
376
432
  {!isSearchOpen ? (
377
433
  <div className="sidebar-files">
@@ -414,15 +470,24 @@ export const VZSidebar = ({
414
470
  )}
415
471
  </div>
416
472
  </div>
417
-
418
473
  {enableConnectionStatus && (
419
474
  <div className="connection-status">
420
- {connected ? 'Connected' : 'Connection Lost'}
475
+ {isConnecting ? 'Connecting...' :
476
+ !connected ? 'Connection Lost' :
477
+ pending ? 'Saving...' :
478
+ saved ? 'Saved.' :
479
+ 'Connected'}
421
480
  <div className="connection">
422
481
  <div
423
482
  className={`connection-status-indicator ${
424
- connected ? 'connected' : 'disconnected'
425
- }`}
483
+ isConnecting
484
+ ? 'pending'
485
+ : !connected
486
+ ? 'disconnected'
487
+ : pending
488
+ ? 'pending'
489
+ : 'connected'
490
+ }`}
426
491
  />
427
492
  </div>
428
493
  </div>
@@ -261,6 +261,10 @@
261
261
  background-color: var(--vh-color-success-01);
262
262
  }
263
263
 
264
+ &.pending {
265
+ background-color: var(--vh-color-caution-01);
266
+ }
267
+
264
268
  &.disconnected {
265
269
  background-color: var(--vh-color-warning-01);
266
270
  }
@@ -290,7 +294,7 @@
290
294
  align-items: center;
291
295
  justify-content: center;
292
296
  font-size: 8px;
293
- background-color: #ff5733;
294
- color: #ffffff;
297
+ color: #000000;
298
+ z-index: 1;
295
299
  }
296
300
  }
@@ -0,0 +1,8 @@
1
+ import ColorHash from 'color-hash';
2
+
3
+ export const assignUserColor = (name: string): string => {
4
+ const userColor = new ColorHash({ lightness: 0.75 })
5
+ .rgb(name)
6
+ .join(',');
7
+ return userColor;
8
+ };
@@ -69,9 +69,13 @@ const LINT_ERROR_CODE_OBJ_REASSINGMENT = 2739;
69
69
  // Type 'any' is not assignable to type 'never'.
70
70
  const LINT_ERROR_CODE_ANY_NOT_ASSIGNABLE_TO_NEVER = 2322;
71
71
 
72
+ // Ignore potential undefined error for variables
73
+ const LINT_ERROR_CODE_POSSIBLY_UNDEFINED = 18048;
74
+
72
75
  // This code is for errors like:
73
76
  // "Object is of type 'unknown'."
74
77
  const LINT_ERROR_CODE_UNKNOWN = 18046;
78
+ const LINT_ERROR_CODE_UNKNOWN_SYMBOL_ITERATOR = 2488;
75
79
 
76
80
  export const excludedErrorCodes = new Set([
77
81
  LINT_ERROR_CODE_ANY,
@@ -85,4 +89,6 @@ export const excludedErrorCodes = new Set([
85
89
  LINT_ERROR_CODE_OBJ_REASSINGMENT,
86
90
  LINT_ERROR_CODE_ANY_NOT_ASSIGNABLE_TO_NEVER,
87
91
  LINT_ERROR_CODE_UNKNOWN,
92
+ LINT_ERROR_CODE_UNKNOWN_SYMBOL_ITERATOR,
93
+ LINT_ERROR_CODE_POSSIBLY_UNDEFINED,
88
94
  ]);
@@ -1,24 +1,76 @@
1
1
  import OpenAI from 'openai';
2
2
  import { json1Presence } from '../ot.js';
3
3
 
4
+ // This module implements the generation of the AI response,
5
+ // using the OpenAI client.
6
+
7
+ // These environment variables are used to configure the OpenAI client.
8
+ // See `.env.sample` for the expected values.
9
+ const { VZCODE_AI_API_KEY, VZCODE_AI_BASE_URL } =
10
+ process.env;
11
+
12
+ // If neither of these are not set, the OpenAI client will not be initialized,
13
+ // e.g. for local development where no AI is needed.
14
+ const isAIEnabled =
15
+ VZCODE_AI_API_KEY !== undefined &&
16
+ VZCODE_AI_BASE_URL !== undefined;
17
+
4
18
  const { editOp, type } = json1Presence;
5
19
 
20
+ // Debug flag to log more information during development.
6
21
  const debug = false;
7
22
 
8
- // Feature flag to slow down AI for development/testing
23
+ // Feature flag to slow down AI for development/testing.
24
+ // Particularly relevant for debugging ShareDB and CodeMirror sync issues.
25
+ // This allows you to test the case of editing the document at the
26
+ // same time as the AI generation is happening.
9
27
  const slowdown = false;
10
28
 
11
- let openai;
12
- if (process.env.OPENAI_API_KEY !== undefined) {
13
- openai = new OpenAI();
29
+ // The options passed into the OpenAI client
30
+ // new OpenAI(openAIOptions)
31
+ const openAIOptions = {};
32
+
33
+ // Support specifying the API key via an environment variable
34
+ // If VZCODE_AI_API_KEY is not set, note that the OpenAI client
35
+ // will look for the OPENAI_API_KEY environment variable instead.
36
+ if (process.env.VZCODE_AI_API_KEY !== undefined) {
37
+ openAIOptions.apiKey = process.env.VZCODE_AI_API_KEY;
14
38
  }
15
39
 
16
- const AISourceName = 'AIAssist';
40
+ // Support for local AI server
41
+ if (process.env.VZCODE_AI_BASE_URL !== undefined) {
42
+ // The OpenAI client errors if the API key is not set,
43
+ // so in the case where we don't need an API key,
44
+ // e.g. for testing with a local AI server like LM Studio or LocalAI,
45
+ // we populate the option with a fake API key, so it doesn't error.
46
+ if (!openAIOptions.apiKey) {
47
+ openAIOptions.apiKey = 'Fake API Key';
48
+ }
17
49
 
18
- function opComesFromAIAssist(ops, source) {
19
- return source === AISourceName;
50
+ openAIOptions.baseURL = process.env.VZCODE_AI_BASE_URL;
20
51
  }
21
52
 
53
+ debug &&
54
+ console.log(
55
+ 'openAIOptions: ' +
56
+ JSON.stringify(openAIOptions, null, 2),
57
+ );
58
+ let openai;
59
+
60
+ if (isAIEnabled) {
61
+ openai = new OpenAI(openAIOptions);
62
+ }
63
+
64
+ // The name of the source that the AI responses
65
+ // will be attributed to in ShareDB operations.
66
+ const AIShareDBSourceName = 'AIAssist';
67
+
68
+ // Returns trie if the operation comes from AI.
69
+ const opComesFromAIAssist = (ops, source) =>
70
+ source === AIShareDBSourceName;
71
+
72
+ // Keeps track of the currently ongoing AI streams.
73
+ // There could be many streams at the same time.
22
74
  const streams = {};
23
75
 
24
76
  export const generateAIResponse = async ({
@@ -28,6 +80,16 @@ export const generateAIResponse = async ({
28
80
  streamId,
29
81
  shareDBDoc,
30
82
  }) => {
83
+ if (!isAIEnabled) {
84
+ console.log(
85
+ '[generateAIResponse] AI is not enabled. Skipping AI generation.',
86
+ );
87
+ console.log(
88
+ '[generateAIResponse] To enable AI, see .env.sample for the required environment variables.',
89
+ );
90
+ return;
91
+ }
92
+
31
93
  if (debug) {
32
94
  console.log(
33
95
  '[generateAIResponse] inputText:',
@@ -44,7 +106,10 @@ export const generateAIResponse = async ({
44
106
  shareDBDoc,
45
107
  );
46
108
  }
47
- function accomodateDocChanges(op, source) {
109
+
110
+ // Handle the case that a user edits the text in the document
111
+ // that comes becofore the insertion cursor.
112
+ const accomodateDocChanges = (op, source) => {
48
113
  if (!opComesFromAIAssist(op, source)) {
49
114
  if (op !== null) {
50
115
  insertionCursor = type
@@ -55,10 +120,10 @@ export const generateAIResponse = async ({
55
120
  .slice(-1)[0];
56
121
  }
57
122
  }
58
- }
59
-
123
+ };
60
124
  shareDBDoc.on('op', accomodateDocChanges);
61
125
 
126
+ // The prompt!
62
127
  const messages = [
63
128
  {
64
129
  role: 'system',
@@ -82,8 +147,7 @@ export const generateAIResponse = async ({
82
147
 
83
148
  streams[streamId] = await openai.chat.completions.create({
84
149
  // model: 'gpt-3.5-turbo',
85
- model: 'gpt-4',
86
-
150
+ model: 'gpt-4o',
87
151
  messages,
88
152
  stream: true,
89
153
  });
@@ -98,7 +162,9 @@ export const generateAIResponse = async ({
98
162
  ],
99
163
  );
100
164
 
101
- shareDBDoc.submitOp(op, { source: AISourceName });
165
+ shareDBDoc.submitOp(op, {
166
+ source: AIShareDBSourceName,
167
+ });
102
168
 
103
169
  if (slowdown) {
104
170
  await new Promise((resolve) => {
@@ -0,0 +1,77 @@
1
+ import OpenAI from 'openai';
2
+
3
+ // This module implements the generation of the copilot response,
4
+ // using the OpenAI client (but not necessarily the OpenAI service).
5
+
6
+ // Debug flag to log more information during development.
7
+ const debug = false;
8
+
9
+ // These environment variables are used to configure the OpenAI client.
10
+ // See `.env.sample` for the expected values.
11
+ const {
12
+ VZCODE_AI_COPILOT_API_KEY,
13
+ VZCODE_AI_COLIPOT_BASE_URL,
14
+ VZCODE_AI_COLIPOT_MODEL,
15
+ } = process.env;
16
+
17
+ // If neither of these are not set, the OpenAI client will not be initialized,
18
+ // e.g. for local development where no AI is needed.
19
+ const isCopilotEnabled =
20
+ VZCODE_AI_COPILOT_API_KEY !== undefined &&
21
+ VZCODE_AI_COLIPOT_BASE_URL !== undefined &&
22
+ VZCODE_AI_COLIPOT_MODEL !== undefined;
23
+
24
+ debug &&
25
+ console.log('isCopilotEnabled: ' + isCopilotEnabled);
26
+
27
+ let openai;
28
+ if (isCopilotEnabled) {
29
+ const openAIOptions = {
30
+ apiKey: VZCODE_AI_COPILOT_API_KEY,
31
+ baseURL: VZCODE_AI_COLIPOT_BASE_URL,
32
+ };
33
+ debug &&
34
+ console.log(
35
+ 'openAIOptions: ' +
36
+ JSON.stringify(openAIOptions, null, 2),
37
+ );
38
+ openai = new OpenAI(openAIOptions);
39
+ }
40
+
41
+ export const handleAICopilot =
42
+ (/*TODO shareDBDoc*/) => async (req, res) => {
43
+ // Don't break if the AI is not enabled.
44
+ // This is useful for local development.
45
+ if (!isCopilotEnabled) {
46
+ res.status(200).send({ text: '' });
47
+ return;
48
+ }
49
+ const { prefix, suffix } = req.body;
50
+
51
+ const prompt = `<|fim_prefix|>${prefix}<|fim_suffix|>${suffix}<|fim_middle|>`;
52
+
53
+ if (debug) {
54
+ console.log(
55
+ '[generateAIResponse] prompt:\n```\n' +
56
+ prompt +
57
+ '\n```\n',
58
+ );
59
+ }
60
+ try {
61
+ const completion = await openai.completions.create({
62
+ model: VZCODE_AI_COLIPOT_MODEL,
63
+ prompt,
64
+ max_tokens: 150,
65
+ stop: ['<|fim_pad|>', '<|file_sep|>'],
66
+ });
67
+ const text = completion.choices[0].text;
68
+
69
+ res.status(200).send({ text });
70
+ } catch (error) {
71
+ console.error('handleAICopilot error:', error);
72
+ res.status(500).send({
73
+ message: 'Internal Server Error',
74
+ error: error.message,
75
+ });
76
+ }
77
+ };
@@ -1,20 +1,22 @@
1
1
  #!/usr/bin/env node
2
- import './setupEnv.js';
3
- import http from 'http';
4
- import express from 'express';
5
- import ShareDB from 'sharedb';
6
- import { WebSocketServer } from 'ws';
7
2
  import WebSocketJSONStream from '@teamwork/websocket-json-stream';
3
+ import bodyParser from 'body-parser';
4
+ import express from 'express';
8
5
  import fs from 'fs';
6
+ import http from 'http';
7
+ import ngrok from 'ngrok';
8
+ import open from 'open';
9
9
  import path from 'path';
10
+ import ShareDB from 'sharedb';
10
11
  import { fileURLToPath } from 'url';
11
- import open from 'open';
12
- import ngrok from 'ngrok';
13
- import bodyParser from 'body-parser';
12
+ import { WebSocketServer } from 'ws';
14
13
  import { json1Presence } from '../ot.js';
15
14
  import { computeInitialDocument } from './computeInitialDocument.js';
16
15
  import { handleAIAssist } from './handleAIAssist.js';
16
+ import { handleAICopilot } from './handleAICopilot.js';
17
17
  import { isDirectory } from './isDirectory.js';
18
+ import { createToken } from './livekit.js';
19
+ import './setupEnv.js';
18
20
 
19
21
  // The time in milliseconds by which auto-saving is debounced.
20
22
  const autoSaveDebounceTimeMS = 800;
@@ -103,6 +105,19 @@ app.post(
103
105
  handleAIAssist(shareDBDoc),
104
106
  );
105
107
 
108
+ // Handle AI Copilot requests.
109
+ app.post(
110
+ '/ai-copilot',
111
+ bodyParser.json(),
112
+ handleAICopilot(shareDBDoc),
113
+ );
114
+
115
+ // Livekit Token Generator
116
+ app.get('/livekit-token', async (req, res) => {
117
+ const { room, username } = req.query;
118
+ res.send(await createToken(room, username));
119
+ });
120
+
106
121
  // The state of the document when files were last auto-saved.
107
122
  let previousDocument = initialDocument;
108
123
 
@@ -0,0 +1,24 @@
1
+ import { AccessToken } from 'livekit-server-sdk';
2
+
3
+ const { LIVEKIT_API_KEY, LIVEKIT_API_SECRET } = process.env;
4
+
5
+ export const createToken = async (roomName, username) => {
6
+ // If this room doesn't exist, it'll be automatically created when the first
7
+ // client joins
8
+ // Identifier to be used for participant.
9
+ // It's available as LocalParticipant.identity with livekit-client SDK
10
+ console.log(username);
11
+ const participantName = `${username}`;
12
+
13
+ const at = new AccessToken(
14
+ LIVEKIT_API_KEY,
15
+ LIVEKIT_API_SECRET,
16
+ {
17
+ identity: participantName,
18
+ // Token to expire after 10 minutes
19
+ ttl: '10m',
20
+ },
21
+ );
22
+ at.addGrant({ roomJoin: true, room: roomName });
23
+ return await at.toJwt();
24
+ };
@@ -6,7 +6,7 @@ const dir = fileURLToPath(import.meta.url);
6
6
  dotenv.config({
7
7
  path: join(dir, '../../../.env'),
8
8
  });
9
- if (process.env.DEBUG_SETUP_ENV) {
9
+ if (process.env.VZCODE_DEBUG_SETUP_ENV) {
10
10
  console.log(
11
11
  'environment variables set up',
12
12
  Object.fromEntries(
package/src/types.ts CHANGED
@@ -173,6 +173,7 @@ export type ShareDBDoc<T> = {
173
173
  options?: any,
174
174
  callback?: () => void,
175
175
  ) => void;
176
+ whenNothingPending: (callback: () => void) => void;
176
177
  };
177
178
 
178
179
  // A unique ID for an AI stream.