From c87870b18e4c1ece8be123dcd69786b0ce985806 Mon Sep 17 00:00:00 2001 From: Ettore Di Giacinto Date: Wed, 26 Feb 2025 18:27:18 +0100 Subject: [PATCH] feat(ui): improve chat interface (#4910) * feat(ui): show more informations in the chat view, minor adjustments to model gallery Signed-off-by: Ettore Di Giacinto * fix(ui): UI improvements Visual improvements and bugfixes including: - disable pagination during search - fix scrolling on new message Signed-off-by: Ettore Di Giacinto --------- Signed-off-by: Ettore Di Giacinto --- core/gallery/gallery.go | 2 + core/http/routes/ui.go | 21 ++ core/http/static/chat.js | 7 +- core/http/views/chat.html | 540 +++++++++++++++++++++++------------- core/http/views/models.html | 55 +++- 5 files changed, 421 insertions(+), 204 deletions(-) diff --git a/core/gallery/gallery.go b/core/gallery/gallery.go index a3a1d909..0c540052 100644 --- a/core/gallery/gallery.go +++ b/core/gallery/gallery.go @@ -29,6 +29,8 @@ func InstallModelFromGallery(galleries []config.Gallery, name string, basePath s if err != nil { return err } + config.Description = model.Description + config.License = model.License } else if len(model.ConfigFile) > 0 { // TODO: is this worse than using the override method with a blank cfg yaml? reYamlConfig, err := yaml.Marshal(model.ConfigFile) diff --git a/core/http/routes/ui.go b/core/http/routes/ui.go index 40919a83..65d1b09c 100644 --- a/core/http/routes/ui.go +++ b/core/http/routes/ui.go @@ -404,6 +404,15 @@ func RegisterUIRoutes(app *fiber.App, return c.Redirect(utils.BaseURL(c)) } modelThatCanBeUsed := "" + galleryConfigs := map[string]*gallery.Config{} + + for _, m := range backendConfigs { + cfg, err := gallery.GetLocalModelConfiguration(ml.ModelPath, m.Name) + if err != nil { + continue + } + galleryConfigs[m.Name] = cfg + } title := "LocalAI - Chat" @@ -419,6 +428,7 @@ func RegisterUIRoutes(app *fiber.App, "Title": title, "BaseURL": utils.BaseURL(c), "ModelsWithoutConfig": modelsWithoutConfig, + "GalleryConfig": galleryConfigs, "ModelsConfig": backendConfigs, "Model": modelThatCanBeUsed, "Version": internal.PrintableVersion(), @@ -434,10 +444,21 @@ func RegisterUIRoutes(app *fiber.App, backendConfigs := cl.GetAllBackendConfigs() modelsWithoutConfig, _ := services.ListModels(cl, ml, config.NoFilterFn, services.LOOSE_ONLY) + galleryConfigs := map[string]*gallery.Config{} + + for _, m := range backendConfigs { + cfg, err := gallery.GetLocalModelConfiguration(ml.ModelPath, m.Name) + if err != nil { + continue + } + galleryConfigs[m.Name] = cfg + } + summary := fiber.Map{ "Title": "LocalAI - Chat with " + c.Params("model"), "BaseURL": utils.BaseURL(c), "ModelsConfig": backendConfigs, + "GalleryConfig": galleryConfigs, "ModelsWithoutConfig": modelsWithoutConfig, "Model": c.Params("model"), "Version": internal.PrintableVersion(), diff --git a/core/http/static/chat.js b/core/http/static/chat.js index 67e0bb60..ac1e0ba8 100644 --- a/core/http/static/chat.js +++ b/core/http/static/chat.js @@ -49,7 +49,7 @@ function submitPrompt(event) { document.getElementById("input").value = ""; const key = localStorage.getItem("key"); const systemPrompt = localStorage.getItem("system_prompt"); - + Alpine.nextTick(() => { document.getElementById('messages').scrollIntoView(false); }); promptGPT(systemPrompt, key, input); } @@ -74,7 +74,6 @@ function readInputImage() { // Make the "loader" visible document.getElementById("loader").style.display = "block"; document.getElementById("input").disabled = true; - document.getElementById('messages').scrollIntoView(false) messages = Alpine.store("chat").messages(); @@ -181,8 +180,8 @@ function readInputImage() { const chatStore = Alpine.store("chat"); chatStore.add("assistant", token); // Efficiently scroll into view without triggering multiple reflows - const messages = document.getElementById('messages'); - messages.scrollTop = messages.scrollHeight; + // const messages = document.getElementById('messages'); + // messages.scrollTop = messages.scrollHeight; }; let buffer = ""; diff --git a/core/http/views/chat.html b/core/http/views/chat.html index 71e9b8d6..59414fe4 100644 --- a/core/http/views/chat.html +++ b/core/http/views/chat.html @@ -4,7 +4,7 @@ Part of this page is based on the OpenAI Chatbot example by David Härer: https://github.com/david-haerer/chatapi MIT License Copyright (c) 2023 David Härer - Copyright (c) 2024 Ettore Di Giacinto + Copyright (c) 2024-2025 Ettore Di Giacinto Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -29,205 +29,355 @@ SOFTWARE. {{template "views/partials/head" .}} - - - + {{ $allGalleryConfigs:=.GalleryConfig }} + {{ $model:=.Model}} + {{template "views/partials/navbar" .}} - -
+ +
+ + + + +
+ + +
+ + + +
+ + {{ if $model }} + {{ $galleryConfig:= index $allGalleryConfigs $model}} + + {{ end }} +

+ Chat {{ if .Model }} with {{.Model}} {{ end }} +

+
+
+ + +
+

+ Start chatting with the AI by typing a prompt in the input field below and pressing Enter. + For models that support images, you can upload an image by clicking the paperclip + icon. +

+
+ +
+
+ + + +
+ + + +
+
+ + + + +
+
+
+ + + {{ if $model }} + {{ $galleryConfig:= index $allGalleryConfigs $model}} + {{ if $galleryConfig }} + + {{ end }} + {{ end }} diff --git a/core/http/views/models.html b/core/http/views/models.html index 9d1e8578..c3910bdc 100644 --- a/core/http/views/models.html +++ b/core/http/views/models.html @@ -6,6 +6,7 @@
{{template "views/partials/navbar" .}} + {{ $numModelsPerPage := 21 }}
@@ -20,38 +21,45 @@ 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" hx-target="#search-results" hx-vals='{"search": "tts"}' - hx-indicator=".htmx-indicator" >TTS + onclick="hidePagination()" + hx-indicator=".htmx-indicator" >TTS + onclick="hidePagination()" + hx-indicator=".htmx-indicator" >Image generation + onclick="hidePagination()" + hx-indicator=".htmx-indicator" >Text generation + onclick="hidePagination()" + hx-indicator=".htmx-indicator" >Multimodal + onclick="hidePagination()" + hx-indicator=".htmx-indicator" >Embeddings + onclick="hidePagination()" + hx-indicator=".htmx-indicator" >Rerankers + onclick="hidePagination()" + hx-indicator=".htmx-indicator" >Audio transcription
@@ -59,6 +67,7 @@ {{ range .AllTags }} {{ end }}
@@ -71,17 +80,21 @@ name="search" placeholder="Begin Typing To Search models..." hx-post="browse/search/models" hx-trigger="input changed delay:500ms, search" - hx-target="#search-results" + hx-target="#search-results" + oninput="hidePagination()" + onchange="hidePagination()" + onsearch="hidePagination()" hx-indicator=".htmx-indicator">
{{.Models}}
+ {{ if gt .AvailableModels $numModelsPerPage }} -
+
- - +
+ {{ end }}
{{template "views/partials/footer" .}}
+ + - + \ No newline at end of file