mirror of https://github.com/mudler/LocalAI.git
WIP: image-gen page
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
This commit is contained in:
parent
4c71167608
commit
f1b92d9875
|
@ -9,6 +9,7 @@ import (
|
||||||
"github.com/go-skynet/LocalAI/core/http/elements"
|
"github.com/go-skynet/LocalAI/core/http/elements"
|
||||||
"github.com/go-skynet/LocalAI/core/http/endpoints/localai"
|
"github.com/go-skynet/LocalAI/core/http/endpoints/localai"
|
||||||
"github.com/go-skynet/LocalAI/core/services"
|
"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/gallery"
|
||||||
"github.com/go-skynet/LocalAI/pkg/model"
|
"github.com/go-skynet/LocalAI/pkg/model"
|
||||||
"github.com/go-skynet/LocalAI/pkg/xsync"
|
"github.com/go-skynet/LocalAI/pkg/xsync"
|
||||||
|
@ -35,6 +36,7 @@ func RegisterUIRoutes(app *fiber.App,
|
||||||
|
|
||||||
summary := fiber.Map{
|
summary := fiber.Map{
|
||||||
"Title": "LocalAI - Models",
|
"Title": "LocalAI - Models",
|
||||||
|
"Version": internal.PrintableVersion(),
|
||||||
"Models": template.HTML(elements.ListModels(models, installingModels)),
|
"Models": template.HTML(elements.ListModels(models, installingModels)),
|
||||||
"Repositories": appConfig.Galleries,
|
"Repositories": appConfig.Galleries,
|
||||||
// "ApplicationConfig": appConfig,
|
// "ApplicationConfig": appConfig,
|
||||||
|
@ -178,6 +180,7 @@ func RegisterUIRoutes(app *fiber.App,
|
||||||
"Title": "LocalAI - Chat with " + c.Params("model"),
|
"Title": "LocalAI - Chat with " + c.Params("model"),
|
||||||
"ModelsConfig": backendConfigs,
|
"ModelsConfig": backendConfigs,
|
||||||
"Model": c.Params("model"),
|
"Model": c.Params("model"),
|
||||||
|
"Version": internal.PrintableVersion(),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render index
|
// Render index
|
||||||
|
@ -195,9 +198,43 @@ func RegisterUIRoutes(app *fiber.App,
|
||||||
"Title": "LocalAI - Chat with " + backendConfigs[0].Name,
|
"Title": "LocalAI - Chat with " + backendConfigs[0].Name,
|
||||||
"ModelsConfig": backendConfigs,
|
"ModelsConfig": backendConfigs,
|
||||||
"Model": backendConfigs[0].Name,
|
"Model": backendConfigs[0].Name,
|
||||||
|
"Version": internal.PrintableVersion(),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render index
|
// Render index
|
||||||
return c.Render("views/chat", summary)
|
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)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ https://github.com/david-haerer/chatapi
|
||||||
MIT License
|
MIT License
|
||||||
|
|
||||||
Copyright (c) 2023 David Härer
|
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
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
@ -31,40 +32,22 @@ function submitKey(event) {
|
||||||
document.getElementById("apiKey").blur();
|
document.getElementById("apiKey").blur();
|
||||||
}
|
}
|
||||||
|
|
||||||
function submitPrompt(event) {
|
function submitPrompt(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
const input = document.getElementById("input").value;
|
const input = document.getElementById("input").value;
|
||||||
Alpine.store("chat").add("user", input);
|
Alpine.store("chat").add("user", input);
|
||||||
document.getElementById("input").value = "";
|
document.getElementById("input").value = "";
|
||||||
const key = localStorage.getItem("key");
|
const key = localStorage.getItem("key");
|
||||||
|
|
||||||
if (input.startsWith("!img")) {
|
if (input.startsWith("!img")) {
|
||||||
promptDallE(key, input.slice(4));
|
promptDallE(key, input.slice(4));
|
||||||
} else {
|
} else {
|
||||||
promptGPT(key, input);
|
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) {
|
async function promptGPT(key, input) {
|
||||||
const model = document.getElementById("chat-model").value;
|
const model = document.getElementById("chat-model").value;
|
||||||
// Set class "loader" to the element with "loader" id
|
// Set class "loader" to the element with "loader" id
|
||||||
|
@ -145,7 +128,7 @@ function submitKey(event) {
|
||||||
document.getElementById("key").addEventListener("submit", submitKey);
|
document.getElementById("key").addEventListener("submit", submitKey);
|
||||||
document.getElementById("prompt").addEventListener("submit", submitPrompt);
|
document.getElementById("prompt").addEventListener("submit", submitPrompt);
|
||||||
document.getElementById("input").focus();
|
document.getElementById("input").focus();
|
||||||
|
|
||||||
const storeKey = localStorage.getItem("key");
|
const storeKey = localStorage.getItem("key");
|
||||||
if (storeKey) {
|
if (storeKey) {
|
||||||
document.getElementById("apiKey").value = storeKey;
|
document.getElementById("apiKey").value = storeKey;
|
|
@ -60,4 +60,14 @@ body {
|
||||||
|
|
||||||
.assistant {
|
.assistant {
|
||||||
background-color: #28a745;
|
background-color: #28a745;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.message {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user, .assistant {
|
||||||
|
flex-grow: 1;
|
||||||
|
margin: 0.5rem;
|
||||||
|
}
|
||||||
|
|
|
@ -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 = '<p style="color:red;">' + json.error.message + '</p>';
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ Part of this page is based on the OpenAI Chatbot example by David Härer:
|
||||||
https://github.com/david-haerer/chatapi
|
https://github.com/david-haerer/chatapi
|
||||||
|
|
||||||
MIT License Copyright (c) 2023 David Härer
|
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
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
@ -27,6 +28,7 @@ SOFTWARE.
|
||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
{{template "views/partials/head" .}}
|
{{template "views/partials/head" .}}
|
||||||
|
<script defer src="/static/chat.js"></script>
|
||||||
<style>
|
<style>
|
||||||
body {
|
body {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
@ -101,13 +103,18 @@ SOFTWARE.
|
||||||
</p>
|
</p>
|
||||||
<div id="messages">
|
<div id="messages">
|
||||||
<template x-for="message in history">
|
<template x-for="message in history">
|
||||||
<div class="message">
|
<div class="message flex items-start space-x-2 my-2" >
|
||||||
<template x-if="message.role === 'user'">
|
<!--<img :src="message.role === 'user' ? '/path/to/user-icon.png' : '/path/to/bot-icon.png'" alt="" class="h-6 w-6">-->
|
||||||
<div class="p-2 my-2 rounded" :class="message.role" x-text="message.content"></div>
|
<i class="fa-solid h-8 w-8" :class="message.role === 'user' ? 'fa-user' : 'fa-robot'" ></i>
|
||||||
</template>
|
<div class="flex flex-col flex-1">
|
||||||
<template x-if="message.role === 'assistant'">
|
<span class="text-xs font-semibold text-gray-600" x-text="message.role === 'user' ? 'User' : 'Assistant ({{.Model}})'"></span>
|
||||||
<div class="p-2 my-2 rounded" :class="message.role" x-html="message.html"></div>
|
<template x-if="message.role === 'user'">
|
||||||
</template>
|
<div class="p-2 flex-1 rounded" :class="message.role" x-text="message.content"></div>
|
||||||
|
</template>
|
||||||
|
<template x-if="message.role === 'assistant'">
|
||||||
|
<div class="p-2 flex-1 rounded" :class="message.role" x-html="message.html"></div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -24,10 +24,6 @@
|
||||||
></script>
|
></script>
|
||||||
|
|
||||||
<link href="/static/general.css" rel="stylesheet" />
|
<link href="/static/general.css" rel="stylesheet" />
|
||||||
|
|
||||||
<link href="/static/styles.css" rel="stylesheet" />
|
|
||||||
<link href="/static/github-dark.css" rel="stylesheet" />
|
|
||||||
<script defer src="/static/main.js"></script>
|
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&family=Roboto:wght@400;500&display=swap" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&family=Roboto:wght@400;500&display=swap" rel="stylesheet">
|
||||||
<link
|
<link
|
||||||
href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700,900&display=swap"
|
href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700,900&display=swap"
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
<a href="https://localai.io" class="text-gray-400 hover:text-white px-3 py-2 rounded" target="_blank" ><i class="fas fa-book-reader pr-2"></i> Documentation</a>
|
<a href="https://localai.io" class="text-gray-400 hover:text-white px-3 py-2 rounded" target="_blank" ><i class="fas fa-book-reader pr-2"></i> Documentation</a>
|
||||||
<a href="/browse/" class="text-gray-400 hover:text-white px-3 py-2 rounded"><i class="fas fa-brain pr-2"></i> Models</a>
|
<a href="/browse/" class="text-gray-400 hover:text-white px-3 py-2 rounded"><i class="fas fa-brain pr-2"></i> Models</a>
|
||||||
<a href="/chat/" class="text-gray-400 hover:text-white px-3 py-2 rounded"><i class="fa-solid fa-comments pr-2"></i> Chat</a>
|
<a href="/chat/" class="text-gray-400 hover:text-white px-3 py-2 rounded"><i class="fa-solid fa-comments pr-2"></i> Chat</a>
|
||||||
|
<a href="/text2image/" class="text-gray-400 hover:text-white px-3 py-2 rounded"><i class="fas fa-image pr-2"></i> Generate images</a>
|
||||||
<a href="/swagger/" class="text-gray-400 hover:text-white px-3 py-2 rounded"><i class="fas fa-code pr-2"></i> API</a>
|
<a href="/swagger/" class="text-gray-400 hover:text-white px-3 py-2 rounded"><i class="fas fa-code pr-2"></i> API</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -0,0 +1,78 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
{{template "views/partials/head" .}}
|
||||||
|
<script defer src="/static/image.js"></script>
|
||||||
|
|
||||||
|
<body class="bg-gray-900 text-gray-200">
|
||||||
|
<div class="flex flex-col min-h-screen">
|
||||||
|
|
||||||
|
{{template "views/partials/navbar" .}}
|
||||||
|
<div class="container mx-auto px-4 flex-grow " x-data="{ component: 'menu' }">
|
||||||
|
|
||||||
|
<div class="mt-12">
|
||||||
|
<div class="text-center font-semibold text-gray-100">
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
|
||||||
|
<div x-show="component === 'menu'" id="menu">
|
||||||
|
<button @click="component = 'key'" title="Update API key"
|
||||||
|
class="m-2 float-right inline-block rounded bg-primary px-6 pb-2.5 mb-3 pt-2.5 text-xs font-medium uppercase leading-normal text-white shadow-primary-3 transition duration-150 ease-in-out hover:bg-primary-accent-300 hover:shadow-primary-2 focus:bg-primary-accent-300 focus:shadow-primary-2 focus:outline-none focus:ring-0 active:bg-primary-600 active:shadow-primary-2 dark:shadow-black/30 dark:hover:shadow-dark-strong dark:focus:shadow-dark-strong dark:active:shadow-dark-strong"
|
||||||
|
>Set API Key🔑</button>
|
||||||
|
</div>
|
||||||
|
<form x-show="component === 'key'" id="key">
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
id="apiKey"
|
||||||
|
name="apiKey"
|
||||||
|
placeholder="OpenAI API Key"
|
||||||
|
x-model.lazy="key"
|
||||||
|
/>
|
||||||
|
<button @click="component = 'menu'" type="submit" title="Save API key">
|
||||||
|
🔒
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<select x-data="{ link : '' }" x-model="link" x-init="$watch('link', value => window.location = link)"
|
||||||
|
class="bg-gray-800 text-white border border-gray-600 focus:border-blue-500 focus:ring focus:ring-blue-500 focus:ring-opacity-50 rounded-md shadow-sm p-2 appearance-none"
|
||||||
|
>
|
||||||
|
<!-- Options -->
|
||||||
|
<option value="" disabled class="text-gray-400" >Select a model</option>
|
||||||
|
{{ $model:=.Model}}
|
||||||
|
{{ range .ModelsConfig }}
|
||||||
|
{{ if eq .Name $model }}
|
||||||
|
<option value="/text2image/{{.Name}}" selected class="bg-gray-700 text-white">{{.Name}}</option>
|
||||||
|
{{ else }}
|
||||||
|
<option value="/text2image/{{.Name}}" class="bg-gray-700 text-white">{{.Name}}</option>
|
||||||
|
{{ end }}
|
||||||
|
{{ end }}
|
||||||
|
</select>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-12">
|
||||||
|
<input id="image-model" type="hidden" value="{{.Model}}">
|
||||||
|
<form id="genimage" action="/text2image/{{.Model}}" method="get">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="input"
|
||||||
|
name="input"
|
||||||
|
placeholder="Prompt…"
|
||||||
|
autocomplete="off"
|
||||||
|
class="p-2 border rounded w-full bg-gray-600 text-white placeholder-gray-300"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</form>
|
||||||
|
<div class="container max-w-screen-lg mx-auto mt-4 pb-10 flex justify-center">
|
||||||
|
<div id="loader" class="my-2 loader" ></div>
|
||||||
|
</div>
|
||||||
|
<div class="container max-w-screen-lg mx-auto mt-4 pb-10 flex justify-center">
|
||||||
|
<div id="result" class="mx-auto"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{template "views/partials/footer" .}}
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
Loading…
Reference in New Issue