diff --git a/preloader/index.js b/preloader/index.js index 1ece726..2825d0d 100644 --- a/preloader/index.js +++ b/preloader/index.js @@ -6,7 +6,7 @@ const { setClient, downloadModel, updateModelSettings, - formator + formator, deleteModel } = require("./node-llama-cpp-preloader.js") const { @@ -15,7 +15,8 @@ const { contextBridge.exposeInMainWorld('node-llama-cpp', { loadModel, chatCompletions, updateModelSettings, - abortCompletion, setClient, downloadModel, formator + abortCompletion, setClient, downloadModel, formator, + deleteModel }) contextBridge.exposeInMainWorld('file-handler', { diff --git a/preloader/node-llama-cpp-preloader.js b/preloader/node-llama-cpp-preloader.js index bad950c..7001b59 100644 --- a/preloader/node-llama-cpp-preloader.js +++ b/preloader/node-llama-cpp-preloader.js @@ -1,5 +1,5 @@ const { ipcRenderer } = require("electron"); -const { createWriteStream, existsSync, statSync, mkdirSync } = require("fs"); +const { createWriteStream, existsSync, statSync, mkdirSync, rmSync } = require("fs"); const path = require("path"); let llama, getLlama, LlamaChatSession, current_model; @@ -216,6 +216,19 @@ function downloadModel(url, cb=null) { }) } +/** + * Delete a model from disk + * @param {String} model_name the model name on disk + * @returns {Boolean} + */ +function deleteModel(model_name) { + const model = path.join(model_path, model_name); + if(existsSync(model)){ + rmSync(model); + } + return true; +} + /** * format messages, reset history if needed * @param {Message[]} messages @@ -239,5 +252,6 @@ module.exports = { setClient, downloadModel, updateModelSettings, - formator + formator, + deleteModel } \ No newline at end of file diff --git a/src/components/ConfirmationDialog.jsx b/src/components/ConfirmationDialog.jsx new file mode 100644 index 0000000..d08d5d8 --- /dev/null +++ b/src/components/ConfirmationDialog.jsx @@ -0,0 +1,26 @@ +import { useEffect, useRef } from "react"; + +export default function ConfirmationDialog({children, open_status, setOpenStatus, callback}) { + const dialogRef = useRef(null); + + useEffect(()=>{ + if(dialogRef.current) { + if(open_status) dialogRef.current.showModal(); + else dialogRef.current.close(); + } + }, [open_status]) + + return ( + setOpenStatus(false)}> + { children } +
callback(true)} + >Yes, Continue
+
callback(false)} + >No, Go Back
+
+ ) +} \ No newline at end of file diff --git a/src/components/chat/DeleteConfirm.jsx b/src/components/chat/DeleteConfirm.jsx deleted file mode 100644 index 2105103..0000000 --- a/src/components/chat/DeleteConfirm.jsx +++ /dev/null @@ -1,29 +0,0 @@ -import { useEffect } from "react"; -import { useRef } from "react"; - -export default function DeleteConfirm({showConfirm, closeDialog, deleteHistory, resetRequestDelete, conv_to_delete}) { - const dialogRef = useRef(null); - - useEffect(()=>{ - if(dialogRef.current) { - if(showConfirm) dialogRef.current.showModal(); - else dialogRef.current.close(); - } - }, [showConfirm]) - - return ( - -
- Delete {conv_to_delete && conv_to_delete.title}? -
-
Yes, Delete
-
No, Go Back
-
- ) -} \ No newline at end of file diff --git a/src/components/chat/index.jsx b/src/components/chat/index.jsx index cf64802..ff6f6be 100644 --- a/src/components/chat/index.jsx +++ b/src/components/chat/index.jsx @@ -1,12 +1,11 @@ import { useEffect, useRef, useState } from "react"; import Tickets from "./Tickets"; -// import Conversation from "./Conversation"; import useIDB from "../../utils/idb"; -import DeleteConfirm from "./DeleteConfirm"; import ChatPage from "./ChatPage"; import { getCompletionFunctions } from "../../utils/workers"; import { getPlatformSettings } from "../../utils/general_settings"; import { exportChatHistory } from "../../utils/chat-history-handler"; +import ConfirmationDialog from "../ConfirmationDialog"; export default function Chat() { @@ -184,13 +183,15 @@ export default function Chat() { pending_message={pending_message} abort={session_setting.abort} sendMessage={sendMessage} updateSystemInstruction={updateSystemInstruction} /> - toggleConfirm(false)} - deleteHistory={deleteHistory} - resetRequestDelete={resetRequestDelete} - conv_to_delete={conv_to_delete} - /> + { + cb ? deleteHistory() : resetRequestDelete(); + }} + > +
Delete { conv_to_delete && conv_to_delete.title }?
+
: <> ) diff --git a/src/components/settings/LlamaSettings.jsx b/src/components/settings/LlamaSettings.jsx index 167e386..1f22342 100644 --- a/src/components/settings/LlamaSettings.jsx +++ b/src/components/settings/LlamaSettings.jsx @@ -6,12 +6,16 @@ import useIDB from "../../utils/idb"; import { getPlatformSettings, updatePlatformSettings } from "../../utils/general_settings"; import { DEFAULT_LLAMA_CPP_MODEL_URL } from "../../utils/types"; import DropdownComponent from "./components/DropdownComponent"; +import ButtonComponent from "./components/ButtonComponent"; +import ConfirmationDialog from "../ConfirmationDialog"; export default function LlamaSettings({ trigger, enabled, updateEnabled, openDownloadProtector, updateState }) { const [model_download_link, setModelDownloadLink] = useState(''); + const [selected_model, setSelectedModel] = useState({}); const [reset_everytime, setResetEveryTime] = useState(false); const [downloaded_models, setDownloadedModels] = useState([]) + const [delete_confirm_opened, setDeleteConfirmOpenStatus] = useState(false); const idb = useIDB(); async function saveSettings() { @@ -41,25 +45,58 @@ export default function LlamaSettings({ trigger, enabled, updateEnabled, openDow platform: 'Llama' } await idb.insert("downloaded-models", stored_model) - setDownloadedModels([...downloaded_models, { title: stored_model['model-name'], value: stored_model.url }]) + setDownloadedModels([...downloaded_models, { title: stored_model['model-name'], value: JSON.stringify(stored_model) }]) + setSelectedModel(stored_model) } } ) } - await openDownloadProtector( - 'Loading model...', - `Loading model ${stored_model['model-name']}`, - async callback => { - callback(100, false); - // load model using the model name retrieved - await window['node-llama-cpp'].loadModel(stored_model['model-name']) - updateState(); - callback(100, true) - } - ) + await loadModel(stored_model); } } + async function loadModel(model) { + await openDownloadProtector( + 'Loading model...', + `Loading model ${model['model-name']}`, + async callback => { + callback(100, false); + // load model using the model name retrieved + await window['node-llama-cpp'].loadModel(model['model-name']) + updateState(); + callback(100, true) + } + ) + } + + async function deleteModel() { + let load_after_delete = {}; + await openDownloadProtector( + 'Please wait while we deleteing the model...', + `Loading model ${selected_model['model-name']}`, + async callback => { + window['node-llama-cpp'].deleteModel(selected_model['model-name']); + callback(80, false); + await idb.deleteOne("downloaded-models", [selected_model]); + callback(90, false); + + const models = await idb.getAll("downloaded-models", { + where: [{'platform': 'Llama'}] + }); + setDownloadedModels(models.map(e=>{return { title: e['model-name'], value: JSON.stringify(e) }})) + load_after_delete = models.pop() || {} + const url = load_after_delete.url || ''; + setSelectedModel(load_after_delete); + setModelDownloadLink(url); + updatePlatformSettings({ + llama_model_url: url + }) + callback(100, true); + } + ) + await loadModel(load_after_delete); + } + useEffect(()=>{ trigger && saveSettings(); // eslint-disable-next-line @@ -72,10 +109,10 @@ export default function LlamaSettings({ trigger, enabled, updateEnabled, openDow setResetEveryTime(llama_reset_everytime); const models = await idb.getAll("downloaded-models", { - where: [{'platform': 'Llama'}], - select: ["model-name", "url"] + where: [{'platform': 'Llama'}] }); - setDownloadedModels(models.map(e=>{return { title: e['model-name'], value: e.url }})) + setDownloadedModels(models.map(e=>{return { title: e['model-name'], value: JSON.stringify(e) }})) + setSelectedModel(models.filter(e=>e.url === llama_model_url).pop()) })() // eslint-disable-next-line }, []) @@ -90,7 +127,12 @@ export default function LlamaSettings({ trigger, enabled, updateEnabled, openDow { + model = JSON.parse(model) + setSelectedModel(model) + setModelDownloadLink(model.url) + }} + selected={JSON.stringify(selected_model)} /> + {setDeleteConfirmOpenStatus(true)}} + /> + { + cb && deleteModel(); + setDeleteConfirmOpenStatus(false); + }} + > +
+ Are you sure you want to delete model
+ {selected_model['model-name']}? +
+
) } \ No newline at end of file diff --git a/src/components/settings/components/ButtonComponent.jsx b/src/components/settings/components/ButtonComponent.jsx new file mode 100644 index 0000000..5644bf9 --- /dev/null +++ b/src/components/settings/components/ButtonComponent.jsx @@ -0,0 +1,9 @@ +export default function ButtonComponent({ cb, value, disabled, title, description, className }) { + return ( +
+
{title}
+ { description &&
{description}
} +
{ value }
+
+ ) +} \ No newline at end of file diff --git a/src/components/settings/components/DropdownComponent.jsx b/src/components/settings/components/DropdownComponent.jsx index 35be96a..ed21994 100644 --- a/src/components/settings/components/DropdownComponent.jsx +++ b/src/components/settings/components/DropdownComponent.jsx @@ -1,12 +1,27 @@ -export default function DropdownComponent({ cb, value, title, description }) { +import { useEffect } from "react"; +import { useState } from "react"; + +export default function DropdownComponent({ cb, value, selected, title, description }) { + + const [selected_option, setSelectedOption] = useState(selected || (value && value.length && value[0].value) || ''); + + useEffect(()=>{ + selected && setSelectedOption(selected); + }, [selected]) + return (
{title}
{ description &&
{description}
}