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
f9e3c47d
Commit
f9e3c47d
authored
Jul 09, 2024
by
Michael Poluektov
Browse files
rebase
parents
49b4211c
24ef5af2
Changes
149
Hide whitespace changes
Inline
Side-by-side
Showing
20 changed files
with
1008 additions
and
98 deletions
+1008
-98
backend/test/apps/webui/routers/test_chats.py
backend/test/apps/webui/routers/test_chats.py
+238
-0
backend/test/apps/webui/routers/test_documents.py
backend/test/apps/webui/routers/test_documents.py
+106
-0
backend/test/apps/webui/routers/test_models.py
backend/test/apps/webui/routers/test_models.py
+62
-0
backend/test/apps/webui/routers/test_prompts.py
backend/test/apps/webui/routers/test_prompts.py
+92
-0
backend/test/apps/webui/routers/test_users.py
backend/test/apps/webui/routers/test_users.py
+168
-0
backend/test/util/abstract_integration_test.py
backend/test/util/abstract_integration_test.py
+161
-0
backend/test/util/mock_user.py
backend/test/util/mock_user.py
+45
-0
backend/utils/tools.py
backend/utils/tools.py
+4
-1
backend/utils/utils.py
backend/utils/utils.py
+1
-0
src/app.css
src/app.css
+16
-2
src/app.html
src/app.html
+7
-5
src/lib/components/ChangelogModal.svelte
src/lib/components/ChangelogModal.svelte
+3
-3
src/lib/components/admin/Settings.svelte
src/lib/components/admin/Settings.svelte
+37
-23
src/lib/components/admin/Settings/Audio.svelte
src/lib/components/admin/Settings/Audio.svelte
+6
-6
src/lib/components/admin/Settings/Connections.svelte
src/lib/components/admin/Settings/Connections.svelte
+2
-2
src/lib/components/admin/Settings/Documents.svelte
src/lib/components/admin/Settings/Documents.svelte
+14
-14
src/lib/components/admin/Settings/General.svelte
src/lib/components/admin/Settings/General.svelte
+2
-2
src/lib/components/admin/Settings/Images.svelte
src/lib/components/admin/Settings/Images.svelte
+9
-9
src/lib/components/admin/Settings/Interface.svelte
src/lib/components/admin/Settings/Interface.svelte
+12
-10
src/lib/components/admin/Settings/Models.svelte
src/lib/components/admin/Settings/Models.svelte
+23
-21
No files found.
backend/test/apps/webui/routers/test_chats.py
0 → 100644
View file @
f9e3c47d
import
uuid
from
test.util.abstract_integration_test
import
AbstractPostgresTest
from
test.util.mock_user
import
mock_webui_user
class
TestChats
(
AbstractPostgresTest
):
BASE_PATH
=
"/api/v1/chats"
def
setup_class
(
cls
):
super
().
setup_class
()
def
setup_method
(
self
):
super
().
setup_method
()
from
apps.webui.models.chats
import
ChatForm
from
apps.webui.models.chats
import
Chats
self
.
chats
=
Chats
self
.
chats
.
insert_new_chat
(
"2"
,
ChatForm
(
**
{
"chat"
:
{
"name"
:
"chat1"
,
"description"
:
"chat1 description"
,
"tags"
:
[
"tag1"
,
"tag2"
],
"history"
:
{
"currentId"
:
"1"
,
"messages"
:
[]},
}
}
),
)
def
test_get_session_user_chat_list
(
self
):
with
mock_webui_user
(
id
=
"2"
):
response
=
self
.
fast_api_client
.
get
(
self
.
create_url
(
"/"
))
assert
response
.
status_code
==
200
first_chat
=
response
.
json
()[
0
]
assert
first_chat
[
"id"
]
is
not
None
assert
first_chat
[
"title"
]
==
"New Chat"
assert
first_chat
[
"created_at"
]
is
not
None
assert
first_chat
[
"updated_at"
]
is
not
None
def
test_delete_all_user_chats
(
self
):
with
mock_webui_user
(
id
=
"2"
):
response
=
self
.
fast_api_client
.
delete
(
self
.
create_url
(
"/"
))
assert
response
.
status_code
==
200
assert
len
(
self
.
chats
.
get_chats
())
==
0
def
test_get_user_chat_list_by_user_id
(
self
):
with
mock_webui_user
(
id
=
"3"
):
response
=
self
.
fast_api_client
.
get
(
self
.
create_url
(
"/list/user/2"
))
assert
response
.
status_code
==
200
first_chat
=
response
.
json
()[
0
]
assert
first_chat
[
"id"
]
is
not
None
assert
first_chat
[
"title"
]
==
"New Chat"
assert
first_chat
[
"created_at"
]
is
not
None
assert
first_chat
[
"updated_at"
]
is
not
None
def
test_create_new_chat
(
self
):
with
mock_webui_user
(
id
=
"2"
):
response
=
self
.
fast_api_client
.
post
(
self
.
create_url
(
"/new"
),
json
=
{
"chat"
:
{
"name"
:
"chat2"
,
"description"
:
"chat2 description"
,
"tags"
:
[
"tag1"
,
"tag2"
],
}
},
)
assert
response
.
status_code
==
200
data
=
response
.
json
()
assert
data
[
"archived"
]
is
False
assert
data
[
"chat"
]
==
{
"name"
:
"chat2"
,
"description"
:
"chat2 description"
,
"tags"
:
[
"tag1"
,
"tag2"
],
}
assert
data
[
"user_id"
]
==
"2"
assert
data
[
"id"
]
is
not
None
assert
data
[
"share_id"
]
is
None
assert
data
[
"title"
]
==
"New Chat"
assert
data
[
"updated_at"
]
is
not
None
assert
data
[
"created_at"
]
is
not
None
assert
len
(
self
.
chats
.
get_chats
())
==
2
def
test_get_user_chats
(
self
):
self
.
test_get_session_user_chat_list
()
def
test_get_user_archived_chats
(
self
):
self
.
chats
.
archive_all_chats_by_user_id
(
"2"
)
from
apps.webui.internal.db
import
Session
Session
.
commit
()
with
mock_webui_user
(
id
=
"2"
):
response
=
self
.
fast_api_client
.
get
(
self
.
create_url
(
"/all/archived"
))
assert
response
.
status_code
==
200
first_chat
=
response
.
json
()[
0
]
assert
first_chat
[
"id"
]
is
not
None
assert
first_chat
[
"title"
]
==
"New Chat"
assert
first_chat
[
"created_at"
]
is
not
None
assert
first_chat
[
"updated_at"
]
is
not
None
def
test_get_all_user_chats_in_db
(
self
):
with
mock_webui_user
(
id
=
"4"
):
response
=
self
.
fast_api_client
.
get
(
self
.
create_url
(
"/all/db"
))
assert
response
.
status_code
==
200
assert
len
(
response
.
json
())
==
1
def
test_get_archived_session_user_chat_list
(
self
):
self
.
test_get_user_archived_chats
()
def
test_archive_all_chats
(
self
):
with
mock_webui_user
(
id
=
"2"
):
response
=
self
.
fast_api_client
.
post
(
self
.
create_url
(
"/archive/all"
))
assert
response
.
status_code
==
200
assert
len
(
self
.
chats
.
get_archived_chats_by_user_id
(
"2"
))
==
1
def
test_get_shared_chat_by_id
(
self
):
chat_id
=
self
.
chats
.
get_chats
()[
0
].
id
self
.
chats
.
update_chat_share_id_by_id
(
chat_id
,
chat_id
)
with
mock_webui_user
(
id
=
"2"
):
response
=
self
.
fast_api_client
.
get
(
self
.
create_url
(
f
"/share/
{
chat_id
}
"
))
assert
response
.
status_code
==
200
data
=
response
.
json
()
assert
data
[
"id"
]
==
chat_id
assert
data
[
"chat"
]
==
{
"name"
:
"chat1"
,
"description"
:
"chat1 description"
,
"tags"
:
[
"tag1"
,
"tag2"
],
"history"
:
{
"currentId"
:
"1"
,
"messages"
:
[]},
}
assert
data
[
"id"
]
==
chat_id
assert
data
[
"share_id"
]
==
chat_id
assert
data
[
"title"
]
==
"New Chat"
def
test_get_chat_by_id
(
self
):
chat_id
=
self
.
chats
.
get_chats
()[
0
].
id
with
mock_webui_user
(
id
=
"2"
):
response
=
self
.
fast_api_client
.
get
(
self
.
create_url
(
f
"/
{
chat_id
}
"
))
assert
response
.
status_code
==
200
data
=
response
.
json
()
assert
data
[
"id"
]
==
chat_id
assert
data
[
"chat"
]
==
{
"name"
:
"chat1"
,
"description"
:
"chat1 description"
,
"tags"
:
[
"tag1"
,
"tag2"
],
"history"
:
{
"currentId"
:
"1"
,
"messages"
:
[]},
}
assert
data
[
"share_id"
]
is
None
assert
data
[
"title"
]
==
"New Chat"
assert
data
[
"user_id"
]
==
"2"
def
test_update_chat_by_id
(
self
):
chat_id
=
self
.
chats
.
get_chats
()[
0
].
id
with
mock_webui_user
(
id
=
"2"
):
response
=
self
.
fast_api_client
.
post
(
self
.
create_url
(
f
"/
{
chat_id
}
"
),
json
=
{
"chat"
:
{
"name"
:
"chat2"
,
"description"
:
"chat2 description"
,
"tags"
:
[
"tag2"
,
"tag4"
],
"title"
:
"Just another title"
,
}
},
)
assert
response
.
status_code
==
200
data
=
response
.
json
()
assert
data
[
"id"
]
==
chat_id
assert
data
[
"chat"
]
==
{
"name"
:
"chat2"
,
"title"
:
"Just another title"
,
"description"
:
"chat2 description"
,
"tags"
:
[
"tag2"
,
"tag4"
],
"history"
:
{
"currentId"
:
"1"
,
"messages"
:
[]},
}
assert
data
[
"share_id"
]
is
None
assert
data
[
"title"
]
==
"Just another title"
assert
data
[
"user_id"
]
==
"2"
def
test_delete_chat_by_id
(
self
):
chat_id
=
self
.
chats
.
get_chats
()[
0
].
id
with
mock_webui_user
(
id
=
"2"
):
response
=
self
.
fast_api_client
.
delete
(
self
.
create_url
(
f
"/
{
chat_id
}
"
))
assert
response
.
status_code
==
200
assert
response
.
json
()
is
True
def
test_clone_chat_by_id
(
self
):
chat_id
=
self
.
chats
.
get_chats
()[
0
].
id
with
mock_webui_user
(
id
=
"2"
):
response
=
self
.
fast_api_client
.
get
(
self
.
create_url
(
f
"/
{
chat_id
}
/clone"
))
assert
response
.
status_code
==
200
data
=
response
.
json
()
assert
data
[
"id"
]
!=
chat_id
assert
data
[
"chat"
]
==
{
"branchPointMessageId"
:
"1"
,
"description"
:
"chat1 description"
,
"history"
:
{
"currentId"
:
"1"
,
"messages"
:
[]},
"name"
:
"chat1"
,
"originalChatId"
:
chat_id
,
"tags"
:
[
"tag1"
,
"tag2"
],
"title"
:
"Clone of New Chat"
,
}
assert
data
[
"share_id"
]
is
None
assert
data
[
"title"
]
==
"Clone of New Chat"
assert
data
[
"user_id"
]
==
"2"
def
test_archive_chat_by_id
(
self
):
chat_id
=
self
.
chats
.
get_chats
()[
0
].
id
with
mock_webui_user
(
id
=
"2"
):
response
=
self
.
fast_api_client
.
get
(
self
.
create_url
(
f
"/
{
chat_id
}
/archive"
))
assert
response
.
status_code
==
200
chat
=
self
.
chats
.
get_chat_by_id
(
chat_id
)
assert
chat
.
archived
is
True
def
test_share_chat_by_id
(
self
):
chat_id
=
self
.
chats
.
get_chats
()[
0
].
id
with
mock_webui_user
(
id
=
"2"
):
response
=
self
.
fast_api_client
.
post
(
self
.
create_url
(
f
"/
{
chat_id
}
/share"
))
assert
response
.
status_code
==
200
chat
=
self
.
chats
.
get_chat_by_id
(
chat_id
)
assert
chat
.
share_id
is
not
None
def
test_delete_shared_chat_by_id
(
self
):
chat_id
=
self
.
chats
.
get_chats
()[
0
].
id
share_id
=
str
(
uuid
.
uuid4
())
self
.
chats
.
update_chat_share_id_by_id
(
chat_id
,
share_id
)
with
mock_webui_user
(
id
=
"2"
):
response
=
self
.
fast_api_client
.
delete
(
self
.
create_url
(
f
"/
{
chat_id
}
/share"
))
assert
response
.
status_code
chat
=
self
.
chats
.
get_chat_by_id
(
chat_id
)
assert
chat
.
share_id
is
None
backend/test/apps/webui/routers/test_documents.py
0 → 100644
View file @
f9e3c47d
from
test.util.abstract_integration_test
import
AbstractPostgresTest
from
test.util.mock_user
import
mock_webui_user
class
TestDocuments
(
AbstractPostgresTest
):
BASE_PATH
=
"/api/v1/documents"
def
setup_class
(
cls
):
super
().
setup_class
()
from
apps.webui.models.documents
import
Documents
cls
.
documents
=
Documents
def
test_documents
(
self
):
# Empty database
assert
len
(
self
.
documents
.
get_docs
())
==
0
with
mock_webui_user
(
id
=
"2"
):
response
=
self
.
fast_api_client
.
get
(
self
.
create_url
(
"/"
))
assert
response
.
status_code
==
200
assert
len
(
response
.
json
())
==
0
# Create a new document
with
mock_webui_user
(
id
=
"2"
):
response
=
self
.
fast_api_client
.
post
(
self
.
create_url
(
"/create"
),
json
=
{
"name"
:
"doc_name"
,
"title"
:
"doc title"
,
"collection_name"
:
"custom collection"
,
"filename"
:
"doc_name.pdf"
,
"content"
:
""
,
},
)
assert
response
.
status_code
==
200
assert
response
.
json
()[
"name"
]
==
"doc_name"
assert
len
(
self
.
documents
.
get_docs
())
==
1
# Get the document
with
mock_webui_user
(
id
=
"2"
):
response
=
self
.
fast_api_client
.
get
(
self
.
create_url
(
"/doc?name=doc_name"
))
assert
response
.
status_code
==
200
data
=
response
.
json
()
assert
data
[
"collection_name"
]
==
"custom collection"
assert
data
[
"name"
]
==
"doc_name"
assert
data
[
"title"
]
==
"doc title"
assert
data
[
"filename"
]
==
"doc_name.pdf"
assert
data
[
"content"
]
==
{}
# Create another document
with
mock_webui_user
(
id
=
"2"
):
response
=
self
.
fast_api_client
.
post
(
self
.
create_url
(
"/create"
),
json
=
{
"name"
:
"doc_name 2"
,
"title"
:
"doc title 2"
,
"collection_name"
:
"custom collection 2"
,
"filename"
:
"doc_name2.pdf"
,
"content"
:
""
,
},
)
assert
response
.
status_code
==
200
assert
response
.
json
()[
"name"
]
==
"doc_name 2"
assert
len
(
self
.
documents
.
get_docs
())
==
2
# Get all documents
with
mock_webui_user
(
id
=
"2"
):
response
=
self
.
fast_api_client
.
get
(
self
.
create_url
(
"/"
))
assert
response
.
status_code
==
200
assert
len
(
response
.
json
())
==
2
# Update the first document
with
mock_webui_user
(
id
=
"2"
):
response
=
self
.
fast_api_client
.
post
(
self
.
create_url
(
"/doc/update?name=doc_name"
),
json
=
{
"name"
:
"doc_name rework"
,
"title"
:
"updated title"
},
)
assert
response
.
status_code
==
200
data
=
response
.
json
()
assert
data
[
"name"
]
==
"doc_name rework"
assert
data
[
"title"
]
==
"updated title"
# Tag the first document
with
mock_webui_user
(
id
=
"2"
):
response
=
self
.
fast_api_client
.
post
(
self
.
create_url
(
"/doc/tags"
),
json
=
{
"name"
:
"doc_name rework"
,
"tags"
:
[{
"name"
:
"testing-tag"
},
{
"name"
:
"another-tag"
}],
},
)
assert
response
.
status_code
==
200
data
=
response
.
json
()
assert
data
[
"name"
]
==
"doc_name rework"
assert
data
[
"content"
]
==
{
"tags"
:
[{
"name"
:
"testing-tag"
},
{
"name"
:
"another-tag"
}]
}
assert
len
(
self
.
documents
.
get_docs
())
==
2
# Delete the first document
with
mock_webui_user
(
id
=
"2"
):
response
=
self
.
fast_api_client
.
delete
(
self
.
create_url
(
"/doc/delete?name=doc_name rework"
)
)
assert
response
.
status_code
==
200
assert
len
(
self
.
documents
.
get_docs
())
==
1
backend/test/apps/webui/routers/test_models.py
0 → 100644
View file @
f9e3c47d
from
test.util.abstract_integration_test
import
AbstractPostgresTest
from
test.util.mock_user
import
mock_webui_user
class
TestModels
(
AbstractPostgresTest
):
BASE_PATH
=
"/api/v1/models"
def
setup_class
(
cls
):
super
().
setup_class
()
from
apps.webui.models.models
import
Model
cls
.
models
=
Model
def
test_models
(
self
):
with
mock_webui_user
(
id
=
"2"
):
response
=
self
.
fast_api_client
.
get
(
self
.
create_url
(
"/"
))
assert
response
.
status_code
==
200
assert
len
(
response
.
json
())
==
0
with
mock_webui_user
(
id
=
"2"
):
response
=
self
.
fast_api_client
.
post
(
self
.
create_url
(
"/add"
),
json
=
{
"id"
:
"my-model"
,
"base_model_id"
:
"base-model-id"
,
"name"
:
"Hello World"
,
"meta"
:
{
"profile_image_url"
:
"/static/favicon.png"
,
"description"
:
"description"
,
"capabilities"
:
None
,
"model_config"
:
{},
},
"params"
:
{},
},
)
assert
response
.
status_code
==
200
with
mock_webui_user
(
id
=
"2"
):
response
=
self
.
fast_api_client
.
get
(
self
.
create_url
(
"/"
))
assert
response
.
status_code
==
200
assert
len
(
response
.
json
())
==
1
with
mock_webui_user
(
id
=
"2"
):
response
=
self
.
fast_api_client
.
get
(
self
.
create_url
(
query_params
=
{
"id"
:
"my-model"
})
)
assert
response
.
status_code
==
200
data
=
response
.
json
()[
0
]
assert
data
[
"id"
]
==
"my-model"
assert
data
[
"name"
]
==
"Hello World"
with
mock_webui_user
(
id
=
"2"
):
response
=
self
.
fast_api_client
.
delete
(
self
.
create_url
(
"/delete?id=my-model"
)
)
assert
response
.
status_code
==
200
with
mock_webui_user
(
id
=
"2"
):
response
=
self
.
fast_api_client
.
get
(
self
.
create_url
(
"/"
))
assert
response
.
status_code
==
200
assert
len
(
response
.
json
())
==
0
backend/test/apps/webui/routers/test_prompts.py
0 → 100644
View file @
f9e3c47d
from
test.util.abstract_integration_test
import
AbstractPostgresTest
from
test.util.mock_user
import
mock_webui_user
class
TestPrompts
(
AbstractPostgresTest
):
BASE_PATH
=
"/api/v1/prompts"
def
test_prompts
(
self
):
# Get all prompts
with
mock_webui_user
(
id
=
"2"
):
response
=
self
.
fast_api_client
.
get
(
self
.
create_url
(
"/"
))
assert
response
.
status_code
==
200
assert
len
(
response
.
json
())
==
0
# Create a two new prompts
with
mock_webui_user
(
id
=
"2"
):
response
=
self
.
fast_api_client
.
post
(
self
.
create_url
(
"/create"
),
json
=
{
"command"
:
"/my-command"
,
"title"
:
"Hello World"
,
"content"
:
"description"
,
},
)
assert
response
.
status_code
==
200
with
mock_webui_user
(
id
=
"3"
):
response
=
self
.
fast_api_client
.
post
(
self
.
create_url
(
"/create"
),
json
=
{
"command"
:
"/my-command2"
,
"title"
:
"Hello World 2"
,
"content"
:
"description 2"
,
},
)
assert
response
.
status_code
==
200
# Get all prompts
with
mock_webui_user
(
id
=
"2"
):
response
=
self
.
fast_api_client
.
get
(
self
.
create_url
(
"/"
))
assert
response
.
status_code
==
200
assert
len
(
response
.
json
())
==
2
# Get prompt by command
with
mock_webui_user
(
id
=
"2"
):
response
=
self
.
fast_api_client
.
get
(
self
.
create_url
(
"/command/my-command"
))
assert
response
.
status_code
==
200
data
=
response
.
json
()
assert
data
[
"command"
]
==
"/my-command"
assert
data
[
"title"
]
==
"Hello World"
assert
data
[
"content"
]
==
"description"
assert
data
[
"user_id"
]
==
"2"
# Update prompt
with
mock_webui_user
(
id
=
"2"
):
response
=
self
.
fast_api_client
.
post
(
self
.
create_url
(
"/command/my-command2/update"
),
json
=
{
"command"
:
"irrelevant for request"
,
"title"
:
"Hello World Updated"
,
"content"
:
"description Updated"
,
},
)
assert
response
.
status_code
==
200
data
=
response
.
json
()
assert
data
[
"command"
]
==
"/my-command2"
assert
data
[
"title"
]
==
"Hello World Updated"
assert
data
[
"content"
]
==
"description Updated"
assert
data
[
"user_id"
]
==
"3"
# Get prompt by command
with
mock_webui_user
(
id
=
"2"
):
response
=
self
.
fast_api_client
.
get
(
self
.
create_url
(
"/command/my-command2"
))
assert
response
.
status_code
==
200
data
=
response
.
json
()
assert
data
[
"command"
]
==
"/my-command2"
assert
data
[
"title"
]
==
"Hello World Updated"
assert
data
[
"content"
]
==
"description Updated"
assert
data
[
"user_id"
]
==
"3"
# Delete prompt
with
mock_webui_user
(
id
=
"2"
):
response
=
self
.
fast_api_client
.
delete
(
self
.
create_url
(
"/command/my-command/delete"
)
)
assert
response
.
status_code
==
200
# Get all prompts
with
mock_webui_user
(
id
=
"2"
):
response
=
self
.
fast_api_client
.
get
(
self
.
create_url
(
"/"
))
assert
response
.
status_code
==
200
assert
len
(
response
.
json
())
==
1
backend/test/apps/webui/routers/test_users.py
0 → 100644
View file @
f9e3c47d
from
test.util.abstract_integration_test
import
AbstractPostgresTest
from
test.util.mock_user
import
mock_webui_user
def
_get_user_by_id
(
data
,
param
):
return
next
((
item
for
item
in
data
if
item
[
"id"
]
==
param
),
None
)
def
_assert_user
(
data
,
id
,
**
kwargs
):
user
=
_get_user_by_id
(
data
,
id
)
assert
user
is
not
None
comparison_data
=
{
"name"
:
f
"user
{
id
}
"
,
"email"
:
f
"user
{
id
}
@openwebui.com"
,
"profile_image_url"
:
f
"/user
{
id
}
.png"
,
"role"
:
"user"
,
**
kwargs
,
}
for
key
,
value
in
comparison_data
.
items
():
assert
user
[
key
]
==
value
class
TestUsers
(
AbstractPostgresTest
):
BASE_PATH
=
"/api/v1/users"
def
setup_class
(
cls
):
super
().
setup_class
()
from
apps.webui.models.users
import
Users
cls
.
users
=
Users
def
setup_method
(
self
):
super
().
setup_method
()
self
.
users
.
insert_new_user
(
id
=
"1"
,
name
=
"user 1"
,
email
=
"user1@openwebui.com"
,
profile_image_url
=
"/user1.png"
,
role
=
"user"
,
)
self
.
users
.
insert_new_user
(
id
=
"2"
,
name
=
"user 2"
,
email
=
"user2@openwebui.com"
,
profile_image_url
=
"/user2.png"
,
role
=
"user"
,
)
def
test_users
(
self
):
# Get all users
with
mock_webui_user
(
id
=
"3"
):
response
=
self
.
fast_api_client
.
get
(
self
.
create_url
(
""
))
assert
response
.
status_code
==
200
assert
len
(
response
.
json
())
==
2
data
=
response
.
json
()
_assert_user
(
data
,
"1"
)
_assert_user
(
data
,
"2"
)
# update role
with
mock_webui_user
(
id
=
"3"
):
response
=
self
.
fast_api_client
.
post
(
self
.
create_url
(
"/update/role"
),
json
=
{
"id"
:
"2"
,
"role"
:
"admin"
}
)
assert
response
.
status_code
==
200
_assert_user
([
response
.
json
()],
"2"
,
role
=
"admin"
)
# Get all users
with
mock_webui_user
(
id
=
"3"
):
response
=
self
.
fast_api_client
.
get
(
self
.
create_url
(
""
))
assert
response
.
status_code
==
200
assert
len
(
response
.
json
())
==
2
data
=
response
.
json
()
_assert_user
(
data
,
"1"
)
_assert_user
(
data
,
"2"
,
role
=
"admin"
)
# Get (empty) user settings
with
mock_webui_user
(
id
=
"2"
):
response
=
self
.
fast_api_client
.
get
(
self
.
create_url
(
"/user/settings"
))
assert
response
.
status_code
==
200
assert
response
.
json
()
is
None
# Update user settings
with
mock_webui_user
(
id
=
"2"
):
response
=
self
.
fast_api_client
.
post
(
self
.
create_url
(
"/user/settings/update"
),
json
=
{
"ui"
:
{
"attr1"
:
"value1"
,
"attr2"
:
"value2"
},
"model_config"
:
{
"attr3"
:
"value3"
,
"attr4"
:
"value4"
},
},
)
assert
response
.
status_code
==
200
# Get user settings
with
mock_webui_user
(
id
=
"2"
):
response
=
self
.
fast_api_client
.
get
(
self
.
create_url
(
"/user/settings"
))
assert
response
.
status_code
==
200
assert
response
.
json
()
==
{
"ui"
:
{
"attr1"
:
"value1"
,
"attr2"
:
"value2"
},
"model_config"
:
{
"attr3"
:
"value3"
,
"attr4"
:
"value4"
},
}
# Get (empty) user info
with
mock_webui_user
(
id
=
"1"
):
response
=
self
.
fast_api_client
.
get
(
self
.
create_url
(
"/user/info"
))
assert
response
.
status_code
==
200
assert
response
.
json
()
is
None
# Update user info
with
mock_webui_user
(
id
=
"1"
):
response
=
self
.
fast_api_client
.
post
(
self
.
create_url
(
"/user/info/update"
),
json
=
{
"attr1"
:
"value1"
,
"attr2"
:
"value2"
},
)
assert
response
.
status_code
==
200
# Get user info
with
mock_webui_user
(
id
=
"1"
):
response
=
self
.
fast_api_client
.
get
(
self
.
create_url
(
"/user/info"
))
assert
response
.
status_code
==
200
assert
response
.
json
()
==
{
"attr1"
:
"value1"
,
"attr2"
:
"value2"
}
# Get user by id
with
mock_webui_user
(
id
=
"1"
):
response
=
self
.
fast_api_client
.
get
(
self
.
create_url
(
"/2"
))
assert
response
.
status_code
==
200
assert
response
.
json
()
==
{
"name"
:
"user 2"
,
"profile_image_url"
:
"/user2.png"
}
# Update user by id
with
mock_webui_user
(
id
=
"1"
):
response
=
self
.
fast_api_client
.
post
(
self
.
create_url
(
"/2/update"
),
json
=
{
"name"
:
"user 2 updated"
,
"email"
:
"user2-updated@openwebui.com"
,
"profile_image_url"
:
"/user2-updated.png"
,
},
)
assert
response
.
status_code
==
200
# Get all users
with
mock_webui_user
(
id
=
"3"
):
response
=
self
.
fast_api_client
.
get
(
self
.
create_url
(
""
))
assert
response
.
status_code
==
200
assert
len
(
response
.
json
())
==
2
data
=
response
.
json
()
_assert_user
(
data
,
"1"
)
_assert_user
(
data
,
"2"
,
role
=
"admin"
,
name
=
"user 2 updated"
,
email
=
"user2-updated@openwebui.com"
,
profile_image_url
=
"/user2-updated.png"
,
)
# Delete user by id
with
mock_webui_user
(
id
=
"1"
):
response
=
self
.
fast_api_client
.
delete
(
self
.
create_url
(
"/2"
))
assert
response
.
status_code
==
200
# Get all users
with
mock_webui_user
(
id
=
"3"
):
response
=
self
.
fast_api_client
.
get
(
self
.
create_url
(
""
))
assert
response
.
status_code
==
200
assert
len
(
response
.
json
())
==
1
data
=
response
.
json
()
_assert_user
(
data
,
"1"
)
backend/test/util/abstract_integration_test.py
0 → 100644
View file @
f9e3c47d
import
logging
import
os
import
time
import
docker
import
pytest
from
docker
import
DockerClient
from
pytest_docker.plugin
import
get_docker_ip
from
fastapi.testclient
import
TestClient
from
sqlalchemy
import
text
,
create_engine
log
=
logging
.
getLogger
(
__name__
)
def
get_fast_api_client
():
from
main
import
app
with
TestClient
(
app
)
as
c
:
return
c
class
AbstractIntegrationTest
:
BASE_PATH
=
None
def
create_url
(
self
,
path
=
""
,
query_params
=
None
):
if
self
.
BASE_PATH
is
None
:
raise
Exception
(
"BASE_PATH is not set"
)
parts
=
self
.
BASE_PATH
.
split
(
"/"
)
parts
=
[
part
.
strip
()
for
part
in
parts
if
part
.
strip
()
!=
""
]
path_parts
=
path
.
split
(
"/"
)
path_parts
=
[
part
.
strip
()
for
part
in
path_parts
if
part
.
strip
()
!=
""
]
query_parts
=
""
if
query_params
:
query_parts
=
"&"
.
join
(
[
f
"
{
key
}
=
{
value
}
"
for
key
,
value
in
query_params
.
items
()]
)
query_parts
=
f
"?
{
query_parts
}
"
return
"/"
.
join
(
parts
+
path_parts
)
+
query_parts
@
classmethod
def
setup_class
(
cls
):
pass
def
setup_method
(
self
):
pass
@
classmethod
def
teardown_class
(
cls
):
pass
def
teardown_method
(
self
):
pass
class
AbstractPostgresTest
(
AbstractIntegrationTest
):
DOCKER_CONTAINER_NAME
=
"postgres-test-container-will-get-deleted"
docker_client
:
DockerClient
@
classmethod
def
_create_db_url
(
cls
,
env_vars_postgres
:
dict
)
->
str
:
host
=
get_docker_ip
()
user
=
env_vars_postgres
[
"POSTGRES_USER"
]
pw
=
env_vars_postgres
[
"POSTGRES_PASSWORD"
]
port
=
8081
db
=
env_vars_postgres
[
"POSTGRES_DB"
]
return
f
"postgresql://
{
user
}
:
{
pw
}
@
{
host
}
:
{
port
}
/
{
db
}
"
@
classmethod
def
setup_class
(
cls
):
super
().
setup_class
()
try
:
env_vars_postgres
=
{
"POSTGRES_USER"
:
"user"
,
"POSTGRES_PASSWORD"
:
"example"
,
"POSTGRES_DB"
:
"openwebui"
,
}
cls
.
docker_client
=
docker
.
from_env
()
cls
.
docker_client
.
containers
.
run
(
"postgres:16.2"
,
detach
=
True
,
environment
=
env_vars_postgres
,
name
=
cls
.
DOCKER_CONTAINER_NAME
,
ports
=
{
5432
:
(
"0.0.0.0"
,
8081
)},
command
=
"postgres -c log_statement=all"
,
)
time
.
sleep
(
0.5
)
database_url
=
cls
.
_create_db_url
(
env_vars_postgres
)
os
.
environ
[
"DATABASE_URL"
]
=
database_url
retries
=
10
db
=
None
while
retries
>
0
:
try
:
from
config
import
BACKEND_DIR
db
=
create_engine
(
database_url
,
pool_pre_ping
=
True
)
db
=
db
.
connect
()
log
.
info
(
"postgres is ready!"
)
break
except
Exception
as
e
:
log
.
warning
(
e
)
time
.
sleep
(
3
)
retries
-=
1
if
db
:
# import must be after setting env!
cls
.
fast_api_client
=
get_fast_api_client
()
db
.
close
()
else
:
raise
Exception
(
"Could not connect to Postgres"
)
except
Exception
as
ex
:
log
.
error
(
ex
)
cls
.
teardown_class
()
pytest
.
fail
(
f
"Could not setup test environment:
{
ex
}
"
)
def
_check_db_connection
(
self
):
from
apps.webui.internal.db
import
Session
retries
=
10
while
retries
>
0
:
try
:
Session
.
execute
(
text
(
"SELECT 1"
))
Session
.
commit
()
break
except
Exception
as
e
:
Session
.
rollback
()
log
.
warning
(
e
)
time
.
sleep
(
3
)
retries
-=
1
def
setup_method
(
self
):
super
().
setup_method
()
self
.
_check_db_connection
()
@
classmethod
def
teardown_class
(
cls
)
->
None
:
super
().
teardown_class
()
cls
.
docker_client
.
containers
.
get
(
cls
.
DOCKER_CONTAINER_NAME
).
remove
(
force
=
True
)
def
teardown_method
(
self
):
from
apps.webui.internal.db
import
Session
# rollback everything not yet committed
Session
.
commit
()
# truncate all tables
tables
=
[
"auth"
,
"chat"
,
"chatidtag"
,
"document"
,
"memory"
,
"model"
,
"prompt"
,
"tag"
,
'"user"'
,
]
for
table
in
tables
:
Session
.
execute
(
text
(
f
"TRUNCATE TABLE
{
table
}
"
))
Session
.
commit
()
backend/test/util/mock_user.py
0 → 100644
View file @
f9e3c47d
from
contextlib
import
contextmanager
from
fastapi
import
FastAPI
@
contextmanager
def
mock_webui_user
(
**
kwargs
):
from
apps.webui.main
import
app
with
mock_user
(
app
,
**
kwargs
):
yield
@
contextmanager
def
mock_user
(
app
:
FastAPI
,
**
kwargs
):
from
utils.utils
import
(
get_current_user
,
get_verified_user
,
get_admin_user
,
get_current_user_by_api_key
,
)
from
apps.webui.models.users
import
User
def
create_user
():
user_parameters
=
{
"id"
:
"1"
,
"name"
:
"John Doe"
,
"email"
:
"john.doe@openwebui.com"
,
"role"
:
"user"
,
"profile_image_url"
:
"/user.png"
,
"last_active_at"
:
1627351200
,
"updated_at"
:
1627351200
,
"created_at"
:
162735120
,
**
kwargs
,
}
return
User
(
**
user_parameters
)
app
.
dependency_overrides
=
{
get_current_user
:
create_user
,
get_verified_user
:
create_user
,
get_admin_user
:
create_user
,
get_current_user_by_api_key
:
create_user
,
}
yield
app
.
dependency_overrides
=
{}
backend/utils/tools.py
View file @
f9e3c47d
...
@@ -59,7 +59,10 @@ def get_tools_specs(tools) -> List[dict]:
...
@@ -59,7 +59,10 @@ def get_tools_specs(tools) -> List[dict]:
for
param_name
,
param_annotation
in
get_type_hints
(
for
param_name
,
param_annotation
in
get_type_hints
(
function
function
).
items
()
).
items
()
if
param_name
!=
"return"
and
param_name
!=
"__user__"
if
param_name
!=
"return"
and
not
(
param_name
.
startswith
(
"__"
)
and
param_name
.
endswith
(
"__"
)
)
},
},
"required"
:
[
"required"
:
[
name
name
...
...
backend/utils/utils.py
View file @
f9e3c47d
from
fastapi.security
import
HTTPBearer
,
HTTPAuthorizationCredentials
from
fastapi.security
import
HTTPBearer
,
HTTPAuthorizationCredentials
from
fastapi
import
HTTPException
,
status
,
Depends
,
Request
from
fastapi
import
HTTPException
,
status
,
Depends
,
Request
from
sqlalchemy.orm
import
Session
from
apps.webui.models.users
import
Users
from
apps.webui.models.users
import
Users
...
...
src/app.css
View file @
f9e3c47d
@font-face
{
@font-face
{
font-family
:
'Arimo'
;
font-family
:
'Inter'
;
src
:
url('/assets/fonts/Arimo-Variable.ttf')
;
src
:
url('/assets/fonts/Inter-Variable.ttf')
;
font-display
:
swap
;
}
@font-face
{
font-family
:
'Archivo'
;
src
:
url('/assets/fonts/Archivo-Variable.ttf')
;
font-display
:
swap
;
font-display
:
swap
;
}
}
...
@@ -32,6 +38,10 @@ math {
...
@@ -32,6 +38,10 @@ math {
@apply
underline;
@apply
underline;
}
}
.font-primary
{
font-family
:
'Archivo'
,
sans-serif
;
}
iframe
{
iframe
{
@apply
rounded-lg;
@apply
rounded-lg;
}
}
...
@@ -140,3 +150,7 @@ input[type='number'] {
...
@@ -140,3 +150,7 @@ input[type='number'] {
.cm-editor.cm-focused
{
.cm-editor.cm-focused
{
outline
:
none
;
outline
:
none
;
}
}
.tippy-box
[
data-theme
~=
'dark'
]
{
@apply
rounded-lg
bg-gray-950
text-xs
border
border-gray-900
shadow-xl;
}
src/app.html
View file @
f9e3c47d
...
@@ -23,6 +23,8 @@
...
@@ -23,6 +23,8 @@
// On page load or when changing themes, best to add inline in `head` to avoid FOUC
// On page load or when changing themes, best to add inline in `head` to avoid FOUC
(()
=>
{
(()
=>
{
if
(
localStorage
?.
theme
&&
localStorage
?.
theme
.
includes
(
'
oled
'
))
{
if
(
localStorage
?.
theme
&&
localStorage
?.
theme
.
includes
(
'
oled
'
))
{
document
.
documentElement
.
style
.
setProperty
(
'
--color-gray-800
'
,
'
#101010
'
);
document
.
documentElement
.
style
.
setProperty
(
'
--color-gray-850
'
,
'
#050505
'
);
document
.
documentElement
.
style
.
setProperty
(
'
--color-gray-900
'
,
'
#000000
'
);
document
.
documentElement
.
style
.
setProperty
(
'
--color-gray-900
'
,
'
#000000
'
);
document
.
documentElement
.
style
.
setProperty
(
'
--color-gray-950
'
,
'
#000000
'
);
document
.
documentElement
.
style
.
setProperty
(
'
--color-gray-950
'
,
'
#000000
'
);
document
.
documentElement
.
classList
.
add
(
'
dark
'
);
document
.
documentElement
.
classList
.
add
(
'
dark
'
);
...
@@ -80,13 +82,13 @@
...
@@ -80,13 +82,13 @@
id=
"logo"
id=
"logo"
style=
"
style=
"
position: absolute;
position: absolute;
width:
6rem
;
width:
auto
;
height: 6rem;
height: 6rem;
top: 4
1
%;
top: 4
4
%;
left: 50%;
left: 50%;
margin-left: -3rem;
margin-left: -3rem;
"
"
src=
"/
logo.sv
g"
src=
"/
static/splash.pn
g"
/>
/>
<div
<div
...
@@ -105,8 +107,8 @@
...
@@ -105,8 +107,8 @@
>
>
<img
<img
id=
"logo-her"
id=
"logo-her"
style=
"width:
13rem
; height: 13rem"
style=
"width:
auto
; height: 13rem"
src=
"/
logo.sv
g"
src=
"/
static/splash.pn
g"
class=
"animate-pulse-fast"
class=
"animate-pulse-fast"
/>
/>
...
...
src/lib/components/ChangelogModal.svelte
View file @
f9e3c47d
...
@@ -24,7 +24,7 @@
...
@@ -24,7 +24,7 @@
<Modal bind:show>
<Modal bind:show>
<div class="px-5 pt-4 dark:text-gray-300 text-gray-700">
<div class="px-5 pt-4 dark:text-gray-300 text-gray-700">
<div class="flex justify-between items-start">
<div class="flex justify-between items-start">
<div class="text-xl font-bold">
<div class="text-xl font-
semi
bold">
{$i18n.t('What’s New in')}
{$i18n.t('What’s New in')}
{$WEBUI_NAME}
{$WEBUI_NAME}
<Confetti x={[-1, -0.25]} y={[0, 0.5]} />
<Confetti x={[-1, -0.25]} y={[0, 0.5]} />
...
@@ -63,7 +63,7 @@
...
@@ -63,7 +63,7 @@
{#if changelog}
{#if changelog}
{#each Object.keys(changelog) as version}
{#each Object.keys(changelog) as version}
<div class=" mb-3 pr-2">
<div class=" mb-3 pr-2">
<div class="font-bold text-xl mb-1 dark:text-white">
<div class="font-
semi
bold text-xl mb-1 dark:text-white">
v{version} - {changelog[version].date}
v{version} - {changelog[version].date}
</div>
</div>
...
@@ -72,7 +72,7 @@
...
@@ -72,7 +72,7 @@
{#each Object.keys(changelog[version]).filter((section) => section !== 'date') as section}
{#each Object.keys(changelog[version]).filter((section) => section !== 'date') as section}
<div class="">
<div class="">
<div
<div
class="font-bold uppercase text-xs {section === 'added'
class="font-
semi
bold uppercase text-xs {section === 'added'
? 'text-white bg-blue-600'
? 'text-white bg-blue-600'
: section === 'fixed'
: section === 'fixed'
? 'text-white bg-green-600'
? 'text-white bg-green-600'
...
...
src/lib/components/admin/Settings.svelte
View file @
f9e3c47d
<script>
<script>
import { getContext, tick } from 'svelte';
import { getContext, tick
, onMount
} from 'svelte';
import { toast } from 'svelte-sonner';
import { toast } from 'svelte-sonner';
import Database from './Settings/Database.svelte';
import Database from './Settings/Database.svelte';
...
@@ -21,17 +21,31 @@
...
@@ -21,17 +21,31 @@
const i18n = getContext('i18n');
const i18n = getContext('i18n');
let selectedTab = 'general';
let selectedTab = 'general';
onMount(() => {
const containerElement = document.getElementById('admin-settings-tabs-container');
if (containerElement) {
containerElement.addEventListener('wheel', function (event) {
if (event.deltaY !== 0) {
// Adjust horizontal scroll position based on vertical scroll
containerElement.scrollLeft += event.deltaY;
}
});
}
});
</script>
</script>
<div class="flex flex-col lg:flex-row w-full h-full py-2 lg:space-x-4">
<div class="flex flex-col lg:flex-row w-full h-full py-2 lg:space-x-4">
<div
<div
id="admin-settings-tabs-container"
class="tabs flex flex-row overflow-x-auto space-x-1 max-w-full lg:space-x-0 lg:space-y-1 lg:flex-col lg:flex-none lg:w-44 dark:text-gray-200 text-xs text-left scrollbar-none"
class="tabs flex flex-row overflow-x-auto space-x-1 max-w-full lg:space-x-0 lg:space-y-1 lg:flex-col lg:flex-none lg:w-44 dark:text-gray-200 text-xs text-left scrollbar-none"
>
>
<button
<button
class="px-2.5 py-2.5 min-w-fit rounded-lg flex-1 lg:flex-none flex text-right transition {selectedTab ===
class="px-2.5 py-2.5 min-w-fit rounded-lg flex-1 lg:flex-none flex text-right transition {selectedTab ===
'general'
'general'
? 'bg-gray-
2
00 dark:bg-gray-800'
? 'bg-gray-
1
00 dark:bg-gray-800'
: ' hover:bg-gray-
30
0 dark:hover:bg-gray-850'}"
: ' hover:bg-gray-
5
0 dark:hover:bg-gray-850'}"
on:click={() => {
on:click={() => {
selectedTab = 'general';
selectedTab = 'general';
}}
}}
...
@@ -56,8 +70,8 @@
...
@@ -56,8 +70,8 @@
<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 ===
'users'
'users'
? 'bg-gray-
2
00 dark:bg-gray-800'
? 'bg-gray-
1
00 dark:bg-gray-800'
: ' hover:bg-gray-
30
0 dark:hover:bg-gray-850'}"
: ' hover:bg-gray-
5
0 dark:hover:bg-gray-850'}"
on:click={() => {
on:click={() => {
selectedTab = 'users';
selectedTab = 'users';
}}
}}
...
@@ -80,8 +94,8 @@
...
@@ -80,8 +94,8 @@
<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 ===
'connections'
'connections'
? 'bg-gray-
2
00 dark:bg-gray-800'
? 'bg-gray-
1
00 dark:bg-gray-800'
: ' hover:bg-gray-
30
0 dark:hover:bg-gray-850'}"
: ' hover:bg-gray-
5
0 dark:hover:bg-gray-850'}"
on:click={() => {
on:click={() => {
selectedTab = 'connections';
selectedTab = 'connections';
}}
}}
...
@@ -104,8 +118,8 @@
...
@@ -104,8 +118,8 @@
<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'
? 'bg-gray-
2
00 dark:bg-gray-800'
? 'bg-gray-
1
00 dark:bg-gray-800'
: ' hover:bg-gray-
30
0 dark:hover:bg-gray-850'}"
: ' hover:bg-gray-
5
0 dark:hover:bg-gray-850'}"
on:click={() => {
on:click={() => {
selectedTab = 'models';
selectedTab = 'models';
}}
}}
...
@@ -130,8 +144,8 @@
...
@@ -130,8 +144,8 @@
<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 ===
'documents'
'documents'
? 'bg-gray-
2
00 dark:bg-gray-800'
? 'bg-gray-
1
00 dark:bg-gray-800'
: ' hover:bg-gray-
30
0 dark:hover:bg-gray-850'}"
: ' hover:bg-gray-
5
0 dark:hover:bg-gray-850'}"
on:click={() => {
on:click={() => {
selectedTab = 'documents';
selectedTab = 'documents';
}}
}}
...
@@ -160,8 +174,8 @@
...
@@ -160,8 +174,8 @@
<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 ===
'web'
'web'
? 'bg-gray-
2
00 dark:bg-gray-800'
? 'bg-gray-
1
00 dark:bg-gray-800'
: ' hover:bg-gray-
30
0 dark:hover:bg-gray-850'}"
: ' hover:bg-gray-
5
0 dark:hover:bg-gray-850'}"
on:click={() => {
on:click={() => {
selectedTab = 'web';
selectedTab = 'web';
}}
}}
...
@@ -184,8 +198,8 @@
...
@@ -184,8 +198,8 @@
<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 ===
'interface'
'interface'
? 'bg-gray-
2
00 dark:bg-gray-800'
? 'bg-gray-
1
00 dark:bg-gray-800'
: ' hover:bg-gray-
30
0 dark:hover:bg-gray-850'}"
: ' hover:bg-gray-
5
0 dark:hover:bg-gray-850'}"
on:click={() => {
on:click={() => {
selectedTab = 'interface';
selectedTab = 'interface';
}}
}}
...
@@ -210,8 +224,8 @@
...
@@ -210,8 +224,8 @@
<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 ===
'audio'
'audio'
? 'bg-gray-
2
00 dark:bg-gray-800'
? 'bg-gray-
1
00 dark:bg-gray-800'
: ' hover:bg-gray-
30
0 dark:hover:bg-gray-850'}"
: ' hover:bg-gray-
5
0 dark:hover:bg-gray-850'}"
on:click={() => {
on:click={() => {
selectedTab = 'audio';
selectedTab = 'audio';
}}
}}
...
@@ -237,8 +251,8 @@
...
@@ -237,8 +251,8 @@
<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 ===
'images'
'images'
? 'bg-gray-
2
00 dark:bg-gray-800'
? 'bg-gray-
1
00 dark:bg-gray-800'
: ' hover:bg-gray-
30
0 dark:hover:bg-gray-850'}"
: ' hover:bg-gray-
5
0 dark:hover:bg-gray-850'}"
on:click={() => {
on:click={() => {
selectedTab = 'images';
selectedTab = 'images';
}}
}}
...
@@ -263,8 +277,8 @@
...
@@ -263,8 +277,8 @@
<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 ===
'pipelines'
'pipelines'
? 'bg-gray-
2
00 dark:bg-gray-800'
? 'bg-gray-
1
00 dark:bg-gray-800'
: ' hover:bg-gray-
30
0 dark:hover:bg-gray-850'}"
: ' hover:bg-gray-
5
0 dark:hover:bg-gray-850'}"
on:click={() => {
on:click={() => {
selectedTab = 'pipelines';
selectedTab = 'pipelines';
}}
}}
...
@@ -293,8 +307,8 @@
...
@@ -293,8 +307,8 @@
<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 ===
'db'
'db'
? 'bg-gray-
2
00 dark:bg-gray-800'
? 'bg-gray-
1
00 dark:bg-gray-800'
: ' hover:bg-gray-
30
0 dark:hover:bg-gray-850'}"
: ' hover:bg-gray-
5
0 dark:hover:bg-gray-850'}"
on:click={() => {
on:click={() => {
selectedTab = 'db';
selectedTab = 'db';
}}
}}
...
...
src/lib/components/admin/Settings/Audio.svelte
View file @
f9e3c47d
...
@@ -138,7 +138,7 @@
...
@@ -138,7 +138,7 @@
<div>
<div>
<div class="mt-1 flex gap-2 mb-1">
<div class="mt-1 flex gap-2 mb-1">
<input
<input
class="flex-1 w-full rounded-l-lg py-2 pl-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
class="flex-1 w-full rounded-l-lg py-2 pl-4 text-sm
bg-gray-50
dark:text-gray-300 dark:bg-gray-850 outline-none"
placeholder={$i18n.t('API Base URL')}
placeholder={$i18n.t('API Base URL')}
bind:value={STT_OPENAI_API_BASE_URL}
bind:value={STT_OPENAI_API_BASE_URL}
required
required
...
@@ -156,7 +156,7 @@
...
@@ -156,7 +156,7 @@
<div class="flex-1">
<div class="flex-1">
<input
<input
list="model-list"
list="model-list"
class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
class="w-full rounded-lg py-2 px-4 text-sm
bg-gray-50
dark:text-gray-300 dark:bg-gray-850 outline-none"
bind:value={STT_MODEL}
bind:value={STT_MODEL}
placeholder="Select a model"
placeholder="Select a model"
/>
/>
...
@@ -203,7 +203,7 @@
...
@@ -203,7 +203,7 @@
<div>
<div>
<div class="mt-1 flex gap-2 mb-1">
<div class="mt-1 flex gap-2 mb-1">
<input
<input
class="flex-1 w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
class="flex-1 w-full rounded-lg py-2 px-4 text-sm
bg-gray-50
dark:text-gray-300 dark:bg-gray-850 outline-none"
placeholder={$i18n.t('API Base URL')}
placeholder={$i18n.t('API Base URL')}
bind:value={TTS_OPENAI_API_BASE_URL}
bind:value={TTS_OPENAI_API_BASE_URL}
required
required
...
@@ -222,7 +222,7 @@
...
@@ -222,7 +222,7 @@
<div class="flex w-full">
<div class="flex w-full">
<div class="flex-1">
<div class="flex-1">
<select
<select
class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
class="w-full rounded-lg py-2 px-4 text-sm
bg-gray-50
dark:text-gray-300 dark:bg-gray-850 outline-none"
bind:value={TTS_VOICE}
bind:value={TTS_VOICE}
>
>
<option value="" selected={TTS_VOICE !== ''}>{$i18n.t('Default')}</option>
<option value="" selected={TTS_VOICE !== ''}>{$i18n.t('Default')}</option>
...
@@ -245,7 +245,7 @@
...
@@ -245,7 +245,7 @@
<div class="flex-1">
<div class="flex-1">
<input
<input
list="voice-list"
list="voice-list"
class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
class="w-full rounded-lg py-2 px-4 text-sm
bg-gray-50
dark:text-gray-300 dark:bg-gray-850 outline-none"
bind:value={TTS_VOICE}
bind:value={TTS_VOICE}
placeholder="Select a voice"
placeholder="Select a voice"
/>
/>
...
@@ -264,7 +264,7 @@
...
@@ -264,7 +264,7 @@
<div class="flex-1">
<div class="flex-1">
<input
<input
list="model-list"
list="model-list"
class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
class="w-full rounded-lg py-2 px-4 text-sm
bg-gray-50
dark:text-gray-300 dark:bg-gray-850 outline-none"
bind:value={TTS_MODEL}
bind:value={TTS_MODEL}
placeholder="Select a model"
placeholder="Select a model"
/>
/>
...
...
src/lib/components/admin/Settings/Connections.svelte
View file @
f9e3c47d
...
@@ -200,7 +200,7 @@
...
@@ -200,7 +200,7 @@
<input
<input
class="w-full rounded-lg py-2 px-4 {pipelineUrls[url]
class="w-full rounded-lg py-2 px-4 {pipelineUrls[url]
? 'pr-8'
? 'pr-8'
: ''} text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
: ''} text-sm
bg-gray-50
dark:text-gray-300 dark:bg-gray-850 outline-none"
placeholder={$i18n.t('API Base URL')}
placeholder={$i18n.t('API Base URL')}
bind:value={url}
bind:value={url}
autocomplete="off"
autocomplete="off"
...
@@ -338,7 +338,7 @@
...
@@ -338,7 +338,7 @@
{#each OLLAMA_BASE_URLS as url, idx}
{#each OLLAMA_BASE_URLS as url, idx}
<div class="flex gap-1.5">
<div class="flex gap-1.5">
<input
<input
class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
class="w-full rounded-lg py-2 px-4 text-sm
bg-gray-50
dark:text-gray-300 dark:bg-gray-850 outline-none"
placeholder={$i18n.t('Enter URL (e.g. http://localhost:11434)')}
placeholder={$i18n.t('Enter URL (e.g. http://localhost:11434)')}
bind:value={url}
bind:value={url}
/>
/>
...
...
src/lib/components/admin/Settings/Documents.svelte
View file @
f9e3c47d
...
@@ -279,7 +279,7 @@
...
@@ -279,7 +279,7 @@
</div>
</div>
<button
<button
class=" self-center text-xs p-1 px-3 bg-gray-
10
0 dark:bg-gray-800 dark:hover:bg-gray-700 rounded-lg flex flex-row space-x-1 items-center {scanDirLoading
class=" self-center text-xs p-1 px-3 bg-gray-
5
0 dark:bg-gray-800 dark:hover:bg-gray-700 rounded-lg flex flex-row space-x-1 items-center {scanDirLoading
? ' cursor-not-allowed'
? ' cursor-not-allowed'
: ''}"
: ''}"
on:click={() => {
on:click={() => {
...
@@ -352,7 +352,7 @@
...
@@ -352,7 +352,7 @@
{#if embeddingEngine === 'openai'}
{#if embeddingEngine === 'openai'}
<div class="my-0.5 flex gap-2">
<div class="my-0.5 flex gap-2">
<input
<input
class="flex-1 w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
class="flex-1 w-full rounded-lg py-2 px-4 text-sm
bg-gray-50
dark:text-gray-300 dark:bg-gray-850 outline-none"
placeholder={$i18n.t('API Base URL')}
placeholder={$i18n.t('API Base URL')}
bind:value={OpenAIUrl}
bind:value={OpenAIUrl}
required
required
...
@@ -415,7 +415,7 @@
...
@@ -415,7 +415,7 @@
<div class="flex w-full">
<div class="flex w-full">
<div class="flex-1 mr-2">
<div class="flex-1 mr-2">
<select
<select
class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
class="w-full rounded-lg py-2 px-4 text-sm
bg-gray-50
dark:text-gray-300 dark:bg-gray-850 outline-none"
bind:value={embeddingModel}
bind:value={embeddingModel}
placeholder={$i18n.t('Select a model')}
placeholder={$i18n.t('Select a model')}
required
required
...
@@ -424,7 +424,7 @@
...
@@ -424,7 +424,7 @@
<option value="" disabled selected>{$i18n.t('Select a model')}</option>
<option value="" disabled selected>{$i18n.t('Select a model')}</option>
{/if}
{/if}
{#each $models.filter((m) => m.id && m.ollama && !(m?.preset ?? false)) as model}
{#each $models.filter((m) => m.id && m.ollama && !(m?.preset ?? false)) as model}
<option value={model.id} class="bg-gray-
10
0 dark:bg-gray-700">{model.name}</option>
<option value={model.id} class="bg-gray-
5
0 dark:bg-gray-700">{model.name}</option>
{/each}
{/each}
</select>
</select>
</div>
</div>
...
@@ -433,7 +433,7 @@
...
@@ -433,7 +433,7 @@
<div class="flex w-full">
<div class="flex w-full">
<div class="flex-1 mr-2">
<div class="flex-1 mr-2">
<input
<input
class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
class="w-full rounded-lg py-2 px-4 text-sm
bg-gray-50
dark:text-gray-300 dark:bg-gray-850 outline-none"
placeholder={$i18n.t('Set embedding model (e.g. {{model}})', {
placeholder={$i18n.t('Set embedding model (e.g. {{model}})', {
model: embeddingModel.slice(-40)
model: embeddingModel.slice(-40)
})}
})}
...
@@ -443,7 +443,7 @@
...
@@ -443,7 +443,7 @@
{#if embeddingEngine === ''}
{#if embeddingEngine === ''}
<button
<button
class="px-2.5 bg-gray-
10
0 hover:bg-gray-200 text-gray-800 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-gray-100 rounded-lg transition"
class="px-2.5 bg-gray-
5
0 hover:bg-gray-200 text-gray-800 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-gray-100 rounded-lg transition"
on:click={() => {
on:click={() => {
embeddingModelUpdateHandler();
embeddingModelUpdateHandler();
}}
}}
...
@@ -512,7 +512,7 @@
...
@@ -512,7 +512,7 @@
<div class="flex w-full">
<div class="flex w-full">
<div class="flex-1 mr-2">
<div class="flex-1 mr-2">
<input
<input
class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
class="w-full rounded-lg py-2 px-4 text-sm
bg-gray-50
dark:text-gray-300 dark:bg-gray-850 outline-none"
placeholder={$i18n.t('Set reranking model (e.g. {{model}})', {
placeholder={$i18n.t('Set reranking model (e.g. {{model}})', {
model: 'BAAI/bge-reranker-v2-m3'
model: 'BAAI/bge-reranker-v2-m3'
})}
})}
...
@@ -520,7 +520,7 @@
...
@@ -520,7 +520,7 @@
/>
/>
</div>
</div>
<button
<button
class="px-2.5 bg-gray-
10
0 hover:bg-gray-200 text-gray-800 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-gray-100 rounded-lg transition"
class="px-2.5 bg-gray-
5
0 hover:bg-gray-200 text-gray-800 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-gray-100 rounded-lg transition"
on:click={() => {
on:click={() => {
rerankingModelUpdateHandler();
rerankingModelUpdateHandler();
}}
}}
...
@@ -602,7 +602,7 @@
...
@@ -602,7 +602,7 @@
<div class="flex w-full mt-2">
<div class="flex w-full mt-2">
<div class="flex-1 mr-2">
<div class="flex-1 mr-2">
<input
<input
class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
class="w-full rounded-lg py-2 px-4 text-sm
bg-gray-50
dark:text-gray-300 dark:bg-gray-850 outline-none"
placeholder={$i18n.t('Enter Tika Server URL')}
placeholder={$i18n.t('Enter Tika Server URL')}
bind:value={tikaServerUrl}
bind:value={tikaServerUrl}
/>
/>
...
@@ -621,7 +621,7 @@
...
@@ -621,7 +621,7 @@
<div class="self-center p-3">
<div class="self-center p-3">
<input
<input
class=" w-full rounded-lg py-1.5 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
class=" w-full rounded-lg py-1.5 px-4 text-sm
bg-gray-50
dark:text-gray-300 dark:bg-gray-850 outline-none"
type="number"
type="number"
placeholder={$i18n.t('Enter Top K')}
placeholder={$i18n.t('Enter Top K')}
bind:value={querySettings.k}
bind:value={querySettings.k}
...
@@ -639,7 +639,7 @@
...
@@ -639,7 +639,7 @@
<div class="self-center p-3">
<div class="self-center p-3">
<input
<input
class=" w-full rounded-lg py-1.5 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
class=" w-full rounded-lg py-1.5 px-4 text-sm
bg-gray-50
dark:text-gray-300 dark:bg-gray-850 outline-none"
type="number"
type="number"
step="0.01"
step="0.01"
placeholder={$i18n.t('Enter Score')}
placeholder={$i18n.t('Enter Score')}
...
@@ -667,7 +667,7 @@
...
@@ -667,7 +667,7 @@
<div class=" mb-2.5 text-sm font-medium">{$i18n.t('RAG Template')}</div>
<div class=" mb-2.5 text-sm font-medium">{$i18n.t('RAG Template')}</div>
<textarea
<textarea
bind:value={querySettings.template}
bind:value={querySettings.template}
class="w-full rounded-lg px-4 py-3 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none resize-none"
class="w-full rounded-lg px-4 py-3 text-sm
bg-gray-50
dark:text-gray-300 dark:bg-gray-850 outline-none resize-none"
rows="4"
rows="4"
/>
/>
</div>
</div>
...
@@ -683,7 +683,7 @@
...
@@ -683,7 +683,7 @@
<div class="self-center text-xs font-medium min-w-fit mb-1">{$i18n.t('Chunk Size')}</div>
<div class="self-center text-xs font-medium min-w-fit mb-1">{$i18n.t('Chunk Size')}</div>
<div class="self-center">
<div class="self-center">
<input
<input
class=" w-full rounded-lg py-1.5 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
class=" w-full rounded-lg py-1.5 px-4 text-sm
bg-gray-50
dark:text-gray-300 dark:bg-gray-850 outline-none"
type="number"
type="number"
placeholder={$i18n.t('Enter Chunk Size')}
placeholder={$i18n.t('Enter Chunk Size')}
bind:value={chunkSize}
bind:value={chunkSize}
...
@@ -700,7 +700,7 @@
...
@@ -700,7 +700,7 @@
<div class="self-center">
<div class="self-center">
<input
<input
class="w-full rounded-lg py-1.5 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
class="w-full rounded-lg py-1.5 px-4 text-sm
bg-gray-50
dark:text-gray-300 dark:bg-gray-850 outline-none"
type="number"
type="number"
placeholder={$i18n.t('Enter Chunk Overlap')}
placeholder={$i18n.t('Enter Chunk Overlap')}
bind:value={chunkOverlap}
bind:value={chunkOverlap}
...
...
src/lib/components/admin/Settings/General.svelte
View file @
f9e3c47d
...
@@ -107,7 +107,7 @@
...
@@ -107,7 +107,7 @@
<div class="flex mt-2 space-x-2">
<div class="flex mt-2 space-x-2">
<input
<input
class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
class="w-full rounded-lg py-2 px-4 text-sm
bg-gray-50
dark:text-gray-300 dark:bg-gray-850 outline-none"
type="text"
type="text"
placeholder={`e.g.) "30m","1h", "10d". `}
placeholder={`e.g.) "30m","1h", "10d". `}
bind:value={adminConfig.JWT_EXPIRES_IN}
bind:value={adminConfig.JWT_EXPIRES_IN}
...
@@ -131,7 +131,7 @@
...
@@ -131,7 +131,7 @@
<div class="flex mt-2 space-x-2">
<div class="flex mt-2 space-x-2">
<input
<input
class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
class="w-full rounded-lg py-2 px-4 text-sm
bg-gray-50
dark:text-gray-300 dark:bg-gray-850 outline-none"
type="text"
type="text"
placeholder={`https://example.com/webhook`}
placeholder={`https://example.com/webhook`}
bind:value={webhookUrl}
bind:value={webhookUrl}
...
...
src/lib/components/admin/Settings/Images.svelte
View file @
f9e3c47d
...
@@ -240,13 +240,13 @@
...
@@ -240,13 +240,13 @@
<div class="flex w-full">
<div class="flex w-full">
<div class="flex-1 mr-2">
<div class="flex-1 mr-2">
<input
<input
class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
class="w-full rounded-lg py-2 px-4 text-sm
bg-gray-50
dark:text-gray-300 dark:bg-gray-850 outline-none"
placeholder={$i18n.t('Enter URL (e.g. http://127.0.0.1:7860/)')}
placeholder={$i18n.t('Enter URL (e.g. http://127.0.0.1:7860/)')}
bind:value={AUTOMATIC1111_BASE_URL}
bind:value={AUTOMATIC1111_BASE_URL}
/>
/>
</div>
</div>
<button
<button
class="px-2.5 bg-gray-
10
0 hover:bg-gray-
2
00 text-gray-800 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-gray-100 rounded-lg transition"
class="px-2.5 bg-gray-
5
0 hover:bg-gray-
1
00 text-gray-800 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-gray-100 rounded-lg transition"
type="button"
type="button"
on:click={() => {
on:click={() => {
updateUrlHandler();
updateUrlHandler();
...
@@ -299,13 +299,13 @@
...
@@ -299,13 +299,13 @@
<div class="flex w-full">
<div class="flex w-full">
<div class="flex-1 mr-2">
<div class="flex-1 mr-2">
<input
<input
class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
class="w-full rounded-lg py-2 px-4 text-sm
bg-gray-50
dark:text-gray-300 dark:bg-gray-850 outline-none"
placeholder={$i18n.t('Enter URL (e.g. http://127.0.0.1:7860/)')}
placeholder={$i18n.t('Enter URL (e.g. http://127.0.0.1:7860/)')}
bind:value={COMFYUI_BASE_URL}
bind:value={COMFYUI_BASE_URL}
/>
/>
</div>
</div>
<button
<button
class="px-2.5 bg-gray-
10
0 hover:bg-gray-
2
00 text-gray-800 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-gray-100 rounded-lg transition"
class="px-2.5 bg-gray-
5
0 hover:bg-gray-
1
00 text-gray-800 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-gray-100 rounded-lg transition"
type="button"
type="button"
on:click={() => {
on:click={() => {
updateUrlHandler();
updateUrlHandler();
...
@@ -331,7 +331,7 @@
...
@@ -331,7 +331,7 @@
<div class="flex gap-2 mb-1">
<div class="flex gap-2 mb-1">
<input
<input
class="flex-1 w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
class="flex-1 w-full rounded-lg py-2 px-4 text-sm
bg-gray-50
dark:text-gray-300 dark:bg-gray-850 outline-none"
placeholder={$i18n.t('API Base URL')}
placeholder={$i18n.t('API Base URL')}
bind:value={OPENAI_API_BASE_URL}
bind:value={OPENAI_API_BASE_URL}
required
required
...
@@ -354,7 +354,7 @@
...
@@ -354,7 +354,7 @@
<div class="flex-1">
<div class="flex-1">
<input
<input
list="model-list"
list="model-list"
class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
class="w-full rounded-lg py-2 px-4 text-sm
bg-gray-50
dark:text-gray-300 dark:bg-gray-850 outline-none"
bind:value={selectedModel}
bind:value={selectedModel}
placeholder="Select a model"
placeholder="Select a model"
/>
/>
...
@@ -368,7 +368,7 @@
...
@@ -368,7 +368,7 @@
</div>
</div>
{:else}
{:else}
<select
<select
class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
class="w-full rounded-lg py-2 px-4 text-sm
bg-gray-50
dark:text-gray-300 dark:bg-gray-850 outline-none"
bind:value={selectedModel}
bind:value={selectedModel}
placeholder={$i18n.t('Select a model')}
placeholder={$i18n.t('Select a model')}
required
required
...
@@ -391,7 +391,7 @@
...
@@ -391,7 +391,7 @@
<div class="flex w-full">
<div class="flex w-full">
<div class="flex-1 mr-2">
<div class="flex-1 mr-2">
<input
<input
class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
class="w-full rounded-lg py-2 px-4 text-sm
bg-gray-50
dark:text-gray-300 dark:bg-gray-850 outline-none"
placeholder={$i18n.t('Enter Image Size (e.g. 512x512)')}
placeholder={$i18n.t('Enter Image Size (e.g. 512x512)')}
bind:value={imageSize}
bind:value={imageSize}
/>
/>
...
@@ -404,7 +404,7 @@
...
@@ -404,7 +404,7 @@
<div class="flex w-full">
<div class="flex w-full">
<div class="flex-1 mr-2">
<div class="flex-1 mr-2">
<input
<input
class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
class="w-full rounded-lg py-2 px-4 text-sm
bg-gray-50
dark:text-gray-300 dark:bg-gray-850 outline-none"
placeholder={$i18n.t('Enter Number of Steps (e.g. 50)')}
placeholder={$i18n.t('Enter Number of Steps (e.g. 50)')}
bind:value={steps}
bind:value={steps}
/>
/>
...
...
src/lib/components/admin/Settings/Interface.svelte
View file @
f9e3c47d
...
@@ -88,7 +88,7 @@
...
@@ -88,7 +88,7 @@
<div class="flex-1">
<div class="flex-1">
<div class=" text-xs mb-1">{$i18n.t('Local Models')}</div>
<div class=" text-xs mb-1">{$i18n.t('Local Models')}</div>
<select
<select
class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
class="w-full rounded-lg py-2 px-4 text-sm
bg-gray-50
dark:text-gray-300 dark:bg-gray-850 outline-none"
bind:value={taskConfig.TASK_MODEL}
bind:value={taskConfig.TASK_MODEL}
placeholder={$i18n.t('Select a model')}
placeholder={$i18n.t('Select a model')}
>
>
...
@@ -104,7 +104,7 @@
...
@@ -104,7 +104,7 @@
<div class="flex-1">
<div class="flex-1">
<div class=" text-xs mb-1">{$i18n.t('External Models')}</div>
<div class=" text-xs mb-1">{$i18n.t('External Models')}</div>
<select
<select
class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
class="w-full rounded-lg py-2 px-4 text-sm
bg-gray-50
dark:text-gray-300 dark:bg-gray-850 outline-none"
bind:value={taskConfig.TASK_MODEL_EXTERNAL}
bind:value={taskConfig.TASK_MODEL_EXTERNAL}
placeholder={$i18n.t('Select a model')}
placeholder={$i18n.t('Select a model')}
>
>
...
@@ -122,7 +122,7 @@
...
@@ -122,7 +122,7 @@
<div class=" mb-2.5 text-sm font-medium">{$i18n.t('Title Generation Prompt')}</div>
<div class=" mb-2.5 text-sm font-medium">{$i18n.t('Title Generation Prompt')}</div>
<textarea
<textarea
bind:value={taskConfig.TITLE_GENERATION_PROMPT_TEMPLATE}
bind:value={taskConfig.TITLE_GENERATION_PROMPT_TEMPLATE}
class="w-full rounded-lg py-3 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none resize-none"
class="w-full rounded-lg py-3 px-4 text-sm
bg-gray-50
dark:text-gray-300 dark:bg-gray-850 outline-none resize-none"
rows="6"
rows="6"
/>
/>
</div>
</div>
...
@@ -131,7 +131,7 @@
...
@@ -131,7 +131,7 @@
<div class=" mb-2.5 text-sm font-medium">{$i18n.t('Search Query Generation Prompt')}</div>
<div class=" mb-2.5 text-sm font-medium">{$i18n.t('Search Query Generation Prompt')}</div>
<textarea
<textarea
bind:value={taskConfig.SEARCH_QUERY_GENERATION_PROMPT_TEMPLATE}
bind:value={taskConfig.SEARCH_QUERY_GENERATION_PROMPT_TEMPLATE}
class="w-full rounded-lg py-3 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none resize-none"
class="w-full rounded-lg py-3 px-4 text-sm
bg-gray-50
dark:text-gray-300 dark:bg-gray-850 outline-none resize-none"
rows="6"
rows="6"
/>
/>
</div>
</div>
...
@@ -142,7 +142,7 @@
...
@@ -142,7 +142,7 @@
</div>
</div>
<input
<input
bind:value={taskConfig.SEARCH_QUERY_PROMPT_LENGTH_THRESHOLD}
bind:value={taskConfig.SEARCH_QUERY_PROMPT_LENGTH_THRESHOLD}
class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none resize-none"
class="w-full rounded-lg py-2 px-4 text-sm
bg-gray-50
dark:text-gray-300 dark:bg-gray-850 outline-none resize-none"
type="number"
type="number"
/>
/>
</div>
</div>
...
@@ -273,24 +273,26 @@
...
@@ -273,24 +273,26 @@
</div>
</div>
<div class="grid lg:grid-cols-2 flex-col gap-1.5">
<div class="grid lg:grid-cols-2 flex-col gap-1.5">
{#each promptSuggestions as prompt, promptIdx}
{#each promptSuggestions as prompt, promptIdx}
<div class=" flex dark:bg-gray-850 rounded-xl py-1.5">
<div
class=" flex border border-gray-100 dark:border-none dark:bg-gray-850 rounded-xl py-1.5"
>
<div class="flex flex-col flex-1 pl-1">
<div class="flex flex-col flex-1 pl-1">
<div class="flex border-b dark:border-gray-800 w-full">
<div class="flex border-b
border-gray-100
dark:border-gray-800 w-full">
<input
<input
class="px-3 py-1.5 text-xs w-full bg-transparent outline-none border-r dark:border-gray-800"
class="px-3 py-1.5 text-xs w-full bg-transparent outline-none border-r
border-gray-100
dark:border-gray-800"
placeholder={$i18n.t('Title (e.g. Tell me a fun fact)')}
placeholder={$i18n.t('Title (e.g. Tell me a fun fact)')}
bind:value={prompt.title[0]}
bind:value={prompt.title[0]}
/>
/>
<input
<input
class="px-3 py-1.5 text-xs w-full bg-transparent outline-none border-r dark:border-gray-800"
class="px-3 py-1.5 text-xs w-full bg-transparent outline-none border-r
border-gray-100
dark:border-gray-800"
placeholder={$i18n.t('Subtitle (e.g. about the Roman Empire)')}
placeholder={$i18n.t('Subtitle (e.g. about the Roman Empire)')}
bind:value={prompt.title[1]}
bind:value={prompt.title[1]}
/>
/>
</div>
</div>
<input
<input
class="px-3 py-1.5 text-xs w-full bg-transparent outline-none border-r dark:border-gray-800"
class="px-3 py-1.5 text-xs w-full bg-transparent outline-none border-r
border-gray-100
dark:border-gray-800"
placeholder={$i18n.t('Prompt (e.g. Tell me a fun fact about the Roman Empire)')}
placeholder={$i18n.t('Prompt (e.g. Tell me a fun fact about the Roman Empire)')}
bind:value={prompt.content}
bind:value={prompt.content}
/>
/>
...
...
src/lib/components/admin/Settings/Models.svelte
View file @
f9e3c47d
...
@@ -158,12 +158,14 @@
...
@@ -158,12 +158,14 @@
return;
return;
}
}
const [res, controller] = await pullModel(localStorage.token, sanitizedModelTag, '0').catch(
const [res, controller] = await pullModel(
(error) => {
localStorage.token,
toast.error(error);
sanitizedModelTag,
return null;
selectedOllamaUrlIdx
}
).catch((error) => {
);
toast.error(error);
return null;
});
if (res) {
if (res) {
const reader = res.body
const reader = res.body
...
@@ -570,12 +572,12 @@
...
@@ -570,12 +572,12 @@
<div class="flex gap-2">
<div class="flex gap-2">
<div class="flex-1 pb-1">
<div class="flex-1 pb-1">
<select
<select
class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
class="w-full rounded-lg py-2 px-4 text-sm
bg-gray-50
dark:text-gray-300 dark:bg-gray-850 outline-none"
bind:value={selectedOllamaUrlIdx}
bind:value={selectedOllamaUrlIdx}
placeholder={$i18n.t('Select an Ollama instance')}
placeholder={$i18n.t('Select an Ollama instance')}
>
>
{#each OLLAMA_URLS as url, idx}
{#each OLLAMA_URLS as url, idx}
<option value={idx} class="bg-gray-
10
0 dark:bg-gray-700">{url}</option>
<option value={idx} class="bg-gray-
5
0 dark:bg-gray-700">{url}</option>
{/each}
{/each}
</select>
</select>
</div>
</div>
...
@@ -584,7 +586,7 @@
...
@@ -584,7 +586,7 @@
<div class="flex w-full justify-end">
<div class="flex w-full justify-end">
<Tooltip content="Update All Models" placement="top">
<Tooltip content="Update All Models" placement="top">
<button
<button
class="p-2.5 flex gap-2 items-center bg-gray-
10
0 hover:bg-gray-200 text-gray-800 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-gray-100 rounded-lg transition"
class="p-2.5 flex gap-2 items-center bg-gray-
5
0 hover:bg-gray-200 text-gray-800 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-gray-100 rounded-lg transition"
on:click={() => {
on:click={() => {
updateModelsHandler();
updateModelsHandler();
}}
}}
...
@@ -619,7 +621,7 @@
...
@@ -619,7 +621,7 @@
<div class="flex w-full">
<div class="flex w-full">
<div class="flex-1 mr-2">
<div class="flex-1 mr-2">
<input
<input
class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
class="w-full rounded-lg py-2 px-4 text-sm
bg-gray-50
dark:text-gray-300 dark:bg-gray-850 outline-none"
placeholder={$i18n.t('Enter model tag (e.g. {{modelTag}})', {
placeholder={$i18n.t('Enter model tag (e.g. {{modelTag}})', {
modelTag: 'mistral:7b'
modelTag: 'mistral:7b'
})}
})}
...
@@ -627,7 +629,7 @@
...
@@ -627,7 +629,7 @@
/>
/>
</div>
</div>
<button
<button
class="px-2.5 bg-gray-
10
0 hover:bg-gray-200 text-gray-800 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-gray-100 rounded-lg transition"
class="px-2.5 bg-gray-
5
0 hover:bg-gray-200 text-gray-800 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-gray-100 rounded-lg transition"
on:click={() => {
on:click={() => {
pullModelHandler();
pullModelHandler();
}}
}}
...
@@ -753,7 +755,7 @@
...
@@ -753,7 +755,7 @@
<div class="flex w-full">
<div class="flex w-full">
<div class="flex-1 mr-2">
<div class="flex-1 mr-2">
<select
<select
class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
class="w-full rounded-lg py-2 px-4 text-sm
bg-gray-50
dark:text-gray-300 dark:bg-gray-850 outline-none"
bind:value={deleteModelTag}
bind:value={deleteModelTag}
placeholder={$i18n.t('Select a model')}
placeholder={$i18n.t('Select a model')}
>
>
...
@@ -761,7 +763,7 @@
...
@@ -761,7 +763,7 @@
<option value="" disabled selected>{$i18n.t('Select a model')}</option>
<option value="" disabled selected>{$i18n.t('Select a model')}</option>
{/if}
{/if}
{#each $models.filter((m) => !(m?.preset ?? false) && m.owned_by === 'ollama' && (selectedOllamaUrlIdx === null ? true : (m?.ollama?.urls ?? []).includes(selectedOllamaUrlIdx))) as model}
{#each $models.filter((m) => !(m?.preset ?? false) && m.owned_by === 'ollama' && (selectedOllamaUrlIdx === null ? true : (m?.ollama?.urls ?? []).includes(selectedOllamaUrlIdx))) as model}
<option value={model.name} class="bg-gray-
10
0 dark:bg-gray-700"
<option value={model.name} class="bg-gray-
5
0 dark:bg-gray-700"
>{model.name +
>{model.name +
' (' +
' (' +
(model.ollama.size / 1024 ** 3).toFixed(1) +
(model.ollama.size / 1024 ** 3).toFixed(1) +
...
@@ -771,7 +773,7 @@
...
@@ -771,7 +773,7 @@
</select>
</select>
</div>
</div>
<button
<button
class="px-2.5 bg-gray-
10
0 hover:bg-gray-200 text-gray-800 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-gray-100 rounded-lg transition"
class="px-2.5 bg-gray-
5
0 hover:bg-gray-200 text-gray-800 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-gray-100 rounded-lg transition"
on:click={() => {
on:click={() => {
showModelDeleteConfirm = true;
showModelDeleteConfirm = true;
}}
}}
...
@@ -797,7 +799,7 @@
...
@@ -797,7 +799,7 @@
<div class="flex w-full">
<div class="flex w-full">
<div class="flex-1 mr-2 flex flex-col gap-2">
<div class="flex-1 mr-2 flex flex-col gap-2">
<input
<input
class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
class="w-full rounded-lg py-2 px-4 text-sm
bg-gray-50
dark:text-gray-300 dark:bg-gray-850 outline-none"
placeholder={$i18n.t('Enter model tag (e.g. {{modelTag}})', {
placeholder={$i18n.t('Enter model tag (e.g. {{modelTag}})', {
modelTag: 'my-modelfile'
modelTag: 'my-modelfile'
})}
})}
...
@@ -807,7 +809,7 @@
...
@@ -807,7 +809,7 @@
<textarea
<textarea
bind:value={createModelContent}
bind:value={createModelContent}
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-
10
0 dark:text-gray-100 dark:bg-gray-850 outline-none resize-none scrollbar-hidden"
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-
5
0 dark:text-gray-100 dark:bg-gray-850 outline-none resize-none scrollbar-hidden"
rows="6"
rows="6"
placeholder={`TEMPLATE """{{ .System }}\nUSER: {{ .Prompt }}\nASSISTANT: """\nPARAMETER num_ctx 4096\nPARAMETER stop "</s>"\nPARAMETER stop "USER:"\nPARAMETER stop "ASSISTANT:"`}
placeholder={`TEMPLATE """{{ .System }}\nUSER: {{ .Prompt }}\nASSISTANT: """\nPARAMETER num_ctx 4096\nPARAMETER stop "</s>"\nPARAMETER stop "USER:"\nPARAMETER stop "ASSISTANT:"`}
disabled={createModelLoading}
disabled={createModelLoading}
...
@@ -816,7 +818,7 @@
...
@@ -816,7 +818,7 @@
<div class="flex self-start">
<div class="flex self-start">
<button
<button
class="px-2.5 py-2.5 bg-gray-
10
0 hover:bg-gray-200 text-gray-800 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-gray-100 rounded-lg transition disabled:cursor-not-allowed"
class="px-2.5 py-2.5 bg-gray-
5
0 hover:bg-gray-200 text-gray-800 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-gray-100 rounded-lg transition disabled:cursor-not-allowed"
on:click={() => {
on:click={() => {
createModelHandler();
createModelHandler();
}}
}}
...
@@ -925,7 +927,7 @@
...
@@ -925,7 +927,7 @@
<button
<button
type="button"
type="button"
class="w-full rounded-lg text-left py-2 px-4 bg-
white
dark:text-gray-300 dark:bg-gray-850"
class="w-full rounded-lg text-left py-2 px-4 bg-
gray-50
dark:text-gray-300 dark:bg-gray-850"
on:click={() => {
on:click={() => {
modelUploadInputElement.click();
modelUploadInputElement.click();
}}
}}
...
@@ -940,7 +942,7 @@
...
@@ -940,7 +942,7 @@
{:else}
{:else}
<div class="flex-1 {modelFileUrl !== '' ? 'mr-2' : ''}">
<div class="flex-1 {modelFileUrl !== '' ? 'mr-2' : ''}">
<input
<input
class="w-full rounded-lg text-left py-2 px-4 bg-
white
dark:text-gray-300 dark:bg-gray-850 outline-none {modelFileUrl !==
class="w-full rounded-lg text-left py-2 px-4 bg-
gray-50
dark:text-gray-300 dark:bg-gray-850 outline-none {modelFileUrl !==
''
''
? 'mr-2'
? 'mr-2'
: ''}"
: ''}"
...
@@ -955,7 +957,7 @@
...
@@ -955,7 +957,7 @@
{#if (modelUploadMode === 'file' && modelInputFile && modelInputFile.length > 0) || (modelUploadMode === 'url' && modelFileUrl !== '')}
{#if (modelUploadMode === 'file' && modelInputFile && modelInputFile.length > 0) || (modelUploadMode === 'url' && modelFileUrl !== '')}
<button
<button
class="px-2.5 bg-gray-
10
0 hover:bg-gray-200 text-gray-800 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-gray-100 rounded-lg disabled:cursor-not-allowed transition"
class="px-2.5 bg-gray-
5
0 hover:bg-gray-200 text-gray-800 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-gray-100 rounded-lg disabled:cursor-not-allowed transition"
type="submit"
type="submit"
disabled={modelTransferring}
disabled={modelTransferring}
>
>
...
@@ -1014,7 +1016,7 @@
...
@@ -1014,7 +1016,7 @@
<div class=" my-2.5 text-sm font-medium">{$i18n.t('Modelfile Content')}</div>
<div class=" my-2.5 text-sm font-medium">{$i18n.t('Modelfile Content')}</div>
<textarea
<textarea
bind:value={modelFileContent}
bind:value={modelFileContent}
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-
10
0 dark:text-gray-100 dark:bg-gray-850 outline-none resize-none"
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-
5
0 dark:text-gray-100 dark:bg-gray-850 outline-none resize-none"
rows="6"
rows="6"
/>
/>
</div>
</div>
...
...
Prev
1
2
3
4
5
6
7
8
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