mirror of
https://github.com/mudler/LocalAI.git
synced 2026-04-18 13:58:07 -04:00
* feat(ui): show loaded models in the index Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * chore(ui): re-organize navbar Signed-off-by: Ettore Di Giacinto <mudler@localai.io> --------- Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
570 lines
29 KiB
HTML
570 lines
29 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
{{template "views/partials/head" .}}
|
|
|
|
<body class="bg-[#101827] text-[#E5E7EB]">
|
|
<div class="flex flex-col min-h-screen" x-data="indexDashboard()">
|
|
|
|
{{template "views/partials/navbar" .}}
|
|
|
|
<!-- Notifications -->
|
|
<div class="fixed top-20 right-4 z-50 space-y-2" style="max-width: 400px;">
|
|
<template x-for="notification in notifications" :key="notification.id">
|
|
<div x-show="true"
|
|
x-transition:enter="transition ease-out duration-200"
|
|
x-transition:enter-start="opacity-0"
|
|
x-transition:enter-end="opacity-100"
|
|
x-transition:leave="transition ease-in duration-150"
|
|
x-transition:leave-start="opacity-100"
|
|
x-transition:leave-end="opacity-0"
|
|
:class="notification.type === 'error' ? 'bg-red-500' : 'bg-green-500'"
|
|
class="rounded-lg p-4 text-white flex items-start space-x-3">
|
|
<div class="flex-shrink-0">
|
|
<i :class="notification.type === 'error' ? 'fas fa-exclamation-circle' : 'fas fa-check-circle'" class="text-xl"></i>
|
|
</div>
|
|
<div class="flex-1 min-w-0">
|
|
<p class="text-sm font-medium break-words" x-text="notification.message"></p>
|
|
</div>
|
|
<button @click="dismissNotification(notification.id)" class="flex-shrink-0 text-white hover:opacity-80 transition-opacity">
|
|
<i class="fas fa-times"></i>
|
|
</button>
|
|
</div>
|
|
</template>
|
|
</div>
|
|
|
|
<div class="container mx-auto px-4 py-6 flex-grow">
|
|
<!-- Header -->
|
|
<div class="mb-6">
|
|
<h1 class="text-2xl font-semibold text-[#E5E7EB] mb-1">
|
|
Model & Backend Management
|
|
</h1>
|
|
<p class="text-sm text-[#94A3B8]">Manage your installed models and backends</p>
|
|
</div>
|
|
|
|
<!-- Quick Actions -->
|
|
<div class="flex flex-wrap gap-2 mb-6">
|
|
<a href="browse"
|
|
class="inline-flex items-center bg-[#8B5CF6] hover:bg-[#8B5CF6]/90 text-white py-1.5 px-3 rounded text-xs font-medium transition-colors">
|
|
<i class="fas fa-images mr-1.5 text-[10px]"></i>
|
|
<span>Model Gallery</span>
|
|
</a>
|
|
|
|
<a href="/import-model"
|
|
class="inline-flex items-center bg-green-600 hover:bg-green-700 text-white py-1.5 px-3 rounded text-xs font-medium transition-colors">
|
|
<i class="fas fa-plus mr-1.5 text-[10px]"></i>
|
|
<span>Import Model</span>
|
|
</a>
|
|
|
|
<button id="reload-models-btn"
|
|
class="inline-flex items-center bg-orange-600 hover:bg-orange-700 text-white py-1.5 px-3 rounded text-xs font-medium transition-colors">
|
|
<i class="fas fa-sync-alt mr-1.5 text-[10px]"></i>
|
|
<span>Update Models</span>
|
|
</button>
|
|
|
|
<a href="/browse/backends"
|
|
class="inline-flex items-center bg-[#1E293B] hover:bg-[#1E293B]/80 border border-[#8B5CF6]/20 text-[#E5E7EB] py-1.5 px-3 rounded text-xs font-medium transition-colors">
|
|
<i class="fas fa-cogs mr-1.5 text-[10px]"></i>
|
|
<span>Backend Gallery</span>
|
|
</a>
|
|
</div>
|
|
|
|
<!-- Models Section -->
|
|
<div class="models mt-8">
|
|
{{template "views/partials/inprogress" .}}
|
|
|
|
{{ if eq (len .ModelsConfig) 0 }}
|
|
<!-- No Models State -->
|
|
<div class="bg-[#1E293B] border border-[#38BDF8]/20 rounded-lg p-8">
|
|
<div class="text-center max-w-4xl mx-auto">
|
|
<div class="inline-flex items-center justify-center w-12 h-12 rounded-full bg-yellow-500/10 border border-yellow-500/20 mb-4">
|
|
<i class="text-yellow-400 text-xl fas fa-robot"></i>
|
|
</div>
|
|
<h2 class="text-2xl font-bold text-[#E5E7EB] mb-2">No models installed yet</h2>
|
|
<p class="text-sm text-[#94A3B8] mb-6">Get started by installing a model from the gallery or importing it</p>
|
|
|
|
<div class="flex flex-wrap justify-center gap-2 mb-6">
|
|
<a href="browse" class="inline-flex items-center bg-[#38BDF8] hover:bg-[#38BDF8]/90 text-[#101827] py-1.5 px-3 rounded text-xs font-medium transition-colors">
|
|
<i class="fas fa-images mr-1.5 text-[10px]"></i>
|
|
Browse Model Gallery
|
|
</a>
|
|
<a href="/import-model" class="inline-flex items-center bg-green-600 hover:bg-green-700 text-white py-1.5 px-3 rounded text-xs font-medium transition-colors">
|
|
<i class="fas fa-upload mr-1.5 text-[10px]"></i>
|
|
Import Model
|
|
</a>
|
|
<a href="https://localai.io/basics/getting_started/" target="_blank" class="inline-flex items-center bg-[#1E293B] hover:bg-[#1E293B]/80 border border-[#38BDF8]/20 text-[#E5E7EB] py-1.5 px-3 rounded text-xs font-medium transition-colors">
|
|
<i class="fas fa-book mr-1.5 text-[10px]"></i>
|
|
Documentation
|
|
</a>
|
|
</div>
|
|
|
|
{{ if ne (len .Models) 0 }}
|
|
<div class="mt-8 pt-6 border-t border-[#38BDF8]/20">
|
|
<h3 class="text-lg font-semibold text-[#E5E7EB] mb-2 flex items-center">
|
|
<i class="fas fa-file-alt mr-2 text-[#38BDF8] text-sm"></i>
|
|
Detected Model Files
|
|
</h3>
|
|
<p class="text-xs text-[#94A3B8] mb-4">These models were found but don't have configuration files yet</p>
|
|
<div class="flex flex-wrap gap-2 justify-center">
|
|
{{ range .Models }}
|
|
<div class="bg-[#101827] border border-[#38BDF8]/20 rounded px-2 py-1 flex items-center gap-2">
|
|
<i class="fas fa-brain text-xs text-[#38BDF8]"></i>
|
|
<span class="text-xs text-[#E5E7EB] font-medium">{{.}}</span>
|
|
</div>
|
|
{{end}}
|
|
</div>
|
|
</div>
|
|
{{end}}
|
|
</div>
|
|
</div>
|
|
{{ else }}
|
|
<!-- Models Table -->
|
|
{{ $modelsN := len .ModelsConfig}}
|
|
{{ $modelsN = add $modelsN (len .Models)}}
|
|
<div class="mb-6">
|
|
<h2 class="text-2xl font-semibold text-[#E5E7EB] mb-1 flex items-center">
|
|
<i class="fas fa-brain mr-2 text-[#38BDF8] text-sm"></i>
|
|
Installed Models
|
|
</h2>
|
|
<p class="text-sm text-[#94A3B8] mb-4">
|
|
<span class="text-[#38BDF8] font-medium">{{$modelsN}}</span> model{{if gt $modelsN 1}}s{{end}} ready to use
|
|
</p>
|
|
</div>
|
|
|
|
<div class="overflow-x-auto mb-8">
|
|
<table class="w-full border-collapse">
|
|
<thead>
|
|
<tr class="border-b border-[#1E293B]">
|
|
<th class="text-left p-2 text-xs font-semibold text-[#94A3B8]">Name</th>
|
|
<th class="text-left p-2 text-xs font-semibold text-[#94A3B8]">Status</th>
|
|
<th class="text-left p-2 text-xs font-semibold text-[#94A3B8]">Backend</th>
|
|
<th class="text-left p-2 text-xs font-semibold text-[#94A3B8]">Use Cases</th>
|
|
<th class="text-right p-2 text-xs font-semibold text-[#94A3B8]">Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{{$galleryConfig:=.GalleryConfig}}
|
|
{{ $loadedModels := .LoadedModels }}
|
|
|
|
{{ range .ModelsConfig }}
|
|
{{ $backendCfg := . }}
|
|
{{ $cfg:= index $galleryConfig .Name}}
|
|
<tr class="hover:bg-[#1E293B]/50 border-b border-[#1E293B] transition-colors">
|
|
<!-- Name Column -->
|
|
<td class="p-2">
|
|
<div class="flex items-center gap-2">
|
|
<div class="relative flex-shrink-0">
|
|
{{ if and $cfg $cfg.Icon }}
|
|
<img src="{{$cfg.Icon}}" class="w-4 h-4 object-contain" alt="{{.Name}} icon">
|
|
{{ else }}
|
|
<i class="fas fa-brain text-xs text-[#38BDF8]"></i>
|
|
{{ end }}
|
|
{{ if index $loadedModels .Name }}
|
|
<div class="absolute -top-0.5 -right-0.5 w-2 h-2 bg-green-500 rounded-full border border-[#1E293B]"></div>
|
|
{{ end }}
|
|
</div>
|
|
<span class="text-xs text-[#E5E7EB] font-medium truncate">{{.Name}}</span>
|
|
<a href="/models/edit/{{.Name}}"
|
|
class="text-[#38BDF8]/60 hover:text-[#38BDF8] hover:bg-[#38BDF8]/10 rounded p-0.5 transition-colors ml-1 flex-shrink-0"
|
|
title="Edit {{.Name}}">
|
|
<i class="fas fa-edit text-[10px]"></i>
|
|
</a>
|
|
</div>
|
|
</td>
|
|
|
|
<!-- Status Column -->
|
|
<td class="p-2">
|
|
<div class="flex flex-wrap gap-1">
|
|
{{ if index $loadedModels .Name }}
|
|
<span class="inline-flex items-center px-1.5 py-0.5 rounded text-[10px] font-medium bg-green-500/10 text-green-300">
|
|
<i class="fas fa-circle text-[8px] mr-1"></i>Running
|
|
</span>
|
|
{{ end }}
|
|
{{ if and $backendCfg (or (ne $backendCfg.MCP.Servers "") (ne $backendCfg.MCP.Stdio "")) }}
|
|
<span class="inline-flex items-center px-1.5 py-0.5 rounded text-[10px] font-medium bg-[#8B5CF6]/10 text-[#8B5CF6]">
|
|
<i class="fas fa-plug text-[8px] mr-1"></i>MCP
|
|
</span>
|
|
{{ end }}
|
|
</div>
|
|
</td>
|
|
|
|
<!-- Backend Column -->
|
|
<td class="p-2">
|
|
{{ if .Backend }}
|
|
<span class="inline-flex items-center px-1.5 py-0.5 rounded text-[10px] font-medium bg-[#38BDF8]/10 text-[#38BDF8]">
|
|
<i class="fas fa-cog text-[8px] mr-1"></i>{{.Backend}}
|
|
</span>
|
|
{{ else }}
|
|
<span class="inline-flex items-center px-1.5 py-0.5 rounded text-[10px] font-medium bg-yellow-500/10 text-yellow-300">
|
|
<i class="fas fa-magic text-[8px] mr-1"></i>Auto
|
|
</span>
|
|
{{ end }}
|
|
</td>
|
|
|
|
<!-- Use Cases Column -->
|
|
<td class="p-2">
|
|
<div class="flex flex-wrap gap-1">
|
|
{{ range .KnownUsecaseStrings }}
|
|
{{ if eq . "FLAG_CHAT" }}
|
|
<a href="chat/{{$backendCfg.Name}}" class="inline-flex items-center px-1.5 py-0.5 rounded text-[10px] font-medium bg-[#38BDF8]/10 text-[#38BDF8] hover:bg-[#38BDF8]/20 transition-colors" title="Chat">
|
|
<i class="fas fa-comment-alt text-[8px] mr-1"></i>Chat
|
|
</a>
|
|
{{ end }}
|
|
{{ if eq . "FLAG_IMAGE" }}
|
|
<a href="text2image/{{$backendCfg.Name}}" class="inline-flex items-center px-1.5 py-0.5 rounded text-[10px] font-medium bg-green-500/10 text-green-300 hover:bg-green-500/20 transition-colors" title="Image">
|
|
<i class="fas fa-image text-[8px] mr-1"></i>Image
|
|
</a>
|
|
{{ end }}
|
|
{{ if eq . "FLAG_TTS" }}
|
|
<a href="tts/{{$backendCfg.Name}}" class="inline-flex items-center px-1.5 py-0.5 rounded text-[10px] font-medium bg-[#8B5CF6]/10 text-[#8B5CF6] hover:bg-[#8B5CF6]/20 transition-colors" title="TTS">
|
|
<i class="fas fa-microphone text-[8px] mr-1"></i>TTS
|
|
</a>
|
|
{{ end }}
|
|
{{ end }}
|
|
</div>
|
|
</td>
|
|
|
|
<!-- Actions Column -->
|
|
<td class="p-2">
|
|
<div class="flex items-center justify-end gap-1">
|
|
{{ if index $loadedModels .Name }}
|
|
<button class="text-red-400/60 hover:text-red-400 hover:bg-red-500/10 rounded p-1 transition-colors"
|
|
onclick="handleStopModel('{{.Name}}')"
|
|
title="Stop {{.Name}}">
|
|
<i class="fas fa-stop text-xs"></i>
|
|
</button>
|
|
{{ end }}
|
|
<button class="text-red-400/60 hover:text-red-400 hover:bg-red-500/10 rounded p-1 transition-colors"
|
|
onclick="handleDeleteModel('{{.Name}}')"
|
|
title="Delete {{.Name}}">
|
|
<i class="fas fa-trash-alt text-xs"></i>
|
|
</button>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
{{ end }}
|
|
|
|
<!-- Models without config -->
|
|
{{ range .Models }}
|
|
<tr class="hover:bg-[#1E293B]/50 border-b border-[#1E293B] transition-colors">
|
|
<td class="p-2">
|
|
<div class="flex items-center gap-2">
|
|
<i class="fas fa-brain text-xs text-[#94A3B8]"></i>
|
|
<span class="text-xs text-[#E5E7EB] font-medium truncate">{{.}}</span>
|
|
</div>
|
|
</td>
|
|
<td class="p-2">
|
|
<span class="inline-flex items-center px-1.5 py-0.5 rounded text-[10px] font-medium bg-orange-500/10 text-orange-300">
|
|
<i class="fas fa-exclamation-triangle text-[8px] mr-1"></i>No Config
|
|
</span>
|
|
</td>
|
|
<td class="p-2">
|
|
<span class="inline-flex items-center px-1.5 py-0.5 rounded text-[10px] font-medium bg-yellow-500/10 text-yellow-300">
|
|
<i class="fas fa-magic text-[8px] mr-1"></i>Auto
|
|
</span>
|
|
</td>
|
|
<td class="p-2">
|
|
<span class="text-xs text-[#94A3B8]">—</span>
|
|
</td>
|
|
<td class="p-2">
|
|
<span class="text-xs text-[#94A3B8]">—</span>
|
|
</td>
|
|
</tr>
|
|
{{end}}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
{{ end }}
|
|
</div>
|
|
|
|
<!-- Backends Section -->
|
|
<div class="mt-8">
|
|
<div class="mb-6">
|
|
<h2 class="text-2xl font-semibold text-[#E5E7EB] mb-1 flex items-center">
|
|
<i class="fas fa-cogs mr-2 text-[#8B5CF6] text-sm"></i>
|
|
Installed Backends
|
|
</h2>
|
|
<p class="text-sm text-[#94A3B8] mb-4">
|
|
<span class="text-[#8B5CF6] font-medium">{{len .InstalledBackends}}</span> backend{{if gt (len .InstalledBackends) 1}}s{{end}} ready to use
|
|
</p>
|
|
</div>
|
|
|
|
{{ if eq (len .InstalledBackends) 0 }}
|
|
<!-- No backends state -->
|
|
<div class="bg-[#1E293B] border border-[#8B5CF6]/20 rounded-lg p-8">
|
|
<div class="text-center max-w-4xl mx-auto">
|
|
<div class="inline-flex items-center justify-center w-12 h-12 rounded-full bg-[#8B5CF6]/10 border border-[#8B5CF6]/20 mb-4">
|
|
<i class="text-[#8B5CF6] text-xl fas fa-cogs"></i>
|
|
</div>
|
|
<h2 class="text-2xl font-bold text-[#E5E7EB] mb-2">No backends installed yet</h2>
|
|
<p class="text-sm text-[#94A3B8] mb-6">Backends power your AI models. Install them from the backend gallery to get started</p>
|
|
|
|
<div class="flex flex-wrap justify-center gap-3">
|
|
<a href="/browse/backends" class="inline-flex items-center bg-[#8B5CF6] hover:bg-[#8B5CF6]/90 text-white py-2 px-4 rounded-lg text-sm font-medium transition-colors">
|
|
<i class="fas fa-cogs mr-2 text-xs"></i>
|
|
Browse Backend Gallery
|
|
</a>
|
|
<a href="https://localai.io/backends/" target="_blank" class="inline-flex items-center bg-[#1E293B] hover:bg-[#1E293B]/80 border border-[#8B5CF6]/20 text-[#E5E7EB] py-2 px-4 rounded-lg text-sm font-medium transition-colors">
|
|
<i class="fas fa-book mr-2 text-xs"></i>
|
|
Documentation
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{{ else }}
|
|
<!-- Backends Table -->
|
|
<div class="overflow-x-auto mb-8">
|
|
<table class="w-full border-collapse">
|
|
<thead>
|
|
<tr class="border-b border-[#1E293B]">
|
|
<th class="text-left p-2 text-xs font-semibold text-[#94A3B8]">Name</th>
|
|
<th class="text-left p-2 text-xs font-semibold text-[#94A3B8]">Type</th>
|
|
<th class="text-left p-2 text-xs font-semibold text-[#94A3B8]">Metadata</th>
|
|
<th class="text-right p-2 text-xs font-semibold text-[#94A3B8]">Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{{ range .InstalledBackends }}
|
|
<tr class="hover:bg-[#1E293B]/50 border-b border-[#1E293B] transition-colors">
|
|
<!-- Name Column -->
|
|
<td class="p-2">
|
|
<div class="flex items-center gap-2">
|
|
<i class="fas fa-cog text-xs text-[#8B5CF6]"></i>
|
|
<span class="text-xs text-[#E5E7EB] font-medium truncate">{{.Name}}</span>
|
|
</div>
|
|
</td>
|
|
|
|
<!-- Type Column -->
|
|
<td class="p-2">
|
|
<div class="flex flex-wrap gap-1">
|
|
{{ if .IsSystem }}
|
|
<span class="inline-flex items-center px-1.5 py-0.5 rounded text-[10px] font-medium bg-blue-500/10 text-blue-300">
|
|
<i class="fas fa-shield-alt text-[8px] mr-1"></i>System
|
|
</span>
|
|
{{ else }}
|
|
<span class="inline-flex items-center px-1.5 py-0.5 rounded text-[10px] font-medium bg-green-500/10 text-green-300">
|
|
<i class="fas fa-download text-[8px] mr-1"></i>User
|
|
</span>
|
|
{{ end }}
|
|
{{ if .IsMeta }}
|
|
<span class="inline-flex items-center px-1.5 py-0.5 rounded text-[10px] font-medium bg-[#8B5CF6]/10 text-[#8B5CF6]">
|
|
<i class="fas fa-layer-group text-[8px] mr-1"></i>Meta
|
|
</span>
|
|
{{ end }}
|
|
</div>
|
|
</td>
|
|
|
|
<!-- Metadata Column -->
|
|
<td class="p-2">
|
|
<div class="flex flex-col gap-1">
|
|
{{ if and .Metadata .Metadata.Alias }}
|
|
<span class="text-xs text-[#94A3B8]">
|
|
<i class="fas fa-tag text-[8px] mr-1"></i>Alias: <span class="text-[#E5E7EB]">{{.Metadata.Alias}}</span>
|
|
</span>
|
|
{{ end }}
|
|
{{ if and .Metadata .Metadata.MetaBackendFor }}
|
|
<span class="text-xs text-[#94A3B8]">
|
|
<i class="fas fa-link text-[8px] mr-1"></i>For: <span class="text-[#8B5CF6]">{{.Metadata.MetaBackendFor}}</span>
|
|
</span>
|
|
{{ end }}
|
|
{{ if and .Metadata .Metadata.InstalledAt }}
|
|
<span class="text-xs text-[#94A3B8]">
|
|
<i class="fas fa-calendar text-[8px] mr-1"></i>{{.Metadata.InstalledAt}}
|
|
</span>
|
|
{{ end }}
|
|
</div>
|
|
</td>
|
|
|
|
<!-- Actions Column -->
|
|
<td class="p-2">
|
|
<div class="flex items-center justify-end gap-1">
|
|
{{ if not .IsSystem }}
|
|
<button
|
|
@click="deleteBackend('{{.Name}}')"
|
|
class="text-red-400/60 hover:text-red-400 hover:bg-red-500/10 rounded p-1 transition-colors"
|
|
title="Delete {{.Name}}">
|
|
<i class="fas fa-trash-alt text-xs"></i>
|
|
</button>
|
|
{{ else }}
|
|
<span class="text-xs text-[#94A3B8]">—</span>
|
|
{{ end }}
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
{{end}}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
{{ end }}
|
|
</div>
|
|
</div>
|
|
|
|
{{template "views/partials/footer" .}}
|
|
</div>
|
|
|
|
<script>
|
|
// Alpine.js component for index dashboard
|
|
function indexDashboard() {
|
|
return {
|
|
notifications: [],
|
|
|
|
init() {
|
|
// Initialize component
|
|
},
|
|
|
|
addNotification(message, type = 'success') {
|
|
const id = Date.now();
|
|
this.notifications.push({ id, message, type });
|
|
// Auto-dismiss after 5 seconds
|
|
setTimeout(() => this.dismissNotification(id), 5000);
|
|
},
|
|
|
|
dismissNotification(id) {
|
|
this.notifications = this.notifications.filter(n => n.id !== id);
|
|
},
|
|
|
|
async deleteBackend(backendName) {
|
|
if (!confirm(`Are you sure you want to delete the backend "${backendName}"?`)) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const response = await fetch(`/api/backends/system/delete/${encodeURIComponent(backendName)}`, {
|
|
method: 'POST'
|
|
});
|
|
|
|
const data = await response.json();
|
|
|
|
if (response.ok && data.success) {
|
|
this.addNotification(`Backend "${backendName}" deleted successfully!`, 'success');
|
|
// Reload page after short delay
|
|
setTimeout(() => {
|
|
window.location.reload();
|
|
}, 1500);
|
|
} else {
|
|
this.addNotification(`Failed to delete backend: ${data.error || 'Unknown error'}`, 'error');
|
|
}
|
|
} catch (error) {
|
|
console.error('Error deleting backend:', error);
|
|
this.addNotification(`Failed to delete backend: ${error.message}`, 'error');
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
async function handleStopModel(modelName) {
|
|
if (!confirm('Are you sure you wish to stop this model?')) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const response = await fetch('/backend/shutdown', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify({ model: modelName })
|
|
});
|
|
|
|
if (response.ok) {
|
|
window.location.reload();
|
|
} else {
|
|
alert('Failed to stop model');
|
|
}
|
|
} catch (error) {
|
|
console.error('Error stopping model:', error);
|
|
alert('Failed to stop model');
|
|
}
|
|
}
|
|
|
|
async function handleDeleteModel(modelName) {
|
|
if (!confirm('Are you sure you wish to delete this model?')) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const response = await fetch(`/api/models/delete/${encodeURIComponent(modelName)}`, {
|
|
method: 'POST'
|
|
});
|
|
|
|
if (response.ok) {
|
|
window.location.reload();
|
|
} else {
|
|
alert('Failed to delete model');
|
|
}
|
|
} catch (error) {
|
|
console.error('Error deleting model:', error);
|
|
alert('Failed to delete model');
|
|
}
|
|
}
|
|
|
|
// Handle reload models button
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
const reloadBtn = document.getElementById('reload-models-btn');
|
|
if (reloadBtn) {
|
|
reloadBtn.addEventListener('click', function() {
|
|
const button = this;
|
|
const originalText = button.querySelector('span').textContent;
|
|
const icon = button.querySelector('i');
|
|
|
|
// Show loading state
|
|
button.disabled = true;
|
|
button.querySelector('span').textContent = 'Updating...';
|
|
icon.classList.add('fa-spin');
|
|
|
|
// Make the API call
|
|
fetch('/models/reload', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
}
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
// Show success state briefly
|
|
button.querySelector('span').textContent = 'Updated!';
|
|
icon.classList.remove('fa-spin', 'fa-sync-alt');
|
|
icon.classList.add('fa-check');
|
|
|
|
// Reload the page after a short delay
|
|
setTimeout(() => {
|
|
window.location.reload();
|
|
}, 1000);
|
|
} else {
|
|
// Show error state
|
|
button.querySelector('span').textContent = 'Error!';
|
|
icon.classList.remove('fa-spin');
|
|
console.error('Failed to reload models:', data.error);
|
|
|
|
// Reset button after delay
|
|
setTimeout(() => {
|
|
button.disabled = false;
|
|
button.querySelector('span').textContent = originalText;
|
|
icon.classList.remove('fa-check');
|
|
icon.classList.add('fa-sync-alt');
|
|
}, 3000);
|
|
}
|
|
})
|
|
.catch(error => {
|
|
// Show error state
|
|
button.querySelector('span').textContent = 'Error!';
|
|
icon.classList.remove('fa-spin');
|
|
console.error('Error reloading models:', error);
|
|
|
|
// Reset button after delay
|
|
setTimeout(() => {
|
|
button.disabled = false;
|
|
button.querySelector('span').textContent = originalText;
|
|
icon.classList.remove('fa-check');
|
|
icon.classList.add('fa-sync-alt');
|
|
}, 3000);
|
|
});
|
|
});
|
|
}
|
|
});
|
|
</script>
|
|
|
|
</body>
|
|
</html>
|
|
|