Commit f9e3c47d authored by Michael Poluektov's avatar Michael Poluektov
Browse files

rebase

parents 49b4211c 24ef5af2
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
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
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
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
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")
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()
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 = {}
...@@ -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
......
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
......
@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;
}
...@@ -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: 41%; top: 44%;
left: 50%; left: 50%;
margin-left: -3rem; margin-left: -3rem;
" "
src="/logo.svg" src="/static/splash.png"
/> />
<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.svg" src="/static/splash.png"
class="animate-pulse-fast" class="animate-pulse-fast"
/> />
......
...@@ -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-semibold">
{$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-semibold 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-semibold 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'
......
<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-200 dark:bg-gray-800' ? 'bg-gray-100 dark:bg-gray-800'
: ' hover:bg-gray-300 dark:hover:bg-gray-850'}" : ' hover:bg-gray-50 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-200 dark:bg-gray-800' ? 'bg-gray-100 dark:bg-gray-800'
: ' hover:bg-gray-300 dark:hover:bg-gray-850'}" : ' hover:bg-gray-50 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-200 dark:bg-gray-800' ? 'bg-gray-100 dark:bg-gray-800'
: ' hover:bg-gray-300 dark:hover:bg-gray-850'}" : ' hover:bg-gray-50 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-200 dark:bg-gray-800' ? 'bg-gray-100 dark:bg-gray-800'
: ' hover:bg-gray-300 dark:hover:bg-gray-850'}" : ' hover:bg-gray-50 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-200 dark:bg-gray-800' ? 'bg-gray-100 dark:bg-gray-800'
: ' hover:bg-gray-300 dark:hover:bg-gray-850'}" : ' hover:bg-gray-50 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-200 dark:bg-gray-800' ? 'bg-gray-100 dark:bg-gray-800'
: ' hover:bg-gray-300 dark:hover:bg-gray-850'}" : ' hover:bg-gray-50 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-200 dark:bg-gray-800' ? 'bg-gray-100 dark:bg-gray-800'
: ' hover:bg-gray-300 dark:hover:bg-gray-850'}" : ' hover:bg-gray-50 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-200 dark:bg-gray-800' ? 'bg-gray-100 dark:bg-gray-800'
: ' hover:bg-gray-300 dark:hover:bg-gray-850'}" : ' hover:bg-gray-50 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-200 dark:bg-gray-800' ? 'bg-gray-100 dark:bg-gray-800'
: ' hover:bg-gray-300 dark:hover:bg-gray-850'}" : ' hover:bg-gray-50 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-200 dark:bg-gray-800' ? 'bg-gray-100 dark:bg-gray-800'
: ' hover:bg-gray-300 dark:hover:bg-gray-850'}" : ' hover:bg-gray-50 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-200 dark:bg-gray-800' ? 'bg-gray-100 dark:bg-gray-800'
: ' hover:bg-gray-300 dark:hover:bg-gray-850'}" : ' hover:bg-gray-50 dark:hover:bg-gray-850'}"
on:click={() => { on:click={() => {
selectedTab = 'db'; selectedTab = 'db';
}} }}
......
...@@ -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"
/> />
......
...@@ -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}
/> />
......
...@@ -279,7 +279,7 @@ ...@@ -279,7 +279,7 @@
</div> </div>
<button <button
class=" self-center text-xs p-1 px-3 bg-gray-100 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-50 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-100 dark:bg-gray-700">{model.name}</option> <option value={model.id} class="bg-gray-50 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-100 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-50 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-100 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-50 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}
......
...@@ -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}
......
...@@ -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-100 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-50 hover:bg-gray-100 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-100 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-50 hover:bg-gray-100 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}
/> />
......
...@@ -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}
/> />
......
...@@ -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-100 dark:bg-gray-700">{url}</option> <option value={idx} class="bg-gray-50 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-100 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-50 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-100 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-50 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-100 dark:bg-gray-700" <option value={model.name} class="bg-gray-50 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-100 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-50 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-100 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-50 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-100 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-50 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-100 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-50 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-100 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-50 dark:text-gray-100 dark:bg-gray-850 outline-none resize-none"
rows="6" rows="6"
/> />
</div> </div>
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment