mirror of https://github.com/mudler/LocalAI.git
Compare commits
2 Commits
9680a16bc6
...
b8febc14fb
Author | SHA1 | Date |
---|---|---|
mudler | b8febc14fb | |
mudler | 7b18f4edf1 |
|
@ -6,6 +6,7 @@ import (
|
|||
|
||||
"github.com/chasefleming/elem-go"
|
||||
"github.com/chasefleming/elem-go/attrs"
|
||||
"github.com/go-skynet/LocalAI/core/services"
|
||||
"github.com/go-skynet/LocalAI/pkg/gallery"
|
||||
"github.com/go-skynet/LocalAI/pkg/xsync"
|
||||
)
|
||||
|
@ -72,12 +73,13 @@ func StartProgressBar(uid, progress, text string) string {
|
|||
if progress == "" {
|
||||
progress = "0"
|
||||
}
|
||||
return elem.Div(attrs.Props{
|
||||
"hx-trigger": "done",
|
||||
"hx-get": "/browse/job/" + uid,
|
||||
"hx-swap": "innerHTML",
|
||||
"hx-target": "this",
|
||||
},
|
||||
return elem.Div(
|
||||
attrs.Props{
|
||||
"hx-trigger": "done",
|
||||
"hx-get": "/browse/job/" + uid,
|
||||
"hx-swap": "innerHTML",
|
||||
"hx-target": "this",
|
||||
},
|
||||
elem.H3(
|
||||
attrs.Props{
|
||||
"role": "status",
|
||||
|
@ -223,7 +225,7 @@ func deleteButton(modelName string) elem.Node {
|
|||
)
|
||||
}
|
||||
|
||||
func ListModels(models []*gallery.GalleryModel, installing *xsync.SyncedMap[string, string]) string {
|
||||
func ListModels(models []*gallery.GalleryModel, processing *xsync.SyncedMap[string, string], galleryService *services.GalleryService) string {
|
||||
//StartProgressBar(uid, "0")
|
||||
modelsElements := []elem.Node{}
|
||||
// span := func(s string) elem.Node {
|
||||
|
@ -258,7 +260,15 @@ func ListModels(models []*gallery.GalleryModel, installing *xsync.SyncedMap[stri
|
|||
|
||||
actionDiv := func(m *gallery.GalleryModel) elem.Node {
|
||||
galleryID := fmt.Sprintf("%s@%s", m.Gallery.Name, m.Name)
|
||||
currentlyInstalling := installing.Exists(galleryID)
|
||||
currentlyProcessing := processing.Exists(galleryID)
|
||||
isDeletionOp := false
|
||||
if currentlyProcessing {
|
||||
status := galleryService.GetStatus(galleryID)
|
||||
if status != nil && status.Deletion {
|
||||
isDeletionOp = true
|
||||
}
|
||||
// if status == nil : "Waiting"
|
||||
}
|
||||
|
||||
nodes := []elem.Node{
|
||||
cardSpan("Repository: "+m.Gallery.Name, "fa-brands fa-git-alt"),
|
||||
|
@ -292,6 +302,11 @@ func ListModels(models []*gallery.GalleryModel, installing *xsync.SyncedMap[stri
|
|||
)
|
||||
}
|
||||
|
||||
progressMessage := "Installation"
|
||||
if isDeletionOp {
|
||||
progressMessage = "Deletion"
|
||||
}
|
||||
|
||||
return elem.Div(
|
||||
attrs.Props{
|
||||
"class": "px-6 pt-4 pb-2",
|
||||
|
@ -303,9 +318,9 @@ func ListModels(models []*gallery.GalleryModel, installing *xsync.SyncedMap[stri
|
|||
nodes...,
|
||||
),
|
||||
elem.If(
|
||||
currentlyInstalling,
|
||||
currentlyProcessing,
|
||||
elem.Node( // If currently installing, show progress bar
|
||||
elem.Raw(StartProgressBar(installing.Get(galleryID), "0", "Installing")),
|
||||
elem.Raw(StartProgressBar(processing.Get(galleryID), "0", progressMessage)),
|
||||
), // Otherwise, show install button (if not installed) or display "Installed"
|
||||
elem.If(m.Installed,
|
||||
elem.Node(elem.Div(
|
||||
|
|
|
@ -9,7 +9,7 @@ import (
|
|||
)
|
||||
|
||||
func WelcomeEndpoint(appConfig *config.ApplicationConfig,
|
||||
cl *config.BackendConfigLoader, ml *model.ModelLoader) func(*fiber.Ctx) error {
|
||||
cl *config.BackendConfigLoader, ml *model.ModelLoader, modelStatus func() (map[string]string, map[string]string)) func(*fiber.Ctx) error {
|
||||
return func(c *fiber.Ctx) error {
|
||||
models, _ := ml.ListModels()
|
||||
backendConfigs := cl.GetAllBackendConfigs()
|
||||
|
@ -24,6 +24,9 @@ func WelcomeEndpoint(appConfig *config.ApplicationConfig,
|
|||
galleryConfigs[m.Name] = cfg
|
||||
}
|
||||
|
||||
// Get model statuses to display in the UI the operation in progress
|
||||
processingModels, taskTypes := modelStatus()
|
||||
|
||||
summary := fiber.Map{
|
||||
"Title": "LocalAI API - " + internal.PrintableVersion(),
|
||||
"Version": internal.PrintableVersion(),
|
||||
|
@ -31,6 +34,8 @@ func WelcomeEndpoint(appConfig *config.ApplicationConfig,
|
|||
"ModelsConfig": backendConfigs,
|
||||
"GalleryConfig": galleryConfigs,
|
||||
"ApplicationConfig": appConfig,
|
||||
"ProcessingModels": processingModels,
|
||||
"TaskTypes": taskTypes,
|
||||
}
|
||||
|
||||
if string(c.Context().Request.Header.ContentType()) == "application/json" || len(c.Accepts("html")) == 0 {
|
||||
|
|
|
@ -26,10 +26,30 @@ func RegisterUIRoutes(app *fiber.App,
|
|||
galleryService *services.GalleryService,
|
||||
auth func(*fiber.Ctx) error) {
|
||||
|
||||
app.Get("/", auth, localai.WelcomeEndpoint(appConfig, cl, ml))
|
||||
|
||||
// keeps the state of models that are being installed from the UI
|
||||
var installingModels = xsync.NewSyncedMap[string, string]()
|
||||
var processingModels = xsync.NewSyncedMap[string, string]()
|
||||
|
||||
// modelStatus returns the current status of the models being processed (installation or deletion)
|
||||
// it is called asynchonously from the UI
|
||||
modelStatus := func() (map[string]string, map[string]string) {
|
||||
processingModelsData := processingModels.Map()
|
||||
|
||||
taskTypes := map[string]string{}
|
||||
|
||||
for k, v := range processingModelsData {
|
||||
status := galleryService.GetStatus(v)
|
||||
taskTypes[k] = "Installation"
|
||||
if status != nil && status.Deletion {
|
||||
taskTypes[k] = "Deletion"
|
||||
} else if status == nil {
|
||||
taskTypes[k] = "Waiting"
|
||||
}
|
||||
}
|
||||
|
||||
return processingModelsData, taskTypes
|
||||
}
|
||||
|
||||
app.Get("/", auth, localai.WelcomeEndpoint(appConfig, cl, ml, modelStatus))
|
||||
|
||||
// Show the Models page (all models)
|
||||
app.Get("/browse", auth, func(c *fiber.Ctx) error {
|
||||
|
@ -54,12 +74,17 @@ func RegisterUIRoutes(app *fiber.App,
|
|||
models = gallery.GalleryModels(models).Search(term)
|
||||
}
|
||||
|
||||
// Get model statuses
|
||||
processingModelsData, taskTypes := modelStatus()
|
||||
|
||||
summary := fiber.Map{
|
||||
"Title": "LocalAI - Models",
|
||||
"Version": internal.PrintableVersion(),
|
||||
"Models": template.HTML(elements.ListModels(models, installingModels)),
|
||||
"Repositories": appConfig.Galleries,
|
||||
"AllTags": tags,
|
||||
"Title": "LocalAI - Models",
|
||||
"Version": internal.PrintableVersion(),
|
||||
"Models": template.HTML(elements.ListModels(models, processingModels, galleryService)),
|
||||
"Repositories": appConfig.Galleries,
|
||||
"AllTags": tags,
|
||||
"ProcessingModels": processingModelsData,
|
||||
"TaskTypes": taskTypes,
|
||||
// "ApplicationConfig": appConfig,
|
||||
}
|
||||
|
||||
|
@ -79,7 +104,7 @@ func RegisterUIRoutes(app *fiber.App,
|
|||
|
||||
models, _ := gallery.AvailableGalleryModels(appConfig.Galleries, appConfig.ModelPath)
|
||||
|
||||
return c.SendString(elements.ListModels(gallery.GalleryModels(models).Search(form.Search), installingModels))
|
||||
return c.SendString(elements.ListModels(gallery.GalleryModels(models).Search(form.Search), processingModels, galleryService))
|
||||
})
|
||||
|
||||
/*
|
||||
|
@ -100,7 +125,7 @@ func RegisterUIRoutes(app *fiber.App,
|
|||
|
||||
uid := id.String()
|
||||
|
||||
installingModels.Set(galleryID, uid)
|
||||
processingModels.Set(galleryID, uid)
|
||||
|
||||
op := gallery.GalleryOp{
|
||||
Id: uid,
|
||||
|
@ -126,7 +151,7 @@ func RegisterUIRoutes(app *fiber.App,
|
|||
|
||||
uid := id.String()
|
||||
|
||||
installingModels.Set(galleryID, uid)
|
||||
processingModels.Set(galleryID, uid)
|
||||
|
||||
op := gallery.GalleryOp{
|
||||
Id: uid,
|
||||
|
@ -171,10 +196,10 @@ func RegisterUIRoutes(app *fiber.App,
|
|||
status := galleryService.GetStatus(c.Params("uid"))
|
||||
|
||||
galleryID := ""
|
||||
for _, k := range installingModels.Keys() {
|
||||
if installingModels.Get(k) == c.Params("uid") {
|
||||
for _, k := range processingModels.Keys() {
|
||||
if processingModels.Get(k) == c.Params("uid") {
|
||||
galleryID = k
|
||||
installingModels.Delete(k)
|
||||
processingModels.Delete(k)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -217,14 +217,18 @@ function readInputImage() {
|
|||
document.getElementById("input").focus();
|
||||
document.getElementById("input_image").addEventListener("change", readInputImage);
|
||||
|
||||
const storeKey = localStorage.getItem("key");
|
||||
storeKey = localStorage.getItem("key");
|
||||
if (storeKey) {
|
||||
document.getElementById("apiKey").value = storeKey;
|
||||
} else {
|
||||
document.getElementById("apiKey").value = null;
|
||||
}
|
||||
|
||||
const storesystemPrompt = localStorage.getItem("system_prompt");
|
||||
storesystemPrompt = localStorage.getItem("system_prompt");
|
||||
if (storesystemPrompt) {
|
||||
document.getElementById("systemPrompt").value = storesystemPrompt;
|
||||
} else {
|
||||
document.getElementById("systemPrompt").value = null;
|
||||
}
|
||||
|
||||
marked.setOptions({
|
||||
|
|
|
@ -10,8 +10,6 @@
|
|||
<div class="container mx-auto px-4 flex-grow">
|
||||
<div class="header text-center py-12">
|
||||
<h1 class="text-5xl font-bold text-gray-100">Welcome to <i>your</i> LocalAI instance!</h1>
|
||||
<div class="mt-6">
|
||||
</div>
|
||||
<p class="mt-4 text-lg">The FOSS alternative to OpenAI, Claude, ...</p>
|
||||
<a href="https://localai.io" target="_blank" class="mt-4 inline-block bg-blue-500 text-white py-2 px-4 rounded-lg shadow transition duration-300 ease-in-out hover:bg-blue-700 hover:shadow-lg">
|
||||
<i class="fas fa-book-reader pr-2"></i>Documentation
|
||||
|
@ -19,11 +17,35 @@
|
|||
</div>
|
||||
|
||||
<div class="models mt-4">
|
||||
|
||||
<!-- Show in progress operations-->
|
||||
{{ if .ProcessingModels }}
|
||||
<h3
|
||||
class="mt-4 mb-4 text-center text-3xl font-semibold text-gray-100">Operations in progress</h2>
|
||||
{{end}}
|
||||
{{$taskType:=.TaskTypes}}
|
||||
{{ range $key,$value:=.ProcessingModels }}
|
||||
{{ $op := index $taskType $key}}
|
||||
{{$parts := split "@" $key}}
|
||||
<div class="flex items-center justify-between bg-slate-600 p-2 mb-2 rounded-md">
|
||||
<div class="flex items center">
|
||||
<span class="text-gray-300"><a href="/browse?term={{$parts._1}}"
|
||||
class="text-white-500 inline-block bg-blue-200 rounded-full px-3 py-1 text-sm font-semibold text-gray-700 mr-2 mb-2 hover:bg-gray-300 hover:shadow-gray-2"
|
||||
>{{$parts._1}}</a> (from the '{{$parts._0}}' repository)</span>
|
||||
</div>
|
||||
<div hx-get="/browse/job/{{$value}}" hx-swap="innerHTML" hx-target="this" hx-trigger="done">
|
||||
<h3 role="status" id="pblabel" >{{$op}}
|
||||
<div hx-get="/browse/job/progress/{{$value}}" hx-trigger="every 600ms" hx-target="this"
|
||||
hx-swap= "innerHTML" ></div></h3>
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
<!-- END Show in progress operations-->
|
||||
|
||||
{{ if eq (len .ModelsConfig) 0 }}
|
||||
<h2 class="text-center text-3xl font-semibold text-gray-100"> <i class="text-yellow-200 ml-2 fa-solid fa-triangle-exclamation animate-pulse"></i> Ouch! seems you don't have any models installed!</h2>
|
||||
<p class="text-center mt-4 text-xl">..install something from the <a class="text-gray-400 hover:text-white ml-1 px-3 py-2 rounded" href="/browse">🖼️ Gallery</a> or check the <a href="https://localai.io/basics/getting_started/" class="text-gray-400 hover:text-white ml-1 px-3 py-2 rounded"> <i class="fa-solid fa-book"></i> Getting started documentation </a></p>
|
||||
{{ else }}
|
||||
|
||||
<h2 class="text-center text-3xl font-semibold text-gray-100">Installed models</h2>
|
||||
<p class="text-center mt-4 text-xl">We have {{len .ModelsConfig}} pre-loaded models available.</p>
|
||||
<table class="table-auto mt-4 w-full text-left text-gray-200">
|
||||
|
@ -79,6 +101,9 @@
|
|||
</tbody>
|
||||
</table>
|
||||
{{ end }}
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -63,8 +63,33 @@
|
|||
{{ end }}
|
||||
</div>
|
||||
|
||||
|
||||
<span class="htmx-indicator loader"></span>
|
||||
<input class="form-control appearance-none block w-full px-3 py-2 text-base font-normal text-gray-300 pb-2 mb-5 bg-gray-800 bg-clip-padding border border-solid border-gray-600 rounded transition ease-in-out m-0 focus:text-gray-300 focus:bg-gray-900 focus:border-blue-500 focus:outline-none" type="search"
|
||||
<!-- Show in progress operations-->
|
||||
{{ if .ProcessingModels }}
|
||||
<h2
|
||||
class="mt-4 mb-4 text-center text-3xl font-semibold text-gray-100">Operations in progress</h2>
|
||||
{{end}}
|
||||
{{$taskType:=.TaskTypes}}
|
||||
{{ range $key,$value:=.ProcessingModels }}
|
||||
{{ $op := index $taskType $key}}
|
||||
{{$parts := split "@" $key}}
|
||||
<div class="flex items-center justify-between bg-slate-600 p-2 mb-2 rounded-md">
|
||||
<div class="flex items center">
|
||||
<span class="text-gray-300"><a href="/browse?term={{$parts._1}}"
|
||||
class="text-white-500 inline-block bg-blue-200 rounded-full px-3 py-1 text-sm font-semibold text-gray-700 mr-2 mb-2 hover:bg-gray-300 hover:shadow-gray-2"
|
||||
>{{$parts._1}}</a> (from the '{{$parts._0}}' repository)</span>
|
||||
</div>
|
||||
<div hx-get="/browse/job/{{$value}}" hx-swap="innerHTML" hx-target="this" hx-trigger="done">
|
||||
<h3 role="status" id="pblabel" >{{$op}}
|
||||
<div hx-get="/browse/job/progress/{{$value}}" hx-trigger="every 600ms" hx-target="this"
|
||||
hx-swap= "innerHTML" ></div></h3>
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
<!-- END Show in progress operations-->
|
||||
|
||||
<input class="form-control appearance-none block w-full mt-5 px-3 py-2 text-base font-normal text-gray-300 pb-2 mb-5 bg-gray-800 bg-clip-padding border border-solid border-gray-600 rounded transition ease-in-out m-0 focus:text-gray-300 focus:bg-gray-900 focus:border-blue-500 focus:outline-none" type="search"
|
||||
name="search" placeholder="Begin Typing To Search models..."
|
||||
hx-post="/browse/search/models"
|
||||
hx-trigger="input changed delay:500ms, search"
|
||||
|
|
|
@ -15,6 +15,12 @@ func NewSyncedMap[K comparable, V any]() *SyncedMap[K, V] {
|
|||
}
|
||||
}
|
||||
|
||||
func (m *SyncedMap[K, V]) Map() map[K]V {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
return m.m
|
||||
}
|
||||
|
||||
func (m *SyncedMap[K, V]) Get(key K) V {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
|
|
Loading…
Reference in New Issue