From 83910e6fec7118caaa7a36c5f3d91234b1c0c30b Mon Sep 17 00:00:00 2001 From: Classic298 <27028174+Classic298@users.noreply.github.com> Date: Sat, 21 Jun 2025 13:54:40 +0200 Subject: [PATCH 01/26] Create e7f8a9b2c5d1_add_system_prompt_to_folder.py --- ...7f8a9b2c5d1_add_system_prompt_to_folder.py | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 backend/open_webui/migrations/versions/e7f8a9b2c5d1_add_system_prompt_to_folder.py diff --git a/backend/open_webui/migrations/versions/e7f8a9b2c5d1_add_system_prompt_to_folder.py b/backend/open_webui/migrations/versions/e7f8a9b2c5d1_add_system_prompt_to_folder.py new file mode 100644 index 000000000..44426b720 --- /dev/null +++ b/backend/open_webui/migrations/versions/e7f8a9b2c5d1_add_system_prompt_to_folder.py @@ -0,0 +1,22 @@ +"""Add system prompt to folder + +Revision ID: e7f8a9b2c5d1 +Revises: 9f0c9cd09105 +Create Date: 2025-06-21 10:00:00.000000 + +""" +from typing import Sequence, Union +from alembic import op +import sqlalchemy as sa + +# revision identifiers, used by Alembic. +revision: str = 'e7f8a9b2c5d1' +down_revision: Union[str, None] = '9f0c9cd09105' +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + +def upgrade() -> None: + op.add_column('folder', sa.Column('system_prompt', sa.Text(), nullable=True)) + +def downgrade() -> None: + op.drop_column('folder', 'system_prompt') From 9e7735e69566a4371bbe207b11f5324c0e8cbf92 Mon Sep 17 00:00:00 2001 From: Classic298 <27028174+Classic298@users.noreply.github.com> Date: Sat, 21 Jun 2025 13:54:57 +0200 Subject: [PATCH 02/26] Update chats.py --- backend/open_webui/models/chats.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/backend/open_webui/models/chats.py b/backend/open_webui/models/chats.py index 0ac53a023..891a4ee77 100644 --- a/backend/open_webui/models/chats.py +++ b/backend/open_webui/models/chats.py @@ -66,6 +66,7 @@ class ChatModel(BaseModel): class ChatForm(BaseModel): chat: dict + folder_id: Optional[str] = None class ChatImportForm(ChatForm): @@ -118,6 +119,7 @@ class ChatTable: else "New Chat" ), "chat": form_data.chat, + "folder_id": form_data.folder_id, "created_at": int(time.time()), "updated_at": int(time.time()), } From 92af07afce6607484d537454006e54e0f1cce4c6 Mon Sep 17 00:00:00 2001 From: Classic298 <27028174+Classic298@users.noreply.github.com> Date: Sat, 21 Jun 2025 13:55:20 +0200 Subject: [PATCH 03/26] Update folders.py --- backend/open_webui/models/folders.py | 47 ++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/backend/open_webui/models/folders.py b/backend/open_webui/models/folders.py index 1c97de26c..013008a3d 100644 --- a/backend/open_webui/models/folders.py +++ b/backend/open_webui/models/folders.py @@ -30,6 +30,7 @@ class Folder(Base): items = Column(JSON, nullable=True) meta = Column(JSON, nullable=True) is_expanded = Column(Boolean, default=False) + system_prompt = Column(Text, nullable=True) created_at = Column(BigInteger) updated_at = Column(BigInteger) @@ -42,6 +43,7 @@ class FolderModel(BaseModel): items: Optional[dict] = None meta: Optional[dict] = None is_expanded: bool = False + system_prompt: Optional[str] = None created_at: int updated_at: int @@ -55,6 +57,7 @@ class FolderModel(BaseModel): class FolderForm(BaseModel): name: str + system_prompt: Optional[str] = None model_config = ConfigDict(extra="allow") @@ -70,6 +73,7 @@ class FolderTable: "user_id": user_id, "name": name, "parent_id": parent_id, + "system_prompt": None, "created_at": int(time.time()), "updated_at": int(time.time()), } @@ -236,6 +240,49 @@ class FolderTable: log.error(f"update_folder: {e}") return + def update_folder_details_by_id_and_user_id( + self, + id: str, + user_id: str, + name: str, + system_prompt: Optional[str], + ) -> Optional[FolderModel]: + try: + with get_db() as db: + folder = db.query(Folder).filter_by(id=id, user_id=user_id).first() + if not folder: + return None + + # Check if a folder with the new name already exists at the same parent level + # Exclude the current folder being renamed from the check + if name != folder.name: + existing_folder = ( + db.query(Folder) + .filter( + Folder.name == name, + Folder.parent_id == folder.parent_id, + Folder.user_id == user_id, + Folder.id != id, + ) + .first() + ) + if existing_folder: + log.warning( + f"Folder with name '{name}' already exists for user_id '{user_id}' at parent_id '{folder.parent_id}'" + ) + return None + + folder.name = name + folder.system_prompt = system_prompt + folder.updated_at = int(time.time()) + db.commit() + db.refresh(folder) + return FolderModel.model_validate(folder) + except Exception as e: + log.error(f"Error updating folder details: {e}") + db.rollback() + return None + def delete_folder_by_id_and_user_id( self, id: str, user_id: str, delete_chats=True ) -> bool: From 93991b858a2f367292cf97f9ce23ffe904c19b5a Mon Sep 17 00:00:00 2001 From: Classic298 <27028174+Classic298@users.noreply.github.com> Date: Sat, 21 Jun 2025 13:55:36 +0200 Subject: [PATCH 04/26] Update folders.py --- backend/open_webui/routers/folders.py | 32 +++++++++++++-------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/backend/open_webui/routers/folders.py b/backend/open_webui/routers/folders.py index 2c41c9285..3e3c9399b 100644 --- a/backend/open_webui/routers/folders.py +++ b/backend/open_webui/routers/folders.py @@ -105,33 +105,33 @@ async def get_folder_by_id(id: str, user=Depends(get_verified_user)): detail=ERROR_MESSAGES.NOT_FOUND, ) - ############################ -# Update Folder Name By Id +# Update Folder Details By Id ############################ @router.post("/{id}/update") -async def update_folder_name_by_id( +async def update_folder_by_id( id: str, form_data: FolderForm, user=Depends(get_verified_user) ): folder = Folders.get_folder_by_id_and_user_id(id, user.id) if folder: - existing_folder = Folders.get_folder_by_parent_id_and_user_id_and_name( - folder.parent_id, user.id, form_data.name - ) - if existing_folder: - raise HTTPException( - status_code=status.HTTP_400_BAD_REQUEST, - detail=ERROR_MESSAGES.DEFAULT("Folder already exists"), - ) - try: - folder = Folders.update_folder_name_by_id_and_user_id( - id, user.id, form_data.name + updated_folder = Folders.update_folder_details_by_id_and_user_id( + id=id, + user_id=user.id, + name=form_data.name, + system_prompt=form_data.system_prompt, ) - - return folder + if updated_folder: + return updated_folder + else: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail=ERROR_MESSAGES.DEFAULT( + "Could not update folder. It might be due to a name conflict or invalid data." + ), + ) except Exception as e: log.exception(e) log.error(f"Error updating folder: {id}") From 4cd0119fd1f7587e6f04351d5adef255e62bf950 Mon Sep 17 00:00:00 2001 From: Classic298 <27028174+Classic298@users.noreply.github.com> Date: Sat, 21 Jun 2025 13:55:52 +0200 Subject: [PATCH 05/26] Update chat.py --- backend/open_webui/utils/chat.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/backend/open_webui/utils/chat.py b/backend/open_webui/utils/chat.py index 268c910e3..e84b46e16 100644 --- a/backend/open_webui/utils/chat.py +++ b/backend/open_webui/utils/chat.py @@ -15,6 +15,8 @@ from starlette.responses import Response, StreamingResponse, JSONResponse from open_webui.models.users import UserModel +from open_webui.models.chats import Chats +from open_webui.models.folders import Folders from open_webui.socket.main import ( sio, @@ -165,6 +167,29 @@ async def generate_chat_completion( bypass_filter: bool = False, ): log.debug(f"generate_chat_completion: {form_data}") + metadata = form_data.get("metadata", {}) + if hasattr(request.state, "metadata"): + metadata.update(request.state.metadata) + form_data["metadata"] = metadata + + chat_id = metadata.get("chat_id") + if chat_id and user: + chat = Chats.get_chat_by_id(chat_id) + if chat and chat.folder_id: + folder = Folders.get_folder_by_id_and_user_id(chat.folder_id, user.id) + if folder and folder.system_prompt: + folder_system_message = { + "role": "system", + "content": folder.system_prompt, + } + # Ensure messages list exists + if "messages" not in form_data: + form_data["messages"] = [] + + form_data["messages"] = [folder_system_message] + form_data["messages"] + log.debug( + f"Prepended system prompt from folder {folder.id} to chat {chat_id}" + ) if BYPASS_MODEL_ACCESS_CONTROL: bypass_filter = True From 05ffb9363f3130832a2bad156aa5f55d683a4b01 Mon Sep 17 00:00:00 2001 From: Classic298 <27028174+Classic298@users.noreply.github.com> Date: Sat, 21 Jun 2025 13:56:09 +0200 Subject: [PATCH 06/26] Update index.ts --- src/lib/apis/chats/index.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/lib/apis/chats/index.ts b/src/lib/apis/chats/index.ts index 9d24b3971..f3a1c1999 100644 --- a/src/lib/apis/chats/index.ts +++ b/src/lib/apis/chats/index.ts @@ -4,6 +4,8 @@ import { getTimeRange } from '$lib/utils'; export const createNewChat = async (token: string, chat: object) => { let error = null; + const { folder_id, ...chatData } = chat as any; + const res = await fetch(`${WEBUI_API_BASE_URL}/chats/new`, { method: 'POST', headers: { @@ -12,7 +14,8 @@ export const createNewChat = async (token: string, chat: object) => { authorization: `Bearer ${token}` }, body: JSON.stringify({ - chat: chat + chat: chatData, + ...(folder_id && { folder_id: folder_id }) }) }) .then(async (res) => { From e97bd4733655d7549f2ac99236d2b3d79de8f110 Mon Sep 17 00:00:00 2001 From: Classic298 <27028174+Classic298@users.noreply.github.com> Date: Sat, 21 Jun 2025 13:56:22 +0200 Subject: [PATCH 07/26] Update index.ts --- src/lib/apis/folders/index.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/lib/apis/folders/index.ts b/src/lib/apis/folders/index.ts index 21ec426b0..22109451a 100644 --- a/src/lib/apis/folders/index.ts +++ b/src/lib/apis/folders/index.ts @@ -92,7 +92,11 @@ export const getFolderById = async (token: string, id: string) => { return res; }; -export const updateFolderNameById = async (token: string, id: string, name: string) => { +export const updateFolderById = async ( + token: string, + id: string, + folderData: { name: string; system_prompt?: string } +) => { let error = null; const res = await fetch(`${WEBUI_API_BASE_URL}/folders/${id}/update`, { @@ -102,9 +106,7 @@ export const updateFolderNameById = async (token: string, id: string, name: stri 'Content-Type': 'application/json', authorization: `Bearer ${token}` }, - body: JSON.stringify({ - name: name - }) + body: JSON.stringify(folderData) }) .then(async (res) => { if (!res.ok) throw await res.json(); From 62ff79d350acd96736adc5ea9967d231231b7de2 Mon Sep 17 00:00:00 2001 From: Classic298 <27028174+Classic298@users.noreply.github.com> Date: Sat, 21 Jun 2025 13:56:41 +0200 Subject: [PATCH 08/26] Update Chat.svelte --- src/lib/components/chat/Chat.svelte | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/src/lib/components/chat/Chat.svelte b/src/lib/components/chat/Chat.svelte index 7d5659479..765c3a1db 100644 --- a/src/lib/components/chat/Chat.svelte +++ b/src/lib/components/chat/Chat.svelte @@ -36,7 +36,11 @@ chatTitle, showArtifacts, tools, - toolServers + toolServers, + pendingFolderId, + pendingFolderName, + folders, + refreshSidebar } from '$lib/stores'; import { convertMessagesToHistory, @@ -65,6 +69,7 @@ getTagsById, updateChatById } from '$lib/apis/chats'; + import { getFolders } from '$lib/apis/folders'; import { generateOpenAIChatCompletion } from '$lib/apis/openai'; import { processWeb, processWebSearch, processYoutubeVideo } from '$lib/apis/retrieval'; import { createOpenAITextStream } from '$lib/apis/streaming'; @@ -308,11 +313,9 @@ } } else if (type === 'chat:title') { chatTitle.set(data); - currentChatPage.set(1); - await chats.set(await getChatList(localStorage.token, $currentChatPage)); + await $refreshSidebar(); } else if (type === 'chat:tags') { - chat = await getChatById(localStorage.token, $chatId); - allTags.set(await getAllTags(localStorage.token)); + await $refreshSidebar(); } else if (type === 'source' || type === 'citation') { if (data?.type === 'code_execution') { // Code execution; update existing code execution by ID, or add new one. @@ -708,7 +711,8 @@ ////////////////////////// const initNewChat = async () => { - const availableModels = $models + + const availableModels = $models .filter((m) => !(m?.info?.meta?.hidden ?? false)) .map((m) => m.id); @@ -1958,14 +1962,17 @@ history: history, messages: createMessagesList(history, history.currentId), tags: [], + folder_id: $pendingFolderId, timestamp: Date.now() }); _chatId = chat.id; await chatId.set(_chatId); - - await chats.set(await getChatList(localStorage.token, $currentChatPage)); - currentChatPage.set(1); + + // Clear the pending folder after creating the chat + $refreshSidebar(); + pendingFolderId.set(null); + pendingFolderName.set(null); window.history.replaceState(history.state, '', `/c/${_chatId}`); } else { From c6fc09b6c3996e92a1e896f1b190ba816aedff89 Mon Sep 17 00:00:00 2001 From: Classic298 <27028174+Classic298@users.noreply.github.com> Date: Sat, 21 Jun 2025 13:56:59 +0200 Subject: [PATCH 09/26] Update ChatPlaceholder.svelte --- .../components/chat/ChatPlaceholder.svelte | 25 ++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/src/lib/components/chat/ChatPlaceholder.svelte b/src/lib/components/chat/ChatPlaceholder.svelte index ee39149e8..463ad9a4d 100644 --- a/src/lib/components/chat/ChatPlaceholder.svelte +++ b/src/lib/components/chat/ChatPlaceholder.svelte @@ -2,7 +2,9 @@ import { WEBUI_BASE_URL } from '$lib/constants'; import { marked } from 'marked'; - import { config, user, models as _models, temporaryChatEnabled } from '$lib/stores'; + import { config, user, models as _models, temporaryChatEnabled, pendingFolderId, pendingFolderName } from '$lib/stores'; + import FolderOpen from '$lib/components/icons/FolderOpen.svelte'; + import XMark from '$lib/components/icons/XMark.svelte'; import { onMount, getContext } from 'svelte'; import { blur, fade } from 'svelte/transition'; @@ -78,6 +80,27 @@ {/if} + {#if $pendingFolderName} +