vzcode 1.32.0 → 1.33.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/index.html CHANGED
@@ -20,8 +20,8 @@
20
20
  href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap"
21
21
  rel="stylesheet"
22
22
  />
23
- <script type="module" crossorigin src="/assets/index-sreAszZh.js"></script>
24
- <link rel="stylesheet" crossorigin href="/assets/index-DTtcN2MP.css">
23
+ <script type="module" crossorigin src="/assets/index-DPIrco_g.js"></script>
24
+ <link rel="stylesheet" crossorigin href="/assets/index-DnunmWjy.css">
25
25
  </head>
26
26
  <body>
27
27
  <div id="root"></div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vzcode",
3
- "version": "1.32.0",
3
+ "version": "1.33.0",
4
4
  "description": "Multiplayer code editor system",
5
5
  "main": "src/index.ts",
6
6
  "type": "module",
@@ -108,78 +108,78 @@
108
108
  "@codemirror/lang-css": "^6.3.1",
109
109
  "@codemirror/lang-html": "^6.4.9",
110
110
  "@codemirror/lang-javascript": "^6.2.4",
111
- "@codemirror/lang-json": "^6.0.1",
112
- "@codemirror/lang-markdown": "^6.3.2",
111
+ "@codemirror/lang-json": "^6.0.2",
112
+ "@codemirror/lang-markdown": "^6.3.3",
113
113
  "@codemirror/lint": "^6.8.5",
114
114
  "@codemirror/state": "^6.5.2",
115
- "@codemirror/theme-one-dark": "^6.1.2",
116
- "@codemirror/view": "^6.37.1",
117
- "@langchain/core": "^0.3.57",
118
- "@langchain/openai": "^0.5.12",
115
+ "@codemirror/theme-one-dark": "^6.1.3",
116
+ "@codemirror/view": "^6.38.1",
117
+ "@langchain/core": "^0.3.65",
118
+ "@langchain/openai": "^0.6.2",
119
119
  "@lezer/highlight": "^1.2.1",
120
- "@livekit/components-react": "^2.9.9",
120
+ "@livekit/components-react": "^2.9.14",
121
121
  "@replit/codemirror-indentation-markers": "^6.5.3",
122
122
  "@replit/codemirror-interact": "^6.3.1",
123
123
  "@replit/codemirror-lang-svelte": "^6.0.0",
124
124
  "@replit/codemirror-vscode-keymap": "^6.0.2",
125
125
  "@teamwork/websocket-json-stream": "^2.0.0",
126
126
  "@typescript/vfs": "^1.6.1",
127
- "@uiw/codemirror-theme-abcdef": "^4.23.12",
128
- "@uiw/codemirror-theme-dracula": "^4.23.12",
129
- "@uiw/codemirror-theme-eclipse": "^4.23.12",
130
- "@uiw/codemirror-theme-github": "^4.23.12",
131
- "@uiw/codemirror-theme-material": "^4.23.12",
132
- "@uiw/codemirror-theme-nord": "^4.23.12",
133
- "@uiw/codemirror-theme-okaidia": "^4.23.12",
134
- "@uiw/codemirror-theme-xcode": "^4.23.12",
135
- "@uiw/codemirror-themes": "^4.23.12",
127
+ "@uiw/codemirror-theme-abcdef": "^4.24.1",
128
+ "@uiw/codemirror-theme-dracula": "^4.24.1",
129
+ "@uiw/codemirror-theme-eclipse": "^4.24.1",
130
+ "@uiw/codemirror-theme-github": "^4.24.1",
131
+ "@uiw/codemirror-theme-material": "^4.24.1",
132
+ "@uiw/codemirror-theme-nord": "^4.24.1",
133
+ "@uiw/codemirror-theme-okaidia": "^4.24.1",
134
+ "@uiw/codemirror-theme-xcode": "^4.24.1",
135
+ "@uiw/codemirror-themes": "^4.24.1",
136
136
  "@valtown/codemirror-ts": "^2.3.1",
137
- "@vizhub/viz-types": "^0.1.0",
137
+ "@vizhub/viz-types": "^0.2.0",
138
138
  "body-parser": "^2.2.0",
139
- "codemirror": "^6.0.1",
139
+ "codemirror": "^6.0.2",
140
140
  "codemirror-copilot": "^0.0.7",
141
141
  "codemirror-ot": "^4.4.0",
142
142
  "color-hash": "^2.0.2",
143
143
  "comlink": "^4.4.2",
144
144
  "d3-array": "^3.2.4",
145
145
  "diff-match-patch": "^1.0.5",
146
- "dotenv": "^16.5.0",
147
- "eslint": "^9.28.0",
148
- "eslint-linter-browserify": "^9.28.0",
146
+ "dotenv": "^17.2.0",
147
+ "eslint": "^9.31.0",
148
+ "eslint-linter-browserify": "^9.31.0",
149
149
  "express": "^5.1.0",
150
- "globals": "^16.2.0",
150
+ "globals": "^16.3.0",
151
151
  "ignore": "^7.0.5",
152
152
  "json0-ot-diff": "^1.1.2",
153
- "livekit-server-sdk": "^2.13.0",
153
+ "livekit-server-sdk": "^2.13.1",
154
154
  "ngrok": "^5.0.0-beta.2",
155
- "npm": "^11.4.1",
156
- "open": "^10.1.2",
155
+ "npm": "^11.4.2",
156
+ "open": "^10.2.0",
157
157
  "prettier-plugin-svelte": "^3.4.0",
158
158
  "react": "^18",
159
159
  "react-bootstrap": "^2.10.10",
160
160
  "react-dom": "^18",
161
- "react-router-dom": "^7.6.2",
161
+ "react-router-dom": "^7.7.0",
162
162
  "sharedb": "^5.2.2",
163
163
  "sharedb-client-browser": "^5.2.2",
164
164
  "uuid": "^11.1.0",
165
- "vizhub-ui": "^3.23.0",
166
- "ws": "^8.18.2"
165
+ "vizhub-ui": "^3.24.0",
166
+ "ws": "^8.18.3"
167
167
  },
168
168
  "devDependencies": {
169
169
  "@types/react": "^18",
170
170
  "@types/react-dom": "^18",
171
- "@vitejs/plugin-react": "^4.5.1",
172
- "concurrently": "^9.1.2",
171
+ "@vitejs/plugin-react": "^4.7.0",
172
+ "concurrently": "^9.2.0",
173
173
  "cross-env": "^7.0.3",
174
174
  "npm-check-updates": "^18.0.1",
175
- "prettier": "^3.5.3",
176
- "sass": "^1.89.1",
175
+ "prettier": "^3.6.2",
176
+ "sass": "^1.89.2",
177
177
  "typescript": "^5.8.3",
178
- "vite": "^6.3.5",
179
- "vitest": "^3.2.2"
178
+ "vite": "^7.0.5",
179
+ "vitest": "^3.2.4"
180
180
  },
181
181
  "optionalDependencies": {
182
- "@rollup/rollup-darwin-arm64": "^4.42.0",
183
- "@rollup/rollup-win32-x64-msvc": "^4.42.0"
182
+ "@rollup/rollup-darwin-arm64": "^4.45.1",
183
+ "@rollup/rollup-win32-x64-msvc": "^4.45.1"
184
184
  }
185
185
  }
@@ -59,6 +59,7 @@ import {
59
59
  tsLinterWorker,
60
60
  tsSyncWorker,
61
61
  } from '@valtown/codemirror-ts';
62
+ import { getFileExtension } from '../utils/fileExtension';
62
63
 
63
64
  const DEBUG = false;
64
65
 
@@ -131,6 +132,14 @@ const languageExtensions = {
131
132
  svelte,
132
133
  };
133
134
 
135
+ // Exported utility function to get language extension for a file extension
136
+ export const getLanguageExtension = (
137
+ fileExtension: string,
138
+ ) => {
139
+ const languageFunc = languageExtensions[fileExtension];
140
+ return languageFunc ? languageFunc() : undefined;
141
+ };
142
+
134
143
  // Gets a value at a path in an object.
135
144
  // e.g. getAtPath({a: {b: 1}}, ['a', 'b']) === 1
136
145
  const getAtPath = (obj, path) => {
@@ -146,6 +155,7 @@ const getAtPath = (obj, path) => {
146
155
  interface ExtendedEditorCacheValue
147
156
  extends EditorCacheValue {
148
157
  themeCompartment: Compartment;
158
+ languageCompartment: Compartment;
149
159
  rainbowBracketsCompartment: Compartment;
150
160
  updateRainbowBrackets: (enabled: boolean) => void;
151
161
  }
@@ -227,7 +237,8 @@ export const getOrCreateEditor = async ({
227
237
  const namePath = [...filesPath, fileId, 'name'];
228
238
  const text = getAtPath(content, textPath);
229
239
  const name = getAtPath(content, namePath);
230
- const fileExtension = name.split('.').pop();
240
+
241
+ const fileExtension = getFileExtension(name);
231
242
 
232
243
  // Create a compartment for the theme so that it can be changed dynamically.
233
244
  // Inspired by: https://github.com/craftzdog/cm6-themes/blob/main/example/index.ts
@@ -321,10 +332,6 @@ export const getOrCreateEditor = async ({
321
332
  // using a Compartment.
322
333
  const languageCompartment = new Compartment();
323
334
  // See https://github.com/vizhub-core/vzcode/issues/55
324
- const getLanguageExtension = (fileExtension: string) => {
325
- const languageFunc = languageExtensions[fileExtension];
326
- return languageFunc ? languageFunc() : undefined;
327
- };
328
335
 
329
336
  const languageExtension =
330
337
  getLanguageExtension(fileExtension);
@@ -346,6 +353,11 @@ export const getOrCreateEditor = async ({
346
353
  extensions.push(languageCompartment.of([]));
347
354
  }
348
355
 
356
+ // Enable line wrapping for Markdown files
357
+ if (fileExtension === 'md') {
358
+ extensions.push(EditorView.lineWrapping);
359
+ }
360
+
349
361
  // Add interactive widgets.
350
362
  // Includes the Alt+drag functionality for numbers.
351
363
  // Calls `onInteract` when one of those widgets is interacted with.
@@ -458,6 +470,7 @@ export const getOrCreateEditor = async ({
458
470
  const editorCacheValue: ExtendedEditorCacheValue = {
459
471
  editor,
460
472
  themeCompartment,
473
+ languageCompartment,
461
474
  rainbowBracketsCompartment,
462
475
  updateRainbowBrackets: (enabled) => {
463
476
  editor.dispatch({
@@ -7,7 +7,7 @@ export const MicSVG = () => (
7
7
  version="1.1"
8
8
  xmlns="http://www.w3.org/2000/svg"
9
9
  viewBox="0 0 512 512"
10
- enable-background="new 0 0 512 512"
10
+ enableBackground="new 0 0 512 512"
11
11
  >
12
12
  <g>
13
13
  <g>
@@ -113,6 +113,11 @@ export type VZCodeContextValue = {
113
113
  isSearchOpen: boolean;
114
114
  setIsSearchOpen: (isSearchOpen: boolean) => void;
115
115
  setSearch: (pattern: string) => void;
116
+
117
+ isAIChatOpen: boolean;
118
+ setIsAIChatOpen: (isAIChatOpen: boolean) => void;
119
+ aiChatFocused: boolean;
120
+ toggleAIChatFocused: () => void;
116
121
  setSearchResults: (files: ShareDBDoc<VizContent>) => void;
117
122
  setSearchFileVisibility: (
118
123
  files: ShareDBDoc<VizContent>,
@@ -252,6 +257,8 @@ export const VZCodeProvider = ({
252
257
  theme,
253
258
  search,
254
259
  isSearchOpen,
260
+ isAIChatOpen,
261
+ aiChatFocused,
255
262
  isSettingsOpen,
256
263
  isDocOpen,
257
264
  editorWantsFocus,
@@ -260,7 +267,7 @@ export const VZCodeProvider = ({
260
267
  sidebarPresenceIndicators,
261
268
  } = state;
262
269
 
263
- const activePane = findPane(pane, activePaneId);
270
+ const activePane: Pane = findPane(pane, activePaneId);
264
271
  if (activePane.type !== 'leafPane') {
265
272
  // Should never happen
266
273
  throw new Error('Expected leafPane');
@@ -281,6 +288,8 @@ export const VZCodeProvider = ({
281
288
  setSearchLineVisibility,
282
289
  setSearchFocusedIndex,
283
290
  toggleSearchFocused,
291
+ setIsAIChatOpen,
292
+ toggleAIChatFocused,
284
293
  setIsSettingsOpen,
285
294
  setIsDocOpen,
286
295
  closeSettings,
@@ -304,7 +313,7 @@ export const VZCodeProvider = ({
304
313
  // The set of open directories.
305
314
  // TODO move this into reducer/useActions
306
315
  const { isDirectoryOpen, toggleDirectory } =
307
- useOpenDirectories();
316
+ useOpenDirectories({ activePane, content });
308
317
 
309
318
  // Cache of CodeMirror editors by file id.
310
319
  const editorCache: EditorCache = useEditorCache();
@@ -324,6 +333,8 @@ export const VZCodeProvider = ({
324
333
  submitOperation,
325
334
  closeTabs,
326
335
  openTab,
336
+ editorCache,
337
+ content,
327
338
  });
328
339
 
329
340
  // State to control the create file modal's visibility
@@ -435,6 +446,11 @@ export const VZCodeProvider = ({
435
446
  setSearchFocusedIndex,
436
447
  toggleSearchFocused,
437
448
 
449
+ isAIChatOpen,
450
+ setIsAIChatOpen,
451
+ aiChatFocused,
452
+ toggleAIChatFocused,
453
+
438
454
  isSettingsOpen,
439
455
  setIsSettingsOpen,
440
456
  closeSettings,
@@ -0,0 +1,142 @@
1
+ import {
2
+ useRef,
3
+ useEffect,
4
+ useContext,
5
+ useState,
6
+ useCallback,
7
+ } from 'react';
8
+ import { Form, Button } from '../bootstrap';
9
+ import { VZCodeContext } from '../VZCodeContext';
10
+
11
+ export const AIChat = () => {
12
+ const [message, setMessage] = useState('');
13
+ const [messages, setMessages] = useState([
14
+ {
15
+ id: 1,
16
+ type: 'assistant',
17
+ content:
18
+ "Hello! I'm your AI assistant. How can I help you with your code today?",
19
+ timestamp: new Date(),
20
+ },
21
+ ]);
22
+ const [isLoading, setIsLoading] = useState(false);
23
+ const inputRef = useRef(null);
24
+ const messagesEndRef = useRef(null);
25
+
26
+ const { aiChatFocused } = useContext(VZCodeContext);
27
+
28
+ const scrollToBottom = () => {
29
+ messagesEndRef.current?.scrollIntoView({
30
+ behavior: 'smooth',
31
+ });
32
+ };
33
+
34
+ useEffect(() => {
35
+ scrollToBottom();
36
+ }, [messages]);
37
+
38
+ useEffect(() => {
39
+ // Focus the input when the AI chat is focused
40
+ if (aiChatFocused && inputRef.current) {
41
+ inputRef.current.focus();
42
+ }
43
+ }, [aiChatFocused]);
44
+
45
+ const handleSendMessage = useCallback(async () => {
46
+ if (!message.trim() || isLoading) return;
47
+
48
+ const userMessage = {
49
+ id: Date.now(),
50
+ type: 'user',
51
+ content: message.trim(),
52
+ timestamp: new Date(),
53
+ };
54
+
55
+ setMessages((prev) => [...prev, userMessage]);
56
+ setMessage('');
57
+ setIsLoading(true);
58
+
59
+ // Simulate AI response (replace with actual AI integration later)
60
+ setTimeout(() => {
61
+ const aiResponse = {
62
+ id: Date.now() + 1,
63
+ type: 'assistant',
64
+ content:
65
+ 'I understand you\'re asking about: "' +
66
+ userMessage.content +
67
+ '". This is a placeholder response. The AI chat feature is still being developed!',
68
+ timestamp: new Date(),
69
+ };
70
+ setMessages((prev) => [...prev, aiResponse]);
71
+ setIsLoading(false);
72
+ }, 1000);
73
+ }, [message, isLoading]);
74
+
75
+ const handleKeyDown = (event) => {
76
+ if (event.key === 'Enter' && !event.shiftKey) {
77
+ event.preventDefault();
78
+ handleSendMessage();
79
+ }
80
+ };
81
+
82
+ return (
83
+ <div className="ai-chat-container">
84
+ <div className="ai-chat-messages">
85
+ {messages.map((msg) => (
86
+ <div
87
+ key={msg.id}
88
+ className={`ai-chat-message ${msg.type === 'user' ? 'user' : 'assistant'}`}
89
+ >
90
+ <div className="ai-chat-message-content">
91
+ {msg.content}
92
+ </div>
93
+ <div className="ai-chat-message-time">
94
+ {msg.timestamp.toLocaleTimeString([], {
95
+ hour: '2-digit',
96
+ minute: '2-digit',
97
+ })}
98
+ </div>
99
+ </div>
100
+ ))}
101
+ {isLoading && (
102
+ <div className="ai-chat-message assistant">
103
+ <div className="ai-chat-message-content">
104
+ <div className="ai-chat-typing">
105
+ <span></span>
106
+ <span></span>
107
+ <span></span>
108
+ </div>
109
+ </div>
110
+ </div>
111
+ )}
112
+ <div ref={messagesEndRef} />
113
+ </div>
114
+
115
+ <div className="ai-chat-input-container">
116
+ <Form.Group className="ai-chat-input-group">
117
+ <Form.Control
118
+ as="textarea"
119
+ rows={2}
120
+ value={message}
121
+ onChange={(event) =>
122
+ setMessage(event.target.value)
123
+ }
124
+ onKeyDown={handleKeyDown}
125
+ ref={inputRef}
126
+ placeholder="Ask me anything about your code..."
127
+ spellCheck="false"
128
+ disabled={isLoading}
129
+ />
130
+ <Button
131
+ variant="primary"
132
+ onClick={handleSendMessage}
133
+ disabled={!message.trim() || isLoading}
134
+ className="ai-chat-send-button"
135
+ >
136
+ Send
137
+ </Button>
138
+ </Form.Group>
139
+ </div>
140
+ </div>
141
+ );
142
+ };
@@ -25,15 +25,20 @@ import {
25
25
  PinSVG,
26
26
  QuestionMarkSVG,
27
27
  SearchSVG,
28
+ SparklesSVG,
28
29
  } from '../Icons';
29
30
  import { MicSVG } from '../Icons/MicSVG';
30
31
  import { sortFileTree } from '../sortFileTree';
31
32
  import { SplitPaneResizeContext } from '../SplitPaneResizeContext';
32
33
  import { VZCodeContext } from '../VZCodeContext';
34
+ import { AIChat } from './AIChat';
33
35
  import { Listing } from './Listing';
34
36
  import { Search } from './Search';
35
37
  import { useDragAndDrop } from './useDragAndDrop';
36
- import { enableLiveKit } from '../featureFlags';
38
+ import {
39
+ enableLiveKit,
40
+ enableAIChat,
41
+ } from '../featureFlags';
37
42
  import './styles.scss';
38
43
 
39
44
  // TODO turn this UI back on when we are actually detecting
@@ -105,6 +110,11 @@ export const VZSidebar = ({
105
110
  <strong>Open Voice Chat Menu</strong>
106
111
  </div>
107
112
  ),
113
+ aiChatToolTipText = (
114
+ <div>
115
+ <strong>AI Chat</strong>
116
+ </div>
117
+ ),
108
118
  }: {
109
119
  createFileTooltipText?: React.ReactNode;
110
120
  createDirTooltipText?: React.ReactNode;
@@ -116,6 +126,7 @@ export const VZSidebar = ({
116
126
  enableAutoFollowTooltipText?: React.ReactNode;
117
127
  disableAutoFollowTooltipText?: React.ReactNode;
118
128
  voiceChatToolTipText?: React.ReactNode;
129
+ aiChatToolTipText?: React.ReactNode;
119
130
  }) => {
120
131
  const {
121
132
  files,
@@ -124,6 +135,8 @@ export const VZSidebar = ({
124
135
  setIsDocOpen,
125
136
  isSearchOpen,
126
137
  setIsSearchOpen,
138
+ isAIChatOpen,
139
+ setIsAIChatOpen,
127
140
  handleOpenCreateFileModal,
128
141
  handleOpenCreateDirModal,
129
142
  connected,
@@ -270,7 +283,10 @@ export const VZSidebar = ({
270
283
  <i
271
284
  id="files-icon"
272
285
  className="icon-button icon-button-dark"
273
- onClick={() => setIsSearchOpen(false)}
286
+ onClick={() => {
287
+ setIsSearchOpen(false);
288
+ setIsAIChatOpen(false);
289
+ }}
274
290
  >
275
291
  <FolderSVG />
276
292
  </i>
@@ -287,12 +303,37 @@ export const VZSidebar = ({
287
303
  <i
288
304
  id="search-icon"
289
305
  className="icon-button icon-button-dark"
290
- onClick={() => setIsSearchOpen(true)}
306
+ onClick={() => {
307
+ setIsSearchOpen(true);
308
+ setIsAIChatOpen(false);
309
+ }}
291
310
  >
292
311
  <SearchSVG />
293
312
  </i>
294
313
  </OverlayTrigger>
295
314
 
315
+ {enableAIChat && (
316
+ <OverlayTrigger
317
+ placement="right"
318
+ overlay={
319
+ <Tooltip id="ai-chat-tooltip">
320
+ {aiChatToolTipText}
321
+ </Tooltip>
322
+ }
323
+ >
324
+ <i
325
+ id="ai-chat-icon"
326
+ className="icon-button icon-button-dark"
327
+ onClick={() => {
328
+ setIsAIChatOpen(true);
329
+ setIsSearchOpen(false);
330
+ }}
331
+ >
332
+ <SparklesSVG />
333
+ </i>
334
+ </OverlayTrigger>
335
+ )}
336
+
296
337
  <OverlayTrigger
297
338
  placement="right"
298
339
  overlay={
@@ -438,7 +479,15 @@ export const VZSidebar = ({
438
479
  onDragLeave={handleDragLeave}
439
480
  onDrop={handleDrop}
440
481
  >
441
- {!isSearchOpen ? (
482
+ {isAIChatOpen ? (
483
+ <div className="sidebar-ai-chat">
484
+ <AIChat />
485
+ </div>
486
+ ) : isSearchOpen ? (
487
+ <div className="sidebar-search">
488
+ <Search />
489
+ </div>
490
+ ) : (
442
491
  <div className="sidebar-files">
443
492
  {isDragOver ? (
444
493
  <div className="empty drag-over">
@@ -480,10 +529,6 @@ export const VZSidebar = ({
480
529
  </div>
481
530
  )}
482
531
  </div>
483
- ) : (
484
- <div className="sidebar-search">
485
- <Search />
486
- </div>
487
532
  )}
488
533
  </div>
489
534
  </div>