mirror of
https://github.com/open-webui/open-webui
synced 2025-06-26 18:26:48 +00:00
commit
9204498420
11
CHANGELOG.md
11
CHANGELOG.md
@ -5,6 +5,17 @@ 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/),
|
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).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## [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
|
## [0.3.17] - 2024-09-04
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
@ -8,7 +8,7 @@ from open_webui.apps.webui.models.functions import (
|
|||||||
FunctionResponse,
|
FunctionResponse,
|
||||||
Functions,
|
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.config import CACHE_DIR, FUNCTIONS_DIR
|
||||||
from open_webui.constants import ERROR_MESSAGES
|
from open_webui.constants import ERROR_MESSAGES
|
||||||
from fastapi import APIRouter, Depends, HTTPException, Request, status
|
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)
|
function = Functions.get_function_by_id(form_data.id)
|
||||||
if function is None:
|
if function is None:
|
||||||
function_path = os.path.join(FUNCTIONS_DIR, f"{form_data.id}.py")
|
|
||||||
try:
|
try:
|
||||||
with open(function_path, "w") as function_file:
|
form_data.content = replace_imports(form_data.content)
|
||||||
function_file.write(form_data.content)
|
|
||||||
|
|
||||||
function_module, function_type, frontmatter = load_function_module_by_id(
|
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
|
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(
|
async def update_function_by_id(
|
||||||
request: Request, id: str, form_data: FunctionForm, user=Depends(get_admin_user)
|
request: Request, id: str, form_data: FunctionForm, user=Depends(get_admin_user)
|
||||||
):
|
):
|
||||||
function_path = os.path.join(FUNCTIONS_DIR, f"{id}.py")
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with open(function_path, "w") as function_file:
|
form_data.content = replace_imports(form_data.content)
|
||||||
function_file.write(form_data.content)
|
function_module, function_type, frontmatter = load_function_module_by_id(
|
||||||
|
id, content=form_data.content
|
||||||
function_module, function_type, frontmatter = load_function_module_by_id(id)
|
)
|
||||||
form_data.meta.manifest = frontmatter
|
form_data.meta.manifest = frontmatter
|
||||||
|
|
||||||
FUNCTIONS = request.app.state.FUNCTIONS
|
FUNCTIONS = request.app.state.FUNCTIONS
|
||||||
@ -222,13 +218,6 @@ async def delete_function_by_id(
|
|||||||
if id in FUNCTIONS:
|
if id in FUNCTIONS:
|
||||||
del FUNCTIONS[id]
|
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
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@ from pathlib import Path
|
|||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from open_webui.apps.webui.models.tools import ToolForm, ToolModel, ToolResponse, Tools
|
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.config import CACHE_DIR, DATA_DIR
|
||||||
from open_webui.constants import ERROR_MESSAGES
|
from open_webui.constants import ERROR_MESSAGES
|
||||||
from fastapi import APIRouter, Depends, HTTPException, Request, status
|
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)
|
toolkit = Tools.get_tool_by_id(form_data.id)
|
||||||
if toolkit is None:
|
if toolkit is None:
|
||||||
toolkit_path = os.path.join(TOOLS_DIR, f"{form_data.id}.py")
|
|
||||||
try:
|
try:
|
||||||
with open(toolkit_path, "w") as tool_file:
|
form_data.content = replace_imports(form_data.content)
|
||||||
tool_file.write(form_data.content)
|
toolkit_module, frontmatter = load_toolkit_module_by_id(
|
||||||
|
form_data.id, content=form_data.content
|
||||||
toolkit_module, frontmatter = load_toolkit_module_by_id(form_data.id)
|
)
|
||||||
form_data.meta.manifest = frontmatter
|
form_data.meta.manifest = frontmatter
|
||||||
|
|
||||||
TOOLS = request.app.state.TOOLS
|
TOOLS = request.app.state.TOOLS
|
||||||
@ -126,13 +125,11 @@ async def update_toolkit_by_id(
|
|||||||
form_data: ToolForm,
|
form_data: ToolForm,
|
||||||
user=Depends(get_admin_user),
|
user=Depends(get_admin_user),
|
||||||
):
|
):
|
||||||
toolkit_path = os.path.join(TOOLS_DIR, f"{id}.py")
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with open(toolkit_path, "w") as tool_file:
|
form_data.content = replace_imports(form_data.content)
|
||||||
tool_file.write(form_data.content)
|
toolkit_module, frontmatter = load_toolkit_module_by_id(
|
||||||
|
id, content=form_data.content
|
||||||
toolkit_module, frontmatter = load_toolkit_module_by_id(id)
|
)
|
||||||
form_data.meta.manifest = frontmatter
|
form_data.meta.manifest = frontmatter
|
||||||
|
|
||||||
TOOLS = request.app.state.TOOLS
|
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:
|
if id in TOOLS:
|
||||||
del TOOLS[id]
|
del TOOLS[id]
|
||||||
|
|
||||||
# delete the toolkit file
|
|
||||||
toolkit_path = os.path.join(TOOLS_DIR, f"{id}.py")
|
|
||||||
os.remove(toolkit_path)
|
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
@ -3,6 +3,8 @@ import re
|
|||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
from importlib import util
|
from importlib import util
|
||||||
|
import types
|
||||||
|
|
||||||
|
|
||||||
from open_webui.apps.webui.models.functions import Functions
|
from open_webui.apps.webui.models.functions import Functions
|
||||||
from open_webui.apps.webui.models.tools import Tools
|
from open_webui.apps.webui.models.tools import Tools
|
||||||
@ -49,75 +51,91 @@ def extract_frontmatter(file_path):
|
|||||||
return frontmatter
|
return frontmatter
|
||||||
|
|
||||||
|
|
||||||
def load_toolkit_module_by_id(toolkit_id):
|
def replace_imports(content):
|
||||||
toolkit_path = os.path.join(TOOLS_DIR, f"{toolkit_id}.py")
|
"""
|
||||||
|
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)
|
tool = Tools.get_tool_by_id(toolkit_id)
|
||||||
if tool:
|
if not tool:
|
||||||
with open(toolkit_path, "w") as file:
|
|
||||||
content = tool.content
|
|
||||||
content = content.replace("from utils", "from open_webui.utils")
|
|
||||||
content = content.replace("from apps", "from open_webui.apps")
|
|
||||||
content = content.replace("from main", "from open_webui.main")
|
|
||||||
content = content.replace("from config", "from open_webui.config")
|
|
||||||
|
|
||||||
if tool.content != content:
|
|
||||||
print(f"Replaced imports for: {toolkit_id}")
|
|
||||||
Tools.update_tool_by_id(toolkit_id, {"content": content})
|
|
||||||
|
|
||||||
file.write(content)
|
|
||||||
else:
|
|
||||||
raise Exception(f"Toolkit not found: {toolkit_id}")
|
raise Exception(f"Toolkit not found: {toolkit_id}")
|
||||||
|
|
||||||
spec = util.spec_from_file_location(toolkit_id, toolkit_path)
|
content = tool.content
|
||||||
module = util.module_from_spec(spec)
|
|
||||||
frontmatter = extract_frontmatter(toolkit_path)
|
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:
|
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", ""))
|
install_frontmatter_requirements(frontmatter.get("requirements", ""))
|
||||||
spec.loader.exec_module(module)
|
|
||||||
print(f"Loaded module: {module.__name__}")
|
print(f"Loaded module: {module.__name__}")
|
||||||
|
# Create and return the object if the class 'Tools' is found in the module
|
||||||
if hasattr(module, "Tools"):
|
if hasattr(module, "Tools"):
|
||||||
return module.Tools(), frontmatter
|
return module.Tools(), frontmatter
|
||||||
else:
|
else:
|
||||||
raise Exception("No Tools class found")
|
raise Exception("No Tools class found in the module")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error loading module: {toolkit_id}")
|
print(f"Error loading module: {toolkit_id}")
|
||||||
# Move the file to the error folder
|
del sys.modules[module_name] # Clean up
|
||||||
os.rename(toolkit_path, f"{toolkit_path}.error")
|
|
||||||
raise e
|
raise e
|
||||||
|
|
||||||
|
|
||||||
def load_function_module_by_id(function_id):
|
def load_function_module_by_id(function_id, content=None):
|
||||||
function_path = os.path.join(FUNCTIONS_DIR, f"{function_id}.py")
|
if content is None:
|
||||||
|
|
||||||
if not os.path.exists(function_path):
|
|
||||||
function = Functions.get_function_by_id(function_id)
|
function = Functions.get_function_by_id(function_id)
|
||||||
if function:
|
if not function:
|
||||||
with open(function_path, "w") as file:
|
raise Exception(f"Function not found: {function_id}")
|
||||||
content = function.content
|
content = function.content
|
||||||
content = content.replace("from utils", "from open_webui.utils")
|
|
||||||
content = content.replace("from apps", "from open_webui.apps")
|
|
||||||
content = content.replace("from main", "from open_webui.main")
|
|
||||||
content = content.replace("from config", "from open_webui.config")
|
|
||||||
|
|
||||||
if function.content != content:
|
content = replace_imports(content)
|
||||||
print(f"Replaced imports for: {function_id}")
|
|
||||||
Functions.update_function_by_id(function_id, {"content": content})
|
Functions.update_function_by_id(function_id, {"content": content})
|
||||||
|
|
||||||
file.write(content)
|
module_name = f"function_{function_id}"
|
||||||
else:
|
module = types.ModuleType(module_name)
|
||||||
raise Exception(f"Function not found: {function_id}")
|
sys.modules[module_name] = module
|
||||||
|
|
||||||
spec = util.spec_from_file_location(function_id, function_path)
|
|
||||||
module = util.module_from_spec(spec)
|
|
||||||
frontmatter = extract_frontmatter(function_path)
|
|
||||||
|
|
||||||
try:
|
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", ""))
|
install_frontmatter_requirements(frontmatter.get("requirements", ""))
|
||||||
spec.loader.exec_module(module)
|
|
||||||
print(f"Loaded module: {module.__name__}")
|
print(f"Loaded module: {module.__name__}")
|
||||||
|
|
||||||
|
# Create appropriate object based on available class type in the module
|
||||||
if hasattr(module, "Pipe"):
|
if hasattr(module, "Pipe"):
|
||||||
return module.Pipe(), "pipe", frontmatter
|
return module.Pipe(), "pipe", frontmatter
|
||||||
elif hasattr(module, "Filter"):
|
elif hasattr(module, "Filter"):
|
||||||
@ -125,11 +143,12 @@ def load_function_module_by_id(function_id):
|
|||||||
elif hasattr(module, "Action"):
|
elif hasattr(module, "Action"):
|
||||||
return module.Action(), "action", frontmatter
|
return module.Action(), "action", frontmatter
|
||||||
else:
|
else:
|
||||||
raise Exception("No Function class found")
|
raise Exception("No Function class found in the module")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error loading module: {function_id}")
|
print(f"Error loading module: {function_id}")
|
||||||
# Move the file to the error folder
|
del sys.modules[module_name] # Cleanup by removing the module in case of error
|
||||||
os.rename(function_path, f"{function_path}.error")
|
|
||||||
|
Functions.update_function_by_id(function_id, {"is_active": False})
|
||||||
raise e
|
raise e
|
||||||
|
|
||||||
|
|
||||||
|
4
package-lock.json
generated
4
package-lock.json
generated
@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "open-webui",
|
"name": "open-webui",
|
||||||
"version": "0.3.17",
|
"version": "0.3.18",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "open-webui",
|
"name": "open-webui",
|
||||||
"version": "0.3.17",
|
"version": "0.3.18",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@codemirror/lang-javascript": "^6.2.2",
|
"@codemirror/lang-javascript": "^6.2.2",
|
||||||
"@codemirror/lang-python": "^6.1.6",
|
"@codemirror/lang-python": "^6.1.6",
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "open-webui",
|
"name": "open-webui",
|
||||||
"version": "0.3.17",
|
"version": "0.3.18",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "npm run pyodide:fetch && vite dev --host",
|
"dev": "npm run pyodide:fetch && vite dev --host",
|
||||||
|
@ -356,7 +356,11 @@
|
|||||||
{#if status?.action === 'web_search' && status?.urls}
|
{#if status?.action === 'web_search' && status?.urls}
|
||||||
<WebSearchResults {status}>
|
<WebSearchResults {status}>
|
||||||
<div class="flex flex-col justify-center -space-y-0.5">
|
<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}
|
{status?.description}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -364,7 +368,9 @@
|
|||||||
{:else}
|
{:else}
|
||||||
<div class="flex flex-col justify-center -space-y-0.5">
|
<div class="flex flex-col justify-center -space-y-0.5">
|
||||||
<div
|
<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}
|
{status?.description}
|
||||||
</div>
|
</div>
|
||||||
|
Loading…
Reference in New Issue
Block a user