Skip to content

Commit f4b2f91

Browse files
committed
Change voice input loading/unloading time and update new models
1 parent cac4a92 commit f4b2f91

File tree

9 files changed

+244
-143
lines changed

9 files changed

+244
-143
lines changed

mobile/android/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ buildscript {
77
mavenCentral()
88
}
99
dependencies {
10-
classpath 'com.android.tools.build:gradle:8.2.1'
10+
classpath 'com.android.tools.build:gradle:8.7.3'
1111
classpath 'com.google.gms:google-services:4.4.0'
1212

1313
// NOTE: Do not place your application dependencies here; they belong

mobile/android/gradle/wrapper/gradle-wrapper.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
distributionBase=GRADLE_USER_HOME
22
distributionPath=wrapper/dists
3-
distributionUrl=https\://services.gradle.org/distributions/gradle-8.2.1-all.zip
3+
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-all.zip
44
networkTimeout=10000
55
validateDistributionUrl=true
66
zipStoreBase=GRADLE_USER_HOME

web/app/page.tsx

Lines changed: 4 additions & 118 deletions
Original file line numberDiff line numberDiff line change
@@ -1,134 +1,20 @@
11
"use client";
22

3-
import { useMicVAD, utils } from "@/lib/hooks/use-mic-vad";
4-
import { BaseLLM } from "@/lib/llm/llm";
5-
import { BaseSTT } from "@/lib/stt/stt";
6-
import { useContext, useEffect, useRef } from "react";
7-
import toast from "react-hot-toast";
8-
import { CodeEditorAgent } from "@/lib/agents/code-editor-agent";
9-
import { BaseTTS } from "@/lib/tts/tts";
103
import EditorToolbar from "@/components/interface/editor-toolbar";
114
import { EditorContext } from "@/components/providers/editor-context-provider";
5+
import Voice from "@/components/tools/voice";
126
import ViewDisplayArea from "@/components/views/file-view-display-area";
13-
import { useViewManager } from "@/lib/hooks/use-view-manager";
7+
import { useContext } from "react";
148

159
export default function Home() {
1610
const editorContext = useContext(EditorContext);
17-
const { updateFileView, activeFileView } = useViewManager();
18-
19-
// TODO: Use a timer to stop recorder if no speech is detected for more than 30 seconds
20-
const isProcessingRef = useRef(false);
21-
const vad = useMicVAD({
22-
startOnLoad: false,
23-
baseAssetPath: "/vad/",
24-
onnxWASMBasePath: "/vad/",
25-
positiveSpeechThreshold: 0.75,
26-
onSpeechStart: () => {
27-
if (!isProcessingRef.current) {
28-
const sttModel = editorContext?.aiModelConfig.getSTTModel();
29-
const llmModel = editorContext?.aiModelConfig.getLLMModel();
30-
const ttsModel = editorContext?.aiModelConfig.getTTSModel();
31-
let isConfigured = true;
32-
if (!sttModel) {
33-
toast.error("STT model is not configured in settings.");
34-
isConfigured = false;
35-
}
36-
if (!llmModel) {
37-
toast.error("LLM model is not configured in settings.");
38-
isConfigured = false;
39-
}
40-
if (!ttsModel) {
41-
toast.error("TTS model is not configured in settings.");
42-
isConfigured = false;
43-
}
44-
45-
if (!isConfigured) {
46-
return;
47-
}
48-
49-
editorContext?.setEditorStates((prev) => ({
50-
...prev,
51-
isListening: true,
52-
}));
53-
}
54-
},
55-
onSpeechEnd: (audio) => {
56-
if (!isProcessingRef.current) {
57-
isProcessingRef.current = true;
58-
const wavBuffer = utils.encodeWAV(audio);
59-
const blob = new Blob([wavBuffer], { type: "audio/wav" });
60-
console.log("Speech end\n", blob);
61-
62-
const sttModel = editorContext?.aiModelConfig.getSTTModel() as BaseSTT;
63-
const llmModel = editorContext?.aiModelConfig.getLLMModel() as BaseLLM;
64-
const ttsModel = editorContext?.aiModelConfig.getTTSModel() as BaseTTS;
65-
66-
const agent = new CodeEditorAgent(sttModel, llmModel, ttsModel);
67-
editorContext?.setEditorStates((prev) => ({
68-
...prev,
69-
isListening: false,
70-
isThinking: true,
71-
}));
72-
agent
73-
.generateAgentCompletion(
74-
activeFileView?.fileContent || "",
75-
activeFileView?.selections || [],
76-
{
77-
audio: blob,
78-
},
79-
)
80-
.then((result) => {
81-
const changes = agent.getLineChanges(result.text.codeCompletion);
82-
editorContext?.setEditorStates((prev) => ({
83-
...prev,
84-
isThinking: false,
85-
}));
86-
87-
// Apply changes
88-
updateFileView({
89-
filePath: activeFileView?.filePath || "",
90-
// suggestedLines: changes,
91-
fileContent: result.text.codeCompletion,
92-
isActive: true,
93-
});
94-
95-
// Play the audio in the blob
96-
if (result.audio) {
97-
const audio = new Audio(URL.createObjectURL(result.audio));
98-
audio.onended = () => {
99-
console.log("Audio ended");
100-
editorContext?.setEditorStates((prev) => ({
101-
...prev,
102-
isSpeaking: false,
103-
}));
104-
isProcessingRef.current = false;
105-
};
106-
editorContext?.setEditorStates((prev) => ({
107-
...prev,
108-
isSpeaking: true,
109-
}));
110-
audio.play();
111-
return;
112-
}
113-
isProcessingRef.current = false;
114-
});
115-
}
116-
},
117-
});
118-
119-
// Toggle recording
120-
useEffect(() => {
121-
if (editorContext?.editorStates?.isRecording) {
122-
vad.start();
123-
} else {
124-
vad.stop();
125-
}
126-
}, [editorContext?.editorStates, vad]);
12711

12812
return (
12913
<div className="flex h-full w-full flex-col">
13014
<EditorToolbar />
13115
<ViewDisplayArea />
16+
17+
{editorContext?.editorStates.isRecording && <Voice />}
13218
</div>
13319
);
13420
}

web/components/misc/icon.tsx

Lines changed: 37 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"use client";
22

33
import { useTheme } from "next-themes";
4+
import Image from "next/image";
45

56
export default function Icon({
67
name,
@@ -24,33 +25,55 @@ export default function Icon({
2425
}
2526
if (name && uri) {
2627
throw new Error(
27-
"Icon component requires either a name or a uri prop, not both."
28+
"Icon component requires either a name or a uri prop, not both.",
2829
);
2930
}
3031

3132
if (name) {
3233
return (
33-
<div className="flex justify-center items-center w-full h-full">
34-
<span
35-
className={
36-
`material-icons${variant ? "-" + variant : ""}` +
37-
(className ? " " + className : "")
38-
}
39-
>
40-
{name}
41-
</span>
42-
</div>
34+
<span
35+
className={
36+
`material-icons${variant ? "-" + variant : ""}` +
37+
(className ? " " + className : "")
38+
}
39+
>
40+
{name}
41+
</span>
4342
);
4443
}
4544

4645
if (!isThemed) {
4746
const iconUri = uri + extension;
48-
return <img src={iconUri} alt="icon" className={"h-6 w-6 " + className} />;
47+
return (
48+
<Image
49+
src={iconUri}
50+
alt="icon"
51+
width={24}
52+
height={24}
53+
className={className}
54+
/>
55+
);
4956
} else if (resolvedTheme === "dark") {
5057
const darkUri = uri + "-dark" + extension;
51-
return <img src={darkUri} alt="icon" className={"h-6 w-6 " + className} />;
58+
return (
59+
<Image
60+
src={darkUri}
61+
alt="icon"
62+
width={24}
63+
height={24}
64+
className={className}
65+
/>
66+
);
5267
}
5368

5469
const lightUri = uri + "-light" + extension;
55-
return <img src={lightUri} alt="icon" className={"h-6 w-6 " + className} />;
70+
return (
71+
<Image
72+
src={lightUri}
73+
alt="icon"
74+
width={24}
75+
height={24}
76+
className={className}
77+
/>
78+
);
5679
}

web/components/modals/extension-modal.tsx

Lines changed: 55 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ export default function ExtensionModal({
8181
description: ext.description ?? "No description available",
8282
displayName: ext.displayName ?? ext.name,
8383
},
84-
isEnabled: false,
84+
isEnabled: true,
8585
remoteOrigin: `https://cdn.pulse-editor.com/extension`,
8686
};
8787
});
@@ -227,14 +227,16 @@ function ExtensionPreview({
227227

228228
const editorContext = useContext(EditorContext);
229229

230+
const [isShowInfo, setIsShowInfo] = useState(false);
231+
230232
useEffect(() => {
231-
setIsEnabled(extension.isEnabled);
232233
setIsLoaded(true);
233-
setIsInstalled(
234-
editorContext?.persistSettings?.extensions?.some(
235-
(ext) => ext.config.id === extension.config.id,
236-
) ?? false,
234+
235+
const foundExt = editorContext?.persistSettings?.extensions?.find(
236+
(ext) => ext.config.id === extension.config.id,
237237
);
238+
setIsInstalled(foundExt !== undefined);
239+
setIsEnabled(foundExt?.isEnabled ?? false);
238240
}, [extension]);
239241

240242
function toggleExtension() {
@@ -255,7 +257,16 @@ function ExtensionPreview({
255257

256258
return (
257259
<div className="w-full">
258-
<div className="relative h-28 w-full">
260+
<div
261+
className="relative h-28 w-full"
262+
onMouseEnter={() => {
263+
setIsShowInfo(true);
264+
}}
265+
// Hide show info when user taps outside of the modal
266+
onMouseLeave={() => {
267+
setIsShowInfo(false);
268+
}}
269+
>
259270
<div className="absolute top-0 right-0.5 z-10">
260271
<div className="flex flex-col">
261272
{showInstalledChip && isInstalled && (
@@ -269,7 +280,7 @@ function ExtensionPreview({
269280
</div>
270281
</div>
271282
<Button
272-
className="m-0 h-full w-full rounded-md p-0"
283+
className="relative m-0 h-full w-full rounded-md p-0"
273284
onContextMenu={(e) => {
274285
e.preventDefault();
275286
// Get parent element position
@@ -284,6 +295,41 @@ function ExtensionPreview({
284295
}));
285296
}}
286297
></Button>
298+
{isShowInfo && (
299+
<div className="absolute bottom-0.5 left-1/2 flex w-full -translate-x-1/2 justify-center gap-x-0.5">
300+
<Button color="secondary" size="sm">
301+
Details
302+
</Button>
303+
{!isInstalled ? (
304+
<Button
305+
color="primary"
306+
size="sm"
307+
onPress={(e) => {
308+
installExtension(extension).then(() => {
309+
toast.success("Extension installed");
310+
setIsInstalled(true);
311+
setIsEnabled(extension.isEnabled);
312+
});
313+
}}
314+
>
315+
Install
316+
</Button>
317+
) : (
318+
<Button
319+
color="danger"
320+
size="sm"
321+
onPress={(e) => {
322+
uninstallExtension(extension.config.id).then(() => {
323+
toast.success("Extension uninstalled");
324+
setIsInstalled(false);
325+
});
326+
}}
327+
>
328+
Uninstall
329+
</Button>
330+
)}
331+
</div>
332+
)}
287333
<ContextMenu state={contextMenuState} setState={setContextMenuState}>
288334
<div className="flex flex-col">
289335
{isInstalled ? (
@@ -307,6 +353,7 @@ function ExtensionPreview({
307353
installExtension(extension).then(() => {
308354
toast.success("Extension installed");
309355
setIsInstalled(true);
356+
setIsEnabled(extension.isEnabled);
310357
});
311358
setContextMenuState({ x: 0, y: 0, isOpen: false });
312359
}}

0 commit comments

Comments
 (0)