From ede26d73d51dadf1d5f981f4d60168670184367d Mon Sep 17 00:00:00 2001 From: Lin Xiaodong Date: Fri, 14 Jun 2024 15:22:05 +0800 Subject: [PATCH] feat: add model control page (#11) Co-authored-by: linxiaodong --- electron-builder.yml | 17 +- main/background.ts | 68 ++- main/helpers/whisper.ts | 61 ++- main/preload.ts | 9 +- package.json | 3 +- renderer/components/DeleteModel.tsx | 44 ++ renderer/components/DownModel.tsx | 23 + renderer/components/Layout.tsx | 113 ++++ renderer/components/TaskStatus.tsx | 15 + renderer/components/ui/alert-dialog.tsx | 139 +++++ renderer/components/ui/card.tsx | 79 +++ renderer/pages/_app.tsx | 15 +- renderer/pages/home.tsx | 682 +++++++++++------------- renderer/pages/modelsControl.tsx | 110 ++++ yarn.lock | 15 +- 15 files changed, 1001 insertions(+), 392 deletions(-) create mode 100644 renderer/components/DeleteModel.tsx create mode 100644 renderer/components/DownModel.tsx create mode 100644 renderer/components/Layout.tsx create mode 100644 renderer/components/TaskStatus.tsx create mode 100644 renderer/components/ui/alert-dialog.tsx create mode 100644 renderer/components/ui/card.tsx create mode 100644 renderer/pages/modelsControl.tsx diff --git a/electron-builder.yml b/electron-builder.yml index fe7bedb..25d872f 100644 --- a/electron-builder.yml +++ b/electron-builder.yml @@ -17,16 +17,15 @@ mac: - arm64 - x64 artifactName: "${productName}_Mac_${version}_${arch}.${ext}" -win: { - target: nsis, - requestedExecutionLevel: asInvoker, - artifactName: "${productName}_Windows_${version}_${arch}.${ext}", -} +win: + target: nsis + requestedExecutionLevel: asInvoker + artifactName: "${productName}_Windows_${version}_${arch}.${ext}" nsis: - oneClick: false, - perMachine: false, - allowToChangeInstallationDirectory: true, - deleteAppDataOnUninstall: false, + oneClick: false + perMachine: false + allowToChangeInstallationDirectory: true + deleteAppDataOnUninstall: false extraResources: - from: ./extraResources/ to: ./extraResources/ diff --git a/main/background.ts b/main/background.ts index bc24f96..5028da1 100644 --- a/main/background.ts +++ b/main/background.ts @@ -9,10 +9,17 @@ import { makeWhisper, checkWhisperInstalled, getModelsInstalled, + deleteModel, + downloadModelSync, + getPath, } from "./helpers/whisper"; import { extractAudio } from "./helpers/ffmpeg"; import translate from "./helpers/translate"; -import { getExtraResourcesPath, isWin32, renderTemplate } from "./helpers/utils"; +import { + getExtraResourcesPath, + isWin32, + renderTemplate, +} from "./helpers/utils"; import fs from "fs"; const isProd = process.env.NODE_ENV === "production"; @@ -91,28 +98,49 @@ ipcMain.on("handleTask", async (event, { files, formData }) => { }; const srtFile = path.join( directory, - `${renderTemplate(sourceSrtSaveFileName, templateData)}`, + `${renderTemplate( + saveSourceSrt ? sourceSrtSaveFileName : "${fileName}-temp", + templateData, + )}`, ); const whisperModel = model?.toLowerCase(); + event.sender.send("taskStatusChange", file, "extractAudio", "loading"); await extractAudio(filePath, audioFile); - event.sender.send("extractAudio-completed", file); + event.sender.send("taskStatusChange", file, "extractAudio", "done"); let mainPath = `${whisperPath}main`; - if(isWin32()){ - mainPath = path.join(getExtraResourcesPath(), 'whisper-bin-x64', 'main.exe'); + if (isWin32()) { + mainPath = path.join( + getExtraResourcesPath(), + "whisper-bin-x64", + "main.exe", + ); } + event.sender.send("taskStatusChange", file, "extractSubtitle", "loading"); exec( - `"${mainPath}" -m "${whisperPath}models/ggml-${whisperModel}.bin" -f "${audioFile}" -osrt -of "${srtFile}"`, + `"${mainPath}" -m "${whisperPath}models/ggml-${whisperModel}.bin" -f "${audioFile}" -osrt -of "${srtFile}" -l ${sourceLanguage}`, async (error, stdout, stderr) => { if (error) { event.sender.send("message", error); } - event.sender.send("extractSubtitle-completed", file); + event.sender.send( + "taskStatusChange", + file, + "extractSubtitle", + "done", + ); fs.unlink(audioFile, (err) => { if (err) { console.log(err); } }); if (translateProvider !== "-1") { + event.sender.send( + "taskStatusChange", + file, + "translateSubtitle", + "loading", + ); + await translate( event, directory, @@ -120,7 +148,12 @@ ipcMain.on("handleTask", async (event, { files, formData }) => { `${srtFile}.srt`, formData, ); - event.sender.send("translate-completed", file); + event.sender.send( + "taskStatusChange", + file, + "translateSubtitle", + "done", + ); } if (!saveSourceSrt) { fs.unlink(`${srtFile}.srt`, (err) => { @@ -158,3 +191,22 @@ ipcMain.on("downModel", (event, { model, source }) => { ipcMain.on("openUrl", (event, url) => { shell.openExternal(url); }); + +ipcMain.handle("deleteModel", async (event, modelName) => { + await deleteModel(modelName); + return true; +}); + +ipcMain.handle("getSystemInfo", async (event, key) => { + const res = { + whisperInstalled: checkWhisperInstalled(), + modelsInstalled: getModelsInstalled(), + modelsPath: getPath("modelsPath"), + }; + return res; +}); + +ipcMain.handle("downloadModel", async (event, { model, source }) => { + await downloadModelSync(model?.toLowerCase(), source); + return true; +}); diff --git a/main/helpers/whisper.ts b/main/helpers/whisper.ts index 87698dc..2400317 100644 --- a/main/helpers/whisper.ts +++ b/main/helpers/whisper.ts @@ -84,12 +84,12 @@ export const downModel = async ( try { let downShellPath; let shell: string; - if(isDarwin()){ - downShellPath = path.join(modelsPath, 'download-ggml-model.sh'); - shell = 'bash'; - } else if(isWin32()){ - downShellPath = path.join(modelsPath, 'download-ggml-model.cmd'); - shell = 'cmd.exe /c' + if (isDarwin()) { + downShellPath = path.join(modelsPath, "download-ggml-model.sh"); + shell = "bash"; + } else if (isWin32()) { + downShellPath = path.join(modelsPath, "download-ggml-model.cmd"); + shell = "cmd.exe /c"; } else { throw Error("platform does not support! "); } @@ -118,7 +118,7 @@ export const makeWhisper = (event) => { if (fs.existsSync(mainPath) || isWin32()) { event.sender.send("makeWhisperComplete", true); return; - }; + } if (!checkWhisperInstalled()) { event.sender.send("message", "whisper.cpp 未下载,请先下载 whisper.cpp"); } @@ -135,3 +135,50 @@ export const makeWhisper = (event) => { event.sender.send("makeWhisperComplete", !err); }); }; + +export const deleteModel = async (model) => { + const modelsPath = getPath("modelsPath"); + const modelPath = path.join(modelsPath, `ggml-${model}.bin`); + return new Promise((resolve, reject) => { + if (fs.existsSync(modelPath)) { + fs.unlinkSync(modelPath); + } + resolve("ok"); + }); +}; + +export const downloadModelSync = async (model, source) => { + const modelsPath = getPath("modelsPath"); + const modelPath = path.join(modelsPath, `ggml-${model}.bin`); + if (fs.existsSync(modelPath)) return; + if (!checkWhisperInstalled()) { + throw Error("whisper.cpp 未下载,请先下载 whisper.cpp"); + } + try { + let downShellPath; + let shell: string; + if (isDarwin()) { + downShellPath = path.join(modelsPath, "download-ggml-model.sh"); + shell = "bash"; + } else if (isWin32()) { + downShellPath = path.join(modelsPath, "download-ggml-model.cmd"); + shell = "cmd.exe /c"; + } else { + throw Error("platform does not support! "); + } + await replaceModelSource(`${downShellPath}`, source); + console.log("完成模型下载地址替换", model); + console.log("正在安装 whisper.cpp 模型"); + return new Promise((resolve, reject) => { + exec(`${shell} "${downShellPath}" ${model}`, (err, stdout) => { + if (err) { + reject(err); + } else { + resolve('ok') + } + }); + }) + } catch (error) { + console.log(error) + } +}; diff --git a/main/preload.ts b/main/preload.ts index 8024cb8..49c2a9f 100644 --- a/main/preload.ts +++ b/main/preload.ts @@ -1,13 +1,12 @@ -import { - contextBridge, - ipcRenderer, - IpcRendererEvent, -} from "electron"; +import { contextBridge, ipcRenderer, IpcRendererEvent } from "electron"; const handler = { send(channel: string, value: unknown) { ipcRenderer.send(channel, value); }, + invoke(channel: string, ...args): Promise { + return ipcRenderer.invoke(channel, ...args) + }, on(channel: string, callback: (...args: unknown[]) => void) { const subscription = (_event: IpcRendererEvent, ...args: unknown[]) => callback(...args); diff --git a/package.json b/package.json index 3a06bdd..3b29ce0 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "private": true, "name": "video-subtitle-master", "description": "视频转字幕,字幕翻译软件", - "version": "1.0.10", + "version": "1.0.11", "author": "buxuku ", "main": "app/background.js", "scripts": { @@ -14,6 +14,7 @@ "dependencies": { "@ffmpeg-installer/ffmpeg": "^1.1.0", "@hookform/resolvers": "^3.4.0", + "@radix-ui/react-alert-dialog": "^1.0.5", "@radix-ui/react-dialog": "^1.0.5", "@radix-ui/react-dropdown-menu": "^2.0.6", "@radix-ui/react-hover-card": "^1.0.7", diff --git a/renderer/components/DeleteModel.tsx b/renderer/components/DeleteModel.tsx new file mode 100644 index 0000000..10ddf9e --- /dev/null +++ b/renderer/components/DeleteModel.tsx @@ -0,0 +1,44 @@ +import React from 'react'; +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, + AlertDialogTrigger, +} from "@/components/ui/alert-dialog" + +const DeleteModel = ({children, modelName, callBack}) => { + const [visibility, setVisibility] = React.useState(false); + const handleDelete = async (e) => { + e.preventDefault(); + const res = await window?.ipc?.invoke('deleteModel', modelName); + setVisibility(false); + callBack && callBack(); + } + return ( + + setVisibility(true)}> + {children} + + + + 确认删除该模型? + + 删除之后,如果你需要再次使用该模型,需要重新下载。 + + + + setVisibility(false)}>取消 + 删除 + + + + + ); +}; + +export default DeleteModel; diff --git a/renderer/components/DownModel.tsx b/renderer/components/DownModel.tsx new file mode 100644 index 0000000..618d553 --- /dev/null +++ b/renderer/components/DownModel.tsx @@ -0,0 +1,23 @@ +import React from "react"; +import { Button } from "@/components/ui/button"; +import { Loader2 } from "lucide-react"; + +const DownModel = ({ modelName, callBack, downSource }) => { + const [loading, setLoading] = React.useState(false); + const handleDownModel = async () => { + setLoading(true); + await window?.ipc?.invoke("downloadModel", { + model: modelName, + source: downSource, + }); + setLoading(false); + callBack && callBack(); + }; + return ( + + ); +}; + +export default DownModel; diff --git a/renderer/components/Layout.tsx b/renderer/components/Layout.tsx new file mode 100644 index 0000000..65783ca --- /dev/null +++ b/renderer/components/Layout.tsx @@ -0,0 +1,113 @@ +import React from "react"; +import Link from "next/link"; +import { Button } from "@/components/ui/button"; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "@/components/ui/tooltip"; +import { BotIcon, FileVideo2, Github, MonitorPlay } from "lucide-react"; +import { openUrl } from "lib/utils"; +import { useRouter } from "next/router"; + +const Layout = ({ children }) => { + const { asPath } = useRouter(); + return ( +
+ +
+
+

+ 批量为视频生成字幕,并可翻译成其它语言 +

+
+
{children}
+
+
+ ); +}; + +export default Layout; diff --git a/renderer/components/TaskStatus.tsx b/renderer/components/TaskStatus.tsx new file mode 100644 index 0000000..e7b5e19 --- /dev/null +++ b/renderer/components/TaskStatus.tsx @@ -0,0 +1,15 @@ +import React from "react"; +import { CircleCheck, Loader, Pause } from "lucide-react"; + +const TaskStatus = ({ file, checkKey, skip = false }) => { + if (skip) return "跳过"; + if (file[checkKey] === "loading") { + return ; + } + if (file[checkKey] === "done") { + return ; + } + return ; +}; + +export default TaskStatus; diff --git a/renderer/components/ui/alert-dialog.tsx b/renderer/components/ui/alert-dialog.tsx new file mode 100644 index 0000000..61b5398 --- /dev/null +++ b/renderer/components/ui/alert-dialog.tsx @@ -0,0 +1,139 @@ +import * as React from "react" +import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog" + +import { cn } from "lib/utils" +import { buttonVariants } from "components/ui/button" + +const AlertDialog = AlertDialogPrimitive.Root + +const AlertDialogTrigger = AlertDialogPrimitive.Trigger + +const AlertDialogPortal = AlertDialogPrimitive.Portal + +const AlertDialogOverlay = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName + +const AlertDialogContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + + +)) +AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName + +const AlertDialogHeader = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+) +AlertDialogHeader.displayName = "AlertDialogHeader" + +const AlertDialogFooter = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+) +AlertDialogFooter.displayName = "AlertDialogFooter" + +const AlertDialogTitle = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName + +const AlertDialogDescription = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AlertDialogDescription.displayName = + AlertDialogPrimitive.Description.displayName + +const AlertDialogAction = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName + +const AlertDialogCancel = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName + +export { + AlertDialog, + AlertDialogPortal, + AlertDialogOverlay, + AlertDialogTrigger, + AlertDialogContent, + AlertDialogHeader, + AlertDialogFooter, + AlertDialogTitle, + AlertDialogDescription, + AlertDialogAction, + AlertDialogCancel, +} diff --git a/renderer/components/ui/card.tsx b/renderer/components/ui/card.tsx new file mode 100644 index 0000000..28da0ca --- /dev/null +++ b/renderer/components/ui/card.tsx @@ -0,0 +1,79 @@ +import * as React from "react" + +import { cn } from "lib/utils" + +const Card = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +Card.displayName = "Card" + +const CardHeader = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +CardHeader.displayName = "CardHeader" + +const CardTitle = React.forwardRef< + HTMLParagraphElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +

+)) +CardTitle.displayName = "CardTitle" + +const CardDescription = React.forwardRef< + HTMLParagraphElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +

+)) +CardDescription.displayName = "CardDescription" + +const CardContent = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +

+)) +CardContent.displayName = "CardContent" + +const CardFooter = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +CardFooter.displayName = "CardFooter" + +export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent } diff --git a/renderer/pages/_app.tsx b/renderer/pages/_app.tsx index 76989c9..13e680d 100644 --- a/renderer/pages/_app.tsx +++ b/renderer/pages/_app.tsx @@ -1,10 +1,15 @@ -import React from 'react' -import type { AppProps } from 'next/app' +import React from "react"; +import type { AppProps } from "next/app"; +import Layout from "@/components/Layout"; -import '../styles/globals.css' +import "../styles/globals.css"; function MyApp({ Component, pageProps }: AppProps) { - return + return ( + + + + ); } -export default MyApp +export default MyApp; diff --git a/renderer/pages/home.tsx b/renderer/pages/home.tsx index 3758835..f619fd8 100644 --- a/renderer/pages/home.tsx +++ b/renderer/pages/home.tsx @@ -37,6 +37,7 @@ import { supportedLanguage, defaultUserConfig, openUrl } from "lib/utils"; import store from "lib/store"; import SavePathNotice from "@/components/SavePathNotice"; import { ISystemInfo, IFiles } from "../types"; +import TaskStatus from "@/components/TaskStatus"; export default function Component() { const [files, setFiles] = React.useState([]); @@ -61,12 +62,18 @@ export default function Component() { useEffect(() => { filesRef.current = files; // 不需要翻译,所有文件生成字幕之后就算任务结束 - if(formData.translateProvider === '-1' && files.every(item => item.extractSubtitle)){ - setTaskLoading(false); + if ( + formData.translateProvider === "-1" && + files.every((item) => item.extractSubtitle) + ) { + setTaskLoading(false); } // 需要翻译,所有文件完成字幕翻译之后就算任务结束 - if(formData.translateProvider !== '-1' && files.every(item => item.translateSubtitle)){ - setTaskLoading(false); + if ( + formData.translateProvider !== "-1" && + files.every((item) => item.translateSubtitle) + ) { + setTaskLoading(false); } }, [files]); useEffect(() => { @@ -85,24 +92,15 @@ export default function Component() { })), ); }); - window?.ipc?.on("extractAudio-completed", (res: IFiles) => { - const finalFiles = filesRef.current.map((file) => - file.uuid === res?.uuid ? { ...file, extractAudio: true } : file, - ); - setFiles(finalFiles); - }); - window?.ipc?.on("extractSubtitle-completed", (res: IFiles) => { - const finalFiles = filesRef.current.map((file) => - file.uuid === res?.uuid ? { ...file, extractSubtitle: true } : file, - ); - setFiles(finalFiles); - }); - window?.ipc?.on("translate-completed", (res: IFiles) => { - const finalFiles = filesRef.current.map((file) => - file.uuid === res?.uuid ? { ...file, translateSubtitle: true } : file, - ); - setFiles(finalFiles); - }); + window?.ipc?.on( + "taskStatusChange", + (res: IFiles, key: string, status: string) => { + const finalFiles = filesRef.current.map((file) => + file.uuid === res?.uuid ? { ...file, [key]: status } : file, + ); + setFiles(finalFiles); + }, + ); window?.ipc?.on("getSystemInfoComplete", (res) => { console.log("systemInfo", res); setSystemInfo(res); @@ -123,6 +121,20 @@ export default function Component() { }); return; } + if ( + files.every( + (item) => + item.extractAudio && + item.extractSubtitle && + formData.translateProvider !== "-1" && + item.translateSubtitle, + ) + ) { + toast("消息通知", { + description: "所有文件都已经生成字幕,无需再次生成", + }); + return; + } setTaskLoading(true); window?.ipc?.send("handleTask", { files, formData }); }; @@ -145,296 +157,164 @@ export default function Component() { }; return ( -
-
-
-

- - video-subtitle-master - - 批量为视频生成字幕,并可翻译成其它语言 - - openUrl("https://github.com/buxuku/video-subtitle-master") - } - className="inline-block ml-4 cursor-pointer" - /> - -

-
- - -
-
-
-
-
- -
- - 源字幕设置 - -
- ( - - 模型选择 - - - - {!isInstalledModel && ( - - 该模型未下载, - {downModelLoading ? ( - "正在下载中..." - ) : ( - - 立即下载 - - )} - - )} - - )} - /> -
-
- ( - - 视频原始语言 - - - - - )} - /> -
-
- {/*是否单独保存源字幕文件:*/} - ( - - 是否单独保存源字幕文件 - - - - - )} - /> - {formData.saveSourceSrt && ( -
- ( - - - 源字幕保存文件名设置 - - - - - - + 立即下载 + )} + + )} + + )} + /> +
+
+ ( + + 视频原始语言 + + + + + )} + /> +
+
+ {/*是否单独保存源字幕文件:*/} + ( + + 是否单独保存源字幕文件 + + -
- )} -
-
-
- - 翻译设置 - -
+ + + )} + /> + {formData.saveSourceSrt && ( +
( - 翻译服务 + + 源字幕保存文件名设置 + + - + )} />
- {formData.translateProvider !== "-1" && ( -
-
- ( - - API KEY - - - - - )} - /> -
-
- ( - - API SECRET - - - - - )} - /> -
-
+ )} +
+
+
+ + 翻译设置 + +
+ ( + + 翻译服务 + + + + )} -
-
- ( - - 翻译目标语言 - - - - - )} - /> -
-
+ /> +
+ {formData.translateProvider !== "-1" && ( +
( - 翻译输出字幕设置 + API KEY - + )} @@ -443,13 +323,15 @@ export default function Component() {
( - - 翻译字幕保存文件名设置 - - + API SECRET @@ -457,72 +339,160 @@ export default function Component() { )} />
-
-
- -
-
- - - 已经导入的视频列表 - - - 视频文件名 - 提取音频 - 生成字幕 - 翻译字幕 - - - - {files.map((file) => ( - - - {file?.filePath} - - - {file?.extractAudio ? ( - - ) : ( - - )} - - - {file?.extractSubtitle ? ( - - ) : ( - - )} - - - {file?.translateSubtitle ? ( - - ) : formData.translateProvider === "-1" ? ( - "跳过" - ) : ( - - )} - - - ))} - -
-
-
+
+ )} +
+
+ ( + + 翻译目标语言 + + + + + )} + /> +
+
+
+ ( + + 翻译输出字幕设置 + + + + + )} + /> +
+
+ ( + + + 翻译字幕保存文件名设置 + + + + + + + )} + /> +
+ + + +
+
+ +
+
-
- + + 已经导入的视频列表 + + + 视频文件名 + 提取音频 + 生成字幕 + 翻译字幕 + + + + {files.map((file) => ( + + + {file?.filePath} + + + + + + + + + + + + ))} + +
+ +
+
+
); } diff --git a/renderer/pages/modelsControl.tsx b/renderer/pages/modelsControl.tsx new file mode 100644 index 0000000..97fbc5a --- /dev/null +++ b/renderer/pages/modelsControl.tsx @@ -0,0 +1,110 @@ +import React, { useEffect, useRef, useState } from "react"; +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "@/components/ui/card"; +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@/components/ui/table"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { models } from "lib/utils"; +import { Button } from "@/components/ui/button"; +import { ISystemInfo } from "../types"; +import DeleteModel from "@/components/DeleteModel"; +import DownModel from "@/components/DownModel"; + +const ModelsControl = () => { + const [systemInfo, setSystemInfo] = React.useState({ + whisperInstalled: true, + modelsInstalled: [], + }); + const [downSource, setDownSource] = useState("hf-mirror"); + useEffect(() => { + updateSystemInfo(); + }, []); + const updateSystemInfo = async () => { + const systemInfoRes = await window?.ipc?.invoke("getSystemInfo", null); + console.log(systemInfoRes, 'systemInfoRes') + setSystemInfo(systemInfoRes); + }; + const isInstalledModel = (name) => + systemInfo?.modelsInstalled?.includes(name.toLowerCase()); + const handleDownSource = (value: string) => { + setDownSource(value); + }; + return ( + + + 模型管理 + + 您可以在这里管理你的模型,下载,删除
+ 模型保存位置: {systemInfo?.modelsPath} + + + +
+
+ + + + + 模型名 + 描述 + 操作 + + + + {models.map((model) => ( + + {model?.name} + {model?.desc} + + {isInstalledModel(model.name) ? ( + + + + ) : ( + + )} + + + ))} + +
+
+
+ ); +}; + +export default ModelsControl; diff --git a/yarn.lock b/yarn.lock index 64099eb..c5042ca 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1416,6 +1416,19 @@ dependencies: "@babel/runtime" "^7.13.10" +"@radix-ui/react-alert-dialog@^1.0.5": + version "1.0.5" + resolved "https://r.cnpmjs.org/@radix-ui/react-alert-dialog/-/react-alert-dialog-1.0.5.tgz#70dd529cbf1e4bff386814d3776901fcaa131b8c" + integrity sha512-OrVIOcZL0tl6xibeuGt5/+UxoT2N27KCFOPjFyfXMnchxSHZ/OW7cCX2nGlIYJrbHK/fczPcFzAwvNBB6XBNMA== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/primitive" "1.0.1" + "@radix-ui/react-compose-refs" "1.0.1" + "@radix-ui/react-context" "1.0.1" + "@radix-ui/react-dialog" "1.0.5" + "@radix-ui/react-primitive" "1.0.3" + "@radix-ui/react-slot" "1.0.2" + "@radix-ui/react-arrow@1.0.3": version "1.0.3" resolved "https://r.cnpmjs.org/@radix-ui/react-arrow/-/react-arrow-1.0.3.tgz#c24f7968996ed934d57fe6cde5d6ec7266e1d25d" @@ -1449,7 +1462,7 @@ dependencies: "@babel/runtime" "^7.13.10" -"@radix-ui/react-dialog@^1.0.4", "@radix-ui/react-dialog@^1.0.5": +"@radix-ui/react-dialog@1.0.5", "@radix-ui/react-dialog@^1.0.4", "@radix-ui/react-dialog@^1.0.5": version "1.0.5" resolved "https://r.cnpmjs.org/@radix-ui/react-dialog/-/react-dialog-1.0.5.tgz#71657b1b116de6c7a0b03242d7d43e01062c7300" integrity sha512-GjWJX/AUpB703eEBanuBnIWdIXg6NvJFCXcNlSZk4xdszCdhrJgBoUd1cGk67vFO+WdA2pfI/plOpqz/5GUP6Q==