From 409fb39717be9ab7becd9e8c01801a08c5bae318 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Tue, 21 Jul 2026 13:53:30 -0400 Subject: [PATCH 01/29] refac --- backend/open_webui/models/chats.py | 22 ++- backend/open_webui/routers/folders.py | 22 ++- src/lib/apis/folders/index.ts | 39 ++++-- src/lib/components/chat/Chat.svelte | 2 +- .../chat/Placeholder/ChatList.svelte | 129 ++++++++++++------ .../chat/Placeholder/FolderPlaceholder.svelte | 72 +++++++--- .../layout/Sidebar/RecursiveFolder.svelte | 78 +++++++++-- .../layout/Sidebar/SharedFolderItem.svelte | 62 +++++++-- .../(app)/folders/[folderId]/+page.svelte | 46 +++++++ 9 files changed, 374 insertions(+), 98 deletions(-) create mode 100644 src/routes/(app)/folders/[folderId]/+page.svelte diff --git a/backend/open_webui/models/chats.py b/backend/open_webui/models/chats.py index c20c1db247..1d959f0bc6 100644 --- a/backend/open_webui/models/chats.py +++ b/backend/open_webui/models/chats.py @@ -1722,17 +1722,21 @@ class ChatTable: folder_id: str, skip: int = 0, limit: int = 60, + sort_by: str = 'updated_at', + sort_dir: str = 'desc', db: AsyncSession | None = None, ) -> list[dict]: """Get chats in a folder across ALL users. Returns dicts with user_id.""" async with get_async_db_context(db) as session: + sort_column = Chat.title if sort_by == 'title' else Chat.updated_at + order_clause = sort_column.asc() if sort_dir == 'asc' else sort_column.desc() stmt = ( select(Chat.id, Chat.title, Chat.user_id, Chat.updated_at, Chat.created_at, Chat.last_read_at) .filter_by(folder_id=folder_id) .filter(or_(Chat.pinned == False, Chat.pinned == None)) .filter_by(archived=False) .where(Chat.meta['internal'].as_boolean().is_not(True)) - .order_by(Chat.updated_at.desc(), Chat.id) + .order_by(order_clause, Chat.id) ) if skip: @@ -1754,6 +1758,22 @@ class ChatTable: for chat in all_chats ] + async def count_all_chats_by_folder_id( + self, + folder_id: str, + db: AsyncSession | None = None, + ) -> int: + async with get_async_db_context(db) as session: + stmt = ( + select(func.count(Chat.id)) + .filter_by(folder_id=folder_id) + .filter(or_(Chat.pinned == False, Chat.pinned == None)) + .filter_by(archived=False) + .where(Chat.meta['internal'].as_boolean().is_not(True)) + ) + result = await session.execute(stmt) + return result.scalar_one() + async def get_chats_by_folder_ids_and_user_id( self, folder_ids: list[str], user_id: str, db: AsyncSession | None = None ) -> list[ChatModel]: diff --git a/backend/open_webui/routers/folders.py b/backend/open_webui/routers/folders.py index a4ea4b8961..e3ebbfd96f 100644 --- a/backend/open_webui/routers/folders.py +++ b/backend/open_webui/routers/folders.py @@ -6,7 +6,7 @@ import uuid from pathlib import Path from typing import Optional -from fastapi import APIRouter, Depends, File, HTTPException, Request, UploadFile, status +from fastapi import APIRouter, Depends, File, HTTPException, Query, Request, UploadFile, status from fastapi.responses import FileResponse, StreamingResponse from open_webui.config import UPLOAD_DIR from open_webui.constants import ERROR_MESSAGES @@ -482,6 +482,9 @@ async def update_folder_access_by_id( async def get_shared_folder_chats( request: Request, id: str, + page: int | None = Query(None, ge=1), + sort_by: str = Query('updated_at'), + sort_dir: str = Query('desc'), user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session), ): @@ -505,7 +508,17 @@ async def get_shared_folder_chats( detail=ERROR_MESSAGES.ACCESS_PROHIBITED, ) - chats = await Chats.get_all_chats_by_folder_id(id, db=db) + limit = 10 + skip = (page - 1) * limit if page is not None else 0 + chats = await Chats.get_all_chats_by_folder_id( + id, + skip=skip, + limit=limit if page is not None else 60, + sort_by=sort_by, + sort_dir=sort_dir, + db=db, + ) + total = await Chats.count_all_chats_by_folder_id(id, db=db) if page is not None else len(chats) # Resolve owner names for display (avatar URLs are constructed client-side) owner_cache: dict[str, str] = {} @@ -516,10 +529,13 @@ async def get_shared_folder_chats( owner_cache[uid] = u.name if u else 'Unknown' chat['owner_name'] = owner_cache[uid] - return { + response = { 'chats': [{**chat, 'readonly': chat['user_id'] != user.id} for chat in chats], 'folder_permission': 'write' if has_write else 'read', } + if page is not None: + response.update({'total': total, 'has_more': skip + limit < total}) + return response ############################ diff --git a/src/lib/apis/folders/index.ts b/src/lib/apis/folders/index.ts index b79e588947..d82f591289 100644 --- a/src/lib/apis/folders/index.ts +++ b/src/lib/apis/folders/index.ts @@ -290,17 +290,40 @@ export const getSharedFolders = async (token: string) => { return res; }; -export const getSharedFolderChats = async (token: string, folderId: string) => { +export const getSharedFolderChats = async ( + token: string, + folderId: string, + params: { + page?: number | null; + sortBy?: 'title' | 'updated_at'; + sortDir?: 'asc' | 'desc'; + } = {} +) => { let error = null; - const res = await fetch(`${WEBUI_API_BASE_URL}/folders/${folderId}/shared/chats`, { - method: 'GET', - headers: { - Accept: 'application/json', - 'Content-Type': 'application/json', - authorization: `Bearer ${token}` + const searchParams = new URLSearchParams(); + if (params.page !== undefined && params.page !== null) { + searchParams.append('page', `${params.page}`); + } + if (params.sortBy) { + searchParams.append('sort_by', params.sortBy); + } + if (params.sortDir) { + searchParams.append('sort_dir', params.sortDir); + } + const query = searchParams.toString(); + + const res = await fetch( + `${WEBUI_API_BASE_URL}/folders/${folderId}/shared/chats${query ? `?${query}` : ''}`, + { + method: 'GET', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + authorization: `Bearer ${token}` + } } - }) + ) .then(async (res) => { if (!res.ok) throw await res.json(); return res.json(); diff --git a/src/lib/components/chat/Chat.svelte b/src/lib/components/chat/Chat.svelte index e77e296129..08bde0bc9f 100644 --- a/src/lib/components/chat/Chat.svelte +++ b/src/lib/components/chat/Chat.svelte @@ -1177,7 +1177,7 @@ } const pageSubscribe = page.subscribe(async (p) => { - if (p.url.pathname === '/') { + if (p.url.pathname === '/' || p.url.pathname.startsWith('/folders/')) { await tick(); initNewChat(); } diff --git a/src/lib/components/chat/Placeholder/ChatList.svelte b/src/lib/components/chat/Placeholder/ChatList.svelte index 64a9ed215d..77bd5ae4ab 100644 --- a/src/lib/components/chat/Placeholder/ChatList.svelte +++ b/src/lib/components/chat/Placeholder/ChatList.svelte @@ -1,27 +1,35 @@ {#if chatList} @@ -153,7 +170,6 @@ class=" w-full flex justify-between items-center rounded-lg text-sm py-2 px-3 hover:bg-gray-50 dark:hover:bg-gray-850" draggable="false" href={`/c/${chat.id}`} - on:click={() => (show = false)} >
{chat?.title} @@ -177,19 +193,48 @@ {/each} - {#if !allChatsLoaded && loadHandler} - { - if (!chatListLoading) { - loadHandler(); - } - }} - > -
- -
Loading...
-
-
+ {#if totalPages > 1} +
+ + + {#each pages as item, index (item === 'ellipsis' ? `ellipsis-${index}` : item)} + {#if item === 'ellipsis'} + + ... + + {:else} + + {/if} + {/each} + + +
{/if}
{/if} diff --git a/src/lib/components/chat/Placeholder/FolderPlaceholder.svelte b/src/lib/components/chat/Placeholder/FolderPlaceholder.svelte index d7cbc6b400..c943f57ee0 100644 --- a/src/lib/components/chat/Placeholder/FolderPlaceholder.svelte +++ b/src/lib/components/chat/Placeholder/FolderPlaceholder.svelte @@ -11,18 +11,21 @@ import ChatList from './ChatList.svelte'; import FolderKnowledge from './FolderKnowledge.svelte'; import Spinner from '$lib/components/common/Spinner.svelte'; - import { getChatListByFolderId } from '$lib/apis/chats'; import { getSharedFolderChats } from '$lib/apis/folders'; export let folder: any = null; let selectedTab = 'chats'; + const CHATS_PAGE_SIZE = 10; let page = 1; + let totalChats = 0; + let orderBy: 'title' | 'updated_at' = 'updated_at'; + let direction: 'asc' | 'desc' = 'desc'; + let currentFolderId: string | null = null; let chats: any[] | null = null; let chatListLoading = false; - let allChatsLoaded = false; $: showOwnerInfo = Boolean( folder?.shared || @@ -30,42 +33,68 @@ (folder?.access_grants?.length ?? 0) > 0 ); - const loadChats = async () => { - // getSharedFolderChats returns all users' chats in one shot; no pagination - allChatsLoaded = true; + const setSortKey = (key: 'title' | 'updated_at') => { + if (orderBy === key) { + direction = direction === 'asc' ? 'desc' : 'asc'; + } else { + orderBy = key; + direction = key === 'title' ? 'asc' : 'desc'; + } + page = 1; + setChatList(); + }; + + const setPage = (nextPage: number) => { + if (nextPage === page || chatListLoading) { + return; + } + + page = nextPage; + setChatList(); }; const setChatList = async () => { + const folderId = folder?.id; chats = null; - page = 1; - allChatsLoaded = false; - chatListLoading = false; - if (folder && folder.id) { + if (folderId) { // Always use the shared folder endpoint so owners also see // chats created by users who have write access to this folder. - const res = await getSharedFolderChats(localStorage.token, folder.id).catch((error) => { + chatListLoading = true; + const res = await getSharedFolderChats(localStorage.token, folderId, { + page, + sortBy: orderBy, + sortDir: direction + }).catch((error) => { console.error(error); return null; }); + chatListLoading = false; + if (res && res.chats) { chats = res.chats; - allChatsLoaded = true; + totalChats = res.total ?? res.chats.length; } else { - // Fallback to regular API (e.g. if user has no shared access) - const fallback = await getChatListByFolderId(localStorage.token, folder.id, page).catch( - () => [] - ); - chats = fallback || []; + chats = []; + totalChats = 0; } } else { chats = []; + totalChats = 0; } }; - $: if (folder) { + $: if (folder?.id && folder.id !== currentFolderId) { + currentFolderId = folder.id; + page = 1; setChatList(); } + + $: if (!folder?.id && currentFolderId !== null) { + currentFolderId = null; + chats = []; + totalChats = 0; + }
@@ -105,9 +134,14 @@ {:else}
diff --git a/src/lib/components/layout/Sidebar/RecursiveFolder.svelte b/src/lib/components/layout/Sidebar/RecursiveFolder.svelte index c13058200c..f971a93a9c 100644 --- a/src/lib/components/layout/Sidebar/RecursiveFolder.svelte +++ b/src/lib/components/layout/Sidebar/RecursiveFolder.svelte @@ -44,7 +44,6 @@ import DeleteConfirmDialog from '$lib/components/common/ConfirmDialog.svelte'; import FolderModal from './Folders/FolderModal.svelte'; import Emoji from '$lib/components/common/Emoji.svelte'; - import Spinner from '$lib/components/common/Spinner.svelte'; export let folderRegistry = {}; export let open = false; @@ -385,24 +384,46 @@ }, 500); }; + const SIDEBAR_CHATS_PAGE_SIZE = 10; + /** @type {any[] | null} */ let chats = null; - export const setFolderItems = async () => { + let chatsPage = 1; + let hasMoreChats = false; + let chatsLoading = false; + + export const setFolderItems = async (append = false) => { await tick(); - if (open) { + if (open && !chatsLoading) { // Always use getSharedFolderChats so owners also see chats // created by users who have write access to this folder. + const nextPage = append ? chatsPage + 1 : 1; + chatsLoading = true; try { - const res = await getSharedFolderChats(localStorage.token, folderId); - chats = res?.chats ?? []; + const res = await getSharedFolderChats(localStorage.token, folderId, { + page: nextPage + }); + const nextChats = res?.chats ?? []; + chats = append ? [...(chats ?? []), ...nextChats] : nextChats; + chatsPage = nextPage; + hasMoreChats = res?.has_more ?? nextChats.length === SIDEBAR_CHATS_PAGE_SIZE; } catch (error) { // Fallback to regular API - chats = await getChatListByFolderId(localStorage.token, folderId).catch((error) => { - toast.error(`${error}`); - return []; - }); + const fallback = await getChatListByFolderId(localStorage.token, folderId, nextPage).catch( + (error) => { + toast.error(`${error}`); + return []; + } + ); + chats = append ? [...(chats ?? []), ...(fallback ?? [])] : (fallback ?? []); + chatsPage = nextPage; + hasMoreChats = (fallback?.length ?? 0) === SIDEBAR_CHATS_PAGE_SIZE; + } finally { + chatsLoading = false; } } else { chats = null; + chatsPage = 1; + hasMoreChats = false; } }; @@ -564,7 +585,7 @@ await selectedFolder.set({ ...folders[folderId], ...folder }); } - await goto('/'); + await goto(`/folders/${folderId}`); if ($mobile) { showSidebar.set(!$showSidebar); @@ -678,7 +699,7 @@
- {#if (folders[folderId]?.childrenIds ?? []).length > 0 || (chats ?? []).length > 0} + {#if (folders[folderId]?.childrenIds ?? []).length > 0 || (chats ?? []).length > 0 || hasMoreChats}
@@ -731,12 +752,41 @@ }} /> {/each} + + {#if hasMoreChats} + + {/if}
{/if} - {#if chats === null} -
- + {#if chats === null && chatsLoading} +
+ + +
{/if}
diff --git a/src/lib/components/layout/Sidebar/SharedFolderItem.svelte b/src/lib/components/layout/Sidebar/SharedFolderItem.svelte index ba74ffbba4..98a92f9042 100644 --- a/src/lib/components/layout/Sidebar/SharedFolderItem.svelte +++ b/src/lib/components/layout/Sidebar/SharedFolderItem.svelte @@ -1,6 +1,8 @@ + +{#if ready} + +{:else} +
+ +
+{/if} From 9ca8cf528af1c49da3f0a2bc3c6ca95c1dedbcf5 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Tue, 21 Jul 2026 14:16:51 -0400 Subject: [PATCH 02/29] refac --- .../components/admin/Settings/Models.svelte | 48 +-- .../Settings/Models/ModelDefaultsPanel.svelte | 242 ++++++++++++++ .../Settings/Models/ModelSettingsModal.svelte | 295 ------------------ 3 files changed, 271 insertions(+), 314 deletions(-) create mode 100644 src/lib/components/admin/Settings/Models/ModelDefaultsPanel.svelte delete mode 100644 src/lib/components/admin/Settings/Models/ModelSettingsModal.svelte diff --git a/src/lib/components/admin/Settings/Models.svelte b/src/lib/components/admin/Settings/Models.svelte index 7bd287e1ea..b3bc8b677a 100644 --- a/src/lib/components/admin/Settings/Models.svelte +++ b/src/lib/components/admin/Settings/Models.svelte @@ -33,8 +33,8 @@ import ModelEditor from '$lib/components/workspace/Models/ModelEditor.svelte'; import { toast } from 'svelte-sonner'; import ConfirmDialog from '$lib/components/common/ConfirmDialog.svelte'; - import ModelSettingsModal from './Models/ModelSettingsModal.svelte'; import ManageModelsModal from './Models/ManageModelsModal.svelte'; + import ModelDefaultsPanel from './Models/ModelDefaultsPanel.svelte'; import ModelMenu from '$lib/components/admin/Settings/Models/ModelMenu.svelte'; import EllipsisHorizontal from '$lib/components/icons/EllipsisHorizontal.svelte'; import EyeSlash from '$lib/components/icons/EyeSlash.svelte'; @@ -47,7 +47,6 @@ import Download from '$lib/components/icons/Download.svelte'; import EllipsisVertical from '$lib/components/icons/EllipsisVertical.svelte'; import Wrench from '$lib/components/icons/Wrench.svelte'; - import SettingsIcon from '$lib/components/icons/Settings.svelte'; import { WEBUI_API_BASE_URL } from '$lib/constants'; import { goto } from '$app/navigation'; @@ -83,11 +82,13 @@ let filteredModels = []; let selectedModelId = null; - let showConfigModal = false; let showManageModal = false; let showResetModal = false; let savingModelOrder = false; + let savingModelsSettings = false; let modelOrderDirty = false; + let modelDefaultsPanel = null; + let modelDefaultsDirty = false; let viewOption = ''; // '' = All, 'enabled', 'disabled', 'visible', 'hidden' let tags: string[] = []; @@ -322,6 +323,20 @@ savingModelOrder = false; }; + const saveModelsSettings = async () => { + savingModelsSettings = true; + + if (modelOrderDirty) { + await saveModelOrder(modelOrderList); + } + + if (modelDefaultsDirty) { + await modelDefaultsPanel?.save(); + } + + savingModelsSettings = false; + }; + const saveModelDefaults = async ( nextDefaultModelIds: string[], nextDefaultPinnedModelIds: string[], @@ -649,7 +664,6 @@ }} /> - {#if models !== null} @@ -662,18 +676,6 @@ {filteredModels.length} - - - -
{#if $user?.role === 'admin'} @@ -714,6 +716,12 @@ {/if}
+ +
@@ -1105,13 +1113,15 @@ + + +
+ + {#if expanded} + {#if loading} +
{$i18n.t('Loading...')}
+ {:else} +
+
+ + + {#if showCapabilities} +
+ + + {#if availableFeatures.length > 0} +
+ +
+ {/if} + + {#if defaultCapabilities.builtin_tools} +
+ +
+ {/if} +
+ {/if} +
+ +
+ + + {#if showParameters} +
+ +
+ {/if} +
+ +
+ + + {#if showPromptSuggestions} +
+ +
+ {/if} +
+
+ {/if} + {/if} +
diff --git a/src/lib/components/admin/Settings/Models/ModelSettingsModal.svelte b/src/lib/components/admin/Settings/Models/ModelSettingsModal.svelte deleted file mode 100644 index df1caaeea0..0000000000 --- a/src/lib/components/admin/Settings/Models/ModelSettingsModal.svelte +++ /dev/null @@ -1,295 +0,0 @@ - - - -
-
-
- {$i18n.t('Settings')} -
- -
- -
-
- {#if config} -
{ - submitHandler(); - }} - > -
-
- -
- -
-
-
- - - {#if showDefaultPromptSuggestions} -
- - - {#if promptSuggestions.length > 0} -
- {$i18n.t( - 'Adjusting these settings will apply changes universally to all users.' - )} -
- {/if} -
- {/if} -
- -
- -
- - - {#if showDefaultCapabilities} -
- - - {#if Object.keys(defaultCapabilities).filter((key) => defaultCapabilities[key]).length > 0} - {@const availableFeatures = Object.entries(defaultCapabilities) - .filter( - ([key, value]) => - value && - ['web_search', 'code_interpreter', 'image_generation'].includes(key) - ) - .map(([key, value]) => key)} - - {#if availableFeatures.length > 0} -
- -
- {/if} - {/if} - - {#if defaultCapabilities.builtin_tools} -
- -
- {/if} -
- {/if} -
- -
- -
- - - {#if showDefaultParams} -
- -
- {/if} -
-
- -
- -
-
-
-
- {:else} -
- -
- {/if} -
-
-
-
From c4efa81d08c425678c810c51b4d62716e1e57117 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Tue, 21 Jul 2026 14:18:41 -0400 Subject: [PATCH 03/29] refac --- .../components/admin/Settings/Models.svelte | 78 +++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/src/lib/components/admin/Settings/Models.svelte b/src/lib/components/admin/Settings/Models.svelte index b3bc8b677a..6fc41df487 100644 --- a/src/lib/components/admin/Settings/Models.svelte +++ b/src/lib/components/admin/Settings/Models.svelte @@ -40,6 +40,7 @@ import EyeSlash from '$lib/components/icons/EyeSlash.svelte'; import Eye from '$lib/components/icons/Eye.svelte'; import ChevronDown from '$lib/components/icons/ChevronDown.svelte'; + import Check from '$lib/components/icons/Check.svelte'; import CheckCircle from '$lib/components/icons/CheckCircle.svelte'; import GarbageBin from '$lib/components/icons/GarbageBin.svelte'; import Minus from '$lib/components/icons/Minus.svelte'; @@ -47,6 +48,10 @@ import Download from '$lib/components/icons/Download.svelte'; import EllipsisVertical from '$lib/components/icons/EllipsisVertical.svelte'; import Wrench from '$lib/components/icons/Wrench.svelte'; + import Pin from '$lib/components/icons/Pin.svelte'; + import PinSlash from '$lib/components/icons/PinSlash.svelte'; + import GlobeAlt from '$lib/components/icons/GlobeAlt.svelte'; + import LockClosed from '$lib/components/icons/LockClosed.svelte'; import { WEBUI_API_BASE_URL } from '$lib/constants'; import { goto } from '$app/navigation'; @@ -1013,6 +1018,79 @@ {/if} + + + + + + + + + + + + {/if} {:else if item.data.id === 'fork'} - + diff --git a/src/lib/components/chat/Messages.svelte b/src/lib/components/chat/Messages.svelte index 9322175b7c..0adb4a027a 100644 --- a/src/lib/components/chat/Messages.svelte +++ b/src/lib/components/chat/Messages.svelte @@ -40,6 +40,7 @@ export let showMessage: Function = () => {}; export let submitMessage: Function = () => {}; export let addMessages: Function = () => {}; + export let forkHandler: Function | null = null; export let readOnly = false; export let editCodeBlock = true; @@ -539,6 +540,7 @@ {continueResponse} {mergeResponses} {addMessages} + {forkHandler} {triggerScroll} {readOnly} {editCodeBlock} diff --git a/src/lib/components/chat/Messages/Message.svelte b/src/lib/components/chat/Messages/Message.svelte index 5fb9a399da..0b18ca2d9f 100644 --- a/src/lib/components/chat/Messages/Message.svelte +++ b/src/lib/components/chat/Messages/Message.svelte @@ -39,6 +39,7 @@ export let mergeResponses; export let addMessages; + export let forkHandler: Function | null = null; export let triggerScroll; export let readOnly = false; export let editCodeBlock = true; @@ -97,6 +98,7 @@ {continueResponse} {regenerateResponse} {addMessages} + {forkHandler} {readOnly} {editCodeBlock} {topPadding} @@ -122,6 +124,7 @@ {mergeResponses} {triggerScroll} {addMessages} + {forkHandler} {readOnly} {editCodeBlock} {topPadding} diff --git a/src/lib/components/chat/Messages/MultiResponseMessages.svelte b/src/lib/components/chat/Messages/MultiResponseMessages.svelte index a58f6bd9a9..6223cf8338 100644 --- a/src/lib/components/chat/Messages/MultiResponseMessages.svelte +++ b/src/lib/components/chat/Messages/MultiResponseMessages.svelte @@ -45,6 +45,7 @@ export let mergeResponses: Function; export let addMessages: Function; + export let forkHandler: Function | null = null; export let triggerScroll: Function; @@ -319,6 +320,7 @@ groupedMessageIds[selectedModelIdx].messageIds.length - 1; }} {addMessages} + {forkHandler} {readOnly} {topPadding} {onInsertToNote} @@ -376,6 +378,7 @@ groupedMessageIds[modelIdx].messageIds.length - 1; }} {addMessages} + {forkHandler} {readOnly} {editCodeBlock} {topPadding} diff --git a/src/lib/components/chat/Messages/ResponseMessage.svelte b/src/lib/components/chat/Messages/ResponseMessage.svelte index 11b71eaae0..36d05bc37b 100644 --- a/src/lib/components/chat/Messages/ResponseMessage.svelte +++ b/src/lib/components/chat/Messages/ResponseMessage.svelte @@ -159,6 +159,7 @@ export let submitMessage: Function; export let continueResponse: Function; export let regenerateResponse: Function; + export let forkHandler: Function | null = null; export let addMessages: Function; @@ -1054,6 +1055,37 @@ + {#if message.done && !readOnly && forkHandler} + + + + {/if} + {#if onInsertToNote && visibleResponseContent}
- {#if !direct} -
-
- - -
- -