diff --git a/electron/main.ts b/electron/main.ts index 9e50ae228f..b11963e671 100644 --- a/electron/main.ts +++ b/electron/main.ts @@ -20,6 +20,7 @@ const { autoUpdater } = require("electron-updater"); const Store = require("electron-store"); let requiredModules: Record = {}; +const networkRequests: Record = {}; let mainWindow: BrowserWindow | undefined = undefined; app @@ -48,18 +49,6 @@ app.on("quit", () => { app.quit(); }); -ipcMain.handle("setNativeThemeLight", () => { - nativeTheme.themeSource = "light"; -}); - -ipcMain.handle("setNativeThemeDark", () => { - nativeTheme.themeSource = "dark"; -}); - -ipcMain.handle("setNativeThemeSystem", () => { - nativeTheme.themeSource = "system"; -}); - function createMainWindow() { mainWindow = new BrowserWindow({ width: 1200, @@ -138,6 +127,30 @@ function handleAppUpdates() { * Handles various IPC messages from the renderer process. */ function handleIPCs() { + /** + * Handles the "setNativeThemeLight" IPC message by setting the native theme source to "light". + * This will change the appearance of the app to the light theme. + */ + ipcMain.handle("setNativeThemeLight", () => { + nativeTheme.themeSource = "light"; + }); + + /** + * Handles the "setNativeThemeDark" IPC message by setting the native theme source to "dark". + * This will change the appearance of the app to the dark theme. + */ + ipcMain.handle("setNativeThemeDark", () => { + nativeTheme.themeSource = "dark"; + }); + + /** + * Handles the "setNativeThemeSystem" IPC message by setting the native theme source to "system". + * This will change the appearance of the app to match the system's current theme. + */ + ipcMain.handle("setNativeThemeSystem", () => { + nativeTheme.themeSource = "system"; + }); + /** * Invokes a function from a plugin module in main node process. * @param _event - The IPC event object. @@ -319,8 +332,9 @@ function handleIPCs() { ipcMain.handle("downloadFile", async (_event, url, fileName) => { const userDataPath = app.getPath("userData"); const destination = resolve(userDataPath, fileName); + const rq = request(url); - progress(request(url), {}) + progress(rq, {}) .on("progress", function (state: any) { mainWindow?.webContents.send("FILE_DOWNLOAD_UPDATE", { ...state, @@ -332,13 +346,54 @@ function handleIPCs() { fileName, err, }); + networkRequests[fileName] = undefined; }) .on("end", function () { - mainWindow?.webContents.send("FILE_DOWNLOAD_COMPLETE", { - fileName, - }); + if (networkRequests[fileName]) { + mainWindow?.webContents.send("FILE_DOWNLOAD_COMPLETE", { + fileName, + }); + networkRequests[fileName] = undefined; + } else { + mainWindow?.webContents.send("FILE_DOWNLOAD_ERROR", { + fileName, + err: "Download cancelled", + }); + } }) .pipe(createWriteStream(destination)); + + networkRequests[fileName] = rq; + }); + + /** + * Handles the "pauseDownload" IPC message by pausing the download associated with the provided fileName. + * @param _event - The IPC event object. + * @param fileName - The name of the file being downloaded. + */ + ipcMain.handle("pauseDownload", async (_event, fileName) => { + networkRequests[fileName]?.pause(); + }); + + /** + * Handles the "resumeDownload" IPC message by resuming the download associated with the provided fileName. + * @param _event - The IPC event object. + * @param fileName - The name of the file being downloaded. + */ + ipcMain.handle("resumeDownload", async (_event, fileName) => { + networkRequests[fileName]?.resume(); + }); + + /** + * Handles the "abortDownload" IPC message by aborting the download associated with the provided fileName. + * The network request associated with the fileName is then removed from the networkRequests object. + * @param _event - The IPC event object. + * @param fileName - The name of the file being downloaded. + */ + ipcMain.handle("abortDownload", async (_event, fileName) => { + const rq = networkRequests[fileName]; + networkRequests[fileName] = undefined; + rq?.abort(); }); /** diff --git a/electron/preload.ts b/electron/preload.ts index 84fd0d69b5..e8fd723a52 100644 --- a/electron/preload.ts +++ b/electron/preload.ts @@ -38,6 +38,15 @@ contextBridge.exposeInMainWorld("electronAPI", { downloadFile: (url: string, path: string) => ipcRenderer.invoke("downloadFile", url, path), + pauseDownload: (fileName: string) => + ipcRenderer.invoke("pauseDownload", fileName), + + resumeDownload: (fileName: string) => + ipcRenderer.invoke("resumeDownload", fileName), + + abortDownload: (fileName: string) => + ipcRenderer.invoke("abortDownload", fileName), + onFileDownloadUpdate: (callback: any) => ipcRenderer.on("FILE_DOWNLOAD_UPDATE", callback), diff --git a/web/app/_components/ConfirmationModal/index.tsx b/web/app/_components/ConfirmationModal/index.tsx new file mode 100644 index 0000000000..baac7840df --- /dev/null +++ b/web/app/_components/ConfirmationModal/index.tsx @@ -0,0 +1,87 @@ +import React, { Fragment } from 'react' +import { Dialog, Transition } from '@headlessui/react' +import { QuestionMarkCircleIcon } from '@heroicons/react/24/outline' +import { PrimitiveAtom, useAtom } from 'jotai' + +interface Props { + atom: PrimitiveAtom + title: string + description: string + onConfirm: () => void +} + +const ConfirmationModal: React.FC = ({ atom, title, description, onConfirm }) => { + const [show, setShow] = useAtom(atom) + + return ( + + + +
+ + +
+
+ + +
+
+
+
+ + {title} + +
+

{description}

+
+
+
+
+ + +
+
+
+
+
+
+
+ ) +} + +export default React.memo(ConfirmationModal) diff --git a/web/app/_components/ExploreModelItemHeader/index.tsx b/web/app/_components/ExploreModelItemHeader/index.tsx index 55786dbc49..81228d0daf 100644 --- a/web/app/_components/ExploreModelItemHeader/index.tsx +++ b/web/app/_components/ExploreModelItemHeader/index.tsx @@ -11,6 +11,8 @@ import { MainViewState, setMainViewStateAtom, } from '@helpers/atoms/MainView.atom' +import ConfirmationModal from '../ConfirmationModal' +import { showingCancelDownloadModalAtom } from '@helpers/atoms/Modal.atom' type Props = { suitableModel: ModelVersion @@ -31,6 +33,9 @@ const ExploreModelItemHeader: React.FC = ({ ) const downloadState = useAtomValue(downloadAtom) const setMainViewState = useSetAtom(setMainViewStateAtom) + const setShowingCancelDownloadModal = useSetAtom( + showingCancelDownloadModalAtom + ) useEffect(() => { getPerformanceForModel(suitableModel) @@ -70,17 +75,30 @@ const ExploreModelItemHeader: React.FC = ({ // downloading downloadButton = ( ) } + let cancelDownloadModal = + downloadState != null ? ( + { + window.coreAPI?.abortDownload(downloadState?.fileName) + }} + /> + ) : ( + <> + ) + return (
@@ -90,6 +108,7 @@ const ExploreModelItemHeader: React.FC = ({ )}
{downloadButton} + {cancelDownloadModal}
) } diff --git a/web/containers/BottomBar/index.tsx b/web/containers/BottomBar/index.tsx index 19e288cc2e..fedddcfdbe 100644 --- a/web/containers/BottomBar/index.tsx +++ b/web/containers/BottomBar/index.tsx @@ -57,14 +57,6 @@ const BottomBar = () => { {!stateModelStartStop.loading && ( )} - {downloadStates.length > 0 && ( - - )}
diff --git a/web/helpers/EventListenerWrapper.tsx b/web/helpers/EventListenerWrapper.tsx index a75a9bb689..f248bf0209 100644 --- a/web/helpers/EventListenerWrapper.tsx +++ b/web/helpers/EventListenerWrapper.tsx @@ -35,6 +35,7 @@ export default function EventListenerWrapper({ children }: Props) { window.electronAPI.onFileDownloadError( (_event: string, callback: any) => { console.log('Download error', callback) + setDownloadStateSuccess(callback.fileName) } ) diff --git a/web/helpers/atoms/DownloadState.atom.ts b/web/helpers/atoms/DownloadState.atom.ts index 66c10e7a4a..8db01d810d 100644 --- a/web/helpers/atoms/DownloadState.atom.ts +++ b/web/helpers/atoms/DownloadState.atom.ts @@ -28,4 +28,4 @@ export const setDownloadStateSuccessAtom = atom( delete currentState[fileName] set(modelDownloadStateAtom, currentState) } -) +) \ No newline at end of file diff --git a/web/helpers/atoms/Modal.atom.ts b/web/helpers/atoms/Modal.atom.ts index b1608089d4..87bfb80cb7 100644 --- a/web/helpers/atoms/Modal.atom.ts +++ b/web/helpers/atoms/Modal.atom.ts @@ -8,6 +8,7 @@ export const showingAdvancedPromptAtom = atom(false) export const showingProductDetailAtom = atom(false) export const showingMobilePaneAtom = atom(false) export const showingBotListModalAtom = atom(false) +export const showingCancelDownloadModalAtom = atom(false) export const switchingModelConfirmationModalPropsAtom = atom< SwitchingModelConfirmationModalProps | undefined diff --git a/web/screens/MyModels/index.tsx b/web/screens/MyModels/index.tsx index 19ccf76075..dddb5ad850 100644 --- a/web/screens/MyModels/index.tsx +++ b/web/screens/MyModels/index.tsx @@ -30,7 +30,7 @@ const MyModelsScreen = () => { return (
- +
{isDownloadingFirstModel ? (
@@ -38,7 +38,7 @@ const MyModelsScreen = () => {

Donwloading your first model

-

+

{downloadStates[0].fileName} -{' '} {formatDownloadPercentage(downloadStates[0].percent)}

@@ -47,7 +47,7 @@ const MyModelsScreen = () => { ) : (

{`Ups, You don't have a model.`}

-

{`let’s download your first model`}

+

{`let’s download your first model`}