Skip to content

Commit 61aa51f

Browse files
linxiaodongbuxuku
authored andcommitted
feat: show progress when downloading whisper and models
1 parent 2e587c1 commit 61aa51f

File tree

10 files changed

+228
-146
lines changed

10 files changed

+228
-146
lines changed

main/background.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import serve from "electron-serve";
55
import { createWindow } from "./helpers";
66
import {
77
install,
8-
downModel,
98
makeWhisper,
109
checkWhisperInstalled,
1110
getModelsInstalled,
@@ -183,9 +182,6 @@ ipcMain.on("makeWhisper", (event) => {
183182
makeWhisper(event);
184183
});
185184

186-
ipcMain.on("downModel", (event, { model, source }) => {
187-
downModel(event, model, source);
188-
});
189185

190186
ipcMain.on("openUrl", (event, url) => {
191187
shell.openExternal(url);

main/helpers/whisper.ts

Lines changed: 11 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,11 @@ export const install = (event, source) => {
6161
url: repoUrl,
6262
singleBranch: true,
6363
depth: 1,
64+
onProgress: (res) => {
65+
if (res.total) {
66+
event.sender.send("installWhisperProgress", res.phase, res.loaded / res.total);
67+
}
68+
},
6469
})
6570
.then((res) => {
6671
if (checkWhisperInstalled()) {
@@ -80,50 +85,6 @@ export const install = (event, source) => {
8085
});
8186
};
8287

83-
export const downModel = async (
84-
event,
85-
whisperModel,
86-
source = "hf-mirror.com",
87-
) => {
88-
const { modelsPath } = getPath();
89-
const modelName = whisperModel?.toLowerCase();
90-
const modelPath = path.join(modelsPath, `ggml-${modelName}.bin`);
91-
if (fs.existsSync(modelPath)) return;
92-
if (!checkWhisperInstalled()) {
93-
event.sender.send("message", "whisper.cpp 未下载,请先下载 whisper.cpp");
94-
}
95-
try {
96-
let downShellPath;
97-
let shell: string;
98-
if (isDarwin()) {
99-
downShellPath = path.join(modelsPath, "download-ggml-model.sh");
100-
shell = "bash";
101-
} else if (isWin32()) {
102-
downShellPath = path.join(modelsPath, "download-ggml-model.cmd");
103-
shell = "cmd.exe /c";
104-
} else {
105-
throw Error("platform does not support! ");
106-
}
107-
await replaceModelSource(`${downShellPath}`, source);
108-
console.log("完成模型下载地址替换", modelName);
109-
console.log("正在安装 whisper.cpp 模型");
110-
exec(`${shell} "${downShellPath}" ${modelName}`, (err, stdout) => {
111-
if (err) {
112-
event.sender.send("message", err);
113-
} else {
114-
event.sender.send("message", `模型 ${modelName} 下载完成`);
115-
}
116-
event.sender.send("downModelComplete", !err);
117-
event.sender.send("getSystemInfoComplete", {
118-
whisperInstalled: checkWhisperInstalled(),
119-
modelsInstalled: getModelsInstalled(),
120-
});
121-
});
122-
} catch (error) {
123-
event.sender.send("message", error);
124-
}
125-
};
126-
12788
export const makeWhisper = (event) => {
12889
const { whisperPath, mainPath } = getPath();
12990
if (fs.existsSync(mainPath) || isWin32()) {
@@ -133,6 +94,7 @@ export const makeWhisper = (event) => {
13394
if (!checkWhisperInstalled()) {
13495
event.sender.send("message", "whisper.cpp 未下载,请先下载 whisper.cpp");
13596
}
97+
event.sender.send("beginMakeWhisper", true);
13698
exec(`make -C "${whisperPath}"`, (err, stdout) => {
13799
if (err) {
138100
event.sender.send("message", err);
@@ -168,22 +130,23 @@ export const downloadModelSync = async (model, source, onProcess) => {
168130
try {
169131
let downShellPath;
170132
let shell: string;
133+
let args = [];
171134
if (isDarwin()) {
172135
downShellPath = path.join(modelsPath, "download-ggml-model.sh");
173136
shell = "bash";
137+
args = [`${downShellPath}`, `${model}`];
174138
} else if (isWin32()) {
175139
downShellPath = path.join(modelsPath, "download-ggml-model.cmd");
176-
shell = "cmd.exe /c";
140+
shell = "cmd";
141+
args = [`/c`, `${downShellPath}`, `${model}`];
177142
} else {
178143
throw Error("platform does not support! ");
179144
}
180145
await replaceModelSource(`${downShellPath}`, source);
181146
console.log("完成模型下载地址替换", model);
182147
console.log("正在安装 whisper.cpp 模型");
183148
try {
184-
await runCommand(`${shell}`, [`${downShellPath}`, `${model}`], (data) =>
185-
onProcess(data),
186-
);
149+
await runCommand(`${shell}`, args, (data) => onProcess(data));
187150
} catch (error) {
188151
await deleteModel(model);
189152
throw error;

renderer/components/DownModel.tsx

Lines changed: 26 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,48 @@
1-
import React, { useEffect } from "react";
2-
import { Button } from "@/components/ui/button";
3-
import { Loader2 } from "lucide-react";
1+
import React, { useEffect, FC, PropsWithChildren } from "react";
42

5-
const DownModel = ({ modelName, callBack, downSource }) => {
3+
interface IProps extends PropsWithChildren {
4+
modelName: string;
5+
callBack?: () => void;
6+
downSource?: string;
7+
}
8+
9+
const DownModel: FC<IProps> = (props) => {
10+
const { modelName, callBack, downSource = "hf-mirror", children } = props;
611
const [loading, setLoading] = React.useState(false);
712
const [progress, setProgress] = React.useState(0);
813
useEffect(() => {
914
window?.ipc?.on("downloadProgress", (model, progress: number) => {
1015
if (model === modelName) {
1116
setProgress(progress);
1217
setLoading(progress < 100);
13-
if(progress >= 100) {
14-
console.log(progress, 'progress')
15-
callBack && callBack();
18+
if (progress >= 100) {
19+
callBack && callBack();
1620
}
1721
}
1822
});
1923
}, []);
20-
const handleDownModel = async () => {
24+
const handleDownModel = async (source = downSource) => {
2125
setLoading(true);
2226
await window?.ipc?.invoke("downloadModel", {
2327
model: modelName,
24-
source: downSource,
28+
source,
2529
});
2630
setLoading(false);
2731
};
2832
return (
29-
<Button onClick={handleDownModel} disabled={loading}>
30-
{loading ? (
31-
<>
32-
<Loader2 className="mr-2 h-4 w-4 animate-spin" /> {progress} %
33-
</>
34-
) : (
35-
"下载"
36-
)}
37-
</Button>
33+
<span className="inline-block">
34+
{React.isValidElement<{
35+
loading?: boolean;
36+
progress?: number;
37+
handleDownModel?: (source?: string) => void;
38+
}>(children)
39+
? React.cloneElement(children, {
40+
loading,
41+
progress,
42+
handleDownModel,
43+
})
44+
: children}
45+
</span>
3846
);
3947
};
4048

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import React, { FC } from "react";
2+
import { Button } from "@/components/ui/button";
3+
import { Loader2 } from "lucide-react";
4+
5+
interface IProps {
6+
loading?: boolean;
7+
progress?: number;
8+
handleDownModel?: () => void;
9+
}
10+
11+
const DownModelButton: FC<IProps> = ({
12+
loading,
13+
progress,
14+
handleDownModel,
15+
}) => {
16+
return (
17+
<Button disabled={loading} onClick={() => handleDownModel()}>
18+
{loading ? (
19+
<>
20+
<Loader2 className="mr-2 h-4 w-4 animate-spin" /> {progress} %
21+
</>
22+
) : (
23+
"下载"
24+
)}
25+
</Button>
26+
);
27+
};
28+
29+
export default DownModelButton;
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import React, { FC } from "react";
2+
import {
3+
DropdownMenu,
4+
DropdownMenuContent,
5+
DropdownMenuItem,
6+
DropdownMenuLabel,
7+
DropdownMenuSeparator,
8+
DropdownMenuTrigger,
9+
} from "@/components/ui/dropdown-menu";
10+
import { Button } from "@/components/ui/button";
11+
import { Loader2 } from "lucide-react";
12+
13+
interface IProps {
14+
loading?: boolean;
15+
progress?: number;
16+
handleDownModel?: (source: string) => void;
17+
setShowGuide?: (type: boolean) => void;
18+
installComplete?: boolean;
19+
whisperLoading?: boolean;
20+
}
21+
22+
const DownModelDropdown: FC<IProps> = ({
23+
loading,
24+
progress,
25+
handleDownModel,
26+
setShowGuide,
27+
installComplete,
28+
whisperLoading,
29+
}) => {
30+
return (
31+
<DropdownMenu>
32+
<DropdownMenuTrigger asChild>
33+
<Button
34+
variant="outline"
35+
disabled={loading || !installComplete || whisperLoading}
36+
className="w-24"
37+
>
38+
{loading && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
39+
{loading ? `${progress}%` : "下载"}
40+
</Button>
41+
</DropdownMenuTrigger>
42+
<DropdownMenuContent className="w-[225px]">
43+
<DropdownMenuLabel>请选择下载源</DropdownMenuLabel>
44+
<DropdownMenuSeparator />
45+
<DropdownMenuItem
46+
className="cursor-pointer hover:bg-gray-100"
47+
onClick={() => handleDownModel("hf-mirror")}
48+
>
49+
国内镜像源(较快)
50+
</DropdownMenuItem>
51+
<DropdownMenuItem
52+
className="cursor-pointer hover:bg-gray-100"
53+
onClick={() => handleDownModel("huggingface")}
54+
>
55+
huggingface官方源(较慢)
56+
</DropdownMenuItem>
57+
<DropdownMenuSeparator />
58+
<DropdownMenuItem
59+
className="cursor-pointer hover:bg-gray-100"
60+
onClick={() => setShowGuide(false)}
61+
>
62+
稍后下载
63+
</DropdownMenuItem>
64+
</DropdownMenuContent>
65+
</DropdownMenu>
66+
);
67+
};
68+
69+
export default DownModelDropdown;
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import React, { FC } from "react";
2+
3+
interface IProps {
4+
loading?: boolean;
5+
progress?: number;
6+
handleDownModel?: () => void;
7+
}
8+
const DownModelLink: FC<IProps> = ({ loading, progress, handleDownModel }) => {
9+
return (
10+
<span className="inline-block">
11+
该模型未下载,
12+
{loading ? (
13+
`正在下载中 ${progress}%...`
14+
) : (
15+
<a
16+
className="cursor-pointer text-blue-500"
17+
onClick={() => handleDownModel()}
18+
>
19+
立即下载
20+
</a>
21+
)}
22+
</span>
23+
);
24+
};
25+
26+
export default DownModelLink;

0 commit comments

Comments
 (0)