diff --git a/CHANGELOG.md b/CHANGELOG.md index 7e1122870..da4046e73 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,19 @@ 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.5.19] - 2024-03-04 +## [0.5.20] - 2025-03-05 + +### Added + +- **⚡ Toggle Code Execution On/Off**: You can now enable or disable code execution, providing more control over security, ensuring a safer and more customizable experience. + +### Fixed + +- **📜 Pinyin Keyboard Enter Key Now Works Properly**: Resolved an issue where the Enter key for Pinyin keyboards was not functioning as expected, ensuring seamless input for Chinese users. +- **🖼️ Web Manifest Loading Issue Fixed**: Addressed inconsistencies with 'site.webmanifest', guaranteeing proper loading and representation of the app across different browsers and devices. +- **📦 Non-Root Container Issue Resolved**: Fixed a critical issue where the UI failed to load correctly in non-root containers, ensuring reliable deployment in various environments. + +## [0.5.19] - 2025-03-04 ### Added diff --git a/backend/open_webui/config.py b/backend/open_webui/config.py index 349c35ce5..1e265f2ce 100644 --- a/backend/open_webui/config.py +++ b/backend/open_webui/config.py @@ -593,7 +593,10 @@ for file_path in (FRONTEND_BUILD_DIR / "static").glob("**/*"): (FRONTEND_BUILD_DIR / "static") ) target_path.parent.mkdir(parents=True, exist_ok=True) - shutil.copyfile(file_path, target_path) + try: + shutil.copyfile(file_path, target_path) + except Exception as e: + logging.error(f"An error occurred: {e}") frontend_favicon = FRONTEND_BUILD_DIR / "static" / "favicon.png" @@ -1377,6 +1380,11 @@ Responses from models: {{responses}}""" # Code Interpreter #################################### +ENABLE_CODE_EXECUTION = PersistentConfig( + "ENABLE_CODE_EXECUTION", + "code_execution.enable", + os.environ.get("ENABLE_CODE_EXECUTION", "True").lower() == "true", +) CODE_EXECUTION_ENGINE = PersistentConfig( "CODE_EXECUTION_ENGINE", @@ -1553,7 +1561,9 @@ ELASTICSEARCH_USERNAME = os.environ.get("ELASTICSEARCH_USERNAME", None) ELASTICSEARCH_PASSWORD = os.environ.get("ELASTICSEARCH_PASSWORD", None) ELASTICSEARCH_CLOUD_ID = os.environ.get("ELASTICSEARCH_CLOUD_ID", None) SSL_ASSERT_FINGERPRINT = os.environ.get("SSL_ASSERT_FINGERPRINT", None) - +ELASTICSEARCH_INDEX_PREFIX = os.environ.get( + "ELASTICSEARCH_INDEX_PREFIX", "open_webui_collections" +) # Pgvector PGVECTOR_DB_URL = os.environ.get("PGVECTOR_DB_URL", DATABASE_URL) if VECTOR_DB == "pgvector" and not PGVECTOR_DB_URL.startswith("postgres"): diff --git a/backend/open_webui/main.py b/backend/open_webui/main.py index 779fcec2b..416460837 100644 --- a/backend/open_webui/main.py +++ b/backend/open_webui/main.py @@ -105,6 +105,7 @@ from open_webui.config import ( # Direct Connections ENABLE_DIRECT_CONNECTIONS, # Code Execution + ENABLE_CODE_EXECUTION, CODE_EXECUTION_ENGINE, CODE_EXECUTION_JUPYTER_URL, CODE_EXECUTION_JUPYTER_AUTH, @@ -660,6 +661,7 @@ app.state.EMBEDDING_FUNCTION = get_embedding_function( # ######################################## +app.state.config.ENABLE_CODE_EXECUTION = ENABLE_CODE_EXECUTION app.state.config.CODE_EXECUTION_ENGINE = CODE_EXECUTION_ENGINE app.state.config.CODE_EXECUTION_JUPYTER_URL = CODE_EXECUTION_JUPYTER_URL app.state.config.CODE_EXECUTION_JUPYTER_AUTH = CODE_EXECUTION_JUPYTER_AUTH @@ -1173,6 +1175,7 @@ async def get_app_config(request: Request): "enable_direct_connections": app.state.config.ENABLE_DIRECT_CONNECTIONS, "enable_channels": app.state.config.ENABLE_CHANNELS, "enable_web_search": app.state.config.ENABLE_RAG_WEB_SEARCH, + "enable_code_execution": app.state.config.ENABLE_CODE_EXECUTION, "enable_code_interpreter": app.state.config.ENABLE_CODE_INTERPRETER, "enable_image_generation": app.state.config.ENABLE_IMAGE_GENERATION, "enable_autocomplete_generation": app.state.config.ENABLE_AUTOCOMPLETE_GENERATION, diff --git a/backend/open_webui/retrieval/vector/dbs/elasticsearch.py b/backend/open_webui/retrieval/vector/dbs/elasticsearch.py index 2dc79d2c2..c89628494 100644 --- a/backend/open_webui/retrieval/vector/dbs/elasticsearch.py +++ b/backend/open_webui/retrieval/vector/dbs/elasticsearch.py @@ -10,6 +10,7 @@ from open_webui.config import ( ELASTICSEARCH_USERNAME, ELASTICSEARCH_PASSWORD, ELASTICSEARCH_CLOUD_ID, + ELASTICSEARCH_INDEX_PREFIX, SSL_ASSERT_FINGERPRINT, ) @@ -23,7 +24,7 @@ class ElasticsearchClient: """ def __init__(self): - self.index_prefix = "open_webui_collections" + self.index_prefix = ELASTICSEARCH_INDEX_PREFIX self.client = Elasticsearch( hosts=[ELASTICSEARCH_URL], ca_certs=ELASTICSEARCH_CA_CERTS, @@ -95,6 +96,14 @@ class ElasticsearchClient: def _create_index(self, dimension: int): body = { "mappings": { + "dynamic_templates": [ + { + "strings": { + "match_mapping_type": "string", + "mapping": {"type": "keyword"}, + } + } + ], "properties": { "collection": {"type": "keyword"}, "id": {"type": "keyword"}, @@ -106,7 +115,7 @@ class ElasticsearchClient: }, "text": {"type": "text"}, "metadata": {"type": "object"}, - } + }, } } self.client.indices.create(index=self._get_index_name(dimension), body=body) @@ -131,12 +140,9 @@ class ElasticsearchClient: except Exception as e: return None - # @TODO: Make this delete a collection and not an index - def delete_colleciton(self, collection_name: str): - # TODO: fix this to include the dimension or a * prefix - # delete_collection here means delete a bunch of documents for an index. - # We are simply adapting to the norms of the other DBs. - self.client.indices.delete(index=self._get_collection_name(collection_name)) + def delete_collection(self, collection_name: str): + query = {"query": {"term": {"collection": collection_name}}} + self.client.delete_by_query(index=f"{self.index_prefix}*", body=query) # Status: works def search( @@ -239,34 +245,49 @@ class ElasticsearchClient: ] bulk(self.client, actions) - # Status: should work + # Upsert documents using the update API with doc_as_upsert=True. def upsert(self, collection_name: str, items: list[VectorItem]): if not self._has_index(dimension=len(items[0]["vector"])): - self._create_index(collection_name, dimension=len(items[0]["vector"])) - + self._create_index(dimension=len(items[0]["vector"])) for batch in self._create_batches(items): actions = [ { - "_index": self._get_index_name(dimension=len(items[0]["vector"])), + "_op_type": "update", + "_index": self._get_index_name(dimension=len(item["vector"])), "_id": item["id"], - "_source": { + "doc": { + "collection": collection_name, "vector": item["vector"], "text": item["text"], "metadata": item["metadata"], }, + "doc_as_upsert": True, } for item in batch ] - self.client.bulk(actions) + bulk(self.client, actions) - # TODO: This currently deletes by * which is not always supported in ElasticSearch. - # Need to read a bit before changing. Also, need to delete from a specific collection - def delete(self, collection_name: str, ids: list[str]): - # Assuming ID is unique across collections and indexes - actions = [ - {"delete": {"_index": f"{self.index_prefix}*", "_id": id}} for id in ids - ] - self.client.bulk(body=actions) + # Delete specific documents from a collection by filtering on both collection and document IDs. + def delete( + self, + collection_name: str, + ids: Optional[list[str]] = None, + filter: Optional[dict] = None, + ): + + query = { + "query": {"bool": {"filter": [{"term": {"collection": collection_name}}]}} + } + # logic based on chromaDB + if ids: + query["query"]["bool"]["filter"].append({"terms": {"_id": ids}}) + elif filter: + for field, value in filter.items(): + query["query"]["bool"]["filter"].append( + {"term": {f"metadata.{field}": value}} + ) + + self.client.delete_by_query(index=f"{self.index_prefix}*", body=query) def reset(self): indices = self.client.indices.get(index=f"{self.index_prefix}*") diff --git a/backend/open_webui/routers/configs.py b/backend/open_webui/routers/configs.py index 388c44f9c..2a4c651f2 100644 --- a/backend/open_webui/routers/configs.py +++ b/backend/open_webui/routers/configs.py @@ -70,6 +70,7 @@ async def set_direct_connections_config( # CodeInterpreterConfig ############################ class CodeInterpreterConfigForm(BaseModel): + ENABLE_CODE_EXECUTION: bool CODE_EXECUTION_ENGINE: str CODE_EXECUTION_JUPYTER_URL: Optional[str] CODE_EXECUTION_JUPYTER_AUTH: Optional[str] @@ -89,6 +90,7 @@ class CodeInterpreterConfigForm(BaseModel): @router.get("/code_execution", response_model=CodeInterpreterConfigForm) async def get_code_execution_config(request: Request, user=Depends(get_admin_user)): return { + "ENABLE_CODE_EXECUTION": request.app.state.config.ENABLE_CODE_EXECUTION, "CODE_EXECUTION_ENGINE": request.app.state.config.CODE_EXECUTION_ENGINE, "CODE_EXECUTION_JUPYTER_URL": request.app.state.config.CODE_EXECUTION_JUPYTER_URL, "CODE_EXECUTION_JUPYTER_AUTH": request.app.state.config.CODE_EXECUTION_JUPYTER_AUTH, @@ -111,6 +113,8 @@ async def set_code_execution_config( request: Request, form_data: CodeInterpreterConfigForm, user=Depends(get_admin_user) ): + request.app.state.config.ENABLE_CODE_EXECUTION = form_data.ENABLE_CODE_EXECUTION + request.app.state.config.CODE_EXECUTION_ENGINE = form_data.CODE_EXECUTION_ENGINE request.app.state.config.CODE_EXECUTION_JUPYTER_URL = ( form_data.CODE_EXECUTION_JUPYTER_URL @@ -153,6 +157,7 @@ async def set_code_execution_config( ) return { + "ENABLE_CODE_EXECUTION": request.app.state.config.ENABLE_CODE_EXECUTION, "CODE_EXECUTION_ENGINE": request.app.state.config.CODE_EXECUTION_ENGINE, "CODE_EXECUTION_JUPYTER_URL": request.app.state.config.CODE_EXECUTION_JUPYTER_URL, "CODE_EXECUTION_JUPYTER_AUTH": request.app.state.config.CODE_EXECUTION_JUPYTER_AUTH, diff --git a/package-lock.json b/package-lock.json index 719e8718d..e98b0968c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "open-webui", - "version": "0.5.19", + "version": "0.5.20", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "open-webui", - "version": "0.5.19", + "version": "0.5.20", "dependencies": { "@azure/msal-browser": "^4.5.0", "@codemirror/lang-javascript": "^6.2.2", diff --git a/package.json b/package.json index 63d7a49c9..2e4b905b1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "open-webui", - "version": "0.5.19", + "version": "0.5.20", "private": true, "scripts": { "dev": "npm run pyodide:fetch && vite dev --host", diff --git a/src/lib/components/admin/Settings/CodeExecution.svelte b/src/lib/components/admin/Settings/CodeExecution.svelte index c83537455..6050fb26b 100644 --- a/src/lib/components/admin/Settings/CodeExecution.svelte +++ b/src/lib/components/admin/Settings/CodeExecution.svelte @@ -45,6 +45,16 @@