Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1ac3dd4a89 | ||
|
|
55c489146c | ||
|
|
ffcf97e3e1 | ||
|
|
95bde946ba | ||
|
|
895c805e62 |
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
Generated
+2
-2
@@ -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
@@ -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 () => {
|
||||||
|
|||||||
@@ -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]: {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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');
|
||||||
|
|||||||
Reference in New Issue
Block a user