This commit is contained in:
Timothy Jaeryang Baek
2026-03-20 22:48:01 -05:00
parent 5132ef790e
commit 8be489d9d6
6 changed files with 115 additions and 3 deletions
+24 -1
View File
@@ -65,7 +65,8 @@ import {
getLlamaCppPty,
validateLlamaCppProcess,
checkLlamaCppUpdate,
updateLlamaCpp
updateLlamaCpp,
uninstallLlamaCpp
} from './utils/llamacpp'
import {
@@ -907,6 +908,28 @@ if (!gotTheLock) {
ipcMain.handle('llamacpp:logs', () => getLlamaCppLog())
ipcMain.handle('llamacpp:pty:connect', () => connectLlamaCppPtyPort())
ipcMain.handle('llamacpp:uninstall', async () => {
try {
const info = getLlamaCppInfo()
await uninstallLlamaCpp()
sendToRenderer('status:llamacpp', null)
// Unregister OpenAI endpoint if it was running
if (info.url) {
sendToRenderer('connections:openai', {
action: 'remove',
url: `${info.url}/v1`
})
setTimeout(() => sendToRenderer('models:refresh'), 500)
}
await setConfig({ llamaCpp: { ...CONFIG?.llamaCpp, enabled: false } })
CONFIG = await getConfig()
return true
} catch (error) {
log.error('Failed to uninstall llamacpp:', error)
return false
}
})
// Hugging Face models
ipcMain.handle('huggingface:models:list', () => listModels())
ipcMain.handle('huggingface:models:dir', () => getModelsDir())
+15
View File
@@ -505,3 +505,18 @@ export const validateLlamaCppProcess = (): boolean => {
lock.release()
return false
}
/**
* Uninstall llama.cpp — stop the server and remove all downloaded binaries.
*/
export const uninstallLlamaCpp = async (): Promise<void> => {
await stopLlamaCpp()
const cacheBase = path.join(getUserDataPath(), 'llama.cpp')
if (fs.existsSync(cacheBase)) {
fs.rmSync(cacheBase, { recursive: true, force: true })
log.info('Removed llama.cpp directory:', cacheBase)
}
binaryPath = null
}
+1
View File
@@ -175,6 +175,7 @@ const api = {
},
checkLlamaCppUpdate: () => ipcRenderer.invoke('llamacpp:check-update'),
updateLlamaCpp: () => ipcRenderer.invoke('llamacpp:update'),
uninstallLlamaCpp: () => ipcRenderer.invoke('llamacpp:uninstall'),
// Hugging Face models
listHfModels: () => ipcRenderer.invoke('huggingface:models:list'),
@@ -67,7 +67,7 @@
<div>
<div class="text-[13px] font-medium text-gray-700 dark:text-gray-300 flex items-center gap-1.5">
{$i18n.t('main.getStarted.llamaCpp')}
<span class="text-[9px] px-1.5 py-0.5 rounded-full bg-amber-500/10 text-amber-600 dark:text-amber-400 uppercase tracking-wide font-medium">{$i18n.t('common.experimental')}</span>
<span class="text-[9px] opacity-30 uppercase tracking-wide">{$i18n.t('common.experimental')}</span>
</div>
<div class="text-[11px] text-gray-400 dark:text-gray-500 mt-0.5">{$i18n.t('main.getStarted.llamaCppDesc')}</div>
</div>
@@ -11,6 +11,8 @@
let settingUp = $state(false)
let loaded = $state(false)
let setupStatus = $state('')
let uninstalling = $state(false)
let installing = $state(false)
type UpdateStatus = 'idle' | 'checking' | 'available' | 'updating' | 'up-to-date' | 'error'
let updateStatus = $state<UpdateStatus>('idle')
@@ -166,6 +168,8 @@
deleting = null
}
const installed = $derived(!!lsInfo?.binaryPath)
const formatSize = (bytes: number): string => {
if (bytes < 1024) return `${bytes} B`
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`
@@ -176,6 +180,37 @@
{#if !loaded}
<div class="py-6 text-[12px] opacity-20 text-center">{$i18n.t('common.loading')}</div>
{:else if !installed}
<div class="py-4 flex items-center justify-between">
<div>
<div class="text-[13px] opacity-40 flex items-center gap-1.5">
{$i18n.t('settings.inference.notInstalled')}
<span class="text-[9px] opacity-30 uppercase tracking-wide">{$i18n.t('common.experimental')}</span>
</div>
<div class="text-[11px] opacity-20 mt-0.5">{$i18n.t('settings.inference.notInstalledDesc')}</div>
</div>
<button
class="text-[12px] opacity-40 hover:opacity-70 px-3 py-1.5 bg-black/[0.04] dark:bg-white/[0.06] transition border-none text-[#1d1d1f] dark:text-[#fafafa] rounded-xl flex items-center gap-1.5 {installing ? 'pointer-events-none opacity-20' : ''}"
disabled={installing}
onclick={async () => {
installing = true
try {
await window.electronAPI.startLlamaCpp()
lsInfo = await window.electronAPI.getLlamaCppInfo()
} catch (e) {
console.error('Failed to install:', e)
}
installing = false
}}
>
{#if installing}
<div class="w-2.5 h-2.5 rounded-full border-[1.5px] border-black/20 dark:border-white/30 border-t-transparent animate-spin"></div>
{$i18n.t('common.installing')}
{:else}
{$i18n.t('common.install')}
{/if}
</button>
</div>
{:else}
<div class="flex flex-col divide-y divide-white/[0.04]">
<!-- Server status & controls -->
@@ -184,7 +219,7 @@
<div>
<div class="text-[13px] opacity-70 flex items-center gap-1.5">
{$i18n.t('settings.inference.llamaServer')}
<span class="text-[9px] px-1.5 py-0.5 rounded-full bg-amber-500/10 text-amber-600 dark:text-amber-400 uppercase tracking-wide font-medium">{$i18n.t('common.experimental')}</span>
<span class="text-[9px] opacity-30 uppercase tracking-wide">{$i18n.t('common.experimental')}</span>
</div>
<div class="text-[11px] opacity-25 mt-0.5">
{$i18n.t('settings.inference.llamaServerDesc')}
@@ -430,5 +465,38 @@
/>
</div>
<!-- Uninstall -->
{#if lsInfo?.binaryPath}
<div class="py-4 flex items-center justify-between">
<div>
<div class="text-[13px] opacity-70">{$i18n.t('settings.inference.uninstall')}</div>
<div class="text-[11px] opacity-25 mt-0.5">{$i18n.t('settings.inference.uninstallDesc')}</div>
</div>
<button
class="text-[12px] opacity-40 hover:opacity-70 px-3 py-1.5 bg-black/[0.04] dark:bg-white/[0.06] transition border-none text-[#1d1d1f] dark:text-[#fafafa] rounded-xl flex items-center gap-1.5 {uninstalling ? 'pointer-events-none opacity-20' : ''}"
disabled={uninstalling}
onclick={async () => {
if (confirm($i18n.t('settings.inference.uninstallConfirm'))) {
uninstalling = true
try {
await window.electronAPI.uninstallLlamaCpp()
lsInfo = await window.electronAPI.getLlamaCppInfo()
} catch (e) {
console.error('Failed to uninstall llama.cpp:', e)
}
uninstalling = false
}
}}
>
{#if uninstalling}
<div class="w-2.5 h-2.5 rounded-full border-[1.5px] border-black/20 dark:border-white/30 border-t-transparent animate-spin"></div>
{$i18n.t('common.uninstalling')}
{:else}
{$i18n.t('common.uninstall')}
{/if}
</button>
</div>
{/if}
</div>
{/if}
@@ -235,6 +235,11 @@
"settings.inference.updateAvailable": "Update available: {{version}}",
"settings.inference.upToDate": "Up to date",
"settings.inference.updating": "Updating…",
"settings.inference.uninstall": "Uninstall",
"settings.inference.uninstallDesc": "Remove llama.cpp binaries",
"settings.inference.uninstallConfirm": "This will stop llama-server and remove all downloaded binaries. Continue?",
"settings.inference.notInstalled": "llama.cpp is not installed",
"settings.inference.notInstalledDesc": "Download and set up llama.cpp for local model inference",
"settings.models.downloadedModels": "Downloaded Models",
"settings.models.noModels": "No models downloaded yet",