forked from open-webui/open-webui
Merge pull request #838 from open-webui/image-generation
feat: stable diffusion integration
This commit is contained in:
commit
210122f1e2
19 changed files with 1165 additions and 236 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
|
||||||
|
|
165
backend/apps/images/main.py
Normal file
165
backend/apps/images/main.py
Normal file
|
@ -0,0 +1,165 @@
|
||||||
|
import os
|
||||||
|
import requests
|
||||||
|
from fastapi import (
|
||||||
|
FastAPI,
|
||||||
|
Request,
|
||||||
|
Depends,
|
||||||
|
HTTPException,
|
||||||
|
status,
|
||||||
|
UploadFile,
|
||||||
|
File,
|
||||||
|
Form,
|
||||||
|
)
|
||||||
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
|
from faster_whisper import WhisperModel
|
||||||
|
|
||||||
|
from constants import ERROR_MESSAGES
|
||||||
|
from utils.utils import (
|
||||||
|
get_current_user,
|
||||||
|
get_admin_user,
|
||||||
|
)
|
||||||
|
from utils.misc import calculate_sha256
|
||||||
|
from typing import Optional
|
||||||
|
from pydantic import BaseModel
|
||||||
|
from config import AUTOMATIC1111_BASE_URL
|
||||||
|
|
||||||
|
app = FastAPI()
|
||||||
|
app.add_middleware(
|
||||||
|
CORSMiddleware,
|
||||||
|
allow_origins=["*"],
|
||||||
|
allow_credentials=True,
|
||||||
|
allow_methods=["*"],
|
||||||
|
allow_headers=["*"],
|
||||||
|
)
|
||||||
|
|
||||||
|
app.state.AUTOMATIC1111_BASE_URL = AUTOMATIC1111_BASE_URL
|
||||||
|
app.state.ENABLED = app.state.AUTOMATIC1111_BASE_URL != ""
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/enabled", response_model=bool)
|
||||||
|
async def get_enable_status(request: Request, user=Depends(get_admin_user)):
|
||||||
|
return app.state.ENABLED
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/enabled/toggle", response_model=bool)
|
||||||
|
async def toggle_enabled(request: Request, user=Depends(get_admin_user)):
|
||||||
|
try:
|
||||||
|
r = requests.head(app.state.AUTOMATIC1111_BASE_URL)
|
||||||
|
app.state.ENABLED = not app.state.ENABLED
|
||||||
|
return app.state.ENABLED
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(status_code=r.status_code, detail=ERROR_MESSAGES.DEFAULT(e))
|
||||||
|
|
||||||
|
|
||||||
|
class UrlUpdateForm(BaseModel):
|
||||||
|
url: str
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/url")
|
||||||
|
async def get_openai_url(user=Depends(get_admin_user)):
|
||||||
|
return {"AUTOMATIC1111_BASE_URL": app.state.AUTOMATIC1111_BASE_URL}
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/url/update")
|
||||||
|
async def update_openai_url(form_data: UrlUpdateForm, user=Depends(get_admin_user)):
|
||||||
|
|
||||||
|
if form_data.url == "":
|
||||||
|
app.state.AUTOMATIC1111_BASE_URL = AUTOMATIC1111_BASE_URL
|
||||||
|
else:
|
||||||
|
app.state.AUTOMATIC1111_BASE_URL = form_data.url.strip("/")
|
||||||
|
|
||||||
|
return {
|
||||||
|
"AUTOMATIC1111_BASE_URL": app.state.AUTOMATIC1111_BASE_URL,
|
||||||
|
"status": True,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/models")
|
||||||
|
def get_models(user=Depends(get_current_user)):
|
||||||
|
try:
|
||||||
|
r = requests.get(url=f"{app.state.AUTOMATIC1111_BASE_URL}/sdapi/v1/sd-models")
|
||||||
|
models = r.json()
|
||||||
|
return models
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(status_code=r.status_code, detail=ERROR_MESSAGES.DEFAULT(e))
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/models/default")
|
||||||
|
async def get_default_model(user=Depends(get_admin_user)):
|
||||||
|
try:
|
||||||
|
r = requests.get(url=f"{app.state.AUTOMATIC1111_BASE_URL}/sdapi/v1/options")
|
||||||
|
options = r.json()
|
||||||
|
|
||||||
|
return {"model": options["sd_model_checkpoint"]}
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(status_code=r.status_code, detail=ERROR_MESSAGES.DEFAULT(e))
|
||||||
|
|
||||||
|
|
||||||
|
class UpdateModelForm(BaseModel):
|
||||||
|
model: str
|
||||||
|
|
||||||
|
|
||||||
|
def set_model_handler(model: str):
|
||||||
|
r = requests.get(url=f"{app.state.AUTOMATIC1111_BASE_URL}/sdapi/v1/options")
|
||||||
|
options = r.json()
|
||||||
|
|
||||||
|
if model != options["sd_model_checkpoint"]:
|
||||||
|
options["sd_model_checkpoint"] = model
|
||||||
|
r = requests.post(
|
||||||
|
url=f"{app.state.AUTOMATIC1111_BASE_URL}/sdapi/v1/options", json=options
|
||||||
|
)
|
||||||
|
|
||||||
|
return options
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/models/default/update")
|
||||||
|
def update_default_model(
|
||||||
|
form_data: UpdateModelForm,
|
||||||
|
user=Depends(get_current_user),
|
||||||
|
):
|
||||||
|
return set_model_handler(form_data.model)
|
||||||
|
|
||||||
|
|
||||||
|
class GenerateImageForm(BaseModel):
|
||||||
|
model: Optional[str] = None
|
||||||
|
prompt: str
|
||||||
|
n: int = 1
|
||||||
|
size: str = "512x512"
|
||||||
|
negative_prompt: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/generations")
|
||||||
|
def generate_image(
|
||||||
|
form_data: GenerateImageForm,
|
||||||
|
user=Depends(get_current_user),
|
||||||
|
):
|
||||||
|
|
||||||
|
print(form_data)
|
||||||
|
|
||||||
|
try:
|
||||||
|
if form_data.model:
|
||||||
|
set_model_handler(form_data.model)
|
||||||
|
|
||||||
|
width, height = tuple(map(int, form_data.size.split("x")))
|
||||||
|
|
||||||
|
data = {
|
||||||
|
"prompt": form_data.prompt,
|
||||||
|
"batch_size": form_data.n,
|
||||||
|
"width": width,
|
||||||
|
"height": height,
|
||||||
|
}
|
||||||
|
|
||||||
|
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))
|
|
@ -57,7 +57,6 @@ app.include_router(utils.router, prefix="/utils", tags=["utils"])
|
||||||
async def get_status():
|
async def get_status():
|
||||||
return {
|
return {
|
||||||
"status": True,
|
"status": True,
|
||||||
"version": WEBUI_VERSION,
|
|
||||||
"auth": WEBUI_AUTH,
|
"auth": WEBUI_AUTH,
|
||||||
"default_models": app.state.DEFAULT_MODELS,
|
"default_models": app.state.DEFAULT_MODELS,
|
||||||
"default_prompt_suggestions": app.state.DEFAULT_PROMPT_SUGGESTIONS,
|
"default_prompt_suggestions": app.state.DEFAULT_PROMPT_SUGGESTIONS,
|
||||||
|
|
|
@ -185,3 +185,10 @@ Query: [query]"""
|
||||||
|
|
||||||
WHISPER_MODEL = os.getenv("WHISPER_MODEL", "base")
|
WHISPER_MODEL = os.getenv("WHISPER_MODEL", "base")
|
||||||
WHISPER_MODEL_DIR = os.getenv("WHISPER_MODEL_DIR", f"{CACHE_DIR}/whisper/models")
|
WHISPER_MODEL_DIR = os.getenv("WHISPER_MODEL_DIR", f"{CACHE_DIR}/whisper/models")
|
||||||
|
|
||||||
|
|
||||||
|
####################################
|
||||||
|
# Images
|
||||||
|
####################################
|
||||||
|
|
||||||
|
AUTOMATIC1111_BASE_URL = os.getenv("AUTOMATIC1111_BASE_URL", "")
|
||||||
|
|
|
@ -11,10 +11,10 @@ from starlette.exceptions import HTTPException as StarletteHTTPException
|
||||||
from apps.ollama.main import app as ollama_app
|
from apps.ollama.main import app as ollama_app
|
||||||
from apps.openai.main import app as openai_app
|
from apps.openai.main import app as openai_app
|
||||||
from apps.audio.main import app as audio_app
|
from apps.audio.main import app as audio_app
|
||||||
|
from apps.images.main import app as images_app
|
||||||
|
from apps.rag.main import app as rag_app
|
||||||
|
|
||||||
from apps.web.main import app as webui_app
|
from apps.web.main import app as webui_app
|
||||||
from apps.rag.main import app as rag_app
|
|
||||||
|
|
||||||
from config import ENV, FRONTEND_BUILD_DIR
|
from config import ENV, FRONTEND_BUILD_DIR
|
||||||
|
|
||||||
|
@ -58,10 +58,21 @@ app.mount("/api/v1", webui_app)
|
||||||
app.mount("/ollama/api", ollama_app)
|
app.mount("/ollama/api", ollama_app)
|
||||||
app.mount("/openai/api", openai_app)
|
app.mount("/openai/api", openai_app)
|
||||||
|
|
||||||
|
app.mount("/images/api/v1", images_app)
|
||||||
app.mount("/audio/api/v1", audio_app)
|
app.mount("/audio/api/v1", audio_app)
|
||||||
app.mount("/rag/api/v1", rag_app)
|
app.mount("/rag/api/v1", rag_app)
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/api/config")
|
||||||
|
async def get_app_config():
|
||||||
|
return {
|
||||||
|
"status": True,
|
||||||
|
"images": images_app.state.ENABLED,
|
||||||
|
"default_models": webui_app.state.DEFAULT_MODELS,
|
||||||
|
"default_prompt_suggestions": webui_app.state.DEFAULT_PROMPT_SUGGESTIONS,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
app.mount(
|
app.mount(
|
||||||
"/",
|
"/",
|
||||||
SPAStaticFiles(directory=FRONTEND_BUILD_DIR, html=True),
|
SPAStaticFiles(directory=FRONTEND_BUILD_DIR, html=True),
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "open-webui",
|
"name": "open-webui",
|
||||||
"version": "0.0.1",
|
"version": "v1.0.0-alpha.101",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite dev --host",
|
"dev": "vite dev --host",
|
||||||
|
|
266
src/lib/apis/images/index.ts
Normal file
266
src/lib/apis/images/index.ts
Normal file
|
@ -0,0 +1,266 @@
|
||||||
|
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 = '') => {
|
||||||
|
let error = null;
|
||||||
|
|
||||||
|
const res = await fetch(`${IMAGES_API_BASE_URL}/url`, {
|
||||||
|
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.AUTOMATIC1111_BASE_URL;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const updateAUTOMATIC1111Url = async (token: string = '', url: string) => {
|
||||||
|
let error = null;
|
||||||
|
|
||||||
|
const res = await fetch(`${IMAGES_API_BASE_URL}/url/update`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
Accept: 'application/json',
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
...(token && { authorization: `Bearer ${token}` })
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
url: url
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.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.AUTOMATIC1111_BASE_URL;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getDiffusionModels = async (token: string = '') => {
|
||||||
|
let error = null;
|
||||||
|
|
||||||
|
const res = await fetch(`${IMAGES_API_BASE_URL}/models`, {
|
||||||
|
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 getDefaultDiffusionModel = async (token: string = '') => {
|
||||||
|
let error = null;
|
||||||
|
|
||||||
|
const res = await fetch(`${IMAGES_API_BASE_URL}/models/default`, {
|
||||||
|
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.model;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const updateDefaultDiffusionModel = async (token: string = '', model: string) => {
|
||||||
|
let error = null;
|
||||||
|
|
||||||
|
const res = await fetch(`${IMAGES_API_BASE_URL}/models/default/update`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
Accept: 'application/json',
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
...(token && { authorization: `Bearer ${token}` })
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
model: model
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.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.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;
|
||||||
|
};
|
|
@ -1,9 +1,9 @@
|
||||||
import { WEBUI_API_BASE_URL } from '$lib/constants';
|
import { WEBUI_BASE_URL } from '$lib/constants';
|
||||||
|
|
||||||
export const getBackendConfig = async () => {
|
export const getBackendConfig = async () => {
|
||||||
let error = null;
|
let error = null;
|
||||||
|
|
||||||
const res = await fetch(`${WEBUI_API_BASE_URL}/`, {
|
const res = await fetch(`${WEBUI_BASE_URL}/api/config`, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json'
|
||||||
|
|
|
@ -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;
|
||||||
|
@ -308,6 +309,16 @@
|
||||||
{copyToClipboard}
|
{copyToClipboard}
|
||||||
{continueGeneration}
|
{continueGeneration}
|
||||||
{regenerateResponse}
|
{regenerateResponse}
|
||||||
|
on:save={async (e) => {
|
||||||
|
console.log('save', e);
|
||||||
|
|
||||||
|
const message = e.detail;
|
||||||
|
history.messages[message.id] = message;
|
||||||
|
await updateChatById(localStorage.token, chatId, {
|
||||||
|
messages: messages,
|
||||||
|
history: history
|
||||||
|
});
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -2,21 +2,25 @@
|
||||||
import toast from 'svelte-french-toast';
|
import toast from 'svelte-french-toast';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import { marked } from 'marked';
|
import { marked } from 'marked';
|
||||||
import { settings } from '$lib/stores';
|
|
||||||
import tippy from 'tippy.js';
|
import tippy from 'tippy.js';
|
||||||
import auto_render from 'katex/dist/contrib/auto-render.mjs';
|
import auto_render from 'katex/dist/contrib/auto-render.mjs';
|
||||||
import 'katex/dist/katex.min.css';
|
import 'katex/dist/katex.min.css';
|
||||||
|
|
||||||
|
import { createEventDispatcher } from 'svelte';
|
||||||
import { onMount, tick } from 'svelte';
|
import { onMount, tick } from 'svelte';
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher();
|
||||||
|
|
||||||
|
import { config, settings } from '$lib/stores';
|
||||||
|
import { synthesizeOpenAISpeech } from '$lib/apis/openai';
|
||||||
|
import { imageGenerations } from '$lib/apis/images';
|
||||||
|
import { extractSentences } from '$lib/utils';
|
||||||
|
|
||||||
import Name from './Name.svelte';
|
import Name from './Name.svelte';
|
||||||
import ProfileImage from './ProfileImage.svelte';
|
import ProfileImage from './ProfileImage.svelte';
|
||||||
import Skeleton from './Skeleton.svelte';
|
import Skeleton from './Skeleton.svelte';
|
||||||
import CodeBlock from './CodeBlock.svelte';
|
import CodeBlock from './CodeBlock.svelte';
|
||||||
|
|
||||||
import { synthesizeOpenAISpeech } from '$lib/apis/openai';
|
|
||||||
import { extractSentences } from '$lib/utils';
|
|
||||||
|
|
||||||
export let modelfiles = [];
|
export let modelfiles = [];
|
||||||
export let message;
|
export let message;
|
||||||
export let siblings;
|
export let siblings;
|
||||||
|
@ -43,6 +47,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 +273,23 @@
|
||||||
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}`
|
||||||
|
}));
|
||||||
|
|
||||||
|
dispatch('save', message);
|
||||||
|
}
|
||||||
|
|
||||||
|
generatingImage = false;
|
||||||
|
};
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
await tick();
|
await tick();
|
||||||
renderStyling();
|
renderStyling();
|
||||||
|
@ -295,6 +318,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"
|
||||||
>
|
>
|
||||||
|
@ -595,6 +630,71 @@
|
||||||
{/if}
|
{/if}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
{#if $config.images}
|
||||||
|
<button
|
||||||
|
class="{isLastMessage
|
||||||
|
? 'visible'
|
||||||
|
: 'invisible group-hover:visible'} p-1 rounded dark:hover:text-white hover:text-black transition"
|
||||||
|
on:click={() => {
|
||||||
|
if (!generatingImage) {
|
||||||
|
generateImage(message);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{#if generatingImage}
|
||||||
|
<svg
|
||||||
|
class=" w-4 h-4"
|
||||||
|
fill="currentColor"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
><style>
|
||||||
|
.spinner_S1WN {
|
||||||
|
animation: spinner_MGfb 0.8s linear infinite;
|
||||||
|
animation-delay: -0.8s;
|
||||||
|
}
|
||||||
|
.spinner_Km9P {
|
||||||
|
animation-delay: -0.65s;
|
||||||
|
}
|
||||||
|
.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>
|
||||||
|
{/if}
|
||||||
|
|
||||||
{#if message.info}
|
{#if message.info}
|
||||||
<button
|
<button
|
||||||
class=" {isLastMessage
|
class=" {isLastMessage
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
<div class=" mb-2.5 text-sm font-medium">{WEBUI_NAME} Version</div>
|
<div class=" mb-2.5 text-sm font-medium">{WEBUI_NAME} Version</div>
|
||||||
<div class="flex w-full">
|
<div class="flex w-full">
|
||||||
<div class="flex-1 text-xs text-gray-700 dark:text-gray-200">
|
<div class="flex-1 text-xs text-gray-700 dark:text-gray-200">
|
||||||
{$config && $config.version ? $config.version : WEB_UI_VERSION}
|
{WEB_UI_VERSION}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,12 +1,17 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { getOpenAIKey, getOpenAIUrl, updateOpenAIKey, updateOpenAIUrl } from '$lib/apis/openai';
|
|
||||||
import { models, user } from '$lib/stores';
|
import { models, user } from '$lib/stores';
|
||||||
import { createEventDispatcher, onMount } from 'svelte';
|
import { createEventDispatcher, onMount } from 'svelte';
|
||||||
const dispatch = createEventDispatcher();
|
const dispatch = createEventDispatcher();
|
||||||
|
|
||||||
|
import { getOllamaAPIUrl, updateOllamaAPIUrl } from '$lib/apis/ollama';
|
||||||
|
import { getOpenAIKey, getOpenAIUrl, updateOpenAIKey, updateOpenAIUrl } from '$lib/apis/openai';
|
||||||
|
import toast from 'svelte-french-toast';
|
||||||
|
|
||||||
export let getModels: Function;
|
export let getModels: Function;
|
||||||
|
|
||||||
// External
|
// External
|
||||||
|
let API_BASE_URL = '';
|
||||||
|
|
||||||
let OPENAI_API_KEY = '';
|
let OPENAI_API_KEY = '';
|
||||||
let OPENAI_API_BASE_URL = '';
|
let OPENAI_API_BASE_URL = '';
|
||||||
|
|
||||||
|
@ -17,8 +22,19 @@
|
||||||
await models.set(await getModels());
|
await models.set(await getModels());
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const updateOllamaAPIUrlHandler = async () => {
|
||||||
|
API_BASE_URL = await updateOllamaAPIUrl(localStorage.token, API_BASE_URL);
|
||||||
|
const _models = await getModels('ollama');
|
||||||
|
|
||||||
|
if (_models.length > 0) {
|
||||||
|
toast.success('Server connection verified');
|
||||||
|
await models.set(_models);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
if ($user.role === 'admin') {
|
if ($user.role === 'admin') {
|
||||||
|
API_BASE_URL = await getOllamaAPIUrl(localStorage.token);
|
||||||
OPENAI_API_BASE_URL = await getOpenAIUrl(localStorage.token);
|
OPENAI_API_BASE_URL = await getOpenAIUrl(localStorage.token);
|
||||||
OPENAI_API_KEY = await getOpenAIKey(localStorage.token);
|
OPENAI_API_KEY = await getOpenAIKey(localStorage.token);
|
||||||
}
|
}
|
||||||
|
@ -26,7 +42,7 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<form
|
<form
|
||||||
class="flex flex-col h-full justify-between space-y-3 text-sm"
|
class="flex flex-col h-full space-y-3 text-sm"
|
||||||
on:submit|preventDefault={() => {
|
on:submit|preventDefault={() => {
|
||||||
updateOpenAIHandler();
|
updateOpenAIHandler();
|
||||||
dispatch('save');
|
dispatch('save');
|
||||||
|
@ -37,6 +53,52 @@
|
||||||
// });
|
// });
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
<div>
|
||||||
|
<div class=" mb-2.5 text-sm font-medium">Ollama API URL</div>
|
||||||
|
<div class="flex w-full">
|
||||||
|
<div class="flex-1 mr-2">
|
||||||
|
<input
|
||||||
|
class="w-full rounded py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none"
|
||||||
|
placeholder="Enter URL (e.g. http://localhost:11434/api)"
|
||||||
|
bind:value={API_BASE_URL}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
class="px-3 bg-gray-200 hover:bg-gray-300 dark:bg-gray-600 dark:hover:bg-gray-700 rounded transition"
|
||||||
|
on:click={() => {
|
||||||
|
updateOllamaAPIUrlHandler();
|
||||||
|
}}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 20 20"
|
||||||
|
fill="currentColor"
|
||||||
|
class="w-4 h-4"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill-rule="evenodd"
|
||||||
|
d="M15.312 11.424a5.5 5.5 0 01-9.201 2.466l-.312-.311h2.433a.75.75 0 000-1.5H3.989a.75.75 0 00-.75.75v4.242a.75.75 0 001.5 0v-2.43l.31.31a7 7 0 0011.712-3.138.75.75 0 00-1.449-.39zm1.23-3.723a.75.75 0 00.219-.53V2.929a.75.75 0 00-1.5 0V5.36l-.31-.31A7 7 0 003.239 8.188a.75.75 0 101.448.389A5.5 5.5 0 0113.89 6.11l.311.31h-2.432a.75.75 0 000 1.5h4.243a.75.75 0 00.53-.219z"
|
||||||
|
clip-rule="evenodd"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-2 text-xs text-gray-400 dark:text-gray-500">
|
||||||
|
Trouble accessing Ollama?
|
||||||
|
<a
|
||||||
|
class=" text-gray-300 font-medium"
|
||||||
|
href="https://github.com/open-webui/open-webui#troubleshooting"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
Click here for help.
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr class=" dark:border-gray-700" />
|
||||||
|
|
||||||
<div class=" space-y-3">
|
<div class=" space-y-3">
|
||||||
<div>
|
<div>
|
||||||
<div class=" mb-2.5 text-sm font-medium">OpenAI API Key</div>
|
<div class=" mb-2.5 text-sm font-medium">OpenAI API Key</div>
|
||||||
|
@ -50,13 +112,8 @@
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-2 text-xs text-gray-400 dark:text-gray-500">
|
|
||||||
Adds optional support for online models.
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<hr class=" dark:border-gray-700" />
|
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<div class=" mb-2.5 text-sm font-medium">OpenAI API Base URL</div>
|
<div class=" mb-2.5 text-sm font-medium">OpenAI API Base URL</div>
|
||||||
<div class="flex w-full">
|
<div class="flex w-full">
|
|
@ -3,31 +3,20 @@
|
||||||
import { createEventDispatcher, onMount } from 'svelte';
|
import { createEventDispatcher, onMount } from 'svelte';
|
||||||
const dispatch = createEventDispatcher();
|
const dispatch = createEventDispatcher();
|
||||||
|
|
||||||
import { getOllamaAPIUrl, updateOllamaAPIUrl } from '$lib/apis/ollama';
|
|
||||||
import { models, user } from '$lib/stores';
|
import { models, user } from '$lib/stores';
|
||||||
|
|
||||||
|
import AdvancedParams from './Advanced/AdvancedParams.svelte';
|
||||||
|
|
||||||
export let saveSettings: Function;
|
export let saveSettings: Function;
|
||||||
export let getModels: Function;
|
export let getModels: Function;
|
||||||
|
|
||||||
// General
|
// General
|
||||||
let API_BASE_URL = '';
|
|
||||||
let themes = ['dark', 'light', 'rose-pine dark', 'rose-pine-dawn light'];
|
let themes = ['dark', 'light', 'rose-pine dark', 'rose-pine-dawn light'];
|
||||||
let theme = 'dark';
|
let theme = 'dark';
|
||||||
let notificationEnabled = false;
|
let notificationEnabled = false;
|
||||||
let system = '';
|
let system = '';
|
||||||
|
|
||||||
const toggleTheme = async () => {
|
let showAdvanced = false;
|
||||||
if (theme === 'dark') {
|
|
||||||
theme = 'light';
|
|
||||||
} else {
|
|
||||||
theme = 'dark';
|
|
||||||
}
|
|
||||||
|
|
||||||
localStorage.theme = theme;
|
|
||||||
|
|
||||||
document.documentElement.classList.remove(theme === 'dark' ? 'light' : 'dark');
|
|
||||||
document.documentElement.classList.add(theme);
|
|
||||||
};
|
|
||||||
|
|
||||||
const toggleNotification = async () => {
|
const toggleNotification = async () => {
|
||||||
const permission = await Notification.requestPermission();
|
const permission = await Notification.requestPermission();
|
||||||
|
@ -42,170 +31,233 @@
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateOllamaAPIUrlHandler = async () => {
|
// Advanced
|
||||||
API_BASE_URL = await updateOllamaAPIUrl(localStorage.token, API_BASE_URL);
|
let requestFormat = '';
|
||||||
const _models = await getModels('ollama');
|
let keepAlive = null;
|
||||||
|
|
||||||
if (_models.length > 0) {
|
let options = {
|
||||||
toast.success('Server connection verified');
|
// Advanced
|
||||||
await models.set(_models);
|
seed: 0,
|
||||||
|
temperature: '',
|
||||||
|
repeat_penalty: '',
|
||||||
|
repeat_last_n: '',
|
||||||
|
mirostat: '',
|
||||||
|
mirostat_eta: '',
|
||||||
|
mirostat_tau: '',
|
||||||
|
top_k: '',
|
||||||
|
top_p: '',
|
||||||
|
stop: '',
|
||||||
|
tfs_z: '',
|
||||||
|
num_ctx: '',
|
||||||
|
num_predict: ''
|
||||||
|
};
|
||||||
|
|
||||||
|
const toggleRequestFormat = async () => {
|
||||||
|
if (requestFormat === '') {
|
||||||
|
requestFormat = 'json';
|
||||||
|
} else {
|
||||||
|
requestFormat = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
saveSettings({ requestFormat: requestFormat !== '' ? requestFormat : undefined });
|
||||||
};
|
};
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
if ($user.role === 'admin') {
|
|
||||||
API_BASE_URL = await getOllamaAPIUrl(localStorage.token);
|
|
||||||
}
|
|
||||||
|
|
||||||
let settings = JSON.parse(localStorage.getItem('settings') ?? '{}');
|
let settings = JSON.parse(localStorage.getItem('settings') ?? '{}');
|
||||||
|
|
||||||
theme = localStorage.theme ?? 'dark';
|
theme = localStorage.theme ?? 'dark';
|
||||||
notificationEnabled = settings.notificationEnabled ?? false;
|
notificationEnabled = settings.notificationEnabled ?? false;
|
||||||
system = settings.system ?? '';
|
system = settings.system ?? '';
|
||||||
|
|
||||||
|
requestFormat = settings.requestFormat ?? '';
|
||||||
|
keepAlive = settings.keepAlive ?? null;
|
||||||
|
|
||||||
|
options.seed = settings.seed ?? 0;
|
||||||
|
options.temperature = settings.temperature ?? '';
|
||||||
|
options.repeat_penalty = settings.repeat_penalty ?? '';
|
||||||
|
options.top_k = settings.top_k ?? '';
|
||||||
|
options.top_p = settings.top_p ?? '';
|
||||||
|
options.num_ctx = settings.num_ctx ?? '';
|
||||||
|
options = { ...options, ...settings.options };
|
||||||
|
options.stop = (settings?.options?.stop ?? []).join(',');
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex flex-col space-y-3">
|
<div class="flex flex-col h-full justify-between text-sm">
|
||||||
<div>
|
<div class=" pr-1.5 overflow-y-scroll max-h-[21rem]">
|
||||||
<div class=" mb-1 text-sm font-medium">WebUI Settings</div>
|
<div class="">
|
||||||
|
<div class=" mb-1 text-sm font-medium">WebUI Settings</div>
|
||||||
|
|
||||||
<div class=" py-0.5 flex w-full justify-between">
|
<div class=" py-0.5 flex w-full justify-between">
|
||||||
<div class=" self-center text-xs font-medium">Theme</div>
|
<div class=" self-center text-xs font-medium">Theme</div>
|
||||||
<div class="flex items-center relative">
|
<div class="flex items-center relative">
|
||||||
<div class=" absolute right-16">
|
<div class=" absolute right-16">
|
||||||
{#if theme === 'dark'}
|
{#if theme === 'dark'}
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
viewBox="0 0 20 20"
|
viewBox="0 0 20 20"
|
||||||
fill="currentColor"
|
fill="currentColor"
|
||||||
class="w-4 h-4"
|
class="w-4 h-4"
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
fill-rule="evenodd"
|
fill-rule="evenodd"
|
||||||
d="M7.455 2.004a.75.75 0 01.26.77 7 7 0 009.958 7.967.75.75 0 011.067.853A8.5 8.5 0 116.647 1.921a.75.75 0 01.808.083z"
|
d="M7.455 2.004a.75.75 0 01.26.77 7 7 0 009.958 7.967.75.75 0 011.067.853A8.5 8.5 0 116.647 1.921a.75.75 0 01.808.083z"
|
||||||
clip-rule="evenodd"
|
clip-rule="evenodd"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
{:else if theme === 'light'}
|
{:else if theme === 'light'}
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
viewBox="0 0 20 20"
|
viewBox="0 0 20 20"
|
||||||
fill="currentColor"
|
fill="currentColor"
|
||||||
class="w-4 h-4 self-center"
|
class="w-4 h-4 self-center"
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
d="M10 2a.75.75 0 01.75.75v1.5a.75.75 0 01-1.5 0v-1.5A.75.75 0 0110 2zM10 15a.75.75 0 01.75.75v1.5a.75.75 0 01-1.5 0v-1.5A.75.75 0 0110 15zM10 7a3 3 0 100 6 3 3 0 000-6zM15.657 5.404a.75.75 0 10-1.06-1.06l-1.061 1.06a.75.75 0 001.06 1.06l1.06-1.06zM6.464 14.596a.75.75 0 10-1.06-1.06l-1.06 1.06a.75.75 0 001.06 1.06l1.06-1.06zM18 10a.75.75 0 01-.75.75h-1.5a.75.75 0 010-1.5h1.5A.75.75 0 0118 10zM5 10a.75.75 0 01-.75.75h-1.5a.75.75 0 010-1.5h1.5A.75.75 0 015 10zM14.596 15.657a.75.75 0 001.06-1.06l-1.06-1.061a.75.75 0 10-1.06 1.06l1.06 1.06zM5.404 6.464a.75.75 0 001.06-1.06l-1.06-1.06a.75.75 0 10-1.061 1.06l1.06 1.06z"
|
d="M10 2a.75.75 0 01.75.75v1.5a.75.75 0 01-1.5 0v-1.5A.75.75 0 0110 2zM10 15a.75.75 0 01.75.75v1.5a.75.75 0 01-1.5 0v-1.5A.75.75 0 0110 15zM10 7a3 3 0 100 6 3 3 0 000-6zM15.657 5.404a.75.75 0 10-1.06-1.06l-1.061 1.06a.75.75 0 001.06 1.06l1.06-1.06zM6.464 14.596a.75.75 0 10-1.06-1.06l-1.06 1.06a.75.75 0 001.06 1.06l1.06-1.06zM18 10a.75.75 0 01-.75.75h-1.5a.75.75 0 010-1.5h1.5A.75.75 0 0118 10zM5 10a.75.75 0 01-.75.75h-1.5a.75.75 0 010-1.5h1.5A.75.75 0 015 10zM14.596 15.657a.75.75 0 001.06-1.06l-1.06-1.061a.75.75 0 10-1.06 1.06l1.06 1.06zM5.404 6.464a.75.75 0 001.06-1.06l-1.06-1.06a.75.75 0 10-1.061 1.06l1.06 1.06z"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<select
|
<select
|
||||||
class="w-fit pr-8 rounded py-2 px-2 text-xs bg-transparent outline-none text-right"
|
class="w-fit pr-8 rounded py-2 px-2 text-xs bg-transparent outline-none text-right"
|
||||||
bind:value={theme}
|
bind:value={theme}
|
||||||
placeholder="Select a theme"
|
placeholder="Select a theme"
|
||||||
on:change={(e) => {
|
on:change={(e) => {
|
||||||
localStorage.theme = theme;
|
localStorage.theme = theme;
|
||||||
|
|
||||||
themes
|
themes
|
||||||
.filter((e) => e !== theme)
|
.filter((e) => e !== theme)
|
||||||
.forEach((e) => {
|
.forEach((e) => {
|
||||||
e.split(' ').forEach((e) => {
|
e.split(' ').forEach((e) => {
|
||||||
document.documentElement.classList.remove(e);
|
document.documentElement.classList.remove(e);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
theme.split(' ').forEach((e) => {
|
||||||
|
document.documentElement.classList.add(e);
|
||||||
});
|
});
|
||||||
|
|
||||||
theme.split(' ').forEach((e) => {
|
console.log(theme);
|
||||||
document.documentElement.classList.add(e);
|
}}
|
||||||
});
|
|
||||||
|
|
||||||
console.log(theme);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<option value="dark">Dark</option>
|
|
||||||
<option value="light">Light</option>
|
|
||||||
<option value="rose-pine dark">Rosé Pine</option>
|
|
||||||
<option value="rose-pine-dawn light">Rosé Pine Dawn</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<div class=" py-0.5 flex w-full justify-between">
|
|
||||||
<div class=" self-center text-xs font-medium">Notification</div>
|
|
||||||
|
|
||||||
<button
|
|
||||||
class="p-1 px-3 text-xs flex rounded transition"
|
|
||||||
on:click={() => {
|
|
||||||
toggleNotification();
|
|
||||||
}}
|
|
||||||
type="button"
|
|
||||||
>
|
|
||||||
{#if notificationEnabled === true}
|
|
||||||
<span class="ml-2 self-center">On</span>
|
|
||||||
{:else}
|
|
||||||
<span class="ml-2 self-center">Off</span>
|
|
||||||
{/if}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{#if $user.role === 'admin'}
|
|
||||||
<hr class=" dark:border-gray-700" />
|
|
||||||
<div>
|
|
||||||
<div class=" mb-2.5 text-sm font-medium">Ollama API URL</div>
|
|
||||||
<div class="flex w-full">
|
|
||||||
<div class="flex-1 mr-2">
|
|
||||||
<input
|
|
||||||
class="w-full rounded py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none"
|
|
||||||
placeholder="Enter URL (e.g. http://localhost:11434/api)"
|
|
||||||
bind:value={API_BASE_URL}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<button
|
|
||||||
class="px-3 bg-gray-200 hover:bg-gray-300 dark:bg-gray-600 dark:hover:bg-gray-700 rounded transition"
|
|
||||||
on:click={() => {
|
|
||||||
updateOllamaAPIUrlHandler();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
viewBox="0 0 20 20"
|
|
||||||
fill="currentColor"
|
|
||||||
class="w-4 h-4"
|
|
||||||
>
|
>
|
||||||
<path
|
<option value="dark">Dark</option>
|
||||||
fill-rule="evenodd"
|
<option value="light">Light</option>
|
||||||
d="M15.312 11.424a5.5 5.5 0 01-9.201 2.466l-.312-.311h2.433a.75.75 0 000-1.5H3.989a.75.75 0 00-.75.75v4.242a.75.75 0 001.5 0v-2.43l.31.31a7 7 0 0011.712-3.138.75.75 0 00-1.449-.39zm1.23-3.723a.75.75 0 00.219-.53V2.929a.75.75 0 00-1.5 0V5.36l-.31-.31A7 7 0 003.239 8.188a.75.75 0 101.448.389A5.5 5.5 0 0113.89 6.11l.311.31h-2.432a.75.75 0 000 1.5h4.243a.75.75 0 00.53-.219z"
|
<option value="rose-pine dark">Rosé Pine</option>
|
||||||
clip-rule="evenodd"
|
<option value="rose-pine-dawn light">Rosé Pine Dawn</option>
|
||||||
/>
|
</select>
|
||||||
</svg>
|
</div>
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mt-2 text-xs text-gray-400 dark:text-gray-500">
|
<div>
|
||||||
Trouble accessing Ollama?
|
<div class=" py-0.5 flex w-full justify-between">
|
||||||
<a
|
<div class=" self-center text-xs font-medium">Notification</div>
|
||||||
class=" text-gray-300 font-medium"
|
|
||||||
href="https://github.com/open-webui/open-webui#troubleshooting"
|
<button
|
||||||
target="_blank"
|
class="p-1 px-3 text-xs flex rounded transition"
|
||||||
>
|
on:click={() => {
|
||||||
Click here for help.
|
toggleNotification();
|
||||||
</a>
|
}}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
{#if notificationEnabled === true}
|
||||||
|
<span class="ml-2 self-center">On</span>
|
||||||
|
{:else}
|
||||||
|
<span class="ml-2 self-center">Off</span>
|
||||||
|
{/if}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
|
||||||
|
|
||||||
<hr class=" dark:border-gray-700" />
|
<hr class=" dark:border-gray-700 my-3" />
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<div class=" mb-2.5 text-sm font-medium">System Prompt</div>
|
<div class=" my-2.5 text-sm font-medium">System Prompt</div>
|
||||||
<textarea
|
<textarea
|
||||||
bind:value={system}
|
bind:value={system}
|
||||||
class="w-full rounded p-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none resize-none"
|
class="w-full rounded p-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none resize-none"
|
||||||
rows="4"
|
rows="4"
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-2 space-y-3 pr-1.5">
|
||||||
|
<div class="flex justify-between items-center text-sm">
|
||||||
|
<div class=" font-medium">Advanced Parameters</div>
|
||||||
|
<button
|
||||||
|
class=" text-xs font-medium text-gray-500"
|
||||||
|
type="button"
|
||||||
|
on:click={() => {
|
||||||
|
showAdvanced = !showAdvanced;
|
||||||
|
}}>{showAdvanced ? 'Hide' : 'Show'}</button
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{#if showAdvanced}
|
||||||
|
<AdvancedParams bind:options />
|
||||||
|
<hr class=" dark:border-gray-700" />
|
||||||
|
|
||||||
|
<div class=" py-1 w-full justify-between">
|
||||||
|
<div class="flex w-full justify-between">
|
||||||
|
<div class=" self-center text-xs font-medium">Keep Alive</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
class="p-1 px-3 text-xs flex rounded transition"
|
||||||
|
type="button"
|
||||||
|
on:click={() => {
|
||||||
|
keepAlive = keepAlive === null ? '5m' : null;
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{#if keepAlive === null}
|
||||||
|
<span class="ml-2 self-center"> Default </span>
|
||||||
|
{:else}
|
||||||
|
<span class="ml-2 self-center"> Custom </span>
|
||||||
|
{/if}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{#if keepAlive !== null}
|
||||||
|
<div class="flex mt-1 space-x-2">
|
||||||
|
<input
|
||||||
|
class="w-full rounded py-1.5 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none border border-gray-100 dark:border-gray-600"
|
||||||
|
type="text"
|
||||||
|
placeholder={`e.g.) "30s","10m". Valid time units are "s", "m", "h".`}
|
||||||
|
bind:value={keepAlive}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div class=" py-1 flex w-full justify-between">
|
||||||
|
<div class=" self-center text-sm font-medium">Request Mode</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
class="p-1 px-3 text-xs flex rounded transition"
|
||||||
|
on:click={() => {
|
||||||
|
toggleRequestFormat();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{#if requestFormat === ''}
|
||||||
|
<span class="ml-2 self-center"> Default </span>
|
||||||
|
{:else if requestFormat === 'json'}
|
||||||
|
<!-- <svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 20 20"
|
||||||
|
fill="currentColor"
|
||||||
|
class="w-4 h-4 self-center"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M10 2a.75.75 0 01.75.75v1.5a.75.75 0 01-1.5 0v-1.5A.75.75 0 0110 2zM10 15a.75.75 0 01.75.75v1.5a.75.75 0 01-1.5 0v-1.5A.75.75 0 0110 15zM10 7a3 3 0 100 6 3 3 0 000-6zM15.657 5.404a.75.75 0 10-1.06-1.06l-1.061 1.06a.75.75 0 001.06 1.06l1.06-1.06zM6.464 14.596a.75.75 0 10-1.06-1.06l-1.06 1.06a.75.75 0 001.06 1.06l1.06-1.06zM18 10a.75.75 0 01-.75.75h-1.5a.75.75 0 010-1.5h1.5A.75.75 0 0118 10zM5 10a.75.75 0 01-.75.75h-1.5a.75.75 0 010-1.5h1.5A.75.75 0 015 10zM14.596 15.657a.75.75 0 001.06-1.06l-1.06-1.061a.75.75 0 10-1.06 1.06l1.06 1.06zM5.404 6.464a.75.75 0 001.06-1.06l-1.06-1.06a.75.75 0 10-1.061 1.06l1.06 1.06z"
|
||||||
|
/>
|
||||||
|
</svg> -->
|
||||||
|
<span class="ml-2 self-center"> JSON </span>
|
||||||
|
{/if}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex justify-end pt-3 text-sm font-medium">
|
<div class="flex justify-end pt-3 text-sm font-medium">
|
||||||
|
@ -213,7 +265,23 @@
|
||||||
class=" px-4 py-2 bg-emerald-600 hover:bg-emerald-700 text-gray-100 transition rounded"
|
class=" px-4 py-2 bg-emerald-600 hover:bg-emerald-700 text-gray-100 transition rounded"
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
saveSettings({
|
saveSettings({
|
||||||
system: system !== '' ? system : undefined
|
system: system !== '' ? system : undefined,
|
||||||
|
options: {
|
||||||
|
seed: (options.seed !== 0 ? options.seed : undefined) ?? undefined,
|
||||||
|
stop: options.stop !== '' ? options.stop.split(',').filter((e) => e) : undefined,
|
||||||
|
temperature: options.temperature !== '' ? options.temperature : undefined,
|
||||||
|
repeat_penalty: options.repeat_penalty !== '' ? options.repeat_penalty : undefined,
|
||||||
|
repeat_last_n: options.repeat_last_n !== '' ? options.repeat_last_n : undefined,
|
||||||
|
mirostat: options.mirostat !== '' ? options.mirostat : undefined,
|
||||||
|
mirostat_eta: options.mirostat_eta !== '' ? options.mirostat_eta : undefined,
|
||||||
|
mirostat_tau: options.mirostat_tau !== '' ? options.mirostat_tau : undefined,
|
||||||
|
top_k: options.top_k !== '' ? options.top_k : undefined,
|
||||||
|
top_p: options.top_p !== '' ? options.top_p : undefined,
|
||||||
|
tfs_z: options.tfs_z !== '' ? options.tfs_z : undefined,
|
||||||
|
num_ctx: options.num_ctx !== '' ? options.num_ctx : undefined,
|
||||||
|
num_predict: options.num_predict !== '' ? options.num_predict : undefined
|
||||||
|
},
|
||||||
|
keepAlive: keepAlive ? (isNaN(keepAlive) ? keepAlive : parseInt(keepAlive)) : undefined
|
||||||
});
|
});
|
||||||
dispatch('save');
|
dispatch('save');
|
||||||
}}
|
}}
|
||||||
|
|
234
src/lib/components/chat/Settings/Images.svelte
Normal file
234
src/lib/components/chat/Settings/Images.svelte
Normal file
|
@ -0,0 +1,234 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import toast from 'svelte-french-toast';
|
||||||
|
|
||||||
|
import { createEventDispatcher, onMount } from 'svelte';
|
||||||
|
import { config, user } from '$lib/stores';
|
||||||
|
import {
|
||||||
|
getAUTOMATIC1111Url,
|
||||||
|
getDefaultDiffusionModel,
|
||||||
|
getDiffusionModels,
|
||||||
|
getImageGenerationEnabledStatus,
|
||||||
|
toggleImageGenerationEnabledStatus,
|
||||||
|
updateAUTOMATIC1111Url,
|
||||||
|
updateDefaultDiffusionModel
|
||||||
|
} from '$lib/apis/images';
|
||||||
|
import { getBackendConfig } from '$lib/apis';
|
||||||
|
const dispatch = createEventDispatcher();
|
||||||
|
|
||||||
|
export let saveSettings: Function;
|
||||||
|
|
||||||
|
let loading = false;
|
||||||
|
|
||||||
|
let enableImageGeneration = true;
|
||||||
|
let AUTOMATIC1111_BASE_URL = '';
|
||||||
|
|
||||||
|
let selectedModel = '';
|
||||||
|
let models = [];
|
||||||
|
|
||||||
|
const getModels = async () => {
|
||||||
|
models = await getDiffusionModels(localStorage.token).catch((error) => {
|
||||||
|
toast.error(error);
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
selectedModel = await getDefaultDiffusionModel(localStorage.token);
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateAUTOMATIC1111UrlHandler = async () => {
|
||||||
|
const res = await updateAUTOMATIC1111Url(localStorage.token, AUTOMATIC1111_BASE_URL).catch(
|
||||||
|
(error) => {
|
||||||
|
toast.error(error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (res) {
|
||||||
|
AUTOMATIC1111_BASE_URL = res;
|
||||||
|
|
||||||
|
await getModels();
|
||||||
|
|
||||||
|
if (models) {
|
||||||
|
toast.success('Server connection verified');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
AUTOMATIC1111_BASE_URL = await getAUTOMATIC1111Url(localStorage.token);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const toggleImageGeneration = async () => {
|
||||||
|
if (AUTOMATIC1111_BASE_URL) {
|
||||||
|
enableImageGeneration = await toggleImageGenerationEnabledStatus(localStorage.token).catch(
|
||||||
|
(error) => {
|
||||||
|
toast.error(error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (enableImageGeneration) {
|
||||||
|
config.set(await getBackendConfig(localStorage.token));
|
||||||
|
getModels();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
enableImageGeneration = false;
|
||||||
|
toast.error('AUTOMATIC1111_BASE_URL not provided');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
onMount(async () => {
|
||||||
|
if ($user.role === 'admin') {
|
||||||
|
enableImageGeneration = await getImageGenerationEnabledStatus(localStorage.token);
|
||||||
|
AUTOMATIC1111_BASE_URL = await getAUTOMATIC1111Url(localStorage.token);
|
||||||
|
|
||||||
|
if (enableImageGeneration && AUTOMATIC1111_BASE_URL) {
|
||||||
|
getModels();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<form
|
||||||
|
class="flex flex-col h-full justify-between space-y-3 text-sm"
|
||||||
|
on:submit|preventDefault={async () => {
|
||||||
|
loading = true;
|
||||||
|
const res = await updateDefaultDiffusionModel(localStorage.token, selectedModel);
|
||||||
|
|
||||||
|
dispatch('save');
|
||||||
|
loading = false;
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div class=" space-y-3 pr-1.5 overflow-y-scroll max-h-80">
|
||||||
|
<div>
|
||||||
|
<div class=" mb-1 text-sm font-medium">Image Settings</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div class=" py-0.5 flex w-full justify-between">
|
||||||
|
<div class=" self-center text-xs font-medium">Image Generation (Experimental)</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
class="p-1 px-3 text-xs flex rounded transition"
|
||||||
|
on:click={() => {
|
||||||
|
toggleImageGeneration();
|
||||||
|
}}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
{#if enableImageGeneration === true}
|
||||||
|
<span class="ml-2 self-center">On</span>
|
||||||
|
{:else}
|
||||||
|
<span class="ml-2 self-center">Off</span>
|
||||||
|
{/if}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<hr class=" dark:border-gray-700" />
|
||||||
|
|
||||||
|
<div class=" mb-2.5 text-sm font-medium">AUTOMATIC1111 Base URL</div>
|
||||||
|
<div class="flex w-full">
|
||||||
|
<div class="flex-1 mr-2">
|
||||||
|
<input
|
||||||
|
class="w-full rounded py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none"
|
||||||
|
placeholder="Enter URL (e.g. http://127.0.0.1:7860/)"
|
||||||
|
bind:value={AUTOMATIC1111_BASE_URL}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
class="px-3 bg-gray-200 hover:bg-gray-300 dark:bg-gray-600 dark:hover:bg-gray-700 rounded transition"
|
||||||
|
type="button"
|
||||||
|
on:click={() => {
|
||||||
|
// updateOllamaAPIUrlHandler();
|
||||||
|
|
||||||
|
updateAUTOMATIC1111UrlHandler();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 20 20"
|
||||||
|
fill="currentColor"
|
||||||
|
class="w-4 h-4"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill-rule="evenodd"
|
||||||
|
d="M15.312 11.424a5.5 5.5 0 01-9.201 2.466l-.312-.311h2.433a.75.75 0 000-1.5H3.989a.75.75 0 00-.75.75v4.242a.75.75 0 001.5 0v-2.43l.31.31a7 7 0 0011.712-3.138.75.75 0 00-1.449-.39zm1.23-3.723a.75.75 0 00.219-.53V2.929a.75.75 0 00-1.5 0V5.36l-.31-.31A7 7 0 003.239 8.188a.75.75 0 101.448.389A5.5 5.5 0 0113.89 6.11l.311.31h-2.432a.75.75 0 000 1.5h4.243a.75.75 0 00.53-.219z"
|
||||||
|
clip-rule="evenodd"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-2 text-xs text-gray-400 dark:text-gray-500">
|
||||||
|
Include `--api` flag when running stable-diffusion-webui
|
||||||
|
<a
|
||||||
|
class=" text-gray-300 font-medium"
|
||||||
|
href="https://github.com/AUTOMATIC1111/stable-diffusion-webui/discussions/3734"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
(e.g. `sh webui.sh --api`)
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{#if enableImageGeneration}
|
||||||
|
<hr class=" dark:border-gray-700" />
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div class=" mb-2.5 text-sm font-medium">Set default model</div>
|
||||||
|
<div class="flex w-full">
|
||||||
|
<div class="flex-1 mr-2">
|
||||||
|
<select
|
||||||
|
class="w-full rounded py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none"
|
||||||
|
bind:value={selectedModel}
|
||||||
|
placeholder="Select a model"
|
||||||
|
>
|
||||||
|
{#if !selectedModel}
|
||||||
|
<option value="" disabled selected>Select a model</option>
|
||||||
|
{/if}
|
||||||
|
{#each models as model}
|
||||||
|
<option value={model.title} class="bg-gray-100 dark:bg-gray-700"
|
||||||
|
>{model.model_name}</option
|
||||||
|
>
|
||||||
|
{/each}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex justify-end pt-3 text-sm font-medium">
|
||||||
|
<button
|
||||||
|
class=" px-4 py-2 bg-emerald-600 hover:bg-emerald-700 text-gray-100 transition rounded flex flex-row space-x-1 items-center {loading
|
||||||
|
? ' cursor-not-allowed'
|
||||||
|
: ''}"
|
||||||
|
type="submit"
|
||||||
|
disabled={loading}
|
||||||
|
>
|
||||||
|
Save
|
||||||
|
|
||||||
|
{#if loading}
|
||||||
|
<div class="ml-2 self-center">
|
||||||
|
<svg
|
||||||
|
class=" w-4 h-4"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="currentColor"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
><style>
|
||||||
|
.spinner_ajPY {
|
||||||
|
transform-origin: center;
|
||||||
|
animation: spinner_AtaB 0.75s infinite linear;
|
||||||
|
}
|
||||||
|
@keyframes spinner_AtaB {
|
||||||
|
100% {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style><path
|
||||||
|
d="M12,1A11,11,0,1,0,23,12,11,11,0,0,0,12,1Zm0,19a8,8,0,1,1,8-8A8,8,0,0,1,12,20Z"
|
||||||
|
opacity=".25"
|
||||||
|
/><path
|
||||||
|
d="M10.14,1.16a11,11,0,0,0-9,8.92A1.59,1.59,0,0,0,2.46,12,1.52,1.52,0,0,0,4.11,10.7a8,8,0,0,1,6.66-6.61A1.42,1.42,0,0,0,12,2.69h0A1.57,1.57,0,0,0,10.14,1.16Z"
|
||||||
|
class="spinner_ajPY"
|
||||||
|
/></svg
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
|
@ -7,14 +7,14 @@
|
||||||
|
|
||||||
import Modal from '../common/Modal.svelte';
|
import Modal from '../common/Modal.svelte';
|
||||||
import Account from './Settings/Account.svelte';
|
import Account from './Settings/Account.svelte';
|
||||||
import Advanced from './Settings/Advanced.svelte';
|
|
||||||
import About from './Settings/About.svelte';
|
import About from './Settings/About.svelte';
|
||||||
import Models from './Settings/Models.svelte';
|
import Models from './Settings/Models.svelte';
|
||||||
import General from './Settings/General.svelte';
|
import General from './Settings/General.svelte';
|
||||||
import External from './Settings/External.svelte';
|
|
||||||
import Interface from './Settings/Interface.svelte';
|
import Interface from './Settings/Interface.svelte';
|
||||||
import Audio from './Settings/Audio.svelte';
|
import Audio from './Settings/Audio.svelte';
|
||||||
import Chats from './Settings/Chats.svelte';
|
import Chats from './Settings/Chats.svelte';
|
||||||
|
import Connections from './Settings/Connections.svelte';
|
||||||
|
import Images from './Settings/Images.svelte';
|
||||||
|
|
||||||
export let show = false;
|
export let show = false;
|
||||||
|
|
||||||
|
@ -102,31 +102,31 @@
|
||||||
<div class=" self-center">General</div>
|
<div class=" self-center">General</div>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
|
||||||
class="px-2.5 py-2.5 min-w-fit rounded-lg flex-1 md:flex-none flex text-right transition {selectedTab ===
|
|
||||||
'advanced'
|
|
||||||
? 'bg-gray-200 dark:bg-gray-700'
|
|
||||||
: ' hover:bg-gray-300 dark:hover:bg-gray-800'}"
|
|
||||||
on:click={() => {
|
|
||||||
selectedTab = 'advanced';
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div class=" self-center mr-2">
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
viewBox="0 0 20 20"
|
|
||||||
fill="currentColor"
|
|
||||||
class="w-4 h-4"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M17 2.75a.75.75 0 00-1.5 0v5.5a.75.75 0 001.5 0v-5.5zM17 15.75a.75.75 0 00-1.5 0v1.5a.75.75 0 001.5 0v-1.5zM3.75 15a.75.75 0 01.75.75v1.5a.75.75 0 01-1.5 0v-1.5a.75.75 0 01.75-.75zM4.5 2.75a.75.75 0 00-1.5 0v5.5a.75.75 0 001.5 0v-5.5zM10 11a.75.75 0 01.75.75v5.5a.75.75 0 01-1.5 0v-5.5A.75.75 0 0110 11zM10.75 2.75a.75.75 0 00-1.5 0v1.5a.75.75 0 001.5 0v-1.5zM10 6a2 2 0 100 4 2 2 0 000-4zM3.75 10a2 2 0 100 4 2 2 0 000-4zM16.25 10a2 2 0 100 4 2 2 0 000-4z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<div class=" self-center">Advanced</div>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
{#if $user?.role === 'admin'}
|
{#if $user?.role === 'admin'}
|
||||||
|
<button
|
||||||
|
class="px-2.5 py-2.5 min-w-fit rounded-lg flex-1 md:flex-none flex text-right transition {selectedTab ===
|
||||||
|
'connections'
|
||||||
|
? 'bg-gray-200 dark:bg-gray-700'
|
||||||
|
: ' hover:bg-gray-300 dark:hover:bg-gray-800'}"
|
||||||
|
on:click={() => {
|
||||||
|
selectedTab = 'connections';
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div class=" self-center mr-2">
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 16 16"
|
||||||
|
fill="currentColor"
|
||||||
|
class="w-4 h-4"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M1 9.5A3.5 3.5 0 0 0 4.5 13H12a3 3 0 0 0 .917-5.857 2.503 2.503 0 0 0-3.198-3.019 3.5 3.5 0 0 0-6.628 2.171A3.5 3.5 0 0 0 1 9.5Z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div class=" self-center">Connections</div>
|
||||||
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
class="px-2.5 py-2.5 min-w-fit rounded-lg flex-1 md:flex-none flex text-right transition {selectedTab ===
|
class="px-2.5 py-2.5 min-w-fit rounded-lg flex-1 md:flex-none flex text-right transition {selectedTab ===
|
||||||
'models'
|
'models'
|
||||||
|
@ -152,30 +152,6 @@
|
||||||
</div>
|
</div>
|
||||||
<div class=" self-center">Models</div>
|
<div class=" self-center">Models</div>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
|
||||||
class="px-2.5 py-2.5 min-w-fit rounded-lg flex-1 md:flex-none flex text-right transition {selectedTab ===
|
|
||||||
'external'
|
|
||||||
? 'bg-gray-200 dark:bg-gray-700'
|
|
||||||
: ' hover:bg-gray-300 dark:hover:bg-gray-800'}"
|
|
||||||
on:click={() => {
|
|
||||||
selectedTab = 'external';
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div class=" self-center mr-2">
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
viewBox="0 0 16 16"
|
|
||||||
fill="currentColor"
|
|
||||||
class="w-4 h-4"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M1 9.5A3.5 3.5 0 0 0 4.5 13H12a3 3 0 0 0 .917-5.857 2.503 2.503 0 0 0-3.198-3.019 3.5 3.5 0 0 0-6.628 2.171A3.5 3.5 0 0 0 1 9.5Z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<div class=" self-center">External</div>
|
|
||||||
</button>
|
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<button
|
<button
|
||||||
|
@ -196,7 +172,7 @@
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
fill-rule="evenodd"
|
fill-rule="evenodd"
|
||||||
d="M2 4a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V4Zm10.5 5.707a.5.5 0 0 0-.146-.353l-1-1a.5.5 0 0 0-.708 0L9.354 9.646a.5.5 0 0 1-.708 0L6.354 7.354a.5.5 0 0 0-.708 0l-2 2a.5.5 0 0 0-.146.353V12a.5.5 0 0 0 .5.5h8a.5.5 0 0 0 .5-.5V9.707ZM12 5a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z"
|
d="M2 4.25A2.25 2.25 0 0 1 4.25 2h7.5A2.25 2.25 0 0 1 14 4.25v5.5A2.25 2.25 0 0 1 11.75 12h-1.312c.1.128.21.248.328.36a.75.75 0 0 1 .234.545v.345a.75.75 0 0 1-.75.75h-4.5a.75.75 0 0 1-.75-.75v-.345a.75.75 0 0 1 .234-.545c.118-.111.228-.232.328-.36H4.25A2.25 2.25 0 0 1 2 9.75v-5.5Zm2.25-.75a.75.75 0 0 0-.75.75v4.5c0 .414.336.75.75.75h7.5a.75.75 0 0 0 .75-.75v-4.5a.75.75 0 0 0-.75-.75h-7.5Z"
|
||||||
clip-rule="evenodd"
|
clip-rule="evenodd"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
|
@ -231,6 +207,34 @@
|
||||||
<div class=" self-center">Audio</div>
|
<div class=" self-center">Audio</div>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
{#if $user.role === 'admin'}
|
||||||
|
<button
|
||||||
|
class="px-2.5 py-2.5 min-w-fit rounded-lg flex-1 md:flex-none flex text-right transition {selectedTab ===
|
||||||
|
'images'
|
||||||
|
? 'bg-gray-200 dark:bg-gray-700'
|
||||||
|
: ' hover:bg-gray-300 dark:hover:bg-gray-800'}"
|
||||||
|
on:click={() => {
|
||||||
|
selectedTab = 'images';
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div class=" self-center mr-2">
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 16 16"
|
||||||
|
fill="currentColor"
|
||||||
|
class="w-4 h-4"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill-rule="evenodd"
|
||||||
|
d="M2 4a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V4Zm10.5 5.707a.5.5 0 0 0-.146-.353l-1-1a.5.5 0 0 0-.708 0L9.354 9.646a.5.5 0 0 1-.708 0L6.354 7.354a.5.5 0 0 0-.708 0l-2 2a.5.5 0 0 0-.146.353V12a.5.5 0 0 0 .5.5h8a.5.5 0 0 0 .5-.5V9.707ZM12 5a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z"
|
||||||
|
clip-rule="evenodd"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div class=" self-center">Images</div>
|
||||||
|
</button>
|
||||||
|
{/if}
|
||||||
|
|
||||||
<button
|
<button
|
||||||
class="px-2.5 py-2.5 min-w-fit rounded-lg flex-1 md:flex-none flex text-right transition {selectedTab ===
|
class="px-2.5 py-2.5 min-w-fit rounded-lg flex-1 md:flex-none flex text-right transition {selectedTab ===
|
||||||
'chats'
|
'chats'
|
||||||
|
@ -318,17 +322,10 @@
|
||||||
show = false;
|
show = false;
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{:else if selectedTab === 'advanced'}
|
|
||||||
<Advanced
|
|
||||||
on:save={() => {
|
|
||||||
show = false;
|
|
||||||
}}
|
|
||||||
{saveSettings}
|
|
||||||
/>
|
|
||||||
{:else if selectedTab === 'models'}
|
{:else if selectedTab === 'models'}
|
||||||
<Models {getModels} />
|
<Models {getModels} />
|
||||||
{:else if selectedTab === 'external'}
|
{:else if selectedTab === 'connections'}
|
||||||
<External
|
<Connections
|
||||||
{getModels}
|
{getModels}
|
||||||
on:save={() => {
|
on:save={() => {
|
||||||
show = false;
|
show = false;
|
||||||
|
@ -348,6 +345,13 @@
|
||||||
show = false;
|
show = false;
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
{:else if selectedTab === 'images'}
|
||||||
|
<Images
|
||||||
|
{saveSettings}
|
||||||
|
on:save={() => {
|
||||||
|
show = false;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
{:else if selectedTab === 'chats'}
|
{:else if selectedTab === 'chats'}
|
||||||
<Chats {saveSettings} />
|
<Chats {saveSettings} />
|
||||||
{:else if selectedTab === 'account'}
|
{:else if selectedTab === 'account'}
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
} else if (size === 'sm') {
|
} else if (size === 'sm') {
|
||||||
return 'w-[30rem]';
|
return 'w-[30rem]';
|
||||||
} else {
|
} else {
|
||||||
return 'w-[42rem]';
|
return 'w-[44rem]';
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { dev } from '$app/environment';
|
import { dev } from '$app/environment';
|
||||||
|
// import { version } from '../../package.json';
|
||||||
|
|
||||||
export const WEBUI_NAME = 'Open WebUI';
|
export const WEBUI_NAME = 'Open WebUI';
|
||||||
export const WEBUI_BASE_URL = dev ? `http://${location.hostname}:8080` : ``;
|
export const WEBUI_BASE_URL = dev ? `http://${location.hostname}:8080` : ``;
|
||||||
|
@ -6,10 +7,11 @@ export const WEBUI_BASE_URL = dev ? `http://${location.hostname}:8080` : ``;
|
||||||
export const WEBUI_API_BASE_URL = `${WEBUI_BASE_URL}/api/v1`;
|
export const WEBUI_API_BASE_URL = `${WEBUI_BASE_URL}/api/v1`;
|
||||||
export const OLLAMA_API_BASE_URL = `${WEBUI_BASE_URL}/ollama/api`;
|
export const OLLAMA_API_BASE_URL = `${WEBUI_BASE_URL}/ollama/api`;
|
||||||
export const OPENAI_API_BASE_URL = `${WEBUI_BASE_URL}/openai/api`;
|
export const OPENAI_API_BASE_URL = `${WEBUI_BASE_URL}/openai/api`;
|
||||||
export const RAG_API_BASE_URL = `${WEBUI_BASE_URL}/rag/api/v1`;
|
|
||||||
export const AUDIO_API_BASE_URL = `${WEBUI_BASE_URL}/audio/api/v1`;
|
export const AUDIO_API_BASE_URL = `${WEBUI_BASE_URL}/audio/api/v1`;
|
||||||
|
export const IMAGES_API_BASE_URL = `${WEBUI_BASE_URL}/images/api/v1`;
|
||||||
|
export const RAG_API_BASE_URL = `${WEBUI_BASE_URL}/rag/api/v1`;
|
||||||
|
|
||||||
export const WEB_UI_VERSION = 'v1.0.0-alpha-static';
|
export const WEB_UI_VERSION = APP_VERSION;
|
||||||
|
|
||||||
export const REQUIRED_OLLAMA_VERSION = '0.1.16';
|
export const REQUIRED_OLLAMA_VERSION = '0.1.16';
|
||||||
|
|
||||||
|
|
|
@ -2,5 +2,8 @@ import { sveltekit } from '@sveltejs/kit/vite';
|
||||||
import { defineConfig } from 'vite';
|
import { defineConfig } from 'vite';
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [sveltekit()]
|
plugins: [sveltekit()],
|
||||||
|
define: {
|
||||||
|
APP_VERSION: JSON.stringify(process.env.npm_package_version)
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in a new issue