Compare commits

...
5 Commits
Author SHA1 Message Date
Tim Baek 1ac3dd4a89 Merge pull request #21773 from open-webui/dev
0.8.5
2026-02-23 13:26:21 +04:00
Timothy Jaeryang Baek 55c489146c doc: changelog 2026-02-23 03:25:17 -06:00
Timothy Jaeryang Baek ffcf97e3e1 chore: bump 2026-02-23 03:24:33 -06:00
Timothy Jaeryang Baek 95bde946ba refac 2026-02-23 03:22:19 -06:00
Timothy Jaeryang Baek 895c805e62 feat: dictation toggle 2026-02-23 02:54:53 -06:00
9 changed files with 60 additions and 10 deletions
+12
View File
@@ -5,6 +5,18 @@ 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.5] - 2026-02-23
### Added
- ⌨️ **Voice dictation shortcut.** Users can now toggle voice dictation using Cmd+Shift+L (or Ctrl+Shift+L on Windows/Linux), making it faster to start and stop dictation without clicking the microphone button.
### Fixed
- 🚫 **Model access KeyError fix.** The /api/models endpoint no longer crashes with a 500 error when models have incomplete info metadata missing the user_id field (e.g. models using global default metadata).
- 🔄 **Frontend initialization resilience.** The app layout now gracefully handles individual API failures during initialization (getModels, getBanners, getTools, getUserSettings, setToolServers) instead of blocking the entire page load when any single call fails.
- 🛡️ **Backend config null safety.** Language detection during app initialization no longer crashes when the backend config fetch fails, preventing a secondary cause of infinite loading.
## [0.8.4] - 2026-02-23 ## [0.8.4] - 2026-02-23
### Added ### Added
+1 -1
View File
@@ -459,7 +459,7 @@ def get_filtered_models(models, user, db=None):
if model_info: if model_info:
if ( if (
(user.role == "admin" and BYPASS_ADMIN_ACCESS_CONTROL) (user.role == "admin" and BYPASS_ADMIN_ACCESS_CONTROL)
or user.id == model_info["user_id"] or user.id == model_info.get("user_id")
or model["id"] in accessible_model_ids or model["id"] in accessible_model_ids
): ):
filtered_models.append(model) filtered_models.append(model)
+2 -2
View File
@@ -1,12 +1,12 @@
{ {
"name": "open-webui", "name": "open-webui",
"version": "0.8.4", "version": "0.8.5",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "open-webui", "name": "open-webui",
"version": "0.8.4", "version": "0.8.5",
"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.4", "version": "0.8.5",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "npm run pyodide:fetch && vite dev --host", "dev": "npm run pyodide:fetch && vite dev --host",
@@ -824,6 +824,19 @@
shiftKey = true; shiftKey = true;
} }
// Cmd/Ctrl+Shift+L to toggle dictation
if (e.key.toLowerCase() === 'l' && (e.metaKey || e.ctrlKey) && e.shiftKey) {
e.preventDefault();
if (recording) {
// Confirm and stop recording
document.getElementById('confirm-recording-button')?.click();
} else {
// Start recording (same logic as voice-input-button click)
document.getElementById('voice-input-button')?.click();
}
return;
}
if (e.key === 'Escape') { if (e.key === 'Escape') {
console.log('Escape'); console.log('Escape');
dragged = false; dragged = false;
@@ -368,7 +368,17 @@
let maxVisibleItems = 300; let maxVisibleItems = 300;
$: maxVisibleItems = Math.floor(containerWidth / 5); // 2px width + 0.5px gap $: maxVisibleItems = Math.floor(containerWidth / 5); // 2px width + 0.5px gap
const handleKeyDown = (e) => {
if (e.key === 'Escape') {
e.preventDefault();
stopRecording();
onCancel();
}
};
onMount(() => { onMount(() => {
window.addEventListener('keydown', handleKeyDown);
// listen to width changes // listen to width changes
resizeObserver = new ResizeObserver(() => { resizeObserver = new ResizeObserver(() => {
VISUALIZER_BUFFER_LENGTH = Math.floor(window.innerWidth / 4); VISUALIZER_BUFFER_LENGTH = Math.floor(window.innerWidth / 4);
@@ -385,6 +395,7 @@
}); });
onDestroy(() => { onDestroy(() => {
window.removeEventListener('keydown', handleKeyDown);
// remove resize observer // remove resize observer
resizeObserver.disconnect(); resizeObserver.disconnect();
}); });
@@ -547,6 +558,7 @@
</div> </div>
{:else} {:else}
<button <button
id="confirm-recording-button"
type="button" type="button"
class="p-1.5 bg-indigo-500 text-white dark:bg-indigo-500 dark:text-blue-950 rounded-full" class="p-1.5 bg-indigo-500 text-white dark:bg-indigo-500 dark:text-blue-950 rounded-full"
on:click={async () => { on:click={async () => {
+6
View File
@@ -17,6 +17,7 @@ export enum Shortcut {
NEW_TEMPORARY_CHAT = 'newTemporaryChat', NEW_TEMPORARY_CHAT = 'newTemporaryChat',
DELETE_CHAT = 'deleteChat', DELETE_CHAT = 'deleteChat',
OPEN_MODEL_SELECTOR = 'openModelSelector', OPEN_MODEL_SELECTOR = 'openModelSelector',
TOGGLE_DICTATION = 'toggleDictation',
//Global //Global
SEARCH = 'search', SEARCH = 'search',
@@ -64,6 +65,11 @@ export const shortcuts: ShortcutRegistry = {
keys: ['mod', 'shift', 'M'], keys: ['mod', 'shift', 'M'],
category: 'Chat' category: 'Chat'
}, },
[Shortcut.TOGGLE_DICTATION]: {
name: 'Toggle Dictation',
keys: ['mod', 'shift', 'L'],
category: 'Chat'
},
//Global //Global
[Shortcut.SEARCH]: { [Shortcut.SEARCH]: {
+7 -4
View File
@@ -152,11 +152,14 @@
clearChatInputStorage(); clearChatInputStorage();
await Promise.all([ await Promise.all([
checkLocalDBChats(), checkLocalDBChats(),
setBanners(), setBanners().catch((e) => console.error('Failed to load banners:', e)),
setTools(), setTools().catch((e) => console.error('Failed to load tools:', e)),
setUserSettings(async () => { setUserSettings(async () => {
await Promise.all([setModels(), setToolServers()]); await Promise.all([
}) setModels().catch((e) => console.error('Failed to load models:', e)),
setToolServers().catch((e) => console.error('Failed to load tool servers:', e))
]);
}).catch((e) => console.error('Failed to load user settings:', e))
]); ]);
// Helper function to check if the pressed keys match the shortcut definition // Helper function to check if the pressed keys match the shortcut definition
+6 -2
View File
@@ -770,7 +770,7 @@
const browserLanguages = navigator.languages const browserLanguages = navigator.languages
? navigator.languages ? navigator.languages
: [navigator.language || navigator.userLanguage]; : [navigator.language || navigator.userLanguage];
const lang = backendConfig.default_locale const lang = backendConfig?.default_locale
? backendConfig.default_locale ? backendConfig.default_locale
: bestMatchingLanguage(languages, browserLanguages, 'en-US'); : bestMatchingLanguage(languages, browserLanguages, 'en-US');
changeLanguage(lang); changeLanguage(lang);
@@ -797,7 +797,11 @@
if (sessionUser) { if (sessionUser) {
await user.set(sessionUser); await user.set(sessionUser);
await config.set(await getBackendConfig()); try {
await config.set(await getBackendConfig());
} catch (error) {
console.error('Error refreshing backend config:', error);
}
} else { } else {
// Redirect Invalid Session User to /auth Page // Redirect Invalid Session User to /auth Page
localStorage.removeItem('token'); localStorage.removeItem('token');