Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Menu
Open sidebar
chenpangpang
open-webui
Commits
6a36039a
Commit
6a36039a
authored
May 27, 2024
by
Jun Siang Cheah
Browse files
Merge remote-tracking branch 'upstream/dev' into feat/oauth
parents
985fdca5
b6b71c08
Changes
77
Hide whitespace changes
Inline
Side-by-side
Showing
20 changed files
with
621 additions
and
44 deletions
+621
-44
.github/workflows/docker-build.yaml
.github/workflows/docker-build.yaml
+8
-2
Dockerfile
Dockerfile
+6
-0
backend/apps/openai/main.py
backend/apps/openai/main.py
+3
-3
backend/apps/webui/internal/migrations/011_add_user_settings.py
...d/apps/webui/internal/migrations/011_add_user_settings.py
+48
-0
backend/apps/webui/main.py
backend/apps/webui/main.py
+5
-3
backend/apps/webui/models/users.py
backend/apps/webui/models/users.py
+10
-2
backend/apps/webui/routers/configs.py
backend/apps/webui/routers/configs.py
+30
-0
backend/apps/webui/routers/users.py
backend/apps/webui/routers/users.py
+45
-1
backend/config.py
backend/config.py
+25
-2
backend/main.py
backend/main.py
+24
-7
hatch_build.py
hatch_build.py
+2
-0
package-lock.json
package-lock.json
+2
-2
package.json
package.json
+1
-1
src/lib/apis/configs/index.ts
src/lib/apis/configs/index.ts
+58
-0
src/lib/apis/index.ts
src/lib/apis/index.ts
+55
-1
src/lib/apis/users/index.ts
src/lib/apis/users/index.ts
+56
-0
src/lib/components/admin/Settings/Banners.svelte
src/lib/components/admin/Settings/Banners.svelte
+137
-0
src/lib/components/admin/Settings/Database.svelte
src/lib/components/admin/Settings/Database.svelte
+1
-1
src/lib/components/admin/Settings/General.svelte
src/lib/components/admin/Settings/General.svelte
+69
-5
src/lib/components/admin/Settings/Users.svelte
src/lib/components/admin/Settings/Users.svelte
+36
-14
No files found.
.github/workflows/docker-build.yaml
View file @
6a36039a
...
...
@@ -84,6 +84,8 @@ jobs:
outputs
:
type=image,name=${{ env.FULL_IMAGE_NAME }},push-by-digest=true,name-canonical=true,push=true
cache-from
:
type=registry,ref=${{ steps.cache-meta.outputs.tags }}
cache-to
:
type=registry,ref=${{ steps.cache-meta.outputs.tags }},mode=max
build-args
:
|
BUILD_HASH=${{ github.sha }}
-
name
:
Export digest
run
:
|
...
...
@@ -170,7 +172,9 @@ jobs:
outputs
:
type=image,name=${{ env.FULL_IMAGE_NAME }},push-by-digest=true,name-canonical=true,push=true
cache-from
:
type=registry,ref=${{ steps.cache-meta.outputs.tags }}
cache-to
:
type=registry,ref=${{ steps.cache-meta.outputs.tags }},mode=max
build-args
:
USE_CUDA=true
build-args
:
|
BUILD_HASH=${{ github.sha }}
USE_CUDA=true
-
name
:
Export digest
run
:
|
...
...
@@ -257,7 +261,9 @@ jobs:
outputs
:
type=image,name=${{ env.FULL_IMAGE_NAME }},push-by-digest=true,name-canonical=true,push=true
cache-from
:
type=registry,ref=${{ steps.cache-meta.outputs.tags }}
cache-to
:
type=registry,ref=${{ steps.cache-meta.outputs.tags }},mode=max
build-args
:
USE_OLLAMA=true
build-args
:
|
BUILD_HASH=${{ github.sha }}
USE_OLLAMA=true
-
name
:
Export digest
run
:
|
...
...
Dockerfile
View file @
6a36039a
...
...
@@ -11,12 +11,14 @@ ARG USE_CUDA_VER=cu121
# IMPORTANT: If you change the embedding model (sentence-transformers/all-MiniLM-L6-v2) and vice versa, you aren't able to use RAG Chat with your previous documents loaded in the WebUI! You need to re-embed them.
ARG
USE_EMBEDDING_MODEL=sentence-transformers/all-MiniLM-L6-v2
ARG
USE_RERANKING_MODEL=""
ARG
BUILD_HASH=dev-build
# Override at your own risk - non-root configurations are untested
ARG
UID=0
ARG
GID=0
######## WebUI frontend ########
FROM
--platform=$BUILDPLATFORM node:21-alpine3.19 as build
ARG
BUILD_HASH
WORKDIR
/app
...
...
@@ -24,6 +26,7 @@ COPY package.json package-lock.json ./
RUN
npm ci
COPY
. .
ENV
APP_BUILD_HASH=${BUILD_HASH}
RUN
npm run build
######## WebUI backend ########
...
...
@@ -35,6 +38,7 @@ ARG USE_OLLAMA
ARG
USE_CUDA_VER
ARG
USE_EMBEDDING_MODEL
ARG
USE_RERANKING_MODEL
ARG
BUILD_HASH
ARG
UID
ARG
GID
...
...
@@ -150,4 +154,6 @@ HEALTHCHECK CMD curl --silent --fail http://localhost:8080/health | jq -e '.stat
USER
$UID:$GID
ENV
WEBUI_BUILD_VERSION=${BUILD_HASH}
CMD
[ "bash", "start.sh"]
backend/apps/openai/main.py
View file @
6a36039a
...
...
@@ -198,7 +198,7 @@ async def fetch_url(url, key):
def
merge_models_lists
(
model_lists
):
log
.
info
(
f
"merge_models_lists
{
model_lists
}
"
)
log
.
debug
(
f
"merge_models_lists
{
model_lists
}
"
)
merged_list
=
[]
for
idx
,
models
in
enumerate
(
model_lists
):
...
...
@@ -237,7 +237,7 @@ async def get_all_models():
]
responses
=
await
asyncio
.
gather
(
*
tasks
)
log
.
info
(
f
"get_all_models:responses()
{
responses
}
"
)
log
.
debug
(
f
"get_all_models:responses()
{
responses
}
"
)
models
=
{
"data"
:
merge_models_lists
(
...
...
@@ -254,7 +254,7 @@ async def get_all_models():
)
}
log
.
info
(
f
"models:
{
models
}
"
)
log
.
debug
(
f
"models:
{
models
}
"
)
app
.
state
.
MODELS
=
{
model
[
"id"
]:
model
for
model
in
models
[
"data"
]}
return
models
...
...
backend/apps/webui/internal/migrations/011_add_user_settings.py
0 → 100644
View file @
6a36039a
"""Peewee migrations -- 002_add_local_sharing.py.
Some examples (model - class or model name)::
> Model = migrator.orm['table_name'] # Return model in current state by name
> Model = migrator.ModelClass # Return model in current state by name
> migrator.sql(sql) # Run custom SQL
> migrator.run(func, *args, **kwargs) # Run python function with the given args
> migrator.create_model(Model) # Create a model (could be used as decorator)
> migrator.remove_model(model, cascade=True) # Remove a model
> migrator.add_fields(model, **fields) # Add fields to a model
> migrator.change_fields(model, **fields) # Change fields
> migrator.remove_fields(model, *field_names, cascade=True)
> migrator.rename_field(model, old_field_name, new_field_name)
> migrator.rename_table(model, new_table_name)
> migrator.add_index(model, *col_names, unique=False)
> migrator.add_not_null(model, *field_names)
> migrator.add_default(model, field_name, default)
> migrator.add_constraint(model, name, sql)
> migrator.drop_index(model, *col_names)
> migrator.drop_not_null(model, *field_names)
> migrator.drop_constraints(model, *constraints)
"""
from
contextlib
import
suppress
import
peewee
as
pw
from
peewee_migrate
import
Migrator
with
suppress
(
ImportError
):
import
playhouse.postgres_ext
as
pw_pext
def
migrate
(
migrator
:
Migrator
,
database
:
pw
.
Database
,
*
,
fake
=
False
):
"""Write your migrations here."""
# Adding fields settings to the 'user' table
migrator
.
add_fields
(
"user"
,
settings
=
pw
.
TextField
(
null
=
True
))
def
rollback
(
migrator
:
Migrator
,
database
:
pw
.
Database
,
*
,
fake
=
False
):
"""Write your rollback migrations here."""
# Remove the settings field
migrator
.
remove_fields
(
"user"
,
"settings"
)
backend/apps/webui/main.py
View file @
6a36039a
...
...
@@ -15,7 +15,7 @@ from apps.webui.routers import (
utils
,
)
from
config
import
(
WEBUI_
VERSION
,
WEBUI_
BUILD_HASH
,
WEBUI_AUTH
,
DEFAULT_MODELS
,
DEFAULT_PROMPT_SUGGESTIONS
,
...
...
@@ -26,8 +26,8 @@ from config import (
WEBUI_AUTH_TRUSTED_EMAIL_HEADER
,
JWT_EXPIRES_IN
,
AppConfig
,
WEBUI_SECRET_KEY
,
OAUTH_PROVID
ERS
,
ENABLE_COMMUNITY_SHARING
,
WEBUI_BANN
ERS
,
)
app
=
FastAPI
()
...
...
@@ -44,7 +44,9 @@ app.state.config.DEFAULT_PROMPT_SUGGESTIONS = DEFAULT_PROMPT_SUGGESTIONS
app
.
state
.
config
.
DEFAULT_USER_ROLE
=
DEFAULT_USER_ROLE
app
.
state
.
config
.
USER_PERMISSIONS
=
USER_PERMISSIONS
app
.
state
.
config
.
WEBHOOK_URL
=
WEBHOOK_URL
app
.
state
.
config
.
BANNERS
=
WEBUI_BANNERS
app
.
state
.
config
.
ENABLE_COMMUNITY_SHARING
=
ENABLE_COMMUNITY_SHARING
app
.
state
.
MODELS
=
{}
app
.
state
.
AUTH_TRUSTED_EMAIL_HEADER
=
WEBUI_AUTH_TRUSTED_EMAIL_HEADER
...
...
backend/apps/webui/models/users.py
View file @
6a36039a
from
pydantic
import
BaseModel
from
pydantic
import
BaseModel
,
ConfigDict
from
peewee
import
*
from
playhouse.shortcuts
import
model_to_dict
from
typing
import
List
,
Union
,
Optional
import
time
from
utils.misc
import
get_gravatar_url
from
apps.webui.internal.db
import
DB
from
apps.webui.internal.db
import
DB
,
JSONField
from
apps.webui.models.chats
import
Chats
####################
...
...
@@ -25,6 +25,7 @@ class User(Model):
created_at
=
BigIntegerField
()
api_key
=
CharField
(
null
=
True
,
unique
=
True
)
settings
=
JSONField
(
null
=
True
)
oauth_sub
=
TextField
(
null
=
True
,
unique
=
True
)
...
...
@@ -32,6 +33,12 @@ class User(Model):
database
=
DB
class
UserSettings
(
BaseModel
):
ui
:
Optional
[
dict
]
=
{}
model_config
=
ConfigDict
(
extra
=
"allow"
)
pass
class
UserModel
(
BaseModel
):
id
:
str
name
:
str
...
...
@@ -44,6 +51,7 @@ class UserModel(BaseModel):
created_at
:
int
# timestamp in epoch
api_key
:
Optional
[
str
]
=
None
settings
:
Optional
[
UserSettings
]
=
None
oauth_sub
:
Optional
[
str
]
=
None
...
...
backend/apps/webui/routers/configs.py
View file @
6a36039a
...
...
@@ -8,6 +8,8 @@ from pydantic import BaseModel
import
time
import
uuid
from
config
import
BannerModel
from
apps.webui.models.users
import
Users
from
utils.utils
import
(
...
...
@@ -57,3 +59,31 @@ async def set_global_default_suggestions(
data
=
form_data
.
model_dump
()
request
.
app
.
state
.
config
.
DEFAULT_PROMPT_SUGGESTIONS
=
data
[
"suggestions"
]
return
request
.
app
.
state
.
config
.
DEFAULT_PROMPT_SUGGESTIONS
############################
# SetBanners
############################
class
SetBannersForm
(
BaseModel
):
banners
:
List
[
BannerModel
]
@
router
.
post
(
"/banners"
,
response_model
=
List
[
BannerModel
])
async
def
set_banners
(
request
:
Request
,
form_data
:
SetBannersForm
,
user
=
Depends
(
get_admin_user
),
):
data
=
form_data
.
model_dump
()
request
.
app
.
state
.
config
.
BANNERS
=
data
[
"banners"
]
return
request
.
app
.
state
.
config
.
BANNERS
@
router
.
get
(
"/banners"
,
response_model
=
List
[
BannerModel
])
async
def
get_banners
(
request
:
Request
,
user
=
Depends
(
get_current_user
),
):
return
request
.
app
.
state
.
config
.
BANNERS
backend/apps/webui/routers/users.py
View file @
6a36039a
...
...
@@ -9,7 +9,13 @@ import time
import
uuid
import
logging
from
apps.webui.models.users
import
UserModel
,
UserUpdateForm
,
UserRoleUpdateForm
,
Users
from
apps.webui.models.users
import
(
UserModel
,
UserUpdateForm
,
UserRoleUpdateForm
,
UserSettings
,
Users
,
)
from
apps.webui.models.auths
import
Auths
from
apps.webui.models.chats
import
Chats
...
...
@@ -68,6 +74,42 @@ async def update_user_role(form_data: UserRoleUpdateForm, user=Depends(get_admin
)
############################
# GetUserSettingsBySessionUser
############################
@
router
.
get
(
"/user/settings"
,
response_model
=
Optional
[
UserSettings
])
async
def
get_user_settings_by_session_user
(
user
=
Depends
(
get_verified_user
)):
user
=
Users
.
get_user_by_id
(
user
.
id
)
if
user
:
return
user
.
settings
else
:
raise
HTTPException
(
status_code
=
status
.
HTTP_400_BAD_REQUEST
,
detail
=
ERROR_MESSAGES
.
USER_NOT_FOUND
,
)
############################
# UpdateUserSettingsBySessionUser
############################
@
router
.
post
(
"/user/settings/update"
,
response_model
=
UserSettings
)
async
def
update_user_settings_by_session_user
(
form_data
:
UserSettings
,
user
=
Depends
(
get_verified_user
)
):
user
=
Users
.
update_user_by_id
(
user
.
id
,
{
"settings"
:
form_data
.
model_dump
()})
if
user
:
return
user
.
settings
else
:
raise
HTTPException
(
status_code
=
status
.
HTTP_400_BAD_REQUEST
,
detail
=
ERROR_MESSAGES
.
USER_NOT_FOUND
,
)
############################
# GetUserById
############################
...
...
@@ -81,6 +123,8 @@ class UserResponse(BaseModel):
@
router
.
get
(
"/{user_id}"
,
response_model
=
UserResponse
)
async
def
get_user_by_id
(
user_id
:
str
,
user
=
Depends
(
get_verified_user
)):
# Check if user_id is a shared chat
# If it is, get the user_id from the chat
if
user_id
.
startswith
(
"shared-"
):
chat_id
=
user_id
.
replace
(
"shared-"
,
""
)
chat
=
Chats
.
get_chat_by_id
(
chat_id
)
...
...
backend/config.py
View file @
6a36039a
...
...
@@ -8,6 +8,8 @@ from chromadb import Settings
from
base64
import
b64encode
from
bs4
import
BeautifulSoup
from
typing
import
TypeVar
,
Generic
,
Union
from
pydantic
import
BaseModel
from
typing
import
Optional
from
pathlib
import
Path
import
json
...
...
@@ -166,10 +168,10 @@ CHANGELOG = changelog_json
####################################
# WEBUI_
VERSION
# WEBUI_
BUILD_HASH
####################################
WEBUI_
VERSION
=
os
.
environ
.
get
(
"WEBUI_
VERSION"
,
"v1.0.0-alpha.100
"
)
WEBUI_
BUILD_HASH
=
os
.
environ
.
get
(
"WEBUI_
BUILD_HASH"
,
"dev-build
"
)
####################################
# DATA/FRONTEND BUILD DIR
...
...
@@ -695,6 +697,27 @@ WEBHOOK_URL = PersistentConfig(
ENABLE_ADMIN_EXPORT
=
os
.
environ
.
get
(
"ENABLE_ADMIN_EXPORT"
,
"True"
).
lower
()
==
"true"
ENABLE_COMMUNITY_SHARING
=
PersistentConfig
(
"ENABLE_COMMUNITY_SHARING"
,
"ui.enable_community_sharing"
,
os
.
environ
.
get
(
"ENABLE_COMMUNITY_SHARING"
,
"True"
).
lower
()
==
"true"
,
)
class
BannerModel
(
BaseModel
):
id
:
str
type
:
str
title
:
Optional
[
str
]
=
None
content
:
str
dismissible
:
bool
timestamp
:
int
WEBUI_BANNERS
=
PersistentConfig
(
"WEBUI_BANNERS"
,
"ui.banners"
,
[
BannerModel
(
**
banner
)
for
banner
in
json
.
loads
(
"[]"
)],
)
####################################
# WEBUI_SECRET_KEY
####################################
...
...
backend/main.py
View file @
6a36039a
...
...
@@ -68,6 +68,7 @@ from config import (
WEBHOOK_URL
,
ENABLE_ADMIN_EXPORT
,
AppConfig
,
WEBUI_BUILD_HASH
,
OAUTH_PROVIDERS
,
ENABLE_OAUTH_SIGNUP
,
OAUTH_MERGE_ACCOUNTS_BY_EMAIL
,
...
...
@@ -102,7 +103,8 @@ print(
|_|
v
{
VERSION
}
- building the best open-source AI user interface.
v
{
VERSION
}
- building the best open-source AI user interface.
{
f
"Commit:
{
WEBUI_BUILD_HASH
}
" if WEBUI_BUILD_HASH != "
dev
-
build
" else ""
}
https://github.com/open-webui/open-webui
"""
)
...
...
@@ -374,15 +376,17 @@ async def get_app_config():
"status"
:
True
,
"name"
:
WEBUI_NAME
,
"version"
:
VERSION
,
"auth"
:
WEBUI_AUTH
,
"auth_trusted_header"
:
bool
(
webui_app
.
state
.
AUTH_TRUSTED_EMAIL_HEADER
),
"enable_signup"
:
webui_app
.
state
.
config
.
ENABLE_SIGNUP
,
"enable_image_generation"
:
images_app
.
state
.
config
.
ENABLED
,
"enable_admin_export"
:
ENABLE_ADMIN_EXPORT
,
"default_locale"
:
default_locale
,
"default_models"
:
webui_app
.
state
.
config
.
DEFAULT_MODELS
,
"default_prompt_suggestions"
:
webui_app
.
state
.
config
.
DEFAULT_PROMPT_SUGGESTIONS
,
"trusted_header_auth"
:
bool
(
webui_app
.
state
.
AUTH_TRUSTED_EMAIL_HEADER
),
"features"
:
{
"auth"
:
WEBUI_AUTH
,
"auth_trusted_header"
:
bool
(
webui_app
.
state
.
AUTH_TRUSTED_EMAIL_HEADER
),
"enable_signup"
:
webui_app
.
state
.
config
.
ENABLE_SIGNUP
,
"enable_image_generation"
:
images_app
.
state
.
config
.
ENABLED
,
"enable_admin_export"
:
ENABLE_ADMIN_EXPORT
,
"enable_community_sharing"
:
webui_app
.
state
.
config
.
ENABLE_COMMUNITY_SHARING
,
},
"oauth"
:
{
"providers"
:
{
name
:
config
.
get
(
"name"
,
name
)
...
...
@@ -439,6 +443,19 @@ async def update_webhook_url(form_data: UrlForm, user=Depends(get_admin_user)):
}
@
app
.
get
(
"/api/community_sharing"
,
response_model
=
bool
)
async
def
get_community_sharing_status
(
request
:
Request
,
user
=
Depends
(
get_admin_user
)):
return
webui_app
.
state
.
config
.
ENABLE_COMMUNITY_SHARING
@
app
.
get
(
"/api/community_sharing/toggle"
,
response_model
=
bool
)
async
def
toggle_community_sharing
(
request
:
Request
,
user
=
Depends
(
get_admin_user
)):
webui_app
.
state
.
config
.
ENABLE_COMMUNITY_SHARING
=
(
not
webui_app
.
state
.
config
.
ENABLE_COMMUNITY_SHARING
)
return
webui_app
.
state
.
config
.
ENABLE_COMMUNITY_SHARING
@
app
.
get
(
"/api/version"
)
async
def
get_app_config
():
return
{
...
...
hatch_build.py
View file @
6a36039a
# noqa: INP001
import
os
import
shutil
import
subprocess
from
sys
import
stderr
...
...
@@ -18,4 +19,5 @@ class CustomBuildHook(BuildHookInterface):
stderr
.
write
(
"### npm install
\n
"
)
subprocess
.
run
([
npm
,
"install"
],
check
=
True
)
# noqa: S603
stderr
.
write
(
"
\n
### npm run build
\n
"
)
os
.
environ
[
"APP_BUILD_HASH"
]
=
version
subprocess
.
run
([
npm
,
"run"
,
"build"
],
check
=
True
)
# noqa: S603
package-lock.json
View file @
6a36039a
{
"name"
:
"open-webui"
,
"version"
:
"0.2.0.dev
1
"
,
"version"
:
"0.2.0.dev
2
"
,
"lockfileVersion"
:
3
,
"requires"
:
true
,
"packages"
:
{
""
:
{
"name"
:
"open-webui"
,
"version"
:
"0.2.0.dev
1
"
,
"version"
:
"0.2.0.dev
2
"
,
"dependencies"
:
{
"@pyscript/core"
:
"^0.4.32"
,
"@sveltejs/adapter-node"
:
"^1.3.1"
,
...
...
package.json
View file @
6a36039a
{
"name"
:
"open-webui"
,
"version"
:
"0.2.0.dev
1
"
,
"version"
:
"0.2.0.dev
2
"
,
"private"
:
true
,
"scripts"
:
{
"dev"
:
"npm run pyodide:fetch && vite dev --host"
,
...
...
src/lib/apis/configs/index.ts
View file @
6a36039a
import
{
WEBUI_API_BASE_URL
}
from
'
$lib/constants
'
;
import
type
{
Banner
}
from
'
$lib/types
'
;
export
const
setDefaultModels
=
async
(
token
:
string
,
models
:
string
)
=>
{
let
error
=
null
;
...
...
@@ -59,3 +60,60 @@ export const setDefaultPromptSuggestions = async (token: string, promptSuggestio
return
res
;
};
export
const
getBanners
=
async
(
token
:
string
):
Promise
<
Banner
[]
>
=>
{
let
error
=
null
;
const
res
=
await
fetch
(
`
${
WEBUI_API_BASE_URL
}
/configs/banners`
,
{
method
:
'
GET
'
,
headers
:
{
'
Content-Type
'
:
'
application/json
'
,
Authorization
:
`Bearer
${
token
}
`
}
})
.
then
(
async
(
res
)
=>
{
if
(
!
res
.
ok
)
throw
await
res
.
json
();
return
res
.
json
();
})
.
catch
((
err
)
=>
{
console
.
log
(
err
);
error
=
err
.
detail
;
return
null
;
});
if
(
error
)
{
throw
error
;
}
return
res
;
};
export
const
setBanners
=
async
(
token
:
string
,
banners
:
Banner
[])
=>
{
let
error
=
null
;
const
res
=
await
fetch
(
`
${
WEBUI_API_BASE_URL
}
/configs/banners`
,
{
method
:
'
POST
'
,
headers
:
{
'
Content-Type
'
:
'
application/json
'
,
Authorization
:
`Bearer
${
token
}
`
},
body
:
JSON
.
stringify
({
banners
:
banners
})
})
.
then
(
async
(
res
)
=>
{
if
(
!
res
.
ok
)
throw
await
res
.
json
();
return
res
.
json
();
})
.
catch
((
err
)
=>
{
console
.
log
(
err
);
error
=
err
.
detail
;
return
null
;
});
if
(
error
)
{
throw
error
;
}
return
res
;
};
src/lib/apis/index.ts
View file @
6a36039a
import
{
WEBUI_BASE_URL
}
from
'
$lib/constants
'
;
import
{
WEBUI_API_BASE_URL
,
WEBUI_BASE_URL
}
from
'
$lib/constants
'
;
export
const
getModels
=
async
(
token
:
string
=
''
)
=>
{
let
error
=
null
;
...
...
@@ -246,6 +246,60 @@ export const updateWebhookUrl = async (token: string, url: string) => {
return
res
.
url
;
};
export
const
getCommunitySharingEnabledStatus
=
async
(
token
:
string
)
=>
{
let
error
=
null
;
const
res
=
await
fetch
(
`
${
WEBUI_BASE_URL
}
/api/community_sharing`
,
{
method
:
'
GET
'
,
headers
:
{
'
Content-Type
'
:
'
application/json
'
,
Authorization
:
`Bearer
${
token
}
`
}
})
.
then
(
async
(
res
)
=>
{
if
(
!
res
.
ok
)
throw
await
res
.
json
();
return
res
.
json
();
})
.
catch
((
err
)
=>
{
console
.
log
(
err
);
error
=
err
;
return
null
;
});
if
(
error
)
{
throw
error
;
}
return
res
;
};
export
const
toggleCommunitySharingEnabledStatus
=
async
(
token
:
string
)
=>
{
let
error
=
null
;
const
res
=
await
fetch
(
`
${
WEBUI_BASE_URL
}
/api/community_sharing/toggle`
,
{
method
:
'
GET
'
,
headers
:
{
'
Content-Type
'
:
'
application/json
'
,
Authorization
:
`Bearer
${
token
}
`
}
})
.
then
(
async
(
res
)
=>
{
if
(
!
res
.
ok
)
throw
await
res
.
json
();
return
res
.
json
();
})
.
catch
((
err
)
=>
{
console
.
log
(
err
);
error
=
err
.
detail
;
return
null
;
});
if
(
error
)
{
throw
error
;
}
return
res
;
};
export
const
getModelConfig
=
async
(
token
:
string
):
Promise
<
GlobalModelConfig
>
=>
{
let
error
=
null
;
...
...
src/lib/apis/users/index.ts
View file @
6a36039a
...
...
@@ -115,6 +115,62 @@ export const getUsers = async (token: string) => {
return
res
?
res
:
[];
};
export
const
getUserSettings
=
async
(
token
:
string
)
=>
{
let
error
=
null
;
const
res
=
await
fetch
(
`
${
WEBUI_API_BASE_URL
}
/users/user/settings`
,
{
method
:
'
GET
'
,
headers
:
{
'
Content-Type
'
:
'
application/json
'
,
Authorization
:
`Bearer
${
token
}
`
}
})
.
then
(
async
(
res
)
=>
{
if
(
!
res
.
ok
)
throw
await
res
.
json
();
return
res
.
json
();
})
.
catch
((
err
)
=>
{
console
.
log
(
err
);
error
=
err
.
detail
;
return
null
;
});
if
(
error
)
{
throw
error
;
}
return
res
;
};
export
const
updateUserSettings
=
async
(
token
:
string
,
settings
:
object
)
=>
{
let
error
=
null
;
const
res
=
await
fetch
(
`
${
WEBUI_API_BASE_URL
}
/users/user/settings/update`
,
{
method
:
'
POST
'
,
headers
:
{
'
Content-Type
'
:
'
application/json
'
,
Authorization
:
`Bearer
${
token
}
`
},
body
:
JSON
.
stringify
({
...
settings
})
})
.
then
(
async
(
res
)
=>
{
if
(
!
res
.
ok
)
throw
await
res
.
json
();
return
res
.
json
();
})
.
catch
((
err
)
=>
{
console
.
log
(
err
);
error
=
err
.
detail
;
return
null
;
});
if
(
error
)
{
throw
error
;
}
return
res
;
};
export
const
getUserById
=
async
(
token
:
string
,
userId
:
string
)
=>
{
let
error
=
null
;
...
...
src/lib/components/admin/Settings/Banners.svelte
0 → 100644
View file @
6a36039a
<script lang="ts">
import { v4 as uuidv4 } from 'uuid';
import { getContext, onMount } from 'svelte';
import { banners as _banners } from '$lib/stores';
import type { Banner } from '$lib/types';
import { getBanners, setBanners } from '$lib/apis/configs';
import type { Writable } from 'svelte/store';
import type { i18n as i18nType } from 'i18next';
import Tooltip from '$lib/components/common/Tooltip.svelte';
import Switch from '$lib/components/common/Switch.svelte';
const i18n: Writable<i18nType> = getContext('i18n');
export let saveHandler: Function;
let banners: Banner[] = [];
onMount(async () => {
banners = await getBanners(localStorage.token);
});
const updateBanners = async () => {
_banners.set(await setBanners(localStorage.token, banners));
};
</script>
<form
class="flex flex-col h-full justify-between space-y-3 text-sm"
on:submit|preventDefault={async () => {
updateBanners();
saveHandler();
}}
>
<div class=" space-y-3 pr-1.5 overflow-y-scroll max-h-80 h-full">
<div class=" space-y-3 pr-1.5">
<div class="flex w-full justify-between mb-2">
<div class=" self-center text-sm font-semibold">
{$i18n.t('Banners')}
</div>
<button
class="p-1 px-3 text-xs flex rounded transition"
type="button"
on:click={() => {
if (banners.length === 0 || banners.at(-1).content !== '') {
banners = [
...banners,
{
id: uuidv4(),
type: '',
title: '',
content: '',
dismissible: true,
timestamp: Math.floor(Date.now() / 1000)
}
];
}
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
class="w-4 h-4"
>
<path
d="M10.75 4.75a.75.75 0 00-1.5 0v4.5h-4.5a.75.75 0 000 1.5h4.5v4.5a.75.75 0 001.5 0v-4.5h4.5a.75.75 0 000-1.5h-4.5v-4.5z"
/>
</svg>
</button>
</div>
<div class="flex flex-col space-y-1">
{#each banners as banner, bannerIdx}
<div class=" flex justify-between">
<div class="flex flex-row flex-1 border rounded-xl dark:border-gray-800">
<select
class="w-fit capitalize rounded-xl py-2 px-4 text-xs bg-transparent outline-none"
bind:value={banner.type}
>
{#if banner.type == ''}
<option value="" selected disabled class="text-gray-900">{$i18n.t('Type')}</option
>
{/if}
<option value="info" class="text-gray-900">{$i18n.t('Info')}</option>
<option value="warning" class="text-gray-900">{$i18n.t('Warning')}</option>
<option value="error" class="text-gray-900">{$i18n.t('Error')}</option>
<option value="success" class="text-gray-900">{$i18n.t('Success')}</option>
</select>
<input
class="pr-5 py-1.5 text-xs w-full bg-transparent outline-none"
placeholder={$i18n.t('Content')}
bind:value={banner.content}
/>
<div class="relative top-1.5 -left-2">
<Tooltip content="Dismissible" className="flex h-fit items-center">
<Switch bind:state={banner.dismissible} />
</Tooltip>
</div>
</div>
<button
class="px-2"
type="button"
on:click={() => {
banners.splice(bannerIdx, 1);
banners = banners;
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
class="w-4 h-4"
>
<path
d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z"
/>
</svg>
</button>
</div>
{/each}
</div>
</div>
</div>
<div class="flex justify-end pt-3 text-sm font-medium">
<button
class=" px-4 py-2 bg-emerald-700 hover:bg-emerald-800 text-gray-100 transition rounded-lg"
type="submit"
>
Save
</button>
</div>
</form>
src/lib/components/admin/Settings/Database.svelte
View file @
6a36039a
...
...
@@ -34,7 +34,7 @@
<div>
<div class=" mb-2 text-sm font-medium">{$i18n.t('Database')}</div>
{#if $config?.enable_admin_export ?? true}
{#if $config?.
features.
enable_admin_export ?? true}
<div class=" flex w-full justify-between">
<!-- <div class=" self-center text-xs font-medium">{$i18n.t('Allow Chat Deletion')}</div> -->
...
...
src/lib/components/admin/Settings/General.svelte
View file @
6a36039a
<script lang="ts">
import { getWebhookUrl, updateWebhookUrl } from '$lib/apis';
import {
getCommunitySharingEnabledStatus,
getWebhookUrl,
toggleCommunitySharingEnabledStatus,
updateWebhookUrl
} from '$lib/apis';
import {
getDefaultUserRole,
getJWTExpiresDuration,
...
...
@@ -18,6 +23,7 @@
let JWTExpiresIn = '';
let webhookUrl = '';
let communitySharingEnabled = true;
const toggleSignUpEnabled = async () => {
signUpEnabled = await toggleSignUpEnabledStatus(localStorage.token);
...
...
@@ -35,11 +41,28 @@
webhookUrl = await updateWebhookUrl(localStorage.token, webhookUrl);
};
const toggleCommunitySharingEnabled = async () => {
communitySharingEnabled = await toggleCommunitySharingEnabledStatus(localStorage.token);
};
onMount(async () => {
signUpEnabled = await getSignUpEnabledStatus(localStorage.token);
defaultUserRole = await getDefaultUserRole(localStorage.token);
JWTExpiresIn = await getJWTExpiresDuration(localStorage.token);
webhookUrl = await getWebhookUrl(localStorage.token);
await Promise.all([
(async () => {
signUpEnabled = await getSignUpEnabledStatus(localStorage.token);
})(),
(async () => {
defaultUserRole = await getDefaultUserRole(localStorage.token);
})(),
(async () => {
JWTExpiresIn = await getJWTExpiresDuration(localStorage.token);
})(),
(async () => {
webhookUrl = await getWebhookUrl(localStorage.token);
})(),
(async () => {
communitySharingEnabled = await getCommunitySharingEnabledStatus(localStorage.token);
})()
]);
});
</script>
...
...
@@ -114,6 +137,47 @@
</div>
</div>
<div class=" flex w-full justify-between">
<div class=" self-center text-xs font-medium">{$i18n.t('Enable Community Sharing')}</div>
<button
class="p-1 px-3 text-xs flex rounded transition"
on:click={() => {
toggleCommunitySharingEnabled();
}}
type="button"
>
{#if communitySharingEnabled}
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
class="w-4 h-4"
>
<path
d="M11.5 1A3.5 3.5 0 0 0 8 4.5V7H2.5A1.5 1.5 0 0 0 1 8.5v5A1.5 1.5 0 0 0 2.5 15h7a1.5 1.5 0 0 0 1.5-1.5v-5A1.5 1.5 0 0 0 9.5 7V4.5a2 2 0 1 1 4 0v1.75a.75.75 0 0 0 1.5 0V4.5A3.5 3.5 0 0 0 11.5 1Z"
/>
</svg>
<span class="ml-2 self-center">{$i18n.t('Enabled')}</span>
{:else}
<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="M8 1a3.5 3.5 0 0 0-3.5 3.5V7A1.5 1.5 0 0 0 3 8.5v5A1.5 1.5 0 0 0 4.5 15h7a1.5 1.5 0 0 0 1.5-1.5v-5A1.5 1.5 0 0 0 11.5 7V4.5A3.5 3.5 0 0 0 8 1Zm2 6V4.5a2 2 0 1 0-4 0V7h4Z"
clip-rule="evenodd"
/>
</svg>
<span class="ml-2 self-center">{$i18n.t('Disabled')}</span>
{/if}
</button>
</div>
<hr class=" dark:border-gray-700 my-3" />
<div class=" w-full justify-between">
...
...
src/lib/components/admin/Settings/Users.svelte
View file @
6a36039a
<script lang="ts">
import { getModelFilterConfig, updateModelFilterConfig } from '$lib/apis';
import {
getBackendConfig,
getModelFilterConfig, updateModelFilterConfig } from '$lib/apis';
import { getSignUpEnabledStatus, toggleSignUpEnabledStatus } from '$lib/apis/auths';
import { getUserPermissions, updateUserPermissions } from '$lib/apis/users';
import { onMount, getContext } from 'svelte';
import { models } from '$lib/stores';
import { models, config } from '$lib/stores';
import Switch from '$lib/components/common/Switch.svelte';
import { setDefaultModels } from '$lib/apis/configs';
const i18n = getContext('i18n');
export let saveHandler: Function;
let defaultModelId = '';
let whitelistEnabled = false;
let whitelistModels = [''];
let permissions = {
...
...
@@ -24,9 +28,10 @@
const res = await getModelFilterConfig(localStorage.token);
if (res) {
whitelistEnabled = res.enabled;
whitelistModels = res.models.length > 0 ? res.models : [''];
}
defaultModelId = $config.default_models ? $config?.default_models.split(',')[0] : '';
});
</script>
...
...
@@ -34,10 +39,13 @@
class="flex flex-col h-full justify-between space-y-3 text-sm"
on:submit|preventDefault={async () => {
// console.log('submit');
await updateUserPermissions(localStorage.token, permissions);
await setDefaultModels(localStorage.token, defaultModelId);
await updateUserPermissions(localStorage.token, permissions);
await updateModelFilterConfig(localStorage.token, whitelistEnabled, whitelistModels);
saveHandler();
await config.set(await getBackendConfig());
}}
>
<div class=" space-y-3 pr-1.5 overflow-y-scroll max-h-80">
...
...
@@ -88,26 +96,40 @@
<hr class=" dark:border-gray-700 my-2" />
<div class="mt-2 space-y-3
pr-1.5
">
<div class="mt-2 space-y-3">
<div>
<div class="mb-2">
<div class="flex justify-between items-center text-xs">
<div class=" text-sm font-medium">{$i18n.t('Manage Models')}</div>
</div>
</div>
<div class=" space-y-1 mb-3">
<div class="mb-2">
<div class="flex justify-between items-center text-xs">
<div class=" text-xs font-medium">{$i18n.t('Default Model')}</div>
</div>
</div>
<div class="flex-1 mr-2">
<select
class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
bind:value={defaultModelId}
placeholder="Select a model"
>
<option value="" disabled selected>{$i18n.t('Select a model')}</option>
{#each $models.filter((model) => model.id) as model}
<option value={model.id} class="bg-gray-100 dark:bg-gray-700">{model.name}</option>
{/each}
</select>
</div>
</div>
<div class=" space-y-
3
">
<div>
<div class=" space-y-
1
">
<div
class="mb-2"
>
<div class="flex justify-between items-center text-xs">
<div class=" text-xs font-medium">{$i18n.t('Model Whitelisting')}</div>
<button
class=" text-xs font-medium text-gray-500"
type="button"
on:click={() => {
whitelistEnabled = !whitelistEnabled;
}}>{whitelistEnabled ? $i18n.t('On') : $i18n.t('Off')}</button
>
<Switch bind:state={whitelistEnabled} />
</div>
</div>
...
...
Prev
1
2
3
4
Next
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment