diff --git a/CHANGELOG.md b/CHANGELOG.md index acb280f5f..fcf7d5155 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +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.6.9] - 2025-05-10 + +### Added + +- 📝 **Edit Attached Images/Files in Messages**: You can now easily edit your sent messages by removing attached files—streamlining document management, correcting mistakes on the fly, and keeping your chats clutter-free. +- 🚨 **Clear Alerts for Private Task Models**: When interacting with private task models, the UI now clearly alerts you—making it easier to understand resource availability and access, reducing confusion during workflow setup. + +### Fixed + +- 🛡️ **Confirm Dialog Focus Trap Reliability**: The focus now stays correctly within confirmation dialogs, ensuring keyboard navigation and accessibility is seamless and preventing accidental operations—especially helpful during critical or rapid workflows. +- 💬 **Temporary Chat Admin Controls & Session Cleanliness**: Admins are now able to properly enable temporary chat mode without errors, and previous session prompts or tool selections no longer carry over—delivering a fresh, predictable, and consistent temporary chat experience every time. +- 🤖 **External Reranker Integration Functionality Restored**: External reranker integrations now work correctly, allowing you to fully leverage advanced ranking services for sharper, more relevant search results in your RAG and knowledge base workflows. + ## [0.6.8] - 2025-05-10 ### Added diff --git a/package-lock.json b/package-lock.json index de4902c16..ff0414895 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "open-webui", - "version": "0.6.8", + "version": "0.6.9", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "open-webui", - "version": "0.6.8", + "version": "0.6.9", "dependencies": { "@azure/msal-browser": "^4.5.0", "@codemirror/lang-javascript": "^6.2.2", diff --git a/package.json b/package.json index 7fa6ac5a5..e7229fb5c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "open-webui", - "version": "0.6.8", + "version": "0.6.9", "private": true, "scripts": { "dev": "npm run pyodide:fetch && vite dev --host", diff --git a/src/lib/components/admin/Settings/Interface.svelte b/src/lib/components/admin/Settings/Interface.svelte index adb4fbdf9..c70259983 100644 --- a/src/lib/components/admin/Settings/Interface.svelte +++ b/src/lib/components/admin/Settings/Interface.svelte @@ -2,9 +2,9 @@ import { v4 as uuidv4 } from 'uuid'; import { toast } from 'svelte-sonner'; - import { getBackendConfig, getTaskConfig, updateTaskConfig } from '$lib/apis'; + import { getBackendConfig, getModels, getTaskConfig, updateTaskConfig } from '$lib/apis'; import { setDefaultPromptSuggestions } from '$lib/apis/configs'; - import { config, models, settings, user } from '$lib/stores'; + import { config, settings, user } from '$lib/stores'; import { createEventDispatcher, onMount, getContext } from 'svelte'; import { banners as _banners } from '$lib/stores'; @@ -15,6 +15,8 @@ import Tooltip from '$lib/components/common/Tooltip.svelte'; import Switch from '$lib/components/common/Switch.svelte'; import Textarea from '$lib/components/common/Textarea.svelte'; + import Spinner from '$lib/components/common/Spinner.svelte'; + import { getBaseModels } from '$lib/apis/models'; const dispatch = createEventDispatcher(); @@ -49,6 +51,7 @@ }; onMount(async () => { + await init(); taskConfig = await getTaskConfig(localStorage.token); promptSuggestions = $config?.default_prompt_suggestions ?? []; @@ -58,9 +61,40 @@ const updateBanners = async () => { _banners.set(await setBanners(localStorage.token, banners)); }; + + let workspaceModels = null; + let baseModels = null; + + let models = null; + + const init = async () => { + workspaceModels = await getBaseModels(localStorage.token); + baseModels = await getModels(localStorage.token, null, true); + + models = baseModels.map((m) => { + const workspaceModel = workspaceModels.find((wm) => wm.id === m.id); + + if (workspaceModel) { + return { + ...m, + ...workspaceModel + }; + } else { + return { + ...m, + id: m.id, + name: m.name, + + is_active: true + }; + } + }); + + console.log('models', models); + }; -{#if taskConfig} +{#if models !== null && taskConfig}
{ @@ -105,9 +139,27 @@ class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden" bind:value={taskConfig.TASK_MODEL} placeholder={$i18n.t('Select a model')} + on:change={() => { + if (taskConfig.TASK_MODEL) { + const model = models.find((m) => m.id === taskConfig.TASK_MODEL); + if (model) { + if (model?.access_control !== null) { + toast.error( + $i18n.t( + 'This model is not publicly available. Please select another model.' + ) + ); + } + + taskConfig.TASK_MODEL = model.id; + } else { + taskConfig.TASK_MODEL = ''; + } + } + }} > - {#each $models.filter((m) => m.owned_by === 'ollama') as model} + {#each models.filter((m) => m.owned_by === 'ollama') as model} @@ -121,9 +173,27 @@ class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden" bind:value={taskConfig.TASK_MODEL_EXTERNAL} placeholder={$i18n.t('Select a model')} + on:change={() => { + if (taskConfig.TASK_MODEL_EXTERNAL) { + const model = models.find((m) => m.id === taskConfig.TASK_MODEL_EXTERNAL); + if (model) { + if (model?.access_control !== null) { + toast.error( + $i18n.t( + 'This model is not publicly available. Please select another model.' + ) + ); + } + + taskConfig.TASK_MODEL_EXTERNAL = model.id; + } else { + taskConfig.TASK_MODEL_EXTERNAL = ''; + } + } + }} > - {#each $models as model} + {#each models as model} @@ -480,4 +550,8 @@
+{:else} +
+ +
{/if} diff --git a/src/lib/components/chat/Chat.svelte b/src/lib/components/chat/Chat.svelte index 5d11ce940..7e3e3843e 100644 --- a/src/lib/components/chat/Chat.svelte +++ b/src/lib/components/chat/Chat.svelte @@ -155,12 +155,14 @@ localStorage.getItem(`chat-input${chatIdProp ? `-${chatIdProp}` : ''}`) ); - prompt = input.prompt; - files = input.files; - selectedToolIds = input.selectedToolIds; - webSearchEnabled = input.webSearchEnabled; - imageGenerationEnabled = input.imageGenerationEnabled; - codeInterpreterEnabled = input.codeInterpreterEnabled; + if (!$temporaryChatEnabled) { + prompt = input.prompt; + files = input.files; + selectedToolIds = input.selectedToolIds; + webSearchEnabled = input.webSearchEnabled; + imageGenerationEnabled = input.imageGenerationEnabled; + codeInterpreterEnabled = input.codeInterpreterEnabled; + } } catch (e) {} } @@ -419,25 +421,27 @@ } if (localStorage.getItem(`chat-input${chatIdProp ? `-${chatIdProp}` : ''}`)) { + prompt = ''; + files = []; + selectedToolIds = []; + webSearchEnabled = false; + imageGenerationEnabled = false; + codeInterpreterEnabled = false; + try { const input = JSON.parse( localStorage.getItem(`chat-input${chatIdProp ? `-${chatIdProp}` : ''}`) ); - console.log('chat-input', input); - prompt = input.prompt; - files = input.files; - selectedToolIds = input.selectedToolIds; - webSearchEnabled = input.webSearchEnabled; - imageGenerationEnabled = input.imageGenerationEnabled; - codeInterpreterEnabled = input.codeInterpreterEnabled; - } catch (e) { - prompt = ''; - files = []; - selectedToolIds = []; - webSearchEnabled = false; - imageGenerationEnabled = false; - codeInterpreterEnabled = false; - } + + if (!$temporaryChatEnabled) { + prompt = input.prompt; + files = input.files; + selectedToolIds = input.selectedToolIds; + webSearchEnabled = input.webSearchEnabled; + imageGenerationEnabled = input.imageGenerationEnabled; + codeInterpreterEnabled = input.codeInterpreterEnabled; + } + } catch (e) {} } if (!chatIdProp) { @@ -1496,9 +1500,20 @@ }; const sendPromptSocket = async (_history, model, responseMessageId, _chatId) => { + const chatMessages = createMessagesList(history, history.currentId); const responseMessage = _history.messages[responseMessageId]; const userMessage = _history.messages[responseMessage.parentId]; + const chatMessageFiles = chatMessages + .filter((message) => message.files) + .flatMap((message) => message.files); + + // Filter chatFiles to only include files that are in the chatMessageFiles + chatFiles = chatFiles.filter((item) => { + const fileExists = chatMessageFiles.some((messageFile) => messageFile.id === item.id); + return fileExists; + }); + let files = JSON.parse(JSON.stringify(chatFiles)); files.push( ...(userMessage?.files ?? []).filter((item) => diff --git a/src/lib/components/chat/Messages.svelte b/src/lib/components/chat/Messages.svelte index 160d7eb37..22d6b95d0 100644 --- a/src/lib/components/chat/Messages.svelte +++ b/src/lib/components/chat/Messages.svelte @@ -255,7 +255,7 @@ await updateChat(); }; - const editMessage = async (messageId, content, submit = true) => { + const editMessage = async (messageId, { content, files }, submit = true) => { if (history.messages[messageId].role === 'user') { if (submit) { // New user message @@ -268,7 +268,7 @@ childrenIds: [], role: 'user', content: userPrompt, - ...(history.messages[messageId].files && { files: history.messages[messageId].files }), + ...(files && { files: files }), models: selectedModels, timestamp: Math.floor(Date.now() / 1000) // Unix epoch }; @@ -290,6 +290,7 @@ } else { // Edit user message history.messages[messageId].content = content; + history.messages[messageId].files = files; await updateChat(); } } else { diff --git a/src/lib/components/chat/Messages/ResponseMessage.svelte b/src/lib/components/chat/Messages/ResponseMessage.svelte index e8143e47c..79491d7eb 100644 --- a/src/lib/components/chat/Messages/ResponseMessage.svelte +++ b/src/lib/components/chat/Messages/ResponseMessage.svelte @@ -377,7 +377,7 @@ const editMessageConfirmHandler = async () => { const messageContent = postprocessAfterEditing(editedContent ? editedContent : ''); - editMessage(message.id, messageContent, false); + editMessage(message.id, { content: messageContent }, false); edit = false; editedContent = ''; @@ -388,7 +388,7 @@ const saveAsCopyHandler = async () => { const messageContent = postprocessAfterEditing(editedContent ? editedContent : ''); - editMessage(message.id, messageContent); + editMessage(message.id, { content: messageContent }); edit = false; editedContent = ''; diff --git a/src/lib/components/chat/Messages/UserMessage.svelte b/src/lib/components/chat/Messages/UserMessage.svelte index 24d657816..4a61b7190 100644 --- a/src/lib/components/chat/Messages/UserMessage.svelte +++ b/src/lib/components/chat/Messages/UserMessage.svelte @@ -43,6 +43,8 @@ let edit = false; let editedContent = ''; + let editedFiles = []; + let messageEditTextAreaElement: HTMLTextAreaElement; let message = JSON.parse(JSON.stringify(history.messages[messageId])); @@ -62,6 +64,7 @@ const editMessageHandler = async () => { edit = true; editedContent = message.content; + editedFiles = message.files; await tick(); @@ -74,15 +77,17 @@ }; const editMessageConfirmHandler = async (submit = true) => { - editMessage(message.id, editedContent, submit); + editMessage(message.id, { content: editedContent, files: editedFiles }, submit); edit = false; editedContent = ''; + editedFiles = []; }; const cancelEditMessage = () => { edit = false; editedContent = ''; + editedFiles = []; }; const deleteMessageHandler = async () => { @@ -141,30 +146,90 @@ {/if}
- {#if message.files} -
- {#each message.files as file} -
- {#if file.type === 'image'} - - {:else} - - {/if} -
- {/each} -
+ {#if edit !== true} + {#if message.files} +
+ {#each message.files as file} +
+ {#if file.type === 'image'} + + {:else} + + {/if} +
+ {/each} +
+ {/if} {/if} {#if message.content !== ''} {#if edit === true}
+ {#if (editedFiles ?? []).length > 0} +
+ {#each editedFiles as file, fileIdx} + {#if file.type === 'image'} +
+
+ input +
+
+ +
+
+ {:else} + { + editedFiles.splice(fileIdx, 1); + + editedFiles = editedFiles; + }} + on:click={() => { + console.log(file); + }} + /> + {/if} + {/each} +
+ {/if} +