Compare commits

...
9 Commits
Author SHA1 Message Date
Tim Baek 6137f7cb7e Merge pull request #22121 from open-webui/dev
0.8.7
2026-03-02 05:14:08 +04:00
Timothy Jaeryang Baek 832d0181b6 chore: format 2026-03-01 19:13:14 -06:00
Timothy Jaeryang Baek d1dd449f63 doc: changelog 2026-03-01 19:12:06 -06:00
Timothy Jaeryang Baek 2751a0f0b6 refac 2026-03-01 19:09:10 -06:00
Shirasawa a9e9fe7899 fix: fix memory leaking of ChatControls (#22112) 2026-03-01 19:06:20 -06:00
Tim Baek 702906aee7 Merge pull request #22119 from Algorithm5838/fix/save-temp-chat-params
fix: pass params when saving a temporary chat
2026-03-02 05:06:02 +04:00
Algorithm5838 fe837d80e7 fix: pass params when saving a temporary chat
The system prompt and other chat controls overrides were lost after
saving because `params` wasn't included in the `createNewChat` call.
2026-03-02 01:35:59 +03:00
Tim Baek 860a0b414e Merge pull request #22111 from Algorithm5838/perf/debounce-get-contents
perf: use rAF to debounce getContents() during streaming
2026-03-02 01:28:15 +04:00
Algorithm5838 6863ca482c perf: use rAF to debounce getContents() during streaming 2026-03-01 23:42:16 +03:00
6 changed files with 71 additions and 49 deletions
+9
View File
@@ -5,6 +5,15 @@ 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.8.7] - 2026-03-01
### Fixed
- 🔒 **Connection access control privacy.** Tool server and terminal connections without explicit access grants are now private (admin-only) by default, fixing a bug where connections configured with no access grants were visible to all users instead of being restricted. [Commit](https://github.com/open-webui/open-webui/commit/2751a0f0b)
- 🧠 **ChatControls memory leak.** The ChatControls panel no longer leaks event listeners, ResizeObserver instances, and media query handlers when navigating between chats, fixing memory accumulation that could degrade performance during extended use. [#22112](https://github.com/open-webui/open-webui/pull/22112)
- 💾 **Temporary chat params preservation.** Model parameters are now correctly saved when creating a temporary chat, ensuring custom settings like temperature and top_p persist across the session. [Commit](https://github.com/open-webui/open-webui/commit/fe837d80e)
- ⚡ **Faster artifact content updates.** Artifact content extraction during streaming is now debounced via requestAnimationFrame, reducing redundant DOM reads and improving CPU efficiency when tokens arrive faster than the browser can paint. [Commit](https://github.com/open-webui/open-webui/commit/6863ca482)
## [0.8.6] - 2026-03-01 ## [0.8.6] - 2026-03-01
### Added ### Added
@@ -163,8 +163,8 @@ def has_connection_access(
based on ``config.access_grants`` within the connection dict. based on ``config.access_grants`` within the connection dict.
- Admin with BYPASS_ADMIN_ACCESS_CONTROL → always allowed - Admin with BYPASS_ADMIN_ACCESS_CONTROL → always allowed
- Empty / missing access_grants → allowed for all users - Missing, None, or empty access_grants → private, admin-only
- Otherwise → delegates to ``has_access`` - access_grants has entries → delegates to ``has_access``
""" """
from open_webui.config import BYPASS_ADMIN_ACCESS_CONTROL from open_webui.config import BYPASS_ADMIN_ACCESS_CONTROL
@@ -175,9 +175,6 @@ def has_connection_access(
user_group_ids = {group.id for group in Groups.get_groups_by_member_id(user.id)} user_group_ids = {group.id for group in Groups.get_groups_by_member_id(user.id)}
access_grants = (connection.get("config") or {}).get("access_grants", []) access_grants = (connection.get("config") or {}).get("access_grants", [])
if not access_grants:
return True
return has_access(user.id, "read", access_grants, user_group_ids) return has_access(user.id, "read", access_grants, user_group_ids)
+2 -2
View File
@@ -1,12 +1,12 @@
{ {
"name": "open-webui", "name": "open-webui",
"version": "0.8.6", "version": "0.8.7",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "open-webui", "name": "open-webui",
"version": "0.8.6", "version": "0.8.7",
"dependencies": { "dependencies": {
"@azure/msal-browser": "^4.5.0", "@azure/msal-browser": "^4.5.0",
"@codemirror/lang-javascript": "^6.2.2", "@codemirror/lang-javascript": "^6.2.2",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "open-webui", "name": "open-webui",
"version": "0.8.6", "version": "0.8.7",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "npm run pyodide:fetch && vite dev --host", "dev": "npm run pyodide:fetch && vite dev --host",
+7 -1
View File
@@ -964,7 +964,11 @@
}; };
$: if (history) { $: if (history) {
getContents(); cancelAnimationFrame(contentsRAF);
contentsRAF = requestAnimationFrame(() => {
getContents();
contentsRAF = null;
});
} else { } else {
artifactContents.set([]); artifactContents.set([]);
} }
@@ -1294,6 +1298,7 @@
}; };
let scrollRAF = null; let scrollRAF = null;
let contentsRAF = null;
const scheduleScrollToBottom = () => { const scheduleScrollToBottom = () => {
if (!scrollRAF) { if (!scrollRAF) {
scrollRAF = requestAnimationFrame(async () => { scrollRAF = requestAnimationFrame(async () => {
@@ -2719,6 +2724,7 @@
id: uuidv4(), id: uuidv4(),
title: title.length > 50 ? `${title.slice(0, 50)}...` : title, title: title.length > 50 ? `${title.slice(0, 50)}...` : title,
models: selectedModels, models: selectedModels,
params: params,
history: history, history: history,
messages: messages, messages: messages,
timestamp: Date.now() timestamp: Date.now()
+50 -40
View File
@@ -50,9 +50,8 @@
export let files; export let files;
export let modelId; export let modelId;
export let pane; export let pane: Pane | null = null;
let mediaQuery;
let largeScreen = false; let largeScreen = false;
let dragged = false; let dragged = false;
let minSize = 0; let minSize = 0;
@@ -172,56 +171,67 @@
dragged = false; dragged = false;
}; };
onMount(async () => { onMount(() => {
mediaQuery = window.matchMedia('(min-width: 1024px)'); const mediaQuery = window.matchMedia('(min-width: 1024px)');
mediaQuery.addEventListener('change', handleMediaQuery); mediaQuery.addEventListener('change', handleMediaQuery);
handleMediaQuery(mediaQuery); handleMediaQuery(mediaQuery);
let resizeObserver: ResizeObserver | null = null;
let isDestroyed = false;
// Wait for Svelte to render the Pane after largeScreen changed // Wait for Svelte to render the Pane after largeScreen changed
await tick(); const init = async () => {
await tick();
const container = document.getElementById('chat-container'); if (isDestroyed) return;
minSize = Math.floor((350 / container.clientWidth) * 100);
const resizeObserver = new ResizeObserver((entries) => { // If controls were persisted as open, set the pane to the saved size
for (let entry of entries) { if ($showControls && pane) {
const width = entry.contentRect.width; openPane();
minSize = Math.floor((350 / width) * 100); }
if ($showControls) {
if (pane && pane.isExpanded() && pane.getSize() < minSize) { setTimeout(() => {
pane.resize(minSize); paneReady = true;
} else { }, 0);
let size = Math.floor(
(parseInt(localStorage?.chatControlsSize) / container.clientWidth) * 100 const container = document.getElementById('chat-container') as HTMLElement;
); if (!container) return;
if (size < minSize) pane.resize(minSize);
minSize = Math.floor((350 / container.clientWidth) * 100);
resizeObserver = new ResizeObserver((entries) => {
for (let entry of entries) {
const width = entry.contentRect.width;
minSize = Math.floor((350 / width) * 100);
if ($showControls) {
if (pane && pane.isExpanded() && pane.getSize() < minSize) {
pane.resize(minSize);
} else {
let size = Math.floor(
(parseInt(localStorage?.chatControlsSize) / container.clientWidth) * 100
);
if (size < minSize && pane) pane.resize(minSize);
}
} }
} }
} });
}); resizeObserver.observe(container);
resizeObserver.observe(container); };
init();
document.addEventListener('mousedown', onMouseDown); document.addEventListener('mousedown', onMouseDown);
document.addEventListener('mouseup', onMouseUp); document.addEventListener('mouseup', onMouseUp);
setTimeout(() => { return () => {
paneReady = true; isDestroyed = true;
}, 0); paneReady = false;
resizeObserver?.disconnect();
// If controls were persisted as open, set the pane to the saved size if (!largeScreen) {
if ($showControls && pane) { showControls.set(false);
openPane(); }
} mediaQuery.removeEventListener('change', handleMediaQuery);
}); document.removeEventListener('mousedown', onMouseDown);
document.removeEventListener('mouseup', onMouseUp);
onDestroy(() => { };
paneReady = false;
if (!largeScreen) {
showControls.set(false);
}
mediaQuery.removeEventListener('change', handleMediaQuery);
document.removeEventListener('mousedown', onMouseDown);
document.removeEventListener('mouseup', onMouseUp);
}); });
const closeHandler = () => { const closeHandler = () => {