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/assets/{index-Br76WttW.css → index-BM8tDh6M.css} +1 -1
- package/dist/assets/{index-vbF7Aejt.js → index-CpxqGhXj.js} +163 -163
- package/dist/index.html +2 -2
- package/package.json +2 -1
- package/src/client/Icons/MicOffSVG.tsx +35 -0
- package/src/client/Icons/index.tsx +2 -0
- package/src/client/VZCodeContext/types.ts +1 -0
- package/src/client/VZCodeContext/useVZCodeState.ts +2 -0
- package/src/client/VZSidebar/AIChat/ChatInput.tsx +58 -15
- package/src/client/VZSidebar/AIChat/MessageList.tsx +23 -0
- package/src/client/VZSidebar/AIChat/useSpeechRecognition.ts +117 -0
- package/src/client/VZSidebar/DeleteConfirmationModal.tsx +15 -4
- package/src/client/VZSidebar/VisualEditor.tsx +120 -133
- package/src/client/VZSidebar/aiCopyPaste.ts +1 -1
- package/src/client/VZSidebar/index.tsx +42 -0
- package/src/client/VZSidebar/styles.scss +15 -0
- package/src/client/useFileCRUD.ts +14 -0
- package/src/server/aiChatHandler/aiEditing.ts +50 -1
- package/src/server/prettier.ts +115 -0
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-
|
|
24
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
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": "
|
|
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';
|
|
@@ -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
|
-
<
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
? '
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
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
|
|
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
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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
|
|