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
@@ -32,7 +32,8 @@ class ColorWidget extends WidgetType {
32
32
 
33
33
  parent.setAttribute(
34
34
  'style',
35
- `width:${size}px;height:${size}px`,
35
+ `width:${size}px;height:${size}px;position:relative;`,
36
+ // Position relative for the hovering tool tip to work
36
37
  );
37
38
  parent.className = 'color-circle-parent';
38
39
  const svg = document.createElementNS(
@@ -72,8 +73,42 @@ class ColorWidget extends WidgetType {
72
73
  svg.appendChild(colorCircle);
73
74
 
74
75
  parent.appendChild(svg);
76
+
77
+ // Hovering tool tip shortcut for when users hover over color circle
78
+ const hoveringTooltip = document.createElement('div');
79
+
80
+ // Set class for tooltip to use CSS styles
81
+ hoveringTooltip.className = 'tooltip_hover_circle';
82
+
83
+
84
+ hoveringTooltip.innerHTML =
85
+ `<strong>Alt + Click on a hex color</strong><br />
86
+ <div>Open a color picker to modify the color</div>`;
87
+
88
+
89
+ parent.appendChild(hoveringTooltip);
90
+
91
+ // Timer to prevent constant pop-ups when scrolling through code
92
+ let hoverTimeout: number;
93
+
94
+ // Fade in and fade out effect for when user hovers over the circle
95
+ // Mouse Event to set y and x of the tool tip pop up to prevent overlap onto circle
96
+ parent.addEventListener('mouseenter', (event: MouseEvent) => {
97
+ hoverTimeout = window.setTimeout(() => {
98
+ hoveringTooltip.style.top = `${event.clientY - 60}px`;
99
+ hoveringTooltip.style.left = `${event.clientX}px`;
100
+ hoveringTooltip.style.opacity = '1';
101
+ }, 700);
102
+ });
103
+
104
+ parent.addEventListener('mouseleave', () => {
105
+ clearTimeout(hoverTimeout);
106
+ hoveringTooltip.style.opacity = '0';
107
+ });
108
+
75
109
  return parent;
76
110
  }
111
+
77
112
  ignoreEvent() {
78
113
  return false;
79
114
  }
@@ -10,11 +10,13 @@ import { markdown } from '@codemirror/lang-markdown';
10
10
  import { html } from '@codemirror/lang-html';
11
11
  import { css } from '@codemirror/lang-css';
12
12
  import { json1Sync } from 'codemirror-ot';
13
+
13
14
  import { autocompletion } from '@codemirror/autocomplete';
14
15
  import { indentationMarkers } from '@replit/codemirror-indentation-markers';
15
16
  // import { showMinimap } from '@replit/codemirror-minimap';
16
17
  import { vscodeKeymap } from '@replit/codemirror-vscode-keymap';
17
18
  import { Diagnostic, linter } from '@codemirror/lint';
19
+
18
20
  import { json1Presence, textUnicode } from '../../ot';
19
21
  import {
20
22
  FileId,
@@ -45,11 +47,14 @@ import { InteractRule } from '@replit/codemirror-interact';
45
47
  import rainbowBrackets from '../CodeEditor/rainbowBrackets';
46
48
  import { cssLanguage } from '@codemirror/lang-css';
47
49
  import { javascriptLanguage } from '@codemirror/lang-javascript';
50
+ import { copilot } from './Copilot';
48
51
 
49
52
  // Feature flag to enable TypeScript completions & TypeScript Linter.
50
- const enableTypeScriptCompletions = true;
51
53
  const enableTypeScriptLinter = true;
52
54
 
55
+ // Feature flag to enable AI copilot
56
+ const enableCopilot = true;
57
+
53
58
  // Enables TypeScript +JSX support in CodeMirror.
54
59
  const tsx = () =>
55
60
  javascript({ jsx: true, typescript: true });
@@ -74,6 +79,7 @@ const htmlConfig = {
74
79
  ],
75
80
  nestedAttributes: [],
76
81
  };
82
+
77
83
  // Language extensions for CodeMirror.
78
84
  // Keys are file extensions.
79
85
  // Values are CodeMirror extensions.
@@ -101,6 +107,13 @@ const getAtPath = (obj, path) => {
101
107
  return current;
102
108
  };
103
109
 
110
+ // Extend the EditorCacheValue type to include the compartments and the updateRainbowBrackets method
111
+ interface ExtendedEditorCacheValue extends EditorCacheValue {
112
+ themeCompartment: Compartment;
113
+ rainbowBracketsCompartment: Compartment;
114
+ updateRainbowBrackets: (enabled: boolean) => void;
115
+ }
116
+
104
117
  // Gets or creates an `editorCache` entry for the given file id.
105
118
  // Looks in `editorCache` first, and if not found, creates a new editor.
106
119
  export const getOrCreateEditor = ({
@@ -120,6 +133,8 @@ export const getOrCreateEditor = ({
120
133
  allowGlobals,
121
134
  enableAutoFollowRef,
122
135
  openTab,
136
+ aiCopilotEndpoint,
137
+ rainbowBracketsEnabled = true, // New parameter to enable or disable Rainbow Brackets
123
138
  }: {
124
139
  // TODO pass this in from the outside
125
140
  paneId?: PaneId;
@@ -156,13 +171,15 @@ export const getOrCreateEditor = ({
156
171
  // enable auto-following the cursors of remote users.
157
172
  enableAutoFollowRef: React.MutableRefObject<boolean>;
158
173
  openTab: (tabState: TabState) => void;
159
- }): EditorCacheValue => {
174
+ aiCopilotEndpoint: string;
175
+ rainbowBracketsEnabled?: boolean; // New parameter type
176
+ }): ExtendedEditorCacheValue => {
160
177
  // Cache hit
161
178
 
162
179
  const cacheKey = editorCacheKey(fileId, paneId);
163
180
 
164
181
  if (editorCache.has(cacheKey)) {
165
- return editorCache.get(cacheKey);
182
+ return editorCache.get(cacheKey) as ExtendedEditorCacheValue;
166
183
  }
167
184
 
168
185
  // Cache miss
@@ -177,6 +194,9 @@ export const getOrCreateEditor = ({
177
194
  // Create a compartment for the theme so that it can be changed dynamically.
178
195
  // Inspired by: https://github.com/craftzdog/cm6-themes/blob/main/example/index.ts
179
196
  let themeCompartment = new Compartment();
197
+
198
+ // Create a compartment for rainbow brackets so that it can be enabled/disabled dynamically.
199
+ let rainbowBracketsCompartment = new Compartment();
180
200
 
181
201
  // The CodeMirror extensions to use.
182
202
  // const extensions = [autocompletion(), html(htmlConfig)]
@@ -237,6 +257,13 @@ export const getOrCreateEditor = ({
237
257
  themeCompartment.of(themeOptionsByLabel[theme].value),
238
258
  );
239
259
 
260
+ // Adds compartment for rainbow brackets with initial toggle state.
261
+ extensions.push(
262
+ rainbowBracketsCompartment.of(
263
+ rainbowBracketsEnabled ? rainbowBrackets() : []
264
+ ),
265
+ );
266
+
240
267
  // TODO handle dynamic changing of the file extension.
241
268
  // TODO handle dynamic file extensions by making
242
269
  // the CodeMirror language extension dynamic
@@ -380,8 +407,11 @@ export const getOrCreateEditor = ({
380
407
  }),
381
408
  ),
382
409
  );
383
- // adds rainbow brackets
384
- extensions.push(rainbowBrackets());
410
+
411
+ // adds copilot
412
+ if (enableCopilot) {
413
+ extensions.push(copilot({ aiCopilotEndpoint }));
414
+ }
385
415
 
386
416
  const editor = new EditorView({
387
417
  state: EditorState.create({
@@ -390,10 +420,17 @@ export const getOrCreateEditor = ({
390
420
  }),
391
421
  });
392
422
 
393
- const editorCacheValue = { editor, themeCompartment };
423
+ const editorCacheValue: ExtendedEditorCacheValue = { editor, themeCompartment, rainbowBracketsCompartment, updateRainbowBrackets: (enabled) => {
424
+ editor.dispatch({
425
+ effects: rainbowBracketsCompartment.reconfigure(enabled ? rainbowBrackets() : []),
426
+ });
427
+ }};
394
428
 
395
429
  // Populate the cache.
396
430
  editorCache.set(cacheKey, editorCacheValue);
397
431
 
432
+ // Initialize rainbow brackets based on the toggle state
433
+ editorCacheValue.updateRainbowBrackets(rainbowBracketsEnabled);
434
+
398
435
  return editorCacheValue;
399
436
  };
@@ -19,12 +19,14 @@ const filesPath = ['files'];
19
19
  export const CodeEditor = ({
20
20
  customInteractRules,
21
21
  allowGlobals,
22
+ aiCopilotEndpoint,
22
23
  }: {
23
24
  customInteractRules?: Array<InteractRule>;
24
25
  allowGlobals: boolean;
26
+ aiCopilotEndpoint: string;
25
27
  }) => {
26
28
  const {
27
- activeFileId,
29
+ activePane,
28
30
  shareDBDoc,
29
31
  content,
30
32
  submitOperation,
@@ -84,7 +86,7 @@ export const CodeEditor = ({
84
86
  const editorCacheValue: EditorCacheValue = useMemo(
85
87
  () =>
86
88
  getOrCreateEditor({
87
- fileId: activeFileId,
89
+ fileId: activePane.activeFileId,
88
90
  shareDBDoc,
89
91
  content,
90
92
  filesPath,
@@ -99,10 +101,10 @@ export const CodeEditor = ({
99
101
  allowGlobals,
100
102
  enableAutoFollowRef,
101
103
  openTab,
102
- // splitPaneView,
104
+ aiCopilotEndpoint,
103
105
  }),
104
106
  [
105
- activeFileId,
107
+ activePane,
106
108
  shareDBDoc,
107
109
  filesPath,
108
110
  localPresence,
@@ -112,6 +114,7 @@ export const CodeEditor = ({
112
114
  editorCache,
113
115
  usernameRef,
114
116
  typeScriptWorker,
117
+ aiCopilotEndpoint,
115
118
  ],
116
119
  );
117
120
 
@@ -5,7 +5,7 @@ import {
5
5
  Decoration,
6
6
  } from '@codemirror/view';
7
7
  import { Annotation, RangeSet } from '@codemirror/state';
8
- import ColorHash from 'color-hash';
8
+ import { assignUserColor } from '../presenceColor';
9
9
  import {
10
10
  FileId,
11
11
  Presence,
@@ -114,11 +114,11 @@ export const json1PresenceDisplay = ({
114
114
  const { start, end } = presence;
115
115
  const from = +start[start.length - 1];
116
116
  const to = +end[end.length - 1];
117
- const userColor = new ColorHash({
118
- lightness: 0.75,
119
- }).rgb(id);
117
+ const userColor = assignUserColor(
118
+ presence.username,
119
+ );
120
120
  const { username } = presence;
121
-
121
+ //console.log("File User Color:" + assignUserColor(presence.username));
122
122
  presenceDecorations.push({
123
123
  from,
124
124
  to: from,
@@ -8,6 +8,12 @@ function generateColors() {
8
8
  return ['red', 'orange', 'yellow', 'green'];
9
9
  }
10
10
 
11
+ let isRainbowEnabled = true;
12
+
13
+ export const toggleRainbowBrackets = (enabled: boolean) => {
14
+ isRainbowEnabled = enabled;
15
+ };
16
+
11
17
  const rainbowBracketsPlugin = ViewPlugin.fromClass(
12
18
  class {
13
19
  decorations;
@@ -50,12 +56,16 @@ const rainbowBracketsPlugin = ViewPlugin.fromClass(
50
56
  ) {
51
57
  const color =
52
58
  colors[stack.length % colors.length];
59
+ const bracketClass = isRainbowEnabled
60
+ ? `rainbow-bracket-${color}`
61
+ : `default-bracket`;
62
+
53
63
  decorations.push(
54
64
  Decoration.mark({
55
- class: `rainbow-bracket-${color}`,
65
+ class: bracketClass,
56
66
  }).range(open.from, open.from + 1),
57
67
  Decoration.mark({
58
- class: `rainbow-bracket-${color}`,
68
+ class: bracketClass,
59
69
  }).range(pos, pos + 1),
60
70
  );
61
71
  }
@@ -105,6 +115,8 @@ export default function rainbowBrackets() {
105
115
  },
106
116
  '.rainbow-bracket-green': { color: '#B2FFB2' },
107
117
  '.rainbow-bracket-green > span': { color: '#B2FFB2' },
118
+ '.default-bracket': { color: 'currentColor' },
119
+ '.default-bracket > span': { color: 'currentColor' },
108
120
  }),
109
121
  ];
110
122
  }
@@ -48,4 +48,109 @@
48
48
  .cm-line {
49
49
  font-family: var(--vzcode-font-family);
50
50
  }
51
- }
51
+
52
+ .tooltip_hover_circle {
53
+ color: white;
54
+ text-align: center;
55
+ position: fixed;
56
+ background-color: black;
57
+ padding: 5px;
58
+ border-radius: 5px;
59
+ white-space: nowrap;
60
+ opacity: 0;
61
+ transition: opacity 0.3s ease;
62
+ z-index: 1000;
63
+ pointer-events: none;
64
+ }
65
+
66
+ .tooltip_hover_circle strong {
67
+ font-size: 14px;
68
+ }
69
+
70
+ .tooltip_hover_circle div {
71
+ font-size: 12px;
72
+ }
73
+
74
+ .cm-panel.cm-search {
75
+ padding: 5px;
76
+ position: relative;
77
+ background-color: var(--vh-color-neutral-01);
78
+ border-radius: 6px;
79
+ color: var(--vh-color-neutral-04);
80
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.4);
81
+ }
82
+
83
+ .cm-panels {
84
+ background-color: #333338;
85
+ color: var(--vh-color-neutral-04);
86
+ border: 2px solid var(--vh-color-neutral-02);
87
+ border-radius: 6px;
88
+ }
89
+
90
+ .cm-textfield {
91
+ background-color: var(--vh-color-neutral-01);
92
+ color: #efe7dc;
93
+ border: 1px solid var(--vh-color-neutral-04);
94
+ padding: 8px;
95
+ border-radius: 6px;
96
+ font-size: 14px;
97
+ }
98
+
99
+ .cm-button {
100
+ background-color: var(--vh-color-neutral-01);
101
+ border: 1px solid #193350;
102
+ color: var(--vh-color-neutral-04);
103
+ border: 1px solid var(--vh-color-neutral-04);
104
+ border-radius: 5px;
105
+ padding: 6px 12px;
106
+ font-weight: bold;
107
+ cursor: pointer;
108
+ transition: all 0.3s ease-in-out;
109
+ }
110
+
111
+ .cm-button:hover {
112
+ background-color: #50c878;
113
+ color: #ffffff;
114
+ transform: scale(1.1);
115
+ }
116
+
117
+ .cm-button:active {
118
+ background-color: #202e46;
119
+ transform: scale(0.95);
120
+ }
121
+
122
+ .cm-searchMatch {
123
+ background-color: rgba(255, 255, 0, 0.5);
124
+ border-radius: 2px;
125
+ }
126
+
127
+ .cm-searchMatch-selected {
128
+ background-color: rgba(255, 140, 0, 0.5);
129
+ border: 2px solid #ff8800;
130
+ border-radius: 2px;
131
+ }
132
+ .cm-panel.cm-search [name="close"] {
133
+ position: absolute;
134
+ top: 0;
135
+ right: 4px;
136
+ background-color: var(--vh-color-neutral-01);
137
+ color: var(--vh-color-neutral-04);
138
+ border: none;
139
+ font: inherit;
140
+ padding: 4px 8px;
141
+ margin: 4px;
142
+ border-radius: 4px;
143
+ cursor: pointer;
144
+ transition: all 0.3s ease-in-out;
145
+ }
146
+
147
+ .cm-panel.cm-search [name="close"]:hover {
148
+ background-color: var(--vh-color-caution-01);
149
+ color: #ffffff;
150
+ }
151
+
152
+ .cm-panel.cm-search [name="close"]:active {
153
+ background-color: var(--vh-color-caution-02);
154
+ transform: scale(0.95);
155
+ }
156
+ }
@@ -0,0 +1,25 @@
1
+ export const MicSVG = () => (
2
+ <svg
3
+ fill="#000000"
4
+ height="24"
5
+ width="24"
6
+ stroke="currentColor"
7
+ version="1.1"
8
+ xmlns="http://www.w3.org/2000/svg"
9
+ viewBox="0 0 512 512"
10
+ enable-background="new 0 0 512 512"
11
+ >
12
+ <g>
13
+ <g>
14
+ <path
15
+ fill="currentColor"
16
+ d="m439.5,236c0-11.3-9.1-20.4-20.4-20.4s-20.4,9.1-20.4,20.4c0,70-64,126.9-142.7,126.9-78.7,0-142.7-56.9-142.7-126.9 0-11.3-9.1-20.4-20.4-20.4s-20.4,9.1-20.4,20.4c0,86.2 71.5,157.4 163.1,166.7v57.5h-23.6c-11.3,0-20.4,9.1-20.4,20.4 0,11.3 9.1,20.4 20.4,20.4h88c11.3,0 20.4-9.1 20.4-20.4 0-11.3-9.1-20.4-20.4-20.4h-23.6v-57.5c91.6-9.3 163.1-80.5 163.1-166.7z"
17
+ />
18
+ <path
19
+ fill="currentColor"
20
+ d="m256,323.5c51,0 92.3-41.3 92.3-92.3v-127.9c0-51-41.3-92.3-92.3-92.3s-92.3,41.3-92.3,92.3v127.9c0,51 41.3,92.3 92.3,92.3zm-52.3-220.2c0-28.8 23.5-52.3 52.3-52.3s52.3,23.5 52.3,52.3v127.9c0,28.8-23.5,52.3-52.3,52.3s-52.3-23.5-52.3-52.3v-127.9z"
21
+ />
22
+ </g>
23
+ </g>
24
+ </svg>
25
+ );
@@ -1,4 +1,4 @@
1
- import { useCallback, useContext, useState } from 'react';
1
+ import { useCallback, useContext, useEffect, useState } from 'react';
2
2
  import { PlaySVG } from '../Icons';
3
3
  import { SplitEditorSVG } from '../Icons/SplitEditorSVG';
4
4
  import { VZCodeContext } from '../VZCodeContext';
@@ -17,7 +17,7 @@ export const RunCodeWidget = ({
17
17
  </>
18
18
  ),
19
19
  }: {
20
- runCodeWidgetTooltipText?: JSX.Element;
20
+ runCodeWidgetTooltipText?: JSX.Element;
21
21
  }) => {
22
22
  const { runCodeRef, runPrettierRef, splitCurrentPane } =
23
23
  useContext(VZCodeContext);
@@ -40,7 +40,7 @@ export const RunCodeWidget = ({
40
40
 
41
41
  // Optional: reset the icon state after animation completes (e.g., 1 second)
42
42
  setTimeout(() => setIsRunning(false), 1000);
43
- }, []);
43
+ }, [runCodeRef, runPrettierRef]);
44
44
 
45
45
  const handleSplitEditor = useCallback(() => {
46
46
  console.log('Split Editor');
@@ -48,6 +48,23 @@ export const RunCodeWidget = ({
48
48
  splitCurrentPane();
49
49
  }, [splitCurrentPane]);
50
50
 
51
+ // Add the keyboard event listener for Ctrl+S and Shift+Enter
52
+ useEffect(() => {
53
+ const handleKeyDown = (event: KeyboardEvent) => {
54
+ if ((event.ctrlKey && event.key === 's') || (event.shiftKey && event.key === 'Enter')) {
55
+ event.preventDefault();
56
+ handleClick();
57
+ }
58
+ };
59
+
60
+ window.addEventListener('keydown', handleKeyDown);
61
+
62
+ // Cleanup the event listener on component unmount
63
+ return () => {
64
+ window.removeEventListener('keydown', handleKeyDown);
65
+ };
66
+ }, [handleClick]);
67
+
51
68
  return (
52
69
  <div>
53
70
  <div className="vz-code-split-view-widget">
@@ -1,26 +1,23 @@
1
1
  import { useContext } from 'react';
2
+ import { TabState } from '../../types';
2
3
  import { VZCodeContext } from '../VZCodeContext';
3
4
  import { Tab } from './Tab';
4
5
  import './style.scss';
5
- import { TabState } from '../../types';
6
6
 
7
7
  // Displays the list of tabs above the code editor.
8
- export const TabList = () => {
9
- const {
10
- files,
11
- activeFileId,
12
- setActiveFileId,
13
- tabList,
14
- openTab,
15
- closeTabs,
16
- } = useContext(VZCodeContext);
8
+ export const TabList = ({ activeFileId, tabList }) => {
9
+ const { files, setActiveFileId, openTab, closeTabs } =
10
+ useContext(VZCodeContext);
17
11
 
18
12
  return (
19
13
  <div className="vz-tab-list">
20
14
  {files &&
21
15
  tabList
22
16
  // Handles the case that the tab list references a file that was deleted.
23
- .filter((tabState) => tabState.fileId in files)
17
+ .filter(
18
+ (tabState: TabState) =>
19
+ tabState.fileId in files,
20
+ )
24
21
  .map((tabState: TabState) => (
25
22
  <Tab
26
23
  key={tabState.fileId}