vzcode 1.62.0 → 2.2.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-vbF7Aejt.js"></script>
24
- <link rel="stylesheet" crossorigin href="/assets/index-Br76WttW.css">
23
+ <script type="module" crossorigin src="/assets/index-CpxqGhXj.js"></script>
24
+ <link rel="stylesheet" crossorigin href="/assets/index-BM8tDh6M.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.62.0",
3
+ "version": "2.2.0",
4
4
  "description": "Multiplayer code editor system",
5
5
  "main": "src/index.ts",
6
6
  "type": "module",
@@ -160,6 +160,7 @@
160
160
  "jszip": "^3.10.1",
161
161
  "livekit-server-sdk": "^2.13.1",
162
162
  "llm-code-format": "^3.0.0",
163
+ "lucide-react": "^0.539.0",
163
164
  "npm": "^11.5.2",
164
165
  "open": "^10.2.0",
165
166
  "prettier-plugin-svelte": "^3.4.0",
@@ -0,0 +1,35 @@
1
+ export const MicOffSVG = () => (
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
+ enableBackground="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
+ {/* Diagonal line to indicate "off" state */}
23
+ <line
24
+ x1="100"
25
+ y1="100"
26
+ x2="412"
27
+ y2="412"
28
+ stroke="currentColor"
29
+ strokeWidth="40"
30
+ strokeLinecap="round"
31
+ />
32
+ </g>
33
+ </g>
34
+ </svg>
35
+ );
@@ -21,4 +21,6 @@ export { HtmlSVG } from './HtmlSVG';
21
21
  export { CssSVG } from './CssSVG';
22
22
  export { JsonSVG } from './JsonSVG';
23
23
  export { MarkdownSVG } from './MarkdownSVG';
24
+ export { MicSVG } from './MicSVG';
25
+ export { MicOffSVG } from './MicOffSVG';
24
26
  export { AdjustmentSVG } from './AdjustmentSVG';
@@ -41,6 +41,7 @@ export type VZCodeContextValue = {
41
41
  directoryName: string,
42
42
  ) => void;
43
43
  deleteDirectory: (directoryId: string) => void;
44
+ deleteAllFiles: () => void;
44
45
 
45
46
  setActiveFileId: (fileId: string | null) => void;
46
47
  setActiveFileLeft: () => void;
@@ -184,6 +184,7 @@ export const useVZCodeState = ({
184
184
  createDirectory,
185
185
  renameDirectory,
186
186
  deleteDirectory,
187
+ deleteAllFiles,
187
188
  } = useFileCRUD({
188
189
  submitOperation,
189
190
  closeTabs,
@@ -461,6 +462,7 @@ export const useVZCodeState = ({
461
462
  createDirectory,
462
463
  renameDirectory,
463
464
  deleteDirectory,
465
+ deleteAllFiles,
464
466
 
465
467
  setActiveFileId,
466
468
  setActiveFileLeft,
@@ -11,6 +11,8 @@ import {
11
11
  ToggleButton,
12
12
  } from '../../bootstrap';
13
13
  import { enableAskMode } from '../../featureFlags';
14
+ import { useSpeechRecognition } from './useSpeechRecognition';
15
+ import { MicSVG, MicOffSVG } from '../../Icons';
14
16
 
15
17
  interface ChatInputProps {
16
18
  aiChatMessage: string;
@@ -39,6 +41,13 @@ const ChatInputComponent = ({
39
41
  }: ChatInputProps) => {
40
42
  const inputRef = useRef<HTMLTextAreaElement>(null);
41
43
 
44
+ // Use the speech recognition hook
45
+ const {
46
+ isSpeaking,
47
+ toggleSpeechRecognition,
48
+ stopSpeaking,
49
+ } = useSpeechRecognition(setAIChatMessage);
50
+
42
51
  useEffect(() => {
43
52
  // Focus the input when the AI chat is focused
44
53
  if (focused && inputRef.current) {
@@ -92,8 +101,12 @@ const ChatInputComponent = ({
92
101
  );
93
102
 
94
103
  const handleSendClick = useCallback(() => {
104
+ // If speech recognition is active, stop it
105
+ if (isSpeaking) {
106
+ stopSpeaking();
107
+ }
95
108
  onSendMessage();
96
- }, [onSendMessage]);
109
+ }, [onSendMessage, isSpeaking, stopSpeaking]);
97
110
 
98
111
  const handleChange = useCallback(
99
112
  (event: React.ChangeEvent<HTMLTextAreaElement>) => {
@@ -173,20 +186,50 @@ const ChatInputComponent = ({
173
186
  <span className="ai-chat-hint">
174
187
  {aiChatMessage ? 'Press Enter to send' : ''}
175
188
  </span>
176
- <Button
177
- variant={
178
- aiChatMessage.trim()
179
- ? 'primary'
180
- : 'outline-secondary'
181
- }
182
- onClick={handleSendClick}
183
- disabled={!aiChatMessage.trim() || isLoading}
184
- className="ai-chat-send-button"
185
- aria-label="Send message"
186
- title="Send message (Enter)"
187
- >
188
- Send
189
- </Button>
189
+ <div style={{ display: 'flex', gap: '8px' }}>
190
+ <Button
191
+ variant={
192
+ isSpeaking ? 'danger' : 'outline-secondary'
193
+ }
194
+ size="sm"
195
+ onClick={toggleSpeechRecognition}
196
+ disabled={isLoading}
197
+ aria-label={
198
+ isSpeaking
199
+ ? 'Stop voice typing'
200
+ : 'Start voice typing'
201
+ }
202
+ title={
203
+ isSpeaking
204
+ ? 'Stop voice typing'
205
+ : 'Start voice typing'
206
+ }
207
+ style={{
208
+ borderColor: isSpeaking
209
+ ? undefined
210
+ : '#dee2e6',
211
+ borderRadius: '0.375rem',
212
+ borderWidth: '1px',
213
+ borderStyle: 'solid',
214
+ }}
215
+ >
216
+ {isSpeaking ? <MicOffSVG /> : <MicSVG />}
217
+ </Button>
218
+ <Button
219
+ variant={
220
+ aiChatMessage.trim()
221
+ ? 'primary'
222
+ : 'outline-secondary'
223
+ }
224
+ onClick={handleSendClick}
225
+ disabled={!aiChatMessage.trim() || isLoading}
226
+ className="ai-chat-send-button"
227
+ aria-label="Send message"
228
+ title="Send message (Enter)"
229
+ >
230
+ Send
231
+ </Button>
232
+ </div>
190
233
  </div>
191
234
  </Form.Group>
192
235
  </div>
@@ -107,6 +107,29 @@ const MessageListComponent = ({
107
107
  scrollToBottom,
108
108
  ]);
109
109
 
110
+ // Track previous loading state to detect when AI generation completes
111
+ const [prevIsLoading, setPrevIsLoading] =
112
+ useState(isLoading);
113
+
114
+ // Scroll to bottom when AI generation completes (loading changes from true to false)
115
+ useEffect(() => {
116
+ // If AI was generating and now it's finished, scroll to bottom
117
+ if (prevIsLoading && !isLoading) {
118
+ // Force scroll to bottom regardless of user scroll state
119
+ // This ensures we always scroll to bottom when generation completes
120
+ messagesEndRef.current?.scrollIntoView({
121
+ behavior: 'smooth',
122
+ block: 'end',
123
+ });
124
+
125
+ // Re-enable auto-scroll for future messages
126
+ setIsUserScrolled(false);
127
+ setAutoScrollEnabled(true);
128
+ }
129
+
130
+ setPrevIsLoading(isLoading);
131
+ }, [isLoading, prevIsLoading]);
132
+
110
133
  // Clean up timeout on unmount
111
134
  useEffect(() => {
112
135
  return () => {
@@ -0,0 +1,117 @@
1
+ import {
2
+ useState,
3
+ useEffect,
4
+ useCallback,
5
+ Dispatch,
6
+ SetStateAction,
7
+ } from 'react';
8
+
9
+ export interface UseSpeechRecognitionResult {
10
+ isSpeaking: boolean;
11
+ toggleSpeechRecognition: () => void;
12
+ stopSpeaking: () => void;
13
+ }
14
+
15
+ export const useSpeechRecognition = (
16
+ onTranscriptChange: Dispatch<SetStateAction<string>>,
17
+ ): UseSpeechRecognitionResult => {
18
+ const [isSpeaking, setIsSpeaking] =
19
+ useState<boolean>(false);
20
+ const [recognition, setRecognition] =
21
+ // @ts-ignore
22
+ useState<SpeechRecognition | null>(null);
23
+ const [finalTranscript, setFinalTranscript] =
24
+ useState<string>('');
25
+
26
+ // Speech recognition setup
27
+ useEffect(() => {
28
+ // Check if browser supports SpeechRecognition
29
+ const SpeechRecognition =
30
+ // @ts-ignore
31
+ window.SpeechRecognition ||
32
+ // @ts-ignore
33
+ window.webkitSpeechRecognition;
34
+
35
+ if (SpeechRecognition) {
36
+ const recognitionInstance = new SpeechRecognition();
37
+ recognitionInstance.continuous = true;
38
+ recognitionInstance.interimResults = true;
39
+
40
+ recognitionInstance.onresult = (event) => {
41
+ let finalText = '';
42
+ let interimText = '';
43
+
44
+ // Process all results to separate final and interim text
45
+ for (let i = 0; i < event.results.length; i++) {
46
+ const result = event.results[i];
47
+ if (result.isFinal) {
48
+ finalText += result[0].transcript;
49
+ } else {
50
+ interimText += result[0].transcript;
51
+ }
52
+ }
53
+
54
+ // Update our final transcript state
55
+ setFinalTranscript(finalText);
56
+
57
+ // Combine final and interim text and update the prompt
58
+ const fullTranscript = finalText + interimText;
59
+ onTranscriptChange(fullTranscript);
60
+ };
61
+
62
+ recognitionInstance.onerror = (event) => {
63
+ console.error(
64
+ 'Speech recognition error',
65
+ event.error,
66
+ );
67
+ setIsSpeaking(false);
68
+ };
69
+
70
+ recognitionInstance.onend = () => {
71
+ setIsSpeaking(false);
72
+ // Reset final transcript when recognition ends
73
+ setFinalTranscript('');
74
+ };
75
+
76
+ setRecognition(recognitionInstance);
77
+
78
+ return () => {
79
+ recognitionInstance.abort();
80
+ };
81
+ }
82
+ }, [onTranscriptChange]);
83
+
84
+ const toggleSpeechRecognition = useCallback(() => {
85
+ if (!recognition) {
86
+ console.error(
87
+ 'Speech recognition not supported in this browser',
88
+ );
89
+ return;
90
+ }
91
+
92
+ if (isSpeaking) {
93
+ recognition.stop();
94
+ setIsSpeaking(false);
95
+ } else {
96
+ // Reset final transcript when starting new recognition
97
+ setFinalTranscript('');
98
+ recognition.start();
99
+ setIsSpeaking(true);
100
+ }
101
+ }, [isSpeaking, recognition]);
102
+
103
+ const stopSpeaking = useCallback(() => {
104
+ if (recognition && isSpeaking) {
105
+ recognition.stop();
106
+ setIsSpeaking(false);
107
+ // Reset final transcript when manually stopping
108
+ setFinalTranscript('');
109
+ }
110
+ }, [isSpeaking, recognition]);
111
+
112
+ return {
113
+ isSpeaking,
114
+ toggleSpeechRecognition,
115
+ stopSpeaking,
116
+ };
117
+ };
@@ -36,15 +36,26 @@ export const DeleteConfirmationModal = ({
36
36
  <Modal show={show} onHide={onClose} centered>
37
37
  <Modal.Header closeButton onClick={onClose}>
38
38
  <Modal.Title>
39
- Delete {isDirectory ? 'Directory' : 'File'}
39
+ Delete{' '}
40
+ {name === 'all files'
41
+ ? 'All Files'
42
+ : isDirectory
43
+ ? 'Directory'
44
+ : 'File'}
40
45
  </Modal.Title>
41
46
  </Modal.Header>
42
47
 
43
48
  <Modal.Body>
44
49
  <p>
45
- This will delete the{' '}
46
- {isDirectory ? 'directory' : 'file'}{' '}
47
- <strong>{name}</strong>.
50
+ {name === 'all files' ? (
51
+ 'This will delete all files in the project.'
52
+ ) : (
53
+ <>
54
+ This will delete the{' '}
55
+ {isDirectory ? 'directory' : 'file'}{' '}
56
+ <strong>{name}</strong>.
57
+ </>
58
+ )}
48
59
  </p>
49
60
  </Modal.Body>
50
61