From 2582e2917969b09f5f409fb13328dc2981e4ece1 Mon Sep 17 00:00:00 2001 From: Ettore Di Giacinto Date: Tue, 30 Apr 2024 00:16:32 +0200 Subject: [PATCH 1/3] feat(webui): Add chat page Signed-off-by: Ettore Di Giacinto --- core/cli/run.go | 6 +- core/config/application_config.go | 6 +- core/http/app.go | 19 ++- core/http/routes/ui.go | 34 +++++ core/http/routes/welcome.go | 19 --- core/http/static/general.css | 63 ++++++++++ core/http/static/main.js | 158 +++++++++++++++++++++++ core/http/static/styles.css | 0 core/http/views/chat.html | 180 +++++++++++++++++++++++++++ core/http/views/partials/head.html | 74 ++++------- core/http/views/partials/navbar.html | 1 + 11 files changed, 485 insertions(+), 75 deletions(-) delete mode 100644 core/http/routes/welcome.go create mode 100644 core/http/static/general.css create mode 100644 core/http/static/main.js create mode 100644 core/http/static/styles.css create mode 100644 core/http/views/chat.html diff --git a/core/cli/run.go b/core/cli/run.go index 42185a28cd7..6185627d6c9 100644 --- a/core/cli/run.go +++ b/core/cli/run.go @@ -42,7 +42,7 @@ type RunCMD struct { CORSAllowOrigins string `env:"LOCALAI_CORS_ALLOW_ORIGINS,CORS_ALLOW_ORIGINS" group:"api"` UploadLimit int `env:"LOCALAI_UPLOAD_LIMIT,UPLOAD_LIMIT" default:"15" help:"Default upload-limit in MB" group:"api"` APIKeys []string `env:"LOCALAI_API_KEY,API_KEY" help:"List of API Keys to enable API authentication. When this is set, all the requests must be authenticated with one of these API keys" group:"api"` - DisableWelcome bool `env:"LOCALAI_DISABLE_WELCOME,DISABLE_WELCOME" default:"false" help:"Disable welcome pages" group:"api"` + DisableWebUI bool `env:"LOCALAI_DISABLE_WEBUI,DISABLE_WEBUI" default:"false" help:"Disable webui" group:"api"` ParallelRequests bool `env:"LOCALAI_PARALLEL_REQUESTS,PARALLEL_REQUESTS" help:"Enable backends to handle multiple requests in parallel if they support it (e.g.: llama.cpp or vllm)" group:"backends"` SingleActiveBackend bool `env:"LOCALAI_SINGLE_ACTIVE_BACKEND,SINGLE_ACTIVE_BACKEND" help:"Allow only one backend to be run at a time" group:"backends"` @@ -84,8 +84,8 @@ func (r *RunCMD) Run(ctx *Context) error { idleWatchDog := r.EnableWatchdogIdle busyWatchDog := r.EnableWatchdogBusy - if r.DisableWelcome { - opts = append(opts, config.DisableWelcomePage) + if r.DisableWebUI { + opts = append(opts, config.DisableWebUI) } if idleWatchDog || busyWatchDog { diff --git a/core/config/application_config.go b/core/config/application_config.go index 2d733c1eb02..398418adade 100644 --- a/core/config/application_config.go +++ b/core/config/application_config.go @@ -15,7 +15,7 @@ type ApplicationConfig struct { ConfigFile string ModelPath string UploadLimitMB, Threads, ContextSize int - DisableWelcomePage bool + DisableWebUI bool F16 bool Debug bool ImageDir string @@ -107,8 +107,8 @@ var EnableWatchDogBusyCheck = func(o *ApplicationConfig) { o.WatchDogBusy = true } -var DisableWelcomePage = func(o *ApplicationConfig) { - o.DisableWelcomePage = true +var DisableWebUI = func(o *ApplicationConfig) { + o.DisableWebUI = true } func SetWatchDogBusyTimeout(t time.Duration) AppOption { diff --git a/core/http/app.go b/core/http/app.go index 080535a4c25..19c9375f2ac 100644 --- a/core/http/app.go +++ b/core/http/app.go @@ -1,7 +1,9 @@ package http import ( + "embed" "errors" + "net/http" "strings" "github.com/go-skynet/LocalAI/pkg/utils" @@ -18,6 +20,7 @@ import ( "github.com/gofiber/contrib/fiberzerolog" "github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2/middleware/cors" + "github.com/gofiber/fiber/v2/middleware/filesystem" "github.com/gofiber/fiber/v2/middleware/recover" // swagger handler @@ -42,6 +45,11 @@ func readAuthHeader(c *fiber.Ctx) string { return authHeader } +// Embed a directory +// +//go:embed static/* +var embedDirStatic embed.FS + // @title LocalAI API // @version 2.0.0 // @description The LocalAI Rest API. @@ -169,10 +177,17 @@ func App(cl *config.BackendConfigLoader, ml *model.ModelLoader, appConfig *confi routes.RegisterElevenLabsRoutes(app, cl, ml, appConfig, auth) routes.RegisterLocalAIRoutes(app, cl, ml, appConfig, galleryService, auth) routes.RegisterOpenAIRoutes(app, cl, ml, appConfig, auth) - routes.RegisterPagesRoutes(app, cl, ml, appConfig, auth) - routes.RegisterUIRoutes(app, cl, ml, appConfig, galleryService, auth) + if !appConfig.DisableWebUI { + routes.RegisterUIRoutes(app, cl, ml, appConfig, galleryService, auth) + } routes.RegisterJINARoutes(app, cl, ml, appConfig, auth) + app.Use("/static", filesystem.New(filesystem.Config{ + Root: http.FS(embedDirStatic), + PathPrefix: "static", + Browse: true, + })) + // Define a custom 404 handler // Note: keep this at the bottom! app.Use(notFoundHandler) diff --git a/core/http/routes/ui.go b/core/http/routes/ui.go index 2b8c6b953d0..b6bd5b2626f 100644 --- a/core/http/routes/ui.go +++ b/core/http/routes/ui.go @@ -7,6 +7,7 @@ import ( "github.com/go-skynet/LocalAI/core/config" "github.com/go-skynet/LocalAI/core/http/elements" + "github.com/go-skynet/LocalAI/core/http/endpoints/localai" "github.com/go-skynet/LocalAI/core/services" "github.com/go-skynet/LocalAI/pkg/gallery" "github.com/go-skynet/LocalAI/pkg/model" @@ -23,6 +24,8 @@ 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]() @@ -166,4 +169,35 @@ func RegisterUIRoutes(app *fiber.App, return c.SendString(elements.DoneProgress(c.Params("uid"), displayText)) }) + + // Show the Chat page + app.Get("/chat/:model", auth, func(c *fiber.Ctx) error { + backendConfigs := cl.GetAllBackendConfigs() + + summary := fiber.Map{ + "Title": "LocalAI - Chat with " + c.Params("model"), + "ModelsConfig": backendConfigs, + "Model": c.Params("model"), + } + + // Render index + return c.Render("views/chat", summary) + }) + app.Get("/chat/", auth, func(c *fiber.Ctx) error { + + backendConfigs := cl.GetAllBackendConfigs() + + if len(backendConfigs) == 0 { + return c.SendString("No models available") + } + + summary := fiber.Map{ + "Title": "LocalAI - Chat with " + backendConfigs[0].Name, + "ModelsConfig": backendConfigs, + "Model": backendConfigs[0].Name, + } + + // Render index + return c.Render("views/chat", summary) + }) } diff --git a/core/http/routes/welcome.go b/core/http/routes/welcome.go deleted file mode 100644 index 6b600d2daf6..00000000000 --- a/core/http/routes/welcome.go +++ /dev/null @@ -1,19 +0,0 @@ -package routes - -import ( - "github.com/go-skynet/LocalAI/core/config" - "github.com/go-skynet/LocalAI/core/http/endpoints/localai" - "github.com/go-skynet/LocalAI/pkg/model" - "github.com/gofiber/fiber/v2" -) - -func RegisterPagesRoutes(app *fiber.App, - cl *config.BackendConfigLoader, - ml *model.ModelLoader, - appConfig *config.ApplicationConfig, - auth func(*fiber.Ctx) error) { - - if !appConfig.DisableWelcomePage { - app.Get("/", auth, localai.WelcomeEndpoint(appConfig, cl, ml)) - } -} diff --git a/core/http/static/general.css b/core/http/static/general.css new file mode 100644 index 00000000000..61774f14c84 --- /dev/null +++ b/core/http/static/general.css @@ -0,0 +1,63 @@ +body { + font-family: 'Inter', sans-serif; +} +.chat-container { height: 90vh; display: flex; flex-direction: column; } +.chat-messages { overflow-y: auto; flex-grow: 1; } +.htmx-indicator{ + opacity:0; + transition: opacity 10ms ease-in; +} +.htmx-request .htmx-indicator{ + opacity:1 +} +/* Loader (https://cssloaders.github.io/) */ +.loader { + width: 12px; + height: 12px; + border-radius: 50%; + display: block; + margin:15px auto; + position: relative; + color: #FFF; + box-sizing: border-box; + animation: animloader 2s linear infinite; +} + +@keyframes animloader { + 0% { box-shadow: 14px 0 0 -2px, 38px 0 0 -2px, -14px 0 0 -2px, -38px 0 0 -2px; } + 25% { box-shadow: 14px 0 0 -2px, 38px 0 0 -2px, -14px 0 0 -2px, -38px 0 0 2px; } + 50% { box-shadow: 14px 0 0 -2px, 38px 0 0 -2px, -14px 0 0 2px, -38px 0 0 -2px; } + 75% { box-shadow: 14px 0 0 2px, 38px 0 0 -2px, -14px 0 0 -2px, -38px 0 0 -2px; } + 100% { box-shadow: 14px 0 0 -2px, 38px 0 0 2px, -14px 0 0 -2px, -38px 0 0 -2px; } +} +.progress { + height: 20px; + margin-bottom: 20px; + overflow: hidden; + background-color: #f5f5f5; + border-radius: 4px; + box-shadow: inset 0 1px 2px rgba(0,0,0,.1); +} +.progress-bar { + float: left; + width: 0%; + height: 100%; + font-size: 12px; + line-height: 20px; + color: #fff; + text-align: center; + background-color: #337ab7; + -webkit-box-shadow: inset 0 -1px 0 rgba(0,0,0,.15); + box-shadow: inset 0 -1px 0 rgba(0,0,0,.15); + -webkit-transition: width .6s ease; + -o-transition: width .6s ease; + transition: width .6s ease; +} + +.user { + background-color: #007bff; +} + +.assistant { + background-color: #28a745; +} \ No newline at end of file diff --git a/core/http/static/main.js b/core/http/static/main.js new file mode 100644 index 00000000000..75bf56da1e9 --- /dev/null +++ b/core/http/static/main.js @@ -0,0 +1,158 @@ +/* + +https://github.com/david-haerer/chatapi + +MIT License + +Copyright (c) 2023 David Härer + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ +function submitKey(event) { + event.preventDefault(); + localStorage.setItem("key", document.getElementById("apiKey").value); + document.getElementById("apiKey").blur(); + } + + function submitPrompt(event) { + event.preventDefault(); + + const input = document.getElementById("input").value; + Alpine.store("chat").add("user", input); + document.getElementById("input").value = ""; + const key = localStorage.getItem("key"); + + if (input.startsWith("!img")) { + promptDallE(key, input.slice(4)); + } else { + promptGPT(key, input); + } + } + + async function promptDallE(key, input) { + const response = await fetch("/v1/images/generations", { + method: "POST", + headers: { + Authorization: `Bearer ${key}`, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + model: "dall-e-3", + prompt: input, + n: 1, + size: "1792x1024", + }), + }); + const json = await response.json(); + const url = json.data[0].url; + Alpine.store("chat").add("assistant", `![${input}](${url})`); + } + + async function promptGPT(key, input) { + const model = document.getElementById("chat-model").value; + // Set class "loader" to the element with "loader" id + //document.getElementById("loader").classList.add("loader"); + // Make the "loader" visible + document.getElementById("loader").style.display = "block"; + document.getElementById("input").disabled = true; + document.getElementById('messages').scrollIntoView(false) + + // Source: https://stackoverflow.com/a/75751803/11386095 + const response = await fetch("/v1/chat/completions", { + method: "POST", + headers: { + Authorization: `Bearer ${key}`, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + model: model, + messages: Alpine.store("chat").messages(), + stream: true, + }), + }); + + if (!response.ok) { + Alpine.store("chat").add( + "assistant", + `Error: POST /v1/chat/completions ${response.status}`, + ); + return; + } + + const reader = response.body + ?.pipeThrough(new TextDecoderStream()) + .getReader(); + + if (!reader) { + Alpine.store("chat").add( + "assistant", + `Error: Failed to decode API response`, + ); + return; + } + + while (true) { + const { value, done } = await reader.read(); + if (done) break; + let dataDone = false; + const arr = value.split("\n"); + arr.forEach((data) => { + if (data.length === 0) return; + if (data.startsWith(":")) return; + if (data === "data: [DONE]") { + dataDone = true; + return; + } + const token = JSON.parse(data.substring(6)).choices[0].delta.content; + if (!token) { + return; + } + hljs.highlightAll(); + Alpine.store("chat").add("assistant", token); + document.getElementById('messages').scrollIntoView(false) + }); + hljs.highlightAll(); + if (dataDone) break; + } + // Remove class "loader" from the element with "loader" id + //document.getElementById("loader").classList.remove("loader"); + document.getElementById("loader").style.display = "none"; + // enable input + document.getElementById("input").disabled = false; + // scroll to the bottom of the chat + document.getElementById('messages').scrollIntoView(false) + // set focus to the input + document.getElementById("input").focus(); + } + + document.getElementById("key").addEventListener("submit", submitKey); + document.getElementById("prompt").addEventListener("submit", submitPrompt); + document.getElementById("input").focus(); + + const storeKey = localStorage.getItem("key"); + if (storeKey) { + document.getElementById("apiKey").value = storeKey; + } + + marked.setOptions({ + highlight: function (code) { + return hljs.highlightAuto(code).value; + }, + }); diff --git a/core/http/static/styles.css b/core/http/static/styles.css new file mode 100644 index 00000000000..e69de29bb2d diff --git a/core/http/views/chat.html b/core/http/views/chat.html new file mode 100644 index 00000000000..59b2af34c5a --- /dev/null +++ b/core/http/views/chat.html @@ -0,0 +1,180 @@ + + + + {{template "views/partials/head" .}} + + +
+ + {{template "views/partials/navbar"}} +
+ +
+ +
+ +

Chat with {{.Model}}

+ +
+ + +
+ + + +
+
+ +
+

+ Start chatting with the AI by typing a prompt in the input field below. +

+
+ +
+
+ +
+ + +
+ +
+
+ + +
+ + diff --git a/core/http/views/partials/head.html b/core/http/views/partials/head.html index 9dbfecdbf16..dad73c79481 100644 --- a/core/http/views/partials/head.html +++ b/core/http/views/partials/head.html @@ -2,6 +2,32 @@ {{.Title}} + + + + + + + + + + + - \ No newline at end of file diff --git a/core/http/views/partials/navbar.html b/core/http/views/partials/navbar.html index 36332ed2b7f..3a4afdea03c 100644 --- a/core/http/views/partials/navbar.html +++ b/core/http/views/partials/navbar.html @@ -10,6 +10,7 @@ Home Documentation Models + Chat API
From 10cbd335a2ec560ddbe72792f0ea66d274c417fc Mon Sep 17 00:00:00 2001 From: mudler Date: Thu, 2 May 2024 00:44:43 +0200 Subject: [PATCH 2/3] feat(webui): Add image-gen page Signed-off-by: Ettore Di Giacinto --- core/http/routes/ui.go | 37 +++++++++++ core/http/static/{main.js => chat.js} | 51 +++++--------- core/http/static/general.css | 12 +++- core/http/static/image.js | 96 +++++++++++++++++++++++++++ core/http/static/styles.css | 0 core/http/views/chat.html | 21 ++++-- core/http/views/partials/head.html | 4 -- core/http/views/partials/navbar.html | 1 + core/http/views/text2image.html | 78 ++++++++++++++++++++++ 9 files changed, 254 insertions(+), 46 deletions(-) rename core/http/static/{main.js => chat.js} (82%) create mode 100644 core/http/static/image.js delete mode 100644 core/http/static/styles.css create mode 100644 core/http/views/text2image.html diff --git a/core/http/routes/ui.go b/core/http/routes/ui.go index b6bd5b2626f..c12f40f6505 100644 --- a/core/http/routes/ui.go +++ b/core/http/routes/ui.go @@ -9,6 +9,7 @@ import ( "github.com/go-skynet/LocalAI/core/http/elements" "github.com/go-skynet/LocalAI/core/http/endpoints/localai" "github.com/go-skynet/LocalAI/core/services" + "github.com/go-skynet/LocalAI/internal" "github.com/go-skynet/LocalAI/pkg/gallery" "github.com/go-skynet/LocalAI/pkg/model" "github.com/go-skynet/LocalAI/pkg/xsync" @@ -35,6 +36,7 @@ func RegisterUIRoutes(app *fiber.App, summary := fiber.Map{ "Title": "LocalAI - Models", + "Version": internal.PrintableVersion(), "Models": template.HTML(elements.ListModels(models, installingModels)), "Repositories": appConfig.Galleries, // "ApplicationConfig": appConfig, @@ -178,6 +180,7 @@ func RegisterUIRoutes(app *fiber.App, "Title": "LocalAI - Chat with " + c.Params("model"), "ModelsConfig": backendConfigs, "Model": c.Params("model"), + "Version": internal.PrintableVersion(), } // Render index @@ -195,9 +198,43 @@ func RegisterUIRoutes(app *fiber.App, "Title": "LocalAI - Chat with " + backendConfigs[0].Name, "ModelsConfig": backendConfigs, "Model": backendConfigs[0].Name, + "Version": internal.PrintableVersion(), } // Render index return c.Render("views/chat", summary) }) + + app.Get("/text2image/:model", auth, func(c *fiber.Ctx) error { + backendConfigs := cl.GetAllBackendConfigs() + + summary := fiber.Map{ + "Title": "LocalAI - Generate images with " + c.Params("model"), + "ModelsConfig": backendConfigs, + "Model": c.Params("model"), + "Version": internal.PrintableVersion(), + } + + // Render index + return c.Render("views/text2image", summary) + }) + + app.Get("/text2image/", auth, func(c *fiber.Ctx) error { + + backendConfigs := cl.GetAllBackendConfigs() + + if len(backendConfigs) == 0 { + return c.SendString("No models available") + } + + summary := fiber.Map{ + "Title": "LocalAI - Generate images with " + backendConfigs[0].Name, + "ModelsConfig": backendConfigs, + "Model": backendConfigs[0].Name, + "Version": internal.PrintableVersion(), + } + + // Render index + return c.Render("views/text2image", summary) + }) } diff --git a/core/http/static/main.js b/core/http/static/chat.js similarity index 82% rename from core/http/static/main.js rename to core/http/static/chat.js index 75bf56da1e9..48017d60b91 100644 --- a/core/http/static/main.js +++ b/core/http/static/chat.js @@ -5,6 +5,7 @@ https://github.com/david-haerer/chatapi MIT License Copyright (c) 2023 David Härer +Copyright (c) 2024 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 @@ -31,40 +32,22 @@ function submitKey(event) { document.getElementById("apiKey").blur(); } - function submitPrompt(event) { - event.preventDefault(); - - const input = document.getElementById("input").value; - Alpine.store("chat").add("user", input); - document.getElementById("input").value = ""; - const key = localStorage.getItem("key"); - - if (input.startsWith("!img")) { - promptDallE(key, input.slice(4)); - } else { - promptGPT(key, input); - } - } - - async function promptDallE(key, input) { - const response = await fetch("/v1/images/generations", { - method: "POST", - headers: { - Authorization: `Bearer ${key}`, - "Content-Type": "application/json", - }, - body: JSON.stringify({ - model: "dall-e-3", - prompt: input, - n: 1, - size: "1792x1024", - }), - }); - const json = await response.json(); - const url = json.data[0].url; - Alpine.store("chat").add("assistant", `![${input}](${url})`); +function submitPrompt(event) { + event.preventDefault(); + + const input = document.getElementById("input").value; + Alpine.store("chat").add("user", input); + document.getElementById("input").value = ""; + const key = localStorage.getItem("key"); + + if (input.startsWith("!img")) { + promptDallE(key, input.slice(4)); + } else { + promptGPT(key, input); } - +} + + async function promptGPT(key, input) { const model = document.getElementById("chat-model").value; // Set class "loader" to the element with "loader" id @@ -145,7 +128,7 @@ function submitKey(event) { document.getElementById("key").addEventListener("submit", submitKey); document.getElementById("prompt").addEventListener("submit", submitPrompt); document.getElementById("input").focus(); - + const storeKey = localStorage.getItem("key"); if (storeKey) { document.getElementById("apiKey").value = storeKey; diff --git a/core/http/static/general.css b/core/http/static/general.css index 61774f14c84..40d67fb41a6 100644 --- a/core/http/static/general.css +++ b/core/http/static/general.css @@ -60,4 +60,14 @@ body { .assistant { background-color: #28a745; -} \ No newline at end of file +} + +.message { + display: flex; + align-items: center; +} + +.user, .assistant { + flex-grow: 1; + margin: 0.5rem; +} diff --git a/core/http/static/image.js b/core/http/static/image.js new file mode 100644 index 00000000000..315bdda089b --- /dev/null +++ b/core/http/static/image.js @@ -0,0 +1,96 @@ +/* + +https://github.com/david-haerer/chatapi + +MIT License + +Copyright (c) 2023 David Härer +Copyright (c) 2024 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 +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ +function submitKey(event) { + event.preventDefault(); + localStorage.setItem("key", document.getElementById("apiKey").value); + document.getElementById("apiKey").blur(); + } + + +function genImage(event) { + event.preventDefault(); + const input = document.getElementById("input").value; + const key = localStorage.getItem("key"); + + promptDallE(key, input); + +} + +async function promptDallE(key, input) { + document.getElementById("loader").style.display = "block"; + document.getElementById("input").value = ""; + document.getElementById("input").disabled = true; + + const model = document.getElementById("image-model").value; + const response = await fetch("/v1/images/generations", { + method: "POST", + headers: { + Authorization: `Bearer ${key}`, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + model: model, + steps: 10, + prompt: input, + n: 1, + size: "512x512", + }), + }); + const json = await response.json(); + if (json.error) { + // Display error if there is one + var div = document.getElementById('result'); // Get the div by its ID + div.innerHTML = '

' + json.error.message + '

'; + return; + } + const url = json.data[0].url; + + var div = document.getElementById('result'); // Get the div by its ID + var img = document.createElement('img'); // Create a new img element + img.src = url; // Set the source of the image + img.alt = 'Generated image'; // Set the alt text of the image + + div.innerHTML = ''; // Clear the existing content of the div + div.appendChild(img); // Add the new img element to the div + + document.getElementById("loader").style.display = "none"; + document.getElementById("input").disabled = false; + document.getElementById("input").focus(); +} + +document.getElementById("key").addEventListener("submit", submitKey); +document.getElementById("input").focus(); +document.getElementById("genimage").addEventListener("submit", genImage); +document.getElementById("loader").style.display = "none"; + +const storeKey = localStorage.getItem("key"); +if (storeKey) { + document.getElementById("apiKey").value = storeKey; +} + diff --git a/core/http/static/styles.css b/core/http/static/styles.css deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/core/http/views/chat.html b/core/http/views/chat.html index 59b2af34c5a..17ba282256b 100644 --- a/core/http/views/chat.html +++ b/core/http/views/chat.html @@ -4,6 +4,7 @@ https://github.com/david-haerer/chatapi MIT License Copyright (c) 2023 David Härer + Copyright (c) 2024 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 @@ -27,6 +28,7 @@ {{template "views/partials/head" .}} +