forked from open-webui/open-webui
feat: sd frontend integration
This commit is contained in:
parent
733e963c44
commit
cc50cc10e6
7 changed files with 218 additions and 29 deletions
|
@ -5,6 +5,8 @@ OLLAMA_API_BASE_URL='http://localhost:11434/api'
|
||||||
OPENAI_API_BASE_URL=''
|
OPENAI_API_BASE_URL=''
|
||||||
OPENAI_API_KEY=''
|
OPENAI_API_KEY=''
|
||||||
|
|
||||||
|
# AUTOMATIC1111_BASE_URL="http://localhost:7860"
|
||||||
|
|
||||||
# DO NOT TRACK
|
# DO NOT TRACK
|
||||||
SCARF_NO_ANALYTICS=true
|
SCARF_NO_ANALYTICS=true
|
||||||
DO_NOT_TRACK=true
|
DO_NOT_TRACK=true
|
|
@ -283,7 +283,7 @@ git clone https://github.com/open-webui/open-webui.git
|
||||||
cd open-webui/
|
cd open-webui/
|
||||||
|
|
||||||
# Copying required .env file
|
# Copying required .env file
|
||||||
cp -RPp example.env .env
|
cp -RPp .env.example .env
|
||||||
|
|
||||||
# Building Frontend Using Node
|
# Building Frontend Using Node
|
||||||
npm i
|
npm i
|
||||||
|
|
|
@ -33,7 +33,7 @@ app.add_middleware(
|
||||||
)
|
)
|
||||||
|
|
||||||
app.state.AUTOMATIC1111_BASE_URL = AUTOMATIC1111_BASE_URL
|
app.state.AUTOMATIC1111_BASE_URL = AUTOMATIC1111_BASE_URL
|
||||||
app.state.ENABLED = False
|
app.state.ENABLED = app.state.AUTOMATIC1111_BASE_URL != ""
|
||||||
|
|
||||||
|
|
||||||
@app.get("/enabled", response_model=bool)
|
@app.get("/enabled", response_model=bool)
|
||||||
|
@ -129,20 +129,33 @@ def generate_image(
|
||||||
form_data: GenerateImageForm,
|
form_data: GenerateImageForm,
|
||||||
user=Depends(get_current_user),
|
user=Depends(get_current_user),
|
||||||
):
|
):
|
||||||
if form_data.model:
|
|
||||||
set_model_handler(form_data.model)
|
|
||||||
|
|
||||||
width, height = tuple(map(int, form_data.size.split("x")))
|
print(form_data)
|
||||||
|
|
||||||
r = requests.get(
|
try:
|
||||||
url=f"{app.state.AUTOMATIC1111_BASE_URL}/sdapi/v1/txt2img",
|
if form_data.model:
|
||||||
json={
|
set_model_handler(form_data.model)
|
||||||
|
|
||||||
|
width, height = tuple(map(int, form_data.size.split("x")))
|
||||||
|
|
||||||
|
data = {
|
||||||
"prompt": form_data.prompt,
|
"prompt": form_data.prompt,
|
||||||
"negative_prompt": form_data.negative_prompt,
|
|
||||||
"batch_size": form_data.n,
|
"batch_size": form_data.n,
|
||||||
"width": width,
|
"width": width,
|
||||||
"height": height,
|
"height": height,
|
||||||
},
|
}
|
||||||
)
|
|
||||||
|
|
||||||
return r.json()
|
if form_data.negative_prompt != None:
|
||||||
|
data["negative_prompt"] = form_data.negative_prompt
|
||||||
|
|
||||||
|
print(data)
|
||||||
|
|
||||||
|
r = requests.post(
|
||||||
|
url=f"{app.state.AUTOMATIC1111_BASE_URL}/sdapi/v1/txt2img",
|
||||||
|
json=data,
|
||||||
|
)
|
||||||
|
|
||||||
|
return r.json()
|
||||||
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
|
raise HTTPException(status_code=r.status_code, detail=ERROR_MESSAGES.DEFAULT(e))
|
||||||
|
|
|
@ -1,5 +1,69 @@
|
||||||
import { IMAGES_API_BASE_URL } from '$lib/constants';
|
import { IMAGES_API_BASE_URL } from '$lib/constants';
|
||||||
|
|
||||||
|
export const getImageGenerationEnabledStatus = async (token: string = '') => {
|
||||||
|
let error = null;
|
||||||
|
|
||||||
|
const res = await fetch(`${IMAGES_API_BASE_URL}/enabled`, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
Accept: 'application/json',
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
...(token && { authorization: `Bearer ${token}` })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(async (res) => {
|
||||||
|
if (!res.ok) throw await res.json();
|
||||||
|
return res.json();
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.log(err);
|
||||||
|
if ('detail' in err) {
|
||||||
|
error = err.detail;
|
||||||
|
} else {
|
||||||
|
error = 'Server connection failed';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const toggleImageGenerationEnabledStatus = async (token: string = '') => {
|
||||||
|
let error = null;
|
||||||
|
|
||||||
|
const res = await fetch(`${IMAGES_API_BASE_URL}/enabled/toggle`, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
Accept: 'application/json',
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
...(token && { authorization: `Bearer ${token}` })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(async (res) => {
|
||||||
|
if (!res.ok) throw await res.json();
|
||||||
|
return res.json();
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.log(err);
|
||||||
|
if ('detail' in err) {
|
||||||
|
error = err.detail;
|
||||||
|
} else {
|
||||||
|
error = 'Server connection failed';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
};
|
||||||
|
|
||||||
export const getAUTOMATIC1111Url = async (token: string = '') => {
|
export const getAUTOMATIC1111Url = async (token: string = '') => {
|
||||||
let error = null;
|
let error = null;
|
||||||
|
|
||||||
|
@ -165,3 +229,38 @@ export const updateDefaultDiffusionModel = async (token: string = '', model: str
|
||||||
|
|
||||||
return res.model;
|
return res.model;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const imageGenerations = async (token: string = '', prompt: string) => {
|
||||||
|
let error = null;
|
||||||
|
|
||||||
|
const res = await fetch(`${IMAGES_API_BASE_URL}/generations`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
Accept: 'application/json',
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
...(token && { authorization: `Bearer ${token}` })
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
prompt: prompt
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.then(async (res) => {
|
||||||
|
if (!res.ok) throw await res.json();
|
||||||
|
return res.json();
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.log(err);
|
||||||
|
if ('detail' in err) {
|
||||||
|
error = err.detail;
|
||||||
|
} else {
|
||||||
|
error = 'Server connection failed';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
};
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
import ResponseMessage from './Messages/ResponseMessage.svelte';
|
import ResponseMessage from './Messages/ResponseMessage.svelte';
|
||||||
import Placeholder from './Messages/Placeholder.svelte';
|
import Placeholder from './Messages/Placeholder.svelte';
|
||||||
import Spinner from '../common/Spinner.svelte';
|
import Spinner from '../common/Spinner.svelte';
|
||||||
|
import { imageGenerations } from '$lib/apis/images';
|
||||||
|
|
||||||
export let chatId = '';
|
export let chatId = '';
|
||||||
export let sendPrompt: Function;
|
export let sendPrompt: Function;
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
|
|
||||||
import { synthesizeOpenAISpeech } from '$lib/apis/openai';
|
import { synthesizeOpenAISpeech } from '$lib/apis/openai';
|
||||||
import { extractSentences } from '$lib/utils';
|
import { extractSentences } from '$lib/utils';
|
||||||
|
import { imageGenerations } from '$lib/apis/images';
|
||||||
|
|
||||||
export let modelfiles = [];
|
export let modelfiles = [];
|
||||||
export let message;
|
export let message;
|
||||||
|
@ -43,6 +44,8 @@
|
||||||
|
|
||||||
let loadingSpeech = false;
|
let loadingSpeech = false;
|
||||||
|
|
||||||
|
let generatingImage = false;
|
||||||
|
|
||||||
$: tokens = marked.lexer(message.content);
|
$: tokens = marked.lexer(message.content);
|
||||||
|
|
||||||
const renderer = new marked.Renderer();
|
const renderer = new marked.Renderer();
|
||||||
|
@ -267,6 +270,21 @@
|
||||||
renderStyling();
|
renderStyling();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const generateImage = async (message) => {
|
||||||
|
generatingImage = true;
|
||||||
|
const res = await imageGenerations(localStorage.token, message.content);
|
||||||
|
console.log(res);
|
||||||
|
|
||||||
|
if (res) {
|
||||||
|
message.files = res.images.map((image) => ({
|
||||||
|
type: 'image',
|
||||||
|
url: `data:image/png;base64,${image}`
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
generatingImage = false;
|
||||||
|
};
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
await tick();
|
await tick();
|
||||||
renderStyling();
|
renderStyling();
|
||||||
|
@ -295,6 +313,18 @@
|
||||||
{#if message.content === ''}
|
{#if message.content === ''}
|
||||||
<Skeleton />
|
<Skeleton />
|
||||||
{:else}
|
{:else}
|
||||||
|
{#if message.files}
|
||||||
|
<div class="my-2.5 w-full flex overflow-x-auto gap-2 flex-wrap">
|
||||||
|
{#each message.files as file}
|
||||||
|
<div>
|
||||||
|
{#if file.type === 'image'}
|
||||||
|
<img src={file.url} alt="input" class=" max-h-96 rounded-lg" draggable="false" />
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="prose chat-{message.role} w-full max-w-full dark:prose-invert prose-headings:my-0 prose-p:m-0 prose-p:-mb-6 prose-pre:my-0 prose-table:my-0 prose-blockquote:my-0 prose-img:my-0 prose-ul:-my-4 prose-ol:-my-4 prose-li:-my-3 prose-ul:-mb-6 prose-ol:-mb-8 prose-li:-mb-4 whitespace-pre-line"
|
class="prose chat-{message.role} w-full max-w-full dark:prose-invert prose-headings:my-0 prose-p:m-0 prose-p:-mb-6 prose-pre:my-0 prose-table:my-0 prose-blockquote:my-0 prose-img:my-0 prose-ul:-my-4 prose-ol:-my-4 prose-li:-my-3 prose-ul:-mb-6 prose-ol:-mb-8 prose-li:-mb-4 whitespace-pre-line"
|
||||||
>
|
>
|
||||||
|
@ -601,23 +631,62 @@
|
||||||
? 'visible'
|
? 'visible'
|
||||||
: 'invisible group-hover:visible'} p-1 rounded dark:hover:text-white hover:text-black transition"
|
: 'invisible group-hover:visible'} p-1 rounded dark:hover:text-white hover:text-black transition"
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
// generateImage
|
if (!generatingImage) {
|
||||||
|
generateImage(message);
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<svg
|
{#if generatingImage}
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
<svg
|
||||||
fill="none"
|
class=" w-4 h-4"
|
||||||
viewBox="0 0 24 24"
|
fill="currentColor"
|
||||||
stroke-width="1.5"
|
viewBox="0 0 24 24"
|
||||||
stroke="currentColor"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
class="w-4 h-4"
|
><style>
|
||||||
>
|
.spinner_S1WN {
|
||||||
<path
|
animation: spinner_MGfb 0.8s linear infinite;
|
||||||
stroke-linecap="round"
|
animation-delay: -0.8s;
|
||||||
stroke-linejoin="round"
|
}
|
||||||
d="m2.25 15.75 5.159-5.159a2.25 2.25 0 0 1 3.182 0l5.159 5.159m-1.5-1.5 1.409-1.409a2.25 2.25 0 0 1 3.182 0l2.909 2.909m-18 3.75h16.5a1.5 1.5 0 0 0 1.5-1.5V6a1.5 1.5 0 0 0-1.5-1.5H3.75A1.5 1.5 0 0 0 2.25 6v12a1.5 1.5 0 0 0 1.5 1.5Zm10.5-11.25h.008v.008h-.008V8.25Zm.375 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Z"
|
.spinner_Km9P {
|
||||||
/>
|
animation-delay: -0.65s;
|
||||||
</svg>
|
}
|
||||||
|
.spinner_JApP {
|
||||||
|
animation-delay: -0.5s;
|
||||||
|
}
|
||||||
|
@keyframes spinner_MGfb {
|
||||||
|
93.75%,
|
||||||
|
100% {
|
||||||
|
opacity: 0.2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style><circle class="spinner_S1WN" cx="4" cy="12" r="3" /><circle
|
||||||
|
class="spinner_S1WN spinner_Km9P"
|
||||||
|
cx="12"
|
||||||
|
cy="12"
|
||||||
|
r="3"
|
||||||
|
/><circle
|
||||||
|
class="spinner_S1WN spinner_JApP"
|
||||||
|
cx="20"
|
||||||
|
cy="12"
|
||||||
|
r="3"
|
||||||
|
/></svg
|
||||||
|
>
|
||||||
|
{:else}
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke-width="1.5"
|
||||||
|
stroke="currentColor"
|
||||||
|
class="w-4 h-4"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
d="m2.25 15.75 5.159-5.159a2.25 2.25 0 0 1 3.182 0l5.159 5.159m-1.5-1.5 1.409-1.409a2.25 2.25 0 0 1 3.182 0l2.909 2.909m-18 3.75h16.5a1.5 1.5 0 0 0 1.5-1.5V6a1.5 1.5 0 0 0-1.5-1.5H3.75A1.5 1.5 0 0 0 2.25 6v12a1.5 1.5 0 0 0 1.5 1.5Zm10.5-11.25h.008v.008h-.008V8.25Zm.375 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
{/if}
|
||||||
</button>
|
</button>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
|
|
@ -2,14 +2,17 @@
|
||||||
import toast from 'svelte-french-toast';
|
import toast from 'svelte-french-toast';
|
||||||
|
|
||||||
import { createEventDispatcher, onMount } from 'svelte';
|
import { createEventDispatcher, onMount } from 'svelte';
|
||||||
import { user } from '$lib/stores';
|
import { config, user } from '$lib/stores';
|
||||||
import {
|
import {
|
||||||
getAUTOMATIC1111Url,
|
getAUTOMATIC1111Url,
|
||||||
getDefaultDiffusionModel,
|
getDefaultDiffusionModel,
|
||||||
getDiffusionModels,
|
getDiffusionModels,
|
||||||
|
getImageGenerationEnabledStatus,
|
||||||
|
toggleImageGenerationEnabledStatus,
|
||||||
updateAUTOMATIC1111Url,
|
updateAUTOMATIC1111Url,
|
||||||
updateDefaultDiffusionModel
|
updateDefaultDiffusionModel
|
||||||
} from '$lib/apis/images';
|
} from '$lib/apis/images';
|
||||||
|
import { getBackendConfig } from '$lib/apis';
|
||||||
const dispatch = createEventDispatcher();
|
const dispatch = createEventDispatcher();
|
||||||
|
|
||||||
export let saveSettings: Function;
|
export let saveSettings: Function;
|
||||||
|
@ -42,11 +45,13 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
const toggleImageGeneration = async () => {
|
const toggleImageGeneration = async () => {
|
||||||
enableImageGeneration = !enableImageGeneration;
|
enableImageGeneration = await toggleImageGenerationEnabledStatus(localStorage.token);
|
||||||
|
config.set(await getBackendConfig(localStorage.token));
|
||||||
};
|
};
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
if ($user.role === 'admin') {
|
if ($user.role === 'admin') {
|
||||||
|
enableImageGeneration = await getImageGenerationEnabledStatus(localStorage.token);
|
||||||
AUTOMATIC1111_BASE_URL = await getAUTOMATIC1111Url(localStorage.token);
|
AUTOMATIC1111_BASE_URL = await getAUTOMATIC1111Url(localStorage.token);
|
||||||
|
|
||||||
if (AUTOMATIC1111_BASE_URL) {
|
if (AUTOMATIC1111_BASE_URL) {
|
||||||
|
|
Loading…
Reference in a new issue