Compare commits

..

36 Commits

Author SHA1 Message Date
Timothy Jaeryang Baek 05c0423d6e Merge pull request #5197 from open-webui/dev
Create and publish Docker images with specific build args / build-main-image (linux/amd64) (push) Waiting to run
Create and publish Docker images with specific build args / build-main-image (linux/arm64) (push) Waiting to run
Create and publish Docker images with specific build args / build-cuda-image (linux/amd64) (push) Waiting to run
Create and publish Docker images with specific build args / build-cuda-image (linux/arm64) (push) Waiting to run
Create and publish Docker images with specific build args / build-ollama-image (linux/amd64) (push) Waiting to run
Create and publish Docker images with specific build args / build-ollama-image (linux/arm64) (push) Waiting to run
Create and publish Docker images with specific build args / merge-main-images (push) Blocked by required conditions
Create and publish Docker images with specific build args / merge-cuda-images (push) Blocked by required conditions
Create and publish Docker images with specific build args / merge-ollama-images (push) Blocked by required conditions
0.3.19
2024-09-05 20:47:33 +02:00
Timothy J. Baek 377efc8a0c doc: changelog 2024-09-05 20:46:32 +02:00
Timothy J. Baek b35bbaade2 fix: extract_frontmatter issue 2024-09-05 20:35:58 +02:00
Timothy J. Baek c9107fa87f refac 2024-09-05 19:11:27 +02:00
Timothy J. Baek 0c1fab09ff refac 2024-09-05 18:55:31 +02:00
Timothy J. Baek 8b67959695 refac: styling 2024-09-05 18:44:52 +02:00
Timothy J. Baek 21e81d78d9 refac 2024-09-05 17:25:24 +02:00
Timothy J. Baek 31623ff330 refac: styling 2024-09-05 17:23:59 +02:00
Timothy J. Baek 7edfffdcc5 refac: styling 2024-09-05 17:11:55 +02:00
Timothy Jaeryang Baek 3b868be77b Merge pull request #5185 from open-webui/main
refac: styling
2024-09-05 17:04:10 +02:00
Timothy J. Baek c3271e84ef refac: styling 2024-09-05 17:03:09 +02:00
Timothy Jaeryang Baek 1cac9fce46 Merge pull request #5182 from open-webui/main
dev
2024-09-05 16:26:15 +02:00
Timothy J. Baek d0869bbfbc fix: migrations 2024-09-05 16:23:19 +02:00
Timothy J. Baek ca4beb413b refac: styling 2024-09-05 16:21:34 +02:00
Timothy Jaeryang Baek 7b31ef60fb Merge pull request #5161 from KarlLee830/translate
i18n: Update Chinese translation
2024-09-05 16:15:46 +02:00
Timothy Jaeryang Baek 73576dfcaf Merge pull request #5180 from open-webui/main
dev
2024-09-05 16:14:11 +02:00
Timothy J. Baek 8a411decac fix 2024-09-05 16:13:40 +02:00
Timothy Jaeryang Baek fa786c8e05 Merge pull request #5160 from vikrantrathore/main
fix: avoid overriding DATA_DIR and prevent errors when directories are the same
2024-09-05 16:11:42 +02:00
Timothy J. Baek 85fc35492d refac 2024-09-05 16:11:07 +02:00
Karl Lee 1c94f4dd71 i18n: Update Chinese translation 2024-09-05 15:28:13 +08:00
vikrantrathore 74169b0320 fix: avoid overriding DATA_DIR and prevent errors when directories are the same
Previously, the `DATA_DIR` environment variable was always overridden by defaulting to `OPEN_WEBUI_DIR / "data"`, which ignored user-defined `DATA_DIR` values. Additionally, when `DATA_DIR` and `NEW_DATA_DIR` were the same, the script attempted to copy files into themselves, leading to errors or redundant operations.

This commit ensures that:
1. The `DATA_DIR` environment variable is respected and not overridden.
2. Copy operations between `DATA_DIR` and `NEW_DATA_DIR` are only performed if the directories are different, preventing errors when they point to the same location.

These changes resolve potential file copy errors and preserve user configurations.
2024-09-05 14:17:58 +08:00
Timothy Jaeryang Baek 83574ccf6c Merge pull request #5159 from drupol/let-customize-pip-install
feat: add `OVERRIDE_PIP_INSTALL` env. variable
2024-09-04 23:01:01 +02:00
Timothy J. Baek a0be3822bf refac 2024-09-04 23:00:01 +02:00
Pol Dellaiera 004700c125 feat: add OVERRIDE_PIP_INSTALL environment variable 2024-09-04 22:44:59 +02:00
Timothy Jaeryang Baek 19ee110b7d Merge pull request #5158 from pascallim/fix/start_scripts
fix scripts to point to correct path
2024-09-04 20:29:58 +02:00
Pascal Lim 998616c0fd fix scripts to point to correct path 2024-09-04 20:27:30 +02:00
Timothy Jaeryang Baek 9204498420 Merge pull request #5157 from open-webui/dev
Create and publish Docker images with specific build args / build-main-image (linux/amd64) (push) Waiting to run
Create and publish Docker images with specific build args / build-main-image (linux/arm64) (push) Waiting to run
Create and publish Docker images with specific build args / build-cuda-image (linux/amd64) (push) Waiting to run
Create and publish Docker images with specific build args / build-cuda-image (linux/arm64) (push) Waiting to run
Create and publish Docker images with specific build args / build-ollama-image (linux/amd64) (push) Waiting to run
Create and publish Docker images with specific build args / build-ollama-image (linux/arm64) (push) Waiting to run
Create and publish Docker images with specific build args / merge-main-images (push) Blocked by required conditions
Create and publish Docker images with specific build args / merge-cuda-images (push) Blocked by required conditions
Create and publish Docker images with specific build args / merge-ollama-images (push) Blocked by required conditions
0.3.18
2024-09-04 20:09:58 +02:00
Timothy J. Baek b1957e5cfe doc: changelog 2024-09-04 20:08:14 +02:00
Timothy J. Baek 92a88df484 refac 2024-09-04 20:00:47 +02:00
Timothy J. Baek f2f713023d refac 2024-09-04 19:57:41 +02:00
Timothy J. Baek cf86ba7786 refac: tools & functions 2024-09-04 19:55:20 +02:00
Timothy J. Baek 94502d6494 fix: styling 2024-09-04 19:55:10 +02:00
Timothy Jaeryang Baek 5e3f9ec757 Merge pull request #5156 from open-webui/dev
fix
2024-09-04 19:04:03 +02:00
Timothy J. Baek d6fc0ccf65 fix 2024-09-04 19:03:51 +02:00
Timothy Jaeryang Baek a988c53949 Merge pull request #5155 from open-webui/dev
fix: tools & functions import
2024-09-04 19:00:52 +02:00
Timothy J. Baek 3afd66d50f fix: tools & functions import 2024-09-04 18:59:50 +02:00
19 changed files with 368 additions and 196 deletions
+3 -3
View File
@@ -157,7 +157,7 @@ jobs:
GLOBAL_LOG_LEVEL: debug
run: |
cd backend
uvicorn main:app --port "8080" --forwarded-allow-ips '*' &
uvicorn open_webui.main:app --port "8080" --forwarded-allow-ips '*' &
UVICORN_PID=$!
# Wait up to 40 seconds for the server to start
for i in {1..40}; do
@@ -184,7 +184,7 @@ jobs:
DATABASE_URL: postgresql://postgres:postgres@localhost:5432/postgres
run: |
cd backend
uvicorn main:app --port "8081" --forwarded-allow-ips '*' &
uvicorn open_webui.main:app --port "8081" --forwarded-allow-ips '*' &
UVICORN_PID=$!
# Wait up to 20 seconds for the server to start
for i in {1..20}; do
@@ -230,7 +230,7 @@ jobs:
# DATABASE_URL: mysql://root:mysql@localhost:3306/mysql
# run: |
# cd backend
# uvicorn main:app --port "8083" --forwarded-allow-ips '*' &
# uvicorn open_webui.main:app --port "8083" --forwarded-allow-ips '*' &
# UVICORN_PID=$!
# # Wait up to 20 seconds for the server to start
# for i in {1..20}; do
+26
View File
@@ -5,6 +5,32 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [0.3.19] - 2024-09-05
### Added
- **🌐 Translation Update**: Improved Chinese translations.
### Fixed
- **📂 DATA_DIR Overriding**: Fixed an issue to avoid overriding DATA_DIR, preventing errors when directories are set identically, ensuring smoother operation and data management.
- **🛠️ Frontmatter Extraction**: Fixed the extraction process for frontmatter in tools and functions.
### Changed
- **🎨 UI Styling**: Refined the user interface styling for enhanced visual coherence and user experience.
## [0.3.18] - 2024-09-04
### Added
- **🛠️ Direct Database Execution for Tools & Functions**: Enhanced the execution of Python files for tools and functions, now directly loading from the database for a more streamlined backend process.
### Fixed
- **🔄 Automatic Rewrite of Import Statements in Tools & Functions**: Tool and function scripts that import 'utils', 'apps', 'main', 'config' will now automatically rename these with 'open_webui.', ensuring compatibility and consistency across different modules.
- **🎨 Styling Adjustments**: Minor fixes in the visual styling to improve user experience and interface consistency.
## [0.3.17] - 2024-09-04
### Added
+18 -16
View File
@@ -152,29 +152,33 @@ async def get_pipe_models():
# Check if function is a manifold
if hasattr(function_module, "pipes"):
manifold_pipes = []
sub_pipes = []
# Check if pipes is a function or a list
if callable(function_module.pipes):
manifold_pipes = function_module.pipes()
else:
manifold_pipes = function_module.pipes
for p in manifold_pipes:
manifold_pipe_id = f'{pipe.id}.{p["id"]}'
manifold_pipe_name = p["name"]
try:
if callable(function_module.pipes):
sub_pipes = function_module.pipes()
else:
sub_pipes = function_module.pipes
except Exception as e:
log.exception(e)
sub_pipes = []
print(sub_pipes)
for p in sub_pipes:
sub_pipe_id = f'{pipe.id}.{p["id"]}'
sub_pipe_name = p["name"]
if hasattr(function_module, "name"):
manifold_pipe_name = f"{function_module.name}{manifold_pipe_name}"
sub_pipe_name = f"{function_module.name}{sub_pipe_name}"
pipe_flag = {"type": pipe.type}
if hasattr(function_module, "ChatValves"):
pipe_flag["valves_spec"] = function_module.ChatValves.schema()
pipe_models.append(
{
"id": manifold_pipe_id,
"name": manifold_pipe_name,
"id": sub_pipe_id,
"name": sub_pipe_name,
"object": "model",
"created": pipe.created_at,
"owned_by": "openai",
@@ -183,8 +187,6 @@ async def get_pipe_models():
)
else:
pipe_flag = {"type": "pipe"}
if hasattr(function_module, "ChatValves"):
pipe_flag["valves_spec"] = function_module.ChatValves.schema()
pipe_models.append(
{
@@ -8,7 +8,7 @@ from open_webui.apps.webui.models.functions import (
FunctionResponse,
Functions,
)
from open_webui.apps.webui.utils import load_function_module_by_id
from open_webui.apps.webui.utils import load_function_module_by_id, replace_imports
from open_webui.config import CACHE_DIR, FUNCTIONS_DIR
from open_webui.constants import ERROR_MESSAGES
from fastapi import APIRouter, Depends, HTTPException, Request, status
@@ -55,13 +55,11 @@ async def create_new_function(
function = Functions.get_function_by_id(form_data.id)
if function is None:
function_path = os.path.join(FUNCTIONS_DIR, f"{form_data.id}.py")
try:
with open(function_path, "w") as function_file:
function_file.write(form_data.content)
form_data.content = replace_imports(form_data.content)
function_module, function_type, frontmatter = load_function_module_by_id(
form_data.id
form_data.id,
content=form_data.content,
)
form_data.meta.manifest = frontmatter
@@ -174,13 +172,11 @@ async def toggle_global_by_id(id: str, user=Depends(get_admin_user)):
async def update_function_by_id(
request: Request, id: str, form_data: FunctionForm, user=Depends(get_admin_user)
):
function_path = os.path.join(FUNCTIONS_DIR, f"{id}.py")
try:
with open(function_path, "w") as function_file:
function_file.write(form_data.content)
function_module, function_type, frontmatter = load_function_module_by_id(id)
form_data.content = replace_imports(form_data.content)
function_module, function_type, frontmatter = load_function_module_by_id(
id, content=form_data.content
)
form_data.meta.manifest = frontmatter
FUNCTIONS = request.app.state.FUNCTIONS
@@ -222,13 +218,6 @@ async def delete_function_by_id(
if id in FUNCTIONS:
del FUNCTIONS[id]
# delete the function file
function_path = os.path.join(FUNCTIONS_DIR, f"{id}.py")
try:
os.remove(function_path)
except Exception:
pass
return result
+9 -16
View File
@@ -3,7 +3,7 @@ from pathlib import Path
from typing import Optional
from open_webui.apps.webui.models.tools import ToolForm, ToolModel, ToolResponse, Tools
from open_webui.apps.webui.utils import load_toolkit_module_by_id
from open_webui.apps.webui.utils import load_toolkit_module_by_id, replace_imports
from open_webui.config import CACHE_DIR, DATA_DIR
from open_webui.constants import ERROR_MESSAGES
from fastapi import APIRouter, Depends, HTTPException, Request, status
@@ -59,12 +59,11 @@ async def create_new_toolkit(
toolkit = Tools.get_tool_by_id(form_data.id)
if toolkit is None:
toolkit_path = os.path.join(TOOLS_DIR, f"{form_data.id}.py")
try:
with open(toolkit_path, "w") as tool_file:
tool_file.write(form_data.content)
toolkit_module, frontmatter = load_toolkit_module_by_id(form_data.id)
form_data.content = replace_imports(form_data.content)
toolkit_module, frontmatter = load_toolkit_module_by_id(
form_data.id, content=form_data.content
)
form_data.meta.manifest = frontmatter
TOOLS = request.app.state.TOOLS
@@ -126,13 +125,11 @@ async def update_toolkit_by_id(
form_data: ToolForm,
user=Depends(get_admin_user),
):
toolkit_path = os.path.join(TOOLS_DIR, f"{id}.py")
try:
with open(toolkit_path, "w") as tool_file:
tool_file.write(form_data.content)
toolkit_module, frontmatter = load_toolkit_module_by_id(id)
form_data.content = replace_imports(form_data.content)
toolkit_module, frontmatter = load_toolkit_module_by_id(
id, content=form_data.content
)
form_data.meta.manifest = frontmatter
TOOLS = request.app.state.TOOLS
@@ -177,10 +174,6 @@ async def delete_toolkit_by_id(request: Request, id: str, user=Depends(get_admin
if id in TOOLS:
del TOOLS[id]
# delete the toolkit file
toolkit_path = os.path.join(TOOLS_DIR, f"{id}.py")
os.remove(toolkit_path)
return result
+85 -50
View File
@@ -3,15 +3,17 @@ import re
import subprocess
import sys
from importlib import util
import types
from open_webui.apps.webui.models.functions import Functions
from open_webui.apps.webui.models.tools import Tools
from open_webui.config import FUNCTIONS_DIR, TOOLS_DIR
def extract_frontmatter(file_path):
def extract_frontmatter(content):
"""
Extract frontmatter as a dictionary from the specified file path.
Extract frontmatter as a dictionary from the provided content string.
"""
frontmatter = {}
frontmatter_started = False
@@ -19,29 +21,25 @@ def extract_frontmatter(file_path):
frontmatter_pattern = re.compile(r"^\s*([a-z_]+):\s*(.*)\s*$", re.IGNORECASE)
try:
with open(file_path, "r", encoding="utf-8") as file:
first_line = file.readline()
if first_line.strip() != '"""':
# The file doesn't start with triple quotes
return {}
lines = content.splitlines()
if len(lines) < 1 or lines[0].strip() != '"""':
# The content doesn't start with triple quotes
return {}
frontmatter_started = True
frontmatter_started = True
for line in file:
if '"""' in line:
if frontmatter_started:
frontmatter_ended = True
break
for line in lines[1:]:
if '"""' in line:
if frontmatter_started:
frontmatter_ended = True
break
if frontmatter_started and not frontmatter_ended:
match = frontmatter_pattern.match(line)
if match:
key, value = match.groups()
frontmatter[key.strip()] = value.strip()
if frontmatter_started and not frontmatter_ended:
match = frontmatter_pattern.match(line)
if match:
key, value = match.groups()
frontmatter[key.strip()] = value.strip()
except FileNotFoundError:
print(f"Error: The file {file_path} does not exist.")
return {}
except Exception as e:
print(f"An error occurred: {e}")
return {}
@@ -49,55 +47,91 @@ def extract_frontmatter(file_path):
return frontmatter
def load_toolkit_module_by_id(toolkit_id):
toolkit_path = os.path.join(TOOLS_DIR, f"{toolkit_id}.py")
def replace_imports(content):
"""
Replace the import paths in the content.
"""
replacements = {
"from utils": "from open_webui.utils",
"from apps": "from open_webui.apps",
"from main": "from open_webui.main",
"from config": "from open_webui.config",
}
if not os.path.exists(toolkit_path):
for old, new in replacements.items():
content = content.replace(old, new)
return content
def load_toolkit_module_by_id(toolkit_id, content=None):
if content is None:
tool = Tools.get_tool_by_id(toolkit_id)
if tool:
with open(toolkit_path, "w") as file:
file.write(tool.content)
else:
if not tool:
raise Exception(f"Toolkit not found: {toolkit_id}")
spec = util.spec_from_file_location(toolkit_id, toolkit_path)
module = util.module_from_spec(spec)
frontmatter = extract_frontmatter(toolkit_path)
content = tool.content
content = replace_imports(content)
Tools.update_tool_by_id(toolkit_id, {"content": content})
module_name = f"tool_{toolkit_id}"
module = types.ModuleType(module_name)
sys.modules[module_name] = module
try:
# Executing the modified content in the created module's namespace
exec(content, module.__dict__)
# Extract frontmatter, assuming content can be treated directly as a string
frontmatter = extract_frontmatter(
content
) # Ensure this method is adaptable to handle content strings
# Install required packages found within the frontmatter
install_frontmatter_requirements(frontmatter.get("requirements", ""))
spec.loader.exec_module(module)
print(f"Loaded module: {module.__name__}")
# Create and return the object if the class 'Tools' is found in the module
if hasattr(module, "Tools"):
return module.Tools(), frontmatter
else:
raise Exception("No Tools class found")
raise Exception("No Tools class found in the module")
except Exception as e:
print(f"Error loading module: {toolkit_id}")
# Move the file to the error folder
os.rename(toolkit_path, f"{toolkit_path}.error")
del sys.modules[module_name] # Clean up
raise e
def load_function_module_by_id(function_id):
function_path = os.path.join(FUNCTIONS_DIR, f"{function_id}.py")
if not os.path.exists(function_path):
def load_function_module_by_id(function_id, content=None):
if content is None:
function = Functions.get_function_by_id(function_id)
if function:
with open(function_path, "w") as file:
file.write(function.content)
else:
if not function:
raise Exception(f"Function not found: {function_id}")
content = function.content
spec = util.spec_from_file_location(function_id, function_path)
module = util.module_from_spec(spec)
frontmatter = extract_frontmatter(function_path)
content = replace_imports(content)
Functions.update_function_by_id(function_id, {"content": content})
module_name = f"function_{function_id}"
module = types.ModuleType(module_name)
sys.modules[module_name] = module
try:
# Execute the modified content in the created module's namespace
exec(content, module.__dict__)
# Extract the frontmatter from the content, simulate file-like behaviour
frontmatter = extract_frontmatter(
content
) # This function needs to handle string inputs
# Install necessary requirements specified in frontmatter
install_frontmatter_requirements(frontmatter.get("requirements", ""))
spec.loader.exec_module(module)
print(f"Loaded module: {module.__name__}")
# Create appropriate object based on available class type in the module
if hasattr(module, "Pipe"):
return module.Pipe(), "pipe", frontmatter
elif hasattr(module, "Filter"):
@@ -105,11 +139,12 @@ def load_function_module_by_id(function_id):
elif hasattr(module, "Action"):
return module.Action(), "action", frontmatter
else:
raise Exception("No Function class found")
raise Exception("No Function class found in the module")
except Exception as e:
print(f"Error loading module: {function_id}")
# Move the file to the error folder
os.rename(function_path, f"{function_path}.error")
del sys.modules[module_name] # Cleanup by removing the module in case of error
Functions.update_function_by_id(function_id, {"is_active": False})
raise e
+9 -2
View File
@@ -96,6 +96,12 @@ except importlib.metadata.PackageNotFoundError:
pass
PIP_INSTALL = (
os.environ.get("PIP_INSTALL", "False").lower() == "true"
if os.environ.get("PIP_INSTALL")
else PIP_INSTALL
)
if PIP_INSTALL:
PACKAGE_DATA = {"version": importlib.metadata.version("open-webui")}
else:
@@ -192,7 +198,7 @@ if PIP_INSTALL:
NEW_DATA_DIR.mkdir(parents=True, exist_ok=True)
# Check if the data directory exists in the package directory
if DATA_DIR.exists():
if DATA_DIR.exists() and DATA_DIR != NEW_DATA_DIR:
log.info(f"Moving {DATA_DIR} to {NEW_DATA_DIR}")
for item in DATA_DIR.iterdir():
dest = NEW_DATA_DIR / item.name
@@ -201,10 +207,11 @@ if PIP_INSTALL:
else:
shutil.copy2(item, dest)
DATA_DIR = OPEN_WEBUI_DIR / "data"
DATA_DIR = Path(os.getenv("DATA_DIR", OPEN_WEBUI_DIR / "data"))
FRONTEND_BUILD_DIR = Path(os.getenv("FRONTEND_BUILD_DIR", BASE_DIR / "build")).resolve()
if PIP_INSTALL:
FRONTEND_BUILD_DIR = Path(
os.getenv("FRONTEND_BUILD_DIR", OPEN_WEBUI_DIR / "frontend")
@@ -82,7 +82,9 @@ def upgrade() -> None:
sa.Column("id", sa.String(), nullable=False),
sa.Column("user_id", sa.String(), nullable=True),
sa.Column("filename", sa.Text(), nullable=True),
sa.Column("meta", apps.webui.internal.db.JSONField(), nullable=True),
sa.Column(
"meta", open_webui.apps.webui.internal.db.JSONField(), nullable=True
),
sa.Column("created_at", sa.BigInteger(), nullable=True),
sa.PrimaryKeyConstraint("id"),
)
@@ -95,8 +97,12 @@ def upgrade() -> None:
sa.Column("name", sa.Text(), nullable=True),
sa.Column("type", sa.Text(), nullable=True),
sa.Column("content", sa.Text(), nullable=True),
sa.Column("meta", apps.webui.internal.db.JSONField(), nullable=True),
sa.Column("valves", apps.webui.internal.db.JSONField(), nullable=True),
sa.Column(
"meta", open_webui.apps.webui.internal.db.JSONField(), nullable=True
),
sa.Column(
"valves", open_webui.apps.webui.internal.db.JSONField(), nullable=True
),
sa.Column("is_active", sa.Boolean(), nullable=True),
sa.Column("is_global", sa.Boolean(), nullable=True),
sa.Column("updated_at", sa.BigInteger(), nullable=True),
@@ -122,8 +128,12 @@ def upgrade() -> None:
sa.Column("user_id", sa.Text(), nullable=True),
sa.Column("base_model_id", sa.Text(), nullable=True),
sa.Column("name", sa.Text(), nullable=True),
sa.Column("params", apps.webui.internal.db.JSONField(), nullable=True),
sa.Column("meta", apps.webui.internal.db.JSONField(), nullable=True),
sa.Column(
"params", open_webui.apps.webui.internal.db.JSONField(), nullable=True
),
sa.Column(
"meta", open_webui.apps.webui.internal.db.JSONField(), nullable=True
),
sa.Column("updated_at", sa.BigInteger(), nullable=True),
sa.Column("created_at", sa.BigInteger(), nullable=True),
sa.PrimaryKeyConstraint("id"),
@@ -157,9 +167,15 @@ def upgrade() -> None:
sa.Column("user_id", sa.String(), nullable=True),
sa.Column("name", sa.Text(), nullable=True),
sa.Column("content", sa.Text(), nullable=True),
sa.Column("specs", apps.webui.internal.db.JSONField(), nullable=True),
sa.Column("meta", apps.webui.internal.db.JSONField(), nullable=True),
sa.Column("valves", apps.webui.internal.db.JSONField(), nullable=True),
sa.Column(
"specs", open_webui.apps.webui.internal.db.JSONField(), nullable=True
),
sa.Column(
"meta", open_webui.apps.webui.internal.db.JSONField(), nullable=True
),
sa.Column(
"valves", open_webui.apps.webui.internal.db.JSONField(), nullable=True
),
sa.Column("updated_at", sa.BigInteger(), nullable=True),
sa.Column("created_at", sa.BigInteger(), nullable=True),
sa.PrimaryKeyConstraint("id"),
@@ -177,8 +193,12 @@ def upgrade() -> None:
sa.Column("updated_at", sa.BigInteger(), nullable=True),
sa.Column("created_at", sa.BigInteger(), nullable=True),
sa.Column("api_key", sa.String(), nullable=True),
sa.Column("settings", apps.webui.internal.db.JSONField(), nullable=True),
sa.Column("info", apps.webui.internal.db.JSONField(), nullable=True),
sa.Column(
"settings", open_webui.apps.webui.internal.db.JSONField(), nullable=True
),
sa.Column(
"info", open_webui.apps.webui.internal.db.JSONField(), nullable=True
),
sa.Column("oauth_sub", sa.Text(), nullable=True),
sa.PrimaryKeyConstraint("id"),
sa.UniqueConstraint("api_key"),
+1 -1
View File
@@ -30,4 +30,4 @@ IF "%WEBUI_SECRET_KEY%%WEBUI_JWT_SECRET_KEY%" == " " (
:: Execute uvicorn
SET "WEBUI_SECRET_KEY=%WEBUI_SECRET_KEY%"
uvicorn main:app --host "%HOST%" --port "%PORT%" --forwarded-allow-ips '*'
uvicorn open_webui.main:app --host "%HOST%" --port "%PORT%" --forwarded-allow-ips '*'
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "open-webui",
"version": "0.3.17",
"version": "0.3.19",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "open-webui",
"version": "0.3.17",
"version": "0.3.19",
"dependencies": {
"@codemirror/lang-javascript": "^6.2.2",
"@codemirror/lang-python": "^6.1.6",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "open-webui",
"version": "0.3.17",
"version": "0.3.19",
"private": true,
"scripts": {
"dev": "npm run pyodide:fetch && vite dev --host",
+51 -55
View File
@@ -5,6 +5,7 @@
import { onMount } from 'svelte';
import { mobile, showCallOverlay } from '$lib/stores';
import CallOverlay from './MessageInput/CallOverlay.svelte';
import Drawer from '../common/Drawer.svelte';
export let show = false;
@@ -43,65 +44,60 @@
});
</script>
{#if largeScreen}
{#if !largeScreen}
{#if show}
<div class=" absolute bottom-0 right-0 z-20 h-full pointer-events-none">
<div class="pr-4 pt-14 pb-8 w-[24rem] h-full" in:slide={{ duration: 200, axis: 'x' }}>
<div
class="w-full h-full px-5 py-4 bg-white dark:shadow-lg dark:bg-gray-850 border border-gray-50 dark:border-gray-800 rounded-xl z-50 pointer-events-auto overflow-y-auto scrollbar-hidden"
>
{#if $showCallOverlay}
<CallOverlay
bind:files
{submitPrompt}
{stopResponse}
{modelId}
{chatId}
{eventTarget}
/>
{:else}
<Controls
on:close={() => {
show = false;
}}
{models}
bind:chatFiles
bind:params
/>
{/if}
</div>
<Drawer bind:show>
<div class=" px-6 py-4 h-full">
<Controls
on:close={() => {
show = false;
}}
{models}
bind:chatFiles
bind:params
/>
</div>
</Drawer>
{/if}
{#if $showCallOverlay}
<div class=" absolute w-full h-screen max-h-[100dvh] flex z-[999] overflow-hidden">
<div
class="absolute w-full h-screen max-h-[100dvh] bg-white text-gray-700 dark:bg-black dark:text-gray-300 flex justify-center"
>
<CallOverlay
bind:files
{submitPrompt}
{stopResponse}
{modelId}
{chatId}
{eventTarget}
on:close={() => {
show = false;
}}
/>
</div>
</div>
{/if}
{:else if $showCallOverlay}
<div class=" absolute w-full h-screen max-h-[100dvh] flex z-[999] overflow-hidden">
<div
class="absolute w-full h-screen max-h-[100dvh] bg-white text-gray-700 dark:bg-black dark:text-gray-300 flex justify-center"
>
<CallOverlay
bind:files
{submitPrompt}
{stopResponse}
{modelId}
{chatId}
{eventTarget}
on:close={() => {
show = false;
}}
/>
{:else if show}
<div class=" absolute bottom-0 right-0 z-20 h-full pointer-events-none">
<div class="pr-4 pt-14 pb-8 w-[24rem] h-full" in:slide={{ duration: 200, axis: 'x' }}>
<div
class="w-full h-full px-5 py-4 bg-white dark:shadow-lg dark:bg-gray-850 border border-gray-50 dark:border-gray-800 rounded-xl z-50 pointer-events-auto overflow-y-auto scrollbar-hidden"
>
{#if $showCallOverlay}
<CallOverlay bind:files {submitPrompt} {stopResponse} {modelId} {chatId} {eventTarget} />
{:else}
<Controls
on:close={() => {
show = false;
}}
{models}
bind:chatFiles
bind:params
/>
{/if}
</div>
</div>
</div>
{:else}
<Modal bind:show>
<div class=" px-6 py-4 h-full">
<Controls
on:close={() => {
show = false;
}}
{models}
bind:chatFiles
bind:params
/>
</div>
</Modal>
{/if}
@@ -34,9 +34,9 @@
</script>
{#key mounted}
<div class="m-auto w-full max-w-6xl px-8 lg:px-20 pb-10">
<div class="m-auto w-full max-w-6xl px-8 lg:px-20">
<div class="flex justify-start">
<div class="flex -space-x-4 mb-1" in:fade={{ duration: 200 }}>
<div class="flex -space-x-4 mb-0.5" in:fade={{ duration: 200 }}>
{#each models as model, modelIdx}
<button
on:click={() => {
@@ -78,7 +78,7 @@
{/if}
<div
class=" mt-2 mb-4 text-3xl text-gray-800 dark:text-gray-100 font-semibold text-left flex items-center gap-4 font-primary"
class=" mt-2 mb-4 text-3xl text-gray-800 dark:text-gray-100 font-medium text-left flex items-center gap-4 font-primary"
>
<div>
<div class=" capitalize line-clamp-1" in:fade={{ duration: 200 }}>
@@ -356,7 +356,11 @@
{#if status?.action === 'web_search' && status?.urls}
<WebSearchResults {status}>
<div class="flex flex-col justify-center -space-y-0.5">
<div class="shimmer text-base line-clamp-1 text-wrap">
<div
class="{status?.done === false
? 'shimmer'
: ''} text-base line-clamp-1 text-wrap"
>
{status?.description}
</div>
</div>
@@ -364,7 +368,9 @@
{:else}
<div class="flex flex-col justify-center -space-y-0.5">
<div
class="shimmer text-gray-500 dark:text-gray-500 text-base line-clamp-1 text-wrap"
class="{status?.done === false
? 'shimmer'
: ''} text-gray-500 dark:text-gray-500 text-base line-clamp-1 text-wrap"
>
{status?.description}
</div>
+9 -5
View File
@@ -55,7 +55,9 @@
</div>
{#if selectedModelIdx === 0}
<div class=" self-center mr-2 disabled:text-gray-600 disabled:hover:text-gray-600">
<div
class=" self-center mx-1 disabled:text-gray-600 disabled:hover:text-gray-600 -translate-y-[0.5px]"
>
<Tooltip content={$i18n.t('Add Model')}>
<button
class=" "
@@ -79,7 +81,9 @@
</Tooltip>
</div>
{:else}
<div class=" self-center disabled:text-gray-600 disabled:hover:text-gray-600 mr-2">
<div
class=" self-center mx-1 disabled:text-gray-600 disabled:hover:text-gray-600 -translate-y-[0.5px]"
>
<Tooltip content={$i18n.t('Remove Model')}>
<button
{disabled}
@@ -95,7 +99,7 @@
viewBox="0 0 24 24"
stroke-width="2"
stroke="currentColor"
class="size-3.5"
class="size-3"
>
<path stroke-linecap="round" stroke-linejoin="round" d="M19.5 12h-15" />
</svg>
@@ -107,8 +111,8 @@
{/each}
</div>
{#if showSetDefault && !$mobile}
<div class="text-left mt-0.5 ml-1 text-[0.7rem] text-gray-500 font-primary">
{#if showSetDefault}
<div class=" absolute text-left mt-[1px] ml-1 text-[0.7rem] text-gray-500 font-primary">
<button on:click={saveDefaultModel}> {$i18n.t('Set as default')}</button>
</div>
{/if}
@@ -231,7 +231,7 @@
>
<DropdownMenu.Trigger class="relative w-full font-primary" aria-label={placeholder}>
<div
class="flex w-full text-left px-0.5 outline-none bg-transparent truncate text-lg font-semibold placeholder-gray-400 focus:outline-none"
class="flex w-full text-left px-0.5 outline-none bg-transparent truncate text-lg font-medium placeholder-gray-400 focus:outline-none"
>
{#if selectedModel}
{selectedModel.label}
@@ -302,7 +302,7 @@
>
<div class="flex flex-col">
{#if $mobile && (item?.model?.info?.meta?.tags ?? []).length > 0}
<div class="flex gap-0.5 self-start h-full mb-0.5 -translate-x-1">
<div class="flex gap-0.5 self-start h-full mb-1.5 -translate-x-1">
{#each item.model?.info?.meta.tags as tag}
<div
class=" text-xs font-bold px-1 rounded uppercase line-clamp-1 bg-gray-500/20 text-gray-700 dark:text-gray-200"
@@ -418,7 +418,7 @@
</div>
{#if value === item.value}
<div class="ml-auto pl-2">
<div class="ml-auto pl-2 pr-2 md:pr-0">
<Check />
</div>
{/if}
+94
View File
@@ -0,0 +1,94 @@
<script lang="ts">
import { onDestroy, onMount, createEventDispatcher } from 'svelte';
import { flyAndScale } from '$lib/utils/transitions';
import { fade, fly, slide } from 'svelte/transition';
export let show = false;
export let size = 'md';
let modalElement = null;
let mounted = false;
const sizeToWidth = (size) => {
if (size === 'xs') {
return 'w-[16rem]';
} else if (size === 'sm') {
return 'w-[30rem]';
} else if (size === 'md') {
return 'w-[48rem]';
} else {
return 'w-[56rem]';
}
};
const handleKeyDown = (event: KeyboardEvent) => {
if (event.key === 'Escape' && isTopModal()) {
console.log('Escape');
show = false;
}
};
const isTopModal = () => {
const modals = document.getElementsByClassName('modal');
return modals.length && modals[modals.length - 1] === modalElement;
};
onMount(() => {
mounted = true;
});
$: if (show && modalElement) {
document.body.appendChild(modalElement);
window.addEventListener('keydown', handleKeyDown);
document.body.style.overflow = 'hidden';
} else if (modalElement) {
window.removeEventListener('keydown', handleKeyDown);
document.body.removeChild(modalElement);
document.body.style.overflow = 'unset';
}
onDestroy(() => {
show = false;
if (modalElement) {
document.body.removeChild(modalElement);
}
});
</script>
<!-- svelte-ignore a11y-click-events-have-key-events -->
<!-- svelte-ignore a11y-no-static-element-interactions -->
<div
bind:this={modalElement}
class="modal fixed right-0 left-0 bottom-0 bg-black/60 w-full min-h-screen h-screen flex justify-center z-[9999] overflow-hidden overscroll-contain"
in:fly={{ y: 100, duration: 100 }}
on:mousedown={() => {
show = false;
}}
>
<div
class=" mt-auto max-w-full w-full bg-gray-50 dark:bg-gray-900 max-h-[100dvh] overflow-y-auto scrollbar-hidden"
on:mousedown={(e) => {
e.stopPropagation();
}}
>
<slot />
</div>
</div>
<style>
.modal-content {
animation: scaleUp 0.1s ease-out forwards;
}
@keyframes scaleUp {
from {
transform: scale(0.985);
opacity: 0;
}
to {
transform: scale(1);
opacity: 1;
}
}
</style>
+2 -2
View File
@@ -261,7 +261,7 @@
id="sidebar"
class="h-screen max-h-[100dvh] min-h-screen select-none {$showSidebar
? 'md:relative w-[260px]'
: '-translate-x-[260px] w-[0px]'} bg-gray-50 text-gray-900 dark:bg-gray-950 dark:text-gray-200 text-sm transition fixed z-50 top-0 left-0 rounded-r-2xl
: '-translate-x-[260px] w-[0px]'} bg-gray-50 text-gray-900 dark:bg-gray-950 dark:text-gray-200 text-sm transition fixed z-50 top-0 left-0
"
data-state={$showSidebar}
>
@@ -273,7 +273,7 @@
<div class="px-2.5 flex justify-between space-x-1 text-gray-600 dark:text-gray-400">
<a
id="sidebar-new-chat-button"
class="flex flex-1 justify-between rounded-xl px-2 py-2 hover:bg-gray-100 dark:hover:bg-gray-900 transition"
class="flex flex-1 justify-between rounded-xl px-2 h-full hover:bg-gray-100 dark:hover:bg-gray-900 transition"
href="/"
draggable="false"
on:click={async () => {
+6 -6
View File
@@ -248,8 +248,8 @@
"Enter model tag (e.g. {{modelTag}})": "输入模型标签 (例如:{{modelTag}})",
"Enter Number of Steps (e.g. 50)": "输入步骤数 (Steps) (例如:50)",
"Enter Score": "输入评分",
"Enter SearchApi API Key": "",
"Enter SearchApi Engine": "",
"Enter SearchApi API Key": "输入 SearchApi API 密钥",
"Enter SearchApi Engine": "输入 SearchApi 引擎",
"Enter Searxng Query URL": "输入 Searxng 查询地址",
"Enter Serper API Key": "输入 Serper API 密钥",
"Enter Serply API Key": "输入 Serply API 密钥",
@@ -272,7 +272,7 @@
"Export All Chats (All Users)": "导出所有用户对话",
"Export chat (.json)": "JSON 文件 (.json)",
"Export Chats": "导出对话",
"Export Config to JSON File": "",
"Export Config to JSON File": "导出配置信息至 JSON 文件中",
"Export Documents Mapping": "导出文档映射",
"Export Functions": "导出函数",
"Export LiteLLM config.yaml": "导出 LteLLM config.yaml 文件",
@@ -337,7 +337,7 @@
"Image Settings": "图像设置",
"Images": "图像",
"Import Chats": "导入对话记录",
"Import Config from JSON File": "",
"Import Config from JSON File": "导入 JSON 文件中的配置信息",
"Import Documents Mapping": "导入文档映射",
"Import Functions": "导入函数",
"Import Models": "导入模型",
@@ -541,8 +541,8 @@
"Search Query Generation Prompt Length Threshold": "搜索查询生成提示长度阈值",
"Search Result Count": "搜索结果数量",
"Search Tools": "搜索工具",
"SearchApi API Key": "",
"SearchApi Engine": "",
"SearchApi API Key": "SearchApi API 密钥",
"SearchApi Engine": "SearchApi 引擎",
"Searched {{count}} sites_other": "搜索到 {{count}} 个结果",
"Searching \"{{searchQuery}}\"": "搜索 \"{{searchQuery}}\" 中",
"Searching Knowledge for \"{{searchQuery}}\"": "检索有关 \"{{searchQuery}}\" 的知识中",