From 121a13d4ed62a77ea6c1bc9c0b5628644c264112 Mon Sep 17 00:00:00 2001 From: Rory <16675082+roryeckel@users.noreply.github.com> Date: Mon, 3 Feb 2025 17:37:20 -0600 Subject: [PATCH 01/13] fix: Filter to valid RAG web search URLs --- backend/open_webui/retrieval/web/main.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/backend/open_webui/retrieval/web/main.py b/backend/open_webui/retrieval/web/main.py index 1af8a70aa..28a749e7d 100644 --- a/backend/open_webui/retrieval/web/main.py +++ b/backend/open_webui/retrieval/web/main.py @@ -1,3 +1,5 @@ +import validators + from typing import Optional from urllib.parse import urlparse @@ -10,6 +12,8 @@ def get_filtered_results(results, filter_list): filtered_results = [] for result in results: url = result.get("url") or result.get("link", "") + if not validators.url(url): + continue domain = urlparse(url).netloc if any(domain.endswith(filtered_domain) for filtered_domain in filter_list): filtered_results.append(result) From 3db6b4352fdb75d469fc58e272d2de2e0b071b5c Mon Sep 17 00:00:00 2001 From: Rory <16675082+roryeckel@users.noreply.github.com> Date: Mon, 3 Feb 2025 18:18:49 -0600 Subject: [PATCH 02/13] fix: Filter out invalid RAG web URLs (continued) --- backend/open_webui/retrieval/web/utils.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/backend/open_webui/retrieval/web/utils.py b/backend/open_webui/retrieval/web/utils.py index a322bbbfc..d91c7da15 100644 --- a/backend/open_webui/retrieval/web/utils.py +++ b/backend/open_webui/retrieval/web/utils.py @@ -42,6 +42,15 @@ def validate_url(url: Union[str, Sequence[str]]): else: return False +def safe_validate_urls(url: Sequence[str]) -> Sequence[str]: + valid_urls = [] + for u in url: + try: + if validate_url(u): + valid_urls.append(u) + except ValueError: + continue + return valid_urls def resolve_hostname(hostname): # Get address information @@ -86,11 +95,11 @@ def get_web_loader( verify_ssl: bool = True, requests_per_second: int = 2, ): - # Check if the URL is valid - if not validate_url(urls): - raise ValueError(ERROR_MESSAGES.INVALID_URL) + # Check if the URLs are valid + safe_urls = safe_validate_urls([urls] if isinstance(urls, str) else urls) + return SafeWebBaseLoader( - urls, + safe_urls, verify_ssl=verify_ssl, requests_per_second=requests_per_second, continue_on_failure=True, From 94c6428ae2248b57ed0b92e6fc861032ec4ecac4 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Mon, 3 Feb 2025 19:06:15 -0800 Subject: [PATCH 03/13] refac: chat --- src/lib/components/chat/Chat.svelte | 92 +++++++++++++------------ src/lib/components/chat/Messages.svelte | 2 +- 2 files changed, 48 insertions(+), 46 deletions(-) diff --git a/src/lib/components/chat/Chat.svelte b/src/lib/components/chat/Chat.svelte index 7fac140de..072a0140a 100644 --- a/src/lib/components/chat/Chat.svelte +++ b/src/lib/components/chat/Chat.svelte @@ -41,6 +41,7 @@ convertMessagesToHistory, copyToClipboard, getMessageContentParts, + createMessagesList, extractSentencesForAudio, promptTemplate, splitStream, @@ -226,7 +227,7 @@ } await tick(); - saveChatHandler(_chatId); + saveChatHandler(_chatId, history); }; const chatEventHandler = async (event, cb) => { @@ -826,20 +827,6 @@ messagesContainerElement.scrollTop = messagesContainerElement.scrollHeight; } }; - - const createMessagesList = (responseMessageId) => { - if (responseMessageId === null) { - return []; - } - - const message = history.messages[responseMessageId]; - if (message?.parentId) { - return [...createMessagesList(message.parentId), message]; - } else { - return [message]; - } - }; - const chatCompletedHandler = async (chatId, modelId, responseMessageId, messages) => { const res = await chatCompleted(localStorage.token, { model: modelId, @@ -896,7 +883,7 @@ }; const chatActionHandler = async (chatId, actionId, modelId, responseMessageId, event = null) => { - const messages = createMessagesList(responseMessageId); + const messages = createMessagesList(history, responseMessageId); const res = await chatAction(localStorage.token, actionId, { model: modelId, @@ -965,7 +952,7 @@ const modelId = selectedModels[0]; const model = $models.filter((m) => m.id === modelId).at(0); - const messages = createMessagesList(history.currentId); + const messages = createMessagesList(history, history.currentId); const parentMessage = messages.length !== 0 ? messages.at(-1) : null; const userMessageId = uuidv4(); @@ -1010,9 +997,9 @@ } if (messages.length === 0) { - await initChatHandler(); + await initChatHandler(history); } else { - await saveChatHandler($chatId); + await saveChatHandler($chatId, history); } } }; @@ -1074,9 +1061,9 @@ } if (messages.length === 0) { - await initChatHandler(); + await initChatHandler(history); } else { - await saveChatHandler($chatId); + await saveChatHandler($chatId, history); } }; @@ -1210,7 +1197,12 @@ ); history.messages[message.id] = message; - await chatCompletedHandler(chatId, message.model, message.id, createMessagesList(message.id)); + await chatCompletedHandler( + chatId, + message.model, + message.id, + createMessagesList(history, message.id) + ); } console.log(data); @@ -1226,7 +1218,7 @@ const submitPrompt = async (userPrompt, { _raw = false } = {}) => { console.log('submitPrompt', userPrompt, $chatId); - const messages = createMessagesList(history.currentId); + const messages = createMessagesList(history, history.currentId); const _selectedModels = selectedModels.map((modelId) => $models.map((m) => m.id).includes(modelId) ? modelId : '' ); @@ -1325,25 +1317,30 @@ saveSessionSelectedModels(); - await sendPrompt(userPrompt, userMessageId, { newChat: true }); + await sendPrompt(history, userPrompt, userMessageId, { newChat: true }); }; const sendPrompt = async ( + history, prompt: string, parentId: string, { modelId = null, modelIdx = null, newChat = false } = {} ) => { + let _chatId = JSON.parse(JSON.stringify($chatId)); + // Create new chat if newChat is true and first user message if ( newChat && history.messages[history.currentId].parentId === null && history.messages[history.currentId].role === 'user' ) { - await initChatHandler(); + _chatId = await initChatHandler(history); } else { - await saveChatHandler($chatId); + await saveChatHandler(_chatId, history); } + await tick(); + // If modelId is provided, use it, else use selected model let selectedModelIds = modelId ? [modelId] @@ -1390,16 +1387,15 @@ await tick(); // Save chat after all messages have been created - await saveChatHandler($chatId); + await saveChatHandler(_chatId, history); - const _chatId = JSON.parse(JSON.stringify($chatId)); await Promise.all( selectedModelIds.map(async (modelId, _modelIdx) => { console.log('modelId', modelId); const model = $models.filter((m) => m.id === modelId).at(0); if (model) { - const messages = createMessagesList(parentId); + const messages = createMessagesList(history, parentId); // If there are image files, check if model is vision capable const hasImages = messages.some((message) => message.files?.some((file) => file.type === 'image') @@ -1507,7 +1503,7 @@ }` } : undefined, - ...createMessagesList(responseMessageId).map((message) => ({ + ...createMessagesList(history, responseMessageId).map((message) => ({ ...message, content: removeDetails(message.content, ['reasoning', 'code_interpreter']) })) @@ -1704,7 +1700,7 @@ history.currentId = userMessageId; await tick(); - await sendPrompt(userPrompt, userMessageId); + await sendPrompt(history, userPrompt, userMessageId); }; const regenerateResponse = async (message) => { @@ -1716,11 +1712,11 @@ if ((userMessage?.models ?? [...selectedModels]).length == 1) { // If user message has only one model selected, sendPrompt automatically selects it for regeneration - await sendPrompt(userPrompt, userMessage.id); + await sendPrompt(history, userPrompt, userMessage.id); } else { // If there are multiple models selected, use the model of the response message for regeneration // e.g. many model chat - await sendPrompt(userPrompt, userMessage.id, { + await sendPrompt(history, userPrompt, userMessage.id, { modelId: message.model, modelIdx: message.modelIdx }); @@ -1785,7 +1781,7 @@ } } - await saveChatHandler(_chatId); + await saveChatHandler(_chatId, history); } else { console.error(res); } @@ -1794,42 +1790,48 @@ } }; - const initChatHandler = async () => { + const initChatHandler = async (history) => { + let _chatId = $chatId; + if (!$temporaryChatEnabled) { chat = await createNewChat(localStorage.token, { - id: $chatId, + id: _chatId, title: $i18n.t('New Chat'), models: selectedModels, system: $settings.system ?? undefined, params: params, history: history, - messages: createMessagesList(history.currentId), + messages: createMessagesList(history, history.currentId), tags: [], timestamp: Date.now() }); - currentChatPage.set(1); - await chats.set(await getChatList(localStorage.token, $currentChatPage)); - await chatId.set(chat.id); + _chatId = chat.id; + await chatId.set(_chatId); - window.history.replaceState(history.state, '', `/c/${chat.id}`); + await chats.set(await getChatList(localStorage.token, $currentChatPage)); + currentChatPage.set(1); + + window.history.replaceState(history.state, '', `/c/${_chatId}`); } else { + _chatId = 'local'; await chatId.set('local'); } await tick(); + + return _chatId; }; - const saveChatHandler = async (_chatId) => { + const saveChatHandler = async (_chatId, history) => { if ($chatId == _chatId) { if (!$temporaryChatEnabled) { chat = await updateChatById(localStorage.token, _chatId, { models: selectedModels, history: history, - messages: createMessagesList(history.currentId), + messages: createMessagesList(history, history.currentId), params: params, files: chatFiles }); - currentChatPage.set(1); await chats.set(await getChatList(localStorage.token, $currentChatPage)); } @@ -1933,7 +1935,7 @@ {/if}
- {#if $settings?.landingPageMode === 'chat' || createMessagesList(history.currentId).length > 0} + {#if $settings?.landingPageMode === 'chat' || createMessagesList(history, history.currentId).length > 0} {/if} -
-
- { - filesInputElement.click(); - }} - > - - -
- +
+
+ +
+
+ { + filesInputElement.click(); + }} + > + + +
{#if content === ''} diff --git a/src/lib/components/chat/MessageInput.svelte b/src/lib/components/chat/MessageInput.svelte index 6886ca5c3..d2cc8f061 100644 --- a/src/lib/components/chat/MessageInput.svelte +++ b/src/lib/components/chat/MessageInput.svelte @@ -673,66 +673,10 @@
{/if} -
-
- { - filesInputElement.click(); - }} - uploadGoogleDriveHandler={async () => { - try { - const fileData = await createPicker(); - if (fileData) { - const file = new File([fileData.blob], fileData.name, { - type: fileData.blob.type - }); - await uploadFileHandler(file); - } else { - console.log('No file was selected from Google Drive'); - } - } catch (error) { - console.error('Google Drive Error:', error); - toast.error( - $i18n.t('Error accessing Google Drive: {{error}}', { - error: error.message - }) - ); - } - }} - onClose={async () => { - await tick(); - - const chatInput = document.getElementById('chat-input'); - chatInput?.focus(); - }} - > - - -
- +
{#if $settings?.richTextInput ?? true}
{ @@ -1125,6 +1069,64 @@ }} /> {/if} +
+ +
+
+ { + filesInputElement.click(); + }} + uploadGoogleDriveHandler={async () => { + try { + const fileData = await createPicker(); + if (fileData) { + const file = new File([fileData.blob], fileData.name, { + type: fileData.blob.type + }); + await uploadFileHandler(file); + } else { + console.log('No file was selected from Google Drive'); + } + } catch (error) { + console.error('Google Drive Error:', error); + toast.error( + $i18n.t('Error accessing Google Drive: {{error}}', { + error: error.message + }) + ); + } + }} + onClose={async () => { + await tick(); + + const chatInput = document.getElementById('chat-input'); + chatInput?.focus(); + }} + > + + +
{#if !history?.currentId || history.messages[history.currentId]?.done == true} From 2517a2b65340ab36f437abf97e46fc9b42061c61 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Mon, 3 Feb 2025 19:34:36 -0800 Subject: [PATCH 06/13] refac: styling --- .../components/channel/MessageInput.svelte | 4 +- src/lib/components/chat/MessageInput.svelte | 53 +++++++++++-------- src/lib/components/layout/Help.svelte | 4 +- 3 files changed, 34 insertions(+), 27 deletions(-) diff --git a/src/lib/components/channel/MessageInput.svelte b/src/lib/components/channel/MessageInput.svelte index 9642e32fc..105e46a79 100644 --- a/src/lib/components/channel/MessageInput.svelte +++ b/src/lib/components/channel/MessageInput.svelte @@ -513,7 +513,7 @@ }} > + + {#if $config?.features?.enable_web_search} + + + + {/if}
@@ -1183,7 +1186,9 @@
From b61138e67629c473c01a45f88e2943d286211304 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Mon, 3 Feb 2025 19:54:20 -0800 Subject: [PATCH 07/13] refac: message input --- src/lib/components/chat/MessageInput.svelte | 77 ++++++++++++++----- .../chat/MessageInput/InputMenu.svelte | 64 --------------- src/lib/components/icons/CommandLine.svelte | 19 +++++ src/lib/components/icons/Photo.svelte | 21 +++++ 4 files changed, 99 insertions(+), 82 deletions(-) create mode 100644 src/lib/components/icons/CommandLine.svelte create mode 100644 src/lib/components/icons/Photo.svelte diff --git a/src/lib/components/chat/MessageInput.svelte b/src/lib/components/chat/MessageInput.svelte index b277479bf..ef12211d5 100644 --- a/src/lib/components/chat/MessageInput.svelte +++ b/src/lib/components/chat/MessageInput.svelte @@ -22,24 +22,27 @@ import { blobToFile, compressImage, createMessagesList, findWordIndices } from '$lib/utils'; import { transcribeAudio } from '$lib/apis/audio'; import { uploadFile } from '$lib/apis/files'; - import { getTools } from '$lib/apis/tools'; + import { generateAutoCompletion } from '$lib/apis'; + import { deleteFileById } from '$lib/apis/files'; import { WEBUI_BASE_URL, WEBUI_API_BASE_URL, PASTED_TEXT_CHARACTER_LIMIT } from '$lib/constants'; - import Tooltip from '../common/Tooltip.svelte'; import InputMenu from './MessageInput/InputMenu.svelte'; - import Headphone from '../icons/Headphone.svelte'; import VoiceRecording from './MessageInput/VoiceRecording.svelte'; - import FileItem from '../common/FileItem.svelte'; import FilesOverlay from './MessageInput/FilesOverlay.svelte'; import Commands from './MessageInput/Commands.svelte'; - import XMark from '../icons/XMark.svelte'; + import RichTextInput from '../common/RichTextInput.svelte'; - import { generateAutoCompletion } from '$lib/apis'; - import { error, text } from '@sveltejs/kit'; + import Tooltip from '../common/Tooltip.svelte'; + import FileItem from '../common/FileItem.svelte'; import Image from '../common/Image.svelte'; - import { deleteFileById } from '$lib/apis/files'; + + import XMark from '../icons/XMark.svelte'; + import Headphone from '../icons/Headphone.svelte'; import GlobeAlt from '../icons/GlobeAlt.svelte'; + import PhotoSolid from '../icons/PhotoSolid.svelte'; + import Photo from '../icons/Photo.svelte'; + import CommandLine from '../icons/CommandLine.svelte'; const i18n = getContext('i18n'); @@ -1112,19 +1115,57 @@ - {#if $config?.features?.enable_web_search} - + {#if $_user} + {#if $config?.features?.enable_web_search && ($_user.role === 'admin' || $_user?.permissions?.features?.web_search)} + + + + {/if} + + {#if $config?.features?.enable_image_generation && ($_user.role === 'admin' || $_user?.permissions?.features?.image_generation)} + + + + {/if} + + @@ -1187,7 +1228,7 @@ - {/if} - - - - {#if showWebSearch} - - {/if} - - {#if showImageGeneration || showWebSearch} -
- {/if} - {#if !$mobile} + export let className = 'w-4 h-4'; + export let strokeWidth = '1.5'; + + + + + diff --git a/src/lib/components/icons/Photo.svelte b/src/lib/components/icons/Photo.svelte new file mode 100644 index 000000000..2933ba633 --- /dev/null +++ b/src/lib/components/icons/Photo.svelte @@ -0,0 +1,21 @@ + + + From 2bf68593af1ab4464c5fa94e0b8fdced2705444c Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Mon, 3 Feb 2025 20:06:17 -0800 Subject: [PATCH 08/13] refac: styling --- .../components/channel/MessageInput.svelte | 6 +- src/lib/components/chat/MessageInput.svelte | 10 ++- .../chat/MessageInput/InputMenu.svelte | 64 +++++++++++++------ src/lib/components/common/FileItem.svelte | 2 +- 4 files changed, 52 insertions(+), 30 deletions(-) diff --git a/src/lib/components/channel/MessageInput.svelte b/src/lib/components/channel/MessageInput.svelte index 105e46a79..7615a8e03 100644 --- a/src/lib/components/channel/MessageInput.svelte +++ b/src/lib/components/channel/MessageInput.svelte @@ -398,7 +398,7 @@ dir={$settings?.chatDirection ?? 'LTR'} > {#if files.length > 0} -
+
{#each files as file, fileIdx} {#if file.type === 'image'}
@@ -411,7 +411,7 @@
{/if} -
+
diff --git a/src/lib/components/chat/MessageInput.svelte b/src/lib/components/chat/MessageInput.svelte index ef12211d5..f84a3f1ee 100644 --- a/src/lib/components/chat/MessageInput.svelte +++ b/src/lib/components/chat/MessageInput.svelte @@ -573,7 +573,7 @@ dir={$settings?.chatDirection ?? 'LTR'} > {#if files.length > 0} -
+
{#each files as file, fileIdx} {#if file.type === 'image'}
@@ -611,7 +611,7 @@
{/if} -
+
{#if $settings?.richTextInput ?? true}
{ filesInputElement.click(); }} diff --git a/src/lib/components/chat/MessageInput/InputMenu.svelte b/src/lib/components/chat/MessageInput/InputMenu.svelte index faa14a9a7..4a7624cfe 100644 --- a/src/lib/components/chat/MessageInput/InputMenu.svelte +++ b/src/lib/components/chat/MessageInput/InputMenu.svelte @@ -21,12 +21,12 @@ export let screenCaptureHandler: Function; export let uploadFilesHandler: Function; + export let inputFilesHandler: Function; + export let uploadGoogleDriveHandler: Function; export let selectedToolIds: string[] = []; - export let codeInterpreterEnabled: boolean; - export let onClose: Function; let tools = {}; @@ -53,8 +53,26 @@ return a; }, {}); }; + + function handleFileChange(event) { + const inputFiles = Array.from(event.target?.files); + if (inputFiles && inputFiles.length > 0) { + console.log(inputFiles); + inputFilesHandler(inputFiles); + } + } + + + { @@ -120,26 +138,32 @@
{/if} - {#if !$mobile} - - { - if (fileUploadEnabled) { + + { + if (fileUploadEnabled) { + if (!$mobile) { screenCaptureHandler(); + } else { + const cameraInputElement = document.getElementById('camera-input'); + + if (cameraInputElement) { + cameraInputElement.click(); + } } - }} - > - -
{$i18n.t('Capture')}
-
-
- {/if} + } + }} + > + +
{$i18n.t('Capture')}
+
+
-
-
+
+
-
+
{#if !history?.currentId || history.messages[history.currentId]?.done == true}