diff --git a/CHANGELOG.md b/CHANGELOG.md index 772753def0..3c5e3b35a3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,22 @@ 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.6.22] - 2025-08-11 + +### Added + +- 🔗 **OpenAI API '/v1' Endpoint Compatibility**: Enhanced API compatibility by supporting requests to paths like '/v1/models', '/v1/embeddings', and '/v1/chat/completions'. This allows Open WebUI to integrate more seamlessly with tools that expect OpenAI's '/v1' API structure. +- 🪄 **Toggle for Guided Response Regeneration Menu**: Introduced a new setting in 'Interface' settings, providing the ability to enable or disable the expanded guided response regeneration menu. This offers users more control over their chat workflow and interface preferences. +- ✨ **General UI/UX Enhancements**: Implemented various user interface and experience improvements, including more rounded corners for cards in the Knowledge, Prompts, and Tools sections, and minor layout adjustments within the chat Navbar for improved visual consistency. +- 🌐 **Localization & Internationalization Improvements**: Introduced support for the Kabyle (Taqbaylit) language, refined and expanded translations for Chinese, expanding the platform's linguistic coverage. + +### Fixed + +- 🐞 **OpenAI Error Message Propagation**: Resolved an issue where specific OpenAI API errors (e.g., 'Organization Not Verified') were obscured by generic 'JSONResponse' iterable errors. The system now correctly propagates detailed and actionable error messages from OpenAI to the user. +- 🌲 **Pinecone Insert Issue**: Fixed a bug that prevented proper insertion of items into Pinecone vector databases. +- 📦 **S3 Vector Issue**: Resolved a bug where s3vector functionality failed due to incorrect import paths. +- 🏠 **Landing Page Option Setting Not Working**: Fixed an issue where the landing page option in settings was not functioning as intended. + ## [0.6.21] - 2025-08-10 ### Added diff --git a/backend/open_webui/config.py b/backend/open_webui/config.py index b10dee5b63..43c864ef22 100644 --- a/backend/open_webui/config.py +++ b/backend/open_webui/config.py @@ -871,7 +871,7 @@ CACHE_DIR.mkdir(parents=True, exist_ok=True) ENABLE_DIRECT_CONNECTIONS = PersistentConfig( "ENABLE_DIRECT_CONNECTIONS", "direct.enable", - os.environ.get("ENABLE_DIRECT_CONNECTIONS", "True").lower() == "true", + os.environ.get("ENABLE_DIRECT_CONNECTIONS", "False").lower() == "true", ) #################################### diff --git a/backend/open_webui/main.py b/backend/open_webui/main.py index 618640486d..076d4c486d 100644 --- a/backend/open_webui/main.py +++ b/backend/open_webui/main.py @@ -1261,6 +1261,7 @@ if audit_level != AuditLevel.NONE: @app.get("/api/models") +@app.get("/api/v1/models") # Experimental: Compatibility with OpenAI API async def get_models( request: Request, refresh: bool = False, user=Depends(get_verified_user) ): @@ -1341,6 +1342,7 @@ async def get_base_models(request: Request, user=Depends(get_admin_user)): @app.post("/api/embeddings") +@app.post("/api/v1/embeddings") # Experimental: Compatibility with OpenAI API async def embeddings( request: Request, form_data: dict, user=Depends(get_verified_user) ): @@ -1367,6 +1369,7 @@ async def embeddings( @app.post("/api/chat/completions") +@app.post("/api/v1/chat/completions") # Experimental: Compatibility with OpenAI API async def chat_completion( request: Request, form_data: dict, diff --git a/backend/open_webui/retrieval/vector/dbs/pinecone.py b/backend/open_webui/retrieval/vector/dbs/pinecone.py index 8291332c0f..466b5a6e24 100644 --- a/backend/open_webui/retrieval/vector/dbs/pinecone.py +++ b/backend/open_webui/retrieval/vector/dbs/pinecone.py @@ -32,6 +32,8 @@ from open_webui.config import ( PINECONE_CLOUD, ) from open_webui.env import SRC_LOG_LEVELS +from open_webui.retrieval.vector.utils import stringify_metadata + NO_LIMIT = 10000 # Reasonable limit to avoid overwhelming the system BATCH_SIZE = 100 # Recommended batch size for Pinecone operations @@ -183,7 +185,7 @@ class PineconeClient(VectorDBBase): point = { "id": item["id"], "values": item["vector"], - "metadata": metadata, + "metadata": stringify_metadata(metadata), } points.append(point) return points diff --git a/backend/open_webui/retrieval/vector/dbs/s3vector.py b/backend/open_webui/retrieval/vector/dbs/s3vector.py index 74253a3b36..8db1ecb778 100644 --- a/backend/open_webui/retrieval/vector/dbs/s3vector.py +++ b/backend/open_webui/retrieval/vector/dbs/s3vector.py @@ -1,4 +1,4 @@ -from backend.open_webui.retrieval.vector.utils import stringify_metadata +from open_webui.retrieval.vector.utils import stringify_metadata from open_webui.retrieval.vector.main import ( VectorDBBase, VectorItem, diff --git a/backend/open_webui/utils/middleware.py b/backend/open_webui/utils/middleware.py index a433ef3a0c..b459f211b1 100644 --- a/backend/open_webui/utils/middleware.py +++ b/backend/open_webui/utils/middleware.py @@ -19,7 +19,7 @@ from concurrent.futures import ThreadPoolExecutor from fastapi import Request, HTTPException -from starlette.responses import Response, StreamingResponse +from starlette.responses import Response, StreamingResponse, JSONResponse from open_webui.models.chats import Chats @@ -1254,91 +1254,111 @@ async def process_chat_response( # Non-streaming response if not isinstance(response, StreamingResponse): if event_emitter: - if "error" in response: - error = response["error"].get("detail", response["error"]) - Chats.upsert_message_to_chat_by_id_and_message_id( - metadata["chat_id"], - metadata["message_id"], - { - "error": {"content": error}, - }, - ) + if isinstance(response, dict) or isinstance(response, JSONResponse): - if "selected_model_id" in response: - Chats.upsert_message_to_chat_by_id_and_message_id( - metadata["chat_id"], - metadata["message_id"], - { - "selectedModelId": response["selected_model_id"], - }, - ) + if isinstance(response, JSONResponse) and isinstance( + response.body, bytes + ): + try: + response_data = json.loads(response.body.decode("utf-8")) + except json.JSONDecodeError: + response_data = {"error": {"detail": "Invalid JSON response"}} + else: + response_data = response - choices = response.get("choices", []) - if choices and choices[0].get("message", {}).get("content"): - content = response["choices"][0]["message"]["content"] - - if content: - - await event_emitter( - { - "type": "chat:completion", - "data": response, - } - ) - - title = Chats.get_chat_title_by_id(metadata["chat_id"]) - - await event_emitter( - { - "type": "chat:completion", - "data": { - "done": True, - "content": content, - "title": title, - }, - } - ) - - # Save message in the database + if "error" in response_data: + error = response_data["error"].get("detail", response_data["error"]) Chats.upsert_message_to_chat_by_id_and_message_id( metadata["chat_id"], metadata["message_id"], { - "role": "assistant", - "content": content, + "error": {"content": error}, }, ) - # Send a webhook notification if the user is not active - if not get_active_status_by_user_id(user.id): - webhook_url = Users.get_user_webhook_url_by_id(user.id) - if webhook_url: - post_webhook( - request.app.state.WEBUI_NAME, - webhook_url, - f"{title} - {request.app.state.config.WEBUI_URL}/c/{metadata['chat_id']}\n\n{content}", - { - "action": "chat", - "message": content, + if "selected_model_id" in response_data: + Chats.upsert_message_to_chat_by_id_and_message_id( + metadata["chat_id"], + metadata["message_id"], + { + "selectedModelId": response_data["selected_model_id"], + }, + ) + + choices = response_data.get("choices", []) + if choices and choices[0].get("message", {}).get("content"): + content = response_data["choices"][0]["message"]["content"] + + if content: + await event_emitter( + { + "type": "chat:completion", + "data": response_data, + } + ) + + title = Chats.get_chat_title_by_id(metadata["chat_id"]) + + await event_emitter( + { + "type": "chat:completion", + "data": { + "done": True, + "content": content, "title": title, - "url": f"{request.app.state.config.WEBUI_URL}/c/{metadata['chat_id']}", }, - ) + } + ) - await background_tasks_handler() + # Save message in the database + Chats.upsert_message_to_chat_by_id_and_message_id( + metadata["chat_id"], + metadata["message_id"], + { + "role": "assistant", + "content": content, + }, + ) - if events and isinstance(events, list) and isinstance(response, dict): - extra_response = {} - for event in events: - if isinstance(event, dict): - extra_response.update(event) - else: - extra_response[event] = True + # Send a webhook notification if the user is not active + if not get_active_status_by_user_id(user.id): + webhook_url = Users.get_user_webhook_url_by_id(user.id) + if webhook_url: + post_webhook( + request.app.state.WEBUI_NAME, + webhook_url, + f"{title} - {request.app.state.config.WEBUI_URL}/c/{metadata['chat_id']}\n\n{content}", + { + "action": "chat", + "message": content, + "title": title, + "url": f"{request.app.state.config.WEBUI_URL}/c/{metadata['chat_id']}", + }, + ) - response = { - **extra_response, - **response, - } + await background_tasks_handler() + + if events and isinstance(events, list): + extra_response = {} + for event in events: + if isinstance(event, dict): + extra_response.update(event) + else: + extra_response[event] = True + + response_data = { + **extra_response, + **response_data, + } + + if isinstance(response, dict): + response = response_data + if isinstance(response, JSONResponse): + response = JSONResponse( + content=response_data, + headers=response.headers, + status_code=response.status_code, + ) return response else: diff --git a/package-lock.json b/package-lock.json index e286f91d31..58d7858226 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "open-webui", - "version": "0.6.21", + "version": "0.6.22", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "open-webui", - "version": "0.6.21", + "version": "0.6.22", "dependencies": { "@azure/msal-browser": "^4.5.0", "@codemirror/lang-javascript": "^6.2.2", diff --git a/package.json b/package.json index 9266954c49..7e2d59074c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "open-webui", - "version": "0.6.21", + "version": "0.6.22", "private": true, "scripts": { "dev": "npm run pyodide:fetch && vite dev --host", diff --git a/src/lib/apis/openai/index.ts b/src/lib/apis/openai/index.ts index 070118a1a2..c0cbe7b6d3 100644 --- a/src/lib/apis/openai/index.ts +++ b/src/lib/apis/openai/index.ts @@ -379,7 +379,7 @@ export const generateOpenAIChatCompletion = async ( return res.json(); }) .catch((err) => { - error = `${err?.detail ?? err}`; + error = err?.detail ?? err; return null; }); diff --git a/src/lib/components/chat/Chat.svelte b/src/lib/components/chat/Chat.svelte index 5fd1885456..98a492ccfc 100644 --- a/src/lib/components/chat/Chat.svelte +++ b/src/lib/components/chat/Chat.svelte @@ -1783,11 +1783,24 @@ }, `${WEBUI_BASE_URL}/api` ).catch(async (error) => { - toast.error(`${error}`); + console.log(error); + let errorMessage = error; + if (error?.error?.message) { + errorMessage = error.error.message; + } else if (error?.message) { + errorMessage = error.message; + } + + if (typeof errorMessage === 'object') { + errorMessage = $i18n.t(`Uh-oh! There was an issue with the response.`); + } + + toast.error(`${errorMessage}`); responseMessage.error = { content: error }; + responseMessage.done = true; history.messages[responseMessageId] = responseMessage; diff --git a/src/lib/components/chat/Messages/Error.svelte b/src/lib/components/chat/Messages/Error.svelte index cc71036632..2c046485c2 100644 --- a/src/lib/components/chat/Messages/Error.svelte +++ b/src/lib/components/chat/Messages/Error.svelte @@ -10,6 +10,20 @@