From c5921dea58d1bd332cd820df4c8493316a641184 Mon Sep 17 00:00:00 2001 From: Aleix Dorca Date: Mon, 14 Oct 2024 11:38:33 +0200 Subject: [PATCH 01/53] Update catalan translation.json --- src/lib/i18n/locales/ca-ES/translation.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/lib/i18n/locales/ca-ES/translation.json b/src/lib/i18n/locales/ca-ES/translation.json index 56cac9760a..ae32c6c9dc 100644 --- a/src/lib/i18n/locales/ca-ES/translation.json +++ b/src/lib/i18n/locales/ca-ES/translation.json @@ -294,7 +294,7 @@ "Export Prompts": "Exportar les indicacions", "Export Tools": "Exportar les eines", "External Models": "Models externs", - "Failed to add file.": "", + "Failed to add file.": "No s'ha pogut afegir l'arxiu.", "Failed to create API Key.": "No s'ha pogut crear la clau API.", "Failed to read clipboard contents": "No s'ha pogut llegir el contingut del porta-retalls", "Failed to update settings": "No s'han pogut actualitzar les preferències", @@ -450,7 +450,7 @@ "Name your model": "Posa un nom al teu model", "New Chat": "Nou xat", "New Password": "Nova contrasenya", - "No content found": "", + "No content found": "No s'ha trobat contingut", "No content to speak": "No hi ha contingut per parlar", "No file selected": "No s'ha escollit cap fitxer", "No HTML, CSS, or JavaScript content found.": "No s'ha trobat contingut HTML, CSS o JavaScript.", @@ -591,7 +591,7 @@ "Seed": "Llavor", "Select a base model": "Seleccionar un model base", "Select a engine": "Seleccionar un motor", - "Select a file to view or drag and drop a file to upload": "", + "Select a file to view or drag and drop a file to upload": "Seleccionar un arxiu o arrossegar un arxiu a pujar", "Select a function": "Seleccionar una funció", "Select a model": "Seleccionar un model", "Select a pipeline": "Seleccionar una Pipeline", @@ -729,8 +729,8 @@ "Update and Copy Link": "Actualitzar i copiar l'enllaç", "Update for the latest features and improvements.": "Actualitza per a les darreres característiques i millores.", "Update password": "Actualitzar la contrasenya", - "Updated": "", - "Updated at": "Actualitzat", + "Updated": "Actualitzat", + "Updated at": "Actualitzat el", "Upload": "Pujar", "Upload a GGUF model": "Pujar un model GGUF", "Upload directory": "Pujar directori", From 6f07afc79bb8d10d596ff565d2bd38724a6b41f7 Mon Sep 17 00:00:00 2001 From: "Timothy J. Baek" Date: Mon, 14 Oct 2024 12:46:05 -0700 Subject: [PATCH 02/53] refac: styling --- src/lib/components/workspace/Functions.svelte | 37 ++--- src/lib/components/workspace/Knowledge.svelte | 28 ++-- src/lib/components/workspace/Models.svelte | 48 ++++--- src/lib/components/workspace/Prompts.svelte | 29 ++-- src/lib/components/workspace/Tools.svelte | 36 ++--- src/routes/(app)/admin/+layout.svelte | 56 ++++---- src/routes/(app)/workspace/+layout.svelte | 127 +++++++++--------- 7 files changed, 196 insertions(+), 165 deletions(-) diff --git a/src/lib/components/workspace/Functions.svelte b/src/lib/components/workspace/Functions.svelte index 2618b25b34..990b06428d 100644 --- a/src/lib/components/workspace/Functions.svelte +++ b/src/lib/components/workspace/Functions.svelte @@ -47,6 +47,14 @@ let showDeleteConfirm = false; + let filteredItems = []; + $: filteredItems = $functions.filter( + (f) => + query === '' || + f.name.toLowerCase().includes(query.toLowerCase()) || + f.id.toLowerCase().includes(query.toLowerCase()) + ); + const shareHandler = async (func) => { const item = await getFunctionById(localStorage.token, func.id).catch((error) => { toast.error(error); @@ -174,17 +182,7 @@ -
-
-
- {$i18n.t('Functions')} -
- {$functions.length} -
-
-
- -
+
-
+ +
+
+
+ {$i18n.t('Functions')} +
+ {filteredItems.length} +
+
+
- {#each $functions.filter((f) => query === '' || f.name - .toLowerCase() - .includes(query.toLowerCase()) || f.id.toLowerCase().includes(query.toLowerCase())) as func} + {#each filteredItems as func}
diff --git a/src/lib/components/workspace/Knowledge.svelte b/src/lib/components/workspace/Knowledge.svelte index 6ed4864e84..7053c845a2 100644 --- a/src/lib/components/workspace/Knowledge.svelte +++ b/src/lib/components/workspace/Knowledge.svelte @@ -72,17 +72,7 @@ }} /> -
-
-
- {$i18n.t('Knowledge')} -
- {$knowledge.length} -
-
-
- -
+
-
+
+
+
+ {$i18n.t('Knowledge')} +
+ {filteredItems.length} +
+
+
-
+
{#each filteredItems as item}
-
{$i18n.t('Admin Panel')}
+ + +
- - -
- -
+
diff --git a/src/routes/(app)/workspace/+layout.svelte b/src/routes/(app)/workspace/+layout.svelte index 65cd300a92..fb908543c9 100644 --- a/src/routes/(app)/workspace/+layout.svelte +++ b/src/routes/(app)/workspace/+layout.svelte @@ -1,6 +1,6 @@ From 0b2c7046cd78b20694df80925a7a07d5325c693a Mon Sep 17 00:00:00 2001 From: "Timothy J. Baek" Date: Mon, 14 Oct 2024 15:29:43 -0700 Subject: [PATCH 06/53] fix: archived chats --- backend/open_webui/apps/webui/models/chats.py | 4 +++- src/lib/components/layout/Sidebar.svelte | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/backend/open_webui/apps/webui/models/chats.py b/backend/open_webui/apps/webui/models/chats.py index 509dff9feb..2138723714 100644 --- a/backend/open_webui/apps/webui/models/chats.py +++ b/backend/open_webui/apps/webui/models/chats.py @@ -61,10 +61,12 @@ class ChatModel(BaseModel): class ChatForm(BaseModel): chat: dict + class ChatTitleMessagesForm(BaseModel): title: str messages: list[dict] + class ChatTitleForm(BaseModel): title: str @@ -361,7 +363,7 @@ class ChatTable: with get_db() as db: all_chats = ( db.query(Chat) - .filter_by(user_id=user_id, pinned=True) + .filter_by(user_id=user_id, pinned=True, archived=False) .order_by(Chat.updated_at.desc()) ) return [ChatModel.model_validate(chat) for chat in all_chats] diff --git a/src/lib/components/layout/Sidebar.svelte b/src/lib/components/layout/Sidebar.svelte index ed7107c6fd..4ac597bd29 100644 --- a/src/lib/components/layout/Sidebar.svelte +++ b/src/lib/components/layout/Sidebar.svelte @@ -311,6 +311,7 @@ { + await pinnedChats.set(await getPinnedChatList(localStorage.token)); await chats.set(await getChatList(localStorage.token)); }} /> From 1db1ef7c26b1dc47aa6a95e71d041499ab372aa5 Mon Sep 17 00:00:00 2001 From: "Timothy J. Baek" Date: Mon, 14 Oct 2024 17:31:52 -0700 Subject: [PATCH 07/53] refac: tag search --- backend/open_webui/apps/webui/models/chats.py | 52 +++++ src/lib/components/layout/Sidebar.svelte | 39 +--- .../layout/Sidebar/SearchInput.svelte | 213 ++++++++++++++++++ 3 files changed, 274 insertions(+), 30 deletions(-) create mode 100644 src/lib/components/layout/Sidebar/SearchInput.svelte diff --git a/backend/open_webui/apps/webui/models/chats.py b/backend/open_webui/apps/webui/models/chats.py index 2138723714..76923b05a6 100644 --- a/backend/open_webui/apps/webui/models/chats.py +++ b/backend/open_webui/apps/webui/models/chats.py @@ -389,9 +389,22 @@ class ChatTable: Filters chats based on a search query using Python, allowing pagination using skip and limit. """ search_text = search_text.lower().strip() + if not search_text: return self.get_chat_list_by_user_id(user_id, include_archived, skip, limit) + search_text_words = search_text.split(" ") + + # search_text might contain 'tag:tag_name' format so we need to extract the tag_name, split the search_text and remove the tags + tag_ids = [] + for word in search_text_words: + if word.startswith("tag:"): + tag_id = word[4:] + tag_ids.append(tag_id) + search_text_words.remove(word) + + search_text = " ".join(search_text_words) + with get_db() as db: query = db.query(Chat).filter(Chat.user_id == user_id) @@ -420,6 +433,26 @@ class ChatTable: ) ).params(search_text=search_text) ) + + # Check if there are any tags to filter, it should have all the tags + if tag_ids: + query = query.filter( + and_( + *[ + text( + f""" + EXISTS ( + SELECT 1 + FROM json_each(Chat.meta, '$.tags') AS tag + WHERE tag.value = :tag_id + ) + """ + ).params(tag_id=tag_id) + for tag_id in tag_ids + ] + ) + ) + elif dialect_name == "postgresql": # PostgreSQL relies on proper JSON query for search query = query.filter( @@ -438,6 +471,25 @@ class ChatTable: ) ).params(search_text=search_text) ) + + # Check if there are any tags to filter, it should have all the tags + if tag_ids: + query = query.filter( + and_( + *[ + text( + f""" + EXISTS ( + SELECT 1 + FROM json_array_elements_text(Chat.meta->'tags') AS tag + WHERE tag = :tag_id + ) + """ + ).params(tag_id=tag_id) + for tag_id in tag_ids + ] + ) + ) else: raise NotImplementedError( f"Unsupported dialect: {db.bind.dialect.name}" diff --git a/src/lib/components/layout/Sidebar.svelte b/src/lib/components/layout/Sidebar.svelte index 4ac597bd29..9a78274bbf 100644 --- a/src/lib/components/layout/Sidebar.svelte +++ b/src/lib/components/layout/Sidebar.svelte @@ -48,6 +48,7 @@ import FilesOverlay from '../chat/MessageInput/FilesOverlay.svelte'; import AddFilesPlaceholder from '../AddFilesPlaceholder.svelte'; import { select } from 'd3-selection'; + import SearchInput from './Sidebar/SearchInput.svelte'; const BREAKPOINT = 768; @@ -93,7 +94,7 @@ // once the bottom of the list has been reached (no results) there is no need to continue querying allChatsLoaded = newChatList.length === 0; - await chats.set([...$chats, ...newChatList]); + await chats.set([...($chats ? $chats : []), ...newChatList]); chatListLoading = false; }; @@ -484,35 +485,13 @@
{/if} -
- -
- - {#if $tags.length > 0} + {#if !search && $pinnedChats.length > 0}
diff --git a/src/lib/components/layout/Sidebar/SearchInput.svelte b/src/lib/components/layout/Sidebar/SearchInput.svelte new file mode 100644 index 0000000000..e7825de632 --- /dev/null +++ b/src/lib/components/layout/Sidebar/SearchInput.svelte @@ -0,0 +1,213 @@ + + +
+ + + {#if focused && (filteredOptions.length > 0 || filteredTags.length > 0)} + +
{ + selectedIdx = null; + }} + on:mouseleave={() => { + selectedIdx = 0; + }} + > +
+ {#if filteredTags.length > 0} +
Tags
+ +
+ {#each filteredTags as tag, tagIdx} + + {/each} +
+ {:else if filteredOptions.length > 0} +
Search options
+ +
+ {#each filteredOptions as option, optionIdx} + + {/each} +
+ {/if} +
+
+ {/if} +
From 8eb45acf106e8c8b5d4e3eb16c78bd1a3fd3a29b Mon Sep 17 00:00:00 2001 From: "Timothy J. Baek" Date: Mon, 14 Oct 2024 18:47:41 -0700 Subject: [PATCH 08/53] refac --- backend/open_webui/apps/retrieval/utils.py | 8 ++++---- backend/open_webui/config.py | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/backend/open_webui/apps/retrieval/utils.py b/backend/open_webui/apps/retrieval/utils.py index a53f2b6b3e..22255002fd 100644 --- a/backend/open_webui/apps/retrieval/utils.py +++ b/backend/open_webui/apps/retrieval/utils.py @@ -403,9 +403,7 @@ def get_rag_context( ) contexts.append( - (", ".join(file_names) + ":\n\n") - if file_names - else "" + ((", ".join(file_names) + ":\n\n") if file_names else "") + "\n\n".join( [text for text in context["documents"][0] if text is not None] ) @@ -422,7 +420,9 @@ def get_rag_context( except Exception as e: log.exception(e) - print(contexts, citations) + print("contexts", contexts) + print("citations", citations) + return contexts, citations diff --git a/backend/open_webui/config.py b/backend/open_webui/config.py index d55619ee0d..05b27c4471 100644 --- a/backend/open_webui/config.py +++ b/backend/open_webui/config.py @@ -1042,7 +1042,7 @@ CHUNK_OVERLAP = PersistentConfig( DEFAULT_RAG_TEMPLATE = """You are given a user query, some textual context and rules, all inside xml tags. You have to answer the query based on the context while respecting the rules. -[context] +{{CONTEXT}} @@ -1055,7 +1055,7 @@ DEFAULT_RAG_TEMPLATE = """You are given a user query, some textual context and r -[query] +{{QUERY}} """ From effa77379e085e8f128624a792ff8a16a02c1eea Mon Sep 17 00:00:00 2001 From: "Timothy J. Baek" Date: Mon, 14 Oct 2024 18:53:29 -0700 Subject: [PATCH 09/53] refac --- src/lib/components/layout/Sidebar/SearchInput.svelte | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/components/layout/Sidebar/SearchInput.svelte b/src/lib/components/layout/Sidebar/SearchInput.svelte index e7825de632..08d0efc0ab 100644 --- a/src/lib/components/layout/Sidebar/SearchInput.svelte +++ b/src/lib/components/layout/Sidebar/SearchInput.svelte @@ -157,7 +157,7 @@ ? 'bg-gray-100 dark:bg-gray-900' : ''}" id="search-tag-{tagIdx}" - on:click={async () => { + on:click|stopPropagation={async () => { const words = value.split(' '); words.pop(); @@ -187,7 +187,7 @@ ? 'bg-gray-100 dark:bg-gray-900' : ''}" id="search-option-{optionIdx}" - on:click={async () => { + on:click|stopPropagation={async () => { const words = value.split(' '); words.pop(); From bc758702894f47cc70b8ea9d8e5f1c73f5a03db1 Mon Sep 17 00:00:00 2001 From: "Timothy J. Baek" Date: Mon, 14 Oct 2024 19:02:08 -0700 Subject: [PATCH 10/53] fix: tag search --- backend/open_webui/apps/webui/models/chats.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/backend/open_webui/apps/webui/models/chats.py b/backend/open_webui/apps/webui/models/chats.py index 76923b05a6..473cf964c4 100644 --- a/backend/open_webui/apps/webui/models/chats.py +++ b/backend/open_webui/apps/webui/models/chats.py @@ -396,12 +396,14 @@ class ChatTable: search_text_words = search_text.split(" ") # search_text might contain 'tag:tag_name' format so we need to extract the tag_name, split the search_text and remove the tags - tag_ids = [] - for word in search_text_words: - if word.startswith("tag:"): - tag_id = word[4:] - tag_ids.append(tag_id) - search_text_words.remove(word) + tag_ids = [ + tag_name.replace("tag:", "").replace(" ", "_").lower() + for tag_name in search_text_words + if tag_name.startswith("tag:") + ] + search_text_words = [ + word for word in search_text_words if not word.startswith("tag:") + ] search_text = " ".join(search_text_words) From 90b7754cd6d387f8ca742ca7a065276d5f22ca6f Mon Sep 17 00:00:00 2001 From: "Timothy J. Baek" Date: Mon, 14 Oct 2024 19:33:32 -0700 Subject: [PATCH 11/53] refac: collapsible pinned chat list --- src/lib/components/layout/Sidebar.svelte | 138 +++++++++--------- .../layout/Sidebar/SearchInput.svelte | 3 +- 2 files changed, 68 insertions(+), 73 deletions(-) diff --git a/src/lib/components/layout/Sidebar.svelte b/src/lib/components/layout/Sidebar.svelte index 9a78274bbf..1f0f3bf79a 100644 --- a/src/lib/components/layout/Sidebar.svelte +++ b/src/lib/components/layout/Sidebar.svelte @@ -49,6 +49,9 @@ import AddFilesPlaceholder from '../AddFilesPlaceholder.svelte'; import { select } from 'd3-selection'; import SearchInput from './Sidebar/SearchInput.svelte'; + import ChevronDown from '../icons/ChevronDown.svelte'; + import ChevronUp from '../icons/ChevronUp.svelte'; + import ChevronRight from '../icons/ChevronRight.svelte'; const BREAKPOINT = 768; @@ -65,6 +68,8 @@ let selectedTagName = null; + let showPinnedChat = true; + // Pagination variables let chatListLoading = false; let allChatsLoaded = false; @@ -257,6 +262,8 @@ }; onMount(async () => { + showPinnedChat = localStorage?.showPinnedChat ? localStorage.showPinnedChat === 'true' : true; + mobile.subscribe((e) => { if ($showSidebar && e) { showSidebar.set(false); @@ -476,11 +483,7 @@
{/if} -
+
{#if $temporaryChatEnabled}
{/if} @@ -490,78 +493,71 @@ on:input={searchDebounceHandler} placeholder={$i18n.t('Search')} /> +
- +
+ {#if $temporaryChatEnabled} +
+ {/if} {#if !search && $pinnedChats.length > 0} -
+
-
- {$i18n.t('Pinned')} +
+
- {#each $pinnedChats as chat, idx} - { - selectedChatId = chat.id; - }} - on:unselect={() => { - selectedChatId = null; - }} - on:delete={(e) => { - if ((e?.detail ?? '') === 'shift') { - deleteChatHandler(chat.id); - } else { - deleteChat = chat; - showDeleteConfirm = true; - } - }} - on:tag={(e) => { - const { type, name } = e.detail; - tagEventHandler(type, name, chat.id); - }} - /> - {/each} + {#if showPinnedChat} +
+ {#each $pinnedChats as chat, idx} + { + selectedChatId = chat.id; + }} + on:unselect={() => { + selectedChatId = null; + }} + on:delete={(e) => { + if ((e?.detail ?? '') === 'shift') { + deleteChatHandler(chat.id); + } else { + deleteChat = chat; + showDeleteConfirm = true; + } + }} + on:tag={(e) => { + const { type, name } = e.detail; + tagEventHandler(type, name, chat.id); + }} + /> + {/each} +
+ {/if}
{/if} diff --git a/src/lib/components/layout/Sidebar/SearchInput.svelte b/src/lib/components/layout/Sidebar/SearchInput.svelte index 08d0efc0ab..04af4109d2 100644 --- a/src/lib/components/layout/Sidebar/SearchInput.svelte +++ b/src/lib/components/layout/Sidebar/SearchInput.svelte @@ -71,7 +71,7 @@ }); -
+
diff --git a/src/lib/components/chat/Messages/ResponseMessage.svelte b/src/lib/components/chat/Messages/ResponseMessage.svelte index ad49697de6..9676835e49 100644 --- a/src/lib/components/chat/Messages/ResponseMessage.svelte +++ b/src/lib/components/chat/Messages/ResponseMessage.svelte @@ -1103,7 +1103,7 @@ { + on:save={(e) => { dispatch('save', { ...message, annotation: { From f8bb77324db16d36c7a10be9d3f0fb729762b1ad Mon Sep 17 00:00:00 2001 From: "Timothy J. Baek" Date: Mon, 14 Oct 2024 20:36:16 -0700 Subject: [PATCH 13/53] refac: styling --- src/lib/components/layout/Sidebar.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/components/layout/Sidebar.svelte b/src/lib/components/layout/Sidebar.svelte index 1f0f3bf79a..0caadd6492 100644 --- a/src/lib/components/layout/Sidebar.svelte +++ b/src/lib/components/layout/Sidebar.svelte @@ -509,7 +509,7 @@
{:else} - -
- - {#if showPinnedChat} -
- {#each $pinnedChats as chat, idx} - { - selectedChatId = chat.id; - }} - on:unselect={() => { - selectedChatId = null; - }} - on:delete={(e) => { - if ((e?.detail ?? '') === 'shift') { - deleteChatHandler(chat.id); - } else { - deleteChat = chat; - showDeleteConfirm = true; - } - }} - on:tag={(e) => { - const { type, name } = e.detail; - tagEventHandler(type, name, chat.id); - }} - /> - {/each} +
+ {$i18n.t('Pinned')} +
+
- {/if} + +
+
+ {#each $pinnedChats as chat, idx} + { + selectedChatId = chat.id; + }} + on:unselect={() => { + selectedChatId = null; + }} + on:delete={(e) => { + if ((e?.detail ?? '') === 'shift') { + deleteChatHandler(chat.id); + } else { + deleteChat = chat; + showDeleteConfirm = true; + } + }} + on:tag={(e) => { + const { type, name } = e.detail; + tagEventHandler(type, name, chat.id); + }} + /> + {/each} +
+
+
{/if} From d8a30bd6ae5cb6d74f66dc466dbe93c52da13cfa Mon Sep 17 00:00:00 2001 From: "Timothy J. Baek" Date: Mon, 14 Oct 2024 21:21:45 -0700 Subject: [PATCH 17/53] refac: sidebar tag add behaviour --- backend/open_webui/apps/webui/models/chats.py | 16 +++++++++++----- src/lib/components/chat/Tags.svelte | 5 ++++- src/lib/components/layout/Sidebar.svelte | 5 ++++- .../components/layout/Sidebar/ChatMenu.svelte | 7 +++++++ 4 files changed, 26 insertions(+), 7 deletions(-) diff --git a/backend/open_webui/apps/webui/models/chats.py b/backend/open_webui/apps/webui/models/chats.py index 473cf964c4..33cab3f184 100644 --- a/backend/open_webui/apps/webui/models/chats.py +++ b/backend/open_webui/apps/webui/models/chats.py @@ -257,11 +257,15 @@ class ChatTable: query = db.query(Chat).filter_by(user_id=user_id) if not include_archived: query = query.filter_by(archived=False) - all_chats = ( - query.order_by(Chat.updated_at.desc()) - # .limit(limit).offset(skip) - .all() - ) + + query = query.order_by(Chat.updated_at.desc()) + + if skip: + query = query.offset(skip) + if limit: + query = query.limit(limit) + + all_chats = query.all() return [ChatModel.model_validate(chat) for chat in all_chats] def get_chat_title_id_list_by_user_id( @@ -500,6 +504,8 @@ class ChatTable: # Perform pagination at the SQL level all_chats = query.offset(skip).limit(limit).all() + print(len(all_chats)) + # Validate and return chats return [ChatModel.model_validate(chat) for chat in all_chats] diff --git a/src/lib/components/chat/Tags.svelte b/src/lib/components/chat/Tags.svelte index 7c5a0c0c1a..1ce3bc6586 100644 --- a/src/lib/components/chat/Tags.svelte +++ b/src/lib/components/chat/Tags.svelte @@ -37,7 +37,10 @@ tags: tags }); - _tags.set(await getAllChatTags(localStorage.token)); + await _tags.set(await getAllChatTags(localStorage.token)); + dispatch('add', { + name: tagName + }); }; const deleteTag = async (tagName) => { diff --git a/src/lib/components/layout/Sidebar.svelte b/src/lib/components/layout/Sidebar.svelte index 1e0cea0ae0..583624a289 100644 --- a/src/lib/components/layout/Sidebar.svelte +++ b/src/lib/components/layout/Sidebar.svelte @@ -179,7 +179,10 @@ console.log(type, tagName, chatId); if (type === 'delete') { currentChatPage.set(1); - await chats.set(await getChatListBySearchText(localStorage.token, search)); + await chats.set(await getChatListBySearchText(localStorage.token, search, $currentChatPage)); + } else if (type === 'add') { + currentChatPage.set(1); + await chats.set(await getChatListBySearchText(localStorage.token, search, $currentChatPage)); } }; diff --git a/src/lib/components/layout/Sidebar/ChatMenu.svelte b/src/lib/components/layout/Sidebar/ChatMenu.svelte index 0522c24461..306cd36336 100644 --- a/src/lib/components/layout/Sidebar/ChatMenu.svelte +++ b/src/lib/components/layout/Sidebar/ChatMenu.svelte @@ -141,6 +141,13 @@
{ + dispatch('tag', { + type: 'add', + name: e.detail.name + }); + show = false; + }} on:delete={(e) => { dispatch('tag', { type: 'delete', From 6703cacb999080be0831121874570c22dd9d92a0 Mon Sep 17 00:00:00 2001 From: "Timothy J. Baek" Date: Mon, 14 Oct 2024 22:57:11 -0700 Subject: [PATCH 18/53] fix: tag unarchive/archive issue --- backend/open_webui/apps/webui/models/chats.py | 21 +++++----- .../open_webui/apps/webui/routers/chats.py | 38 ++++++++++++++++++- src/lib/apis/chats/index.ts | 2 +- src/lib/components/chat/Tags.svelte | 6 +-- src/lib/components/layout/Sidebar.svelte | 13 +++++-- .../layout/Sidebar/ArchivedChatsModal.svelte | 2 - .../components/layout/Sidebar/ChatItem.svelte | 5 ++- .../components/layout/Sidebar/ChatMenu.svelte | 11 ++---- src/routes/(app)/+layout.svelte | 4 +- 9 files changed, 72 insertions(+), 30 deletions(-) diff --git a/backend/open_webui/apps/webui/models/chats.py b/backend/open_webui/apps/webui/models/chats.py index 33cab3f184..9032d2e357 100644 --- a/backend/open_webui/apps/webui/models/chats.py +++ b/backend/open_webui/apps/webui/models/chats.py @@ -401,10 +401,11 @@ class ChatTable: # search_text might contain 'tag:tag_name' format so we need to extract the tag_name, split the search_text and remove the tags tag_ids = [ - tag_name.replace("tag:", "").replace(" ", "_").lower() - for tag_name in search_text_words - if tag_name.startswith("tag:") + word.replace("tag:", "").replace(" ", "_").lower() + for word in search_text_words + if word.startswith("tag:") ] + search_text_words = [ word for word in search_text_words if not word.startswith("tag:") ] @@ -450,11 +451,11 @@ class ChatTable: EXISTS ( SELECT 1 FROM json_each(Chat.meta, '$.tags') AS tag - WHERE tag.value = :tag_id + WHERE tag.value = :tag_id_{tag_idx} ) """ - ).params(tag_id=tag_id) - for tag_id in tag_ids + ).params(**{f"tag_id_{tag_idx}": tag_id}) + for tag_idx, tag_id in enumerate(tag_ids) ] ) ) @@ -488,11 +489,11 @@ class ChatTable: EXISTS ( SELECT 1 FROM json_array_elements_text(Chat.meta->'tags') AS tag - WHERE tag = :tag_id + WHERE tag = :tag_id_{tag_idx} ) """ - ).params(tag_id=tag_id) - for tag_id in tag_ids + ).params(**{f"tag_id_{tag_idx}": tag_id}) + for tag_idx, tag_id in enumerate(tag_ids) ] ) ) @@ -571,7 +572,7 @@ class ChatTable: def count_chats_by_tag_name_and_user_id(self, tag_name: str, user_id: str) -> int: with get_db() as db: # Assuming `get_db()` returns a session object - query = db.query(Chat).filter_by(user_id=user_id) + query = db.query(Chat).filter_by(user_id=user_id, archived=False) # Normalize the tag_name for consistency tag_id = tag_name.replace(" ", "_").lower() diff --git a/backend/open_webui/apps/webui/routers/chats.py b/backend/open_webui/apps/webui/routers/chats.py index b919d14473..a91d1e11d2 100644 --- a/backend/open_webui/apps/webui/routers/chats.py +++ b/backend/open_webui/apps/webui/routers/chats.py @@ -114,13 +114,24 @@ async def search_user_chats( limit = 60 skip = (page - 1) * limit - return [ + chat_list = [ ChatTitleIdResponse(**chat.model_dump()) for chat in Chats.get_chats_by_user_id_and_search_text( user.id, text, skip=skip, limit=limit ) ] + # Delete tag if no chat is found + words = text.strip().split(" ") + if page == 1 and len(words) == 1 and words[0].startswith("tag:"): + tag_id = words[0].replace("tag:", "") + if len(chat_list) == 0: + if Tags.get_tag_by_name_and_user_id(tag_id, user.id): + log.debug(f"deleting tag: {tag_id}") + Tags.delete_tag_by_name_and_user_id(tag_id, user.id) + + return chat_list + ############################ # GetPinnedChats @@ -315,7 +326,13 @@ async def update_chat_by_id( @router.delete("/{id}", response_model=bool) async def delete_chat_by_id(request: Request, id: str, user=Depends(get_verified_user)): if user.role == "admin": + chat = Chats.get_chat_by_id(id) + for tag in chat.meta.get("tags", []): + if Chats.count_chats_by_tag_name_and_user_id(tag, user.id) == 0: + Tags.delete_tag_by_name_and_user_id(tag, user.id) + result = Chats.delete_chat_by_id(id) + return result else: if not request.app.state.config.USER_PERMISSIONS.get("chat", {}).get( @@ -326,6 +343,11 @@ async def delete_chat_by_id(request: Request, id: str, user=Depends(get_verified detail=ERROR_MESSAGES.ACCESS_PROHIBITED, ) + chat = Chats.get_chat_by_id(id) + for tag in chat.meta.get("tags", []): + if Chats.count_chats_by_tag_name_and_user_id(tag, user.id) == 0: + Tags.delete_tag_by_name_and_user_id(tag, user.id) + result = Chats.delete_chat_by_id_and_user_id(id, user.id) return result @@ -397,6 +419,20 @@ async def archive_chat_by_id(id: str, user=Depends(get_verified_user)): chat = Chats.get_chat_by_id_and_user_id(id, user.id) if chat: chat = Chats.toggle_chat_archive_by_id(id) + + # Delete tags if chat is archived + if chat.archived: + for tag_id in chat.meta.get("tags", []): + if Chats.count_chats_by_tag_name_and_user_id(tag_id, user.id) == 0: + log.debug(f"deleting tag: {tag_id}") + Tags.delete_tag_by_name_and_user_id(tag_id, user.id) + else: + for tag_id in chat.meta.get("tags", []): + tag = Tags.get_tag_by_name_and_user_id(tag_id, user.id) + if tag is None: + log.debug(f"inserting tag: {tag_id}") + tag = Tags.insert_new_tag(tag_id, user.id) + return ChatResponse(**chat.model_dump()) else: raise HTTPException( diff --git a/src/lib/apis/chats/index.ts b/src/lib/apis/chats/index.ts index ff89fdf43e..6056f6dbfb 100644 --- a/src/lib/apis/chats/index.ts +++ b/src/lib/apis/chats/index.ts @@ -267,7 +267,7 @@ export const getAllUserChats = async (token: string) => { return res; }; -export const getAllChatTags = async (token: string) => { +export const getAllTags = async (token: string) => { let error = null; const res = await fetch(`${WEBUI_API_BASE_URL}/chats/all/tags`, { diff --git a/src/lib/components/chat/Tags.svelte b/src/lib/components/chat/Tags.svelte index 1ce3bc6586..f71ff0af99 100644 --- a/src/lib/components/chat/Tags.svelte +++ b/src/lib/components/chat/Tags.svelte @@ -2,7 +2,7 @@ import { addTagById, deleteTagById, - getAllChatTags, + getAllTags, getChatList, getChatListByTagName, getTagsById, @@ -37,7 +37,7 @@ tags: tags }); - await _tags.set(await getAllChatTags(localStorage.token)); + await _tags.set(await getAllTags(localStorage.token)); dispatch('add', { name: tagName }); @@ -50,7 +50,7 @@ tags: tags }); - await _tags.set(await getAllChatTags(localStorage.token)); + await _tags.set(await getAllTags(localStorage.token)); dispatch('delete', { name: tagName }); diff --git a/src/lib/components/layout/Sidebar.svelte b/src/lib/components/layout/Sidebar.svelte index 583624a289..cfe5b99763 100644 --- a/src/lib/components/layout/Sidebar.svelte +++ b/src/lib/components/layout/Sidebar.svelte @@ -30,7 +30,7 @@ getChatById, getChatListByTagName, updateChatById, - getAllChatTags, + getAllTags, archiveChatById, cloneChatById, getChatListBySearchText, @@ -77,6 +77,8 @@ const initChatList = async () => { // Reset pagination variables + tags.set(await getAllTags(localStorage.token)); + currentChatPage.set(1); allChatsLoaded = false; await chats.set(await getChatList(localStorage.token, $currentChatPage)); @@ -123,6 +125,10 @@ searchDebounceTimeout = setTimeout(async () => { currentChatPage.set(1); await chats.set(await getChatListBySearchText(localStorage.token, search)); + + if ($chats.length === 0) { + tags.set(await getAllTags(localStorage.token)); + } }, 1000); } }; @@ -134,6 +140,8 @@ }); if (res) { + tags.set(await getAllTags(localStorage.token)); + if ($chatId === id) { await chatId.set(''); await tick(); @@ -143,7 +151,6 @@ allChatsLoaded = false; currentChatPage.set(1); await chats.set(await getChatList(localStorage.token, $currentChatPage)); - await pinnedChats.set(await getPinnedChatList(localStorage.token)); } }; @@ -324,7 +331,7 @@ bind:show={$showArchivedChats} on:change={async () => { await pinnedChats.set(await getPinnedChatList(localStorage.token)); - await chats.set(await getChatList(localStorage.token)); + await initChatList(); }} /> diff --git a/src/lib/components/layout/Sidebar/ArchivedChatsModal.svelte b/src/lib/components/layout/Sidebar/ArchivedChatsModal.svelte index 80e3f15790..b2ae110583 100644 --- a/src/lib/components/layout/Sidebar/ArchivedChatsModal.svelte +++ b/src/lib/components/layout/Sidebar/ArchivedChatsModal.svelte @@ -15,7 +15,6 @@ getArchivedChatList } from '$lib/apis/chats'; import Tooltip from '$lib/components/common/Tooltip.svelte'; - const i18n = getContext('i18n'); export let show = false; @@ -30,7 +29,6 @@ }); chats = await getArchivedChatList(localStorage.token); - dispatch('change'); }; diff --git a/src/lib/components/layout/Sidebar/ChatItem.svelte b/src/lib/components/layout/Sidebar/ChatItem.svelte index 8356b24cce..ff96ed05f6 100644 --- a/src/lib/components/layout/Sidebar/ChatItem.svelte +++ b/src/lib/components/layout/Sidebar/ChatItem.svelte @@ -10,6 +10,7 @@ archiveChatById, cloneChatById, deleteChatById, + getAllTags, getChatList, getChatListByTagName, getPinnedChatList, @@ -22,7 +23,8 @@ mobile, pinnedChats, showSidebar, - currentChatPage + currentChatPage, + tags } from '$lib/stores'; import ChatMenu from './ChatMenu.svelte'; @@ -77,6 +79,7 @@ const archiveChatHandler = async (id) => { await archiveChatById(localStorage.token, id); + tags.set(await getAllTags(localStorage.token)); currentChatPage.set(1); await chats.set(await getChatList(localStorage.token, $currentChatPage)); diff --git a/src/lib/components/layout/Sidebar/ChatMenu.svelte b/src/lib/components/layout/Sidebar/ChatMenu.svelte index 306cd36336..38d0f1182c 100644 --- a/src/lib/components/layout/Sidebar/ChatMenu.svelte +++ b/src/lib/components/layout/Sidebar/ChatMenu.svelte @@ -15,13 +15,8 @@ import DocumentDuplicate from '$lib/components/icons/DocumentDuplicate.svelte'; import Bookmark from '$lib/components/icons/Bookmark.svelte'; import BookmarkSlash from '$lib/components/icons/BookmarkSlash.svelte'; - import { - addTagById, - deleteTagById, - getChatPinnedStatusById, - getTagsById, - toggleChatPinnedStatusById - } from '$lib/apis/chats'; + import { getChatPinnedStatusById, toggleChatPinnedStatusById } from '$lib/apis/chats'; + import { chats } from '$lib/stores'; const i18n = getContext('i18n'); @@ -146,6 +141,7 @@ type: 'add', name: e.detail.name }); + show = false; }} on:delete={(e) => { @@ -153,6 +149,7 @@ type: 'delete', name: e.detail.name }); + show = false; }} on:close={() => { diff --git a/src/routes/(app)/+layout.svelte b/src/routes/(app)/+layout.svelte index bd711470ac..2cc9b5c3d4 100644 --- a/src/routes/(app)/+layout.svelte +++ b/src/routes/(app)/+layout.svelte @@ -13,7 +13,7 @@ import { getKnowledgeItems } from '$lib/apis/knowledge'; import { getFunctions } from '$lib/apis/functions'; import { getModels as _getModels, getVersionUpdates } from '$lib/apis'; - import { getAllChatTags } from '$lib/apis/chats'; + import { getAllTags } from '$lib/apis/chats'; import { getPrompts } from '$lib/apis/prompts'; import { getTools } from '$lib/apis/tools'; import { getBanners } from '$lib/apis/configs'; @@ -117,7 +117,7 @@ banners.set(await getBanners(localStorage.token)); })(), (async () => { - tags.set(await getAllChatTags(localStorage.token)); + tags.set(await getAllTags(localStorage.token)); })() ]); From 9d2d53bfb5e4054605fe27d15535486a019a105c Mon Sep 17 00:00:00 2001 From: "Timothy J. Baek" Date: Mon, 14 Oct 2024 22:59:17 -0700 Subject: [PATCH 19/53] fix --- backend/open_webui/apps/webui/routers/chats.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/open_webui/apps/webui/routers/chats.py b/backend/open_webui/apps/webui/routers/chats.py index a91d1e11d2..2e3afcdfea 100644 --- a/backend/open_webui/apps/webui/routers/chats.py +++ b/backend/open_webui/apps/webui/routers/chats.py @@ -328,7 +328,7 @@ async def delete_chat_by_id(request: Request, id: str, user=Depends(get_verified if user.role == "admin": chat = Chats.get_chat_by_id(id) for tag in chat.meta.get("tags", []): - if Chats.count_chats_by_tag_name_and_user_id(tag, user.id) == 0: + if Chats.count_chats_by_tag_name_and_user_id(tag, user.id) == 1: Tags.delete_tag_by_name_and_user_id(tag, user.id) result = Chats.delete_chat_by_id(id) @@ -345,7 +345,7 @@ async def delete_chat_by_id(request: Request, id: str, user=Depends(get_verified chat = Chats.get_chat_by_id(id) for tag in chat.meta.get("tags", []): - if Chats.count_chats_by_tag_name_and_user_id(tag, user.id) == 0: + if Chats.count_chats_by_tag_name_and_user_id(tag, user.id) == 1: Tags.delete_tag_by_name_and_user_id(tag, user.id) result = Chats.delete_chat_by_id_and_user_id(id, user.id) From cf24a65caa6b5602a73dbb6cc83dfbfb976286ef Mon Sep 17 00:00:00 2001 From: "Timothy J. Baek" Date: Mon, 14 Oct 2024 23:04:10 -0700 Subject: [PATCH 20/53] fix: duplicate tags --- backend/open_webui/apps/webui/models/chats.py | 4 ++-- .../migrations/versions/1af9b942657b_migrate_tags.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/open_webui/apps/webui/models/chats.py b/backend/open_webui/apps/webui/models/chats.py index 9032d2e357..15501e49a1 100644 --- a/backend/open_webui/apps/webui/models/chats.py +++ b/backend/open_webui/apps/webui/models/chats.py @@ -561,7 +561,7 @@ class ChatTable: if tag_id not in chat.meta.get("tags", []): chat.meta = { **chat.meta, - "tags": chat.meta.get("tags", []) + [tag_id], + "tags": list(set(chat.meta.get("tags", []) + [tag_id])), } db.commit() @@ -618,7 +618,7 @@ class ChatTable: tags = [tag for tag in tags if tag != tag_id] chat.meta = { **chat.meta, - "tags": tags, + "tags": list(set(tags)), } db.commit() return True diff --git a/backend/open_webui/migrations/versions/1af9b942657b_migrate_tags.py b/backend/open_webui/migrations/versions/1af9b942657b_migrate_tags.py index 9d79b57495..8a0ab1b491 100644 --- a/backend/open_webui/migrations/versions/1af9b942657b_migrate_tags.py +++ b/backend/open_webui/migrations/versions/1af9b942657b_migrate_tags.py @@ -135,7 +135,7 @@ def upgrade(): tags = chat_updates[chat_id]["meta"].get("tags", []) tags.append(tag_name) - chat_updates[chat_id]["meta"]["tags"] = tags + chat_updates[chat_id]["meta"]["tags"] = list(set(tags)) # Update chats based on accumulated changes for chat_id, updates in chat_updates.items(): From 73a251fc49e9bfdf8725cb4a5de5c30c5a9388e9 Mon Sep 17 00:00:00 2001 From: "Timothy J. Baek" Date: Mon, 14 Oct 2024 23:55:50 -0700 Subject: [PATCH 21/53] enh: drag ghost --- src/lib/components/common/DragGhost.svelte | 29 +++++++++ .../components/layout/Sidebar/ChatItem.svelte | 64 ++++++++++++++++++- 2 files changed, 91 insertions(+), 2 deletions(-) create mode 100644 src/lib/components/common/DragGhost.svelte diff --git a/src/lib/components/common/DragGhost.svelte b/src/lib/components/common/DragGhost.svelte new file mode 100644 index 0000000000..a7f9c29348 --- /dev/null +++ b/src/lib/components/common/DragGhost.svelte @@ -0,0 +1,29 @@ + + + + + +
+ +
diff --git a/src/lib/components/layout/Sidebar/ChatItem.svelte b/src/lib/components/layout/Sidebar/ChatItem.svelte index ff96ed05f6..bebc637855 100644 --- a/src/lib/components/layout/Sidebar/ChatItem.svelte +++ b/src/lib/components/layout/Sidebar/ChatItem.svelte @@ -1,7 +1,7 @@ -
+{#if drag && x && y} + +
+
+
+ {chat.title} +
+
+
+
+{/if} + +
{#if confirmEdit}
Date: Tue, 15 Oct 2024 00:35:14 -0700 Subject: [PATCH 22/53] refac: folder --- src/lib/components/common/Collapsible.svelte | 5 ++ src/lib/components/common/Folder.svelte | 56 ++++++++++++ src/lib/components/layout/Sidebar.svelte | 90 ++++++++----------- .../components/layout/Sidebar/ChatItem.svelte | 9 +- .../layout/Sidebar/SearchInput.svelte | 4 - 5 files changed, 101 insertions(+), 63 deletions(-) create mode 100644 src/lib/components/common/Folder.svelte diff --git a/src/lib/components/common/Collapsible.svelte b/src/lib/components/common/Collapsible.svelte index fea21151fc..6a9d1ff19a 100644 --- a/src/lib/components/common/Collapsible.svelte +++ b/src/lib/components/common/Collapsible.svelte @@ -1,4 +1,9 @@ + +
+ { + dispatch('change', e.detail); + }} + > + +
{ + e.stopPropagation(); + }} + on:drop={(e) => { + console.log('Dropped on the Button'); + }} + on:dragleave={(e) => {}} + > + +
+ +
+ +
+
+
diff --git a/src/lib/components/layout/Sidebar.svelte b/src/lib/components/layout/Sidebar.svelte index cfe5b99763..8372d77b13 100644 --- a/src/lib/components/layout/Sidebar.svelte +++ b/src/lib/components/layout/Sidebar.svelte @@ -53,6 +53,7 @@ import ChevronUp from '../icons/ChevronUp.svelte'; import ChevronRight from '../icons/ChevronRight.svelte'; import Collapsible from '../common/Collapsible.svelte'; + import Folder from '../common/Folder.svelte'; const BREAKPOINT = 768; @@ -517,61 +518,42 @@ {#if !search && $pinnedChats.length > 0}
-
- -
- -
- -
-
- {#each $pinnedChats as chat, idx} - { - selectedChatId = chat.id; - }} - on:unselect={() => { - selectedChatId = null; - }} - on:delete={(e) => { - if ((e?.detail ?? '') === 'shift') { - deleteChatHandler(chat.id); - } else { - deleteChat = chat; - showDeleteConfirm = true; - } - }} - on:tag={(e) => { - const { type, name } = e.detail; - tagEventHandler(type, name, chat.id); - }} - /> - {/each} -
-
-
-
+ on:unselect={() => { + selectedChatId = null; + }} + on:delete={(e) => { + if ((e?.detail ?? '') === 'shift') { + deleteChatHandler(chat.id); + } else { + deleteChat = chat; + showDeleteConfirm = true; + } + }} + on:tag={(e) => { + const { type, name } = e.detail; + tagEventHandler(type, name, chat.id); + }} + /> + {/each} +
+
{/if} diff --git a/src/lib/components/layout/Sidebar/ChatItem.svelte b/src/lib/components/layout/Sidebar/ChatItem.svelte index bebc637855..7682dd3c56 100644 --- a/src/lib/components/layout/Sidebar/ChatItem.svelte +++ b/src/lib/components/layout/Sidebar/ChatItem.svelte @@ -98,10 +98,10 @@ let y = 0; const dragImage = new Image(); - dragImage.src = 'data:image/gif;base64,R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs='; + dragImage.src = + 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII='; const onDragStart = (event) => { - console.log('Dragging started:', event.target); event.dataTransfer.setDragImage(dragImage, 0, 0); drag = true; @@ -114,7 +114,6 @@ }; const onDragEnd = (event) => { - console.log('Dragging ended:', event.target); drag = false; itemElement.style.opacity = '1'; // Reset visual cue after drag }; @@ -143,9 +142,9 @@ {#if drag && x && y} -
+
-
+
{chat.title}
diff --git a/src/lib/components/layout/Sidebar/SearchInput.svelte b/src/lib/components/layout/Sidebar/SearchInput.svelte index 04af4109d2..5e8213308c 100644 --- a/src/lib/components/layout/Sidebar/SearchInput.svelte +++ b/src/lib/components/layout/Sidebar/SearchInput.svelte @@ -51,10 +51,6 @@ const chatSearch = document.getElementById('chat-search'); if (!searchContainer.contains(e.target) && !chatSearch.contains(e.target)) { - console.log( - e.target.id, - e.target.id.startsWith('search-tag-') || e.target.id.startsWith('search-option-') - ); if (e.target.id.startsWith('search-tag-') || e.target.id.startsWith('search-option-')) { return; } From 2f4c04055ce05f0610a949b5bcd6d056dc4f781a Mon Sep 17 00:00:00 2001 From: "Timothy J. Baek" Date: Tue, 15 Oct 2024 02:12:39 -0700 Subject: [PATCH 23/53] enh: drag and drop chat to pin --- src/lib/components/common/DragGhost.svelte | 7 +- src/lib/components/common/Folder.svelte | 120 +++++++++----- src/lib/components/layout/Sidebar.svelte | 153 +++++++++++------- .../components/layout/Sidebar/ChatItem.svelte | 16 +- 4 files changed, 195 insertions(+), 101 deletions(-) diff --git a/src/lib/components/common/DragGhost.svelte b/src/lib/components/common/DragGhost.svelte index a7f9c29348..6d97a45174 100644 --- a/src/lib/components/common/DragGhost.svelte +++ b/src/lib/components/common/DragGhost.svelte @@ -22,8 +22,9 @@
- +
+ +
diff --git a/src/lib/components/common/Folder.svelte b/src/lib/components/common/Folder.svelte index a9883520e7..fe356c187a 100644 --- a/src/lib/components/common/Folder.svelte +++ b/src/lib/components/common/Folder.svelte @@ -1,5 +1,5 @@ -
- { - dispatch('change', e.detail); - }} - > - +
+ {#if dragged}
{ - e.stopPropagation(); + class="absolute top-0 left-0 w-full h-full rounded-sm bg-gray-200 bg-opacity-50 dark:bg-opacity-10 z-50 pointer-events-none touch-none" + >
+ {/if} + + {#if collapsible} + { + dispatch('change', e.detail); }} - on:drop={(e) => { - console.log('Dropped on the Button'); - }} - on:dragleave={(e) => {}} > - -
+
+ {name} +
+ +
-
- -
- +
+ +
+ + {:else} + + {/if}
diff --git a/src/lib/components/layout/Sidebar.svelte b/src/lib/components/layout/Sidebar.svelte index 8372d77b13..c3d3103207 100644 --- a/src/lib/components/layout/Sidebar.svelte +++ b/src/lib/components/layout/Sidebar.svelte @@ -35,7 +35,9 @@ cloneChatById, getChatListBySearchText, createNewChat, - getPinnedChatList + getPinnedChatList, + toggleChatPinnedStatusById, + getChatPinnedStatusById } from '$lib/apis/chats'; import { WEBUI_BASE_URL } from '$lib/constants'; @@ -363,8 +365,8 @@ bind:this={navElement} id="sidebar" class="h-screen max-h-[100dvh] min-h-screen select-none {$showSidebar - ? 'md:relative w-[260px]' - : '-translate-x-[260px] w-[0px]'} bg-gray-50 text-gray-900 dark:bg-gray-950 dark:text-gray-200 text-sm transition fixed z-50 top-0 left-0 + ? 'md:relative w-[260px] max-w-[260px]' + : '-translate-x-[260px] w-[0px]'} bg-gray-50 text-gray-900 dark:bg-gray-950 dark:text-gray-200 text-sm transition fixed z-50 top-0 left-0 overflow-x-hidden " data-state={$showSidebar} > @@ -381,7 +383,7 @@
{/if}
@@ -517,13 +519,27 @@ {/if} {#if !search && $pinnedChats.length > 0} -
+
{ localStorage.setItem('showPinnedChat', e.detail); console.log(e.detail); }} + on:drop={async (e) => { + const { id } = e.detail; + + const status = await getChatPinnedStatusById(localStorage.token, id); + + if (!status) { + const res = await toggleChatPinnedStatusById(localStorage.token, id); + + if (res) { + await pinnedChats.set(await getPinnedChatList(localStorage.token)); + initChatList(); + } + } + }} name={$i18n.t('Pinned')} >
@@ -557,17 +573,36 @@
{/if} -
- {#if $chats} - {#each $chats as chat, idx} - {#if idx === 0 || (idx > 0 && chat.time_range !== $chats[idx - 1].time_range)} -
- {$i18n.t(chat.time_range)} - -
- {/if} +
+ {/if} - { - selectedChatId = chat.id; - }} - on:unselect={() => { - selectedChatId = null; - }} - on:delete={(e) => { - if ((e?.detail ?? '') === 'shift') { - deleteChatHandler(chat.id); - } else { - deleteChat = chat; - showDeleteConfirm = true; - } - }} - on:tag={(e) => { - const { type, name } = e.detail; - tagEventHandler(type, name, chat.id); - }} - /> - {/each} + { + selectedChatId = chat.id; + }} + on:unselect={() => { + selectedChatId = null; + }} + on:delete={(e) => { + if ((e?.detail ?? '') === 'shift') { + deleteChatHandler(chat.id); + } else { + deleteChat = chat; + showDeleteConfirm = true; + } + }} + on:tag={(e) => { + const { type, name } = e.detail; + tagEventHandler(type, name, chat.id); + }} + /> + {/each} - {#if $scrollPaginationEnabled && !allChatsLoaded} - { - if (!chatListLoading) { - loadMoreChats(); - } - }} - > + {#if $scrollPaginationEnabled && !allChatsLoaded} + { + if (!chatListLoading) { + loadMoreChats(); + } + }} + > +
+ +
Loading...
+
+
+ {/if} + {:else}
Loading...
-
- {/if} - {:else} -
- -
Loading...
+ {/if}
- {/if} +
diff --git a/src/lib/components/layout/Sidebar/ChatItem.svelte b/src/lib/components/layout/Sidebar/ChatItem.svelte index 7682dd3c56..a6ce47e5f9 100644 --- a/src/lib/components/layout/Sidebar/ChatItem.svelte +++ b/src/lib/components/layout/Sidebar/ChatItem.svelte @@ -104,6 +104,14 @@ const onDragStart = (event) => { event.dataTransfer.setDragImage(dragImage, 0, 0); + // Set the data to be transferred + event.dataTransfer.setData( + 'text/plain', + JSON.stringify({ + id: chat.id + }) + ); + drag = true; itemElement.style.opacity = '0.5'; // Optional: Visual cue to show it's being dragged }; @@ -114,8 +122,8 @@ }; const onDragEnd = (event) => { - drag = false; itemElement.style.opacity = '1'; // Reset visual cue after drag + drag = false; }; onMount(() => { @@ -142,9 +150,9 @@ {#if drag && x && y} -
+
-
+
{chat.title}
@@ -169,7 +177,7 @@
{:else} Date: Tue, 15 Oct 2024 02:48:41 -0700 Subject: [PATCH 24/53] refac --- .../open_webui/apps/webui/routers/auths.py | 68 +++++++++++++++---- backend/open_webui/utils/utils.py | 5 +- src/lib/components/layout/Sidebar.svelte | 4 +- 3 files changed, 62 insertions(+), 15 deletions(-) diff --git a/backend/open_webui/apps/webui/routers/auths.py b/backend/open_webui/apps/webui/routers/auths.py index e9f94ff6a0..e9103f1d60 100644 --- a/backend/open_webui/apps/webui/routers/auths.py +++ b/backend/open_webui/apps/webui/routers/auths.py @@ -1,10 +1,13 @@ import re import uuid +import time +import datetime from open_webui.apps.webui.models.auths import ( AddUserForm, ApiKey, Auths, + Token, SigninForm, SigninResponse, SignupForm, @@ -34,6 +37,7 @@ from open_webui.utils.utils import ( get_password_hash, ) from open_webui.utils.webhook import post_webhook +from typing import Optional router = APIRouter() @@ -42,25 +46,42 @@ router = APIRouter() ############################ -@router.get("/", response_model=UserResponse) +class SessionUserResponse(Token, UserResponse): + expires_at: Optional[int] = None + + +@router.get("/", response_model=SessionUserResponse) async def get_session_user( request: Request, response: Response, user=Depends(get_current_user) ): + expires_delta = parse_duration(request.app.state.config.JWT_EXPIRES_IN) + expires_at = None + if expires_delta: + expires_at = int(time.time()) + int(expires_delta.total_seconds()) + token = create_token( data={"id": user.id}, - expires_delta=parse_duration(request.app.state.config.JWT_EXPIRES_IN), + expires_delta=expires_delta, + ) + + datetime_expires_at = datetime.datetime.fromtimestamp( + expires_at, datetime.timezone.utc ) # Set the cookie token response.set_cookie( key="token", value=token, + expires=datetime_expires_at, httponly=True, # Ensures the cookie is not accessible via JavaScript - samesite=WEBUI_SESSION_COOKIE_SAME_SITE, - secure=WEBUI_SESSION_COOKIE_SECURE, + samesite=WEBUI_SESSION_COOKIE_SAME_SITE, + secure=WEBUI_SESSION_COOKIE_SECURE, ) return { + "token": token, + "token_type": "Bearer", + "expires_at": expires_at, "id": user.id, "email": user.email, "name": user.name, @@ -119,7 +140,7 @@ async def update_password( ############################ -@router.post("/signin", response_model=SigninResponse) +@router.post("/signin", response_model=SessionUserResponse) async def signin(request: Request, response: Response, form_data: SigninForm): if WEBUI_AUTH_TRUSTED_EMAIL_HEADER: if WEBUI_AUTH_TRUSTED_EMAIL_HEADER not in request.headers: @@ -161,23 +182,35 @@ async def signin(request: Request, response: Response, form_data: SigninForm): user = Auths.authenticate_user(form_data.email.lower(), form_data.password) if user: + + expires_delta = parse_duration(request.app.state.config.JWT_EXPIRES_IN) + expires_at = None + if expires_delta: + expires_at = int(time.time()) + int(expires_delta.total_seconds()) + token = create_token( data={"id": user.id}, - expires_delta=parse_duration(request.app.state.config.JWT_EXPIRES_IN), + expires_delta=expires_delta, + ) + + datetime_expires_at = datetime.datetime.fromtimestamp( + expires_at, datetime.timezone.utc ) # Set the cookie token response.set_cookie( key="token", value=token, + expires=datetime_expires_at, httponly=True, # Ensures the cookie is not accessible via JavaScript - samesite=WEBUI_SESSION_COOKIE_SAME_SITE, - secure=WEBUI_SESSION_COOKIE_SECURE, + samesite=WEBUI_SESSION_COOKIE_SAME_SITE, + secure=WEBUI_SESSION_COOKIE_SECURE, ) return { "token": token, "token_type": "Bearer", + "expires_at": expires_at, "id": user.id, "email": user.email, "name": user.name, @@ -193,7 +226,7 @@ async def signin(request: Request, response: Response, form_data: SigninForm): ############################ -@router.post("/signup", response_model=SigninResponse) +@router.post("/signup", response_model=SessionUserResponse) async def signup(request: Request, response: Response, form_data: SignupForm): if WEBUI_AUTH: if ( @@ -233,18 +266,28 @@ async def signup(request: Request, response: Response, form_data: SignupForm): ) if user: + expires_delta = parse_duration(request.app.state.config.JWT_EXPIRES_IN) + expires_at = None + if expires_delta: + expires_at = int(time.time()) + int(expires_delta.total_seconds()) + token = create_token( data={"id": user.id}, - expires_delta=parse_duration(request.app.state.config.JWT_EXPIRES_IN), + expires_delta=expires_delta, + ) + + datetime_expires_at = datetime.datetime.fromtimestamp( + expires_at, datetime.timezone.utc ) # Set the cookie token response.set_cookie( key="token", value=token, + expires=datetime_expires_at, httponly=True, # Ensures the cookie is not accessible via JavaScript - samesite=WEBUI_SESSION_COOKIE_SAME_SITE, - secure=WEBUI_SESSION_COOKIE_SECURE, + samesite=WEBUI_SESSION_COOKIE_SAME_SITE, + secure=WEBUI_SESSION_COOKIE_SECURE, ) if request.app.state.config.WEBHOOK_URL: @@ -261,6 +304,7 @@ async def signup(request: Request, response: Response, form_data: SignupForm): return { "token": token, "token_type": "Bearer", + "expires_at": expires_at, "id": user.id, "email": user.email, "name": user.name, diff --git a/backend/open_webui/utils/utils.py b/backend/open_webui/utils/utils.py index 45a7eef305..b744ab164a 100644 --- a/backend/open_webui/utils/utils.py +++ b/backend/open_webui/utils/utils.py @@ -7,7 +7,7 @@ import jwt from open_webui.apps.webui.models.users import Users from open_webui.constants import ERROR_MESSAGES from open_webui.env import WEBUI_SECRET_KEY -from fastapi import Depends, HTTPException, Request, status +from fastapi import Depends, HTTPException, Request, Response, status from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer from passlib.context import CryptContext @@ -73,6 +73,7 @@ def get_http_authorization_cred(auth_header: str): def get_current_user( request: Request, + response: Response, auth_token: HTTPAuthorizationCredentials = Depends(bearer_security), ): token = None @@ -103,6 +104,8 @@ def get_current_user( Users.update_user_last_active_by_id(user.id) return user else: + response.delete_cookie("token") + raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail=ERROR_MESSAGES.UNAUTHORIZED, diff --git a/src/lib/components/layout/Sidebar.svelte b/src/lib/components/layout/Sidebar.svelte index c3d3103207..a4b3a123ff 100644 --- a/src/lib/components/layout/Sidebar.svelte +++ b/src/lib/components/layout/Sidebar.svelte @@ -675,7 +675,7 @@
-
+
@@ -689,7 +689,7 @@ }} >
-
- - +
{#if $user !== undefined} Date: Tue, 15 Oct 2024 03:11:03 -0700 Subject: [PATCH 28/53] refac --- backend/open_webui/apps/webui/models/chats.py | 4 +++- src/lib/components/layout/Sidebar.svelte | 11 +---------- 2 files changed, 4 insertions(+), 11 deletions(-) diff --git a/backend/open_webui/apps/webui/models/chats.py b/backend/open_webui/apps/webui/models/chats.py index 13823e7091..12bdd1c38c 100644 --- a/backend/open_webui/apps/webui/models/chats.py +++ b/backend/open_webui/apps/webui/models/chats.py @@ -276,7 +276,9 @@ class ChatTable: limit: Optional[int] = None, ) -> list[ChatTitleIdResponse]: with get_db() as db: - query = db.query(Chat).filter_by(user_id=user_id, pinned=False) + query = db.query(Chat).filter_by(user_id=user_id) + query = query.filter(or_(Chat.pinned == False, Chat.pinned == None)) + if not include_archived: query = query.filter_by(archived=False) diff --git a/src/lib/components/layout/Sidebar.svelte b/src/lib/components/layout/Sidebar.svelte index 1140c2cbea..95fc4abe6e 100644 --- a/src/lib/components/layout/Sidebar.svelte +++ b/src/lib/components/layout/Sidebar.svelte @@ -14,25 +14,16 @@ pinnedChats, scrollPaginationEnabled, currentChatPage, - temporaryChatEnabled, - showArtifacts, - showOverview, - showControls + temporaryChatEnabled } from '$lib/stores'; import { onMount, getContext, tick, onDestroy } from 'svelte'; const i18n = getContext('i18n'); - import { updateUserSettings } from '$lib/apis/users'; import { deleteChatById, getChatList, - getChatById, - getChatListByTagName, - updateChatById, getAllTags, - archiveChatById, - cloneChatById, getChatListBySearchText, createNewChat, getPinnedChatList, From 86c961e342f59d113596e47496520d2356f70f62 Mon Sep 17 00:00:00 2001 From: "Timothy J. Baek" Date: Tue, 15 Oct 2024 03:30:02 -0700 Subject: [PATCH 29/53] fix --- backend/open_webui/utils/utils.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/backend/open_webui/utils/utils.py b/backend/open_webui/utils/utils.py index e087cb48f1..79faa1831f 100644 --- a/backend/open_webui/utils/utils.py +++ b/backend/open_webui/utils/utils.py @@ -73,7 +73,6 @@ def get_http_authorization_cred(auth_header: str): def get_current_user( request: Request, - response: Response, auth_token: HTTPAuthorizationCredentials = Depends(bearer_security), ): token = None @@ -104,8 +103,6 @@ def get_current_user( Users.update_user_last_active_by_id(user.id) return user else: - response.set_cookie("token", "", expires=0) - raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail=ERROR_MESSAGES.UNAUTHORIZED, From 69d04728984be7cd84815abce8dd6d6cf7804b8e Mon Sep 17 00:00:00 2001 From: "Timothy J. Baek" Date: Tue, 15 Oct 2024 05:00:36 -0700 Subject: [PATCH 30/53] refac: styling --- src/lib/components/common/Folder.svelte | 2 +- .../workspace/Knowledge/Collection.svelte | 32 +------------------ 2 files changed, 2 insertions(+), 32 deletions(-) diff --git a/src/lib/components/common/Folder.svelte b/src/lib/components/common/Folder.svelte index fe356c187a..5aa536ed65 100644 --- a/src/lib/components/common/Folder.svelte +++ b/src/lib/components/common/Folder.svelte @@ -78,7 +78,7 @@ - -
-
- -
-
-
-