vzcode 2.29.0 → 2.30.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/bindings_wasm_bg-9w8E-TmY.wasm +0 -0
- package/dist/assets/buildWorker-DylWm2VX.js +687 -0
- package/dist/assets/index-CD0x02Rq.js +364 -0
- package/dist/assets/index-Dyt9tpmu.css +1 -0
- package/dist/assets/worker-D8p1JlHW.js +342 -0
- package/dist/assets/worker-b9bkZkXK.js +116 -0
- package/dist/assets/{worker-CoJaqtpi.js → worker-k7HZ3t-V.js} +12 -12
- package/dist/index.html +2 -2
- package/package.json +1 -1
- package/src/client/App/index.tsx +38 -24
- package/src/client/featureFlags.ts +8 -0
- package/src/llm-streaming-ui/components/ChatInput.tsx +49 -29
- package/src/llm-streaming-ui/components/useSpeechRecognition.ts +84 -60
- package/src/vite-env.d.ts +1 -0
- package/dist/assets/bindings_wasm_bg--2fP_mkJ.wasm +0 -0
- package/dist/assets/buildWorker-BV4WiKg8.js +0 -687
- package/dist/assets/index-CCmGeE_1.css +0 -1
- package/dist/assets/index-JcHUw7hN.js +0 -403
- package/dist/assets/worker-AbbeoMbH.js +0 -340
- package/dist/assets/worker-B71kfwKL.js +0 -125
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-CD0x02Rq.js"></script>
|
|
24
|
+
<link rel="stylesheet" crossorigin href="/assets/index-Dyt9tpmu.css">
|
|
25
25
|
</head>
|
|
26
26
|
<body>
|
|
27
27
|
<div id="root"></div>
|
package/package.json
CHANGED
package/src/client/App/index.tsx
CHANGED
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
import './style.scss';
|
|
27
27
|
import { useShareDB } from './useShareDB';
|
|
28
28
|
import { useESLint } from '../useESLint';
|
|
29
|
+
import { enableLiveKit } from '../featureFlags';
|
|
29
30
|
|
|
30
31
|
// Instantiate the Prettier worker.
|
|
31
32
|
const prettierWorker = new PrettierWorker();
|
|
@@ -99,6 +100,29 @@ function App() {
|
|
|
99
100
|
// Clicking on it accepts it but should not.
|
|
100
101
|
const enableCopilot = false;
|
|
101
102
|
|
|
103
|
+
// The editor UI itself. Rendered with or without the LiveKit
|
|
104
|
+
// provider around it, so that LiveKit (and its audio capture) is
|
|
105
|
+
// only mounted when explicitly enabled.
|
|
106
|
+
const appContent = (
|
|
107
|
+
<>
|
|
108
|
+
<div className="app">
|
|
109
|
+
<VZLeft />
|
|
110
|
+
<VZMiddle
|
|
111
|
+
aiCopilotEndpoint={
|
|
112
|
+
enableCopilot ? '/ai-copilot' : null
|
|
113
|
+
}
|
|
114
|
+
esLintSource={esLintSource}
|
|
115
|
+
/>
|
|
116
|
+
{enableRightPanel ? <VZRight /> : null}
|
|
117
|
+
<VZResizer side="left" />
|
|
118
|
+
{enableRightPanel ? (
|
|
119
|
+
<VZResizer side="right" />
|
|
120
|
+
) : null}
|
|
121
|
+
</div>
|
|
122
|
+
<PersistUsername />
|
|
123
|
+
</>
|
|
124
|
+
);
|
|
125
|
+
|
|
102
126
|
return (
|
|
103
127
|
<SplitPaneResizeProvider>
|
|
104
128
|
<VZCodeProvider
|
|
@@ -120,30 +144,20 @@ function App() {
|
|
|
120
144
|
aiChatEndpoint="/ai-chat-message"
|
|
121
145
|
aiChatOptions={{}}
|
|
122
146
|
>
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
<
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
/>
|
|
138
|
-
{enableRightPanel ? <VZRight /> : null}
|
|
139
|
-
<VZResizer side="left" />
|
|
140
|
-
{enableRightPanel ? (
|
|
141
|
-
<VZResizer side="right" />
|
|
142
|
-
) : null}
|
|
143
|
-
</div>
|
|
144
|
-
<PersistUsername />
|
|
145
|
-
<RoomAudioRenderer />
|
|
146
|
-
</LiveKitRoom>
|
|
147
|
+
{enableLiveKit ? (
|
|
148
|
+
<LiveKitRoom
|
|
149
|
+
audio={true}
|
|
150
|
+
token={liveKitToken}
|
|
151
|
+
serverUrl={serverUrl}
|
|
152
|
+
connect={liveKitConnection}
|
|
153
|
+
style={{ height: '100%' }}
|
|
154
|
+
>
|
|
155
|
+
{appContent}
|
|
156
|
+
<RoomAudioRenderer />
|
|
157
|
+
</LiveKitRoom>
|
|
158
|
+
) : (
|
|
159
|
+
appContent
|
|
160
|
+
)}
|
|
147
161
|
</VZCodeProvider>
|
|
148
162
|
</SplitPaneResizeProvider>
|
|
149
163
|
);
|
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
export const enableLiveKit =
|
|
2
2
|
import.meta.env.VITE_ENABLE_LIVEKIT === 'true';
|
|
3
3
|
|
|
4
|
+
// Enables the speech-to-text microphone button in the AI chat
|
|
5
|
+
// input. Off by default: speech recognition is a browser media
|
|
6
|
+
// feature, and merely constructing a `SpeechRecognition` instance
|
|
7
|
+
// can make the browser show a microphone permission prompt. Set
|
|
8
|
+
// `VITE_ENABLE_VOICE_INPUT=true` to opt in.
|
|
9
|
+
export const enableVoiceInput =
|
|
10
|
+
import.meta.env.VITE_ENABLE_VOICE_INPUT === 'true';
|
|
11
|
+
|
|
4
12
|
export const enableAIChat = true;
|
|
5
13
|
|
|
6
14
|
export const enableDiffView = true;
|
|
@@ -10,10 +10,50 @@ import {
|
|
|
10
10
|
ButtonGroup,
|
|
11
11
|
ToggleButton,
|
|
12
12
|
} from '../../client/bootstrap';
|
|
13
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
enableAskMode,
|
|
15
|
+
enableVoiceInput,
|
|
16
|
+
} from '../../client/featureFlags';
|
|
14
17
|
import { useSpeechRecognition } from './useSpeechRecognition';
|
|
15
18
|
import { MicSVG, MicOffSVG } from '../../client/Icons';
|
|
16
19
|
|
|
20
|
+
/**
|
|
21
|
+
* Microphone toggle for voice typing. Purely presentational: the
|
|
22
|
+
* speech recognition state lives in `ChatInputComponent`, and is only
|
|
23
|
+
* ever instantiated when the user actually toggles the button.
|
|
24
|
+
*
|
|
25
|
+
* Rendered only when `enableVoiceInput` is set. See `featureFlags`.
|
|
26
|
+
*/
|
|
27
|
+
const VoiceInputButton = ({
|
|
28
|
+
isSpeaking,
|
|
29
|
+
onToggle,
|
|
30
|
+
}: {
|
|
31
|
+
isSpeaking: boolean;
|
|
32
|
+
onToggle: () => void;
|
|
33
|
+
}) => {
|
|
34
|
+
const label = isSpeaking
|
|
35
|
+
? 'Stop voice typing'
|
|
36
|
+
: 'Start voice typing';
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<Button
|
|
40
|
+
variant={isSpeaking ? 'danger' : 'outline-secondary'}
|
|
41
|
+
size="sm"
|
|
42
|
+
onClick={onToggle}
|
|
43
|
+
aria-label={label}
|
|
44
|
+
title={label}
|
|
45
|
+
style={{
|
|
46
|
+
borderColor: isSpeaking ? undefined : '#dee2e6',
|
|
47
|
+
borderRadius: '0.375rem',
|
|
48
|
+
borderWidth: '1px',
|
|
49
|
+
borderStyle: 'solid',
|
|
50
|
+
}}
|
|
51
|
+
>
|
|
52
|
+
{isSpeaking ? <MicOffSVG /> : <MicSVG />}
|
|
53
|
+
</Button>
|
|
54
|
+
);
|
|
55
|
+
};
|
|
56
|
+
|
|
17
57
|
interface ChatInputProps {
|
|
18
58
|
aiChatMessage: string;
|
|
19
59
|
setAIChatMessage: (message: string) => void;
|
|
@@ -39,7 +79,8 @@ const ChatInputComponent = ({
|
|
|
39
79
|
}: ChatInputProps) => {
|
|
40
80
|
const inputRef = useRef<HTMLTextAreaElement>(null);
|
|
41
81
|
|
|
42
|
-
//
|
|
82
|
+
// Speech recognition is created lazily on first toggle, so simply
|
|
83
|
+
// mounting this hook does not touch any browser media APIs.
|
|
43
84
|
const {
|
|
44
85
|
isSpeaking,
|
|
45
86
|
toggleSpeechRecognition,
|
|
@@ -186,33 +227,12 @@ const ChatInputComponent = ({
|
|
|
186
227
|
{aiChatMessage ? 'Press Enter to send' : ''}
|
|
187
228
|
</span>
|
|
188
229
|
<div style={{ display: 'flex', gap: '8px' }}>
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
isSpeaking
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
aria-label={
|
|
196
|
-
isSpeaking
|
|
197
|
-
? 'Stop voice typing'
|
|
198
|
-
: 'Start voice typing'
|
|
199
|
-
}
|
|
200
|
-
title={
|
|
201
|
-
isSpeaking
|
|
202
|
-
? 'Stop voice typing'
|
|
203
|
-
: 'Start voice typing'
|
|
204
|
-
}
|
|
205
|
-
style={{
|
|
206
|
-
borderColor: isSpeaking
|
|
207
|
-
? undefined
|
|
208
|
-
: '#dee2e6',
|
|
209
|
-
borderRadius: '0.375rem',
|
|
210
|
-
borderWidth: '1px',
|
|
211
|
-
borderStyle: 'solid',
|
|
212
|
-
}}
|
|
213
|
-
>
|
|
214
|
-
{isSpeaking ? <MicOffSVG /> : <MicSVG />}
|
|
215
|
-
</Button>
|
|
230
|
+
{enableVoiceInput && (
|
|
231
|
+
<VoiceInputButton
|
|
232
|
+
isSpeaking={isSpeaking}
|
|
233
|
+
onToggle={toggleSpeechRecognition}
|
|
234
|
+
/>
|
|
235
|
+
)}
|
|
216
236
|
<Button
|
|
217
237
|
variant={
|
|
218
238
|
aiChatMessage.trim()
|
|
@@ -2,6 +2,7 @@ import {
|
|
|
2
2
|
useState,
|
|
3
3
|
useEffect,
|
|
4
4
|
useCallback,
|
|
5
|
+
useRef,
|
|
5
6
|
Dispatch,
|
|
6
7
|
SetStateAction,
|
|
7
8
|
} from 'react';
|
|
@@ -12,18 +13,41 @@ export interface UseSpeechRecognitionResult {
|
|
|
12
13
|
stopSpeaking: () => void;
|
|
13
14
|
}
|
|
14
15
|
|
|
16
|
+
/**
|
|
17
|
+
* Speech-to-text input for the AI chat box.
|
|
18
|
+
*
|
|
19
|
+
* The underlying `SpeechRecognition` instance is created lazily, on the
|
|
20
|
+
* first call to `toggleSpeechRecognition`, rather than eagerly when the
|
|
21
|
+
* hook mounts. Eager construction can make some browsers show a
|
|
22
|
+
* microphone permission prompt as soon as the editor loads, before the
|
|
23
|
+
* user has asked for voice input.
|
|
24
|
+
*/
|
|
15
25
|
export const useSpeechRecognition = (
|
|
16
26
|
onTranscriptChange: Dispatch<SetStateAction<string>>,
|
|
17
27
|
): UseSpeechRecognitionResult => {
|
|
18
28
|
const [isSpeaking, setIsSpeaking] =
|
|
19
29
|
useState<boolean>(false);
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
30
|
+
// `SpeechRecognition` is not part of the DOM lib, so the type
|
|
31
|
+
// reference is suppressed here, as with the constructor lookups below.
|
|
32
|
+
// @ts-ignore
|
|
33
|
+
const recognitionRef = useRef<SpeechRecognition | null>(
|
|
34
|
+
null,
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
// Keep the latest callback in a ref so the lazily-created
|
|
38
|
+
// recognition instance always calls the current handler, without
|
|
39
|
+
// having to tear down and recreate the instance.
|
|
40
|
+
const onTranscriptChangeRef = useRef(onTranscriptChange);
|
|
26
41
|
useEffect(() => {
|
|
42
|
+
onTranscriptChangeRef.current = onTranscriptChange;
|
|
43
|
+
}, [onTranscriptChange]);
|
|
44
|
+
|
|
45
|
+
// Create the SpeechRecognition instance on first use.
|
|
46
|
+
const getRecognition = useCallback(() => {
|
|
47
|
+
if (recognitionRef.current) {
|
|
48
|
+
return recognitionRef.current;
|
|
49
|
+
}
|
|
50
|
+
|
|
27
51
|
// Check if browser supports SpeechRecognition
|
|
28
52
|
const SpeechRecognition =
|
|
29
53
|
// @ts-ignore
|
|
@@ -31,56 +55,52 @@ export const useSpeechRecognition = (
|
|
|
31
55
|
// @ts-ignore
|
|
32
56
|
window.webkitSpeechRecognition;
|
|
33
57
|
|
|
34
|
-
if (SpeechRecognition) {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
58
|
+
if (!SpeechRecognition) {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const recognitionInstance = new SpeechRecognition();
|
|
63
|
+
recognitionInstance.continuous = true;
|
|
64
|
+
recognitionInstance.interimResults = true;
|
|
65
|
+
|
|
66
|
+
recognitionInstance.onresult = (event) => {
|
|
67
|
+
let finalText = '';
|
|
68
|
+
let interimText = '';
|
|
69
|
+
|
|
70
|
+
// Process all results to separate final and interim text
|
|
71
|
+
for (let i = 0; i < event.results.length; i++) {
|
|
72
|
+
const result = event.results[i];
|
|
73
|
+
if (result.isFinal) {
|
|
74
|
+
finalText += result[0].transcript;
|
|
75
|
+
} else {
|
|
76
|
+
interimText += result[0].transcript;
|
|
51
77
|
}
|
|
78
|
+
}
|
|
52
79
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
setRecognition(recognitionInstance);
|
|
76
|
-
|
|
77
|
-
return () => {
|
|
78
|
-
recognitionInstance.abort();
|
|
79
|
-
};
|
|
80
|
-
}
|
|
81
|
-
}, [onTranscriptChange]);
|
|
80
|
+
// Combine final and interim text and update the prompt
|
|
81
|
+
onTranscriptChangeRef.current(
|
|
82
|
+
finalText + interimText,
|
|
83
|
+
);
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
recognitionInstance.onerror = (event) => {
|
|
87
|
+
console.error(
|
|
88
|
+
'Speech recognition error',
|
|
89
|
+
event.error,
|
|
90
|
+
);
|
|
91
|
+
setIsSpeaking(false);
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
recognitionInstance.onend = () => {
|
|
95
|
+
setIsSpeaking(false);
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
recognitionRef.current = recognitionInstance;
|
|
99
|
+
return recognitionInstance;
|
|
100
|
+
}, []);
|
|
82
101
|
|
|
83
102
|
const toggleSpeechRecognition = useCallback(() => {
|
|
103
|
+
const recognition = getRecognition();
|
|
84
104
|
if (!recognition) {
|
|
85
105
|
console.error(
|
|
86
106
|
'Speech recognition not supported in this browser',
|
|
@@ -92,21 +112,25 @@ export const useSpeechRecognition = (
|
|
|
92
112
|
recognition.stop();
|
|
93
113
|
setIsSpeaking(false);
|
|
94
114
|
} else {
|
|
95
|
-
// Reset final transcript when starting new recognition
|
|
96
|
-
setFinalTranscript('');
|
|
97
115
|
recognition.start();
|
|
98
116
|
setIsSpeaking(true);
|
|
99
117
|
}
|
|
100
|
-
}, [
|
|
118
|
+
}, [getRecognition, isSpeaking]);
|
|
101
119
|
|
|
102
120
|
const stopSpeaking = useCallback(() => {
|
|
103
|
-
if (
|
|
104
|
-
|
|
121
|
+
if (recognitionRef.current && isSpeaking) {
|
|
122
|
+
recognitionRef.current.stop();
|
|
105
123
|
setIsSpeaking(false);
|
|
106
|
-
// Reset final transcript when manually stopping
|
|
107
|
-
setFinalTranscript('');
|
|
108
124
|
}
|
|
109
|
-
}, [isSpeaking
|
|
125
|
+
}, [isSpeaking]);
|
|
126
|
+
|
|
127
|
+
// Release the microphone if the component unmounts while listening.
|
|
128
|
+
useEffect(
|
|
129
|
+
() => () => {
|
|
130
|
+
recognitionRef.current?.abort();
|
|
131
|
+
},
|
|
132
|
+
[],
|
|
133
|
+
);
|
|
110
134
|
|
|
111
135
|
return {
|
|
112
136
|
isSpeaking,
|
package/src/vite-env.d.ts
CHANGED
|
Binary file
|